Просмотр исходного кода

Bug 1542: Import host keys from OpenSSH known_hosts file + Cached host keys are unconditionally imported with PuTTY and FileZilla sites.

https://winscp.net/tracker/1542

Source commit: a3240b3f530d98d00af3d1d7405b840811df6104
Martin Prikryl 8 лет назад
Родитель
Сommit
d152316f14

+ 48 - 0
source/core/Configuration.cpp

@@ -1326,6 +1326,54 @@ bool __fastcall TConfiguration::AnyFilezillaSessionForImport(TStoredSessionList
     return false;
   }
 }
+//---------------------------------------------------------------------
+TStoredSessionList * __fastcall TConfiguration::SelectKnownHostsSessionsForImport(
+  TStoredSessionList * Sessions, UnicodeString & Error)
+{
+  std::unique_ptr<TStoredSessionList> ImportSessionList(new TStoredSessionList(true));
+  ImportSessionList->DefaultSettings = Sessions->DefaultSettings;
+
+  UnicodeString ProfilePath = GetShellFolderPath(CSIDL_PROFILE);
+  UnicodeString KnownHostsFile = IncludeTrailingBackslash(ProfilePath) + L".ssh\\known_hosts";
+
+  try
+  {
+    if (FileExists(ApiPath(KnownHostsFile)))
+    {
+      std::unique_ptr<TStrings> Lines(new TStringList());
+      LoadScriptFromFile(KnownHostsFile, Lines.get());
+      ImportSessionList->ImportFromKnownHosts(Lines.get());
+    }
+    else
+    {
+      throw Exception(LoadStr(KNOWN_HOSTS_NOT_FOUND));
+    }
+  }
+  catch (Exception & E)
+  {
+    Error = FORMAT(L"%s\n(%s)", (E.Message, KnownHostsFile));
+  }
+
+  return ImportSessionList.release();
+}
+//---------------------------------------------------------------------
+TStoredSessionList * __fastcall TConfiguration::SelectKnownHostsSessionsForImport(
+  TStrings * Lines, TStoredSessionList * Sessions, UnicodeString & Error)
+{
+  std::unique_ptr<TStoredSessionList> ImportSessionList(new TStoredSessionList(true));
+  ImportSessionList->DefaultSettings = Sessions->DefaultSettings;
+
+  try
+  {
+    ImportSessionList->ImportFromKnownHosts(Lines);
+  }
+  catch (Exception & E)
+  {
+    Error = E.Message;
+  }
+
+  return ImportSessionList.release();
+}
 //---------------------------------------------------------------------------
 void __fastcall TConfiguration::SetRandomSeedFile(UnicodeString value)
 {

+ 4 - 0
source/core/Configuration.h

@@ -238,6 +238,10 @@ public:
   TStoredSessionList * __fastcall SelectFilezillaSessionsForImport(
     TStoredSessionList * Sessions, UnicodeString & Error);
   bool __fastcall AnyFilezillaSessionForImport(TStoredSessionList * Sessions);
+  TStoredSessionList * __fastcall SelectKnownHostsSessionsForImport(
+    TStoredSessionList * Sessions, UnicodeString & Error);
+  TStoredSessionList * __fastcall SelectKnownHostsSessionsForImport(
+    TStrings * Lines, TStoredSessionList * Sessions, UnicodeString & Error);
 
   __property TVSFixedFileInfo *FixedApplicationInfo  = { read=GetFixedApplicationInfo };
   __property void * ApplicationInfo  = { read=GetApplicationInfo };

+ 197 - 7
source/core/SessionData.cpp

@@ -3995,6 +3995,155 @@ void __fastcall TStoredSessionList::ImportFromFilezilla(
   }
 }
 //---------------------------------------------------------------------
