diff --git a/CMakeLists.txt b/CMakeLists.txt index d78458ed..029e7dea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,192 +1,198 @@ cmake_minimum_required(VERSION 3.0) # KDE Applications Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "19") set (KDE_APPLICATIONS_VERSION_MINOR "03") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") project(kio-extras VERSION ${KDE_APPLICATIONS_VERSION}) include(FeatureSummary) set(QT_MIN_VERSION "5.7.0") set(KF5_MIN_VERSION "5.48.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS DBus Network Widgets Svg) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG QUIET) set_package_properties(Qt5Test PROPERTIES PURPOSE "Required for tests" TYPE OPTIONAL ) add_feature_info("Qt5Test" Qt5Test_FOUND "Required for building tests") if (NOT Qt5Test_FOUND) set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") endif() find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Archive Config ConfigWidgets CoreAddons DBusAddons DocTools DNSSD IconThemes I18n KIO Solid Bookmarks GuiAddons ) # As this is the check used for linkage, only require it in the same location... if (UNIX) find_package(KF5Pty ${KF5_MIN_VERSION} REQUIRED) endif() include(CheckIncludeFile) include(CMakePackageConfigHelpers) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMMarkNonGuiExecutable) include(ECMMarkAsTest) include(ECMOptionalAddSubdirectory) include(ECMQtDeclareLoggingCategory) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) find_package(KF5Activities QUIET) set_package_properties(KF5Activities PROPERTIES PURPOSE "Provides the activities:/ kioslave and fileitem plugin." TYPE OPTIONAL ) find_package(Phonon4Qt5 4.6.60 NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" PURPOSE "Required for the audio preview plugin" TYPE OPTIONAL) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(NOT WIN32) # we need a version of samba which has already smbc_set_context(), Alex set(SAMBA_REQUIRE_SMBC_SET_CONTEXT TRUE) set(SAMBA_REQUIRE_SMBC_OPTION_SET TRUE) find_package(Samba) set_package_properties(Samba PROPERTIES DESCRIPTION "the SMB client library, a version with smbc_set_context() and smbc_option_set()" URL "http://www.samba.org" TYPE OPTIONAL PURPOSE "Needed to build the SMB kioslave" ) endif() find_package(libssh 0.7.0 MODULE) set_package_properties(libssh PROPERTIES DESCRIPTION "the SSH library with SFTP support" URL "http://www.libssh.org/" TYPE OPTIONAL PURPOSE "Needed to build the SFTP kioslave" ) find_package(Mtp) set_package_properties(Mtp PROPERTIES DESCRIPTION "the MTP library" URL "http://libmtp.sourceforge.net/" TYPE OPTIONAL PURPOSE "Needed to build the MTP kioslave" ) check_include_file(utime.h HAVE_UTIME_H) # ECM's KDECompilerSettings.cmake should take care of enabling supporting on # 32bit architectures. # Thorw a fatal error if off_t isn't >=64bit to ensure that large files are working # as expected. # BUG: 165449 if(UNIX) check_cxx_source_compiles(" #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main() { return 0; } " OFFT_IS_64BIT) if(NOT OFFT_IS_64BIT) message(FATAL_ERROR "Large file support is not enabled.") endif() find_package(Gperf) set_package_properties(Gperf PROPERTIES TYPE OPTIONAL PURPOSE "Needed to build the man kioslave" ) find_package(KF5KHtml QUIET) set_package_properties(KF5KHtml PROPERTIES TYPE OPTIONAL PURPOSE "Needed to build the man kioslave" ) else() # FIXME: on windows we ignore support until trash gets integrated endif() add_subdirectory( doc ) add_subdirectory( about ) if(KF5Activities_FOUND) add_subdirectory( activities ) endif() add_subdirectory( bookmarks ) add_subdirectory( filter ) if(Phonon4Qt5_FOUND) add_subdirectory( kfileaudiopreview ) endif() add_subdirectory( info ) add_subdirectory( archive ) if(NOT WIN32) add_subdirectory( network ) endif() add_subdirectory( recentdocuments ) if (NOT WIN32) # does not compile: fish.cpp(41): fatal error C1083: Cannot open include file: 'sys/resource.h': No such file or directory # Used for getting the resource limit for closing all child process FDs. Could be completely replaced by fcloseall() if available for Unix or _fcloseall() for Windows, either conditionally on Q_OS_type or using a configure test. add_subdirectory( fish ) endif() add_subdirectory( thumbnail ) add_subdirectory( docfilter ) if (libssh_FOUND AND HAVE_UTIME_H) # does not compile on Windows: kio_sftp.cpp(28): fatal error C1083: Cannot open include file: 'utime.h': No such file or directory add_subdirectory(sftp) endif () add_subdirectory(settings) add_subdirectory( filenamesearch ) if (MTP_FOUND) add_subdirectory(mtp) endif() if(NOT WIN32) if(Gperf_FOUND AND KF5KHtml_FOUND) add_subdirectory( man ) endif() check_include_files(rpc/rpc.h HAVE_RPC_RPC_H) add_feature_info("NFS kioslave" HAVE_RPC_RPC_H "The RPC library is needed to build the NFS kioslave") if(HAVE_RPC_RPC_H) add_subdirectory( nfs ) endif() endif() +# KDNSSD before 5.54 suffers from a race condition in avahi's dbus API and +# ideally should not be used in ways that can deadlock a slave. +if(${KF5DNSSD_FOUND} AND ${KF5DNSSD_VERSION} VERSION_GREATER "5.54") + set(HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION TRUE) +endif() + if(SAMBA_FOUND) add_subdirectory(smb) endif() configure_file (config-runtime.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-runtime.h ) install( FILES kio-extras.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/config-runtime.h.cmake b/config-runtime.h.cmake index 9c8c60a4..2e8007c9 100644 --- a/config-runtime.h.cmake +++ b/config-runtime.h.cmake @@ -1,16 +1,19 @@ /* config-runtime.h. Generated by cmake from config-runtime.h.cmake */ /* Define to 1 if you have the `nice' function. */ #cmakedefine HAVE_NICE 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_SELECT_H 1 /* Number of bits in a file offset, on hosts where this is settable. */ #define _FILE_OFFSET_BITS 64 /* * Used for the "KDE" version string in generated man pages. * Using the definition from this project so as to be independent of Plasma. */ #define KDE_VERSION_STRING "@PROJECT_VERSION@" + +/* kdnssd with signal race protection */ +#cmakedefine HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION 1 diff --git a/smb/CMakeLists.txt b/smb/CMakeLists.txt index e5526720..28cdbee3 100644 --- a/smb/CMakeLists.txt +++ b/smb/CMakeLists.txt @@ -1,45 +1,58 @@ +add_feature_info("SMB DNS-SD Discovery" HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION + "Discover SMB hosts via DNS-SD/Avahi/Bonjour. KF5DNSSD >= 5.54 is required to support this.") + add_definitions(-DTRANSLATION_DOMAIN=\"kio5_smb\") include(CheckIncludeFile) set(CMAKE_AUTOMAKE ON) if(NOT WIN32) check_include_file(utime.h HAVE_UTIME_H) configure_file(config-smb.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-smb.h) set(kio_smb_PART_SRCS kio_smb.cpp kio_smb_auth.cpp kio_smb_browse.cpp kio_smb_config.cpp kio_smb_dir.cpp kio_smb_file.cpp kio_smb_internal.cpp kio_smb_mount.cpp ) include_directories(${SAMBA_INCLUDE_DIR}) add_library(kio_smb MODULE ${kio_smb_PART_SRCS}) - -target_link_libraries(kio_smb KF5::KIOCore KF5::I18n ${SAMBA_LIBRARIES} Qt5::Network) +target_link_libraries(kio_smb + KF5::KIOCore + KF5::I18n + ${SAMBA_LIBRARIES} + Qt5::Network + KF5::DNSSD) else() set(kio_smb_PART_SRCS kio_smb_win.cpp) add_library(kio_smb MODULE ${kio_smb_PART_SRCS}) -target_link_libraries(kio_smb KF5::KIOCore KF5::I18n mpr Qt5::Network) +target_link_libraries(kio_smb + KF5::KIOCore + KF5::I18n + mpr + Qt5::Network + KF5::DNSSD) endif() set_target_properties(kio_smb PROPERTIES OUTPUT_NAME "smb") install(TARGETS kio_smb DESTINATION ${PLUGIN_INSTALL_DIR}/kf5/kio) ########### install files ############### install( FILES smb-network.desktop DESTINATION ${DATA_INSTALL_DIR}/konqueror/dirtree/remote ) install( FILES smb.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) install( FILES smb-network.desktop DESTINATION ${DATA_INSTALL_DIR}/remoteview ) + diff --git a/smb/kio_smb.h b/smb/kio_smb.h index 209dba49..e2e69753 100644 --- a/smb/kio_smb.h +++ b/smb/kio_smb.h @@ -1,320 +1,321 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE // // File: kio_smb.h // // Abstract: The main kio slave class declaration. For convenience, // in concurrent devlopment, the implementation for this class // is separated into several .cpp files -- the file containing // the implementation should be noted in the comments for each // member function. // // Author(s): Matthew Peterson // //--------------------------------------------------------------------------- // // Copyright (c) 2000 Caldera Systems, Inc. // // 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.1 of the License, or // (at your option) any later version. // // 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 Lesser General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, please obtain // a copy from http://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// #ifndef KIO_SMB_H_INCLUDED #define KIO_SMB_H_INCLUDED #include //-------------- // KDE includes //-------------- #include #include //----------------------------- // Standard C library includes //----------------------------- #include #include #include #include #include #include #include #include #include #include //----------------------------- // Qt includes //----------------------------- #include #include #include //------------------------------- // Samba client library includes //------------------------------- extern "C" { #include } //--------------------------- // kio_smb internal includes //--------------------------- #include "kio_smb_internal.h" #define MAX_XFER_BUF_SIZE 65534 // Categorized logger Q_DECLARE_LOGGING_CATEGORY(KIO_SMB) using namespace KIO; //=========================================================================== class SMBSlave : public QObject, public KIO::SlaveBase { Q_OBJECT private: class SMBError { public: int kioErrorId; QString errorString; }; //--------------------------------------------------------------------- // please make sure your private data does not duplicate existing data //--------------------------------------------------------------------- bool m_initialized_smbc; /** * From Controlcenter */ QString m_default_user; // QString m_default_workgroup; //currently unused, Alex QString m_default_password; QString m_default_encoding; /** * we store the current url, it's needed for * callback authorization method */ SMBUrl m_current_url; /** * From Controlcenter, show SHARE$ or not */ // bool m_showHiddenShares; //currently unused, Alex /** * libsmbclient need global variables to store in, * else it crashes on exit next method after use cache_stat, * looks like gcc (C/C++) failure */ struct stat st; protected: //--------------------------------------------- // Authentication functions (kio_smb_auth.cpp) //--------------------------------------------- // (please prefix functions with auth) /** * Description : Initializes the libsmbclient * Return : true on success false with errno set on error */ bool auth_initialize_smbc(); bool checkPassword(SMBUrl &url); //--------------------------------------------- // Cache functions (kio_smb_auth.cpp) //--------------------------------------------- //Stat methods //----------------------------------------- // Browsing functions (kio_smb_browse.cpp) //----------------------------------------- // (please prefix functions with browse) /** * Description : Return a stat of given SMBUrl. Calls cache_stat and * pack it in UDSEntry. UDSEntry will not be cleared * Parameter : SMBUrl the url to stat * Return : cache_stat() return code */ int browse_stat_path(const SMBUrl& url, UDSEntry& udsentry); /** * Description : call smbc_stat and return stats of the url * Parameter : SMBUrl the url to stat * Return : stat* of the url * Note : it has some problems with stat in method, looks like * something leave(or removed) on the stack. If your * method segfault on returning try to change the stat* * variable */ int cache_stat( const SMBUrl& url, struct stat* st ); //--------------------------------------------- // Configuration functions (kio_smb_config.cpp) //--------------------------------------------- // (please prefix functions with config) //--------------------------------------- // Directory functions (kio_smb_dir.cpp) //--------------------------------------- // (please prefix functions with dir) //-------------------------------------- // File IO functions (kio_smb_file.cpp) //-------------------------------------- // (please prefix functions with file) //---------------------------- // Misc functions (this file) //---------------------------- /** * Description : correct a given URL * valid URL's are * * smb://[[domain;]user[:password]@]server[:port][/share[/path[/file]]] * smb:/[[domain;]user[:password]@][group/[server[/share[/path[/file]]]]] * domain = workgroup(domain) of the user * user = username * password = password of useraccount * group = workgroup(domain) of server * server = host to connect * share = a share of the server (host) * path = a path of the share * Parameter : QUrl the url to check * Return : new QUrl if it is corrected. else the same QUrl */ QUrl checkURL(const QUrl& kurl) const; void reportError(const SMBUrl& url, const int errNum); void reportWarning(const SMBUrl& url, const int errNum); public: //----------------------------------------------------------------------- // smbclient authentication callback (note that this is called by the // global ::auth_smbc_get_data() call. void auth_smbc_get_data(const char *server,const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen); //----------------------------------------------------------------------- // Overwritten functions from the base class that define the operation of // this slave. (See the base class headerfile slavebase.h for more // details) //----------------------------------------------------------------------- // Functions overwritten in kio_smb.cpp SMBSlave(const QByteArray& pool, const QByteArray& app); ~SMBSlave() override; // Functions overwritten in kio_smb_browse.cpp void listDir( const QUrl& url ) override; void stat( const QUrl& url ) override; // Functions overwritten in kio_smb_config.cpp void reparseConfiguration() override; // Functions overwritten in kio_smb_dir.cpp void copy( const QUrl& src, const QUrl &dest, int permissions, KIO::JobFlags flags ) override; void del( const QUrl& kurl, bool isfile) override; void mkdir( const QUrl& kurl, int permissions ) override; void rename( const QUrl& src, const QUrl& dest, KIO::JobFlags flags ) override; // Functions overwritten in kio_smb_file.cpp void get( const QUrl& kurl ) override; void put( const QUrl& kurl, int permissions, KIO::JobFlags flags ) override; void open( const QUrl& kurl, QIODevice::OpenMode mode ) override; void read( KIO::filesize_t bytesRequested ) override; void write( const QByteArray &fileData ) override; void seek( KIO::filesize_t offset ) override; void close() override; // Functions not implemented (yet) //virtual void setHost(const QString& host, int port, const QString& user, const QString& pass); //virtual void openConnection(); //virtual void closeConnection(); //virtual void slave_status(); void special( const QByteArray & ) override; protected: void virtual_hook(int id, void *data) override; private: SMBError errnumToKioError(const SMBUrl& url, const int errNum); void smbCopy(const QUrl& src, const QUrl &dest, int permissions, KIO::JobFlags flags); void smbCopyGet(const QUrl& src, const QUrl& dest, int permissions, KIO::JobFlags flags); void smbCopyPut(const QUrl& src, const QUrl& dest, int permissions, KIO::JobFlags flags); bool workaroundEEXIST(const int errNum) const; + void listDNSSD(UDSEntry &udsentry, const QUrl &url, const uint direntCount); void fileSystemFreeSpace(const QUrl &url); /** * Used in open(), read(), write(), and close() * FIXME Placing these in the private section above causes m_openUrl = kurl * to fail in SMBSlave::open. Need to find out why this is. */ int m_openFd; SMBUrl m_openUrl; const bool m_enableEEXISTWorkaround; /* Enables a workaround for some broken libsmbclient versions */ }; //========================================================================== // the global libsmbclient authentication callback function extern "C" { void auth_smbc_get_data(SMBCCTX * context, const char *server,const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen); } //=========================================================================== // Main slave entrypoint (see kio_smb.cpp) extern "C" { int kdemain( int argc, char **argv ); } #endif //#endif KIO_SMB_H_INCLUDED diff --git a/smb/kio_smb_browse.cpp b/smb/kio_smb_browse.cpp index 9f30ecc8..9f646cbb 100644 --- a/smb/kio_smb_browse.cpp +++ b/smb/kio_smb_browse.cpp @@ -1,539 +1,662 @@ ///////////////////////////////////////////////////////////////////////////// // // Project: SMB kioslave for KDE2 // // File: kio_smb_browse.cpp // // Abstract: member function implementations for SMBSlave that deal with // SMB browsing // // Author(s): Matthew Peterson // //--------------------------------------------------------------------------- // // Copyright (c) 2000 Caldera Systems, Inc. +// Copyright (c) 2018 Harald Sitter // // 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.1 of the License, or // (at your option) any later version. // // 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 Lesser General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, please obtain // a copy from http://www.gnu.org/copyleft/gpl.html // ///////////////////////////////////////////////////////////////////////////// -#include -#include - #include "kio_smb.h" #include "kio_smb_internal.h" + +#include +#include #include #include +#include + +#include +#include + +#include + using namespace KIO; int SMBSlave::cache_stat(const SMBUrl &url, struct stat* st ) { int cacheStatErr; int result = smbc_stat( url.toSmbcUrl(), st); if (result == 0){ cacheStatErr = 0; } else { cacheStatErr = errno; } qCDebug(KIO_SMB) << "size " << (KIO::filesize_t)st->st_size; return cacheStatErr; } //--------------------------------------------------------------------------- int SMBSlave::browse_stat_path(const SMBUrl& _url, UDSEntry& udsentry) { SMBUrl url = _url; int cacheStatErr = cache_stat(url, &st); if(cacheStatErr == 0) { if(!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) { qCDebug(KIO_SMB) << "mode: "<< st.st_mode; warning(i18n("%1:\n" "Unknown file type, neither directory or file.", url.toDisplayString())); return EINVAL; } udsentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, st.st_mode & S_IFMT); udsentry.insert(KIO::UDSEntry::UDS_SIZE, st.st_size); QString str; uid_t uid = st.st_uid; struct passwd *user = getpwuid( uid ); if ( user ) str = user->pw_name; else str = QString::number( uid ); udsentry.insert(KIO::UDSEntry::UDS_USER, str); gid_t gid = st.st_gid; struct group *grp = getgrgid( gid ); if ( grp ) str = grp->gr_name; else str = QString::number( gid ); udsentry.insert(KIO::UDSEntry::UDS_GROUP, str); udsentry.insert(KIO::UDSEntry::UDS_ACCESS, st.st_mode & 07777); udsentry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, st.st_mtime); udsentry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, st.st_atime); // No, st_ctime is not UDS_CREATION_TIME... } return cacheStatErr; } //=========================================================================== void SMBSlave::stat( const QUrl& kurl ) { qCDebug(KIO_SMB) << kurl; // make a valid URL QUrl url = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (url != kurl) { qCDebug(KIO_SMB) << "redirection " << url; redirection(url); finished(); return; } m_current_url = url; UDSEntry udsentry; // Set name udsentry.insert( KIO::UDSEntry::UDS_NAME, kurl.fileName() ); switch(m_current_url.getType()) { case SMBURLTYPE_UNKNOWN: error(ERR_MALFORMED_URL, url.toDisplayString()); return; case SMBURLTYPE_ENTIRE_NETWORK: case SMBURLTYPE_WORKGROUP_OR_SERVER: udsentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); break; case SMBURLTYPE_SHARE_OR_PATH: { int ret = browse_stat_path(m_current_url, udsentry); if (ret == EPERM || ret == EACCES || workaroundEEXIST(ret)) { SMBUrl smbUrl(url); if (checkPassword(smbUrl)) { redirection(smbUrl); finished(); } else { reportError(url, ret); } return; } else if (ret != 0) { qCDebug(KIO_SMB) << "stat() error" << ret << url; reportError(url, ret); return; } break; } default: qCDebug(KIO_SMB) << "UNKNOWN " << url; finished(); return; } statEntry(udsentry); finished(); } //=========================================================================== // TODO: complete checking QUrl SMBSlave::checkURL(const QUrl& kurl) const { qCDebug(KIO_SMB) << "checkURL " << kurl; QString surl = kurl.url(); //transform any links in the form smb:/ into smb:// if (surl.startsWith(QLatin1String("smb:/"))) { if (surl.length() == 5) { return QUrl("smb://"); } if (surl.at(5) != '/') { surl = "smb://" + surl.mid(5); qCDebug(KIO_SMB) << "checkURL return1 " << surl << " " << QUrl(surl); return QUrl(surl); } } if (surl == QLatin1String("smb://")) { return kurl; //unchanged } // smb:// normally have no userinfo // we must redirect ourself to remove the username and password if (surl.contains('@') && !surl.contains("smb://")) { QUrl url(kurl); url.setPath('/'+kurl.url().right( kurl.url().length()-kurl.url().indexOf('@') -1)); QString userinfo = kurl.url().mid(5, kurl.url().indexOf('@')-5); if(userinfo.contains(':')) { url.setUserName(userinfo.left(userinfo.indexOf(':'))); url.setPassword(userinfo.right(userinfo.length()-userinfo.indexOf(':')-1)); } else { url.setUserName(userinfo); } qCDebug(KIO_SMB) << "checkURL return2 " << url; return url; } //if there's a valid host, don't have an empty path QUrl url(kurl); if (url.path().isEmpty()) url.setPath("/"); qCDebug(KIO_SMB) << "checkURL return3 " << url; return url; } SMBSlave::SMBError SMBSlave::errnumToKioError(const SMBUrl &url, const int errNum) { qCDebug(KIO_SMB) << "errNum" << errNum; switch(errNum) { case ENOENT: if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK) return SMBError{ ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.") }; else return SMBError{ ERR_DOES_NOT_EXIST, url.toDisplayString() }; #ifdef ENOMEDIUM case ENOMEDIUM: return SMBError{ ERR_SLAVE_DEFINED, i18n("No media in device for %1", url.toDisplayString()) }; #endif #ifdef EHOSTDOWN case EHOSTDOWN: #endif case ECONNREFUSED: return SMBError{ ERR_SLAVE_DEFINED, i18n("Could not connect to host for %1", url.toDisplayString()) }; break; case ENOTDIR: return SMBError{ ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString() }; case EFAULT: case EINVAL: return SMBError{ ERR_DOES_NOT_EXIST, url.toDisplayString() }; case EPERM: case EACCES: return SMBError{ ERR_ACCESS_DENIED, url.toDisplayString() }; case EIO: case ENETUNREACH: if ( url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER ) return SMBError{ ERR_SLAVE_DEFINED, i18n("Error while connecting to server responsible for %1", url.toDisplayString()) }; else return SMBError{ ERR_CONNECTION_BROKEN, url.toDisplayString() }; case ENOMEM: return SMBError{ ERR_OUT_OF_MEMORY, url.toDisplayString() }; case ENODEV: return SMBError{ ERR_SLAVE_DEFINED, i18n("Share could not be found on given server") }; case EBADF: return SMBError{ ERR_INTERNAL, i18n("Bad file descriptor") }; case ETIMEDOUT: return SMBError{ ERR_SERVER_TIMEOUT, url.host() }; case ENOTEMPTY: return SMBError{ ERR_CANNOT_RMDIR, url.toDisplayString() }; #ifdef ENOTUNIQ case ENOTUNIQ: return SMBError{ ERR_SLAVE_DEFINED, i18n("The given name could not be resolved to a unique server. " "Make sure your network is setup without any name conflicts " "between names used by Windows and by UNIX name resolution." ) }; #endif case 0: // success return SMBError{ ERR_INTERNAL, i18n("libsmbclient reported an error, but did not specify " "what the problem is. This might indicate a severe problem " "with your network - but also might indicate a problem with " "libsmbclient.\n" "If you want to help us, please provide a tcpdump of the " "network interface while you try to browse (be aware that " "it might contain private data, so do not post it if you are " "unsure about that - you can send it privately to the developers " "if they ask for it)") }; default: return SMBError{ ERR_INTERNAL, i18n("Unknown error condition in stat: %1", QString::fromLocal8Bit( strerror(errNum))) }; } } void SMBSlave::reportError(const SMBUrl& url, const int errNum) { const SMBError smbErr = errnumToKioError(url, errNum); error(smbErr.kioErrorId, smbErr.errorString); } void SMBSlave::reportWarning(const SMBUrl& url, const int errNum) { const SMBError smbErr = errnumToKioError(url, errNum); const QString errorString = buildErrorString(smbErr.kioErrorId, smbErr.errorString); warning(xi18n("Error occurred while trying to access %1%2", url.url(), errorString)); } //=========================================================================== void SMBSlave::listDir( const QUrl& kurl ) { qCDebug(KIO_SMB) << kurl; int errNum = 0; // check (correct) URL QUrl url = checkURL(kurl); // if URL is not valid we have to redirect to correct URL if (url != kurl) { redirection(url); finished(); return; } m_current_url = kurl; int dirfd; struct smbc_dirent *dirp = nullptr; UDSEntry udsentry; bool dir_is_root = true; dirfd = smbc_opendir( m_current_url.toSmbcUrl() ); if (dirfd > 0){ errNum = 0; } else { errNum = errno; } qCDebug(KIO_SMB) << "open " << m_current_url.toSmbcUrl() << " " << m_current_url.getType() << " " << dirfd; if(dirfd >= 0) { + uint direntCount = 0; do { qCDebug(KIO_SMB) << "smbc_readdir "; dirp = smbc_readdir(dirfd); if(dirp == nullptr) break; + ++direntCount; + // Set name QString udsName; const QString dirpName = QString::fromUtf8( dirp->name ); // We cannot trust dirp->commentlen has it might be with or without the NUL character // See KDE bug #111430 and Samba bug #3030 const QString comment = QString::fromUtf8( dirp->comment ); if ( dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_WORKGROUP ) { udsName = dirpName.toLower(); udsName[0] = dirpName.at( 0 ).toUpper(); if ( !comment.isEmpty() && dirp->smbc_type == SMBC_SERVER ) udsName += " (" + comment + ')'; } else udsName = dirpName; qCDebug(KIO_SMB) << "dirp->name " << dirp->name << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type; udsentry.insert( KIO::UDSEntry::UDS_NAME, udsName ); // Mark all administrative shares, e.g ADMIN$, as hidden. #197903 if (dirpName.endsWith(QLatin1Char('$'))) { //qCDebug(KIO_SMB) << dirpName << "marked as hidden"; udsentry.insert(KIO::UDSEntry::UDS_HIDDEN, 1); } if (udsName == ".") { // Skip the "." entry // Mind the way m_current_url is handled in the loop } else if (udsName == "..") { dir_is_root = false; // fprintf(stderr,"----------- hide: -%s-\n",dirp->name); // do nothing and hide the hidden shares } else if (dirp->smbc_type == SMBC_FILE || dirp->smbc_type == SMBC_DIR) { // Set stat information m_current_url.addPath(dirpName); const int statErr = browse_stat_path(m_current_url, udsentry); if (statErr) { if (statErr == ENOENT || statErr == ENOTDIR) { reportWarning(m_current_url, statErr); } } else { // Call base class to list entry listEntry(udsentry); } m_current_url.cd(".."); } else if(dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_FILE_SHARE) { // Set type udsentry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR ); if (dirp->smbc_type == SMBC_SERVER) { udsentry.insert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); // QString workgroup = m_current_url.host().toUpper(); QUrl u("smb://"); u.setHost(dirpName); // when libsmbclient knows // u = QString("smb://%1?WORKGROUP=%2").arg(dirpName).arg(workgroup.toUpper()); qCDebug(KIO_SMB) << "list item " << u; udsentry.insert(KIO::UDSEntry::UDS_URL, u.url()); udsentry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-server")); } else udsentry.insert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); // Call base class to list entry listEntry(udsentry); } else if(dirp->smbc_type == SMBC_WORKGROUP) { // Set type udsentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); // Set permissions udsentry.insert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); udsentry.insert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-workgroup")); // QString workgroup = m_current_url.host().toUpper(); QUrl u("smb://"); u.setHost(dirpName); udsentry.insert(KIO::UDSEntry::UDS_URL, u.url()); // Call base class to list entry listEntry(udsentry); } else { qCDebug(KIO_SMB) << "SMBC_UNKNOWN :" << dirpName; // TODO: we don't handle SMBC_IPC_SHARE, SMBC_PRINTER_SHARE // SMBC_LINK, SMBC_COMMS_SHARE //SlaveBase::error(ERR_INTERNAL, TEXT_UNSUPPORTED_FILE_TYPE); // continue; } udsentry.clear(); } while (dirp); // checked already in the head + listDNSSD(udsentry, url, direntCount); + if (dir_is_root) { udsentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); udsentry.insert(KIO::UDSEntry::UDS_NAME, "."); udsentry.insert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH)); } else { udsentry.insert(KIO::UDSEntry::UDS_NAME, "."); const int statErr = browse_stat_path(m_current_url, udsentry); if (statErr) { if (statErr == ENOENT || statErr == ENOTDIR) { reportWarning(m_current_url, statErr); } // Create a default UDSEntry if we could not stat the actual directory udsentry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); udsentry.insert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)); } } listEntry(udsentry); udsentry.clear(); // clean up smbc_closedir(dirfd); } else { if (errNum == EPERM || errNum == EACCES || workaroundEEXIST(errNum)) { if (checkPassword(m_current_url)) { redirection( m_current_url ); finished(); return; } } reportError(m_current_url, errNum); return; } finished(); } +void SMBSlave::listDNSSD(UDSEntry &udsentry, const QUrl &url, const uint direntCount) +{ + // Certain versions of KDNSSD suffer from signal races which can easily + // deadlock the slave. +#ifndef HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION + return; +#endif // HAVE_KDNSSD_WITH_SIGNAL_RACE_PROTECTION + + // This entire method act as fallback logic iff SMB discovery is not working + // (for example when using a protocol version that doesn't have discovery). + // As such we can return if entries were discovered or the URL is not '/' + auto normalizedUrl = url.adjusted(QUrl::NormalizePathSegments); + if (direntCount > 0 || !normalizedUrl.path().isEmpty()) { + return; + } + + // Slaves have no event loop, start one for the poll. + // KDNSSD has an internal timeout which may trigger if this takes too long + // so in theory this should not ever be able to get stuck. + // The eventloop runs until the discovery is finished. The finished slot + // will quit it. + QList services; + QEventLoop e; + KDNSSD::ServiceBrowser browser(QStringLiteral("_smb._tcp")); + connect(&browser, &KDNSSD::ServiceBrowser::serviceAdded, + this, [&services](KDNSSD::RemoteService::Ptr service){ + qCDebug(KIO_SMB) << "DNSSD added:" + << service->serviceName() + << service->type() + << service->domain() + << service->hostName() + << service->port(); + // Manual contains check. We need to use the == of the underlying + // objects, not the pointers. The same service may have >1 + // RemoteService* instances representing it, so the == impl of + // RemoteService::Ptr is useless here. + for (const auto &it : services) { + if (*service == *it) { + return; + } + } + // Schedule resolution of hostname. We'll later call resolve + // which will block until the resolution is done. This basically + // gives us a head start. + service->resolveAsync(); + services.append(service); + }); + connect(&browser, &KDNSSD::ServiceBrowser::serviceRemoved, + this, [&services](KDNSSD::RemoteService::Ptr service){ + qCDebug(KIO_SMB) << "DNSSD removed:" + << service->serviceName() + << service->type() + << service->domain() + << service->hostName() + << service->port(); + services.removeAll(service); + }); + connect(&browser, &KDNSSD::ServiceBrowser::finished, + this, [&]() { + browser.disconnect(); // Stop sending anything, we'll exit here. + // Resolution still requires an event loop. So, let the resolutions + // finish and then quit the loop. Services that fail resolution + // get dropped since we won't be able to access them properly. + for (auto it = services.begin(); it != services.end(); ++it) { + auto service = *it; + if (!service->resolve()) { + qCWarning(KIO_SMB) << "Failed to resolve DNSSD service" + << service->serviceName(); + it = services.erase(it); + } + } + e.quit(); + }); + browser.startBrowse(); + e.exec(); + + for (const auto &service : services) { + udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, service->serviceName()); + + udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)); + + // TODO: it may be better to resolve the host to an ip address. dnssd + // being able to find a service doesn't mean name resolution is + // properly set up for its domain. So, we may not be able to resolve + // this without help from avahi. OTOH KDNSSD doesn't have API for this + // and from a platform POV we should probably assume that if avahi + // is functional it is also set up as resolution provider. + // Given the plugin design on glibc's libnss however I am not sure + // that assumption will be true all the time. ~sitter, 2018 + QUrl u(QStringLiteral("smb://")); + u.setHost(service->hostName()); + if (service->port() > 0 && service->port() != 445 /* default smb */) { + u.setPort(service->port()); + } + + udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url()); + udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, + QStringLiteral("application/x-smb-server")); + + listEntry(udsentry); + udsentry.clear(); + } + + // NOTE: workgroups cannot be properly resolved because libsmbclient + // seems to lack the appropriate API to pull this data out of netbios. + // Netbios is also not working on IPv6 and its replacement (LLMNR) + // doesn't support the concept of workgroups. +} + void SMBSlave::fileSystemFreeSpace(const QUrl& url) { qCDebug(KIO_SMB) << url; // Avoid crashing in smbc_fstatvfs below when // requesting free space for smb:// which doesn't // make sense to do to begin with if (url.host().isEmpty()) { error(KIO::ERR_COULD_NOT_STAT, url.url()); return; } SMBUrl smbcUrl = url; int handle = smbc_opendir(smbcUrl.toSmbcUrl()); if (handle < 0) { error(KIO::ERR_COULD_NOT_STAT, url.url()); return; } struct statvfs dirStat; memset(&dirStat, 0, sizeof(struct statvfs)); int err = smbc_fstatvfs(handle, &dirStat); smbc_closedir(handle); if (err < 0) { error(KIO::ERR_COULD_NOT_STAT, url.url()); return; } KIO::filesize_t blockSize; if (dirStat.f_frsize != 0) { blockSize = dirStat.f_frsize; } else { blockSize = dirStat.f_bsize; } setMetaData("total", QString::number(blockSize * dirStat.f_blocks)); setMetaData("available", QString::number(blockSize * dirStat.f_bavail)); finished(); } bool SMBSlave::workaroundEEXIST(const int errNum) const { return (errNum == EEXIST) && m_enableEEXISTWorkaround; }