Version in base suite: 4.0.0-1 Base version: smb4k_4.0.0-1 Target version: smb4k_4.0.0-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/s/smb4k/smb4k_4.0.0-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/s/smb4k/smb4k_4.0.0-1+deb13u1.dsc changelog | 14 patches/CVE-2025-66002_CVE-2025-66003-1.patch | 732 ++ patches/CVE-2025-66002_CVE-2025-66003-2.patch | 2816 ++++++++++ patches/CVE-2025-66002_CVE-2025-66003-3.patch | 1417 +++++ patches/Merge-Smb4KHardwareInterface-class-from-master-so-th.patch | 260 patches/series | 4 6 files changed, 5243 insertions(+) Unrecognised file line in .dsc: -----BEGIN PGP SIGNATURE----- dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpgi1mripj/smb4k_4.0.0-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpgi1mripj/smb4k_4.0.0-1+deb13u1.dsc: no acceptable signature found diff -Nru smb4k-4.0.0/debian/changelog smb4k-4.0.0/debian/changelog --- smb4k-4.0.0/debian/changelog 2025-02-08 08:39:18.000000000 +0000 +++ smb4k-4.0.0/debian/changelog 2025-12-27 09:40:36.000000000 +0000 @@ -1,3 +1,17 @@ +smb4k (4.0.0-1+deb13u1) trixie-security; urgency=high + + * Non-maintainer upload by the Security Team. + * Fix two security issues in the KAuth mounthelper: + - CVE-2025-66002: local users can perform arbitrary unmounts via + smb4kmounthelper due to lack of input validation + - CVE-2025-66003: local users can perform a local root exploit via smb4k + mounthelper if they can access and control the contents of a Samba share + (Closes: #1122381) + * Merge Smb4KHardwareInterface class from master so that the merged security + fixes can be compiled + + -- Salvatore Bonaccorso Sat, 27 Dec 2025 10:40:36 +0100 + smb4k (4.0.0-1) unstable; urgency=medium * Upload to unstable. diff -Nru smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-1.patch smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-1.patch --- smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-1.patch 2025-12-26 20:14:45.000000000 +0000 @@ -0,0 +1,732 @@ +From: Alexander Reinholdt +Date: Wed, 10 Dec 2025 10:45:50 +0100 +Subject: Merge security bug fix from master. +Origin: https://git.kernel.org/linus/0aeabfa9d0f041479589dd855cbfe7cdebedfdb6 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-66003 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-66002 +Bug-Debian: https://bugs.debian.org/1122381 + +This patch fixes two security issues found by the SUSE security team in +the KAuth mounthelper: + +CVE-2025-66002: local users can perform arbitrary unmounts via +smb4kmounthelper due to lack of input validation +CVE-2025-66003: local users can perform a local root exploit via smb4k +mounthelper if they can access and control the contents of a Samba share + +In summary, the fix comprises of the following: +- Only allow mounting and unmounting below the mount prefix + /var/run/smb4k//. +- Only accept sane URLs and especially deny specially crafted ones. +- Let the mounthelper create the mount points by itself depending on the + passed URL. +- Use the mount and umount binaries returned by the respective functions + from Smb4KGlobal. +- Only allow whitelisted mount and unmount options. +- Make sure that no comma separated values can be passed with the mount + options. +- Only allow setting the UID and GID to the ones of the calling user. +- Only accept four-digit octal numbers as file and directory masks that + do not carry any special permissions (e.g. a setuid root bit). +- Only accept the location of the Kerberos ticket being passed as a file + descriptor. +--- + helpers/smb4kmounthelper.cpp | 523 ++++++++++++++++++++++++++++------- + helpers/smb4kmounthelper.h | 14 +- + 2 files changed, 435 insertions(+), 102 deletions(-) + +diff --git a/helpers/smb4kmounthelper.cpp b/helpers/smb4kmounthelper.cpp +index b6d2b993e5df..a4a58ea575bb 100644 +--- a/helpers/smb4kmounthelper.cpp ++++ b/helpers/smb4kmounthelper.cpp +@@ -1,7 +1,7 @@ + /* + The helper that mounts and unmounts shares. + +- SPDX-FileCopyrightText: 2010-2022 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2010-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -10,91 +10,166 @@ + #include "../core/smb4kglobal.h" + + // Qt includes ++#include + #include ++#include + #include + #include ++#include + #include + + // KDE includes + #include + #include +-#include + #include ++#include ++ ++// system includes ++#include ++#include ++#if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++#include ++#include ++#include ++#endif + + using namespace Smb4KGlobal; + + KAUTH_HELPER_MAIN("org.kde.smb4k.mounthelper", Smb4KMountHelper); + ++static const QStringList MOUNT_ARG_WHITELIST { ++ QStringList { ++#if defined(Q_OS_LINUX) ++ QStringLiteral("domain"), ++ QStringLiteral("ip"), ++ QStringLiteral("username"), ++ QStringLiteral("guest"), ++ QStringLiteral("netbiosname"), ++ QStringLiteral("servern"), ++ QStringLiteral("file_mode"), ++ QStringLiteral("dir_mode"), ++ QStringLiteral("forceuid"), ++ QStringLiteral("forcegid"), ++ QStringLiteral("iocharset"), ++ QStringLiteral("rw"), ++ QStringLiteral("ro"), ++ QStringLiteral("perm"), ++ QStringLiteral("noperm"), ++ QStringLiteral("setuids"), ++ QStringLiteral("nosetuids"), ++ QStringLiteral("serverino"), ++ QStringLiteral("noserverino"), ++ QStringLiteral("cache"), ++ QStringLiteral("mapchars"), ++ QStringLiteral("nomapchars"), ++ QStringLiteral("nobrl"), ++ QStringLiteral("sec"), ++ QStringLiteral("vers") ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ QStringLiteral("-E"), ++ QStringLiteral("-I"), ++ QStringLiteral("-L"), ++ QStringLiteral("-N"), ++ QStringLiteral("-U"), ++ QStringLiteral("-W"), ++ QStringLiteral("-f"), ++ QStringLiteral("-d") ++#endif ++ } ++}; ++ ++static const QStringList UNMOUNT_ARG_WHITELIST { ++ QStringList { ++#if defined(Q_OS_LINUX) ++ QStringLiteral("-l"), ++ QStringLiteral("--lazy") ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ QStringLiteral("-f") ++#endif ++ } ++}; ++ + KAuth::ActionReply Smb4KMountHelper::mount(const QVariantMap &args) + { +- // +- // The action reply +- // + ActionReply reply; + +- // +- // Check if the system is online and return an error if it is not +- // +- bool online = false; +- QList interfaces = QNetworkInterface::allInterfaces(); +- +- for (const QNetworkInterface &interface : std::as_const(interfaces)) { +- if (interface.isValid() && interface.type() != QNetworkInterface::Loopback && interface.flags() & QNetworkInterface::IsRunning && !online) { +- online = true; +- break; +- } ++ if (!isOnline()) { ++ return errorReply(i18n("The computer is not online.")); + } + +- if (!online) { +- reply.setType(ActionReply::HelperErrorType); +- return reply; ++ QString mountPoint; ++ QUrl shareUrl = args[QStringLiteral("mh_url")].toUrl(); ++ ++ if (auto mp = createMountPoint(shareUrl)) { ++ mountPoint = *mp; ++ } else { ++ return errorReply(i18n("Could not create mount point for share %1.", shareUrl.toDisplayString())); + } + +- // +- // Get the mount executable +- // + const QString mount = findMountExecutable(); + +- // +- // Check the mount executable +- // +- if (mount != args[QStringLiteral("mh_command")].toString()) { +- // Something weird is going on, bail out. +- reply.setType(ActionReply::HelperErrorType); +- return reply; ++ if (mount.isEmpty()) { ++ return errorReply(i18n("The mount command could not be found.")); ++ } ++ ++ QStringList mountOptions = args[QStringLiteral("mh_options")].toStringList(); ++ ++ if (!checkMountArguments(&mountOptions)) { ++ return errorReply(i18n("Forbidden mount options were passed.")); ++ } ++ ++ if (args.contains(QStringLiteral("mh_use_ids")) && args[QStringLiteral("mh_use_ids")].toBool()) { ++ QString uid = KUser(HelperSupport::callerUid()).userId().toString(); ++ QString gid = KUser(HelperSupport::callerUid()).groupId().toString(); ++#if defined(Q_OS_LINUX) ++ mountOptions << QStringLiteral("uid=") + uid; ++ mountOptions << QStringLiteral("gid=") + gid; ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ mountOptions << QStringLiteral("-u"); ++ mountOptions << uid; ++ mountOptions << QStringLiteral("-g"); ++ mountOptions << gid; ++#endif + } + +- // +- // Mount command +- // + QStringList command; + #if defined(Q_OS_LINUX) + command << mount; +- command << args[QStringLiteral("mh_url")].toUrl().toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::RemovePort); +- command << args[QStringLiteral("mh_mountpoint")].toString(); +- command << args[QStringLiteral("mh_options")].toStringList(); ++ command << shareUrl.toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::RemovePort); ++ command << mountPoint; ++ if (!mountOptions.join(QString()).trimmed().isEmpty()) { ++ command << QStringLiteral("-o"); ++ command << mountOptions.join(QStringLiteral(",")); ++ } + #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + command << mount; +- command << args[QStringLiteral("mh_options")].toStringList(); +- command << args[QStringLiteral("mh_url")].toUrl().toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::RemovePort); +- command << args[QStringLiteral("mh_mountpoint")].toString(); ++ if (!mountOptions.join(QString()).trimmed().isEmpty()) { ++ command << mountOptions; ++ } ++ command << shareUrl.toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::RemovePort); ++ command << mountPoint; + #endif + +- // +- // The process +- // + KProcess proc(this); + proc.setOutputChannelMode(KProcess::SeparateChannels); + proc.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); + #if defined(Q_OS_LINUX) + proc.setEnv(QStringLiteral("PASSWD"), args[QStringLiteral("mh_url")].toUrl().password(), true); +-#endif ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + // We need this to avoid a translated password prompt. + proc.setEnv(QStringLiteral("LANG"), QStringLiteral("C")); ++#endif + // If the location of a Kerberos ticket is passed, it needs to + // be passed to the process environment here. + if (args.contains(QStringLiteral("mh_krb5ticket"))) { +- proc.setEnv(QStringLiteral("KRB5CCNAME"), args[QStringLiteral("mh_krb5ticket")].toString()); ++ auto ticketFd = args[QStringLiteral("mh_krb5ticket")].value(); ++ ++ if (!checkFileDescriptor(ticketFd)) { ++ return errorReply(i18n("There is something wrong with the provided Kerberos ticket.")); ++ } ++ ++ QString krb5ccFile = QString(QStringLiteral("/proc/self/fd/%1")).arg(ticketFd.fileDescriptor()); ++ proc.setEnv(QStringLiteral("KRB5CCNAME"), krb5ccFile); + } + + proc.setProgram(command); +@@ -111,7 +186,7 @@ KAuth::ActionReply Smb4KMountHelper::mount(const QVariantMap &args) + proc.kill(); + break; + } +- ++#if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) + // Check if there is a password prompt. If there is one, pass + // the password to it. + QByteArray out = proc.readAllStandardError(); +@@ -120,19 +195,21 @@ KAuth::ActionReply Smb4KMountHelper::mount(const QVariantMap &args) + proc.write(args[QStringLiteral("mh_url")].toUrl().password().toUtf8().data()); + proc.write("\r"); + } +- ++#endif + timeout += 10; +- + wait(10); + } + + if (proc.exitStatus() == KProcess::NormalExit) { + QString stdErr = QString::fromUtf8(proc.readAllStandardError()); + reply.addData(QStringLiteral("mh_error_message"), stdErr.trimmed()); ++ ++ if (stdErr.isEmpty()) { ++ reply.addData(QStringLiteral("mh_mountpoint"), mountPoint); ++ } + } + } else { +- reply.setType(ActionReply::HelperErrorType); +- reply.setErrorDescription(i18n("The mount process could not be started.")); ++ return errorReply(i18n("The mount process could not be started.")); + } + + return reply; +@@ -142,77 +219,40 @@ KAuth::ActionReply Smb4KMountHelper::unmount(const QVariantMap &args) + { + ActionReply reply; + +- // +- // Get the umount executable +- // +- const QString umount = findUmountExecutable(); ++ QString mountPoint = args[QStringLiteral("mh_mountpoint")].toString(); + +- // +- // Check the mount executable +- // +- if (umount != args[QStringLiteral("mh_command")].toString()) { +- // Something weird is going on, bail out. +- reply.setType(ActionReply::HelperErrorType); +- return reply; ++ if (!isMountPointAllowed(mountPoint)) { ++ return errorReply(i18n("The mountpoint %1 is illegal.", args[QStringLiteral("mh_mountpoint")].toString())); + } + +- // +- // Check if the mountpoint is valid and the filesystem is correct. +- // +- bool mountPointOk = false; +- KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions); ++ const QString umount = findUmountExecutable(); + +- for (const QExplicitlySharedDataPointer &mountPoint : std::as_const(mountPoints)) { +- if (args[QStringLiteral("mh_mountpoint")].toString() == mountPoint->mountPoint() +- && (mountPoint->mountType() == QStringLiteral("cifs") || mountPoint->mountType() == QStringLiteral("smb3") +- || mountPoint->mountType() == QStringLiteral("smbfs"))) { +- mountPointOk = true; +- break; +- } ++ if (umount.isEmpty()) { ++ return errorReply(i18n("The umount command could not be found.")); + } + +- // +- // Stop here if the mountpoint is not valid +- // +- if (!mountPointOk) { +- reply.setType(ActionReply::HelperErrorType); +- reply.setErrorDescription(i18n("The mountpoint %1 is invalid.", args[QStringLiteral("mh_mountpoint")].toString())); ++ QStringList unmountOptions = args[QStringLiteral("mh_options")].toStringList(); ++ ++ if (!checkUnmountArguments(&unmountOptions)) { ++ return errorReply(i18n("Forbidden unmount options were passed.")); + } + +- // +- // The command +- // + QStringList command; + command << umount; +- command << args[QStringLiteral("mh_options")].toStringList(); +- command << args[QStringLiteral("mh_mountpoint")].toString(); ++ command << unmountOptions; ++ command << QDir(mountPoint).canonicalPath(); + +- // +- // The process +- // + KProcess proc(this); + proc.setOutputChannelMode(KProcess::SeparateChannels); + proc.setProcessEnvironment(QProcessEnvironment::systemEnvironment()); + proc.setProgram(command); + +- // + // Depending on the online state, use a different behavior for unmounting. + // + // Extensive tests have shown that - when offline - unmounting does not + // work properly when the process is not detached. Thus, detach it when + // the system is offline. +- // +- bool online = false; +- QList interfaces = QNetworkInterface::allInterfaces(); +- +- for (const QNetworkInterface &interface : std::as_const(interfaces)) { +- if (interface.isValid() && interface.type() != QNetworkInterface::Loopback && interface.flags() & QNetworkInterface::IsRunning && !online) { +- online = true; +- break; +- } +- } +- +- if (online) { ++ if (isOnline()) { + proc.start(); + + if (proc.waitForStarted(-1)) { +@@ -228,7 +268,6 @@ KAuth::ActionReply Smb4KMountHelper::unmount(const QVariantMap &args) + } + + timeout += 10; +- + wait(10); + } + +@@ -237,12 +276,294 @@ KAuth::ActionReply Smb4KMountHelper::unmount(const QVariantMap &args) + reply.addData(QStringLiteral("mh_error_message"), stdErr.trimmed()); + } + } else { +- reply.setType(ActionReply::HelperErrorType); +- reply.setErrorDescription(i18n("The unmount process could not be started.")); ++ return errorReply(i18n("The unmount process could not be started.")); + } + } else { + proc.startDetached(); + } + ++ removeMountPoint(QDir(mountPoint).canonicalPath()); ++ + return reply; + } ++ ++bool Smb4KMountHelper::isOnline() const ++{ ++ // FIXME: Do not allow virtual networks ++ bool online = false; ++ QList interfaces = QNetworkInterface::allInterfaces(); ++ ++ for (const QNetworkInterface &interface : std::as_const(interfaces)) { ++ if (interface.isValid() && interface.type() != QNetworkInterface::Loopback && interface.flags() & QNetworkInterface::IsRunning) { ++ online = true; ++ break; ++ } ++ } ++ ++ return online; ++} ++ ++bool Smb4KMountHelper::checkMountArguments(QStringList *argList) const ++{ ++#if defined(Q_OS_LINUX) ++ QStringListIterator it(*argList); ++ ++ while (it.hasNext()) { ++ QString entry = it.next(); ++ QString arg; ++ ++ // Do not allow commata in an entry. We do not want a ++ // malicious user to be able to inject arbitrary options. ++ if (entry.contains(QStringLiteral(","))) { ++ return false; ++ } ++ ++ if (entry.contains(QStringLiteral("="))) { ++ arg = entry.section(QStringLiteral("="), 1, 1); ++ entry = entry.section(QStringLiteral("="), 0, 0); ++ } ++ ++ // If the option is not whitelisted, return here ++ if (!MOUNT_ARG_WHITELIST.contains(entry)) { ++ return false; ++ } ++ ++ // Do not allow setuid root bits or the like ++ if (entry == QStringLiteral("file_mode") || entry == QStringLiteral("dir_mode")) { ++ // We only take an octal number with 3 or 4 digits ++ if (arg.size() < 3 || arg.size() > 4) { ++ return false; ++ } ++ ++ // If the argument is not an octal number, return here ++ bool ok = false; ++ (void)arg.toInt(&ok, 8); ++ ++ if (!ok) { ++ return false; ++ } ++ ++ // We only accept a '0' as first digit in a 4 digit octal number ++ if (arg.size() == 4 && arg[0].toLatin1() != '0') { ++ return false; ++ } ++ } ++ } ++#elif defined(Q_OS_FREEBSD) | defined(Q_OS_NETBSD) ++ QStringListIterator it(*argList); ++ ++ while (it.hasNext()) { ++ QString entry = it.next(); ++ ++ if (!entry.startsWith(QStringLiteral("-"))) { ++ // These can only be arguments of the options ++ continue; ++ } ++ ++ // If the option is not whitelisted, return here ++ if (!MOUNT_ARG_WHITELIST.contains(entry)) { ++ return false; ++ } ++ ++ // Do not allow setuid root bits or the like ++ if (entry == QStringLiteral("-f") || entry == QStringLiteral("-d")) { ++ QString arg = it.peekNext(); ++ ++ // We only take an octal number with 3 or 4 digits ++ if (arg.size() < 3 || arg.size() > 4) { ++ return false; ++ } ++ ++ // If the argument is not an octal number, return here ++ bool ok = false; ++ (void)arg.toInt(&ok, 8); ++ ++ if (!ok) { ++ return false; ++ } ++ ++ // We only accept a '0' as first digit in a 4 digit octal number ++ if (arg.size() == 4 && arg[0].toLatin1() != '0') { ++ return false; ++ } ++ } ++ } ++#endif ++ ++ return true; ++} ++ ++bool Smb4KMountHelper::checkUnmountArguments(QStringList *argList) const ++{ ++ QStringListIterator it(*argList); ++ ++ while (it.hasNext()) { ++ QString entry = it.next(); ++ ++ if (!UNMOUNT_ARG_WHITELIST.contains(entry)) { ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++QString Smb4KMountHelper::mountPrefix() const ++{ ++ return (QDir::cleanPath(QStringLiteral("/var/run")) + QDir::separator() + QStringLiteral("smb4k")); ++} ++ ++ActionReply Smb4KMountHelper::errorReply(const QString &message) const ++{ ++ ActionReply reply = ActionReply::HelperErrorReply(); ++ reply.setErrorDescription(message); ++ return reply; ++} ++ ++std::optional Smb4KMountHelper::createMountPoint(const QUrl &url) const ++{ ++ QDir dir(mountPrefix()); ++ ++ if (!dir.exists()) { ++ if (!dir.mkdir(dir.path(), ++ QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup ++ | QFileDevice::ReadOther | QFileDevice::ExeOther)) { ++ return std::nullopt; ++ } ++ } ++ ++ // Check that the other path components are valid. ++ // NOTE: The host component can not contain any slash characters that would ++ // convert into path delimiters. This is ensured by the QUrl implementation. ++ QString hostComponent = url.host(); ++ QString pathComponent = ++ (url.adjusted(QUrl::StripTrailingSlash).path().startsWith(QDir::separator()) ? url.adjusted(QUrl::StripTrailingSlash).path().removeFirst() ++ : url.adjusted(QUrl::StripTrailingSlash).path()); ++ ++ // Do not allow an empty host name ++ if (hostComponent.isEmpty()) { ++ return std::nullopt; ++ } ++ ++ // Do not allow empty paths, paths containing any '..' or that have more ++ // than one level (= contains slash characters). ++ if (pathComponent.isEmpty() || pathComponent.contains(QStringLiteral("..")) || pathComponent.contains(QDir::separator())) { ++ return std::nullopt; ++ } ++ ++ QStringList pathComponents; ++ pathComponents << KUser(HelperSupport::callerUid()).loginName(); ++ pathComponents << hostComponent; ++ pathComponents << pathComponent; ++ ++ for (const QString &component : std::as_const(pathComponents)) { ++ dir.setPath(dir.canonicalPath() + QDir::separator() + component); ++ ++ if (!dir.exists()) { ++ if (!dir.mkdir(dir.path(), ++ QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner | QFileDevice::ReadGroup | QFileDevice::ExeGroup ++ | QFileDevice::ReadOther | QFileDevice::ExeOther)) { ++ return std::nullopt; ++ } ++ } ++ } ++ ++ return dir.canonicalPath(); ++} ++ ++void Smb4KMountHelper::removeMountPoint(const QString &mountPoint) const ++{ ++ QDir dir; ++ QString prefix = mountPrefix(); ++ ++ // Try to remove everything up to the mount root directory. ++ if (mountPoint.startsWith(prefix) && dir.cd(mountPoint)) { ++ dir.rmdir(dir.canonicalPath()); ++ ++ while (true) { ++ if (dir.cdUp()) { ++ if (dir.canonicalPath() != prefix) { ++ if (!dir.rmdir(dir.canonicalPath())) { ++ break; ++ } ++ } else { ++ break; ++ } ++ } else { ++ break; ++ } ++ } ++ } ++} ++ ++bool Smb4KMountHelper::isMountPointAllowed(const QString &mountPoint) const ++{ ++ QString canonicalMountPoint = QDir(mountPoint).canonicalPath(); ++ QString canonicalUserDirectory = QDir(mountPrefix()).canonicalPath() + QDir::separator() + KUser(HelperSupport::callerUid()).loginName(); ++ ++ if (!canonicalMountPoint.startsWith(canonicalUserDirectory)) { ++ return false; ++ } ++ ++ bool mountPointOk = false; ++ ++#if defined(Q_OS_LINUX) ++ QList mountedVolumes = QStorageInfo::mountedVolumes(); ++ ++ for (const QStorageInfo &volume : std::as_const(mountedVolumes)) { ++ if (volume.fileSystemType() != "cifs" && volume.fileSystemType() != "smb3") { ++ continue; ++ } ++ ++ if (canonicalMountPoint == QDir(volume.rootPath()).canonicalPath()) { ++ mountPointOk = true; ++ break; ++ } ++ } ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ // Under FreeBSD, QStorageInfo does not return 'smbfs' filesystems, so ++ // we need to use the getmntinfo() approach. ++ struct statfs *filesystems = nullptr; ++ int number = getmntinfo(&filesystems, MNT_NOWAIT); ++ ++ if (number > 0) { ++ for (int i = 0; i < number; i++) { ++ if (QString::fromUtf8(filesystems[i].f_fstypename) != QStringLiteral("smbfs")) { ++ continue; ++ } ++ ++ if (canonicalMountPoint == QDir(QString::fromUtf8(filesystems[i].f_mntonname)).canonicalPath()) { ++ mountPointOk = true; ++ break; ++ } ++ } ++ } ++#endif ++ ++ return mountPointOk; ++} ++ ++bool Smb4KMountHelper::checkFileDescriptor(const QDBusUnixFileDescriptor &dbusFd) ++{ ++ if (!dbusFd.isValid()) { ++ return false; ++ } ++ ++ // Check if the file descriptor actually points to a regular file ++ struct stat s; ++ ++ if (fstat(dbusFd.fileDescriptor(), &s)) { ++ return false; ++ } ++ ++ if (!S_ISREG(s.st_mode)) { ++ return false; ++ } ++ ++ int flags = fcntl(dbusFd.fileDescriptor(), F_GETFL); ++ if ((flags & (O_PATH | O_DIRECTORY)) != 0) { ++ return false; ++ } ++ ++ return true; ++} +diff --git a/helpers/smb4kmounthelper.h b/helpers/smb4kmounthelper.h +index c58cddab0ff6..666f03051764 100644 +--- a/helpers/smb4kmounthelper.h ++++ b/helpers/smb4kmounthelper.h +@@ -1,7 +1,7 @@ + /* + The helper that mounts and unmounts shares. + +- SPDX-FileCopyrightText: 2010-2022 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2010-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -9,6 +9,7 @@ + #define SMB4KMOUNTHELPER_H + + // Qt includes ++#include + #include + + // KDE includes +@@ -30,6 +31,17 @@ public Q_SLOTS: + * Unmounts a CIFS/SMBFS share. + */ + KAuth::ActionReply unmount(const QVariantMap &args); ++ ++private: ++ bool isOnline() const; ++ bool checkMountArguments(QStringList *argList) const; ++ bool checkUnmountArguments(QStringList* argList) const; ++ QString mountPrefix() const; ++ ActionReply errorReply(const QString &message) const; ++ std::optional createMountPoint(const QUrl &url) const; ++ void removeMountPoint(const QString &mountPoint) const; ++ bool isMountPointAllowed(const QString &mountPoint) const; ++ bool checkFileDescriptor(const QDBusUnixFileDescriptor &dbusFd); + }; + + #endif +-- +2.51.0 + diff -Nru smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-2.patch smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-2.patch --- smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-2.patch 2025-12-27 09:40:36.000000000 +0000 @@ -0,0 +1,2816 @@ +From: Alexander Reinholdt +Date: Wed, 10 Dec 2025 10:56:43 +0100 +Subject: Merge security bug fix from master. +Origin: https://invent.kde.org/network/smb4k/-/commit/ffc6da7beb1879a968a8181372587ff71f247c1b +Bug-Debian: https://bugs.debian.org/1122381 + +Adjust the core classes according to the changes made to the KAuth +mounthelper for fixing the security issues CVE-2025-66002 and +CVE-2025-66003: + +- Remove all settings that are obsolete: mount prefix, unmounting of + forein shares, user and group, filesystem port, custom CIFS mount + options. +- Introduce the setting to use the user's UID and GID for mounting. +- Remove the whitelist of mount options from Smb4KGlobal. +- Remove the mount point creation and removal from the mounter. +- Pass the Kerberos ticket as a file descriptor to the mounthelper. +- Do not allow the unmounting of foreign shares. +- Do not pass obsolete and rejected settings to the mounthelper. +- Improve error handling in the mounter. +- All shares on the system that are not mounted under + /var/run/smb4k// are considered foreign. +- Improve notifications with regard to failed KAuth actions. +--- + core/smb4kcustomsettings.cpp | 167 +---- + core/smb4kcustomsettings.h | 88 +-- + core/smb4kcustomsettingsmanager.cpp | 53 +- + core/smb4kglobal.cpp | 86 ++- + core/smb4kglobal.h | 34 +- + core/smb4kglobal_p.cpp | 59 +- + core/smb4kglobal_p.h | 2 +- + core/smb4kmounter.cpp | 965 +++++++++++----------------- + core/smb4kmounter.h | 35 +- + core/smb4kmountsettings_bsd.kcfg | 36 +- + core/smb4kmountsettings_linux.kcfg | 54 +- + core/smb4knotification.cpp | 149 ++--- + core/smb4knotification.h | 4 +- + 13 files changed, 572 insertions(+), 1160 deletions(-) + +--- a/core/smb4kcustomsettings.cpp ++++ b/core/smb4kcustomsettings.cpp +@@ -1,7 +1,7 @@ + /* + This class carries custom settings + +- SPDX-FileCopyrightText: 2011-2023 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2011-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -36,18 +36,13 @@ public: + QString profile; + + QPair remount; +- QPair useUser; +- QPair user; +- QPair useGroup; +- QPair group; ++ QPair useIds; + QPair useFileMode; + QPair fileMode; + QPair useDirectoryMode; + QPair directoryMode; + #if defined(Q_OS_LINUX) + QPair cifsUnixExtensionsSupport; +- QPair useFileSystemPort; +- QPair fileSystemPort; + QPair useMountProtocolVersion; + QPair mountProtocolVersion; + QPair useSecurityMode; +@@ -96,16 +91,9 @@ Smb4KCustomSettings::Smb4KCustomSettings + if (host) { + setWorkgroupName(host->workgroupName()); + setIpAddress(host->ipAddress()); +- setUseUser(Smb4KMountSettings::useUserId()); +- setUser(KUser((K_UID)Smb4KMountSettings::userId().toInt())); +- setUseGroup(Smb4KMountSettings::useGroupId()); +- setGroup(KUserGroup((K_GID)Smb4KMountSettings::groupId().toInt())); ++ setUseIds(Smb4KMountSettings::useIds()); + setUseSmbPort(Smb4KSettings::useRemoteSmbPort()); + setSmbPort(host->port() != -1 ? host->port() : Smb4KSettings::remoteSmbPort()); +-#if defined(Q_OS_LINUX) +- setUseFileSystemPort(Smb4KMountSettings::useRemoteFileSystemPort()); +- setFileSystemPort(Smb4KMountSettings::remoteFileSystemPort()); +-#endif + } + break; + } +@@ -115,16 +103,9 @@ Smb4KCustomSettings::Smb4KCustomSettings + if (share) { + setWorkgroupName(share->workgroupName()); + setIpAddress(share->hostIpAddress()); +- setUseUser(Smb4KMountSettings::useUserId()); +- setUser(share->user()); +- setUseGroup(Smb4KMountSettings::useGroupId()); +- setGroup(share->group()); ++ setUseIds(Smb4KMountSettings::useIds()); + setUseSmbPort(Smb4KSettings::useRemoteSmbPort()); + setSmbPort(Smb4KSettings::remoteSmbPort()); +-#if defined(Q_OS_LINUX) +- setUseFileSystemPort(Smb4KMountSettings::useRemoteFileSystemPort()); +- setFileSystemPort(share->port() != -1 ? share->port() : Smb4KMountSettings::remoteFileSystemPort()); +-#endif + } + break; + } +@@ -157,18 +138,13 @@ Smb4KCustomSettings::Smb4KCustomSettings + setProfile(Smb4KSettings::activeProfile()); + + setRemount(UndefinedRemount); +- setUseUser(Smb4KMountSettings::useUserId()); +- setUser(KUser((K_UID)Smb4KMountSettings::userId().toInt())); +- setUseGroup(Smb4KMountSettings::useGroupId()); +- setGroup(KUserGroup((K_GID)Smb4KMountSettings::groupId().toInt())); ++ setUseIds(Smb4KMountSettings::useIds()); + setUseFileMode(Smb4KMountSettings::useFileMode()); + setFileMode(Smb4KMountSettings::fileMode()); + setUseDirectoryMode(Smb4KMountSettings::useDirectoryMode()); + setDirectoryMode(Smb4KMountSettings::directoryMode()); + #if defined(Q_OS_LINUX) + setCifsUnixExtensionsSupport(Smb4KMountSettings::cifsUnixExtensionsSupport()); +- setUseFileSystemPort(Smb4KMountSettings::useRemoteFileSystemPort()); +- setFileSystemPort(Smb4KMountSettings::remoteFileSystemPort()); + setUseMountProtocolVersion(Smb4KMountSettings::useSmbProtocolVersion()); + setMountProtocolVersion(Smb4KMountSettings::smbProtocolVersion()); + setUseSecurityMode(Smb4KMountSettings::useSecurityMode()); +@@ -214,11 +190,6 @@ void Smb4KCustomSettings::setNetworkItem + + if (share) { + setWorkgroupName(share->workgroupName()); +-#if defined(Q_OS_LINUX) +- setFileSystemPort(share->port() != -1 ? share->port() : Smb4KMountSettings::remoteFileSystemPort()); +-#endif +- setUser(share->user()); +- setGroup(share->group()); + setIpAddress(share->hostIpAddress()); + } + break; +@@ -334,44 +305,14 @@ int Smb4KCustomSettings::remount() const + return d->remount.first; + } + +-void Smb4KCustomSettings::setUseUser(bool use) const +-{ +- d->useUser = {use, (use != Smb4KMountSettings::useUserId())}; +-} +- +-bool Smb4KCustomSettings::useUser() const +-{ +- return d->useUser.first; +-} +- +-void Smb4KCustomSettings::setUser(const KUser &user) const +-{ +- d->user = {user, (user.userId().toString() != Smb4KMountSettings::userId())}; +-} +- +-KUser Smb4KCustomSettings::user() const +-{ +- return d->user.first; +-} +- +-void Smb4KCustomSettings::setUseGroup(bool use) const ++void Smb4KCustomSettings::setUseIds(bool set) const + { +- d->useGroup = {use, (use != Smb4KMountSettings::useGroupId())}; ++ d->useIds = {set, (set != Smb4KMountSettings::useIds())}; + } + +-bool Smb4KCustomSettings::useGroup() const ++bool Smb4KCustomSettings::useIds() const + { +- return d->useGroup.first; +-} +- +-void Smb4KCustomSettings::setGroup(const KUserGroup &group) const +-{ +- d->group = {group, (group.groupId().toString() != Smb4KMountSettings::groupId())}; +-} +- +-KUserGroup Smb4KCustomSettings::group() const +-{ +- return d->group.first; ++ return d->useIds.first; + } + + void Smb4KCustomSettings::setUseFileMode(bool use) const +@@ -425,36 +366,6 @@ bool Smb4KCustomSettings::cifsUnixExtens + return d->cifsUnixExtensionsSupport.first; + } + +-void Smb4KCustomSettings::setUseFileSystemPort(bool use) const +-{ +- d->useFileSystemPort = {use, (use != Smb4KMountSettings::useRemoteFileSystemPort())}; +-} +- +-bool Smb4KCustomSettings::useFileSystemPort() const +-{ +- return d->useFileSystemPort.first; +-} +- +-void Smb4KCustomSettings::setFileSystemPort(int port) const +-{ +- d->fileSystemPort = {port, (port != Smb4KMountSettings::remoteFileSystemPort())}; +- +- switch (d->type) { +- case Share: { +- d->url.setPort(port); +- break; +- } +- default: { +- break; +- } +- } +-} +- +-int Smb4KCustomSettings::fileSystemPort() const +-{ +- return d->fileSystemPort.first; +-} +- + void Smb4KCustomSettings::setUseMountProtocolVersion(bool use) const + { + d->useMountProtocolVersion = {use, (use != Smb4KMountSettings::useSmbProtocolVersion())}; +@@ -629,22 +540,8 @@ QMap Smb4KCustomSettin + entries.insert(QStringLiteral("remount"), QString::number(d->remount.first)); + } + +- // User +- if (d->useUser.second && (d->useUser.first != Smb4KMountSettings::useUserId())) { +- entries.insert(QStringLiteral("use_user"), QString::number(d->useUser.first)); +- } +- +- if (d->user.second && (d->user.first.userId().toString() != Smb4KMountSettings::userId())) { +- entries.insert(QStringLiteral("uid"), d->user.first.userId().toString()); +- } +- +- // Group +- if (d->useGroup.second && (d->useGroup.first != Smb4KMountSettings::useGroupId())) { +- entries.insert(QStringLiteral("use_group"), QString::number(d->useGroup.first)); +- } +- +- if (d->group.second && (d->group.first.groupId().toString() != Smb4KMountSettings::groupId())) { +- entries.insert(QStringLiteral("gid"), d->group.first.groupId().toString()); ++ if (d->useIds.second && (d->useIds.first != Smb4KMountSettings::useIds())) { ++ entries.insert(QStringLiteral("use_ids"), QString::number(d->useIds.first)); + } + + // File mode +@@ -671,15 +568,6 @@ QMap Smb4KCustomSettin + entries.insert(QStringLiteral("cifs_unix_extensions_support"), QString::number(d->cifsUnixExtensionsSupport.first)); + } + +- // File system port +- if (d->useFileSystemPort.second && (d->useFileSystemPort.first != Smb4KMountSettings::useRemoteFileSystemPort())) { +- entries.insert(QStringLiteral("use_filesystem_port"), QString::number(d->useFileSystemPort.first)); +- } +- +- if (d->fileSystemPort.second && (d->fileSystemPort.first != Smb4KMountSettings::remoteFileSystemPort())) { +- entries.insert(QStringLiteral("filesystem_port"), QString::number(d->fileSystemPort.first)); +- } +- + // Mount protocol version + if (d->useMountProtocolVersion.second && (d->useMountProtocolVersion.first != Smb4KMountSettings::useSmbProtocolVersion())) { + entries.insert(QStringLiteral("use_smb_mount_protocol_version"), QString::number(d->useMountProtocolVersion.first)); +@@ -763,21 +651,8 @@ bool Smb4KCustomSettings::hasCustomSetti + return true; + } + +- // User +- if (d->useUser.second && (d->useUser.first != Smb4KMountSettings::useUserId())) { +- return true; +- } +- +- if (d->user.second && (d->user.first.userId().toString() != Smb4KMountSettings::userId())) { +- return true; +- } +- +- // Group +- if (d->useGroup.second && (d->useGroup.first != Smb4KMountSettings::useGroupId())) { +- return true; +- } +- +- if (d->group.second && (d->group.first.groupId().toString() != Smb4KMountSettings::groupId())) { ++ // Use IDs ++ if (d->useIds.second && (d->useIds.first != Smb4KMountSettings::useIds())) { + return true; + } + +@@ -805,15 +680,6 @@ bool Smb4KCustomSettings::hasCustomSetti + return true; + } + +- // File system port +- if (d->useFileSystemPort.second && (d->useFileSystemPort.first != Smb4KMountSettings::useRemoteFileSystemPort())) { +- return true; +- } +- +- if (d->fileSystemPort.second && (d->fileSystemPort.first != Smb4KMountSettings::remoteFileSystemPort())) { +- return true; +- } +- + // Mount protocol version + if (d->useMountProtocolVersion.second && (d->useMountProtocolVersion.first != Smb4KMountSettings::useSmbProtocolVersion())) { + return true; +@@ -895,18 +761,13 @@ void Smb4KCustomSettings::update(Smb4KCu + d->profile = customSettings->profile(); + + setRemount(customSettings->remount()); +- setUseUser(customSettings->useUser()); +- setUser(customSettings->user()); +- setUseGroup(customSettings->useGroup()); +- setGroup(customSettings->group()); ++ setUseIds(customSettings->useIds()); + setUseFileMode(customSettings->useFileMode()); + setFileMode(customSettings->fileMode()); + setUseDirectoryMode(customSettings->useDirectoryMode()); + setDirectoryMode(customSettings->directoryMode()); + #if defined(Q_OS_LINUX) + setCifsUnixExtensionsSupport(customSettings->cifsUnixExtensionsSupport()); +- setUseFileSystemPort(customSettings->useFileSystemPort()); +- setFileSystemPort(customSettings->fileSystemPort()); + setUseMountProtocolVersion(customSettings->useMountProtocolVersion()); + setMountProtocolVersion(customSettings->mountProtocolVersion()); + setUseSecurityMode(customSettings->useSecurityMode()); +--- a/core/smb4kcustomsettings.h ++++ b/core/smb4kcustomsettings.h +@@ -1,7 +1,7 @@ + /* + This class carries custom settings + +- SPDX-FileCopyrightText: 2011-2023 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2011-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -175,61 +175,19 @@ public: + int remount() const; + + /** +- * Set if the information about the user that is to be owner of the share +- * should be used when mounting or not. ++ * Set if the user and group ID should be used when mounting. + * +- * @param usage Boolean that determines whether the uid should be +- * used. ++ * @param set Boolean that determines whether the ids should be ++ * used. + */ +- void setUseUser(bool use) const; ++ void setUseIds(bool set) const; + + /** +- * Use the information about the user that is to be owner of the share. ++ * The user and group ID are used when mounting. + * +- * @returns TRUE if the uid should be used when mounting. ++ * @returns TRUE if the user and group ID should be used when mounting. + */ +- bool useUser() const; +- +- /** +- * Set the user who owns the share. +- * @param user The user +- */ +- void setUser(const KUser &user) const; +- +- /** +- * Returns the user who owns the share. +- * @returns the user +- */ +- KUser user() const; +- +- /** +- * Set if the information about the group that is to be owner of the share +- * should be used when mounting or not. +- * +- * @param use Boolean that determines whether the gid should be used. +- */ +- void setUseGroup(bool use) const; +- +- /** +- * Use the information about the group that is to be owner of the share. +- * +- * @returns TRUE if the gid should be used when mounting. +- */ +- bool useGroup() const; +- +- /** +- * Set the group that owns the share. +- * +- * @param group The group +- */ +- void setGroup(const KUserGroup &group) const; +- +- /** +- * Returns the group that owns the share. +- * +- * @returns the group +- */ +- KUserGroup group() const; ++ bool useIds() const; + + /** + * Set if the file mode should be used. +@@ -305,36 +263,6 @@ public: + bool cifsUnixExtensionsSupport() const; + + /** +- * Set if the filesystem port should be used +- * +- * @param use Boolean that determines if the filesystem port should +- * be used +- */ +- void setUseFileSystemPort(bool use) const; +- +- /** +- * Returns if the filesystem port should be used. +- * +- * @returns TRUE if the filesystem port should be used +- */ +- bool useFileSystemPort() const; +- +- /** +- * Set the port that is to be used with mounting for a single share or all +- * shares of a host. +- * +- * @param port The file system port +- */ +- void setFileSystemPort(int port) const; +- +- /** +- * Returns the file system port. The default value is 445. +- * +- * @returns the file system port +- */ +- int fileSystemPort() const; +- +- /** + * Set if the SMB protocol version for mounting should be set. + * + * @param use Boolean that determines if the SMB protocol version +--- a/core/smb4kcustomsettingsmanager.cpp ++++ b/core/smb4kcustomsettingsmanager.cpp +@@ -1,7 +1,7 @@ + /* + Manage custom settings + +- SPDX-FileCopyrightText: 2011-2024 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2011-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -418,41 +418,12 @@ void Smb4KCustomSettingsManager::read() + if (ok) { + settings->setRemount(remount); + } +- } else if (xmlReader.name() == QStringLiteral("use_user")) { ++ } else if (xmlReader.name() == QStringLiteral("use_ids")) { + bool ok = false; +- bool useUser = xmlReader.readElementText().toInt(&ok); ++ bool useIds = xmlReader.readElementText().toInt(&ok); + + if (ok) { +- settings->setUseUser(useUser); +- } +- } else if (xmlReader.name() == QStringLiteral("uid")) { +- bool ok = false; +- int uid = xmlReader.readElementText().toInt(&ok); +- +- if (ok) { +- KUser user((K_UID)uid); +- +- if (user.isValid()) { +- settings->setUser(user); +- } +- } +- } else if (xmlReader.name() == QStringLiteral("use_group")) { +- bool ok = false; +- bool useGroup = xmlReader.readElementText().toInt(&ok); +- +- if (ok) { +- settings->setUseGroup(useGroup); +- } +- } else if (xmlReader.name() == QStringLiteral("gid")) { +- bool ok = false; +- int gid = xmlReader.readElementText().toInt(&ok); +- +- if (ok) { +- KUserGroup group((K_GID)gid); +- +- if (group.isValid()) { +- settings->setGroup(group); +- } ++ settings->setUseIds(useIds); + } + } else if (xmlReader.name() == QStringLiteral("use_file_mode")) { + bool ok = false; +@@ -502,20 +473,6 @@ void Smb4KCustomSettingsManager::read() + if (ok) { + settings->setCifsUnixExtensionsSupport(cifsUnixExtensionsSupported); + } +- } else if (xmlReader.name() == QStringLiteral("use_filesystem_port")) { +- bool ok = false; +- bool useFilesystemPort = xmlReader.readElementText().toInt(&ok); +- +- if (ok) { +- settings->setUseFileSystemPort(useFilesystemPort); +- } +- } else if (xmlReader.name() == QStringLiteral("filesystem_port")) { +- bool ok = false; +- int portNumber = xmlReader.readElementText().toInt(&ok); +- +- if (ok) { +- settings->setFileSystemPort(portNumber); +- } + } else if (xmlReader.name() == QStringLiteral("use_smb_mount_protocol_version")) { + bool ok = false; + bool useMountProtocolVersion = xmlReader.readElementText().toInt(&ok); +--- a/core/smb4kglobal.cpp ++++ b/core/smb4kglobal.cpp +@@ -1,7 +1,7 @@ + /* + This is the global namespace for Smb4K. + +- SPDX-FileCopyrightText: 2005-2024 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2005-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -26,6 +26,7 @@ + // KDE includes + #include + #include ++#include + + Q_GLOBAL_STATIC(Smb4KGlobalPrivate, p); + QRecursiveMutex mutex; +@@ -634,7 +635,7 @@ bool Smb4KGlobal::updateMountedShare(Sha + return updated; + } + +-bool Smb4KGlobal::removeMountedShare(SharePtr share) ++bool Smb4KGlobal::removeMountedShare(SharePtr share, bool takeOnly) + { + Q_ASSERT(share); + +@@ -643,12 +644,8 @@ bool Smb4KGlobal::removeMountedShare(Sha + if (share) { + mutex.lock(); + +- // +- // Reset the mount data for the network share and the +- // search result +- // ++ // Reset the mount data for the _network share_ + if (!share->isForeign()) { +- // Network share + SharePtr networkShare = findShare(share->url(), share->workgroupName()); + + if (networkShare) { +@@ -656,17 +653,12 @@ bool Smb4KGlobal::removeMountedShare(Sha + } + } + +- // +- // Remove the mounted share +- // + int index = p->mountedSharesList.indexOf(share); + + if (index != -1) { +- // The share was found. Remove it. +- p->mountedSharesList.takeAt(index).clear(); ++ p->mountedSharesList.takeAt(index); + removed = true; + } else { +- // Try harder to find the share. + SharePtr s = findShareByPath(share->isInaccessible() ? share->path() : share->canonicalPath()); + + if (s) { +@@ -677,7 +669,9 @@ bool Smb4KGlobal::removeMountedShare(Sha + removed = true; + } + } ++ } + ++ if (share && !takeOnly) { + share.clear(); + } + +@@ -746,13 +740,6 @@ const QString Smb4KGlobal::machineWorkgr + return p->machineWorkgroupName; + } + +-#if defined(Q_OS_LINUX) +-QStringList Smb4KGlobal::allowedMountArguments() +-{ +- return p->allowedMountArguments; +-} +-#endif +- + const QString Smb4KGlobal::findMountExecutable() + { + QStringList paths; +@@ -796,3 +783,60 @@ void Smb4KGlobal::wait(int time) + QTimer::singleShot(time, &loop, SLOT(quit())); + loop.exec(); + } ++ ++const QString Smb4KGlobal::findMacAddress(const QString &ipAddress) ++{ ++ QString macAddress, executable; ++ ++#if defined(Q_OS_LINUX) ++ executable = QStandardPaths::findExecutable(QStringLiteral("ip")); ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ QHostAddress address(ipAddress); ++ ++ if (address.protocol() == QHostAddress::IPv4Protocol) { ++ executable = QStandardPaths::findExecutable(QStringLiteral("arp")); ++ } else if (address.protocol() == QHostAddress::IPv6Protocol) { ++ executable = QStandardPaths::findExecutable(QStringLiteral("ndp")); ++ } ++#endif ++ ++ if (!executable.isEmpty()) { ++ QStringList command; ++ command << executable; ++#if defined(Q_OS_LINUX) ++ command << QStringLiteral("neighbor"); ++ command << QStringLiteral("show"); ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ command << QStringLiteral("-an"); ++#endif ++ ++ KProcess process; ++ process.setProgram(command); ++ process.setOutputChannelMode(KProcess::SeparateChannels); ++ ++ if (process.execute(-1) >= 0) { ++ QStringList result = QString::fromLocal8Bit(process.readAllStandardOutput()).split(QStringLiteral("\n")); ++ ++ for (const QString &r : result) { ++#if defined(Q_OS_LINUX) ++ if (r.section(QStringLiteral(" "), 0, 0) == ipAddress) { ++ macAddress = r.section(QStringLiteral(" "), 4, 4); ++ break; ++ } ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ if (address.protocol() == QHostAddress::IPv4Protocol ++ && r.section(QStringLiteral(" "), 1, 1).remove(QStringLiteral("(")).remove(QStringLiteral(")")) == ipAddress) { ++ macAddress = r.simplified().section(QStringLiteral(" "), 3, 3); ++ break; ++ } else if (address.protocol() == QHostAddress::IPv6Protocol ++ && r.section(QStringLiteral(" "), 0, 0).section(QStringLiteral("%"), 0, 0) == ipAddress) { ++ macAddress = r.simplified().section(QStringLiteral(" "), 1, 1); ++ break; ++ } ++#endif ++ } ++ } ++ } ++ ++ return macAddress; ++} +--- a/core/smb4kglobal.h ++++ b/core/smb4kglobal.h +@@ -1,7 +1,7 @@ + /* + This is the global namespace for Smb4K. + +- SPDX-FileCopyrightText: 2005-2024 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2005-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -314,23 +314,29 @@ SMB4KCORE_EXPORT bool addMountedShare(Sh + * is already present in the internal list. + * + * @param share The share item ++ * + * @returns TRUE if a share was found and updated. Returns FALSE otherwise. + */ + SMB4KCORE_EXPORT bool updateMountedShare(SharePtr share); + + /** + * This function removes a mounted share @p share from the list of mounted +- * shares. The pointer that is passed to this function will be deleted. +- * You won't be able to use it afterwards. This function returns TRUE if +- * the share was removed and FALSE otherwise. ++ * shares. If @p takeOnly is FALSE, the pointer that is passed to this function ++ * will be deleted. You won't be able to use it afterwards. If @p takeOnly is ++ * TRUE, the mounted share is only removed, but not deleted. ++ * ++ * This function returns TRUE if the share was removed and FALSE otherwise. + * + * Please prefer this function over per class solutions. + * + * @param share The share item + * ++ * @param takeOnly TRUE if the share should only be removed from the ++ * list, but not deleted, and FALSE otherwise ++ * + * @returns TRUE if the share was removed and FALSE otherwise. + */ +-SMB4KCORE_EXPORT bool removeMountedShare(SharePtr share); ++SMB4KCORE_EXPORT bool removeMountedShare(SharePtr share, bool takeOnly); + + /** + * This function returns TRUE if only shares are present that are owned by +@@ -383,16 +389,6 @@ SMB4KCORE_EXPORT const QString machineWo + */ + SMB4KCORE_EXPORT bool modifyCursor(); + +-#if defined(Q_OS_LINUX) +-/** +- * This list contains all allowed arguments for the mount.cifs binary and +- * is only present under the Linux operating system. +- * +- * @returns the list of allowed mount arguments +- */ +-SMB4KCORE_EXPORT QStringList allowedMountArguments(); +-#endif +- + /** + * Find the mount executable on the system. + * +@@ -420,6 +416,14 @@ SMB4KCORE_EXPORT const QString dataLocat + * @param time The waiting time in msec + */ + SMB4KCORE_EXPORT void wait(int time); ++ ++/** ++ * Query the local arp cache and retrieve the MAC address for the given ++ * IP address if available. ++ * ++ * @param ipAddress The IP addess of the server ++ */ ++SMB4KCORE_EXPORT const QString findMacAddress(const QString &ipAddress); + }; + + #endif +--- a/core/smb4kglobal_p.cpp ++++ b/core/smb4kglobal_p.cpp +@@ -1,7 +1,7 @@ + /* + These are the private helper classes of the Smb4KGlobal namespace. + +- SPDX-FileCopyrightText: 2007-2024 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2007-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -26,63 +26,6 @@ Smb4KGlobalPrivate::Smb4KGlobalPrivate() + { + onlyForeignShares = false; + +-#ifdef Q_OS_LINUX +- // +- // 2023-01-08: Updated to mount.cifs, version 7.0 +- // +- // The options 'credentials', 'domainauto', 'intr', 'nointr' +- // and 'snapshot' were not included for various reasons. +- // +- allowedMountArguments << QStringLiteral("idsfromsid"); +- allowedMountArguments << QStringLiteral("dynperm"); +- allowedMountArguments << QStringLiteral("nostrictsync"); +- allowedMountArguments << QStringLiteral("handlecache"); +- allowedMountArguments << QStringLiteral("nohandlecache"); +- allowedMountArguments << QStringLiteral("handletimeout"); +- allowedMountArguments << QStringLiteral("rwpidforward"); +- allowedMountArguments << QStringLiteral("mapposix"); +- allowedMountArguments << QStringLiteral("hard"); +- allowedMountArguments << QStringLiteral("soft"); +- allowedMountArguments << QStringLiteral("noacl"); +- allowedMountArguments << QStringLiteral("cifsacl"); +- allowedMountArguments << QStringLiteral("backupuid"); +- allowedMountArguments << QStringLiteral("backupgid"); +- allowedMountArguments << QStringLiteral("ignorecase"); +- allowedMountArguments << QStringLiteral("nocase"); +- allowedMountArguments << QStringLiteral("seal"); +- allowedMountArguments << QStringLiteral("rdma"); +- allowedMountArguments << QStringLiteral("resilienthandles"); +- allowedMountArguments << QStringLiteral("noresilienthandles"); +- allowedMountArguments << QStringLiteral("persistenthandles"); +- allowedMountArguments << QStringLiteral("nopersistenthandles"); +- allowedMountArguments << QStringLiteral("forcemandatorylock"); +- allowedMountArguments << QStringLiteral("locallease"); +- allowedMountArguments << QStringLiteral("nolease"); +- allowedMountArguments << QStringLiteral("sfu"); +- allowedMountArguments << QStringLiteral("mfsymlinks"); +- allowedMountArguments << QStringLiteral("echo_interval"); +- allowedMountArguments << QStringLiteral("posix"); +- allowedMountArguments << QStringLiteral("unix"); +- allowedMountArguments << QStringLiteral("linux"); +- allowedMountArguments << QStringLiteral("noposix"); +- allowedMountArguments << QStringLiteral("nounix"); +- allowedMountArguments << QStringLiteral("nolinux"); +- allowedMountArguments << QStringLiteral("nouser_xattr"); +- allowedMountArguments << QStringLiteral("nodfs"); +- allowedMountArguments << QStringLiteral("noautotune"); +- allowedMountArguments << QStringLiteral("nosharesock"); +- allowedMountArguments << QStringLiteral("noblocksend"); +- allowedMountArguments << QStringLiteral("rsize"); +- allowedMountArguments << QStringLiteral("wsize"); +- allowedMountArguments << QStringLiteral("bsize"); +- allowedMountArguments << QStringLiteral("max_credits"); +- allowedMountArguments << QStringLiteral("fsc"); +- allowedMountArguments << QStringLiteral("multiuser"); +- allowedMountArguments << QStringLiteral("actimeo"); +- allowedMountArguments << QStringLiteral("noposixpaths"); +- allowedMountArguments << QStringLiteral("posixpaths"); +-#endif +- + // + // Create and init the SMB context and read the NetBIOS and + // workgroup name of this machine. +--- a/core/smb4kglobal_p.h ++++ b/core/smb4kglobal_p.h +@@ -1,7 +1,7 @@ + /* + These are the private helper classes of the Smb4KGlobal namespace. + +- SPDX-FileCopyrightText: 2007-2024 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2007-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +--- a/core/smb4kmounter.cpp ++++ b/core/smb4kmounter.cpp +@@ -26,8 +26,13 @@ + #include "smb4kmountsettings_bsd.h" + #endif + ++// system includes ++#include ++#include ++ + // Qt includes + #include ++#include + #include + #include + #include +@@ -57,13 +62,11 @@ public: + int remountAttempts; + int timerId; + int checkTimeout; +- int newlyMounted; +- int newlyUnmounted; +- QList importedShares; ++ QList newlyMounted; ++ QList newlyUnmounted; + QList retries; + QList remounts; + bool detectAllShares; +- bool firstImportDone; + bool longActionRunning; + QStorageInfo storageInfo; + }; +@@ -86,9 +89,6 @@ Smb4KMounter::Smb4KMounter(QObject *pare + d->remountTimeout = 0; + d->remountAttempts = 0; + d->checkTimeout = 0; +- d->newlyMounted = 0; +- d->newlyUnmounted = 0; +- d->firstImportDone = false; + d->longActionRunning = false; + d->detectAllShares = Smb4KMountSettings::detectAllShares(); + +@@ -101,21 +101,29 @@ Smb4KMounter::Smb4KMounter(QObject *pare + connect(Smb4KMountSettings::self(), &Smb4KMountSettings::configChanged, this, &Smb4KMounter::slotConfigChanged); + + connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::onlineStateChanged, this, &Smb4KMounter::slotOnlineStateChanged); +- connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::networkShareAdded, this, &Smb4KMounter::slotTriggerImport); +- connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::networkShareRemoved, this, &Smb4KMounter::slotTriggerImport); ++ connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::networkShareAdded, this, &Smb4KMounter::slotShareMounted); ++ connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::networkShareRemoved, this, &Smb4KMounter::slotShareUnmounted); + + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Smb4KMounter::slotAboutToQuit); + } + + Smb4KMounter::~Smb4KMounter() + { +- while (!d->importedShares.isEmpty()) { +- d->importedShares.takeFirst().clear(); ++ while (!d->newlyMounted.isEmpty()) { ++ d->newlyMounted.takeFirst().clear(); ++ } ++ ++ while (!d->newlyUnmounted.isEmpty()) { ++ d->newlyUnmounted.takeFirst().clear(); + } + + while (!d->retries.isEmpty()) { + d->retries.takeFirst().clear(); + } ++ ++ while (!d->remounts.isEmpty()) { ++ d->remounts.takeFirst().clear(); ++ } + } + + Smb4KMounter *Smb4KMounter::self() +@@ -141,26 +149,18 @@ bool Smb4KMounter::isRunning() + + void Smb4KMounter::triggerRemounts(bool fillList) + { ++ if (d->remounts.isEmpty() && !fillList) { ++ return; ++ } ++ + if (fillList) { +- // +- // Get the list of shares that are to be remounted +- // + QList options = Smb4KCustomSettingsManager::self()->sharesToRemount(); + +- // +- // Process the list and honor the settings the user chose +- // + for (const CustomSettingsPtr &option : std::as_const(options)) { +- // +- // Skip one time remount shares, if needed +- // + if (option->remount() == Smb4KCustomSettings::RemountOnce && !Smb4KMountSettings::remountShares()) { + continue; + } + +- // +- // Check which share has to be remounted +- // + QList mountedShares = findShareByUrl(option->url()); + bool remountShare = true; + +@@ -171,9 +171,6 @@ void Smb4KMounter::triggerRemounts(bool + } + } + +- // +- // Insert the share to the list of remounts +- // + if (remountShare) { + bool insertShare = true; + +@@ -200,198 +197,10 @@ void Smb4KMounter::triggerRemounts(bool + } + } + +- // +- // Remount the shares +- // + mountShares(d->remounts); +- +- // +- // Count the remount attempts +- // + d->remountAttempts++; + } + +-void Smb4KMounter::import(bool checkInaccessible) +-{ +- if (!d->importedShares.isEmpty()) { +- return; +- } +- +- bool changedMountedSharesList = false; +- QStringList allMountPoints; +- +- // +- // Import new shares +- // +- KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions); +- +- for (const QExplicitlySharedDataPointer &mountPoint : std::as_const(mountPoints)) { +- if (!mountPoint->isOnNetwork()) { +- continue; +- } +- +- if (mountPoint->mountType() == QStringLiteral("cifs") || mountPoint->mountType() == QStringLiteral("smb3") +- || mountPoint->mountType() == QStringLiteral("smbfs")) { +- allMountPoints << mountPoint->mountPoint(); +- +- // Exclude all shares that were already imported. However, while we are +- // at it, we run a check on them nonetheless. +- if (SharePtr mountedShare = findShareByPath(mountPoint->mountPoint())) { +- if (!mountedShare->isInaccessible() || checkInaccessible) { +- check(mountedShare); +- } +- continue; +- } +- +- SharePtr share = SharePtr(new Smb4KShare()); +- share->setUrl(QUrl(mountPoint->mountedFrom())); +- share->setPath(mountPoint->mountPoint()); +- share->setMounted(true); +- +- QStringList mountOptions = mountPoint->mountOptions(); +- +- for (const QString &option : std::as_const(mountOptions)) { +- if (option.startsWith(QStringLiteral("domain=")) || option.startsWith(QStringLiteral("workgroup="))) { +- share->setWorkgroupName(option.section(QStringLiteral("="), 1, 1).trimmed()); +- } else if (option.startsWith(QStringLiteral("addr="))) { +- share->setHostIpAddress(option.section(QStringLiteral("="), 1, 1).trimmed()); +- } else if (option.startsWith(QStringLiteral("username=")) || option.startsWith(QStringLiteral("user="))) { +- share->setUserName(option.section(QStringLiteral("="), 1, 1).trimmed()); +- } +- } +- +- // Work around empty usernames +- if (share->userName().isEmpty()) { +- share->setUserName(QStringLiteral("guest")); +- } +- +- d->importedShares << share; +- } +- } +- +- // +- // Process the shares +- // +- SharePtr share; +- +- QList mountedShares = mountedSharesList(); +- QMutableListIterator it(mountedShares); +- +- while (it.hasNext()) { +- share = it.next(); +- +- if (!allMountPoints.contains(share->path())) { +- if (!share->isForeign()) { +- QDir dir; +- dir.cd(share->canonicalPath()); +- dir.rmdir(dir.canonicalPath()); +- +- if (dir.cdUp()) { +- dir.rmdir(dir.canonicalPath()); +- } +- } +- +- share->setMounted(false); +- +- // NOTE: We are operating on a copy of the mounted shares list, +- // so the following should be fine. +- if (removeMountedShare(share)) { +- d->newlyUnmounted += 1; +- changedMountedSharesList = true; +- Q_EMIT unmounted(share); +- } +- } +- } +- +- if (!isRunning()) { +- if (d->newlyUnmounted > 1) { +- Smb4KNotification::sharesUnmounted(d->newlyUnmounted); +- } else if (d->newlyUnmounted == 1) { +- Smb4KNotification::shareUnmounted(share); +- } +- +- d->newlyUnmounted = 0; +- } +- +- share.clear(); +- +- if (Smb4KHardwareInterface::self()->isOnline()) { +- while (!d->importedShares.isEmpty()) { +- share = d->importedShares.takeFirst(); +- +- check(share); +- +- if (share->path().startsWith(Smb4KMountSettings::mountPrefix().path()) +- || (!share->isInaccessible() && share->canonicalPath().startsWith(QDir(Smb4KMountSettings::mountPrefix().path()).canonicalPath()))) { +- share->setForeign(false); +- } else if (share->path().startsWith(QDir::homePath()) +- || (!share->isInaccessible() && share->canonicalPath().startsWith(QDir::home().canonicalPath()))) { +- share->setForeign(false); +- } else if (share->user().userId() == KUser(KUser::UseRealUserID).userId() +- && share->group().groupId() == KUserGroup(KUser::UseRealUserID).groupId()) { +- share->setForeign(false); +- } else { +- share->setForeign(true); +- } +- +- if (!share->isForeign() || Smb4KMountSettings::detectAllShares()) { +- if (addMountedShare(share)) { +- d->newlyMounted += 1; +- changedMountedSharesList = true; +- +- // Remove share from the remounts +- QMutableListIterator s(d->remounts); +- +- while (s.hasNext()) { +- SharePtr remount = s.next(); +- +- if (!share->isForeign() +- && QString::compare(remount->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort), +- share->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort), +- Qt::CaseInsensitive) +- == 0) { +- Smb4KCustomSettingsManager::self()->removeRemount(remount); +- s.remove(); +- break; +- } +- } +- +- Q_EMIT mounted(share); +- } +- } +- } +- +- if (!isRunning()) { +- if (d->firstImportDone) { +- if (d->newlyMounted > 1) { +- Smb4KNotification::sharesMounted(d->newlyMounted); +- } else if (d->newlyMounted == 1) { +- Smb4KNotification::shareMounted(share); +- } +- } +- +- d->newlyMounted = 0; +- } +- +- share.clear(); +- +- if (!d->firstImportDone && d->importedShares.isEmpty()) { +- d->firstImportDone = true; +- } +- } else { +- // If the system is offline, no mounted shares are processed, so +- // empty the list of imported shares here. +- while (!d->importedShares.isEmpty()) { +- SharePtr share = d->importedShares.takeFirst(); +- share.clear(); +- } +- } +- +- if (changedMountedSharesList) { +- Q_EMIT mountedSharesListChanged(); +- } +-} +- + void Smb4KMounter::mountShare(const SharePtr &share) + { + if (share) { +@@ -483,56 +292,6 @@ void Smb4KMounter::mountShare(const Shar + } + + // +- // Create the mountpoint +- // +- QString mountpoint; +- mountpoint += Smb4KMountSettings::mountPrefix().path(); +- mountpoint += QDir::separator(); +- mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->hostName().toLower() : share->hostName()); +- mountpoint += QDir::separator(); +- +- if (!share->isHomesShare()) { +- mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->shareName().toLower() : share->shareName()); +- } else { +- mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->userName().toLower() : share->userName()); +- } +- +- // Get the permissions that should be used for creating the +- // mount prefix and all its subdirectories. +- // Please note that the actual permissions of the mount points +- // are determined by the mount utility. +- QFile::Permissions permissions; +- QUrl parentDirectory; +- +- if (QFile::exists(Smb4KMountSettings::mountPrefix().path())) { +- parentDirectory = Smb4KMountSettings::mountPrefix(); +- } else { +- QUrl u = Smb4KMountSettings::mountPrefix(); +- parentDirectory = u.resolved(QUrl(QStringLiteral(".."))); +- ; +- } +- +- QFile f(parentDirectory.path()); +- permissions = f.permissions(); +- +- QDir dir(mountpoint); +- +- if (!dir.mkpath(dir.path())) { +- share->setPath(QStringLiteral("")); +- Smb4KNotification::mkdirFailed(dir); +- return; +- } else { +- QUrl u = QUrl::fromLocalFile(dir.path()); +- +- while (!parentDirectory.matches(u, QUrl::StripTrailingSlash)) { +- QFile(u.path()).setPermissions(permissions); +- u = u.resolved(QUrl(QStringLiteral(".."))); +- } +- } +- +- share->setPath(QDir::cleanPath(mountpoint)); +- +- // + // Get the authentication information + // + Smb4KCredentialsManager::self()->readLoginCredentials(share); +@@ -541,17 +300,16 @@ void Smb4KMounter::mountShare(const Shar + // Mount arguments + // + QVariantMap args; ++ int fd = -1; + +- if (!fillMountActionArgs(share, args)) { ++ if (!fillMountActionArgs(share, &fd, args)) { ++ if (fd >= 0) { ++ close(fd); ++ } + return; + } + + // +- // Emit the aboutToStart() signal +- // +- Q_EMIT aboutToStart(MountShare); +- +- // + // Create the mount action + // + KAuth::Action mountAction(QStringLiteral("org.kde.smb4k.mounthelper.mount")); +@@ -559,59 +317,47 @@ void Smb4KMounter::mountShare(const Shar + mountAction.setArguments(args); + + KAuth::ExecuteJob *job = mountAction.execute(); ++ addSubjob(job); + + // +- // Modify the cursor, if necessary. +- // +- if (!hasSubjobs()) { +- QApplication::setOverrideCursor(Qt::BusyCursor); +- } +- +- // +- // Add the job ++ // Emit the aboutToStart() signal + // +- addSubjob(job); ++ Q_EMIT aboutToStart(MountShare); + + // + // Start the job and process the returned result. + // +- bool success = job->exec(); ++ if (job->exec()) { ++ QString errorMsg = job->data().value(QStringLiteral("mh_error_message")).toString(); + +- if (success) { +- int errorCode = job->error(); +- +- if (errorCode == 0) { +- // Get the error message +- QString errorMsg = job->data().value(QStringLiteral("mh_error_message")).toString(); +- +- if (!errorMsg.isEmpty()) { ++ if (!errorMsg.isEmpty()) { + #if defined(Q_OS_LINUX) +- if (errorMsg.contains(QStringLiteral("mount error 13")) +- || errorMsg.contains(QStringLiteral("mount error(13)")) /* authentication error */) { +- d->retries << share; +- Q_EMIT requestCredentials(share); +- } else if (errorMsg.contains(QStringLiteral("Unable to find suitable address."))) { +- // Swallow this +- } else { +- Smb4KNotification::mountingFailed(share, errorMsg); +- } ++ if (errorMsg.contains(QStringLiteral("mount error 13")) || errorMsg.contains(QStringLiteral("mount error(13)")) /* authentication error */) { ++ d->retries << share; ++ Q_EMIT requestCredentials(share); ++ } else if (errorMsg.contains(QStringLiteral("Unable to find suitable address."))) { ++ // Swallow this ++ } else { ++ Smb4KNotification::mountingFailed(share, errorMsg); ++ } + #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +- if (errorMsg.contains(QStringLiteral("Authentication error")) || errorMsg.contains(QStringLiteral("Permission denied"))) { +- d->retries << share; +- Q_EMIT requestCredentials(share); +- } else { +- Smb4KNotification::mountingFailed(share, errorMsg); +- } +-#else +- qWarning() << "Smb4KMounter::slotMountJobFinished(): Error handling not implemented!"; ++ if (errorMsg.contains(QStringLiteral("Authentication error")) || errorMsg.contains(QStringLiteral("Permission denied"))) { ++ d->retries << share; ++ Q_EMIT requestCredentials(share); ++ } else { + Smb4KNotification::mountingFailed(share, errorMsg); +-#endif + } +- } else { +- Smb4KNotification::actionFailed(errorCode); ++#else ++ qWarning() << "Smb4KMounter::slotMountJobFinished(): Error handling not implemented!"; ++ Smb4KNotification::mountingFailed(share, errorMsg); ++#endif + } + } else { +- // FIXME: Report that the action could not be started ++ Smb4KNotification::actionFailed(job->error(), job->errorString()); ++ } ++ ++ if (fd >= 0) { ++ close(fd); + } + + // +@@ -635,22 +381,27 @@ void Smb4KMounter::mountShare(const Shar + + void Smb4KMounter::mountShares(const QList &shares) + { +- // +- // This action takes longer +- // + d->longActionRunning = true; + +- // +- // Mount the shares +- // + for (const SharePtr &share : shares) { + mountShare(share); + } + +- // +- // This action is over +- // + d->longActionRunning = false; ++ ++ if (Smb4KHardwareInterface::self()->initialImportDone()) { ++ if (d->newlyMounted.size() > 1) { ++ Smb4KNotification::sharesMounted(d->newlyMounted.size()); ++ } else { ++ if (d->newlyMounted.size() == 1) { ++ Smb4KNotification::shareMounted(d->newlyMounted.first()); ++ } ++ } ++ } ++ ++ while (!d->newlyMounted.isEmpty()) { ++ d->newlyMounted.takeFirst().clear(); ++ } + } + + void Smb4KMounter::unmountShare(const SharePtr &share, bool silent) +@@ -667,35 +418,26 @@ void Smb4KMounter::unmountShare(const Sh + } + + // +- // Handle foreign shares according to the settings ++ // Check the mountpoint ++ // Use Smb4KShare::path() here, otherwise the check will always return true. + // +- if (share->isForeign()) { +- if (!Smb4KMountSettings::unmountForeignShares()) { +- if (!silent) { +- Smb4KNotification::unmountingNotAllowed(share); +- } ++ QFileInfo info(share->path()); ++ info.setCaching(false); + +- return; +- } else { +- if (!silent) { +- if (KMessageBox::warningTwoActions(QApplication::activeWindow(), +- i18n("

The share %1 is mounted to
%2 and owned by user %3.

" +- "

Do you really want to unmount it?

", +- share->displayString(), +- share->path(), +- share->user().loginName()), +- i18n("Foreign Share"), +- KStandardGuiItem::ok(), +- KStandardGuiItem::cancel()) +- == KMessageBox::SecondaryAction) { +- return; +- } +- } else { +- // Without the confirmation of the user, we are not +- // unmounting a foreign share! +- return; +- } ++ if (!info.isDir() || info.isSymLink()) { ++ Smb4KNotification::mountingFailed(share, i18n("The mountpoint %1 is illegal.", share->path())); ++ return; ++ } ++ ++ // ++ // Foreign shares cannot be unmounted ++ // ++ if (share->isForeign()) { ++ if (!silent) { ++ Smb4KNotification::unmountingNotAllowed(share); + } ++ ++ return; + } + + // +@@ -724,11 +466,6 @@ void Smb4KMounter::unmountShare(const Sh + } + + // +- // Emit the aboutToStart() signal +- // +- Q_EMIT aboutToStart(UnmountShare); +- +- // + // Create the unmount action + // + KAuth::Action unmountAction(QStringLiteral("org.kde.smb4k.mounthelper.unmount")); +@@ -736,40 +473,26 @@ void Smb4KMounter::unmountShare(const Sh + unmountAction.setArguments(args); + + KAuth::ExecuteJob *job = unmountAction.execute(); ++ addSubjob(job); + + // +- // Modify the cursor, if necessary. +- // +- if (!hasSubjobs()) { +- QApplication::setOverrideCursor(Qt::BusyCursor); +- } +- +- // +- // Add the job ++ // Emit the aboutToStart() signal + // +- addSubjob(job); ++ Q_EMIT aboutToStart(UnmountShare); + + // + // Start the job and process the returned result. + // +- bool success = job->exec(); +- +- if (success) { +- int errorCode = job->error(); +- +- if (errorCode == 0) { +- // Get the error message +- QString errorMsg = job->data().value(QStringLiteral("mh_error_message")).toString(); +- +- if (!errorMsg.isEmpty()) { +- // No error handling needed, just report the error message. +- Smb4KNotification::unmountingFailed(share, errorMsg); +- } +- } else { +- Smb4KNotification::actionFailed(errorCode); ++ if (job->exec()) { ++ // Get the error message ++ QString errorMsg = job->data().value(QStringLiteral("mh_error_message")).toString(); ++ ++ if (!errorMsg.isEmpty()) { ++ // No error handling needed, just report the error message. ++ Smb4KNotification::unmountingFailed(share, errorMsg); + } + } else { +- // FIXME: Report that the action could not be started ++ Smb4KNotification::actionFailed(job->error(), job->errorString()); + } + + // +@@ -793,33 +516,29 @@ void Smb4KMounter::unmountShare(const Sh + + void Smb4KMounter::unmountShares(const QList &shares, bool silent) + { +- // +- // This action takes longer +- // + d->longActionRunning = true; + +- // +- // Inhibit shutdown and sleep +- // + Smb4KHardwareInterface::self()->inhibit(); + +- // +- // Unmount the list of shares +- // + for (const SharePtr &share : shares) { +- // Unmount the share + unmountShare(share, silent); + } + +- // +- // Uninhibit shutdown and sleep +- // + Smb4KHardwareInterface::self()->uninhibit(); + +- // +- // This action is over +- // + d->longActionRunning = false; ++ ++ if (d->newlyUnmounted.size() > 1) { ++ Smb4KNotification::sharesUnmounted(d->newlyUnmounted.size()); ++ } else { ++ if (d->newlyUnmounted.size() == 1) { ++ Smb4KNotification::shareUnmounted(d->newlyUnmounted.first()); ++ } ++ } ++ ++ while (!d->newlyUnmounted.isEmpty()) { ++ d->newlyUnmounted.takeFirst().clear(); ++ } + } + + void Smb4KMounter::unmountAllShares(bool silent) +@@ -836,9 +555,6 @@ void Smb4KMounter::start() + + void Smb4KMounter::saveSharesForRemount() + { +- // +- // Save the shares for remount +- // + for (const SharePtr &share : mountedSharesList()) { + if (!share->isForeign()) { + Smb4KCustomSettingsManager::self()->addRemount(share, false); +@@ -847,9 +563,6 @@ void Smb4KMounter::saveSharesForRemount( + } + } + +- // +- // Also save each failed remount and remove it from the list +- // + while (!d->remounts.isEmpty()) { + SharePtr share = d->remounts.takeFirst(); + Smb4KCustomSettingsManager::self()->addRemount(share, false); +@@ -865,7 +578,7 @@ void Smb4KMounter::timerEvent(QTimerEven + // + // Try to remount shares + // +- if (d->remountAttempts < Smb4KMountSettings::remountAttempts() && d->firstImportDone) { ++ if (d->remountAttempts < Smb4KMountSettings::remountAttempts() && Smb4KHardwareInterface::self()->initialImportDone()) { + if (d->remountAttempts == 0) { + triggerRemounts(true); + } +@@ -881,7 +594,7 @@ void Smb4KMounter::timerEvent(QTimerEven + // + // Check the size, accessibility, etc. of the shares + // +- if (d->checkTimeout >= 2500 && d->importedShares.isEmpty()) { ++ if (d->checkTimeout >= 2500) { + for (const SharePtr &share : mountedSharesList()) { + check(share); + Q_EMIT updated(share); +@@ -898,16 +611,17 @@ void Smb4KMounter::timerEvent(QTimerEven + // + // Linux arguments + // +-bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, QVariantMap &map) ++bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, int *fd, QVariantMap &map) + { + // +- // Find the mount executable ++ // Check the availability of the umount command. ++ // ++ // NOTE: We do not need to pass the command to the helper, though, since it ++ // will be invoke by it directly. + // + const QString mount = findMountExecutable(); + +- if (!mount.isEmpty()) { +- map.insert(QStringLiteral("mh_command"), mount); +- } else { ++ if (mount.isEmpty()) { + Smb4KNotification::commandNotFound(QStringLiteral("mount.cifs")); + return false; + } +@@ -918,19 +632,6 @@ bool Smb4KMounter::fillMountActionArgs(c + CustomSettingsPtr options = Smb4KCustomSettingsManager::self()->findCustomSettings(share); + + // +- // Pass the remote file system port to the URL +- // +- if (options) { +- if (options->useFileSystemPort()) { +- share->setPort(options->fileSystemPort()); +- } +- } else { +- if (Smb4KMountSettings::useRemoteFileSystemPort()) { +- share->setPort(Smb4KMountSettings::remoteFileSystemPort()); +- } +- } +- +- // + // List of arguments passed via "-o ..." to the mount command + // + QStringList argumentsList; +@@ -963,63 +664,28 @@ bool Smb4KMounter::fillMountActionArgs(c + } + + // +- // Client's and server's NetBIOS name +- // +- // According to the manual page, this is only needed when port 139 +- // is used. So, we only pass the NetBIOS name in that case. +- // +- bool setNetbiosNames = false; +- +- if (options) { +- setNetbiosNames = (options->useFileSystemPort() && options->fileSystemPort() == 139); +- } else { +- setNetbiosNames = (Smb4KMountSettings::useRemoteFileSystemPort() && Smb4KMountSettings::remoteFileSystemPort() == 139); +- } +- +- if (setNetbiosNames) { +- // The client's NetBIOS name. If that is empty, fall back to the local host name. +- if (!machineNetbiosName().isEmpty()) { +- argumentsList << QStringLiteral("netbiosname=") + KShell::quoteArg(machineNetbiosName()); +- } else { +- argumentsList << QStringLiteral("netbiosname=") + KShell::quoteArg(QHostInfo::localHostName()); +- } +- +- // The server's NetBIOS name +- argumentsList << QStringLiteral("servern=") + KShell::quoteArg(share->hostName()); +- } +- +- // + // CIFS Unix extensions support + // + // This sets the uid, gid, file_mode and dir_mode arguments, if necessary. + // +- bool useCifsUnixExtensionsSupport = false; +- QString userString, groupString, fileModeString, directoryModeString; ++ bool useCifsUnixExtensionsSupport, useIds = false; ++ QString fileModeString, directoryModeString; + + if (options) { + useCifsUnixExtensionsSupport = options->cifsUnixExtensionsSupport(); +- userString = options->useUser() ? options->user().userId().toString() : QString(); +- groupString = options->useGroup() ? options->group().groupId().toString() : QString(); ++ useIds = options->useIds(); + fileModeString = options->useFileMode() ? options->fileMode() : QString(); + directoryModeString = options->useDirectoryMode() ? options->directoryMode() : QString(); + } else { + useCifsUnixExtensionsSupport = Smb4KMountSettings::cifsUnixExtensionsSupport(); +- userString = Smb4KMountSettings::useUserId() ? Smb4KMountSettings::userId() : QString(); +- groupString = Smb4KMountSettings::useGroupId() ? Smb4KMountSettings::groupId() : QString(); ++ useIds = Smb4KMountSettings::useIds(); + fileModeString = Smb4KMountSettings::useFileMode() ? Smb4KMountSettings::fileMode() : QString(); + directoryModeString = Smb4KMountSettings::useDirectoryMode() ? Smb4KMountSettings::directoryMode() : QString(); + } + + if (!useCifsUnixExtensionsSupport) { +- // User id +- if (!userString.isEmpty()) { +- argumentsList << QStringLiteral("uid=") + userString; +- } +- +- // Group id +- if (!groupString.isEmpty()) { +- argumentsList << QStringLiteral("gid=") + groupString; +- } ++ // Use user and group ID ++ map.insert(QStringLiteral("mh_use_ids"), useIds); + + // File mode + if (!fileModeString.isEmpty()) { +@@ -1069,19 +735,6 @@ bool Smb4KMounter::fillMountActionArgs(c + } + + // +- // File system port +- // +- if (options) { +- if (options->useFileSystemPort()) { +- argumentsList << QStringLiteral("port=") + QString::number(options->fileSystemPort()); +- } +- } else { +- if (Smb4KMountSettings::useRemoteFileSystemPort()) { +- argumentsList << QStringLiteral("port=") + QString::number(Smb4KMountSettings::remoteFileSystemPort()); +- } +- } +- +- // + // Write access + // + bool useWriteAccess = false; +@@ -1293,43 +946,9 @@ bool Smb4KMounter::fillMountActionArgs(c + } + + // +- // Mount options provided by the user +- // +- if (Smb4KMountSettings::useCustomCifsOptions()) { +- if (!Smb4KMountSettings::customCIFSOptions().isEmpty()) { +- // SECURITY: Only pass those arguments to mount.cifs that do not pose +- // a potential security risk and that have not already been defined. +- // +- // This is, among others, the proper fix to the security issue reported +- // by Heiner Markert (aka CVE-2014-2581). +- QStringList allowedArgs = allowedMountArguments(); +- QStringList list = Smb4KMountSettings::customCIFSOptions().split(QStringLiteral(","), Qt::SkipEmptyParts); +- QMutableStringListIterator it(list); +- +- while (it.hasNext()) { +- QString arg = it.next().section(QStringLiteral("="), 0, 0); +- +- if (!allowedArgs.contains(arg)) { +- it.remove(); +- } +- +- argumentsList += list; +- } +- } +- } +- +- // + // Insert the mount options into the map + // +- QStringList mh_options; +- mh_options << QStringLiteral("-o"); +- mh_options << argumentsList.join(QStringLiteral(",")); +- map.insert(QStringLiteral("mh_options"), mh_options); +- +- // +- // Insert the mountpoint into the map +- // +- map.insert(QStringLiteral("mh_mountpoint"), share->canonicalPath()); ++ map.insert(QStringLiteral("mh_options"), argumentsList); + + // + // Insert information about the share and its URL into the map +@@ -1342,21 +961,30 @@ bool Smb4KMounter::fillMountActionArgs(c + } + + // +- // Location of the Kerberos ticket ++ // Kerberos ticket and process id + // + // The path to the Kerberos ticket is stored - if it exists - in the + // KRB5CCNAME environment variable. By default, the ticket is located + // at /tmp/krb5cc_[uid]. So, if the environment variable does not exist, + // but the cache file is there, try to use it. + // ++ // Also, the id of the current process is needed, so that the mount helper ++ // can use the correct file descriptor. ++ // + if (QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KRB5CCNAME"))) { +- map.insert(QStringLiteral("mh_krb5ticket"), QProcessEnvironment::systemEnvironment().value(QStringLiteral("KRB5CCNAME"), QStringLiteral(""))); ++ QString krb5ccFile = QProcessEnvironment::systemEnvironment().value(QStringLiteral("KRB5CCNAME")).section(QStringLiteral(":"), 1, -1); ++ *fd = open(krb5ccFile.toLocal8Bit().data(), O_RDONLY); ++ if (*fd >= 0) { ++ map.insert(QStringLiteral("mh_krb5ticket"), QVariant::fromValue(QDBusUnixFileDescriptor(*fd))); ++ } + } else { +- QString ticket = QStringLiteral("/tmp/krb5cc_") + KUser(KUser::UseRealUserID).userId().toString(); +- +- if (QFile::exists(ticket)) { +- QString fileEntry = QStringLiteral("FILE:") + ticket; +- map.insert(QStringLiteral("mh_krb5ticket"), fileEntry); ++ QString krb5ccFile = QStringLiteral("/tmp/krb5cc_") + KUser(KUser::UseRealUserID).userId().toString(); ++ ++ if (QFile::exists(krb5ccFile)) { ++ *fd = open(krb5ccFile.toLocal8Bit().data(), O_RDONLY); ++ if (*fd >= 0) { ++ map.insert(QStringLiteral("mh_krb5ticket"), QVariant::fromValue(QDBusUnixFileDescriptor(*fd))); ++ } + } + } + +@@ -1366,16 +994,19 @@ bool Smb4KMounter::fillMountActionArgs(c + // + // FreeBSD and NetBSD arguments + // +-bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, QVariantMap &map) ++bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, int *fd, QVariantMap &map) + { ++ Q_UNUSED(fd); ++ ++ // ++ // Check the availability of the mount command. + // +- // Find the mount executable ++ // NOTE: We do not need to pass the command to the helper, though, since it ++ // will be invoke by it directly. + // + const QString mount = findMountExecutable(); + +- if (!mount.isEmpty()) { +- map.insert(QStringLiteral("mh_command"), mount); +- } else { ++ if (mount.isEmpty()) { + Smb4KNotification::commandNotFound(QStringLiteral("mount_smbfs")); + return false; + } +@@ -1411,35 +1042,21 @@ bool Smb4KMounter::fillMountActionArgs(c + } + + // +- // User Id ++ // User and group ID + // ++ bool useIds = false; ++ + if (options) { +- if (options->useUser()) { +- argumentsList << QStringLiteral("-u"); +- argumentsList << options->user().userId().toString(); +- } ++ useIds = options->useIds(); + } else { +- if (Smb4KMountSettings::useUserId()) { +- argumentsList << QStringLiteral("-u"); +- argumentsList << Smb4KMountSettings::userId(); +- } ++ useIds = Smb4KMountSettings::useIds(); + } + ++ map.insert(QStringLiteral("mh_use_ids"), useIds); ++ + // +- // Group Id ++ // Character sets + // +- if (options) { +- if (options->useGroup()) { +- argumentsList << QStringLiteral("-g"); +- argumentsList << options->group().groupId().toString(); +- } +- } else { +- if (Smb4KMountSettings::useGroupId()) { +- argumentsList << QStringLiteral("-g"); +- argumentsList << Smb4KMountSettings::groupId(); +- } +- } +- + if (Smb4KMountSettings::useCharacterSets()) { + // Client character set + QString clientCharset, serverCharset; +@@ -1517,11 +1134,6 @@ bool Smb4KMounter::fillMountActionArgs(c + map.insert(QStringLiteral("mh_options"), argumentsList); + + // +- // Insert the mountpoint into the map +- // +- map.insert(QStringLiteral("mh_mountpoint"), share->canonicalPath()); +- +- // + // Insert information about the share and its URL into the map + // + if (!share->isHomesShare()) { +@@ -1537,7 +1149,7 @@ bool Smb4KMounter::fillMountActionArgs(c + // + // Dummy + // +-bool Smb4KMounter::fillMountActionArgs(const SharePtr &, QVariantMap &) ++bool Smb4KMounter::fillMountActionArgs(const SharePtr &, int *, QVariantMap &) + { + qWarning() << "Smb4KMounter::fillMountActionArgs() is not implemented!"; + qWarning() << "Mounting under this operating system is not supported..."; +@@ -1552,7 +1164,10 @@ bool Smb4KMounter::fillMountActionArgs(c + bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &share, bool force, bool silent, QVariantMap &map) + { + // +- // The umount program ++ // Check the availability of the umount command. ++ // ++ // NOTE: We do not need to pass the command to the helper, though, since it ++ // will be invoke by it directly. + // + const QString umount = findUmountExecutable(); + +@@ -1573,7 +1188,6 @@ bool Smb4KMounter::fillUnmountActionArgs + // + // Insert data into the map + // +- map.insert(QStringLiteral("mh_command"), umount); + map.insert(QStringLiteral("mh_url"), share->url()); + + if (!share->isInaccessible() && Smb4KHardwareInterface::self()->isOnline()) { +@@ -1593,7 +1207,10 @@ bool Smb4KMounter::fillUnmountActionArgs + bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &share, bool force, bool silent, QVariantMap &map) + { + // +- // The umount program ++ // Check the availability of the umount command. ++ // ++ // NOTE: We do not need to pass the command to the helper, though, since it ++ // will be invoke by it directly. + // + const QString umount = findUmountExecutable(); + +@@ -1614,7 +1231,6 @@ bool Smb4KMounter::fillUnmountActionArgs + // + // Insert data into the map + // +- map.insert(QStringLiteral("mh_command"), umount); + map.insert(QStringLiteral("mh_url"), share->url()); + + if (!share->isInaccessible() && Smb4KHardwareInterface::self()->isOnline()) { +@@ -1648,7 +1264,7 @@ void Smb4KMounter::check(const SharePtr + share->setInaccessible(false); + + // Size information +- share->setFreeDiskSpace(d->storageInfo.bytesAvailable()); // Bytes available to the user, might be less that bytesFree() ++ share->setFreeDiskSpace(d->storageInfo.bytesAvailable()); // Bytes available to the user, might be less than bytesFree() + share->setTotalDiskSpace(d->storageInfo.bytesTotal()); + + // Get the owner an group, if possible. +@@ -1677,16 +1293,6 @@ void Smb4KMounter::check(const SharePtr + + void Smb4KMounter::slotStartJobs() + { +- // +- // Start the import of shares +- // +- if (Smb4KHardwareInterface::self()->isOnline()) { +- import(true); +- } +- +- // +- // Start the timer +- // + if (d->timerId == -1) { + d->timerId = startTimer(TIMEOUT); + } +@@ -1713,45 +1319,6 @@ void Smb4KMounter::slotAboutToQuit() + if (Smb4KMountSettings::unmountSharesOnExit()) { + unmountAllShares(true); + } +- +- // +- // Clean up the mount prefix. +- // +- KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions); +- +- QDir dir; +- dir.cd(Smb4KMountSettings::mountPrefix().path()); +- QStringList hostDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort); +- QStringList mountpoints; +- +- for (const QString &hostDir : std::as_const(hostDirs)) { +- dir.cd(hostDir); +- +- QStringList shareDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort); +- +- for (const QString &shareDir : std::as_const(shareDirs)) { +- dir.cd(shareDir); +- mountpoints << dir.absolutePath(); +- dir.cdUp(); +- } +- +- dir.cdUp(); +- } +- +- // Remove those mountpoints where a share is actually mounted. +- for (const QExplicitlySharedDataPointer &mountPoint : std::as_const(mountPoints)) { +- mountpoints.removeOne(mountPoint->mountPoint()); +- } +- +- // Remove the empty mountpoints. +- for (const QString &mp : std::as_const(mountpoints)) { +- dir.cd(mp); +- dir.rmdir(dir.canonicalPath()); +- +- if (dir.cdUp()) { +- dir.rmdir(dir.canonicalPath()); +- } +- } + } + + void Smb4KMounter::slotOnlineStateChanged(bool online) +@@ -1804,7 +1371,6 @@ void Smb4KMounter::slotActiveProfileChan + unmountAllShares(true); + + // Reset some variables. +- // Don't touch d->firstImportDone here, because that remains true + d->remountTimeout = 0; + d->remountAttempts = 0; + +@@ -1812,18 +1378,87 @@ void Smb4KMounter::slotActiveProfileChan + d->timerId = startTimer(TIMEOUT); + } + +-void Smb4KMounter::slotTriggerImport() +-{ +- QTimer::singleShot(2 * TIMEOUT, this, [&]() { +- import(true); +- }); +-} +- + void Smb4KMounter::slotConfigChanged() + { + if (d->detectAllShares != Smb4KMountSettings::detectAllShares()) { +- import(true); + d->detectAllShares = Smb4KMountSettings::detectAllShares(); ++ ++ QListIterator it(mountedSharesList()); ++ ++ while (it.hasNext()) { ++ SharePtr share = it.next(); ++ ++ if (share->isForeign() && !Smb4KMountSettings::detectAllShares()) { ++ if (removeMountedShare(share, true)) { ++ Q_EMIT unmounted(share); ++ share.clear(); ++ } ++ } ++ } ++ ++ // Add all shares that need to be added ++ QStringList mountPoints = Smb4KHardwareInterface::self()->allMountPoints(); ++ ++ for (const QString &mountPoint : std::as_const(mountPoints)) { ++ if (!findShareByPath(mountPoint)) { ++ KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions); ++ KMountPoint::Ptr mp = mountPoints.findByPath(mountPoint); ++ ++ SharePtr share = SharePtr(new Smb4KShare()); ++ share->setUrl(QUrl(mp->mountedFrom())); ++ share->setPath(mp->mountPoint()); ++ share->setMounted(true); ++ ++ QStringList mountOptions = mp->mountOptions(); ++ ++ for (const QString &option : std::as_const(mountOptions)) { ++ if (option.startsWith(QStringLiteral("domain=")) || option.startsWith(QStringLiteral("workgroup="))) { ++ share->setWorkgroupName(option.section(QStringLiteral("="), 1, 1).trimmed()); ++ } else if (option.startsWith(QStringLiteral("addr="))) { ++ share->setHostIpAddress(option.section(QStringLiteral("="), 1, 1).trimmed()); ++ } else if (option.startsWith(QStringLiteral("username=")) || option.startsWith(QStringLiteral("user="))) { ++ share->setUserName(option.section(QStringLiteral("="), 1, 1).trimmed()); ++ } ++ } ++ ++ // Work around empty usernames ++ if (share->userName().isEmpty()) { ++ share->setUserName(QStringLiteral("guest")); ++ } ++ ++ check(share); ++ ++ QFileInfo info(QDir::cleanPath(QStringLiteral("/var/run"))); ++ info.setCaching(false); ++ ++ if (info.isSymLink()) { ++ if (info.canonicalFilePath() == QDir::cleanPath(QStringLiteral("/run"))) { ++ info.setFile(info.canonicalFilePath()); ++ } else { ++ info.setFile(QDir::cleanPath(QStringLiteral("/run"))); ++ } ++ } ++ ++ QString rootPath = info.canonicalFilePath() + QDir::separator() + QStringLiteral("smb4k"); ++ ++ if (share->path().startsWith(rootPath)) { ++ share->setForeign(false); ++ } else if (share->path().startsWith(QDir::homePath()) ++ || (!share->isInaccessible() && share->canonicalPath().startsWith(QDir::home().canonicalPath()))) { ++ share->setForeign(false); ++ } else { ++ share->setForeign(true); ++ } ++ ++ if (!share->isForeign() || Smb4KMountSettings::detectAllShares()) { ++ if (addMountedShare(share)) { ++ Q_EMIT mounted(share); ++ } ++ } ++ } ++ } ++ ++ Q_EMIT mountedSharesListChanged(); + } + } + +@@ -1850,3 +1485,119 @@ void Smb4KMounter::slotCredentialsUpdate + } + } + } ++ ++void Smb4KMounter::slotShareMounted(const QString &mountPoint) ++{ ++ Q_ASSERT(!mountPoint.isEmpty()); ++ ++ KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions); ++ KMountPoint::Ptr mp = mountPoints.findByPath(mountPoint); ++ ++ SharePtr share = SharePtr(new Smb4KShare()); ++ share->setUrl(QUrl(mp->mountedFrom())); ++ share->setPath(mp->mountPoint()); ++ share->setMounted(true); ++ ++ QStringList mountOptions = mp->mountOptions(); ++ ++ for (const QString &option : std::as_const(mountOptions)) { ++ if (option.startsWith(QStringLiteral("domain=")) || option.startsWith(QStringLiteral("workgroup="))) { ++ share->setWorkgroupName(option.section(QStringLiteral("="), 1, 1).trimmed()); ++ } else if (option.startsWith(QStringLiteral("addr="))) { ++ share->setHostIpAddress(option.section(QStringLiteral("="), 1, 1).trimmed()); ++ } else if (option.startsWith(QStringLiteral("username=")) || option.startsWith(QStringLiteral("user="))) { ++ share->setUserName(option.section(QStringLiteral("="), 1, 1).trimmed()); ++ } ++ } ++ ++ // Work around empty usernames ++ if (share->userName().isEmpty()) { ++ share->setUserName(QStringLiteral("guest")); ++ } ++ ++ check(share); ++ ++ QFileInfo info(QDir::cleanPath(QStringLiteral("/var/run"))); ++ info.setCaching(false); ++ ++ if (info.isSymLink()) { ++ if (info.canonicalFilePath() == QDir::cleanPath(QStringLiteral("/run"))) { ++ info.setFile(info.canonicalFilePath()); ++ } else { ++ info.setFile(QDir::cleanPath(QStringLiteral("/run"))); ++ } ++ } ++ ++ QString rootPath = info.canonicalFilePath() + QDir::separator() + QStringLiteral("smb4k"); ++ ++ if (share->path().startsWith(rootPath)) { ++ share->setForeign(false); ++ } else if (share->path().startsWith(QDir::homePath()) || (!share->isInaccessible() && share->canonicalPath().startsWith(QDir::home().canonicalPath()))) { ++ share->setForeign(false); ++ } else { ++ share->setForeign(true); ++ } ++ ++ if (!share->isForeign() || Smb4KMountSettings::detectAllShares()) { ++ if (addMountedShare(share)) { ++ // Remove share from the remounts ++ QMutableListIterator s(d->remounts); ++ ++ while (s.hasNext()) { ++ SharePtr remount = s.next(); ++ ++ if (!share->isForeign() ++ && QString::compare(remount->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort), ++ share->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort), ++ Qt::CaseInsensitive) ++ == 0) { ++ Smb4KCustomSettingsManager::self()->removeRemount(remount); ++ s.remove(); ++ break; ++ } ++ } ++ ++ Q_EMIT mounted(share); ++ ++ if (d->longActionRunning) { ++ d->newlyMounted << share; ++ // Notification is handled in Smb4KMounter::mountShares() ++ } else { ++ if (Smb4KHardwareInterface::self()->initialImportDone()) { ++ Smb4KNotification::shareMounted(share); ++ } ++ ++ while (!d->newlyMounted.isEmpty()) { ++ d->newlyMounted.takeFirst().clear(); ++ } ++ } ++ ++ Q_EMIT mountedSharesListChanged(); ++ } ++ } ++} ++ ++void Smb4KMounter::slotShareUnmounted(const QString &mountPoint) ++{ ++ Q_ASSERT(!mountPoint.isEmpty()); ++ ++ SharePtr share = findShareByPath(mountPoint); ++ share->setMounted(false); ++ ++ if (removeMountedShare(share, d->longActionRunning)) { ++ Q_EMIT unmounted(share); ++ } ++ ++ if (d->longActionRunning) { ++ d->newlyUnmounted << share; ++ // Notification is handled in Smb4KMounter::unmountShares() ++ } else { ++ Smb4KNotification::shareUnmounted(share); ++ ++ while (!d->newlyUnmounted.isEmpty()) { ++ d->newlyUnmounted.takeFirst().clear(); ++ } ++ } ++ ++ Q_EMIT mountedSharesListChanged(); ++} +--- a/core/smb4kmounter.h ++++ b/core/smb4kmounter.h +@@ -217,12 +217,6 @@ protected Q_SLOTS: + void slotActiveProfileChanged(const QString &newProfile); + + /** +- * This slot is called whenever a network share is mounted or +- * unmounted. +- */ +- void slotTriggerImport(); +- +- /** + * This slot is called whenever the configuration changed. It is used + * to trigger the importing of shares when certain settings changed. + */ +@@ -236,21 +230,32 @@ protected Q_SLOTS: + */ + void slotCredentialsUpdated(const QUrl &url); + ++ /** ++ * This slot is called when a share was mounted. It takes the ++ * @p mountPoint as an argument. ++ * ++ * @param mountPoint The mountpoint of the share ++ */ ++ void slotShareMounted(const QString &mountPoint); ++ ++ /** ++ * This slot is called when a share was mounted. It takes the ++ * @p mountPoint as an argument. ++ * ++ * @param mountPoint The mountpoint of the share ++ */ ++ void slotShareUnmounted(const QString &mountPoint); ++ + private: + /** +- * Trigger the remounting of shares. If the parameter @p fill_list is ++ * Trigger the remounting of shares. If the parameter @p fillList is + * set to true, the internal list should be populated with the shares + * that are scheduled for a remount. + * +- * @param fill_list Fill the internal list with shares that are ++ * @param fillList Fill the internal list with shares that are + * to be remounted. + */ +- void triggerRemounts(bool fill_list); +- +- /** +- * Imports mounted shares. +- */ +- void import(bool checkInaccessible); ++ void triggerRemounts(bool fillList); + + /** + * Save all shares that need to be remounted. +@@ -260,7 +265,7 @@ private: + /** + * Fill the mount action arguments into a map. + */ +- bool fillMountActionArgs(const SharePtr &share, QVariantMap &mountArgs); ++ bool fillMountActionArgs(const SharePtr &share, int *fd, QVariantMap &mountArgs); + + /** + * Fill the unmount action arguments into a map. +--- a/core/smb4kmountsettings_bsd.kcfg ++++ b/core/smb4kmountsettings_bsd.kcfg +@@ -9,23 +9,10 @@ + + + +- +- +- This is the user ID (a number) that the files and directories of the mounted share will have. +- false +- +- +- This is the user ID (a number) that the files and directories of the mounted share will have. +- KUserId::currentUserId().toString() +- +- +- +- This is the group ID (a number) that the files and directories of the mounted share will have. +- false +- +- +- This is the group ID (a number) that the files and directories of the mounted share will have. +- KGroupId::currentGroupId().toString() ++ ++ ++ Set the user ID and group ID that the files and directories of the mounted share will have to those of the current user (you). ++ true + + + +@@ -232,16 +219,6 @@ + + default_codepage + +- +- +- This is the prefix where Smb4K will create the mount points and mount the remote shares. +- QUrl::fromLocalFile(QDir::homePath()+QStringLiteral("/smb4k/")) +- +- +- +- All names of the subdirectories created by Smb4K below the mount prefix will be lowercase. +- false +- + + + Unmount all shares that belong to you when the program exits. Shares that are owned by other users are ignored. +@@ -266,11 +243,6 @@ + 30 + 5 + +- +- +- Allow the unmounting of shares that were mounted by other users. Please think before you enable this option. +- false +- + + + Force the unmounting of inaccessible shares (Linux only). In case a share is inaccessible, a lazy unmount is performed. Before the actual unmount is performed, a warning dialog is shown asking to approve the unmount. +--- a/core/smb4kmountsettings_linux.kcfg ++++ b/core/smb4kmountsettings_linux.kcfg +@@ -14,24 +14,11 @@ + Most versions of Samba support the CIFS Unix or POSIX extensions. For these servers, some options are not needed, because the right values are negotiated during the mount process. For other servers, you might want to uncheck this option, so that predefined values can be passed to the server. Please note that if your computer is located in a Windows dominated network neighborhood with only a few Samba servers, you can safely uncheck this option and define custom options for the Samba servers. + true + +- +- +- This is the user ID (a number) that the files and directories of the mounted share will have. If you are using the CIFS filesystem under Linux and the remote server supports the CIFS Unix Extensions, this setting will be ignored unless the assignment of the user ID is forced. ++ ++ ++ Set the user ID and group ID that the files and directories of the mounted share will have to those of the current user (you). + true + +- +- This is the user ID (a number) that the files and directories of the mounted share will have. If you are using the CIFS filesystem under Linux and the remote server supports the CIFS Unix Extensions, this setting will be ignored unless the assignment of the user ID is forced. +- KUserId::currentUserId().toString() +- +- +- +- This is the group ID (a number) that the files and directories of the mounted share will have. If you are using the CIFS filesystem under Linux and the remote server supports the CIFS Unix Extensions, this setting will be ignored unless the assignment of the group ID is forced. +- true +- +- +- This is the group ID (a number) that the files and directories of the mounted share will have. If you are using the CIFS filesystem under Linux and the remote server supports the CIFS Unix Extensions, this setting will be ignored unless the assignment of the group ID is forced. +- KGroupId::currentGroupId().toString() +- + + + This is the file mode that will be used for creating files. It must be defined in octal. In case the CIFS file system is used, this setting only takes effect if the server does not support the CIFS Unix Extensions. +@@ -50,17 +37,6 @@ + This is the directory mode that will be used for creating directories. It must be defined in octal. In case the CIFS file system is used, this setting only takes effect if the server does not support the CIFS Unix Extensions. + 0755 + +- +- +- This is the port that is exclusively used to mount shares from remote servers. The default value is 445 (CIFS file system). +- false +- +- +- This is the port that is exclusively used to mount shares from remote servers. The default value is 445 (CIFS file system). +- 1 +- 65535 +- 445 +- + + + This is the character set that is used by the client side (i.e. your side) either to convert local path names to and from Unicode (CIFS, Linux) or for codepage to charset translations (SMBFS, FreeBSD). If you keep the default setting, Smb4K will try to automatically determine the charset by looking up the "unix charset" option in the smb.conf. +@@ -286,25 +262,6 @@ + + Ntlmssp + +- +- +- Here you can enter advanced options for the CIFS file system in a comma-separated list (refer to the manual page of mount.cifs to learn more). The list will be added AS IS to the "-o" argument of mount.cifs. Please do not enter options that have already been defined in the configuration dialog. +- false +- +- +- Here you can enter advanced options for the CIFS file system in a comma-separated list (refer to the manual page of mount.cifs to learn more). The list will be added AS IS to the "-o" argument of mount.cifs. Please do not enter options that have already been defined in the configuration dialog. +- +- +- +- +- This is the prefix where Smb4K will create the mount points and mount the remote shares. +- QUrl::fromLocalFile(QDir::homePath()+QStringLiteral("/smb4k/")) +- +- +- +- All names of the subdirectories created by Smb4K below the mount prefix will be lowercase. +- false +- + + + Unmount all shares that belong to you when the program exits. Shares that are owned by other users are ignored. +@@ -329,11 +286,6 @@ + 30 + 5 + +- +- +- Allow the unmounting of shares that were mounted by other users. Please think before you enable this option. +- false +- + + + Force the unmounting of inaccessible shares (Linux only). In case a share is inaccessible, a lazy unmount is performed. Before the actual unmount is performed, a warning dialog is shown asking to approve the unmount. +--- a/core/smb4knotification.cpp ++++ b/core/smb4knotification.cpp +@@ -26,10 +26,22 @@ + + using namespace KAuth; + +-class Smb4KNotificationPrivate ++class Smb4KNotificationPrivate : public QObject + { ++ Q_OBJECT ++ + public: + QString componentName; ++ QString path; ++ void open() ++ { ++ if (!path.isEmpty()) { ++ KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(path), QStringLiteral("inode/directory")); ++ job->setFollowRedirections(false); ++ job->setAutoDelete(true); ++ job->start(); ++ } ++ } + }; + + Q_GLOBAL_STATIC(Smb4KNotificationPrivate, p); +@@ -48,36 +60,25 @@ void Smb4KNotification::shareMounted(con + Q_ASSERT(share); + + if (share) { +- QEventLoop loop; +- + KNotification *notification = new KNotification(QStringLiteral("shareMounted"), KNotification::CloseOnTimeout); + + if (!p->componentName.isEmpty()) { + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The share %1 has been mounted to %2.

", share->displayString(), share->path())); ++ notification->setText(i18n("The share %1 has been mounted to %2.", share->displayString(), share->path())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("folder-network"), + KIconLoader::NoGroup, + 0, + KIconLoader::DefaultState, + QStringList(QStringLiteral("emblem-mounted")))); + +- auto open = [&]() { +- KIO::OpenUrlJob *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(share->path()), QStringLiteral("inode/directory")); +- job->setFollowRedirections(false); +- job->setAutoDelete(true); +- job->start(); +- }; ++ p->path = share->path(); + + auto *openAction = notification->addAction(i18nc("Open the contents of the share with the file manager", "Open")); +- QObject::connect(openAction, &KNotificationAction::activated, open); +- +- QObject::connect(notification, &KNotification::closed, &loop, &QEventLoop::quit); ++ QObject::connect(openAction, &KNotificationAction::activated, p, &Smb4KNotificationPrivate::open); + + notification->sendEvent(); +- +- loop.exec(); + } + } + +@@ -92,7 +93,7 @@ void Smb4KNotification::shareUnmounted(c + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The share %1 has been unmounted from %2.

", share->displayString(), share->path())); ++ notification->setText(i18n("The share %1 has been unmounted from %2.", share->displayString(), share->path())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("folder-network"), + KIconLoader::NoGroup, + 0, +@@ -110,7 +111,7 @@ void Smb4KNotification::sharesMounted(in + notification->setComponentName(p->componentName); + } + +- notification->setText(i18np("

%1 share has been mounted.

", "

%1 shares have been mounted.

", number)); ++ notification->setText(i18np("%1 share has been mounted.", "%1 shares have been mounted.", number)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("folder-network"), + KIconLoader::NoGroup, + 0, +@@ -127,7 +128,7 @@ void Smb4KNotification::sharesUnmounted( + notification->setComponentName(p->componentName); + } + +- notification->setText(i18np("

%1 share has been unmounted.

", "

%1 shares have been unmounted.

", number)); ++ notification->setText(i18np("%1 share has been unmounted.", "%1 shares have been unmounted.", number)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("folder-network"), + KIconLoader::NoGroup, + 0, +@@ -145,8 +146,7 @@ void Smb4KNotification::migratingLoginCr + } + + notification->setText( +- i18n("The way Smb4K stores the login credentials has changed. They will now be migrated. " +- "This change is incompatible with earlier versions of Smb4K.")); ++ i18n("The way Smb4K stores the login credentials has changed. They will now be migrated. This change is incompatible with earlier versions of Smb4K.")); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-information"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -163,10 +163,7 @@ void Smb4KNotification::mimetypeNotSuppo + notification->setComponentName(p->componentName); + } + +- notification->setText( +- i18n("

The mimetype %1 is not supported for printing. " +- "Please convert the file to PDF or Postscript and try again.

", +- mimetype)); ++ notification->setText(i18n("The mimetype %1 is not supported for printing. Please convert the file to PDF or Postscript and try again.", mimetype)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -182,7 +179,7 @@ void Smb4KNotification::bookmarkExists(c + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The bookmark for share %1 already exists and will be skipped.

", bookmark->displayString())); ++ notification->setText(i18n("The bookmark for share %1 already exists and will be skipped.", bookmark->displayString())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -199,11 +196,9 @@ void Smb4KNotification::bookmarkLabelInU + notification->setComponentName(p->componentName); + } + +- notification->setText( +- i18n("

The label %1 of the bookmark for the share %2 " +- "is already being used and will automatically be renamed.

", +- bookmark->label(), +- bookmark->displayString())); ++ notification->setText(i18n("The label %1 of the bookmark for the share %2 is already being used and will automatically be renamed.", ++ bookmark->label(), ++ bookmark->displayString())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -220,9 +215,9 @@ void Smb4KNotification::mountingFailed(c + QString text; + + if (!err_msg.isEmpty()) { +- text = i18n("

Mounting the share %1 failed:

%2

", share->displayString(), err_msg); ++ text = i18n("Mounting the share %1 failed: %2", share->displayString(), err_msg); + } else { +- text = i18n("

Mounting the share %1 failed.

", share->displayString()); ++ text = i18n("Mounting the share %1 failed.", share->displayString()); + } + + KNotification *notification = new KNotification(QStringLiteral("mountingFailed"), KNotification::CloseOnTimeout); +@@ -245,9 +240,9 @@ void Smb4KNotification::unmountingFailed + QString text; + + if (!err_msg.isEmpty()) { +- text = i18n("

Unmounting the share %1 from %2 failed:

%3

", share->displayString(), share->path(), err_msg); ++ text = i18n("Unmounting the share %1 from %2 failed: %3", share->displayString(), share->path(), err_msg); + } else { +- text = i18n("

Unmounting the share %1 from %2 failed.

", share->displayString(), share->path()); ++ text = i18n("Unmounting the share %1 from %2 failed.", share->displayString(), share->path()); + } + + KNotification *notification = new KNotification(QStringLiteral("unmountingFailed"), KNotification::CloseOnTimeout); +@@ -273,12 +268,10 @@ void Smb4KNotification::unmountingNotAll + notification->setComponentName(p->componentName); + } + +- notification->setText( +- i18n("

You are not allowed to unmount the share %1 from %2. " +- "It is owned by the user %3.

", +- share->displayString(), +- share->path(), +- share->user().loginName())); ++ notification->setText(i18n("You are not allowed to unmount the share %1 from %2. It is owned by the user %3.", ++ share->displayString(), ++ share->path(), ++ share->user().loginName())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -289,9 +282,9 @@ void Smb4KNotification::synchronizationF + QString text; + + if (!err_msg.isEmpty()) { +- text = i18n("

Synchronizing %1 with %2 failed:

%3

", dest.path(), src.path(), err_msg); ++ text = i18n("Synchronizing %1 with %2 failed: %3", dest.path(), src.path(), err_msg); + } else { +- text = i18n("

Synchronizing %1 with %2 failed.

", dest.path(), src.path()); ++ text = i18n("Synchronizing %1 with %2 failed.", dest.path(), src.path()); + } + + KNotification *notification = new KNotification(QStringLiteral("synchronizationFailed"), KNotification::CloseOnTimeout); +@@ -313,7 +306,7 @@ void Smb4KNotification::commandNotFound( + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The command %1 could not be found. Please check your installation.

", command)); ++ notification->setText(i18n("The command %1 could not be found. Please check your installation.", command)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -329,7 +322,7 @@ void Smb4KNotification::cannotBookmarkPr + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The share %1 is a printer and cannot be bookmarked.

", share->displayString())); ++ notification->setText(i18n("The share %1 is a printer and cannot be bookmarked.", share->displayString())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -343,7 +336,7 @@ void Smb4KNotification::fileNotFound(con + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The file %1 could not be found.

", fileName)); ++ notification->setText(i18n("The file %1 could not be found.", fileName)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -353,9 +346,9 @@ void Smb4KNotification::openingFileFaile + QString text; + + if (!file.errorString().isEmpty()) { +- text = i18n("

Opening the file %1 failed:

%2

", file.fileName(), file.errorString()); ++ text = i18n("Opening the file %1 failed: %2", file.fileName(), file.errorString()); + } else { +- text = i18n("

Opening the file %1 failed.

", file.fileName()); ++ text = i18n("Opening the file %1 failed.", file.fileName()); + } + + KNotification *notification = new KNotification(QStringLiteral("openingFileFailed"), KNotification::CloseOnTimeout); +@@ -374,12 +367,12 @@ void Smb4KNotification::readingFileFaile + QString text; + + if (!err_msg.isEmpty()) { +- text = i18n("

Reading from file %1 failed:

%2

", file.fileName(), err_msg); ++ text = i18n("Reading from file %1 failed: %2", file.fileName(), err_msg); + } else { + if (!file.errorString().isEmpty()) { +- text = i18n("

Reading from file %1 failed:

%2

", file.fileName(), file.errorString()); ++ text = i18n("Reading from file %1 failed: %2", file.fileName(), file.errorString()); + } else { +- text = i18n("

Reading from file %1 failed.

", file.fileName()); ++ text = i18n("Reading from file %1 failed.", file.fileName()); + } + } + +@@ -402,7 +395,7 @@ void Smb4KNotification::mkdirFailed(cons + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The following directory could not be created:

%1

", dir.absolutePath())); ++ notification->setText(i18n("The following directory could not be created: %1", dir.absolutePath())); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -413,28 +406,28 @@ void Smb4KNotification::processError(QPr + + switch (error) { + case QProcess::FailedToStart: { +- text = i18n("

The process failed to start (error code: %1).

", error); ++ text = i18n("The process failed to start (error code: %1).", error); + break; + } + case QProcess::Crashed: { +- text = i18n("

The process crashed (error code: %1).

", error); ++ text = i18n("The process crashed (error code: %1).", error); + break; + } + case QProcess::Timedout: { +- text = i18n("

The process timed out (error code: %1).

", error); ++ text = i18n("The process timed out (error code: %1).", error); + break; + } + case QProcess::WriteError: { +- text = i18n("

Could not write to the process (error code: %1).

", error); ++ text = i18n("Could not write to the process (error code: %1).", error); + break; + } + case QProcess::ReadError: { +- text = i18n("

Could not read from the process (error code: %1).

", error); ++ text = i18n("Could not read from the process (error code: %1).", error); + break; + } + case QProcess::UnknownError: + default: { +- text = i18n("

The process reported an unknown error.

"); ++ text = i18n("The process reported an unknown error."); + break; + } + } +@@ -450,45 +443,45 @@ void Smb4KNotification::processError(QPr + notification->sendEvent(); + } + +-void Smb4KNotification::actionFailed(int errorCode) ++void Smb4KNotification::actionFailed(int errorCode, const QString &errorMessage) + { +- QString text, errorMessage; ++ QString text, errorCodeString; + + switch (errorCode) { + case ActionReply::NoResponderError: { +- errorMessage = QStringLiteral("NoResponderError"); ++ errorCodeString = QStringLiteral("NoResponderError"); + break; + } + case ActionReply::NoSuchActionError: { +- errorMessage = QStringLiteral("NoSuchActionError"); ++ errorCodeString = QStringLiteral("NoSuchActionError"); + break; + } + case ActionReply::InvalidActionError: { +- errorMessage = QStringLiteral("InvalidActionError"); ++ errorCodeString = QStringLiteral("InvalidActionError"); + break; + } + case ActionReply::AuthorizationDeniedError: { +- errorMessage = QStringLiteral("AuthorizationDeniedError"); ++ errorCodeString = QStringLiteral("AuthorizationDeniedError"); + break; + } + case ActionReply::UserCancelledError: { +- errorMessage = QStringLiteral("UserCancelledError"); ++ errorCodeString = QStringLiteral("UserCancelledError"); + break; + } + case ActionReply::HelperBusyError: { +- errorMessage = QStringLiteral("HelperBusyError"); ++ errorCodeString = QStringLiteral("HelperBusyError"); + break; + } + case ActionReply::AlreadyStartedError: { +- errorMessage = QStringLiteral("AlreadyStartedError"); ++ errorCodeString = QStringLiteral("AlreadyStartedError"); + break; + } + case ActionReply::DBusError: { +- errorMessage = QStringLiteral("DBusError"); ++ errorCodeString = QStringLiteral("DBusError"); + break; + } + case ActionReply::BackendError: { +- errorMessage = QStringLiteral("BackendError"); ++ errorCodeString = QStringLiteral("BackendError"); + break; + } + default: { +@@ -496,11 +489,9 @@ void Smb4KNotification::actionFailed(int + } + } + +- if (!errorMessage.isEmpty()) { +- text = i18n("

Executing an action with root privileges failed (error code: %1).

", errorMessage); +- } else { +- text = i18n("

Executing an action with root privileges failed.

"); +- } ++ text = i18n("Executing an action with root privileges failed%1%2", ++ !errorCodeString.isEmpty() ? QStringLiteral(" (") + errorCodeString + QStringLiteral(")") : QString(), ++ !errorMessage.isEmpty() ? QStringLiteral(": ") + errorMessage : QStringLiteral(".")); + + KNotification *notification = new KNotification(QStringLiteral("actionFailed"), KNotification::CloseOnTimeout); + +@@ -521,7 +512,7 @@ void Smb4KNotification::invalidURLPassed + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The URL that was passed is invalid.

")); ++ notification->setText(i18n("The URL that was passed is invalid.")); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -534,7 +525,7 @@ void Smb4KNotification::networkCommunica + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

The network communication failed with the following error message: %1

", errorMessage)); ++ notification->setText(i18n("The network communication failed with the following error message: %1", errorMessage)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -547,7 +538,7 @@ void Smb4KNotification::zeroconfError(co + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

An error with the Zeroconf service occurred: %1

", errorMessage)); ++ notification->setText(i18n("An error with the Zeroconf service occurred: %1", errorMessage)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } +@@ -560,7 +551,9 @@ void Smb4KNotification::keychainError(co + notification->setComponentName(p->componentName); + } + +- notification->setText(i18n("

An error occurred while reading the login credentials from the secure storage:\n%1

", errorMessage)); ++ notification->setText(i18n("An error occurred while reading the login credentials from the secure storage: %1", errorMessage)); + notification->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("dialog-error"), KIconLoader::NoGroup, 0, KIconLoader::DefaultState)); + notification->sendEvent(); + } ++ ++#include "smb4knotification.moc" +--- a/core/smb4knotification.h ++++ b/core/smb4knotification.h +@@ -186,8 +186,10 @@ SMB4KCORE_EXPORT void processError(QProc + * this function if available. + * + * @param errorCode The error code ++ * ++ * @param errorMessage The error message that was passed + */ +-SMB4KCORE_EXPORT void actionFailed(int errorCode = -1); ++SMB4KCORE_EXPORT void actionFailed(int errorCode = -1, const QString &errorMessage = QString()); + + /** + * This error message is shown when an invalid URL was passed to some core diff -Nru smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-3.patch smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-3.patch --- smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ smb4k-4.0.0/debian/patches/CVE-2025-66002_CVE-2025-66003-3.patch 2025-12-27 09:40:36.000000000 +0000 @@ -0,0 +1,1417 @@ +From: Alexander Reinholdt +Date: Wed, 10 Dec 2025 11:01:18 +0100 +Subject: Merge security bug fix from master. +Origin: https://invent.kde.org/network/smb4k/-/commit/55c535cbab6843c88cac033a21e43206b5eefbd0 +Bug-Debian: https://bugs.debian.org/1122381 + +Adjust the user interface classes according to the changes made to +the KAuth mounthelper for fixing the security issues CVE-2025-66002 +and CVE-2025-66003: + +- Remove obsolete settings from the configuration dialog: mount prefix, + unmounting of foreign shares, lower case subdirectories, unmounting + of foreign shares, filesystem port, user and group, additional CIFS + mount options. +- Remove obsolete settings from the custom settings dialog: + filesystem port, user and group. +- Introduce setting to set the UID and GID of a mount to the one of + the user to the configuration and the custom settings dialog. The + IDs are not editable. +- Restrict the input of the file and directory mode to 4 digits and + validate it. +- Remove the possibility to unmount foreign shares through the + network neighborhood browser, the shares view and the shares menu. +--- + smb4k/smb4kconfigpagemounting.cpp | 378 +++++----------------- + smb4k/smb4kconfigpagemounting.h | 24 +- + smb4k/smb4kcustomsettingseditorwidget.cpp | 323 ++++++++---------- + smb4k/smb4kcustomsettingseditorwidget.h | 25 +- + smb4k/smb4ksharesmenu.cpp | 6 +- + smb4k/smb4ksharesviewdockwidget.cpp | 44 +-- + 6 files changed, 256 insertions(+), 544 deletions(-) + +diff --git a/smb4k/smb4kconfigpagemounting.cpp b/smb4k/smb4kconfigpagemounting.cpp +index 7ae0339de3a4..fba25c0c6e93 100644 +--- a/smb4k/smb4kconfigpagemounting.cpp ++++ b/smb4k/smb4kconfigpagemounting.cpp +@@ -1,7 +1,7 @@ + /* + The configuration page for the mount options + +- SPDX-FileCopyrightText: 2015-2023 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2015-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -32,6 +32,32 @@ + + using namespace Smb4KGlobal; + ++class ModeValidator : public QValidator ++{ ++public: ++ ModeValidator(QObject *parent = nullptr) ++ : QValidator(parent) ++ { ++ } ++ ~ModeValidator() ++ { ++ } ++ QValidator::State validate(QString &input, int &pos) const override ++ { ++ Q_UNUSED(pos); ++ if (input.trimmed().size() == 4) { ++ QChar ch = input.trimmed().at(0); ++ ++ if (ch.isDigit() && ch.toLatin1() != '0') { ++ return QValidator::Invalid; ++ } else if (ch.isDigit() && ch.toLatin1() == '0') { ++ return QValidator::Acceptable; ++ } ++ } ++ return QValidator::Intermediate; ++ } ++}; ++ + Smb4KConfigPageMounting::Smb4KConfigPageMounting(QWidget *parent) + : QTabWidget(parent) + { +@@ -44,11 +70,6 @@ Smb4KConfigPageMounting::~Smb4KConfigPageMounting() + + bool Smb4KConfigPageMounting::checkSettings() + { +- if (!m_mountPrefix->url().isValid()) { +- m_mountPrefix->setFocus(); +- return false; +- } +- + if (m_useFileMode->isChecked() && m_fileMode->text().trimmed().isEmpty()) { + m_fileMode->setFocus(); + return false; +@@ -74,28 +95,6 @@ void Smb4KConfigPageMounting::setupWidget() + QWidget *basicTab = new QWidget(this); + QVBoxLayout *basicTabLayout = new QVBoxLayout(basicTab); + +- // +- // Directories +- // +- QGroupBox *directoryBox = new QGroupBox(i18n("Directories"), basicTab); +- QGridLayout *directoryBoxLayout = new QGridLayout(directoryBox); +- +- QLabel *mountPrefixLabel = new QLabel(Smb4KMountSettings::self()->mountPrefixItem()->label(), directoryBox); +- m_mountPrefix = new KUrlRequester(directoryBox); +- m_mountPrefix->setMode(KFile::Directory | KFile::LocalOnly); +- m_mountPrefix->setObjectName(QStringLiteral("kcfg_MountPrefix")); +- +- mountPrefixLabel->setBuddy(m_mountPrefix); +- +- QCheckBox *lowercaseSubdirs = new QCheckBox(Smb4KMountSettings::self()->forceLowerCaseSubdirsItem()->label(), directoryBox); +- lowercaseSubdirs->setObjectName(QStringLiteral("kcfg_ForceLowerCaseSubdirs")); +- +- directoryBoxLayout->addWidget(mountPrefixLabel, 0, 0); +- directoryBoxLayout->addWidget(m_mountPrefix, 0, 1); +- directoryBoxLayout->addWidget(lowercaseSubdirs, 1, 0, 1, 2); +- +- basicTabLayout->addWidget(directoryBox, 0); +- + // + // Behavior + // +@@ -135,9 +134,6 @@ void Smb4KConfigPageMounting::setupWidget() + QCheckBox *unmountAllShares = new QCheckBox(Smb4KMountSettings::self()->unmountSharesOnExitItem()->label(), behaviorBox); + unmountAllShares->setObjectName(QStringLiteral("kcfg_UnmountSharesOnExit")); + +- QCheckBox *unmountForeignShares = new QCheckBox(Smb4KMountSettings::self()->unmountForeignSharesItem()->label(), behaviorBox); +- unmountForeignShares->setObjectName(QStringLiteral("kcfg_UnmountForeignShares")); +- + QCheckBox *unmountInaccessibleShares = new QCheckBox(Smb4KMountSettings::self()->forceUnmountInaccessibleItem()->label(), behaviorBox); + unmountInaccessibleShares->setObjectName(QStringLiteral("kcfg_ForceUnmountInaccessible")); + +@@ -148,7 +144,6 @@ void Smb4KConfigPageMounting::setupWidget() + behaviorBoxLayout->addWidget(m_remountSettingsWidget); + behaviorBoxLayout->addWidget(unmountAllShares); + behaviorBoxLayout->addWidget(unmountInaccessibleShares); +- behaviorBoxLayout->addWidget(unmountForeignShares); + behaviorBoxLayout->addWidget(detectAllShares); + + basicTabLayout->addWidget(behaviorBox, 0); +@@ -200,16 +195,6 @@ void Smb4KConfigPageMounting::setupWidget() + commonOptionsLayout->addWidget(useCharacterSet, 1, 0); + commonOptionsLayout->addWidget(characterSet, 1, 1); + +- // Remote filesystem port +- QCheckBox *useFilesystemPort = new QCheckBox(Smb4KMountSettings::self()->useRemoteFileSystemPortItem()->label(), commonOptions); +- useFilesystemPort->setObjectName(QStringLiteral("kcfg_UseRemoteFileSystemPort")); +- +- QSpinBox *filesystemPort = new QSpinBox(commonOptions); +- filesystemPort->setObjectName(QStringLiteral("kcfg_RemoteFileSystemPort")); +- +- commonOptionsLayout->addWidget(useFilesystemPort, 2, 0); +- commonOptionsLayout->addWidget(filesystemPort, 2, 1); +- + commonTabLayout->addWidget(commonOptions, 0); + + // +@@ -229,77 +214,27 @@ void Smb4KConfigPageMounting::setupWidget() + QGridLayout *singleCifsSettingsWidgetLayout = new QGridLayout(m_singleCifsExtensionsSettingsWidget); + singleCifsSettingsWidgetLayout->setContentsMargins(0, 0, 0, 0); + +- // User information +- QCheckBox *useUserId = new QCheckBox(Smb4KMountSettings::self()->useUserIdItem()->label(), m_singleCifsExtensionsSettingsWidget); +- useUserId->setObjectName(QStringLiteral("kcfg_UseUserId")); ++ // Usage of user and group ID ++ QCheckBox *useIds = new QCheckBox(Smb4KMountSettings::self()->useIdsItem()->label(), m_singleCifsExtensionsSettingsWidget); ++ useIds->setObjectName(QStringLiteral("kcfg_UseIds")); + +- QWidget *userIdInputWidget = new QWidget(m_singleCifsExtensionsSettingsWidget); +- userIdInputWidget->setObjectName(QStringLiteral("UserIdInputWidget")); ++ QLabel *userIdLabel = new QLabel(i18n("User ID:"), m_singleCifsExtensionsSettingsWidget); ++ userIdLabel->setIndent(25); ++ KLineEdit *userId = new KLineEdit(KUser(KUser::UseRealUserID).userId().toString(), m_singleCifsExtensionsSettingsWidget); ++ userId->setAlignment(Qt::AlignRight); ++ userId->setReadOnly(true); + +- QGridLayout *userIdInputWidgetLayout = new QGridLayout(userIdInputWidget); +- userIdInputWidgetLayout->setContentsMargins(0, 0, 0, 0); ++ QLabel *groupIdLabel = new QLabel(i18n("Group ID:"), m_singleCifsExtensionsSettingsWidget); ++ groupIdLabel->setIndent(25); ++ KLineEdit *groupId = new KLineEdit(KUser(KUser::UseRealUserID).groupId().toString(), m_singleCifsExtensionsSettingsWidget); ++ groupId->setAlignment(Qt::AlignRight); ++ groupId->setReadOnly(true); + +- m_userId = new KLineEdit(userIdInputWidget); +- m_userId->setObjectName(QStringLiteral("kcfg_UserId")); +- m_userId->setAlignment(Qt::AlignRight); +- m_userId->setReadOnly(true); +- +- QToolButton *userChooser = new QToolButton(userIdInputWidget); +- userChooser->setIcon(KDE::icon(QStringLiteral("edit-find-user"))); +- userChooser->setToolTip(i18n("Choose a different user")); +- userChooser->setPopupMode(QToolButton::InstantPopup); +- +- QMenu *userMenu = new QMenu(userChooser); +- userChooser->setMenu(userMenu); +- +- QList allUsers = KUser::allUsers(); +- +- for (const KUser &u : std::as_const(allUsers)) { +- QAction *userAction = userMenu->addAction(u.loginName() + QStringLiteral(" (") + u.userId().toString() + QStringLiteral(")")); +- userAction->setData(u.userId().nativeId()); +- } +- +- userIdInputWidgetLayout->addWidget(m_userId, 0, 0); +- userIdInputWidgetLayout->addWidget(userChooser, 0, 1, Qt::AlignCenter); +- +- singleCifsSettingsWidgetLayout->addWidget(useUserId, 0, 0); +- singleCifsSettingsWidgetLayout->addWidget(userIdInputWidget, 0, 1); +- +- // Group information +- QCheckBox *useGroupId = new QCheckBox(Smb4KMountSettings::self()->useGroupIdItem()->label(), m_singleCifsExtensionsSettingsWidget); +- useGroupId->setObjectName(QStringLiteral("kcfg_UseGroupId")); +- +- QWidget *groupIdInputWidget = new QWidget(m_singleCifsExtensionsSettingsWidget); +- groupIdInputWidget->setObjectName(QStringLiteral("GroupIdInputWidget")); +- +- QGridLayout *groupIdInputWidgetLayout = new QGridLayout(groupIdInputWidget); +- groupIdInputWidgetLayout->setContentsMargins(0, 0, 0, 0); +- +- m_groupId = new KLineEdit(groupIdInputWidget); +- m_groupId->setObjectName(QStringLiteral("kcfg_GroupId")); +- m_groupId->setAlignment(Qt::AlignRight); +- m_groupId->setReadOnly(true); +- +- QToolButton *groupChooser = new QToolButton(groupIdInputWidget); +- groupChooser->setIcon(KDE::icon(QStringLiteral("edit-find-user"))); +- groupChooser->setToolTip(i18n("Choose a different group")); +- groupChooser->setPopupMode(QToolButton::InstantPopup); +- +- QMenu *groupMenu = new QMenu(groupChooser); +- groupChooser->setMenu(groupMenu); +- +- QList groupList = KUserGroup::allGroups(); +- +- for (const KUserGroup &g : std::as_const(groupList)) { +- QAction *groupAction = groupMenu->addAction(g.name() + QStringLiteral(" (") + g.groupId().toString() + QStringLiteral(")")); +- groupAction->setData(g.groupId().nativeId()); +- } +- +- groupIdInputWidgetLayout->addWidget(m_groupId, 0, 0); +- groupIdInputWidgetLayout->addWidget(groupChooser, 0, 1, Qt::AlignCenter); +- +- singleCifsSettingsWidgetLayout->addWidget(useGroupId, 1, 0); +- singleCifsSettingsWidgetLayout->addWidget(groupIdInputWidget, 1, 1); ++ singleCifsSettingsWidgetLayout->addWidget(useIds, 0, 0, 1, 2); ++ singleCifsSettingsWidgetLayout->addWidget(userIdLabel, 1, 0); ++ singleCifsSettingsWidgetLayout->addWidget(userId, 1, 1); ++ singleCifsSettingsWidgetLayout->addWidget(groupIdLabel, 2, 0); ++ singleCifsSettingsWidgetLayout->addWidget(groupId, 2, 1); + + // File mask + m_useFileMode = new QCheckBox(Smb4KMountSettings::self()->useFileModeItem()->label(), m_singleCifsExtensionsSettingsWidget); +@@ -309,9 +244,11 @@ void Smb4KConfigPageMounting::setupWidget() + m_fileMode->setObjectName(QStringLiteral("kcfg_FileMode")); + m_fileMode->setClearButtonEnabled(true); + m_fileMode->setAlignment(Qt::AlignRight); ++ m_fileMode->setInputMask(QStringLiteral("0999")); ++ m_fileMode->setValidator(new ModeValidator(m_fileMode)); + +- singleCifsSettingsWidgetLayout->addWidget(m_useFileMode, 2, 0); +- singleCifsSettingsWidgetLayout->addWidget(m_fileMode, 2, 1); ++ singleCifsSettingsWidgetLayout->addWidget(m_useFileMode, 3, 0); ++ singleCifsSettingsWidgetLayout->addWidget(m_fileMode, 3, 1); + + // Directory mask + m_useDirectoryMode = new QCheckBox(Smb4KMountSettings::self()->useDirectoryModeItem()->label(), m_singleCifsExtensionsSettingsWidget); +@@ -321,9 +258,11 @@ void Smb4KConfigPageMounting::setupWidget() + m_directoryMode->setObjectName(QStringLiteral("kcfg_DirectoryMode")); + m_directoryMode->setClearButtonEnabled(true); + m_directoryMode->setAlignment(Qt::AlignRight); ++ m_directoryMode->setInputMask(QStringLiteral("0999")); ++ m_directoryMode->setValidator(new ModeValidator(m_directoryMode)); + +- singleCifsSettingsWidgetLayout->addWidget(m_useDirectoryMode, 3, 0); +- singleCifsSettingsWidgetLayout->addWidget(m_directoryMode, 3, 1); ++ singleCifsSettingsWidgetLayout->addWidget(m_useDirectoryMode, 4, 0); ++ singleCifsSettingsWidgetLayout->addWidget(m_directoryMode, 4, 1); + + cifsExtensionSupportLayout->addWidget(m_singleCifsExtensionsSettingsWidget); + +@@ -436,29 +375,6 @@ void Smb4KConfigPageMounting::setupWidget() + advancedOptionsExtraWidgetLayout->addWidget(useSecurityMode, 2, 0); + advancedOptionsExtraWidgetLayout->addWidget(securityMode, 2, 1); + +- // Additional options +- QCheckBox *useAdditionalCifsOptions = new QCheckBox(Smb4KMountSettings::self()->useCustomCifsOptionsItem()->label(), advancedOptionsExtraWidget); +- useAdditionalCifsOptions->setObjectName(QStringLiteral("kcfg_UseCustomCifsOptions")); +- +- QWidget *additionalOptionsWidget = new QWidget(advancedOptionsExtraWidget); +- QHBoxLayout *additionalOptionsWidgetLayout = new QHBoxLayout(additionalOptionsWidget); +- additionalOptionsWidgetLayout->setContentsMargins(0, 0, 0, 0); +- +- m_additionalCifsOptions = new KLineEdit(additionalOptionsWidget); +- m_additionalCifsOptions->setObjectName(QStringLiteral("kcfg_CustomCIFSOptions")); +- m_additionalCifsOptions->setReadOnly(true); +- m_additionalCifsOptions->setClearButtonEnabled(true); +- +- QToolButton *additionalOptionsEdit = new QToolButton(advancedOptionsExtraWidget); +- additionalOptionsEdit->setIcon(KDE::icon(QStringLiteral("document-edit"))); +- additionalOptionsEdit->setToolTip(i18n("Edit the additional CIFS options.")); +- +- additionalOptionsWidgetLayout->addWidget(m_additionalCifsOptions, 0); +- additionalOptionsWidgetLayout->addWidget(additionalOptionsEdit, 0); +- +- advancedOptionsExtraWidgetLayout->addWidget(useAdditionalCifsOptions, 3, 0); +- advancedOptionsExtraWidgetLayout->addWidget(additionalOptionsWidget, 3, 1); +- + advancedOptionsLayout->addWidget(advancedOptionsExtraWidget, 4, 0, 1, 2); + + advancedTabLayout->addWidget(advancedOptions, 0); +@@ -466,10 +382,7 @@ void Smb4KConfigPageMounting::setupWidget() + + addTab(advancedTab, i18n("Advanced Mount Settings")); + +- connect(userMenu, &QMenu::triggered, this, &Smb4KConfigPageMounting::slotNewUserTriggered); +- connect(groupMenu, &QMenu::triggered, this, &Smb4KConfigPageMounting::slotNewGroupTriggered); + connect(cifsExtensionsSupport, &QCheckBox::toggled, this, &Smb4KConfigPageMounting::slotCIFSUnixExtensionsSupport); +- connect(additionalOptionsEdit, &QToolButton::clicked, this, &Smb4KConfigPageMounting::slotAdditionalCIFSOptions); + connect(remountShares, &QCheckBox::toggled, this, &Smb4KConfigPageMounting::slotRemountSharesToggled); + } + #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +@@ -484,28 +397,6 @@ void Smb4KConfigPageMounting::setupWidget() + QWidget *basicTab = new QWidget(this); + QVBoxLayout *basicTabLayout = new QVBoxLayout(basicTab); + +- // +- // Directories +- // +- QGroupBox *directoryBox = new QGroupBox(i18n("Directories"), basicTab); +- QGridLayout *directoryBoxLayout = new QGridLayout(directoryBox); +- +- QLabel *mountPrefixLabel = new QLabel(Smb4KMountSettings::self()->mountPrefixItem()->label(), directoryBox); +- m_mountPrefix = new KUrlRequester(directoryBox); +- m_mountPrefix->setMode(KFile::Directory | KFile::LocalOnly); +- m_mountPrefix->setObjectName(QStringLiteral("kcfg_MountPrefix")); +- +- mountPrefixLabel->setBuddy(m_mountPrefix); +- +- QCheckBox *lowercaseSubdirs = new QCheckBox(Smb4KMountSettings::self()->forceLowerCaseSubdirsItem()->label(), directoryBox); +- lowercaseSubdirs->setObjectName(QStringLiteral("kcfg_ForceLowerCaseSubdirs")); +- +- directoryBoxLayout->addWidget(mountPrefixLabel, 0, 0); +- directoryBoxLayout->addWidget(m_mountPrefix, 0, 1); +- directoryBoxLayout->addWidget(lowercaseSubdirs, 1, 0, 1, 2); +- +- basicTabLayout->addWidget(directoryBox, 0); +- + // + // Behavior + // +@@ -545,16 +436,12 @@ void Smb4KConfigPageMounting::setupWidget() + QCheckBox *unmountAllShares = new QCheckBox(Smb4KMountSettings::self()->unmountSharesOnExitItem()->label(), behaviorBox); + unmountAllShares->setObjectName(QStringLiteral("kcfg_UnmountSharesOnExit")); + +- QCheckBox *unmountForeignShares = new QCheckBox(Smb4KMountSettings::self()->unmountForeignSharesItem()->label(), behaviorBox); +- unmountForeignShares->setObjectName(QStringLiteral("kcfg_UnmountForeignShares")); +- + QCheckBox *detectAllShares = new QCheckBox(Smb4KMountSettings::self()->detectAllSharesItem()->label(), behaviorBox); + detectAllShares->setObjectName(QStringLiteral("kcfg_DetectAllShares")); + + behaviorBoxLayout->addWidget(remountShares); + behaviorBoxLayout->addWidget(m_remountSettingsWidget); + behaviorBoxLayout->addWidget(unmountAllShares); +- behaviorBoxLayout->addWidget(unmountForeignShares); + behaviorBoxLayout->addWidget(detectAllShares); + + basicTabLayout->addWidget(behaviorBox, 0); +@@ -574,77 +461,27 @@ void Smb4KConfigPageMounting::setupWidget() + QGroupBox *commonOptionsBox = new QGroupBox(i18n("Common Options"), mountTab); + QGridLayout *commonOptionsBoxLayout = new QGridLayout(commonOptionsBox); + +- // User information +- QCheckBox *useUserId = new QCheckBox(Smb4KMountSettings::self()->useUserIdItem()->label(), commonOptionsBox); +- useUserId->setObjectName(QStringLiteral("kcfg_UseUserId")); +- +- QWidget *userIdInputWidget = new QWidget(commonOptionsBox); +- userIdInputWidget->setObjectName(QStringLiteral("UserIdInputWidget")); +- +- QGridLayout *userLayout = new QGridLayout(userIdInputWidget); +- userLayout->setContentsMargins(0, 0, 0, 0); +- +- m_userId = new KLineEdit(userIdInputWidget); +- m_userId->setObjectName(QStringLiteral("kcfg_UserId")); +- m_userId->setAlignment(Qt::AlignRight); +- m_userId->setReadOnly(true); +- +- QToolButton *userChooser = new QToolButton(userIdInputWidget); +- userChooser->setIcon(KDE::icon(QStringLiteral("edit-find-user"))); +- userChooser->setToolTip(i18n("Choose a different user")); +- userChooser->setPopupMode(QToolButton::InstantPopup); +- +- QMenu *userMenu = new QMenu(userChooser); +- userChooser->setMenu(userMenu); +- +- QList allUsers = KUser::allUsers(); +- +- for (const KUser &u : allUsers) { +- QAction *userAction = userMenu->addAction(u.loginName() + QStringLiteral(" (") + u.userId().toString() + QStringLiteral(")")); +- userAction->setData(u.userId().nativeId()); +- } +- +- userLayout->addWidget(m_userId, 0, 0); +- userLayout->addWidget(userChooser, 0, 1, Qt::AlignCenter); +- +- commonOptionsBoxLayout->addWidget(useUserId, 0, 0); +- commonOptionsBoxLayout->addWidget(userIdInputWidget, 0, 1); +- +- // Group information +- QCheckBox *useGroupId = new QCheckBox(Smb4KMountSettings::self()->useGroupIdItem()->label(), commonOptionsBox); +- useGroupId->setObjectName(QStringLiteral("kcfg_UseGroupId")); ++ // Usage of user and group ID ++ QCheckBox *useIds = new QCheckBox(Smb4KMountSettings::self()->useIdsItem()->label(), commonOptionsBox); ++ useIds->setObjectName(QStringLiteral("kcfg_UseIds")); + +- QWidget *groupIdInputWidget = new QWidget(commonOptionsBox); +- groupIdInputWidget->setObjectName(QStringLiteral("GroupIdInputWidget")); ++ QLabel *userIdLabel = new QLabel(i18n("User ID:"), commonOptionsBox); ++ userIdLabel->setIndent(25); ++ KLineEdit *userId = new KLineEdit(KUser(KUser::UseRealUserID).userId().toString(), commonOptionsBox); ++ userId->setAlignment(Qt::AlignRight); ++ userId->setReadOnly(true); + +- QGridLayout *groupLayout = new QGridLayout(groupIdInputWidget); +- groupLayout->setContentsMargins(0, 0, 0, 0); ++ QLabel *groupIdLabel = new QLabel(i18n("Group ID:"), commonOptionsBox); ++ groupIdLabel->setIndent(25); ++ KLineEdit *groupId = new KLineEdit(KUser(KUser::UseRealUserID).groupId().toString(), commonOptionsBox); ++ groupId->setAlignment(Qt::AlignRight); ++ groupId->setReadOnly(true); + +- m_groupId = new KLineEdit(groupIdInputWidget); +- m_groupId->setObjectName(QStringLiteral("kcfg_GroupId")); +- m_groupId->setAlignment(Qt::AlignRight); +- m_groupId->setReadOnly(true); +- +- QToolButton *groupChooser = new QToolButton(groupIdInputWidget); +- groupChooser->setIcon(KDE::icon(QStringLiteral("edit-find-user"))); +- groupChooser->setToolTip(i18n("Choose a different group")); +- groupChooser->setPopupMode(QToolButton::InstantPopup); +- +- QMenu *groupMenu = new QMenu(groupChooser); +- groupChooser->setMenu(groupMenu); +- +- QList groupList = KUserGroup::allGroups(); +- +- for (const KUserGroup &g : groupList) { +- QAction *groupAction = groupMenu->addAction(g.name() + QStringLiteral(" (") + g.groupId().toString() + QStringLiteral(")")); +- groupAction->setData(g.groupId().nativeId()); +- } +- +- groupLayout->addWidget(m_groupId, 0, 0); +- groupLayout->addWidget(groupChooser, 0, 1, Qt::AlignCenter); +- +- commonOptionsBoxLayout->addWidget(useGroupId, 1, 0); +- commonOptionsBoxLayout->addWidget(groupIdInputWidget, 1, 1); ++ commonOptionsBoxLayout->addWidget(useIds, 0, 0, 1, 2); ++ commonOptionsBoxLayout->addWidget(userIdLabel, 1, 0); ++ commonOptionsBoxLayout->addWidget(userId, 1, 1); ++ commonOptionsBoxLayout->addWidget(groupIdLabel, 2, 0); ++ commonOptionsBoxLayout->addWidget(groupId, 2, 1); + + // File mask + m_useFileMode = new QCheckBox(Smb4KMountSettings::self()->useFileModeItem()->label(), commonOptionsBox); +@@ -654,9 +491,11 @@ void Smb4KConfigPageMounting::setupWidget() + m_fileMode->setObjectName(QStringLiteral("kcfg_FileMode")); + m_fileMode->setClearButtonEnabled(true); + m_fileMode->setAlignment(Qt::AlignRight); ++ m_fileMode->setInputMask(QStringLiteral("0999")); ++ m_fileMode->setValidator(new ModeValidator(m_fileMode)); + +- commonOptionsBoxLayout->addWidget(m_useFileMode, 2, 0); +- commonOptionsBoxLayout->addWidget(m_fileMode, 2, 1); ++ commonOptionsBoxLayout->addWidget(m_useFileMode, 3, 0); ++ commonOptionsBoxLayout->addWidget(m_fileMode, 3, 1); + + // Directory mask + m_useDirectoryMode = new QCheckBox(Smb4KMountSettings::self()->useDirectoryModeItem()->label(), commonOptionsBox); +@@ -666,9 +505,11 @@ void Smb4KConfigPageMounting::setupWidget() + m_directoryMode->setObjectName(QStringLiteral("kcfg_DirectoryMode")); + m_directoryMode->setClearButtonEnabled(true); + m_directoryMode->setAlignment(Qt::AlignRight); ++ m_directoryMode->setInputMask(QStringLiteral("0999")); ++ m_directoryMode->setValidator(new ModeValidator(m_directoryMode)); + +- commonOptionsBoxLayout->addWidget(m_useDirectoryMode, 3, 0); +- commonOptionsBoxLayout->addWidget(m_directoryMode, 3, 1); ++ commonOptionsBoxLayout->addWidget(m_useDirectoryMode, 4, 0); ++ commonOptionsBoxLayout->addWidget(m_directoryMode, 4, 1); + + // + // Character sets +@@ -729,8 +570,6 @@ void Smb4KConfigPageMounting::setupWidget() + + addTab(mountTab, i18n("Mount Settings")); + +- connect(userMenu, &QMenu::triggered, this, &Smb4KConfigPageMounting::slotNewUserTriggered); +- connect(groupMenu, &QMenu::triggered, this, &Smb4KConfigPageMounting::slotNewGroupTriggered); + connect(useCharacterSets, &QCheckBox::toggled, this, &Smb4KConfigPageMounting::slotCharacterSets); + connect(remountShares, &QCheckBox::toggled, this, &Smb4KConfigPageMounting::slotRemountSharesToggled); + } +@@ -743,70 +582,11 @@ void Smb4KConfigPageMounting::setupWidget() + } + #endif + +-void Smb4KConfigPageMounting::slotNewUserTriggered(QAction *action) +-{ +- m_userId->setText(action->data().toString()); +-} +- +-void Smb4KConfigPageMounting::slotNewGroupTriggered(QAction *action) +-{ +- m_groupId->setText(action->data().toString()); +-} +- + #if defined(Q_OS_LINUX) + void Smb4KConfigPageMounting::slotCIFSUnixExtensionsSupport(bool checked) + { + m_singleCifsExtensionsSettingsWidget->setEnabled(!checked); + } +- +-void Smb4KConfigPageMounting::slotAdditionalCIFSOptions() +-{ +- QString options = m_additionalCifsOptions->originalText(); +- +- bool ok = false; +- options = QInputDialog::getText(this, +- i18n("Additional CIFS Options"), +- i18n("Enter the desired options as a comma separated list:"), +- QLineEdit::Normal, +- options, +- &ok); +- +- if (ok) { +- if (!options.trimmed().isEmpty()) { +- // SECURITY: Only pass those arguments to mount.cifs that do not pose +- // a potential security risk and that have not already been defined. +- // +- // This is, among others, the proper fix to the security issue reported +- // by Heiner Markert (aka CVE-2014-2581). +- QStringList allowedArgs = allowedMountArguments(); +- QStringList deniedArgs; +- QStringList list = options.split(QStringLiteral(","), Qt::SkipEmptyParts); +- QMutableStringListIterator it(list); +- +- while (it.hasNext()) { +- QString arg = it.next().section(QStringLiteral("="), 0, 0); +- +- if (!allowedArgs.contains(arg)) { +- deniedArgs << arg; +- it.remove(); +- } +- } +- +- if (!deniedArgs.isEmpty()) { +- QString msg = +- i18np("The following entry is going to be removed from the additional options:
%2.
Please read the handbook for details.", +- "The following %1 entries are going to be removed from the additional options:
%2.
Please read the handbook for details.", +- deniedArgs.size(), +- deniedArgs.join(QStringLiteral(", "))); +- KMessageBox::information(this, msg); +- } +- +- m_additionalCifsOptions->setText(list.join(QStringLiteral(",")).trimmed()); +- } else { +- m_additionalCifsOptions->clear(); +- } +- } +-} + #endif + + #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +diff --git a/smb4k/smb4kconfigpagemounting.h b/smb4k/smb4kconfigpagemounting.h +index 6b9a359f99fc..0341930f1b60 100644 +--- a/smb4k/smb4kconfigpagemounting.h ++++ b/smb4k/smb4kconfigpagemounting.h +@@ -1,7 +1,7 @@ + /* + The configuration page for the mount options + +- SPDX-FileCopyrightText: 2015-2023 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2015-2025 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -14,7 +14,6 @@ + + // KDE includes + #include +-#include + + /** + * This configuration page contains the mount options +@@ -47,20 +46,6 @@ public: + bool checkSettings(); + + protected Q_SLOTS: +- /** +- * Sets the new general user ID. +- * +- * @param action The action that represents the new user. +- */ +- void slotNewUserTriggered(QAction *action); +- +- /** +- * Sets the new general group ID. +- * +- * @param action The action that represents the new group. +- */ +- void slotNewGroupTriggered(QAction *action); +- + #if defined(Q_OS_LINUX) + /** + * Enable / disable the options that are only necessary when the servers +@@ -70,11 +55,6 @@ protected Q_SLOTS: + */ + void slotCIFSUnixExtensionsSupport(bool checked); + +- /** +- * This slot is activated when the additional CIFS options are to be +- * edited (Linux only). +- */ +- void slotAdditionalCIFSOptions(); + #endif + + #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +@@ -92,7 +72,6 @@ protected Q_SLOTS: + + private: + void setupWidget(); +- KUrlRequester *m_mountPrefix; + QWidget *m_remountSettingsWidget; + QCheckBox *m_useFileMode; + KLineEdit *m_fileMode; +@@ -102,7 +81,6 @@ private: + KLineEdit *m_groupId; + #if defined(Q_OS_LINUX) + QWidget *m_singleCifsExtensionsSettingsWidget; +- KLineEdit *m_additionalCifsOptions; + #endif + + #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) +diff --git a/smb4k/smb4kcustomsettingseditorwidget.cpp b/smb4k/smb4kcustomsettingseditorwidget.cpp +index 35f3e0ee1751..cb8a6265948b 100644 +--- a/smb4k/smb4kcustomsettingseditorwidget.cpp ++++ b/smb4k/smb4kcustomsettingseditorwidget.cpp +@@ -1,7 +1,7 @@ + /* + * Editor widget for the custom settings + * +- * SPDX-FileCopyrightText: 2023-2024 Alexander Reinholdt ++ * SPDX-FileCopyrightText: 2023-2025 Alexander Reinholdt + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -17,18 +17,44 @@ + // Qt includes + #include + #include ++#include + + // KDE includes + #include + + using namespace Smb4KGlobal; + ++class ModeValidator : public QValidator ++{ ++public: ++ ModeValidator(QObject *parent = nullptr) ++ : QValidator(parent) ++ { ++ } ++ ~ModeValidator() ++ { ++ } ++ QValidator::State validate(QString &input, int &pos) const override ++ { ++ Q_UNUSED(pos); ++ if (input.trimmed().size() == 4) { ++ QChar ch = input.trimmed().at(0); ++ ++ if (ch.isDigit() && ch.toLatin1() != '0') { ++ return QValidator::Invalid; ++ } else if (ch.isDigit() && ch.toLatin1() == '0') { ++ return QValidator::Acceptable; ++ } ++ } ++ return QValidator::Intermediate; ++ } ++}; ++ + Smb4KCustomSettingsEditorWidget::Smb4KCustomSettingsEditorWidget(QWidget *parent) + : QTabWidget(parent) + { + m_hasDefaultCustomSettings = true; + +- // FIXME: Implement mount point!? + // FIXME: Honor disabled widgets and unchecked check boxes!? + + setupView(); +@@ -88,45 +114,32 @@ void Smb4KCustomSettingsEditorWidget::setupView() + connect(m_useWriteAccess, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseWriteAccessToggled); + connect(m_writeAccess, &KComboBox::currentIndexChanged, this, &Smb4KCustomSettingsEditorWidget::slotWriteAccessChanged); + +- m_useFileSystemPort = new QCheckBox(Smb4KMountSettings::self()->useRemoteFileSystemPortItem()->label(), tab2); +- m_fileSystemPort = new QSpinBox(tab2); +- m_fileSystemPort->setMinimum(Smb4KMountSettings::self()->remoteFileSystemPortItem()->minValue().toInt()); +- m_fileSystemPort->setMaximum(Smb4KMountSettings::self()->remoteFileSystemPortItem()->maxValue().toInt()); +- +- connect(m_useFileSystemPort, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseFileSystemPortToggled); +- connect(m_fileSystemPort, &QSpinBox::valueChanged, this, &Smb4KCustomSettingsEditorWidget::slotFileSystemPortChanged); +- + m_cifsUnixExtensionSupport = new QCheckBox(i18n("This server supports the CIFS Unix extensions"), tab2); + + connect(m_cifsUnixExtensionSupport, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotCifsUnixExtensionSupportToggled); + +- m_useUserId = new QCheckBox(Smb4KMountSettings::self()->useUserIdItem()->label(), tab2); +- m_userId = new KComboBox(tab2); ++ m_useIds = new QCheckBox(Smb4KMountSettings::self()->useIdsItem()->label(), tab2); + +- QList allUsers = KUser::allUsers(); ++ m_userIdLabel = new QLabel(i18n("User ID:"), tab2); ++ m_userIdLabel->setIndent(25); ++ m_userId = new KLineEdit(KUser(KUser::UseRealUserID).userId().toString(), tab2); ++ m_userId->setAlignment(Qt::AlignRight); ++ m_userId->setReadOnly(true); + +- for (const KUser &user : std::as_const(allUsers)) { +- m_userId->addItem(user.loginName() + QStringLiteral(" (") + user.userId().toString() + QStringLiteral(")"), user.userId().toString()); +- } ++ m_groupIdLabel = new QLabel(i18n("Group ID:"), tab2); ++ m_groupIdLabel->setIndent(25); ++ m_groupId = new KLineEdit(KUser(KUser::UseRealUserID).groupId().toString(), tab2); ++ m_groupId->setAlignment(Qt::AlignRight); ++ m_groupId->setReadOnly(true); + +- connect(m_useUserId, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseUserIdToggled); +- connect(m_userId, &KComboBox::currentIndexChanged, this, &Smb4KCustomSettingsEditorWidget::slotUserIdChanged); +- +- m_useGroupId = new QCheckBox(Smb4KMountSettings::self()->useGroupIdItem()->label(), tab2); +- m_groupId = new KComboBox(tab2); +- +- QList allGroups = KUserGroup::allGroups(); +- +- for (const KUserGroup &group : std::as_const(allGroups)) { +- m_groupId->addItem(group.name() + QStringLiteral(" (") + group.groupId().toString() + QStringLiteral(")"), group.groupId().toString()); +- } +- +- connect(m_useGroupId, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseGroupIdToggled); +- connect(m_groupId, &KComboBox::currentIndexChanged, this, &Smb4KCustomSettingsEditorWidget::slotGroupIdChanged); ++ connect(m_useIds, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseIdsToggled); + + m_useFileMode = new QCheckBox(Smb4KMountSettings::self()->useFileModeItem()->label(), tab2); + m_fileMode = new KLineEdit(tab2); + m_fileMode->setClearButtonEnabled(true); ++ m_fileMode->setAlignment(Qt::AlignRight); ++ m_fileMode->setInputMask(QStringLiteral("0999")); ++ m_fileMode->setValidator(new ModeValidator(m_fileMode)); + + connect(m_useFileMode, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseFileModeToggled); + connect(m_fileMode, &KLineEdit::textChanged, this, &Smb4KCustomSettingsEditorWidget::slotFileModeChanged); +@@ -134,24 +147,26 @@ void Smb4KCustomSettingsEditorWidget::setupView() + m_useDirectoryMode = new QCheckBox(Smb4KMountSettings::self()->useDirectoryModeItem()->label(), tab2); + m_directoryMode = new KLineEdit(tab2); + m_directoryMode->setClearButtonEnabled(true); ++ m_directoryMode->setAlignment(Qt::AlignRight); ++ m_directoryMode->setInputMask(QStringLiteral("0999")); ++ m_directoryMode->setValidator(new ModeValidator(m_directoryMode)); + + connect(m_useDirectoryMode, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseDirectoryModeToggled); + connect(m_directoryMode, &KLineEdit::textChanged, this, &Smb4KCustomSettingsEditorWidget::slotDirectoryModeChanged); + + tab2Layout->addWidget(m_useWriteAccess, 0, 0); + tab2Layout->addWidget(m_writeAccess, 0, 1); +- tab2Layout->addWidget(m_useFileSystemPort, 1, 0); +- tab2Layout->addWidget(m_fileSystemPort, 1, 1); +- tab2Layout->addWidget(m_cifsUnixExtensionSupport, 2, 0, 1, 2); +- tab2Layout->addWidget(m_useUserId, 3, 0); ++ tab2Layout->addWidget(m_cifsUnixExtensionSupport, 1, 0, 1, 2); ++ tab2Layout->addWidget(m_useIds, 2, 0, 1, 2); ++ tab2Layout->addWidget(m_userIdLabel, 3, 0); + tab2Layout->addWidget(m_userId, 3, 1); +- tab2Layout->addWidget(m_useGroupId, 4, 0); ++ tab2Layout->addWidget(m_groupIdLabel, 4, 0); + tab2Layout->addWidget(m_groupId, 4, 1); + tab2Layout->addWidget(m_useFileMode, 5, 0); + tab2Layout->addWidget(m_fileMode, 5, 1); + tab2Layout->addWidget(m_useDirectoryMode, 6, 0); + tab2Layout->addWidget(m_directoryMode, 6, 1); +- tab2Layout->setRowStretch(7, 100); ++ tab2Layout->setRowStretch(6, 100); + + addTab(tab2, i18n("Common Mount Settings")); + +@@ -250,17 +265,29 @@ void Smb4KCustomSettingsEditorWidget::setupView() + addTab(tab4, i18n("Browse Settings")); + + QWidget *tab5 = new QWidget(this); +- QGridLayout *tab5Layout = new QGridLayout(tab5); ++ QVBoxLayout *tab5Layout = new QVBoxLayout(tab5); + + tab5->setEnabled(Smb4KSettings::enableWakeOnLAN()); + +- m_macAddressLabel = new QLabel(i18n("MAC Address:"), tab5); +- m_macAddress = new KLineEdit(tab5); ++ QWidget *macAddressWidget = new QWidget(tab5); ++ QHBoxLayout *macAddressWidgetLayout = new QHBoxLayout(macAddressWidget); ++ macAddressWidgetLayout->setContentsMargins(0, 0, 0, 0); ++ ++ m_macAddressLabel = new QLabel(i18n("MAC Address:"), macAddressWidget); ++ m_macAddress = new KLineEdit(macAddressWidget); + m_macAddress->setClearButtonEnabled(true); + m_macAddress->setInputMask(QStringLiteral("HH:HH:HH:HH:HH:HH;_")); // MAC address, see QLineEdit doc + m_macAddressLabel->setBuddy(m_macAddress); ++ m_macAddressSearchButton = new QPushButton(macAddressWidget); ++ m_macAddressSearchButton->setIcon(KDE::icon(QStringLiteral("edit-find"))); ++ m_macAddressSearchButton->setToolTip(i18n("Find MAC address")); + + connect(m_macAddress, &KLineEdit::textChanged, this, &Smb4KCustomSettingsEditorWidget::slotMacAddressChanged); ++ connect(m_macAddressSearchButton, &QPushButton::clicked, this, &Smb4KCustomSettingsEditorWidget::slotFindMacAddressClicked); ++ ++ macAddressWidgetLayout->addWidget(m_macAddressLabel); ++ macAddressWidgetLayout->addWidget(m_macAddress); ++ macAddressWidgetLayout->addWidget(m_macAddressSearchButton); + + m_sendPacketBeforeScan = new QCheckBox(i18n("Send magic packet before scanning the network neighborhood"), tab5); + m_sendPacketBeforeScan->setEnabled(false); +@@ -270,11 +297,10 @@ void Smb4KCustomSettingsEditorWidget::setupView() + connect(m_sendPacketBeforeScan, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotSendPacketBeforeScanToggled); + connect(m_sendPacketBeforeMount, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotSendPacketBeforeMountToggled); + +- tab5Layout->addWidget(m_macAddressLabel, 0, 0); +- tab5Layout->addWidget(m_macAddress, 0, 1); +- tab5Layout->addWidget(m_sendPacketBeforeScan, 1, 0, 1, 2); +- tab5Layout->addWidget(m_sendPacketBeforeMount, 2, 0, 1, 2); +- tab5Layout->setRowStretch(3, 100); ++ tab5Layout->addWidget(macAddressWidget); ++ tab5Layout->addWidget(m_sendPacketBeforeScan); ++ tab5Layout->addWidget(m_sendPacketBeforeMount); ++ tab5Layout->addStretch(100); + + m_wakeOnLanTabIndex = addTab(tab5, i18n("Wake-On-LAN Settings")); + } +@@ -316,33 +342,27 @@ void Smb4KCustomSettingsEditorWidget::setupView() + QWidget *tab2 = new QWidget(this); + QGridLayout *tab2Layout = new QGridLayout(tab2); + +- m_useUserId = new QCheckBox(Smb4KMountSettings::self()->useUserIdItem()->label(), tab2); +- m_userId = new KComboBox(tab2); +- +- QList allUsers = KUser::allUsers(); +- +- for (const KUser &user : std::as_const(allUsers)) { +- m_userId->addItem(user.loginName() + QStringLiteral(" (") + user.userId().toString() + QStringLiteral(")"), user.userId().toString()); +- } +- +- connect(m_useUserId, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseUserIdToggled); +- connect(m_userId, &KComboBox::currentIndexChanged, this, &Smb4KCustomSettingsEditorWidget::slotUserIdChanged); +- +- m_useGroupId = new QCheckBox(Smb4KMountSettings::self()->useGroupIdItem()->label(), tab2); +- m_groupId = new KComboBox(tab2); ++ m_useIds = new QCheckBox(Smb4KMountSettings::self()->useIdsItem()->label(), tab2); + +- QList allGroups = KUserGroup::allGroups(); ++ m_userIdLabel = new QLabel(i18n("User ID:"), tab2); ++ m_userIdLabel->setIndent(25); ++ m_userId = new KLineEdit(KUser(KUser::UseRealUserID).userId().toString(), tab2); ++ m_userId->setAlignment(Qt::AlignRight); ++ m_userId->setReadOnly(true); + +- for (const KUserGroup &group : std::as_const(allGroups)) { +- m_groupId->addItem(group.name() + QStringLiteral(" (") + group.groupId().toString() + QStringLiteral(")"), group.groupId().toString()); +- } ++ m_groupIdLabel = new QLabel(i18n("Group ID:"), tab2); ++ m_groupIdLabel->setIndent(25); ++ m_groupId = new KLineEdit(KUser(KUser::UseRealUserID).groupId().toString(), tab2); ++ m_groupId->setAlignment(Qt::AlignRight); ++ m_groupId->setReadOnly(true); + +- connect(m_useGroupId, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseGroupIdToggled); +- connect(m_groupId, &KComboBox::currentIndexChanged, this, &Smb4KCustomSettingsEditorWidget::slotGroupIdChanged); ++ connect(m_useIds, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseIdsToggled); + + m_useFileMode = new QCheckBox(Smb4KMountSettings::self()->useFileModeItem()->label(), tab2); + m_fileMode = new KLineEdit(tab2); + m_fileMode->setClearButtonEnabled(true); ++ m_fileMode->setInputMask(QStringLiteral("0999")); ++ m_fileMode->setValidator(new ModeValidator(m_fileMode)); + + connect(m_useFileMode, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseFileModeToggled); + connect(m_fileMode, &KLineEdit::textChanged, this, &Smb4KCustomSettingsEditorWidget::slotFileModeChanged); +@@ -350,18 +370,21 @@ void Smb4KCustomSettingsEditorWidget::setupView() + m_useDirectoryMode = new QCheckBox(Smb4KMountSettings::self()->useDirectoryModeItem()->label(), tab2); + m_directoryMode = new KLineEdit(tab2); + m_directoryMode->setClearButtonEnabled(true); ++ m_directoryMode->setInputMask(QStringLiteral("0999")); ++ m_directoryMode->setValidator(new ModeValidator(m_directoryMode)); + + connect(m_useDirectoryMode, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotUseDirectoryModeToggled); + connect(m_directoryMode, &KLineEdit::textChanged, this, &Smb4KCustomSettingsEditorWidget::slotDirectoryModeChanged); + +- tab2Layout->addWidget(m_useUserId, 0, 0); +- tab2Layout->addWidget(m_userId, 0, 1); +- tab2Layout->addWidget(m_useGroupId, 1, 0); +- tab2Layout->addWidget(m_groupId, 1, 1); +- tab2Layout->addWidget(m_useFileMode, 2, 0); +- tab2Layout->addWidget(m_fileMode, 2, 1); +- tab2Layout->addWidget(m_useDirectoryMode, 3, 0); +- tab2Layout->addWidget(m_directoryMode, 3, 1); ++ tab2Layout->addWidget(m_useIds, 0, 0, 1, 2); ++ tab2Layout->addWidget(m_userIdLabel, 1, 0); ++ tab2Layout->addWidget(m_userId, 1, 1); ++ tab2Layout->addWidget(m_groupIdLabel, 2, 0); ++ tab2Layout->addWidget(m_groupId, 2, 1); ++ tab2Layout->addWidget(m_useFileMode, 3, 0); ++ tab2Layout->addWidget(m_fileMode, 3, 1); ++ tab2Layout->addWidget(m_useDirectoryMode, 4, 0); ++ tab2Layout->addWidget(m_directoryMode, 4, 1); + tab2Layout->setRowStretch(4, 100); + + addTab(tab2, i18n("Mount Settings")); +@@ -426,17 +449,29 @@ void Smb4KCustomSettingsEditorWidget::setupView() + addTab(tab3, i18n("Browse Settings")); + + QWidget *tab4 = new QWidget(this); +- QGridLayout *tab4Layout = new QGridLayout(tab4); ++ QVBoxLayout *tab4Layout = new QVBoxLayout(tab4); + + tab4->setEnabled(Smb4KSettings::enableWakeOnLAN()); + +- m_macAddressLabel = new QLabel(i18n("MAC Address:"), tab4); +- m_macAddress = new KLineEdit(tab4); ++ QWidget *macAddressWidget = new QWidget(tab4); ++ QHBoxLayout *macAddressWidgetLayout = new QHBoxLayout(macAddressWidget); ++ macAddressWidgetLayout->setContentsMargins(0, 0, 0, 0); ++ ++ m_macAddressLabel = new QLabel(i18n("MAC Address:"), macAddressWidget); ++ m_macAddress = new KLineEdit(macAddressWidget); + m_macAddress->setClearButtonEnabled(true); + m_macAddress->setInputMask(QStringLiteral("HH:HH:HH:HH:HH:HH;_")); // MAC address, see QLineEdit doc + m_macAddressLabel->setBuddy(m_macAddress); ++ m_macAddressSearchButton = new QPushButton(macAddressWidget); ++ m_macAddressSearchButton->setIcon(KDE::icon(QStringLiteral("edit-find"))); ++ m_macAddressSearchButton->setToolTip(i18n("Find MAC address")); + + connect(m_macAddress, &KLineEdit::textChanged, this, &Smb4KCustomSettingsEditorWidget::slotMacAddressChanged); ++ connect(m_macAddressSearchButton, &QPushButton::clicked, this, &Smb4KCustomSettingsEditorWidget::slotFindMacAddressClicked); ++ ++ macAddressWidgetLayout->addWidget(m_macAddressLabel); ++ macAddressWidgetLayout->addWidget(m_macAddress); ++ macAddressWidgetLayout->addWidget(m_macAddressSearchButton); + + m_sendPacketBeforeScan = new QCheckBox(i18n("Send magic packet before scanning the network neighborhood"), tab4); + m_sendPacketBeforeScan->setEnabled(false); +@@ -446,11 +481,10 @@ void Smb4KCustomSettingsEditorWidget::setupView() + connect(m_sendPacketBeforeScan, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotSendPacketBeforeScanToggled); + connect(m_sendPacketBeforeMount, &QCheckBox::toggled, this, &Smb4KCustomSettingsEditorWidget::slotSendPacketBeforeMountToggled); + +- tab4Layout->addWidget(m_macAddressLabel, 0, 0); +- tab4Layout->addWidget(m_macAddress, 0, 1); +- tab4Layout->addWidget(m_sendPacketBeforeScan, 1, 0, 1, 2); +- tab4Layout->addWidget(m_sendPacketBeforeMount, 2, 0, 1, 2); +- tab4Layout->setRowStretch(3, 100); ++ tab4Layout->addWidget(macAddressWidget); ++ tab4Layout->addWidget(m_sendPacketBeforeScan); ++ tab4Layout->addWidget(m_sendPacketBeforeMount); ++ tab4Layout->addStretch(100); + + m_wakeOnLanTabIndex = addTab(tab4, i18n("Wake-On-LAN Settings")); + } +@@ -476,19 +510,10 @@ void Smb4KCustomSettingsEditorWidget::setCustomSettings(const Smb4KCustomSetting + m_useWriteAccess->setChecked(m_customSettings.useWriteAccess()); + m_writeAccess->setCurrentIndex(m_customSettings.writeAccess()); + +- m_useFileSystemPort->setChecked(m_customSettings.useFileSystemPort()); +- m_fileSystemPort->setValue(m_customSettings.fileSystemPort()); +- + m_cifsUnixExtensionSupport->setChecked(m_customSettings.cifsUnixExtensionsSupport()); + #endif + +- m_useUserId->setChecked(m_customSettings.useUser()); +- int userIndex = m_userId->findData(m_customSettings.user().userId().toString()); +- m_userId->setCurrentIndex(userIndex); +- +- m_useGroupId->setChecked(m_customSettings.useGroup()); +- int groupIndex = m_groupId->findData(m_customSettings.group().groupId().toString()); +- m_groupId->setCurrentIndex(groupIndex); ++ m_useIds->setChecked(m_customSettings.useIds()); + + m_useFileMode->setChecked(m_customSettings.useFileMode()); + m_fileMode->setText(m_customSettings.fileMode()); +@@ -520,6 +545,7 @@ void Smb4KCustomSettingsEditorWidget::setCustomSettings(const Smb4KCustomSetting + widget(m_wakeOnLanTabIndex)->setEnabled(Smb4KSettings::enableWakeOnLAN()); + + if (m_customSettings.type() == Host) { ++ // FIXME: Enable Search button if IP address is not empty + m_macAddress->setText(m_customSettings.macAddress()); + m_sendPacketBeforeScan->setChecked(m_customSettings.wakeOnLanSendBeforeNetworkScan()); + m_sendPacketBeforeMount->setChecked(m_customSettings.wakeOnLanSendBeforeMount()); +@@ -539,17 +565,10 @@ Smb4KCustomSettings Smb4KCustomSettingsEditorWidget::getCustomSettings() const + m_customSettings.setUseWriteAccess(m_useWriteAccess->isChecked()); + m_customSettings.setWriteAccess(m_writeAccess->currentIndex()); + +- m_customSettings.setUseFileSystemPort(m_useFileSystemPort->isChecked()); +- m_customSettings.setFileSystemPort(m_fileSystemPort->value()); +- + m_customSettings.setCifsUnixExtensionsSupport(m_cifsUnixExtensionSupport->isChecked()); + #endif + +- m_customSettings.setUseUser(m_useUserId->isChecked()); +- m_customSettings.setUser(KUser(K_UID(m_userId->currentData().toInt()))); +- +- m_customSettings.setUseGroup(m_useGroupId->isChecked()); +- m_customSettings.setGroup(KUserGroup(K_GID(m_groupId->currentData().toInt()))); ++ m_customSettings.setUseIds(m_useIds->isChecked()); + + m_customSettings.setUseFileMode(m_useFileMode->isChecked()); + m_customSettings.setFileMode(m_fileMode->text()); +@@ -597,17 +616,10 @@ void Smb4KCustomSettingsEditorWidget::clear() + m_useWriteAccess->setChecked(false); + m_writeAccess->setCurrentIndex(0); + +- m_useFileSystemPort->setChecked(false); +- m_fileSystemPort->setValue(445); +- + m_cifsUnixExtensionSupport->setChecked(false); + #endif + +- m_useUserId->setChecked(false); +- m_userId->setCurrentIndex(0); +- +- m_useGroupId->setChecked(false); +- m_groupId->setCurrentIndex(0); ++ m_useIds->setChecked(false); + + m_useFileMode->setChecked(false); + m_fileMode->clear(); +@@ -677,32 +689,12 @@ void Smb4KCustomSettingsEditorWidget::checkValues() + m_hasDefaultCustomSettings = false; + } + +- if (m_useFileSystemPort->isChecked() != defaultCustomSettings.useFileSystemPort()) { +- m_hasDefaultCustomSettings = false; +- } +- +- if (m_fileSystemPort->value() != defaultCustomSettings.fileSystemPort()) { +- m_hasDefaultCustomSettings = false; +- } +- + if (m_cifsUnixExtensionSupport->isChecked() != defaultCustomSettings.cifsUnixExtensionsSupport()) { + m_hasDefaultCustomSettings = false; + } + #endif + +- if (m_useUserId->isChecked() != defaultCustomSettings.useUser()) { +- m_hasDefaultCustomSettings = false; +- } +- +- if (m_userId->currentData().toString() != defaultCustomSettings.user().userId().toString()) { +- m_hasDefaultCustomSettings = false; +- } +- +- if (m_useGroupId->isChecked() != defaultCustomSettings.useGroup()) { +- m_hasDefaultCustomSettings = false; +- } +- +- if (m_groupId->currentData().toString() != defaultCustomSettings.group().groupId().toString()) { ++ if (m_useIds->isChecked() != defaultCustomSettings.useIds()) { + m_hasDefaultCustomSettings = false; + } + +@@ -815,38 +807,13 @@ void Smb4KCustomSettingsEditorWidget::checkValues() + return; + } + +- if (m_useFileSystemPort->isChecked() != m_customSettings.useFileSystemPort()) { +- Q_EMIT edited(true); +- return; +- } +- +- if (m_fileSystemPort->value() != m_customSettings.fileSystemPort()) { +- Q_EMIT edited(true); +- return; +- } +- + if (m_cifsUnixExtensionSupport->isChecked() != m_customSettings.cifsUnixExtensionsSupport()) { + Q_EMIT edited(true); + return; + } + #endif + +- if (m_useUserId->isChecked() != m_customSettings.useUser()) { +- Q_EMIT edited(true); +- return; +- } +- +- if (m_userId->currentData().toString() != m_customSettings.user().userId().toString()) { +- Q_EMIT edited(true); +- return; +- } +- +- if (m_useGroupId->isChecked() != m_customSettings.useGroup()) { +- Q_EMIT edited(true); +- return; +- } +- +- if (m_groupId->currentData().toString() != m_customSettings.group().groupId().toString()) { ++ if (m_useIds->isChecked() != m_customSettings.useIds()) { + Q_EMIT edited(true); + return; + } +@@ -975,25 +942,14 @@ void Smb4KCustomSettingsEditorWidget::slotWriteAccessChanged(int index) + checkValues(); + } + +-void Smb4KCustomSettingsEditorWidget::slotUseFileSystemPortToggled(bool checked) +-{ +- Q_UNUSED(checked); +- checkValues(); +-} +- +-void Smb4KCustomSettingsEditorWidget::slotFileSystemPortChanged(int port) +-{ +- Q_UNUSED(port); +- checkValues(); +-} +- + void Smb4KCustomSettingsEditorWidget::slotCifsUnixExtensionSupportToggled(bool checked) + { + Q_UNUSED(checked); + +- m_useUserId->setEnabled(!checked); ++ m_useIds->setEnabled(!checked); ++ m_userIdLabel->setEnabled(!checked); + m_userId->setEnabled(!checked); +- m_useGroupId->setEnabled(!checked); ++ m_groupIdLabel->setEnabled(!checked); + m_groupId->setEnabled(!checked); + m_useFileMode->setEnabled(!checked); + m_fileMode->setEnabled(!checked); +@@ -1004,30 +960,12 @@ void Smb4KCustomSettingsEditorWidget::slotCifsUnixExtensionSupportToggled(bool c + } + #endif + +-void Smb4KCustomSettingsEditorWidget::slotUseUserIdToggled(bool checked) +-{ +- Q_UNUSED(checked); +- checkValues(); +-} +- +-void Smb4KCustomSettingsEditorWidget::slotUserIdChanged(int index) +-{ +- Q_UNUSED(index); +- checkValues(); +-} +- +-void Smb4KCustomSettingsEditorWidget::slotUseGroupIdToggled(bool checked) ++void Smb4KCustomSettingsEditorWidget::slotUseIdsToggled(bool checked) + { + Q_UNUSED(checked); + checkValues(); + } + +-void Smb4KCustomSettingsEditorWidget::slotGroupIdChanged(int index) +-{ +- Q_UNUSED(index); +- checkValues(); +-} +- + void Smb4KCustomSettingsEditorWidget::slotUseFileModeToggled(bool checked) + { + Q_UNUSED(checked); +@@ -1120,6 +1058,19 @@ void Smb4KCustomSettingsEditorWidget::slotUseKerberosToggled(bool checked) + checkValues(); + } + ++void Smb4KCustomSettingsEditorWidget::slotFindMacAddressClicked(bool checked) ++{ ++ Q_UNUSED(checked); ++ ++ if (!m_customSettings.ipAddress().isEmpty()) { ++ QString macAddress = findMacAddress(m_customSettings.ipAddress()); ++ ++ if (!macAddress.isEmpty()) { ++ m_macAddress->setText(macAddress); ++ } ++ } ++} ++ + void Smb4KCustomSettingsEditorWidget::slotMacAddressChanged(const QString &text) + { + Q_UNUSED(text); +diff --git a/smb4k/smb4kcustomsettingseditorwidget.h b/smb4k/smb4kcustomsettingseditorwidget.h +index 6cc401169241..db7fcc72b4fb 100644 +--- a/smb4k/smb4kcustomsettingseditorwidget.h ++++ b/smb4k/smb4kcustomsettingseditorwidget.h +@@ -1,7 +1,7 @@ + /* + * Editor widget for the custom settings + * +- * SPDX-FileCopyrightText: 2023-2024 Alexander Reinholdt ++ * SPDX-FileCopyrightText: 2023-2025 Alexander Reinholdt + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -14,6 +14,7 @@ + // Qt includes + #include + #include ++#include + #include + #include + +@@ -25,7 +26,7 @@ + * This widget is used to edit custom settings + * + * @author Alexander Reinholdt +- * @since 3.3.0 ++ * @since 4.0.0 + */ + + class Smb4KCustomSettingsEditorWidget : public QTabWidget +@@ -76,14 +77,9 @@ protected Q_SLOTS: + #ifdef Q_OS_LINUX + void slotUseWriteAccessToggled(bool checked); + void slotWriteAccessChanged(int index); +- void slotUseFileSystemPortToggled(bool checked); +- void slotFileSystemPortChanged(int port); + void slotCifsUnixExtensionSupportToggled(bool checked); + #endif +- void slotUseUserIdToggled(bool checked); +- void slotUserIdChanged(int index); +- void slotUseGroupIdToggled(bool checked); +- void slotGroupIdChanged(int index); ++ void slotUseIdsToggled(bool checked); + void slotUseFileModeToggled(bool checked); + void slotFileModeChanged(const QString &text); + void slotUseDirectoryModeToggled(bool checked); +@@ -100,6 +96,7 @@ protected Q_SLOTS: + void slotUseRemoteSmbPortToggled(bool checked); + void slotRemoteSmbPortChanged(int port); + void slotUseKerberosToggled(bool checked); ++ void slotFindMacAddressClicked(bool checked); + void slotMacAddressChanged(const QString &text); + void slotSendPacketBeforeScanToggled(bool checked); + void slotSendPacketBeforeMountToggled(bool checked); +@@ -117,14 +114,13 @@ private: + #ifdef Q_OS_LINUX + QCheckBox *m_useWriteAccess; + KComboBox *m_writeAccess; +- QCheckBox *m_useFileSystemPort; +- QSpinBox *m_fileSystemPort; + QCheckBox *m_cifsUnixExtensionSupport; + #endif +- QCheckBox *m_useUserId; +- KComboBox *m_userId; +- QCheckBox *m_useGroupId; +- KComboBox *m_groupId; ++ QCheckBox *m_useIds; ++ QLabel *m_userIdLabel; ++ KLineEdit *m_userId; ++ QLabel *m_groupIdLabel; ++ KLineEdit *m_groupId; + QCheckBox *m_useFileMode; + KLineEdit *m_fileMode; + QCheckBox *m_useDirectoryMode; +@@ -148,6 +144,7 @@ private: + QCheckBox *m_sendPacketBeforeScan; + QCheckBox *m_sendPacketBeforeMount; + int m_wakeOnLanTabIndex; ++ QPushButton *m_macAddressSearchButton; + }; + + #endif +diff --git a/smb4k/smb4ksharesmenu.cpp b/smb4k/smb4ksharesmenu.cpp +index 95bf5d902760..66ce30e43375 100644 +--- a/smb4k/smb4ksharesmenu.cpp ++++ b/smb4k/smb4ksharesmenu.cpp +@@ -97,7 +97,7 @@ void Smb4KSharesMenu::refreshMenu() + // Enable or disable the Unmount All action, depending on the number of + // mounted shares present. + // +- m_unmountAll->setEnabled(((!onlyForeignMountedShares() || Smb4KMountSettings::unmountForeignShares()) && !m_menus->actions().isEmpty())); ++ m_unmountAll->setEnabled((!onlyForeignMountedShares() && !m_menus->actions().isEmpty())); + + // + // Make sure the correct menu entries are shown +@@ -156,7 +156,7 @@ void Smb4KSharesMenu::addShareToMenu(const SharePtr &share) + unmountData[QStringLiteral("mountpoint")] = share->path(); + + unmount->setData(unmountData); +- unmount->setEnabled(!share->isForeign() || Smb4KMountSettings::unmountForeignShares()); ++ unmount->setEnabled(!share->isForeign()); + shareMenu->addAction(unmount); + m_actions->addAction(unmount); + +@@ -305,7 +305,7 @@ void Smb4KSharesMenu::slotMountedSharesListChanged() + // + // Enable or disable the Unmount All action + // +- m_unmountAll->setEnabled(((!onlyForeignMountedShares() || Smb4KMountSettings::unmountForeignShares()) && !m_menus->actions().isEmpty())); ++ m_unmountAll->setEnabled((!onlyForeignMountedShares() && !m_menus->actions().isEmpty())); + + // + // Make the separator visible, if necessary +diff --git a/smb4k/smb4ksharesviewdockwidget.cpp b/smb4k/smb4ksharesviewdockwidget.cpp +index cf511ed1656d..43f1bc41d103 100644 +--- a/smb4k/smb4ksharesviewdockwidget.cpp ++++ b/smb4k/smb4ksharesviewdockwidget.cpp +@@ -1,7 +1,7 @@ + /* + The network search widget dock widget + +- SPDX-FileCopyrightText: 2018-2023 Alexander Reinholdt ++ SPDX-FileCopyrightText: 2018-2024 Alexander Reinholdt + SPDX-License-Identifier: GPL-2.0-or-later + */ + +@@ -94,8 +94,7 @@ void Smb4KSharesViewDockWidget::loadSettings() + + if (selectedItems.size() == 1) { + Smb4KSharesViewItem *item = static_cast(selectedItems.first()); +- m_actionCollection->action(QStringLiteral("unmount_action")) +- ->setEnabled((!item->shareItem()->isForeign() || Smb4KMountSettings::unmountForeignShares())); ++ m_actionCollection->action(QStringLiteral("unmount_action"))->setEnabled(!item->shareItem()->isForeign()); + } else if (selectedItems.size() > 1) { + int foreign = 0; + +@@ -107,14 +106,11 @@ void Smb4KSharesViewDockWidget::loadSettings() + } + } + +- m_actionCollection->action(QStringLiteral("unmount_action")) +- ->setEnabled(((selectedItems.size() > foreign) || Smb4KMountSettings::unmountForeignShares())); ++ m_actionCollection->action(QStringLiteral("unmount_action"))->setEnabled((selectedItems.size() > foreign)); + } + } + +- actionCollection() +- ->action(QStringLiteral("unmount_all_action")) +- ->setEnabled(((!onlyForeignMountedShares() || Smb4KMountSettings::unmountForeignShares()) && m_sharesView->count() != 0)); ++ actionCollection()->action(QStringLiteral("unmount_all_action"))->setEnabled((!onlyForeignMountedShares() && m_sharesView->count() != 0)); + } + + void Smb4KSharesViewDockWidget::saveSettings() +@@ -293,8 +289,7 @@ void Smb4KSharesViewDockWidget::slotItemSelectionChanged() + Smb4KSharesViewItem *item = static_cast(selectedItems.first()); + bool syncRunning = Smb4KSynchronizer::self()->isRunning(QUrl::fromLocalFile(item->shareItem()->path())); + +- m_actionCollection->action(QStringLiteral("unmount_action")) +- ->setEnabled((!item->shareItem()->isForeign() || Smb4KMountSettings::unmountForeignShares())); ++ m_actionCollection->action(QStringLiteral("unmount_action"))->setEnabled(!item->shareItem()->isForeign()); + m_actionCollection->action(QStringLiteral("bookmark_action"))->setEnabled(true); + m_actionCollection->action(QStringLiteral("custom_action"))->setEnabled(true); + +@@ -334,8 +329,7 @@ void Smb4KSharesViewDockWidget::slotItemSelectionChanged() + } + } + +- m_actionCollection->action(QStringLiteral("unmount_action")) +- ->setEnabled(((selectedItems.size() > foreign) || Smb4KMountSettings::unmountForeignShares())); ++ m_actionCollection->action(QStringLiteral("unmount_action"))->setEnabled((selectedItems.size() > foreign)); + m_actionCollection->action(QStringLiteral("bookmark_action"))->setEnabled(true); + m_actionCollection->action(QStringLiteral("custom_action"))->setEnabled(true); + +@@ -365,11 +359,27 @@ void Smb4KSharesViewDockWidget::slotDropEvent(Smb4KSharesViewItem *item, QDropEv + if (e->mimeData()->hasUrls()) { + if (Smb4KHardwareInterface::self()->isOnline()) { + QUrl dest = QUrl::fromLocalFile(item->shareItem()->path()); ++ ++ // FIXME: Either modify the drop menu that it only shows the allowed ++ // drop actions or implement the following code. ++ // ++ // KIO::CopyJob *job = nullptr; ++ // ++ // if (e->proposedAction() == Qt::CopyAction) { ++ // job = KIO::copy(e->mimeData()->urls(), dest, KIO::DefaultFlags); ++ // } else if (e->proposedAction() == Qt::MoveAction) { ++ // job = KIO::move(e->mimeData()->urls(), dest, KIO::DefaultFlags); ++ // } else { ++ // job = KIO::copy(e->mimeData()->urls(), dest, KIO::DefaultFlags); ++ // } ++ + KIO::DropJob *job = KIO::drop(e, dest, KIO::DefaultFlags); +- KJobWidgets::setWindow(job, m_sharesView->viewport()); ++ ++ KJobWidgets::setWindow(job, m_sharesView); + job->uiDelegate()->setAutoErrorHandlingEnabled(true); + job->uiDelegate()->setAutoWarningHandlingEnabled(true); + } else { ++ // FIXME: Move this to the notifications. + KMessageBox::error( + m_sharesView, + i18n("There is no active connection to the share %1! You cannot drop any files here.", item->shareItem()->displayString())); +@@ -413,9 +423,7 @@ void Smb4KSharesViewDockWidget::slotShareMounted(const SharePtr &share) + m_sharesView->sortItems(Qt::AscendingOrder); + + // Enable/disable the 'Unmount All' action +- actionCollection() +- ->action(QStringLiteral("unmount_all_action")) +- ->setEnabled(((!onlyForeignMountedShares() || Smb4KMountSettings::unmountForeignShares()) && m_sharesView->count() != 0)); ++ actionCollection()->action(QStringLiteral("unmount_all_action"))->setEnabled((!onlyForeignMountedShares() && m_sharesView->count() != 0)); + } + } + +@@ -442,9 +450,7 @@ void Smb4KSharesViewDockWidget::slotShareUnmounted(const SharePtr &share) + } + + // Enable/disable the 'Unmount All' action +- actionCollection() +- ->action(QStringLiteral("unmount_all_action")) +- ->setEnabled(((!onlyForeignMountedShares() || Smb4KMountSettings::unmountForeignShares()) && m_sharesView->count() != 0)); ++ actionCollection()->action(QStringLiteral("unmount_all_action"))->setEnabled((!onlyForeignMountedShares() && m_sharesView->count() != 0)); + } + } + +-- +2.51.0 + diff -Nru smb4k-4.0.0/debian/patches/Merge-Smb4KHardwareInterface-class-from-master-so-th.patch smb4k-4.0.0/debian/patches/Merge-Smb4KHardwareInterface-class-from-master-so-th.patch --- smb4k-4.0.0/debian/patches/Merge-Smb4KHardwareInterface-class-from-master-so-th.patch 1970-01-01 00:00:00.000000000 +0000 +++ smb4k-4.0.0/debian/patches/Merge-Smb4KHardwareInterface-class-from-master-so-th.patch 2025-12-27 09:40:36.000000000 +0000 @@ -0,0 +1,260 @@ +From: Alexander Reinholdt +Date: Wed, 10 Dec 2025 11:29:38 +0100 +Subject: Merge Smb4KHardwareInterface class from master so that the merged + security fixes can be compiled. +Origin: https://git.kernel.org/linus/35f8cf121bfab276b739d4b8a866f8f3cdc0f7d1 +Bug-Debian: https://bugs.debian.org/1122381 + +--- + core/smb4khardwareinterface.cpp | 103 ++++++++++++++++++++++---------- + core/smb4khardwareinterface.h | 27 ++++++++- + 2 files changed, 97 insertions(+), 33 deletions(-) + +--- a/core/smb4khardwareinterface.cpp ++++ b/core/smb4khardwareinterface.cpp +@@ -42,7 +42,10 @@ public: + QDBusUnixFileDescriptor fileDescriptor; + bool systemOnline; + bool systemSleep; ++ bool initialImportDone; ++#if defined(Q_OS_LINUX) + QStringList udis; ++#endif + int timerId; + }; + +@@ -60,6 +63,7 @@ Smb4KHardwareInterface::Smb4KHardwareInt + { + d->systemOnline = false; + d->systemSleep = false; ++ d->initialImportDone = false; + d->fileDescriptor.setFileDescriptor(-1); + d->timerId = -1; + +@@ -84,35 +88,46 @@ Smb4KHardwareInterface::Smb4KHardwareInt + .connect(QString(), QString(), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("PrepareForSleep"), this, SLOT(slotSystemSleep(bool))); + + // +- // Get the initial list of udis for network shares +- // +- QList allDevices = Solid::Device::allDevices(); +- +- for (const Solid::Device &device : std::as_const(allDevices)) { +- const Solid::DeviceInterface *iface = device.asDeviceInterface(Solid::DeviceInterface::NetworkShare); +- const Solid::NetworkShare *networkShare = qobject_cast(iface); +- +- if (networkShare && (networkShare->type() == Solid::NetworkShare::Cifs || networkShare->type() == Solid::NetworkShare::Smb3)) { +- d->udis << device.udi(); +- } +- } +- +- // +- // Connections +- // +- connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &Smb4KHardwareInterface::slotDeviceAdded); +- connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &Smb4KHardwareInterface::slotDeviceRemoved); +- +- // + // Check the online state + // + checkOnlineState(false); + + // +- // Start the timer to continously check the online state +- // and, under FreeBSD, additionally the mounted shares. ++ // Get the initial list of CIFS/SMB3/SMBFS shares mounted ++ // on the system and then start the timer. + // +- d->timerId = startTimer(1000); ++ QTimer::singleShot(0, [&]() { ++#if defined(Q_OS_LINUX) ++ QList allDevices = Solid::Device::allDevices(); ++ ++ for (const Solid::Device &device : std::as_const(allDevices)) { ++ const Solid::DeviceInterface *iface = device.asDeviceInterface(Solid::DeviceInterface::NetworkShare); ++ const Solid::NetworkShare *networkShare = qobject_cast(iface); ++ ++ if (networkShare && (networkShare->type() == Solid::NetworkShare::Cifs || networkShare->type() == Solid::NetworkShare::Smb3)) { ++ d->udis << device.udi(); ++ QString mountpoint = device.udi().section(QStringLiteral(":"), -1, -1).trimmed(); ++ Q_EMIT networkShareAdded(mountpoint); ++ } ++ } ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions); ++ ++ for (const QExplicitlySharedDataPointer &mountPoint : mountPoints) { ++ if (mountPoint->mountType() == QStringLiteral("smbfs")) { ++ d->mountPoints.append(mountPoint->mountPoint()); ++ Q_EMIT networkShareAdded(mountPoint->mountPoint()); ++ } ++ } ++#endif ++ d->initialImportDone = true; ++ d->timerId = startTimer(1000); ++ }); ++ ++#if defined(Q_OS_LINUX) ++ connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &Smb4KHardwareInterface::slotDeviceAdded); ++ connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &Smb4KHardwareInterface::slotDeviceRemoved); ++#endif + } + + Smb4KHardwareInterface::~Smb4KHardwareInterface() +@@ -183,6 +198,24 @@ void Smb4KHardwareInterface::checkOnline + } + } + ++bool Smb4KHardwareInterface::initialImportDone() const ++{ ++ return d->initialImportDone; ++} ++ ++QStringList Smb4KHardwareInterface::allMountPoints() const ++{ ++ QStringList mountPoints; ++#if defined(Q_OS_LINUX) ++ for (const QString &udi : std::as_const(d->udis)) { ++ mountPoints << udi.section(QStringLiteral(":"), -1, -1).trimmed(); ++ } ++#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) ++ mountPoints = d->mountPoints; ++#endif ++ return mountPoints; ++} ++ + void Smb4KHardwareInterface::timerEvent(QTimerEvent *event) + { + Q_UNUSED(event); +@@ -205,22 +238,26 @@ void Smb4KHardwareInterface::timerEvent( + QMutableStringListIterator it(mountPointList); + + while (it.hasNext()) { +- QString mp = it.next(); ++ QString mountPoint = it.next(); + int index = -1; + +- if ((index = d->mountPoints.indexOf(mp)) != -1) { ++ if ((index = d->mountPoints.indexOf(mountPoint)) != -1) { + d->mountPoints.removeAt(index); +- alreadyMounted.append(mp); ++ alreadyMounted.append(mountPoint); + it.remove(); + } + } + + if (!d->mountPoints.isEmpty()) { +- Q_EMIT networkShareRemoved(); ++ for (const QString &mountPoint : std::as_const(d->mountPoints)) { ++ Q_EMIT networkShareRemoved(mountPoint); ++ } + } + + if (!mountPointList.isEmpty()) { +- Q_EMIT networkShareAdded(); ++ for (const QString &mountPoint : std::as_const(mountPointList)) { ++ Q_EMIT networkShareAdded(mountPoint); ++ } + } + + d->mountPoints.clear(); +@@ -229,6 +266,7 @@ void Smb4KHardwareInterface::timerEvent( + #endif + } + ++#if defined(Q_OS_LINUX) + void Smb4KHardwareInterface::slotDeviceAdded(const QString &udi) + { + Solid::Device device(udi); +@@ -238,17 +276,20 @@ void Smb4KHardwareInterface::slotDeviceA + + if (networkShare && (networkShare->type() == Solid::NetworkShare::Cifs || networkShare->type() == Solid::NetworkShare::Smb3)) { + d->udis << udi; +- Q_EMIT networkShareAdded(); ++ QString mountpoint = udi.section(QStringLiteral(":"), -1, -1).trimmed(); ++ Q_EMIT networkShareAdded(mountpoint); + } + } + + void Smb4KHardwareInterface::slotDeviceRemoved(const QString &udi) + { + if (d->udis.contains(udi)) { +- Q_EMIT networkShareRemoved(); ++ QString mountpoint = udi.section(QStringLiteral(":"), -1, -1).trimmed(); ++ Q_EMIT networkShareRemoved(mountpoint); + d->udis.removeOne(udi); + } + } ++#endif + + void Smb4KHardwareInterface::slotSystemSleep(bool sleep) + { +--- a/core/smb4khardwareinterface.h ++++ b/core/smb4khardwareinterface.h +@@ -50,6 +50,7 @@ public: + + /** + * This function returns TRUE if the system is online and FALSE otherwise. ++ * + * @returns TRUE if the system is online. + */ + bool isOnline() const; +@@ -64,6 +65,22 @@ public: + */ + void uninhibit(); + ++ /** ++ * This function returns TRUE if the initial list of mountpoints ++ * of all Samba shares is currently being imported. ++ * ++ * @returns TRUE if the the initial import is in progress ++ */ ++ bool initialImportDone() const; ++ ++ /** ++ * This function returns the list of all Samba shares mounted on ++ * the system. ++ * ++ * @returns the list of all mounted Samba shares. ++ */ ++ QStringList allMountPoints() const; ++ + protected: + /** + * Reimplemented from QObject to check the online state and to check +@@ -75,13 +92,17 @@ protected: + Q_SIGNALS: + /** + * This signal is emitted when a network share is added to the system ++ * ++ * @param mountpoint The mountpoint of the share + */ +- void networkShareAdded(); ++ void networkShareAdded(const QString &mountpoint); + + /** + * This signal is emitted when a network share is removed from the system ++ * ++ * @param mountpoint The mountpoint of the share + */ +- void networkShareRemoved(); ++ void networkShareRemoved(const QString &mountpoint); + + /** + * This signal is emitted when the online state changed. +@@ -90,6 +111,7 @@ Q_SIGNALS: + void onlineStateChanged(bool online); + + protected Q_SLOTS: ++#if defined(Q_OS_LINUX) + /** + * This slot is called when a device was added to the system. + * @param udi the device UDI +@@ -101,6 +123,7 @@ protected Q_SLOTS: + * @param udi the device UDI + */ + void slotDeviceRemoved(const QString &udi); ++#endif + + /** + * This slot is called when the system prepares for sleep or has diff -Nru smb4k-4.0.0/debian/patches/series smb4k-4.0.0/debian/patches/series --- smb4k-4.0.0/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ smb4k-4.0.0/debian/patches/series 2025-12-27 09:40:36.000000000 +0000 @@ -0,0 +1,4 @@ +CVE-2025-66002_CVE-2025-66003-1.patch +CVE-2025-66002_CVE-2025-66003-2.patch +CVE-2025-66002_CVE-2025-66003-3.patch +Merge-Smb4KHardwareInterface-class-from-master-so-th.patch