File indexing completed on 2024-04-28 04:58:01

0001 /*
0002     SPDX-License-Identifier: LGPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
0004     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005 */
0006 
0007 #include "kio_smb.h"
0008 
0009 #include <KLocalizedString>
0010 #include <KProcess>
0011 #include <KShell>
0012 
0013 #include <QByteArray>
0014 #include <QDataStream>
0015 #include <QDir>
0016 #include <unistd.h>
0017 
0018 WorkerResult SMBWorker::getACE(QDataStream &stream)
0019 {
0020     QUrl qurl;
0021     stream >> qurl;
0022     const SMBUrl url(qurl);
0023 
0024     // Use the same buffer for all properties to reduce the likelihood of having needless allocations. ACL will usually
0025     // be the largest of the lot and if we try to get that first we'll probably fit all the other properties into the
0026     // same buffer easy peasy.
0027     constexpr auto defaultArraySize = 4096; // arbitrary, the ACL is of unknown size
0028     const int pageSize = static_cast<int>(sysconf(_SC_PAGESIZE));
0029     Q_ASSERT(pageSize == sysconf(_SC_PAGESIZE)); // ensure conversion accuracy
0030     QVarLengthArray<char, defaultArraySize> value(pageSize);
0031 
0032     for (auto xattr : {
0033              "system.nt_sec_desc.acl.*+",
0034              "system.nt_sec_desc.owner+",
0035              "system.nt_sec_desc.group+",
0036          }) {
0037         while (true) {
0038             const auto result = smbc_getxattr(url.toSmbcUrl(), xattr, value.data(), value.size());
0039             if (const auto error = errno; result < 0) {
0040                 // https://bugzilla.samba.org/show_bug.cgi?id=15088
0041                 if (error == ERANGE) {
0042                     value.resize(value.size() + pageSize);
0043                     continue;
0044                 }
0045                 qWarning() << xattr << strerror(error);
0046                 return WorkerResult::fail(ERR_INTERNAL, strerror(error));
0047             }
0048             qCDebug(KIO_SMB_LOG) << "XATTR" << xattr << value.data();
0049             if (QLatin1String("system.nt_sec_desc.acl.*+") == QLatin1String(xattr)) {
0050                 setMetaData("ACL", QString::fromUtf8(value.constData()));
0051             }
0052             if (QLatin1String("system.nt_sec_desc.owner+") == QLatin1String(xattr)) {
0053                 setMetaData("OWNER", QString::fromUtf8(value.constData()));
0054             }
0055             if (QLatin1String("system.nt_sec_desc.group+") == QLatin1String(xattr)) {
0056                 setMetaData("GROUP", QString::fromUtf8(value.constData()));
0057             }
0058             break;
0059         }
0060     }
0061     return WorkerResult::pass();
0062 }
0063 
0064 KIO::WorkerResult SMBWorker::setACE(QDataStream &stream)
0065 {
0066     QUrl qurl;
0067     stream >> qurl;
0068     const SMBUrl url(qurl);
0069 
0070     QString sid;
0071     QString aceString;
0072     stream >> sid >> aceString;
0073 
0074     const QString attr = QLatin1String("system.nt_sec_desc.acl+:") + sid;
0075     qCDebug(KIO_SMB_LOG) << attr << aceString;
0076 
0077     // https://bugzilla.samba.org/show_bug.cgi?id=15089
0078     auto flags = SMBC_XATTR_FLAG_REPLACE | SMBC_XATTR_FLAG_CREATE;
0079     const QByteArray ace = aceString.toUtf8();
0080     int result = smbc_setxattr(url.toSmbcUrl(), qUtf8Printable(attr), ace.constData(), ace.size(), flags);
0081     if (const auto error = errno; result < 0) {
0082         qCDebug(KIO_SMB_LOG) << "smbc_setxattr" << result << strerror(error);
0083         return WorkerResult::fail(ERR_INTERNAL, strerror(error));
0084     }
0085     return WorkerResult::pass();
0086 }
0087 
0088 // TODO: rename this file _special instead of _mount. Or better yet: stop having multiple cpp files for the same class.
0089 KIO::WorkerResult SMBWorker::special(const QByteArray &data)
0090 {
0091     qCDebug(KIO_SMB_LOG) << "Smb::special()";
0092     int tmp;
0093     QDataStream stream(data);
0094     stream >> tmp;
0095     // mounting and umounting are both blocking, "guarded" by a SIGALARM in the future
0096     switch (tmp) {
0097     case 1:
0098     case 3: {
0099         QString remotePath;
0100         QString mountPoint;
0101         QString user;
0102         stream >> remotePath >> mountPoint;
0103 
0104         QStringList sl = remotePath.split('/');
0105         QString share;
0106         QString host;
0107         if (sl.count() >= 2) {
0108             host = sl.at(0).mid(2);
0109             share = sl.at(1);
0110             qCDebug(KIO_SMB_LOG) << "special() host -" << host << "- share -" << share << "-";
0111         }
0112 
0113         remotePath.replace('\\', '/'); // smbmounterplugin sends \\host/share
0114 
0115         qCDebug(KIO_SMB_LOG) << "mounting: " << remotePath.toLocal8Bit() << " to " << mountPoint.toLocal8Bit();
0116 
0117         if (tmp == 3) {
0118             if (!QDir().mkpath(mountPoint)) {
0119                 return WorkerResult::fail(KIO::ERR_CANNOT_MKDIR, mountPoint);
0120             }
0121         }
0122 
0123         SMBUrl smburl(QUrl("smb:///"));
0124         smburl.setHost(host);
0125         smburl.setPath('/' + share);
0126 
0127         const int passwordError = checkPassword(smburl);
0128         if (passwordError != KJob::NoError && passwordError != KIO::ERR_USER_CANCELED) {
0129             return WorkerResult::fail(passwordError, smburl.toString());
0130         }
0131 
0132         // using smbmount instead of "mount -t smbfs", because mount does not allow a non-root
0133         // user to do a mount, but a suid smbmnt does allow this
0134 
0135         KProcess proc;
0136         proc.setOutputChannelMode(KProcess::SeparateChannels);
0137         proc << "smbmount";
0138 
0139         QString options;
0140 
0141         if (smburl.userName().isEmpty()) {
0142             user = "guest";
0143             options = "guest";
0144         } else {
0145             options = "username=" + smburl.userName();
0146             user = smburl.userName();
0147 
0148             if (!smburl.password().isEmpty())
0149                 options += ",password=" + smburl.password();
0150         }
0151 
0152         // TODO: check why the control center uses encodings with a blank char, e.g. "cp 1250"
0153         // if ( ! m_default_encoding.isEmpty() )
0154         // options += ",codepage=" + KShell::quoteArg(m_default_encoding);
0155 
0156         proc << remotePath;
0157         proc << mountPoint;
0158         proc << "-o" << options;
0159 
0160         proc.start();
0161         if (!proc.waitForFinished()) {
0162             return WorkerResult::fail(KIO::ERR_CANNOT_LAUNCH_PROCESS,
0163                                       "smbmount" + i18n("\nMake sure that the samba package is installed properly on your system."));
0164         }
0165 
0166         QString mybuf = QString::fromLocal8Bit(proc.readAllStandardOutput());
0167         QString mystderr = QString::fromLocal8Bit(proc.readAllStandardError());
0168 
0169         qCDebug(KIO_SMB_LOG) << "mount exit " << proc.exitCode() << "stdout:" << mybuf << "\nstderr:" << mystderr;
0170 
0171         if (proc.exitCode() != 0) {
0172             return WorkerResult::fail(KIO::ERR_CANNOT_MOUNT,
0173                                       i18n("Mounting of share \"%1\" from host \"%2\" by user \"%3\" failed.\n%4", share, host, user, mybuf + '\n' + mystderr));
0174         }
0175     } break;
0176     case 2:
0177     case 4: {
0178         QString mountPoint;
0179         stream >> mountPoint;
0180 
0181         KProcess proc;
0182         proc.setOutputChannelMode(KProcess::SeparateChannels);
0183         proc << "smbumount";
0184         proc << mountPoint;
0185 
0186         proc.start();
0187         if (!proc.waitForFinished()) {
0188             return WorkerResult::fail(KIO::ERR_CANNOT_LAUNCH_PROCESS,
0189                                       "smbumount" + i18n("\nMake sure that the samba package is installed properly on your system."));
0190         }
0191 
0192         QString mybuf = QString::fromLocal8Bit(proc.readAllStandardOutput());
0193         QString mystderr = QString::fromLocal8Bit(proc.readAllStandardError());
0194 
0195         qCDebug(KIO_SMB_LOG) << "smbumount exit " << proc.exitCode() << "stdout:" << mybuf << "\nstderr:" << mystderr;
0196 
0197         if (proc.exitCode() != 0) {
0198             return WorkerResult::fail(KIO::ERR_CANNOT_UNMOUNT, i18n("Unmounting of mountpoint \"%1\" failed.\n%2", mountPoint, mybuf + '\n' + mystderr));
0199         }
0200 
0201         if (tmp == 4) {
0202             bool ok;
0203 
0204             QDir dir(mountPoint);
0205             dir.cdUp();
0206             ok = dir.rmdir(mountPoint);
0207             if (ok) {
0208                 QString p = dir.path();
0209                 dir.cdUp();
0210                 ok = dir.rmdir(p);
0211             }
0212 
0213             if (!ok) {
0214                 return WorkerResult::fail(KIO::ERR_CANNOT_RMDIR, mountPoint);
0215             }
0216         }
0217     } break;
0218     case 0xAC: { // ACL
0219         return getACE(stream);
0220     }
0221     case 0xACD: { // setACE
0222         return setACE(stream);
0223     }
0224     default:
0225         break;
0226     }
0227     return WorkerResult::pass();
0228 }