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"