Răsfoiți Sursa

Bug 1798: Allow configuring additional PuTTY settings (e.g. terminal settings) to be used when opening a session in PuTTY

https://winscp.net/tracker/1798

Also fixes a build broken by eef27ac3 (and still not fixed by 5d189553)

Source commit: 0bd13d447e6da8427be1506b7c561d74e83b1b85
Martin Prikryl 6 ani în urmă
părinte
comite
46a9c8fed9

+ 6 - 1
source/core/Configuration.cpp

@@ -1225,9 +1225,14 @@ TStrings * __fastcall TConfiguration::GetOptionsStorage()
   return FOptionsStorage.get();
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall TConfiguration::GetPuttySessionsSubKey()
+{
+  return StoredSessionsSubKey;
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall TConfiguration::GetPuttySessionsKey()
 {
-  return PuttyRegistryStorageKey + L"\\Sessions";
+  return PuttyRegistryStorageKey + L"\\" + PuttySessionsSubKey;
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TConfiguration::GetStoredSessionsSubKey()

+ 2 - 0
source/core/Configuration.h

@@ -93,6 +93,7 @@ private:
   UnicodeString __fastcall GetFileVersion(TVSFixedFileInfo * Info);
   UnicodeString __fastcall GetStoredSessionsSubKey();
   UnicodeString __fastcall GetPuttySessionsKey();
+  UnicodeString __fastcall GetPuttySessionsSubKey();
   void __fastcall SetRandomSeedFile(UnicodeString value);
   UnicodeString __fastcall GetRandomSeedFileName();
   void __fastcall SetPuttyRegistryStorageKey(UnicodeString value);
@@ -270,6 +271,7 @@ public:
   __property UnicodeString StoredSessionsSubKey = {read=GetStoredSessionsSubKey};
   __property UnicodeString PuttyRegistryStorageKey  = { read=FPuttyRegistryStorageKey, write=SetPuttyRegistryStorageKey };
   __property UnicodeString PuttySessionsKey  = { read=GetPuttySessionsKey };
+  __property UnicodeString PuttySessionsSubKey  = { read=GetPuttySessionsSubKey };
   __property UnicodeString RandomSeedFile  = { read=FRandomSeedFile, write=SetRandomSeedFile };
   __property UnicodeString RandomSeedFileName  = { read=GetRandomSeedFileName };
   __property UnicodeString SshHostKeysSubKey  = { read=GetSshHostKeysSubKey };

+ 13 - 2
source/core/HierarchicalStorage.cpp

@@ -26,7 +26,7 @@ UnicodeString __fastcall MungeStr(const UnicodeString & Str, bool ForceAnsi, boo
   RawByteString Source;
   if (ForceAnsi)
   {
-    Source = RawByteString(AnsiString(Str));
+    Source = RawByteString(PuttyStr(Str));
   }
   else
   {
@@ -69,9 +69,14 @@ UnicodeString __fastcall UnMungeStr(const UnicodeString & Str)
   return Result;
 }
 //---------------------------------------------------------------------------
+AnsiString PuttyStr(const UnicodeString & Str)
+{
+  return AnsiString(Str);
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall PuttyMungeStr(const UnicodeString & Str)
 {
-  return MungeStr(Str, false, false);
+  return MungeStr(Str, true, false);
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall MungeIniName(const UnicodeString & Str)
@@ -150,6 +155,12 @@ UnicodeString __fastcall THierarchicalStorage::GetCurrentSubKey()
   return UnMungeStr(GetCurrentSubKeyMunged());
 }
 //---------------------------------------------------------------------------
+void __fastcall THierarchicalStorage::ConfigureForPutty()
+{
+  MungeStringValues = false;
+  ForceAnsi = true;
+}
+//---------------------------------------------------------------------------
 bool __fastcall THierarchicalStorage::OpenRootKey(bool CanCreate)
 {
   return OpenSubKey(UnicodeString(), CanCreate);

+ 2 - 0
source/core/HierarchicalStorage.h

@@ -15,6 +15,7 @@ friend class TCustomIniFileStorage;
 public:
   __fastcall THierarchicalStorage(const UnicodeString & AStorage);
   virtual __fastcall ~THierarchicalStorage();
+  void __fastcall ConfigureForPutty();
   virtual bool __fastcall OpenRootKey(bool CanCreate);
   virtual bool __fastcall OpenSubKey(const UnicodeString & SubKey, bool CanCreate);
   virtual void __fastcall CloseSubKey();
@@ -259,5 +260,6 @@ protected:
 };
 //---------------------------------------------------------------------------
 UnicodeString __fastcall PuttyMungeStr(const UnicodeString & Str);
+AnsiString PuttyStr(const UnicodeString & Str);
 //---------------------------------------------------------------------------
 #endif

+ 137 - 2
source/core/PuttyIntf.cpp

@@ -381,6 +381,12 @@ ScpSeat::ScpSeat(TSecureShell * ASecureShell)
   vt = &ScpSeatVtable;
 }
 //---------------------------------------------------------------------------
+static std::unique_ptr<TCriticalSection> PuttyRegistrySection(TraceInitPtr(new TCriticalSection()));
+enum TPuttyRegistryMode { prmPass, prmRedirect, prmCollect, prmFail };
+static TPuttyRegistryMode PuttyRegistryMode = prmRedirect;
+typedef std::map<UnicodeString, unsigned long> TPuttyRegistryTypes;
+TPuttyRegistryTypes PuttyRegistryTypes;
+//---------------------------------------------------------------------------
 static long OpenWinSCPKey(HKEY Key, const char * SubKey, HKEY * Result, bool CanCreate)
 {
   long R;
@@ -428,17 +434,53 @@ static long OpenWinSCPKey(HKEY Key, const char * SubKey, HKEY * Result, bool Can
 //---------------------------------------------------------------------------
 long reg_open_winscp_key(HKEY Key, const char * SubKey, HKEY * Result)
 {
+  if (PuttyRegistryMode == prmPass)
+  {
+    return RegOpenKeyA(Key, SubKey, Result);
+  }
+  else if (PuttyRegistryMode == prmCollect)
+  {
+    *Result = reinterpret_cast<HKEY>(1);
+    return ERROR_SUCCESS;
+  }
+  else if (PuttyRegistryMode == prmFail)
+  {
+    return ERROR_CANTOPEN;
+  }
+
+  DebugAssert(PuttyRegistryMode == prmRedirect);
   return OpenWinSCPKey(Key, SubKey, Result, false);
 }
 //---------------------------------------------------------------------------
 long reg_create_winscp_key(HKEY Key, const char * SubKey, HKEY * Result)
 {
+  if (PuttyRegistryMode == prmPass)
+  {
+    return RegCreateKeyA(Key, SubKey, Result);
+  }
+  else if (PuttyRegistryMode == prmCollect)
+  {
+    *Result = reinterpret_cast<HKEY>(1);
+    return ERROR_SUCCESS;
+  }
+
+  DebugAssert(PuttyRegistryMode == prmRedirect);
   return OpenWinSCPKey(Key, SubKey, Result, true);
 }
 //---------------------------------------------------------------------------
-long reg_query_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long * /*Reserved*/,
+long reg_query_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long * Reserved,
   unsigned long * Type, unsigned char * Data, unsigned long * DataSize)
 {
+  if (PuttyRegistryMode == prmPass)
+  {
+    return RegQueryValueExA(Key, ValueName, Reserved, Type, Data, DataSize);
+  }
+  else if (PuttyRegistryMode == prmCollect)
+  {
+    return ERROR_READ_FAULT;
+  }
+
+  DebugAssert(PuttyRegistryMode == prmRedirect);
   long R;
   DebugAssert(Configuration != NULL);
 
@@ -483,9 +525,19 @@ long reg_query_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long *
   return R;
 }
 //---------------------------------------------------------------------------
-long reg_set_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long /*Reserved*/,
+long reg_set_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long Reserved,
   unsigned long Type, const unsigned char * Data, unsigned long DataSize)
 {
+  if (PuttyRegistryMode == prmPass)
+  {
+    return RegSetValueExA(Key, ValueName, Reserved, Type, Data, DataSize);
+  }
+  else if (PuttyRegistryMode == prmCollect)
+  {
+    PuttyRegistryTypes[ValueName] = Type;
+    return ERROR_SUCCESS;
+  }
+  DebugAssert(PuttyRegistryMode == prmRedirect);
   DebugAssert(Configuration != NULL);
 
   DebugAssert(Type == REG_SZ);
@@ -503,6 +555,15 @@ long reg_set_winscp_value_ex(HKEY Key, const char * ValueName, unsigned long /*R
 //---------------------------------------------------------------------------
 long reg_close_winscp_key(HKEY Key)
 {
+  if (PuttyRegistryMode == prmPass)
+  {
+    return RegCloseKey(Key);
+  }
+  else if (PuttyRegistryMode == prmCollect)
+  {
+    return ERROR_SUCCESS;
+  }
+  DebugAssert(PuttyRegistryMode == prmRedirect);
   DebugAssert(Configuration != NULL);
 
   THierarchicalStorage * Storage = reinterpret_cast<THierarchicalStorage *>(Key);
@@ -999,4 +1060,78 @@ UnicodeString GetDecompressorName(const ssh_decompressor * Decompressor)
   return Result;
 }
 //---------------------------------------------------------------------------
+void WritePuttySettings(THierarchicalStorage * Storage, const UnicodeString & ASettings)
+{
+  if (PuttyRegistryTypes.empty())
+  {
+    TGuard Guard(PuttyRegistrySection.get());
+    TValueRestorer<TPuttyRegistryMode> PuttyRegistryModeRestorer(PuttyRegistryMode);
+    PuttyRegistryMode = prmCollect;
+    Conf * conf = conf_new();
+    try
+    {
+      do_defaults(NULL, conf);
+      save_settings(NULL, conf);
+    }
+    __finally
+    {
+      conf_free(conf);
+    }
+  }
+
+  std::unique_ptr<TStrings> Settings(new TStringList());
+  UnicodeString Buf = ASettings;
+  UnicodeString Setting;
+  while (CutToken(Buf, Setting))
+  {
+    Settings->Add(Setting);
+  }
+
+  for (int Index = 0; Index < Settings->Count; Index++)
+  {
+    UnicodeString Name = Settings->Names[Index];
+    TPuttyRegistryTypes::const_iterator IType = PuttyRegistryTypes.find(Name);
+    if (IType != PuttyRegistryTypes.end())
+    {
+      UnicodeString Value = Settings->ValueFromIndex[Index];
+      int I;
+      if (IType->second == REG_SZ)
+      {
+        Storage->WriteStringRaw(Name, Value);
+      }
+      else if (DebugAlwaysTrue(IType->second == REG_DWORD) &&
+               TryStrToInt(Value, I))
+      {
+        Storage->WriteInteger(Name, I);
+      }
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void PuttyDefaults(Conf * conf)
+{
+  TGuard Guard(PuttyRegistrySection.get());
+  TValueRestorer<TPuttyRegistryMode> PuttyRegistryModeRestorer(PuttyRegistryMode);
+  PuttyRegistryMode = prmFail;
+  do_defaults(NULL, conf);
+}
+//---------------------------------------------------------------------------
+void SavePuttyDefaults(const UnicodeString & Name)
+{
+  TGuard Guard(PuttyRegistrySection.get());
+  TValueRestorer<TPuttyRegistryMode> PuttyRegistryModeRestorer(PuttyRegistryMode);
+  PuttyRegistryMode = prmPass;
+  Conf * conf = conf_new();
+  try
+  {
+    PuttyDefaults(conf);
+    AnsiString PuttyName = PuttyStr(Name);
+    save_settings(PuttyName.c_str(), conf);
+  }
+  __finally
+  {
+    conf_free(conf);
+  }
+}
+//---------------------------------------------------------------------------
 //---------------------------------------------------------------------------

+ 1 - 0
source/core/PuttyIntf.h

@@ -30,6 +30,7 @@ extern "C"
 UnicodeString GetCipherName(const ssh_cipher * Cipher);
 UnicodeString GetCompressorName(const ssh_compressor * Compressor);
 UnicodeString GetDecompressorName(const ssh_decompressor * Decompressor);
+void PuttyDefaults(Conf * conf);
 //---------------------------------------------------------------------------
 class TSecureShell;
 struct ScpSeat : public Seat

+ 6 - 0
source/core/PuttyTools.h

@@ -45,4 +45,10 @@ TStrings * SshKexList();
 TStrings * SshHostKeyList();
 TStrings * SshMacList();
 //---------------------------------------------------------------------------
+class TSessionData;
+void SaveAsPutty(const UnicodeString & Name, TSessionData * Data);
+class THierarchicalStorage;
+void WritePuttySettings(THierarchicalStorage * Storage, const UnicodeString & Settings);
+void SavePuttyDefaults(const UnicodeString & Name);
+//---------------------------------------------------------------------------
 #endif

+ 1 - 1
source/core/SecureShell.cpp

@@ -166,7 +166,7 @@ Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
 {
   Conf * conf = conf_new();
 
-  do_defaults(NULL, conf);
+  PuttyDefaults(conf);
 
   // user-configurable settings
   conf_set_str(conf, CONF_host, AnsiString(Data->HostNameExpanded).c_str());

+ 18 - 0
source/core/SessionData.cpp

@@ -274,6 +274,8 @@ void __fastcall TSessionData::DefaultSettings()
 
   FtpProxyLogonType = 0; // none
 
+  PuttySettings = UnicodeString();
+
   CustomParam1 = L"";
   CustomParam2 = L"";
 }
@@ -445,6 +447,8 @@ void __fastcall TSessionData::NonPersistant()
   \
   PROPERTY_HANDLER(EncryptKey, F); \
   \
+  PROPERTY(PuttySettings); \
+  \
   PROPERTY(CustomParam1); \
   PROPERTY(CustomParam2);
 #define META_PROPERTIES \
@@ -846,6 +850,8 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
   Link = Storage->ReadString(L"Link", Link);
   NameOverride = Storage->ReadString(L"NameOverride", NameOverride);
 
+  PuttySettings = Storage->ReadString(L"PuttySettings", PuttySettings);
+
   CustomParam1 = Storage->ReadString(L"CustomParam1", CustomParam1);
   CustomParam2 = Storage->ReadString(L"CustomParam2", CustomParam2);
 
@@ -1181,6 +1187,8 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
     WRITE_DATA(String, Link);
     WRITE_DATA(String, NameOverride);
 
+    WRITE_DATA(String, PuttySettings);
+
     WRITE_DATA(String, CustomParam1);
     WRITE_DATA(String, CustomParam2);
   }
@@ -1191,6 +1199,11 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
   bool SaveAll = (Default == NULL) && DoNotEncryptPasswords && !PuttyExport;
 
   SavePasswords(Storage, PuttyExport, DoNotEncryptPasswords, SaveAll);
+
+  if (PuttyExport)
+  {
+    WritePuttySettings(Storage, PuttySettings);
+  }
 }
 //---------------------------------------------------------------------
 TStrings * __fastcall TSessionData::SaveToOptions(const TSessionData * Default, bool SaveName)
@@ -3743,6 +3756,11 @@ TAutoSwitch __fastcall TSessionData::GetBug(TSshBug Bug) const
   return FBugs[Bug];
 }
 //---------------------------------------------------------------------
+void __fastcall TSessionData::SetPuttySettings(UnicodeString value)
+{
+  SET_SESSION_PROPERTY(PuttySettings);
+}
+//---------------------------------------------------------------------
 void __fastcall TSessionData::SetCustomParam1(UnicodeString value)
 {
   SET_SESSION_PROPERTY(CustomParam1);

+ 3 - 0
source/core/SessionData.h

@@ -171,6 +171,7 @@ private:
   bool FProxyLocalhost;
   int FFtpProxyLogonType;
   TAutoSwitch FBugs[BUG_COUNT];
+  UnicodeString FPuttySettings;
   UnicodeString FCustomParam1;
   UnicodeString FCustomParam2;
   bool FResolveSymlinks;
@@ -343,6 +344,7 @@ private:
   void __fastcall SetBug(TSshBug Bug, TAutoSwitch value);
   TAutoSwitch __fastcall GetBug(TSshBug Bug) const;
   UnicodeString __fastcall GetSessionKey();
+  void __fastcall SetPuttySettings(UnicodeString value);
   void __fastcall SetCustomParam1(UnicodeString value);
   void __fastcall SetCustomParam2(UnicodeString value);
   void __fastcall SetResolveSymlinks(bool value);
@@ -604,6 +606,7 @@ public:
   __property bool ProxyLocalhost  = { read=FProxyLocalhost, write=SetProxyLocalhost };
   __property int FtpProxyLogonType  = { read=FFtpProxyLogonType, write=SetFtpProxyLogonType };
   __property TAutoSwitch Bug[TSshBug Bug]  = { read=GetBug, write=SetBug };
+  __property UnicodeString PuttySettings = { read = FPuttySettings, write = SetPuttySettings };
   __property UnicodeString CustomParam1 = { read = FCustomParam1, write = SetCustomParam1 };
   __property UnicodeString CustomParam2 = { read = FCustomParam2, write = SetCustomParam2 };
   __property UnicodeString SessionKey = { read = GetSessionKey };

+ 136 - 3
source/forms/SiteAdvanced.cpp

@@ -177,6 +177,8 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
 
     TrimVMSVersionsCheck->Checked = FSessionData->TrimVMSVersions;
 
+    PuttySettingsEdit->Text = FSessionData->PuttySettings;
+
     // Environment/Recycle bin page
     DeleteToRecycleBinCheck->Checked = FSessionData->DeleteToRecycleBin;
     OverwrittenToRecycleBinCheck->Checked = FSessionData->OverwrittenToRecycleBin;
@@ -418,6 +420,8 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
     FColor = (TColor)FSessionData->Color;
   }
 
+  EnableControl(PuttyGroup, !DoesSessionExistInPutty(FSessionData));
+
   UpdateControls();
 }
 //---------------------------------------------------------------------
@@ -554,6 +558,8 @@ void __fastcall TSiteAdvancedDialog::SaveSession(TSessionData * SessionData)
 
   SessionData->TrimVMSVersions = TrimVMSVersionsCheck->Checked;
 
+  SessionData->PuttySettings = PuttySettingsEdit->Text;
+
   // Environment/Recycle bin page
   SessionData->DeleteToRecycleBin = DeleteToRecycleBinCheck->Checked;
   SessionData->OverwrittenToRecycleBin = OverwrittenToRecycleBinCheck->Checked;
@@ -677,6 +683,14 @@ void __fastcall TSiteAdvancedDialog::SaveSession(TSessionData * SessionData)
   SessionData->Color = FColor;
 }
 //---------------------------------------------------------------------
+TSessionData * __fastcall TSiteAdvancedDialog::GetSessionData()
+{
+  std::unique_ptr<TSessionData> SessionData(new TSessionData(UnicodeString()));
+  SessionData->Assign(FSessionData);
+  SaveSession(SessionData.get());
+  return SessionData.release();
+}
+//---------------------------------------------------------------------
 void __fastcall TSiteAdvancedDialog::UpdateNavigationTree()
 {
   TTreeNode * ActiveNode = NULL;
@@ -1122,6 +1136,7 @@ void __fastcall TSiteAdvancedDialog::ChangePage(TTabSheet * Tab)
 void __fastcall TSiteAdvancedDialog::PageChanged()
 {
   FPrivateKeyMonitors.reset(NULL);
+  ClosePuttySettings();
 
   bool Found = false;
   if (PageControl->ActivePage)
@@ -1580,9 +1595,7 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyGenerateItemClick(TObject * /*Sen
 //---------------------------------------------------------------------------
 void __fastcall TSiteAdvancedDialog::PrivateKeyUploadItemClick(TObject * /*Sender*/)
 {
-  std::unique_ptr<TSessionData> SessionData(new TSessionData(UnicodeString()));
-  SessionData->Assign(FSessionData);
-  SaveSession(SessionData.get());
+  std::unique_ptr<TSessionData> SessionData(GetSessionData());
   SessionData->FSProtocol = fsSFTPonly; // no SCP fallback, as SCP does not implement GetHomeDirectory
   SessionData->RemoteDirectory = UnicodeString();
 
@@ -1670,3 +1683,123 @@ void __fastcall TSiteAdvancedDialog::EncryptKeyEditExit(TObject * /*Sender*/)
   }
 }
 //---------------------------------------------------------------------------
+void TSiteAdvancedDialog::SerializePuttyRegistry(const UnicodeString & Key, TStrings * Values)
+{
+  std::unique_ptr<TRegistry> Registry(new TRegistry());
+  if (DebugAlwaysTrue(Registry->OpenKeyReadOnly(Key)))
+  {
+    std::unique_ptr<TStrings> Names(new TStringList());
+    Registry->GetValueNames(Names.get());
+    for (int Index = 0; Index < Names->Count; Index++)
+    {
+      UnicodeString Name = Names->Strings[Index];
+      TRegDataType Type = Registry->GetDataType(Name);
+      UnicodeString Value;
+      if (Type == rdString)
+      {
+        Value = UnicodeString(PuttyStr(Registry->ReadString(Name)));
+      }
+      else if (DebugAlwaysTrue(Type == rdInteger))
+      {
+        Value = StrToInt(Registry->ReadInteger(Name));
+      }
+      SetStringValueEvenIfEmpty(Values, Name, Value);
+    }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::PuttySettingsTimer(TObject *)
+{
+  if (PuttySettingsEdit->Modified)
+  {
+    FPuttySettingsTimer->Enabled = false;
+  }
+  else
+  {
+    std::unique_ptr<TStrings> NewPuttyRegSettings(new TStringList());
+    SerializePuttyRegistry(GetPuttySiteKey(), NewPuttyRegSettings.get());
+
+    std::unique_ptr<TStrings> PuttySettings(new TStringList());
+    for (int NewIndex = 0; NewIndex < NewPuttyRegSettings->Count; NewIndex++)
+    {
+      UnicodeString Name = NewPuttyRegSettings->Names[NewIndex];
+      UnicodeString NewValue = NewPuttyRegSettings->ValueFromIndex[NewIndex];
+      int Index = -1; // shut up
+      // Ignoring values we do not know (from future versions of PuTTY),
+      // as we cannot tell if they are modified or not
+      if (((Index = FPuttyRegSettings->IndexOfName(Name)) >= 0) &&
+          (FPuttyRegSettings->ValueFromIndex[Index] != NewValue))
+      {
+        SetStringValueEvenIfEmpty(PuttySettings.get(), Name, NewValue);
+      }
+    }
+
+    PuttySettingsEdit->Text = StringsToParams(PuttySettings.get()).TrimLeft();
+    UpdateControls();
+  }
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TSiteAdvancedDialog::GetPuttySiteName()
+{
+  return FORMAT(L"(%s)", (FMTLOAD(PUTTY_SETTINGS_SITE_NAME, (FSessionData->SessionName))));
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall TSiteAdvancedDialog::GetPuttySiteKey()
+{
+  return OriginalPuttyRegistryStorageKey + L"\\" + Configuration->PuttySessionsSubKey + L"\\" + PuttyMungeStr(GetPuttySiteName());
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::ClosePuttySettings()
+{
+  if (FPuttySettingsTimer.get() != NULL)
+  {
+    FPuttySettingsTimer.reset(NULL);
+    std::unique_ptr<TRegistry> Registry(new TRegistry());
+    Registry->DeleteKey(GetPuttySiteKey());
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::PuttySettingsButtonClick(TObject *)
+{
+  ClosePuttySettings();
+  UnicodeString SiteName = GetPuttySiteName();
+
+  MessageDialog(FMTLOAD(PUTTY_SETTINGS_INSTRUCTIONS, (SiteName, SiteName)), qtInformation, qaOK, HELP_PUTTY_SETTINGS);
+
+  PuttySettingsEdit->Modified = false;
+  std::unique_ptr<TSessionData> SessionData(GetSessionData());
+  UnicodeString PuttySettings = SessionData->PuttySettings;
+  SessionData->PuttySettings = UnicodeString();
+  SavePuttyDefaults(SiteName);
+  ExportSessionToPutty(SessionData.get(), false, SiteName);
+
+  UnicodeString PuttySiteKey = GetPuttySiteKey();
+  FPuttyRegSettings.reset(new TStringList());
+  SerializePuttyRegistry(PuttySiteKey, FPuttyRegSettings.get());
+
+  SessionData->PuttySettings = PuttySettings;
+  ExportSessionToPutty(SessionData.get(), false, SiteName);
+
+  UnicodeString Program, Params, Dir;
+  SplitCommand(GUIConfiguration->PuttyPath, Program, Params, Dir);
+  Program = ExpandEnvironmentVariables(Program);
+  if (!FindFile(Program))
+  {
+    throw Exception(FMTLOAD(EXECUTE_APP_ERROR, (Program)));
+  }
+
+  ExecuteShellChecked(Program, L"");
+
+  FPuttySettingsTimer.reset(new TTimer(this));
+  FPuttySettingsTimer->OnTimer = PuttySettingsTimer;
+  FPuttySettingsTimer->Interval = MSecsPerSec;
+  FPuttySettingsTimer->Enabled = true;
+
+  UpdateControls();
+}
+//---------------------------------------------------------------------------
+void __fastcall TSiteAdvancedDialog::FormClose(TObject *, TCloseAction &)
+{
+  ClosePuttySettings();
+}
+//---------------------------------------------------------------------------

+ 43 - 1
source/forms/SiteAdvanced.dfm

@@ -12,6 +12,7 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
   ParentFont = True
   OldCreateOrder = True
   Position = poOwnerFormCenter
+  OnClose = FormClose
   OnCloseQuery = FormCloseQuery
   OnShow = FormShow
   DesignSize = (
@@ -67,7 +68,7 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
             Top = 20
             Width = 241
             Height = 13
-            Caption = '&End-of-line characters (if not indicated by server):'
+            Caption = 'End-of-line &characters (if not indicated by server):'
             FocusControl = EOLTypeCombo
           end
           object UtfLabel: TLabel
@@ -213,6 +214,47 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
             OnClick = DataChange
           end
         end
+        object PuttyGroup: TGroupBox
+          Left = 0
+          Top = 252
+          Width = 393
+          Height = 98
+          Anchors = [akLeft, akTop, akRight]
+          Caption = 'PuTTY'
+          TabOrder = 2
+          DesignSize = (
+            393
+            98)
+          object PuttySettingsLabel: TLabel
+            Left = 12
+            Top = 18
+            Width = 116
+            Height = 13
+            Caption = '&PuTTY terminal settings:'
+            FocusControl = EncryptKeyPasswordEdit
+          end
+          object PuttySettingsButton: TButton
+            Left = 12
+            Top = 61
+            Width = 125
+            Height = 25
+            Anchors = [akTop, akRight]
+            Caption = '&Edit in PuTTY...'
+            TabOrder = 1
+            OnClick = PuttySettingsButtonClick
+          end
+          object PuttySettingsEdit: TEdit
+            Left = 12
+            Top = 34
+            Width = 370
+            Height = 21
+            MaxLength = 64
+            TabOrder = 0
+            Text = 'PuttySettingsEdit'
+            OnChange = DataChange
+            OnExit = EncryptKeyEditExit
+          end
+        end
       end
       object DirectoriesSheet: TTabSheet
         Tag = 2

+ 14 - 0
source/forms/SiteAdvanced.h

@@ -276,6 +276,10 @@ __published:
   TComboBox *S3DefaultReqionCombo;
   TLabel *S3UrlStyleLabel;
   TComboBox *S3UrlStyleCombo;
+  TGroupBox *PuttyGroup;
+  TButton *PuttySettingsButton;
+  TLabel *PuttySettingsLabel;
+  TEdit *PuttySettingsEdit;
   void __fastcall DataChange(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall PageControlChange(TObject *Sender);
@@ -315,6 +319,8 @@ __published:
   void __fastcall ShowEncryptionKeyCheckClick(TObject *Sender);
   void __fastcall GenerateKeyButtonClick(TObject *Sender);
   void __fastcall EncryptKeyEditExit(TObject *Sender);
+  void __fastcall PuttySettingsButtonClick(TObject *Sender);
+  void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
 
 
 public:
@@ -340,9 +346,12 @@ private:
   TColor FColor;
   std::unique_ptr<TPopupMenu> FColorPopupMenu;
   std::unique_ptr<TObjectList> FPrivateKeyMonitors;
+  std::unique_ptr<TStrings> FPuttyRegSettings;
+  std::unique_ptr<TTimer> FPuttySettingsTimer;
 
   void __fastcall LoadSession();
   void __fastcall UpdateControls();
+  TSessionData * __fastcall GetSessionData();
   void __fastcall SaveSession(TSessionData * SessionData);
   void __fastcall CMDialogKey(TWMKeyDown & Message);
   void __fastcall WMHelp(TWMHelp & Message);
@@ -360,6 +369,11 @@ private:
   bool __fastcall IsNeon(TFSProtocol FSProtocol);
   void __fastcall PrivateKeyCreatedOrModified(TObject * Sender, const UnicodeString FileName);
   TCustomEdit * __fastcall GetEncryptKeyEdit(bool AShow = true);
+  void __fastcall PuttySettingsTimer(TObject * Sender);
+  UnicodeString __fastcall GetPuttySiteName();
+  UnicodeString __fastcall GetPuttySiteKey();
+  void __fastcall ClosePuttySettings();
+  void SerializePuttyRegistry(const UnicodeString & Key, TStrings * Values);
 
   INTERFACE_HOOK;
 };

+ 1 - 0
source/resource/HelpWin.h

@@ -66,5 +66,6 @@
 #define HELP_FILE_COLORS             "ui_file_color"
 #define HELP_AUTOMATIC_UPDATE        "updates#automatic_upgrade"
 #define HELP_SITE_RAW                "ui_login_raw"
+#define HELP_PUTTY_SETTINGS          "ui_login_environment#putty"
 
 #endif // TextsWin

+ 2 - 0
source/resource/TextsWin.h

@@ -294,6 +294,8 @@
 #define USAGE_PASSPHRASE        1584
 #define AUTO_WORKSPACE_ENABLE   1585
 #define USAGE_BROWSE            1586
+#define PUTTY_SETTINGS_INSTRUCTIONS 1587
+#define PUTTY_SETTINGS_SITE_NAME 1588
 
 #define WIN_FORMS_STRINGS       1600
 #define COPY_FILE               1605

+ 2 - 0
source/resource/TextsWin1.rc

@@ -299,6 +299,8 @@ BEGIN
         USAGE_PASSPHRASE, "A passphrase for an encrypted private key or a client certificate"
         AUTO_WORKSPACE_ENABLE, "Press 'No' to enable automatic saving of the workspace."
         USAGE_BROWSE, "Selects the specified file in file panel(s)."
+        PUTTY_SETTINGS_INSTRUCTIONS, "**Edit terminal settings in PuTTY.**\n\nPuTTY will be started. Edit terminal settings of a temporary site %s. WinSCP will remember these settings after you close PuTTY."
+        PUTTY_SETTINGS_SITE_NAME, "Terminal settings for %s"
 
         WIN_FORMS_STRINGS, "WIN_FORMS_STRINGS"
         COPY_FILE, "%s file '%s' to %s:"

+ 65 - 58
source/windows/GUITools.cpp

@@ -77,9 +77,64 @@ bool __fastcall FindFile(UnicodeString & Path)
   return Result;
 }
 //---------------------------------------------------------------------------
+bool DoesSessionExistInPutty(TSessionData * SessionData)
+{
+  std::unique_ptr<TRegistryStorage> Storage(new TRegistryStorage(Configuration->PuttySessionsKey));
+  Storage->ConfigureForPutty();
+  return Storage->OpenRootKey(true) && Storage->KeyExists(SessionData->StorageKey);
+}
+//---------------------------------------------------------------------------
+bool __fastcall ExportSessionToPutty(TSessionData * SessionData, bool ReuseExisting, const UnicodeString & SessionName)
+{
+  bool Result = true;
+  std::unique_ptr<TRegistryStorage> Storage(new TRegistryStorage(Configuration->PuttySessionsKey));
+  Storage->AccessMode = smReadWrite;
+  Storage->ConfigureForPutty();
+  if (Storage->OpenRootKey(true))
+  {
+    Result = ReuseExisting && Storage->KeyExists(SessionData->StorageKey);
+    if (!Result)
+    {
+      std::unique_ptr<TRegistryStorage> SourceStorage(new TRegistryStorage(Configuration->PuttySessionsKey));
+      SourceStorage->ConfigureForPutty();
+      if (SourceStorage->OpenSubKey(StoredSessions->DefaultSettings->Name, false) &&
+          Storage->OpenSubKey(SessionName, true))
+      {
+        Storage->Copy(SourceStorage.get());
+        Storage->CloseSubKey();
+      }
+
+      std::unique_ptr<TSessionData> ExportData(new TSessionData(L""));
+      ExportData->Assign(SessionData);
+      ExportData->Modified = true;
+      ExportData->Name = SessionName;
+      ExportData->WinTitle = SessionData->SessionName;
+      ExportData->Password = L"";
+
+      if (SessionData->FSProtocol == fsFTP)
+      {
+        if (GUIConfiguration->TelnetForFtpInPutty)
+        {
+          ExportData->PuttyProtocol = PuttyTelnetProtocol;
+          ExportData->PortNumber = TelnetPortNumber;
+        }
+        else
+        {
+          ExportData->PuttyProtocol = PuttySshProtocol;
+          ExportData->PortNumber = SshPortNumber;
+        }
+      }
+
+      ExportData->Save(Storage.get(), true);
+    }
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall OpenSessionInPutty(const UnicodeString PuttyPath,
   TSessionData * SessionData)
 {
+  // See also TSiteAdvancedDialog::PuttySettingsButtonClick
   UnicodeString Program, AParams, Dir;
   SplitCommand(PuttyPath, Program, AParams, Dir);
   Program = ExpandEnvironmentVariables(Program);
@@ -177,67 +232,19 @@ void __fastcall OpenSessionInPutty(const UnicodeString PuttyPath,
       else
       {
         UnicodeString SessionName;
-        TRegistryStorage * Storage = NULL;
-        TSessionData * ExportData = NULL;
-        TRegistryStorage * SourceStorage = NULL;
-        try
+        if (ExportSessionToPutty(SessionData, true, GUIConfiguration->PuttySession))
         {
-          Storage = new TRegistryStorage(Configuration->PuttySessionsKey);
-          Storage->AccessMode = smReadWrite;
-          // make it compatible with putty
-          Storage->MungeStringValues = false;
-          Storage->ForceAnsi = true;
-          if (Storage->OpenRootKey(true))
-          {
-            if (Storage->KeyExists(SessionData->StorageKey))
-            {
-              SessionName = SessionData->SessionName;
-            }
-            else
-            {
-              SourceStorage = new TRegistryStorage(Configuration->PuttySessionsKey);
-              SourceStorage->MungeStringValues = false;
-              SourceStorage->ForceAnsi = true;
-              if (SourceStorage->OpenSubKey(StoredSessions->DefaultSettings->Name, false) &&
-                  Storage->OpenSubKey(GUIConfiguration->PuttySession, true))
-              {
-                Storage->Copy(SourceStorage);
-                Storage->CloseSubKey();
-              }
-
-              ExportData = new TSessionData(L"");
-              ExportData->Assign(SessionData);
-              ExportData->Modified = true;
-              ExportData->Name = GUIConfiguration->PuttySession;
-              ExportData->WinTitle = SessionData->SessionName;
-              ExportData->Password = L"";
-
-              if (SessionData->FSProtocol == fsFTP)
-              {
-                if (GUIConfiguration->TelnetForFtpInPutty)
-                {
-                  ExportData->PuttyProtocol = PuttyTelnetProtocol;
-                  ExportData->PortNumber = TelnetPortNumber;
-                  // PuTTY  does not allow -pw for telnet
-                  Password = L"";
-                }
-                else
-                {
-                  ExportData->PuttyProtocol = PuttySshProtocol;
-                  ExportData->PortNumber = SshPortNumber;
-                }
-              }
-
-              ExportData->Save(Storage, true);
-              SessionName = GUIConfiguration->PuttySession;
-            }
-          }
+          SessionName = SessionData->SessionName;
         }
-        __finally
+        else
         {
-          delete Storage;
-          delete ExportData;
-          delete SourceStorage;
+          SessionName = GUIConfiguration->PuttySession;
+          if ((SessionData->FSProtocol == fsFTP) &&
+              GUIConfiguration->TelnetForFtpInPutty)
+          {
+            // PuTTY  does not allow -pw for telnet
+            Password = L"";
+          }
         }
 
         UnicodeString LoadSwitch = L"-load";

+ 2 - 0
source/windows/GUITools.h

@@ -22,6 +22,8 @@ void __fastcall ExecuteShellCheckedAndWait(const UnicodeString Command, TProcess
 TObjectList * StartCreationDirectoryMonitorsOnEachDrive(unsigned int Filter, TFileChangedEvent OnChanged);
 extern bool DontCopyCommandToClipboard;
 bool __fastcall CopyCommandToClipboard(const UnicodeString & Command);
+bool DoesSessionExistInPutty(TSessionData * SessionData);
+bool __fastcall ExportSessionToPutty(TSessionData * SessionData, bool ReuseExisting, const UnicodeString & SessionName);
 void __fastcall OpenSessionInPutty(const UnicodeString PuttyPath,
   TSessionData * SessionData);
 bool __fastcall SpecialFolderLocation(int PathID, UnicodeString & Path);