//--------------------------------------------------------------------------- #include #pragma hdrstop #include #include #define SECURITY_WIN32 #include #include #include "Common.h" #include "SessionInfo.h" #include "Exceptions.h" #include "TextsCore.h" #include "CoreMain.h" #include "Script.h" #include //--------------------------------------------------------------------------- #pragma package(smart_init) //--------------------------------------------------------------------------- UnicodeString __fastcall DoXmlEscape(UnicodeString Str, bool NewLine) { for (int i = 1; i <= Str.Length(); i++) { UnicodeString Repl; switch (Str[i]) { case L'\x00': // \0 Is not valid in XML anyway case L'\x01': case L'\x02': case L'\x03': case L'\x04': case L'\x05': case L'\x06': case L'\x07': case L'\x08': // \n is handled below case L'\x0B': case L'\x0C': // \r is handled below case L'\x0E': case L'\x0F': case L'\x10': case L'\x11': case L'\x12': case L'\x13': case L'\x14': case L'\x15': case L'\x16': case L'\x17': case L'\x18': case L'\x19': case L'\x1A': case L'\x1B': case L'\x1C': case L'\x1D': case L'\x1E': case L'\x1F': Repl = L"#x" + ByteToHex((unsigned char)Str[i]) + L";"; break; case L'&': Repl = L"amp;"; break; case L'>': Repl = L"gt;"; break; case L'<': Repl = L"lt;"; break; case L'"': Repl = L"quot;"; break; case L'\n': if (NewLine) { Repl = L"#10;"; } break; case L'\r': Str.Delete(i, 1); i--; break; } if (!Repl.IsEmpty()) { Str[i] = L'&'; Str.Insert(Repl, i + 1); i += Repl.Length(); } } return Str; } //--------------------------------------------------------------------------- UnicodeString __fastcall XmlEscape(UnicodeString Str) { return DoXmlEscape(Str, false); } //--------------------------------------------------------------------------- UnicodeString __fastcall XmlAttributeEscape(UnicodeString Str) { return DoXmlEscape(Str, true); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #pragma warn -inl class TSessionActionRecord { public: __fastcall TSessionActionRecord(TActionLog * Log, TLogAction Action) : FLog(Log), FAction(Action), FState(Opened), FRecursive(false), FErrorMessages(NULL), FNames(new TStringList()), FValues(new TStringList()), FFileList(NULL), FFile(NULL) { FLog->AddPendingAction(this); } __fastcall ~TSessionActionRecord() { delete FErrorMessages; delete FNames; delete FValues; delete FFileList; delete FFile; } void __fastcall Restart() { FState = Opened; FRecursive = false; delete FErrorMessages; FErrorMessages = NULL; delete FFileList; FFileList = NULL; delete FFile; FFile = NULL; FNames->Clear(); FValues->Clear(); } bool __fastcall Record() { bool Result = (FState != Opened); if (Result) { if (FLog->FLogging && (FState != Cancelled)) { const wchar_t * Name = ActionName(); UnicodeString Attrs; if (FRecursive) { Attrs = L" recursive=\"true\""; } FLog->AddIndented(FORMAT(L"<%s%s>", (Name, Attrs))); for (int Index = 0; Index < FNames->Count; Index++) { UnicodeString Value = FValues->Strings[Index]; if (Value.IsEmpty()) { FLog->AddIndented(FORMAT(L" <%s />", (FNames->Strings[Index]))); } else { FLog->AddIndented(FORMAT(L" <%s value=\"%s\" />", (FNames->Strings[Index], XmlAttributeEscape(Value)))); } } if (FFileList != NULL) { FLog->AddIndented(L" "); for (int Index = 0; Index < FFileList->Count; Index++) { TRemoteFile * File = FFileList->Files[Index]; RecordFile(L" ", File, true); } FLog->AddIndented(L" "); } if (FFile != NULL) { RecordFile(L" ", FFile, false); } if (FState == RolledBack) { if (FErrorMessages != NULL) { FLog->AddIndented(L" "); FLog->AddMessages(L" ", FErrorMessages); FLog->AddIndented(L" "); } else { FLog->AddIndented(L" "); } } else { FLog->AddIndented(L" "); } FLog->AddIndented(FORMAT(L"", (Name))); } delete this; } return Result; } void __fastcall Commit() { Close(Committed); } void __fastcall Rollback(Exception * E) { DebugAssert(FErrorMessages == NULL); FErrorMessages = ExceptionToMoreMessages(E); Close(RolledBack); } void __fastcall Cancel() { Close(Cancelled); } void __fastcall FileName(const UnicodeString & FileName) { Parameter(L"filename", FileName); } void __fastcall Destination(const UnicodeString & Destination) { Parameter(L"destination", Destination); } void __fastcall Rights(const TRights & Rights) { Parameter(L"permissions", Rights.Text); } void __fastcall Modification(const TDateTime & DateTime) { Parameter(L"modification", StandardTimestamp(DateTime)); } void __fastcall Recursive() { FRecursive = true; } void __fastcall Command(const UnicodeString & Command) { Parameter(L"command", Command); } void __fastcall AddOutput(UnicodeString Output, bool StdError) { const wchar_t * Name = (StdError ? L"erroroutput" : L"output"); int Index = FNames->IndexOf(Name); if (Index >= 0) { FValues->Strings[Index] = FValues->Strings[Index] + L"\r\n" + Output; } else { Parameter(Name, Output); } } void __fastcall ExitCode(int ExitCode) { Parameter(L"exitcode", IntToStr(ExitCode)); } void __fastcall Checksum(const UnicodeString & Alg, const UnicodeString & Checksum) { Parameter(L"algorithm", Alg); Parameter(L"checksum", Checksum); } void __fastcall Cwd(const UnicodeString & Path) { Parameter(L"cwd", Path); } void __fastcall FileList(TRemoteFileList * FileList) { if (FFileList == NULL) { FFileList = new TRemoteFileList(); } FileList->DuplicateTo(FFileList); } void __fastcall File(TRemoteFile * File) { if (FFile != NULL) { delete FFile; } FFile = File->Duplicate(true); } void __fastcall SynchronizeChecklistItem(const TSynchronizeChecklist::TItem * Item) { UnicodeString Action; bool RecordLocal = false; bool RecordRemote = false; switch (Item->Action) { case TSynchronizeChecklist::saUploadNew: Action = L"uploadnew"; RecordLocal = true; break; case TSynchronizeChecklist::saDownloadNew: Action = L"downloadnew"; RecordRemote = true; break; case TSynchronizeChecklist::saUploadUpdate: Action = L"uploadupdate"; RecordLocal = true; RecordRemote = true; break; case TSynchronizeChecklist::saDownloadUpdate: Action = L"downloadupdate"; RecordLocal = true; RecordRemote = true; break; case TSynchronizeChecklist::saDeleteRemote: Action = L"deleteremote"; RecordRemote = true; break; case TSynchronizeChecklist::saDeleteLocal: Action = L"deletelocal"; RecordLocal = true; break; default: DebugFail(); break; } Parameter(L"action", Action); if (RecordLocal) { UnicodeString FileName = TPath::Combine(Item->Local.Directory, Item->Local.FileName); SynchronizeChecklistItemFileInfo(FileName, Item->IsDirectory, Item->Local); } if (RecordRemote) { UnicodeString FileName = UnixCombinePaths(Item->Remote.Directory, Item->Remote.FileName); SynchronizeChecklistItemFileInfo(FileName, Item->IsDirectory, Item->Remote); } } protected: enum TState { Opened, Committed, RolledBack, Cancelled }; inline void __fastcall Close(TState State) { DebugAssert(FState == Opened); FState = State; FLog->RecordPendingActions(); } const wchar_t * __fastcall ActionName() { switch (FAction) { case laUpload: return L"upload"; case laDownload: return L"download"; case laTouch: return L"touch"; case laChmod: return L"chmod"; case laMkdir: return L"mkdir"; case laRm: return L"rm"; case laMv: return L"mv"; case laCp: return L"cp"; case laCall: return L"call"; case laLs: return L"ls"; case laStat: return L"stat"; case laChecksum: return L"checksum"; case laCwd: return L"cwd"; case laDifference: return L"difference"; default: DebugFail(); return L""; } } void __fastcall Parameter(const UnicodeString & Name, const UnicodeString & Value = L"") { FNames->Add(Name); FValues->Add(Value); } void __fastcall RecordFile(const UnicodeString & Indent, TRemoteFile * File, bool IncludeFileName) { FLog->AddIndented(Indent + L""); FLog->AddIndented(Indent + FORMAT(L" ", (XmlAttributeEscape(File->FileName)))); FLog->AddIndented(Indent + FORMAT(L" ", (XmlAttributeEscape(towupper(File->Type))))); if (!File->IsDirectory) { FLog->AddIndented(Indent + FORMAT(L" ", (IntToStr(File->Size)))); } if (File->ModificationFmt != mfNone) { FLog->AddIndented(Indent + FORMAT(L" ", (StandardTimestamp(File->Modification)))); } if (!File->Rights->Unknown) { FLog->AddIndented(Indent + FORMAT(L" ", (XmlAttributeEscape(File->Rights->Text)))); } if (File->Owner.IsSet) { FLog->AddIndented(Indent + FORMAT(L" ", (XmlAttributeEscape(File->Owner.DisplayText)))); } if (File->Group.IsSet) { FLog->AddIndented(Indent + FORMAT(L" ", (XmlAttributeEscape(File->Group.DisplayText)))); } FLog->AddIndented(Indent + L""); } void __fastcall SynchronizeChecklistItemFileInfo( const UnicodeString & AFileName, bool IsDirectory, const TSynchronizeChecklist::TItem::TFileInfo FileInfo) { Parameter(L"type", (IsDirectory ? L'D' : L'-')); FileName(AFileName); if (!IsDirectory) { Parameter(L"size", IntToStr(FileInfo.Size)); } if (FileInfo.ModificationFmt != mfNone) { Modification(FileInfo.Modification); } } private: TActionLog * FLog; TLogAction FAction; TState FState; bool FRecursive; TStrings * FErrorMessages; TStrings * FNames; TStrings * FValues; TRemoteFileList * FFileList; TRemoteFile * FFile; }; #pragma warn .inl //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TSessionAction::TSessionAction(TActionLog * Log, TLogAction Action) { FCancelled = false; if (Log->FLogging) { FRecord = new TSessionActionRecord(Log, Action); } else { FRecord = NULL; } } //--------------------------------------------------------------------------- __fastcall TSessionAction::~TSessionAction() { if (FRecord != NULL) { TSessionActionRecord * Record = FRecord; FRecord = NULL; Record->Commit(); } } //--------------------------------------------------------------------------- void __fastcall TSessionAction::Restart() { if (FRecord != NULL) { FRecord->Restart(); } } //--------------------------------------------------------------------------- void __fastcall TSessionAction::Rollback(Exception * E) { if (FRecord != NULL) { TSessionActionRecord * Record = FRecord; FRecord = NULL; Record->Rollback(E); } } //--------------------------------------------------------------------------- void __fastcall TSessionAction::Cancel() { if (FRecord != NULL) { TSessionActionRecord * Record = FRecord; FRecord = NULL; FCancelled = true; Record->Cancel(); } } //--------------------------------------------------------------------------- bool __fastcall TSessionAction::IsValid() { return !FCancelled; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TFileSessionAction::TFileSessionAction(TActionLog * Log, TLogAction Action) : TSessionAction(Log, Action) { } //--------------------------------------------------------------------------- __fastcall TFileSessionAction::TFileSessionAction( TActionLog * Log, TLogAction Action, const UnicodeString & AFileName) : TSessionAction(Log, Action) { FileName(AFileName); } //--------------------------------------------------------------------------- void __fastcall TFileSessionAction::FileName(const UnicodeString & FileName) { if (FRecord != NULL) { FRecord->FileName(FileName); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TFileLocationSessionAction::TFileLocationSessionAction( TActionLog * Log, TLogAction Action) : TFileSessionAction(Log, Action) { } //--------------------------------------------------------------------------- __fastcall TFileLocationSessionAction::TFileLocationSessionAction( TActionLog * Log, TLogAction Action, const UnicodeString & FileName) : TFileSessionAction(Log, Action, FileName) { } //--------------------------------------------------------------------------- void __fastcall TFileLocationSessionAction::Destination(const UnicodeString & Destination) { if (FRecord != NULL) { FRecord->Destination(Destination); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TUploadSessionAction::TUploadSessionAction(TActionLog * Log) : TFileLocationSessionAction(Log, laUpload) { } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TDownloadSessionAction::TDownloadSessionAction(TActionLog * Log) : TFileLocationSessionAction(Log, laDownload) { } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TChmodSessionAction::TChmodSessionAction( TActionLog * Log, const UnicodeString & FileName) : TFileSessionAction(Log, laChmod, FileName) { } //--------------------------------------------------------------------------- void __fastcall TChmodSessionAction::Recursive() { if (FRecord != NULL) { FRecord->Recursive(); } } //--------------------------------------------------------------------------- __fastcall TChmodSessionAction::TChmodSessionAction( TActionLog * Log, const UnicodeString & FileName, const TRights & ARights) : TFileSessionAction(Log, laChmod, FileName) { Rights(ARights); } //--------------------------------------------------------------------------- void __fastcall TChmodSessionAction::Rights(const TRights & Rights) { if (FRecord != NULL) { FRecord->Rights(Rights); } } //--------------------------------------------------------------------------- __fastcall TTouchSessionAction::TTouchSessionAction( TActionLog * Log, const UnicodeString & FileName, const TDateTime & Modification) : TFileSessionAction(Log, laTouch, FileName) { if (FRecord != NULL) { FRecord->Modification(Modification); } } //--------------------------------------------------------------------------- __fastcall TMkdirSessionAction::TMkdirSessionAction( TActionLog * Log, const UnicodeString & FileName) : TFileSessionAction(Log, laMkdir, FileName) { } //--------------------------------------------------------------------------- __fastcall TRmSessionAction::TRmSessionAction( TActionLog * Log, const UnicodeString & FileName) : TFileSessionAction(Log, laRm, FileName) { } //--------------------------------------------------------------------------- void __fastcall TRmSessionAction::Recursive() { if (FRecord != NULL) { FRecord->Recursive(); } } //--------------------------------------------------------------------------- __fastcall TMvSessionAction::TMvSessionAction(TActionLog * Log, const UnicodeString & FileName, const UnicodeString & ADestination) : TFileLocationSessionAction(Log, laMv, FileName) { Destination(ADestination); } //--------------------------------------------------------------------------- __fastcall TCpSessionAction::TCpSessionAction(TActionLog * Log, const UnicodeString & FileName, const UnicodeString & ADestination) : TFileLocationSessionAction(Log, laCp, FileName) { Destination(ADestination); } //--------------------------------------------------------------------------- __fastcall TCallSessionAction::TCallSessionAction(TActionLog * Log, const UnicodeString & Command, const UnicodeString & Destination) : TSessionAction(Log, laCall) { if (FRecord != NULL) { FRecord->Command(Command); FRecord->Destination(Destination); } } //--------------------------------------------------------------------------- void __fastcall TCallSessionAction::AddOutput(const UnicodeString & Output, bool StdError) { if (FRecord != NULL) { FRecord->AddOutput(Output, StdError); } } //--------------------------------------------------------------------------- void __fastcall TCallSessionAction::ExitCode(int ExitCode) { if (FRecord != NULL) { FRecord->ExitCode(ExitCode); } } //--------------------------------------------------------------------------- __fastcall TLsSessionAction::TLsSessionAction(TActionLog * Log, const UnicodeString & Destination) : TSessionAction(Log, laLs) { if (FRecord != NULL) { FRecord->Destination(Destination); } } //--------------------------------------------------------------------------- void __fastcall TLsSessionAction::FileList(TRemoteFileList * FileList) { if (FRecord != NULL) { FRecord->FileList(FileList); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TStatSessionAction::TStatSessionAction(TActionLog * Log, const UnicodeString & FileName) : TFileSessionAction(Log, laStat, FileName) { } //--------------------------------------------------------------------------- void __fastcall TStatSessionAction::File(TRemoteFile * File) { if (FRecord != NULL) { FRecord->File(File); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TChecksumSessionAction::TChecksumSessionAction(TActionLog * Log) : TFileSessionAction(Log, laChecksum) { } //--------------------------------------------------------------------------- void __fastcall TChecksumSessionAction::Checksum(const UnicodeString & Alg, const UnicodeString & Checksum) { if (FRecord != NULL) { FRecord->Checksum(Alg, Checksum); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TCwdSessionAction::TCwdSessionAction(TActionLog * Log, const UnicodeString & Path) : TSessionAction(Log, laCwd) { if (FRecord != NULL) { FRecord->Cwd(Path); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TDifferenceSessionAction::TDifferenceSessionAction(TActionLog * Log, const TSynchronizeChecklist::TItem * Item) : TSessionAction(Log, laDifference) { if (FRecord != NULL) { FRecord->SynchronizeChecklistItem(Item); } } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- TSessionInfo::TSessionInfo() { LoginTime = Now(); CertificateVerifiedManually = false; } //--------------------------------------------------------------------------- TFileSystemInfo::TFileSystemInfo() { memset(&IsCapable, false, sizeof(IsCapable)); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- FILE * __fastcall OpenFile(UnicodeString LogFileName, TDateTime Started, TSessionData * SessionData, bool Append, UnicodeString & NewFileName) { FILE * Result; UnicodeString ANewFileName = GetExpandedLogFileName(LogFileName, Started, SessionData); Result = _wfopen(ApiPath(ANewFileName).c_str(), (Append ? L"ab" : L"wb")); if (Result != NULL) { setvbuf(Result, NULL, _IONBF, BUFSIZ); NewFileName = ANewFileName; } else { throw ECRTExtException(FMTLOAD(LOG_OPENERROR, (ANewFileName))); } return Result; } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- const wchar_t *LogLineMarks = L"<>!.*"; __fastcall TSessionLog::TSessionLog(TSessionUI* UI, TDateTime Started, TSessionData * SessionData, TConfiguration * Configuration) { FCriticalSection = new TCriticalSection; FLogging = false; FConfiguration = Configuration; FParent = NULL; FUI = UI; FSessionData = SessionData; FStarted = Started; FFile = NULL; FCurrentLogFileName = L""; FCurrentFileName = L""; FClosed = false; } //--------------------------------------------------------------------------- __fastcall TSessionLog::~TSessionLog() { FClosed = true; ReflectSettings(); DebugAssert(FFile == NULL); delete FCriticalSection; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::SetParent(TSessionLog * Parent, const UnicodeString & Name) { FParent = Parent; FName = Name; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::DoAddToParent(TLogLineType Type, const UnicodeString & Line) { DebugAssert(FParent != NULL); FParent->Add(Type, Line); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::DoAddToSelf(TLogLineType Type, const UnicodeString & Line) { if (LogToFile()) { if (FFile == NULL) { OpenLogFile(); } if (FFile != NULL) { UnicodeString Timestamp = FormatDateTime(L" yyyy-mm-dd hh:nn:ss.zzz ", Now()); UTF8String UtfLine = UTF8String(UnicodeString(LogLineMarks[Type]) + Timestamp + Line + L"\r\n"); for (int Index = 1; Index <= UtfLine.Length(); Index++) { if ((UtfLine[Index] == '\n') && ((Index == 1) || (UtfLine[Index - 1] != '\r'))) { UtfLine.Insert('\r', Index); } } int Writting = UtfLine.Length(); CheckSize(Writting); FCurrentFileSize += fwrite(UtfLine.c_str(), 1, Writting, (FILE *)FFile); } } } //--------------------------------------------------------------------------- UnicodeString __fastcall TSessionLog::LogPartFileName(const UnicodeString & BaseName, int Index) { UnicodeString Result; if (Index >= 1) { Result = FORMAT(L"%s.%d", (BaseName, Index)); } else { Result = BaseName; } return Result; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::CheckSize(__int64 Addition) { __int64 MaxSize = FConfiguration->LogMaxSize; if ((MaxSize > 0) && (FCurrentFileSize + Addition >= MaxSize)) { // Before we close it UnicodeString BaseName = FCurrentFileName; CloseLogFile(); FCurrentFileSize = 0; int Index = 0; while (FileExists(LogPartFileName(BaseName, Index + 1))) { Index++; } int MaxCount = FConfiguration->LogMaxCount; do { UnicodeString LogPart = LogPartFileName(BaseName, Index); if ((MaxCount > 0) && (Index >= MaxCount)) { DeleteFileChecked(LogPart); } else { THROWOSIFFALSE(RenameFile(LogPart, LogPartFileName(BaseName, Index + 1))); } Index--; } while (Index >= 0); OpenLogFile(); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::DoAdd(TLogLineType Type, UnicodeString Line, void __fastcall (__closure *f)(TLogLineType Type, const UnicodeString & Line)) { UnicodeString Prefix; if (!FName.IsEmpty()) { Prefix = L"[" + FName + L"] "; } while (!Line.IsEmpty()) { f(Type, Prefix + CutToChar(Line, L'\n', false)); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::Add(TLogLineType Type, const UnicodeString & Line) { DebugAssert(FConfiguration); if (Logging) { try { if (FParent != NULL) { DoAdd(Type, Line, DoAddToParent); } else { TGuard Guard(FCriticalSection); DoAdd(Type, Line, DoAddToSelf); } } catch (Exception &E) { // We failed logging, turn it off and notify user. FConfiguration->Logging = false; try { throw ExtException(&E, MainInstructions(LoadStr(LOG_GEN_ERROR))); } catch (Exception &E) { AddException(&E); FUI->HandleExtendedException(&E); } } } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddException(Exception * E) { if (E != NULL) { Add(llException, ExceptionLogString(E)); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::ReflectSettings() { TGuard Guard(FCriticalSection); FLogging = !FClosed && ((FParent != NULL) || FConfiguration->Logging); // if logging to file was turned off or log file was changed -> close current log file if ((FFile != NULL) && (!LogToFile() || (FCurrentLogFileName != FConfiguration->LogFileName))) { CloseLogFile(); } if (FFile != NULL) { CheckSize(0); } } //--------------------------------------------------------------------------- bool __fastcall TSessionLog::LogToFile() { return Logging && FConfiguration->LogToFile && (FParent == NULL); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::CloseLogFile() { if (FFile != NULL) { fclose((FILE *)FFile); FFile = NULL; } FCurrentLogFileName = L""; FCurrentFileName = L""; } //--------------------------------------------------------------------------- void __fastcall TSessionLog::OpenLogFile() { try { DebugAssert(FFile == NULL); DebugAssert(FConfiguration != NULL); FCurrentLogFileName = FConfiguration->LogFileName; FFile = OpenFile(FCurrentLogFileName, FStarted, FSessionData, FConfiguration->LogFileAppend, FCurrentFileName); TSearchRec SearchRec; if (FileSearchRec(FCurrentFileName, SearchRec)) { FCurrentFileSize = SearchRec.Size; } else { FCurrentFileSize = 0; } } catch (Exception & E) { // We failed logging to file, turn it off and notify user. FCurrentLogFileName = L""; FCurrentFileName = L""; FConfiguration->LogFileName = UnicodeString(); try { throw ExtException(&E, MainInstructions(LoadStr(LOG_GEN_ERROR))); } catch (Exception & E) { AddException(&E); // not to deadlock with TSessionLog::ReflectSettings invoked by FConfiguration->LogFileName setter above TUnguard Unguard(FCriticalSection); FUI->HandleExtendedException(&E); } } // in case we are appending and the existing log file is already too large if (FFile != NULL) { CheckSize(0); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddSystemInfo() { AddStartupInfo(true); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddStartupInfo() { AddStartupInfo(false); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddStartupInfo(bool System) { TSessionData * Data = (System ? NULL : FSessionData); if (Logging) { if (FParent != NULL) { // do not add session info for secondary session // (this should better be handled in the TSecondaryTerminal) } else { DoAddStartupInfo(Data); } } } //--------------------------------------------------------------------------- UnicodeString __fastcall TSessionLog::GetTlsVersionName(TTlsVersion TlsVersion) { switch (TlsVersion) { default: DebugFail(); case ssl2: return "SSLv2"; case ssl3: return "SSLv3"; case tls10: return "TLSv1.0"; case tls11: return "TLSv1.1"; case tls12: return "TLSv1.2"; case tls13: return "TLSv1.3"; } } //--------------------------------------------------------------------------- UnicodeString __fastcall TSessionLog::LogSensitive(const UnicodeString & Str) { if (FConfiguration->LogSensitive && !Str.IsEmpty()) { return NormalizeString(Str); } else { return BooleanToEngStr(!Str.IsEmpty()); } } //--------------------------------------------------------------------------- UnicodeString __fastcall TSessionLog::GetCmdLineLog() { UnicodeString Result = CmdLine; if (!Configuration->LogSensitive) { TManagementScript Script(StoredSessions, false); Script.MaskPasswordInCommandLine(Result, true); } return Result; } //--------------------------------------------------------------------------- template UnicodeString __fastcall EnumName(T Value, UnicodeString Names) { int N = int(Value); do { UnicodeString Name = CutToChar(Names, L';', true); if (N == 0) { return Name; } N--; } while ((N >= 0) && !Names.IsEmpty()); return L"(unknown)"; } //--------------------------------------------------------------------------- #define ADSTR(S) DoAdd(llMessage, S, DoAddToSelf); #define ADF(S, F) ADSTR(FORMAT(S, F)); //--------------------------------------------------------------------------- void __fastcall TSessionLog::DoAddStartupInfo(TSessionData * Data) { if (Data == NULL) { AddSeparator(); ADSTR(GetEnvironmentInfo()); THierarchicalStorage * Storage = FConfiguration->CreateConfigStorage(); try { ADF(L"Configuration: %s", (Storage->Source)); } __finally { delete Storage; } wchar_t UserName[UNLEN + 1]; unsigned long UserNameSize = LENOF(UserName); if (DebugAlwaysFalse(!GetUserNameEx(NameSamCompatible, UserName, &UserNameSize))) { wcscpy(UserName, L""); } UnicodeString LogStr; if (FConfiguration->LogProtocol <= -1) { LogStr = L"Reduced"; } else if (FConfiguration->LogProtocol <= 0) { LogStr = L"Normal"; } else if (FConfiguration->LogProtocol == 1) { LogStr = L"Debug 1"; } else if (FConfiguration->LogProtocol >= 2) { LogStr = L"Debug 2"; } if (FConfiguration->LogSensitive) { LogStr += L", Logging passwords"; } if (FConfiguration->LogMaxSize > 0) { LogStr += FORMAT(L", Rotating after: %s", (SizeToStr(FConfiguration->LogMaxSize))); if (FConfiguration->LogMaxCount > 0) { LogStr += FORMAT(L", Keeping at most %d logs", (FConfiguration->LogMaxCount)); } } ADF(L"Log level: %s", (LogStr)); ADF(L"Local account: %s", (UserName)); ADF(L"Working directory: %s", (GetCurrentDir())); ADF(L"Process ID: %d", (int(GetCurrentProcessId()))); ADF(L"Ancestor processes: %s", (GetAncestorProcessNames())); ADF(L"Command-line: %s", (GetCmdLineLog())); if (FConfiguration->ActualLogProtocol >= 1) { AddOptions(GetGlobalOptions()); } ADF(L"Time zone: %s", (GetTimeZoneLogString())); if (!AdjustClockForDSTEnabled()) { ADSTR(L"Warning: System option \"Automatically adjust clock for Daylight Saving Time\" is disabled, timestamps will not be represented correctly"); } ADF(L"Login time: %s", (FormatDateTime(L"dddddd tt", Now()))); AddSeparator(); } else { ADF(L"Session name: %s (%s)", (Data->SessionName, Data->Source)); UnicodeString AddressFamily; if (Data->AddressFamily != afAuto) { AddressFamily = FORMAT(L"%s, ", (Data->AddressFamily == afIPv4 ? L"IPv4" : L"IPv6")); } UnicodeString HostName = Data->HostNameExpanded; if (!Data->HostNameSource.IsEmpty()) { HostName = FORMAT(L"%s [%s]", (HostName, Data->HostNameSource)); } ADF(L"Host name: %s (%sPort: %d)", (HostName, AddressFamily, Data->PortNumber)); UnicodeString UserName = Data->UserNameExpanded; if (!Data->UserNameSource.IsEmpty()) { UserName = FORMAT(L"%s [%s]", (UserName, Data->UserNameSource)); } ADF(L"User name: %s (Password: %s, Key file: %s, Passphrase: %s)", (UserName, LogSensitive(Data->Password), LogSensitive(Data->PublicKeyFile), LogSensitive(Data->Passphrase))); if (Data->UsesSsh) { ADF(L"Tunnel: %s", (BooleanToEngStr(Data->Tunnel))); if (Data->Tunnel) { ADF(L"Tunnel: Host name: %s (Port: %d)", (Data->TunnelHostName, Data->TunnelPortNumber)); ADF(L"Tunnel: User name: %s (Password: %s, Key file: %s)", (Data->TunnelUserName, LogSensitive(Data->TunnelPassword), LogSensitive(Data->TunnelPublicKeyFile))); ADF(L"Tunnel: Local port number: %d", (Data->TunnelLocalPortNumber)); } } ADF(L"Transfer Protocol: %s", (Data->FSProtocolStr)); if (Data->UsesSsh || (Data->FSProtocol == fsFTP)) { TPingType PingType; int PingInterval; if (Data->FSProtocol == fsFTP) { PingType = Data->FtpPingType; PingInterval = Data->FtpPingInterval; } else { PingType = Data->PingType; PingInterval = Data->PingInterval; } ADF(L"Ping type: %s, Ping interval: %d sec; Timeout: %d sec", (EnumName(PingType, PingTypeNames), PingInterval, Data->Timeout)); ADF(L"Disable Nagle: %s", (BooleanToEngStr(Data->TcpNoDelay))); } ADF(L"Proxy: %s", ((Data->FtpProxyLogonType != 0) ? FORMAT(L"FTP proxy %d", (Data->FtpProxyLogonType)) : EnumName(Data->ProxyMethod, ProxyMethodNames))); if ((Data->FtpProxyLogonType != 0) || (Data->ProxyMethod != ::pmNone)) { ADF(L"HostName: %s (Port: %d); Username: %s; Passwd: %s", (Data->ProxyHost, Data->ProxyPort, Data->ProxyUsername, LogSensitive(Data->ProxyPassword))); if (Data->ProxyMethod == pmTelnet) { ADF(L"Telnet command: %s", (Data->ProxyTelnetCommand)); } if (Data->ProxyMethod == pmCmd) { ADF(L"Local command: %s", (Data->ProxyLocalCommand)); } } if (Data->UsesSsh || (Data->FSProtocol == fsFTP)) { ADF(L"Send buffer: %d", (Data->SendBuf)); } if (Data->UsesSsh && !Data->SourceAddress.IsEmpty()) { ADF(L"Source address: %s", (Data->SourceAddress)); } if (Data->UsesSsh) { ADF(L"Compression: %s", (BooleanToEngStr(Data->Compression))); ADF(L"Bypass authentication: %s", (BooleanToEngStr(Data->SshNoUserAuth))); ADF(L"Try agent: %s; Agent forwarding: %s; KI: %s; GSSAPI: %s", (BooleanToEngStr(Data->TryAgent), BooleanToEngStr(Data->AgentFwd), BooleanToEngStr(Data->AuthKI), BooleanToEngStr(Data->AuthGSSAPI))); if (Data->AuthGSSAPI) { ADF(L"GSSAPI: KEX: %s; Forwarding: %s; Libs: %s; Custom: %s", (BooleanToEngStr(Data->AuthGSSAPIKEX), BooleanToEngStr(Data->GSSAPIFwdTGT), Data->GssLibList, Data->GssLibCustom)); } ADF(L"Ciphers: %s; Ssh2DES: %s", (Data->CipherList, BooleanToEngStr(Data->Ssh2DES))); ADF(L"KEX: %s", (Data->KexList)); UnicodeString Bugs; for (int Index = 0; Index < BUG_COUNT; Index++) { AddToList(Bugs, EnumName(Data->Bug[(TSshBug)Index], AutoSwitchNames), L","); } ADF(L"SSH Bugs: %s", (Bugs)); ADF(L"Simple channel: %s", (BooleanToEngStr(Data->SshSimple))); ADF(L"Return code variable: %s; Lookup user groups: %s", ((Data->DetectReturnVar ? UnicodeString(L"Autodetect") : Data->ReturnVar), EnumName(Data->LookupUserGroups, AutoSwitchNames))); ADF(L"Shell: %s", ((Data->Shell.IsEmpty()? UnicodeString(L"default") : Data->Shell))); ADF(L"EOL: %s, UTF: %s", (EnumName(Data->EOLType, EOLTypeNames), EnumName(Data->NotUtf, NotAutoSwitchNames))); // NotUtf duplicated in FTP branch ADF(L"Clear aliases: %s, Unset nat.vars: %s, Resolve symlinks: %s; Follow directory symlinks: %s", (BooleanToEngStr(Data->ClearAliases), BooleanToEngStr(Data->UnsetNationalVars), BooleanToEngStr(Data->ResolveSymlinks), BooleanToEngStr(Data->FollowDirectorySymlinks))); ADF(L"LS: %s, Ign LS warn: %s, Scp1 Comp: %s; Exit code 1 is error: %s", (Data->ListingCommand, BooleanToEngStr(Data->IgnoreLsWarnings), BooleanToEngStr(Data->Scp1Compatibility), BooleanToEngStr(Data->ExitCode1IsError))); } if ((Data->FSProtocol == fsSFTP) || (Data->FSProtocol == fsSFTPonly)) { UnicodeString Bugs; for (int Index = 0; Index < SFTP_BUG_COUNT; Index++) { AddToList(Bugs, EnumName(Data->SFTPBug[(TSftpBug)Index], AutoSwitchNames), L","); } ADF(L"SFTP Bugs: %s", (Bugs)); ADF(L"SFTP Server: %s", ((Data->SftpServer.IsEmpty()? UnicodeString(L"default") : Data->SftpServer))); if (Data->SFTPRealPath != asAuto) { ADF(L"SFTP Real Path: %s", (EnumName(Data->SFTPRealPath, AutoSwitchNames))); } } bool FtpsOn = false; if (Data->FSProtocol == fsFTP) { ADF(L"UTF: %s", (EnumName(Data->NotUtf, NotAutoSwitchNames))); // duplicated in UsesSsh branch UnicodeString Ftps; switch (Data->Ftps) { case ftpsImplicit: Ftps = L"Implicit TLS/SSL"; FtpsOn = true; break; case ftpsExplicitSsl: Ftps = L"Explicit SSL/TLS"; FtpsOn = true; break; case ftpsExplicitTls: Ftps = L"Explicit TLS/SSL"; FtpsOn = true; break; default: DebugAssert(Data->Ftps == ftpsNone); Ftps = L"None"; break; } // kind of hidden option, so do not reveal it unless it is set if (Data->FtpTransferActiveImmediately != asAuto) { ADF(L"Transfer active immediately: %s", (EnumName(Data->FtpTransferActiveImmediately, AutoSwitchNames))); } ADF(L"FTPS: %s [Client certificate: %s]", (Ftps, LogSensitive(Data->TlsCertificateFile))); ADF(L"FTP: Passive: %s [Force IP: %s]; MLSD: %s [List all: %s]; HOST: %s", (BooleanToEngStr(Data->FtpPasvMode), EnumName(Data->FtpForcePasvIp, AutoSwitchNames), EnumName(Data->FtpUseMlsd, AutoSwitchNames), EnumName(Data->FtpListAll, AutoSwitchNames), EnumName(Data->FtpHost, AutoSwitchNames))); if (Data->FtpWorkFromCwd != asAuto) { ADF(L"FTP: Relative paths: %s", (EnumName(Data->FtpWorkFromCwd, AutoSwitchNames))); } } if (Data->FSProtocol == fsWebDAV) { FtpsOn = (Data->Ftps != ftpsNone); ADF(L"HTTPS: %s [Client certificate: %s]", (BooleanToEngStr(FtpsOn), LogSensitive(Data->TlsCertificateFile))); ADF(L"WebDAV: Tolerate non-encoded: %s", (BooleanToEngStr(Data->WebDavLiberalEscaping))); } if (Data->FSProtocol == fsS3) { FtpsOn = (Data->Ftps != ftpsNone); ADF(L"HTTPS: %s", (BooleanToEngStr(FtpsOn))); ADF(L"S3: URL Style: %s", (EnumName(Data->S3UrlStyle, L"Virtual Host;Path"))); if (!Data->S3DefaultRegion.IsEmpty()) { ADF(L"S3: Default region: %s", (Data->S3DefaultRegion)); } if (!Data->S3SessionToken.IsEmpty()) { ADF(L"S3: Session token: %s", (Data->S3SessionToken)); } } if (FtpsOn) { if (Data->FSProtocol == fsFTP) { ADF(L"Session reuse: %s", (BooleanToEngStr(Data->SslSessionReuse))); } ADF(L"TLS/SSL versions: %s-%s", (GetTlsVersionName(Data->MinTlsVersion), GetTlsVersionName(Data->MaxTlsVersion))); } ADF(L"Local directory: %s, Remote directory: %s, Update: %s, Cache: %s", ((Data->LocalDirectory.IsEmpty() ? UnicodeString(L"default") : Data->LocalDirectory), (Data->RemoteDirectory.IsEmpty() ? UnicodeString(L"home") : Data->RemoteDirectory), BooleanToEngStr(Data->UpdateDirectories), BooleanToEngStr(Data->CacheDirectories))); ADF(L"Cache directory changes: %s, Permanent: %s", (BooleanToEngStr(Data->CacheDirectoryChanges), BooleanToEngStr(Data->PreserveDirectoryChanges))); ADF(L"Recycle bin: Delete to: %s, Overwritten to: %s, Bin path: %s", (BooleanToEngStr(Data->DeleteToRecycleBin), BooleanToEngStr(Data->OverwrittenToRecycleBin), Data->RecycleBinPath)); if (Data->TrimVMSVersions || Data->VMSAllRevisions) { ADF(L"Trim VMS versions: %s; VMS all revisions: %s", (BooleanToEngStr(Data->TrimVMSVersions), BooleanToEngStr(Data->VMSAllRevisions))); } UnicodeString TimeInfo; if ((Data->FSProtocol == fsSFTP) || (Data->FSProtocol == fsSFTPonly) || (Data->FSProtocol == fsSCPonly) || (Data->FSProtocol == fsWebDAV)) { AddToList(TimeInfo, FORMAT(L"DST mode: %s", (EnumName(Data->DSTMode, DSTModeNames))), L";"); } if ((Data->FSProtocol == fsSCPonly) || (Data->FSProtocol == fsFTP)) { int TimeDifferenceMin = TimeToMinutes(Data->TimeDifference); AddToList(TimeInfo, FORMAT(L"Timezone offset: %dh %dm", ((TimeDifferenceMin / MinsPerHour), (TimeDifferenceMin % MinsPerHour))), L";"); } ADSTR(TimeInfo); if (Data->FSProtocol == fsWebDAV) { ADF(L"Compression: %s", (BooleanToEngStr(Data->Compression))); } if ((Data->FSProtocol == fsFTP) && !Data->FtpPasvMode) { if (!Configuration->ExternalIpAddress.IsEmpty() || Configuration->HasLocalPortNumberLimits()) { AddSeparator(); ADF(L"FTP active mode interface: %s:%d-%d", (DefaultStr(Configuration->ExternalIpAddress, L""), Configuration->LocalPortNumberMin, Configuration->LocalPortNumberMax)); } } AddSeparator(); } } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddOption(const UnicodeString & LogStr) { ADSTR(LogStr); } //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddOptions(TOptions * Options) { Options->LogOptions(AddOption); } //--------------------------------------------------------------------------- #undef ADF #undef ADSTR //--------------------------------------------------------------------------- void __fastcall TSessionLog::AddSeparator() { Add(llMessage, L"--------------------------------------------------------------------------"); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- __fastcall TActionLog::TActionLog(TSessionUI * UI, TDateTime Started, TSessionData * SessionData, TConfiguration * Configuration) { DebugAssert(UI != NULL); DebugAssert(SessionData != NULL); Init(UI, Started, SessionData, Configuration); } //--------------------------------------------------------------------------- __fastcall TActionLog::TActionLog(TDateTime Started, TConfiguration * Configuration) { Init(NULL, Started, NULL, Configuration); // not associated with session, so no need to waiting for anything ReflectSettings(); } //--------------------------------------------------------------------------- void __fastcall TActionLog::Init(TSessionUI * UI, TDateTime Started, TSessionData * SessionData, TConfiguration * Configuration) { FCriticalSection = new TCriticalSection; FConfiguration = Configuration; FUI = UI; FSessionData = SessionData; FStarted = Started; FFile = NULL; FCurrentLogFileName = L""; FCurrentFileName = L""; FLogging = false; FClosed = false; FFailed = false; FPendingActions = new TList(); FIndent = L" "; FInGroup = false; FEnabled = true; } //--------------------------------------------------------------------------- __fastcall TActionLog::~TActionLog() { DebugAssert(FPendingActions->Count == 0); delete FPendingActions; FClosed = true; ReflectSettings(); DebugAssert(FFile == NULL); delete FCriticalSection; } //--------------------------------------------------------------------------- void __fastcall TActionLog::Add(const UnicodeString & Line) { DebugAssert(FConfiguration); if (FLogging) { TGuard Guard(FCriticalSection); if (FFile == NULL) { OpenLogFile(); } if (FFile != NULL) { try { UTF8String UtfLine = UTF8String(Line); size_t Written = fwrite(UtfLine.c_str(), 1, UtfLine.Length(), (FILE *)FFile); if (Written != static_cast(UtfLine.Length())) { throw ECRTExtException(L""); } #ifdef _DEBUG #endif Written = fwrite("\n", 1, 1, (FILE *)FFile); if (Written != 1) { throw ECRTExtException(L""); } } catch (Exception &E) { FCriticalSection->Release(); // avoid endless loop when trying to close tags when closing log, when logging has failed if (!FFailed) { FFailed = true; // We failed logging, turn it off and notify user. FConfiguration->LogActions = false; if (FConfiguration->LogActionsRequired) { throw EFatal(&E, LoadStr(LOG_FATAL_ERROR)); } else { try { throw ExtException(&E, LoadStr(LOG_GEN_ERROR)); } catch (Exception &E) { if (FUI != NULL) { FUI->HandleExtendedException(&E); } } } } } } } } //--------------------------------------------------------------------------- void __fastcall TActionLog::AddIndented(const UnicodeString & Line) { Add(FIndent + Line); } //--------------------------------------------------------------------------- void __fastcall TActionLog::AddFailure(TStrings * Messages) { AddIndented(L""); AddMessages(L" ", Messages); AddIndented(L""); } //--------------------------------------------------------------------------- void __fastcall TActionLog::AddFailure(Exception * E) { TStrings * Messages = ExceptionToMoreMessages(E); if (Messages != NULL) { try { AddFailure(Messages); } __finally { delete Messages; } } } //--------------------------------------------------------------------------- void __fastcall TActionLog::AddMessages(UnicodeString Indent, TStrings * Messages) { for (int Index = 0; Index < Messages->Count; Index++) { AddIndented( FORMAT(Indent + L"%s", (XmlEscape(Messages->Strings[Index])))); } } //--------------------------------------------------------------------------- void __fastcall TActionLog::ReflectSettings() { TGuard Guard(FCriticalSection); bool ALogging = !FClosed && FConfiguration->LogActions && Enabled; if (ALogging && !FLogging) { FLogging = true; Add(L""); UnicodeString SessionName = (FSessionData != NULL) ? XmlAttributeEscape(FSessionData->SessionName) : UnicodeString(L"nosession"); Add(FORMAT(L"", (SessionName, StandardTimestamp()))); } else if (!ALogging && FLogging) { if (FInGroup) { EndGroup(); } // do not try to close the file, if it has not been opened, to avoid recursion if (FFile != NULL) { Add(L""); } CloseLogFile(); FLogging = false; } } //--------------------------------------------------------------------------- void __fastcall TActionLog::CloseLogFile() { if (FFile != NULL) { fclose((FILE *)FFile); FFile = NULL; } FCurrentLogFileName = L""; FCurrentFileName = L""; } //--------------------------------------------------------------------------- void __fastcall TActionLog::OpenLogFile() { try { DebugAssert(FFile == NULL); DebugAssert(FConfiguration != NULL); FCurrentLogFileName = FConfiguration->ActionsLogFileName; FFile = OpenFile(FCurrentLogFileName, FStarted, FSessionData, false, FCurrentFileName); } catch (Exception & E) { // We failed logging to file, turn it off and notify user. FCurrentLogFileName = L""; FCurrentFileName = L""; FConfiguration->LogActions = false; if (FConfiguration->LogActionsRequired) { throw EFatal(&E, LoadStr(LOG_FATAL_ERROR)); } else { try { throw ExtException(&E, LoadStr(LOG_GEN_ERROR)); } catch (Exception & E) { if (FUI != NULL) { // not to deadlock with TSessionLog::ReflectSettings invoked by FConfiguration->LogFileName setter above TUnguard Unguard(FCriticalSection); FUI->HandleExtendedException(&E); } } } } } //--------------------------------------------------------------------------- void __fastcall TActionLog::AddPendingAction(TSessionActionRecord * Action) { FPendingActions->Add(Action); } //--------------------------------------------------------------------------- void __fastcall TActionLog::RecordPendingActions() { while ((FPendingActions->Count > 0) && static_cast(FPendingActions->Items[0])->Record()) { FPendingActions->Delete(0); } } //--------------------------------------------------------------------------- void __fastcall TActionLog::BeginGroup(UnicodeString Name) { DebugAssert(!FInGroup); FInGroup = true; DebugAssert(FIndent == L" "); AddIndented(FORMAT(L"", (XmlAttributeEscape(Name), StandardTimestamp()))); FIndent = L" "; } //--------------------------------------------------------------------------- void __fastcall TActionLog::EndGroup() { DebugAssert(FInGroup); FInGroup = false; DebugAssert(FIndent == L" "); FIndent = L" "; // this is called from ReflectSettings that in turn is called when logging fails, // so do not try to close the group, if it has not been opened, to avoid recursion if (FFile != NULL) { AddIndented(L""); } } //--------------------------------------------------------------------------- void __fastcall TActionLog::SetEnabled(bool value) { if (Enabled != value) { FEnabled = value; ReflectSettings(); } }