kio_smb_browse.cpp 27.8 KB
Newer Older
1

2
/////////////////////////////////////////////////////////////////////////////
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
3
//
4 5 6
// Project:     SMB kioslave for KDE2
//
// File:        kio_smb_browse.cpp
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
7 8
//
// Abstract:    member function implementations for SMBSlave that deal with
9 10 11 12 13
//              SMB browsing
//
// Author(s):   Matthew Peterson <mpeterson@caldera.com>
//
//---------------------------------------------------------------------------
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
14 15
//
// Copyright (c) 2000  Caldera Systems, Inc.
16
// Copyright (c) 2018-2020  Harald Sitter <sitter@kde.org>
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
17 18
//
// This program is free software; you can redistribute it and/or modify it
19
// under the terms of the GNU General Public License as published by the
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
20 21 22 23 24 25 26 27 28
// 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
29
//     along with this program; see the file COPYING.  If not, please obtain
30
//     a copy from https://www.gnu.org/copyleft/gpl.html
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
31
//
32
/////////////////////////////////////////////////////////////////////////////
Stephan Kulow's avatar
Stephan Kulow committed
33

34
#include "kio_smb.h"
35
#include "smburl.h"
36 37

#include <DNSSD/RemoteService>
38
#include <DNSSD/ServiceBrowser>
39
#include <KIO/Job>
40
#include <KLocalizedString>
41

42
#include <QEventLoop>
43
#include <QTimer>
44
#include <QUrlQuery>
45

46
#include <grp.h>
47
#include <pwd.h>
48

49
#include "dnssddiscoverer.h"
50 51
#include "wsdiscoverer.h"
#include <config-runtime.h>
52

53 54
using namespace KIO;

55
int SMBSlave::cache_stat(const SMBUrl &url, struct stat *st)
56
{
57
    int cacheStatErr;
58 59
    int result = smbc_stat(url.toSmbcUrl(), st);
    if (result == 0) {
60 61 62 63
        cacheStatErr = 0;
    } else {
        cacheStatErr = errno;
    }
64
    qCDebug(KIO_SMB_LOG) << "size " << static_cast<KIO::filesize_t>(st->st_size);
65
    return cacheStatErr;
66 67
}

68
int SMBSlave::browse_stat_path(const SMBUrl &url, UDSEntry &udsentry)
69
{
70 71
    int cacheStatErr = cache_stat(url, &st);
    if (cacheStatErr == 0) {
72 73 74 75 76 77 78 79 80 81 82
        return statToUDSEntry(url, st, udsentry);
    }

    return cacheStatErr;
}

int SMBSlave::statToUDSEntry(const QUrl &url, const struct stat &st, KIO::UDSEntry &udsentry)
{
    if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) {
        qCDebug(KIO_SMB_LOG) << "mode: "<< st.st_mode;
        warning(i18n("%1:\n"
83 84
                     "Unknown file type, neither directory or file.",
                     url.toDisplayString()));
85 86
        return EINVAL;
    }
87

88 89 90 91 92 93 94 95 96 97 98 99
    if (!S_ISDIR(st.st_mode)) {
        // Awkwardly documented at
        //    https://www.samba.org/samba/docs/using_samba/ch08.html
        // libsmb_stat.c assigns special meaning to +x permissions
        // (obviously only on files, all dirs are +x so this hacky representation
        //  wouldn't work!):
        // - S_IXUSR = DOS archive: This file has been touched since the last DOS backup was performed on it.
        // - S_IXGRP = DOS system: This file has a specific purpose required by the operating system.
        // - S_IXOTH = DOS hidden: This file has been marked to be invisible to the user, unless the operating system is explicitly set to show it.
        // Only hiding has backing through KIO right now.
        if (st.st_mode & S_IXOTH) { // DOS hidden
            udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, true);
100 101 102
        }
    }

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
    // UID and GID **must** not be mapped. The values returned by libsmbclient are
    // simply the getuid/getgid of the process. They mean absolutely nothing.
    // Also see libsmb_stat.c.
    // Related: https://bugs.kde.org/show_bug.cgi?id=212801

    // POSIX Access mode must not be mapped either!
    // It's meaningless for smb shares and downright disadvantagous.
    // The mode attributes outside the ones used and document above are
    // useless. The only one actively set is readonlyness.
    //
    // BUT the READONLY attribute does nothing on NT systems:
    // https://support.microsoft.com/en-us/help/326549/you-cannot-view-or-change-the-read-only-or-the-system-attributes-of-fo
    // The Read-only and System attributes is only used by Windows Explorer to determine
    // whether the folder is a special folder, such as a system folder that has its view
    // customized by Windows (for example, My Documents, Favorites, Fonts, Downloaded Program Files),
    // or a folder that you customized by using the Customize tab of the folder's Properties dialog box.
    //
    // As such respecting it on a KIO level is actually wrong as it doesn't indicate actual
    // readonlyness since the 90s and causes us to show readonly UI states when in fact
    // the directory is perfectly writable.
    // https://bugs.kde.org/show_bug.cgi?id=414482
    //
    // Should we ever want to parse desktop.ini like we do .directory we'd only want to when a
    // dir is readonly as per the above microsoft support article.
    // Also see:
    // https://docs.microsoft.com/en-us/windows/win32/shell/how-to-customize-folders-with-desktop-ini

    udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, st.st_mode & S_IFMT);
    udsentry.fastInsert(KIO::UDSEntry::UDS_SIZE, st.st_size);
    udsentry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, st.st_mtime);
    udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, st.st_atime);
    // No, st_ctime is not UDS_CREATION_TIME...

    return 0;
