initial commit
This commit is contained in:
commit
9d20827c46
2469 changed files with 470994 additions and 0 deletions
676
iris-legacy/iris/xmpp-core/simplesasl.cpp
Normal file
676
iris-legacy/iris/xmpp-core/simplesasl.cpp
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
/*
|
||||
* simplesasl.cpp - Simple SASL implementation
|
||||
* Copyright (C) 2003 Justin Karneges
|
||||
*
|
||||
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "simplesasl.h"
|
||||
|
||||
#include <qhostaddress.h>
|
||||
#include <qstringlist.h>
|
||||
#include <q3ptrlist.h>
|
||||
#include <QList>
|
||||
#include <qca.h>
|
||||
#include <Q3CString>
|
||||
#include <stdlib.h>
|
||||
#include <QtCrypto>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef YAPSI
|
||||
#include <QUrl>
|
||||
#include <QVariantMap>
|
||||
#include <QDateTime>
|
||||
#include "yaonline.h"
|
||||
#endif
|
||||
|
||||
#define SIMPLESASL_PLAIN
|
||||
|
||||
namespace XMPP {
|
||||
struct Prop
|
||||
{
|
||||
Q3CString var, val;
|
||||
};
|
||||
|
||||
class PropList : public QList<Prop>
|
||||
{
|
||||
public:
|
||||
PropList() : QList<Prop>()
|
||||
{
|
||||
}
|
||||
|
||||
void set(const Q3CString &var, const Q3CString &val)
|
||||
{
|
||||
Prop p;
|
||||
p.var = var;
|
||||
p.val = val;
|
||||
append(p);
|
||||
}
|
||||
|
||||
Q3CString get(const Q3CString &var)
|
||||
{
|
||||
for(ConstIterator it = begin(); it != end(); ++it) {
|
||||
if((*it).var == var)
|
||||
return (*it).val;
|
||||
}
|
||||
return Q3CString();
|
||||
}
|
||||
|
||||
Q3CString toString() const
|
||||
{
|
||||
Q3CString str;
|
||||
bool first = true;
|
||||
for(ConstIterator it = begin(); it != end(); ++it) {
|
||||
if(!first)
|
||||
str += ',';
|
||||
if ((*it).var == "realm" || (*it).var == "nonce" || (*it).var == "username" || (*it).var == "cnonce" || (*it).var == "digest-uri" || (*it).var == "authzid")
|
||||
str += (*it).var + "=\"" + (*it).val + '\"';
|
||||
else
|
||||
str += (*it).var + "=" + (*it).val;
|
||||
first = false;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
bool fromString(const QByteArray &str)
|
||||
{
|
||||
PropList list;
|
||||
int at = 0;
|
||||
while(1) {
|
||||
while (at < str.length() && (str[at] == ',' || str[at] == ' ' || str[at] == '\t'))
|
||||
++at;
|
||||
int n = str.find('=', at);
|
||||
if(n == -1)
|
||||
break;
|
||||
Q3CString var, val;
|
||||
var = str.mid(at, n-at);
|
||||
at = n + 1;
|
||||
if(str[at] == '\"') {
|
||||
++at;
|
||||
n = str.find('\"', at);
|
||||
if(n == -1)
|
||||
break;
|
||||
val = str.mid(at, n-at);
|
||||
at = n + 1;
|
||||
}
|
||||
else {
|
||||
n = at;
|
||||
while (n < str.length() && str[n] != ',' && str[n] != ' ' && str[n] != '\t')
|
||||
++n;
|
||||
val = str.mid(at, n-at);
|
||||
at = n;
|
||||
}
|
||||
Prop prop;
|
||||
prop.var = var;
|
||||
if (var == "qop" || var == "cipher") {
|
||||
int a = 0;
|
||||
while (a < val.length()) {
|
||||
while (a < val.length() && (val[a] == ',' || val[a] == ' ' || val[a] == '\t'))
|
||||
++a;
|
||||
if (a == val.length())
|
||||
break;
|
||||
n = a+1;
|
||||
while (n < val.length() && val[n] != ',' && val[n] != ' ' && val[n] != '\t')
|
||||
++n;
|
||||
prop.val = val.mid(a, n-a);
|
||||
list.append(prop);
|
||||
a = n+1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
prop.val = val;
|
||||
list.append(prop);
|
||||
}
|
||||
|
||||
if(at >= str.size() - 1 || (str[at] != ',' && str[at] != ' ' && str[at] != '\t'))
|
||||
break;
|
||||
}
|
||||
|
||||
// integrity check
|
||||
if(list.varCount("nonce") != 1)
|
||||
return false;
|
||||
if(list.varCount("algorithm") != 1)
|
||||
return false;
|
||||
*this = list;
|
||||
return true;
|
||||
}
|
||||
|
||||
int varCount(const Q3CString &var)
|
||||
{
|
||||
int n = 0;
|
||||
for(ConstIterator it = begin(); it != end(); ++it) {
|
||||
if((*it).var == var)
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
QStringList getValues(const Q3CString &var)
|
||||
{
|
||||
QStringList list;
|
||||
for(ConstIterator it = begin(); it != end(); ++it) {
|
||||
if((*it).var == var)
|
||||
list += (*it).val;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
class SimpleSASLContext : public QCA::SASLContext
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class ParamsMutable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
User is held
|
||||
*/
|
||||
bool user;
|
||||
|
||||
/**
|
||||
Authorization ID is held
|
||||
*/
|
||||
bool authzid;
|
||||
|
||||
/**
|
||||
Password is held
|
||||
*/
|
||||
bool pass;
|
||||
|
||||
/**
|
||||
Realm is held
|
||||
*/
|
||||
bool realm;
|
||||
};
|
||||
// core props
|
||||
QString service, host;
|
||||
|
||||
// state
|
||||
int step;
|
||||
bool capable;
|
||||
bool allow_plain;
|
||||
#ifdef YAPSI
|
||||
bool allow_xFacebookPlatform;
|
||||
#endif
|
||||
QByteArray out_buf, in_buf;
|
||||
QString mechanism_;
|
||||
QString out_mech;
|
||||
|
||||
ParamsMutable need;
|
||||
ParamsMutable have;
|
||||
QString user, authz, realm;
|
||||
QCA::SecureArray pass;
|
||||
Result result_;
|
||||
QCA::SASL::AuthCondition authCondition_;
|
||||
QByteArray result_to_net_, result_to_app_;
|
||||
int encoded_;
|
||||
|
||||
SimpleSASLContext(QCA::Provider* p) : QCA::SASLContext(p)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
~SimpleSASLContext()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
resetState();
|
||||
|
||||
capable = true;
|
||||
allow_plain = false;
|
||||
#ifdef YAPSI
|
||||
allow_xFacebookPlatform = false;
|
||||
#endif
|
||||
need.user = false;
|
||||
need.authzid = false;
|
||||
need.pass = false;
|
||||
need.realm = false;
|
||||
have.user = false;
|
||||
have.authzid = false;
|
||||
have.pass = false;
|
||||
have.realm = false;
|
||||
user = QString();
|
||||
authz = QString();
|
||||
pass = QCA::SecureArray();
|
||||
realm = QString();
|
||||
}
|
||||
|
||||
void resetState()
|
||||
{
|
||||
out_mech = QString();
|
||||
out_buf.resize(0);
|
||||
authCondition_ = QCA::SASL::AuthFail;
|
||||
}
|
||||
|
||||
virtual void setConstraints(QCA::SASL::AuthFlags flags, int ssfMin, int) {
|
||||
if(flags & (QCA::SASL::RequireForwardSecrecy | QCA::SASL::RequirePassCredentials | QCA::SASL::RequireMutualAuth) || ssfMin > 0)
|
||||
capable = false;
|
||||
else
|
||||
capable = true;
|
||||
allow_plain = flags & QCA::SASL::AllowPlain;
|
||||
#ifdef YAPSI
|
||||
allow_xFacebookPlatform = flags & QCA::SASL::AllowXFacebookPlatform;
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void setup(const QString& _service, const QString& _host, const QCA::SASLContext::HostPort*, const QCA::SASLContext::HostPort*, const QString&, int) {
|
||||
service = _service;
|
||||
host = _host;
|
||||
}
|
||||
|
||||
virtual void startClient(const QStringList &mechlist, bool allowClientSendFirst) {
|
||||
Q_UNUSED(allowClientSendFirst);
|
||||
|
||||
mechanism_ = QString();
|
||||
foreach(QString mech, mechlist) {
|
||||
#ifdef YAPSI
|
||||
if (mech == "X-FACEBOOK-PLATFORM" && allow_xFacebookPlatform) {
|
||||
mechanism_ = "X-FACEBOOK-PLATFORM";
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mech == "DIGEST-MD5") {
|
||||
mechanism_ = "DIGEST-MD5";
|
||||
break;
|
||||
}
|
||||
#ifdef SIMPLESASL_PLAIN
|
||||
if (mech == "PLAIN" && allow_plain)
|
||||
mechanism_ = "PLAIN";
|
||||
#endif
|
||||
}
|
||||
|
||||
if(!capable || mechanism_.isEmpty()) {
|
||||
result_ = Error;
|
||||
authCondition_ = QCA::SASL::NoMechanism;
|
||||
if (!capable)
|
||||
qWarning("simplesasl.cpp: Not enough capabilities");
|
||||
if (mechanism_.isEmpty())
|
||||
qWarning("simplesasl.cpp: No mechanism available");
|
||||
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
resetState();
|
||||
result_ = Continue;
|
||||
step = 0;
|
||||
tryAgain();
|
||||
}
|
||||
|
||||
virtual void nextStep(const QByteArray &from_net) {
|
||||
in_buf = from_net;
|
||||
tryAgain();
|
||||
}
|
||||
|
||||
virtual void tryAgain() {
|
||||
// All exits of the method must emit the ready signal
|
||||
// so all exits go through a goto ready;
|
||||
if(step == 0) {
|
||||
out_mech = mechanism_;
|
||||
|
||||
#ifdef SIMPLESASL_PLAIN
|
||||
// PLAIN
|
||||
if (out_mech == "PLAIN") {
|
||||
// First, check if we have everything
|
||||
if(need.user || need.pass) {
|
||||
qWarning("simplesasl.cpp: Did not receive necessary auth parameters");
|
||||
result_ = Error;
|
||||
goto ready;
|
||||
}
|
||||
if(!have.user)
|
||||
need.user = true;
|
||||
if(!have.pass)
|
||||
need.pass = true;
|
||||
if(need.user || need.pass) {
|
||||
result_ = Params;
|
||||
goto ready;
|
||||
}
|
||||
|
||||
// Continue with authentication
|
||||
QByteArray plain;
|
||||
if (!authz.isEmpty())
|
||||
plain += authz.utf8();
|
||||
plain += '\0' + user.toUtf8() + '\0' + pass.toByteArray();
|
||||
out_buf.resize(plain.length());
|
||||
memcpy(out_buf.data(), plain.data(), out_buf.size());
|
||||
}
|
||||
#endif
|
||||
++step;
|
||||
if (out_mech == "PLAIN")
|
||||
result_ = Success;
|
||||
else
|
||||
result_ = Continue;
|
||||
}
|
||||
else if(step == 1) {
|
||||
#ifdef YAPSI
|
||||
if (out_mech == "X-FACEBOOK-PLATFORM") {
|
||||
QString fakeUrl = "http://facebook.com/?" + QString(in_buf);
|
||||
QUrl url = QUrl(fakeUrl, QUrl::TolerantMode);
|
||||
|
||||
QVariantMap map;
|
||||
map["method"] = url.queryItemValue("method");
|
||||
map["nonce"] = url.queryItemValue("nonce");
|
||||
map["call_id"] = QDateTime::currentDateTime().toTime_t();
|
||||
xFacebookPlatformLogin(map);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// if we still need params, then the app has failed us!
|
||||
if(need.user || need.authzid || need.pass || need.realm) {
|
||||
qWarning("simplesasl.cpp: Did not receive necessary auth parameters");
|
||||
result_ = Error;
|
||||
goto ready;
|
||||
}
|
||||
|
||||
// see if some params are needed
|
||||
if(!have.user)
|
||||
need.user = true;
|
||||
//if(!have.authzid)
|
||||
// need.authzid = true;
|
||||
if(!have.pass)
|
||||
need.pass = true;
|
||||
if(need.user || need.authzid || need.pass) {
|
||||
result_ = Params;
|
||||
goto ready;
|
||||
}
|
||||
|
||||
// get props
|
||||
QByteArray cs(in_buf);
|
||||
PropList in;
|
||||
if(!in.fromString(cs)) {
|
||||
authCondition_ = QCA::SASL::BadProtocol;
|
||||
result_ = Error;
|
||||
goto ready;
|
||||
}
|
||||
//qDebug() << (QString("simplesasl.cpp: IN: %1").arg(QString(in.toString())));
|
||||
|
||||
// make a cnonce
|
||||
QByteArray a(32);
|
||||
for(int n = 0; n < (int)a.size(); ++n)
|
||||
a[n] = (char)(256.0*rand()/(RAND_MAX+1.0));
|
||||
Q3CString cnonce = QCA::Base64().arrayToString(a).toLatin1();
|
||||
|
||||
// make other variables
|
||||
if (realm.isEmpty())
|
||||
realm = QString::fromUtf8(in.get("realm"));
|
||||
Q3CString nonce = in.get("nonce");
|
||||
Q3CString nc = "00000001";
|
||||
Q3CString uri = service.utf8() + '/' + host.utf8();
|
||||
Q3CString qop = "auth";
|
||||
|
||||
// build 'response'
|
||||
Q3CString X = user.utf8() + ':' + realm.utf8() + ':' + Q3CString(pass.toByteArray());
|
||||
QByteArray Y = QCA::Hash("md5").hash(X).toByteArray();
|
||||
QByteArray tmp = ':' + nonce + ':' + cnonce;
|
||||
if (!authz.isEmpty())
|
||||
tmp += ':' + authz.utf8();
|
||||
//qDebug() << (QString(tmp));
|
||||
|
||||
QByteArray A1(Y + tmp);
|
||||
QByteArray A2 = QByteArray("AUTHENTICATE:") + uri;
|
||||
Q3CString HA1 = QCA::Hash("md5").hashToString(A1).toLatin1();
|
||||
Q3CString HA2 = QCA::Hash("md5").hashToString(A2).toLatin1();
|
||||
Q3CString KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2;
|
||||
Q3CString Z = QCA::Hash("md5").hashToString(KD).toLatin1();
|
||||
|
||||
//qDebug() << (QString("simplesasl.cpp: A1 = %1").arg(QString(A1)).toAscii());
|
||||
//qDebug() << (QString("simplesasl.cpp: A2 = %1").arg(QString(A2)).toAscii());
|
||||
//qDebug() << (QString("simplesasl.cpp: KD = %1").arg(QString(KD)).toAscii());
|
||||
|
||||
// build output
|
||||
PropList out;
|
||||
out.set("username", user.utf8());
|
||||
if (!realm.isEmpty())
|
||||
out.set("realm", realm.utf8());
|
||||
out.set("nonce", nonce);
|
||||
out.set("cnonce", cnonce);
|
||||
out.set("nc", nc);
|
||||
//out.set("serv-type", service.utf8());
|
||||
//out.set("host", host.utf8());
|
||||
out.set("digest-uri", uri);
|
||||
out.set("qop", qop);
|
||||
out.set("response", Z);
|
||||
out.set("charset", "utf-8");
|
||||
if (!authz.isEmpty())
|
||||
out.set("authzid", authz.utf8());
|
||||
QByteArray s(out.toString());
|
||||
//qDebug() << (QString("OUT: %1").arg(QString(out.toString())));
|
||||
|
||||
// done
|
||||
out_buf.resize(s.length());
|
||||
memcpy(out_buf.data(), s.data(), out_buf.size());
|
||||
++step;
|
||||
result_ = Continue;
|
||||
}
|
||||
/*else if (step == 2) {
|
||||
//Commenting this out is Justin's fix for updated QCA.
|
||||
out_buf.resize(0);
|
||||
result_ = Continue;
|
||||
++step;
|
||||
}*/
|
||||
else {
|
||||
out_buf.resize(0);
|
||||
result_ = Success;
|
||||
}
|
||||
ready:
|
||||
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
virtual void update(const QByteArray &from_net, const QByteArray &from_app) {
|
||||
result_to_app_ = from_net;
|
||||
result_to_net_ = from_app;
|
||||
encoded_ = from_app.size();
|
||||
result_ = Success;
|
||||
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
virtual bool waitForResultsReady(int msecs) {
|
||||
|
||||
// TODO: for now, all operations block anyway
|
||||
Q_UNUSED(msecs);
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Result result() const {
|
||||
return result_;
|
||||
}
|
||||
|
||||
virtual QStringList mechlist() const {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
virtual QString mech() const {
|
||||
return out_mech;
|
||||
}
|
||||
|
||||
virtual bool haveClientInit() const {
|
||||
return out_mech == "PLAIN";
|
||||
}
|
||||
|
||||
virtual QByteArray stepData() const {
|
||||
return out_buf;
|
||||
}
|
||||
|
||||
virtual QByteArray to_net() {
|
||||
return result_to_net_;
|
||||
}
|
||||
|
||||
virtual int encoded() const {
|
||||
return encoded_;
|
||||
}
|
||||
|
||||
virtual QByteArray to_app() {
|
||||
return result_to_app_;
|
||||
}
|
||||
|
||||
virtual int ssf() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual QCA::SASL::AuthCondition authCondition() const {
|
||||
return authCondition_;
|
||||
}
|
||||
|
||||
virtual QCA::SASL::Params clientParams() const {
|
||||
return QCA::SASL::Params(need.user, need.authzid, need.pass, need.realm);
|
||||
}
|
||||
|
||||
virtual void setClientParams(const QString *_user, const QString *_authzid, const QCA::SecureArray *_pass, const QString *_realm) {
|
||||
if(_user) {
|
||||
user = *_user;
|
||||
need.user = false;
|
||||
have.user = true;
|
||||
}
|
||||
if(_authzid) {
|
||||
authz = *_authzid;
|
||||
need.authzid = false;
|
||||
have.authzid = true;
|
||||
}
|
||||
if(_pass) {
|
||||
pass = *_pass;
|
||||
need.pass = false;
|
||||
have.pass = true;
|
||||
}
|
||||
if(_realm) {
|
||||
realm = *_realm;
|
||||
need.realm = false;
|
||||
have.realm = true;
|
||||
}
|
||||
}
|
||||
|
||||
virtual QStringList realmlist() const
|
||||
{
|
||||
// TODO
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
virtual QString username() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
virtual QString authzid() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
virtual QCA::Provider::Context* clone() const {
|
||||
SimpleSASLContext* s = new SimpleSASLContext(provider());
|
||||
// TODO: Copy all the members
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual void startServer(const QString &, bool) {
|
||||
result_ = QCA::SASLContext::Error;
|
||||
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
|
||||
}
|
||||
virtual void serverFirstStep(const QString &, const QByteArray *) {
|
||||
result_ = QCA::SASLContext::Error;
|
||||
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
public slots:
|
||||
#ifdef YAPSI
|
||||
void xFacebookPlatformLogin(QVariantMap map) {
|
||||
#ifdef YAPSI_ACTIVEX_SERVER
|
||||
YaOnlineHelper::instance()->xFacebookPlatformLogin(map, this, "xFacebookPlatformDataReady");
|
||||
#else
|
||||
QString apiKey = "API_KEY";
|
||||
QString sessionKey = "SESSION_KEY";
|
||||
QString secret = "SECRET";
|
||||
|
||||
map["api_key"] = apiKey;
|
||||
map["session_key"] = sessionKey;
|
||||
map["v"] = "1.0";
|
||||
|
||||
QStringList signingList;
|
||||
QStringList resultList;
|
||||
QMapIterator<QString, QVariant> it(map);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
|
||||
signingList += QString("%1=%2")
|
||||
.arg(it.key())
|
||||
.arg(it.value().toString());
|
||||
resultList += QString("%1=%2")
|
||||
.arg(it.key())
|
||||
.arg(QUrl::toPercentEncoding(it.value().toString()).constData());
|
||||
}
|
||||
|
||||
QString signingData = signingList.join("") + secret;
|
||||
QString resultData = resultList.join("&");
|
||||
|
||||
QString signature = QCA::Hash("md5").hashToString(signingData.toUtf8());
|
||||
resultData += "&sig=" + signature;
|
||||
|
||||
// qWarning("signingData = '%s'", qPrintable(signingData));
|
||||
// qWarning("resultData = '%s'", qPrintable(resultData));
|
||||
xFacebookPlatformDataReady(resultData);
|
||||
#endif
|
||||
}
|
||||
|
||||
void xFacebookPlatformDataReady(const QString& data) {
|
||||
Q_ASSERT(out_mech == "X-FACEBOOK-PLATFORM");
|
||||
out_buf = data.toUtf8();
|
||||
++step;
|
||||
result_ = Success;
|
||||
|
||||
QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class QCASimpleSASL : public QCA::Provider
|
||||
{
|
||||
public:
|
||||
QCASimpleSASL() {}
|
||||
~QCASimpleSASL() {}
|
||||
|
||||
void init()
|
||||
{
|
||||
}
|
||||
|
||||
QString name() const {
|
||||
return "simplesasl";
|
||||
}
|
||||
|
||||
QStringList features() const {
|
||||
return QStringList("sasl");
|
||||
}
|
||||
|
||||
QCA::Provider::Context* createContext(const QString& cap)
|
||||
{
|
||||
if(cap == "sasl")
|
||||
return new SimpleSASLContext(this);
|
||||
return 0;
|
||||
}
|
||||
int qcaVersion() const
|
||||
{
|
||||
return QCA_VERSION;
|
||||
}
|
||||
};
|
||||
|
||||
QCA::Provider *createProviderSimpleSASL()
|
||||
{
|
||||
return (new QCASimpleSASL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include "simplesasl.moc"
|
||||
Reference in a new issue