Browse Source

SSH password change + When opening secondary shell session fails, the main session is not Closed + Session.ExecuteCommand unit tests

ChangePassword + PasswordChangesFailed counters
task_change_password help keyword

Source commit: 335f4de0796d0367a8ed427790cf1407607cc0d8
Martin Prikryl 9 years ago
parent
commit
0ccd2defd3

+ 2 - 2
dotnet/GlobalSuppressions.cs

@@ -148,7 +148,7 @@ using System.Diagnostics.CodeAnalysis;
 [assembly: SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "WinSCP.Session.#TestHandlesClosedInternal")]
 [assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess.#TryCreateEvent(System.String,System.Threading.EventWaitHandle&)")]
 [assembly: SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Scope = "member", Target = "WinSCP.ExeSessionProcess.#TryCreateEvent(System.String,System.Threading.EventWaitHandle&)")]
-[assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.SessionOptions.#GetPassword()")]
+[assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.SessionOptions.#GetPassword(System.Security.SecureString)")]
 [assembly: SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Scope = "member", Target = "WinSCP.SessionOptions.#ParseUrl(System.String)")]
 [assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess+NoopSafeHandle.#.ctor(System.IntPtr)")]
 [assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess.#CreateFileMapping(System.String)")]
@@ -158,6 +158,6 @@ using System.Diagnostics.CodeAnalysis;
 [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "WinSCP.ExeSessionProcess.#CreateFileMapping(System.String)")]
 [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "WinSCP.Logger.#CreateCounters()")]
 [assembly: SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "WinSCP.SessionLogReader.#LogContents()")]
-[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "WinSCP.Session.#SessionOptionsToSwitches(WinSCP.SessionOptions,System.Boolean)")]
+[assembly: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "WinSCP.Session.#SessionOptionsToSwitches(WinSCP.SessionOptions,System.Boolean,System.String&,System.String&)")]
 [assembly: SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Scope = "member", Target = "WinSCP.UnsafeNativeMethods.#RegGetValue(System.UIntPtr,System.String,System.String,WinSCP.RegistryFlags,WinSCP.RegistryType&,System.IntPtr,System.UInt32&)")]
 [assembly: SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "WinSCP.ExeSessionProcess.#GetInstallationPath(Microsoft.Win32.RegistryHive)")]

+ 22 - 4
dotnet/Session.cs

@@ -1463,22 +1463,27 @@ namespace WinSCP
                 url += tail;
                 logUrl += tail;
 
-                string arguments = SessionOptionsToSwitches(sessionOptions, scanFingerprint);
+                string arguments;
+                string logArguments;
+                SessionOptionsToSwitches(sessionOptions, scanFingerprint, out arguments, out logArguments);
 
+                const string switchName = "-rawsettings";
                 Tools.AddRawParameters(ref arguments, sessionOptions.RawSettings, "-rawsettings");
+                Tools.AddRawParameters(ref logArguments, sessionOptions.RawSettings, switchName);
 
                 if (!string.IsNullOrEmpty(arguments))
                 {
                     arguments = " " + arguments;
+                    logArguments = " " + logArguments;
                 }
 
                 // Switches should (and particularly the -rawsettings MUST) come after the URL
                 command = "\"" + Tools.ArgumentEscape(url) + "\"" + arguments;
-                log = "\"" + Tools.ArgumentEscape(logUrl) + "\"" + arguments;
+                log = "\"" + Tools.ArgumentEscape(logUrl) + "\"" + logArguments;
             }
         }
 
-        private string SessionOptionsToSwitches(SessionOptions sessionOptions, bool scanFingerprint)
+        private void SessionOptionsToSwitches(SessionOptions sessionOptions, bool scanFingerprint, out string arguments, out string logArguments)
         {
             using (Logger.CreateCallstack())
             {
@@ -1580,7 +1585,20 @@ namespace WinSCP
 
                 switches.Add(FormatSwitch("timeout", (int)sessionOptions.Timeout.TotalSeconds));
 
-                return string.Join(" ", switches.ToArray());
+                List<string> logSwitches = new List<string>(switches);
+
+                if ((sessionOptions.SecureNewPassword != null) && !scanFingerprint)
+                {
+                    if (sessionOptions.SecurePassword == null)
+                    {
+                        throw new ArgumentException("SessionOptions.SecureNewPassword is set, but SessionOptions.SecurePassword is not.");
+                    }
+                    switches.Add(FormatSwitch("newpassword", sessionOptions.NewPassword));
+                    logSwitches.Add(FormatSwitch("newpassword", "***"));
+                }
+
+                arguments = string.Join(" ", switches.ToArray());
+                logArguments = string.Join(" ", logSwitches.ToArray());
             }
         }
 

+ 13 - 9
dotnet/SessionOptions.cs

@@ -49,8 +49,10 @@ namespace WinSCP
         public string HostName { get; set; }
         public int PortNumber { get { return _portNumber; } set { SetPortNumber(value); } }
         public string UserName { get; set; }
-        public string Password { get { return GetPassword(); } set { SetPassword(value); } }
-        public SecureString SecurePassword { get; set; }
+        public string Password { get { return GetPassword(_securePassword); } set { SetPassword(ref _securePassword, value); } }
+        public SecureString SecurePassword { get { return _securePassword; } set { _securePassword = value; } }
+        public string NewPassword { get { return GetPassword(_secureNewPassword); } set { SetPassword(ref _secureNewPassword, value); } }
+        public SecureString SecureNewPassword { get { return _secureNewPassword; } set { _secureNewPassword = value; } }
         public TimeSpan Timeout { get { return _timeout; } set { SetTimeout(value); } }
         public int TimeoutInMilliseconds { get { return Tools.TimeSpanToMilliseconds(Timeout); } set { Timeout = Tools.MillisecondsToTimeSpan(value); } }
         public string PrivateKeyPassphrase { get; set; }
