1 /*  This file is part of the KDE project
2 
3     SPDX-FileCopyrightText: 2000 Alexander Neundorf <neundorf@kde.org>
4     SPDX-FileCopyrightText: 2014 Mathias Tillman <master.homer@gmail.com>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "kio_nfs.h"
10 
11 #include <config-runtime.h>
12 
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <sys/utsname.h>
16 
17 #include <arpa/inet.h>
18 #include <netdb.h>
19 
20 #include <QFile>
21 #include <QDir>
22 #include <QDebug>
23 #include <QHostInfo>
24 #include <QCoreApplication>
25 
26 #include <klocalizedstring.h>
27 #include <ksharedconfig.h>
28 #include <kconfiggroup.h>
29 #include <kio/global.h>
30 
31 #include "kio_nfs_debug.h"
32 #include "nfsv2.h"
33 #include "nfsv3.h"
34 
35 using namespace KIO;
36 using namespace std;
37 
38 // Pseudo plugin class to embed meta data
39 class KIOPluginForMetaData : public QObject
40 {
41     Q_OBJECT
42     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.nfs" FILE "nfs.json")
43 };
44 
45 extern "C" int Q_DECL_EXPORT kdemain(int argc, char** argv);
46 
kdemain(int argc,char ** argv)47 int kdemain(int argc, char** argv)
48 {
49     QCoreApplication app(argc, argv);
50     app.setApplicationName(QLatin1String("kio_nfs"));
51 
52     if (argc != 4) {
53         fprintf(stderr, "Usage: kio_nfs protocol domain-socket1 domain-socket2\n");
54         exit(-1);
55     }
56 
57     NFSSlave slave(argv[2], argv[3]);
58     slave.dispatchLoop();
59 
60     return 0;
61 }
62 
63 
64 // Both the insertion and lookup in the file handle cache (managed by
65 // NFSProtocol), and the use of QFileInfo to locate a parent directory,
66 // are sensitive to paths having trailing slashes.  In order to keep
67 // everything consistent, any URLs passed in must be cleaned before using
68 // them as a fileystem or NFS protocol path.
69 
cleanPath(const QUrl & url)70 static QUrl cleanPath(const QUrl &url)
71 {
72     return (url.adjusted(QUrl::StripTrailingSlash|QUrl::NormalizePathSegments));
73 }
74 
75 
NFSSlave(const QByteArray & pool,const QByteArray & app)76 NFSSlave::NFSSlave(const QByteArray& pool, const QByteArray& app)
77     :  KIO::SlaveBase("nfs", pool, app),
78        m_protocol(nullptr),
79        m_usedirplus3(true),
80        m_errorId(KIO::Error(0))
81 {
82     qCDebug(LOG_KIO_NFS) << pool << app;
83 }
84 
~NFSSlave()85 NFSSlave::~NFSSlave()
86 {
87     delete m_protocol;
88 }
89 
openConnection()90 void NFSSlave::openConnection()
91 {
92     qCDebug(LOG_KIO_NFS);
93 
94     if (m_protocol != nullptr)
95     {
96         m_protocol->openConnection();
97         return;
98     }
99 
100     const KSharedConfig::Ptr cfg = KSharedConfig::openConfig("kionfsrc");
101 
102     const KConfigGroup grp1 = cfg->group("Default");        // default for all hosts
103     int minproto = grp1.readEntry("minproto", 2);       // minimum NFS version to accept
104     int maxproto = grp1.readEntry("maxproto", 4);       // maximum NFS version to try
105     m_usedirplus3 = grp1.readEntry("usedirplus3", true);    // use READDIRPLUS3 for listing
106 
107     const KConfigGroup grp2 = cfg->group("Host "+m_host);
108     if (grp2.exists())                      // look for host-specific settings
109     {   // with default values from above
110         minproto = grp2.readEntry("minproto", minproto);
111         maxproto = grp2.readEntry("maxproto", maxproto);
112         m_usedirplus3 = grp2.readEntry("usedirplus3", m_usedirplus3);
113     }
114 
115     minproto = qBound(2, minproto, 4);              // enforce limits
116     maxproto = qBound(minproto, maxproto, 4);
117     qCDebug(LOG_KIO_NFS) << "configuration for" << m_host;
118     qCDebug(LOG_KIO_NFS) << "minproto" << minproto << "maxproto" << maxproto << "usedirplus3" << m_usedirplus3;
119 
120     bool connectionError = false;
121 
122     int version = maxproto;
123     while (version >= minproto)
124     {
125         qCDebug(LOG_KIO_NFS) << "Trying NFS version" << version;
126 
127         // Try to create an NFS protocol handler for that version
128         switch (version)
129         {
130         case 4:     // TODO
131             qCDebug(LOG_KIO_NFS) << "NFSv4 is not supported at this time";
132             break;
133 
134         case 3:
135             m_protocol = new NFSProtocolV3(this);
136             break;
137 
138         case 2:
139             m_protocol = new NFSProtocolV2(this);
140             break;
141         }
142 
143         if (m_protocol != nullptr)          // created protocol for that version
144         {
145             m_protocol->setHost(m_host, m_user);    // try to make initial connection
146             if (m_protocol->isCompatible(connectionError)) break;
147         }
148 
149         delete m_protocol;              // no point using that protocol
150         --version;                  // try the next lower
151         m_protocol = nullptr;               // try again with new protocol
152     }
153 
154     if (m_protocol == nullptr)              // failed to find a protocol
155     {
156         if (!connectionError)               // but connection was possible
157         {
158             setError(KIO::ERR_SLAVE_DEFINED, i18n("Cannot find an NFS version that host '%1' supports", m_host));
159         }
160         else                        // connection failed
161         {
162             setError(KIO::ERR_CANNOT_CONNECT, m_host);
163         }
164     }
165     else                        // usable protocol was created
166     {
167         m_protocol->openConnection();           // open the connection
168     }
169 }
170 
171 
closeConnection()172 void NFSSlave::closeConnection()
173 {
174     qCDebug(LOG_KIO_NFS);
175 
176     if (m_protocol != nullptr) {
177         m_protocol->closeConnection();
178     }
179 }
180 
181 
setHost(const QString & host,quint16,const QString & user,const QString &)182 void NFSSlave::setHost(const QString& host, quint16 /*port*/, const QString &user, const QString& /*pass*/)
183 {
184     qCDebug(LOG_KIO_NFS) << "host" << host << "user" << user;
185 
186     if (m_protocol != nullptr)
187     {
188         // New host or user? New protocol!
189         if (host != m_host || user != m_user)
190         {
191             qCDebug(LOG_KIO_NFS) << "Deleting old protocol";
192             delete m_protocol;
193             m_protocol = nullptr;
194         }
195         else
196         {
197             // TODO: Doing this is pointless if nothing has changed
198             m_protocol->setHost(host, user);
199         }
200     }
201 
202     m_host = host;
203     m_user = user;
204 }
205 
206 
put(const QUrl & url,int _mode,KIO::JobFlags _flags)207 void NFSSlave::put(const QUrl& url, int _mode, KIO::JobFlags _flags)
208 {
209     qCDebug(LOG_KIO_NFS);
210 
211     if (verifyProtocol(url)) {
212         m_protocol->put(cleanPath(url), _mode, _flags);
213     }
214     finishOperation();
215 }
216 
get(const QUrl & url)217 void NFSSlave::get(const QUrl& url)
218 {
219     qCDebug(LOG_KIO_NFS);
220 
221     if (verifyProtocol(url)) {
222         m_protocol->get(cleanPath(url));
223     }
224     finishOperation();
225 }
226 
listDir(const QUrl & url)227 void NFSSlave::listDir(const QUrl& url)
228 {
229     qCDebug(LOG_KIO_NFS) << url;
230 
231     if (verifyProtocol(url)) {
232         m_protocol->listDir(cleanPath(url));
233     }
234     finishOperation();
235 }
236 
symlink(const QString & target,const QUrl & dest,KIO::JobFlags _flags)237 void NFSSlave::symlink(const QString& target, const QUrl& dest, KIO::JobFlags _flags)
238 {
239     qCDebug(LOG_KIO_NFS);
240 
241     if (verifyProtocol(dest)) {
242         m_protocol->symlink(target, cleanPath(dest), _flags);
243     }
244     finishOperation();
245 }
246 
stat(const QUrl & url)247 void NFSSlave::stat(const QUrl& url)
248 {
249     qCDebug(LOG_KIO_NFS);
250 
251     if (verifyProtocol(url)) {
252         m_protocol->stat(cleanPath(url));
253     }
254     finishOperation();
255 }
256 
mkdir(const QUrl & url,int permissions)257 void NFSSlave::mkdir(const QUrl& url, int permissions)
258 {
259     qCDebug(LOG_KIO_NFS);
260 
261     if (verifyProtocol(url)) {
262         m_protocol->mkdir(cleanPath(url), permissions);
263     }
264     finishOperation();
265 }
266 
del(const QUrl & url,bool isfile)267 void NFSSlave::del(const QUrl& url, bool isfile)
268 {
269     qCDebug(LOG_KIO_NFS);
270 
271     if (verifyProtocol(url)) {
272         m_protocol->del(cleanPath(url), isfile);
273     }
274     finishOperation();
275 }
276 
chmod(const QUrl & url,int permissions)277 void NFSSlave::chmod(const QUrl& url, int permissions)
278 {
279     qCDebug(LOG_KIO_NFS);
280 
281     if (verifyProtocol(url)) {
282         m_protocol->chmod(cleanPath(url), permissions);
283     }
284     finishOperation();
285 }
286 
rename(const QUrl & src,const QUrl & dest,KIO::JobFlags flags)287 void NFSSlave::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags)
288 {
289     qCDebug(LOG_KIO_NFS);
290 
291     if (verifyProtocol(src) && verifyProtocol(dest)) {
292         m_protocol->rename(cleanPath(src), cleanPath(dest), flags);
293     }
294     finishOperation();
295 }
296 
copy(const QUrl & src,const QUrl & dest,int mode,KIO::JobFlags flags)297 void NFSSlave::copy(const QUrl& src, const QUrl& dest, int mode, KIO::JobFlags flags)
298 {
299     qCDebug(LOG_KIO_NFS);
300 
301     if (verifyProtocol(src) && verifyProtocol(dest)) {
302         m_protocol->copy(cleanPath(src), cleanPath(dest), mode, flags);
303     }
304     finishOperation();
305 }
306 
307 
308 // Perform initial URL and host checks before starting any operation.
309 // This means any KIO::SlaveBase action which is expected to end by
310 // calling either error() or finished().
verifyProtocol(const QUrl & url)311 bool NFSSlave::verifyProtocol(const QUrl &url)
312 {
313     m_errorId = KIO::Error(0);              // ensure reset before starting
314     m_errorText.clear();
315 
316     // The NFS protocol definition includes copyToFile=true and copyFromFile=true,
317     // so the URL scheme here can also be "file".  No URL or protocol checking
318     // is required in this case.
319     if (url.scheme() != "nfs") return true;
320 
321     if (!url.isValid())                 // also checks for empty
322     {
323         setError(KIO::ERR_MALFORMED_URL, url.toDisplayString());
324         return (false);
325     }
326 
327     // A NFS URL must include a host name, if it does not then nothing
328     // sensible can be done.  Doing the check here and returning immediately
329     // avoids multiple calls of SlaveBase::error() as each protocol is tried
330     // in NFSSlave::openConnection().
331 
332     const QString host = url.host();
333     if (host.isEmpty())
334     {
335         // KIO::ERR_UNKNOWN_HOST with a blank host name results in the
336         // error message "No hostname specified", but Konqueror does not
337         // report it properly.  Return our own message.
338         setError(KIO::ERR_SLAVE_DEFINED, i18n("The NFS protocol requires a server host name."));
339         return (false);
340     }
341     else
342     {
343         // There is a host name, so check that it can be resolved.  If it
344         // can't, return an error now and don't bother trying the protocol.
345         QHostInfo hostInfo = QHostInfo::fromName(host);
346         if (hostInfo.error() != QHostInfo::NoError)
347         {
348             qCDebug(LOG_KIO_NFS) << "host lookup of" << host << "error" << hostInfo.errorString();
349             setError(KIO::ERR_UNKNOWN_HOST, host);
350             return (false);
351         }
352     }
353 
354     if (m_protocol==nullptr)                // no protocol connection yet
355     {
356         openConnection();               // create and open connection
357         if (m_protocol==nullptr)            // if that failed, then
358         {   // no more can be done
359             qCDebug(LOG_KIO_NFS) << "Could not resolve a compatible protocol version!";
360             goto fail;
361         }
362     }
363     else if (!m_protocol->isConnected())        // already have a protocol
364     {
365         m_protocol->openConnection();           // open its connection
366     }
367 
368     if (m_protocol->isConnected()) return true;     // connection succeeded
369 
370 fail:                           // default error if none already
371     setError(KIO::ERR_INTERNAL, i18n("Failed to initialise protocol"));
372     return false;
373 }
374 
375 
376 // These two functions keep track of errors found during any operation,
377 // and return the error or finish the operation appropriately when
378 // the operation is complete.
379 //
380 // NFSProtocol and classes derived from it, and anything that they call,
381 // should call setError() instead of SlaveBase::error().  When the
382 // operation is complete, just return and do not call SlaveBase::finished().
383 
384 // Record the error information, but do not call SlaveBase::error().
385 // If there has been an error, finishOperation() will report it when
386 // the protocol operation is complete.
387 
setError(KIO::Error errid,const QString & text)388 void NFSSlave::setError(KIO::Error errid, const QString &text)
389 {
390     if (m_errorId!=0)
391     {
392         qCDebug(LOG_KIO_NFS) << errid << "ignored due to previous error";
393         return;
394     }
395 
396     qCDebug(LOG_KIO_NFS) << errid << text;
397     m_errorId = errid;
398     m_errorText = text;
399 }
400 
401 
402 // An operation is complete.  If there has been an error, then report it.
finishOperation()403 void NFSSlave::finishOperation()
404 {
405     if (m_errorId==0) {                 // no error encountered
406         SlaveBase::finished();
407     } else {                        // there was an error
408         SlaveBase::error(m_errorId, m_errorText);
409     }
410 }
411 
412 
NFSFileHandle()413 NFSFileHandle::NFSFileHandle()
414     : m_handle(nullptr),
415       m_size(0),
416       m_linkHandle(nullptr),
417       m_linkSize(0),
418       m_isLink(false)
419 {
420 }
421 
NFSFileHandle(const NFSFileHandle & src)422 NFSFileHandle::NFSFileHandle(const NFSFileHandle& src)
423     :  NFSFileHandle()
424 {
425     (*this) = src;
426 }
427 
NFSFileHandle(const fhandle3 & src)428 NFSFileHandle::NFSFileHandle(const fhandle3& src)
429     :  NFSFileHandle()
430 {
431     (*this) = src;
432 }
433 
NFSFileHandle(const fhandle & src)434 NFSFileHandle::NFSFileHandle(const fhandle& src)
435     :  NFSFileHandle()
436 {
437     (*this) = src;
438 }
439 
NFSFileHandle(const nfs_fh3 & src)440 NFSFileHandle::NFSFileHandle(const nfs_fh3& src)
441     :  NFSFileHandle()
442 {
443     (*this) = src;
444 }
445 
NFSFileHandle(const nfs_fh & src)446 NFSFileHandle::NFSFileHandle(const nfs_fh& src)
447     :  NFSFileHandle()
448 {
449     (*this) = src;
450 }
451 
~NFSFileHandle()452 NFSFileHandle::~NFSFileHandle()
453 {
454     if (m_handle != nullptr) {
455         delete [] m_handle;
456     }
457     if (m_linkHandle != nullptr) {
458         delete [] m_linkHandle;
459     }
460 }
461 
toFH(nfs_fh3 & fh) const462 void NFSFileHandle::toFH(nfs_fh3& fh) const
463 {
464     fh.data.data_len = m_size;
465     fh.data.data_val = m_handle;
466 }
467 
toFH(nfs_fh & fh) const468 void NFSFileHandle::toFH(nfs_fh& fh) const
469 {
470     memcpy(fh.data, m_handle, m_size);
471 }
472 
toFHLink(nfs_fh3 & fh) const473 void NFSFileHandle::toFHLink(nfs_fh3& fh) const
474 {
475     fh.data.data_len = m_linkSize;
476     fh.data.data_val = m_linkHandle;
477 }
478 
toFHLink(nfs_fh & fh) const479 void NFSFileHandle::toFHLink(nfs_fh& fh) const
480 {
481     memcpy(fh.data, m_linkHandle, m_size);
482 }
483 
operator =(const NFSFileHandle & src)484 NFSFileHandle& NFSFileHandle::operator=(const NFSFileHandle& src)
485 {
486     if (src.m_size > 0) {
487         if (m_handle != nullptr) {
488             delete [] m_handle;
489             m_handle = nullptr;
490         }
491         m_size = src.m_size;
492         m_handle = new char[m_size];
493         memcpy(m_handle, src.m_handle, m_size);
494     }
495     if (src.m_linkSize > 0) {
496         if (m_linkHandle != nullptr) {
497             delete [] m_linkHandle;
498             m_linkHandle = nullptr;
499         }
500 
501         m_linkSize = src.m_linkSize;
502         m_linkHandle = new char[m_linkSize];
503         memcpy(m_linkHandle, src.m_linkHandle, m_linkSize);
504     }
505 
506     m_isLink = src.m_isLink;
507     return *this;
508 }
509 
operator =(const fhandle3 & src)510 NFSFileHandle& NFSFileHandle::operator=(const fhandle3& src)
511 {
512     if (m_handle != nullptr) {
513         delete [] m_handle;
514         m_handle = nullptr;
515     }
516 
517     m_size = src.fhandle3_len;
518     m_handle = new char[m_size];
519     memcpy(m_handle, src.fhandle3_val, m_size);
520     return *this;
521 }
522 
operator =(const fhandle & src)523 NFSFileHandle& NFSFileHandle::operator=(const fhandle& src)
524 {
525     if (m_handle != nullptr) {
526         delete [] m_handle;
527         m_handle = nullptr;
528     }
529 
530     m_size = NFS_FHSIZE;
531     m_handle = new char[m_size];
532     memcpy(m_handle, src, m_size);
533     return *this;
534 }
535 
operator =(const nfs_fh3 & src)536 NFSFileHandle& NFSFileHandle::operator=(const nfs_fh3& src)
537 {
538     if (m_handle != nullptr) {
539         delete [] m_handle;
540         m_handle = nullptr;
541     }
542 
543     m_size = src.data.data_len;
544     m_handle = new char[m_size];
545     memcpy(m_handle, src.data.data_val, m_size);
546     return *this;
547 }
548 
operator =(const nfs_fh & src)549 NFSFileHandle& NFSFileHandle::operator=(const nfs_fh& src)
550 {
551     if (m_handle != nullptr) {
552         delete [] m_handle;
553         m_handle = nullptr;
554     }
555 
556     m_size = NFS_FHSIZE;
557     m_handle = new char[m_size];
558     memcpy(m_handle, src.data, m_size);
559     return *this;
560 }
561 
setLinkSource(const nfs_fh3 & src)562 void NFSFileHandle::setLinkSource(const nfs_fh3& src)
563 {
564     if (m_linkHandle != nullptr) {
565         delete [] m_linkHandle;
566         m_linkHandle = nullptr;
567     }
568 
569     m_linkSize = src.data.data_len;
570     m_linkHandle = new char[m_linkSize];
571     memcpy(m_linkHandle, src.data.data_val, m_linkSize);
572     m_isLink = true;
573 }
574 
setLinkSource(const nfs_fh & src)575 void NFSFileHandle::setLinkSource(const nfs_fh& src)
576 {
577     if (m_linkHandle != nullptr) {
578         delete [] m_linkHandle;
579         m_linkHandle = nullptr;
580     }
581 
582     m_linkSize = NFS_FHSIZE;
583     m_linkHandle = new char[m_linkSize];
584     memcpy(m_linkHandle, src.data, m_linkSize);
585     m_isLink = true;
586 }
587 
NFSProtocol(NFSSlave * slave)588 NFSProtocol::NFSProtocol(NFSSlave* slave)
589     : m_slave(slave)
590 {
591 }
592 
copy(const QUrl & src,const QUrl & dest,int mode,KIO::JobFlags flags)593 void NFSProtocol::copy(const QUrl& src, const QUrl& dest, int mode, KIO::JobFlags flags)
594 {
595     if (src.isLocalFile()) {
596         copyTo(src, dest, mode, flags);
597     } else if (dest.isLocalFile()) {
598         copyFrom(src, dest, mode, flags);
599     } else {
600         copySame(src, dest, mode, flags);
601     }
602 }
603 
addExportedDir(const QString & path)604 void NFSProtocol::addExportedDir(const QString& path)
605 {
606     m_exportedDirs.append(path);
607 }
608 
getExportedDirs()609 const QStringList& NFSProtocol::getExportedDirs()
610 {
611     return m_exportedDirs;
612 }
613 
614 
isExportedDir(const QString & path)615 bool NFSProtocol::isExportedDir(const QString& path)
616 {
617     // See whether the path is an exported directory:  that is, a prefix
618     // of but not identical to any of the server exports.  If it is, then
619     // it can be virtually listed and some operations are forbidden.
620     // For example, if the server exports "/export/nfs/dir" then the root,
621     // "/export" and "/export/nfs" are considered to be exported directories,
622     // but "/export/nfs/dir" is not because it needs a server mount in order
623     // to be listed.
624     //
625     // This function looks similar to, but is not the same as, isValidPath()
626     // below.  This tests for "is the given path a prefix of any exported
627     // directory", but isValidPath() tests for "is any exported directory equal
628     // to or a prefix of the given path".
629 
630     // The root is always considered to be exported.
631     if (path.isEmpty() || path == "/" || QFileInfo(path).isRoot())
632     {
633         qCDebug(LOG_KIO_NFS) << path << "is root";
634         return true;
635     }
636 
637     const QString dirPath = path+QDir::separator();
638     for (QStringList::const_iterator it = m_exportedDirs.constBegin();
639             it != m_exportedDirs.constEnd(); ++it) {
640         const QString &exportedDir = (*it);
641         // We know that both 'path' and the contents of m_exportedDirs
642         // have been cleaned of any trailing slashes.
643         if (exportedDir.startsWith(dirPath))
644         {
645             qCDebug(LOG_KIO_NFS) << path << "is exported";
646             return true;
647         }
648     }
649 
650     return false;
651 }
652 
653 
removeExportedDir(const QString & path)654 void NFSProtocol::removeExportedDir(const QString& path)
655 {
656     m_exportedDirs.removeOne(path);
657 }
658 
addFileHandle(const QString & path,NFSFileHandle fh)659 void NFSProtocol::addFileHandle(const QString& path, NFSFileHandle fh)
660 {
661     if (fh.isInvalid()) qCDebug(LOG_KIO_NFS) << "not adding" << path << "with invalid NFSFileHandle";
662     else m_handleCache.insert(path, fh);
663 }
664 
getFileHandle(const QString & path)665 NFSFileHandle NFSProtocol::getFileHandle(const QString& path)
666 {
667     if (!isConnected()) {
668         return NFSFileHandle();
669     }
670 
671     if (m_exportedDirs.contains(path))
672     {
673         // All exported directories should have already been stored in
674         // m_handleCache by the protocol's openConnection().  If any
675         // exported directory could not be mounted, then it will be in
676         // m_exportedDirs but not in m_handleCache.  There is nothing more
677         // that can be done in this case.
678         if (!m_handleCache.contains(path)) {
679             m_slave->setError(KIO::ERR_CANNOT_MOUNT, path);
680             return NFSFileHandle();
681         }
682     }
683 
684     if (!isValidPath(path)) {
685         qCDebug(LOG_KIO_NFS) << path << "is not a valid path";
686         m_slave->setError(KIO::ERR_CANNOT_ENTER_DIRECTORY, path);
687         return NFSFileHandle();
688     }
689 
690     // In theory the root ("/") is a valid path but matches here.
691     // However, it should never be seen unless the NFS server is
692     // exporting its entire filesystem (which is very unlikely).
693     if (path.endsWith('/')) {
694         qCWarning(LOG_KIO_NFS) << "Passed a path ending with '/'.  Fix the caller.";
695     }
696 
697     // The handle may already be in the cache, check it now.
698     // The exported dirs are always in the cache, unless there was a
699     // problem mounting them which will have been checked above.
700     if (m_handleCache.contains(path)) {
701         return m_handleCache[path];
702     }
703 
704     // Loop detected, abort.
705     if (QFileInfo(path).path() == path) {
706         return NFSFileHandle();
707     }
708 
709     // Look up the file handle from the protocol
710     NFSFileHandle childFH = lookupFileHandle(path);
711     if (!childFH.isInvalid()) {
712         addFileHandle(path, childFH);
713     }
714 
715     return childFH;
716 }
717 
removeFileHandle(const QString & path)718 void NFSProtocol::removeFileHandle(const QString& path)
719 {
720     m_handleCache.remove(path);
721 }
722 
723 
isValidPath(const QString & path)724 bool NFSProtocol::isValidPath(const QString& path)
725 {
726     // See whether the path is or below an exported directory:
727     // that is, any of the server exports is identical to or a prefix
728     // of the path.  If it does not start with an exported prefix,
729     // then it is not a valid NFS file path on the server.
730 
731     // This function looks similar to, but is not the same as,
732     // isExportedDir() above.  This tests for "is any exported directory
733     // equal to or is a prefix of the given path", but isExportedDir()
734     // tests for "is the given path a prefix of any exported
735     // directory".
736 
737     // The root is always considered to be valid.
738     if (path.isEmpty() || path == "/" || QFileInfo(path).isRoot())
739     {
740         return true;
741     }
742 
743     for (QStringList::const_iterator it = m_exportedDirs.constBegin();
744             it != m_exportedDirs.constEnd(); ++it)
745     {
746         const QString &exportedDir = (*it);
747         // We know that both 'path' and the contents of m_exportedDirs
748         // have been cleaned of any trailing slashes.
749         if (path == exportedDir) return true;
750         if (path.startsWith(exportedDir+QDir::separator())) return true;
751     }
752 
753     return false;
754 }
755 
756 
isValidLink(const QString & parentDir,const QString & linkDest)757 bool NFSProtocol::isValidLink(const QString &parentDir, const QString &linkDest)
758 {
759     qCDebug(LOG_KIO_NFS) << "checking" << linkDest << "in" << parentDir;
760 
761     if (linkDest.isEmpty()) return false;       // ensure link is absolute
762     const QString absDest = QFileInfo(parentDir, linkDest).absoluteFilePath();
763 
764     // The link target may not be valid on the NFS server (i.e. it may
765     // point outside of the exported directories).  Check for this before
766     // calling getFileHandle() for the target of the link, as otherwise
767     // the isValidPath() check in getFileHandle() will set the error
768     // ERR_CANNOT_ENTER_DIRECTORY which will be taken as the result of
769     // the NFS operation.  This is not an error condition if just checking
770     // the target of a link, so do the same check here but ignore any error.
771     if (!isValidPath(absDest))
772     {
773         qCDebug(LOG_KIO_NFS) << "target" << absDest << "is invalid";
774         return false;
775     }
776 
777     // It is now safe to call getFileHandle() on the link target.
778     return (!getFileHandle(absDest).isInvalid());
779 }
780 
781 
openConnection(const QString & host,int prog,int vers,CLIENT * & client,int & sock)782 KIO::Error NFSProtocol::openConnection(const QString& host, int prog, int vers, CLIENT*& client, int& sock)
783 {
784     // NFSSlave::verifyProtocol() should already have checked that
785     // the host name is not blank and is resolveable, so the two
786     // KIO::ERR_UNKNOWN_HOST failures here should never happen.
787 
788     if (host.isEmpty()) {
789         return KIO::ERR_UNKNOWN_HOST;
790     }
791 
792     struct sockaddr_in server_addr;
793     if (host[0] >= '0' && host[0] <= '9') {
794         server_addr.sin_family = AF_INET;
795         server_addr.sin_addr.s_addr = inet_addr(host.toLatin1().constData());
796     } else {
797         struct hostent* hp = gethostbyname(host.toLatin1().constData());
798         if (hp == nullptr) {
799             return KIO::ERR_UNKNOWN_HOST;
800         }
801         server_addr.sin_family = AF_INET;
802         memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
803     }
804 
805     server_addr.sin_port = 0;
806 
807     sock = RPC_ANYSOCK;
808     client = clnttcp_create(&server_addr, prog, vers, &sock, 0, 0);
809     if (client == nullptr) {
810         server_addr.sin_port = 0;
811         sock = RPC_ANYSOCK;
812 
813         timeval pertry_timeout;
814         pertry_timeout.tv_sec = 3;
815         pertry_timeout.tv_usec = 0;
816         client = clntudp_create(&server_addr, prog, vers, pertry_timeout, &sock);
817         if (client == nullptr) {
818             ::close(sock);
819             return KIO::ERR_CANNOT_CONNECT;
820         }
821     }
822 
823     QString hostName = QHostInfo::localHostName();
824     QString domainName = QHostInfo::localDomainName();
825     if (!domainName.isEmpty()) {
826         hostName = hostName + QLatin1Char('.') + domainName;
827     }
828 
829     uid_t uid = geteuid();
830     if (!m_currentUser.isEmpty())
831     {
832         bool ok;
833         uid_t num = m_currentUser.toUInt(&ok);
834         if (ok) uid = num;
835         else
836         {
837             const struct passwd *pwd = getpwnam(m_currentUser.toLocal8Bit().constData());
838             if (pwd != nullptr) uid = pwd->pw_uid;
839         }
840     }
841 
842     client->cl_auth = authunix_create(hostName.toUtf8().data(), uid, getegid(), 0, nullptr);
843     return KIO::Error(0);
844 }
845 
846 
checkForError(int clientStat,int nfsStat,const QString & text)847 bool NFSProtocol::checkForError(int clientStat, int nfsStat, const QString& text)
848 {
849     if (clientStat!=RPC_SUCCESS)
850     {
851         const char *errstr = clnt_sperrno(static_cast<clnt_stat>(clientStat));
852         qCDebug(LOG_KIO_NFS) << "RPC error" << clientStat << errstr << "on" << text;
853         m_slave->setError(KIO::ERR_INTERNAL_SERVER,
854                           i18n("RPC error %1, %2", QString::number(clientStat), errstr));
855         return false;
856     }
857 
858     if (nfsStat != NFS_OK)
859     {
860         qCDebug(LOG_KIO_NFS) << "NFS error" << nfsStat << text;
861         switch (nfsStat) {
862         case NFSERR_PERM:
863             m_slave->setError(KIO::ERR_ACCESS_DENIED, text);
864             break;
865         case NFSERR_NOENT:
866             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
867             break;
868         //does this mapping make sense ?
869         case NFSERR_IO:
870             m_slave->setError(KIO::ERR_INTERNAL_SERVER, text);
871             break;
872         //does this mapping make sense ?
873         case NFSERR_NXIO:
874             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
875             break;
876         case NFSERR_ACCES:
877             m_slave->setError(KIO::ERR_ACCESS_DENIED, text);
878             break;
879         case NFSERR_EXIST:
880             m_slave->setError(KIO::ERR_FILE_ALREADY_EXIST, text);
881             break;
882         //does this mapping make sense ?
883         case NFSERR_NODEV:
884             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
885             break;
886         case NFSERR_NOTDIR:
887             m_slave->setError(KIO::ERR_IS_FILE, text);
888             break;
889         case NFSERR_ISDIR:
890             m_slave->setError(KIO::ERR_IS_DIRECTORY, text);
891             break;
892         //does this mapping make sense ?
893         case NFSERR_FBIG:
894             m_slave->setError(KIO::ERR_INTERNAL_SERVER, text);
895             break;
896         //does this mapping make sense ?
897         case NFSERR_NOSPC:
898             m_slave->setError(KIO::ERR_DISK_FULL, text);
899             break;
900         case NFSERR_ROFS:
901             m_slave->setError(KIO::ERR_WRITE_ACCESS_DENIED, text);
902             break;
903         case NFSERR_NAMETOOLONG:
904             m_slave->setError(KIO::ERR_INTERNAL_SERVER, i18n("Filename too long"));
905             break;
906         case NFSERR_NOTEMPTY:
907             m_slave->setError(KIO::ERR_CANNOT_RMDIR, text);
908             break;
909         //does this mapping make sense ?
910         case NFSERR_DQUOT:
911             m_slave->setError(KIO::ERR_INTERNAL_SERVER, i18n("Disk quota exceeded"));
912             break;
913         case NFSERR_STALE:
914             m_slave->setError(KIO::ERR_DOES_NOT_EXIST, text);
915             break;
916         default:
917             m_slave->setError(KIO::ERR_UNKNOWN, i18n("NFS error %1, %2", QString::number(nfsStat), text));
918             break;
919         }
920         return false;
921     }
922     return true;
923 }
924 
925 
926 // Perform checks and, if so indicated, list a virtual (exported)
927 // directory that will not actually involve accessing the NFS server.
928 // Return the directory path, or a null string if there is a problem
929 // or if the URL refers to a virtual directory which has been listed.
930 
listDirInternal(const QUrl & url)931 QString NFSProtocol::listDirInternal(const QUrl &url)
932 {
933     qCDebug(LOG_KIO_NFS) << url;
934 
935     const QString path(url.path());
936     // Is the path part of an exported (virtual) directory?
937     if (isExportedDir(path))
938     {
939         qCDebug(LOG_KIO_NFS) << "Listing virtual dir" << path;
940 
941         QString dirPrefix = path;
942         if (dirPrefix!="/") dirPrefix += QDir::separator();
943 
944         QStringList virtualList;
945         const QStringList exportedDirs = getExportedDirs();
946         for (QStringList::const_iterator it = exportedDirs.constBegin(); it != exportedDirs.constEnd(); ++it)
947         {
948             // When an export is multiple levels deep (for example "/export/nfs/dir"
949             // where "/export" is being listed), we only want to display one level
950             // ("nfs") at a time.  Find all of the exported directories that are
951             // below the 'dirPrefix', and list the first (or only) path component
952             // of each.
953 
954             QString name = (*it);           // this exported directory
955             if (!name.startsWith(dirPrefix)) continue;  // not below this prefix
956 
957             name = name.mid(dirPrefix.length());    // remainder after the prefix
958 
959             const int idx = name.indexOf(QDir::separator());
960             if (idx!=-1) name = name.left(idx);     // take first path component
961 
962             if (!virtualList.contains(name)) {
963                 qCDebug(LOG_KIO_NFS) << "Found exported" << name;
964                 virtualList.append(name);
965             }
966         }
967 
968         KIO::UDSEntry entry;
969         createVirtualDirEntry(entry);
970         entry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
971         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "folder-network");
972         m_slave->listEntry(entry);
973 
974         for (QStringList::const_iterator it = virtualList.constBegin(); it != virtualList.constEnd(); ++it)
975         {
976             const QString &name = (*it);        // this listed directory
977 
978             entry.replace(KIO::UDSEntry::UDS_NAME, name);
979             if (isExportedDir(dirPrefix+name)) entry.replace(KIO::UDSEntry::UDS_ICON_NAME, "folder-network");
980             else entry.replace(KIO::UDSEntry::UDS_ICON_NAME, "folder");
981 
982             m_slave->listEntry(entry);
983         }
984 
985         return (QString());             // listed, no more to do
986     }
987 
988     // For the listing we now actually need to access the NFS server.
989     // We should always be connected at this point, but better safe than sorry!
990     if (!isConnected()) return (QString());
991 
992     return (path);                  // the path to list
993 }
994 
995 
996 // Perform checks and, if so indicated, return information for
997 // a virtual (exported) directory.  Return the entry path, or a
998 // null string if there is a problem or if the URL refers to a
999 // virtual directory which has been processed.
1000 
statInternal(const QUrl & url)1001 QString NFSProtocol::statInternal(const QUrl &url)
1002 {
1003     qCDebug(LOG_KIO_NFS) << url;
1004 
1005     const QString path(url.path());
1006     if (path.isEmpty())
1007     {
1008         // Displaying a location with an empty path (e.g. "nfs://server")
1009         // seems to confuse Konqueror, it shows the directory but will not
1010         // descend into any subdirectories.  The same location with a root
1011         // path ("nfs://server/") works, so redirect to that.
1012         QUrl redir = url.resolved(QUrl("/"));
1013         qDebug() << "root with empty path, redirecting to" << redir;
1014         slave()->redirection(redir);
1015         return (QString());
1016     }
1017 
1018     // We can't stat an exported directory on the NFS server,
1019     // but we know that it must be a directory.
1020     if (isExportedDir(path))
1021     {
1022         KIO::UDSEntry entry;
1023         entry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
1024         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, "folder-network");
1025         createVirtualDirEntry(entry);
1026 
1027         slave()->statEntry(entry);
1028         return (QString());
1029     }
1030 
1031     return (path);                  // the path to stat
1032 }
1033 
1034 
1035 // Set the host to be accessed.  If the host has changed a new
1036 // host connection is required, so close the current one.
1037 //
1038 // Due the way that NFSSlave::setHost() is implemented, if the
1039 // host name changes then the protocol will always be deleted
1040 // and recreated.  So in reality this function does nothing useful.
1041 
setHost(const QString & host,const QString & user)1042 void NFSProtocol::setHost(const QString &host, const QString &user)
1043 {
1044     qCDebug(LOG_KIO_NFS) << "host" << host << "user" << user;
1045 
1046     if (host.isEmpty())                 // must have a host name
1047     {
1048         m_slave->setError(KIO::ERR_UNKNOWN_HOST, host);
1049         return;
1050     }
1051     // nothing to do if no change
1052     if (host == m_currentHost && user == m_currentUser) return;
1053     closeConnection();                  // close the existing connection
1054     m_currentHost = host;               // set the new host name
1055     m_currentUser = user;               // set the new user name
1056 }
1057 
1058 
1059 // This function and completeInvalidUDSEntry() must use KIO::UDSEntry::replace()
1060 // because they may be called with a UDSEntry that has already been partially
1061 // filled in by NFSProtocol::createVirtualDirEntry().
1062 
completeUDSEntry(KIO::UDSEntry & entry,uid_t uid,gid_t gid)1063 void NFSProtocol::completeUDSEntry(KIO::UDSEntry &entry, uid_t uid, gid_t gid)
1064 {
1065     QString str;
1066 
1067     if (!m_usercache.contains(uid))
1068     {
1069         const struct passwd *user = getpwuid(uid);
1070         if (user!=nullptr)
1071         {
1072             m_usercache.insert(uid, QString::fromLatin1(user->pw_name));
1073             str = user->pw_name;
1074         }
1075         else str = QString::number(uid);
1076     }
1077     else str = m_usercache.value(uid);
1078     entry.replace(KIO::UDSEntry::UDS_USER, str);
1079 
1080     if (!m_groupcache.contains(gid))
1081     {
1082         const struct group *grp = getgrgid(gid);
1083         if (grp!=nullptr)
1084         {
1085             m_groupcache.insert(gid, QString::fromLatin1(grp->gr_name));
1086             str = grp->gr_name;
1087         }
1088         else str = QString::number(gid);
1089     }
1090     else str = m_groupcache.value(gid);
1091     entry.replace(KIO::UDSEntry::UDS_GROUP, str);
1092 }
1093 
1094 
completeInvalidUDSEntry(KIO::UDSEntry & entry)1095 void NFSProtocol::completeInvalidUDSEntry(KIO::UDSEntry &entry)
1096 {
1097     entry.replace(KIO::UDSEntry::UDS_SIZE, 0);      // dummy size
1098     entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, S_IFMT - 1);
1099     entry.replace(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
1100     // The UDS_USER and UDS_GROUP must be string values.  It would be possible
1101     // to look up appropriate values as in completeUDSEntry() above, but it seems
1102     // pointless to go to that trouble for an unusable invalid entry.
1103     entry.replace(KIO::UDSEntry::UDS_USER, QString::fromLatin1("root"));
1104     entry.replace(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1("root"));
1105 }
1106 
1107 
1108 // This uses KIO::UDSEntry::fastInsert() and so must only be called with
1109 // a blank UDSEntry or one where only UDS_NAME has been filled in.
createVirtualDirEntry(UDSEntry & entry)1110 void NFSProtocol::createVirtualDirEntry(UDSEntry& entry)
1111 {
1112     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0);   // dummy size
1113     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
1114     entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory");
1115     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
1116     entry.fastInsert(KIO::UDSEntry::UDS_USER, QString::fromLatin1("root"));
1117     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QString::fromLatin1("root"));
1118 }
1119 
1120 #include "kio_nfs.moc"
1121