浏览代码

Bug 1563: Allow verifying SSH host key by pasting the expected key or its fingerprint from the clipboard

https://winscp.net/tracker/1563

Source commit: 3205dd4eb285e9cd5a7dbaaf9dc9f46dcd0e94dc
Martin Prikryl 8 年之前
父节点
当前提交
868ef381ee

+ 1 - 1
source/core/CoreMain.cpp

@@ -22,7 +22,7 @@ TStoredSessionList * StoredSessions = NULL;
 //---------------------------------------------------------------------------
 TQueryButtonAlias::TQueryButtonAlias()
 {
-  OnClick = NULL;
+  OnSubmit = NULL;
   GroupWith = -1;
   ElevationRequired = false;
   MenuButton = false;

+ 1 - 1
source/core/FtpFileSystem.cpp

@@ -4530,7 +4530,7 @@ bool __fastcall TFTPFileSystem::HandleAsynchRequestVerifyCertificate(
         TQueryButtonAlias Aliases[1];
         Aliases[0].Button = qaRetry;
         Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON);
-        Aliases[0].OnClick = &ClipboardHandler.Copy;
+        Aliases[0].OnSubmit = &ClipboardHandler.Copy;
 
         TQueryParams Params(qpWaitInBatch);
         Params.HelpKeyword = HELP_VERIFY_CERTIFICATE;

+ 5 - 2
source/core/Interface.h

@@ -51,6 +51,7 @@ void __fastcall CopyToClipboard(UnicodeString Text);
 int __fastcall StartThread(void * SecurityAttributes, unsigned StackSize,
   TThreadFunc ThreadFunc, void * Parameter, unsigned CreationFlags,
   TThreadID & ThreadId);
+bool __fastcall TextFromClipboard(UnicodeString & Text, bool Trim);
 
 // Order of the values also define order of the buttons/answers on the prompts
 // MessageDlg relies on these to be <= 0x0000FFFF
@@ -80,13 +81,15 @@ const int qpAllowContinueOnError = 0x04;
 const int qpIgnoreAbort =          0x08;
 const int qpWaitInBatch =          0x10;
 
+typedef void __fastcall (__closure *TButtonSubmitEvent)(TObject * Sender, unsigned int & Answer);
+
 struct TQueryButtonAlias
 {
   TQueryButtonAlias();
 
   unsigned int Button;
   UnicodeString Alias;
-  TNotifyEvent OnClick;
+  TButtonSubmitEvent OnSubmit;
   int GroupWith;
   TShiftState GrouppedShiftState;
   bool ElevationRequired;
@@ -167,7 +170,7 @@ struct TClipboardHandler
 {
   UnicodeString Text;
 
-  void __fastcall Copy(TObject * /*Sender*/)
+  void __fastcall Copy(TObject * /*Sender*/, unsigned int & /*Answer*/)
   {
     TInstantOperationVisualizer Visualizer;
     CopyToClipboard(Text);

+ 53 - 5
source/core/PuttyIntf.cpp

@@ -721,11 +721,16 @@ static void __fastcall DoNormalizeFingerprint(UnicodeString & Fingerprint, Unico
       int LenStart = Name.Length() + 1;
       Fingerprint[LenStart] = NormalizedSeparator;
       int Space = Fingerprint.Pos(L" ");
-      DebugAssert(IsNumber(Fingerprint.SubString(LenStart + 1, Space - LenStart - 1)));
-      Fingerprint.Delete(LenStart + 1, Space - LenStart);
-      Fingerprint = ReplaceChar(Fingerprint, L':', NormalizedSeparator);
-      KeyType = UnicodeString(SignKey->keytype);
-      return;
+      // If not a number, it's an invalid input,
+      // either something completelly wrong, or it can be OpenSSH base64 public key,
+      // that got here from TPasteKeyHandler::Paste
+      if (IsNumber(Fingerprint.SubString(LenStart + 1, Space - LenStart - 1)))
+      {
+        Fingerprint.Delete(LenStart + 1, Space - LenStart);
+        Fingerprint = ReplaceChar(Fingerprint, L':', NormalizedSeparator);
+        KeyType = UnicodeString(SignKey->keytype);
+        return;
+      }
     }
     else if (StartsStr(Name + NormalizedSeparator, Fingerprint))
     {
@@ -774,4 +779,47 @@ void __fastcall DllHijackingProtection()
   dll_hijacking_protection();
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const struct ssh_signkey *& Algorithm)
+{
+  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);
+  UnicodeString Result;
+  if (PubBlob == NULL)
+  {
+    throw Exception(UnicodeString(ErrorStr));
+  }
+  else
+  {
+    try
+    {
+      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);
+      if (Key == NULL)
+      {
+        throw Exception(L"Invalid public key.");
+      }
+      char * FmtKey = Algorithm->fmtkey(Key);
+      Result = UnicodeString(FmtKey);
+      sfree(FmtKey);
+      Algorithm->freekey(Key);
+    }
+    __finally
+    {
+      sfree(PubBlob);
+      sfree(AlgorithmName);
+      sfree(CommentPtr);
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 2 - 0
source/core/PuttyTools.h

@@ -32,4 +32,6 @@ UnicodeString __fastcall Sha256(const char * Data, size_t Size);
 //---------------------------------------------------------------------------
 void __fastcall DllHijackingProtection();
 //---------------------------------------------------------------------------
+UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const struct ssh_signkey *& Algorithm);
+//---------------------------------------------------------------------------
 #endif

+ 59 - 8
source/core/SecureShell.cpp

@@ -2167,6 +2167,49 @@ UnicodeString __fastcall TSecureShell::RetrieveHostKey(UnicodeString Host, int P
   return Result;
 }
 //---------------------------------------------------------------------------
+struct TPasteKeyHandler
+{
+  UnicodeString KeyStr;
+  UnicodeString NormalizedFingerprint;
+  TSessionUI * UI;
+
+  void __fastcall Paste(TObject * /*Sender*/, unsigned int & Answer)
+  {
+    UnicodeString ClipboardText;
+    if (TextFromClipboard(ClipboardText, true))
+    {
+      UnicodeString NormalizedClipboardFingerprint = NormalizeFingerprint(ClipboardText);
+      // case insensitive comparison, contrary to VerifyHostKey (we should change to insesitive there too)
+      if (SameText(NormalizedClipboardFingerprint, NormalizedFingerprint) ||
+          SameText(ClipboardText, KeyStr))
+      {
+        Answer = qaYes;
+      }
+      else
+      {
+        const struct ssh_signkey * Algorithm;
+        try
+        {
+          UnicodeString Key = ParseOpenSshPubLine(ClipboardText, Algorithm);
+          if (Key == KeyStr)
+          {
+            Answer = qaYes;
+          }
+        }
+        catch (...)
+        {
+          // swallow
+        }
+      }
+    }
+
+    if (Answer == 0)
+    {
+      UI->QueryUser(LoadStr(HOSTKEY_NOT_MATCH_CLIPBOARD), NULL, qaOK, NULL, qtError);
+    }
+  }
+};
+//---------------------------------------------------------------------------
 void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
   const UnicodeString KeyType, UnicodeString KeyStr, UnicodeString Fingerprint)
 {
@@ -2270,23 +2313,31 @@ void __fastcall TSecureShell::VerifyHostKey(UnicodeString Host, int Port,
       // it's a small issue.
       TClipboardHandler ClipboardHandler;
       ClipboardHandler.Text = Fingerprint;
+      TPasteKeyHandler PasteKeyHandler;
+      PasteKeyHandler.KeyStr = KeyStr;
+      PasteKeyHandler.NormalizedFingerprint = NormalizedFingerprint;
+      PasteKeyHandler.UI = FUI;
 
       bool Unknown = StoredKeys.IsEmpty();
 
       int Answers;
       int AliasesCount;
-      TQueryButtonAlias Aliases[3];
+      TQueryButtonAlias Aliases[4];
       Aliases[0].Button = qaRetry;
       Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON);
-      Aliases[0].OnClick = &ClipboardHandler.Copy;
-      Answers = qaYes | qaCancel | qaRetry;
-      AliasesCount = 1;
+      Aliases[0].OnSubmit = &ClipboardHandler.Copy;
+      Aliases[1].Button = qaIgnore;
+      Aliases[1].Alias = LoadStr(PASTE_KEY_BUTTON);
+      Aliases[1].OnSubmit = &PasteKeyHandler.Paste;
+      Aliases[1].GroupWith = qaYes;
+      Answers = qaYes | qaCancel | qaRetry | qaIgnore;
+      AliasesCount = 2;
       if (!Unknown)
       {
-        Aliases[1].Button = qaYes;
-        Aliases[1].Alias = LoadStr(UPDATE_KEY_BUTTON);
-        Aliases[2].Button = qaOK;
-        Aliases[2].Alias = LoadStr(ADD_KEY_BUTTON);
+        Aliases[2].Button = qaYes;
+        Aliases[2].Alias = LoadStr(UPDATE_KEY_BUTTON);
+        Aliases[3].Button = qaOK;
+        Aliases[3].Alias = LoadStr(ADD_KEY_BUTTON);
         AliasesCount += 2;
         Answers |= qaSkip | qaOK;
       }

+ 53 - 93
source/core/SessionData.cpp

@@ -4086,105 +4086,65 @@ void __fastcall TStoredSessionList::ImportFromKnownHosts(TStrings * Lines)
           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)
+          P = Pos(L',', HostNameStr);
+          if (P > 0)
           {
-            throw Exception(UnicodeString(ErrorStr));
+            HostNameStr.SetLength(P - 1);
           }
-          else
+          P = Pos(L':', HostNameStr);
+          int PortNumber = -1;
+          if (P > 0)
           {
-            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
+            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)
             {
-              sfree(PubBlob);
-              sfree(AlgorithmName);
-              sfree(CommentPtr);
+              SessionData->PortNumber = PortNumber;
             }
           }
+
+          const struct ssh_signkey * Algorithm;
+          UnicodeString Key = ParseOpenSshPubLine(Line, Algorithm);
+          UnicodeString KeyKey =
+            FORMAT(L"%s@%d:%s", (Algorithm->keytype, SessionData->PortNumber, HostNameStr));
+          UnicodeString HostKey =
+            FORMAT(L"%s:%s=%s", (Algorithm->name, KeyKey, Key));
+          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;
+          }
+
+          if (SessionDataOwner.get() != NULL)
+          {
+            Add(SessionDataOwner.release());
+          }
         }
       }
     }