137 138
}

139
void SMBSlave::stat(const QUrl &kurl)
140
{
Méven Car's avatar
Méven Car committed
141
    qCDebug(KIO_SMB_LOG) << kurl;
142
    // make a valid URL
143
    QUrl url = checkURL(kurl);
Frank Schwanz's avatar
Frank Schwanz committed
144

145
    // if URL is not valid we have to redirect to correct URL
146
    if (url != kurl) {
Méven Car's avatar
Méven Car committed
147
        qCDebug(KIO_SMB_LOG) << "redirection " << url;
148
        redirection(url);
Stephan Kulow's avatar
Stephan Kulow committed
149
        finished();
150 151
        return;
    }
152

153
    m_current_url = url;
154

155
    UDSEntry udsentry;
156
    // Set name
157
    udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, kurl.fileName());
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
158

159
    switch (m_current_url.getType()) {
160
    case SMBURLTYPE_UNKNOWN:
161
        error(ERR_MALFORMED_URL, url.toDisplayString());
162
        return;
163 164 165

    case SMBURLTYPE_ENTIRE_NETWORK:
    case SMBURLTYPE_WORKGROUP_OR_SERVER:
166
        udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
167 168 169
        statEntry(udsentry);
        finished();
        return;
170

171 172
    case SMBURLTYPE_SHARE_OR_PATH: {
        int ret = browse_stat_path(m_current_url, udsentry);
173

174 175
        if (ret == EPERM || ret == EACCES || workaroundEEXIST(ret)) {
            SMBUrl smbUrl(url);
176

177 178 179 180 181
            const int passwordError = checkPassword(smbUrl);
            if (passwordError == KJob::NoError) {
                redirection(smbUrl);
                finished();
            } else if (passwordError == KIO::ERR_USER_CANCELED) {
182
                reportError(url, ret);
183 184
            } else {
                error(passwordError, url.toString());
185
            }
186

187 188 189 190
            return;
        } else if (ret != 0) {
            qCDebug(KIO_SMB_LOG) << "stat() error" << ret << url;
            reportError(url, ret);
191
            return;
192
        }
193 194 195 196 197

        statEntry(udsentry);
        finished();
        return;
    }
198 199
    }

200
    qCDebug(KIO_SMB_LOG) << "UNKNOWN " << url;
201
    finished();
202 203
}

