QCA

saslclient.cpp
1/*
2 Copyright (C) 2003-2008 Justin Karneges <justin@affinix.com>
3 Copyright (C) 2006 Michail Pishchagin
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
19 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21*/
22
23#include <QCoreApplication>
24#include <QTcpServer>
25#include <QTcpSocket>
26#include <QTimer>
27#include <cstdio>
28
29// QtCrypto has the declarations for all of QCA
30#include <QtCrypto>
31
32#ifdef QT_STATICPLUGIN
33#include "import_plugins.h"
34#endif
35
36static QString prompt(const QString &s)
37{
38 printf("* %s ", qPrintable(s));
39 fflush(stdout);
40 char line[256];
41 fgets(line, 255, stdin);
42 QString result = QString::fromLatin1(line);
43 if (result[result.length() - 1] == QLatin1Char('\n'))
44 result.truncate(result.length() - 1);
45 return result;
46}
47
48static QString socketErrorToString(QAbstractSocket::SocketError x)
49{
50 QString s;
51 switch (x) {
53 s = QStringLiteral("connection refused or timed out");
54 break;
56 s = QStringLiteral("remote host closed the connection");
57 break;
59 s = QStringLiteral("host not found");
60 break;
62 s = QStringLiteral("access error");
63 break;
65 s = QStringLiteral("too many sockets");
66 break;
68 s = QStringLiteral("operation timed out");
69 break;
71 s = QStringLiteral("datagram was larger than system limit");
72 break;
74 s = QStringLiteral("network error");
75 break;
77 s = QStringLiteral("address is already in use");
78 break;
80 s = QStringLiteral("address does not belong to the host");
81 break;
83 s = QStringLiteral("operation is not supported by the local operating system");
84 break;
85 default:
86 s = QStringLiteral("unknown socket error");
87 break;
88 }
89 return s;
90}
91
92static QString saslAuthConditionToString(QCA::SASL::AuthCondition x)
93{
94 QString s;
95 switch (x) {
97 s = QStringLiteral("no appropriate mechanism could be negotiated");
98 break;
100 s = QStringLiteral("bad SASL protocol");
101 break;
103 s = QStringLiteral("server failed mutual authentication");
104 break;
105 // AuthFail or unknown (including those defined for server only)
106 default:
107 s = QStringLiteral("generic authentication failure");
108 break;
109 };
110 return s;
111}
112
113class ClientTest : public QObject
114{
116
117private:
118 QString host, proto, authzid, realm, user, pass;
119 int port;
120 bool no_authzid, no_realm;
121 int mode; // 0 = receive mechanism list, 1 = sasl negotiation, 2 = app
122 QTcpSocket *sock;
123 QCA::SASL *sasl;
124 QByteArray inbuf;
125 bool sock_done;
126 int waitCycles;
127
128public:
129 ClientTest(const QString &_host,
130 int _port,
131 const QString &_proto,
132 const QString &_authzid,
133 const QString &_realm,
134 const QString &_user,
135 const QString &_pass,
136 bool _no_authzid,
137 bool _no_realm)
138 : host(_host)
139 , proto(_proto)
140 , authzid(_authzid)
141 , realm(_realm)
142 , user(_user)
143 , pass(_pass)
144 , port(_port)
145 , no_authzid(_no_authzid)
146 , no_realm(_no_realm)
147 , sock_done(false)
148 , waitCycles(0)
149 {
150 sock = new QTcpSocket(this);
151 connect(sock, &QTcpSocket::connected, this, &ClientTest::sock_connected);
152 connect(sock, &QTcpSocket::readyRead, this, &ClientTest::sock_readyRead);
153#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
154 connect(sock, &QTcpSocket::errorOccurred, this, &ClientTest::sock_error);
155#else
156 connect(sock, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &ClientTest::sock_error);
157#endif
158
159 sasl = new QCA::SASL(this);
160 connect(sasl, &QCA::SASL::clientStarted, this, &ClientTest::sasl_clientFirstStep);
161 connect(sasl, &QCA::SASL::nextStep, this, &ClientTest::sasl_nextStep);
162 connect(sasl, &QCA::SASL::needParams, this, &ClientTest::sasl_needParams);
163 connect(sasl, &QCA::SASL::authenticated, this, &ClientTest::sasl_authenticated);
164 connect(sasl, &QCA::SASL::readyRead, this, &ClientTest::sasl_readyRead);
165 connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ClientTest::sasl_readyReadOutgoing);
166 connect(sasl, &QCA::SASL::error, this, &ClientTest::sasl_error);
167 }
168
169public Q_SLOTS:
170 void start()
171 {
172 mode = 0; // mech list mode
173
174 int flags = 0;
175 flags |= QCA::SASL::AllowPlain;
176 flags |= QCA::SASL::AllowAnonymous;
177 sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256);
178
179 if (!user.isEmpty())
180 sasl->setUsername(user);
181 if (!authzid.isEmpty())
182 sasl->setAuthzid(authzid);
183 if (!pass.isEmpty())
184 sasl->setPassword(pass.toUtf8());
185 if (!realm.isEmpty())
186 sasl->setRealm(realm);
187
188 printf("Connecting to %s:%d, for protocol %s\n", qPrintable(host), port, qPrintable(proto));
189 sock->connectToHost(host, port);
190 }
191
193 void quit();
194
195private Q_SLOTS:
196 void sock_connected()
197 {
198 printf("Connected to server. Awaiting mechanism list...\n");
199 }
200
201 void sock_error(QAbstractSocket::SocketError x)
202 {
204 if (mode == 2) // app mode, where disconnect means completion
205 {
206 sock_done = true;
207 tryFinished();
208 return;
209 } else // any other mode, where disconnect is an error
210 {
211 printf("Error: server closed connection unexpectedly.\n");
212 emit quit();
213 return;
214 }
215 }
216
217 printf("Error: socket: %s\n", qPrintable(socketErrorToString(x)));
218 emit quit();
219 }
220
221 void sock_readyRead()
222 {
223 if (mode == 2) // app mode
224 {
225 QByteArray a = sock->readAll();
226 printf("Read %d bytes\n", int(a.size()));
227
228 // there is a possible flaw in the qca 2.0 api, in
229 // that if sasl data is received from the peer
230 // followed by a disconnect from the peer, there is
231 // no clear approach to salvaging the bytes. tls is
232 // not affected because tls has the concept of
233 // closing a session. with sasl, there is no
234 // closing, and since the qca api is asynchronous,
235 // we could potentially wait forever for decoded
236 // data, if the last write was a partial packet.
237 //
238 // for now, we can perform a simple workaround of
239 // waiting at least three event loop cycles for
240 // decoded data before giving up and assuming the
241 // last write was partial. the fact is, all current
242 // qca sasl providers respond within this time
243 // frame, so this fix should work fine for now. in
244 // qca 2.1, we should revise the api to handle this
245 // situation better.
246 //
247 // further note: i guess this only affects application
248 // protocols that have no close message of their
249 // own, and rely on the tcp-level close. examples
250 // are http, and of course this qcatest protocol.
251 if (waitCycles == 0) {
252 waitCycles = 3;
253 QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
254 }
255
256 sasl->writeIncoming(a);
257 } else // mech list or sasl negotiation mode
258 {
259 if (sock->canReadLine()) {
260 QString line = QString::fromLatin1(sock->readLine());
261 line.truncate(line.length() - 1); // chop the newline
262 handleLine(line);
263 }
264 }
265 }
266
267 void sasl_clientFirstStep(bool clientInit, const QByteArray &clientInitData)
268 {
269 printf("Choosing mech: %s\n", qPrintable(sasl->mechanism()));
270 QString line = sasl->mechanism();
271 if (clientInit) {
272 line += QLatin1Char(' ');
273 line += arrayToString(clientInitData);
274 }
275 sendLine(line);
276 }
277
278 void sasl_nextStep(const QByteArray &stepData)
279 {
280 QString line = QStringLiteral("C");
281 if (!stepData.isEmpty()) {
282 line += QLatin1Char(',');
283 line += arrayToString(stepData);
284 }
285 sendLine(line);
286 }
287
288 void sasl_needParams(const QCA::SASL::Params &params)
289 {
290 if (params.needUsername()) {
291 user = prompt(QStringLiteral("Username:"));
292 sasl->setUsername(user);
293 }
294
295 if (params.canSendAuthzid() && !no_authzid) {
296 authzid = prompt(QStringLiteral("Authorize As (enter to skip):"));
297 if (!authzid.isEmpty())
298 sasl->setAuthzid(authzid);
299 }
300
301 if (params.needPassword()) {
302 QCA::ConsolePrompt prompt;
303 prompt.getHidden(QStringLiteral("* Password"));
304 prompt.waitForFinished();
305 QCA::SecureArray pass = prompt.result();
306 sasl->setPassword(pass);
307 }
308
309 if (params.canSendRealm() && !no_realm) {
310 QStringList realms = sasl->realmList();
311 printf("Available realms:\n");
312 if (realms.isEmpty())
313 printf(" (none specified)\n");
314 foreach (const QString &s, realms)
315 printf(" %s\n", qPrintable(s));
316 realm = prompt(QStringLiteral("Realm (enter to skip):"));
317 if (!realm.isEmpty())
318 sasl->setRealm(realm);
319 }
320
321 sasl->continueAfterParams();
322 }
323
324 void sasl_authenticated()
325 {
326 printf("SASL success!\n");
327 printf("SSF: %d\n", sasl->ssf());
328 }
329
330 void sasl_readyRead()
331 {
332 QByteArray a = sasl->read();
333 inbuf += a;
334 processInbuf();
335 }
336
337 void sasl_readyReadOutgoing()
338 {
339 QByteArray a = sasl->readOutgoing();
340 sock->write(a);
341 }
342
343 void sasl_error()
344 {
345 int e = sasl->errorCode();
346 if (e == QCA::SASL::ErrorInit)
347 printf("Error: sasl: initialization failed.\n");
348 else if (e == QCA::SASL::ErrorHandshake)
349 printf("Error: sasl: %s.\n", qPrintable(saslAuthConditionToString(sasl->authCondition())));
350 else if (e == QCA::SASL::ErrorCrypt)
351 printf("Error: sasl: broken security layer.\n");
352 else
353 printf("Error: sasl: unknown error.\n");
354
355 emit quit();
356 }
357
358 void waitWriteIncoming()
359 {
360 --waitCycles;
361 if (waitCycles > 0) {
362 QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
363 return;
364 }
365
366 tryFinished();
367 }
368
369private:
370 void tryFinished()
371 {
372 if (sock_done && waitCycles == 0) {
373 printf("Finished, server closed connection.\n");
374
375 // if we give up on waiting for a response to
376 // writeIncoming, then it might come late. in
377 // theory this shouldn't happen if we wait enough
378 // cycles, but if one were to arrive then it could
379 // occur between the request to quit the app and
380 // the actual quit of the app. to assist with
381 // debugging, then, we'll explicitly stop listening
382 // for signals here. otherwise the response may
383 // still be received and displayed, giving a false
384 // sense of correctness.
385 sasl->disconnect(this);
386
387 emit quit();
388 }
389 }
390
391 QString arrayToString(const QByteArray &ba)
392 {
393 return QCA::Base64().arrayToString(ba);
394 }
395
396 QByteArray stringToArray(const QString &s)
397 {
399 }
400
401 void sendLine(const QString &line)
402 {
403 printf("Writing: {%s}\n", qPrintable(line));
404 QString s = line + QLatin1Char('\n');
405 QByteArray a = s.toUtf8();
406 if (mode == 2) // app mode
407 sasl->write(a); // write to sasl
408 else // mech list or sasl negotiation
409 sock->write(a); // write to socket
410 }
411
412 void processInbuf()
413 {
414 // collect completed lines from inbuf
416 int at;
417 while ((at = inbuf.indexOf('\n')) != -1) {
418 list += QString::fromUtf8(inbuf.mid(0, at));
419 inbuf = inbuf.mid(at + 1);
420 }
421
422 // process the lines
423 foreach (const QString &line, list)
424 handleLine(line);
425 }
426
427 void handleLine(const QString &line)
428 {
429 printf("Reading: [%s]\n", qPrintable(line));
430 if (mode == 0) {
431 // first line is the method list
432 const QStringList mechlist = line.split(QLatin1Char(' '));
433 mode = 1; // switch to sasl negotiation mode
434 sasl->startClient(proto, host, mechlist);
435 } else if (mode == 1) {
436 QString type, rest;
437 int n = line.indexOf(QLatin1Char(','));
438 if (n != -1) {
439 type = line.mid(0, n);
440 rest = line.mid(n + 1);
441 } else
442 type = line;
443
444 if (type == QLatin1String("C")) {
445 sasl->putStep(stringToArray(rest));
446 } else if (type == QLatin1String("E")) {
447 if (!rest.isEmpty())
448 printf("Error: server says: %s.\n", qPrintable(rest));
449 else
450 printf("Error: server error, unspecified.\n");
451 emit quit();
452 return;
453 } else if (type == QLatin1String("A")) {
454 printf("Authentication success.\n");
455 mode = 2; // switch to app mode
456
457 // at this point, the server may send us text
458 // lines for us to display and then close.
459
460 sock_readyRead(); // any extra data?
461 return;
462 } else {
463 printf("Error: Bad format from peer, closing.\n");
464 emit quit();
465 return;
466 }
467 }
468 }
469};
470
471void usage()
472{
473 printf("usage: saslclient (options) host(:port) (user) (pass)\n");
474 printf("options: --proto=x, --authzid=x, --realm=x\n");
475}
476
477int main(int argc, char **argv)
478{
480 QCoreApplication qapp(argc, argv);
481
482 QStringList args = qapp.arguments();
483 args.removeFirst();
484
485 // options
486 QString proto = QStringLiteral("qcatest"); // default protocol
487 QString authzid, realm;
488 bool no_authzid = false;
489 bool no_realm = false;
490 for (int n = 0; n < args.count(); ++n) {
491 if (!args[n].startsWith(QLatin1String("--")))
492 continue;
493
494 QString opt = args[n].mid(2);
495 QString var, val;
496 int at = opt.indexOf(QLatin1Char('='));
497 if (at != -1) {
498 var = opt.mid(0, at);
499 val = opt.mid(at + 1);
500 } else
501 var = opt;
502
503 if (var == QLatin1String("proto")) {
504 proto = val;
505 } else if (var == QLatin1String("authzid")) {
506 // specifying empty authzid means force unspecified
507 if (val.isEmpty())
508 no_authzid = true;
509 else
510 authzid = val;
511 } else if (var == QLatin1String("realm")) {
512 // specifying empty realm means force unspecified
513 if (val.isEmpty())
514 no_realm = true;
515 else
516 realm = val;
517 }
518
519 args.removeAt(n);
520 --n; // adjust position
521 }
522
523 if (args.count() < 1) {
524 usage();
525 return 0;
526 }
527
528 QString host, user, pass;
529 int port = 8001; // default port
530
531 QString hostinput = args[0];
532 if (args.count() >= 2)
533 user = args[1];
534 if (args.count() >= 3)
535 pass = args[2];
536
537 int at = hostinput.indexOf(QLatin1Char(':'));
538 if (at != -1) {
539 host = hostinput.mid(0, at);
540#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
541 port = QStringView(hostinput).mid(at + 1).toInt();
542#else
543 port = hostinput.midRef(at + 1).toInt();
544#endif
545 } else
546 host = hostinput;
547
548 if (!QCA::isSupported("sasl")) {
549 printf("Error: SASL support not found.\n");
550 return 1;
551 }
552
553 ClientTest client(host, port, proto, authzid, realm, user, pass, no_authzid, no_realm);
554 QObject::connect(&client, &ClientTest::quit, &qapp, &QCoreApplication::quit);
555 QTimer::singleShot(0, &client, &ClientTest::start);
556 qapp.exec();
557
558 return 0;
559}
560
561#include "saslclient.moc"
Base64 encoding / decoding
Console prompt handler.
Convenience method for initialising and cleaning up QCA.
Definition qca_core.h:660
QByteArray toByteArray() const
Convert this memory region to a byte array.
Parameter flags for the SASL authentication.
bool needPassword() const
Password is needed.
bool canSendAuthzid() const
An Authorization ID can be sent if desired.
bool needUsername() const
User is needed.
bool canSendRealm() const
A Realm can be sent if desired.
Simple Authentication and Security Layer protocol implementation.
void setUsername(const QString &user)
Specify the username to use in authentication.
Error errorCode() const
Return the error code.
void continueAfterParams()
Continue negotiation after parameters have been set (client)
void write(const QByteArray &a) override
This method writes unencrypted (plain) data to the SecureLayer implementation.
void startClient(const QString &service, const QString &host, const QStringList &mechlist, ClientSendMode mode=AllowClientSendFirst)
Initialise the client side of the connection.
void setConstraints(AuthFlags f, SecurityLevel s=SL_None)
Specify connection constraints.
void setAuthzid(const QString &auth)
Specify the authorization identity to use in authentication.
void nextStep(const QByteArray &stepData)
This signal is emitted when there is data required to be sent over the network to complete the next s...
void writeIncoming(const QByteArray &a) override
This method accepts encoded (typically encrypted) data for processing.
AuthCondition
Possible authentication error states.
@ BadProtocol
Bad protocol or cancelled.
@ NoMechanism
No compatible/appropriate authentication mechanism.
@ BadServer
Server failed mutual authentication (client side only)
QString mechanism() const
Return the mechanism selected (client)
void setPassword(const SecureArray &pass)
Specify the password to use in authentication.
QStringList realmList() const
Return the realm list, if available (client)
void clientStarted(bool clientInit, const QByteArray &clientInitData)
This signal is emitted when the client has been successfully started.
QByteArray read() override
This method reads decrypted (plain) data from the SecureLayer implementation.
void needParams(const QCA::SASL::Params &params)
This signal is emitted when the client needs additional parameters.
@ ErrorInit
problem starting up SASL
@ ErrorCrypt
problem at anytime after
@ ErrorHandshake
problem during the authentication process
int ssf() const
Return the security strength factor of the connection.
void setRealm(const QString &realm)
Specify the realm to use in authentication.
AuthCondition authCondition() const
Return the reason for authentication failure.
void authenticated()
This signal is emitted when authentication is complete.
QByteArray readOutgoing(int *plainBytes=nullptr) override
This method provides encoded (typically encrypted) data.
void putStep(const QByteArray &stepData)
Process an authentication step.
AuthFlags
Authentication requirement flag values.
Secure array of bytes.
Definition qca_tools.h:317
void error()
This signal is emitted when an error is detected.
void readyReadOutgoing()
This signal is emitted when SecureLayer has encrypted (network side) data ready to be read.
void readyRead()
This signal is emitted when SecureLayer has decrypted (application side) data ready to be read.
QString arrayToString(const MemoryRegion &a)
Process an array in the "forward" direction, returning a QString.
MemoryRegion stringToArray(const QString &s)
Process an string in the "reverse" direction, returning a byte array.
Type type(const QSqlDatabase &db)
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QCA_EXPORT bool isSupported(const char *features, const QString &provider=QString())
Test if a capability (algorithm) is available.
void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode)
SocketError error() const const
void errorOccurred(QAbstractSocket::SocketError socketError)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
qsizetype size() const const
virtual bool canReadLine() const const
QByteArray readAll()
QByteArray readLine(qint64 maxSize)
void readyRead()
qint64 write(const QByteArray &data)
qsizetype count() const const
bool isEmpty() const const
QList< T > mid(qsizetype pos, qsizetype length) const const
void removeAt(qsizetype i)
void removeFirst()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
void truncate(qsizetype position)
QStringView mid(qsizetype start, qsizetype length) const const
int toInt(bool *ok, int base) const const
QueuedConnection
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.