QCA

saslclient.cpp
1 /*
2  Copyright (C) 2003-2008 Justin Karneges <[email protected]>
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 prompt(const QString &s)
37 {
38  printf("* %s ", qPrintable(s));
39  fflush(stdout);
40  char line[256];
41  fgets(line, 255, stdin);
42  QString result = QString::fromLatin1(line);
43  if (result[result.length() - 1] == QLatin1Char('\n'))
44  result.truncate(result.length() - 1);
45  return result;
46 }
47 
48 static QString socketErrorToString(QAbstractSocket::SocketError x)
49 {
50  QString s;
51  switch (x) {
53  s = QStringLiteral("connection refused or timed out");
54  break;
56  s = QStringLiteral("remote host closed the connection");
57  break;
59  s = QStringLiteral("host not found");
60  break;
62  s = QStringLiteral("access error");
63  break;
65  s = QStringLiteral("too many sockets");
66  break;
68  s = QStringLiteral("operation timed out");
69  break;
71  s = QStringLiteral("datagram was larger than system limit");
72  break;
74  s = QStringLiteral("network error");
75  break;
77  s = QStringLiteral("address is already in use");
78  break;
80  s = QStringLiteral("address does not belong to the host");
81  break;
83  s = QStringLiteral("operation is not supported by the local operating system");
84  break;
85  default:
86  s = QStringLiteral("unknown socket error");
87  break;
88  }
89  return s;
90 }
91 
92 static QString saslAuthConditionToString(QCA::SASL::AuthCondition x)
93 {
94  QString s;
95  switch (x) {
97  s = QStringLiteral("no appropriate mechanism could be negotiated");
98  break;
100  s = QStringLiteral("bad SASL protocol");
101  break;
103  s = QStringLiteral("server failed mutual authentication");
104  break;
105  // AuthFail or unknown (including those defined for server only)
106  default:
107  s = QStringLiteral("generic authentication failure");
108  break;
109  };
110  return s;
111 }
112 
113 class ClientTest : public QObject
114 {
115  Q_OBJECT
116 
117 private:
118  QString host, proto, authzid, realm, user, pass;
119  int port;
120  bool no_authzid, no_realm;
121  int mode; // 0 = receive mechanism list, 1 = sasl negotiation, 2 = app
122  QTcpSocket *sock;
123  QCA::SASL * sasl;
124  QByteArray inbuf;
125  bool sock_done;
126  int waitCycles;
127 
128 public:
129  ClientTest(const QString &_host,
130  int _port,
131  const QString &_proto,
132  const QString &_authzid,
133  const QString &_realm,
134  const QString &_user,
135  const QString &_pass,
136  bool _no_authzid,
137  bool _no_realm)
138  : host(_host)
139  , proto(_proto)
140  , authzid(_authzid)
141  , realm(_realm)
142  , user(_user)
143  , pass(_pass)
144  , port(_port)
145  , no_authzid(_no_authzid)
146  , no_realm(_no_realm)
147  , sock_done(false)
148  , waitCycles(0)
149  {
150  sock = new QTcpSocket(this);
151  connect(sock, &QTcpSocket::connected, this, &ClientTest::sock_connected);
152  connect(sock, &QTcpSocket::readyRead, this, &ClientTest::sock_readyRead);
153 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
154  connect(sock, &QTcpSocket::errorOccurred, this, &ClientTest::sock_error);
155 #else
156  connect(sock, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &ClientTest::sock_error);
157 #endif
158 
159  sasl = new QCA::SASL(this);
160  connect(sasl, &QCA::SASL::clientStarted, this, &ClientTest::sasl_clientFirstStep);
161  connect(sasl, &QCA::SASL::nextStep, this, &ClientTest::sasl_nextStep);
162  connect(sasl, &QCA::SASL::needParams, this, &ClientTest::sasl_needParams);
163  connect(sasl, &QCA::SASL::authenticated, this, &ClientTest::sasl_authenticated);
164  connect(sasl, &QCA::SASL::readyRead, this, &ClientTest::sasl_readyRead);
165  connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ClientTest::sasl_readyReadOutgoing);
166  connect(sasl, &QCA::SASL::error, this, &ClientTest::sasl_error);
167  }
168 
169 public Q_SLOTS:
170  void start()
171  {
172  mode = 0; // mech list mode
173 
174  int flags = 0;
175  flags |= QCA::SASL::AllowPlain;
176  flags |= QCA::SASL::AllowAnonymous;
177  sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256);
178 
179  if (!user.isEmpty())
180  sasl->setUsername(user);
181  if (!authzid.isEmpty())
182  sasl->setAuthzid(authzid);
183  if (!pass.isEmpty())
184  sasl->setPassword(pass.toUtf8());
185  if (!realm.isEmpty())
186  sasl->setRealm(realm);
187 
188  printf("Connecting to %s:%d, for protocol %s\n", qPrintable(host), port, qPrintable(proto));
189  sock->connectToHost(host, port);
190  }
191 
192 Q_SIGNALS:
193  void quit();
194 
195 private Q_SLOTS:
196  void sock_connected()
197  {
198  printf("Connected to server. Awaiting mechanism list...\n");
199  }
200 
201  void sock_error(QAbstractSocket::SocketError x)
202  {
204  if (mode == 2) // app mode, where disconnect means completion
205  {
206  sock_done = true;
207  tryFinished();
208  return;
209  } else // any other mode, where disconnect is an error
210  {
211  printf("Error: server closed connection unexpectedly.\n");
212  emit quit();
213  return;
214  }
215  }
216 
217  printf("Error: socket: %s\n", qPrintable(socketErrorToString(x)));
218  emit quit();
219  }
220 
221  void sock_readyRead()
222  {
223  if (mode == 2) // app mode
224  {
225  QByteArray a = sock->readAll();
226  printf("Read %d bytes\n", int(a.size()));
227 
228  // there is a possible flaw in the qca 2.0 api, in
229  // that if sasl data is received from the peer
230  // followed by a disconnect from the peer, there is
231  // no clear approach to salvaging the bytes. tls is
232  // not affected because tls has the concept of
233  // closing a session. with sasl, there is no
234  // closing, and since the qca api is asynchronous,
235  // we could potentially wait forever for decoded
236  // data, if the last write was a partial packet.
237  //
238  // for now, we can perform a simple workaround of
239  // waiting at least three event loop cycles for
240  // decoded data before giving up and assuming the
241  // last write was partial. the fact is, all current
242  // qca sasl providers respond within this time
243  // frame, so this fix should work fine for now. in
244  // qca 2.1, we should revise the api to handle this
245  // situation better.
246  //
247  // further note: i guess this only affects application
248  // protocols that have no close message of their
249  // own, and rely on the tcp-level close. examples
250  // are http, and of course this qcatest protocol.
251  if (waitCycles == 0) {
252  waitCycles = 3;
253  QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
254  }
255 
256  sasl->writeIncoming(a);
257  } else // mech list or sasl negotiation mode
258  {
259  if (sock->canReadLine()) {
260  QString line = QString::fromLatin1(sock->readLine());
261  line.truncate(line.length() - 1); // chop the newline
262  handleLine(line);
263  }
264  }
265  }
266 
267  void sasl_clientFirstStep(bool clientInit, const QByteArray &clientInitData)
268  {
269  printf("Choosing mech: %s\n", qPrintable(sasl->mechanism()));
270  QString line = sasl->mechanism();
271  if (clientInit) {
272  line += QLatin1Char(' ');
273  line += arrayToString(clientInitData);
274  }
275  sendLine(line);
276  }
277 
278  void sasl_nextStep(const QByteArray &stepData)
279  {
280  QString line = QStringLiteral("C");
281  if (!stepData.isEmpty()) {
282  line += QLatin1Char(',');
283  line += arrayToString(stepData);
284  }
285  sendLine(line);
286  }
287 
288  void sasl_needParams(const QCA::SASL::Params &params)
289  {
290  if (params.needUsername()) {
291  user = prompt(QStringLiteral("Username:"));
292  sasl->setUsername(user);
293  }
294 
295  if (params.canSendAuthzid() && !no_authzid) {
296  authzid = prompt(QStringLiteral("Authorize As (enter to skip):"));
297  if (!authzid.isEmpty())
298  sasl->setAuthzid(authzid);
299  }
300 
301  if (params.needPassword()) {
302  QCA::ConsolePrompt prompt;
303  prompt.getHidden(QStringLiteral("* Password"));
304  prompt.waitForFinished();
305  QCA::SecureArray pass = prompt.result();
306  sasl->setPassword(pass);
307  }
308 
309  if (params.canSendRealm() && !no_realm) {
310  QStringList realms = sasl->realmList();
311  printf("Available realms:\n");
312  if (realms.isEmpty())
313  printf(" (none specified)\n");
314  foreach (const QString &s, realms)
315  printf(" %s\n", qPrintable(s));
316  realm = prompt(QStringLiteral("Realm (enter to skip):"));
317  if (!realm.isEmpty())
318  sasl->setRealm(realm);
319  }
320 
321  sasl->continueAfterParams();
322  }
323 
324  void sasl_authenticated()
325  {
326  printf("SASL success!\n");
327  printf("SSF: %d\n", sasl->ssf());
328  }
329 
330  void sasl_readyRead()
331  {
332  QByteArray a = sasl->read();
333  inbuf += a;
334  processInbuf();
335  }
336 
337  void sasl_readyReadOutgoing()
338  {
339  QByteArray a = sasl->readOutgoing();
340  sock->write(a);
341  }
342 
343  void sasl_error()
344  {
345  int e = sasl->errorCode();
346  if (e == QCA::SASL::ErrorInit)
347  printf("Error: sasl: initialization failed.\n");
348  else if (e == QCA::SASL::ErrorHandshake)
349  printf("Error: sasl: %s.\n", qPrintable(saslAuthConditionToString(sasl->authCondition())));
350  else if (e == QCA::SASL::ErrorCrypt)
351  printf("Error: sasl: broken security layer.\n");
352  else
353  printf("Error: sasl: unknown error.\n");
354 
355  emit quit();
356  }
357 
358  void waitWriteIncoming()
359  {
360  --waitCycles;
361  if (waitCycles > 0) {
362  QMetaObject::invokeMethod(this, "waitWriteIncoming", Qt::QueuedConnection);
363  return;
364  }
365 
366  tryFinished();
367  }
368 
369 private:
370  void tryFinished()
371  {
372  if (sock_done && waitCycles == 0) {
373  printf("Finished, server closed connection.\n");
374 
375  // if we give up on waiting for a response to
376  // writeIncoming, then it might come late. in
377  // theory this shouldn't happen if we wait enough
378  // cycles, but if one were to arrive then it could
379  // occur between the request to quit the app and
380  // the actual quit of the app. to assist with
381  // debugging, then, we'll explicitly stop listening
382  // for signals here. otherwise the response may
383  // still be received and displayed, giving a false
384  // sense of correctness.
385  sasl->disconnect(this);
386 
387  emit quit();
388  }
389  }
390 
391  QString arrayToString(const QByteArray &ba)
392  {
393  return QCA::Base64().arrayToString(ba);
394  }
395 
396  QByteArray stringToArray(const QString &s)
397  {
398  return QCA::Base64().stringToArray(s).toByteArray();
399  }
400 
401  void sendLine(const QString &line)
402  {
403  printf("Writing: {%s}\n", qPrintable(line));
404  QString s = line + QLatin1Char('\n');
405  QByteArray a = s.toUtf8();
406  if (mode == 2) // app mode
407  sasl->write(a); // write to sasl
408  else // mech list or sasl negotiation
409  sock->write(a); // write to socket
410  }
411 
412  void processInbuf()
413  {
414  // collect completed lines from inbuf
416  int at;
417  while ((at = inbuf.indexOf('\n')) != -1) {
418  list += QString::fromUtf8(inbuf.mid(0, at));
419  inbuf = inbuf.mid(at + 1);
420  }
421 
422  // process the lines
423  foreach (const QString &line, list)
424  handleLine(line);
425  }
426 
427  void handleLine(const QString &line)
428  {
429  printf("Reading: [%s]\n", qPrintable(line));
430  if (mode == 0) {
431  // first line is the method list
432  const QStringList mechlist = line.split(QLatin1Char(' '));
433  mode = 1; // switch to sasl negotiation mode
434  sasl->startClient(proto, host, mechlist);
435  } else if (mode == 1) {
436  QString type, rest;
437  int n = line.indexOf(QLatin1Char(','));
438  if (n != -1) {
439  type = line.mid(0, n);
440  rest = line.mid(n + 1);
441  } else
442  type = line;
443 
444  if (type == QLatin1String("C")) {
445  sasl->putStep(stringToArray(rest));
446  } else if (type == QLatin1String("E")) {
447  if (!rest.isEmpty())
448  printf("Error: server says: %s.\n", qPrintable(rest));
449  else
450  printf("Error: server error, unspecified.\n");
451  emit quit();
452  return;
453  } else if (type == QLatin1String("A")) {
454  printf("Authentication success.\n");
455  mode = 2; // switch to app mode
456 
457  // at this point, the server may send us text
458  // lines for us to display and then close.
459 
460  sock_readyRead(); // any extra data?
461  return;
462  } else {
463  printf("Error: Bad format from peer, closing.\n");
464  emit quit();
465  return;
466  }
467  }
468  }
469 };
470 
471 void usage()
472 {
473  printf("usage: saslclient (options) host(:port) (user) (pass)\n");
474  printf("options: --proto=x, --authzid=x, --realm=x\n");
475 }
476 
477 int main(int argc, char **argv)
478 {
480  QCoreApplication qapp(argc, argv);
481 
482  QStringList args = qapp.arguments();
483  args.removeFirst();
484 
485  // options
486  QString proto = QStringLiteral("qcatest"); // default protocol
487  QString authzid, realm;
488  bool no_authzid = false;
489  bool no_realm = false;
490  for (int n = 0; n < args.count(); ++n) {
491  if (!args[n].startsWith(QLatin1String("--")))
492  continue;
493 
494  QString opt = args[n].mid(2);
495  QString var, val;
496  int at = opt.indexOf(QLatin1Char('='));
497  if (at != -1) {
498  var = opt.mid(0, at);
499  val = opt.mid(at + 1);
500  } else
501  var = opt;
502 
503  if (var == QLatin1String("proto")) {
504  proto = val;
505  } else if (var == QLatin1String("authzid")) {
506  // specifying empty authzid means force unspecified
507  if (val.isEmpty())
508  no_authzid = true;
509  else
510  authzid = val;
511  } else if (var == QLatin1String("realm")) {
512  // specifying empty realm means force unspecified
513  if (val.isEmpty())
514  no_realm = true;
515  else
516  realm = val;
517  }
518 
519  args.removeAt(n);
520  --n; // adjust position
521  }
522 
523  if (args.count() < 1) {
524  usage();
525  return 0;
526  }
527 
528  QString host, user, pass;
529  int port = 8001; // default port
530 
531  QString hostinput = args[0];
532  if (args.count() >= 2)
533  user = args[1];
534  if (args.count() >= 3)
535  pass = args[2];
536 
537  int at = hostinput.indexOf(QLatin1Char(':'));
538  if (at != -1) {
539  host = hostinput.mid(0, at);
540 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
541  port = QStringView(hostinput).mid(at + 1).toInt();
542 #else
543  port = hostinput.midRef(at + 1).toInt();
544 #endif
545  } else
546  host = hostinput;
547 
548  if (!QCA::isSupported("sasl")) {
549  printf("Error: SASL support not found.\n");
550  return 1;
551  }
552 
553  ClientTest client(host, port, proto, authzid, realm, user, pass, no_authzid, no_realm);
554  QObject::connect(&client, &ClientTest::quit, &qapp, &QCoreApplication::quit);
555  QTimer::singleShot(0, &client, &ClientTest::start);
556  qapp.exec();
557 
558  return 0;
559 }
560 
561 #include "saslclient.moc"
void getHidden(const QString &promptStr)
Allow the user to enter data without it being echo&#39;d to the terminal.
void putStep(const QByteArray &stepData)
Process an authentication step.
void clientStarted(bool clientInit, const QByteArray &clientInitData)
This signal is emitted when the client has been successfully started.
int toInt(bool *ok, int base) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QStringList realmList() const
Return the realm list, if available (client)
Console prompt handler.
Definition: qca_support.h:863
AuthCondition authCondition() const
Return the reason for authentication failure.
void truncate(int position)
Parameter flags for the SASL authentication.
void startClient(const QString &service, const QString &host, const QStringList &mechlist, ClientSendMode mode=AllowClientSendFirst)
Initialise the client side of the connection.
QString arrayToString(const MemoryRegion &a)
Process an array in the "forward" direction, returning a QString.
int ssf() const
Return the security strength factor of the connection.
virtual bool canReadLine() const const override
void write(const QByteArray &a) override
This method writes unencrypted (plain) data to the SecureLayer implementation.
void readyRead()
This signal is emitted when SecureLayer has decrypted (application side) data ready to be read...
void removeFirst()
QByteArray read() override
This method reads decrypted (plain) data from the SecureLayer implementation.
bool canSendRealm() const
A Realm can be sent if desired.
void removeAt(int i)
bool isEmpty() const const
QAbstractSocket::SocketError error() const const
MemoryRegion stringToArray(const QString &s)
Process an string in the "reverse" direction, returning a byte array.
Simple Authentication and Security Layer protocol implementation.
Q_SIGNALSQ_SIGNALS
Base64 encoding / decoding
void setUsername(const QString &user)
Specify the username to use in authentication.
Server failed mutual authentication (client side only)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QByteArray readOutgoing(int *plainBytes=nullptr) override
This method provides encoded (typically encrypted) data.
void continueAfterParams()
Continue negotiation after parameters have been set (client)
Error errorCode() const
Return the error code.
virtual void connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode, QAbstractSocket::NetworkLayerProtocol protocol)
problem at anytime after
int indexOf(char ch, int from) const const
int count(const T &value) const const
QString mechanism() const
Return the mechanism selected (client)
QString fromUtf8(const char *str, int size)
Q_OBJECTQ_OBJECT
QCA_EXPORT bool isSupported(const char *features, const QString &provider=QString())
Test if a capability (algorithm) is available.
Type type(const QSqlDatabase &db)
void setConstraints(AuthFlags f, SecurityLevel s=SL_None)
Specify connection constraints.
No compatible/appropriate authentication mechanism.
Bad protocol or cancelled.
void authenticated()
This signal is emitted when authentication is complete.
bool isEmpty() const const
void writeIncoming(const QByteArray &a) override
This method accepts encoded (typically encrypted) data for processing.
bool isEmpty() const const
void readyReadOutgoing()
This signal is emitted when SecureLayer has encrypted (network side) data ready to be read...
QByteArray readAll()
bool canSendAuthzid() const
An Authorization ID can be sent if desired.
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void nextStep(const QByteArray &stepData)
This signal is emitted when there is data required to be sent over the network to complete the next s...
problem starting up SASL
QByteArray mid(int pos, int len) const const
AuthFlags
Authentication requirement flag values.
QStringRef midRef(int position, int n) const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QCA_EXPORT void init()
Initialise QCA.
Secure array of bytes.
Definition: qca_tools.h:316
AuthCondition
Possible authentication error states.
QString mid(int position, int n) const const
problem during the authentication process
QByteArray toByteArray() const
Convert this memory region to a byte array.
void errorOccurred(QAbstractSocket::SocketError socketError)
void setRealm(const QString &realm)
Specify the realm to use in authentication.
bool needUsername() const
User is needed.
void needParams(const QCA::SASL::Params &params)
This signal is emitted when the client needs additional parameters.
void error()
This signal is emitted when an error is detected.
void setAuthzid(const QString &auth)
Specify the authorization identity to use in authentication.
QList< T > mid(int pos, int length) const const
int length() const const
Convenience method for initialising and cleaning up QCA.
Definition: qca_core.h:659
qint64 write(const char *data, qint64 maxSize)
QString fromLatin1(const char *str, int size)
bool needPassword() const
Password is needed.
void readyRead()
Q_SLOTSQ_SLOTS
int size() const const
void waitForFinished()
Block waiting for user input.
QueuedConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
SecureArray result() const
Obtain the result of the user input.
QStringView mid(qsizetype start) const const
const QList< QKeySequence > & quit()
qint64 readLine(char *data, qint64 maxSize)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
void setPassword(const SecureArray &pass)
Specify the password to use in authentication.
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Sep 25 2021 23:05:35 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.