+ 1 - 1
source/core/WebDAVFileSystem.cpp

@@ -2409,7 +2409,7 @@ bool TWebDAVFileSystem::VerifyCertificate(const TWebDAVCertificateData & Data, b
       TQueryButtonAlias Aliases[1];
       Aliases[0].Button = qaRetry;
       Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON);
-      Aliases[0].OnClick = &ClipboardHandler.Copy;
+      Aliases[0].OnSubmit = &ClipboardHandler.Copy;
 
       TQueryParams Params;
       Params.HelpKeyword = HELP_VERIFY_CERTIFICATE;

+ 1 - 1
source/forms/CustomScpExplorer.cpp

@@ -2113,7 +2113,7 @@ void __fastcall TCustomScpExplorerForm::LocalCustomCommand(TStrings * FileList,
       TQueryButtonAlias Aliases[1];
       Aliases[0].Button = qaRetry;
       Aliases[0].Alias = LoadStr(URL_LINK_COPY); // misuse
-      Aliases[0].OnClick = &ClipboardHandler.Copy;
+      Aliases[0].OnSubmit = &ClipboardHandler.Copy;
       Params.Aliases = Aliases;
       Params.AliasesCount = LENOF(Aliases);
 

+ 27 - 15
source/forms/MessageDlg.cpp

@@ -89,7 +89,7 @@ __fastcall TMessageForm::~TMessageForm()
   SAFE_DESTROY(FUpdateForShiftStateTimer);
 }
 //---------------------------------------------------------------------------
