diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 1d2b8636..26617373 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,139 +1,140 @@ if(POLICY CMP0028) cmake_policy(SET CMP0028 OLD) endif() remove_definitions(-DQT_NO_CAST_FROM_ASCII) include(ECMAddTests) add_subdirectory(http) add_subdirectory(kcookiejar) find_package(Qt5Widgets REQUIRED) ########### unittests ############### find_package(Qt5Concurrent ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) ecm_add_tests( kacltest.cpp listdirtest.cpp kmountpointtest.cpp upurltest.cpp dataprotocoltest.cpp jobtest.cpp jobremotetest.cpp kfileitemtest.cpp kprotocolinfotest.cpp ktcpsockettest.cpp globaltest.cpp mkpathjobtest.cpp threadtest.cpp udsentrytest.cpp udsentry_benchmark.cpp kcoredirlister_benchmark.cpp deletejobtest.cpp urlutiltest.cpp batchrenamejobtest.cpp + ksambasharetest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) target_link_libraries(threadtest Qt5::Concurrent) ecm_add_test( http_jobtest.cpp httpserver_p.cpp TEST_NAME http_jobtest NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) if(UNIX) ecm_add_tests( klocalsockettest.cpp klocalsocketservertest.cpp privilegejobtest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) endif() if (TARGET KF5::KIOGui) ecm_add_tests( favicontest.cpp NAME_PREFIX "kiogui-" LINK_LIBRARIES KF5::KIOCore KF5::KIOGui Qt5::Test ) target_link_libraries(favicontest Qt5::Concurrent) endif() if (TARGET KF5::KIOWidgets) ecm_add_tests( clipboardupdatertest.cpp dropjobtest.cpp kdynamicjobtrackernowidgetstest.cpp krununittest.cpp kdirlistertest.cpp kdirmodeltest.cpp kfileitemactionstest.cpp fileundomanagertest.cpp kurifiltertest.cpp kurlcompletiontest.cpp jobguitest.cpp pastetest.cpp accessmanagertest.cpp kurifiltersearchprovideractionstest.cpp NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test Qt5::DBus ) set_target_properties(krununittest PROPERTIES COMPILE_FLAGS "-DCMAKE_INSTALL_FULL_LIBEXECDIR_KF5=\"\\\"${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}\\\"\"") # Same as accessmanagertest, but using QNetworkAccessManager, to make sure we # behave the same ecm_add_test( accessmanagertest.cpp TEST_NAME accessmanagertest-qnam NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(accessmanagertest-qnam PROPERTIES COMPILE_FLAGS "-DUSE_QNAM") # Same as kurlcompletiontest, but with immediate return, and results posted by thread later ecm_add_test( kurlcompletiontest.cpp TEST_NAME kurlcompletiontest-nowait NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT") endif() if (TARGET KF5::KIOFileWidgets) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) include_directories(${CMAKE_SOURCE_DIR}/src/filewidgets ${CMAKE_BINARY_DIR}/src/filewidgets) ecm_add_tests( kurlnavigatortest.cpp kurlcomboboxtest.cpp kdiroperatortest.cpp kfilewidgettest.cpp kfilecustomdialogtest.cpp knewfilemenutest.cpp kfilecopytomenutest.cpp kfileplacesmodeltest.cpp kfileplacesviewtest.cpp kurlrequestertest.cpp NAME_PREFIX "kiofilewidgets-" LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n ) set_tests_properties(kiofilewidgets-kfileplacesmodeltest PROPERTIES RUN_SERIAL TRUE) set_tests_properties(kiofilewidgets-kfileplacesviewtest PROPERTIES RUN_SERIAL TRUE) endif() # this should be done by cmake, see bug 371721 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Qt5Core_VERSION VERSION_GREATER 5.8.0) set_property(TARGET jobtest APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_BINARY_DIR}/src/core/moc_predefs.h) endif() diff --git a/autotests/ksambasharetest.cpp b/autotests/ksambasharetest.cpp new file mode 100644 index 00000000..8eda7fa3 --- /dev/null +++ b/autotests/ksambasharetest.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ksambasharetest.h" + +#include +#include + +#include + +QTEST_MAIN(KSambaShareTest) + +Q_DECLARE_METATYPE(KSambaShareData::UserShareError) + +void KSambaShareTest::initTestCase() +{ + qRegisterMetaType(); +} + +void KSambaShareTest::testAcl() +{ + QFETCH(QString, acl); + QFETCH(KSambaShareData::UserShareError, result); + + KSambaShareData data; + + QCOMPARE(data.setAcl(acl), result); +} + +void KSambaShareTest::testAcl_data() +{ + QTest::addColumn("acl"); + QTest::addColumn("result"); + + QTest::newRow("one entry") << QStringLiteral("Everyone:r") << KSambaShareData::UserShareAclOk; + QTest::newRow("one entry, trailing comma") << QStringLiteral("Everyone:r,") << KSambaShareData::UserShareAclOk; + + QTest::newRow("one entry with hostname") << QStringLiteral("Host\\Someone:r") << KSambaShareData::UserShareAclOk; + + QTest::newRow("space in hostname") << QStringLiteral("Everyone:r,Unix User\\Someone:f,") << KSambaShareData::UserShareAclOk; + + QTest::newRow("garbage") << QStringLiteral("Garbage") << KSambaShareData::UserShareAclInvalid; +} + +void KSambaShareTest::testOwnAcl() +{ + const auto shares = KSambaShare::instance()->shareNames(); + + for (const QString &share : shares) { + KSambaShareData shareData = KSambaShare::instance()->getShareByName(share); + + // KSambaShare reads acl from net usershare info's "usershare_acl" field with no validation + QCOMPARE(shareData.setAcl(shareData.acl()), KSambaShareData::UserShareAclOk); + } +} diff --git a/autotests/ksambasharetest.h b/autotests/ksambasharetest.h new file mode 100644 index 00000000..46654527 --- /dev/null +++ b/autotests/ksambasharetest.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KSAMBASHARETEST_H +#define KSAMBASHARETEST_H + +#include + +class KSambaShareTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testAcl(); + void testAcl_data(); + void testOwnAcl(); +}; + +#endif // KSAMBASHARETEST_H + diff --git a/src/core/ksambashare.cpp b/src/core/ksambashare.cpp index bb4fa7a3..f8de3311 100644 --- a/src/core/ksambashare.cpp +++ b/src/core/ksambashare.cpp @@ -1,518 +1,518 @@ /* This file is part of the KDE project Copyright (c) 2004 Jan Schaefer Copyright 2010 Rodrigo Belem This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ksambashare.h" #include "ksambashare_p.h" #include "ksambasharedata.h" #include "ksambasharedata_p.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_CORE_SAMBASHARE) Q_LOGGING_CATEGORY(KIO_CORE_SAMBASHARE, "kf5.kio.core.sambashare", QtWarningMsg) // Default smb.conf locations // sorted by priority, most priority first static const char *const DefaultSambaConfigFilePathList[] = { "/etc/samba/smb.conf", "/etc/smb.conf", "/usr/local/etc/smb.conf", "/usr/local/samba/lib/smb.conf", "/usr/samba/lib/smb.conf", "/usr/lib/smb.conf", "/usr/local/lib/smb.conf" }; static const int DefaultSambaConfigFilePathListSize = sizeof(DefaultSambaConfigFilePathList) / sizeof(char *); KSambaSharePrivate::KSambaSharePrivate(KSambaShare *parent) : q_ptr(parent) , data() , smbConf() , userSharePath() , skipUserShare(false) { setUserSharePath(); findSmbConf(); sync(); } KSambaSharePrivate::~KSambaSharePrivate() { } bool KSambaSharePrivate::isSambaInstalled() { if (QFile::exists(QStringLiteral("/usr/sbin/smbd")) || QFile::exists(QStringLiteral("/usr/local/sbin/smbd"))) { return true; } //qDebug() << "Samba is not installed!"; return false; } // Try to find the samba config file path // in several well-known paths bool KSambaSharePrivate::findSmbConf() { for (int i = 0; i < DefaultSambaConfigFilePathListSize; ++i) { const QString filePath(DefaultSambaConfigFilePathList[i]); if (QFile::exists(filePath)) { smbConf = filePath; return true; } } qCDebug(KIO_CORE_SAMBASHARE) << "KSambaShare: Could not find smb.conf!"; return false; } void KSambaSharePrivate::setUserSharePath() { const QString rawString = testparmParamValue(QStringLiteral("usershare path")); const QFileInfo fileInfo(rawString); if (fileInfo.isDir()) { userSharePath = rawString; } } int KSambaSharePrivate::runProcess(const QString &progName, const QStringList &args, QByteArray &stdOut, QByteArray &stdErr) { QProcess process; process.setProcessChannelMode(QProcess::SeparateChannels); process.start(progName, args); //TODO: make it async in future process.waitForFinished(); stdOut = process.readAllStandardOutput(); stdErr = process.readAllStandardError(); return process.exitCode(); } QString KSambaSharePrivate::testparmParamValue(const QString ¶meterName) { if (!isSambaInstalled()) { return QString(); } QStringList args; QByteArray stdErr; QByteArray stdOut; args << QStringLiteral("-d0") << QStringLiteral("-s") << QStringLiteral("--parameter-name") << parameterName; runProcess(QStringLiteral("testparm"), args, stdOut, stdErr); //TODO: parse and process error messages. // create a parser for the error output and // send error message somewhere if (!stdErr.isEmpty()) { QList err; err << stdErr.trimmed().split('\n'); if ((err.count() == 2) && err.at(0).startsWith("Load smb config files from") && err.at(1).startsWith("Loaded services file OK.")) { //qDebug() << "Running testparm" << args; } else { qCWarning(KIO_CORE) << "We got some errors while running testparm" << stdErr; } } if (!stdOut.isEmpty()) { return QString::fromLocal8Bit(stdOut.trimmed()); } return QString(); } QByteArray KSambaSharePrivate::getNetUserShareInfo() { if (skipUserShare || !isSambaInstalled()) { return QByteArray(); } QStringList args; QByteArray stdOut; QByteArray stdErr; args << QStringLiteral("usershare") << QStringLiteral("info"); runProcess(QStringLiteral("net"), args, stdOut, stdErr); if (!stdErr.isEmpty()) { if (stdErr.contains("You do not have permission to create a usershare")) { skipUserShare = true; } else if (stdErr.contains("usershares are currently disabled")) { skipUserShare = true; } else { //TODO: parse and process other error messages. // create a parser for the error output and // send error message somewhere qCWarning(KIO_CORE) << "We got some errors while running 'net usershare info'"; qCWarning(KIO_CORE) << stdErr; } } return stdOut; } QStringList KSambaSharePrivate::shareNames() const { return data.keys(); } QStringList KSambaSharePrivate::sharedDirs() const { QStringList dirs; QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (!dirs.contains(i.value().path())) { dirs << i.value().path(); } } return dirs; } KSambaShareData KSambaSharePrivate::getShareByName(const QString &shareName) const { return data.value(shareName); } QList KSambaSharePrivate::getSharesByPath(const QString &path) const { QList shares; QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (i.value().path() == path) { shares << i.value(); } } return shares; } bool KSambaSharePrivate::isShareNameValid(const QString &name) const { // Samba forbidden chars const QRegExp notToMatchRx(QStringLiteral("[%<>*\?|/\\+=;:\",]")); return (notToMatchRx.indexIn(name) == -1); } bool KSambaSharePrivate::isDirectoryShared(const QString &path) const { QMap::ConstIterator i; for (i = data.constBegin(); i != data.constEnd(); ++i) { if (i.value().path() == path) { return true; } } return false; } bool KSambaSharePrivate::isShareNameAvailable(const QString &name) const { // Samba does not allow to name a share with a user name registered in the system return (!KUser::allUserNames().contains(name) || !data.contains(name)); } KSambaShareData::UserShareError KSambaSharePrivate::isPathValid(const QString &path) const { QFileInfo pathInfo = path; if (!pathInfo.exists()) { return KSambaShareData::UserSharePathNotExists; } if (!pathInfo.isDir()) { return KSambaShareData::UserSharePathNotDirectory; } if (pathInfo.isRelative()) { if (pathInfo.makeAbsolute()) { return KSambaShareData::UserSharePathNotAbsolute; } } // TODO: check if the user is root if (KSambaSharePrivate::testparmParamValue(QStringLiteral("usershare owner only")) == QLatin1String("Yes")) { if (!pathInfo.permission(QFile::ReadUser | QFile::WriteUser)) { return KSambaShareData::UserSharePathNotAllowed; } } return KSambaShareData::UserSharePathOk; } KSambaShareData::UserShareError KSambaSharePrivate::isAclValid(const QString &acl) const { - const QRegExp aclRx(QStringLiteral("(?:(?:(\\w+\\s*)\\\\|)(\\w+\\s*):([fFrRd]{1})(?:,|))*")); + const QRegExp aclRx(QStringLiteral("(?:(?:(\\w(\\w|\\s)*)\\\\|)(\\w+\\s*):([fFrRd]{1})(?:,|))*")); // TODO: check if user is a valid smb user return aclRx.exactMatch(acl) ? KSambaShareData::UserShareAclOk : KSambaShareData::UserShareAclInvalid; } KSambaShareData::UserShareError KSambaSharePrivate::guestsAllowed(const KSambaShareData::GuestPermission &guestok) const { if (guestok == KSambaShareData::GuestsAllowed) { if (KSambaSharePrivate::testparmParamValue(QStringLiteral("usershare allow guests")) == QLatin1String("No")) { return KSambaShareData::UserShareGuestsNotAllowed; } } return KSambaShareData::UserShareGuestsOk; } KSambaShareData::UserShareError KSambaSharePrivate::add(const KSambaShareData &shareData) { // TODO: // * check for usershare max shares if (!isSambaInstalled()) { return KSambaShareData::UserShareSystemError; } QStringList args; QByteArray stdOut; QByteArray stdErr; if (data.contains(shareData.name())) { if (data.value(shareData.name()).path() != shareData.path()) { return KSambaShareData::UserShareNameInUse; } } else { // It needs to be added here, otherwise another instance of KSambaShareDataPrivate // will be created and added to data. data.insert(shareData.name(), shareData); } QString guestok = QStringLiteral("guest_ok=%1").arg( (shareData.guestPermission() == KSambaShareData::GuestsNotAllowed) ? QStringLiteral("n") : QStringLiteral("y")); args << QStringLiteral("usershare") << QStringLiteral("add") << shareData.name() << shareData.path() << shareData.comment() << shareData.acl() << guestok; int ret = runProcess(QStringLiteral("net"), args, stdOut, stdErr); //TODO: parse and process error messages. if (!stdErr.isEmpty()) { // create a parser for the error output and // send error message somewhere qCWarning(KIO_CORE) << "We got some errors while running 'net usershare add'" << args; qCWarning(KIO_CORE) << stdErr; } return (ret == 0) ? KSambaShareData::UserShareOk : KSambaShareData::UserShareSystemError; } KSambaShareData::UserShareError KSambaSharePrivate::remove(const KSambaShareData &shareData) const { if (!isSambaInstalled()) { return KSambaShareData::UserShareSystemError; } QStringList args; if (!data.contains(shareData.name())) { return KSambaShareData::UserShareNameInvalid; } args << QStringLiteral("usershare") << QStringLiteral("delete") << shareData.name(); int result = QProcess::execute(QStringLiteral("net"), args); return (result == 0) ? KSambaShareData::UserShareOk : KSambaShareData::UserShareSystemError; } bool KSambaSharePrivate::sync() { const QRegExp headerRx(QString::fromLatin1("^\\s*\\[" "([^%<>*\?|/\\+=;:\",]+)" "\\]")); const QRegExp OptValRx(QString::fromLatin1("^\\s*([\\w\\d\\s]+)" "=" "(.*)$")); QTextStream stream(getNetUserShareInfo()); QString currentShare; QStringList shareList; while (!stream.atEnd()) { const QString line = stream.readLine().trimmed(); if (headerRx.exactMatch(line)) { currentShare = headerRx.cap(1).trimmed(); shareList << currentShare; if (!data.contains(currentShare)) { KSambaShareData shareData; shareData.dd->name = currentShare; data.insert(currentShare, shareData); } } else if (OptValRx.exactMatch(line)) { const QString key = OptValRx.cap(1).trimmed(); const QString value = OptValRx.cap(2).trimmed(); KSambaShareData shareData = getShareByName(currentShare); if (key == QLatin1String("path")) { shareData.dd->path = value; } else if (key == QLatin1String("comment")) { shareData.dd->comment = value; } else if (key == QLatin1String("usershare_acl")) { shareData.dd->acl = value; } else if (key == QLatin1String("guest_ok")) { shareData.dd->guestPermission = value; } else { qCWarning(KIO_CORE) << "Something nasty happen while parsing 'net usershare info'" << "share:" << currentShare << "key:" << key; } } else if (line.trimmed().isEmpty()) { continue; } else { return false; } } QMutableMapIterator i(data); while (i.hasNext()) { i.next(); if (!shareList.contains(i.key())) { i.remove(); } } return true; } void KSambaSharePrivate::_k_slotFileChange(const QString &path) { if (path != userSharePath) { return; } sync(); //qDebug() << "path changed:" << path; Q_Q(KSambaShare); emit q->changed(); } KSambaShare::KSambaShare() : QObject(nullptr) , d_ptr(new KSambaSharePrivate(this)) { Q_D(const KSambaShare); if (!d->userSharePath.isEmpty() && QFileInfo::exists(d->userSharePath)) { KDirWatch::self()->addDir(d->userSharePath, KDirWatch::WatchFiles); connect(KDirWatch::self(), SIGNAL(dirty(QString)), this, SLOT(_k_slotFileChange(QString))); } } KSambaShare::~KSambaShare() { Q_D(const KSambaShare); if (KDirWatch::exists() && KDirWatch::self()->contains(d->userSharePath)) { KDirWatch::self()->removeDir(d->userSharePath); } delete d_ptr; } #ifndef KIOCORE_NO_DEPRECATED QString KSambaShare::smbConfPath() const { Q_D(const KSambaShare); return d->smbConf; } #endif bool KSambaShare::isDirectoryShared(const QString &path) const { Q_D(const KSambaShare); return d->isDirectoryShared(path); } bool KSambaShare::isShareNameAvailable(const QString &name) const { Q_D(const KSambaShare); return d->isShareNameValid(name) && d->isShareNameAvailable(name); } QStringList KSambaShare::shareNames() const { Q_D(const KSambaShare); return d->shareNames(); } QStringList KSambaShare::sharedDirectories() const { Q_D(const KSambaShare); return d->sharedDirs(); } KSambaShareData KSambaShare::getShareByName(const QString &name) const { Q_D(const KSambaShare); return d->getShareByName(name); } QList KSambaShare::getSharesByPath(const QString &path) const { Q_D(const KSambaShare); return d->getSharesByPath(path); } class KSambaShareSingleton { public: KSambaShare instance; }; Q_GLOBAL_STATIC(KSambaShareSingleton, _instance) KSambaShare *KSambaShare::instance() { return &_instance()->instance; } #include "moc_ksambashare.cpp"