204 205 206 207 208
// TODO: complete checking <-- what does that even mean?
// TODO: why is this not part of SMBUrl or at the very least URL validation should
//    be 100% shared between this and SMBUrl. Notably SMBUrl has code that looks
//    to do a similar thing but is much less complete.
QUrl SMBSlave::checkURL(const QUrl &kurl_) const
209
{
Harald Sitter's avatar
Harald Sitter committed
210
    qCDebug(KIO_SMB_LOG) << "checkURL " << kurl_;
211 212 213 214 215 216 217 218 219 220

    QUrl kurl(kurl_);
    // We treat cifs as an alias but need to translate it to smb.
    // https://bugs.kde.org/show_bug.cgi?id=327295
    // It's not IANA registered and also libsmbc internally expects
    // smb URIs so we do very broadly coerce cifs to smb.
    // Also see SMBUrl.
    if (kurl.scheme() == "cifs") {
        kurl.setScheme("smb");
    }
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

    // For WS-Discovered hosts we assume they'll respond to DNSSD names on .local but
    // they may only respond to llmnr/netbios names. Transparently fall back.
    //
    // Desktop linuxes tend to have llmnr disabled, by contrast win10 has dnssd enabled,
    // so chances are we'll be able to find a host.local more reliably.
    // Attempt to resolve foo.local natively, if that works use it, otherwise default to
    // the presumed LLMNR/netbios name found during discovery.
    // This should then yield reasonable results with any combination of WSD/DNSSD/LLMNR support.
    // - WSD+Avahi (on linux)
    // - WSD+Win10 (i.e. dnssd + llmnr)
    // - WSD+CrappyNAS (e.g. llmnr or netbios only)
    //
    // NB: smbc has no way to resolve a name without also triggering auth etc.: we must
    //   rely on the system's ability to resolve DNSSD for this check.
    const QLatin1String wsdSuffix(".kio-discovery-wsd");
237 238
    if (kurl.host().endsWith(wsdSuffix)) {
        QString host = kurl.host();
239 240 241 242 243 244 245 246 247 248 249 250
        host.chop(wsdSuffix.size());
        const QString dnssd(host + ".local");
        auto dnssdHost = QHostInfo::fromName(dnssd);
        if (dnssdHost.error() == QHostInfo::NoError) {
            qCDebug(KIO_SMB_LOG) << "Resolved DNSSD name:" << dnssd;
            host = dnssd;
        } else {
            qCDebug(KIO_SMB_LOG) << "Failed to resolve DNSSD name:" << dnssd;
            qCDebug(KIO_SMB_LOG) << "Falling back to LLMNR name:" << host;
        }
        kurl.setHost(host);
    }
251

252
    QString surl = kurl.url();
253
    // transform any links in the form smb:/ into smb://
John Layt's avatar
John Layt committed
254
    if (surl.startsWith(QLatin1String("smb:/"))) {
David Edmundson's avatar
David Edmundson committed
255 256 257
        if (surl.length() == 5) {
            return QUrl("smb://");
        }
258 259
        if (surl.at(5) != '/') {
            surl = "smb://" + surl.mid(5);
Méven Car's avatar
Méven Car committed
260
            qCDebug(KIO_SMB_LOG) << "checkURL return1 " << surl << " " << QUrl(surl);
261
            return QUrl(surl);
262
        }
263
    }
David Edmundson's avatar
David Edmundson committed
264
    if (surl == QLatin1String("smb://")) {
265
        return kurl; // unchanged
David Edmundson's avatar
David Edmundson committed
266
    }
267

David Edmundson's avatar
David Edmundson committed
268
    // smb:// normally have no userinfo
269 270
    // we must redirect ourself to remove the username and password
    if (surl.contains('@') && !surl.contains("smb://")) {
271
        QUrl url(kurl);
272 273 274
        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(':')) {
275
            url.setUserName(userinfo.left(userinfo.indexOf(':')));
276
            url.setPassword(userinfo.right(userinfo.length() - userinfo.indexOf(':') - 1));
277
        } else {
278
            url.setUserName(userinfo);
279
        }
Méven Car's avatar
Méven Car committed
280
        qCDebug(KIO_SMB_LOG) << "checkURL return2 " << url;
281
        return url;
282
    }
283

284
    // if there's a valid host, don't have an empty path
285
    QUrl url(kurl);
286 287

    if (url.path().isEmpty())
288
        url.setPath("/");
289

Méven Car's avatar
Méven Car committed
290
    qCDebug(KIO_SMB_LOG) << "checkURL return3 " << url;
291
    return url;
292
}
Frank Schwanz's avatar
Frank Schwanz committed
293

