kio_smb_browse.cpp 27.3 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  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
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
30 31
//     a copy from http://www.gnu.org/copyleft/gpl.html
//
32
/////////////////////////////////////////////////////////////////////////////
Stephan Kulow's avatar
Stephan Kulow committed
33

34 35
#include "kio_smb.h"
#include "kio_smb_internal.h"
36 37 38

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

42 43 44 45 46 47 48
#include <QEventLoop>

#include <pwd.h>
#include <grp.h>

#include <config-runtime.h>

49 50
using namespace KIO;

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

64
//---------------------------------------------------------------------------
65
int SMBSlave::browse_stat_path(const SMBUrl& _url, UDSEntry& udsentry)
66
{
67
   SMBUrl url = _url;
68

69 70
   int cacheStatErr = cache_stat(url, &st);
   if(cacheStatErr == 0)
71
   {
72
      if(!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode))
73
      {
74
         qCDebug(KIO_SMB) << "mode: "<< st.st_mode;
75
         warning(i18n("%1:\n"
76
                      "Unknown file type, neither directory or file.", url.toDisplayString()));
77
         return EINVAL;
78
      }
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
      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);
          }
      }

Harald Sitter's avatar
Harald Sitter committed
95 96 97 98
      // 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
Frank Schwanz's avatar
Frank Schwanz committed
99

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
      // 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);
123 124
      udsentry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, st.st_mtime);
      udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, st.st_atime);
125
      // No, st_ctime is not UDS_CREATION_TIME...
126
   }
127

128
   return cacheStatErr;
129 130 131
}

//===========================================================================
132
void SMBSlave::stat( const QUrl& kurl )
133
{
134
    qCDebug(KIO_SMB) << kurl;
135
    // make a valid URL
136
    QUrl url = checkURL(kurl);
Frank Schwanz's avatar
Frank Schwanz committed
137

138 139 140
    // if URL is not valid we have to redirect to correct URL
    if (url != kurl)
    {
141
        qCDebug(KIO_SMB) << "redirection " << url;
142
        redirection(url);
Stephan Kulow's avatar
Stephan Kulow committed
143
        finished();
144 145
        return;
    }
146

147
    m_current_url = url;
148

149 150
    UDSEntry    udsentry;
    // Set name
151
    udsentry.fastInsert( KIO::UDSEntry::UDS_NAME, kurl.fileName() );
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
152

153
    switch(m_current_url.getType())
154 155
    {
    case SMBURLTYPE_UNKNOWN:
156
        error(ERR_MALFORMED_URL, url.toDisplayString());
157
        return;
158 159 160

    case SMBURLTYPE_ENTIRE_NETWORK:
    case SMBURLTYPE_WORKGROUP_OR_SERVER:
161
        udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
162
        break;
163 164

    case SMBURLTYPE_SHARE_OR_PATH:
165 166 167
        {
            int ret = browse_stat_path(m_current_url, udsentry);

168
            if (ret == EPERM || ret == EACCES || workaroundEEXIST(ret))
169 170 171
            {
                SMBUrl smbUrl(url);

172 173
                const int passwordError = checkPassword(smbUrl);
                if (passwordError == KJob::NoError)
174 175 176 177
                {
                    redirection(smbUrl);
                    finished();
                }
178
                else if (passwordError == KIO::ERR_USER_CANCELED)
179 180 181
                {
                    reportError(url, ret);
                }
182 183 184 185
                else
                {
                    error(passwordError, url.toString());
                }
186 187 188 189 190 191 192 193 194

                return;
            }
            else if (ret != 0)
            {
                qCDebug(KIO_SMB) << "stat() error" << ret << url;
                reportError(url, ret);
                return;
            }
195 196
            break;
        }
197
    default:
198
        qCDebug(KIO_SMB) << "UNKNOWN " << url;
199 200
        finished();
        return;
201 202
    }

203 204
    statEntry(udsentry);
    finished();
205 206
}

207
//===========================================================================
208 209 210 211 212
// 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
213
{
214 215 216 217 218 219 220 221 222 223 224 225
    qCDebug(KIO_SMB) << "checkURL " << kurl_;

    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");
    }

226
    QString surl = kurl.url();
David Edmundson's avatar
David Edmundson committed
227
    //transform any links in the form smb:/ into smb://
John Layt's avatar
John Layt committed
228
    if (surl.startsWith(QLatin1String("smb:/"))) {
David Edmundson's avatar
David Edmundson committed
229 230 231
        if (surl.length() == 5) {
            return QUrl("smb://");
        }
232 233
        if (surl.at(5) != '/') {
            surl = "smb://" + surl.mid(5);
234 235
            qCDebug(KIO_SMB) << "checkURL return1 " << surl << " " << QUrl(surl);
            return QUrl(surl);
236
        }
237
    }