@@ -364,25 +366,25 @@ namespace WinSCP
             _webdavRoot = value;
         }
 
-        private void SetPassword(string value)
+        private static void SetPassword(ref SecureString securePassword, string value)
         {
             if (value == null)
             {
-                SecurePassword = null;
+                securePassword = null;
             }
             else
             {
-                SecurePassword = new SecureString();
+                securePassword = new SecureString();
                 foreach (char c in value)
                 {
-                    SecurePassword.AppendChar(c);
+                    securePassword.AppendChar(c);
                 }
             }
         }
 
-        private string GetPassword()
+        private static string GetPassword(SecureString securePassword)
         {
-            if (SecurePassword == null)
+            if (securePassword == null)
             {
                 return null;
             }
@@ -391,7 +393,7 @@ namespace WinSCP
                 IntPtr ptr = IntPtr.Zero;
                 try
                 {
-                    ptr = Marshal.SecureStringToGlobalAllocUnicode(SecurePassword);
+                    ptr = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
                     return Marshal.PtrToStringUni(ptr);
                 }
                 finally
@@ -401,6 +403,8 @@ namespace WinSCP
             }
         }
 
+        private SecureString _securePassword;
+        private SecureString _secureNewPassword;
         private string _sshHostKeyFingerprint;
         private string _tlsHostCertificateFingerprint;
         private TimeSpan _timeout;

+ 1 - 0
source/core/FtpFileSystem.cpp

@@ -2351,6 +2351,7 @@ bool __fastcall TFTPFileSystem::IsCapable(int Capability) const
     case fcLocking:
     case fcPreservingTimestampDirs:
     case fcResumeSupport:
+    case fcChangePassword:
       return false;
 
     default:

+ 1 - 0
source/core/Interface.h

@@ -9,6 +9,7 @@
 #define SCRIPT_SWITCH "script"
 #define COMMAND_SWITCH L"Command"
 #define SESSIONNAME_SWICH L"SessionName"
+#define NEWPASSWORD_SWITCH L"newpassword"
 #define INI_NUL L"nul"
 #define PRESERVETIME_SWITCH L"preservetime"
 #define PRESERVETIMEDIRS_SWITCH_VALUE L"all"

+ 3 - 0
source/core/ScpFileSystem.cpp

@@ -464,6 +464,9 @@ bool __fastcall TSCPFileSystem::IsCapable(int Capability) const
     case fcResumeSupport:
       return false;
 
+    case fcChangePassword:
+      return FSecureShell->CanChangePassword();
+
     default:
       DebugFail();
       return false;

+ 1 - 1
source/core/Script.cpp

@@ -2059,7 +2059,7 @@ __fastcall TManagementScript::TManagementScript(TStoredSessionList * StoredSessi
 
   FCommands->Register(L"exit", SCRIPT_EXIT_DESC, SCRIPT_EXIT_HELP, &ExitProc, 0, 0, false);
   FCommands->Register(L"bye", 0, SCRIPT_EXIT_HELP, &ExitProc, 0, 0, false);
-  FCommands->Register(L"open", SCRIPT_OPEN_DESC, SCRIPT_OPEN_HELP8, &OpenProc, 0, -1, true);
+  FCommands->Register(L"open", SCRIPT_OPEN_DESC, SCRIPT_OPEN_HELP9, &OpenProc, 0, -1, true);
   FCommands->Register(L"close", SCRIPT_CLOSE_DESC, SCRIPT_CLOSE_HELP, &CloseProc, 0, 1, false);
   FCommands->Register(L"session", SCRIPT_SESSION_DESC, SCRIPT_SESSION_HELP, &SessionProc, 0, 1, false);
   FCommands->Register(L"lpwd", SCRIPT_LPWD_DESC, SCRIPT_LPWD_HELP, &LPwdProc, 0, 0, false);

+ 31 - 1
source/core/SecureShell.cpp

@@ -166,6 +166,7 @@ Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
   conf_set_str(conf, CONF_username, UTF8String(Data->UserNameExpanded).c_str());
   conf_set_int(conf, CONF_port, Data->PortNumber);
   conf_set_int(conf, CONF_protocol, PROT_SSH);
+  conf_set_int(conf, CONF_change_password, Data->ChangePassword);
   // always set 0, as we will handle keepalives ourselves to avoid
   // multi-threaded issues in putty timer list
   conf_set_int(conf, CONF_ping_interval, 0);
@@ -866,6 +867,24 @@ bool __fastcall TSecureShell::PromptUser(bool /*ToServer*/,
       FStoredPassphraseTried = true;
     }
   }
+  else if (PromptKind == pkNewPassword)
+  {
+    if (FSessionData->ChangePassword)
+    {
+      FUI->Information(LoadStr(AUTH_CHANGING_PASSWORD), false);
+
+      if (!FSessionData->Password.IsEmpty() && !FSessionData->NewPassword.IsEmpty() && !FStoredPasswordTried)
+      {
+        LogEvent(L"Using stored password and new password.");
+        Result = true;
+        DebugAssert(Results->Count == 3);
+        Results->Strings[0] = FSessionData->Password;
+        Results->Strings[1] = FSessionData->NewPassword;
+        Results->Strings[2] = FSessionData->NewPassword;
+        FStoredPasswordTried = true;
+      }
+    }
+  }
 
   if (!Result)
   {
@@ -896,7 +915,10 @@ void __fastcall TSecureShell::GotHostKey()
   if (!FAuthenticating && !FAuthenticated)
   {
     FAuthenticating = true;
-    FUI->Information(LoadStr(STATUS_AUTHENTICATE), true);
+    if (!FSessionData->ChangePassword)
+    {
+      FUI->Information(LoadStr(STATUS_AUTHENTICATE), true);
+    }
   }
 }
 //---------------------------------------------------------------------------
@@ -2520,3 +2542,11 @@ void __fastcall TSecureShell::CollectUsage()
     Configuration->Usage->Inc(L"OpenedSessionsSSHOther");
   }
 }
