diff options
-rw-r--r-- | distfoldd/agent.cc | 65 | ||||
-rw-r--r-- | distfoldd/agent.h | 34 | ||||
-rw-r--r-- | distfoldd/clientagent.cc | 23 | ||||
-rw-r--r-- | distfoldd/clientagent.h | 4 | ||||
-rw-r--r-- | distfoldd/compressor.cc | 81 | ||||
-rw-r--r-- | distfoldd/distfolder.cc | 14 | ||||
-rw-r--r-- | distfoldd/distfolder.h | 4 | ||||
-rw-r--r-- | distfoldd/localkey.cc | 2 | ||||
-rw-r--r-- | distfoldd/main.cc | 10 | ||||
-rw-r--r-- | distfoldd/server.cc | 1 | ||||
-rw-r--r-- | distfoldd/serveragent.cc | 54 | ||||
-rw-r--r-- | distfoldd/serveragent.h | 10 |
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 |