David Edmundson's avatar
David Edmundson committed
238 239 240
    if (surl == QLatin1String("smb://")) {
        return kurl; //unchanged
    }
241

David Edmundson's avatar
David Edmundson committed
242
    // smb:// normally have no userinfo
243 244
    // we must redirect ourself to remove the username and password
    if (surl.contains('@') && !surl.contains("smb://")) {
245
        QUrl url(kurl);
Thorsten Roeder's avatar
Thorsten Roeder committed
246
        url.setPath('/'+kurl.url().right( kurl.url().length()-kurl.url().indexOf('@') -1));
Stephan Kulow's avatar
Stephan Kulow committed
247
        QString userinfo = kurl.url().mid(5, kurl.url().indexOf('@')-5);
248
        if(userinfo.contains(':'))  {
249 250
            url.setUserName(userinfo.left(userinfo.indexOf(':')));
            url.setPassword(userinfo.right(userinfo.length()-userinfo.indexOf(':')-1));
251
        } else {
252
            url.setUserName(userinfo);
253
        }
254
        qCDebug(KIO_SMB) << "checkURL return2 " << url;
255
        return url;
256
    }
257

David Edmundson's avatar
David Edmundson committed
258
    //if there's a valid host, don't have an empty path
259
    QUrl url(kurl);
260 261

    if (url.path().isEmpty())
262
        url.setPath("/");
263

264
    qCDebug(KIO_SMB) << "checkURL return3 " << url;
265
    return url;
266
}
Frank Schwanz's avatar
Frank Schwanz committed
267

268
SMBSlave::SMBError SMBSlave::errnumToKioError(const SMBUrl &url, const int errNum)
269
{
270
    qCDebug(KIO_SMB) << "errNum" << errNum;
271 272

    switch(errNum)
273 274
    {
    case ENOENT:
275
        if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK)
276
            return SMBError{ ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.") };
277
        else
278
            return SMBError{ ERR_DOES_NOT_EXIST, url.toDisplayString() };
Andy Fawcett's avatar
Andy Fawcett committed
279
#ifdef ENOMEDIUM
280
    case ENOMEDIUM:
281
        return SMBError{ ERR_SLAVE_DEFINED, i18n("No media in device for %1", url.toDisplayString()) };
Andy Fawcett's avatar
Andy Fawcett committed
282
#endif
283 284 285
#ifdef EHOSTDOWN
    case EHOSTDOWN:
#endif
286
    case ECONNREFUSED:
287
        return SMBError{ ERR_SLAVE_DEFINED, i18n("Could not connect to host for %1", url.toDisplayString()) };
288
        break;
289
    case ENOTDIR:
290
        return SMBError{ ERR_CANNOT_ENTER_DIRECTORY, url.toDisplayString() };
291
    case EFAULT:
292
    case EINVAL:
293
        return SMBError{ ERR_DOES_NOT_EXIST, url.toDisplayString() };
294 295
    case EPERM:
    case EACCES:
296
        return SMBError{ ERR_ACCESS_DENIED, url.toDisplayString() };
297
    case EIO:
298
    case ENETUNREACH:
299
        if ( url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER )
300
            return SMBError{ ERR_SLAVE_DEFINED, i18n("Error while connecting to server responsible for %1", url.toDisplayString()) };
301
        else
302
            return SMBError{ ERR_CONNECTION_BROKEN, url.toDisplayString() };
303
    case ENOMEM:
304
        return SMBError{ ERR_OUT_OF_MEMORY, url.toDisplayString() };
305
    case ENODEV:
306
        return SMBError{ ERR_SLAVE_DEFINED, i18n("Share could not be found on given server") };
307
    case EBADF:
308
        return SMBError{ ERR_INTERNAL, i18n("Bad file descriptor") };
309
    case ETIMEDOUT:
310
        return SMBError{ ERR_SERVER_TIMEOUT, url.host() };
311 312
    case ENOTEMPTY:
        return SMBError{ ERR_CANNOT_RMDIR, url.toDisplayString() };
313
#ifdef ENOTUNIQ
314
    case ENOTUNIQ:
315 316 317
        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." ) };
318
#endif
319 320
    case ECONNABORTED:
        return SMBError{ ERR_CONNECTION_BROKEN, url.host() };
321
    case 0: // success
322 323 324 325 326 327 328 329 330
      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)") };
331
    default:
332
        return SMBError{ ERR_INTERNAL, i18n("Unknown error condition in stat: %1", QString::fromLocal8Bit( strerror(errNum))) };
333 334
    }
}
Frank Schwanz's avatar
Frank Schwanz committed
335

