From 64f634db37a755d5a15c258518695cfa863c7bfe Mon Sep 17 00:00:00 2001 From: Martin Prikryl Date: Thu, 10 May 2018 09:08:45 +0000 Subject: [PATCH] Bug 386: Automatic upload of keys pairs + Public key can be displayed in Advanced Site Settings dialog https://winscp.net/tracker/386 Source commit: 78d8baba92c09728ef3bccc9b49f1590a76d4ca5 --- source/core/FileSystems.h | 1 + source/core/PuttyIntf.cpp | 53 ++++++ source/core/PuttyTools.h | 3 + source/core/SecureShell.cpp | 5 +- source/core/SessionData.cpp | 2 +- source/core/SessionData.h | 1 + source/core/SftpFileSystem.h | 2 +- source/core/Terminal.cpp | 5 + source/core/Terminal.h | 1 + source/forms/CustomScpExplorer.cpp | 13 ++ source/forms/CustomScpExplorer.h | 2 + source/forms/NonVisual.cpp | 2 + source/forms/NonVisual.dfm | 7 + source/forms/NonVisual.h | 1 + source/forms/ScpCommander.dfm | 3 + source/forms/ScpCommander.h | 1 + source/forms/ScpExplorer.dfm | 3 + source/forms/ScpExplorer.h | 1 + source/forms/SiteAdvanced.cpp | 62 ++++++- source/forms/SiteAdvanced.dfm | 30 +++- source/forms/SiteAdvanced.h | 11 +- source/resource/HelpWin.h | 1 + source/resource/TextsWin.h | 7 + source/resource/TextsWin1.rc | 7 + source/windows/TerminalManager.cpp | 267 +++++++++++++++++++++++++++-- source/windows/TerminalManager.h | 7 +- source/windows/Tools.cpp | 12 +- source/windows/Tools.h | 2 +- 28 files changed, 466 insertions(+), 46 deletions(-) diff --git a/source/core/FileSystems.h b/source/core/FileSystems.h index e61b4eda2..dfde00358 100644 --- a/source/core/FileSystems.h +++ b/source/core/FileSystems.h @@ -81,6 +81,7 @@ class TCustomFileSystem const TRemoteFile * File, UnicodeString Command, int Params, TCaptureOutputEvent OutputEvent) = 0; virtual void __fastcall DoStartup() = 0; virtual void __fastcall HomeDirectory() = 0; + virtual UnicodeString __fastcall GetHomeDirectory() { throw Exception(L"Not implemented"); }; virtual bool __fastcall IsCapable(int Capability) const = 0; virtual void __fastcall LookupUsersGroups() = 0; virtual void __fastcall ReadCurrentDirectory() = 0; diff --git a/source/core/PuttyIntf.cpp b/source/core/PuttyIntf.cpp index 817ba4960..19d5ceab9 100644 --- a/source/core/PuttyIntf.cpp +++ b/source/core/PuttyIntf.cpp @@ -10,6 +10,7 @@ #include "CoreMain.h" #include "TextsCore.h" #include +#include //--------------------------------------------------------------------------- char sshver[50]; extern const char commitid[] = ""; @@ -686,6 +687,49 @@ void FreeKey(TPrivateKey * PrivateKey) sfree(Ssh2Key); } //--------------------------------------------------------------------------- +RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algorithm, UnicodeString & Comment) +{ + RawByteString Result; + UTF8String UtfFileName = UTF8String(FileName); + Filename * KeyFile = filename_from_str(UtfFileName.c_str()); + try + { + char * AlgorithmStr = NULL; + int PublicKeyLen = 0; + char * CommentStr = NULL; + const char * ErrorStr = NULL; + unsigned char * PublicKeyPtr = + ssh2_userkey_loadpub(KeyFile, &AlgorithmStr, &PublicKeyLen, &CommentStr, &ErrorStr); + if (PublicKeyPtr == NULL) + { + UnicodeString Error = UnicodeString(AnsiString(ErrorStr)); + throw Exception(Error); + } + Algorithm = UnicodeString(AnsiString(AlgorithmStr)); + sfree(AlgorithmStr); + Comment = UnicodeString(AnsiString(CommentStr)); + sfree(CommentStr); + Result = RawByteString(reinterpret_cast(PublicKeyPtr), PublicKeyLen); + free(PublicKeyPtr); + } + __finally + { + filename_free(KeyFile); + } + return Result; +} +//--------------------------------------------------------------------------- +UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment) +{ + UnicodeString Algorithm; + RawByteString PublicKey = LoadPublicKey(FileName, Algorithm, Comment); + UnicodeString PublicKeyBase64 = EncodeBase64(PublicKey.c_str(), PublicKey.Length()); + PublicKeyBase64 = ReplaceStr(PublicKeyBase64, L"\r", L""); + PublicKeyBase64 = ReplaceStr(PublicKeyBase64, L"\n", L""); + UnicodeString Result = FORMAT(L"%s %s %s", (Algorithm, PublicKeyBase64, Comment)); + return Result; +} +//--------------------------------------------------------------------------- bool __fastcall HasGSSAPI(UnicodeString CustomPath) { static int has = -1; @@ -879,4 +923,13 @@ UnicodeString __fastcall GetKeyTypeHuman(const UnicodeString & KeyType) return Result; } //--------------------------------------------------------------------------- +bool IsOpenSSH(const UnicodeString & SshImplementation) +{ + return + // e.g. "OpenSSH_5.3" + (SshImplementation.Pos(L"OpenSSH") == 1) || + // Sun SSH is based on OpenSSH (suffers the same bugs) + (SshImplementation.Pos(L"Sun_SSH") == 1); +} +//--------------------------------------------------------------------------- //--------------------------------------------------------------------------- diff --git a/source/core/PuttyTools.h b/source/core/PuttyTools.h index 1196b1b96..bce642965 100644 --- a/source/core/PuttyTools.h +++ b/source/core/PuttyTools.h @@ -17,6 +17,7 @@ void ChangeKeyComment(TPrivateKey * PrivateKey, const UnicodeString & Comment); void SaveKey(TKeyType KeyType, const UnicodeString & FileName, const UnicodeString & Passphrase, TPrivateKey * PrivateKey); void FreeKey(TPrivateKey * PrivateKey); +UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment); extern const UnicodeString PuttyKeyExt; //--------------------------------------------------------------------------- bool __fastcall HasGSSAPI(UnicodeString CustomPath); @@ -37,4 +38,6 @@ UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const s //--------------------------------------------------------------------------- UnicodeString __fastcall GetKeyTypeHuman(const UnicodeString & KeyType); //--------------------------------------------------------------------------- +bool IsOpenSSH(const UnicodeString & SshImplementation); +//--------------------------------------------------------------------------- #endif diff --git a/source/core/SecureShell.cpp b/source/core/SecureShell.cpp index b70c1e3f0..3548790f2 100644 --- a/source/core/SecureShell.cpp +++ b/source/core/SecureShell.cpp @@ -455,10 +455,7 @@ void __fastcall TSecureShell::Open() FOpened = true; UnicodeString SshImplementation = GetSessionInfo().SshImplementation; - if (// e.g. "OpenSSH_5.3" - (SshImplementation.Pos(L"OpenSSH") == 1) || - // Sun SSH is based on OpenSSH (suffers the same bugs) - (SshImplementation.Pos(L"Sun_SSH") == 1)) + if (IsOpenSSH(SshImplementation)) { FSshImplementation = sshiOpenSSH; } diff --git a/source/core/SessionData.cpp b/source/core/SessionData.cpp index 551069cfc..03e3b40a6 100644 --- a/source/core/SessionData.cpp +++ b/source/core/SessionData.cpp @@ -509,7 +509,7 @@ bool __fastcall TSessionData::IsSame(const TSessionData * Default, bool Advanced return IsSame(Default, AdvancedOnly, NULL); } //--------------------------------------------------------------------- -static TFSProtocol NormalizeFSProtocol(TFSProtocol FSProtocol) +TFSProtocol NormalizeFSProtocol(TFSProtocol FSProtocol) { if ((FSProtocol == fsSCPonly) || (FSProtocol == fsSFTPonly)) { diff --git a/source/core/SessionData.h b/source/core/SessionData.h index d08c46a20..85dabbdc5 100644 --- a/source/core/SessionData.h +++ b/source/core/SessionData.h @@ -711,5 +711,6 @@ bool __fastcall IsSshProtocol(TFSProtocol FSProtocol); int __fastcall DefaultPort(TFSProtocol FSProtocol, TFtps Ftps); bool __fastcall IsIPv6Literal(const UnicodeString & HostName); UnicodeString __fastcall EscapeIPv6Literal(const UnicodeString & IP); +TFSProtocol NormalizeFSProtocol(TFSProtocol FSProtocol); //--------------------------------------------------------------------------- #endif diff --git a/source/core/SftpFileSystem.h b/source/core/SftpFileSystem.h index 6ec53d41e..b9fac022c 100644 --- a/source/core/SftpFileSystem.h +++ b/source/core/SftpFileSystem.h @@ -72,6 +72,7 @@ friend class TSFTPBusy; const TRemoteFile * File, UnicodeString Command, int Params, TCaptureOutputEvent OutputEvent); virtual void __fastcall DoStartup(); virtual void __fastcall HomeDirectory(); + virtual UnicodeString __fastcall GetHomeDirectory(); virtual bool __fastcall IsCapable(int Capability) const; virtual void __fastcall LookupUsersGroups(); virtual void __fastcall ReadCurrentDirectory(); @@ -132,7 +133,6 @@ friend class TSFTPBusy; TRemoteFile *& File, unsigned char Type, TRemoteFile * ALinkedByFile = NULL, int AllowStatus = -1); virtual UnicodeString __fastcall GetCurrentDirectory(); - UnicodeString __fastcall GetHomeDirectory(); unsigned long __fastcall GotStatusPacket(TSFTPPacket * Packet, int AllowStatus); bool __fastcall RemoteFileExists(const UnicodeString FullPath, TRemoteFile ** File = NULL); TRemoteFile * __fastcall LoadFile(TSFTPPacket * Packet, diff --git a/source/core/Terminal.cpp b/source/core/Terminal.cpp index b85024788..c078676c6 100644 --- a/source/core/Terminal.cpp +++ b/source/core/Terminal.cpp @@ -4681,6 +4681,11 @@ void __fastcall TTerminal::HomeDirectory() } } //--------------------------------------------------------------------------- +UnicodeString __fastcall TTerminal::GetHomeDirectory() +{ + return FFileSystem->GetHomeDirectory(); +} +//--------------------------------------------------------------------------- void __fastcall TTerminal::ChangeDirectory(const UnicodeString Directory) { DebugAssert(FFileSystem); diff --git a/source/core/Terminal.h b/source/core/Terminal.h index 72cb3fbab..16f6c034d 100644 --- a/source/core/Terminal.h +++ b/source/core/Terminal.h @@ -500,6 +500,7 @@ friend class TRetryOperationLoop; void __fastcall ChangeDirectory(const UnicodeString Directory); void __fastcall EndTransaction(); void __fastcall HomeDirectory(); + UnicodeString __fastcall GetHomeDirectory(); void __fastcall ChangeFileProperties(UnicodeString FileName, const TRemoteFile * File, /*const TRemoteProperties */ void * Properties); void __fastcall ChangeFilesProperties(TStrings * FileList, diff --git a/source/forms/CustomScpExplorer.cpp b/source/forms/CustomScpExplorer.cpp index 2b974da3f..3601d6bdf 100644 --- a/source/forms/CustomScpExplorer.cpp +++ b/source/forms/CustomScpExplorer.cpp @@ -9486,6 +9486,19 @@ void __fastcall TCustomScpExplorerForm::ChangePassword() } } //--------------------------------------------------------------------------- +bool __fastcall TCustomScpExplorerForm::CanPrivateKeyUpload() +{ + // No nice way to assert SSH2 + return (Terminal != NULL) && Terminal->Active && (Terminal->FSProtocol == cfsSFTP); +} +//--------------------------------------------------------------------------- +void __fastcall TCustomScpExplorerForm::PrivateKeyUpload() +{ + TOperationVisualizer OperationVisualizer; + UnicodeString FileName = Terminal->SessionData->PublicKeyFile; + TTerminalManager::Instance()->UploadPublicKey(Terminal, NULL, FileName); +} +//--------------------------------------------------------------------------- void __fastcall TCustomScpExplorerForm::ChangeScale(int M, int D) { TForm::ChangeScale(M, D); diff --git a/source/forms/CustomScpExplorer.h b/source/forms/CustomScpExplorer.h index eb239c918..d79e7ea6a 100644 --- a/source/forms/CustomScpExplorer.h +++ b/source/forms/CustomScpExplorer.h @@ -717,6 +717,8 @@ class TCustomScpExplorerForm : public TForm bool __fastcall CanConsole(); bool __fastcall CanChangePassword(); void __fastcall ChangePassword(); + bool __fastcall CanPrivateKeyUpload(); + void __fastcall PrivateKeyUpload(); bool __fastcall IsComponentPossible(Byte Component); __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible }; diff --git a/source/forms/NonVisual.cpp b/source/forms/NonVisual.cpp index 48f8e55bb..75acff291 100644 --- a/source/forms/NonVisual.cpp +++ b/source/forms/NonVisual.cpp @@ -417,6 +417,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate( UPD(NewFileAction, DirViewEnabled(osCurrent) && !WinConfiguration->DisableOpenEdit) UPD(EditorListCustomizeAction, true) UPD(ChangePasswordAction, ScpExplorer->CanChangePassword()) + UPD(PrivateKeyUploadAction, ScpExplorer->CanPrivateKeyUpload()) // CUSTOM COMMANDS UPD(CustomCommandsFileAction, true) @@ -730,6 +731,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute( EXE(NewFileAction, ScpExplorer->EditNew(osCurrent)) EXE(EditorListCustomizeAction, PreferencesDialog(pmEditor)) EXE(ChangePasswordAction, ScpExplorer->ChangePassword()) + EXE(PrivateKeyUploadAction, ScpExplorer->PrivateKeyUpload()) // CUSTOM COMMANDS EXE(CustomCommandsFileAction, CreateCustomCommandsMenu(CustomCommandsFileAction, ccltFile)) diff --git a/source/forms/NonVisual.dfm b/source/forms/NonVisual.dfm index 9f15fb2aa..bb6db5a91 100644 --- a/source/forms/NonVisual.dfm +++ b/source/forms/NonVisual.dfm @@ -2208,6 +2208,13 @@ object NonVisualDataModule: TNonVisualDataModule HelpKeyword = 'task_change_password' Hint = 'Change account password' end + object PrivateKeyUploadAction: TAction + Tag = 15 + Category = 'Session' + Caption = '&Install Public Key into Server...' + HelpKeyword = 'guide_public_key' + Hint = 'Install public key for authentication into the server' + end object RemoteNewFileAction: TAction Tag = 15 Category = 'Remote Selected Operation' diff --git a/source/forms/NonVisual.h b/source/forms/NonVisual.h index fd8a97458..3af67e536 100644 --- a/source/forms/NonVisual.h +++ b/source/forms/NonVisual.h @@ -615,6 +615,7 @@ class TNonVisualDataModule : public TDataModule TTBXSubmenuItem *TBXSubmenuItem4; TTBXSubmenuItem *TBXSubmenuItem6; TTBXSubmenuItem *TBXSubmenuItem9; + TAction *PrivateKeyUploadAction; void __fastcall ExplorerActionsUpdate(TBasicAction *Action, bool &Handled); void __fastcall ExplorerActionsExecute(TBasicAction *Action, bool &Handled); void __fastcall SessionIdleTimerTimer(TObject *Sender); diff --git a/source/forms/ScpCommander.dfm b/source/forms/ScpCommander.dfm index 442e2ff3a..15332a8ac 100644 --- a/source/forms/ScpCommander.dfm +++ b/source/forms/ScpCommander.dfm @@ -429,6 +429,9 @@ inherited ScpCommanderForm: TScpCommanderForm object TBXItem227: TTBXItem Action = NonVisualDataModule.ChangePasswordAction end + object TBXItem76: TTBXItem + Action = NonVisualDataModule.PrivateKeyUploadAction + end object TBXSeparatorItem29: TTBXSeparatorItem end object TBXSubmenuItem21: TTBXSubmenuItem diff --git a/source/forms/ScpCommander.h b/source/forms/ScpCommander.h index a62eb749f..452bc03c6 100644 --- a/source/forms/ScpCommander.h +++ b/source/forms/ScpCommander.h @@ -423,6 +423,7 @@ class TScpCommanderForm : public TCustomScpExplorerForm TTBXItem *TBXItem248; TTBXItem *TBXItem249; TTBXItem *TBXItem250; + TTBXItem *TBXItem76; void __fastcall SplitterMoved(TObject *Sender); void __fastcall SplitterCanResize(TObject *Sender, int &NewSize, bool &Accept); diff --git a/source/forms/ScpExplorer.dfm b/source/forms/ScpExplorer.dfm index 8eff14852..6348cbec7 100644 --- a/source/forms/ScpExplorer.dfm +++ b/source/forms/ScpExplorer.dfm @@ -305,6 +305,9 @@ inherited ScpExplorerForm: TScpExplorerForm object TBXItem160: TTBXItem Action = NonVisualDataModule.ChangePasswordAction end + object TBXItem14: TTBXItem + Action = NonVisualDataModule.ChangePasswordAction + end object TBXSeparatorItem29: TTBXSeparatorItem end object TBXSubmenuItem21: TTBXSubmenuItem diff --git a/source/forms/ScpExplorer.h b/source/forms/ScpExplorer.h index 096e86049..4ffb9a0e2 100644 --- a/source/forms/ScpExplorer.h +++ b/source/forms/ScpExplorer.h @@ -313,6 +313,7 @@ class TScpExplorerForm : public TCustomScpExplorerForm TTBXItem *TBXItem247; TTBXItem *TBXItem244; TTBXItem *TBXItem246; + TTBXItem *TBXItem14; void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender, const TStatusFileInfo &FileInfo); void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender, diff --git a/source/forms/SiteAdvanced.cpp b/source/forms/SiteAdvanced.cpp index 74cc5509f..4cc3b2045 100644 --- a/source/forms/SiteAdvanced.cpp +++ b/source/forms/SiteAdvanced.cpp @@ -16,6 +16,8 @@ #include "Tools.h" #include "WinConfiguration.h" #include "PuttyTools.h" +#include "TerminalManager.h" +#include "Authenticate.h" //--------------------------------------------------------------------- #pragma link "ComboEdit" #pragma link "PasswordEdit" @@ -105,8 +107,7 @@ void __fastcall TSiteAdvancedDialog::InitControls() SelectScaledImageList(ColorImageList); SetSessionColor((TColor)0); - UnicodeString Dummy; - PrivateKeyGenerateButton->Enabled = FindTool(PuttygenTool, Dummy); + MenuButton(PrivateKeyToolsButton); } //--------------------------------------------------------------------- void __fastcall TSiteAdvancedDialog::LoadSession() @@ -752,8 +753,7 @@ void __fastcall TSiteAdvancedDialog::UpdateControls() TAutoNestingCounter NoUpdateCounter(NoUpdate); bool SshProtocol = FSessionData->UsesSsh; - bool SftpProtocol = - (FSessionData->FSProtocol == fsSFTPonly) || (FSessionData->FSProtocol == fsSFTP); + bool SftpProtocol = (NormalizeFSProtocol(FSessionData->FSProtocol) == fsSFTP); bool ScpProtocol = (FSessionData->FSProtocol == fsSCPonly); bool FtpProtocol = (FSessionData->FSProtocol == fsFTP); bool WebDavProtocol = (FSessionData->FSProtocol == fsWebDAV); @@ -794,6 +794,7 @@ void __fastcall TSiteAdvancedDialog::UpdateControls() ((AuthTISCheck->Enabled && AuthTISCheck->Checked) || (AuthKICheck->Enabled && AuthKICheck->Checked))); EnableControl(AuthenticationParamsGroup, AuthenticationGroup->Enabled); + EnableControl(PrivateKeyViewButton, PrivateKeyEdit3->Enabled && !PrivateKeyEdit3->Text.IsEmpty()); EnableControl(AuthGSSAPICheck3, AuthenticationGroup->Enabled && (GetSshProt() == ssh2only)); EnableControl(GSSAPIFwdTGTCheck, @@ -1255,7 +1256,7 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyEdit3AfterDialog(TObject * Sender TFilenameEdit * Edit = dynamic_cast(Sender); if (Name != Edit->Text) { - VerifyAndConvertKey(Name, GetSshProt()); + VerifyAndConvertKey(Name, GetSshProt(), true); } } //--------------------------------------------------------------------------- @@ -1521,14 +1522,61 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyCreatedOrModified(TObject * /*Sen { if (SameText(ExtractFileExt(FileName), FORMAT(L".%s", (PuttyKeyExt)))) { - PrivateKeyEdit3->FileName = FileName; + PrivateKeyEdit3->Text = FileName; } } //--------------------------------------------------------------------------- -void __fastcall TSiteAdvancedDialog::PrivateKeyGenerateButtonClick(TObject * /*Sender*/) +void __fastcall TSiteAdvancedDialog::PrivateKeyToolsButtonClick(TObject * /*Sender*/) +{ + UnicodeString Dummy; + PrivateKeyGenerateItem->Enabled = FindTool(PuttygenTool, Dummy); + PrivateKeyUploadItem->Enabled = (GetSshProt() == ssh2only) && (NormalizeFSProtocol(FSessionData->FSProtocol) == fsSFTP); + MenuPopup(PrivateKeyMenu, PrivateKeyToolsButton); +} +//--------------------------------------------------------------------------- +void __fastcall TSiteAdvancedDialog::PrivateKeyGenerateItemClick(TObject * /*Sender*/) { unsigned int Filters = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE; FPrivateKeyMonitors.reset(StartCreationDirectoryMonitorsOnEachDrive(Filters, PrivateKeyCreatedOrModified)); ExecuteTool(PuttygenTool); } //--------------------------------------------------------------------------- +void __fastcall TSiteAdvancedDialog::PrivateKeyUploadItemClick(TObject * /*Sender*/) +{ + SaveSession(); + FSessionData->FSProtocol = fsSFTPonly; // no SCP fallback, as SCP does not implement GetHomeDirectory + FSessionData->RemoteDirectory = UnicodeString(); + + UnicodeString FileName = PrivateKeyEdit3->Text; + if (TTerminalManager::Instance()->UploadPublicKey(NULL, FSessionData, FileName)) + { + PrivateKeyEdit3->Text = FileName; + PrivateKeyEdit3->SetFocus(); + } +} +//--------------------------------------------------------------------------- +void __fastcall TSiteAdvancedDialog::PrivateKeyViewButtonClick(TObject * /*Sender*/) +{ + UnicodeString FileName = PrivateKeyEdit3->Text; + VerifyAndConvertKey(FileName, GetSshProt(), false); + PrivateKeyEdit3->Text = FileName; + UnicodeString CommentDummy; + UnicodeString Line = GetPublicKeyLine(FileName, CommentDummy); + std::unique_ptr Messages(TextToStringList(Line)); + + TClipboardHandler ClipboardHandler; + ClipboardHandler.Text = Line; + + TMessageParams Params; + TQueryButtonAlias Aliases[1]; + Aliases[0].Button = qaRetry; + Aliases[0].Alias = LoadStr(COPY_KEY_BUTTON); + Aliases[0].OnSubmit = &ClipboardHandler.Copy; + Params.Aliases = Aliases; + Params.AliasesCount = LENOF(Aliases); + + UnicodeString Message = LoadStr(LOGIN_AUTHORIZED_KEYS); + int Answers = qaOK | qaRetry; + MoreMessageDialog(Message, Messages.get(), qtInformation, Answers, HELP_LOGIN_AUTHORIZED_KEYS, &Params); +} +//--------------------------------------------------------------------------- diff --git a/source/forms/SiteAdvanced.dfm b/source/forms/SiteAdvanced.dfm index bf647e4c4..b20a1f2f5 100644 --- a/source/forms/SiteAdvanced.dfm +++ b/source/forms/SiteAdvanced.dfm @@ -2048,15 +2048,23 @@ object SiteAdvancedDialog: TSiteAdvancedDialog Text = 'PrivateKeyEdit3' OnChange = DataChange end - object PrivateKeyGenerateButton: TButton - Left = 12 + object PrivateKeyToolsButton: TButton + Left = 151 Top = 86 Width = 101 Height = 25 - Anchors = [akTop, akRight] - Caption = 'Ge&nerate...' + Caption = '&Tools' TabOrder = 2 - OnClick = PrivateKeyGenerateButtonClick + OnClick = PrivateKeyToolsButtonClick + end + object PrivateKeyViewButton: TButton + Left = 12 + Top = 86 + Width = 133 + Height = 25 + Caption = '&Display Public Key' + TabOrder = 3 + OnClick = PrivateKeyViewButtonClick end end object GSSAPIGroup: TGroupBox @@ -3662,4 +3670,16 @@ object SiteAdvancedDialog: TSiteAdvancedDialog FFFFFFFF00000000000000000000000000000000000000000000000000000000 000000000000} end + object PrivateKeyMenu: TPopupMenu + Left = 128 + Top = 384 + object PrivateKeyGenerateItem: TMenuItem + Caption = '&Generate New Key Pair with PuTTYgen...' + OnClick = PrivateKeyGenerateItemClick + end + object PrivateKeyUploadItem: TMenuItem + Caption = '&Install Public Key into Server...' + OnClick = PrivateKeyUploadItemClick + end + end end diff --git a/source/forms/SiteAdvanced.h b/source/forms/SiteAdvanced.h index a1cc6c62c..015daf3bb 100644 --- a/source/forms/SiteAdvanced.h +++ b/source/forms/SiteAdvanced.h @@ -257,7 +257,11 @@ class TSiteAdvancedDialog : public TForm TImageList *ColorImageList120; TImageList *ColorImageList144; TImageList *ColorImageList192; - TButton *PrivateKeyGenerateButton; + TButton *PrivateKeyToolsButton; + TPopupMenu *PrivateKeyMenu; + TMenuItem *PrivateKeyGenerateItem; + TMenuItem *PrivateKeyUploadItem; + TButton *PrivateKeyViewButton; void __fastcall DataChange(TObject *Sender); void __fastcall FormShow(TObject *Sender); void __fastcall PageControlChange(TObject *Sender); @@ -290,7 +294,10 @@ class TSiteAdvancedDialog : public TForm void __fastcall NoteMemoKeyDown(TObject *Sender, WORD &Key, TShiftState Shift); void __fastcall TlsCertificateFileEditAfterDialog(TObject *Sender, UnicodeString &Name, bool &Action); - void __fastcall PrivateKeyGenerateButtonClick(TObject *Sender); + void __fastcall PrivateKeyUploadItemClick(TObject *Sender); + void __fastcall PrivateKeyGenerateItemClick(TObject *Sender); + void __fastcall PrivateKeyToolsButtonClick(TObject *Sender); + void __fastcall PrivateKeyViewButtonClick(TObject *Sender); public: diff --git a/source/resource/HelpWin.h b/source/resource/HelpWin.h index c1182a123..df459ee3c 100644 --- a/source/resource/HelpWin.h +++ b/source/resource/HelpWin.h @@ -60,5 +60,6 @@ #define HELP_EXTENSION_OPTIONS "ui_pref_commands" #define HELP_CHANGE_PASSWORD "task_change_password" #define HELP_FILTER "ui_filter" +#define HELP_LOGIN_AUTHORIZED_KEYS "guide_public_key" #endif // TextsWin diff --git a/source/resource/TextsWin.h b/source/resource/TextsWin.h index 39e5abc45..8247ebb41 100644 --- a/source/resource/TextsWin.h +++ b/source/resource/TextsWin.h @@ -598,6 +598,13 @@ #define PREFERENCES_DRAGEXT_NOT_INSTALLED 6000 #define PREFERENCES_DRAGEXT_NOT_RUNNING 6001 #define PREFERENCES_DRAGEXT_RUNNING 6002 +#define LOGIN_AUTHORIZED_KEYS 6003 +#define LOGIN_NOT_OPENSSH 6004 +#define LOGIN_PUBLIC_KEY_UPLOAD 6005 +#define LOGIN_PUBLIC_KEY_UPLOADED 6006 +#define LOGIN_PUBLIC_KEY_PERMISSIONS 6007 +#define LOGIN_PUBLIC_KEY_TITLE 6008 +#define LOGIN_PUBLIC_KEY_FILTER 6009 // 2xxx is reserved for TextsFileZilla.h diff --git a/source/resource/TextsWin1.rc b/source/resource/TextsWin1.rc index 85d6798e4..1558a0c36 100644 --- a/source/resource/TextsWin1.rc +++ b/source/resource/TextsWin1.rc @@ -601,6 +601,13 @@ BEGIN PREFERENCES_DRAGEXT_NOT_INSTALLED, "Shell extension is not installed." PREFERENCES_DRAGEXT_NOT_RUNNING, "Shell extension is installed, but is not loaded." PREFERENCES_DRAGEXT_RUNNING, "Shell extension is installed and loaded." + LOGIN_AUTHORIZED_KEYS, "**Public key for pasting into OpenSSH authorized_keys file:**" + LOGIN_NOT_OPENSSH, "**Install public key to non-OpenSSH server?**\n\nInstalling public key is supported for OpenSSH server only (authorized_keys file).\n\nYour server is %s." + LOGIN_PUBLIC_KEY_UPLOAD, "Installing public key \"%s\"..." + LOGIN_PUBLIC_KEY_UPLOADED, "**Public key \"%s\" was installed.**\n\nYou can now login to the server using the key pair." + LOGIN_PUBLIC_KEY_PERMISSIONS, "Though potentially wrong permissions of \"%s\" file and/or its parent folder were detected. Please check them." + LOGIN_PUBLIC_KEY_TITLE, "Select key to install into server" + LOGIN_PUBLIC_KEY_FILTER, "PuTTY Private Key Files (*.ppk)|*.ppk|All Private Key Files (*.ppk;*.pem;*.key;id_*)|*.ppk;*.pem;*.key;id_*|All Files (*.*)|*.*" WIN_VARIABLE_STRINGS, "WIN_VARIABLE" WINSCP_COPYRIGHT, "Copyright © 2000-2018 Martin Prikryl" diff --git a/source/windows/TerminalManager.cpp b/source/windows/TerminalManager.cpp index b215c8938..54e5f3797 100644 --- a/source/windows/TerminalManager.cpp +++ b/source/windows/TerminalManager.cpp @@ -18,6 +18,10 @@ #include #include #include +#include +#include +#include +#include //--------------------------------------------------------------------------- #pragma package(smart_init) //--------------------------------------------------------------------------- @@ -72,6 +76,7 @@ __fastcall TTerminalManager::TTerminalManager() : FMainThread = GetCurrentThreadId(); FChangeSection.reset(new TCriticalSection()); FPendingConfigurationChange = 0; + FKeepAuthenticateForm = false; FApplicationsEvents.reset(new TApplicationEvents(Application)); FApplicationsEvents->OnException = ApplicationException; @@ -116,7 +121,7 @@ __fastcall TTerminalManager::~TTerminalManager() delete FQueues; delete FTerminationMessages; delete FTerminalList; - delete FAuthenticateForm; + CloseAutheticateForm(); delete FQueueSection; ReleaseTaskbarList(); } @@ -218,7 +223,7 @@ void __fastcall TTerminalManager::FreeActiveTerminal() } } //--------------------------------------------------------------------------- -void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool Reopen) +void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool Reopen, bool AdHoc) { TManagedTerminal * ManagedTerminal = dynamic_cast(Terminal); // it must be managed terminal, unless it is secondary terminal (of managed terminal) @@ -263,22 +268,21 @@ void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool R __finally { TerminalThread->OnIdle = NULL; - if (!TerminalThread->Release() && (DebugAlwaysTrue(Terminal == FActiveTerminal))) + if (!TerminalThread->Release()) { - // terminal was abandoned, must create a new one to replace it - Terminal = CreateTerminal(new TSessionData(L"")); - SetupTerminal(Terminal); - OwnsObjects = false; - Items[ActiveTerminalIndex] = Terminal; - OwnsObjects = true; - FActiveTerminal = Terminal; - - // when abandoning cancelled terminal, the form remains open - if (FAuthenticateForm != NULL) + if (!AdHoc && (DebugAlwaysTrue(Terminal == FActiveTerminal))) { - delete FAuthenticateForm; - FAuthenticateForm = NULL; + // terminal was abandoned, must create a new one to replace it + Terminal = CreateTerminal(new TSessionData(L"")); + SetupTerminal(Terminal); + OwnsObjects = false; + Items[ActiveTerminalIndex] = Terminal; + OwnsObjects = true; + FActiveTerminal = Terminal; } + + // when abandoning cancelled terminal, the form remains open + CloseAutheticateForm(); } else { @@ -299,6 +303,11 @@ void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool R } } //--------------------------------------------------------------------------- +void __fastcall TTerminalManager::CloseAutheticateForm() +{ + SAFE_DESTROY(FAuthenticateForm); +} +//--------------------------------------------------------------------------- bool __fastcall TTerminalManager::ConnectTerminal(TTerminal * Terminal) { bool Result = true; @@ -306,7 +315,7 @@ bool __fastcall TTerminalManager::ConnectTerminal(TTerminal * Terminal) DebugAssert(Terminal != FActiveTerminal); try { - DoConnectTerminal(Terminal, false); + DoConnectTerminal(Terminal, false, false); } catch (Exception & E) { @@ -333,7 +342,7 @@ bool __fastcall TTerminalManager::ConnectActiveTerminalImpl(bool Reopen) { DebugAssert(ActiveTerminal); - DoConnectTerminal(ActiveTerminal, Reopen); + DoConnectTerminal(ActiveTerminal, Reopen, false); if (ScpExplorer) { @@ -1124,7 +1133,10 @@ void __fastcall TTerminalManager::TerminalInformation( BusyEnd(FBusyToken); FBusyToken = NULL; } - SAFE_DESTROY(FAuthenticateForm); + if (!FKeepAuthenticateForm) + { + CloseAutheticateForm(); + } } else { @@ -1570,3 +1582,222 @@ TTerminalQueue * __fastcall TTerminalManager::FindQueueForTerminal(TTerminal * T int Index = IndexOf(Terminal); return reinterpret_cast(FQueues->Items[Index]); } +//--------------------------------------------------------------------------- +TRemoteFile * __fastcall TTerminalManager::CheckRights( + TTerminal * Terminal, const UnicodeString & EntryType, const UnicodeString & FileName, bool & WrongRights) +{ + std::unique_ptr FileOwner; + TRemoteFile * File; + try + { + Terminal->LogEvent(FORMAT(L"Checking %s \"%s\"...", (LowerCase(EntryType), FileName))); + Terminal->ReadFile(FileName, File); + FileOwner.reset(File); + int ForbiddenRights = TRights::rfGroupWrite | TRights::rfOtherWrite; + if ((File->Rights->Number & ForbiddenRights) != 0) + { + Terminal->LogEvent(FORMAT(L"%s \"%s\" exists, but has incorrect permissions %s.", (EntryType, FileName, File->Rights->Octal))); + WrongRights = true; + } + else + { + Terminal->LogEvent(FORMAT(L"%s \"%s\" exists and has correct permissions %s.", (EntryType, FileName, File->Rights->Octal))); + } + } + catch (Exception & E) + { + } + return FileOwner.release(); +} +//--------------------------------------------------------------------------- +bool __fastcall TTerminalManager::UploadPublicKey( + TTerminal * Terminal, TSessionData * Data, UnicodeString & FileName) +{ + std::unique_ptr OpenDialog(new TOpenDialog(Application)); + OpenDialog->Title = LoadStr(LOGIN_PUBLIC_KEY_TITLE); + OpenDialog->Filter = LoadStr(LOGIN_PUBLIC_KEY_FILTER); + OpenDialog->DefaultExt = PuttyKeyExt; + OpenDialog->FileName = FileName; + + bool Result = OpenDialog->Execute(); + if (Result) + { + Configuration->Usage->Inc(L"PublicKeyInstallation"); + FileName = OpenDialog->FileName; + + bool AutoReadDirectory; + bool ExceptionOnFail; + UnicodeString TemporaryDir; + + bool WrongRights = false; + const UnicodeString SshFolder = L".ssh"; + const UnicodeString AuthorizedKeysFile = L"authorized_keys"; + UnicodeString AuthorizedKeysFilePath = FORMAT(L"%s/%s", (SshFolder, AuthorizedKeysFile)); + + VerifyAndConvertKey(FileName, ssh2only, false); + + UnicodeString Comment; + UnicodeString Line = GetPublicKeyLine(FileName, Comment); + + bool AdHocTerminal = (Terminal == NULL); + std::unique_ptr TerminalOwner; + if (AdHocTerminal) + { + DebugAssert(Data != NULL); + + TAutoFlag KeepAuthenticateFormFlag(FKeepAuthenticateForm); + try + { + TerminalOwner.reset(CreateTerminal(Data)); + Terminal = TerminalOwner.get(); + SetupTerminal(Terminal); + Terminal->OnProgress = NULL; + Terminal->OnFinished = NULL; + DoConnectTerminal(Terminal, false, true); + } + catch (Exception & E) + { + CloseAutheticateForm(); + throw; + } + } + + AutoReadDirectory = Terminal->AutoReadDirectory; + ExceptionOnFail = Terminal->ExceptionOnFail; + + try + { + Terminal->AutoReadDirectory = false; + Terminal->ExceptionOnFail = true; + + UnicodeString SshImplementation = Terminal->GetSessionInfo().SshImplementation; + UnicodeString NotOpenSSHMessage = FMTLOAD(LOGIN_NOT_OPENSSH, (SshImplementation)); + if (IsOpenSSH(SshImplementation) || + (MessageDialog(NotOpenSSHMessage, qtConfirmation, qaOK | qaCancel, HELP_LOGIN_AUTHORIZED_KEYS) == qaOK)) + { + Terminal->Log->AddSeparator(); + Terminal->LogEvent(FORMAT(L"Adding public key line to \"%s\" file:\n%s", (AuthorizedKeysFilePath, Line))); + + // Ad-hoc terminal + if (FAuthenticateForm != NULL) + { + FAuthenticateForm->Log(FMTLOAD(LOGIN_PUBLIC_KEY_UPLOAD, (Comment))); + } + + UnicodeString SshFolderAbsolutePath = UnixIncludeTrailingBackslash(Terminal->GetHomeDirectory()) + SshFolder; + std::unique_ptr SshFolderFile(CheckRights(Terminal, L"Folder", SshFolderAbsolutePath, WrongRights)); + if (SshFolderFile.get() == NULL) + { + TRights SshFolderRights; + SshFolderRights.Number = TRights::rfUserRead | TRights::rfUserWrite | TRights::rfUserExec; + TRemoteProperties SshFolderProperties; + SshFolderProperties.Rights = SshFolderRights; + SshFolderProperties.Valid = TValidProperties() << vpRights; + + Terminal->LogEvent(FORMAT(L"Trying to create \"%s\" folder with permissions %s...", (SshFolder, SshFolderRights.Octal))); + Terminal->CreateDirectory(SshFolderAbsolutePath, &SshFolderProperties); + } + + TemporaryDir = ExcludeTrailingBackslash(WinConfiguration->TemporaryDir()); + if (!ForceDirectories(ApiPath(TemporaryDir))) + { + throw EOSExtException(FMTLOAD(CREATE_TEMP_DIR_ERROR, (TemporaryDir))); + } + UnicodeString TemporaryAuthorizedKeysFile = IncludeTrailingBackslash(TemporaryDir) + AuthorizedKeysFile; + + UnicodeString AuthorizedKeysFileAbsolutePath = UnixIncludeTrailingBackslash(SshFolderAbsolutePath) + AuthorizedKeysFile; + + bool Updated = true; + TCopyParamType CopyParam; // Use factory defaults + CopyParam.ResumeSupport = rsOff; // not to break the permissions + CopyParam.PreserveTime = false; // not needed + + UnicodeString AuthorizedKeys; + std::unique_ptr AuthorizedKeysFileFile(CheckRights(Terminal, L"File", AuthorizedKeysFileAbsolutePath, WrongRights)); + if (AuthorizedKeysFileFile.get() != NULL) + { + AuthorizedKeysFileFile->FullFileName = AuthorizedKeysFileAbsolutePath; + std::unique_ptr Files(new TStringList()); + Files->AddObject(AuthorizedKeysFileAbsolutePath, AuthorizedKeysFileFile.get()); + Terminal->LogEvent(FORMAT(L"Downloading current \"%s\" file...", (AuthorizedKeysFile))); + Terminal->CopyToLocal(Files.get(), TemporaryDir, &CopyParam, cpNoConfirmation, NULL); + // Overload with Encoding parameter work incorrectly, when used on a file without BOM + AuthorizedKeys = TFile::ReadAllText(TemporaryAuthorizedKeysFile); + + std::unique_ptr AuthorizedKeysLines(TextToStringList(AuthorizedKeys)); + int P = Line.Pos(L" "); + if (DebugAlwaysTrue(P > 0)) + { + P = PosEx(L" ", Line, P + 1); + } + UnicodeString Prefix = Line.SubString(1, P); // including the space + for (int Index = 0; Index < AuthorizedKeysLines->Count; Index++) + { + if (StartsStr(Prefix, AuthorizedKeysLines->Strings[Index])) + { + Terminal->LogEvent(FORMAT(L"\"%s\" file already contains public key line:\n%s", (AuthorizedKeysFile, AuthorizedKeysLines->Strings[Index]))); + Updated = false; + } + } + + if (Updated) + { + Terminal->LogEvent(FORMAT(L"\"%s\" file does not contain the public key line yet.", (AuthorizedKeysFile))); + if (!EndsStr(L"\n", AuthorizedKeys)) + { + Terminal->LogEvent(FORMAT(L"Adding missing trailing new line to \"%s\" file...", (AuthorizedKeysFile))); + AuthorizedKeys += L"\n"; + } + } + } + else + { + Terminal->LogEvent(FORMAT(L"Creating new \"%s\" file...", (AuthorizedKeysFile))); + CopyParam.PreserveRights = true; + CopyParam.Rights.Number = TRights::rfUserRead | TRights::rfUserWrite; + } + + if (Updated) + { + AuthorizedKeys += Line + L"\n"; + // Overload without Encoding parameter uses TEncoding::UTF8, but does not write BOM, what we want + TFile::WriteAllText(TemporaryAuthorizedKeysFile, AuthorizedKeys); + std::unique_ptr Files(new TStringList()); + Files->Add(TemporaryAuthorizedKeysFile); + Terminal->LogEvent(FORMAT(L"Uploading updated \"%s\" file...", (AuthorizedKeysFile))); + Terminal->CopyToRemote(Files.get(), SshFolderAbsolutePath, &CopyParam, cpNoConfirmation, NULL); + } + } + } + __finally + { + Terminal->AutoReadDirectory = AutoReadDirectory; + Terminal->ExceptionOnFail = ExceptionOnFail; + if (!TemporaryDir.IsEmpty()) + { + RecursiveDeleteFile(ExcludeTrailingBackslash(TemporaryDir), false); + } + CloseAutheticateForm(); // When uploading from Login dialog + } + + Terminal->LogEvent(L"Public key installation done."); + if (AdHocTerminal) + { + TerminalOwner.reset(NULL); + } + else + { + Terminal->Log->AddSeparator(); + } + + UnicodeString Message = FMTLOAD(LOGIN_PUBLIC_KEY_UPLOADED, (Comment)); + if (WrongRights) + { + Message += L"\n\n" + FMTLOAD(LOGIN_PUBLIC_KEY_PERMISSIONS, (AuthorizedKeysFilePath)); + } + + MessageDialog(Message, qtInformation, qaOK, HELP_LOGIN_AUTHORIZED_KEYS); + } + + return Result; +} diff --git a/source/windows/TerminalManager.h b/source/windows/TerminalManager.h index 6d913594a..d7964954f 100644 --- a/source/windows/TerminalManager.h +++ b/source/windows/TerminalManager.h @@ -63,7 +63,7 @@ class TTerminalManager : public TTerminalList TTerminal * __fastcall FindActiveTerminalForSite(TSessionData * Data); TTerminalQueue * __fastcall FindQueueForTerminal(TTerminal * Terminal); void __fastcall UpdateSessionCredentials(TSessionData * Data); - void __fastcall DoConnectTerminal(TTerminal * Terminal, bool Reopen); + bool __fastcall UploadPublicKey(TTerminal * Terminal, TSessionData * Data, UnicodeString & FileName); __property TCustomScpExplorerForm * ScpExplorer = { read = FScpExplorer, write = SetScpExplorer }; __property TTerminal * ActiveTerminal = { read = FActiveTerminal, write = SetActiveTerminal }; @@ -76,6 +76,7 @@ class TTerminalManager : public TTerminalList protected: virtual TTerminal * __fastcall CreateTerminal(TSessionData * Data); + void __fastcall DoConnectTerminal(TTerminal * Terminal, bool Reopen, bool AdHoc); private: static TTerminalManager * FInstance; @@ -104,6 +105,7 @@ class TTerminalManager : public TTerminalList void * FBusyToken; bool FAuthenticationCancelled; std::unique_ptr FApplicationsEvents; + bool FKeepAuthenticateForm; bool __fastcall ConnectActiveTerminalImpl(bool Reopen); bool __fastcall ConnectActiveTerminal(); @@ -166,6 +168,9 @@ class TTerminalManager : public TTerminalList void __fastcall DoConfigurationChange(); bool __fastcall ShouldDisplayQueueStatusOnAppTitle(); void __fastcall SetupTerminal(TTerminal * Terminal); + void __fastcall CloseAutheticateForm(); + TRemoteFile * __fastcall CheckRights( + TTerminal * Terminal, const UnicodeString & EntryType, const UnicodeString & FileName, bool & WrongRights); }; //--------------------------------------------------------------------------- #endif diff --git a/source/windows/Tools.cpp b/source/windows/Tools.cpp index 9d17a65a7..328a95448 100644 --- a/source/windows/Tools.cpp +++ b/source/windows/Tools.cpp @@ -1162,7 +1162,7 @@ static void __fastcall ConvertKey(UnicodeString & FileName, TKeyType Type) } //--------------------------------------------------------------------------- static void __fastcall DoVerifyKey( - UnicodeString & FileName, TSshProt SshProt, bool Convert) + UnicodeString & FileName, TSshProt SshProt, bool Convert, bool CanIgnore) { if (!FileName.Trim().IsEmpty()) { @@ -1242,8 +1242,8 @@ static void __fastcall DoVerifyKey( if (!Message.IsEmpty()) { Configuration->Usage->Inc(L"PrivateKeySelectErrors"); - if (MoreMessageDialog(Message, MoreMessages.get(), qtWarning, qaIgnore | qaAbort, - HelpKeyword) == qaAbort) + unsigned int Answers = (CanIgnore ? (qaIgnore | qaAbort) : qaOK); + if (MoreMessageDialog(Message, MoreMessages.get(), qtWarning, Answers, HelpKeyword) != qaIgnore) { Abort(); } @@ -1251,14 +1251,14 @@ static void __fastcall DoVerifyKey( } } //--------------------------------------------------------------------------- -void __fastcall VerifyAndConvertKey(UnicodeString & FileName, TSshProt SshProt) +void __fastcall VerifyAndConvertKey(UnicodeString & FileName, TSshProt SshProt, bool CanIgnore) { - DoVerifyKey(FileName, SshProt, true); + DoVerifyKey(FileName, SshProt, true, CanIgnore); } //--------------------------------------------------------------------------- void __fastcall VerifyKey(UnicodeString FileName, TSshProt SshProt) { - DoVerifyKey(FileName, SshProt, false); + DoVerifyKey(FileName, SshProt, false, true); } //--------------------------------------------------------------------------- void __fastcall VerifyCertificate(const UnicodeString & FileName) diff --git a/source/windows/Tools.h b/source/windows/Tools.h index 4580b3c50..d24ab422e 100644 --- a/source/windows/Tools.h +++ b/source/windows/Tools.h @@ -68,7 +68,7 @@ void __fastcall CopyToClipboard(TStrings * Strings); void __fastcall ShutDownWindows(); void __fastcall SuspendWindows(); void __fastcall EditSelectBaseName(HWND Edit); -void __fastcall VerifyAndConvertKey(UnicodeString & FileName, TSshProt SshProt); +void __fastcall VerifyAndConvertKey(UnicodeString & FileName, TSshProt SshProt, bool CanIgnore); void __fastcall VerifyKey(UnicodeString FileName, TSshProt SshProt); void __fastcall VerifyCertificate(const UnicodeString & FileName); TStrings * __fastcall GetUnwrappedMemoLines(TMemo * Memo);