294
SMBSlave::SMBError SMBSlave::errnumToKioError(const SMBUrl &url, const int errNum)
295
{
Méven Car's avatar
Méven Car committed
296
    qCDebug(KIO_SMB_LOG) << "errNum" << errNum;
297

298
    switch (errNum) {
299
    case ENOENT:
300
        if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK)
301
            return SMBError {ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.")};
302
        else
303
            return SMBError {ERR_DOES_NOT_EXIST, url.toDisplayString()};
Andy Fawcett's avatar
Andy Fawcett committed
304
#ifdef ENOMEDIUM
305
    case ENOMEDIUM:
306
        return SMBError {ERR_SLAVE_DEFINED, i18n("No media in device for %1", url.toDisplayString())};
Andy Fawcett's avatar
Andy Fawcett committed
307
#endif
308 309 310
#ifdef EHOSTDOWN
    case EHOSTDOWN:
#endif
311
    case ECONNREFUSED:
312
        return SMBError {ERR_SLAVE_DEFINED, i18n("Could not connect to host for %1", url.toDisplayString())};
313
    case ENOTDIR:
314
        return SMBError {ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString()};
315
    case EFAULT:
316
    case EINVAL:
317
        return SMBError {ERR_DOES_NOT_EXIST, url.toDisplayString()};
318 319
    case EPERM:
    case EACCES:
320
        return SMBError {ERR_ACCESS_DENIED, url.toDisplayString()};
321
    case EIO:
322
    case ENETUNREACH:
323 324
        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())};
325
        else
326
            return SMBError {ERR_CONNECTION_BROKEN, url.toDisplayString()};
327
    case ENOMEM:
328
        return SMBError {ERR_OUT_OF_MEMORY, url.toDisplayString()};
329
    case ENODEV:
330
        return SMBError {ERR_SLAVE_DEFINED, i18n("Share could not be found on given server")};
331
    case EBADF:
332
        return SMBError {ERR_INTERNAL, i18n("Bad file descriptor")};
333
    case ETIMEDOUT:
334
        return SMBError {ERR_SERVER_TIMEOUT, url.host()};
335
    case ENOTEMPTY:
336
        return SMBError {ERR_CANNOT_RMDIR, url.toDisplayString()};
337
#ifdef ENOTUNIQ
338
    case ENOTUNIQ:
339 340 341 342
        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.")};
343
#endif
344
    case ECONNABORTED:
345
        return SMBError {ERR_CONNECTION_BROKEN, url.host()};
346
    case EHOSTUNREACH:
347
        return SMBError {ERR_CANNOT_CONNECT, i18nc("@info:status smb failed to reach the server (e.g. server offline or network failure). %1 is an ip address or hostname", "%1: Host unreachable", url.host())};
348
    case 0: // success
349 350 351 352 353 354 355 356 357 358
        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)")};
359
    default:
360
        return SMBError {ERR_INTERNAL, i18n("Unknown error condition in stat: %1", QString::fromLocal8Bit(strerror(errNum)))};
361 362
    }
}
Frank Schwanz's avatar
Frank Schwanz committed
363

364
void SMBSlave::reportError(const SMBUrl &url, const int errNum)
365 366 367 368 369 370
{
    const SMBError smbErr = errnumToKioError(url, errNum);

    error(smbErr.kioErrorId, smbErr.errorString);
}

371
void SMBSlave::reportWarning(const SMBUrl &url, const int errNum)
Michal Malý's avatar
Michal Malý committed
372
{
373
    const SMBError smbErr = errnumToKioError(url, errNum);
374
    const QString errorString = buildErrorString(smbErr.kioErrorId, smbErr.errorString);
375

376
    warning(xi18n("Error occurred while trying to access %1<nl/>%2", url.url(), errorString));
Michal Malý's avatar
Michal Malý committed
377 378
}

