//--------------------------------------------------------------------------- #include #pragma hdrstop #include #include "PuttyIntf.h" #include "Net.h" #include "Interface.h" #include "SecureShell.h" #include "TextsCore.h" #include "HelpCore.h" #include "Common.h" #include "ScpMain.h" #include "Security.h" #ifndef AUTO_WINSOCK #include #endif //--------------------------------------------------------------------------- #pragma package(smart_init) //--------------------------------------------------------------------------- #define MAX_BUFSIZE 32768 const TColor LogLineColors[] = {clGreen, clRed, clMaroon, clBlue, clGray}; //--------------------------------------------------------------------------- __fastcall TSecureShell::TSecureShell() { FSessionData = new TSessionData(""); FActive = False; ResetConnection(); FLog = new TSessionLog(this); FOnQueryUser = NULL; FOnPromptUser = NULL; FOnDisplayBanner = NULL; FOnShowExtendedException = NULL; FOnUpdateStatus = NULL; FOnStdError = NULL; FOnCaptureOutput = NULL; FOnClose = NULL; FConfig = new Config(); FSocket = new SOCKET; } //--------------------------------------------------------------------------- __fastcall TSecureShell::~TSecureShell() { ClearStdError(); Active = false; SAFE_DESTROY(FSessionData); SAFE_DESTROY(FLog); delete FConfig; FConfig = NULL; UserObject = NULL; delete FSocket; FSocket = NULL; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::ResetConnection() { PendLen = 0; PendSize = 0; Pending = NULL; FStdErrorTemp = ""; FBytesSent = 0; FBytesReceived = 0; FCSCipher = cipWarn; // = not detected yet FSCCipher = cipWarn; FReachedStatus = 0; UpdateStatus(sshClosed); FMaxPacketSize = NULL; FBufSize = 0; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Open() { try { DoOpen(); } __finally { UpdateStatus(-1, false); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::DoOpen() { const char * InitError; char * RealHost; FStoredPasswordTried = false; FStoredPasswordTriedForKI = false; FReachedStatus = 0; { TCoreGuard Guard; SessionsCount++; if (SessionsCount == 1) { UpdateStatus(sshInitWinSock); NetInitialize(); } else { Log->Id = reinterpret_cast(this); } } Log->AddStartupInfo(); Active = false; FBackend = &ssh_backend; FAuthenticationLog = ""; UpdateStatus(sshLookupHost); SessionData->StoreToConfig(FConfig); InitError = FBackend->init(this, &FBackendHandle, FConfig, SessionData->HostName.c_str(), SessionData->PortNumber, &RealHost, 0, FConfig->tcp_keepalives); if (InitError) { FatalError(InitError); } FRealHost = RealHost; UpdateStatus(sshConnect); /*FLoggingContext = log_init(this, (void *)FConfig); FBackend->provide_logctx(FBackendHandle, FLoggingContext);*/ Init(); CheckConnection(CONNECTION_FAILED); FLastDataSent = Now(); FLoginTime = Now(); Log->AddSeparator(); UpdateStatus(sshAuthenticated); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Reopen(int /*Params*/) { if (Active) { Close(); } ResetConnection(); Open(); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Init() { try { try { while (!FBackend->sendok(FBackendHandle)) { if (Configuration->LogProtocol >= 1) { LogEvent("Waiting for the server to continue with the initialisation"); } WaitForData(true); } } catch(Exception & E) { if ((FReachedStatus == sshAuthenticate) && !FAuthenticationLog.IsEmpty()) { FatalError(&E, FMTLOAD(AUTHENTICATION_LOG, (FAuthenticationLog))); } else { throw; } } } catch(Exception & E) { if (FReachedStatus == sshAuthenticate) { FatalError(&E, LoadStr(AUTHENTICATION_FAILED)); } else { throw; } } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::PuttyLogEvent(const AnsiString & Str) { #define SERVER_VERSION_MSG "Server version: " // Gross hack if (Str.Pos(SERVER_VERSION_MSG) == 1) { FSshVersionString = Str.SubString(strlen(SERVER_VERSION_MSG) + 1, Str.Length() - strlen(SERVER_VERSION_MSG)); } LogEvent(Str); } //--------------------------------------------------------------------------- AnsiString __fastcall TSecureShell::GetSshImplementation() { const char * Ptr = strchr(FSshVersionString.c_str(), '-'); if (Ptr != NULL) { Ptr = strchr(Ptr + 1, '-'); } return (Ptr != NULL) ? AnsiString(Ptr + 1) : AnsiString(); } //--------------------------------------------------------------------- AnsiString __fastcall TSecureShell::GetPassword() { return (FPassword.IsEmpty() ? AnsiString() : DecryptPassword(FPassword, SessionData->SessionName)); } //--------------------------------------------------------------------- bool __fastcall TSecureShell::GetStoredPasswordTried() { return FStoredPasswordTried || FStoredPasswordTriedForKI; } //--------------------------------------------------------------------------- TDateTime __fastcall TSecureShell::GetIdleInterval() { return (FSessionData->PingType != ptOff) ? FSessionData->PingIntervalDT : TDateTime(0); } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::PromptUser(const AnsiString Prompt, AnsiString & Response, bool IsPassword) { USEDPARAM(IsPassword); assert(IsPassword); // false only for username prompts bool Result; if (Prompt.Pos("Passphrase for key ") == 1) { AnsiString Key(Prompt); int P = Prompt.Pos("\""); if (P > 0) { Key.Delete(1, P); P = Key.LastDelimiter("\""); if (P > 0) { Key.SetLength(P - 1); } } LogEvent(FORMAT("Passphrase prompt (%s)", (Prompt))); Result = DoPromptUser(FMTLOAD(PROMPT_KEY_PASSPHRASE, (Key)), pkPassphrase, Response); } else if (Prompt.Pos("'s password: ")) { LogEvent(FORMAT("Session password prompt (%s)", (Prompt))); if (!SessionData->Password.IsEmpty() && !FStoredPasswordTried) { LogEvent("Using stored password."); Result = true; Response = SessionData->Password; FStoredPasswordTried = true; } else { LogEvent("Asking user for password."); Result = DoPromptUser( FMTLOAD(PROMPT_SESSION_PASSWORD, (SessionData->SessionName)), pkPassword, Response); } } else { // in other cases we assume TIS/Cryptocard/keyboard-interactive authentification prompt LogEvent(FORMAT("%s prompt from server", (Prompt))); if (!SessionData->Password.IsEmpty() && SessionData->AuthKIPassword && !FStoredPasswordTriedForKI) { LogEvent("Responding with stored password."); Result = true; Response = SessionData->Password; FStoredPasswordTriedForKI = true; } else { LogEvent("Asking user for response."); static const AnsiString ResponseSuffix("\r\nResponse: "); // Strip Cryptocard/TIS "Response" suffix AnsiString UserPrompt = Prompt; if (UserPrompt.SubString(UserPrompt.Length() - ResponseSuffix.Length() + 1, ResponseSuffix.Length()) == ResponseSuffix) { UserPrompt.SetLength(UserPrompt.Length() - ResponseSuffix.Length()); } Result = DoPromptUser(UserPrompt, pkServerPrompt, Response); } }; if (Configuration->RememberPassword) { FPassword = EncryptPassword(Response, SessionData->SessionName); } return Result; } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::DoPromptUser(AnsiString Prompt, TPromptKind Kind, AnsiString & Response) { bool Result = false; if (OnPromptUser != NULL) { OnPromptUser(this, Prompt, Kind, Response, Result, NULL); } return Result; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::GotHostKey() { // due to re-key GotHostKey() may be called again later during session if (FReachedStatus < sshAuthenticate) { UpdateStatus(sshAuthenticate); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::FromBackend(Boolean IsStdErr, char * Data, Integer Length) { CheckConnection(); // Following is taken from scp.c from_backend() and modified if (IsStdErr) { AddStdError(AnsiString(Data, Length), false); } else { unsigned char *p = (unsigned char *)Data; unsigned Len = (unsigned)Length; // If this is before the real session begins, raise exception. if (!OutPtr) { FatalError("Internal error: Session not yet begun."); } if ((OutLen > 0) && (Len > 0)) { unsigned Used = OutLen; if (Used > Len) Used = Len; memcpy(OutPtr, p, Used); OutPtr += Used; OutLen -= Used; p += Used; Len -= Used; } if (Len > 0) { if (PendSize < PendLen + Len) { PendSize = PendLen + Len + 4096; Pending = (char *) (Pending ? srealloc(Pending, PendSize) : smalloc(PendSize)); if (!Pending) FatalError("Out of memory"); } memcpy(Pending + PendLen, p, Len); PendLen += Len; } } } //--------------------------------------------------------------------------- Integer __fastcall TSecureShell::Receive(char * Buf, Integer Len) { CheckConnection(); if (Len > 0) { // Following is taken from scp.c ssh_scp_recv() and modified OutPtr = Buf; OutLen = Len; /* * See if the pending-input block contains some of what we * need. */ if (PendLen > 0) { unsigned PendUsed = PendLen; if (PendUsed > OutLen) { PendUsed = OutLen; } memcpy(OutPtr, Pending, PendUsed); memmove(Pending, Pending + PendUsed, PendLen - PendUsed); OutPtr += PendUsed; OutLen -= PendUsed; PendLen -= PendUsed; if (PendLen == 0) { PendSize = 0; sfree(Pending); Pending = NULL; } } // I don't undestand this yet, but it works :-) while (OutLen > 0) { if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("Waiting for another %u bytes", (static_cast(OutLen)))); } WaitForData(false); } // This seems ambiguous if (Len <= 0) FatalError(LoadStr(LOST_CONNECTION)); }; FBytesReceived += Len; if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("Received %u bytes", (static_cast(Len)))); } return Len; } //--------------------------------------------------------------------------- AnsiString __fastcall TSecureShell::ReceiveLine() { unsigned Index; Char Ch; AnsiString Line; Boolean EOL = False; do { // If there is any buffer of received chars if (PendLen > 0) { Index = 0; // Repeat until we walk thru whole buffer or reach end-of-line while ((Index < PendLen) && (!Index || (Pending[Index-1] != '\n'))) { Index++; } EOL = (Boolean)(Index && (Pending[Index-1] == '\n')); Integer PrevLen = Line.Length(); Line.SetLength(PrevLen + Index); Receive(Line.c_str() + PrevLen, Index); } // If buffer don't contain end-of-line character // we read one more which causes receiving new buffer of chars if (!EOL) { Receive(&Ch, 1); Line += Ch; EOL = (Ch == '\n'); } } while (!EOL); // We don't want end-of-line character Line.SetLength(Line.Length()-1); CaptureOutput(llOutput, Line, false); return Line; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SendSpecial(int Code) { LogEvent(FORMAT("Sending special code: %d", (Code))); CheckConnection(); FBackend->special(FBackendHandle, (Telnet_Special)Code); CheckConnection(); FLastDataSent = Now(); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SendEOF() { SendSpecial(TS_EOF); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Send(const char * Buf, Integer Len) { CheckConnection(); FBufSize = FBackend->send(FBackendHandle, (char *)Buf, Len); if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("Sent %u bytes", (static_cast(Len)))); LogEvent(FORMAT("There are %u bytes remaining in the send buffer", (FBufSize))); } FLastDataSent = Now(); FBytesSent += Len; while (FBufSize > MAX_BUFSIZE) { if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("There are %u bytes remaining in the send buffer, " "need to send at least another %u bytes", (FBufSize, FBufSize - MAX_BUFSIZE))); } // it seems that this does not work anyway // (i.e. once the send buffer fills we hang here) WaitForData(true); FBufSize = FBackend->sendbuffer(FBackendHandle); if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("There are %u bytes remaining in the send buffer", (FBufSize))); } } CheckConnection(); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SendNull() { LogEvent("Sending NULL."); Send("", 1); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SendStr(AnsiString Str) { CheckConnection(); Send(Str.c_str(), Str.Length()); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SendLine(AnsiString Line) { SendStr(Line); Send("\n", 1); Log->Add(llInput, Line); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::TranslateAuthenticationMessage(AnsiString & Message) { struct TMapping { const char * Original; const char * Additional; int Translation; }; TMapping Mapping[] = { { "Using username \"", NULL, AUTH_TRANSL_USERNAME }, { "Using keyboard-interactive authentication.", NULL, AUTH_TRANSL_KEYB_INTER }, { "Authenticating with public key \"", "from agent", AUTH_TRANSL_PUBLIC_KEY_AGENT }, { "Authenticating with public key \"", NULL, AUTH_TRANSL_PUBLIC_KEY }, { "Authenticated using RSA key \"", NULL, AUTH_TRANSL_PUBLIC_KEY_AGENT }, { "Wrong passphrase", NULL, AUTH_TRANSL_WRONG_PASSPHRASE }, { "Access denied", NULL, AUTH_TRANSL_ACCESS_DENIED }, { "Trying public key authentication", NULL, AUTH_TRANSL_TRY_PUBLIC_KEY } }; for (int Index = 0; Index < LENOF(Mapping); Index++) { const char * Original = Mapping[Index].Original; size_t OriginalLen = strlen(Original); if (strncmp(Message.c_str(), Original, OriginalLen) == 0) { if (Original[OriginalLen - 1] == '"') { AnsiString MessageRest = Message.SubString(OriginalLen + 1, Message.Length() - OriginalLen); int P = MessageRest.Pos("\""); if (P > 0) { const char * Additional = Mapping[Index].Additional; if ((Additional == NULL) || (MessageRest.Pos(Additional) > 0)) { MessageRest.SetLength(P - 1); Message = FMTLOAD(Mapping[Index].Translation, (MessageRest)); break; } } } else { Message = LoadStr(Mapping[Index].Translation); break; } } } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::AddStdError(AnsiString Str, bool LogOnly) { StdError += Str; Integer P; Str = DeleteChar(Str, '\r'); // We send only whole line at once to log, so we have to cache // incoming std error data FStdErrorTemp += Str; AnsiString Line; // Do we have at least one complete line in std error cache? while ((P = FStdErrorTemp.Pos("\n")) > 0) { Line = FStdErrorTemp.SubString(1, P-1); FStdErrorTemp.Delete(1, P); AddStdErrorLine(Line, LogOnly); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::AddStdErrorLine(AnsiString Str, bool LogOnly) { if (Status == sshAuthenticate) { TranslateAuthenticationMessage(Str); FAuthenticationLog += (FAuthenticationLog.IsEmpty() ? "" : "\n") + Str; } if (!LogOnly && (OnStdError != NULL)) { OnStdError(this, llStdError, Str); } CaptureOutput(llStdError, Str, LogOnly); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::ClearStdError() { // Flush std error cache if (!FStdErrorTemp.IsEmpty()) { if (Status == sshAuthenticate) { FAuthenticationLog += (FAuthenticationLog.IsEmpty() ? "" : "\n") + FStdErrorTemp; } CaptureOutput(llStdError, FStdErrorTemp, false); FStdErrorTemp = ""; } StdError = ""; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::CaptureOutput(TLogLineType Type, const AnsiString & Line, bool LogOnly) { if (!LogOnly && (FOnCaptureOutput != NULL)) { FOnCaptureOutput(this, Type, Line); } Log->Add(Type, Line); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::FatalError(Exception * E, AnsiString Msg) { if (FActive) { // We log this instead of exception handler, because Close() would // probably cause exception handler to loose pointer to TShellLog() LogEvent("Attempt to close connection due to fatal exception:"); Log->Add(llException, Msg); Log->AddException(E); Close(); } SSH_FATAL_ERROR_EXT(E, Msg); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::FatalError(AnsiString Error) { FatalError(NULL, Error); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SetSocket(void * value) { assert(value); // now this can be called repeatedly, so allow being called again with // the same socket if (FActive && (*static_cast(value) != INVALID_SOCKET) && (*static_cast(FSocket) != *static_cast(value))) FatalError("Cannot set socket during connection"); assert(FSocket); *static_cast(FSocket) = *static_cast(value); if (*static_cast(FSocket) != INVALID_SOCKET) { FActive = true; } else { Discard(); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SetSessionData(TSessionData * value) { assert(!FActive); FSessionData->Assign(value); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SetActive(bool value) { if (FActive != value) { if (value) { Open(); } else { Close(); } } } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::GetActive() const { return FActive; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Discard() { bool WasActive = FActive; FActive = false; if (WasActive) { if (FReachedStatus) { TCoreGuard Guard; SessionsCount--; if (SessionsCount == 0) { NetFinalize(); } } if (OnClose) { OnClose(this); } } FStatus = sshClosed; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Close() { LogEvent("Closing connection."); CheckConnection(); FBackend->free(FBackendHandle); Discard(); } //--------------------------------------------------------------------------- void inline __fastcall TSecureShell::CheckConnection(int Message) { if (!FActive || get_ssh_state_closed(FBackendHandle)) { AnsiString Str = LoadStr(Message >= 0 ? Message : NOT_CONNECTED); int ExitCode = get_ssh_exitcode(FBackendHandle); if (ExitCode >= 0) { Str += " " + FMTLOAD(SSH_EXITCODE, (ExitCode)); } FatalError(Str); } } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::Select(int Sec) { CheckConnection(); struct timeval time; fd_set readfds; FD_ZERO(&readfds); FD_SET(*static_cast(FSocket), &readfds); time.tv_sec = Sec; time.tv_usec = 0; int R = select(1, &readfds, NULL, NULL, &time); if (R < 0) { SSH_FATAL_ERROR(FMTLOAD(UNKNOWN_SOCKET_STATUS, (R))); } if (Configuration->LogProtocol >= 2) { LogEvent(FORMAT("Select result is %d", (R))); } return (R > 0); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::PoolForData(unsigned int & Result) { if (Configuration->LogProtocol >= 2) { LogEvent("Pooling for data in case they finally arrives"); } if (Select(0)) { LogEvent("Data has arrived, closing query to user."); Result = qaRetry; } } //--------------------------------------------------------------------------- extern int select_result(WPARAM, LPARAM); void __fastcall TSecureShell::WaitForData(bool Sending) { bool NeedToWait = true; SOCKET & Socket = *static_cast(FSocket); if (socket_writable(Socket)) { if (Configuration->LogProtocol >= 1) { LogEvent("Checking low level send buffer"); } select_result((WPARAM)(Socket), (LPARAM)FD_WRITE); } if (FBufSize > 0) { if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("Trying to dispatch send buffer (%u bytes)", (FBufSize))); } int NewBufSize = FBackend->send(FBackendHandle, "", 0); if ((NewBufSize < FBufSize) && Sending) { NeedToWait = false; } FBufSize = NewBufSize; if (Configuration->LogProtocol >= 1) { LogEvent(FORMAT("There are %u bytes remaining in the send buffer", (FBufSize))); } } bool IncomingData; if (!NeedToWait) { if (Configuration->LogProtocol >= 2) { LogEvent("Looking for incoming data"); } // This is just attempt to make it as close as possible to previous behaviour // Maybe it is not necessary at all. IncomingData = Select(0); } else { do { if (Configuration->LogProtocol >= 2) { LogEvent("Looking for incoming data"); } IncomingData = Select(FSessionData->Timeout); if (!IncomingData) { LogEvent("Waiting for data timed out, asking user what to do."); TQueryParams Params(qpFatalAbort | qpAllowContinueOnError); Params.Timer = 500; Params.TimerEvent = PoolForData; Params.TimerMessage = FMTLOAD(TIMEOUT_STILL_WAITING, (FSessionData->Timeout)); Params.TimerAnswers = qaAbort; if (DoQueryUser(FMTLOAD(CONFIRM_PROLONG_TIMEOUT, (FSessionData->Timeout)), qaRetry | qaAbort, &Params) != qaRetry) { FatalError(LoadStr(USER_TERMINATED)); } } } while (!IncomingData); } if (IncomingData) { select_result((WPARAM)(Socket), (LPARAM)FD_READ); } } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::SshFallbackCmd() const { return ssh_fallback_cmd(FBackendHandle); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::Error(const AnsiString Error) const { SSH_ERROR(Error); } //--------------------------------------------------------------------------- extern int (WINAPI *p_WSAEnumNetworkEvents) (SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents); //--------------------------------------------------------------------------- void __fastcall TSecureShell::Idle() { if (Configuration->LogProtocol >= 1) { LogEvent("Session upkeep"); } noise_regular(); // Keep session alive if ((FSessionData->PingType != ptOff) && (Now() - FLastDataSent > FSessionData->PingIntervalDT)) { KeepAlive(); // in case keep alive could not be processed, postpone next attempt FLastDataSent = Now(); } call_ssh_timer(FBackendHandle); // to let detect dropped connection immediatelly, also to let // process SSH-level communication with the server (KEX particularly) if (Select(0)) { LogEvent("Detected incoming data while idle"); select_result((WPARAM)(*static_cast(FSocket)), (LPARAM)FD_READ); CheckConnection(); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::KeepAlive() { LogEvent("Sending null packet to keep session alive."); SendSpecial(TS_PING); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SetLog(TSessionLog * value) { FLog->Assign(value); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SetConfiguration(TConfiguration *value) { FConfiguration = value; Log->Configuration = value; } //--------------------------------------------------------------------------- TDateTime __fastcall TSecureShell::GetDuration() const { return Now() - FLoginTime; } //--------------------------------------------------------------------------- int __fastcall TSecureShell::RemainingSendBuffer() { return MAX_BUFSIZE - FBufSize; } //--------------------------------------------------------------------------- unsigned long __fastcall TSecureShell::MaxPacketSize() { if (SshVersion == 1) { return 0; } else { if (FMaxPacketSize == NULL) { FMaxPacketSize = ssh2_remmaxpkt(FBackendHandle); } return *FMaxPacketSize; } } //--------------------------------------------------------------------------- TCompressionType __fastcall TSecureShell::FuncToCompression( const void * Compress) const { if (SshVersion == 1) { return get_ssh1_compressing(FBackendHandle) ? ctZLib : ctNone; } else { return (ssh_compress *)Compress == &ssh_zlib ? ctZLib : ctNone; } } //--------------------------------------------------------------------------- TCompressionType __fastcall TSecureShell::GetCSCompression() const { assert(Active); return FuncToCompression(get_cscomp(FBackendHandle)); } //--------------------------------------------------------------------------- TCompressionType __fastcall TSecureShell::GetSCCompression() const { assert(Active); return FuncToCompression(get_sccomp(FBackendHandle)); } //--------------------------------------------------------------------------- int __fastcall TSecureShell::GetSshVersion() const { assert(Active); return get_ssh_version(FBackendHandle); } //--------------------------------------------------------------------------- TCipher __fastcall TSecureShell::FuncToSsh1Cipher(const void * Cipher) const { const ssh_cipher *CipherFuncs[] = {&ssh_3des, &ssh_des, &ssh_blowfish_ssh1}; const TCipher TCiphers[] = {cip3DES, cipDES, cipBlowfish}; assert(LENOF(CipherFuncs) == LENOF(TCiphers)); TCipher Result = cipWarn; for (int Index = 0; Index < LENOF(TCiphers); Index++) { if ((ssh_cipher *)Cipher == CipherFuncs[Index]) { Result = TCiphers[Index]; } } assert(Result != cipWarn); return Result; } //--------------------------------------------------------------------------- TCipher __fastcall TSecureShell::FuncToSsh2Cipher(const void * Cipher) const { const ssh2_ciphers *CipherFuncs[] = {&ssh2_3des, &ssh2_des, &ssh2_aes, &ssh2_blowfish}; const TCipher TCiphers[] = {cip3DES, cipDES, cipAES, cipBlowfish}; assert(LENOF(CipherFuncs) == LENOF(TCiphers)); TCipher Result = cipWarn; for (int C = 0; C < LENOF(TCiphers); C++) { for (int F = 0; F < CipherFuncs[C]->nciphers; F++) { if ((ssh2_cipher *)Cipher == CipherFuncs[C]->list[F]) { Result = TCiphers[C]; } } } assert(Result != cipWarn); return Result; } //--------------------------------------------------------------------------- TCipher __fastcall TSecureShell::GetCSCipher() { assert(Active); if (FCSCipher == cipWarn) { if (SshVersion == 1) { FCSCipher = FuncToSsh1Cipher(get_cipher(FBackendHandle)); } else { FCSCipher = FuncToSsh2Cipher(get_cscipher(FBackendHandle)); } } return FCSCipher; } //--------------------------------------------------------------------------- TCipher __fastcall TSecureShell::GetSCCipher() { CheckConnection(); if (FSCCipher == cipWarn) { if (SshVersion == 1) { FSCCipher = FuncToSsh1Cipher(get_cipher(FBackendHandle)); } else { FSCCipher = FuncToSsh2Cipher(get_sccipher(FBackendHandle)); } } return FSCCipher; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::DoShowExtendedException(Exception * E) { DoHandleExtendedException(E); if (OnShowExtendedException != NULL) { OnShowExtendedException(this, E, NULL); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::DoHandleExtendedException(Exception * E) { Log->AddException(E); } //--------------------------------------------------------------------------- int __fastcall TSecureShell::DoQueryUser(const AnsiString Query, TStrings * MoreMessages, int Answers, const TQueryParams * Params, TQueryType Type) { LogEvent(FORMAT("Asking user:\n%s (%s)", (Query, (MoreMessages ? MoreMessages->CommaText : AnsiString() )))); int Answer = AbortAnswer(Answers); if (FOnQueryUser) { FOnQueryUser(this, Query, MoreMessages, Answers, Params, Answer, Type, NULL); } return Answer; } //--------------------------------------------------------------------------- int __fastcall TSecureShell::DoQueryUser(const AnsiString Query, const AnsiString OtherMessage, int Answers, const TQueryParams * Params, TQueryType Type) { TStrings * MoreMessages = new TStringList(); Integer Result; try { if (!OtherMessage.IsEmpty()) MoreMessages->Add(OtherMessage); Result = DoQueryUser(Query, MoreMessages, Answers, Params, Type); } __finally { delete MoreMessages; } return Result; } //--------------------------------------------------------------------------- int __fastcall TSecureShell::DoQueryUser(const AnsiString Query, int Answers, const TQueryParams * Params, TQueryType Type) { return DoQueryUser(Query, "", Answers, Params, Type); } //--------------------------------------------------------------------------- int __fastcall TSecureShell::DoQueryUser(const AnsiString Query, Exception * E, int Answers, const TQueryParams * Params, TQueryType Type) { int Result; TStrings * MoreMessages = new TStringList(); try { if (E->InheritsFrom(__classid(ExtException)) && ((ExtException*)E)->MoreMessages) { MoreMessages->Assign(((ExtException*)E)->MoreMessages); } if (!E->Message.IsEmpty() && !Query.IsEmpty()) MoreMessages->Insert(0, E->Message); Result = DoQueryUser(!Query.IsEmpty() ? Query : E->Message, MoreMessages->Count ? MoreMessages : NULL, Answers, Params, Type); } __finally { delete MoreMessages; } return Result; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::VerifyHostKey(const AnsiString Host, int Port, const AnsiString KeyType, const AnsiString KeyStr, const AnsiString Fingerprint) { GotHostKey(); int Result; FHostKeyFingerprint = Fingerprint; // Verify the key against the registry. Result = verify_host_key(Host.c_str(), Port, KeyType.c_str(), KeyStr.c_str()); if (Result != 0) { if (Configuration->DisableAcceptingHostKeys) { FatalError(LoadStr(KEY_NOT_VERIFIED)); } else { TQueryParams Params; Params.HelpKeyword = (Result == 1 ? HELP_UNKNOWN_KEY : HELP_DIFFERENT_KEY); int R = DoQueryUser( FMTLOAD((Result == 1 ? UNKNOWN_KEY2 : DIFFERENT_KEY2), (KeyType, Fingerprint)), qaYes | qaNo | qaCancel, &Params, qtWarning); switch (R) { case qaYes: store_host_key(Host.c_str(), Port, KeyType.c_str(), KeyStr.c_str()); break; case qaCancel: FatalError(LoadStr(KEY_NOT_VERIFIED)); } } } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::AskAlg(const AnsiString AlgType, const AnsiString AlgName) { AnsiString Msg; if (AlgType == "key-exchange algorithm") { Msg = FMTLOAD(KEX_BELOW_TRESHOLD, (AlgName)); } else { int CipherType; if (AlgType == "cipher") { CipherType = CIPHER_TYPE_BOTH; } else if (AlgType == "client-to-server cipher") { CipherType = CIPHER_TYPE_CS; } else if (AlgType == "server-to-client cipher") { CipherType = CIPHER_TYPE_SC; } else { assert(false); } Msg = FMTLOAD(CIPHER_BELOW_TRESHOLD, (LoadStr(CipherType), AlgName)); } if (DoQueryUser(Msg, qaYes | qaNo, NULL, qtWarning) == qaNo) { Abort(); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::DoDisplayBanner(const AnsiString & Banner, bool & Log) { if (OnDisplayBanner != NULL) { Log = false; AddStdError(Banner, true); if (Configuration->ForceBanners || Configuration->ShowBanner(SessionData->SessionKey, Banner)) { bool NeverShowAgain = false; int Options = FLAGMASK(Configuration->ForceBanners, boDisableNeverShowAgain); OnDisplayBanner(this, SessionData->SessionName, Banner, NeverShowAgain, Options); if (!Configuration->ForceBanners && NeverShowAgain) { Configuration->NeverShowBanner(SessionData->SessionKey, Banner); } } } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::DisplayBanner(const AnsiString & Banner, bool & Log) { DoDisplayBanner(Banner, Log); } //--------------------------------------------------------------------------- void __fastcall TSecureShell::OldKeyfileWarning() { DoQueryUser(LoadStr(OLD_KEY), qaOK, NULL, qtWarning); } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::DoQueryReopen(Exception * E, int Params) { bool Result; if (FLAGSET(Params, ropNoConfirmation)) { Result = true; } else { LogEvent("Connection was lost, asking what to do."); TQueryParams Params(qpAllowContinueOnError); Params.Timeout = Configuration->SessionReopenAuto; Params.TimeoutAnswer = qaRetry; TQueryButtonAlias Aliases[1]; Aliases[0].Button = qaRetry; Aliases[0].Alias = LoadStr(RECONNECT_BUTTON); Params.Aliases = Aliases; Params.AliasesCount = LENOF(Aliases); Result = (DoQueryUser("", E, qaRetry | qaAbort, &Params, qtError) == qaRetry); } return Result; } //--------------------------------------------------------------------------- bool __fastcall TSecureShell::QueryReopen(Exception * E, int Params) { bool Result = DoQueryReopen(E, Params); if (Result) { do { try { if (FLAGSET(Params, ropNoConfirmation)) { Sleep(Configuration->SessionReopenNoConfirmation); } Reopen(Params); } catch(Exception & E) { if (!Active) { Result = DoQueryReopen(&E, Params); } else { throw; } } } while (!Active && Result); } return Result; } //--------------------------------------------------------------------------- int __fastcall TSecureShell::GetStatus() const { return FStatus; } //--------------------------------------------------------------------------- void __fastcall TSecureShell::UpdateStatus(int Value, bool Active) { bool Update = (FStatus != Value) && Active; if (Update) { FStatus = Value; if (FStatus > FReachedStatus) FReachedStatus = FStatus; } if ((Update || !Active) && (FOnUpdateStatus != NULL)) { FOnUpdateStatus(this, Active); } } //--------------------------------------------------------------------------- void __fastcall TSecureShell::SetUserObject(TObject * value) { if (UserObject != value) { if (UserObject) { delete UserObject; } FUserObject = value; } } //=== TSessionLog ----------------------------------------------------------- const char *LogLineMarks = "<>!.*"; __fastcall TSessionLog::TSessionLog(TSecureShell * AOwner): TStringList() { FEnabled = true; FOwner = AOwner; FFile = NULL; FLoggedLines = 0; FTopIndex = -1; FFileName = ""; FId = 0; } //--------------------------------------------------------------------------- __fastcall TSessionLog::~TSessionLog() { CloseLogFile(); } //--------------------------------------------------------------------------- AnsiString __fastcall TSessionLog::GetSessionName() { assert(FOwner != NULL); assert(FOwner->SessionData != NULL); return FOwner->SessionData->SessionName; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::SetLine(Integer Index, AnsiString value) { Strings[Index] = AnsiString(Strings[Index - FTopIndex][1]) + value; } //--------------------------------------------------------------------------- AnsiString __fastcall TSessionLog::GetLine(Integer Index) { return Strings[Index - FTopIndex].SubString(2, Strings[Index - FTopIndex].Length() - 1); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::SetType(Integer Index, TLogLineType value) { Strings[Index - FTopIndex][1] = LogLineMarks[value]; } //--------------------------------------------------------------------------- TLogLineType __fastcall TSessionLog::GetType(Integer Index) { int P = AnsiString(LogLineMarks).Pos(Strings[Index - FTopIndex][1]); if (P) { return TLogLineType(P - 1); } else { assert(false); return (TLogLineType)0; } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::DoAdd(TLogLineType aType, AnsiString aLine) { assert(Configuration); if (IsLogging()) { try { if (Configuration->Logging) { BeginUpdate(); } try { while (!aLine.IsEmpty()) { AnsiString MarkStr = LogLineMarks[aType]; AnsiString NewStr = CutToChar(aLine, '\n', False); if (Configuration->Logging) { TStringList::Add(MarkStr + NewStr); FLoggedLines++; } DoAddLine(aType, NewStr); if (Configuration->Logging && Configuration->LogToFile) { if (!FFile) OpenLogFile(); if (FFile) { AnsiString Timestamp = FormatDateTime(" yyyy-mm-dd hh:nn:ss.zzz ", Now()); AnsiString Buf = MarkStr + Timestamp + NewStr; // use fwrite instead of fprintf to make sure that even // non-ascii data (unicode) gets in. fwrite(Buf.c_str(), Buf.Length(), 1, (FILE *)FFile); fputc('\n', (FILE *)FFile); } } } if (Configuration->Logging) { if (FTopIndex < 0) FTopIndex = 0; DeleteUnnecessary(); } } __finally { if (Configuration->Logging) { EndUpdate(); } } } catch (Exception &E) { // We failed logging, turn it of and notify user. Configuration->Logging = False; try { throw ExtException(&E, LOG_ERROR); } catch (Exception &E) { FOwner->DoShowExtendedException(&E); } } } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::Add(TLogLineType aType, AnsiString aLine) { DoAdd(aType, aLine); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddFromOtherLog(TObject * /*Sender*/, TLogLineType aType, const AnsiString AddedLine) { DoAdd(aType, AddedLine); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddException(Exception * E) { if (E) { Add(llException, ExceptionLogString(E)); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::SetConfiguration(TConfiguration * value) { if (FConfiguration != value) { FConfiguration = value; ReflectSettings(); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::ReflectSettings() { // if logging to file was turned off or log file was change -> close current log file if (FFile && (!LogToFile || (FFileName != LogFileName))) CloseLogFile(); // we used to open log file here, now it is posponed until first call to DoAdd() // this allows TSecureShell to change the Id sooner. DeleteUnnecessary(); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::SetEnabled(bool value) { if (FEnabled != value) { FEnabled = value; ReflectSettings(); } } //--------------------------------------------------------------------------- Boolean __fastcall TSessionLog::GetLogToFile() { return Enabled && (Configuration != NULL) && Configuration->Logging && Configuration->LogToFile; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::CloseLogFile() { if (FFile) { fclose((FILE *)FFile); FFile = NULL; } FFileName = ""; } //--------------------------------------------------------------------------- void TSessionLog::OpenLogFile() { if (LogToFile) { try { assert(!FFile); assert(Configuration); AnsiString NewFileName = LogFileName; TDateTime N = Now(); for (int Index = 1; Index < NewFileName.Length(); Index++) { if (NewFileName[Index] == '&') { AnsiString Replacement; switch (tolower(NewFileName[Index + 1])) { case 'y': Replacement = FormatDateTime("yyyy", N); break; case 'm': Replacement = FormatDateTime("mm", N); break; case 'd': Replacement = FormatDateTime("dd", N); break; case 't': Replacement = FormatDateTime("hhnnss", N); break; case 'h': Replacement = MakeValidFileName(FOwner->SessionData->HostName); break; case 's': Replacement = MakeValidFileName(FOwner->SessionData->SessionName); break; case '&': Replacement = "&"; break; default: Replacement = AnsiString("&") + NewFileName[Index + 1]; break; } NewFileName.Delete(Index, 2); NewFileName.Insert(Replacement, Index); Index += Replacement.Length() - 1; } } if (Id != 0) { NewFileName = FORMAT("%s%s.%.8x%s", (ExtractFilePath(NewFileName), ExtractFileName(NewFileName), static_cast(FId), ExtractFileExt(NewFileName))); } FFile = fopen(NewFileName.c_str(), (Configuration->LogFileAppend ? "a" : "w")); if (FFile) { setvbuf((FILE *)FFile, NULL, _IONBF, BUFSIZ); FFileName = NewFileName; } else { throw Exception(FMTLOAD(LOG_OPENERROR, (NewFileName))); } } catch (Exception &E) { // We failed logging to file, turn it of and notify user. FFileName = ""; Configuration->LogToFile = false; try { throw ExtException(&E, LOG_ERROR); } catch (Exception &E) { FOwner->DoShowExtendedException(&E); } } } } //--------------------------------------------------------------------------- void TSessionLog::DeleteUnnecessary() { BeginUpdate(); try { if (!Configuration || !Configuration->Logging) { Clear(); } else { while (!Configuration->LogWindowComplete && (Count > Configuration->LogWindowLines)) { Delete(0); FTopIndex++; } } } __finally { EndUpdate(); } } //--------------------------------------------------------------------------- TColor __fastcall TSessionLog::GetColor(int Index) { return LogLineColors[Type[Index]]; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::DoAddLine(TLogLineType Type, const AnsiString AddedLine) { if (FOnAddLine) { FOnAddLine(this, Type, AddedLine); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddStartupInfo() { assert(FOwner != NULL); TSessionData * Data = FOwner->SessionData; assert(Data != NULL); assert(Configuration); if (Configuration->Logging || FOnAddLine) { BeginUpdate(); try { #define ADF(S, F) Add(llMessage, FORMAT(S, F)); AddSeparator(); ADF("WinSCP %s (OS %s)", (Configuration->VersionStr, Configuration->OSVersionStr)); ADF("Login time: %s", (FormatDateTime("dddddd tt", Now()))); AddSeparator(); ADF("Session name: %s", (Data->SessionName)); ADF("Host name: %s (Port: %d)", (Data->HostName, Data->PortNumber)); ADF("User name: %s (Password: %s, Key file: %s)", (Data->UserName, BooleanToEngStr(!Data->Password.IsEmpty()), BooleanToEngStr(!Data->PublicKeyFile.IsEmpty()))) ADF("Transfer Protocol: %s", (Data->FSProtocolStr)); ADF("SSH protocol version: %s; Compression: %s", (Data->SshProtStr, BooleanToEngStr(Data->Compression))); ADF("Agent forwarding: %s; TIS/CryptoCard: %s; KI: %s; GSSAPI: %s", (BooleanToEngStr(Data->AgentFwd), BooleanToEngStr(Data->AuthTIS), BooleanToEngStr(Data->AuthKI), BooleanToEngStr(Data->AuthGSSAPI))); ADF("Ciphers: %s; Ssh2DES: %s", (Data->CipherList, BooleanToEngStr(Data->Ssh2DES))); char * PingTypes = "-NC"; ADF("Ping type: %s, Ping interval: %d sec; Timeout: %d sec", (AnsiString(PingTypes[Data->PingType]), Data->PingInterval, Data->Timeout)); AnsiString Bugs; char const * BugFlags = "A+-"; for (int Index = 0; Index < BUG_COUNT; Index++) { Bugs += AnsiString(BugFlags[Data->Bug[(TSshBug)Index]])+(IndexSFTPBug[(TSftpBug)Index]])+(IndexProxyMethod])); if (Data->ProxyMethod != pmNone) { ADF("HostName: %s (Port: %d); Username: %s; Passwd: %s", (Data->ProxyHost, Data->ProxyPort, Data->ProxyUsername, BooleanToEngStr(!Data->ProxyPassword.IsEmpty()))); if (Data->ProxyMethod == pmTelnet) { ADF("Telnet command: %s", (Data->ProxyTelnetCommand)); } } ADF("Return code variable: %s; Lookup user groups: %s", ((Data->DetectReturnVar ? AnsiString("Autodetect") : Data->ReturnVar), BooleanToEngStr(Data->LookupUserGroups))); ADF("Shell: %s, EOL: %d", ((Data->Shell.IsEmpty()? AnsiString("default") : Data->Shell), Data->EOLType)); ADF("Local directory: %s, Remote directory: %s, Update: %s, Cache: %s", ((Data->LocalDirectory.IsEmpty() ? AnsiString("default") : Data->LocalDirectory), (Data->RemoteDirectory.IsEmpty() ? AnsiString("home") : Data->RemoteDirectory), BooleanToEngStr(Data->UpdateDirectories), BooleanToEngStr(Data->CacheDirectories))); ADF("Cache directory changes: %s, Permanent: %s", (BooleanToEngStr(Data->CacheDirectoryChanges), BooleanToEngStr(Data->PreserveDirectoryChanges))); ADF("Clear aliases: %s, Unset nat.vars: %s, Resolve symlinks: %s", (BooleanToEngStr(Data->ClearAliases), BooleanToEngStr(Data->UnsetNationalVars), BooleanToEngStr(Data->ResolveSymlinks))); ADF("Alias LS: %s, Ign LS warn: %s, Scp1 Comp: %s", (BooleanToEngStr(Data->AliasGroupList), BooleanToEngStr(Data->IgnoreLsWarnings), BooleanToEngStr(Data->Scp1Compatibility))); AddSeparator(); #undef ADF } __finally { EndUpdate(); } } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddSeparator() { Add(llMessage, "--------------------------------------------------------------------------"); } //--------------------------------------------------------------------------- int __fastcall TSessionLog::GetIndexes(int Index) { assert((Index >= 0) && (Index < Count)); int Result = TopIndex + Index; assert((Result >= 0) && (Result < LoggedLines)); return Result; } //--------------------------------------------------------------------------- int __fastcall TSessionLog::GetBottomIndex() { return (Count ? Indexes[Count-1] : -1); } //--------------------------------------------------------------------------- AnsiString __fastcall TSessionLog::GetLogFileName() { assert(Configuration); AnsiString Result = StripPathQuotes(Configuration->LogFileName); return Result; } //--------------------------------------------------------------------------- Boolean __fastcall TSessionLog::GetLoggingToFile() { return (Configuration && Configuration->Logging && Configuration->LogToFile && FFile); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::Clear() { FTopIndex += Count; TStringList::Clear(); }