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