File indexing completed on 2024-11-10 12:49:07
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 ¶ms) 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"