#include "../Shared/TriggerProtocol.h" #include "../Shared/DvcServerIo.h" #include #include #include #include #include #include #include #include #include #include namespace { constexpr wchar_t kIniSection[] = L"SIOMIN"; constexpr wchar_t kIniTempPathKey[] = L"SIOMIN_TEMP_PATH"; constexpr wchar_t kIniPathKey[] = L"SIOMIN_PATH"; constexpr wchar_t kIniDebugKey[] = L"DEBUG"; constexpr wchar_t kDefaultTempPath[] = L"C:\\PDATA\\temp_siomin.sdx"; constexpr wchar_t kDefaultSiominPath[] = L"C:\\PDATA\\siomin.sdx"; constexpr DWORD kDvcTimeoutMs = 5000; enum class ExitCode : int { Success = 0, IniPathFailed = 20, IniCreateFailed = 21, ChannelOpenFailed = 22, HostnameRequestWriteFailed = 23, HostnameResponseReadFailed = 24, InvalidHostnameResponse = 25, InvalidClientHostname = 26, TempFileReadFailed = 27, NoSiominHostFieldFound = 28, FinalPathInvalid = 29, FinalFileOpenFailed = 30, FinalFileBoundaryInvalid = 31, FinalFileWriteFailed = 32, StartTriggerWriteFailed = 33, TempFileClearFailed = 34, StartAckReadFailed = 35, StartRejected = 36, InvalidStartAck = 37, RollbackFailed = 38 }; std::filesystem::path GetOwnPath() { std::vector buffer(32768, L'\0'); const DWORD chars = GetModuleFileNameW(nullptr, buffer.data(), static_cast(buffer.size())); if (chars == 0 || chars >= buffer.size()) { return {}; } return std::filesystem::path(std::wstring(buffer.data(), chars)); } std::string WideToUtf8(const std::wstring& value) { if (value.empty()) { return {}; } const int required = WideCharToMultiByte( CP_UTF8, 0, value.data(), static_cast(value.size()), nullptr, 0, nullptr, nullptr); if (required <= 0) { return {}; } std::string result(static_cast(required), '\0'); const int written = WideCharToMultiByte( CP_UTF8, 0, value.data(), static_cast(value.size()), result.data(), required, nullptr, nullptr); if (written != required) { return {}; } return result; } class DebugLogger { public: void Initialize(bool enabled, const std::filesystem::path& exePath) { enabled_ = enabled; logPath_ = exePath; logPath_.replace_extension(L".log"); } void Log(const std::wstring& message) const { const std::wstring debugLine = L"PlandentSiominClientTrigger: " + message + L"\n"; OutputDebugStringW(debugLine.c_str()); if (!enabled_ || logPath_.empty()) { return; } SYSTEMTIME st{}; GetLocalTime(&st); wchar_t prefix[96]{}; swprintf_s( prefix, sizeof(prefix) / sizeof(prefix[0]), L"%04u-%02u-%02u %02u:%02u:%02u.%03u [PID:%lu] ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, GetCurrentProcessId()); const std::string utf8 = WideToUtf8(std::wstring(prefix) + message + L"\r\n"); if (utf8.empty()) { return; } HANDLE file = CreateFileW( logPath_.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { return; } DWORD written = 0; WriteFile( file, utf8.data(), static_cast(utf8.size()), &written, nullptr); CloseHandle(file); } void LogWin32Error(const std::wstring& step, DWORD error) const { Log(step + L" fehlgeschlagen, Win32=" + std::to_wstring(error)); } const std::filesystem::path& Path() const { return logPath_; } private: bool enabled_ = false; std::filesystem::path logPath_; }; void DebugMessage(const std::wstring& message) { const std::wstring line = L"PlandentSiominClientTrigger: " + message + L"\n"; OutputDebugStringW(line.c_str()); } bool WriteNewUnicodeIni(const std::filesystem::path& iniPath) { const std::wstring content = std::wstring(L"[") + kIniSection + L"]\r\n" + kIniTempPathKey + L"=" + kDefaultTempPath + L"\r\n" + kIniPathKey + L"=" + kDefaultSiominPath + L"\r\n" + kIniDebugKey + L"=0\r\n"; HANDLE file = CreateFileW( iniPath.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { return GetLastError() == ERROR_FILE_EXISTS; } const BYTE bom[] = { 0xFF, 0xFE }; DWORD written = 0; BOOL ok = WriteFile(file, bom, static_cast(sizeof(bom)), &written, nullptr); if (!ok || written != sizeof(bom)) { CloseHandle(file); return false; } const DWORD bytes = static_cast(content.size() * sizeof(wchar_t)); written = 0; ok = WriteFile(file, content.data(), bytes, &written, nullptr); const bool result = ok && written == bytes; if (result) { FlushFileBuffers(file); } CloseHandle(file); return result; } std::wstring ReadIniValue( const std::filesystem::path& iniPath, const wchar_t* key, const wchar_t* defaultValue) { std::vector buffer(32768, L'\0'); const DWORD chars = GetPrivateProfileStringW( kIniSection, key, defaultValue, buffer.data(), static_cast(buffer.size()), iniPath.c_str()); if (chars == 0) { return defaultValue; } return std::wstring(buffer.data(), chars); } bool ReadIniBool(const std::filesystem::path& iniPath, const wchar_t* key, bool defaultValue) { return GetPrivateProfileIntW( kIniSection, key, defaultValue ? 1 : 0, iniPath.c_str()) != 0; } bool EnsureDebugIniKey(const std::filesystem::path& iniPath) { constexpr wchar_t kMissingMarker[] = L"__PLANDENT_MISSING__"; wchar_t value[64]{}; GetPrivateProfileStringW( kIniSection, kIniDebugKey, kMissingMarker, value, static_cast(sizeof(value) / sizeof(value[0])), iniPath.c_str()); if (wcscmp(value, kMissingMarker) != 0) { return true; } return WritePrivateProfileStringW( kIniSection, kIniDebugKey, L"0", iniPath.c_str()) != FALSE; } bool WriteChannelMessage(HANDLE channel, const char* message, std::size_t length, DWORD& win32Error) { win32Error = ERROR_SUCCESS; ULONG bytesWritten = 0; const BOOL ok = WTSVirtualChannelWrite( channel, const_cast(message), static_cast(length), &bytesWritten); if (!ok) { win32Error = GetLastError(); return false; } if (bytesWritten != static_cast(length)) { win32Error = ERROR_WRITE_FAULT; return false; } return true; } bool Utf8ToWide(const std::string& value, std::wstring& result) { result.clear(); if (value.empty()) { return false; } const int required = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast(value.size()), nullptr, 0); if (required <= 0) { return false; } result.resize(static_cast(required)); const int written = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, value.data(), static_cast(value.size()), result.data(), required); return written == required; } enum class HostnameQueryResult { Success, WriteFailed, ReadFailed, InvalidResponse }; HostnameQueryResult QueryClientHostname( HANDLE channel, std::wstring& hostname, DWORD& win32Error, const DebugLogger& log) { hostname.clear(); win32Error = ERROR_SUCCESS; log.Log(L"Sende GET_CLIENT_HOSTNAME ueber DVC."); if (!WriteChannelMessage( channel, hook::protocol::kGetClientHostnameMessage, hook::protocol::kGetClientHostnameMessageLength, win32Error)) { return HostnameQueryResult::WriteFailed; } std::vector response; if (!hook::dvc::ReadMessage(channel, kDvcTimeoutMs, response, win32Error)) { return HostnameQueryResult::ReadFailed; } log.Log(L"DVC-Hostname-Antwort empfangen, Payload-Bytes=" + std::to_wstring(response.size()) + L"."); if (response.size() <= hook::protocol::kClientHostnameResponsePrefixLength || std::memcmp( response.data(), hook::protocol::kClientHostnameResponsePrefix, hook::protocol::kClientHostnameResponsePrefixLength) != 0) { win32Error = ERROR_INVALID_DATA; return HostnameQueryResult::InvalidResponse; } const std::string hostnameUtf8( response.data() + hook::protocol::kClientHostnameResponsePrefixLength, response.data() + response.size()); if (!Utf8ToWide(hostnameUtf8, hostname)) { win32Error = ERROR_NO_UNICODE_TRANSLATION; return HostnameQueryResult::InvalidResponse; } return HostnameQueryResult::Success; } bool HostnameToSiominBytes(const std::wstring& hostname, std::vector& result) { result.clear(); if (hostname.empty() || hostname.size() > 255) { return false; } // Das beobachtete SIOMIN-Format verwendet einbyteige, NUL-separierte Felder. // Fuer Rechnernamen akzeptieren wir daher bewusst nur druckbares ASCII. result.reserve(hostname.size()); for (wchar_t ch : hostname) { if (ch < 0x20 || ch > 0x7E || ch == L'\\' || ch == L'/') { return false; } result.push_back(static_cast(ch)); } return !result.empty(); } bool ReadAllBytes(const std::filesystem::path& path, std::vector& bytes) { bytes.clear(); std::ifstream stream(path, std::ios::binary | std::ios::ate); if (!stream) { return false; } const std::streamoff length = stream.tellg(); if (length < 0) { return false; } bytes.resize(static_cast(length)); stream.seekg(0, std::ios::beg); if (!bytes.empty()) { stream.read(reinterpret_cast(bytes.data()), static_cast(bytes.size())); if (!stream) { return false; } } return true; } std::size_t FindByte( const std::vector& data, std::size_t begin, std::size_t end, std::uint8_t value) { for (std::size_t i = begin; i < end; ++i) { if (data[i] == value) { return i; } } return end; } bool IsSiominActionRecordAt( const std::vector& data, std::size_t recordStart, std::size_t& recordEnd) { recordEnd = recordStart; // WICHTIG: // Das erste Byte eines SIOMIN-Datensatzes ist KEIN Kennzeichen, // sondern die Gesamtlaenge des Datensatzes in Bytes (inkl. Laengenbyte // und abschliessendem CRLF). // // Beispiele aus realen Dateien: // 0x3A 00 'N' 00 ... -> Laenge 58 // 0x40 00 'N' 00 ... -> Laenge 64 // 0x46 00 'A' 00 ... -> Laenge 70 // 0x4C 00 'A' 00 ... -> Laenge 76 // // Der Datensatztyp steht erst im dritten Byte ('N' bzw. 'A'). if (recordStart + 4 > data.size()) { return false; } const std::size_t recordLength = data[recordStart]; if (recordLength < 6 || recordStart + recordLength > data.size()) { return false; } recordEnd = recordStart + recordLength; if (data[recordStart + 1] != 0x00 || data[recordStart + 2] != static_cast('A') || data[recordStart + 3] != 0x00) { return false; } // Die beobachteten SIOMIN-Datensaetze enden mit CRLF. Damit verhindern // wir, dass bei einer korrupten Laengenangabe in Folgedaten geparst wird. if (recordLength < 2 || data[recordEnd - 2] != 0x0D || data[recordEnd - 1] != 0x0A) { return false; } return true; } std::wstring HexPreview(const std::vector& data, std::size_t maxBytes = 256) { if (data.empty()) { return L""; } const std::size_t count = std::min(data.size(), maxBytes); std::wstring result; result.reserve(count * 3 + 32); constexpr wchar_t hex[] = L"0123456789ABCDEF"; for (std::size_t i = 0; i < count; ++i) { if (i != 0) { result.push_back(L' '); } const std::uint8_t value = data[i]; result.push_back(hex[(value >> 4) & 0x0F]); result.push_back(hex[value & 0x0F]); } if (count < data.size()) { result += L" ..."; } return result; } struct HostFieldRange { std::size_t recordStart = 0; std::size_t recordEnd = 0; // exklusiv std::size_t begin = 0; std::size_t end = 0; // exklusiv; das abschliessende NUL bleibt erhalten std::uint8_t originalRecordLength = 0; }; bool TryGetHostnameField( const std::vector& source, std::size_t recordStart, HostFieldRange& range) { range = {}; std::size_t recordEnd = 0; if (!IsSiominActionRecordAt(source, recordStart, recordEnd)) { return false; } // SIOMIN-Aktionsdatensatz: // LENGTH\0 A\0 Nachname\0 Vorname\0 Geburtsdatum\0 ID\0 HOSTNAME\0 Datum\0 Uhrzeit... // // Vom Beginn des Datensatzes aus muessen sechs NUL-Trenner uebersprungen // werden. Danach beginnt das Hostname-Feld. std::size_t fieldStart = recordStart; for (int field = 0; field < 6; ++field) { const std::size_t separator = FindByte(source, fieldStart, recordEnd, 0x00); if (separator == recordEnd) { return false; } for (std::size_t i = fieldStart; i < separator; ++i) { if (source[i] == 0x0D || source[i] == 0x0A) { return false; } } fieldStart = separator + 1; } if (fieldStart >= recordEnd) { return false; } const std::size_t fieldEnd = FindByte(source, fieldStart, recordEnd, 0x00); if (fieldEnd == recordEnd || fieldEnd == fieldStart) { return false; } for (std::size_t i = fieldStart; i < fieldEnd; ++i) { if (source[i] == 0x0D || source[i] == 0x0A) { return false; } } range.recordStart = recordStart; range.recordEnd = recordEnd; range.begin = fieldStart; range.end = fieldEnd; range.originalRecordLength = source[recordStart]; return true; } bool ReplaceSiominHostnameFields( const std::vector& source, const std::vector& hostname, std::vector& transformed, std::size_t& replacementCount) { transformed.clear(); replacementCount = 0; if (source.empty() || hostname.empty()) { return false; } // SIOMIN ist laengencodiert: Das erste Byte jedes Datensatzes enthaelt // seine Gesamtlaenge. Deshalb duerfen wir beim Ersetzen eines Hostnamens // nicht nur den Text austauschen, sondern MUESSEN auch dieses Laengenbyte // um die Groessendifferenz korrigieren. // // Beispiel: // alter Datensatz: 0x4C = 76 Bytes, Host "TS-FR" = 5 Bytes // neuer Host: "FR-Empfang-3R" = 13 Bytes // neue Laenge: 76 + 8 = 84 = 0x54 std::vector replacements; std::size_t recordStart = 0; while (recordStart < source.size()) { if (recordStart + 1 > source.size()) { transformed = source; return false; } const std::size_t recordLength = source[recordStart]; if (recordLength == 0 || recordStart + recordLength > source.size()) { transformed = source; return false; } const std::size_t recordEnd = recordStart + recordLength; // Grundlegende Integritaetspruefung fuer alle beobachteten Datensaetze. if (recordLength < 2 || source[recordEnd - 2] != 0x0D || source[recordEnd - 1] != 0x0A) { transformed = source; return false; } HostFieldRange range{}; if (TryGetHostnameField(source, recordStart, range)) { const std::size_t oldHostnameLength = range.end - range.begin; const std::ptrdiff_t delta = static_cast(hostname.size()) - static_cast(oldHostnameLength); const std::ptrdiff_t newRecordLength = static_cast(range.originalRecordLength) + delta; // Das Format hat nur ein Byte fuer die Datensatzlaenge. if (newRecordLength <= 0 || newRecordLength > 255) { transformed = source; return false; } replacements.push_back(range); } recordStart = recordEnd; } if (recordStart != source.size() || replacements.empty()) { transformed = source; return false; } std::size_t outputSize = source.size(); for (const HostFieldRange& range : replacements) { outputSize -= (range.end - range.begin); outputSize += hostname.size(); } transformed.reserve(outputSize); std::size_t copyFrom = 0; for (const HostFieldRange& range : replacements) { // Alle Bytes vor diesem Datensatz unveraendert uebernehmen. transformed.insert( transformed.end(), source.begin() + static_cast(copyFrom), source.begin() + static_cast(range.recordStart)); const std::size_t outputRecordStart = transformed.size(); // Datensatz bis direkt vor den Hostnamen kopieren. transformed.insert( transformed.end(), source.begin() + static_cast(range.recordStart), source.begin() + static_cast(range.begin)); const std::size_t oldHostnameLength = range.end - range.begin; const std::ptrdiff_t delta = static_cast(hostname.size()) - static_cast(oldHostnameLength); const std::size_t newRecordLength = static_cast( static_cast(range.originalRecordLength) + delta); // Das bereits kopierte erste Byte ist die alte Datensatzlaenge. // Es muss auf die neue tatsaechliche Laenge gesetzt werden. transformed[outputRecordStart] = static_cast(newRecordLength); transformed.insert(transformed.end(), hostname.begin(), hostname.end()); // NUL hinter dem alten Hostnamen und den gesamten Rest des Datensatzes // unveraendert uebernehmen. transformed.insert( transformed.end(), source.begin() + static_cast(range.end), source.begin() + static_cast(range.recordEnd)); copyFrom = range.recordEnd; ++replacementCount; } transformed.insert( transformed.end(), source.begin() + static_cast(copyFrom), source.end()); return replacementCount > 0; } bool IsSamePath(const std::filesystem::path& a, const std::filesystem::path& b) { const std::wstring aa = std::filesystem::absolute(a).lexically_normal().wstring(); const std::wstring bb = std::filesystem::absolute(b).lexically_normal().wstring(); return _wcsicmp(aa.c_str(), bb.c_str()) == 0; } struct AppendState { bool existed = false; std::uint64_t originalSize = 0; }; bool AppendBytesPreservingExistingFile( const std::filesystem::path& finalPath, const std::vector& bytes, bool& invalidBoundary, AppendState& appendState) { invalidBoundary = false; appendState = {}; std::error_code ec; appendState.existed = std::filesystem::exists(finalPath, ec); if (ec) { return false; } if (appendState.existed) { const std::uintmax_t size = std::filesystem::file_size(finalPath, ec); if (ec) { return false; } appendState.originalSize = static_cast(size); if (size > 0) { if (size < 2) { invalidBoundary = true; return false; } HANDLE existing = CreateFileW( finalPath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (existing == INVALID_HANDLE_VALUE) { return false; } LARGE_INTEGER offset{}; offset.QuadPart = -2; if (!SetFilePointerEx(existing, offset, nullptr, FILE_END)) { CloseHandle(existing); return false; } BYTE tail[2]{}; DWORD read = 0; const BOOL readOk = ReadFile(existing, tail, 2, &read, nullptr); CloseHandle(existing); if (!readOk || read != 2) { return false; } if (!(tail[0] == 0x0D && tail[1] == 0x0A)) { invalidBoundary = true; return false; } } } else { const std::filesystem::path parent = finalPath.parent_path(); if (!parent.empty()) { std::filesystem::create_directories(parent, ec); if (ec) { return false; } } } HANDLE file = CreateFileW( finalPath.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { return false; } std::size_t offset = 0; bool ok = true; while (offset < bytes.size()) { const DWORD chunk = static_cast( std::min(bytes.size() - offset, 1024 * 1024)); DWORD written = 0; if (!WriteFile(file, bytes.data() + offset, chunk, &written, nullptr) || written != chunk) { ok = false; break; } offset += written; } if (ok) { ok = FlushFileBuffers(file) != FALSE; } CloseHandle(file); return ok; } bool TruncateFileToSize(const std::filesystem::path& path, std::uint64_t size) { HANDLE file = CreateFileW( path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) { return false; } LARGE_INTEGER position{}; position.QuadPart = static_cast(size); bool ok = SetFilePointerEx(file, position, nullptr, FILE_BEGIN) != FALSE; if (ok) { ok = SetEndOfFile(file) != FALSE; } if (ok) { ok = FlushFileBuffers(file) != FALSE; } CloseHandle(file); return ok; } bool ClearSourceFile(const std::filesystem::path& path) { return TruncateFileToSize(path, 0); } bool RollbackAppend(const std::filesystem::path& finalPath, const AppendState& state) { if (!state.existed) { return DeleteFileW(finalPath.c_str()) != FALSE; } return TruncateFileToSize(finalPath, state.originalSize); } enum class StartTriggerResult { Success, OpenFailed, WriteFailed, AckReadFailed, Rejected, InvalidAck }; StartTriggerResult SendStartTrigger(DWORD& win32Error, const DebugLogger& log) { win32Error = ERROR_SUCCESS; log.Log(L"Oeffne neuen DVC fuer START-Trigger."); HANDLE channel = WTSVirtualChannelOpenEx( WTS_CURRENT_SESSION, const_cast(hook::protocol::kChannelName), WTS_CHANNEL_OPTION_DYNAMIC | WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED); if (channel == nullptr) { win32Error = GetLastError(); return StartTriggerResult::OpenFailed; } log.Log(L"Sende START ueber DVC."); if (!WriteChannelMessage( channel, hook::protocol::kStartMessage, hook::protocol::kStartMessageLength, win32Error)) { WTSVirtualChannelClose(channel); return StartTriggerResult::WriteFailed; } std::vector response; if (!hook::dvc::ReadMessage(channel, kDvcTimeoutMs, response, win32Error)) { WTSVirtualChannelClose(channel); return StartTriggerResult::AckReadFailed; } WTSVirtualChannelClose(channel); log.Log(L"START-Quittung empfangen, Payload-Bytes=" + std::to_wstring(response.size()) + L"."); if (response.size() == hook::protocol::kStartOkResponseLength && std::memcmp( response.data(), hook::protocol::kStartOkResponse, hook::protocol::kStartOkResponseLength) == 0) { return StartTriggerResult::Success; } if (response.size() == hook::protocol::kStartFailedResponseLength && std::memcmp( response.data(), hook::protocol::kStartFailedResponse, hook::protocol::kStartFailedResponseLength) == 0) { return StartTriggerResult::Rejected; } win32Error = ERROR_INVALID_DATA; return StartTriggerResult::InvalidAck; } } int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { const std::filesystem::path exePath = GetOwnPath(); if (exePath.empty()) { DebugMessage(L"Eigener EXE-Pfad konnte nicht ermittelt werden."); return static_cast(ExitCode::IniPathFailed); } std::filesystem::path iniPath = exePath; iniPath.replace_extension(L".ini"); std::error_code ec; if (!std::filesystem::exists(iniPath, ec)) { if (ec || !WriteNewUnicodeIni(iniPath)) { DebugMessage(L"INI konnte nicht angelegt werden: " + iniPath.wstring()); return static_cast(ExitCode::IniCreateFailed); } } // Auch bei bereits vorhandenen INIs aus aelteren Versionen wird DEBUG=0 // einmalig ergaenzt, ohne die bestehenden Pfade zu veraendern. if (!EnsureDebugIniKey(iniPath)) { DebugMessage(L"DEBUG-Schluessel konnte in der INI nicht ergaenzt werden; Verarbeitung laeuft ohne Datei-Log weiter."); } DebugLogger log; log.Initialize(ReadIniBool(iniPath, kIniDebugKey, false), exePath); log.Log(L"=== Start ==="); log.Log(L"EXE=" + exePath.wstring()); log.Log(L"INI=" + iniPath.wstring()); if (ReadIniBool(iniPath, kIniDebugKey, false)) { log.Log(L"DEBUG=1, Logdatei=" + log.Path().wstring()); } const std::filesystem::path tempPath = ReadIniValue(iniPath, kIniTempPathKey, kDefaultTempPath); const std::filesystem::path finalPath = ReadIniValue(iniPath, kIniPathKey, kDefaultSiominPath); log.Log(L"SIOMIN_TEMP_PATH=" + tempPath.wstring()); log.Log(L"SIOMIN_PATH=" + finalPath.wstring()); if (tempPath.empty() || finalPath.empty() || IsSamePath(tempPath, finalPath)) { log.Log(L"SIOMIN_TEMP_PATH/SIOMIN_PATH sind ungueltig oder identisch."); return static_cast(ExitCode::FinalPathInvalid); } log.Log(L"Oeffne DVC fuer Client-Hostname-Abfrage."); HANDLE hostnameChannel = WTSVirtualChannelOpenEx( WTS_CURRENT_SESSION, const_cast(hook::protocol::kChannelName), WTS_CHANNEL_OPTION_DYNAMIC | WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED); if (hostnameChannel == nullptr) { log.LogWin32Error(L"WTSVirtualChannelOpenEx (Hostname)", GetLastError()); return static_cast(ExitCode::ChannelOpenFailed); } std::wstring clientHostname; DWORD dvcError = ERROR_SUCCESS; const HostnameQueryResult hostnameResult = QueryClientHostname( hostnameChannel, clientHostname, dvcError, log); WTSVirtualChannelClose(hostnameChannel); if (hostnameResult != HostnameQueryResult::Success) { if (hostnameResult == HostnameQueryResult::WriteFailed) { log.LogWin32Error(L"Hostname-Anfrage ueber DVC schreiben", dvcError); return static_cast(ExitCode::HostnameRequestWriteFailed); } if (hostnameResult == HostnameQueryResult::ReadFailed) { log.LogWin32Error(L"Client-Hostname ueber DVC lesen", dvcError); return static_cast(ExitCode::HostnameResponseReadFailed); } log.LogWin32Error(L"Ungueltige DVC-Hostname-Antwort", dvcError); return static_cast(ExitCode::InvalidHostnameResponse); } log.Log(L"Client-Hostname=" + clientHostname); std::vector hostnameBytes; if (!HostnameToSiominBytes(clientHostname, hostnameBytes)) { log.Log(L"Client-Hostname ist fuer das SIOMIN-Byteformat nicht geeignet: " + clientHostname); return static_cast(ExitCode::InvalidClientHostname); } std::vector tempBytes; if (!ReadAllBytes(tempPath, tempBytes)) { log.Log(L"SIOMIN_TEMP_PATH konnte nicht gelesen werden: " + tempPath.wstring()); return static_cast(ExitCode::TempFileReadFailed); } log.Log(L"Temp-SDX gelesen, Bytes=" + std::to_wstring(tempBytes.size()) + L"."); std::vector transformed; std::size_t replacementCount = 0; if (!ReplaceSiominHostnameFields(tempBytes, hostnameBytes, transformed, replacementCount)) { log.Log(L"Kein gueltiges SIOMIN-Aktions-/Hostname-Feld in der temp_siomin.sdx gefunden."); log.Log(L"Temp-SDX Hex-Vorschau: " + HexPreview(tempBytes)); return static_cast(ExitCode::NoSiominHostFieldFound); } log.Log( L"SIOMIN-Transformation erfolgreich: Hostfelder=" + std::to_wstring(replacementCount) + L", Ausgabe-Bytes=" + std::to_wstring(transformed.size()) + L"."); bool invalidBoundary = false; AppendState appendState{}; if (!AppendBytesPreservingExistingFile(finalPath, transformed, invalidBoundary, appendState)) { if (invalidBoundary) { log.Log(L"Bestehende siomin.sdx endet nicht mit CRLF; aus Sicherheitsgruenden nicht veraendert."); return static_cast(ExitCode::FinalFileBoundaryInvalid); } log.LogWin32Error(L"SIOMIN_PATH anlegen/erweitern", GetLastError()); return static_cast(ExitCode::FinalFileWriteFailed); } log.Log( L"Finale SIOMIN-Datei erweitert. Vorherige Groesse=" + std::to_wstring(appendState.originalSize) + L", angehaengte Bytes=" + std::to_wstring(transformed.size()) + L"."); // Erst nachdem die finale Datei sicher geschrieben und geflusht wurde, wird // die Quell-Datei geleert. Dadurch wird derselbe SIOMIN-Eintrag beim naechsten // Aufruf nicht erneut verarbeitet. if (!ClearSourceFile(tempPath)) { const DWORD clearError = GetLastError(); log.LogWin32Error(L"temp_siomin.sdx leeren", clearError); // Verhindert Dubletten: Wenn die Quelle nicht geleert werden konnte, // versuchen wir den soeben angehaengten Block wieder zurueckzurollen. if (!RollbackAppend(finalPath, appendState)) { log.LogWin32Error(L"Rollback der finalen siomin.sdx", GetLastError()); return static_cast(ExitCode::RollbackFailed); } log.Log(L"Finale siomin.sdx wurde wegen Fehler beim Leeren der Quelle erfolgreich zurueckgerollt."); return static_cast(ExitCode::TempFileClearFailed); } log.Log(L"temp_siomin.sdx nach erfolgreicher Uebernahme auf 0 Bytes geleert."); DWORD startError = ERROR_SUCCESS; const StartTriggerResult startResult = SendStartTrigger(startError, log); if (startResult != StartTriggerResult::Success) { if (startResult == StartTriggerResult::OpenFailed) { log.LogWin32Error(L"DVC fuer START oeffnen", startError); return static_cast(ExitCode::ChannelOpenFailed); } if (startResult == StartTriggerResult::WriteFailed) { log.LogWin32Error(L"START-Trigger schreiben", startError); return static_cast(ExitCode::StartTriggerWriteFailed); } if (startResult == StartTriggerResult::AckReadFailed) { log.LogWin32Error(L"START-Quittung lesen", startError); return static_cast(ExitCode::StartAckReadFailed); } if (startResult == StartTriggerResult::Rejected) { log.Log(L"Client hat START mit START_FAILED beantwortet."); return static_cast(ExitCode::StartRejected); } log.LogWin32Error(L"Ungueltige START-Quittung", startError); return static_cast(ExitCode::InvalidStartAck); } log.Log(L"START_OK vom Client empfangen."); log.Log(L"=== Erfolgreich beendet ==="); return static_cast(ExitCode::Success); }