+void __fastcall TStoredSessionList::ImportFromKnownHosts(TStrings * Lines)
+{
+  bool SessionList = false;
+  std::unique_ptr<THierarchicalStorage> HostKeyStorage(Configuration->CreateScpStorage(SessionList));
+  std::unique_ptr<TStrings> KeyList(new TStringList());
+  if (OpenHostKeysSubKey(HostKeyStorage.get(), false))
+  {
+    HostKeyStorage->GetValueNames(KeyList.get());
+  }
+  HostKeyStorage.reset(NULL);
+
+  UnicodeString FirstError;
+  for (int Index = 0; Index < Lines->Count; Index++)
+  {
+    try
+    {
+      UnicodeString Line = Lines->Strings[Index];
+      Line = Trim(Line);
+      if (!Line.IsEmpty() && (Line[1] != L';'))
+      {
+        int P = Pos(L' ', Line);
+        if (P > 0)
+        {
+          UnicodeString HostNameStr = Line.SubString(1, P - 1);
+          Line = Line.SubString(P + 1, Line.Length() - P);
+
+          UTF8String UtfLine = UTF8String(Line);
+          char * AlgorithmName = NULL;
+          int PubBlobLen = 0;
+          char * CommentPtr = NULL;
+          const char * ErrorStr = NULL;
+          unsigned char * PubBlob = openssh_loadpub_line(UtfLine.c_str(), &AlgorithmName, &PubBlobLen, &CommentPtr, &ErrorStr);
+          if (PubBlob == NULL)
+          {
+            throw Exception(UnicodeString(ErrorStr));
+          }
+          else
+          {
+            try
+            {
+              P = Pos(L',', HostNameStr);
+              if (P > 0)
+              {
+                HostNameStr.SetLength(P - 1);
+              }
+              P = Pos(L':', HostNameStr);
+              int PortNumber = -1;
+              if (P > 0)
+              {
+                UnicodeString PortNumberStr = HostNameStr.SubString(P + 1, HostNameStr.Length() - P);
+                PortNumber = StrToInt(PortNumberStr);
+                HostNameStr.SetLength(P - 1);
+              }
+              if ((HostNameStr.Length() >= 2) &&
+                  (HostNameStr[1] == L'[') && (HostNameStr[HostNameStr.Length()] == L']'))
+              {
+                HostNameStr = HostNameStr.SubString(2, HostNameStr.Length() - 2);
+              }
+
+              UnicodeString NameStr = HostNameStr;
+              if (PortNumber >= 0)
+              {
+                NameStr = FORMAT(L"%s:%d", (NameStr, PortNumber));
+              }
+
+              std::unique_ptr<TSessionData> SessionDataOwner;
+              TSessionData * SessionData = dynamic_cast<TSessionData *>(FindByName(NameStr));
+              if (SessionData == NULL)
+              {
+                SessionData = new TSessionData(L"");
+                SessionDataOwner.reset(SessionData);
+                SessionData->CopyData(DefaultSettings);
+                SessionData->Name = NameStr;
+                SessionData->HostName = HostNameStr;
+                if (PortNumber >= 0)
+                {
+                  SessionData->PortNumber = PortNumber;
+                }
+              }
+
+              const struct ssh_signkey * Algorithm = find_pubkey_alg(AlgorithmName);
+              if (Algorithm == NULL)
+              {
+                throw Exception(FORMAT(L"Unknown public key algorithm \"%s\".", (AlgorithmName)));
+              }
+
+              void * Key = Algorithm->newkey(Algorithm, reinterpret_cast<const char*>(PubBlob), PubBlobLen);
+              try
+              {
+                if (Key == NULL)
+                {
+                  throw Exception("Invalid public key.");
+                }
+                char * Fingerprint = Algorithm->fmtkey(Key);
+                UnicodeString KeyKey =
+                  FORMAT(L"%s@%d:%s", (Algorithm->keytype, SessionData->PortNumber, HostNameStr));
+                UnicodeString HostKey =
+                  FORMAT(L"%s:%s=%s", (Algorithm->name, KeyKey, Fingerprint));
+                sfree(Fingerprint);
+                UnicodeString HostKeyList = SessionData->HostKey;
+                AddToList(HostKeyList, HostKey, L";");
+                SessionData->HostKey = HostKeyList;
+                // If there's at least one unknown key type for this host, select it
+                if (KeyList->IndexOf(KeyKey) < 0)
+                {
+                  SessionData->Selected = true;
+                }
+              }
+              __finally
+              {
+                Algorithm->freekey(Key);
+              }
+
+              if (SessionDataOwner.get() != NULL)
+              {
+                Add(SessionDataOwner.release());
+              }
+            }
+            __finally
+            {
+              sfree(PubBlob);
+              sfree(AlgorithmName);
+              sfree(CommentPtr);
+            }
+          }
+        }
+      }
+    }
+    catch (Exception & E)
+    {
+      if (FirstError.IsEmpty())
+      {
+        FirstError = E.Message;
+      }
+    }
+  }
+
+  if (Count == 0)
+  {
+    UnicodeString Message = LoadStr(KNOWN_HOSTS_NO_SITES);
+    if (!FirstError.IsEmpty())
+    {
+      Message = FORMAT(L"%s\n(%s)", (Message, FirstError));
+    }
+
+    throw Exception(Message);
+  }
+}
+//---------------------------------------------------------------------
 void __fastcall TStoredSessionList::Export(const UnicodeString FileName)
 {
   THierarchicalStorage * Storage = TIniFileStorage::CreateFromPath(FileName);
@@ -4263,6 +4412,26 @@ void __fastcall TStoredSessionList::SetDefaultSettings(TSessionData * value)
   }
 }
 //---------------------------------------------------------------------------
