#!/usr/bin/env python3 # # A fake RD Web Access server for the tests: the feed behind the NTLM login, # the forms ticket it hands out, .rdp files and icons. It checks the NTLMv2 # answer for real, against one user and password. # # python3 tests/fake_rdweb.py [port] serve until Ctrl+C # # Copyright (C) 2026 Felitendo # SPDX-License-Identifier: GPL-3.0-or-later import base64 import hmac import http.server import os import socketserver import struct import sys import threading import urllib.parse import importlib.machinery # noqa: E402 import importlib.util # noqa: E402 # rdfeed-fetch has no .py ending, so it is loaded by path. _loader = importlib.machinery.SourceFileLoader( "rdfeed_fetch", os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "fetch", "rdfeed-fetch")) _spec = importlib.util.spec_from_loader("rdfeed_fetch", _loader) fetch = importlib.util.module_from_spec(_spec) _loader.exec_module(fetch) DOMAIN = "CONTOSO" USER = "jdoe" PASSWORD = "Correct horse 1" TOKEN = "5EC2E7F00DBA11ADE0F7EED5ACC0FFEE" APPS = [ ("winword-RemoteApps-CmsRdsh", "Word", "RemoteApp"), ("excel-RemoteApps-CmsRdsh", "Excel", "RemoteApp"), ("powerpnt-RemoteApps-CmsRdsh", "PowerPoint", "RemoteApp"), ("desktop-RemoteApps-CmsRdsh", "Contoso Desktop", "Desktop"), ] def extensions(alias): """Excel comes with its file types, as an administrator would enter them.""" if not alias.startswith("excel"): return "" return ('' '') def feed_xml(): resources = "" for alias, title, kind in APPS: resources += f""" {extensions(alias)} """ return f""" {resources} """.encode() def rdp_file(alias, title, kind): lines = [ "full address:s:RDBROKER.contoso.test", "gatewayhostname:s:gateway.contoso.test", "gatewayusagemethod:i:1", "promptcredentialonce:i:0", "prompt for credentials on client:i:1", "drivestoredirect:s:*", "devicestoredirect:s:*", "camerastoredirect:s:*", "loadbalanceinfo:s:tsv://MS Terminal Services Plugin.1.RemoteApps", ] if kind == "RemoteApp": lines += ["remoteapplicationmode:i:1", f"remoteapplicationprogram:s:||{alias.split('-')[0]}", f"remoteapplicationname:s:{title}"] return ("\r\n".join(lines) + "\r\n").encode("utf-16") def ico_32bpp(size=48, rgba=(30, 110, 220, 255)): """An .ico with one 32-bit bitmap frame, the way Windows writes them.""" r, g, b, a = rgba header = struct.pack("not here", {"Content-Type": "text/html"}) class Server(socketserver.ThreadingMixIn, http.server.HTTPServer): daemon_threads = True allow_reuse_address = True def serve(port=0): """Starts the server in a thread. Returns it; its URL base is server.base.""" server = Server(("127.0.0.1", port), Handler) server.base = f"http://127.0.0.1:{server.server_address[1]}" threading.Thread(target=server.serve_forever, daemon=True).start() return server if __name__ == "__main__": srv = serve(int(sys.argv[1]) if len(sys.argv) > 1 else 0) print(srv.base + "/RDWeb/Feed/webfeed.aspx", flush=True) try: threading.Event().wait() except KeyboardInterrupt: pass