From 2c2c5c78215bbb5546f6794a307306876bf34736 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 4 Apr 2026 19:39:24 +0200 Subject: [PATCH] Server-Config UI (aktiv/passiv) und Port-Forwarding Tipps in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FTP-Tab: Server Configuration mit Active/Passive Radio-Buttons - Passive Mode: Port-Range konfigurierbar (Default 30000-31000) - Apply-Button startet Pure-FTPd automatisch neu - Helper-Script: serverconfig show/active/passive Kommandos - sudoers: systemctl restart, tee und rm für Pure-FTPd Config - README: Port-Forwarding Anleitung für Windows (netsh) und Linux (socat/iptables) Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 90 +++++++++++++++++++++++++++++++ kf5/src/ftpshareplugin.cpp | 107 +++++++++++++++++++++++++++++++++++++ kf5/src/ftpshareplugin.h | 12 +++++ kf6/src/ftpshareplugin.cpp | 107 +++++++++++++++++++++++++++++++++++++ kf6/src/ftpshareplugin.h | 12 +++++ scripts/dolphin-ftp-share | 62 ++++++++++++++++++--- 6 files changed, 383 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e5aa759..f663611 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,96 @@ kde-dolphin-ftp-sharing-tab/ | `ro` | Read Only — nur Download | | `rw` | Read-Write — Upload und Download | +## Tipps: Port-Forwarding für FTP + +### Szenario + +Ein Gerät (z.B. ein Dokumentenscanner) soll per FTP auf den Server scannen, hat aber **keinen direkten Zugang** zum Server-Netzwerk (z.B. VPN). Ein Windows- oder Linux-Rechner, der in **beiden Netzen** hängt, leitet die FTP-Ports weiter. + +``` +Scanner ──► Windows/Linux (Port-Forwarding) ──► VPN ──► FTP-Server +``` + +### Aktiver Modus (empfohlen für Port-Forwarding) + +Im aktiven Modus müssen nur **2 Ports** weitergeleitet werden: **20** (Daten) und **21** (Steuerung). + +### Passiver Modus + +Im passiven Modus muss zusätzlich die **gesamte Port-Range** weitergeleitet werden (z.B. 30000-31000). Das ist aufwändiger, aber nötig wenn aktiver Modus nicht funktioniert. + +### Windows: Port-Forwarding mit netsh + +Als Administrator in der Eingabeaufforderung (cmd): + +```batch +:: Aktiver Modus — Port 21 (Steuerung) und Port 20 (Daten) weiterleiten +netsh interface portproxy add v4tov4 listenport=21 listenaddress=0.0.0.0 connectport=21 connectaddress= +netsh interface portproxy add v4tov4 listenport=20 listenaddress=0.0.0.0 connectport=20 connectaddress= + +:: Passiver Modus — zusätzlich die Port-Range weiterleiten +:: (PowerShell, da netsh keine Ranges unterstützt) +for ($p=30000; $p -le 31000; $p++) { netsh interface portproxy add v4tov4 listenport=$p listenaddress=0.0.0.0 connectport=$p connectaddress= } +``` + +**Windows-Firewall öffnen:** + +```batch +netsh advfirewall firewall add rule name="FTP Active" dir=in action=allow protocol=TCP localport=20,21 +:: Für passiven Modus zusätzlich: +netsh advfirewall firewall add rule name="FTP Passive" dir=in action=allow protocol=TCP localport=30000-31000 +``` + +**Port-Forwarding anzeigen / entfernen:** + +```batch +:: Alle Weiterleitungen anzeigen +netsh interface portproxy show all + +:: Einzelne Weiterleitung entfernen +netsh interface portproxy delete v4tov4 listenport=21 listenaddress=0.0.0.0 + +:: Alle Weiterleitungen entfernen +netsh interface portproxy reset +``` + +### Linux: Port-Forwarding mit iptables oder socat + +**Variante 1: socat (einfach, gut zum Testen)** + +```bash +# Aktiver Modus +sudo socat TCP-LISTEN:21,fork,reuseaddr TCP::21 & +sudo socat TCP-LISTEN:20,fork,reuseaddr TCP::20 & + +# Passiver Modus — zusätzlich Port-Range +for p in $(seq 30000 31000); do + sudo socat TCP-LISTEN:$p,fork,reuseaddr TCP::$p & +done +``` + +**Variante 2: iptables (performanter, dauerhaft)** + +```bash +# IP-Forwarding aktivieren +sudo sysctl -w net.ipv4.ip_forward=1 + +# Aktiver Modus +sudo iptables -t nat -A PREROUTING -p tcp --dport 21 -j DNAT --to-destination :21 +sudo iptables -t nat -A PREROUTING -p tcp --dport 20 -j DNAT --to-destination :20 +sudo iptables -t nat -A POSTROUTING -j MASQUERADE + +# Passiver Modus — zusätzlich Port-Range +sudo iptables -t nat -A PREROUTING -p tcp --dport 30000:31000 -j DNAT --to-destination +``` + +**Dauerhaft speichern:** + +```bash +sudo apt install iptables-persistent +sudo netfilter-persistent save +``` + ## Lizenz GPLv2+ diff --git a/kf5/src/ftpshareplugin.cpp b/kf5/src/ftpshareplugin.cpp index ab4f094..35ec550 100644 --- a/kf5/src/ftpshareplugin.cpp +++ b/kf5/src/ftpshareplugin.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -152,6 +153,47 @@ FtpSharePlugin::FtpSharePlugin(QObject *parent, const QVariantList &args) connect(pwButton, &QPushButton::clicked, this, &FtpSharePlugin::onChangePassword); + // Server configuration section + auto *serverGroup = new QGroupBox(i18n("Server Configuration")); + auto *serverLayout = new QVBoxLayout(serverGroup); + + // Active mode + m_activeRadio = new QRadioButton(i18n("Active Mode (Port 20)")); + serverLayout->addWidget(m_activeRadio); + + // Passive mode + m_passiveRadio = new QRadioButton(i18n("Passive Mode")); + serverLayout->addWidget(m_passiveRadio); + + // Passive port range + m_passivePortsWidget = new QWidget(); + auto *passivePortsLayout = new QHBoxLayout(m_passivePortsWidget); + passivePortsLayout->setContentsMargins(20, 0, 0, 0); + passivePortsLayout->addWidget(new QLabel(i18n("Port-Range:"))); + m_passiveStartSpin = new QSpinBox(); + m_passiveStartSpin->setRange(1024, 65535); + m_passiveStartSpin->setValue(30000); + passivePortsLayout->addWidget(m_passiveStartSpin); + passivePortsLayout->addWidget(new QLabel(QStringLiteral("-"))); + m_passiveEndSpin = new QSpinBox(); + m_passiveEndSpin->setRange(1024, 65535); + m_passiveEndSpin->setValue(31000); + passivePortsLayout->addWidget(m_passiveEndSpin); + passivePortsLayout->addStretch(); + serverLayout->addWidget(m_passivePortsWidget); + + // Apply button + auto *applyServerBtn = new QPushButton(i18n("Apply Server Config")); + serverLayout->addWidget(applyServerBtn); + + mainLayout->addWidget(serverGroup); + + connect(m_activeRadio, &QRadioButton::toggled, this, &FtpSharePlugin::onModeChanged); + connect(applyServerBtn, &QPushButton::clicked, this, &FtpSharePlugin::onApplyServerConfig); + + // Load current server config + loadServerConfig(); + mainLayout->addStretch(); // Connect signals @@ -335,6 +377,71 @@ void FtpSharePlugin::applyChanges() } } +void FtpSharePlugin::loadServerConfig() +{ + QProcess proc; + proc.start(QStringLiteral("dolphin-ftp-share"), + {QStringLiteral("serverconfig"), QStringLiteral("show")}); + proc.waitForFinished(5000); + + const QString output = QString::fromLocal8Bit(proc.readAllStandardOutput()); + bool isPassive = false; + int startPort = 30000; + int endPort = 31000; + + const QStringList lines = output.split(QLatin1Char('\n')); + for (const QString &line : lines) { + if (line.startsWith(QLatin1String("mode=passive"))) + isPassive = true; + else if (line.startsWith(QLatin1String("passive_start="))) + startPort = line.mid(14).toInt(); + else if (line.startsWith(QLatin1String("passive_end="))) + endPort = line.mid(12).toInt(); + } + + if (isPassive) { + m_passiveRadio->setChecked(true); + m_passiveStartSpin->setValue(startPort); + m_passiveEndSpin->setValue(endPort); + } else { + m_activeRadio->setChecked(true); + } + onModeChanged(); +} + +void FtpSharePlugin::onModeChanged() +{ + m_passivePortsWidget->setEnabled(m_passiveRadio->isChecked()); +} + +void FtpSharePlugin::onApplyServerConfig() +{ + QProcess proc; + if (m_activeRadio->isChecked()) { + proc.start(QStringLiteral("dolphin-ftp-share"), + {QStringLiteral("serverconfig"), QStringLiteral("active")}); + } else { + proc.start(QStringLiteral("dolphin-ftp-share"), + {QStringLiteral("serverconfig"), QStringLiteral("passive"), + QString::number(m_passiveStartSpin->value()), + QString::number(m_passiveEndSpin->value())}); + } + proc.waitForFinished(10000); + + if (proc.exitCode() == 0) { + QMessageBox::information(m_page, + i18n("Server Configuration"), + m_activeRadio->isChecked() + ? i18n("Server set to active mode (Port 20).\nPure-FTPd restarted.") + : i18n("Server set to passive mode (Ports %1-%2).\nPure-FTPd restarted.", + m_passiveStartSpin->value(), m_passiveEndSpin->value())); + } else { + QMessageBox::warning(m_page, + i18n("Server Configuration"), + i18n("Failed to apply server configuration.")); + } +} + void FtpSharePlugin::onChangePassword() { const QString username = m_pwUserCombo->currentText(); diff --git a/kf5/src/ftpshareplugin.h b/kf5/src/ftpshareplugin.h index a0dd349..9730472 100644 --- a/kf5/src/ftpshareplugin.h +++ b/kf5/src/ftpshareplugin.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include struct UserPermission { @@ -22,8 +24,11 @@ public: private: void loadCurrentShare(); + void loadServerConfig(); void onShareToggled(bool checked); void onChangePassword(); + void onApplyServerConfig(); + void onModeChanged(); QString m_path; QString m_defaultShareName; @@ -34,6 +39,13 @@ private: QWidget *m_usersWidget = nullptr; QComboBox *m_pwUserCombo = nullptr; + // Server config + QRadioButton *m_activeRadio = nullptr; + QRadioButton *m_passiveRadio = nullptr; + QSpinBox *m_passiveStartSpin = nullptr; + QSpinBox *m_passiveEndSpin = nullptr; + QWidget *m_passivePortsWidget = nullptr; + QVector m_userPerms; bool m_isShared = false; diff --git a/kf6/src/ftpshareplugin.cpp b/kf6/src/ftpshareplugin.cpp index ab4f094..35ec550 100644 --- a/kf6/src/ftpshareplugin.cpp +++ b/kf6/src/ftpshareplugin.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -152,6 +153,47 @@ FtpSharePlugin::FtpSharePlugin(QObject *parent, const QVariantList &args) connect(pwButton, &QPushButton::clicked, this, &FtpSharePlugin::onChangePassword); + // Server configuration section + auto *serverGroup = new QGroupBox(i18n("Server Configuration")); + auto *serverLayout = new QVBoxLayout(serverGroup); + + // Active mode + m_activeRadio = new QRadioButton(i18n("Active Mode (Port 20)")); + serverLayout->addWidget(m_activeRadio); + + // Passive mode + m_passiveRadio = new QRadioButton(i18n("Passive Mode")); + serverLayout->addWidget(m_passiveRadio); + + // Passive port range + m_passivePortsWidget = new QWidget(); + auto *passivePortsLayout = new QHBoxLayout(m_passivePortsWidget); + passivePortsLayout->setContentsMargins(20, 0, 0, 0); + passivePortsLayout->addWidget(new QLabel(i18n("Port-Range:"))); + m_passiveStartSpin = new QSpinBox(); + m_passiveStartSpin->setRange(1024, 65535); + m_passiveStartSpin->setValue(30000); + passivePortsLayout->addWidget(m_passiveStartSpin); + passivePortsLayout->addWidget(new QLabel(QStringLiteral("-"))); + m_passiveEndSpin = new QSpinBox(); + m_passiveEndSpin->setRange(1024, 65535); + m_passiveEndSpin->setValue(31000); + passivePortsLayout->addWidget(m_passiveEndSpin); + passivePortsLayout->addStretch(); + serverLayout->addWidget(m_passivePortsWidget); + + // Apply button + auto *applyServerBtn = new QPushButton(i18n("Apply Server Config")); + serverLayout->addWidget(applyServerBtn); + + mainLayout->addWidget(serverGroup); + + connect(m_activeRadio, &QRadioButton::toggled, this, &FtpSharePlugin::onModeChanged); + connect(applyServerBtn, &QPushButton::clicked, this, &FtpSharePlugin::onApplyServerConfig); + + // Load current server config + loadServerConfig(); + mainLayout->addStretch(); // Connect signals @@ -335,6 +377,71 @@ void FtpSharePlugin::applyChanges() } } +void FtpSharePlugin::loadServerConfig() +{ + QProcess proc; + proc.start(QStringLiteral("dolphin-ftp-share"), + {QStringLiteral("serverconfig"), QStringLiteral("show")}); + proc.waitForFinished(5000); + + const QString output = QString::fromLocal8Bit(proc.readAllStandardOutput()); + bool isPassive = false; + int startPort = 30000; + int endPort = 31000; + + const QStringList lines = output.split(QLatin1Char('\n')); + for (const QString &line : lines) { + if (line.startsWith(QLatin1String("mode=passive"))) + isPassive = true; + else if (line.startsWith(QLatin1String("passive_start="))) + startPort = line.mid(14).toInt(); + else if (line.startsWith(QLatin1String("passive_end="))) + endPort = line.mid(12).toInt(); + } + + if (isPassive) { + m_passiveRadio->setChecked(true); + m_passiveStartSpin->setValue(startPort); + m_passiveEndSpin->setValue(endPort); + } else { + m_activeRadio->setChecked(true); + } + onModeChanged(); +} + +void FtpSharePlugin::onModeChanged() +{ + m_passivePortsWidget->setEnabled(m_passiveRadio->isChecked()); +} + +void FtpSharePlugin::onApplyServerConfig() +{ + QProcess proc; + if (m_activeRadio->isChecked()) { + proc.start(QStringLiteral("dolphin-ftp-share"), + {QStringLiteral("serverconfig"), QStringLiteral("active")}); + } else { + proc.start(QStringLiteral("dolphin-ftp-share"), + {QStringLiteral("serverconfig"), QStringLiteral("passive"), + QString::number(m_passiveStartSpin->value()), + QString::number(m_passiveEndSpin->value())}); + } + proc.waitForFinished(10000); + + if (proc.exitCode() == 0) { + QMessageBox::information(m_page, + i18n("Server Configuration"), + m_activeRadio->isChecked() + ? i18n("Server set to active mode (Port 20).\nPure-FTPd restarted.") + : i18n("Server set to passive mode (Ports %1-%2).\nPure-FTPd restarted.", + m_passiveStartSpin->value(), m_passiveEndSpin->value())); + } else { + QMessageBox::warning(m_page, + i18n("Server Configuration"), + i18n("Failed to apply server configuration.")); + } +} + void FtpSharePlugin::onChangePassword() { const QString username = m_pwUserCombo->currentText(); diff --git a/kf6/src/ftpshareplugin.h b/kf6/src/ftpshareplugin.h index a0dd349..9730472 100644 --- a/kf6/src/ftpshareplugin.h +++ b/kf6/src/ftpshareplugin.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include struct UserPermission { @@ -22,8 +24,11 @@ public: private: void loadCurrentShare(); + void loadServerConfig(); void onShareToggled(bool checked); void onChangePassword(); + void onApplyServerConfig(); + void onModeChanged(); QString m_path; QString m_defaultShareName; @@ -34,6 +39,13 @@ private: QWidget *m_usersWidget = nullptr; QComboBox *m_pwUserCombo = nullptr; + // Server config + QRadioButton *m_activeRadio = nullptr; + QRadioButton *m_passiveRadio = nullptr; + QSpinBox *m_passiveStartSpin = nullptr; + QSpinBox *m_passiveEndSpin = nullptr; + QWidget *m_passivePortsWidget = nullptr; + QVector m_userPerms; bool m_isShared = false; diff --git a/scripts/dolphin-ftp-share b/scripts/dolphin-ftp-share index 91d84f8..c013d26 100755 --- a/scripts/dolphin-ftp-share +++ b/scripts/dolphin-ftp-share @@ -92,6 +92,10 @@ ${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/mount --bind * ${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/mount -o remount?ro?bind * ${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/umount ${REAL_FTP_ROOT}/* ${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/pure-pw * +${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/tee /etc/pure-ftpd/conf/* +${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/pure-ftpd/conf/PassivePortRange +${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/sed -i *pureftpd.passwd* +${REAL_USER} ALL=(root) NOPASSWD: /usr/bin/systemctl restart pure-ftpd EOF sudo chmod 440 /etc/sudoers.d/dolphin-ftp-share @@ -225,15 +229,56 @@ cmd_passwd() { rebuild_db } +cmd_serverconfig() { + local action="$1" + + case "$action" in + show) + # Output current mode and ports + if [ -f /etc/pure-ftpd/conf/PassivePortRange ]; then + local range + range=$(cat /etc/pure-ftpd/conf/PassivePortRange) + echo "mode=passive" + echo "passive_start=${range%% *}" + echo "passive_end=${range##* }" + else + echo "mode=active" + fi + ;; + active) + # Remove passive port range → only active mode + sudo rm -f /etc/pure-ftpd/conf/PassivePortRange + sudo systemctl restart pure-ftpd + echo "Server set to active mode" + ;; + passive) + local start_port="$2" + local end_port="$3" + if [ -z "$start_port" ] || [ -z "$end_port" ]; then + echo "Error: start and end port required" >&2 + exit 1 + fi + echo "${start_port} ${end_port}" | sudo tee /etc/pure-ftpd/conf/PassivePortRange >/dev/null + sudo systemctl restart pure-ftpd + echo "Server set to passive mode (ports ${start_port}-${end_port})" + ;; + *) + echo "Usage: dolphin-ftp-share serverconfig {show|active|passive }" >&2 + exit 1 + ;; + esac +} + case "${1:-}" in - setup) cmd_setup ;; - info) cmd_info ;; - add) cmd_add "$2" "$3" "${4:-}" ;; - delete) cmd_delete "$2" ;; - userexists) cmd_userexists "$2" ;; - passwd) cmd_passwd "$2" "$3" ;; + setup) cmd_setup ;; + info) cmd_info ;; + add) cmd_add "$2" "$3" "${4:-}" ;; + delete) cmd_delete "$2" ;; + userexists) cmd_userexists "$2" ;; + passwd) cmd_passwd "$2" "$3" ;; + serverconfig) cmd_serverconfig "$2" "$3" "$4" ;; *) - echo "Usage: dolphin-ftp-share {setup|info|add|delete|userexists|passwd}" >&2 + echo "Usage: dolphin-ftp-share {setup|info|add|delete|userexists|passwd|serverconfig}" >&2 echo "" >&2 echo "Commands:" >&2 echo " setup Initial Pure-FTPd setup" >&2 @@ -242,6 +287,9 @@ case "${1:-}" in echo " delete Remove a share" >&2 echo " userexists Check if FTP user exists" >&2 echo " passwd Set/create FTP password" >&2 + echo " serverconfig show Show current server config" >&2 + echo " serverconfig active Set active mode" >&2 + echo " serverconfig passive Set passive mode with port range" >&2 exit 1 ;; esac