File indexing completed on 2024-04-14 04:51:50

0001 /**
0002  * SPDX-FileCopyrightText: 2014 Samoilenko Yuri <kinnalru@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "mounter.h"
0008 
0009 #include <QDebug>
0010 #include <QDir>
0011 #include <unistd.h>
0012 
0013 #include <KLocalizedString>
0014 
0015 #include "config-sftp.h"
0016 #include "kdeconnectconfig.h"
0017 #include "mountloop.h"
0018 #include "plugin_sftp_debug.h"
0019 
0020 Mounter::Mounter(SftpPlugin *sftp)
0021     : QObject(sftp)
0022     , m_sftp(sftp)
0023     , m_proc(nullptr)
0024     , m_mountPoint(sftp->mountPoint())
0025     , m_started(false)
0026 {
0027     connect(&m_connectTimer, &QTimer::timeout, this, &Mounter::onMountTimeout);
0028 
0029     connect(this, &Mounter::mounted, &m_connectTimer, &QTimer::stop);
0030     connect(this, &Mounter::failed, &m_connectTimer, &QTimer::stop);
0031 
0032     m_connectTimer.setInterval(10000);
0033     m_connectTimer.setSingleShot(true);
0034 
0035     QTimer::singleShot(0, this, &Mounter::start);
0036     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Created mounter";
0037 }
0038 
0039 Mounter::~Mounter()
0040 {
0041     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Destroy mounter";
0042     unmount(false);
0043 }
0044 
0045 bool Mounter::wait()
0046 {
0047     if (m_started) {
0048         return true;
0049     }
0050 
0051     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting loop to wait for mount";
0052 
0053     MountLoop loop;
0054     connect(this, &Mounter::mounted, &loop, &MountLoop::succeeded);
0055     connect(this, &Mounter::failed, &loop, &MountLoop::failed);
0056     return loop.exec();
0057 }
0058 
0059 void Mounter::onPacketReceived(const NetworkPacket &np)
0060 {
0061     if (np.get<bool>(QStringLiteral("stop"), false)) {
0062         qCDebug(KDECONNECT_PLUGIN_SFTP) << "SFTP server stopped";
0063         unmount(false);
0064         return;
0065     }
0066 
0067     if (np.has(QStringLiteral("errorMessage"))) {
0068         Q_EMIT failed(np.get<QString>(QStringLiteral("errorMessage")));
0069         return;
0070     }
0071 
0072     // This is the previous code, to access sftp server using KIO. Now we are
0073     // using the external binary sshfs, and accessing it as a local filesystem.
0074     /*
0075      *    QUrl url;
0076      *    url.setScheme("sftp");
0077      *    url.setHost(np.get<QString>("ip"));
0078      *    url.setPort(np.get<QString>("port").toInt());
0079      *    url.setUserName(np.get<QString>("user"));
0080      *    url.setPassword(np.get<QString>("password"));
0081      *    url.setPath(np.get<QString>("path"));
0082      *    new KRun(url, 0);
0083      *    Q_EMIT mounted();
0084      */
0085 
0086     unmount(false);
0087 
0088     m_proc = new KProcess();
0089     m_proc->setOutputChannelMode(KProcess::MergedChannels);
0090 
0091     connect(m_proc, &QProcess::started, this, &Mounter::onStarted);
0092     connect(m_proc, &QProcess::errorOccurred, this, &Mounter::onError);
0093     connect(m_proc, &QProcess::finished, this, &Mounter::onFinished);
0094 
0095     QDir().mkpath(m_mountPoint);
0096 
0097     const QString program = QStringLiteral("sshfs");
0098 
0099     QString path;
0100     if (np.has(QStringLiteral("multiPaths")))
0101         path = QStringLiteral("/");
0102     else
0103         path = np.get<QString>(QStringLiteral("path"));
0104 
0105     QHostAddress addr = m_sftp->device()->getLocalIpAddress();
0106     if (addr == QHostAddress::Null) {
0107         qCDebug(KDECONNECT_PLUGIN_SFTP) << "Device doesn't have a LanDeviceLink, unable to get IP address";
0108         return;
0109     }
0110     QString ip = addr.toString();
0111     if (addr.protocol() == QAbstractSocket::IPv6Protocol) {
0112         ip.prepend(QLatin1Char('['));
0113         ip.append(QLatin1Char(']'));
0114     }
0115 
0116     // clang-format off
0117     const QStringList arguments =
0118         QStringList() << QStringLiteral("%1@%2:%3").arg(np.get<QString>(QStringLiteral("user")), ip, path)
0119                       << m_mountPoint << QStringLiteral("-p") << np.get<QString>(QStringLiteral("port"))
0120                       << QStringLiteral("-s") // This fixes a bug where file chunks are sent out of order and get corrupted on reception
0121                       << QStringLiteral("-f") << QStringLiteral("-F") << QStringLiteral("/dev/null") // Do not use ~/.ssh/config
0122                       << QStringLiteral("-o") << QStringLiteral("IdentityFile=") + KdeConnectConfig::instance().privateKeyPath()
0123                       << QStringLiteral("-o") << QStringLiteral("StrictHostKeyChecking=no") // Do not ask for confirmation because it is not a known host
0124                       << QStringLiteral("-o") << QStringLiteral("UserKnownHostsFile=/dev/null") // Prevent storing as a known host
0125                       << QStringLiteral("-o") << QStringLiteral("HostKeyAlgorithms=+ssh-dss\\,ssh-rsa") // https://bugs.kde.org/show_bug.cgi?id=351725
0126                       << QStringLiteral("-o") << QStringLiteral("PubkeyAcceptedKeyTypes=+ssh-rsa") // https://bugs.kde.org/show_bug.cgi?id=443155
0127                       << QStringLiteral("-o") << QStringLiteral("uid=") + QString::number(getuid())
0128                       << QStringLiteral("-o") << QStringLiteral("gid=") + QString::number(getgid())
0129                       << QStringLiteral("-o") << QStringLiteral("reconnect")
0130                       << QStringLiteral("-o") << QStringLiteral("ServerAliveInterval=30")
0131                       << QStringLiteral("-o") << QStringLiteral("password_stdin");
0132     // clang-format on
0133 
0134     m_proc->setProgram(program, arguments);
0135 
0136     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Starting process: " << m_proc->program().join(QStringLiteral(" "));
0137     m_proc->start();
0138 
0139     // qCDebug(KDECONNECT_PLUGIN_SFTP) << "Passing password: " << np.get<QString>("password").toLatin1();
0140     m_proc->write(np.get<QString>(QStringLiteral("password")).toLatin1());
0141     m_proc->write("\n");
0142 }
0143 
0144 void Mounter::onStarted()
0145 {
0146     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process started";
0147     m_started = true;
0148     Q_EMIT mounted();
0149 
0150     // m_proc->setStandardOutputFile("/tmp/kdeconnect-sftp.out");
0151     // m_proc->setStandardErrorFile("/tmp/kdeconnect-sftp.err");
0152 
0153     auto proc = m_proc;
0154     connect(m_proc, &KProcess::readyReadStandardError, this, [proc]() {
0155         qCDebug(KDECONNECT_PLUGIN_SFTP) << "stderr: " << proc->readAll();
0156     });
0157     connect(m_proc, &KProcess::readyReadStandardOutput, this, [proc]() {
0158         qCDebug(KDECONNECT_PLUGIN_SFTP) << "stdout:" << proc->readAll();
0159     });
0160 }
0161 
0162 void Mounter::onError(QProcess::ProcessError error)
0163 {
0164     if (error == QProcess::FailedToStart) {
0165         qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process failed to start";
0166         m_started = false;
0167         Q_EMIT failed(i18n("Failed to start sshfs"));
0168     } else if (error == QProcess::ProcessError::Crashed) {
0169         qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process crashed";
0170         m_started = false;
0171         Q_EMIT failed(i18n("sshfs process crashed"));
0172     } else {
0173         qCDebug(KDECONNECT_PLUGIN_SFTP) << "sshfs process error" << error;
0174         m_started = false;
0175         Q_EMIT failed(i18n("Unknown error in sshfs"));
0176     }
0177 }
0178 
0179 void Mounter::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
0180 {
0181     if (exitStatus == QProcess::NormalExit && exitCode == 0) {
0182         qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process finished (exit code: " << exitCode << ")";
0183         Q_EMIT unmounted();
0184     } else {
0185         qCDebug(KDECONNECT_PLUGIN_SFTP) << "Process failed (exit code:" << exitCode << ")";
0186         Q_EMIT failed(i18n("Error when accessing filesystem. sshfs finished with exit code %0").arg(exitCode));
0187     }
0188 
0189     unmount(true);
0190 }
0191 
0192 void Mounter::onMountTimeout()
0193 {
0194     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Timeout: device not responding";
0195     Q_EMIT failed(i18n("Failed to mount filesystem: device not responding"));
0196 }
0197 
0198 void Mounter::start()
0199 {
0200     NetworkPacket np(PACKET_TYPE_SFTP_REQUEST, {{QStringLiteral("startBrowsing"), true}});
0201     m_sftp->sendPacket(np);
0202 
0203     m_connectTimer.start();
0204 }
0205 
0206 void Mounter::unmount(bool finished)
0207 {
0208     qCDebug(KDECONNECT_PLUGIN_SFTP) << "Unmount" << m_proc;
0209     if (m_proc) {
0210         if (!finished) {
0211             // Process is still running, we want to stop it
0212             // But when the finished signal come, we might have already gone.
0213             // Disconnect everything.
0214             m_proc->disconnect();
0215             m_proc->kill();
0216 
0217             auto proc = m_proc;
0218             m_proc = nullptr;
0219             connect(proc, &QProcess::finished, [proc]() {
0220                 qCDebug(KDECONNECT_PLUGIN_SFTP) << "Free" << proc;
0221                 proc->deleteLater();
0222             });
0223             Q_EMIT unmounted();
0224         } else
0225             m_proc->deleteLater();
0226 
0227             // Free mount point (won't always succeed if the path is in use)
0228 #if defined(HAVE_FUSERMOUNT)
0229         KProcess::execute(QStringList{QStringLiteral("fusermount"), QStringLiteral("-u"), m_mountPoint}, 10000);
0230 #else
0231         KProcess::execute(QStringList{QStringLiteral("umount"), m_mountPoint}, 10000);
0232 #endif
0233         m_proc = nullptr;
0234     }
0235     m_started = false;
0236 }
0237 
0238 #include "moc_mounter.cpp"