File indexing completed on 2024-04-14 04:56:25

0001 /*
0002     The core class that mounts the shares.
0003 
0004     SPDX-FileCopyrightText: 2003-2023 Alexander Reinholdt <alexander.reinholdt@kdemail.net>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // Application specific includes
0009 #include "smb4kmounter.h"
0010 #include "smb4kauthinfo.h"
0011 #include "smb4kbookmark.h"
0012 #include "smb4kbookmarkhandler.h"
0013 #include "smb4kcustomsettings.h"
0014 #include "smb4kcustomsettingsmanager.h"
0015 #include "smb4khardwareinterface.h"
0016 #include "smb4khomesshareshandler.h"
0017 #include "smb4knotification.h"
0018 #include "smb4kprofilemanager.h"
0019 #include "smb4ksettings.h"
0020 #include "smb4kshare.h"
0021 #include "smb4kwalletmanager.h"
0022 #include "smb4kworkgroup.h"
0023 
0024 #if defined(Q_OS_LINUX)
0025 #include "smb4kmountsettings_linux.h"
0026 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
0027 #include "smb4kmountsettings_bsd.h"
0028 #endif
0029 
0030 // Qt includes
0031 #include <QApplication>
0032 #include <QDebug>
0033 #include <QDir>
0034 #include <QFileInfo>
0035 #include <QHostInfo>
0036 #include <QPointer>
0037 #include <QStorageInfo>
0038 #include <QTimer>
0039 #include <QUdpSocket>
0040 
0041 // KDE includes
0042 #include <KAuth/ExecuteJob>
0043 #include <KLocalizedString>
0044 #include <KMessageBox>
0045 #include <KMountPoint>
0046 #include <KShell>
0047 #include <KUser>
0048 #include <kauth_version.h>
0049 
0050 using namespace Smb4KGlobal;
0051 
0052 #define TIMEOUT 50
0053 
0054 class Smb4KMounterPrivate
0055 {
0056 public:
0057     int remountTimeout;
0058     int remountAttempts;
0059     int timerId;
0060     int checkTimeout;
0061     int newlyMounted;
0062     int newlyUnmounted;
0063     QList<SharePtr> importedShares;
0064     QList<SharePtr> retries;
0065     QList<SharePtr> remounts;
0066     bool detectAllShares;
0067     bool firstImportDone;
0068     bool longActionRunning;
0069     QStorageInfo storageInfo;
0070 };
0071 
0072 class Smb4KMounterStatic
0073 {
0074 public:
0075     Smb4KMounter instance;
0076 };
0077 
0078 Q_GLOBAL_STATIC(Smb4KMounterStatic, p);
0079 
0080 Smb4KMounter::Smb4KMounter(QObject *parent)
0081     : KCompositeJob(parent)
0082     , d(new Smb4KMounterPrivate)
0083 {
0084     setAutoDelete(false);
0085 
0086     d->timerId = -1;
0087     d->remountTimeout = 0;
0088     d->remountAttempts = 0;
0089     d->checkTimeout = 0;
0090     d->newlyMounted = 0;
0091     d->newlyUnmounted = 0;
0092     d->firstImportDone = false;
0093     d->longActionRunning = false;
0094     d->detectAllShares = Smb4KMountSettings::detectAllShares();
0095 
0096     //
0097     // Connections
0098     //
0099     connect(Smb4KProfileManager::self(), &Smb4KProfileManager::aboutToChangeProfile, this, &Smb4KMounter::slotAboutToChangeProfile);
0100     connect(Smb4KProfileManager::self(), &Smb4KProfileManager::activeProfileChanged, this, &Smb4KMounter::slotActiveProfileChanged);
0101     connect(Smb4KWalletManager::self(), &Smb4KWalletManager::credentialsUpdated, this, &Smb4KMounter::slotCredentialsUpdated);
0102     connect(Smb4KMountSettings::self(), &Smb4KMountSettings::configChanged, this, &Smb4KMounter::slotConfigChanged);
0103 
0104     connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &Smb4KMounter::slotAboutToQuit);
0105 }
0106 
0107 Smb4KMounter::~Smb4KMounter()
0108 {
0109     while (!d->importedShares.isEmpty()) {
0110         d->importedShares.takeFirst().clear();
0111     }
0112 
0113     while (!d->retries.isEmpty()) {
0114         d->retries.takeFirst().clear();
0115     }
0116 }
0117 
0118 Smb4KMounter *Smb4KMounter::self()
0119 {
0120     return &p->instance;
0121 }
0122 
0123 void Smb4KMounter::abort()
0124 {
0125     if (!QCoreApplication::closingDown()) {
0126         QListIterator<KJob *> it(subjobs());
0127 
0128         while (it.hasNext()) {
0129             it.next()->kill(KJob::EmitResult);
0130         }
0131     }
0132 }
0133 
0134 bool Smb4KMounter::isRunning()
0135 {
0136     return (hasSubjobs() || d->longActionRunning);
0137 }
0138 
0139 void Smb4KMounter::triggerRemounts(bool fillList)
0140 {
0141     if (fillList) {
0142         //
0143         // Get the list of shares that are to be remounted
0144         //
0145         QList<CustomSettingsPtr> options = Smb4KCustomSettingsManager::self()->sharesToRemount();
0146 
0147         //
0148         // Process the list and honor the settings the user chose
0149         //
0150         for (const CustomSettingsPtr &option : qAsConst(options)) {
0151             //
0152             // Skip one time remount shares, if needed
0153             //
0154             if (option->remount() == Smb4KCustomSettings::RemountOnce && !Smb4KMountSettings::remountShares()) {
0155                 continue;
0156             }
0157 
0158             //
0159             // Check which share has to be remounted
0160             //
0161             QList<SharePtr> mountedShares = findShareByUrl(option->url());
0162             bool remountShare = true;
0163 
0164             for (const SharePtr &share : qAsConst(mountedShares)) {
0165                 if (!share->isForeign()) {
0166                     remountShare = false;
0167                     break;
0168                 }
0169             }
0170 
0171             //
0172             // Insert the share to the list of remounts
0173             //
0174             if (remountShare) {
0175                 bool insertShare = true;
0176 
0177                 for (const SharePtr &share : qAsConst(d->remounts)) {
0178                     if (QString::compare(share->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
0179                                          option->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort))
0180                         == 0) {
0181                         insertShare = false;
0182                         break;
0183                     }
0184                 }
0185 
0186                 if (insertShare) {
0187                     SharePtr share = SharePtr(new Smb4KShare());
0188                     share->setUrl(option->url());
0189                     share->setWorkgroupName(option->workgroupName());
0190                     share->setHostIpAddress(option->ipAddress());
0191 
0192                     if (share->url().isValid() && !share->url().isEmpty()) {
0193                         d->remounts << share;
0194                     }
0195                 }
0196             }
0197         }
0198     }
0199 
0200     //
0201     // Remount the shares
0202     //
0203     mountShares(d->remounts);
0204 
0205     //
0206     // Count the remount attempts
0207     //
0208     d->remountAttempts++;
0209 }
0210 
0211 void Smb4KMounter::import(bool checkInaccessible)
0212 {
0213     //
0214     // Immediately return here if we are still processing imported shares
0215     //
0216     if (!d->importedShares.isEmpty()) {
0217         return;
0218     }
0219 
0220     //
0221     // Boolean to determine at the end if the mounted shares list changed
0222     //
0223     bool changed = false;
0224 
0225     //
0226     // Get the mountpoints that are present on the system
0227     //
0228     KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions);
0229 
0230     //
0231     // Now determine all mountpoints that have the appropriate filesystem.
0232     //
0233     for (const QExplicitlySharedDataPointer<KMountPoint> &mountPoint : qAsConst(mountPoints)) {
0234         if (mountPoint->mountType() == QStringLiteral("cifs") || mountPoint->mountType() == QStringLiteral("smb3")
0235             || mountPoint->mountType() == QStringLiteral("smbfs")) {
0236             // Create a new share and set the mountpoint and filesystem
0237             SharePtr share = SharePtr(new Smb4KShare());
0238             share->setUrl(QUrl(mountPoint->mountedFrom()));
0239             share->setPath(mountPoint->mountPoint());
0240             share->setMounted(true);
0241 
0242             // Get all mount options
0243             QStringList mountOptions = mountPoint->mountOptions();
0244 
0245             for (const QString &option : qAsConst(mountOptions)) {
0246                 if (option.startsWith(QStringLiteral("domain=")) || option.startsWith(QStringLiteral("workgroup="))) {
0247                     share->setWorkgroupName(option.section(QStringLiteral("="), 1, 1).trimmed());
0248                 } else if (option.startsWith(QStringLiteral("addr="))) {
0249                     share->setHostIpAddress(option.section(QStringLiteral("="), 1, 1).trimmed());
0250                 } else if (option.startsWith(QStringLiteral("username=")) || option.startsWith(QStringLiteral("user="))) {
0251                     share->setUserName(option.section(QStringLiteral("="), 1, 1).trimmed());
0252                 }
0253             }
0254 
0255             // Work around empty usernames
0256             if (share->userName().isEmpty()) {
0257                 share->setUserName(QStringLiteral("guest"));
0258             }
0259 
0260             d->importedShares << share;
0261         }
0262     }
0263 
0264     //
0265     // Check which shares were unmounted. Remove all obsolete mountpoints, emit
0266     // the unmounted() signal on each of the unmounted shares and remove them
0267     // from the global list.
0268     //
0269     QList<SharePtr> unmountedShares;
0270 
0271     if (!d->importedShares.isEmpty()) {
0272         bool found = false;
0273 
0274         for (const SharePtr &mountedShare : qAsConst(mountedSharesList())) {
0275             for (const SharePtr &importedShare : qAsConst(d->importedShares)) {
0276                 // Check the mountpoint, since that one is unique. We will only use
0277                 // Smb4KShare::path(), so that we do not run into trouble if a share
0278                 // is inaccessible.
0279                 if (QString::compare(mountedShare->path(), importedShare->path()) == 0) {
0280                     found = true;
0281                     break;
0282                 }
0283             }
0284 
0285             if (!found) {
0286                 unmountedShares << mountedShare;
0287             }
0288 
0289             found = false;
0290         }
0291     } else {
0292         unmountedShares << mountedSharesList();
0293     }
0294 
0295     //
0296     // Process the unmounted shares
0297     //
0298     if (!unmountedShares.isEmpty()) {
0299         d->newlyUnmounted += unmountedShares.size();
0300 
0301         for (const SharePtr &share : qAsConst(unmountedShares)) {
0302             //
0303             // Remove the mountpoint if the share is not a foreign one
0304             //
0305             if (!share->isForeign()) {
0306                 QDir dir;
0307                 dir.cd(share->canonicalPath());
0308                 dir.rmdir(dir.canonicalPath());
0309 
0310                 if (dir.cdUp()) {
0311                     dir.rmdir(dir.canonicalPath());
0312                 }
0313             }
0314 
0315             //
0316             // Mark it as unmounted
0317             //
0318             share->setMounted(false);
0319 
0320             //
0321             // Copy the share
0322             //
0323             SharePtr unmountedShare = share;
0324 
0325             //
0326             // Remove the share from the global list and notify the program
0327             //
0328             if (removeMountedShare(share)) {
0329                 changed = true;
0330                 Q_EMIT unmounted(unmountedShare);
0331             }
0332 
0333             //
0334             // Report the unmounted share to the user if it is a single one
0335             //
0336             if (!isRunning() && d->newlyUnmounted == 1) {
0337                 Smb4KNotification::shareUnmounted(unmountedShare);
0338             }
0339 
0340             unmountedShare.clear();
0341         }
0342 
0343         //
0344         // Report the number of unmounted shares to the user if it are
0345         // several ones
0346         //
0347         if (d->newlyUnmounted > 1) {
0348             Smb4KNotification::sharesUnmounted(d->newlyUnmounted);
0349         }
0350 
0351         //
0352         // Reset the number of newly unmounted shares
0353         //
0354         d->newlyUnmounted = 0;
0355     } else {
0356         //
0357         // Reset the number of newly unmounted shares
0358         //
0359         d->newlyUnmounted = 0;
0360     }
0361 
0362     //
0363     // Now process the imported shares
0364     //
0365     if (Smb4KHardwareInterface::self()->isOnline()) {
0366         while (!d->importedShares.isEmpty()) {
0367             SharePtr importedShare = d->importedShares.takeFirst();
0368             SharePtr mountedShare = findShareByPath(importedShare->path());
0369 
0370             if (mountedShare) {
0371                 if (mountedShare->isInaccessible() && !checkInaccessible) {
0372                     importedShare.clear();
0373                     continue;
0374                 }
0375             }
0376 
0377             //
0378             // Check the imported share and retrieve some information about it
0379             //
0380             check(importedShare);
0381 
0382             //
0383             // Find out whether this is a share mounted by the user
0384             //
0385             if (importedShare->path().startsWith(Smb4KMountSettings::mountPrefix().path())
0386                 || (!importedShare->isInaccessible()
0387                     && importedShare->canonicalPath().startsWith(QDir(Smb4KMountSettings::mountPrefix().path()).canonicalPath()))) {
0388                 importedShare->setForeign(false);
0389             } else if (importedShare->path().startsWith(QDir::homePath())
0390                        || (!importedShare->isInaccessible() && importedShare->canonicalPath().startsWith(QDir::home().canonicalPath()))) {
0391                 importedShare->setForeign(false);
0392             } else if (importedShare->user().userId() == KUser(KUser::UseRealUserID).userId()
0393                        && importedShare->group().groupId() == KUserGroup(KUser::UseRealUserID).groupId()) {
0394                 importedShare->setForeign(false);
0395             } else {
0396                 importedShare->setForeign(true);
0397             }
0398 
0399             //
0400             // Search for a previously added mounted share and try to update it. If this fails,
0401             // add the share to the global list.
0402             //
0403             if (!importedShare->isForeign() || Smb4KMountSettings::detectAllShares()) {
0404                 if (updateMountedShare(importedShare)) {
0405                     SharePtr updatedShare = findShareByPath(importedShare->path());
0406 
0407                     if (updatedShare) {
0408                         Q_EMIT updated(updatedShare);
0409                     }
0410 
0411                     importedShare.clear();
0412                 } else {
0413                     if (addMountedShare(importedShare)) {
0414                         changed = true;
0415 
0416                         // Remove the share from the list of shares that are to be remounted
0417                         QMutableListIterator<SharePtr> s(d->remounts);
0418 
0419                         while (s.hasNext()) {
0420                             SharePtr remount = s.next();
0421 
0422                             if (!importedShare->isForeign()
0423                                 && QString::compare(remount->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
0424                                                     importedShare->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
0425                                                     Qt::CaseInsensitive)
0426                                     == 0) {
0427                                 Smb4KCustomSettingsManager::self()->removeRemount(remount);
0428                                 s.remove();
0429                                 break;
0430                             } else {
0431                                 continue;
0432                             }
0433                         }
0434 
0435                         Q_EMIT mounted(importedShare);
0436 
0437                         // Do not notify the user on first import
0438                         if (d->firstImportDone) {
0439                             d->newlyMounted += 1;
0440 
0441                             if (!isRunning() && d->importedShares.isEmpty()) {
0442                                 if (d->newlyMounted > 1) {
0443                                     Smb4KNotification::sharesMounted(d->newlyMounted);
0444                                 } else {
0445                                     Smb4KNotification::shareMounted(importedShare);
0446                                 }
0447                                 d->newlyMounted = 0;
0448                             }
0449                         }
0450                     } else {
0451                         d->newlyMounted = 0;
0452                         importedShare.clear();
0453                     }
0454                 }
0455             } else {
0456                 importedShare.clear();
0457             }
0458         }
0459 
0460         if (!d->firstImportDone && d->importedShares.isEmpty()) {
0461             d->firstImportDone = true;
0462         }
0463     } else {
0464         //
0465         // When the system is offline, no mounted shares are processed, so
0466         // empty the list of imported shares here.
0467         //
0468         while (!d->importedShares.isEmpty()) {
0469             SharePtr share = d->importedShares.takeFirst();
0470             share.clear();
0471         }
0472     }
0473 
0474     //
0475     // Tell the program the list of mounted shares changed
0476     //
0477     if (changed) {
0478         Q_EMIT mountedSharesListChanged();
0479     }
0480 }
0481 
0482 void Smb4KMounter::mountShare(const SharePtr &share)
0483 {
0484     if (share) {
0485         //
0486         // Check that the URL is valid
0487         //
0488         if (!share->url().isValid()) {
0489             Smb4KNotification::invalidURLPassed();
0490             return;
0491         }
0492 
0493         //
0494         // Check if the share has already been mounted. If it is already present,
0495         // do not process it and return.
0496         //
0497         QUrl url;
0498 
0499         if (share->isHomesShare()) {
0500             url = share->homeUrl();
0501         } else {
0502             url = share->url();
0503         }
0504 
0505         QList<SharePtr> mountedShares = findShareByUrl(url);
0506         bool isMounted = false;
0507 
0508         for (const SharePtr &s : qAsConst(mountedShares)) {
0509             if (!s->isForeign()) {
0510                 isMounted = true;
0511                 break;
0512             }
0513         }
0514 
0515         if (isMounted) {
0516             return;
0517         }
0518 
0519         //
0520         // Wake-On-LAN: Wake up the host before mounting
0521         //
0522         if (Smb4KSettings::enableWakeOnLAN()) {
0523             CustomSettingsPtr customSettings = Smb4KCustomSettingsManager::self()->findCustomSettings(share->url().resolved(QUrl(QStringLiteral(".."))));
0524 
0525             if (customSettings && customSettings->wakeOnLanSendBeforeMount()) {
0526                 Q_EMIT aboutToStart(WakeUp);
0527 
0528                 QUdpSocket *socket = new QUdpSocket(this);
0529                 QHostAddress addr;
0530 
0531                 // Use the host's IP address directly from the share object.
0532                 if (share->hasHostIpAddress()) {
0533                     addr.setAddress(share->hostIpAddress());
0534                 } else {
0535                     addr.setAddress(QStringLiteral("255.255.255.255"));
0536                 }
0537 
0538                 // Construct magic sequence
0539                 QByteArray sequence;
0540 
0541                 // 6 times 0xFF
0542                 for (int j = 0; j < 6; ++j) {
0543                     sequence.append(QChar(0xFF).toLatin1());
0544                 }
0545 
0546                 // 16 times the MAC address
0547                 QStringList parts = customSettings->macAddress().split(QStringLiteral(":"), Qt::SkipEmptyParts);
0548 
0549                 for (int j = 0; j < 16; ++j) {
0550                     for (int k = 0; k < parts.size(); ++k) {
0551                         QString item = QStringLiteral("0x") + parts.at(k);
0552                         sequence.append(QChar(item.toInt(nullptr, 16)).toLatin1());
0553                     }
0554                 }
0555 
0556                 socket->writeDatagram(sequence, addr, 9);
0557 
0558                 delete socket;
0559 
0560                 // Wait the defined time
0561                 int stop = 1000 * Smb4KSettings::wakeOnLANWaitingTime() / 250;
0562                 int i = 0;
0563 
0564                 while (i++ < stop) {
0565                     wait(250);
0566                 }
0567 
0568                 Q_EMIT finished(WakeUp);
0569             }
0570         }
0571 
0572         //
0573         // Create the mountpoint
0574         //
0575         QString mountpoint;
0576         mountpoint += Smb4KMountSettings::mountPrefix().path();
0577         mountpoint += QDir::separator();
0578         mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->hostName().toLower() : share->hostName());
0579         mountpoint += QDir::separator();
0580 
0581         if (!share->isHomesShare()) {
0582             mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->shareName().toLower() : share->shareName());
0583         } else {
0584             mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->userName().toLower() : share->userName());
0585         }
0586 
0587         // Get the permissions that should be used for creating the
0588         // mount prefix and all its subdirectories.
0589         // Please note that the actual permissions of the mount points
0590         // are determined by the mount utility.
0591         QFile::Permissions permissions;
0592         QUrl parentDirectory;
0593 
0594         if (QFile::exists(Smb4KMountSettings::mountPrefix().path())) {
0595             parentDirectory = Smb4KMountSettings::mountPrefix();
0596         } else {
0597             QUrl u = Smb4KMountSettings::mountPrefix();
0598             parentDirectory = u.resolved(QUrl(QStringLiteral("..")));
0599             ;
0600         }
0601 
0602         QFile f(parentDirectory.path());
0603         permissions = f.permissions();
0604 
0605         QDir dir(mountpoint);
0606 
0607         if (!dir.mkpath(dir.path())) {
0608             share->setPath(QStringLiteral(""));
0609             Smb4KNotification::mkdirFailed(dir);
0610             return;
0611         } else {
0612             QUrl u = QUrl::fromLocalFile(dir.path());
0613 
0614             while (!parentDirectory.matches(u, QUrl::StripTrailingSlash)) {
0615                 QFile(u.path()).setPermissions(permissions);
0616                 u = u.resolved(QUrl(QStringLiteral("..")));
0617             }
0618         }
0619 
0620         share->setPath(QDir::cleanPath(mountpoint));
0621 
0622         //
0623         // Get the authentication information
0624         //
0625         Smb4KWalletManager::self()->readLoginCredentials(share);
0626 
0627         //
0628         // Mount arguments
0629         //
0630         QVariantMap args;
0631 
0632         if (!fillMountActionArgs(share, args)) {
0633             return;
0634         }
0635 
0636         //
0637         // Emit the aboutToStart() signal
0638         //
0639         Q_EMIT aboutToStart(MountShare);
0640 
0641         //
0642         // Create the mount action
0643         //
0644         KAuth::Action mountAction(QStringLiteral("org.kde.smb4k.mounthelper.mount"));
0645         mountAction.setHelperId(QStringLiteral("org.kde.smb4k.mounthelper"));
0646         mountAction.setArguments(args);
0647 
0648         KAuth::ExecuteJob *job = mountAction.execute();
0649 
0650         //
0651         // Modify the cursor, if necessary.
0652         //
0653         if (!hasSubjobs()) {
0654             QApplication::setOverrideCursor(Qt::BusyCursor);
0655         }
0656 
0657         //
0658         // Add the job
0659         //
0660         addSubjob(job);
0661 
0662         //
0663         // Start the job and process the returned result.
0664         //
0665         bool success = job->exec();
0666 
0667         if (success) {
0668             int errorCode = job->error();
0669 
0670             if (errorCode == 0) {
0671                 // Get the error message
0672                 QString errorMsg = job->data().value(QStringLiteral("mh_error_message")).toString();
0673 
0674                 if (!errorMsg.isEmpty()) {
0675 #if defined(Q_OS_LINUX)
0676                     if (errorMsg.contains(QStringLiteral("mount error 13"))
0677                         || errorMsg.contains(QStringLiteral("mount error(13)")) /* authentication error */) {
0678                         d->retries << share;
0679                         Q_EMIT requestCredentials(share);
0680                     } else if (errorMsg.contains(QStringLiteral("Unable to find suitable address."))) {
0681                         // Swallow this
0682                     } else {
0683                         Smb4KNotification::mountingFailed(share, errorMsg);
0684                     }
0685 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
0686                     if (errorMsg.contains(QStringLiteral("Authentication error")) || errorMsg.contains(QStringLiteral("Permission denied"))) {
0687                         d->retries << share;
0688                         Q_EMIT requestCredentials(share);
0689                     } else {
0690                         Smb4KNotification::mountingFailed(share, errorMsg);
0691                     }
0692 #else
0693                     qWarning() << "Smb4KMounter::slotMountJobFinished(): Error handling not implemented!";
0694                     Smb4KNotification::mountingFailed(share, errorMsg);
0695 #endif
0696                 }
0697             } else {
0698                 Smb4KNotification::actionFailed(errorCode);
0699             }
0700         } else {
0701             // FIXME: Report that the action could not be started
0702         }
0703 
0704         //
0705         // Remove the job from the job list
0706         //
0707         removeSubjob(job);
0708 
0709         //
0710         // Reset the busy cursor
0711         //
0712         if (!hasSubjobs()) {
0713             QApplication::restoreOverrideCursor();
0714         }
0715 
0716         //
0717         // Emit the finished() signal
0718         //
0719         Q_EMIT finished(MountShare);
0720     }
0721 }
0722 
0723 void Smb4KMounter::mountShares(const QList<SharePtr> &shares)
0724 {
0725     //
0726     // This action takes longer
0727     //
0728     d->longActionRunning = true;
0729 
0730     //
0731     // Mount the shares
0732     //
0733     for (const SharePtr &share : shares) {
0734         mountShare(share);
0735     }
0736 
0737     //
0738     // This action is over
0739     //
0740     d->longActionRunning = false;
0741 }
0742 
0743 void Smb4KMounter::unmountShare(const SharePtr &share, bool silent)
0744 {
0745     Q_ASSERT(share);
0746 
0747     if (share) {
0748         //
0749         // Check that the URL is valid.
0750         //
0751         if (!share->url().isValid()) {
0752             Smb4KNotification::invalidURLPassed();
0753             return;
0754         }
0755 
0756         //
0757         // Handle foreign shares according to the settings
0758         //
0759         if (share->isForeign()) {
0760             if (!Smb4KMountSettings::unmountForeignShares()) {
0761                 if (!silent) {
0762                     Smb4KNotification::unmountingNotAllowed(share);
0763                 }
0764 
0765                 return;
0766             } else {
0767                 if (!silent) {
0768                     if (KMessageBox::warningTwoActions(QApplication::activeWindow(),
0769                                                        i18n("<p>The share <b>%1</b> is mounted to <br><b>%2</b> and owned by user <b>%3</b>.</p>"
0770                                                             "<p>Do you really want to unmount it?</p>",
0771                                                             share->displayString(),
0772                                                             share->path(),
0773                                                             share->user().loginName()),
0774                                                        i18n("Foreign Share"),
0775                                                        KStandardGuiItem::ok(),
0776                                                        KStandardGuiItem::cancel())
0777                         == KMessageBox::SecondaryAction) {
0778                         return;
0779                     }
0780                 } else {
0781                     // Without the confirmation of the user, we are not
0782                     // unmounting a foreign share!
0783                     return;
0784                 }
0785             }
0786         }
0787 
0788         //
0789         // Force the unmounting of the share either if the system went offline
0790         // or if the user chose to forcibly unmount inaccessible shares (Linux only).
0791         //
0792         bool force = false;
0793 
0794         if (Smb4KHardwareInterface::self()->isOnline()) {
0795 #if defined(Q_OS_LINUX)
0796             if (share->isInaccessible()) {
0797                 force = Smb4KMountSettings::forceUnmountInaccessible();
0798             }
0799 #endif
0800         } else {
0801             force = true;
0802         }
0803 
0804         //
0805         // Unmount arguments
0806         //
0807         QVariantMap args;
0808 
0809         if (!fillUnmountActionArgs(share, force, silent, args)) {
0810             return;
0811         }
0812 
0813         //
0814         // Emit the aboutToStart() signal
0815         //
0816         Q_EMIT aboutToStart(UnmountShare);
0817 
0818         //
0819         // Create the unmount action
0820         //
0821         KAuth::Action unmountAction(QStringLiteral("org.kde.smb4k.mounthelper.unmount"));
0822         unmountAction.setHelperId(QStringLiteral("org.kde.smb4k.mounthelper"));
0823         unmountAction.setArguments(args);
0824 
0825         KAuth::ExecuteJob *job = unmountAction.execute();
0826 
0827         //
0828         // Modify the cursor, if necessary.
0829         //
0830         if (!hasSubjobs()) {
0831             QApplication::setOverrideCursor(Qt::BusyCursor);
0832         }
0833 
0834         //
0835         // Add the job
0836         //
0837         addSubjob(job);
0838 
0839         //
0840         // Start the job and process the returned result.
0841         //
0842         bool success = job->exec();
0843 
0844         if (success) {
0845             int errorCode = job->error();
0846 
0847             if (errorCode == 0) {
0848                 // Get the error message
0849                 QString errorMsg = job->data().value(QStringLiteral("mh_error_message")).toString();
0850 
0851                 if (!errorMsg.isEmpty()) {
0852                     // No error handling needed, just report the error message.
0853                     Smb4KNotification::unmountingFailed(share, errorMsg);
0854                 }
0855             } else {
0856                 Smb4KNotification::actionFailed(errorCode);
0857             }
0858         } else {
0859             // FIXME: Report that the action could not be started
0860         }
0861 
0862         //
0863         // Remove the job from the job list
0864         //
0865         removeSubjob(job);
0866 
0867         //
0868         // Reset the busy cursor
0869         //
0870         if (!hasSubjobs()) {
0871             QApplication::restoreOverrideCursor();
0872         }
0873 
0874         //
0875         // Emit the finished() signal
0876         //
0877         Q_EMIT finished(UnmountShare);
0878     }
0879 }
0880 
0881 void Smb4KMounter::unmountShares(const QList<SharePtr> &shares, bool silent)
0882 {
0883     //
0884     // This action takes longer
0885     //
0886     d->longActionRunning = true;
0887 
0888     //
0889     // Inhibit shutdown and sleep
0890     //
0891     Smb4KHardwareInterface::self()->inhibit();
0892 
0893     //
0894     // Unmount the list of shares
0895     //
0896     for (const SharePtr &share : shares) {
0897         // Unmount the share
0898         unmountShare(share, silent);
0899     }
0900 
0901     //
0902     // Uninhibit shutdown and sleep
0903     //
0904     Smb4KHardwareInterface::self()->uninhibit();
0905 
0906     //
0907     // This action is over
0908     //
0909     d->longActionRunning = false;
0910 }
0911 
0912 void Smb4KMounter::unmountAllShares(bool silent)
0913 {
0914     unmountShares(mountedSharesList(), silent);
0915 }
0916 
0917 void Smb4KMounter::start()
0918 {
0919     //
0920     // Connect to the relevant signals provided by Smb4KHardwareInterface.
0921     //
0922     connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::onlineStateChanged, this, &Smb4KMounter::slotOnlineStateChanged, Qt::UniqueConnection);
0923     connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::networkShareAdded, this, &Smb4KMounter::slotTriggerImport, Qt::UniqueConnection);
0924     connect(Smb4KHardwareInterface::self(), &Smb4KHardwareInterface::networkShareRemoved, this, &Smb4KMounter::slotTriggerImport, Qt::UniqueConnection);
0925 
0926     //
0927     // Start with importing shares
0928     //
0929     if (Smb4KHardwareInterface::self()->isOnline()) {
0930         QTimer::singleShot(50, this, SLOT(slotStartJobs()));
0931     }
0932 }
0933 
0934 void Smb4KMounter::saveSharesForRemount()
0935 {
0936     //
0937     // Save the shares for remount
0938     //
0939     for (const SharePtr &share : mountedSharesList()) {
0940         if (!share->isForeign()) {
0941             Smb4KCustomSettingsManager::self()->addRemount(share, false);
0942         } else {
0943             Smb4KCustomSettingsManager::self()->removeRemount(share, false);
0944         }
0945     }
0946 
0947     //
0948     // Also save each failed remount and remove it from the list
0949     //
0950     while (!d->remounts.isEmpty()) {
0951         SharePtr share = d->remounts.takeFirst();
0952         Smb4KCustomSettingsManager::self()->addRemount(share, false);
0953         share.clear();
0954     }
0955 }
0956 
0957 void Smb4KMounter::timerEvent(QTimerEvent *)
0958 {
0959     if (!isRunning() && Smb4KHardwareInterface::self()->isOnline()) {
0960         //
0961         // Try to remount shares
0962         //
0963         if (d->remountAttempts < Smb4KMountSettings::remountAttempts() && d->firstImportDone) {
0964             if (d->remountAttempts == 0) {
0965                 triggerRemounts(true);
0966             }
0967 
0968             if ((60000 * Smb4KMountSettings::remountInterval()) < d->remountTimeout) {
0969                 triggerRemounts(false);
0970                 d->remountTimeout = -TIMEOUT;
0971             }
0972 
0973             d->remountTimeout += TIMEOUT;
0974         }
0975 
0976         //
0977         // Check the size, accessibility, etc. of the shares
0978         //
0979         if (d->checkTimeout >= 2500 && d->importedShares.isEmpty()) {
0980             for (const SharePtr &share : mountedSharesList()) {
0981                 check(share);
0982                 Q_EMIT updated(share);
0983             }
0984 
0985             d->checkTimeout = 0;
0986         } else {
0987             d->checkTimeout += TIMEOUT;
0988         }
0989     }
0990 }
0991 
0992 #if defined(Q_OS_LINUX)
0993 //
0994 // Linux arguments
0995 //
0996 bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, QVariantMap &map)
0997 {
0998     //
0999     // Find the mount executable
1000     //
1001     const QString mount = findMountExecutable();
1002 
1003     if (!mount.isEmpty()) {
1004         map.insert(QStringLiteral("mh_command"), mount);
1005     } else {
1006         Smb4KNotification::commandNotFound(QStringLiteral("mount.cifs"));
1007         return false;
1008     }
1009 
1010     //
1011     // Global and custom options
1012     //
1013     CustomSettingsPtr options = Smb4KCustomSettingsManager::self()->findCustomSettings(share);
1014 
1015     //
1016     // Pass the remote file system port to the URL
1017     //
1018     if (options) {
1019         if (options->useFileSystemPort()) {
1020             share->setPort(options->fileSystemPort());
1021         }
1022     } else {
1023         if (Smb4KMountSettings::useRemoteFileSystemPort()) {
1024             share->setPort(Smb4KMountSettings::remoteFileSystemPort());
1025         }
1026     }
1027 
1028     //
1029     // List of arguments passed via "-o ..." to the mount command
1030     //
1031     QStringList argumentsList;
1032 
1033     //
1034     // Workgroup or domain
1035     //
1036     // Do not use this, if the domain is a DNS domain.
1037     //
1038     WorkgroupPtr workgroup = findWorkgroup(share->workgroupName());
1039 
1040     if ((workgroup && !workgroup->dnsDiscovered()) || (!workgroup && !share->workgroupName().trimmed().isEmpty())) {
1041         argumentsList << QStringLiteral("domain=") + KShell::quoteArg(share->workgroupName());
1042     }
1043 
1044     //
1045     // Host IP address
1046     //
1047     if (share->hasHostIpAddress()) {
1048         argumentsList << QStringLiteral("ip=") + share->hostIpAddress();
1049     }
1050 
1051     //
1052     // User name (login)
1053     //
1054     if (!share->userName().isEmpty()) {
1055         argumentsList << QStringLiteral("username=") + share->userName();
1056     } else {
1057         argumentsList << QStringLiteral("guest");
1058     }
1059 
1060     //
1061     // Client's and server's NetBIOS name
1062     //
1063     // According to the manual page, this is only needed when port 139
1064     // is used. So, we only pass the NetBIOS name in that case.
1065     //
1066     bool setNetbiosNames = false;
1067 
1068     if (options) {
1069         setNetbiosNames = (options->useFileSystemPort() && options->fileSystemPort() == 139);
1070     } else {
1071         setNetbiosNames = (Smb4KMountSettings::useRemoteFileSystemPort() && Smb4KMountSettings::remoteFileSystemPort() == 139);
1072     }
1073 
1074     if (setNetbiosNames) {
1075         // The client's NetBIOS name. If that is empty, fall back to the local host name.
1076         if (!machineNetbiosName().isEmpty()) {
1077             argumentsList << QStringLiteral("netbiosname=") + KShell::quoteArg(machineNetbiosName());
1078         } else {
1079             argumentsList << QStringLiteral("netbiosname=") + KShell::quoteArg(QHostInfo::localHostName());
1080         }
1081 
1082         // The server's NetBIOS name
1083         argumentsList << QStringLiteral("servern=") + KShell::quoteArg(share->hostName());
1084     }
1085 
1086     //
1087     // CIFS Unix extensions support
1088     //
1089     // This sets the uid, gid, file_mode and dir_mode arguments, if necessary.
1090     //
1091     bool useCifsUnixExtensionsSupport = false;
1092     QString userString, groupString, fileModeString, directoryModeString;
1093 
1094     if (options) {
1095         useCifsUnixExtensionsSupport = options->cifsUnixExtensionsSupport();
1096         userString = options->useUser() ? options->user().userId().toString() : QString();
1097         groupString = options->useGroup() ? options->group().groupId().toString() : QString();
1098         fileModeString = options->useFileMode() ? options->fileMode() : QString();
1099         directoryModeString = options->useDirectoryMode() ? options->directoryMode() : QString();
1100     } else {
1101         useCifsUnixExtensionsSupport = Smb4KMountSettings::cifsUnixExtensionsSupport();
1102         userString = Smb4KMountSettings::useUserId() ? Smb4KMountSettings::userId() : QString();
1103         groupString = Smb4KMountSettings::useGroupId() ? Smb4KMountSettings::groupId() : QString();
1104         fileModeString = Smb4KMountSettings::useFileMode() ? Smb4KMountSettings::fileMode() : QString();
1105         directoryModeString = Smb4KMountSettings::useDirectoryMode() ? Smb4KMountSettings::directoryMode() : QString();
1106     }
1107 
1108     if (!useCifsUnixExtensionsSupport) {
1109         // User id
1110         if (!userString.isEmpty()) {
1111             argumentsList << QStringLiteral("uid=") + userString;
1112         }
1113 
1114         // Group id
1115         if (!groupString.isEmpty()) {
1116             argumentsList << QStringLiteral("gid=") + groupString;
1117         }
1118 
1119         // File mode
1120         if (!fileModeString.isEmpty()) {
1121             argumentsList << QStringLiteral("file_mode=") + fileModeString;
1122         }
1123 
1124         // Directory mode
1125         if (!directoryModeString.isEmpty()) {
1126             argumentsList << QStringLiteral("dir_mode=") + directoryModeString;
1127         }
1128     }
1129 
1130     //
1131     // Force user id
1132     //
1133     // FIXME: The manual page is not clear about this: Is this option only useful when the uid=...
1134     // argument is given? If so, this should be moved into the 'User id' code block above.
1135     //
1136     if (Smb4KMountSettings::forceUID()) {
1137         argumentsList << QStringLiteral("forceuid");
1138     }
1139 
1140     //
1141     // Force group id
1142     //
1143     // FIXME: The manual page is not clear about this: Is this option only useful when the gid=...
1144     // argument is given? If so, this should be moved into the 'Group id' code block above.
1145     //
1146     if (Smb4KMountSettings::forceGID()) {
1147         argumentsList << QStringLiteral("forcegid");
1148     }
1149 
1150     //
1151     // Client character set
1152     //
1153     if (Smb4KMountSettings::useClientCharset()) {
1154         switch (Smb4KMountSettings::clientCharset()) {
1155         case Smb4KMountSettings::EnumClientCharset::default_charset: {
1156             break;
1157         }
1158         default: {
1159             argumentsList << QStringLiteral("iocharset=")
1160                     + Smb4KMountSettings::self()->clientCharsetItem()->choices().value(Smb4KMountSettings::clientCharset()).label;
1161             break;
1162         }
1163         }
1164     }
1165 
1166     //
1167     // File system port
1168     //
1169     if (options) {
1170         if (options->useFileSystemPort()) {
1171             argumentsList << QStringLiteral("port=") + QString::number(options->fileSystemPort());
1172         }
1173     } else {
1174         if (Smb4KMountSettings::useRemoteFileSystemPort()) {
1175             argumentsList << QStringLiteral("port=") + QString::number(Smb4KMountSettings::remoteFileSystemPort());
1176         }
1177     }
1178 
1179     //
1180     // Write access
1181     //
1182     bool useWriteAccess = false;
1183     int writeAccess = -1;
1184 
1185     if (options) {
1186         useWriteAccess = options->useWriteAccess();
1187         writeAccess = options->writeAccess();
1188     } else {
1189         useWriteAccess = Smb4KMountSettings::useWriteAccess();
1190         writeAccess = Smb4KMountSettings::writeAccess();
1191     }
1192 
1193     if (useWriteAccess) {
1194         switch (writeAccess) {
1195         case Smb4KMountSettings::EnumWriteAccess::ReadWrite: {
1196             argumentsList << QStringLiteral("rw");
1197             break;
1198         }
1199         case Smb4KMountSettings::EnumWriteAccess::ReadOnly: {
1200             argumentsList << QStringLiteral("ro");
1201             break;
1202         }
1203         default: {
1204             break;
1205         }
1206         }
1207     }
1208 
1209     //
1210     // Permission checks
1211     //
1212     if (Smb4KMountSettings::permissionChecks()) {
1213         argumentsList << QStringLiteral("perm");
1214     } else {
1215         argumentsList << QStringLiteral("noperm");
1216     }
1217 
1218     //
1219     // Client controls ids
1220     //
1221     if (Smb4KMountSettings::clientControlsIDs()) {
1222         argumentsList << QStringLiteral("setuids");
1223     } else {
1224         argumentsList << QStringLiteral("nosetuids");
1225     }
1226 
1227     //
1228     // Server inode numbers
1229     //
1230     if (Smb4KMountSettings::serverInodeNumbers()) {
1231         argumentsList << QStringLiteral("serverino");
1232     } else {
1233         argumentsList << QStringLiteral("noserverino");
1234     }
1235 
1236     //
1237     // Cache mode
1238     //
1239     if (Smb4KMountSettings::useCacheMode()) {
1240         switch (Smb4KMountSettings::cacheMode()) {
1241         case Smb4KMountSettings::EnumCacheMode::None: {
1242             argumentsList << QStringLiteral("cache=none");
1243             break;
1244         }
1245         case Smb4KMountSettings::EnumCacheMode::Strict: {
1246             argumentsList << QStringLiteral("cache=strict");
1247             break;
1248         }
1249         case Smb4KMountSettings::EnumCacheMode::Loose: {
1250             argumentsList << QStringLiteral("cache=loose");
1251             break;
1252         }
1253         default: {
1254             break;
1255         }
1256         }
1257     }
1258 
1259     //
1260     // Translate reserved characters
1261     //
1262     if (Smb4KMountSettings::translateReservedChars()) {
1263         argumentsList << QStringLiteral("mapchars");
1264     } else {
1265         argumentsList << QStringLiteral("nomapchars");
1266     }
1267 
1268     //
1269     // Locking
1270     //
1271     if (Smb4KMountSettings::noLocking()) {
1272         argumentsList << QStringLiteral("nobrl");
1273     }
1274 
1275     //
1276     // Security mode
1277     //
1278     bool useSecurityMode = false;
1279     int securityMode = -1;
1280 
1281     if (options) {
1282         useSecurityMode = options->useSecurityMode();
1283         securityMode = options->securityMode();
1284     } else {
1285         useSecurityMode = Smb4KMountSettings::useSecurityMode();
1286         securityMode = Smb4KMountSettings::securityMode();
1287     }
1288 
1289     if (useSecurityMode) {
1290         switch (securityMode) {
1291         case Smb4KMountSettings::EnumSecurityMode::None: {
1292             argumentsList << QStringLiteral("sec=none");
1293             break;
1294         }
1295         case Smb4KMountSettings::EnumSecurityMode::Krb5: {
1296             argumentsList << QStringLiteral("sec=krb5");
1297             argumentsList << QStringLiteral("cruid=") + KUser(KUser::UseRealUserID).userId().toString();
1298             break;
1299         }
1300         case Smb4KMountSettings::EnumSecurityMode::Krb5i: {
1301             argumentsList << QStringLiteral("sec=krb5i");
1302             argumentsList << QStringLiteral("cruid=") + KUser(KUser::UseRealUserID).userId().toString();
1303             break;
1304         }
1305         case Smb4KMountSettings::EnumSecurityMode::Ntlm: {
1306             argumentsList << QStringLiteral("sec=ntlm");
1307             break;
1308         }
1309         case Smb4KMountSettings::EnumSecurityMode::Ntlmi: {
1310             argumentsList << QStringLiteral("sec=ntlmi");
1311             break;
1312         }
1313         case Smb4KMountSettings::EnumSecurityMode::Ntlmv2: {
1314             argumentsList << QStringLiteral("sec=ntlmv2");
1315             break;
1316         }
1317         case Smb4KMountSettings::EnumSecurityMode::Ntlmv2i: {
1318             argumentsList << QStringLiteral("sec=ntlmv2i");
1319             break;
1320         }
1321         case Smb4KMountSettings::EnumSecurityMode::Ntlmssp: {
1322             argumentsList << QStringLiteral("sec=ntlmssp");
1323             break;
1324         }
1325         case Smb4KMountSettings::EnumSecurityMode::Ntlmsspi: {
1326             argumentsList << QStringLiteral("sec=ntlmsspi");
1327             break;
1328         }
1329         default: {
1330             // Smb4KSettings::EnumSecurityMode::Default,
1331             break;
1332         }
1333         }
1334     }
1335 
1336     //
1337     // SMB protocol version
1338     //
1339     bool useMountProtocolVersion = false;
1340     int mountProtocolVersion = -1;
1341 
1342     if (options) {
1343         useMountProtocolVersion = options->useMountProtocolVersion();
1344         mountProtocolVersion = options->mountProtocolVersion();
1345     } else {
1346         useMountProtocolVersion = Smb4KMountSettings::useSmbProtocolVersion();
1347         mountProtocolVersion = Smb4KMountSettings::smbProtocolVersion();
1348     }
1349 
1350     if (useMountProtocolVersion) {
1351         switch (mountProtocolVersion) {
1352         case Smb4KMountSettings::EnumSmbProtocolVersion::OnePointZero: {
1353             argumentsList << QStringLiteral("vers=1.0");
1354             break;
1355         }
1356         case Smb4KMountSettings::EnumSmbProtocolVersion::TwoPointZero: {
1357             argumentsList << QStringLiteral("vers=2.0");
1358             break;
1359         }
1360         case Smb4KMountSettings::EnumSmbProtocolVersion::TwoPointOne: {
1361             argumentsList << QStringLiteral("vers=2.1");
1362             break;
1363         }
1364         case Smb4KMountSettings::EnumSmbProtocolVersion::ThreePointZero: {
1365             argumentsList << QStringLiteral("vers=3.0");
1366             break;
1367         }
1368         case Smb4KMountSettings::EnumSmbProtocolVersion::ThreePointZeroPointTwo: {
1369             argumentsList << QStringLiteral("vers=3.0.2");
1370             break;
1371         }
1372         case Smb4KMountSettings::EnumSmbProtocolVersion::ThreePointOnePointOne: {
1373             argumentsList << QStringLiteral("vers=3.1.1");
1374             break;
1375         }
1376         case Smb4KMountSettings::EnumSmbProtocolVersion::ThreeAndAbove: {
1377             argumentsList << QStringLiteral("vers=3");
1378             break;
1379         }
1380         case Smb4KMountSettings::EnumSmbProtocolVersion::Default: {
1381             argumentsList << QStringLiteral("vers=default");
1382             break;
1383         }
1384         default: {
1385             break;
1386         }
1387         }
1388     }
1389 
1390     //
1391     // Mount options provided by the user
1392     //
1393     if (Smb4KMountSettings::useCustomCifsOptions()) {
1394         if (!Smb4KMountSettings::customCIFSOptions().isEmpty()) {
1395             // SECURITY: Only pass those arguments to mount.cifs that do not pose
1396             // a potential security risk and that have not already been defined.
1397             //
1398             // This is, among others, the proper fix to the security issue reported
1399             // by Heiner Markert (aka CVE-2014-2581).
1400             QStringList allowedArgs = allowedMountArguments();
1401             QStringList list = Smb4KMountSettings::customCIFSOptions().split(QStringLiteral(","), Qt::SkipEmptyParts);
1402             QMutableStringListIterator it(list);
1403 
1404             while (it.hasNext()) {
1405                 QString arg = it.next().section(QStringLiteral("="), 0, 0);
1406 
1407                 if (!allowedArgs.contains(arg)) {
1408                     it.remove();
1409                 }
1410 
1411                 argumentsList += list;
1412             }
1413         }
1414     }
1415 
1416     //
1417     // Insert the mount options into the map
1418     //
1419     QStringList mh_options;
1420     mh_options << QStringLiteral("-o");
1421     mh_options << argumentsList.join(QStringLiteral(","));
1422     map.insert(QStringLiteral("mh_options"), mh_options);
1423 
1424     //
1425     // Insert the mountpoint into the map
1426     //
1427     map.insert(QStringLiteral("mh_mountpoint"), share->canonicalPath());
1428 
1429     //
1430     // Insert information about the share and its URL into the map
1431     //
1432     if (!share->isHomesShare()) {
1433         map.insert(QStringLiteral("mh_url"), share->url());
1434     } else {
1435         map.insert(QStringLiteral("mh_url"), share->homeUrl());
1436         map.insert(QStringLiteral("mh_homes_url"), share->url());
1437     }
1438 
1439     //
1440     // Location of the Kerberos ticket
1441     //
1442     // The path to the Kerberos ticket is stored - if it exists - in the
1443     // KRB5CCNAME environment variable. By default, the ticket is located
1444     // at /tmp/krb5cc_[uid]. So, if the environment variable does not exist,
1445     // but the cache file is there, try to use it.
1446     //
1447     if (QProcessEnvironment::systemEnvironment().contains(QStringLiteral("KRB5CCNAME"))) {
1448         map.insert(QStringLiteral("mh_krb5ticket"), QProcessEnvironment::systemEnvironment().value(QStringLiteral("KRB5CCNAME"), QStringLiteral("")));
1449     } else {
1450         QString ticket = QStringLiteral("/tmp/krb5cc_") + KUser(KUser::UseRealUserID).userId().toString();
1451 
1452         if (QFile::exists(ticket)) {
1453             QString fileEntry = QStringLiteral("FILE:") + ticket;
1454             map.insert(QStringLiteral("mh_krb5ticket"), fileEntry);
1455         }
1456     }
1457 
1458     return true;
1459 }
1460 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
1461 //
1462 // FreeBSD and NetBSD arguments
1463 //
1464 bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, QVariantMap &map)
1465 {
1466     //
1467     // Find the mount executable
1468     //
1469     const QString mount = findMountExecutable();
1470 
1471     if (!mount.isEmpty()) {
1472         map.insert(QStringLiteral("mh_command"), mount);
1473     } else {
1474         Smb4KNotification::commandNotFound(QStringLiteral("mount_smbfs"));
1475         return false;
1476     }
1477 
1478     //
1479     // Global and custom options
1480     //
1481     CustomSettingsPtr options = Smb4KCustomSettingsManager::self()->findCustomSettings(share);
1482 
1483     //
1484     // List of arguments
1485     //
1486     QStringList argumentsList;
1487 
1488     //
1489     // Workgroup or domain
1490     //
1491     // Do not use this, if the domain is a DNS domain.
1492     //
1493     WorkgroupPtr workgroup = findWorkgroup(share->workgroupName());
1494 
1495     if ((workgroup && !workgroup->dnsDiscovered()) || (!workgroup && !share->workgroupName().trimmed().isEmpty())) {
1496         argumentsList << QStringLiteral("-W");
1497         argumentsList << KShell::quoteArg(share->workgroupName());
1498     }
1499 
1500     //
1501     // IP address
1502     //
1503     if (!share->hostIpAddress().isEmpty()) {
1504         argumentsList << QStringLiteral("-I");
1505         argumentsList << share->hostIpAddress();
1506     }
1507 
1508     //
1509     // User Id
1510     //
1511     if (options) {
1512         if (options->useUser()) {
1513             argumentsList << QStringLiteral("-u");
1514             argumentsList << options->user().userId().toString();
1515         }
1516     } else {
1517         if (Smb4KMountSettings::useUserId()) {
1518             argumentsList << QStringLiteral("-u");
1519             argumentsList << Smb4KMountSettings::userId();
1520         }
1521     }
1522 
1523     //
1524     // Group Id
1525     //
1526     if (options) {
1527         if (options->useGroup()) {
1528             argumentsList << QStringLiteral("-g");
1529             argumentsList << options->group().groupId().toString();
1530         }
1531     } else {
1532         if (Smb4KMountSettings::useGroupId()) {
1533             argumentsList << QStringLiteral("-g");
1534             argumentsList << Smb4KMountSettings::groupId();
1535         }
1536     }
1537 
1538     if (Smb4KMountSettings::useCharacterSets()) {
1539         // Client character set
1540         QString clientCharset, serverCharset;
1541 
1542         switch (Smb4KMountSettings::clientCharset()) {
1543         case Smb4KMountSettings::EnumClientCharset::default_charset: {
1544             break;
1545         }
1546         default: {
1547             clientCharset = Smb4KMountSettings::self()->clientCharsetItem()->choices().value(Smb4KMountSettings::clientCharset()).label;
1548             break;
1549         }
1550         }
1551 
1552         // Server character set
1553         switch (Smb4KMountSettings::serverCodepage()) {
1554         case Smb4KMountSettings::EnumServerCodepage::default_codepage: {
1555             break;
1556         }
1557         default: {
1558             serverCharset = Smb4KMountSettings::self()->serverCodepageItem()->choices().value(Smb4KMountSettings::serverCodepage()).label;
1559             break;
1560         }
1561         }
1562 
1563         if (!clientCharset.isEmpty() && !serverCharset.isEmpty()) {
1564             argumentsList << QStringLiteral("-E");
1565             argumentsList << clientCharset + QStringLiteral(":") + serverCharset;
1566         }
1567     }
1568 
1569     //
1570     // File mode
1571     //
1572     if (options) {
1573         if (options->useFileMode()) {
1574             argumentsList << QStringLiteral("-f");
1575             argumentsList << options->fileMode();
1576         }
1577     } else {
1578         if (Smb4KMountSettings::useFileMode()) {
1579             argumentsList << QStringLiteral("-f");
1580             argumentsList << Smb4KMountSettings::fileMode();
1581         }
1582     }
1583 
1584     //
1585     // Directory mode
1586     //
1587     if (options) {
1588         if (options->useDirectoryMode()) {
1589             argumentsList << QStringLiteral("-d");
1590             argumentsList << options->directoryMode();
1591         }
1592     } else {
1593         if (Smb4KMountSettings::useDirectoryMode()) {
1594             argumentsList << QStringLiteral("-d");
1595             argumentsList << Smb4KMountSettings::directoryMode();
1596         }
1597     }
1598 
1599     //
1600     // User name (login)
1601     //
1602     if (!share->userName().isEmpty()) {
1603         argumentsList << QStringLiteral("-U");
1604         argumentsList << share->userName();
1605     } else {
1606         argumentsList << QStringLiteral("-N");
1607     }
1608 
1609     //
1610     // Insert the mount options into the map
1611     //
1612     map.insert(QStringLiteral("mh_options"), argumentsList);
1613 
1614     //
1615     // Insert the mountpoint into the map
1616     //
1617     map.insert(QStringLiteral("mh_mountpoint"), share->canonicalPath());
1618 
1619     //
1620     // Insert information about the share and its URL into the map
1621     //
1622     if (!share->isHomesShare()) {
1623         map.insert(QStringLiteral("mh_url"), share->url());
1624     } else {
1625         map.insert(QStringLiteral("mh_url"), share->homeUrl());
1626         map.insert(QStringLiteral("mh_homes_url"), share->url());
1627     }
1628 
1629     return true;
1630 }
1631 #else
1632 //
1633 // Dummy
1634 //
1635 bool Smb4KMounter::fillMountActionArgs(const SharePtr &, QVariantMap &)
1636 {
1637     qWarning() << "Smb4KMounter::fillMountActionArgs() is not implemented!";
1638     qWarning() << "Mounting under this operating system is not supported...";
1639     return false;
1640 }
1641 #endif
1642 
1643 #if defined(Q_OS_LINUX)
1644 //
1645 // Linux arguments
1646 //
1647 bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &share, bool force, bool silent, QVariantMap &map)
1648 {
1649     //
1650     // The umount program
1651     //
1652     const QString umount = findUmountExecutable();
1653 
1654     if (umount.isEmpty() && !silent) {
1655         Smb4KNotification::commandNotFound(QStringLiteral("umount"));
1656         return false;
1657     }
1658 
1659     //
1660     // The options
1661     //
1662     QStringList options;
1663 
1664     if (force) {
1665         options << QStringLiteral("-l"); // lazy unmount
1666     }
1667 
1668     //
1669     // Insert data into the map
1670     //
1671     map.insert(QStringLiteral("mh_command"), umount);
1672     map.insert(QStringLiteral("mh_url"), share->url());
1673 
1674     if (!share->isInaccessible() && Smb4KHardwareInterface::self()->isOnline()) {
1675         map.insert(QStringLiteral("mh_mountpoint"), share->canonicalPath());
1676     } else {
1677         map.insert(QStringLiteral("mh_mountpoint"), share->path());
1678     }
1679 
1680     map.insert(QStringLiteral("mh_options"), options);
1681 
1682     return true;
1683 }
1684 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
1685 //
1686 // FreeBSD and NetBSD arguments
1687 //
1688 bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &share, bool force, bool silent, QVariantMap &map)
1689 {
1690     //
1691     // The umount program
1692     //
1693     const QString umount = findUmountExecutable();
1694 
1695     if (umount.isEmpty() && !silent) {
1696         Smb4KNotification::commandNotFound(QStringLiteral("umount"));
1697         return false;
1698     }
1699 
1700     //
1701     // The options
1702     //
1703     QStringList options;
1704 
1705     if (force) {
1706         options << QStringLiteral("-f");
1707     }
1708 
1709     //
1710     // Insert data into the map
1711     //
1712     map.insert(QStringLiteral("mh_command"), umount);
1713     map.insert(QStringLiteral("mh_url"), share->url());
1714 
1715     if (!share->isInaccessible() && Smb4KHardwareInterface::self()->isOnline()) {
1716         map.insert(QStringLiteral("mh_mountpoint"), share->canonicalPath());
1717     } else {
1718         map.insert(QStringLiteral("mh_mountpoint"), share->path());
1719     }
1720 
1721     map.insert(QStringLiteral("mh_options"), options);
1722 
1723     return true;
1724 }
1725 #else
1726 //
1727 // Dummy
1728 //
1729 bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &, bool, bool, QVariantMap &)
1730 {
1731     qWarning() << "Smb4KMounter::fillUnmountActionArgs() is not implemented!";
1732     qWarning() << "Unmounting under this operating system is not supported...";
1733     return false;
1734 }
1735 #endif
1736 
1737 void Smb4KMounter::check(const SharePtr &share)
1738 {
1739     d->storageInfo.setPath(share->path());
1740 
1741     if (d->storageInfo.isValid() && d->storageInfo.isReady()) {
1742         // Accessibility
1743         share->setInaccessible(false);
1744 
1745         // Size information
1746         share->setFreeDiskSpace(d->storageInfo.bytesAvailable()); // Bytes available to the user, might be less that bytesFree()
1747         share->setTotalDiskSpace(d->storageInfo.bytesTotal());
1748 
1749         // Get the owner an group, if possible.
1750         QFileInfo fileInfo(share->path());
1751         fileInfo.setCaching(false);
1752 
1753         if (fileInfo.exists()) {
1754             share->setUser(KUser(static_cast<K_UID>(fileInfo.ownerId())));
1755             share->setGroup(KUserGroup(static_cast<K_GID>(fileInfo.groupId())));
1756         } else {
1757             share->setUser(KUser(KUser::UseRealUserID));
1758             share->setGroup(KUserGroup(KUser::UseRealUserID));
1759         }
1760     } else {
1761         share->setInaccessible(true);
1762         share->setFreeDiskSpace(0);
1763         share->setTotalDiskSpace(0);
1764         share->setUser(KUser(KUser::UseRealUserID));
1765         share->setGroup(KUserGroup(KUser::UseRealUserID));
1766     }
1767 }
1768 
1769 /////////////////////////////////////////////////////////////////////////////
1770 // SLOT IMPLEMENTATIONS
1771 /////////////////////////////////////////////////////////////////////////////
1772 
1773 void Smb4KMounter::slotStartJobs()
1774 {
1775     //
1776     // Start the import of shares
1777     //
1778     if (Smb4KHardwareInterface::self()->isOnline()) {
1779         import(true);
1780     }
1781 
1782     //
1783     // Start the timer
1784     //
1785     if (d->timerId == -1) {
1786         d->timerId = startTimer(TIMEOUT);
1787     }
1788 }
1789 
1790 void Smb4KMounter::slotAboutToQuit()
1791 {
1792     //
1793     // Abort any actions
1794     //
1795     abort();
1796 
1797     //
1798     // Check if the user wants to remount shares and save the
1799     // shares for remount if so.
1800     //
1801     if (Smb4KMountSettings::remountShares()) {
1802         saveSharesForRemount();
1803     }
1804 
1805     //
1806     // Unmount the shares if the user chose to do so.
1807     //
1808     if (Smb4KMountSettings::unmountSharesOnExit()) {
1809         unmountAllShares(true);
1810     }
1811 
1812     //
1813     // Clean up the mount prefix.
1814     //
1815     KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions);
1816 
1817     QDir dir;
1818     dir.cd(Smb4KMountSettings::mountPrefix().path());
1819     QStringList hostDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
1820     QStringList mountpoints;
1821 
1822     for (const QString &hostDir : qAsConst(hostDirs)) {
1823         dir.cd(hostDir);
1824 
1825         QStringList shareDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
1826 
1827         for (const QString &shareDir : qAsConst(shareDirs)) {
1828             dir.cd(shareDir);
1829             mountpoints << dir.absolutePath();
1830             dir.cdUp();
1831         }
1832 
1833         dir.cdUp();
1834     }
1835 
1836     // Remove those mountpoints where a share is actually mounted.
1837     for (const QExplicitlySharedDataPointer<KMountPoint> &mountPoint : qAsConst(mountPoints)) {
1838         mountpoints.removeOne(mountPoint->mountPoint());
1839     }
1840 
1841     // Remove the empty mountpoints.
1842     for (const QString &mp : qAsConst(mountpoints)) {
1843         dir.cd(mp);
1844         dir.rmdir(dir.canonicalPath());
1845 
1846         if (dir.cdUp()) {
1847             dir.rmdir(dir.canonicalPath());
1848         }
1849     }
1850 }
1851 
1852 void Smb4KMounter::slotOnlineStateChanged(bool online)
1853 {
1854     if (online) {
1855         slotStartJobs();
1856     } else {
1857         abort();
1858         saveSharesForRemount();
1859 
1860         // FIXME: Do we need this at all?
1861         for (const SharePtr &share : mountedSharesList()) {
1862             share->setInaccessible(true);
1863         }
1864 
1865         unmountAllShares(true);
1866 
1867         d->remountAttempts = 0;
1868         d->remountTimeout = 0;
1869     }
1870 }
1871 
1872 void Smb4KMounter::slotAboutToChangeProfile()
1873 {
1874     if (Smb4KMountSettings::remountShares()) {
1875         saveSharesForRemount();
1876     }
1877 }
1878 
1879 void Smb4KMounter::slotActiveProfileChanged(const QString &newProfile)
1880 {
1881     Q_UNUSED(newProfile);
1882 
1883     // Stop the timer.
1884     killTimer(d->timerId);
1885 
1886     abort();
1887 
1888     // Clear all remounts.
1889     while (!d->remounts.isEmpty()) {
1890         d->remounts.takeFirst().clear();
1891     }
1892 
1893     // Clear all retries.
1894     while (!d->retries.isEmpty()) {
1895         d->retries.takeFirst().clear();
1896     }
1897 
1898     // Unmount all shares
1899     unmountAllShares(true);
1900 
1901     // Reset some variables.
1902     // Don't touch d->firstImportDone here, because that remains true
1903     d->remountTimeout = 0;
1904     d->remountAttempts = 0;
1905 
1906     // Restart the timer
1907     d->timerId = startTimer(TIMEOUT);
1908 }
1909 
1910 void Smb4KMounter::slotTriggerImport()
1911 {
1912     //
1913     // Wait a bit so that the mount or unmount process can finish and
1914     // then start importing the shares, if no jobs are running anymore
1915     //
1916     QTimer::singleShot(2 * TIMEOUT, this, [&]() {
1917         if (!isRunning()) {
1918             import(true);
1919         }
1920     });
1921 }
1922 
1923 void Smb4KMounter::slotConfigChanged()
1924 {
1925     if (d->detectAllShares != Smb4KMountSettings::detectAllShares()) {
1926         import(true);
1927         d->detectAllShares = Smb4KMountSettings::detectAllShares();
1928     }
1929 }
1930 
1931 void Smb4KMounter::slotCredentialsUpdated(const QUrl &url)
1932 {
1933     if (!url.isEmpty() && !d->retries.isEmpty()) {
1934         QMutableListIterator<SharePtr> it(d->retries);
1935 
1936         while (it.hasNext()) {
1937             SharePtr share = it.next();
1938 
1939             QUrl parentUrl = share->url().resolved(QUrl(QStringLiteral(".."))).adjusted(QUrl::StripTrailingSlash);
1940 
1941             if (QString::compare(share->url().toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
1942                                  url.toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
1943                                  Qt::CaseInsensitive)
1944                     == 0
1945                 || QString::compare(parentUrl.toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
1946                                     url.toString(QUrl::RemoveUserInfo | QUrl::RemovePort),
1947                                     Qt::CaseInsensitive)
1948                     == 0) {
1949                 share->setUserName(url.userName());
1950                 share->setPassword(url.password());
1951 
1952                 mountShare(share);
1953 
1954                 it.remove();
1955             }
1956         }
1957     }
1958 }