Commit e2dc8491 authored by Harald Sitter's avatar Harald Sitter 🏳🌈
Browse files

smb: refactor auth and context logic out of slave

in an effort to implement kdirnotify for smb I needed to revisit the
ancient monolithic code structure :|

authentication is now in an authenticator class the authenticator class
is plugged into a context class and the context takes care of context
initialization (and callback handling because the smbc API is a bit meh
vis a vis callback context void*).

since the authentication now doesn't live in the slavebase anymore the
interaction with `checkCachedAuthentication()` was moved into a pure
virtual interface class. slavebase has an implementation of that
interface and so will the future kdirnotify thingy.
parent e2771300
......@@ -51,6 +51,8 @@ set(kio_smb_PART_SRCS
discovery.cpp
transfer.cpp
smbcdiscoverer.cpp
smbcontext.cpp
smbauthenticator.cpp
)
ecm_qt_declare_logging_category(kio_smb_PART_SRCS
......
......@@ -46,13 +46,6 @@ SMBSlave::SMBSlave(const QByteArray &pool, const QByteArray &app)
, m_openFd(-1)
, m_enableEEXISTWorkaround(needsEEXISTWorkaround())
{
m_initialized_smbc = false;
// read in the default workgroup info...
reparseConfiguration();
// initialize the library...
auth_initialize_smbc();
}
SMBSlave::~SMBSlave() = default;
......@@ -74,4 +67,14 @@ void SMBSlave::virtual_hook(int id, void *data)
}
}
SlaveFrontend::SlaveFrontend(SMBSlave &slave)
: m_slave(slave)
{
}
bool SlaveFrontend::checkCachedAuthentication(AuthInfo &info)
{
return m_slave.checkCachedAuthentication(info);
}
#include "kio_smb.moc"
......@@ -50,16 +50,30 @@ extern "C" {
// kio_smb internal includes
//---------------------------
#include "smburl.h"
#include "smbauthenticator.h"
#include "smbcontext.h"
using namespace KIO;
class SMBSlave;
class SlaveFrontend : public SMBAbstractFrontend
{
public:
SlaveFrontend(SMBSlave &slave);
bool checkCachedAuthentication(AuthInfo &info) override;
private:
SMBSlave &m_slave;
};
class SMBSlave : public QObject, public KIO::SlaveBase
{
Q_OBJECT
friend class SMBCDiscoverer;
SlaveFrontend m_frontend { *this };
SMBContext m_context { new SMBAuthenticator(m_frontend) };
private:
class SMBError
{
public:
......@@ -67,19 +81,6 @@ private:
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 = QStringLiteral("WORKGROUP"); // overwritten with value from smbc
QString m_default_password;
QString m_default_encoding;
/**
* we store the current url, it's needed for
* callback authorization method
......@@ -184,11 +185,6 @@ protected:
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
......@@ -254,12 +250,6 @@ private:
void closeWithoutFinish();
};
//==========================================================================
// 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" {
......
......@@ -12,92 +12,6 @@
#include <kconfiggroup.h>
#include <klocalizedstring.h>
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)
{
if (context != nullptr) {
#ifdef DEPRECATED_SMBC_INTERFACE
auto *theSlave = static_cast<SMBSlave *>(smbc_getOptionUserData(context));
#else
auto *theSlave = static_cast<SMBSlave *>(smbc_option_get(context, "user_data"));
#endif
theSlave->auth_smbc_get_data(server, share,
workgroup,wgmaxlen,
username, unmaxlen,
password, pwmaxlen);
}
}
void SMBSlave::auth_smbc_get_data(const char *server,const char *share,
char *workgroup, int wgmaxlen,
char *username, int unmaxlen,
char *password, int pwmaxlen)
{
// check this to see if we "really" need to authenticate...
SMBUrlType t = m_current_url.getType();
if (t == SMBURLTYPE_ENTIRE_NETWORK) {
qCDebug(KIO_SMB_LOG) << "we don't really need to authenticate for this top level url, returning";
return;
}
qCDebug(KIO_SMB_LOG) << "auth_smbc_get_dat: set user=" << username << ", workgroup=" << workgroup
<< " server=" << server << ", share=" << share;
QString s_server = QString::fromUtf8(server);
QString s_share = QString::fromUtf8(share);
workgroup[wgmaxlen - 1] = 0;
QString s_workgroup = QString::fromUtf8(workgroup);
username[unmaxlen - 1] = 0;
QString s_username = QString::fromUtf8(username);
password[pwmaxlen - 1] = 0;
QString s_password = QString::fromUtf8(password);
KIO::AuthInfo info;
info.url = QUrl("smb:///");
info.url.setHost(s_server);
info.url.setPath('/' + s_share);
info.username = s_username;
info.password = s_password;
info.verifyPath = true;
info.setExtraField("domain", s_workgroup);
qCDebug(KIO_SMB_LOG) << "libsmb-auth-callback URL:" << info.url;
if (!checkCachedAuthentication(info)) {
if (m_default_user.isEmpty()) {
// ok, we do not know the password. Let's try anonymous before we try for real
info.username = "anonymous";
info.password.clear();
} else {
// user defined a default username/password in kcontrol; try this
info.username = m_default_user;
info.password = m_default_password;
}
qCDebug(KIO_SMB_LOG) << "trying defaults for user" << info.username;
} else
qCDebug(KIO_SMB_LOG) << "got password through cache";
// Make sure it'll be safe to cast to size_t (unsigned)
Q_ASSERT(unmaxlen > 0);
Q_ASSERT(pwmaxlen > 0);
Q_ASSERT(wgmaxlen > 0);
strncpy(username, info.username.toUtf8(), static_cast<size_t>(unmaxlen - 1));
strncpy(password, info.password.toUtf8(), static_cast<size_t>(pwmaxlen - 1));
// TODO: isEmpty guard can be removed in 20.08+
// It is only here to prevent us setting an empty work group if a user updates
// but doesn't restart so kiod5 could hold an old cache without domain
// field. In that event we'll leave the input workgroup as-is.
const QString domain = info.getExtraField("domain").toString();
if (!domain.isEmpty()) {
strncpy(workgroup, domain.toUtf8(), static_cast<size_t>(wgmaxlen - 1));
}
}
int SMBSlave::checkPassword(SMBUrl &url)
{
qCDebug(KIO_SMB_LOG) << "checkPassword for " << url;
......@@ -117,7 +31,7 @@ int SMBSlave::checkPassword(SMBUrl &url)
info.keepPassword = true;
info.setExtraField("anonymous", true); // arbitrary default for dialog
info.setExtraField("domain", m_default_workgroup);
info.setExtraField("domain", m_context.authenticator()->defaultWorkgroup());
if (share.isEmpty())
info.prompt = i18n("<qt>Please enter authentication information for <b>%1</b></qt>", url.host());
......@@ -148,62 +62,3 @@ int SMBSlave::checkPassword(SMBUrl &url)
qCDebug(KIO_SMB_LOG) << "no value from openPasswordDialog; error:" << passwordDialogErrorCode;
return passwordDialogErrorCode;
}
bool SMBSlave::auth_initialize_smbc()
{
if (m_initialized_smbc) {
return true;
}
qCDebug(KIO_SMB_LOG) << "auth_initialize_smbc";
KConfig cfg("kioslaverc", KConfig::SimpleConfig);
int debug_level = cfg.group("SMB").readEntry("DebugLevel", 0);
qCDebug(KIO_SMB_LOG) << "smbc_new_context call";
SMBCCTX *smb_context = smbc_new_context();
if (!smb_context) {
SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to create context"));
return false;
}
#ifdef DEPRECATED_SMBC_INTERFACE // defined by libsmbclient.h of Samba 3.2
/* New libsmbclient interface of Samba 3.2 */
qCDebug(KIO_SMB_LOG) << "Setting debug level to:" << debug_level;
smbc_setDebug(smb_context, debug_level);
smbc_setFunctionAuthDataWithContext(smb_context, ::auth_smbc_get_data);
smbc_setOptionUserData(smb_context, this);
/* Enable Kerberos support */
smbc_setOptionUseKerberos(smb_context, 1);
smbc_setOptionFallbackAfterKerberos(smb_context, 1);
#else
smb_context->debug = debug_level;
smb_context->callbacks.auth_fn = NULL;
smbc_option_set(smb_context, "auth_function", (void *)::auth_smbc_get_data);
smbc_option_set(smb_context, "user_data", this);
#if defined(SMB_CTX_FLAG_USE_KERBEROS) && defined(SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS)
smb_context->flags |= SMB_CTX_FLAG_USE_KERBEROS | SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS;
#endif
#endif /* DEPRECATED_SMBC_INTERFACE */
if (!smbc_init_context(smb_context)) {
smbc_free_context(smb_context, 0);
smb_context = nullptr;
SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to initialize context"));
return false;
}
smbc_set_context(smb_context);
// TODO: refactor; checkPassword should query this on
// demand to not run into situations where we may have cached
// the workgroup early on and it changed since. Needs context
// being held in the slave though, which opens us up to nullptr
// problems should checkPassword be called without init first.
m_default_workgroup = smbc_getWorkgroup(smb_context);
m_initialized_smbc = true;
return true;
}
......@@ -13,27 +13,5 @@
void SMBSlave::reparseConfiguration()
{
KConfig cfg("kioslaverc", KConfig::NoGlobals);
const KConfigGroup group = cfg.group("Browser Settings/SMBro");
m_default_user = group.readEntry("User");
// m_default_workgroup=group.readEntry("Workgroup");
// m_showHiddenShares=group.readEntry("ShowHiddenShares", QVariant(false)).toBool();
QString m_encoding = QTextCodec::codecForLocale()->name();
m_default_encoding = group.readEntry("Encoding", m_encoding.toLower());
// unscramble, taken from Nicola Brodu's smb ioslave
// not really secure, but better than storing the plain password
QString scrambled = group.readEntry("Password");
m_default_password = "";
for (int i = 0; i < scrambled.length() / 3; i++) {
QChar qc1 = scrambled[i * 3];
QChar qc2 = scrambled[i * 3 + 1];
QChar qc3 = scrambled[i * 3 + 2];
unsigned int a1 = qc1.toLatin1() - '0';
unsigned int a2 = qc2.toLatin1() - 'A';
unsigned int a3 = qc3.toLatin1() - '0';
unsigned int num = ((a1 & 0x3F) << 10) | ((a2 & 0x1F) << 5) | (a3 & 0x1F);
m_default_password[i] = QChar((uchar)((num - 17) ^ 173)); // restore
}
m_context.authenticator()->loadConfiguration();
}
......@@ -12,6 +12,7 @@
#include <QMimeDatabase>
#include <QMimeType>
#include <QVarLengthArray>
#include <KLocalizedString>
#include <future>
......@@ -30,8 +31,10 @@ void SMBSlave::get(const QUrl &kurl)
return;
}
if (!auth_initialize_smbc())
if (!m_context.isValid()) {
SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to create context"));
return;
}
// Stat
SMBUrl url = kurl;
......@@ -131,7 +134,7 @@ void SMBSlave::open(const QUrl &kurl, QIODevice::OpenMode mode)
return;
}
if (!auth_initialize_smbc()) {
if (!m_context.isValid()) {
error(KIO::ERR_ACCESS_DENIED, kurl.toDisplayString());
return;
}
......
/*
SPDX-License-Identifier: GPL-2.0-or-later
SPDX-FileCopyrightText: 2000 Caldera Systems, Inc.
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-FileContributor: Matthew Peterson <mpeterson@caldera.com>
*/
#include "smbauthenticator.h"
#include <KConfig>
#include <KConfigGroup>
#include <KIO/AuthInfo>
#include <QTextCodec>
#include "smburl.h"
#include "smb-logsettings.h"
SMBAuthenticator::SMBAuthenticator(SMBAbstractFrontend &frontend)
: m_frontend(frontend)
{
}
void SMBAuthenticator::loadConfiguration()
{
KConfig cfg("kioslaverc", KConfig::NoGlobals);
const KConfigGroup group = cfg.group("Browser Settings/SMBro");
m_defaultUser = group.readEntry("User");
// m_default_workgroup=group.readEntry("Workgroup");
// m_showHiddenShares=group.readEntry("ShowHiddenShares", QVariant(false)).toBool();
QString m_encoding = QTextCodec::codecForLocale()->name();
m_defaultEncoding = group.readEntry("Encoding", m_encoding.toLower());
// unscramble, taken from Nicola Brodu's smb ioslave
// not really secure, but better than storing the plain password
QString scrambled = group.readEntry("Password");
m_defaultPassword = "";
for (int i = 0; i < scrambled.length() / 3; i++) {
QChar qc1 = scrambled[i * 3];
QChar qc2 = scrambled[i * 3 + 1];
QChar qc3 = scrambled[i * 3 + 2];
unsigned int a1 = qc1.toLatin1() - '0';
unsigned int a2 = qc2.toLatin1() - 'A';
unsigned int a3 = qc3.toLatin1() - '0';
unsigned int num = ((a1 & 0x3F) << 10) | ((a2 & 0x1F) << 5) | (a3 & 0x1F);
m_defaultPassword[i] = QChar((uchar)((num - 17) ^ 173)); // restore
}
}
QString SMBAuthenticator::defaultWorkgroup() const
{
return m_defaultWorkgroup;
}
void SMBAuthenticator::setDefaultWorkgroup(const QString &workGroup)
{
m_defaultWorkgroup = workGroup;
}
void SMBAuthenticator::auth(const char *server, const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen)
{
qCDebug(KIO_SMB_LOG) << "auth_smbc_get_dat: set user=" << username << ", workgroup=" << workgroup
<< " server=" << server << ", share=" << share;
QString s_server = QString::fromUtf8(server);
QString s_share = QString::fromUtf8(share);
workgroup[wgmaxlen - 1] = 0;
QString s_workgroup = QString::fromUtf8(workgroup);
username[unmaxlen - 1] = 0;
QString s_username = QString::fromUtf8(username);
password[pwmaxlen - 1] = 0;
QString s_password = QString::fromUtf8(password);
KIO::AuthInfo info;
info.url = QUrl("smb:///");
info.url.setHost(s_server);
info.url.setPath('/' + s_share);
// check this to see if we "really" need to authenticate...
if (SMBUrl(info.url).getType() == SMBURLTYPE_ENTIRE_NETWORK) {
qCDebug(KIO_SMB_LOG) << "we don't really need to authenticate for this top level url, returning";
return;
}
info.username = s_username;
info.password = s_password;
info.verifyPath = true;
info.setExtraField("domain", s_workgroup);
qCDebug(KIO_SMB_LOG) << "libsmb-auth-callback URL:" << info.url;
if (!m_frontend.checkCachedAuthentication(info)) {
if (m_defaultUser.isEmpty()) {
// ok, we do not know the password. Let's try anonymous before we try for real
info.username = "anonymous";
info.password.clear();
} else {
// user defined a default username/password in kcontrol; try this
info.username = m_defaultUser;
info.password = m_defaultPassword;
}
qCDebug(KIO_SMB_LOG) << "trying defaults for user" << info.username;
} else
qCDebug(KIO_SMB_LOG) << "got password through cache" << info.username << info.password;
// Make sure it'll be safe to cast to size_t (unsigned)
Q_ASSERT(unmaxlen > 0);
Q_ASSERT(pwmaxlen > 0);
Q_ASSERT(wgmaxlen > 0);
strncpy(username, info.username.toUtf8(), static_cast<size_t>(unmaxlen - 1));
strncpy(password, info.password.toUtf8(), static_cast<size_t>(pwmaxlen - 1));
// TODO: isEmpty guard can be removed in 20.08+
// It is only here to prevent us setting an empty work group if a user updates
// but doesn't restart so kiod5 could hold an old cache without domain
// field. In that event we'll leave the input workgroup as-is.
const QString domain = info.getExtraField("domain").toString();
if (!domain.isEmpty()) {
strncpy(workgroup, domain.toUtf8(), static_cast<size_t>(wgmaxlen - 1));
}
}
/*
SPDX-License-Identifier: GPL-2.0-or-later
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
#pragma once
#include <QString>
namespace KIO {
class AuthInfo;
}
// Abstracts SlaveBase API so Authenticator may be used without
// a SlaveBase for the KDirNotify implementation)
class SMBAbstractFrontend
{
public:
virtual ~SMBAbstractFrontend() = default;
virtual bool checkCachedAuthentication(KIO::AuthInfo &info) = 0;
};
// Base class for SMBC management + basic authentication
class SMBAuthenticator
{
public:
SMBAuthenticator(SMBAbstractFrontend &frontend);
QString defaultWorkgroup() const;
void setDefaultWorkgroup(const QString &workGroup);
// (Re)loads default values from configuration
void loadConfiguration();
// Callback for authentication requests.
void auth(const char *server, const char *share,
char *workgroup, int wgmaxlen,
char *username, int unmaxlen,
char *password, int pwmaxlen);
private:
// Frontend for authentication requests.
SMBAbstractFrontend &m_frontend;
QString m_defaultUser;
QString m_defaultPassword;
QString m_defaultEncoding;
QString m_defaultWorkgroup = QStringLiteral("WORKGROUP"); // overwritten with value from smbc
Q_DISABLE_COPY(SMBAuthenticator)
};
/*
SPDX-License-Identifier: GPL-2.0-or-later
SPDX-FileCopyrightText: 2000 Caldera Systems, Inc.
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-FileContributor: Matthew Peterson <mpeterson@caldera.com>
*/
#include "smbcontext.h"
#include <KConfig>
#include <KConfigGroup>
#include "smbauthenticator.h"
#include "smb-logsettings.h"
SMBContext::SMBContext(SMBAuthenticator *authenticator)
: m_context(smbc_new_context(), &freeContext)
, m_authenticator(authenticator)
{
Q_ASSERT(m_context);
if (!m_context) {
return;
}
authenticator->loadConfiguration();
qCDebug(KIO_SMB_LOG) << "auth_initialize_smbc";
KConfig cfg("kioslaverc", KConfig::SimpleConfig);
int debugLevel = cfg.group("SMB").readEntry("DebugLevel", 0);
qCDebug(KIO_SMB_LOG) << "Setting debug level to:" << debugLevel;
#ifdef DEPRECATED_SMBC_INTERFACE // defined by libsmbclient.h of Samba 3.2
smbc_setOptionUserData(m_context.get(), this);
smbc_setFunctionAuthDataWithContext(m_context.get(), auth_cb);
/* New libsmbclient interface of Samba 3.2 */
smbc_setDebug(m_context.get(), debugLevel);
/* Enable Kerberos support */
smbc_setOptionUseKerberos(m_context.get(), 1);
smbc_setOptionFallbackAfterKerberos(m_context.get(), 1);
#else
smbc_option_set(m_context.get(), "user_data", this);
m_context->callbacks.auth_fn = NULL;
smbc_option_set(m_context.get(), "auth_function", (void *)auth_cb);
m_context->debug = debug_level;
#if defined(SMB_CTX_FLAG_USE_KERBEROS) && defined(SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS)
m_context->flags |= SMB_CTX_FLAG_USE_KERBEROS | SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS;
#endif
#endif /* DEPRECATED_SMBC_INTERFACE */
if (!smbc_init_context(m_context.get())) {
m_context.reset();
return;
}
smbc_set_context(m_context.get());
// TODO: refactor; checkPassword should query this on
// demand to not run into situations where we may have cached
// the workgroup early on and it changed since. Needs context
// being held in the slave though, which opens us up to nullptr
// problems should checkPassword be called without init first.
authenticator->setDefaultWorkgroup(smbc_getWorkgroup(*this));
return;
}
bool SMBContext::isValid() const
{
return smbcctx() && authenticator();
}
void SMBContext::auth_cb(SMBCCTX *context, const char *server, const char *share, char *workgroup, int wgmaxlen, char *username, int unmaxlen, char *password, int pwmaxlen)
{
// Unfortunately because the callback API doesn't support callback specific user_data we need
// to route all auths through our context object otherwise the authenticator would have
// to twiddle the global context user_data and that seems much worse :|
if (context != nullptr) {
#ifdef DEPRECATED_SMBC_INTERFACE
auto *that = static_cast<SMBContext *>(smbc_getOptionUserData(context));
#else
auto *that = static_cast<SMBCContext *>(smbc_option_get(context, "user_data"));
#endif
that->m_authenticator->auth(server, share,
workgroup,wgmaxlen,
username, unmaxlen,
password, pwmaxlen);
}
}
void SMBContext::freeContext(SMBCCTX *ptr)
{
smbc_free_context(ptr, 1);
}
/*
SPDX-License-Identifier: GPL-2.0-or-later
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
*/
#pragma once
extern "C" {