379
void SMBSlave::listDir(const QUrl &kurl)
380
{
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
    qCDebug(KIO_SMB_LOG) << 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;

    struct smbc_dirent *dirp = nullptr;
    UDSEntry udsentry;
    bool dir_is_root = true;

    int dirfd = smbc_opendir(m_current_url.toSmbcUrl());
    if (dirfd > 0) {
        errNum = 0;
    } else {
        errNum = errno;
    }

406 407 408 409
    qCDebug(KIO_SMB_LOG) << "open " << m_current_url.toSmbcUrl()
                         << "url-type:" << m_current_url.getType()
                         << "dirfd:" << dirfd
                         << "errNum:" << errNum;
410
    if (dirfd >= 0) {
411 412 413 414 415 416 417 418 419 420
#ifdef HAVE_READDIRPLUS2
           // readdirplus2 improves performance by giving us a stat without separate call (Samba>=4.12)
           while (const struct libsmb_file_info *fileInfo = smbc_readdirplus2(dirfd, &st)) {
               const QString name = QString::fromUtf8(fileInfo->name);
               if (name == ".") {
                   continue;
               } else if (name == "..") {
                   dir_is_root = false;
                   continue;
               }
Harald Sitter's avatar
style++  
Harald Sitter committed
421
               udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, name);
422 423 424 425

               m_current_url.addPath(name);
               statToUDSEntry(m_current_url, st, udsentry); // won't produce useful error
               listEntry(udsentry);
Harald Sitter's avatar
Harald Sitter committed
426
               m_current_url.cdUp();
427 428 429 430 431

               udsentry.clear();
           }
#endif // HAVE_READDIRPLUS2

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
        uint direntCount = 0;
        do {
            qCDebug(KIO_SMB_LOG) << "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_LOG) << "dirp->name " <<  dirp->name  << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type;

            udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, udsName);
            udsentry.fastInsert(KIO::UDSEntry::UDS_COMMENT, QString::fromUtf8(dirp->comment));

            // Mark all administrative shares, e.g ADMIN$, as hidden. #197903
            if (dirpName.endsWith(QLatin1Char('$'))) {
                // qCDebug(KIO_SMB_LOG) << dirpName << "marked as hidden";
                udsentry.fastInsert(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
473
#if !defined(HAVE_READDIRPLUS2)
Harald Sitter's avatar
Harald Sitter committed
474
            } else if (dirp->smbc_type == SMBC_FILE || dirp->smbc_type == SMBC_DIR) {
475 476 477 478 479 480 481 482 483 484 485
                // 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);
                }
486
                m_current_url.cdUp();
487
#endif // HAVE_READDIRPLUS2
Harald Sitter's avatar
Harald Sitter committed
488
            } else if (dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_FILE_SHARE) {
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
                // Set type
                udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);

                if (dirp->smbc_type == SMBC_SERVER) {
                    udsentry.fastInsert(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_LOG) << "list item " << u;
                    udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url());

                    udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-server"));
                } else
                    udsentry.fastInsert(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.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);

                // Set permissions
                udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH));

                udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-workgroup"));

                // QString workgroup = m_current_url.host().toUpper();
                QUrl u("smb://");
                u.setHost(dirpName);