-void __fastcall TMessageForm::HelpButtonClick(TObject * /*Sender*/)
+void __fastcall TMessageForm::HelpButtonSubmit(TObject * /*Sender*/, unsigned int & /*Answer*/)
 {
   if (HelpKeyword != HELP_NONE)
   {
@@ -101,7 +101,7 @@ void __fastcall TMessageForm::HelpButtonClick(TObject * /*Sender*/)
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TMessageForm::ReportButtonClick(TObject * /*Sender*/)
+void __fastcall TMessageForm::ReportButtonSubmit(TObject * /*Sender*/, unsigned int & /*Answer*/)
 {
   // Report text goes last, as it may exceed URL parameters limit (2048) and get truncated.
   // And we need to preserve the other parameters.
@@ -415,6 +415,16 @@ void __fastcall TMessageForm::DoShow()
 
 }
 //---------------------------------------------------------------------------
+void __fastcall TMessageForm::ButtonSubmit(TObject * Sender)
+{
+  unsigned int Answer = 0;
+  FButtonSubmitEvents[Sender](Sender, Answer);
+  if (Answer != 0)
+  {
+    ModalResult = Answer;
+  }
+}
+//---------------------------------------------------------------------------
 void __fastcall TMessageForm::MenuItemClick(TObject * Sender)
 {
   TMenuItem * Item = DebugNotNull(dynamic_cast<TMenuItem *>(Sender));
@@ -476,7 +486,7 @@ static UnicodeString __fastcall GetKeyNameStr(int Key)
 //---------------------------------------------------------------------------
 TButton * __fastcall TMessageForm::CreateButton(
   UnicodeString Name, UnicodeString Caption, unsigned int Answer,
-  TNotifyEvent OnClick, bool IsTimeoutButton,
+  TButtonSubmitEvent OnSubmit, bool IsTimeoutButton,
   int GroupWith, TShiftState GrouppedShiftState, bool ElevationRequired, bool MenuButton,
   TAnswerButtons & AnswerButtons, bool HasMoreMessages, int & ButtonWidths)
 {
@@ -553,9 +563,10 @@ TButton * __fastcall TMessageForm::CreateButton(
     }
 
     Item->Caption = Caption;
-    if (OnClick != NULL)
+    if (OnSubmit != NULL)
     {
-      Item->OnClick = OnClick;
+      Item->OnClick = ButtonSubmit;
+      FButtonSubmitEvents[Item] = OnSubmit;
     }
     else
     {
@@ -592,9 +603,10 @@ TButton * __fastcall TMessageForm::CreateButton(
     Button->Height = ScaleByTextHeightRunTime(FDummyForm, Button->Height);
     Button->Width = ScaleByTextHeightRunTime(FDummyForm, Button->Width);
 
-    if (OnClick != NULL)
+    if (OnSubmit != NULL)
     {
-      Button->OnClick = OnClick;
+      Button->OnClick = ButtonSubmit;
+      FButtonSubmitEvents[Button] = OnSubmit;
     }
     else
     {
@@ -851,7 +863,7 @@ TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg,
 
       AnswerNameAndCaption(Answer, Name, Caption);
 
-      TNotifyEvent OnClick = NULL;
+      TButtonSubmitEvent OnSubmit = NULL;
       int GroupWith = -1;
       TShiftState GrouppedShiftState;
       bool ElevationRequired = false;
@@ -866,12 +878,12 @@ TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg,
             {
               Caption = Aliases[i].Alias;
             }
-            OnClick = Aliases[i].OnClick;
+            OnSubmit = Aliases[i].OnSubmit;
             GroupWith = Aliases[i].GroupWith;
             GrouppedShiftState = Aliases[i].GrouppedShiftState;
             ElevationRequired = Aliases[i].ElevationRequired;
             MenuButton = Aliases[i].MenuButton;
-            DebugAssert((OnClick == NULL) || (GrouppedShiftState == TShiftState()));
+            DebugAssert((OnSubmit == NULL) || (GrouppedShiftState == TShiftState()));
             break;
           }
         }
@@ -894,19 +906,19 @@ TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg,
 
       if (Answer == qaHelp)
       {
-        DebugAssert(OnClick == NULL);
-        OnClick = Result->HelpButtonClick;
+        DebugAssert(OnSubmit == NULL);
+        OnSubmit = Result->HelpButtonSubmit;
       }
 
       if (Answer == qaReport)
       {
-        DebugAssert(OnClick == NULL);
-        OnClick = Result->ReportButtonClick;
+        DebugAssert(OnSubmit == NULL);
+        OnSubmit = Result->ReportButtonSubmit;
       }
 
       TButton * Button = Result->CreateButton(
         Name, Caption, Answer,
-        OnClick, IsTimeoutButton, GroupWith, GrouppedShiftState, ElevationRequired, MenuButton,
+        OnSubmit, IsTimeoutButton, GroupWith, GrouppedShiftState, ElevationRequired, MenuButton,
         AnswerButtons, HasMoreMessages, ButtonWidths);
 
       if (Button != NULL)

+ 5 - 3
source/forms/MessageDlg.h

@@ -35,6 +35,7 @@ protected:
   DYNAMIC void __fastcall DoShow();
   virtual void __fastcall Dispatch(void * Message);
   void __fastcall MenuItemClick(TObject * Sender);
+  void __fastcall ButtonSubmit(TObject * Sender);
   void __fastcall ButtonDropDownClick(TObject * Sender);
   void __fastcall UpdateForShiftStateTimer(TObject * Sender);
   DYNAMIC void __fastcall SetZOrder(bool TopMost);
@@ -54,15 +55,16 @@ private:
   TTimer * FUpdateForShiftStateTimer;
   TForm * FDummyForm;
   bool FShowNoActivate;
+  std::map<TObject *, TButtonSubmitEvent> FButtonSubmitEvents;
 
-  void __fastcall HelpButtonClick(TObject * Sender);
-  void __fastcall ReportButtonClick(TObject * Sender);
+  void __fastcall HelpButtonSubmit(TObject * Sender, unsigned int & Answer);
+  void __fastcall ReportButtonSubmit(TObject * Sender, unsigned int & Answer);
   void __fastcall CMDialogKey(TWMKeyDown & Message);
   void __fastcall CMShowingChanged(TMessage & Message);
   void __fastcall UpdateForShiftState();
   TButton * __fastcall CreateButton(
     UnicodeString Name, UnicodeString Caption, unsigned int Answer,
-    TNotifyEvent OnClick, bool IsTimeoutButton,
+    TButtonSubmitEvent OnSubmit, bool IsTimeoutButton,
     int GroupWith, TShiftState GrouppedShiftState, bool ElevationRequired, bool MenuButton,
     TAnswerButtons & AnswerButtons, bool HasMoreMessages, int & ButtonWidths);
 };

+ 2 - 0
source/resource/TextsCore.h

@@ -263,6 +263,7 @@
 #define SIZE_INVALID            739
 #define KNOWN_HOSTS_NOT_FOUND   740
 #define KNOWN_HOSTS_NO_SITES    741
+#define HOSTKEY_NOT_MATCH_CLIPBOARD 742
 
 #define CORE_CONFIRMATION_STRINGS 300
 #define CONFIRM_PROLONG_TIMEOUT3 301
@@ -459,6 +460,7 @@
 #define TEXT_FILE_ENCODING      555
 #define AND_STR                 556
 #define AUTH_CHANGING_PASSWORD  557
+#define PASTE_KEY_BUTTON        558
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 2 - 0
source/resource/TextsCore1.rc

@@ -233,6 +233,7 @@ BEGIN
   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."
+  HOSTKEY_NOT_MATCH_CLIPBOARD, "Contents of the clipboard does not match the host key nor its fingerprint."
 
   CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION"
   CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"
@@ -429,6 +430,7 @@ BEGIN
   TEXT_FILE_ENCODING, "The file must be in UTF-8 or UTF-16 encoding."
   AND_STR, "%s and %s"
   AUTH_CHANGING_PASSWORD, "Changing password."
+  PASTE_KEY_BUTTON, "&Paste key"
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 5 - 5
source/windows/ConsoleRunner.cpp

@@ -1377,7 +1377,7 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
 
   std::vector<unsigned int> Buttons;
   std::vector<UnicodeString> Captions;
-  std::vector<TNotifyEvent> OnClicks;
+  std::vector<TButtonSubmitEvent> OnSubmits;
 
   for (unsigned int Answer = qaFirst; Answer <= qaLast; Answer = Answer << 1)
   {
@@ -1388,7 +1388,7 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
       AnswerNameAndCaption(Answer, Name, Caption);
       Captions.push_back(Caption);
       Buttons.push_back(Answer);
-      OnClicks.push_back(NULL);
+      OnSubmits.push_back(NULL);
       AAnswers -= Answer;
     }
   }
@@ -1409,7 +1409,7 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
           {
             Captions[bi] = Params->Aliases[ai].Alias;
           }
-          OnClicks[bi] = Params->Aliases[ai].OnClick;
+          OnSubmits[bi] = Params->Aliases[ai].OnSubmit;
           break;
         }
       }
@@ -1631,9 +1631,9 @@ void __fastcall TConsoleRunner::ScriptTerminalQueryUser(TObject * /*Sender*/,
       PrintLine(AnswerCaption);
       FirstOutput = true;
 
-      if (OnClicks[AnswerIndex - 1] != NULL)
+      if (OnSubmits[AnswerIndex - 1] != NULL)
       {
-        OnClicks[AnswerIndex - 1](NULL);
+        OnSubmits[AnswerIndex - 1](NULL, Answer);
       }
       else
       {

+ 7 - 7
source/windows/Setup.cpp

@@ -1188,7 +1188,7 @@ void __fastcall EnableAutomaticUpdates()
   OpenBrowser(GetEnableAutomaticUpdatesUrl());
 }
 //---------------------------------------------------------------------------
-static void __fastcall OpenHistory(void * /*Data*/, TObject * /*Sender*/)
+static void __fastcall OpenHistory(void * /*Data*/, TObject * /*Sender*/, unsigned int & /*Answer*/)
 {
   Configuration->Usage->Inc(L"UpdateHistoryOpens");
   OpenBrowser(LoadStr(HISTORY_URL));
@@ -1458,7 +1458,7 @@ static void __fastcall DownloadClose(void * /*Data*/, TObject * Sender, TCloseAc
   }
 }
 //---------------------------------------------------------------------------
-static void __fastcall DownloadUpdate(void * /*Data*/, TObject * Sender)
+static void __fastcall DownloadUpdate(void * /*Data*/, TObject * Sender, unsigned int & /*Answer*/)
 {
   Configuration->Usage->Inc(L"UpdateDownloadStarts");
   TButton * Button = DebugNotNull(dynamic_cast<TButton *>(Sender));
@@ -1603,7 +1603,7 @@ bool __fastcall CheckForUpdates(bool CachedResults)
     }
     Aliases[1].Button = qaAll;
     Aliases[1].Alias = LoadStr(WHATS_NEW_BUTTON);
-    Aliases[1].OnClick = MakeMethod<TNotifyEvent>(NULL, OpenHistory);
+    Aliases[1].OnSubmit = MakeMethod<TButtonSubmitEvent>(NULL, OpenHistory);
     Aliases[2].Button = qaCancel;
     Aliases[2].Alias = Vcl_Consts_SMsgDlgClose;
     // Used only when New == true, see AliasesCount below
@@ -1611,7 +1611,7 @@ bool __fastcall CheckForUpdates(bool CachedResults)
     Aliases[3].Alias = LoadStr(UPGRADE_BUTTON);
     if (!Updates.Results.DownloadUrl.IsEmpty())
     {
-      Aliases[3].OnClick = MakeMethod<TNotifyEvent>(NULL, DownloadUpdate);
+      Aliases[3].OnSubmit = MakeMethod<TButtonSubmitEvent>(NULL, DownloadUpdate);
       Aliases[3].ElevationRequired = true;
     }
 
@@ -2013,7 +2013,7 @@ static void __fastcall TipSeen(const UnicodeString & Tip)
   WinConfiguration->Save();
 }
 //---------------------------------------------------------------------------
-static void __fastcall PrevNextTipClick(void * Data, TObject * Sender)
+static void __fastcall PrevNextTipClick(void * Data, TObject * Sender, unsigned int & /*Answer*/)
 {
   TCustomForm * Form = GetParentForm(dynamic_cast<TControl *>(Sender));
   TTipsData * TipsData = TTipsData::Retrieve(Form);
@@ -2051,10 +2051,10 @@ static void __fastcall ShowTip(bool AutoShow)
   TQueryButtonAlias Aliases[3];
   Aliases[0].Button = qaYes;
   Aliases[0].Alias = LoadStr(PREV_BUTTON);
-  Aliases[0].OnClick = MakeMethod<TNotifyEvent>(reinterpret_cast<void *>(-1), PrevNextTipClick);
+  Aliases[0].OnSubmit = MakeMethod<TButtonSubmitEvent>(reinterpret_cast<void *>(-1), PrevNextTipClick);
   Aliases[1].Button = qaNo;
   Aliases[1].Alias = LoadStr(NEXT_BUTTON);
-  Aliases[1].OnClick = MakeMethod<TNotifyEvent>(reinterpret_cast<void *>(+1), PrevNextTipClick);
+  Aliases[1].OnSubmit = MakeMethod<TButtonSubmitEvent>(reinterpret_cast<void *>(+1), PrevNextTipClick);
   Aliases[2].Button = qaCancel;
   Aliases[2].Alias = LoadStr(CLOSE_BUTTON);
 

+ 0 - 1
source/windows/Tools.h

@@ -47,7 +47,6 @@ void __fastcall OpenFileInExplorer(const UnicodeString & Path);
 void __fastcall OpenFolderInExplorer(const UnicodeString & Path);
 void __fastcall ShowHelp(const UnicodeString & HelpKeyword);
 bool __fastcall IsFormatInClipboard(unsigned int Format);
-bool __fastcall TextFromClipboard(UnicodeString & Text, bool Trim);
 bool __fastcall NonEmptyTextFromClipboard(UnicodeString & Text);
 HANDLE __fastcall OpenTextFromClipboard(const wchar_t *& Text);
 void __fastcall CloseTextFromClipboard(HANDLE Handle);

+ 2 - 2
source/windows/UserInterface.cpp

@@ -135,7 +135,7 @@ struct TOpenLocalPathHandler
   UnicodeString LocalPath;
   UnicodeString LocalFileName;
 
-  void __fastcall Open(TObject * Sender)
+  void __fastcall Open(TObject * Sender, unsigned int & /*Answer*/)
   {
     TButton * Button = DebugNotNull(dynamic_cast<TButton *>(Sender));
     // Reason for separate AMenu variable is given in TPreferencesDialog::EditorFontColorButtonClick
@@ -256,7 +256,7 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
 
           Aliases[0].Button = qaIgnore;
           Aliases[0].Alias = LoadStr(OPEN_BUTTON);
-          Aliases[0].OnClick = OpenLocalPathHandler.Open;
+          Aliases[0].OnSubmit = OpenLocalPathHandler.Open;
           Aliases[0].MenuButton = true;
           Answers |= Aliases[0].Button;
           Params.Aliases = Aliases;