Umbau auf Pure-FTPd mit PureDB Virtual Users und Passwort-Dialog
- Komplett auf Pure-FTPd umgestellt (weg von vsftpd) - PureDB virtuelle Benutzer: jeder User sieht nur seine Freigaben - Per-User Bind-Mounts mit Read-Only/Read-Write Durchsetzung - Passwort-Dialog beim Anlegen neuer FTP-Benutzer - Change-Password UI: Dropdown + Button zum Passwort ändern - Setup-Kommando für automatische Pure-FTPd Einrichtung - Anonymous-Checkbox entfernt (nur authentifizierte User) - sudoers-Fix: $SUDO_USER statt $USER bei sudo-Ausführung - openssl passwd statt pure-pw stdin für GUI-Kompatibilität Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+108
-20
@@ -5,7 +5,9 @@
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
@@ -37,6 +39,26 @@ static int indexForAcl(const QString &acl)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if a virtual FTP user exists
|
||||
static bool ftpUserExists(const QString &username)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.start(QStringLiteral("dolphin-ftp-share"),
|
||||
{QStringLiteral("userexists"), username});
|
||||
proc.waitForFinished(5000);
|
||||
return proc.exitCode() == 0;
|
||||
}
|
||||
|
||||
// Create a virtual FTP user with password
|
||||
static bool createFtpUser(const QString &username, const QString &password)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.start(QStringLiteral("dolphin-ftp-share"),
|
||||
{QStringLiteral("passwd"), username, password});
|
||||
proc.waitForFinished(5000);
|
||||
return proc.exitCode() == 0;
|
||||
}
|
||||
|
||||
// Get list of local users (UID >= 1000, excluding nobody)
|
||||
static QStringList getLocalUsers()
|
||||
{
|
||||
@@ -89,13 +111,8 @@ FtpSharePlugin::FtpSharePlugin(QObject *parent, const QVariantList &args)
|
||||
nameLayout->addRow(i18n("Name:"), m_nameEdit);
|
||||
mainLayout->addLayout(nameLayout);
|
||||
|
||||
// Anonymous access
|
||||
m_anonymousCheckBox = new QCheckBox(i18n("Allow Anonymous Access"));
|
||||
mainLayout->addWidget(m_anonymousCheckBox);
|
||||
|
||||
// User permissions section
|
||||
auto *usersGroup = new QGroupBox();
|
||||
usersGroup->setFlat(true);
|
||||
auto *usersGroup = new QGroupBox(i18n("User Permissions"));
|
||||
auto *usersLayout = new QVBoxLayout(usersGroup);
|
||||
|
||||
// Scroll area for user list
|
||||
@@ -123,6 +140,18 @@ FtpSharePlugin::FtpSharePlugin(QObject *parent, const QVariantList &args)
|
||||
usersLayout->addWidget(scrollArea);
|
||||
mainLayout->addWidget(usersGroup);
|
||||
|
||||
// Change password section
|
||||
auto *pwGroup = new QGroupBox(i18n("FTP Password"));
|
||||
auto *pwLayout = new QHBoxLayout(pwGroup);
|
||||
m_pwUserCombo = new QComboBox();
|
||||
m_pwUserCombo->addItems(users);
|
||||
auto *pwButton = new QPushButton(i18n("Change Password..."));
|
||||
pwLayout->addWidget(m_pwUserCombo);
|
||||
pwLayout->addWidget(pwButton);
|
||||
mainLayout->addWidget(pwGroup);
|
||||
|
||||
connect(pwButton, &QPushButton::clicked, this, &FtpSharePlugin::onChangePassword);
|
||||
|
||||
mainLayout->addStretch();
|
||||
|
||||
// Connect signals
|
||||
@@ -130,8 +159,6 @@ FtpSharePlugin::FtpSharePlugin(QObject *parent, const QVariantList &args)
|
||||
this, &FtpSharePlugin::onShareToggled);
|
||||
connect(m_nameEdit, &QLineEdit::textChanged,
|
||||
this, [this]() { setDirty(true); });
|
||||
connect(m_anonymousCheckBox, &QCheckBox::toggled,
|
||||
this, [this]() { setDirty(true); });
|
||||
|
||||
// Load existing share info
|
||||
loadCurrentShare();
|
||||
@@ -146,7 +173,6 @@ FtpSharePlugin::FtpSharePlugin(QObject *parent, const QVariantList &args)
|
||||
void FtpSharePlugin::onShareToggled(bool checked)
|
||||
{
|
||||
m_nameEdit->setEnabled(checked);
|
||||
m_anonymousCheckBox->setEnabled(checked);
|
||||
m_usersWidget->setEnabled(checked);
|
||||
setDirty(true);
|
||||
}
|
||||
@@ -167,12 +193,10 @@ void FtpSharePlugin::loadCurrentShare()
|
||||
// Format:
|
||||
// [sharename]
|
||||
// path=/some/path
|
||||
// anonymous=y
|
||||
// user_acl=user1:rw,user2:ro
|
||||
QString currentSection;
|
||||
QString sharePath;
|
||||
QString shareAcl;
|
||||
QString anonymous;
|
||||
|
||||
const QStringList lines = output.split(QLatin1Char('\n'));
|
||||
for (const QString &line : lines) {
|
||||
@@ -185,13 +209,10 @@ void FtpSharePlugin::loadCurrentShare()
|
||||
currentSection = line.mid(1, line.indexOf(QLatin1Char(']')) - 1);
|
||||
sharePath.clear();
|
||||
shareAcl.clear();
|
||||
anonymous.clear();
|
||||
} else if (line.startsWith(QLatin1String("path="))) {
|
||||
sharePath = line.mid(5);
|
||||
} else if (line.startsWith(QLatin1String("user_acl="))) {
|
||||
shareAcl = line.mid(9);
|
||||
} else if (line.startsWith(QLatin1String("anonymous="))) {
|
||||
anonymous = line.mid(10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +225,6 @@ void FtpSharePlugin::loadCurrentShare()
|
||||
m_originalShareName = currentSection;
|
||||
m_shareCheckBox->setChecked(true);
|
||||
m_nameEdit->setText(currentSection);
|
||||
m_anonymousCheckBox->setChecked(anonymous == QLatin1String("y"));
|
||||
|
||||
// Parse ACL: "user1:rw,user2:ro"
|
||||
if (!shareAcl.isEmpty()) {
|
||||
@@ -257,6 +277,41 @@ void FtpSharePlugin::applyChanges()
|
||||
proc.waitForFinished(5000);
|
||||
}
|
||||
|
||||
// Create FTP users for users that have permissions but no FTP account yet
|
||||
for (const auto &up : m_userPerms) {
|
||||
if (up.combo->currentIndex() == 0)
|
||||
continue; // No permission set, skip
|
||||
|
||||
if (ftpUserExists(up.username))
|
||||
continue; // Already has FTP account
|
||||
|
||||
// Ask for FTP password
|
||||
bool ok = false;
|
||||
const QString password = QInputDialog::getText(
|
||||
m_page,
|
||||
i18n("FTP Password"),
|
||||
i18n("Set FTP password for user '%1':", up.username),
|
||||
QLineEdit::Password,
|
||||
QString(),
|
||||
&ok);
|
||||
|
||||
if (!ok || password.isEmpty()) {
|
||||
QMessageBox::warning(m_page,
|
||||
i18n("FTP Share"),
|
||||
i18n("No password set for '%1'. This user will be skipped.", up.username));
|
||||
up.combo->setCurrentIndex(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!createFtpUser(up.username, password)) {
|
||||
QMessageBox::warning(m_page,
|
||||
i18n("FTP Share"),
|
||||
i18n("Failed to create FTP user '%1'.", up.username));
|
||||
up.combo->setCurrentIndex(0);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Build ACL string
|
||||
QStringList aclParts;
|
||||
for (const auto &up : m_userPerms) {
|
||||
@@ -267,14 +322,11 @@ void FtpSharePlugin::applyChanges()
|
||||
}
|
||||
|
||||
const QString aclString = aclParts.join(QLatin1Char(','));
|
||||
const QString anonymous = m_anonymousCheckBox->isChecked()
|
||||
? QStringLiteral("y")
|
||||
: QStringLiteral("n");
|
||||
|
||||
// dolphin-ftp-share add <name> <path> <anonymous> <acl>
|
||||
// dolphin-ftp-share add <name> <path> <acl>
|
||||
QProcess proc;
|
||||
proc.start(QStringLiteral("dolphin-ftp-share"),
|
||||
{QStringLiteral("add"), shareName, m_path, anonymous, aclString});
|
||||
{QStringLiteral("add"), shareName, m_path, aclString});
|
||||
proc.waitForFinished(5000);
|
||||
|
||||
if (proc.exitCode() == 0) {
|
||||
@@ -283,4 +335,40 @@ void FtpSharePlugin::applyChanges()
|
||||
}
|
||||
}
|
||||
|
||||
void FtpSharePlugin::onChangePassword()
|
||||
{
|
||||
const QString username = m_pwUserCombo->currentText();
|
||||
if (username.isEmpty())
|
||||
return;
|
||||
|
||||
if (!ftpUserExists(username)) {
|
||||
QMessageBox::information(m_page,
|
||||
i18n("FTP Password"),
|
||||
i18n("User '%1' has no FTP account yet.\nSet permissions for this user first.", username));
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const QString password = QInputDialog::getText(
|
||||
m_page,
|
||||
i18n("FTP Password"),
|
||||
i18n("New FTP password for '%1':", username),
|
||||
QLineEdit::Password,
|
||||
QString(),
|
||||
&ok);
|
||||
|
||||
if (!ok || password.isEmpty())
|
||||
return;
|
||||
|
||||
if (createFtpUser(username, password)) {
|
||||
QMessageBox::information(m_page,
|
||||
i18n("FTP Password"),
|
||||
i18n("Password for '%1' updated.", username));
|
||||
} else {
|
||||
QMessageBox::warning(m_page,
|
||||
i18n("FTP Password"),
|
||||
i18n("Failed to update password for '%1'.", username));
|
||||
}
|
||||
}
|
||||
|
||||
#include "ftpshareplugin.moc"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
|
||||
struct UserPermission {
|
||||
@@ -22,6 +23,7 @@ public:
|
||||
private:
|
||||
void loadCurrentShare();
|
||||
void onShareToggled(bool checked);
|
||||
void onChangePassword();
|
||||
|
||||
QString m_path;
|
||||
QString m_defaultShareName;
|
||||
@@ -29,8 +31,8 @@ private:
|
||||
QWidget *m_page = nullptr;
|
||||
QCheckBox *m_shareCheckBox = nullptr;
|
||||
QLineEdit *m_nameEdit = nullptr;
|
||||
QCheckBox *m_anonymousCheckBox = nullptr;
|
||||
QWidget *m_usersWidget = nullptr;
|
||||
QComboBox *m_pwUserCombo = nullptr;
|
||||
|
||||
QVector<UserPermission> m_userPerms;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user