File indexing completed on 2024-05-05 04:44:58

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 prompt(const QString &s)
0037 {
0038     printf("* %s ", qPrintable(s));
0039     fflush(stdout);
0040     char line[256];
0041     fgets(line, 255, stdin);
0042     QString result = QString::fromLatin1(line);
0043     if (result[result.length() - 1] == QLatin1Char('\n'))
0044         result.truncate(result.length() - 1);
0045     return result;
0046 }
0047 
0048 static QString socketErrorToString(QAbstractSocket::SocketError x)
0049 {
0050     QString s;
0051     switch (x) {
0052     case QAbstractSocket::ConnectionRefusedError:
0053         s = QStringLiteral("connection refused or timed out");
0054         break;
0055     case QAbstractSocket::RemoteHostClosedError:
0056         s = QStringLiteral("remote host closed the connection");
0057         break;
0058     case QAbstractSocket::HostNotFoundError:
0059         s = QStringLiteral("host not found");
0060         break;
0061     case QAbstractSocket::SocketAccessError:
0062         s = QStringLiteral("access error");
0063         break;
0064     case QAbstractSocket::SocketResourceError:
0065         s = QStringLiteral("too many sockets");
0066         break;
0067     case QAbstractSocket::SocketTimeoutError:
0068         s = QStringLiteral("operation timed out");
0069         break;
0070     case QAbstractSocket::DatagramTooLargeError:
0071         s = QStringLiteral("datagram was larger than system limit");
0072         break;
0073     case QAbstractSocket::NetworkError:
0074         s = QStringLiteral("network error");
0075         break;
0076     case QAbstractSocket::AddressInUseError:
0077         s = QStringLiteral("address is already in use");
0078         break;
0079     case QAbstractSocket::SocketAddressNotAvailableError:
0080         s = QStringLiteral("address does not belong to the host");
0081         break;
0082     case QAbstractSocket::UnsupportedSocketOperationError:
0083         s = QStringLiteral("operation is not supported by the local operating system");
0084         break;
0085     default:
0086         s = QStringLiteral("unknown socket error");
0087         break;
0088     }
0089     return s;
0090 }
0091 
0092 static QString saslAuthConditionToString(QCA::SASL::AuthCondition x)
0093 {
0094     QString s;
0095     switch (x) {
0096     case QCA::SASL::NoMechanism:
0097         s = QStringLiteral("no appropriate mechanism could be negotiated");
0098         break;
0099     case QCA::SASL::BadProtocol:
0100         s = QStringLiteral("bad SASL protocol");
0101         break;
0102     case QCA::SASL::BadServer:
0103         s = QStringLiteral("server failed mutual authentication");
0104         break;
0105     // AuthFail or unknown (including those defined for server only)
0106     default:
0107         s = QStringLiteral("generic authentication failure");
0108         break;
0109     };
0110     return s;
0111 }
0112 
0113 class ClientTest : public QObject
0114 {
0115     Q_OBJECT
0116 
0117 private:
0118     QString     host, proto, authzid, realm, user, pass;
0119     int         port;
0120     bool        no_authzid, no_realm;
0121     int         mode; // 0 = receive mechanism list, 1 = sasl negotiation, 2 = app
0122     QTcpSocket *sock;
0123     QCA::SASL  *sasl;
0124     QByteArray  inbuf;
0125     bool        sock_done;
0126     int         waitCycles;
0127 
0128 public:
0129     ClientTest(const QString &_host,
0130                int            _port,
0131                const QString &_proto,
0132                const QString &_authzid,
0133                const QString &_realm,
0134                const QString &_user,
0135                const QString &_pass,
0136                bool           _no_authzid,
0137                bool           _no_realm)
0138         : host(_host)
0139         , proto(_proto)
0140         , authzid(_authzid)
0141         , realm(_realm)
0142         , user(_user)
0143         , pass(_pass)
0144         , port(_port)
0145         , no_authzid(_no_authzid)
0146         , no_realm(_no_realm)
0147         , sock_done(false)
0148         , waitCycles(0)
0149     {
0150         sock = new QTcpSocket(this);
0151         connect(sock, &QTcpSocket::connected, this, &ClientTest::sock_connected);
0152         connect(sock, &QTcpSocket::readyRead, this, &ClientTest::sock_readyRead);
0153 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
0154         connect(sock, &QTcpSocket::errorOccurred, this, &ClientTest::sock_error);
0155 #else
0156         connect(sock, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &ClientTest::sock_error);
0157 #endif
0158 
0159         sasl = new QCA::SASL(this);
0160         connect(sasl, &QCA::SASL::clientStarted, this, &ClientTest::sasl_clientFirstStep);
0161         connect(sasl, &QCA::SASL::nextStep, this, &ClientTest::sasl_nextStep);
0162         connect(sasl, &QCA::SASL::needParams, this, &ClientTest::sasl_needParams);
0163         connect(sasl, &QCA::SASL::authenticated, this, &ClientTest::sasl_authenticated);
0164         connect(sasl, &QCA::SASL::readyRead, this, &ClientTest::sasl_readyRead);
0165         connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ClientTest::sasl_readyReadOutgoing);
0166         connect(sasl, &QCA::SASL::error, this, &ClientTest::sasl_error);
0167     }
0168 
0169 public Q_SLOTS:
0170     void start()
0171     {
0172         mode = 0; // mech list mode
0173 
0174         int flags = 0;
0175         flags |= QCA::SASL::AllowPlain;
0176         flags |= QCA::SASL::AllowAnonymous;
0177         sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256);
0178 
0179         if (!user.isEmpty())
0180             sasl->setUsername(user);
0181         if (!authzid.isEmpty())
0182             sasl->setAuthzid(authzid);
0183         if (!pass.isEmpty())
0184             sasl->setPassword(pass.toUtf8());
0185         if (!realm.isEmpty())
0186             sasl->setRealm(realm);
0187 
0188         printf("Connecting to %s:%d, for protocol %s\n", qPrintable(host), port, qPrintable(proto));
0189         sock->connectToHost(host, port);
0190     }
0191 
0192 Q_SIGNALS:
0193     void quit();
0194 
0195 private Q_SLOTS:
0196     void sock_connected()
0197     {
0198         printf("Connected to server.  Awaiting mechanism list...\n");
0199     }
0200 
0201     void sock_error(QAbstractSocket::SocketError x)
0202     {
0203         if (x == QAbstractSocket::RemoteHostClosedError) {
0204             if (mode == 2) // app mode, where disconnect means completion
0205             {
0206                 sock_done = true;
0207                 tryFinished();
0208                 return;
0209             } else // any other mode, where disconnect is an error
0210             {
0211                 printf("Error: server closed connection unexpectedly.\n");
0212                 emit quit();
0213                 return;
0214             }
0215         }
0216 
0217         printf("Error: socket: %s\n", qPrintable(socketErrorToString(x)));
0218         emit quit();
0219     }
0220 
0221     void sock_readyRead()
0222     {
0223         if (mode == 2) // app mode
0224         {
0225             QByteArray a = sock->readAll();
0226             printf("Read %d bytes\n", int(a.size()));
0227 
0228             // there is a possible flaw in the qca 2.0 api, in
0229             //   that if sasl data is received from the peer
0230             //   followed by a disconnect from the peer, there is
0231             //   no clear approach to salvaging the bytes.  tls is
0232             //   not affected because tls has the concept of
0233             //   closing a session.  with sasl, there is no
0234             //   closing, and since the qca api is asynchronous,
0235             //   we could potentially wait forever for decoded
0236             //   data, if the last write was a partial packet.
0237             //
0238             // for now, we can perform a simple workaround of
0239             //   waiting at least three event loop cycles for
0240             //   decoded data before giving up and assuming the
0241             //   last write was partial.  the fact is, all current
0242             //   qca sasl providers respond within this time
0243             //   frame, so this fix should work fine for now.  in
0244             //   qca 2.1, we should revise the api to handle this
0245             //   situation better.
0246             //
0247             // further note: i guess this only affects application
0248             //   protocols that have no close message of their
0249             //   own, and rely on the tcp-level close.  examples
0250             //   are http, and of course this qcatest protocol.
0251             if (waitCycles == 0) {
0252                 waitCycles = 3;
0253                 QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
0254             }
0255 
0256             sasl->writeIncoming(a);
0257         } else // mech list or sasl negotiation mode
0258         {
0259             if (sock->canReadLine()) {
0260                 QString line = QString::fromLatin1(sock->readLine());
0261                 line.truncate(line.length() - 1); // chop the newline
0262                 handleLine(line);
0263             }
0264         }
0265     }
0266 
0267     void sasl_clientFirstStep(bool clientInit, const QByteArray &clientInitData)
0268     {
0269         printf("Choosing mech: %s\n", qPrintable(sasl->mechanism()));
0270         QString line = sasl->mechanism();
0271         if (clientInit) {
0272             line += QLatin1Char(' ');
0273             line += arrayToString(clientInitData);
0274         }
0275         sendLine(line);
0276     }
0277 
0278     void sasl_nextStep(const QByteArray &stepData)
0279     {
0280         QString line = QStringLiteral("C");
0281         if (!stepData.isEmpty()) {
0282             line += QLatin1Char(',');
0283             line += arrayToString(stepData);
0284         }
0285         sendLine(line);
0286     }
0287 
0288     void sasl_needParams(const QCA::SASL::Params &params)
0289     {
0290         if (params.needUsername()) {
0291             user = prompt(QStringLiteral("Username:"));
0292             sasl->setUsername(user);
0293         }
0294 
0295         if (params.canSendAuthzid() && !no_authzid) {
0296             authzid = prompt(QStringLiteral("Authorize As (enter to skip):"));
0297             if (!authzid.isEmpty())
0298                 sasl->setAuthzid(authzid);
0299         }
0300 
0301         if (params.needPassword()) {
0302             QCA::ConsolePrompt prompt;
0303             prompt.getHidden(QStringLiteral("* Password"));
0304             prompt.waitForFinished();
0305             QCA::SecureArray pass = prompt.result();
0306             sasl->setPassword(pass);
0307         }
0308 
0309         if (params.canSendRealm() && !no_realm) {
0310             QStringList realms = sasl->realmList();
0311             printf("Available realms:\n");
0312             if (realms.isEmpty())
0313                 printf("  (none specified)\n");
0314             foreach (const QString &s, realms)
0315                 printf("  %s\n", qPrintable(s));
0316             realm = prompt(QStringLiteral("Realm (enter to skip):"));
0317             if (!realm.isEmpty())
0318                 sasl->setRealm(realm);
0319         }
0320 
0321         sasl->continueAfterParams();
0322     }
0323 
0324     void sasl_authenticated()
0325     {
0326         printf("SASL success!\n");
0327         printf("SSF: %d\n", sasl->ssf());
0328     }
0329 
0330     void sasl_readyRead()
0331     {
0332         QByteArray a = sasl->read();
0333         inbuf += a;
0334         processInbuf();
0335     }
0336 
0337     void sasl_readyReadOutgoing()
0338     {
0339         QByteArray a = sasl->readOutgoing();
0340         sock->write(a);
0341     }
0342 
0343     void sasl_error()
0344     {
0345         int e = sasl->errorCode();
0346         if (e == QCA::SASL::ErrorInit)
0347             printf("Error: sasl: initialization failed.\n");
0348         else if (e == QCA::SASL::ErrorHandshake)
0349             printf("Error: sasl: %s.\n", qPrintable(saslAuthConditionToString(sasl->authCondition())));
0350         else if (e == QCA::SASL::ErrorCrypt)
0351             printf("Error: sasl: broken security layer.\n");
0352         else
0353             printf("Error: sasl: unknown error.\n");
0354 
0355         emit quit();
0356     }
0357 
0358     void waitWriteIncoming()
0359     {
0360         --waitCycles;
0361         if (waitCycles > 0) {
0362             QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
0363             return;
0364         }
0365 
0366         tryFinished();
0367     }
0368 
0369 private:
0370     void tryFinished()
0371     {
0372         if (sock_done && waitCycles == 0) {
0373             printf("Finished, server closed connection.\n");
0374 
0375             // if we give up on waiting for a response to
0376             //   writeIncoming, then it might come late.  in
0377             //   theory this shouldn't happen if we wait enough
0378             //   cycles, but if one were to arrive then it could
0379             //   occur between the request to quit the app and
0380             //   the actual quit of the app.  to assist with
0381             //   debugging, then, we'll explicitly stop listening
0382             //   for signals here.  otherwise the response may
0383             //   still be received and displayed, giving a false
0384             //   sense of correctness.
0385             sasl->disconnect(this);
0386 
0387             emit quit();
0388         }
0389     }
0390 
0391     QString arrayToString(const QByteArray &ba)
0392     {
0393         return QCA::Base64().arrayToString(ba);
0394     }
0395 
0396     QByteArray stringToArray(const QString &s)
0397     {
0398         return QCA::Base64().stringToArray(s).toByteArray();
0399     }
0400 
0401     void sendLine(const QString &line)
0402     {
0403         printf("Writing: {%s}\n", qPrintable(line));
0404         QString    s = line + QLatin1Char('\n');
0405         QByteArray a = s.toUtf8();
0406         if (mode == 2)      // app mode
0407             sasl->write(a); // write to sasl
0408         else                // mech list or sasl negotiation
0409             sock->write(a); // write to socket
0410     }
0411 
0412     void processInbuf()
0413     {
0414         // collect completed lines from inbuf
0415         QStringList list;
0416         int         at;
0417         while ((at = inbuf.indexOf('\n')) != -1) {
0418             list += QString::fromUtf8(inbuf.mid(0, at));
0419             inbuf = inbuf.mid(at + 1);
0420         }
0421 
0422         // process the lines
0423         foreach (const QString &line, list)
0424             handleLine(line);
0425     }
0426 
0427     void handleLine(const QString &line)
0428     {
0429         printf("Reading: [%s]\n", qPrintable(line));
0430         if (mode == 0) {
0431             // first line is the method list
0432             const QStringList mechlist = line.split(QLatin1Char(' '));
0433             mode                       = 1; // switch to sasl negotiation mode
0434             sasl->startClient(proto, host, mechlist);
0435         } else if (mode == 1) {
0436             QString type, rest;
0437             int     n = line.indexOf(QLatin1Char(','));
0438             if (n != -1) {
0439                 type = line.mid(0, n);
0440                 rest = line.mid(n + 1);
0441             } else
0442                 type = line;
0443 
0444             if (type == QLatin1String("C")) {
0445                 sasl->putStep(stringToArray(rest));
0446             } else if (type == QLatin1String("E")) {
0447                 if (!rest.isEmpty())
0448                     printf("Error: server says: %s.\n", qPrintable(rest));
0449                 else
0450                     printf("Error: server error, unspecified.\n");
0451                 emit quit();
0452                 return;
0453             } else if (type == QLatin1String("A")) {
0454                 printf("Authentication success.\n");
0455                 mode = 2; // switch to app mode
0456 
0457                 // at this point, the server may send us text
0458                 //   lines for us to display and then close.
0459 
0460                 sock_readyRead(); // any extra data?
0461                 return;
0462             } else {
0463                 printf("Error: Bad format from peer, closing.\n");
0464                 emit quit();
0465                 return;
0466             }
0467         }
0468     }
0469 };
0470 
0471 void usage()
0472 {
0473     printf("usage: saslclient (options) host(:port) (user) (pass)\n");
0474     printf("options: --proto=x, --authzid=x, --realm=x\n");
0475 }
0476 
0477 int main(int argc, char **argv)
0478 {
0479     QCA::Initializer init;
0480     QCoreApplication qapp(argc, argv);
0481 
0482     QStringList args = qapp.arguments();
0483     args.removeFirst();
0484 
0485     // options
0486     QString proto = QStringLiteral("qcatest"); // default protocol
0487     QString authzid, realm;
0488     bool    no_authzid = false;
0489     bool    no_realm   = false;
0490     for (int n = 0; n < args.count(); ++n) {
0491         if (!args[n].startsWith(QLatin1String("--")))
0492             continue;
0493 
0494         QString opt = args[n].mid(2);
0495         QString var, val;
0496         int     at = opt.indexOf(QLatin1Char('='));
0497         if (at != -1) {
0498             var = opt.mid(0, at);
0499             val = opt.mid(at + 1);
0500         } else
0501             var = opt;
0502 
0503         if (var == QLatin1String("proto")) {
0504             proto = val;
0505         } else if (var == QLatin1String("authzid")) {
0506             // specifying empty authzid means force unspecified
0507             if (val.isEmpty())
0508                 no_authzid = true;
0509             else
0510                 authzid = val;
0511         } else if (var == QLatin1String("realm")) {
0512             // specifying empty realm means force unspecified
0513             if (val.isEmpty())
0514                 no_realm = true;
0515             else
0516                 realm = val;
0517         }
0518 
0519         args.removeAt(n);
0520         --n; // adjust position
0521     }
0522 
0523     if (args.count() < 1) {
0524         usage();
0525         return 0;
0526     }
0527 
0528     QString host, user, pass;
0529     int     port = 8001; // default port
0530 
0531     QString hostinput = args[0];
0532     if (args.count() >= 2)
0533         user = args[1];
0534     if (args.count() >= 3)
0535         pass = args[2];
0536 
0537     int at = hostinput.indexOf(QLatin1Char(':'));
0538     if (at != -1) {
0539         host = hostinput.mid(0, at);
0540 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
0541         port = QStringView(hostinput).mid(at + 1).toInt();
0542 #else
0543         port = hostinput.midRef(at + 1).toInt();
0544 #endif
0545     } else
0546         host = hostinput;
0547 
0548     if (!QCA::isSupported("sasl")) {
0549         printf("Error: SASL support not found.\n");
0550         return 1;
0551     }
0552 
0553     ClientTest client(host, port, proto, authzid, realm, user, pass, no_authzid, no_realm);
0554     QObject::connect(&client, &ClientTest::quit, &qapp, &QCoreApplication::quit);
0555     QTimer::singleShot(0, &client, &ClientTest::start);
0556     qapp.exec();
0557 
0558     return 0;
0559 }
0560 
0561 #include "saslclient.moc"