Modus proxychains pro Dienst hinzufügen
Neben den Proxy-Variablen (env) kann pro Dienst jetzt der Modus proxychains gewählt werden: proxychains-ng wird per LD_PRELOAD geladen und leitet alle ausgehenden TCP-Verbindungen über den Proxy, auch bei Programmen ohne Proxy-Unterstützung (z.B. Kerio Connect SMTP/POP3). - eigene proxychains-Konfiguration je Dienst unter /etc/svc-proxy/proxychains/ - localhost, private Netze, DNS und IP-Einträge aus No-Proxy gehen direkt - Proxy-Hostname wird zu IPv4 aufgelöst, Kennwort-Datei an User= des Dienstes - Modus-Auswahl im ncurses-Formular, --mode für set, Anzeige in list/show - README und --help ergänzt - __pycache__ aus dem Repo entfernt, .gitignore angelegt Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2d786a0270
commit
3968c1a7b8
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
@@ -6,12 +6,20 @@ aufbaut. Bedienen lässt es sich auf zwei Wegen:
|
|||||||
- **ncurses-Menü**: listet alle Dienste auf. Man wählt einen aus und trägt die Daten bequem ein.
|
- **ncurses-Menü**: listet alle Dienste auf. Man wählt einen aus und trägt die Daten bequem ein.
|
||||||
- **Kommandozeile**: für Skripte, Ansible, Provisionierung usw.
|
- **Kommandozeile**: für Skripte, Ansible, Provisionierung usw.
|
||||||
|
|
||||||
|
Pro Dienst wählt man den **Modus**:
|
||||||
|
|
||||||
|
| Modus | Wirkung | Geeignet für |
|
||||||
|
|---|---|---|
|
||||||
|
| `env` – Proxy-Variablen (Standard) | setzt `http_proxy`, `https_proxy`, … | Programme, die diese Variablen beachten (curl, apt, Docker, Python, Go, …) |
|
||||||
|
| `proxychains` | leitet **alle** ausgehenden TCP-Verbindungen des Dienstes über den Proxy | Programme ohne Proxy-Unterstützung, z.B. **Kerio Connect** (SMTP, POP3, IMAP) |
|
||||||
|
|
||||||
Es gibt einen **globalen Proxy**. Alle Felder, die man bei einem Dienst leer lässt,
|
Es gibt einen **globalen Proxy**. Alle Felder, die man bei einem Dienst leer lässt,
|
||||||
übernehmen den globalen Wert. Explizit gesetzte Werte beim Dienst gelten vorrangig.
|
übernehmen den globalen Wert. Explizit gesetzte Werte beim Dienst gelten vorrangig.
|
||||||
Benutzername und Kennwort sind optional; ohne sie wird der Proxy ohne Anmeldung genutzt.
|
Benutzername und Kennwort sind optional; ohne sie wird der Proxy ohne Anmeldung genutzt.
|
||||||
|
|
||||||
Abhängigkeiten: nur `python3` (≥ 3.6) mit den Standardmodulen. `curses` ist bei
|
Abhängigkeiten: nur `python3` (≥ 3.6) mit den Standardmodulen. `curses` ist bei
|
||||||
Debian/Ubuntu/RHEL im Python-Paket enthalten.
|
Debian/Ubuntu/RHEL im Python-Paket enthalten. Für den Modus `proxychains` zusätzlich
|
||||||
|
`proxychains4` (Debian/Ubuntu) bzw. `proxychains-ng` (RHEL/Fedora).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,6 +27,7 @@ Debian/Ubuntu/RHEL im Python-Paket enthalten.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo install -m 0755 svc-proxy /usr/local/sbin/svc-proxy
|
sudo install -m 0755 svc-proxy /usr/local/sbin/svc-proxy
|
||||||
|
sudo apt install proxychains4 # nur für den Modus proxychains nötig
|
||||||
```
|
```
|
||||||
|
|
||||||
Danach `sudo svc-proxy` (Menü) oder `svc-proxy --help` (Hilfe).
|
Danach `sudo svc-proxy` (Menü) oder `svc-proxy --help` (Hilfe).
|
||||||
@@ -34,10 +43,18 @@ Für jeden konfigurierten Dienst werden zwei Dateien erzeugt:
|
|||||||
| `/etc/systemd/system/<dienst>.d/50-svc-proxy.conf` | Drop-in mit `EnvironmentFile=` | 0644 |
|
| `/etc/systemd/system/<dienst>.d/50-svc-proxy.conf` | Drop-in mit `EnvironmentFile=` | 0644 |
|
||||||
| `/etc/svc-proxy/env/<dienst>.env` | die eigentlichen Proxy-Variablen | 0600 |
|
| `/etc/svc-proxy/env/<dienst>.env` | die eigentlichen Proxy-Variablen | 0600 |
|
||||||
|
|
||||||
|
Im Modus `proxychains` kommt eine dritte Datei dazu:
|
||||||
|
|
||||||
|
| Datei | Inhalt | Rechte |
|
||||||
|
|-------|--------|--------|
|
||||||
|
| `/etc/svc-proxy/proxychains/<dienst>.conf` | proxychains-Konfiguration | 0644, mit Kennwort 0600 |
|
||||||
|
|
||||||
Die Konfiguration selbst (global + alle Dienste) liegt in
|
Die Konfiguration selbst (global + alle Dienste) liegt in
|
||||||
`/etc/svc-proxy/config.json` (0600). Die Drop-ins und Env-Dateien werden daraus
|
`/etc/svc-proxy/config.json` (0600). Die Drop-ins und Env-Dateien werden daraus
|
||||||
generiert und sollten nicht von Hand bearbeitet werden.
|
generiert und sollten nicht von Hand bearbeitet werden.
|
||||||
|
|
||||||
|
### Modus `env` (Proxy-Variablen)
|
||||||
|
|
||||||
Gesetzt werden die Variablen
|
Gesetzt werden die Variablen
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -63,7 +80,66 @@ Programm *kann* den Proxy umgehen.
|
|||||||
|
|
||||||
SOCKS-Proxys (`socks5://`, `socks5h://`) werden ebenfalls in alle Variablen
|
SOCKS-Proxys (`socks5://`, `socks5h://`) werden ebenfalls in alle Variablen
|
||||||
geschrieben. Das funktioniert z.B. mit curl und Go. Manche Programme verstehen in
|
geschrieben. Das funktioniert z.B. mit curl und Go. Manche Programme verstehen in
|
||||||
`http_proxy` aber nur `http://`.
|
`http_proxy` aber nur `http://`. Für solche Fälle gibt es den Modus `proxychains`.
|
||||||
|
|
||||||
|
### Modus `proxychains` (alle TCP-Verbindungen)
|
||||||
|
|
||||||
|
[proxychains-ng](https://github.com/rofl0r/proxychains-ng) wird per `LD_PRELOAD`
|
||||||
|
in den Dienst geladen. Die Bibliothek fängt jeden `connect()`-Aufruf ab und baut
|
||||||
|
die Verbindung stattdessen über den Proxy auf. Das Programm selbst muss nichts von
|
||||||
|
einem Proxy wissen. Das ist dasselbe wie `proxychains4 programm`, nur fest im
|
||||||
|
Dienst eingetragen. Die Env-Datei enthält dann:
|
||||||
|
|
||||||
|
```
|
||||||
|
LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libproxychains.so.4"
|
||||||
|
PROXYCHAINS_CONF_FILE="/etc/svc-proxy/proxychains/<dienst>.conf"
|
||||||
|
PROXYCHAINS_QUIET_MODE="1"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direkt (ohne Proxy) verbunden werden immer:**
|
||||||
|
|
||||||
|
- localhost (`127.0.0.0/8`, `::1`)
|
||||||
|
- private Netze: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `fc00::/7`, `fe80::/10`
|
||||||
|
- DNS (Port 53, jedes Ziel), damit z.B. MX-Abfragen normal funktionieren
|
||||||
|
- alle **IP-Adressen und Netze** aus der No-Proxy-Liste (z.B. `192.0.2.0/24`).
|
||||||
|
Hostnamen/Domains aus No-Proxy kann proxychains nicht berücksichtigen.
|
||||||
|
|
||||||
|
Hinweise:
|
||||||
|
|
||||||
|
- Proxy-Typen: `http` (CONNECT), `socks4`, `socks5`. `socks5h` wird wie `socks5`
|
||||||
|
behandelt. Einen `https`-Proxy kann proxychains nicht.
|
||||||
|
- proxychains braucht für den Proxy eine **IPv4-Adresse**. Ein Hostname wird beim
|
||||||
|
Speichern aufgelöst und die IP fest eingetragen. Ändert sich die IP des Proxys,
|
||||||
|
einmal `svc-proxy apply` ausführen.
|
||||||
|
- Benutzer/Kennwort dürfen hier keine Leerzeichen enthalten.
|
||||||
|
- Läuft der Dienst unter einem eigenen Benutzer (`User=`), bekommt dieser die
|
||||||
|
Datei mit dem Kennwort zugewiesen. Bei `DynamicUser=yes` geht das nicht, dann
|
||||||
|
einen Proxy ohne Anmeldung verwenden.
|
||||||
|
- Nur TCP wird umgeleitet, UDP nicht. Eingehende Verbindungen (z.B. MX-Empfang)
|
||||||
|
sind nicht betroffen.
|
||||||
|
- Statisch gelinkte Programme (z.B. viele Go-Binaries) ignorieren `LD_PRELOAD`.
|
||||||
|
Für die gibt es meist den Modus `env`. Prüfen mit `ldd /pfad/zum/programm`.
|
||||||
|
- Der Modus wird **nicht** vom globalen Proxy geerbt, er gilt immer nur für den
|
||||||
|
jeweiligen Dienst (Standard: `env`).
|
||||||
|
|
||||||
|
### Beispiel: Kerio Connect
|
||||||
|
|
||||||
|
Kerio beachtet keine Proxy-Variablen, und der Proxy in den Kerio-Einstellungen
|
||||||
|
gilt nur für Updates und Antivirus. Mit proxychains laufen auch SMTP-Versand und
|
||||||
|
POP3/IMAP-Abruf über den Proxy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install proxychains4
|
||||||
|
ldd /opt/kerio/mailserver/mailserver | grep libc # muss dynamisch gelinkt sein
|
||||||
|
sudo svc-proxy global --proxy socks5://192.168.1.50:1080
|
||||||
|
sudo svc-proxy set kerio-connect --mode proxychains --restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Beim Mailversand über einen Proxy gilt: Empfänger sehen die IP des Proxys. Diese
|
||||||
|
muss im SPF-Eintrag stehen und einen passenden Reverse-DNS haben, und der Proxy
|
||||||
|
muss ausgehend Port 25 erlauben. Alternativ in Kerio einen SMTP-Relay (Smarthost,
|
||||||
|
Port 587 mit Anmeldung) eintragen. Die Verbindung dorthin läuft dann ebenfalls
|
||||||
|
über den Proxy.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -108,7 +184,7 @@ sudo svc-proxy
|
|||||||
```
|
```
|
||||||
|
|
||||||
Markierungen: **G** = nutzt globalen Proxy, **E** = eigene Werte,
|
Markierungen: **G** = nutzt globalen Proxy, **E** = eigene Werte,
|
||||||
**\*** = in dieser Sitzung geändert (Neustart noch offen).
|
**\*** = in dieser Sitzung geändert (Neustart noch offen), **[proxychains]** = Modus proxychains.
|
||||||
|
|
||||||
### Tasten in der Liste
|
### Tasten in der Liste
|
||||||
|
|
||||||
@@ -130,7 +206,7 @@ Markierungen: **G** = nutzt globalen Proxy, **E** = eigene Werte,
|
|||||||
| Taste | Funktion |
|
| Taste | Funktion |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Tab / ↑ ↓ | Feld wechseln |
|
| Tab / ↑ ↓ | Feld wechseln |
|
||||||
| ← → / Leertaste | Proxy-Typ wählen (beim Dienst gibt es zusätzlich „global“) |
|
| ← → / Leertaste | Modus bzw. Proxy-Typ wählen (beim Typ gibt es zusätzlich „global“) |
|
||||||
| Enter | nächstes Feld bzw. Schaltfläche auslösen |
|
| Enter | nächstes Feld bzw. Schaltfläche auslösen |
|
||||||
| F2 | Speichern |
|
| F2 | Speichern |
|
||||||
| Strg+U | Feld leeren |
|
| Strg+U | Feld leeren |
|
||||||
@@ -174,6 +250,7 @@ svc-proxy [BEFEHL] [OPTIONEN]
|
|||||||
| `--password PW` | Kennwort (in `ps` sichtbar!) |
|
| `--password PW` | Kennwort (in `ps` sichtbar!) |
|
||||||
| `--password-stdin` | Kennwort aus der Standardeingabe lesen (empfohlen) |
|
| `--password-stdin` | Kennwort aus der Standardeingabe lesen (empfohlen) |
|
||||||
| `--no-proxy LISTE` | Ausnahmen, kommagetrennt |
|
| `--no-proxy LISTE` | Ausnahmen, kommagetrennt |
|
||||||
|
| `--mode MODUS` | nur bei `set`: `env` (Standard) oder `proxychains` |
|
||||||
|
|
||||||
- Bei `set` und `global` werden **nur die angegebenen Felder** geändert, der Rest bleibt.
|
- Bei `set` und `global` werden **nur die angegebenen Felder** geändert, der Rest bleibt.
|
||||||
- Leerer Wert leert ein Feld: `--user ""`.
|
- Leerer Wert leert ein Feld: `--user ""`.
|
||||||
@@ -202,6 +279,9 @@ Gestoppte Dienste werden nicht gestartet.
|
|||||||
| `--systemd-dir DIR` | anderes Ziel für Drop-ins (Standard `/etc/systemd/system`) |
|
| `--systemd-dir DIR` | anderes Ziel für Drop-ins (Standard `/etc/systemd/system`) |
|
||||||
| `-V`, `--version` | Version |
|
| `-V`, `--version` | Version |
|
||||||
|
|
||||||
|
Umgebungsvariable `SVC_PROXY_PROXYCHAINS_LIB`: Pfad zur proxychains-Bibliothek,
|
||||||
|
falls sie nicht automatisch gefunden wird.
|
||||||
|
|
||||||
Mit `--config-dir`/`--systemd-dir` läuft das Skript auch ohne root. Das ist
|
Mit `--config-dir`/`--systemd-dir` läuft das Skript auch ohne root. Das ist
|
||||||
praktisch zum Testen, z.B. zusammen mit `--no-reload`.
|
praktisch zum Testen, z.B. zusammen mit `--no-reload`.
|
||||||
|
|
||||||
@@ -234,6 +314,16 @@ sudo svc-proxy set myapp --proxy socks5h://10.1.1.1:1080
|
|||||||
# keine Anmeldung (eigener Host ohne Benutzer)
|
# keine Anmeldung (eigener Host ohne Benutzer)
|
||||||
sudo svc-proxy set backup --host proxy2.firma.local
|
sudo svc-proxy set backup --host proxy2.firma.local
|
||||||
|
|
||||||
|
# Kerio Connect: alle Mail-Verbindungen über den (globalen) SOCKS5-Proxy
|
||||||
|
sudo svc-proxy set kerio-connect --mode proxychains --restart
|
||||||
|
|
||||||
|
# Eigener Proxy mit Anmeldung im proxychains-Modus, Netz 192.0.2.0/24 direkt
|
||||||
|
echo 'geheim' | sudo svc-proxy set kerio-connect --mode proxychains \
|
||||||
|
--proxy socks5://192.0.2.10:1080 --user max --password-stdin --no-proxy 192.0.2.0/24
|
||||||
|
|
||||||
|
# Zurück zu den Proxy-Variablen
|
||||||
|
sudo svc-proxy set kerio-connect --mode env
|
||||||
|
|
||||||
# Nur den Port ändern
|
# Nur den Port ändern
|
||||||
sudo svc-proxy set myapp --port 1081 --no-restart
|
sudo svc-proxy set myapp --port 1081 --no-restart
|
||||||
|
|
||||||
@@ -256,7 +346,8 @@ Beispielausgabe von `show`:
|
|||||||
```
|
```
|
||||||
Dienst: ssh.service
|
Dienst: ssh.service
|
||||||
Status: active
|
Status: active
|
||||||
Modus: eigene Werte (leere Felder = global)
|
Herkunft: eigene Werte (leere Felder = global)
|
||||||
|
Modus: Proxy-Variablen
|
||||||
Proxy-Typ http (global)
|
Proxy-Typ http (global)
|
||||||
Host other.proxy (dienst)
|
Host other.proxy (dienst)
|
||||||
Port 3128 (global)
|
Port 3128 (global)
|
||||||
@@ -285,6 +376,11 @@ fehlgeschlagener Neustart).
|
|||||||
```bash
|
```bash
|
||||||
systemctl cat docker.service # zeigt das Drop-in
|
systemctl cat docker.service # zeigt das Drop-in
|
||||||
sudo cat /proc/$(systemctl show -p MainPID --value docker)/environ | tr '\0' '\n' | grep -i proxy
|
sudo cat /proc/$(systemctl show -p MainPID --value docker)/environ | tr '\0' '\n' | grep -i proxy
|
||||||
|
|
||||||
|
# proxychains-Modus: ist die Bibliothek im Prozess geladen?
|
||||||
|
sudo grep proxychains /proc/$(systemctl show -p MainPID --value kerio-connect)/maps | head -1
|
||||||
|
# laufende Verbindungen des Dienstes: Ziel sollte der Proxy sein
|
||||||
|
sudo ss -tnp | grep mailserver
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deinstallation
|
## Deinstallation
|
||||||
|
|||||||
Binary file not shown.
@@ -9,16 +9,20 @@ Ausführliche Hilfe: svc-proxy --help
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import curses
|
import curses
|
||||||
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
|
import pwd
|
||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from glob import glob as glob_paths
|
||||||
from urllib.parse import quote, unquote, urlsplit
|
from urllib.parse import quote, unquote, urlsplit
|
||||||
|
|
||||||
VERSION = "1.0.0"
|
VERSION = "1.1.0"
|
||||||
PROG = "svc-proxy"
|
PROG = "svc-proxy"
|
||||||
|
|
||||||
DEFAULT_CONFIG_DIR = "/etc/svc-proxy"
|
DEFAULT_CONFIG_DIR = "/etc/svc-proxy"
|
||||||
@@ -38,6 +42,34 @@ FIELD_LABELS = {
|
|||||||
}
|
}
|
||||||
PROXY_ENV_VARS = ["http_proxy", "https_proxy", "ftp_proxy", "all_proxy"]
|
PROXY_ENV_VARS = ["http_proxy", "https_proxy", "ftp_proxy", "all_proxy"]
|
||||||
|
|
||||||
|
# Modus pro Dienst: Proxy-Variablen oder proxychains (LD_PRELOAD, alle TCP-Verbindungen)
|
||||||
|
MODES = ["env", "proxychains"]
|
||||||
|
MODE_LABELS = {
|
||||||
|
"env": "Proxy-Variablen",
|
||||||
|
"proxychains": "proxychains (alle TCP-Verbindungen)",
|
||||||
|
}
|
||||||
|
PROXYCHAINS_TYPES = {"http": "http", "socks4": "socks4", "socks5": "socks5", "socks5h": "socks5"}
|
||||||
|
PROXYCHAINS_LIB_GLOBS = [
|
||||||
|
"/usr/lib/*/libproxychains.so.4",
|
||||||
|
"/usr/lib/libproxychains.so.4",
|
||||||
|
"/usr/lib*/libproxychains4.so",
|
||||||
|
"/usr/lib*/proxychains-ng/libproxychains4.so",
|
||||||
|
"/usr/local/lib/libproxychains4.so",
|
||||||
|
]
|
||||||
|
# Ziele, die im proxychains-Modus immer direkt (ohne Proxy) erreicht werden
|
||||||
|
PROXYCHAINS_LOCALNETS = [
|
||||||
|
"127.0.0.0/255.0.0.0",
|
||||||
|
"::1/128",
|
||||||
|
"10.0.0.0/255.0.0.0",
|
||||||
|
"172.16.0.0/255.240.0.0",
|
||||||
|
"192.168.0.0/255.255.0.0",
|
||||||
|
"169.254.0.0/255.255.0.0",
|
||||||
|
"fc00::/7",
|
||||||
|
"fe80::/10",
|
||||||
|
"0.0.0.0:53/0.0.0.0",
|
||||||
|
"[::]:53/0",
|
||||||
|
]
|
||||||
|
|
||||||
UNIT_RE = re.compile(r"^[A-Za-z0-9:_.@\\-]+$")
|
UNIT_RE = re.compile(r"^[A-Za-z0-9:_.@\\-]+$")
|
||||||
HOST_RE = re.compile(r"^[A-Za-z0-9._:-]+$")
|
HOST_RE = re.compile(r"^[A-Za-z0-9._:-]+$")
|
||||||
NO_PROXY_RE = re.compile(r"^[A-Za-z0-9.,:*/_\[\]-]*$")
|
NO_PROXY_RE = re.compile(r"^[A-Za-z0-9.,:*/_\[\]-]*$")
|
||||||
@@ -45,10 +77,19 @@ NO_PROXY_RE = re.compile(r"^[A-Za-z0-9.,:*/_\[\]-]*$")
|
|||||||
HELP_TEXT = """\
|
HELP_TEXT = """\
|
||||||
{prog} {version} – Proxy für systemd-Dienste setzen
|
{prog} {version} – Proxy für systemd-Dienste setzen
|
||||||
|
|
||||||
Hängt an beliebige systemd-Dienste ein Drop-in an, das die Umgebungsvariablen
|
Hängt an beliebige systemd-Dienste ein Drop-in an, das den Dienst über einen
|
||||||
http_proxy, https_proxy, ftp_proxy, all_proxy und no_proxy (jeweils auch in
|
Proxy laufen lässt. Pro Dienst gibt es zwei Modi:
|
||||||
GROSSBUCHSTABEN) setzt. Alle Programme, die diese Variablen beachten (curl,
|
|
||||||
wget, apt, Python, Go, Node, ...), laufen dann über den angegebenen Proxy.
|
env (Standard) Setzt http_proxy, https_proxy, ftp_proxy, all_proxy und
|
||||||
|
no_proxy (auch in GROSSBUCHSTABEN). Wirkt bei allen Programmen, die
|
||||||
|
diese Variablen beachten (curl, wget, apt, Python, Go, Docker, ...).
|
||||||
|
proxychains Lädt proxychains-ng per LD_PRELOAD in den Dienst. Dadurch laufen ALLE
|
||||||
|
ausgehenden TCP-Verbindungen über den Proxy, auch bei Programmen, die
|
||||||
|
keine Proxy-Variablen kennen (z.B. Kerio Connect: SMTP, POP3, IMAP).
|
||||||
|
Direkt (ohne Proxy) bleiben: localhost, private Netze (10/8, 172.16/12,
|
||||||
|
192.168/16, fc00::/7, Link-Local), DNS (Port 53) und alle IP-/Netz-
|
||||||
|
Einträge aus der No-Proxy-Liste. Benötigt: apt install proxychains4
|
||||||
|
Proxy-Typen: http, socks4, socks5 (socks5h wird wie socks5 behandelt).
|
||||||
|
|
||||||
AUFRUF
|
AUFRUF
|
||||||
{prog} ncurses-Menü starten (wie '{prog} tui')
|
{prog} ncurses-Menü starten (wie '{prog} tui')
|
||||||
@@ -79,6 +120,8 @@ FELDER (für 'global' und 'set')
|
|||||||
--password KENNWORT Kennwort (Achtung: in der Prozessliste sichtbar!)
|
--password KENNWORT Kennwort (Achtung: in der Prozessliste sichtbar!)
|
||||||
--password-stdin Kennwort von der Standardeingabe lesen (empfohlen)
|
--password-stdin Kennwort von der Standardeingabe lesen (empfohlen)
|
||||||
--no-proxy LISTE Kommagetrennte Ausnahmen, z.B. localhost,10.0.0.0/8,.intern
|
--no-proxy LISTE Kommagetrennte Ausnahmen, z.B. localhost,10.0.0.0/8,.intern
|
||||||
|
--mode MODUS (nur 'set') env | proxychains (Standard: env, wird nicht
|
||||||
|
vom globalen Proxy geerbt)
|
||||||
|
|
||||||
Ein Feld mit leerem Wert (z.B. --user "") wird geleert. Bei 'set' werden nur
|
Ein Feld mit leerem Wert (z.B. --user "") wird geleert. Bei 'set' werden nur
|
||||||
die angegebenen Felder geändert, die übrigen bleiben erhalten.
|
die angegebenen Felder geändert, die übrigen bleiben erhalten.
|
||||||
@@ -131,6 +174,17 @@ BEISPIELE
|
|||||||
# Eigener SOCKS-Proxy ohne Anmeldung für einen Dienst
|
# Eigener SOCKS-Proxy ohne Anmeldung für einen Dienst
|
||||||
sudo {prog} set myapp --proxy socks5h://10.1.1.1:1080
|
sudo {prog} set myapp --proxy socks5h://10.1.1.1:1080
|
||||||
|
|
||||||
|
# Kerio Connect: alle Mailverbindungen über SOCKS5, Proxy-Daten vom globalen Proxy
|
||||||
|
sudo apt install proxychains4
|
||||||
|
sudo {prog} set kerio-connect --mode proxychains --restart
|
||||||
|
|
||||||
|
# Kerio mit eigenem SOCKS5-Proxy inkl. Anmeldung, LAN-Netz 192.0.2.0/24 direkt
|
||||||
|
sudo {prog} set kerio-connect --mode proxychains --proxy socks5://192.0.2.10:1080 \\
|
||||||
|
--user max --password-stdin --no-proxy 192.0.2.0/24
|
||||||
|
|
||||||
|
# Zurück zu den Proxy-Variablen
|
||||||
|
sudo {prog} set kerio-connect --mode env
|
||||||
|
|
||||||
# Nur den Port eines Dienstes ändern (Rest bleibt)
|
# Nur den Port eines Dienstes ändern (Rest bleibt)
|
||||||
sudo {prog} set myapp --port 1081 --no-restart
|
sudo {prog} set myapp --port 1081 --no-restart
|
||||||
|
|
||||||
@@ -147,6 +201,7 @@ BEISPIELE
|
|||||||
DATEIEN
|
DATEIEN
|
||||||
{cfg}/config.json Konfiguration (global + Dienste, 0600)
|
{cfg}/config.json Konfiguration (global + Dienste, 0600)
|
||||||
{cfg}/env/DIENST.env erzeugte Umgebungsdatei (0600)
|
{cfg}/env/DIENST.env erzeugte Umgebungsdatei (0600)
|
||||||
|
{cfg}/proxychains/DIENST.conf proxychains-Konfiguration (Modus proxychains)
|
||||||
{sysd}/DIENST.d/{dropin} erzeugtes Drop-in
|
{sysd}/DIENST.d/{dropin} erzeugtes Drop-in
|
||||||
|
|
||||||
Dienstnamen ohne Endung erhalten automatisch '.service'.
|
Dienstnamen ohne Endung erhalten automatisch '.service'.
|
||||||
@@ -163,7 +218,9 @@ class ProxyError(Exception):
|
|||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
def empty_settings():
|
def empty_settings():
|
||||||
return {k: "" for k in FIELDS}
|
s = {k: "" for k in FIELDS}
|
||||||
|
s["mode"] = "" # nur bei Diensten benutzt, wird nicht vererbt
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def normalize_unit(name):
|
def normalize_unit(name):
|
||||||
@@ -245,7 +302,13 @@ def validate_settings(raw, is_global):
|
|||||||
if not NO_PROXY_RE.match(s["no_proxy"]):
|
if not NO_PROXY_RE.match(s["no_proxy"]):
|
||||||
raise ProxyError("Ungültige No-Proxy-Liste: %s" % s["no_proxy"])
|
raise ProxyError("Ungültige No-Proxy-Liste: %s" % s["no_proxy"])
|
||||||
|
|
||||||
|
s["mode"] = (raw.get("mode") or "").strip().lower()
|
||||||
|
if s["mode"] and s["mode"] not in MODES:
|
||||||
|
raise ProxyError("Unbekannter Modus '%s' (erlaubt: %s)" % (s["mode"], ", ".join(MODES)))
|
||||||
|
|
||||||
if is_global:
|
if is_global:
|
||||||
|
if s["mode"]:
|
||||||
|
raise ProxyError("Den Modus gibt es nur pro Dienst, nicht global")
|
||||||
if any(s[k] for k in ("host", "port", "user", "password")):
|
if any(s[k] for k in ("host", "port", "user", "password")):
|
||||||
if not s["host"]:
|
if not s["host"]:
|
||||||
raise ProxyError("Globaler Proxy: Host fehlt")
|
raise ProxyError("Globaler Proxy: Host fehlt")
|
||||||
@@ -283,13 +346,85 @@ def resolve(svc, glob):
|
|||||||
eff["user"], eff["password"] = glob["user"], glob["password"]
|
eff["user"], eff["password"] = glob["user"], glob["password"]
|
||||||
src["user"] = src["password"] = "global"
|
src["user"] = src["password"] = "global"
|
||||||
|
|
||||||
|
eff["mode"] = svc.get("mode") or "env"
|
||||||
|
src["mode"] = "dienst" if svc.get("mode") else "standard"
|
||||||
|
|
||||||
if not eff["host"]:
|
if not eff["host"]:
|
||||||
raise ProxyError("kein Proxy-Host (weder beim Dienst noch global gesetzt)")
|
raise ProxyError("kein Proxy-Host (weder beim Dienst noch global gesetzt)")
|
||||||
if not eff["port"]:
|
if not eff["port"]:
|
||||||
raise ProxyError("kein Proxy-Port (weder beim Dienst noch global gesetzt)")
|
raise ProxyError("kein Proxy-Port (weder beim Dienst noch global gesetzt)")
|
||||||
|
if eff["mode"] == "proxychains":
|
||||||
|
if eff["type"] not in PROXYCHAINS_TYPES:
|
||||||
|
raise ProxyError("proxychains kann keinen '%s'-Proxy (nur http, socks4, socks5)" % eff["type"])
|
||||||
|
if re.search(r"\s", eff["user"] + eff["password"]):
|
||||||
|
raise ProxyError("proxychains: Benutzer/Kennwort dürfen keine Leerzeichen enthalten")
|
||||||
return eff, src
|
return eff, src
|
||||||
|
|
||||||
|
|
||||||
|
def find_proxychains_lib():
|
||||||
|
override = os.environ.get("SVC_PROXY_PROXYCHAINS_LIB")
|
||||||
|
if override:
|
||||||
|
return override
|
||||||
|
for pattern in PROXYCHAINS_LIB_GLOBS:
|
||||||
|
for path in sorted(glob_paths(pattern)):
|
||||||
|
if os.path.isfile(path):
|
||||||
|
return path
|
||||||
|
raise ProxyError("proxychains ist nicht installiert (Debian/Ubuntu: apt install proxychains4, "
|
||||||
|
"RHEL/Fedora: dnf install proxychains-ng)")
|
||||||
|
|
||||||
|
|
||||||
|
def proxy_ipv4(host):
|
||||||
|
"""proxychains akzeptiert in der ProxyList nur IPv4-Adressen."""
|
||||||
|
try:
|
||||||
|
addr = ipaddress.ip_address(host)
|
||||||
|
except ValueError:
|
||||||
|
addr = None
|
||||||
|
if addr is not None:
|
||||||
|
if addr.version != 4:
|
||||||
|
raise ProxyError("proxychains braucht eine IPv4-Adresse für den Proxy, nicht %s" % host)
|
||||||
|
return str(addr)
|
||||||
|
try:
|
||||||
|
infos = socket.getaddrinfo(host, None, socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
except socket.gaierror as e:
|
||||||
|
raise ProxyError("Proxy-Host %s nicht auflösbar (%s) – proxychains braucht eine IPv4-Adresse"
|
||||||
|
% (host, e.strerror))
|
||||||
|
return infos[0][4][0]
|
||||||
|
|
||||||
|
|
||||||
|
def proxychains_localnets(no_proxy):
|
||||||
|
"""Standard-Ausnahmen plus alle IP-/Netz-Einträge aus der No-Proxy-Liste."""
|
||||||
|
nets = list(PROXYCHAINS_LOCALNETS)
|
||||||
|
for entry in no_proxy.split(","):
|
||||||
|
entry = entry.strip().strip("[]")
|
||||||
|
try:
|
||||||
|
net = ipaddress.ip_network(entry, strict=False)
|
||||||
|
except ValueError:
|
||||||
|
continue # Hostnamen/Domains kann proxychains nicht ausnehmen
|
||||||
|
if net.version == 4:
|
||||||
|
item = "%s/%s" % (net.network_address, net.netmask)
|
||||||
|
else:
|
||||||
|
item = "%s/%d" % (net.network_address, net.prefixlen)
|
||||||
|
if item not in nets:
|
||||||
|
nets.append(item)
|
||||||
|
return nets
|
||||||
|
|
||||||
|
|
||||||
|
def check_mode(eff):
|
||||||
|
"""Prüft vor dem Speichern, ob der gewählte Modus nutzbar ist."""
|
||||||
|
if eff["mode"] == "proxychains":
|
||||||
|
find_proxychains_lib()
|
||||||
|
proxy_ipv4(eff["host"])
|
||||||
|
|
||||||
|
|
||||||
|
def service_user(unit):
|
||||||
|
"""User= des Dienstes (leer = root)."""
|
||||||
|
rc, out, _ = run_cmd(["systemctl", "show", "-p", "User", "-p", "DynamicUser", unit])
|
||||||
|
if rc != 0:
|
||||||
|
return "", False
|
||||||
|
vals = dict(line.split("=", 1) for line in out.splitlines() if "=" in line)
|
||||||
|
return vals.get("User", ""), vals.get("DynamicUser", "no") == "yes"
|
||||||
|
|
||||||
|
|
||||||
def proxy_url(eff, mask=False):
|
def proxy_url(eff, mask=False):
|
||||||
auth = ""
|
auth = ""
|
||||||
if eff.get("user"):
|
if eff.get("user"):
|
||||||
@@ -330,6 +465,10 @@ def is_inheriting(svc):
|
|||||||
return not any(svc[k] for k in FIELDS)
|
return not any(svc[k] for k in FIELDS)
|
||||||
|
|
||||||
|
|
||||||
|
def mode_suffix(eff):
|
||||||
|
return " [proxychains]" if eff["mode"] == "proxychains" else ""
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Konfiguration und Erzeugen der Dateien
|
# Konfiguration und Erzeugen der Dateien
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@@ -339,13 +478,21 @@ class Config:
|
|||||||
self.config_dir = config_dir
|
self.config_dir = config_dir
|
||||||
self.config_file = os.path.join(config_dir, "config.json")
|
self.config_file = os.path.join(config_dir, "config.json")
|
||||||
self.env_dir = os.path.join(config_dir, "env")
|
self.env_dir = os.path.join(config_dir, "env")
|
||||||
|
self.pc_dir = os.path.join(config_dir, "proxychains")
|
||||||
self.systemd_dir = systemd_dir
|
self.systemd_dir = systemd_dir
|
||||||
self.global_ = empty_settings()
|
self.global_ = empty_settings()
|
||||||
self.services = {}
|
self.services = {}
|
||||||
|
|
||||||
# Pfade
|
# Pfade
|
||||||
|
@staticmethod
|
||||||
|
def safe_name(unit):
|
||||||
|
return re.sub(r"[^A-Za-z0-9:_.@-]", "_", unit)
|
||||||
|
|
||||||
def env_file(self, unit):
|
def env_file(self, unit):
|
||||||
return os.path.join(self.env_dir, re.sub(r"[^A-Za-z0-9:_.@-]", "_", unit) + ".env")
|
return os.path.join(self.env_dir, self.safe_name(unit) + ".env")
|
||||||
|
|
||||||
|
def pc_file(self, unit):
|
||||||
|
return os.path.join(self.pc_dir, self.safe_name(unit) + ".conf")
|
||||||
|
|
||||||
def dropin_dir(self, unit):
|
def dropin_dir(self, unit):
|
||||||
return os.path.join(self.systemd_dir, unit + ".d")
|
return os.path.join(self.systemd_dir, unit + ".d")
|
||||||
@@ -365,35 +512,46 @@ class Config:
|
|||||||
except (OSError, ValueError) as e:
|
except (OSError, ValueError) as e:
|
||||||
raise ProxyError("Konfiguration %s nicht lesbar: %s" % (self.config_file, e))
|
raise ProxyError("Konfiguration %s nicht lesbar: %s" % (self.config_file, e))
|
||||||
|
|
||||||
def clean(d):
|
def clean(d, keys):
|
||||||
s = empty_settings()
|
s = empty_settings()
|
||||||
for k in FIELDS:
|
for k in keys:
|
||||||
if isinstance(d, dict) and isinstance(d.get(k), str):
|
if isinstance(d, dict) and isinstance(d.get(k), str):
|
||||||
s[k] = d[k]
|
s[k] = d[k]
|
||||||
return s
|
return s
|
||||||
|
|
||||||
self.global_ = clean(data.get("global"))
|
self.global_ = clean(data.get("global"), FIELDS)
|
||||||
self.services = {}
|
self.services = {}
|
||||||
for unit, s in (data.get("services") or {}).items():
|
for unit, s in (data.get("services") or {}).items():
|
||||||
if UNIT_RE.match(unit):
|
if UNIT_RE.match(unit):
|
||||||
self.services[unit] = clean(s)
|
self.services[unit] = clean(s, FIELDS + ["mode"])
|
||||||
|
|
||||||
|
def ensure_config_dir(self):
|
||||||
|
# 0711: Dienste mit eigenem Benutzer müssen ihre proxychains-Datei erreichen,
|
||||||
|
# dürfen das Verzeichnis aber nicht auflisten. config.json und env/ bleiben 0600/0700.
|
||||||
|
os.makedirs(self.config_dir, mode=0o711, exist_ok=True)
|
||||||
|
os.chmod(self.config_dir, 0o711)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
os.makedirs(self.config_dir, mode=0o700, exist_ok=True)
|
self.ensure_config_dir()
|
||||||
data = {
|
data = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"global": self.global_,
|
"global": {k: self.global_[k] for k in FIELDS},
|
||||||
"services": {u: self.services[u] for u in sorted(self.services)},
|
"services": {u: self.services[u] for u in sorted(self.services)},
|
||||||
}
|
}
|
||||||
atomic_write(self.config_file, json.dumps(data, indent=2, ensure_ascii=False) + "\n", 0o600)
|
atomic_write(self.config_file, json.dumps(data, indent=2, ensure_ascii=False) + "\n", 0o600)
|
||||||
|
|
||||||
# Drop-ins
|
# Drop-ins
|
||||||
def render_env(self, unit, eff):
|
def render_env(self, unit, eff):
|
||||||
url = proxy_url(eff)
|
|
||||||
lines = [
|
lines = [
|
||||||
"# Erzeugt von %s fuer %s - Aenderungen werden ueberschrieben." % (PROG, unit),
|
"# Erzeugt von %s fuer %s - Aenderungen werden ueberschrieben." % (PROG, unit),
|
||||||
"# Bearbeiten mit: %s set %s ..." % (PROG, unit),
|
"# Bearbeiten mit: %s set %s ..." % (PROG, unit),
|
||||||
]
|
]
|
||||||
|
if eff["mode"] == "proxychains":
|
||||||
|
lines.append('LD_PRELOAD="%s"' % find_proxychains_lib())
|
||||||
|
lines.append('PROXYCHAINS_CONF_FILE="%s"' % self.pc_file(unit))
|
||||||
|
lines.append('PROXYCHAINS_QUIET_MODE="1"')
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
url = proxy_url(eff)
|
||||||
for var in PROXY_ENV_VARS:
|
for var in PROXY_ENV_VARS:
|
||||||
lines.append('%s="%s"' % (var, url))
|
lines.append('%s="%s"' % (var, url))
|
||||||
lines.append('%s="%s"' % (var.upper(), url))
|
lines.append('%s="%s"' % (var.upper(), url))
|
||||||
@@ -402,6 +560,53 @@ class Config:
|
|||||||
lines.append('NO_PROXY="%s"' % eff["no_proxy"])
|
lines.append('NO_PROXY="%s"' % eff["no_proxy"])
|
||||||
return "\n".join(lines) + "\n"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
def render_proxychains(self, unit, eff):
|
||||||
|
entry = "%s %s %s" % (PROXYCHAINS_TYPES[eff["type"]], proxy_ipv4(eff["host"]), eff["port"])
|
||||||
|
if eff["user"]:
|
||||||
|
entry += " %s %s" % (eff["user"], eff["password"])
|
||||||
|
lines = [
|
||||||
|
"# Erzeugt von %s fuer %s - Aenderungen werden ueberschrieben." % (PROG, unit),
|
||||||
|
"# Proxy: %s (%s)" % (proxy_url(eff, mask=True), eff["host"]),
|
||||||
|
"strict_chain",
|
||||||
|
"quiet_mode",
|
||||||
|
"tcp_read_time_out 15000",
|
||||||
|
"tcp_connect_time_out 8000",
|
||||||
|
"# Kein proxy_dns: DNS wird normal aufgeloest (wichtig z.B. fuer MX-Abfragen).",
|
||||||
|
"",
|
||||||
|
"# Diese Ziele werden direkt (ohne Proxy) verbunden:",
|
||||||
|
]
|
||||||
|
lines += ["localnet " + n for n in proxychains_localnets(eff["no_proxy"])]
|
||||||
|
lines += ["", "[ProxyList]", entry]
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
def write_proxychains(self, unit, eff):
|
||||||
|
"""Schreibt die proxychains-Datei. Liefert (geändert, warnung)."""
|
||||||
|
self.ensure_config_dir()
|
||||||
|
os.makedirs(self.pc_dir, mode=0o755, exist_ok=True)
|
||||||
|
os.chmod(self.pc_dir, 0o755)
|
||||||
|
path = self.pc_file(unit)
|
||||||
|
secret = bool(eff["user"])
|
||||||
|
changed = write_if_different(path, self.render_proxychains(unit, eff), 0o600 if secret else 0o644)
|
||||||
|
os.chmod(path, 0o600 if secret else 0o644)
|
||||||
|
warning = None
|
||||||
|
# Die Bibliothek liest die Datei im Dienst-Prozess, also mit dessen Benutzer.
|
||||||
|
user, dynamic = service_user(unit)
|
||||||
|
if secret and os.geteuid() == 0:
|
||||||
|
uid = gid = 0
|
||||||
|
if dynamic:
|
||||||
|
warning = ("%s: nutzt DynamicUser – die proxychains-Datei mit Kennwort ist für den "
|
||||||
|
"Dienst nicht lesbar. Proxy ohne Anmeldung verwenden." % unit)
|
||||||
|
elif user and user != "root":
|
||||||
|
try:
|
||||||
|
pw = pwd.getpwnam(user)
|
||||||
|
uid, gid = pw.pw_uid, pw.pw_gid
|
||||||
|
except KeyError:
|
||||||
|
warning = "%s: Benutzer '%s' unbekannt – proxychains-Datei evtl. nicht lesbar" % (unit, user)
|
||||||
|
st = os.stat(path)
|
||||||
|
if (st.st_uid, st.st_gid) != (uid, gid):
|
||||||
|
os.chown(path, uid, gid)
|
||||||
|
return changed, warning
|
||||||
|
|
||||||
def render_dropin(self, unit):
|
def render_dropin(self, unit):
|
||||||
return (
|
return (
|
||||||
"# Erzeugt von %s - nicht manuell bearbeiten.\n"
|
"# Erzeugt von %s - nicht manuell bearbeiten.\n"
|
||||||
@@ -413,6 +618,7 @@ class Config:
|
|||||||
def remove_unit_files(self, unit):
|
def remove_unit_files(self, unit):
|
||||||
changed = remove_file(self.dropin_file(unit))
|
changed = remove_file(self.dropin_file(unit))
|
||||||
changed |= remove_file(self.env_file(unit))
|
changed |= remove_file(self.env_file(unit))
|
||||||
|
changed |= remove_file(self.pc_file(unit))
|
||||||
try:
|
try:
|
||||||
os.rmdir(self.dropin_dir(unit))
|
os.rmdir(self.dropin_dir(unit))
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -424,14 +630,22 @@ class Config:
|
|||||||
svc = self.services.get(unit)
|
svc = self.services.get(unit)
|
||||||
if svc is None:
|
if svc is None:
|
||||||
return self.remove_unit_files(unit), None
|
return self.remove_unit_files(unit), None
|
||||||
|
warning = None
|
||||||
try:
|
try:
|
||||||
eff, _ = resolve(svc, self.global_)
|
eff, _ = resolve(svc, self.global_)
|
||||||
|
changed = False
|
||||||
|
if eff["mode"] == "proxychains":
|
||||||
|
changed, warning = self.write_proxychains(unit, eff)
|
||||||
|
else:
|
||||||
|
changed = remove_file(self.pc_file(unit))
|
||||||
|
env = self.render_env(unit, eff)
|
||||||
except ProxyError as e:
|
except ProxyError as e:
|
||||||
return self.remove_unit_files(unit), "%s: %s – Proxy für diesen Dienst deaktiviert" % (unit, e)
|
return self.remove_unit_files(unit), "%s: %s – Proxy für diesen Dienst deaktiviert" % (unit, e)
|
||||||
|
self.ensure_config_dir()
|
||||||
os.makedirs(self.env_dir, mode=0o700, exist_ok=True)
|
os.makedirs(self.env_dir, mode=0o700, exist_ok=True)
|
||||||
changed = write_if_different(self.env_file(unit), self.render_env(unit, eff), 0o600)
|
changed |= write_if_different(self.env_file(unit), env, 0o600)
|
||||||
changed |= write_if_different(self.dropin_file(unit), self.render_dropin(unit), 0o644)
|
changed |= write_if_different(self.dropin_file(unit), self.render_dropin(unit), 0o644)
|
||||||
return changed, None
|
return changed, warning
|
||||||
|
|
||||||
def managed_dropins(self):
|
def managed_dropins(self):
|
||||||
"""Units, für die ein Drop-in von svc-proxy existiert."""
|
"""Units, für die ein Drop-in von svc-proxy existiert."""
|
||||||
@@ -549,12 +763,14 @@ def is_backspace(k):
|
|||||||
|
|
||||||
|
|
||||||
class FormField:
|
class FormField:
|
||||||
def __init__(self, key, label, value, kind="text", choices=None, secret=False, placeholder=""):
|
def __init__(self, key, label, value, kind="text", choices=None, secret=False, placeholder="",
|
||||||
|
display=None):
|
||||||
self.key = key
|
self.key = key
|
||||||
self.label = label
|
self.label = label
|
||||||
self.kind = kind # text | choice | button
|
self.kind = kind # text | choice | button
|
||||||
self.value = value
|
self.value = value
|
||||||
self.choices = choices or []
|
self.choices = choices or []
|
||||||
|
self.display = display or {}
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
self.pos = len(value)
|
self.pos = len(value)
|
||||||
@@ -659,8 +875,8 @@ class TUI:
|
|||||||
except ProxyError as e:
|
except ProxyError as e:
|
||||||
return "FEHLER: " + str(e), self.c_err
|
return "FEHLER: " + str(e), self.c_err
|
||||||
if is_inheriting(svc):
|
if is_inheriting(svc):
|
||||||
return "global → " + proxy_url(eff, mask=True), self.c_global
|
return "global → " + proxy_url(eff, mask=True) + mode_suffix(eff), self.c_global
|
||||||
return "eigen → " + proxy_url(eff, mask=True), self.c_own
|
return "eigen → " + proxy_url(eff, mask=True) + mode_suffix(eff), self.c_own
|
||||||
|
|
||||||
# ---------- Hauptliste ----------
|
# ---------- Hauptliste ----------
|
||||||
def draw_main(self):
|
def draw_main(self):
|
||||||
@@ -959,6 +1175,11 @@ class TUI:
|
|||||||
"Benutzer/Kennwort werden nur geerbt, wenn auch der Host global ist.",
|
"Benutzer/Kennwort werden nur geerbt, wenn auch der Host global ist.",
|
||||||
"Eigener Host ohne Benutzer = Proxy ohne Anmeldung.",
|
"Eigener Host ohne Benutzer = Proxy ohne Anmeldung.",
|
||||||
"",
|
"",
|
||||||
|
"Modus (pro Dienst):",
|
||||||
|
" Proxy-Variablen setzt http_proxy & Co. – nur für Programme, die sie beachten",
|
||||||
|
" proxychains leitet ALLE TCP-Verbindungen des Dienstes über den Proxy",
|
||||||
|
" (z.B. Kerio SMTP/POP3). Lokale/private Netze und DNS gehen direkt.",
|
||||||
|
"",
|
||||||
"Tasten: Enter bearbeiten, g global, Entf/d entfernen, / filtern,",
|
"Tasten: Enter bearbeiten, g global, Entf/d entfernen, / filtern,",
|
||||||
"c nur konfigurierte, r Dienst neu starten, F5 Liste neu laden, q Ende.",
|
"c nur konfigurierte, r Dienst neu starten, F5 Liste neu laden, q Ende.",
|
||||||
"Nach jeder Änderung wird 'systemctl daemon-reload' ausgeführt.",
|
"Nach jeder Änderung wird 'systemctl daemon-reload' ausgeführt.",
|
||||||
@@ -983,7 +1204,11 @@ class TUI:
|
|||||||
else:
|
else:
|
||||||
type_choices = [""] + PROXY_TYPES
|
type_choices = [""] + PROXY_TYPES
|
||||||
tval = settings["type"]
|
tval = settings["type"]
|
||||||
fields = [FormField("type", "Proxy-Typ", tval, kind="choice", choices=type_choices)]
|
fields = []
|
||||||
|
if not is_global:
|
||||||
|
fields.append(FormField("mode", "Modus", settings["mode"] or "env", kind="choice",
|
||||||
|
choices=MODES, display=MODE_LABELS))
|
||||||
|
fields.append(FormField("type", "Proxy-Typ", tval, kind="choice", choices=type_choices))
|
||||||
for key in ("host", "port", "user", "password", "no_proxy"):
|
for key in ("host", "port", "user", "password", "no_proxy"):
|
||||||
fields.append(FormField(key, FIELD_LABELS[key], settings[key],
|
fields.append(FormField(key, FIELD_LABELS[key], settings[key],
|
||||||
secret=(key == "password"), placeholder=ph(key)))
|
secret=(key == "password"), placeholder=ph(key)))
|
||||||
@@ -993,7 +1218,7 @@ class TUI:
|
|||||||
buttons.append(FormField("remove", "Proxy entfernen" if not is_global else "Global löschen",
|
buttons.append(FormField("remove", "Proxy entfernen" if not is_global else "Global löschen",
|
||||||
"", kind="button"))
|
"", kind="button"))
|
||||||
items = fields + buttons
|
items = fields + buttons
|
||||||
cur = 1 # Host
|
cur = [f.key for f in fields].index("host")
|
||||||
error = ""
|
error = ""
|
||||||
|
|
||||||
def collect():
|
def collect():
|
||||||
@@ -1016,13 +1241,17 @@ class TUI:
|
|||||||
input_x = 2 + label_w + 3
|
input_x = 2 + label_w + 3
|
||||||
input_w = max(10, min(w - input_x - 2, 60))
|
input_w = max(10, min(w - input_x - 2, 60))
|
||||||
cursor_pos = None
|
cursor_pos = None
|
||||||
|
step = 2 if h >= 4 + len(fields) * 2 + 9 else 1
|
||||||
for i, f in enumerate(fields):
|
for i, f in enumerate(fields):
|
||||||
y = 4 + i * 2
|
y = 4 + i * step
|
||||||
focused = (i == cur)
|
focused = (i == cur)
|
||||||
put(scr, y, 2, f.label.rjust(label_w), curses.A_BOLD if focused else 0)
|
put(scr, y, 2, f.label.rjust(label_w), curses.A_BOLD if focused else 0)
|
||||||
put(scr, y, 2 + label_w, " : ")
|
put(scr, y, 2 + label_w, " : ")
|
||||||
if f.kind == "choice":
|
if f.kind == "choice":
|
||||||
shown = f.value if f.value else "(global: %s)" % (g["type"] or "http")
|
if f.display:
|
||||||
|
shown = f.display.get(f.value, f.value)
|
||||||
|
else:
|
||||||
|
shown = f.value if f.value else "(global: %s)" % (g["type"] or "http")
|
||||||
text = "◀ %s ▶" % shown
|
text = "◀ %s ▶" % shown
|
||||||
put(scr, y, input_x, text.ljust(input_w), self.c_sel if focused else curses.A_UNDERLINE)
|
put(scr, y, input_x, text.ljust(input_w), self.c_sel if focused else curses.A_UNDERLINE)
|
||||||
else:
|
else:
|
||||||
@@ -1039,7 +1268,7 @@ class TUI:
|
|||||||
if focused:
|
if focused:
|
||||||
cursor_pos = (y, input_x + f.pos - f.off)
|
cursor_pos = (y, input_x + f.pos - f.off)
|
||||||
|
|
||||||
by = 4 + len(fields) * 2
|
by = 4 + len(fields) * step
|
||||||
bx = input_x
|
bx = input_x
|
||||||
for j, b in enumerate(buttons):
|
for j, b in enumerate(buttons):
|
||||||
focused = (len(fields) + j == cur)
|
focused = (len(fields) + j == cur)
|
||||||
@@ -1055,7 +1284,7 @@ class TUI:
|
|||||||
preview = proxy_url(cand, mask=True) if cand["host"] else "(kein globaler Proxy)"
|
preview = proxy_url(cand, mask=True) if cand["host"] else "(kein globaler Proxy)"
|
||||||
else:
|
else:
|
||||||
eff, _ = resolve(cand, g)
|
eff, _ = resolve(cand, g)
|
||||||
preview = proxy_url(eff, mask=True)
|
preview = proxy_url(eff, mask=True) + mode_suffix(eff)
|
||||||
if eff["no_proxy"]:
|
if eff["no_proxy"]:
|
||||||
preview += " No-Proxy: " + eff["no_proxy"]
|
preview += " No-Proxy: " + eff["no_proxy"]
|
||||||
put(scr, py, 2, "Ergebnis: ", curses.A_BOLD)
|
put(scr, py, 2, "Ergebnis: ", curses.A_BOLD)
|
||||||
@@ -1066,12 +1295,15 @@ class TUI:
|
|||||||
if error:
|
if error:
|
||||||
put(scr, py + 1, 2, error, self.c_err)
|
put(scr, py + 1, 2, error, self.c_err)
|
||||||
if not is_global:
|
if not is_global:
|
||||||
put(scr, py + 3, 2, "Leere Felder = globaler Wert. Benutzer/Kennwort werden nur geerbt,", self.c_dim)
|
put(scr, py + 2, 2, "Leere Felder = globaler Wert. Benutzer/Kennwort werden nur geerbt,", self.c_dim)
|
||||||
put(scr, py + 4, 2, "wenn auch der Host leer ist; eigener Host ohne Benutzer = ohne Anmeldung.", self.c_dim)
|
put(scr, py + 3, 2, "wenn auch der Host leer ist; eigener Host ohne Benutzer = ohne Anmeldung.", self.c_dim)
|
||||||
|
if fields[0].value == "proxychains":
|
||||||
|
put(scr, py + 4, 2, "proxychains: alle TCP-Verbindungen über den Proxy; direkt bleiben localhost,", self.c_dim)
|
||||||
|
put(scr, py + 5, 2, "private Netze, DNS (Port 53) und IP-/Netz-Einträge aus No-Proxy.", self.c_dim)
|
||||||
else:
|
else:
|
||||||
put(scr, py + 3, 2, "Benutzer/Kennwort leer = Proxy ohne Anmeldung.", self.c_dim)
|
put(scr, py + 3, 2, "Benutzer/Kennwort leer = Proxy ohne Anmeldung.", self.c_dim)
|
||||||
|
|
||||||
self.key_bar("Tab/↑↓ Feld ←→ Typ Enter weiter F2 Speichern Strg+U Feld leeren Esc Abbrechen")
|
self.key_bar("Tab/↑↓ Feld ←→ Auswahl Enter weiter F2 Speichern Strg+U Feld leeren Esc Abbrechen")
|
||||||
if cursor_pos and items[cur].kind == "text":
|
if cursor_pos and items[cur].kind == "text":
|
||||||
self.cursor(True)
|
self.cursor(True)
|
||||||
try:
|
try:
|
||||||
@@ -1148,7 +1380,8 @@ class TUI:
|
|||||||
try:
|
try:
|
||||||
cand = validate_settings(collect(), is_global)
|
cand = validate_settings(collect(), is_global)
|
||||||
if not is_global:
|
if not is_global:
|
||||||
resolve(cand, g)
|
eff, _ = resolve(cand, g)
|
||||||
|
check_mode(eff)
|
||||||
except ProxyError as e:
|
except ProxyError as e:
|
||||||
error = "Nicht gespeichert: %s" % e
|
error = "Nicht gespeichert: %s" % e
|
||||||
continue
|
continue
|
||||||
@@ -1210,7 +1443,7 @@ def fields_from_args(args):
|
|||||||
vals = {}
|
vals = {}
|
||||||
if args.proxy is not None:
|
if args.proxy is not None:
|
||||||
vals.update(parse_proxy_url(args.proxy))
|
vals.update(parse_proxy_url(args.proxy))
|
||||||
for key in ("type", "host", "port", "user", "password", "no_proxy"):
|
for key in ("type", "host", "port", "user", "password", "no_proxy", "mode"):
|
||||||
v = getattr(args, key)
|
v = getattr(args, key)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
vals[key] = v
|
vals[key] = v
|
||||||
@@ -1248,11 +1481,12 @@ def cmd_set(cfg, args):
|
|||||||
svc = validate_settings(base, is_global=False)
|
svc = validate_settings(base, is_global=False)
|
||||||
try:
|
try:
|
||||||
eff, _ = resolve(svc, cfg.global_)
|
eff, _ = resolve(svc, cfg.global_)
|
||||||
|
check_mode(eff)
|
||||||
except ProxyError as e:
|
except ProxyError as e:
|
||||||
raise ProxyError("%s: %s" % (unit, e))
|
raise ProxyError("%s: %s" % (unit, e))
|
||||||
staged[unit] = svc
|
staged[unit] = svc
|
||||||
print("%s -> %s%s" % (unit, proxy_url(eff, mask=True),
|
print("%s -> %s%s%s" % (unit, proxy_url(eff, mask=True),
|
||||||
" (global)" if is_inheriting(svc) else ""))
|
" (global)" if is_inheriting(svc) else "", mode_suffix(eff)))
|
||||||
cfg.services.update(staged)
|
cfg.services.update(staged)
|
||||||
cfg.save()
|
cfg.save()
|
||||||
changed, warnings = set(), []
|
changed, warnings = set(), []
|
||||||
@@ -1318,7 +1552,7 @@ def cmd_list(cfg, args):
|
|||||||
mark = "G" if is_inheriting(svc) else "E"
|
mark = "G" if is_inheriting(svc) else "E"
|
||||||
try:
|
try:
|
||||||
eff, _ = resolve(svc, cfg.global_)
|
eff, _ = resolve(svc, cfg.global_)
|
||||||
text = proxy_url(eff, mask=True)
|
text = proxy_url(eff, mask=True) + mode_suffix(eff)
|
||||||
except ProxyError as e:
|
except ProxyError as e:
|
||||||
text = "FEHLER: %s" % e
|
text = "FEHLER: %s" % e
|
||||||
print(" %s %-*s %-10s %s" % (mark, width, name, state, text))
|
print(" %s %-*s %-10s %s" % (mark, width, name, state, text))
|
||||||
@@ -1347,12 +1581,13 @@ def cmd_show(cfg, args):
|
|||||||
if svc is None:
|
if svc is None:
|
||||||
print("Proxy: keiner")
|
print("Proxy: keiner")
|
||||||
return 0
|
return 0
|
||||||
print("Modus: %s" % ("nutzt globalen Proxy" if is_inheriting(svc) else "eigene Werte (leere Felder = global)"))
|
print("Herkunft: %s" % ("nutzt globalen Proxy" if is_inheriting(svc) else "eigene Werte (leere Felder = global)"))
|
||||||
try:
|
try:
|
||||||
eff, src = resolve(svc, cfg.global_)
|
eff, src = resolve(svc, cfg.global_)
|
||||||
except ProxyError as e:
|
except ProxyError as e:
|
||||||
print("FEHLER: %s" % e)
|
print("FEHLER: %s" % e)
|
||||||
return 1
|
return 1
|
||||||
|
print("Modus: %s" % MODE_LABELS[eff["mode"]])
|
||||||
for key in FIELDS:
|
for key in FIELDS:
|
||||||
val = eff[key]
|
val = eff[key]
|
||||||
if key == "password" and val:
|
if key == "password" and val:
|
||||||
@@ -1361,6 +1596,8 @@ def cmd_show(cfg, args):
|
|||||||
print("Proxy-URL: %s" % proxy_url(eff, mask=True))
|
print("Proxy-URL: %s" % proxy_url(eff, mask=True))
|
||||||
print("Drop-in: %s" % cfg.dropin_file(unit))
|
print("Drop-in: %s" % cfg.dropin_file(unit))
|
||||||
print("Env-Datei: %s" % cfg.env_file(unit))
|
print("Env-Datei: %s" % cfg.env_file(unit))
|
||||||
|
if eff["mode"] == "proxychains":
|
||||||
|
print("proxychains-Konfiguration: %s" % cfg.pc_file(unit))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -1387,6 +1624,7 @@ def build_parser():
|
|||||||
fields.add_argument("--password")
|
fields.add_argument("--password")
|
||||||
fields.add_argument("--password-stdin", action="store_true")
|
fields.add_argument("--password-stdin", action="store_true")
|
||||||
fields.add_argument("--no-proxy", dest="no_proxy")
|
fields.add_argument("--no-proxy", dest="no_proxy")
|
||||||
|
fields.add_argument("--mode", type=str.lower, choices=MODES)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog=PROG, add_help=False, parents=[common(False)])
|
parser = argparse.ArgumentParser(prog=PROG, add_help=False, parents=[common(False)])
|
||||||
parser.add_argument("-V", "--version", action="version", version="%s %s" % (PROG, VERSION))
|
parser.add_argument("-V", "--version", action="version", version="%s %s" % (PROG, VERSION))
|
||||||
|
|||||||
Reference in New Issue
Block a user