浏览代码

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
Martin Prikryl 7 年之前
父节点
当前提交
64f634db37

+ 1 - 0
source/core/FileSystems.h

@@ -81,6 +81,7 @@ public:
     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;

+ 53 - 0
source/core/PuttyIntf.cpp

@@ -10,6 +10,7 @@
 #include "CoreMain.h"
 #include "TextsCore.h"
 #include <StrUtils.hpp>
+#include <Soap.EncdDecd.hpp>
 //---------------------------------------------------------------------------
 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<char *>(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);
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 3 - 0
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

+ 1 - 4
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;
   }

+ 1 - 1
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))
   {

+ 1 - 0
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

+ 1 - 1
source/core/SftpFileSystem.h

@@ -72,6 +72,7 @@ public:
     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 @@ protected:
     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,

+ 5 - 0
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);

+ 1 - 0
source/core/Terminal.h

@@ -500,6 +500,7 @@ public:
   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,

+ 13 - 0
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);

+ 2 - 0
source/forms/CustomScpExplorer.h

@@ -717,6 +717,8 @@ public:
   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 };

+ 2 - 0
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))

+ 7 - 0
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'

+ 1 - 0
source/forms/NonVisual.h

@@ -615,6 +615,7 @@ __published:    // IDE-managed Components
   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);

+ 3 - 0
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

+ 1 - 0
source/forms/ScpCommander.h

@@ -423,6 +423,7 @@ __published:
   TTBXItem *TBXItem248;
   TTBXItem *TBXItem249;
   TTBXItem *TBXItem250;
+  TTBXItem *TBXItem76;
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
     bool &Accept);

+ 3 - 0
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

+ 1 - 0
source/forms/ScpExplorer.h

@@ -313,6 +313,7 @@ __published:
   TTBXItem *TBXItem247;
   TTBXItem *TBXItem244;
   TTBXItem *TBXItem246;
+  TTBXItem *TBXItem14;
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
           const TStatusFileInfo &FileInfo);
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,

+ 55 - 7
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<TFilenameEdit *>(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<TStrings> 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);
+}
+//---------------------------------------------------------------------------

+ 25 - 5
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

+ 9 - 2
source/forms/SiteAdvanced.h

@@ -257,7 +257,11 @@ __published:
   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 @@ __published:
   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:

+ 1 - 0
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

+ 7 - 0
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
 

+ 7 - 0
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"

+ 249 - 18
source/windows/TerminalManager.cpp

@@ -18,6 +18,10 @@
 #include <Exceptions.h>
 #include <VCLCommon.h>
 #include <WinApi.h>
+#include <PuttyTools.h>
+#include <HelpWin.h>
+#include <System.IOUtils.hpp>
+#include <StrUtils.hpp>
 //---------------------------------------------------------------------------
 #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<TManagedTerminal *>(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<TTerminalQueue *>(FQueues->Items[Index]);
 }
+//---------------------------------------------------------------------------
+TRemoteFile * __fastcall TTerminalManager::CheckRights(
+  TTerminal * Terminal, const UnicodeString & EntryType, const UnicodeString & FileName, bool & WrongRights)
+{
+  std::unique_ptr<TRemoteFile> 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<TOpenDialog> 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<TTerminal> 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<TRemoteFile> 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<TRemoteFile> AuthorizedKeysFileFile(CheckRights(Terminal, L"File", AuthorizedKeysFileAbsolutePath, WrongRights));
+        if (AuthorizedKeysFileFile.get() != NULL)
+        {
+          AuthorizedKeysFileFile->FullFileName = AuthorizedKeysFileAbsolutePath;
+          std::unique_ptr<TStrings> 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<TStrings> 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<TStrings> 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;
+}

+ 6 - 1
source/windows/TerminalManager.h

@@ -63,7 +63,7 @@ public:
   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 @@ public:
 
 protected:
   virtual TTerminal * __fastcall CreateTerminal(TSessionData * Data);
+  void __fastcall DoConnectTerminal(TTerminal * Terminal, bool Reopen, bool AdHoc);
 
 private:
   static TTerminalManager * FInstance;
@@ -104,6 +105,7 @@ private:
   void * FBusyToken;
   bool FAuthenticationCancelled;
   std::unique_ptr<TApplicationEvents> FApplicationsEvents;
+  bool FKeepAuthenticateForm;
 
   bool __fastcall ConnectActiveTerminalImpl(bool Reopen);
   bool __fastcall ConnectActiveTerminal();
@@ -166,6 +168,9 @@ private:
   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

+ 6 - 6
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)

+ 1 - 1
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);