+bool __fastcall TStoredSessionList::OpenHostKeysSubKey(THierarchicalStorage * Storage, bool CanCreate)
+{
+  return
+    Storage->OpenRootKey(CanCreate) &&
+    Storage->OpenSubKey(Configuration->SshHostKeysSubKey, CanCreate);
+}
+//---------------------------------------------------------------------------
+THierarchicalStorage * __fastcall TStoredSessionList::CreateHostKeysStorageForWritting()
+{
+  bool SessionList = false;
+  std::unique_ptr<THierarchicalStorage> Storage(Configuration->CreateScpStorage(SessionList));
+  Storage->Explicit = true;
+  Storage->AccessMode = smReadWrite;
+  if (!OpenHostKeysSubKey(Storage.get(), true))
+  {
+    Storage.reset(NULL);
+  }
+  return Storage.release();
+}
+//---------------------------------------------------------------------------
 void __fastcall TStoredSessionList::ImportHostKeys(
   const UnicodeString SourceKey, TStoredSessionList * Sessions,
   bool OnlySelected)
@@ -4272,16 +4441,12 @@ void __fastcall TStoredSessionList::ImportHostKeys(
   TStringList * KeyList = NULL;
   try
   {
-    bool SessionList = false;
-    TargetStorage = Configuration->CreateScpStorage(SessionList);
+    TargetStorage = CreateHostKeysStorageForWritting();
     SourceStorage = new TRegistryStorage(SourceKey);
-    TargetStorage->Explicit = true;
-    TargetStorage->AccessMode = smReadWrite;
     KeyList = new TStringList();
 
-    if (SourceStorage->OpenRootKey(false) &&
-        TargetStorage->OpenRootKey(true) &&
-        TargetStorage->OpenSubKey(Configuration->SshHostKeysSubKey, true))
+    if ((TargetStorage != NULL) &&
+        SourceStorage->OpenRootKey(false))
     {
       SourceStorage->GetValueNames(KeyList);
 
@@ -4317,6 +4482,31 @@ void __fastcall TStoredSessionList::ImportHostKeys(
   }
 }
 //---------------------------------------------------------------------------
+void __fastcall TStoredSessionList::ImportSelectedKnownHosts(TStoredSessionList * Sessions)
+{
+  std::unique_ptr<THierarchicalStorage> Storage(CreateHostKeysStorageForWritting());
+  if (Storage.get() != NULL)
+  {
+    for (int Index = 0; Index < Sessions->Count; Index++)
+    {
+      TSessionData * Session = Sessions->Sessions[Index];
+      if (Session->Selected)
+      {
+        UnicodeString Algs;
+        UnicodeString HostKeys = Session->HostKey;
+        while (!HostKeys.IsEmpty())
+        {
+          UnicodeString HostKey = CutToChar(HostKeys, L';', true);
+          // skip alg
+          CutToChar(HostKey, L':', true);
+          UnicodeString Key = CutToChar(HostKey, L'=', true);
+          Storage->WriteStringRaw(Key, HostKey);
+        }
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------
 bool __fastcall TStoredSessionList::IsFolderOrWorkspace(
   const UnicodeString & Name, bool Workspace)
 {

+ 4 - 0
source/core/SessionData.h

@@ -623,6 +623,7 @@ public:
   void __fastcall Save(bool All, bool Explicit);
   void __fastcall Saved();
   void __fastcall ImportFromFilezilla(const UnicodeString FileName, const UnicodeString ConfigurationFileName);
+  void __fastcall ImportFromKnownHosts(TStrings * Lines);
   void __fastcall Export(const UnicodeString FileName);
   void __fastcall Load(THierarchicalStorage * Storage, bool AsModified = false,
     bool UseDefaults = false, bool PuttyImport = false);
@@ -657,6 +658,7 @@ public:
   static void __fastcall ImportHostKeys(
     const UnicodeString SourceKey, TStoredSessionList * Sessions,
     bool OnlySelected);
+  static void __fastcall ImportSelectedKnownHosts(TStoredSessionList * Sessions);
 
 private:
   TSessionData * FDefaultSettings;
@@ -674,6 +676,8 @@ private:
   TSessionData * __fastcall CheckIsInFolderOrWorkspaceAndResolve(
     TSessionData * Data, const UnicodeString & Name);
   void __fastcall ImportLevelFromFilezilla(_di_IXMLNode Node, const UnicodeString & Path, _di_IXMLNode SettingsNode);
+  static THierarchicalStorage * __fastcall CreateHostKeysStorageForWritting();
+  static bool __fastcall OpenHostKeysSubKey(THierarchicalStorage * Storage, bool CanCreate);
 };
 //---------------------------------------------------------------------------
 UnicodeString GetExpandedLogFileName(UnicodeString LogFileName, TDateTime Started, TSessionData * SessionData);

+ 107 - 32
source/forms/ImportSessions.cpp

@@ -13,12 +13,16 @@
 #include <WinInterface.h>
 #include <TextsWin.h>
 #include <CoreMain.h>
+#include <Tools.h>
+#include <WinApi.h>
 #include <PasTools.hpp>
 //---------------------------------------------------------------------
 #ifndef NO_RESOURCES
 #pragma resource "*.dfm"
 #endif
 //---------------------------------------------------------------------
+const int KnownHostsIndex = 2;
+//---------------------------------------------------------------------
 bool __fastcall DoImportSessionsDialog(TList * Imported)
 {
   std::unique_ptr<TStrings> Errors(new TStringList());
@@ -29,34 +33,40 @@ bool __fastcall DoImportSessionsDialog(TList * Imported)
   std::unique_ptr<TStoredSessionList> FilezillaImportSessionList(
     Configuration->SelectFilezillaSessionsForImport(StoredSessions, Error));
   Errors->Add(Error);
+  std::unique_ptr<TStoredSessionList> KnownHostsImportSessionList(
+    Configuration->SelectKnownHostsSessionsForImport(StoredSessions, Error));
+  Errors->Add(Error);
 
   std::unique_ptr<TList> SessionListsList(new TList());
   SessionListsList->Add(PuttyImportSessionList.get());
   SessionListsList->Add(FilezillaImportSessionList.get());
-
-  bool ImportKeys = true;
+  SessionListsList->Add(KnownHostsImportSessionList.get());
 
   std::unique_ptr<TImportSessionsDialog> ImportSessionsDialog(
     SafeFormCreate<TImportSessionsDialog>(Application));
 
   ImportSessionsDialog->Init(SessionListsList.get(), Errors.get());
 
-  bool Result = ImportSessionsDialog->Execute(ImportKeys);
+  bool Result = ImportSessionsDialog->Execute();
 
   if (Result)
   {
+    // Particularly when importing known_hosts, there is no feedback.
+    TInstantOperationVisualizer Visualizer;
+
     StoredSessions->Import(PuttyImportSessionList.get(), true, Imported);
     StoredSessions->Import(FilezillaImportSessionList.get(), true, Imported);
 
-    if (ImportKeys)
-    {
-      UnicodeString SourceKey = Configuration->PuttyRegistryStorageKey + L"\\" + Configuration->SshHostKeysSubKey;
+    UnicodeString SourceKey = Configuration->PuttyRegistryStorageKey + L"\\" + Configuration->SshHostKeysSubKey;
 
-      TStoredSessionList::ImportHostKeys(SourceKey, PuttyImportSessionList.get(), true);
+    TStoredSessionList::ImportHostKeys(SourceKey, PuttyImportSessionList.get(), true);
 
-      // Filezilla uses PuTTY's host key store
-      TStoredSessionList::ImportHostKeys(SourceKey, FilezillaImportSessionList.get(), true);
-    }
+    // Filezilla uses PuTTY's host key store
+    TStoredSessionList::ImportHostKeys(SourceKey, FilezillaImportSessionList.get(), true);
+
+    TStoredSessionList * AKnownHostsImportSessionList =
+      static_cast<TStoredSessionList *>(SessionListsList->Items[KnownHostsIndex]);
+    TStoredSessionList::ImportSelectedKnownHosts(AKnownHostsImportSessionList);
   }
   return Result;
 }
@@ -71,11 +81,11 @@ __fastcall TImportSessionsDialog::TImportSessionsDialog(TComponent * AOwner) :
 //---------------------------------------------------------------------
 void __fastcall TImportSessionsDialog::Init(TList * SessionListsList, TStrings * Errors)
 {
+  FSessionListsList = SessionListsList;
   FErrors = Errors;
 
   for (int Index = 0; Index < SessionListsList->Count; Index++)
   {
-    SourceComboBox->Items->Objects[Index] = static_cast<TObject *>(SessionListsList->Items[Index]);
     if ((SourceComboBox->ItemIndex < 0) && (GetSessionList(Index)->Count > 0))
     {
       SourceComboBox->ItemIndex = Index;
@@ -97,27 +107,14 @@ void __fastcall TImportSessionsDialog::Init(TList * SessionListsList, TStrings *
 //---------------------------------------------------------------------
 TStoredSessionList * __fastcall TImportSessionsDialog::GetSessionList(int Index)
 {
-  return dynamic_cast<TStoredSessionList *>(SourceComboBox->Items->Objects[Index]);
+  return reinterpret_cast<TStoredSessionList *>(FSessionListsList->Items[Index]);
 }
 //---------------------------------------------------------------------
 void __fastcall TImportSessionsDialog::UpdateControls()
 {
+  PasteButton->Visible = (SourceComboBox->ItemIndex == KnownHostsIndex);
+  EnableControl(PasteButton, IsFormatInClipboard(CF_TEXT));
   EnableControl(OKButton, ListViewAnyChecked(SessionListView2));
-
-  bool AnySshChecked = false;
-  for (int Index = 0; Index < SessionListView2->Items->Count; Index++)
-  {
-    TListItem * Item = SessionListView2->Items->Item[Index];
-    TSessionData * Data = (TSessionData*)Item->Data;
-    if (Item->Checked && Data->UsesSsh)
-    {
-      AnySshChecked = true;
-      break;
-    }
-  }
-
-  EnableControl(ImportKeysCheck, AnySshChecked);
-
   EnableControl(CheckAllButton, SessionListView2->Items->Count > 0);
   AutoSizeListColumnsWidth(SessionListView2);
 }
@@ -182,7 +179,24 @@ void __fastcall TImportSessionsDialog::LoadSessions()
 void __fastcall TImportSessionsDialog::SessionListView2InfoTip(
       TObject * /*Sender*/, TListItem * Item, UnicodeString & InfoTip)
 {
-  InfoTip = ((TSessionData*)Item->Data)->InfoTip;
+  TSessionData * Data = DebugNotNull(reinterpret_cast<TSessionData *>(Item->Data));
+  if (SourceComboBox->ItemIndex == KnownHostsIndex)
+  {
+    UnicodeString Algs;
+    UnicodeString HostKeys = Data->HostKey;
+    while (!HostKeys.IsEmpty())
+    {
+      UnicodeString HostKey = CutToChar(HostKeys, L';', true);
+      UnicodeString Alg = CutToChar(HostKey, L':', true);
+      AddToList(Algs, Alg, L", ");
+    }
+
+    InfoTip = FMTLOAD(IMPORT_KNOWNHOSTS_INFO_TIP, (Data->HostName, Algs));
+  }
+  else
+  {
+    InfoTip = Data->InfoTip;
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TImportSessionsDialog::SessionListView2MouseDown(
@@ -215,17 +229,14 @@ void __fastcall TImportSessionsDialog::HelpButtonClick(TObject * /*Sender*/)
   FormHelp(this);
 }
 //---------------------------------------------------------------------------
-bool __fastcall TImportSessionsDialog::Execute(bool & ImportKeys)
+bool __fastcall TImportSessionsDialog::Execute()
 {
-  ImportKeysCheck->Checked = ImportKeys;
-
   bool Result = (ShowModal() == DefaultResult(this));
 
   if (Result)
   {
     ClearSelections();
     SaveSelection();
-    ImportKeys = ImportKeysCheck->Enabled && ImportKeysCheck->Checked;
   }
 
   return Result;
@@ -237,3 +248,67 @@ void __fastcall TImportSessionsDialog::SourceComboBoxSelect(TObject * /*Sender*/
   LoadSessions();
 }
 //---------------------------------------------------------------------------
+void __fastcall TImportSessionsDialog::CreateHandle()
+{
+  TForm::CreateHandle();
+
+  if (DebugAlwaysTrue(HandleAllocated()))
+  {
+    HINSTANCE User32Library = LoadLibrary(L"user32.dll");
+    AddClipboardFormatListenerProc AddClipboardFormatListener =
+      (AddClipboardFormatListenerProc)GetProcAddress(User32Library, "AddClipboardFormatListener");
+    if (AddClipboardFormatListener != NULL)
+    {
+      AddClipboardFormatListener(Handle);
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TImportSessionsDialog::DestroyHandle()
+{
+  if (DebugAlwaysTrue(HandleAllocated()))
+  {
+    HINSTANCE User32Library = LoadLibrary(L"user32.dll");
+    RemoveClipboardFormatListenerProc RemoveClipboardFormatListener =
+      (RemoveClipboardFormatListenerProc)GetProcAddress(User32Library, "RemoveClipboardFormatListener");
+    if (RemoveClipboardFormatListener != NULL)
+    {
+      RemoveClipboardFormatListener(Handle);
+    }
+  }
+
+  TForm::DestroyHandle();
+}
+//---------------------------------------------------------------------------
+void __fastcall TImportSessionsDialog::Dispatch(void * Message)
+{
+  TMessage * M = static_cast<TMessage*>(Message);
+  switch (M->Msg)
+  {
+    case WM_CLIPBOARDUPDATE:
+      UpdateControls();
+      TForm::Dispatch(Message);
+      break;
+
+    default:
+      TForm::Dispatch(Message);
+      break;
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TImportSessionsDialog::PasteButtonClick(TObject * /*Sender*/)
+{
+  UnicodeString Text;
+  // Proceed even when retriving from clipboard fails, "no host keys" error will show.
+  TextFromClipboard(Text, false);
+  std::unique_ptr<TStrings> Lines(new TStringList());
+  Lines->Text = Text;
+  SessionListView2->Items->Clear();
+  int Index = SourceComboBox->ItemIndex;
+  UnicodeString Error;
+  FPastedKnownHosts.reset(Configuration->SelectKnownHostsSessionsForImport(Lines.get(), StoredSessions, Error));
+  FSessionListsList->Items[Index] = FPastedKnownHosts.get();
+  FErrors->Strings[Index] = Error;
+  LoadSessions();
+}
+//---------------------------------------------------------------------------

+ 19 - 18
source/forms/ImportSessions.dfm

@@ -20,7 +20,7 @@ object ImportSessionsDialog: TImportSessionsDialog
   TextHeight = 13
   object Label: TLabel
     Left = 8
-    Top = 11
+    Top = 13
     Width = 61
     Height = 13
     Anchors = [akLeft, akTop, akRight]
@@ -50,9 +50,9 @@ object ImportSessionsDialog: TImportSessionsDialog
   end
   object SessionListView2: TListView
     Left = 8
-    Top = 35
+    Top = 39
     Width = 361
-    Height = 177
+    Height = 197
     Anchors = [akLeft, akTop, akRight, akBottom]
     Checkboxes = True
     Columns = <
@@ -67,7 +67,7 @@ object ImportSessionsDialog: TImportSessionsDialog
     ParentShowHint = False
     ShowColumnHeaders = False
     ShowHint = True
-    TabOrder = 1
+    TabOrder = 2
     ViewStyle = vsReport
     OnInfoTip = SessionListView2InfoTip
     OnKeyUp = SessionListView2KeyUp
@@ -83,15 +83,6 @@ object ImportSessionsDialog: TImportSessionsDialog
     TabOrder = 3
     OnClick = CheckAllButtonClick
   end
-  object ImportKeysCheck: TCheckBox
-    Left = 16
-    Top = 218
-    Width = 345
-    Height = 17
-    Anchors = [akLeft, akBottom]
-    Caption = 'Import cached host &keys for checked sites'
-    TabOrder = 2
-  end
   object HelpButton: TButton
     Left = 294
     Top = 242
@@ -104,7 +95,7 @@ object ImportSessionsDialog: TImportSessionsDialog
   end
   object SourceComboBox: TComboBox
     Left = 106
-    Top = 8
+    Top = 10
     Width = 120
     Height = 21
     Style = csDropDownList
@@ -112,11 +103,12 @@ object ImportSessionsDialog: TImportSessionsDialog
     OnSelect = SourceComboBoxSelect
     Items.Strings = (
       'PuTTY'
-      'FileZilla')
+      'FileZilla'
+      'known_hosts')
   end
   object ErrorPanel: TPanel
     Left = 48
-    Top = 80
+    Top = 92
     Width = 281
     Height = 97
     BevelOuter = bvNone
@@ -126,8 +118,8 @@ object ImportSessionsDialog: TImportSessionsDialog
     object ErrorLabel: TLabel
       Left = 0
       Top = 0
-      Width = 281
-      Height = 97
+      Width = 49
+      Height = 13
       Align = alClient
       Alignment = taCenter
       Caption = 'ErrorLabel'
@@ -135,4 +127,13 @@ object ImportSessionsDialog: TImportSessionsDialog
       WordWrap = True
     end
   end
+  object PasteButton: TButton
+    Left = 232
+    Top = 8
+    Width = 75
+    Height = 25
+    Caption = '&Paste'
+    TabOrder = 1
+    OnClick = PasteButtonClick
+  end
 end

+ 8 - 2
source/forms/ImportSessions.h

@@ -18,11 +18,11 @@ __published:
   TListView *SessionListView2;
   TLabel *Label;
   TButton *CheckAllButton;
-  TCheckBox *ImportKeysCheck;
   TButton *HelpButton;
   TComboBox *SourceComboBox;
   TPanel *ErrorPanel;
   TLabel *ErrorLabel;
+  TButton *PasteButton;
   void __fastcall SessionListView2InfoTip(TObject *Sender,
     TListItem *Item, UnicodeString &InfoTip);
   void __fastcall SessionListView2MouseDown(TObject *Sender,
@@ -33,19 +33,25 @@ __published:
   void __fastcall CheckAllButtonClick(TObject *Sender);
   void __fastcall HelpButtonClick(TObject *Sender);
   void __fastcall SourceComboBoxSelect(TObject *Sender);
+  void __fastcall PasteButtonClick(TObject *Sender);
 
 private:
+  TList * FSessionListsList;
   TStrings * FErrors;
+  std::unique_ptr<TStoredSessionList> FPastedKnownHosts;
   void __fastcall UpdateControls();
   void __fastcall LoadSessions();
   void __fastcall ClearSelections();
   void __fastcall SaveSelection();
   TStoredSessionList * __fastcall GetSessionList(int Index);
+  virtual void __fastcall CreateHandle();
+  virtual void __fastcall DestroyHandle();
+  virtual void __fastcall Dispatch(void * Message);
 
 public:
   virtual __fastcall TImportSessionsDialog(TComponent * AOwner);
   void __fastcall Init(TList * SessionListsList, TStrings * Errors);
-  bool __fastcall Execute(bool & ImportKeys);
+  bool __fastcall Execute();
 };
 //----------------------------------------------------------------------------
 #endif

+ 6 - 9
source/forms/Login.cpp

@@ -1078,16 +1078,13 @@ void __fastcall TLoginDialog::ReloadSessions(const UnicodeString & SelectSite)
 void __fastcall TLoginDialog::ImportSessionsActionExecute(TObject * /*Sender*/)
 {
   std::unique_ptr<TList> Imported(new TList());
-  if (DoImportSessionsDialog(Imported.get()))
+  if (DoImportSessionsDialog(Imported.get()) &&
+      // Can be empty when imported known_hosts
+      (Imported->Count > 0))
   {
-    UnicodeString SelectSite;
-    if (DebugAlwaysTrue(Imported->Count > 0))
-    {
-      // Focus the first imported session.
-      // We should also consider expanding all newly created folders
-      SelectSite = static_cast<TSessionData *>(Imported->Items[0])->Name;
-    }
-
+    // Focus the first imported session.
+    // We should also consider expanding all newly created folders
+    UnicodeString SelectSite = static_cast<TSessionData *>(Imported->Items[0])->Name;
     ReloadSessions(SelectSite);
 
     // Focus the tree with focused imported session(s).

+ 5 - 0
source/putty/ssh.h

@@ -784,6 +784,11 @@ char *ssh2_fingerprint_blob(const void *blob, int bloblen);
 char *ssh2_fingerprint(const struct ssh_signkey *alg, void *data);
 int key_type(const Filename *filename);
 const char *key_type_to_str(int type);
+#ifdef MPEXT
+unsigned char *openssh_loadpub_line(char * line, char **algorithm,
+                                    int *pub_blob_len, char **commentptr,
+                                    const char **errorstr);
+#endif
 
 int import_possible(int type);
 int import_target_type(int type);

+ 25 - 1
source/putty/sshpubk.c

@@ -1028,7 +1028,11 @@ unsigned char *rfc4716_loadpub(FILE *fp, char **algorithm,
     return NULL;
 }
 
-unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
+#ifdef MPEXT
+unsigned char *openssh_loadpub_line(char * aline, char **algorithm,
+#else
+unsigned char *openssh_loadpub_line(FILE *fp, char **algorithm,
+#endif
                                int *pub_blob_len, char **commentptr,
                                const char **errorstr)
 {
@@ -1039,7 +1043,11 @@ unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
     int pubbloblen, pubblobsize;
     int alglen;
 
+#ifndef MPEXT
     line = chomp(fgetline(fp));
+#else
+    line = aline;
+#endif
 
     base64 = strchr(line, ' ');
     if (!base64) {
@@ -1092,11 +1100,15 @@ unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
         *commentptr = comment;
     else
         sfree(comment);
+#ifndef MPEXT
     sfree(line);
+#endif
     return pubblob;
 
   error:
+#ifndef MPEXT
     sfree(line);
+#endif
     sfree(comment);
     sfree(pubblob);
     if (errorstr)
@@ -1104,6 +1116,18 @@ unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
     return NULL;
 }
 
+#ifdef MPEXT
+unsigned char *openssh_loadpub(FILE *fp, char **algorithm,
+                               int *pub_blob_len, char **commentptr,
+                               const char **errorstr)
+{
+    char * line = chomp(fgetline(fp));
+    unsigned char * pubblob = openssh_loadpub_line(line, algorithm, pub_blob_len, commentptr, errorstr);
+    sfree(line);
+    return pubblob;
+}
+#endif
+
 unsigned char *ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
 				    int *pub_blob_len, char **commentptr,
 				    const char **errorstr)

+ 2 - 0
source/resource/TextsCore.h

@@ -262,6 +262,8 @@
 #define SFTP_AS_FTP_ERROR       737
 #define LOG_FATAL_ERROR         738
 #define SIZE_INVALID            739
+#define KNOWN_HOSTS_NOT_FOUND   740
+#define KNOWN_HOSTS_NO_SITES    741
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301

+ 2 - 0
source/resource/TextsCore1.rc

@@ -232,6 +232,8 @@ BEGIN
   SFTP_AS_FTP_ERROR, "You cannot connect to an SFTP server using an FTP protocol. Please select the correct protocol."
   LOG_FATAL_ERROR, "Error occurred during logging. Cannot continue."
   SIZE_INVALID, "'%s' is not a valid size."
+  KNOWN_HOSTS_NOT_FOUND, "OpenSSH known_hosts file not found."
+  KNOWN_HOSTS_NO_SITES, "No host keys found in known_hosts."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"

+ 1 - 0
source/resource/TextsWin.h

@@ -593,6 +593,7 @@
 #define USAGE_CAPTION           1983
 #define USAGE_FILTER            1984
 #define USAGE_COPY              1985
+#define IMPORT_KNOWNHOSTS_INFO_TIP 1986
 
 // 2xxx is reserved for TextsFileZilla.h
 

+ 1 - 0
source/resource/TextsWin1.rc

@@ -596,6 +596,7 @@ BEGIN
         USAGE_CAPTION, "Usage Statistics"
         USAGE_FILTER, "&Filter:"
         USAGE_COPY, "&Copy to Clipboard"
+        IMPORT_KNOWNHOSTS_INFO_TIP, "Host: %s\nHost key: %s"
 
         WIN_VARIABLE_STRINGS, "WIN_VARIABLE"
         WINSCP_COPYRIGHT, "Copyright © 2000-2017 Martin Prikryl"

+ 2 - 0
source/windows/WinApi.h

@@ -8,6 +8,8 @@
 //---------------------------------------------------------------------------
 typedef BOOL WINAPI (* ChangeWindowMessageFilterExProc)(
   HWND hwnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
+typedef BOOL WINAPI (* AddClipboardFormatListenerProc)(HWND hwnd);
+typedef BOOL WINAPI (* RemoveClipboardFormatListenerProc)(HWND hwnd);
 //---------------------------------------------------------------------------
 #define WM_DPICHANGED 0x02E0
 //---------------------------------------------------------------------------