summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier S. Pedro <maemo@javispedro.com>2012-09-30 19:41:17 +0200
committerJavier S. Pedro <maemo@javispedro.com>2012-09-30 19:41:17 +0200
commit15253d1995ea49b114ef5c627e15b661dbd602b2 (patch)
tree1111441406a0b043f45ab19fe42e567be36d4d09
parentc8f89279026af6bb03ef3d66e44e859fb7208925 (diff)
downloaddistfold-15253d1995ea49b114ef5c627e15b661dbd602b2.tar.gz
distfold-15253d1995ea49b114ef5c627e15b661dbd602b2.zip
add some trivial authentication
-rw-r--r--distfoldd/agent.cc65
-rw-r--r--distfoldd/agent.h34
-rw-r--r--distfoldd/clientagent.cc23
-rw-r--r--distfoldd/clientagent.h4
-rw-r--r--distfoldd/compressor.cc81
-rw-r--r--distfoldd/distfolder.cc14
-rw-r--r--distfoldd/distfolder.h4
-rw-r--r--distfoldd/localkey.cc2
-rw-r--r--distfoldd/main.cc10
-rw-r--r--distfoldd/server.cc1
-rw-r--r--distfoldd/serveragent.cc54
-rw-r--r--distfoldd/serveragent.h10
12 files changed, 201 insertions, 101 deletions
diff --git a/distfoldd/agent.cc b/distfoldd/agent.cc
index a718230..8baa85b 100644
--- a/distfoldd/agent.cc
+++ b/distfoldd/agent.cc
@@ -1,3 +1,5 @@
+#include <QtCrypto>
+
#include "agent.h"
#ifdef Q_OS_UNIX
@@ -5,8 +7,8 @@
#include <utime.h>
#endif
-Agent::Agent(QSslSocket *socket, const QDir& dir, SyncFlags flags, QObject *parent) :
- QObject(parent), _dir(dir), _subPath("/"), _flags(flags), _socket(socket)
+Agent::Agent(QSslSocket *socket, const QDir& dir, const QString& passwd, SyncFlags flags, QObject *parent) :
+ QObject(parent), _dir(dir), _subPath("/"), _passwd(passwd), _flags(flags), _socket(socket)
{
connect(_socket, SIGNAL(readyRead()), SLOT(handleDataAvailable()));
connect(_socket, SIGNAL(sslErrors(QList<QSslError>)), SLOT(handleSslErrors(QList<QSslError>)));
@@ -73,7 +75,7 @@ bool Agent::equalDateTime(const QDateTime& dt1, const QDateTime& dt2)
void Agent::setLocalFileDateTime(const QString &path, const QDateTime &dt)
{
-#ifdef Q_OS_UNIX
+#if defined(Q_OS_UNIX)
const char *filename = path.toLocal8Bit().constData();
struct utimbuf times;
times.actime = dt.toTime_t();
@@ -82,6 +84,8 @@ void Agent::setLocalFileDateTime(const QString &path, const QDateTime &dt)
if (rc != 0) {
qWarning() << "Could not set local mtime of" << path;
}
+#elif defined(Q_OS_WIN32)
+ // TODO SetFileAttributes, SetFileTime
#endif
}
@@ -135,6 +139,61 @@ bool Agent::morePathDepthThan(const QString& s1, const QString& s2)
return d1 > d2;
}
+QByteArray Agent::encodeHelloMessage(const QByteArray& client_challenge)
+{
+ QByteArray ba(sizeof(quint32), '\0');
+ quint32 *p = reinterpret_cast<quint32*>(ba.data());
+ *p = PROTO_CURRENT;
+ ba.append(client_challenge);
+ return ba;
+}
+
+Agent::ProtoVersion Agent::decodeHelloMessage(const QByteArray& ba, QByteArray *client_challenge)
+{
+ if (ba.size() < static_cast<int>(sizeof(quint32))) return PROTO_BAD;
+ const quint32 *p = reinterpret_cast<const quint32*>(ba.constData());
+
+ switch (*p) {
+ case PROTO_1:
+ *client_challenge = ba.mid(sizeof(quint32));
+ return PROTO_1;
+ default:
+ return PROTO_BAD;
+ }
+}
+
+QByteArray Agent::hmacSha1(const QByteArray& key, const QByteArray& message)
+{
+ QCA::MessageAuthenticationCode mac("hmac(sha1)", QCA::SymmetricKey(key));
+ mac.update(message);
+ return mac.final().toByteArray();
+}
+
+QByteArray Agent::generateChallenge()
+{
+ const int challenge_size = 10;
+ return QCA::Random::randomArray(challenge_size).toByteArray();
+}
+
+QByteArray Agent::generateChallengeResponse(const QByteArray& server_challenge, const QByteArray& client_challenge)
+{
+ return hmacSha1(_passwd.toUtf8(), server_challenge + client_challenge);
+}
+
+QByteArray Agent::encodeAuthReply(AuthResult result)
+{
+ QByteArray ba(sizeof(quint8), static_cast<char>(result));
+ return ba;
+}
+
+Agent::AuthResult Agent::decodeAuthReply(const QByteArray& ba)
+{
+ if (ba.size() != 1) return AUTH_FAILED;
+ const quint8 *p = reinterpret_cast<const quint8*>(ba.constData());
+
+ return static_cast<AuthResult>(*p);
+}
+
QFileInfoList Agent::scanFiles(const QDir& dir)
{
QFileInfoList all;
diff --git a/distfoldd/agent.h b/distfoldd/agent.h
index fa202f5..0d25077 100644
--- a/distfoldd/agent.h
+++ b/distfoldd/agent.h
@@ -3,6 +3,7 @@
#include <QtCore/QDateTime>
#include <QtCore/QDir>
+#include <QtCore/QCryptographicHash>
#include <QtNetwork/QSslSocket>
#include "compressor.h"
@@ -22,7 +23,7 @@ public:
Q_DECLARE_FLAGS(SyncFlags, SyncFlag)
public:
- explicit Agent(QSslSocket *socket, const QDir& dir, SyncFlags flags, QObject *parent = 0);
+ explicit Agent(QSslSocket *socket, const QDir& dir, const QString& passwd, SyncFlags flags, QObject *parent = 0);
static const uint servicePort = 17451;
@@ -33,14 +34,16 @@ protected:
#pragma pack(push)
#pragma pack(1)
enum MessageType {
- MSG_HELLO = 0,
- MSG_HELLO_REPLY,
+ MSG_HELLO = 0, //args: uint32 (proto_version) + string (client challenge)
+ MSG_HELLO_REPLY, //args: string (server challenge)
+ MSG_AUTH, //args: string (challenge response)
+ MSG_AUTH_REPLY, // args: uint8 (AuthResult)
MSG_SET_SUBROOT, //args: string
MSG_FILE_LIST, //args: FileListItem[]
MSG_FILE_ACTIONS_REPLY, //args: ActionItem[]
MSG_PULL_FILE, //args: string
- MSG_PULL_FILE_REPLY, //args: data
- MSG_PUSH_FILE, //args: FileNameItem + data
+ MSG_PULL_FILE_REPLY, //args: file data
+ MSG_PUSH_FILE, //args: FileNameItem + file data
MSG_PUSH_FILE_METADATA, //args: FileListItem[]
MSG_DELETE_FILE, //args: string
MSG_BYE
@@ -49,6 +52,16 @@ protected:
quint32 msg;
quint32 len;
};
+ enum ProtoVersion {
+ PROTO_BAD = -1,
+ PROTO_0 = 0,
+ PROTO_1 = 1,
+ PROTO_CURRENT = PROTO_1
+ };
+ enum AuthResult {
+ AUTH_OK = 0,
+ AUTH_FAILED
+ };
enum FileType {
FILE_TYPE_NONE = 0,
FILE_TYPE_FILE,
@@ -169,6 +182,16 @@ protected:
static bool lessPathDepthThan(const QString& s1, const QString& s2);
static bool morePathDepthThan(const QString& s1, const QString& s2);
+ QByteArray encodeHelloMessage(const QByteArray& client_challenge);
+ ProtoVersion decodeHelloMessage(const QByteArray& ba, QByteArray* client_challenge);
+
+ static QByteArray hmacSha1(const QByteArray& key, const QByteArray& message);
+ QByteArray generateChallenge();
+ QByteArray generateChallengeResponse(const QByteArray& server_challenge, const QByteArray& client_challenge);
+
+ QByteArray encodeAuthReply(AuthResult result);
+ AuthResult decodeAuthReply(const QByteArray& ba);
+
static QFileInfoList scanFiles(const QDir& dir);
QByteArray encodeFileInfoList(const QFileInfoList& list);
RemoteFileInfoList decodeFileInfoList(const QByteArray& ba);
@@ -187,6 +210,7 @@ protected:
protected:
QDir _dir;
QString _subPath;
+ QString _passwd;
SyncFlags _flags;
QSslSocket *_socket;
QByteArray _inBuf;
diff --git a/distfoldd/clientagent.cc b/distfoldd/clientagent.cc
index f77a21a..8eb6c44 100644
--- a/distfoldd/clientagent.cc
+++ b/distfoldd/clientagent.cc
@@ -2,14 +2,15 @@
#include "clientagent.h"
-ClientAgent::ClientAgent(const QHostAddress& addr, uint port, const QDir& local_dir, SyncFlags flags, QObject *parent) :
- Agent(new QSslSocket, local_dir, flags, parent)
+ClientAgent::ClientAgent(const QHostAddress& addr, uint port, const QDir& local_dir, const QString& passwd, SyncFlags flags, QObject *parent) :
+ Agent(new QSslSocket, local_dir, passwd, flags, parent),
+ _state(STATE_HELLO), _challenge(generateChallenge())
{
qDebug() << "Starting client agent at" << QDateTime::currentDateTime();
- _socket->setParent(this); // Can't set parent until QObject constructed
+ _socket->setParent(this);
+ _socket->setPeerVerifyMode(QSslSocket::QueryPeer);
_socket->connectToHostEncrypted(addr.toString(), port);
- sendMessage(MSG_HELLO);
- _state = STATE_HELLO;
+ sendMessage(MSG_HELLO, encodeHelloMessage(_challenge));
}
void ClientAgent::handleMessage(MessageType msg, const QByteArray &data)
@@ -19,6 +20,18 @@ void ClientAgent::handleMessage(MessageType msg, const QByteArray &data)
case MSG_HELLO_REPLY:
Q_ASSERT(_state == STATE_HELLO);
qDebug() << "Hello reply";
+ Q_ASSERT(_socket->isEncrypted());
+ _state = STATE_AUTH;
+ sendMessage(MSG_AUTH, generateChallengeResponse(data, _challenge));
+ break;
+ case MSG_AUTH_REPLY:
+ Q_ASSERT(_state == STATE_AUTH);
+ qDebug() << "Auth reply";
+ if (decodeAuthReply(data) != AUTH_OK) {
+ qWarning() << "Authentication failed!";
+ _socket->close();
+ return;
+ }
_state = STATE_FILE_LIST;
sendFileList();
break;
diff --git a/distfoldd/clientagent.h b/distfoldd/clientagent.h
index d54bc69..85cb9dc 100644
--- a/distfoldd/clientagent.h
+++ b/distfoldd/clientagent.h
@@ -10,10 +10,11 @@ class ClientAgent : public Agent
{
Q_OBJECT
public:
- explicit ClientAgent(const QHostAddress& addr, uint port, const QDir& local_dir, SyncFlags flags, QObject *parent = 0);
+ explicit ClientAgent(const QHostAddress& addr, uint port, const QDir& local_dir, const QString& passwd, SyncFlags flags, QObject *parent = 0);
enum State {
STATE_HELLO,
+ STATE_AUTH,
STATE_FILE_LIST,
STATE_FILE_ACTIONS
};
@@ -32,6 +33,7 @@ private:
private:
State _state;
RemoteActionInfoList _pendingActions;
+ QByteArray _challenge;
};
#endif // CLIENTAGENT_H
diff --git a/distfoldd/compressor.cc b/distfoldd/compressor.cc
index 98eaa92..1e42446 100644
--- a/distfoldd/compressor.cc
+++ b/distfoldd/compressor.cc
@@ -1,6 +1,5 @@
#include <QtCore/QDebug>
-#include <zlib.h>
#include "compressor.h"
Compressor::Compressor()
@@ -9,86 +8,10 @@ Compressor::Compressor()
QByteArray Compressor::compress(const QByteArray& data)
{
- if (data.isEmpty()) return data;
-
- QByteArray in = data, out;
- z_stream strm;
- int ret;
-
- memset(&strm, 0, sizeof(strm));
- out.resize(qMax(in.size(), 1024));
-
- strm.avail_in = in.size();
- strm.next_in = reinterpret_cast<Bytef*>(in.data());
- strm.avail_out = out.size();
- strm.next_out = reinterpret_cast<Bytef*>(out.data());
-
- ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
- if (ret != Z_OK) {
- qWarning() << "deflateInit failed";
- return data;
- }
-
- do {
- if (strm.avail_out == 0) {
- int cur_size = out.size();
- out.resize(cur_size * 2);
- strm.avail_out = cur_size;
- strm.next_out = reinterpret_cast<Bytef*>(&out.data()[cur_size]);
- }
- ret = deflate(&strm, Z_FINISH);
- } while (ret == Z_OK || ret == Z_BUF_ERROR);
-
- Q_ASSERT(ret == Z_STREAM_END);
- Q_ASSERT(strm.avail_in == 0);
- if (strm.avail_out > 0) {
- out.resize(out.size() - strm.avail_out);
- }
-
- deflateEnd(&strm);
-
- return out;
+ return qCompress(data);
}
QByteArray Compressor::decompress(const QByteArray& data)
{
- if (data.isEmpty()) return data;
-
- QByteArray in = data, out;
- z_stream strm;
- int ret;
-
- memset(&strm, 0, sizeof(strm));
- out.resize(qMax(static_cast<int>(in.size() * 1.5), 1024));
-
- strm.avail_in = in.size();
- strm.next_in = reinterpret_cast<Bytef*>(in.data());
- strm.avail_out = out.size();
- strm.next_out = reinterpret_cast<Bytef*>(out.data());
-
- ret = inflateInit(&strm);
- if (ret != Z_OK) {
- qWarning() << "inflateInit failed";
- return data;
- }
-
- do {
- if (strm.avail_out == 0) {
- int cur_size = out.size();
- out.resize(cur_size * 2);
- strm.avail_out = cur_size;
- strm.next_out = reinterpret_cast<Bytef*>(&out.data()[cur_size]);
- }
- ret = inflate(&strm, Z_FINISH);
- } while (ret == Z_OK || ret == Z_BUF_ERROR);
-
- Q_ASSERT(ret == Z_STREAM_END);
- Q_ASSERT(strm.avail_in == 0);
- if (strm.avail_out > 0) {
- out.resize(out.size() - strm.avail_out);
- }
-
- inflateEnd(&strm);
-
- return out;
+ return qUncompress(data);
}
diff --git a/distfoldd/distfolder.cc b/distfoldd/distfolder.cc
index 211aafe..f047d02 100644
--- a/distfoldd/distfolder.cc
+++ b/distfoldd/distfolder.cc
@@ -24,6 +24,16 @@ DistFolder::DistFolder(const QUuid& uuid, const QString& localPath, QObject *par
qDebug() << "Ready Folder UUID:" << _uuid;
}
+QString DistFolder::password() const
+{
+ return _password;
+}
+
+void DistFolder::setPassword(const QString &passwd)
+{
+ _password = passwd;
+}
+
bool DistFolder::readOnlySync() const
{
return _syncFlags & Agent::SYNC_READ_ONLY;
@@ -122,7 +132,7 @@ void DistFolder::handleNewConnection()
qDebug() << "Incoming connection";
ServerAgent *agent =
new ServerAgent(static_cast<QSslSocket*>(_server->nextPendingConnection()),
- _localPath, _syncFlags, this);
+ _localPath, _password, _syncFlags, this);
connect(agent, SIGNAL(destroyed()), SLOT(handleDestroyedAgent()));
_numAgents++;
qDebug() << "Num agents" << _numAgents;
@@ -166,7 +176,7 @@ void DistFolder::handleMoreRecentHost(const QHostAddress &address, uint port, co
Q_UNUSED(dateTime);
if (_numAgents == 0) {
qDebug() << "Trying to connect to" << address.toString() << port;
- ClientAgent *agent = new ClientAgent(address, port, _localPath, _syncFlags, this);
+ ClientAgent *agent = new ClientAgent(address, port, _localPath, _password, _syncFlags, this);
connect(agent, SIGNAL(destroyed()), SLOT(handleDestroyedAgent()));
connect(agent, SIGNAL(finished()), SLOT(handleClientFinished()));
_numAgents++;
diff --git a/distfoldd/distfolder.h b/distfoldd/distfolder.h
index ae7a0ea..c3ba57a 100644
--- a/distfoldd/distfolder.h
+++ b/distfoldd/distfolder.h
@@ -14,6 +14,7 @@
class DistFolder : public QObject
{
Q_OBJECT
+ Q_PROPERTY(QString password READ password WRITE setPassword)
Q_PROPERTY(bool readOnlySync READ readOnlySync WRITE setReadOnlySync)
Q_PROPERTY(bool pullMode READ pullMode WRITE setPullMode)
Q_PROPERTY(bool compress READ compress WRITE setCompress)
@@ -21,6 +22,8 @@ class DistFolder : public QObject
public:
explicit DistFolder(const QUuid& uuid, const QString& path, QObject *parent = 0);
+ void setPassword(const QString& passwd);
+ QString password() const;
bool readOnlySync() const;
void setReadOnlySync(bool read_only);
bool pullMode() const;
@@ -50,6 +53,7 @@ private:
Watcher *_watcher;
Server *_server;
Discoverer *_discoverer;
+ QString _password;
Agent::SyncFlags _syncFlags;
int _numAgents;
};
diff --git a/distfoldd/localkey.cc b/distfoldd/localkey.cc
index f104afd..031c0b8 100644
--- a/distfoldd/localkey.cc
+++ b/distfoldd/localkey.cc
@@ -26,8 +26,6 @@ bool LocalKey::setupLocalKey()
}
}
- QCA::Initializer qca;
-
QCA::KeyGenerator keygen;
keygen.setBlockingEnabled(true);
diff --git a/distfoldd/main.cc b/distfoldd/main.cc
index 48d52a5..01ad11c 100644
--- a/distfoldd/main.cc
+++ b/distfoldd/main.cc
@@ -1,6 +1,7 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QSettings>
#include <QtCore/QDebug>
+#include <QtCrypto>
#include "distfolder.h"
#include "localkey.h"
@@ -13,6 +14,14 @@ int main(int argc, char *argv[])
a.setApplicationName("distfoldd");
a.setApplicationVersion("0.2");
+ QCA::Initializer qca;
+ qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
+
+ if (!QCA::isSupported("cert,pkey,rsa,sha1,hmac(sha1),random")) {
+ qWarning() << "QCA was not build with the OpenSSL plugin";
+ return EXIT_FAILURE;
+ }
+
if (!LocalKey::setupLocalKey()) {
qWarning() << "Failed to setup local private key";
return EXIT_FAILURE;
@@ -24,6 +33,7 @@ int main(int argc, char *argv[])
QUuid uuid(settings.value("uuid").toString());
QString path = settings.value("path").toString();
DistFolder *f = new DistFolder(uuid, path);
+ f->setPassword(settings.value("password", uuid.toString()).toString());
f->setReadOnlySync(settings.value("ro", false).toBool());
f->setPullMode(settings.value("pull", false).toBool());
f->setCompress(settings.value("compress", true).toBool());
diff --git a/distfoldd/server.cc b/distfoldd/server.cc
index 4c7c222..a0ab98f 100644
--- a/distfoldd/server.cc
+++ b/distfoldd/server.cc
@@ -43,6 +43,7 @@ void Server::incomingConnection(int socketDescriptor)
if (socket->setSocketDescriptor(socketDescriptor)) {
socket->setLocalCertificate(_cert);
socket->setPrivateKey(_key);
+ socket->setPeerVerifyMode(QSslSocket::QueryPeer);
socket->startServerEncryption();
addPendingConnection(socket);
} else {
diff --git a/distfoldd/serveragent.cc b/distfoldd/serveragent.cc
index 13e7848..3f133bd 100644
--- a/distfoldd/serveragent.cc
+++ b/distfoldd/serveragent.cc
@@ -2,8 +2,9 @@
#include "serveragent.h"
-ServerAgent::ServerAgent(QSslSocket *socket, const QDir& local_dir, SyncFlags flags, QObject *parent) :
- Agent(socket, local_dir, flags, parent)
+ServerAgent::ServerAgent(QSslSocket *socket, const QDir& local_dir, const QString& passwd, SyncFlags flags, QObject *parent) :
+ Agent(socket, local_dir, passwd, flags, parent),
+ _challenge(generateChallenge()), _authAttempted(false), _authOk(false)
{
qDebug() << "Starting server agent at" << QDateTime::currentDateTime();
}
@@ -13,24 +14,37 @@ void ServerAgent::handleMessage(MessageType msg, const QByteArray& data)
qDebug() << "Server::handleMessage" << msg << data.size();
switch (msg) {
case MSG_HELLO:
- sendMessage(MSG_HELLO_REPLY);
+ if (decodeHelloMessage(data, &_clientChallenge) == PROTO_BAD) {
+ qWarning() << "Invalid protocol version";
+ _socket->close();
+ }
+ sendMessage(MSG_HELLO_REPLY, _challenge);
+ break;
+ case MSG_AUTH:
+ handleAuth(data);
break;
case MSG_FILE_LIST:
+ if (!checkAuth()) return;
handleClientFileList(decodeFileInfoList(data));
break;
case MSG_PULL_FILE:
+ if (!checkAuth()) return;
handlePullFile(decodeFileName(data));
break;
case MSG_PUSH_FILE:
+ if (!checkAuth()) return;
handlePushedFile(data);
break;
case MSG_PUSH_FILE_METADATA:
+ if (!checkAuth()) return;
handlePushedMetadata(decodeFileInfoList(data));
break;
case MSG_DELETE_FILE:
+ if (!checkAuth()) return;
handleDeleteFile(decodeFileName(data));
break;
case MSG_BYE:
+ if (!checkAuth()) return;
qDebug() << "Got Bye";
emit finished();
_socket->close();
@@ -41,6 +55,40 @@ void ServerAgent::handleMessage(MessageType msg, const QByteArray& data)
}
}
+bool ServerAgent::checkAuth()
+{
+ if (_authOk) {
+ return true;
+ } else {
+ sendMessage(MSG_AUTH_REPLY, encodeAuthReply(AUTH_FAILED));
+ return false;
+ }
+}
+
+void ServerAgent::handleAuth(const QByteArray &response)
+{
+ if (_authAttempted) {
+ qWarning() << "Too many auth attempts";
+ sendMessage(MSG_AUTH_REPLY, encodeAuthReply(AUTH_FAILED));
+ _socket->flush();
+ _socket->close();
+ return;
+ }
+ _authAttempted = true;
+
+ qDebug() << "Server Handling client auth";
+
+ if (response == generateChallengeResponse(_challenge, _clientChallenge)) {
+ _authOk = true;
+ qDebug() << "Authentication successful";
+ } else {
+ _authOk = false;
+ qDebug() << "Authentication failed";
+ }
+
+ sendMessage(MSG_AUTH_REPLY, encodeAuthReply(_authOk ? AUTH_OK : AUTH_FAILED));
+}
+
void ServerAgent::handleClientFileList(const RemoteFileInfoList& list)
{
QFileInfoList files = scanFiles(QDir(wireToLocalPath(_subPath)));
diff --git a/distfoldd/serveragent.h b/distfoldd/serveragent.h
index c692e60..b5fd380 100644
--- a/distfoldd/serveragent.h
+++ b/distfoldd/serveragent.h
@@ -7,17 +7,25 @@ class ServerAgent : public Agent
{
Q_OBJECT
public:
- explicit ServerAgent(QSslSocket *socket, const QDir& local_dir, SyncFlags flags, QObject *parent = 0);
+ explicit ServerAgent(QSslSocket *socket, const QDir& local_dir, const QString& passwd, SyncFlags flags, QObject *parent = 0);
protected:
void handleMessage(MessageType msg, const QByteArray& data);
private:
+ bool checkAuth();
+ void handleAuth(const QByteArray& response);
void handleClientFileList(const RemoteFileInfoList& list);
void handlePullFile(const QString& path);
void handlePushedFile(const QByteArray& data);
void handlePushedMetadata(const RemoteFileInfoList& list);
void handleDeleteFile(const QString& path);
+
+private:
+ QByteArray _challenge;
+ QByteArray _clientChallenge;
+ bool _authAttempted;
+ bool _authOk;
};
#endif // SERVERAGENT_H