File indexing completed on 2024-04-28 04:43:39

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"