+//---------------------------------------------------------------------------
+bool __fastcall TSecureShell::CanChangePassword()
+{
+  return
+    // These major SSH servers explicitly do not support password change.
+    (SshImplementation != sshiOpenSSH) && // See userauth_passwd
+    (SshImplementation != sshiProFTPD); // See sftp_auth_password
+}

+ 1 - 0
source/core/SecureShell.h

@@ -139,6 +139,7 @@ public:
   void __fastcall ClearStdError();
   bool __fastcall GetStoredCredentialsTried();
   void __fastcall CollectUsage();
+  bool __fastcall CanChangePassword();
 
   void __fastcall RegisterReceiveHandler(TNotifyEvent Handler);
   void __fastcall UnregisterReceiveHandler(TNotifyEvent Handler);

+ 54 - 2
source/core/SessionData.cpp

@@ -104,6 +104,8 @@ void __fastcall TSessionData::Default()
   PortNumber = SshPortNumber;
   UserName = L"";
   Password = L"";
+  NewPassword = L"";
+  ChangePassword = false;
   PingInterval = 30;
   PingType = ptOff;
   Timeout = 15;
@@ -277,6 +279,8 @@ void __fastcall TSessionData::NonPersistant()
   PROPERTY(Note);
 //---------------------------------------------------------------------
 #define ADVANCED_PROPERTIES \
+  PROPERTY(NewPassword); \
+  PROPERTY(ChangePassword); \
   PROPERTY(PingInterval); \
   PROPERTY(PingType); \
   PROPERTY(Timeout); \
