Martin Prikryl 22 years ago
parent
commit
8b72483783
100 changed files with 11541 additions and 7315 deletions
  1. 1 1
      Putty.bpr
  2. 8 8
      WinSCP3.bpr
  3. BIN
      WinSCP3.res
  4. 119 84
      components/UnixDirView.cpp
  5. 12 2
      components/UnixDirView.h
  6. 1 5
      core/Common.cpp
  7. 2 1
      core/Exceptions.cpp
  8. 1 1
      core/FileOperationProgress.h
  9. 1 1
      core/FileSystems.h
  10. 19 7
      core/RemoteFiles.cpp
  11. 3 1
      core/ScpFileSystem.cpp
  12. 7 2
      core/SessionData.cpp
  13. 154 26
      core/SftpFileSystem.cpp
  14. 35 5
      core/Terminal.cpp
  15. 6 1
      core/Terminal.h
  16. 12 0
      forms/CopyParams.cpp
  17. 3 0
      forms/CopyParams.h
  18. 405 44
      forms/CustomScpExplorer.cpp
  19. 4 1
      forms/CustomScpExplorer.dfm
  20. 30 2
      forms/CustomScpExplorer.h
  21. 1 1
      forms/FileSystemInfo.dfm
  22. 12 1
      forms/FullSynchronize.cpp
  23. 15 7
      forms/FullSynchronize.dfm
  24. 6 2
      forms/FullSynchronize.h
  25. 44 16
      forms/InputDlg.cpp
  26. 7 2
      forms/Login.cpp
  27. 28 7
      forms/Login.dfm
  28. 2 0
      forms/Login.h
  29. 35 4
      forms/NonVisual.cpp
  30. 147 10
      forms/NonVisual.dfm
  31. 33 1
      forms/NonVisual.h
  32. 1 1
      forms/OperationStatus.dfm
  33. 29 3
      forms/Preferences.cpp
  34. 171 103
      forms/Preferences.dfm
  35. 16 8
      forms/Preferences.h
  36. 6 2
      forms/Progress.cpp
  37. 366 58
      forms/ScpCommander.cpp
  38. 65 10
      forms/ScpCommander.dfm
  39. 42 6
      forms/ScpCommander.h
  40. 3 2
      forms/ScpExplorer.cpp
  41. 1 0
      forms/Synchronize.cpp
  42. 13 0
      forms/SynchronizeProgress.cpp
  43. 11 1
      forms/SynchronizeProgress.dfm
  44. 3 0
      forms/SynchronizeProgress.h
  45. 7 6
      general/DragDrop_B5.bpk
  46. 5 5
      general/Moje_B5.bpk
  47. 9 2
      general/drag and drop components/DragDrop.pas
  48. 35 9
      general/filemanager toolset/DirView.pas
  49. 15 5
      general/moje komponenty/NortonLikeListView.pas
  50. 101 48
      general/moje komponenty/filemanager toolset/CustomDirView.pas
  51. 4 1
      packages/dragndrop/DragDrop.hpp
  52. 6 1
      packages/filemng/DirView.hpp
  53. 1 0
      packages/my/NortonLikeListView.hpp
  54. 35 2
      packages/my/filemng/CustomDirView.hpp
  55. 1524 0
      putty/CMDGEN.C
  56. 11 7
      putty/IMPORT.C
  57. 1 1
      putty/LICENCE
  58. 6 0
      putty/MAKEFILE.BOR
  59. 6 0
      putty/MAKEFILE.CYG
  60. 523 0
      putty/MAKEFILE.LCC
  61. 6 0
      putty/MAKEFILE.VC
  62. 322 6
      putty/MKFILES.PL
  63. 46 0
      putty/MKUNXARC.SH
  64. 3 1
      putty/PAGEANT.C
  65. 8 2
      putty/PAGEANT.RC
  66. 11 0
      putty/PAGEANTC.C
  67. 6 5
      putty/PLINK.C
  68. 6 7
      putty/PORTFWD.C
  69. 1 0
      putty/PRINTING.C
  70. 12 2
      putty/PROXY.C
  71. 20 7
      putty/PSFTP.C
  72. 1 1
      putty/PUTTY.ISS
  73. 8 2
      putty/PUTTYGEN.RC
  74. 27 18
      putty/README
  75. 2 2
      putty/README.TXT
  76. 4 0
      putty/RECIPE
  77. 11 11
      putty/SCP.C
  78. 2 0
      putty/SETTINGS.C
  79. 6665 6654
      putty/SSH.C
  80. 2 0
      putty/SSH.H
  81. 2 1
      putty/SSHBN.C
  82. 13 0
      putty/SSHDSS.C
  83. 19 11
      putty/SSHPUBK.C
  84. 14 1
      putty/SSHRSA.C
  85. 5 1
      putty/SSHZLIB.C
  86. 21 18
      putty/TERMINAL.C
  87. 3 2
      putty/WINCTRLS.C
  88. 1 1
      putty/WINDLG.C
  89. 39 9
      putty/WINDOW.C
  90. 0 12
      putty/WINSTORE.C
  91. 8 2
      putty/WIN_RES.RC
  92. 2 0
      putty/putty.org.h
  93. 30 10
      release/winscpsetup.iss
  94. 3 0
      resource/TextsCore.h
  95. 6 3
      resource/TextsCore1.rc
  96. 14 1
      resource/TextsWin.h
  97. 12 0
      resource/TextsWin1.rc
  98. 11 0
      resource/TextsWin2.rc
  99. 3 0
      windows/GUIConfiguration.cpp
  100. 3 1
      windows/GUIConfiguration.h

+ 1 - 1
Putty.bpr

@@ -32,7 +32,7 @@
     <MAINSOURCE value="Putty.bpf"/>
     <INCLUDEPATH value="putty;putty\CHARSET;$(BCB)\include;$(BCB)\include\vcl"/>
     <LIBPATH value="putty\CHARSET;putty;$(BCB)\lib\obj;$(BCB)\lib"/>
-    <WARNINGS value="-w-sus -w-rvl -w-rch -w-pia -w-pck -w-pch -w-par -w-eff -w-aus"/>
+    <WARNINGS value="-w-sus -w-rvl -w-rch -w-pia -w-pck -w-pch -w-par -w-8027 -w-eff -w-aus"/>
     <LISTFILE value=""/>
     <OTHERFILES value=""/>
   </MACROS>

+ 8 - 8
WinSCP3.bpr

@@ -10,7 +10,7 @@
       windows\ScpCommander.obj windows\ScpExplorer.obj 
       windows\TerminalManager.obj windows\Tools.obj windows\UserInterface.obj 
       windows\WinConfiguration.obj windows\WinInterface.obj windows\WinMain.obj"/>
-    <RESFILES value="windows\Windows.res WinSCP3.res"/>
+    <RESFILES value="Windows.res WinSCP3.res"/>
     <DEFFILE value=""/>
     <RESDEPEN value="$(RESFILES) forms\CustomScpExplorer.dfm forms\NonVisual.dfm 
       forms\ScpCommander.dfm forms\ScpExplorer.dfm"/>
@@ -43,7 +43,7 @@
     <USERDEFINES value="OLD_DND"/>
     <SYSDEFINES value="NO_STRICT"/>
     <MAINSOURCE value="WinSCP3.cpp"/>
-    <INCLUDEPATH value="core;forms;windows;resource;components;packages\filemng;packages\dragndrop;packages\my;packages\my\filemng;$(BCB)\include;$(BCB)\include\vcl"/>
+    <INCLUDEPATH value="core;forms;windows;resource;components;dragext;packages\filemng;packages\dragndrop;packages\my;packages\my\filemng;$(BCB)\include;$(BCB)\include\vcl"/>
     <LIBPATH value="lib;windows;forms;packages\my\filemng;$(BCB)\lib\obj;$(BCB)\lib;"/>
     <WARNINGS value="-w8092 -w8091 -w8090 -w8089 -w8087 -wprc -wuse -wucp -wstv -wstu -wsig 
       -wpin -wnod -wnak -wdef -wcln -wbbf -wasm -wamp -wamb"/>
@@ -83,7 +83,7 @@
       <FILE FILENAME="lib\RScpComp.lib" FORMNAME="" UNITNAME="RScpComp.lib" CONTAINERID="LibTool" DESIGNCLASS="" LOCALCOMMAND=""/>
       <FILE FILENAME="lib\ScpCore.lib" FORMNAME="" UNITNAME="ScpCore.lib" CONTAINERID="LibTool" DESIGNCLASS="" LOCALCOMMAND=""/>
       <FILE FILENAME="lib\ScpForms.lib" FORMNAME="" UNITNAME="ScpForms.lib" CONTAINERID="LibTool" DESIGNCLASS="" LOCALCOMMAND=""/>
-      <FILE FILENAME="windows\Windows.rc" FORMNAME="" UNITNAME="Windows.rc" CONTAINERID="RC" DESIGNCLASS="" LOCALCOMMAND=""/>
+      <FILE FILENAME="windows\Windows.rc" FORMNAME="" UNITNAME="Windows.rc" CONTAINERID="RCCompiler" DESIGNCLASS="" LOCALCOMMAND=""/>
       <FILE FILENAME="WinSCP3.res" FORMNAME="" UNITNAME="WinSCP3.res" CONTAINERID="ResTool" DESIGNCLASS="" LOCALCOMMAND=""/>
   </FILELIST>
   <BUILDTOOLS>
@@ -95,8 +95,8 @@ IncludeVerInfo=1
 AutoIncBuild=1
 MajorVer=3
 MinorVer=5
-Release=0
-Build=204
+Release=5
+Build=211
 Debug=0
 PreRelease=1
 Special=0
@@ -108,13 +108,13 @@ CodePage=1252
 [Version Info Keys]
 CompanyName=Martin Prikryl
 FileDescription=Windows SCP/SFTP client
-FileVersion=3.5.0.204
+FileVersion=3.5.5.211
 InternalName=winscp3
 LegalCopyright=(c) 2000-2004 Martin Prikryl
 LegalTrademarks=
-OriginalFilename=winscp350.exe
+OriginalFilename=winscp355.exe
 ProductName=WinSCP
-ProductVersion=3.5.0.0
+ProductVersion=3.5.5.0
 WWW=http://winscp.sourceforge.net/
 
 [Compiler]

BIN
WinSCP3.res


+ 119 - 84
components/UnixDirView.cpp

@@ -101,6 +101,7 @@ __fastcall TUnixDirView::TUnixDirView(TComponent* Owner)
   FShowInaccesibleDirectories = true;
   FOnWarnLackOfTempSpace = NULL;
   FDDTotalSize = -1;
+  FOnDDTargetDrop = NULL;
   FDelayedDeletionTimer = NULL;
   FFullLoad = false;
   FDDFileList = NULL;
@@ -536,9 +537,15 @@ void __fastcall TUnixDirView::PerformItemDragDropOperation(TListItem * Item, int
   FileList = new TStringList();
   try
   {
+    AnsiString SourceDirectory;
     for (int Index = 0; Index < DragDropFilesEx->FileList->Count; Index++)
     {
-      FileList->Add(DragDropFilesEx->FileList->Items[Index]->Name);
+      AnsiString FileName = DragDropFilesEx->FileList->Items[Index]->Name;
+      if (SourceDirectory.IsEmpty())
+      {
+        SourceDirectory = ExtractFilePath(FileName);
+      }
+      FileList->Add(FileName);
     }
 
     if (Item)
@@ -551,36 +558,50 @@ void __fastcall TUnixDirView::PerformItemDragDropOperation(TListItem * Item, int
       Directory = Path;
     }
 
-    if (OnGetCopyParam)
+    bool DoFileOperation = true;
+    if (OnDDFileOperation != NULL)
     {
-      OnGetCopyParam(this, tdToRemote, Type, Directory, FileList, CopyParams);
+      OnDDFileOperation(this, Effect, SourceDirectory, Directory,
+        DoFileOperation);
     }
 
-    assert(Terminal);
-    int Params = cpDragDrop;
-    if (Type == ttMove) Params |= cpDelete;
-    Terminal->CopyToRemote(FileList, Directory, &CopyParams, Params);
-
-    // If target is current directory, we try to focus first dropped file
-    if (!Item)
+    if (DoFileOperation)
     {
-      int RemoteIndex, DragIndex;
-      for (RemoteIndex = 0; RemoteIndex < Items->Count; RemoteIndex++)
+      if (OnGetCopyParam)
       {
-        for (DragIndex = 0; DragIndex < FileList->Count; DragIndex++)
+        OnGetCopyParam(this, tdToRemote, Type, Directory, FileList, CopyParams);
+      }
+
+      assert(Terminal);
+      int Params = cpDragDrop;
+      if (Type == ttMove) Params |= cpDelete;
+      Terminal->CopyToRemote(FileList, Directory, &CopyParams, Params);
+
+      if (OnDDFileOperationExecuted)
+      {
+        OnDDFileOperationExecuted(this, Effect, SourceDirectory, Directory);
+      }
+
+      // If target is current directory, we try to focus first dropped file
+      if (!Item)
+      {
+        int RemoteIndex, DragIndex;
+        for (RemoteIndex = 0; RemoteIndex < Items->Count; RemoteIndex++)
         {
-          if (ItemFileName(Items->Item[RemoteIndex]) ==
-            ExtractFileName(FileList->Strings[DragIndex]))
+          for (DragIndex = 0; DragIndex < FileList->Count; DragIndex++)
           {
-            ItemFocused = Items->Item[RemoteIndex];
-            // We need to break both FOR cycles
-            RemoteIndex = Items->Count-1;
-            break;
+            if (ItemFileName(Items->Item[RemoteIndex]) ==
+              ExtractFileName(FileList->Strings[DragIndex]))
+            {
+              ItemFocused = Items->Item[RemoteIndex];
+              // We need to break both FOR cycles
+              RemoteIndex = Items->Count-1;
+              break;
+            }
           }
         }
       }
     }
-
   }
   __finally
   {
@@ -689,7 +710,7 @@ void __fastcall TUnixDirView::SetPath(AnsiString Value)
 #ifndef DESIGN_ONLY
   Value = UnixExcludeTrailingBackslash(
     StringReplace(Value, '\\', '/', TReplaceFlags() << rfReplaceAll));
-    
+
   if (Active && (Terminal->CurrentDirectory != Value))
   {
     FLastPath = PathName;
@@ -795,6 +816,18 @@ void __fastcall TUnixDirView::DDGiveFeedback(int dwEffect, HRESULT & Result)
   TCustomUnixDirView::DDGiveFeedback(dwEffect, Result);
 }
 //---------------------------------------------------------------------------
+void __fastcall TUnixDirView::DDChooseEffect(int grfKeyState, int &dwEffect)
+{
+  if (DDOwnerIsSource)
+  {
+    dwEffect = (DropTarget != NULL) ? DROPEFFECT_Move : DROPEFFECT_None;
+  }
+  else if ((grfKeyState & (MK_CONTROL | MK_SHIFT)) == 0)
+  {
+    dwEffect = DROPEFFECT_Copy;
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TUnixDirView::DDQueryContinueDrag(BOOL FEscapePressed,
   int grfKeyState, HRESULT & Result)
 {
@@ -823,97 +856,99 @@ void __fastcall TUnixDirView::DDQueryContinueDrag(BOOL FEscapePressed,
 void __fastcall TUnixDirView::DDTargetDrop()
 {
 #ifndef DESIGN_ONLY
-  assert(!FUniqTempDir.IsEmpty());
-  TTransferType Type;
-  AnsiString TempDir = FUniqTempDir;
-  // We clear FUniqTempDir before calling
-  // just in case it fail (raises exception)
-  FUniqTempDir = "";
-  Type = (FLastDropEffect & DROPEFFECT_MOVE ? ttMove : Type = ttCopy);
+  bool Continue = true;
+  if (OnDDTargetDrop)
+  {
+    OnDDTargetDrop(this, FLastDropEffect, Continue);
+  }
 
-  try
+  if (Continue)
   {
-    TStrings * FileList = new TStringList();
+    assert(!FUniqTempDir.IsEmpty());
+    TTransferType Type;
+    AnsiString TempDir = FUniqTempDir;
+    // We clear FUniqTempDir before calling
+    // just in case it fail (raises exception)
+    FUniqTempDir = "";
+    Type = (FLastDropEffect & DROPEFFECT_MOVE ? ttMove : Type = ttCopy);
+
     try
     {
-      assert(DragDropFilesEx);
-      assert(FDDFileList);
-      TRemoteFile * File;
-      int FileIndex;
-      for (int Index = 0; Index < DragDropFilesEx->FileList->Count; Index++)
+      TStrings * FileList = new TStringList();
+      try
       {
-        FileIndex = FDDFileList->IndexOf(DragDropFilesEx->FileList->Items[Index]->Name);
-        assert(FileIndex >= 0);
-        File = dynamic_cast<TRemoteFile *>(FDDFileList->Objects[FileIndex]);
-        assert(File);
-        FileList->AddObject(File->FileName, File);
-      }
+        assert(DragDropFilesEx);
+        assert(FDDFileList);
+        TRemoteFile * File;
+        int FileIndex;
+        for (int Index = 0; Index < DragDropFilesEx->FileList->Count; Index++)
+        {
+          FileIndex = FDDFileList->IndexOf(DragDropFilesEx->FileList->Items[Index]->Name);
+          assert(FileIndex >= 0);
+          File = dynamic_cast<TRemoteFile *>(FDDFileList->Objects[FileIndex]);
+          assert(File);
+          FileList->AddObject(File->FileName, File);
+        }
 
-      TCopyParamType CopyParams = Configuration->CopyParam;
-      AnsiString TargetDir = "";
-      bool UseTempDir;
+        TCopyParamType CopyParams = Configuration->CopyParam;
+        AnsiString TargetDir = "";
 
-      if (OnGetCopyParam)
-      {
-        OnGetCopyParam(this, tdToLocal, Type,
-          TargetDir /* empty directory parameter means temp directory -> don't display it! */,
-          FileList, CopyParams);
-      }
+        if (OnGetCopyParam)
+        {
+          OnGetCopyParam(this, tdToLocal, Type,
+            TargetDir /* empty directory parameter means temp directory -> don't display it! */,
+            FileList, CopyParams);
+        }
 
-      UseTempDir = TargetDir.IsEmpty();
-      bool Continue = true;
+        bool TemporaryDownload = TargetDir.IsEmpty();
+        bool Continue = true;
 
-      if (UseTempDir)
-      {
-        DoWarnLackOfTempSpace(TempDir, FDDTotalSize, Continue);
-        TargetDir = TempDir;
-      }
+        if (TemporaryDownload)
+        {
+          DoWarnLackOfTempSpace(TempDir, FDDTotalSize, Continue);
+          TargetDir = TempDir;
+        }
 
-      if (Continue)
-      {
-        if (ForceDirectories(TargetDir))
+        if (Continue)
         {
-          assert(Terminal && !TargetDir.IsEmpty());
-          try
+          if (TemporaryDownload)
           {
-            Terminal->CopyToLocal(FileList, TargetDir, &CopyParams,
-              (UseTempDir ? cpDragDrop : 0) | (Type == ttMove ? cpDelete : 0));
-
-            if (!UseTempDir)
+            if (ForceDirectories(TargetDir))
             {
-              // abort drag&drop, files are already at their place
-              Abort();
+              assert(Terminal && !TargetDir.IsEmpty());
+              try
+              {
+                Terminal->CopyToLocal(FileList, TargetDir, &CopyParams,
+                  cpDragDrop | (Type == ttMove ? cpDelete : 0));
+              }
+              __finally
+              {
+                AddDelayedDirectoryDeletion(TargetDir, DDDeleteDelay);
+              }
             }
-          }
-          __finally
-          {
-            if (UseTempDir)
+            else
             {
-              AddDelayedDirectoryDeletion(TempDir, DDDeleteDelay);
+              DragDropDirException(TargetDir);
             }
           }
         }
         else
         {
-          DragDropDirException(TempDir);
+          Abort();
         }
       }
-      else
+      __finally
       {
-        Abort();
+        delete FileList;
       }
     }
-    __finally
+    catch(ESshTerminate & E)
     {
-      delete FileList;
+      assert(!E.MoreMessages); // not supported
+      assert(!E.Message.IsEmpty());
+      FDragDropSshTerminate = E.Message;
     }
   }
-  catch(ESshTerminate & E)
-  {
-    assert(!E.MoreMessages); // not supported
-    assert(!E.Message.IsEmpty());
-    FDragDropSshTerminate = E.Message;
-  }
 #endif
 }
 //---------------------------------------------------------------------------

+ 12 - 2
components/UnixDirView.h

@@ -23,6 +23,8 @@ typedef void __fastcall (__closure *TGetCopyParamEvent)
 typedef void __fastcall (__closure *TWarnLackOfTempSpaceEvent)
   (TUnixDirView * Sender, const AnsiString Path, __int64 RequiredSpace,
     bool & Continue);
+typedef void __fastcall (__closure *TDDTargetDropEvent)
+  (TUnixDirView * Sender, int DropEffect, bool & Continue);
 #define DefaultDDDeleteDelay 120
 #define HOMEDIRECTORY ""
 #define CURRENTDIRECTORY "."
@@ -49,6 +51,7 @@ private:
   TWarnLackOfTempSpaceEvent FOnWarnLackOfTempSpace;
   AnsiString FDragDropSshTerminate;
   TStrings * FDDFileList;
+  TDDTargetDropEvent FOnDDTargetDrop;
   bool __fastcall GetActive();
   TTimer * FDelayedDeletionTimer;
   TStrings * FDelayedDeletionList;
@@ -67,7 +70,8 @@ protected:
   virtual void __fastcall DDMenuDone(TObject* Sender, HMENU AMenu);
   virtual void __fastcall DDQueryContinueDrag(BOOL FEscapePressed, int grfKeyState, HRESULT & Result);
   void __fastcall DDTargetDrop();
-    virtual void __fastcall AddToDragFileList(TFileList* FileList, TListItem* Item);
+  virtual void __fastcall DDChooseEffect(int grfKeyState, int &dwEffect);
+  virtual void __fastcall AddToDragFileList(TFileList* FileList, TListItem* Item);
   void __fastcall DisplayContextMenu(const TPoint &Where);
   void __fastcall DoChangeDirectory(TObject * Sender);
   void __fastcall DoDelayedDeletion(TObject * Sender);
@@ -86,7 +90,6 @@ protected:
   virtual AnsiString __fastcall ItemDragFileName(TListItem * Item);
   virtual AnsiString __fastcall ItemFileName(TListItem * Item);
   virtual __int64 __fastcall ItemFileSize(TListItem * Item);
-  virtual AnsiString __fastcall ItemFullFileName(TListItem * Item);
   virtual int __fastcall ItemImageIndex(TListItem * Item, bool Cache);
   virtual bool __fastcall ItemIsFile(TListItem * Item);
   virtual bool __fastcall ItemMatchesFilter(TListItem * Item, const TFileFilter &Filter);
@@ -114,6 +117,7 @@ public:
   virtual void __fastcall ReloadDirectory();
   virtual bool __fastcall ItemIsDirectory(TListItem * Item);
   virtual bool __fastcall ItemIsParentDirectory(TListItem * Item);
+  virtual AnsiString __fastcall ItemFullFileName(TListItem * Item);
   __property bool Active = { read = GetActive };
   __property AnsiString DDTemporaryDirectory  = { read=FDDTemporaryDirectory, write=FDDTemporaryDirectory };
 #ifndef DESIGN_ONLY
@@ -126,6 +130,8 @@ __published:
     write = SetDDDeleteDelay, default = DefaultDDDeleteDelay };
   __property TGetCopyParamEvent OnGetCopyParam = { read = FOnGetCopyParam,
     write = FOnGetCopyParam};
+  __property TDDTargetDropEvent OnDDTargetDrop = { read = FOnDDTargetDrop,
+    write = FOnDDTargetDrop};
   __property bool ShowInaccesibleDirectories  =
     { read=FShowInaccesibleDirectories, write=SetShowInaccesibleDirectories,
       default=true  };
@@ -156,11 +162,15 @@ __published:
   __property OnDDQueryContinueDrag;
   __property OnDDGiveFeedback;
   __property OnDDDragDetect;
+  __property OnDDEnd;
+  __property OnDDCreateDragFileList;
+  __property OnDDTargetHasDropHandler;
   __property OnDDProcessDropped;
   __property OnDDError;
   __property OnDDExecuted;
   __property OnDDFileOperation;
   __property OnDDFileOperationExecuted;
+  __property OnDDCreateDataObject;
 
   __property OnContextPopup;
   __property OnBeginRename;

+ 1 - 5
core/Common.cpp

@@ -440,7 +440,7 @@ bool __fastcall RecursiveDeleteFile(const AnsiString FileName, bool ToRecycleBin
   SHFILEOPSTRUCT Data;
 
   memset(&Data, 0, sizeof(Data)); 
-  Data.hwnd = Application->Handle;
+  Data.hwnd = NULL;
   Data.wFunc = FO_DELETE;
   AnsiString FileList(FileName);
   FileList.SetLength(FileList.Length() + 2);
@@ -455,9 +455,5 @@ bool __fastcall RecursiveDeleteFile(const AnsiString FileName, bool ToRecycleBin
     Data.fFlags |= FOF_ALLOWUNDO;
   }
   int Result = SHFileOperation(&Data);
-  if (Result != 0)
-  {
-    //Win32Check(false);
-  }
   return (Result == 0);
 }

+ 2 - 1
core/Exceptions.cpp

@@ -30,7 +30,8 @@ void __fastcall ExtException::AddMoreMessages(Exception* E)
     	FMoreMessages->Assign(((ExtException*)E)->MoreMessages);
     }
 
-    if (!E->Message.IsEmpty())
+    if (!E->Message.IsEmpty() &&
+        !E->InheritsFrom(__classid(EAbort)))
     {
       FMoreMessages->Insert(0, E->Message);
     }

+ 1 - 1
core/FileOperationProgress.h

@@ -6,7 +6,7 @@
 //---------------------------------------------------------------------------
 class TFileOperationProgressType;
 enum TFileOperation { foNone, foCopy, foMove, foDelete, foSetProperties,
-  foRename, foCustomCommand, foCalculateSize };
+  foRename, foCustomCommand, foCalculateSize, foRemoteMove };
 enum TCancelStatus { csContinue = 0, csCancel, csCancelTransfer, csRemoteAbort };
 enum TResumeStatus { rsNotAvailable, rsEnabled, rsDisabled };
 typedef void __fastcall (__closure *TFileOperationProgressEvent)

+ 1 - 1
core/FileSystems.h

@@ -17,7 +17,7 @@ enum TFSCommand { fsNull = 0, fsVarValue, fsLastLine, fsFirstLine,
   fsListFile, fsLookupUserGroups, fsCopyToRemote, fsCopyToLocal, fsDeleteFile,
   fsRenameFile, fsCreateDirectory, fsChangeMode, fsChangeGroup, fsChangeOwner,
   fsHomeDirectory, fsUnset, fsUnalias, fsAliasGroupList, fsCreateLink,
-  fsAnyCommand, fsReadSymlink, fsChangeProperties };
+  fsAnyCommand, fsReadSymlink, fsChangeProperties, fsMoveFile };
 //---------------------------------------------------------------------------
 typedef void __fastcall (__closure * TGetParamValueEvent)
   (const AnsiString Name, AnsiString & Value);

+ 19 - 7
core/RemoteFiles.cpp

@@ -35,8 +35,14 @@ AnsiString __fastcall UnixExtractFileDir(const AnsiString Path)
 {
   int Pos = Path.LastDelimiter('/');
   // it used to return Path when no slash was found
-  return (Pos > 1) ? Path.SubString(1, Pos - 1) :
-    ((Pos == 1) ? AnsiString("/") : AnsiString());
+  if (Pos > 1)
+  {
+    return Path.SubString(1, Pos - 1);
+  }
+  else
+  {
+    return (Pos == 1) ? AnsiString("/") : AnsiString();
+  }
 }
 //---------------------------------------------------------------------------
 // must return trailing backslash
@@ -339,18 +345,24 @@ void __fastcall TRemoteFile::SetListingStr(AnsiString value)
     {
       FSize = ASize;
 
-      Word Day, Month, Year, Hour, Min, P;
+      Word Day = 0, Month, Year, Hour, Min, P;
 
       GETCOL;
+      Day = (Word)StrToIntDef(Col, 0);
+      if (Day > 0)
+      {
+        GETCOL;
+      }
       Month = 0;
       for (Word IMonth = 0; IMonth < 12; IMonth++)
         if (!Col.AnsiCompareIC(EngShortMonthNames[IMonth])) { Month = IMonth; Month++; break; }
       if (!Month) Abort();
 
-      // don't trim possible leading space of year column
-      // we need to know is space is before (most systems) or after year
-      GETNCOL;
-      Day = (Word)StrToInt(Col);
+      if (Day == 0)
+      {
+        GETNCOL;
+        Day = (Word)StrToInt(Col);
+      }
       if ((Day < 1) || (Day > 31)) Abort();
 
       // Time/Year indicator is always 5 charactes long (???), on most

+ 3 - 1
core/ScpFileSystem.cpp

@@ -2090,8 +2090,10 @@ void __fastcall TSCPFileSystem::SCPSink(const AnsiString TargetDir,
           }
           HandleExtendedException(&E, this);
         );
-        Success = false;
       }
+      // this was inside above condition, but then transfer was considered
+      // succesfull, even when for example user refused to overwrite file
+      Success = false;
     }
     catch (EScpSkipFile &E)
     {

+ 7 - 2
core/SessionData.cpp

@@ -569,9 +569,14 @@ bool __fastcall TSessionData::ParseUrl(AnsiString Url, int Params,
   {
     if (Path != NULL)
     {
-      int Delta = ((Params & puExcludeLeadingSlash) == 0) ? 0 : 1;
-      *Path = Url.SubString(PSlash + Delta,
+      bool ExcludeLeadingSlash = (Params & puExcludeLeadingSlash) != 0;
+      int Delta = ExcludeLeadingSlash ? 1 : 0;
+      AnsiString APath = Url.SubString(PSlash + Delta,
         Url.Length() - PSlash - Delta + 1);
+      if (ExcludeLeadingSlash || (APath != "/"))
+      {
+        *Path = APath;
+      }
     }
 
     if (ConnectInfo != NULL)

+ 154 - 26
core/SftpFileSystem.cpp

@@ -43,6 +43,7 @@ const int SFTPMinVersion = 0;
 const int SFTPMaxVersion = 4;
 const int SFTPNoMessageNumber = -1;
 
+const int asNo =            0;
 const int asOK =            1 << SSH_FX_OK;
 const int asEOF =           1 << SSH_FX_EOF;
 const int asOpUnsupported = 1 << SSH_FX_OP_UNSUPPORTED;
@@ -100,12 +101,24 @@ public:
     AddByte(FType);
     if (FType != SSH_FXP_INIT)
     {
-      FMessageNumber = (FMessageCounter << 8) + FType;
-      FMessageCounter++;
+      AssignNumber();
       AddCardinal(FMessageNumber);
     }
   }
 
+  void Reuse()
+  {
+    AssignNumber();
+    
+    assert(Length >= 5);
+
+    // duplicated in AddCardinal()
+    unsigned char Buf[4];
+    PUT_32BIT(Buf, FMessageNumber);
+
+    memcpy(FData + 1, Buf, sizeof(Buf));
+  }
+
   void AddByte(unsigned char Value)
   {
     Add(&Value, sizeof(Value));
@@ -113,6 +126,7 @@ public:
 
   void AddCardinal(unsigned long Value)
   {
+    // duplicated in Reuse()
     unsigned char Buf[4];
     PUT_32BIT(Buf, Value);
     Add(&Buf, sizeof(Buf));
@@ -341,6 +355,20 @@ public:
     }
   }
 
+  AnsiString __fastcall Dump() const
+  {
+    AnsiString Result;
+    for (unsigned int Index = 0; Index < Length; Index++)
+    {
+      Result += IntToHex(int((unsigned char)Data[Index]), 2) + ",";
+      if (((Index + 1) % 25) == 0)
+      {
+        Result += "\n";
+      }
+    }
+    return Result;
+  }
+
   TSFTPPacket & operator = (const TSFTPPacket & Source)
   {
     Capacity = 0;
@@ -384,6 +412,12 @@ private:
     FReservedBy = NULL;
   }
 
+  void AssignNumber()
+  {
+    FMessageNumber = (FMessageCounter << 8) + FType;
+    FMessageCounter++;
+  }
+
   unsigned char GetRequestType()
   {
     if (FMessageNumber != SFTPNoMessageNumber)
@@ -703,17 +737,29 @@ public:
     return TSFTPTransferQueue::Init(QueueLen);
   }
 
+  void __fastcall InitFillGapRequest(__int64 Offset, unsigned long Missing,
+    TSFTPPacket * Packet)
+  {
+    InitRequest(Packet, Offset, Missing);
+  }
+
 protected:
   virtual bool __fastcall InitRequest(TSFTPPacket * Request)
   {
-    Request->ChangeType(SSH_FXP_READ);
-    Request->AddString(FHandle);
-    Request->AddInt64(FTransfered);
-    Request->AddCardinal(FBlockSize);
+    InitRequest(Request, FTransfered, FBlockSize);
     FTransfered += FBlockSize;
     return true;
   }
 
+  void __fastcall InitRequest(TSFTPPacket * Request, __int64 Offset,
+    unsigned long Size)
+  {
+    Request->ChangeType(SSH_FXP_READ);
+    Request->AddString(FHandle);
+    Request->AddInt64(Offset);
+    Request->AddCardinal(Size);
+  }
+
   virtual bool __fastcall SendNext(TSFTPPacket * Request)
   {
     return (Request->Type == SSH_FXP_DATA);
@@ -1118,7 +1164,7 @@ unsigned long __fastcall TSFTPFileSystem::GotStatusPacket(TSFTPPacket * Packet,
     }
     if (LanguageTag.IsEmpty())
     {
-      LanguageTag = "?";
+      LanguageTag = "*";
     }
     if (FTerminal->IsLogging())
     {
@@ -1171,13 +1217,25 @@ int __fastcall TSFTPFileSystem::ReceivePacket(TSFTPPacket * Packet,
       IsReserved = false;
 
       assert(Packet);
-      char LenBuf[4];
-      FTerminal->Receive(LenBuf, sizeof(LenBuf));
+      char LenBuf[5];
+      LenBuf[sizeof(LenBuf) - 1] = '\0';
+      FTerminal->Receive(LenBuf, sizeof(LenBuf) - 1);
       int Length = GET_32BIT(LenBuf);
       if (Length > SFTP_MAX_PACKET_LEN)
       {
-        FTerminal->FatalError(FMTLOAD(SFTP_PACKET_TOO_BIG, (
-          Length, SFTP_MAX_PACKET_LEN)));
+        AnsiString Message = FMTLOAD(SFTP_PACKET_TOO_BIG, (
+          int(Length), SFTP_MAX_PACKET_LEN));
+        if (ExpectedType == SSH_FXP_VERSION)
+        {
+          AnsiString LenString = LenBuf;
+          if (!IsDisplayableStr(LenString))
+          {
+            LenString = "0x" + StrToHex(LenString);
+          }
+          Message = FMTLOAD(SFTP_PACKET_TOO_BIG_INIT_EXPLAIN,
+            (Message, LenString));
+        }
+        FTerminal->FatalError(Message);
       }
       Packet->Capacity = Length;
       FTerminal->Receive(Packet->Data, Length);
@@ -1635,7 +1693,7 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
     SendPacketAndReceiveResponse(&Packet, &Packet, SSH_FXP_HANDLE);
 
     Handle = Packet.GetString();
-  }     
+  }
   catch(...)
   {
     if (FTerminal->Active)
@@ -1654,6 +1712,7 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
 
     Packet.ChangeType(SSH_FXP_READDIR);
     Packet.AddString(Handle);
+
     SendPacket(&Packet);
 
     do
@@ -1666,16 +1725,25 @@ void __fastcall TSFTPFileSystem::ReadDirectory(TRemoteFileList * FileList)
 
         Packet.ChangeType(SSH_FXP_READDIR);
         Packet.AddString(Handle);
+
         SendPacket(&Packet);
         ReserveResponse(&Packet, &Response);
 
         unsigned int Count = ListingPacket.GetCardinal();
+
         for (unsigned long Index = 0; Index < Count; Index++)
         {
           File = LoadFile(&ListingPacket, NULL);
           FileList->AddFile(File);
+
           Total++;
         }
+
+        if (Count == 0)
+        {
+          FTerminal->LogEvent("Empty directory listing packet. Aborting directory reading."); 
+          isEOF = true;
+        }
       }
       else if (Response.Type == SSH_FXP_STATUS)
       {
@@ -2336,7 +2404,7 @@ void __fastcall TSFTPFileSystem::SFTPSource(const AnsiString FileName,
 
       if (CopyParam->PreserveTime)
       {
-        FILE_OPERATION_LOOP(FMTLOAD(CHANGE_PROPERTIES_ERROR, (DestFileName)),
+        FILE_OPERATION_LOOP(FMTLOAD(PRESERVE_TIME_ERROR, (DestFileName)),
           TSFTPPacket Packet(SSH_FXP_SETSTAT);
           Packet.AddString(DestFullName);
           if (FVersion >= 4)
@@ -2511,6 +2579,7 @@ void __fastcall TSFTPFileSystem::SFTPDirectorySource(const AnsiString DirectoryN
 
   OperationProgress->SetFile(DirectoryName);
 
+  bool CreateDir = false;
   try
   {
     TryOpenDirectory(DestFullName);
@@ -2521,13 +2590,7 @@ void __fastcall TSFTPFileSystem::SFTPDirectorySource(const AnsiString DirectoryN
     {
       // opening directory failed, it probably does not exists, try to
       // create it
-      TRemoteProperties Properties;
-      if (CopyParam->PreserveRights)
-      {
-        Properties.Valid = TValidProperties() << vpRights;
-        Properties.Rights = CopyParam->RemoteFileRights(Attrs);
-      }
-      FTerminal->CreateDirectory(DestFullName, &Properties);
+      CreateDir = true;
     }
     else
     {
@@ -2535,6 +2598,17 @@ void __fastcall TSFTPFileSystem::SFTPDirectorySource(const AnsiString DirectoryN
     }
   }
 
+  if (CreateDir)
+  {
+    TRemoteProperties Properties;
+    if (CopyParam->PreserveRights)
+    {
+      Properties.Valid = TValidProperties() << vpRights;
+      Properties.Rights = CopyParam->RemoteFileRights(Attrs);
+    }
+    FTerminal->CreateDirectory(DestFullName, &Properties);
+  }
+
   int FindAttrs = faReadOnly | faHidden | faSysFile | faDirectory | faArchive;
   TSearchRec SearchRec;
   bool FindOK;
@@ -2845,6 +2919,7 @@ void __fastcall TSFTPFileSystem::SFTPSink(const AnsiString FileName,
       {
         TSFTPDownloadQueue Queue(this);
         TSFTPPacket DataPacket;
+
         int QueueLen = int(File->Size / BlockSize) + 1;
         if (QueueLen > FTerminal->SessionData->SFTPDownloadQueue)
         {
@@ -2855,13 +2930,25 @@ void __fastcall TSFTPFileSystem::SFTPSink(const AnsiString FileName,
 
         bool Eof = false;
         bool PrevIncomplete = false;
+        int GapFillCount = 0;
+        int GapCount = 0;
+        unsigned long Missing = 0;
         unsigned long DataLen = 0;
+
         while (!Eof)
         {
-          // Buffer for one block of data
-          TFileBuffer BlockBuf;
-
-          Queue.ReceivePacket(&DataPacket, SSH_FXP_DATA, asEOF);
+          if (Missing > 0)
+          {
+            Queue.InitFillGapRequest(OperationProgress->TransferedSize, Missing,
+              &DataPacket);
+            GapFillCount++;
+            SendPacketAndReceiveResponse(&DataPacket, &DataPacket,
+              SSH_FXP_DATA, asEOF);
+          }
+          else
+          {
+            Queue.ReceivePacket(&DataPacket, SSH_FXP_DATA, asEOF);
+          }
 
           if (DataPacket.Type == SSH_FXP_STATUS)
           {
@@ -2871,8 +2958,14 @@ void __fastcall TSFTPFileSystem::SFTPSink(const AnsiString FileName,
 
           if (!Eof)
           {
-            if (PrevIncomplete)
+            if ((Missing == 0) && PrevIncomplete)
             {
+              // This can happen only if last request returned less bytes
+              // than expected, but exacly number of bytes missing to last
+              // known file size, but actually EOF was not reached.
+              // Can happen only when filesize has changed since directory
+              // listing and server returns less bytes than requested and
+              // fiel has some special file size.
               FTerminal->LogEvent(FORMAT(
                 "Received incomplete data packet before end of file, "
                 "offset: %s, size: %d, requested: %d",
@@ -2881,9 +2974,37 @@ void __fastcall TSFTPFileSystem::SFTPSink(const AnsiString FileName,
               FTerminal->TerminalError(NULL, LoadStr(SFTP_INCOMPLETE_BEFORE_EOF));
             }
 
+            // Buffer for one block of data
+            TFileBuffer BlockBuf;
+
             DataLen = DataPacket.GetCardinal();
+
+            PrevIncomplete = false;
+            if (Missing > 0)
+            {
+              assert(DataLen <= Missing);
+              Missing -= DataLen;
+            }
+            else if (DataLen < BlockSize)
+            {
+              if (OperationProgress->TransferedSize + DataLen !=
+                    OperationProgress->TransferSize)
+              {
+                // with native text transfer mode (SFTP>=4), do not bother about
+                // getting less than requested, read offset is ignored anyway
+                if ((FVersion < 4) || !OperationProgress->AsciiTransfer)
+                {
+                  GapCount++;
+                  Missing = BlockSize - DataLen;
+                }
+              }
+              else
+              {
+                PrevIncomplete = true;
+              }
+            }
+
             assert(DataLen <= BlockSize);
-            PrevIncomplete = (DataLen < BlockSize);
             BlockBuf.Insert(0, DataPacket.NextData, DataLen);
             OperationProgress->AddTransfered(DataLen);
 
@@ -2909,6 +3030,13 @@ void __fastcall TSFTPFileSystem::SFTPSink(const AnsiString FileName,
             }
           }
         };
+
+        if (GapCount > 0)
+        {
+          FTerminal->LogEvent(FORMAT(
+            "%d requests to fill %d data gaps were issued.",
+            (GapFillCount, GapCount)));
+        }
       }
 
       if (CopyParam->PreserveTime)

+ 35 - 5
core/Terminal.cpp

@@ -30,6 +30,12 @@
 #define FILE_OPERATION_LOOP_EX(ALLOW_SKIP, MESSAGE, OPERATION) \
   FILE_OPERATION_LOOP_CUSTOM(this, ALLOW_SKIP, MESSAGE, OPERATION)
 //---------------------------------------------------------------------------
+struct TMoveFileParams
+{
+  AnsiString Target;
+  AnsiString FileMask;
+};
+//---------------------------------------------------------------------------
 __fastcall TTerminal::TTerminal(): TSecureShell()
 {
   FFiles = new TRemoteDirectory(this);
@@ -198,6 +204,7 @@ void __fastcall TTerminal::ReactOnCommand(int /*TFSCommand*/ Cmd)
     case fsCopyToRemote:
     case fsDeleteFile:
     case fsRenameFile:
+    case fsMoveFile:
     case fsCreateDirectory:
     case fsChangeMode:
     case fsChangeGroup:
@@ -544,7 +551,7 @@ void __fastcall TTerminal::ReloadDirectory()
 void __fastcall TTerminal::EnsureNonExistence(const AnsiString FileName)
 {
   // if filename doesn't contain path, we check for existence of file
-  if ((UnixExtractFileDir(FileName) == FileName) &&
+  if ((UnixExtractFileDir(FileName).IsEmpty()) &&
       UnixComparePaths(CurrentDirectory, FFiles->Directory))
   {
     TRemoteFile *File = FFiles->FindFile(FileName);
@@ -1112,7 +1119,7 @@ void __fastcall TTerminal::RenameFile(const AnsiString FileName,
   const AnsiString NewName)
 {
   LogEvent(FORMAT("Renaming file \"%s\" to \"%s\".", (FileName, NewName)));
-  DoRenameFile(FileName, NewName);
+  DoRenameFile(FileName, NewName, false);
   ReactOnCommand(fsRenameFile);
 }
 //---------------------------------------------------------------------------
@@ -1155,7 +1162,7 @@ void __fastcall TTerminal::RenameFile(const TRemoteFile * File,
 }
 //---------------------------------------------------------------------------
 void __fastcall TTerminal::DoRenameFile(const AnsiString FileName,
-  const AnsiString NewName)
+  const AnsiString NewName, bool Move)
 {
   try
   {
@@ -1166,12 +1173,35 @@ void __fastcall TTerminal::DoRenameFile(const AnsiString FileName,
   {
     COMMAND_ERROR_ARI
     (
-      FMTLOAD(RENAME_FILE_ERROR, (FileName, NewName)),
-      DoRenameFile(FileName, NewName)
+      FMTLOAD(Move ? MOVE_FILE_ERROR : RENAME_FILE_ERROR, (FileName, NewName)),
+      DoRenameFile(FileName, NewName, Move)
     );
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TTerminal::MoveFile(const AnsiString FileName,
+  const TRemoteFile * File, /*const TMoveFileParams*/ void * Param)
+{
+  assert(Param != NULL);
+  const TMoveFileParams & Params = *static_cast<const TMoveFileParams*>(Param);
+  AnsiString NewName = UnixIncludeTrailingBackslash(Params.Target) +
+    MaskFileName(UnixExtractFileName(FileName), Params.FileMask);
+  LogEvent(FORMAT("Moving file \"%s\" to \"%s\".", (FileName, NewName)));
+  FileModified(File, FileName);
+  DoRenameFile(FileName, NewName, true);
+  ReactOnCommand(fsMoveFile);
+}
+//---------------------------------------------------------------------------
+bool __fastcall TTerminal::MoveFiles(TStrings * FileList, const AnsiString Target,
+  const AnsiString FileMask)
+{
+  TMoveFileParams Params;
+  Params.Target = Target;
+  Params.FileMask = FileMask;
+  DirectoryModified(Target, true);
+  return ProcessFiles(FileList, foRemoteMove, MoveFile, &Params);
+}
+//---------------------------------------------------------------------------
 void __fastcall TTerminal::CreateDirectory(const AnsiString DirName,
   const TRemoteProperties * Properties)
 {

+ 6 - 1
core/Terminal.h

@@ -150,7 +150,8 @@ protected:
     const TRemoteFile * File, void * Param);
   void __fastcall DoCustomCommandOnFile(AnsiString FileName,
     const TRemoteFile * File, AnsiString Command, int Params);
-  void __fastcall DoRenameFile(const AnsiString FileName, const AnsiString NewName);
+  void __fastcall DoRenameFile(const AnsiString FileName,
+    const AnsiString NewName, bool Move);
   void __fastcall DoChangeFileProperties(const AnsiString FileName,
     const TRemoteFile * File, const TRemoteProperties * Properties);
   void __fastcall DoChangeDirectory();
@@ -237,6 +238,10 @@ public:
   void __fastcall ReloadDirectory();
   void __fastcall RenameFile(const AnsiString FileName, const AnsiString NewName);
   void __fastcall RenameFile(const TRemoteFile * File, const AnsiString NewName, bool CheckExistence);
+  void __fastcall MoveFile(const AnsiString FileName, const TRemoteFile * File,
+    /*const TMoveFileParams*/ void * Param);
+  bool __fastcall MoveFiles(TStrings * FileList, const AnsiString Target,
+    const AnsiString FileMask);
   void __fastcall CalculateFilesSize(TStrings * FileList, __int64 & Size, int Params);
   void __fastcall ClearCaches();
   void __fastcall Synchronize(const AnsiString LocalDirectory,

+ 12 - 0
forms/CopyParams.cpp

@@ -22,6 +22,7 @@ __fastcall TCopyParamsFrame::TCopyParamsFrame(TComponent* Owner)
   FDirection = pdToLocal;
   Direction = pdToRemote;
 
+  FForcePreserveTime = false;
   FAllowTransferMode = True;
   RightsFrame->AllowAddXToDirectories = True;
   FParams = new TCopyParamType();
@@ -130,6 +131,11 @@ void __fastcall TCopyParamsFrame::UpdateControls()
   EnableControl(RightsFrame, PreserveRightsCheck->Checked && Enabled);
   EnableControl(ReplaceInvalidCharsCheck,
     Direction == pdToLocal || Direction == pdBoth || Direction == pdAll);
+  EnableControl(PreserveTimeCheck, !ForcePreserveTime);
+  if (ForcePreserveTime)
+  {
+    PreserveTimeCheck->Checked = true;
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TCopyParamsFrame::SetDirection(TParamsForDirection value)
@@ -180,6 +186,12 @@ void __fastcall TCopyParamsFrame::SetAllowTransferMode(Boolean value)
   UpdateControls();
 }
 //---------------------------------------------------------------------------
+void __fastcall TCopyParamsFrame::SetForcePreserveTime(bool value)
+{
+  FForcePreserveTime = value;
+  UpdateControls();
+}
+//---------------------------------------------------------------------------
 Boolean __fastcall TCopyParamsFrame::GetAllowTransferMode()
 {
   return FAllowTransferMode;

+ 3 - 0
forms/CopyParams.h

@@ -45,6 +45,7 @@ private:
   AnsiString FOrigMasks;
   Boolean FAllowTransferMode;
   TCopyParamType * FParams;
+  bool FForcePreserveTime;
   Boolean __fastcall GetAllowTransferMode();
   AnsiString __fastcall GetAsciiFileMask();
   void __fastcall SetParams(TCopyParamType value);
@@ -52,6 +53,7 @@ private:
   void __fastcall SetAllowTransferMode(Boolean value);
   void __fastcall SetDirection(TParamsForDirection value);
   TCheckBox * __fastcall GetPreserveTimeCheck();
+  void __fastcall SetForcePreserveTime(bool value);
 protected:
   void __fastcall UpdateControls();
   virtual void __fastcall SetEnabled(Boolean Value);
@@ -66,6 +68,7 @@ public:
   __property TParamsForDirection Direction = { read = FDirection, write = SetDirection };
   __property TCopyParamType Params = { read = GetParams, write = SetParams };
   __property TCheckBox * PreserveTimeCheck = { read = GetPreserveTimeCheck };
+  __property bool ForcePreserveTime = { read = FForcePreserveTime, write = SetForcePreserveTime };
 };
 //---------------------------------------------------------------------------
 #endif

+ 405 - 44
forms/CustomScpExplorer.cpp

@@ -2,6 +2,8 @@
 #include <vcl.h>
 #pragma hdrstop
 
+#include <Clipbrd.hpp>
+
 #include "CustomScpExplorer.h"
 
 #include <Common.h>
@@ -14,15 +16,17 @@
 
 #include <VCLCommon.h>
 #include <Log.h>
+#include <Progress.h>
+#include <SynchronizeProgress.h>
+#include <OperationStatus.h>
+
+#include <DragExt.h>
 
 #include "GUITools.h"
 #include "NonVisual.h"
 #include "Tools.h"
 #include "WinConfiguration.h"
 #include "TerminalManager.h"
-#include <Progress.h>
-#include <SynchronizeProgress.h>
-#include <OperationStatus.h>
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 #pragma link "CustomDirView"
@@ -57,6 +61,36 @@
     } \
   }
 //---------------------------------------------------------------------------
+class TMutexGuard
+{
+public:
+  TMutexGuard(HANDLE AMutex, int Message = MUTEX_RELEASE_TIMEOUT,
+    int Timeout = 5000)
+  {
+    FMutex = NULL;
+    unsigned long WaitResult = WaitForSingleObject(AMutex, Timeout);
+    if (WaitResult == WAIT_TIMEOUT)
+    {
+      throw Exception(LoadStr(MUTEX_RELEASE_TIMEOUT));
+    }
+    else
+    {
+      FMutex = AMutex;
+    }
+  }
+
+  ~TMutexGuard()
+  {
+    if (FMutex != NULL)
+    {
+      ReleaseMutex(FMutex);
+    }
+  }
+
+private:
+  HANDLE FMutex;
+};
+//---------------------------------------------------------------------------
 __fastcall TCustomScpExplorerForm::TCustomScpExplorerForm(TComponent* Owner):
     FLastDirView(NULL), FFormRestored(False), TForm(Owner)
 {
@@ -72,6 +106,10 @@ __fastcall TCustomScpExplorerForm::TCustomScpExplorerForm(TComponent* Owner):
   FErrorList = NULL;
   FSynchronizeProgressForm = NULL;
   FProgressForm = NULL;
+  FDDExtDropEffect = DROPEFFECT_NONE;
+  FDDExtMapFile = NULL;
+  FDDExtMutex = CreateMutex(NULL, false, DRAG_EXT_MUTEX);
+  assert(FDDExtMutex != NULL);
 
   UseSystemSettings(this);
 
@@ -91,10 +129,14 @@ __fastcall TCustomScpExplorerForm::TCustomScpExplorerForm(TComponent* Owner):
   MenuToolBar->Height = MenuToolBar->Controls[0]->Height;
 
   RemoteDirView->Font = Screen->IconFont;
+  RemoteDirView->DDAllowMove = true;
 }
 //---------------------------------------------------------------------------
 __fastcall TCustomScpExplorerForm::~TCustomScpExplorerForm()
 {
+  CloseHandle(FDDExtMutex);
+  FDDExtMutex = NULL;
+
   assert(!FErrorList);
   StoreParams();
   Terminal = NULL;
@@ -129,7 +171,6 @@ void __fastcall TCustomScpExplorerForm::TerminalChanged()
 void __fastcall TCustomScpExplorerForm::ConfigurationChanged()
 {
   assert(Configuration && RemoteDirView);
-  RemoteDirView->DDAllowMove = WinConfiguration->DDAllowMove;
   RemoteDirView->DimmHiddenFiles = WinConfiguration->DimmHiddenFiles;
   RemoteDirView->ShowHiddenFiles = WinConfiguration->ShowHiddenFiles;
   RemoteDirView->ShowInaccesibleDirectories = WinConfiguration->ShowInaccesibleDirectories;
@@ -153,17 +194,30 @@ bool __fastcall TCustomScpExplorerForm::CopyParamDialog(
   TStrings * FileList, AnsiString & TargetDirectory, TCopyParamType & CopyParam,
   bool Confirm)
 {
+  bool Result = true;
   assert(Terminal && Terminal->Active);
-  if (Confirm)
+  if (DragDrop && (Direction == tdToLocal) && (Type == ttMove) &&
+      !WinConfiguration->DDAllowMove)
   {
-    return DoCopyDialog(Direction == tdToRemote, Type == ttMove,
-      DragDrop, FileList, Terminal->IsCapable[fcTextMode], TargetDirectory,
-      &CopyParam, true);
+    int Answer = MessageDialog(LoadStr(DND_DOWNLOAD_MOVE_WARNING), qtWarning,
+      qaOK | qaCancel, 0, mpNeverAskAgainCheck);
+    if (Answer == qaNeverAskAgain)
+    {
+      WinConfiguration->DDAllowMove = true;
+    }
+    else if (Answer == qaCancel)
+    {
+      Result = false;
+    }
   }
-  else
+
+  if (Result && Confirm)
   {
-    return true;
+    Result = DoCopyDialog(Direction == tdToRemote, Type == ttMove,
+      DragDrop, FileList, Terminal->IsCapable[fcTextMode], TargetDirectory,
+      &CopyParam, true);
   }
+  return Result;
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::RestoreFormParams()
@@ -339,34 +393,21 @@ void __fastcall TCustomScpExplorerForm::BatchEnd(void * Storage)
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Operation,
-  TOperationSide Side, bool OnFocused, bool NoConfirmation, void * Param)
+  TOperationSide Side, TStrings * FileList, bool NoConfirmation, void * Param)
 {
-  if (Side == osCurrent)
-  {
-    if (FLastDirView == RemoteDirView)
-    {
-      Side = osRemote;
-    }
-    else
-    {
-      assert(FLastDirView);
-      Side = osLocal;
-    }
-  }
-
-  TStrings * FileList = NULL;
   void * BatchStorage;
-
   BatchStart(BatchStorage);
   try
   {
-    FileList = DirView(Side)->CreateFileList(OnFocused, (Side == osLocal), NULL);
-
     if ((Operation == foCopy) || (Operation == foMove))
     {
       TTransferDirection Direction = (Side == osLocal ? tdToRemote : tdToLocal);
       TTransferType Type = (Operation == foCopy ? ttCopy : ttMove);
       AnsiString TargetDirectory;
+      if (Param != NULL)
+      {
+        TargetDirectory = *static_cast<AnsiString*>(Param);
+      }
       TCopyParamType CopyParam = Configuration->CopyParam;
       if (CopyParamDialog(Direction, Type, false, FileList, TargetDirectory,
           CopyParam, !NoConfirmation))
@@ -490,6 +531,11 @@ void __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Oper
         FCustomCommandName = "";
       }
     }
+    else if (Operation == foRemoteMove)
+    {
+      assert(Side == osRemote);
+      RemoteMoveFiles(FileList, NoConfirmation);
+    }
     else
     {
       assert(false);
@@ -498,6 +544,31 @@ void __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Oper
   __finally
   {
     BatchEnd(BatchStorage);
+  }
+}
+//---------------------------------------------------------------------------
+TOperationSide __fastcall TCustomScpExplorerForm::GetSide(TOperationSide Side)
+{
+  if (Side == osCurrent)
+  {
+    Side = osRemote;
+  }
+
+  return Side;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::ExecuteFileOperation(TFileOperation Operation,
+  TOperationSide Side, bool OnFocused, bool NoConfirmation, void * Param)
+{
+  Side = GetSide(Side);
+
+  TStrings * FileList = DirView(Side)->CreateFileList(OnFocused, (Side == osLocal), NULL);
+  try
+  {
+    ExecuteFileOperation(Operation, Side, FileList, NoConfirmation, Param);
+  }
+  __finally
+  {
     delete FileList;
   }
 }
@@ -754,7 +825,7 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(TObject * Sender)
       try
       {
         int FileTimestamp = FileAge(FExecutedFile);
-        if (FileTimestamp > FExecutedFileTimestamp)
+        if (FileTimestamp != FExecutedFileTimestamp)
         {
           FExecutedFileTimestamp = FileTimestamp;
 
@@ -821,7 +892,13 @@ void __fastcall TCustomScpExplorerForm::ExecutedFileChanged(TObject * Sender)
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::DirViewEnter(TObject *Sender)
 {
-  FLastDirView = ((TCustomDirView *)Sender);
+  DoDirViewEnter(dynamic_cast<TCustomDirView*>(Sender));
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DoDirViewEnter(TCustomDirView * DirView)
+{
+  assert(DirView != NULL);
+  FLastDirView = DirView;
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::DeleteFiles(TOperationSide Side,
@@ -857,6 +934,49 @@ void __fastcall TCustomScpExplorerForm::DeleteFiles(TOperationSide Side,
   DView->RestoreSelection();
 }
 //---------------------------------------------------------------------------
+bool __fastcall TCustomScpExplorerForm::RemoteMoveDialog(TStrings * FileList,
+  AnsiString & Target, AnsiString & FileMask, bool NoConfirmation)
+{
+  if (RemoteDirView->DropTarget != NULL)
+  {
+    assert(RemoteDirView->ItemIsDirectory(RemoteDirView->DropTarget));
+    Target = RemoteDirView->ItemFullFileName(RemoteDirView->DropTarget);
+  }
+  else
+  {
+    Target = RemoteDirView->Path;
+  }
+  Target = UnixIncludeTrailingBackslash(Target);
+  FileMask = "*.*";
+  bool Result = true;
+  if (!NoConfirmation)
+  {
+    Result = DoRemoteMoveDialog(FileList, Target, FileMask);
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteMoveFiles(
+  TStrings * FileList, bool NoConfirmation)
+{
+  AnsiString Target, FileMask;
+  if (RemoteMoveDialog(FileList, Target, FileMask, NoConfirmation))
+  {
+    RemoteDirView->SaveSelection();
+
+    try
+    {
+      Terminal->MoveFiles(FileList, Target, FileMask);
+    }
+    catch(...)
+    {
+      RemoteDirView->DiscardSavedSelection();
+      throw;
+    }
+    RemoteDirView->RestoreSelection();
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::CreateDirectory(TOperationSide Side)
 {
   AnsiString Name = LoadStr(NEW_FOLDER);
@@ -913,7 +1033,9 @@ void __fastcall TCustomScpExplorerForm::KeyDown(Word & Key, Classes::TShiftState
     for (int Index = 0; Index < NonVisualDataModule->ExplorerActions->ActionCount; Index++)
     {
       TAction * Action = (TAction *)NonVisualDataModule->ExplorerActions->Actions[Index];
-      if ((Action->ShortCut == KeyShortCut) && AllowedAction(Action, aaShortCut))
+      if (((Action->ShortCut == KeyShortCut) ||
+           (Action->SecondaryShortCuts->IndexOfShortCut(KeyShortCut) >= 0)) &&
+          AllowedAction(Action, aaShortCut))
       {
         Key = 0;
         Action->Execute();
@@ -1203,6 +1325,11 @@ void __fastcall TCustomScpExplorerForm::SetComponentVisible(Word Component, Bool
   {
     Control->Visible = value;
   }
+
+  if (RemoteDirView->ItemFocused != NULL)
+  {
+    RemoteDirView->ItemFocused->MakeVisible(false);
+  }
 }
 //---------------------------------------------------------------------------
 bool __fastcall TCustomScpExplorerForm::GetComponentVisible(Word Component)
@@ -1311,15 +1438,21 @@ void __fastcall TCustomScpExplorerForm::FullSynchronizeDirectories()
 }
 //---------------------------------------------------------------------------
 bool __fastcall TCustomScpExplorerForm::DoFullSynchronizeDirectories(
-  AnsiString & LocalDirectory, AnsiString & RemoteDirectory)
+  AnsiString & LocalDirectory, AnsiString & RemoteDirectory, TSynchronizeMode & Mode)
 {
   bool Result;
-  TSynchronizeMode Mode = smRemote;
-  int Params = spDelete | spNoConfirmation;
+  int Params = GUIConfiguration->SynchronizeParams;
 
-  Result = DoFullSynchronizeDialog(Mode, Params, LocalDirectory, RemoteDirectory);
+  bool SaveSettings;
+  Result = DoFullSynchronizeDialog(Mode, Params, LocalDirectory, RemoteDirectory,
+    SaveSettings);
   if (Result)
   {
+    if (SaveSettings)
+    {
+      GUIConfiguration->SynchronizeParams = Params;
+    }
+
     assert(!FAutoOperation);
     void * BatchStorage;
     BatchStart(BatchStorage);
@@ -1352,6 +1485,7 @@ bool __fastcall TCustomScpExplorerForm::DoFullSynchronizeDirectories(
 void __fastcall TCustomScpExplorerForm::TerminalSynchronizeDirectory(
   const AnsiString LocalDirectory, const AnsiString RemoteDirectory, bool & Continue)
 {
+  assert(FSynchronizeProgressForm != NULL);
   FSynchronizeProgressForm->SetData(LocalDirectory, RemoteDirectory, Continue);
 }
 //---------------------------------------------------------------------------
@@ -1389,12 +1523,10 @@ void __fastcall TCustomScpExplorerForm::UpdateSessionData(TSessionData * Data)
   {
     Data = Terminal->SessionData;
   }
-  if (Data->UpdateDirectories || (Data != Terminal->SessionData))
-  {
-    // cannot use RemoteDirView->Path, because it is empty if connection
-    // was already closed
-    Data->RemoteDirectory = Terminal->CurrentDirectory;
-  }
+
+  // cannot use RemoteDirView->Path, because it is empty if connection
+  // was already closed
+  Data->RemoteDirectory = Terminal->CurrentDirectory;
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::ToolBarResize(TObject *Sender)
@@ -1502,15 +1634,21 @@ void __fastcall TCustomScpExplorerForm::OpenInPutty()
   OpenSessionInPutty(Terminal->SessionData);
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::OpenConsole()
+void __fastcall TCustomScpExplorerForm::OpenConsole(AnsiString Command)
 {
-  DoConsoleDialog(Terminal);
+  DoConsoleDialog(Terminal, Command);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::DirViewDDDragEnter(
       TObject *Sender, _di_IDataObject /*DataObj*/, int /*grfKeyState*/,
-      const TPoint & /*Point*/, int &/*dwEffect*/, bool & /*Accept*/)
+      const TPoint & /*Point*/, int & /*dwEffect*/, bool & Accept)
 {
+  if ((DropSourceControl == RemoteDirView) &&
+      (FDDExtMapFile != NULL))
+  {
+    Accept = true;
+  }
+
   FDDTargetDirView = (TCustomDirView*)Sender;
 }
 //---------------------------------------------------------------------------
@@ -1736,3 +1874,226 @@ int __fastcall TCustomScpExplorerForm::MoreMessageDialog(const AnsiString Messag
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewDDCreateDragFileList(
+  TObject * /*Sender*/, TFileList * FileList, bool & Created)
+{
+  if (FDDExtMapFile != NULL)
+  {
+    CloseHandle(FDDExtMapFile);
+    FDDExtMapFile = NULL;
+  }
+
+  if (WinConfiguration->DDExtEnabled)
+  {
+    DDExtInitDrag(FileList, Created);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DDExtInitDrag(TFileList * FileList,
+  bool & Created)
+{
+  FDragExtFakeDirectory =
+    ExcludeTrailingBackslash(UniqTempDir(WinConfiguration->DDTemporaryDirectory));
+  ForceDirectories(FDragExtFakeDirectory);
+  FileList->AddItem(NULL, FDragExtFakeDirectory);
+
+  Created = true;
+
+  FDDExtMapFile = CreateFileMapping((HANDLE)0xFFFFFFFF, NULL, PAGE_READWRITE,
+    0, sizeof(TDragExtCommStruct), DRAG_EXT_MAPPING);
+
+  {
+    TMutexGuard Guard(FDDExtMutex, DRAGEXT_MUTEX_RELEASE_TIMEOUT);
+    TDragExtCommStruct* CommStruct;
+    CommStruct = static_cast<TDragExtCommStruct*>(MapViewOfFile(FDDExtMapFile,
+      FILE_MAP_ALL_ACCESS, 0, 0, 0));
+    assert(CommStruct != NULL);
+    CommStruct->Version = TDragExtCommStruct::CurrentVersion;
+    CommStruct->Dragging = true;
+    strncpy(CommStruct->DropDest, FDragExtFakeDirectory.c_str(),
+      sizeof(CommStruct->DropDest));
+    CommStruct->DropDest[sizeof(CommStruct->DropDest) - 1] = '\0';
+    UnmapViewOfFile(CommStruct);
+  }
+
+  FDDExtDropEffect = DROPEFFECT_NONE;
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewDDEnd(TObject * /*Sender*/)
+{
+  if (FDDExtMapFile != NULL)
+  {
+    try
+    {
+      if (FDDExtDropEffect != DROPEFFECT_NONE)
+      {
+        AnsiString TargetDirectory;
+
+        DDGetTarget(TargetDirectory);
+
+        ExecuteFileOperation(
+          FDDExtDropEffect == DROPEFFECT_MOVE ? foMove : foCopy, osRemote, true,
+          !WinConfiguration->DDTransferConfirmation, &TargetDirectory);
+      }
+    }
+    __finally
+    {
+      CloseHandle(FDDExtMapFile);
+      FDDExtMapFile = NULL;
+      RemoveDir(FDragExtFakeDirectory);
+      FDragExtFakeDirectory = "";
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::DDGetTarget(AnsiString & Directory)
+{
+  bool Result = false;
+
+  Enabled = false;
+  try
+  {
+    int Timer = 0;
+    while (!Result && (Timer < WinConfiguration->DDExtTimeout))
+    {
+      {
+        TMutexGuard Guard(FDDExtMutex, DRAGEXT_MUTEX_RELEASE_TIMEOUT);
+        TDragExtCommStruct* CommStruct;
+        CommStruct = static_cast<TDragExtCommStruct*>(MapViewOfFile(FDDExtMapFile,
+          FILE_MAP_ALL_ACCESS, 0, 0, 0));
+        assert(CommStruct != NULL);
+        Result = !CommStruct->Dragging;
+        if (Result)
+        {
+          Directory = ExtractFilePath(CommStruct->DropDest);
+        }
+        UnmapViewOfFile(CommStruct);
+      }
+      Sleep(50);
+      Timer += 50;
+      Application->ProcessMessages();
+    }
+  }
+  __finally
+  {
+    Enabled = true;
+  }
+
+  if (!Result)
+  {
+    throw Exception(LoadStr(DRAGEXT_TARGET_UNKNOWN));
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewDDTargetDrop(
+  TUnixDirView * /*Sender*/, int DropEffect, bool & Continue)
+{
+  if ((DropEffect == DROPEFFECT_MOVE) &&
+      (FDDTargetDirView == RemoteDirView))
+  {
+    ExecuteFileOperation(foRemoteMove, osRemote, true,
+      !WinConfiguration->DDTransferConfirmation);
+    // abort drag&drop
+    Abort();
+  }
+  else if (FDDExtMapFile != NULL)
+  {
+    Continue = false;
+    FDDExtDropEffect = DropEffect;
+  }
+}
+//---------------------------------------------------------------------------
+class TFakeDataObjectFilesEx : public TDataObjectFilesEx
+{
+public:
+	__fastcall TFakeDataObjectFilesEx(TFileList * AFileList, bool RenderPIDL,
+    bool RenderFilename) : TDataObjectFilesEx(AFileList, RenderPIDL, RenderFilename)
+  {
+  }
+
+  virtual bool __fastcall AllowData(const tagFORMATETC & FormatEtc)
+  {
+    return (FormatEtc.cfFormat == CF_HDROP) ? false :
+      TDataObjectFilesEx::AllowData(FormatEtc);
+  }
+};
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::RemoteDirViewDDCreateDataObject(
+  TObject * /*Sender*/, TDataObject *& DataObject)
+{
+  if (FDDExtMapFile != NULL)
+  {
+    TFileList * FileList = RemoteDirView->DragDropFilesEx->FileList;
+    if (!FileList->RenderPIDLs() || !FileList->RenderNames())
+    {
+      Abort();
+    }
+
+    if (FileList->Count > 0)
+    {
+      TDataObjectFilesEx * FilesObject = new TFakeDataObjectFilesEx(FileList, true, true);
+      if (!FilesObject->IsValid(true, true))
+      {
+        FilesObject->_Release();
+      }
+      else
+      {
+        DataObject = FilesObject;
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::GoToCommandLine()
+{
+  assert(false);
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::PanelExport(TOperationSide Side,
+  TPanelExport Export, TPanelExportDestination Destination, bool OnFocused)
+{
+  Side = GetSide(Side);
+
+  TCustomDirView * DirView = this->DirView(Side);
+  TStringList * ExportData = new TStringList();
+  try
+  {
+    if (Export == pePath)
+    {
+      ExportData->Add(DirView->PathName);
+    }
+    else
+    {
+      bool FullPath = (Export == peFullFileList);
+      DirView->CreateFileList(OnFocused, FullPath, ExportData);
+      AnsiString FileName;
+      for (int Index = 0; Index < ExportData->Count; Index++)
+      {
+        if (ExportData->Strings[Index].Pos(" ") > 0)
+        {
+          ExportData->Strings[Index] = FORMAT("\"%s\"", (ExportData->Strings[Index]));
+        }
+      }
+    }
+
+    PanelExportStore(Side, Export, Destination, ExportData);
+  }
+  __finally
+  {
+    delete ExportData;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::PanelExportStore(TOperationSide /*Side*/,
+  TPanelExport /*Export*/, TPanelExportDestination Destination,
+  TStringList * ExportData)
+{
+  if (Destination == pedClipboard)
+  {
+    Clipboard()->AsText = ExportData->Text;
+  }
+  else
+  {
+    assert(false);
+  }
+}

+ 4 - 1
forms/CustomScpExplorer.dfm

@@ -64,12 +64,15 @@ object CustomScpExplorerForm: TCustomScpExplorerForm
       UnixColProperties.ExtWidth = 20
       UnixColProperties.ExtVisible = False
       OnGetCopyParam = RemoteDirViewGetCopyParam
+      OnDDTargetDrop = RemoteDirViewDDTargetDrop
       StatusBar = RemoteStatusBar
       OnGetSelectFilter = RemoteDirViewGetSelectFilter
-      TargetPopupMenu = False
       OnExecFile = DirViewExecFile
       OnDDDragEnter = DirViewDDDragEnter
       OnDDDragLeave = DirViewDDDragLeave
+      OnDDCreateDragFileList = RemoteDirViewDDCreateDragFileList
+      OnDDEnd = RemoteDirViewDDEnd
+      OnDDCreateDataObject = RemoteDirViewDDCreateDataObject
       OnContextPopup = RemoteDirViewContextPopup
       OnDisplayProperties = RemoteDirViewDisplayProperties
       OnWarnLackOfTempSpace = RemoteDirViewWarnLackOfTempSpace

+ 30 - 2
forms/CustomScpExplorer.h

@@ -24,6 +24,8 @@ class TSynchronizeProgressForm;
 enum TActionAllowed { aaShortCut, aaUpdate, aaExecute };
 enum TActionFlag { afLocal = 1, afRemote = 2, afExplorer = 4 , afCommander = 8 };
 enum TExecuteFileBy { efDefault, efEditor, efAlternativeEditor };
+enum TPanelExport { pePath, peFileList, peFullFileList };
+enum TPanelExportDestination { pedClipboard, pedCommandLine };
 //---------------------------------------------------------------------------
 class TCustomScpExplorerForm : public TForm
 {
@@ -59,6 +61,13 @@ __published:
     _di_IDataObject DataObj, int grfKeyState, const TPoint &Point,
     int &dwEffect, bool &Accept);
   void __fastcall DirViewDDDragLeave(TObject *Sender);
+  void __fastcall RemoteDirViewDDCreateDragFileList(TObject *Sender,
+    TFileList *FileList, bool &Created);
+  void __fastcall RemoteDirViewDDEnd(TObject *Sender);
+  void __fastcall RemoteDirViewDDTargetDrop(TUnixDirView *Sender,
+    int DropEffect, bool &Continue);
+  void __fastcall RemoteDirViewDDCreateDataObject(TObject *Sender,
+    TDataObject *&DataObject);
   
 private:
   TTerminal * FTerminal;
@@ -72,6 +81,8 @@ private:
   AnsiString FStatusBarHint;
   bool FIgnoreNextSysCommand;
   TStringList * FErrorList;
+  HANDLE FDDExtMutex;
+  AnsiString FDragExtFakeDirectory;
 
   bool __fastcall GetEnableFocusedOperation(TOperationSide Side);
   bool __fastcall GetEnableSelectedOperation(TOperationSide Side);
@@ -89,12 +100,17 @@ protected:
   TProgressForm * FProgressForm;
   AnsiString FCustomCommandName;
   TSynchronizeProgressForm * FSynchronizeProgressForm;
+  HANDLE FDDExtMapFile;
+  int FDDExtDropEffect;
 
   virtual bool __fastcall CopyParamDialog(TTransferDirection Direction,
     TTransferType Type, bool DragDrop, TStrings * FileList,
     AnsiString & TargetDirectory, TCopyParamType & CopyParam, bool Confirm);
+  virtual bool __fastcall RemoteMoveDialog(TStrings * FileList,
+    AnsiString & Target, AnsiString & FileMask, bool NoConfirmation);
   virtual void __fastcall CreateParams(TCreateParams & Params);
   void __fastcall DeleteFiles(TOperationSide Side, TStrings * FileList);
+  void __fastcall RemoteMoveFiles(TStrings * FileList, bool NoConfirmation);
   virtual void __fastcall DoDirViewExecFile(TObject * Sender, TListItem * Item, bool & AllowExec);
   virtual TControl * __fastcall GetComponent(Byte Component);
   virtual TCoolBand * __fastcall GetCoolBand(TCoolBar * Coolbar, int ID);
@@ -121,9 +137,18 @@ protected:
   void __fastcall TerminalSynchronizeDirectory(const AnsiString LocalDirectory,
     const AnsiString RemoteDirectory, bool & Continue);
   bool __fastcall DoFullSynchronizeDirectories(AnsiString & LocalDirectory,
-    AnsiString & RemoteDirectory);
+    AnsiString & RemoteDirectory, TSynchronizeMode & Mode);
   void __fastcall BatchStart(void *& Storage);
   void __fastcall BatchEnd(void * Storage);
+  void __fastcall ExecuteFileOperation(TFileOperation Operation, TOperationSide Side,
+    TStrings * FileList, bool NoConfirmation, void * Param);
+  virtual void __fastcall DDGetTarget(AnsiString & Directory);
+  virtual void __fastcall DDExtInitDrag(TFileList * FileList, bool & Created);
+  virtual void __fastcall DoDirViewEnter(TCustomDirView * DirView);
+  virtual TOperationSide __fastcall GetSide(TOperationSide Side);
+  virtual void __fastcall PanelExportStore(TOperationSide Side,
+    TPanelExport Export, TPanelExportDestination Destination,
+    TStringList * ExportData);
 
   #pragma warn -inl
   BEGIN_MESSAGE_MAP
@@ -152,12 +177,15 @@ public:
   void __fastcall SaveCurrentSession();
   virtual void __fastcall CompareDirectories();
   void __fastcall ExecuteCurrentFile();
-  void __fastcall OpenConsole();
+  virtual void __fastcall OpenConsole(AnsiString Command = "");
   void __fastcall OpenInPutty();
   virtual void __fastcall UpdateSessionData(TSessionData * Data = NULL);
   virtual void __fastcall SynchronizeDirectories();
   virtual void __fastcall FullSynchronizeDirectories();
   virtual void __fastcall ExploreLocalDirectory();
+  virtual void __fastcall GoToCommandLine();
+  virtual void __fastcall PanelExport(TOperationSide Side, TPanelExport Export,
+    TPanelExportDestination Destination, bool OnFocused = false);
   void __fastcall ExecuteFile(TOperationSide Side, TExecuteFileBy ExecuteFileBy);
   void __fastcall LastTerminalClosed(TObject * Sender);
   void __fastcall TerminalListChanged(TObject * Sender);

+ 1 - 1
forms/FileSystemInfo.dfm

@@ -8,7 +8,7 @@ object FileSystemInfoDialog: TFileSystemInfoDialog
   Color = clBtnFace
   ParentFont = True
   OldCreateOrder = True
-  Position = poScreenCenter
+  Position = poMainFormCenter
   DesignSize = (
     367
     372)

+ 12 - 1
forms/FullSynchronize.cpp

@@ -17,7 +17,7 @@
 #pragma resource "*.dfm"
 //---------------------------------------------------------------------------
 bool __fastcall DoFullSynchronizeDialog(TSynchronizeMode & Mode, int & Params,
-  AnsiString & LocalDirectory, AnsiString & RemoteDirectory)
+  AnsiString & LocalDirectory, AnsiString & RemoteDirectory, bool & SaveSettings)
 {
   bool Result;
   TFullSynchronizeDialog * Dialog = new TFullSynchronizeDialog(Application);
@@ -27,6 +27,7 @@ bool __fastcall DoFullSynchronizeDialog(TSynchronizeMode & Mode, int & Params,
     Dialog->Params = Params;
     Dialog->LocalDirectory = LocalDirectory;
     Dialog->RemoteDirectory = RemoteDirectory;
+    Dialog->SaveSettings = SaveSettings;
     Result = Dialog->Execute();
     if (Result)
     {
@@ -34,6 +35,7 @@ bool __fastcall DoFullSynchronizeDialog(TSynchronizeMode & Mode, int & Params,
       Params = Dialog->Params;
       LocalDirectory = Dialog->LocalDirectory;
       RemoteDirectory = Dialog->RemoteDirectory;
+      SaveSettings = Dialog->SaveSettings;
     }
   }
   __finally
@@ -176,4 +178,13 @@ void __fastcall TFullSynchronizeDialog::FormCloseQuery(TObject * /*Sender*/,
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TFullSynchronizeDialog::SetSaveSettings(bool value)
+{
+  SaveSettingsCheck->Checked = value;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TFullSynchronizeDialog::GetSaveSettings()
+{
+  return SaveSettingsCheck->Checked;
+}
 

+ 15 - 7
forms/FullSynchronize.dfm

@@ -3,7 +3,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
   Top = 185
   BorderStyle = bsDialog
   Caption = 'Synchronize'
-  ClientHeight = 243
+  ClientHeight = 265
   ClientWidth = 396
   Color = clBtnFace
   Font.Charset = DEFAULT_CHARSET
@@ -16,7 +16,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
   OnCloseQuery = FormCloseQuery
   DesignSize = (
     396
-    243)
+    265)
   PixelsPerInch = 96
   TextHeight = 13
   object DirectoriesGroup: TXPGroupBox
@@ -30,7 +30,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
     DesignSize = (
       380
       119)
-    object FileNameLabel: TLabel
+    object LocalDirectoryLabel: TLabel
       Left = 11
       Top = 19
       Width = 72
@@ -39,7 +39,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
       Caption = 'Lo&cal directory:'
       FocusControl = LocalDirectoryEdit
     end
-    object Label1: TLabel
+    object RemoteDirectoryLabel: TLabel
       Left = 11
       Top = 68
       Width = 83
@@ -86,7 +86,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
   end
   object OkButton: TButton
     Left = 228
-    Top = 210
+    Top = 232
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -97,7 +97,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
   end
   object CancelButton: TButton
     Left = 312
-    Top = 210
+    Top = 232
     Width = 75
     Height = 25
     Anchors = [akRight, akBottom]
@@ -110,7 +110,7 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
     Left = 8
     Top = 130
     Width = 380
-    Height = 71
+    Height = 95
     Anchors = [akLeft, akTop, akRight]
     Caption = 'Synchronize options'
     TabOrder = 1
@@ -159,5 +159,13 @@ object FullSynchronizeDialog: TFullSynchronizeDialog
       TabOrder = 4
       OnClick = ControlChange
     end
+    object SaveSettingsCheck: TCheckBox
+      Left = 11
+      Top = 68
+      Width = 361
+      Height = 17
+      Caption = 'Use &same options next time'
+      TabOrder = 5
+    end
   end
 end

+ 6 - 2
forms/FullSynchronize.h

@@ -17,8 +17,8 @@ __published:
   TXPGroupBox *DirectoriesGroup;
   TButton *OkButton;
   TButton *CancelButton;
-  TLabel *FileNameLabel;
-  TLabel *Label1;
+  TLabel *LocalDirectoryLabel;
+  TLabel *RemoteDirectoryLabel;
   THistoryComboBox *RemoteDirectoryEdit;
   THistoryComboBox *LocalDirectoryEdit;
   TXPGroupBox *OptionsGroup;
@@ -28,6 +28,7 @@ __published:
   TCheckBox *SynchronizeDeleteCheck;
   TCheckBox *SynchronizeNoConfirmationCheck;
   TButton *LocalDirectoryBrowseButton;
+  TCheckBox *SaveSettingsCheck;
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall LocalDirectoryBrowseButtonClick(TObject *Sender);
   void __fastcall FormCloseQuery(TObject *Sender, bool &CanClose);
@@ -42,6 +43,8 @@ private:
   TSynchronizeMode __fastcall GetMode();
   void __fastcall SetParams(int value);
   int __fastcall GetParams();
+  void __fastcall SetSaveSettings(bool value);
+  bool __fastcall GetSaveSettings();
 
 public:
   __fastcall TFullSynchronizeDialog(TComponent* Owner);
@@ -52,6 +55,7 @@ public:
   __property AnsiString LocalDirectory = { read = GetLocalDirectory, write = SetLocalDirectory };
   __property int Params = { read = GetParams, write = SetParams };
   __property TSynchronizeMode Mode = { read = GetMode, write = SetMode };
+  __property bool SaveSettings = { read = GetSaveSettings, write = SetSaveSettings };
 
 protected:
   void __fastcall UpdateControls();

+ 44 - 16
forms/InputDlg.cpp

@@ -5,26 +5,28 @@
 #include <VCLCommon.h>
 #include <Windows.hpp>
 #include <Consts.hpp>
+#include <HistoryComboBox.hpp>
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 //---------------------------------------------------------------------------
 TPoint __fastcall GetAveCharSize(TCanvas* Canvas)
 {
-	Integer I;
-	Char Buffer[52];
-	TSize Result;
-	for (I = 0; I <= 25; I++) Buffer[I] = (Char)('A' + I);
-	for (I = 0; I <= 25; I++) Buffer[I+26] = (Char)('a' + I);
+  Integer I;
+  Char Buffer[52];
+  TSize Result;
+  for (I = 0; I <= 25; I++) Buffer[I] = (Char)('A' + I);
+  for (I = 0; I <= 25; I++) Buffer[I+26] = (Char)('a' + I);
   GetTextExtentPoint(Canvas->Handle, Buffer, 52, &Result);
   return TPoint(Result.cx / 52, Result.cy);
 }
 //---------------------------------------------------------------------------
 bool __fastcall InputDialog(const AnsiString ACaption,
-	const AnsiString APrompt, AnsiString &Value)
+  const AnsiString APrompt, AnsiString & Value, TStrings * History)
 {
   TForm * Form;
   TLabel * Prompt;
   TEdit * Edit;
+  THistoryComboBox * HistoryCombo;
   TPoint DialogUnits;
   int ButtonTop, ButtonWidth, ButtonHeight;
   bool Result = False;
@@ -47,14 +49,31 @@ bool __fastcall InputDialog(const AnsiString ACaption,
     Prompt->Top = MulDiv(8, DialogUnits.y, 8);
     Prompt->Caption = APrompt;
 
-    Edit = new TEdit(Form);
-    Edit->Parent = Form;
-    Edit->Left = Prompt->Left;
-    Edit->Top = MulDiv(19, DialogUnits.y, 8);
-    Edit->Width = MulDiv(204, DialogUnits.x, 4);
-    Edit->MaxLength = 255;
-    Edit->Text = Value;
-    Edit->SelectAll();
+    TWinControl * EditControl;
+    if (History == NULL)
+    {
+      Edit = new TEdit(Form);
+      Edit->Parent = Form;
+      Edit->Text = Value;
+      Edit->SelectAll();
+      Edit->MaxLength = 255;
+      EditControl = Edit;
+    }
+    else
+    {
+      HistoryCombo = new THistoryComboBox(Form);
+      HistoryCombo->Parent = Form;
+      HistoryCombo->Text = Value;
+      HistoryCombo->SelectAll();
+      HistoryCombo->Items = History;
+      HistoryCombo->MaxLength = 255;
+      EditControl = HistoryCombo;
+    }
+    EditControl->Left = Prompt->Left;
+    EditControl->Top = MulDiv(19, DialogUnits.y, 8);
+    EditControl->Width = MulDiv(204, DialogUnits.x, 4);
+
+    Prompt->FocusControl = EditControl;
 
     ButtonTop = MulDiv(41, DialogUnits.y, 8);
     ButtonWidth = MulDiv(50, DialogUnits.x, 4);
@@ -79,8 +98,17 @@ bool __fastcall InputDialog(const AnsiString ACaption,
 
     if (Form->ShowModal() == mrOk)
     {
-      Value = Edit->Text;
-      Result = True;
+      if (History != NULL)
+      {
+        HistoryCombo->SaveToHistory();
+        History->Assign(HistoryCombo->Items);
+        Value = HistoryCombo->Text;
+      }
+      else
+      {
+        Value = Edit->Text;
+      }
+      Result = true;
     }
   }
   __finally

+ 7 - 2
forms/Login.cpp

@@ -268,7 +268,9 @@ void __fastcall TLoginDialog::LoadSession(TSessionData * aSessionData)
     Scp1CompatibilityCheck->Checked = aSessionData->Scp1Compatibility;
     UnsetNationalVarsCheck->Checked = aSessionData->UnsetNationalVars;
     AliasGroupListCheck->Checked = aSessionData->AliasGroupList;
-    TimeDifferenceEdit->AsInteger = double(aSessionData->TimeDifference) * 24;
+    int TimeDifferenceMin = double(aSessionData->TimeDifference) * 24 * 60;
+    TimeDifferenceEdit->AsInteger = TimeDifferenceMin / 60;
+    TimeDifferenceMinutesEdit->AsInteger = TimeDifferenceMin % 60;
 
     // Proxy tab
     switch (aSessionData->ProxyMethod) {
@@ -388,7 +390,9 @@ void __fastcall TLoginDialog::SaveSession(TSessionData * aSessionData)
     else aSessionData->EOLType = eolCRLF;
   aSessionData->UnsetNationalVars = UnsetNationalVarsCheck->Checked;
   aSessionData->AliasGroupList = AliasGroupListCheck->Checked;
-  aSessionData->TimeDifference = double(TimeDifferenceEdit->AsInteger) / 24;
+  aSessionData->TimeDifference =
+    (double(TimeDifferenceEdit->AsInteger) / 24) +
+    (double(TimeDifferenceMinutesEdit->AsInteger) / 24 / 60);
 
   // Proxy tab
   if (ProxyHTTPButton->Checked) aSessionData->ProxyMethod = pmHTTP;
@@ -687,6 +691,7 @@ void __fastcall TLoginDialog::SaveSessionActionExecute(TObject * /*Sender*/)
     SessionData = NewSession;
 
     ChangePage(SessionListSheet);
+    SessionListView->SetFocus();
   }
 }
 //---------------------------------------------------------------------------

+ 28 - 7
forms/Login.dfm

@@ -10,7 +10,7 @@ object LoginDialog: TLoginDialog
   ParentFont = True
   KeyPreview = True
   OldCreateOrder = True
-  Position = poScreenCenter
+  Position = poMainFormCenter
   OnShow = FormShow
   DesignSize = (
     522
@@ -641,13 +641,21 @@ object LoginDialog: TLoginDialog
             FocusControl = TimeDifferenceEdit
           end
           object Label30: TLabel
-            Left = 204
+            Left = 196
             Top = 88
             Width = 26
             Height = 13
             Caption = 'hours'
             FocusControl = TimeDifferenceEdit
           end
+          object Label9: TLabel
+            Left = 298
+            Top = 88
+            Width = 36
+            Height = 13
+            Caption = 'minutes'
+            FocusControl = TimeDifferenceMinutesEdit
+          end
           object LookupUserGroupsCheck: TCheckBox
             Left = 12
             Top = 17
@@ -705,17 +713,30 @@ object LoginDialog: TLoginDialog
           object TimeDifferenceEdit: TUpDownEdit
             Left = 137
             Top = 83
-            Width = 61
+            Width = 54
             Height = 21
             Alignment = taRightJustify
-            Decimal = 1
-            MaxValue = 12
-            MinValue = -12
-            Value = 1
+            MaxValue = 13
+            MinValue = -13
+            Value = -13
             Anchors = [akTop, akRight]
             TabOrder = 6
             OnChange = DataChange
           end
+          object TimeDifferenceMinutesEdit: TUpDownEdit
+            Left = 239
+            Top = 83
+            Width = 54
+            Height = 21
+            Alignment = taRightJustify
+            Increment = 15
+            MaxValue = 45
+            MinValue = -45
+            Value = -13
+            Anchors = [akTop, akRight]
+            TabOrder = 7
+            OnChange = DataChange
+          end
         end
         object ReturnVarGroup: TXPGroupBox
           Left = 0

+ 2 - 0
forms/Login.h

@@ -196,6 +196,8 @@ __published:
   TRadioButton *PingOffButton;
   TRadioButton *PingNullPacketButton;
   TRadioButton *PingDummyCommandButton;
+  TUpDownEdit *TimeDifferenceMinutesEdit;
+  TLabel *Label9;
   void __fastcall DataChange(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall SessionListViewSelectItem(TObject *Sender,

+ 35 - 4
forms/NonVisual.cpp

@@ -118,6 +118,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPD(CurrentMoveFocusedAction, EnableFocusedOperation)
   UPD(CurrentDeleteFocusedAction, EnableFocusedOperation)
   UPD(CurrentPropertiesFocusedAction, EnableFocusedOperation)
+  UPD(RemoteMoveToFocusedAction, EnableFocusedOperation && (DirView(osRemote) == DirView(osCurrent)))
   // file operation
   UPD(CurrentRenameAction, EnableFocusedOperation &&
     ((ScpExplorer->HasDirView[osLocal] && DirView(osLocal) == DirView(osCurrent)) ||
@@ -141,9 +142,15 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPD(CurrentMoveAction, EnableSelectedOperation)
   UPD(CurrentDeleteAction, EnableSelectedOperation)
   UPD(CurrentPropertiesAction, EnableSelectedOperation)
+  UPD(RemoteMoveToAction, EnableSelectedOperation && (DirView(osRemote) == DirView(osCurrent)))
+  UPD(FileListToCommandLineAction, EnableSelectedOperation &&
+    ((DirView(osLocal) == DirView(osCurrent)) || ScpExplorer->Terminal->IsCapable[fcAnyCommand]))
+  UPD(FileListToClipboardAction, EnableSelectedOperation)
+  UPD(FullFileListToClipboardAction, EnableSelectedOperation)
   // directory
   UPD(CurrentCreateDirAction, true)
   // selection
+  UPD(SelectOneAction, DirView(osCurrent)->FilesCount)
   UPD(SelectAction, DirView(osCurrent)->FilesCount)
   UPD(UnselectAction, DirView(osCurrent)->SelCount)
   UPD(SelectAllAction, DirView(osCurrent)->FilesCount)
@@ -179,7 +186,8 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
     UPD(SIDE ## RefreshAction, DirView(os ## SIDE)->DirOK) \
     UPD(SIDE ## OpenDirAction, true) \
     UPD(SIDE ## ChangePathAction, true) \
-    EXE(SIDE ## AddBookmarkAction, true)
+    UPD(SIDE ## AddBookmarkAction, true) \
+    UPD(SIDE ## PathToClipboardAction, true)
   PANEL_ACTIONS(Local)
   PANEL_ACTIONS(Remote)
   #undef PANEL_ACTIONS
@@ -199,6 +207,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPDCOMP(ToolBar)
   UPDCOMP(LocalStatusBar)
   UPDCOMP(RemoteStatusBar)
+  UPDCOMP(CommandLinePanel)
   UPDCOMP(ExplorerMenuBand)
   UPDCOMP(ExplorerAddressBand)
   UPDCOMP(ExplorerToolbarBand)
@@ -218,9 +227,12 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPDCOMP(CommanderRemoteHistoryBand)
   UPDCOMP(CommanderRemoteNavigationBand)
 
+  UPD(GoToCommandLineAction, true)
   UPDEX(ViewLogAction, Configuration->Logging,
     ViewLogAction->Checked = (WinConfiguration->LogView == lvWindow),
     ViewLogAction->Checked = false )
+  UPDEX(ShowHiddenFilesAction, true,
+    ShowHiddenFilesAction->Checked = WinConfiguration->ShowHiddenFiles, )
   UPD(PreferencesAction, true)
 
   // SORT
@@ -286,7 +298,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPD(FullSynchronizeAction, true)
   UPD(ConsoleAction, ScpExplorer->Terminal && ScpExplorer->Terminal->IsCapable[fcAnyCommand])
   UPD(PuttyAction, true)
-  UPD(SynchorizeBrowsingAction, true)
+  UPD(SynchronizeBrowsingAction, true)
   UPD(CloseApplicationAction, true)
   UPD(FileSystemInfoAction, true)
   UPD(ClearCachesAction, (ScpExplorer->Terminal != NULL) && !ScpExplorer->Terminal->AreCachesEmpty)
@@ -314,6 +326,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
   EXE(CurrentMoveFocusedAction, ScpExplorer->ExecuteFileOperation(foMove, osCurrent, true))
   EXE(CurrentDeleteFocusedAction, ScpExplorer->ExecuteFileOperation(foDelete, osCurrent, true))
   EXE(CurrentPropertiesFocusedAction, ScpExplorer->ExecuteFileOperation(foSetProperties, osCurrent, true))
+  EXE(RemoteMoveToFocusedAction, ScpExplorer->ExecuteFileOperation(foRemoteMove, osCurrent, true))
   // operation
   EXE(CurrentCopyAction, ScpExplorer->ExecuteFileOperation(foCopy, osCurrent, false))
   EXE(CurrentMoveAction, ScpExplorer->ExecuteFileOperation(foMove, osCurrent, false))
@@ -324,9 +337,14 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
   EXE(CurrentRenameAction, ScpExplorer->ExecuteFileOperation(foRename, osCurrent, false))
   EXE(CurrentDeleteAction, ScpExplorer->ExecuteFileOperation(foDelete, osCurrent, false))
   EXE(CurrentPropertiesAction, ScpExplorer->ExecuteFileOperation(foSetProperties, osCurrent, false))
+  EXE(RemoteMoveToAction, ScpExplorer->ExecuteFileOperation(foRemoteMove, osCurrent, false))
+  EXE(FileListToCommandLineAction, ScpExplorer->PanelExport(osCurrent, peFileList, pedCommandLine))
+  EXE(FileListToClipboardAction, ScpExplorer->PanelExport(osCurrent, peFileList, pedClipboard))
+  EXE(FullFileListToClipboardAction, ScpExplorer->PanelExport(osCurrent, peFullFileList, pedClipboard))
   // directory
   EXE(CurrentCreateDirAction, ScpExplorer->CreateDirectory(osCurrent))
   //selection
+  EXE(SelectOneAction, DirView(osCurrent)->SelectCurrentItem(DirView(osCurrent)->NortonLike))
   EXE(SelectAction, DirView(osCurrent)->DoSelectByMask(true))
   EXE(UnselectAction, DirView(osCurrent)->DoSelectByMask(false))
   EXE(SelectAllAction, DirView(osCurrent)->SelectAll(smAll))
@@ -355,7 +373,8 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXE(SIDE ## RefreshAction, DirView(os ## SIDE)->ReloadDirectory()) \
     EXE(SIDE ## OpenDirAction, ScpExplorer->OpenDirectory(os ## SIDE)) \
     EXE(SIDE ## ChangePathAction, ScpExplorer->ChangePath(os ## SIDE)) \
-    EXE(SIDE ## AddBookmarkAction, ScpExplorer->AddBookmark(os ## SIDE))
+    EXE(SIDE ## AddBookmarkAction, ScpExplorer->AddBookmark(os ## SIDE)) \
+    EXE(SIDE ## PathToClipboardAction, ScpExplorer->PanelExport(os ## SIDE, pePath, pedClipboard))
   PANEL_ACTIONS(Local)
   PANEL_ACTIONS(Remote)
   #undef PANEL_ACTIONS
@@ -393,9 +412,12 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
   EXECOMP(CommanderLocalNavigationBand)
   EXECOMP(CommanderRemoteHistoryBand)
   EXECOMP(CommanderRemoteNavigationBand)
+  EXECOMP(CommandLinePanel)
+  EXE(GoToCommandLineAction, ScpExplorer->GoToCommandLine())
 
   EXE(ViewLogAction, WinConfiguration->LogView =
     (WinConfiguration->LogView == lvNone ? lvWindow : lvNone) )
+  EXE(ShowHiddenFilesAction, WinConfiguration->ShowHiddenFiles = !WinConfiguration->ShowHiddenFiles)
   EXE(PreferencesAction, DoPreferencesDialog(pmDefault) )
 
   #define COLVIEWPROPS ((TCustomDirViewColProperties*)(((TCustomDirView*)(((TListColumns*)(ListColumn->Collection))->Owner()))->ColProperties))
@@ -460,7 +482,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
   EXE(FullSynchronizeAction, ScpExplorer->FullSynchronizeDirectories())
   EXE(ConsoleAction, ScpExplorer->OpenConsole())
   EXE(PuttyAction, ScpExplorer->OpenInPutty())
-  EXE(SynchorizeBrowsingAction, )
+  EXE(SynchronizeBrowsingAction, )
   EXE(CloseApplicationAction, ScpExplorer->Close())
   EXE(FileSystemInfoAction, DoFileSystemInfoDialog(ScpExplorer->Terminal))
   EXE(ClearCachesAction, ScpExplorer->Terminal->ClearCaches())
@@ -491,6 +513,7 @@ void __fastcall TNonVisualDataModule::ExplorerShortcuts()
   CurrentMoveFocusedAction->ShortCut = ShortCut('M', CTRL);
   CurrentDeleteFocusedAction->ShortCut = ShortCut(VK_DELETE, NONE);
   CurrentPropertiesFocusedAction->ShortCut = ShortCut(VK_RETURN, ALT);
+  RemoteMoveToFocusedAction->ShortCut = ShortCut('M', CTRLALT);
   // remote directory
   RemoteOpenDirAction->ShortCut = ShortCut('O', CTRL);
   RemoteRefreshAction->ShortCut = ShortCut(VK_F5, NONE);
@@ -500,6 +523,7 @@ void __fastcall TNonVisualDataModule::ExplorerShortcuts()
   CurrentMoveAction->ShortCut = CurrentMoveFocusedAction->ShortCut;
   CurrentDeleteAction->ShortCut = CurrentDeleteFocusedAction->ShortCut;
   CurrentPropertiesAction->ShortCut = CurrentPropertiesFocusedAction->ShortCut;
+  RemoteMoveToAction->ShortCut = ShortCut('M', CTRLALT);
   // selection
   SelectAction->ShortCut = ShortCut(VK_ADD, NONE);
   UnselectAction->ShortCut = ShortCut(VK_SUBTRACT, NONE);
@@ -524,20 +548,27 @@ void __fastcall TNonVisualDataModule::CommanderShortcuts()
   CurrentMoveFocusedAction->ShortCut = ShortCut(VK_F6, NONE);
   CurrentDeleteFocusedAction->ShortCut = ShortCut(VK_F8, NONE);
   CurrentPropertiesFocusedAction->ShortCut = ShortCut(VK_F9, NONE);
+  RemoteMoveToFocusedAction->ShortCut = ShortCut(VK_F6, SHIFT);
   // remote directory
   RemoteOpenDirAction->ShortCut = ShortCut('O', CTRL);
   RemoteRefreshAction->ShortCut = ShortCut('R', CTRL);
   RemoteHomeDirAction->ShortCut = ShortCut('H', CTRL);
+  RemotePathToClipboardAction->ShortCut = ShortCut(VK_OEM_6 /* ] */, CTRL);
   // local directory
   LocalOpenDirAction->ShortCut = RemoteOpenDirAction->ShortCut;
   LocalRefreshAction->ShortCut = RemoteRefreshAction->ShortCut;
   LocalHomeDirAction->ShortCut = RemoteHomeDirAction->ShortCut;
+  LocalPathToClipboardAction->ShortCut = ShortCut(VK_OEM_4 /* [ */, CTRL);
   // selected operation
   CurrentCopyAction->ShortCut = CurrentCopyFocusedAction->ShortCut;
   CurrentMoveAction->ShortCut = CurrentMoveFocusedAction->ShortCut;
   CurrentDeleteAction->ShortCut = CurrentDeleteFocusedAction->ShortCut;
+  CurrentDeleteAction->SecondaryShortCuts->Clear();
+  CurrentDeleteAction->SecondaryShortCuts->Add(ShortCutToText(ShortCut(VK_DELETE, NONE)));
   CurrentPropertiesAction->ShortCut = CurrentPropertiesFocusedAction->ShortCut;
+  RemoteMoveToAction->ShortCut = ShortCut(VK_F6, SHIFT);
   // selection
+  SelectOneAction->ShortCut = VK_INSERT;
   SelectAction->ShortCut = ShortCut(VK_ADD, NONE);
   UnselectAction->ShortCut = ShortCut(VK_SUBTRACT, NONE);
   SelectAllAction->ShortCut = ShortCut('A', CTRL);

+ 147 - 10
forms/NonVisual.dfm

@@ -3,7 +3,7 @@ object NonVisualDataModule: TNonVisualDataModule
   Left = 361
   Top = 156
   Height = 502
-  Width = 543
+  Width = 624
   object LogActions: TActionList
     Images = LogImages
     OnExecute = LogActionsExecute
@@ -3270,6 +3270,9 @@ object NonVisualDataModule: TNonVisualDataModule
     object Moveto1: TMenuItem
       Action = CurrentMoveFocusedAction
     end
+    object Moveto6: TMenuItem
+      Action = RemoteMoveToFocusedAction
+    end
     object Delete1: TMenuItem
       Action = CurrentDeleteFocusedAction
     end
@@ -3299,6 +3302,19 @@ object NonVisualDataModule: TNonVisualDataModule
     OnUpdate = ExplorerActionsUpdate
     Left = 440
     Top = 24
+    object GoToCommandLineAction: TAction
+      Tag = 11
+      Category = 'View'
+      Caption = 'Go To Comma&nd Line'
+      Hint = 'Go to command line'
+      ShortCut = 49230
+    end
+    object SelectOneAction: TAction
+      Tag = 12
+      Category = 'Selection'
+      Caption = '&Select/Unselect'
+      Hint = 'Select|Select/unselect focused file'
+    end
     object CurrentRenameAction: TAction
       Tag = 15
       Category = 'Toolbar Operation (selected + rename + mkdir + close)'
@@ -3390,6 +3406,12 @@ object NonVisualDataModule: TNonVisualDataModule
       Hint = 'Copy|Copy selected file(s) to local directory'
       ImageIndex = 0
     end
+    object RemoteMoveToAction: TAction
+      Tag = 14
+      Category = 'Selected Operation'
+      Caption = 'Mo&ve to ...'
+      Hint = 'Move|Move selected file(s) to remote directory'
+    end
     object CurrentMoveFocusedAction: TAction
       Tag = 12
       Category = 'Focused Operation'
@@ -3444,6 +3466,13 @@ object NonVisualDataModule: TNonVisualDataModule
       Caption = '&Forward'
       ImageIndex = 7
     end
+    object CommandLinePanelAction: TAction
+      Tag = 8
+      Category = 'View'
+      Caption = 'Comma&nd Line'
+      Hint = 'Hide/show command line'
+      ShortCut = 49230
+    end
     object RemoteParentDirAction: TAction
       Tag = 12
       Category = 'Remote Directory'
@@ -3517,7 +3546,7 @@ object NonVisualDataModule: TNonVisualDataModule
     object SelectAction: TAction
       Tag = 15
       Category = 'Selection'
-      Caption = '&Select Files'
+      Caption = 'Sele&ct Files'
       Hint = 'Select|Select files by mask'
       ImageIndex = 19
     end
@@ -4168,7 +4197,7 @@ object NonVisualDataModule: TNonVisualDataModule
         'd with document type'
       ImageIndex = 58
     end
-    object SynchorizeBrowsingAction: TAction
+    object SynchronizeBrowsingAction: TAction
       Tag = 11
       Category = 'Command'
       AutoCheck = True
@@ -4251,6 +4280,52 @@ object NonVisualDataModule: TNonVisualDataModule
       ImageIndex = 66
       ShortCut = 16467
     end
+    object RemoteMoveToFocusedAction: TAction
+      Tag = 14
+      Category = 'Focused Operation'
+      Caption = 'Mo&ve to ...'
+      Hint = 'Move|Move selected file(s) to remote directory'
+    end
+    object ShowHiddenFilesAction: TAction
+      Tag = 15
+      Category = 'View'
+      Caption = 'Show/hide &hidden files'
+      Hint = 'Toggle showing hidden files in panel(s)'
+      ShortCut = 49224
+    end
+    object LocalPathToClipboardAction: TAction
+      Tag = 15
+      Category = 'Local Directory'
+      Caption = 'Copy Path to &Clipboard'
+      Hint = 'Copy current local path to clipboard'
+    end
+    object RemotePathToClipboardAction: TAction
+      Tag = 15
+      Category = 'Remote Directory'
+      Caption = 'Copy Path to &Clipboard'
+      Hint = 'Copy current remote path to clipboard'
+    end
+    object FileListToCommandLineAction: TAction
+      Tag = 11
+      Category = 'Selected Operation'
+      Caption = 'Insert to Command &Line'
+      Hint = 'Insert name(s) of selected file(s) to command line'
+      ShortCut = 16397
+    end
+    object FileListToClipboardAction: TAction
+      Tag = 15
+      Category = 'Selected Operation'
+      Caption = 'Copy to &Clipboard'
+      Hint = 'Copy name(s) of selected file(s) to clipboard'
+      ShortCut = 24643
+    end
+    object FullFileListToClipboardAction: TAction
+      Tag = 15
+      Category = 'Selected Operation'
+      Caption = 'Copy to Clipboard (Include &Paths)'
+      Hint = 'Copy name(s) including path of selected file(s) to clipboard'
+      ShortCut = 49219
+    end
   end
   object ExplorerDisabledImages: TImageList
     Left = 336
@@ -6678,6 +6753,9 @@ object NonVisualDataModule: TNonVisualDataModule
       object Moveto2: TMenuItem
         Action = CurrentMoveAction
       end
+      object Moveto5: TMenuItem
+        Action = RemoteMoveToAction
+      end
       object N39: TMenuItem
         Caption = '-'
         Hint = 'E'
@@ -6687,6 +6765,16 @@ object NonVisualDataModule: TNonVisualDataModule
         object TMenuItem
         end
       end
+      object FileNames2: TMenuItem
+        Caption = '&File Names'
+        Hint = 'Operations with name(s) of selected file(s)'
+        object CopytoClipboard2: TMenuItem
+          Action = FileListToClipboardAction
+        end
+        object CopytoClipboardIncludePaths2: TMenuItem
+          Action = FullFileListToClipboardAction
+        end
+      end
       object N10: TMenuItem
         Caption = '-'
         Hint = 'E'
@@ -6714,6 +6802,9 @@ object NonVisualDataModule: TNonVisualDataModule
       object Addtobookmarks3: TMenuItem
         Action = RemoteAddBookmarkAction
       end
+      object CopyPathtoClipboard5: TMenuItem
+        Action = RemotePathToClipboardAction
+      end
       object N3: TMenuItem
         Caption = '-'
         Hint = 'E'
@@ -7404,6 +7495,9 @@ object NonVisualDataModule: TNonVisualDataModule
     object CommonMarkMenu: TMenuItem
       Caption = '&Mark'
       Hint = 'Mark commands'
+      object SelectUnselect1: TMenuItem
+        Action = SelectOneAction
+      end
       object SelectFiles1: TMenuItem
         Action = SelectAction
       end
@@ -7511,6 +7605,9 @@ object NonVisualDataModule: TNonVisualDataModule
       object Addtobookmarks1: TMenuItem
         Action = LocalAddBookmarkAction
       end
+      object CopyPathtoClipboard3: TMenuItem
+        Action = LocalPathToClipboardAction
+      end
       object N30: TMenuItem
         Caption = '-'
         Hint = 'E'
@@ -7607,6 +7704,9 @@ object NonVisualDataModule: TNonVisualDataModule
       object Moveto3: TMenuItem
         Action = CurrentMoveAction
       end
+      object Moveto4: TMenuItem
+        Action = RemoteMoveToAction
+      end
       object Delete3: TMenuItem
         Action = CurrentDeleteAction
       end
@@ -7622,6 +7722,19 @@ object NonVisualDataModule: TNonVisualDataModule
         object TMenuItem
         end
       end
+      object FileNames1: TMenuItem
+        Caption = '&File Names'
+        Hint = 'Operations with name(s) of selected file(s)'
+        object InserttoCommandLine1: TMenuItem
+          Action = FileListToCommandLineAction
+        end
+        object CopytoClipboard1: TMenuItem
+          Action = FileListToClipboardAction
+        end
+        object CopytoClipboardIncludePaths1: TMenuItem
+          Action = FullFileListToClipboardAction
+        end
+      end
       object N43: TMenuItem
         Caption = '-'
         Hint = 'E'
@@ -7643,7 +7756,7 @@ object NonVisualDataModule: TNonVisualDataModule
         Action = FullSynchronizeAction
       end
       object Synchronizebrowsing1: TMenuItem
-        Action = SynchorizeBrowsingAction
+        Action = SynchronizeBrowsingAction
         AutoCheck = True
       end
       object N47: TMenuItem
@@ -7737,12 +7850,15 @@ object NonVisualDataModule: TNonVisualDataModule
         Caption = '-'
         Hint = 'E'
       end
-      object StatusBar3: TMenuItem
-        Action = StatusBarAction
+      object CommandLine1: TMenuItem
+        Action = CommandLinePanelAction
       end
       object CommandToolbar1: TMenuItem
         Action = ToolBarAction
       end
+      object StatusBar3: TMenuItem
+        Action = StatusBarAction
+      end
       object LogWindow2: TMenuItem
         Action = ViewLogAction
       end
@@ -7800,6 +7916,9 @@ object NonVisualDataModule: TNonVisualDataModule
       object Addtobookmarks2: TMenuItem
         Action = RemoteAddBookmarkAction
       end
+      object CopyPathtoClipboard4: TMenuItem
+        Action = RemotePathToClipboardAction
+      end
       object N33: TMenuItem
         Caption = '-'
         Hint = 'E'
@@ -7899,12 +8018,15 @@ object NonVisualDataModule: TNonVisualDataModule
       Caption = '-'
       Hint = 'E'
     end
-    object StatusBar8: TMenuItem
-      Action = StatusBarAction
+    object CommandLine2: TMenuItem
+      Action = CommandLinePanelAction
     end
     object CommandsToolbar1: TMenuItem
       Action = ToolBarAction
     end
+    object StatusBar8: TMenuItem
+      Action = StatusBarAction
+    end
     object N27: TMenuItem
       Caption = '-'
       Hint = 'E'
@@ -7948,6 +8070,13 @@ object NonVisualDataModule: TNonVisualDataModule
     Images = ExplorerImages
     Left = 312
     Top = 264
+    object CopyPathtoClipboard1: TMenuItem
+      Action = RemotePathToClipboardAction
+    end
+    object N51: TMenuItem
+      Caption = '-'
+      Hint = 'E'
+    end
     object HistoryButtons5: TMenuItem
       Action = CommanderRemoteHistoryBandAction
     end
@@ -7966,6 +8095,13 @@ object NonVisualDataModule: TNonVisualDataModule
     Images = ExplorerImages
     Left = 312
     Top = 336
+    object CopyPathtoClipboard2: TMenuItem
+      Action = LocalPathToClipboardAction
+    end
+    object N52: TMenuItem
+      Caption = '-'
+      Hint = 'E'
+    end
     object HistoryButtons6: TMenuItem
       Action = CommanderLocalHistoryBandAction
     end
@@ -8059,7 +8195,7 @@ object NonVisualDataModule: TNonVisualDataModule
     Left = 168
     Top = 176
     Bitmap = {
-      494C010102000400040010001000FFFFFFFFFF00FFFFFFFFFFFFFFFF424D3600
+      494C010102000400040010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
       0000000000003600000028000000400000001000000001002000000000000010
       0000000000000000000000000000000000000000000000000000000000000000
       0000000000000000000000000000000000000000000000000000000000000000
@@ -8195,6 +8331,7 @@ object NonVisualDataModule: TNonVisualDataModule
       FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000F00FFE7F00000000
       F7EFFDBF00000000FBDFFDBF00000000FBDFFBDF00000000FDBFFBDF00000000
       FDBFF7EF00000000FE7FF00F00000000FFFFFFFF00000000FFFFFFFF00000000
-      FFFFFFFF00000000FFFFFFFF00000000}
+      FFFFFFFF00000000FFFFFFFF0000000000000000000000000000000000000000
+      000000000000}
   end
 end

+ 33 - 1
forms/NonVisual.h

@@ -24,6 +24,7 @@
 #define fcSessionCombo     0x15
 #define fcMenuToolBar      0x16
 #define fcRemotePopup      0x17
+#define fcCommandLinePanel 0x18
 
 #define fcExplorerMenuBand        0x0003
 #define fcExplorerAddressBand     0x0103
@@ -450,7 +451,7 @@ __published:	// IDE-managed Components
   TMenuItem *N41;
   TMenuItem *Open3;
   TMenuItem *N42;
-  TAction *SynchorizeBrowsingAction;
+  TAction *SynchronizeBrowsingAction;
   TMenuItem *Synchronizebrowsing1;
   TAction *AddEditLinkAction;
   TMenuItem *Addeditlink1;
@@ -491,6 +492,37 @@ __published:	// IDE-managed Components
   TMenuItem *N47;
   TMenuItem *N49;
   TMenuItem *N50;
+  TAction *RemoteMoveToAction;
+  TMenuItem *Moveto4;
+  TMenuItem *Moveto5;
+  TAction *RemoteMoveToFocusedAction;
+  TMenuItem *Moveto6;
+  TAction *SelectOneAction;
+  TMenuItem *SelectUnselect1;
+  TAction *ShowHiddenFilesAction;
+  TAction *CommandLinePanelAction;
+  TMenuItem *CommandLine1;
+  TAction *LocalPathToClipboardAction;
+  TAction *RemotePathToClipboardAction;
+  TAction *GoToCommandLineAction;
+  TMenuItem *N51;
+  TMenuItem *CopyPathtoClipboard1;
+  TMenuItem *CopyPathtoClipboard2;
+  TMenuItem *N52;
+  TMenuItem *CopyPathtoClipboard3;
+  TMenuItem *CopyPathtoClipboard4;
+  TAction *FileListToCommandLineAction;
+  TAction *FileListToClipboardAction;
+  TAction *FullFileListToClipboardAction;
+  TMenuItem *FileNames1;
+  TMenuItem *InserttoCommandLine1;
+  TMenuItem *CopytoClipboard1;
+  TMenuItem *CopytoClipboardIncludePaths1;
+  TMenuItem *FileNames2;
+  TMenuItem *CopytoClipboardIncludePaths2;
+  TMenuItem *CopytoClipboard2;
+  TMenuItem *CopyPathtoClipboard5;
+  TMenuItem *CommandLine2;
   void __fastcall LogActionsUpdate(TBasicAction *Action, bool &Handled);
   void __fastcall LogActionsExecute(TBasicAction *Action, bool &Handled);
   void __fastcall ExplorerActionsUpdate(TBasicAction *Action, bool &Handled);

+ 1 - 1
forms/OperationStatus.dfm

@@ -12,7 +12,7 @@ object OperationStatusForm: TOperationStatusForm
   Font.Name = 'MS Sans Serif'
   Font.Style = []
   OldCreateOrder = False
-  Position = poScreenCenter
+  Position = poMainFormCenter
   DesignSize = (
     366
     46)

+ 29 - 3
forms/Preferences.cpp

@@ -121,7 +121,6 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
   #define BOOLPROP(PROP) PROP ## Check->Checked = WinConfiguration->PROP;
   BOOLPROP(DefaultDirIsHome);
   BOOLPROP(DeleteToRecycleBin);
-  BOOLPROP(DDAllowMove);
   BOOLPROP(DDTransferConfirmation);
   BOOLPROP(DDWarnLackOfTempSpace);
   BOOLPROP(ShowHiddenFiles);
@@ -138,6 +137,11 @@ void __fastcall TPreferencesDialog::LoadConfiguration()
   CompareByTimeCheck->Checked = WinConfiguration->ScpCommander.CompareByTime;
   CompareBySizeCheck->Checked = WinConfiguration->ScpCommander.CompareBySize;
 
+  DDExtEnabledButton->Checked = WinConfiguration->DDExtEnabled &&
+    WinConfiguration->DDExtInstalled;
+  DDExtDisabledButton->Checked = !DDExtEnabledButton->Checked;
+  DDWarnOnMoveCheck->Checked = !WinConfiguration->DDAllowMove;
+
   if (WinConfiguration->DDTemporaryDirectory.IsEmpty())
   {
     DDSystemTemporaryDirectoryButton->Checked = true;
@@ -216,7 +220,6 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
     #define BOOLPROP(PROP) WinConfiguration->PROP = PROP ## Check->Checked
     BOOLPROP(DefaultDirIsHome);
     BOOLPROP(DeleteToRecycleBin);
-    BOOLPROP(DDAllowMove);
     BOOLPROP(DDTransferConfirmation);
     BOOLPROP(DDWarnLackOfTempSpace);
     BOOLPROP(ShowHiddenFiles);
@@ -232,6 +235,8 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
 
     WinConfiguration->ScpCommander.CompareByTime = CompareByTimeCheck->Checked;
     WinConfiguration->ScpCommander.CompareBySize = CompareBySizeCheck->Checked;
+    WinConfiguration->DDAllowMove = !DDWarnOnMoveCheck->Checked;
+    WinConfiguration->DDExtEnabled = DDExtEnabledButton->Checked;
 
     if (DDSystemTemporaryDirectoryButton->Checked)
     {
@@ -321,7 +326,6 @@ void __fastcall TPreferencesDialog::ControlChange(TObject * /*Sender*/)
 void __fastcall TPreferencesDialog::UpdateControls()
 {
   EnableControl(CopyOnDoubleClickConfirmationCheck, CopyOnDoubleClickCheck->Checked);
-  EnableControl(DDTemporaryDirectoryEdit, DDCustomTemporaryDirectoryButton->Checked);
   EnableControl(ResumeThresholdEdit, ResumeSmartButton->Checked);
 
   EditorFontLabel->Caption = FMTLOAD(EDITOR_FONT_FMT,
@@ -341,6 +345,12 @@ void __fastcall TPreferencesDialog::UpdateControls()
   EnableControl(UpCommandButton, CustomCommandsView->ItemIndex > 0);
   EnableControl(DownCommandButton, CustomCommandsView->ItemIndex >= 0 &&
     CustomCommandsView->ItemIndex < CustomCommandsView->Items->Count - 1);
+
+  EnableControl(DDExtEnabledButton, WinConfiguration->DDExtInstalled);
+  EnableControl(DDExtEnabledLabel, WinConfiguration->DDExtInstalled);
+  EnableControl(DDExtDisabledPanel, DDExtDisabledButton->Checked);
+  EnableControl(DDTemporaryDirectoryEdit, DDCustomTemporaryDirectoryButton->Enabled && 
+    DDCustomTemporaryDirectoryButton->Checked);
 }
 //---------------------------------------------------------------------------
 void __fastcall TPreferencesDialog::EditorFontButtonClick(TObject * /*Sender*/)
@@ -725,4 +735,20 @@ void __fastcall TPreferencesDialog::Dispatch(void *Message)
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::RegisterAsUrlHandlerButtonClick(
+  TObject * /*Sender*/)
+{
+  if (MessageDialog(LoadStr(CONFIRM_REGISTER_URL),
+        qtConfirmation, qaYes | qaNo, 0) == qaYes)
+  {
+    RegisterAsUrlHandler();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::DDExtLabelClick(TObject * Sender)
+{
+  ((Sender == DDExtEnabledLabel) ? DDExtEnabledButton : DDExtDisabledButton)->
+    Checked = true;
+}
+//---------------------------------------------------------------------------
 

+ 171 - 103
forms/Preferences.dfm

@@ -280,7 +280,7 @@ object PreferencesDialog: TPreferencesDialog
           335)
         object PanelsRemoteDirectoryGroup: TXPGroupBox
           Left = 8
-          Top = 88
+          Top = 105
           Width = 362
           Height = 51
           Anchors = [akLeft, akTop, akRight]
@@ -304,26 +304,26 @@ object PreferencesDialog: TPreferencesDialog
           Left = 8
           Top = 8
           Width = 362
-          Height = 72
+          Height = 91
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Common'
           TabOrder = 0
           DesignSize = (
             362
-            72)
+            91)
           object ShowHiddenFilesCheck: TCheckBox
             Left = 16
             Top = 21
             Width = 330
             Height = 17
             Anchors = [akLeft, akTop, akRight]
-            Caption = '&Show hidden files'
+            Caption = '&Show hidden files (Ctrl+Alt+H)'
             TabOrder = 0
             OnClick = ControlChange
           end
           object DefaultDirIsHomeCheck: TCheckBox
             Left = 16
-            Top = 42
+            Top = 63
             Width = 330
             Height = 17
             Anchors = [akLeft, akTop, akRight]
@@ -331,79 +331,16 @@ object PreferencesDialog: TPreferencesDialog
             TabOrder = 1
             OnClick = ControlChange
           end
-        end
-        object DragDropPreferencesGroup: TXPGroupBox
-          Left = 8
-          Top = 147
-          Width = 362
-          Height = 166
-          Anchors = [akLeft, akTop, akRight]
-          Caption = 'Drag && Drop'
-          TabOrder = 2
-          DesignSize = (
-            362
-            166)
-          object Label5: TLabel
-            Left = 16
-            Top = 47
-            Width = 337
-            Height = 39
-            AutoSize = False
-            Caption = 
-              'When downloading files using drag && drop, they are stored first' +
-              ' to temporary directory.'
-            WordWrap = True
-          end
-          object DDAllowMoveCheck: TCheckBox
+          object DeleteToRecycleBinCheck: TCheckBox
             Left = 16
-            Top = 23
-            Width = 338
+            Top = 42
+            Width = 330
             Height = 17
             Anchors = [akLeft, akTop, akRight]
-            Caption = 'Allow &move from remote directory (not recommended)'
-            TabOrder = 0
-            OnClick = ControlChange
-          end
-          object DDSystemTemporaryDirectoryButton: TRadioButton
-            Left = 32
-            Top = 83
-            Width = 297
-            Height = 17
-            Caption = '&Use temporary directory of system'
-            TabOrder = 1
-            OnClick = ControlChange
-          end
-          object DDCustomTemporaryDirectoryButton: TRadioButton
-            Left = 32
-            Top = 107
-            Width = 129
-            Height = 17
-            Caption = 'Use this &directory:'
+            Caption = '&Delete local files to recycle bin'
             TabOrder = 2
             OnClick = ControlChange
           end
-          object DDTemporaryDirectoryEdit: TDirectoryEdit
-            Left = 168
-            Top = 103
-            Width = 181
-            Height = 21
-            AcceptFiles = True
-            DialogText = 'Select directory for temporary drag && drop files.'
-            ClickKey = 16397
-            Anchors = [akLeft, akTop, akRight]
-            TabOrder = 3
-            Text = 'DDTemporaryDirectoryEdit'
-            OnClick = ControlChange
-          end
-          object DDWarnLackOfTempSpaceCheck: TCheckBox
-            Left = 32
-            Top = 136
-            Width = 321
-            Height = 17
-            Caption = '&Warn when there is not enough free space'
-            TabOrder = 4
-            OnClick = ControlChange
-          end
         end
       end
       object CommanderSheet: TTabSheet
@@ -430,47 +367,37 @@ object PreferencesDialog: TPreferencesDialog
           Left = 8
           Top = 38
           Width = 362
-          Height = 99
+          Height = 75
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Panels'
           TabOrder = 0
           DesignSize = (
             362
-            99)
-          object DeleteToRecycleBinCheck: TCheckBox
-            Left = 16
-            Top = 21
-            Width = 330
-            Height = 17
-            Anchors = [akLeft, akTop, akRight]
-            Caption = '&Delete local files to recycle bin'
-            TabOrder = 0
-            OnClick = ControlChange
-          end
+            75)
           object ExplorerStyleSelectionCheck: TCheckBox
             Left = 16
-            Top = 45
+            Top = 21
             Width = 330
             Height = 17
             Anchors = [akLeft, akTop, akRight]
             Caption = '&Explorer style selection'
-            TabOrder = 1
+            TabOrder = 0
             OnClick = ControlChange
           end
           object PreserveLocalDirectoryCheck: TCheckBox
             Left = 16
-            Top = 69
+            Top = 45
             Width = 330
             Height = 17
             Anchors = [akLeft, akTop, akRight]
             Caption = 'Do not &change local directory when switching sessions'
-            TabOrder = 2
+            TabOrder = 1
             OnClick = ControlChange
           end
         end
         object CommanderMiscGroup: TXPGroupBox
           Left = 8
-          Top = 146
+          Top = 122
           Width = 362
           Height = 53
           Anchors = [akLeft, akTop, akRight]
@@ -492,7 +419,7 @@ object PreferencesDialog: TPreferencesDialog
         end
         object CompareCriterionsGroup: TXPGroupBox
           Left = 8
-          Top = 209
+          Top = 185
           Width = 362
           Height = 74
           Anchors = [akLeft, akTop, akRight]
@@ -826,18 +753,18 @@ object PreferencesDialog: TPreferencesDialog
           Left = 8
           Top = 8
           Width = 362
-          Height = 209
+          Height = 233
           Anchors = [akLeft, akTop, akRight]
           Caption = 'Shell icons'
           TabOrder = 0
           DesignSize = (
             362
-            209)
+            233)
           object ShellIconsLabel: TLabel
             Left = 16
-            Top = 155
+            Top = 184
             Width = 329
-            Height = 46
+            Height = 44
             AutoSize = False
             Caption = 
               'To add shortcuts, which directly open stored session, use button' +
@@ -884,10 +811,20 @@ object PreferencesDialog: TPreferencesDialog
             TabOrder = 3
             OnClick = IconButtonClick
           end
+          object RegisterAsUrlHandlerButton: TButton
+            Left = 16
+            Top = 152
+            Width = 330
+            Height = 25
+            Anchors = [akLeft, akTop, akRight]
+            Caption = 'Register to handle scp:// and sftp:// addresses'
+            TabOrder = 4
+            OnClick = RegisterAsUrlHandlerButtonClick
+          end
         end
         object XPGroupBox1: TXPGroupBox
           Left = 8
-          Top = 224
+          Top = 248
           Width = 362
           Height = 78
           Anchors = [akLeft, akTop, akRight]
@@ -1094,6 +1031,136 @@ object PreferencesDialog: TPreferencesDialog
           end
         end
       end
+      object DragDropSheet: TTabSheet
+        Tag = 11
+        Hint = 'Drag & Drop'
+        Caption = 'DragDrop'
+        ImageIndex = 10
+        DesignSize = (
+          378
+          335)
+        object DragDropDownloadsGroup: TXPGroupBox
+          Left = 8
+          Top = 8
+          Width = 362
+          Height = 267
+          Anchors = [akLeft, akTop, akRight]
+          Caption = 'Drag && Drop download mode'
+          TabOrder = 0
+          DesignSize = (
+            362
+            267)
+          object DDExtEnabledLabel: TLabel
+            Left = 35
+            Top = 44
+            Width = 318
+            Height = 41
+            Anchors = [akLeft, akTop, akRight]
+            AutoSize = False
+            Caption = 
+              'Allows direct downloads to regular local folders (e.g. Window Ex' +
+              'plorer). Does not allow downloads to other destinations (ZIP arc' +
+              'hives,  FTP, etc.)'
+            WordWrap = True
+            OnClick = DDExtLabelClick
+          end
+          object DDExtDisabledLabel: TLabel
+            Left = 35
+            Top = 116
+            Width = 319
+            Height = 41
+            Anchors = [akLeft, akTop, akRight]
+            AutoSize = False
+            Caption = 
+              'Allows downloads to any destinations (regular folders, ZIP archi' +
+              'ves,  FTP, etc.). Files are downloaded first to temporary folder' +
+              ', from where they are delivered to the destination.'
+            WordWrap = True
+            OnClick = DDExtLabelClick
+          end
+          object DDExtEnabledButton: TRadioButton
+            Left = 16
+            Top = 24
+            Width = 337
+            Height = 17
+            Anchors = [akLeft, akTop, akRight]
+            Caption = 'Use &shell extension'
+            TabOrder = 0
+            OnClick = ControlChange
+          end
+          object DDExtDisabledButton: TRadioButton
+            Left = 16
+            Top = 96
+            Width = 329
+            Height = 17
+            Anchors = [akLeft, akTop, akRight]
+            Caption = 'Use &temporary folder'
+            TabOrder = 1
+            OnClick = ControlChange
+          end
+          object DDExtDisabledPanel: TPanel
+            Left = 34
+            Top = 160
+            Width = 325
+            Height = 102
+            BevelOuter = bvNone
+            TabOrder = 2
+            DesignSize = (
+              325
+              102)
+            object DDSystemTemporaryDirectoryButton: TRadioButton
+              Left = 0
+              Top = 0
+              Width = 297
+              Height = 17
+              Caption = '&Use temporary directory of system'
+              TabOrder = 0
+              OnClick = ControlChange
+            end
+            object DDCustomTemporaryDirectoryButton: TRadioButton
+              Left = 0
+              Top = 24
+              Width = 129
+              Height = 17
+              Caption = 'Use this &directory:'
+              TabOrder = 1
+              OnClick = ControlChange
+            end
+            object DDTemporaryDirectoryEdit: TDirectoryEdit
+              Left = 136
+              Top = 20
+              Width = 181
+              Height = 21
+              AcceptFiles = True
+              DialogText = 'Select directory for temporary drag && drop files.'
+              ClickKey = 16397
+              Anchors = [akLeft, akTop, akRight]
+              TabOrder = 2
+              Text = 'DDTemporaryDirectoryEdit'
+              OnClick = ControlChange
+            end
+            object DDWarnLackOfTempSpaceCheck: TCheckBox
+              Left = 0
+              Top = 53
+              Width = 321
+              Height = 17
+              Caption = '&Warn when there is not enough free space'
+              TabOrder = 3
+              OnClick = ControlChange
+            end
+            object DDWarnOnMoveCheck: TCheckBox
+              Left = 0
+              Top = 76
+              Width = 319
+              Height = 17
+              Anchors = [akLeft, akTop, akRight]
+              Caption = 'Warn when &moving to temporary directory'
+              TabOrder = 4
+              OnClick = ControlChange
+            end
+          end
+        end
+      end
     end
     object LeftPanel: TPanel
       Left = 0
@@ -1119,19 +1186,20 @@ object PreferencesDialog: TPreferencesDialog
         TabOrder = 0
         OnChange = NavigationTreeChange
         Items.Data = {
-          06000000250000000000000001000000FFFFFFFFFFFFFFFF0000000004000000
+          06000000250000000000000001000000FFFFFFFFFFFFFFFF0000000005000000
           0C456E7669726F6E6D656E7458230000000000000003000000FFFFFFFFFFFFFF
           FF00000000000000000A496E7465726661636558200000000000000004000000
           FFFFFFFFFFFFFFFF00000000000000000750616E656C73582300000000000000
           05000000FFFFFFFFFFFFFFFF00000000000000000A436F6D6D616E6465725822
           0000000000000006000000FFFFFFFFFFFFFFFF0000000000000000094578706C
-          6F72657258200000000000000008000000FFFFFFFFFFFFFFFF00000000000000
-          0007456469746F7258220000000000000007000000FFFFFFFFFFFFFFFF000000
-          0000000000095472616E7366657258210000000000000002000000FFFFFFFFFF
-          FFFFFF0000000000000000084C6F6767696E6758250000000000000009000000
-          FFFFFFFFFFFFFFFF00000000000000000C496E746567726174696F6E58220000
-          00000000000A000000FFFFFFFFFFFFFFFF000000000000000009436F6D6D616E
-          647358}
+          6F7265725822000000000000000B000000FFFFFFFFFFFFFFFF00000000000000
+          00094472616744726F7058200000000000000008000000FFFFFFFFFFFFFFFF00
+          0000000000000007456469746F7258220000000000000007000000FFFFFFFFFF
+          FFFFFF0000000000000000095472616E73666572582100000000000000020000
+          00FFFFFFFFFFFFFFFF0000000000000000084C6F6767696E6758250000000000
+          000009000000FFFFFFFFFFFFFFFF00000000000000000C496E74656772617469
+          6F6E5822000000000000000A000000FFFFFFFFFFFFFFFF000000000000000009
+          436F6D6D616E647358}
       end
     end
   end

+ 16 - 8
forms/Preferences.h

@@ -58,17 +58,9 @@ __published:
   TXPGroupBox *PanelsCommonGroup;
   TCheckBox *ShowHiddenFilesCheck;
   TCheckBox *DefaultDirIsHomeCheck;
-  TXPGroupBox *DragDropPreferencesGroup;
-  TLabel *Label5;
-  TCheckBox *DDAllowMoveCheck;
-  TRadioButton *DDSystemTemporaryDirectoryButton;
-  TRadioButton *DDCustomTemporaryDirectoryButton;
-  TDirectoryEdit *DDTemporaryDirectoryEdit;
-  TCheckBox *DDWarnLackOfTempSpaceCheck;
   TTabSheet *CommanderSheet;
   TLabel *Label3;
   TXPGroupBox *PanelsGroup;
-  TCheckBox *DeleteToRecycleBinCheck;
   TCheckBox *ExplorerStyleSelectionCheck;
   TCheckBox *PreserveLocalDirectoryCheck;
   TXPGroupBox *CommanderMiscGroup;
@@ -125,6 +117,20 @@ __published:
   TCheckBox *CustomCommandRecursiveCheck;
   TPanel *LeftPanel;
   TTreeView *NavigationTree;
+  TCheckBox *DeleteToRecycleBinCheck;
+  TButton *RegisterAsUrlHandlerButton;
+  TTabSheet *DragDropSheet;
+  TXPGroupBox *DragDropDownloadsGroup;
+  TLabel *DDExtEnabledLabel;
+  TLabel *DDExtDisabledLabel;
+  TRadioButton *DDExtEnabledButton;
+  TRadioButton *DDExtDisabledButton;
+  TPanel *DDExtDisabledPanel;
+  TRadioButton *DDSystemTemporaryDirectoryButton;
+  TRadioButton *DDCustomTemporaryDirectoryButton;
+  TDirectoryEdit *DDTemporaryDirectoryEdit;
+  TCheckBox *DDWarnLackOfTempSpaceCheck;
+  TCheckBox *DDWarnOnMoveCheck;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall ControlChange(TObject *Sender);
   void __fastcall EditorFontButtonClick(TObject *Sender);
@@ -153,6 +159,8 @@ __published:
   void __fastcall CompareBySizeCheckClick(TObject *Sender);
   void __fastcall NavigationTreeChange(TObject *Sender, TTreeNode *Node);
   void __fastcall PageControlChange(TObject *Sender);
+  void __fastcall RegisterAsUrlHandlerButtonClick(TObject *Sender);
+  void __fastcall DDExtLabelClick(TObject *Sender);
 private:
   TPreferencesMode FPreferencesMode;
   TFont * FEditorFont;

+ 6 - 2
forms/Progress.cpp

@@ -18,7 +18,8 @@
 AnsiString __fastcall TProgressForm::OperationName(TFileOperation Operation)
 {
   static const int Captions[] = { PROGRESS_COPY, PROGRESS_MOVE, PROGRESS_DELETE,
-    PROGRESS_SETPROPERTIES, 0, PROGRESS_CUSTOM_COMAND, PROGRESS_CALCULATE_SIZE };
+    PROGRESS_SETPROPERTIES, 0, PROGRESS_CUSTOM_COMAND, PROGRESS_CALCULATE_SIZE,
+    PROGRESS_REMOTE_MOVE };
   assert((int)Operation >= 1 && ((int)Operation - 1) < LENOF(Captions));
   return LoadStr(Captions[(int)Operation - 1]);
 }
@@ -45,14 +46,16 @@ __fastcall TProgressForm::~TProgressForm()
   // to prevent raising assertion (e.g. IsProgress == True)
   FData.Clear();
   if (IsIconic(Application->Handle) && FMinimizedByMe)
+  {
     Application->Restore();
+  }
 
   ReleaseAsModal(this, FShowAsModalStorage);
 }
 //---------------------------------------------------------------------
 void __fastcall TProgressForm::UpdateControls()
 {
-  assert((FData.Operation >= foCopy) && (FData.Operation <= foCalculateSize) &&
+  assert((FData.Operation >= foCopy) && (FData.Operation <= foRemoteMove) &&
     FData.Operation != foRename );
 
   bool TransferOperation =
@@ -69,6 +72,7 @@ void __fastcall TProgressForm::UpdateControls()
       switch (FData.Operation) {
         case foCopy:
         case foMove:
+        case foRemoteMove:
           if (FData.Count == 1) Animate->CommonAVI = aviCopyFile;
             else Animate->CommonAVI = aviCopyFiles;
           break;

+ 366 - 58
forms/ScpCommander.cpp

@@ -9,6 +9,8 @@
 #include <ScpMain.h>
 #include <Interface.h>
 #include <TextsWin.h>
+#include <VCLCommon.h>
+#include <GUITools.h>
 #include <DragDrop.hpp>
 
 #include "NonVisual.h"
@@ -31,6 +33,7 @@
 #pragma link "PathLabel"
 #pragma link "UnixPathComboBox"
 #pragma link "ToolbarPanel"
+#pragma link "HistoryComboBox"
 #pragma resource "*.dfm"
 //---------------------------------------------------------------------------
 __fastcall TScpCommanderForm::TScpCommanderForm(TComponent* Owner)
@@ -42,6 +45,7 @@ __fastcall TScpCommanderForm::TScpCommanderForm(TComponent* Owner)
   FSynchronizeDialog = NULL;
   FSynchronisingBrowse = false;
   FFirstTerminal = true;
+  FInternalDDDownloadList = new TStringList();
 
   LocalBackButton->DropdownMenu = LocalDirView->BackMenu;
   LocalForwardButton->DropdownMenu = LocalDirView->ForwardMenu;
@@ -67,9 +71,18 @@ __fastcall TScpCommanderForm::TScpCommanderForm(TComponent* Owner)
   ((TLabel*)Splitter)->OnDblClick = SplitterDblClick;
   RemotePathComboBox->TabStop = False;
 
+  CommandLineLabel->FocusControl = CommandLineCombo;
+  CommandLineCombo->Text = "";
+  FCommandLineComboPopulated = false;
+
   LocalDirView->Font = Screen->IconFont;
 }
 //---------------------------------------------------------------------------
+__fastcall TScpCommanderForm::~TScpCommanderForm()
+{
+  delete FInternalDDDownloadList;
+}
+//---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::RestoreFormParams()
 {
   assert(WinConfiguration);
@@ -94,6 +107,9 @@ void __fastcall TScpCommanderForm::RestoreParams()
   LoadCoolbarLayoutStr(TopCoolBar, WinConfiguration->ScpCommander.CoolBarLayout);
   StatusBar->Visible = WinConfiguration->ScpCommander.StatusBar;
   ToolbarPanel->Visible = WinConfiguration->ScpCommander.ToolBar;
+
+  CommandLinePanel->Visible = WinConfiguration->ScpCommander.CommandLine;
+
   FDirViewToSelect = (WinConfiguration->ScpCommander.CurrentPanel == osLocal ?
     (TCustomDirView *)LocalDirView : (TCustomDirView *)RemoteDirView);
   #define RESTORE_PANEL_PARAMS(PANEL) \
@@ -103,6 +119,8 @@ void __fastcall TScpCommanderForm::RestoreParams()
   RESTORE_PANEL_PARAMS(Local);
   RESTORE_PANEL_PARAMS(Remote);
   #undef RESTORE_PANEL_PARAMS
+
+  NonVisualDataModule->SynchronizeBrowsingAction->Checked = WinConfiguration->ScpCommander.SynchronizeBrowsing;
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::StoreParams()
@@ -116,6 +134,10 @@ void __fastcall TScpCommanderForm::StoreParams()
     WinConfiguration->ScpCommander.LocalPanelWidth = LocalPanelWidth;
     WinConfiguration->ScpCommander.StatusBar = StatusBar->Visible;
     WinConfiguration->ScpCommander.ToolBar = ToolbarPanel->Visible;
+
+    WinConfiguration->ScpCommander.CommandLine = CommandLinePanel->Visible;
+    SaveCommandLine();
+
     WinConfiguration->ScpCommander.CurrentPanel =
       ((FLastDirView == LocalDirView) ? osLocal : osRemote);
 
@@ -128,6 +150,9 @@ void __fastcall TScpCommanderForm::StoreParams()
     #undef RESTORE_PANEL_PARAMS
 
     WinConfiguration->ScpCommander.WindowParams = WinConfiguration->StoreForm(this);;
+
+    WinConfiguration->ScpCommander.SynchronizeBrowsing = NonVisualDataModule->SynchronizeBrowsingAction->Checked;
+
     TCustomScpExplorerForm::StoreParams();
   }
   __finally
@@ -145,47 +170,50 @@ void __fastcall TScpCommanderForm::UpdateSessionData(TSessionData * Data)
     Data = Terminal->SessionData;
   }
   TCustomScpExplorerForm::UpdateSessionData(Data);
-  if (Data->UpdateDirectories || (Data != Terminal->SessionData))
+
+  assert(LocalDirView);
+  Data->LocalDirectory = LocalDirView->PathName;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TScpCommanderForm::InternalDDDownload(AnsiString & TargetDirectory)
+{
+  bool Result = false;
+  if (LocalDirView->DropTarget)
   {
-    assert(LocalDirView);
-    Data->LocalDirectory = LocalDirView->PathName;
-    Terminal->UserObject = NULL;
+    // when drop target is not directory, it is probably file type, which have
+    // associated drop handler (such as ZIP file in WinXP). in this case we
+    // must leave drop handling to destination application.
+    // ! this check is duplicated in LocalDirViewDDTargetHasDropHandler()
+    // for shellex downloads
+    if (LocalDirView->ItemIsDirectory(LocalDirView->DropTarget))
+    {
+      TargetDirectory = LocalDirView->ItemFullFileName(LocalDirView->DropTarget);
+      Result = true;
+    }
   }
   else
   {
-    if (!Terminal->UserObject)
-    {
-      Terminal->UserObject = new TTerminalUserObject();
-    }
-    dynamic_cast<TTerminalUserObject *>(Terminal->UserObject)->LocalDirectory =
-      LocalDirView->PathName;
+    TargetDirectory = IncludeTrailingBackslash(LocalDirView->Path);
+    Result = true;
   }
+  return Result;
 }
 //---------------------------------------------------------------------------
 bool __fastcall TScpCommanderForm::CopyParamDialog(TTransferDirection Direction,
   TTransferType Type, bool DragDrop, TStrings * FileList, AnsiString & TargetDirectory,
   TCopyParamType & CopyParam, bool Confirm)
 {
+  bool Result = false;
   if (DragDrop && (Direction == tdToLocal) && (FDDTargetDirView == LocalDirView))
   {
-    if (LocalDirView->DropTarget)
+    Result = InternalDDDownload(TargetDirectory);
+    if (Result)
     {
-      // when drop target is not directory, it is probably file type, which have
-      // associated drop handler (sich as ZIP file in WinXP). in this case we
-      // must leave drop handling to destination application.
-      DragDrop = !LocalDirView->ItemIsDirectory(LocalDirView->DropTarget);
-      if (!DragDrop)
-      {
-        TargetDirectory = LocalDirView->ItemFullFileName(LocalDirView->DropTarget);
-      }
-    }
-    else
-    {
-      DragDrop = false;
-      TargetDirectory = IncludeTrailingBackslash(LocalDirView->Path);
+      assert(FileList->Count > 0);
+      FInternalDDDownloadList->Assign(FileList);
     }
   }
-  else if (!DragDrop)
+  else if (!DragDrop && TargetDirectory.IsEmpty())
   {
     if (Direction == tdToLocal)
     {
@@ -197,8 +225,12 @@ bool __fastcall TScpCommanderForm::CopyParamDialog(TTransferDirection Direction,
     }
   }
 
-  return TCustomScpExplorerForm::CopyParamDialog(Direction, Type, DragDrop,
-    FileList, TargetDirectory, CopyParam, Confirm);
+  if (!Result)
+  {
+    Result = TCustomScpExplorerForm::CopyParamDialog(Direction, Type, DragDrop,
+      FileList, TargetDirectory, CopyParam, Confirm);
+  }
+  return Result;
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::FormShow(TObject */*Sender*/)
@@ -239,28 +271,36 @@ TCustomDirView * __fastcall TScpCommanderForm::DirView(TOperationSide Side)
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TScpCommanderForm::ExecuteFileOperation(::TFileOperation Operation, TOperationSide Side, Boolean OnFocused)
+TOperationSide __fastcall TScpCommanderForm::GetSide(TOperationSide Side)
 {
-  TCustomScpExplorerForm::ExecuteFileOperation(Operation, Side, OnFocused);
+  if (Side == osCurrent)
+  {
+    if (FLastDirView == RemoteDirView)
+    {
+      Side = osRemote;
+    }
+    else
+    {
+      assert(FLastDirView);
+      Side = osLocal;
+    }
+  }
+
+  return TCustomScpExplorerForm::GetSide(Side);
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::TerminalChanged()
 {
-  TCustomScpExplorerForm::TerminalChanged();
   if (Terminal)
   {
+    bool WasSynchronisingBrowsing = NonVisualDataModule->SynchronizeBrowsingAction->Checked;
+    NonVisualDataModule->SynchronizeBrowsingAction->Checked = false;
+
+    TCustomScpExplorerForm::TerminalChanged();
+
     if (FFirstTerminal || !WinConfiguration->ScpCommander.PreserveLocalDirectory)
     {
-      AnsiString LocalDirectory;
-
-      if (Terminal->UserObject)
-      {
-        LocalDirectory = dynamic_cast<TTerminalUserObject *>(Terminal->UserObject)->LocalDirectory;
-      }
-      else
-      {
-        LocalDirectory = Terminal->SessionData->LocalDirectory;
-      }
+      AnsiString LocalDirectory = Terminal->SessionData->LocalDirectory;
       bool DocumentsDir = LocalDirectory.IsEmpty();
 
       if (!DocumentsDir)
@@ -297,6 +337,17 @@ void __fastcall TScpCommanderForm::TerminalChanged()
       }
     }
     FFirstTerminal = false;
+
+    if (WasSynchronisingBrowsing &&
+        SameText(ExtractFileName(LocalDirView->PathName),
+          UnixExtractFileName(RemoteDirView->PathName)))
+    {
+      NonVisualDataModule->SynchronizeBrowsingAction->Checked = true;
+    }
+  }
+  else
+  {
+    TCustomScpExplorerForm::TerminalChanged();
   }
 }
 //---------------------------------------------------------------------------
@@ -316,6 +367,9 @@ void __fastcall TScpCommanderForm::ConfigurationChanged()
 
   LocalDirView->NortonLike = !WinConfiguration->ScpCommander.ExplorerStyleSelection;
   RemoteDirView->NortonLike = !WinConfiguration->ScpCommander.ExplorerStyleSelection;
+
+  LocalDirView->DragDropFilesEx->ShellExtensions->DropHandler =
+    !WinConfiguration->DDExtEnabled;
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::SetLocalPanelWidth(float value)
@@ -361,12 +415,21 @@ void __fastcall TScpCommanderForm::SplitterDblClick(TObject * /*Sender*/)
 void __fastcall TScpCommanderForm::UpdateControls()
 {
   Splitter->Hint = FormatFloat("0%|X", LocalPanelWidth*100);
+  if (FLastDirView != NULL)
+  {
+    CommandLineLabel->UnixPath = (FLastDirView == RemoteDirView);
+    CommandLineLabel->Caption = FLastDirView->PathName;
+    CommandLinePromptLabel->Caption =
+      (FLastDirView == RemoteDirView) ? "$" : ">";
+    EnableControl(CommandLineCombo,
+      (FLastDirView == LocalDirView) || Terminal->IsCapable[fcAnyCommand]);
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::ChangePath(TOperationSide Side)
 {
   assert((Side == osLocal) || (Side == osRemote));
-  TCustomPathComboBox * PathComboBox; 
+  TCustomPathComboBox * PathComboBox;
   if (Side == osLocal) PathComboBox = LocalPathComboBox;
     else PathComboBox = RemotePathComboBox;
   assert(PathComboBox);
@@ -403,6 +466,7 @@ TControl * __fastcall TScpCommanderForm::GetComponent(Byte Component)
     case fcRemoteStatusBar: return RemoteStatusBar;
     case fcSessionCombo: return SessionCombo;
     case fcMenuToolBar: return MenuToolBar;
+    case fcCommandLinePanel: return CommandLinePanel; 
     default: return TCustomScpExplorerForm::GetComponent(Component);
   }
 }
@@ -410,24 +474,18 @@ TControl * __fastcall TScpCommanderForm::GetComponent(Byte Component)
 void __fastcall TScpCommanderForm::SetComponentVisible(Word Component, Boolean value)
 {
   TCustomScpExplorerForm::SetComponentVisible(Component, value);
-  if (StatusBar->Top < ToolbarPanel->Top)
+  if ((StatusBar->Top < ToolbarPanel->Top) && ToolbarPanel->Visible)
   {
     StatusBar->Top = ToolbarPanel->Top + ToolbarPanel->Height;
   }
-}
-//---------------------------------------------------------------------------
-void __fastcall TScpCommanderForm::KeyDown(Word & Key, Classes::TShiftState Shift)
-{
-  // duplicate shortcut for deleting
-  if ((ShortCut(VK_DELETE, TShiftState()) == ShortCut(Key, Shift)) &&
-      !DirView(osCurrent)->IsEditing())
+  if ((ToolbarPanel->Top < CommandLinePanel->Top) && CommandLinePanel->Visible)
   {
-    NonVisualDataModule->CurrentDeleteAction->Execute();
-    Key = 0;
+    ToolbarPanel->Top = CommandLinePanel->Top + CommandLinePanel->Height;
   }
-  else
+
+  if (LocalDirView->ItemFocused != NULL)
   {
-    TCustomScpExplorerForm::KeyDown(Key, Shift);
+    LocalDirView->ItemFocused->MakeVisible(false);
   }
 }
 //---------------------------------------------------------------------------
@@ -482,7 +540,8 @@ void __fastcall TScpCommanderForm::FullSynchronizeDirectories()
 {
   AnsiString LocalDirectory = LocalDirView->PathName;
   AnsiString RemoteDirectory = RemoteDirView->PathName;
-  DoFullSynchronizeDirectories(LocalDirectory, RemoteDirectory);
+  TSynchronizeMode Mode = (FLastDirView == LocalDirView) ? smRemote : smLocal;
+  DoFullSynchronizeDirectories(LocalDirectory, RemoteDirectory, Mode);
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::LocalDirViewChangeDetected(
@@ -606,14 +665,16 @@ void __fastcall TScpCommanderForm::FileOperationProgress(
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::DirViewLoaded(TObject *Sender)
 {
+  UpdateControls();
+  
   try
   {
     TCustomDirView * ADirView = dynamic_cast<TCustomDirView *>(Sender);
     assert(ADirView);
     AnsiString PrevPath = FPrevPath[ADirView == LocalDirView];
     FPrevPath[ADirView == LocalDirView] = ADirView->Path;
-    
-    if (!FSynchronisingBrowse && NonVisualDataModule->SynchorizeBrowsingAction->Checked &&
+
+    if (!FSynchronisingBrowse && NonVisualDataModule->SynchronizeBrowsingAction->Checked &&
         !PrevPath.IsEmpty() && PrevPath != ADirView->Path)
     {
       FSynchronisingBrowse = true;
@@ -671,7 +732,7 @@ void __fastcall TScpCommanderForm::DirViewLoaded(TObject *Sender)
   catch(Exception & E)
   {
     FSynchronisingBrowse = false;
-    NonVisualDataModule->SynchorizeBrowsingAction->Checked = false;
+    NonVisualDataModule->SynchronizeBrowsingAction->Checked = false;
     if (!Application->Terminated)
     {
       ShowExtendedException(&E);
@@ -783,5 +844,252 @@ void __fastcall TScpCommanderForm::DoOpenDirectoryDialog(TOpenDirectoryMode Mode
     TCustomScpExplorerForm::DoOpenDirectoryDialog(Mode, Side);
   }
 }
-
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::LocalDirViewDDTargetHasDropHandler(
+  TObject * /*Sender*/, TListItem * Item, int & /*Effect*/, bool & DropHandler)
+{
+  // when drop target is not directory, it is probably file type, which have
+  // associated drop handler (such as ZIP file in WinXP). in this case we
+  // cannot allow downloading when using shellex.
+  // ! this check is duplicated in InternalDDDownload() for non-shellex downloads
+  if ((FDDExtMapFile != NULL) &&
+      !LocalDirView->ItemIsDirectory(Item))
+  {
+    DropHandler = false;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::LocalDirViewDDDragOver(TObject * /*Sender*/,
+  int grfKeyState, TPoint & /*Point*/, int & dwEffect)
+{
+  if ((grfKeyState & (MK_CONTROL | MK_SHIFT)) == 0)
+  {
+    if (DropSourceControl == RemoteDirView)
+    {
+      dwEffect = DROPEFFECT_Copy;
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::DDGetTarget(AnsiString & Directory)
+{
+  if (!FDDExtTarget.IsEmpty())
+  {
+    Directory = FDDExtTarget;
+    FDDExtTarget = "";
+  }
+  else
+  {
+    TCustomScpExplorerForm::DDGetTarget(Directory);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::DDExtInitDrag(TFileList * FileList,
+  bool & Created)
+{
+  FDDExtTarget = "";
+  TCustomScpExplorerForm::DDExtInitDrag(FileList, Created);
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::LocalDirViewDDFileOperation(
+  TObject * /*Sender*/, int dwEffect, AnsiString SourcePath,
+  AnsiString TargetPath, bool & DoOperation)
+{
+  if (DropSourceControl == RemoteDirView)
+  {
+    AnsiString TargetDirectory;
+    if (InternalDDDownload(TargetDirectory))
+    {
+      if (FDDExtMapFile != NULL)
+      {
+        FDDExtTarget = TargetDirectory;
+      }
+      else
+      {
+        assert(FInternalDDDownloadList->Count > 0);
+        assert(dwEffect == DROPEFFECT_Copy || dwEffect == DROPEFFECT_Move);
+        TCopyParamType CopyParams = Configuration->CopyParam;
+        if (CopyParamDialog(tdToLocal, dwEffect == DROPEFFECT_Copy ? ttCopy : ttMove,
+              false, FInternalDDDownloadList, TargetDirectory, CopyParams,
+              WinConfiguration->DDTransferConfirmation))
+        {
+          Terminal->CopyToLocal(FInternalDDDownloadList, TargetDirectory, &CopyParams,
+            (dwEffect == DROPEFFECT_Move ? cpDelete : 0));
+          FInternalDDDownloadList->Clear();
+        }
+      }
+      DoOperation = false;
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::RemoteDirViewDDFileOperationExecuted(
+  TObject * /*Sender*/, int dwEffect, AnsiString /*SourcePath*/,
+  AnsiString /*TargetPath*/)
+{
+  if ((dwEffect == DROPEFFECT_Move) && (DropSourceControl == LocalDirView))
+  {
+    LocalDirView->Reload(true);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::DoDirViewEnter(TCustomDirView * DirView)
+{
+  if (FLastDirView != DirView)
+  {
+    CommandLineCombo->Items->Clear();
+    FCommandLineComboPopulated = false;
+  }
+  TCustomScpExplorerForm::DoDirViewEnter(DirView);
+  UpdateControls();
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::OpenConsole(AnsiString Command)
+{
+  SaveCommandLine();
+  try
+  {
+    TCustomScpExplorerForm::OpenConsole(Command);
+  }
+  __finally
+  {
+    FCommandLineComboPopulated = false;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::CommandLineComboKeyDown(TObject * /*Sender*/,
+  WORD & Key, TShiftState /*Shift*/)
+{
+  if (Key == VK_RETURN)
+  {
+    Key = 0;
+    ExecuteCommandLine();
+  }
+  else if ((Key == VK_ESCAPE) && !CommandLineCombo->DroppedDown)
+  {
+    Key = 0;
+    CommandLineCombo->Text = "";
+  }
+  else if ((Key == VK_UP) || (Key == VK_DOWN))
+  {
+    CommandLinePopulate();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::SaveCommandLine()
+{
+  if (FCommandLineComboPopulated)
+  {
+    CustomWinConfiguration->History[
+      FLastDirView == RemoteDirView ? "Commands" : "LocalCommands"] =
+        CommandLineCombo->Items;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::ExecuteCommandLine()
+{
+  if (!CommandLineCombo->Text.Trim().IsEmpty())
+  {
+    CommandLinePopulate();
+    CommandLineCombo->SaveToHistory();
+    AnsiString Command = CommandLineCombo->Text;
+    CommandLineCombo->Text = "";
+    if (FLastDirView == RemoteDirView)
+    {
+      OpenConsole(Command);
+    }
+    else
+    {
+      AnsiString Program, Params, Dir;
+      SplitCommand(Command, Program, Params, Dir);
+      if (!ExecuteShell(Program, Params))
+      {
+        throw Exception(FMTLOAD(EXECUTE_APP_ERROR, (Program)));
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::CommandLineComboDropDown(
+  TObject * /*Sender*/)
+{
+  CommandLinePopulate();
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::CommandLinePopulate()
+{
+  if (!FCommandLineComboPopulated)
+  {
+    TStrings * CommandsHistory;
+    CommandsHistory = CustomWinConfiguration->History[
+      FLastDirView == RemoteDirView ? "Commands" : "LocalCommands"];
+    if ((CommandsHistory != NULL) && (CommandsHistory->Count > 0))
+    {
+      CommandLineCombo->Items = CommandsHistory;
+    }
+    else
+    {
+      CommandLineCombo->Items->Clear();
+    }
+    FCommandLineComboPopulated = true;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::GoToCommandLine()
+{
+  ComponentVisible[fcCommandLinePanel] = true;
+  if (CommandLineCombo->Enabled)
+  {
+    CommandLineCombo->SetFocus();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::CommandLineComboEnter(TObject * /*Sender*/)
+{
+  KeyPreview = false;
+  TPanel * LastPanel = FLastDirView == LocalDirView ? LocalPanel : RemotePanel;
+  if (CommandLinePanel->TabOrder > LastPanel->TabOrder)
+  {
+    CommandLinePanel->TabOrder = LastPanel->TabOrder;
+  }
+  else if (CommandLinePanel->TabOrder < LastPanel->TabOrder - 1)
+  {
+    CommandLinePanel->TabOrder = static_cast<TTabOrder>(LastPanel->TabOrder - 1);
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::CommandLineComboExit(TObject * /*Sender*/)
+{
+  KeyPreview = true;
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::PanelExportStore(TOperationSide Side,
+  TPanelExport Export, TPanelExportDestination Destination,
+  TStringList * ExportData)
+{
+  if (Destination == pedCommandLine)
+  {
+    ComponentVisible[fcCommandLinePanel] = true;
+    
+    AnsiString Buf;
+    for (int Index = 0; Index < ExportData->Count; Index++)
+    {
+      Buf += ExportData->Strings[Index] + " ";
+    }
+    
+    if (CommandLineCombo->Focused())
+    {
+      CommandLineCombo->SelText = Buf;
+    }
+    else
+    {
+      CommandLineCombo->Text = CommandLineCombo->Text + Buf;
+    }
+  }
+  else
+  {
+    TCustomScpExplorerForm::PanelExportStore(Side, Export, Destination, ExportData);
+  }
+}
+//---------------------------------------------------------------------------
 

+ 65 - 10
forms/ScpCommander.dfm

@@ -13,7 +13,7 @@ inherited ScpCommanderForm: TScpCommanderForm
     Left = 313
     Top = 170
     Width = 5
-    Height = 338
+    Height = 317
     Cursor = crHSplit
     ResizeStyle = rsUpdate
     OnCanResize = SplitterCanResize
@@ -550,7 +550,7 @@ inherited ScpCommanderForm: TScpCommanderForm
       object ToolButton46: TToolButton
         Left = 108
         Top = 0
-        Action = NonVisualDataModule.SynchorizeBrowsingAction
+        Action = NonVisualDataModule.SynchronizeBrowsingAction
         Style = tbsCheck
       end
     end
@@ -559,7 +559,7 @@ inherited ScpCommanderForm: TScpCommanderForm
     Left = 318
     Top = 170
     Width = 335
-    Height = 338
+    Height = 317
     Constraints.MinWidth = 170
     TabOrder = 1
     object RemotePathLabel: TPathLabel [0]
@@ -567,17 +567,19 @@ inherited ScpCommanderForm: TScpCommanderForm
       Top = 72
       Width = 335
       Height = 15
+      UnixPath = True
+      AutoSize = False
       PopupMenu = NonVisualDataModule.RemotePanelPopup
     end
     inherited RemoteStatusBar: TAssociatedStatusBar
-      Top = 319
+      Top = 298
       Width = 335
       Hint = ''
     end
     inherited RemoteDirView: TUnixDirView
       Top = 87
       Width = 335
-      Height = 232
+      Height = 211
       Constraints.MinHeight = 100
       RowSelect = True
       NortonLike = True
@@ -585,6 +587,7 @@ inherited ScpCommanderForm: TScpCommanderForm
       PathLabel = RemotePathLabel
       AddParentDir = True
       OnLoaded = DirViewLoaded
+      OnDDFileOperationExecuted = RemoteDirViewDDFileOperationExecuted
       OnWarnLackOfTempSpace = nil
     end
     object RemoteCoolBar: TCoolBar
@@ -737,7 +740,7 @@ inherited ScpCommanderForm: TScpCommanderForm
     Left = 0
     Top = 170
     Width = 313
-    Height = 338
+    Height = 317
     Align = alLeft
     BevelOuter = bvNone
     Constraints.MinWidth = 170
@@ -747,11 +750,12 @@ inherited ScpCommanderForm: TScpCommanderForm
       Top = 72
       Width = 313
       Height = 15
+      AutoSize = False
       PopupMenu = NonVisualDataModule.LocalPanelPopup
     end
     object LocalStatusBar: TAssociatedStatusBar
       Left = 0
-      Top = 319
+      Top = 298
       Width = 313
       Height = 19
       Panels = <
@@ -771,7 +775,7 @@ inherited ScpCommanderForm: TScpCommanderForm
       Left = 0
       Top = 87
       Width = 313
-      Height = 232
+      Height = 211
       Align = alClient
       Constraints.MinHeight = 100
       FullDrag = True
@@ -787,11 +791,13 @@ inherited ScpCommanderForm: TScpCommanderForm
       StatusBar = LocalStatusBar
       OnGetSelectFilter = RemoteDirViewGetSelectFilter
       HeaderImages = NonVisualDataModule.ArrowImages
-      TargetPopupMenu = False
       AddParentDir = True
       OnLoaded = DirViewLoaded
       OnDDDragEnter = LocalDirViewDDDragEnter
       OnDDDragLeave = DirViewDDDragLeave
+      OnDDDragOver = LocalDirViewDDDragOver
+      OnDDTargetHasDropHandler = LocalDirViewDDTargetHasDropHandler
+      OnDDFileOperation = LocalDirViewDDFileOperation
       OnExecFile = LocalDirViewExecFile
       ConfirmDelete = False
       ConfirmOverwrite = False
@@ -955,7 +961,7 @@ inherited ScpCommanderForm: TScpCommanderForm
     Stretch = True
     DisabledImages = NonVisualDataModule.ExplorerDisabledImages
     PopupMenu = NonVisualDataModule.CommanderBarPopup
-    TabOrder = 3
+    TabOrder = 4
   end
   object StatusBar: TStatusBar
     Left = 0
@@ -1006,4 +1012,53 @@ inherited ScpCommanderForm: TScpCommanderForm
     OnMouseMove = SessionStatusBarMouseMove
     OnDrawPanel = SessionStatusBarDrawPanel
   end
+  object CommandLinePanel: TPanel
+    Left = 0
+    Top = 487
+    Width = 653
+    Height = 21
+    Align = alBottom
+    BevelOuter = bvNone
+    PopupMenu = NonVisualDataModule.CommanderBarPopup
+    TabOrder = 2
+    DesignSize = (
+      653
+      21)
+    object CommandLineLabel: TPathLabel
+      Left = 4
+      Top = 4
+      Width = 159
+      Height = 15
+      IndentHorizontal = 0
+      IndentVertical = 0
+      Align = alNone
+      Alignment = taRightJustify
+      AutoSize = False
+    end
+    object CommandLinePromptLabel: TLabel
+      Left = 164
+      Top = 4
+      Width = 6
+      Height = 13
+      Caption = '>'
+    end
+    object CommandLineCombo: THistoryComboBox
+      Left = 173
+      Top = 0
+      Width = 477
+      Height = 21
+      AutoComplete = False
+      Anchors = [akLeft, akTop, akRight, akBottom]
+      ItemHeight = 13
+      MaxLength = 250
+      TabOrder = 0
+      TabStop = False
+      Text = 'CommandLineCombo'
+      OnDropDown = CommandLineComboDropDown
+      OnEnter = CommandLineComboEnter
+      OnExit = CommandLineComboExit
+      OnKeyDown = CommandLineComboKeyDown
+      SaveOn = []
+    end
+  end
 end

+ 42 - 6
forms/ScpCommander.h

@@ -28,6 +28,7 @@
 #include <WinInterface.h>
 
 #include <Synchronize.h>
+#include "HistoryComboBox.hpp"
 //---------------------------------------------------------------------------
 class TScpCommanderForm : public TCustomScpExplorerForm
 {
@@ -125,6 +126,10 @@ __published:
   TComboBox *SessionCombo;
   TToolButton *ToolButton49;
   TToolButton *ToolButton51;
+  TPanel *CommandLinePanel;
+  THistoryComboBox *CommandLineCombo;
+  TPathLabel *CommandLineLabel;
+  TLabel *CommandLinePromptLabel;
   void __fastcall FormShow(TObject *Sender);
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
@@ -135,12 +140,26 @@ __published:
   void __fastcall FormResize(TObject *Sender);
   void __fastcall LocalDirViewChangeDetected(TObject *Sender);
   void __fastcall LocalDirViewExecFile(TObject *Sender, TListItem *Item,
-          bool &AllowExec);
+    bool &AllowExec);
   void __fastcall LocalDirViewDDDragEnter(TObject *Sender,
-          IDataObject *DataObj, int grfKeyState, TPoint &Point,
-          int &dwEffect, bool &Accept);
+    IDataObject *DataObj, int grfKeyState, TPoint &Point,
+    int &dwEffect, bool &Accept);
   void __fastcall DirViewLoaded(TObject *Sender);
   void __fastcall SessionComboCloseUp(TObject *Sender);
+  void __fastcall LocalDirViewDDDragOver(TObject *Sender, int grfKeyState,
+    TPoint &Point, int &dwEffect);
+  void __fastcall LocalDirViewDDFileOperation(TObject *Sender,
+    int dwEffect, AnsiString SourcePath, AnsiString TargetPath,
+    bool &DoOperation);
+  void __fastcall RemoteDirViewDDFileOperationExecuted(TObject *Sender,
+    int dwEffect, AnsiString SourcePath, AnsiString TargetPath);
+  void __fastcall CommandLineComboKeyDown(TObject *Sender, WORD &Key,
+    TShiftState Shift);
+  void __fastcall CommandLineComboDropDown(TObject *Sender);
+  void __fastcall CommandLineComboEnter(TObject *Sender);
+  void __fastcall CommandLineComboExit(TObject *Sender);
+  void __fastcall LocalDirViewDDTargetHasDropHandler(TObject *Sender,
+    TListItem *Item, int &Effect, bool &DropHandler);
 
 private:
   TCustomDirView * FDirViewToSelect;
@@ -148,11 +167,15 @@ private:
   float FLocalPanelWidth;
   int FLastWidth;
   bool FSynchronisingBrowse;
+  TStrings * FInternalDDDownloadList;
   TSynchronizationStatus FSynchronization;
   TSynchronizeParamType FSynchronizeParams;
   TSynchronizeDialog * FSynchronizeDialog;
   AnsiString FPrevPath[2];
   bool FFirstTerminal;
+  AnsiString FDDExtTarget;
+  bool FCommandLineComboPopulated;
+
   void __fastcall SetLocalPanelWidth(float value);
   float __fastcall GetLocalPanelWidth();
 
@@ -161,7 +184,6 @@ protected:
     TTransferType Type, bool DragDrop, TStrings * FileList,
     AnsiString & TargetDirectory, TCopyParamType & CopyParam, bool Confirm);
   virtual TCustomDirView * __fastcall DirView(TOperationSide Side);
-  virtual void __fastcall ExecuteFileOperation(::TFileOperation Operation, TOperationSide Side, Boolean OnFocused);
   TControl * __fastcall GetComponent(Byte Component);
   virtual void __fastcall RestoreFormParams();
   virtual void __fastcall RestoreParams();
@@ -169,7 +191,6 @@ protected:
   virtual void __fastcall TerminalChanged();
   virtual void __fastcall ConfigurationChanged();
   virtual bool __fastcall GetHasDirView(TOperationSide Side);
-  DYNAMIC void __fastcall KeyDown(Word & Key, Classes::TShiftState Shift);
   void __fastcall UpdateControls();
   void __fastcall SynchronizeStartStop(TObject* Sender, bool Start,
     TSynchronizeParamType Params);
@@ -181,10 +202,23 @@ protected:
     TFileOperationProgressType & ProgressData, TCancelStatus & Cancel);
   virtual void __fastcall DoOpenDirectoryDialog(TOpenDirectoryMode Mode,
     TOperationSide Side);
+  bool __fastcall InternalDDDownload(AnsiString & TargetDirectory);
+  virtual void __fastcall DDGetTarget(AnsiString & Directory);
+  virtual void __fastcall DDExtInitDrag(TFileList * FileList, bool & Created);
+  virtual void __fastcall DoDirViewEnter(TCustomDirView * DirView);
+  void __fastcall SaveCommandLine();
+  void __fastcall ExecuteCommandLine();
+  virtual TOperationSide __fastcall GetSide(TOperationSide Side);
+  virtual void __fastcall PanelExportStore(TOperationSide Side,
+    TPanelExport Export, TPanelExportDestination Destination,
+    TStringList * ExportData);
+  void __fastcall CommandLinePopulate();
 
 public:
-  virtual void __fastcall AddEditLink();
   __fastcall TScpCommanderForm(TComponent* Owner);
+  virtual __fastcall ~TScpCommanderForm();
+  
+  virtual void __fastcall AddEditLink();
   virtual bool __fastcall AllowedAction(TAction * Action, TActionAllowed Allowed);
   virtual void __fastcall ChangePath(TOperationSide Side);
   virtual void __fastcall CompareDirectories();
@@ -193,6 +227,8 @@ public:
   virtual void __fastcall FullSynchronizeDirectories();
   virtual void __fastcall StoreParams();
   virtual void __fastcall ExploreLocalDirectory();
+  virtual void __fastcall GoToCommandLine();
+  virtual void __fastcall OpenConsole(AnsiString Command = "");
   __property float LocalPanelWidth = { read = GetLocalPanelWidth, write = SetLocalPanelWidth };
 };
 //---------------------------------------------------------------------------

+ 3 - 2
forms/ScpExplorer.cpp

@@ -93,7 +93,7 @@ bool __fastcall TScpExplorerForm::CopyParamDialog(TTransferDirection Direction,
   TTransferType Type, Boolean DragDrop, TStrings * FileList,
   AnsiString & TargetDirectory, TCopyParamType & CopyParam, bool Confirm)
 {
-  if ((Direction == tdToLocal) && !DragDrop)
+  if ((Direction == tdToLocal) && !DragDrop && TargetDirectory.IsEmpty())
   {
     TargetDirectory = WinConfiguration->ScpExplorer.LastLocalTargetDirectory;
   }
@@ -140,7 +140,8 @@ void __fastcall TScpExplorerForm::FullSynchronizeDirectories()
 {
   AnsiString LocalDirectory = WinConfiguration->ScpExplorer.LastLocalTargetDirectory;
   AnsiString RemoteDirectory = RemoteDirView->PathName;
-  if (DoFullSynchronizeDirectories(LocalDirectory, RemoteDirectory))
+  TSynchronizeMode Mode = smRemote;
+  if (DoFullSynchronizeDirectories(LocalDirectory, RemoteDirectory, Mode))
   {
     WinConfiguration->ScpExplorer.LastLocalTargetDirectory = LocalDirectory; 
   }

+ 1 - 0
forms/Synchronize.cpp

@@ -53,6 +53,7 @@ __fastcall TSynchronizeDialog::TSynchronizeDialog(TComponent* AOwner)
   FMinimizedByMe = False;
   UseSystemSettings(this);
   CopyParamsFrame->Direction = pdToRemote;
+  CopyParamsFrame->ForcePreserveTime = true;
 }
 //---------------------------------------------------------------------------
 bool __fastcall TSynchronizeDialog::Execute()

+ 13 - 0
forms/SynchronizeProgress.cpp

@@ -21,10 +21,16 @@ __fastcall TSynchronizeProgressForm::TSynchronizeProgressForm(TComponent* Owner)
   FCanceled = false;
   FElapsed = EncodeTime(0, 0, 0, 0);
   FShowAsModalStorage = NULL;
+  FMinimizedByMe = false;
 }
 //---------------------------------------------------------------------------
 __fastcall TSynchronizeProgressForm::~TSynchronizeProgressForm()
 {
+  if (IsIconic(Application->Handle) && FMinimizedByMe)
+  {
+    Application->Restore();
+  }
+
   ReleaseAsModal(this, FShowAsModalStorage);
 }
 //---------------------------------------------------------------------------
@@ -79,4 +85,11 @@ void __fastcall TSynchronizeProgressForm::UpdateTimerTimer(TObject * /*Sender*/)
   UpdateControls();
 }
 //---------------------------------------------------------------------------
+void __fastcall TSynchronizeProgressForm::MinimizeButtonClick(
+  TObject * /*Sender*/)
+{
+  Application->Minimize();
+  FMinimizedByMe = true;
+}
+//---------------------------------------------------------------------------
 

+ 11 - 1
forms/SynchronizeProgress.dfm

@@ -82,7 +82,7 @@ object SynchronizeProgressForm: TSynchronizeProgressForm
     Caption = '00:00:00'
   end
   object CancelButton: TButton
-    Left = 149
+    Left = 105
     Top = 93
     Width = 73
     Height = 25
@@ -91,6 +91,16 @@ object SynchronizeProgressForm: TSynchronizeProgressForm
     TabOrder = 0
     OnClick = CancelButtonClick
   end
+  object MinimizeButton: TButton
+    Left = 190
+    Top = 93
+    Width = 73
+    Height = 25
+    Anchors = [akTop, akRight]
+    Caption = '&Minimize'
+    TabOrder = 1
+    OnClick = MinimizeButtonClick
+  end
   object UpdateTimer: TTimer
     Enabled = False
     OnTimer = UpdateTimerTimer

+ 3 - 0
forms/SynchronizeProgress.h

@@ -22,8 +22,10 @@ __published:
   TLabel *TimeElapsedLabel;
   TButton *CancelButton;
   TTimer *UpdateTimer;
+  TButton *MinimizeButton;
   void __fastcall CancelButtonClick(TObject *Sender);
   void __fastcall UpdateTimerTimer(TObject *Sender);
+  void __fastcall MinimizeButtonClick(TObject *Sender);
 
 public:
   __fastcall TSynchronizeProgressForm(TComponent* Owner);
@@ -40,6 +42,7 @@ private:
   bool FStarted;
   bool FCanceled;
   void * FShowAsModalStorage;
+  bool FMinimizedByMe;
 
   void __fastcall UpdateControls();
 };

+ 7 - 6
general/DragDrop_B5.bpk

@@ -16,16 +16,17 @@
     <DEFFILE value=""/>
     <RESDEPEN value="$(RESFILES)"/>
     <LIBFILES value=""/>
-    <LIBRARIES value="visualdbclx.lib bcb2kaxserver.lib indy.lib dbxcds.lib dclocx.lib 
-      soaprtl.lib bcbie.lib nmfast.lib dbexpress.lib inetdbxpress.lib inetdb.lib 
-      inetdbbde.lib inet.lib xmlrtl.lib ibxpress.lib teeqr.lib tee.lib teedb.lib 
-      teeui.lib bdecds.lib cds.lib dsnap.lib vcldbx.lib bdertl.lib qrpt.lib 
-      adortl.lib dbrtl.lib vcldb.lib bcbsmp.lib"/>
+    <LIBRARIES value="visualdbclx.lib bcb2kaxserver.lib indy.lib 
+      dbxcds.lib dclocx.lib soaprtl.lib bcbie.lib nmfast.lib dbexpress.lib 
+      inetdbxpress.lib inetdb.lib inetdbbde.lib inet.lib xmlrtl.lib ibxpress.lib 
+      teeqr.lib tee.lib teedb.lib teeui.lib bdecds.lib cds.lib dsnap.lib 
+      vcldbx.lib bdertl.lib qrpt.lib adortl.lib dbrtl.lib vcldb.lib bcbsmp.lib"/>
     <SPARELIBS value="rtl.lib vcl.lib bcbsmp.lib vcldb.lib dbrtl.lib adortl.lib qrpt.lib 
       bdertl.lib vcldbx.lib dsnap.lib cds.lib bdecds.lib teeui.lib teedb.lib 
       tee.lib teeqr.lib ibxpress.lib xmlrtl.lib inet.lib inetdbbde.lib 
       inetdb.lib inetdbxpress.lib dbexpress.lib nmfast.lib bcbie.lib soaprtl.lib 
-      dclocx.lib dbxcds.lib indy.lib bcb2kaxserver.lib visualdbclx.lib"/>
+      dclocx.lib dbxcds.lib indy.lib bcb2kaxserver.lib visualdbclx.lib 
+      "/>
     <PACKAGES value="rtl.bpi vcl.bpi"/>
     <PATHCPP value=".;"/>
     <PATHPAS value=".;&quot;Drag and Drop Components&quot;"/>

+ 5 - 5
general/Moje_B5.bpk

@@ -15,11 +15,11 @@
     <DEFFILE value=""/>
     <RESDEPEN value="$(RESFILES)"/>
     <LIBFILES value=""/>
-    <LIBRARIES value="visualdbclx.lib bcb2kaxserver.lib indy.lib dbxcds.lib 
-      dclocx.lib soaprtl.lib nmfast.lib dbexpress.lib inetdbxpress.lib 
-      inetdb.lib inetdbbde.lib inet.lib xmlrtl.lib ibxpress.lib teeqr.lib tee.lib 
-      teedb.lib teeui.lib bdecds.lib cds.lib dsnap.lib vcldbx.lib bdertl.lib 
-      qrpt.lib adortl.lib dbrtl.lib vcldb.lib"/>
+    <LIBRARIES value="visualdbclx.lib bcb2kaxserver.lib indy.lib 
+      dbxcds.lib dclocx.lib soaprtl.lib nmfast.lib dbexpress.lib 
+      inetdbxpress.lib inetdb.lib inetdbbde.lib inet.lib xmlrtl.lib ibxpress.lib 
+      teeqr.lib tee.lib teedb.lib teeui.lib bdecds.lib cds.lib dsnap.lib 
+      vcldbx.lib bdertl.lib qrpt.lib adortl.lib dbrtl.lib vcldb.lib"/>
     <SPARELIBS value="rtl.lib vcl.lib bcbsmp.lib vcldb.lib dbrtl.lib adortl.lib qrpt.lib 
       bdertl.lib vcldbx.lib dsnap.lib cds.lib bdecds.lib teeui.lib teedb.lib 
       tee.lib teeqr.lib ibxpress.lib xmlrtl.lib inet.lib inetdbbde.lib 

+ 9 - 2
general/drag and drop components/DragDrop.pas

@@ -162,7 +162,7 @@ type
   end;
 
   TDataObject = class(TDDInterfacedObject, IDataObject)
-  private
+  protected
     FFormatEtcList:TFormatEtcList;
     FCheckLindex:boolean;
     FCheckdwAspect:boolean;
@@ -188,6 +188,8 @@ type
       stdcall;
     function RenderData(FormatEtc:TFormatEtc;
        var StgMedium: TStgMedium): HResult; virtual; abstract;
+  protected
+    function AllowData(FormatEtc: TFormatEtc): Boolean; virtual;
   end;
 
   // forward declaration, because TDropSource and TDropTarget uses this class ...
@@ -772,7 +774,7 @@ const DVError:array[0..3] of HResult=(DV_E_FORMATETC,DV_E_TYMED,DV_E_DVASPECT,DV
 var i,j:integer;
 begin
      j:=0;
-     if FFormatEtcList.Count>0 then
+     if (FFormatEtcList.Count>0) and AllowData(FormatEtc) then
         for i:=0 to FFormatEtcList.Count-1 do
             if FormatEtc.cfFormat=FFormatEtcList.Items[i].cfFormat then
             begin
@@ -794,6 +796,11 @@ begin
      Result:=DVError[j];
 end;
 
+function TDataObject.AllowData(FormatEtc: TFormatEtc): Boolean;
+begin
+  Result := True;
+end;
+
 function TDataObject.EnumFormatEtc(dwDirection: Longint; out enumFormatEtc:
       IEnumFormatEtc): HResult;
 begin

+ 35 - 9
general/filemanager toolset/DirView.pas

@@ -262,6 +262,7 @@ type
     procedure DDMenuDone(Sender: TObject; AMenu: HMenu); override;
     procedure DDDropHandlerSucceeded(Sender: TObject; grfKeyState: Longint;
       Point: TPoint; dwEffect: Longint); override;
+    procedure DDChooseEffect(grfKeyState: Integer; var dwEffect: Integer); override;
 
     function GetPathName: string; override;
     procedure SetChangeInterval(Value: Cardinal); virtual;
@@ -440,6 +441,10 @@ type
     property OnDDQueryContinueDrag;
     property OnDDGiveFeedback;
     property OnDDDragDetect;
+    property OnDDCreateDragFileList;
+    property OnDDEnd;
+    property OnDDCreateDataObject;
+    property OnDDTargetHasDropHandler;
     {Drag&Drop:}
     property DDLinkOnExeDrag default True;
     property OnDDProcessDropped;
@@ -528,9 +533,6 @@ uses
   ShellDialogs, IEDriveInfo,
   MaskSearch, FileChanges, BaseUtils, Math;
 
-{resourcestring
-  SResolveLinkError = 'Can''t resolve link ''%s''';} 
-
 procedure Register;
 begin
   RegisterComponents('DriveDir', [TDirView]);
@@ -947,6 +949,9 @@ begin
   begin
     SourceEffects := DragSourceEffects;
     TargetEffects := [deCopy, deMove, deLink];
+
+    ShellExtensions.DragDropHandler := True;
+    ShellExtensions.DropHandler := True;
   end;
 end; {Create}
 
@@ -2131,7 +2136,7 @@ begin
   end;
 end; {GetDisplayData}
 
-function TDirView.GetDirOK: Boolean; 
+function TDirView.GetDirOK: Boolean;
 begin
   Result := FDirOK;
 end;
@@ -2141,9 +2146,14 @@ begin
   if Assigned(Item) and Assigned(Item.Data) then
   begin
     if not IsRecycleBin then
-      Result := FPath + '\' + PFileRec(Item.Data)^.FileName
-    else
-      Result := PFileRec(Item.Data)^.FileName;
+    begin
+      if PFileRec(Item.Data)^.IsParentDir then
+        Result := ExcludeTrailingBackslash(ExtractFilePath(FPath))
+      else
+        Result := FPath + '\' + PFileRec(Item.Data)^.FileName;
+    end
+      else
+    Result := PFileRec(Item.Data)^.FileName;
   end
     else
   Result := EmptyStr;
@@ -2745,8 +2755,8 @@ begin
 {$ENDIF}
     try
       {create the phyical directory:}
-      if not Windows.CreateDirectory(PChar(DirName), nil) then
-        LastIOResult := GetLastError;
+      if Windows.CreateDirectory(PChar(DirName), nil) then LastIOResult := 0 // MP
+        else LastIOResult := GetLastError;
 
       if LastIOResult = 0 then
       begin
@@ -3716,6 +3726,22 @@ begin
   end;
 end; {DDDragDetect}
 
+procedure TDirView.DDChooseEffect(grfKeyState: Integer;
+  var dwEffect: Integer);
+begin
+  if (grfKeyState and (MK_CONTROL or MK_SHIFT) = 0) then
+  begin
+    if ExeDrag and (Path[1] >= FirstFixedDrive) and
+      (DragDrive >= FirstFixedDrive) then dwEffect := DropEffect_Link
+      else
+    if DragOnDriveIsMove and
+       (not DDOwnerIsSource or Assigned(DropTarget)) and
+       (((DragDrive = Upcase(Path[1])) and (dwEffect = DropEffect_Copy) and
+       (DragDropFilesEx.AvailableDropEffects and DropEffect_Move <> 0))
+         or IsRecycleBin) then dwEffect := DropEffect_Move;
+  end;
+end;
+
 procedure TDirView.PerformDragDropFileOperation(TargetPath: string;
   dwEffect: Integer; RenameOnCollision: Boolean);
 var

+ 15 - 5
general/moje komponenty/NortonLikeListView.pas

@@ -54,6 +54,7 @@ type
     constructor Create(AOwner: TComponent); override;
     function ClosestUnselected(Item: TListItem): TListItem;
     procedure SelectAll(Mode: TSelectMode); reintroduce;
+    procedure SelectCurrentItem(FocusNext: Boolean);
 
     property ColProperties: TCustomListViewColProperties read FColProperties write FColProperties stored False;
     //CLEAN property SelCount: Integer read GetSelCount;
@@ -304,20 +305,29 @@ begin
     end;
 end;
 
+procedure TCustomNortonLikeListView.SelectCurrentItem(FocusNext: Boolean);
+var
+  Item: TListItem;
+begin
+  Item := ItemFocused;
+  if Item = nil then Item := Items[0];
+  Item.Selected := not Item.Selected;
+  if FocusNext then
+  begin
+    SendMessage(Handle, WM_KEYDOWN, VK_DOWN, LongInt(0));
+  end;
+end;
+
 procedure TCustomNortonLikeListView.WMKeyDown(var Message: TWMKeyDown);
 var
   PDontUnSelectItem: Boolean;
   PDontSelectItem: Boolean;
-  Item: TListItem;
 begin
   if NortonLike and (Message.CharCode = VK_INSERT) then
   begin
     if Items.Count > 0 then
     begin
-      Item := ItemFocused;
-      if Item = nil then Item := Items[0];
-      Item.Selected := not Item.Selected;
-      SendMessage(Handle, WM_KEYDOWN, VK_DOWN, LongInt(0));
+      SelectCurrentItem(True);
       Message.Result := 1;
     end;
   end

+ 101 - 48
general/moje komponenty/filemanager toolset/CustomDirView.pas

@@ -42,6 +42,9 @@ type
   TDDOnQueryContinueDrag = procedure(Sender: TObject; FEscapePressed: BOOL; grfKeyState: Longint; var Result: HResult) of object;
   TDDOnGiveFeedback = procedure(Sender: TObject; dwEffect: Longint; var Result: HResult) of object;
   TDDOnDragDetect = procedure(Sender: TObject; grfKeyState: Longint; DetectStart, Point: TPoint; DragStatus: TDragDetectStatus) of object;
+  TDDOnCreateDragFileList = procedure(Sender: TObject; FileList: TFileList; var Created: Boolean) of object;
+  TDDOnCreateDataObject = procedure(Sender: TObject; var DataObject: TDataObject) of object;
+  TDDOnTargetHasDropHandler = procedure(Sender: TObject; Item: TListItem; var Effect: Integer; var DropHandler: Boolean) of object;
   TOnProcessDropped = procedure(Sender: TObject; grfKeyState: Longint; Point: TPoint; var dwEffect: Longint) of object;
 
   TDDErrorEvent = procedure(Sender: TObject; ErrorNo: TDDError) of object;
@@ -73,6 +76,11 @@ type
   TCompareCriteria = (ccTime, ccSize);
   TCompareCriterias = set of TCompareCriteria;
 
+  TCustomizableDragDropFilesEx = class(TDragDropFilesEx)
+  public
+    function Execute(DataObject: TDataObject): TDragResult;
+  end;
+
   TCustomDirView = class(TIEListView)
   private
     FAddParentDir: Boolean;
@@ -83,7 +91,7 @@ type
     FSortByExtension: Boolean;
     FWantUseDragImages: Boolean;
     FCanUseDragImages: Boolean;
-    FDragDropFilesEx: TDragDropFilesEx;
+    FDragDropFilesEx: TCustomizableDragDropFilesEx;
     FInvalidNameChars: string;
     FSingleClickToExec: Boolean;
     FUseSystemContextMenu: Boolean;
@@ -102,11 +110,15 @@ type
     FOnDDQueryContinueDrag: TDDOnQueryContinueDrag;
     FOnDDGiveFeedback: TDDOnGiveFeedback;
     FOnDDDragDetect: TDDOnDragDetect;
+    FOnDDCreateDragFileList: TDDOnCreateDragFileList;
     FOnDDProcessDropped: TOnProcessDropped;
     FOnDDError: TDDErrorEvent;
     FOnDDExecuted: TDDExecutedEvent;
     FOnDDFileOperation: TDDFileOperationEvent;
     FOnDDFileOperationExecuted: TDDFileOperationExecutedEvent;
+    FOnDDEnd: TNotifyEvent;
+    FOnDDCreateDataObject: TDDOnCreateDataObject;
+    FOnDDTargetHasDropHandler: TDDOnTargetHasDropHandler;
     FOnExecFile: TDirViewExecFileEvent;
     FForceRename: Boolean;
     FLastDDResult: TDragResult;
@@ -198,6 +210,7 @@ type
     procedure DDDragEnter(DataObj: IDataObject; grfKeyState: Longint; Point: TPoint; var dwEffect: longint; var Accept: Boolean);
     procedure DDDragLeave;
     procedure DDDragOver(grfKeyState: Longint; Point: TPoint; var dwEffect: Longint);
+    procedure DDChooseEffect(grfKeyState: Integer; var dwEffect: Integer); virtual; abstract;
     procedure DDDrop(DataObj: IDataObject; grfKeyState: LongInt; Point: TPoint; var dwEffect: Longint);
     procedure DDDropHandlerSucceeded(Sender: TObject; grfKeyState: Longint; Point: TPoint; dwEffect: Longint); virtual;
     procedure DDGiveFeedback(dwEffect: Longint; var Result: HResult); virtual;
@@ -318,7 +331,7 @@ type
     property DimmHiddenFiles: Boolean read FDimmHiddenFiles write SetDimmHiddenFiles default True;
     property ShowDirectories: Boolean read FShowDirectories write SetShowDirectories default True;
     property DirsOnTop: Boolean read FDirsOnTop write SetDirsOnTop default True;
-    property DragDropFilesEx: TDragDropFilesEx read FDragDropFilesEx;
+    property DragDropFilesEx: TCustomizableDragDropFilesEx read FDragDropFilesEx;
     property ShowSubDirSize: Boolean read FShowSubDirSize write SetShowSubDirSize default False;
     property SortByExtension: Boolean read FSortByExtension write SetSortByExtension default False;
     property WantUseDragImages: Boolean read FWantUseDragImages write FWantUseDragImages default True;
@@ -391,6 +404,14 @@ type
      the components window as the source:}
     property OnDDDragDetect: TDDOnDragDetect
       read FOnDDDragDetect write FOnDDDragDetect;
+    property OnDDCreateDragFileList: TDDOnCreateDragFileList
+      read FOnDDCreateDragFileList write FOnDDCreateDragFileList;
+    property OnDDEnd: TNotifyEvent
+      read FOnDDEnd write FOnDDEnd;
+    property OnDDCreateDataObject: TDDOnCreateDataObject
+      read FOnDDCreateDataObject write FOnDDCreateDataObject;
+    property OnDDTargetHasDropHandler: TDDOnTargetHasDropHandler
+      read FOnDDTargetHasDropHandler write FOnDDTargetHasDropHandler;
     {The component window is the target of a drag&drop operation:}
     property OnDDProcessDropped: TOnProcessDropped
       read FOnDDProcessDropped write FOnDDProcessDropped;
@@ -715,6 +736,17 @@ begin
   FAnimation.Active := True;
 end; }
 
+  { TCustomizableDragDropFilesEx }
+
+function TCustomizableDragDropFilesEx.Execute(DataObject: TDataObject): TDragResult;
+begin
+  if not Assigned(DataObject) then
+  begin
+    DataObject := CreateDataObject;
+  end;
+  Result := ExecuteOperation(DataObject);
+end;
+
   { TCustomDirView }
 
 constructor TCustomDirView.Create(AOwner: TComponent);
@@ -784,7 +816,7 @@ begin
   OnCustomDrawItem := DumbCustomDrawItem;
   OnCustomDrawSubItem := DumbCustomDrawSubItem;
 
-  FDragDropFilesEx := TDragDropFilesEx.Create(Self);
+  FDragDropFilesEx := TCustomizableDragDropFilesEx.Create(Self);
   with FDragDropFilesEx do
   begin
     {$IFDEF OLD_DND}
@@ -816,9 +848,6 @@ begin
     OnGiveFeedback := DDGiveFeedback;
     OnProcessDropped := DDProcessDropped;
     OnDragDetect := DDDragDetect;
-
-    ShellExtensions.DragDropHandler := True;
-    ShellExtensions.DropHandler := True;
   end;
 end;
 
@@ -1402,6 +1431,11 @@ begin
   Assert(Assigned(DragDropFilesEx) and Assigned(Item));
   Result :=
     DragDropFilesEx.TargetHasDropHandler(nil, ItemFullFileName(Item), Effect);
+
+  if Assigned(OnDDTargetHasDropHandler) then
+  begin
+    OnDDTargetHasDropHandler(Self, Item, Effect, Result);
+  end;
 end;
 
 procedure TCustomDirView.UpdatePathComboBox;
@@ -1892,17 +1926,7 @@ begin
   {Set dropeffect:}
   if (not HasDropHandler) and (not Loading) then
   begin
-    if (grfKeyState and (MK_CONTROL or MK_SHIFT) = 0) then
-    begin
-      if ExeDrag and (Path[1] >= FirstFixedDrive) and
-        (DragDrive >= FirstFixedDrive) then dwEffect := DropEffect_Link
-        else
-      if DragOnDriveIsMove and
-         (not FDDOwnerIsSource or Assigned(DropTarget)) and
-         (((DragDrive = Upcase(Path[1])) and (dwEffect = DropEffect_Copy) and
-         (DragDropFilesEx.AvailableDropEffects and DropEffect_Move <> 0))
-           or IsRecycleBin) then dwEffect := DropEffect_Move;
-    end;
+    DDChooseEffect(grfKeyState, dwEffect);
 
     if Assigned(FOnDDDragOver) then
       FOnDDDragOver(Self, grfKeyState, Point, dwEffect);
@@ -2236,6 +2260,9 @@ var
   DragText: string;
   ClientPoint: TPoint;
   OldCursor: TCursor;
+  FileListCreated: Boolean;
+  AvoidDragImage: Boolean;
+  DataObject: TDataObject;
 begin
   if Assigned(FOnDDDragDetect) then
     FOnDDDragDetect(Self, grfKeyState, DetectStart, Point, DragStatus);
@@ -2247,34 +2274,46 @@ begin
     FirstItem := nil;
     FilesCount := 0;
     DirsCount := 0;
+    FileListCreated := False;
 
-    if Assigned(ItemFocused) and (not ItemFocused.Selected) and
-       ItemCanDrag(ItemFocused) then
+    if Assigned(OnDDCreateDragFileList) then
     begin
-      FirstItem := ItemFocused;
-      AddToDragFileList(DragDropFilesEx.FileList, ItemFocused);
-      if ItemIsDirectory(ItemFocused) then DirsCount := 1
-        else FilesCount := 1;
-    end
-      else
-    if SelCount > 0 then
+      OnDDCreateDragFileList(Self, DragDropFilesEx.FileList, FileListCreated);
+      if FileListCreated then
+      begin
+        AvoidDragImage := True;
+      end;
+    end;
+
+    if not FileListCreated then
     begin
-      Item := GetNextItem(nil, sdAll, [isSelected]);
-      while Assigned(Item) do
+      if Assigned(ItemFocused) and (not ItemFocused.Selected) and
+         ItemCanDrag(ItemFocused) then
+      begin
+        FirstItem := ItemFocused;
+        AddToDragFileList(DragDropFilesEx.FileList, ItemFocused);
+        if ItemIsDirectory(ItemFocused) then Inc(DirsCount)
+          else Inc(FilesCount);
+      end
+        else
+      if SelCount > 0 then
       begin
-        if ItemCanDrag(Item) then
+        Item := GetNextItem(nil, sdAll, [isSelected]);
+        while Assigned(Item) do
         begin
-          if not Assigned(FirstItem) then
-            FirstItem := Item;
-          AddToDragFileList(DragDropFilesEx.FileList, Item);
-          if ItemIsDirectory(Item) then Inc(DirsCount)
-            else Inc(FilesCount);
+          if ItemCanDrag(Item) then
+          begin
+            if not Assigned(FirstItem) then FirstItem := Item;
+            AddToDragFileList(DragDropFilesEx.FileList, Item);
+            if ItemIsDirectory(Item) then Inc(DirsCount)
+              else Inc(FilesCount);
+          end;
+          Item := GetNextItem(Item, sdAll, [isSelected]);
         end;
-        Item := GetNextItem(Item, sdAll, [isSelected]);
       end;
     end;
 
-    if Assigned(FirstItem) then
+    if DragDropFilesEx.FileList.Count > 0 then
     begin
       OldCursor := Screen.Cursor;
       Screen.Cursor := crHourGlass;
@@ -2282,7 +2321,7 @@ begin
         FDragEnabled := False;
         {Create the dragimage:}
         GlobalDragImageList := DragImageList;
-        if UseDragImages then
+        if UseDragImages and (not AvoidDragImage) then
         begin
           ImageListHandle := ListView_CreateDragImage(Handle, FirstItem.Index, Spot);
           ItemPos := ClientToScreen(FirstItem.DisplayRect(drBounds).TopLeft);
@@ -2353,21 +2392,35 @@ begin
         else DragDropFilesEx.SourceEffects := DragSourceEffects;
 
       DropSourceControl := Self;
-      GetSystemTimeAsFileTime(FDragStartTime);
 
-      {Execute the drag&drop-Operation:}
-      FLastDDResult := DragDropFilesEx.Execute;
+      try
+        GetSystemTimeAsFileTime(FDragStartTime);
 
-      {the drag&drop operation is finished, so clean up the used drag image:}
-      GlobalDragImageList.EndDrag;
-      GlobalDragImageList.Clear;
+        DataObject := nil;
+        if Assigned(OnDDCreateDataObject) then
+        begin
+          OnDDCreateDataObject(Self, DataObject);
+        end;
+        {Execute the drag&drop-Operation:}
+        FLastDDResult := DragDropFilesEx.Execute(DataObject);
 
-      Application.ProcessMessages;
+        {the drag&drop operation is finished, so clean up the used drag image:}
+        GlobalDragImageList.EndDrag;
+        GlobalDragImageList.Clear;
 
-      DragDropFilesEx.FileList.Clear;
-      FContextMenu := False;
-      DropTarget := nil;
-      DropSourceControl := nil;
+        Application.ProcessMessages;
+      finally
+        DropSourceControl := nil;
+
+        DragDropFilesEx.FileList.Clear;
+        FContextMenu := False;
+        DropTarget := nil;
+
+        if Assigned(OnDDEnd) then
+        begin
+          OnDDEnd(Self);
+        end;
+      end;
     end;
   end;
 end;

+ 4 - 1
packages/dragndrop/DragDrop.hpp

@@ -175,7 +175,7 @@ class PASCALIMPLEMENTATION TDataObject : public TDDInterfacedObject
 {
 	typedef TDDInterfacedObject inherited;
 	
-private:
+protected:
 	TFormatEtcList* FFormatEtcList;
 	bool FCheckLindex;
 	bool FCheckdwAspect;
@@ -193,6 +193,9 @@ public:
 	HRESULT __stdcall DUnadvise(int dwConnection);
 	HRESULT __stdcall EnumDAdvise(/* out */ _di_IEnumSTATDATA &enumAdvise);
 	virtual HRESULT __fastcall RenderData(const tagFORMATETC &FormatEtc, tagSTGMEDIUM &StgMedium) = 0 ;
+	
+protected:
+	virtual bool __fastcall AllowData(const tagFORMATETC &FormatEtc);
 private:
 	void *__IDataObject;	/* IDataObject */
 	

+ 6 - 1
packages/filemng/DirView.hpp

@@ -14,8 +14,8 @@
 #include <PathLabel.hpp>	// Pascal unit
 #include <CustomPathComboBox.hpp>	// Pascal unit
 #include <Controls.hpp>	// Pascal unit
-#include <NortonLikeListView.hpp>	// Pascal unit
 #include <IEListView.hpp>	// Pascal unit
+#include <NortonLikeListView.hpp>	// Pascal unit
 #include <SysUtils.hpp>	// Pascal unit
 #include <FileCtrl.hpp>	// Pascal unit
 #include <DragDropFilesEx.hpp>	// Pascal unit
@@ -351,6 +351,7 @@ protected:
 	virtual void __fastcall DDDragDetect(int grfKeyState, const Types::TPoint &DetectStart, const Types::TPoint &Point, Dragdrop::TDragDetectStatus DragStatus);
 	virtual void __fastcall DDMenuDone(System::TObject* Sender, HMENU AMenu);
 	virtual void __fastcall DDDropHandlerSucceeded(System::TObject* Sender, int grfKeyState, const Types::TPoint &Point, int dwEffect);
+	virtual void __fastcall DDChooseEffect(int grfKeyState, int &dwEffect);
 	virtual AnsiString __fastcall GetPathName();
 	virtual void __fastcall SetChangeInterval(unsigned Value);
 	virtual void __fastcall LoadFromRecycleBin(AnsiString Dir);
@@ -482,6 +483,10 @@ __published:
 	__property OnDDQueryContinueDrag ;
 	__property OnDDGiveFeedback ;
 	__property OnDDDragDetect ;
+	__property OnDDCreateDragFileList ;
+	__property OnDDEnd ;
+	__property OnDDCreateDataObject ;
+	__property OnDDTargetHasDropHandler ;
 	__property DDLinkOnExeDrag  = {default=1};
 	__property OnDDProcessDropped ;
 	__property OnDDError ;

+ 1 - 0
packages/my/NortonLikeListView.hpp

@@ -82,6 +82,7 @@ public:
 	__fastcall virtual TCustomNortonLikeListView(Classes::TComponent* AOwner);
 	Comctrls::TListItem* __fastcall ClosestUnselected(Comctrls::TListItem* Item);
 	HIDESBASE void __fastcall SelectAll(TSelectMode Mode);
+	void __fastcall SelectCurrentItem(bool FocusNext);
 	__property Listviewcolproperties::TCustomListViewColProperties* ColProperties = {read=FColProperties, write=FColProperties, stored=false};
 	__property MultiSelect  = {default=1};
 	__property bool NortonLike = {read=FNortonLike, write=FNortonLike, default=1};

+ 35 - 2
packages/my/filemng/CustomDirView.hpp

@@ -63,6 +63,12 @@ typedef void __fastcall (__closure *TDDOnGiveFeedback)(System::TObject* Sender,
 
 typedef void __fastcall (__closure *TDDOnDragDetect)(System::TObject* Sender, int grfKeyState, const Types::TPoint &DetectStart, const Types::TPoint &Point, Dragdrop::TDragDetectStatus DragStatus);
 
+typedef void __fastcall (__closure *TDDOnCreateDragFileList)(System::TObject* Sender, Dragdropfilesex::TFileList* FileList, bool &Created);
+
+typedef void __fastcall (__closure *TDDOnCreateDataObject)(System::TObject* Sender, Dragdrop::TDataObject* &DataObject);
+
+typedef void __fastcall (__closure *TDDOnTargetHasDropHandler)(System::TObject* Sender, Comctrls::TListItem* Item, int &Effect, bool &DropHandler);
+
 typedef void __fastcall (__closure *TOnProcessDropped)(System::TObject* Sender, int grfKeyState, const Types::TPoint &Point, int &dwEffect);
 
 typedef void __fastcall (__closure *TDDErrorEvent)(System::TObject* Sender, TDDError ErrorNo);
@@ -108,6 +114,24 @@ enum TCompareCriteria { ccTime, ccSize };
 
 typedef Set<TCompareCriteria, ccTime, ccSize>  TCompareCriterias;
 
+class DELPHICLASS TCustomizableDragDropFilesEx;
+class PASCALIMPLEMENTATION TCustomizableDragDropFilesEx : public Dragdropfilesex::TDragDropFilesEx 
+{
+	typedef Dragdropfilesex::TDragDropFilesEx inherited;
+	
+public:
+	HIDESBASE Dragdrop::TDragResult __fastcall Execute(Dragdrop::TDataObject* DataObject);
+public:
+	#pragma option push -w-inl
+	/* TDragDropFilesEx.Create */ inline __fastcall virtual TCustomizableDragDropFilesEx(Classes::TComponent* AOwner) : Dragdropfilesex::TDragDropFilesEx(AOwner) { }
+	#pragma option pop
+	#pragma option push -w-inl
+	/* TDragDropFilesEx.Destroy */ inline __fastcall virtual ~TCustomizableDragDropFilesEx(void) { }
+	#pragma option pop
+	
+};
+
+
 class PASCALIMPLEMENTATION TCustomDirView : public Ielistview::TIEListView 
 {
 	typedef Ielistview::TIEListView inherited;
@@ -121,7 +145,7 @@ private:
 	bool FSortByExtension;
 	bool FWantUseDragImages;
 	bool FCanUseDragImages;
-	Dragdropfilesex::TDragDropFilesEx* FDragDropFilesEx;
+	TCustomizableDragDropFilesEx* FDragDropFilesEx;
 	AnsiString FInvalidNameChars;
 	bool FSingleClickToExec;
 	bool FUseSystemContextMenu;
@@ -140,11 +164,15 @@ private:
 	TDDOnQueryContinueDrag FOnDDQueryContinueDrag;
 	TDDOnGiveFeedback FOnDDGiveFeedback;
 	TDDOnDragDetect FOnDDDragDetect;
+	TDDOnCreateDragFileList FOnDDCreateDragFileList;
 	TOnProcessDropped FOnDDProcessDropped;
 	TDDErrorEvent FOnDDError;
 	TDDExecutedEvent FOnDDExecuted;
 	TDDFileOperationEvent FOnDDFileOperation;
 	TDDFileOperationExecutedEvent FOnDDFileOperationExecuted;
+	Classes::TNotifyEvent FOnDDEnd;
+	TDDOnCreateDataObject FOnDDCreateDataObject;
+	TDDOnTargetHasDropHandler FOnDDTargetHasDropHandler;
 	TDirViewExecFileEvent FOnExecFile;
 	bool FForceRename;
 	Dragdrop::TDragResult FLastDDResult;
@@ -236,6 +264,7 @@ protected:
 	void __fastcall DDDragEnter(_di_IDataObject DataObj, int grfKeyState, const Types::TPoint &Point, int &dwEffect, bool &Accept);
 	void __fastcall DDDragLeave(void);
 	void __fastcall DDDragOver(int grfKeyState, const Types::TPoint &Point, int &dwEffect);
+	virtual void __fastcall DDChooseEffect(int grfKeyState, int &dwEffect) = 0 ;
 	void __fastcall DDDrop(_di_IDataObject DataObj, int grfKeyState, const Types::TPoint &Point, int &dwEffect);
 	virtual void __fastcall DDDropHandlerSucceeded(System::TObject* Sender, int grfKeyState, const Types::TPoint &Point, int dwEffect);
 	virtual void __fastcall DDGiveFeedback(int dwEffect, HRESULT &Result);
@@ -345,7 +374,7 @@ public:
 	__property bool DimmHiddenFiles = {read=FDimmHiddenFiles, write=SetDimmHiddenFiles, default=1};
 	__property bool ShowDirectories = {read=FShowDirectories, write=SetShowDirectories, default=1};
 	__property bool DirsOnTop = {read=FDirsOnTop, write=SetDirsOnTop, default=1};
-	__property Dragdropfilesex::TDragDropFilesEx* DragDropFilesEx = {read=FDragDropFilesEx};
+	__property TCustomizableDragDropFilesEx* DragDropFilesEx = {read=FDragDropFilesEx};
 	__property bool ShowSubDirSize = {read=FShowSubDirSize, write=SetShowSubDirSize, default=0};
 	__property bool SortByExtension = {read=FSortByExtension, write=SetSortByExtension, default=0};
 	__property bool WantUseDragImages = {read=FWantUseDragImages, write=FWantUseDragImages, default=1};
@@ -402,6 +431,10 @@ public:
 	__property TDDOnQueryContinueDrag OnDDQueryContinueDrag = {read=FOnDDQueryContinueDrag, write=FOnDDQueryContinueDrag};
 	__property TDDOnGiveFeedback OnDDGiveFeedback = {read=FOnDDGiveFeedback, write=FOnDDGiveFeedback};
 	__property TDDOnDragDetect OnDDDragDetect = {read=FOnDDDragDetect, write=FOnDDDragDetect};
+	__property TDDOnCreateDragFileList OnDDCreateDragFileList = {read=FOnDDCreateDragFileList, write=FOnDDCreateDragFileList};
+	__property Classes::TNotifyEvent OnDDEnd = {read=FOnDDEnd, write=FOnDDEnd};
+	__property TDDOnCreateDataObject OnDDCreateDataObject = {read=FOnDDCreateDataObject, write=FOnDDCreateDataObject};
+	__property TDDOnTargetHasDropHandler OnDDTargetHasDropHandler = {read=FOnDDTargetHasDropHandler, write=FOnDDTargetHasDropHandler};
 	__property TOnProcessDropped OnDDProcessDropped = {read=FOnDDProcessDropped, write=FOnDDProcessDropped};
 	__property TDDErrorEvent OnDDError = {read=FOnDDError, write=FOnDDError};
 	__property TDDExecutedEvent OnDDExecuted = {read=FOnDDExecuted, write=FOnDDExecuted};

+ 1524 - 0
putty/CMDGEN.C

@@ -0,0 +1,1524 @@
+/*
+ * cmdgen.c - command-line form of PuTTYgen
+ */
+
+#define PUTTY_DO_GLOBALS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <assert.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+#ifdef TEST_CMDGEN
+/*
+ * This section overrides some definitions below for test purposes.
+ * When compiled with -DTEST_CMDGEN:
+ * 
+ *  - Calls to get_random_data() are replaced with the diagnostic
+ *    function below (I #define the name so that I can still link
+ *    with the original set of modules without symbol clash), in
+ *    order to avoid depleting the test system's /dev/random
+ *    unnecessarily.
+ * 
+ *  - Calls to console_get_line() are replaced with the diagnostic
+ *    function below, so that I can run tests in an automated
+ *    manner and provide their interactive passphrase inputs.
+ * 
+ *  - main() is renamed to cmdgen_main(); at the bottom of the file
+ *    I define another main() which calls the former repeatedly to
+ *    run tests.
+ */
+#define get_random_data get_random_data_diagnostic
+char *get_random_data(int len)
+{
+    char *buf = snewn(len, char);
+    memset(buf, 'x', len);
+    return buf;
+}
+#define console_get_line console_get_line_diagnostic
+int nprompts, promptsgot;
+const char *prompts[3];
+int console_get_line(const char *prompt, char *str, int maxlen, int is_pw)
+{
+    if (promptsgot < nprompts) {
+	assert(strlen(prompts[promptsgot]) < maxlen);
+	strcpy(str, prompts[promptsgot++]);
+	return TRUE;
+    } else {
+	promptsgot++;		       /* track number of requests anyway */
+	return FALSE;
+    }
+}
+#define main cmdgen_main
+#endif
+
+struct progress {
+    int phase, current;
+};
+
+static void progress_update(void *param, int action, int phase, int iprogress)
+{
+    struct progress *p = (struct progress *)param;
+    if (action != PROGFN_PROGRESS)
+	return;
+    if (phase > p->phase) {
+	if (p->phase >= 0)
+	    fputc('\n', stderr);
+	p->phase = phase;
+	if (iprogress >= 0)
+	    p->current = iprogress - 1;
+	else
+	    p->current = iprogress;
+    }
+    while (p->current < iprogress) {
+	fputc('+', stdout);
+	p->current++;
+    }
+    fflush(stdout);
+}
+
+static void no_progress(void *param, int action, int phase, int iprogress)
+{
+}
+
+void modalfatalbox(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    cleanup_exit(1);
+}
+
+/*
+ * Stubs to let everything else link sensibly.
+ */
+void log_eventlog(void *handle, const char *event)
+{
+}
+char *x_get_default(const char *key)
+{
+    return NULL;
+}
+void sk_cleanup(void)
+{
+}
+
+void showversion(void)
+{
+    char *verstr = dupstr(ver);
+    verstr[0] = tolower(verstr[0]);
+    printf("PuTTYgen %s\n", verstr);
+    sfree(verstr);
+}
+
+void usage(void)
+{
+    fprintf(stderr,
+	    "Usage: puttygen ( keyfile | -t type [ -b bits ] )\n"
+	    "                [ -C comment ] [ -P ]\n"
+	    "                [ -o output-keyfile ] [ -O type | -l | -L"
+	    " | -p ]\n");
+}
+
+void help(void)
+{
+    /*
+     * Help message is an extended version of the usage message. So
+     * start with that, plus a version heading.
+     */
+    showversion();
+    usage();
+    fprintf(stderr,
+	    "  -t    specify key type when generating (rsa, dsa, rsa1)\n"
+	    "  -b    specify number of bits when generating key\n"
+	    "  -C    change or specify key comment\n"
+	    "  -P    change key passphrase\n"
+	    "  -O    specify output type:\n"
+	    "           private             output PuTTY private key format\n"
+	    "           private-openssh     export OpenSSH private key\n"
+	    "           private-sshcom      export ssh.com private key\n"
+	    "           public              standard / ssh.com public key\n"
+	    "           public-openssh      OpenSSH public key\n"
+	    "           fingerprint         output the key fingerprint\n"
+	    "  -o    specify output file\n"
+	    "  -l    equivalent to `-O fingerprint'\n"
+	    "  -L    equivalent to `-O public-openssh'\n"
+	    "  -p    equivalent to `-O public'\n"
+	    );
+}
+
+static int save_ssh2_pubkey(char *filename, char *comment,
+			    void *v_pub_blob, int pub_len)
+{
+    unsigned char *pub_blob = (unsigned char *)v_pub_blob;
+    char *p;
+    int i, column;
+    FILE *fp;
+
+    if (filename) {
+	fp = fopen(filename, "wb");
+	if (!fp)
+	    return 0;
+    } else
+	fp = stdout;
+
+    fprintf(fp, "---- BEGIN SSH2 PUBLIC KEY ----\n");
+
+    if (comment) {
+	fprintf(fp, "Comment: \"");
+	for (p = comment; *p; p++) {
+	    if (*p == '\\' || *p == '\"')
+		fputc('\\', fp);
+	    fputc(*p, fp);
+	}
+	fprintf(fp, "\"\n");
+    }
+
+    i = 0;
+    column = 0;
+    while (i < pub_len) {
+	char buf[5];
+	int n = (pub_len - i < 3 ? pub_len - i : 3);
+	base64_encode_atom(pub_blob + i, n, buf);
+	i += n;
+	buf[4] = '\0';
+	fputs(buf, fp);
+	if (++column >= 16) {
+	    fputc('\n', fp);
+	    column = 0;
+	}
+    }
+    if (column > 0)
+	fputc('\n', fp);
+    
+    fprintf(fp, "---- END SSH2 PUBLIC KEY ----\n");
+    if (filename)
+	fclose(fp);
+    return 1;
+}
+
+static int move(char *from, char *to)
+{
+    int ret;
+
+    ret = rename(from, to);
+    if (ret) {
+	/*
+	 * This OS may require us to remove the original file first.
+	 */
+	remove(to);
+	ret = rename(from, to);
+    }
+    if (ret) {
+	perror("puttygen: cannot move new file on to old one");
+	return FALSE;
+    }
+    return TRUE;
+}
+
+static char *blobfp(char *alg, int bits, char *blob, int bloblen)
+{
+    char buffer[128];
+    unsigned char digest[16];
+    struct MD5Context md5c;
+    int i;
+
+    MD5Init(&md5c);
+    MD5Update(&md5c, blob, bloblen);
+    MD5Final(digest, &md5c);
+
+    sprintf(buffer, "%s ", alg);
+    if (bits > 0)
+	sprintf(buffer + strlen(buffer), "%d ", bits);
+    for (i = 0; i < 16; i++)
+	sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
+		digest[i]);
+
+    return dupstr(buffer);
+}
+
+int main(int argc, char **argv)
+{
+    char *infile = NULL;
+    Filename infilename;
+    enum { NOKEYGEN, RSA1, RSA2, DSA } keytype = NOKEYGEN;    
+    char *outfile = NULL, *outfiletmp = NULL;
+    Filename outfilename;
+    enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH, SSHCOM } outtype = PRIVATE;
+    int bits = 1024;
+    char *comment = NULL, *origcomment = NULL;
+    int change_passphrase = FALSE;
+    int errs = FALSE, nogo = FALSE;
+    int intype = SSH_KEYTYPE_UNOPENABLE;
+    int sshver = 0;
+    struct ssh2_userkey *ssh2key = NULL;
+    struct RSAKey *ssh1key = NULL;
+    char *ssh2blob = NULL, *ssh2alg = NULL;
+    const struct ssh_signkey *ssh2algf = NULL;
+    int ssh2bloblen;
+    char *passphrase = NULL;
+    int load_encrypted;
+    progfn_t progressfn = is_interactive() ? progress_update : no_progress;
+
+    /* ------------------------------------------------------------------
+     * Parse the command line to figure out what we've been asked to do.
+     */
+
+    /*
+     * If run with no arguments at all, print the usage message and
+     * return success.
+     */
+    if (argc <= 1) {
+	usage();
+	return 0;
+    }
+
+    /*
+     * Parse command line arguments.
+     */
+    while (--argc) {
+	char *p = *++argv;
+	if (*p == '-') {
+	    /*
+	     * An option.
+	     */
+	    while (p && *++p) {
+		char c = *p;
+		switch (c) {
+		  case '-':
+		    /*
+		     * Long option.
+		     */
+		    {
+			char *opt, *val;
+			opt = p++;     /* opt will have _one_ leading - */
+			while (*p && *p != '=')
+			    p++;	       /* find end of option */
+			if (*p == '=') {
+			    *p++ = '\0';
+			    val = p;
+			} else
+			    val = NULL;
+			if (!strcmp(opt, "-help")) {
+			    help();
+			    nogo = TRUE;
+			} else if (!strcmp(opt, "-version")) {
+			    showversion();
+			    nogo = TRUE;
+			}
+			/*
+			 * A sample option requiring an argument:
+			 * 
+			 * else if (!strcmp(opt, "-output")) {
+			 *     if (!val)
+			 *         errs = TRUE, error(err_optnoarg, opt);
+			 *     else
+			 *         ofile = val;
+			 * }
+			 */
+			else {
+			    errs = TRUE;
+			    fprintf(stderr,
+				    "puttygen: no such option `--%s'\n", opt);
+			}
+		    }
+		    p = NULL;
+		    break;
+		  case 'h':
+		  case 'V':
+		  case 'P':
+		  case 'l':
+		  case 'L':
+		  case 'p':
+		  case 'q':
+		    /*
+		     * Option requiring no parameter.
+		     */
+		    switch (c) {
+		      case 'h':
+			help();
+			nogo = TRUE;
+			break;
+		      case 'V':
+			showversion();
+			nogo = TRUE;
+			break;
+		      case 'P':
+			change_passphrase = TRUE;
+			break;
+		      case 'l':
+			outtype = FP;
+			break;
+		      case 'L':
+			outtype = PUBLICO;
+			break;
+		      case 'p':
+			outtype = PUBLIC;
+			break;
+		      case 'q':
+			progressfn = no_progress;
+			break;
+		    }
+		    break;
+		  case 't':
+		  case 'b':
+		  case 'C':
+		  case 'O':
+		  case 'o':
+		    /*
+		     * Option requiring parameter.
+		     */
+		    p++;
+		    if (!*p && argc > 1)
+			--argc, p = *++argv;
+		    else if (!*p) {
+			fprintf(stderr, "puttygen: option `-%c' expects a"
+				" parameter\n", c);
+			errs = TRUE;
+		    }
+		    /*
+		     * Now c is the option and p is the parameter.
+		     */
+		    switch (c) {
+		      case 't':
+			if (!strcmp(p, "rsa") || !strcmp(p, "rsa2"))
+			    keytype = RSA2, sshver = 2;
+			else if (!strcmp(p, "rsa1"))
+			    keytype = RSA1, sshver = 1;
+			else if (!strcmp(p, "dsa") || !strcmp(p, "dss"))
+			    keytype = DSA, sshver = 2;
+			else {
+			    fprintf(stderr,
+				    "puttygen: unknown key type `%s'\n", p);
+			    errs = TRUE;
+			}
+                        break;
+		      case 'b':
+			bits = atoi(p);
+                        break;
+		      case 'C':
+			comment = p;
+                        break;
+		      case 'O':
+			if (!strcmp(p, "public"))
+			    outtype = PUBLIC;
+			else if (!strcmp(p, "public-openssh"))
+			    outtype = PUBLICO;
+			else if (!strcmp(p, "private"))
+			    outtype = PRIVATE;
+			else if (!strcmp(p, "fingerprint"))
+			    outtype = FP;
+			else if (!strcmp(p, "private-openssh"))
+			    outtype = OPENSSH, sshver = 2;
+			else if (!strcmp(p, "private-sshcom"))
+			    outtype = SSHCOM, sshver = 2;
+			else {
+			    fprintf(stderr,
+				    "puttygen: unknown output type `%s'\n", p);
+			    errs = TRUE;
+			}
+                        break;
+		      case 'o':
+			outfile = p;
+                        break;
+		    }
+		    p = NULL;	       /* prevent continued processing */
+		    break;
+		  default:
+		    /*
+		     * Unrecognised option.
+		     */
+		    errs = TRUE;
+		    fprintf(stderr, "puttygen: no such option `-%c'\n", c);
+		    break;
+		}
+	    }
+	} else {
+	    /*
+	     * A non-option argument.
+	     */
+	    if (!infile)
+		infile = p;
+	    else {
+		errs = TRUE;
+		fprintf(stderr, "puttygen: cannot handle more than one"
+			" input file\n");
+	    }
+	}
+    }
+
+    if (errs)
+	return 1;
+
+    if (nogo)
+	return 0;
+
+    /*
+     * If run with at least one argument _but_ not the required
+     * ones, print the usage message and return failure.
+     */
+    if (!infile && keytype == NOKEYGEN) {
+	usage();
+	return 1;
+    }
+
+    /* ------------------------------------------------------------------
+     * Figure out further details of exactly what we're going to do.
+     */
+
+    /*
+     * Bomb out if we've been asked to both load and generate a
+     * key.
+     */
+    if (keytype != NOKEYGEN && intype) {
+	fprintf(stderr, "puttygen: cannot both load and generate a key\n");
+	return 1;
+    }
+
+    /*
+     * Analyse the type of the input file, in case this affects our
+     * course of action.
+     */
+    if (infile) {
+	infilename = filename_from_str(infile);
+
+	intype = key_type(&infilename);
+
+	switch (intype) {
+	    /*
+	     * It would be nice here to be able to load _public_
+	     * key files, in any of a number of forms, and (a)
+	     * convert them to other public key types, (b) print
+	     * out their fingerprints. Or, I suppose, for real
+	     * orthogonality, (c) change their comment!
+	     * 
+	     * In fact this opens some interesting possibilities.
+	     * Suppose ssh2_userkey_loadpub() were able to load
+	     * public key files as well as extracting the public
+	     * key from private ones. And suppose I did the thing
+	     * I've been wanting to do, where specifying a
+	     * particular private key file for authentication
+	     * causes any _other_ key in the agent to be discarded.
+	     * Then, if you had an agent forwarded to the machine
+	     * you were running Unix PuTTY or Plink on, and you
+	     * needed to specify which of the keys in the agent it
+	     * should use, you could do that by supplying a
+	     * _public_ key file, thus not needing to trust even
+	     * your encrypted private key file to the network. Ooh!
+	     */
+
+	  case SSH_KEYTYPE_UNOPENABLE:
+	  case SSH_KEYTYPE_UNKNOWN:
+	    fprintf(stderr, "puttygen: unable to load file `%s': %s\n",
+		    infile, key_type_to_str(intype));
+	    return 1;
+
+	  case SSH_KEYTYPE_SSH1:
+	    if (sshver == 2) {
+		fprintf(stderr, "puttygen: conversion from SSH1 to SSH2 keys"
+			" not supported\n");
+		return 1;
+	    }
+	    sshver = 1;
+	    break;
+
+	  case SSH_KEYTYPE_SSH2:
+	  case SSH_KEYTYPE_OPENSSH:
+	  case SSH_KEYTYPE_SSHCOM:
+	    if (sshver == 1) {
+		fprintf(stderr, "puttygen: conversion from SSH2 to SSH1 keys"
+			" not supported\n");
+		return 1;
+	    }
+	    sshver = 2;
+	    break;
+	}
+    }
+
+    /*
+     * Determine the default output file, if none is provided.
+     * 
+     * This will usually be equal to stdout, except that if the
+     * input and output file formats are the same then the default
+     * output is to overwrite the input.
+     * 
+     * Also in this code, we bomb out if the input and output file
+     * formats are the same and no other action is performed.
+     */
+    if ((intype == SSH_KEYTYPE_SSH1 && outtype == PRIVATE) ||
+	(intype == SSH_KEYTYPE_SSH2 && outtype == PRIVATE) ||
+	(intype == SSH_KEYTYPE_OPENSSH && outtype == OPENSSH) ||
+	(intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) {
+	if (!outfile) {
+	    outfile = infile;
+	    outfiletmp = dupcat(outfile, ".tmp", NULL);
+	}
+
+	if (!change_passphrase && !comment) {
+	    fprintf(stderr, "puttygen: this command would perform no useful"
+		    " action\n");
+	    return 1;
+	}
+    } else {
+	if (!outfile) {
+	    /*
+	     * Bomb out rather than automatically choosing to write
+	     * a private key file to stdout.
+	     */
+	    if (outtype==PRIVATE || outtype==OPENSSH || outtype==SSHCOM) {
+		fprintf(stderr, "puttygen: need to specify an output file\n");
+		return 1;
+	    }
+	}
+    }
+
+    /*
+     * Figure out whether we need to load the encrypted part of the
+     * key. This will be the case if either (a) we need to write
+     * out a private key format, or (b) the entire input key file
+     * is encrypted.
+     */
+    if (outtype == PRIVATE || outtype == OPENSSH || outtype == SSHCOM ||
+	intype == SSH_KEYTYPE_OPENSSH || intype == SSH_KEYTYPE_SSHCOM)
+	load_encrypted = TRUE;
+    else
+	load_encrypted = FALSE;
+
+    /* ------------------------------------------------------------------
+     * Now we're ready to actually do some stuff.
+     */
+
+    /*
+     * Either load or generate a key.
+     */
+    if (keytype != NOKEYGEN) {
+	char *entropy;
+	char default_comment[80];
+	time_t t;
+	struct tm *tm;
+	struct progress prog;
+
+	prog.phase = -1;
+	prog.current = -1;
+
+	time(&t);
+	tm = localtime(&t);
+	if (keytype == DSA)
+	    strftime(default_comment, 30, "dsa-key-%Y%m%d", tm);
+	else
+	    strftime(default_comment, 30, "rsa-key-%Y%m%d", tm);
+
+	random_init();
+	entropy = get_random_data(bits / 8);
+	random_add_heavynoise(entropy, bits / 8);
+	memset(entropy, 0, bits/8);
+	sfree(entropy);
+
+	if (keytype == DSA) {
+	    struct dss_key *dsskey = snew(struct dss_key);
+	    dsa_generate(dsskey, bits, progressfn, &prog);
+	    ssh2key = snew(struct ssh2_userkey);
+	    ssh2key->data = dsskey;
+	    ssh2key->alg = &ssh_dss;
+	    ssh1key = NULL;
+	} else {
+	    struct RSAKey *rsakey = snew(struct RSAKey);
+	    rsa_generate(rsakey, bits, progressfn, &prog);
+	    rsakey->comment = NULL;
+	    if (keytype == RSA1) {
+		ssh1key = rsakey;
+	    } else {
+		ssh2key = snew(struct ssh2_userkey);
+		ssh2key->data = rsakey;
+		ssh2key->alg = &ssh_rsa;
+	    }
+	}
+	progressfn(&prog, PROGFN_PROGRESS, INT_MAX, -1);
+
+	if (ssh2key)
+	    ssh2key->comment = dupstr(default_comment);
+	if (ssh1key)
+	    ssh1key->comment = dupstr(default_comment);
+
+    } else {
+	const char *error = NULL;
+	int encrypted;
+
+	assert(infile != NULL);
+
+	/*
+	 * Find out whether the input key is encrypted.
+	 */
+	if (intype == SSH_KEYTYPE_SSH1)
+	    encrypted = rsakey_encrypted(&infilename, &origcomment);
+	else if (intype == SSH_KEYTYPE_SSH2)
+	    encrypted = ssh2_userkey_encrypted(&infilename, &origcomment);
+	else
+	    encrypted = import_encrypted(&infilename, intype, &origcomment);
+
+	/*
+	 * If so, ask for a passphrase.
+	 */
+	if (encrypted && load_encrypted) {
+	    passphrase = snewn(512, char);
+	    if (!console_get_line("Enter passphrase to load key: ",
+				  passphrase, 512, TRUE)) {
+		perror("puttygen: unable to read passphrase");
+		return 1;
+	    }
+	} else {
+	    passphrase = NULL;
+	}
+
+	switch (intype) {
+	    int ret;
+
+	  case SSH_KEYTYPE_SSH1:
+	    ssh1key = snew(struct RSAKey);
+	    if (!load_encrypted) {
+		void *vblob;
+		char *blob;
+		int n, bloblen;
+
+		ret = rsakey_pubblob(&infilename, &vblob, &bloblen, &error);
+		blob = (char *)vblob;
+
+		n = 4;		       /* skip modulus bits */
+		n += ssh1_read_bignum(blob + n, &ssh1key->exponent);
+		n += ssh1_read_bignum(blob + n, &ssh1key->modulus);
+		ssh1key->comment = NULL;
+		ssh1key->private_exponent = NULL;
+	    } else {
+		ret = loadrsakey(&infilename, ssh1key, passphrase, &error);
+	    }
+	    if (ret > 0)
+		error = NULL;
+	    else if (!error)
+		error = "unknown error";
+	    break;
+
+	  case SSH_KEYTYPE_SSH2:
+	    if (!load_encrypted) {
+		ssh2blob = ssh2_userkey_loadpub(&infilename, &ssh2alg,
+						&ssh2bloblen, &error);
+		ssh2algf = find_pubkey_alg(ssh2alg);
+		if (ssh2algf)
+		    bits = ssh2algf->pubkey_bits(ssh2blob, ssh2bloblen);
+		else
+		    bits = -1;
+	    } else {
+		ssh2key = ssh2_load_userkey(&infilename, passphrase, &error);
+	    }
+	    if ((ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) || ssh2blob)
+		error = NULL;
+	    else if (!error) {
+		if (ssh2key == SSH2_WRONG_PASSPHRASE)
+		    error = "wrong passphrase";
+		else
+		    error = "unknown error";
+	    }
+	    break;
+
+	  case SSH_KEYTYPE_OPENSSH:
+	  case SSH_KEYTYPE_SSHCOM:
+	    ssh2key = import_ssh2(&infilename, intype, passphrase);
+	    if (ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE)
+		error = NULL;
+	    else if (!error) {
+		if (ssh2key == SSH2_WRONG_PASSPHRASE)
+		    error = "wrong passphrase";
+		else
+		    error = "unknown error";
+	    }
+	    break;
+
+	  default:
+	    assert(0);
+	}
+
+	if (error) {
+	    fprintf(stderr, "puttygen: error loading `%s': %s\n",
+		    infile, error);
+	    return 1;
+	}
+    }
+
+    /*
+     * Change the comment if asked to.
+     */
+    if (comment) {
+	if (sshver == 1) {
+	    assert(ssh1key);
+	    sfree(ssh1key->comment);
+	    ssh1key->comment = dupstr(comment);
+	} else {
+	    assert(ssh2key);
+	    sfree(ssh2key->comment);
+	    ssh2key->comment = dupstr(comment);
+	}
+    }
+
+    /*
+     * Prompt for a new passphrase if we have been asked to, or if
+     * we have just generated a key.
+     */
+    if (change_passphrase || keytype != NOKEYGEN) {
+	char *passphrase2;
+
+	if (passphrase) {
+	    memset(passphrase, 0, strlen(passphrase));
+	    sfree(passphrase);
+	}
+
+	passphrase = snewn(512, char);
+	passphrase2 = snewn(512, char);
+	if (!console_get_line("Enter passphrase to save key: ",
+			      passphrase, 512, TRUE) ||
+	    !console_get_line("Re-enter passphrase to verify: ",
+			      passphrase2, 512, TRUE)) {
+	    perror("puttygen: unable to read new passphrase");
+	    return 1;
+	}
+	if (strcmp(passphrase, passphrase2)) {
+	    fprintf(stderr, "puttygen: passphrases do not match\n");
+	    return 1;
+	}
+	memset(passphrase2, 0, strlen(passphrase2));
+	sfree(passphrase2);
+	if (!*passphrase) {
+	    sfree(passphrase);
+	    passphrase = NULL;
+	}
+    }
+
+    /*
+     * Write output.
+     * 
+     * (In the case where outfile and outfiletmp are both NULL,
+     * there is no semantic reason to initialise outfilename at
+     * all; but we have to write _something_ to it or some compiler
+     * will probably complain that it might be used uninitialised.)
+     */
+    if (outfiletmp)
+	outfilename = filename_from_str(outfiletmp);
+    else
+	outfilename = filename_from_str(outfile ? outfile : "");
+
+    switch (outtype) {
+	int ret;
+
+      case PRIVATE:
+	if (sshver == 1) {
+	    assert(ssh1key);
+	    ret = saversakey(&outfilename, ssh1key, passphrase);
+	    if (!ret) {
+		fprintf(stderr, "puttygen: unable to save SSH1 private key\n");
+		return 1;
+	    }
+	} else {
+	    assert(ssh2key);
+	    ret = ssh2_save_userkey(&outfilename, ssh2key, passphrase);
+ 	    if (!ret) {
+		fprintf(stderr, "puttygen: unable to save SSH2 private key\n");
+		return 1;
+	    }
+	}
+	if (outfiletmp) {
+	    if (!move(outfiletmp, outfile))
+		return 1;	       /* rename failed */
+	}
+	break;
+
+      case PUBLIC:
+      case PUBLICO:
+	if (sshver == 1) {
+	    FILE *fp;
+	    char *dec1, *dec2;
+
+	    assert(ssh1key);
+
+	    if (outfile)
+		fp = f_open(outfilename, "w");
+	    else
+		fp = stdout;
+	    dec1 = bignum_decimal(ssh1key->exponent);
+	    dec2 = bignum_decimal(ssh1key->modulus);
+	    fprintf(fp, "%d %s %s %s\n", bignum_bitcount(ssh1key->modulus),
+		    dec1, dec2, ssh1key->comment);
+	    sfree(dec1);
+	    sfree(dec2);
+	    if (outfile)
+		fclose(fp);
+	} else if (outtype == PUBLIC) {
+	    if (!ssh2blob) {
+		assert(ssh2key);
+		ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
+						     &ssh2bloblen);
+	    }
+	    save_ssh2_pubkey(outfile, ssh2key ? ssh2key->comment : origcomment,
+			     ssh2blob, ssh2bloblen);
+	} else if (outtype == PUBLICO) {
+	    char *buffer, *p;
+	    int i;
+	    FILE *fp;
+
+	    if (!ssh2blob) {
+		assert(ssh2key);
+		ssh2blob = ssh2key->alg->public_blob(ssh2key->data,
+						     &ssh2bloblen);
+	    }
+	    if (!ssh2alg) {
+		assert(ssh2key);
+		ssh2alg = ssh2key->alg->name;
+	    }
+	    if (ssh2key)
+		comment = ssh2key->comment;
+	    else
+		comment = origcomment;
+
+	    buffer = snewn(strlen(ssh2alg) +
+			   4 * ((ssh2bloblen+2) / 3) +
+			   strlen(comment) + 3, char);
+	    strcpy(buffer, ssh2alg);
+	    p = buffer + strlen(buffer);
+	    *p++ = ' ';
+	    i = 0;
+	    while (i < ssh2bloblen) {
+		int n = (ssh2bloblen - i < 3 ? ssh2bloblen - i : 3);
+		base64_encode_atom(ssh2blob + i, n, p);
+		i += n;
+		p += 4;
+	    }
+	    if (*comment) {
+		*p++ = ' ';
+		strcpy(p, comment);
+	    } else
+		*p++ = '\0';
+
+	    if (outfile)
+		fp = f_open(outfilename, "w");
+	    else
+		fp = stdout;
+	    fprintf(fp, "%s\n", buffer);
+	    if (outfile)
+		fclose(fp);
+
+	    sfree(buffer);
+	}
+	break;
+
+      case FP:
+	{
+	    FILE *fp;
+	    char *fingerprint;
+
+	    if (sshver == 1) {
+		assert(ssh1key);
+		fingerprint = snewn(128, char);
+		rsa_fingerprint(fingerprint, 128, ssh1key);
+	    } else {
+		if (ssh2key) {
+		    fingerprint = ssh2key->alg->fingerprint(ssh2key->data);
+		} else {
+		    assert(ssh2blob);
+		    fingerprint = blobfp(ssh2alg, bits, ssh2blob, ssh2bloblen);
+		}
+	    }
+
+	    if (outfile)
+		fp = f_open(outfilename, "w");
+	    else
+		fp = stdout;
+	    fprintf(fp, "%s\n", fingerprint);
+	    if (outfile)
+		fclose(fp);
+
+	    sfree(fingerprint);
+	}
+	break;
+	
+      case OPENSSH:
+      case SSHCOM:
+	assert(sshver == 2);
+	assert(ssh2key);
+	ret = export_ssh2(&outfilename, outtype, ssh2key, passphrase);
+	if (!ret) {
+	    fprintf(stderr, "puttygen: unable to export key\n");
+	    return 1;
+	}
+	if (outfiletmp) {
+	    if (!move(outfiletmp, outfile))
+		return 1;	       /* rename failed */
+	}
+	break;
+    }
+
+    if (passphrase) {
+	memset(passphrase, 0, strlen(passphrase));
+	sfree(passphrase);
+    }
+
+    if (ssh1key)
+	freersakey(ssh1key);
+    if (ssh2key) {
+	ssh2key->alg->freekey(ssh2key->data);
+	sfree(ssh2key);
+    }
+
+    return 0;
+}
+
+#ifdef TEST_CMDGEN
+
+#undef main
+
+#include <stdarg.h>
+
+int passes, fails;
+
+void setup_passphrases(char *first, ...)
+{
+    va_list ap;
+    char *next;
+
+    nprompts = 0;
+    if (first) {
+	prompts[nprompts++] = first;
+	va_start(ap, first);
+	while ((next = va_arg(ap, char *)) != NULL) {
+	    assert(nprompts < lenof(prompts));
+	    prompts[nprompts++] = next;
+	}
+	va_end(ap);
+    }
+}
+
+void test(int retval, ...)
+{
+    va_list ap;
+    int i, argc, ret;
+    char **argv;
+
+    argc = 0;
+    va_start(ap, retval);
+    while (va_arg(ap, char *) != NULL)
+	argc++;
+    va_end(ap);
+
+    argv = snewn(argc+1, char *);
+    va_start(ap, retval);
+    for (i = 0; i <= argc; i++)
+	argv[i] = va_arg(ap, char *);
+    va_end(ap);
+
+    promptsgot = 0;
+    ret = cmdgen_main(argc, argv);
+
+    if (ret != retval) {
+	printf("FAILED retval (exp %d got %d):", retval, ret);
+	for (i = 0; i < argc; i++)
+	    printf(" %s", argv[i]);
+	printf("\n");
+	fails++;
+    } else if (promptsgot != nprompts) {
+	printf("FAILED nprompts (exp %d got %d):", nprompts, promptsgot);
+	for (i = 0; i < argc; i++)
+	    printf(" %s", argv[i]);
+	printf("\n");
+	fails++;
+    } else {
+	passes++;
+    }
+}
+
+void filecmp(char *file1, char *file2, char *fmt, ...)
+{
+    /*
+     * Ideally I should do file comparison myself, to maximise the
+     * portability of this test suite once this application begins
+     * running on non-Unix platforms. For the moment, though,
+     * calling Unix diff is perfectly adequate.
+     */
+    char *buf;
+    int ret;
+
+    buf = dupprintf("diff -q '%s' '%s'", file1, file2);
+    ret = system(buf);
+    sfree(buf);
+
+    if (ret) {
+	va_list ap;
+
+	printf("FAILED diff (ret=%d): ", ret);
+
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+
+	printf("\n");
+
+	fails++;
+    } else
+	passes++;
+}
+
+char *cleanup_fp(char *s)
+{
+    char *p;
+
+    if (!strncmp(s, "ssh-", 4)) {
+	s += strcspn(s, " \n\t");
+	s += strspn(s, " \n\t");
+    }
+
+    p = s;
+    s += strcspn(s, " \n\t");
+    s += strspn(s, " \n\t");
+    s += strcspn(s, " \n\t");
+
+    return dupprintf("%.*s", s - p, p);
+}
+
+char *get_fp(char *filename)
+{
+    FILE *fp;
+    char buf[256], *ret;
+
+    fp = fopen(filename, "r");
+    if (!fp)
+	return NULL;
+    ret = fgets(buf, sizeof(buf), fp);
+    fclose(fp);
+    if (!ret)
+	return NULL;
+    return cleanup_fp(buf);
+}
+
+void check_fp(char *filename, char *fp, char *fmt, ...)
+{
+    char *newfp;
+
+    if (!fp)
+	return;
+
+    newfp = get_fp(filename);
+
+    if (!strcmp(fp, newfp)) {
+	passes++;
+    } else {
+	va_list ap;
+
+	printf("FAILED check_fp ['%s' != '%s']: ", newfp, fp);
+
+	va_start(ap, fmt);
+	vprintf(fmt, ap);
+	va_end(ap);
+
+	printf("\n");
+
+	fails++;
+    }
+
+    sfree(newfp);
+}
+
+int main(int argc, char **argv)
+{
+    int i;
+    static char *const keytypes[] = { "rsa1", "dsa", "rsa" };
+
+    /*
+     * Even when this thing is compiled for automatic test mode,
+     * it's helpful to be able to invoke it with command-line
+     * options for _manual_ tests.
+     */
+    if (argc > 1)
+	return cmdgen_main(argc, argv);
+
+    passes = fails = 0;
+
+    for (i = 0; i < lenof(keytypes); i++) {
+	char filename[128], osfilename[128], scfilename[128];
+	char pubfilename[128], tmpfilename1[128], tmpfilename2[128];
+	char *fp;
+
+	sprintf(filename, "test-%s.ppk", keytypes[i]);
+	sprintf(pubfilename, "test-%s.pub", keytypes[i]);
+	sprintf(osfilename, "test-%s.os", keytypes[i]);
+	sprintf(scfilename, "test-%s.sc", keytypes[i]);
+	sprintf(tmpfilename1, "test-%s.tmp1", keytypes[i]);
+	sprintf(tmpfilename2, "test-%s.tmp2", keytypes[i]);
+
+	/*
+	 * Create an encrypted key.
+	 */
+	setup_passphrases("sponge", "sponge", NULL);
+	test(0, "puttygen", "-t", keytypes[i], "-o", filename, NULL);
+
+	/*
+	 * List the public key in OpenSSH format.
+	 */
+	setup_passphrases(NULL);
+	test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL);
+	{
+	    char cmdbuf[256];
+	    fp = NULL;
+	    sprintf(cmdbuf, "ssh-keygen -l -f '%s' > '%s'",
+		    pubfilename, tmpfilename1);
+	    if (system(cmdbuf) ||
+		(fp = get_fp(tmpfilename1)) == NULL) {
+		printf("UNABLE to test fingerprint matching against OpenSSH");
+	    }
+	}
+
+	/*
+	 * List the public key in IETF/ssh.com format.
+	 */
+	setup_passphrases(NULL);
+	test(0, "puttygen", "-p", filename, NULL);
+
+	/*
+	 * List the fingerprint of the key.
+	 */
+	setup_passphrases(NULL);
+	test(0, "puttygen", "-l", filename, "-o", tmpfilename1, NULL);
+	if (!fp) {
+	    /*
+	     * If we can't test fingerprints against OpenSSH, we
+	     * can at the very least test equality of all the
+	     * fingerprints we generate of this key throughout
+	     * testing.
+	     */
+	    fp = get_fp(tmpfilename1);
+	} else {
+	    check_fp(tmpfilename1, fp, "%s initial fp", keytypes[i]);
+	}
+
+	/*
+	 * Change the comment of the key; this _does_ require a
+	 * passphrase owing to the tamperproofing.
+	 * 
+	 * NOTE: In SSH1, this only requires a passphrase because
+	 * of inadequacies of the loading and saving mechanisms. In
+	 * _principle_, it should be perfectly possible to modify
+	 * the comment on an SSH1 key without requiring a
+	 * passphrase; the only reason I can't do it is because my
+	 * loading and saving mechanisms don't include a method of
+	 * loading all the key data without also trying to decrypt
+	 * the private section.
+	 * 
+	 * I don't consider this to be a problem worth solving,
+	 * because (a) to fix it would probably end up bloating
+	 * PuTTY proper, and (b) SSH1 is on the way out anyway so
+	 * it shouldn't be highly significant. If it seriously
+	 * bothers anyone then perhaps I _might_ be persuadable.
+	 */
+	setup_passphrases("sponge", NULL);
+	test(0, "puttygen", "-C", "new-comment", filename, NULL);
+
+	/*
+	 * Change the passphrase to nothing.
+	 */
+	setup_passphrases("sponge", "", "", NULL);
+	test(0, "puttygen", "-P", filename, NULL);
+
+	/*
+	 * Change the comment of the key again; this time we expect no
+	 * passphrase to be required.
+	 */
+	setup_passphrases(NULL);
+	test(0, "puttygen", "-C", "new-comment-2", filename, NULL);
+
+	/*
+	 * Export the private key into OpenSSH format; no passphrase
+	 * should be required since the key is currently unencrypted.
+	 * For RSA1 keys, this should give an error.
+	 */
+	setup_passphrases(NULL);
+	test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename,
+	     filename, NULL);
+
+	if (i) {
+	    /*
+	     * List the fingerprint of the OpenSSH-formatted key.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
+	    check_fp(tmpfilename1, fp, "%s openssh clear fp", keytypes[i]);
+
+	    /*
+	     * List the public half of the OpenSSH-formatted key in
+	     * OpenSSH format.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-L", osfilename, NULL);
+
+	    /*
+	     * List the public half of the OpenSSH-formatted key in
+	     * IETF/ssh.com format.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-p", osfilename, NULL);
+	}
+
+	/*
+	 * Export the private key into ssh.com format; no passphrase
+	 * should be required since the key is currently unencrypted.
+	 * For RSA1 keys, this should give an error.
+	 */
+	setup_passphrases(NULL);
+	test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename,
+	     filename, NULL);
+
+	if (i) {
+	    /*
+	     * List the fingerprint of the ssh.com-formatted key.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
+	    check_fp(tmpfilename1, fp, "%s ssh.com clear fp", keytypes[i]);
+
+	    /*
+	     * List the public half of the ssh.com-formatted key in
+	     * OpenSSH format.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-L", scfilename, NULL);
+
+	    /*
+	     * List the public half of the ssh.com-formatted key in
+	     * IETF/ssh.com format.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-p", scfilename, NULL);
+	}
+
+	if (i) {
+	    /*
+	     * Convert from OpenSSH into ssh.com.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", osfilename, "-o", tmpfilename1,
+		 "-O", "private-sshcom", NULL);
+
+	    /*
+	     * Convert from ssh.com back into a PuTTY key,
+	     * supplying the same comment as we had before we
+	     * started to ensure the comparison works.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+		 "-o", tmpfilename2, NULL);
+
+	    /*
+	     * See if the PuTTY key thus generated is the same as
+	     * the original.
+	     */
+	    filecmp(filename, tmpfilename2,
+		    "p->o->s->p clear %s", keytypes[i]);
+
+	    /*
+	     * Convert from ssh.com to OpenSSH.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", scfilename, "-o", tmpfilename1,
+		 "-O", "private-openssh", NULL);
+
+	    /*
+	     * Convert from OpenSSH back into a PuTTY key,
+	     * supplying the same comment as we had before we
+	     * started to ensure the comparison works.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+		 "-o", tmpfilename2, NULL);
+
+	    /*
+	     * See if the PuTTY key thus generated is the same as
+	     * the original.
+	     */
+	    filecmp(filename, tmpfilename2,
+		    "p->s->o->p clear %s", keytypes[i]);
+
+	    /*
+	     * Finally, do a round-trip conversion between PuTTY
+	     * and ssh.com without involving OpenSSH, to test that
+	     * the key comment is preserved in that case.
+	     */
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
+		 filename, NULL);
+	    setup_passphrases(NULL);
+	    test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
+	    filecmp(filename, tmpfilename2,
+		    "p->s->p clear %s", keytypes[i]);
+	}
+
+	/*
+	 * Check that mismatched passphrases cause an error.
+	 */
+	setup_passphrases("sponge2", "sponge3", NULL);
+	test(1, "puttygen", "-P", filename, NULL);
+
+	/*
+	 * Put a passphrase back on.
+	 */
+	setup_passphrases("sponge2", "sponge2", NULL);
+	test(0, "puttygen", "-P", filename, NULL);
+
+	/*
+	 * Export the private key into OpenSSH format, this time
+	 * while encrypted. For RSA1 keys, this should give an
+	 * error.
+	 */
+	if (i == 0)
+	    setup_passphrases(NULL);   /* error, hence no passphrase read */
+	else
+	    setup_passphrases("sponge2", NULL);
+	test((i==0), "puttygen", "-O", "private-openssh", "-o", osfilename,
+	     filename, NULL);
+
+	if (i) {
+	    /*
+	     * List the fingerprint of the OpenSSH-formatted key.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL);
+	    check_fp(tmpfilename1, fp, "%s openssh encrypted fp", keytypes[i]);
+
+	    /*
+	     * List the public half of the OpenSSH-formatted key in
+	     * OpenSSH format.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-L", osfilename, NULL);
+
+	    /*
+	     * List the public half of the OpenSSH-formatted key in
+	     * IETF/ssh.com format.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-p", osfilename, NULL);
+	}
+
+	/*
+	 * Export the private key into ssh.com format, this time
+	 * while encrypted. For RSA1 keys, this should give an
+	 * error.
+	 */
+	if (i == 0)
+	    setup_passphrases(NULL);   /* error, hence no passphrase read */
+	else
+	    setup_passphrases("sponge2", NULL);
+	test((i==0), "puttygen", "-O", "private-sshcom", "-o", scfilename,
+	     filename, NULL);
+
+	if (i) {
+	    /*
+	     * List the fingerprint of the ssh.com-formatted key.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL);
+	    check_fp(tmpfilename1, fp, "%s ssh.com encrypted fp", keytypes[i]);
+
+	    /*
+	     * List the public half of the ssh.com-formatted key in
+	     * OpenSSH format.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-L", scfilename, NULL);
+
+	    /*
+	     * List the public half of the ssh.com-formatted key in
+	     * IETF/ssh.com format.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-p", scfilename, NULL);
+	}
+
+	if (i) {
+	    /*
+	     * Convert from OpenSSH into ssh.com.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", osfilename, "-o", tmpfilename1,
+		 "-O", "private-sshcom", NULL);
+
+	    /*
+	     * Convert from ssh.com back into a PuTTY key,
+	     * supplying the same comment as we had before we
+	     * started to ensure the comparison works.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+		 "-o", tmpfilename2, NULL);
+
+	    /*
+	     * See if the PuTTY key thus generated is the same as
+	     * the original.
+	     */
+	    filecmp(filename, tmpfilename2,
+		    "p->o->s->p encrypted %s", keytypes[i]);
+
+	    /*
+	     * Convert from ssh.com to OpenSSH.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", scfilename, "-o", tmpfilename1,
+		 "-O", "private-openssh", NULL);
+
+	    /*
+	     * Convert from OpenSSH back into a PuTTY key,
+	     * supplying the same comment as we had before we
+	     * started to ensure the comparison works.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", tmpfilename1, "-C", "new-comment-2",
+		 "-o", tmpfilename2, NULL);
+
+	    /*
+	     * See if the PuTTY key thus generated is the same as
+	     * the original.
+	     */
+	    filecmp(filename, tmpfilename2,
+		    "p->s->o->p encrypted %s", keytypes[i]);
+
+	    /*
+	     * Finally, do a round-trip conversion between PuTTY
+	     * and ssh.com without involving OpenSSH, to test that
+	     * the key comment is preserved in that case.
+	     */
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", "-O", "private-sshcom", "-o", tmpfilename1,
+		 filename, NULL);
+	    setup_passphrases("sponge2", NULL);
+	    test(0, "puttygen", tmpfilename1, "-o", tmpfilename2, NULL);
+	    filecmp(filename, tmpfilename2,
+		    "p->s->p encrypted %s", keytypes[i]);
+	}
+
+	/*
+	 * Load with the wrong passphrase.
+	 */
+	setup_passphrases("sponge8", NULL);
+	test(1, "puttygen", "-C", "spurious-new-comment", filename, NULL);
+
+	/*
+	 * Load a totally bogus file.
+	 */
+	setup_passphrases(NULL);
+	test(1, "puttygen", "-C", "spurious-new-comment", pubfilename, NULL);
+    }
+    printf("%d passes, %d fails\n", passes, fails);
+    return 0;
+}
+
+#endif

+ 11 - 7
putty/IMPORT.C

@@ -483,9 +483,9 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase)
     struct ssh2_userkey *retval = NULL;
     char *errmsg;
     unsigned char *blob;
-    int blobsize, blobptr, privptr;
-    char *modptr;
-    int modlen;
+    int blobsize = 0, blobptr, privptr;
+    char *modptr = NULL;
+    int modlen = 0;
 
     blob = NULL;
 
@@ -559,6 +559,8 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase)
 	num_integers = 9;
     else if (key->type == OSSH_DSA)
 	num_integers = 6;
+    else
+	num_integers = 0;	       /* placate compiler warnings */
 
     /*
      * Space to create key blob in.
@@ -580,6 +582,7 @@ struct ssh2_userkey *openssh_read(const Filename *filename, char *passphrase)
 	if (ret < 0 || id != 2 ||
 	    key->keyblob+key->keyblob_len-p < len) {
 	    errmsg = "ASN.1 decoding failure";
+	    retval = SSH2_WRONG_PASSPHRASE;
 	    goto error;
 	}
 
@@ -666,7 +669,7 @@ int openssh_write(const Filename *filename, struct ssh2_userkey *key,
 		  char *passphrase)
 {
     unsigned char *pubblob, *privblob, *spareblob;
-    int publen, privlen, sparelen;
+    int publen, privlen, sparelen = 0;
     unsigned char *outblob;
     int outlen;
     struct mpint_pos numbers[9];
@@ -1200,7 +1203,7 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase)
     struct ssh2_userkey *ret = NULL, *retkey;
     const struct ssh_signkey *alg;
     unsigned char *blob = NULL;
-    int blobsize, publen, privlen;
+    int blobsize = 0, publen, privlen;
 
     if (!key)
         return NULL;
@@ -1321,7 +1324,7 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase)
      * Strip away the containing string to get to the real meat.
      */
     len = GET_32BIT(ciphertext);
-    if (len > cipherlen-4) {
+    if (len < 0 || len > cipherlen-4) {
         errmsg = "containing string was ill-formed";
         goto error;
     }
@@ -1388,7 +1391,8 @@ struct ssh2_userkey *sshcom_read(const Filename *filename, char *passphrase)
         publen = pos;
         pos += put_mp(blob+pos, x.start, x.bytes);
         privlen = pos - publen;
-    }
+    } else
+	return NULL;
 
     assert(privlen > 0);	       /* should have bombed by now if not */
 

+ 1 - 1
putty/LICENCE

@@ -1,4 +1,4 @@
-PuTTY is copyright 1997-2003 Simon Tatham.
+PuTTY is copyright 1997-2004 Simon Tatham.
 
 Portions copyright Robert de Bath, Joris van Rantwijk, Delian
 Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,

+ 6 - 0
putty/MAKEFILE.BOR

@@ -276,6 +276,9 @@ be_none.obj: be_none.c putty.h puttyps.h network.h misc.h winstuff.h \
 be_nossh.obj: be_nossh.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
 		charset\charset.h
+cmdgen.obj: cmdgen.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
 cmdline.obj: cmdline.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
 		charset\charset.h
@@ -481,6 +484,9 @@ uxcfg.obj: unix\uxcfg.c putty.h dialog.h storage.h puttyps.h network.h \
 uxcons.obj: unix\uxcons.c putty.h storage.h ssh.h puttyps.h network.h misc.h \
 		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
 		tree234.h winhelp.h charset\charset.h
+uxgen.obj: unix\uxgen.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
 uxmisc.obj: unix\uxmisc.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
 		charset\charset.h

+ 6 - 0
putty/MAKEFILE.CYG

@@ -221,6 +221,9 @@ be_none.o: be_none.c putty.h puttyps.h network.h misc.h winstuff.h \
 be_nossh.o: be_nossh.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac/macstuff.h unix/unix.h puttymem.h tree234.h winhelp.h \
 		charset/charset.h
+cmdgen.o: cmdgen.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac/macstuff.h unix/unix.h tree234.h \
+		winhelp.h charset/charset.h
 cmdline.o: cmdline.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac/macstuff.h unix/unix.h puttymem.h tree234.h winhelp.h \
 		charset/charset.h
@@ -424,6 +427,9 @@ uxcfg.o: unix/uxcfg.c putty.h dialog.h storage.h puttyps.h network.h misc.h \
 uxcons.o: unix/uxcons.c putty.h storage.h ssh.h puttyps.h network.h misc.h \
 		puttymem.h int64.h winstuff.h mac/macstuff.h unix/unix.h \
 		tree234.h winhelp.h charset/charset.h
+uxgen.o: unix/uxgen.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac/macstuff.h unix/unix.h puttymem.h tree234.h winhelp.h \
+		charset/charset.h
 uxmisc.o: unix/uxmisc.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac/macstuff.h unix/unix.h puttymem.h tree234.h winhelp.h \
 		charset/charset.h

+ 523 - 0
putty/MAKEFILE.LCC

@@ -0,0 +1,523 @@
+# Makefile for PuTTY under lcc.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+#
+# Extra options you can set:
+#
+#  - FWHACK=-DFWHACK
+#      Enables a hack that tunnels through some firewall proxies.
+#
+#  - VER=-DSNAPSHOT=1999-01-25
+#      Generates executables whose About box report them as being a
+#      development snapshot.
+#
+#  - VER=-DRELEASE=0.43
+#      Generates executables whose About box report them as being a
+#      release version.
+#
+#  - COMPAT=-DAUTO_WINSOCK
+#      Causes PuTTY to assume that <windows.h> includes its own WinSock
+#      header file, so that it won't try to include <winsock.h>.
+#
+#  - COMPAT=-DWINSOCK_TWO
+#      Causes the PuTTY utilities to include <winsock2.h> instead of
+#      <winsock.h>, except Plink which _needs_ WinSock 2 so it already
+#      does this.
+#
+#  - COMPAT=-DNO_SECURITY
+#      Disables Pageant's use of <aclapi.h>, which is not available
+#      with some development environments (such as older versions of
+#      the Cygwin/mingw GNU toolchain). This means that Pageant
+#      won't care about the local user ID of processes accessing it; a
+#      version of Pageant built with this option will therefore refuse
+#      to run under NT-series OSes on security grounds (although it
+#      will run fine on Win95-series OSes where there is no access
+#      control anyway).
+#
+#  - COMPAT=-DNO_MULTIMON
+#      Disables PuTTY's use of <multimon.h>, which is not available
+#      with some development environments. This means that PuTTY's
+#      full-screen mode (configurable to work on Alt-Enter) will
+#      not behave usefully in a multi-monitor environment.
+#
+#      Note that this definition is always enabled in the Cygwin
+#      build, since at the time of writing this <multimon.h> is
+#      known not to be available in Cygwin.
+#
+#  - COMPAT=-DMSVC4
+#  - RCFL=-DMSVC4
+#      Makes a couple of minor changes so that PuTTY compiles using
+#      MSVC 4. You will also need /DNO_SECURITY and /DNO_MULTIMON.
+#
+#  - RCFL=-DASCIICTLS
+#      Uses ASCII rather than Unicode to specify the tab control in
+#      the resource file. Probably most useful when compiling with
+#      Cygnus/mingw32, whose resource compiler may have less of a
+#      problem with it.
+#
+#  - XFLAGS=-DTELNET_DEFAULT
+#      Causes PuTTY to default to the Telnet protocol (in the absence
+#      of Default Settings and so on to the contrary). Normally PuTTY
+#      will default to SSH.
+#
+#  - XFLAGS=-DDEBUG
+#      Causes PuTTY to enable internal debugging.
+#
+#  - XFLAGS=-DMALLOC_LOG
+#      Causes PuTTY to emit a file called putty_mem.log, logging every
+#      memory allocation and free, so you can track memory leaks.
+#
+#  - XFLAGS=-DMINEFIELD
+#      Causes PuTTY to use a custom memory allocator, similar in
+#      concept to Electric Fence, in place of regular malloc(). Wastes
+#      huge amounts of RAM, but should cause heap-corruption bugs to
+#      show up as GPFs at the point of failure rather than appearing
+#      later on as second-level damage.
+#
+
+# If you rename this file to `Makefile', you should change this line,
+# so that the .rsp files still depend on the correct makefile.
+MAKEFILE = Makefile.lcc
+
+# C compilation flags
+CFLAGS = -D_WINDOWS
+
+# Get include directory for resource compiler
+
+.c.obj:
+	lcc -O -p6 $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS)  $*.c
+.rc.res:
+	lrc $(FWHACK) $(RCFL) -r $*.rc
+
+all: pageant.exe plink.exe pscp.exe psftp.exe putty.exe puttygen.exe \
+		puttytel.exe
+
+pageant.exe: misc.obj pageant.obj pageant.res pageantc.obj sshaes.obj \
+		sshbn.obj sshdes.obj sshdss.obj sshmd5.obj sshpubk.obj \
+		sshrsa.obj sshsh512.obj sshsha.obj tree234.obj version.obj \
+		winmisc.obj winutils.obj
+	lcclnk -subsystem  windows -o pageant.exe misc.obj pageant.obj pageant.res \
+		pageantc.obj sshaes.obj sshbn.obj sshdes.obj sshdss.obj \
+		sshmd5.obj sshpubk.obj sshrsa.obj sshsh512.obj sshsha.obj \
+		tree234.obj version.obj winmisc.obj winutils.obj shell32.lib \
+		wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib
+
+plink.exe: be_all.obj cmdline.obj console.obj ldisc.obj logging.obj misc.obj \
+		noise.obj pageantc.obj plink.obj plink.res portfwd.obj \
+		pproxy.obj proxy.obj raw.obj rlogin.obj settings.obj ssh.obj \
+		sshaes.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \
+		sshdes.obj sshdh.obj sshdss.obj sshmd5.obj sshpubk.obj \
+		sshrand.obj sshrsa.obj sshsh512.obj sshsha.obj sshzlib.obj \
+		telnet.obj tree234.obj version.obj wildcard.obj windefs.obj \
+		winmisc.obj winnet.obj winstore.obj x11fwd.obj
+	lcclnk  -o plink.exe be_all.obj cmdline.obj console.obj ldisc.obj \
+		logging.obj misc.obj noise.obj pageantc.obj plink.obj \
+		plink.res portfwd.obj pproxy.obj proxy.obj raw.obj \
+		rlogin.obj settings.obj ssh.obj sshaes.obj sshblowf.obj \
+		sshbn.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \
+		sshdss.obj sshmd5.obj sshpubk.obj sshrand.obj sshrsa.obj \
+		sshsh512.obj sshsha.obj sshzlib.obj telnet.obj tree234.obj \
+		version.obj wildcard.obj windefs.obj winmisc.obj winnet.obj \
+		winstore.obj x11fwd.obj shell32.lib wsock32.lib ws2_32.lib \
+		winspool.lib winmm.lib imm32.lib
+
+pscp.exe: be_none.obj cmdline.obj console.obj int64.obj logging.obj misc.obj \
+		noise.obj pageantc.obj portfwd.obj pproxy.obj proxy.obj \
+		scp.obj scp.res settings.obj sftp.obj ssh.obj sshaes.obj \
+		sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj sshdes.obj \
+		sshdh.obj sshdss.obj sshmd5.obj sshpubk.obj sshrand.obj \
+		sshrsa.obj sshsh512.obj sshsha.obj sshzlib.obj tree234.obj \
+		version.obj wildcard.obj windefs.obj winmisc.obj winnet.obj \
+		winsftp.obj winstore.obj x11fwd.obj
+	lcclnk  -o pscp.exe be_none.obj cmdline.obj console.obj int64.obj \
+		logging.obj misc.obj noise.obj pageantc.obj portfwd.obj \
+		pproxy.obj proxy.obj scp.obj scp.res settings.obj sftp.obj \
+		ssh.obj sshaes.obj sshblowf.obj sshbn.obj sshcrc.obj \
+		sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj sshmd5.obj \
+		sshpubk.obj sshrand.obj sshrsa.obj sshsh512.obj sshsha.obj \
+		sshzlib.obj tree234.obj version.obj wildcard.obj windefs.obj \
+		winmisc.obj winnet.obj winsftp.obj winstore.obj x11fwd.obj \
+		shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \
+		imm32.lib
+
+psftp.exe: be_none.obj cmdline.obj console.obj int64.obj logging.obj \
+		misc.obj noise.obj pageantc.obj portfwd.obj pproxy.obj \
+		proxy.obj psftp.obj scp.res settings.obj sftp.obj ssh.obj \
+		sshaes.obj sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj \
+		sshdes.obj sshdh.obj sshdss.obj sshmd5.obj sshpubk.obj \
+		sshrand.obj sshrsa.obj sshsh512.obj sshsha.obj sshzlib.obj \
+		tree234.obj version.obj wildcard.obj windefs.obj winmisc.obj \
+		winnet.obj winsftp.obj winstore.obj x11fwd.obj
+	lcclnk  -o psftp.exe be_none.obj cmdline.obj console.obj int64.obj \
+		logging.obj misc.obj noise.obj pageantc.obj portfwd.obj \
+		pproxy.obj proxy.obj psftp.obj scp.res settings.obj sftp.obj \
+		ssh.obj sshaes.obj sshblowf.obj sshbn.obj sshcrc.obj \
+		sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj sshmd5.obj \
+		sshpubk.obj sshrand.obj sshrsa.obj sshsh512.obj sshsha.obj \
+		sshzlib.obj tree234.obj version.obj wildcard.obj windefs.obj \
+		winmisc.obj winnet.obj winsftp.obj winstore.obj x11fwd.obj \
+		shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \
+		imm32.lib
+
+putty.exe: be_all.obj cmdline.obj config.obj dialog.obj ldisc.obj \
+		ldiscucs.obj logging.obj misc.obj noise.obj pageantc.obj \
+		portfwd.obj pproxy.obj printing.obj proxy.obj raw.obj \
+		rlogin.obj settings.obj sizetip.obj ssh.obj sshaes.obj \
+		sshblowf.obj sshbn.obj sshcrc.obj sshcrcda.obj sshdes.obj \
+		sshdh.obj sshdss.obj sshmd5.obj sshpubk.obj sshrand.obj \
+		sshrsa.obj sshsh512.obj sshsha.obj sshzlib.obj telnet.obj \
+		terminal.obj tree234.obj unicode.obj version.obj wcwidth.obj \
+		wildcard.obj win_res.res wincfg.obj winctrls.obj windefs.obj \
+		windlg.obj window.obj winmisc.obj winnet.obj winstore.obj \
+		winutils.obj x11fwd.obj
+	lcclnk -subsystem  windows -o putty.exe be_all.obj cmdline.obj config.obj \
+		dialog.obj ldisc.obj ldiscucs.obj logging.obj misc.obj \
+		noise.obj pageantc.obj portfwd.obj pproxy.obj printing.obj \
+		proxy.obj raw.obj rlogin.obj settings.obj sizetip.obj \
+		ssh.obj sshaes.obj sshblowf.obj sshbn.obj sshcrc.obj \
+		sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj sshmd5.obj \
+		sshpubk.obj sshrand.obj sshrsa.obj sshsh512.obj sshsha.obj \
+		sshzlib.obj telnet.obj terminal.obj tree234.obj unicode.obj \
+		version.obj wcwidth.obj wildcard.obj win_res.res wincfg.obj \
+		winctrls.obj windefs.obj windlg.obj window.obj winmisc.obj \
+		winnet.obj winstore.obj winutils.obj x11fwd.obj shell32.lib \
+		wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib
+
+puttygen.exe: import.obj misc.obj noise.obj puttygen.obj puttygen.res \
+		sshaes.obj sshbn.obj sshdes.obj sshdss.obj sshdssg.obj \
+		sshmd5.obj sshprime.obj sshpubk.obj sshrand.obj sshrsa.obj \
+		sshrsag.obj sshsh512.obj sshsha.obj tree234.obj version.obj \
+		winctrls.obj winmisc.obj winstore.obj winutils.obj
+	lcclnk -subsystem  windows -o puttygen.exe import.obj misc.obj noise.obj \
+		puttygen.obj puttygen.res sshaes.obj sshbn.obj sshdes.obj \
+		sshdss.obj sshdssg.obj sshmd5.obj sshprime.obj sshpubk.obj \
+		sshrand.obj sshrsa.obj sshrsag.obj sshsh512.obj sshsha.obj \
+		tree234.obj version.obj winctrls.obj winmisc.obj \
+		winstore.obj winutils.obj shell32.lib wsock32.lib ws2_32.lib \
+		winspool.lib winmm.lib imm32.lib
+
+puttytel.exe: be_nossh.obj cmdline.obj config.obj dialog.obj ldisc.obj \
+		ldiscucs.obj logging.obj misc.obj pproxy.obj printing.obj \
+		proxy.obj raw.obj rlogin.obj settings.obj sizetip.obj \
+		telnet.obj terminal.obj tree234.obj unicode.obj version.obj \
+		wcwidth.obj win_res.res wincfg.obj winctrls.obj windefs.obj \
+		windlg.obj window.obj winmisc.obj winnet.obj winstore.obj \
+		winutils.obj
+	lcclnk -subsystem  windows -o puttytel.exe be_nossh.obj cmdline.obj \
+		config.obj dialog.obj ldisc.obj ldiscucs.obj logging.obj \
+		misc.obj pproxy.obj printing.obj proxy.obj raw.obj \
+		rlogin.obj settings.obj sizetip.obj telnet.obj terminal.obj \
+		tree234.obj unicode.obj version.obj wcwidth.obj win_res.res \
+		wincfg.obj winctrls.obj windefs.obj windlg.obj window.obj \
+		winmisc.obj winnet.obj winstore.obj winutils.obj shell32.lib \
+		wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib
+
+be_all.obj: be_all.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+be_none.obj: be_none.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+be_nossh.obj: be_nossh.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+cmdgen.obj: cmdgen.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+cmdline.obj: cmdline.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+config.obj: config.c putty.h dialog.h storage.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h tree234.h \
+		winhelp.h charset\charset.h
+console.obj: console.c putty.h storage.h ssh.h puttyps.h network.h misc.h \
+		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
+		tree234.h winhelp.h charset\charset.h
+dialog.obj: dialog.c putty.h dialog.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+fromucs.obj: charset\fromucs.c charset\charset.h charset\internal.h
+gtkcols.obj: unix\gtkcols.c unix\gtkcols.h
+gtkdlg.obj: unix\gtkdlg.c unix\gtkcols.h unix\gtkpanel.h putty.h storage.h \
+		dialog.h tree234.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h winhelp.h \
+		charset\charset.h
+gtkpanel.obj: unix\gtkpanel.c unix\gtkpanel.h
+import.obj: import.c putty.h ssh.h misc.h puttyps.h network.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+int64.obj: int64.c int64.h
+ldisc.obj: ldisc.c putty.h terminal.h ldisc.h puttyps.h network.h misc.h \
+		tree234.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		winhelp.h charset\charset.h
+ldiscucs.obj: ldiscucs.c putty.h terminal.h ldisc.h puttyps.h network.h \
+		misc.h tree234.h winstuff.h mac\macstuff.h unix\unix.h \
+		puttymem.h winhelp.h charset\charset.h
+localenc.obj: charset\localenc.c charset\charset.h charset\internal.h
+logging.obj: logging.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+mac.obj: mac\mac.c mac\macresid.h putty.h ssh.h terminal.h mac\mac.h \
+		puttyps.h network.h misc.h puttymem.h int64.h tree234.h \
+		charset\charset.h winstuff.h mac\macstuff.h unix\unix.h \
+		winhelp.h
+mac_res.res: mac\mac_res.r mac\macresid.h mac\version.r
+macabout.obj: mac\macabout.c putty.h mac\mac.h mac\macresid.h puttyps.h \
+		network.h misc.h charset\charset.h tree234.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h winhelp.h
+macctrls.obj: mac\macctrls.c putty.h mac\mac.h mac\macresid.h dialog.h \
+		tree234.h puttyps.h network.h misc.h charset\charset.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h
+macdlg.obj: mac\macdlg.c putty.h dialog.h mac\mac.h mac\macresid.h storage.h \
+		puttyps.h network.h misc.h charset\charset.h tree234.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h
+macenc.obj: charset\macenc.c charset\charset.h charset\internal.h
+macevlog.obj: mac\macevlog.c putty.h mac\mac.h mac\macresid.h terminal.h \
+		puttyps.h network.h misc.h charset\charset.h tree234.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h
+macmisc.obj: mac\macmisc.c putty.h mac\mac.h puttyps.h network.h misc.h \
+		charset\charset.h tree234.h winstuff.h mac\macstuff.h \
+		unix\unix.h puttymem.h winhelp.h
+macnet.obj: mac\macnet.c putty.h network.h mac\mac.h puttyps.h misc.h \
+		charset\charset.h tree234.h winstuff.h mac\macstuff.h \
+		unix\unix.h puttymem.h winhelp.h
+macnoise.obj: mac\macnoise.c putty.h ssh.h storage.h puttyps.h network.h \
+		misc.h puttymem.h int64.h winstuff.h mac\macstuff.h \
+		unix\unix.h tree234.h winhelp.h charset\charset.h
+macpgen.obj: mac\macpgen.c mac\macpgrid.h putty.h ssh.h mac\mac.h puttyps.h \
+		network.h misc.h puttymem.h int64.h charset\charset.h \
+		tree234.h winstuff.h mac\macstuff.h unix\unix.h winhelp.h
+macpgen.res: mac\macpgen.r mac\macpgrid.h mac\version.r
+macpgkey.obj: mac\macpgkey.c putty.h mac\mac.h mac\macpgrid.h ssh.h \
+		puttyps.h network.h misc.h charset\charset.h tree234.h \
+		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
+		winhelp.h
+macstore.obj: mac\macstore.c putty.h storage.h mac\mac.h mac\macresid.h \
+		puttyps.h network.h misc.h charset\charset.h tree234.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h
+macterm.obj: mac\macterm.c mac\macresid.h putty.h charset\charset.h \
+		mac\mac.h terminal.h puttyps.h network.h misc.h tree234.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h
+macucs.obj: mac\macucs.c putty.h terminal.h misc.h mac\mac.h puttyps.h \
+		network.h tree234.h puttymem.h charset\charset.h winstuff.h \
+		mac\macstuff.h unix\unix.h winhelp.h
+mimeenc.obj: charset\mimeenc.c charset\charset.h charset\internal.h
+misc.obj: misc.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+mtcpnet.obj: mac\mtcpnet.c putty.h network.h mac\mac.h puttyps.h misc.h \
+		charset\charset.h tree234.h winstuff.h mac\macstuff.h \
+		unix\unix.h puttymem.h winhelp.h
+noise.obj: noise.c putty.h ssh.h storage.h puttyps.h network.h misc.h \
+		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
+		tree234.h winhelp.h charset\charset.h
+otnet.obj: mac\otnet.c putty.h network.h mac\mac.h puttyps.h misc.h \
+		charset\charset.h tree234.h winstuff.h mac\macstuff.h \
+		unix\unix.h puttymem.h winhelp.h
+pageant.obj: pageant.c putty.h ssh.h misc.h tree234.h puttyps.h network.h \
+		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
+		winhelp.h charset\charset.h
+pageant.res: pageant.rc pageant.ico pageants.ico
+pageantc.obj: pageantc.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+plink.obj: plink.c putty.h storage.h tree234.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h \
+		charset\charset.h
+plink.res: plink.rc putty.ico
+portfwd.obj: portfwd.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+pproxy.obj: pproxy.c putty.h network.h proxy.h puttyps.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+printing.obj: printing.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+proxy.obj: proxy.c putty.h network.h proxy.h puttyps.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+psftp.obj: psftp.c putty.h psftp.h storage.h ssh.h sftp.h int64.h puttyps.h \
+		network.h misc.h puttymem.h winstuff.h mac\macstuff.h \
+		unix\unix.h tree234.h winhelp.h charset\charset.h
+pterm.obj: unix\pterm.c putty.h terminal.h puttyps.h network.h misc.h \
+		tree234.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		winhelp.h charset\charset.h
+ptermm.obj: unix\ptermm.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+pty.obj: unix\pty.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+puttygen.obj: puttygen.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+puttygen.res: puttygen.rc puttygen.ico
+raw.obj: raw.c putty.h puttyps.h network.h misc.h winstuff.h mac\macstuff.h \
+		unix\unix.h puttymem.h tree234.h winhelp.h charset\charset.h
+rlogin.obj: rlogin.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+sbcs.obj: charset\sbcs.c charset\charset.h charset\internal.h
+sbcsdat.obj: charset\sbcsdat.c charset\charset.h charset\internal.h
+scp.obj: scp.c putty.h psftp.h ssh.h sftp.h storage.h puttyps.h network.h \
+		misc.h puttymem.h int64.h winstuff.h mac\macstuff.h \
+		unix\unix.h tree234.h winhelp.h charset\charset.h
+scp.res: scp.rc scp.ico
+settings.obj: settings.c putty.h storage.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h tree234.h \
+		winhelp.h charset\charset.h
+sftp.obj: sftp.c misc.h int64.h tree234.h sftp.h puttymem.h
+signal.obj: unix\signal.c
+sizetip.obj: sizetip.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+slookup.obj: charset\slookup.c charset\charset.h charset\internal.h \
+		charset\enum.c charset\sbcsdat.c charset\utf8.c
+ssh.obj: ssh.c putty.h tree234.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h winhelp.h \
+		charset\charset.h
+sshaes.obj: sshaes.c ssh.h puttymem.h network.h int64.h misc.h
+sshblowf.obj: sshblowf.c ssh.h puttymem.h network.h int64.h misc.h
+sshbn.obj: sshbn.c misc.h ssh.h puttymem.h network.h int64.h
+sshcrc.obj: sshcrc.c ssh.h puttymem.h network.h int64.h misc.h
+sshcrcda.obj: sshcrcda.c misc.h ssh.h puttymem.h network.h int64.h
+sshdes.obj: sshdes.c ssh.h puttymem.h network.h int64.h misc.h
+sshdh.obj: sshdh.c ssh.h puttymem.h network.h int64.h misc.h
+sshdss.obj: sshdss.c ssh.h misc.h puttymem.h network.h int64.h
+sshdssg.obj: sshdssg.c misc.h ssh.h puttymem.h network.h int64.h
+sshmd5.obj: sshmd5.c ssh.h puttymem.h network.h int64.h misc.h
+sshprime.obj: sshprime.c ssh.h puttymem.h network.h int64.h misc.h
+sshpubk.obj: sshpubk.c putty.h ssh.h misc.h puttyps.h network.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+sshrand.obj: sshrand.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+sshrsa.obj: sshrsa.c ssh.h misc.h puttymem.h network.h int64.h
+sshrsag.obj: sshrsag.c ssh.h puttymem.h network.h int64.h misc.h
+sshsh512.obj: sshsh512.c ssh.h puttymem.h network.h int64.h misc.h
+sshsha.obj: sshsha.c ssh.h puttymem.h network.h int64.h misc.h
+sshzlib.obj: sshzlib.c ssh.h puttymem.h network.h int64.h misc.h
+stricmp.obj: mac\stricmp.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+telnet.obj: telnet.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+terminal.obj: terminal.c putty.h terminal.h puttyps.h network.h misc.h \
+		tree234.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		winhelp.h charset\charset.h
+testback.obj: testback.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+toucs.obj: charset\toucs.c charset\charset.h charset\internal.h
+tree234.obj: tree234.c puttymem.h tree234.h
+unicode.obj: unicode.c putty.h terminal.h misc.h puttyps.h network.h \
+		tree234.h puttymem.h winstuff.h mac\macstuff.h unix\unix.h \
+		winhelp.h charset\charset.h
+utf8.obj: charset\utf8.c charset\charset.h charset\internal.h
+ux_x11.obj: unix\ux_x11.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+uxagentc.obj: unix\uxagentc.c putty.h misc.h tree234.h puttymem.h puttyps.h \
+		network.h winstuff.h mac\macstuff.h unix\unix.h winhelp.h \
+		charset\charset.h
+uxcfg.obj: unix\uxcfg.c putty.h dialog.h storage.h puttyps.h network.h \
+		misc.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		tree234.h winhelp.h charset\charset.h
+uxcons.obj: unix\uxcons.c putty.h storage.h ssh.h puttyps.h network.h misc.h \
+		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
+		tree234.h winhelp.h charset\charset.h
+uxgen.obj: unix\uxgen.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+uxmisc.obj: unix\uxmisc.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+uxnet.obj: unix\uxnet.c putty.h network.h tree234.h puttyps.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h \
+		charset\charset.h
+uxnoise.obj: unix\uxnoise.c putty.h ssh.h storage.h puttyps.h network.h \
+		misc.h puttymem.h int64.h winstuff.h mac\macstuff.h \
+		unix\unix.h tree234.h winhelp.h charset\charset.h
+uxplink.obj: unix\uxplink.c putty.h storage.h tree234.h puttyps.h network.h \
+		misc.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		winhelp.h charset\charset.h
+uxprint.obj: unix\uxprint.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+uxproxy.obj: unix\uxproxy.c tree234.h putty.h network.h proxy.h puttyps.h \
+		misc.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		winhelp.h charset\charset.h
+uxputty.obj: unix\uxputty.c putty.h storage.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h tree234.h \
+		winhelp.h charset\charset.h
+uxsel.obj: unix\uxsel.c putty.h tree234.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h winhelp.h \
+		charset\charset.h
+uxsftp.obj: unix\uxsftp.c putty.h psftp.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h tree234.h \
+		winhelp.h charset\charset.h
+uxstore.obj: unix\uxstore.c putty.h storage.h tree234.h puttyps.h network.h \
+		misc.h winstuff.h mac\macstuff.h unix\unix.h puttymem.h \
+		winhelp.h charset\charset.h
+uxucs.obj: unix\uxucs.c putty.h charset\charset.h terminal.h misc.h \
+		puttyps.h network.h tree234.h puttymem.h winstuff.h \
+		mac\macstuff.h unix\unix.h winhelp.h
+version.obj: version.c
+vsnprint.obj: mac\vsnprint.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+wcwidth.obj: wcwidth.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+wildcard.obj: wildcard.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+win_res.res: win_res.rc win_res.h putty.ico puttycfg.ico
+wincfg.obj: wincfg.c putty.h dialog.h storage.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h tree234.h \
+		winhelp.h charset\charset.h
+winctrls.obj: winctrls.c putty.h misc.h dialog.h puttyps.h network.h \
+		puttymem.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+windefs.obj: windefs.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+windlg.obj: windlg.c putty.h ssh.h win_res.h storage.h dialog.h puttyps.h \
+		network.h misc.h puttymem.h int64.h winstuff.h \
+		mac\macstuff.h unix\unix.h tree234.h winhelp.h \
+		charset\charset.h
+window.obj: window.c putty.h terminal.h storage.h win_res.h puttyps.h \
+		network.h misc.h tree234.h winstuff.h mac\macstuff.h \
+		unix\unix.h puttymem.h winhelp.h charset\charset.h
+winmisc.obj: winmisc.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+winnet.obj: winnet.c putty.h network.h tree234.h puttyps.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h winhelp.h \
+		charset\charset.h
+winsftp.obj: winsftp.c putty.h psftp.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
+winstore.obj: winstore.c putty.h storage.h puttyps.h network.h misc.h \
+		winstuff.h mac\macstuff.h unix\unix.h puttymem.h tree234.h \
+		winhelp.h charset\charset.h
+winutils.obj: winutils.c misc.h puttymem.h
+x11fwd.obj: x11fwd.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
+xenc.obj: charset\xenc.c charset\charset.h charset\internal.h
+xkeysym.obj: unix\xkeysym.c misc.h puttymem.h
+
+version.o: FORCE
+# Hack to force version.o to be rebuilt always
+FORCE:
+	lcc $(FWHACK) $(VER) $(CFLAGS) /c version.c
+
+clean:
+	-del *.obj
+	-del *.exe
+	-del *.res

+ 6 - 0
putty/MAKEFILE.VC

@@ -269,6 +269,9 @@ be_none.obj: be_none.c putty.h puttyps.h network.h misc.h winstuff.h \
 be_nossh.obj: be_nossh.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
 		charset\charset.h
+cmdgen.obj: cmdgen.c putty.h ssh.h puttyps.h network.h misc.h puttymem.h \
+		int64.h winstuff.h mac\macstuff.h unix\unix.h tree234.h \
+		winhelp.h charset\charset.h
 cmdline.obj: cmdline.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
 		charset\charset.h
@@ -474,6 +477,9 @@ uxcfg.obj: unix\uxcfg.c putty.h dialog.h storage.h puttyps.h network.h \
 uxcons.obj: unix\uxcons.c putty.h storage.h ssh.h puttyps.h network.h misc.h \
 		puttymem.h int64.h winstuff.h mac\macstuff.h unix\unix.h \
 		tree234.h winhelp.h charset\charset.h
+uxgen.obj: unix\uxgen.c putty.h puttyps.h network.h misc.h winstuff.h \
+		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
+		charset\charset.h
 uxmisc.obj: unix\uxmisc.c putty.h puttyps.h network.h misc.h winstuff.h \
 		mac\macstuff.h unix\unix.h puttymem.h tree234.h winhelp.h \
 		charset\charset.h

+ 322 - 6
putty/MKFILES.PL

@@ -184,12 +184,15 @@ foreach $i (keys %depends) {
 sub findfile {
   my ($name) = @_;
   my $dir, $i, $outdir = "";
-  $i = 0;
-  foreach $dir (@incdirs) {
-    $outdir = $dir, $i++ if -f "$dir$name";
+  unless (defined $findfilecache{$name}) {
+    $i = 0;
+    foreach $dir (@incdirs) {
+      $outdir = $dir, $i++ if -f "$dir$name";
+    }
+    die "multiple instances of source file $name\n" if $i > 1;
+    $findfilecache{$name} = $outdir . $name;
   }
-  die "multiple instances of source file $name\n" if $i > 1;
-  return "$outdir$name";
+  return $findfilecache{$name};
 }
 
 sub objects {
@@ -530,6 +533,256 @@ print
 "\t-del debug.log\n";
 select STDOUT; close OUT;
 
+##-- MSVC 6 Workspace and projects
+#
+# Note: All files created in this section are written in binary
+# mode, because although MSVC's command-line make can deal with
+# LF-only line endings, MSVC project files really _need_ to be
+# CRLF. Hence, in order for mkfiles.pl to generate usable project
+# files even when run from Unix, I make sure all files are binary
+# and explicitly write the CRLFs.
+#
+# Create directories if necessary
+mkdir 'MSVC'
+	if(! -d 'MSVC');
+chdir 'MSVC';
+@deps = &deps("X.obj", "X.res", "", "\\");
+%all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
+# Create the project files
+# Get names of all Windows projects (GUI and console)
+my @prognames = &prognames("GC");
+foreach $progname (@prognames) {
+	create_project(\%all_object_deps, $progname);
+}
+# Create the workspace file
+open OUT, ">putty.dsw"; binmode OUT; select OUT;
+print
+"Microsoft Developer Studio Workspace File, Format Version 6.00\r\n".
+"# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r\n".
+"\r\n".
+"###############################################################################\r\n".
+"\r\n";
+# List projects
+foreach $progname (@prognames) {
+  ($windows_project, $type) = split ",", $progname;
+	print "Project: \"$windows_project\"=\".\\$windows_project\\$windows_project.dsp\" - Package Owner=<4>\r\n";
+}
+print
+"\r\n".
+"Package=<5>\r\n".
+"{{{\r\n".
+"}}}\r\n".
+"\r\n".
+"Package=<4>\r\n".
+"{{{\r\n".
+"}}}\r\n".
+"\r\n".
+"###############################################################################\r\n".
+"\r\n".
+"Global:\r\n".
+"\r\n".
+"Package=<5>\r\n".
+"{{{\r\n".
+"}}}\r\n".
+"\r\n".
+"Package=<3>\r\n".
+"{{{\r\n".
+"}}}\r\n".
+"\r\n".
+"###############################################################################\r\n".
+"\r\n";
+select STDOUT; close OUT;
+chdir '..';
+
+sub create_project {
+	my ($all_object_deps, $progname) = @_;
+	# Construct program's dependency info
+	%seen_objects = ();
+	%lib_files = ();
+	%source_files = ();
+	%header_files = ();
+	%resource_files = ();
+	@object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
+	foreach $object_file (@object_files) {
+		next if defined $seen_objects{$object_file};
+		$seen_objects{$object_file} = 1;
+		if($object_file =~ /\.lib$/io) {
+			$lib_files{$object_file} = 1;
+			next;
+		}
+		$object_deps = $all_object_deps{$object_file};
+		foreach $object_dep (@$object_deps) {
+			if($object_dep =~ /\.c$/io) {
+				$source_files{$object_dep} = 1;
+				next;
+			}
+			if($object_dep =~ /\.h$/io) {
+				$header_files{$object_dep} = 1;
+				next;
+			}
+			if($object_dep =~ /\.(rc|ico)$/io) {
+				$resource_files{$object_dep} = 1;
+				next;
+			}
+		}
+	}
+	$libs = join " ", sort keys %lib_files;
+	@source_files = sort keys %source_files;
+	@header_files = sort keys %header_files;
+	@resources = sort keys %resource_files;
+  ($windows_project, $type) = split ",", $progname;
+	mkdir $windows_project
+		if(! -d $windows_project);
+	chdir $windows_project;
+  $subsys = ($type eq "G") ? "windows" : "console";
+	open OUT, ">$windows_project.dsp"; binmode OUT; select OUT;
+	print
+	"# Microsoft Developer Studio Project File - Name=\"$windows_project\" - Package Owner=<4>\r\n".
+	"# Microsoft Developer Studio Generated Build File, Format Version 6.00\r\n".
+	"# ** DO NOT EDIT **\r\n".
+	"\r\n".
+	"# TARGTYPE \"Win32 (x86) Application\" 0x0101\r\n".
+	"\r\n".
+	"CFG=$windows_project - Win32 Debug\r\n".
+	"!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r\n".
+	"!MESSAGE use the Export Makefile command and run\r\n".
+	"!MESSAGE \r\n".
+	"!MESSAGE NMAKE /f \"$windows_project.mak\".\r\n".
+	"!MESSAGE \r\n".
+	"!MESSAGE You can specify a configuration when running NMAKE\r\n".
+	"!MESSAGE by defining the macro CFG on the command line. For example:\r\n".
+	"!MESSAGE \r\n".
+	"!MESSAGE NMAKE /f \"$windows_project.mak\" CFG=\"$windows_project - Win32 Debug\"\r\n".
+	"!MESSAGE \r\n".
+	"!MESSAGE Possible choices for configuration are:\r\n".
+	"!MESSAGE \r\n".
+	"!MESSAGE \"$windows_project - Win32 Release\" (based on \"Win32 (x86) Application\")\r\n".
+	"!MESSAGE \"$windows_project - Win32 Debug\" (based on \"Win32 (x86) Application\")\r\n".
+	"!MESSAGE \r\n".
+	"\r\n".
+	"# Begin Project\r\n".
+	"# PROP AllowPerConfigDependencies 0\r\n".
+	"# PROP Scc_ProjName \"\"\r\n".
+	"# PROP Scc_LocalPath \"\"\r\n".
+	"CPP=cl.exe\r\n".
+	"MTL=midl.exe\r\n".
+	"RSC=rc.exe\r\n".
+	"\r\n".
+	"!IF  \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
+	"\r\n".
+	"# PROP BASE Use_MFC 0\r\n".
+	"# PROP BASE Use_Debug_Libraries 0\r\n".
+	"# PROP BASE Output_Dir \"Release\"\r\n".
+	"# PROP BASE Intermediate_Dir \"Release\"\r\n".
+	"# PROP BASE Target_Dir \"\"\r\n".
+	"# PROP Use_MFC 0\r\n".
+	"# PROP Use_Debug_Libraries 0\r\n".
+	"# PROP Output_Dir \"Release\"\r\n".
+	"# PROP Intermediate_Dir \"Release\"\r\n".
+	"# PROP Ignore_Export_Lib 0\r\n".
+	"# PROP Target_Dir \"\"\r\n".
+	"# ADD BASE CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
+	"# ADD CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
+	"# ADD BASE MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
+	"# ADD MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
+	"# ADD BASE RSC /l 0x809 /d \"NDEBUG\"\r\n".
+	"# ADD RSC /l 0x809 /d \"NDEBUG\"\r\n".
+	"BSC32=bscmake.exe\r\n".
+	"# ADD BASE BSC32 /nologo\r\n".
+	"# ADD BSC32 /nologo\r\n".
+	"LINK32=link.exe\r\n".
+	"# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /machine:I386\r\n".
+	"# ADD LINK32 $libs /nologo /subsystem:$subsys /machine:I386\r\n".
+	"# SUBTRACT LINK32 /pdb:none\r\n".
+	"\r\n".
+	"!ELSEIF  \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
+	"\r\n".
+	"# PROP BASE Use_MFC 0\r\n".
+	"# PROP BASE Use_Debug_Libraries 1\r\n".
+	"# PROP BASE Output_Dir \"Debug\"\r\n".
+	"# PROP BASE Intermediate_Dir \"Debug\"\r\n".
+	"# PROP BASE Target_Dir \"\"\r\n".
+	"# PROP Use_MFC 0\r\n".
+	"# PROP Use_Debug_Libraries 1\r\n".
+	"# PROP Output_Dir \"Debug\"\r\n".
+	"# PROP Intermediate_Dir \"Debug\"\r\n".
+	"# PROP Ignore_Export_Lib 0\r\n".
+	"# PROP Target_Dir \"\"\r\n".
+	"# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
+	"# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
+	"# ADD BASE MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
+	"# ADD MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
+	"# ADD BASE RSC /l 0x809 /d \"_DEBUG\"\r\n".
+	"# ADD RSC /l 0x809 /d \"_DEBUG\"\r\n".
+	"BSC32=bscmake.exe\r\n".
+	"# ADD BASE BSC32 /nologo\r\n".
+	"# ADD BSC32 /nologo\r\n".
+	"LINK32=link.exe\r\n".
+	"# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
+	"# ADD LINK32 $libs /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
+	"# SUBTRACT LINK32 /pdb:none\r\n".
+	"\r\n".
+	"!ENDIF \r\n".
+	"\r\n".
+	"# Begin Target\r\n".
+	"\r\n".
+	"# Name \"$windows_project - Win32 Release\"\r\n".
+	"# Name \"$windows_project - Win32 Debug\"\r\n".
+	"# Begin Group \"Source Files\"\r\n".
+	"\r\n".
+	"# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\r\n";
+	foreach $source_file (@source_files) {
+		print
+		"# Begin Source File\r\n".
+		"\r\n".
+		"SOURCE=..\\..\\$source_file\r\n";
+		if($source_file =~ /ssh\.c/io) {
+			# Disable 'Edit and continue' as Visual Studio can't handle the macros
+			print
+			"\r\n".
+			"!IF  \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
+			"\r\n".
+			"!ELSEIF  \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
+			"\r\n".
+			"# ADD CPP /Zi\r\n".
+			"\r\n".
+			"!ENDIF \r\n".
+			"\r\n";
+		}
+		print "# End Source File\r\n";
+	}
+	print
+	"# End Group\r\n".
+	"# Begin Group \"Header Files\"\r\n".
+	"\r\n".
+	"# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\r\n";
+	foreach $header_file (@header_files) {
+		print
+		"# Begin Source File\r\n".
+		"\r\n".
+		"SOURCE=..\\..\\$header_file\r\n".
+		"# End Source File\r\n";
+		}
+	print
+	"# End Group\r\n".
+	"# Begin Group \"Resource Files\"\r\n".
+	"\r\n".
+	"# PROP Default_Filter \"ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe\"\r\n";
+	foreach $resource_file (@resources) {
+		print
+		"# Begin Source File\r\n".
+		"\r\n".
+		"SOURCE=..\\..\\$resource_file\r\n".
+		"# End Source File\r\n";
+		}
+	print
+	"# End Group\r\n".
+	"# End Target\r\n".
+	"# End Project\r\n";
+	select STDOUT; close OUT;
+	chdir '..';
+	}
+
 ##-- X/GTK/Unix makefile
 open OUT, ">unix/Makefile.gtk"; select OUT;
 print
@@ -581,7 +834,11 @@ print
 "version.o: FORCE;\n".
 "# Hack to force version.o to be rebuilt always\n".
 "FORCE:\n".
-"\t\$(CC) \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) \$(VER) -c ../version.c\n".
+"\tif test -z \"\$(VER)\" && (cd ..; md5sum -c manifest); then \\\n".
+"\t\t\$(CC) \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) `cat ../version.def` -c ../version.c; \\\n".
+"\telse \\\n".
+"\t\t\$(CC) \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) \$(VER) -c ../version.c; \\\n".
+"\tfi\n".
 "clean:\n".
 "\trm -f *.o". (join "", map { " $_" } &progrealnames("XU")) . "\n".
 "\n",
@@ -735,3 +992,62 @@ foreach $arch (qw(PPC Carbon)) {
      }
 }
 select STDOUT; close OUT;
+
+##-- lcc makefile
+open OUT, ">Makefile.lcc"; select OUT;
+print
+"# Makefile for PuTTY under lcc.\n".
+"#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+"# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+# lcc command line option is -D not /D
+($_ = $help) =~ s/=\/D/=-D/gs;
+print $_;
+print
+"\n".
+"# If you rename this file to `Makefile', you should change this line,\n".
+"# so that the .rsp files still depend on the correct makefile.\n".
+"MAKEFILE = Makefile.lcc\n".
+"\n".
+"# C compilation flags\n".
+"CFLAGS = -D_WINDOWS\n".
+"\n".
+"# Get include directory for resource compiler\n".
+"\n".
+".c.obj:\n".
+&splitline("\tlcc -O -p6 \$(COMPAT) \$(FWHACK)".
+  " \$(XFLAGS) \$(CFLAGS)  \$*.c",69)."\n".
+".rc.res:\n".
+&splitline("\tlrc \$(FWHACK) \$(RCFL) -r \$*.rc",69)."\n".
+"\n";
+print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+print "\n\n";
+foreach $p (&prognames("GC")) {
+  ($prog, $type) = split ",", $p;
+  $objstr = &objects($p, "X.obj", "X.res", undef);
+  print &splitline("$prog.exe: " . $objstr ), "\n";
+  $subsystemtype = undef;
+  if ($prog eq "pageant" || $prog eq "putty" ||$prog eq "puttygen" || $prog eq "puttytel") { 
+	$subsystemtype = "-subsystem  windows";	}
+  my $libss = "shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib";
+  print &splitline("\tlcclnk $subsystemtype -o $prog.exe $objstr $libss");
+  print "\n\n";
+}
+
+
+foreach $d (&deps("X.obj", "X.res", "", "\\")) {
+  print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+    "\n";
+}
+print
+"\n".
+"version.o: FORCE\n".
+"# Hack to force version.o to be rebuilt always\n".
+"FORCE:\n".
+"\tlcc \$(FWHACK) \$(VER) \$(CFLAGS) /c version.c\n\n".
+"clean:\n".
+"\t-del *.obj\n".
+"\t-del *.exe\n".
+"\t-del *.res\n";
+
+select STDOUT; close OUT;
+

+ 46 - 0
putty/MKUNXARC.SH

@@ -0,0 +1,46 @@
+#!/bin/sh 
+
+# Build a Unix source distribution from the PuTTY CVS area.
+#
+# Pass an argument of the form `2004-02-08' to have the archive
+# tagged as a development snapshot; of the form `0.54' to have it
+# tagged as a release.
+
+case "$1" in
+  ????-??-??)
+    case "$1" in *[!-0-9]*) echo "Malformed snapshot ID '$1'" >&2;exit 1;;esac
+    arcsuffix="-`cat LATEST.VER`-$1"
+    ver="-DSNAPSHOT=$1"
+    ;;
+  '')
+    arcsuffix=
+    ver=
+    ;;
+  *)
+    case "$1" in *[!.0-9a-z]*) echo "Malformed release ID '$1'">&2;exit 1;;esac
+    arcsuffix="-$1"
+    ver="-DRELEASE=$1"
+    ;;
+esac
+
+perl mkfiles.pl
+
+relver=`cat LATEST.VER`
+arcname="putty$arcsuffix"
+mkdir uxarc
+mkdir uxarc/$arcname
+find . -name uxarc -prune -o -name . -o \
+       -type d -exec mkdir uxarc/$arcname/{} \;
+find . -name uxarc -prune -o \
+       -name CVS -prune -o \
+       -name .cvsignore -prune -o \
+       -name '*.zip' -prune -o \
+       -name '*.tar.gz' -prune -o \
+       -type f -exec ln -s $PWD/{} uxarc/$arcname/{} \;
+if test "x$ver" != "x"; then
+  (cd uxarc/$arcname;
+   md5sum `find . -name '*.[ch]' -print` > manifest;
+   echo "$ver" > version.def)
+fi
+tar -C uxarc -chzf - $arcname > $arcname.tar.gz
+rm -rf uxarc

+ 3 - 1
putty/PAGEANT.C

@@ -13,6 +13,8 @@
 #include "misc.h"
 #include "tree234.h"
 
+#include <shellapi.h>
+
 #ifndef NO_SECURITY
 #include <aclapi.h>
 #endif
@@ -49,7 +51,7 @@ static HMENU systray_menu, session_menu;
 static int already_running;
 static int requested_help;
 
-static char *help_path;
+char *help_path;
 static char *putty_path;
 
 #define IDM_PUTTY         0x0060

+ 8 - 2
putty/PAGEANT.RC

@@ -1,4 +1,8 @@
 /* Some compilers, like Borland, don't have winresrc.h */
+#ifdef __LCC__ 
+#include <win.h>
+#else
+
 #ifndef NO_WINRESRC_H
 #ifndef MSVC4
 #include <winresrc.h>
@@ -7,6 +11,8 @@
 #endif
 #endif
 
+#endif /* end #ifdef __LCC__ */
+
 /* Some systems don't define this, so I do it myself if necessary */
 #ifndef RT_MANIFEST
 #define RT_MANIFEST 24
@@ -50,7 +56,7 @@ BEGIN
     PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14
     CTEXT "Pageant", 102, 10, 6, 120, 8
     CTEXT "", 100, 10, 16, 120, 16
-    CTEXT "\251 1997-2003 Simon Tatham. All rights reserved.",
+    CTEXT "\251 1997-2004 Simon Tatham. All rights reserved.",
           103, 10, 34, 120, 16
 END
 
@@ -62,7 +68,7 @@ FONT 8, "MS Shell Dlg"
 BEGIN
     DEFPUSHBUTTON "OK", IDOK, 98, 235, 44, 14
 
-    LTEXT "Copyright \251 1997-2003 Simon Tatham", 1000, 10, 10, 206, 8
+    LTEXT "Copyright \251 1997-2004 Simon Tatham", 1000, 10, 10, 206, 8
 
     LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8
     LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8

+ 11 - 0
putty/PAGEANTC.C

@@ -26,6 +26,13 @@ int agent_exists(void)
 	return TRUE;
 }
 
+/*
+ * Unfortunately, this asynchronous agent request mechanism doesn't
+ * appear to work terribly well. I'm going to comment it out for
+ * the moment, and see if I can come up with a better one :-/
+ */
+#ifdef WINDOWS_ASYNC_AGENT
+
 struct agent_query_data {
     COPYDATASTRUCT cds;
     unsigned char *mapping;
@@ -63,6 +70,8 @@ DWORD WINAPI agent_query_thread(LPVOID param)
     return 0;
 }
 
+#endif
+
 int agent_query(void *in, int inlen, void **out, int *outlen,
 		void (*callback)(void *, void *, int), void *callback_ctx)
 {
@@ -89,6 +98,7 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
     cds.dwData = AGENT_COPYDATA_ID;
     cds.cbData = 1 + strlen(mapname);
     cds.lpData = mapname;
+#ifdef WINDOWS_ASYNC_AGENT
     if (callback != NULL && !(flags & FLAG_SYNCAGENT)) {
 	/*
 	 * We need an asynchronous Pageant request. Since I know of
@@ -111,6 +121,7 @@ int agent_query(void *in, int inlen, void **out, int *outlen,
 	    return 0;
 	sfree(data);
     }
+#endif
 
     /*
      * The user either passed a null callback (indicating that the

+ 6 - 5
putty/PLINK.C

@@ -220,11 +220,12 @@ static void usage(void)
     printf("  -batch    disable all interactive prompts\n");
     printf("The following options only apply to SSH connections:\n");
     printf("  -pw passw login with specified password\n");
-    printf("  -D listen-port   Dynamic SOCKS-based port forwarding\n");
-    printf("  -L listen-port:host:port   Forward local port to "
-	   "remote address\n");
-    printf("  -R listen-port:host:port   Forward remote port to"
-	   " local address\n");
+    printf("  -D [listen-IP:]listen-port\n");
+    printf("            Dynamic SOCKS-based port forwarding\n");
+    printf("  -L [listen-IP:]listen-port:host:port\n");
+    printf("            Forward local port to remote address\n");
+    printf("  -R [listen-IP:]listen-port:host:port\n");
+    printf("            Forward remote port to local address\n");
     printf("  -X -x     enable / disable X11 forwarding\n");
     printf("  -A -a     enable / disable agent forwarding\n");
     printf("  -t -T     enable / disable pty allocation\n");

+ 6 - 7
putty/PORTFWD.C

@@ -255,7 +255,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len)
 		    if (pr->hostname[1] != 1 || pr->hostname[2] != 0) {
 			/* Not CONNECT or reserved field nonzero - error */
 			reply[1] = 1;	/* generic failure */
-			sk_write(pr->s, reply, lenof(reply));
+			sk_write(pr->s, (char *) reply, lenof(reply));
 			pfd_close(pr->s);
 			return 1;
 		    }
@@ -266,7 +266,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len)
 		    pr->port = GET_16BIT_MSB_FIRST(pr->hostname+4+alen);
 		    if (atype == 1) {
 			/* REP=0 (success) already */
-			sk_write(pr->s, reply, lenof(reply));
+			sk_write(pr->s, (char *) reply, lenof(reply));
 			sprintf(pr->hostname, "%d.%d.%d.%d",
 				(unsigned char)pr->hostname[4],
 				(unsigned char)pr->hostname[5],
@@ -275,7 +275,7 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len)
 			goto connect;
 		    } else if (atype == 3) {
 			/* REP=0 (success) already */
-			sk_write(pr->s, reply, lenof(reply));
+			sk_write(pr->s, (char *) reply, lenof(reply));
 			memmove(pr->hostname, pr->hostname + 5, alen-1);
 			pr->hostname[alen-1] = '\0';
 			goto connect;
@@ -284,13 +284,13 @@ static int pfd_receive(Plug plug, int urgent, char *data, int len)
 			 * Unknown address type. (FIXME: support IPv6!)
 			 */
 			reply[1] = 8;	/* atype not supported */
-			sk_write(pr->s, reply, lenof(reply));
+			sk_write(pr->s, (char *) reply, lenof(reply));
 			pfd_close(pr->s);
-			return 1;			
+			return 1;
 		    }
 		}
 	    }
-	    
+
 	    /*
 	     * If we get here without either having done `continue'
 	     * or `goto connect', it must be because there is no
@@ -569,4 +569,3 @@ void pfd_confirm(Socket s)
 	pr->buffer = NULL;
     }
 }
-

+ 1 - 0
putty/PRINTING.C

@@ -3,6 +3,7 @@
  */
 
 #include "putty.h"
+#include <winspool.h>
 
 struct printer_enum_tag {
     int nprinters;

+ 12 - 2
putty/PROXY.C

@@ -161,10 +161,14 @@ static void sk_proxy_set_frozen (Socket s, int is_frozen)
 	 */
         while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
 	    void *data;
+	    char databuf[512];
 	    int len;
 	    bufchain_prefix(&ps->pending_input_data, &data, &len);
-	    plug_receive(ps->plug, 0, data, len);
+	    if (len > lenof(databuf))
+		len = lenof(databuf);
+	    memcpy(databuf, data, len);
 	    bufchain_consume(&ps->pending_input_data, len);
+	    plug_receive(ps->plug, 0, databuf, len);
 	}
 
 	/* if we're still frozen, we'll have to wait for another
@@ -586,8 +590,14 @@ int proxy_http_negotiate (Proxy_Socket p, int change)
 	    /* get the status line */
 	    len = bufchain_size(&p->pending_input_data);
 	    assert(len > 0);	       /* or we wouldn't be here */
-	    data = snewn(len, char);
+	    data = snewn(len+1, char);
 	    bufchain_fetch(&p->pending_input_data, data, len);
+	    /*
+	     * We must NUL-terminate this data, because Windows
+	     * sscanf appears to require a NUL at the end of the
+	     * string because it strlens it _first_. Sigh.
+	     */
+	    data[len] = '\0';
 
 	    eol = get_line_end(data, len);
 	    if (eol < 0) {

+ 20 - 7
putty/PSFTP.C

@@ -1014,6 +1014,8 @@ int sftp_cmd_chmod(struct sftp_command *cmd)
 
 static int sftp_cmd_open(struct sftp_command *cmd)
 {
+    int portnumber;
+
     if (back != NULL) {
 	printf("psftp: already connected\n");
 	return 0;
@@ -1024,7 +1026,16 @@ static int sftp_cmd_open(struct sftp_command *cmd)
 	return 0;
     }
 
-    if (psftp_connect(cmd->words[1], NULL, 0)) {
+    if (cmd->nwords > 2) {
+	portnumber = atoi(cmd->words[2]);
+	if (portnumber == 0) {
+	    printf("open: invalid port number\n");
+	    return 0;
+	}
+    } else
+	portnumber = 0;
+
+    if (psftp_connect(cmd->words[1], NULL, portnumber)) {
 	back = NULL;		       /* connection is already closed */
 	return -1;		       /* this is fatal */
     }
@@ -1216,7 +1227,7 @@ static struct sftp_cmd_lookup {
     },
     {
 	"open", TRUE, "connect to a host",
-	    " [<user>@]<hostname>\n"
+	    " [<user>@]<hostname> [<port>]\n"
 	    "  Establishes an SFTP connection to a given host. Only usable\n"
 	    "  when you did not already specify a host name on the command\n"
 	    "  line.\n",
@@ -1509,10 +1520,12 @@ static int do_sftp_init(void)
 void do_sftp_cleanup()
 {
     char ch;
-    back->special(backhandle, TS_EOF);
-    sftp_recvdata(&ch, 1);
-    back->free(backhandle);
-    sftp_cleanup_request();
+    if (back) {
+	back->special(backhandle, TS_EOF);
+	sftp_recvdata(&ch, 1);
+	back->free(backhandle);
+	sftp_cleanup_request();
+    }
     if (pwd) {
 	sfree(pwd);
 	pwd = NULL;
@@ -1741,7 +1754,7 @@ int sftp_recvdata(char *buf, int len)
 }
 int sftp_senddata(char *buf, int len)
 {
-    back->send(backhandle, (unsigned char *) buf, len);
+    back->send(backhandle, buf, len);
     return 1;
 }
 

+ 1 - 1
putty/PUTTY.ISS

@@ -19,7 +19,7 @@
 
 [Setup]
 AppName=PuTTY
-AppVerName=PuTTY version 0.53
+AppVerName=PuTTY version 0.54
 DefaultDirName={pf}\PuTTY
 DefaultGroupName=PuTTY
 UninstallDisplayIcon={app}\putty.exe

+ 8 - 2
putty/PUTTYGEN.RC

@@ -1,4 +1,8 @@
 /* Some compilers, like Borland, don't have winresrc.h */
+#ifdef __LCC__ 
+#include <win.h>
+#else
+
 #ifndef NO_WINRESRC_H
 #ifndef MSVC4
 #include <winresrc.h>
@@ -7,6 +11,8 @@
 #endif
 #endif
 
+#endif /* end #ifdef __LCC__ */
+
 /* Some systems don't define this, so I do it myself if necessary */
 #ifndef RT_MANIFEST
 #define RT_MANIFEST 24
@@ -43,7 +49,7 @@ BEGIN
     PUSHBUTTON "View &Licence", 101, 6, 52, 70, 14
     CTEXT "PuTTYgen", 102, 10, 6, 120, 8
     CTEXT "", 100, 10, 16, 120, 16
-    CTEXT "\251 1997-2003 Simon Tatham. All rights reserved.",
+    CTEXT "\251 1997-2004 Simon Tatham. All rights reserved.",
           103, 10, 34, 120, 16
 END
 
@@ -55,7 +61,7 @@ FONT 8, "MS Shell Dlg"
 BEGIN
     DEFPUSHBUTTON "OK", IDOK, 98, 235, 44, 14
 
-    LTEXT "Copyright \251 1997-2003 Simon Tatham", 1000, 10, 10, 206, 8
+    LTEXT "Copyright \251 1997-2004 Simon Tatham", 1000, 10, 10, 206, 8
 
     LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8
     LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8

+ 27 - 18
putty/README

@@ -1,17 +1,28 @@
 This is the README for the source archive of PuTTY, a free Win32
 Telnet and SSH client.
 
-If you want to rebuild PuTTY from source, we provide three
-Makefiles:
+If you want to rebuild PuTTY from source, we provide a variety of
+Makefiles and equivalents.
 
- - Makefile.vc is for MS Visual C++ systems. Type `nmake -f
-   Makefile.vc' to build all the PuTTY binaries.
+For building on Windows:
+
+ - Makefile.vc is for command-line builds on MS Visual C++ systems.
+   Type `nmake -f Makefile.vc' to build all the PuTTY binaries.
 
    (We've also had one report of success building with the
    OpenWatcom compiler -- www.openwatcom.org -- using Makefile.vc
    with `wmake -ms -f makefile.vc' and NO_MULTIMON, although we
    haven't tried this ourselves.)
 
+ - Inside the MSVC subdirectory are MS Visual Studio project files
+   for doing GUI-based builds of the various PuTTY utilities. These
+   have been tested on Visual Studio 6.
+
+   You should be able to build each PuTTY utility by loading the
+   corresponding .dsp file in Visual Studio. For example,
+   MSVC/putty/putty.dsp builds PuTTY itself, MSVC/plink/plink.dsp
+   builds Plink, and so on.
+
  - Makefile.bor is for the Borland C compiler. Type `make -f
    Makefile.bor' to build all the PuTTY binaries.
 
@@ -22,20 +33,18 @@ Makefiles:
    time of writing this Cygwin doesn't include the necessary
    headers.
 
-If you have MS Visual Studio version 6 and you want to build a
-DevStudio project for GUI editing and debugging, you should be aware
-that the default GUI configuration of the compiler falls over on the
-nasty macros in ssh.c. This is a bug in Visual Studio. The culprit
-is the /ZI compiler option (debug info generation: Edit and
-Continue). To avoid this problem while compiling PuTTY under VS6,
-you should:
- - right-click ssh.c in the FileView
- - click Settings
- - select the C/C++ tab and the General category
- - under `Debug info:', select anything _other_ than `Program
-   Database for Edit and Continue'.
-Alternatively disable the /ZI option, replacing it with a saner
-value, such as /Zi.
+ - Makefile.lcc is for lcc-win32. Type `make -f Makefile.lcc'. (You
+   will probably need to specify COMPAT=-DNO_MULTIMON.)
+
+For building on Unix:
+
+ - unix/Makefile.gtk is for Unix and GTK. If you don't have GTK, you
+   should still be able to build the command-line utilities (PSCP,
+   PSFTP, Plink, PuTTYgen) using this makefile. The makefile expects
+   you to change into the `unix' subdirectory, then run `make -f
+   Makefile.gtk'. Note that Unix PuTTY has mostly only been tested
+   on Linux so far; portability problems such as BSD-style ptys or
+   different header file requirements are expected.
 
 All of the Makefiles are generated automatically from the file
 `Recipe' by the Perl script `mkfiles.pl'. Additions and corrections

+ 2 - 2
putty/README.TXT

@@ -13,8 +13,8 @@ to transfer files, you should just be able to run them from the
 Start menu.

 

 If you want to use the command-line-only file transfer utility PSCP,

-you will probably want to put the PuTTY installation directory to be

-on your PATH. How you do this depends on your version of Windows. On

+you will probably want to put the PuTTY installation directory on

+your PATH. How you do this depends on your version of Windows. On

 Windows NT and 2000, you can set it using Control Panel > System; on

 Windows 95 you will need to edit AUTOEXEC.BAT. Consult your Windows

 manuals for details.


+ 4 - 0
putty/RECIPE

@@ -158,6 +158,10 @@ puttytel : [X] UXTERM uxmisc misc ldisc settings pty uxsel be_nossh uxstore
 
 plink    : [U] uxplink uxcons NONSSH UXSSH be_all logging UXMISC signal ux_x11
 
+puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
+         + sshrand uxnoise sshsha misc sshrsa sshdss uxcons uxstore uxmisc
+         + sshpubk sshaes sshsh512 import puttygen.res tree234 uxgen
+
 pscp     : [U] scp uxsftp uxcons UXSSH be_none SFTP wildcard UXMISC
 psftp    : [U] psftp uxsftp uxcons UXSSH be_none SFTP UXMISC
 

+ 11 - 11
putty/SCP.C

@@ -290,7 +290,7 @@ static void bump(char *fmt, ...)
     if (back != NULL && back->socket(backhandle) != NULL) {
 	char ch;
 	back->special(backhandle, TS_EOF);
-	ssh_scp_recv(&ch, 1);
+	ssh_scp_recv((unsigned char *) &ch, 1);
     }
 
     if (gui_mode)
@@ -455,7 +455,7 @@ static void print_stats(char *name, unsigned long size, unsigned long done,
     pct = (int) (100 * (done * 1.0 / size));
 
     if (gui_mode) {
-	gui_update_stats(name, size, pct, elap, done, eta, 
+	gui_update_stats(name, size, pct, elap, done, eta,
 			 (unsigned long) ratebs);
     } else {
 	len = printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%",
@@ -531,7 +531,7 @@ static int response(void)
     char ch, resp, rbuf[2048];
     int p;
 
-    if (ssh_scp_recv(&resp, 1) <= 0)
+    if (ssh_scp_recv((unsigned char *) &resp, 1) <= 0)
 	bump("Lost connection");
 
     p = 0;
@@ -544,7 +544,7 @@ static int response(void)
       case 1:			       /* error */
       case 2:			       /* fatal error */
 	do {
-	    if (ssh_scp_recv(&ch, 1) <= 0)
+	    if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
 		bump("Protocol error: Lost connection");
 	    rbuf[p++] = ch;
 	} while (p < sizeof(rbuf) && ch != '\n');
@@ -560,11 +560,11 @@ static int response(void)
 
 int sftp_recvdata(char *buf, int len)
 {
-    return ssh_scp_recv(buf, len);
+    return ssh_scp_recv((unsigned char *) buf, len);
 }
 int sftp_senddata(char *buf, int len)
 {
-    back->send(backhandle, (unsigned char *) buf, len);
+    back->send(backhandle, buf, len);
     return 1;
 }
 
@@ -1320,14 +1320,14 @@ int scp_get_sink_action(struct scp_sink_action *act)
 	bufsize = 0;
 
 	while (!done) {
-	    if (ssh_scp_recv(&ch, 1) <= 0)
+	    if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
 		return 1;
 	    if (ch == '\n')
 		bump("Protocol error: Unexpected newline");
 	    i = 0;
 	    action = ch;
 	    do {
-		if (ssh_scp_recv(&ch, 1) <= 0)
+		if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
 		    bump("Lost connection");
 		if (i >= bufsize) {
 		    bufsize = i + 128;
@@ -1442,7 +1442,7 @@ int scp_recv_filedata(char *data, int len)
 
 	return actuallen;
     } else {
-	return ssh_scp_recv(data, len);
+	return ssh_scp_recv((unsigned char *) data, len);
     }
 }
 
@@ -2034,7 +2034,7 @@ static void get_dir_list(int argc, char *argv[])
     if (using_sftp) {
 	scp_sftp_listdir(src);
     } else {
-	while (ssh_scp_recv(&c, 1) > 0)
+	while (ssh_scp_recv((unsigned char *) &c, 1) > 0)
 	    tell_char(stdout, c);
     }
 }
@@ -2171,7 +2171,7 @@ int psftp_main(int argc, char *argv[])
     if (back != NULL && back->socket(backhandle) != NULL) {
 	char ch;
 	back->special(backhandle, TS_EOF);
-	ssh_scp_recv(&ch, 1);
+	ssh_scp_recv((unsigned char *) &ch, 1);
     }
     random_save_seed();
 

+ 2 - 0
putty/SETTINGS.C

@@ -353,6 +353,8 @@ void save_open_settings(void *sesskey, int do_host, Config *cfg)
     write_setting_i(sesskey, "LoginShell", cfg->login_shell);
     write_setting_i(sesskey, "ScrollbarOnLeft", cfg->scrollbar_on_left);
     write_setting_fontspec(sesskey, "BoldFont", cfg->boldfont);
+    write_setting_fontspec(sesskey, "WideFont", cfg->widefont);
+    write_setting_fontspec(sesskey, "WideBoldFont", cfg->wideboldfont);
     write_setting_i(sesskey, "ShadowBold", cfg->shadowbold);
     write_setting_i(sesskey, "ShadowBoldOffset", cfg->shadowboldoffset);
 }

+ 6665 - 6654
putty/SSH.C

@@ -1,6654 +1,6665 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-
-#include "putty.h"
-#include "tree234.h"
-#include "ssh.h"
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-#define SSH1_MSG_DISCONNECT                       1	/* 0x1 */
-#define SSH1_SMSG_PUBLIC_KEY                      2	/* 0x2 */
-#define SSH1_CMSG_SESSION_KEY                     3	/* 0x3 */
-#define SSH1_CMSG_USER                            4	/* 0x4 */
-#define SSH1_CMSG_AUTH_RSA                        6	/* 0x6 */
-#define SSH1_SMSG_AUTH_RSA_CHALLENGE              7	/* 0x7 */
-#define SSH1_CMSG_AUTH_RSA_RESPONSE               8	/* 0x8 */
-#define SSH1_CMSG_AUTH_PASSWORD                   9	/* 0x9 */
-#define SSH1_CMSG_REQUEST_PTY                     10	/* 0xa */
-#define SSH1_CMSG_WINDOW_SIZE                     11	/* 0xb */
-#define SSH1_CMSG_EXEC_SHELL                      12	/* 0xc */
-#define SSH1_CMSG_EXEC_CMD                        13	/* 0xd */
-#define SSH1_SMSG_SUCCESS                         14	/* 0xe */
-#define SSH1_SMSG_FAILURE                         15	/* 0xf */
-#define SSH1_CMSG_STDIN_DATA                      16	/* 0x10 */
-#define SSH1_SMSG_STDOUT_DATA                     17	/* 0x11 */
-#define SSH1_SMSG_STDERR_DATA                     18	/* 0x12 */
-#define SSH1_CMSG_EOF                             19	/* 0x13 */
-#define SSH1_SMSG_EXIT_STATUS                     20	/* 0x14 */
-#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION        21	/* 0x15 */
-#define SSH1_MSG_CHANNEL_OPEN_FAILURE             22	/* 0x16 */
-#define SSH1_MSG_CHANNEL_DATA                     23	/* 0x17 */
-#define SSH1_MSG_CHANNEL_CLOSE                    24	/* 0x18 */
-#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25	/* 0x19 */
-#define SSH1_SMSG_X11_OPEN                        27	/* 0x1b */
-#define SSH1_CMSG_PORT_FORWARD_REQUEST            28	/* 0x1c */
-#define SSH1_MSG_PORT_OPEN                        29	/* 0x1d */
-#define SSH1_CMSG_AGENT_REQUEST_FORWARDING        30	/* 0x1e */
-#define SSH1_SMSG_AGENT_OPEN                      31	/* 0x1f */
-#define SSH1_MSG_IGNORE                           32	/* 0x20 */
-#define SSH1_CMSG_EXIT_CONFIRMATION               33	/* 0x21 */
-#define SSH1_CMSG_X11_REQUEST_FORWARDING          34	/* 0x22 */
-#define SSH1_CMSG_AUTH_RHOSTS_RSA                 35	/* 0x23 */
-#define SSH1_MSG_DEBUG                            36	/* 0x24 */
-#define SSH1_CMSG_REQUEST_COMPRESSION             37	/* 0x25 */
-#define SSH1_CMSG_AUTH_TIS                        39	/* 0x27 */
-#define SSH1_SMSG_AUTH_TIS_CHALLENGE              40	/* 0x28 */
-#define SSH1_CMSG_AUTH_TIS_RESPONSE               41	/* 0x29 */
-#define SSH1_CMSG_AUTH_CCARD                      70	/* 0x46 */
-#define SSH1_SMSG_AUTH_CCARD_CHALLENGE            71	/* 0x47 */
-#define SSH1_CMSG_AUTH_CCARD_RESPONSE             72	/* 0x48 */
-
-#define SSH1_AUTH_TIS                             5	/* 0x5 */
-#define SSH1_AUTH_CCARD                           16	/* 0x10 */
-
-#define SSH1_PROTOFLAG_SCREEN_NUMBER              1	/* 0x1 */
-/* Mask for protoflags we will echo back to server if seen */
-#define SSH1_PROTOFLAGS_SUPPORTED                 0	/* 0x1 */
-
-#define SSH2_MSG_DISCONNECT                       1	/* 0x1 */
-#define SSH2_MSG_IGNORE                           2	/* 0x2 */
-#define SSH2_MSG_UNIMPLEMENTED                    3	/* 0x3 */
-#define SSH2_MSG_DEBUG                            4	/* 0x4 */
-#define SSH2_MSG_SERVICE_REQUEST                  5	/* 0x5 */
-#define SSH2_MSG_SERVICE_ACCEPT                   6	/* 0x6 */
-#define SSH2_MSG_KEXINIT                          20	/* 0x14 */
-#define SSH2_MSG_NEWKEYS                          21	/* 0x15 */
-#define SSH2_MSG_KEXDH_INIT                       30	/* 0x1e */
-#define SSH2_MSG_KEXDH_REPLY                      31	/* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_REQUEST               30	/* 0x1e */
-#define SSH2_MSG_KEX_DH_GEX_GROUP                 31	/* 0x1f */
-#define SSH2_MSG_KEX_DH_GEX_INIT                  32	/* 0x20 */
-#define SSH2_MSG_KEX_DH_GEX_REPLY                 33	/* 0x21 */
-#define SSH2_MSG_USERAUTH_REQUEST                 50	/* 0x32 */
-#define SSH2_MSG_USERAUTH_FAILURE                 51	/* 0x33 */
-#define SSH2_MSG_USERAUTH_SUCCESS                 52	/* 0x34 */
-#define SSH2_MSG_USERAUTH_BANNER                  53	/* 0x35 */
-#define SSH2_MSG_USERAUTH_PK_OK                   60	/* 0x3c */
-#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60	/* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_REQUEST            60	/* 0x3c */
-#define SSH2_MSG_USERAUTH_INFO_RESPONSE           61	/* 0x3d */
-#define SSH2_MSG_GLOBAL_REQUEST                   80	/* 0x50 */
-#define SSH2_MSG_REQUEST_SUCCESS                  81	/* 0x51 */
-#define SSH2_MSG_REQUEST_FAILURE                  82	/* 0x52 */
-#define SSH2_MSG_CHANNEL_OPEN                     90	/* 0x5a */
-#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91	/* 0x5b */
-#define SSH2_MSG_CHANNEL_OPEN_FAILURE             92	/* 0x5c */
-#define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93	/* 0x5d */
-#define SSH2_MSG_CHANNEL_DATA                     94	/* 0x5e */
-#define SSH2_MSG_CHANNEL_EXTENDED_DATA            95	/* 0x5f */
-#define SSH2_MSG_CHANNEL_EOF                      96	/* 0x60 */
-#define SSH2_MSG_CHANNEL_CLOSE                    97	/* 0x61 */
-#define SSH2_MSG_CHANNEL_REQUEST                  98	/* 0x62 */
-#define SSH2_MSG_CHANNEL_SUCCESS                  99	/* 0x63 */
-#define SSH2_MSG_CHANNEL_FAILURE                  100	/* 0x64 */
-
-/*
- * Packet type contexts, so that ssh2_pkt_type can correctly decode
- * the ambiguous type numbers back into the correct type strings.
- */
-#define SSH2_PKTCTX_DHGROUP1         0x0001
-#define SSH2_PKTCTX_DHGEX            0x0002
-#define SSH2_PKTCTX_PUBLICKEY        0x0010
-#define SSH2_PKTCTX_PASSWORD         0x0020
-#define SSH2_PKTCTX_KBDINTER         0x0040
-#define SSH2_PKTCTX_AUTH_MASK        0x00F0
-
-#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1	/* 0x1 */
-#define SSH2_DISCONNECT_PROTOCOL_ERROR            2	/* 0x2 */
-#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED       3	/* 0x3 */
-#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4	/* 0x4 */
-#define SSH2_DISCONNECT_MAC_ERROR                 5	/* 0x5 */
-#define SSH2_DISCONNECT_COMPRESSION_ERROR         6	/* 0x6 */
-#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE     7	/* 0x7 */
-#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8	/* 0x8 */
-#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE   9	/* 0x9 */
-#define SSH2_DISCONNECT_CONNECTION_LOST           10	/* 0xa */
-#define SSH2_DISCONNECT_BY_APPLICATION            11	/* 0xb */
-#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS      12	/* 0xc */
-#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER    13	/* 0xd */
-#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14	/* 0xe */
-#define SSH2_DISCONNECT_ILLEGAL_USER_NAME         15	/* 0xf */
-
-static const char *const ssh2_disconnect_reasons[] = {
-    NULL,
-    "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT",
-    "SSH_DISCONNECT_PROTOCOL_ERROR",
-    "SSH_DISCONNECT_KEY_EXCHANGE_FAILED",
-    "SSH_DISCONNECT_HOST_AUTHENTICATION_FAILED",
-    "SSH_DISCONNECT_MAC_ERROR",
-    "SSH_DISCONNECT_COMPRESSION_ERROR",
-    "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE",
-    "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED",
-    "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE",
-    "SSH_DISCONNECT_CONNECTION_LOST",
-    "SSH_DISCONNECT_BY_APPLICATION",
-    "SSH_DISCONNECT_TOO_MANY_CONNECTIONS",
-    "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER",
-    "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE",
-    "SSH_DISCONNECT_ILLEGAL_USER_NAME",
-};
-
-#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED     1	/* 0x1 */
-#define SSH2_OPEN_CONNECT_FAILED                  2	/* 0x2 */
-#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE            3	/* 0x3 */
-#define SSH2_OPEN_RESOURCE_SHORTAGE               4	/* 0x4 */
-
-#define SSH2_EXTENDED_DATA_STDERR                 1	/* 0x1 */
-
-/*
- * Various remote-bug flags.
- */
-#define BUG_CHOKES_ON_SSH1_IGNORE                 1
-#define BUG_SSH2_HMAC                             2
-#define BUG_NEEDS_SSH1_PLAIN_PASSWORD        	  4
-#define BUG_CHOKES_ON_RSA	        	  8
-#define BUG_SSH2_RSA_PADDING	        	 16
-#define BUG_SSH2_DERIVEKEY                       32
-#define BUG_SSH2_DH_GEX                          64
-#define BUG_SSH2_PK_SESSIONID                   128
-
-#define translate(x) if (type == x) return #x
-#define translatec(x,ctx) if (type == x && (pkt_ctx & ctx)) return #x
-static char *ssh1_pkt_type(int type)
-{
-    translate(SSH1_MSG_DISCONNECT);
-    translate(SSH1_SMSG_PUBLIC_KEY);
-    translate(SSH1_CMSG_SESSION_KEY);
-    translate(SSH1_CMSG_USER);
-    translate(SSH1_CMSG_AUTH_RSA);
-    translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
-    translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
-    translate(SSH1_CMSG_AUTH_PASSWORD);
-    translate(SSH1_CMSG_REQUEST_PTY);
-    translate(SSH1_CMSG_WINDOW_SIZE);
-    translate(SSH1_CMSG_EXEC_SHELL);
-    translate(SSH1_CMSG_EXEC_CMD);
-    translate(SSH1_SMSG_SUCCESS);
-    translate(SSH1_SMSG_FAILURE);
-    translate(SSH1_CMSG_STDIN_DATA);
-    translate(SSH1_SMSG_STDOUT_DATA);
-    translate(SSH1_SMSG_STDERR_DATA);
-    translate(SSH1_CMSG_EOF);
-    translate(SSH1_SMSG_EXIT_STATUS);
-    translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
-    translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
-    translate(SSH1_MSG_CHANNEL_DATA);
-    translate(SSH1_MSG_CHANNEL_CLOSE);
-    translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
-    translate(SSH1_SMSG_X11_OPEN);
-    translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
-    translate(SSH1_MSG_PORT_OPEN);
-    translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
-    translate(SSH1_SMSG_AGENT_OPEN);
-    translate(SSH1_MSG_IGNORE);
-    translate(SSH1_CMSG_EXIT_CONFIRMATION);
-    translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
-    translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
-    translate(SSH1_MSG_DEBUG);
-    translate(SSH1_CMSG_REQUEST_COMPRESSION);
-    translate(SSH1_CMSG_AUTH_TIS);
-    translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
-    translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
-    translate(SSH1_CMSG_AUTH_CCARD);
-    translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
-    translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
-    return "unknown";
-}
-static char *ssh2_pkt_type(int pkt_ctx, int type)
-{
-    translate(SSH2_MSG_DISCONNECT);
-    translate(SSH2_MSG_IGNORE);
-    translate(SSH2_MSG_UNIMPLEMENTED);
-    translate(SSH2_MSG_DEBUG);
-    translate(SSH2_MSG_SERVICE_REQUEST);
-    translate(SSH2_MSG_SERVICE_ACCEPT);
-    translate(SSH2_MSG_KEXINIT);
-    translate(SSH2_MSG_NEWKEYS);
-    translatec(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP1);
-    translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP1);
-    translatec(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
-    translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
-    translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
-    translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
-    translate(SSH2_MSG_USERAUTH_REQUEST);
-    translate(SSH2_MSG_USERAUTH_FAILURE);
-    translate(SSH2_MSG_USERAUTH_SUCCESS);
-    translate(SSH2_MSG_USERAUTH_BANNER);
-    translatec(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
-    translatec(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
-    translatec(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
-    translatec(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
-    translate(SSH2_MSG_GLOBAL_REQUEST);
-    translate(SSH2_MSG_REQUEST_SUCCESS);
-    translate(SSH2_MSG_REQUEST_FAILURE);
-    translate(SSH2_MSG_CHANNEL_OPEN);
-    translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
-    translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
-    translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-    translate(SSH2_MSG_CHANNEL_DATA);
-    translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
-    translate(SSH2_MSG_CHANNEL_EOF);
-    translate(SSH2_MSG_CHANNEL_CLOSE);
-    translate(SSH2_MSG_CHANNEL_REQUEST);
-    translate(SSH2_MSG_CHANNEL_SUCCESS);
-    translate(SSH2_MSG_CHANNEL_FAILURE);
-    return "unknown";
-}
-#undef translate
-#undef translatec
-
-#define GET_32BIT(cp) \
-    (((unsigned long)(unsigned char)(cp)[0] << 24) | \
-    ((unsigned long)(unsigned char)(cp)[1] << 16) | \
-    ((unsigned long)(unsigned char)(cp)[2] << 8) | \
-    ((unsigned long)(unsigned char)(cp)[3]))
-
-#define PUT_32BIT(cp, value) { \
-    (cp)[0] = (unsigned char)((value) >> 24); \
-    (cp)[1] = (unsigned char)((value) >> 16); \
-    (cp)[2] = (unsigned char)((value) >> 8); \
-    (cp)[3] = (unsigned char)(value); }
-
-enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM };
-
-/*
- * Coroutine mechanics for the sillier bits of the code. If these
- * macros look impenetrable to you, you might find it helpful to
- * read
- * 
- *   http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
- * 
- * which explains the theory behind these macros.
- * 
- * In particular, if you are getting `case expression not constant'
- * errors when building with MS Visual Studio, this is because MS's
- * Edit and Continue debugging feature causes their compiler to
- * violate ANSI C. To disable Edit and Continue debugging:
- * 
- *  - right-click ssh.c in the FileView
- *  - click Settings
- *  - select the C/C++ tab and the General category
- *  - under `Debug info:', select anything _other_ than `Program
- *    Database for Edit and Continue'.
- */
-#define crBegin(v)	{ int *crLine = &v; switch(v) { case 0:;
-#define crState(t) \
-    struct t *s; \
-    if (!ssh->t) ssh->t = snew(struct t); \
-    s = ssh->t;
-#define crFinish(z)	} *crLine = 0; return (z); }
-#define crFinishV	} *crLine = 0; return; }
-#define crReturn(z)	\
-	do {\
-	    *crLine =__LINE__; return (z); case __LINE__:;\
-	} while (0)
-#define crReturnV	\
-	do {\
-	    *crLine=__LINE__; return; case __LINE__:;\
-	} while (0)
-#define crStop(z)	do{ *crLine = 0; return (z); }while(0)
-#define crStopV		do{ *crLine = 0; return; }while(0)
-#define crWaitUntil(c)	do { crReturn(0); } while (!(c))
-#define crWaitUntilV(c)	do { crReturnV; } while (!(c))
-
-typedef struct ssh_tag *Ssh;
-
-static void ssh2_pkt_init(Ssh, int pkt_type);
-static void ssh2_pkt_addbool(Ssh, unsigned char value);
-static void ssh2_pkt_adduint32(Ssh, unsigned long value);
-static void ssh2_pkt_addstring_start(Ssh);
-static void ssh2_pkt_addstring_str(Ssh, char *data);
-static void ssh2_pkt_addstring_data(Ssh, char *data, int len);
-static void ssh2_pkt_addstring(Ssh, char *data);
-static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
-static void ssh2_pkt_addmp(Ssh, Bignum b);
-static int ssh2_pkt_construct(Ssh);
-static void ssh2_pkt_send(Ssh);
-static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, int ispkt);
-static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt);
-
-/*
- * Buffer management constants. There are several of these for
- * various different purposes:
- * 
- *  - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
- *    on a local data stream before we throttle the whole SSH
- *    connection (in SSH1 only). Throttling the whole connection is
- *    pretty drastic so we set this high in the hope it won't
- *    happen very often.
- * 
- *  - SSH_MAX_BACKLOG is the amount of backlog that must build up
- *    on the SSH connection itself before we defensively throttle
- *    _all_ local data streams. This is pretty drastic too (though
- *    thankfully unlikely in SSH2 since the window mechanism should
- *    ensure that the server never has any need to throttle its end
- *    of the connection), so we set this high as well.
- * 
- *  - OUR_V2_WINSIZE is the maximum window size we present on SSH2
- *    channels.
- */
-
-#define SSH1_BUFFER_LIMIT 32768
-#define SSH_MAX_BACKLOG 32768
-#define OUR_V2_WINSIZE 16384
-
-const static struct ssh_kex *kex_algs[] = {
-    &ssh_diffiehellman_gex,
-    &ssh_diffiehellman
-};
-
-const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
-
-static void *nullmac_make_context(void)
-{
-    return NULL;
-}
-static void nullmac_free_context(void *handle)
-{
-}
-static void nullmac_key(void *handle, unsigned char *key)
-{
-}
-static void nullmac_generate(void *handle, unsigned char *blk, int len,
-			     unsigned long seq)
-{
-}
-static int nullmac_verify(void *handle, unsigned char *blk, int len,
-			  unsigned long seq)
-{
-    return 1;
-}
-const static struct ssh_mac ssh_mac_none = {
-    nullmac_make_context, nullmac_free_context, nullmac_key,
-    nullmac_generate, nullmac_verify, "none", 0
-};
-const static struct ssh_mac *macs[] = {
-    &ssh_sha1, &ssh_md5, &ssh_mac_none
-};
-const static struct ssh_mac *buggymacs[] = {
-    &ssh_sha1_buggy, &ssh_md5, &ssh_mac_none
-};
-
-static void *ssh_comp_none_init(void)
-{
-    return NULL;
-}
-static void ssh_comp_none_cleanup(void *handle)
-{
-}
-static int ssh_comp_none_block(void *handle, unsigned char *block, int len,
-			       unsigned char **outblock, int *outlen)
-{
-    return 0;
-}
-static int ssh_comp_none_disable(void *handle)
-{
-    return 0;
-}
-const static struct ssh_compress ssh_comp_none = {
-    "none",
-    ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
-    ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
-    ssh_comp_none_disable, NULL
-};
-extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
-    &ssh_zlib, &ssh_comp_none
-};
-
-enum {				       /* channel types */
-    CHAN_MAINSESSION,
-    CHAN_X11,
-    CHAN_AGENT,
-    CHAN_SOCKDATA,
-    CHAN_SOCKDATA_DORMANT	       /* one the remote hasn't confirmed */
-};
-
-/*
- * 2-3-4 tree storing channels.
- */
-struct ssh_channel {
-    Ssh ssh;			       /* pointer back to main context */
-    unsigned remoteid, localid;
-    int type;
-    /*
-     * In SSH1, this value contains four bits:
-     * 
-     *   1   We have sent SSH1_MSG_CHANNEL_CLOSE.
-     *   2   We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
-     *   4   We have received SSH1_MSG_CHANNEL_CLOSE.
-     *   8   We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
-     * 
-     * A channel is completely finished with when all four bits are set.
-     */
-    int closes;
-    union {
-	struct ssh1_data_channel {
-	    int throttling;
-	} v1;
-	struct ssh2_data_channel {
-	    bufchain outbuffer;
-	    unsigned remwindow, remmaxpkt;
-	    unsigned locwindow;
-	} v2;
-    } v;
-    union {
-	struct ssh_agent_channel {
-	    unsigned char *message;
-	    unsigned char msglen[4];
-	    int lensofar, totallen;
-	} a;
-	struct ssh_x11_channel {
-	    Socket s;
-	} x11;
-	struct ssh_pfd_channel {
-	    Socket s;
-	} pfd;
-    } u;
-};
-
-/*
- * 2-3-4 tree storing remote->local port forwardings. SSH 1 and SSH
- * 2 use this structure in different ways, reflecting SSH 2's
- * altogether saner approach to port forwarding.
- * 
- * In SSH 1, you arrange a remote forwarding by sending the server
- * the remote port number, and the local destination host:port.
- * When a connection comes in, the server sends you back that
- * host:port pair, and you connect to it. This is a ready-made
- * security hole if you're not on the ball: a malicious server
- * could send you back _any_ host:port pair, so if you trustingly
- * connect to the address it gives you then you've just opened the
- * entire inside of your corporate network just by connecting
- * through it to a dodgy SSH server. Hence, we must store a list of
- * host:port pairs we _are_ trying to forward to, and reject a
- * connection request from the server if it's not in the list.
- * 
- * In SSH 2, each side of the connection minds its own business and
- * doesn't send unnecessary information to the other. You arrange a
- * remote forwarding by sending the server just the remote port
- * number. When a connection comes in, the server tells you which
- * of its ports was connected to; and _you_ have to remember what
- * local host:port pair went with that port number.
- * 
- * Hence: in SSH 1 this structure stores host:port pairs we intend
- * to allow connections to, and is indexed by those host:port
- * pairs. In SSH 2 it stores a mapping from source port to
- * destination host:port pair, and is indexed by source port.
- */
-struct ssh_rportfwd {
-    unsigned sport, dport;
-    char dhost[256];
-};
-
-struct Packet {
-    long length;
-    int type;
-    unsigned char *data;
-    unsigned char *body;
-    long savedpos;
-    long maxlen;
-};
-
-static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt);
-static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt);
-static void ssh_size(void *handle, int width, int height);
-static void ssh_special(void *handle, Telnet_Special);
-static int ssh2_try_send(struct ssh_channel *c);
-static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
-static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
-static void ssh2_set_window(struct ssh_channel *c, unsigned newwin);
-static int ssh_sendbuffer(void *handle);
-static void ssh_do_close(Ssh ssh);
-
-struct rdpkt1_state_tag {
-    long len, pad, biglen, to_read;
-    unsigned long realcrc, gotcrc;
-    unsigned char *p;
-    int i;
-    int chunk;
-};
-
-struct rdpkt2_state_tag {
-    long len, pad, payload, packetlen, maclen;
-    int i;
-    int cipherblk;
-    unsigned long incoming_sequence;
-};
-
-struct ssh_tag {
-    const struct plug_function_table *fn;
-    /* the above field _must_ be first in the structure */
-
-    SHA_State exhash, exhashbase;
-
-    Socket s;
-
-    void *ldisc;
-    void *logctx;
-
-    unsigned char session_key[32];
-    int v1_compressing;
-    int v1_remote_protoflags;
-    int v1_local_protoflags;
-    int agentfwd_enabled;
-    int X11_fwd_enabled;
-    int remote_bugs;
-    const struct ssh_cipher *cipher;
-    void *v1_cipher_ctx;
-    void *crcda_ctx;
-    const struct ssh2_cipher *cscipher, *sccipher;
-    void *cs_cipher_ctx, *sc_cipher_ctx;
-    const struct ssh_mac *csmac, *scmac;
-    void *cs_mac_ctx, *sc_mac_ctx;
-    const struct ssh_compress *cscomp, *sccomp;
-    void *cs_comp_ctx, *sc_comp_ctx;
-    const struct ssh_kex *kex;
-    const struct ssh_signkey *hostkey;
-    unsigned char v2_session_id[20];
-    void *kex_ctx;
-
-    char *savedhost;
-    int savedport;
-    int send_ok;
-    int echoing, editing;
-
-    void *frontend;
-
-    int term_width, term_height;
-
-    tree234 *channels;		       /* indexed by local id */
-    struct ssh_channel *mainchan;      /* primary session channel */
-    int exitcode;
-
-    tree234 *rportfwds;
-
-    enum {
-	SSH_STATE_PREPACKET,
-	SSH_STATE_BEFORE_SIZE,
-	SSH_STATE_INTERMED,
-	SSH_STATE_SESSION,
-	SSH_STATE_CLOSED
-    } state;
-
-    int size_needed, eof_needed;
-
-    struct Packet pktin;
-    struct Packet pktout;
-    unsigned char *deferred_send_data;
-    int deferred_len, deferred_size;
-
-    /*
-     * Gross hack: pscp will try to start SFTP but fall back to
-     * scp1 if that fails. This variable is the means by which
-     * scp.c can reach into the SSH code and find out which one it
-     * got.
-     */
-    int fallback_cmd;
-
-    /*
-     * Used for username and password input.
-     */
-    char *userpass_input_buffer;
-    int userpass_input_buflen;
-    int userpass_input_bufpos;
-    int userpass_input_echo;
-
-    char *portfwd_strptr;
-    int pkt_ctx;
-
-    void *x11auth;
-
-    int version;
-    int v1_throttle_count;
-    int overall_bufsize;
-    int throttled_all;
-    int v1_stdout_throttling;
-    int v2_outgoing_sequence;
-
-    int ssh1_rdpkt_crstate;
-    int ssh2_rdpkt_crstate;
-    int do_ssh_init_crstate;
-    int ssh_gotdata_crstate;
-    int ssh1_protocol_crstate;
-    int do_ssh1_login_crstate;
-    int do_ssh2_transport_crstate;
-    int do_ssh2_authconn_crstate;
-
-    void *do_ssh_init_state;
-    void *do_ssh1_login_state;
-    void *do_ssh2_transport_state;
-    void *do_ssh2_authconn_state;
-
-    struct rdpkt1_state_tag rdpkt1_state;
-    struct rdpkt2_state_tag rdpkt2_state;
-
-    void (*protocol) (Ssh ssh, unsigned char *in, int inlen, int ispkt);
-    int (*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
-
-    /*
-     * We maintain a full _copy_ of a Config structure here, not
-     * merely a pointer to it. That way, when we're passed a new
-     * one for reconfiguration, we can check the differences and
-     * potentially reconfigure port forwardings etc in mid-session.
-     */
-    Config cfg;
-
-    /*
-     * Used to transfer data back from async agent callbacks.
-     */
-    void *agent_response;
-    int agent_response_len;
-};
-
-#define logevent(s) logevent(ssh->frontend, s)
-
-/* logevent, only printf-formatted. */
-static void logeventf(Ssh ssh, const char *fmt, ...)
-{
-    va_list ap;
-    char *buf;
-
-    va_start(ap, fmt);
-    buf = dupvprintf(fmt, ap);
-    va_end(ap);
-    logevent(buf);
-    sfree(buf);
-}
-
-#define bombout(msg) \
-    do { \
-        char *text = dupprintf msg; \
-	ssh_do_close(ssh); \
-        logevent(text); \
-        connection_fatal(ssh->frontend, "%s", text); \
-        sfree(text); \
-    } while (0)
-
-static int ssh_channelcmp(void *av, void *bv)
-{
-    struct ssh_channel *a = (struct ssh_channel *) av;
-    struct ssh_channel *b = (struct ssh_channel *) bv;
-    if (a->localid < b->localid)
-	return -1;
-    if (a->localid > b->localid)
-	return +1;
-    return 0;
-}
-static int ssh_channelfind(void *av, void *bv)
-{
-    unsigned *a = (unsigned *) av;
-    struct ssh_channel *b = (struct ssh_channel *) bv;
-    if (*a < b->localid)
-	return -1;
-    if (*a > b->localid)
-	return +1;
-    return 0;
-}
-
-static int ssh_rportcmp_ssh1(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->dhost, b->dhost)) != 0)
-	return i < 0 ? -1 : +1;
-    if (a->dport > b->dport)
-	return +1;
-    if (a->dport < b->dport)
-	return -1;
-    return 0;
-}
-
-static int ssh_rportcmp_ssh2(void *av, void *bv)
-{
-    struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
-    struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
-
-    if (a->sport > b->sport)
-	return +1;
-    if (a->sport < b->sport)
-	return -1;
-    return 0;
-}
-
-static int alloc_channel_id(Ssh ssh)
-{
-    const unsigned CHANNEL_NUMBER_OFFSET = 256;
-    unsigned low, high, mid;
-    int tsize;
-    struct ssh_channel *c;
-
-    /*
-     * First-fit allocation of channel numbers: always pick the
-     * lowest unused one. To do this, binary-search using the
-     * counted B-tree to find the largest channel ID which is in a
-     * contiguous sequence from the beginning. (Precisely
-     * everything in that sequence must have ID equal to its tree
-     * index plus CHANNEL_NUMBER_OFFSET.)
-     */
-    tsize = count234(ssh->channels);
-
-    low = -1;
-    high = tsize;
-    while (high - low > 1) {
-	mid = (high + low) / 2;
-	c = index234(ssh->channels, mid);
-	if (c->localid == mid + CHANNEL_NUMBER_OFFSET)
-	    low = mid;		       /* this one is fine */
-	else
-	    high = mid;		       /* this one is past it */
-    }
-    /*
-     * Now low points to either -1, or the tree index of the
-     * largest ID in the initial sequence.
-     */
-    {
-	unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET;
-	assert(NULL == find234(ssh->channels, &i, ssh_channelfind));
-    }
-    return low + 1 + CHANNEL_NUMBER_OFFSET;
-}
-
-static void c_write(Ssh ssh, const char *buf, int len)
-{
-    if ((flags & FLAG_STDERR)) {
-	int i;
-	for (i = 0; i < len; i++)
-	    if (buf[i] != '\r')
-		fputc(buf[i], stderr);
-	return;
-    }
-    from_backend(ssh->frontend, 1, buf, len);
-}
-
-static void c_write_untrusted(Ssh ssh, const char *buf, int len)
-{
-    int i;
-    for (i = 0; i < len; i++) {
-	if (buf[i] == '\n')
-	    c_write(ssh, "\r\n", 2);
-	else if ((buf[i] & 0x60) || (buf[i] == '\r'))
-	    c_write(ssh, buf + i, 1);
-    }
-}
-
-static void c_write_str(Ssh ssh, const char *buf)
-{
-    c_write(ssh, buf, strlen(buf));
-}
-
-/*
- * Collect incoming data in the incoming packet buffer.
- * Decipher and verify the packet when it is completely read.
- * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets.
- * Update the *data and *datalen variables.
- * Return the additional nr of bytes needed, or 0 when
- * a complete packet is available.
- */
-static int ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
-{
-    struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
-
-    crBegin(ssh->ssh1_rdpkt_crstate);
-
-  next_packet:
-
-    ssh->pktin.type = 0;
-    ssh->pktin.length = 0;
-
-    for (st->i = st->len = 0; st->i < 4; st->i++) {
-	while ((*datalen) == 0)
-	    crReturn(4 - st->i);
-	st->len = (st->len << 8) + **data;
-	(*data)++, (*datalen)--;
-    }
-
-    st->pad = 8 - (st->len % 8);
-    st->biglen = st->len + st->pad;
-    ssh->pktin.length = st->len - 5;
-
-    if (ssh->pktin.maxlen < st->biglen) {
-	ssh->pktin.maxlen = st->biglen;
-	ssh->pktin.data = sresize(ssh->pktin.data, st->biglen + APIEXTRA,
-				  unsigned char);
-    }
-
-    st->to_read = st->biglen;
-    st->p = ssh->pktin.data;
-    while (st->to_read > 0) {
-	st->chunk = st->to_read;
-	while ((*datalen) == 0)
-	    crReturn(st->to_read);
-	if (st->chunk > (*datalen))
-	    st->chunk = (*datalen);
-	memcpy(st->p, *data, st->chunk);
-	*data += st->chunk;
-	*datalen -= st->chunk;
-	st->p += st->chunk;
-	st->to_read -= st->chunk;
-    }
-
-    if (ssh->cipher && detect_attack(ssh->crcda_ctx, ssh->pktin.data,
-				     st->biglen, NULL)) {
-        bombout(("Network attack (CRC compensation) detected!"));
-        crStop(0);
-    }
-
-    if (ssh->cipher)
-	ssh->cipher->decrypt(ssh->v1_cipher_ctx, ssh->pktin.data, st->biglen);
-
-    st->realcrc = crc32_compute(ssh->pktin.data, st->biglen - 4);
-    st->gotcrc = GET_32BIT(ssh->pktin.data + st->biglen - 4);
-    if (st->gotcrc != st->realcrc) {
-	bombout(("Incorrect CRC received on packet"));
-	crStop(0);
-    }
-
-    ssh->pktin.body = ssh->pktin.data + st->pad + 1;
-
-    if (ssh->v1_compressing) {
-	unsigned char *decompblk;
-	int decomplen;
-	if (!zlib_decompress_block(ssh->sc_comp_ctx,
-				   ssh->pktin.body - 1, ssh->pktin.length + 1,
-				   &decompblk, &decomplen)) {
-	    bombout(("Zlib decompression encountered invalid data"));
-	    crStop(0);
-	}
-
-	if (ssh->pktin.maxlen < st->pad + decomplen) {
-	    ssh->pktin.maxlen = st->pad + decomplen;
-	    ssh->pktin.data = sresize(ssh->pktin.data,
-				      ssh->pktin.maxlen + APIEXTRA,
-				      unsigned char);
-	    ssh->pktin.body = ssh->pktin.data + st->pad + 1;
-	}
-
-	memcpy(ssh->pktin.body - 1, decompblk, decomplen);
-	sfree(decompblk);
-	ssh->pktin.length = decomplen - 1;
-    }
-
-    ssh->pktin.type = ssh->pktin.body[-1];
-
-    if (ssh->logctx)
-	log_packet(ssh->logctx,
-		   PKT_INCOMING, ssh->pktin.type,
-		   ssh1_pkt_type(ssh->pktin.type),
-		   ssh->pktin.body, ssh->pktin.length);
-
-    if (ssh->pktin.type == SSH1_SMSG_STDOUT_DATA ||
-	ssh->pktin.type == SSH1_SMSG_STDERR_DATA ||
-	ssh->pktin.type == SSH1_MSG_DEBUG ||
-	ssh->pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE ||
-	ssh->pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
-	long stringlen = GET_32BIT(ssh->pktin.body);
-	if (stringlen + 4 != ssh->pktin.length) {
-	    bombout(("Received data packet with bogus string length"));
-	    crStop(0);
-	}
-    }
-
-    if (ssh->pktin.type == SSH1_MSG_DEBUG) {
-	/* log debug message */
-	char buf[512];
-	int stringlen = GET_32BIT(ssh->pktin.body);
-	strcpy(buf, "Remote debug message: ");
-	if (stringlen > 480)
-	    stringlen = 480;
-	memcpy(buf + 8, ssh->pktin.body + 4, stringlen);
-	buf[8 + stringlen] = '\0';
-	logevent(buf);
-	goto next_packet;
-    } else if (ssh->pktin.type == SSH1_MSG_IGNORE) {
-	/* do nothing */
-	goto next_packet;
-    }
-
-    if (ssh->pktin.type == SSH1_MSG_DISCONNECT) {
-	/* log reason code in disconnect message */
-	char buf[256];
-	unsigned msglen = GET_32BIT(ssh->pktin.body);
-	unsigned nowlen;
-	strcpy(buf, "Remote sent disconnect: ");
-	nowlen = strlen(buf);
-	if (msglen > sizeof(buf) - nowlen - 1)
-	    msglen = sizeof(buf) - nowlen - 1;
-	memcpy(buf + nowlen, ssh->pktin.body + 4, msglen);
-	buf[nowlen + msglen] = '\0';
-	/* logevent(buf); (this is now done within the bombout macro) */
-	bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen));
-	crStop(0);
-    }
-
-    crFinish(0);
-}
-
-static int ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
-{
-    struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
-
-    crBegin(ssh->ssh2_rdpkt_crstate);
-
-  next_packet:
-    ssh->pktin.type = 0;
-    ssh->pktin.length = 0;
-    if (ssh->sccipher)
-	st->cipherblk = ssh->sccipher->blksize;
-    else
-	st->cipherblk = 8;
-    if (st->cipherblk < 8)
-	st->cipherblk = 8;
-
-    if (ssh->pktin.maxlen < st->cipherblk) {
-	ssh->pktin.maxlen = st->cipherblk;
-	ssh->pktin.data = sresize(ssh->pktin.data, st->cipherblk + APIEXTRA,
-				  unsigned char);
-    }
-
-    /*
-     * Acquire and decrypt the first block of the packet. This will
-     * contain the length and padding details.
-     */
-    for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) {
-	while ((*datalen) == 0)
-	    crReturn(st->cipherblk - st->i);
-	ssh->pktin.data[st->i] = *(*data)++;
-	(*datalen)--;
-    }
-
-    if (ssh->sccipher)
-	ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
-			       ssh->pktin.data, st->cipherblk);
-
-    /*
-     * Now get the length and padding figures.
-     */
-    st->len = GET_32BIT(ssh->pktin.data);
-    st->pad = ssh->pktin.data[4];
-
-    /*
-     * _Completely_ silly lengths should be stomped on before they
-     * do us any more damage.
-     */
-    if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) {
-	bombout(("Incoming packet was garbled on decryption"));
-	crStop(0);
-    }
-
-    /*
-     * This enables us to deduce the payload length.
-     */
-    st->payload = st->len - st->pad - 1;
-
-    ssh->pktin.length = st->payload + 5;
-
-    /*
-     * So now we can work out the total packet length.
-     */
-    st->packetlen = st->len + 4;
-    st->maclen = ssh->scmac ? ssh->scmac->len : 0;
-
-    /*
-     * Adjust memory allocation if packet is too big.
-     */
-    if (ssh->pktin.maxlen < st->packetlen + st->maclen) {
-	ssh->pktin.maxlen = st->packetlen + st->maclen;
-	ssh->pktin.data = sresize(ssh->pktin.data,
-				  ssh->pktin.maxlen + APIEXTRA,
-				  unsigned char);
-    }
-
-    /*
-     * Read and decrypt the remainder of the packet.
-     */
-    for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen;
-	 st->i++) {
-	while ((*datalen) == 0)
-	    crReturn(st->packetlen + st->maclen - st->i);
-	ssh->pktin.data[st->i] = *(*data)++;
-	(*datalen)--;
-    }
-    /* Decrypt everything _except_ the MAC. */
-    if (ssh->sccipher)
-	ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
-			       ssh->pktin.data + st->cipherblk,
-			       st->packetlen - st->cipherblk);
-
-    /*
-     * Check the MAC.
-     */
-    if (ssh->scmac
-	&& !ssh->scmac->verify(ssh->sc_mac_ctx, ssh->pktin.data, st->len + 4,
-			       st->incoming_sequence)) {
-	bombout(("Incorrect MAC received on packet"));
-	crStop(0);
-    }
-    st->incoming_sequence++;	       /* whether or not we MACed */
-
-    /*
-     * Decompress packet payload.
-     */
-    {
-	unsigned char *newpayload;
-	int newlen;
-	if (ssh->sccomp &&
-	    ssh->sccomp->decompress(ssh->sc_comp_ctx,
-				    ssh->pktin.data + 5, ssh->pktin.length - 5,
-				    &newpayload, &newlen)) {
-	    if (ssh->pktin.maxlen < newlen + 5) {
-		ssh->pktin.maxlen = newlen + 5;
-		ssh->pktin.data = sresize(ssh->pktin.data,
-					  ssh->pktin.maxlen + APIEXTRA,
-					  unsigned char);
-	    }
-	    ssh->pktin.length = 5 + newlen;
-	    memcpy(ssh->pktin.data + 5, newpayload, newlen);
-	    sfree(newpayload);
-	}
-    }
-
-    ssh->pktin.savedpos = 6;
-    ssh->pktin.type = ssh->pktin.data[5];
-
-    if (ssh->logctx)
-	log_packet(ssh->logctx, PKT_INCOMING, ssh->pktin.type,
-		   ssh2_pkt_type(ssh->pkt_ctx, ssh->pktin.type),
-		   ssh->pktin.data+6, ssh->pktin.length-6);
-
-    switch (ssh->pktin.type) {
-        /*
-         * These packets we must handle instantly.
-         */
-      case SSH2_MSG_DISCONNECT:
-        {
-            /* log reason code in disconnect message */
-            char *buf;
-	    int nowlen;
-            int reason = GET_32BIT(ssh->pktin.data + 6);
-            unsigned msglen = GET_32BIT(ssh->pktin.data + 10);
-
-            if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
-                buf = dupprintf("Received disconnect message (%s)",
-				ssh2_disconnect_reasons[reason]);
-            } else {
-                buf = dupprintf("Received disconnect message (unknown"
-				" type %d)", reason);
-            }
-            logevent(buf);
-	    sfree(buf);
-            buf = dupprintf("Disconnection message text: %n%.*s",
-			    &nowlen, msglen, ssh->pktin.data + 14);
-            logevent(buf);
-            bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
-                     reason,
-                     (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
-                     ssh2_disconnect_reasons[reason] : "unknown",
-                     buf+nowlen));
-	    sfree(buf);
-            crStop(0);
-        }
-        break;
-      case SSH2_MSG_IGNORE:
-	goto next_packet;
-      case SSH2_MSG_DEBUG:
-	{
-	    /* log the debug message */
-	    char buf[512];
-	    /* int display = ssh->pktin.body[6]; */
-	    int stringlen = GET_32BIT(ssh->pktin.data+7);
-	    int prefix;
-	    strcpy(buf, "Remote debug message: ");
-	    prefix = strlen(buf);
-	    if (stringlen > (int)(sizeof(buf)-prefix-1))
-		stringlen = sizeof(buf)-prefix-1;
-	    memcpy(buf + prefix, ssh->pktin.data + 11, stringlen);
-	    buf[prefix + stringlen] = '\0';
-	    logevent(buf);
-	}
-        goto next_packet;              /* FIXME: print the debug message */
-
-        /*
-         * These packets we need do nothing about here.
-         */
-      case SSH2_MSG_UNIMPLEMENTED:
-      case SSH2_MSG_SERVICE_REQUEST:
-      case SSH2_MSG_SERVICE_ACCEPT:
-      case SSH2_MSG_KEXINIT:
-      case SSH2_MSG_NEWKEYS:
-      case SSH2_MSG_KEXDH_INIT:
-      case SSH2_MSG_KEXDH_REPLY:
-      /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */
-      /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */
-      case SSH2_MSG_KEX_DH_GEX_INIT:
-      case SSH2_MSG_KEX_DH_GEX_REPLY:
-      case SSH2_MSG_USERAUTH_REQUEST:
-      case SSH2_MSG_USERAUTH_FAILURE:
-      case SSH2_MSG_USERAUTH_SUCCESS:
-      case SSH2_MSG_USERAUTH_BANNER:
-      case SSH2_MSG_USERAUTH_PK_OK:
-      /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */
-      /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */
-      case SSH2_MSG_USERAUTH_INFO_RESPONSE:
-      case SSH2_MSG_GLOBAL_REQUEST:
-      case SSH2_MSG_REQUEST_SUCCESS:
-      case SSH2_MSG_REQUEST_FAILURE:
-      case SSH2_MSG_CHANNEL_OPEN:
-      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
-      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
-      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:
-        break;
-
-        /*
-         * For anything else we send SSH2_MSG_UNIMPLEMENTED.
-         */
-      default:
-	ssh2_pkt_init(ssh, SSH2_MSG_UNIMPLEMENTED);
-	ssh2_pkt_adduint32(ssh, st->incoming_sequence - 1);
-	ssh2_pkt_send(ssh);
-        break;
-    }
-
-    crFinish(0);
-}
-
-static void ssh1_pktout_size(Ssh ssh, int len)
-{
-    int pad, biglen;
-
-    len += 5;			       /* type and CRC */
-    pad = 8 - (len % 8);
-    biglen = len + pad;
-
-    ssh->pktout.length = len - 5;
-    if (ssh->pktout.maxlen < biglen) {
-	ssh->pktout.maxlen = biglen;
-#ifdef MSCRYPTOAPI
-	/* Allocate enough buffer space for extra block
-	 * for MS CryptEncrypt() */
-	ssh->pktout.data = sresize(ssh->pktout.data, biglen + 12,
-				   unsigned char);
-#else
-	ssh->pktout.data = sresize(ssh->pktout.data, biglen + 4,
-				   unsigned char);
-#endif
-    }
-    ssh->pktout.body = ssh->pktout.data + 4 + pad + 1;
-}
-
-static void s_wrpkt_start(Ssh ssh, int type, int len)
-{
-    ssh1_pktout_size(ssh, len);
-    ssh->pktout.type = type;
-}
-
-static int s_wrpkt_prepare(Ssh ssh)
-{
-    int pad, biglen, i;
-    unsigned long crc;
-#ifdef __SC__
-    /*
-     * XXX various versions of SC (including 8.8.4) screw up the
-     * register allocation in this function and use the same register
-     * (D6) for len and as a temporary, with predictable results.  The
-     * following sledgehammer prevents this.
-     */
-    volatile
-#endif
-    int len;
-
-    ssh->pktout.body[-1] = ssh->pktout.type;
-
-    if (ssh->logctx)
-	log_packet(ssh->logctx, PKT_OUTGOING, ssh->pktout.type,
-		   ssh1_pkt_type(ssh->pktout.type),
-		   ssh->pktout.body, ssh->pktout.length);
-
-    if (ssh->v1_compressing) {
-	unsigned char *compblk;
-	int complen;
-	zlib_compress_block(ssh->cs_comp_ctx,
-			    ssh->pktout.body - 1, ssh->pktout.length + 1,
-			    &compblk, &complen);
-	ssh1_pktout_size(ssh, complen - 1);
-	memcpy(ssh->pktout.body - 1, compblk, complen);
-	sfree(compblk);
-    }
-
-    len = ssh->pktout.length + 5;	       /* type and CRC */
-    pad = 8 - (len % 8);
-    biglen = len + pad;
-
-    for (i = 0; i < pad; i++)
-	ssh->pktout.data[i + 4] = random_byte();
-    crc = crc32_compute(ssh->pktout.data + 4, biglen - 4);
-    PUT_32BIT(ssh->pktout.data + biglen, crc);
-    PUT_32BIT(ssh->pktout.data, len);
-
-    if (ssh->cipher)
-	ssh->cipher->encrypt(ssh->v1_cipher_ctx, ssh->pktout.data + 4, biglen);
-
-    return biglen + 4;
-}
-
-static void s_wrpkt(Ssh ssh)
-{
-    int len, backlog;
-    len = s_wrpkt_prepare(ssh);
-    backlog = sk_write(ssh->s, (char *)ssh->pktout.data, len);
-    if (backlog > SSH_MAX_BACKLOG)
-	ssh_throttle_all(ssh, 1, backlog);
-}
-
-static void s_wrpkt_defer(Ssh ssh)
-{
-    int len;
-    len = s_wrpkt_prepare(ssh);
-    if (ssh->deferred_len + len > ssh->deferred_size) {
-	ssh->deferred_size = ssh->deferred_len + len + 128;
-	ssh->deferred_send_data = sresize(ssh->deferred_send_data,
-					  ssh->deferred_size,
-					  unsigned char);
-    }
-    memcpy(ssh->deferred_send_data + ssh->deferred_len, ssh->pktout.data, len);
-    ssh->deferred_len += len;
-}
-
-/*
- * Construct a packet with the specified contents.
- */
-static void construct_packet(Ssh ssh, int pkttype, va_list ap1, va_list ap2)
-{
-    unsigned char *p, *argp, argchar;
-    unsigned long argint;
-    int pktlen, argtype, arglen;
-    Bignum bn;
-
-    pktlen = 0;
-    while ((argtype = va_arg(ap1, int)) != PKT_END) {
-	switch (argtype) {
-	  case PKT_INT:
-	    (void) va_arg(ap1, int);
-	    pktlen += 4;
-	    break;
-	  case PKT_CHAR:
-	    (void) va_arg(ap1, int);
-	    pktlen++;
-	    break;
-	  case PKT_DATA:
-	    (void) va_arg(ap1, unsigned char *);
-	    arglen = va_arg(ap1, int);
-	    pktlen += arglen;
-	    break;
-	  case PKT_STR:
-	    argp = va_arg(ap1, unsigned char *);
-	    arglen = strlen((char *)argp);
-	    pktlen += 4 + arglen;
-	    break;
-	  case PKT_BIGNUM:
-	    bn = va_arg(ap1, Bignum);
-	    pktlen += ssh1_bignum_length(bn);
-	    break;
-	  default:
-	    assert(0);
-	}
-    }
-
-    s_wrpkt_start(ssh, pkttype, pktlen);
-    p = ssh->pktout.body;
-
-    while ((argtype = va_arg(ap2, int)) != PKT_END) {
-	switch (argtype) {
-	  case PKT_INT:
-	    argint = va_arg(ap2, int);
-	    PUT_32BIT(p, argint);
-	    p += 4;
-	    break;
-	  case PKT_CHAR:
-	    argchar = (unsigned char) va_arg(ap2, int);
-	    *p = argchar;
-	    p++;
-	    break;
-	  case PKT_DATA:
-	    argp = va_arg(ap2, unsigned char *);
-	    arglen = va_arg(ap2, int);
-	    memcpy(p, argp, arglen);
-	    p += arglen;
-	    break;
-	  case PKT_STR:
-	    argp = va_arg(ap2, unsigned char *);
-	    arglen = strlen((char *)argp);
-	    PUT_32BIT(p, arglen);
-	    memcpy(p + 4, argp, arglen);
-	    p += 4 + arglen;
-	    break;
-	  case PKT_BIGNUM:
-	    bn = va_arg(ap2, Bignum);
-	    p += ssh1_write_bignum(p, bn);
-	    break;
-	}
-    }
-}
-
-static void send_packet(Ssh ssh, int pkttype, ...)
-{
-    va_list ap1, ap2;
-    va_start(ap1, pkttype);
-    va_start(ap2, pkttype);
-    construct_packet(ssh, pkttype, ap1, ap2);
-    s_wrpkt(ssh);
-}
-
-static void defer_packet(Ssh ssh, int pkttype, ...)
-{
-    va_list ap1, ap2;
-    va_start(ap1, pkttype);
-    va_start(ap2, pkttype);
-    construct_packet(ssh, pkttype, ap1, ap2);
-    s_wrpkt_defer(ssh);
-}
-
-static int ssh_versioncmp(char *a, 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;
-}
-
-/*
- * Utility routines for putting an SSH-protocol `string' and
- * `uint32' into a SHA state.
- */
-#include <stdio.h>
-static void sha_string(SHA_State * s, void *str, int len)
-{
-    unsigned char lenblk[4];
-    PUT_32BIT(lenblk, len);
-    SHA_Bytes(s, lenblk, 4);
-    SHA_Bytes(s, str, len);
-}
-
-static void sha_uint32(SHA_State * s, unsigned i)
-{
-    unsigned char intblk[4];
-    PUT_32BIT(intblk, i);
-    SHA_Bytes(s, intblk, 4);
-}
-
-/*
- * SSH2 packet construction functions.
- */
-static void ssh2_pkt_ensure(Ssh ssh, int length)
-{
-    if (ssh->pktout.maxlen < length) {
-	ssh->pktout.maxlen = length + 256;
-	ssh->pktout.data = sresize(ssh->pktout.data,
-				   ssh->pktout.maxlen + APIEXTRA,
-				   unsigned char);
-	if (!ssh->pktout.data)
-	    fatalbox("Out of memory");
-    }
-}
-static void ssh2_pkt_adddata(Ssh ssh, void *data, int len)
-{
-    ssh->pktout.length += len;
-    ssh2_pkt_ensure(ssh, ssh->pktout.length);
-    memcpy(ssh->pktout.data + ssh->pktout.length - len, data, len);
-}
-static void ssh2_pkt_addbyte(Ssh ssh, unsigned char byte)
-{
-    ssh2_pkt_adddata(ssh, &byte, 1);
-}
-static void ssh2_pkt_init(Ssh ssh, int pkt_type)
-{
-    ssh->pktout.length = 5;
-    ssh2_pkt_addbyte(ssh, (unsigned char) pkt_type);
-}
-static void ssh2_pkt_addbool(Ssh ssh, unsigned char value)
-{
-    ssh2_pkt_adddata(ssh, &value, 1);
-}
-static void ssh2_pkt_adduint32(Ssh ssh, unsigned long value)
-{
-    unsigned char x[4];
-    PUT_32BIT(x, value);
-    ssh2_pkt_adddata(ssh, x, 4);
-}
-static void ssh2_pkt_addstring_start(Ssh ssh)
-{
-    ssh2_pkt_adduint32(ssh, 0);
-    ssh->pktout.savedpos = ssh->pktout.length;
-}
-static void ssh2_pkt_addstring_str(Ssh ssh, char *data)
-{
-    ssh2_pkt_adddata(ssh, data, strlen(data));
-    PUT_32BIT(ssh->pktout.data + ssh->pktout.savedpos - 4,
-	      ssh->pktout.length - ssh->pktout.savedpos);
-}
-static void ssh2_pkt_addstring_data(Ssh ssh, char *data, int len)
-{
-    ssh2_pkt_adddata(ssh, data, len);
-    PUT_32BIT(ssh->pktout.data + ssh->pktout.savedpos - 4,
-	      ssh->pktout.length - ssh->pktout.savedpos);
-}
-static void ssh2_pkt_addstring(Ssh ssh, char *data)
-{
-    ssh2_pkt_addstring_start(ssh);
-    ssh2_pkt_addstring_str(ssh, data);
-}
-static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
-{
-    unsigned char *p;
-    int i, n = (bignum_bitcount(b) + 7) / 8;
-    p = snewn(n + 1, unsigned char);
-    if (!p)
-	fatalbox("out of memory");
-    p[0] = 0;
-    for (i = 1; i <= n; i++)
-	p[i] = bignum_byte(b, n - i);
-    i = 0;
-    while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0)
-	i++;
-    memmove(p, p + i, n + 1 - i);
-    *len = n + 1 - i;
-    return p;
-}
-static void ssh2_pkt_addmp(Ssh ssh, Bignum b)
-{
-    unsigned char *p;
-    int len;
-    p = ssh2_mpint_fmt(b, &len);
-    ssh2_pkt_addstring_start(ssh);
-    ssh2_pkt_addstring_data(ssh, (char *)p, len);
-    sfree(p);
-}
-
-/*
- * Construct an SSH2 final-form packet: compress it, encrypt it,
- * put the MAC on it. Final packet, ready to be sent, is stored in
- * ssh->pktout.data. Total length is returned.
- */
-static int ssh2_pkt_construct(Ssh ssh)
-{
-    int cipherblk, maclen, padding, i;
-
-    if (ssh->logctx)
-	log_packet(ssh->logctx, PKT_OUTGOING, ssh->pktout.data[5],
-		   ssh2_pkt_type(ssh->pkt_ctx, ssh->pktout.data[5]),
-		   ssh->pktout.data + 6, ssh->pktout.length - 6);
-
-    /*
-     * Compress packet payload.
-     */
-    {
-	unsigned char *newpayload;
-	int newlen;
-	if (ssh->cscomp &&
-	    ssh->cscomp->compress(ssh->cs_comp_ctx, ssh->pktout.data + 5,
-				  ssh->pktout.length - 5,
-				  &newpayload, &newlen)) {
-	    ssh->pktout.length = 5;
-	    ssh2_pkt_adddata(ssh, 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.
-     */
-    cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8;  /* block size */
-    cipherblk = cipherblk < 8 ? 8 : cipherblk;	/* or 8 if blksize < 8 */
-    padding = 4;
-    padding +=
-	(cipherblk - (ssh->pktout.length + padding) % cipherblk) % cipherblk;
-    maclen = ssh->csmac ? ssh->csmac->len : 0;
-    ssh2_pkt_ensure(ssh, ssh->pktout.length + padding + maclen);
-    ssh->pktout.data[4] = padding;
-    for (i = 0; i < padding; i++)
-	ssh->pktout.data[ssh->pktout.length + i] = random_byte();
-    PUT_32BIT(ssh->pktout.data, ssh->pktout.length + padding - 4);
-    if (ssh->csmac)
-	ssh->csmac->generate(ssh->cs_mac_ctx, ssh->pktout.data,
-			     ssh->pktout.length + padding,
-			     ssh->v2_outgoing_sequence);
-    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
-
-    if (ssh->cscipher)
-	ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
-			       ssh->pktout.data, ssh->pktout.length + padding);
-
-    /* Ready-to-send packet starts at ssh->pktout.data. We return length. */
-    return ssh->pktout.length + padding + maclen;
-}
-
-/*
- * Construct and send an SSH2 packet immediately.
- */
-static void ssh2_pkt_send(Ssh ssh)
-{
-    int len;
-    int backlog;
-    len = ssh2_pkt_construct(ssh);
-    backlog = sk_write(ssh->s, (char *)ssh->pktout.data, len);
-    if (backlog > SSH_MAX_BACKLOG)
-	ssh_throttle_all(ssh, 1, backlog);
-}
-
-/*
- * Construct an SSH2 packet and add it to a deferred data block.
- * Useful for sending multiple packets in a single sk_write() call,
- * to prevent a traffic-analysing listener from being able to work
- * out the length of any particular packet (such as the password
- * packet).
- * 
- * Note that because SSH2 sequence-numbers its packets, this can
- * NOT be used as an m4-style `defer' allowing packets to be
- * constructed in one order and sent in another.
- */
-static void ssh2_pkt_defer(Ssh ssh)
-{
-    int len = ssh2_pkt_construct(ssh);
-    if (ssh->deferred_len + len > ssh->deferred_size) {
-	ssh->deferred_size = ssh->deferred_len + len + 128;
-	ssh->deferred_send_data = sresize(ssh->deferred_send_data,
-					  ssh->deferred_size,
-					  unsigned char);
-    }
-    memcpy(ssh->deferred_send_data + ssh->deferred_len, ssh->pktout.data, len);
-    ssh->deferred_len += len;
-}
-
-/*
- * Send the whole deferred data block constructed by
- * ssh2_pkt_defer() or SSH1's defer_packet().
- */
-static void ssh_pkt_defersend(Ssh ssh)
-{
-    int backlog;
-    backlog = sk_write(ssh->s, (char *)ssh->deferred_send_data,
-		       ssh->deferred_len);
-    ssh->deferred_len = ssh->deferred_size = 0;
-    sfree(ssh->deferred_send_data);
-    ssh->deferred_send_data = NULL;
-    if (backlog > SSH_MAX_BACKLOG)
-	ssh_throttle_all(ssh, 1, backlog);
-}
-
-#if 0
-void bndebug(char *string, Bignum b)
-{
-    unsigned char *p;
-    int i, len;
-    p = ssh2_mpint_fmt(b, &len);
-    debug(("%s", string));
-    for (i = 0; i < len; i++)
-	debug((" %02x", p[i]));
-    debug(("\n"));
-    sfree(p);
-}
-#endif
-
-static void sha_mpint(SHA_State * s, Bignum b)
-{
-    unsigned char *p;
-    int len;
-    p = ssh2_mpint_fmt(b, &len);
-    sha_string(s, p, len);
-    sfree(p);
-}
-
-/*
- * SSH2 packet decode functions.
- */
-static unsigned long ssh2_pkt_getuint32(Ssh ssh)
-{
-    unsigned long value;
-    if (ssh->pktin.length - ssh->pktin.savedpos < 4)
-	return 0;		       /* arrgh, no way to decline (FIXME?) */
-    value = GET_32BIT(ssh->pktin.data + ssh->pktin.savedpos);
-    ssh->pktin.savedpos += 4;
-    return value;
-}
-static int ssh2_pkt_getbool(Ssh ssh)
-{
-    unsigned long value;
-    if (ssh->pktin.length - ssh->pktin.savedpos < 1)
-	return 0;		       /* arrgh, no way to decline (FIXME?) */
-    value = ssh->pktin.data[ssh->pktin.savedpos] != 0;
-    ssh->pktin.savedpos++;
-    return value;
-}
-static void ssh2_pkt_getstring(Ssh ssh, char **p, int *length)
-{
-    int len;
-    *p = NULL;
-    *length = 0;
-    if (ssh->pktin.length - ssh->pktin.savedpos < 4)
-	return;
-    len = GET_32BIT(ssh->pktin.data + ssh->pktin.savedpos);
-    if (len < 0)
-	return;
-    *length = len;
-    ssh->pktin.savedpos += 4;
-    if (ssh->pktin.length - ssh->pktin.savedpos < *length)
-	return;
-    *p = (char *)(ssh->pktin.data + ssh->pktin.savedpos);
-    ssh->pktin.savedpos += *length;
-}
-static Bignum ssh2_pkt_getmp(Ssh ssh)
-{
-    char *p;
-    int length;
-    Bignum b;
-
-    ssh2_pkt_getstring(ssh, &p, &length);
-    if (!p)
-	return NULL;
-    if (p[0] & 0x80) {
-	bombout(("internal error: Can't handle negative mpints"));
-	return NULL;
-    }
-    b = bignum_from_bytes((unsigned char *)p, length);
-    return b;
-}
-
-/*
- * Helper function to add an SSH2 signature blob to a packet.
- * Expects to be shown the public key blob as well as the signature
- * blob. Normally works just like ssh2_pkt_addstring, but will
- * fiddle with the signature packet if necessary for
- * BUG_SSH2_RSA_PADDING.
- */
-static void ssh2_add_sigblob(Ssh ssh, void *pkblob_v, int pkblob_len,
-			     void *sigblob_v, int sigblob_len)
-{
-    unsigned char *pkblob = (unsigned char *)pkblob_v;
-    unsigned char *sigblob = (unsigned char *)sigblob_v;
-
-    /* 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 ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) &&
-	(GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
-	int pos, len, siglen;
-
-	/*
-	 * Find the byte length of the modulus.
-	 */
-
-	pos = 4+7;		       /* skip over "ssh-rsa" */
-	pos += 4 + GET_32BIT(pkblob+pos);   /* skip over exponent */
-	len = GET_32BIT(pkblob+pos);   /* find length of modulus */
-	pos += 4;		       /* find modulus itself */
-	while (len > 0 && pkblob[pos] == 0)
-	    len--, pos++;
-	/* debug(("modulus length is %d\n", len)); */
-
-	/*
-	 * Now find the signature integer.
-	 */
-	pos = 4+7;		       /* skip over "ssh-rsa" */
-	siglen = GET_32BIT(sigblob+pos);
-	/* debug(("signature length is %d\n", siglen)); */
-
-	if (len != siglen) {
-	    unsigned char newlen[4];
-	    ssh2_pkt_addstring_start(ssh);
-	    ssh2_pkt_addstring_data(ssh, (char *)sigblob, pos);
-	    /* dmemdump(sigblob, pos); */
-	    pos += 4;		       /* point to start of actual sig */
-	    PUT_32BIT(newlen, len);
-	    ssh2_pkt_addstring_data(ssh, (char *)newlen, 4);
-	    /* dmemdump(newlen, 4); */
-	    newlen[0] = 0;
-	    while (len-- > siglen) {
-		ssh2_pkt_addstring_data(ssh, (char *)newlen, 1);
-		/* dmemdump(newlen, 1); */
-	    }
-	    ssh2_pkt_addstring_data(ssh, (char *)(sigblob+pos), siglen);
-	    /* dmemdump(sigblob+pos, siglen); */
-	    return;
-	}
-
-	/* Otherwise fall through and do it the easy way. */
-    }
-
-    ssh2_pkt_addstring_start(ssh);
-    ssh2_pkt_addstring_data(ssh, (char *)sigblob, sigblob_len);
-}
-
-/*
- * Examine the remote side's version string and compare it against
- * a list of known buggy implementations.
- */
-static void ssh_detect_bugs(Ssh ssh, char *vstring)
-{
-    char *imp;			       /* pointer to implementation part */
-    imp = vstring;
-    imp += strcspn(imp, "-");
-    if (*imp) imp++;
-    imp += strcspn(imp, "-");
-    if (*imp) imp++;
-
-    ssh->remote_bugs = 0;
-
-    if (ssh->cfg.sshbug_ignore1 == FORCE_ON ||
-	(ssh->cfg.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")))) {
-	/*
-	 * These versions don't support SSH1_MSG_IGNORE, so we have
-	 * to use a different defence against password length
-	 * sniffing.
-	 */
-	ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
-	logevent("We believe remote version has SSH1 ignore bug");
-    }
-
-    if (ssh->cfg.sshbug_plainpw1 == FORCE_ON ||
-	(ssh->cfg.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.
-	 */
-	ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
-	logevent("We believe remote version needs a plain SSH1 password");
-    }
-
-    if (ssh->cfg.sshbug_rsa1 == FORCE_ON ||
-	(ssh->cfg.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.
-	 */
-	ssh->remote_bugs |= BUG_CHOKES_ON_RSA;
-	logevent("We believe remote version can't handle RSA authentication");
-    }
-
-    if (ssh->cfg.sshbug_hmac2 == FORCE_ON ||
-	(ssh->cfg.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.
-	 */
-	ssh->remote_bugs |= BUG_SSH2_HMAC;
-	logevent("We believe remote version has SSH2 HMAC bug");
-    }
-
-    if (ssh->cfg.sshbug_derivekey2 == FORCE_ON ||
-	(ssh->cfg.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).
-	 */
-	ssh->remote_bugs |= BUG_SSH2_DERIVEKEY;
-	logevent("We believe remote version has SSH2 key-derivation bug");
-    }
-
-    if (ssh->cfg.sshbug_rsapad2 == FORCE_ON ||
-	(ssh->cfg.sshbug_rsapad2 == AUTO &&
-	 (wc_match("OpenSSH_2.[5-9]*", imp) ||
-	  wc_match("OpenSSH_3.[0-2]*", imp)))) {
-	/*
-	 * These versions have the SSH2 RSA padding bug.
-	 */
-	ssh->remote_bugs |= BUG_SSH2_RSA_PADDING;
-	logevent("We believe remote version has SSH2 RSA padding bug");
-    }
-
-    if (ssh->cfg.sshbug_pksessid2 == FORCE_ON ||
-	(ssh->cfg.sshbug_pksessid2 == AUTO &&
-	 wc_match("OpenSSH_2.[0-2]*", imp))) {
-	/*
-	 * These versions have the SSH2 session-ID bug in
-	 * public-key authentication.
-	 */
-	ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
-	logevent("We believe remote version has SSH2 public-key-session-ID bug");
-    }
-
-    if (ssh->cfg.sshbug_dhgex2 == FORCE_ON) {
-	/*
-	 * User specified the SSH2 DH GEX bug.
-	 */
-	ssh->remote_bugs |= BUG_SSH2_DH_GEX;
-	logevent("We believe remote version has SSH2 DH group exchange bug");
-    }
-}
-
-static int do_ssh_init(Ssh ssh, unsigned char c)
-{
-    struct do_ssh_init_state {
-	int vslen;
-	char version[10];
-	char *vstring;
-	int vstrsize;
-	int i;
-	int proto1, proto2;
-    };
-    crState(do_ssh_init_state);
-
-    crBegin(ssh->do_ssh_init_crstate);
-
-    /* Search for the string "SSH-" in the input. */
-    s->i = 0;
-    while (1) {
-	static const int transS[] = { 1, 2, 2, 1 };
-	static const int transH[] = { 0, 0, 3, 0 };
-	static const int transminus[] = { 0, 0, 0, -1 };
-	if (c == 'S')
-	    s->i = transS[s->i];
-	else if (c == 'H')
-	    s->i = transH[s->i];
-	else if (c == '-')
-	    s->i = transminus[s->i];
-	else
-	    s->i = 0;
-	if (s->i < 0)
-	    break;
-	crReturn(1);		       /* get another character */
-    }
-
-    s->vstrsize = 16;
-    s->vstring = snewn(s->vstrsize, char);
-    strcpy(s->vstring, "SSH-");
-    s->vslen = 4;
-    s->i = 0;
-    while (1) {
-	crReturn(1);		       /* get another char */
-	if (s->vslen >= s->vstrsize - 1) {
-	    s->vstrsize += 16;
-	    s->vstring = sresize(s->vstring, s->vstrsize, char);
-	}
-	s->vstring[s->vslen++] = c;
-	if (s->i >= 0) {
-	    if (c == '-') {
-		s->version[s->i] = '\0';
-		s->i = -1;
-	    } else if (s->i < sizeof(s->version) - 1)
-		s->version[s->i++] = c;
-	} else if (c == '\012')
-	    break;
-    }
-
-    ssh->agentfwd_enabled = FALSE;
-    ssh->rdpkt2_state.incoming_sequence = 0;
-
-    s->vstring[s->vslen] = 0;
-    s->vstring[strcspn(s->vstring, "\r\n")] = '\0';/* remove EOL chars */
-    {
-	char *vlog;
-	vlog = snewn(20 + s->vslen, char);
-	sprintf(vlog, "Server version: %s", s->vstring);
-	logevent(vlog);
-	sfree(vlog);
-    }
-    ssh_detect_bugs(ssh, s->vstring);
-
-    /*
-     * Decide which SSH protocol version to support.
-     */
-
-    /* Anything strictly below "2.0" means protocol 1 is supported. */
-    s->proto1 = ssh_versioncmp(s->version, "2.0") < 0;
-    /* Anything greater or equal to "1.99" means protocol 2 is supported. */
-    s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
-
-    if (ssh->cfg.sshprot == 0 && !s->proto1) {
-	bombout(("SSH protocol version 1 required by user but not provided by server"));
-	crStop(0);
-    }
-    if (ssh->cfg.sshprot == 3 && !s->proto2) {
-	bombout(("SSH protocol version 2 required by user but not provided by server"));
-	crStop(0);
-    }
-
-    if (s->proto2 && (ssh->cfg.sshprot >= 2 || !s->proto1)) {
-	/*
-	 * Use v2 protocol.
-	 */
-	char verstring[80], vlog[100];
-	sprintf(verstring, "SSH-2.0-%s", sshver);
-	SHA_Init(&ssh->exhashbase);
-	/*
-	 * Hash our version string and their version string.
-	 */
-	sha_string(&ssh->exhashbase, verstring, strlen(verstring));
-	sha_string(&ssh->exhashbase, s->vstring, strcspn(s->vstring, "\r\n"));
-	sprintf(vlog, "We claim version: %s", verstring);
-	logevent(vlog);
-	strcat(verstring, "\012");
-	logevent("Using SSH protocol version 2");
-	sk_write(ssh->s, verstring, strlen(verstring));
-	ssh->protocol = ssh2_protocol;
-	ssh->version = 2;
-	ssh->s_rdpkt = ssh2_rdpkt;
-    } else {
-	/*
-	 * Use v1 protocol.
-	 */
-	char verstring[80], vlog[100];
-	sprintf(verstring, "SSH-%s-%s",
-		(ssh_versioncmp(s->version, "1.5") <= 0 ? s->version : "1.5"),
-		sshver);
-	sprintf(vlog, "We claim version: %s", verstring);
-	logevent(vlog);
-	strcat(verstring, "\012");
-
-	logevent("Using SSH protocol version 1");
-	sk_write(ssh->s, verstring, strlen(verstring));
-	ssh->protocol = ssh1_protocol;
-	ssh->version = 1;
-	ssh->s_rdpkt = ssh1_rdpkt;
-    }
-    update_specials_menu(ssh->frontend);
-    ssh->state = SSH_STATE_BEFORE_SIZE;
-
-    sfree(s->vstring);
-
-    crFinish(0);
-}
-
-static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
-{
-    crBegin(ssh->ssh_gotdata_crstate);
-
-    /*
-     * To begin with, feed the characters one by one to the
-     * protocol initialisation / selection function do_ssh_init().
-     * When that returns 0, we're done with the initial greeting
-     * exchange and can move on to packet discipline.
-     */
-    while (1) {
-	int ret;		       /* need not be kept across crReturn */
-	if (datalen == 0)
-	    crReturnV;		       /* more data please */
-	ret = do_ssh_init(ssh, *data);
-	data++;
-	datalen--;
-	if (ret == 0)
-	    break;
-    }
-
-    /*
-     * We emerge from that loop when the initial negotiation is
-     * over and we have selected an s_rdpkt function. Now pass
-     * everything to s_rdpkt, and then pass the resulting packets
-     * to the proper protocol handler.
-     */
-    if (datalen == 0)
-	crReturnV;
-    while (1) {
-	while (datalen > 0) {
-	    if (ssh->s_rdpkt(ssh, &data, &datalen) == 0) {
-		if (ssh->state == SSH_STATE_CLOSED) {
-		    return;
-		}
-		ssh->protocol(ssh, NULL, 0, 1);
-		if (ssh->state == SSH_STATE_CLOSED) {
-		    return;
-		}
-	    }
-	}
-	crReturnV;
-    }
-    crFinishV;
-}
-
-static void ssh_do_close(Ssh ssh)
-{
-    int i;
-    struct ssh_channel *c;
-
-    ssh->state = SSH_STATE_CLOSED;
-    if (ssh->s) {
-        sk_close(ssh->s);
-        ssh->s = NULL;
-    }
-    /*
-     * Now we must shut down any port and X forwardings going
-     * through this connection.
-     */
-    if (ssh->channels) {
-	for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
-	    switch (c->type) {
-	      case CHAN_X11:
-		x11_close(c->u.x11.s);
-		break;
-	      case CHAN_SOCKDATA:
-		pfd_close(c->u.pfd.s);
-		break;
-	    }
-	    del234(ssh->channels, c);
-	    if (ssh->version == 2)
-		bufchain_clear(&c->v.v2.outbuffer);
-	    sfree(c);
-	}
-    }
-}
-
-static int ssh_closing(Plug plug, const char *error_msg, int error_code,
-		       int calling_back)
-{
-    Ssh ssh = (Ssh) plug;
-    ssh_do_close(ssh);
-    if (error_msg) {
-	/* A socket error has occurred. */
-	logevent(error_msg);
-	connection_fatal(ssh->frontend, "%s", error_msg);
-    } else {
-	/* Otherwise, the remote side closed the connection normally. */
-    }
-    return 0;
-}
-
-static int ssh_receive(Plug plug, int urgent, char *data, int len)
-{
-    Ssh ssh = (Ssh) plug;
-    ssh_gotdata(ssh, (unsigned char *)data, len);
-    if (ssh->state == SSH_STATE_CLOSED) {
-	ssh_do_close(ssh);
-	return 0;
-    }
-    return 1;
-}
-
-static void ssh_sent(Plug plug, int bufsize)
-{
-    Ssh ssh = (Ssh) plug;
-    /*
-     * If the send backlog on the SSH socket itself clears, we
-     * should unthrottle the whole world if it was throttled.
-     */
-    if (bufsize < SSH_MAX_BACKLOG)
-	ssh_throttle_all(ssh, 0, bufsize);
-}
-
-/*
- * 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 const char *connect_to_host(Ssh ssh, char *host, int port,
-				   char **realhost, int nodelay)
-{
-    static const struct plug_function_table fn_table = {
-	ssh_closing,
-	ssh_receive,
-	ssh_sent,
-	NULL
-    };
-
-    SockAddr addr;
-    const char *err;
-
-    ssh->savedhost = snewn(1 + strlen(host), char);
-    if (!ssh->savedhost)
-	fatalbox("Out of memory");
-    strcpy(ssh->savedhost, host);
-
-    if (port < 0)
-	port = 22;		       /* default ssh port */
-    ssh->savedport = port;
-
-    /*
-     * Try to find host.
-     */
-    logeventf(ssh, "Looking up host \"%s\"", host);
-    addr = name_lookup(host, port, realhost, &ssh->cfg);
-    if ((err = sk_addr_error(addr)) != NULL) {
-	sk_addr_free(addr);
-	return err;
-    }
-
-    /*
-     * Open socket.
-     */
-    {
-	char addrbuf[100];
-	sk_getaddr(addr, addrbuf, 100);
-	logeventf(ssh, "Connecting to %s port %d", addrbuf, port);
-    }
-    ssh->fn = &fn_table;
-    ssh->s = new_connection(addr, *realhost, port,
-			    0, 1, nodelay, (Plug) ssh, &ssh->cfg);
-    if ((err = sk_socket_error(ssh->s)) != NULL) {
-	ssh->s = NULL;
-	return err;
-    }
-
-    return NULL;
-}
-
-/*
- * Throttle or unthrottle the SSH connection.
- */
-static void ssh1_throttle(Ssh ssh, int adjust)
-{
-    int old_count = ssh->v1_throttle_count;
-    ssh->v1_throttle_count += adjust;
-    assert(ssh->v1_throttle_count >= 0);
-    if (ssh->v1_throttle_count && !old_count) {
-	sk_set_frozen(ssh->s, 1);
-    } else if (!ssh->v1_throttle_count && old_count) {
-	sk_set_frozen(ssh->s, 0);
-    }
-}
-
-/*
- * Throttle or unthrottle _all_ local data streams (for when sends
- * on the SSH connection itself back up).
- */
-static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
-{
-    int i;
-    struct ssh_channel *c;
-
-    if (enable == ssh->throttled_all)
-	return;
-    ssh->throttled_all = enable;
-    ssh->overall_bufsize = bufsize;
-    if (!ssh->channels)
-	return;
-    for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
-	switch (c->type) {
-	  case CHAN_MAINSESSION:
-	    /*
-	     * This is treated separately, outside the switch.
-	     */
-	    break;
-	  case CHAN_X11:
-	    x11_override_throttle(c->u.x11.s, enable);
-	    break;
-	  case CHAN_AGENT:
-	    /* Agent channels require no buffer management. */
-	    break;
-	  case CHAN_SOCKDATA:
-	    pfd_override_throttle(c->u.pfd.s, enable);
-	    break;
-	}
-    }
-}
-
-/*
- * Username and password input, abstracted off into routines
- * reusable in several places - even between SSH1 and SSH2.
- */
-
-/* Set up a username or password input loop on a given buffer. */
-static void setup_userpass_input(Ssh ssh, char *buffer, int buflen, int echo)
-{
-    ssh->userpass_input_buffer = buffer;
-    ssh->userpass_input_buflen = buflen;
-    ssh->userpass_input_bufpos = 0;
-    ssh->userpass_input_echo = echo;
-}
-
-/*
- * Process some terminal data in the course of username/password
- * input. Returns >0 for success (line of input returned in
- * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0
- * for inconclusive (keep waiting for more input please).
- */
-static int process_userpass_input(Ssh ssh, unsigned char *in, int inlen)
-{
-    char c;
-
-    while (inlen--) {
-	switch (c = *in++) {
-	  case 10:
-	  case 13:
-	    ssh->userpass_input_buffer[ssh->userpass_input_bufpos] = 0;
-	    ssh->userpass_input_buffer[ssh->userpass_input_buflen-1] = 0;
-	    return +1;
-	    break;
-	  case 8:
-	  case 127:
-	    if (ssh->userpass_input_bufpos > 0) {
-		if (ssh->userpass_input_echo)
-		    c_write_str(ssh, "\b \b");
-		ssh->userpass_input_bufpos--;
-	    }
-	    break;
-	  case 21:
-	  case 27:
-	    while (ssh->userpass_input_bufpos > 0) {
-		if (ssh->userpass_input_echo)
-		    c_write_str(ssh, "\b \b");
-		ssh->userpass_input_bufpos--;
-	    }
-	    break;
-	  case 3:
-	  case 4:
-	    return -1;
-	    break;
-	  default:
-	    /*
-	     * This simplistic check for printability is disabled
-	     * when we're doing password input, because some people
-	     * have control characters in their passwords.o
-	     */
-	    if ((!ssh->userpass_input_echo ||
-		 (c >= ' ' && c <= '~') ||
-		 ((unsigned char) c >= 160))
-		&& ssh->userpass_input_bufpos < ssh->userpass_input_buflen-1) {
-		ssh->userpass_input_buffer[ssh->userpass_input_bufpos++] = c;
-		if (ssh->userpass_input_echo)
-		    c_write(ssh, &c, 1);
-	    }
-	    break;
-	}
-    }
-    return 0;
-}
-
-static void ssh_agent_callback(void *sshv, void *reply, int replylen)
-{
-    Ssh ssh = (Ssh) sshv;
-
-    ssh->agent_response = reply;
-    ssh->agent_response_len = replylen;
-
-    if (ssh->version == 1)
-	do_ssh1_login(ssh, NULL, -1, 0);
-    else
-	do_ssh2_authconn(ssh, NULL, -1, 0);
-}
-
-static void ssh_agentf_callback(void *cv, void *reply, int replylen)
-{
-    struct ssh_channel *c = (struct ssh_channel *)cv;
-    Ssh ssh = c->ssh;
-    void *sentreply = reply;
-
-    if (!sentreply) {
-	/* Fake SSH_AGENT_FAILURE. */
-	sentreply = "\0\0\0\1\5";
-	replylen = 5;
-    }
-    if (ssh->version == 2) {
-	ssh2_add_channel_data(c, sentreply, replylen);
-	ssh2_try_send(c);
-    } else {
-	send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
-		    PKT_INT, c->remoteid,
-		    PKT_INT, replylen,
-		    PKT_DATA, sentreply, replylen,
-		    PKT_END);
-    }
-    if (reply)
-	sfree(reply);
-}
-
-/*
- * Handle the key exchange and user authentication phases.
- */
-static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, int ispkt)
-{
-    int i, j;
-    unsigned char cookie[8];
-    struct RSAKey servkey, hostkey;
-    struct MD5Context md5c;
-    struct do_ssh1_login_state {
-	int len;
-	unsigned char *rsabuf, *keystr1, *keystr2;
-	unsigned long supported_ciphers_mask, supported_auths_mask;
-	int tried_publickey, tried_agent;
-	int tis_auth_refused, ccard_auth_refused;
-	unsigned char session_id[16];
-	int cipher_type;
-	char username[100];
-	void *publickey_blob;
-	int publickey_bloblen;
-	char password[100];
-	char prompt[200];
-	int pos;
-	char c;
-	int pwpkt_type;
-	unsigned char request[5], *response, *p;
-	int responselen;
-	int keyi, nkeys;
-	int authed;
-	struct RSAKey key;
-	Bignum challenge;
-	char *commentp;
-	int commentlen;
-    };
-    crState(do_ssh1_login_state);
-
-    crBegin(ssh->do_ssh1_login_crstate);
-
-    if (!ispkt)
-	crWaitUntil(ispkt);
-
-    if (ssh->pktin.type != SSH1_SMSG_PUBLIC_KEY) {
-	bombout(("Public key packet not received"));
-	crStop(0);
-    }
-
-    logevent("Received public keys");
-
-    memcpy(cookie, ssh->pktin.body, 8);
-
-    i = makekey(ssh->pktin.body + 8, &servkey, &s->keystr1, 0);
-    j = makekey(ssh->pktin.body + 8 + i, &hostkey, &s->keystr2, 0);
-
-    /*
-     * Log the host key fingerprint.
-     */
-    {
-	char logmsg[80];
-	logevent("Host key fingerprint is:");
-	strcpy(logmsg, "      ");
-	hostkey.comment = NULL;
-	rsa_fingerprint(logmsg + strlen(logmsg),
-			sizeof(logmsg) - strlen(logmsg), &hostkey);
-	logevent(logmsg);
-    }
-
-    ssh->v1_remote_protoflags = GET_32BIT(ssh->pktin.body + 8 + i + j);
-    s->supported_ciphers_mask = GET_32BIT(ssh->pktin.body + 12 + i + j);
-    s->supported_auths_mask = GET_32BIT(ssh->pktin.body + 16 + i + j);
-
-    ssh->v1_local_protoflags =
-	ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
-    ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
-
-    MD5Init(&md5c);
-    MD5Update(&md5c, s->keystr2, hostkey.bytes);
-    MD5Update(&md5c, s->keystr1, servkey.bytes);
-    MD5Update(&md5c, ssh->pktin.body, 8);
-    MD5Final(s->session_id, &md5c);
-
-    for (i = 0; i < 32; i++)
-	ssh->session_key[i] = random_byte();
-
-    s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
-
-    s->rsabuf = snewn(s->len, unsigned char);
-    if (!s->rsabuf)
-	fatalbox("Out of memory");
-
-    /*
-     * Verify the host key.
-     */
-    {
-	/*
-	 * First format the key into a string.
-	 */
-	int len = rsastr_len(&hostkey);
-	char fingerprint[100];
-	char *keystr = snewn(len, char);
-	if (!keystr)
-	    fatalbox("Out of memory");
-	rsastr_fmt(keystr, &hostkey);
-	rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
-	verify_ssh_host_key(ssh->frontend,
-			    ssh->savedhost, ssh->savedport, "rsa", keystr,
-			    fingerprint);
-	sfree(keystr);
-    }
-
-    for (i = 0; i < 32; i++) {
-	s->rsabuf[i] = ssh->session_key[i];
-	if (i < 16)
-	    s->rsabuf[i] ^= s->session_id[i];
-    }
-
-    if (hostkey.bytes > servkey.bytes) {
-	rsaencrypt(s->rsabuf, 32, &servkey);
-	rsaencrypt(s->rsabuf, servkey.bytes, &hostkey);
-    } else {
-	rsaencrypt(s->rsabuf, 32, &hostkey);
-	rsaencrypt(s->rsabuf, hostkey.bytes, &servkey);
-    }
-
-    logevent("Encrypted session key");
-
-    {
-	int cipher_chosen = 0, warn = 0;
-	char *cipher_string = NULL;
-	int i;
-	for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
-	    int next_cipher = ssh->cfg.ssh_cipherlist[i];
-	    if (next_cipher == CIPHER_WARN) {
-		/* If/when we choose a cipher, warn about it */
-		warn = 1;
-	    } else if (next_cipher == CIPHER_AES) {
-		/* XXX Probably don't need to mention this. */
-		logevent("AES not supported in SSH1, skipping");
-	    } else {
-		switch (next_cipher) {
-		  case CIPHER_3DES:     s->cipher_type = SSH_CIPHER_3DES;
-					cipher_string = "3DES"; break;
-		  case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
-					cipher_string = "Blowfish"; break;
-		  case CIPHER_DES:	s->cipher_type = SSH_CIPHER_DES;
-					cipher_string = "single-DES"; break;
-		}
-		if (s->supported_ciphers_mask & (1 << s->cipher_type))
-		    cipher_chosen = 1;
-	    }
-	}
-	if (!cipher_chosen) {
-	    if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0)
-		bombout(("Server violates SSH 1 protocol by not "
-			 "supporting 3DES encryption"));
-	    else
-		/* shouldn't happen */
-		bombout(("No supported ciphers found"));
-	    crStop(0);
-	}
-
-	/* Warn about chosen cipher if necessary. */
-	if (warn)
-	    askcipher(ssh->frontend, cipher_string, 0);
-    }
-
-    switch (s->cipher_type) {
-      case SSH_CIPHER_3DES:
-	logevent("Using 3DES encryption");
-	break;
-      case SSH_CIPHER_DES:
-	logevent("Using single-DES encryption");
-	break;
-      case SSH_CIPHER_BLOWFISH:
-	logevent("Using Blowfish encryption");
-	break;
-    }
-
-    send_packet(ssh, SSH1_CMSG_SESSION_KEY,
-		PKT_CHAR, s->cipher_type,
-		PKT_DATA, cookie, 8,
-		PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF,
-		PKT_DATA, s->rsabuf, s->len,
-		PKT_INT, ssh->v1_local_protoflags, PKT_END);
-
-    logevent("Trying to enable encryption...");
-
-    sfree(s->rsabuf);
-
-    ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
-		   s->cipher_type == SSH_CIPHER_DES ? &ssh_des :
-		   &ssh_3des);
-    ssh->v1_cipher_ctx = ssh->cipher->make_context();
-    ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key);
-    logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name);
-
-    ssh->crcda_ctx = crcda_make_context();
-    logevent("Installing CRC compensation attack detector");
-
-    if (servkey.modulus) {
-	sfree(servkey.modulus);
-	servkey.modulus = NULL;
-    }
-    if (servkey.exponent) {
-	sfree(servkey.exponent);
-	servkey.exponent = NULL;
-    }
-    if (hostkey.modulus) {
-	sfree(hostkey.modulus);
-	hostkey.modulus = NULL;
-    }
-    if (hostkey.exponent) {
-	sfree(hostkey.exponent);
-	hostkey.exponent = NULL;
-    }
-    crWaitUntil(ispkt);
-
-    if (ssh->pktin.type != SSH1_SMSG_SUCCESS) {
-	bombout(("Encryption not successfully enabled"));
-	crStop(0);
-    }
-
-    logevent("Successfully started encryption");
-
-    fflush(stdout);
-    {
-	if ((flags & FLAG_INTERACTIVE) && !*ssh->cfg.username) {
-	    if (ssh_get_line && !ssh_getline_pw_only) {
-		if (!ssh_get_line("login as: ",
-				  s->username, sizeof(s->username), FALSE)) {
-		    /*
-		     * get_line failed to get a username.
-		     * Terminate.
-		     */
-		    logevent("No username provided. Abandoning session.");
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
-		    crStop(1);
-		}
-	    } else {
-		int ret;	       /* need not be kept over crReturn */
-		c_write_str(ssh, "login as: ");
-		ssh->send_ok = 1;
-
-		setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
-		do {
-		    crWaitUntil(!ispkt);
-		    ret = process_userpass_input(ssh, in, inlen);
-		} while (ret == 0);
-		if (ret < 0)
-		    cleanup_exit(0);
-		c_write_str(ssh, "\r\n");
-	    }
-	} else {
-	    strncpy(s->username, ssh->cfg.username, sizeof(s->username));
-	    s->username[sizeof(s->username)-1] = '\0';
-	}
-
-	send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END);
-	{
-	    char userlog[22 + sizeof(s->username)];
-	    sprintf(userlog, "Sent username \"%s\"", s->username);
-	    logevent(userlog);
-	    if (flags & FLAG_INTERACTIVE &&
-		(!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
-		strcat(userlog, "\r\n");
-		c_write_str(ssh, userlog);
-	    }
-	}
-    }
-
-    crWaitUntil(ispkt);
-
-    if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) {
-	/* We must not attempt PK auth. Pretend we've already tried it. */
-	s->tried_publickey = s->tried_agent = 1;
-    } else {
-	s->tried_publickey = s->tried_agent = 0;
-    }
-    s->tis_auth_refused = s->ccard_auth_refused = 0;
-    /* Load the public half of ssh->cfg.keyfile so we notice if it's in Pageant */
-    if (!filename_is_null(ssh->cfg.keyfile)) {
-	if (!rsakey_pubblob(&ssh->cfg.keyfile,
-			    &s->publickey_blob, &s->publickey_bloblen, NULL))
-	    s->publickey_blob = NULL;
-    } else
-	s->publickey_blob = NULL;
-
-    while (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-	s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
-
-	if (agent_exists() && !s->tried_agent) {
-	    /*
-	     * Attempt RSA authentication using Pageant.
-	     */
-	    void *r;
-
-	    s->authed = FALSE;
-	    s->tried_agent = 1;
-	    logevent("Pageant is running. Requesting keys.");
-
-	    /* Request the keys held by the agent. */
-	    PUT_32BIT(s->request, 1);
-	    s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
-	    if (!agent_query(s->request, 5, &r, &s->responselen,
-			     ssh_agent_callback, ssh)) {
-		do {
-		    crReturn(0);
-		    if (ispkt) {
-			bombout(("Unexpected data from server while waiting"
-				 " for agent response"));
-			crStop(0);
-		    }
-		} while (ispkt || inlen > 0);
-		r = ssh->agent_response;
-		s->responselen = ssh->agent_response_len;
-	    }
-	    s->response = (unsigned char *) r;
-	    if (s->response && s->responselen >= 5 &&
-		s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
-		s->p = s->response + 5;
-		s->nkeys = GET_32BIT(s->p);
-		s->p += 4;
-		{
-		    char buf[64];
-		    sprintf(buf, "Pageant has %d SSH1 keys", s->nkeys);
-		    logevent(buf);
-		}
-		for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
-		    {
-			char buf[64];
-			sprintf(buf, "Trying Pageant key #%d", s->keyi);
-			logevent(buf);
-		    }
-		    if (s->publickey_blob &&
-			!memcmp(s->p, s->publickey_blob,
-				s->publickey_bloblen)) {
-			logevent("This key matches configured key file");
-			s->tried_publickey = 1;
-		    }
-		    s->p += 4;
-		    s->p += ssh1_read_bignum(s->p, &s->key.exponent);
-		    s->p += ssh1_read_bignum(s->p, &s->key.modulus);
-		    s->commentlen = GET_32BIT(s->p);
-		    s->p += 4;
-		    s->commentp = (char *)s->p;
-		    s->p += s->commentlen;
-		    send_packet(ssh, SSH1_CMSG_AUTH_RSA,
-				PKT_BIGNUM, s->key.modulus, PKT_END);
-		    crWaitUntil(ispkt);
-		    if (ssh->pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
-			logevent("Key refused");
-			continue;
-		    }
-		    logevent("Received RSA challenge");
-		    ssh1_read_bignum(ssh->pktin.body, &s->challenge);
-		    {
-			char *agentreq, *q, *ret;
-			void *vret;
-			int len, retlen;
-			len = 1 + 4;   /* message type, bit count */
-			len += ssh1_bignum_length(s->key.exponent);
-			len += ssh1_bignum_length(s->key.modulus);
-			len += ssh1_bignum_length(s->challenge);
-			len += 16;     /* session id */
-			len += 4;      /* response format */
-			agentreq = snewn(4 + len, char);
-			PUT_32BIT(agentreq, len);
-			q = agentreq + 4;
-			*q++ = SSH1_AGENTC_RSA_CHALLENGE;
-			PUT_32BIT(q, bignum_bitcount(s->key.modulus));
-			q += 4;
-			q += ssh1_write_bignum(q, s->key.exponent);
-			q += ssh1_write_bignum(q, s->key.modulus);
-			q += ssh1_write_bignum(q, s->challenge);
-			memcpy(q, s->session_id, 16);
-			q += 16;
-			PUT_32BIT(q, 1);	/* response format */
-			if (!agent_query(agentreq, len + 4, &vret, &retlen,
-					 ssh_agent_callback, ssh)) {
-			    sfree(agentreq);
-			    do {
-				crReturn(0);
-				if (ispkt) {
-				    bombout(("Unexpected data from server"
-					     " while waiting for agent"
-					     " response"));
-				    crStop(0);
-				}
-			    } while (ispkt || inlen > 0);
-			    vret = ssh->agent_response;
-			    retlen = ssh->agent_response_len;
-			} else
-			    sfree(agentreq);
-			ret = vret;
-			if (ret) {
-			    if (ret[4] == SSH1_AGENT_RSA_RESPONSE) {
-				logevent("Sending Pageant's response");
-				send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
-					    PKT_DATA, ret + 5, 16,
-					    PKT_END);
-				sfree(ret);
-				crWaitUntil(ispkt);
-				if (ssh->pktin.type == SSH1_SMSG_SUCCESS) {
-				    logevent
-					("Pageant's response accepted");
-				    if (flags & FLAG_VERBOSE) {
-					c_write_str(ssh, "Authenticated using"
-						    " RSA key \"");
-					c_write(ssh, s->commentp,
-						s->commentlen);
-					c_write_str(ssh, "\" from agent\r\n");
-				    }
-				    s->authed = TRUE;
-				} else
-				    logevent
-					("Pageant's response not accepted");
-			    } else {
-				logevent
-				    ("Pageant failed to answer challenge");
-				sfree(ret);
-			    }
-			} else {
-			    logevent("No reply received from Pageant");
-			}
-		    }
-		    freebn(s->key.exponent);
-		    freebn(s->key.modulus);
-		    freebn(s->challenge);
-		    if (s->authed)
-			break;
-		}
-	    }
-	    if (s->authed)
-		break;
-	}
-	if (!filename_is_null(ssh->cfg.keyfile) && !s->tried_publickey)
-	    s->pwpkt_type = SSH1_CMSG_AUTH_RSA;
-
-	if (ssh->cfg.try_tis_auth &&
-	    (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
-	    !s->tis_auth_refused) {
-	    s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
-	    logevent("Requested TIS authentication");
-	    send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END);
-	    crWaitUntil(ispkt);
-	    if (ssh->pktin.type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
-		logevent("TIS authentication declined");
-		if (flags & FLAG_INTERACTIVE)
-		    c_write_str(ssh, "TIS authentication refused.\r\n");
-		s->tis_auth_refused = 1;
-		continue;
-	    } else {
-		int challengelen = GET_32BIT(ssh->pktin.body);
-		logevent("Received TIS challenge");
-		if (challengelen > sizeof(s->prompt) - 1)
-		    challengelen = sizeof(s->prompt) - 1;/* prevent overrun */
-		memcpy(s->prompt, ssh->pktin.body + 4, challengelen);
-		/* Prompt heuristic comes from OpenSSH */
-		strncpy(s->prompt + challengelen,
-		        memchr(s->prompt, '\n', challengelen) ?
-			"": "\r\nResponse: ",
-			(sizeof s->prompt) - challengelen);
-		s->prompt[(sizeof s->prompt) - 1] = '\0';
-	    }
-	}
-	if (ssh->cfg.try_tis_auth &&
-	    (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
-	    !s->ccard_auth_refused) {
-	    s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
-	    logevent("Requested CryptoCard authentication");
-	    send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END);
-	    crWaitUntil(ispkt);
-	    if (ssh->pktin.type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
-		logevent("CryptoCard authentication declined");
-		c_write_str(ssh, "CryptoCard authentication refused.\r\n");
-		s->ccard_auth_refused = 1;
-		continue;
-	    } else {
-		int challengelen = GET_32BIT(ssh->pktin.body);
-		logevent("Received CryptoCard challenge");
-		if (challengelen > sizeof(s->prompt) - 1)
-		    challengelen = sizeof(s->prompt) - 1;/* prevent overrun */
-		memcpy(s->prompt, ssh->pktin.body + 4, challengelen);
-		strncpy(s->prompt + challengelen,
-		        memchr(s->prompt, '\n', challengelen) ?
-			"" : "\r\nResponse: ",
-			sizeof(s->prompt) - challengelen);
-		s->prompt[sizeof(s->prompt) - 1] = '\0';
-	    }
-	}
-	if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
-	    sprintf(s->prompt, "%.90s@%.90s's password: ",
-		    s->username, ssh->savedhost);
-	}
-	if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) {
-	    char *comment = NULL;
-	    int type;
-	    char msgbuf[256];
-	    if (flags & FLAG_VERBOSE)
-		c_write_str(ssh, "Trying public key authentication.\r\n");
-	    logeventf(ssh, "Trying public key \"%s\"",
-		      filename_to_str(&ssh->cfg.keyfile));
-	    type = key_type(&ssh->cfg.keyfile);
-	    if (type != SSH_KEYTYPE_SSH1) {
-		sprintf(msgbuf, "Key is of wrong type (%s)",
-			key_type_to_str(type));
-		logevent(msgbuf);
-		c_write_str(ssh, msgbuf);
-		c_write_str(ssh, "\r\n");
-		s->tried_publickey = 1;
-		continue;
-	    }
-	    if (!rsakey_encrypted(&ssh->cfg.keyfile, &comment)) {
-		if (flags & FLAG_VERBOSE)
-		    c_write_str(ssh, "No passphrase required.\r\n");
-		goto tryauth;
-	    }
-	    sprintf(s->prompt, "Passphrase for key \"%.100s\": ", comment);
-	    sfree(comment);
-	}
-
-	/*
-	 * Show password prompt, having first obtained it via a TIS
-	 * or CryptoCard exchange if we're doing TIS or CryptoCard
-	 * authentication.
-	 */
-	if (ssh_get_line) {
-	    if (!ssh_get_line(s->prompt, s->password,
-			      sizeof(s->password), TRUE)) {
-		/*
-		 * get_line failed to get a password (for example
-		 * because one was supplied on the command line
-		 * which has already failed to work). Terminate.
-		 */
-		send_packet(ssh, SSH1_MSG_DISCONNECT,
-			    PKT_STR, "No more passwords available to try",
-			    PKT_END);
-		logevent("Unable to authenticate");
-		connection_fatal(ssh->frontend, "Unable to authenticate");
-                ssh_closing((Plug)ssh, NULL, 0, 0);
-		crStop(1);
-	    }
-	} else {
-	    /* Prompt may have come from server. We've munged it a bit, so
-	     * we know it to be zero-terminated at least once. */
-	    int ret;		       /* need not be saved over crReturn */
-	    c_write_untrusted(ssh, s->prompt, strlen(s->prompt));
-	    s->pos = 0;
-
-	    setup_userpass_input(ssh, s->password, sizeof(s->password), 0);
-	    do {
-		crWaitUntil(!ispkt);
-		ret = process_userpass_input(ssh, in, inlen);
-	    } while (ret == 0);
-	    if (ret < 0)
-		cleanup_exit(0);
-	    c_write_str(ssh, "\r\n");
-	}
-
-      tryauth:
-	if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) {
-	    /*
-	     * Try public key authentication with the specified
-	     * key file.
-	     */
-	    s->tried_publickey = 1;
-	    
-	    {
-		const char *error = NULL;
-		int ret = loadrsakey(&ssh->cfg.keyfile, &s->key, s->password,
-				     &error);
-		if (ret == 0) {
-		    c_write_str(ssh, "Couldn't load private key from ");
-		    c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile));
-		    c_write_str(ssh, " (");
-		    c_write_str(ssh, error);
-		    c_write_str(ssh, ").\r\n");
-		    continue;	       /* go and try password */
-		}
-		if (ret == -1) {
-		    c_write_str(ssh, "Wrong passphrase.\r\n");
-		    s->tried_publickey = 0;
-		    continue;	       /* try again */
-		}
-	    }
-
-	    /*
-	     * Send a public key attempt.
-	     */
-	    send_packet(ssh, SSH1_CMSG_AUTH_RSA,
-			PKT_BIGNUM, s->key.modulus, PKT_END);
-
-	    crWaitUntil(ispkt);
-	    if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-		c_write_str(ssh, "Server refused our public key.\r\n");
-		continue;	       /* go and try password */
-	    }
-	    if (ssh->pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
-		bombout(("Bizarre response to offer of public key"));
-		crStop(0);
-	    }
-
-	    {
-		int i;
-		unsigned char buffer[32];
-		Bignum challenge, response;
-
-		ssh1_read_bignum(ssh->pktin.body, &challenge);
-		response = rsadecrypt(challenge, &s->key);
-		freebn(s->key.private_exponent);/* burn the evidence */
-
-		for (i = 0; i < 32; i++) {
-		    buffer[i] = bignum_byte(response, 31 - i);
-		}
-
-		MD5Init(&md5c);
-		MD5Update(&md5c, buffer, 32);
-		MD5Update(&md5c, s->session_id, 16);
-		MD5Final(buffer, &md5c);
-
-		send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
-			    PKT_DATA, buffer, 16, PKT_END);
-
-		freebn(challenge);
-		freebn(response);
-	    }
-
-	    crWaitUntil(ispkt);
-	    if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-		if (flags & FLAG_VERBOSE)
-		    c_write_str(ssh, "Failed to authenticate with"
-				" our public key.\r\n");
-		continue;	       /* go and try password */
-	    } else if (ssh->pktin.type != SSH1_SMSG_SUCCESS) {
-		bombout(("Bizarre response to RSA authentication response"));
-		crStop(0);
-	    }
-
-	    break;		       /* we're through! */
-	} else {
-	    if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
-		/*
-		 * Defence against traffic analysis: we send a
-		 * whole bunch of packets containing strings of
-		 * different lengths. One of these strings is the
-		 * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
-		 * The others are all random data in
-		 * SSH1_MSG_IGNORE packets. This way a passive
-		 * listener can't tell which is the password, and
-		 * hence can't deduce the password length.
-		 * 
-		 * Anybody with a password length greater than 16
-		 * bytes is going to have enough entropy in their
-		 * password that a listener won't find it _that_
-		 * much help to know how long it is. So what we'll
-		 * do is:
-		 * 
-		 *  - if password length < 16, we send 15 packets
-		 *    containing string lengths 1 through 15
-		 * 
-		 *  - otherwise, we let N be the nearest multiple
-		 *    of 8 below the password length, and send 8
-		 *    packets containing string lengths N through
-		 *    N+7. This won't obscure the order of
-		 *    magnitude of the password length, but it will
-		 *    introduce a bit of extra uncertainty.
-		 * 
-		 * A few servers (the old 1.2.18 through 1.2.22)
-		 * can't deal with SSH1_MSG_IGNORE. For these
-		 * servers, we need an alternative defence. We make
-		 * use of the fact that the password is interpreted
-		 * as a C string: so we can append a NUL, then some
-		 * random data.
-		 * 
-		 * One server (a Cisco one) can deal with neither
-		 * SSH1_MSG_IGNORE _nor_ a padded password string.
-		 * For this server we are left with no defences
-		 * against password length sniffing.
-		 */
-		if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
-		    /*
-		     * The server can deal with SSH1_MSG_IGNORE, so
-		     * we can use the primary defence.
-		     */
-		    int bottom, top, pwlen, i;
-		    char *randomstr;
-
-		    pwlen = strlen(s->password);
-		    if (pwlen < 16) {
-			bottom = 0;    /* zero length passwords are OK! :-) */
-			top = 15;
-		    } else {
-			bottom = pwlen & ~7;
-			top = bottom + 7;
-		    }
-
-		    assert(pwlen >= bottom && pwlen <= top);
-
-		    randomstr = snewn(top + 1, char);
-
-		    for (i = bottom; i <= top; i++) {
-			if (i == pwlen)
-			    defer_packet(ssh, s->pwpkt_type,
-					 PKT_STR, s->password, PKT_END);
-			else {
-			    for (j = 0; j < i; j++) {
-				do {
-				    randomstr[j] = random_byte();
-				} while (randomstr[j] == '\0');
-			    }
-			    randomstr[i] = '\0';
-			    defer_packet(ssh, SSH1_MSG_IGNORE,
-					 PKT_STR, randomstr, PKT_END);
-			}
-		    }
-		    logevent("Sending password with camouflage packets");
-		    ssh_pkt_defersend(ssh);
-		    sfree(randomstr);
-		} 
-		else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
-		    /*
-		     * The server can't deal with SSH1_MSG_IGNORE
-		     * but can deal with padded passwords, so we
-		     * can use the secondary defence.
-		     */
-		    char string[64];
-		    char *ss;
-		    int len;
-
-		    len = strlen(s->password);
-		    if (len < sizeof(string)) {
-			ss = string;
-			strcpy(string, s->password);
-			len++;	       /* cover the zero byte */
-			while (len < sizeof(string)) {
-			    string[len++] = (char) random_byte();
-			}
-		    } else {
-			ss = s->password;
-		    }
-		    logevent("Sending length-padded password");
-		    send_packet(ssh, s->pwpkt_type, PKT_INT, len,
-				PKT_DATA, ss, len, PKT_END);
-		} else {
-		    /*
-		     * The server has _both_
-		     * BUG_CHOKES_ON_SSH1_IGNORE and
-		     * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
-		     * therefore nothing we can do.
-		     */
-		    int len;
-		    len = strlen(s->password);
-		    logevent("Sending unpadded password");
-		    send_packet(ssh, s->pwpkt_type, PKT_INT, len,
-				PKT_DATA, s->password, len, PKT_END);
-		}
-	    } else {
-		send_packet(ssh, s->pwpkt_type, PKT_STR, s->password, PKT_END);
-	    }
-	}
-	logevent("Sent password");
-	memset(s->password, 0, strlen(s->password));
-	crWaitUntil(ispkt);
-	if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-	    if (flags & FLAG_VERBOSE)
-		c_write_str(ssh, "Access denied\r\n");
-	    logevent("Authentication refused");
-	} else if (ssh->pktin.type != SSH1_SMSG_SUCCESS) {
-	    bombout(("Strange packet received, type %d", ssh->pktin.type));
-	    crStop(0);
-	}
-    }
-
-    logevent("Authentication successful");
-
-    crFinish(1);
-}
-
-void sshfwd_close(struct ssh_channel *c)
-{
-    Ssh ssh = c->ssh;
-
-    if (ssh->state != SSH_STATE_SESSION) {
-	assert(ssh->state == SSH_STATE_CLOSED);
-	return;
-    }
-
-    if (c && !c->closes) {
-	/*
-	 * If the channel's remoteid is -1, we have sent
-	 * CHANNEL_OPEN for this channel, but it hasn't even been
-	 * acknowledged by the server. So we must set a close flag
-	 * on it now, and then when the server acks the channel
-	 * open, we can close it then.
-	 */
-	if (((int)c->remoteid) != -1) {
-	    if (ssh->version == 1) {
-		send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
-			    PKT_END);
-	    } else {
-		ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
-		ssh2_pkt_adduint32(ssh, c->remoteid);
-		ssh2_pkt_send(ssh);
-	    }
-	}
-	c->closes = 1;		       /* sent MSG_CLOSE */
-	if (c->type == CHAN_X11) {
-	    c->u.x11.s = NULL;
-	    logevent("Forwarded X11 connection terminated");
-	} else if (c->type == CHAN_SOCKDATA ||
-		   c->type == CHAN_SOCKDATA_DORMANT) {
-	    c->u.pfd.s = NULL;
-	    logevent("Forwarded port closed");
-	}
-    }
-}
-
-int sshfwd_write(struct ssh_channel *c, char *buf, int len)
-{
-    Ssh ssh = c->ssh;
-
-    if (ssh->state != SSH_STATE_SESSION) {
-	assert(ssh->state == SSH_STATE_CLOSED);
-	return 0;
-    }
-
-    if (ssh->version == 1) {
-	send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
-		    PKT_INT, c->remoteid,
-		    PKT_INT, len, PKT_DATA, buf, len, PKT_END);
-	/*
-	 * In SSH1 we can return 0 here - implying that forwarded
-	 * connections are never individually throttled - because
-	 * the only circumstance that can cause throttling will be
-	 * the whole SSH connection backing up, in which case
-	 * _everything_ will be throttled as a whole.
-	 */
-	return 0;
-    } else {
-	ssh2_add_channel_data(c, buf, len);
-	return ssh2_try_send(c);
-    }
-}
-
-void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
-{
-    Ssh ssh = c->ssh;
-
-    if (ssh->state != SSH_STATE_SESSION) {
-	assert(ssh->state == SSH_STATE_CLOSED);
-	return;
-    }
-
-    if (ssh->version == 1) {
-	if (c->v.v1.throttling && bufsize < SSH1_BUFFER_LIMIT) {
-	    c->v.v1.throttling = 0;
-	    ssh1_throttle(ssh, -1);
-	}
-    } else {
-	ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
-    }
-}
-
-static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt)
-{
-    crBegin(ssh->ssh1_protocol_crstate);
-
-    random_init();
-
-    while (!do_ssh1_login(ssh, in, inlen, ispkt)) {
-	crReturnV;
-    }
-    if (ssh->state == SSH_STATE_CLOSED)
-	crReturnV;
-
-    if (ssh->cfg.agentfwd && agent_exists()) {
-	logevent("Requesting agent forwarding");
-	send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
-	do {
-	    crReturnV;
-	} while (!ispkt);
-	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
-	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
-	    bombout(("Protocol confusion"));
-	    crStopV;
-	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-	    logevent("Agent forwarding refused");
-	} else {
-	    logevent("Agent forwarding enabled");
-	    ssh->agentfwd_enabled = TRUE;
-	}
-    }
-
-    if (ssh->cfg.x11_forward) {
-	char proto[20], data[64];
-	logevent("Requesting X11 forwarding");
-	ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
-				       data, sizeof(data), ssh->cfg.x11_auth);
-        x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display);
-	if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
-	    send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-			PKT_STR, proto, PKT_STR, data,
-			PKT_INT, x11_get_screen_number(ssh->cfg.x11_display),
-			PKT_END);
-	} else {
-	    send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-			PKT_STR, proto, PKT_STR, data, PKT_END);
-	}
-	do {
-	    crReturnV;
-	} while (!ispkt);
-	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
-	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
-	    bombout(("Protocol confusion"));
-	    crStopV;
-	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-	    logevent("X11 forwarding refused");
-	} else {
-	    logevent("X11 forwarding enabled");
-	    ssh->X11_fwd_enabled = TRUE;
-	}
-    }
-
-    {
-	char type;
-	int n;
-	int sport,dport,sserv,dserv;
-	char sports[256], dports[256], saddr[256], host[256];
-
-	ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
-        /* Add port forwardings. */
-	ssh->portfwd_strptr = ssh->cfg.portfwd;
-	while (*ssh->portfwd_strptr) {
-	    type = *ssh->portfwd_strptr++;
-	    saddr[0] = '\0';
-	    n = 0;
-	    while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') {
-		if (*ssh->portfwd_strptr == ':') {
-		    /*
-		     * We've seen a colon in the middle of the
-		     * source port number. This means that
-		     * everything we've seen until now is the
-		     * source _address_, so we'll move it into
-		     * saddr and start sports from the beginning
-		     * again.
-		     */
-		    ssh->portfwd_strptr++;
-		    sports[n] = '\0';
-		    strcpy(saddr, sports);
-		    n = 0;
-		}
-		if (n < 255) sports[n++] = *ssh->portfwd_strptr++;
-	    }
-	    sports[n] = 0;
-	    if (type != 'D') {
-		if (*ssh->portfwd_strptr == '\t')
-		    ssh->portfwd_strptr++;
-		n = 0;
-		while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') {
-		    if (n < 255) host[n++] = *ssh->portfwd_strptr++;
-		}
-		host[n] = 0;
-		if (*ssh->portfwd_strptr == ':')
-		    ssh->portfwd_strptr++;
-		n = 0;
-		while (*ssh->portfwd_strptr) {
-		    if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
-		}
-		dports[n] = 0;
-		ssh->portfwd_strptr++;
-		dport = atoi(dports);
-		dserv = 0;
-		if (dport == 0) {
-		    dserv = 1;
-		    dport = net_service_lookup(dports);
-		    if (!dport) {
-			logeventf(ssh, "Service lookup failed for"
-				  " destination port \"%s\"", dports);
-		    }
-		}
-	    } else {
-		while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
-		dport = dserv = -1;
-		ssh->portfwd_strptr++; /* eat the NUL and move to next one */
-	    }
-	    sport = atoi(sports);
-	    sserv = 0;
-	    if (sport == 0) {
-		sserv = 1;
-		sport = net_service_lookup(sports);
-		if (!sport) {
-		    logeventf(ssh, "Service lookup failed for source"
-			      " port \"%s\"", sports);
-		}
-	    }
-	    if (sport && dport) {
-		if (type == 'L') {
-		    pfd_addforward(host, dport, *saddr ? saddr : NULL,
-				   sport, ssh, &ssh->cfg);
-		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
-			      " forwarding to %s:%.*s%.*s%d%.*s",
-			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
-			      (int)(*saddr?1:0), ":",
-			      (int)(sserv ? strlen(sports) : 0), sports,
-			      sserv, "(", sport, sserv, ")",
-			      host,
-			      (int)(dserv ? strlen(dports) : 0), dports,
-			      dserv, "(", dport, dserv, ")");
-		} else if (type == 'D') {
-		    pfd_addforward(NULL, -1, *saddr ? saddr : NULL,
-				   sport, ssh, &ssh->cfg);
-		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
-			      " doing SOCKS dynamic forwarding",
-			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
-			      (int)(*saddr?1:0), ":",
-			      (int)(sserv ? strlen(sports) : 0), sports,
-			      sserv, "(", sport, sserv, ")");
-		} else {
-		    struct ssh_rportfwd *pf;
-		    pf = snew(struct ssh_rportfwd);
-		    strcpy(pf->dhost, host);
-		    pf->dport = dport;
-		    if (saddr) {
-			logeventf(ssh,
-				  "SSH1 cannot handle source address spec \"%s:%d\"; ignoring",
-				  saddr, sport);
-		    }
-		    if (add234(ssh->rportfwds, pf) != pf) {
-			logeventf(ssh, 
-				  "Duplicate remote port forwarding to %s:%d",
-				  host, dport);
-			sfree(pf);
-		    } else {
-			logeventf(ssh, "Requesting remote port %.*s%.*s%d%.*s"
-				  " forward to %s:%.*s%.*s%d%.*s",
-				  (int)(sserv ? strlen(sports) : 0), sports,
-				  sserv, "(", sport, sserv, ")",
-				  host,
-				  (int)(dserv ? strlen(dports) : 0), dports,
-				  dserv, "(", dport, dserv, ")");
-			send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
-				    PKT_INT, sport,
-				    PKT_STR, host,
-				    PKT_INT, dport,
-				    PKT_END);
-			do {
-			    crReturnV;
-			} while (!ispkt);
-			if (ssh->pktin.type != SSH1_SMSG_SUCCESS
-			    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
-			    bombout(("Protocol confusion"));
-			    crStopV;
-			} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-			    c_write_str(ssh, "Server refused port"
-					" forwarding\r\n");
-			}
-			logevent("Remote port forwarding enabled");
-		    }
-		}
-	    }
-	}
-    }
-
-    if (!ssh->cfg.nopty) {
-	send_packet(ssh, SSH1_CMSG_REQUEST_PTY,
-		    PKT_STR, ssh->cfg.termtype,
-		    PKT_INT, ssh->term_height,
-		    PKT_INT, ssh->term_width,
-		    PKT_INT, 0, PKT_INT, 0, PKT_CHAR, 0, PKT_END);
-	ssh->state = SSH_STATE_INTERMED;
-	do {
-	    crReturnV;
-	} while (!ispkt);
-	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
-	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
-	    bombout(("Protocol confusion"));
-	    crStopV;
-	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-	    c_write_str(ssh, "Server refused to allocate pty\r\n");
-	    ssh->editing = ssh->echoing = 1;
-	}
-	logevent("Allocated pty");
-    } else {
-	ssh->editing = ssh->echoing = 1;
-    }
-
-    if (ssh->cfg.compression) {
-	send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
-	do {
-	    crReturnV;
-	} while (!ispkt);
-	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
-	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
-	    bombout(("Protocol confusion"));
-	    crStopV;
-	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-	    c_write_str(ssh, "Server refused to compress\r\n");
-	}
-	logevent("Started compression");
-	ssh->v1_compressing = TRUE;
-	ssh->cs_comp_ctx = zlib_compress_init();
-	logevent("Initialised zlib (RFC1950) compression");
-	ssh->sc_comp_ctx = zlib_decompress_init();
-	logevent("Initialised zlib (RFC1950) decompression");
-    }
-
-    /*
-     * Start the shell or command.
-     * 
-     * Special case: if the first-choice command is an SSH2
-     * subsystem (hence not usable here) and the second choice
-     * exists, we fall straight back to that.
-     */
-    {
-	char *cmd = ssh->cfg.remote_cmd_ptr;
-	
-	if (ssh->cfg.ssh_subsys && ssh->cfg.remote_cmd_ptr2) {
-	    cmd = ssh->cfg.remote_cmd_ptr2;
-	    ssh->fallback_cmd = TRUE;
-	}
-	if (*cmd)
-	    send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
-	else
-	    send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END);
-	logevent("Started session");
-    }
-
-    ssh->state = SSH_STATE_SESSION;
-    if (ssh->size_needed)
-	ssh_size(ssh, ssh->term_width, ssh->term_height);
-    if (ssh->eof_needed)
-	ssh_special(ssh, TS_EOF);
-
-    if (ssh->ldisc)
-	ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
-    ssh->send_ok = 1;
-    ssh->channels = newtree234(ssh_channelcmp);
-    while (1) {
-	crReturnV;
-	if (ispkt) {
-	    if (ssh->pktin.type == SSH1_SMSG_STDOUT_DATA ||
-		ssh->pktin.type == SSH1_SMSG_STDERR_DATA) {
-		long len = GET_32BIT(ssh->pktin.body);
-		int bufsize =
-		    from_backend(ssh->frontend,
-				 ssh->pktin.type == SSH1_SMSG_STDERR_DATA,
-				 (char *)(ssh->pktin.body) + 4, len);
-		if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
-		    ssh->v1_stdout_throttling = 1;
-		    ssh1_throttle(ssh, +1);
-		}
-	    } else if (ssh->pktin.type == SSH1_MSG_DISCONNECT) {
-                ssh_closing((Plug)ssh, NULL, 0, 0);
-		logevent("Received disconnect request");
-		crStopV;
-	    } else if (ssh->pktin.type == SSH1_SMSG_X11_OPEN) {
-		/* Remote side is trying to open a channel to talk to our
-		 * X-Server. Give them back a local channel number. */
-		struct ssh_channel *c;
-
-		logevent("Received X11 connect request");
-		/* Refuse if X11 forwarding is disabled. */
-		if (!ssh->X11_fwd_enabled) {
-		    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
-				PKT_INT, GET_32BIT(ssh->pktin.body), PKT_END);
-		    logevent("Rejected X11 connect request");
-		} else {
-		    c = snew(struct ssh_channel);
-		    c->ssh = ssh;
-
-		    if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c,
-				 ssh->x11auth, NULL, -1, &ssh->cfg) != NULL) {
-			logevent("opening X11 forward connection failed");
-			sfree(c);
-			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
-				    PKT_INT, GET_32BIT(ssh->pktin.body),
-				    PKT_END);
-		    } else {
-			logevent
-			    ("opening X11 forward connection succeeded");
-			c->remoteid = GET_32BIT(ssh->pktin.body);
-			c->localid = alloc_channel_id(ssh);
-			c->closes = 0;
-			c->v.v1.throttling = 0;
-			c->type = CHAN_X11;	/* identify channel type */
-			add234(ssh->channels, c);
-			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
-				    PKT_INT, c->remoteid, PKT_INT,
-				    c->localid, PKT_END);
-			logevent("Opened X11 forward channel");
-		    }
-		}
-	    } else if (ssh->pktin.type == SSH1_SMSG_AGENT_OPEN) {
-		/* Remote side is trying to open a channel to talk to our
-		 * agent. Give them back a local channel number. */
-		struct ssh_channel *c;
-
-		/* Refuse if agent forwarding is disabled. */
-		if (!ssh->agentfwd_enabled) {
-		    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
-				PKT_INT, GET_32BIT(ssh->pktin.body), PKT_END);
-		} else {
-		    c = snew(struct ssh_channel);
-		    c->ssh = ssh;
-		    c->remoteid = GET_32BIT(ssh->pktin.body);
-		    c->localid = alloc_channel_id(ssh);
-		    c->closes = 0;
-		    c->v.v1.throttling = 0;
-		    c->type = CHAN_AGENT;	/* identify channel type */
-		    c->u.a.lensofar = 0;
-		    add234(ssh->channels, c);
-		    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
-				PKT_INT, c->remoteid, PKT_INT, c->localid,
-				PKT_END);
-		}
-	    } else if (ssh->pktin.type == SSH1_MSG_PORT_OPEN) {
-   		/* Remote side is trying to open a channel to talk to a
-		 * forwarded port. Give them back a local channel number. */
-		struct ssh_channel *c;
-		struct ssh_rportfwd pf;
-		int hostsize, port;
-		char host[256], buf[1024];
-		char *p, *h;
-		const char *e;
-		c = snew(struct ssh_channel);
-		c->ssh = ssh;
-
-		hostsize = GET_32BIT(ssh->pktin.body+4);
-		for (h = host, p = (char *)(ssh->pktin.body+8);
-		     hostsize != 0; hostsize--) {
-		    if (h+1 < host+sizeof(host))
-			*h++ = *p;
-		    p++;
-		}
-		*h = 0;
-		port = GET_32BIT(p);
-
-		strcpy(pf.dhost, host);
-		pf.dport = port;
-
-		if (find234(ssh->rportfwds, &pf, NULL) == NULL) {
-		    sprintf(buf, "Rejected remote port open request for %s:%d",
-			    host, port);
-		    logevent(buf);
-                    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
-                                PKT_INT, GET_32BIT(ssh->pktin.body), PKT_END);
-		} else {
-		    sprintf(buf, "Received remote port open request for %s:%d",
-			    host, port);
-		    logevent(buf);
-		    e = pfd_newconnect(&c->u.pfd.s, host, port, c, &ssh->cfg);
-		    if (e != NULL) {
-			char buf[256];
-			sprintf(buf, "Port open failed: %s", e);
-			logevent(buf);
-			sfree(c);
-			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
-				    PKT_INT, GET_32BIT(ssh->pktin.body),
-				    PKT_END);
-		    } else {
-			c->remoteid = GET_32BIT(ssh->pktin.body);
-			c->localid = alloc_channel_id(ssh);
-			c->closes = 0;
-			c->v.v1.throttling = 0;
-			c->type = CHAN_SOCKDATA;	/* identify channel type */
-			add234(ssh->channels, c);
-			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
-				    PKT_INT, c->remoteid, PKT_INT,
-				    c->localid, PKT_END);
-			logevent("Forwarded port opened successfully");
-		    }
-		}
-
-	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION) {
-		unsigned int remoteid = GET_32BIT(ssh->pktin.body);
-		unsigned int localid = GET_32BIT(ssh->pktin.body+4);
-		struct ssh_channel *c;
-
-		c = find234(ssh->channels, &remoteid, ssh_channelfind);
-		if (c && c->type == CHAN_SOCKDATA_DORMANT) {
-		    c->remoteid = localid;
-		    c->type = CHAN_SOCKDATA;
-		    c->v.v1.throttling = 0;
-		    pfd_confirm(c->u.pfd.s);
-		}
-
-		if (c && c->closes) {
-		    /*
-		     * We have a pending close on this channel,
-		     * which we decided on before the server acked
-		     * the channel open. So now we know the
-		     * remoteid, we can close it again.
-		     */
-		    send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE,
-				PKT_INT, c->remoteid, PKT_END);
-		}
-
-	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) {
-		unsigned int remoteid = GET_32BIT(ssh->pktin.body);
-		struct ssh_channel *c;
-
-		c = find234(ssh->channels, &remoteid, ssh_channelfind);
-		if (c && c->type == CHAN_SOCKDATA_DORMANT) {
-		    logevent("Forwarded connection refused by server");
-		    pfd_close(c->u.pfd.s);
-		    del234(ssh->channels, c);
-		    sfree(c);
-		}
-
-	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
-		       ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
-		/* Remote side closes a channel. */
-		unsigned i = GET_32BIT(ssh->pktin.body);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (c && ((int)c->remoteid) != -1) {
-		    int closetype;
-		    closetype =
-			(ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
-
-		    if ((c->closes == 0) && (c->type == CHAN_X11)) {
-			logevent("Forwarded X11 connection terminated");
-			assert(c->u.x11.s != NULL);
-			x11_close(c->u.x11.s);
-			c->u.x11.s = NULL;
-		    }
-		    if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) {
-			logevent("Forwarded port closed");
-			assert(c->u.pfd.s != NULL);
-			pfd_close(c->u.pfd.s);
-			c->u.pfd.s = NULL;
-		    }
-
-		    c->closes |= (closetype << 2);   /* seen this message */
-		    if (!(c->closes & closetype)) {
-			send_packet(ssh, ssh->pktin.type, PKT_INT, c->remoteid,
-				    PKT_END);
-			c->closes |= closetype;      /* sent it too */
-		    }
-
-		    if (c->closes == 15) {
-			del234(ssh->channels, c);
-			sfree(c);
-		    }
-		} else {
-		    bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
-			     ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE ? "" :
-			     "_CONFIRMATION", c ? "half-open" : "nonexistent",
-			     i));
-		    crStopV;
-		}
-	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_DATA) {
-		/* Data sent down one of our channels. */
-		int i = GET_32BIT(ssh->pktin.body);
-		int len = GET_32BIT(ssh->pktin.body + 4);
-		unsigned char *p = ssh->pktin.body + 8;
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (c) {
-		    int bufsize = 0;
-		    switch (c->type) {
-		      case CHAN_X11:
-			bufsize = x11_send(c->u.x11.s, (char *)p, len);
-			break;
-		      case CHAN_SOCKDATA:
-			bufsize = pfd_send(c->u.pfd.s, (char *)p, len);
-			break;
-		      case CHAN_AGENT:
-			/* Data for an agent message. Buffer it. */
-			while (len > 0) {
-			    if (c->u.a.lensofar < 4) {
-				int l = min(4 - c->u.a.lensofar, len);
-				memcpy(c->u.a.msglen + c->u.a.lensofar, p,
-				       l);
-				p += l;
-				len -= l;
-				c->u.a.lensofar += l;
-			    }
-			    if (c->u.a.lensofar == 4) {
-				c->u.a.totallen =
-				    4 + GET_32BIT(c->u.a.msglen);
-				c->u.a.message = snewn(c->u.a.totallen,
-						       unsigned char);
-				memcpy(c->u.a.message, c->u.a.msglen, 4);
-			    }
-			    if (c->u.a.lensofar >= 4 && len > 0) {
-				int l =
-				    min(c->u.a.totallen - c->u.a.lensofar,
-					len);
-				memcpy(c->u.a.message + c->u.a.lensofar, p,
-				       l);
-				p += l;
-				len -= l;
-				c->u.a.lensofar += l;
-			    }
-			    if (c->u.a.lensofar == c->u.a.totallen) {
-				void *reply;
-				int replylen;
-				if (agent_query(c->u.a.message,
-						c->u.a.totallen,
-						&reply, &replylen,
-						ssh_agentf_callback, c))
-				    ssh_agentf_callback(c, reply, replylen);
-				sfree(c->u.a.message);
-				c->u.a.lensofar = 0;
-			    }
-			}
-			bufsize = 0;   /* agent channels never back up */
-			break;
-		    }
-		    if (!c->v.v1.throttling && bufsize > SSH1_BUFFER_LIMIT) {
-			c->v.v1.throttling = 1;
-			ssh1_throttle(ssh, +1);
-		    }
-		}
-	    } else if (ssh->pktin.type == SSH1_SMSG_SUCCESS) {
-		/* may be from EXEC_SHELL on some servers */
-	    } else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
-		/* may be from EXEC_SHELL on some servers
-		 * if no pty is available or in other odd cases. Ignore */
-	    } else if (ssh->pktin.type == SSH1_SMSG_EXIT_STATUS) {
-		char buf[100];
-		ssh->exitcode = GET_32BIT(ssh->pktin.body);
-		sprintf(buf, "Server sent command exit status %d",
-			ssh->exitcode);
-		logevent(buf);
-		send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
-                /*
-                 * In case `helpful' firewalls or proxies tack
-                 * extra human-readable text on the end of the
-                 * session which we might mistake for another
-                 * encrypted packet, we close the session once
-                 * we've sent EXIT_CONFIRMATION.
-                 */
-                ssh_closing((Plug)ssh, NULL, 0, 0);
-                crStopV;
-	    } else {
-		bombout(("Strange packet received: type %d", ssh->pktin.type));
-		crStopV;
-	    }
-	} else {
-	    while (inlen > 0) {
-		int len = min(inlen, 512);
-		send_packet(ssh, SSH1_CMSG_STDIN_DATA,
-			    PKT_INT, len, PKT_DATA, in, len, PKT_END);
-		in += len;
-		inlen -= len;
-	    }
-	}
-    }
-
-    crFinishV;
-}
-
-/*
- * Utility routine for decoding comma-separated strings in KEXINIT.
- */
-static int in_commasep_string(char *needle, char *haystack, int haylen)
-{
-    int needlen;
-    if (!needle || !haystack)	       /* protect against null pointers */
-	return 0;
-    needlen = strlen(needle);
-    while (1) {
-	/*
-	 * Is it at the start of the string?
-	 */
-	if (haylen >= needlen &&       /* haystack is long enough */
-	    !memcmp(needle, haystack, needlen) &&	/* initial match */
-	    (haylen == needlen || haystack[needlen] == ',')
-	    /* either , or EOS follows */
-	    )
-	    return 1;
-	/*
-	 * If not, search for the next comma and resume after that.
-	 * If no comma found, terminate.
-	 */
-	while (haylen > 0 && *haystack != ',')
-	    haylen--, haystack++;
-	if (haylen == 0)
-	    return 0;
-	haylen--, haystack++;	       /* skip over comma itself */
-    }
-}
-
-/*
- * SSH2 key creation method.
- */
-static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
-		       unsigned char *sessid, char chr,
-		       unsigned char *keyspace)
-{
-    SHA_State s;
-    /* First 20 bytes. */
-    SHA_Init(&s);
-    if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
-	sha_mpint(&s, K);
-    SHA_Bytes(&s, H, 20);
-    SHA_Bytes(&s, &chr, 1);
-    SHA_Bytes(&s, sessid, 20);
-    SHA_Final(&s, keyspace);
-    /* Next 20 bytes. */
-    SHA_Init(&s);
-    if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
-	sha_mpint(&s, K);
-    SHA_Bytes(&s, H, 20);
-    SHA_Bytes(&s, keyspace, 20);
-    SHA_Final(&s, keyspace + 20);
-}
-
-/*
- * Handle the SSH2 transport layer.
- */
-static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, int ispkt)
-{
-    struct do_ssh2_transport_state {
-	int nbits, pbits, warn;
-	Bignum p, g, e, f, K;
-	int kex_init_value, kex_reply_value;
-	const struct ssh_mac **maclist;
-	int nmacs;
-	const struct ssh2_cipher *cscipher_tobe;
-	const struct ssh2_cipher *sccipher_tobe;
-	const struct ssh_mac *csmac_tobe;
-	const struct ssh_mac *scmac_tobe;
-	const struct ssh_compress *cscomp_tobe;
-	const struct ssh_compress *sccomp_tobe;
-	char *hostkeydata, *sigdata, *keystr, *fingerprint;
-	int hostkeylen, siglen;
-	void *hkey;		       /* actual host key */
-	unsigned char exchange_hash[20];
-	int n_preferred_ciphers;
-	const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
-	const struct ssh_compress *preferred_comp;
-	int first_kex;
-    };
-    crState(do_ssh2_transport_state);
-
-    crBegin(ssh->do_ssh2_transport_crstate);
-
-    s->cscipher_tobe = s->sccipher_tobe = NULL;
-    s->csmac_tobe = s->scmac_tobe = NULL;
-    s->cscomp_tobe = s->sccomp_tobe = NULL;
-
-    random_init();
-    s->first_kex = 1;
-
-    {
-	int i;
-	/*
-	 * Set up the preferred ciphers. (NULL => warn below here)
-	 */
-	s->n_preferred_ciphers = 0;
-	for (i = 0; i < CIPHER_MAX; i++) {
-	    switch (ssh->cfg.ssh_cipherlist[i]) {
-	      case CIPHER_BLOWFISH:
-		s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish;
-		break;
-	      case CIPHER_DES:
-		if (ssh->cfg.ssh2_des_cbc) {
-		    s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des;
-		}
-		break;
-	      case CIPHER_3DES:
-		s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des;
-		break;
-	      case CIPHER_AES:
-		s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes;
-		break;
-	      case CIPHER_WARN:
-		/* Flag for later. Don't bother if it's the last in
-		 * the list. */
-		if (i < CIPHER_MAX - 1) {
-		    s->preferred_ciphers[s->n_preferred_ciphers++] = NULL;
-		}
-		break;
-	    }
-	}
-    }
-
-    /*
-     * Set up preferred compression.
-     */
-    if (ssh->cfg.compression)
-	s->preferred_comp = &ssh_zlib;
-    else
-	s->preferred_comp = &ssh_comp_none;
-
-    /*
-     * Be prepared to work around the buggy MAC problem.
-     */
-    if (ssh->remote_bugs & BUG_SSH2_HMAC)
-	s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
-    else
-	s->maclist = macs, s->nmacs = lenof(macs);
-
-  begin_key_exchange:
-    {
-	int i, j, cipherstr_started;
-
-	/*
-	 * Construct and send our key exchange packet.
-	 */
-	ssh2_pkt_init(ssh, SSH2_MSG_KEXINIT);
-	for (i = 0; i < 16; i++)
-	    ssh2_pkt_addbyte(ssh, (unsigned char) random_byte());
-	/* List key exchange algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	for (i = 0; i < lenof(kex_algs); i++) {
-	    if (kex_algs[i] == &ssh_diffiehellman_gex &&
-		(ssh->remote_bugs & BUG_SSH2_DH_GEX))
-		continue;
-	    ssh2_pkt_addstring_str(ssh, kex_algs[i]->name);
-	    if (i < lenof(kex_algs) - 1)
-		ssh2_pkt_addstring_str(ssh, ",");
-	}
-	/* List server host key algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	for (i = 0; i < lenof(hostkey_algs); i++) {
-	    ssh2_pkt_addstring_str(ssh, hostkey_algs[i]->name);
-	    if (i < lenof(hostkey_algs) - 1)
-		ssh2_pkt_addstring_str(ssh, ",");
-	}
-	/* List client->server encryption algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	cipherstr_started = 0;
-	for (i = 0; i < s->n_preferred_ciphers; i++) {
-	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-	    if (!c) continue;	       /* warning flag */
-	    for (j = 0; j < c->nciphers; j++) {
-		if (cipherstr_started)
-		    ssh2_pkt_addstring_str(ssh, ",");
-		ssh2_pkt_addstring_str(ssh, c->list[j]->name);
-		cipherstr_started = 1;
-	    }
-	}
-	/* List server->client encryption algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	cipherstr_started = 0;
-	for (i = 0; i < s->n_preferred_ciphers; i++) {
-	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-	    if (!c) continue; /* warning flag */
-	    for (j = 0; j < c->nciphers; j++) {
-		if (cipherstr_started)
-		    ssh2_pkt_addstring_str(ssh, ",");
-		ssh2_pkt_addstring_str(ssh, c->list[j]->name);
-		cipherstr_started = 1;
-	    }
-	}
-	/* List client->server MAC algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	for (i = 0; i < s->nmacs; i++) {
-	    ssh2_pkt_addstring_str(ssh, s->maclist[i]->name);
-	    if (i < s->nmacs - 1)
-		ssh2_pkt_addstring_str(ssh, ",");
-	}
-	/* List server->client MAC algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	for (i = 0; i < s->nmacs; i++) {
-	    ssh2_pkt_addstring_str(ssh, s->maclist[i]->name);
-	    if (i < s->nmacs - 1)
-		ssh2_pkt_addstring_str(ssh, ",");
-	}
-	/* List client->server compression algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	for (i = 0; i < lenof(compressions) + 1; i++) {
-	    const struct ssh_compress *c =
-		i == 0 ? s->preferred_comp : compressions[i - 1];
-	    ssh2_pkt_addstring_str(ssh, c->name);
-	    if (i < lenof(compressions))
-		ssh2_pkt_addstring_str(ssh, ",");
-	}
-	/* List server->client compression algorithms. */
-	ssh2_pkt_addstring_start(ssh);
-	for (i = 0; i < lenof(compressions) + 1; i++) {
-	    const struct ssh_compress *c =
-		i == 0 ? s->preferred_comp : compressions[i - 1];
-	    ssh2_pkt_addstring_str(ssh, c->name);
-	    if (i < lenof(compressions))
-		ssh2_pkt_addstring_str(ssh, ",");
-	}
-	/* List client->server languages. Empty list. */
-	ssh2_pkt_addstring_start(ssh);
-	/* List server->client languages. Empty list. */
-	ssh2_pkt_addstring_start(ssh);
-	/* First KEX packet does _not_ follow, because we're not that brave. */
-	ssh2_pkt_addbool(ssh, FALSE);
-	/* Reserved. */
-	ssh2_pkt_adduint32(ssh, 0);
-    }
-
-    ssh->exhash = ssh->exhashbase;
-    sha_string(&ssh->exhash, ssh->pktout.data + 5, ssh->pktout.length - 5);
-
-    ssh2_pkt_send(ssh);
-
-    if (!ispkt)
-	crWaitUntil(ispkt);
-    if (ssh->pktin.length > 5)
-	sha_string(&ssh->exhash, ssh->pktin.data + 5, ssh->pktin.length - 5);
-
-    /*
-     * Now examine the other side's KEXINIT to see what we're up
-     * to.
-     */
-    {
-	char *str;
-	int i, j, len;
-
-	if (ssh->pktin.type != SSH2_MSG_KEXINIT) {
-	    bombout(("expected key exchange packet from server"));
-	    crStop(0);
-	}
-	ssh->kex = NULL;
-	ssh->hostkey = NULL;
-	s->cscipher_tobe = NULL;
-	s->sccipher_tobe = NULL;
-	s->csmac_tobe = NULL;
-	s->scmac_tobe = NULL;
-	s->cscomp_tobe = NULL;
-	s->sccomp_tobe = NULL;
-	ssh->pktin.savedpos += 16;	        /* skip garbage cookie */
-	ssh2_pkt_getstring(ssh, &str, &len);    /* key exchange algorithms */
-	for (i = 0; i < lenof(kex_algs); i++) {
-	    if (kex_algs[i] == &ssh_diffiehellman_gex &&
-		(ssh->remote_bugs & BUG_SSH2_DH_GEX))
-		continue;
-	    if (in_commasep_string(kex_algs[i]->name, str, len)) {
-		ssh->kex = kex_algs[i];
-		break;
-	    }
-	}
-	ssh2_pkt_getstring(ssh, &str, &len);    /* host key algorithms */
-	for (i = 0; i < lenof(hostkey_algs); i++) {
-	    if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
-		ssh->hostkey = hostkey_algs[i];
-		break;
-	    }
-	}
-	ssh2_pkt_getstring(ssh, &str, &len);    /* client->server cipher */
-	s->warn = 0;
-	for (i = 0; i < s->n_preferred_ciphers; i++) {
-	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-	    if (!c) {
-		s->warn = 1;
-	    } else {
-		for (j = 0; j < c->nciphers; j++) {
-		    if (in_commasep_string(c->list[j]->name, str, len)) {
-			s->cscipher_tobe = c->list[j];
-			break;
-		    }
-		}
-	    }
-	    if (s->cscipher_tobe) {
-		if (s->warn)
-		    askcipher(ssh->frontend, s->cscipher_tobe->name, 1);
-		break;
-	    }
-	}
-	if (!s->cscipher_tobe) {
-	    bombout(("Couldn't agree a client-to-server cipher (available: %s)",
-		     str ? str : "(null)"));
-	    crStop(0);
-	}
-
-	ssh2_pkt_getstring(ssh, &str, &len);    /* server->client cipher */
-	s->warn = 0;
-	for (i = 0; i < s->n_preferred_ciphers; i++) {
-	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
-	    if (!c) {
-		s->warn = 1;
-	    } else {
-		for (j = 0; j < c->nciphers; j++) {
-		    if (in_commasep_string(c->list[j]->name, str, len)) {
-			s->sccipher_tobe = c->list[j];
-			break;
-		    }
-		}
-	    }
-	    if (s->sccipher_tobe) {
-		if (s->warn)
-		    askcipher(ssh->frontend, s->sccipher_tobe->name, 2);
-		break;
-	    }
-	}
-	if (!s->sccipher_tobe) {
-	    bombout(("Couldn't agree a server-to-client cipher (available: %s)",
-		     str ? str : "(null)"));
-	    crStop(0);
-	}
-
-	ssh2_pkt_getstring(ssh, &str, &len);    /* client->server mac */
-	for (i = 0; i < s->nmacs; i++) {
-	    if (in_commasep_string(s->maclist[i]->name, str, len)) {
-		s->csmac_tobe = s->maclist[i];
-		break;
-	    }
-	}
-	ssh2_pkt_getstring(ssh, &str, &len);    /* server->client mac */
-	for (i = 0; i < s->nmacs; i++) {
-	    if (in_commasep_string(s->maclist[i]->name, str, len)) {
-		s->scmac_tobe = s->maclist[i];
-		break;
-	    }
-	}
-	ssh2_pkt_getstring(ssh, &str, &len);  /* client->server compression */
-	for (i = 0; i < lenof(compressions) + 1; i++) {
-	    const struct ssh_compress *c =
-		i == 0 ? s->preferred_comp : compressions[i - 1];
-	    if (in_commasep_string(c->name, str, len)) {
-		s->cscomp_tobe = c;
-		break;
-	    }
-	}
-	ssh2_pkt_getstring(ssh, &str, &len);  /* server->client compression */
-	for (i = 0; i < lenof(compressions) + 1; i++) {
-	    const struct ssh_compress *c =
-		i == 0 ? s->preferred_comp : compressions[i - 1];
-	    if (in_commasep_string(c->name, str, len)) {
-		s->sccomp_tobe = c;
-		break;
-	    }
-	}
-    }
-
-    /*
-     * 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->cscipher_tobe->keylen;
-	scbits = s->sccipher_tobe->keylen;
-	s->nbits = (csbits > scbits ? csbits : scbits);
-    }
-    /* The keys only have 160-bit entropy, since they're based on
-     * a SHA-1 hash. So cap the key size at 160 bits. */
-    if (s->nbits > 160)
-	s->nbits = 160;
-
-    /*
-     * If we're doing Diffie-Hellman group exchange, start by
-     * requesting a group.
-     */
-    if (ssh->kex == &ssh_diffiehellman_gex) {
-	logevent("Doing Diffie-Hellman group exchange");
-	ssh->pkt_ctx |= 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);
-	ssh2_pkt_init(ssh, SSH2_MSG_KEX_DH_GEX_REQUEST);
-	ssh2_pkt_adduint32(ssh, s->pbits);
-	ssh2_pkt_send(ssh);
-
-	crWaitUntil(ispkt);
-	if (ssh->pktin.type != SSH2_MSG_KEX_DH_GEX_GROUP) {
-	    bombout(("expected key exchange group packet from server"));
-	    crStop(0);
-	}
-	s->p = ssh2_pkt_getmp(ssh);
-	s->g = ssh2_pkt_getmp(ssh);
-	ssh->kex_ctx = dh_setup_group(s->p, s->g);
-	s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
-	s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
-    } else {
-	ssh->pkt_ctx |= SSH2_PKTCTX_DHGROUP1;
-	ssh->kex_ctx = dh_setup_group1();
-	s->kex_init_value = SSH2_MSG_KEXDH_INIT;
-	s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
-    }
-
-    logevent("Doing Diffie-Hellman key exchange");
-    /*
-     * Now generate and send e for Diffie-Hellman.
-     */
-    s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2);
-    ssh2_pkt_init(ssh, s->kex_init_value);
-    ssh2_pkt_addmp(ssh, s->e);
-    ssh2_pkt_send(ssh);
-
-    crWaitUntil(ispkt);
-    if (ssh->pktin.type != s->kex_reply_value) {
-	bombout(("expected key exchange reply packet from server"));
-	crStop(0);
-    }
-    ssh2_pkt_getstring(ssh, &s->hostkeydata, &s->hostkeylen);
-    s->f = ssh2_pkt_getmp(ssh);
-    ssh2_pkt_getstring(ssh, &s->sigdata, &s->siglen);
-
-    s->K = dh_find_K(ssh->kex_ctx, s->f);
-
-    sha_string(&ssh->exhash, s->hostkeydata, s->hostkeylen);
-    if (ssh->kex == &ssh_diffiehellman_gex) {
-	sha_uint32(&ssh->exhash, s->pbits);
-	sha_mpint(&ssh->exhash, s->p);
-	sha_mpint(&ssh->exhash, s->g);
-    }
-    sha_mpint(&ssh->exhash, s->e);
-    sha_mpint(&ssh->exhash, s->f);
-    sha_mpint(&ssh->exhash, s->K);
-    SHA_Final(&ssh->exhash, s->exchange_hash);
-
-    dh_cleanup(ssh->kex_ctx);
-    ssh->kex_ctx = NULL;
-
-#if 0
-    debug(("Exchange hash is:\n"));
-    dmemdump(s->exchange_hash, 20);
-#endif
-
-    s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
-    if (!s->hkey ||
-	!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
-				 (char *)s->exchange_hash, 20)) {
-	bombout(("Server's host key did not match the signature supplied"));
-	crStop(0);
-    }
-
-    /*
-     * Authenticate remote host: verify host key. (We've already
-     * checked the signature of the exchange hash.)
-     */
-    s->keystr = ssh->hostkey->fmtkey(s->hkey);
-    s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
-    verify_ssh_host_key(ssh->frontend,
-			ssh->savedhost, ssh->savedport, ssh->hostkey->keytype,
-			s->keystr, s->fingerprint);
-    if (s->first_kex) {		       /* don't bother logging this in rekeys */
-	logevent("Host key fingerprint is:");
-	logevent(s->fingerprint);
-    }
-    sfree(s->fingerprint);
-    sfree(s->keystr);
-    ssh->hostkey->freekey(s->hkey);
-
-    /*
-     * Send SSH2_MSG_NEWKEYS.
-     */
-    ssh2_pkt_init(ssh, SSH2_MSG_NEWKEYS);
-    ssh2_pkt_send(ssh);
-
-    /*
-     * Expect SSH2_MSG_NEWKEYS from server.
-     */
-    crWaitUntil(ispkt);
-    if (ssh->pktin.type != SSH2_MSG_NEWKEYS) {
-	bombout(("expected new-keys packet from server"));
-	crStop(0);
-    }
-
-    /*
-     * Create and initialise session keys.
-     */
-    if (ssh->cs_cipher_ctx)
-	ssh->cscipher->free_context(ssh->cs_cipher_ctx);
-    ssh->cscipher = s->cscipher_tobe;
-    ssh->cs_cipher_ctx = ssh->cscipher->make_context();
-
-    if (ssh->sc_cipher_ctx)
-	ssh->sccipher->free_context(ssh->sc_cipher_ctx);
-    ssh->sccipher = s->sccipher_tobe;
-    ssh->sc_cipher_ctx = ssh->sccipher->make_context();
-
-    if (ssh->cs_mac_ctx)
-	ssh->csmac->free_context(ssh->cs_mac_ctx);
-    ssh->csmac = s->csmac_tobe;
-    ssh->cs_mac_ctx = ssh->csmac->make_context();
-
-    if (ssh->sc_mac_ctx)
-	ssh->scmac->free_context(ssh->sc_mac_ctx);
-    ssh->scmac = s->scmac_tobe;
-    ssh->sc_mac_ctx = ssh->scmac->make_context();
-
-    if (ssh->cs_comp_ctx)
-	ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
-    ssh->cscomp = s->cscomp_tobe;
-    ssh->cs_comp_ctx = ssh->cscomp->compress_init();
-
-    if (ssh->sc_comp_ctx)
-	ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
-    ssh->sccomp = s->sccomp_tobe;
-    ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
-
-    /*
-     * Set IVs after keys. Here we use the exchange hash from the
-     * _first_ key exchange.
-     */
-    {
-	unsigned char keyspace[40];
-	if (s->first_kex)
-	    memcpy(ssh->v2_session_id, s->exchange_hash,
-		   sizeof(s->exchange_hash));
-	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'C',keyspace);
-	ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
-	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'D',keyspace);
-	ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
-	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'A',keyspace);
-	ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
-	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'B',keyspace);
-	ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
-	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'E',keyspace);
-	ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
-	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'F',keyspace);
-	ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
-    }
-    logeventf(ssh, "Initialised %.200s client->server encryption",
-	      ssh->cscipher->text_name);
-    logeventf(ssh, "Initialised %.200s server->client encryption",
-	      ssh->sccipher->text_name);
-    if (ssh->cscomp->text_name)
-	logeventf(ssh, "Initialised %s compression",
-		  ssh->cscomp->text_name);
-    if (ssh->sccomp->text_name)
-	logeventf(ssh, "Initialised %s decompression",
-		  ssh->sccomp->text_name);
-    freebn(s->f);
-    freebn(s->K);
-    if (ssh->kex == &ssh_diffiehellman_gex) {
-	freebn(s->g);
-	freebn(s->p);
-    }
-
-    /*
-     * If this is the first key exchange phase, we must pass the
-     * SSH2_MSG_NEWKEYS packet to the next layer, not because it
-     * wants to see it but because it will need time to initialise
-     * itself before it sees an actual packet. In subsequent key
-     * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
-     * it would only confuse the layer above.
-     */
-    if (!s->first_kex) {
-	crReturn(0);
-    }
-    s->first_kex = 0;
-
-    /*
-     * Now we're encrypting. Begin returning 1 to the protocol main
-     * function so that other things can run on top of the
-     * transport. If we ever see a KEXINIT, we must go back to the
-     * start.
-     */
-    while (!(ispkt && ssh->pktin.type == SSH2_MSG_KEXINIT)) {
-	crReturn(1);
-    }
-    logevent("Server initiated key re-exchange");
-    goto begin_key_exchange;
-
-    crFinish(1);
-}
-
-/*
- * Add data to an SSH2 channel output buffer.
- */
-static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
-				  int len)
-{
-    bufchain_add(&c->v.v2.outbuffer, buf, len);
-}
-
-/*
- * Attempt to send data on an SSH2 channel.
- */
-static int ssh2_try_send(struct ssh_channel *c)
-{
-    Ssh ssh = c->ssh;
-
-    while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
-	int len;
-	void *data;
-	bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
-	if ((unsigned)len > c->v.v2.remwindow)
-	    len = c->v.v2.remwindow;
-	if ((unsigned)len > c->v.v2.remmaxpkt)
-	    len = c->v.v2.remmaxpkt;
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_DATA);
-	ssh2_pkt_adduint32(ssh, c->remoteid);
-	ssh2_pkt_addstring_start(ssh);
-	ssh2_pkt_addstring_data(ssh, data, len);
-	ssh2_pkt_send(ssh);
-	bufchain_consume(&c->v.v2.outbuffer, len);
-	c->v.v2.remwindow -= len;
-    }
-
-    /*
-     * After having sent as much data as we can, return the amount
-     * still buffered.
-     */
-    return bufchain_size(&c->v.v2.outbuffer);
-}
-
-/*
- * Potentially enlarge the window on an SSH2 channel.
- */
-static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
-{
-    Ssh ssh = c->ssh;
-
-    /*
-     * Never send WINDOW_ADJUST for a channel that the remote side
-     * already thinks it's closed; there's no point, since it won't
-     * be sending any more data anyway.
-     */
-    if (c->closes != 0)
-	return;
-
-    if (newwin > c->v.v2.locwindow) {
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-	ssh2_pkt_adduint32(ssh, c->remoteid);
-	ssh2_pkt_adduint32(ssh, newwin - c->v.v2.locwindow);
-	ssh2_pkt_send(ssh);
-	c->v.v2.locwindow = newwin;
-    }
-}
-
-/*
- * Handle the SSH2 userauth and connection layers.
- */
-static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
-{
-    struct do_ssh2_authconn_state {
-	enum {
-	    AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE,
-		AUTH_PASSWORD,
-		AUTH_KEYBOARD_INTERACTIVE
-	} method;
-	enum {
-	    AUTH_TYPE_NONE,
-		AUTH_TYPE_PUBLICKEY,
-		AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
-		AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
-		AUTH_TYPE_PASSWORD,
-		AUTH_TYPE_KEYBOARD_INTERACTIVE,
-		AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
-	} type;
-	int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
-	int tried_pubkey_config, tried_agent, tried_keyb_inter;
-	int kbd_inter_running;
-	int we_are_in;
-	int num_prompts, curr_prompt, echo;
-	char username[100];
-	int got_username;
-	char pwprompt[200];
-	char password[100];
-	void *publickey_blob;
-	int publickey_bloblen;
-	unsigned char request[5], *response, *p;
-	int responselen;
-	int keyi, nkeys;
-	int authed;
-	char *pkblob, *alg, *commentp;
-	int pklen, alglen, commentlen;
-	int siglen, retlen, len;
-	char *q, *agentreq, *ret;
-	int try_send;
-    };
-    crState(do_ssh2_authconn_state);
-
-    crBegin(ssh->do_ssh2_authconn_crstate);
-
-    /*
-     * Request userauth protocol, and await a response to it.
-     */
-    ssh2_pkt_init(ssh, SSH2_MSG_SERVICE_REQUEST);
-    ssh2_pkt_addstring(ssh, "ssh-userauth");
-    ssh2_pkt_send(ssh);
-    crWaitUntilV(ispkt);
-    if (ssh->pktin.type != SSH2_MSG_SERVICE_ACCEPT) {
-	bombout(("Server refused user authentication protocol"));
-	crStopV;
-    }
-
-    /*
-     * 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->username[0] = '\0';
-    s->got_username = FALSE;
-    s->need_pw = FALSE;
-    do {
-	/*
-	 * Get a username.
-	 */
-	if (s->got_username && !ssh->cfg.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 ((flags & FLAG_INTERACTIVE) && !*ssh->cfg.username) {
-	    if (ssh_get_line && !ssh_getline_pw_only) {
-		if (!ssh_get_line("login as: ",
-				  s->username, sizeof(s->username), FALSE)) {
-		    /*
-		     * get_line failed to get a username.
-		     * Terminate.
-		     */
-		    logevent("No username provided. Abandoning session.");
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
-		    crStopV;
-		}
-	    } else {
-		int ret;	       /* need not be saved across crReturn */
-		c_write_str(ssh, "login as: ");
-		ssh->send_ok = 1;
-		setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
-		do {
-		    crWaitUntilV(!ispkt);
-		    ret = process_userpass_input(ssh, in, inlen);
-		} while (ret == 0);
-		if (ret < 0)
-		    cleanup_exit(0);
-		c_write_str(ssh, "\r\n");
-	    }
-	    s->username[strcspn(s->username, "\n\r")] = '\0';
-	} else {
-	    char *stuff;
-	    strncpy(s->username, ssh->cfg.username, sizeof(s->username));
-	    s->username[sizeof(s->username)-1] = '\0';
-	    if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
-		stuff = dupprintf("Using username \"%s\".\r\n", s->username);
-		c_write_str(ssh, stuff);
-		sfree(stuff);
-	    }
-	}
-	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.
-	 */
-	ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-
-	ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-	ssh2_pkt_addstring(ssh, s->username);
-	ssh2_pkt_addstring(ssh, "ssh-connection");/* service requested */
-	ssh2_pkt_addstring(ssh, "none");    /* method */
-	ssh2_pkt_send(ssh);
-	s->type = AUTH_TYPE_NONE;
-	s->gotit = FALSE;
-	s->we_are_in = FALSE;
-
-	s->tried_pubkey_config = FALSE;
-	s->tried_agent = FALSE;
-	s->tried_keyb_inter = FALSE;
-	s->kbd_inter_running = FALSE;
-	/* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */
-	if (!filename_is_null(ssh->cfg.keyfile)) {
-	    int keytype;
-	    logeventf(ssh, "Reading private key file \"%.150s\"",
-		      filename_to_str(&ssh->cfg.keyfile));
-	    keytype = key_type(&ssh->cfg.keyfile);
-	    if (keytype == SSH_KEYTYPE_SSH2) {
-		s->publickey_blob =
-		    ssh2_userkey_loadpub(&ssh->cfg.keyfile, NULL,
-					 &s->publickey_bloblen, NULL);
-	    } else {
-		char *msgbuf;
-		logeventf(ssh, "Unable to use this key file (%s)",
-			  key_type_to_str(keytype));
-		msgbuf = dupprintf("Unable to use key file \"%.150s\""
-				   " (%s)\r\n",
-				   filename_to_str(&ssh->cfg.keyfile),
-				   key_type_to_str(keytype));
-		c_write_str(ssh, msgbuf);
-		sfree(msgbuf);
-		s->publickey_blob = NULL;
-	    }
-	} else
-	    s->publickey_blob = NULL;
-
-	while (1) {
-	    /*
-	     * Wait for the result of the last authentication request.
-	     */
-	    if (!s->gotit)
-		crWaitUntilV(ispkt);
-	    while (ssh->pktin.type == SSH2_MSG_USERAUTH_BANNER) {
-		char *banner;
-		int size;
-		/*
-		 * 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.)
-		 */
-		if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) {
-		    ssh2_pkt_getstring(ssh, &banner, &size);
-		    if (banner)
-			c_write_untrusted(ssh, banner, size);
-		}
-		crWaitUntilV(ispkt);
-	    }
-	    if (ssh->pktin.type == SSH2_MSG_USERAUTH_SUCCESS) {
-		logevent("Access granted");
-		s->we_are_in = TRUE;
-		break;
-	    }
-
-	    if (s->kbd_inter_running &&
-		ssh->pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
-		/*
-		 * This is either a further set-of-prompts packet
-		 * in keyboard-interactive authentication, or it's
-		 * the same one and we came back here with `gotit'
-		 * set. In the former case, we must reset the
-		 * curr_prompt variable.
-		 */
-		if (!s->gotit)
-		    s->curr_prompt = 0;
-	    } else if (ssh->pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
-		bombout(("Strange packet received during authentication: type %d",
-			 ssh->pktin.type));
-		crStopV;
-	    }
-
-	    s->gotit = FALSE;
-
-	    /*
-	     * 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 (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE) {
-		char *methods;
-		int methlen;
-		ssh2_pkt_getstring(ssh, &methods, &methlen);
-		s->kbd_inter_running = FALSE;
-		if (!ssh2_pkt_getbool(ssh)) {
-		    /*
-		     * We have received an unequivocal Access
-		     * Denied. This can translate to a variety of
-		     * messages:
-		     * 
-		     *  - if we'd just tried "none" authentication,
-		     *    it's not worth printing anything at all
-		     * 
-		     *  - if we'd just tried a public key _offer_,
-		     *    the message should be "Server refused our
-		     *    key" (or no message at all if the key
-		     *    came from Pageant)
-		     * 
-		     *  - if we'd just tried anything else, the
-		     *    message really should be "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.
-		     */
-		    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)
-			    c_write_str(ssh, "Server refused our key\r\n");
-			logevent("Server refused public key");
-		    } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
-			/* server declined keyboard-interactive; ignore */
-		    } else {
-			c_write_str(ssh, "Access denied\r\n");
-			logevent("Access denied");
-			if (s->type == AUTH_TYPE_PASSWORD) {
-			    s->we_are_in = FALSE;
-			    break;
-			}
-		    }
-		} else {
-		    c_write_str(ssh, "Further authentication required\r\n");
-		    logevent("Further authentication required");
-		}
-
-		s->can_pubkey =
-		    in_commasep_string("publickey", methods, methlen);
-		s->can_passwd =
-		    in_commasep_string("password", methods, methlen);
-		s->can_keyb_inter = ssh->cfg.try_ki_auth &&
-		    in_commasep_string("keyboard-interactive", methods, methlen);
-	    }
-
-	    s->method = 0;
-	    ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-
-	    /*
-	     * Most password/passphrase prompts will be
-	     * non-echoing, so we set this to 0 by default.
-	     * Exception is that some keyboard-interactive prompts
-	     * can be echoing, in which case we'll set this to 1.
-	     */
-	    s->echo = 0;
-
-	    if (!s->method && s->can_pubkey &&
-		agent_exists() && !s->tried_agent) {
-		/*
-		 * Attempt public-key authentication using Pageant.
-		 */
-		void *r;
-		s->authed = FALSE;
-
-		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-		ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
-
-		s->tried_agent = TRUE;
-
-		logevent("Pageant is running. Requesting keys.");
-
-		/* Request the keys held by the agent. */
-		PUT_32BIT(s->request, 1);
-		s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
-		if (!agent_query(s->request, 5, &r, &s->responselen,
-				 ssh_agent_callback, ssh)) {
-		    do {
-			crReturnV;
-			if (ispkt) {
-			    bombout(("Unexpected data from server while"
-				     " waiting for agent response"));
-			    crStopV;
-			}
-		    } while (ispkt || inlen > 0);
-		    r = ssh->agent_response;
-		    s->responselen = ssh->agent_response_len;
-		}
-		s->response = (unsigned char *) r;
-		if (s->response && s->responselen >= 5 &&
-		    s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
-		    s->p = s->response + 5;
-		    s->nkeys = GET_32BIT(s->p);
-		    s->p += 4;
-		    {
-			char buf[64];
-			sprintf(buf, "Pageant has %d SSH2 keys", s->nkeys);
-			logevent(buf);
-		    }
-		    for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
-			void *vret;
-
-			{
-			    char buf[64];
-			    sprintf(buf, "Trying Pageant key #%d", s->keyi);
-			    logevent(buf);
-			}
-			s->pklen = GET_32BIT(s->p);
-			s->p += 4;
-			if (s->publickey_blob &&
-			    s->pklen == s->publickey_bloblen &&
-			    !memcmp(s->p, s->publickey_blob,
-				    s->publickey_bloblen)) {
-			    logevent("This key matches configured key file");
-			    s->tried_pubkey_config = 1;
-			}
-			s->pkblob = (char *)s->p;
-			s->p += s->pklen;
-			s->alglen = GET_32BIT(s->pkblob);
-			s->alg = s->pkblob + 4;
-			s->commentlen = GET_32BIT(s->p);
-			s->p += 4;
-			s->commentp = (char *)s->p;
-			s->p += s->commentlen;
-			ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-			ssh2_pkt_addstring(ssh, s->username);
-			ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-			ssh2_pkt_addstring(ssh, "publickey");	/* method */
-			ssh2_pkt_addbool(ssh, FALSE);	/* no signature included */
-			ssh2_pkt_addstring_start(ssh);
-			ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
-			ssh2_pkt_addstring_start(ssh);
-			ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
-			ssh2_pkt_send(ssh);
-
-			crWaitUntilV(ispkt);
-			if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
-			    logevent("Key refused");
-			    continue;
-			}
-
-			if (flags & FLAG_VERBOSE) {
-			    c_write_str(ssh, "Authenticating with "
-					"public key \"");
-			    c_write(ssh, s->commentp, s->commentlen);
-			    c_write_str(ssh, "\" from agent\r\n");
-			}
-
-			/*
-			 * Server is willing to accept the key.
-			 * Construct a SIGN_REQUEST.
-			 */
-			ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-			ssh2_pkt_addstring(ssh, s->username);
-			ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-			ssh2_pkt_addstring(ssh, "publickey");	/* method */
-			ssh2_pkt_addbool(ssh, TRUE);
-			ssh2_pkt_addstring_start(ssh);
-			ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
-			ssh2_pkt_addstring_start(ssh);
-			ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
-
-			s->siglen = ssh->pktout.length - 5 + 4 + 20;
-                        if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
-                            s->siglen -= 4;
-			s->len = 1;       /* message type */
-			s->len += 4 + s->pklen;	/* key blob */
-			s->len += 4 + s->siglen;	/* data to sign */
-			s->len += 4;      /* flags */
-			s->agentreq = snewn(4 + s->len, char);
-			PUT_32BIT(s->agentreq, s->len);
-			s->q = s->agentreq + 4;
-			*s->q++ = SSH2_AGENTC_SIGN_REQUEST;
-			PUT_32BIT(s->q, s->pklen);
-			s->q += 4;
-			memcpy(s->q, s->pkblob, s->pklen);
-			s->q += s->pklen;
-			PUT_32BIT(s->q, s->siglen);
-			s->q += 4;
-			/* Now the data to be signed... */
-                        if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
-                            PUT_32BIT(s->q, 20);
-                            s->q += 4;
-                        }
-			memcpy(s->q, ssh->v2_session_id, 20);
-			s->q += 20;
-			memcpy(s->q, ssh->pktout.data + 5,
-			       ssh->pktout.length - 5);
-			s->q += ssh->pktout.length - 5;
-			/* And finally the (zero) flags word. */
-			PUT_32BIT(s->q, 0);
-			if (!agent_query(s->agentreq, s->len + 4,
-					 &vret, &s->retlen,
-					 ssh_agent_callback, ssh)) {
-			    do {
-				crReturnV;
-				if (ispkt) {
-				    bombout(("Unexpected data from server"
-					     " while waiting for agent"
-					     " response"));
-				    crStopV;
-				}
-			    } while (ispkt || inlen > 0);
-			    vret = ssh->agent_response;
-			    s->retlen = ssh->agent_response_len;
-			}
-			s->ret = vret;
-			sfree(s->agentreq);
-			if (s->ret) {
-			    if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
-				logevent("Sending Pageant's response");
-				ssh2_add_sigblob(ssh, s->pkblob, s->pklen,
-						 s->ret + 9,
-						 GET_32BIT(s->ret + 5));
-				ssh2_pkt_send(ssh);
-				s->authed = TRUE;
-				break;
-			    } else {
-				logevent
-				    ("Pageant failed to answer challenge");
-				sfree(s->ret);
-			    }
-			}
-		    }
-		    if (s->authed)
-			continue;
-		}
-	    }
-
-	    if (!s->method && s->can_pubkey && s->publickey_blob
-		&& !s->tried_pubkey_config) {
-		unsigned char *pub_blob;
-		char *algorithm, *comment;
-		int pub_blob_len;
-
-		s->tried_pubkey_config = TRUE;
-
-		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-		ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
-
-		/*
-		 * Try the public key supplied in the configuration.
-		 *
-		 * First, offer the public blob to see if the server is
-		 * willing to accept it.
-		 */
-		pub_blob =
-		    (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile,
-							  &algorithm,
-							  &pub_blob_len,
-							  NULL);
-		if (pub_blob) {
-		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-		    ssh2_pkt_addstring(ssh, s->username);
-		    ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-		    ssh2_pkt_addstring(ssh, "publickey");	/* method */
-		    ssh2_pkt_addbool(ssh, FALSE);	/* no signature included */
-		    ssh2_pkt_addstring(ssh, algorithm);
-		    ssh2_pkt_addstring_start(ssh);
-		    ssh2_pkt_addstring_data(ssh, (char *)pub_blob,
-					    pub_blob_len);
-		    ssh2_pkt_send(ssh);
-		    logevent("Offered public key");	/* FIXME */
-
-		    crWaitUntilV(ispkt);
-		    if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
-			s->gotit = TRUE;
-			s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
-			continue;      /* key refused; give up on it */
-		    }
-
-		    logevent("Offer of public key accepted");
-		    /*
-		     * Actually attempt a serious authentication using
-		     * the key.
-		     */
-		    if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) {
-			sprintf(s->pwprompt,
-				"Passphrase for key \"%.100s\": ",
-				comment);
-			s->need_pw = TRUE;
-		    } else {
-			s->need_pw = FALSE;
-		    }
-		    c_write_str(ssh, "Authenticating with public key \"");
-		    c_write_str(ssh, comment);
-		    c_write_str(ssh, "\"\r\n");
-		    s->method = AUTH_PUBLICKEY_FILE;
-		}
-	    }
-
-	    if (!s->method && s->can_keyb_inter && !s->tried_keyb_inter) {
-		s->method = AUTH_KEYBOARD_INTERACTIVE;
-		s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-		s->tried_keyb_inter = TRUE;
-
-		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-		ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
-
-		ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-		ssh2_pkt_addstring(ssh, s->username);
-		ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-		ssh2_pkt_addstring(ssh, "keyboard-interactive");	/* method */
-		ssh2_pkt_addstring(ssh, ""); /* lang */
-		ssh2_pkt_addstring(ssh, "");
-		ssh2_pkt_send(ssh);
-
-		crWaitUntilV(ispkt);
-		if (ssh->pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
-		    if (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE)
-			s->gotit = TRUE;
-		    logevent("Keyboard-interactive authentication refused");
-		    s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
-		    continue;
-		}
-
-		s->kbd_inter_running = TRUE;
-		s->curr_prompt = 0;
-	    }
-
-	    if (s->kbd_inter_running) {
-		s->method = AUTH_KEYBOARD_INTERACTIVE;
-		s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-		s->tried_keyb_inter = TRUE;
-
-		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-		ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
-
-		if (s->curr_prompt == 0) {
-		    /*
-		     * We've got a fresh USERAUTH_INFO_REQUEST.
-		     * Display header data, and start going through
-		     * the prompts.
-		     */
-		    char *name, *inst, *lang;
-		    int name_len, inst_len, lang_len;
-
-		    ssh2_pkt_getstring(ssh, &name, &name_len);
-		    ssh2_pkt_getstring(ssh, &inst, &inst_len);
-		    ssh2_pkt_getstring(ssh, &lang, &lang_len);
-		    if (name_len > 0) {
-			c_write_untrusted(ssh, name, name_len);
-			c_write_str(ssh, "\r\n");
-		    }
-		    if (inst_len > 0) {
-			c_write_untrusted(ssh, inst, inst_len);
-			c_write_str(ssh, "\r\n");
-		    }
-		    s->num_prompts = ssh2_pkt_getuint32(ssh);
-		}
-
-		/*
-		 * If there are prompts remaining in the packet,
-		 * display one and get a response.
-		 */
-		if (s->curr_prompt < s->num_prompts) {
-		    char *prompt;
-		    int prompt_len;
-
-		    ssh2_pkt_getstring(ssh, &prompt, &prompt_len);
-		    if (prompt_len > 0) {
-			strncpy(s->pwprompt, prompt, sizeof(s->pwprompt));
-			s->pwprompt[prompt_len < sizeof(s->pwprompt) ?
-				    prompt_len : sizeof(s->pwprompt)-1] = '\0';
-		    } else {
-			strcpy(s->pwprompt,
-			       "<server failed to send prompt>: ");
-		    }
-		    s->echo = ssh2_pkt_getbool(ssh);
-		    s->need_pw = TRUE;
-		} else
-		    s->need_pw = FALSE;
-	    }
-
-	    if (!s->method && s->can_passwd) {
-		s->method = AUTH_PASSWORD;
-		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
-		ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD;
-		sprintf(s->pwprompt, "%.90s@%.90s's password: ", s->username,
-			ssh->savedhost);
-		s->need_pw = TRUE;
-	    }
-
-	    if (s->need_pw) {
-		if (ssh_get_line) {
-		    if (!ssh_get_line(s->pwprompt, s->password,
-				      sizeof(s->password), TRUE)) {
-			/*
-			 * get_line failed to get a password (for
-			 * example because one was supplied on the
-			 * command line which has already failed to
-			 * work). Terminate.
-			 */
-			ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
-			ssh2_pkt_adduint32(ssh,SSH2_DISCONNECT_BY_APPLICATION);
-			ssh2_pkt_addstring(ssh, "No more passwords available"
-					   " to try");
-			ssh2_pkt_addstring(ssh, "en");	/* language tag */
-			ssh2_pkt_send(ssh);
-			logevent("Unable to authenticate");
-			connection_fatal(ssh->frontend,
-					 "Unable to authenticate");
-                        ssh_closing((Plug)ssh, NULL, 0, 0);
-			crStopV;
-		    }
-		} else {
-		    int ret;	       /* need not be saved across crReturn */
-		    c_write_untrusted(ssh, s->pwprompt, strlen(s->pwprompt));
-		    ssh->send_ok = 1;
-
-		    setup_userpass_input(ssh, s->password,
-					 sizeof(s->password), s->echo);
-		    do {
-			crWaitUntilV(!ispkt);
-			ret = process_userpass_input(ssh, in, inlen);
-		    } while (ret == 0);
-		    if (ret < 0)
-			cleanup_exit(0);
-		    c_write_str(ssh, "\r\n");
-		}
-	    }
-
-	    if (s->method == AUTH_PUBLICKEY_FILE) {
-		/*
-		 * We have our passphrase. Now try the actual authentication.
-		 */
-		struct ssh2_userkey *key;
-		const char *error = NULL;
-
-		key = ssh2_load_userkey(&ssh->cfg.keyfile, s->password,
-					&error);
-		if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
-		    if (key == SSH2_WRONG_PASSPHRASE) {
-			c_write_str(ssh, "Wrong passphrase\r\n");
-			s->tried_pubkey_config = FALSE;
-		    } else {
-			c_write_str(ssh, "Unable to load private key (");
-			c_write_str(ssh, error);
-			c_write_str(ssh, ")\r\n");
-			s->tried_pubkey_config = TRUE;
-		    }
-		    /* Send a spurious AUTH_NONE to return to the top. */
-		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-		    ssh2_pkt_addstring(ssh, s->username);
-		    ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-		    ssh2_pkt_addstring(ssh, "none");	/* method */
-		    ssh2_pkt_send(ssh);
-		    s->type = AUTH_TYPE_NONE;
-		} else {
-		    unsigned char *pkblob, *sigblob, *sigdata;
-		    int pkblob_len, sigblob_len, sigdata_len;
-                    int p;
-
-		    /*
-		     * 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.
-		     */
-		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-		    ssh2_pkt_addstring(ssh, s->username);
-		    ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-		    ssh2_pkt_addstring(ssh, "publickey");	/* method */
-		    ssh2_pkt_addbool(ssh, TRUE);
-		    ssh2_pkt_addstring(ssh, key->alg->name);
-		    pkblob = key->alg->public_blob(key->data, &pkblob_len);
-		    ssh2_pkt_addstring_start(ssh);
-		    ssh2_pkt_addstring_data(ssh, (char *)pkblob, pkblob_len);
-
-		    /*
-		     * The data to be signed is:
-		     *
-		     *   string  session-id
-		     *
-		     * followed by everything so far placed in the
-		     * outgoing packet.
-		     */
-		    sigdata_len = ssh->pktout.length - 5 + 4 + 20;
-                    if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
-                        sigdata_len -= 4;
-		    sigdata = snewn(sigdata_len, unsigned char);
-                    p = 0;
-                    if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
-                        PUT_32BIT(sigdata+p, 20);
-                        p += 4;
-                    }
-		    memcpy(sigdata+p, ssh->v2_session_id, 20); p += 20;
-		    memcpy(sigdata+p, ssh->pktout.data + 5,
-			   ssh->pktout.length - 5);
-                    p += ssh->pktout.length - 5;
-                    assert(p == sigdata_len);
-		    sigblob = key->alg->sign(key->data, (char *)sigdata,
-					     sigdata_len, &sigblob_len);
-		    ssh2_add_sigblob(ssh, pkblob, pkblob_len,
-				     sigblob, sigblob_len);
-		    sfree(pkblob);
-		    sfree(sigblob);
-		    sfree(sigdata);
-
-		    ssh2_pkt_send(ssh);
-		    s->type = AUTH_TYPE_PUBLICKEY;
-		}
-	    } else if (s->method == AUTH_PASSWORD) {
-		/*
-		 * We send the password packet lumped tightly together with
-		 * an SSH_MSG_IGNORE packet. The IGNORE packet contains a
-		 * string long enough to make the total length of the two
-		 * packets constant. This should ensure that a passive
-		 * listener doing traffic analyis can't work out the length
-		 * of the password.
-		 *
-		 * For this to work, we need an assumption about the
-		 * maximum length of the password packet. I think 256 is
-		 * pretty conservative. Anyone using a password longer than
-		 * that probably doesn't have much to worry about from
-		 * people who find out how long their password is!
-		 */
-		ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
-		ssh2_pkt_addstring(ssh, s->username);
-		ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
-		ssh2_pkt_addstring(ssh, "password");
-		ssh2_pkt_addbool(ssh, FALSE);
-		ssh2_pkt_addstring(ssh, s->password);
-		memset(s->password, 0, sizeof(s->password));
-		ssh2_pkt_defer(ssh);
-		/*
-		 * We'll include a string that's an exact multiple of the
-		 * cipher block size. If the cipher is NULL for some
-		 * reason, we don't do this trick at all because we gain
-		 * nothing by it.
-		 */
-		if (ssh->cscipher) {
-		    int stringlen, i;
-
-		    stringlen = (256 - ssh->deferred_len);
-		    stringlen += ssh->cscipher->blksize - 1;
-		    stringlen -= (stringlen % ssh->cscipher->blksize);
-		    if (ssh->cscomp) {
-			/*
-			 * Temporarily disable actual compression,
-			 * so we can guarantee to get this string
-			 * exactly the length we want it. The
-			 * compression-disabling routine should
-			 * return an integer indicating how many
-			 * bytes we should adjust our string length
-			 * by.
-			 */
-			stringlen -= 
-			    ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
-		    }
-		    ssh2_pkt_init(ssh, SSH2_MSG_IGNORE);
-		    ssh2_pkt_addstring_start(ssh);
-		    for (i = 0; i < stringlen; i++) {
-			char c = (char) random_byte();
-			ssh2_pkt_addstring_data(ssh, &c, 1);
-		    }
-		    ssh2_pkt_defer(ssh);
-		}
-		ssh_pkt_defersend(ssh);
-		logevent("Sent password");
-		s->type = AUTH_TYPE_PASSWORD;
-	    } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) {
-		if (s->curr_prompt == 0) {
-		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE);
-		    ssh2_pkt_adduint32(ssh, s->num_prompts);
-		}
-		if (s->need_pw) {      /* only add pw if we just got one! */
-		    ssh2_pkt_addstring(ssh, s->password);
-		    memset(s->password, 0, sizeof(s->password));
-		    s->curr_prompt++;
-		}
-		if (s->curr_prompt >= s->num_prompts) {
-		    ssh2_pkt_send(ssh);
-		} else {
-		    /*
-		     * If there are prompts remaining, we set
-		     * `gotit' so that we won't attempt to get
-		     * another packet. Then we go back round the
-		     * loop and will end up retrieving another
-		     * prompt out of the existing packet. Funky or
-		     * what?
-		     */
-		    s->gotit = TRUE;
-		}
-		s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
-	    } else {
-		c_write_str(ssh, "No supported authentication methods"
-			    " left to try!\r\n");
-		logevent("No supported authentications offered."
-			 " Disconnecting");
-		ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
-		ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
-		ssh2_pkt_addstring(ssh, "No supported authentication"
-				   " methods available");
-		ssh2_pkt_addstring(ssh, "en");	/* language tag */
-		ssh2_pkt_send(ssh);
-                ssh_closing((Plug)ssh, NULL, 0, 0);
-		crStopV;
-	    }
-	}
-    } while (!s->we_are_in);
-
-    /*
-     * Now we're authenticated for the connection protocol. The
-     * connection protocol will automatically have started at this
-     * point; there's no need to send SERVICE_REQUEST.
-     */
-
-    /*
-     * So now create a channel with a session in it.
-     */
-    ssh->channels = newtree234(ssh_channelcmp);
-    ssh->mainchan = snew(struct ssh_channel);
-    ssh->mainchan->ssh = ssh;
-    ssh->mainchan->localid = alloc_channel_id(ssh);
-    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN);
-    ssh2_pkt_addstring(ssh, "session");
-    ssh2_pkt_adduint32(ssh, ssh->mainchan->localid);
-    ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
-    ssh2_pkt_adduint32(ssh, ssh->mainchan->v.v2.locwindow);/* our window size */
-    ssh2_pkt_adduint32(ssh, 0x4000UL);      /* our max pkt size */
-    ssh2_pkt_send(ssh);
-    crWaitUntilV(ispkt);
-    if (ssh->pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
-	bombout(("Server refused to open a session"));
-	crStopV;
-	/* FIXME: error data comes back in FAILURE packet */
-    }
-    if (ssh2_pkt_getuint32(ssh) != ssh->mainchan->localid) {
-	bombout(("Server's channel confirmation cited wrong channel"));
-	crStopV;
-    }
-    ssh->mainchan->remoteid = ssh2_pkt_getuint32(ssh);
-    ssh->mainchan->type = CHAN_MAINSESSION;
-    ssh->mainchan->closes = 0;
-    ssh->mainchan->v.v2.remwindow = ssh2_pkt_getuint32(ssh);
-    ssh->mainchan->v.v2.remmaxpkt = ssh2_pkt_getuint32(ssh);
-    bufchain_init(&ssh->mainchan->v.v2.outbuffer);
-    add234(ssh->channels, ssh->mainchan);
-    logevent("Opened channel for session");
-
-    /*
-     * Potentially enable X11 forwarding.
-     */
-    if (ssh->cfg.x11_forward) {
-	char proto[20], data[64];
-	logevent("Requesting X11 forwarding");
-	ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
-				       data, sizeof(data), ssh->cfg.x11_auth);
-        x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display);
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
-	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
-	ssh2_pkt_addstring(ssh, "x11-req");
-	ssh2_pkt_addbool(ssh, 1);	       /* want reply */
-	ssh2_pkt_addbool(ssh, 0);	       /* many connections */
-	ssh2_pkt_addstring(ssh, proto);
-	ssh2_pkt_addstring(ssh, data);
-	ssh2_pkt_adduint32(ssh, x11_get_screen_number(ssh->cfg.x11_display));
-	ssh2_pkt_send(ssh);
-
-	do {
-	    crWaitUntilV(ispkt);
-	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
-	    }
-	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-
-	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
-	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-		bombout(("Unexpected response to X11 forwarding request:"
-			 " packet type %d", ssh->pktin.type));
-		crStopV;
-	    }
-	    logevent("X11 forwarding refused");
-	} else {
-	    logevent("X11 forwarding enabled");
-	    ssh->X11_fwd_enabled = TRUE;
-	}
-    }
-
-    /*
-     * Enable port forwardings.
-     */
-    {
-	char type;
-	int n;
-	int sport,dport,sserv,dserv;
-	char sports[256], dports[256], saddr[256], host[256];
-
-	ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
-        /* Add port forwardings. */
-	ssh->portfwd_strptr = ssh->cfg.portfwd;
-	while (*ssh->portfwd_strptr) {
-	    type = *ssh->portfwd_strptr++;
-	    saddr[0] = '\0';
-	    n = 0;
-	    while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') {
-		if (*ssh->portfwd_strptr == ':') {
-		    /*
-		     * We've seen a colon in the middle of the
-		     * source port number. This means that
-		     * everything we've seen until now is the
-		     * source _address_, so we'll move it into
-		     * saddr and start sports from the beginning
-		     * again.
-		     */
-		    ssh->portfwd_strptr++;
-		    sports[n] = '\0';
-		    strcpy(saddr, sports);
-		    n = 0;
-		}
-		if (n < 255) sports[n++] = *ssh->portfwd_strptr++;
-	    }
-	    sports[n] = 0;
-	    if (type != 'D') {
-		if (*ssh->portfwd_strptr == '\t')
-		    ssh->portfwd_strptr++;
-		n = 0;
-		while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') {
-		    if (n < 255) host[n++] = *ssh->portfwd_strptr++;
-		}
-		host[n] = 0;
-		if (*ssh->portfwd_strptr == ':')
-		    ssh->portfwd_strptr++;
-		n = 0;
-		while (*ssh->portfwd_strptr) {
-		    if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
-		}
-		dports[n] = 0;
-		ssh->portfwd_strptr++;
-		dport = atoi(dports);
-		dserv = 0;
-		if (dport == 0) {
-		    dserv = 1;
-		    dport = net_service_lookup(dports);
-		    if (!dport) {
-			logeventf(ssh, "Service lookup failed for destination"
-				  " port \"%s\"", dports);
-		    }
-		}
-	    } else {
-		while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
-		dport = dserv = -1;
-		ssh->portfwd_strptr++; /* eat the NUL and move to next one */
-	    }
-	    sport = atoi(sports);
-	    sserv = 0;
-	    if (sport == 0) {
-		sserv = 1;
-		sport = net_service_lookup(sports);
-		if (!sport) {
-		    logeventf(ssh, "Service lookup failed for source"
-			      " port \"%s\"", sports);
-		}
-	    }
-	    if (sport && dport) {
-		if (type == 'L') {
-		    pfd_addforward(host, dport, *saddr ? saddr : NULL,
-				   sport, ssh, &ssh->cfg);
-		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
-			      " forwarding to %s:%.*s%.*s%d%.*s",
-			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
-			      (int)(*saddr?1:0), ":",
-			      (int)(sserv ? strlen(sports) : 0), sports,
-			      sserv, "(", sport, sserv, ")",
-			      host,
-			      (int)(dserv ? strlen(dports) : 0), dports,
-			      dserv, "(", dport, dserv, ")");
-		} else if (type == 'D') {
-		    pfd_addforward(NULL, -1, *saddr ? saddr : NULL,
-				   sport, ssh, &ssh->cfg);
-		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
-			      " doing SOCKS dynamic forwarding",
-			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
-			      (int)(*saddr?1:0), ":",
-			      (int)(sserv ? strlen(sports) : 0), sports,
-			      sserv, "(", sport, sserv, ")");
-		} else {
-		    struct ssh_rportfwd *pf;
-		    pf = snew(struct ssh_rportfwd);
-		    strcpy(pf->dhost, host);
-		    pf->dport = dport;
-		    pf->sport = sport;
-		    if (add234(ssh->rportfwds, pf) != pf) {
-			logeventf(ssh, "Duplicate remote port forwarding"
-				  " to %s:%d", host, dport);
-			sfree(pf);
-		    } else {
-			logeventf(ssh, "Requesting remote port "
-				  "%.*s%.*s%.*s%.*s%d%.*s"
-				  " forward to %s:%.*s%.*s%d%.*s",
-				  (int)(*saddr?strlen(saddr):0),
-				  *saddr?saddr:NULL,
-				  (int)(*saddr?1:0), ":",
-				  (int)(sserv ? strlen(sports) : 0), sports,
-				  sserv, "(", sport, sserv, ")",
-				  host,
-				  (int)(dserv ? strlen(dports) : 0), dports,
-				  dserv, "(", dport, dserv, ")");
-			ssh2_pkt_init(ssh, SSH2_MSG_GLOBAL_REQUEST);
-			ssh2_pkt_addstring(ssh, "tcpip-forward");
-			ssh2_pkt_addbool(ssh, 1);/* want reply */
-			if (*saddr)
-			    ssh2_pkt_addstring(ssh, saddr);
-			if (ssh->cfg.rport_acceptall)
-			    ssh2_pkt_addstring(ssh, "0.0.0.0");
-			else
-			    ssh2_pkt_addstring(ssh, "127.0.0.1");
-			ssh2_pkt_adduint32(ssh, sport);
-			ssh2_pkt_send(ssh);
-
-			do {
-			    crWaitUntilV(ispkt);
-			    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-				unsigned i = ssh2_pkt_getuint32(ssh);
-				struct ssh_channel *c;
-				c = find234(ssh->channels, &i, ssh_channelfind);
-				if (!c)
-				    continue;/* nonexistent channel */
-				c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
-			    }
-			} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-
-			if (ssh->pktin.type != SSH2_MSG_REQUEST_SUCCESS) {
-			    if (ssh->pktin.type != SSH2_MSG_REQUEST_FAILURE) {
-				bombout(("Unexpected response to port "
-					 "forwarding request: packet type %d",
-					 ssh->pktin.type));
-				crStopV;
-			    }
-			    logevent("Server refused this port forwarding");
-			} else {
-			    logevent("Remote port forwarding enabled");
-			}
-		    }
-		}
-	    }
-	}
-    }
-
-    /*
-     * Potentially enable agent forwarding.
-     */
-    if (ssh->cfg.agentfwd && agent_exists()) {
-	logevent("Requesting OpenSSH-style agent forwarding");
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
-	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
-	ssh2_pkt_addstring(ssh, "[email protected]");
-	ssh2_pkt_addbool(ssh, 1);	       /* want reply */
-	ssh2_pkt_send(ssh);
-
-	do {
-	    crWaitUntilV(ispkt);
-	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
-	    }
-	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-
-	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
-	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-		bombout(("Unexpected response to agent forwarding request:"
-			 " packet type %d", ssh->pktin.type));
-		crStopV;
-	    }
-	    logevent("Agent forwarding refused");
-	} else {
-	    logevent("Agent forwarding enabled");
-	    ssh->agentfwd_enabled = TRUE;
-	}
-    }
-
-    /*
-     * Now allocate a pty for the session.
-     */
-    if (!ssh->cfg.nopty) {
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
-	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);	/* recipient channel */
-	ssh2_pkt_addstring(ssh, "pty-req");
-	ssh2_pkt_addbool(ssh, 1);	       /* want reply */
-	ssh2_pkt_addstring(ssh, ssh->cfg.termtype);
-	ssh2_pkt_adduint32(ssh, ssh->term_width);
-	ssh2_pkt_adduint32(ssh, ssh->term_height);
-	ssh2_pkt_adduint32(ssh, 0);	       /* pixel width */
-	ssh2_pkt_adduint32(ssh, 0);	       /* pixel height */
-	ssh2_pkt_addstring_start(ssh);
-	ssh2_pkt_addstring_data(ssh, "\0", 1);	/* TTY_OP_END, no special options */
-	ssh2_pkt_send(ssh);
-	ssh->state = SSH_STATE_INTERMED;
-
-	do {
-	    crWaitUntilV(ispkt);
-	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
-	    }
-	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-
-	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
-	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-		bombout(("Unexpected response to pty request:"
-			 " packet type %d", ssh->pktin.type));
-		crStopV;
-	    }
-	    c_write_str(ssh, "Server refused to allocate pty\r\n");
-	    ssh->editing = ssh->echoing = 1;
-	} else {
-	    logevent("Allocated pty");
-	}
-    } else {
-	ssh->editing = ssh->echoing = 1;
-    }
-
-    /*
-     * Start a shell or a remote command. We may have to attempt
-     * this twice if the config data has provided a second choice
-     * of command.
-     */
-    while (1) {
-	int subsys;
-	char *cmd;
-
-	if (ssh->fallback_cmd) {
-	    subsys = ssh->cfg.ssh_subsys2;
-	    cmd = ssh->cfg.remote_cmd_ptr2;
-	} else {
-	    subsys = ssh->cfg.ssh_subsys;
-	    cmd = ssh->cfg.remote_cmd_ptr;
-	}
-
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
-	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);	/* recipient channel */
-	if (subsys) {
-	    ssh2_pkt_addstring(ssh, "subsystem");
-	    ssh2_pkt_addbool(ssh, 1);	       /* want reply */
-	    ssh2_pkt_addstring(ssh, cmd);
-	} else if (*cmd) {
-	    ssh2_pkt_addstring(ssh, "exec");
-	    ssh2_pkt_addbool(ssh, 1);	       /* want reply */
-	    ssh2_pkt_addstring(ssh, cmd);
-	} else {
-	    ssh2_pkt_addstring(ssh, "shell");
-	    ssh2_pkt_addbool(ssh, 1);	       /* want reply */
-	}
-	ssh2_pkt_send(ssh);
-	do {
-	    crWaitUntilV(ispkt);
-	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
-	    }
-	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
-	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
-	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
-		bombout(("Unexpected response to shell/command request:"
-			 " packet type %d", ssh->pktin.type));
-		crStopV;
-	    }
-	    /*
-	     * We failed to start the command. If this is the
-	     * fallback command, we really are finished; if it's
-	     * not, and if the fallback command exists, try falling
-	     * back to it before complaining.
-	     */
-	    if (!ssh->fallback_cmd && ssh->cfg.remote_cmd_ptr2 != NULL) {
-		logevent("Primary command failed; attempting fallback");
-		ssh->fallback_cmd = TRUE;
-		continue;
-	    }
-	    bombout(("Server refused to start a shell/command"));
-	    crStopV;
-	} else {
-	    logevent("Started a shell/command");
-	}
-	break;
-    }
-
-    ssh->state = SSH_STATE_SESSION;
-    if (ssh->size_needed)
-	ssh_size(ssh, ssh->term_width, ssh->term_height);
-    if (ssh->eof_needed)
-	ssh_special(ssh, TS_EOF);
-
-    /*
-     * Transfer data!
-     */
-    if (ssh->ldisc)
-	ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
-    ssh->send_ok = 1;
-    while (1) {
-	crReturnV;
-	s->try_send = FALSE;
-	if (ispkt) {
-	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_DATA ||
-		ssh->pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
-		char *data;
-		int length;
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		if (ssh->pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
-		    ssh2_pkt_getuint32(ssh) != SSH2_EXTENDED_DATA_STDERR)
-		    continue;	       /* extended but not stderr */
-		ssh2_pkt_getstring(ssh, &data, &length);
-		if (data) {
-		    int bufsize = 0;
-		    c->v.v2.locwindow -= length;
-		    switch (c->type) {
-		      case CHAN_MAINSESSION:
-			bufsize =
-			    from_backend(ssh->frontend, ssh->pktin.type ==
-					 SSH2_MSG_CHANNEL_EXTENDED_DATA,
-					 data, length);
-			break;
-		      case CHAN_X11:
-			bufsize = x11_send(c->u.x11.s, data, length);
-			break;
-		      case CHAN_SOCKDATA:
-			bufsize = pfd_send(c->u.pfd.s, data, length);
-			break;
-		      case CHAN_AGENT:
-			while (length > 0) {
-			    if (c->u.a.lensofar < 4) {
-				int l = min(4 - c->u.a.lensofar, length);
-				memcpy(c->u.a.msglen + c->u.a.lensofar,
-				       data, l);
-				data += l;
-				length -= l;
-				c->u.a.lensofar += l;
-			    }
-			    if (c->u.a.lensofar == 4) {
-				c->u.a.totallen =
-				    4 + GET_32BIT(c->u.a.msglen);
-				c->u.a.message = snewn(c->u.a.totallen,
-						       unsigned char);
-				memcpy(c->u.a.message, c->u.a.msglen, 4);
-			    }
-			    if (c->u.a.lensofar >= 4 && length > 0) {
-				int l =
-				    min(c->u.a.totallen - c->u.a.lensofar,
-					length);
-				memcpy(c->u.a.message + c->u.a.lensofar,
-				       data, l);
-				data += l;
-				length -= l;
-				c->u.a.lensofar += l;
-			    }
-			    if (c->u.a.lensofar == c->u.a.totallen) {
-				void *reply;
-				int replylen;
-				if (agent_query(c->u.a.message,
-						c->u.a.totallen,
-						&reply, &replylen,
-						ssh_agentf_callback, c))
-				    ssh_agentf_callback(c, reply, replylen);
-				sfree(c->u.a.message);
-				c->u.a.lensofar = 0;
-			    }
-			}
-			bufsize = 0;
-			break;
-		    }
-		    /*
-		     * If we are not buffering too much data,
-		     * enlarge the window again at the remote side.
-		     */
-		    if (bufsize < OUR_V2_WINSIZE)
-			ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
-		}
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_EOF) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-
-		if (c->type == CHAN_X11) {
-		    /*
-		     * Remote EOF on an X11 channel means we should
-		     * wrap up and close the channel ourselves.
-		     */
-		    x11_close(c->u.x11.s);
-		    sshfwd_close(c);
-		} else if (c->type == CHAN_AGENT) {
-		    sshfwd_close(c);
-		} else if (c->type == CHAN_SOCKDATA) {
-		    pfd_close(c->u.pfd.s);
-		    sshfwd_close(c);
-		}
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c || ((int)c->remoteid) == -1) {
-		    bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
-			     c ? "half-open" : "nonexistent", i));
-		    crStopV;
-		}
-		/* Do pre-close processing on the channel. */
-		switch (c->type) {
-		  case CHAN_MAINSESSION:
-		    break;	       /* nothing to see here, move along */
-		  case CHAN_X11:
-		    if (c->u.x11.s != NULL)
-			x11_close(c->u.x11.s);
-		    sshfwd_close(c);
-		    break;
-		  case CHAN_AGENT:
-		    sshfwd_close(c);
-		    break;
-		  case CHAN_SOCKDATA:
-		    if (c->u.pfd.s != NULL)
-			pfd_close(c->u.pfd.s);
-		    sshfwd_close(c);
-		    break;
-		}
-		if (c->closes == 0) {
-		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
-		    ssh2_pkt_adduint32(ssh, c->remoteid);
-		    ssh2_pkt_send(ssh);
-		}
-		del234(ssh->channels, c);
-		bufchain_clear(&c->v.v2.outbuffer);
-		sfree(c);
-
-		/*
-		 * See if that was the last channel left open.
-		 */
-		if (count234(ssh->channels) == 0) {
-		    logevent("All channels closed. Disconnecting");
-#if 0
-                    /*
-                     * We used to send SSH_MSG_DISCONNECT here,
-                     * because I'd believed that _every_ conforming
-                     * SSH2 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.
-                     */
-		    ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
-		    ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
-		    ssh2_pkt_addstring(ssh, "All open channels closed");
-		    ssh2_pkt_addstring(ssh, "en");	/* language tag */
-		    ssh2_pkt_send(ssh);
-#endif
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
-		    crStopV;
-		}
-		continue;	       /* remote sends close; ignore (FIXME) */
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c || c->closes)
-		    continue;	       /* nonexistent or closing channel */
-		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
-		s->try_send = TRUE;
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		if (c->type != CHAN_SOCKDATA_DORMANT)
-		    continue;	       /* dunno why they're confirming this */
-		c->remoteid = ssh2_pkt_getuint32(ssh);
-		c->type = CHAN_SOCKDATA;
-		c->v.v2.remwindow = ssh2_pkt_getuint32(ssh);
-		c->v.v2.remmaxpkt = ssh2_pkt_getuint32(ssh);
-		if (c->u.pfd.s)
-		    pfd_confirm(c->u.pfd.s);
-		if (c->closes) {
-		    /*
-		     * We have a pending close on this channel,
-		     * which we decided on before the server acked
-		     * the channel open. So now we know the
-		     * remoteid, we can close it again.
-		     */
-		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
-		    ssh2_pkt_adduint32(ssh, c->remoteid);
-		    ssh2_pkt_send(ssh);
-		}
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
-		unsigned i = ssh2_pkt_getuint32(ssh);
-		struct ssh_channel *c;
-		c = find234(ssh->channels, &i, ssh_channelfind);
-		if (!c)
-		    continue;	       /* nonexistent channel */
-		if (c->type != CHAN_SOCKDATA_DORMANT)
-		    continue;	       /* dunno why they're failing this */
-
-		logevent("Forwarded connection refused by server");
-
-		pfd_close(c->u.pfd.s);
-
-		del234(ssh->channels, c);
-		sfree(c);
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
- 		unsigned localid;
-		char *type;
-		int typelen, want_reply;
-		struct ssh_channel *c;
-
-		localid = ssh2_pkt_getuint32(ssh);
-		ssh2_pkt_getstring(ssh, &type, &typelen);
-		want_reply = ssh2_pkt_getbool(ssh);
-
-		/*
-		 * First, check that the channel exists. Otherwise,
-		 * we can instantly disconnect with a rude message.
-		 */
-		c = find234(ssh->channels, &localid, ssh_channelfind);
-		if (!c) {
-		    char buf[80];
-		    sprintf(buf, "Received channel request for nonexistent"
-			    " channel %d", localid);
-		    logevent(buf);
-		    ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
-		    ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
-		    ssh2_pkt_addstring(ssh, buf);
-		    ssh2_pkt_addstring(ssh, "en");	/* language tag */
-		    ssh2_pkt_send(ssh);
-		    connection_fatal(ssh->frontend, "%s", buf);
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
-		    crStopV;
-		}
-
-		/*
-		 * Having got the channel number, we now look at
-		 * the request type string to see if it's something
-		 * we recognise.
-		 */
-		if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
-		    c == ssh->mainchan) {
-		    /* We recognise "exit-status" on the primary channel. */
-		    char buf[100];
-		    ssh->exitcode = ssh2_pkt_getuint32(ssh);
-		    sprintf(buf, "Server sent command exit status %d",
-			    ssh->exitcode);
-		    logevent(buf);
-		    if (want_reply) {
-			ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_SUCCESS);
-			ssh2_pkt_adduint32(ssh, c->remoteid);
-			ssh2_pkt_send(ssh);
-		    }
-		} else {
-		    /*
-		     * This is a channel request we don't know
-		     * about, so we now either ignore the request
-		     * or respond with CHANNEL_FAILURE, depending
-		     * on want_reply.
-		     */
-		    if (want_reply) {
-			ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_FAILURE);
-			ssh2_pkt_adduint32(ssh, c->remoteid);
-			ssh2_pkt_send(ssh);
-		    }
-		}
-	    } else if (ssh->pktin.type == SSH2_MSG_GLOBAL_REQUEST) {
-		char *type;
-		int typelen, want_reply;
-
-		ssh2_pkt_getstring(ssh, &type, &typelen);
-		want_reply = ssh2_pkt_getbool(ssh);
-
-                /*
-                 * We currently don't support any global requests
-                 * at all, so we either ignore the request or
-                 * respond with REQUEST_FAILURE, depending on
-                 * want_reply.
-                 */
-                if (want_reply) {
-                    ssh2_pkt_init(ssh, SSH2_MSG_REQUEST_FAILURE);
-                    ssh2_pkt_send(ssh);
-		}
-	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN) {
-		char *type;
-		int typelen;
-		char *peeraddr;
-		int peeraddrlen;
-		int peerport;
-		char *error = NULL;
-		struct ssh_channel *c;
-		unsigned remid, winsize, pktsize;
-		ssh2_pkt_getstring(ssh, &type, &typelen);
-		c = snew(struct ssh_channel);
-		c->ssh = ssh;
-
-		remid = ssh2_pkt_getuint32(ssh);
-		winsize = ssh2_pkt_getuint32(ssh);
-		pktsize = ssh2_pkt_getuint32(ssh);
-
-		if (typelen == 3 && !memcmp(type, "x11", 3)) {
-		    char *addrstr;
-
-                    ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen);
-		    addrstr = snewn(peeraddrlen+1, char);
-		    memcpy(addrstr, peeraddr, peeraddrlen);
-		    peeraddr[peeraddrlen] = '\0';
-                    peerport = ssh2_pkt_getuint32(ssh);
-
-		    if (!ssh->X11_fwd_enabled)
-			error = "X11 forwarding is not enabled";
-		    else if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c,
-				      ssh->x11auth, addrstr, peerport,
-				      &ssh->cfg) != NULL) {
-			error = "Unable to open an X11 connection";
-		    } else {
-			c->type = CHAN_X11;
-		    }
-
-		    sfree(addrstr);
-		} else if (typelen == 15 &&
-			   !memcmp(type, "forwarded-tcpip", 15)) {
-		    struct ssh_rportfwd pf, *realpf;
-		    char *dummy;
-		    int dummylen;
-		    ssh2_pkt_getstring(ssh, &dummy, &dummylen);/* skip address */
-		    pf.sport = ssh2_pkt_getuint32(ssh);
-                    ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen);
-                    peerport = ssh2_pkt_getuint32(ssh);
-		    realpf = find234(ssh->rportfwds, &pf, NULL);
-		    if (realpf == NULL) {
-			error = "Remote port is not recognised";
-		    } else {
-			const char *e = pfd_newconnect(&c->u.pfd.s,
-						       realpf->dhost,
-						       realpf->dport, c,
-						       &ssh->cfg);
-			logeventf(ssh, "Received remote port open request"
-				  " for %s:%d", realpf->dhost, realpf->dport);
-			if (e != NULL) {
-			    logeventf(ssh, "Port open failed: %s", e);
-			    error = "Port open failed";
-			} else {
-			    logevent("Forwarded port opened successfully");
-			    c->type = CHAN_SOCKDATA;
-			}
-		    }
-		} else if (typelen == 22 &&
-			   !memcmp(type, "[email protected]", 3)) {
-		    if (!ssh->agentfwd_enabled)
-			error = "Agent forwarding is not enabled";
-		    else {
-			c->type = CHAN_AGENT;	/* identify channel type */
-			c->u.a.lensofar = 0;
-		    }
-		} else {
-		    error = "Unsupported channel type requested";
-		}
-
-		c->remoteid = remid;
-		if (error) {
-		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE);
-		    ssh2_pkt_adduint32(ssh, c->remoteid);
-		    ssh2_pkt_adduint32(ssh, SSH2_OPEN_CONNECT_FAILED);
-		    ssh2_pkt_addstring(ssh, error);
-		    ssh2_pkt_addstring(ssh, "en");	/* language tag */
-		    ssh2_pkt_send(ssh);
-		    sfree(c);
-		} else {
-		    c->localid = alloc_channel_id(ssh);
-		    c->closes = 0;
-		    c->v.v2.locwindow = OUR_V2_WINSIZE;
-		    c->v.v2.remwindow = winsize;
-		    c->v.v2.remmaxpkt = pktsize;
-		    bufchain_init(&c->v.v2.outbuffer);
-		    add234(ssh->channels, c);
-		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
-		    ssh2_pkt_adduint32(ssh, c->remoteid);
-		    ssh2_pkt_adduint32(ssh, c->localid);
-		    ssh2_pkt_adduint32(ssh, c->v.v2.locwindow);
-		    ssh2_pkt_adduint32(ssh, 0x4000UL);	/* our max pkt size */
-		    ssh2_pkt_send(ssh);
-		}
-	    } else {
-		bombout(("Strange packet received: type %d", ssh->pktin.type));
-		crStopV;
-	    }
-	} else {
-	    /*
-	     * We have spare data. Add it to the channel buffer.
-	     */
-	    ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
-	    s->try_send = TRUE;
-	}
-	if (s->try_send) {
-	    int i;
-	    struct ssh_channel *c;
-	    /*
-	     * Try to send data on all channels if we can.
-	     */
-	    for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
-		int bufsize;
-		if (c->closes)
-		    continue;	       /* don't send on closing channels */
-		bufsize = ssh2_try_send(c);
-		if (bufsize == 0) {
-		    switch (c->type) {
-		      case CHAN_MAINSESSION:
-			/* stdin need not receive an unthrottle
-			 * notification since it will be polled */
-			break;
-		      case CHAN_X11:
-			x11_unthrottle(c->u.x11.s);
-			break;
-		      case CHAN_AGENT:
-			/* agent sockets are request/response and need no
-			 * buffer management */
-			break;
-		      case CHAN_SOCKDATA:
-			pfd_unthrottle(c->u.pfd.s);
-			break;
-		    }
-		}
-	    }
-	}
-    }
-
-    crFinishV;
-}
-
-/*
- * Handle the top-level SSH2 protocol.
- */
-static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt)
-{
-    if (do_ssh2_transport(ssh, in, inlen, ispkt) == 0)
-	return;
-    do_ssh2_authconn(ssh, in, inlen, ispkt);
-}
-
-/*
- * Called to set up the connection.
- *
- * Returns an error message, or NULL on success.
- */
-static const char *ssh_init(void *frontend_handle, void **backend_handle,
-			    Config *cfg,
-			    char *host, int port, char **realhost, int nodelay)
-{
-    const char *p;
-    Ssh ssh;
-
-    ssh = snew(struct ssh_tag);
-    ssh->cfg = *cfg;		       /* STRUCTURE COPY */
-    ssh->version = 0;		       /* when not ready yet */
-    ssh->s = NULL;
-    ssh->cipher = NULL;
-    ssh->v1_cipher_ctx = NULL;
-    ssh->crcda_ctx = NULL;
-    ssh->cscipher = NULL;
-    ssh->cs_cipher_ctx = NULL;
-    ssh->sccipher = NULL;
-    ssh->sc_cipher_ctx = NULL;
-    ssh->csmac = NULL;
-    ssh->cs_mac_ctx = NULL;
-    ssh->scmac = NULL;
-    ssh->sc_mac_ctx = NULL;
-    ssh->cscomp = NULL;
-    ssh->cs_comp_ctx = NULL;
-    ssh->sccomp = NULL;
-    ssh->sc_comp_ctx = NULL;
-    ssh->kex = NULL;
-    ssh->kex_ctx = NULL;
-    ssh->hostkey = NULL;
-    ssh->exitcode = -1;
-    ssh->state = SSH_STATE_PREPACKET;
-    ssh->size_needed = FALSE;
-    ssh->eof_needed = FALSE;
-    ssh->ldisc = NULL;
-    ssh->logctx = NULL;
-    {
-	static const struct Packet empty = { 0, 0, NULL, NULL, 0 };
-	ssh->pktin = ssh->pktout = empty;
-    }
-    ssh->deferred_send_data = NULL;
-    ssh->deferred_len = 0;
-    ssh->deferred_size = 0;
-    ssh->fallback_cmd = 0;
-    ssh->pkt_ctx = 0;
-    ssh->x11auth = NULL;
-    ssh->v1_compressing = FALSE;
-    ssh->v2_outgoing_sequence = 0;
-    ssh->ssh1_rdpkt_crstate = 0;
-    ssh->ssh2_rdpkt_crstate = 0;
-    ssh->do_ssh_init_crstate = 0;
-    ssh->ssh_gotdata_crstate = 0;
-    ssh->ssh1_protocol_crstate = 0;
-    ssh->do_ssh1_login_crstate = 0;
-    ssh->do_ssh2_transport_crstate = 0;
-    ssh->do_ssh2_authconn_crstate = 0;
-    ssh->do_ssh_init_state = NULL;
-    ssh->do_ssh1_login_state = NULL;
-    ssh->do_ssh2_transport_state = NULL;
-    ssh->do_ssh2_authconn_state = NULL;
-    ssh->mainchan = NULL;
-    ssh->throttled_all = 0;
-    ssh->v1_stdout_throttling = 0;
-
-    *backend_handle = ssh;
-
-#ifdef MSCRYPTOAPI
-    if (crypto_startup() == 0)
-	return "Microsoft high encryption pack not installed!";
-#endif
-
-    ssh->frontend = frontend_handle;
-    ssh->term_width = ssh->cfg.width;
-    ssh->term_height = ssh->cfg.height;
-
-    ssh->channels = NULL;
-    ssh->rportfwds = NULL;
-
-    ssh->send_ok = 0;
-    ssh->editing = 0;
-    ssh->echoing = 0;
-    ssh->v1_throttle_count = 0;
-    ssh->overall_bufsize = 0;
-    ssh->fallback_cmd = 0;
-
-    ssh->protocol = NULL;
-
-    p = connect_to_host(ssh, host, port, realhost, nodelay);
-    if (p != NULL)
-	return p;
-
-    return NULL;
-}
-
-static void ssh_free(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-    struct ssh_channel *c;
-    struct ssh_rportfwd *pf;
-
-    if (ssh->v1_cipher_ctx)
-	ssh->cipher->free_context(ssh->v1_cipher_ctx);
-    if (ssh->cs_cipher_ctx)
-	ssh->cscipher->free_context(ssh->cs_cipher_ctx);
-    if (ssh->sc_cipher_ctx)
-	ssh->sccipher->free_context(ssh->sc_cipher_ctx);
-    if (ssh->cs_mac_ctx)
-	ssh->csmac->free_context(ssh->cs_mac_ctx);
-    if (ssh->sc_mac_ctx)
-	ssh->scmac->free_context(ssh->sc_mac_ctx);
-    if (ssh->cs_comp_ctx)
-	ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
-    if (ssh->sc_comp_ctx)
-	ssh->sccomp->compress_cleanup(ssh->sc_comp_ctx);
-    if (ssh->kex_ctx)
-	dh_cleanup(ssh->kex_ctx);
-    sfree(ssh->savedhost);
-
-    if (ssh->channels) {
-	while ((c = delpos234(ssh->channels, 0)) != NULL) {
-	    switch (c->type) {
-	      case CHAN_X11:
-		if (c->u.x11.s != NULL)
-		    x11_close(c->u.x11.s);
-		break;
-	      case CHAN_SOCKDATA:
-		if (c->u.pfd.s != NULL)
-		    pfd_close(c->u.pfd.s);
-		break;
-	    }
-	    sfree(c);
-	}
-	freetree234(ssh->channels);
-    }
-
-    if (ssh->rportfwds) {
-	while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
-	    sfree(pf);
-	freetree234(ssh->rportfwds);
-    }
-    sfree(ssh->deferred_send_data);
-    if (ssh->x11auth)
-	x11_free_auth(ssh->x11auth);
-    sfree(ssh->do_ssh_init_state);
-    sfree(ssh->do_ssh1_login_state);
-    sfree(ssh->do_ssh2_transport_state);
-    sfree(ssh->do_ssh2_authconn_state);
-    if (ssh->pktout.data) {
-	sfree(ssh->pktout.data);
-	ssh->pktout.data = NULL;
-    }
-    if (ssh->pktin.data) {
-	sfree(ssh->pktin.data);
-	ssh->pktin.data = NULL;
-    }
-    if (ssh->crcda_ctx) {
-	crcda_free_context(ssh->crcda_ctx);
-	ssh->crcda_ctx = NULL;
-    }
-    if (ssh->logctx) {
-	log_free(ssh->logctx);
-	ssh->logctx = NULL;
-    }
-    if (ssh->s)
-	ssh_do_close(ssh);
-    sfree(ssh);
-}
-
-/*
- * Reconfigure the SSH backend.
- * 
- * Currently, this function does nothing very useful. In future,
- * however, we could do some handy things with it. For example, we
- * could make the port forwarding configurer active in the Change
- * Settings box, and this routine could close down existing
- * forwardings and open up new ones in response to changes.
- */
-static void ssh_reconfig(void *handle, Config *cfg)
-{
-    Ssh ssh = (Ssh) handle;
-    ssh->cfg = *cfg;		       /* STRUCTURE COPY */
-}
-
-/*
- * Called to send data down the Telnet connection.
- */
-static int ssh_send(void *handle, char *buf, int len)
-{
-    Ssh ssh = (Ssh) handle;
-
-    if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
-	return 0;
-
-    ssh->protocol(ssh, (unsigned char *)buf, len, 0);
-
-    return ssh_sendbuffer(ssh);
-}
-
-/*
- * Called to query the current amount of buffered stdin data.
- */
-static int ssh_sendbuffer(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-    int override_value;
-
-    if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
-	return 0;
-
-    /*
-     * If the SSH socket itself has backed up, add the total backup
-     * size on that to any individual buffer on the stdin channel.
-     */
-    override_value = 0;
-    if (ssh->throttled_all)
-	override_value = ssh->overall_bufsize;
-
-    if (ssh->version == 1) {
-	return override_value;
-    } else if (ssh->version == 2) {
-	if (!ssh->mainchan || ssh->mainchan->closes > 0)
-	    return override_value;
-	else
-	    return (override_value +
-		    bufchain_size(&ssh->mainchan->v.v2.outbuffer));
-    }
-
-    return 0;
-}
-
-/*
- * Called to set the size of the window from SSH's POV.
- */
-static void ssh_size(void *handle, int width, int height)
-{
-    Ssh ssh = (Ssh) handle;
-
-    ssh->term_width = width;
-    ssh->term_height = height;
-
-    switch (ssh->state) {
-      case SSH_STATE_BEFORE_SIZE:
-      case SSH_STATE_PREPACKET:
-      case SSH_STATE_CLOSED:
-	break;			       /* do nothing */
-      case SSH_STATE_INTERMED:
-	ssh->size_needed = TRUE;       /* buffer for later */
-	break;
-      case SSH_STATE_SESSION:
-	if (!ssh->cfg.nopty) {
-	    if (ssh->version == 1) {
-		send_packet(ssh, SSH1_CMSG_WINDOW_SIZE,
-			    PKT_INT, ssh->term_height,
-			    PKT_INT, ssh->term_width,
-			    PKT_INT, 0, PKT_INT, 0, PKT_END);
-	    } else {
-		ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
-		ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
-		ssh2_pkt_addstring(ssh, "window-change");
-		ssh2_pkt_addbool(ssh, 0);
-		ssh2_pkt_adduint32(ssh, ssh->term_width);
-		ssh2_pkt_adduint32(ssh, ssh->term_height);
-		ssh2_pkt_adduint32(ssh, 0);
-		ssh2_pkt_adduint32(ssh, 0);
-		ssh2_pkt_send(ssh);
-	    }
-	}
-	break;
-    }
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const struct telnet_special *ssh_get_specials(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-
-    if (ssh->version == 1) {
-	static const struct telnet_special ssh1_specials[] = {
-	    {"IGNORE message", TS_NOP},
-	    {NULL, 0}
-	};
-	return ssh1_specials;
-    } else if (ssh->version == 2) {
-	static const struct telnet_special ssh2_specials[] = {
-	    {"Break", TS_BRK},
-	    {"IGNORE message", TS_NOP},
-	    {NULL, 0}
-	};
-	return ssh2_specials;
-    } else
-	return NULL;
-}
-
-/*
- * Send Telnet special codes. TS_EOF is useful for `plink', so you
- * can send an EOF and collect resulting output (e.g. `plink
- * hostname sort').
- */
-static void ssh_special(void *handle, Telnet_Special code)
-{
-    Ssh ssh = (Ssh) handle;
-
-    if (code == TS_EOF) {
-	if (ssh->state != SSH_STATE_SESSION) {
-	    /*
-	     * Buffer the EOF in case we are pre-SESSION, so we can
-	     * send it as soon as we reach SESSION.
-	     */
-	    if (code == TS_EOF)
-		ssh->eof_needed = TRUE;
-	    return;
-	}
-	if (ssh->version == 1) {
-	    send_packet(ssh, SSH1_CMSG_EOF, PKT_END);
-	} else {
-	    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_EOF);
-	    ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
-	    ssh2_pkt_send(ssh);
-	}
-	logevent("Sent EOF message");
-    } else if (code == TS_PING || code == TS_NOP) {
-	if (ssh->state == SSH_STATE_CLOSED
-	    || ssh->state == SSH_STATE_PREPACKET) return;
-	if (ssh->version == 1) {
-	    if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
-		send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
-	} else {
-	    ssh2_pkt_init(ssh, SSH2_MSG_IGNORE);
-	    ssh2_pkt_addstring_start(ssh);
-	    ssh2_pkt_send(ssh);
-	}
-    } else if (code == TS_BRK) {
-	if (ssh->state == SSH_STATE_CLOSED
-	    || ssh->state == SSH_STATE_PREPACKET) return;
-	if (ssh->version == 1) {
-	    logevent("Unable to send BREAK signal in SSH1");
-	} else {
-	    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
-	    ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
-	    ssh2_pkt_addstring(ssh, "break");
-	    ssh2_pkt_addbool(ssh, 0);
-	    ssh2_pkt_adduint32(ssh, 0);   /* default break length */
-	    ssh2_pkt_send(ssh);
-	}
-    } else {
-	/* do nothing */
-    }
-}
-
-void *new_sock_channel(void *handle, Socket s)
-{
-    Ssh ssh = (Ssh) handle;
-    struct ssh_channel *c;
-    c = snew(struct ssh_channel);
-    c->ssh = ssh;
-
-    if (c) {
-	c->remoteid = -1;	       /* to be set when open confirmed */
-	c->localid = alloc_channel_id(ssh);
-	c->closes = 0;
-	c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
-	c->u.pfd.s = s;
-	bufchain_init(&c->v.v2.outbuffer);
-	add234(ssh->channels, c);
-    }
-    return c;
-}
-
-/*
- * This is called when stdout/stderr (the entity to which
- * from_backend sends data) manages to clear some backlog.
- */
-static void ssh_unthrottle(void *handle, int bufsize)
-{
-    Ssh ssh = (Ssh) handle;
-    if (ssh->version == 1) {
-	if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
-	    ssh->v1_stdout_throttling = 0;
-	    ssh1_throttle(ssh, -1);
-	}
-    } else {
-	if (ssh->mainchan && ssh->mainchan->closes == 0)
-	    ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize);
-    }
-}
-
-void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
-{
-    struct ssh_channel *c = (struct ssh_channel *)channel;
-    Ssh ssh = c->ssh;
-
-    logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port);
-
-    if (ssh->version == 1) {
-	send_packet(ssh, SSH1_MSG_PORT_OPEN,
-		    PKT_INT, c->localid,
-		    PKT_STR, hostname,
-		    PKT_INT, port,
-		    //PKT_STR, <org:orgport>,
-		    PKT_END);
-    } else {
-	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN);
-	ssh2_pkt_addstring(ssh, "direct-tcpip");
-	ssh2_pkt_adduint32(ssh, c->localid);
-	c->v.v2.locwindow = OUR_V2_WINSIZE;
-	ssh2_pkt_adduint32(ssh, c->v.v2.locwindow);/* our window size */
-	ssh2_pkt_adduint32(ssh, 0x4000UL);      /* our max pkt size */
-	ssh2_pkt_addstring(ssh, hostname);
-	ssh2_pkt_adduint32(ssh, 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.
-	 */
-	ssh2_pkt_addstring(ssh, "client-side-connection");
-	ssh2_pkt_adduint32(ssh, 0);
-	ssh2_pkt_send(ssh);
-    }
-}
-
-
-static Socket ssh_socket(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-    return ssh->s;
-}
-
-static int ssh_sendok(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-    return ssh->send_ok;
-}
-
-static int ssh_ldisc(void *handle, int option)
-{
-    Ssh ssh = (Ssh) handle;
-    if (option == LD_ECHO)
-	return ssh->echoing;
-    if (option == LD_EDIT)
-	return ssh->editing;
-    return FALSE;
-}
-
-static void ssh_provide_ldisc(void *handle, void *ldisc)
-{
-    Ssh ssh = (Ssh) handle;
-    ssh->ldisc = ldisc;
-}
-
-static void ssh_provide_logctx(void *handle, void *logctx)
-{
-    Ssh ssh = (Ssh) handle;
-    ssh->logctx = logctx;
-}
-
-static int ssh_return_exitcode(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-    if (ssh->s != NULL)
-        return -1;
-    else
-        return (ssh->exitcode >= 0 ? ssh->exitcode : 0);
-}
-
-/*
- * Gross hack: pscp will try to start SFTP but fall back to scp1 if
- * that fails. This variable is the means by which scp.c can reach
- * into the SSH code and find out which one it got.
- */
-extern int ssh_fallback_cmd(void *handle)
-{
-    Ssh ssh = (Ssh) handle;
-    return ssh->fallback_cmd;
-}
-
-Backend ssh_backend = {
-    ssh_init,
-    ssh_free,
-    ssh_reconfig,
-    ssh_send,
-    ssh_sendbuffer,
-    ssh_size,
-    ssh_special,
-    ssh_get_specials,
-    ssh_socket,
-    ssh_return_exitcode,
-    ssh_sendok,
-    ssh_ldisc,
-    ssh_provide_ldisc,
-    ssh_provide_logctx,
-    ssh_unthrottle,
-    22
-};
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "tree234.h"
+#include "ssh.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#define SSH1_MSG_DISCONNECT                       1	/* 0x1 */
+#define SSH1_SMSG_PUBLIC_KEY                      2	/* 0x2 */
+#define SSH1_CMSG_SESSION_KEY                     3	/* 0x3 */
+#define SSH1_CMSG_USER                            4	/* 0x4 */
+#define SSH1_CMSG_AUTH_RSA                        6	/* 0x6 */
+#define SSH1_SMSG_AUTH_RSA_CHALLENGE              7	/* 0x7 */
+#define SSH1_CMSG_AUTH_RSA_RESPONSE               8	/* 0x8 */
+#define SSH1_CMSG_AUTH_PASSWORD                   9	/* 0x9 */
+#define SSH1_CMSG_REQUEST_PTY                     10	/* 0xa */
+#define SSH1_CMSG_WINDOW_SIZE                     11	/* 0xb */
+#define SSH1_CMSG_EXEC_SHELL                      12	/* 0xc */
+#define SSH1_CMSG_EXEC_CMD                        13	/* 0xd */
+#define SSH1_SMSG_SUCCESS                         14	/* 0xe */
+#define SSH1_SMSG_FAILURE                         15	/* 0xf */
+#define SSH1_CMSG_STDIN_DATA                      16	/* 0x10 */
+#define SSH1_SMSG_STDOUT_DATA                     17	/* 0x11 */
+#define SSH1_SMSG_STDERR_DATA                     18	/* 0x12 */
+#define SSH1_CMSG_EOF                             19	/* 0x13 */
+#define SSH1_SMSG_EXIT_STATUS                     20	/* 0x14 */
+#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION        21	/* 0x15 */
+#define SSH1_MSG_CHANNEL_OPEN_FAILURE             22	/* 0x16 */
+#define SSH1_MSG_CHANNEL_DATA                     23	/* 0x17 */
+#define SSH1_MSG_CHANNEL_CLOSE                    24	/* 0x18 */
+#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION       25	/* 0x19 */
+#define SSH1_SMSG_X11_OPEN                        27	/* 0x1b */
+#define SSH1_CMSG_PORT_FORWARD_REQUEST            28	/* 0x1c */
+#define SSH1_MSG_PORT_OPEN                        29	/* 0x1d */
+#define SSH1_CMSG_AGENT_REQUEST_FORWARDING        30	/* 0x1e */
+#define SSH1_SMSG_AGENT_OPEN                      31	/* 0x1f */
+#define SSH1_MSG_IGNORE                           32	/* 0x20 */
+#define SSH1_CMSG_EXIT_CONFIRMATION               33	/* 0x21 */
+#define SSH1_CMSG_X11_REQUEST_FORWARDING          34	/* 0x22 */
+#define SSH1_CMSG_AUTH_RHOSTS_RSA                 35	/* 0x23 */
+#define SSH1_MSG_DEBUG                            36	/* 0x24 */
+#define SSH1_CMSG_REQUEST_COMPRESSION             37	/* 0x25 */
+#define SSH1_CMSG_AUTH_TIS                        39	/* 0x27 */
+#define SSH1_SMSG_AUTH_TIS_CHALLENGE              40	/* 0x28 */
+#define SSH1_CMSG_AUTH_TIS_RESPONSE               41	/* 0x29 */
+#define SSH1_CMSG_AUTH_CCARD                      70	/* 0x46 */
+#define SSH1_SMSG_AUTH_CCARD_CHALLENGE            71	/* 0x47 */
+#define SSH1_CMSG_AUTH_CCARD_RESPONSE             72	/* 0x48 */
+
+#define SSH1_AUTH_TIS                             5	/* 0x5 */
+#define SSH1_AUTH_CCARD                           16	/* 0x10 */
+
+#define SSH1_PROTOFLAG_SCREEN_NUMBER              1	/* 0x1 */
+/* Mask for protoflags we will echo back to server if seen */
+#define SSH1_PROTOFLAGS_SUPPORTED                 0	/* 0x1 */
+
+#define SSH2_MSG_DISCONNECT                       1	/* 0x1 */
+#define SSH2_MSG_IGNORE                           2	/* 0x2 */
+#define SSH2_MSG_UNIMPLEMENTED                    3	/* 0x3 */
+#define SSH2_MSG_DEBUG                            4	/* 0x4 */
+#define SSH2_MSG_SERVICE_REQUEST                  5	/* 0x5 */
+#define SSH2_MSG_SERVICE_ACCEPT                   6	/* 0x6 */
+#define SSH2_MSG_KEXINIT                          20	/* 0x14 */
+#define SSH2_MSG_NEWKEYS                          21	/* 0x15 */
+#define SSH2_MSG_KEXDH_INIT                       30	/* 0x1e */
+#define SSH2_MSG_KEXDH_REPLY                      31	/* 0x1f */
+#define SSH2_MSG_KEX_DH_GEX_REQUEST               30	/* 0x1e */
+#define SSH2_MSG_KEX_DH_GEX_GROUP                 31	/* 0x1f */
+#define SSH2_MSG_KEX_DH_GEX_INIT                  32	/* 0x20 */
+#define SSH2_MSG_KEX_DH_GEX_REPLY                 33	/* 0x21 */
+#define SSH2_MSG_USERAUTH_REQUEST                 50	/* 0x32 */
+#define SSH2_MSG_USERAUTH_FAILURE                 51	/* 0x33 */
+#define SSH2_MSG_USERAUTH_SUCCESS                 52	/* 0x34 */
+#define SSH2_MSG_USERAUTH_BANNER                  53	/* 0x35 */
+#define SSH2_MSG_USERAUTH_PK_OK                   60	/* 0x3c */
+#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60	/* 0x3c */
+#define SSH2_MSG_USERAUTH_INFO_REQUEST            60	/* 0x3c */
+#define SSH2_MSG_USERAUTH_INFO_RESPONSE           61	/* 0x3d */
+#define SSH2_MSG_GLOBAL_REQUEST                   80	/* 0x50 */
+#define SSH2_MSG_REQUEST_SUCCESS                  81	/* 0x51 */
+#define SSH2_MSG_REQUEST_FAILURE                  82	/* 0x52 */
+#define SSH2_MSG_CHANNEL_OPEN                     90	/* 0x5a */
+#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91	/* 0x5b */
+#define SSH2_MSG_CHANNEL_OPEN_FAILURE             92	/* 0x5c */
+#define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93	/* 0x5d */
+#define SSH2_MSG_CHANNEL_DATA                     94	/* 0x5e */
+#define SSH2_MSG_CHANNEL_EXTENDED_DATA            95	/* 0x5f */
+#define SSH2_MSG_CHANNEL_EOF                      96	/* 0x60 */
+#define SSH2_MSG_CHANNEL_CLOSE                    97	/* 0x61 */
+#define SSH2_MSG_CHANNEL_REQUEST                  98	/* 0x62 */
+#define SSH2_MSG_CHANNEL_SUCCESS                  99	/* 0x63 */
+#define SSH2_MSG_CHANNEL_FAILURE                  100	/* 0x64 */
+
+/*
+ * Packet type contexts, so that ssh2_pkt_type can correctly decode
+ * the ambiguous type numbers back into the correct type strings.
+ */
+#define SSH2_PKTCTX_DHGROUP1         0x0001
+#define SSH2_PKTCTX_DHGEX            0x0002
+#define SSH2_PKTCTX_PUBLICKEY        0x0010
+#define SSH2_PKTCTX_PASSWORD         0x0020
+#define SSH2_PKTCTX_KBDINTER         0x0040
+#define SSH2_PKTCTX_AUTH_MASK        0x00F0
+
+#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1	/* 0x1 */
+#define SSH2_DISCONNECT_PROTOCOL_ERROR            2	/* 0x2 */
+#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED       3	/* 0x3 */
+#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4	/* 0x4 */
+#define SSH2_DISCONNECT_MAC_ERROR                 5	/* 0x5 */
+#define SSH2_DISCONNECT_COMPRESSION_ERROR         6	/* 0x6 */
+#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE     7	/* 0x7 */
+#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8	/* 0x8 */
+#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE   9	/* 0x9 */
+#define SSH2_DISCONNECT_CONNECTION_LOST           10	/* 0xa */
+#define SSH2_DISCONNECT_BY_APPLICATION            11	/* 0xb */
+#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS      12	/* 0xc */
+#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER    13	/* 0xd */
+#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14	/* 0xe */
+#define SSH2_DISCONNECT_ILLEGAL_USER_NAME         15	/* 0xf */
+
+static const char *const ssh2_disconnect_reasons[] = {
+    NULL,
+    "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT",
+    "SSH_DISCONNECT_PROTOCOL_ERROR",
+    "SSH_DISCONNECT_KEY_EXCHANGE_FAILED",
+    "SSH_DISCONNECT_HOST_AUTHENTICATION_FAILED",
+    "SSH_DISCONNECT_MAC_ERROR",
+    "SSH_DISCONNECT_COMPRESSION_ERROR",
+    "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE",
+    "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED",
+    "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE",
+    "SSH_DISCONNECT_CONNECTION_LOST",
+    "SSH_DISCONNECT_BY_APPLICATION",
+    "SSH_DISCONNECT_TOO_MANY_CONNECTIONS",
+    "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER",
+    "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE",
+    "SSH_DISCONNECT_ILLEGAL_USER_NAME",
+};
+
+#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED     1	/* 0x1 */
+#define SSH2_OPEN_CONNECT_FAILED                  2	/* 0x2 */
+#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE            3	/* 0x3 */
+#define SSH2_OPEN_RESOURCE_SHORTAGE               4	/* 0x4 */
+
+#define SSH2_EXTENDED_DATA_STDERR                 1	/* 0x1 */
+
+/*
+ * Various remote-bug flags.
+ */
+#define BUG_CHOKES_ON_SSH1_IGNORE                 1
+#define BUG_SSH2_HMAC                             2
+#define BUG_NEEDS_SSH1_PLAIN_PASSWORD        	  4
+#define BUG_CHOKES_ON_RSA	        	  8
+#define BUG_SSH2_RSA_PADDING	        	 16
+#define BUG_SSH2_DERIVEKEY                       32
+#define BUG_SSH2_DH_GEX                          64
+#define BUG_SSH2_PK_SESSIONID                   128
+
+#define translate(x) if (type == x) return #x
+#define translatec(x,ctx) if (type == x && (pkt_ctx & ctx)) return #x
+static char *ssh1_pkt_type(int type)
+{
+    translate(SSH1_MSG_DISCONNECT);
+    translate(SSH1_SMSG_PUBLIC_KEY);
+    translate(SSH1_CMSG_SESSION_KEY);
+    translate(SSH1_CMSG_USER);
+    translate(SSH1_CMSG_AUTH_RSA);
+    translate(SSH1_SMSG_AUTH_RSA_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_RSA_RESPONSE);
+    translate(SSH1_CMSG_AUTH_PASSWORD);
+    translate(SSH1_CMSG_REQUEST_PTY);
+    translate(SSH1_CMSG_WINDOW_SIZE);
+    translate(SSH1_CMSG_EXEC_SHELL);
+    translate(SSH1_CMSG_EXEC_CMD);
+    translate(SSH1_SMSG_SUCCESS);
+    translate(SSH1_SMSG_FAILURE);
+    translate(SSH1_CMSG_STDIN_DATA);
+    translate(SSH1_SMSG_STDOUT_DATA);
+    translate(SSH1_SMSG_STDERR_DATA);
+    translate(SSH1_CMSG_EOF);
+    translate(SSH1_SMSG_EXIT_STATUS);
+    translate(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH1_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH1_MSG_CHANNEL_DATA);
+    translate(SSH1_MSG_CHANNEL_CLOSE);
+    translate(SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+    translate(SSH1_SMSG_X11_OPEN);
+    translate(SSH1_CMSG_PORT_FORWARD_REQUEST);
+    translate(SSH1_MSG_PORT_OPEN);
+    translate(SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+    translate(SSH1_SMSG_AGENT_OPEN);
+    translate(SSH1_MSG_IGNORE);
+    translate(SSH1_CMSG_EXIT_CONFIRMATION);
+    translate(SSH1_CMSG_X11_REQUEST_FORWARDING);
+    translate(SSH1_CMSG_AUTH_RHOSTS_RSA);
+    translate(SSH1_MSG_DEBUG);
+    translate(SSH1_CMSG_REQUEST_COMPRESSION);
+    translate(SSH1_CMSG_AUTH_TIS);
+    translate(SSH1_SMSG_AUTH_TIS_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_TIS_RESPONSE);
+    translate(SSH1_CMSG_AUTH_CCARD);
+    translate(SSH1_SMSG_AUTH_CCARD_CHALLENGE);
+    translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
+    return "unknown";
+}
+static char *ssh2_pkt_type(int pkt_ctx, int type)
+{
+    translate(SSH2_MSG_DISCONNECT);
+    translate(SSH2_MSG_IGNORE);
+    translate(SSH2_MSG_UNIMPLEMENTED);
+    translate(SSH2_MSG_DEBUG);
+    translate(SSH2_MSG_SERVICE_REQUEST);
+    translate(SSH2_MSG_SERVICE_ACCEPT);
+    translate(SSH2_MSG_KEXINIT);
+    translate(SSH2_MSG_NEWKEYS);
+    translatec(SSH2_MSG_KEXDH_INIT, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEXDH_REPLY, SSH2_PKTCTX_DHGROUP1);
+    translatec(SSH2_MSG_KEX_DH_GEX_REQUEST, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_GROUP, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_INIT, SSH2_PKTCTX_DHGEX);
+    translatec(SSH2_MSG_KEX_DH_GEX_REPLY, SSH2_PKTCTX_DHGEX);
+    translate(SSH2_MSG_USERAUTH_REQUEST);
+    translate(SSH2_MSG_USERAUTH_FAILURE);
+    translate(SSH2_MSG_USERAUTH_SUCCESS);
+    translate(SSH2_MSG_USERAUTH_BANNER);
+    translatec(SSH2_MSG_USERAUTH_PK_OK, SSH2_PKTCTX_PUBLICKEY);
+    translatec(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, SSH2_PKTCTX_PASSWORD);
+    translatec(SSH2_MSG_USERAUTH_INFO_REQUEST, SSH2_PKTCTX_KBDINTER);
+    translatec(SSH2_MSG_USERAUTH_INFO_RESPONSE, SSH2_PKTCTX_KBDINTER);
+    translate(SSH2_MSG_GLOBAL_REQUEST);
+    translate(SSH2_MSG_REQUEST_SUCCESS);
+    translate(SSH2_MSG_REQUEST_FAILURE);
+    translate(SSH2_MSG_CHANNEL_OPEN);
+    translate(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+    translate(SSH2_MSG_CHANNEL_OPEN_FAILURE);
+    translate(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+    translate(SSH2_MSG_CHANNEL_DATA);
+    translate(SSH2_MSG_CHANNEL_EXTENDED_DATA);
+    translate(SSH2_MSG_CHANNEL_EOF);
+    translate(SSH2_MSG_CHANNEL_CLOSE);
+    translate(SSH2_MSG_CHANNEL_REQUEST);
+    translate(SSH2_MSG_CHANNEL_SUCCESS);
+    translate(SSH2_MSG_CHANNEL_FAILURE);
+    return "unknown";
+}
+#undef translate
+#undef translatec
+
+#define GET_32BIT(cp) \
+    (((unsigned long)(unsigned char)(cp)[0] << 24) | \
+    ((unsigned long)(unsigned char)(cp)[1] << 16) | \
+    ((unsigned long)(unsigned char)(cp)[2] << 8) | \
+    ((unsigned long)(unsigned char)(cp)[3]))
+
+#define PUT_32BIT(cp, value) { \
+    (cp)[0] = (unsigned char)((value) >> 24); \
+    (cp)[1] = (unsigned char)((value) >> 16); \
+    (cp)[2] = (unsigned char)((value) >> 8); \
+    (cp)[3] = (unsigned char)(value); }
+
+enum { PKT_END, PKT_INT, PKT_CHAR, PKT_DATA, PKT_STR, PKT_BIGNUM };
+
+/*
+ * Coroutine mechanics for the sillier bits of the code. If these
+ * macros look impenetrable to you, you might find it helpful to
+ * read
+ * 
+ *   http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
+ * 
+ * which explains the theory behind these macros.
+ * 
+ * In particular, if you are getting `case expression not constant'
+ * errors when building with MS Visual Studio, this is because MS's
+ * Edit and Continue debugging feature causes their compiler to
+ * violate ANSI C. To disable Edit and Continue debugging:
+ * 
+ *  - right-click ssh.c in the FileView
+ *  - click Settings
+ *  - select the C/C++ tab and the General category
+ *  - under `Debug info:', select anything _other_ than `Program
+ *    Database for Edit and Continue'.
+ */
+#define crBegin(v)	{ int *crLine = &v; switch(v) { case 0:;
+#define crState(t) \
+    struct t *s; \
+    if (!ssh->t) ssh->t = snew(struct t); \
+    s = ssh->t;
+#define crFinish(z)	} *crLine = 0; return (z); }
+#define crFinishV	} *crLine = 0; return; }
+#define crReturn(z)	\
+	do {\
+	    *crLine =__LINE__; return (z); case __LINE__:;\
+	} while (0)
+#define crReturnV	\
+	do {\
+	    *crLine=__LINE__; return; case __LINE__:;\
+	} while (0)
+#define crStop(z)	do{ *crLine = 0; return (z); }while(0)
+#define crStopV		do{ *crLine = 0; return; }while(0)
+#define crWaitUntil(c)	do { crReturn(0); } while (!(c))
+#define crWaitUntilV(c)	do { crReturnV; } while (!(c))
+
+typedef struct ssh_tag *Ssh;
+
+static void ssh2_pkt_init(Ssh, int pkt_type);
+static void ssh2_pkt_addbool(Ssh, unsigned char value);
+static void ssh2_pkt_adduint32(Ssh, unsigned long value);
+static void ssh2_pkt_addstring_start(Ssh);
+static void ssh2_pkt_addstring_str(Ssh, char *data);
+static void ssh2_pkt_addstring_data(Ssh, char *data, int len);
+static void ssh2_pkt_addstring(Ssh, char *data);
+static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
+static void ssh2_pkt_addmp(Ssh, Bignum b);
+static int ssh2_pkt_construct(Ssh);
+static void ssh2_pkt_send(Ssh);
+static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, int ispkt);
+static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt);
+
+/*
+ * Buffer management constants. There are several of these for
+ * various different purposes:
+ * 
+ *  - SSH1_BUFFER_LIMIT is the amount of backlog that must build up
+ *    on a local data stream before we throttle the whole SSH
+ *    connection (in SSH1 only). Throttling the whole connection is
+ *    pretty drastic so we set this high in the hope it won't
+ *    happen very often.
+ * 
+ *  - SSH_MAX_BACKLOG is the amount of backlog that must build up
+ *    on the SSH connection itself before we defensively throttle
+ *    _all_ local data streams. This is pretty drastic too (though
+ *    thankfully unlikely in SSH2 since the window mechanism should
+ *    ensure that the server never has any need to throttle its end
+ *    of the connection), so we set this high as well.
+ * 
+ *  - OUR_V2_WINSIZE is the maximum window size we present on SSH2
+ *    channels.
+ */
+
+#define SSH1_BUFFER_LIMIT 32768
+#define SSH_MAX_BACKLOG 32768
+#define OUR_V2_WINSIZE 16384
+
+const static struct ssh_kex *kex_algs[] = {
+    &ssh_diffiehellman_gex,
+    &ssh_diffiehellman
+};
+
+const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
+
+static void *nullmac_make_context(void)
+{
+    return NULL;
+}
+static void nullmac_free_context(void *handle)
+{
+}
+static void nullmac_key(void *handle, unsigned char *key)
+{
+}
+static void nullmac_generate(void *handle, unsigned char *blk, int len,
+			     unsigned long seq)
+{
+}
+static int nullmac_verify(void *handle, unsigned char *blk, int len,
+			  unsigned long seq)
+{
+    return 1;
+}
+const static struct ssh_mac ssh_mac_none = {
+    nullmac_make_context, nullmac_free_context, nullmac_key,
+    nullmac_generate, nullmac_verify, "none", 0
+};
+const static struct ssh_mac *macs[] = {
+    &ssh_sha1, &ssh_md5, &ssh_mac_none
+};
+const static struct ssh_mac *buggymacs[] = {
+    &ssh_sha1_buggy, &ssh_md5, &ssh_mac_none
+};
+
+static void *ssh_comp_none_init(void)
+{
+    return NULL;
+}
+static void ssh_comp_none_cleanup(void *handle)
+{
+}
+static int ssh_comp_none_block(void *handle, unsigned char *block, int len,
+			       unsigned char **outblock, int *outlen)
+{
+    return 0;
+}
+static int ssh_comp_none_disable(void *handle)
+{
+    return 0;
+}
+const static struct ssh_compress ssh_comp_none = {
+    "none",
+    ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+    ssh_comp_none_init, ssh_comp_none_cleanup, ssh_comp_none_block,
+    ssh_comp_none_disable, NULL
+};
+extern const struct ssh_compress ssh_zlib;
+const static struct ssh_compress *compressions[] = {
+    &ssh_zlib, &ssh_comp_none
+};
+
+enum {				       /* channel types */
+    CHAN_MAINSESSION,
+    CHAN_X11,
+    CHAN_AGENT,
+    CHAN_SOCKDATA,
+    CHAN_SOCKDATA_DORMANT	       /* one the remote hasn't confirmed */
+};
+
+/*
+ * 2-3-4 tree storing channels.
+ */
+struct ssh_channel {
+    Ssh ssh;			       /* pointer back to main context */
+    unsigned remoteid, localid;
+    int type;
+    /*
+     * In SSH1, this value contains four bits:
+     * 
+     *   1   We have sent SSH1_MSG_CHANNEL_CLOSE.
+     *   2   We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+     *   4   We have received SSH1_MSG_CHANNEL_CLOSE.
+     *   8   We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+     * 
+     * A channel is completely finished with when all four bits are set.
+     */
+    int closes;
+    union {
+	struct ssh1_data_channel {
+	    int throttling;
+	} v1;
+	struct ssh2_data_channel {
+	    bufchain outbuffer;
+	    unsigned remwindow, remmaxpkt;
+	    unsigned locwindow;
+	} v2;
+    } v;
+    union {
+	struct ssh_agent_channel {
+	    unsigned char *message;
+	    unsigned char msglen[4];
+	    int lensofar, totallen;
+	} a;
+	struct ssh_x11_channel {
+	    Socket s;
+	} x11;
+	struct ssh_pfd_channel {
+	    Socket s;
+	} pfd;
+    } u;
+};
+
+/*
+ * 2-3-4 tree storing remote->local port forwardings. SSH 1 and SSH
+ * 2 use this structure in different ways, reflecting SSH 2's
+ * altogether saner approach to port forwarding.
+ * 
+ * In SSH 1, you arrange a remote forwarding by sending the server
+ * the remote port number, and the local destination host:port.
+ * When a connection comes in, the server sends you back that
+ * host:port pair, and you connect to it. This is a ready-made
+ * security hole if you're not on the ball: a malicious server
+ * could send you back _any_ host:port pair, so if you trustingly
+ * connect to the address it gives you then you've just opened the
+ * entire inside of your corporate network just by connecting
+ * through it to a dodgy SSH server. Hence, we must store a list of
+ * host:port pairs we _are_ trying to forward to, and reject a
+ * connection request from the server if it's not in the list.
+ * 
+ * In SSH 2, each side of the connection minds its own business and
+ * doesn't send unnecessary information to the other. You arrange a
+ * remote forwarding by sending the server just the remote port
+ * number. When a connection comes in, the server tells you which
+ * of its ports was connected to; and _you_ have to remember what
+ * local host:port pair went with that port number.
+ * 
+ * Hence: in SSH 1 this structure stores host:port pairs we intend
+ * to allow connections to, and is indexed by those host:port
+ * pairs. In SSH 2 it stores a mapping from source port to
+ * destination host:port pair, and is indexed by source port.
+ */
+struct ssh_rportfwd {
+    unsigned sport, dport;
+    char dhost[256];
+};
+
+struct Packet {
+    long length;
+    int type;
+    unsigned char *data;
+    unsigned char *body;
+    long savedpos;
+    long maxlen;
+};
+
+static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt);
+static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt);
+static void ssh_size(void *handle, int width, int height);
+static void ssh_special(void *handle, Telnet_Special);
+static int ssh2_try_send(struct ssh_channel *c);
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
+static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
+static void ssh2_set_window(struct ssh_channel *c, unsigned newwin);
+static int ssh_sendbuffer(void *handle);
+static void ssh_do_close(Ssh ssh);
+
+struct rdpkt1_state_tag {
+    long len, pad, biglen, to_read;
+    unsigned long realcrc, gotcrc;
+    unsigned char *p;
+    int i;
+    int chunk;
+};
+
+struct rdpkt2_state_tag {
+    long len, pad, payload, packetlen, maclen;
+    int i;
+    int cipherblk;
+    unsigned long incoming_sequence;
+};
+
+struct ssh_tag {
+    const struct plug_function_table *fn;
+    /* the above field _must_ be first in the structure */
+
+    SHA_State exhash, exhashbase;
+
+    Socket s;
+
+    void *ldisc;
+    void *logctx;
+
+    unsigned char session_key[32];
+    int v1_compressing;
+    int v1_remote_protoflags;
+    int v1_local_protoflags;
+    int agentfwd_enabled;
+    int X11_fwd_enabled;
+    int remote_bugs;
+    const struct ssh_cipher *cipher;
+    void *v1_cipher_ctx;
+    void *crcda_ctx;
+    const struct ssh2_cipher *cscipher, *sccipher;
+    void *cs_cipher_ctx, *sc_cipher_ctx;
+    const struct ssh_mac *csmac, *scmac;
+    void *cs_mac_ctx, *sc_mac_ctx;
+    const struct ssh_compress *cscomp, *sccomp;
+    void *cs_comp_ctx, *sc_comp_ctx;
+    const struct ssh_kex *kex;
+    const struct ssh_signkey *hostkey;
+    unsigned char v2_session_id[20];
+    void *kex_ctx;
+
+    char *savedhost;
+    int savedport;
+    int send_ok;
+    int echoing, editing;
+
+    void *frontend;
+
+    int term_width, term_height;
+
+    tree234 *channels;		       /* indexed by local id */
+    struct ssh_channel *mainchan;      /* primary session channel */
+    int exitcode;
+
+    tree234 *rportfwds;
+
+    enum {
+	SSH_STATE_PREPACKET,
+	SSH_STATE_BEFORE_SIZE,
+	SSH_STATE_INTERMED,
+	SSH_STATE_SESSION,
+	SSH_STATE_CLOSED
+    } state;
+
+    int size_needed, eof_needed;
+
+    struct Packet pktin;
+    struct Packet pktout;
+    unsigned char *deferred_send_data;
+    int deferred_len, deferred_size;
+
+    /*
+     * Gross hack: pscp will try to start SFTP but fall back to
+     * scp1 if that fails. This variable is the means by which
+     * scp.c can reach into the SSH code and find out which one it
+     * got.
+     */
+    int fallback_cmd;
+
+    /*
+     * Used for username and password input.
+     */
+    char *userpass_input_buffer;
+    int userpass_input_buflen;
+    int userpass_input_bufpos;
+    int userpass_input_echo;
+
+    char *portfwd_strptr;
+    int pkt_ctx;
+
+    void *x11auth;
+
+    int version;
+    int v1_throttle_count;
+    int overall_bufsize;
+    int throttled_all;
+    int v1_stdout_throttling;
+    int v2_outgoing_sequence;
+
+    int ssh1_rdpkt_crstate;
+    int ssh2_rdpkt_crstate;
+    int do_ssh_init_crstate;
+    int ssh_gotdata_crstate;
+    int ssh1_protocol_crstate;
+    int do_ssh1_login_crstate;
+    int do_ssh2_transport_crstate;
+    int do_ssh2_authconn_crstate;
+
+    void *do_ssh_init_state;
+    void *do_ssh1_login_state;
+    void *do_ssh2_transport_state;
+    void *do_ssh2_authconn_state;
+
+    struct rdpkt1_state_tag rdpkt1_state;
+    struct rdpkt2_state_tag rdpkt2_state;
+
+    void (*protocol) (Ssh ssh, unsigned char *in, int inlen, int ispkt);
+    int (*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
+
+    /*
+     * We maintain a full _copy_ of a Config structure here, not
+     * merely a pointer to it. That way, when we're passed a new
+     * one for reconfiguration, we can check the differences and
+     * potentially reconfigure port forwardings etc in mid-session.
+     */
+    Config cfg;
+
+    /*
+     * Used to transfer data back from async agent callbacks.
+     */
+    void *agent_response;
+    int agent_response_len;
+};
+
+#define logevent(s) logevent(ssh->frontend, s)
+
+/* logevent, only printf-formatted. */
+static void logeventf(Ssh ssh, const char *fmt, ...)
+{
+    va_list ap;
+    char *buf;
+
+    va_start(ap, fmt);
+    buf = dupvprintf(fmt, ap);
+    va_end(ap);
+    logevent(buf);
+    sfree(buf);
+}
+
+#define bombout(msg) \
+    do { \
+        char *text = dupprintf msg; \
+	ssh_do_close(ssh); \
+        logevent(text); \
+        connection_fatal(ssh->frontend, "%s", text); \
+        sfree(text); \
+    } while (0)
+
+static int ssh_channelcmp(void *av, void *bv)
+{
+    struct ssh_channel *a = (struct ssh_channel *) av;
+    struct ssh_channel *b = (struct ssh_channel *) bv;
+    if (a->localid < b->localid)
+	return -1;
+    if (a->localid > b->localid)
+	return +1;
+    return 0;
+}
+static int ssh_channelfind(void *av, void *bv)
+{
+    unsigned *a = (unsigned *) av;
+    struct ssh_channel *b = (struct ssh_channel *) bv;
+    if (*a < b->localid)
+	return -1;
+    if (*a > b->localid)
+	return +1;
+    return 0;
+}
+
+static int ssh_rportcmp_ssh1(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->dhost, b->dhost)) != 0)
+	return i < 0 ? -1 : +1;
+    if (a->dport > b->dport)
+	return +1;
+    if (a->dport < b->dport)
+	return -1;
+    return 0;
+}
+
+static int ssh_rportcmp_ssh2(void *av, void *bv)
+{
+    struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+    struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+
+    if (a->sport > b->sport)
+	return +1;
+    if (a->sport < b->sport)
+	return -1;
+    return 0;
+}
+
+static int alloc_channel_id(Ssh ssh)
+{
+    const unsigned CHANNEL_NUMBER_OFFSET = 256;
+    unsigned low, high, mid;
+    int tsize;
+    struct ssh_channel *c;
+
+    /*
+     * First-fit allocation of channel numbers: always pick the
+     * lowest unused one. To do this, binary-search using the
+     * counted B-tree to find the largest channel ID which is in a
+     * contiguous sequence from the beginning. (Precisely
+     * everything in that sequence must have ID equal to its tree
+     * index plus CHANNEL_NUMBER_OFFSET.)
+     */
+    tsize = count234(ssh->channels);
+
+    low = -1;
+    high = tsize;
+    while (high - low > 1) {
+	mid = (high + low) / 2;
+	c = index234(ssh->channels, mid);
+	if (c->localid == mid + CHANNEL_NUMBER_OFFSET)
+	    low = mid;		       /* this one is fine */
+	else
+	    high = mid;		       /* this one is past it */
+    }
+    /*
+     * Now low points to either -1, or the tree index of the
+     * largest ID in the initial sequence.
+     */
+    {
+	unsigned i = low + 1 + CHANNEL_NUMBER_OFFSET;
+	assert(NULL == find234(ssh->channels, &i, ssh_channelfind));
+    }
+    return low + 1 + CHANNEL_NUMBER_OFFSET;
+}
+
+static void c_write(Ssh ssh, const char *buf, int len)
+{
+    if ((flags & FLAG_STDERR)) {
+	int i;
+	for (i = 0; i < len; i++)
+	    if (buf[i] != '\r')
+		fputc(buf[i], stderr);
+	return;
+    }
+    from_backend(ssh->frontend, 1, buf, len);
+}
+
+static void c_write_untrusted(Ssh ssh, const char *buf, int len)
+{
+    int i;
+    for (i = 0; i < len; i++) {
+	if (buf[i] == '\n')
+	    c_write(ssh, "\r\n", 2);
+	else if ((buf[i] & 0x60) || (buf[i] == '\r'))
+	    c_write(ssh, buf + i, 1);
+    }
+}
+
+static void c_write_str(Ssh ssh, const char *buf)
+{
+    c_write(ssh, buf, strlen(buf));
+}
+
+/*
+ * Collect incoming data in the incoming packet buffer.
+ * Decipher and verify the packet when it is completely read.
+ * Drop SSH1_MSG_DEBUG and SSH1_MSG_IGNORE packets.
+ * Update the *data and *datalen variables.
+ * Return the additional nr of bytes needed, or 0 when
+ * a complete packet is available.
+ */
+static int ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+{
+    struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
+
+    crBegin(ssh->ssh1_rdpkt_crstate);
+
+  next_packet:
+
+    ssh->pktin.type = 0;
+    ssh->pktin.length = 0;
+
+    for (st->i = st->len = 0; st->i < 4; st->i++) {
+	while ((*datalen) == 0)
+	    crReturn(4 - st->i);
+	st->len = (st->len << 8) + **data;
+	(*data)++, (*datalen)--;
+    }
+
+    st->pad = 8 - (st->len % 8);
+    st->biglen = st->len + st->pad;
+    ssh->pktin.length = st->len - 5;
+
+    if (ssh->pktin.maxlen < st->biglen) {
+	ssh->pktin.maxlen = st->biglen;
+	ssh->pktin.data = sresize(ssh->pktin.data, st->biglen + APIEXTRA,
+				  unsigned char);
+    }
+
+    st->to_read = st->biglen;
+    st->p = ssh->pktin.data;
+    while (st->to_read > 0) {
+	st->chunk = st->to_read;
+	while ((*datalen) == 0)
+	    crReturn(st->to_read);
+	if (st->chunk > (*datalen))
+	    st->chunk = (*datalen);
+	memcpy(st->p, *data, st->chunk);
+	*data += st->chunk;
+	*datalen -= st->chunk;
+	st->p += st->chunk;
+	st->to_read -= st->chunk;
+    }
+
+    if (ssh->cipher && detect_attack(ssh->crcda_ctx, ssh->pktin.data,
+				     st->biglen, NULL)) {
+        bombout(("Network attack (CRC compensation) detected!"));
+        crStop(0);
+    }
+
+    if (ssh->cipher)
+	ssh->cipher->decrypt(ssh->v1_cipher_ctx, ssh->pktin.data, st->biglen);
+
+    st->realcrc = crc32_compute(ssh->pktin.data, st->biglen - 4);
+    st->gotcrc = GET_32BIT(ssh->pktin.data + st->biglen - 4);
+    if (st->gotcrc != st->realcrc) {
+	bombout(("Incorrect CRC received on packet"));
+	crStop(0);
+    }
+
+    ssh->pktin.body = ssh->pktin.data + st->pad + 1;
+
+    if (ssh->v1_compressing) {
+	unsigned char *decompblk;
+	int decomplen;
+	if (!zlib_decompress_block(ssh->sc_comp_ctx,
+				   ssh->pktin.body - 1, ssh->pktin.length + 1,
+				   &decompblk, &decomplen)) {
+	    bombout(("Zlib decompression encountered invalid data"));
+	    crStop(0);
+	}
+
+	if (ssh->pktin.maxlen < st->pad + decomplen) {
+	    ssh->pktin.maxlen = st->pad + decomplen;
+	    ssh->pktin.data = sresize(ssh->pktin.data,
+				      ssh->pktin.maxlen + APIEXTRA,
+				      unsigned char);
+	    ssh->pktin.body = ssh->pktin.data + st->pad + 1;
+	}
+
+	memcpy(ssh->pktin.body - 1, decompblk, decomplen);
+	sfree(decompblk);
+	ssh->pktin.length = decomplen - 1;
+    }
+
+    ssh->pktin.type = ssh->pktin.body[-1];
+
+    if (ssh->logctx)
+	log_packet(ssh->logctx,
+		   PKT_INCOMING, ssh->pktin.type,
+		   ssh1_pkt_type(ssh->pktin.type),
+		   ssh->pktin.body, ssh->pktin.length);
+
+    if (ssh->pktin.type == SSH1_SMSG_STDOUT_DATA ||
+	ssh->pktin.type == SSH1_SMSG_STDERR_DATA ||
+	ssh->pktin.type == SSH1_MSG_DEBUG ||
+	ssh->pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE ||
+	ssh->pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+	long stringlen = GET_32BIT(ssh->pktin.body);
+	if (stringlen + 4 != ssh->pktin.length) {
+	    bombout(("Received data packet with bogus string length"));
+	    crStop(0);
+	}
+    }
+
+    if (ssh->pktin.type == SSH1_MSG_DEBUG) {
+	/* log debug message */
+	char buf[512];
+	int stringlen = GET_32BIT(ssh->pktin.body);
+	strcpy(buf, "Remote debug message: ");
+	if (stringlen > 480)
+	    stringlen = 480;
+	memcpy(buf + 8, ssh->pktin.body + 4, stringlen);
+	buf[8 + stringlen] = '\0';
+	logevent(buf);
+	goto next_packet;
+    } else if (ssh->pktin.type == SSH1_MSG_IGNORE) {
+	/* do nothing */
+	goto next_packet;
+    }
+
+    if (ssh->pktin.type == SSH1_MSG_DISCONNECT) {
+	/* log reason code in disconnect message */
+	char buf[256];
+	unsigned msglen = GET_32BIT(ssh->pktin.body);
+	unsigned nowlen;
+	strcpy(buf, "Remote sent disconnect: ");
+	nowlen = strlen(buf);
+	if (msglen > sizeof(buf) - nowlen - 1)
+	    msglen = sizeof(buf) - nowlen - 1;
+	memcpy(buf + nowlen, ssh->pktin.body + 4, msglen);
+	buf[nowlen + msglen] = '\0';
+	/* logevent(buf); (this is now done within the bombout macro) */
+	bombout(("Server sent disconnect message:\n\"%s\"", buf+nowlen));
+	crStop(0);
+    }
+
+    crFinish(0);
+}
+
+static int ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
+{
+    struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
+
+    crBegin(ssh->ssh2_rdpkt_crstate);
+
+  next_packet:
+    ssh->pktin.type = 0;
+    ssh->pktin.length = 0;
+    if (ssh->sccipher)
+	st->cipherblk = ssh->sccipher->blksize;
+    else
+	st->cipherblk = 8;
+    if (st->cipherblk < 8)
+	st->cipherblk = 8;
+
+    if (ssh->pktin.maxlen < st->cipherblk) {
+	ssh->pktin.maxlen = st->cipherblk;
+	ssh->pktin.data = sresize(ssh->pktin.data, st->cipherblk + APIEXTRA,
+				  unsigned char);
+    }
+
+    /*
+     * Acquire and decrypt the first block of the packet. This will
+     * contain the length and padding details.
+     */
+    for (st->i = st->len = 0; st->i < st->cipherblk; st->i++) {
+	while ((*datalen) == 0)
+	    crReturn(st->cipherblk - st->i);
+	ssh->pktin.data[st->i] = *(*data)++;
+	(*datalen)--;
+    }
+
+    if (ssh->sccipher)
+	ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+			       ssh->pktin.data, st->cipherblk);
+
+    /*
+     * Now get the length and padding figures.
+     */
+    st->len = GET_32BIT(ssh->pktin.data);
+    st->pad = ssh->pktin.data[4];
+
+    /*
+     * _Completely_ silly lengths should be stomped on before they
+     * do us any more damage.
+     */
+    if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) {
+	bombout(("Incoming packet was garbled on decryption"));
+	crStop(0);
+    }
+
+    /*
+     * This enables us to deduce the payload length.
+     */
+    st->payload = st->len - st->pad - 1;
+
+    ssh->pktin.length = st->payload + 5;
+
+    /*
+     * So now we can work out the total packet length.
+     */
+    st->packetlen = st->len + 4;
+    st->maclen = ssh->scmac ? ssh->scmac->len : 0;
+
+    /*
+     * Adjust memory allocation if packet is too big.
+     */
+    if (ssh->pktin.maxlen < st->packetlen + st->maclen) {
+	ssh->pktin.maxlen = st->packetlen + st->maclen;
+	ssh->pktin.data = sresize(ssh->pktin.data,
+				  ssh->pktin.maxlen + APIEXTRA,
+				  unsigned char);
+    }
+
+    /*
+     * Read and decrypt the remainder of the packet.
+     */
+    for (st->i = st->cipherblk; st->i < st->packetlen + st->maclen;
+	 st->i++) {
+	while ((*datalen) == 0)
+	    crReturn(st->packetlen + st->maclen - st->i);
+	ssh->pktin.data[st->i] = *(*data)++;
+	(*datalen)--;
+    }
+    /* Decrypt everything _except_ the MAC. */
+    if (ssh->sccipher)
+	ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
+			       ssh->pktin.data + st->cipherblk,
+			       st->packetlen - st->cipherblk);
+
+    /*
+     * Check the MAC.
+     */
+    if (ssh->scmac
+	&& !ssh->scmac->verify(ssh->sc_mac_ctx, ssh->pktin.data, st->len + 4,
+			       st->incoming_sequence)) {
+	bombout(("Incorrect MAC received on packet"));
+	crStop(0);
+    }
+    st->incoming_sequence++;	       /* whether or not we MACed */
+
+    /*
+     * Decompress packet payload.
+     */
+    {
+	unsigned char *newpayload;
+	int newlen;
+	if (ssh->sccomp &&
+	    ssh->sccomp->decompress(ssh->sc_comp_ctx,
+				    ssh->pktin.data + 5, ssh->pktin.length - 5,
+				    &newpayload, &newlen)) {
+	    if (ssh->pktin.maxlen < newlen + 5) {
+		ssh->pktin.maxlen = newlen + 5;
+		ssh->pktin.data = sresize(ssh->pktin.data,
+					  ssh->pktin.maxlen + APIEXTRA,
+					  unsigned char);
+	    }
+	    ssh->pktin.length = 5 + newlen;
+	    memcpy(ssh->pktin.data + 5, newpayload, newlen);
+	    sfree(newpayload);
+	}
+    }
+
+    ssh->pktin.savedpos = 6;
+    ssh->pktin.type = ssh->pktin.data[5];
+
+    if (ssh->logctx)
+	log_packet(ssh->logctx, PKT_INCOMING, ssh->pktin.type,
+		   ssh2_pkt_type(ssh->pkt_ctx, ssh->pktin.type),
+		   ssh->pktin.data+6, ssh->pktin.length-6);
+
+    switch (ssh->pktin.type) {
+        /*
+         * These packets we must handle instantly.
+         */
+      case SSH2_MSG_DISCONNECT:
+        {
+            /* log reason code in disconnect message */
+            char *buf;
+	    int nowlen;
+            int reason = GET_32BIT(ssh->pktin.data + 6);
+            unsigned msglen = GET_32BIT(ssh->pktin.data + 10);
+
+            if (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) {
+                buf = dupprintf("Received disconnect message (%s)",
+				ssh2_disconnect_reasons[reason]);
+            } else {
+                buf = dupprintf("Received disconnect message (unknown"
+				" type %d)", reason);
+            }
+            logevent(buf);
+	    sfree(buf);
+            buf = dupprintf("Disconnection message text: %n%.*s",
+			    &nowlen, msglen, ssh->pktin.data + 14);
+            logevent(buf);
+            bombout(("Server sent disconnect message\ntype %d (%s):\n\"%s\"",
+                     reason,
+                     (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+                     ssh2_disconnect_reasons[reason] : "unknown",
+                     buf+nowlen));
+	    sfree(buf);
+            crStop(0);
+        }
+        break;
+      case SSH2_MSG_IGNORE:
+	goto next_packet;
+      case SSH2_MSG_DEBUG:
+	{
+	    /* log the debug message */
+	    char buf[512];
+	    /* int display = ssh->pktin.body[6]; */
+	    int stringlen = GET_32BIT(ssh->pktin.data+7);
+	    int prefix;
+	    strcpy(buf, "Remote debug message: ");
+	    prefix = strlen(buf);
+	    if (stringlen > (int)(sizeof(buf)-prefix-1))
+		stringlen = sizeof(buf)-prefix-1;
+	    memcpy(buf + prefix, ssh->pktin.data + 11, stringlen);
+	    buf[prefix + stringlen] = '\0';
+	    logevent(buf);
+	}
+        goto next_packet;              /* FIXME: print the debug message */
+
+        /*
+         * These packets we need do nothing about here.
+         */
+      case SSH2_MSG_UNIMPLEMENTED:
+      case SSH2_MSG_SERVICE_REQUEST:
+      case SSH2_MSG_SERVICE_ACCEPT:
+      case SSH2_MSG_KEXINIT:
+      case SSH2_MSG_NEWKEYS:
+      case SSH2_MSG_KEXDH_INIT:
+      case SSH2_MSG_KEXDH_REPLY:
+      /* case SSH2_MSG_KEX_DH_GEX_REQUEST: duplicate case value */
+      /* case SSH2_MSG_KEX_DH_GEX_GROUP: duplicate case value */
+      case SSH2_MSG_KEX_DH_GEX_INIT:
+      case SSH2_MSG_KEX_DH_GEX_REPLY:
+      case SSH2_MSG_USERAUTH_REQUEST:
+      case SSH2_MSG_USERAUTH_FAILURE:
+      case SSH2_MSG_USERAUTH_SUCCESS:
+      case SSH2_MSG_USERAUTH_BANNER:
+      case SSH2_MSG_USERAUTH_PK_OK:
+      /* case SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: duplicate case value */
+      /* case SSH2_MSG_USERAUTH_INFO_REQUEST: duplicate case value */
+      case SSH2_MSG_USERAUTH_INFO_RESPONSE:
+      case SSH2_MSG_GLOBAL_REQUEST:
+      case SSH2_MSG_REQUEST_SUCCESS:
+      case SSH2_MSG_REQUEST_FAILURE:
+      case SSH2_MSG_CHANNEL_OPEN:
+      case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+      case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+      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:
+        break;
+
+        /*
+         * For anything else we send SSH2_MSG_UNIMPLEMENTED.
+         */
+      default:
+	ssh2_pkt_init(ssh, SSH2_MSG_UNIMPLEMENTED);
+	ssh2_pkt_adduint32(ssh, st->incoming_sequence - 1);
+	ssh2_pkt_send(ssh);
+        break;
+    }
+
+    crFinish(0);
+}
+
+static void ssh1_pktout_size(Ssh ssh, int len)
+{
+    int pad, biglen;
+
+    len += 5;			       /* type and CRC */
+    pad = 8 - (len % 8);
+    biglen = len + pad;
+
+    ssh->pktout.length = len - 5;
+    if (ssh->pktout.maxlen < biglen) {
+	ssh->pktout.maxlen = biglen;
+#ifdef MSCRYPTOAPI
+	/* Allocate enough buffer space for extra block
+	 * for MS CryptEncrypt() */
+	ssh->pktout.data = sresize(ssh->pktout.data, biglen + 12,
+				   unsigned char);
+#else
+	ssh->pktout.data = sresize(ssh->pktout.data, biglen + 4,
+				   unsigned char);
+#endif
+    }
+    ssh->pktout.body = ssh->pktout.data + 4 + pad + 1;
+}
+
+static void s_wrpkt_start(Ssh ssh, int type, int len)
+{
+    ssh1_pktout_size(ssh, len);
+    ssh->pktout.type = type;
+}
+
+static int s_wrpkt_prepare(Ssh ssh)
+{
+    int pad, biglen, i;
+    unsigned long crc;
+#ifdef __SC__
+    /*
+     * XXX various versions of SC (including 8.8.4) screw up the
+     * register allocation in this function and use the same register
+     * (D6) for len and as a temporary, with predictable results.  The
+     * following sledgehammer prevents this.
+     */
+    volatile
+#endif
+    int len;
+
+    ssh->pktout.body[-1] = ssh->pktout.type;
+
+    if (ssh->logctx)
+	log_packet(ssh->logctx, PKT_OUTGOING, ssh->pktout.type,
+		   ssh1_pkt_type(ssh->pktout.type),
+		   ssh->pktout.body, ssh->pktout.length);
+
+    if (ssh->v1_compressing) {
+	unsigned char *compblk;
+	int complen;
+	zlib_compress_block(ssh->cs_comp_ctx,
+			    ssh->pktout.body - 1, ssh->pktout.length + 1,
+			    &compblk, &complen);
+	ssh1_pktout_size(ssh, complen - 1);
+	memcpy(ssh->pktout.body - 1, compblk, complen);
+	sfree(compblk);
+    }
+
+    len = ssh->pktout.length + 5;	       /* type and CRC */
+    pad = 8 - (len % 8);
+    biglen = len + pad;
+
+    for (i = 0; i < pad; i++)
+	ssh->pktout.data[i + 4] = random_byte();
+    crc = crc32_compute(ssh->pktout.data + 4, biglen - 4);
+    PUT_32BIT(ssh->pktout.data + biglen, crc);
+    PUT_32BIT(ssh->pktout.data, len);
+
+    if (ssh->cipher)
+	ssh->cipher->encrypt(ssh->v1_cipher_ctx, ssh->pktout.data + 4, biglen);
+
+    return biglen + 4;
+}
+
+static void s_wrpkt(Ssh ssh)
+{
+    int len, backlog;
+    len = s_wrpkt_prepare(ssh);
+    backlog = sk_write(ssh->s, (char *)ssh->pktout.data, len);
+    if (backlog > SSH_MAX_BACKLOG)
+	ssh_throttle_all(ssh, 1, backlog);
+}
+
+static void s_wrpkt_defer(Ssh ssh)
+{
+    int len;
+    len = s_wrpkt_prepare(ssh);
+    if (ssh->deferred_len + len > ssh->deferred_size) {
+	ssh->deferred_size = ssh->deferred_len + len + 128;
+	ssh->deferred_send_data = sresize(ssh->deferred_send_data,
+					  ssh->deferred_size,
+					  unsigned char);
+    }
+    memcpy(ssh->deferred_send_data + ssh->deferred_len, ssh->pktout.data, len);
+    ssh->deferred_len += len;
+}
+
+/*
+ * Construct a packet with the specified contents.
+ */
+static void construct_packet(Ssh ssh, int pkttype, va_list ap1, va_list ap2)
+{
+    unsigned char *p, *argp, argchar;
+    unsigned long argint;
+    int pktlen, argtype, arglen;
+    Bignum bn;
+
+    pktlen = 0;
+    while ((argtype = va_arg(ap1, int)) != PKT_END) {
+	switch (argtype) {
+	  case PKT_INT:
+	    (void) va_arg(ap1, int);
+	    pktlen += 4;
+	    break;
+	  case PKT_CHAR:
+	    (void) va_arg(ap1, int);
+	    pktlen++;
+	    break;
+	  case PKT_DATA:
+	    (void) va_arg(ap1, unsigned char *);
+	    arglen = va_arg(ap1, int);
+	    pktlen += arglen;
+	    break;
+	  case PKT_STR:
+	    argp = va_arg(ap1, unsigned char *);
+	    arglen = strlen((char *)argp);
+	    pktlen += 4 + arglen;
+	    break;
+	  case PKT_BIGNUM:
+	    bn = va_arg(ap1, Bignum);
+	    pktlen += ssh1_bignum_length(bn);
+	    break;
+	  default:
+	    assert(0);
+	}
+    }
+
+    s_wrpkt_start(ssh, pkttype, pktlen);
+    p = ssh->pktout.body;
+
+    while ((argtype = va_arg(ap2, int)) != PKT_END) {
+	switch (argtype) {
+	  case PKT_INT:
+	    argint = va_arg(ap2, int);
+	    PUT_32BIT(p, argint);
+	    p += 4;
+	    break;
+	  case PKT_CHAR:
+	    argchar = (unsigned char) va_arg(ap2, int);
+	    *p = argchar;
+	    p++;
+	    break;
+	  case PKT_DATA:
+	    argp = va_arg(ap2, unsigned char *);
+	    arglen = va_arg(ap2, int);
+	    memcpy(p, argp, arglen);
+	    p += arglen;
+	    break;
+	  case PKT_STR:
+	    argp = va_arg(ap2, unsigned char *);
+	    arglen = strlen((char *)argp);
+	    PUT_32BIT(p, arglen);
+	    memcpy(p + 4, argp, arglen);
+	    p += 4 + arglen;
+	    break;
+	  case PKT_BIGNUM:
+	    bn = va_arg(ap2, Bignum);
+	    p += ssh1_write_bignum(p, bn);
+	    break;
+	}
+    }
+}
+
+static void send_packet(Ssh ssh, int pkttype, ...)
+{
+    va_list ap1, ap2;
+    va_start(ap1, pkttype);
+    va_start(ap2, pkttype);
+    construct_packet(ssh, pkttype, ap1, ap2);
+    s_wrpkt(ssh);
+}
+
+static void defer_packet(Ssh ssh, int pkttype, ...)
+{
+    va_list ap1, ap2;
+    va_start(ap1, pkttype);
+    va_start(ap2, pkttype);
+    construct_packet(ssh, pkttype, ap1, ap2);
+    s_wrpkt_defer(ssh);
+}
+
+static int ssh_versioncmp(char *a, 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;
+}
+
+/*
+ * Utility routines for putting an SSH-protocol `string' and
+ * `uint32' into a SHA state.
+ */
+#include <stdio.h>
+static void sha_string(SHA_State * s, void *str, int len)
+{
+    unsigned char lenblk[4];
+    PUT_32BIT(lenblk, len);
+    SHA_Bytes(s, lenblk, 4);
+    SHA_Bytes(s, str, len);
+}
+
+static void sha_uint32(SHA_State * s, unsigned i)
+{
+    unsigned char intblk[4];
+    PUT_32BIT(intblk, i);
+    SHA_Bytes(s, intblk, 4);
+}
+
+/*
+ * SSH2 packet construction functions.
+ */
+static void ssh2_pkt_ensure(Ssh ssh, int length)
+{
+    if (ssh->pktout.maxlen < length) {
+	ssh->pktout.maxlen = length + 256;
+	ssh->pktout.data = sresize(ssh->pktout.data,
+				   ssh->pktout.maxlen + APIEXTRA,
+				   unsigned char);
+	if (!ssh->pktout.data)
+	    fatalbox("Out of memory");
+    }
+}
+static void ssh2_pkt_adddata(Ssh ssh, void *data, int len)
+{
+    ssh->pktout.length += len;
+    ssh2_pkt_ensure(ssh, ssh->pktout.length);
+    memcpy(ssh->pktout.data + ssh->pktout.length - len, data, len);
+}
+static void ssh2_pkt_addbyte(Ssh ssh, unsigned char byte)
+{
+    ssh2_pkt_adddata(ssh, &byte, 1);
+}
+static void ssh2_pkt_init(Ssh ssh, int pkt_type)
+{
+    ssh->pktout.length = 5;
+    ssh2_pkt_addbyte(ssh, (unsigned char) pkt_type);
+}
+static void ssh2_pkt_addbool(Ssh ssh, unsigned char value)
+{
+    ssh2_pkt_adddata(ssh, &value, 1);
+}
+static void ssh2_pkt_adduint32(Ssh ssh, unsigned long value)
+{
+    unsigned char x[4];
+    PUT_32BIT(x, value);
+    ssh2_pkt_adddata(ssh, x, 4);
+}
+static void ssh2_pkt_addstring_start(Ssh ssh)
+{
+    ssh2_pkt_adduint32(ssh, 0);
+    ssh->pktout.savedpos = ssh->pktout.length;
+}
+static void ssh2_pkt_addstring_str(Ssh ssh, char *data)
+{
+    ssh2_pkt_adddata(ssh, data, strlen(data));
+    PUT_32BIT(ssh->pktout.data + ssh->pktout.savedpos - 4,
+	      ssh->pktout.length - ssh->pktout.savedpos);
+}
+static void ssh2_pkt_addstring_data(Ssh ssh, char *data, int len)
+{
+    ssh2_pkt_adddata(ssh, data, len);
+    PUT_32BIT(ssh->pktout.data + ssh->pktout.savedpos - 4,
+	      ssh->pktout.length - ssh->pktout.savedpos);
+}
+static void ssh2_pkt_addstring(Ssh ssh, char *data)
+{
+    ssh2_pkt_addstring_start(ssh);
+    ssh2_pkt_addstring_str(ssh, data);
+}
+static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
+{
+    unsigned char *p;
+    int i, n = (bignum_bitcount(b) + 7) / 8;
+    p = snewn(n + 1, unsigned char);
+    if (!p)
+	fatalbox("out of memory");
+    p[0] = 0;
+    for (i = 1; i <= n; i++)
+	p[i] = bignum_byte(b, n - i);
+    i = 0;
+    while (i <= n && p[i] == 0 && (p[i + 1] & 0x80) == 0)
+	i++;
+    memmove(p, p + i, n + 1 - i);
+    *len = n + 1 - i;
+    return p;
+}
+static void ssh2_pkt_addmp(Ssh ssh, Bignum b)
+{
+    unsigned char *p;
+    int len;
+    p = ssh2_mpint_fmt(b, &len);
+    ssh2_pkt_addstring_start(ssh);
+    ssh2_pkt_addstring_data(ssh, (char *)p, len);
+    sfree(p);
+}
+
+/*
+ * Construct an SSH2 final-form packet: compress it, encrypt it,
+ * put the MAC on it. Final packet, ready to be sent, is stored in
+ * ssh->pktout.data. Total length is returned.
+ */
+static int ssh2_pkt_construct(Ssh ssh)
+{
+    int cipherblk, maclen, padding, i;
+
+    if (ssh->logctx)
+	log_packet(ssh->logctx, PKT_OUTGOING, ssh->pktout.data[5],
+		   ssh2_pkt_type(ssh->pkt_ctx, ssh->pktout.data[5]),
+		   ssh->pktout.data + 6, ssh->pktout.length - 6);
+
+    /*
+     * Compress packet payload.
+     */
+    {
+	unsigned char *newpayload;
+	int newlen;
+	if (ssh->cscomp &&
+	    ssh->cscomp->compress(ssh->cs_comp_ctx, ssh->pktout.data + 5,
+				  ssh->pktout.length - 5,
+				  &newpayload, &newlen)) {
+	    ssh->pktout.length = 5;
+	    ssh2_pkt_adddata(ssh, 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.
+     */
+    cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8;  /* block size */
+    cipherblk = cipherblk < 8 ? 8 : cipherblk;	/* or 8 if blksize < 8 */
+    padding = 4;
+    padding +=
+	(cipherblk - (ssh->pktout.length + padding) % cipherblk) % cipherblk;
+    maclen = ssh->csmac ? ssh->csmac->len : 0;
+    ssh2_pkt_ensure(ssh, ssh->pktout.length + padding + maclen);
+    ssh->pktout.data[4] = padding;
+    for (i = 0; i < padding; i++)
+	ssh->pktout.data[ssh->pktout.length + i] = random_byte();
+    PUT_32BIT(ssh->pktout.data, ssh->pktout.length + padding - 4);
+    if (ssh->csmac)
+	ssh->csmac->generate(ssh->cs_mac_ctx, ssh->pktout.data,
+			     ssh->pktout.length + padding,
+			     ssh->v2_outgoing_sequence);
+    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
+
+    if (ssh->cscipher)
+	ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
+			       ssh->pktout.data, ssh->pktout.length + padding);
+
+    /* Ready-to-send packet starts at ssh->pktout.data. We return length. */
+    return ssh->pktout.length + padding + maclen;
+}
+
+/*
+ * Construct and send an SSH2 packet immediately.
+ */
+static void ssh2_pkt_send(Ssh ssh)
+{
+    int len;
+    int backlog;
+    len = ssh2_pkt_construct(ssh);
+    backlog = sk_write(ssh->s, (char *)ssh->pktout.data, len);
+    if (backlog > SSH_MAX_BACKLOG)
+	ssh_throttle_all(ssh, 1, backlog);
+}
+
+/*
+ * Construct an SSH2 packet and add it to a deferred data block.
+ * Useful for sending multiple packets in a single sk_write() call,
+ * to prevent a traffic-analysing listener from being able to work
+ * out the length of any particular packet (such as the password
+ * packet).
+ * 
+ * Note that because SSH2 sequence-numbers its packets, this can
+ * NOT be used as an m4-style `defer' allowing packets to be
+ * constructed in one order and sent in another.
+ */
+static void ssh2_pkt_defer(Ssh ssh)
+{
+    int len = ssh2_pkt_construct(ssh);
+    if (ssh->deferred_len + len > ssh->deferred_size) {
+	ssh->deferred_size = ssh->deferred_len + len + 128;
+	ssh->deferred_send_data = sresize(ssh->deferred_send_data,
+					  ssh->deferred_size,
+					  unsigned char);
+    }
+    memcpy(ssh->deferred_send_data + ssh->deferred_len, ssh->pktout.data, len);
+    ssh->deferred_len += len;
+}
+
+/*
+ * Send the whole deferred data block constructed by
+ * ssh2_pkt_defer() or SSH1's defer_packet().
+ */
+static void ssh_pkt_defersend(Ssh ssh)
+{
+    int backlog;
+    backlog = sk_write(ssh->s, (char *)ssh->deferred_send_data,
+		       ssh->deferred_len);
+    ssh->deferred_len = ssh->deferred_size = 0;
+    sfree(ssh->deferred_send_data);
+    ssh->deferred_send_data = NULL;
+    if (backlog > SSH_MAX_BACKLOG)
+	ssh_throttle_all(ssh, 1, backlog);
+}
+
+#if 0
+void bndebug(char *string, Bignum b)
+{
+    unsigned char *p;
+    int i, len;
+    p = ssh2_mpint_fmt(b, &len);
+    debug(("%s", string));
+    for (i = 0; i < len; i++)
+	debug((" %02x", p[i]));
+    debug(("\n"));
+    sfree(p);
+}
+#endif
+
+static void sha_mpint(SHA_State * s, Bignum b)
+{
+    unsigned char *p;
+    int len;
+    p = ssh2_mpint_fmt(b, &len);
+    sha_string(s, p, len);
+    sfree(p);
+}
+
+/*
+ * SSH2 packet decode functions.
+ */
+static unsigned long ssh2_pkt_getuint32(Ssh ssh)
+{
+    unsigned long value;
+    if (ssh->pktin.length - ssh->pktin.savedpos < 4)
+	return 0;		       /* arrgh, no way to decline (FIXME?) */
+    value = GET_32BIT(ssh->pktin.data + ssh->pktin.savedpos);
+    ssh->pktin.savedpos += 4;
+    return value;
+}
+static int ssh2_pkt_getbool(Ssh ssh)
+{
+    unsigned long value;
+    if (ssh->pktin.length - ssh->pktin.savedpos < 1)
+	return 0;		       /* arrgh, no way to decline (FIXME?) */
+    value = ssh->pktin.data[ssh->pktin.savedpos] != 0;
+    ssh->pktin.savedpos++;
+    return value;
+}
+static void ssh2_pkt_getstring(Ssh ssh, char **p, int *length)
+{
+    int len;
+    *p = NULL;
+    *length = 0;
+    if (ssh->pktin.length - ssh->pktin.savedpos < 4)
+	return;
+    len = GET_32BIT(ssh->pktin.data + ssh->pktin.savedpos);
+    if (len < 0)
+	return;
+    *length = len;
+    ssh->pktin.savedpos += 4;
+    if (ssh->pktin.length - ssh->pktin.savedpos < *length)
+	return;
+    *p = (char *)(ssh->pktin.data + ssh->pktin.savedpos);
+    ssh->pktin.savedpos += *length;
+}
+static Bignum ssh2_pkt_getmp(Ssh ssh)
+{
+    char *p;
+    int length;
+    Bignum b;
+
+    ssh2_pkt_getstring(ssh, &p, &length);
+    if (!p)
+	return NULL;
+    if (p[0] & 0x80) {
+	bombout(("internal error: Can't handle negative mpints"));
+	return NULL;
+    }
+    b = bignum_from_bytes((unsigned char *)p, length);
+    return b;
+}
+
+/*
+ * Helper function to add an SSH2 signature blob to a packet.
+ * Expects to be shown the public key blob as well as the signature
+ * blob. Normally works just like ssh2_pkt_addstring, but will
+ * fiddle with the signature packet if necessary for
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_add_sigblob(Ssh ssh, void *pkblob_v, int pkblob_len,
+			     void *sigblob_v, int sigblob_len)
+{
+    unsigned char *pkblob = (unsigned char *)pkblob_v;
+    unsigned char *sigblob = (unsigned char *)sigblob_v;
+
+    /* 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 ((ssh->remote_bugs & BUG_SSH2_RSA_PADDING) &&
+	(GET_32BIT(pkblob) == 7 && !memcmp(pkblob+4, "ssh-rsa", 7))) {
+	int pos, len, siglen;
+
+	/*
+	 * Find the byte length of the modulus.
+	 */
+
+	pos = 4+7;		       /* skip over "ssh-rsa" */
+	pos += 4 + GET_32BIT(pkblob+pos);   /* skip over exponent */
+	len = GET_32BIT(pkblob+pos);   /* find length of modulus */
+	pos += 4;		       /* find modulus itself */
+	while (len > 0 && pkblob[pos] == 0)
+	    len--, pos++;
+	/* debug(("modulus length is %d\n", len)); */
+
+	/*
+	 * Now find the signature integer.
+	 */
+	pos = 4+7;		       /* skip over "ssh-rsa" */
+	siglen = GET_32BIT(sigblob+pos);
+	/* debug(("signature length is %d\n", siglen)); */
+
+	if (len != siglen) {
+	    unsigned char newlen[4];
+	    ssh2_pkt_addstring_start(ssh);
+	    ssh2_pkt_addstring_data(ssh, (char *)sigblob, pos);
+	    /* dmemdump(sigblob, pos); */
+	    pos += 4;		       /* point to start of actual sig */
+	    PUT_32BIT(newlen, len);
+	    ssh2_pkt_addstring_data(ssh, (char *)newlen, 4);
+	    /* dmemdump(newlen, 4); */
+	    newlen[0] = 0;
+	    while (len-- > siglen) {
+		ssh2_pkt_addstring_data(ssh, (char *)newlen, 1);
+		/* dmemdump(newlen, 1); */
+	    }
+	    ssh2_pkt_addstring_data(ssh, (char *)(sigblob+pos), siglen);
+	    /* dmemdump(sigblob+pos, siglen); */
+	    return;
+	}
+
+	/* Otherwise fall through and do it the easy way. */
+    }
+
+    ssh2_pkt_addstring_start(ssh);
+    ssh2_pkt_addstring_data(ssh, (char *)sigblob, sigblob_len);
+}
+
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(Ssh ssh, char *vstring)
+{
+    char *imp;			       /* pointer to implementation part */
+    imp = vstring;
+    imp += strcspn(imp, "-");
+    if (*imp) imp++;
+    imp += strcspn(imp, "-");
+    if (*imp) imp++;
+
+    ssh->remote_bugs = 0;
+
+    if (ssh->cfg.sshbug_ignore1 == FORCE_ON ||
+	(ssh->cfg.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")))) {
+	/*
+	 * These versions don't support SSH1_MSG_IGNORE, so we have
+	 * to use a different defence against password length
+	 * sniffing.
+	 */
+	ssh->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+	logevent("We believe remote version has SSH1 ignore bug");
+    }
+
+    if (ssh->cfg.sshbug_plainpw1 == FORCE_ON ||
+	(ssh->cfg.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.
+	 */
+	ssh->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+	logevent("We believe remote version needs a plain SSH1 password");
+    }
+
+    if (ssh->cfg.sshbug_rsa1 == FORCE_ON ||
+	(ssh->cfg.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.
+	 */
+	ssh->remote_bugs |= BUG_CHOKES_ON_RSA;
+	logevent("We believe remote version can't handle RSA authentication");
+    }
+
+    if (ssh->cfg.sshbug_hmac2 == FORCE_ON ||
+	(ssh->cfg.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.
+	 */
+	ssh->remote_bugs |= BUG_SSH2_HMAC;
+	logevent("We believe remote version has SSH2 HMAC bug");
+    }
+
+    if (ssh->cfg.sshbug_derivekey2 == FORCE_ON ||
+	(ssh->cfg.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).
+	 */
+	ssh->remote_bugs |= BUG_SSH2_DERIVEKEY;
+	logevent("We believe remote version has SSH2 key-derivation bug");
+    }
+
+    if (ssh->cfg.sshbug_rsapad2 == FORCE_ON ||
+	(ssh->cfg.sshbug_rsapad2 == AUTO &&
+	 (wc_match("OpenSSH_2.[5-9]*", imp) ||
+	  wc_match("OpenSSH_3.[0-2]*", imp)))) {
+	/*
+	 * These versions have the SSH2 RSA padding bug.
+	 */
+	ssh->remote_bugs |= BUG_SSH2_RSA_PADDING;
+	logevent("We believe remote version has SSH2 RSA padding bug");
+    }
+
+    if (ssh->cfg.sshbug_pksessid2 == FORCE_ON ||
+	(ssh->cfg.sshbug_pksessid2 == AUTO &&
+	 wc_match("OpenSSH_2.[0-2]*", imp))) {
+	/*
+	 * These versions have the SSH2 session-ID bug in
+	 * public-key authentication.
+	 */
+	ssh->remote_bugs |= BUG_SSH2_PK_SESSIONID;
+	logevent("We believe remote version has SSH2 public-key-session-ID bug");
+    }
+
+    if (ssh->cfg.sshbug_dhgex2 == FORCE_ON) {
+	/*
+	 * User specified the SSH2 DH GEX bug.
+	 */
+	ssh->remote_bugs |= BUG_SSH2_DH_GEX;
+	logevent("We believe remote version has SSH2 DH group exchange bug");
+    }
+}
+
+static int do_ssh_init(Ssh ssh, unsigned char c)
+{
+    struct do_ssh_init_state {
+	int vslen;
+	char version[10];
+	char *vstring;
+	int vstrsize;
+	int i;
+	int proto1, proto2;
+    };
+    crState(do_ssh_init_state);
+
+    crBegin(ssh->do_ssh_init_crstate);
+
+    /* Search for the string "SSH-" in the input. */
+    s->i = 0;
+    while (1) {
+	static const int transS[] = { 1, 2, 2, 1 };
+	static const int transH[] = { 0, 0, 3, 0 };
+	static const int transminus[] = { 0, 0, 0, -1 };
+	if (c == 'S')
+	    s->i = transS[s->i];
+	else if (c == 'H')
+	    s->i = transH[s->i];
+	else if (c == '-')
+	    s->i = transminus[s->i];
+	else
+	    s->i = 0;
+	if (s->i < 0)
+	    break;
+	crReturn(1);		       /* get another character */
+    }
+
+    s->vstrsize = 16;
+    s->vstring = snewn(s->vstrsize, char);
+    strcpy(s->vstring, "SSH-");
+    s->vslen = 4;
+    s->i = 0;
+    while (1) {
+	crReturn(1);		       /* get another char */
+	if (s->vslen >= s->vstrsize - 1) {
+	    s->vstrsize += 16;
+	    s->vstring = sresize(s->vstring, s->vstrsize, char);
+	}
+	s->vstring[s->vslen++] = c;
+	if (s->i >= 0) {
+	    if (c == '-') {
+		s->version[s->i] = '\0';
+		s->i = -1;
+	    } else if (s->i < sizeof(s->version) - 1)
+		s->version[s->i++] = c;
+	} else if (c == '\012')
+	    break;
+    }
+
+    ssh->agentfwd_enabled = FALSE;
+    ssh->rdpkt2_state.incoming_sequence = 0;
+
+    s->vstring[s->vslen] = 0;
+    s->vstring[strcspn(s->vstring, "\r\n")] = '\0';/* remove EOL chars */
+    {
+	char *vlog;
+	vlog = snewn(20 + s->vslen, char);
+	sprintf(vlog, "Server version: %s", s->vstring);
+	logevent(vlog);
+	sfree(vlog);
+    }
+    ssh_detect_bugs(ssh, s->vstring);
+
+    /*
+     * Decide which SSH protocol version to support.
+     */
+
+    /* Anything strictly below "2.0" means protocol 1 is supported. */
+    s->proto1 = ssh_versioncmp(s->version, "2.0") < 0;
+    /* Anything greater or equal to "1.99" means protocol 2 is supported. */
+    s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
+
+    if (ssh->cfg.sshprot == 0 && !s->proto1) {
+	bombout(("SSH protocol version 1 required by user but not provided by server"));
+	crStop(0);
+    }
+    if (ssh->cfg.sshprot == 3 && !s->proto2) {
+	bombout(("SSH protocol version 2 required by user but not provided by server"));
+	crStop(0);
+    }
+
+    if (s->proto2 && (ssh->cfg.sshprot >= 2 || !s->proto1)) {
+	/*
+	 * Use v2 protocol.
+	 */
+	char verstring[80], vlog[100];
+	sprintf(verstring, "SSH-2.0-%s", sshver);
+	SHA_Init(&ssh->exhashbase);
+	/*
+	 * Hash our version string and their version string.
+	 */
+	sha_string(&ssh->exhashbase, verstring, strlen(verstring));
+	sha_string(&ssh->exhashbase, s->vstring, strcspn(s->vstring, "\r\n"));
+	sprintf(vlog, "We claim version: %s", verstring);
+	logevent(vlog);
+	strcat(verstring, "\012");
+	logevent("Using SSH protocol version 2");
+	sk_write(ssh->s, verstring, strlen(verstring));
+	ssh->protocol = ssh2_protocol;
+	ssh->version = 2;
+	ssh->s_rdpkt = ssh2_rdpkt;
+    } else {
+	/*
+	 * Use v1 protocol.
+	 */
+	char verstring[80], vlog[100];
+	sprintf(verstring, "SSH-%s-%s",
+		(ssh_versioncmp(s->version, "1.5") <= 0 ? s->version : "1.5"),
+		sshver);
+	sprintf(vlog, "We claim version: %s", verstring);
+	logevent(vlog);
+	strcat(verstring, "\012");
+
+	logevent("Using SSH protocol version 1");
+	sk_write(ssh->s, verstring, strlen(verstring));
+	ssh->protocol = ssh1_protocol;
+	ssh->version = 1;
+	ssh->s_rdpkt = ssh1_rdpkt;
+    }
+    update_specials_menu(ssh->frontend);
+    ssh->state = SSH_STATE_BEFORE_SIZE;
+
+    sfree(s->vstring);
+
+    crFinish(0);
+}
+
+static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
+{
+    crBegin(ssh->ssh_gotdata_crstate);
+
+    /*
+     * To begin with, feed the characters one by one to the
+     * protocol initialisation / selection function do_ssh_init().
+     * When that returns 0, we're done with the initial greeting
+     * exchange and can move on to packet discipline.
+     */
+    while (1) {
+	int ret;		       /* need not be kept across crReturn */
+	if (datalen == 0)
+	    crReturnV;		       /* more data please */
+	ret = do_ssh_init(ssh, *data);
+	data++;
+	datalen--;
+	if (ret == 0)
+	    break;
+    }
+
+    /*
+     * We emerge from that loop when the initial negotiation is
+     * over and we have selected an s_rdpkt function. Now pass
+     * everything to s_rdpkt, and then pass the resulting packets
+     * to the proper protocol handler.
+     */
+    if (datalen == 0)
+	crReturnV;
+    while (1) {
+	while (datalen > 0) {
+	    if (ssh->s_rdpkt(ssh, &data, &datalen) == 0) {
+		if (ssh->state == SSH_STATE_CLOSED) {
+		    return;
+		}
+		ssh->protocol(ssh, NULL, 0, 1);
+		if (ssh->state == SSH_STATE_CLOSED) {
+		    return;
+		}
+	    }
+	}
+	crReturnV;
+    }
+    crFinishV;
+}
+
+static void ssh_do_close(Ssh ssh)
+{
+    int i;
+    struct ssh_channel *c;
+
+    ssh->state = SSH_STATE_CLOSED;
+    if (ssh->s) {
+        sk_close(ssh->s);
+        ssh->s = NULL;
+    }
+    /*
+     * Now we must shut down any port and X forwardings going
+     * through this connection.
+     */
+    if (ssh->channels) {
+	for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
+	    switch (c->type) {
+	      case CHAN_X11:
+		x11_close(c->u.x11.s);
+		break;
+	      case CHAN_SOCKDATA:
+		pfd_close(c->u.pfd.s);
+		break;
+	    }
+	    del234(ssh->channels, c);
+	    if (ssh->version == 2)
+		bufchain_clear(&c->v.v2.outbuffer);
+	    sfree(c);
+	}
+    }
+}
+
+static int ssh_closing(Plug plug, const char *error_msg, int error_code,
+		       int calling_back)
+{
+    Ssh ssh = (Ssh) plug;
+    ssh_do_close(ssh);
+    if (error_msg) {
+	/* A socket error has occurred. */
+	logevent(error_msg);
+	connection_fatal(ssh->frontend, "%s", error_msg);
+    } else {
+	/* Otherwise, the remote side closed the connection normally. */
+    }
+    return 0;
+}
+
+static int ssh_receive(Plug plug, int urgent, char *data, int len)
+{
+    Ssh ssh = (Ssh) plug;
+    ssh_gotdata(ssh, (unsigned char *)data, len);
+    if (ssh->state == SSH_STATE_CLOSED) {
+	ssh_do_close(ssh);
+	return 0;
+    }
+    return 1;
+}
+
+static void ssh_sent(Plug plug, int bufsize)
+{
+    Ssh ssh = (Ssh) plug;
+    /*
+     * If the send backlog on the SSH socket itself clears, we
+     * should unthrottle the whole world if it was throttled.
+     */
+    if (bufsize < SSH_MAX_BACKLOG)
+	ssh_throttle_all(ssh, 0, bufsize);
+}
+
+/*
+ * 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 const char *connect_to_host(Ssh ssh, char *host, int port,
+				   char **realhost, int nodelay)
+{
+    static const struct plug_function_table fn_table = {
+	ssh_closing,
+	ssh_receive,
+	ssh_sent,
+	NULL
+    };
+
+    SockAddr addr;
+    const char *err;
+
+    ssh->savedhost = snewn(1 + strlen(host), char);
+    if (!ssh->savedhost)
+	fatalbox("Out of memory");
+    strcpy(ssh->savedhost, host);
+
+    if (port < 0)
+	port = 22;		       /* default ssh port */
+    ssh->savedport = port;
+
+    /*
+     * Try to find host.
+     */
+    logeventf(ssh, "Looking up host \"%s\"", host);
+    addr = name_lookup(host, port, realhost, &ssh->cfg);
+    if ((err = sk_addr_error(addr)) != NULL) {
+	sk_addr_free(addr);
+	return err;
+    }
+
+    /*
+     * Open socket.
+     */
+    {
+	char addrbuf[100];
+	sk_getaddr(addr, addrbuf, 100);
+	logeventf(ssh, "Connecting to %s port %d", addrbuf, port);
+    }
+    ssh->fn = &fn_table;
+    ssh->s = new_connection(addr, *realhost, port,
+			    0, 1, nodelay, (Plug) ssh, &ssh->cfg);
+    if ((err = sk_socket_error(ssh->s)) != NULL) {
+	ssh->s = NULL;
+	return err;
+    }
+
+    return NULL;
+}
+
+/*
+ * Throttle or unthrottle the SSH connection.
+ */
+static void ssh1_throttle(Ssh ssh, int adjust)
+{
+    int old_count = ssh->v1_throttle_count;
+    ssh->v1_throttle_count += adjust;
+    assert(ssh->v1_throttle_count >= 0);
+    if (ssh->v1_throttle_count && !old_count) {
+	sk_set_frozen(ssh->s, 1);
+    } else if (!ssh->v1_throttle_count && old_count) {
+	sk_set_frozen(ssh->s, 0);
+    }
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(Ssh ssh, int enable, int bufsize)
+{
+    int i;
+    struct ssh_channel *c;
+
+    if (enable == ssh->throttled_all)
+	return;
+    ssh->throttled_all = enable;
+    ssh->overall_bufsize = bufsize;
+    if (!ssh->channels)
+	return;
+    for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
+	switch (c->type) {
+	  case CHAN_MAINSESSION:
+	    /*
+	     * This is treated separately, outside the switch.
+	     */
+	    break;
+	  case CHAN_X11:
+	    x11_override_throttle(c->u.x11.s, enable);
+	    break;
+	  case CHAN_AGENT:
+	    /* Agent channels require no buffer management. */
+	    break;
+	  case CHAN_SOCKDATA:
+	    pfd_override_throttle(c->u.pfd.s, enable);
+	    break;
+	}
+    }
+}
+
+/*
+ * Username and password input, abstracted off into routines
+ * reusable in several places - even between SSH1 and SSH2.
+ */
+
+/* Set up a username or password input loop on a given buffer. */
+static void setup_userpass_input(Ssh ssh, char *buffer, int buflen, int echo)
+{
+    ssh->userpass_input_buffer = buffer;
+    ssh->userpass_input_buflen = buflen;
+    ssh->userpass_input_bufpos = 0;
+    ssh->userpass_input_echo = echo;
+}
+
+/*
+ * Process some terminal data in the course of username/password
+ * input. Returns >0 for success (line of input returned in
+ * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0
+ * for inconclusive (keep waiting for more input please).
+ */
+static int process_userpass_input(Ssh ssh, unsigned char *in, int inlen)
+{
+    char c;
+
+    while (inlen--) {
+	switch (c = *in++) {
+	  case 10:
+	  case 13:
+	    ssh->userpass_input_buffer[ssh->userpass_input_bufpos] = 0;
+	    ssh->userpass_input_buffer[ssh->userpass_input_buflen-1] = 0;
+	    return +1;
+	    break;
+	  case 8:
+	  case 127:
+	    if (ssh->userpass_input_bufpos > 0) {
+		if (ssh->userpass_input_echo)
+		    c_write_str(ssh, "\b \b");
+		ssh->userpass_input_bufpos--;
+	    }
+	    break;
+	  case 21:
+	  case 27:
+	    while (ssh->userpass_input_bufpos > 0) {
+		if (ssh->userpass_input_echo)
+		    c_write_str(ssh, "\b \b");
+		ssh->userpass_input_bufpos--;
+	    }
+	    break;
+	  case 3:
+	  case 4:
+	    return -1;
+	    break;
+	  default:
+	    /*
+	     * This simplistic check for printability is disabled
+	     * when we're doing password input, because some people
+	     * have control characters in their passwords.o
+	     */
+	    if ((!ssh->userpass_input_echo ||
+		 (c >= ' ' && c <= '~') ||
+		 ((unsigned char) c >= 160))
+		&& ssh->userpass_input_bufpos < ssh->userpass_input_buflen-1) {
+		ssh->userpass_input_buffer[ssh->userpass_input_bufpos++] = c;
+		if (ssh->userpass_input_echo)
+		    c_write(ssh, &c, 1);
+	    }
+	    break;
+	}
+    }
+    return 0;
+}
+
+static void ssh_agent_callback(void *sshv, void *reply, int replylen)
+{
+    Ssh ssh = (Ssh) sshv;
+
+    ssh->agent_response = reply;
+    ssh->agent_response_len = replylen;
+
+    if (ssh->version == 1)
+	do_ssh1_login(ssh, NULL, -1, 0);
+    else
+	do_ssh2_authconn(ssh, NULL, -1, 0);
+}
+
+static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+{
+    struct ssh_channel *c = (struct ssh_channel *)cv;
+    Ssh ssh = c->ssh;
+    void *sentreply = reply;
+
+    if (!sentreply) {
+	/* Fake SSH_AGENT_FAILURE. */
+	sentreply = "\0\0\0\1\5";
+	replylen = 5;
+    }
+    if (ssh->version == 2) {
+	ssh2_add_channel_data(c, sentreply, replylen);
+	ssh2_try_send(c);
+    } else {
+	send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
+		    PKT_INT, c->remoteid,
+		    PKT_INT, replylen,
+		    PKT_DATA, sentreply, replylen,
+		    PKT_END);
+    }
+    if (reply)
+	sfree(reply);
+}
+
+/*
+ * Handle the key exchange and user authentication phases.
+ */
+static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, int ispkt)
+{
+    int i, j;
+    unsigned char cookie[8];
+    struct RSAKey servkey, hostkey;
+    struct MD5Context md5c;
+    struct do_ssh1_login_state {
+	int len;
+	unsigned char *rsabuf, *keystr1, *keystr2;
+	unsigned long supported_ciphers_mask, supported_auths_mask;
+	int tried_publickey, tried_agent;
+	int tis_auth_refused, ccard_auth_refused;
+	unsigned char session_id[16];
+	int cipher_type;
+	char username[100];
+	void *publickey_blob;
+	int publickey_bloblen;
+	char password[100];
+	char prompt[200];
+	int pos;
+	char c;
+	int pwpkt_type;
+	unsigned char request[5], *response, *p;
+	int responselen;
+	int keyi, nkeys;
+	int authed;
+	struct RSAKey key;
+	Bignum challenge;
+	char *commentp;
+	int commentlen;
+    };
+    crState(do_ssh1_login_state);
+
+    crBegin(ssh->do_ssh1_login_crstate);
+
+    if (!ispkt)
+	crWaitUntil(ispkt);
+
+    if (ssh->pktin.type != SSH1_SMSG_PUBLIC_KEY) {
+	bombout(("Public key packet not received"));
+	crStop(0);
+    }
+
+    logevent("Received public keys");
+
+    memcpy(cookie, ssh->pktin.body, 8);
+
+    i = makekey(ssh->pktin.body + 8, &servkey, &s->keystr1, 0);
+    j = makekey(ssh->pktin.body + 8 + i, &hostkey, &s->keystr2, 0);
+
+    /*
+     * Log the host key fingerprint.
+     */
+    {
+	char logmsg[80];
+	logevent("Host key fingerprint is:");
+	strcpy(logmsg, "      ");
+	hostkey.comment = NULL;
+	rsa_fingerprint(logmsg + strlen(logmsg),
+			sizeof(logmsg) - strlen(logmsg), &hostkey);
+	logevent(logmsg);
+    }
+
+    ssh->v1_remote_protoflags = GET_32BIT(ssh->pktin.body + 8 + i + j);
+    s->supported_ciphers_mask = GET_32BIT(ssh->pktin.body + 12 + i + j);
+    s->supported_auths_mask = GET_32BIT(ssh->pktin.body + 16 + i + j);
+
+    ssh->v1_local_protoflags =
+	ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
+    ssh->v1_local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
+
+    MD5Init(&md5c);
+    MD5Update(&md5c, s->keystr2, hostkey.bytes);
+    MD5Update(&md5c, s->keystr1, servkey.bytes);
+    MD5Update(&md5c, ssh->pktin.body, 8);
+    MD5Final(s->session_id, &md5c);
+
+    for (i = 0; i < 32; i++)
+	ssh->session_key[i] = random_byte();
+
+    s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
+
+    s->rsabuf = snewn(s->len, unsigned char);
+    if (!s->rsabuf)
+	fatalbox("Out of memory");
+
+    /*
+     * Verify the host key.
+     */
+    {
+	/*
+	 * First format the key into a string.
+	 */
+	int len = rsastr_len(&hostkey);
+	char fingerprint[100];
+	char *keystr = snewn(len, char);
+	if (!keystr)
+	    fatalbox("Out of memory");
+	rsastr_fmt(keystr, &hostkey);
+	rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
+	verify_ssh_host_key(ssh->frontend,
+			    ssh->savedhost, ssh->savedport, "rsa", keystr,
+			    fingerprint);
+	sfree(keystr);
+    }
+
+    for (i = 0; i < 32; i++) {
+	s->rsabuf[i] = ssh->session_key[i];
+	if (i < 16)
+	    s->rsabuf[i] ^= s->session_id[i];
+    }
+
+    if (hostkey.bytes > servkey.bytes) {
+	rsaencrypt(s->rsabuf, 32, &servkey);
+	rsaencrypt(s->rsabuf, servkey.bytes, &hostkey);
+    } else {
+	rsaencrypt(s->rsabuf, 32, &hostkey);
+	rsaencrypt(s->rsabuf, hostkey.bytes, &servkey);
+    }
+
+    logevent("Encrypted session key");
+
+    {
+	int cipher_chosen = 0, warn = 0;
+	char *cipher_string = NULL;
+	int i;
+	for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
+	    int next_cipher = ssh->cfg.ssh_cipherlist[i];
+	    if (next_cipher == CIPHER_WARN) {
+		/* If/when we choose a cipher, warn about it */
+		warn = 1;
+	    } else if (next_cipher == CIPHER_AES) {
+		/* XXX Probably don't need to mention this. */
+		logevent("AES not supported in SSH1, skipping");
+	    } else {
+		switch (next_cipher) {
+		  case CIPHER_3DES:     s->cipher_type = SSH_CIPHER_3DES;
+					cipher_string = "3DES"; break;
+		  case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
+					cipher_string = "Blowfish"; break;
+		  case CIPHER_DES:	s->cipher_type = SSH_CIPHER_DES;
+					cipher_string = "single-DES"; break;
+		}
+		if (s->supported_ciphers_mask & (1 << s->cipher_type))
+		    cipher_chosen = 1;
+	    }
+	}
+	if (!cipher_chosen) {
+	    if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0)
+		bombout(("Server violates SSH 1 protocol by not "
+			 "supporting 3DES encryption"));
+	    else
+		/* shouldn't happen */
+		bombout(("No supported ciphers found"));
+	    crStop(0);
+	}
+
+	/* Warn about chosen cipher if necessary. */
+	if (warn)
+	    askcipher(ssh->frontend, cipher_string, 0);
+    }
+
+    switch (s->cipher_type) {
+      case SSH_CIPHER_3DES:
+	logevent("Using 3DES encryption");
+	break;
+      case SSH_CIPHER_DES:
+	logevent("Using single-DES encryption");
+	break;
+      case SSH_CIPHER_BLOWFISH:
+	logevent("Using Blowfish encryption");
+	break;
+    }
+
+    send_packet(ssh, SSH1_CMSG_SESSION_KEY,
+		PKT_CHAR, s->cipher_type,
+		PKT_DATA, cookie, 8,
+		PKT_CHAR, (s->len * 8) >> 8, PKT_CHAR, (s->len * 8) & 0xFF,
+		PKT_DATA, s->rsabuf, s->len,
+		PKT_INT, ssh->v1_local_protoflags, PKT_END);
+
+    logevent("Trying to enable encryption...");
+
+    sfree(s->rsabuf);
+
+    ssh->cipher = (s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
+		   s->cipher_type == SSH_CIPHER_DES ? &ssh_des :
+		   &ssh_3des);
+    ssh->v1_cipher_ctx = ssh->cipher->make_context();
+    ssh->cipher->sesskey(ssh->v1_cipher_ctx, ssh->session_key);
+    logeventf(ssh, "Initialised %s encryption", ssh->cipher->text_name);
+
+    ssh->crcda_ctx = crcda_make_context();
+    logevent("Installing CRC compensation attack detector");
+
+    if (servkey.modulus) {
+	sfree(servkey.modulus);
+	servkey.modulus = NULL;
+    }
+    if (servkey.exponent) {
+	sfree(servkey.exponent);
+	servkey.exponent = NULL;
+    }
+    if (hostkey.modulus) {
+	sfree(hostkey.modulus);
+	hostkey.modulus = NULL;
+    }
+    if (hostkey.exponent) {
+	sfree(hostkey.exponent);
+	hostkey.exponent = NULL;
+    }
+    crWaitUntil(ispkt);
+
+    if (ssh->pktin.type != SSH1_SMSG_SUCCESS) {
+	bombout(("Encryption not successfully enabled"));
+	crStop(0);
+    }
+
+    logevent("Successfully started encryption");
+
+    fflush(stdout);
+    {
+	if ((flags & FLAG_INTERACTIVE) && !*ssh->cfg.username) {
+	    if (ssh_get_line && !ssh_getline_pw_only) {
+		if (!ssh_get_line("login as: ",
+				  s->username, sizeof(s->username), FALSE)) {
+		    /*
+		     * get_line failed to get a username.
+		     * Terminate.
+		     */
+		    logevent("No username provided. Abandoning session.");
+                    ssh_closing((Plug)ssh, NULL, 0, 0);
+		    crStop(1);
+		}
+	    } else {
+		int ret;	       /* need not be kept over crReturn */
+		c_write_str(ssh, "login as: ");
+		ssh->send_ok = 1;
+
+		setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
+		do {
+		    crWaitUntil(!ispkt);
+		    ret = process_userpass_input(ssh, in, inlen);
+		} while (ret == 0);
+		if (ret < 0)
+		    cleanup_exit(0);
+		c_write_str(ssh, "\r\n");
+	    }
+	} else {
+	    strncpy(s->username, ssh->cfg.username, sizeof(s->username));
+	    s->username[sizeof(s->username)-1] = '\0';
+	}
+
+	send_packet(ssh, SSH1_CMSG_USER, PKT_STR, s->username, PKT_END);
+	{
+	    char userlog[22 + sizeof(s->username)];
+	    sprintf(userlog, "Sent username \"%s\"", s->username);
+	    logevent(userlog);
+	    if (flags & FLAG_INTERACTIVE &&
+		(!((flags & FLAG_STDERR) && (flags & FLAG_VERBOSE)))) {
+		strcat(userlog, "\r\n");
+		c_write_str(ssh, userlog);
+	    }
+	}
+    }
+
+    crWaitUntil(ispkt);
+
+    if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) {
+	/* We must not attempt PK auth. Pretend we've already tried it. */
+	s->tried_publickey = s->tried_agent = 1;
+    } else {
+	s->tried_publickey = s->tried_agent = 0;
+    }
+    s->tis_auth_refused = s->ccard_auth_refused = 0;
+    /* Load the public half of ssh->cfg.keyfile so we notice if it's in Pageant */
+    if (!filename_is_null(ssh->cfg.keyfile)) {
+	if (!rsakey_pubblob(&ssh->cfg.keyfile,
+			    &s->publickey_blob, &s->publickey_bloblen, NULL))
+	    s->publickey_blob = NULL;
+    } else
+	s->publickey_blob = NULL;
+
+    while (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+	s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+
+	if (agent_exists() && !s->tried_agent) {
+	    /*
+	     * Attempt RSA authentication using Pageant.
+	     */
+	    void *r;
+
+	    s->authed = FALSE;
+	    s->tried_agent = 1;
+	    logevent("Pageant is running. Requesting keys.");
+
+	    /* Request the keys held by the agent. */
+	    PUT_32BIT(s->request, 1);
+	    s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
+	    if (!agent_query(s->request, 5, &r, &s->responselen,
+			     ssh_agent_callback, ssh)) {
+		do {
+		    crReturn(0);
+		    if (ispkt) {
+			bombout(("Unexpected data from server while waiting"
+				 " for agent response"));
+			crStop(0);
+		    }
+		} while (ispkt || inlen > 0);
+		r = ssh->agent_response;
+		s->responselen = ssh->agent_response_len;
+	    }
+	    s->response = (unsigned char *) r;
+	    if (s->response && s->responselen >= 5 &&
+		s->response[4] == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+		s->p = s->response + 5;
+		s->nkeys = GET_32BIT(s->p);
+		s->p += 4;
+		{
+		    char buf[64];
+		    sprintf(buf, "Pageant has %d SSH1 keys", s->nkeys);
+		    logevent(buf);
+		}
+		for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
+		    {
+			char buf[64];
+			sprintf(buf, "Trying Pageant key #%d", s->keyi);
+			logevent(buf);
+		    }
+		    if (s->publickey_blob &&
+			!memcmp(s->p, s->publickey_blob,
+				s->publickey_bloblen)) {
+			logevent("This key matches configured key file");
+			s->tried_publickey = 1;
+		    }
+		    s->p += 4;
+		    s->p += ssh1_read_bignum(s->p, &s->key.exponent);
+		    s->p += ssh1_read_bignum(s->p, &s->key.modulus);
+		    s->commentlen = GET_32BIT(s->p);
+		    s->p += 4;
+		    s->commentp = (char *)s->p;
+		    s->p += s->commentlen;
+		    send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+				PKT_BIGNUM, s->key.modulus, PKT_END);
+		    crWaitUntil(ispkt);
+		    if (ssh->pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+			logevent("Key refused");
+			continue;
+		    }
+		    logevent("Received RSA challenge");
+		    ssh1_read_bignum(ssh->pktin.body, &s->challenge);
+		    {
+			char *agentreq, *q, *ret;
+			void *vret;
+			int len, retlen;
+			len = 1 + 4;   /* message type, bit count */
+			len += ssh1_bignum_length(s->key.exponent);
+			len += ssh1_bignum_length(s->key.modulus);
+			len += ssh1_bignum_length(s->challenge);
+			len += 16;     /* session id */
+			len += 4;      /* response format */
+			agentreq = snewn(4 + len, char);
+			PUT_32BIT(agentreq, len);
+			q = agentreq + 4;
+			*q++ = SSH1_AGENTC_RSA_CHALLENGE;
+			PUT_32BIT(q, bignum_bitcount(s->key.modulus));
+			q += 4;
+			q += ssh1_write_bignum(q, s->key.exponent);
+			q += ssh1_write_bignum(q, s->key.modulus);
+			q += ssh1_write_bignum(q, s->challenge);
+			memcpy(q, s->session_id, 16);
+			q += 16;
+			PUT_32BIT(q, 1);	/* response format */
+			if (!agent_query(agentreq, len + 4, &vret, &retlen,
+					 ssh_agent_callback, ssh)) {
+			    sfree(agentreq);
+			    do {
+				crReturn(0);
+				if (ispkt) {
+				    bombout(("Unexpected data from server"
+					     " while waiting for agent"
+					     " response"));
+				    crStop(0);
+				}
+			    } while (ispkt || inlen > 0);
+			    vret = ssh->agent_response;
+			    retlen = ssh->agent_response_len;
+			} else
+			    sfree(agentreq);
+			ret = vret;
+			if (ret) {
+			    if (ret[4] == SSH1_AGENT_RSA_RESPONSE) {
+				logevent("Sending Pageant's response");
+				send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+					    PKT_DATA, ret + 5, 16,
+					    PKT_END);
+				sfree(ret);
+				crWaitUntil(ispkt);
+				if (ssh->pktin.type == SSH1_SMSG_SUCCESS) {
+				    logevent
+					("Pageant's response accepted");
+				    if (flags & FLAG_VERBOSE) {
+					c_write_str(ssh, "Authenticated using"
+						    " RSA key \"");
+					c_write(ssh, s->commentp,
+						s->commentlen);
+					c_write_str(ssh, "\" from agent\r\n");
+				    }
+				    s->authed = TRUE;
+				} else
+				    logevent
+					("Pageant's response not accepted");
+			    } else {
+				logevent
+				    ("Pageant failed to answer challenge");
+				sfree(ret);
+			    }
+			} else {
+			    logevent("No reply received from Pageant");
+			}
+		    }
+		    freebn(s->key.exponent);
+		    freebn(s->key.modulus);
+		    freebn(s->challenge);
+		    if (s->authed)
+			break;
+		}
+		sfree(s->response);
+	    }
+	    if (s->authed)
+		break;
+	}
+	if (!filename_is_null(ssh->cfg.keyfile) && !s->tried_publickey)
+	    s->pwpkt_type = SSH1_CMSG_AUTH_RSA;
+
+	if (ssh->cfg.try_tis_auth &&
+	    (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
+	    !s->tis_auth_refused) {
+	    s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
+	    logevent("Requested TIS authentication");
+	    send_packet(ssh, SSH1_CMSG_AUTH_TIS, PKT_END);
+	    crWaitUntil(ispkt);
+	    if (ssh->pktin.type != SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+		logevent("TIS authentication declined");
+		if (flags & FLAG_INTERACTIVE)
+		    c_write_str(ssh, "TIS authentication refused.\r\n");
+		s->tis_auth_refused = 1;
+		continue;
+	    } else {
+		int challengelen = GET_32BIT(ssh->pktin.body);
+		logevent("Received TIS challenge");
+		if (challengelen > sizeof(s->prompt) - 1)
+		    challengelen = sizeof(s->prompt) - 1;/* prevent overrun */
+		memcpy(s->prompt, ssh->pktin.body + 4, challengelen);
+		/* Prompt heuristic comes from OpenSSH */
+		strncpy(s->prompt + challengelen,
+		        memchr(s->prompt, '\n', challengelen) ?
+			"": "\r\nResponse: ",
+			(sizeof s->prompt) - challengelen);
+		s->prompt[(sizeof s->prompt) - 1] = '\0';
+	    }
+	}
+	if (ssh->cfg.try_tis_auth &&
+	    (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
+	    !s->ccard_auth_refused) {
+	    s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
+	    logevent("Requested CryptoCard authentication");
+	    send_packet(ssh, SSH1_CMSG_AUTH_CCARD, PKT_END);
+	    crWaitUntil(ispkt);
+	    if (ssh->pktin.type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+		logevent("CryptoCard authentication declined");
+		c_write_str(ssh, "CryptoCard authentication refused.\r\n");
+		s->ccard_auth_refused = 1;
+		continue;
+	    } else {
+		int challengelen = GET_32BIT(ssh->pktin.body);
+		logevent("Received CryptoCard challenge");
+		if (challengelen > sizeof(s->prompt) - 1)
+		    challengelen = sizeof(s->prompt) - 1;/* prevent overrun */
+		memcpy(s->prompt, ssh->pktin.body + 4, challengelen);
+		strncpy(s->prompt + challengelen,
+		        memchr(s->prompt, '\n', challengelen) ?
+			"" : "\r\nResponse: ",
+			sizeof(s->prompt) - challengelen);
+		s->prompt[sizeof(s->prompt) - 1] = '\0';
+	    }
+	}
+	if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+	    sprintf(s->prompt, "%.90s@%.90s's password: ",
+		    s->username, ssh->savedhost);
+	}
+	if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+	    char *comment = NULL;
+	    int type;
+	    char msgbuf[256];
+	    if (flags & FLAG_VERBOSE)
+		c_write_str(ssh, "Trying public key authentication.\r\n");
+	    logeventf(ssh, "Trying public key \"%s\"",
+		      filename_to_str(&ssh->cfg.keyfile));
+	    type = key_type(&ssh->cfg.keyfile);
+	    if (type != SSH_KEYTYPE_SSH1) {
+		sprintf(msgbuf, "Key is of wrong type (%s)",
+			key_type_to_str(type));
+		logevent(msgbuf);
+		c_write_str(ssh, msgbuf);
+		c_write_str(ssh, "\r\n");
+		s->tried_publickey = 1;
+		continue;
+	    }
+	    if (!rsakey_encrypted(&ssh->cfg.keyfile, &comment)) {
+		if (flags & FLAG_VERBOSE)
+		    c_write_str(ssh, "No passphrase required.\r\n");
+		goto tryauth;
+	    }
+	    sprintf(s->prompt, "Passphrase for key \"%.100s\": ", comment);
+	    sfree(comment);
+	}
+
+	/*
+	 * Show password prompt, having first obtained it via a TIS
+	 * or CryptoCard exchange if we're doing TIS or CryptoCard
+	 * authentication.
+	 */
+	if (ssh_get_line) {
+	    if (!ssh_get_line(s->prompt, s->password,
+			      sizeof(s->password), TRUE)) {
+		/*
+		 * get_line failed to get a password (for example
+		 * because one was supplied on the command line
+		 * which has already failed to work). Terminate.
+		 */
+		send_packet(ssh, SSH1_MSG_DISCONNECT,
+			    PKT_STR, "No more passwords available to try",
+			    PKT_END);
+		logevent("Unable to authenticate");
+		connection_fatal(ssh->frontend, "Unable to authenticate");
+                ssh_closing((Plug)ssh, NULL, 0, 0);
+		crStop(1);
+	    }
+	} else {
+	    /* Prompt may have come from server. We've munged it a bit, so
+	     * we know it to be zero-terminated at least once. */
+	    int ret;		       /* need not be saved over crReturn */
+	    c_write_untrusted(ssh, s->prompt, strlen(s->prompt));
+	    s->pos = 0;
+
+	    setup_userpass_input(ssh, s->password, sizeof(s->password), 0);
+	    do {
+		crWaitUntil(!ispkt);
+		ret = process_userpass_input(ssh, in, inlen);
+	    } while (ret == 0);
+	    if (ret < 0)
+		cleanup_exit(0);
+	    c_write_str(ssh, "\r\n");
+	}
+
+      tryauth:
+	if (s->pwpkt_type == SSH1_CMSG_AUTH_RSA) {
+	    /*
+	     * Try public key authentication with the specified
+	     * key file.
+	     */
+	    s->tried_publickey = 1;
+	    
+	    {
+		const char *error = NULL;
+		int ret = loadrsakey(&ssh->cfg.keyfile, &s->key, s->password,
+				     &error);
+		if (ret == 0) {
+		    c_write_str(ssh, "Couldn't load private key from ");
+		    c_write_str(ssh, filename_to_str(&ssh->cfg.keyfile));
+		    c_write_str(ssh, " (");
+		    c_write_str(ssh, error);
+		    c_write_str(ssh, ").\r\n");
+		    continue;	       /* go and try password */
+		}
+		if (ret == -1) {
+		    c_write_str(ssh, "Wrong passphrase.\r\n");
+		    s->tried_publickey = 0;
+		    continue;	       /* try again */
+		}
+	    }
+
+	    /*
+	     * Send a public key attempt.
+	     */
+	    send_packet(ssh, SSH1_CMSG_AUTH_RSA,
+			PKT_BIGNUM, s->key.modulus, PKT_END);
+
+	    crWaitUntil(ispkt);
+	    if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+		c_write_str(ssh, "Server refused our public key.\r\n");
+		continue;	       /* go and try password */
+	    }
+	    if (ssh->pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+		bombout(("Bizarre response to offer of public key"));
+		crStop(0);
+	    }
+
+	    {
+		int i;
+		unsigned char buffer[32];
+		Bignum challenge, response;
+
+		ssh1_read_bignum(ssh->pktin.body, &challenge);
+		response = rsadecrypt(challenge, &s->key);
+		freebn(s->key.private_exponent);/* burn the evidence */
+
+		for (i = 0; i < 32; i++) {
+		    buffer[i] = bignum_byte(response, 31 - i);
+		}
+
+		MD5Init(&md5c);
+		MD5Update(&md5c, buffer, 32);
+		MD5Update(&md5c, s->session_id, 16);
+		MD5Final(buffer, &md5c);
+
+		send_packet(ssh, SSH1_CMSG_AUTH_RSA_RESPONSE,
+			    PKT_DATA, buffer, 16, PKT_END);
+
+		freebn(challenge);
+		freebn(response);
+	    }
+
+	    crWaitUntil(ispkt);
+	    if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+		if (flags & FLAG_VERBOSE)
+		    c_write_str(ssh, "Failed to authenticate with"
+				" our public key.\r\n");
+		continue;	       /* go and try password */
+	    } else if (ssh->pktin.type != SSH1_SMSG_SUCCESS) {
+		bombout(("Bizarre response to RSA authentication response"));
+		crStop(0);
+	    }
+
+	    break;		       /* we're through! */
+	} else {
+	    if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+		/*
+		 * Defence against traffic analysis: we send a
+		 * whole bunch of packets containing strings of
+		 * different lengths. One of these strings is the
+		 * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+		 * The others are all random data in
+		 * SSH1_MSG_IGNORE packets. This way a passive
+		 * listener can't tell which is the password, and
+		 * hence can't deduce the password length.
+		 * 
+		 * Anybody with a password length greater than 16
+		 * bytes is going to have enough entropy in their
+		 * password that a listener won't find it _that_
+		 * much help to know how long it is. So what we'll
+		 * do is:
+		 * 
+		 *  - if password length < 16, we send 15 packets
+		 *    containing string lengths 1 through 15
+		 * 
+		 *  - otherwise, we let N be the nearest multiple
+		 *    of 8 below the password length, and send 8
+		 *    packets containing string lengths N through
+		 *    N+7. This won't obscure the order of
+		 *    magnitude of the password length, but it will
+		 *    introduce a bit of extra uncertainty.
+		 * 
+		 * A few servers (the old 1.2.18 through 1.2.22)
+		 * can't deal with SSH1_MSG_IGNORE. For these
+		 * servers, we need an alternative defence. We make
+		 * use of the fact that the password is interpreted
+		 * as a C string: so we can append a NUL, then some
+		 * random data.
+		 * 
+		 * One server (a Cisco one) can deal with neither
+		 * SSH1_MSG_IGNORE _nor_ a padded password string.
+		 * For this server we are left with no defences
+		 * against password length sniffing.
+		 */
+		if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+		    /*
+		     * The server can deal with SSH1_MSG_IGNORE, so
+		     * we can use the primary defence.
+		     */
+		    int bottom, top, pwlen, i;
+		    char *randomstr;
+
+		    pwlen = strlen(s->password);
+		    if (pwlen < 16) {
+			bottom = 0;    /* zero length passwords are OK! :-) */
+			top = 15;
+		    } else {
+			bottom = pwlen & ~7;
+			top = bottom + 7;
+		    }
+
+		    assert(pwlen >= bottom && pwlen <= top);
+
+		    randomstr = snewn(top + 1, char);
+
+		    for (i = bottom; i <= top; i++) {
+			if (i == pwlen)
+			    defer_packet(ssh, s->pwpkt_type,
+					 PKT_STR, s->password, PKT_END);
+			else {
+			    for (j = 0; j < i; j++) {
+				do {
+				    randomstr[j] = random_byte();
+				} while (randomstr[j] == '\0');
+			    }
+			    randomstr[i] = '\0';
+			    defer_packet(ssh, SSH1_MSG_IGNORE,
+					 PKT_STR, randomstr, PKT_END);
+			}
+		    }
+		    logevent("Sending password with camouflage packets");
+		    ssh_pkt_defersend(ssh);
+		    sfree(randomstr);
+		} 
+		else if (!(ssh->remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+		    /*
+		     * The server can't deal with SSH1_MSG_IGNORE
+		     * but can deal with padded passwords, so we
+		     * can use the secondary defence.
+		     */
+		    char string[64];
+		    char *ss;
+		    int len;
+
+		    len = strlen(s->password);
+		    if (len < sizeof(string)) {
+			ss = string;
+			strcpy(string, s->password);
+			len++;	       /* cover the zero byte */
+			while (len < sizeof(string)) {
+			    string[len++] = (char) random_byte();
+			}
+		    } else {
+			ss = s->password;
+		    }
+		    logevent("Sending length-padded password");
+		    send_packet(ssh, s->pwpkt_type, PKT_INT, len,
+				PKT_DATA, ss, len, PKT_END);
+		} else {
+		    /*
+		     * The server has _both_
+		     * BUG_CHOKES_ON_SSH1_IGNORE and
+		     * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
+		     * therefore nothing we can do.
+		     */
+		    int len;
+		    len = strlen(s->password);
+		    logevent("Sending unpadded password");
+		    send_packet(ssh, s->pwpkt_type, PKT_INT, len,
+				PKT_DATA, s->password, len, PKT_END);
+		}
+	    } else {
+		send_packet(ssh, s->pwpkt_type, PKT_STR, s->password, PKT_END);
+	    }
+	}
+	logevent("Sent password");
+	memset(s->password, 0, strlen(s->password));
+	crWaitUntil(ispkt);
+	if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+	    if (flags & FLAG_VERBOSE)
+		c_write_str(ssh, "Access denied\r\n");
+	    logevent("Authentication refused");
+	} else if (ssh->pktin.type != SSH1_SMSG_SUCCESS) {
+	    bombout(("Strange packet received, type %d", ssh->pktin.type));
+	    crStop(0);
+	}
+    }
+
+    logevent("Authentication successful");
+
+    crFinish(1);
+}
+
+void sshfwd_close(struct ssh_channel *c)
+{
+    Ssh ssh = c->ssh;
+
+    if (ssh->state != SSH_STATE_SESSION) {
+	assert(ssh->state == SSH_STATE_CLOSED);
+	return;
+    }
+
+    if (c && !c->closes) {
+	/*
+	 * If the channel's remoteid is -1, we have sent
+	 * CHANNEL_OPEN for this channel, but it hasn't even been
+	 * acknowledged by the server. So we must set a close flag
+	 * on it now, and then when the server acks the channel
+	 * open, we can close it then.
+	 */
+	if (((int)c->remoteid) != -1) {
+	    if (ssh->version == 1) {
+		send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+			    PKT_END);
+	    } else {
+		ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
+		ssh2_pkt_adduint32(ssh, c->remoteid);
+		ssh2_pkt_send(ssh);
+	    }
+	}
+	c->closes = 1;		       /* sent MSG_CLOSE */
+	if (c->type == CHAN_X11) {
+	    c->u.x11.s = NULL;
+	    logevent("Forwarded X11 connection terminated");
+	} else if (c->type == CHAN_SOCKDATA ||
+		   c->type == CHAN_SOCKDATA_DORMANT) {
+	    c->u.pfd.s = NULL;
+	    logevent("Forwarded port closed");
+	}
+    }
+}
+
+int sshfwd_write(struct ssh_channel *c, char *buf, int len)
+{
+    Ssh ssh = c->ssh;
+
+    if (ssh->state != SSH_STATE_SESSION) {
+	assert(ssh->state == SSH_STATE_CLOSED);
+	return 0;
+    }
+
+    if (ssh->version == 1) {
+	send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
+		    PKT_INT, c->remoteid,
+		    PKT_INT, len, PKT_DATA, buf, len, PKT_END);
+	/*
+	 * In SSH1 we can return 0 here - implying that forwarded
+	 * connections are never individually throttled - because
+	 * the only circumstance that can cause throttling will be
+	 * the whole SSH connection backing up, in which case
+	 * _everything_ will be throttled as a whole.
+	 */
+	return 0;
+    } else {
+	ssh2_add_channel_data(c, buf, len);
+	return ssh2_try_send(c);
+    }
+}
+
+void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
+{
+    Ssh ssh = c->ssh;
+
+    if (ssh->state != SSH_STATE_SESSION) {
+	assert(ssh->state == SSH_STATE_CLOSED);
+	return;
+    }
+
+    if (ssh->version == 1) {
+	if (c->v.v1.throttling && bufsize < SSH1_BUFFER_LIMIT) {
+	    c->v.v1.throttling = 0;
+	    ssh1_throttle(ssh, -1);
+	}
+    } else {
+	ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
+    }
+}
+
+static void ssh1_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt)
+{
+    crBegin(ssh->ssh1_protocol_crstate);
+
+    random_init();
+
+    while (!do_ssh1_login(ssh, in, inlen, ispkt)) {
+	crReturnV;
+    }
+    if (ssh->state == SSH_STATE_CLOSED)
+	crReturnV;
+
+    if (ssh->cfg.agentfwd && agent_exists()) {
+	logevent("Requesting agent forwarding");
+	send_packet(ssh, SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
+	do {
+	    crReturnV;
+	} while (!ispkt);
+	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
+	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
+	    bombout(("Protocol confusion"));
+	    crStopV;
+	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+	    logevent("Agent forwarding refused");
+	} else {
+	    logevent("Agent forwarding enabled");
+	    ssh->agentfwd_enabled = TRUE;
+	}
+    }
+
+    if (ssh->cfg.x11_forward) {
+	char proto[20], data[64];
+	logevent("Requesting X11 forwarding");
+	ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
+				       data, sizeof(data), ssh->cfg.x11_auth);
+        x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display);
+	if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
+	    send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+			PKT_STR, proto, PKT_STR, data,
+			PKT_INT, x11_get_screen_number(ssh->cfg.x11_display),
+			PKT_END);
+	} else {
+	    send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
+			PKT_STR, proto, PKT_STR, data, PKT_END);
+	}
+	do {
+	    crReturnV;
+	} while (!ispkt);
+	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
+	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
+	    bombout(("Protocol confusion"));
+	    crStopV;
+	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+	    logevent("X11 forwarding refused");
+	} else {
+	    logevent("X11 forwarding enabled");
+	    ssh->X11_fwd_enabled = TRUE;
+	}
+    }
+
+    {
+	char type;
+	int n;
+	int sport,dport,sserv,dserv;
+	char sports[256], dports[256], saddr[256], host[256];
+
+	ssh->rportfwds = newtree234(ssh_rportcmp_ssh1);
+        /* Add port forwardings. */
+	ssh->portfwd_strptr = ssh->cfg.portfwd;
+	while (*ssh->portfwd_strptr) {
+	    type = *ssh->portfwd_strptr++;
+	    saddr[0] = '\0';
+	    n = 0;
+	    while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') {
+		if (*ssh->portfwd_strptr == ':') {
+		    /*
+		     * We've seen a colon in the middle of the
+		     * source port number. This means that
+		     * everything we've seen until now is the
+		     * source _address_, so we'll move it into
+		     * saddr and start sports from the beginning
+		     * again.
+		     */
+		    ssh->portfwd_strptr++;
+		    sports[n] = '\0';
+		    strcpy(saddr, sports);
+		    n = 0;
+		}
+		if (n < 255) sports[n++] = *ssh->portfwd_strptr++;
+	    }
+	    sports[n] = 0;
+	    if (type != 'D') {
+		if (*ssh->portfwd_strptr == '\t')
+		    ssh->portfwd_strptr++;
+		n = 0;
+		while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') {
+		    if (n < 255) host[n++] = *ssh->portfwd_strptr++;
+		}
+		host[n] = 0;
+		if (*ssh->portfwd_strptr == ':')
+		    ssh->portfwd_strptr++;
+		n = 0;
+		while (*ssh->portfwd_strptr) {
+		    if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
+		}
+		dports[n] = 0;
+		ssh->portfwd_strptr++;
+		dport = atoi(dports);
+		dserv = 0;
+		if (dport == 0) {
+		    dserv = 1;
+		    dport = net_service_lookup(dports);
+		    if (!dport) {
+			logeventf(ssh, "Service lookup failed for"
+				  " destination port \"%s\"", dports);
+		    }
+		}
+	    } else {
+		while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
+		dport = dserv = -1;
+		ssh->portfwd_strptr++; /* eat the NUL and move to next one */
+	    }
+	    sport = atoi(sports);
+	    sserv = 0;
+	    if (sport == 0) {
+		sserv = 1;
+		sport = net_service_lookup(sports);
+		if (!sport) {
+		    logeventf(ssh, "Service lookup failed for source"
+			      " port \"%s\"", sports);
+		}
+	    }
+	    if (sport && dport) {
+		if (type == 'L') {
+		    pfd_addforward(host, dport, *saddr ? saddr : NULL,
+				   sport, ssh, &ssh->cfg);
+		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
+			      " forwarding to %s:%.*s%.*s%d%.*s",
+			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+			      (int)(*saddr?1:0), ":",
+			      (int)(sserv ? strlen(sports) : 0), sports,
+			      sserv, "(", sport, sserv, ")",
+			      host,
+			      (int)(dserv ? strlen(dports) : 0), dports,
+			      dserv, "(", dport, dserv, ")");
+		} else if (type == 'D') {
+		    pfd_addforward(NULL, -1, *saddr ? saddr : NULL,
+				   sport, ssh, &ssh->cfg);
+		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
+			      " doing SOCKS dynamic forwarding",
+			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+			      (int)(*saddr?1:0), ":",
+			      (int)(sserv ? strlen(sports) : 0), sports,
+			      sserv, "(", sport, sserv, ")");
+		} else {
+		    struct ssh_rportfwd *pf;
+		    pf = snew(struct ssh_rportfwd);
+		    strcpy(pf->dhost, host);
+		    pf->dport = dport;
+		    if (saddr) {
+			logeventf(ssh,
+				  "SSH1 cannot handle source address spec \"%s:%d\"; ignoring",
+				  saddr, sport);
+		    }
+		    if (add234(ssh->rportfwds, pf) != pf) {
+			logeventf(ssh, 
+				  "Duplicate remote port forwarding to %s:%d",
+				  host, dport);
+			sfree(pf);
+		    } else {
+			logeventf(ssh, "Requesting remote port %.*s%.*s%d%.*s"
+				  " forward to %s:%.*s%.*s%d%.*s",
+				  (int)(sserv ? strlen(sports) : 0), sports,
+				  sserv, "(", sport, sserv, ")",
+				  host,
+				  (int)(dserv ? strlen(dports) : 0), dports,
+				  dserv, "(", dport, dserv, ")");
+			send_packet(ssh, SSH1_CMSG_PORT_FORWARD_REQUEST,
+				    PKT_INT, sport,
+				    PKT_STR, host,
+				    PKT_INT, dport,
+				    PKT_END);
+			do {
+			    crReturnV;
+			} while (!ispkt);
+			if (ssh->pktin.type != SSH1_SMSG_SUCCESS
+			    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
+			    bombout(("Protocol confusion"));
+			    crStopV;
+			} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+			    c_write_str(ssh, "Server refused port"
+					" forwarding\r\n");
+			}
+			logevent("Remote port forwarding enabled");
+		    }
+		}
+	    }
+	}
+    }
+
+    if (!ssh->cfg.nopty) {
+	send_packet(ssh, SSH1_CMSG_REQUEST_PTY,
+		    PKT_STR, ssh->cfg.termtype,
+		    PKT_INT, ssh->term_height,
+		    PKT_INT, ssh->term_width,
+		    PKT_INT, 0, PKT_INT, 0, PKT_CHAR, 0, PKT_END);
+	ssh->state = SSH_STATE_INTERMED;
+	do {
+	    crReturnV;
+	} while (!ispkt);
+	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
+	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
+	    bombout(("Protocol confusion"));
+	    crStopV;
+	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+	    c_write_str(ssh, "Server refused to allocate pty\r\n");
+	    ssh->editing = ssh->echoing = 1;
+	}
+	logevent("Allocated pty");
+    } else {
+	ssh->editing = ssh->echoing = 1;
+    }
+
+    if (ssh->cfg.compression) {
+	send_packet(ssh, SSH1_CMSG_REQUEST_COMPRESSION, PKT_INT, 6, PKT_END);
+	do {
+	    crReturnV;
+	} while (!ispkt);
+	if (ssh->pktin.type != SSH1_SMSG_SUCCESS
+	    && ssh->pktin.type != SSH1_SMSG_FAILURE) {
+	    bombout(("Protocol confusion"));
+	    crStopV;
+	} else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+	    c_write_str(ssh, "Server refused to compress\r\n");
+	}
+	logevent("Started compression");
+	ssh->v1_compressing = TRUE;
+	ssh->cs_comp_ctx = zlib_compress_init();
+	logevent("Initialised zlib (RFC1950) compression");
+	ssh->sc_comp_ctx = zlib_decompress_init();
+	logevent("Initialised zlib (RFC1950) decompression");
+    }
+
+    /*
+     * Start the shell or command.
+     * 
+     * Special case: if the first-choice command is an SSH2
+     * subsystem (hence not usable here) and the second choice
+     * exists, we fall straight back to that.
+     */
+    {
+	char *cmd = ssh->cfg.remote_cmd_ptr;
+	
+	if (ssh->cfg.ssh_subsys && ssh->cfg.remote_cmd_ptr2) {
+	    cmd = ssh->cfg.remote_cmd_ptr2;
+	    ssh->fallback_cmd = TRUE;
+	}
+	if (*cmd)
+	    send_packet(ssh, SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
+	else
+	    send_packet(ssh, SSH1_CMSG_EXEC_SHELL, PKT_END);
+	logevent("Started session");
+    }
+
+    ssh->state = SSH_STATE_SESSION;
+    if (ssh->size_needed)
+	ssh_size(ssh, ssh->term_width, ssh->term_height);
+    if (ssh->eof_needed)
+	ssh_special(ssh, TS_EOF);
+
+    if (ssh->ldisc)
+	ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+    ssh->send_ok = 1;
+    ssh->channels = newtree234(ssh_channelcmp);
+    while (1) {
+	crReturnV;
+	if (ispkt) {
+	    if (ssh->pktin.type == SSH1_SMSG_STDOUT_DATA ||
+		ssh->pktin.type == SSH1_SMSG_STDERR_DATA) {
+		long len = GET_32BIT(ssh->pktin.body);
+		int bufsize =
+		    from_backend(ssh->frontend,
+				 ssh->pktin.type == SSH1_SMSG_STDERR_DATA,
+				 (char *)(ssh->pktin.body) + 4, len);
+		if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+		    ssh->v1_stdout_throttling = 1;
+		    ssh1_throttle(ssh, +1);
+		}
+	    } else if (ssh->pktin.type == SSH1_MSG_DISCONNECT) {
+                ssh_closing((Plug)ssh, NULL, 0, 0);
+		logevent("Received disconnect request");
+		crStopV;
+	    } else if (ssh->pktin.type == SSH1_SMSG_X11_OPEN) {
+		/* Remote side is trying to open a channel to talk to our
+		 * X-Server. Give them back a local channel number. */
+		struct ssh_channel *c;
+
+		logevent("Received X11 connect request");
+		/* Refuse if X11 forwarding is disabled. */
+		if (!ssh->X11_fwd_enabled) {
+		    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+				PKT_INT, GET_32BIT(ssh->pktin.body), PKT_END);
+		    logevent("Rejected X11 connect request");
+		} else {
+		    c = snew(struct ssh_channel);
+		    c->ssh = ssh;
+
+		    if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c,
+				 ssh->x11auth, NULL, -1, &ssh->cfg) != NULL) {
+			logevent("opening X11 forward connection failed");
+			sfree(c);
+			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+				    PKT_INT, GET_32BIT(ssh->pktin.body),
+				    PKT_END);
+		    } else {
+			logevent
+			    ("opening X11 forward connection succeeded");
+			c->remoteid = GET_32BIT(ssh->pktin.body);
+			c->localid = alloc_channel_id(ssh);
+			c->closes = 0;
+			c->v.v1.throttling = 0;
+			c->type = CHAN_X11;	/* identify channel type */
+			add234(ssh->channels, c);
+			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+				    PKT_INT, c->remoteid, PKT_INT,
+				    c->localid, PKT_END);
+			logevent("Opened X11 forward channel");
+		    }
+		}
+	    } else if (ssh->pktin.type == SSH1_SMSG_AGENT_OPEN) {
+		/* Remote side is trying to open a channel to talk to our
+		 * agent. Give them back a local channel number. */
+		struct ssh_channel *c;
+
+		/* Refuse if agent forwarding is disabled. */
+		if (!ssh->agentfwd_enabled) {
+		    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+				PKT_INT, GET_32BIT(ssh->pktin.body), PKT_END);
+		} else {
+		    c = snew(struct ssh_channel);
+		    c->ssh = ssh;
+		    c->remoteid = GET_32BIT(ssh->pktin.body);
+		    c->localid = alloc_channel_id(ssh);
+		    c->closes = 0;
+		    c->v.v1.throttling = 0;
+		    c->type = CHAN_AGENT;	/* identify channel type */
+		    c->u.a.lensofar = 0;
+		    add234(ssh->channels, c);
+		    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+				PKT_INT, c->remoteid, PKT_INT, c->localid,
+				PKT_END);
+		}
+	    } else if (ssh->pktin.type == SSH1_MSG_PORT_OPEN) {
+   		/* Remote side is trying to open a channel to talk to a
+		 * forwarded port. Give them back a local channel number. */
+		struct ssh_channel *c;
+		struct ssh_rportfwd pf;
+		int hostsize, port;
+		char host[256], buf[1024];
+		char *p, *h;
+		const char *e;
+		c = snew(struct ssh_channel);
+		c->ssh = ssh;
+
+		hostsize = GET_32BIT(ssh->pktin.body+4);
+		for (h = host, p = (char *)(ssh->pktin.body+8);
+		     hostsize != 0; hostsize--) {
+		    if (h+1 < host+sizeof(host))
+			*h++ = *p;
+		    p++;
+		}
+		*h = 0;
+		port = GET_32BIT(p);
+
+		strcpy(pf.dhost, host);
+		pf.dport = port;
+
+		if (find234(ssh->rportfwds, &pf, NULL) == NULL) {
+		    sprintf(buf, "Rejected remote port open request for %s:%d",
+			    host, port);
+		    logevent(buf);
+                    send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+                                PKT_INT, GET_32BIT(ssh->pktin.body), PKT_END);
+		} else {
+		    sprintf(buf, "Received remote port open request for %s:%d",
+			    host, port);
+		    logevent(buf);
+		    e = pfd_newconnect(&c->u.pfd.s, host, port, c, &ssh->cfg);
+		    if (e != NULL) {
+			char buf[256];
+			sprintf(buf, "Port open failed: %s", e);
+			logevent(buf);
+			sfree(c);
+			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
+				    PKT_INT, GET_32BIT(ssh->pktin.body),
+				    PKT_END);
+		    } else {
+			c->remoteid = GET_32BIT(ssh->pktin.body);
+			c->localid = alloc_channel_id(ssh);
+			c->closes = 0;
+			c->v.v1.throttling = 0;
+			c->type = CHAN_SOCKDATA;	/* identify channel type */
+			add234(ssh->channels, c);
+			send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+				    PKT_INT, c->remoteid, PKT_INT,
+				    c->localid, PKT_END);
+			logevent("Forwarded port opened successfully");
+		    }
+		}
+
+	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION) {
+		unsigned int remoteid = GET_32BIT(ssh->pktin.body);
+		unsigned int localid = GET_32BIT(ssh->pktin.body+4);
+		struct ssh_channel *c;
+
+		c = find234(ssh->channels, &remoteid, ssh_channelfind);
+		if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+		    c->remoteid = localid;
+		    c->type = CHAN_SOCKDATA;
+		    c->v.v1.throttling = 0;
+		    pfd_confirm(c->u.pfd.s);
+		}
+
+		if (c && c->closes) {
+		    /*
+		     * We have a pending close on this channel,
+		     * which we decided on before the server acked
+		     * the channel open. So now we know the
+		     * remoteid, we can close it again.
+		     */
+		    send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE,
+				PKT_INT, c->remoteid, PKT_END);
+		}
+
+	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) {
+		unsigned int remoteid = GET_32BIT(ssh->pktin.body);
+		struct ssh_channel *c;
+
+		c = find234(ssh->channels, &remoteid, ssh_channelfind);
+		if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+		    logevent("Forwarded connection refused by server");
+		    pfd_close(c->u.pfd.s);
+		    del234(ssh->channels, c);
+		    sfree(c);
+		}
+
+	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
+		       ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
+		/* Remote side closes a channel. */
+		unsigned i = GET_32BIT(ssh->pktin.body);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (c && ((int)c->remoteid) != -1) {
+		    int closetype;
+		    closetype =
+			(ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
+
+		    if ((c->closes == 0) && (c->type == CHAN_X11)) {
+			logevent("Forwarded X11 connection terminated");
+			assert(c->u.x11.s != NULL);
+			x11_close(c->u.x11.s);
+			c->u.x11.s = NULL;
+		    }
+		    if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) {
+			logevent("Forwarded port closed");
+			assert(c->u.pfd.s != NULL);
+			pfd_close(c->u.pfd.s);
+			c->u.pfd.s = NULL;
+		    }
+
+		    c->closes |= (closetype << 2);   /* seen this message */
+		    if (!(c->closes & closetype)) {
+			send_packet(ssh, ssh->pktin.type, PKT_INT, c->remoteid,
+				    PKT_END);
+			c->closes |= closetype;      /* sent it too */
+		    }
+
+		    if (c->closes == 15) {
+			del234(ssh->channels, c);
+			sfree(c);
+		    }
+		} else {
+		    bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
+			     ssh->pktin.type == SSH1_MSG_CHANNEL_CLOSE ? "" :
+			     "_CONFIRMATION", c ? "half-open" : "nonexistent",
+			     i));
+		    crStopV;
+		}
+	    } else if (ssh->pktin.type == SSH1_MSG_CHANNEL_DATA) {
+		/* Data sent down one of our channels. */
+		int i = GET_32BIT(ssh->pktin.body);
+		int len = GET_32BIT(ssh->pktin.body + 4);
+		unsigned char *p = ssh->pktin.body + 8;
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (c) {
+		    int bufsize = 0;
+		    switch (c->type) {
+		      case CHAN_X11:
+			bufsize = x11_send(c->u.x11.s, (char *)p, len);
+			break;
+		      case CHAN_SOCKDATA:
+			bufsize = pfd_send(c->u.pfd.s, (char *)p, len);
+			break;
+		      case CHAN_AGENT:
+			/* Data for an agent message. Buffer it. */
+			while (len > 0) {
+			    if (c->u.a.lensofar < 4) {
+				int l = min(4 - c->u.a.lensofar, len);
+				memcpy(c->u.a.msglen + c->u.a.lensofar, p,
+				       l);
+				p += l;
+				len -= l;
+				c->u.a.lensofar += l;
+			    }
+			    if (c->u.a.lensofar == 4) {
+				c->u.a.totallen =
+				    4 + GET_32BIT(c->u.a.msglen);
+				c->u.a.message = snewn(c->u.a.totallen,
+						       unsigned char);
+				memcpy(c->u.a.message, c->u.a.msglen, 4);
+			    }
+			    if (c->u.a.lensofar >= 4 && len > 0) {
+				int l =
+				    min(c->u.a.totallen - c->u.a.lensofar,
+					len);
+				memcpy(c->u.a.message + c->u.a.lensofar, p,
+				       l);
+				p += l;
+				len -= l;
+				c->u.a.lensofar += l;
+			    }
+			    if (c->u.a.lensofar == c->u.a.totallen) {
+				void *reply;
+				int replylen;
+				if (agent_query(c->u.a.message,
+						c->u.a.totallen,
+						&reply, &replylen,
+						ssh_agentf_callback, c))
+				    ssh_agentf_callback(c, reply, replylen);
+				sfree(c->u.a.message);
+				c->u.a.lensofar = 0;
+			    }
+			}
+			bufsize = 0;   /* agent channels never back up */
+			break;
+		    }
+		    if (!c->v.v1.throttling && bufsize > SSH1_BUFFER_LIMIT) {
+			c->v.v1.throttling = 1;
+			ssh1_throttle(ssh, +1);
+		    }
+		}
+	    } else if (ssh->pktin.type == SSH1_SMSG_SUCCESS) {
+		/* may be from EXEC_SHELL on some servers */
+	    } else if (ssh->pktin.type == SSH1_SMSG_FAILURE) {
+		/* may be from EXEC_SHELL on some servers
+		 * if no pty is available or in other odd cases. Ignore */
+	    } else if (ssh->pktin.type == SSH1_SMSG_EXIT_STATUS) {
+		char buf[100];
+		ssh->exitcode = GET_32BIT(ssh->pktin.body);
+		sprintf(buf, "Server sent command exit status %d",
+			ssh->exitcode);
+		logevent(buf);
+		send_packet(ssh, SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
+                /*
+                 * In case `helpful' firewalls or proxies tack
+                 * extra human-readable text on the end of the
+                 * session which we might mistake for another
+                 * encrypted packet, we close the session once
+                 * we've sent EXIT_CONFIRMATION.
+                 */
+                ssh_closing((Plug)ssh, NULL, 0, 0);
+                crStopV;
+	    } else {
+		bombout(("Strange packet received: type %d", ssh->pktin.type));
+		crStopV;
+	    }
+	} else {
+	    while (inlen > 0) {
+		int len = min(inlen, 512);
+		send_packet(ssh, SSH1_CMSG_STDIN_DATA,
+			    PKT_INT, len, PKT_DATA, in, len, PKT_END);
+		in += len;
+		inlen -= len;
+	    }
+	}
+    }
+
+    crFinishV;
+}
+
+/*
+ * Utility routine for decoding comma-separated strings in KEXINIT.
+ */
+static int in_commasep_string(char *needle, char *haystack, int haylen)
+{
+    int needlen;
+    if (!needle || !haystack)	       /* protect against null pointers */
+	return 0;
+    needlen = strlen(needle);
+    while (1) {
+	/*
+	 * Is it at the start of the string?
+	 */
+	if (haylen >= needlen &&       /* haystack is long enough */
+	    !memcmp(needle, haystack, needlen) &&	/* initial match */
+	    (haylen == needlen || haystack[needlen] == ',')
+	    /* either , or EOS follows */
+	    )
+	    return 1;
+	/*
+	 * If not, search for the next comma and resume after that.
+	 * If no comma found, terminate.
+	 */
+	while (haylen > 0 && *haystack != ',')
+	    haylen--, haystack++;
+	if (haylen == 0)
+	    return 0;
+	haylen--, haystack++;	       /* skip over comma itself */
+    }
+}
+
+/*
+ * SSH2 key creation method.
+ */
+static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
+		       unsigned char *sessid, char chr,
+		       unsigned char *keyspace)
+{
+    SHA_State s;
+    /* First 20 bytes. */
+    SHA_Init(&s);
+    if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+	sha_mpint(&s, K);
+    SHA_Bytes(&s, H, 20);
+    SHA_Bytes(&s, &chr, 1);
+    SHA_Bytes(&s, sessid, 20);
+    SHA_Final(&s, keyspace);
+    /* Next 20 bytes. */
+    SHA_Init(&s);
+    if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+	sha_mpint(&s, K);
+    SHA_Bytes(&s, H, 20);
+    SHA_Bytes(&s, keyspace, 20);
+    SHA_Final(&s, keyspace + 20);
+}
+
+/*
+ * Handle the SSH2 transport layer.
+ */
+static int do_ssh2_transport(Ssh ssh, unsigned char *in, int inlen, int ispkt)
+{
+    struct do_ssh2_transport_state {
+	int nbits, pbits, warn;
+	Bignum p, g, e, f, K;
+	int kex_init_value, kex_reply_value;
+	const struct ssh_mac **maclist;
+	int nmacs;
+	const struct ssh2_cipher *cscipher_tobe;
+	const struct ssh2_cipher *sccipher_tobe;
+	const struct ssh_mac *csmac_tobe;
+	const struct ssh_mac *scmac_tobe;
+	const struct ssh_compress *cscomp_tobe;
+	const struct ssh_compress *sccomp_tobe;
+	char *hostkeydata, *sigdata, *keystr, *fingerprint;
+	int hostkeylen, siglen;
+	void *hkey;		       /* actual host key */
+	unsigned char exchange_hash[20];
+	int n_preferred_ciphers;
+	const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+	const struct ssh_compress *preferred_comp;
+	int first_kex;
+    };
+    crState(do_ssh2_transport_state);
+
+    crBegin(ssh->do_ssh2_transport_crstate);
+
+    s->cscipher_tobe = s->sccipher_tobe = NULL;
+    s->csmac_tobe = s->scmac_tobe = NULL;
+    s->cscomp_tobe = s->sccomp_tobe = NULL;
+
+    random_init();
+    s->first_kex = 1;
+
+    {
+	int i;
+	/*
+	 * Set up the preferred ciphers. (NULL => warn below here)
+	 */
+	s->n_preferred_ciphers = 0;
+	for (i = 0; i < CIPHER_MAX; i++) {
+	    switch (ssh->cfg.ssh_cipherlist[i]) {
+	      case CIPHER_BLOWFISH:
+		s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_blowfish;
+		break;
+	      case CIPHER_DES:
+		if (ssh->cfg.ssh2_des_cbc) {
+		    s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_des;
+		}
+		break;
+	      case CIPHER_3DES:
+		s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_3des;
+		break;
+	      case CIPHER_AES:
+		s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_aes;
+		break;
+	      case CIPHER_WARN:
+		/* Flag for later. Don't bother if it's the last in
+		 * the list. */
+		if (i < CIPHER_MAX - 1) {
+		    s->preferred_ciphers[s->n_preferred_ciphers++] = NULL;
+		}
+		break;
+	    }
+	}
+    }
+
+    /*
+     * Set up preferred compression.
+     */
+    if (ssh->cfg.compression)
+	s->preferred_comp = &ssh_zlib;
+    else
+	s->preferred_comp = &ssh_comp_none;
+
+    /*
+     * Be prepared to work around the buggy MAC problem.
+     */
+    if (ssh->remote_bugs & BUG_SSH2_HMAC)
+	s->maclist = buggymacs, s->nmacs = lenof(buggymacs);
+    else
+	s->maclist = macs, s->nmacs = lenof(macs);
+
+  begin_key_exchange:
+    {
+	int i, j, cipherstr_started;
+
+	/*
+	 * Construct and send our key exchange packet.
+	 */
+	ssh2_pkt_init(ssh, SSH2_MSG_KEXINIT);
+	for (i = 0; i < 16; i++)
+	    ssh2_pkt_addbyte(ssh, (unsigned char) random_byte());
+	/* List key exchange algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	for (i = 0; i < lenof(kex_algs); i++) {
+	    if (kex_algs[i] == &ssh_diffiehellman_gex &&
+		(ssh->remote_bugs & BUG_SSH2_DH_GEX))
+		continue;
+	    ssh2_pkt_addstring_str(ssh, kex_algs[i]->name);
+	    if (i < lenof(kex_algs) - 1)
+		ssh2_pkt_addstring_str(ssh, ",");
+	}
+	/* List server host key algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	for (i = 0; i < lenof(hostkey_algs); i++) {
+	    ssh2_pkt_addstring_str(ssh, hostkey_algs[i]->name);
+	    if (i < lenof(hostkey_algs) - 1)
+		ssh2_pkt_addstring_str(ssh, ",");
+	}
+	/* List client->server encryption algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	cipherstr_started = 0;
+	for (i = 0; i < s->n_preferred_ciphers; i++) {
+	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+	    if (!c) continue;	       /* warning flag */
+	    for (j = 0; j < c->nciphers; j++) {
+		if (cipherstr_started)
+		    ssh2_pkt_addstring_str(ssh, ",");
+		ssh2_pkt_addstring_str(ssh, c->list[j]->name);
+		cipherstr_started = 1;
+	    }
+	}
+	/* List server->client encryption algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	cipherstr_started = 0;
+	for (i = 0; i < s->n_preferred_ciphers; i++) {
+	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+	    if (!c) continue; /* warning flag */
+	    for (j = 0; j < c->nciphers; j++) {
+		if (cipherstr_started)
+		    ssh2_pkt_addstring_str(ssh, ",");
+		ssh2_pkt_addstring_str(ssh, c->list[j]->name);
+		cipherstr_started = 1;
+	    }
+	}
+	/* List client->server MAC algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	for (i = 0; i < s->nmacs; i++) {
+	    ssh2_pkt_addstring_str(ssh, s->maclist[i]->name);
+	    if (i < s->nmacs - 1)
+		ssh2_pkt_addstring_str(ssh, ",");
+	}
+	/* List server->client MAC algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	for (i = 0; i < s->nmacs; i++) {
+	    ssh2_pkt_addstring_str(ssh, s->maclist[i]->name);
+	    if (i < s->nmacs - 1)
+		ssh2_pkt_addstring_str(ssh, ",");
+	}
+	/* List client->server compression algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	for (i = 0; i < lenof(compressions) + 1; i++) {
+	    const struct ssh_compress *c =
+		i == 0 ? s->preferred_comp : compressions[i - 1];
+	    ssh2_pkt_addstring_str(ssh, c->name);
+	    if (i < lenof(compressions))
+		ssh2_pkt_addstring_str(ssh, ",");
+	}
+	/* List server->client compression algorithms. */
+	ssh2_pkt_addstring_start(ssh);
+	for (i = 0; i < lenof(compressions) + 1; i++) {
+	    const struct ssh_compress *c =
+		i == 0 ? s->preferred_comp : compressions[i - 1];
+	    ssh2_pkt_addstring_str(ssh, c->name);
+	    if (i < lenof(compressions))
+		ssh2_pkt_addstring_str(ssh, ",");
+	}
+	/* List client->server languages. Empty list. */
+	ssh2_pkt_addstring_start(ssh);
+	/* List server->client languages. Empty list. */
+	ssh2_pkt_addstring_start(ssh);
+	/* First KEX packet does _not_ follow, because we're not that brave. */
+	ssh2_pkt_addbool(ssh, FALSE);
+	/* Reserved. */
+	ssh2_pkt_adduint32(ssh, 0);
+    }
+
+    ssh->exhash = ssh->exhashbase;
+    sha_string(&ssh->exhash, ssh->pktout.data + 5, ssh->pktout.length - 5);
+
+    ssh2_pkt_send(ssh);
+
+    if (!ispkt)
+	crWaitUntil(ispkt);
+    if (ssh->pktin.length > 5)
+	sha_string(&ssh->exhash, ssh->pktin.data + 5, ssh->pktin.length - 5);
+
+    /*
+     * Now examine the other side's KEXINIT to see what we're up
+     * to.
+     */
+    {
+	char *str;
+	int i, j, len;
+
+	if (ssh->pktin.type != SSH2_MSG_KEXINIT) {
+	    bombout(("expected key exchange packet from server"));
+	    crStop(0);
+	}
+	ssh->kex = NULL;
+	ssh->hostkey = NULL;
+	s->cscipher_tobe = NULL;
+	s->sccipher_tobe = NULL;
+	s->csmac_tobe = NULL;
+	s->scmac_tobe = NULL;
+	s->cscomp_tobe = NULL;
+	s->sccomp_tobe = NULL;
+	ssh->pktin.savedpos += 16;	        /* skip garbage cookie */
+	ssh2_pkt_getstring(ssh, &str, &len);    /* key exchange algorithms */
+	for (i = 0; i < lenof(kex_algs); i++) {
+	    if (kex_algs[i] == &ssh_diffiehellman_gex &&
+		(ssh->remote_bugs & BUG_SSH2_DH_GEX))
+		continue;
+	    if (in_commasep_string(kex_algs[i]->name, str, len)) {
+		ssh->kex = kex_algs[i];
+		break;
+	    }
+	}
+	ssh2_pkt_getstring(ssh, &str, &len);    /* host key algorithms */
+	for (i = 0; i < lenof(hostkey_algs); i++) {
+	    if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
+		ssh->hostkey = hostkey_algs[i];
+		break;
+	    }
+	}
+	ssh2_pkt_getstring(ssh, &str, &len);    /* client->server cipher */
+	s->warn = 0;
+	for (i = 0; i < s->n_preferred_ciphers; i++) {
+	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+	    if (!c) {
+		s->warn = 1;
+	    } else {
+		for (j = 0; j < c->nciphers; j++) {
+		    if (in_commasep_string(c->list[j]->name, str, len)) {
+			s->cscipher_tobe = c->list[j];
+			break;
+		    }
+		}
+	    }
+	    if (s->cscipher_tobe) {
+		if (s->warn)
+		    askcipher(ssh->frontend, s->cscipher_tobe->name, 1);
+		break;
+	    }
+	}
+	if (!s->cscipher_tobe) {
+	    bombout(("Couldn't agree a client-to-server cipher (available: %s)",
+		     str ? str : "(null)"));
+	    crStop(0);
+	}
+
+	ssh2_pkt_getstring(ssh, &str, &len);    /* server->client cipher */
+	s->warn = 0;
+	for (i = 0; i < s->n_preferred_ciphers; i++) {
+	    const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+	    if (!c) {
+		s->warn = 1;
+	    } else {
+		for (j = 0; j < c->nciphers; j++) {
+		    if (in_commasep_string(c->list[j]->name, str, len)) {
+			s->sccipher_tobe = c->list[j];
+			break;
+		    }
+		}
+	    }
+	    if (s->sccipher_tobe) {
+		if (s->warn)
+		    askcipher(ssh->frontend, s->sccipher_tobe->name, 2);
+		break;
+	    }
+	}
+	if (!s->sccipher_tobe) {
+	    bombout(("Couldn't agree a server-to-client cipher (available: %s)",
+		     str ? str : "(null)"));
+	    crStop(0);
+	}
+
+	ssh2_pkt_getstring(ssh, &str, &len);    /* client->server mac */
+	for (i = 0; i < s->nmacs; i++) {
+	    if (in_commasep_string(s->maclist[i]->name, str, len)) {
+		s->csmac_tobe = s->maclist[i];
+		break;
+	    }
+	}
+	ssh2_pkt_getstring(ssh, &str, &len);    /* server->client mac */
+	for (i = 0; i < s->nmacs; i++) {
+	    if (in_commasep_string(s->maclist[i]->name, str, len)) {
+		s->scmac_tobe = s->maclist[i];
+		break;
+	    }
+	}
+	ssh2_pkt_getstring(ssh, &str, &len);  /* client->server compression */
+	for (i = 0; i < lenof(compressions) + 1; i++) {
+	    const struct ssh_compress *c =
+		i == 0 ? s->preferred_comp : compressions[i - 1];
+	    if (in_commasep_string(c->name, str, len)) {
+		s->cscomp_tobe = c;
+		break;
+	    }
+	}
+	ssh2_pkt_getstring(ssh, &str, &len);  /* server->client compression */
+	for (i = 0; i < lenof(compressions) + 1; i++) {
+	    const struct ssh_compress *c =
+		i == 0 ? s->preferred_comp : compressions[i - 1];
+	    if (in_commasep_string(c->name, str, len)) {
+		s->sccomp_tobe = c;
+		break;
+	    }
+	}
+    }
+
+    /*
+     * 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->cscipher_tobe->keylen;
+	scbits = s->sccipher_tobe->keylen;
+	s->nbits = (csbits > scbits ? csbits : scbits);
+    }
+    /* The keys only have 160-bit entropy, since they're based on
+     * a SHA-1 hash. So cap the key size at 160 bits. */
+    if (s->nbits > 160)
+	s->nbits = 160;
+
+    /*
+     * If we're doing Diffie-Hellman group exchange, start by
+     * requesting a group.
+     */
+    if (ssh->kex == &ssh_diffiehellman_gex) {
+	logevent("Doing Diffie-Hellman group exchange");
+	ssh->pkt_ctx |= 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);
+	ssh2_pkt_init(ssh, SSH2_MSG_KEX_DH_GEX_REQUEST);
+	ssh2_pkt_adduint32(ssh, s->pbits);
+	ssh2_pkt_send(ssh);
+
+	crWaitUntil(ispkt);
+	if (ssh->pktin.type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+	    bombout(("expected key exchange group packet from server"));
+	    crStop(0);
+	}
+	s->p = ssh2_pkt_getmp(ssh);
+	s->g = ssh2_pkt_getmp(ssh);
+	ssh->kex_ctx = dh_setup_group(s->p, s->g);
+	s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+	s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+    } else {
+	ssh->pkt_ctx |= SSH2_PKTCTX_DHGROUP1;
+	ssh->kex_ctx = dh_setup_group1();
+	s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+	s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+    }
+
+    logevent("Doing Diffie-Hellman key exchange");
+    /*
+     * Now generate and send e for Diffie-Hellman.
+     */
+    s->e = dh_create_e(ssh->kex_ctx, s->nbits * 2);
+    ssh2_pkt_init(ssh, s->kex_init_value);
+    ssh2_pkt_addmp(ssh, s->e);
+    ssh2_pkt_send(ssh);
+
+    crWaitUntil(ispkt);
+    if (ssh->pktin.type != s->kex_reply_value) {
+	bombout(("expected key exchange reply packet from server"));
+	crStop(0);
+    }
+    ssh2_pkt_getstring(ssh, &s->hostkeydata, &s->hostkeylen);
+    s->f = ssh2_pkt_getmp(ssh);
+    ssh2_pkt_getstring(ssh, &s->sigdata, &s->siglen);
+
+    s->K = dh_find_K(ssh->kex_ctx, s->f);
+
+    sha_string(&ssh->exhash, s->hostkeydata, s->hostkeylen);
+    if (ssh->kex == &ssh_diffiehellman_gex) {
+	sha_uint32(&ssh->exhash, s->pbits);
+	sha_mpint(&ssh->exhash, s->p);
+	sha_mpint(&ssh->exhash, s->g);
+    }
+    sha_mpint(&ssh->exhash, s->e);
+    sha_mpint(&ssh->exhash, s->f);
+    sha_mpint(&ssh->exhash, s->K);
+    SHA_Final(&ssh->exhash, s->exchange_hash);
+
+    dh_cleanup(ssh->kex_ctx);
+    ssh->kex_ctx = NULL;
+
+#if 0
+    debug(("Exchange hash is:\n"));
+    dmemdump(s->exchange_hash, 20);
+#endif
+
+    s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+    if (!s->hkey ||
+	!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+				 (char *)s->exchange_hash, 20)) {
+	bombout(("Server's host key did not match the signature supplied"));
+	crStop(0);
+    }
+
+    /*
+     * Authenticate remote host: verify host key. (We've already
+     * checked the signature of the exchange hash.)
+     */
+    s->keystr = ssh->hostkey->fmtkey(s->hkey);
+    s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
+    verify_ssh_host_key(ssh->frontend,
+			ssh->savedhost, ssh->savedport, ssh->hostkey->keytype,
+			s->keystr, s->fingerprint);
+    if (s->first_kex) {		       /* don't bother logging this in rekeys */
+	logevent("Host key fingerprint is:");
+	logevent(s->fingerprint);
+    }
+    sfree(s->fingerprint);
+    sfree(s->keystr);
+    ssh->hostkey->freekey(s->hkey);
+
+    /*
+     * Send SSH2_MSG_NEWKEYS.
+     */
+    ssh2_pkt_init(ssh, SSH2_MSG_NEWKEYS);
+    ssh2_pkt_send(ssh);
+
+    /*
+     * Expect SSH2_MSG_NEWKEYS from server.
+     */
+    crWaitUntil(ispkt);
+    if (ssh->pktin.type != SSH2_MSG_NEWKEYS) {
+	bombout(("expected new-keys packet from server"));
+	crStop(0);
+    }
+
+    /*
+     * Create and initialise session keys.
+     */
+    if (ssh->cs_cipher_ctx)
+	ssh->cscipher->free_context(ssh->cs_cipher_ctx);
+    ssh->cscipher = s->cscipher_tobe;
+    ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+
+    if (ssh->sc_cipher_ctx)
+	ssh->sccipher->free_context(ssh->sc_cipher_ctx);
+    ssh->sccipher = s->sccipher_tobe;
+    ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+
+    if (ssh->cs_mac_ctx)
+	ssh->csmac->free_context(ssh->cs_mac_ctx);
+    ssh->csmac = s->csmac_tobe;
+    ssh->cs_mac_ctx = ssh->csmac->make_context();
+
+    if (ssh->sc_mac_ctx)
+	ssh->scmac->free_context(ssh->sc_mac_ctx);
+    ssh->scmac = s->scmac_tobe;
+    ssh->sc_mac_ctx = ssh->scmac->make_context();
+
+    if (ssh->cs_comp_ctx)
+	ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+    ssh->cscomp = s->cscomp_tobe;
+    ssh->cs_comp_ctx = ssh->cscomp->compress_init();
+
+    if (ssh->sc_comp_ctx)
+	ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+    ssh->sccomp = s->sccomp_tobe;
+    ssh->sc_comp_ctx = ssh->sccomp->decompress_init();
+
+    /*
+     * Set IVs after keys. Here we use the exchange hash from the
+     * _first_ key exchange.
+     */
+    {
+	unsigned char keyspace[40];
+	if (s->first_kex)
+	    memcpy(ssh->v2_session_id, s->exchange_hash,
+		   sizeof(s->exchange_hash));
+	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'C',keyspace);
+	ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
+	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'D',keyspace);
+	ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
+	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'A',keyspace);
+	ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
+	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'B',keyspace);
+	ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
+	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'E',keyspace);
+	ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
+	ssh2_mkkey(ssh,s->K,s->exchange_hash,ssh->v2_session_id,'F',keyspace);
+	ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
+    }
+    logeventf(ssh, "Initialised %.200s client->server encryption",
+	      ssh->cscipher->text_name);
+    logeventf(ssh, "Initialised %.200s server->client encryption",
+	      ssh->sccipher->text_name);
+    if (ssh->cscomp->text_name)
+	logeventf(ssh, "Initialised %s compression",
+		  ssh->cscomp->text_name);
+    if (ssh->sccomp->text_name)
+	logeventf(ssh, "Initialised %s decompression",
+		  ssh->sccomp->text_name);
+    freebn(s->f);
+    freebn(s->K);
+    if (ssh->kex == &ssh_diffiehellman_gex) {
+	freebn(s->g);
+	freebn(s->p);
+    }
+
+    /*
+     * If this is the first key exchange phase, we must pass the
+     * SSH2_MSG_NEWKEYS packet to the next layer, not because it
+     * wants to see it but because it will need time to initialise
+     * itself before it sees an actual packet. In subsequent key
+     * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
+     * it would only confuse the layer above.
+     */
+    if (!s->first_kex) {
+	crReturn(0);
+    }
+    s->first_kex = 0;
+
+    /*
+     * Now we're encrypting. Begin returning 1 to the protocol main
+     * function so that other things can run on top of the
+     * transport. If we ever see a KEXINIT, we must go back to the
+     * start.
+     */
+    while (!(ispkt && ssh->pktin.type == SSH2_MSG_KEXINIT)) {
+	crReturn(1);
+    }
+    logevent("Server initiated key re-exchange");
+    goto begin_key_exchange;
+
+    crFinish(1);
+}
+
+/*
+ * Add data to an SSH2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+				  int len)
+{
+    bufchain_add(&c->v.v2.outbuffer, buf, len);
+}
+
+/*
+ * Attempt to send data on an SSH2 channel.
+ */
+static int ssh2_try_send(struct ssh_channel *c)
+{
+    Ssh ssh = c->ssh;
+
+    while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
+	int len;
+	void *data;
+	bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
+	if ((unsigned)len > c->v.v2.remwindow)
+	    len = c->v.v2.remwindow;
+	if ((unsigned)len > c->v.v2.remmaxpkt)
+	    len = c->v.v2.remmaxpkt;
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_DATA);
+	ssh2_pkt_adduint32(ssh, c->remoteid);
+	ssh2_pkt_addstring_start(ssh);
+	ssh2_pkt_addstring_data(ssh, data, len);
+	ssh2_pkt_send(ssh);
+	bufchain_consume(&c->v.v2.outbuffer, len);
+	c->v.v2.remwindow -= len;
+    }
+
+    /*
+     * After having sent as much data as we can, return the amount
+     * still buffered.
+     */
+    return bufchain_size(&c->v.v2.outbuffer);
+}
+
+/*
+ * Potentially enlarge the window on an SSH2 channel.
+ */
+static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
+{
+    Ssh ssh = c->ssh;
+
+    /*
+     * Never send WINDOW_ADJUST for a channel that the remote side
+     * already thinks it's closed; there's no point, since it won't
+     * be sending any more data anyway.
+     */
+    if (c->closes != 0)
+	return;
+
+    if (newwin > c->v.v2.locwindow) {
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+	ssh2_pkt_adduint32(ssh, c->remoteid);
+	ssh2_pkt_adduint32(ssh, newwin - c->v.v2.locwindow);
+	ssh2_pkt_send(ssh);
+	c->v.v2.locwindow = newwin;
+    }
+}
+
+/*
+ * Handle the SSH2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, int ispkt)
+{
+    struct do_ssh2_authconn_state {
+	enum {
+	    AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE,
+		AUTH_PASSWORD,
+		AUTH_KEYBOARD_INTERACTIVE
+	} method;
+	enum {
+	    AUTH_TYPE_NONE,
+		AUTH_TYPE_PUBLICKEY,
+		AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+		AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+		AUTH_TYPE_PASSWORD,
+		AUTH_TYPE_KEYBOARD_INTERACTIVE,
+		AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+	} type;
+	int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
+	int tried_pubkey_config, tried_agent, tried_keyb_inter;
+	int kbd_inter_running;
+	int we_are_in;
+	int num_prompts, curr_prompt, echo;
+	char username[100];
+	int got_username;
+	char pwprompt[200];
+	char password[100];
+	void *publickey_blob;
+	int publickey_bloblen;
+	unsigned char request[5], *response, *p;
+	int responselen;
+	int keyi, nkeys;
+	int authed;
+	char *pkblob, *alg, *commentp;
+	int pklen, alglen, commentlen;
+	int siglen, retlen, len;
+	char *q, *agentreq, *ret;
+	int try_send;
+    };
+    crState(do_ssh2_authconn_state);
+
+    crBegin(ssh->do_ssh2_authconn_crstate);
+
+    /*
+     * Request userauth protocol, and await a response to it.
+     */
+    ssh2_pkt_init(ssh, SSH2_MSG_SERVICE_REQUEST);
+    ssh2_pkt_addstring(ssh, "ssh-userauth");
+    ssh2_pkt_send(ssh);
+    crWaitUntilV(ispkt);
+    if (ssh->pktin.type != SSH2_MSG_SERVICE_ACCEPT) {
+	bombout(("Server refused user authentication protocol"));
+	crStopV;
+    }
+
+    /*
+     * 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->username[0] = '\0';
+    s->got_username = FALSE;
+    do {
+	/*
+	 * Get a username.
+	 */
+	if (s->got_username && !ssh->cfg.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 ((flags & FLAG_INTERACTIVE) && !*ssh->cfg.username) {
+	    if (ssh_get_line && !ssh_getline_pw_only) {
+		if (!ssh_get_line("login as: ",
+				  s->username, sizeof(s->username), FALSE)) {
+		    /*
+		     * get_line failed to get a username.
+		     * Terminate.
+		     */
+		    logevent("No username provided. Abandoning session.");
+                    ssh_closing((Plug)ssh, NULL, 0, 0);
+		    crStopV;
+		}
+	    } else {
+		int ret;	       /* need not be saved across crReturn */
+		c_write_str(ssh, "login as: ");
+		ssh->send_ok = 1;
+		setup_userpass_input(ssh, s->username, sizeof(s->username), 1);
+		do {
+		    crWaitUntilV(!ispkt);
+		    ret = process_userpass_input(ssh, in, inlen);
+		} while (ret == 0);
+		if (ret < 0)
+		    cleanup_exit(0);
+		c_write_str(ssh, "\r\n");
+	    }
+	    s->username[strcspn(s->username, "\n\r")] = '\0';
+	} else {
+	    char *stuff;
+	    strncpy(s->username, ssh->cfg.username, sizeof(s->username));
+	    s->username[sizeof(s->username)-1] = '\0';
+	    if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+		stuff = dupprintf("Using username \"%s\".\r\n", s->username);
+		c_write_str(ssh, stuff);
+		sfree(stuff);
+	    }
+	}
+	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.
+	 */
+	ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+
+	ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+	ssh2_pkt_addstring(ssh, s->username);
+	ssh2_pkt_addstring(ssh, "ssh-connection");/* service requested */
+	ssh2_pkt_addstring(ssh, "none");    /* method */
+	ssh2_pkt_send(ssh);
+	s->type = AUTH_TYPE_NONE;
+	s->gotit = FALSE;
+	s->we_are_in = FALSE;
+
+	s->tried_pubkey_config = FALSE;
+	s->tried_agent = FALSE;
+	s->tried_keyb_inter = FALSE;
+	s->kbd_inter_running = FALSE;
+	/* Load the pub half of ssh->cfg.keyfile so we notice if it's in Pageant */
+	if (!filename_is_null(ssh->cfg.keyfile)) {
+	    int keytype;
+	    logeventf(ssh, "Reading private key file \"%.150s\"",
+		      filename_to_str(&ssh->cfg.keyfile));
+	    keytype = key_type(&ssh->cfg.keyfile);
+	    if (keytype == SSH_KEYTYPE_SSH2) {
+		s->publickey_blob =
+		    ssh2_userkey_loadpub(&ssh->cfg.keyfile, NULL,
+					 &s->publickey_bloblen, NULL);
+	    } else {
+		char *msgbuf;
+		logeventf(ssh, "Unable to use this key file (%s)",
+			  key_type_to_str(keytype));
+		msgbuf = dupprintf("Unable to use key file \"%.150s\""
+				   " (%s)\r\n",
+				   filename_to_str(&ssh->cfg.keyfile),
+				   key_type_to_str(keytype));
+		c_write_str(ssh, msgbuf);
+		sfree(msgbuf);
+		s->publickey_blob = NULL;
+	    }
+	} else
+	    s->publickey_blob = NULL;
+
+	while (1) {
+	    /*
+	     * Wait for the result of the last authentication request.
+	     */
+	    if (!s->gotit)
+		crWaitUntilV(ispkt);
+	    while (ssh->pktin.type == SSH2_MSG_USERAUTH_BANNER) {
+		char *banner;
+		int size;
+		/*
+		 * 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.)
+		 */
+		if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) {
+		    ssh2_pkt_getstring(ssh, &banner, &size);
+		    if (banner)
+			c_write_untrusted(ssh, banner, size);
+		}
+		crWaitUntilV(ispkt);
+	    }
+	    if (ssh->pktin.type == SSH2_MSG_USERAUTH_SUCCESS) {
+		logevent("Access granted");
+		s->we_are_in = TRUE;
+		break;
+	    }
+
+	    if (s->kbd_inter_running &&
+		ssh->pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+		/*
+		 * This is either a further set-of-prompts packet
+		 * in keyboard-interactive authentication, or it's
+		 * the same one and we came back here with `gotit'
+		 * set. In the former case, we must reset the
+		 * curr_prompt variable.
+		 */
+		if (!s->gotit)
+		    s->curr_prompt = 0;
+	    } else if (ssh->pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
+		bombout(("Strange packet received during authentication: type %d",
+			 ssh->pktin.type));
+		crStopV;
+	    }
+
+	    s->gotit = FALSE;
+
+	    /*
+	     * 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 (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE) {
+		char *methods;
+		int methlen;
+		ssh2_pkt_getstring(ssh, &methods, &methlen);
+		s->kbd_inter_running = FALSE;
+		if (!ssh2_pkt_getbool(ssh)) {
+		    /*
+		     * We have received an unequivocal Access
+		     * Denied. This can translate to a variety of
+		     * messages:
+		     * 
+		     *  - if we'd just tried "none" authentication,
+		     *    it's not worth printing anything at all
+		     * 
+		     *  - if we'd just tried a public key _offer_,
+		     *    the message should be "Server refused our
+		     *    key" (or no message at all if the key
+		     *    came from Pageant)
+		     * 
+		     *  - if we'd just tried anything else, the
+		     *    message really should be "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.
+		     */
+		    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)
+			    c_write_str(ssh, "Server refused our key\r\n");
+			logevent("Server refused public key");
+		    } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+			/* server declined keyboard-interactive; ignore */
+		    } else {
+			c_write_str(ssh, "Access denied\r\n");
+			logevent("Access denied");
+			if (s->type == AUTH_TYPE_PASSWORD) {
+			    s->we_are_in = FALSE;
+			    break;
+			}
+		    }
+		} else {
+		    c_write_str(ssh, "Further authentication required\r\n");
+		    logevent("Further authentication required");
+		}
+
+		s->can_pubkey =
+		    in_commasep_string("publickey", methods, methlen);
+		s->can_passwd =
+		    in_commasep_string("password", methods, methlen);
+		s->can_keyb_inter = ssh->cfg.try_ki_auth &&
+		    in_commasep_string("keyboard-interactive", methods, methlen);
+	    }
+
+	    s->method = 0;
+	    ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+	    s->need_pw = FALSE;
+
+	    /*
+	     * Most password/passphrase prompts will be
+	     * non-echoing, so we set this to 0 by default.
+	     * Exception is that some keyboard-interactive prompts
+	     * can be echoing, in which case we'll set this to 1.
+	     */
+	    s->echo = 0;
+
+	    if (!s->method && s->can_pubkey &&
+		agent_exists() && !s->tried_agent) {
+		/*
+		 * Attempt public-key authentication using Pageant.
+		 */
+		void *r;
+		s->authed = FALSE;
+
+		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+		ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
+		s->tried_agent = TRUE;
+
+		logevent("Pageant is running. Requesting keys.");
+
+		/* Request the keys held by the agent. */
+		PUT_32BIT(s->request, 1);
+		s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+		if (!agent_query(s->request, 5, &r, &s->responselen,
+				 ssh_agent_callback, ssh)) {
+		    do {
+			crReturnV;
+			if (ispkt) {
+			    bombout(("Unexpected data from server while"
+				     " waiting for agent response"));
+			    crStopV;
+			}
+		    } while (ispkt || inlen > 0);
+		    r = ssh->agent_response;
+		    s->responselen = ssh->agent_response_len;
+		}
+		s->response = (unsigned char *) r;
+		if (s->response && s->responselen >= 5 &&
+		    s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+		    s->p = s->response + 5;
+		    s->nkeys = GET_32BIT(s->p);
+		    s->p += 4;
+		    {
+			char buf[64];
+			sprintf(buf, "Pageant has %d SSH2 keys", s->nkeys);
+			logevent(buf);
+		    }
+		    for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
+			void *vret;
+
+			{
+			    char buf[64];
+			    sprintf(buf, "Trying Pageant key #%d", s->keyi);
+			    logevent(buf);
+			}
+			s->pklen = GET_32BIT(s->p);
+			s->p += 4;
+			if (s->publickey_blob &&
+			    s->pklen == s->publickey_bloblen &&
+			    !memcmp(s->p, s->publickey_blob,
+				    s->publickey_bloblen)) {
+			    logevent("This key matches configured key file");
+			    s->tried_pubkey_config = 1;
+			}
+			s->pkblob = (char *)s->p;
+			s->p += s->pklen;
+			s->alglen = GET_32BIT(s->pkblob);
+			s->alg = s->pkblob + 4;
+			s->commentlen = GET_32BIT(s->p);
+			s->p += 4;
+			s->commentp = (char *)s->p;
+			s->p += s->commentlen;
+			ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+			ssh2_pkt_addstring(ssh, s->username);
+			ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+			ssh2_pkt_addstring(ssh, "publickey");	/* method */
+			ssh2_pkt_addbool(ssh, FALSE);	/* no signature included */
+			ssh2_pkt_addstring_start(ssh);
+			ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
+			ssh2_pkt_addstring_start(ssh);
+			ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
+			ssh2_pkt_send(ssh);
+
+			crWaitUntilV(ispkt);
+			if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
+			    logevent("Key refused");
+			    continue;
+			}
+
+			if (flags & FLAG_VERBOSE) {
+			    c_write_str(ssh, "Authenticating with "
+					"public key \"");
+			    c_write(ssh, s->commentp, s->commentlen);
+			    c_write_str(ssh, "\" from agent\r\n");
+			}
+
+			/*
+			 * Server is willing to accept the key.
+			 * Construct a SIGN_REQUEST.
+			 */
+			ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+			ssh2_pkt_addstring(ssh, s->username);
+			ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+			ssh2_pkt_addstring(ssh, "publickey");	/* method */
+			ssh2_pkt_addbool(ssh, TRUE);
+			ssh2_pkt_addstring_start(ssh);
+			ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
+			ssh2_pkt_addstring_start(ssh);
+			ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
+
+			s->siglen = ssh->pktout.length - 5 + 4 + 20;
+                        if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+                            s->siglen -= 4;
+			s->len = 1;       /* message type */
+			s->len += 4 + s->pklen;	/* key blob */
+			s->len += 4 + s->siglen;	/* data to sign */
+			s->len += 4;      /* flags */
+			s->agentreq = snewn(4 + s->len, char);
+			PUT_32BIT(s->agentreq, s->len);
+			s->q = s->agentreq + 4;
+			*s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+			PUT_32BIT(s->q, s->pklen);
+			s->q += 4;
+			memcpy(s->q, s->pkblob, s->pklen);
+			s->q += s->pklen;
+			PUT_32BIT(s->q, s->siglen);
+			s->q += 4;
+			/* Now the data to be signed... */
+                        if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+                            PUT_32BIT(s->q, 20);
+                            s->q += 4;
+                        }
+			memcpy(s->q, ssh->v2_session_id, 20);
+			s->q += 20;
+			memcpy(s->q, ssh->pktout.data + 5,
+			       ssh->pktout.length - 5);
+			s->q += ssh->pktout.length - 5;
+			/* And finally the (zero) flags word. */
+			PUT_32BIT(s->q, 0);
+			if (!agent_query(s->agentreq, s->len + 4,
+					 &vret, &s->retlen,
+					 ssh_agent_callback, ssh)) {
+			    do {
+				crReturnV;
+				if (ispkt) {
+				    bombout(("Unexpected data from server"
+					     " while waiting for agent"
+					     " response"));
+				    crStopV;
+				}
+			    } while (ispkt || inlen > 0);
+			    vret = ssh->agent_response;
+			    s->retlen = ssh->agent_response_len;
+			}
+			s->ret = vret;
+			sfree(s->agentreq);
+			if (s->ret) {
+			    if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+				logevent("Sending Pageant's response");
+				ssh2_add_sigblob(ssh, s->pkblob, s->pklen,
+						 s->ret + 9,
+						 GET_32BIT(s->ret + 5));
+				ssh2_pkt_send(ssh);
+				s->authed = TRUE;
+				break;
+			    } else {
+				logevent
+				    ("Pageant failed to answer challenge");
+				sfree(s->ret);
+			    }
+			}
+		    }
+		    if (s->authed)
+			continue;
+		}
+		sfree(s->response);
+	    }
+
+	    if (!s->method && s->can_pubkey && s->publickey_blob
+		&& !s->tried_pubkey_config) {
+		unsigned char *pub_blob;
+		char *algorithm, *comment;
+		int pub_blob_len;
+
+		s->tried_pubkey_config = TRUE;
+
+		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+		ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
+		/*
+		 * Try the public key supplied in the configuration.
+		 *
+		 * First, offer the public blob to see if the server is
+		 * willing to accept it.
+		 */
+		pub_blob =
+		    (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile,
+							  &algorithm,
+							  &pub_blob_len,
+							  NULL);
+		if (pub_blob) {
+		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+		    ssh2_pkt_addstring(ssh, s->username);
+		    ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+		    ssh2_pkt_addstring(ssh, "publickey");	/* method */
+		    ssh2_pkt_addbool(ssh, FALSE);	/* no signature included */
+		    ssh2_pkt_addstring(ssh, algorithm);
+		    ssh2_pkt_addstring_start(ssh);
+		    ssh2_pkt_addstring_data(ssh, (char *)pub_blob,
+					    pub_blob_len);
+		    ssh2_pkt_send(ssh);
+		    logevent("Offered public key");	/* FIXME */
+
+		    crWaitUntilV(ispkt);
+		    if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
+			s->gotit = TRUE;
+			s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+			continue;      /* key refused; give up on it */
+		    }
+
+		    logevent("Offer of public key accepted");
+		    /*
+		     * Actually attempt a serious authentication using
+		     * the key.
+		     */
+		    if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) {
+			sprintf(s->pwprompt,
+				"Passphrase for key \"%.100s\": ",
+				comment);
+			s->need_pw = TRUE;
+		    } else {
+			s->need_pw = FALSE;
+		    }
+		    c_write_str(ssh, "Authenticating with public key \"");
+		    c_write_str(ssh, comment);
+		    c_write_str(ssh, "\"\r\n");
+		    s->method = AUTH_PUBLICKEY_FILE;
+		}
+	    }
+
+	    if (!s->method && s->can_keyb_inter && !s->tried_keyb_inter) {
+		s->method = AUTH_KEYBOARD_INTERACTIVE;
+		s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+		s->tried_keyb_inter = TRUE;
+
+		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+		ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+		ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+		ssh2_pkt_addstring(ssh, s->username);
+		ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+		ssh2_pkt_addstring(ssh, "keyboard-interactive");	/* method */
+		ssh2_pkt_addstring(ssh, ""); /* lang */
+		ssh2_pkt_addstring(ssh, "");
+		ssh2_pkt_send(ssh);
+
+		crWaitUntilV(ispkt);
+		if (ssh->pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+		    if (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE)
+			s->gotit = TRUE;
+		    logevent("Keyboard-interactive authentication refused");
+		    s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+		    continue;
+		}
+
+		s->kbd_inter_running = TRUE;
+		s->curr_prompt = 0;
+	    }
+
+	    if (s->kbd_inter_running) {
+		s->method = AUTH_KEYBOARD_INTERACTIVE;
+		s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+		s->tried_keyb_inter = TRUE;
+
+		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+		ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+		if (s->curr_prompt == 0) {
+		    /*
+		     * We've got a fresh USERAUTH_INFO_REQUEST.
+		     * Display header data, and start going through
+		     * the prompts.
+		     */
+		    char *name, *inst, *lang;
+		    int name_len, inst_len, lang_len;
+
+		    ssh2_pkt_getstring(ssh, &name, &name_len);
+		    ssh2_pkt_getstring(ssh, &inst, &inst_len);
+		    ssh2_pkt_getstring(ssh, &lang, &lang_len);
+		    if (name_len > 0) {
+			c_write_untrusted(ssh, name, name_len);
+			c_write_str(ssh, "\r\n");
+		    }
+		    if (inst_len > 0) {
+			c_write_untrusted(ssh, inst, inst_len);
+			c_write_str(ssh, "\r\n");
+		    }
+		    s->num_prompts = ssh2_pkt_getuint32(ssh);
+		}
+
+		/*
+		 * If there are prompts remaining in the packet,
+		 * display one and get a response.
+		 */
+		if (s->curr_prompt < s->num_prompts) {
+		    char *prompt;
+		    int prompt_len;
+
+		    ssh2_pkt_getstring(ssh, &prompt, &prompt_len);
+		    if (prompt_len > 0) {
+			strncpy(s->pwprompt, prompt, sizeof(s->pwprompt));
+			s->pwprompt[prompt_len < sizeof(s->pwprompt) ?
+				    prompt_len : sizeof(s->pwprompt)-1] = '\0';
+		    } else {
+			strcpy(s->pwprompt,
+			       "<server failed to send prompt>: ");
+		    }
+		    s->echo = ssh2_pkt_getbool(ssh);
+		    s->need_pw = TRUE;
+		} else
+		    s->need_pw = FALSE;
+	    }
+
+	    if (!s->method && s->can_passwd) {
+		s->method = AUTH_PASSWORD;
+		ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+		ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD;
+		sprintf(s->pwprompt, "%.90s@%.90s's password: ", s->username,
+			ssh->savedhost);
+		s->need_pw = TRUE;
+	    }
+
+	    if (s->need_pw) {
+		if (ssh_get_line) {
+		    if (!ssh_get_line(s->pwprompt, s->password,
+				      sizeof(s->password), TRUE)) {
+			/*
+			 * get_line failed to get a password (for
+			 * example because one was supplied on the
+			 * command line which has already failed to
+			 * work). Terminate.
+			 */
+			ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+			ssh2_pkt_adduint32(ssh,SSH2_DISCONNECT_BY_APPLICATION);
+			ssh2_pkt_addstring(ssh, "No more passwords available"
+					   " to try");
+			ssh2_pkt_addstring(ssh, "en");	/* language tag */
+			ssh2_pkt_send(ssh);
+			logevent("Unable to authenticate");
+			connection_fatal(ssh->frontend,
+					 "Unable to authenticate");
+                        ssh_closing((Plug)ssh, NULL, 0, 0);
+			crStopV;
+		    }
+		} else {
+		    int ret;	       /* need not be saved across crReturn */
+		    c_write_untrusted(ssh, s->pwprompt, strlen(s->pwprompt));
+		    ssh->send_ok = 1;
+
+		    setup_userpass_input(ssh, s->password,
+					 sizeof(s->password), s->echo);
+		    do {
+			crWaitUntilV(!ispkt);
+			ret = process_userpass_input(ssh, in, inlen);
+		    } while (ret == 0);
+		    if (ret < 0)
+			cleanup_exit(0);
+		    c_write_str(ssh, "\r\n");
+		}
+	    }
+
+	    if (s->method == AUTH_PUBLICKEY_FILE) {
+		/*
+		 * We have our passphrase. Now try the actual authentication.
+		 */
+		struct ssh2_userkey *key;
+		const char *error = NULL;
+
+		key = ssh2_load_userkey(&ssh->cfg.keyfile, s->password,
+					&error);
+		if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+		    if (key == SSH2_WRONG_PASSPHRASE) {
+			c_write_str(ssh, "Wrong passphrase\r\n");
+			s->tried_pubkey_config = FALSE;
+		    } else {
+			c_write_str(ssh, "Unable to load private key (");
+			c_write_str(ssh, error);
+			c_write_str(ssh, ")\r\n");
+			s->tried_pubkey_config = TRUE;
+		    }
+		    /* Send a spurious AUTH_NONE to return to the top. */
+		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+		    ssh2_pkt_addstring(ssh, s->username);
+		    ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+		    ssh2_pkt_addstring(ssh, "none");	/* method */
+		    ssh2_pkt_send(ssh);
+		    s->type = AUTH_TYPE_NONE;
+		} else {
+		    unsigned char *pkblob, *sigblob, *sigdata;
+		    int pkblob_len, sigblob_len, sigdata_len;
+                    int p;
+
+		    /*
+		     * 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.
+		     */
+		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+		    ssh2_pkt_addstring(ssh, s->username);
+		    ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+		    ssh2_pkt_addstring(ssh, "publickey");	/* method */
+		    ssh2_pkt_addbool(ssh, TRUE);
+		    ssh2_pkt_addstring(ssh, key->alg->name);
+		    pkblob = key->alg->public_blob(key->data, &pkblob_len);
+		    ssh2_pkt_addstring_start(ssh);
+		    ssh2_pkt_addstring_data(ssh, (char *)pkblob, pkblob_len);
+
+		    /*
+		     * The data to be signed is:
+		     *
+		     *   string  session-id
+		     *
+		     * followed by everything so far placed in the
+		     * outgoing packet.
+		     */
+		    sigdata_len = ssh->pktout.length - 5 + 4 + 20;
+                    if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+                        sigdata_len -= 4;
+		    sigdata = snewn(sigdata_len, unsigned char);
+                    p = 0;
+                    if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+                        PUT_32BIT(sigdata+p, 20);
+                        p += 4;
+                    }
+		    memcpy(sigdata+p, ssh->v2_session_id, 20); p += 20;
+		    memcpy(sigdata+p, ssh->pktout.data + 5,
+			   ssh->pktout.length - 5);
+                    p += ssh->pktout.length - 5;
+                    assert(p == sigdata_len);
+		    sigblob = key->alg->sign(key->data, (char *)sigdata,
+					     sigdata_len, &sigblob_len);
+		    ssh2_add_sigblob(ssh, pkblob, pkblob_len,
+				     sigblob, sigblob_len);
+		    sfree(pkblob);
+		    sfree(sigblob);
+		    sfree(sigdata);
+
+		    ssh2_pkt_send(ssh);
+		    s->type = AUTH_TYPE_PUBLICKEY;
+		    key->alg->freekey(key->data);
+		}
+	    } else if (s->method == AUTH_PASSWORD) {
+		/*
+		 * We send the password packet lumped tightly together with
+		 * an SSH_MSG_IGNORE packet. The IGNORE packet contains a
+		 * string long enough to make the total length of the two
+		 * packets constant. This should ensure that a passive
+		 * listener doing traffic analyis can't work out the length
+		 * of the password.
+		 *
+		 * For this to work, we need an assumption about the
+		 * maximum length of the password packet. I think 256 is
+		 * pretty conservative. Anyone using a password longer than
+		 * that probably doesn't have much to worry about from
+		 * people who find out how long their password is!
+		 */
+		ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+		ssh2_pkt_addstring(ssh, s->username);
+		ssh2_pkt_addstring(ssh, "ssh-connection");	/* service requested */
+		ssh2_pkt_addstring(ssh, "password");
+		ssh2_pkt_addbool(ssh, FALSE);
+		ssh2_pkt_addstring(ssh, s->password);
+		memset(s->password, 0, sizeof(s->password));
+		ssh2_pkt_defer(ssh);
+		/*
+		 * We'll include a string that's an exact multiple of the
+		 * cipher block size. If the cipher is NULL for some
+		 * reason, we don't do this trick at all because we gain
+		 * nothing by it.
+		 */
+		if (ssh->cscipher) {
+		    int stringlen, i;
+
+		    stringlen = (256 - ssh->deferred_len);
+		    stringlen += ssh->cscipher->blksize - 1;
+		    stringlen -= (stringlen % ssh->cscipher->blksize);
+		    if (ssh->cscomp) {
+			/*
+			 * Temporarily disable actual compression,
+			 * so we can guarantee to get this string
+			 * exactly the length we want it. The
+			 * compression-disabling routine should
+			 * return an integer indicating how many
+			 * bytes we should adjust our string length
+			 * by.
+			 */
+			stringlen -= 
+			    ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
+		    }
+		    ssh2_pkt_init(ssh, SSH2_MSG_IGNORE);
+		    ssh2_pkt_addstring_start(ssh);
+		    for (i = 0; i < stringlen; i++) {
+			char c = (char) random_byte();
+			ssh2_pkt_addstring_data(ssh, &c, 1);
+		    }
+		    ssh2_pkt_defer(ssh);
+		}
+		ssh_pkt_defersend(ssh);
+		logevent("Sent password");
+		s->type = AUTH_TYPE_PASSWORD;
+	    } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) {
+		if (s->curr_prompt == 0) {
+		    ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE);
+		    ssh2_pkt_adduint32(ssh, s->num_prompts);
+		}
+		if (s->need_pw) {      /* only add pw if we just got one! */
+		    ssh2_pkt_addstring(ssh, s->password);
+		    memset(s->password, 0, sizeof(s->password));
+		    s->curr_prompt++;
+		}
+		if (s->curr_prompt >= s->num_prompts) {
+		    ssh2_pkt_send(ssh);
+		} else {
+		    /*
+		     * If there are prompts remaining, we set
+		     * `gotit' so that we won't attempt to get
+		     * another packet. Then we go back round the
+		     * loop and will end up retrieving another
+		     * prompt out of the existing packet. Funky or
+		     * what?
+		     */
+		    s->gotit = TRUE;
+		}
+		s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+	    } else {
+		c_write_str(ssh, "No supported authentication methods"
+			    " left to try!\r\n");
+		logevent("No supported authentications offered."
+			 " Disconnecting");
+		ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+		ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
+		ssh2_pkt_addstring(ssh, "No supported authentication"
+				   " methods available");
+		ssh2_pkt_addstring(ssh, "en");	/* language tag */
+		ssh2_pkt_send(ssh);
+                ssh_closing((Plug)ssh, NULL, 0, 0);
+		crStopV;
+	    }
+	}
+    } while (!s->we_are_in);
+
+    /*
+     * Now we're authenticated for the connection protocol. The
+     * connection protocol will automatically have started at this
+     * point; there's no need to send SERVICE_REQUEST.
+     */
+
+    /*
+     * So now create a channel with a session in it.
+     */
+    ssh->channels = newtree234(ssh_channelcmp);
+    ssh->mainchan = snew(struct ssh_channel);
+    ssh->mainchan->ssh = ssh;
+    ssh->mainchan->localid = alloc_channel_id(ssh);
+    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN);
+    ssh2_pkt_addstring(ssh, "session");
+    ssh2_pkt_adduint32(ssh, ssh->mainchan->localid);
+    ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
+    ssh2_pkt_adduint32(ssh, ssh->mainchan->v.v2.locwindow);/* our window size */
+    ssh2_pkt_adduint32(ssh, 0x4000UL);      /* our max pkt size */
+    ssh2_pkt_send(ssh);
+    crWaitUntilV(ispkt);
+    if (ssh->pktin.type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+	bombout(("Server refused to open a session"));
+	crStopV;
+	/* FIXME: error data comes back in FAILURE packet */
+    }
+    if (ssh2_pkt_getuint32(ssh) != ssh->mainchan->localid) {
+	bombout(("Server's channel confirmation cited wrong channel"));
+	crStopV;
+    }
+    ssh->mainchan->remoteid = ssh2_pkt_getuint32(ssh);
+    ssh->mainchan->type = CHAN_MAINSESSION;
+    ssh->mainchan->closes = 0;
+    ssh->mainchan->v.v2.remwindow = ssh2_pkt_getuint32(ssh);
+    ssh->mainchan->v.v2.remmaxpkt = ssh2_pkt_getuint32(ssh);
+    bufchain_init(&ssh->mainchan->v.v2.outbuffer);
+    add234(ssh->channels, ssh->mainchan);
+    logevent("Opened channel for session");
+
+    /*
+     * Potentially enable X11 forwarding.
+     */
+    if (ssh->cfg.x11_forward) {
+	char proto[20], data[64];
+	logevent("Requesting X11 forwarding");
+	ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
+				       data, sizeof(data), ssh->cfg.x11_auth);
+        x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display);
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
+	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
+	ssh2_pkt_addstring(ssh, "x11-req");
+	ssh2_pkt_addbool(ssh, 1);	       /* want reply */
+	ssh2_pkt_addbool(ssh, 0);	       /* many connections */
+	ssh2_pkt_addstring(ssh, proto);
+	ssh2_pkt_addstring(ssh, data);
+	ssh2_pkt_adduint32(ssh, x11_get_screen_number(ssh->cfg.x11_display));
+	ssh2_pkt_send(ssh);
+
+	do {
+	    crWaitUntilV(ispkt);
+	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+	    }
+	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+		bombout(("Unexpected response to X11 forwarding request:"
+			 " packet type %d", ssh->pktin.type));
+		crStopV;
+	    }
+	    logevent("X11 forwarding refused");
+	} else {
+	    logevent("X11 forwarding enabled");
+	    ssh->X11_fwd_enabled = TRUE;
+	}
+    }
+
+    /*
+     * Enable port forwardings.
+     */
+    {
+	char type;
+	int n;
+	int sport,dport,sserv,dserv;
+	char sports[256], dports[256], saddr[256], host[256];
+
+	ssh->rportfwds = newtree234(ssh_rportcmp_ssh2);
+        /* Add port forwardings. */
+	ssh->portfwd_strptr = ssh->cfg.portfwd;
+	while (*ssh->portfwd_strptr) {
+	    type = *ssh->portfwd_strptr++;
+	    saddr[0] = '\0';
+	    n = 0;
+	    while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != '\t') {
+		if (*ssh->portfwd_strptr == ':') {
+		    /*
+		     * We've seen a colon in the middle of the
+		     * source port number. This means that
+		     * everything we've seen until now is the
+		     * source _address_, so we'll move it into
+		     * saddr and start sports from the beginning
+		     * again.
+		     */
+		    ssh->portfwd_strptr++;
+		    sports[n] = '\0';
+		    strcpy(saddr, sports);
+		    n = 0;
+		}
+		if (n < 255) sports[n++] = *ssh->portfwd_strptr++;
+	    }
+	    sports[n] = 0;
+	    if (type != 'D') {
+		if (*ssh->portfwd_strptr == '\t')
+		    ssh->portfwd_strptr++;
+		n = 0;
+		while (*ssh->portfwd_strptr && *ssh->portfwd_strptr != ':') {
+		    if (n < 255) host[n++] = *ssh->portfwd_strptr++;
+		}
+		host[n] = 0;
+		if (*ssh->portfwd_strptr == ':')
+		    ssh->portfwd_strptr++;
+		n = 0;
+		while (*ssh->portfwd_strptr) {
+		    if (n < 255) dports[n++] = *ssh->portfwd_strptr++;
+		}
+		dports[n] = 0;
+		ssh->portfwd_strptr++;
+		dport = atoi(dports);
+		dserv = 0;
+		if (dport == 0) {
+		    dserv = 1;
+		    dport = net_service_lookup(dports);
+		    if (!dport) {
+			logeventf(ssh, "Service lookup failed for destination"
+				  " port \"%s\"", dports);
+		    }
+		}
+	    } else {
+		while (*ssh->portfwd_strptr) ssh->portfwd_strptr++;
+		dport = dserv = -1;
+		ssh->portfwd_strptr++; /* eat the NUL and move to next one */
+	    }
+	    sport = atoi(sports);
+	    sserv = 0;
+	    if (sport == 0) {
+		sserv = 1;
+		sport = net_service_lookup(sports);
+		if (!sport) {
+		    logeventf(ssh, "Service lookup failed for source"
+			      " port \"%s\"", sports);
+		}
+	    }
+	    if (sport && dport) {
+		if (type == 'L') {
+		    pfd_addforward(host, dport, *saddr ? saddr : NULL,
+				   sport, ssh, &ssh->cfg);
+		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
+			      " forwarding to %s:%.*s%.*s%d%.*s",
+			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+			      (int)(*saddr?1:0), ":",
+			      (int)(sserv ? strlen(sports) : 0), sports,
+			      sserv, "(", sport, sserv, ")",
+			      host,
+			      (int)(dserv ? strlen(dports) : 0), dports,
+			      dserv, "(", dport, dserv, ")");
+		} else if (type == 'D') {
+		    pfd_addforward(NULL, -1, *saddr ? saddr : NULL,
+				   sport, ssh, &ssh->cfg);
+		    logeventf(ssh, "Local port %.*s%.*s%.*s%.*s%d%.*s"
+			      " doing SOCKS dynamic forwarding",
+			      (int)(*saddr?strlen(saddr):0), *saddr?saddr:NULL,
+			      (int)(*saddr?1:0), ":",
+			      (int)(sserv ? strlen(sports) : 0), sports,
+			      sserv, "(", sport, sserv, ")");
+		} else {
+		    struct ssh_rportfwd *pf;
+		    pf = snew(struct ssh_rportfwd);
+		    strcpy(pf->dhost, host);
+		    pf->dport = dport;
+		    pf->sport = sport;
+		    if (add234(ssh->rportfwds, pf) != pf) {
+			logeventf(ssh, "Duplicate remote port forwarding"
+				  " to %s:%d", host, dport);
+			sfree(pf);
+		    } else {
+			logeventf(ssh, "Requesting remote port "
+				  "%.*s%.*s%.*s%.*s%d%.*s"
+				  " forward to %s:%.*s%.*s%d%.*s",
+				  (int)(*saddr?strlen(saddr):0),
+				  *saddr?saddr:NULL,
+				  (int)(*saddr?1:0), ":",
+				  (int)(sserv ? strlen(sports) : 0), sports,
+				  sserv, "(", sport, sserv, ")",
+				  host,
+				  (int)(dserv ? strlen(dports) : 0), dports,
+				  dserv, "(", dport, dserv, ")");
+			ssh2_pkt_init(ssh, SSH2_MSG_GLOBAL_REQUEST);
+			ssh2_pkt_addstring(ssh, "tcpip-forward");
+			ssh2_pkt_addbool(ssh, 1);/* want reply */
+			if (*saddr)
+			    ssh2_pkt_addstring(ssh, saddr);
+			if (ssh->cfg.rport_acceptall)
+			    ssh2_pkt_addstring(ssh, "0.0.0.0");
+			else
+			    ssh2_pkt_addstring(ssh, "127.0.0.1");
+			ssh2_pkt_adduint32(ssh, sport);
+			ssh2_pkt_send(ssh);
+
+			do {
+			    crWaitUntilV(ispkt);
+			    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+				unsigned i = ssh2_pkt_getuint32(ssh);
+				struct ssh_channel *c;
+				c = find234(ssh->channels, &i, ssh_channelfind);
+				if (!c)
+				    continue;/* nonexistent channel */
+				c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+			    }
+			} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+			if (ssh->pktin.type != SSH2_MSG_REQUEST_SUCCESS) {
+			    if (ssh->pktin.type != SSH2_MSG_REQUEST_FAILURE) {
+				bombout(("Unexpected response to port "
+					 "forwarding request: packet type %d",
+					 ssh->pktin.type));
+				crStopV;
+			    }
+			    logevent("Server refused this port forwarding");
+			} else {
+			    logevent("Remote port forwarding enabled");
+			}
+		    }
+		}
+	    }
+	}
+    }
+
+    /*
+     * Potentially enable agent forwarding.
+     */
+    if (ssh->cfg.agentfwd && agent_exists()) {
+	logevent("Requesting OpenSSH-style agent forwarding");
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
+	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
+	ssh2_pkt_addstring(ssh, "[email protected]");
+	ssh2_pkt_addbool(ssh, 1);	       /* want reply */
+	ssh2_pkt_send(ssh);
+
+	do {
+	    crWaitUntilV(ispkt);
+	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+	    }
+	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+		bombout(("Unexpected response to agent forwarding request:"
+			 " packet type %d", ssh->pktin.type));
+		crStopV;
+	    }
+	    logevent("Agent forwarding refused");
+	} else {
+	    logevent("Agent forwarding enabled");
+	    ssh->agentfwd_enabled = TRUE;
+	}
+    }
+
+    /*
+     * Now allocate a pty for the session.
+     */
+    if (!ssh->cfg.nopty) {
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
+	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);	/* recipient channel */
+	ssh2_pkt_addstring(ssh, "pty-req");
+	ssh2_pkt_addbool(ssh, 1);	       /* want reply */
+	ssh2_pkt_addstring(ssh, ssh->cfg.termtype);
+	ssh2_pkt_adduint32(ssh, ssh->term_width);
+	ssh2_pkt_adduint32(ssh, ssh->term_height);
+	ssh2_pkt_adduint32(ssh, 0);	       /* pixel width */
+	ssh2_pkt_adduint32(ssh, 0);	       /* pixel height */
+	ssh2_pkt_addstring_start(ssh);
+	ssh2_pkt_addstring_data(ssh, "\0", 1);	/* TTY_OP_END, no special options */
+	ssh2_pkt_send(ssh);
+	ssh->state = SSH_STATE_INTERMED;
+
+	do {
+	    crWaitUntilV(ispkt);
+	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+	    }
+	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+		bombout(("Unexpected response to pty request:"
+			 " packet type %d", ssh->pktin.type));
+		crStopV;
+	    }
+	    c_write_str(ssh, "Server refused to allocate pty\r\n");
+	    ssh->editing = ssh->echoing = 1;
+	} else {
+	    logevent("Allocated pty");
+	}
+    } else {
+	ssh->editing = ssh->echoing = 1;
+    }
+
+    /*
+     * Start a shell or a remote command. We may have to attempt
+     * this twice if the config data has provided a second choice
+     * of command.
+     */
+    while (1) {
+	int subsys;
+	char *cmd;
+
+	if (ssh->fallback_cmd) {
+	    subsys = ssh->cfg.ssh_subsys2;
+	    cmd = ssh->cfg.remote_cmd_ptr2;
+	} else {
+	    subsys = ssh->cfg.ssh_subsys;
+	    cmd = ssh->cfg.remote_cmd_ptr;
+	}
+
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
+	ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);	/* recipient channel */
+	if (subsys) {
+	    ssh2_pkt_addstring(ssh, "subsystem");
+	    ssh2_pkt_addbool(ssh, 1);	       /* want reply */
+	    ssh2_pkt_addstring(ssh, cmd);
+	} else if (*cmd) {
+	    ssh2_pkt_addstring(ssh, "exec");
+	    ssh2_pkt_addbool(ssh, 1);	       /* want reply */
+	    ssh2_pkt_addstring(ssh, cmd);
+	} else {
+	    ssh2_pkt_addstring(ssh, "shell");
+	    ssh2_pkt_addbool(ssh, 1);	       /* want reply */
+	}
+	ssh2_pkt_send(ssh);
+	do {
+	    crWaitUntilV(ispkt);
+	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+	    }
+	} while (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+	if (ssh->pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+	    if (ssh->pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+		bombout(("Unexpected response to shell/command request:"
+			 " packet type %d", ssh->pktin.type));
+		crStopV;
+	    }
+	    /*
+	     * We failed to start the command. If this is the
+	     * fallback command, we really are finished; if it's
+	     * not, and if the fallback command exists, try falling
+	     * back to it before complaining.
+	     */
+	    if (!ssh->fallback_cmd && ssh->cfg.remote_cmd_ptr2 != NULL) {
+		logevent("Primary command failed; attempting fallback");
+		ssh->fallback_cmd = TRUE;
+		continue;
+	    }
+	    bombout(("Server refused to start a shell/command"));
+	    crStopV;
+	} else {
+	    logevent("Started a shell/command");
+	}
+	break;
+    }
+
+    ssh->state = SSH_STATE_SESSION;
+    if (ssh->size_needed)
+	ssh_size(ssh, ssh->term_width, ssh->term_height);
+    if (ssh->eof_needed)
+	ssh_special(ssh, TS_EOF);
+
+    /*
+     * Transfer data!
+     */
+    if (ssh->ldisc)
+	ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+    ssh->send_ok = 1;
+    while (1) {
+	crReturnV;
+	s->try_send = FALSE;
+	if (ispkt) {
+	    if (ssh->pktin.type == SSH2_MSG_CHANNEL_DATA ||
+		ssh->pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
+		char *data;
+		int length;
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		if (ssh->pktin.type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
+		    ssh2_pkt_getuint32(ssh) != SSH2_EXTENDED_DATA_STDERR)
+		    continue;	       /* extended but not stderr */
+		ssh2_pkt_getstring(ssh, &data, &length);
+		if (data) {
+		    int bufsize = 0;
+		    c->v.v2.locwindow -= length;
+		    switch (c->type) {
+		      case CHAN_MAINSESSION:
+			bufsize =
+			    from_backend(ssh->frontend, ssh->pktin.type ==
+					 SSH2_MSG_CHANNEL_EXTENDED_DATA,
+					 data, length);
+			break;
+		      case CHAN_X11:
+			bufsize = x11_send(c->u.x11.s, data, length);
+			break;
+		      case CHAN_SOCKDATA:
+			bufsize = pfd_send(c->u.pfd.s, data, length);
+			break;
+		      case CHAN_AGENT:
+			while (length > 0) {
+			    if (c->u.a.lensofar < 4) {
+				int l = min(4 - c->u.a.lensofar, length);
+				memcpy(c->u.a.msglen + c->u.a.lensofar,
+				       data, l);
+				data += l;
+				length -= l;
+				c->u.a.lensofar += l;
+			    }
+			    if (c->u.a.lensofar == 4) {
+				c->u.a.totallen =
+				    4 + GET_32BIT(c->u.a.msglen);
+				c->u.a.message = snewn(c->u.a.totallen,
+						       unsigned char);
+				memcpy(c->u.a.message, c->u.a.msglen, 4);
+			    }
+			    if (c->u.a.lensofar >= 4 && length > 0) {
+				int l =
+				    min(c->u.a.totallen - c->u.a.lensofar,
+					length);
+				memcpy(c->u.a.message + c->u.a.lensofar,
+				       data, l);
+				data += l;
+				length -= l;
+				c->u.a.lensofar += l;
+			    }
+			    if (c->u.a.lensofar == c->u.a.totallen) {
+				void *reply;
+				int replylen;
+				if (agent_query(c->u.a.message,
+						c->u.a.totallen,
+						&reply, &replylen,
+						ssh_agentf_callback, c))
+				    ssh_agentf_callback(c, reply, replylen);
+				sfree(c->u.a.message);
+				c->u.a.lensofar = 0;
+			    }
+			}
+			bufsize = 0;
+			break;
+		    }
+		    /*
+		     * If we are not buffering too much data,
+		     * enlarge the window again at the remote side.
+		     */
+		    if (bufsize < OUR_V2_WINSIZE)
+			ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
+		}
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_EOF) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+
+		if (c->type == CHAN_X11) {
+		    /*
+		     * Remote EOF on an X11 channel means we should
+		     * wrap up and close the channel ourselves.
+		     */
+		    x11_close(c->u.x11.s);
+		    sshfwd_close(c);
+		} else if (c->type == CHAN_AGENT) {
+		    sshfwd_close(c);
+		} else if (c->type == CHAN_SOCKDATA) {
+		    pfd_close(c->u.pfd.s);
+		    sshfwd_close(c);
+		}
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c || ((int)c->remoteid) == -1) {
+		    bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
+			     c ? "half-open" : "nonexistent", i));
+		    crStopV;
+		}
+		/* Do pre-close processing on the channel. */
+		switch (c->type) {
+		  case CHAN_MAINSESSION:
+		    break;	       /* nothing to see here, move along */
+		  case CHAN_X11:
+		    if (c->u.x11.s != NULL)
+			x11_close(c->u.x11.s);
+		    sshfwd_close(c);
+		    break;
+		  case CHAN_AGENT:
+		    sshfwd_close(c);
+		    break;
+		  case CHAN_SOCKDATA:
+		    if (c->u.pfd.s != NULL)
+			pfd_close(c->u.pfd.s);
+		    sshfwd_close(c);
+		    break;
+		}
+		if (c->closes == 0) {
+		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
+		    ssh2_pkt_adduint32(ssh, c->remoteid);
+		    ssh2_pkt_send(ssh);
+		}
+		del234(ssh->channels, c);
+		bufchain_clear(&c->v.v2.outbuffer);
+		sfree(c);
+
+		/*
+		 * See if that was the last channel left open.
+		 */
+		if (count234(ssh->channels) == 0) {
+		    logevent("All channels closed. Disconnecting");
+#if 0
+                    /*
+                     * We used to send SSH_MSG_DISCONNECT here,
+                     * because I'd believed that _every_ conforming
+                     * SSH2 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.
+                     */
+		    ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+		    ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
+		    ssh2_pkt_addstring(ssh, "All open channels closed");
+		    ssh2_pkt_addstring(ssh, "en");	/* language tag */
+		    ssh2_pkt_send(ssh);
+#endif
+                    ssh_closing((Plug)ssh, NULL, 0, 0);
+		    crStopV;
+		}
+		continue;	       /* remote sends close; ignore (FIXME) */
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c || c->closes)
+		    continue;	       /* nonexistent or closing channel */
+		c->v.v2.remwindow += ssh2_pkt_getuint32(ssh);
+		s->try_send = TRUE;
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		if (c->type != CHAN_SOCKDATA_DORMANT)
+		    continue;	       /* dunno why they're confirming this */
+		c->remoteid = ssh2_pkt_getuint32(ssh);
+		c->type = CHAN_SOCKDATA;
+		c->v.v2.remwindow = ssh2_pkt_getuint32(ssh);
+		c->v.v2.remmaxpkt = ssh2_pkt_getuint32(ssh);
+		if (c->u.pfd.s)
+		    pfd_confirm(c->u.pfd.s);
+		if (c->closes) {
+		    /*
+		     * We have a pending close on this channel,
+		     * which we decided on before the server acked
+		     * the channel open. So now we know the
+		     * remoteid, we can close it again.
+		     */
+		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_CLOSE);
+		    ssh2_pkt_adduint32(ssh, c->remoteid);
+		    ssh2_pkt_send(ssh);
+		}
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+		unsigned i = ssh2_pkt_getuint32(ssh);
+		struct ssh_channel *c;
+		c = find234(ssh->channels, &i, ssh_channelfind);
+		if (!c)
+		    continue;	       /* nonexistent channel */
+		if (c->type != CHAN_SOCKDATA_DORMANT)
+		    continue;	       /* dunno why they're failing this */
+
+		logevent("Forwarded connection refused by server");
+
+		pfd_close(c->u.pfd.s);
+
+		del234(ssh->channels, c);
+		sfree(c);
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+ 		unsigned localid;
+		char *type;
+		int typelen, want_reply;
+		struct ssh_channel *c;
+
+		localid = ssh2_pkt_getuint32(ssh);
+		ssh2_pkt_getstring(ssh, &type, &typelen);
+		want_reply = ssh2_pkt_getbool(ssh);
+
+		/*
+		 * First, check that the channel exists. Otherwise,
+		 * we can instantly disconnect with a rude message.
+		 */
+		c = find234(ssh->channels, &localid, ssh_channelfind);
+		if (!c) {
+		    char buf[80];
+		    sprintf(buf, "Received channel request for nonexistent"
+			    " channel %d", localid);
+		    logevent(buf);
+		    ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+		    ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
+		    ssh2_pkt_addstring(ssh, buf);
+		    ssh2_pkt_addstring(ssh, "en");	/* language tag */
+		    ssh2_pkt_send(ssh);
+		    connection_fatal(ssh->frontend, "%s", buf);
+                    ssh_closing((Plug)ssh, NULL, 0, 0);
+		    crStopV;
+		}
+
+		/*
+		 * Having got the channel number, we now look at
+		 * the request type string to see if it's something
+		 * we recognise.
+		 */
+		if (typelen == 11 && !memcmp(type, "exit-status", 11) &&
+		    c == ssh->mainchan) {
+		    /* We recognise "exit-status" on the primary channel. */
+		    char buf[100];
+		    ssh->exitcode = ssh2_pkt_getuint32(ssh);
+		    sprintf(buf, "Server sent command exit status %d",
+			    ssh->exitcode);
+		    logevent(buf);
+		    if (want_reply) {
+			ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_SUCCESS);
+			ssh2_pkt_adduint32(ssh, c->remoteid);
+			ssh2_pkt_send(ssh);
+		    }
+		} else {
+		    /*
+		     * This is a channel request we don't know
+		     * about, so we now either ignore the request
+		     * or respond with CHANNEL_FAILURE, depending
+		     * on want_reply.
+		     */
+		    if (want_reply) {
+			ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_FAILURE);
+			ssh2_pkt_adduint32(ssh, c->remoteid);
+			ssh2_pkt_send(ssh);
+		    }
+		}
+	    } else if (ssh->pktin.type == SSH2_MSG_GLOBAL_REQUEST) {
+		char *type;
+		int typelen, want_reply;
+
+		ssh2_pkt_getstring(ssh, &type, &typelen);
+		want_reply = ssh2_pkt_getbool(ssh);
+
+                /*
+                 * We currently don't support any global requests
+                 * at all, so we either ignore the request or
+                 * respond with REQUEST_FAILURE, depending on
+                 * want_reply.
+                 */
+                if (want_reply) {
+                    ssh2_pkt_init(ssh, SSH2_MSG_REQUEST_FAILURE);
+                    ssh2_pkt_send(ssh);
+		}
+	    } else if (ssh->pktin.type == SSH2_MSG_CHANNEL_OPEN) {
+		char *type;
+		int typelen;
+		char *peeraddr;
+		int peeraddrlen;
+		int peerport;
+		char *error = NULL;
+		struct ssh_channel *c;
+		unsigned remid, winsize, pktsize;
+		ssh2_pkt_getstring(ssh, &type, &typelen);
+		c = snew(struct ssh_channel);
+		c->ssh = ssh;
+
+		remid = ssh2_pkt_getuint32(ssh);
+		winsize = ssh2_pkt_getuint32(ssh);
+		pktsize = ssh2_pkt_getuint32(ssh);
+
+		if (typelen == 3 && !memcmp(type, "x11", 3)) {
+		    char *addrstr;
+
+                    ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen);
+		    addrstr = snewn(peeraddrlen+1, char);
+		    memcpy(addrstr, peeraddr, peeraddrlen);
+		    peeraddr[peeraddrlen] = '\0';
+                    peerport = ssh2_pkt_getuint32(ssh);
+
+		    if (!ssh->X11_fwd_enabled)
+			error = "X11 forwarding is not enabled";
+		    else if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c,
+				      ssh->x11auth, addrstr, peerport,
+				      &ssh->cfg) != NULL) {
+			error = "Unable to open an X11 connection";
+		    } else {
+			c->type = CHAN_X11;
+		    }
+
+		    sfree(addrstr);
+		} else if (typelen == 15 &&
+			   !memcmp(type, "forwarded-tcpip", 15)) {
+		    struct ssh_rportfwd pf, *realpf;
+		    char *dummy;
+		    int dummylen;
+		    ssh2_pkt_getstring(ssh, &dummy, &dummylen);/* skip address */
+		    pf.sport = ssh2_pkt_getuint32(ssh);
+                    ssh2_pkt_getstring(ssh, &peeraddr, &peeraddrlen);
+                    peerport = ssh2_pkt_getuint32(ssh);
+		    realpf = find234(ssh->rportfwds, &pf, NULL);
+		    if (realpf == NULL) {
+			error = "Remote port is not recognised";
+		    } else {
+			const char *e = pfd_newconnect(&c->u.pfd.s,
+						       realpf->dhost,
+						       realpf->dport, c,
+						       &ssh->cfg);
+			logeventf(ssh, "Received remote port open request"
+				  " for %s:%d", realpf->dhost, realpf->dport);
+			if (e != NULL) {
+			    logeventf(ssh, "Port open failed: %s", e);
+			    error = "Port open failed";
+			} else {
+			    logevent("Forwarded port opened successfully");
+			    c->type = CHAN_SOCKDATA;
+			}
+		    }
+		} else if (typelen == 22 &&
+			   !memcmp(type, "[email protected]", 3)) {
+		    if (!ssh->agentfwd_enabled)
+			error = "Agent forwarding is not enabled";
+		    else {
+			c->type = CHAN_AGENT;	/* identify channel type */
+			c->u.a.lensofar = 0;
+		    }
+		} else {
+		    error = "Unsupported channel type requested";
+		}
+
+		c->remoteid = remid;
+		if (error) {
+		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN_FAILURE);
+		    ssh2_pkt_adduint32(ssh, c->remoteid);
+		    ssh2_pkt_adduint32(ssh, SSH2_OPEN_CONNECT_FAILED);
+		    ssh2_pkt_addstring(ssh, error);
+		    ssh2_pkt_addstring(ssh, "en");	/* language tag */
+		    ssh2_pkt_send(ssh);
+		    sfree(c);
+		} else {
+		    c->localid = alloc_channel_id(ssh);
+		    c->closes = 0;
+		    c->v.v2.locwindow = OUR_V2_WINSIZE;
+		    c->v.v2.remwindow = winsize;
+		    c->v.v2.remmaxpkt = pktsize;
+		    bufchain_init(&c->v.v2.outbuffer);
+		    add234(ssh->channels, c);
+		    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+		    ssh2_pkt_adduint32(ssh, c->remoteid);
+		    ssh2_pkt_adduint32(ssh, c->localid);
+		    ssh2_pkt_adduint32(ssh, c->v.v2.locwindow);
+		    ssh2_pkt_adduint32(ssh, 0x4000UL);	/* our max pkt size */
+		    ssh2_pkt_send(ssh);
+		}
+	    } else {
+		bombout(("Strange packet received: type %d", ssh->pktin.type));
+		crStopV;
+	    }
+	} else {
+	    /*
+	     * We have spare data. Add it to the channel buffer.
+	     */
+	    ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
+	    s->try_send = TRUE;
+	}
+	if (s->try_send) {
+	    int i;
+	    struct ssh_channel *c;
+	    /*
+	     * Try to send data on all channels if we can.
+	     */
+	    for (i = 0; NULL != (c = index234(ssh->channels, i)); i++) {
+		int bufsize;
+		if (c->closes)
+		    continue;	       /* don't send on closing channels */
+		bufsize = ssh2_try_send(c);
+		if (bufsize == 0) {
+		    switch (c->type) {
+		      case CHAN_MAINSESSION:
+			/* stdin need not receive an unthrottle
+			 * notification since it will be polled */
+			break;
+		      case CHAN_X11:
+			x11_unthrottle(c->u.x11.s);
+			break;
+		      case CHAN_AGENT:
+			/* agent sockets are request/response and need no
+			 * buffer management */
+			break;
+		      case CHAN_SOCKDATA:
+			pfd_unthrottle(c->u.pfd.s);
+			break;
+		    }
+		}
+	    }
+	}
+    }
+
+    crFinishV;
+}
+
+/*
+ * Handle the top-level SSH2 protocol.
+ */
+static void ssh2_protocol(Ssh ssh, unsigned char *in, int inlen, int ispkt)
+{
+    if (do_ssh2_transport(ssh, in, inlen, ispkt) == 0)
+	return;
+    do_ssh2_authconn(ssh, in, inlen, ispkt);
+}
+
+/*
+ * Called to set up the connection.
+ *
+ * Returns an error message, or NULL on success.
+ */
+static const char *ssh_init(void *frontend_handle, void **backend_handle,
+			    Config *cfg,
+			    char *host, int port, char **realhost, int nodelay)
+{
+    const char *p;
+    Ssh ssh;
+
+    ssh = snew(struct ssh_tag);
+    ssh->cfg = *cfg;		       /* STRUCTURE COPY */
+    ssh->version = 0;		       /* when not ready yet */
+    ssh->s = NULL;
+    ssh->cipher = NULL;
+    ssh->v1_cipher_ctx = NULL;
+    ssh->crcda_ctx = NULL;
+    ssh->cscipher = NULL;
+    ssh->cs_cipher_ctx = NULL;
+    ssh->sccipher = NULL;
+    ssh->sc_cipher_ctx = NULL;
+    ssh->csmac = NULL;
+    ssh->cs_mac_ctx = NULL;
+    ssh->scmac = NULL;
+    ssh->sc_mac_ctx = NULL;
+    ssh->cscomp = NULL;
+    ssh->cs_comp_ctx = NULL;
+    ssh->sccomp = NULL;
+    ssh->sc_comp_ctx = NULL;
+    ssh->kex = NULL;
+    ssh->kex_ctx = NULL;
+    ssh->hostkey = NULL;
+    ssh->exitcode = -1;
+    ssh->state = SSH_STATE_PREPACKET;
+    ssh->size_needed = FALSE;
+    ssh->eof_needed = FALSE;
+    ssh->ldisc = NULL;
+    ssh->logctx = NULL;
+    {
+	static const struct Packet empty = { 0, 0, NULL, NULL, 0 };
+	ssh->pktin = ssh->pktout = empty;
+    }
+    ssh->deferred_send_data = NULL;
+    ssh->deferred_len = 0;
+    ssh->deferred_size = 0;
+    ssh->fallback_cmd = 0;
+    ssh->pkt_ctx = 0;
+    ssh->x11auth = NULL;
+    ssh->v1_compressing = FALSE;
+    ssh->v2_outgoing_sequence = 0;
+    ssh->ssh1_rdpkt_crstate = 0;
+    ssh->ssh2_rdpkt_crstate = 0;
+    ssh->do_ssh_init_crstate = 0;
+    ssh->ssh_gotdata_crstate = 0;
+    ssh->ssh1_protocol_crstate = 0;
+    ssh->do_ssh1_login_crstate = 0;
+    ssh->do_ssh2_transport_crstate = 0;
+    ssh->do_ssh2_authconn_crstate = 0;
+    ssh->do_ssh_init_state = NULL;
+    ssh->do_ssh1_login_state = NULL;
+    ssh->do_ssh2_transport_state = NULL;
+    ssh->do_ssh2_authconn_state = NULL;
+    ssh->mainchan = NULL;
+    ssh->throttled_all = 0;
+    ssh->v1_stdout_throttling = 0;
+
+    *backend_handle = ssh;
+
+#ifdef MSCRYPTOAPI
+    if (crypto_startup() == 0)
+	return "Microsoft high encryption pack not installed!";
+#endif
+
+    ssh->frontend = frontend_handle;
+    ssh->term_width = ssh->cfg.width;
+    ssh->term_height = ssh->cfg.height;
+
+    ssh->channels = NULL;
+    ssh->rportfwds = NULL;
+
+    ssh->send_ok = 0;
+    ssh->editing = 0;
+    ssh->echoing = 0;
+    ssh->v1_throttle_count = 0;
+    ssh->overall_bufsize = 0;
+    ssh->fallback_cmd = 0;
+
+    ssh->protocol = NULL;
+
+    p = connect_to_host(ssh, host, port, realhost, nodelay);
+    if (p != NULL)
+	return p;
+
+    return NULL;
+}
+
+static void ssh_free(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    struct ssh_channel *c;
+    struct ssh_rportfwd *pf;
+
+    if (ssh->v1_cipher_ctx)
+	ssh->cipher->free_context(ssh->v1_cipher_ctx);
+    if (ssh->cs_cipher_ctx)
+	ssh->cscipher->free_context(ssh->cs_cipher_ctx);
+    if (ssh->sc_cipher_ctx)
+	ssh->sccipher->free_context(ssh->sc_cipher_ctx);
+    if (ssh->cs_mac_ctx)
+	ssh->csmac->free_context(ssh->cs_mac_ctx);
+    if (ssh->sc_mac_ctx)
+	ssh->scmac->free_context(ssh->sc_mac_ctx);
+    if (ssh->cs_comp_ctx) {
+	if (ssh->cscomp)
+	    ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
+	else
+	    zlib_compress_cleanup(ssh->cs_comp_ctx);
+    }
+    if (ssh->sc_comp_ctx) {
+	if (ssh->sccomp)
+	    ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
+	else
+	    zlib_decompress_cleanup(ssh->sc_comp_ctx);
+    }
+    if (ssh->kex_ctx)
+	dh_cleanup(ssh->kex_ctx);
+    sfree(ssh->savedhost);
+
+    if (ssh->channels) {
+	while ((c = delpos234(ssh->channels, 0)) != NULL) {
+	    switch (c->type) {
+	      case CHAN_X11:
+		if (c->u.x11.s != NULL)
+		    x11_close(c->u.x11.s);
+		break;
+	      case CHAN_SOCKDATA:
+		if (c->u.pfd.s != NULL)
+		    pfd_close(c->u.pfd.s);
+		break;
+	    }
+	    sfree(c);
+	}
+	freetree234(ssh->channels);
+    }
+
+    if (ssh->rportfwds) {
+	while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
+	    sfree(pf);
+	freetree234(ssh->rportfwds);
+    }
+    sfree(ssh->deferred_send_data);
+    if (ssh->x11auth)
+	x11_free_auth(ssh->x11auth);
+    sfree(ssh->do_ssh_init_state);
+    sfree(ssh->do_ssh1_login_state);
+    sfree(ssh->do_ssh2_transport_state);
+    sfree(ssh->do_ssh2_authconn_state);
+    if (ssh->pktout.data) {
+	sfree(ssh->pktout.data);
+	ssh->pktout.data = NULL;
+    }
+    if (ssh->pktin.data) {
+	sfree(ssh->pktin.data);
+	ssh->pktin.data = NULL;
+    }
+    if (ssh->crcda_ctx) {
+	crcda_free_context(ssh->crcda_ctx);
+	ssh->crcda_ctx = NULL;
+    }
+    if (ssh->logctx) {
+	log_free(ssh->logctx);
+	ssh->logctx = NULL;
+    }
+    if (ssh->s)
+	ssh_do_close(ssh);
+    sfree(ssh);
+}
+
+/*
+ * Reconfigure the SSH backend.
+ * 
+ * Currently, this function does nothing very useful. In future,
+ * however, we could do some handy things with it. For example, we
+ * could make the port forwarding configurer active in the Change
+ * Settings box, and this routine could close down existing
+ * forwardings and open up new ones in response to changes.
+ */
+static void ssh_reconfig(void *handle, Config *cfg)
+{
+    Ssh ssh = (Ssh) handle;
+    ssh->cfg = *cfg;		       /* STRUCTURE COPY */
+}
+
+/*
+ * Called to send data down the Telnet connection.
+ */
+static int ssh_send(void *handle, char *buf, int len)
+{
+    Ssh ssh = (Ssh) handle;
+
+    if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
+	return 0;
+
+    ssh->protocol(ssh, (unsigned char *)buf, len, 0);
+
+    return ssh_sendbuffer(ssh);
+}
+
+/*
+ * Called to query the current amount of buffered stdin data.
+ */
+static int ssh_sendbuffer(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    int override_value;
+
+    if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
+	return 0;
+
+    /*
+     * If the SSH socket itself has backed up, add the total backup
+     * size on that to any individual buffer on the stdin channel.
+     */
+    override_value = 0;
+    if (ssh->throttled_all)
+	override_value = ssh->overall_bufsize;
+
+    if (ssh->version == 1) {
+	return override_value;
+    } else if (ssh->version == 2) {
+	if (!ssh->mainchan || ssh->mainchan->closes > 0)
+	    return override_value;
+	else
+	    return (override_value +
+		    bufchain_size(&ssh->mainchan->v.v2.outbuffer));
+    }
+
+    return 0;
+}
+
+/*
+ * Called to set the size of the window from SSH's POV.
+ */
+static void ssh_size(void *handle, int width, int height)
+{
+    Ssh ssh = (Ssh) handle;
+
+    ssh->term_width = width;
+    ssh->term_height = height;
+
+    switch (ssh->state) {
+      case SSH_STATE_BEFORE_SIZE:
+      case SSH_STATE_PREPACKET:
+      case SSH_STATE_CLOSED:
+	break;			       /* do nothing */
+      case SSH_STATE_INTERMED:
+	ssh->size_needed = TRUE;       /* buffer for later */
+	break;
+      case SSH_STATE_SESSION:
+	if (!ssh->cfg.nopty) {
+	    if (ssh->version == 1) {
+		send_packet(ssh, SSH1_CMSG_WINDOW_SIZE,
+			    PKT_INT, ssh->term_height,
+			    PKT_INT, ssh->term_width,
+			    PKT_INT, 0, PKT_INT, 0, PKT_END);
+	    } else {
+		ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
+		ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
+		ssh2_pkt_addstring(ssh, "window-change");
+		ssh2_pkt_addbool(ssh, 0);
+		ssh2_pkt_adduint32(ssh, ssh->term_width);
+		ssh2_pkt_adduint32(ssh, ssh->term_height);
+		ssh2_pkt_adduint32(ssh, 0);
+		ssh2_pkt_adduint32(ssh, 0);
+		ssh2_pkt_send(ssh);
+	    }
+	}
+	break;
+    }
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const struct telnet_special *ssh_get_specials(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+
+    if (ssh->version == 1) {
+	static const struct telnet_special ssh1_specials[] = {
+	    {"IGNORE message", TS_NOP},
+	    {NULL, 0}
+	};
+	return ssh1_specials;
+    } else if (ssh->version == 2) {
+	static const struct telnet_special ssh2_specials[] = {
+	    {"Break", TS_BRK},
+	    {"IGNORE message", TS_NOP},
+	    {NULL, 0}
+	};
+	return ssh2_specials;
+    } else
+	return NULL;
+}
+
+/*
+ * Send Telnet special codes. TS_EOF is useful for `plink', so you
+ * can send an EOF and collect resulting output (e.g. `plink
+ * hostname sort').
+ */
+static void ssh_special(void *handle, Telnet_Special code)
+{
+    Ssh ssh = (Ssh) handle;
+
+    if (code == TS_EOF) {
+	if (ssh->state != SSH_STATE_SESSION) {
+	    /*
+	     * Buffer the EOF in case we are pre-SESSION, so we can
+	     * send it as soon as we reach SESSION.
+	     */
+	    if (code == TS_EOF)
+		ssh->eof_needed = TRUE;
+	    return;
+	}
+	if (ssh->version == 1) {
+	    send_packet(ssh, SSH1_CMSG_EOF, PKT_END);
+	} else {
+	    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_EOF);
+	    ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
+	    ssh2_pkt_send(ssh);
+	}
+	logevent("Sent EOF message");
+    } else if (code == TS_PING || code == TS_NOP) {
+	if (ssh->state == SSH_STATE_CLOSED
+	    || ssh->state == SSH_STATE_PREPACKET) return;
+	if (ssh->version == 1) {
+	    if (!(ssh->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+		send_packet(ssh, SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
+	} else {
+	    ssh2_pkt_init(ssh, SSH2_MSG_IGNORE);
+	    ssh2_pkt_addstring_start(ssh);
+	    ssh2_pkt_send(ssh);
+	}
+    } else if (code == TS_BRK) {
+	if (ssh->state == SSH_STATE_CLOSED
+	    || ssh->state == SSH_STATE_PREPACKET) return;
+	if (ssh->version == 1) {
+	    logevent("Unable to send BREAK signal in SSH1");
+	} else {
+	    ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_REQUEST);
+	    ssh2_pkt_adduint32(ssh, ssh->mainchan->remoteid);
+	    ssh2_pkt_addstring(ssh, "break");
+	    ssh2_pkt_addbool(ssh, 0);
+	    ssh2_pkt_adduint32(ssh, 0);   /* default break length */
+	    ssh2_pkt_send(ssh);
+	}
+    } else {
+	/* do nothing */
+    }
+}
+
+void *new_sock_channel(void *handle, Socket s)
+{
+    Ssh ssh = (Ssh) handle;
+    struct ssh_channel *c;
+    c = snew(struct ssh_channel);
+    c->ssh = ssh;
+
+    if (c) {
+	c->remoteid = -1;	       /* to be set when open confirmed */
+	c->localid = alloc_channel_id(ssh);
+	c->closes = 0;
+	c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
+	c->u.pfd.s = s;
+	bufchain_init(&c->v.v2.outbuffer);
+	add234(ssh->channels, c);
+    }
+    return c;
+}
+
+/*
+ * This is called when stdout/stderr (the entity to which
+ * from_backend sends data) manages to clear some backlog.
+ */
+static void ssh_unthrottle(void *handle, int bufsize)
+{
+    Ssh ssh = (Ssh) handle;
+    if (ssh->version == 1) {
+	if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
+	    ssh->v1_stdout_throttling = 0;
+	    ssh1_throttle(ssh, -1);
+	}
+    } else {
+	if (ssh->mainchan && ssh->mainchan->closes == 0)
+	    ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize);
+    }
+}
+
+void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
+{
+    struct ssh_channel *c = (struct ssh_channel *)channel;
+    Ssh ssh = c->ssh;
+
+    logeventf(ssh, "Opening forwarded connection to %s:%d", hostname, port);
+
+    if (ssh->version == 1) {
+	send_packet(ssh, SSH1_MSG_PORT_OPEN,
+		    PKT_INT, c->localid,
+		    PKT_STR, hostname,
+		    PKT_INT, port,
+		    //PKT_STR, <org:orgport>,
+		    PKT_END);
+    } else {
+	ssh2_pkt_init(ssh, SSH2_MSG_CHANNEL_OPEN);
+	ssh2_pkt_addstring(ssh, "direct-tcpip");
+	ssh2_pkt_adduint32(ssh, c->localid);
+	c->v.v2.locwindow = OUR_V2_WINSIZE;
+	ssh2_pkt_adduint32(ssh, c->v.v2.locwindow);/* our window size */
+	ssh2_pkt_adduint32(ssh, 0x4000UL);      /* our max pkt size */
+	ssh2_pkt_addstring(ssh, hostname);
+	ssh2_pkt_adduint32(ssh, 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.
+	 */
+	ssh2_pkt_addstring(ssh, "client-side-connection");
+	ssh2_pkt_adduint32(ssh, 0);
+	ssh2_pkt_send(ssh);
+    }
+}
+
+
+static Socket ssh_socket(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    return ssh->s;
+}
+
+static int ssh_sendok(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    return ssh->send_ok;
+}
+
+static int ssh_ldisc(void *handle, int option)
+{
+    Ssh ssh = (Ssh) handle;
+    if (option == LD_ECHO)
+	return ssh->echoing;
+    if (option == LD_EDIT)
+	return ssh->editing;
+    return FALSE;
+}
+
+static void ssh_provide_ldisc(void *handle, void *ldisc)
+{
+    Ssh ssh = (Ssh) handle;
+    ssh->ldisc = ldisc;
+}
+
+static void ssh_provide_logctx(void *handle, void *logctx)
+{
+    Ssh ssh = (Ssh) handle;
+    ssh->logctx = logctx;
+}
+
+static int ssh_return_exitcode(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    if (ssh->s != NULL)
+        return -1;
+    else
+        return (ssh->exitcode >= 0 ? ssh->exitcode : 0);
+}
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which scp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern int ssh_fallback_cmd(void *handle)
+{
+    Ssh ssh = (Ssh) handle;
+    return ssh->fallback_cmd;
+}
+
+Backend ssh_backend = {
+    ssh_init,
+    ssh_free,
+    ssh_reconfig,
+    ssh_send,
+    ssh_sendbuffer,
+    ssh_size,
+    ssh_special,
+    ssh_get_specials,
+    ssh_socket,
+    ssh_return_exitcode,
+    ssh_sendok,
+    ssh_ldisc,
+    ssh_provide_ldisc,
+    ssh_provide_logctx,
+    ssh_unthrottle,
+    22
+};

+ 2 - 0
putty/SSH.H

@@ -186,6 +186,7 @@ struct ssh_signkey {
 			unsigned char *priv_blob, int priv_len);
     void *(*openssh_createkey) (unsigned char **blob, int *len);
     int (*openssh_fmtkey) (void *key, unsigned char *blob, int len);
+    int (*pubkey_bits) (void *blob, int len);
     char *(*fingerprint) (void *key);
     int (*verifysig) (void *key, char *sig, int siglen,
 		      char *data, int datalen);
@@ -350,6 +351,7 @@ char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
 			   int *pub_blob_len, const char **errorstr);
 int ssh2_save_userkey(const Filename *filename, struct ssh2_userkey *key,
 		      char *passphrase);
+const struct ssh_signkey *find_pubkey_alg(const char *name);
 
 enum {
     SSH_KEYTYPE_UNOPENABLE,

+ 2 - 1
putty/SSHBN.C

@@ -133,7 +133,7 @@ static void internal_add_shifted(BignumInt *number,
     int bshift = shift % BIGNUM_INT_BITS;
     BignumDblInt addend;
 
-    addend = n << bshift;
+    addend = (BignumDblInt)n << bshift;
 
     while (addend) {
 	addend += number[word];
@@ -909,6 +909,7 @@ Bignum modinv(Bignum number, Bignum modulus)
 	x = bigmuladd(q, xp, t);
 	sign = -sign;
 	freebn(t);
+	freebn(q);
     }
 
     freebn(b);

+ 13 - 0
putty/SSHDSS.C

@@ -473,6 +473,18 @@ static int dss_openssh_fmtkey(void *key, unsigned char *blob, int len)
     return bloblen;
 }
 
+static int dss_pubkey_bits(void *blob, int len)
+{
+    struct dss_key *dss;
+    int ret;
+
+    dss = dss_newkey((char *) blob, len);
+    ret = bignum_bitcount(dss->p);
+    dss_freekey(dss);
+
+    return ret;
+}
+
 static unsigned char *dss_sign(void *key, char *data, int datalen, int *siglen)
 {
     /*
@@ -630,6 +642,7 @@ const struct ssh_signkey ssh_dss = {
     dss_createkey,
     dss_openssh_createkey,
     dss_openssh_fmtkey,
+    dss_pubkey_bits,
     dss_fingerprint,
     dss_verifysig,
     dss_sign,

+ 19 - 11
putty/SSHPUBK.C

@@ -186,6 +186,7 @@ int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase,
 	 * This routine will take care of calling fclose() for us.
 	 */
 	ret = loadrsakey_main(fp, key, FALSE, NULL, passphrase, &error);
+	fp = NULL;
 	goto end;
     }
 
@@ -195,7 +196,8 @@ int loadrsakey(const Filename *filename, struct RSAKey *key, char *passphrase,
     error = "not an SSH-1 RSA file";
 
   end:
-    fclose(fp);
+    if (fp)
+	fclose(fp);
     if ((ret != 1) && errorstr)
 	*errorstr = error;
     return ret;
@@ -264,6 +266,7 @@ int rsakey_pubblob(const Filename *filename, void **blob, int *bloblen,
 	    *blob = rsa_public_blob(&key, bloblen);
 	    freersakey(&key);
 	    ret = 1;
+	    fp = NULL;
 	}
     } else {
 	error = "not an SSH-1 RSA file";
@@ -612,6 +615,16 @@ struct ssh2_userkey ssh2_wrong_passphrase = {
     NULL, NULL, NULL
 };
 
+const struct ssh_signkey *find_pubkey_alg(const char *name)
+{
+    if (!strcmp(name, "ssh-rsa"))
+	return &ssh_rsa;
+    else if (!strcmp(name, "ssh-dss"))
+	return &ssh_dss;
+    else
+	return NULL;
+}
+
 struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
 				       char *passphrase, const char **errorstr)
 {
@@ -653,11 +666,8 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
     if ((b = read_body(fp)) == NULL)
 	goto error;
     /* Select key algorithm structure. */
-    if (!strcmp(b, "ssh-rsa"))
-	alg = &ssh_rsa;
-    else if (!strcmp(b, "ssh-dss"))
-	alg = &ssh_dss;
-    else {
+    alg = find_pubkey_alg(b);
+    if (!alg) {
 	sfree(b);
 	goto error;
     }
@@ -814,6 +824,7 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
 	    /* An incorrect MAC is an unconditional Error if the key is
 	     * unencrypted. Otherwise, it means Wrong Passphrase. */
 	    if (cipher) {
+		error = "wrong passphrase";
 		ret = SSH2_WRONG_PASSPHRASE;
 	    } else {
 		error = "MAC failed";
@@ -897,11 +908,8 @@ char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
     if ((b = read_body(fp)) == NULL)
 	goto error;
     /* Select key algorithm structure. Currently only ssh-rsa. */
-    if (!strcmp(b, "ssh-rsa"))
-	alg = &ssh_rsa;
-    else if (!strcmp(b, "ssh-dss"))
-	alg = &ssh_dss;
-    else {
+    alg = find_pubkey_alg(b);
+    if (!alg) {
 	sfree(b);
 	goto error;
     }

+ 14 - 1
putty/SSHRSA.C

@@ -629,6 +629,18 @@ static int rsa2_openssh_fmtkey(void *key, unsigned char *blob, int len)
     return bloblen;
 }
 
+static int rsa2_pubkey_bits(void *blob, int len)
+{
+    struct RSAKey *rsa;
+    int ret;
+
+    rsa = rsa2_newkey((char *) blob, len);
+    ret = bignum_bitcount(rsa->modulus);
+    rsa2_freekey(rsa);
+
+    return ret;
+}
+
 static char *rsa2_fingerprint(void *key)
 {
     struct RSAKey *rsa = (struct RSAKey *) key;
@@ -715,7 +727,7 @@ static int rsa2_verifysig(void *key, char *sig, int siglen,
 
     ret = 1;
 
-    bytes = bignum_bitcount(rsa->modulus) / 8;
+    bytes = (bignum_bitcount(rsa->modulus)+7) / 8;
     /* Top (partial) byte should be zero. */
     if (bignum_byte(out, bytes - 1) != 0)
 	ret = 0;
@@ -794,6 +806,7 @@ const struct ssh_signkey ssh_rsa = {
     rsa2_createkey,
     rsa2_openssh_createkey,
     rsa2_openssh_fmtkey,
+    rsa2_pubkey_bits,
     rsa2_fingerprint,
     rsa2_verifysig,
     rsa2_sign,

+ 5 - 1
putty/SSHZLIB.C

@@ -602,6 +602,8 @@ void zlib_compress_cleanup(void *handle)
 {
     struct LZ77Context *ectx = (struct LZ77Context *)handle;
     sfree(ectx->userdata);
+    sfree(ectx->ictx);
+    sfree(ectx);
 }
 
 /*
@@ -963,13 +965,15 @@ void *zlib_decompress_init(void)
 void zlib_decompress_cleanup(void *handle)
 {
     struct zlib_decompress_ctx *dctx = (struct zlib_decompress_ctx *)handle;
-    
+
     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);
     sfree(dctx);
 }
 

+ 21 - 18
putty/TERMINAL.C

@@ -850,26 +850,29 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
 	     */
 	    seltop = sb ? -term->savelines : topline;
 
-	    if (term->selstart.y >= seltop &&
-		term->selstart.y <= botline) {
-		term->selstart.y--;
-		if (term->selstart.y < seltop) {
-		    term->selstart.y = seltop;
-		    term->selstart.x = 0;
+	    if (term->selstate != NO_SELECTION) {
+		if (term->selstart.y >= seltop &&
+		    term->selstart.y <= botline) {
+		    term->selstart.y--;
+		    if (term->selstart.y < seltop) {
+			term->selstart.y = seltop;
+			term->selstart.x = 0;
+		    }
 		}
-	    }
-	    if (term->selend.y >= seltop && term->selend.y <= botline) {
-		term->selend.y--;
-		if (term->selend.y < seltop) {
-		    term->selend.y = seltop;
-		    term->selend.x = 0;
+		if (term->selend.y >= seltop && term->selend.y <= botline) {
+		    term->selend.y--;
+		    if (term->selend.y < seltop) {
+			term->selend.y = seltop;
+			term->selend.x = 0;
+		    }
 		}
-	    }
-	    if (term->selanchor.y >= seltop && term->selanchor.y <= botline) {
-		term->selanchor.y--;
-		if (term->selanchor.y < seltop) {
-		    term->selanchor.y = seltop;
-		    term->selanchor.x = 0;
+		if (term->selanchor.y >= seltop &&
+		    term->selanchor.y <= botline) {
+		    term->selanchor.y--;
+		    if (term->selanchor.y < seltop) {
+			term->selanchor.y = seltop;
+			term->selanchor.x = 0;
+		    }
 		}
 	    }
 

+ 3 - 2
putty/WINCTRLS.C

@@ -2282,8 +2282,9 @@ void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
     if (fs.height == 0)
 	buf = dupprintf("Font: %s, %sdefault height", fs.name, boldstr);
     else
-	buf = dupprintf("Font: %s, %s%d-point", fs.name, boldstr,
-			(fs.height < 0 ? -fs.height : fs.height));
+	buf = dupprintf("Font: %s, %s%d-%s", fs.name, boldstr,
+			(fs.height < 0 ? -fs.height : fs.height),
+			(fs.height < 0 ? "pixel" : "point"));
     SetDlgItemText(dp->hwnd, c->base_id+1, buf);
     sfree(buf);
 }

+ 1 - 1
putty/WINDLG.C

@@ -13,6 +13,7 @@
 
 #include <commctrl.h>
 #include <commdlg.h>
+#include <shellapi.h>
 
 #ifdef MSVC4
 #define TVINSERTSTRUCT  TV_INSERTSTRUCT
@@ -648,7 +649,6 @@ int do_reconfig(HWND hwnd)
     ctrl_free_box(ctrlbox);
     winctrl_cleanup(&ctrls_base);
     winctrl_cleanup(&ctrls_panel);
-    sfree(dp.errtitle);
     dp_cleanup(&dp);
 
     if (!ret)

+ 39 - 9
putty/WINDOW.C

@@ -110,7 +110,6 @@ static struct unicode_data ucsdata;
 static int session_closed;
 
 static const struct telnet_special *specials;
-static int specials_menu_position;
 
 static struct {
     HMENU menu;
@@ -2078,11 +2077,42 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 	     * window, we put up the System menu instead of doing
 	     * selection.
 	     */
-	    if (is_full_screen() && press && button == MBT_LEFT &&
-		X_POS(lParam) == 0 && Y_POS(lParam) == 0) {
-		SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, 0);
-		return 0;
+	    {
+		char mouse_on_hotspot = 0;
+		POINT pt;
+
+		GetCursorPos(&pt);
+#ifndef NO_MULTIMON
+		{
+		    HMONITOR mon;
+		    MONITORINFO mi;
+
+		    mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
+
+		    if (mon != NULL) {
+			mi.cbSize = sizeof(MONITORINFO);
+			GetMonitorInfo(mon, &mi);
+
+			if (mi.rcMonitor.left == pt.x &&
+			    mi.rcMonitor.top == pt.y) {
+			    mouse_on_hotspot = 1;
+			}
+			CloseHandle(mon);
+		    }
+		}
+#else
+		if (pt.x == 0 && pt.y == 0) {
+		    mouse_on_hotspot = 1;
+		}
+#endif
+		if (is_full_screen() && press &&
+		    button == MBT_LEFT && mouse_on_hotspot) {
+		    SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
+				MAKELPARAM(pt.x, pt.y));
+		    return 0;
+		}
 	    }
+
 	    if (press) {
 		click(button,
 		      TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
@@ -4570,7 +4600,7 @@ char *get_window_title(void *frontend, int icon)
 /*
  * See if we're in full-screen mode.
  */
-int is_full_screen()
+static int is_full_screen()
 {
     if (!IsZoomed(hwnd))
 	return FALSE;
@@ -4609,7 +4639,7 @@ static int get_fullscreen_rect(RECT * ss)
  * Go full-screen. This should only be called when we are already
  * maximised.
  */
-void make_full_screen()
+static void make_full_screen()
 {
     DWORD style;
 	RECT ss;
@@ -4643,7 +4673,7 @@ void make_full_screen()
 /*
  * Clear the full-screen attributes.
  */
-void clear_full_screen()
+static void clear_full_screen()
 {
     DWORD oldstyle, style;
 
@@ -4673,7 +4703,7 @@ void clear_full_screen()
 /*
  * Toggle full-screen mode.
  */
-void flip_full_screen()
+static void flip_full_screen()
 {
     if (is_full_screen()) {
 	ShowWindow(hwnd, SW_RESTORE);

+ 0 - 12
putty/WINSTORE.C

@@ -182,18 +182,6 @@ int read_setting_fontspec(void *handle, const char *name, FontSpec *result)
     ret.height = read_setting_i(handle, settingname, INT_MIN);
     sfree(settingname);
     if (ret.height == INT_MIN) return 0;
-    if (ret.height < 0) {
-	int oldh, newh;
-	HDC hdc = GetDC(NULL);
-	int logpix = GetDeviceCaps(hdc, LOGPIXELSY);
-	ReleaseDC(NULL, hdc);
-
-	oldh = -ret.height;
-	newh = MulDiv(oldh, 72, logpix) + 1;
-	if (MulDiv(newh, logpix, 72) > oldh)
-	    newh--;
-	ret.height = newh;
-    }
     *result = ret;
     return 1;
 }

+ 8 - 2
putty/WIN_RES.RC

@@ -1,4 +1,8 @@
 /* Some compilers, like Borland, don't have winresrc.h */
+#ifdef __LCC__ 
+#include <win.h>
+#else
+
 #ifndef NO_WINRESRC_H
 #ifndef MSVC4
 #include <winresrc.h>
@@ -7,6 +11,8 @@
 #endif
 #endif
 
+#endif /* end #ifdef __LCC__ */
+
 /* Some systems don't define this, so I do it myself if necessary */
 #ifndef TCS_MULTILINE
 #define TCS_MULTILINE 0x0200
@@ -38,7 +44,7 @@ BEGIN
     PUSHBUTTON "Visit &Web Site", IDA_WEB, 84, 52, 70, 14
     CTEXT "PuTTY", IDA_TEXT1, 10, 6, 194, 8
     CTEXT "", IDA_VERSION, 10, 16, 194, 16
-    CTEXT "\251 1997-2003 Simon Tatham. All rights reserved.",
+    CTEXT "\251 1997-2004 Simon Tatham. All rights reserved.",
           IDA_TEXT2, 10, 34, 194, 16
 END
 
@@ -70,7 +76,7 @@ FONT 8, "MS Shell Dlg"
 BEGIN
     DEFPUSHBUTTON "OK", IDOK, 98, 235, 44, 14
 
-    LTEXT "Copyright \251 1997-2003 Simon Tatham", 1000, 10, 10, 206, 8
+    LTEXT "Copyright \251 1997-2004 Simon Tatham", 1000, 10, 10, 206, 8
 
     LTEXT "Portions copyright Robert de Bath, Joris van Rantwijk, Delian", 1001, 10, 26, 206, 8
     LTEXT "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas", 1002, 10, 34, 206, 8

+ 2 - 0
putty/putty.org.h

@@ -794,6 +794,7 @@ int askappend(void *frontend, Filename filename);
 extern int console_batch_mode;
 int console_get_line(const char *prompt, char *str, int maxlen, int is_pw);
 void console_provide_logctx(void *logctx);
+int is_interactive(void);
 
 /*
  * Exports from printing.c.
@@ -848,5 +849,6 @@ const char *filename_to_str(const Filename *fn);
 int filename_equal(Filename f1, Filename f2);
 int filename_is_null(Filename fn);
 char *get_username(void);	       /* return value needs freeing */
+char *get_random_data(int bytes);      /* used in cmdgen.c */
 
 #endif

+ 30 - 10
release/winscpsetup.iss

@@ -1,4 +1,5 @@
 #define MainFileSource "..\WinSCP3.exe"
+#define ShellExtFileSource "..\DragExt.dll"
 #define ParentRegistryKey "Software\Martin Prikryl"
 #define RegistryKey ParentRegistryKey+"\WinSCP 2"
 #define PuttySourceDir "c:\Program Files\PuTTY"
@@ -37,6 +38,10 @@ AppPublisher=Martin Prikryl
 AppPublisherURL=http://winscp.sourceforge.net/
 AppSupportURL=http://winscp.sourceforge.net/forum/
 AppUpdatesURL=http://winscp.sourceforge.net/eng/download.php
+VersionInfoCompany=Martin Prikryl
+VersionInfoDescription=Setup for WinSCP {#Version} (Freeware SCP/SFTP client for Windows)
+VersionInfoVersion={#Major}.{#Minor}.{#Rev}.{#Build}
+VersionInfoTextVersion={#Version}
 DefaultDirName={pf}\WinSCP3
 DefaultGroupName=WinSCP3
 AllowNoIcons=yes
@@ -47,7 +52,8 @@ DisableStartupPrompt=yes
 AppVersion={#Version}
 AppVerName=WinSCP {#Version}
 OutputBaseFilename=winscp{#Major}{#Minor}{#Rev}setup{#SetupExt}
-Compression=bzip/9
+SolidCompression=yes
+Compression=lzma
 ShowTasksTreeLines=yes
 
 #define FindHandle
@@ -146,6 +152,8 @@ Name: custom; Description: {#Transl("CustomInstallation")}; \
 [Components]
 Name: main; Description: {#Transl("ApplicationComponent")}; \
   Types: {#FullLangs} full custom compact; Flags: fixed; Languages: {#Lang}
+Name: shellext; Description: {#Transl("ShellExtComponent")}; \
+  Types: {#FullLangs} compact full; Languages: {#Lang}
 Name: pageant; Description: {#Transl("PuTTYgenComponent")}; \
   Types: {#FullLangs} full; Languages: {#Lang}
 Name: puttygen; Description: {#Transl("PageantComponent")}; \
@@ -165,6 +173,7 @@ Name: desktopicon\common; Description: {#Transl("DesktopIconCommonTask")}; \
 Name: quicklaunchicon; Description: {#Transl("QuickLaunchIconTask")}; \
   Flags: unchecked; Languages: {#Lang}
 Name: sendtohook; Description: {#Transl("SendToHookTask")}; Languages: {#Lang}
+Name: urlhandler; Description: {#Transl("RegisterAsUrlHandler")}; Languages: {#Lang}
 
 [INI]
 Filename: "{app}\{#Transl("SupportForum")}.url"; Section: "InternetShortcut"; Key: "URL"; String: "http://winscp.sourceforge.net/forum/"; Languages: {#Lang}
@@ -245,11 +254,19 @@ Name: transl\eng; Description: "English"; Types: fulllangs full custom compact;
 
 #endif
 
+[Run]
+; This is called when urlhandler task is selected
+Filename: "{app}\WinSCP3.exe"; Parameters: "/RegisterAsUrlHandler"; \
+  Tasks: urlhandler
+
 [Files]
-Source: "{#MainFileSource}"; DestDir: "{app}"; Components: main; \
-  Flags: ignoreversion
+Source: "{#MainFileSource}"; DestDir: "{app}"; \
+  Components: main; Flags: ignoreversion
 Source: "licence"; DestName: "licence"; DestDir: "{app}"; \
   Components: main; Flags: ignoreversion
+Source: "{#ShellExtFileSource}"; DestDir: "{app}"; \
+  Components: shellext; \
+  Flags: ignoreversion regserver restartreplace restartreplace uninsrestartdelete
 Source: "{#PuttySourceDir}\LICENCE"; DestDir: "{app}\PuTTY"; \
   Components: pageant puttygen; Flags: ignoreversion
 Source: "{#PuttySourceDir}\putty.hlp"; DestDir: "{app}\PuTTY"; \
@@ -273,6 +290,16 @@ Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; ValueType:dword; \
   ValueName: "ShowAdvancedLoginOptions"; ValueData: 0; Check: IsTrue(20)
 Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; ValueType:dword; \
   ValueName: "ShowAdvancedLoginOptions"; ValueData: 1; Check: IsTrue(21)
+; This will remove url handler on uninstall 
+; (when urlhandler task was selected when installing)
+Root: HKCR; Subkey: "SFTP"; Flags: dontcreatekey uninsdeletekey; \
+  Tasks: urlhandler
+Root: HKCR; Subkey: "SCP"; Flags: dontcreatekey uninsdeletekey; \
+  Tasks: urlhandler
+Root: HKCU; Subkey: "Software\Classes\SFTP"; Flags: dontcreatekey uninsdeletekey; \
+  Tasks: urlhandler
+Root: HKCU; Subkey: "Software\Classes\SCP"; Flags: dontcreatekey uninsdeletekey; \
+  Tasks: urlhandler
 
 #ifdef INTL
 
@@ -298,13 +325,6 @@ Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; \
 
 #for {LangI = 0; LangI < LanguageCount; LangI++} EmitLang
 
-;[Components]
-;Name: transl\ru; Description: "Russian"; Types: fulllangs custom compact
-
-;[Files]
-;Source: "translations.nosetup\WinSCP3.ru"; DestDir: "{app}"; \
-;  Components: transl\ru; Flags: ignoreversion
-
 #endif
 
 [UninstallRun]

+ 3 - 0
resource/TextsCore.h

@@ -107,6 +107,9 @@
 #define SFTP_PACKET_TOO_BIG     201
 #define SCP_INIT_ERROR          202
 #define DUPLICATE_BOOKMARK      203
+#define MOVE_FILE_ERROR         204
+#define SFTP_PACKET_TOO_BIG_INIT_EXPLAIN 205
+#define PRESERVE_TIME_ERROR     206
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT 301

+ 6 - 3
resource/TextsCore1.rc

@@ -84,7 +84,7 @@ BEGIN
   SFTP_OPEN_FILE_ERROR, "Cannot open remote file '%s'."
   SFTP_CLOSE_FILE_ERROR, "Cannot close remote file '%s'."
   NOT_FILE_ERROR, "'%s' is not file!"
-  RENAME_AFTER_RESUME_ERROR, "Transfer was succesfully finished, but temporary transfer file '%s' could not be renamed to target file name '%s'."
+  RENAME_AFTER_RESUME_ERROR, "Transfer was succesfully finished, but temporary transfer file '%s' could not be renamed to target file name '%s'. If the problem persists, you may try to turn off transfer resume support."
   CREATE_LINK_ERROR, "Cannot create link '%s'."
   INVALID_SHELL_COMMAND, "Invalid command '%s'."
   SFTP_SERVER_MESSAGE_UNSUPPORTED, "None"
@@ -104,6 +104,9 @@ BEGIN
   SFTP_PACKET_TOO_BIG, "Received too large (%d B) SFTP packet. Max supported packet size is %d B."
   SCP_INIT_ERROR, "Cannot execute SCP to start transfer. Please make sure that SCP is installed on the server and path to it is included in PATH. You may also try SFTP instead of SCP."
   DUPLICATE_BOOKMARK, "Location Profile with name '%s' already exists."
+  MOVE_FILE_ERROR, "Error moving file '%s' to '%s'."
+  SFTP_PACKET_TOO_BIG_INIT_EXPLAIN, "%s\n \nThe error is typically caused by message printed from startup script (like .profile). The message may start with \"%s\"."
+  PRESERVE_TIME_ERROR, "Upload of file '%s' was successful, but error occurred while setting the timestamp. If the problem persists, turn off 'Preserve timestamp' option."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT, "Host hasn't answered for %d seconds.\n\nWait for another %0:d seconds? Pressing 'Abort' button will close session."
@@ -136,7 +139,7 @@ BEGIN
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"
-  PUTTY_VERSION, "(2003-12-22)"
-  PUTTY_COPYRIGHT, "Copyright © 1997-2003 Simon Tatham"
+  PUTTY_VERSION, "0.54"
+  PUTTY_COPYRIGHT, "Copyright © 1997-2004 Simon Tatham"
   PUTTY_URL, "http://www.chiark.greenend.org.uk/~sgtatham/putty/"
 END

+ 14 - 1
resource/TextsWin.h

@@ -21,6 +21,7 @@
 #define LICENCE_18      1018
 #define LICENCE_PUTTY   1021
 #define LICENCE_PUTTY_2 1022
+#define DND_DOWNLOAD_MOVE_WARNING 1050
 
 #define WIN_ERROR_STRINGS       1100
 #define MASK_ERROR              1101
@@ -48,6 +49,11 @@
 #define EXECUTE_APP_ERROR       1127
 #define FILE_NOT_FOUND          1128
 #define ABSOLUTE_PATH_REQUIRED  1129
+#define UNKNOWN_PROTOCOL        1130
+#define REGISTER_URL_ERROR      1131
+#define MUTEX_RELEASE_TIMEOUT   1132
+#define DRAGEXT_MUTEX_RELEASE_TIMEOUT 1133
+#define DRAGEXT_TARGET_UNKNOWN  1134
 
 #define WIN_CONFIRMATION_STRINGS 1300
 #define CONFIRM_OVERWRITE_SESSION 1301
@@ -73,6 +79,7 @@
 #define PREV_BUTTON             1321
 #define NEXT_BUTTON             1322
 #define APPEND_BUTTON           1323
+#define CONFIRM_REGISTER_URL    1324
 
 #define WIN_INFORMATION_STRINGS 1400
 #define APP_CAPTION             1401
@@ -102,6 +109,7 @@
 #define STATUS_STARTUP          1457
 #define STATUS_OPEN_DIRECTORY   1458
 #define STATUS_READY            1459
+#define PROTOCOL_URL_DESC       1460
 
 #define WIN_FORMS_STRINGS       1500
 #define LOG_NOLOG               1501
@@ -208,6 +216,12 @@
 #define CLEANUP_HOSTKEYS        1602
 #define CLEANUP_INIFILE         1603
 #define CLEANUP_SEEDFILE        1604
+#define SELECT_LOCAL_DIRECTORY  1605
+#define PROGRESS_REMOTE_MOVE    1606
+#define REMOTE_MOVE_FILE        1607
+#define REMOTE_MOVE_FILES       1608
+#define REMOTE_MOVE_TITLE       1609
+
 #define WIN_VARIABLE_STRINGS    1700
 #define WINSCP_COPYRIGHT        1701
 #define HOMEPAGE_URL            1702
@@ -217,7 +231,6 @@
 #define UPDATES_URL             1706
 #define DOWNLOAD_URL            1707
 #define DONATE_URL              1708
-#define SELECT_LOCAL_DIRECTORY  1709
 
 #define TRANSLATOR_INFO         1801
 #define MIDDLE_EAST             1802

+ 12 - 0
resource/TextsWin1.rc

@@ -36,6 +36,12 @@ BEGIN
         EXECUTE_APP_ERROR, "Cannot execute '%s'."
         FILE_NOT_FOUND, "File '%s' not found."
         ABSOLUTE_PATH_REQUIRED, "Only full (absolute) path may be used."
+        UNKNOWN_PROTOCOL, "Unknown or undefined protocol (%s)"
+        MUTEX_RELEASE_TIMEOUT, "Mutex was not released in required interval."
+        DRAGEXT_MUTEX_RELEASE_TIMEOUT, "Shell drag extension mutex was not released in required interval."
+        DRAGEXT_TARGET_UNKNOWN, "WinSCP was not able to detect folder, where the dragged file(s) was dropped. Either you have not dropped the file(s) to regular folder (e.g. Windows Explorer) or WinSCP shell drag extension is not installed. Install the extension or switch to compatible drag&&drop mode (from Preferences window), which uses temporary folder for downloads. It allows you to drop files to any destination."
+
+        REGISTER_URL_ERROR, "Cannot register application to handle scp:// and sftp:// addresses."
 
         WIN_CONFIRMATION_STRINGS, "WIN_CONFIRMATION"
         CONFIRM_OVERWRITE_SESSION, "Session with name '%s' already exists. Overwrite?"
@@ -58,6 +64,7 @@ BEGIN
         PREV_BUTTON, "&Previous"
         NEXT_BUTTON, "&Next"
         APPEND_BUTTON, "A&ppend"
+        CONFIRM_REGISTER_URL, "Do you want to register application to handle scp:// and sftp:// addresses?"
 
         WIN_INFORMATION_STRINGS, "WIN_INFORMATION"
         APP_CAPTION, "%s - %s"
@@ -79,6 +86,7 @@ BEGIN
         NEW_VERSION, "New version %s was released. Do you want to open application download page?\n\nHint: Use Help menu to open application history page to see list of new features. "
         CUSTOM_COMMANDS_PARAM_TITLE, "'%s' command parameter"
         CUSTOM_COMMANDS_PARAM_PROMPT, "'%s' command &parameter value:"
+        PROTOCOL_URL_DESC, "URL: %s Protocol"
 
         WIN_STATUS_STRINGS, "WIN_STATUS_STRINGS"
         STATUS_CLOSED, "Connection terminated."
@@ -198,6 +206,10 @@ BEGIN
         CLEANUP_INIFILE, "Configuration INI file"
         CLEANUP_SEEDFILE, "Random seed file"
         SELECT_LOCAL_DIRECTORY, "Select local directory."
+        PROGRESS_REMOTE_MOVE, "Moving"
+        REMOTE_MOVE_FILE, "Move file '%s' to remote directory:"
+        REMOTE_MOVE_FILES, "Move %d files to remote directory:"
+        REMOTE_MOVE_TITLE, "Move"
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000-2004 Martin Prikryl"

+ 11 - 0
resource/TextsWin2.rc

@@ -2,6 +2,17 @@
 
 STRINGTABLE
 BEGIN
+  DND_DOWNLOAD_MOVE_WARNING,
+"You are trying to move remote file(s) to destination to which WinSCP cannot "
+"transfer files directly. Files will be downloaded to temporary directory "
+"instead. Transfer to final destination will be left to responsibility "
+"of target application (e.g. Windows Explorer), which WinSCP cannot control. "
+"Source files will be deleted just after download to temporary directory "
+"finishes. If target application fails to deliver temporary files, they may "
+"be lost. Please, consider copying files instead of moving.\n"
+"Hint: To copy files hold down Ctrl key while dragging.\n"
+"\n"
+"Do you still want to move the files?"
   LICENCE,
 "WinSCP\n"
 "                    GNU GENERAL PUBLIC LICENSE\n"

+ 3 - 0
windows/GUIConfiguration.cpp

@@ -6,6 +6,7 @@
 #include <Common.h>
 #include <FileInfo.h>
 #include <TextsCore.h>
+#include <Terminal.h>
 //---------------------------------------------------------------------------
 #pragma package(smart_init)
 //---------------------------------------------------------------------------
@@ -39,6 +40,7 @@ void __fastcall TGUIConfiguration::Default()
   FCopyParamDialogExpanded = false;
   FErrorDialogExpanded = false;
   FContinueOnError = false;
+  FSynchronizeParams = TTerminal::spDelete | TTerminal::spNoConfirmation; 
   AnsiString ProgramsFolder;
   SpecialFolderLocation(CSIDL_PROGRAM_FILES, ProgramsFolder);
   FPuttyPath = IncludeTrailingBackslash(ProgramsFolder) + "PuTTY\\putty.exe";
@@ -55,6 +57,7 @@ void __fastcall TGUIConfiguration::Default()
     KEY(Bool,     CopyParamDialogExpanded); \
     KEY(Bool,     ErrorDialogExpanded); \
     KEY(Bool,     ContinueOnError); \
+    KEY(Integer,  SynchronizeParams); \
     KEY(String,   PuttySession); \
     KEY(String,   PuttyPath); \
   );

+ 3 - 1
windows/GUIConfiguration.h

@@ -18,6 +18,7 @@ private:
   bool FContinueOnError;
   AnsiString FPuttyPath;
   AnsiString FPuttySession;
+  int FSynchronizeParams;
 
 protected:
   LCID FLocale;
@@ -40,7 +41,8 @@ public:
 
   __property bool CopyParamDialogExpanded = { read = FCopyParamDialogExpanded, write = FCopyParamDialogExpanded };
   __property bool ErrorDialogExpanded = { read = FErrorDialogExpanded, write = FErrorDialogExpanded };
-  __property bool ContinueOnError = { read = FContinueOnError, write = FContinueOnError};
+  __property bool ContinueOnError = { read = FContinueOnError, write = FContinueOnError };
+  __property int SynchronizeParams = { read = FSynchronizeParams, write = FSynchronizeParams };
   __property LCID Locale = { read = GetLocale, write = SetLocale };
   __property LCID LocaleSafe = { read = GetLocale, write = SetLocaleSafe };
   __property TStrings * Locales = { read = GetLocales };

Some files were not shown because too many files changed in this diff