File indexing completed on 2024-09-15 04:15:56
0001 /* 0002 Copyright (C) 2003-2008 Justin Karneges <justin@affinix.com> 0003 Copyright (C) 2006 Michail Pishchagin 0004 0005 Permission is hereby granted, free of charge, to any person obtaining a copy 0006 of this software and associated documentation files (the "Software"), to deal 0007 in the Software without restriction, including without limitation the rights 0008 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 0009 copies of the Software, and to permit persons to whom the Software is 0010 furnished to do so, subject to the following conditions: 0011 0012 The above copyright notice and this permission notice shall be included in 0013 all copies or substantial portions of the Software. 0014 0015 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 0016 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 0017 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 0018 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 0019 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 0020 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 0021 */ 0022 0023 #include <QCoreApplication> 0024 #include <QTcpServer> 0025 #include <QTcpSocket> 0026 #include <QTimer> 0027 #include <cstdio> 0028 0029 // QtCrypto has the declarations for all of QCA 0030 #include <QtCrypto> 0031 0032 #ifdef QT_STATICPLUGIN 0033 #include "import_plugins.h" 0034 #endif 0035 0036 static QString socketErrorToString(QAbstractSocket::SocketError x) 0037 { 0038 QString s; 0039 switch (x) { 0040 case QAbstractSocket::ConnectionRefusedError: 0041 s = QStringLiteral("connection refused or timed out"); 0042 break; 0043 case QAbstractSocket::RemoteHostClosedError: 0044 s = QStringLiteral("remote host closed the connection"); 0045 break; 0046 case QAbstractSocket::HostNotFoundError: 0047 s = QStringLiteral("host not found"); 0048 break; 0049 case QAbstractSocket::SocketAccessError: 0050 s = QStringLiteral("access error"); 0051 break; 0052 case QAbstractSocket::SocketResourceError: 0053 s = QStringLiteral("too many sockets"); 0054 break; 0055 case QAbstractSocket::SocketTimeoutError: 0056 s = QStringLiteral("operation timed out"); 0057 break; 0058 case QAbstractSocket::DatagramTooLargeError: 0059 s = QStringLiteral("datagram was larger than system limit"); 0060 break; 0061 case QAbstractSocket::NetworkError: 0062 s = QStringLiteral("network error"); 0063 break; 0064 case QAbstractSocket::AddressInUseError: 0065 s = QStringLiteral("address is already in use"); 0066 break; 0067 case QAbstractSocket::SocketAddressNotAvailableError: 0068 s = QStringLiteral("address does not belong to the host"); 0069 break; 0070 case QAbstractSocket::UnsupportedSocketOperationError: 0071 s = QStringLiteral("operation is not supported by the local operating system"); 0072 break; 0073 default: 0074 s = QStringLiteral("unknown socket error"); 0075 break; 0076 } 0077 return s; 0078 } 0079 0080 static QString saslAuthConditionToString(QCA::SASL::AuthCondition x) 0081 { 0082 QString s; 0083 switch (x) { 0084 case QCA::SASL::NoMechanism: 0085 s = QStringLiteral("no appropriate mechanism could be negotiated"); 0086 break; 0087 case QCA::SASL::BadProtocol: 0088 s = QStringLiteral("bad SASL protocol"); 0089 break; 0090 case QCA::SASL::BadAuth: 0091 s = QStringLiteral("authentication failed"); 0092 break; 0093 case QCA::SASL::NoAuthzid: 0094 s = QStringLiteral("authorization failed"); 0095 break; 0096 case QCA::SASL::TooWeak: 0097 s = QStringLiteral("mechanism too weak for this user"); 0098 break; 0099 case QCA::SASL::NeedEncrypt: 0100 s = QStringLiteral("encryption is needed to use this mechanism"); 0101 break; 0102 case QCA::SASL::Expired: 0103 s = QStringLiteral("passphrase expired"); 0104 break; 0105 case QCA::SASL::Disabled: 0106 s = QStringLiteral("account is disabled"); 0107 break; 0108 case QCA::SASL::NoUser: 0109 s = QStringLiteral("user not found"); 0110 break; 0111 case QCA::SASL::RemoteUnavailable: 0112 s = QStringLiteral("needed remote service is unavailable"); 0113 break; 0114 // AuthFail or unknown (including those defined for client only) 0115 default: 0116 s = QStringLiteral("generic authentication failure"); 0117 break; 0118 }; 0119 return s; 0120 } 0121 0122 // --- ServerTest declaration 0123 0124 class ServerTest : public QObject 0125 { 0126 Q_OBJECT 0127 0128 private: 0129 QString host, proto, realm, str; 0130 int port; 0131 QTcpServer *tcpServer; 0132 QList<int> ids; 0133 0134 public: 0135 ServerTest(const QString &_host, int _port, const QString &_proto, const QString &_realm, const QString &_str); 0136 0137 int reserveId(); 0138 void releaseId(int id); 0139 0140 public Q_SLOTS: 0141 void start(); 0142 0143 Q_SIGNALS: 0144 void quit(); 0145 0146 private Q_SLOTS: 0147 void server_newConnection(); 0148 }; 0149 0150 // --- ServerTestHandler 0151 0152 class ServerTestHandler : public QObject 0153 { 0154 Q_OBJECT 0155 0156 private: 0157 ServerTest *serverTest; 0158 QTcpSocket *sock; 0159 QCA::SASL *sasl; 0160 int id; 0161 QString host, proto, realm, str; 0162 int mode; // 0 = receive mechanism list, 1 = sasl negotiation, 2 = app 0163 int toWrite; 0164 0165 public: 0166 ServerTestHandler(ServerTest *_serverTest, 0167 QTcpSocket *_sock, 0168 const QString &_host, 0169 const QString &_proto, 0170 const QString &_realm, 0171 const QString &_str) 0172 : serverTest(_serverTest) 0173 , sock(_sock) 0174 , host(_host) 0175 , proto(_proto) 0176 , realm(_realm) 0177 , str(_str) 0178 { 0179 id = serverTest->reserveId(); 0180 0181 sock->setParent(this); 0182 connect(sock, &QTcpSocket::disconnected, this, &ServerTestHandler::sock_disconnected); 0183 connect(sock, &QTcpSocket::readyRead, this, &ServerTestHandler::sock_readyRead); 0184 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 0185 connect(sock, &QTcpSocket::errorOccurred, this, &ServerTestHandler::sock_error); 0186 #else 0187 connect(sock, 0188 QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), 0189 this, 0190 &ServerTestHandler::sock_error); 0191 #endif 0192 connect(sock, &QTcpSocket::bytesWritten, this, &ServerTestHandler::sock_bytesWritten); 0193 0194 sasl = new QCA::SASL(this); 0195 connect(sasl, &QCA::SASL::authCheck, this, &ServerTestHandler::sasl_authCheck); 0196 connect(sasl, &QCA::SASL::nextStep, this, &ServerTestHandler::sasl_nextStep); 0197 connect(sasl, &QCA::SASL::authenticated, this, &ServerTestHandler::sasl_authenticated); 0198 connect(sasl, &QCA::SASL::readyRead, this, &ServerTestHandler::sasl_readyRead); 0199 connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ServerTestHandler::sasl_readyReadOutgoing); 0200 connect(sasl, &QCA::SASL::error, this, &ServerTestHandler::sasl_error); 0201 connect(sasl, &QCA::SASL::serverStarted, this, &ServerTestHandler::sasl_serverStarted); 0202 0203 mode = 0; // mech list mode 0204 toWrite = 0; 0205 0206 int flags = 0; 0207 flags |= QCA::SASL::AllowPlain; 0208 flags |= QCA::SASL::AllowAnonymous; 0209 sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256); 0210 0211 printf("%d: Connection received! Starting SASL handshake...\n", id); 0212 sasl->startServer(proto, host, realm); 0213 } 0214 0215 ~ServerTestHandler() override 0216 { 0217 serverTest->releaseId(id); 0218 } 0219 0220 private Q_SLOTS: 0221 void sasl_serverStarted() 0222 { 0223 sendLine(sasl->mechanismList().join(QStringLiteral(" "))); 0224 } 0225 0226 void sock_disconnected() 0227 { 0228 printf("%d: Connection closed.\n", id); 0229 discard(); 0230 } 0231 0232 void sock_error(QAbstractSocket::SocketError x) 0233 { 0234 if (x == QAbstractSocket::RemoteHostClosedError) { 0235 printf("%d: Error: client closed connection unexpectedly.\n", id); 0236 discard(); 0237 return; 0238 } 0239 0240 printf("%d: Error: socket: %s\n", id, qPrintable(socketErrorToString(x))); 0241 discard(); 0242 } 0243 0244 void sock_readyRead() 0245 { 0246 if (sock->canReadLine()) { 0247 QString line = QString::fromLatin1(sock->readLine()); 0248 line.truncate(line.length() - 1); // chop the newline 0249 handleLine(line); 0250 } 0251 } 0252 0253 void sock_bytesWritten(qint64 x) 0254 { 0255 if (mode == 2) // app mode 0256 { 0257 toWrite -= sasl->convertBytesWritten(x); 0258 if (toWrite == 0) { 0259 printf("%d: Sent, closing.\n", id); 0260 sock->close(); 0261 } 0262 } 0263 } 0264 0265 void sasl_nextStep(const QByteArray &stepData) 0266 { 0267 QString line = QStringLiteral("C"); 0268 if (!stepData.isEmpty()) { 0269 line += QLatin1Char(','); 0270 line += arrayToString(stepData); 0271 } 0272 sendLine(line); 0273 } 0274 0275 void sasl_authCheck(const QString &user, const QString &authzid) 0276 { 0277 printf("%d: AuthCheck: User: [%s], Authzid: [%s]\n", id, qPrintable(user), qPrintable(authzid)); 0278 0279 // user - who has logged in, confirmed by sasl 0280 // authzid - the identity the user wishes to act as, which 0281 // could be another user or just any arbitrary string (in 0282 // XMPP, this field holds a Jabber ID, for example). this 0283 // field is not necessarily confirmed by sasl, and the 0284 // decision about whether the user can act as the authzid 0285 // must be made by the app. 0286 0287 // for this simple example program, we allow anyone to use 0288 // the service, and simply continue onward with the 0289 // negotiation. 0290 sasl->continueAfterAuthCheck(); 0291 } 0292 0293 void sasl_authenticated() 0294 { 0295 sendLine(QStringLiteral("A")); 0296 printf("%d: Authentication success.\n", id); 0297 mode = 2; // switch to app mode 0298 printf("%d: SSF: %d\n", id, sasl->ssf()); 0299 sendLine(str); 0300 } 0301 0302 void sasl_readyRead() 0303 { 0304 QByteArray a = sasl->read(); 0305 printf("%d: Warning, client sent %d bytes unexpectedly.\n", id, int(a.size())); 0306 } 0307 0308 void sasl_readyReadOutgoing() 0309 { 0310 sock->write(sasl->readOutgoing()); 0311 } 0312 0313 void sasl_error() 0314 { 0315 int e = sasl->errorCode(); 0316 if (e == QCA::SASL::ErrorInit) { 0317 printf("%d: Error: sasl: initialization failed.\n", id); 0318 } else if (e == QCA::SASL::ErrorHandshake) { 0319 QString errstr = saslAuthConditionToString(sasl->authCondition()); 0320 sendLine(QStringLiteral("E,") + errstr); 0321 printf("%d: Error: sasl: %s.\n", id, qPrintable(errstr)); 0322 } else if (e == QCA::SASL::ErrorCrypt) { 0323 printf("%d: Error: sasl: broken security layer.\n", id); 0324 } else { 0325 printf("%d: Error: sasl: unknown error.\n", id); 0326 } 0327 0328 sock->close(); 0329 } 0330 0331 private: 0332 void discard() 0333 { 0334 deleteLater(); 0335 } 0336 0337 void handleLine(const QString &line) 0338 { 0339 printf("%d: Reading: [%s]\n", id, qPrintable(line)); 0340 if (mode == 0) { 0341 int n = line.indexOf(QLatin1Char(' ')); 0342 if (n != -1) { 0343 QString mech = line.mid(0, n); 0344 QString rest = QString::fromLatin1(line.mid(n + 1).toUtf8()); 0345 sasl->putServerFirstStep(mech, stringToArray(rest)); 0346 } else 0347 sasl->putServerFirstStep(line); 0348 ++mode; 0349 } else if (mode == 1) { 0350 QString type, rest; 0351 int n = line.indexOf(QLatin1Char(',')); 0352 if (n != -1) { 0353 type = line.mid(0, n); 0354 rest = line.mid(n + 1); 0355 } else { 0356 type = line; 0357 rest = QLatin1String(""); 0358 } 0359 0360 if (type == QLatin1String("C")) { 0361 sasl->putStep(stringToArray(rest)); 0362 } else { 0363 printf("%d: Bad format from peer, closing.\n", id); 0364 sock->close(); 0365 return; 0366 } 0367 } 0368 } 0369 0370 QString arrayToString(const QByteArray &ba) 0371 { 0372 QCA::Base64 encoder; 0373 return encoder.arrayToString(ba); 0374 } 0375 0376 QByteArray stringToArray(const QString &s) 0377 { 0378 QCA::Base64 decoder(QCA::Decode); 0379 return decoder.stringToArray(s).toByteArray(); 0380 } 0381 0382 void sendLine(const QString &line) 0383 { 0384 printf("%d: Writing: {%s}\n", id, qPrintable(line)); 0385 QString s = line + QLatin1Char('\n'); 0386 QByteArray a = s.toUtf8(); 0387 if (mode == 2) // app mode 0388 { 0389 toWrite += a.size(); 0390 sasl->write(a); // write to sasl 0391 } else // mech list or sasl negotiation 0392 sock->write(a); // write to socket 0393 } 0394 }; 0395 0396 // --- ServerTest implementation 0397 0398 ServerTest::ServerTest(const QString &_host, 0399 int _port, 0400 const QString &_proto, 0401 const QString &_realm, 0402 const QString &_str) 0403 : host(_host) 0404 , proto(_proto) 0405 , realm(_realm) 0406 , str(_str) 0407 , port(_port) 0408 { 0409 tcpServer = new QTcpServer(this); 0410 connect(tcpServer, &QTcpServer::newConnection, this, &ServerTest::server_newConnection); 0411 } 0412 0413 int ServerTest::reserveId() 0414 { 0415 int n = 0; 0416 while (ids.contains(n)) 0417 ++n; 0418 ids += n; 0419 return n; 0420 } 0421 0422 void ServerTest::releaseId(int id) 0423 { 0424 ids.removeAll(id); 0425 } 0426 0427 void ServerTest::start() 0428 { 0429 if (!tcpServer->listen(QHostAddress::Any, port)) { 0430 printf("Error: unable to bind to port %d.\n", port); 0431 emit quit(); 0432 return; 0433 } 0434 0435 printf("Serving on %s:%d, for protocol %s ...\n", qPrintable(host), port, qPrintable(proto)); 0436 } 0437 0438 void ServerTest::server_newConnection() 0439 { 0440 QTcpSocket *sock = tcpServer->nextPendingConnection(); 0441 new ServerTestHandler(this, sock, host, proto, realm, str); 0442 } 0443 0444 // --- 0445 0446 void usage() 0447 { 0448 printf("usage: saslserver host (message)\n"); 0449 printf("options: --proto=x, --realm=x\n"); 0450 } 0451 0452 int main(int argc, char **argv) 0453 { 0454 QCA::Initializer init; 0455 QCoreApplication qapp(argc, argv); 0456 0457 QCA::setAppName(QStringLiteral("saslserver")); 0458 0459 QStringList args = qapp.arguments(); 0460 args.removeFirst(); 0461 0462 // options 0463 QString proto = QStringLiteral("qcatest"); // default protocol 0464 QString realm; 0465 for (int n = 0; n < args.count(); ++n) { 0466 if (!args[n].startsWith(QLatin1String("--"))) 0467 continue; 0468 0469 QString opt = args[n].mid(2); 0470 QString var, val; 0471 int at = opt.indexOf(QLatin1Char('=')); 0472 if (at != -1) { 0473 var = opt.mid(0, at); 0474 val = opt.mid(at + 1); 0475 } else 0476 var = opt; 0477 0478 if (var == QLatin1String("proto")) 0479 proto = val; 0480 else if (var == QLatin1String("realm")) 0481 realm = val; 0482 0483 args.removeAt(n); 0484 --n; // adjust position 0485 } 0486 0487 if (args.count() < 1) { 0488 usage(); 0489 return 0; 0490 } 0491 0492 QString host; 0493 int port = 8001; // default port 0494 0495 QString hostinput = args[0]; 0496 QString str = QStringLiteral("Hello, World"); 0497 if (args.count() >= 2) 0498 str = args[1]; 0499 0500 int at = hostinput.indexOf(QLatin1Char(':')); 0501 if (at != -1) { 0502 host = hostinput.mid(0, at); 0503 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2) 0504 port = QStringView(hostinput).mid(at + 1).toInt(); 0505 #else 0506 port = hostinput.midRef(at + 1).toInt(); 0507 #endif 0508 } else 0509 host = hostinput; 0510 0511 if (!QCA::isSupported("sasl")) { 0512 printf("Error: SASL support not found.\n"); 0513 return 1; 0514 } 0515 0516 ServerTest server(host, port, proto, realm, str); 0517 QObject::connect(&server, &ServerTest::quit, &qapp, &QCoreApplication::quit); 0518 QTimer::singleShot(0, &server, &ServerTest::start); 0519 qapp.exec(); 0520 0521 return 0; 0522 } 0523 0524 #include "saslserver.moc"