4
0
mirror of https://github.com/QuasarApp/qca.git synced 2025-05-11 18:29:33 +00:00
Albert Astals Cid dce16514fe Enable a clazy CI
Starting with only missing-qobject-macro check enabled
2020-01-29 14:31:55 +01:00

950 lines
20 KiB
C++

/*
* qca-sasl.cpp - SASL plugin for QCA
* Copyright (C) 2003-2007 Justin Karneges <justin@affinix.com>
* Copyright (C) 2006 Michail Pishchagin <mblsha@gmail.com>
*
* 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 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 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 <QDebug>
#include <QtPlugin>
extern "C"
{
#include <sasl/sasl.h>
}
#include <QStringList>
#include <QList>
#include <QFile>
#define SASL_BUFSIZE 8192
#define SASL_APP "qca"
using namespace QCA;
namespace saslQCAPlugin {
class saslProvider : public Provider
{
public:
saslProvider();
void init() override;
~saslProvider();
int qcaVersion() const override;
QString name() const override;
QString credit() const override;
QStringList features() const override;
Context *createContext(const QString &type) override;
bool client_init;
bool server_init;
QString appname;
};
//----------------------------------------------------------------------------
// SASLParams
//----------------------------------------------------------------------------
class SASLParams
{
public:
class SParams
{
public:
bool user, authzid, pass, realm;
};
SASLParams()
{
reset();
}
void reset()
{
resetNeed();
resetHave();
foreach(char *result, results)
delete result;
results.clear();
}
void resetNeed()
{
need.user = false;
need.authzid = false;
need.pass = false;
need.realm = false;
}
void resetHave()
{
have.user = false;
have.authzid = false;
have.pass = false;
have.realm = false;
}
void setUsername(const QString &s)
{
have.user = true;
user = s;
}
void setAuthzid(const QString &s)
{
have.authzid = true;
authzid = s;
}
void setPassword(const SecureArray &s)
{
have.pass = true;
pass = QString::fromUtf8(s.toByteArray());
}
void setRealm(const QString &s)
{
have.realm = true;
realm = s;
}
void applyInteract(sasl_interact_t *needp)
{
for(int n = 0; needp[n].id != SASL_CB_LIST_END; ++n) {
if(needp[n].id == SASL_CB_AUTHNAME)
need.user = true; // yes, I know these
if(needp[n].id == SASL_CB_USER)
need.authzid = true; // look backwards
if(needp[n].id == SASL_CB_PASS)
need.pass = true;
if(needp[n].id == SASL_CB_GETREALM)
need.realm = true;
}
}
void extractHave(sasl_interact_t *needp)
{
for(int n = 0; needp[n].id != SASL_CB_LIST_END; ++n) {
if(needp[n].id == SASL_CB_AUTHNAME && have.user)
setValue(&needp[n], user);
if(needp[n].id == SASL_CB_USER && have.authzid)
setValue(&needp[n], authzid);
if(needp[n].id == SASL_CB_PASS && have.pass)
setValue(&needp[n], pass);
if(needp[n].id == SASL_CB_GETREALM && have.realm)
setValue(&needp[n], realm);
}
}
bool missingAny() const
{
if((need.user && !have.user) /*|| (need.authzid && !have.authzid)*/ || (need.pass && !have.pass) /*|| (need.realm && !have.realm)*/)
return true;
return false;
}
SParams missing() const
{
SParams np = need;
if(have.user)
np.user = false;
if(have.authzid)
np.authzid = false;
if(have.pass)
np.pass = false;
if(have.realm)
np.realm = false;
return np;
}
void setValue(sasl_interact_t *i, const QString &s)
{
if(i->result)
return;
QByteArray cs = s.toUtf8();
int len = cs.length();
char *p = new char[len+1];
memcpy(p, cs.data(), len);
p[len] = 0;
i->result = p;
i->len = len;
// record this
results.append(p);
}
QList<char *> results;
SParams need;
SParams have;
QString user, authzid, pass, realm;
};
static QByteArray makeByteArray(const void *in, unsigned int len)
{
QByteArray buf(len, 0);
memcpy(buf.data(), in, len);
return buf;
}
static QString addrString(const SASLContext::HostPort &hp)
{
return (hp.addr + ';' + QString::number(hp.port));
}
//----------------------------------------------------------------------------
// saslContext
//----------------------------------------------------------------------------
class saslContext : public SASLContext
{
Q_OBJECT
saslProvider *g;
// core props
QString service, host;
QString localAddr, remoteAddr;
// security props
int secflags;
int ssf_min, ssf_max;
QString ext_authid;
int ext_ssf;
sasl_conn_t *con;
sasl_interact_t *need;
int maxoutbuf;
sasl_callback_t *callbacks;
// state
bool servermode;
int step;
bool in_sendFirst;
QByteArray in_buf;
QString in_mech;
bool in_useClientInit;
QByteArray in_clientInit;
QString out_mech;
// bool out_useClientInit;
// QByteArray out_clientInit;
QByteArray out_buf;
SASLParams params;
QString sc_username, sc_authzid;
bool ca_flag, ca_done, ca_skip;
int last_r;
int result_ssf;
Result result_result;
bool result_haveClientInit;
QStringList result_mechlist;
SASL::AuthCondition result_authCondition;
QByteArray result_to_net;
QByteArray result_plain;
int result_encoded;
private:
void resetState()
{
if(con) {
sasl_dispose(&con);
con = 0;
}
need = 0;
if(callbacks) {
delete callbacks;
callbacks = 0;
}
localAddr = "";
remoteAddr = "";
maxoutbuf = 128;
sc_username = "";
sc_authzid = "";
result_authCondition = SASL::AuthFail;
result_haveClientInit = false;
result_mechlist.clear();
result_plain.clear();
result_plain.clear();
result_plain.clear();
result_ssf = 0;
}
void resetParams()
{
params.reset();
secflags = 0;
ssf_min = 0;
ssf_max = 0;
ext_authid = "";
ext_ssf = 0;
}
bool setsecprops()
{
sasl_security_properties_t secprops;
secprops.min_ssf = ssf_min;
secprops.max_ssf = ssf_max;
secprops.maxbufsize = SASL_BUFSIZE;
secprops.property_names = NULL;
secprops.property_values = NULL;
secprops.security_flags = secflags;
int r = sasl_setprop(con, SASL_SEC_PROPS, &secprops);
if(r != SASL_OK)
return false;
if(!ext_authid.isEmpty()) {
const QByteArray ext_authidBA = ext_authid.toLatin1();
const char *authid = ext_authidBA.data();
sasl_ssf_t ssf = ext_ssf;
r = sasl_setprop(con, SASL_SSF_EXTERNAL, &ssf);
if(r != SASL_OK)
return false;
r = sasl_setprop(con, SASL_AUTH_EXTERNAL, authid);
if(r != SASL_OK)
return false;
}
return true;
}
void setAuthCondition(int r)
{
//qDebug() << "authcondition: " << r;
SASL::AuthCondition x;
switch(r) {
// common
case SASL_NOMECH: x = SASL::NoMechanism; break;
case SASL_BADPROT: x = SASL::BadProtocol; break;
// client
case SASL_BADSERV: x = SASL::BadServer; break;
// server
case SASL_BADAUTH: x = SASL::BadAuth; break;
case SASL_NOAUTHZ: x = SASL::NoAuthzid; break;
case SASL_TOOWEAK: x = SASL::TooWeak; break;
case SASL_ENCRYPT: x = SASL::NeedEncrypt; break;
case SASL_EXPIRED: x = SASL::Expired; break;
case SASL_DISABLED: x = SASL::Disabled; break;
case SASL_NOUSER: x = SASL::NoUser; break;
case SASL_UNAVAIL: x = SASL::RemoteUnavailable; break;
default: x = SASL::AuthFail; break;
}
result_authCondition = x;
}
void getssfparams()
{
const void *maybe_sff;
if( SASL_OK == sasl_getprop( con, SASL_SSF, &maybe_sff ) )
result_ssf = *(const int*)maybe_sff;
const void *maybe_maxoutbuf;
if (SASL_OK == sasl_getprop( con, SASL_MAXOUTBUF, &maybe_maxoutbuf ) )
maxoutbuf = *(const int*)maybe_maxoutbuf;
}
static int scb_checkauth(sasl_conn_t *, void *context, const char *requested_user, unsigned, const char *auth_identity, unsigned, const char *, unsigned, struct propctx *)
{
saslContext *that = (saslContext *)context;
that->sc_username = auth_identity; // yeah yeah, it looks
that->sc_authzid = requested_user; // backwards, but it is right
that->ca_flag = true;
return SASL_OK;
}
void clientTryAgain()
{
result_haveClientInit = false;
if(step == 0) {
const char *clientout, *m;
unsigned int clientoutlen;
need = 0;
QString list = result_mechlist.join(" ");
int r;
while(1) {
if(need)
params.extractHave(need);
if(in_sendFirst)
r = sasl_client_start(con, list.toLatin1().data(), &need, &clientout, &clientoutlen, &m);
else
r = sasl_client_start(con, list.toLatin1().data(), &need, NULL, NULL, &m);
if(r != SASL_INTERACT)
break;
params.applyInteract(need);
if(params.missingAny()) {
out_mech = m;
result_result = Params;
return;
}
}
if(r != SASL_OK && r != SASL_CONTINUE) {
setAuthCondition(r);
result_result = Error;
return;
}
out_mech = m;
if(in_sendFirst && clientout) {
out_buf = makeByteArray(clientout, clientoutlen);
result_haveClientInit = true;
}
++step;
if(r == SASL_OK) {
getssfparams();
result_result = Success;
return;
}
result_result = Continue;
return;
}
else {
const char *clientout;
unsigned int clientoutlen;
int r;
while(1) {
if(need)
params.extractHave(need);
//printf("sasl_client_step(con, {%s}, %d, &need, &clientout, &clientoutlen);\n", in_buf.data(), in_buf.size());
r = sasl_client_step(con, in_buf.data(), in_buf.size(), &need, &clientout, &clientoutlen);
//printf("returned: %d\n", r);
if(r != SASL_INTERACT)
break;
params.applyInteract(need);
if(params.missingAny()) {
result_result = Params;
return;
}
}
if(r != SASL_OK && r != SASL_CONTINUE) {
setAuthCondition(r);
result_result = Error;
return;
}
out_buf = makeByteArray(clientout, clientoutlen);
if(r == SASL_OK) {
getssfparams();
result_result = Success;
return;
}
result_result = Continue;
return;
}
}
void serverTryAgain()
{
if(step == 0) {
if(!ca_skip) {
const char *clientin = 0;
unsigned int clientinlen = 0;
if(in_useClientInit) {
clientin = in_clientInit.data();
clientinlen = in_clientInit.size();
}
const char *serverout;
unsigned int serveroutlen;
ca_flag = false;
int r = sasl_server_start(con, in_mech.toLatin1().data(), clientin, clientinlen, &serverout, &serveroutlen);
if(r != SASL_OK && r != SASL_CONTINUE) {
setAuthCondition(r);
result_result = Error;
return;
}
out_buf = makeByteArray(serverout, serveroutlen);
last_r = r;
if(ca_flag && !ca_done) {
ca_done = true;
ca_skip = true;
result_result = AuthCheck;
return;
}
}
ca_skip = false;
++step;
if(last_r == SASL_OK) {
getssfparams();
result_result = Success;
return;
}
result_result = Continue;
return;
}
else {
if(!ca_skip) {
const char *serverout;
unsigned int serveroutlen;
int r = sasl_server_step(con, in_buf.data(), in_buf.size(), &serverout, &serveroutlen);
if(r != SASL_OK && r != SASL_CONTINUE) {
setAuthCondition(r);
result_result = Error;
return;
}
if(r == SASL_OK)
out_buf.resize(0);
else
out_buf = makeByteArray(serverout, serveroutlen);
last_r = r;
if(ca_flag && !ca_done) {
ca_done = true;
ca_skip = true;
result_result = AuthCheck;
return;
}
}
ca_skip = false;
if(last_r == SASL_OK) {
getssfparams();
result_result = Success;
return;
}
result_result = Continue;
return;
}
}
bool sasl_endecode(const QByteArray &in, QByteArray *out, bool enc)
{
// no security
if(result_ssf == 0) {
*out = in;
return true;
}
int at = 0;
out->resize(0);
while(1) {
int size = in.size() - at;
if(size == 0)
break;
if(size > maxoutbuf)
size = maxoutbuf;
const char *outbuf;
unsigned len;
int r;
if(enc)
r = sasl_encode(con, in.data() + at, size, &outbuf, &len);
else
r = sasl_decode(con, in.data() + at, size, &outbuf, &len);
if(r != SASL_OK)
return false;
int oldsize = out->size();
out->resize(oldsize + len);
memcpy(out->data() + oldsize, outbuf, len);
at += size;
}
return true;
}
void doResultsReady()
{
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
}
public:
saslContext(saslProvider *_g)
: SASLContext(_g)
{
result_result = Success;
g = _g;
con = 0;
callbacks = 0;
reset();
}
~saslContext()
{
reset();
}
Provider::Context *clone() const override
{
return 0;
}
Result result() const override
{
return result_result;
}
void reset() override
{
resetState();
resetParams();
}
void setup(const QString &_service, const QString &_host, const HostPort *local, const HostPort *remote, const QString &ext_id, int _ext_ssf) override
{
service = _service;
host = _host;
localAddr = local ? addrString(*local) : "";
remoteAddr = remote ? addrString(*remote) : "";
ext_authid = ext_id;
ext_ssf = _ext_ssf;
}
int ssf() const override
{
return result_ssf;
}
void startClient(const QStringList &mechlist, bool allowClientSendFirst) override
{
resetState();
in_sendFirst = allowClientSendFirst;
if(!g->client_init) {
sasl_client_init(NULL);
g->client_init = true;
}
callbacks = new sasl_callback_t[5];
callbacks[0].id = SASL_CB_GETREALM;
callbacks[0].proc = 0;
callbacks[0].context = 0;
callbacks[1].id = SASL_CB_USER;
callbacks[1].proc = 0;
callbacks[1].context = 0;
callbacks[2].id = SASL_CB_AUTHNAME;
callbacks[2].proc = 0;
callbacks[2].context = 0;
callbacks[3].id = SASL_CB_PASS;
callbacks[3].proc = 0;
callbacks[3].context = 0;
callbacks[4].id = SASL_CB_LIST_END;
callbacks[4].proc = 0;
callbacks[4].context = 0;
result_result = Error;
int r = sasl_client_new(service.toLatin1().data(), host.toLatin1().data(), localAddr.isEmpty() ? 0 : localAddr.toLatin1().data(), remoteAddr.isEmpty() ? 0 : remoteAddr.toLatin1().data(), callbacks, 0, &con);
if(r != SASL_OK) {
setAuthCondition(r);
doResultsReady();
return;
}
if(!setsecprops())
{
doResultsReady();
return;
}
result_mechlist = mechlist;
servermode = false;
step = 0;
result_result = Success;
clientTryAgain();
doResultsReady();
return;
}
// TODO: make use of disableServerSendLast
void startServer(const QString &realm, bool disableServerSendLast) override
{
Q_UNUSED(disableServerSendLast);
resetState();
g->appname = SASL_APP;
if(!g->server_init) {
sasl_server_init(NULL, QFile::encodeName(g->appname).constData());
g->server_init = true;
}
callbacks = new sasl_callback_t[2];
callbacks[0].id = SASL_CB_PROXY_POLICY;
callbacks[0].proc = (int(*)())scb_checkauth;
callbacks[0].context = this;
callbacks[1].id = SASL_CB_LIST_END;
callbacks[1].proc = 0;
callbacks[1].context = 0;
result_result = Error;
int r = sasl_server_new(service.toLatin1().data(), host.toLatin1().data(), !realm.isEmpty() ? realm.toLatin1().data() : 0, localAddr.isEmpty() ? 0 : localAddr.toLatin1().data(), remoteAddr.isEmpty() ? 0 : remoteAddr.toLatin1().data(), callbacks, 0, &con);
if(r != SASL_OK) {
setAuthCondition(r);
doResultsReady();
return;
}
if(!setsecprops())
{
doResultsReady();
return;
}
const char *ml;
r = sasl_listmech(con, 0, 0, " ", 0, &ml, 0, 0);
if(r != SASL_OK)
return;
result_mechlist = QString::fromUtf8(ml).split(' ');
servermode = true;
step = 0;
ca_done = false;
ca_skip = false;
result_result = Success;
doResultsReady();
return;
}
void serverFirstStep(const QString &mech, const QByteArray *clientInit) override
{
in_mech = mech;
if(clientInit) {
in_useClientInit = true;
in_clientInit = *clientInit;
}
else
in_useClientInit = false;
serverTryAgain();
doResultsReady();
}
SASL::Params clientParams() const override
{
SASLParams::SParams sparams = params.missing();
return SASL::Params(sparams.user, sparams.authzid, sparams.pass, sparams.realm);
}
void setClientParams(const QString *user, const QString *authzid, const SecureArray *pass, const QString *realm) override
{
if(user)
params.setUsername(*user);
if(authzid)
params.setAuthzid(*authzid);
if(pass)
params.setPassword(*pass);
if(realm)
params.setRealm(*realm);
}
QString username() const override
{
return sc_username;
}
QString authzid() const override
{
return sc_authzid;
}
void nextStep(const QByteArray &from_net) override
{
in_buf = from_net;
tryAgain();
}
void tryAgain() override
{
if(servermode)
serverTryAgain();
else
clientTryAgain();
doResultsReady();
}
QString mech() const override
{
if (servermode)
return in_mech;
else
return out_mech;
}
QStringList mechlist() const override
{
return result_mechlist;
}
QStringList realmlist() const override
{
// TODO
return QStringList();
}
void setConstraints(SASL::AuthFlags f, int minSSF, int maxSSF) override
{
int sf = 0;
if( !(f & SASL::AllowPlain) )
sf |= SASL_SEC_NOPLAINTEXT;
// if( !(f & SASL::AllowActiveVulnerable) ) // TODO
// sf |= SASL_SEC_NOACTIVE;
// if( !(f & SASL::AllowDictVulnerable) ) // TODO
// sf |= SASL_SEC_NODICTIONARY;
if( !(f & SASL::AllowAnonymous) )
sf |= SASL_SEC_NOANONYMOUS;
if( f & SASL::RequireForwardSecrecy )
sf |= SASL_SEC_FORWARD_SECRECY;
if( f & SASL::RequirePassCredentials )
sf |= SASL_SEC_PASS_CREDENTIALS;
if( f & SASL::RequireMutualAuth )
sf |= SASL_SEC_MUTUAL_AUTH;
secflags = sf;
ssf_min = minSSF;
ssf_max = maxSSF;
}
bool waitForResultsReady(int msecs) override
{
// TODO: for now, all operations block anyway
Q_UNUSED(msecs);
return true;
}
void update(const QByteArray &from_net, const QByteArray &from_app) override
{
bool ok = true;
if(!from_app.isEmpty())
ok = sasl_endecode(from_app, &result_to_net, true);
if(ok && !from_net.isEmpty())
ok = sasl_endecode(from_net, &result_plain, false);
result_result = ok ? Success : Error;
result_encoded = from_app.size();
//printf("update (from_net=%d, to_net=%d, from_app=%d, to_app=%d)\n", from_net.size(), result_to_net.size(), from_app.size(), result_plain.size());
doResultsReady();
}
bool haveClientInit() const override
{
return result_haveClientInit;
}
QByteArray stepData() const override
{
return out_buf;
}
QByteArray to_net() override
{
QByteArray a = result_to_net;
result_to_net.clear();
return a;
}
int encoded() const override
{
return result_encoded;
}
QByteArray to_app() override
{
QByteArray a = result_plain;
result_plain.clear();
return a;
}
SASL::AuthCondition authCondition() const override
{
return result_authCondition;
}
};
//----------------------------------------------------------------------------
// saslProvider
//----------------------------------------------------------------------------
saslProvider::saslProvider()
{
client_init = false;
server_init = false;
}
void saslProvider::init()
{
}
saslProvider::~saslProvider()
{
if(client_init || server_init)
sasl_done();
}
int saslProvider::qcaVersion() const
{
return QCA_VERSION;
}
QString saslProvider::name() const
{
return "qca-cyrus-sasl";
}
QString saslProvider::credit() const
{
return QString(); // TODO
}
QStringList saslProvider::features() const
{
QStringList list;
list += "sasl";
return list;
}
Provider::Context *saslProvider::createContext(const QString &type)
{
if ( type == "sasl" )
return new saslContext( this );
return 0;
}
} // namespace saslQCAPlugin
using namespace saslQCAPlugin;
//----------------------------------------------------------------------------
// saslPlugin
//----------------------------------------------------------------------------
class saslPlugin : public QObject, public QCAPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.affinix.qca.Plugin/1.0")
Q_INTERFACES(QCAPlugin)
public:
Provider *createProvider() override { return new saslProvider; }
};
#include "qca-cyrus-sasl.moc"