mirror of
https://github.com/QuasarApp/qca.git
synced 2025-05-12 10:49:32 +00:00
2353 lines
52 KiB
C++
2353 lines
52 KiB
C++
/*
|
|
* Copyright (C) 2008 Barracuda Networks, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser 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 library is distributed in the hope that it will be useful,
|
|
* but WITHANY 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 Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <QtCrypto>
|
|
#include <qcaprovider.h>
|
|
#include <QtPlugin>
|
|
#include <QMutex>
|
|
#include <QLibrary>
|
|
#include <QTimer>
|
|
|
|
#ifndef FORWARD_ONLY
|
|
#include <windows.h>
|
|
#define SECURITY_WIN32
|
|
#include <Security.h>
|
|
#endif
|
|
|
|
using namespace QCA;
|
|
|
|
#define PROVIDER_NAME "qca-wingss"
|
|
|
|
#if !defined(FORWARD_ONLY)
|
|
|
|
// some defs possibly missing from MinGW
|
|
|
|
#ifndef SEC_E_MESSAGE_ALTERED
|
|
#define SEC_E_MESSAGE_ALTERED 0x8009030F
|
|
#endif
|
|
|
|
#ifndef SEC_E_CONTEXT_EXPIRED
|
|
#define SEC_E_CONTEXT_EXPIRED 0x80090317
|
|
#endif
|
|
|
|
#ifndef SEC_E_CRYPTO_SYSTEM_INVALID
|
|
#define SEC_E_CRYPTO_SYSTEM_INVALID 0x80090337
|
|
#endif
|
|
|
|
#ifndef SEC_E_OUT_OF_SEQUENCE
|
|
#define SEC_E_OUT_OF_SEQUENCE 0x80090310
|
|
#endif
|
|
|
|
#ifndef SEC_E_BUFFER_TOO_SMALL
|
|
#define SEC_E_BUFFER_TOO_SMALL 0x80090321
|
|
#endif
|
|
|
|
#ifndef SECURITY_ENTRYPOINTW
|
|
#define SECURITY_ENTRYPOINTW TEXT("InitSecurityInterfaceW")
|
|
#endif
|
|
|
|
#ifndef SECURITY_ENTRYPOINT_ANSIA
|
|
#define SECURITY_ENTRYPOINT_ANSIA "InitSecurityInterfaceA"
|
|
#endif
|
|
|
|
#ifndef SECPKG_FLAG_GSS_COMPATIBLE
|
|
#define SECPKG_FLAG_GSS_COMPATIBLE 0x00001000
|
|
#endif
|
|
|
|
#ifndef SECQOP_WRAP_NO_ENCRYPT
|
|
#define SECQOP_WRAP_NO_ENCRYPT 0x80000001
|
|
#endif
|
|
|
|
#ifndef ISC_RET_MUTUAL_AUTH
|
|
#define ISC_RET_MUTUAL_AUTH 0x00000002
|
|
#endif
|
|
|
|
#ifndef ISC_RET_SEQUENCE_DETECT
|
|
#define ISC_RET_SEQUENCE_DETECT 0x00000008
|
|
#endif
|
|
|
|
#ifndef ISC_RET_CONFIDENTIALITY
|
|
#define ISC_RET_CONFIDENTIALITY 0x00000010
|
|
#endif
|
|
|
|
#ifndef ISC_RET_INTEGRITY
|
|
#define ISC_RET_INTEGRITY 0x00010000
|
|
#endif
|
|
|
|
#ifdef Q_CC_MINGW
|
|
|
|
// for some reason, the MinGW definition of the W table has A functions in
|
|
// it, so we define a fixed version to use instead...
|
|
|
|
typedef struct _FIXED_SECURITY_FUNCTION_TABLEW {
|
|
unsigned long dwVersion;
|
|
ENUMERATE_SECURITY_PACKAGES_FN_W EnumerateSecurityPackagesW;
|
|
QUERY_CREDENTIALS_ATTRIBUTES_FN_W QueryCredentialsAttributesW;
|
|
ACQUIRE_CREDENTIALS_HANDLE_FN_W AcquireCredentialsHandleW;
|
|
FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle;
|
|
void SEC_FAR* Reserved2;
|
|
INITIALIZE_SECURITY_CONTEXT_FN_W InitializeSecurityContextW;
|
|
ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext;
|
|
COMPLETE_AUTH_TOKEN_FN CompleteAuthToken;
|
|
DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext;
|
|
APPLY_CONTROL_TOKEN_FN_W ApplyControlTokenW;
|
|
QUERY_CONTEXT_ATTRIBUTES_FN_W QueryContextAttributesW;
|
|
IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext;
|
|
REVERT_SECURITY_CONTEXT_FN RevertSecurityContext;
|
|
MAKE_SIGNATURE_FN MakeSignature;
|
|
VERIFY_SIGNATURE_FN VerifySignature;
|
|
FREE_CONTEXT_BUFFER_FN FreeContextBuffer;
|
|
QUERY_SECURITY_PACKAGE_INFO_FN_W QuerySecurityPackageInfoW;
|
|
void SEC_FAR* Reserved3;
|
|
void SEC_FAR* Reserved4;
|
|
void SEC_FAR* Unknown1;
|
|
void SEC_FAR* Unknown2;
|
|
void SEC_FAR* Unknown3;
|
|
void SEC_FAR* Unknown4;
|
|
void SEC_FAR* Unknown5;
|
|
ENCRYPT_MESSAGE_FN EncryptMessage;
|
|
DECRYPT_MESSAGE_FN DecryptMessage;
|
|
} FixedSecurityFunctionTableW, *PFixedSecurityFunctionTableW;
|
|
|
|
typedef FixedSecurityFunctionTableW MySecurityFunctionTableW;
|
|
typedef PFixedSecurityFunctionTableW PMySecurityFunctionTableW;
|
|
|
|
#else
|
|
|
|
typedef SecurityFunctionTableW MySecurityFunctionTableW;
|
|
typedef PSecurityFunctionTableW PMySecurityFunctionTableW;
|
|
|
|
#endif
|
|
|
|
#ifdef UNICODE
|
|
# define MySecurityFunctionTable MySecurityFunctionTableW
|
|
# define PMySecurityFunctionTable PMySecurityFunctionTableW
|
|
#else
|
|
# define MySecurityFunctionTable MySecurityFunctionTableA
|
|
# define PMySecurityFunctionTable PMySecurityFunctionTableA
|
|
#endif
|
|
|
|
#endif // !defined(FORWARD_ONLY)
|
|
|
|
namespace wingssQCAPlugin {
|
|
|
|
//----------------------------------------------------------------------------
|
|
// SafeTimer (copied from qca_safeobj)
|
|
//----------------------------------------------------------------------------
|
|
static void releaseAndDeleteLater(QObject *owner, QObject *obj)
|
|
{
|
|
obj->disconnect(owner);
|
|
obj->setParent(0);
|
|
obj->deleteLater();
|
|
}
|
|
|
|
class SafeTimer : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
SafeTimer(QObject *parent = 0) :
|
|
QObject(parent)
|
|
{
|
|
t = new QTimer(this);
|
|
connect(t, SIGNAL(timeout()), SIGNAL(timeout()));
|
|
}
|
|
|
|
~SafeTimer()
|
|
{
|
|
releaseAndDeleteLater(this, t);
|
|
}
|
|
|
|
int interval() const { return t->interval(); }
|
|
bool isActive() const { return t->isActive(); }
|
|
bool isSingleShot() const { return t->isSingleShot(); }
|
|
void setInterval(int msec) { t->setInterval(msec); }
|
|
void setSingleShot(bool singleShot) { t->setSingleShot(singleShot); }
|
|
int timerId() const { return t->timerId(); }
|
|
|
|
public slots:
|
|
void start(int msec) { t->start(msec); }
|
|
void start() { t->start(); }
|
|
void stop() { t->stop(); }
|
|
|
|
signals:
|
|
void timeout();
|
|
|
|
private:
|
|
QTimer *t;
|
|
};
|
|
|
|
#if !defined(FORWARD_ONLY)
|
|
|
|
//----------------------------------------------------------------------------
|
|
// SSPI helper API
|
|
//----------------------------------------------------------------------------
|
|
|
|
typedef void (*sspi_logger_func)(const QString &str);
|
|
|
|
class SspiPackage
|
|
{
|
|
public:
|
|
QString name;
|
|
quint32 caps;
|
|
quint32 maxtok;
|
|
quint16 version;
|
|
quint16 rpcid;
|
|
QString comment;
|
|
};
|
|
|
|
// logger can be set even when sspi is not loaded (this is needed so logging
|
|
// during sspi_load/unload can be captured). pass 0 to disable.
|
|
void sspi_set_logger(sspi_logger_func p);
|
|
|
|
void sspi_log(const QString &str);
|
|
|
|
bool sspi_load();
|
|
void sspi_unload();
|
|
|
|
// returns the available security packages. only the first call actually
|
|
// queries the sspi subsystem. subsequent calls return a cached result.
|
|
QList<SspiPackage> sspi_get_packagelist();
|
|
|
|
// refresh the package list cache. call sspi_get_packagelist afterwards to
|
|
// get the new list.
|
|
void sspi_refresh_packagelist();
|
|
|
|
// helper functions for logging
|
|
QString SECURITY_STATUS_toString(SECURITY_STATUS i);
|
|
QString ptr_toString(const void *p);
|
|
|
|
//----------------------------------------------------------------------------
|
|
// SSPI helper implementation
|
|
//----------------------------------------------------------------------------
|
|
|
|
Q_GLOBAL_STATIC(QMutex, sspi_mutex)
|
|
Q_GLOBAL_STATIC(QMutex, sspi_logger_mutex)
|
|
|
|
union SecurityFunctionTableUnion
|
|
{
|
|
PMySecurityFunctionTableW W;
|
|
PSecurityFunctionTableA A;
|
|
void *ptr;
|
|
};
|
|
|
|
static QLibrary *sspi_lib = 0;
|
|
static SecurityFunctionTableUnion sspi;
|
|
static sspi_logger_func sspi_logger;
|
|
static QList<SspiPackage> *sspi_packagelist = 0;
|
|
|
|
void sspi_log(const QString &str)
|
|
{
|
|
QMutexLocker locker(sspi_logger_mutex());
|
|
|
|
if(sspi_logger)
|
|
sspi_logger(str);
|
|
}
|
|
|
|
void sspi_set_logger(sspi_logger_func p)
|
|
{
|
|
QMutexLocker locker(sspi_logger_mutex());
|
|
|
|
sspi_logger = p;
|
|
}
|
|
|
|
#define CASE_SS_STRING(s) case s: return #s;
|
|
|
|
static const char *SECURITY_STATUS_lookup(SECURITY_STATUS i)
|
|
{
|
|
switch(i)
|
|
{
|
|
CASE_SS_STRING(SEC_E_OK);
|
|
CASE_SS_STRING(SEC_I_COMPLETE_AND_CONTINUE);
|
|
CASE_SS_STRING(SEC_I_COMPLETE_NEEDED);
|
|
CASE_SS_STRING(SEC_I_CONTINUE_NEEDED);
|
|
CASE_SS_STRING(SEC_I_INCOMPLETE_CREDENTIALS);
|
|
CASE_SS_STRING(SEC_E_UNSUPPORTED_FUNCTION);
|
|
CASE_SS_STRING(SEC_E_INVALID_TOKEN);
|
|
CASE_SS_STRING(SEC_E_MESSAGE_ALTERED);
|
|
CASE_SS_STRING(SEC_E_INSUFFICIENT_MEMORY);
|
|
CASE_SS_STRING(SEC_E_INTERNAL_ERROR);
|
|
CASE_SS_STRING(SEC_E_INVALID_HANDLE);
|
|
CASE_SS_STRING(SEC_E_LOGON_DENIED);
|
|
CASE_SS_STRING(SEC_E_NO_AUTHENTICATING_AUTHORITY);
|
|
CASE_SS_STRING(SEC_E_NO_CREDENTIALS);
|
|
CASE_SS_STRING(SEC_E_TARGET_UNKNOWN);
|
|
CASE_SS_STRING(SEC_E_WRONG_PRINCIPAL);
|
|
CASE_SS_STRING(SEC_E_BUFFER_TOO_SMALL);
|
|
CASE_SS_STRING(SEC_E_CONTEXT_EXPIRED);
|
|
CASE_SS_STRING(SEC_E_CRYPTO_SYSTEM_INVALID);
|
|
CASE_SS_STRING(SEC_E_QOP_NOT_SUPPORTED);
|
|
CASE_SS_STRING(SEC_E_INCOMPLETE_MESSAGE);
|
|
CASE_SS_STRING(SEC_E_OUT_OF_SEQUENCE);
|
|
default: break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QString SECURITY_STATUS_toString(SECURITY_STATUS i)
|
|
{
|
|
const char *str = SECURITY_STATUS_lookup(i);
|
|
if(str)
|
|
return QString(str);
|
|
else
|
|
return QString::number(i);
|
|
}
|
|
|
|
QString ptr_toString(const void *p)
|
|
{
|
|
return QString().sprintf("%p", p);
|
|
}
|
|
|
|
bool sspi_load()
|
|
{
|
|
QMutexLocker locker(sspi_mutex());
|
|
if(sspi_lib)
|
|
return true;
|
|
|
|
sspi_lib = new QLibrary("secur32");
|
|
if(!sspi_lib->load())
|
|
{
|
|
delete sspi_lib;
|
|
sspi_lib = 0;
|
|
return false;
|
|
}
|
|
|
|
union
|
|
{
|
|
INIT_SECURITY_INTERFACE_W W;
|
|
INIT_SECURITY_INTERFACE_A A;
|
|
void *ptr;
|
|
} pInitSecurityInterface;
|
|
pInitSecurityInterface.ptr = 0;
|
|
|
|
QString securityEntrypoint;
|
|
#if QT_VERSION >= 0x050000
|
|
securityEntrypoint = QString::fromUtf16((const ushort *)SECURITY_ENTRYPOINTW);
|
|
pInitSecurityInterface.W = (INIT_SECURITY_INTERFACE_W)(sspi_lib->resolve(securityEntrypoint.toLatin1().data()));
|
|
#else
|
|
QT_WA(
|
|
securityEntrypoint = QString::fromUtf16((const ushort *)SECURITY_ENTRYPOINTW);
|
|
pInitSecurityInterface.W = (INIT_SECURITY_INTERFACE_W)(sspi_lib->resolve(securityEntrypoint.toLatin1().data()));
|
|
,
|
|
securityEntrypoint = QString::fromLatin1(SECURITY_ENTRYPOINT_ANSIA);
|
|
pInitSecurityInterface.A = (INIT_SECURITY_INTERFACE_A)(sspi_lib->resolve(securityEntrypoint.toLatin1().data()));
|
|
)
|
|
#endif
|
|
if(!pInitSecurityInterface.ptr)
|
|
{
|
|
sspi_lib->unload();
|
|
delete sspi_lib;
|
|
sspi_lib = 0;
|
|
return false;
|
|
}
|
|
|
|
union
|
|
{
|
|
PMySecurityFunctionTableW W;
|
|
PSecurityFunctionTableA A;
|
|
void *ptr;
|
|
} funcs;
|
|
funcs.ptr = 0;
|
|
|
|
#if QT_VERSION >= 0x050000
|
|
funcs.W = (PMySecurityFunctionTableW)pInitSecurityInterface.W();
|
|
#else
|
|
QT_WA(
|
|
funcs.W = (PMySecurityFunctionTableW)pInitSecurityInterface.W();
|
|
,
|
|
funcs.A = pInitSecurityInterface.A();
|
|
)
|
|
#endif
|
|
|
|
sspi_log(QString("%1() = %2\n").arg(securityEntrypoint, ptr_toString(funcs.ptr)));
|
|
if(!funcs.ptr)
|
|
{
|
|
sspi_lib->unload();
|
|
delete sspi_lib;
|
|
sspi_lib = 0;
|
|
return false;
|
|
}
|
|
|
|
#if QT_VERSION >= 0x050000
|
|
sspi.W = funcs.W;
|
|
#else
|
|
QT_WA(
|
|
sspi.W = funcs.W;
|
|
,
|
|
sspi.A = funcs.A;
|
|
)
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void sspi_unload()
|
|
{
|
|
QMutexLocker locker(sspi_mutex());
|
|
|
|
sspi_lib->unload();
|
|
delete sspi_lib;
|
|
sspi_lib = 0;
|
|
sspi.ptr = 0;
|
|
}
|
|
|
|
static QList<SspiPackage> sspi_get_packagelist_direct()
|
|
{
|
|
QList<SspiPackage> out;
|
|
|
|
#if QT_VERSION >= 0x050000
|
|
ULONG cPackages;
|
|
SecPkgInfoW *pPackageInfo;
|
|
SECURITY_STATUS ret = sspi.W->EnumerateSecurityPackagesW(&cPackages, &pPackageInfo);
|
|
sspi_log(QString("EnumerateSecurityPackages() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret != SEC_E_OK)
|
|
return out;
|
|
|
|
for(int n = 0; n < (int)cPackages; ++n)
|
|
{
|
|
SecPkgInfoW *p = &pPackageInfo[n];
|
|
SspiPackage i;
|
|
i.name = QString::fromUtf16((const ushort *)p->Name);
|
|
i.caps = p->fCapabilities;
|
|
i.version = p->wVersion;
|
|
i.rpcid = p->wRPCID;
|
|
i.maxtok = p->cbMaxToken;
|
|
i.comment = QString::fromUtf16((const ushort *)p->Comment);
|
|
out += i;
|
|
}
|
|
|
|
ret = sspi.W->FreeContextBuffer(&pPackageInfo);
|
|
sspi_log(QString("FreeContextBuffer() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
#else
|
|
QT_WA(
|
|
ULONG cPackages;
|
|
SecPkgInfoW *pPackageInfo;
|
|
SECURITY_STATUS ret = sspi.W->EnumerateSecurityPackagesW(&cPackages, &pPackageInfo);
|
|
sspi_log(QString("EnumerateSecurityPackages() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret != SEC_E_OK)
|
|
return out;
|
|
|
|
for(int n = 0; n < (int)cPackages; ++n)
|
|
{
|
|
SecPkgInfoW *p = &pPackageInfo[n];
|
|
SspiPackage i;
|
|
i.name = QString::fromUtf16((const ushort *)p->Name);
|
|
i.caps = p->fCapabilities;
|
|
i.version = p->wVersion;
|
|
i.rpcid = p->wRPCID;
|
|
i.maxtok = p->cbMaxToken;
|
|
i.comment = QString::fromUtf16((const ushort *)p->Comment);
|
|
out += i;
|
|
}
|
|
|
|
ret = sspi.W->FreeContextBuffer(&pPackageInfo);
|
|
sspi_log(QString("FreeContextBuffer() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
,
|
|
ULONG cPackages;
|
|
SecPkgInfoA *pPackageInfo;
|
|
SECURITY_STATUS ret = sspi.A->EnumerateSecurityPackagesA(&cPackages, &pPackageInfo);
|
|
sspi_log(QString("EnumerateSecurityPackages() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret != SEC_E_OK)
|
|
return out;
|
|
|
|
for(int n = 0; n < (int)cPackages; ++n)
|
|
{
|
|
SecPkgInfoA *p = &pPackageInfo[n];
|
|
SspiPackage i;
|
|
i.name = QString::fromLocal8Bit(p->Name);
|
|
i.caps = p->fCapabilities;
|
|
i.version = p->wVersion;
|
|
i.rpcid = p->wRPCID;
|
|
i.maxtok = p->cbMaxToken;
|
|
i.comment = QString::fromLocal8Bit(p->Comment);
|
|
out += i;
|
|
}
|
|
|
|
ret = sspi.A->FreeContextBuffer(&pPackageInfo);
|
|
sspi_log(QString("FreeContextBuffer() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
)
|
|
#endif
|
|
|
|
return out;
|
|
}
|
|
|
|
static void sspi_refresh_packagelist_internal()
|
|
{
|
|
if(sspi_packagelist)
|
|
*sspi_packagelist = sspi_get_packagelist_direct();
|
|
else
|
|
sspi_packagelist = new QList<SspiPackage>(sspi_get_packagelist_direct());
|
|
}
|
|
|
|
QList<SspiPackage> sspi_get_packagelist()
|
|
{
|
|
QMutexLocker locker(sspi_mutex());
|
|
|
|
if(!sspi_packagelist)
|
|
sspi_refresh_packagelist_internal();
|
|
return *sspi_packagelist;
|
|
}
|
|
|
|
void sspi_refresh_packagelist()
|
|
{
|
|
QMutexLocker locker(sspi_mutex());
|
|
|
|
sspi_refresh_packagelist_internal();
|
|
}
|
|
|
|
template <typename T>
|
|
inline T cap_to_int(const T &t)
|
|
{
|
|
if(sizeof(int) <= sizeof(T))
|
|
return (int)((t > INT_MAX) ? INT_MAX : t);
|
|
else
|
|
return (int)t;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// KerberosSession
|
|
//----------------------------------------------------------------------------
|
|
// this class thinly wraps SSPI to perform kerberos.
|
|
class KerberosSession
|
|
{
|
|
public:
|
|
enum ReturnCode
|
|
{
|
|
Success,
|
|
NeedMoreData, // for decrypt
|
|
ErrorInvalidSystem,
|
|
ErrorKerberosNotFound,
|
|
ErrorAcquireCredentials,
|
|
ErrorInitialize,
|
|
ErrorQueryContext,
|
|
ErrorEncrypt,
|
|
ErrorDecrypt
|
|
};
|
|
|
|
SECURITY_STATUS lastErrorCode;
|
|
|
|
quint32 maxtok;
|
|
|
|
bool initialized;
|
|
bool first_step;
|
|
QByteArray first_out_token;
|
|
bool authed;
|
|
|
|
QString spn;
|
|
|
|
CredHandle user_cred;
|
|
TimeStamp user_cred_expiry;
|
|
|
|
CtxtHandle ctx;
|
|
ULONG ctx_attr_req;
|
|
ULONG ctx_attr;
|
|
bool have_sizes;
|
|
SecPkgContext_Sizes ctx_sizes;
|
|
SecPkgContext_StreamSizes ctx_streamSizes;
|
|
|
|
KerberosSession() :
|
|
initialized(false),
|
|
have_sizes(false)
|
|
{
|
|
}
|
|
|
|
~KerberosSession()
|
|
{
|
|
if(initialized)
|
|
{
|
|
SECURITY_STATUS ret = sspi.W->DeleteSecurityContext(&ctx);
|
|
sspi_log(QString("DeleteSecurityContext() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
|
|
ret = sspi.W->FreeCredentialsHandle(&user_cred);
|
|
sspi_log(QString("FreeCredentialsHandle() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
}
|
|
}
|
|
|
|
ReturnCode init(const QString &_spn)
|
|
{
|
|
// kerberos only works on unicode-based systems. we do this
|
|
// check so we can lazily use the W api from here on out.
|
|
#if QT_VERSION < 0x050000
|
|
bool validSystem;
|
|
QT_WA(
|
|
validSystem = true;
|
|
,
|
|
validSystem = false;
|
|
)
|
|
if(!validSystem)
|
|
return ErrorInvalidSystem;
|
|
#endif
|
|
|
|
// ensure kerberos is available
|
|
bool found = false;
|
|
quint32 _maxtok = 0;
|
|
QList<SspiPackage> packages = sspi_get_packagelist();
|
|
sspi_log("SSPI packages:\n");
|
|
foreach(const SspiPackage &p, packages)
|
|
{
|
|
bool gss = false;
|
|
if(p.caps & SECPKG_FLAG_GSS_COMPATIBLE)
|
|
gss = true;
|
|
|
|
if(p.name == "Kerberos" && gss)
|
|
{
|
|
found = true;
|
|
_maxtok = p.maxtok;
|
|
}
|
|
|
|
QString gssstr = gss ? "yes" : "no";
|
|
sspi_log(QString(" %1 (gss=%2, maxtok=%3)\n").arg(p.name, gssstr, QString::number(p.maxtok)));
|
|
}
|
|
|
|
if(!found)
|
|
return ErrorKerberosNotFound;
|
|
|
|
// get the logged-in user's credentials
|
|
SECURITY_STATUS ret = sspi.W->AcquireCredentialsHandleW(
|
|
(SEC_WCHAR *)0, // we want creds of logged-in user
|
|
(SEC_WCHAR *)QString("Kerberos").utf16(),
|
|
SECPKG_CRED_OUTBOUND,
|
|
0, // don't need a LUID
|
|
0, // default credentials for kerberos
|
|
0, // not used
|
|
0, // not used
|
|
&user_cred,
|
|
&user_cred_expiry);
|
|
sspi_log(QString("AcquireCredentialsHandle() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret != SEC_E_OK)
|
|
{
|
|
lastErrorCode = ret;
|
|
return ErrorAcquireCredentials;
|
|
}
|
|
|
|
maxtok = _maxtok;
|
|
authed = false;
|
|
spn = _spn;
|
|
|
|
SecBuffer outbuf;
|
|
outbuf.BufferType = SECBUFFER_TOKEN;
|
|
outbuf.cbBuffer = 0;
|
|
outbuf.pvBuffer = NULL;
|
|
|
|
SecBufferDesc outbufdesc;
|
|
outbufdesc.ulVersion = SECBUFFER_VERSION;
|
|
outbufdesc.cBuffers = 1;
|
|
outbufdesc.pBuffers = &outbuf;
|
|
|
|
ctx_attr_req = 0;
|
|
|
|
// not strictly required, but some SSPI calls seem to always
|
|
// allocate memory, so for consistency we'll explicity
|
|
// request to have it that way all the time
|
|
ctx_attr_req |= ISC_REQ_ALLOCATE_MEMORY;
|
|
|
|
// required by SASL GSSAPI RFC
|
|
ctx_attr_req |= ISC_REQ_INTEGRITY;
|
|
|
|
// required for security layer
|
|
ctx_attr_req |= ISC_REQ_MUTUAL_AUTH;
|
|
ctx_attr_req |= ISC_REQ_SEQUENCE_DETECT;
|
|
|
|
// required for encryption
|
|
ctx_attr_req |= ISC_REQ_CONFIDENTIALITY;
|
|
|
|
// other options that may be of use, but we currently aren't
|
|
// using:
|
|
// ISC_REQ_DELEGATE
|
|
// ISC_REQ_REPLAY_DETECT
|
|
|
|
ret = sspi.W->InitializeSecurityContextW(
|
|
&user_cred,
|
|
0, // NULL for the first call
|
|
(SEC_WCHAR *)spn.utf16(),
|
|
ctx_attr_req,
|
|
0,
|
|
SECURITY_NETWORK_DREP,
|
|
0, // NULL for first call
|
|
0,
|
|
&ctx,
|
|
&outbufdesc,
|
|
&ctx_attr,
|
|
0); // don't care about expiration
|
|
sspi_log(QString("InitializeSecurityContext(*, 0, ...) = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret == SEC_E_OK || ret == SEC_I_CONTINUE_NEEDED)
|
|
{
|
|
if(outbuf.pvBuffer)
|
|
{
|
|
first_out_token.resize(outbuf.cbBuffer);
|
|
memcpy(first_out_token.data(), outbuf.pvBuffer, outbuf.cbBuffer);
|
|
|
|
SECURITY_STATUS fret = sspi.W->FreeContextBuffer(outbuf.pvBuffer);
|
|
sspi_log(QString("FreeContextBuffer() = %1\n").arg(SECURITY_STATUS_toString(fret)));
|
|
}
|
|
|
|
if(ret == SEC_E_OK)
|
|
authed = true;
|
|
}
|
|
else
|
|
{
|
|
// ret was an error, or some unexpected value like
|
|
// SEC_I_COMPLETE_NEEDED or
|
|
// SEC_I_COMPLETE_AND_CONTINUE, which i believe are
|
|
// not used for kerberos
|
|
|
|
lastErrorCode = ret;
|
|
|
|
ret = sspi.W->FreeCredentialsHandle(&user_cred);
|
|
sspi_log(QString("FreeCredentialsHandle() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
|
|
return ErrorInitialize;
|
|
}
|
|
|
|
initialized = true;
|
|
first_step = true;
|
|
|
|
return Success;
|
|
}
|
|
|
|
ReturnCode step(const QByteArray &in, QByteArray *out, bool *authenticated)
|
|
{
|
|
if(authed)
|
|
{
|
|
out->clear();
|
|
*authenticated = true;
|
|
return Success;
|
|
}
|
|
|
|
if(first_step)
|
|
{
|
|
// ignore 'in'
|
|
|
|
*out = first_out_token;
|
|
first_out_token.clear();
|
|
|
|
first_step = false;
|
|
}
|
|
else
|
|
{
|
|
SecBuffer outbuf;
|
|
outbuf.BufferType = SECBUFFER_TOKEN;
|
|
outbuf.cbBuffer = 0;
|
|
outbuf.pvBuffer = NULL;
|
|
|
|
SecBufferDesc outbufdesc;
|
|
outbufdesc.ulVersion = SECBUFFER_VERSION;
|
|
outbufdesc.cBuffers = 1;
|
|
outbufdesc.pBuffers = &outbuf;
|
|
|
|
SecBuffer inbuf;
|
|
inbuf.BufferType = SECBUFFER_TOKEN;
|
|
inbuf.cbBuffer = in.size();
|
|
inbuf.pvBuffer = (void *)in.data();
|
|
|
|
SecBufferDesc inbufdesc;
|
|
inbufdesc.ulVersion = SECBUFFER_VERSION;
|
|
inbufdesc.cBuffers = 1;
|
|
inbufdesc.pBuffers = &inbuf;
|
|
|
|
SECURITY_STATUS ret = sspi.W->InitializeSecurityContextW(
|
|
&user_cred,
|
|
&ctx,
|
|
(SEC_WCHAR *)spn.utf16(),
|
|
ctx_attr_req,
|
|
0,
|
|
SECURITY_NETWORK_DREP,
|
|
&inbufdesc,
|
|
0,
|
|
&ctx,
|
|
&outbufdesc,
|
|
&ctx_attr,
|
|
0); // don't care about expiration
|
|
sspi_log(QString("InitializeSecurityContext(*, ctx, ...) = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret == SEC_E_OK || ret == SEC_I_CONTINUE_NEEDED)
|
|
{
|
|
if(outbuf.pvBuffer)
|
|
{
|
|
out->resize(outbuf.cbBuffer);
|
|
memcpy(out->data(), outbuf.pvBuffer, outbuf.cbBuffer);
|
|
|
|
SECURITY_STATUS fret = sspi.W->FreeContextBuffer(outbuf.pvBuffer);
|
|
sspi_log(QString("FreeContextBuffer() = %1\n").arg(SECURITY_STATUS_toString(fret)));
|
|
}
|
|
else
|
|
out->clear();
|
|
|
|
if(ret == SEC_E_OK)
|
|
authed = true;
|
|
}
|
|
else
|
|
{
|
|
// ret was an error, or some unexpected value like
|
|
// SEC_I_COMPLETE_NEEDED or
|
|
// SEC_I_COMPLETE_AND_CONTINUE, which i believe are
|
|
// not used for kerberos
|
|
|
|
lastErrorCode = ret;
|
|
|
|
ret = sspi.W->DeleteSecurityContext(&ctx);
|
|
sspi_log(QString("DeleteSecurityContext() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
|
|
ret = sspi.W->FreeCredentialsHandle(&user_cred);
|
|
sspi_log(QString("FreeCredentialsHandle() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
|
|
initialized = false;
|
|
return ErrorInitialize;
|
|
}
|
|
}
|
|
|
|
*authenticated = authed;
|
|
return Success;
|
|
}
|
|
|
|
// private
|
|
bool ensure_sizes_cached()
|
|
{
|
|
if(!have_sizes)
|
|
{
|
|
SECURITY_STATUS ret = sspi.W->QueryContextAttributesW(&ctx, SECPKG_ATTR_SIZES, &ctx_sizes);
|
|
sspi_log(QString("QueryContextAttributes(ctx, SECPKG_ATTR_SIZES, ...) = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret != SEC_E_OK)
|
|
{
|
|
lastErrorCode = ret;
|
|
return false;
|
|
}
|
|
|
|
// for some reason, querying the stream sizes returns
|
|
// SEC_E_UNSUPPORTED_FUNCTION on my system, even
|
|
// though the docs say it should work and putty
|
|
// wingss also calls it.
|
|
|
|
// all we really need is cbMaximumMessage, and since
|
|
// we can't query for it, we'll hard code some
|
|
// value. according to putty wingss, the max size
|
|
// is essentially unbounded anyway, so this should
|
|
// be safe to do.
|
|
ctx_streamSizes.cbMaximumMessage = 8192;
|
|
|
|
//ret = sspi.W->QueryContextAttributesW(&ctx, SECPKG_ATTR_STREAM_SIZES, &ctx_streamSizes);
|
|
//sspi_log(QString("QueryContextAttributes(ctx, SECPKG_ATTR_STREAM_SIZES, ...) = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
//if(ret != SEC_E_OK)
|
|
//{
|
|
// lastErrorCode = ret;
|
|
// return ErrorQueryContext;
|
|
//}
|
|
|
|
have_sizes = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ReturnCode get_max_encrypt_size(int *max)
|
|
{
|
|
if(!ensure_sizes_cached())
|
|
return ErrorQueryContext;
|
|
|
|
*max = cap_to_int<unsigned long>(ctx_streamSizes.cbMaximumMessage);
|
|
|
|
return Success;
|
|
}
|
|
|
|
ReturnCode encode(const QByteArray &in, QByteArray *out, bool encrypt)
|
|
{
|
|
if(!ensure_sizes_cached())
|
|
return ErrorQueryContext;
|
|
|
|
QByteArray tokenbuf(ctx_sizes.cbSecurityTrailer, 0);
|
|
QByteArray padbuf(ctx_sizes.cbBlockSize, 0);
|
|
|
|
// we assume here, like putty wingss, that the output size is
|
|
// less than or equal to the input size. honestly I don't
|
|
// see how this is clear from the SSPI documentation, but
|
|
// the code seems to work so we'll go with it...
|
|
QByteArray databuf = in;
|
|
|
|
SecBuffer buf[3];
|
|
buf[0].BufferType = SECBUFFER_TOKEN;
|
|
buf[0].cbBuffer = tokenbuf.size();
|
|
buf[0].pvBuffer = tokenbuf.data();
|
|
buf[1].BufferType = SECBUFFER_DATA;
|
|
buf[1].cbBuffer = databuf.size();
|
|
buf[1].pvBuffer = databuf.data();
|
|
buf[2].BufferType = SECBUFFER_PADDING;
|
|
buf[2].cbBuffer = padbuf.size();
|
|
buf[2].pvBuffer = padbuf.data();
|
|
|
|
SecBufferDesc bufdesc;
|
|
bufdesc.ulVersion = SECBUFFER_VERSION;
|
|
bufdesc.cBuffers = 3;
|
|
bufdesc.pBuffers = buf;
|
|
|
|
SECURITY_STATUS ret = sspi.W->EncryptMessage(&ctx, encrypt ? 0 : SECQOP_WRAP_NO_ENCRYPT, &bufdesc, 0);
|
|
sspi_log(QString("EncryptMessage() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret != SEC_E_OK)
|
|
{
|
|
lastErrorCode = ret;
|
|
return ErrorEncrypt;
|
|
}
|
|
|
|
QByteArray fullbuf;
|
|
for(int i = 0; i < (int)bufdesc.cBuffers; ++i)
|
|
fullbuf += QByteArray((const char *)bufdesc.pBuffers[i].pvBuffer, bufdesc.pBuffers[i].cbBuffer);
|
|
|
|
*out = fullbuf;
|
|
return Success;
|
|
}
|
|
|
|
ReturnCode decode(const QByteArray &in, QByteArray *out, bool *encrypted)
|
|
{
|
|
SecBuffer buf[2];
|
|
buf[0].BufferType = SECBUFFER_DATA;
|
|
buf[0].cbBuffer = 0;
|
|
buf[0].pvBuffer = NULL;
|
|
buf[1].BufferType = SECBUFFER_STREAM;
|
|
buf[1].cbBuffer = in.size();
|
|
buf[1].pvBuffer = (void *)in.data();
|
|
|
|
SecBufferDesc bufdesc;
|
|
bufdesc.ulVersion = SECBUFFER_VERSION;
|
|
bufdesc.cBuffers = 2;
|
|
bufdesc.pBuffers = buf;
|
|
|
|
ULONG fQOP;
|
|
SECURITY_STATUS ret = sspi.W->DecryptMessage(&ctx, &bufdesc, 0, &fQOP);
|
|
sspi_log(QString("DecryptMessage() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
if(ret == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
return NeedMoreData;
|
|
}
|
|
else if(ret != SEC_E_OK)
|
|
{
|
|
lastErrorCode = ret;
|
|
return ErrorDecrypt;
|
|
}
|
|
|
|
if(buf[0].pvBuffer)
|
|
{
|
|
out->resize(buf[0].cbBuffer);
|
|
memcpy(out->data(), buf[0].pvBuffer, buf[0].cbBuffer);
|
|
|
|
SECURITY_STATUS ret = sspi.W->FreeContextBuffer(buf[0].pvBuffer);
|
|
sspi_log(QString("FreeContextBuffer() = %1\n").arg(SECURITY_STATUS_toString(ret)));
|
|
}
|
|
else
|
|
out->clear();
|
|
|
|
if(fQOP & SECQOP_WRAP_NO_ENCRYPT)
|
|
*encrypted = false;
|
|
else
|
|
*encrypted = true;
|
|
|
|
return Success;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
// SaslGssapiSession
|
|
//----------------------------------------------------------------------------
|
|
// this class wraps KerberosSession to perform SASL GSSAPI. it hides away
|
|
// any SSPI details, and is thus very simple to use.
|
|
class SaslGssapiSession
|
|
{
|
|
private:
|
|
int secflags;
|
|
KerberosSession sess;
|
|
int mode; // 0 = kerberos tokens, 1 = app packets
|
|
bool authed;
|
|
QByteArray inbuf;
|
|
|
|
int max_enc_size; // most we can encrypt to them
|
|
int max_dec_size; // most we are expected to decrypt from them
|
|
|
|
public:
|
|
enum SecurityFlags
|
|
{
|
|
// only one of these should be set
|
|
RequireAtLeastInt = 0x0001,
|
|
RequireConf = 0x0002
|
|
};
|
|
|
|
enum ReturnCode
|
|
{
|
|
Success,
|
|
ErrorInit,
|
|
ErrorKerberosStep,
|
|
ErrorAppTokenDecode,
|
|
ErrorAppTokenIsEncrypted,
|
|
ErrorAppTokenWrongSize,
|
|
ErrorAppTokenInvalid,
|
|
ErrorAppTokenEncode,
|
|
ErrorLayerTooWeak,
|
|
ErrorEncode,
|
|
ErrorDecode,
|
|
ErrorDecodeTooLarge,
|
|
ErrorDecodeNotEncrypted
|
|
};
|
|
|
|
// set this before auth, if you want
|
|
QString authzid;
|
|
|
|
// read-only
|
|
bool do_layer, do_conf;
|
|
|
|
SaslGssapiSession()
|
|
{
|
|
}
|
|
|
|
ReturnCode init(const QString &proto, const QString &fqdn, int _secflags)
|
|
{
|
|
secflags = _secflags;
|
|
mode = 0; // kerberos tokens
|
|
authed = false;
|
|
|
|
do_layer = false;
|
|
do_conf = false;
|
|
|
|
if(sess.init(proto + '/' + fqdn) != KerberosSession::Success)
|
|
return ErrorInit;
|
|
|
|
return Success;
|
|
}
|
|
|
|
ReturnCode step(const QByteArray &in, QByteArray *out, bool *authenticated)
|
|
{
|
|
if(authed)
|
|
{
|
|
out->clear();
|
|
*authenticated = true;
|
|
return Success;
|
|
}
|
|
|
|
if(mode == 0) // kerberos tokens
|
|
{
|
|
bool kerb_authed;
|
|
if(sess.step(in, out, &kerb_authed) != KerberosSession::Success)
|
|
return ErrorKerberosStep;
|
|
|
|
if(kerb_authed)
|
|
mode = 1; // switch to app packets
|
|
|
|
*authenticated = false;
|
|
}
|
|
else if(mode == 1)
|
|
{
|
|
bool layerPossible = false;
|
|
bool encryptionPossible = false;
|
|
if(sess.ctx_attr & ISC_RET_INTEGRITY &&
|
|
sess.ctx_attr & ISC_RET_MUTUAL_AUTH &&
|
|
sess.ctx_attr & ISC_RET_SEQUENCE_DETECT)
|
|
{
|
|
layerPossible = true;
|
|
|
|
if(sess.ctx_attr & ISC_RET_CONFIDENTIALITY)
|
|
encryptionPossible = true;
|
|
}
|
|
|
|
if(layerPossible)
|
|
{
|
|
if(encryptionPossible)
|
|
sspi_log("Kerberos application data protection supported (with encryption)\n");
|
|
else
|
|
sspi_log("Kerberos application data protection supported (without encryption)\n");
|
|
}
|
|
else
|
|
sspi_log("No Kerberos application data protection supported\n");
|
|
|
|
QByteArray decbuf;
|
|
bool encrypted;
|
|
if(sess.decode(in, &decbuf, &encrypted) != KerberosSession::Success)
|
|
{
|
|
sspi_log("Error decoding application token\n");
|
|
return ErrorAppTokenDecode;
|
|
}
|
|
|
|
// this packet is supposed to be not encrypted
|
|
if(encrypted)
|
|
{
|
|
sspi_log("Error, application token is encrypted\n");
|
|
return ErrorAppTokenIsEncrypted;
|
|
}
|
|
|
|
// packet must be exactly 4 bytes
|
|
if(decbuf.size() != 4)
|
|
{
|
|
sspi_log("Error, application token is the wrong size\n");
|
|
return ErrorAppTokenWrongSize;
|
|
}
|
|
|
|
QString str;
|
|
str.sprintf("%02x%02x%02x%02x",
|
|
(unsigned int)decbuf[0],
|
|
(unsigned int)decbuf[1],
|
|
(unsigned int)decbuf[2],
|
|
(unsigned int)decbuf[3]);
|
|
sspi_log(QString("Received application token: [%1]\n").arg(str));
|
|
|
|
unsigned char layermask = decbuf[0];
|
|
quint32 maxsize = 0;
|
|
maxsize += (unsigned char)decbuf[1];
|
|
maxsize <<= 8;
|
|
maxsize += (unsigned char)decbuf[2];
|
|
maxsize <<= 8;
|
|
maxsize += (unsigned char)decbuf[3];
|
|
|
|
// if 'None' is all that is supported, then maxsize
|
|
// must be zero
|
|
if(layermask == 1 && maxsize > 0)
|
|
{
|
|
sspi_log("Error, supports no security layer but the max buffer size is not zero\n");
|
|
return ErrorAppTokenInvalid;
|
|
}
|
|
|
|
// convert maxsize to a signed int, by capping it
|
|
int _max_enc_size = cap_to_int<quint32>(maxsize);
|
|
|
|
// parse out layermask
|
|
bool saslLayerNone = false;
|
|
bool saslLayerInt = false;
|
|
bool saslLayerConf = false;
|
|
QStringList saslLayerModes;
|
|
if(layermask & 1)
|
|
{
|
|
saslLayerNone = true;
|
|
saslLayerModes += "None";
|
|
}
|
|
if(layermask & 2)
|
|
{
|
|
saslLayerInt = true;
|
|
saslLayerModes += "Int";
|
|
}
|
|
if(layermask & 4)
|
|
{
|
|
saslLayerConf = true;
|
|
saslLayerModes += "Conf";
|
|
}
|
|
|
|
sspi_log(QString("Security layer modes supported: %1\n").arg(saslLayerModes.join(", ")));
|
|
sspi_log(QString("Security layer max packet size: %1\n").arg(maxsize));
|
|
|
|
// create outbound application token
|
|
QByteArray obuf(4, 0); // initially 4 bytes
|
|
|
|
// set one of use_conf or use_int, but not both
|
|
bool use_conf = false;
|
|
bool use_int = false;
|
|
if(encryptionPossible && saslLayerConf)
|
|
use_conf = true;
|
|
else if(layerPossible && saslLayerInt)
|
|
use_int = true;
|
|
else if(!saslLayerNone)
|
|
{
|
|
sspi_log("Error, no compatible layer mode supported, not even 'None'\n");
|
|
return ErrorLayerTooWeak;
|
|
}
|
|
|
|
if((secflags & RequireConf) && !use_conf)
|
|
{
|
|
sspi_log("Error, 'Conf' required but not supported\n");
|
|
return ErrorLayerTooWeak;
|
|
}
|
|
|
|
if((secflags & RequireAtLeastInt) && !use_conf && !use_int)
|
|
{
|
|
sspi_log("Error, 'Conf' or 'Int' required but not supported\n");
|
|
return ErrorLayerTooWeak;
|
|
}
|
|
|
|
if(use_conf)
|
|
{
|
|
sspi_log("Using 'Conf' layer\n");
|
|
obuf[0] = 4;
|
|
}
|
|
else if(use_int)
|
|
{
|
|
sspi_log("Using 'Int' layer\n");
|
|
obuf[0] = 2;
|
|
}
|
|
else
|
|
{
|
|
sspi_log("Using 'None' layer\n");
|
|
obuf[0] = 1;
|
|
}
|
|
|
|
// as far as i can tell, there is no max decrypt size
|
|
// with sspi. so we'll just pick some number.
|
|
// a small one is good, to prevent excessive input
|
|
// buffering.
|
|
// in other parts of the code, it is assumed this
|
|
// value is less than INT_MAX
|
|
int _max_dec_size = 8192; // same as cyrus
|
|
|
|
// max size must be zero if no security layer is used
|
|
if(!use_conf && !use_int)
|
|
_max_dec_size = 0;
|
|
|
|
obuf[1] = (unsigned char)((_max_dec_size >> 16) & 0xff);
|
|
obuf[2] = (unsigned char)((_max_dec_size >> 8) & 0xff);
|
|
obuf[3] = (unsigned char)((_max_dec_size) & 0xff);
|
|
|
|
if(!authzid.isEmpty())
|
|
obuf += authzid.toUtf8();
|
|
|
|
str.clear();
|
|
for(int n = 0; n < obuf.size(); ++n)
|
|
str += QString().sprintf("%02x", (unsigned int)obuf[n]);
|
|
sspi_log(QString("Sending application token: [%1]\n").arg(str));
|
|
|
|
if(sess.encode(obuf, out, false) != KerberosSession::Success)
|
|
{
|
|
sspi_log("Error encoding application token\n");
|
|
return ErrorAppTokenEncode;
|
|
}
|
|
|
|
if(use_conf || use_int)
|
|
do_layer = true;
|
|
if(use_conf)
|
|
do_conf = true;
|
|
|
|
max_enc_size = _max_enc_size;
|
|
max_dec_size = _max_dec_size;
|
|
|
|
*authenticated = true;
|
|
}
|
|
|
|
return Success;
|
|
}
|
|
|
|
ReturnCode encode(const QByteArray &in, QByteArray *out)
|
|
{
|
|
if(!do_layer)
|
|
{
|
|
*out = in;
|
|
return Success;
|
|
}
|
|
|
|
int local_encrypt_max;
|
|
if(sess.get_max_encrypt_size(&local_encrypt_max) != KerberosSession::Success)
|
|
return ErrorEncode;
|
|
|
|
// send no more per-packet than what our local system will
|
|
// encrypt AND no more than what the peer will accept.
|
|
int chunk_max = qMin(local_encrypt_max, max_enc_size);
|
|
if(chunk_max < 8)
|
|
{
|
|
sspi_log("Error, chunk_max is ridiculously small\n");
|
|
return ErrorEncode;
|
|
}
|
|
|
|
QByteArray total_out;
|
|
|
|
// break up into packets, if input exceeds max size
|
|
int encoded = 0;
|
|
while(encoded < in.size())
|
|
{
|
|
int left = in.size() - encoded;
|
|
int chunk_size = qMin(left, chunk_max);
|
|
QByteArray kerb_in = QByteArray::fromRawData(in.data() + encoded, chunk_size);
|
|
QByteArray kerb_out;
|
|
if(sess.encode(kerb_in, &kerb_out, do_conf) != KerberosSession::Success)
|
|
return ErrorEncode;
|
|
|
|
QByteArray sasl_out(kerb_out.size() + 4, 0);
|
|
|
|
// SASL (not GSS!) uses a 4 byte length prefix
|
|
quint32 len = kerb_out.size();
|
|
sasl_out[0] = (unsigned char)((len >> 24) & 0xff);
|
|
sasl_out[1] = (unsigned char)((len >> 16) & 0xff);
|
|
sasl_out[2] = (unsigned char)((len >> 8) & 0xff);
|
|
sasl_out[3] = (unsigned char)((len) & 0xff);
|
|
|
|
memcpy(sasl_out.data() + 4, kerb_out.data(), kerb_out.size());
|
|
|
|
encoded += kerb_in.size();
|
|
total_out += sasl_out;
|
|
}
|
|
|
|
*out = total_out;
|
|
return Success;
|
|
}
|
|
|
|
ReturnCode decode(const QByteArray &in, QByteArray *out)
|
|
{
|
|
if(!do_layer)
|
|
{
|
|
*out = in;
|
|
return Success;
|
|
}
|
|
|
|
// buffer the input
|
|
inbuf += in;
|
|
|
|
QByteArray total_out;
|
|
|
|
// the buffer might contain many packets. decode as many
|
|
// as possible
|
|
while(1)
|
|
{
|
|
if(inbuf.size() < 4)
|
|
{
|
|
// need more data
|
|
break;
|
|
}
|
|
|
|
// SASL (not GSS!) uses a 4 byte length prefix
|
|
quint32 ulen = 0;
|
|
ulen += (unsigned char)inbuf[0];
|
|
ulen <<= 8;
|
|
ulen += (unsigned char)inbuf[1];
|
|
ulen <<= 8;
|
|
ulen += (unsigned char)inbuf[2];
|
|
ulen <<= 8;
|
|
ulen += (unsigned char)inbuf[3];
|
|
|
|
// this capping is safe, because we throw error if the value
|
|
// is too large, and an acceptable value will always be
|
|
// lower than the maximum integer size.
|
|
int len = cap_to_int<quint32>(ulen);
|
|
if(len > max_dec_size)
|
|
{
|
|
// this means the peer ignored our max buffer size.
|
|
// very evil, or we're under attack.
|
|
sspi_log("Error, decode size too large\n");
|
|
return ErrorDecodeTooLarge;
|
|
}
|
|
|
|
if(inbuf.size() - 4 < len)
|
|
{
|
|
// need more data
|
|
break;
|
|
}
|
|
|
|
// take the packet from the inbuf
|
|
QByteArray kerb_in = inbuf.mid(4, len);
|
|
memmove(inbuf.data(), inbuf.data() + len + 4, inbuf.size() - len - 4);
|
|
inbuf.resize(inbuf.size() - len - 4);
|
|
|
|
// count incomplete packets as errors, since they are sasl framed
|
|
QByteArray kerb_out;
|
|
bool encrypted;
|
|
if(sess.decode(kerb_in, &kerb_out, &encrypted) != KerberosSession::Success)
|
|
return ErrorDecode;
|
|
|
|
if(do_conf && !encrypted)
|
|
{
|
|
sspi_log("Error, received unencrypted packet in 'Conf' mode\n");
|
|
return ErrorDecodeNotEncrypted;
|
|
}
|
|
|
|
total_out += kerb_out;
|
|
}
|
|
|
|
*out = total_out;
|
|
return Success;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
// SaslWinGss
|
|
//----------------------------------------------------------------------------
|
|
class SaslWinGss : public SASLContext
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
SaslGssapiSession *sess;
|
|
bool authed;
|
|
Result _result;
|
|
SASL::AuthCondition _authCondition;
|
|
QByteArray _step_to_net;
|
|
QByteArray _to_net, _to_app;
|
|
int enc;
|
|
SafeTimer resultsReadyTrigger;
|
|
|
|
QString opt_service, opt_host, opt_ext_id;
|
|
int opt_ext_ssf;
|
|
int opt_flags;
|
|
int opt_minssf, opt_maxssf;
|
|
|
|
QString opt_authzid;
|
|
|
|
SaslWinGss(Provider *p) :
|
|
SASLContext(p),
|
|
sess(0),
|
|
resultsReadyTrigger(this)
|
|
{
|
|
connect(&resultsReadyTrigger, SIGNAL(timeout()), SIGNAL(resultsReady()));
|
|
resultsReadyTrigger.setSingleShot(true);
|
|
}
|
|
|
|
Provider::Context *clone() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
virtual void reset()
|
|
{
|
|
delete sess;
|
|
sess = 0;
|
|
authed = false;
|
|
_step_to_net.clear();
|
|
_to_net.clear();
|
|
_to_app.clear();
|
|
resultsReadyTrigger.stop();
|
|
|
|
opt_service.clear();
|
|
opt_host.clear();
|
|
opt_ext_id.clear();
|
|
opt_authzid.clear();
|
|
}
|
|
|
|
virtual void setup(const QString &service, const QString &host, const HostPort *local, const HostPort *remote, const QString &ext_id, int ext_ssf)
|
|
{
|
|
// unused by this provider
|
|
Q_UNUSED(local);
|
|
Q_UNUSED(remote);
|
|
|
|
opt_service = service;
|
|
opt_host = host;
|
|
opt_ext_id = ext_id;
|
|
opt_ext_ssf = ext_ssf;
|
|
}
|
|
|
|
virtual void setConstraints(SASL::AuthFlags f, int minSSF, int maxSSF)
|
|
{
|
|
opt_flags = (int)f;
|
|
opt_minssf = minSSF;
|
|
opt_maxssf = maxSSF;
|
|
}
|
|
|
|
virtual void startClient(const QStringList &mechlist, bool allowClientSendFirst)
|
|
{
|
|
// we only support GSSAPI
|
|
if(!mechlist.contains("GSSAPI"))
|
|
{
|
|
_result = Error;
|
|
_authCondition = SASL::NoMechanism;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
// GSSAPI (or this provider) doesn't meet these requirements
|
|
if(opt_flags & SASL::RequireForwardSecrecy
|
|
|| opt_flags & SASL::RequirePassCredentials
|
|
|| !allowClientSendFirst)
|
|
{
|
|
_result = Error;
|
|
_authCondition = SASL::NoMechanism;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
sess = new SaslGssapiSession;
|
|
sess->authzid = opt_authzid;
|
|
|
|
int secflags = 0;
|
|
if(opt_minssf > 1)
|
|
secflags |= SaslGssapiSession::RequireConf;
|
|
else if(opt_minssf == 1)
|
|
secflags |= SaslGssapiSession::RequireAtLeastInt;
|
|
|
|
SaslGssapiSession::ReturnCode ret = sess->init(opt_service, opt_host, secflags);
|
|
if(ret != SaslGssapiSession::Success)
|
|
{
|
|
_result = Error;
|
|
_authCondition = SASL::AuthFail;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
ret = sess->step(QByteArray(), &_step_to_net, &authed);
|
|
if(ret != SaslGssapiSession::Success)
|
|
{
|
|
_result = Error;
|
|
_authCondition = SASL::AuthFail;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
if(authed)
|
|
_result = Success;
|
|
else
|
|
_result = Continue;
|
|
|
|
resultsReadyTrigger.start();
|
|
}
|
|
|
|
virtual void startServer(const QString &realm, bool disableServerSendLast)
|
|
{
|
|
// server mode unsupported at this time
|
|
Q_UNUSED(realm);
|
|
Q_UNUSED(disableServerSendLast);
|
|
|
|
_result = Error;
|
|
_authCondition = SASL::AuthFail;
|
|
resultsReadyTrigger.start();
|
|
}
|
|
|
|
virtual void serverFirstStep(const QString &mech, const QByteArray *clientInit)
|
|
{
|
|
// server mode unsupported at this time
|
|
Q_UNUSED(mech);
|
|
Q_UNUSED(clientInit);
|
|
}
|
|
|
|
virtual void nextStep(const QByteArray &from_net)
|
|
{
|
|
SaslGssapiSession::ReturnCode ret = sess->step(from_net, &_step_to_net, &authed);
|
|
if(ret != SaslGssapiSession::Success)
|
|
{
|
|
_result = Error;
|
|
_authCondition = SASL::AuthFail;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
if(authed)
|
|
_result = Success;
|
|
else
|
|
_result = Continue;
|
|
|
|
resultsReadyTrigger.start();
|
|
}
|
|
|
|
virtual void tryAgain()
|
|
{
|
|
// we never ask for params, so this function should never be
|
|
// called
|
|
}
|
|
|
|
virtual void update(const QByteArray &from_net, const QByteArray &from_app)
|
|
{
|
|
SaslGssapiSession::ReturnCode ret;
|
|
QByteArray a;
|
|
|
|
if(!from_net.isEmpty())
|
|
{
|
|
ret = sess->decode(from_net, &a);
|
|
if(ret != SaslGssapiSession::Success)
|
|
{
|
|
_result = Error;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
_to_app += a;
|
|
}
|
|
|
|
if(!from_app.isEmpty())
|
|
{
|
|
ret = sess->encode(from_app, &a);
|
|
if(ret != SaslGssapiSession::Success)
|
|
{
|
|
_result = Error;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
_to_net += a;
|
|
enc += from_app.size();
|
|
}
|
|
|
|
_result = Success;
|
|
resultsReadyTrigger.start();
|
|
}
|
|
|
|
virtual bool waitForResultsReady(int msecs)
|
|
{
|
|
// all results are ready instantly
|
|
Q_UNUSED(msecs);
|
|
resultsReadyTrigger.stop();
|
|
return true;
|
|
}
|
|
|
|
virtual Result result() const
|
|
{
|
|
return _result;
|
|
}
|
|
|
|
virtual QStringList mechlist() const
|
|
{
|
|
// server mode unsupported at this time
|
|
return QStringList();
|
|
}
|
|
|
|
virtual QString mech() const
|
|
{
|
|
// only mech we support :)
|
|
return "GSSAPI";
|
|
}
|
|
|
|
virtual bool haveClientInit() const
|
|
{
|
|
// GSSAPI always has a client init response
|
|
return true;
|
|
}
|
|
|
|
virtual QByteArray stepData() const
|
|
{
|
|
return _step_to_net;
|
|
}
|
|
|
|
virtual QByteArray to_net()
|
|
{
|
|
QByteArray a = _to_net;
|
|
_to_net.clear();
|
|
enc = 0;
|
|
return a;
|
|
}
|
|
|
|
virtual int encoded() const
|
|
{
|
|
return enc;
|
|
}
|
|
|
|
virtual QByteArray to_app()
|
|
{
|
|
QByteArray a = _to_app;
|
|
_to_app.clear();
|
|
return a;
|
|
}
|
|
|
|
virtual int ssf() const
|
|
{
|
|
if(!sess->do_layer)
|
|
return 0;
|
|
|
|
if(sess->do_conf)
|
|
{
|
|
// TODO: calculate this value somehow? for now we'll
|
|
// just hard code it to 56, which is basically what
|
|
// cyrus does.
|
|
return 56;
|
|
}
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
virtual SASL::AuthCondition authCondition() const
|
|
{
|
|
return _authCondition;
|
|
}
|
|
|
|
virtual SASL::Params clientParams() const
|
|
{
|
|
// we never ask for params
|
|
return SASL::Params();
|
|
}
|
|
|
|
virtual void setClientParams(const QString *user, const QString *authzid, const SecureArray *pass, const QString *realm)
|
|
{
|
|
// unused by this provider
|
|
Q_UNUSED(user);
|
|
Q_UNUSED(pass);
|
|
Q_UNUSED(realm);
|
|
|
|
if(authzid)
|
|
{
|
|
opt_authzid = *authzid;
|
|
if(sess)
|
|
sess->authzid = opt_authzid;
|
|
}
|
|
else
|
|
{
|
|
opt_authzid.clear();
|
|
if(sess)
|
|
sess->authzid.clear();
|
|
}
|
|
}
|
|
|
|
virtual QStringList realmlist() const
|
|
{
|
|
// unused by this provider
|
|
return QStringList();
|
|
}
|
|
|
|
virtual QString username() const
|
|
{
|
|
// server mode unsupported at this time
|
|
return QString();
|
|
}
|
|
|
|
virtual QString authzid() const
|
|
{
|
|
// server mode unsupported at this time
|
|
return QString();
|
|
}
|
|
};
|
|
|
|
#endif // !defined(FORWARD_ONLY)
|
|
|
|
//----------------------------------------------------------------------------
|
|
// MetaSasl
|
|
//----------------------------------------------------------------------------
|
|
#ifndef FORWARD_ONLY
|
|
class wingssProvider;
|
|
static bool wingssProvider_have_sspi(wingssProvider *provider);
|
|
#endif
|
|
|
|
class MetaSasl : public SASLContext
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
SASLContext *s;
|
|
|
|
Result _result;
|
|
SASL::AuthCondition _authCondition;
|
|
SafeTimer resultsReadyTrigger;
|
|
Synchronizer sync;
|
|
bool waiting;
|
|
|
|
QString opt_service, opt_host;
|
|
bool have_opt_local, have_opt_remote;
|
|
HostPort opt_local, opt_remote;
|
|
QString opt_ext_id;
|
|
int opt_ext_ssf;
|
|
SASL::AuthFlags opt_flags;
|
|
int opt_minssf, opt_maxssf;
|
|
|
|
bool have_opt_user, have_opt_authzid, have_opt_pass, have_opt_realm;
|
|
QString opt_user, opt_authzid, opt_realm;
|
|
SecureArray opt_pass;
|
|
|
|
class SaslProvider
|
|
{
|
|
public:
|
|
SASLContext *sasl;
|
|
bool ready;
|
|
QStringList mechlist;
|
|
|
|
SaslProvider() :
|
|
sasl(0),
|
|
ready(false)
|
|
{
|
|
}
|
|
};
|
|
|
|
QList<SaslProvider> saslProviders;
|
|
bool serverInit_active;
|
|
Result serverInit_result;
|
|
QStringList serverInit_mechlist;
|
|
|
|
MetaSasl(Provider *p) :
|
|
SASLContext(p),
|
|
resultsReadyTrigger(this),
|
|
sync(this),
|
|
waiting(false),
|
|
serverInit_active(false)
|
|
{
|
|
s = 0;
|
|
|
|
have_opt_user = false;
|
|
have_opt_authzid = false;
|
|
have_opt_pass = false;
|
|
have_opt_realm = false;
|
|
|
|
connect(&resultsReadyTrigger, SIGNAL(timeout()), SIGNAL(resultsReady()));
|
|
resultsReadyTrigger.setSingleShot(true);
|
|
}
|
|
|
|
~MetaSasl()
|
|
{
|
|
delete s;
|
|
}
|
|
|
|
virtual Provider::Context *clone() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void clearSaslProviders()
|
|
{
|
|
foreach(const SaslProvider &sp, saslProviders)
|
|
delete sp.sasl;
|
|
|
|
saslProviders.clear();
|
|
}
|
|
|
|
virtual void reset()
|
|
{
|
|
delete s;
|
|
s = 0;
|
|
|
|
resultsReadyTrigger.stop();
|
|
|
|
opt_service.clear();
|
|
opt_host.clear();
|
|
opt_ext_id.clear();
|
|
opt_user.clear();
|
|
opt_authzid.clear();
|
|
opt_realm.clear();
|
|
opt_pass.clear();
|
|
|
|
have_opt_user = false;
|
|
have_opt_authzid = false;
|
|
have_opt_pass = false;
|
|
have_opt_realm = false;
|
|
|
|
clearSaslProviders();
|
|
serverInit_active = false;
|
|
serverInit_mechlist.clear();
|
|
}
|
|
|
|
virtual void setup(const QString &service, const QString &host, const HostPort *local, const HostPort *remote, const QString &ext_id, int ext_ssf)
|
|
{
|
|
opt_service = service;
|
|
opt_host = host;
|
|
have_opt_local = false;
|
|
have_opt_remote = false;
|
|
if(local)
|
|
{
|
|
have_opt_local = true;
|
|
opt_local = *local;
|
|
}
|
|
if(remote)
|
|
{
|
|
have_opt_remote = true;
|
|
opt_remote = *remote;
|
|
}
|
|
opt_ext_id = ext_id;
|
|
opt_ext_ssf = ext_ssf;
|
|
}
|
|
|
|
virtual void setConstraints(SASL::AuthFlags f, int minSSF, int maxSSF)
|
|
{
|
|
opt_flags = f;
|
|
opt_minssf = minSSF;
|
|
opt_maxssf = maxSSF;
|
|
}
|
|
|
|
virtual void startClient(const QStringList &mechlist, bool allowClientSendFirst)
|
|
{
|
|
#ifndef FORWARD_ONLY
|
|
if(mechlist.contains("GSSAPI") && wingssProvider_have_sspi((wingssProvider *)provider()))
|
|
{
|
|
s = new SaslWinGss(provider());
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
// collect providers supporting sasl, in priority order.
|
|
// (note: providers() is in priority order already)
|
|
ProviderList list;
|
|
foreach(Provider *p, providers())
|
|
{
|
|
QString name = p->name();
|
|
|
|
// skip ourself
|
|
if(name == PROVIDER_NAME)
|
|
continue;
|
|
|
|
if(p->features().contains("sasl"))
|
|
{
|
|
// FIXME: improve qca so this isn't needed
|
|
SASL tmp_object_to_cause_plugin_init(0, name);
|
|
|
|
// add to the list
|
|
list += p;
|
|
}
|
|
}
|
|
|
|
if(!list.isEmpty())
|
|
{
|
|
// use the first
|
|
s = static_cast<SASLContext *>(list.first()->createContext("sasl"));
|
|
}
|
|
#ifndef FORWARD_ONLY
|
|
}
|
|
#endif
|
|
|
|
if(!s)
|
|
{
|
|
// no usable provider? throw error
|
|
_result = Error;
|
|
_authCondition = SASL::NoMechanism;
|
|
resultsReadyTrigger.start();
|
|
return;
|
|
}
|
|
|
|
// proper parenting
|
|
s->setParent(this);
|
|
|
|
const HostPort *pLocal = 0;
|
|
const HostPort *pRemote = 0;
|
|
if(have_opt_local)
|
|
pLocal = &opt_local;
|
|
if(have_opt_remote)
|
|
pRemote = &opt_remote;
|
|
s->setup(opt_service, opt_host, pLocal, pRemote, opt_ext_id, opt_ext_ssf);
|
|
s->setConstraints(opt_flags, opt_minssf, opt_maxssf);
|
|
|
|
const QString *pUser = 0;
|
|
const QString *pAuthzid = 0;
|
|
const SecureArray *pPass = 0;
|
|
const QString *pRealm = 0;
|
|
if(have_opt_user)
|
|
pUser = &opt_user;
|
|
if(have_opt_authzid)
|
|
pAuthzid = &opt_authzid;
|
|
if(have_opt_pass)
|
|
pPass = &opt_pass;
|
|
if(have_opt_realm)
|
|
pRealm = &opt_realm;
|
|
s->setClientParams(pUser, pAuthzid, pPass, pRealm);
|
|
connect(s, SIGNAL(resultsReady()), SLOT(s_resultsReady()));
|
|
|
|
QString str = QString("MetaSasl: client using %1 with %2 mechs").arg(s->provider()->name(), QString::number(mechlist.count()));
|
|
QCA_logTextMessage(str, Logger::Debug);
|
|
s->startClient(mechlist, allowClientSendFirst);
|
|
}
|
|
|
|
virtual void startServer(const QString &realm, bool disableServerSendLast)
|
|
{
|
|
// collect mechs of all providers, by starting all of them
|
|
serverInit_active = true;
|
|
|
|
ProviderList list;
|
|
foreach(Provider *p, providers())
|
|
{
|
|
QString name = p->name();
|
|
|
|
// skip ourself
|
|
if(name == PROVIDER_NAME)
|
|
continue;
|
|
|
|
if(p->features().contains("sasl"))
|
|
{
|
|
// FIXME: improve qca so this isn't needed
|
|
SASL tmp_object_to_cause_plugin_init(0, name);
|
|
|
|
// add to the list
|
|
list += p;
|
|
}
|
|
}
|
|
|
|
foreach(Provider *p, list)
|
|
{
|
|
SaslProvider sp;
|
|
|
|
sp.sasl = static_cast<SASLContext *>(p->createContext("sasl"));
|
|
|
|
// proper parenting
|
|
sp.sasl->setParent(this);
|
|
|
|
const HostPort *pLocal = 0;
|
|
const HostPort *pRemote = 0;
|
|
if(have_opt_local)
|
|
pLocal = &opt_local;
|
|
if(have_opt_remote)
|
|
pRemote = &opt_remote;
|
|
sp.sasl->setup(opt_service, opt_host, pLocal, pRemote, opt_ext_id, opt_ext_ssf);
|
|
sp.sasl->setConstraints(opt_flags, opt_minssf, opt_maxssf);
|
|
connect(sp.sasl, SIGNAL(resultsReady()), SLOT(serverInit_resultsReady()));
|
|
|
|
saslProviders += sp;
|
|
|
|
sp.sasl->startServer(realm, disableServerSendLast);
|
|
}
|
|
}
|
|
|
|
virtual void serverFirstStep(const QString &mech, const QByteArray *clientInit)
|
|
{
|
|
// choose a provider based on the mech
|
|
int at = choose_provider(mech);
|
|
|
|
// extract it and clean up the rest
|
|
SASLContext *sasl = saslProviders[at].sasl;
|
|
sasl->disconnect(this);
|
|
saslProviders.removeAt(at);
|
|
clearSaslProviders();
|
|
serverInit_active = false;
|
|
|
|
// use the chosen provider
|
|
s = sasl;
|
|
connect(s, SIGNAL(resultsReady()), SLOT(s_resultsReady()));
|
|
s->serverFirstStep(mech, clientInit);
|
|
}
|
|
|
|
virtual void nextStep(const QByteArray &from_net)
|
|
{
|
|
s->nextStep(from_net);
|
|
}
|
|
|
|
virtual void tryAgain()
|
|
{
|
|
s->tryAgain();
|
|
}
|
|
|
|
virtual void update(const QByteArray &from_net, const QByteArray &from_app)
|
|
{
|
|
s->update(from_net, from_app);
|
|
}
|
|
|
|
virtual bool waitForResultsReady(int msecs)
|
|
{
|
|
if(serverInit_active)
|
|
{
|
|
waiting = true;
|
|
bool ret = sync.waitForCondition(msecs);
|
|
waiting = false;
|
|
return ret;
|
|
}
|
|
else if(s)
|
|
return s->waitForResultsReady(msecs);
|
|
else
|
|
return true;
|
|
}
|
|
|
|
virtual Result result() const
|
|
{
|
|
if(serverInit_active)
|
|
return serverInit_result;
|
|
else if(s)
|
|
return s->result();
|
|
else
|
|
return _result;
|
|
}
|
|
|
|
virtual QStringList mechlist() const
|
|
{
|
|
return serverInit_mechlist;
|
|
}
|
|
|
|
virtual QString mech() const
|
|
{
|
|
if(s)
|
|
return s->mech();
|
|
else
|
|
return QString();
|
|
}
|
|
|
|
virtual bool haveClientInit() const
|
|
{
|
|
return s->haveClientInit();
|
|
}
|
|
|
|
virtual QByteArray stepData() const
|
|
{
|
|
return s->stepData();
|
|
}
|
|
|
|
virtual QByteArray to_net()
|
|
{
|
|
return s->to_net();
|
|
}
|
|
|
|
virtual int encoded() const
|
|
{
|
|
return s->encoded();
|
|
}
|
|
|
|
virtual QByteArray to_app()
|
|
{
|
|
return s->to_app();
|
|
}
|
|
|
|
virtual int ssf() const
|
|
{
|
|
return s->ssf();
|
|
}
|
|
|
|
virtual SASL::AuthCondition authCondition() const
|
|
{
|
|
if(s)
|
|
return s->authCondition();
|
|
else
|
|
return _authCondition;
|
|
}
|
|
|
|
virtual SASL::Params clientParams() const
|
|
{
|
|
return s->clientParams();
|
|
}
|
|
|
|
virtual void setClientParams(const QString *user, const QString *authzid, const SecureArray *pass, const QString *realm)
|
|
{
|
|
if(!s)
|
|
{
|
|
if(user)
|
|
{
|
|
have_opt_user = true;
|
|
opt_user = *user;
|
|
}
|
|
if(authzid)
|
|
{
|
|
have_opt_authzid = true;
|
|
opt_authzid = *authzid;
|
|
}
|
|
if(pass)
|
|
{
|
|
have_opt_pass = true;
|
|
opt_pass = *pass;
|
|
}
|
|
if(realm)
|
|
{
|
|
have_opt_realm = true;
|
|
opt_realm = *realm;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s->setClientParams(user, authzid, pass, realm);
|
|
}
|
|
}
|
|
|
|
virtual QStringList realmlist() const
|
|
{
|
|
return s->realmlist();
|
|
}
|
|
|
|
virtual QString username() const
|
|
{
|
|
return s->username();
|
|
}
|
|
|
|
virtual QString authzid() const
|
|
{
|
|
return s->authzid();
|
|
}
|
|
|
|
private slots:
|
|
void s_resultsReady()
|
|
{
|
|
emit resultsReady();
|
|
}
|
|
|
|
void serverInit_resultsReady()
|
|
{
|
|
SASLContext *sasl = (SASLContext *)sender();
|
|
|
|
int at = -1;
|
|
for(int n = 0; n < saslProviders.count(); ++n)
|
|
{
|
|
if(saslProviders[n].sasl == sasl)
|
|
{
|
|
at = n;
|
|
break;
|
|
}
|
|
}
|
|
if(at == -1)
|
|
return;
|
|
|
|
if(sasl->result() == Success)
|
|
{
|
|
saslProviders[at].ready = true;
|
|
saslProviders[at].mechlist = sasl->mechlist();
|
|
|
|
bool allReady = true;
|
|
for(int n = 0; n < saslProviders.count(); ++n)
|
|
{
|
|
if(!saslProviders[n].ready)
|
|
{
|
|
allReady = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(allReady)
|
|
{
|
|
// indicate success
|
|
serverInit_result = Success;
|
|
serverInit_mechlist = combine_mechlists();
|
|
|
|
if(waiting)
|
|
sync.conditionMet();
|
|
else
|
|
emit resultsReady();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete sasl;
|
|
saslProviders.removeAt(at);
|
|
|
|
if(saslProviders.isEmpty())
|
|
{
|
|
// indicate error
|
|
serverInit_result = Error;
|
|
_authCondition = SASL::NoMechanism;
|
|
|
|
if(waiting)
|
|
sync.conditionMet();
|
|
else
|
|
emit resultsReady();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
QStringList combine_mechlists()
|
|
{
|
|
QStringList out;
|
|
|
|
// FIXME: consider prioritizing certain mechs?
|
|
foreach(const SaslProvider &sp, saslProviders)
|
|
{
|
|
foreach(const QString &mech, sp.mechlist)
|
|
{
|
|
if(!out.contains(mech))
|
|
out += mech;
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
int choose_provider(const QString &mech)
|
|
{
|
|
int at = -1;
|
|
|
|
// find a provider for this mech
|
|
for(int n = 0; n < saslProviders.count(); ++n)
|
|
{
|
|
const SaslProvider &sp = saslProviders[n];
|
|
if(sp.mechlist.contains(mech))
|
|
{
|
|
at = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// no provider offered this mech? then just go with the
|
|
// first provider
|
|
if(at == -1)
|
|
at = 0;
|
|
|
|
return at;
|
|
}
|
|
};
|
|
|
|
class wingssProvider : public Provider
|
|
{
|
|
public:
|
|
mutable QMutex m;
|
|
mutable bool forced_priority;
|
|
bool have_sspi;
|
|
|
|
wingssProvider() :
|
|
forced_priority(false),
|
|
have_sspi(false)
|
|
{
|
|
}
|
|
|
|
virtual void init()
|
|
{
|
|
#ifndef FORWARD_ONLY
|
|
sspi_set_logger(do_log);
|
|
have_sspi = sspi_load();
|
|
#endif
|
|
}
|
|
|
|
~wingssProvider()
|
|
{
|
|
#ifndef FORWARD_ONLY
|
|
if(have_sspi)
|
|
sspi_unload();
|
|
#endif
|
|
}
|
|
|
|
virtual int qcaVersion() const
|
|
{
|
|
return QCA_VERSION;
|
|
}
|
|
|
|
virtual QString name() const
|
|
{
|
|
return PROVIDER_NAME;
|
|
}
|
|
|
|
virtual QStringList features() const
|
|
{
|
|
// due to context manipulation, this plugin is only designed
|
|
// for qca 2.0 at this time, and not a possible 2.1, etc.
|
|
if((qcaVersion() & 0xffff00) > 0x020000)
|
|
return QStringList();
|
|
|
|
m.lock();
|
|
// FIXME: we need to prioritize this plugin to be higher
|
|
// than other plugins by default. unfortunately there's
|
|
// no clean way to do this. we can't change our priority
|
|
// until we are slotted into the qca provider system. the
|
|
// constructor, qcaVersion, and name functions are all
|
|
// guaranteed to be called, but unfortunately they are
|
|
// only guaranteed to be called before the slotting. the
|
|
// features function is essentially guaranteed to be called
|
|
// after the slotting though, since QCA::isSupported()
|
|
// trips it, and any proper QCA app will call isSupported.
|
|
if(!forced_priority)
|
|
{
|
|
forced_priority = true;
|
|
setProviderPriority(PROVIDER_NAME, 0);
|
|
}
|
|
m.unlock();
|
|
|
|
QStringList list;
|
|
list += "sasl";
|
|
return list;
|
|
}
|
|
|
|
virtual Context *createContext(const QString &type)
|
|
{
|
|
if(type == "sasl")
|
|
return new MetaSasl(this);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
#ifndef FORWARD_ONLY
|
|
static void do_log(const QString &str)
|
|
{
|
|
QCA_logTextMessage(str, Logger::Debug);
|
|
}
|
|
#endif
|
|
};
|
|
|
|
#ifndef FORWARD_ONLY
|
|
bool wingssProvider_have_sspi(wingssProvider *provider)
|
|
{
|
|
return provider->have_sspi;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
using namespace wingssQCAPlugin;
|
|
|
|
//----------------------------------------------------------------------------
|
|
// wingssPlugin
|
|
//----------------------------------------------------------------------------
|
|
|
|
class wingssPlugin : public QObject, public QCAPlugin
|
|
{
|
|
Q_OBJECT
|
|
#if QT_VERSION >= 0x050000
|
|
Q_PLUGIN_METADATA(IID "com.affinix.qca.Plugin/1.0")
|
|
#endif
|
|
Q_INTERFACES(QCAPlugin)
|
|
|
|
public:
|
|
virtual Provider *createProvider() { return new wingssProvider; }
|
|
};
|
|
|
|
#include "qca-wingss.moc"
|
|
|
|
#if QT_VERSION < 0x050000
|
|
Q_EXPORT_PLUGIN2(qca_wingss, wingssPlugin)
|
|
#endif
|