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