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 | |
36 | static 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 | |
80 | static 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 | |
124 | class ServerTest : public QObject |
125 | { |
126 | Q_OBJECT |
127 | |
128 | private: |
129 | QString host, proto, realm, str; |
130 | int port; |
131 | QTcpServer *tcpServer; |
132 | QList<int> ids; |
133 | |
134 | public: |
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 | |
140 | public Q_SLOTS: |
141 | void start(); |
142 | |
143 | Q_SIGNALS: |
144 | void quit(); |
145 | |
146 | private Q_SLOTS: |
147 | void server_newConnection(); |
148 | }; |
149 | |
150 | // --- ServerTestHandler |
151 | |
152 | class ServerTestHandler : public QObject |
153 | { |
154 | Q_OBJECT |
155 | |
156 | private: |
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 | |
165 | public: |
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 | |
220 | private 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 | |
331 | private: |
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 | |
398 | ServerTest::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 | |
413 | int ServerTest::reserveId() |
414 | { |
415 | int n = 0; |
416 | while (ids.contains(t: n)) |
417 | ++n; |
418 | ids += n; |
419 | return n; |
420 | } |
421 | |
422 | void ServerTest::releaseId(int id) |
423 | { |
424 | ids.removeAll(t: id); |
425 | } |
426 | |
427 | void 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 | |
438 | void ServerTest::server_newConnection() |
439 | { |
440 | QTcpSocket *sock = tcpServer->nextPendingConnection(); |
441 | new ServerTestHandler(this, sock, host, proto, realm, str); |
442 | } |
443 | |
444 | // --- |
445 | |
446 | void usage() |
447 | { |
448 | printf(format: "usage: saslserver host (message)\n" ); |
449 | printf(format: "options: --proto=x, --realm=x\n" ); |
450 | } |
451 | |
452 | int 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 | |