2 * simplesasl.cpp - Simple SASL implementation
3 * Copyright (C) 2003 Justin Karneges <justin@affinix.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #include "simplesasl.h"
23 #include <qhostaddress.h>
24 #include <qstringlist.h>
25 #include <q3ptrlist.h>
33 #define SIMPLESASL_PLAIN
41 class PropList
: public QList
<Prop
>
44 PropList() : QList
<Prop
>()
48 void set(const Q3CString
&var
, const Q3CString
&val
)
56 Q3CString
get(const Q3CString
&var
)
58 for(ConstIterator it
= constBegin(); it
!= constEnd(); ++it
) {
65 Q3CString
toString() const
69 for(ConstIterator it
= constBegin(); it
!= constEnd(); ++it
) {
72 if ((*it
).var
== "realm" || (*it
).var
== "nonce" || (*it
).var
== "username" || (*it
).var
== "cnonce" || (*it
).var
== "digest-uri" || (*it
).var
== "authzid")
73 str
+= (*it
).var
+ "=\"" + (*it
).val
+ '\"';
75 str
+= (*it
).var
+ "=" + (*it
).val
;
81 bool fromString(const QByteArray
&str
)
86 while (at
< str
.length() && (str
[at
] == ',' || str
[at
] == ' ' || str
[at
] == '\t'))
88 int n
= str
.find('=', at
);
92 var
= str
.mid(at
, n
-at
);
96 n
= str
.find('\"', at
);
99 val
= str
.mid(at
, n
-at
);
104 while (n
< str
.length() && str
[n
] != ',' && str
[n
] != ' ' && str
[n
] != '\t')
106 val
= str
.mid(at
, n
-at
);
111 if (var
== "qop" || var
== "cipher") {
113 while (a
< val
.length()) {
114 while (a
< val
.length() && (val
[a
] == ',' || val
[a
] == ' ' || val
[a
] == '\t'))
116 if (a
== val
.length())
119 while (n
< val
.length() && val
[n
] != ',' && val
[n
] != ' ' && val
[n
] != '\t')
121 prop
.val
= val
.mid(a
, n
-a
);
131 if(at
>= str
.size() - 1 || (str
[at
] != ',' && str
[at
] != ' ' && str
[at
] != '\t'))
136 if(list
.varCount("nonce") != 1)
138 if(list
.varCount("algorithm") != 1)
144 int varCount(const Q3CString
&var
)
147 for(ConstIterator it
= constBegin(); it
!= constEnd(); ++it
) {
154 QStringList
getValues(const Q3CString
&var
)
157 for(ConstIterator it
= constBegin(); it
!= constEnd(); ++it
) {
165 class SimpleSASLContext
: public QCA::SASLContext
177 Authorization ID is held
192 QString service
, host
;
198 QByteArray out_buf
, in_buf
;
204 QString user
, authz
, realm
;
205 QCA::SecureArray pass
;
207 QCA::SASL::AuthCondition authCondition_
;
208 QByteArray result_to_net_
, result_to_app_
;
211 SimpleSASLContext(QCA::Provider
* p
) : QCA::SASLContext(p
)
228 need
.authzid
= false;
232 have
.authzid
= false;
237 pass
= QCA::SecureArray();
243 out_mech
= QString();
245 authCondition_
= QCA::SASL::AuthFail
;
248 virtual void setConstraints(QCA::SASL::AuthFlags flags
, int ssfMin
, int) {
249 if(flags
& (QCA::SASL::RequireForwardSecrecy
| QCA::SASL::RequirePassCredentials
| QCA::SASL::RequireMutualAuth
) || ssfMin
> 0)
253 allow_plain
= flags
& QCA::SASL::AllowPlain
;
256 virtual void setup(const QString
& _service
, const QString
& _host
, const QCA::SASLContext::HostPort
*, const QCA::SASLContext::HostPort
*, const QString
&, int) {
261 virtual void startClient(const QStringList
&mechlist
, bool allowClientSendFirst
) {
262 Q_UNUSED(allowClientSendFirst
);
264 mechanism_
= QString();
265 foreach(QString mech
, mechlist
) {
266 if (mech
== "DIGEST-MD5") {
267 mechanism_
= "DIGEST-MD5";
270 #ifdef SIMPLESASL_PLAIN
271 if (mech
== "PLAIN" && allow_plain
)
272 mechanism_
= "PLAIN";
276 if(!capable
|| mechanism_
.isEmpty()) {
278 authCondition_
= QCA::SASL::NoMechanism
;
280 qWarning("simplesasl.cpp: Not enough capabilities");
281 if (mechanism_
.isEmpty())
282 qWarning("simplesasl.cpp: No mechanism available");
283 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection
);
293 virtual void nextStep(const QByteArray
&from_net
) {
298 virtual void tryAgain() {
299 // All exits of the method must emit the ready signal
300 // so all exits go through a goto ready;
302 out_mech
= mechanism_
;
304 #ifdef SIMPLESASL_PLAIN
306 if (out_mech
== "PLAIN") {
307 // First, check if we have everything
308 if(need
.user
|| need
.pass
) {
309 qWarning("simplesasl.cpp: Did not receive necessary auth parameters");
317 if(need
.user
|| need
.pass
) {
322 // Continue with authentication
324 if (!authz
.isEmpty())
325 plain
+= authz
.utf8();
326 plain
+= '\0' + user
.toUtf8() + '\0' + pass
.toByteArray();
327 out_buf
.resize(plain
.length());
328 memcpy(out_buf
.data(), plain
.data(), out_buf
.size());
332 if (out_mech
== "PLAIN")
338 // if we still need params, then the app has failed us!
339 if(need
.user
|| need
.authzid
|| need
.pass
|| need
.realm
) {
340 qWarning("simplesasl.cpp: Did not receive necessary auth parameters");
345 // see if some params are needed
349 // need.authzid = true;
352 if(need
.user
|| need
.authzid
|| need
.pass
) {
358 QByteArray
cs(in_buf
);
360 if(!in
.fromString(cs
)) {
361 authCondition_
= QCA::SASL::BadProtocol
;
365 //qDebug() << (QString("simplesasl.cpp: IN: %1").arg(QString(in.toString())));
369 for(int n
= 0; n
< (int)a
.size(); ++n
)
370 a
[n
] = (char)(256.0*rand()/(RAND_MAX
+1.0));
371 Q3CString cnonce
= QCA::Base64().arrayToString(a
).latin1();
373 // make other variables
375 realm
= QString::fromUtf8(in
.get("realm"));
376 Q3CString nonce
= in
.get("nonce");
377 Q3CString nc
= "00000001";
378 Q3CString uri
= service
.utf8() + '/' + host
.utf8();
379 Q3CString qop
= "auth";
382 Q3CString X
= user
.utf8() + ':' + realm
.utf8() + ':' + Q3CString(pass
.toByteArray());
383 QByteArray Y
= QCA::Hash("md5").hash(X
).toByteArray();
384 QByteArray tmp
= ':' + nonce
+ ':' + cnonce
;
385 if (!authz
.isEmpty())
386 tmp
+= ':' + authz
.utf8();
387 //qDebug() << (QString(tmp));
389 QByteArray
A1(Y
+ tmp
);
390 QByteArray A2
= QByteArray("AUTHENTICATE:") + uri
;
391 Q3CString HA1
= QCA::Hash("md5").hashToString(A1
).latin1();
392 Q3CString HA2
= QCA::Hash("md5").hashToString(A2
).latin1();
393 Q3CString KD
= HA1
+ ':' + nonce
+ ':' + nc
+ ':' + cnonce
+ ':' + qop
+ ':' + HA2
;
394 Q3CString Z
= QCA::Hash("md5").hashToString(KD
).latin1();
396 //qDebug() << (QString("simplesasl.cpp: A1 = %1").arg(QString(A1)).toAscii());
397 //qDebug() << (QString("simplesasl.cpp: A2 = %1").arg(QString(A2)).toAscii());
398 //qDebug() << (QString("simplesasl.cpp: KD = %1").arg(QString(KD)).toAscii());
402 out
.set("username", user
.utf8());
403 if (!realm
.isEmpty())
404 out
.set("realm", realm
.utf8());
405 out
.set("nonce", nonce
);
406 out
.set("cnonce", cnonce
);
408 //out.set("serv-type", service.utf8());
409 //out.set("host", host.utf8());
410 out
.set("digest-uri", uri
);
412 out
.set("response", Z
);
413 out
.set("charset", "utf-8");
414 if (!authz
.isEmpty())
415 out
.set("authzid", authz
.utf8());
416 QByteArray
s(out
.toString());
417 //qDebug() << (QString("OUT: %1").arg(QString(out.toString())));
420 out_buf
.resize(s
.length());
421 memcpy(out_buf
.data(), s
.data(), out_buf
.size());
425 /*else if (step == 2) {
426 //Commenting this out is Justin's fix for updated QCA.
436 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection
);
439 virtual void update(const QByteArray
&from_net
, const QByteArray
&from_app
) {
440 result_to_app_
= from_net
;
441 result_to_net_
= from_app
;
442 encoded_
= from_app
.size();
444 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection
);
447 virtual bool waitForResultsReady(int msecs
) {
449 // TODO: for now, all operations block anyway
454 virtual Result
result() const {
458 virtual QStringList
mechlist() const {
459 return QStringList();
462 virtual QString
mech() const {
466 virtual bool haveClientInit() const {
467 return out_mech
== "PLAIN";
470 virtual QByteArray
stepData() const {
474 virtual QByteArray
to_net() {
475 return result_to_net_
;
478 virtual int encoded() const {
482 virtual QByteArray
to_app() {
483 return result_to_app_
;
486 virtual int ssf() const {
490 virtual QCA::SASL::AuthCondition
authCondition() const {
491 return authCondition_
;
494 virtual QCA::SASL::Params
clientParams() const {
495 return QCA::SASL::Params(need
.user
, need
.authzid
, need
.pass
, need
.realm
);
498 virtual void setClientParams(const QString
*_user
, const QString
*_authzid
, const QCA::SecureArray
*_pass
, const QString
*_realm
) {
506 need
.authzid
= false;
521 virtual QStringList
realmlist() const
524 return QStringList();
527 virtual QString
username() const {
531 virtual QString
authzid() const {
535 virtual QCA::Provider::Context
* clone() const {
536 SimpleSASLContext
* s
= new SimpleSASLContext(provider());
537 // TODO: Copy all the members
541 virtual void startServer(const QString
&, bool) {
542 result_
= QCA::SASLContext::Error
;
543 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection
);
545 virtual void serverFirstStep(const QString
&, const QByteArray
*) {
546 result_
= QCA::SASLContext::Error
;
547 QMetaObject::invokeMethod(this, "resultsReady", Qt::QueuedConnection
);
552 class QCASimpleSASL
: public QCA::Provider
562 QString
name() const {
566 QStringList
features() const {
567 return QStringList("sasl");
570 QCA::Provider::Context
* createContext(const QString
& cap
)
573 return new SimpleSASLContext(this);
576 int qcaVersion() const
582 QCA::Provider
*createProviderSimpleSASL()
584 return (new QCASimpleSASL
);