File indexing completed on 2024-12-01 04:37:13

0001 /*
0002     The helper that mounts and unmounts shares.
0003 
0004     SPDX-FileCopyrightText: 2010-2022 Alexander Reinholdt <alexander.reinholdt@kdemail.net>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // application specific includes
0009 #include "smb4kmounthelper.h"
0010 #include "../core/smb4kglobal.h"
0011 
0012 // Qt includes
0013 #include <QDebug>
0014 #include <QNetworkInterface>
0015 #include <QProcessEnvironment>
0016 #include <QUrl>
0017 
0018 // KDE includes
0019 #include <KAuth/HelperSupport>
0020 #include <KLocalizedString>
0021 #include <KMountPoint>
0022 #include <KProcess>
0023 
0024 using namespace Smb4KGlobal;
0025 
0026 KAUTH_HELPER_MAIN("org.kde.smb4k.mounthelper", Smb4KMountHelper);
0027 
0028 KAuth::ActionReply Smb4KMountHelper::mount(const QVariantMap &args)
0029 {
0030     //
0031     // The action reply
0032     //
0033     ActionReply reply;
0034 
0035     //
0036     // Check if the system is online and return an error if it is not
0037     //
0038     bool online = false;
0039     QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
0040 
0041     for (const QNetworkInterface &interface : qAsConst(interfaces)) {
0042         if (interface.isValid() && interface.type() != QNetworkInterface::Loopback && interface.flags() & QNetworkInterface::IsRunning && !online) {
0043             online = true;
0044             break;
0045         }
0046     }
0047 
0048     if (!online) {
0049         reply.setType(ActionReply::HelperErrorType);
0050         return reply;
0051     }
0052 
0053     //
0054     // Get the mount executable
0055     //
0056     const QString mount = findMountExecutable();
0057 
0058     //
0059     // Check the mount executable
0060     //
0061     if (mount != args[QStringLiteral("mh_command")].toString()) {
0062         // Something weird is going on, bail out.
0063         reply.setType(ActionReply::HelperErrorType);
0064         return reply;
0065     }
0066 
0067     //
0068     // Mount command
0069     //
0070     QStringList command;
0071 #if defined(Q_OS_LINUX)
0072     command << mount;
0073     command << args[QStringLiteral("mh_url")].toUrl().toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::RemovePort);
0074     command << args[QStringLiteral("mh_mountpoint")].toString();
0075     command << args[QStringLiteral("mh_options")].toStringList();
0076 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
0077     command << mount;
0078     command << args[QStringLiteral("mh_options")].toStringList();
0079     command << args[QStringLiteral("mh_url")].toUrl().toString(QUrl::RemoveScheme | QUrl::RemoveUserInfo | QUrl::RemovePort);
0080     command << args[QStringLiteral("mh_mountpoint")].toString();
0081 #endif
0082 
0083     //
0084     // The process
0085     //
0086     KProcess proc(this);
0087     proc.setOutputChannelMode(KProcess::SeparateChannels);
0088     proc.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
0089 #if defined(Q_OS_LINUX)
0090     proc.setEnv(QStringLiteral("PASSWD"), args[QStringLiteral("mh_url")].toUrl().password(), true);
0091 #endif
0092     // We need this to avoid a translated password prompt.
0093     proc.setEnv(QStringLiteral("LANG"), QStringLiteral("C"));
0094     // If the location of a Kerberos ticket is passed, it needs to
0095     // be passed to the process environment here.
0096     if (args.contains(QStringLiteral("mh_krb5ticket"))) {
0097         proc.setEnv(QStringLiteral("KRB5CCNAME"), args[QStringLiteral("mh_krb5ticket")].toString());
0098     }
0099 
0100     proc.setProgram(command);
0101     proc.start();
0102 
0103     if (proc.waitForStarted(-1)) {
0104         int timeout = 0;
0105 
0106         while (proc.state() != KProcess::NotRunning) {
0107             // We want to be able to terminate the process from outside.
0108             // Thus, we implement a loop that checks periodically, if we
0109             // need to kill the process.
0110             if (HelperSupport::isStopped() || timeout == 30000) {
0111                 proc.kill();
0112                 break;
0113             }
0114 
0115             // Check if there is a password prompt. If there is one, pass
0116             // the password to it.
0117             QByteArray out = proc.readAllStandardError();
0118 
0119             if (out.startsWith("Password")) {
0120                 proc.write(args[QStringLiteral("mh_url")].toUrl().password().toUtf8().data());
0121                 proc.write("\r");
0122             }
0123 
0124             timeout += 10;
0125 
0126             wait(10);
0127         }
0128 
0129         if (proc.exitStatus() == KProcess::NormalExit) {
0130             QString stdErr = QString::fromUtf8(proc.readAllStandardError());
0131             reply.addData(QStringLiteral("mh_error_message"), stdErr.trimmed());
0132         }
0133     } else {
0134         reply.setType(ActionReply::HelperErrorType);
0135         reply.setErrorDescription(i18n("The mount process could not be started."));
0136     }
0137 
0138     return reply;
0139 }
0140 
0141 KAuth::ActionReply Smb4KMountHelper::unmount(const QVariantMap &args)
0142 {
0143     ActionReply reply;
0144 
0145     //
0146     // Get the umount executable
0147     //
0148     const QString umount = findUmountExecutable();
0149 
0150     //
0151     // Check the mount executable
0152     //
0153     if (umount != args[QStringLiteral("mh_command")].toString()) {
0154         // Something weird is going on, bail out.
0155         reply.setType(ActionReply::HelperErrorType);
0156         return reply;
0157     }
0158 
0159     //
0160     // Check if the mountpoint is valid and the filesystem is correct.
0161     //
0162     bool mountPointOk = false;
0163     KMountPoint::List mountPoints = KMountPoint::currentMountPoints(KMountPoint::BasicInfoNeeded | KMountPoint::NeedMountOptions);
0164 
0165     for (const QExplicitlySharedDataPointer<KMountPoint> &mountPoint : qAsConst(mountPoints)) {
0166         if (args[QStringLiteral("mh_mountpoint")].toString() == mountPoint->mountPoint()
0167             && (mountPoint->mountType() == QStringLiteral("cifs") || mountPoint->mountType() == QStringLiteral("smb3")
0168                 || mountPoint->mountType() == QStringLiteral("smbfs"))) {
0169             mountPointOk = true;
0170             break;
0171         }
0172     }
0173 
0174     //
0175     // Stop here if the mountpoint is not valid
0176     //
0177     if (!mountPointOk) {
0178         reply.setType(ActionReply::HelperErrorType);
0179         reply.setErrorDescription(i18n("The mountpoint %1 is invalid.", args[QStringLiteral("mh_mountpoint")].toString()));
0180     }
0181 
0182     //
0183     // The command
0184     //
0185     QStringList command;
0186     command << umount;
0187     command << args[QStringLiteral("mh_options")].toStringList();
0188     command << args[QStringLiteral("mh_mountpoint")].toString();
0189 
0190     //
0191     // The process
0192     //
0193     KProcess proc(this);
0194     proc.setOutputChannelMode(KProcess::SeparateChannels);
0195     proc.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
0196     proc.setProgram(command);
0197 
0198     //
0199     // Depending on the online state, use a different behavior for unmounting.
0200     //
0201     // Extensive tests have shown that - when offline - unmounting does not
0202     // work properly when the process is not detached. Thus, detach it when
0203     // the system is offline.
0204     //
0205     bool online = false;
0206     QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
0207 
0208     for (const QNetworkInterface &interface : qAsConst(interfaces)) {
0209         if (interface.isValid() && interface.type() != QNetworkInterface::Loopback && interface.flags() & QNetworkInterface::IsRunning && !online) {
0210             online = true;
0211             break;
0212         }
0213     }
0214 
0215     if (online) {
0216         proc.start();
0217 
0218         if (proc.waitForStarted(-1)) {
0219             // We want to be able to terminate the process from outside.
0220             // Thus, we implement a loop that checks periodically, if we
0221             // need to kill the process.
0222             int timeout = 0;
0223 
0224             while (proc.state() != KProcess::NotRunning) {
0225                 if (HelperSupport::isStopped() || timeout == 30000) {
0226                     proc.kill();
0227                     break;
0228                 }
0229 
0230                 timeout += 10;
0231 
0232                 wait(10);
0233             }
0234 
0235             if (proc.exitStatus() == KProcess::NormalExit) {
0236                 QString stdErr = QString::fromUtf8(proc.readAllStandardError());
0237                 reply.addData(QStringLiteral("mh_error_message"), stdErr.trimmed());
0238             }
0239         } else {
0240             reply.setType(ActionReply::HelperErrorType);
0241             reply.setErrorDescription(i18n("The unmount process could not be started."));
0242         }
0243     } else {
0244         proc.startDetached();
0245     }
0246 
0247     return reply;
0248 }