QCA

saslserver.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 socketErrorToString(QAbstractSocket::SocketError x)
37 {
38  QString s;
39  switch (x) {
41  s = QStringLiteral("connection refused or timed out");
42  break;
44  s = QStringLiteral("remote host closed the connection");
45  break;
47  s = QStringLiteral("host not found");
48  break;
50  s = QStringLiteral("access error");
51  break;
53  s = QStringLiteral("too many sockets");
54  break;
56  s = QStringLiteral("operation timed out");
57  break;
59  s = QStringLiteral("datagram was larger than system limit");
60  break;
62  s = QStringLiteral("network error");
63  break;
65  s = QStringLiteral("address is already in use");
66  break;
68  s = QStringLiteral("address does not belong to the host");
69  break;
71  s = QStringLiteral("operation is not supported by the local operating system");
72  break;
73  default:
74  s = QStringLiteral("unknown socket error");
75  break;
76  }
77  return s;
78 }
79 
80 static QString saslAuthConditionToString(QCA::SASL::AuthCondition x)
81 {
82  QString s;
83  switch (x) {
85  s = QStringLiteral("no appropriate mechanism could be negotiated");
86  break;
88  s = QStringLiteral("bad SASL protocol");
89  break;
90  case QCA::SASL::BadAuth:
91  s = QStringLiteral("authentication failed");
92  break;
94  s = QStringLiteral("authorization failed");
95  break;
96  case QCA::SASL::TooWeak:
97  s = QStringLiteral("mechanism too weak for this user");
98  break;
100  s = QStringLiteral("encryption is needed to use this mechanism");
101  break;
102  case QCA::SASL::Expired:
103  s = QStringLiteral("passphrase expired");
104  break;
105  case QCA::SASL::Disabled:
106  s = QStringLiteral("account is disabled");
107  break;
108  case QCA::SASL::NoUser:
109  s = QStringLiteral("user not found");
110  break;
112  s = QStringLiteral("needed remote service is unavailable");
113  break;
114  // AuthFail or unknown (including those defined for client only)
115  default:
116  s = QStringLiteral("generic authentication failure");
117  break;
118  };
119  return s;
120 }
121 
122 // --- ServerTest declaration
123 
124 class ServerTest : public QObject
125 {
126  Q_OBJECT
127 
128 private:
129  QString host, proto, realm, str;
130  int port;
131  QTcpServer *tcpServer;
132  QList<int> ids;
133 
134 public:
135  ServerTest(const QString &_host, int _port, const QString &_proto, const QString &_realm, const QString &_str);
136 
137  int reserveId();
138  void releaseId(int id);
139 
140 public Q_SLOTS:
141  void start();
142 
143 Q_SIGNALS:
144  void quit();
145 
146 private Q_SLOTS:
147  void server_newConnection();
148 };
149 
150 // --- ServerTestHandler
151 
152 class ServerTestHandler : public QObject
153 {
154  Q_OBJECT
155 
156 private:
157  ServerTest *serverTest;
158  QTcpSocket *sock;
159  QCA::SASL * sasl;
160  int id;
161  QString host, proto, realm, str;
162  int mode; // 0 = receive mechanism list, 1 = sasl negotiation, 2 = app
163  int toWrite;
164 
165 public:
166  ServerTestHandler(ServerTest * _serverTest,
167  QTcpSocket * _sock,
168  const QString &_host,
169  const QString &_proto,
170  const QString &_realm,
171  const QString &_str)
172  : serverTest(_serverTest)
173  , sock(_sock)
174  , host(_host)
175  , proto(_proto)
176  , realm(_realm)
177  , str(_str)
178  {
179  id = serverTest->reserveId();
180 
181  sock->setParent(this);
182  connect(sock, &QTcpSocket::disconnected, this, &ServerTestHandler::sock_disconnected);
183  connect(sock, &QTcpSocket::readyRead, this, &ServerTestHandler::sock_readyRead);
184 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
185  connect(sock, &QTcpSocket::errorOccurred, this, &ServerTestHandler::sock_error);
186 #else
187  connect(sock,
188  QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),
189  this,
190  &ServerTestHandler::sock_error);
191 #endif
192  connect(sock, &QTcpSocket::bytesWritten, this, &ServerTestHandler::sock_bytesWritten);
193 
194  sasl = new QCA::SASL(this);
195  connect(sasl, &QCA::SASL::authCheck, this, &ServerTestHandler::sasl_authCheck);
196  connect(sasl, &QCA::SASL::nextStep, this, &ServerTestHandler::sasl_nextStep);
197  connect(sasl, &QCA::SASL::authenticated, this, &ServerTestHandler::sasl_authenticated);
198  connect(sasl, &QCA::SASL::readyRead, this, &ServerTestHandler::sasl_readyRead);
199  connect(sasl, &QCA::SASL::readyReadOutgoing, this, &ServerTestHandler::sasl_readyReadOutgoing);
200  connect(sasl, &QCA::SASL::error, this, &ServerTestHandler::sasl_error);
201  connect(sasl, &QCA::SASL::serverStarted, this, &ServerTestHandler::sasl_serverStarted);
202 
203  mode = 0; // mech list mode
204  toWrite = 0;
205 
206  int flags = 0;
207  flags |= QCA::SASL::AllowPlain;
208  flags |= QCA::SASL::AllowAnonymous;
209  sasl->setConstraints((QCA::SASL::AuthFlags)flags, 0, 256);
210 
211  printf("%d: Connection received! Starting SASL handshake...\n", id);
212  sasl->startServer(proto, host, realm);
213  }
214 
215  ~ServerTestHandler() override
216  {
217  serverTest->releaseId(id);
218  }
219 
220 private Q_SLOTS:
221  void sasl_serverStarted()
222  {
223  sendLine(sasl->mechanismList().join(QStringLiteral(" ")));
224  }
225 
226  void sock_disconnected()
227  {
228  printf("%d: Connection closed.\n", id);
229  discard();
230  }
231 
232  void sock_error(QAbstractSocket::SocketError x)
233  {
235  printf("%d: Error: client closed connection unexpectedly.\n", id);
236  discard();
237  return;
238  }
239 
240  printf("%d: Error: socket: %s\n", id, qPrintable(socketErrorToString(x)));
241  discard();
242  }
243 
244  void sock_readyRead()
245  {
246  if (sock->canReadLine()) {
247  QString line = QString::fromLatin1(sock->readLine());
248  line.truncate(line.length() - 1); // chop the newline
249  handleLine(line);
250  }
251  }
252 
253  void sock_bytesWritten(qint64 x)
254  {
255  if (mode == 2) // app mode
256  {
257  toWrite -= sasl->convertBytesWritten(x);
258  if (toWrite == 0) {
259  printf("%d: Sent, closing.\n", id);
260  sock->close();
261  }
262  }
263  }
264 
265  void sasl_nextStep(const QByteArray &stepData)
266  {
267  QString line = QStringLiteral("C");
268  if (!stepData.isEmpty()) {
269  line += QLatin1Char(',');
270  line += arrayToString(stepData);
271  }
272  sendLine(line);
273  }
274 
275  void sasl_authCheck(const QString &user, const QString &authzid)
276  {
277  printf("%d: AuthCheck: User: [%s], Authzid: [%s]\n", id, qPrintable(user), qPrintable(authzid));
278 
279  // user - who has logged in, confirmed by sasl
280  // authzid - the identity the user wishes to act as, which
281  // could be another user or just any arbitrary string (in
282  // XMPP, this field holds a Jabber ID, for example). this
283  // field is not necessarily confirmed by sasl, and the
284  // decision about whether the user can act as the authzid
285  // must be made by the app.
286 
287  // for this simple example program, we allow anyone to use
288  // the service, and simply continue onward with the
289  // negotiation.
290  sasl->continueAfterAuthCheck();
291  }
292 
293  void sasl_authenticated()
294  {
295  sendLine(QStringLiteral("A"));
296  printf("%d: Authentication success.\n", id);
297  mode = 2; // switch to app mode
298  printf("%d: SSF: %d\n", id, sasl->ssf());
299  sendLine(str);
300  }
301 
302  void sasl_readyRead()
303  {
304  QByteArray a = sasl->read();
305  printf("%d: Warning, client sent %d bytes unexpectedly.\n", id, int(a.size()));
306  }
307 
308  void sasl_readyReadOutgoing()
309  {
310  sock->write(sasl->readOutgoing());
311  }
312 
313  void sasl_error()
314  {
315  int e = sasl->errorCode();
316  if (e == QCA::SASL::ErrorInit) {
317  printf("%d: Error: sasl: initialization failed.\n", id);
318  } else if (e == QCA::SASL::ErrorHandshake) {
319  QString errstr = saslAuthConditionToString(sasl->authCondition());
320  sendLine(QStringLiteral("E,") + errstr);
321  printf("%d: Error: sasl: %s.\n", id, qPrintable(errstr));
322  } else if (e == QCA::SASL::ErrorCrypt) {
323  printf("%d: Error: sasl: broken security layer.\n", id);
324  } else {
325  printf("%d: Error: sasl: unknown error.\n", id);
326  }
327 
328  sock->close();
329  }
330 
331 private:
332  void discard()
333  {
334  deleteLater();
335  }
336 
337  void handleLine(const QString &line)
338  {
339  printf("%d: Reading: [%s]\n", id, qPrintable(line));
340  if (mode == 0) {
341  int n = line.indexOf(QLatin1Char(' '));
342  if (n != -1) {
343  QString mech = line.mid(0, n);
344  QString rest = QString::fromLatin1(line.mid(n + 1).toUtf8());
345  sasl->putServerFirstStep(mech, stringToArray(rest));
346  } else
347  sasl->putServerFirstStep(line);
348  ++mode;
349  } else if (mode == 1) {
350  QString type, rest;
351  int n = line.indexOf(QLatin1Char(','));
352  if (n != -1) {
353  type = line.mid(0, n);
354  rest = line.mid(n + 1);
355  } else {
356  type = line;
357  rest = QLatin1String("");
358  }
359 
360  if (type == QLatin1String("C")) {
361  sasl->putStep(stringToArray(rest));
362  } else {
363  printf("%d: Bad format from peer, closing.\n", id);
364  sock->close();
365  return;
366  }
367  }
368  }
369 
370  QString arrayToString(const QByteArray &ba)
371  {
372  QCA::Base64 encoder;
373  return encoder.arrayToString(ba);
374  }
375 
376  QByteArray stringToArray(const QString &s)
377  {
378  QCA::Base64 decoder(QCA::Decode);
379  return decoder.stringToArray(s).toByteArray();
380  }
381 
382  void sendLine(const QString &line)
383  {
384  printf("%d: Writing: {%s}\n", id, qPrintable(line));
385  QString s = line + QLatin1Char('\n');
386  QByteArray a = s.toUtf8();
387  if (mode == 2) // app mode
388  {
389  toWrite += a.size();
390  sasl->write(a); // write to sasl
391  } else // mech list or sasl negotiation
392  sock->write(a); // write to socket
393  }
394 };
395 
396 // --- ServerTest implementation
397 
398 ServerTest::ServerTest(const QString &_host,
399  int _port,
400  const QString &_proto,
401  const QString &_realm,
402  const QString &_str)
403  : host(_host)
404  , proto(_proto)
405  , realm(_realm)
406  , str(_str)
407  , port(_port)
408 {
409  tcpServer = new QTcpServer(this);
410  connect(tcpServer, &QTcpServer::newConnection, this, &ServerTest::server_newConnection);
411 }
412 
413 int ServerTest::reserveId()
414 {
415  int n = 0;
416  while (ids.contains(n))
417  ++n;
418  ids += n;
419  return n;
420 }
421 
422 void ServerTest::releaseId(int id)
423 {
424  ids.removeAll(id);
425 }
426 
427 void ServerTest::start()
428 {
429  if (!tcpServer->listen(QHostAddress::Any, port)) {
430  printf("Error: unable to bind to port %d.\n", port);
431  emit quit();
432  return;
433  }
434 
435  printf("Serving on %s:%d, for protocol %s ...\n", qPrintable(host), port, qPrintable(proto));
436 }
437 
438 void ServerTest::server_newConnection()
439 {
440  QTcpSocket *sock = tcpServer->nextPendingConnection();
441  new ServerTestHandler(this, sock, host, proto, realm, str);
442 }
443 
444 // ---
445 
446 void usage()
447 {
448  printf("usage: saslserver host (message)\n");
449  printf("options: --proto=x, --realm=x\n");
450 }
451 
452 int main(int argc, char **argv)
453 {
455  QCoreApplication qapp(argc, argv);
456 
457  QCA::setAppName(QStringLiteral("saslserver"));
458 
459  QStringList args = qapp.arguments();
460  args.removeFirst();
461 
462  // options
463  QString proto = QStringLiteral("qcatest"); // default protocol
464  QString realm;
465  for (int n = 0; n < args.count(); ++n) {
466  if (!args[n].startsWith(QLatin1String("--")))
467  continue;
468 
469  QString opt = args[n].mid(2);
470  QString var, val;
471  int at = opt.indexOf(QLatin1Char('='));
472  if (at != -1) {
473  var = opt.mid(0, at);
474  val = opt.mid(at + 1);
475  } else
476  var = opt;
477 
478  if (var == QLatin1String("proto"))
479  proto = val;
480  else if (var == QLatin1String("realm"))
481  realm = val;
482 
483  args.removeAt(n);
484  --n; // adjust position
485  }
486 
487  if (args.count() < 1) {
488  usage();
489  return 0;
490  }
491 
492  QString host;
493  int port = 8001; // default port
494 
495  QString hostinput = args[0];
496  QString str = QStringLiteral("Hello, World");
497  if (args.count() >= 2)
498  str = args[1];
499 
500  int at = hostinput.indexOf(QLatin1Char(':'));
501  if (at != -1) {
502  host = hostinput.mid(0, at);
503 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 2)
504  port = QStringView(hostinput).mid(at + 1).toInt();
505 #else
506  port = hostinput.midRef(at + 1).toInt();
507 #endif
508  } else
509  host = hostinput;
510 
511  if (!QCA::isSupported("sasl")) {
512  printf("Error: SASL support not found.\n");
513  return 1;
514  }
515 
516  ServerTest server(host, port, proto, realm, str);
517  QObject::connect(&server, &ServerTest::quit, &qapp, &QCoreApplication::quit);
518  QTimer::singleShot(0, &server, &ServerTest::start);
519  qapp.exec();
520 
521  return 0;
522 }
523 
524 #include "saslserver.moc"
void putStep(const QByteArray &stepData)
Process an authentication step.
int toInt(bool *ok, int base) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
AuthCondition authCondition() const
Return the reason for authentication failure.
void truncate(int position)
Remote service needed for auth is gone (server side only)
QStringList mechanismList() const
Return the mechanism list (server)
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.
void newConnection()
void removeAt(int i)
bool isEmpty() const const
QAbstractSocket::SocketError error() const const
Simple Authentication and Security Layer protocol implementation.
void authCheck(const QString &user, const QString &authzid)
This signal is emitted when the server needs to perform the authentication check. ...
void startServer(const QString &service, const QString &host, const QString &realm, ServerSendMode mode=DisableServerSendLast)
Initialise the server side of the connection.
QString join(const QString &separator) const const
Q_SIGNALSQ_SIGNALS
Base64 encoding / decoding
void putServerFirstStep(const QString &mech)
Process the first step in server mode (server)
QCA_EXPORT void setAppName(const QString &name)
Set the application name that will be used by SASL server mode.
QByteArray readOutgoing(int *plainBytes=nullptr) override
This method provides encoded (typically encrypted) data.
Error errorCode() const
Return the error code.
problem at anytime after
Passphrase expired, has to be reset (server side only)
int count(const T &value) const const
User not found (server side only)
virtual void close() override
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.
Authentication failure (server side only)
void readyReadOutgoing()
This signal is emitted when SecureLayer has encrypted (network side) data ready to be read...
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...
void bytesWritten(qint64 bytes)
problem starting up SASL
Account is disabled (server side only)
void continueAfterAuthCheck()
Continue negotiation after auth ids have been checked (server)
KGuiItem discard()
void setParent(QObject *parent)
AuthFlags
Authentication requirement flag values.
QStringRef midRef(int position, int n) const const
QCA_EXPORT void init()
Initialise QCA.
AuthCondition
Possible authentication error states.
QString mid(int position, int n) const const
problem during the authentication process
void errorOccurred(QAbstractSocket::SocketError socketError)
void error()
This signal is emitted when an error is detected.
Mechanism too weak for this user (server side only)
QList< T > mid(int pos, int length) const const
int length() const const
void serverStarted()
This signal is emitted after the server has been successfully started.
Operate in the "reverse" direction; for example, decrypting.
Definition: qca_core.h:143
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)
Authorization failure (server side only)
Encryption is needed in order to use mechanism (server side only)
void readyRead()
int convertBytesWritten(qint64 encryptedBytes) override
Convert encrypted bytes written to plain text bytes written.
Q_SLOTSQ_SLOTS
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QStringView mid(qsizetype start) const const
const QList< QKeySequence > & quit()
qint64 readLine(char *data, qint64 maxSize)
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.