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) {
40 case QAbstractSocket::ConnectionRefusedError:
41 s = QStringLiteral("connection refused or timed out");
42 break;
43 case QAbstractSocket::RemoteHostClosedError:
44 s = QStringLiteral("remote host closed the connection");
45 break;
46 case QAbstractSocket::HostNotFoundError:
47 s = QStringLiteral("host not found");
48 break;
49 case QAbstractSocket::SocketAccessError:
50 s = QStringLiteral("access error");
51 break;
52 case QAbstractSocket::SocketResourceError:
53 s = QStringLiteral("too many sockets");
54 break;
55 case QAbstractSocket::SocketTimeoutError:
56 s = QStringLiteral("operation timed out");
57 break;
58 case QAbstractSocket::DatagramTooLargeError:
59 s = QStringLiteral("datagram was larger than system limit");
60 break;
61 case QAbstractSocket::NetworkError:
62 s = QStringLiteral("network error");
63 break;
64 case QAbstractSocket::AddressInUseError:
65 s = QStringLiteral("address is already in use");
66 break;
67 case QAbstractSocket::SocketAddressNotAvailableError:
68 s = QStringLiteral("address does not belong to the host");
69 break;
70 case QAbstractSocket::UnsupportedSocketOperationError:
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) {
84 case QCA::SASL::NoMechanism:
85 s = QStringLiteral("no appropriate mechanism could be negotiated");
86 break;
87 case QCA::SASL::BadProtocol:
88 s = QStringLiteral("bad SASL protocol");
89 break;
90 case QCA::SASL::BadAuth:
91 s = QStringLiteral("authentication failed");
92 break;
93 case QCA::SASL::NoAuthzid:
94 s = QStringLiteral("authorization failed");
95 break;
96 case QCA::SASL::TooWeak:
97 s = QStringLiteral("mechanism too weak for this user");
98 break;
99 case QCA::SASL::NeedEncrypt:
100 s = QStringLiteral("encryption is needed to use this mechanism");
101 break;
102 case QCA::SASL::Expired:
103 s = QStringLiteral("passphrase expired");
104 break;
105 case QCA::SASL::Disabled:
106 s = QStringLiteral("account is disabled");
107 break;
108 case QCA::SASL::NoUser:
109 s = QStringLiteral("user not found");
110 break;
111 case QCA::SASL::RemoteUnavailable:
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{
126 Q_OBJECT
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
143Q_SIGNALS:
144 void quit();
145
146private Q_SLOTS:
147 void server_newConnection();
148};
149
150// --- ServerTestHandler
151
152class ServerTestHandler : public QObject
153{
154 Q_OBJECT
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(sender: sock, signal: &QTcpSocket::disconnected, context: this, slot: &ServerTestHandler::sock_disconnected);
183 connect(sender: sock, signal: &QTcpSocket::readyRead, context: this, slot: &ServerTestHandler::sock_readyRead);
184#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
185 connect(sender: sock, signal: &QTcpSocket::errorOccurred, context: this, slot: &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(sender: sock, signal: &QTcpSocket::bytesWritten, context: this, slot: &ServerTestHandler::sock_bytesWritten);
193
194 sasl = new QCA::SASL(this);
195 connect(sender: sasl, signal: &QCA::SASL::authCheck, context: this, slot: &ServerTestHandler::sasl_authCheck);
196 connect(sender: sasl, signal: &QCA::SASL::nextStep, context: this, slot: &ServerTestHandler::sasl_nextStep);
197 connect(sender: sasl, signal: &QCA::SASL::authenticated, context: this, slot: &ServerTestHandler::sasl_authenticated);
198 connect(sender: sasl, signal: &QCA::SASL::readyRead, context: this, slot: &ServerTestHandler::sasl_readyRead);
199 connect(sender: sasl, signal: &QCA::SASL::readyReadOutgoing, context: this, slot: &ServerTestHandler::sasl_readyReadOutgoing);
200 connect(sender: sasl, signal: &QCA::SASL::error, context: this, slot: &ServerTestHandler::sasl_error);
201 connect(sender: sasl, signal: &QCA::SASL::serverStarted, context: this, slot: &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(f: (QCA::SASL::AuthFlags)flags, minSSF: 0, maxSSF: 256);
210
211 printf(format: "%d: Connection received! Starting SASL handshake...\n", id);
212 sasl->startServer(service: 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(line: sasl->mechanismList().join(QStringLiteral(" ")));
224 }
225
226 void sock_disconnected()
227 {
228 printf(format: "%d: Connection closed.\n", id);
229 discard();
230 }
231
232 void sock_error(QAbstractSocket::SocketError x)
233 {
234 if (x == QAbstractSocket::RemoteHostClosedError) {
235 printf(format: "%d: Error: client closed connection unexpectedly.\n", id);
236 discard();
237 return;
238 }
239
240 printf(format: "%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(ba: sock->readLine());
248 line.truncate(pos: 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(encryptedBytes: x);
258 if (toWrite == 0) {
259 printf(format: "%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(ba: stepData);
271 }
272 sendLine(line);
273 }
274
275 void sasl_authCheck(const QString &user, const QString &authzid)
276 {
277 printf(format: "%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.
290 sasl->continueAfterAuthCheck();
291 }
292
293 void sasl_authenticated()
294 {
295 sendLine(QStringLiteral("A"));
296 printf(format: "%d: Authentication success.\n", id);
297 mode = 2; // switch to app mode
298 printf(format: "%d: SSF: %d\n", id, sasl->ssf());
299 sendLine(line: str);
300 }
301
302 void sasl_readyRead()
303 {
304 QByteArray a = sasl->read();
305 printf(format: "%d: Warning, client sent %d bytes unexpectedly.\n", id, int(a.size()));
306 }
307
308 void sasl_readyReadOutgoing()
309 {
310 sock->write(data: sasl->readOutgoing());
311 }
312
313 void sasl_error()
314 {
315 int e = sasl->errorCode();
316 if (e == QCA::SASL::ErrorInit) {
317 printf(format: "%d: Error: sasl: initialization failed.\n", id);
318 } else if (e == QCA::SASL::ErrorHandshake) {
319 QString errstr = saslAuthConditionToString(x: sasl->authCondition());
320 sendLine(QStringLiteral("E,") + errstr);
321 printf(format: "%d: Error: sasl: %s.\n", id, qPrintable(errstr));
322 } else if (e == QCA::SASL::ErrorCrypt) {
323 printf(format: "%d: Error: sasl: broken security layer.\n", id);
324 } else {
325 printf(format: "%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(format: "%d: Reading: [%s]\n", id, qPrintable(line));
340 if (mode == 0) {
341 int n = line.indexOf(c: QLatin1Char(' '));
342 if (n != -1) {
343 QString mech = line.mid(position: 0, n);
344 QString rest = QString::fromLatin1(ba: line.mid(position: n + 1).toUtf8());
345 sasl->putServerFirstStep(mech, clientInit: stringToArray(s: rest));
346 } else
347 sasl->putServerFirstStep(mech: line);
348 ++mode;
349 } else if (mode == 1) {
350 QString type, rest;
351 int n = line.indexOf(c: QLatin1Char(','));
352 if (n != -1) {
353 type = line.mid(position: 0, n);
354 rest = line.mid(position: n + 1);
355 } else {
356 type = line;
357 rest = QLatin1String("");
358 }
359
360 if (type == QLatin1String("C")) {
361 sasl->putStep(stepData: stringToArray(s: rest));
362 } else {
363 printf(format: "%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(a: 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(format: "%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(data: 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(sender: tcpServer, signal: &QTcpServer::newConnection, context: this, slot: &ServerTest::server_newConnection);
411}
412
413int ServerTest::reserveId()
414{
415 int n = 0;
416 while (ids.contains(t: n))
417 ++n;
418 ids += n;
419 return n;
420}
421
422void ServerTest::releaseId(int id)
423{
424 ids.removeAll(t: id);
425}
426
427void ServerTest::start()
428{
429 if (!tcpServer->listen(address: QHostAddress::Any, port)) {
430 printf(format: "Error: unable to bind to port %d.\n", port);
431 emit quit();
432 return;
433 }
434
435 printf(format: "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(format: "usage: saslserver host (message)\n");
449 printf(format: "options: --proto=x, --realm=x\n");
450}
451
452int main(int argc, char **argv)
453{
454 QCA::Initializer init;
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(s: QLatin1String("--")))
467 continue;
468
469 QString opt = args[n].mid(position: 2);
470 QString var, val;
471 int at = opt.indexOf(c: QLatin1Char('='));
472 if (at != -1) {
473 var = opt.mid(position: 0, n: at);
474 val = opt.mid(position: 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(i: 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(c: QLatin1Char(':'));
501 if (at != -1) {
502 host = hostinput.mid(position: 0, n: at);
503#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
504 port = QStringView(hostinput).mid(pos: at + 1).toInt();
505#else
506 port = hostinput.midRef(at + 1).toInt();
507#endif
508 } else
509 host = hostinput;
510
511 if (!QCA::isSupported(features: "sasl")) {
512 printf(format: "Error: SASL support not found.\n");
513 return 1;
514 }
515
516 ServerTest server(host, port, proto, realm, str);
517 QObject::connect(sender: &server, signal: &ServerTest::quit, context: &qapp, slot: &QCoreApplication::quit);
518 QTimer::singleShot(interval: 0, receiver: &server, slot: &ServerTest::start);
519 qapp.exec();
520
521 return 0;
522}
523
524#include "saslserver.moc"
525

source code of qca/examples/saslserver/saslserver.cpp