|
|
|
@@ -746,7 +746,11 @@ IMMER_ANGEFRAGT = frozenset({1, 3, 6, 12, 15, 28, 42, 51, 54, 58, 59, 119, 121})
|
|
|
|
|
#: * **dhclient** -- Debian 11 bis 13 und Ubuntu 22.04, dort wo isc-dhcp-client
|
|
|
|
|
#: noch installiert ist.
|
|
|
|
|
CLIENT_STILE: tuple[tuple[str, str, str], ...] = (
|
|
|
|
|
("networkd", "systemd-networkd", "/etc/systemd/network/10-tesm.network.d/50-tesm.conf"),
|
|
|
|
|
# Der Zusatz gehoert zu der .network-Datei, die fuer die Schnittstelle
|
|
|
|
|
# wirklich gilt -- bei netplan heisst die 10-netplan-<if>.network und liegt
|
|
|
|
|
# unter /run. Ein Verzeichnis "10-tesm.network.d" liest niemand: es gibt
|
|
|
|
|
# keine 10-tesm.network. Den Namen ermittelt das Skript auf dem Client.
|
|
|
|
|
("networkd", "systemd-networkd", "/etc/systemd/network/<aktive>.network.d/50-tesm.conf"),
|
|
|
|
|
("dhcpcd", "dhcpcd", "/etc/dhcpcd.conf"),
|
|
|
|
|
("dhclient", "dhclient (isc-dhcp-client)", "/etc/dhcp/dhclient.conf"),
|
|
|
|
|
)
|
|
|
|
@@ -789,6 +793,40 @@ def gesendete_optionen(conn: sqlite3.Connection) -> list[dict[str, Any]]:
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#: Traegt den Zusatz dort ein, wo networkd ihn auch liest.
|
|
|
|
|
NETWORKD_SKRIPT = """set -e
|
|
|
|
|
schnittstelle=$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}')
|
|
|
|
|
[ -n "$schnittstelle" ] || { echo "Keine Standardroute -- Schnittstelle unbekannt." >&2; exit 1; }
|
|
|
|
|
datei=$(COLUMNS=400 SYSTEMD_COLORS=0 networkctl status "$schnittstelle" 2>/dev/null | sed -n 's/^ *Network File: *//p' | head -n1)
|
|
|
|
|
case "${datei:-}" in
|
|
|
|
|
""|n/a|*not*) datei=$(grep -ls "Name=$schnittstelle" /run/systemd/network/*.network /etc/systemd/network/*.network 2>/dev/null | head -n1) ;;
|
|
|
|
|
esac
|
|
|
|
|
[ -n "${datei:-}" ] || { echo "Keine .network-Datei fuer $schnittstelle gefunden." >&2; exit 1; }
|
|
|
|
|
ziel="/etc/systemd/network/$(basename "$datei").d/50-tesm.conf"
|
|
|
|
|
mkdir -p "$(dirname "$ziel")"
|
|
|
|
|
printf %s '@FRACHT@' | base64 -d > "$ziel"
|
|
|
|
|
chmod 0644 "$ziel"
|
|
|
|
|
echo "geschrieben: $ziel"
|
|
|
|
|
networkctl reload
|
|
|
|
|
networkctl reconfigure "$schnittstelle"
|
|
|
|
|
echo "Lease: /run/systemd/netif/leases/$(cat /sys/class/net/"$schnittstelle"/ifindex)"
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
#: Ersetzt den eigenen Block in einer Datei, die dem Client gehoert.
|
|
|
|
|
BLOCK_SKRIPT = """set -e
|
|
|
|
|
ziel=@ZIEL@
|
|
|
|
|
mkdir -p "$(dirname "$ziel")"
|
|
|
|
|
[ -f "$ziel" ] || : > "$ziel"
|
|
|
|
|
zwischen=$(mktemp)
|
|
|
|
|
sed '/^# >>> TESM/,/^# <<< TESM/d' "$ziel" > "$zwischen"
|
|
|
|
|
printf %s '@FRACHT@' | base64 -d >> "$zwischen"
|
|
|
|
|
cat "$zwischen" > "$ziel"
|
|
|
|
|
rm -f "$zwischen"
|
|
|
|
|
echo "ergaenzt: $ziel"
|
|
|
|
|
@DIENST@
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
|
|
|
|
"""Was ein Linux-Client braucht, damit die Optionen bei ihm ankommen.
|
|
|
|
|
|
|
|
|
@@ -798,8 +836,10 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
|
|
|
|
ausdruecklich anfordern. Ohne diese Zeilen sendet der Server brav und der
|
|
|
|
|
Client wirft es weg, ohne dass irgendwo etwas danebengeht.
|
|
|
|
|
|
|
|
|
|
Rueckgabe: ``{"pfad", "inhalt", "nachher", "optionen"}`` -- Datei, Inhalt,
|
|
|
|
|
der Befehl zum Uebernehmen und die Optionen, um die es geht.
|
|
|
|
|
Rueckgabe: ``{"pfad", "inhalt", "nachher", "skript", "optionen"}``.
|
|
|
|
|
``skript`` ist die Fassung zum Ausfuehren -- sie sucht sich das Ziel auf
|
|
|
|
|
dem Client selbst und traegt den Inhalt ein, ohne Vorhandenes zu
|
|
|
|
|
verlieren.
|
|
|
|
|
"""
|
|
|
|
|
optionen = [
|
|
|
|
|
eintrag
|
|
|
|
@@ -814,20 +854,22 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
|
|
|
|
"# Von TESM erzeugt. Fordert die Optionen an, die dieser Server sendet --",
|
|
|
|
|
"# ohne diese Zeilen verwirft der Client sie stillschweigend.",
|
|
|
|
|
"#",
|
|
|
|
|
"# Der Dateiname muss zur .network-Datei der Schnittstelle passen:",
|
|
|
|
|
"# netplan legt sie unter /run/systemd/network/10-netplan-<if>.network ab,",
|
|
|
|
|
"# der Zusatz gehoert also nach",
|
|
|
|
|
"# Diese Datei ist ein Zusatz zu der .network-Datei, die fuer die",
|
|
|
|
|
"# Schnittstelle gilt. Wie sie heisst, sagt 'networkctl status <if>';",
|
|
|
|
|
"# bei netplan ist es /run/systemd/network/10-netplan-<if>.network, der",
|
|
|
|
|
"# Zusatz gehoert dann nach",
|
|
|
|
|
"# /etc/systemd/network/10-netplan-<if>.network.d/50-tesm.conf.",
|
|
|
|
|
"#",
|
|
|
|
|
"# Die Werte stehen danach in /run/systemd/netif/leases/<ifindex>",
|
|
|
|
|
"# als OPTION_<code>=...",
|
|
|
|
|
"# als OPTION_<code>= -- hexkodiert, weil systemd fremde Optionen nicht",
|
|
|
|
|
"# deutet.",
|
|
|
|
|
"[DHCPv4]",
|
|
|
|
|
]
|
|
|
|
|
zeilen.append("RequestOptions=" + " ".join(str(code) for code in codes))
|
|
|
|
|
nachher = "networkctl reload && networkctl reconfigure <schnittstelle>"
|
|
|
|
|
nachher = "networkctl reload && networkctl reconfigure <Schnittstelle>"
|
|
|
|
|
elif stil == "dhcpcd":
|
|
|
|
|
zeilen = [
|
|
|
|
|
"# Von TESM erzeugt -- an das Ende von /etc/dhcpcd.conf.",
|
|
|
|
|
"# >>> TESM -- nicht von Hand aendern, wird beim naechsten Mal ersetzt",
|
|
|
|
|
"# 'define' macht die Option bekannt, 'option' fordert sie an.",
|
|
|
|
|
"# Danach steht der Wert in der Lease (/var/lib/dhcpcd/<if>.lease)",
|
|
|
|
|
"# und laesst sich mit 'dhcpcd -U <if>' als Variable auslesen.",
|
|
|
|
@@ -837,10 +879,11 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
|
|
|
|
zeilen.append(f"define {eintrag['code']} {typ} {eintrag['name']}")
|
|
|
|
|
for eintrag in optionen:
|
|
|
|
|
zeilen.append(f"option {eintrag['name']}")
|
|
|
|
|
zeilen.append("# <<< TESM")
|
|
|
|
|
nachher = "systemctl restart dhcpcd"
|
|
|
|
|
else:
|
|
|
|
|
zeilen = [
|
|
|
|
|
"# Von TESM erzeugt -- an das Ende von /etc/dhcp/dhclient.conf.",
|
|
|
|
|
"# >>> TESM -- nicht von Hand aendern, wird beim naechsten Mal ersetzt",
|
|
|
|
|
"# Die Werte stehen danach in /var/lib/dhcp/dhclient.leases,",
|
|
|
|
|
"# und in Hooks unter $new_<name>.",
|
|
|
|
|
]
|
|
|
|
@@ -851,16 +894,80 @@ def client_konfiguration(conn: sqlite3.Connection, stil: str) -> dict[str, Any]:
|
|
|
|
|
zeilen.append(
|
|
|
|
|
"also request " + ", ".join(str(eintrag["name"]) for eintrag in optionen) + ";"
|
|
|
|
|
)
|
|
|
|
|
zeilen.append("# <<< TESM")
|
|
|
|
|
nachher = "systemctl restart networking || dhclient -r && dhclient"
|
|
|
|
|
|
|
|
|
|
inhalt = chr(10).join(zeilen) + chr(10)
|
|
|
|
|
return {
|
|
|
|
|
"pfad": pfad,
|
|
|
|
|
"inhalt": chr(10).join(zeilen) + chr(10),
|
|
|
|
|
"inhalt": inhalt,
|
|
|
|
|
"nachher": nachher,
|
|
|
|
|
"skript": _uebernahme_skript(stil, inhalt),
|
|
|
|
|
"auslesen": _auslesen(stil, codes),
|
|
|
|
|
"optionen": optionen,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _auslesen(stil: str, codes: list[int]) -> 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.
|
|
|
|
|
"""
|
|
|
|
|
if not codes:
|
|
|
|
|
return ""
|
|
|
|
|
code = codes[0]
|
|
|
|
|
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')\""
|
|
|
|
|
)
|
|
|
|
|
if stil == "dhcpcd":
|
|
|
|
|
return "dhcpcd -U $IF | grep -i '^url='"
|
|
|
|
|
return "grep -i 'option url' /var/lib/dhcp/dhclient.leases | tail -n1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _uebernahme_skript(stil: str, inhalt: str) -> str:
|
|
|
|
|
"""Das Skript, das die Konfiguration auf dem Client eintraegt.
|
|
|
|
|
|
|
|
|
|
Zwei Dinge macht es anders als ein blosses ``tee``, und beide sind mit
|
|
|
|
|
Schaden bezahlt worden:
|
|
|
|
|
|
|
|
|
|
* Bei ``networkd`` **sucht es das Ziel auf dem Client**. Der Name des
|
|
|
|
|
Zusatzverzeichnisses muss zur aktiven .network-Datei passen; ein fester
|
|
|
|
|
Pfad legt eine Datei an, die kein Dienst je liest -- sie stand da, und
|
|
|
|
|
die Option fehlte trotzdem in der Lease.
|
|
|
|
|
* Bei ``dhcpcd`` und ``dhclient`` gehoeren die Zeilen in eine Datei, die
|
|
|
|
|
dem Client gehoert. Sie zu ueberschreiben haette seine uebrige
|
|
|
|
|
Konfiguration geloescht. Stattdessen wird nur der eigene Block zwischen
|
|
|
|
|
den Marken ersetzt -- beliebig oft wiederholbar.
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
if stil == "networkd":
|
|
|
|
|
return NETWORKD_SKRIPT.replace("@FRACHT@", fracht)
|
|
|
|
|
|
|
|
|
|
ziel = "/etc/dhcpcd.conf" if stil == "dhcpcd" else "/etc/dhcp/dhclient.conf"
|
|
|
|
|
dienst = (
|
|
|
|
|
"systemctl restart dhcpcd"
|
|
|
|
|
if stil == "dhcpcd"
|
|
|
|
|
else "systemctl restart networking 2>/dev/null || { dhclient -r || true; dhclient; }"
|
|
|
|
|
)
|
|
|
|
|
return (
|
|
|
|
|
BLOCK_SKRIPT.replace("@ZIEL@", ziel)
|
|
|
|
|
.replace("@FRACHT@", fracht)
|
|
|
|
|
.replace("@DIENST@", dienst)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def service_state(sysops: SysOps) -> dict[str, str]:
|
|
|
|
|
state = sysops.service_state(SERVICE_UNIT)
|
|
|
|
|
import shutil
|
|
|
|
|