336 337 338 339 340 341 342
void SMBSlave::reportError(const SMBUrl& url, const int errNum)
{
    const SMBError smbErr = errnumToKioError(url, errNum);

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

Michal Malý's avatar
Michal Malý committed
343 344
void SMBSlave::reportWarning(const SMBUrl& url, const int errNum)
{
345
    const SMBError smbErr = errnumToKioError(url, errNum);
346
    const QString errorString = buildErrorString(smbErr.kioErrorId, smbErr.errorString);
347

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

351
//===========================================================================
352
void SMBSlave::listDir( const QUrl& kurl )
353
{
354
   qCDebug(KIO_SMB) << kurl;
355
   int errNum = 0;
356 357

   // check (correct) URL
358
   QUrl url = checkURL(kurl);
359 360 361 362 363 364 365
   // if URL is not valid we have to redirect to correct URL
   if (url != kurl)
   {
      redirection(url);
      finished();
      return;
   }
366

367
   m_current_url = kurl;
368

369
   int                 dirfd;
370
   struct smbc_dirent  *dirp = nullptr;
371
   UDSEntry    udsentry;
Michal Malý's avatar
Michal Malý committed
372
   bool dir_is_root = true;
373

374
   dirfd = smbc_opendir( m_current_url.toSmbcUrl() );
375 376 377 378 379 380
   if (dirfd > 0){
      errNum = 0;
   } else {
      errNum = errno;
   }

381
   qCDebug(KIO_SMB) << "open " << m_current_url.toSmbcUrl() << " " << m_current_url.getType() << " " << dirfd;
382 383
   if(dirfd >= 0)
   {
384
       uint direntCount = 0;
385
       do {
386
           qCDebug(KIO_SMB) << "smbc_readdir ";
387
           dirp = smbc_readdir(dirfd);
388
           if(dirp == nullptr)
389 390
               break;

391 392
           ++direntCount;

393
           // Set name
394
           QString udsName;
395
           const QString dirpName = QString::fromUtf8( dirp->name );
396 397
           // We cannot trust dirp->commentlen has it might be with or without the NUL character
           // See KDE bug #111430 and Samba bug #3030
398
           const QString comment = QString::fromUtf8( dirp->comment );
Stephan Kulow's avatar
Stephan Kulow committed
399
           if ( dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_WORKGROUP ) {
400 401
               udsName = dirpName.toLower();
               udsName[0] = dirpName.at( 0 ).toUpper();
Stephan Kulow's avatar
Stephan Kulow committed
402
               if ( !comment.isEmpty() && dirp->smbc_type == SMBC_SERVER )
Thorsten Roeder's avatar
Thorsten Roeder committed
403
                   udsName += " (" + comment + ')';
Stephan Kulow's avatar
Stephan Kulow committed
404
           } else
405
               udsName = dirpName;
Stephan Kulow's avatar
Stephan Kulow committed
406

407
           qCDebug(KIO_SMB) << "dirp->name " <<  dirp->name  << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type;
Stephan Kulow's avatar
Stephan Kulow committed
408

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

412 413
           // Mark all administrative shares, e.g ADMIN$, as hidden. #197903
           if (dirpName.endsWith(QLatin1Char('$'))) {
414
              //qCDebug(KIO_SMB) << dirpName << "marked as hidden";
415
              udsentry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1);
416 417
           }

Michal Malý's avatar
Michal Malý committed
418
           if (udsName == ".")
419
           {
Michal Malý's avatar
Michal Malý committed
420 421 422 423 424 425
               // Skip the "." entry
               // Mind the way m_current_url is handled in the loop
           }
           else if (udsName == "..")
           {
               dir_is_root = false;
426
               // fprintf(stderr,"----------- hide: -%s-\n",dirp->name);
427 428
               // do nothing and hide the hidden shares
           }
429 430
           else if (dirp->smbc_type == SMBC_FILE ||
                    dirp->smbc_type == SMBC_DIR)
431 432 433
           {
               // Set stat information
               m_current_url.addPath(dirpName);
Michal Malý's avatar
Michal Malý committed
434
               const int statErr = browse_stat_path(m_current_url, udsentry);
435
               if (statErr)
Michal Malý's avatar
Michal Malý committed
436
               {
437 438 439 440 441 442 443 444 445
                   if (statErr == ENOENT || statErr == ENOTDIR)
                   {
                       reportWarning(m_current_url, statErr);
                   }
               }
               else
               {
                   // Call base class to list entry
                   listEntry(udsentry);
Michal Malý's avatar
Michal Malý committed
446
               }
447 448 449 450 451
               m_current_url.cd("..");
           }
           else if(dirp->smbc_type == SMBC_SERVER ||
                   dirp->smbc_type == SMBC_FILE_SHARE)
           {
452
               // Set type
453
               udsentry.fastInsert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
454 455


456
               if (dirp->smbc_type == SMBC_SERVER) {
457
                   udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH));
458

Laurent Montel's avatar
Laurent Montel committed
459
                   // QString workgroup = m_current_url.host().toUpper();
David Edmundson's avatar
David Edmundson committed
460
                   QUrl u("smb://");
461 462
                   u.setHost(dirpName);

463
                   // when libsmbclient knows
464
                   // u = QString("smb://%1?WORKGROUP=%2").arg(dirpName).arg(workgroup.toUpper());
465
                   qCDebug(KIO_SMB) << "list item " << u;
466
                   udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url());
Stephan Kulow's avatar
Stephan Kulow committed
467

468
                   udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-server"));
469
               } else
470
                   udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH));
