feat: initiale Version 0.1.0 des MSTSC Script Hooks
- Native x64 MSTSC-DVC-Erweiterung für lokalen Programmstart - Automatischer Script-/Programmstart bei RDP-Verbindungsaufbau - Separater Programmstart über Trigger innerhalb der RDP-Sitzung - Bidirektionaler DVC zur Ermittlung des lokalen Client-Hostnamens - SIOMIN-Trigger zur Anbindung lokaler Sidexis-Installationen - SIOMIN-Datensatzlängen und Hostname-Felder bytegenau angepasst - Temporäre SIOMIN-Einträge nach erfolgreicher Übernahme bereinigt - Native Win32-Konfigurationsoberfläche ohne .NET-Abhängigkeit - Mehrfachstartschutz und optionaler unsichtbarer Programmstart - Debug- und Plugin-Logging ergänzt - RemoteVDDS-Anwendungsfall integriert - Installer/Registrierung des MSTSC-Plugins über Client-Konfigurator - Techniker-, Entwickler- und Projektdokumentation ergänzt - Proprietäre Lizenz für Copyright Patrick Gniza hinzugefügt
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <wtsapi32.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace hook::dvc
|
||||
{
|
||||
// Antworten, die das Client-Plugin ueber IWTSVirtualChannel::Write sendet,
|
||||
// werden auf der Serverseite bei WTSVirtualChannelRead bei DVCs mit einem
|
||||
// CHANNEL_PDU_HEADER (UINT32 length + UINT32 flags) geliefert. Fuer kleine
|
||||
// Nachrichten kommt normalerweise genau ein PDU; die Funktion kann aber auch
|
||||
// mehrere Chunks zusammensetzen.
|
||||
inline bool ReadMessage(
|
||||
HANDLE channel,
|
||||
DWORD timeoutMs,
|
||||
std::vector<char>& payload,
|
||||
DWORD& win32Error)
|
||||
{
|
||||
payload.clear();
|
||||
win32Error = ERROR_SUCCESS;
|
||||
|
||||
constexpr std::size_t kHeaderSize = sizeof(std::uint32_t) * 2;
|
||||
constexpr std::uint32_t kFlagFirst = 0x00000001u;
|
||||
constexpr std::uint32_t kFlagLast = 0x00000002u;
|
||||
constexpr std::size_t kMaxMessageSize = 1024 * 1024;
|
||||
|
||||
bool firstRead = true;
|
||||
std::uint32_t expectedLength = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
char buffer[4096]{};
|
||||
ULONG bytesRead = 0;
|
||||
const BOOL ok = WTSVirtualChannelRead(
|
||||
channel,
|
||||
timeoutMs,
|
||||
buffer,
|
||||
static_cast<ULONG>(sizeof(buffer)),
|
||||
&bytesRead);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
win32Error = GetLastError();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
win32Error = ERROR_NO_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kompatibilitaets-Fallback fuer Implementierungen, die das Payload
|
||||
// ohne CHANNEL_PDU_HEADER zurueckgeben. Unsere Protokollnachrichten
|
||||
// beginnen immer mit "PLANDENT_".
|
||||
if (firstRead &&
|
||||
bytesRead >= 9 &&
|
||||
std::memcmp(buffer, "PLANDENT_", 9) == 0)
|
||||
{
|
||||
payload.assign(buffer, buffer + bytesRead);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bytesRead < kHeaderSize)
|
||||
{
|
||||
win32Error = ERROR_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t length = 0;
|
||||
std::uint32_t flags = 0;
|
||||
std::memcpy(&length, buffer, sizeof(length));
|
||||
std::memcpy(&flags, buffer + sizeof(length), sizeof(flags));
|
||||
|
||||
const std::size_t chunkPayload = bytesRead - kHeaderSize;
|
||||
|
||||
if (length > kMaxMessageSize || chunkPayload > length)
|
||||
{
|
||||
win32Error = ERROR_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (firstRead)
|
||||
{
|
||||
expectedLength = length;
|
||||
if ((flags & kFlagFirst) == 0)
|
||||
{
|
||||
win32Error = ERROR_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
payload.reserve(expectedLength);
|
||||
firstRead = false;
|
||||
}
|
||||
else if (length != expectedLength)
|
||||
{
|
||||
win32Error = ERROR_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.insert(
|
||||
payload.end(),
|
||||
buffer + static_cast<std::ptrdiff_t>(kHeaderSize),
|
||||
buffer + bytesRead);
|
||||
|
||||
if (payload.size() > expectedLength)
|
||||
{
|
||||
win32Error = ERROR_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((flags & kFlagLast) != 0)
|
||||
{
|
||||
if (payload.size() != expectedLength)
|
||||
{
|
||||
win32Error = ERROR_INVALID_DATA;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Nach dem ersten Chunk gilt das Timeout erneut. Unsere Nachrichten
|
||||
// sind sehr klein; dieser Pfad ist nur fuer Vollstaendigkeit vorhanden.
|
||||
timeoutMs = std::max<DWORD>(timeoutMs, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,28 @@
|
||||
namespace hook::protocol
|
||||
{
|
||||
// Der Dynamic-Virtual-Channel-Name muss auf Client und Terminalserver identisch sein.
|
||||
// DVC-Namen dürfen laut WTSVirtualChannelOpenEx bis MAX_PATH lang sein.
|
||||
inline constexpr char kChannelName[] = "plandent::mstsc-script-hook";
|
||||
|
||||
// Absichtlich ein festes Protokollkommando ohne Pfad/Argumente.
|
||||
// Der Terminalserver kann damit nur den lokal vorkonfigurierten Start anfordern.
|
||||
inline constexpr char kStartMessage[] = "PLANDENT_MSTSC_HOOK/1 START";
|
||||
inline constexpr std::size_t kStartMessageLength = sizeof(kStartMessage) - 1;
|
||||
|
||||
// START wird quittiert. Die Server-EXE wartet auf diese Antwort, bevor sie den
|
||||
// DVC schliesst. Dadurch geht die START-Nachricht nicht durch ein zu fruehes
|
||||
// Schliessen des kurzlebigen Kanals verloren.
|
||||
inline constexpr char kStartOkResponse[] = "PLANDENT_MSTSC_HOOK/1 START_OK";
|
||||
inline constexpr std::size_t kStartOkResponseLength = sizeof(kStartOkResponse) - 1;
|
||||
|
||||
inline constexpr char kStartFailedResponse[] = "PLANDENT_MSTSC_HOOK/1 START_FAILED";
|
||||
inline constexpr std::size_t kStartFailedResponseLength = sizeof(kStartFailedResponse) - 1;
|
||||
|
||||
// Abfrage des tatsaechlichen Hostnamens des lokalen RDP-Clients.
|
||||
// Der Name wird direkt auf dem Client vom Plugin ermittelt und nicht aus
|
||||
// CLIENTNAME/COMPUTERNAME der Remotesitzung uebernommen.
|
||||
inline constexpr char kGetClientHostnameMessage[] = "PLANDENT_MSTSC_HOOK/1 GET_CLIENT_HOSTNAME";
|
||||
inline constexpr std::size_t kGetClientHostnameMessageLength = sizeof(kGetClientHostnameMessage) - 1;
|
||||
|
||||
inline constexpr char kClientHostnameResponsePrefix[] = "PLANDENT_MSTSC_HOOK/1 CLIENT_HOSTNAME ";
|
||||
inline constexpr std::size_t kClientHostnameResponsePrefixLength = sizeof(kClientHostnameResponsePrefix) - 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user