522 523 524 525 526 527 528 529 530 531
                if (!u.isValid()) {
                    // In the event that the workgroup contains bad characters, put it in a query instead.
                    // This is transparently handled by SMBUrl when we get this as input again.
                    // Also see documentation there.
                    // https://bugs.kde.org/show_bug.cgi?id=204423
                    u.setHost(QString());
                    QUrlQuery q;
                    q.addQueryItem("kio-workgroup", dirpName);
                    u.setQuery(q);
                }
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
                udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url());

                // Call base class to list entry
                listEntry(udsentry);
            } else {
                qCDebug(KIO_SMB_LOG) << "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

        // Run service discovery if the path is root. This augments
        // "native" results from libsmbclient.
        auto normalizedUrl = url.adjusted(QUrl::NormalizePathSegments);
        if (normalizedUrl.path().isEmpty()) {
            qCDebug(KIO_SMB_LOG) << "Trying modern discovery (dnssd/wsdiscovery)";

            QEventLoop e;

            UDSEntryList list;
555
            QStringList discoveredNames;
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584

            const auto flushEntries = [this, &list]() {
                if (list.isEmpty()) {
                    return;
                }
                listEntries(list);
                list.clear();
            };

            const auto quitLoop = [&e, &flushEntries]() {
                flushEntries();
                e.quit();
            };

            // Since slavebase has no eventloop it wont publish results
            // on a timer, since we do not know how long our discovery
            // will take this is super meh because we may appear
            // stuck for a while. Implement our own listing system
            // based on QTimer to mitigate.
            QTimer sendTimer;
            sendTimer.setInterval(300);
            connect(&sendTimer, &QTimer::timeout, this, flushEntries);
            sendTimer.start();

            DNSSDDiscoverer d;
            WSDiscoverer w;

            const QList<Discoverer *> discoverers {&d, &w};

585 586 587 588 589 590 591
            auto appendDiscovery = [&](const Discovery::Ptr &discovery) {
                if (discoveredNames.contains(discovery->udsName())) {
                    return;
                }
                discoveredNames << discovery->udsName();
                list.append(discovery->toEntry());
            };
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640

            auto maybeFinished = [&] { // finishes if all discoveries finished
                bool allFinished = true;
                for (auto discoverer : discoverers) {
                    allFinished = allFinished && discoverer->isFinished();
                }
                if (allFinished) {
                    quitLoop();
                }
            };

            connect(&d, &DNSSDDiscoverer::newDiscovery, this, appendDiscovery);
            connect(&w, &WSDiscoverer::newDiscovery, this, appendDiscovery);

            connect(&d, &DNSSDDiscoverer::finished, this, maybeFinished);
            connect(&w, &WSDiscoverer::finished, this, maybeFinished);

            d.start();
            w.start();

            QTimer::singleShot(16000, &e, quitLoop); // max execution time!
            e.exec();

            qCDebug(KIO_SMB_LOG) << "Modern discovery finished.";
        }

        if (dir_is_root) {
            udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
            udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
            udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH));
        } else {
            udsentry.fastInsert(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.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
                udsentry.fastInsert(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)) {
641
            qCDebug(KIO_SMB_LOG) << "trying checkPassword";
642 643 644 645 646
            const int passwordError = checkPassword(m_current_url);
            if (passwordError == KJob::NoError) {
                redirection(m_current_url);
                finished();
            } else if (passwordError == KIO::ERR_USER_CANCELED) {
647
                qCDebug(KIO_SMB_LOG) << "user cancelled password request";
648 649
                reportError(m_current_url, errNum);
            } else {
650
                qCDebug(KIO_SMB_LOG) << "generic password error:" << passwordError;
651 652 653 654 655 656
                error(passwordError, m_current_url.toString());
            }

            return;
        }

657
        qCDebug(KIO_SMB_LOG) << "reporting generic error:" << errNum;
658 659 660 661 662
        reportError(m_current_url, errNum);
        return;
    }

    finished();
663
}
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
664

665
void SMBSlave::fileSystemFreeSpace(const QUrl &url)
666
{
Méven Car's avatar
Méven Car committed
667
    qCDebug(KIO_SMB_LOG) << url;
668

669 670 671 672
    // 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()) {
Laurent Montel's avatar
Laurent Montel committed
673
        error(KIO::ERR_CANNOT_STAT, url.url());
674 675 676
        return;
    }

677 678
    SMBUrl smbcUrl = url;

679 680
    struct statvfs dirStat {
    };
681
    memset(&dirStat, 0, sizeof(struct statvfs));
682
    const int err = smbc_statvfs(smbcUrl.toSmbcUrl().data(), &dirStat);
683
    if (err < 0) {
684 685
        error(KIO::ERR_CANNOT_STAT, url.url());
        return;
686 687
    }

688 689 690 691 692 693 694 695 696 697 698 699
    // libsmb_stat.c has very awkward conditional branching that results
    // in data meaning different things based on context:
    // A samba host with unix extensions has f_frsize==0 and the f_bsize is
    // the actual block size. Any other server (such as windows) has a non-zero
    // f_frsize denoting the amount of sectors in a block and the f_bsize is
    // the amount of bytes in a sector. As such frsize*bsize is the actual
    // block size.
    // This was also broken in different ways throughout history, so depending
    // on the specific libsmbc versions the milage will vary. 4.7 to 4.11 are
    // at least behaving as described though.
    // https://bugs.kde.org/show_bug.cgi?id=298801
    const auto frames = (dirStat.f_frsize == 0) ? 1 : dirStat.f_frsize;
700
    const auto blockSize = dirStat.f_bsize * frames;
701 702 703 704 705 706
    // Further more on older versions of samba f_bavail may not be set...
    const auto total = blockSize * dirStat.f_blocks;
    const auto available = blockSize * ((dirStat.f_bavail != 0) ? dirStat.f_bavail : dirStat.f_bfree);

    setMetaData("total", QString::number(total));
    setMetaData("available", QString::number(available));
707 708 709 710

    finished();
}

711 712 713 714
bool SMBSlave::workaroundEEXIST(const int errNum) const
{
    return (errNum == EEXIST) && m_enableEEXISTWorkaround;
}