471

472

473
               // Call base class to list entry
474
               listEntry(udsentry);
475
           }
476 477 478
           else if(dirp->smbc_type == SMBC_WORKGROUP)
           {
               // Set type
479
               udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
480 481

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

484
               udsentry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("application/x-smb-workgroup"));
Stephan Kulow's avatar
Stephan Kulow committed
485

Laurent Montel's avatar
Laurent Montel committed
486
               // QString workgroup = m_current_url.host().toUpper();
David Edmundson's avatar
David Edmundson committed
487
               QUrl u("smb://");
Stephan Kulow's avatar
Stephan Kulow committed
488
               u.setHost(dirpName);
489
               udsentry.fastInsert(KIO::UDSEntry::UDS_URL, u.url());
Stephan Kulow's avatar
Stephan Kulow committed
490

491
               // Call base class to list entry
492
               listEntry(udsentry);
493 494 495
           }
           else
           {
496
               qCDebug(KIO_SMB) << "SMBC_UNKNOWN :" << dirpName;
497 498 499 500 501 502
               // 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();
503
       } while (dirp); // checked already in the head
504

505 506
       listDNSSD(udsentry, url, direntCount);

Michal Malý's avatar
Michal Malý committed
507
       if (dir_is_root) {
508 509 510
           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));
Michal Malý's avatar
Michal Malý committed
511 512 513
       }
       else
       {
514
           udsentry.fastInsert(KIO::UDSEntry::UDS_NAME, ".");
Michal Malý's avatar
Michal Malý committed
515
           const int statErr = browse_stat_path(m_current_url, udsentry);
516
           if (statErr)
Michal Malý's avatar
Michal Malý committed
517
           {
518 519 520 521 522
               if (statErr == ENOENT || statErr == ENOTDIR)
               {
                   reportWarning(m_current_url, statErr);
               }
               // Create a default UDSEntry if we could not stat the actual directory
523 524
               udsentry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
               udsentry.fastInsert(KIO::UDSEntry::UDS_ACCESS, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH));
Michal Malý's avatar
Michal Malý committed
525
           }
Michal Malý's avatar
Michal Malý committed
526 527 528 529
       }
       listEntry(udsentry);
       udsentry.clear();

530 531
       // clean up
       smbc_closedir(dirfd);
532 533 534
   }
   else
   {
535
       if (errNum == EPERM || errNum == EACCES || workaroundEEXIST(errNum)) {
536 537
           const int passwordError = checkPassword(m_current_url);
           if (passwordError == KJob::NoError) {
538 539
               redirection( m_current_url );
               finished();
540 541 542 543
           } else if (passwordError == KIO::ERR_USER_CANCELED) {
               reportError(m_current_url, errNum);
           } else {
               error(passwordError, m_current_url.toString());
544
           }
545 546

           return;
547
       }
548

549
       reportError(m_current_url, errNum);
550
       return;
551
   }
552

553
   finished();
554
}
Toshitaka Fujioka's avatar
i18n  
Toshitaka Fujioka committed
555

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 585 586 587 588 589 590 591 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 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
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<KDNSSD::RemoteService::Ptr> 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.
}

666 667 668 669
void SMBSlave::fileSystemFreeSpace(const QUrl& url)
{
    qCDebug(KIO_SMB) << url;

670 671 672 673 674 675 676 677
    // 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;
    }

678 679 680 681
    SMBUrl smbcUrl = url;

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

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
    // 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;
    const auto blockSize =  dirStat.f_bsize * frames;
    // 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 715
bool SMBSlave::workaroundEEXIST(const int errNum) const
{
    return (errNum == EEXIST) && m_enableEEXISTWorkaround;
}