commit ccd89085384e4af766f0aa7a99770ff560f59a33 Author: patrick Date: Wed Jul 1 14:59:34 2026 +0200 Dateien nach "/" hochladen diff --git a/patch_jviewer.py b/patch_jviewer.py new file mode 100644 index 0000000..80ec26b --- /dev/null +++ b/patch_jviewer.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +""" +Zwei Funktionen fuer den JViewer-Start mit IcedTea-Web: + +1) JNLP-Strukturpatch (Standardaufruf): + patch_jviewer.py + + - / bleiben UNVERAENDERT: die Jars werden + signiert (s.u.), signierter+vertrauter Code mit dieser Anforderung + bekommt regulaer AllPermission - kein Grund mehr, das zu entfernen. + - wird trotzdem zu umbenannt: das ist laut Fehlermeldung + "nativelib kann nicht angegeben werden sofern vertrauenswuerdige + Umgebung angefordert wird" mit inkompatibel, + unabhaengig davon ob signiert oder nicht. + - bleibt UNVERAENDERT (zeigt weiter auf die echte BMC). Das ist + wichtig: IcedTea-Web erlaubt unsignierten/nicht vertrauten Apps im + Sandbox-Modus nur Netzwerkzugriff zurueck zu Host+Port der Codebase. Der + KVM-Redirection-Socket (Port 7578) geht an dieselbe IP wie die BMC/ + Codebase, aber einen anderen Port - wuerde man die Codebase auf einen + lokalen Proxy umbiegen, wuerde der Zugriff verboten (getestet, "access + denied SocketPermission"). + +2) Manifest-Patch fuer ein bereits von IcedTea-Web heruntergeladenes, + gecachtes Jar (wird NACH einem ersten, absichtlich fehlschlagenden + javaws-Lauf aufgerufen, der nur zum Befuellen des Caches dient): + patch_jviewer.py --strip-manifest [ ...] + + Frueherer Ansatz: entfernte "Permissions: all-permissions" aus dem + Manifest, um IcedTea-Webs hartcodierte Forced-Sandbox fuer UNSIGNIERTEN + Code mit dieser Deklaration zu umgehen. Wird seit Einfuehrung der + Selbstsignierung NICHT MEHR aufgerufen (das Attribut wird fuer signierten, + vertrauten Code wieder gebraucht) - Funktion bleibt nur als Fallback im + Code, falls Signierung aus irgendeinem Grund nicht moeglich ist. +""" +import os +import re +import sys +import zipfile + + +def localname(t): + """Tag-Namen ohne Namespace-Praefix, egal ob die JNLP ein xmlns deklariert + oder nicht (manche AMI-Firmwarestaende tun es, manche nicht).""" + return t.split("}", 1)[-1] if "}" in t else t + + +def is_tag(el, name): + return localname(el.tag) == name + + +def strip_all_permissions_from_manifest(jar_path): + with zipfile.ZipFile(jar_path, "r") as zin: + manifest_name = "META-INF/MANIFEST.MF" + try: + manifest = zin.read(manifest_name).decode("utf-8", "replace") + except KeyError: + print(f"(kein Manifest in {jar_path}, nichts zu tun)") + return + + # Manifest-Continuation-Zeilen (beginnen mit einem Leerzeichen) an die + # vorherige Zeile anhaengen, damit "Permissions: all-permissions" + # sicher als eine logische Zeile erkannt wird. + lines = manifest.splitlines() + logical = [] + for line in lines: + if line.startswith(" ") and logical: + logical[-1] += line[1:] + else: + logical.append(line) + + filtered = [l for l in logical if not re.match(r"(?i)^permissions\s*:", l)] + + if filtered == logical: + print(f"(Permissions-Attribut in {jar_path} nicht vorhanden, nichts zu tun)") + return + + new_manifest = "\n".join(filtered) + "\n" + + entries = {} + for item in zin.infolist(): + entries[item] = zin.read(item.filename) + + tmp_path = jar_path + ".tmp" + with zipfile.ZipFile(tmp_path, "w", zipfile.ZIP_DEFLATED) as zout: + for item, data in entries.items(): + if item.filename == manifest_name: + data = new_manifest.encode("utf-8") + zout.writestr(item, data) + + os.replace(tmp_path, jar_path) + print(f"Manifest gepatcht (Permissions: all-permissions entfernt): {jar_path}") + + +def patch_jnlp(src, dst): + import xml.etree.ElementTree as ET + + tree = ET.parse(src) + root = tree.getroot() + + # Falls die JNLP ein xmlns deklariert (z.B. xmlns="http://www.w3.org/1999/xhtml"), + # dieselbe Namespace-URI beim Schreiben wieder als Default-Namespace nutzen, + # damit ET keine ns0:-Praefixe einfuegt. + root_ns = localname(root.tag) + if root.tag != root_ns: + ns_uri = root.tag[1:root.tag.index("}")] + ET.register_namespace("", ns_uri) + + # / bleiben unangetastet (s. Docstring oben) - + # nur zu umbenennen (Namespace-Praefix des Originaltags beibehalten) + for el in root.iter(): + if is_tag(el, "nativelib"): + prefix = el.tag[: el.tag.index("}") + 1] if "}" in el.tag else "" + el.tag = f"{prefix}jar" + + tree.write(dst, encoding="UTF-8", xml_declaration=True) + print(f"Gepatchte JNLP geschrieben: {dst}") + + +def main(): + args = sys.argv[1:] + + if args and args[0] == "--strip-manifest": + jar_paths = args[1:] + if not jar_paths: + print("Benutzung: patch_jviewer.py --strip-manifest [ ...]") + sys.exit(1) + for jar_path in jar_paths: + strip_all_permissions_from_manifest(jar_path) + return + + if len(args) != 2: + print("Benutzung: patch_jviewer.py ") + print(" oder: patch_jviewer.py --strip-manifest [ ...]") + sys.exit(1) + + patch_jnlp(args[0], args[1]) + + +if __name__ == "__main__": + main() diff --git a/start-jviewer.sh b/start-jviewer.sh new file mode 100644 index 0000000..d5c095d --- /dev/null +++ b/start-jviewer.sh @@ -0,0 +1,312 @@ +#!/bin/bash +set -e + +BASE="$(cd "$(dirname "$0")" && pwd)" + +echo "=== JViewer Auto Starter ===" +echo "Ordner: $BASE" + +# Altes Log löschen, damit nie ein Log aus einem früheren Lauf verwechselt wird +rm -f "$BASE/jviewer-debug.log" + + +if [ "$1" != "-c" ] || [ -z "$2" ]; then + echo "Benutzung:" + echo "$0 -c datei.jnlp" + exit 1 +fi + + +JNLP="$2" +if [ "${JNLP:0:1}" != "/" ]; then + JNLP="$BASE/$JNLP" +fi + + +if [ ! -f "$JNLP" ]; then + echo "JNLP nicht gefunden:" + echo "$JNLP" + exit 1 +fi + + +echo "JNLP:" +echo "$JNLP" + + + +# --------------------------------------------------------------------------- +# Java 8 suchen / herunterladen (Temurin JDK von Adoptium - JDK statt JRE, +# weil wir jarsigner/keytool brauchen um die Jars selbst zu signieren, s.u.) +# --------------------------------------------------------------------------- + +JAVA=$(find "$BASE/java8" -type f -name java 2>/dev/null | head -1) +JARSIGNER=$(find "$BASE/java8" -type f -name jarsigner 2>/dev/null | head -1) + +if [ -z "$JAVA" ] || [ -z "$JARSIGNER" ]; then + + echo "Kein vollständiges Java8-JDK gefunden (java+jarsigner) - lade neu..." + + rm -rf "$BASE/java8" + mkdir -p "$BASE/java8" + + ( + cd "$BASE/java8" + + wget -O java8.tar.gz \ + "https://api.adoptium.net/v3/binary/latest/8/ga/linux/x64/jdk/hotspot/normal/eclipse" + + tar xf java8.tar.gz + rm java8.tar.gz + ) + + JAVA=$(find "$BASE/java8" -type f -name java | head -1) + JARSIGNER=$(find "$BASE/java8" -type f -name jarsigner | head -1) +fi + +if [ -z "$JAVA" ] || [ -z "$JARSIGNER" ]; then + echo "Java8-JDK (java + jarsigner) konnte nicht bereitgestellt werden" + exit 1 +fi + +KEYTOOL="$(dirname "$JARSIGNER")/keytool" + +JAVA_HOME="$(dirname "$(dirname "$JAVA")")" + +echo +echo "Java:" +echo "$JAVA" + +"$JAVA" -version + +echo "jarsigner: $JARSIGNER" +echo "keytool: $KEYTOOL" + + + +# --------------------------------------------------------------------------- +# javaws finden (icedtea-netx). Wird NICHT lokal erwartet, sondern im System +# gesucht (z.B. /usr/bin/javaws aus dem Paket "icedtea-netx"). +# --------------------------------------------------------------------------- + +JAVAWS="" +for CAND in "$BASE/javaws" "$(command -v javaws 2>/dev/null)" /usr/bin/javaws; do + if [ -n "$CAND" ] && [ -x "$CAND" ]; then + JAVAWS="$CAND" + break + fi +done + +if [ -z "$JAVAWS" ]; then + echo "javaws wurde nicht gefunden." + echo "Bitte installieren: sudo apt install icedtea-netx" + exit 1 +fi + +echo +echo "javaws:" +echo "$JAVAWS" + + + +# --------------------------------------------------------------------------- +# Eigene, isolierte Umgebung (HOME) für IcedTea-Web, damit Systemeinstellungen +# nicht stören und nichts außerhalb von $BASE verändert wird. +# --------------------------------------------------------------------------- + +export HOME="$BASE/runtime-home" + +# IcedTea-Web folgt zuerst XDG_CONFIG_HOME/XDG_CACHE_HOME, falls diese im +# System bereits gesetzt sind - dann wird HOME ignoriert und die echte +# System-Config (~/.config/icedtea-web) verwendet statt unserer eigenen. +# Deshalb XDG-Variablen explizit auf unseren eigenen Bereich zwingen. +export XDG_CONFIG_HOME="$HOME/.config" +export XDG_CACHE_HOME="$HOME/.cache" + +# Cache wird bei jedem Lauf frisch geleert (Jars sollen neu von der BMC +# geladen und neu gepatcht/signiert werden). Der Config-Ordner wird NICHT +# mehr komplett gelöscht: falls die automatische Zertifikatsvertrauensstellung +# unten nicht zuverlässig greift und stattdessen ein GUI-Dialog erscheint, in +# dem man einmalig "Diesem Zertifikat immer vertrauen" klickt, soll diese +# Entscheidung über Neustarts hinweg erhalten bleiben statt jedes Mal verworfen +# zu werden. +rm -rf "$XDG_CACHE_HOME/icedtea-web" + +mkdir -p "$XDG_CONFIG_HOME/icedtea-web" +mkdir -p "$XDG_CACHE_HOME/icedtea-web" + +# JAVA_HOME/PATH auf das heruntergeladene Java8 zwingen, damit javaws nicht +# irgendeine System-Java-Version verwendet. +export JAVA_HOME +export PATH="$JAVA_HOME/bin:$PATH" + +# Hinweis: deployment.javaws.jre.0.*, deployment.javaws.cache.* und +# deployment.manifest.attribute.check werden von dieser IcedTea-Web-Version +# nicht erkannt ("Eigenschaft ist unbekannt" im Log) und wurden deshalb +# entfernt. Das heruntergeladene Java8 wird stattdessen ueber JAVA_HOME/PATH +# erzwungen (s.o.) - das greift laut Log zuverlaessig ("your JRE - 1.8.0_492"). +# Die Sandbox-Beschraenkung fuer unsignierten Code wird nicht per Config, +# sondern durch Selbstsignierung der Jars geloest (siehe unten). +cat > "$XDG_CONFIG_HOME/icedtea-web/deployment.properties" </ bleiben unverändert (Jars werden signiert, +# s.u. - signierter+vertrauter Code mit dieser Anforderung bekommt regulär +# AllPermission) +# - zu ändern (inkompatibel mit , +# unabhängig von Signierung) +# - bleibt UNVERÄNDERT (zeigt weiter auf die echte BMC), damit +# IcedTea-Webs Sandbox-Regel "Netzwerkzugriff nur zurück zum Codebase-Host" +# den KVM-Redirection-Socket (Port 7578, selbe IP wie die BMC) erlaubt, +# falls Vertrauen aus irgendeinem Grund doch nicht greift. +# --------------------------------------------------------------------------- + +echo +echo "Patch JNLP" + +FIXED="$BASE/fixed-$(basename "$JNLP")" + +rm -f "$FIXED" + +if [ ! -f "$BASE/patch_jviewer.py" ]; then + echo "FEHLER: $BASE/patch_jviewer.py fehlt." + exit 1 +fi + +python3 "$BASE/patch_jviewer.py" "$JNLP" "$FIXED" +PATCH_RC=$? + +if [ "$PATCH_RC" -ne 0 ]; then + echo "FEHLER: patch_jviewer.py ist mit Exitcode $PATCH_RC abgebrochen." + exit 1 +fi + +if [ ! -f "$FIXED" ]; then + echo "FEHLER: $FIXED wurde nicht erzeugt." + exit 1 +fi + +echo +echo "Kontrolle:" +if grep -q "nativelib" "$FIXED"; then + echo "FEHLER: JNLP wurde NICHT gepatcht (nativelib noch vorhanden)." + exit 1 +fi +echo "(nativelib wurde in jar umbenannt - ok)" + + + +# --------------------------------------------------------------------------- +# 1. Lauf: absichtlich nur zum Befüllen des Caches. javaws lädt die Original- +# jars von der echten BMC in den eigenen Cache und wird danach vermutlich mit +# einem Berechtigungsfehler abbrechen (Manifest deklariert "all-permissions", +# ist aber unsigniert) - das ist an dieser Stelle erwartet und kein Problem. +# --------------------------------------------------------------------------- + +echo +echo "Lauf 1/2: Cache befüllen (Abbruch danach ist normal)" + +cd "$BASE" + +set +e +"$JAVAWS" -verbose "$FIXED" >"$BASE/jviewer-debug-pass1.log" 2>&1 +set -e + +CACHED_JARS=$(find "$XDG_CACHE_HOME/icedtea-web" -type f -name '*.jar' 2>/dev/null) + +if [ -z "$CACHED_JARS" ]; then + echo "FEHLER: Nach Lauf 1 wurden keine Jars im Cache gefunden." + echo "--- jviewer-debug-pass1.log ---" + cat "$BASE/jviewer-debug-pass1.log" + exit 1 +fi + +echo "Im Cache gefundene Jars:" +echo "$CACHED_JARS" + + + +# --------------------------------------------------------------------------- +# Jars selbst signieren (selbstsigniertes Zertifikat). Das Manifest-Attribut +# "Permissions: all-permissions" bleibt dabei UNVERÄNDERT im Jar (frühere +# Script-Version entfernte es, um die Forced-Sandbox für UNSIGNIERTEN Code zu +# umgehen - das Attribut wird jetzt für signierten, vertrauten Code aber +# gerade gebraucht, um volle Rechte statt Sandbox zu bekommen). +# --------------------------------------------------------------------------- +# Unsignierter Code bekommt von IcedTea-Web nur die restriktive Sandbox +# (Socket-Permission ist an Host UND Port der Codebase gebunden - der +# KVM-Redirection-Port 7578 unterscheidet sich vom Codebase-Port 443 und wird +# deshalb verweigert, s. DEVLOG). Signierter Code mit "Permissions: +# all-permissions" bekommt nach einmaligem Vertrauen des Zertifikats volle +# Rechte statt Sandbox - deshalb signieren wir hier selbst und importieren +# das Zertifikat in IcedTea-Webs Trusted-Certs-Store, damit kein manueller +# Klick nötig ist (Best-Effort - Pfad/Format ist nicht offiziell dokumentiert +# und wurde nicht auf dem Zielsystem verifiziert, siehe DEVLOG). +# --------------------------------------------------------------------------- + +echo +echo "Signiere gecachte Jars (selbstsigniertes Zertifikat)" + +SIGNING_DIR="$BASE/signing" +KEYSTORE="$SIGNING_DIR/jviewer.jks" +CERT="$SIGNING_DIR/jviewer.crt" +ALIAS="jviewer" +STOREPASS="jviewer-local" + +mkdir -p "$SIGNING_DIR" + +if [ ! -f "$KEYSTORE" ]; then + echo "Erzeuge neues Signier-Schlüsselpaar (einmalig, wird unter $SIGNING_DIR aufbewahrt)" + "$KEYTOOL" -genkeypair \ + -keystore "$KEYSTORE" -storepass "$STOREPASS" -keypass "$STOREPASS" \ + -alias "$ALIAS" -keyalg RSA -keysize 2048 -validity 3650 \ + -dname "CN=JViewer Local, OU=jviewer, O=local, L=local, S=local, C=DE" +fi + +"$KEYTOOL" -exportcert -rfc \ + -keystore "$KEYSTORE" -storepass "$STOREPASS" -alias "$ALIAS" -file "$CERT" + +# shellcheck disable=SC2086 +for JAR in $CACHED_JARS; do + "$JARSIGNER" -keystore "$KEYSTORE" -storepass "$STOREPASS" "$JAR" "$ALIAS" +done + +# Zertifikat in IcedTea-Webs Trusted-Certs-Store eintragen, damit kein +# Vertrauens-Dialog erscheinen muss. Falls das nicht greift, fragt IcedTea-Web +# beim 2. Lauf per GUI-Dialog nach - dort einmalig "immer vertrauen" wählen. +TRUSTED_CERTS_DIR="$XDG_CONFIG_HOME/icedtea-web/security" +TRUSTED_CERTS_STORE="$TRUSTED_CERTS_DIR/trusted.certs" +TRUSTED_STOREPASS="changeit" + +mkdir -p "$TRUSTED_CERTS_DIR" + +"$KEYTOOL" -importcert -noprompt \ + -keystore "$TRUSTED_CERTS_STORE" -storepass "$TRUSTED_STOREPASS" \ + -alias "$ALIAS" -file "$CERT" || echo "Hinweis: Import in Trusted-Certs-Store fehlgeschlagen - ggf. manuell im GUI-Dialog bestätigen." + + + +# --------------------------------------------------------------------------- +# 2. Lauf: mit -Xoffline, damit die (jetzt gepatchten und signierten) Cache- +# Dateien verwendet werden, ohne dass IcedTea-Web sie erneut von der BMC +# herunterlädt und damit den Patch überschreibt. +# --------------------------------------------------------------------------- + +echo +echo "Lauf 2/2: Starte JViewer (offline, aus gepatchtem Cache):" +echo "$FIXED" + +"$JAVAWS" -Xoffline -verbose "$FIXED" 2>&1 | tee "$BASE/jviewer-debug.log" + +echo +echo "Log:" +echo "$BASE/jviewer-debug.log" + +read -p "ENTER zum Beenden"