Example #1
0
//---------------------------------------------------------------------------
void TSessionLog::DoAddStartupInfo(TSessionData * Data)
{
  TGuard Guard(FCriticalSection);

  BeginUpdate();
  auto cleanup = finally([&]()
  {
    DeleteUnnecessary();
    EndUpdate();
  });
  {
    #define ADF(S, ...) DoAdd(llMessage, FORMAT(S, ##__VA_ARGS__), MAKE_CALLBACK(TSessionLog::DoAddToSelf, this));
    if (Data == nullptr)
    {
      AddSeparator();
      ADF(L"NetBox %s (OS %s)", FConfiguration->GetVersionStr().c_str(), FConfiguration->GetOSVersionStr().c_str());
      std::auto_ptr<THierarchicalStorage> Storage(FConfiguration->CreateStorage(false));
      assert(Storage.get());
      ADF(L"Configuration: %s", Storage->GetSource().c_str());

      if (0)
      {
        typedef BOOL (WINAPI * TGetUserNameEx)(EXTENDED_NAME_FORMAT NameFormat, LPWSTR lpNameBuffer, PULONG nSize);
        HINSTANCE Secur32 = LoadLibrary(L"secur32.dll");
        TGetUserNameEx GetUserNameEx =
          (Secur32 != nullptr) ? reinterpret_cast<TGetUserNameEx>(GetProcAddress(Secur32, "GetUserNameExW")) : nullptr;
        wchar_t UserName[UNLEN + 1];
        unsigned long UserNameSize = LENOF(UserName);
        if ((GetUserNameEx == nullptr) || !GetUserNameEx(NameSamCompatible, (LPWSTR)UserName, &UserNameSize))
        {
          wcscpy(UserName, L"<Failed to retrieve username>");
        }
        ADF(L"Local account: %s", UserName);
      }
      unsigned short Y, M, D, H, N, S, MS;
      TDateTime DateTime = Now();
      DateTime.DecodeDate(Y, M, D);
      DateTime.DecodeTime(H, N, S, MS);
      UnicodeString dt = FORMAT(L"%02d.%02d.%04d %02d:%02d:%02d", D, M, Y, H, N, S);
      // ADF(L"Login time: %s", FormatDateTime(L"dddddd tt", Now()).c_str());
      ADF(L"Working directory: %s", GetCurrentDir().c_str());
      // ADF(L"Command-line: %s", CmdLine.c_str());
      // ADF(L"Time zone: %s", GetTimeZoneLogString().c_str());
      ADF(L"Login time: %s", dt.c_str());
      AddSeparator();
    }
    else
    {
      if (0)
      {
        ADF(L"Session name: %s (%s)", Data->GetSessionName().c_str(), Data->GetSource().c_str());
      }
      ADF(L"Host name: %s (Port: %d)", Data->GetHostNameExpanded().c_str(), Data->GetPortNumber());
      if (0)
      {
        ADF(L"User name: %s (Password: %s, Key file: %s)",
          Data->GetUserNameExpanded().c_str(), BooleanToEngStr(!Data->GetPassword().IsEmpty()).c_str(),
           BooleanToEngStr(!Data->GetPublicKeyFile().IsEmpty()).c_str())
      }
      ADF(L"Tunnel: %s", BooleanToEngStr(Data->GetTunnel()).c_str());
      if (Data->GetTunnel())
      {
        ADF(L"Tunnel: Host name: %s (Port: %d)", Data->GetTunnelHostName().c_str(), Data->GetTunnelPortNumber());
        if (0)
        {
          ADF(L"Tunnel: User name: %s (Password: %s, Key file: %s)",
            Data->GetTunnelUserName().c_str(), BooleanToEngStr(!Data->GetTunnelPassword().IsEmpty()).c_str(),
             BooleanToEngStr(!Data->GetTunnelPublicKeyFile().IsEmpty()).c_str());
            ADF(L"Tunnel: Local port number: %d", Data->GetTunnelLocalPortNumber());
        }
      }
      ADF(L"Transfer Protocol: %s", Data->GetFSProtocolStr().c_str());
      ADF(L"Code Page: %d", Data->GetCodePageAsNumber());
      wchar_t * PingTypes = L"-NC";
      TPingType PingType;
      intptr_t PingInterval;
      if (Data->GetFSProtocol() == fsFTP)
      {
        PingType = Data->GetFtpPingType();
        PingInterval = Data->GetFtpPingInterval();
      }
      else
      {
        PingType = Data->GetPingType();
        PingInterval = Data->GetPingInterval();
      }
      ADF(L"Ping type: %s, Ping interval: %d sec; Timeout: %d sec",
        UnicodeString(PingTypes[PingType]).c_str(), PingInterval, Data->GetTimeout());
      ADF(L"Proxy: %s%s", ProxyMethodList[Data->GetProxyMethod()],
        Data->GetProxyMethod() == pmSystem ?
          ::Format(L" (%s)", ProxyMethodList[Data->GetActualProxyMethod()]).c_str() :
          L"")
      if (Data->GetProxyMethod() != ::pmNone)
      {
        ADF(L"HostName: %s (Port: %d); Username: %s; Passwd: %s",
          Data->GetProxyHost().c_str(), Data->GetProxyPort(),
           Data->GetProxyUsername().c_str(), BooleanToEngStr(!Data->GetProxyPassword().IsEmpty()).c_str());
        if (Data->GetProxyMethod() == pmTelnet)
        {
          ADF(L"Telnet command: %s", Data->GetProxyTelnetCommand().c_str());
        }
        if (Data->GetProxyMethod() == pmCmd)
        {
          ADF(L"Local command: %s", Data->GetProxyLocalCommand().c_str());
        }
      }
      wchar_t const * BugFlags = L"+-A";
      if (Data->GetUsesSsh())
      {
        ADF(L"SSH protocol version: %s; Compression: %s",
          Data->GetSshProtStr().c_str(), BooleanToEngStr(Data->GetCompression()).c_str());
        ADF(L"Bypass authentication: %s",
         BooleanToEngStr(Data->GetSshNoUserAuth()).c_str());
        ADF(L"Try agent: %s; Agent forwarding: %s; TIS/CryptoCard: %s; KI: %s; GSSAPI: %s",
           BooleanToEngStr(Data->GetTryAgent()).c_str(), BooleanToEngStr(Data->GetAgentFwd()).c_str(), BooleanToEngStr(Data->GetAuthTIS()).c_str(),
           BooleanToEngStr(Data->GetAuthKI()).c_str(), BooleanToEngStr(Data->GetAuthGSSAPI()).c_str());
        if (Data->GetAuthGSSAPI())
        {
          ADF(L"GSSAPI: Forwarding: %s; Server realm: %s",
            BooleanToEngStr(Data->GetGSSAPIFwdTGT()).c_str(), Data->GetGSSAPIServerRealm().c_str());
        }
        ADF(L"Ciphers: %s; Ssh2DES: %s",
          Data->GetCipherList().c_str(), BooleanToEngStr(Data->GetSsh2DES()).c_str());
        UnicodeString Bugs;
        for (intptr_t Index = 0; Index < BUG_COUNT; ++Index)
        {
          Bugs += UnicodeString(BugFlags[Data->GetBug(static_cast<TSshBug>(Index))])+(Index<BUG_COUNT-1?L",":L"");
        }
        ADF(L"SSH Bugs: %s", Bugs.c_str());
        ADF(L"Return code variable: %s; Lookup user groups: %c",
           Data->GetDetectReturnVar() ? UnicodeString(L"Autodetect").c_str() : Data->GetReturnVar().c_str(),
           BugFlags[Data->GetLookupUserGroups()]);
        ADF(L"Shell: %s", Data->GetShell().IsEmpty() ? UnicodeString(L"default").c_str() : Data->GetShell().c_str());
        ADF(L"EOL: %d, UTF: %d", Data->GetEOLType(), Data->GetNotUtf());
        ADF(L"Clear aliases: %s, Unset nat.vars: %s, Resolve symlinks: %s",
           BooleanToEngStr(Data->GetClearAliases()).c_str(), BooleanToEngStr(Data->GetUnsetNationalVars()).c_str(),
           BooleanToEngStr(Data->GetResolveSymlinks()).c_str());
        ADF(L"LS: %s, Ign LS warn: %s, Scp1 Comp: %s",
           Data->GetListingCommand().c_str(),
           BooleanToEngStr(Data->GetIgnoreLsWarnings()).c_str(),
           BooleanToEngStr(Data->GetScp1Compatibility()).c_str());
      }
      if (Data->GetFSProtocol() == fsSFTP)
      {
        UnicodeString Bugs;
        for (int Index = 0; Index < SFTP_BUG_COUNT; Index++)
        {
          Bugs += UnicodeString(BugFlags[Data->GetSFTPBug(static_cast<TSftpBug>(Index))])+(Index<SFTP_BUG_COUNT-1 ? L"," : L"");
        }
        ADF(L"SFTP Bugs: %s", Bugs.c_str());
        ADF(L"SFTP Server: %s", Data->GetSftpServer().IsEmpty()? UnicodeString(L"default").c_str() : Data->GetSftpServer().c_str());
      }
      if (Data->GetFSProtocol() == fsFTP)
      {
        UnicodeString Ftps;
        switch (Data->GetFtps())
        {
          case ftpsImplicit:
            Ftps = L"Implicit TLS/SSL";
            break;

          case ftpsExplicitSsl:
            Ftps = L"Explicit SSL";
            break;

          case ftpsExplicitTls:
            Ftps = L"Explicit TLS";
            break;

          default:
            assert(Data->GetFtps() == ftpsNone);
            Ftps = L"None";
            break;
        }
        ADF(L"FTP: FTPS: %s; Passive: %s [Force IP: %c]; MLSD: %c  [List all: %c]",
           Ftps.c_str(), BooleanToEngStr(Data->GetFtpPasvMode()).c_str(),
           BugFlags[Data->GetFtpForcePasvIp()],
           BugFlags[Data->GetFtpUseMlsd()],
           BugFlags[Data->GetFtpListAll()]);
        if (Data->GetFtps() != ftpsNone)
        {
          ADF(L"Session reuse: %s", BooleanToEngStr(Data->GetSslSessionReuse()).c_str());
          ADF(L"TLS/SSL versions: %s-%s", GetTlsVersionName(Data->GetMinTlsVersion()).c_str(), GetTlsVersionName(Data->GetMaxTlsVersion()).c_str());
        }
      }
      ADF(L"Local directory: %s, Remote directory: %s, Update: %s, Cache: %s",
        (Data->GetLocalDirectory().IsEmpty() ? UnicodeString(L"default").c_str() : Data->GetLocalDirectory().c_str()),
         (Data->GetRemoteDirectory().IsEmpty() ? UnicodeString(L"home").c_str() : Data->GetRemoteDirectory().c_str()),
         BooleanToEngStr(Data->GetUpdateDirectories()).c_str(),
         BooleanToEngStr(Data->GetCacheDirectories()).c_str());
      ADF(L"Cache directory changes: %s, Permanent: %s",
         BooleanToEngStr(Data->GetCacheDirectoryChanges()).c_str(),
         BooleanToEngStr(Data->GetPreserveDirectoryChanges()).c_str());
      intptr_t TimeDifferenceMin = TimeToMinutes(Data->GetTimeDifference());
      ADF(L"DST mode: %d; Timezone offset: %dh %dm", static_cast<int>(Data->GetDSTMode()), (TimeDifferenceMin / MinsPerHour), (TimeDifferenceMin % MinsPerHour));

      if (Data->GetFSProtocol() == fsWebDAV)
      {
        ADF(L"Compression: %s",
          BooleanToEngStr(Data->GetCompression()).c_str());
      }

      AddSeparator();
    }

    #undef ADF
  }
}
void TSessionLog::DoAddStartupInfo(TSessionData *Data)
{
  if (Data == nullptr)
  {
    AddSeparator();
    UnicodeString OS = WindowsVersionLong();
    AddToList(OS, WindowsProductName(), L" - ");
    ADF("NetBox %s (OS %s)", FConfiguration->GetProductVersionStr(), OS);
    {
      std::unique_ptr<THierarchicalStorage> Storage(FConfiguration->CreateConfigStorage());
      DebugAssert(Storage.get());
      ADF("Configuration: %s", Storage->GetSource());
    }

#if 0
    typedef BOOL (WINAPI * TGetUserNameEx)(EXTENDED_NAME_FORMAT NameFormat, LPWSTR lpNameBuffer, PULONG nSize);
    HINSTANCE Secur32 = LoadLibrary(L"secur32.dll");
    TGetUserNameEx GetUserNameEx =
      (Secur32 != nullptr) ? reinterpret_cast<TGetUserNameEx>(::GetProcAddress(Secur32, "GetUserNameExW")) : nullptr;
    wchar_t UserName[UNLEN + 1];
    ULONG UserNameSize = _countof(UserName);
    if ((GetUserNameEx == nullptr) || DebugAlwaysFalse(!GetUserNameEx(NameSamCompatible, (LPWSTR)UserName, &UserNameSize)))
    {
      wcscpy_s(UserName, UNLEN, L"<Failed to retrieve username>");
    }
#endif // #if 0
    UnicodeString LogStr;
    if (FConfiguration->GetLogProtocol() <= 0)
    {
      LogStr = L"Normal";
    }
    else if (FConfiguration->GetLogProtocol() == 1)
    {
      LogStr = L"Debug 1";
    }
    else if (FConfiguration->GetLogProtocol() >= 2)
    {
      LogStr = L"Debug 2";
    }
#if 0
    if (FConfiguration->GetLogSensitive())
    {
      LogStr += L", Logging passwords";
    }
#endif // #if 0
    if (FConfiguration->GetLogMaxSize() > 0)
    {
      LogStr += FORMAT(L", Rotating after: %s", SizeToStr(FConfiguration->GetLogMaxSize()));
      if (FConfiguration->GetLogMaxCount() > 0)
      {
        LogStr += FORMAT(L", Keeping at most %d logs", FConfiguration->GetLogMaxCount());
      }
    }
#if 0
    ADF("Log level: %s", LogStr);
    ADF("Local account: %s", UserName);
#endif // #if 0
    ADF("Working directory: %s", GetCurrentDir());
    ADF("Process ID: %d", intptr_t(GetCurrentProcessId()));
#if 0
    ADF("Command-line: %s", GetCmdLineLog());
    if (FConfiguration->GetLogProtocol() >= 1)
    {
      AddOptions(GetGlobalOptions());
    }
#endif // #if 0
    ADF("Time zone: %s", GetTimeZoneLogString());
    if (!AdjustClockForDSTEnabled())
    {
      ADF("Warning: System option \"Automatically adjust clock for Daylight Saving Time\" is disabled, timestamps will not be represented correctly");
    }
#if 0
    ADF("Login time: %s", dt);
#endif // #if 0
    AddSeparator();
  }
  else
  {
#if 0
    ADF("Session name: %s (%s)", Data->GetSessionName(), Data->GetSource());
    ADF("Host name: %s (Port: %d)", Data->GetHostNameExpanded(), Data->GetPortNumber());
    ADF("User name: %s (Password: %s, Key file: %s, Passphrase: %s)",
      Data->GetUserNameExpanded(), LogSensitive(Data->GetPassword()),
      LogSensitive(Data->GetPublicKeyFile()), LogSensitive(Data->GetPassphrase()))
#endif // #if 0
    if (Data->GetUsesSsh())
    {
      ADF("Tunnel: %s", BooleanToEngStr(Data->GetTunnel()));
      if (Data->GetTunnel())
      {
        ADF("Tunnel: Host name: %s (Port: %d)", Data->GetTunnelHostName(), Data->GetTunnelPortNumber());
#if 0
        ADF("Tunnel: User name: %s (Password: %s, Key file: %s)",
          Data->GetTunnelUserName(), BooleanToEngStr(!Data->GetTunnelPassword().IsEmpty()),
          BooleanToEngStr(!Data->GetTunnelPublicKeyFile().IsEmpty()));
        ADF("Tunnel: Local port number: %d", Data->GetTunnelLocalPortNumber());
#endif // #if 0
      }
    }
    ADF("Transfer Protocol: %s", Data->GetFSProtocolStr());
    ADF("Code Page: %d", Data->GetCodePageAsNumber());
    if (Data->GetUsesSsh() || (Data->GetFSProtocol() == fsFTP))
    {
      TPingType PingType;
      intptr_t PingInterval;
      if (Data->GetFSProtocol() == fsFTP)
      {
        PingType = Data->GetFtpPingType();
        PingInterval = Data->GetFtpPingInterval();
      }
      else
      {
        PingType = Data->GetPingType();
        PingInterval = Data->GetPingInterval();
      }
      ADF("Ping type: %s, Ping interval: %d sec; Timeout: %d sec",
        EnumName(PingType, PingTypeNames), PingInterval, Data->GetTimeout());
      ADF("Disable Nagle: %s",
        BooleanToEngStr(Data->GetTcpNoDelay()));
    }
    TProxyMethod ProxyMethod = Data->GetActualProxyMethod();
    {
      UnicodeString fp = FORMAT(L"FTP proxy %d", Data->GetFtpProxyLogonType());
      ADF("Proxy: %s",
        (Data->GetFtpProxyLogonType() != 0) ? fp : EnumName(ProxyMethod, ProxyMethodNames));
    }
    if ((Data->GetFtpProxyLogonType() != 0) || (ProxyMethod != ::pmNone))
    {
      ADF("ProxyHostName: %s (Port: %d); ProxyUsername: %s; Passwd: %s",
        Data->GetProxyHost(), Data->GetProxyPort(),
        Data->GetProxyUsername(), BooleanToEngStr(!Data->GetProxyPassword().IsEmpty()));
      if (ProxyMethod == pmTelnet)
      {
        ADF("Telnet command: %s", Data->GetProxyTelnetCommand());
      }
      if (ProxyMethod == pmCmd)
      {
        ADF("Local command: %s", Data->GetProxyLocalCommand());
      }
    }
    if (Data->GetUsesSsh() || (Data->GetFSProtocol() == fsFTP))
    {
      ADF("Send buffer: %d", Data->GetSendBuf());
    }
    if (Data->GetUsesSsh())
    {
      ADF("SSH protocol version: %s; Compression: %s",
        Data->GetSshProtStr(), BooleanToEngStr(Data->GetCompression()));
      ADF("Bypass authentication: %s",
        BooleanToEngStr(Data->GetSshNoUserAuth()));
      ADF("Try agent: %s; Agent forwarding: %s; TIS/CryptoCard: %s; KI: %s; GSSAPI: %s",
        BooleanToEngStr(Data->GetTryAgent()), BooleanToEngStr(Data->GetAgentFwd()), BooleanToEngStr(Data->GetAuthTIS()),
        BooleanToEngStr(Data->GetAuthKI()), BooleanToEngStr(Data->GetAuthGSSAPI()));
      if (Data->GetAuthGSSAPI())
      {
        ADF("GSSAPI: Forwarding: %s",
          BooleanToEngStr(Data->GetGSSAPIFwdTGT()));
      }
      ADF("Ciphers: %s; Ssh2DES: %s",
        Data->GetCipherList(), BooleanToEngStr(Data->GetSsh2DES()));
      ADF("KEX: %s", Data->GetKexList());
      UnicodeString Bugs;
      for (intptr_t Index = 0; Index < BUG_COUNT; ++Index)
      {
        AddToList(Bugs, EnumName(Data->GetBug(static_cast<TSshBug>(Index)), AutoSwitchNames), L",");
      }
      ADF("SSH Bugs: %s", Bugs);
      ADF("Simple channel: %s", BooleanToEngStr(Data->GetSshSimple()));
      ADF("Return code variable: %s; Lookup user groups: %s",
        Data->GetDetectReturnVar() ? UnicodeString(L"Autodetect") : Data->GetReturnVar(),
        EnumName(Data->GetLookupUserGroups(), AutoSwitchNames));
      ADF("Shell: %s", Data->GetShell().IsEmpty() ? UnicodeString(L"default") : Data->GetShell());
      ADF("EOL: %s, UTF: %s", EnumName(Data->GetEOLType(), EOLTypeNames), EnumName(Data->GetNotUtf(), NotAutoSwitchNames)); // NotUtf duplicated in FTP branch
      ADF("Clear aliases: %s, Unset nat.vars: %s, Resolve symlinks: %s; Follow directory symlinks: %s",
        BooleanToEngStr(Data->GetClearAliases()), BooleanToEngStr(Data->GetUnsetNationalVars()),
        BooleanToEngStr(Data->GetResolveSymlinks()), BooleanToEngStr(Data->GetFollowDirectorySymlinks()));
      ADF("LS: %s, Ign LS warn: %s, Scp1 Comp: %s",
        Data->GetListingCommand(),
        BooleanToEngStr(Data->GetIgnoreLsWarnings()),
        BooleanToEngStr(Data->GetScp1Compatibility()));
    }
    if ((Data->GetFSProtocol() == fsSFTP) || (Data->GetFSProtocol() == fsSFTPonly))
    {
      UnicodeString Bugs;
      for (intptr_t Index = 0; Index < SFTP_BUG_COUNT; ++Index)
      {
        AddToList(Bugs, EnumName(Data->GetSFTPBug(static_cast<TSftpBug>(Index)), AutoSwitchNames), L",");
      }
      ADF("SFTP Bugs: %s", Bugs);
      ADF("SFTP Server: %s", Data->GetSftpServer().IsEmpty() ? UnicodeString(L"default") : Data->GetSftpServer());
    }
    bool FtpsOn = false;
    if (Data->GetFSProtocol() == fsFTP)
    {
      ADF("UTF: %s", EnumName(Data->GetNotUtf(), NotAutoSwitchNames)); // duplicated in UsesSsh branch
      UnicodeString Ftps;
      switch (Data->GetFtps())
      {
      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->GetFtps() == ftpsNone);
        Ftps = L"None";
        break;
      }
      // kind of hidden option, so do not reveal it unless it is set
      if (Data->GetFtpTransferActiveImmediately() != asAuto)
      {
        ADF("Transfer active immediately: %s", EnumName(Data->GetFtpTransferActiveImmediately(), AutoSwitchNames));
      }
      ADF("FTPS: %s [Client certificate: %s]",
        Ftps, LogSensitive(Data->GetTlsCertificateFile()));
      ADF("FTP: Passive: %s [Force IP: %s]; MLSD: %s [List all: %s]; HOST: %s",
        BooleanToEngStr(Data->GetFtpPasvMode()),
        EnumName(Data->GetFtpForcePasvIp(), AutoSwitchNames),
        EnumName(Data->GetFtpUseMlsd(), AutoSwitchNames),
        EnumName(Data->GetFtpListAll(), AutoSwitchNames),
        EnumName(Data->GetFtpHost(), AutoSwitchNames));
    }
    if (Data->GetFSProtocol() == fsWebDAV)
    {
      FtpsOn = (Data->GetFtps() != ftpsNone);
      ADF("HTTPS: %s [Client certificate: %s]",
        BooleanToEngStr(FtpsOn), LogSensitive(Data->GetTlsCertificateFile()));
    }
    if (FtpsOn)
    {
      if (Data->GetFSProtocol() == fsFTP)
      {
        ADF("Session reuse: %s", BooleanToEngStr(Data->GetSslSessionReuse()));
      }
      ADF("TLS/SSL versions: %s-%s", GetTlsVersionName(Data->GetMinTlsVersion()), GetTlsVersionName(Data->GetMaxTlsVersion()));
    }
    ADF("Local directory: %s, Remote directory: %s, Update: %s, Cache: %s",
      Data->GetLocalDirectory().IsEmpty() ? UnicodeString(L"default") : Data->GetLocalDirectory(),
      Data->GetRemoteDirectory().IsEmpty() ? UnicodeString(L"home") : Data->GetRemoteDirectory(),
      BooleanToEngStr(Data->GetUpdateDirectories()),
      BooleanToEngStr(Data->GetCacheDirectories()));
    ADF("Cache directory changes: %s, Permanent: %s",
      BooleanToEngStr(Data->GetCacheDirectoryChanges()),
      BooleanToEngStr(Data->GetPreserveDirectoryChanges()));
    ADF("Recycle bin: Delete to: %s, Overwritten to: %s, Bin path: %s",
      BooleanToEngStr(Data->GetDeleteToRecycleBin()),
      BooleanToEngStr(Data->GetOverwrittenToRecycleBin()),
      Data->GetRecycleBinPath());
    if (Data->GetTrimVMSVersions())
    {
      ADF("Trim VMS versions: %s",
        BooleanToEngStr(Data->GetTrimVMSVersions()));
    }
    UnicodeString TimeInfo;
    if ((Data->GetFSProtocol() == fsSFTP) || (Data->GetFSProtocol() == fsSFTPonly) || (Data->GetFSProtocol() == fsSCPonly) || (Data->GetFSProtocol() == fsWebDAV))
    {
      AddToList(TimeInfo, FORMAT(L"DST mode: %s", EnumName(ToInt(Data->GetDSTMode()), DSTModeNames)), L";");
    }
    if ((Data->GetFSProtocol() == fsSCPonly) || (Data->GetFSProtocol() == fsFTP))
    {
      intptr_t TimeDifferenceMin = TimeToMinutes(Data->GetTimeDifference());
      AddToList(TimeInfo, FORMAT(L"Timezone offset: %dh %dm", TimeDifferenceMin / MinsPerHour, TimeDifferenceMin % MinsPerHour), L";");
    }
    ADSTR(TimeInfo);

    if (Data->GetFSProtocol() == fsWebDAV)
    {
      ADF("Compression: %s",
        BooleanToEngStr(Data->GetCompression()));
    }

    AddSeparator();
  }
}