1 /***************************************************************************
2     The core class that mounts the shares.
3                              -------------------
4     begin                : Die Jun 10 2003
5     copyright            : (C) 2003-2021 by Alexander Reinholdt
6     email                : alexander.reinholdt@kdemail.net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  *   This program is distributed in the hope that it will be useful, but   *
16  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
17  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
18  *   General Public License for more details.                              *
19  *                                                                         *
20  *   You should have received a copy of the GNU General Public License     *
21  *   along with this program; if not, write to the                         *
22  *   Free Software Foundation, Inc., 51 Franklin Street, Suite 500, Boston,*
23  *   MA 02110-1335, USA                                                    *
24  ***************************************************************************/
25 
26 // Application specific includes
27 #include "smb4kmounter.h"
28 #include "smb4kmounter_p.h"
29 #include "smb4kauthinfo.h"
30 #include "smb4kworkgroup.h"
31 #include "smb4kshare.h"
32 #include "smb4ksettings.h"
33 #include "smb4khomesshareshandler.h"
34 #include "smb4kwalletmanager.h"
35 #include "smb4knotification.h"
36 #include "smb4kbookmarkhandler.h"
37 #include "smb4kcustomoptionsmanager.h"
38 #include "smb4kcustomoptions.h"
39 #include "smb4kbookmark.h"
40 #include "smb4kprofilemanager.h"
41 #include "smb4khardwareinterface.h"
42 
43 #if defined(Q_OS_LINUX)
44 #include "smb4kmountsettings_linux.h"
45 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
46 #include "smb4kmountsettings_bsd.h"
47 #endif
48 
49 // Qt includes
50 #include <QDir>
51 #include <QTextStream>
52 #include <QTextCodec>
53 #include <QTimer>
54 #include <QFileInfo>
55 #include <QDebug>
56 #include <QApplication>
57 #include <QTest>
58 #include <QUdpSocket>
59 
60 // KDE includes
61 #define TRANSLATION_DOMAIN "smb4k-core"
62 #include <KCoreAddons/KShell>
63 #include <KCoreAddons/KUser>
64 #include <KIOCore/KIO/Global>
65 #include <KIOCore/KIO/StatJob>
66 #include <KIOCore/KMountPoint>
67 #include <KIOCore/KDiskFreeSpaceInfo>
68 #include <KI18n/KLocalizedString>
69 #include <KWidgetsAddons/KMessageBox>
70 #include <KAuth/KAuthExecuteJob>
71 
72 using namespace Smb4KGlobal;
73 
74 #define TIMEOUT 50
75 
76 Q_GLOBAL_STATIC(Smb4KMounterStatic, p);
77 
78 
79 
Smb4KMounter(QObject * parent)80 Smb4KMounter::Smb4KMounter(QObject *parent)
81 : KCompositeJob(parent), d(new Smb4KMounterPrivate)
82 {
83   setAutoDelete(false);
84 
85   d->timerId = -1;
86   d->remountTimeout = 0;
87   d->remountAttempts = 0;
88   d->checkTimeout = 0;
89   d->newlyMounted = 0;
90   d->newlyUnmounted = 0;
91   d->dialog = nullptr;
92   d->firstImportDone = false;
93   d->longActionRunning = false;
94   d->activeProfile = Smb4KProfileManager::self()->activeProfile();
95   d->detectAllShares = Smb4KMountSettings::detectAllShares();
96 
97   //
98   // Connections
99   //
100   connect(Smb4KProfileManager::self(), SIGNAL(migratedProfile(QString,QString)), this, SLOT(slotProfileMigrated(QString,QString)));
101   connect(Smb4KProfileManager::self(), SIGNAL(aboutToChangeProfile()), this, SLOT(slotAboutToChangeProfile()));
102   connect(Smb4KProfileManager::self(), SIGNAL(activeProfileChanged(QString)), this, SLOT(slotActiveProfileChanged(QString)));
103 
104   connect(Smb4KMountSettings::self(), SIGNAL(configChanged()), this, SLOT(slotConfigChanged()));
105 
106   connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()),this, SLOT(slotAboutToQuit()));
107 }
108 
109 
~Smb4KMounter()110 Smb4KMounter::~Smb4KMounter()
111 {
112   while (!d->importedShares.isEmpty())
113   {
114     d->importedShares.takeFirst().clear();
115   }
116 
117   while (!d->retries.isEmpty())
118   {
119     d->retries.takeFirst().clear();
120   }
121 }
122 
123 
self()124 Smb4KMounter *Smb4KMounter::self()
125 {
126   return &p->instance;
127 }
128 
129 
abort()130 void Smb4KMounter::abort()
131 {
132   if (!QCoreApplication::closingDown())
133   {
134     QListIterator<KJob *> it(subjobs());
135 
136     while (it.hasNext())
137     {
138       it.next()->kill(KJob::EmitResult);
139     }
140   }
141 }
142 
143 
isRunning()144 bool Smb4KMounter::isRunning()
145 {
146   return (hasSubjobs() || d->longActionRunning);
147 }
148 
149 
triggerRemounts(bool fillList)150 void Smb4KMounter::triggerRemounts(bool fillList)
151 {
152   if (fillList)
153   {
154     //
155     // Get the list of shares that are to be remounted
156     //
157     QList<OptionsPtr> options = Smb4KCustomOptionsManager::self()->sharesToRemount();
158 
159     //
160     // Process the list and honor the settings the user chose
161     //
162     for (const OptionsPtr &option : qAsConst(options))
163     {
164       //
165       // Skip one time remount shares, if needed
166       //
167       if (option->remount() == Smb4KCustomOptions::RemountOnce && !Smb4KMountSettings::remountShares())
168       {
169         continue;
170       }
171 
172       //
173       // Check which share has to be remounted
174       //
175       QList<SharePtr> mountedShares = findShareByUrl(option->url());
176       bool remountShare = true;
177 
178       for (const SharePtr &share : qAsConst(mountedShares))
179       {
180         if (!share->isForeign())
181         {
182           remountShare = false;
183           break;
184         }
185       }
186 
187       //
188       // Insert the share to the list of remounts
189       //
190       if (remountShare)
191       {
192         bool insertShare = true;
193 
194         for (const SharePtr &share : qAsConst(d->remounts))
195         {
196           if (QString::compare(share->url().toString(QUrl::RemoveUserInfo|QUrl::RemovePort), option->url().toString(QUrl::RemoveUserInfo|QUrl::RemovePort)) == 0)
197           {
198             insertShare = false;
199             break;
200           }
201         }
202 
203         if (insertShare)
204         {
205           SharePtr share = SharePtr(new Smb4KShare());
206           share->setUrl(option->url());
207           share->setWorkgroupName(option->workgroupName());
208           share->setHostIpAddress(option->ipAddress());
209 
210           if (share->url().isValid() && !share->url().isEmpty())
211           {
212             d->remounts << share;
213           }
214         }
215       }
216     }
217   }
218 
219   //
220   // Remount the shares
221   //
222   mountShares(d->remounts);
223 
224   //
225   // Count the remount attempts
226   //
227   d->remountAttempts++;
228 }
229 
230 
import(bool checkInaccessible)231 void Smb4KMounter::import(bool checkInaccessible)
232 {
233   //
234   // Immediately return here if we are still processing imported shares
235   //
236   if (!d->importedShares.isEmpty())
237   {
238     return;
239   }
240 
241   //
242   // Get the mountpoints that are present on the system
243   //
244   KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded|KMountPoint::NeedMountOptions);
245 
246   //
247   // Now determine all mountpoints that have the appropriate filesystem.
248   //
249   for (const QExplicitlySharedDataPointer<KMountPoint> &mountPoint : qAsConst(mountPoints))
250   {
251     if (mountPoint->mountType() == "cifs" || mountPoint->mountType() == "smb3" || mountPoint->mountType() == "smbfs")
252     {
253       // Create a new share and set the mountpoint and filesystem
254       SharePtr share = SharePtr(new Smb4KShare());
255       share->setUrl(mountPoint->mountedFrom());
256       share->setPath(mountPoint->mountPoint());
257       share->setMounted(true);
258 
259       // Get all mount options
260       for (const QString &option : mountPoint->mountOptions())
261       {
262         if (option.startsWith(QLatin1String("domain=")) || option.startsWith(QLatin1String("workgroup=")))
263         {
264           share->setWorkgroupName(option.section('=', 1, 1).trimmed());
265         }
266         else if (option.startsWith(QLatin1String("addr=")))
267         {
268           share->setHostIpAddress(option.section('=', 1, 1).trimmed());
269         }
270         else if (option.startsWith(QLatin1String("username=")) || option.startsWith(QLatin1String("user=")))
271         {
272           share->setLogin(option.section('=', 1, 1).trimmed());
273         }
274       }
275 
276       // Work around empty usernames
277       if (share->login().isEmpty())
278       {
279         share->setLogin("guest");
280       }
281 
282       d->importedShares << share;
283     }
284   }
285 
286   //
287   // Check which shares were unmounted. Remove all obsolete mountpoints, emit
288   // the unmounted() signal on each of the unmounted shares and remove them
289   // from the global list.
290   //
291   QList<SharePtr> unmountedShares;
292 
293   if (!d->importedShares.isEmpty())
294   {
295     bool found = false;
296 
297     for (const SharePtr &mountedShare : qAsConst(mountedSharesList()))
298     {
299       for (const SharePtr &importedShare : qAsConst(d->importedShares))
300       {
301         // Check the mountpoint, since that one is unique. We will only use
302         // Smb4KShare::path(), so that we do not run into trouble if a share
303         // is inaccessible.
304         if (QString::compare(mountedShare->path(), importedShare->path()) == 0)
305         {
306           found = true;
307           break;
308         }
309       }
310 
311       if (!found)
312       {
313         unmountedShares << mountedShare;
314       }
315 
316       found = false;
317     }
318   }
319   else
320   {
321     unmountedShares << mountedSharesList();
322   }
323 
324   //
325   // Process the unmounted shares
326   //
327   if (!unmountedShares.isEmpty())
328   {
329     d->newlyUnmounted += unmountedShares.size();
330 
331     for (const SharePtr &share : qAsConst(unmountedShares))
332     {
333       //
334       // Remove the mountpoint if the share is not a foreign one
335       //
336       if (!share->isForeign())
337       {
338         QDir dir;
339         dir.cd(share->canonicalPath());
340         dir.rmdir(dir.canonicalPath());
341 
342         if (dir.cdUp())
343         {
344           dir.rmdir(dir.canonicalPath());
345         }
346       }
347 
348       //
349       // Mark it as unmounted
350       //
351       share->setMounted(false);
352 
353       //
354       // Copy the share
355       //
356       SharePtr unmountedShare = share;
357 
358       //
359       // Remove the share from the global list and notify the program
360       //
361       removeMountedShare(share);
362       emit unmounted(unmountedShare);
363 
364       //
365       // Report the unmounted share to the user if it is a single one
366       //
367       if (!isRunning() && d->newlyUnmounted == 1)
368       {
369         Smb4KNotification::shareUnmounted(unmountedShare);
370       }
371 
372       unmountedShare.clear();
373     }
374 
375     //
376     // Report the number of unmounted shares to the user if it are
377     // several ones
378     //
379     if (d->newlyUnmounted > 1)
380     {
381       Smb4KNotification::sharesUnmounted(d->newlyUnmounted);
382     }
383 
384     //
385     // Reset the number of newly unmounted shares
386     //
387     d->newlyUnmounted = 0;
388 
389     //
390     // Tell the program the list of mounted shares changed
391     //
392     emit mountedSharesListChanged();
393   }
394   else
395   {
396     //
397     // Reset the number of newly unmounted shares
398     //
399     d->newlyUnmounted = 0;
400   }
401 
402   //
403   // Now stat the imported shares to get information about them.
404   // Do not use Smb4KShare::canonicalPath() here, otherwise we might
405   // get lock-ups with inaccessible shares.
406   //
407   if (Smb4KHardwareInterface::self()->isOnline())
408   {
409     QMutableListIterator<SharePtr> it(d->importedShares);
410 
411     while (it.hasNext())
412     {
413       SharePtr share = it.next();
414       SharePtr mountedShare = findShareByPath(share->path());
415 
416       if (mountedShare)
417       {
418         if (mountedShare->isInaccessible() && !checkInaccessible)
419         {
420           it.remove();
421           continue;
422         }
423       }
424 
425       QUrl url = QUrl::fromLocalFile(share->path());
426       KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo);
427       job->setDetails(0);
428       connect(job, SIGNAL(result(KJob*)), this, SLOT(slotStatResult(KJob*)));
429 
430       // Do not use addSubJob(), because that would confuse isRunning(), etc.
431 
432       job->start();
433     }
434 
435     //
436     // Set d->firstImportDone here only for the case that no mounted shares
437     // could be found. In all other cases d->firstImportDone will be set in
438     // slotStatResult().
439     //
440     if (!d->firstImportDone && d->importedShares.isEmpty())
441     {
442       d->firstImportDone = true;
443     }
444   }
445   else
446   {
447     //
448     // When the system is offline, no mounted shares are processed, so
449     // empty the list of imported shares here.
450     //
451     while (!d->importedShares.isEmpty())
452     {
453       SharePtr share = d->importedShares.takeFirst();
454       share.clear();
455     }
456   }
457 }
458 
459 
mountShare(const SharePtr & share)460 void Smb4KMounter::mountShare(const SharePtr &share)
461 {
462   if (share)
463   {
464     //
465     // Check that the URL is valid
466     //
467     if (!share->url().isValid())
468     {
469       Smb4KNotification::invalidURLPassed();
470       return;
471     }
472 
473     //
474     // Check if the share has already been mounted. If it is already present,
475     // do not process it and return.
476     //
477     QUrl url;
478 
479     if (share->isHomesShare())
480     {
481       if (!Smb4KHomesSharesHandler::self()->specifyUser(share, true))
482       {
483         return;
484       }
485 
486       url = share->homeUrl();
487     }
488     else
489     {
490       url = share->url();
491     }
492 
493     QList<SharePtr> mountedShares = findShareByUrl(url);
494     bool isMounted = false;
495 
496     for (const SharePtr &s : qAsConst(mountedShares))
497     {
498       if (!s->isForeign())
499       {
500         isMounted = true;
501         break;
502       }
503     }
504 
505     if (isMounted)
506     {
507       return;
508     }
509 
510     //
511     // Wake-On-LAN: Wake up the host before mounting
512     //
513     if (Smb4KSettings::enableWakeOnLAN())
514     {
515       OptionsPtr options = Smb4KCustomOptionsManager::self()->findOptions(KIO::upUrl(share->url()));
516 
517       if (options && options->wolSendBeforeMount())
518       {
519         emit aboutToStart(WakeUp);
520 
521         QUdpSocket *socket = new QUdpSocket(this);
522         QHostAddress addr;
523 
524         // Use the host's IP address directly from the share object.
525         if (share->hasHostIpAddress())
526         {
527           addr.setAddress(share->hostIpAddress());
528         }
529         else
530         {
531           addr.setAddress("255.255.255.255");
532         }
533 
534         // Construct magic sequence
535         QByteArray sequence;
536 
537         // 6 times 0xFF
538         for (int j = 0; j < 6; ++j)
539         {
540           sequence.append(QChar(0xFF).toLatin1());
541         }
542 
543         // 16 times the MAC address
544         QStringList parts = options->macAddress().split(':', QString::SkipEmptyParts);
545 
546         for (int j = 0; j < 16; ++j)
547         {
548           for (int k = 0; k < parts.size(); ++k)
549           {
550             sequence.append(QChar(QString("0x%1").arg(parts.at(k)).toInt(0, 16)).toLatin1());
551           }
552         }
553 
554         socket->writeDatagram(sequence, addr, 9);
555 
556         delete socket;
557 
558         // Wait the defined time
559         int stop = 1000 * Smb4KSettings::wakeOnLANWaitingTime() / 250;
560         int i = 0;
561 
562         while (i++ < stop)
563         {
564           QTest::qWait(250);
565         }
566 
567         emit finished(WakeUp);
568       }
569     }
570 
571     //
572     // Create the mountpoint
573     //
574     QString mountpoint;
575     mountpoint += Smb4KMountSettings::mountPrefix().path();
576     mountpoint += QDir::separator();
577     mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->hostName().toLower() : share->hostName());
578     mountpoint += QDir::separator();
579 
580     if (!share->isHomesShare())
581     {
582       mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->shareName().toLower() : share->shareName());
583     }
584     else
585     {
586       mountpoint += (Smb4KMountSettings::forceLowerCaseSubdirs() ? share->login().toLower() : share->login());
587     }
588 
589     // Get the permissions that should be used for creating the
590     // mount prefix and all its subdirectories.
591     // Please note that the actual permissions of the mount points
592     // are determined by the mount utility.
593     QFile::Permissions permissions;
594     QUrl parentDirectory;
595 
596     if (QFile::exists(Smb4KMountSettings::mountPrefix().path()))
597     {
598       parentDirectory = Smb4KMountSettings::mountPrefix();
599     }
600     else
601     {
602       QUrl u = Smb4KMountSettings::mountPrefix();
603       parentDirectory = KIO::upUrl(u);
604     }
605 
606     QFile f(parentDirectory.path());
607     permissions = f.permissions();
608 
609     QDir dir(mountpoint);
610 
611     if (!dir.mkpath(dir.path()))
612     {
613       share->setPath("");
614       Smb4KNotification::mkdirFailed(dir);
615       return;
616     }
617     else
618     {
619       QUrl u = QUrl::fromLocalFile(dir.path());
620 
621       while (!parentDirectory.matches(u, QUrl::StripTrailingSlash))
622       {
623         QFile(u.path()).setPermissions(permissions);
624         u = KIO::upUrl(u);
625       }
626     }
627 
628     share->setPath(QDir::cleanPath(mountpoint));
629 
630     //
631     // Get the authentication information
632     //
633     Smb4KWalletManager::self()->readAuthInfo(share);
634 
635     //
636     // Mount arguments
637     //
638     QVariantMap args;
639 
640     if (!fillMountActionArgs(share, args))
641     {
642       return;
643     }
644 
645     //
646     // Emit the aboutToStart() signal
647     //
648     emit aboutToStart(MountShare);
649 
650     //
651     // Create the mount action
652     //
653     KAuth::Action mountAction("org.kde.smb4k.mounthelper.mount");
654     mountAction.setHelperId("org.kde.smb4k.mounthelper");
655     mountAction.setArguments(args);
656 
657     KAuth::ExecuteJob *job = mountAction.execute();
658 
659     //
660     // Modify the cursor, if necessary.
661     //
662     if (!hasSubjobs() && modifyCursor())
663     {
664       QApplication::setOverrideCursor(Qt::BusyCursor);
665     }
666 
667     //
668     // Add the job
669     //
670     addSubjob(job);
671 
672     //
673     // Start the job and process the returned result.
674     //
675     bool success = job->exec();
676 
677     if (success)
678     {
679       int errorCode = job->error();
680 
681       if (errorCode == 0)
682       {
683         // Get the error message
684         QString errorMsg = job->data().value("mh_error_message").toString();
685 
686         if (!errorMsg.isEmpty())
687         {
688 #if defined(Q_OS_LINUX)
689           if (errorMsg.contains("mount error 13") || errorMsg.contains("mount error(13)") /* authentication error */)
690           {
691             if (Smb4KWalletManager::self()->showPasswordDialog(share))
692             {
693               d->retries << share;
694             }
695           }
696           else if (errorMsg.contains("Unable to find suitable address."))
697           {
698             // Swallow this
699           }
700           else
701           {
702             Smb4KNotification::mountingFailed(share, errorMsg);
703           }
704 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
705           if (errorMsg.contains("Authentication error") || errorMsg.contains("Permission denied"))
706           {
707             if (Smb4KWalletManager::self()->showPasswordDialog(share))
708             {
709               d->retries << share;
710             }
711           }
712           else
713           {
714             Smb4KNotification::mountingFailed(share, errorMsg);
715           }
716 #else
717           qWarning() << "Smb4KMounter::slotMountJobFinished(): Error handling not implemented!";
718           Smb4KNotification::mountingFailed(share, errorMsg);
719 #endif
720         }
721       }
722       else
723       {
724         Smb4KNotification::actionFailed(errorCode);
725       }
726     }
727     else
728     {
729       // FIXME: Report that the action could not be started
730     }
731 
732     //
733     // Remove the job from the job list
734     //
735     removeSubjob(job);
736 
737     //
738     // Reset the busy cursor
739     //
740     if (!hasSubjobs() && modifyCursor())
741     {
742       QApplication::restoreOverrideCursor();
743     }
744 
745     //
746     // Emit the finished() signal
747     //
748     emit finished(MountShare);
749   }
750 }
751 
752 
mountShares(const QList<SharePtr> & shares)753 void Smb4KMounter::mountShares(const QList<SharePtr> &shares)
754 {
755   //
756   // This action takes longer
757   //
758   d->longActionRunning = true;
759 
760   //
761   // Mount the shares
762   //
763   for (const SharePtr &share : shares)
764   {
765     mountShare(share);
766   }
767 
768   //
769   // This action is over
770   //
771   d->longActionRunning = false;
772 }
773 
774 
unmountShare(const SharePtr & share,bool silent)775 void Smb4KMounter::unmountShare(const SharePtr &share, bool silent)
776 {
777   Q_ASSERT(share);
778 
779   if (share)
780   {
781     //
782     // Check that the URL is valid.
783     //
784     if (!share->url().isValid())
785     {
786       Smb4KNotification::invalidURLPassed();
787       return;
788     }
789 
790     //
791     // Handle foreign shares according to the settings
792     //
793     if (share->isForeign())
794     {
795       if (!Smb4KMountSettings::unmountForeignShares())
796       {
797         if (!silent)
798         {
799           Smb4KNotification::unmountingNotAllowed(share);
800         }
801 
802         return;
803       }
804       else
805       {
806         if (!silent)
807         {
808           if (KMessageBox::warningYesNo(QApplication::activeWindow(),
809               i18n("<qt><p>The share <b>%1</b> is mounted to <br><b>%2</b> and owned by user <b>%3</b>.</p>"
810                   "<p>Do you really want to unmount it?</p></qt>",
811                   share->displayString(), share->path(), share->user().loginName()),
812               i18n("Foreign Share")) == KMessageBox::No)
813           {
814             return;
815           }
816         }
817         else
818         {
819           // Without the confirmation of the user, we are not
820           // unmounting a foreign share!
821           return;
822         }
823       }
824     }
825 
826     //
827     // Force the unmounting of the share either if the system went offline
828     // or if the user chose to forcibly unmount inaccessible shares (Linux only).
829     //
830     bool force = false;
831 
832     if (Smb4KHardwareInterface::self()->isOnline())
833     {
834 #if defined(Q_OS_LINUX)
835       if (share->isInaccessible())
836       {
837         force = Smb4KMountSettings::forceUnmountInaccessible();
838       }
839 #endif
840     }
841     else
842     {
843       force = true;
844     }
845 
846     //
847     // Unmount arguments
848     //
849     QVariantMap args;
850 
851     if (!fillUnmountActionArgs(share, force, silent, args))
852     {
853       return;
854     }
855 
856     //
857     // Emit the aboutToStart() signal
858     //
859     emit aboutToStart(UnmountShare);
860 
861     //
862     // Create the unmount action
863     //
864     KAuth::Action unmountAction("org.kde.smb4k.mounthelper.unmount");
865     unmountAction.setHelperId("org.kde.smb4k.mounthelper");
866     unmountAction.setArguments(args);
867 
868     KAuth::ExecuteJob *job = unmountAction.execute();
869 
870     //
871     // Modify the cursor, if necessary.
872     //
873     if (!hasSubjobs() && modifyCursor())
874     {
875       QApplication::setOverrideCursor(Qt::BusyCursor);
876     }
877 
878     //
879     // Add the job
880     //
881     addSubjob(job);
882 
883     //
884     // Start the job and process the returned result.
885     //
886     bool success = job->exec();
887 
888     if (success)
889     {
890       int errorCode = job->error();
891 
892       if (errorCode == 0)
893       {
894         // Get the error message
895         QString errorMsg = job->data().value("mh_error_message").toString();
896 
897         if (!errorMsg.isEmpty())
898         {
899           // No error handling needed, just report the error message.
900           Smb4KNotification::unmountingFailed(share, errorMsg);
901         }
902       }
903       else
904       {
905         Smb4KNotification::actionFailed(errorCode);
906       }
907     }
908     else
909     {
910       // FIXME: Report that the action could not be started
911     }
912 
913     //
914     // Remove the job from the job list
915     //
916     removeSubjob(job);
917 
918     //
919     // Reset the busy cursor
920     //
921     if (!hasSubjobs() && modifyCursor())
922     {
923       QApplication::restoreOverrideCursor();
924     }
925 
926     //
927     // Emit the finished() signal
928     //
929     emit finished(UnmountShare);
930   }
931 }
932 
933 
unmountShares(const QList<SharePtr> & shares,bool silent)934 void Smb4KMounter::unmountShares(const QList<SharePtr> &shares, bool silent)
935 {
936   //
937   // This action takes longer
938   //
939   d->longActionRunning = true;
940 
941   //
942   // Inhibit shutdown and sleep
943   //
944   Smb4KHardwareInterface::self()->inhibit();
945 
946   //
947   // Unmount the list of shares
948   //
949   for (const SharePtr &share : shares)
950   {
951     // Unmount the share
952     unmountShare(share, silent);
953   }
954 
955   //
956   // Uninhibit shutdown and sleep
957   //
958   Smb4KHardwareInterface::self()->uninhibit();
959 
960   //
961   // This action is over
962   //
963   d->longActionRunning = false;
964 }
965 
966 
unmountAllShares(bool silent)967 void Smb4KMounter::unmountAllShares(bool silent)
968 {
969   unmountShares(mountedSharesList(), silent);
970 }
971 
972 
openMountDialog()973 void Smb4KMounter::openMountDialog()
974 {
975   if (!d->dialog)
976   {
977     SharePtr share = SharePtr(new Smb4KShare());
978     BookmarkPtr bookmark = BookmarkPtr(new Smb4KBookmark());
979 
980     d->dialog = new Smb4KMountDialog(share, bookmark, QApplication::activeWindow());
981 
982     if (d->dialog->exec() == QDialog::Accepted && d->dialog->validUserInput())
983     {
984       // Pass the share to mountShare().
985       mountShare(share);
986 
987       // Bookmark the share if the user wants this.
988       if (d->dialog->bookmarkShare())
989       {
990         Smb4KBookmarkHandler::self()->addBookmark(bookmark);
991       }
992     }
993 
994     delete d->dialog;
995     d->dialog = nullptr;
996 
997     share.clear();
998     bookmark.clear();
999   }
1000 }
1001 
1002 
start()1003 void Smb4KMounter::start()
1004 {
1005   //
1006   // Connect to the relevant signals provided by Smb4KHardwareInterface.
1007   //
1008   connect(Smb4KHardwareInterface::self(), SIGNAL(onlineStateChanged(bool)), this, SLOT(slotOnlineStateChanged(bool)), Qt::UniqueConnection);
1009   connect(Smb4KHardwareInterface::self(), SIGNAL(networkShareAdded()), this, SLOT(slotTriggerImport()), Qt::UniqueConnection);
1010   connect(Smb4KHardwareInterface::self(), SIGNAL(networkShareRemoved()), this, SLOT(slotTriggerImport()), Qt::UniqueConnection);
1011 
1012   //
1013   // Start with importing shares
1014   //
1015   if (Smb4KHardwareInterface::self()->isOnline())
1016   {
1017     QTimer::singleShot(50, this, SLOT(slotStartJobs()));
1018   }
1019 }
1020 
1021 
saveSharesForRemount()1022 void Smb4KMounter::saveSharesForRemount()
1023 {
1024   //
1025   // Save the shares for remount
1026   //
1027   for (const SharePtr &share : mountedSharesList())
1028   {
1029     if (!share->isForeign())
1030     {
1031       Smb4KCustomOptionsManager::self()->addRemount(share, false);
1032     }
1033     else
1034     {
1035       Smb4KCustomOptionsManager::self()->removeRemount(share, false);
1036     }
1037   }
1038 
1039   //
1040   // Also save each failed remount and remove it from the list
1041   //
1042   while (!d->remounts.isEmpty())
1043   {
1044     SharePtr share = d->remounts.takeFirst();
1045     Smb4KCustomOptionsManager::self()->addRemount(share, false);
1046     share.clear();
1047   }
1048 }
1049 
1050 
timerEvent(QTimerEvent *)1051 void Smb4KMounter::timerEvent(QTimerEvent *)
1052 {
1053   if (!isRunning() && Smb4KHardwareInterface::self()->isOnline())
1054   {
1055     //
1056     // Try to remount shares
1057     //
1058     if (d->remountAttempts < Smb4KMountSettings::remountAttempts() && d->firstImportDone)
1059     {
1060       if (d->remountAttempts == 0)
1061       {
1062         triggerRemounts(true);
1063       }
1064 
1065       if ((60000 * Smb4KMountSettings::remountInterval()) < d->remountTimeout)
1066       {
1067         triggerRemounts(false);
1068         d->remountTimeout = -TIMEOUT;
1069       }
1070 
1071       d->remountTimeout += TIMEOUT;
1072     }
1073 
1074     //
1075     // Retry to mount those shares that initially failed
1076     //
1077     while (!d->retries.isEmpty())
1078     {
1079       SharePtr share = d->retries.takeFirst();
1080       mountShare(share);
1081       share.clear();
1082     }
1083 
1084     //
1085     // Check the size, accessibility, etc. of the shares
1086     //
1087     // FIXME: Hopefully we can replace this with a recursive QFileSystemWatcher
1088     // approach in the future. However, using the existing QFileSystemWatcher
1089     // and a QDirIterator to add all the subdirectories of a share to the watcher
1090     // seems to be too resource consuming...
1091     //
1092     if (d->checkTimeout >= 2500 && d->importedShares.isEmpty())
1093     {
1094       for (const SharePtr &share : mountedSharesList())
1095       {
1096         check(share);
1097         emit updated(share);
1098       }
1099 
1100       d->checkTimeout = 0;
1101     }
1102     else
1103     {
1104       d->checkTimeout += TIMEOUT;
1105     }
1106   }
1107 }
1108 
1109 
1110 #if defined(Q_OS_LINUX)
1111 //
1112 // Linux arguments
1113 //
fillMountActionArgs(const SharePtr & share,QVariantMap & map)1114 bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, QVariantMap& map)
1115 {
1116   //
1117   // Find the mount executable
1118   //
1119   const QString mount = findMountExecutable();
1120 
1121   if (!mount.isEmpty())
1122   {
1123     map.insert("mh_command", mount);
1124   }
1125   else
1126   {
1127     Smb4KNotification::commandNotFound("mount.cifs");
1128     return false;
1129   }
1130 
1131   //
1132   // Global and custom options
1133   //
1134   OptionsPtr options = Smb4KCustomOptionsManager::self()->findOptions(share);
1135 
1136   //
1137   // Pass the remote file system port to the URL
1138   //
1139   if (options)
1140   {
1141     if (options->useFileSystemPort())
1142     {
1143       share->setPort(options->fileSystemPort());
1144     }
1145   }
1146   else
1147   {
1148     if (Smb4KMountSettings::useRemoteFileSystemPort())
1149     {
1150       share->setPort(Smb4KMountSettings::remoteFileSystemPort());
1151     }
1152   }
1153 
1154   //
1155   // List of arguments passed via "-o ..." to the mount command
1156   //
1157   QStringList argumentsList;
1158 
1159   //
1160   // Workgroup or domain
1161   //
1162   // Do not use this, if the domain is a DNS domain.
1163   //
1164   WorkgroupPtr workgroup = findWorkgroup(share->workgroupName());
1165 
1166   if ((workgroup && !workgroup->dnsDiscovered()) && !share->workgroupName().isEmpty())
1167   {
1168     argumentsList << QString("domain=%1").arg(KShell::quoteArg(share->workgroupName()));
1169   }
1170 
1171   //
1172   // Host IP address
1173   //
1174   if (share->hasHostIpAddress())
1175   {
1176     argumentsList << QString("ip=%1").arg(share->hostIpAddress());
1177   }
1178 
1179   //
1180   // User name (login)
1181   //
1182   if (!share->login().isEmpty())
1183   {
1184     argumentsList << QString("username=%1").arg(share->login());
1185   }
1186   else
1187   {
1188     argumentsList << "guest";
1189   }
1190 
1191   //
1192   // Client's and server's NetBIOS name
1193   //
1194   // According to the manual page, this is only needed when port 139
1195   // is used. So, we only pass the NetBIOS name in that case.
1196   //
1197   bool setNetbiosNames = false;
1198 
1199   if (options)
1200   {
1201     setNetbiosNames = (options->useFileSystemPort() && options->fileSystemPort() == 139);
1202   }
1203   else
1204   {
1205     setNetbiosNames = (Smb4KMountSettings::useRemoteFileSystemPort() && Smb4KMountSettings::remoteFileSystemPort() == 139);
1206   }
1207 
1208   if (setNetbiosNames)
1209   {
1210     // The client's NetBIOS name
1211     if (!Smb4KSettings::netBIOSName().isEmpty())
1212     {
1213       argumentsList << QString("netbiosname=%1").arg(KShell::quoteArg(Smb4KSettings::netBIOSName()));
1214     }
1215     else if (!machineNetbiosName().isEmpty())
1216     {
1217       argumentsList << QString("netbiosname=%1").arg(KShell::quoteArg(machineNetbiosName()));
1218     }
1219 
1220     // The server's NetBIOS name
1221     argumentsList << QString("servern=%1").arg(KShell::quoteArg(share->hostName()));
1222   }
1223 
1224   //
1225   // CIFS Unix extensions support
1226   //
1227   // This sets the uid, gid, file_mode and dir_mode arguments, if necessary.
1228   //
1229   bool useCifsUnixExtensionsSupport = false;
1230   QString userString, groupString, fileModeString, directoryModeString;
1231 
1232   if (options)
1233   {
1234     useCifsUnixExtensionsSupport = options->cifsUnixExtensionsSupport();
1235     userString = options->useUser() ? options->user().userId().toString() : QString();
1236     groupString = options->useGroup() ? options->group().groupId().toString() : QString();
1237     fileModeString = options->useFileMode() ? options->fileMode() : QString();
1238     directoryModeString = options->useDirectoryMode() ? options->directoryMode() : QString();
1239   }
1240   else
1241   {
1242     useCifsUnixExtensionsSupport = Smb4KMountSettings::cifsUnixExtensionsSupport();
1243     userString = Smb4KMountSettings::useUserId() ? Smb4KMountSettings::userId() : QString();
1244     groupString = Smb4KMountSettings::useGroupId() ? Smb4KMountSettings::groupId() : QString();
1245     fileModeString = Smb4KMountSettings::useFileMode() ? Smb4KMountSettings::fileMode() : QString();
1246     directoryModeString = Smb4KMountSettings::useDirectoryMode() ? Smb4KMountSettings::directoryMode() : QString();
1247   }
1248 
1249   if (!useCifsUnixExtensionsSupport)
1250   {
1251     // User id
1252     if (!userString.isEmpty())
1253     {
1254       argumentsList << QString("uid=%1").arg(userString);
1255     }
1256 
1257     // Group id
1258     if (!groupString.isEmpty())
1259     {
1260       argumentsList << QString("gid=%1").arg(groupString);
1261     }
1262 
1263     // File mode
1264     if (!fileModeString.isEmpty())
1265     {
1266       argumentsList << QString("file_mode=%1").arg(fileModeString);
1267     }
1268 
1269     // Directory mode
1270     if (!directoryModeString.isEmpty())
1271     {
1272       argumentsList << QString("dir_mode=%1").arg(directoryModeString);
1273     }
1274   }
1275 
1276   //
1277   // Force user id
1278   //
1279   // FIXME: The manual page is not clear about this: Is this option only useful when the uid=...
1280   // argument is given? If so, this should be moved into the 'User id' code block above.
1281   //
1282   if (Smb4KMountSettings::forceUID())
1283   {
1284     argumentsList << "forceuid";
1285   }
1286 
1287   //
1288   // Force group id
1289   //
1290   // FIXME: The manual page is not clear about this: Is this option only useful when the gid=...
1291   // argument is given? If so, this should be moved into the 'Group id' code block above.
1292   //
1293   if (Smb4KMountSettings::forceGID())
1294   {
1295     argumentsList << "forcegid";
1296   }
1297 
1298   //
1299   // Client character set
1300   //
1301   if (Smb4KMountSettings::useClientCharset())
1302   {
1303     switch (Smb4KMountSettings::clientCharset())
1304     {
1305       case Smb4KMountSettings::EnumClientCharset::default_charset:
1306       {
1307         break;
1308       }
1309       default:
1310       {
1311         argumentsList << QString("iocharset=%1").arg(Smb4KMountSettings::self()->clientCharsetItem()->choices().value(Smb4KMountSettings::clientCharset()).label);
1312         break;
1313       }
1314     }
1315   }
1316 
1317   //
1318   // File system port
1319   //
1320   if (options)
1321   {
1322     if (options->useFileSystemPort())
1323     {
1324       argumentsList << QString("port=%1").arg(options->fileSystemPort());
1325     }
1326   }
1327   else
1328   {
1329     if (Smb4KMountSettings::useRemoteFileSystemPort())
1330     {
1331       argumentsList << QString("port=%1").arg(Smb4KMountSettings::remoteFileSystemPort());
1332     }
1333   }
1334 
1335   //
1336   // Write access
1337   //
1338   bool useWriteAccess = false;
1339   int writeAccess = -1;
1340 
1341   if (options)
1342   {
1343     useWriteAccess = options->useWriteAccess();
1344     writeAccess = options->writeAccess();
1345   }
1346   else
1347   {
1348     useWriteAccess = Smb4KMountSettings::useWriteAccess();
1349     writeAccess = Smb4KMountSettings::writeAccess();
1350   }
1351 
1352   if (useWriteAccess)
1353   {
1354     switch (writeAccess)
1355     {
1356       case Smb4KMountSettings::EnumWriteAccess::ReadWrite:
1357       {
1358         argumentsList << "rw";
1359         break;
1360       }
1361       case Smb4KMountSettings::EnumWriteAccess::ReadOnly:
1362       {
1363         argumentsList << "ro";
1364         break;
1365       }
1366       default:
1367       {
1368         break;
1369       }
1370     }
1371   }
1372 
1373   //
1374   // Permission checks
1375   //
1376   if (Smb4KMountSettings::permissionChecks())
1377   {
1378     argumentsList << "perm";
1379   }
1380   else
1381   {
1382     argumentsList << "noperm";
1383   }
1384 
1385   //
1386   // Client controls ids
1387   //
1388   if (Smb4KMountSettings::clientControlsIDs())
1389   {
1390     argumentsList << "setuids";
1391   }
1392   else
1393   {
1394     argumentsList << "nosetuids";
1395   }
1396 
1397   //
1398   // Server inode numbers
1399   //
1400   if (Smb4KMountSettings::serverInodeNumbers())
1401   {
1402     argumentsList << "serverino";
1403   }
1404   else
1405   {
1406     argumentsList << "noserverino";
1407   }
1408 
1409   //
1410   // Cache mode
1411   //
1412   if (Smb4KMountSettings::useCacheMode())
1413   {
1414     switch (Smb4KMountSettings::cacheMode())
1415     {
1416       case Smb4KMountSettings::EnumCacheMode::None:
1417       {
1418         argumentsList << "cache=none";
1419         break;
1420       }
1421       case Smb4KMountSettings::EnumCacheMode::Strict:
1422       {
1423         argumentsList << "cache=strict";
1424         break;
1425       }
1426       case Smb4KMountSettings::EnumCacheMode::Loose:
1427       {
1428         argumentsList << "cache=loose";
1429         break;
1430       }
1431       default:
1432       {
1433         break;
1434       }
1435     }
1436   }
1437 
1438   //
1439   // Translate reserved characters
1440   //
1441   if (Smb4KMountSettings::translateReservedChars())
1442   {
1443     argumentsList << "mapchars";
1444   }
1445   else
1446   {
1447     argumentsList << "nomapchars";
1448   }
1449 
1450   //
1451   // Locking
1452   //
1453   if (Smb4KMountSettings::noLocking())
1454   {
1455     argumentsList << "nolock";
1456   }
1457 
1458   //
1459   // Security mode
1460   //
1461   bool useSecurityMode = false;
1462   int securityMode = -1;
1463 
1464   if (options)
1465   {
1466     useSecurityMode = options->useSecurityMode();
1467     securityMode = options->securityMode();
1468   }
1469   else
1470   {
1471     useSecurityMode = Smb4KMountSettings::useSecurityMode();
1472     securityMode = Smb4KMountSettings::securityMode();
1473   }
1474 
1475   if (useSecurityMode)
1476   {
1477     switch (securityMode)
1478     {
1479       case Smb4KMountSettings::EnumSecurityMode::None:
1480       {
1481         argumentsList << "sec=none";
1482         break;
1483       }
1484       case Smb4KMountSettings::EnumSecurityMode::Krb5:
1485       {
1486         argumentsList << "sec=krb5";
1487         argumentsList << QString("cruid=%1").arg(KUser(KUser::UseRealUserID).userId().nativeId());
1488         break;
1489       }
1490       case Smb4KMountSettings::EnumSecurityMode::Krb5i:
1491       {
1492         argumentsList << "sec=krb5i";
1493         argumentsList << QString("cruid=%1").arg(KUser(KUser::UseRealUserID).userId().nativeId());
1494         break;
1495       }
1496       case Smb4KMountSettings::EnumSecurityMode::Ntlm:
1497       {
1498         argumentsList << "sec=ntlm";
1499         break;
1500       }
1501       case Smb4KMountSettings::EnumSecurityMode::Ntlmi:
1502       {
1503         argumentsList << "sec=ntlmi";
1504         break;
1505       }
1506       case Smb4KMountSettings::EnumSecurityMode::Ntlmv2:
1507       {
1508         argumentsList << "sec=ntlmv2";
1509         break;
1510       }
1511       case Smb4KMountSettings::EnumSecurityMode::Ntlmv2i:
1512       {
1513         argumentsList << "sec=ntlmv2i";
1514         break;
1515       }
1516       case Smb4KMountSettings::EnumSecurityMode::Ntlmssp:
1517       {
1518         argumentsList << "sec=ntlmssp";
1519         break;
1520       }
1521       case Smb4KMountSettings::EnumSecurityMode::Ntlmsspi:
1522       {
1523         argumentsList << "sec=ntlmsspi";
1524         break;
1525       }
1526       default:
1527       {
1528         // Smb4KSettings::EnumSecurityMode::Default,
1529         break;
1530       }
1531     }
1532   }
1533 
1534   //
1535   // SMB protocol version
1536   //
1537   bool useMountProtocolVersion = false;
1538   int mountProtocolVersion = -1;
1539 
1540   if (options)
1541   {
1542     useMountProtocolVersion = options->useMountProtocolVersion();
1543     mountProtocolVersion = options->mountProtocolVersion();
1544   }
1545   else
1546   {
1547     useMountProtocolVersion = Smb4KMountSettings::useSmbProtocolVersion();
1548     mountProtocolVersion = Smb4KMountSettings::smbProtocolVersion();
1549   }
1550 
1551   if (useMountProtocolVersion)
1552   {
1553     switch (mountProtocolVersion)
1554     {
1555       case Smb4KMountSettings::EnumSmbProtocolVersion::OnePointZero:
1556       {
1557         argumentsList << "vers=1.0";
1558         break;
1559       }
1560       case Smb4KMountSettings::EnumSmbProtocolVersion::TwoPointZero:
1561       {
1562         argumentsList << "vers=2.0";
1563         break;
1564       }
1565       case Smb4KMountSettings::EnumSmbProtocolVersion::TwoPointOne:
1566       {
1567         argumentsList << "vers=2.1";
1568         break;
1569       }
1570       case Smb4KMountSettings::EnumSmbProtocolVersion::ThreePointZero:
1571       {
1572         argumentsList << "vers=3.0";
1573         break;
1574       }
1575       case Smb4KMountSettings::EnumSmbProtocolVersion::ThreePointZeroPointTwo:
1576       {
1577         argumentsList << "vers=3.0.2";
1578         break;
1579       }
1580       case Smb4KMountSettings::EnumSmbProtocolVersion::ThreePointOnePointOne:
1581       {
1582         argumentsList << "vers=3.1.1";
1583         break;
1584       }
1585       case Smb4KMountSettings::EnumSmbProtocolVersion::ThreeAndAbove:
1586       {
1587         argumentsList << "vers=3";
1588         break;
1589       }
1590       case Smb4KMountSettings::EnumSmbProtocolVersion::Default:
1591       {
1592         argumentsList << "vers=default";
1593         break;
1594       }
1595       default:
1596       {
1597         break;
1598       }
1599     }
1600   }
1601 
1602   //
1603   // Mount options provided by the user
1604   //
1605   if (!Smb4KMountSettings::customCIFSOptions().isEmpty())
1606   {
1607     // SECURITY: Only pass those arguments to mount.cifs that do not pose
1608     // a potential security risk and that have not already been defined.
1609     //
1610     // This is, among others, the proper fix to the security issue reported
1611     // by Heiner Markert (aka CVE-2014-2581).
1612     QStringList allowedArgs = allowedMountArguments();
1613     QStringList list = Smb4KMountSettings::customCIFSOptions().split(',', QString::SkipEmptyParts);
1614     QMutableStringListIterator it(list);
1615 
1616     while (it.hasNext())
1617     {
1618       QString arg = it.next().section("=", 0, 0);
1619 
1620       if (!allowedArgs.contains(arg))
1621       {
1622         it.remove();
1623       }
1624 
1625       argumentsList += list;
1626     }
1627   }
1628 
1629   //
1630   // Insert the mount options into the map
1631   //
1632   QStringList mh_options;
1633   mh_options << "-o";
1634   mh_options << argumentsList.join(",");
1635   map.insert("mh_options", mh_options);
1636 
1637   //
1638   // Insert the mountpoint into the map
1639   //
1640   map.insert("mh_mountpoint", share->canonicalPath());
1641 
1642   //
1643   // Insert information about the share and its URL into the map
1644   //
1645   if (!share->isHomesShare())
1646   {
1647     map.insert("mh_url", share->url());
1648   }
1649   else
1650   {
1651     map.insert("mh_url", share->homeUrl());
1652     map.insert("mh_homes_url", share->url());
1653   }
1654 
1655   //
1656   // Location of the Kerberos ticket
1657   //
1658   // The path to the Kerberos ticket is stored - if it exists - in the
1659   // KRB5CCNAME environment variable. By default, the ticket is located
1660   // at /tmp/krb5cc_[uid]. So, if the environment variable does not exist,
1661   // but the cache file is there, try to use it.
1662   //
1663   if (QProcessEnvironment::systemEnvironment().contains("KRB5CCNAME"))
1664   {
1665     map.insert("mh_krb5ticket", QProcessEnvironment::systemEnvironment().value("KRB5CCNAME", ""));
1666   }
1667   else
1668   {
1669     QString ticket = QString("/tmp/krb5cc_%1").arg(KUser(KUser::UseRealUserID).userId().nativeId());
1670 
1671     if (QFile::exists(ticket))
1672     {
1673       map.insert("mh_krb5ticket", "FILE:"+ticket);
1674     }
1675   }
1676 
1677   return true;
1678 }
1679 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
1680 //
1681 // FreeBSD and NetBSD arguments
1682 //
fillMountActionArgs(const SharePtr & share,QVariantMap & map)1683 bool Smb4KMounter::fillMountActionArgs(const SharePtr &share, QVariantMap& map)
1684 {
1685   //
1686   // Find the mount executable
1687   //
1688   const QString mount = findMountExecutable();
1689 
1690   if (!mount.isEmpty())
1691   {
1692     map.insert("mh_command", mount);
1693   }
1694   else
1695   {
1696     Smb4KNotification::commandNotFound("mount_smbfs");
1697     return false;
1698   }
1699 
1700   //
1701   // Global and custom options
1702   //
1703   OptionsPtr options = Smb4KCustomOptionsManager::self()->findOptions(share);
1704 
1705   //
1706   // List of arguments
1707   //
1708   QStringList argumentsList;
1709 
1710   //
1711   // Workgroup or domain
1712   //
1713   // Do not use this, if the domain is a DNS domain.
1714   //
1715   WorkgroupPtr workgroup = findWorkgroup(share->workgroupName());
1716 
1717   if ((workgroup && !workgroup->dnsDiscovered()) && !share->workgroupName().isEmpty())
1718   {
1719     argumentsList << "-W";
1720     argumentsList << KShell::quoteArg(share->workgroupName());
1721   }
1722 
1723   //
1724   // IP address
1725   //
1726   if (!share->hostIpAddress().isEmpty())
1727   {
1728     argumentsList << "-I";
1729     argumentsList << share->hostIpAddress();
1730   }
1731 
1732   //
1733   // User Id
1734   //
1735   if (options)
1736   {
1737     if (options->useUser())
1738     {
1739       argumentsList << "-u";
1740       argumentsList << QString("%1").arg(options->user().userId().nativeId());
1741     }
1742   }
1743   else
1744   {
1745     if (Smb4KMountSettings::useUserId())
1746     {
1747       argumentsList << "-u";
1748       argumentsList << QString("%1").arg((K_UID)Smb4KMountSettings::userId().toInt());
1749     }
1750   }
1751 
1752   //
1753   // Group Id
1754   //
1755   if (options)
1756   {
1757     if (options->useGroup())
1758     {
1759       argumentsList << "-g";
1760       argumentsList << QString("%1").arg(options->group().groupId().nativeId());
1761     }
1762   }
1763   else
1764   {
1765     if (Smb4KMountSettings::useGroupId())
1766     {
1767       argumentsList << "-g";
1768       argumentsList << QString("%1").arg((K_GID)Smb4KMountSettings::groupId().toInt());
1769     }
1770   }
1771 
1772   if (Smb4KMountSettings::useCharacterSets())
1773   {
1774     // Client character set
1775     QString clientCharset, serverCharset;
1776 
1777     switch (Smb4KMountSettings::clientCharset())
1778     {
1779       case Smb4KMountSettings::EnumClientCharset::default_charset:
1780       {
1781         break;
1782       }
1783       default:
1784       {
1785         clientCharset = Smb4KMountSettings::self()->clientCharsetItem()->choices().value(Smb4KMountSettings::clientCharset()).label;
1786         break;
1787       }
1788     }
1789 
1790     // Server character set
1791     switch (Smb4KMountSettings::serverCodepage())
1792     {
1793       case Smb4KMountSettings::EnumServerCodepage::default_codepage:
1794       {
1795         break;
1796       }
1797       default:
1798       {
1799         serverCharset = Smb4KMountSettings::self()->serverCodepageItem()->choices().value(Smb4KMountSettings::serverCodepage()).label;
1800         break;
1801       }
1802     }
1803 
1804     if (!clientCharset.isEmpty() && !serverCharset.isEmpty())
1805     {
1806       argumentsList << "-E";
1807       argumentsList << QString("%1:%2").arg(clientCharset, serverCharset);
1808     }
1809   }
1810 
1811   //
1812   // File mode
1813   //
1814   if (options)
1815   {
1816     if (options->useFileMode())
1817     {
1818       argumentsList << "-f";
1819       argumentsList << options->fileMode();
1820     }
1821   }
1822   else
1823   {
1824     if (Smb4KMountSettings::useFileMode())
1825     {
1826       argumentsList << "-f";
1827       argumentsList << Smb4KMountSettings::fileMode();
1828     }
1829   }
1830 
1831   //
1832   // Directory mode
1833   //
1834   if (options)
1835   {
1836     if (options->useDirectoryMode())
1837     {
1838       argumentsList << "-d";
1839       argumentsList << options->directoryMode();
1840     }
1841   }
1842   else
1843   {
1844     if (Smb4KMountSettings::useDirectoryMode())
1845     {
1846       argumentsList << "-d";
1847       argumentsList << Smb4KMountSettings::directoryMode();
1848     }
1849   }
1850 
1851   //
1852   // User name (login)
1853   //
1854   if (!share->login().isEmpty())
1855   {
1856     argumentsList << "-U";
1857     argumentsList << share->login();
1858   }
1859   else
1860   {
1861     argumentsList << "-N";
1862   }
1863 
1864   //
1865   // Insert the mount options into the map
1866   //
1867   map.insert("mh_options", argumentsList);
1868 
1869   //
1870   // Insert the mountpoint into the map
1871   //
1872   map.insert("mh_mountpoint", share->canonicalPath());
1873 
1874   //
1875   // Insert information about the share and its URL into the map
1876   //
1877   if (!share->isHomesShare())
1878   {
1879     map.insert("mh_url", share->url());
1880   }
1881   else
1882   {
1883     map.insert("mh_url", share->homeUrl());
1884     map.insert("mh_homes_url", share->url());
1885   }
1886 
1887   return true;
1888 }
1889 #else
1890 //
1891 // Dummy
1892 //
fillMountActionArgs(const SharePtr &,QVariantMap &)1893 bool Smb4KMounter::fillMountActionArgs(const SharePtr &, QVariantMap&)
1894 {
1895   qWarning() << "Smb4KMounter::fillMountActionArgs() is not implemented!";
1896   qWarning() << "Mounting under this operating system is not supported...";
1897   return false;
1898 }
1899 #endif
1900 
1901 
1902 #if defined(Q_OS_LINUX)
1903 //
1904 // Linux arguments
1905 //
fillUnmountActionArgs(const SharePtr & share,bool force,bool silent,QVariantMap & map)1906 bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &share, bool force, bool silent, QVariantMap &map)
1907 {
1908   //
1909   // The umount program
1910   //
1911   const QString umount = findUmountExecutable();
1912 
1913   if (umount.isEmpty() && !silent)
1914   {
1915     Smb4KNotification::commandNotFound("umount");
1916     return false;
1917   }
1918 
1919   //
1920   // The options
1921   //
1922   QStringList options;
1923 
1924   if (force)
1925   {
1926     options << "-l"; // lazy unmount
1927   }
1928 
1929   //
1930   // Insert data into the map
1931   //
1932   map.insert("mh_command", umount);
1933   map.insert("mh_url", share->url());
1934 
1935   if (!share->isInaccessible() && Smb4KHardwareInterface::self()->isOnline())
1936   {
1937     map.insert("mh_mountpoint", share->canonicalPath());
1938   }
1939   else
1940   {
1941     map.insert("mh_mountpoint", share->path());
1942   }
1943 
1944   map.insert("mh_options", options);
1945 
1946   return true;
1947 }
1948 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
1949 //
1950 // FreeBSD and NetBSD arguments
1951 //
fillUnmountActionArgs(const SharePtr & share,bool force,bool silent,QVariantMap & map)1952 bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &share, bool force, bool silent, QVariantMap &map)
1953 {
1954   //
1955   // The umount program
1956   //
1957   const QString umount = findUmountExecutable();
1958 
1959   if (umount.isEmpty() && !silent)
1960   {
1961     Smb4KNotification::commandNotFound("umount");
1962     return false;
1963   }
1964 
1965   //
1966   // The options
1967   //
1968   QStringList options;
1969 
1970   if (force)
1971   {
1972     options << "-f";
1973   }
1974 
1975   //
1976   // Insert data into the map
1977   //
1978   map.insert("mh_command", umount);
1979   map.insert("mh_url", share->url());
1980 
1981   if (!share->isInaccessible() && Smb4KHardwareInterface::self()->isOnline())
1982   {
1983     map.insert("mh_mountpoint", share->canonicalPath());
1984   }
1985   else
1986   {
1987     map.insert("mh_mountpoint", share->path());
1988   }
1989 
1990   map.insert("mh_options", options);
1991 
1992   return true;
1993 }
1994 #else
1995 //
1996 // Dummy
1997 //
fillUnmountActionArgs(const SharePtr &,bool,bool,QVariantMap &)1998 bool Smb4KMounter::fillUnmountActionArgs(const SharePtr &, bool, bool, QVariantMap &)
1999 {
2000   qWarning() << "Smb4KMounter::fillUnmountActionArgs() is not implemented!";
2001   qWarning() << "Unmounting under this operating system is not supported...";
2002   return false;
2003 }
2004 #endif
2005 
2006 
check(const SharePtr & share)2007 void Smb4KMounter::check(const SharePtr &share)
2008 {
2009   // Get the info about the usage, etc.
2010   KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo(share->path());
2011 
2012   if (spaceInfo.isValid())
2013   {
2014     // Accessibility
2015     share->setInaccessible(false);
2016 
2017     // Size information
2018     share->setFreeDiskSpace(spaceInfo.available());
2019     share->setTotalDiskSpace(spaceInfo.size());
2020     share->setUsedDiskSpace(spaceInfo.used());
2021 
2022     // Get the owner an group, if possible.
2023     QFileInfo fileInfo(share->path());
2024     fileInfo.setCaching(false);
2025 
2026     if (fileInfo.exists())
2027     {
2028       share->setUser(KUser(static_cast<K_UID>(fileInfo.ownerId())));
2029       share->setGroup(KUserGroup(static_cast<K_GID>(fileInfo.groupId())));
2030       share->setInaccessible(!(fileInfo.isDir() && fileInfo.isExecutable()));
2031     }
2032     else
2033     {
2034       share->setInaccessible(true);
2035       share->setFreeDiskSpace(0);
2036       share->setTotalDiskSpace(0);
2037       share->setUsedDiskSpace(0);
2038       share->setUser(KUser(KUser::UseRealUserID));
2039       share->setGroup(KUserGroup(KUser::UseRealUserID));
2040     }
2041   }
2042   else
2043   {
2044     share->setInaccessible(true);
2045     share->setFreeDiskSpace(0);
2046     share->setTotalDiskSpace(0);
2047     share->setUsedDiskSpace(0);
2048     share->setUser(KUser(KUser::UseRealUserID));
2049     share->setGroup(KUserGroup(KUser::UseRealUserID));
2050   }
2051 }
2052 
2053 
2054 
2055 /////////////////////////////////////////////////////////////////////////////
2056 // SLOT IMPLEMENTATIONS
2057 /////////////////////////////////////////////////////////////////////////////
2058 
2059 
slotStartJobs()2060 void Smb4KMounter::slotStartJobs()
2061 {
2062   //
2063   // Start the import of shares
2064   //
2065   if (Smb4KHardwareInterface::self()->isOnline())
2066   {
2067     import(true);
2068   }
2069 
2070   //
2071   // Start the timer
2072   //
2073   if (d->timerId == -1)
2074   {
2075     d->timerId = startTimer(TIMEOUT);
2076   }
2077 }
2078 
2079 
slotAboutToQuit()2080 void Smb4KMounter::slotAboutToQuit()
2081 {
2082   //
2083   // Abort any actions
2084   //
2085   abort();
2086 
2087   //
2088   // Check if the user wants to remount shares and save the
2089   // shares for remount if so.
2090   //
2091   if (Smb4KMountSettings::remountShares())
2092   {
2093     saveSharesForRemount();
2094   }
2095 
2096   //
2097   // Unmount the shares if the user chose to do so.
2098   //
2099   if (Smb4KMountSettings::unmountSharesOnExit())
2100   {
2101     unmountAllShares(true);
2102   }
2103 
2104   //
2105   // Clean up the mount prefix.
2106   //
2107   KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded|KMountPoint::NeedMountOptions);
2108 
2109   QDir dir;
2110   dir.cd(Smb4KMountSettings::mountPrefix().path());
2111   QStringList hostDirs = dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot, QDir::NoSort);
2112   QStringList mountpoints;
2113 
2114   for (const QString &hostDir : qAsConst(hostDirs))
2115   {
2116     dir.cd(hostDir);
2117 
2118     QStringList shareDirs = dir.entryList(QDir::Dirs|QDir::NoDotAndDotDot, QDir::NoSort);
2119 
2120     for (const QString &shareDir : qAsConst(shareDirs))
2121     {
2122       dir.cd(shareDir);
2123       mountpoints << dir.absolutePath();
2124       dir.cdUp();
2125     }
2126 
2127     dir.cdUp();
2128   }
2129 
2130   // Remove those mountpoints where a share is actually mounted.
2131   for (const QExplicitlySharedDataPointer<KMountPoint> &mountPoint : qAsConst(mountPoints))
2132   {
2133     mountpoints.removeOne(mountPoint->mountPoint());
2134   }
2135 
2136   // Remove the empty mountpoints.
2137   for (const QString &mp : qAsConst(mountpoints))
2138   {
2139     dir.cd(mp);
2140     dir.rmdir(dir.canonicalPath());
2141 
2142     if (dir.cdUp())
2143     {
2144       dir.rmdir(dir.canonicalPath());
2145     }
2146   }
2147 }
2148 
2149 
slotOnlineStateChanged(bool online)2150 void Smb4KMounter::slotOnlineStateChanged(bool online)
2151 {
2152   if (online)
2153   {
2154     //
2155     // (Re-)start the job
2156     //
2157     slotStartJobs();
2158   }
2159   else
2160   {
2161     //
2162     // Abort all running jobs if the computer goes offline
2163     //
2164     abort();
2165 
2166     //
2167     // Save the list of shares for later remount
2168     //
2169     saveSharesForRemount();
2170 
2171     //
2172     // Mark all mounted shares as inaccessible and send the updated() signal
2173     //
2174     for (const SharePtr &share : mountedSharesList())
2175     {
2176       // Only mark the shares inaccessible and DO NOT emit
2177       // the updated() signal here, because that would freeze
2178       // the application.
2179       share->setInaccessible(true);
2180     }
2181 
2182     //
2183     // Now unmount all shares
2184     //
2185     unmountAllShares(true);
2186   }
2187 }
2188 
2189 
slotStatResult(KJob * job)2190 void Smb4KMounter::slotStatResult(KJob *job)
2191 {
2192   Q_ASSERT(job);
2193 
2194   //
2195   // Stat job
2196   //
2197   KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job);
2198 
2199   //
2200   // Get the mountpoint
2201   //
2202   QString mountpoint = statJob->url().toDisplayString(QUrl::PreferLocalFile);
2203 
2204   //
2205   // Find the imported share
2206   //
2207   SharePtr importedShare;
2208 
2209   for (int i = 0; i < d->importedShares.size(); ++i)
2210   {
2211     if (QString::compare(d->importedShares.at(i)->path(), mountpoint) == 0)
2212     {
2213       importedShare = d->importedShares.takeAt(i);
2214       break;
2215     }
2216   }
2217 
2218   //
2219   // If the share should have vanished in the meantime, return here.
2220   //
2221   if (!importedShare)
2222   {
2223     return;
2224   }
2225 
2226   //
2227   // Add the size, user and group information
2228   //
2229   if (statJob->error() == 0 /* no error */)
2230   {
2231     check(importedShare);
2232   }
2233   else
2234   {
2235     importedShare->setInaccessible(true);
2236     importedShare->setFreeDiskSpace(0);
2237     importedShare->setTotalDiskSpace(0);
2238     importedShare->setUsedDiskSpace(0);
2239     importedShare->setUser(KUser(KUser::UseRealUserID));
2240     importedShare->setGroup(KUserGroup(KUser::UseRealUserID));
2241   }
2242 
2243   //
2244   // Decide whether this is a share mounted by the user or by someone else.
2245   //
2246   QString canonicalMountPrefix = QDir(Smb4KMountSettings::mountPrefix().path()).canonicalPath();
2247   QString canonicalHomePath = QDir::home().canonicalPath();
2248 
2249   if (importedShare->path().startsWith(Smb4KMountSettings::mountPrefix().path()) || importedShare->canonicalPath().startsWith(canonicalMountPrefix))
2250   {
2251     //
2252     // The path is below the mount prefix
2253     //
2254     importedShare->setForeign(false);
2255   }
2256   else if (importedShare->path().startsWith(QDir::homePath()) || importedShare->canonicalPath().startsWith(canonicalHomePath))
2257   {
2258     //
2259     // The path is below the home directory
2260     //
2261     importedShare->setForeign(false);
2262   }
2263   else if (importedShare->user().userId() == KUser(KUser::UseRealUserID).userId() && importedShare->group().groupId() == KUserGroup(KUser::UseRealUserID).groupId())
2264   {
2265     //
2266     // The IDs are the same
2267     //
2268     importedShare->setForeign(false);
2269   }
2270   else
2271   {
2272     //
2273     // The path is elsewhere. This is most certainly a foreign share.
2274     //
2275     importedShare->setForeign(true);
2276   }
2277 
2278   //
2279   // Search for a previously added mounted share and try to update it. If this fails,
2280   // add the share to the global list.
2281   //
2282   if (!importedShare->isForeign() || Smb4KMountSettings::detectAllShares())
2283   {
2284     if (updateMountedShare(importedShare))
2285     {
2286       SharePtr updatedShare = findShareByPath(importedShare->path());
2287 
2288       if (updatedShare)
2289       {
2290         emit updated(updatedShare);
2291       }
2292 
2293       importedShare.clear();
2294     }
2295     else
2296     {
2297       if (addMountedShare(importedShare))
2298       {
2299         // Remove the share from the list of shares that are to be remounted
2300         QMutableListIterator<SharePtr> s(d->remounts);
2301 
2302         while (s.hasNext())
2303         {
2304           SharePtr remount = s.next();
2305 
2306           if (!importedShare->isForeign() &&
2307               QString::compare(remount->url().toString(QUrl::RemoveUserInfo|QUrl::RemovePort),
2308                                importedShare->url().toString(QUrl::RemoveUserInfo|QUrl::RemovePort),
2309                                Qt::CaseInsensitive) == 0)
2310           {
2311             Smb4KCustomOptionsManager::self()->removeRemount(remount);
2312             s.remove();
2313             break;
2314           }
2315           else
2316           {
2317             continue;
2318           }
2319         }
2320 
2321         // Tell the program and the user that the share was mounted. Also, reset the
2322         // counter of newly mounted shares, if necessary.
2323         d->newlyMounted += 1;
2324         emit mounted(importedShare);
2325 
2326         if (!isRunning() && d->firstImportDone && d->importedShares.isEmpty() && d->newlyMounted == 1)
2327         {
2328           Smb4KNotification::shareMounted(importedShare);
2329         }
2330 
2331         QTimer::singleShot(250, this, [&] () {
2332           if (!isRunning())
2333           {
2334             if (d->firstImportDone && d->importedShares.isEmpty() && d->newlyMounted > 1)
2335             {
2336               Smb4KNotification::sharesMounted(d->newlyMounted);
2337             }
2338 
2339             d->newlyMounted = 0;
2340           }
2341         });
2342 
2343         emit mountedSharesListChanged();
2344       }
2345       else
2346       {
2347         importedShare.clear();
2348       }
2349     }
2350   }
2351   else
2352   {
2353     importedShare.clear();
2354   }
2355 
2356   if (!d->firstImportDone && d->importedShares.isEmpty())
2357   {
2358     d->firstImportDone = true;
2359   }
2360 }
2361 
2362 
slotAboutToChangeProfile()2363 void Smb4KMounter::slotAboutToChangeProfile()
2364 {
2365   //
2366   // Save those shares that are to be remounted
2367   //
2368   if (Smb4KMountSettings::remountShares())
2369   {
2370     saveSharesForRemount();
2371   }
2372 }
2373 
2374 
slotActiveProfileChanged(const QString & newProfile)2375 void Smb4KMounter::slotActiveProfileChanged(const QString &newProfile)
2376 {
2377   if (d->activeProfile != newProfile)
2378   {
2379     // Stop the timer.
2380     killTimer(d->timerId);
2381 
2382     abort();
2383 
2384     // Clear all remounts.
2385     while (!d->remounts.isEmpty())
2386     {
2387       d->remounts.takeFirst().clear();
2388     }
2389 
2390     // Clear all retries.
2391     while (!d->retries.isEmpty())
2392     {
2393       d->retries.takeFirst().clear();
2394     }
2395 
2396     // Unmount all shares
2397     unmountAllShares(true);
2398 
2399     // Reset some variables.
2400     d->remountTimeout = 0;
2401     d->remountAttempts = 0;
2402     d->firstImportDone = false;
2403     d->activeProfile = newProfile;
2404 
2405     // Restart the timer
2406     d->timerId = startTimer(TIMEOUT);
2407   }
2408 }
2409 
2410 
slotProfileMigrated(const QString & from,const QString & to)2411 void Smb4KMounter::slotProfileMigrated(const QString& from, const QString& to)
2412 {
2413   if (QString::compare(from, d->activeProfile, Qt::CaseSensitive) == 0)
2414   {
2415     d->activeProfile = to;
2416   }
2417 }
2418 
2419 
slotTriggerImport()2420 void Smb4KMounter::slotTriggerImport()
2421 {
2422   //
2423   // Wait a bit so that the mount or unmount process can finish and
2424   // then start importing the shares, if no jobs are running anymore
2425   //
2426   QTimer::singleShot(TIMEOUT, this, [&] () {
2427     if (!isRunning())
2428     {
2429       import(true);
2430     }
2431   });
2432 }
2433 
2434 
slotConfigChanged()2435 void Smb4KMounter::slotConfigChanged()
2436 {
2437   if (d->detectAllShares != Smb4KMountSettings::detectAllShares())
2438   {
2439     import(true);
2440     d->detectAllShares = Smb4KMountSettings::detectAllShares();
2441   }
2442 }
2443 
2444