@@ -1420,6 +1424,7 @@ void __fastcall TSessionData::SavePasswords(THierarchicalStorage * Storage, bool
 void __fastcall TSessionData::RecryptPasswords()
 {
   Password = Password;
+  NewPassword = NewPassword;
   ProxyPassword = ProxyPassword;
   TunnelPassword = TunnelPassword;
   Passphrase = Passphrase;
@@ -1437,12 +1442,17 @@ bool __fastcall TSessionData::HasAnySessionPassword()
 //---------------------------------------------------------------------
 bool __fastcall TSessionData::HasAnyPassword()
 {
-  return HasAnySessionPassword() || !FProxyPassword.IsEmpty();
+  return
+    HasAnySessionPassword() ||
+    !FProxyPassword.IsEmpty() ||
+    // will probably be never used
+    FNewPassword.IsEmpty();
 }
 //---------------------------------------------------------------------
 void __fastcall TSessionData::ClearSessionPasswords()
 {
   FPassword = L"";
+  FNewPassword = L"";
   FTunnelPassword = L"";
 }
 //---------------------------------------------------------------------
@@ -1560,7 +1570,9 @@ bool __fastcall TSessionData::IsProtocolUrl(
 //---------------------------------------------------------------------
 bool __fastcall TSessionData::IsSensitiveOption(const UnicodeString & Option)
 {
-  return SameText(Option, PassphraseOption);
+  return
+    SameText(Option, PassphraseOption) ||
+    SameText(Option, NEWPASSWORD_SWITCH);
 }
 //---------------------------------------------------------------------
 bool __fastcall TSessionData::ParseUrl(UnicodeString Url, TOptions * Options,
@@ -1869,6 +1881,11 @@ bool __fastcall TSessionData::ParseUrl(UnicodeString Url, TOptions * Options,
     {
       Name = Value;
     }
+    if (Options->FindSwitch(NEWPASSWORD_SWITCH, Value))
+    {
+      ChangePassword = true;
+      NewPassword = Value;
+    }
     if (Options->FindSwitch(L"privatekey", Value))
     {
       PublicKeyFile = Value;
@@ -2077,6 +2094,7 @@ void __fastcall TSessionData::SetHostName(UnicodeString value)
   {
     // HostName is key for password encryption
     UnicodeString XPassword = Password;
+    UnicodeString XNewPassword = Password;
 
     // This is now hardly used as hostname is parsed directly on login dialog.
     // But can be used when importing sites from PuTTY, as it allows same format too.
@@ -2090,7 +2108,9 @@ void __fastcall TSessionData::SetHostName(UnicodeString value)
     Modify();
 
     Password = XPassword;
+    NewPassword = XNewPassword;
     Shred(XPassword);
+    Shred(XNewPassword);
   }
 }
 //---------------------------------------------------------------------
@@ -2141,9 +2161,12 @@ void __fastcall TSessionData::SetUserName(UnicodeString value)
   {
     // UserName is key for password encryption
     UnicodeString XPassword = Password;
+    UnicodeString XNewPassword = NewPassword;
     SET_SESSION_PROPERTY(UserName);
     Password = XPassword;
+    NewPassword = XNewPassword;
     Shred(XPassword);
+    Shred(XNewPassword);
   }
 }
 //---------------------------------------------------------------------
@@ -2163,6 +2186,22 @@ UnicodeString __fastcall TSessionData::GetPassword() const
   return DecryptPassword(FPassword, UserName+HostName);
 }
 //---------------------------------------------------------------------
+void __fastcall TSessionData::SetNewPassword(UnicodeString avalue)
+{
+  RawByteString value = EncryptPassword(avalue, UserName+HostName);
+  SET_SESSION_PROPERTY(NewPassword);
+}
+//---------------------------------------------------------------------
+UnicodeString __fastcall TSessionData::GetNewPassword() const
+{
+  return DecryptPassword(FNewPassword, UserName+HostName);
+}
+//---------------------------------------------------------------------
+void __fastcall TSessionData::SetChangePassword(bool value)
+{
+  SET_SESSION_PROPERTY(ChangePassword);
+}
+//---------------------------------------------------------------------
 void __fastcall TSessionData::SetPingInterval(int value)
 {
   SET_SESSION_PROPERTY(PingInterval);
@@ -3646,6 +3685,19 @@ UnicodeString __fastcall TSessionData::ComposePath(
 {
   return UnixIncludeTrailingBackslash(Path) + Name;
 }
+//---------------------------------------------------------------------
+void __fastcall TSessionData::DisableAuthentationsExceptPassword()
+{
+  SshNoUserAuth = false;
+  AuthTIS = false;
+  AuthKI = false;
+  AuthKIPassword = false;
+  AuthGSSAPI = false;
+  PublicKeyFile = L"";
+  TlsCertificateFile = L"";
+  Passphrase = L"";
+  TryAgent = false;
+}
 //=== TStoredSessionList ----------------------------------------------
 __fastcall TStoredSessionList::TStoredSessionList(bool aReadOnly):
   TNamedObjectList(), FReadOnly(aReadOnly)

+ 8 - 0
source/core/SessionData.h

@@ -93,6 +93,8 @@ private:
   int FPortNumber;
   UnicodeString FUserName;
   RawByteString FPassword;
+  RawByteString FNewPassword;
+  bool FChangePassword;
   int FPingInterval;
   TPingType FPingType;
   bool FTryAgent;
@@ -219,6 +221,9 @@ private:
   UnicodeString __fastcall GetUserNameExpanded();
   void __fastcall SetPassword(UnicodeString value);
   UnicodeString __fastcall GetPassword() const;
+  void __fastcall SetNewPassword(UnicodeString value);
+  UnicodeString __fastcall GetNewPassword() const;
+  void __fastcall SetChangePassword(bool value);
   void __fastcall SetPingInterval(int value);
   void __fastcall SetTryAgent(bool value);
   void __fastcall SetAgentFwd(bool value);
@@ -442,6 +447,7 @@ public:
   void __fastcall ConfigureTunnel(int PortNumber);
   void __fastcall RollbackTunnel();
   void __fastcall ExpandEnvironmentVariables();
+  void __fastcall DisableAuthentationsExceptPassword();
   bool __fastcall IsSame(const TSessionData * Default, bool AdvancedOnly);
   bool __fastcall IsSameSite(const TSessionData * Default);
   bool __fastcall IsInFolderOrWorkspace(UnicodeString Name);
@@ -465,6 +471,8 @@ public:
   __property UnicodeString UserName  = { read=FUserName, write=SetUserName };
   __property UnicodeString UserNameExpanded  = { read=GetUserNameExpanded };
   __property UnicodeString Password  = { read=GetPassword, write=SetPassword };
+  __property UnicodeString NewPassword  = { read=GetNewPassword, write=SetNewPassword };
+  __property bool ChangePassword  = { read=FChangePassword, write=SetChangePassword };
   __property int PingInterval  = { read=FPingInterval, write=SetPingInterval };
   __property bool TryAgent  = { read=FTryAgent, write=SetTryAgent };
   __property bool AgentFwd  = { read=FAgentFwd, write=SetAgentFwd };

+ 1 - 0
source/core/SessionInfo.h

@@ -42,6 +42,7 @@ enum TFSCapability { fcUserGroupListing, fcModeChanging, fcGroupChanging,
   fcModeChangingUpload, fcPreservingTimestampUpload, fcShellAnyCommand,
   fcSecondaryShell, fcRemoveCtrlZUpload, fcRemoveBOMUpload, fcMoveToQueue,
   fcLocking, fcPreservingTimestampDirs, fcResumeSupport,
+  fcChangePassword,
   fcCount };
 //---------------------------------------------------------------------------
 struct TFileSystemInfo

+ 3 - 0
source/core/SftpFileSystem.cpp

@@ -2161,6 +2161,9 @@ bool __fastcall TSFTPFileSystem::IsCapable(int Capability) const
     case fcLocking:
       return false;
 
+    case fcChangePassword:
+      return FSecureShell->CanChangePassword();
+
     default:
       DebugFail();
       return false;

+ 37 - 45
source/core/Terminal.cpp

@@ -822,8 +822,7 @@ void __fastcall TTerminal::Close()
 {
   FFileSystem->Close();
 
-  // Cannot rely on CommandSessionOpened here as Status is set to ssClosed
-  // only after the OnClose is called
+  // Cannot rely on CommandSessionOpened here as Status is set to ssClosed too late
   if ((FCommandSession != NULL) && FCommandSession->Active)
   {
     // prevent recursion
@@ -1264,14 +1263,6 @@ void __fastcall TTerminal::Reopen(int Params)
       SessionData->FSProtocol = (FFSProtocol == cfsSCP ? fsSCPonly : fsSFTPonly);
     }
 
-    // Could be active before, if fatal error occured in the secondary terminal.
-    // But now, since we handle the secondary terminal's OnClose,
-    // by closing outselves, it should not happen anymore.
-    if (DebugAlwaysFalse(Active))
-    {
-      Close();
-    }
-
     Open();
   }
   __finally
@@ -4088,6 +4079,25 @@ bool __fastcall TTerminal::GetCommandSessionOpened()
     (FCommandSession->Status == ssOpened);
 }
 //---------------------------------------------------------------------------
+TTerminal * __fastcall TTerminal::CreateSecondarySession(const UnicodeString & Name, TSessionData * SessionData)
+{
+  std::unique_ptr<TTerminal> Result(new TSecondaryTerminal(this, SessionData, Configuration, Name));
+
+  Result->AutoReadDirectory = false;
+
+  Result->FExceptionOnFail = FExceptionOnFail;
+
+  Result->OnQueryUser = OnQueryUser;
+  Result->OnPromptUser = OnPromptUser;
+  Result->OnShowExtendedException = OnShowExtendedException;
+  Result->OnProgress = OnProgress;
+  Result->OnFinished = OnFinished;
+  Result->OnInformation = OnInformation;
+  Result->OnCustomCommand = OnCustomCommand;
+  // do not copy OnDisplayBanner to avoid it being displayed
+  return Result.release();
+}
+//---------------------------------------------------------------------------
 TTerminal * __fastcall TTerminal::GetCommandSession()
 {
   if ((FCommandSession != NULL) && !FCommandSession->Active)
@@ -4101,50 +4111,32 @@ TTerminal * __fastcall TTerminal::GetCommandSession()
     // levels between main and command session
     DebugAssert(FInTransaction == 0);
 
-    try
-    {
-      FCommandSession = new TSecondaryTerminal(this, SessionData,
-        Configuration, L"Shell");
+    std::unique_ptr<TSessionData> CommandSessionData(SessionData->Clone());
+    CommandSessionData->RemoteDirectory = CurrentDirectory;
+    CommandSessionData->FSProtocol = fsSCPonly;
+    CommandSessionData->ClearAliases = false;
+    CommandSessionData->UnsetNationalVars = false;
+    CommandSessionData->LookupUserGroups = asOff;
 
-      FCommandSession->AutoReadDirectory = false;
+    FCommandSession = CreateSecondarySession(L"Shell", CommandSessionData.get());
 
-      TSessionData * CommandSessionData = FCommandSession->FSessionData;
-      CommandSessionData->RemoteDirectory = CurrentDirectory;
-      CommandSessionData->FSProtocol = fsSCPonly;
-      CommandSessionData->ClearAliases = false;
-      CommandSessionData->UnsetNationalVars = false;
-      CommandSessionData->LookupUserGroups = asOff;
+    FCommandSession->AutoReadDirectory = false;
 
-      FCommandSession->FExceptionOnFail = FExceptionOnFail;
+    FCommandSession->FExceptionOnFail = FExceptionOnFail;
 
-      FCommandSession->OnQueryUser = OnQueryUser;
-      FCommandSession->OnPromptUser = OnPromptUser;
-      FCommandSession->OnShowExtendedException = OnShowExtendedException;
-      FCommandSession->OnProgress = OnProgress;
-      FCommandSession->OnFinished = OnFinished;
-      FCommandSession->OnInformation = OnInformation;
-      FCommandSession->OnCustomCommand = OnCustomCommand;
-      FCommandSession->OnClose = CommandSessionClose;
-      // do not copy OnDisplayBanner to avoid it being displayed
-    }
-    catch(...)
-    {
-      SAFE_DESTROY(FCommandSession);
-      throw;
-    }
+    FCommandSession->OnQueryUser = OnQueryUser;
+    FCommandSession->OnPromptUser = OnPromptUser;
+    FCommandSession->OnShowExtendedException = OnShowExtendedException;
+    FCommandSession->OnProgress = OnProgress;
+    FCommandSession->OnFinished = OnFinished;
+    FCommandSession->OnInformation = OnInformation;
+    FCommandSession->OnCustomCommand = OnCustomCommand;
+    // do not copy OnDisplayBanner to avoid it being displayed
   }
 
   return FCommandSession;
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminal::CommandSessionClose(TObject * /*Sender*/)
-{
-  // Keep the states in sync.
-  // This is particularly to invoke ours OnClose,
-  // So that it is triggered before Reopen is called
-  Close();
-}
-//---------------------------------------------------------------------------
 void __fastcall TTerminal::AnyCommand(const UnicodeString Command,
   TCaptureOutputEvent OutputEvent)
 {

+ 1 - 1
source/core/Terminal.h

@@ -394,7 +394,6 @@ protected:
     const UnicodeString & FileName, TFileOperation Operation1, TFileOperation Operation2 = foNone);
   void __fastcall StartOperationWithFile(
     const UnicodeString & FileName, TFileOperation Operation1, TFileOperation Operation2 = foNone);
-  void __fastcall CommandSessionClose(TObject * Sender);
   bool __fastcall CanRecurseToDirectory(const TRemoteFile * File);
   bool __fastcall DoOnCustomCommand(const UnicodeString & Command);
 
@@ -497,6 +496,7 @@ public:
   void __fastcall ReflectSettings();
   void __fastcall CollectUsage();
   bool __fastcall IsThisOrChild(TTerminal * Terminal);
+  TTerminal * __fastcall CreateSecondarySession(const UnicodeString & Name, TSessionData * SessionData);
 
   const TSessionInfo & __fastcall GetSessionInfo();
   const TFileSystemInfo & __fastcall GetFileSystemInfo(bool Retrieve = false);

+ 1 - 0
source/core/WebDAVFileSystem.cpp

@@ -702,6 +702,7 @@ bool __fastcall TWebDAVFileSystem::IsCapable(int Capability) const
     case fcRemoteCopy:
     case fcPreservingTimestampDirs:
     case fcResumeSupport:
+    case fcChangePassword:
       return false;
 
     case fcLocking:

+ 1 - 0
source/forms/Console.cpp

@@ -242,6 +242,7 @@ void __fastcall TConsoleDialog::ExecuteCommand()
   catch(Exception & E)
   {
     DebugAssert(FLastTerminal != NULL);
+    // Should use the command session, if there's one, not to close the main session
     FLastTerminal->ShowExtendedException(&E);
   }
 }

+ 26 - 12
source/forms/CustomScpExplorer.cpp

@@ -5599,20 +5599,9 @@ void __fastcall TCustomScpExplorerForm::DoOpenDirectoryDialog(
 //---------------------------------------------------------------------------
 bool __fastcall TCustomScpExplorerForm::CommandSessionFallback()
 {
-  bool Result = true;
-
   DebugAssert(!FTerminal->CommandSessionOpened);
 
-  try
-  {
-    TTerminalManager::ConnectTerminal(FTerminal->CommandSession, false);
-  }
-  catch(Exception & E)
-  {
-    ShowExtendedExceptionEx(FTerminal->CommandSession, &E);
-    Result = false;
-  }
-  return Result;
+  return TTerminalManager::Instance()->ConnectTerminal(FTerminal->CommandSession);
 }
 //---------------------------------------------------------------------------
 bool __fastcall TCustomScpExplorerForm::EnsureCommandSessionFallback(TFSCapability Capability)
@@ -9132,3 +9121,28 @@ void __fastcall TCustomScpExplorerForm::SessionsPageControlContextPopup(TObject
   Handled = true;
 }
 //---------------------------------------------------------------------------
+bool __fastcall TCustomScpExplorerForm::CanChangePassword()
+{
+  return (Terminal != NULL) && Terminal->Active && Terminal->IsCapable[fcChangePassword];
+}
+//---------------------------------------------------------------------------
+void __fastcall TCustomScpExplorerForm::ChangePassword()
+{
+  Configuration->Usage->Inc(L"PasswordChanges");
+  std::unique_ptr<TSessionData> ChangePasswordData(Terminal->SessionData->Clone());
+  ChangePasswordData->DisableAuthentationsExceptPassword();
+  ChangePasswordData->ChangePassword = true;
+
+  std::unique_ptr<TTerminal> ChangePasswordSession(
+    Terminal->CreateSecondarySession(L"ChangePassword", ChangePasswordData.get()));
+  ChangePasswordSession->AutoReadDirectory = false;
+
+  if (TTerminalManager::Instance()->ConnectTerminal(ChangePasswordSession.get()))
+  {
+    MessageDialog(MainInstructions(LoadStr(PASSWORD_CHANGED)), qtInformation, qaOK, HELP_CHANGE_PASSWORD);
+  }
+  else
+  {
+    Configuration->Usage->Inc(L"PasswordChangesFailed");
+  }
+}

+ 2 - 0
source/forms/CustomScpExplorer.h

@@ -684,6 +684,8 @@ public:
   virtual void __fastcall DisplaySystemContextMenu();
   virtual void __fastcall GoToAddress() = 0;
   bool __fastcall CanConsole();
+  bool __fastcall CanChangePassword();
+  void __fastcall ChangePassword();
 
   __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
   __property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };

+ 2 - 0
source/forms/NonVisual.cpp

@@ -444,6 +444,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   UPD(ClearCachesAction, HasTerminal && !ScpExplorer->Terminal->AreCachesEmpty)
   UPD(NewFileAction, DirViewEnabled(osCurrent) && !WinConfiguration->DisableOpenEdit)
   UPD(EditorListCustomizeAction, true)
+  UPD(ChangePasswordAction, ScpExplorer->CanChangePassword())
 
   // CUSTOM COMMANDS
   UPD(CustomCommandsFileAction, true)
@@ -778,6 +779,7 @@ void __fastcall TNonVisualDataModule::ExplorerActionsExecute(
     EXE(ClearCachesAction, ScpExplorer->Terminal->ClearCaches())
     EXE(NewFileAction, ScpExplorer->EditNew(osCurrent))
     EXE(EditorListCustomizeAction, PreferencesDialog(pmEditor))
+    EXE(ChangePasswordAction, ScpExplorer->ChangePassword())
 
     // CUSTOM COMMANDS
     EXE(CustomCommandsFileAction, CreateCustomCommandsMenu(CustomCommandsFileAction, ccltFile))

+ 7 - 0
source/forms/NonVisual.dfm

@@ -2259,6 +2259,13 @@ object NonVisualDataModule: TNonVisualDataModule
       Hint = 'Displays tips on using WinSCP'
       ImageIndex = 110
     end
+    object ChangePasswordAction: TAction
+      Tag = 15
+      Category = 'Session'
+      Caption = '&Change Password...'
+      HelpKeyword = 'task_change_password'
+      Hint = 'Change account password'
+    end
   end
   object ExplorerBarPopup: TTBXPopupMenu
     Images = GlyphsModule.ExplorerImages

+ 1 - 0
source/forms/NonVisual.h

@@ -617,6 +617,7 @@ __published:    // IDE-managed Components
   TTBXSubmenuItem *RemoteDirViewPopupCustomCommandsMenu;
   TTBXItem *TBXItem75;
   TTBXItem *TBXItem76;
+  TAction *ChangePasswordAction;
   void __fastcall LogActionsUpdate(TBasicAction *Action, bool &Handled);
   void __fastcall LogActionsExecute(TBasicAction *Action, bool &Handled);
   void __fastcall ExplorerActionsUpdate(TBasicAction *Action, bool &Handled);

+ 3 - 0
source/forms/ScpCommander.dfm

@@ -425,6 +425,9 @@ inherited ScpCommanderForm: TScpCommanderForm
         object TBXItem135: TTBXItem
           Action = NonVisualDataModule.SessionGenerateUrlAction2
         end
+        object TBXItem227: TTBXItem
+          Action = NonVisualDataModule.ChangePasswordAction
+        end
         object TBXSeparatorItem29: TTBXSeparatorItem
         end
         object TBXSubmenuItem21: TTBXSubmenuItem

+ 1 - 0
source/forms/ScpCommander.h

@@ -420,6 +420,7 @@ __published:
   TTBXItem *TBXItem216;
   TTBXItem *TBXItem217;
   TTBXSubmenuItem *TBXSubmenuItem28;
+  TTBXItem *TBXItem227;
   void __fastcall SplitterMoved(TObject *Sender);
   void __fastcall SplitterCanResize(TObject *Sender, int &NewSize,
     bool &Accept);

+ 3 - 0
source/forms/ScpExplorer.dfm

@@ -301,6 +301,9 @@ inherited ScpExplorerForm: TScpExplorerForm
         object TBXItem144: TTBXItem
           Action = NonVisualDataModule.SessionGenerateUrlAction2
         end
+        object TBXItem160: TTBXItem
+          Action = NonVisualDataModule.ChangePasswordAction
+        end
         object TBXSeparatorItem29: TTBXSeparatorItem
         end
         object TBXSubmenuItem21: TTBXSubmenuItem

+ 1 - 0
source/forms/ScpExplorer.h

@@ -312,6 +312,7 @@ __published:
   TTBXItem *TBXItem216;
   TTBXItem *TBXItem159;
   TTBXSubmenuItem *TBXSubmenuItem28;
+  TTBXItem *TBXItem160;
   void __fastcall RemoteDirViewUpdateStatusBar(TObject *Sender,
           const TStatusFileInfo &FileInfo);
   void __fastcall UnixPathComboBoxBeginEdit(TTBEditItem *Sender,

+ 1 - 0
source/putty/putty.h

@@ -913,6 +913,7 @@ void cleanup_exit(int);
     X(INT, NONE, connect_timeout) \
     X(INT, NONE, sndbuf) \
     X(INT, NONE, force_remote_cmd2) \
+    X(INT, NONE, change_password) \
     /* MPEXT END */ \
 
 /* Now define the actual enum of option keywords using that macro. */

+ 29 - 2
source/putty/ssh.c

@@ -9145,6 +9145,9 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 	int kbd_inter_refused;
 	int we_are_in, userauth_success;
 	prompts_t *cur_prompt;
+#ifdef MPEXT
+	int change_password;
+#endif
 	int num_prompts;
 	char *username;
 	char *password;
@@ -10372,6 +10375,17 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 
 		ssh->pkt_actx = SSH2_PKTCTX_PASSWORD;
 
+#ifdef MPEXT
+		s->change_password = conf_get_int(ssh->conf, CONF_change_password);
+		if (s->change_password != 0)
+		{
+			s->password = dupstr("");
+			s->type = AUTH_TYPE_PASSWORD;
+		}
+		else
+		{
+		/* no indentation to ease merges */
+#endif
 		s->cur_prompt = new_prompts(ssh->frontend);
 		s->cur_prompt->to_server = TRUE;
 		s->cur_prompt->name = dupstr("SSH password");
@@ -10436,10 +10450,16 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 		 * request.
 		 */
 		crWaitUntilV(pktin);
+#ifdef MPEXT
+		}
+#endif
 		changereq_first_time = TRUE;
 
-		while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
-
+		while ((pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ)
+#ifdef MPEXT
+		       || (s->change_password != 0)
+#endif
+           ) {
 		    /*
 		     * We're being asked for a new password
 		     * (perhaps not for the first time).
@@ -10450,6 +10470,9 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 		    char *prompt;   /* not live over crReturn */
 		    int prompt_len; /* not live over crReturn */
 
+#ifdef MPEXT
+			if (s->change_password == 0)
+#endif
 		    {
 			const char *msg;
 			if (changereq_first_time)
@@ -10461,6 +10484,10 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
 			c_write_str(ssh, "\r\n");
 		    }
 
+#ifdef MPEXT
+		    s->change_password = 0;
+#endif
+
 		    ssh_pkt_getstring(pktin, &prompt, &prompt_len);
 
 		    s->cur_prompt = new_prompts(ssh->frontend);

+ 1 - 0
source/resource/HelpWin.h

@@ -58,5 +58,6 @@
 #define HELP_TIPS                    "ui_tips"
 #define HELP_DEBUGGING               "debugging"
 #define HELP_EXTENSION_OPTIONS       "ui_pref_commands"
+#define HELP_CHANGE_PASSWORD         "task_change_password"
 
 #endif // TextsWin

+ 2 - 1
source/resource/TextsCore.h

@@ -7,7 +7,7 @@
 
 #define SCRIPT_HELP_HELP        4
 #define SCRIPT_EXIT_HELP        5
-#define SCRIPT_OPEN_HELP8       6
+#define SCRIPT_OPEN_HELP9       6
 #define SCRIPT_CLOSE_HELP       7
 #define SCRIPT_SESSION_HELP     8
 #define SCRIPT_PWD_HELP         9
@@ -456,6 +456,7 @@
 #define COPY_INFO_PRESERVE_TIME_DIRS 554
 #define TEXT_FILE_ENCODING      555
 #define AND_STR                 556
+#define AUTH_CHANGING_PASSWORD  557
 
 #define CORE_VARIABLE_STRINGS   600
 #define PUTTY_BASED_ON          601

+ 1 - 0
source/resource/TextsCore1.rc

@@ -426,6 +426,7 @@ BEGIN
   COPY_INFO_PRESERVE_TIME_DIRS, "%s (including directories)"
   TEXT_FILE_ENCODING, "The file must be in UTF-8 or UTF-16 encoding."
   AND_STR, "%s and %s"
+  AUTH_CHANGING_PASSWORD, "Changing password."
 
   CORE_VARIABLE_STRINGS, "CORE_VARIABLE"
   PUTTY_BASED_ON, "SSH and SCP code based on PuTTY %s"

+ 2 - 1
source/resource/TextsCore2.rc

@@ -59,7 +59,7 @@ BEGIN
     "  Closes all sessions and terminates the program.\n"
     "alias:\n"
     "  bye\n"
-  SCRIPT_OPEN_HELP8,
+  SCRIPT_OPEN_HELP9,
     "open <site>\n"
     "open sftp|scp|ftp[es]|http[s] :// [ <user> [ :password ] @ ] <host> [ :<port> ]\n"
     "  Establishes connection to given host. Use either name of the site or\n"
@@ -79,6 +79,7 @@ BEGIN
     "                     Configures any site settings using raw format\n"
     "                     as in an INI file\n"
     "  -filezilla         Load <site> from FileZilla site manager\n"
+    "  -newpassword=<password> Changes password to <password>\n"
     "examples:\n"
     "  open\n"
     "  open sftp://[email protected]:2222 -privatekey=mykey.ppk\n"

+ 1 - 0
source/resource/TextsWin.h

@@ -283,6 +283,7 @@
 #define USAGE_SESSIONNAME       1573
 #define USAGE_REFRESH           1574
 #define USAGE_LOGSIZE           1575
+#define PASSWORD_CHANGED        1576
 
 #define WIN_FORMS_STRINGS       1600
 #define LOG_NOLOG               1601

+ 1 - 0
source/resource/TextsWin1.rc

@@ -286,6 +286,7 @@ BEGIN
         USAGE_SESSIONNAME, "Gives a name to the session"
         USAGE_REFRESH, "Reloads remote panel of all running instances of WinSCP. If session (and optionally a path) is specified, refreshes only the instances with that session (and path)."
         USAGE_LOGSIZE, "Enables log rotation and optionally deleting of old logs."
+        PASSWORD_CHANGED, "Password has been changed."
 
         WIN_FORMS_STRINGS, "WIN_FORMS_STRINGS"
         LOG_NOLOG, "No session log."

+ 17 - 2
source/windows/TerminalManager.cpp

@@ -210,7 +210,7 @@ void __fastcall TTerminalManager::FreeActiveTerminal()
   }
 }
 //---------------------------------------------------------------------------
-void TTerminalManager::ConnectTerminal(TTerminal * Terminal, bool Reopen)
+void __fastcall TTerminalManager::DoConnectTerminal(TTerminal * Terminal, bool Reopen)
 {
   TManagedTerminal * ManagedTerminal = dynamic_cast<TManagedTerminal *>(Terminal);
   // it must be managed terminal, unless it is secondary terminal (of managed terminal)
@@ -271,6 +271,21 @@ void TTerminalManager::ConnectTerminal(TTerminal * Terminal, bool Reopen)
   }
 }
 //---------------------------------------------------------------------------
+bool __fastcall TTerminalManager::ConnectTerminal(TTerminal * Terminal)
+{
+  bool Result = true;
+  try
+  {
+    DoConnectTerminal(Terminal, false);
+  }
+  catch (Exception & E)
+  {
+    ShowExtendedExceptionEx(Terminal, &E);
+    Result = false;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 void __fastcall TTerminalManager::TerminalThreadIdle(void * /*Data*/, TObject * /*Sender*/)
 {
   Application->ProcessMessages();
@@ -288,7 +303,7 @@ bool __fastcall TTerminalManager::ConnectActiveTerminalImpl(bool Reopen)
     {
       DebugAssert(ActiveTerminal);
 
-      ConnectTerminal(ActiveTerminal, Reopen);
+      DoConnectTerminal(ActiveTerminal, Reopen);
 
       if (ScpExplorer)
       {

+ 2 - 1
source/windows/TerminalManager.h

@@ -48,7 +48,7 @@ public:
   void __fastcall ReconnectActiveTerminal();
   void __fastcall FreeActiveTerminal();
   void __fastcall CycleTerminals(bool Forward);
-  static void ConnectTerminal(TTerminal * Terminal, bool Reopen);
+  bool __fastcall ConnectTerminal(TTerminal * Terminal);
   void __fastcall SetActiveTerminalWithAutoReconnect(TTerminal * value);
   void __fastcall UpdateAppTitle();
   bool __fastcall CanOpenInPutty();
@@ -62,6 +62,7 @@ public:
   TTerminal * __fastcall FindActiveTerminalForSite(TSessionData * Data);
   TTerminalQueue * __fastcall FindQueueForTerminal(TTerminal * Terminal);
   void __fastcall UpdateSessionCredentials(TSessionData * Data);
+  void __fastcall DoConnectTerminal(TTerminal * Terminal, bool Reopen);
 
   __property TCustomScpExplorerForm * ScpExplorer = { read = FScpExplorer, write = SetScpExplorer };
   __property TTerminal * ActiveTerminal = { read = FActiveTerminal, write = SetActiveTerminal };

+ 1 - 1
source/windows/UserInterface.cpp

@@ -175,7 +175,7 @@ void __fastcall ShowExtendedExceptionEx(TTerminal * Terminal,
       WinConfiguration->ConfirmExitOnCompletion;
 
     if (E->InheritsFrom(__classid(EFatal)) && (Terminal != NULL) &&
-        (Manager != NULL) && (Manager->ActiveTerminal != NULL) && Manager->ActiveTerminal->IsThisOrChild(Terminal))
+        (Manager != NULL) && (Manager->ActiveTerminal == Terminal))
     {
       int SessionReopenTimeout = 0;
       TManagedTerminal * ManagedTerminal = dynamic_cast<TManagedTerminal *>(Manager->ActiveTerminal);

+ 4 - 2
source/windows/VCLCommon.cpp

@@ -892,12 +892,14 @@ void __fastcall HideAsModal(TForm * Form, void *& Storage)
   delete AStorage;
 }
 //---------------------------------------------------------------------------
-void __fastcall ReleaseAsModal(TForm * Form, void *& Storage)
+bool __fastcall ReleaseAsModal(TForm * Form, void *& Storage)
 {
-  if (Storage != NULL)
+  bool Result = (Storage != NULL);
+  if (Result)
   {
     HideAsModal(Form, Storage);
   }
+  return Result;
 }
 //---------------------------------------------------------------------------
 bool __fastcall SelectDirectory(UnicodeString & Path, const UnicodeString Prompt,

+ 1 - 1
source/windows/VCLCommon.h

@@ -31,7 +31,7 @@ bool __fastcall HasLabelHintPopup(TLabel * Label, const UnicodeString & HintStr)
 void __fastcall FixComboBoxResizeBug(TCustomComboBox * ComboBox);
 void __fastcall ShowAsModal(TForm * Form, void *& Storage, bool BringToFront = true);
 void __fastcall HideAsModal(TForm * Form, void *& Storage);
-void __fastcall ReleaseAsModal(TForm * Form, void *& Storage);
+bool __fastcall ReleaseAsModal(TForm * Form, void *& Storage);
 TImageList * __fastcall SharedSystemImageList(bool Large);
 bool __fastcall SelectDirectory(UnicodeString & Path, const UnicodeString Prompt,
   bool PreserveFileName);