File indexing completed on 2024-04-21 04:57:37

0001 /*  This file is part of the KDE project
0002 
0003     SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
0004     SPDX-FileCopyrightText: 2014 Mathias Tillman <master.homer@gmail.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kio_nfs.h"
0010 
0011 #include <config-runtime.h>
0012 
0013 #include <sys/stat.h>
0014 #include <sys/types.h>
0015 #include <sys/utsname.h>
0016 
0017 #include <arpa/inet.h>
0018 #include <netdb.h>
0019 
0020 #include <QCoreApplication>
0021 #include <QDebug>
0022 #include <QDir>
0023 #include <QFile>
0024 #include <QHostInfo>
0025 #include <QUrl>
0026 
0027 #include <KLocalizedString>
0028 #include <KSharedConfig>
0029 
0030 #include "kio_nfs_debug.h"
0031 #include "nfsv2.h"
0032 #include "nfsv3.h"
0033 
0034 using namespace KIO;
0035 using namespace std;
0036 
0037 // Pseudo plugin class to embed meta data
0038 class KIOPluginForMetaData : public QObject
0039 {
0040     Q_OBJECT
0041     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.nfs" FILE "nfs.json")
0042 };
0043 
0044 extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv);
0045 
0046 int kdemain(int argc, char **argv)
0047 {
0048     QCoreApplication app(argc, argv);
0049     app.setApplicationName(QLatin1String("kio_nfs"));
0050 
0051     if (argc != 4) {
0052         fprintf(stderr, "Usage: kio_nfs protocol domain-socket1 domain-socket2\n");
0053         exit(-1);
0054     }
0055 
0056     NFSSlave slave(argv[2], argv[3]);
0057     slave.dispatchLoop();
0058 
0059     return 0;
0060 }
0061 
0062 // Both the insertion and lookup in the file handle cache (managed by
0063 // NFSProtocol), and the use of QFileInfo to locate a parent directory,
0064 // are sensitive to paths having trailing slashes.  In order to keep
0065 // everything consistent, any URLs passed in must be cleaned before using
0066 // them as a fileystem or NFS protocol path.
0067 
0068 static QUrl cleanPath(const QUrl &url)
0069 {
0070     return (url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments));
0071 }
0072 
0073 NFSSlave::NFSSlave(const QByteArray &pool, const QByteArray &app)
0074     : KIO::SlaveBase("nfs", pool, app)
0075     , m_protocol(nullptr)
0076     , m_usedirplus3(true)
0077     , m_errorId(KIO::Error(0))
0078 {
0079     qCDebug(LOG_KIO_NFS) << pool << app;
0080 }
0081 
0082 NFSSlave::~NFSSlave()
0083 {
0084     delete m_protocol;
0085 }
0086 
0087 void NFSSlave::openConnection()
0088 {
0089     qCDebug(LOG_KIO_NFS);
0090 
0091     if (m_protocol != nullptr) {
0092         m_protocol->openConnection();
0093         return;
0094     }
0095 
0096     const KSharedConfig::Ptr cfg = KSharedConfig::openConfig("kionfsrc");
0097 
0098     const KConfigGroup grp1 = cfg->group("Default"); // default for all hosts
0099     int minproto = grp1.readEntry("minproto", 2); // minimum NFS version to accept
0100     int maxproto = grp1.readEntry("maxproto", 4); // maximum NFS version to try
0101     m_usedirplus3 = grp1.readEntry("usedirplus3", true); // use READDIRPLUS3 for listing
0102 
0103     const KConfigGroup grp2 = cfg->group("Host " + m_host);
0104     if (grp2.exists()) // look for host-specific settings
0105     { // with default values from above
0106         minproto = grp2.readEntry("minproto", minproto);
0107         maxproto = grp2.readEntry("maxproto", maxproto);
0108         m_usedirplus3 = grp2.readEntry("usedirplus3", m_usedirplus3);
0109     }
0110 
0111     minproto = qBound(2, minproto, 4); // enforce limits
0112     maxproto = qBound(minproto, maxproto, 4);
0113     qCDebug(LOG_KIO_NFS) << "configuration for" << m_host;
0114     qCDebug(LOG_KIO_NFS) << "minproto" << minproto << "maxproto" << maxproto << "usedirplus3" << m_usedirplus3;
0115 
0116     bool connectionError = false;
0117 
0118     int version = maxproto;
0119     while (version >= minproto) {
0120         qCDebug(LOG_KIO_NFS) << "Trying NFS version" << version;
0121 
0122         // Try to create an NFS protocol handler for that version
0123         switch (version) {
0124         case 4: // TODO
0125             qCDebug(LOG_KIO_NFS) << "NFSv4 is not supported at this time";
0126             break;
0127 
0128         case 3:
0129             m_protocol = new NFSProtocolV3(this);
0130             break;
0131 
0132         case 2:
0133             m_protocol = new NFSProtocolV2(this);
0134             break;
0135         }
0136 
0137         if (m_protocol != nullptr) // created protocol for that version
0138         {
0139             m_protocol->setHost(m_host, m_user); // try to make initial connection
0140             if (m_protocol->isCompatible(connectionError))
0141                 break;
0142         }
0143 
0144         delete m_protocol; // no point using that protocol
0145         --version; // try the next lower
0146         m_protocol = nullptr; // try again with new protocol
0147     }
0148 
0149     if (m_protocol == nullptr) // failed to find a protocol
0150     {
0151         if (!connectionError) // but connection was possible
0152         {
0153             setError(KIO::ERR_WORKER_DEFINED, i18n("Cannot find an NFS version that host '%1' supports", m_host));
0154         } else // connection failed
0155         {
0156             setError(KIO::ERR_CANNOT_CONNECT, m_host);
0157         }
0158     } else // usable protocol was created
0159     {
0160         m_protocol->openConnection(); // open the connection
0161     }
0162 }
0163 
0164 void NFSSlave::closeConnection()
0165 {
0166     qCDebug(LOG_KIO_NFS);
0167 
0168     if (m_protocol != nullptr) {
0169         m_protocol->closeConnection();
0170     }
0171 }
0172 
0173 void NFSSlave::setHost(const QString &host, quint16 /*port*/, const QString &user, const QString & /*pass*/)
0174 {
0175     qCDebug(LOG_KIO_NFS) << "host" << host << "user" << user;
0176 
0177     if (m_protocol != nullptr) {
0178         // New host or user? New protocol!
0179         if (host != m_host || user != m_user) {
0180             qCDebug(LOG_KIO_NFS) << "Deleting old protocol";
0181             delete m_protocol;
0182             m_protocol = nullptr;
0183         } else {
0184             // TODO: Doing this is pointless if nothing has changed
0185             m_protocol->setHost(host, user);
0186         }
0187     }
0188 
0189     m_host = host;
0190     m_user = user;
0191 }
0192 
0193 void NFSSlave::put(const QUrl &url, int _mode, KIO::JobFlags _flags)
0194 {
0195     qCDebug(LOG_KIO_NFS);
0196 
0197     if (verifyProtocol(url)) {
0198         m_protocol->put(cleanPath(url), _mode, _flags);
0199     }
0200     finishOperation();
0201 }
0202 
0203 void NFSSlave::get(const QUrl &url)
0204 {
0205     qCDebug(LOG_KIO_NFS);
0206 
0207     if (verifyProtocol(url)) {
0208         m_protocol->get(cleanPath(url));
0209     }
0210     finishOperation();
0211 }
0212 
0213 void NFSSlave::listDir(const QUrl &url)
0214 {
0215     qCDebug(LOG_KIO_NFS) << url;
0216 
0217     if (verifyProtocol(url)) {
0218         m_protocol->listDir(cleanPath(url));
0219     }
0220     finishOperation();
0221 }
0222 
0223 void NFSSlave::symlink(const QString &target, const QUrl &dest, KIO::JobFlags _flags)
0224 {
0225     qCDebug(LOG_KIO_NFS);
0226 
0227     if (verifyProtocol(dest)) {
0228         m_protocol->symlink(target, cleanPath(dest), _flags);
0229     }
0230     finishOperation();
0231 }
0232 
0233 void NFSSlave::stat(const QUrl &url)
0234 {
0235     qCDebug(LOG_KIO_NFS);
0236 
0237     if (verifyProtocol(url)) {
0238         m_protocol->stat(cleanPath(url));
0239     }
0240     finishOperation();
0241 }
0242 
0243 void NFSSlave::mkdir(const QUrl &url, int permissions)
0244 {
0245     qCDebug(LOG_KIO_NFS);
0246 
0247     if (verifyProtocol(url)) {
0248         m_protocol->mkdir(cleanPath(url), permissions);
0249     }
0250     finishOperation();
0251 }
0252 
0253 void NFSSlave::del(const QUrl &url, bool isfile)
0254 {
0255     qCDebug(LOG_KIO_NFS);
0256 
0257     if (verifyProtocol(url)) {
0258         m_protocol->del(cleanPath(url), isfile);
0259     }
0260     finishOperation();
0261 }
0262 
0263 void NFSSlave::chmod(const QUrl &url, int permissions)
0264 {
0265     qCDebug(LOG_KIO_NFS);
0266 
0267     if (verifyProtocol(url)) {
0268         m_protocol->chmod(cleanPath(url), permissions);
0269     }
0270     finishOperation();
0271 }
0272 
0273 void NFSSlave::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags)
0274 {
0275     qCDebug(LOG_KIO_NFS);
0276 
0277     if (verifyProtocol(src) && verifyProtocol(dest)) {
0278         m_protocol->rename(cleanPath(src), cleanPath(dest), flags);
0279     }
0280     finishOperation();
0281 }
0282 
0283 void NFSSlave::copy(const QUrl &src, const QUrl &dest, int mode, KIO::JobFlags flags)
0284 {
0285     qCDebug(LOG_KIO_NFS);
0286 
0287     if (verifyProtocol(src) && verifyProtocol(dest)) {
0288         m_protocol->copy(cleanPath(src), cleanPath(dest), mode, flags);
0289     }
0290     finishOperation();
0291 }
0292 
0293 // Perform initial URL and host checks before starting any operation.
0294 // This means any KIO::SlaveBase action which is expected to end by
0295 // calling either error() or finished().
0296 bool NFSSlave::verifyProtocol(const QUrl &url)
0297 {
0298     m_errorId = KIO::Error(0); // ensure reset before starting
0299     m_errorText.clear();
0300 
0301     // The NFS protocol definition includes copyToFile=true and copyFromFile=true,
0302     // so the URL scheme here can also be "file".  No URL or protocol checking
0303     // is required in this case.
0304     if (url.scheme() != "nfs")
0305         return true;
0306 
0307     if (!url.isValid()) // also checks for empty
0308     {
0309         setError(KIO::ERR_MALFORMED_URL, url.toDisplayString());
0310         return (false);
0311     }
0312 
0313     // A NFS URL must include a host name, if it does not then nothing
0314     // sensible can be done.  Doing the check here and returning immediately
0315     // avoids multiple calls of SlaveBase::error() as each protocol is tried
0316     // in NFSSlave::openConnection().
0317 
0318     const QString host = url.host();
0319     if (host.isEmpty()) {
0320         // KIO::ERR_UNKNOWN_HOST with a blank host name results in the
0321         // error message "No hostname specified", but Konqueror does not
0322         // report it properly.  Return our own message.
0323         setError(KIO::ERR_WORKER_DEFINED, i18n("The NFS protocol requires a server host name."));
0324         return (false);
0325     } else {
0326         // There is a host name, so check that it can be resolved.  If it
0327         // can't, return an error now and don't bother trying the protocol.
0328         QHostInfo hostInfo = QHostInfo::fromName(host);
0329         if (hostInfo.error() != QHostInfo::NoError) {
0330             qCDebug(LOG_KIO_NFS) << "host lookup of" << host << "error" << hostInfo.errorString();
0331             setError(KIO::ERR_UNKNOWN_HOST, host);
0332             return (false);
0333         }
0334     }
0335 
0336     if (m_protocol == nullptr) // no protocol connection yet
0337     {
0338         openConnection(); // create and open connection
0339         if (m_protocol == nullptr) // if that failed, then
0340         { // no more can be done
0341             qCDebug(LOG_KIO_NFS) << "Could not resolve a compatible protocol version!";
0342             goto fail;
0343         }
0344     } else if (!m_protocol->isConnected()) // already have a protocol
0345     {
0346         m_protocol->openConnection(); // open its connection
0347     }
0348 
0349     if (m_protocol->isConnected())
0350         return true; // connection succeeded
0351 
0352 fail: // default error if none already
0353     setError(KIO::ERR_INTERNAL, i18n("Failed to initialise protocol"));
0354     return false;
0355 }
0356 
0357 // These two functions keep track of errors found during any operation,
0358 // and return the error or finish the operation appropriately when
0359 // the operation is complete.
0360 //
0361 // NFSProtocol and classes derived from it, and anything that they call,
0362 // should call setError() instead of SlaveBase::error().  When the
0363 // operation is complete, just return and do not call SlaveBase::finished().
0364 
0365 // Record the error information, but do not call SlaveBase::error().
0366 // If there has been an error, finishOperation() will report it when
0367 // the protocol operation is complete.
0368 
0369 void NFSSlave::setError(KIO::Error errid, const QString &text)
0370 {
0371     if (m_errorId != 0) {
0372         qCDebug(LOG_KIO_NFS) << errid << "ignored due to previous error";
0373         return;
0374     }
0375 
0376     qCDebug(LOG_KIO_NFS) << errid << text;
0377     m_errorId = errid;
0378     m_errorText = text;
0379 }
0380 
0381 // An operation is complete.  If there has been an error, then report it.
0382 void NFSSlave::finishOperation()
0383 {
0384     if (m_errorId == 0) { // no error encountered
0385         SlaveBase::finished();
0386     } else { // there was an error
0387         SlaveBase::error(m_errorId, m_errorText);
0388     }
0389 }
0390 
0391 NFSFileHandle::NFSFileHandle()
0392     : m_handle(nullptr)
0393     , m_size(0)
0394     , m_linkHandle(nullptr)
0395     , m_linkSize(0)
0396     , m_isLink(false)
0397 {
0398 }
0399 
0400 NFSFileHandle::NFSFileHandle(const NFSFileHandle &src)
0401     : NFSFileHandle()
0402 {
0403     (*this) = src;
0404 }
0405 
0406 NFSFileHandle::NFSFileHandle(const fhandle3 &src)
0407     : NFSFileHandle()
0408 {
0409     (*this) = src;
0410 }
0411 
0412 NFSFileHandle::NFSFileHandle(const fhandle &src)
0413     : NFSFileHandle()
0414 {
0415     (*this) = src;
0416 }
0417 
0418 NFSFileHandle::NFSFileHandle(const nfs_fh3 &src)
0419     : NFSFileHandle()
0420 {
0421     (*this) = src;
0422 }
0423 
0424 NFSFileHandle::NFSFileHandle(const nfs_fh &src)
0425     : NFSFileHandle()
0426 {
0427     (*this) = src;
0428 }
0429 
0430 NFSFileHandle::~NFSFileHandle()
0431 {
0432     if (m_handle != nullptr) {
0433         delete[] m_handle;
0434     }
0435     if (m_linkHandle != nullptr) {
0436         delete[] m_linkHandle;
0437     }
0438 }
0439 
0440 void NFSFileHandle::toFH(nfs_fh3 &fh) const
0441 {
0442     fh.data.data_len = m_size;
0443     fh.data.data_val = m_handle;
0444 }
0445 
0446 void NFSFileHandle::toFH(nfs_fh &fh) const
0447 {
0448     memcpy(fh.data, m_handle, m_size);
0449 }
0450 
0451 void NFSFileHandle::toFHLink(nfs_fh3 &fh) const
0452 {
0453     fh.data.data_len = m_linkSize;
0454     fh.data.data_val = m_linkHandle;
0455 }
0456 
0457 void NFSFileHandle::toFHLink(nfs_fh &fh) const
0458 {
0459     memcpy(fh.data, m_linkHandle, m_size);
0460 }
0461 
0462 NFSFileHandle &NFSFileHandle::operator=(const NFSFileHandle &src)
0463 {
0464     if (src.m_size > 0) {
0465         if (m_handle != nullptr) {
0466             delete[] m_handle;
0467             m_handle = nullptr;
0468         }
0469         m_size = src.m_size;
0470         m_handle = new char[m_size];
0471         memcpy(m_handle, src.m_handle, m_size);
0472     }
0473     if (src.m_linkSize > 0) {
0474         if (m_linkHandle != nullptr) {
0475             delete[] m_linkHandle;
0476             m_linkHandle = nullptr;
0477         }
0478 
0479         m_linkSize = src.m_linkSize;
0480         m_linkHandle = new char[m_linkSize];
0481         memcpy(m_linkHandle, src.m_linkHandle, m_linkSize);
0482     }
0483 
0484     m_isLink = src.m_isLink;
0485     return *this;
0486 }
0487 
0488 NFSFileHandle &NFSFileHandle::operator=(const fhandle3 &src)
0489 {
0490     if (m_handle != nullptr) {
0491         delete[] m_handle;
0492         m_handle = nullptr;
0493     }
0494 
0495     m_size = src.fhandle3_len;
0496     m_handle = new char[m_size];
0497     memcpy(m_handle, src.fhandle3_val, m_size);
0498     return *this;
0499 }
0500 
0501 NFSFileHandle &NFSFileHandle::operator=(const fhandle &src)
0502 {
0503     if (m_handle != nullptr) {
0504         delete[] m_handle;
0505         m_handle = nullptr;
0506     }
0507 
0508     m_size = NFS_FHSIZE;
0509     m_handle = new char[m_size];
0510     memcpy(m_handle, src, m_size);
0511     return *this;
0512 }
0513 
0514 NFSFileHandle &NFSFileHandle::operator=(const nfs_fh3 &src)
0515 {
0516     if (m_handle != nullptr) {
0517         delete[] m_handle;
0518         m_handle = nullptr;
0519     }
0520 
0521     m_size = src.data.data_len;
0522     m_handle = new char[m_size];
0523     memcpy(m_handle, src.data.data_val, m_size);
0524     return *this;
0525 }
0526 
0527 NFSFileHandle &NFSFileHandle::operator=(const nfs_fh &src)
0528 {
0529     if (m_handle != nullptr) {
0530         delete[] m_handle;
0531         m_handle = nullptr;
0532     }
0533 
0534     m_size = NFS_FHSIZE;
0535     m_handle = new char[m_size];
0536     memcpy(m_handle, src.data, m_size);
0537     return *this;
0538 }
0539 
0540 void NFSFileHandle::setLinkSource(const nfs_fh3 &src)
0541 {
0542     if (m_linkHandle != nullptr) {
0543         delete[] m_linkHandle;
0544         m_linkHandle = nullptr;
0545     }
0546 
0547     m_linkSize = src.data.data_len;
0548     m_linkHandle = new char[m_linkSize];
0549     memcpy(m_linkHandle, src.data.data_val, m_linkSize);
0550     m_isLink = true;
0551 }
0552 
0553 void NFSFileHandle::setLinkSource(const nfs_fh &src)
0554 {
0555     if (m_linkHandle != nullptr) {
0556         delete[] m_linkHandle;
0557         m_linkHandle = nullptr;
0558     }
0559 
0560     m_linkSize = NFS_FHSIZE;
0561     m_linkHandle = new char[m_linkSize];
0562     memcpy(m_linkHandle, src.data, m_linkSize);
0563     m_isLink = true;
0564 }
0565 
0566 NFSProtocol::NFSProtocol(NFSSlave *slave)
0567     : m_slave(slave)
0568 {
0569 }
0570 
0571 void NFSProtocol::copy(const QUrl &src, const QUrl &dest, int mode, KIO::JobFlags flags)
0572 {
0573     if (src.isLocalFile()) {
0574         copyTo(src, dest, mode, flags);
0575     } else if (dest.isLocalFile()) {
0576         copyFrom(src, dest, mode, flags);
0577     } else {
0578         copySame(src, dest, mode, flags);
0579     }
0580 }
0581 
0582 void NFSProtocol::addExportedDir(const QString &path)
0583 {
0584     m_exportedDirs.append(path);
0585 }
0586 
0587 const QStringList &NFSProtocol::getExportedDirs()
0588 {
0589     return m_exportedDirs;
0590 }
0591 
0592 bool NFSProtocol::isExportedDir(const QString &path)
0593 {
0594     // See whether the path is an exported directory:  that is, a prefix
0595     // of but not identical to any of the server exports.  If it is, then
0596     // it can be virtually listed and some operations are forbidden.
0597     // For example, if the server exports "/export/nfs/dir" then the root,
0598     // "/export" and "/export/nfs" are considered to be exported directories,
0599     // but "/export/nfs/dir" is not because it needs a server mount in order
0600     // to be listed.
0601     //
0602     // This function looks similar to, but is not the same as, isValidPath()
0603     // below.  This tests for "is the given path a prefix of any exported
0604     // directory", but isValidPath() tests for "is any exported directory equal
0605     // to or a prefix of the given path".
0606 
0607     // The root is always considered to be exported.
0608     if (path.isEmpty() || path == "/" || QFileInfo(path).isRoot()) {
0609         qCDebug(LOG_KIO_NFS) << path << "is root";
0610         return true;
0611     }
0612 
0613     const QString dirPath = path + QDir::separator();
0614     for (QStringList::const_iterator it = m_exportedDirs.constBegin(); it != m_exportedDirs.constEnd(); ++it) {
0615         const QString &exportedDir = (*it);
0616         // We know that both 'path' and the contents of m_exportedDirs
0617         // have been cleaned of any trailing slashes.
0618         if (exportedDir.startsWith(dirPath)) {
0619             qCDebug(LOG_KIO_NFS) << path << "is exported";
0620             return true;
0621         }
0622     }
0623 
0624     return false;
0625 }
0626 
0627 void NFSProtocol::removeExportedDir(const QString &path)
0628 {
0629     m_exportedDirs.removeOne(path);
0630 }
0631 
0632 void NFSProtocol::addFileHandle(const QString &path, NFSFileHandle fh)
0633 {
0634     if (fh.isInvalid())
0635         qCDebug(LOG_KIO_NFS) << "not adding" << path << "with invalid NFSFileHandle";
0636     else
0637         m_handleCache.insert(path, fh);
0638 }
0639 
0640 NFSFileHandle NFSProtocol::getFileHandle(const QString &path)
0641 {
0642     if (!isConnected()) {
0643         return NFSFileHandle();
0644     }
0645 
0646     if (m_exportedDirs.contains(path)) {
0647         // All exported directories should have already been stored in
0648         // m_handleCache by the protocol's openConnection().  If any
0649         // exported directory could not be mounted, then it will be in
0650         // m_exportedDirs but not in m_handleCache.  There is nothing more
0651         // that can be done in this case.
0652         if (!m_handleCache.contains(path)) {
0653             m_slave->setError(KIO::ERR_CANNOT_MOUNT, path);
0654             return NFSFileHandle();
0655         }
0656     }
0657 
0658     if (!isValidPath(path)) {
0659         qCDebug(LOG_KIO_NFS) << path << "is not a valid path";
0660         m_slave->setError(KIO::ERR_CANNOT_ENTER_DIRECTORY, path);
0661         return NFSFileHandle();
0662     }
0663 
0664     // In theory the root ("/") is a valid path but matches here.
0665     // However, it should never be seen unless the NFS server is
0666     // exporting its entire filesystem (which is very unlikely).
0667     if (path.endsWith('/')) {
0668         qCWarning(LOG_KIO_NFS) << "Passed a path ending with '/'.  Fix the caller.";
0669     }
0670 
0671     // The handle may already be in the cache, check it now.
0672     // The exported dirs are always in the cache, unless there was a
0673     // problem mounting them which will have been checked above.
0674     if (m_handleCache.contains(path)) {
0675         return m_handleCache[path];
0676     }
0677 
0678     // Loop detected, abort.
0679     if (QFileInfo(path).path() == path) {
0680         return NFSFileHandle();
0681     }
0682 
0683     // Look up the file handle from the protocol
0684     NFSFileHandle childFH = lookupFileHandle(path);
0685     if (!childFH.isInvalid()) {
0686         addFileHandle(path, childFH);
0687     }
0688 
0689     return childFH;
0690 }
0691 
0692 void NFSProtocol::removeFileHandle(const QString &path)
0693 {
0694     m_handleCache.remove(path);
0695 }
0696 
0697 bool NFSProtocol::isValidPath(const QString &path)
0698 {
0699     // See whether the path is or below an exported directory:
0700     // that is, any of the server exports is identical to or a prefix
0701     // of the path.  If it does not start with an exported prefix,
0702     // then it is not a valid NFS file path on the server.
0703 
0704     // This function looks similar to, but is not the same as,
0705     // isExportedDir() above.  This tests for "is any exported directory
0706     // equal to or is a prefix of the given path", but isExportedDir()
0707     // tests for "is the given path a prefix of any exported
0708     // directory".
0709 
0710     // The root is always considered to be valid.
0711     if (path.isEmpty() || path == "/" || QFileInfo(path).isRoot()) {
0712         return true;
0713     }
0714 
0715     for (QStringList::const_iterator it = m_exportedDirs.constBegin(); it != m_exportedDirs.constEnd(); ++it) {
0716         const QString &exportedDir = (*it);
0717         // We know that both 'path' and the contents of m_exportedDirs
0718         // have been cleaned of any trailing slashes.
0719         if (path == exportedDir)
0720             return true;
0721         if (path.startsWith(exportedDir + QDir::separator()))
0722             return true;
0723     }
0724 
0725     return false;
0726 }
0727 
0728 bool NFSProtocol::isValidLink(const QString &parentDir, const QString &linkDest)
0729 {
0730     qCDebug(LOG_KIO_NFS) << "checking" << linkDest << "in" << parentDir;
0731 
0732     if (linkDest.isEmpty())
0733         return false; // ensure link is absolute
0734     const QString absDest = QFileInfo(parentDir, linkDest).absoluteFilePath();
0735 
0736     // The link target may not be valid on the NFS server (i.e. it may
0737     // point outside of the exported directories).  Check for this before
0738     // calling getFileHandle() for the target of the link, as otherwise
0739     // the isValidPath() check in getFileHandle() will set the error
0740     // ERR_CANNOT_ENTER_DIRECTORY which will be taken as the result of
0741     // the NFS operation.  This is not an error condition if just checking
0742     // the target of a link, so do the same check here but ignore any error.
0743     if (!isValidPath(absDest)) {
0744         qCDebug(LOG_KIO_NFS) << "target" << absDest << "is invalid";
0745         return false;
0746     }
0747 
0748     // It is now safe to call getFileHandle() on the link target.
0749     return (!getFileHandle(absDest).isInvalid());
0750 }
0751 
0752 KIO::Error NFSProtocol::openConnection(const QString &host, int prog, int vers, CLIENT *&client, int &sock)
0753 {
0754     // NFSSlave::verifyProtocol() should already have checked that
0755     // the host name is not blank and is resolveable, so the two
0756     // KIO::ERR_UNKNOWN_HOST failures here should never happen.
0757 
0758     if (host.isEmpty()) {
0759         return KIO::ERR_UNKNOWN_HOST;
0760     }
0761 
0762     struct sockaddr_in server_addr;
0763     if (host[0] >= '0' && host[0] <= '9') {
0764         server_addr.sin_family = AF_INET;
0765         server_addr.sin_addr.s_addr = inet_addr(host.toLatin1().constData());
0766     } else {
0767         struct hostent *hp = gethostbyname(host.toLatin1().constData());
0768         if (hp == nullptr) {
0769             return KIO::ERR_UNKNOWN_HOST;
0770         }
0771         server_addr.sin_family = AF_INET;
0772         memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
0773     }
0774 
0775     server_addr.sin_port = 0;
0776 
0777     sock = RPC_ANYSOCK;
0778     client = clnttcp_create(&server_addr, prog, vers, &sock, 0, 0);
0779     if (client == nullptr) {
0780         server_addr.sin_port = 0;
0781         sock = RPC_ANYSOCK;
0782 
0783         timeval pertry_timeout;
0784         pertry_timeout.tv_sec = 3;
0785         pertry_timeout.tv_usec = 0;
0786         client = clntudp_create(&server_addr, prog, vers, pertry_timeout, &sock);
0787         if (client == nullptr) {
0788             ::close(sock);
0789             return KIO::ERR_CANNOT_CONNECT;
0790         }
0791     }
0792 
0793     QString hostName = QHostInfo::localHostName();
0794     QString domainName = QHostInfo::localDomainName();
0795     if (!domainName.isEmpty()) {
0796         hostName = hostName + QLatin1Char('.') + domainName;
0797     }
0798 
0799     uid_t uid = geteuid();
0800     if (!m_currentUser.isEmpty()) {
0801         bool ok;
0802         uid_t num = m_currentUser.toUInt(&ok);
0803         if (ok)
0804             uid = num;
0805         else {
0806             const struct passwd *pwd = getpwnam(m_currentUser.toLocal8Bit().constData());
0807             if (pwd != nullptr)
0808                 uid = pwd->pw_uid;
0809         }
0810     }
0811 
0812     client->cl_auth = authunix_create(hostName.toUtf8().data(), uid, getegid(), 0, nullptr);
0813     return KIO::Error(0);
0814 }
0815 
0816 bool NFSProtocol::checkForError(int clientStat, int nfsStat, const QString &text)
0817 {
0818     if (clientStat != RPC_SUCCESS) {
0819         const char *errstr = clnt_sperrno(static_cast<clnt_stat>(clientStat));
0820         qCDebug(LOG_KIO_NFS) << "RPC error" << clientStat << errstr << "on" << text;
0821         m_slave->setError(KIO::ERR_INTERNAL_SERVER, i18n("RPC error %1, %2", QString::number(clientStat), errstr));
0822         return false;
0823     }
0824 
0825     if (nfsStat != NFS_OK) {
0826         qCDebug(LOG_KIO_NFS) << "NFS error" << nfsStat << text;
0827         switch (nfsStat) {
0828         case NFSERR_PERM:
0829             m_slave->setError(KIO::ERR_ACCESS_DENIED, text);
0830             break;
0831         case NFSERR_NOENT:
0832             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
0833             break;
0834         // does this mapping make sense ?
0835         case NFSERR_IO:
0836             m_slave->setError(KIO::ERR_INTERNAL_SERVER, text);
0837             break;
0838         // does this mapping make sense ?
0839         case NFSERR_NXIO:
0840             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
0841             break;
0842         case NFSERR_ACCES:
0843             m_slave->setError(KIO::ERR_ACCESS_DENIED, text);
0844             break;
0845         case NFSERR_EXIST:
0846             m_slave->setError(KIO::ERR_FILE_ALREADY_EXIST, text);
0847             break;
0848         // does this mapping make sense ?
0849         case NFSERR_NODEV:
0850             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
0851             break;
0852         case NFSERR_NOTDIR:
0853             m_slave->setError(KIO::ERR_IS_FILE, text);
0854             break;
0855         case NFSERR_ISDIR:
0856             m_slave->setError(KIO::ERR_IS_DIRECTORY, text);
0857             break;
0858         // does this mapping make sense ?
0859         case NFSERR_FBIG:
0860             m_slave->setError(KIO::ERR_INTERNAL_SERVER, text);
0861             break;
0862         // does this mapping make sense ?
0863         case NFSERR_NOSPC:
0864             m_slave->setError(KIO::ERR_DISK_FULL, text);
0865             break;
0866         case NFSERR_ROFS:
0867             m_slave->setError(KIO::ERR_WRITE_ACCESS_DENIED, text);
0868             break;
0869         case NFSERR_NAMETOOLONG:
0870             m_slave->setError(KIO::ERR_INTERNAL_SERVER, i18n("Filename too long"));
0871             break;
0872         case NFSERR_NOTEMPTY:
0873             m_slave->setError(KIO::ERR_CANNOT_RMDIR, text);
0874             break;
0875         // does this mapping make sense ?
0876         case NFSERR_DQUOT:
0877             m_slave->setError(KIO::ERR_INTERNAL_SERVER, i18n("Disk quota exceeded"));
0878             break;
0879         case NFSERR_STALE:
0880             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
0881             break;
0882         default:
0883             m_slave->setError(KIO::ERR_UNKNOWN, i18n("NFS error %1, %2", QString::number(nfsStat), text));
0884             break;
0885         }
0886         return false;
0887     }
0888     return true;
0889 }
0890 
0891 // Perform checks and, if so indicated, list a virtual (exported)
0892 // directory that will not actually involve accessing the NFS server.
0893 // Return the directory path, or a null string if there is a problem
0894 // or if the URL refers to a virtual directory which has been listed.
0895 
0896 QString NFSProtocol::listDirInternal(const QUrl &url)
0897 {
0898     qCDebug(LOG_KIO_NFS) << url;
0899 
0900     const QString path(url.path());
0901     // Is the path part of an exported (virtual) directory?
0902     if (isExportedDir(path)) {
0903         qCDebug(LOG_KIO_NFS) << "Listing virtual dir" << path;
0904 
0905         QString dirPrefix = path;
0906         if (dirPrefix != "/")
0907             dirPrefix += QDir::separator();
0908 
0909         QStringList virtualList;
0910         const QStringList exportedDirs = getExportedDirs();
0911         for (QStringList::const_iterator it = exportedDirs.constBegin(); it != exportedDirs.constEnd(); ++it) {
0912             // When an export is multiple levels deep (for example "/export/nfs/dir"
0913             // where "/export" is being listed), we only want to display one level
0914             // ("nfs") at a time.  Find all of the exported directories that are
0915             // below the 'dirPrefix', and list the first (or only) path component
0916             // of each.
0917 
0918             QString name = (*it); // this exported directory
0919             if (!name.startsWith(dirPrefix))
0920                 continue; // not below this prefix
0921 
0922             name = name.mid(dirPrefix.length()); // remainder after the prefix
0923 
0924             const int idx = name.indexOf(QDir::separator());
0925             if (idx != -1)
0926                 name = name.left(idx); // take first path component
0927 
0928             if (!virtualList.contains(name)) {
0929                 qCDebug(LOG_KIO_NFS) << "Found exported" << name;
0930                 virtualList.append(name);
0931             }
0932         }
0933 
0934         KIO::UDSEntry entry;
0935         createVirtualDirEntry(entry);
0936         entry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
0937         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "folder-network");
0938         m_slave->listEntry(entry);
0939 
0940         for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it) {
0941             const QString &name = (*it); // this listed directory
0942 
0943             entry.replace(KIO::UDSEntry::UDS_NAME, name);
0944             if (isExportedDir(dirPrefix + name))
0945                 entry.replace(KIO::UDSEntry::UDS_ICON_NAME, "folder-network");
0946             else
0947                 entry.replace(KIO::UDSEntry::UDS_ICON_NAME, "folder");
0948 
0949             m_slave->listEntry(entry);
0950         }
0951 
0952         return (QString()); // listed, no more to do
0953     }
0954 
0955     // For the listing we now actually need to access the NFS server.
0956     // We should always be connected at this point, but better safe than sorry!
0957     if (!isConnected())
0958         return (QString());
0959 
0960     return (path); // the path to list
0961 }
0962 
0963 // Perform checks and, if so indicated, return information for
0964 // a virtual (exported) directory.  Return the entry path, or a
0965 // null string if there is a problem or if the URL refers to a
0966 // virtual directory which has been processed.
0967 
0968 QString NFSProtocol::statInternal(const QUrl &url)
0969 {
0970     qCDebug(LOG_KIO_NFS) << url;
0971 
0972     const QString path(url.path());
0973     if (path.isEmpty()) {
0974         // Displaying a location with an empty path (e.g. "nfs://server")
0975         // seems to confuse Konqueror, it shows the directory but will not
0976         // descend into any subdirectories.  The same location with a root
0977         // path ("nfs://server/") works, so redirect to that.
0978         QUrl redir = url.resolved(QUrl("/"));
0979         qDebug() << "root with empty path, redirecting to" << redir;
0980         slave()->redirection(redir);
0981         return (QString());
0982     }
0983 
0984     // We can't stat an exported directory on the NFS server,
0985     // but we know that it must be a directory.
0986     if (isExportedDir(path)) {
0987         KIO::UDSEntry entry;
0988         entry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
0989         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "folder-network");
0990         createVirtualDirEntry(entry);
0991 
0992         slave()->statEntry(entry);
0993         return (QString());
0994     }
0995 
0996     return (path); // the path to stat
0997 }
0998 
0999 // Set the host to be accessed.  If the host has changed a new
1000 // host connection is required, so close the current one.
1001 //
1002 // Due the way that NFSSlave::setHost() is implemented, if the
1003 // host name changes then the protocol will always be deleted
1004 // and recreated.  So in reality this function does nothing useful.
1005 
1006 void NFSProtocol::setHost(const QString &host, const QString &user)
1007 {
1008     qCDebug(LOG_KIO_NFS) << "host" << host << "user" << user;
1009 
1010     if (host.isEmpty()) // must have a host name
1011     {
1012         m_slave->setError(KIO::ERR_UNKNOWN_HOST, host);
1013         return;
1014     }
1015     // nothing to do if no change
1016     if (host == m_currentHost && user == m_currentUser)
1017         return;
1018     closeConnection(); // close the existing connection
1019     m_currentHost = host; // set the new host name
1020     m_currentUser = user; // set the new user name
1021 }
1022 
1023 // This function and completeInvalidUDSEntry() must use KIO::UDSEntry::replace()
1024 // because they may be called with a UDSEntry that has already been partially
1025 // filled in by NFSProtocol::createVirtualDirEntry().
1026 
1027 void NFSProtocol::completeUDSEntry(KIO::UDSEntry &entry, uid_t uid, gid_t gid)
1028 {
1029     QString str;
1030 
1031     if (!m_usercache.contains(uid)) {
1032         const struct passwd *user = getpwuid(uid);
1033         if (user != nullptr) {
1034             m_usercache.insert(uid, QString::fromLatin1(user->pw_name));
1035             str = user->pw_name;
1036         } else
1037             str = QString::number(uid);
1038     } else
1039         str = m_usercache.value(uid);
1040     entry.replace(KIO::UDSEntry::UDS_USER, str);
1041 
1042     if (!m_groupcache.contains(gid)) {
1043         const struct group *grp = getgrgid(gid);
1044         if (grp != nullptr) {
1045             m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name));
1046             str = grp->gr_name;
1047         } else
1048             str = QString::number(gid);
1049     } else
1050         str = m_groupcache.value(gid);
1051     entry.replace(KIO::UDSEntry::UDS_GROUP, str);
1052 }
1053 
1054 void NFSProtocol::completeInvalidUDSEntry(KIO::UDSEntry &entry)
1055 {
1056     entry.replace(KIO::UDSEntry::UDS_SIZE, 0); // dummy size
1057     entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1);
1058     entry.replace(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
1059     // The UDS_USER and UDS_GROUP must be string values.  It would be possible
1060     // to look up appropriate values as in completeUDSEntry() above, but it seems
1061     // pointless to go to that trouble for an unusable invalid entry.
1062     entry.replace(KIO::UDSEntry::UDS_USER, QString::fromLatin1("root"));
1063     entry.replace(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1("root"));
1064 }
1065 
1066 // This uses KIO::UDSEntry::fastInsert() and so must only be called with
1067 // a blank UDSEntry or one where only UDS_NAME has been filled in.
1068 void NFSProtocol::createVirtualDirEntry(UDSEntry &entry)
1069 {
1070     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); // dummy size
1071     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
1072     entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory");
1073     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
1074     entry.fastInsert(KIO::UDSEntry::UDS_USER, QString::fromLatin1("root"));
1075     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1("root"));
1076 }
1077 
1078 #include "kio_nfs.moc"
1079 #include "moc_kio_nfs.cpp"