From e284d708559d634da9ebe33c98c3bea12f270fc5 Mon Sep 17 00:00:00 2001 From: Tim Eertmoed Date: Thu, 17 Sep 2026 11:37:17 +0200 Subject: [PATCH] Version 2.8.46: DHCP-Optionen im Klartext auf dem Client --- apps/tesm/pyproject.toml | 2 +- apps/tesm/src/tesm/services/kea.py | 141 +++++++++++++++++--- apps/tesm/src/tesm/templates/tesm/dhcp.html | 14 +- 3 files changed, 133 insertions(+), 24 deletions(-) diff --git a/apps/tesm/pyproject.toml b/apps/tesm/pyproject.toml index e6f8aea..68aec39 100644 --- a/apps/tesm/pyproject.toml +++ b/apps/tesm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm" -version = "2.8.45" +version = "2.8.46" description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen" requires-python = ">=3.11" dependencies = [ diff --git a/apps/tesm/src/tesm/services/kea.py b/apps/tesm/src/tesm/services/kea.py index c30ad0e..e355436 100644 --- a/apps/tesm/src/tesm/services/kea.py +++ b/apps/tesm/src/tesm/services/kea.py @@ -807,9 +807,82 @@ mkdir -p "$(dirname "$ziel")" printf %s '@FRACHT@' | base64 -d > "$ziel" chmod 0644 "$ziel" echo "geschrieben: $ziel" + +# Der Uebersetzer: systemd legt fremde Optionen roh und unter ihrer Nummer ab +# ("OPTION_225=687474..."), weil im DHCP-Paket weder Name noch Typ steht. +# Beides weiss nur dieser Server -- also schreibt er die Zuordnung mit. +mkdir -p /usr/local/lib/tesm +printf %s '@UEBERSETZER@' | base64 -d > /usr/local/lib/tesm/dhcp-options +chmod 0755 /usr/local/lib/tesm/dhcp-options +printf %s '@DIENST@' | base64 -d > /etc/systemd/system/tesm-dhcp-options.service +printf %s '@PFADEINHEIT@' | base64 -d > /etc/systemd/system/tesm-dhcp-options.path +chmod 0644 /etc/systemd/system/tesm-dhcp-options.service /etc/systemd/system/tesm-dhcp-options.path +systemctl daemon-reload +systemctl enable --now tesm-dhcp-options.path >/dev/null 2>&1 || true +systemctl enable tesm-dhcp-options.service >/dev/null 2>&1 || true + networkctl reload networkctl reconfigure "$schnittstelle" +# Der Pfadwaechter loest bei der naechsten Lease aus; einmal von Hand, damit +# die Datei sofort dasteht. +sleep 2 +systemctl start tesm-dhcp-options.service || true echo "Lease: /run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex)" +echo "Klartext: /run/tesm-dhcp-options.env" +cat /run/tesm-dhcp-options.env 2>/dev/null || true +""" + +#: Uebersetzt die rohen Optionen der Lease in "name=wert". +#: +#: Warum es das braucht: in einem DHCP-Paket stehen nur Nummer, Laenge und +#: Bytes. Dass 225 "url" heisst und eine Zeichenkette ist, steht allein im +#: Katalog dieses Servers -- systemd kann es nicht wissen und legt deshalb +#: Hex unter der Nummer ab. Diese Zuordnung reist mit. +UEBERSETZER = """#!/bin/sh +# Von TESM erzeugt. Schreibt die eigenen DHCP-Optionen im Klartext nach +# /run/tesm-dhcp-options.env -- eine Zeile "name=wert" je Option. +set -e +ziel=/run/tesm-dhcp-options.env +schnittstelle=$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}') +[ -n "$schnittstelle" ] || exit 0 +lease=/run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex 2>/dev/null) +[ -f "$lease" ] || exit 0 +zwischen=$(mktemp) +for paar in @PAARE@; do + code=${paar%%:*} + name=${paar#*:} + roh=$(sed -n "s/^OPTION_$code=//p" "$lease" | head -n1) + [ -n "$roh" ] || continue + wert=$(/usr/bin/printf "$(echo "$roh" | sed 's/../@BS@x&/g')") + echo "$name=$wert" >> "$zwischen" +done +mv -f "$zwischen" "$ziel" +chmod 0644 "$ziel" +""" + +#: Einmal beim Start und nach jeder Lease. +DIENST_EINHEIT = """[Unit] +Description=TESM -- DHCP-Optionen im Klartext +After=systemd-networkd.service + +[Service] +Type=oneshot +ExecStart=/usr/local/lib/tesm/dhcp-options +RemainAfterExit=no + +[Install] +WantedBy=multi-user.target +""" + +PFAD_EINHEIT = """[Unit] +Description=TESM -- wacht ueber die DHCP-Lease + +[Path] +PathModified=/run/systemd/netif/leases +Unit=tesm-dhcp-options.service + +[Install] +WantedBy=multi-user.target """ #: Ersetzt den eigenen Block in einer Datei, die dem Client gehoert. @@ -902,35 +975,55 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]: "pfad": pfad, "inhalt": inhalt, "nachher": nachher, - "skript": _uebernahme_skript(stil, inhalt), - "auslesen": _auslesen(stil, codes), + "skript": _uebernahme_skript(stil, inhalt, optionen), + "auslesen": _auslesen(stil, optionen), "optionen": optionen, } -def _auslesen(stil: str, codes: list[int]) -> str: +def _auslesen(stil: str, benannt: list[dict[str, Any]]) -> dict[str, str]: """Wie der Wert auf dem Client wieder herauskommt. Gehoert dazu, weil "angekommen" sonst niemand pruefen kann -- und weil ein - RMM-Agent genau diese Zeile braucht. Bei systemd-networkd steht der Wert - **hexkodiert** in der Lease: systemd kennt fremde Optionen nicht und legt - sie roh ab. Ohne das Umrechnen liest man 687474703a... und haelt es fuer - Unsinn. + RMM-Agent genau diese Zeile braucht. + + Zwei Wege bei systemd-networkd, und das hat einen Grund: in der Lease + steht der Wert **hexkodiert unter seiner Nummer**. Im DHCP-Paket reisen + nur Code, Laenge und Bytes; dass 225 "url" heisst und Text enthaelt, weiss + allein der Katalog dieses Servers. Deshalb legt der SSH-Push einen kleinen + Uebersetzer mit, der nach jeder Lease ``/run/tesm-dhcp-options.env`` + schreibt -- "name=wert", lesbar. ``roh`` bleibt fuer den Fall, dass nur + die Datei von Hand kopiert wurde. """ - if not codes: - return "" - code = codes[0] + if not benannt: + return {} + code = int(benannt[0]["code"]) + name = str(benannt[0]["name"]) if stil == "networkd": - return ( - "lease=/run/systemd/netif/leases/$(cat /sys/class/net/$IF/ifindex); " - f"/usr/bin/printf \"$(sed -n 's/^OPTION_{code}=//p' \"$lease\" | sed 's/../\\\\x&/g')\"" - ) + return { + "einfach": f"grep '^{name}=' /run/tesm-dhcp-options.env", + "roh": ( + "lease=/run/systemd/netif/leases/$(cat /sys/class/net/$IF/ifindex); " + f"/usr/bin/printf \"$(sed -n 's/^OPTION_{code}=//p' \"$lease\" " + "| sed 's/../@BS@x&/g')\"" + ).replace("@BS@", "\\" * 2), + } if stil == "dhcpcd": - return "dhcpcd -U $IF | grep -i '^url='" - return "grep -i 'option url' /var/lib/dhcp/dhclient.leases | tail -n1" + # dhcpcd kennt den Namen, weil 'define' ihn ihm beigebracht hat. + return {"einfach": f"dhcpcd -U $IF | grep -i '^{name}='", "roh": ""} + return { + "einfach": f"grep -i 'option {name}' /var/lib/dhcp/dhclient.leases | tail -n1", + "roh": "", + } -def _uebernahme_skript(stil: str, inhalt: str) -> str: +def _b64(text: str) -> str: + import base64 + + return base64.b64encode(text.encode("utf-8")).decode("ascii") + + +def _uebernahme_skript(stil: str, inhalt: str, benannt: list[dict[str, Any]] | None = None) -> str: """Das Skript, das die Konfiguration auf dem Client eintraegt. Zwei Dinge macht es anders als ein blosses ``tee``, und beide sind mit @@ -948,12 +1041,18 @@ def _uebernahme_skript(stil: str, inhalt: str) -> str: Der Inhalt reist base64-kodiert und als **Argument**, nicht ueber stdin: dort steht bei einem Client mit Passwort das Kennwort fuer ``sudo``. """ - import base64 - - fracht = base64.b64encode(inhalt.encode("utf-8")).decode("ascii") + fracht = _b64(inhalt) + benannt = benannt or [] if stil == "networkd": - return NETWORKD_SKRIPT.replace("@FRACHT@", fracht) + paare = " ".join(f"{eintrag['code']}:{eintrag['name']}" for eintrag in benannt) + uebersetzer = UEBERSETZER.replace("@PAARE@", paare).replace("@BS@", "\\\\") # sed gibt aus zwei Zeichen eines aus + return ( + NETWORKD_SKRIPT.replace("@FRACHT@", fracht) + .replace("@UEBERSETZER@", _b64(uebersetzer)) + .replace("@DIENST@", _b64(DIENST_EINHEIT)) + .replace("@PFADEINHEIT@", _b64(PFAD_EINHEIT)) + ) ziel = "/etc/dhcpcd.conf" if stil == "dhcpcd" else "/etc/dhcp/dhclient.conf" dienst = ( diff --git a/apps/tesm/src/tesm/templates/tesm/dhcp.html b/apps/tesm/src/tesm/templates/tesm/dhcp.html index d5db117..809ddac 100644 --- a/apps/tesm/src/tesm/templates/tesm/dhcp.html +++ b/apps/tesm/src/tesm/templates/tesm/dhcp.html @@ -445,12 +445,22 @@
{{ konfig.inhalt }}
Danach: {{ konfig.nachher }}
- {% if konfig.auslesen %} + {% if konfig.auslesen.einfach %} {# Ohne diese Zeile kann niemand pruefen, ob es angekommen ist -- und ein RMM-Agent braucht genau sie. #}
- Auslesen auf dem Client: {{ konfig.auslesen }} + Auslesen auf dem Client: + {{ konfig.auslesen.einfach }}
+ {% if konfig.auslesen.roh %} +
+ systemd legt fremde Optionen unter ihrer Nummer und hexkodiert ab – den + Klartext schreibt ein kleiner Übersetzer, den der SSH-Push mitinstalliert + (/run/tesm-dhcp-options.env, nach jeder Lease erneuert). + Direkt aus der Lease: + {{ konfig.auslesen.roh }} +
+ {% endif %} {% endif %} {% if current_user.has('servicing_command.execute') %}