QCA

saslserver.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 socketErrorToString(QAbstractSocket::SocketError x)
37{
38 QString s;
39 switch (x) {
41 s = QStringLiteral("connection refused or timed out");
42 break;
44 s = QStringLiteral("remote host closed the connection");
45 break;
47 s = QStringLiteral("host not found");
48 break;
50 s = QStringLiteral("access error");
51 break;
53 s = QStringLiteral("too many sockets");
54 break;
56 s = QStringLiteral("operation timed out");
57 break;
59 s = QStringLiteral("datagram was larger than system limit");
60 break;
62 s = QStringLiteral("network error");
63 break;
65 s = QStringLiteral("address is already in use");
66 break;
68 s = QStringLiteral("address does not belong to the host");
69 break;
71 s = QStringLiteral("operation is not supported by the local operating system");
72 break;
73 default:
74 s = QStringLiteral("unknown socket error");
75 break;
76 }
77 return s;
78}
79
80static QString saslAuthConditionToString(QCA::SASL::AuthCondition x)
81{
82 QString s;
83 switch (x) {
85 s = QStringLiteral("no appropriate mechanism could be negotiated");
86 break;
88 s = QStringLiteral("bad SASL protocol");
89 break;
91 s = QStringLiteral("authentication failed");
92 break;
94 s = QStringLiteral("authorization failed");
95 break;
97 s = QStringLiteral("mechanism too weak for this user");
98 break;
100 s = QStringLiteral("encryption is needed to use this mechanism");
101 break;
103 s = QStringLiteral("passphrase expired");
104 break;
106 s = QStringLiteral("account is disabled");
107 break;
109 s = QStringLiteral("user not found");
110 break;
112 s = QStringLiteral("needed remote service is unavailable");
113 break;
114 // AuthFail or unknown (including those defined for client only)
115 default:
116 s = QStringLiteral("generic authentication failure");
117 break;
118 };
119 return s;
120}
121
122// --- ServerTest declaration
123
124class ServerTest : public QObject
125{
127
128private:
129 QString host, proto, realm, str;
130 int port;
131 QTcpServer *tcpServer;
132 QList<int> ids;
133
134public:
135 ServerTest(const QString &_host, int _port, const QString &_proto, const QString &_realm, const QString &_str);
136
137 int reserveId();
138 void releaseId(int id);
139
140public Q_SLOTS:
141 void start();
142
144 void quit();
145
146private Q_SLOTS:
147 void server_newConnection();
148};
149
150// --- ServerTestHandler
151
152class ServerTestHandler : public QObject
153{
155
156private:
157 ServerTest *serverTest;
158 QTcpSocket *sock;
159 QCA::SASL *sasl;
160 int id;
161 QString host, proto, realm, str;
162 int mode; // 0 = receive mechanism list, 1 = sasl negotiation, 2 = app
163 int toWrite;
164
165public:
166 ServerTestHandler(ServerTest *_serverTest,
167 QTcpSocket *_sock,
168 const QString &_host,
169 const QString &_proto,
170 const QString &_realm,
171 const QString &_str)
172 : serverTest(_serverTest)
173 , sock(_sock)
174 , host(_host)
175 , proto(_proto)
176 , realm(_realm)
177 , str(_str)
178 {
179 id = serverTest->reserveId();
180
181 sock->setParent(this);
182 connect(sock, &QTcpSocket::disconnected, this, &ServerTestHandler::sock_disconnected);
183 connect(sock, &QTcpSocket::readyRead, this, &ServerTestHandler::sock_readyRead);
184#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
185 connect(sock, &QTcpSocket::errorOccurred, this, &ServerTestHandler::sock_error);
186#else
187 connect(sock,
188 QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
189 this,
190 &ServerTestHandler::sock_error);
191#endif
192 connect(sock, &QTcpSocket::bytesWritten, this, &ServerTestHandler::sock_bytesWritten);
193
194 sasl = new QCA::SASL(this);
195 connect(sasl, &QCA::SASL::authCheck, this, &ServerTestHandler::sasl_authCheck);
196 connect(sasl, &QCA::SASL::nextStep, this, &ServerTestHandler::sasl_nextStep);
197 connect(sasl, &QCA::SASL::authenticated, this, &ServerTestHandler::sasl_authenticated);
198 connect(sasl, &QCA::SASL::readyRead, this, &ServerTestHandler::sasl_readyRead);
199 connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ServerTestHandler::sasl_readyReadOutgoing);
200 connect(sasl, &QCA::SASL::error, this, &ServerTestHandler::sasl_error);
201 connect(sasl, &QCA::SASL::serverStarted, this, &ServerTestHandler::sasl_serverStarted);
202
203 mode = 0; // mech list mode
204 toWrite = 0;
205
206 int flags = 0;
207 flags |= QCA::SASL::AllowPlain;
208 flags |= QCA::SASL::AllowAnonymous;
209 sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256);
210
211 printf("%d: Connection received! Starting SASL handshake...\n", id);
212 sasl->startServer(proto, host, realm);
213 }
214
215 ~ServerTestHandler() override
216 {
217 serverTest->releaseId(id);
218 }
219
220private Q_SLOTS:
221 void sasl_serverStarted()
222 {
223 sendLine(sasl->mechanismList().join(QStringLiteral(" ")));
224 }
225
226 void sock_disconnected()
227 {
228 printf("%d: Connection closed.\n", id);
229 discard();
230 }
231
232 void sock_error(QAbstractSocket::SocketError x)
233 {
235 printf("%d: Error: client closed connection unexpectedly.\n", id);
236 discard();
237 return;
238 }
239
240 printf("%d: Error: socket: %s\n", id, qPrintable(socketErrorToString(x)));
241 discard();
242 }
243
244 void sock_readyRead()
245 {
246 if (sock->canReadLine()) {
247 QString line = QString::fromLatin1(sock->readLine());
248 line.truncate(line.length() - 1); // chop the newline
249 handleLine(line);
250 }
251 }
252
253 void sock_bytesWritten(qint64 x)
254 {
255 if (mode == 2) // app mode
256 {
257 toWrite -= sasl->convertBytesWritten(x);
258 if (toWrite == 0) {
259 printf("%d: Sent, closing.\n", id);
260 sock->close();
261 }
262 }
263 }
264
265 void sasl_nextStep(const QByteArray &stepData)
266 {
267 QString line = QStringLiteral("C");
268 if (!stepData.isEmpty()) {
269 line += QLatin1Char(',');
270 line += arrayToString(stepData);
271 }
272 sendLine(line);
273 }
274
275 void sasl_authCheck(const QString &user, const QString &authzid)
276 {
277 printf("%d: AuthCheck: User: [%s], Authzid: [%s]\n", id, qPrintable(user), qPrintable(authzid));
278
279 // user - who has logged in, confirmed by sasl
280 // authzid - the identity the user wishes to act as, which
281 // could be another user or just any arbitrary string (in
282 // XMPP, this field holds a Jabber ID, for example). this
283 // field is not necessarily confirmed by sasl, and the
284 // decision about whether the user can act as the authzid
285 // must be made by the app.
286
287 // for this simple example program, we allow anyone to use
288 // the service, and simply continue onward with the
289 // negotiation.
291 }
292
293 void sasl_authenticated()
294 {
295 sendLine(QStringLiteral("A"));
296 printf("%d: Authentication success.\n", id);
297 mode = 2; // switch to app mode
298 printf("%d: SSF: %d\n", id, sasl->ssf());
299 sendLine(str);
300 }
301
302 void sasl_readyRead()
303 {
304 QByteArray a = sasl->read();
305 printf("%d: Warning, client sent %d bytes unexpectedly.\n", id, int(a.size()));
306 }
307
308 void sasl_readyReadOutgoing()
309 {
310 sock->write(sasl->readOutgoing());
311 }
312
313 void sasl_error()
314 {
315 int e = sasl->errorCode();
316 if (e == QCA::SASL::ErrorInit) {
317 printf("%d: Error: sasl: initialization failed.\n", id);
318 } else if (e == QCA::SASL::ErrorHandshake) {
319 QString errstr = saslAuthConditionToString(sasl->authCondition());
320 sendLine(QStringLiteral("E,") + errstr);
321 printf("%d: Error: sasl: %s.\n", id, qPrintable(errstr));
322 } else if (e == QCA::SASL::ErrorCrypt) {
323 printf("%d: Error: sasl: broken security layer.\n", id);
324 } else {
325 printf("%d: Error: sasl: unknown error.\n", id);
326 }
327
328 sock->close();
329 }
330
331private:
332 void discard()
333 {
334 deleteLater();
335 }
336
337 void handleLine(const QString &line)
338 {
339 printf("%d: Reading: [%s]\n", id, qPrintable(line));
340 if (mode == 0) {
341 int n = line.indexOf(QLatin1Char(' '));
342 if (n != -1) {
343 QString mech = line.mid(0, n);
344 QString rest = QString::fromLatin1(line.mid(n + 1).toUtf8());
345 sasl->putServerFirstStep(mech, stringToArray(rest));
346 } else
347 sasl->putServerFirstStep(line);
348 ++mode;
349 } else if (mode == 1) {
350 QString type, rest;
351 int n = line.indexOf(QLatin1Char(','));
352 if (n != -1) {
353 type = line.mid(0, n);
354 rest = line.mid(n + 1);
355 } else {
356 type = line;
357 rest = QLatin1String("");
358 }
359
360 if (type == QLatin1String("C")) {
361 sasl->putStep(stringToArray(rest));
362 } else {
363 printf("%d: Bad format from peer, closing.\n", id);
364 sock->close();
365 return;
366 }
367 }
368 }
369
370 QString arrayToString(const QByteArray &ba)
371 {
372 QCA::Base64 encoder;
373 return encoder.arrayToString(ba);
374 }
375
376 QByteArray stringToArray(const QString &s)
377 {
378 QCA::Base64 decoder(QCA::Decode);
379 return decoder.stringToArray(s).toByteArray();
380 }
381
382 void sendLine(const QString &line)
383 {
384 printf("%d: Writing: {%s}\n", id, qPrintable(line));
385 QString s = line + QLatin1Char('\n');
386 QByteArray a = s.toUtf8();
387 if (mode == 2) // app mode
388 {
389 toWrite += a.size();
390 sasl->write(a); // write to sasl
391 } else // mech list or sasl negotiation
392 sock->write(a); // write to socket
393 }
394};
395
396// --- ServerTest implementation
397
398ServerTest::ServerTest(const QString &_host,
399 int _port,
400 const QString &_proto,
401 const QString &_realm,
402 const QString &_str)
403 : host(_host)
404 , proto(_proto)
405 , realm(_realm)
406 , str(_str)
407 , port(_port)
408{
409 tcpServer = new QTcpServer(this);
410 connect(tcpServer, &QTcpServer::newConnection, this, &ServerTest::server_newConnection);
411}
412
413int ServerTest::reserveId()
414{
415 int n = 0;
416 while (ids.contains(n))
417 ++n;
418 ids += n;
419 return n;
420}
421
422void ServerTest::releaseId(int id)
423{
424 ids.removeAll(id);
425}
426
427void ServerTest::start()
428{
429 if (!tcpServer->listen(QHostAddress::Any, port)) {
430 printf("Error: unable to bind to port %d.\n", port);
431 emit quit();
432 return;
433 }
434
435 printf("Serving on %s:%d, for protocol %s ...\n", qPrintable(host), port, qPrintable(proto));
436}
437
438void ServerTest::server_newConnection()
439{
440 QTcpSocket *sock = tcpServer->nextPendingConnection();
441 new ServerTestHandler(this, sock, host, proto, realm, str);
442}
443
444// ---
445
446void usage()
447{
448 printf("usage: saslserver host (message)\n");
449 printf("options: --proto=x, --realm=x\n");
450}
451
452int main(int argc, char **argv)
453{
455 QCoreApplication qapp(argc, argv);
456
457 QCA::setAppName(QStringLiteral("saslserver"));
458
459 QStringList args = qapp.arguments();
460 args.removeFirst();
461
462 // options
463 QString proto = QStringLiteral("qcatest"); // default protocol
464 QString realm;
465 for (int n = 0; n < args.count(); ++n) {
466 if (!args[n].startsWith(QLatin1String("--")))
467 continue;
468
469 QString opt = args[n].mid(2);
470 QString var, val;
471 int at = opt.indexOf(QLatin1Char('='));
472 if (at != -1) {
473 var = opt.mid(0, at);
474 val = opt.mid(at + 1);
475 } else
476 var = opt;
477
478 if (var == QLatin1String("proto"))
479 proto = val;
480 else if (var == QLatin1String("realm"))
481 realm = val;
482
483 args.removeAt(n);
484 --n; // adjust position
485 }
486
487 if (args.count() < 1) {
488 usage();
489 return 0;
490 }
491
492 QString host;
493 int port = 8001; // default port
494
495 QString hostinput = args[0];
496 QString str = QStringLiteral("Hello, World");
497 if (args.count() >= 2)
498 str = args[1];
499
500 int at = hostinput.indexOf(QLatin1Char(':'));
501 if (at != -1) {
502 host = hostinput.mid(0, at);
503#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
504 port = QStringView(hostinput).mid(at + 1).toInt();
505#else
506 port = hostinput.midRef(at + 1).toInt();
507#endif
508 } else
509 host = hostinput;
510
511 if (!QCA::isSupported("sasl")) {
512 printf("Error: SASL support not found.\n");
513 return 1;
514 }
515
516 ServerTest server(host, port, proto, realm, str);
517 QObject::connect(&server, &ServerTest::quit, &qapp, &QCoreApplication::quit);
518 QTimer::singleShot(0, &server, &ServerTest::start);
519 qapp.exec();
520
521 return 0;
522}
523
524#include "saslserver.moc"
Base64 encoding / decoding
Convenience method for initialising and cleaning up QCA.
Definition qca_core.h:660
Simple Authentication and Security Layer protocol implementation.
void startServer(const QString &service, const QString &host, const QString &realm, ServerSendMode mode=DisableServerSendLast)
Initialise the server side of the connection.
void putServerFirstStep(const QString &mech)
Process the first step in server mode (server)
Error errorCode() const
Return the error code.
void authCheck(const QString &user, const QString &authzid)
This signal is emitted when the server needs to perform the authentication check.
void write(const QByteArray &a) override
This method writes unencrypted (plain) data to the SecureLayer implementation.
int convertBytesWritten(qint64 encryptedBytes) override
Convert encrypted bytes written to plain text bytes written.
void setConstraints(AuthFlags f, SecurityLevel s=SL_None)
Specify connection constraints.
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 continueAfterAuthCheck()
Continue negotiation after auth ids have been checked (server)
AuthCondition
Possible authentication error states.
@ NeedEncrypt
Encryption is needed in order to use mechanism (server side only)
@ TooWeak
Mechanism too weak for this user (server side only)
@ BadProtocol
Bad protocol or cancelled.
@ NoUser
User not found (server side only)
@ NoMechanism
No compatible/appropriate authentication mechanism.
@ RemoteUnavailable
Remote service needed for auth is gone (server side only)
@ Expired
Passphrase expired, has to be reset (server side only)
@ Disabled
Account is disabled (server side only)
@ BadAuth
Authentication failure (server side only)
@ NoAuthzid
Authorization failure (server side only)
void serverStarted()
This signal is emitted after the server has been successfully started.
QByteArray read() override
This method reads decrypted (plain) data from the SecureLayer implementation.
QStringList mechanismList() const
Return the mechanism list (server)
@ 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.
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.
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.
Type type(const QSqlDatabase &db)
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
QCA_EXPORT bool isSupported(const char *features, const QString &provider=QString())
Test if a capability (algorithm) is available.
@ Decode
Operate in the "reverse" direction; for example, decrypting.
Definition qca_core.h:143
QCA_EXPORT void setAppName(const QString &name)
Set the application name that will be used by SASL server mode.
virtual void close() override
SocketError error() const const
void errorOccurred(QAbstractSocket::SocketError socketError)
bool isEmpty() const const
qsizetype size() const const
void bytesWritten(qint64 bytes)
virtual bool canReadLine() const const
QByteArray readLine(qint64 maxSize)
void readyRead()
qint64 write(const QByteArray &data)
bool contains(const AT &value) const const
qsizetype count() const const
QList< T > mid(qsizetype pos, qsizetype length) const const
qsizetype removeAll(const AT &t)
void removeAt(qsizetype i)
void removeFirst()
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void setParent(QObject *parent)
QString fromLatin1(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
void truncate(qsizetype position)
QString join(QChar separator) const const
QStringView mid(qsizetype start, qsizetype length) const const
int toInt(bool *ok, int base) const const
bool listen(const QHostAddress &address, quint16 port)
void newConnection()
virtual QTcpSocket * nextPendingConnection()
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
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.