From 735fcbf3111fbaf5a50d1d1bc25df8ce94a05fe7 Mon Sep 17 00:00:00 2001 From: Gavin Bises Date: Sat, 21 Feb 2015 21:29:59 -0500 Subject: [PATCH 01/12] Add first draft of protocol extension for registration Stub for registration command handling in server First draft of handling registration requests WIP (will be rebased) clean up bad imports (rebase this later) Finish checkUserIsBanned method Add username validity check Check servatrice registration settings WIP Finish(?) server side of registration Needs testing Fix switch case compile failure I have no idea why I have to do this WIP for registration testing python script Stub register script initial attempt Rearrange register script First try at sending reg register.py sends commands correctly now Add more debug to register.py Pack bytes the right way - servatrice can parse py script sends now register.py should be working now Parse xml hack correctly Log registration enabled settings on server start Insert gender correctly on register Show tcpserver error message on failed gameserver listen Fail startup if db configured and can't be opened. TIL qt5 comes without mysql by default in homebrew... --- common/pb/CMakeLists.txt | 1 + common/pb/response.proto | 9 + common/pb/response_register.proto | 9 + common/pb/session_commands.proto | 21 +++ common/server.cpp | 39 +++++ common/server.h | 18 ++ common/server_database_interface.h | 4 + common/server_protocolhandler.cpp | 49 ++++++ common/server_protocolhandler.h | 2 + servatrice/scripts/.gitignore | 1 + servatrice/scripts/mk_pypb.sh | 10 ++ servatrice/scripts/register.py | 73 ++++++++ servatrice/servatrice.ini.example | 7 + servatrice/src/servatrice.cpp | 24 ++- .../src/servatrice_database_interface.cpp | 162 +++++++++++++----- .../src/servatrice_database_interface.h | 13 +- 16 files changed, 394 insertions(+), 48 deletions(-) create mode 100644 common/pb/response_register.proto create mode 100644 servatrice/scripts/.gitignore create mode 100755 servatrice/scripts/mk_pypb.sh create mode 100755 servatrice/scripts/register.py diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt index adf1e1b8..e698c95f 100644 --- a/common/pb/CMakeLists.txt +++ b/common/pb/CMakeLists.txt @@ -123,6 +123,7 @@ SET(PROTO_FILES response_join_room.proto response_list_users.proto response_login.proto + response_register.proto response_replay_download.proto response_replay_list.proto response.proto diff --git a/common/pb/response.proto b/common/pb/response.proto index 0f13c415..302b1801 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -24,6 +24,14 @@ message Response { RespAccessDenied = 20; RespUsernameInvalid = 21; RespRegistrationRequired = 22; + RespRegistrationAccepted = 23; // Server agrees to process client's registration request + RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered + RespEmailRequiredToRegister = 25; // Server requires email to register accounts but client did not provide one + RespServerDoesNotUseAuth = 26; // Client attempted to register but server does not use authentication + RespTooManyRequests = 27; // Server refused to complete command because client has sent too many too quickly + RespAccountNotActivated = 28; // Client attempted to log into a registered username but the account hasn't been activated + RespRegistrationDisabled = 29; // Server does not allow clients to register + RespRegistrationFailed = 30; // Server accepted a reg request but failed to perform the registration } enum ResponseType { JOIN_ROOM = 1000; @@ -35,6 +43,7 @@ message Response { DECK_LIST = 1006; DECK_DOWNLOAD = 1007; DECK_UPLOAD = 1008; + REGISTER = 1009; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; } diff --git a/common/pb/response_register.proto b/common/pb/response_register.proto new file mode 100644 index 00000000..9c6998ef --- /dev/null +++ b/common/pb/response_register.proto @@ -0,0 +1,9 @@ +import "response.proto"; + +message Response_Register { + extend Response { + optional Response_Register ext = 1009; + } + optional string denied_reason_str = 1; + optional uint64 denied_end_time = 2; +} \ No newline at end of file diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index bbb5e81d..be5cc1db 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -1,3 +1,5 @@ +import "serverinfo_user.proto"; + message SessionCommand { enum SessionCommandType { PING = 1000; @@ -16,6 +18,7 @@ message SessionCommand { DECK_UPLOAD = 1013; LIST_ROOMS = 1014; JOIN_ROOM = 1015; + REGISTER = 1016; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; REPLAY_MODIFY_MATCH = 1102; @@ -94,3 +97,21 @@ message Command_JoinRoom { } optional uint32 room_id = 1; } + +// User wants to register a new account +message Command_Register { + extend SessionCommand { + optional Command_Register ext = 1016; + } + // User name client wants to register + required string user_name = 1; + // Hashed password to be inserted into database + required string password = 2; + // Email address of the client for user validation + optional string email = 3; + // Gender of the user + optional ServerInfo_User.Gender gender = 4; + // Country code of the user. 2 letter ISO format + optional string country = 5; + optional string real_name = 6; +} diff --git a/common/server.cpp b/common/server.cpp index 4b65739b..7fd083e2 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -175,6 +175,45 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString return authState; } +RegistrationResult Server::registerUserAccount(const QString &ipAddress, const Command_Register &cmd, QString &banReason, int &banSecondsRemaining) +{ + // TODO + + if (!registrationEnabled) + return RegistrationDisabled; + + QString emailAddress = QString::fromStdString(cmd.email()); + if (requireEmailForRegistration && emailAddress.isEmpty()) + return EmailRequired; + + Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); + + // TODO: Move this method outside of the db interface + QString userName = QString::fromStdString(cmd.user_name()); + if (!databaseInterface->usernameIsValid(userName)) + return InvalidUsername; + + if (databaseInterface->checkUserIsBanned(ipAddress, userName, banReason, banSecondsRemaining)) + return ClientIsBanned; + + if (tooManyRegistrationAttempts(ipAddress)) + return TooManyRequests; + + QString realName = QString::fromStdString(cmd.real_name()); + ServerInfo_User_Gender gender = cmd.gender(); + QString country = QString::fromStdString(cmd.country()); + QString passwordSha512 = QString::fromStdString(cmd.password()); + bool regSucceeded = databaseInterface->registerUser(userName, realName, gender, passwordSha512, emailAddress, country, false); + + return regSucceeded ? Accepted : Failed; +} + +bool Server::tooManyRegistrationAttempts(const QString &ipAddress) +{ + // TODO: implement + return false; +} + void Server::addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId) { QWriteLocker locker(&persistentPlayersLock); diff --git a/common/server.h b/common/server.h index 896f6ef8..ca039350 100644 --- a/common/server.h +++ b/common/server.h @@ -7,6 +7,7 @@ #include #include #include +#include "pb/commands.pb.h" #include "pb/serverinfo_user.pb.h" #include "server_player_reference.h" @@ -28,6 +29,7 @@ class CommandContainer; class Command_JoinGame; enum AuthenticationResult { NotLoggedIn = 0, PasswordRight = 1, UnknownUser = 2, WouldOverwriteOldSession = 3, UserIsBanned = 4, UsernameInvalid = 5, RegistrationRequired = 6 }; +enum RegistrationResult { Accepted = 0, UserAlreadyExists = 1, EmailRequired = 2, UnauthenticatedServer = 3, TooManyRequests = 4, InvalidUsername = 5, ClientIsBanned = 6, RegistrationDisabled = 7, Failed = 8}; class Server : public QObject { @@ -44,6 +46,19 @@ public: ~Server(); void setThreaded(bool _threaded) { threaded = _threaded; } AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); + + /** + * Registers a user account. + * @param ipAddress The address of the connection from the user + * @param userName The username to attempt to register + * @param emailAddress The email address to associate with the new account (and to use for activation) + * @param banReason If the client is banned, the reason for the ban will be included in this string. + * @param banSecondsRemaining If the client is banned, the time left will be included in this. 0 if the ban is permanent. + * @return RegistrationResult member indicating whether it succeeded or failed. + */ + RegistrationResult registerUserAccount(const QString &ipAddress, const Command_Register &cmd, QString &banReason, int &banSecondsRemaining); + + bool tooManyRegistrationAttempts(const QString &ipAddress); const QMap &getRooms() { return rooms; } Server_AbstractUserInterface *findUser(const QString &userName) const; @@ -115,6 +130,9 @@ protected: int getUsersCount() const; int getGamesCount() const; void addRoom(Server_Room *newRoom); + + bool registrationEnabled; + bool requireEmailForRegistration; }; #endif diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 518bd5e6..2568f195 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -13,6 +13,7 @@ public: : QObject(parent) { } virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; + virtual bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) { return false; } virtual bool userExists(const QString & /* user */) { return false; } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } virtual QMap getIgnoreList(const QString & /* name */) { return QMap(); } @@ -23,6 +24,7 @@ public: virtual DeckList *getDeckFromDatabase(int /* deckId */, int /* userId */) { return 0; } virtual qint64 startSession(const QString & /* userName */, const QString & /* address */) { return 0; } + virtual bool usernameIsValid(const QString &userName) { return true; }; public slots: virtual void endSession(qint64 /* sessionId */ ) { } public: @@ -35,9 +37,11 @@ public: virtual bool userSessionExists(const QString & /* userName */) { return false; } virtual bool getRequireRegistration() { return false; } + virtual bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &passwordSha512, const QString &emailAddress, const QString &country, bool active = false) { return false; } enum LogMessage_TargetType { MessageTargetRoom, MessageTargetGame, MessageTargetChat, MessageTargetIslRoom }; virtual void logMessage(const int /* senderId */, const QString & /* senderName */, const QString & /* senderIp */, const QString & /* logMessage */, LogMessage_TargetType /* targetType */, const int /* targetId */, const QString & /* targetName */) { }; + bool checkUserIsBanned(Server_ProtocolHandler *session, QString &banReason, int &banSecondsRemaining); }; #endif diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 46d79139..cc5dd7f9 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -9,6 +9,7 @@ #include "pb/commands.pb.h" #include "pb/response.pb.h" #include "pb/response_login.pb.h" +#include "pb/response_register.pb.h" #include "pb/response_list_users.pb.h" #include "pb/response_get_games_of_user.pb.h" #include "pb/response_get_user_info.pb.h" @@ -134,12 +135,17 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co SessionCommand debugSc(sc); debugSc.MutableExtension(Command_Login::ext)->clear_password(); logDebugMessage(QString::fromStdString(debugSc.ShortDebugString())); + } else if (num == SessionCommand::REGISTER) { + SessionCommand logSc(sc); + logSc.MutableExtension(Command_Register::ext)->clear_password(); + logDebugMessage(QString::fromStdString(logSc.ShortDebugString())); } else logDebugMessage(QString::fromStdString(sc.ShortDebugString())); } switch ((SessionCommand::SessionCommandType) num) { case SessionCommand::PING: resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc); break; case SessionCommand::LOGIN: resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc); break; + case SessionCommand::REGISTER: resp = cmdRegisterAccount(sc.GetExtension(Command_Register::ext), rc); break; case SessionCommand::MESSAGE: resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc); break; case SessionCommand::GET_GAMES_OF_USER: resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc); break; case SessionCommand::GET_USER_INFO: resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc); break; @@ -413,6 +419,49 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd return Response::RespOk; } +Response::ResponseCode Server_ProtocolHandler::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) +{ + qDebug() << "Got register command: " << QString::fromStdString(cmd.user_name()); + + QString banReason; + int banSecondsRemaining; + RegistrationResult result = + server->registerUserAccount( + this->getAddress(), + cmd, + banReason, + banSecondsRemaining); + qDebug() << "Register command result:" << result; + + switch (result) { + case RegistrationDisabled: + return Response::RespRegistrationDisabled; + case Accepted: + return Response::RespRegistrationAccepted; + case UserAlreadyExists: + return Response::RespUserAlreadyExists; + case EmailRequired: + return Response::RespEmailRequiredToRegister; + case UnauthenticatedServer: + return Response::RespServerDoesNotUseAuth; + case TooManyRequests: + return Response::RespTooManyRequests; + case InvalidUsername: + return Response::RespUsernameInvalid; + case Failed: + return Response::RespRegistrationFailed; + case ClientIsBanned: + Response_Register *re = new Response_Register; + re->set_denied_reason_str(banReason.toStdString()); + if (banSecondsRemaining != 0) + re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsRemaining).toTime_t()); + rc.setResponseExtension(re); + return Response::RespUserIsBanned; + } + + return Response::RespInvalidCommand; +} + Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index 5389b724..7ea34cf8 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -28,6 +28,7 @@ class AdminCommand; class Command_Ping; class Command_Login; +class Command_Register; class Command_Message; class Command_ListUsers; class Command_GetGamesOfUser; @@ -59,6 +60,7 @@ private: Response::ResponseCode cmdPing(const Command_Ping &cmd, ResponseContainer &rc); Response::ResponseCode cmdLogin(const Command_Login &cmd, ResponseContainer &rc); + Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc); Response::ResponseCode cmdMessage(const Command_Message &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc); diff --git a/servatrice/scripts/.gitignore b/servatrice/scripts/.gitignore new file mode 100644 index 00000000..eb47a082 --- /dev/null +++ b/servatrice/scripts/.gitignore @@ -0,0 +1 @@ +pypb/ diff --git a/servatrice/scripts/mk_pypb.sh b/servatrice/scripts/mk_pypb.sh new file mode 100755 index 00000000..e11d237d --- /dev/null +++ b/servatrice/scripts/mk_pypb.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +SRC_DIR=../../common/pb/ +DST_DIR=./pypb + +rm -rf "$DST_DIR" +mkdir -p "$DST_DIR" +protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/*.proto +touch "$DST_DIR/__init__.py" + diff --git a/servatrice/scripts/register.py b/servatrice/scripts/register.py new file mode 100755 index 00000000..427c9e72 --- /dev/null +++ b/servatrice/scripts/register.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +import socket, sys, struct, time + +from pypb.server_message_pb2 import ServerMessage +from pypb.session_commands_pb2 import Command_Register as Reg +from pypb.commands_pb2 import CommandContainer as Cmd +from pypb.event_server_identification_pb2 import Event_ServerIdentification as ServerId +from pypb.response_pb2 import Response + +HOST = "localhost" +PORT = 4747 + +CMD_ID = 1 + +def build_reg(): + global CMD_ID + cmd = Cmd() + sc = cmd.session_command.add() + + reg = sc.Extensions[Reg.ext] + reg.user_name = "testUser" + reg.email = "test@example.com" + reg.password = "password" + + cmd.cmd_id = CMD_ID + CMD_ID += 1 + return cmd + +def send(msg): + packed = struct.pack('>I', len(msg)) + sock.sendall(packed) + sock.sendall(msg) + +def print_resp(resp): + print "<<<" + print repr(resp) + m = ServerMessage() + m.ParseFromString(bytes(resp)) + print m + +def recv(sock): + print "< header" + header = sock.recv(4) + msg_size = struct.unpack('>I', header)[0] + print "< ", msg_size + raw_msg = sock.recv(msg_size) + print_resp(raw_msg) + +if __name__ == "__main__": + address = (HOST, PORT) + sock = socket.socket() + + print "Connecting to server ", address + sock.connect(address) + + # hack for old xml clients - server expects this and discards first message + print ">>> xml hack" + xmlClientHack = Cmd().SerializeToString() + send(xmlClientHack) + print sock.recv(60) + + recv(sock) + + print ">>> register" + r = build_reg() + print r + msg = r.SerializeToString() + send(msg) + recv(sock) + + print "Done" + diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index bd1cad55..f75a8958 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -54,6 +54,13 @@ password=123456 ; Accept only registered users? default is 0 (accept unregistered users) regonly=0 +[registration] + +; Servatrice can process registration requests to add new users on the fly. +; Enable this feature? Default false. +;enabled=false +; Require users to provide an email address in order to register. Default true. +;requireemail=true [database] diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 66a9c977..e4430912 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -160,6 +160,13 @@ bool Servatrice::initServer() authenticationMethod = AuthenticationNone; } + registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); + requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); + + qDebug() << "Registration enabled: " << registrationEnabled; + if (registrationEnabled) + qDebug() << "Require email address to register: " << requireEmailForRegistration; + QString dbTypeStr = settingsCache->value("database/type").toString(); if (dbTypeStr == "mysql") databaseType = DatabaseMySql; @@ -172,12 +179,17 @@ bool Servatrice::initServer() if (databaseType != DatabaseNone) { settingsCache->beginGroup("database"); dbPrefix = settingsCache->value("prefix").toString(); - servatriceDatabaseInterface->initDatabase("QMYSQL", - settingsCache->value("hostname").toString(), - settingsCache->value("database").toString(), - settingsCache->value("user").toString(), - settingsCache->value("password").toString()); + bool dbOpened = + servatriceDatabaseInterface->initDatabase("QMYSQL", + settingsCache->value("hostname").toString(), + settingsCache->value("database").toString(), + settingsCache->value("user").toString(), + settingsCache->value("password").toString()); settingsCache->endGroup(); + if (!dbOpened) { + qDebug() << "Failed to open database"; + return false; + } updateServerList(); @@ -342,7 +354,7 @@ bool Servatrice::initServer() if (gameServer->listen(QHostAddress::Any, gamePort)) qDebug() << "Server listening."; else { - qDebug() << "gameServer->listen(): Error."; + qDebug() << "gameServer->listen(): Error:" << gameServer->errorString(); return false; } return true; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 77f31156..aabf2a2a 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -9,6 +9,7 @@ #include #include #include +#include Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) : instanceId(_instanceId), @@ -34,7 +35,9 @@ void Servatrice_DatabaseInterface::initDatabase(const QSqlDatabase &_sqlDatabase } } -void Servatrice_DatabaseInterface::initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password) +bool Servatrice_DatabaseInterface::initDatabase(const QString &type, const QString &hostName, + const QString &databaseName, const QString &userName, + const QString &password) { sqlDatabase = QSqlDatabase::addDatabase(type, "main"); sqlDatabase.setHostName(hostName); @@ -42,7 +45,7 @@ void Servatrice_DatabaseInterface::initDatabase(const QString &type, const QStri sqlDatabase.setUserName(userName); sqlDatabase.setPassword(password); - openDatabase(); + return openDatabase(); } bool Servatrice_DatabaseInterface::openDatabase() @@ -102,11 +105,52 @@ bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user) return re.exactMatch(user); } +// TODO move this to Server bool Servatrice_DatabaseInterface::getRequireRegistration() { return settingsCache->value("authentication/regonly", 0).toBool(); } +bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &passwordSha512, const QString &emailAddress, const QString &country, bool active) +{ + if (!checkSql()) + return false; + + QSqlQuery *query = prepareQuery("insert into {prefix}_users " + "(name, realname, gender, password_sha512, email, country, registrationDate, active) " + "values " + "(:userName, :realName, :gender, :password_sha512, :email, :country, UTC_TIMESTAMP(), :active)"); + query->bindValue(":userName", userName); + query->bindValue(":realName", realName); + query->bindValue(":gender", getGenderChar(gender)); + query->bindValue(":password_sha512", passwordSha512); + query->bindValue(":email", emailAddress); + query->bindValue(":country", country); + query->bindValue(":active", active ? 1 : 0); + + if (!execSqlQuery(query)) { + qDebug() << "Failed to insert user: " << query->lastError() << " sql: " << query->lastQuery(); + // TODO handle duplicate insert error + return false; + } + + return true; +} + +QChar Servatrice_DatabaseInterface::getGenderChar(ServerInfo_User_Gender const &gender) +{ + switch (gender) { + case ServerInfo_User_Gender_GenderUnknown: + return QChar('u'); + case ServerInfo_User_Gender_Male: + return QChar('m'); + case ServerInfo_User_Gender_Female: + return QChar('f'); + default: + return QChar('u'); + } +} + AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) { switch (server->getAuthenticationMethod()) { @@ -125,43 +169,8 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (!usernameIsValid(user)) return UsernameInvalid; - QSqlQuery *ipBanQuery = prepareQuery("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.ip_address = :address) and b.ip_address = :address2"); - ipBanQuery->bindValue(":address", static_cast(handler)->getPeerAddress().toString()); - ipBanQuery->bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); - if (!execSqlQuery(ipBanQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } - - if (ipBanQuery->next()) { - const int secondsLeft = -ipBanQuery->value(0).toInt(); - const bool permanentBan = ipBanQuery->value(1).toInt(); - if ((secondsLeft > 0) || permanentBan) { - reasonStr = ipBanQuery->value(2).toString(); - banSecondsLeft = permanentBan ? 0 : secondsLeft; - qDebug("Login denied: banned by address"); - return UserIsBanned; - } - } - - QSqlQuery *nameBanQuery = prepareQuery("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.user_name = :name2) and b.user_name = :name1"); - nameBanQuery->bindValue(":name1", user); - nameBanQuery->bindValue(":name2", user); - if (!execSqlQuery(nameBanQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } - - if (nameBanQuery->next()) { - const int secondsLeft = -nameBanQuery->value(0).toInt(); - const bool permanentBan = nameBanQuery->value(1).toInt(); - if ((secondsLeft > 0) || permanentBan) { - reasonStr = nameBanQuery->value(2).toString(); - banSecondsLeft = permanentBan ? 0 : secondsLeft; - qDebug("Login denied: banned by name"); - return UserIsBanned; - } - } + if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) + return UserIsBanned; QSqlQuery *passwordQuery = prepareQuery("select password_sha512 from {prefix}_users where name = :name and active = 1"); passwordQuery->bindValue(":name", user); @@ -188,6 +197,79 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot return UnknownUser; } +bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) + return false; + + if (!checkSql()) { + qDebug("Failed to check if user is banned. Database invalid."); + return false; + } + + return + checkUserIsIpBanned(ipAddress, banReason, banSecondsRemaining) + || checkUserIsNameBanned(userName, banReason, banSecondsRemaining); + +} + +bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName, QString &banReason, int &banSecondsRemaining) +{ + QSqlQuery *nameBanQuery = prepareQuery("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.user_name = :name2) and b.user_name = :name1"); + nameBanQuery->bindValue(":name1", userName); + nameBanQuery->bindValue(":name2", userName); + if (!execSqlQuery(nameBanQuery)) { + qDebug() << "Name ban check failed: SQL error" << nameBanQuery->lastError(); + return false; + } + + if (nameBanQuery->next()) { + const int secondsLeft = -nameBanQuery->value(0).toInt(); + const bool permanentBan = nameBanQuery->value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + banReason = nameBanQuery->value(2).toString(); + banSecondsRemaining = permanentBan ? 0 : secondsLeft; + qDebug() << "Username" << userName << "is banned by name"; + return true; + } + } + return false; +} + +bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining) +{ + QSqlQuery *ipBanQuery = prepareQuery( + "select" + " time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute)))," + " b.minutes <=> 0," + " b.visible_reason" + " from {prefix}_bans b" + " where" + " b.time_from = (select max(c.time_from)" + " from {prefix}_bans c" + " where c.ip_address = :address)" + " and b.ip_address = :address2"); + + ipBanQuery->bindValue(":address", ipAddress); + ipBanQuery->bindValue(":address2", ipAddress); + if (!execSqlQuery(ipBanQuery)) { + qDebug() << "IP ban check failed: SQL error." << ipBanQuery->lastError(); + return false; + } + + if (ipBanQuery->next()) { + const int secondsLeft = -ipBanQuery->value(0).toInt(); + const bool permanentBan = ipBanQuery->value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + banReason = ipBanQuery->value(2).toString(); + banSecondsRemaining = permanentBan ? 0 : secondsLeft; + qDebug() << "User is banned by address" << ipAddress; + return true; + } + } + return false; +} + bool Servatrice_DatabaseInterface::userExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { @@ -579,4 +661,4 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, const QString query->bindValue(":target_id", (targetType == MessageTargetChat && targetId < 1) ? QVariant() : targetId); query->bindValue(":target_name", targetName); execSqlQuery(query); -} +} \ No newline at end of file diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 8850d7be..d352ba7a 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "server.h" #include "server_database_interface.h" @@ -18,15 +19,22 @@ private: QHash preparedStatements; Servatrice *server; ServerInfo_User evalUserQueryResult(const QSqlQuery *query, bool complete, bool withId = false); - bool usernameIsValid(const QString &user); + /** Must be called after checkSql and server is known to be in auth mode. */ + bool checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining); + /** Must be called after checkSql and server is known to be in auth mode. */ + bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); + QChar getGenderChar(ServerInfo_User_Gender const &gender); protected: + bool usernameIsValid(const QString &user); AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); + bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); public slots: void initDatabase(const QSqlDatabase &_sqlDatabase); public: Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server); ~Servatrice_DatabaseInterface(); - void initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password); + bool initDatabase(const QString &type, const QString &hostName, const QString &databaseName, + const QString &userName, const QString &password); bool openDatabase(); bool checkSql(); QSqlQuery * prepareQuery(const QString &queryText); @@ -55,6 +63,7 @@ public: bool userSessionExists(const QString &userName); bool getRequireRegistration(); + bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &passwordSha512, const QString &emailAddress, const QString &country, bool active = false); void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); }; From 5ace0dd8927d2145bd67a63c5be34f1d32369ed4 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sat, 23 May 2015 20:13:03 +0200 Subject: [PATCH 02/12] Almost completed registration * added missing bits of serverside code; * added fronted in client; * removed demo python scripts; --- cockatrice/CMakeLists.txt | 1 + cockatrice/src/abstractclient.h | 8 +- cockatrice/src/dlg_register.cpp | 355 ++++++++++++++++++ cockatrice/src/dlg_register.h | 33 ++ cockatrice/src/remoteclient.cpp | 54 ++- cockatrice/src/remoteclient.h | 6 + cockatrice/src/window_main.cpp | 89 ++++- cockatrice/src/window_main.h | 5 +- common/pb/response.proto | 4 +- common/server.cpp | 17 +- common/server.h | 4 +- common/server_database_interface.h | 3 +- common/server_protocolhandler.cpp | 5 +- servatrice/scripts/mk_pypb.sh | 10 - servatrice/scripts/register.py | 73 ---- servatrice/servatrice.ini.example | 2 + servatrice/src/passwordhasher.cpp | 19 + servatrice/src/passwordhasher.h | 1 + .../src/servatrice_database_interface.cpp | 3 - .../src/servatrice_database_interface.h | 5 +- 20 files changed, 585 insertions(+), 112 deletions(-) create mode 100644 cockatrice/src/dlg_register.cpp create mode 100644 cockatrice/src/dlg_register.h delete mode 100755 servatrice/scripts/mk_pypb.sh delete mode 100755 servatrice/scripts/register.py diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 7d0abade..99f2e62e 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -12,6 +12,7 @@ SET(cockatrice_SOURCES src/dlg_connect.cpp src/dlg_create_token.cpp src/dlg_edit_tokens.cpp + src/dlg_register.cpp src/abstractclient.cpp src/remoteclient.cpp src/main.cpp diff --git a/cockatrice/src/abstractclient.h b/cockatrice/src/abstractclient.h index 6f3b7ede..e683e3ba 100644 --- a/cockatrice/src/abstractclient.h +++ b/cockatrice/src/abstractclient.h @@ -29,9 +29,9 @@ enum ClientStatus { StatusDisconnected, StatusDisconnecting, StatusConnecting, - StatusAwaitingWelcome, + StatusRegistering, StatusLoggingIn, - StatusLoggedIn + StatusLoggedIn, }; class AbstractClient : public QObject { @@ -59,6 +59,7 @@ signals: void buddyListReceived(const QList &buddyList); void ignoreListReceived(const QList &ignoreList); void replayAddedEventReceived(const Event_ReplayAdded &event); + void registerAccepted(); void sigQueuePendingCommand(PendingCommand *pend); private: @@ -71,7 +72,8 @@ protected slots: void processProtocolItem(const ServerMessage &item); protected: QMap pendingCommands; - QString userName, password; + QString userName, password, email, country, realName; + int gender; void setStatus(ClientStatus _status); int getNewCmdId() { return nextCmdId++; } virtual void sendCommandContainer(const CommandContainer &cont) = 0; diff --git a/cockatrice/src/dlg_register.cpp b/cockatrice/src/dlg_register.cpp new file mode 100644 index 00000000..af30da59 --- /dev/null +++ b/cockatrice/src/dlg_register.cpp @@ -0,0 +1,355 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dlg_register.h" +#include "pb/serverinfo_user.pb.h" + +DlgRegister::DlgRegister(QWidget *parent) + : QDialog(parent) +{ + QSettings settings; + settings.beginGroup("server"); + + hostLabel = new QLabel(tr("&Host:")); + hostEdit = new QLineEdit(settings.value("hostname", "cockatrice.woogerworks.com").toString()); + hostLabel->setBuddy(hostEdit); + + portLabel = new QLabel(tr("&Port:")); + portEdit = new QLineEdit(settings.value("port", "4747").toString()); + portLabel->setBuddy(portEdit); + + playernameLabel = new QLabel(tr("Player &name:")); + playernameEdit = new QLineEdit(settings.value("playername", "Player").toString()); + playernameLabel->setBuddy(playernameEdit); + + passwordLabel = new QLabel(tr("P&assword:")); + passwordEdit = new QLineEdit(settings.value("password").toString()); + passwordLabel->setBuddy(passwordEdit); + passwordEdit->setEchoMode(QLineEdit::Password); + + emailLabel = new QLabel(tr("Email:")); + emailEdit = new QLineEdit(); + emailLabel->setBuddy(emailEdit); + + genderLabel = new QLabel(tr("Gender:")); + genderEdit = new QComboBox(); + genderLabel->setBuddy(genderEdit); + genderEdit->insertItem(0, QIcon(":/resources/genders/unknown.svg"), tr("Undefined")); + genderEdit->insertItem(1, QIcon(":/resources/genders/male.svg"), tr("Male")); + genderEdit->insertItem(2, QIcon(":/resources/genders/female.svg"), tr("Female")); + genderEdit->setCurrentIndex(0); + + countryLabel = new QLabel(tr("Country:")); + countryEdit = new QComboBox(); + countryLabel->setBuddy(countryEdit); + countryEdit->insertItem(0, tr("Undefined")); + countryEdit->addItem(QPixmap(":/resources/countries/ad.svg"), "ad"); + countryEdit->addItem(QIcon(":/resources/countries/ae.svg"), "ae"); + countryEdit->addItem(QIcon(":/resources/countries/af.svg"), "af"); + countryEdit->addItem(QIcon(":/resources/countries/ag.svg"), "ag"); + countryEdit->addItem(QIcon(":/resources/countries/ai.svg"), "ai"); + countryEdit->addItem(QIcon(":/resources/countries/al.svg"), "al"); + countryEdit->addItem(QIcon(":/resources/countries/am.svg"), "am"); + countryEdit->addItem(QIcon(":/resources/countries/ao.svg"), "ao"); + countryEdit->addItem(QIcon(":/resources/countries/aq.svg"), "aq"); + countryEdit->addItem(QIcon(":/resources/countries/ar.svg"), "ar"); + countryEdit->addItem(QIcon(":/resources/countries/as.svg"), "as"); + countryEdit->addItem(QIcon(":/resources/countries/at.svg"), "at"); + countryEdit->addItem(QIcon(":/resources/countries/au.svg"), "au"); + countryEdit->addItem(QIcon(":/resources/countries/aw.svg"), "aw"); + countryEdit->addItem(QIcon(":/resources/countries/ax.svg"), "ax"); + countryEdit->addItem(QIcon(":/resources/countries/az.svg"), "az"); + countryEdit->addItem(QIcon(":/resources/countries/ba.svg"), "ba"); + countryEdit->addItem(QIcon(":/resources/countries/bb.svg"), "bb"); + countryEdit->addItem(QIcon(":/resources/countries/bd.svg"), "bd"); + countryEdit->addItem(QIcon(":/resources/countries/be.svg"), "be"); + countryEdit->addItem(QIcon(":/resources/countries/bf.svg"), "bf"); + countryEdit->addItem(QIcon(":/resources/countries/bg.svg"), "bg"); + countryEdit->addItem(QIcon(":/resources/countries/bh.svg"), "bh"); + countryEdit->addItem(QIcon(":/resources/countries/bi.svg"), "bi"); + countryEdit->addItem(QIcon(":/resources/countries/bj.svg"), "bj"); + countryEdit->addItem(QIcon(":/resources/countries/bl.svg"), "bl"); + countryEdit->addItem(QIcon(":/resources/countries/bm.svg"), "bm"); + countryEdit->addItem(QIcon(":/resources/countries/bn.svg"), "bn"); + countryEdit->addItem(QIcon(":/resources/countries/bo.svg"), "bo"); + countryEdit->addItem(QIcon(":/resources/countries/bq.svg"), "bq"); + countryEdit->addItem(QIcon(":/resources/countries/br.svg"), "br"); + countryEdit->addItem(QIcon(":/resources/countries/bs.svg"), "bs"); + countryEdit->addItem(QIcon(":/resources/countries/bt.svg"), "bt"); + countryEdit->addItem(QIcon(":/resources/countries/bv.svg"), "bv"); + countryEdit->addItem(QIcon(":/resources/countries/bw.svg"), "bw"); + countryEdit->addItem(QIcon(":/resources/countries/by.svg"), "by"); + countryEdit->addItem(QIcon(":/resources/countries/bz.svg"), "bz"); + countryEdit->addItem(QIcon(":/resources/countries/ca.svg"), "ca"); + countryEdit->addItem(QIcon(":/resources/countries/cc.svg"), "cc"); + countryEdit->addItem(QIcon(":/resources/countries/cd.svg"), "cd"); + countryEdit->addItem(QIcon(":/resources/countries/cf.svg"), "cf"); + countryEdit->addItem(QIcon(":/resources/countries/cg.svg"), "cg"); + countryEdit->addItem(QIcon(":/resources/countries/ch.svg"), "ch"); + countryEdit->addItem(QIcon(":/resources/countries/ci.svg"), "ci"); + countryEdit->addItem(QIcon(":/resources/countries/ck.svg"), "ck"); + countryEdit->addItem(QIcon(":/resources/countries/cl.svg"), "cl"); + countryEdit->addItem(QIcon(":/resources/countries/cm.svg"), "cm"); + countryEdit->addItem(QIcon(":/resources/countries/cn.svg"), "cn"); + countryEdit->addItem(QIcon(":/resources/countries/co.svg"), "co"); + countryEdit->addItem(QIcon(":/resources/countries/cr.svg"), "cr"); + countryEdit->addItem(QIcon(":/resources/countries/cu.svg"), "cu"); + countryEdit->addItem(QIcon(":/resources/countries/cv.svg"), "cv"); + countryEdit->addItem(QIcon(":/resources/countries/cw.svg"), "cw"); + countryEdit->addItem(QIcon(":/resources/countries/cx.svg"), "cx"); + countryEdit->addItem(QIcon(":/resources/countries/cy.svg"), "cy"); + countryEdit->addItem(QIcon(":/resources/countries/cz.svg"), "cz"); + countryEdit->addItem(QIcon(":/resources/countries/de.svg"), "de"); + countryEdit->addItem(QIcon(":/resources/countries/dj.svg"), "dj"); + countryEdit->addItem(QIcon(":/resources/countries/dk.svg"), "dk"); + countryEdit->addItem(QIcon(":/resources/countries/dm.svg"), "dm"); + countryEdit->addItem(QIcon(":/resources/countries/do.svg"), "do"); + countryEdit->addItem(QIcon(":/resources/countries/dz.svg"), "dz"); + countryEdit->addItem(QIcon(":/resources/countries/ec.svg"), "ec"); + countryEdit->addItem(QIcon(":/resources/countries/ee.svg"), "ee"); + countryEdit->addItem(QIcon(":/resources/countries/eg.svg"), "eg"); + countryEdit->addItem(QIcon(":/resources/countries/eh.svg"), "eh"); + countryEdit->addItem(QIcon(":/resources/countries/er.svg"), "er"); + countryEdit->addItem(QIcon(":/resources/countries/es.svg"), "es"); + countryEdit->addItem(QIcon(":/resources/countries/et.svg"), "et"); + countryEdit->addItem(QIcon(":/resources/countries/fi.svg"), "fi"); + countryEdit->addItem(QIcon(":/resources/countries/fj.svg"), "fj"); + countryEdit->addItem(QIcon(":/resources/countries/fk.svg"), "fk"); + countryEdit->addItem(QIcon(":/resources/countries/fm.svg"), "fm"); + countryEdit->addItem(QIcon(":/resources/countries/fo.svg"), "fo"); + countryEdit->addItem(QIcon(":/resources/countries/fr.svg"), "fr"); + countryEdit->addItem(QIcon(":/resources/countries/ga.svg"), "ga"); + countryEdit->addItem(QIcon(":/resources/countries/gb.svg"), "gb"); + countryEdit->addItem(QIcon(":/resources/countries/gd.svg"), "gd"); + countryEdit->addItem(QIcon(":/resources/countries/ge.svg"), "ge"); + countryEdit->addItem(QIcon(":/resources/countries/gf.svg"), "gf"); + countryEdit->addItem(QIcon(":/resources/countries/gg.svg"), "gg"); + countryEdit->addItem(QIcon(":/resources/countries/gh.svg"), "gh"); + countryEdit->addItem(QIcon(":/resources/countries/gi.svg"), "gi"); + countryEdit->addItem(QIcon(":/resources/countries/gl.svg"), "gl"); + countryEdit->addItem(QIcon(":/resources/countries/gm.svg"), "gm"); + countryEdit->addItem(QIcon(":/resources/countries/gn.svg"), "gn"); + countryEdit->addItem(QIcon(":/resources/countries/gp.svg"), "gp"); + countryEdit->addItem(QIcon(":/resources/countries/gq.svg"), "gq"); + countryEdit->addItem(QIcon(":/resources/countries/gr.svg"), "gr"); + countryEdit->addItem(QIcon(":/resources/countries/gs.svg"), "gs"); + countryEdit->addItem(QIcon(":/resources/countries/gt.svg"), "gt"); + countryEdit->addItem(QIcon(":/resources/countries/gu.svg"), "gu"); + countryEdit->addItem(QIcon(":/resources/countries/gw.svg"), "gw"); + countryEdit->addItem(QIcon(":/resources/countries/gy.svg"), "gy"); + countryEdit->addItem(QIcon(":/resources/countries/hk.svg"), "hk"); + countryEdit->addItem(QIcon(":/resources/countries/hm.svg"), "hm"); + countryEdit->addItem(QIcon(":/resources/countries/hn.svg"), "hn"); + countryEdit->addItem(QIcon(":/resources/countries/hr.svg"), "hr"); + countryEdit->addItem(QIcon(":/resources/countries/ht.svg"), "ht"); + countryEdit->addItem(QIcon(":/resources/countries/hu.svg"), "hu"); + countryEdit->addItem(QIcon(":/resources/countries/id.svg"), "id"); + countryEdit->addItem(QIcon(":/resources/countries/ie.svg"), "ie"); + countryEdit->addItem(QIcon(":/resources/countries/il.svg"), "il"); + countryEdit->addItem(QIcon(":/resources/countries/im.svg"), "im"); + countryEdit->addItem(QIcon(":/resources/countries/in.svg"), "in"); + countryEdit->addItem(QIcon(":/resources/countries/io.svg"), "io"); + countryEdit->addItem(QIcon(":/resources/countries/iq.svg"), "iq"); + countryEdit->addItem(QIcon(":/resources/countries/ir.svg"), "ir"); + countryEdit->addItem(QIcon(":/resources/countries/is.svg"), "is"); + countryEdit->addItem(QIcon(":/resources/countries/it.svg"), "it"); + countryEdit->addItem(QIcon(":/resources/countries/je.svg"), "je"); + countryEdit->addItem(QIcon(":/resources/countries/jm.svg"), "jm"); + countryEdit->addItem(QIcon(":/resources/countries/jo.svg"), "jo"); + countryEdit->addItem(QIcon(":/resources/countries/jp.svg"), "jp"); + countryEdit->addItem(QIcon(":/resources/countries/ke.svg"), "ke"); + countryEdit->addItem(QIcon(":/resources/countries/kg.svg"), "kg"); + countryEdit->addItem(QIcon(":/resources/countries/kh.svg"), "kh"); + countryEdit->addItem(QIcon(":/resources/countries/ki.svg"), "ki"); + countryEdit->addItem(QIcon(":/resources/countries/km.svg"), "km"); + countryEdit->addItem(QIcon(":/resources/countries/kn.svg"), "kn"); + countryEdit->addItem(QIcon(":/resources/countries/kp.svg"), "kp"); + countryEdit->addItem(QIcon(":/resources/countries/kr.svg"), "kr"); + countryEdit->addItem(QIcon(":/resources/countries/kw.svg"), "kw"); + countryEdit->addItem(QIcon(":/resources/countries/ky.svg"), "ky"); + countryEdit->addItem(QIcon(":/resources/countries/kz.svg"), "kz"); + countryEdit->addItem(QIcon(":/resources/countries/la.svg"), "la"); + countryEdit->addItem(QIcon(":/resources/countries/lb.svg"), "lb"); + countryEdit->addItem(QIcon(":/resources/countries/lc.svg"), "lc"); + countryEdit->addItem(QIcon(":/resources/countries/li.svg"), "li"); + countryEdit->addItem(QIcon(":/resources/countries/lk.svg"), "lk"); + countryEdit->addItem(QIcon(":/resources/countries/lr.svg"), "lr"); + countryEdit->addItem(QIcon(":/resources/countries/ls.svg"), "ls"); + countryEdit->addItem(QIcon(":/resources/countries/lt.svg"), "lt"); + countryEdit->addItem(QIcon(":/resources/countries/lu.svg"), "lu"); + countryEdit->addItem(QIcon(":/resources/countries/lv.svg"), "lv"); + countryEdit->addItem(QIcon(":/resources/countries/ly.svg"), "ly"); + countryEdit->addItem(QIcon(":/resources/countries/ma.svg"), "ma"); + countryEdit->addItem(QIcon(":/resources/countries/mc.svg"), "mc"); + countryEdit->addItem(QIcon(":/resources/countries/md.svg"), "md"); + countryEdit->addItem(QIcon(":/resources/countries/me.svg"), "me"); + countryEdit->addItem(QIcon(":/resources/countries/mf.svg"), "mf"); + countryEdit->addItem(QIcon(":/resources/countries/mg.svg"), "mg"); + countryEdit->addItem(QIcon(":/resources/countries/mh.svg"), "mh"); + countryEdit->addItem(QIcon(":/resources/countries/mk.svg"), "mk"); + countryEdit->addItem(QIcon(":/resources/countries/ml.svg"), "ml"); + countryEdit->addItem(QIcon(":/resources/countries/mm.svg"), "mm"); + countryEdit->addItem(QIcon(":/resources/countries/mn.svg"), "mn"); + countryEdit->addItem(QIcon(":/resources/countries/mo.svg"), "mo"); + countryEdit->addItem(QIcon(":/resources/countries/mp.svg"), "mp"); + countryEdit->addItem(QIcon(":/resources/countries/mq.svg"), "mq"); + countryEdit->addItem(QIcon(":/resources/countries/mr.svg"), "mr"); + countryEdit->addItem(QIcon(":/resources/countries/ms.svg"), "ms"); + countryEdit->addItem(QIcon(":/resources/countries/mt.svg"), "mt"); + countryEdit->addItem(QIcon(":/resources/countries/mu.svg"), "mu"); + countryEdit->addItem(QIcon(":/resources/countries/mv.svg"), "mv"); + countryEdit->addItem(QIcon(":/resources/countries/mw.svg"), "mw"); + countryEdit->addItem(QIcon(":/resources/countries/mx.svg"), "mx"); + countryEdit->addItem(QIcon(":/resources/countries/my.svg"), "my"); + countryEdit->addItem(QIcon(":/resources/countries/mz.svg"), "mz"); + countryEdit->addItem(QIcon(":/resources/countries/na.svg"), "na"); + countryEdit->addItem(QIcon(":/resources/countries/nc.svg"), "nc"); + countryEdit->addItem(QIcon(":/resources/countries/ne.svg"), "ne"); + countryEdit->addItem(QIcon(":/resources/countries/nf.svg"), "nf"); + countryEdit->addItem(QIcon(":/resources/countries/ng.svg"), "ng"); + countryEdit->addItem(QIcon(":/resources/countries/ni.svg"), "ni"); + countryEdit->addItem(QIcon(":/resources/countries/nl.svg"), "nl"); + countryEdit->addItem(QIcon(":/resources/countries/no.svg"), "no"); + countryEdit->addItem(QIcon(":/resources/countries/np.svg"), "np"); + countryEdit->addItem(QIcon(":/resources/countries/nr.svg"), "nr"); + countryEdit->addItem(QIcon(":/resources/countries/nu.svg"), "nu"); + countryEdit->addItem(QIcon(":/resources/countries/nz.svg"), "nz"); + countryEdit->addItem(QIcon(":/resources/countries/om.svg"), "om"); + countryEdit->addItem(QIcon(":/resources/countries/pa.svg"), "pa"); + countryEdit->addItem(QIcon(":/resources/countries/pe.svg"), "pe"); + countryEdit->addItem(QIcon(":/resources/countries/pf.svg"), "pf"); + countryEdit->addItem(QIcon(":/resources/countries/pg.svg"), "pg"); + countryEdit->addItem(QIcon(":/resources/countries/ph.svg"), "ph"); + countryEdit->addItem(QIcon(":/resources/countries/pk.svg"), "pk"); + countryEdit->addItem(QIcon(":/resources/countries/pl.svg"), "pl"); + countryEdit->addItem(QIcon(":/resources/countries/pm.svg"), "pm"); + countryEdit->addItem(QIcon(":/resources/countries/pn.svg"), "pn"); + countryEdit->addItem(QIcon(":/resources/countries/pr.svg"), "pr"); + countryEdit->addItem(QIcon(":/resources/countries/ps.svg"), "ps"); + countryEdit->addItem(QIcon(":/resources/countries/pt.svg"), "pt"); + countryEdit->addItem(QIcon(":/resources/countries/pw.svg"), "pw"); + countryEdit->addItem(QIcon(":/resources/countries/py.svg"), "py"); + countryEdit->addItem(QIcon(":/resources/countries/qa.svg"), "qa"); + countryEdit->addItem(QIcon(":/resources/countries/re.svg"), "re"); + countryEdit->addItem(QIcon(":/resources/countries/ro.svg"), "ro"); + countryEdit->addItem(QIcon(":/resources/countries/rs.svg"), "rs"); + countryEdit->addItem(QIcon(":/resources/countries/ru.svg"), "ru"); + countryEdit->addItem(QIcon(":/resources/countries/rw.svg"), "rw"); + countryEdit->addItem(QIcon(":/resources/countries/sa.svg"), "sa"); + countryEdit->addItem(QIcon(":/resources/countries/sb.svg"), "sb"); + countryEdit->addItem(QIcon(":/resources/countries/sc.svg"), "sc"); + countryEdit->addItem(QIcon(":/resources/countries/sd.svg"), "sd"); + countryEdit->addItem(QIcon(":/resources/countries/se.svg"), "se"); + countryEdit->addItem(QIcon(":/resources/countries/sg.svg"), "sg"); + countryEdit->addItem(QIcon(":/resources/countries/sh.svg"), "sh"); + countryEdit->addItem(QIcon(":/resources/countries/si.svg"), "si"); + countryEdit->addItem(QIcon(":/resources/countries/sj.svg"), "sj"); + countryEdit->addItem(QIcon(":/resources/countries/sk.svg"), "sk"); + countryEdit->addItem(QIcon(":/resources/countries/sl.svg"), "sl"); + countryEdit->addItem(QIcon(":/resources/countries/sm.svg"), "sm"); + countryEdit->addItem(QIcon(":/resources/countries/sn.svg"), "sn"); + countryEdit->addItem(QIcon(":/resources/countries/so.svg"), "so"); + countryEdit->addItem(QIcon(":/resources/countries/sr.svg"), "sr"); + countryEdit->addItem(QIcon(":/resources/countries/ss.svg"), "ss"); + countryEdit->addItem(QIcon(":/resources/countries/st.svg"), "st"); + countryEdit->addItem(QIcon(":/resources/countries/sv.svg"), "sv"); + countryEdit->addItem(QIcon(":/resources/countries/sx.svg"), "sx"); + countryEdit->addItem(QIcon(":/resources/countries/sy.svg"), "sy"); + countryEdit->addItem(QIcon(":/resources/countries/sz.svg"), "sz"); + countryEdit->addItem(QIcon(":/resources/countries/tc.svg"), "tc"); + countryEdit->addItem(QIcon(":/resources/countries/td.svg"), "td"); + countryEdit->addItem(QIcon(":/resources/countries/tf.svg"), "tf"); + countryEdit->addItem(QIcon(":/resources/countries/tg.svg"), "tg"); + countryEdit->addItem(QIcon(":/resources/countries/th.svg"), "th"); + countryEdit->addItem(QIcon(":/resources/countries/tj.svg"), "tj"); + countryEdit->addItem(QIcon(":/resources/countries/tk.svg"), "tk"); + countryEdit->addItem(QIcon(":/resources/countries/tl.svg"), "tl"); + countryEdit->addItem(QIcon(":/resources/countries/tm.svg"), "tm"); + countryEdit->addItem(QIcon(":/resources/countries/tn.svg"), "tn"); + countryEdit->addItem(QIcon(":/resources/countries/to.svg"), "to"); + countryEdit->addItem(QIcon(":/resources/countries/tr.svg"), "tr"); + countryEdit->addItem(QIcon(":/resources/countries/tt.svg"), "tt"); + countryEdit->addItem(QIcon(":/resources/countries/tv.svg"), "tv"); + countryEdit->addItem(QIcon(":/resources/countries/tw.svg"), "tw"); + countryEdit->addItem(QIcon(":/resources/countries/tz.svg"), "tz"); + countryEdit->addItem(QIcon(":/resources/countries/ua.svg"), "ua"); + countryEdit->addItem(QIcon(":/resources/countries/ug.svg"), "ug"); + countryEdit->addItem(QIcon(":/resources/countries/um.svg"), "um"); + countryEdit->addItem(QIcon(":/resources/countries/us.svg"), "us"); + countryEdit->addItem(QIcon(":/resources/countries/uy.svg"), "uy"); + countryEdit->addItem(QIcon(":/resources/countries/uz.svg"), "uz"); + countryEdit->addItem(QIcon(":/resources/countries/va.svg"), "va"); + countryEdit->addItem(QIcon(":/resources/countries/vc.svg"), "vc"); + countryEdit->addItem(QIcon(":/resources/countries/ve.svg"), "ve"); + countryEdit->addItem(QIcon(":/resources/countries/vg.svg"), "vg"); + countryEdit->addItem(QIcon(":/resources/countries/vi.svg"), "vi"); + countryEdit->addItem(QIcon(":/resources/countries/vn.svg"), "vn"); + countryEdit->addItem(QIcon(":/resources/countries/vu.svg"), "vu"); + countryEdit->addItem(QIcon(":/resources/countries/wf.svg"), "wf"); + countryEdit->addItem(QIcon(":/resources/countries/ws.svg"), "ws"); + countryEdit->addItem(QIcon(":/resources/countries/ye.svg"), "ye"); + countryEdit->addItem(QIcon(":/resources/countries/yt.svg"), "yt"); + countryEdit->addItem(QIcon(":/resources/countries/za.svg"), "za"); + countryEdit->addItem(QIcon(":/resources/countries/zm.svg"), "zm"); + countryEdit->addItem(QIcon(":/resources/countries/zw.svg"), "zw"); + countryEdit->setCurrentIndex(0); + + realnameLabel = new QLabel(tr("Real name:")); + realnameEdit = new QLineEdit(); + realnameLabel->setBuddy(realnameEdit); + + QGridLayout *grid = new QGridLayout; + grid->addWidget(hostLabel, 0, 0); + grid->addWidget(hostEdit, 0, 1); + grid->addWidget(portLabel, 1, 0); + grid->addWidget(portEdit, 1, 1); + grid->addWidget(playernameLabel, 2, 0); + grid->addWidget(playernameEdit, 2, 1); + grid->addWidget(passwordLabel, 3, 0); + grid->addWidget(passwordEdit, 3, 1); + grid->addWidget(emailLabel, 4, 0); + grid->addWidget(emailEdit, 4, 1); + grid->addWidget(genderLabel, 5, 0); + grid->addWidget(genderEdit, 5, 1); + grid->addWidget(countryLabel, 6, 0); + grid->addWidget(countryEdit, 6, 1); + grid->addWidget(realnameLabel, 7, 0); + grid->addWidget(realnameEdit, 7, 1); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(actCancel())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(grid); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + setWindowTitle(tr("Register to server")); + setFixedHeight(sizeHint().height()); + setMinimumWidth(300); +} + +void DlgRegister::actOk() +{ + QSettings settings; + settings.beginGroup("server"); + settings.setValue("hostname", hostEdit->text()); + settings.setValue("port", portEdit->text()); + settings.setValue("playername", playernameEdit->text()); + // always save the password so it will be picked up by the connect dialog + settings.setValue("password", passwordEdit->text()); + settings.endGroup(); + + accept(); +} + +void DlgRegister::actCancel() +{ + reject(); +} diff --git a/cockatrice/src/dlg_register.h b/cockatrice/src/dlg_register.h new file mode 100644 index 00000000..3ee7b105 --- /dev/null +++ b/cockatrice/src/dlg_register.h @@ -0,0 +1,33 @@ +#ifndef DLG_REGISTER_H +#define DLG_REGISTER_H + +#include +#include +#include + +class QLabel; +class QPushButton; +class QCheckBox; + +class DlgRegister : public QDialog { + Q_OBJECT +public: + DlgRegister(QWidget *parent = 0); + QString getHost() const { return hostEdit->text(); } + int getPort() const { return portEdit->text().toInt(); } + QString getPlayerName() const { return playernameEdit->text(); } + QString getPassword() const { return passwordEdit->text(); } + QString getEmail() const { return emailEdit->text(); } + int getGender() const { return genderEdit->currentIndex() - 1; } + QString getCountry() const { return genderEdit->currentIndex() == 0 ? "" : countryEdit->currentText(); } + QString getRealName() const { return realnameEdit->text(); } +private slots: + void actOk(); + void actCancel(); +private: + QLabel *hostLabel, *portLabel, *playernameLabel, *passwordLabel, *emailLabel, *genderLabel, *countryLabel, *realnameLabel; + QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit, *emailEdit, *realnameEdit; + QComboBox *genderEdit, *countryEdit; +}; + +#endif diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 4f8deaed..aae2df01 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -6,6 +6,7 @@ #include "pb/commands.pb.h" #include "pb/session_commands.pb.h" #include "pb/response_login.pb.h" +#include "pb/response_register.pb.h" #include "pb/server_message.pb.h" #include "pb/event_server_identification.pb.h" @@ -28,6 +29,7 @@ RemoteClient::RemoteClient(QObject *parent) connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, SLOT(processConnectionClosedEvent(Event_ConnectionClosed))); connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this, SLOT(doConnectToServer(QString, unsigned int, QString, QString))); connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer())); + connect(this, SIGNAL(sigRegisterToServer(QString, unsigned int, QString, QString, QString, int, QString, QString)), this, SLOT(doRegisterToServer(QString, unsigned int, QString, QString, QString, int, QString, QString))); } RemoteClient::~RemoteClient() @@ -52,8 +54,6 @@ void RemoteClient::slotConnected() sendCommandContainer(CommandContainer()); getNewCmdId(); // end of hack - - setStatus(StatusAwaitingWelcome); } void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event) @@ -63,6 +63,24 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica setStatus(StatusDisconnecting); return; } + + if(getStatus() == StatusRegistering) + { + Command_Register cmdRegister; + cmdRegister.set_user_name(userName.toStdString()); + cmdRegister.set_password(password.toStdString()); + cmdRegister.set_email(email.toStdString()); + cmdRegister.set_gender((ServerInfo_User_Gender) gender); + cmdRegister.set_country(country.toStdString()); + cmdRegister.set_real_name(realName.toStdString()); + + PendingCommand *pend = prepareSessionCommand(cmdRegister); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); + sendCommand(pend); + + return; + } + setStatus(StatusLoggingIn); Command_Login cmdLogin; @@ -101,6 +119,18 @@ void RemoteClient::loginResponse(const Response &response) } } +void RemoteClient::registerResponse(const Response &response) +{ + const Response_Register &resp = response.GetExtension(Response_Register::ext); + if (response.response_code() == Response::RespRegistrationAccepted) { + emit registerAccepted(); + } else { + emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); + } + setStatus(StatusDisconnecting); + doDisconnectFromServer(); +} + void RemoteClient::readData() { lastDataReceived = timeRunning; @@ -175,6 +205,21 @@ void RemoteClient::doConnectToServer(const QString &hostname, unsigned int port, setStatus(StatusConnecting); } +void RemoteClient::doRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname) +{ + doDisconnectFromServer(); + + userName = _userName; + password = _password; + email = _email; + gender = _gender; + country = _country; + realName = _realname; + + socket->connectToHost(hostname, port); + setStatus(StatusRegistering); +} + void RemoteClient::doDisconnectFromServer() { timer->stop(); @@ -225,6 +270,11 @@ void RemoteClient::connectToServer(const QString &hostname, unsigned int port, c emit sigConnectToServer(hostname, port, _userName, _password); } +void RemoteClient::registerToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname) +{ + emit sigRegisterToServer(hostname, port, _userName, _password, _email, _gender, _country, _realname); +} + void RemoteClient::disconnectFromServer() { emit sigDisconnectFromServer(); diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index 2cb84e90..6359f6f4 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -12,10 +12,12 @@ signals: void maxPingTime(int seconds, int maxSeconds); void serverTimeout(); void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); + void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); void socketError(const QString &errorString); void protocolVersionMismatch(int clientVersion, int serverVersion); void protocolError(); void sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void sigRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); void sigDisconnectFromServer(); private slots: void slotConnected(); @@ -25,7 +27,9 @@ private slots: void processServerIdentificationEvent(const Event_ServerIdentification &event); void processConnectionClosedEvent(const Event_ConnectionClosed &event); void loginResponse(const Response &response); + void registerResponse(const Response &response); void doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void doRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); void doDisconnectFromServer(); private: static const int maxTimeout = 10; @@ -45,6 +49,8 @@ public: ~RemoteClient(); QString peerName() const { return socket->peerName(); } void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void registerToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); + void disconnectFromServer(); }; diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index e6f88e95..c19f1c9d 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -34,6 +34,7 @@ #include "main.h" #include "window_main.h" #include "dlg_connect.h" +#include "dlg_register.h" #include "dlg_settings.h" #include "tab_supervisor.h" #include "remoteclient.h" @@ -99,8 +100,6 @@ void MainWindow::statusChanged(ClientStatus _status) { setClientStatusTitle(); switch (_status) { - case StatusConnecting: - break; case StatusDisconnected: tabSupervisor->stop(); aSinglePlayer->setEnabled(true); @@ -112,8 +111,9 @@ void MainWindow::statusChanged(ClientStatus _status) aConnect->setEnabled(false); aDisconnect->setEnabled(true); break; + case StatusConnecting: + case StatusRegistering: case StatusLoggedIn: - break; default: break; } @@ -124,6 +124,12 @@ void MainWindow::userInfoReceived(const ServerInfo_User &info) tabSupervisor->start(info); } +void MainWindow::registerAccepted() +{ + QMessageBox::information(this, tr("Success"), tr("Registration accepted.\nNow check your email for instructions on how to activate your account.")); + actConnect(); +} + // Actions void MainWindow::actConnect() @@ -133,6 +139,24 @@ void MainWindow::actConnect() client->connectToServer(dlg.getHost(), dlg.getPort(), dlg.getPlayerName(), dlg.getPassword()); } +void MainWindow::actRegister() +{ + DlgRegister dlg(this); + if (dlg.exec()) + { + client->registerToServer( + dlg.getHost(), + dlg.getPort(), + dlg.getPlayerName(), + dlg.getPassword(), + dlg.getEmail(), + dlg.getGender(), + dlg.getCountry(), + dlg.getRealName() + ); + } +} + void MainWindow::actDisconnect() { client->disconnectFromServer(); @@ -271,7 +295,12 @@ void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 QMessageBox::critical(this, tr("Error"), tr("Invalid username.\nYou may only use A-Z, a-z, 0-9, _, ., and - in your username.")); break; case Response::RespRegistrationRequired: - QMessageBox::critical(this, tr("Error"), tr("This server requires user registration.")); + if (QMessageBox::question(this, tr("Error"), tr("This server requires user registration. Do you want to register now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + actRegister(); + } + break; + case Response::RespAccountNotActivated: + QMessageBox::critical(this, tr("Error"), tr("Your account has not been activated yet.")); break; default: QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); @@ -279,6 +308,48 @@ void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 actConnect(); } +void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime) +{ + switch (r) { + case Response::RespRegistrationDisabled: + QMessageBox::critical(this, tr("Registration denied"), tr("Registration is currently disabled on this server")); + break; + case Response::RespUserAlreadyExists: + QMessageBox::critical(this, tr("Registration denied"), tr("There is already an existing account with the same user name.")); + break; + case Response::RespEmailRequiredToRegister: + QMessageBox::critical(this, tr("Registration denied"), tr("It's mandatory to specify an email when registering.")); + break; + case Response::RespTooManyRequests: + QMessageBox::critical(this, tr("Registration denied"), tr("Too many registration attempts from your IP address.")); + break; + case Response::RespPasswordTooShort: + QMessageBox::critical(this, tr("Registration denied"), tr("Password too short.")); + break; + case Response::RespUserIsBanned: { + QString bannedStr; + if (endTime) + bannedStr = tr("You are banned until %1.").arg(QDateTime::fromTime_t(endTime).toString()); + else + bannedStr = tr("You are banned indefinitely."); + if (!reasonStr.isEmpty()) + bannedStr.append("\n\n" + reasonStr); + + QMessageBox::critical(this, tr("Error"), bannedStr); + break; + } + case Response::RespUsernameInvalid: + QMessageBox::critical(this, tr("Error"), tr("Invalid username.\nYou may only use A-Z, a-z, 0-9, _, ., and - in your username.")); + break; + case Response::RespRegistrationFailed: + QMessageBox::critical(this, tr("Error"), tr("Registration failed for a technical problem on the server.")); + break; + default: + QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); + } + actRegister(); +} + void MainWindow::socketError(const QString &errorStr) { QMessageBox::critical(this, tr("Error"), tr("Socket error: %1").arg(errorStr)); @@ -297,6 +368,7 @@ void MainWindow::setClientStatusTitle() { switch (client->getStatus()) { case StatusConnecting: setWindowTitle(appName + " - " + tr("Connecting to %1...").arg(client->peerName())); break; + case StatusRegistering: setWindowTitle(appName + " - " + tr("Registering to %1 as %2...").arg(client->peerName()).arg(client->getUserName())); break; case StatusDisconnected: setWindowTitle(appName + " - " + tr("Disconnected")); break; case StatusLoggingIn: setWindowTitle(appName + " - " + tr("Connected, logging in at %1").arg(client->peerName())); break; case StatusLoggedIn: setWindowTitle(appName + " - " + tr("Logged in as %1 at %2").arg(client->getUserName()).arg(client->peerName())); break; @@ -315,6 +387,7 @@ void MainWindow::retranslateUi() aDeckEditor->setText(tr("&Deck editor")); aFullScreen->setText(tr("&Full screen")); aFullScreen->setShortcut(QKeySequence("Ctrl+F")); + aRegister->setText(tr("&Register to server...")); aSettings->setText(tr("&Settings...")); aExit->setText(tr("&Exit")); @@ -346,6 +419,8 @@ void MainWindow::createActions() aFullScreen = new QAction(this); aFullScreen->setCheckable(true); connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); + aRegister = new QAction(this); + connect(aRegister, SIGNAL(triggered()), this, SLOT(actRegister())); aSettings = new QAction(this); connect(aSettings, SIGNAL(triggered()), this, SLOT(actSettings())); aExit = new QAction(this); @@ -378,6 +453,7 @@ void MainWindow::createMenus() cockatriceMenu = menuBar()->addMenu(QString()); cockatriceMenu->addAction(aConnect); cockatriceMenu->addAction(aDisconnect); + cockatriceMenu->addAction(aRegister); cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aSinglePlayer); cockatriceMenu->addAction(aWatchReplay); @@ -410,7 +486,10 @@ MainWindow::MainWindow(QWidget *parent) connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); - + + connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted())); + connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, SLOT(registerError(Response::ResponseCode, QString, quint32))); + clientThread = new QThread(this); client->moveToThread(clientThread); clientThread->start(); diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 1087d2e7..6b1660a7 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -43,9 +43,11 @@ private slots: void processServerShutdownEvent(const Event_ServerShutdown &event); void serverTimeout(); void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); + void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime); void socketError(const QString &errorStr); void protocolVersionMismatch(int localVersion, int remoteVersion); void userInfoReceived(const ServerInfo_User &userInfo); + void registerAccepted(); void localGameEnded(); void pixmapCacheSizeChanged(int newSizeInMBs); @@ -55,6 +57,7 @@ private slots: void actWatchReplay(); void actDeckEditor(); void actFullScreen(bool checked); + void actRegister(); void actSettings(); void actExit(); @@ -82,7 +85,7 @@ private: QList tabMenus; QMenu *cockatriceMenu, *helpMenu; QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, - *aAbout, *aCheckCardUpdates; + *aAbout, *aCheckCardUpdates, *aRegister; TabSupervisor *tabSupervisor; QMenu *trayIconMenu; diff --git a/common/pb/response.proto b/common/pb/response.proto index 302b1801..ccd09389 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -27,8 +27,8 @@ message Response { RespRegistrationAccepted = 23; // Server agrees to process client's registration request RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered RespEmailRequiredToRegister = 25; // Server requires email to register accounts but client did not provide one - RespServerDoesNotUseAuth = 26; // Client attempted to register but server does not use authentication - RespTooManyRequests = 27; // Server refused to complete command because client has sent too many too quickly + RespTooManyRequests = 26; // Server refused to complete command because client has sent too many too quickly + RespPasswordTooShort = 27; // Server requires a decent password RespAccountNotActivated = 28; // Client attempted to log into a registered username but the account hasn't been activated RespRegistrationDisabled = 29; // Server does not allow clients to register RespRegistrationFailed = 30; // Server accepted a reg request but failed to perform the registration diff --git a/common/server.cpp b/common/server.cpp index 7fd083e2..6ca0fd04 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -113,7 +113,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString QWriteLocker locker(&clientsLock); AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); - if ((authState == NotLoggedIn) || (authState == UserIsBanned || authState == UsernameInvalid)) + if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid || authState == UserIsInactive) return authState; ServerInfo_User data = databaseInterface->getUserData(name, true); @@ -140,7 +140,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString QString tempName = name; int i = 0; - while (users.contains(tempName) || databaseInterface->userExists(tempName) || databaseInterface->userSessionExists(tempName)) + while (users.contains(tempName) || databaseInterface->activeUserExists(tempName) || databaseInterface->userSessionExists(tempName)) tempName = name + "_" + QString::number(++i); name = tempName; data.set_name(name.toStdString()); @@ -177,8 +177,6 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString RegistrationResult Server::registerUserAccount(const QString &ipAddress, const Command_Register &cmd, QString &banReason, int &banSecondsRemaining) { - // TODO - if (!registrationEnabled) return RegistrationDisabled; @@ -193,6 +191,9 @@ RegistrationResult Server::registerUserAccount(const QString &ipAddress, const C if (!databaseInterface->usernameIsValid(userName)) return InvalidUsername; + if(databaseInterface->userExists(userName)) + return UserAlreadyExists; + if (databaseInterface->checkUserIsBanned(ipAddress, userName, banReason, banSecondsRemaining)) return ClientIsBanned; @@ -202,8 +203,12 @@ RegistrationResult Server::registerUserAccount(const QString &ipAddress, const C QString realName = QString::fromStdString(cmd.real_name()); ServerInfo_User_Gender gender = cmd.gender(); QString country = QString::fromStdString(cmd.country()); - QString passwordSha512 = QString::fromStdString(cmd.password()); - bool regSucceeded = databaseInterface->registerUser(userName, realName, gender, passwordSha512, emailAddress, country, false); + QString password = QString::fromStdString(cmd.password()); + + if(password.length() < 6) + return PasswordTooShort; + + bool regSucceeded = databaseInterface->registerUser(userName, realName, gender, password, emailAddress, country, false); return regSucceeded ? Accepted : Failed; } diff --git a/common/server.h b/common/server.h index ca039350..736ef582 100644 --- a/common/server.h +++ b/common/server.h @@ -28,8 +28,8 @@ class GameEventContainer; class CommandContainer; class Command_JoinGame; -enum AuthenticationResult { NotLoggedIn = 0, PasswordRight = 1, UnknownUser = 2, WouldOverwriteOldSession = 3, UserIsBanned = 4, UsernameInvalid = 5, RegistrationRequired = 6 }; -enum RegistrationResult { Accepted = 0, UserAlreadyExists = 1, EmailRequired = 2, UnauthenticatedServer = 3, TooManyRequests = 4, InvalidUsername = 5, ClientIsBanned = 6, RegistrationDisabled = 7, Failed = 8}; +enum AuthenticationResult { NotLoggedIn, PasswordRight, UnknownUser, WouldOverwriteOldSession, UserIsBanned, UsernameInvalid, RegistrationRequired, UserIsInactive }; +enum RegistrationResult { Accepted, UserAlreadyExists, EmailRequired, TooManyRequests, InvalidUsername, ClientIsBanned, RegistrationDisabled, Failed, PasswordTooShort }; class Server : public QObject { diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 2568f195..90b072fc 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -14,6 +14,7 @@ public: virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; virtual bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) { return false; } + virtual bool activeUserExists(const QString & /* user */) { return false; } virtual bool userExists(const QString & /* user */) { return false; } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } virtual QMap getIgnoreList(const QString & /* name */) { return QMap(); } @@ -37,7 +38,7 @@ public: virtual bool userSessionExists(const QString & /* userName */) { return false; } virtual bool getRequireRegistration() { return false; } - virtual bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &passwordSha512, const QString &emailAddress, const QString &country, bool active = false) { return false; } + virtual bool registerUser(const QString & /* userName */, const QString & /* realName */, ServerInfo_User_Gender const & /* gender */, const QString & /* password */, const QString & /* emailAddress */, const QString & /* country */, bool /* active = false */) { return false; } enum LogMessage_TargetType { MessageTargetRoom, MessageTargetGame, MessageTargetChat, MessageTargetIslRoom }; virtual void logMessage(const int /* senderId */, const QString & /* senderName */, const QString & /* senderIp */, const QString & /* logMessage */, LogMessage_TargetType /* targetType */, const int /* targetId */, const QString & /* targetName */) { }; diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index cc5dd7f9..9a213108 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -392,6 +392,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd case WouldOverwriteOldSession: return Response::RespWouldOverwriteOldSession; case UsernameInvalid: return Response::RespUsernameInvalid; case RegistrationRequired: return Response::RespRegistrationRequired; + case UserIsInactive: return Response::RespAccountNotActivated; default: authState = res; } @@ -442,10 +443,10 @@ Response::ResponseCode Server_ProtocolHandler::cmdRegisterAccount(const Command_ return Response::RespUserAlreadyExists; case EmailRequired: return Response::RespEmailRequiredToRegister; - case UnauthenticatedServer: - return Response::RespServerDoesNotUseAuth; case TooManyRequests: return Response::RespTooManyRequests; + case PasswordTooShort: + return Response::RespPasswordTooShort; case InvalidUsername: return Response::RespUsernameInvalid; case Failed: diff --git a/servatrice/scripts/mk_pypb.sh b/servatrice/scripts/mk_pypb.sh deleted file mode 100755 index e11d237d..00000000 --- a/servatrice/scripts/mk_pypb.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -SRC_DIR=../../common/pb/ -DST_DIR=./pypb - -rm -rf "$DST_DIR" -mkdir -p "$DST_DIR" -protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/*.proto -touch "$DST_DIR/__init__.py" - diff --git a/servatrice/scripts/register.py b/servatrice/scripts/register.py deleted file mode 100755 index 427c9e72..00000000 --- a/servatrice/scripts/register.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python - -import socket, sys, struct, time - -from pypb.server_message_pb2 import ServerMessage -from pypb.session_commands_pb2 import Command_Register as Reg -from pypb.commands_pb2 import CommandContainer as Cmd -from pypb.event_server_identification_pb2 import Event_ServerIdentification as ServerId -from pypb.response_pb2 import Response - -HOST = "localhost" -PORT = 4747 - -CMD_ID = 1 - -def build_reg(): - global CMD_ID - cmd = Cmd() - sc = cmd.session_command.add() - - reg = sc.Extensions[Reg.ext] - reg.user_name = "testUser" - reg.email = "test@example.com" - reg.password = "password" - - cmd.cmd_id = CMD_ID - CMD_ID += 1 - return cmd - -def send(msg): - packed = struct.pack('>I', len(msg)) - sock.sendall(packed) - sock.sendall(msg) - -def print_resp(resp): - print "<<<" - print repr(resp) - m = ServerMessage() - m.ParseFromString(bytes(resp)) - print m - -def recv(sock): - print "< header" - header = sock.recv(4) - msg_size = struct.unpack('>I', header)[0] - print "< ", msg_size - raw_msg = sock.recv(msg_size) - print_resp(raw_msg) - -if __name__ == "__main__": - address = (HOST, PORT) - sock = socket.socket() - - print "Connecting to server ", address - sock.connect(address) - - # hack for old xml clients - server expects this and discards first message - print ">>> xml hack" - xmlClientHack = Cmd().SerializeToString() - send(xmlClientHack) - print sock.recv(60) - - recv(sock) - - print ">>> register" - r = build_reg() - print r - msg = r.SerializeToString() - send(msg) - recv(sock) - - print "Done" - diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index f75a8958..d9cc7629 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -59,9 +59,11 @@ regonly=0 ; Servatrice can process registration requests to add new users on the fly. ; Enable this feature? Default false. ;enabled=false + ; Require users to provide an email address in order to register. Default true. ;requireemail=true + [database] ; Database type. Valid values are: diff --git a/servatrice/src/passwordhasher.cpp b/servatrice/src/passwordhasher.cpp index 554c8f2f..b02d8e9b 100644 --- a/servatrice/src/passwordhasher.cpp +++ b/servatrice/src/passwordhasher.cpp @@ -8,6 +8,8 @@ #include #endif +#include "rng_sfmt.h" + void PasswordHasher::initialize() { #if QT_VERSION < 0x050000 @@ -51,3 +53,20 @@ QString PasswordHasher::computeHash(const QString &password, const QString &salt return hashedPass; } #endif + +QString PasswordHasher::generateRandomSalt(const int len) +{ + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + QString ret; + int size = sizeof(alphanum) - 1; + + for (int i = 0; i < len; ++i) { + ret.append(alphanum[rng->rand(0, size)]); + } + + return ret; +} diff --git a/servatrice/src/passwordhasher.h b/servatrice/src/passwordhasher.h index 0cb6744c..ffe21a9d 100644 --- a/servatrice/src/passwordhasher.h +++ b/servatrice/src/passwordhasher.h @@ -7,6 +7,7 @@ class PasswordHasher { public: static void initialize(); static QString computeHash(const QString &password, const QString &salt); + static QString generateRandomSalt(const int len = 16); }; #endif diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index aabf2a2a..fd6b00ea 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -130,7 +130,6 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const Q if (!execSqlQuery(query)) { qDebug() << "Failed to insert user: " << query->lastError() << " sql: " << query->lastQuery(); - // TODO handle duplicate insert error return false; } @@ -172,7 +171,6 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) return UserIsBanned; - QSqlQuery *passwordQuery = prepareQuery("select password_sha512 from {prefix}_users where name = :name and active = 1"); passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { qDebug("Login denied: SQL error"); @@ -270,7 +268,6 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, return false; } -bool Servatrice_DatabaseInterface::userExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index d352ba7a..4e077dac 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "server.h" #include "server_database_interface.h" @@ -41,6 +41,7 @@ public: bool execSqlQuery(QSqlQuery *query); const QSqlDatabase &getDatabase() { return sqlDatabase; } + bool activeUserExists(const QString &user); bool userExists(const QString &user); int getUserIdInDB(const QString &name); QMap getBuddyList(const QString &name); @@ -63,7 +64,7 @@ public: bool userSessionExists(const QString &userName); bool getRequireRegistration(); - bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &passwordSha512, const QString &emailAddress, const QString &country, bool active = false); + bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, bool active = false); void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); }; From d7b6f7619107d2b183bab96087d0277333e91ec7 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sat, 23 May 2015 20:13:21 +0200 Subject: [PATCH 03/12] missing file from previous commit --- .../src/servatrice_database_interface.cpp | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index fd6b00ea..26d9a4f5 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -111,11 +111,13 @@ bool Servatrice_DatabaseInterface::getRequireRegistration() return settingsCache->value("authentication/regonly", 0).toBool(); } -bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &passwordSha512, const QString &emailAddress, const QString &country, bool active) +bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, bool active) { if (!checkSql()) return false; + QString passwordSha512 = PasswordHasher::computeHash(password, PasswordHasher::generateRandomSalt()); + QSqlQuery *query = prepareQuery("insert into {prefix}_users " "(name, realname, gender, password_sha512, email, country, registrationDate, active) " "values " @@ -171,6 +173,7 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) return UserIsBanned; + QSqlQuery *passwordQuery = prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { qDebug("Login denied: SQL error"); @@ -179,6 +182,11 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (passwordQuery->next()) { const QString correctPassword = passwordQuery->value(0).toString(); + const bool userIsActive = passwordQuery->value(1).toBool(); + if(!userIsActive) { + qDebug("Login denied: user not active"); + return UserIsInactive; + } if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { qDebug("Login accepted: password right"); return PasswordRight; @@ -268,6 +276,7 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, return false; } +bool Servatrice_DatabaseInterface::activeUserExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); @@ -281,6 +290,20 @@ bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, return false; } +bool Servatrice_DatabaseInterface::userExists(const QString &user) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery *query = prepareQuery("select 1 from {prefix}_users where name = :name"); + query->bindValue(":name", user); + if (!execSqlQuery(query)) + return false; + return query->next(); + } + return false; +} + int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { From 16d9534757cc28e28275114682495b8919c34da2 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sat, 23 May 2015 21:09:29 +0200 Subject: [PATCH 04/12] fix for -Werror=unused-parameter --- common/server.cpp | 1 + common/server_database_interface.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/server.cpp b/common/server.cpp index 6ca0fd04..48dd54e2 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -216,6 +216,7 @@ RegistrationResult Server::registerUserAccount(const QString &ipAddress, const C bool Server::tooManyRegistrationAttempts(const QString &ipAddress) { // TODO: implement + Q_UNUSED(ipAddress); return false; } diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 90b072fc..b97d1021 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -13,7 +13,7 @@ public: : QObject(parent) { } virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; - virtual bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) { return false; } + virtual bool checkUserIsBanned(const QString & /* ipAddress */, const QString & /* userName */, QString & /* banReason */, int & /* banSecondsRemaining */) { return false; } virtual bool activeUserExists(const QString & /* user */) { return false; } virtual bool userExists(const QString & /* user */) { return false; } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } From 42796b0d0ede41bf810922de41424ea1010ebd8b Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sat, 23 May 2015 21:17:22 +0200 Subject: [PATCH 05/12] More fix for -Werror=unused-parameter --- common/server_database_interface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/server_database_interface.h b/common/server_database_interface.h index b97d1021..0cabe867 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -25,7 +25,7 @@ public: virtual DeckList *getDeckFromDatabase(int /* deckId */, int /* userId */) { return 0; } virtual qint64 startSession(const QString & /* userName */, const QString & /* address */) { return 0; } - virtual bool usernameIsValid(const QString &userName) { return true; }; + virtual bool usernameIsValid(const QString & /*userName */) { return true; }; public slots: virtual void endSession(qint64 /* sessionId */ ) { } public: From ff1aed717eb0d4ff5190ab87f3d73f1df06281ff Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 24 May 2015 00:37:45 +0200 Subject: [PATCH 06/12] Added token generation, user activation command and response. --- cockatrice/src/abstractclient.h | 5 +- cockatrice/src/remoteclient.cpp | 73 +++++++++++++++++-- cockatrice/src/remoteclient.h | 10 ++- cockatrice/src/window_main.cpp | 36 ++++++++- cockatrice/src/window_main.h | 3 + common/pb/CMakeLists.txt | 1 + common/pb/response.proto | 4 + common/pb/response_activate.proto | 7 ++ common/pb/session_commands.proto | 12 +++ common/server.cpp | 17 ++++- common/server.h | 3 +- common/server_database_interface.h | 1 + common/server_protocolhandler.cpp | 18 ++++- common/server_protocolhandler.h | 1 + servatrice/servatrice.ini.example | 4 +- servatrice/src/passwordhasher.cpp | 5 ++ servatrice/src/passwordhasher.h | 1 + .../src/servatrice_database_interface.cpp | 40 +++++++++- .../src/servatrice_database_interface.h | 1 + 19 files changed, 223 insertions(+), 19 deletions(-) create mode 100644 common/pb/response_activate.proto diff --git a/cockatrice/src/abstractclient.h b/cockatrice/src/abstractclient.h index e683e3ba..df029a1e 100644 --- a/cockatrice/src/abstractclient.h +++ b/cockatrice/src/abstractclient.h @@ -30,6 +30,7 @@ enum ClientStatus { StatusDisconnecting, StatusConnecting, StatusRegistering, + StatusActivating, StatusLoggingIn, StatusLoggedIn, }; @@ -60,6 +61,8 @@ signals: void ignoreListReceived(const QList &ignoreList); void replayAddedEventReceived(const Event_ReplayAdded &event); void registerAccepted(); + void registerAcceptedNeedsActivate(); + void activateAccepted(); void sigQueuePendingCommand(PendingCommand *pend); private: @@ -72,7 +75,7 @@ protected slots: void processProtocolItem(const ServerMessage &item); protected: QMap pendingCommands; - QString userName, password, email, country, realName; + QString userName, password, email, country, realName, token; int gender; void setStatus(ClientStatus _status); int getNewCmdId() { return nextCmdId++; } diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index aae2df01..13d75a04 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -7,6 +7,7 @@ #include "pb/session_commands.pb.h" #include "pb/response_login.pb.h" #include "pb/response_register.pb.h" +#include "pb/response_activate.pb.h" #include "pb/server_message.pb.h" #include "pb/event_server_identification.pb.h" @@ -30,6 +31,7 @@ RemoteClient::RemoteClient(QObject *parent) connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this, SLOT(doConnectToServer(QString, unsigned int, QString, QString))); connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer())); connect(this, SIGNAL(sigRegisterToServer(QString, unsigned int, QString, QString, QString, int, QString, QString)), this, SLOT(doRegisterToServer(QString, unsigned int, QString, QString, QString, int, QString, QString))); + connect(this, SIGNAL(sigActivateToServer(QString)), this, SLOT(doActivateToServer(QString))); } RemoteClient::~RemoteClient() @@ -81,6 +83,24 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica return; } + if(getStatus() == StatusActivating) + { + Command_Activate cmdActivate; + cmdActivate.set_user_name(userName.toStdString()); + cmdActivate.set_token(token.toStdString()); + + PendingCommand *pend = prepareSessionCommand(cmdActivate); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(activateResponse(Response))); + sendCommand(pend); + + return; + } + + doLogin(); +} + +void RemoteClient::doLogin() +{ setStatus(StatusLoggingIn); Command_Login cmdLogin; @@ -122,13 +142,34 @@ void RemoteClient::loginResponse(const Response &response) void RemoteClient::registerResponse(const Response &response) { const Response_Register &resp = response.GetExtension(Response_Register::ext); - if (response.response_code() == Response::RespRegistrationAccepted) { - emit registerAccepted(); - } else { - emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); + switch(response.response_code()) + { + case Response::RespRegistrationAccepted: + emit registerAccepted(); + doLogin(); + break; + case Response::RespRegistrationAcceptedNeedsActivation: + emit registerAcceptedNeedsActivate(); + doLogin(); + break; + default: + emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); + setStatus(StatusDisconnecting); + doDisconnectFromServer(); + break; + } +} + +void RemoteClient::activateResponse(const Response &response) +{ + const Response_Activate &resp = response.GetExtension(Response_Activate::ext); + if (response.response_code() == Response::RespActivationAccepted) { + emit activateAccepted(); + + doLogin(); + } else { + emit activateError(); } - setStatus(StatusDisconnecting); - doDisconnectFromServer(); } void RemoteClient::readData() @@ -201,6 +242,9 @@ void RemoteClient::doConnectToServer(const QString &hostname, unsigned int port, userName = _userName; password = _password; + lastHostname = hostname; + lastPort = port; + socket->connectToHost(hostname, port); setStatus(StatusConnecting); } @@ -215,11 +259,23 @@ void RemoteClient::doRegisterToServer(const QString &hostname, unsigned int port gender = _gender; country = _country; realName = _realname; + lastHostname = hostname; + lastPort = port; socket->connectToHost(hostname, port); setStatus(StatusRegistering); } +void RemoteClient::doActivateToServer(const QString &_token) +{ + doDisconnectFromServer(); + + token = _token; + + socket->connectToHost(lastHostname, lastPort); + setStatus(StatusActivating); +} + void RemoteClient::doDisconnectFromServer() { timer->stop(); @@ -275,6 +331,11 @@ void RemoteClient::registerToServer(const QString &hostname, unsigned int port, emit sigRegisterToServer(hostname, port, _userName, _password, _email, _gender, _country, _realname); } +void RemoteClient::activateToServer(const QString &_token) +{ + emit sigActivateToServer(_token); +} + void RemoteClient::disconnectFromServer() { emit sigDisconnectFromServer(); diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index 6359f6f4..c1478732 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -13,11 +13,13 @@ signals: void serverTimeout(); void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); + void activateError(); void socketError(const QString &errorString); void protocolVersionMismatch(int clientVersion, int serverVersion); void protocolError(); void sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); void sigRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); + void sigActivateToServer(const QString &_token); void sigDisconnectFromServer(); private slots: void slotConnected(); @@ -28,9 +30,13 @@ private slots: void processConnectionClosedEvent(const Event_ConnectionClosed &event); void loginResponse(const Response &response); void registerResponse(const Response &response); + void activateResponse(const Response &response); void doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); void doRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); + void doLogin(); void doDisconnectFromServer(); + void doActivateToServer(const QString &_token); + private: static const int maxTimeout = 10; int timeRunning, lastDataReceived; @@ -42,6 +48,8 @@ private: QTimer *timer; QTcpSocket *socket; + QString lastHostname; + int lastPort; protected slots: void sendCommandContainer(const CommandContainer &cont); public: @@ -50,7 +58,7 @@ public: QString peerName() const { return socket->peerName(); } void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); void registerToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); - + void activateToServer(const QString &_token); void disconnectFromServer(); }; diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index c19f1c9d..896d7821 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -126,8 +126,17 @@ void MainWindow::userInfoReceived(const ServerInfo_User &info) void MainWindow::registerAccepted() { - QMessageBox::information(this, tr("Success"), tr("Registration accepted.\nNow check your email for instructions on how to activate your account.")); - actConnect(); + QMessageBox::information(this, tr("Success"), tr("Registration accepted.\nWill now login.")); +} + +void MainWindow::registerAcceptedNeedsActivate() +{ + // nothing +} + +void MainWindow::activateAccepted() +{ + QMessageBox::information(this, tr("Success"), tr("Account activation accepted.\nWill now login.")); } // Actions @@ -299,11 +308,20 @@ void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 actRegister(); } break; - case Response::RespAccountNotActivated: - QMessageBox::critical(this, tr("Error"), tr("Your account has not been activated yet.")); + case Response::RespAccountNotActivated: { + bool ok = false; + QString token = QInputDialog::getText(this, tr("Account activation"), tr("Your account has not been activated yet.\n You need to provide the activation token received in the activation email"), QLineEdit::Normal, QString(), &ok); + if(ok && !token.isEmpty()) + { + client->activateToServer(token); + return; + } + client->disconnectFromServer(); break; + } default: QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); + break; } actConnect(); } @@ -350,6 +368,13 @@ void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quin actRegister(); } +void MainWindow::activateError() +{ + QMessageBox::critical(this, tr("Error"), tr("Account activation failed")); + client->disconnectFromServer(); + actConnect(); +} + void MainWindow::socketError(const QString &errorStr) { QMessageBox::critical(this, tr("Error"), tr("Socket error: %1").arg(errorStr)); @@ -488,7 +513,10 @@ MainWindow::MainWindow(QWidget *parent) connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted())); + connect(client, SIGNAL(registerAcceptedNeedsActivate()), this, SLOT(registerAcceptedNeedsActivate())); connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, SLOT(registerError(Response::ResponseCode, QString, quint32))); + connect(client, SIGNAL(activateAccepted()), this, SLOT(activateAccepted())); + connect(client, SIGNAL(activateError()), this, SLOT(activateError())); clientThread = new QThread(this); client->moveToThread(clientThread); diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 6b1660a7..c60a2db3 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -44,10 +44,13 @@ private slots: void serverTimeout(); void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime); + void activateError(); void socketError(const QString &errorStr); void protocolVersionMismatch(int localVersion, int remoteVersion); void userInfoReceived(const ServerInfo_User &userInfo); void registerAccepted(); + void registerAcceptedNeedsActivate(); + void activateAccepted(); void localGameEnded(); void pixmapCacheSizeChanged(int newSizeInMBs); diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt index e698c95f..db5cd130 100644 --- a/common/pb/CMakeLists.txt +++ b/common/pb/CMakeLists.txt @@ -114,6 +114,7 @@ SET(PROTO_FILES isl_message.proto moderator_commands.proto move_card_to_zone.proto + response_activate.proto response_deck_download.proto response_deck_list.proto response_deck_upload.proto diff --git a/common/pb/response.proto b/common/pb/response.proto index ccd09389..4955bed5 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -32,6 +32,9 @@ message Response { RespAccountNotActivated = 28; // Client attempted to log into a registered username but the account hasn't been activated RespRegistrationDisabled = 29; // Server does not allow clients to register RespRegistrationFailed = 30; // Server accepted a reg request but failed to perform the registration + RespActivationAccepted = 31; // Server accepted a reg user activation token + RespActivationFailed = 32; // Server didn't accept a reg user activation token + RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation } enum ResponseType { JOIN_ROOM = 1000; @@ -44,6 +47,7 @@ message Response { DECK_DOWNLOAD = 1007; DECK_UPLOAD = 1008; REGISTER = 1009; + ACTIVATE = 1010; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; } diff --git a/common/pb/response_activate.proto b/common/pb/response_activate.proto new file mode 100644 index 00000000..25667534 --- /dev/null +++ b/common/pb/response_activate.proto @@ -0,0 +1,7 @@ +import "response.proto"; + +message Response_Activate { + extend Response { + optional Response_Activate ext = 1010; + } +} \ No newline at end of file diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index be5cc1db..11e5ce6f 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -19,6 +19,7 @@ message SessionCommand { LIST_ROOMS = 1014; JOIN_ROOM = 1015; REGISTER = 1016; + ACTIVATE = 1017; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; REPLAY_MODIFY_MATCH = 1102; @@ -115,3 +116,14 @@ message Command_Register { optional string country = 5; optional string real_name = 6; } + +// User wants to activate an account +message Command_Activate { + extend SessionCommand { + optional Command_Activate ext = 1017; + } + // User name client wants to activate + required string user_name = 1; + // Activation token + required string token = 2; +} diff --git a/common/server.cpp b/common/server.cpp index 48dd54e2..1e3991c3 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -208,9 +208,22 @@ RegistrationResult Server::registerUserAccount(const QString &ipAddress, const C if(password.length() < 6) return PasswordTooShort; - bool regSucceeded = databaseInterface->registerUser(userName, realName, gender, password, emailAddress, country, false); + bool regSucceeded = databaseInterface->registerUser(userName, realName, gender, password, emailAddress, country, !requireEmailForRegistration); - return regSucceeded ? Accepted : Failed; + if(regSucceeded) + return requireEmailForRegistration ? AcceptedNeedsActivation : Accepted; + else + return Failed; +} + +bool Server::activateUserAccount(const Command_Activate &cmd) +{ + QString userName = QString::fromStdString(cmd.user_name()); + QString token = QString::fromStdString(cmd.token()); + + Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); + + return databaseInterface->activateUser(userName, token); } bool Server::tooManyRegistrationAttempts(const QString &ipAddress) diff --git a/common/server.h b/common/server.h index 736ef582..827b38f4 100644 --- a/common/server.h +++ b/common/server.h @@ -29,7 +29,7 @@ class CommandContainer; class Command_JoinGame; enum AuthenticationResult { NotLoggedIn, PasswordRight, UnknownUser, WouldOverwriteOldSession, UserIsBanned, UsernameInvalid, RegistrationRequired, UserIsInactive }; -enum RegistrationResult { Accepted, UserAlreadyExists, EmailRequired, TooManyRequests, InvalidUsername, ClientIsBanned, RegistrationDisabled, Failed, PasswordTooShort }; +enum RegistrationResult { Accepted, UserAlreadyExists, EmailRequired, TooManyRequests, InvalidUsername, ClientIsBanned, RegistrationDisabled, Failed, PasswordTooShort, AcceptedNeedsActivation }; class Server : public QObject { @@ -57,6 +57,7 @@ public: * @return RegistrationResult member indicating whether it succeeded or failed. */ RegistrationResult registerUserAccount(const QString &ipAddress, const Command_Register &cmd, QString &banReason, int &banSecondsRemaining); + bool activateUserAccount(const Command_Activate &cmd); bool tooManyRegistrationAttempts(const QString &ipAddress); const QMap &getRooms() { return rooms; } diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 0cabe867..d809b1ff 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -39,6 +39,7 @@ public: virtual bool getRequireRegistration() { return false; } virtual bool registerUser(const QString & /* userName */, const QString & /* realName */, ServerInfo_User_Gender const & /* gender */, const QString & /* password */, const QString & /* emailAddress */, const QString & /* country */, bool /* active = false */) { return false; } + virtual bool activateUser(const QString & /* userName */, const QString & /* token */) { return false; } enum LogMessage_TargetType { MessageTargetRoom, MessageTargetGame, MessageTargetChat, MessageTargetIslRoom }; virtual void logMessage(const int /* senderId */, const QString & /* senderName */, const QString & /* senderIp */, const QString & /* logMessage */, LogMessage_TargetType /* targetType */, const int /* targetId */, const QString & /* targetName */) { }; diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 9a213108..608f8716 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -146,6 +146,7 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co case SessionCommand::PING: resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc); break; case SessionCommand::LOGIN: resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc); break; case SessionCommand::REGISTER: resp = cmdRegisterAccount(sc.GetExtension(Command_Register::ext), rc); break; + case SessionCommand::ACTIVATE: resp = cmdActivateAccount(sc.GetExtension(Command_Activate::ext), rc); break; case SessionCommand::MESSAGE: resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc); break; case SessionCommand::GET_GAMES_OF_USER: resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc); break; case SessionCommand::GET_USER_INFO: resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc); break; @@ -437,8 +438,11 @@ Response::ResponseCode Server_ProtocolHandler::cmdRegisterAccount(const Command_ switch (result) { case RegistrationDisabled: return Response::RespRegistrationDisabled; - case Accepted: + case Accepted: return Response::RespRegistrationAccepted; + case AcceptedNeedsActivation: + // TODO SEND EMAIL WITH TOKEN TO THE USER + return Response::RespRegistrationAcceptedNeedsActivation; case UserAlreadyExists: return Response::RespUserAlreadyExists; case EmailRequired: @@ -463,6 +467,18 @@ Response::ResponseCode Server_ProtocolHandler::cmdRegisterAccount(const Command_ return Response::RespInvalidCommand; } +Response::ResponseCode Server_ProtocolHandler::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer &rc) +{ + if(server->activateUserAccount(cmd)) + { + qDebug() << "Accepted activation for user" << QString::fromStdString(cmd.user_name()); + return Response::RespActivationAccepted; + } else { + qDebug() << "Failed activation for user" << QString::fromStdString(cmd.user_name()); + return Response::RespActivationFailed; + } +} + Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index 7ea34cf8..b819d9b3 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -61,6 +61,7 @@ private: Response::ResponseCode cmdPing(const Command_Ping &cmd, ResponseContainer &rc); Response::ResponseCode cmdLogin(const Command_Login &cmd, ResponseContainer &rc); Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc); + Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer &rc); Response::ResponseCode cmdMessage(const Command_Message &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc); diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index d9cc7629..27a3e2f1 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -60,7 +60,9 @@ regonly=0 ; Enable this feature? Default false. ;enabled=false -; Require users to provide an email address in order to register. Default true. +; Require users to provide an email address in order to register. Newly registered users will receive an +; activation token by email, and will be required to input back this token on cockatrice at the first login +; to get their account activated. Default true. ;requireemail=true diff --git a/servatrice/src/passwordhasher.cpp b/servatrice/src/passwordhasher.cpp index b02d8e9b..38528a1b 100644 --- a/servatrice/src/passwordhasher.cpp +++ b/servatrice/src/passwordhasher.cpp @@ -70,3 +70,8 @@ QString PasswordHasher::generateRandomSalt(const int len) return ret; } + +QString PasswordHasher::generateActivationToken() +{ + return QCryptographicHash::hash(generateRandomSalt().toUtf8(), QCryptographicHash::Md5).toBase64().left(16); +} \ No newline at end of file diff --git a/servatrice/src/passwordhasher.h b/servatrice/src/passwordhasher.h index ffe21a9d..4160cb0f 100644 --- a/servatrice/src/passwordhasher.h +++ b/servatrice/src/passwordhasher.h @@ -8,6 +8,7 @@ public: static void initialize(); static QString computeHash(const QString &password, const QString &salt); static QString generateRandomSalt(const int len = 16); + static QString generateActivationToken(); }; #endif diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 26d9a4f5..e5ff7d1e 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -117,11 +117,12 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const Q return false; QString passwordSha512 = PasswordHasher::computeHash(password, PasswordHasher::generateRandomSalt()); + QString token = PasswordHasher::generateActivationToken(); QSqlQuery *query = prepareQuery("insert into {prefix}_users " - "(name, realname, gender, password_sha512, email, country, registrationDate, active) " + "(name, realname, gender, password_sha512, email, country, registrationDate, active, token) " "values " - "(:userName, :realName, :gender, :password_sha512, :email, :country, UTC_TIMESTAMP(), :active)"); + "(:userName, :realName, :gender, :password_sha512, :email, :country, UTC_TIMESTAMP(), :active, :token)"); query->bindValue(":userName", userName); query->bindValue(":realName", realName); query->bindValue(":gender", getGenderChar(gender)); @@ -129,6 +130,7 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const Q query->bindValue(":email", emailAddress); query->bindValue(":country", country); query->bindValue(":active", active ? 1 : 0); + query->bindValue(":token", token); if (!execSqlQuery(query)) { qDebug() << "Failed to insert user: " << query->lastError() << " sql: " << query->lastQuery(); @@ -138,6 +140,40 @@ bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const Q return true; } +bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const QString &token) +{ + if (!checkSql()) + return false; + + QSqlQuery *activateQuery = prepareQuery("select name from {prefix}_users where active=0 and name=:username and token=:token"); + + activateQuery->bindValue(":username", userName); + activateQuery->bindValue(":token", token); + if (!execSqlQuery(activateQuery)) { + qDebug() << "Account activation failed: SQL error." << activateQuery->lastError()<< " sql: " << activateQuery->lastQuery(); + return false; + } + + if (activateQuery->next()) { + const QString name = activateQuery->value(0).toString(); + // redundant check + if(name == userName) + { + + QSqlQuery *query = prepareQuery("update {prefix}_users set active=1 where name = :userName"); + query->bindValue(":userName", userName); + + if (!execSqlQuery(query)) { + qDebug() << "Failed to activate user: " << query->lastError() << " sql: " << query->lastQuery(); + return false; + } + + return true; + } + } + return false; +} + QChar Servatrice_DatabaseInterface::getGenderChar(ServerInfo_User_Gender const &gender) { switch (gender) { diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 4e077dac..9bb111ff 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -65,6 +65,7 @@ public: bool getRequireRegistration(); bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, bool active = false); + bool activateUser(const QString &userName, const QString &token); void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); }; From 21155ce54abc12385306fbd98b8488649d417771 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 24 May 2015 00:47:12 +0200 Subject: [PATCH 07/12] Make gcc an happy puppy (-Wunused-parameter) --- common/server_protocolhandler.cpp | 2 +- common/server_protocolhandler.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 608f8716..fb38f2b6 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -467,7 +467,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdRegisterAccount(const Command_ return Response::RespInvalidCommand; } -Response::ResponseCode Server_ProtocolHandler::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer &rc) +Response::ResponseCode Server_ProtocolHandler::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /*rc*/) { if(server->activateUserAccount(cmd)) { diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index b819d9b3..b285ae99 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -61,7 +61,7 @@ private: Response::ResponseCode cmdPing(const Command_Ping &cmd, ResponseContainer &rc); Response::ResponseCode cmdLogin(const Command_Login &cmd, ResponseContainer &rc); Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc); - Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer &rc); + Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /* rc */); Response::ResponseCode cmdMessage(const Command_Message &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc); From d6cee242e3d126794547fb7c1d1d863962557ef3 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 24 May 2015 00:53:08 +0200 Subject: [PATCH 08/12] Another gcc warning-as error removal --- cockatrice/src/remoteclient.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 13d75a04..3a597589 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -162,7 +162,6 @@ void RemoteClient::registerResponse(const Response &response) void RemoteClient::activateResponse(const Response &response) { - const Response_Activate &resp = response.GetExtension(Response_Activate::ext); if (response.response_code() == Response::RespActivationAccepted) { emit activateAccepted(); From 8cf44616161e4e5e5ec2d055fd175733012d5b63 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 24 May 2015 01:00:55 +0200 Subject: [PATCH 09/12] fix qt4 compilation --- servatrice/src/passwordhasher.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/servatrice/src/passwordhasher.cpp b/servatrice/src/passwordhasher.cpp index 38528a1b..41e73b9e 100644 --- a/servatrice/src/passwordhasher.cpp +++ b/servatrice/src/passwordhasher.cpp @@ -4,10 +4,9 @@ #include #include #include -#else - #include #endif +#include #include "rng_sfmt.h" void PasswordHasher::initialize() From 471f6371b5aea2dabd404decca4e8258a103c39a Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 24 May 2015 23:02:51 +0200 Subject: [PATCH 10/12] More work * Refactored code out of common/ into servatrice/ * added smtp client library * disable registration when connected * validate email address * send activation token via email --- cockatrice/src/window_main.cpp | 6 +- common/server.cpp | 58 --- common/server.h | 17 - common/server_protocolhandler.cpp | 61 --- common/server_protocolhandler.h | 3 +- servatrice/CMakeLists.txt | 12 + servatrice/servatrice.ini.example | 33 ++ servatrice/src/servatrice.cpp | 4 +- .../src/servatrice_database_interface.cpp | 4 +- .../src/servatrice_database_interface.h | 6 +- servatrice/src/serversocketinterface.cpp | 178 +++++++ servatrice/src/serversocketinterface.h | 5 + servatrice/src/smtp/SmtpMime | 31 ++ servatrice/src/smtp/emailaddress.cpp | 60 +++ servatrice/src/smtp/emailaddress.h | 61 +++ servatrice/src/smtp/mimeattachment.cpp | 50 ++ servatrice/src/smtp/mimeattachment.h | 51 ++ servatrice/src/smtp/mimecontentformatter.cpp | 66 +++ servatrice/src/smtp/mimecontentformatter.h | 43 ++ servatrice/src/smtp/mimefile.cpp | 70 +++ servatrice/src/smtp/mimefile.h | 62 +++ servatrice/src/smtp/mimehtml.cpp | 57 +++ servatrice/src/smtp/mimehtml.h | 61 +++ servatrice/src/smtp/mimeinlinefile.cpp | 52 ++ servatrice/src/smtp/mimeinlinefile.h | 56 ++ servatrice/src/smtp/mimemessage.cpp | 257 ++++++++++ servatrice/src/smtp/mimemessage.h | 92 ++++ servatrice/src/smtp/mimemultipart.cpp | 78 +++ servatrice/src/smtp/mimemultipart.h | 75 +++ servatrice/src/smtp/mimepart.cpp | 214 ++++++++ servatrice/src/smtp/mimepart.h | 114 +++++ servatrice/src/smtp/mimetext.cpp | 62 +++ servatrice/src/smtp/mimetext.h | 62 +++ servatrice/src/smtp/quotedprintable.cpp | 69 +++ servatrice/src/smtp/quotedprintable.h | 39 ++ servatrice/src/smtp/smtpclient.cpp | 482 ++++++++++++++++++ servatrice/src/smtp/smtpclient.h | 184 +++++++ servatrice/src/smtp/smtpexports.h | 10 + 38 files changed, 2699 insertions(+), 146 deletions(-) create mode 100644 servatrice/src/smtp/SmtpMime create mode 100644 servatrice/src/smtp/emailaddress.cpp create mode 100644 servatrice/src/smtp/emailaddress.h create mode 100644 servatrice/src/smtp/mimeattachment.cpp create mode 100644 servatrice/src/smtp/mimeattachment.h create mode 100644 servatrice/src/smtp/mimecontentformatter.cpp create mode 100644 servatrice/src/smtp/mimecontentformatter.h create mode 100644 servatrice/src/smtp/mimefile.cpp create mode 100644 servatrice/src/smtp/mimefile.h create mode 100644 servatrice/src/smtp/mimehtml.cpp create mode 100644 servatrice/src/smtp/mimehtml.h create mode 100644 servatrice/src/smtp/mimeinlinefile.cpp create mode 100644 servatrice/src/smtp/mimeinlinefile.h create mode 100644 servatrice/src/smtp/mimemessage.cpp create mode 100644 servatrice/src/smtp/mimemessage.h create mode 100644 servatrice/src/smtp/mimemultipart.cpp create mode 100644 servatrice/src/smtp/mimemultipart.h create mode 100644 servatrice/src/smtp/mimepart.cpp create mode 100644 servatrice/src/smtp/mimepart.h create mode 100644 servatrice/src/smtp/mimetext.cpp create mode 100644 servatrice/src/smtp/mimetext.h create mode 100644 servatrice/src/smtp/quotedprintable.cpp create mode 100644 servatrice/src/smtp/quotedprintable.h create mode 100644 servatrice/src/smtp/smtpclient.cpp create mode 100644 servatrice/src/smtp/smtpclient.h create mode 100644 servatrice/src/smtp/smtpexports.h diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 896d7821..5b640046 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -104,11 +104,13 @@ void MainWindow::statusChanged(ClientStatus _status) tabSupervisor->stop(); aSinglePlayer->setEnabled(true); aConnect->setEnabled(true); + aRegister->setEnabled(true); aDisconnect->setEnabled(false); break; case StatusLoggingIn: aSinglePlayer->setEnabled(false); aConnect->setEnabled(false); + aRegister->setEnabled(false); aDisconnect->setEnabled(true); break; case StatusConnecting: @@ -179,6 +181,7 @@ void MainWindow::actSinglePlayer() return; aConnect->setEnabled(false); + aRegister->setEnabled(false); aSinglePlayer->setEnabled(false); localServer = new LocalServer(this); @@ -226,6 +229,7 @@ void MainWindow::localGameEnded() localServer = 0; aConnect->setEnabled(true); + aRegister->setEnabled(true); aSinglePlayer->setEnabled(true); } @@ -336,7 +340,7 @@ void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quin QMessageBox::critical(this, tr("Registration denied"), tr("There is already an existing account with the same user name.")); break; case Response::RespEmailRequiredToRegister: - QMessageBox::critical(this, tr("Registration denied"), tr("It's mandatory to specify an email when registering.")); + QMessageBox::critical(this, tr("Registration denied"), tr("It's mandatory to specify a valid email address when registering.")); break; case Response::RespTooManyRequests: QMessageBox::critical(this, tr("Registration denied"), tr("Too many registration attempts from your IP address.")); diff --git a/common/server.cpp b/common/server.cpp index 1e3991c3..3c2c9718 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -175,64 +175,6 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString return authState; } -RegistrationResult Server::registerUserAccount(const QString &ipAddress, const Command_Register &cmd, QString &banReason, int &banSecondsRemaining) -{ - if (!registrationEnabled) - return RegistrationDisabled; - - QString emailAddress = QString::fromStdString(cmd.email()); - if (requireEmailForRegistration && emailAddress.isEmpty()) - return EmailRequired; - - Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); - - // TODO: Move this method outside of the db interface - QString userName = QString::fromStdString(cmd.user_name()); - if (!databaseInterface->usernameIsValid(userName)) - return InvalidUsername; - - if(databaseInterface->userExists(userName)) - return UserAlreadyExists; - - if (databaseInterface->checkUserIsBanned(ipAddress, userName, banReason, banSecondsRemaining)) - return ClientIsBanned; - - if (tooManyRegistrationAttempts(ipAddress)) - return TooManyRequests; - - QString realName = QString::fromStdString(cmd.real_name()); - ServerInfo_User_Gender gender = cmd.gender(); - QString country = QString::fromStdString(cmd.country()); - QString password = QString::fromStdString(cmd.password()); - - if(password.length() < 6) - return PasswordTooShort; - - bool regSucceeded = databaseInterface->registerUser(userName, realName, gender, password, emailAddress, country, !requireEmailForRegistration); - - if(regSucceeded) - return requireEmailForRegistration ? AcceptedNeedsActivation : Accepted; - else - return Failed; -} - -bool Server::activateUserAccount(const Command_Activate &cmd) -{ - QString userName = QString::fromStdString(cmd.user_name()); - QString token = QString::fromStdString(cmd.token()); - - Server_DatabaseInterface *databaseInterface = getDatabaseInterface(); - - return databaseInterface->activateUser(userName, token); -} - -bool Server::tooManyRegistrationAttempts(const QString &ipAddress) -{ - // TODO: implement - Q_UNUSED(ipAddress); - return false; -} - void Server::addPersistentPlayer(const QString &userName, int roomId, int gameId, int playerId) { QWriteLocker locker(&persistentPlayersLock); diff --git a/common/server.h b/common/server.h index 827b38f4..ad17bc53 100644 --- a/common/server.h +++ b/common/server.h @@ -29,7 +29,6 @@ class CommandContainer; class Command_JoinGame; enum AuthenticationResult { NotLoggedIn, PasswordRight, UnknownUser, WouldOverwriteOldSession, UserIsBanned, UsernameInvalid, RegistrationRequired, UserIsInactive }; -enum RegistrationResult { Accepted, UserAlreadyExists, EmailRequired, TooManyRequests, InvalidUsername, ClientIsBanned, RegistrationDisabled, Failed, PasswordTooShort, AcceptedNeedsActivation }; class Server : public QObject { @@ -47,19 +46,6 @@ public: void setThreaded(bool _threaded) { threaded = _threaded; } AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); - /** - * Registers a user account. - * @param ipAddress The address of the connection from the user - * @param userName The username to attempt to register - * @param emailAddress The email address to associate with the new account (and to use for activation) - * @param banReason If the client is banned, the reason for the ban will be included in this string. - * @param banSecondsRemaining If the client is banned, the time left will be included in this. 0 if the ban is permanent. - * @return RegistrationResult member indicating whether it succeeded or failed. - */ - RegistrationResult registerUserAccount(const QString &ipAddress, const Command_Register &cmd, QString &banReason, int &banSecondsRemaining); - bool activateUserAccount(const Command_Activate &cmd); - - bool tooManyRegistrationAttempts(const QString &ipAddress); const QMap &getRooms() { return rooms; } Server_AbstractUserInterface *findUser(const QString &userName) const; @@ -131,9 +117,6 @@ protected: int getUsersCount() const; int getGamesCount() const; void addRoom(Server_Room *newRoom); - - bool registrationEnabled; - bool requireEmailForRegistration; }; #endif diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index fb38f2b6..46e2bb83 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -9,7 +9,6 @@ #include "pb/commands.pb.h" #include "pb/response.pb.h" #include "pb/response_login.pb.h" -#include "pb/response_register.pb.h" #include "pb/response_list_users.pb.h" #include "pb/response_get_games_of_user.pb.h" #include "pb/response_get_user_info.pb.h" @@ -145,8 +144,6 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co switch ((SessionCommand::SessionCommandType) num) { case SessionCommand::PING: resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc); break; case SessionCommand::LOGIN: resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc); break; - case SessionCommand::REGISTER: resp = cmdRegisterAccount(sc.GetExtension(Command_Register::ext), rc); break; - case SessionCommand::ACTIVATE: resp = cmdActivateAccount(sc.GetExtension(Command_Activate::ext), rc); break; case SessionCommand::MESSAGE: resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc); break; case SessionCommand::GET_GAMES_OF_USER: resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc); break; case SessionCommand::GET_USER_INFO: resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc); break; @@ -421,64 +418,6 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd return Response::RespOk; } -Response::ResponseCode Server_ProtocolHandler::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) -{ - qDebug() << "Got register command: " << QString::fromStdString(cmd.user_name()); - - QString banReason; - int banSecondsRemaining; - RegistrationResult result = - server->registerUserAccount( - this->getAddress(), - cmd, - banReason, - banSecondsRemaining); - qDebug() << "Register command result:" << result; - - switch (result) { - case RegistrationDisabled: - return Response::RespRegistrationDisabled; - case Accepted: - return Response::RespRegistrationAccepted; - case AcceptedNeedsActivation: - // TODO SEND EMAIL WITH TOKEN TO THE USER - return Response::RespRegistrationAcceptedNeedsActivation; - case UserAlreadyExists: - return Response::RespUserAlreadyExists; - case EmailRequired: - return Response::RespEmailRequiredToRegister; - case TooManyRequests: - return Response::RespTooManyRequests; - case PasswordTooShort: - return Response::RespPasswordTooShort; - case InvalidUsername: - return Response::RespUsernameInvalid; - case Failed: - return Response::RespRegistrationFailed; - case ClientIsBanned: - Response_Register *re = new Response_Register; - re->set_denied_reason_str(banReason.toStdString()); - if (banSecondsRemaining != 0) - re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsRemaining).toTime_t()); - rc.setResponseExtension(re); - return Response::RespUserIsBanned; - } - - return Response::RespInvalidCommand; -} - -Response::ResponseCode Server_ProtocolHandler::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /*rc*/) -{ - if(server->activateUserAccount(cmd)) - { - qDebug() << "Accepted activation for user" << QString::fromStdString(cmd.user_name()); - return Response::RespActivationAccepted; - } else { - qDebug() << "Failed activation for user" << QString::fromStdString(cmd.user_name()); - return Response::RespActivationFailed; - } -} - Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message &cmd, ResponseContainer &rc) { if (authState == NotLoggedIn) diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index b285ae99..bf9d1940 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -60,8 +60,6 @@ private: Response::ResponseCode cmdPing(const Command_Ping &cmd, ResponseContainer &rc); Response::ResponseCode cmdLogin(const Command_Login &cmd, ResponseContainer &rc); - Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc); - Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /* rc */); Response::ResponseCode cmdMessage(const Command_Message &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd, ResponseContainer &rc); Response::ResponseCode cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc); @@ -101,6 +99,7 @@ public: void sendProtocolItem(const SessionEvent &item); void sendProtocolItem(const GameEventContainer &item); void sendProtocolItem(const RoomEvent &item); + }; #endif diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index e249fdcd..2fe46e00 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -15,6 +15,18 @@ SET(servatrice_SOURCES src/settingscache.cpp src/isl_interface.cpp ${VERSION_STRING_CPP} + src/smtp/emailaddress.cpp + src/smtp/mimeattachment.cpp + src/smtp/mimecontentformatter.cpp + src/smtp/mimefile.cpp + src/smtp/mimehtml.cpp + src/smtp/mimeinlinefile.cpp + src/smtp/mimemessage.cpp + src/smtp/mimemultipart.cpp + src/smtp/mimepart.cpp + src/smtp/mimetext.cpp + src/smtp/quotedprintable.cpp + src/smtp/smtpclient.cpp ) set(servatrice_RESOURCES servatrice.qrc) diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index 27a3e2f1..31483afe 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -65,6 +65,39 @@ regonly=0 ; to get their account activated. Default true. ;requireemail=true +[smtp] + +; Connectin type: currently supported method are "tcp", "ssl" and "tls" +connection=tcp + +; Auth type: currently supported method are "plain" and "login" +auth=plain + +; Hostname or IP addres of the smtp server +host=localhost + +; Smtp port number of the smtp server. Usual values are 25 or 587 for tcp, 465 for ssl +port=25 + +; Username: this typically matches the "from" email address +username=root@localhost + +; Password for the username +password=foobar + +; Sender email address: the "from" email address +email=root@localhost + +; Sender email name +name="Cockatrice server" + +; Email subject +subject="Cockatrice server account activation token" + +; Email body. You can use these tags here: %username %token +; They will be substituted with the actual values in the email +; +body="Hi %username, thank our for registering on our Cockatrice server\r\nHere's the activation token you need to supply for activatin your account:\r\n\r\n%token\r\n\r\nHappy gaming!" [database] diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index e4430912..ad6f6ada 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -160,8 +160,8 @@ bool Servatrice::initServer() authenticationMethod = AuthenticationNone; } - registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); - requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); + bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); + bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); qDebug() << "Registration enabled: " << registrationEnabled; if (registrationEnabled) diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index e5ff7d1e..b2027709 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -111,13 +111,13 @@ bool Servatrice_DatabaseInterface::getRequireRegistration() return settingsCache->value("authentication/regonly", 0).toBool(); } -bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, bool active) +bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active) { if (!checkSql()) return false; QString passwordSha512 = PasswordHasher::computeHash(password, PasswordHasher::generateRandomSalt()); - QString token = PasswordHasher::generateActivationToken(); + token = active ? QString() : PasswordHasher::generateActivationToken(); QSqlQuery *query = prepareQuery("insert into {prefix}_users " "(name, realname, gender, password_sha512, email, country, registrationDate, active, token) " diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 9bb111ff..d7715544 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -25,9 +25,7 @@ private: bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); QChar getGenderChar(ServerInfo_User_Gender const &gender); protected: - bool usernameIsValid(const QString &user); AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); - bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); public slots: void initDatabase(const QSqlDatabase &_sqlDatabase); public: @@ -62,9 +60,11 @@ public: void lockSessionTables(); void unlockSessionTables(); bool userSessionExists(const QString &userName); + bool usernameIsValid(const QString &user); + bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); bool getRequireRegistration(); - bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, bool active = false); + bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active = false); bool activateUser(const QString &userName, const QString &token); void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 86485661..a2fa26b2 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -51,12 +51,15 @@ #include "pb/response_deck_list.pb.h" #include "pb/response_deck_download.pb.h" #include "pb/response_deck_upload.pb.h" +#include "pb/response_register.pb.h" #include "pb/response_replay_list.pb.h" #include "pb/response_replay_download.pb.h" #include "pb/serverinfo_replay.pb.h" #include "pb/serverinfo_user.pb.h" #include "pb/serverinfo_deckstorage.pb.h" +#include "smtp/SmtpMime" + #include "version_string.h" #include #include @@ -267,6 +270,8 @@ Response::ResponseCode ServerSocketInterface::processExtendedSessionCommand(int case SessionCommand::REPLAY_DOWNLOAD: return cmdReplayDownload(cmd.GetExtension(Command_ReplayDownload::ext), rc); case SessionCommand::REPLAY_MODIFY_MATCH: return cmdReplayModifyMatch(cmd.GetExtension(Command_ReplayModifyMatch::ext), rc); case SessionCommand::REPLAY_DELETE_MATCH: return cmdReplayDeleteMatch(cmd.GetExtension(Command_ReplayDeleteMatch::ext), rc); + case SessionCommand::REGISTER: return cmdRegisterAccount(cmd.GetExtension(Command_Register::ext), rc); break; + case SessionCommand::ACTIVATE: return cmdActivateAccount(cmd.GetExtension(Command_Activate::ext), rc); break; default: return Response::RespFunctionNotAllowed; } } @@ -764,6 +769,179 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban return Response::RespOk; } +Response::ResponseCode ServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) +{ + QString userName = QString::fromStdString(cmd.user_name()); + qDebug() << "Got register command: " << userName; + + bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); + if (!registrationEnabled) + return Response::RespRegistrationDisabled; + + QString emailAddress = QString::fromStdString(cmd.email()); + bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); + if (requireEmailForRegistration) + { + QRegExp rx("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}\\b"); + if(emailAddress.isEmpty() || !rx.exactMatch(emailAddress)) + return Response::RespEmailRequiredToRegister; + } + + // TODO: Move this method outside of the db interface + if (!sqlInterface->usernameIsValid(userName)) + return Response::RespUsernameInvalid; + + if(sqlInterface->userExists(userName)) + return Response::RespUserAlreadyExists; + + QString banReason; + int banSecondsRemaining; + if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, banReason, banSecondsRemaining)) + { + Response_Register *re = new Response_Register; + re->set_denied_reason_str(banReason.toStdString()); + if (banSecondsRemaining != 0) + re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsRemaining).toTime_t()); + rc.setResponseExtension(re); + return Response::RespUserIsBanned; + } + + if (tooManyRegistrationAttempts(this->getAddress())) + return Response::RespTooManyRequests; + + QString realName = QString::fromStdString(cmd.real_name()); + ServerInfo_User_Gender gender = cmd.gender(); + QString country = QString::fromStdString(cmd.country()); + QString password = QString::fromStdString(cmd.password()); + + // TODO make this configurable? + if(password.length() < 6) + return Response::RespPasswordTooShort; + + QString token; + bool regSucceeded = sqlInterface->registerUser(userName, realName, gender, password, emailAddress, country, token, !requireEmailForRegistration); + + if(regSucceeded) + { + qDebug() << "Accepted register command for user: " << userName; + if(requireEmailForRegistration) + { + // TODO call a slot on another thread to send email + sendActivationTokenMail(userName, emailAddress, token); + return Response::RespRegistrationAcceptedNeedsActivation; + } else { + return Response::RespRegistrationAccepted; + } + } else { + return Response::RespRegistrationFailed; + } +} + +bool ServerSocketInterface::sendActivationTokenMail(const QString &nickname, const QString &recipient, const QString &token) +{ + QString tmp = settingsCache->value("smtp/connection", "tcp").toString(); + SmtpClient::ConnectionType connection = SmtpClient::TcpConnection; + if(tmp == "ssl") + connection = SmtpClient::SslConnection; + else if(tmp == "tls") + connection = SmtpClient::TlsConnection; + + tmp = settingsCache->value("smtp/auth", "plain").toString(); + SmtpClient::AuthMethod auth = SmtpClient::AuthPlain; + if(tmp == "login") + auth = SmtpClient::AuthLogin; + + QString host = settingsCache->value("smtp/host", "localhost").toString(); + int port = settingsCache->value("smtp/port", 25).toInt(); + QString username = settingsCache->value("smtp/username", "").toString(); + QString password = settingsCache->value("smtp/password", "").toString(); + QString email = settingsCache->value("smtp/email", "").toString(); + QString name = settingsCache->value("smtp/name", "").toString(); + QString subject = settingsCache->value("smtp/subject", "").toString(); + QString body = settingsCache->value("smtp/body", "").toString(); + + if(email.isEmpty()) + { + qDebug() << "[MAIL] Missing email field" << endl; + return false; + } + if(body.isEmpty()) + { + qDebug() << "[MAIL] Missing body field" << endl; + return false; + } + if(recipient.isEmpty()) + { + qDebug() << "[MAIL] Missing recipient field" << endl; + return false; + } + if(token.isEmpty()) + { + qDebug() << "[MAIL] Missing token field" << endl; + return false; + } + + SmtpClient smtp(host, port, connection); + smtp.setUser(username); + smtp.setPassword(password); + smtp.setAuthMethod(auth); + + MimeMessage message; + EmailAddress sender(email, name); + message.setSender(&sender); + EmailAddress to(recipient, nickname); + message.addRecipient(&to); + message.setSubject(subject); + + MimeText text; + text.setText(body.replace("%username", nickname).replace("%token", token)); + message.addPart(&text); + + // Now we can send the mail + + if (!smtp.connectToHost()) { + qDebug() << "[MAIL] Failed to connect to host" << host << "on port" << port; + return false; + } + + if (!smtp.login()) { + qDebug() << "[MAIL] Failed to login as " << username; + return false; + } + + if (!smtp.sendMail(message)) { + qDebug() << "[MAIL] Failed to send mail to " << recipient; + return false; + } + + smtp.quit(); + qDebug() << "[MAIL] Sent activation email to " << recipient << " for nickname " << nickname; + return true; +} + +bool ServerSocketInterface::tooManyRegistrationAttempts(const QString &ipAddress) +{ + // TODO: implement + Q_UNUSED(ipAddress); + return false; +} + +Response::ResponseCode ServerSocketInterface::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /*rc*/) +{ + QString userName = QString::fromStdString(cmd.user_name()); + QString token = QString::fromStdString(cmd.token()); + + if(sqlInterface->activateUser(userName, token)) + { + qDebug() << "Accepted activation for user" << QString::fromStdString(cmd.user_name()); + return Response::RespActivationAccepted; + } else { + qDebug() << "Failed activation for user" << QString::fromStdString(cmd.user_name()); + return Response::RespActivationFailed; + } +} + + // ADMIN FUNCTIONS. // Permission is checked by the calling function. diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 6a2a41f8..c89709d3 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -59,6 +59,7 @@ signals: void outputQueueChanged(); protected: void logDebugMessage(const QString &message); + bool tooManyRegistrationAttempts(const QString &ipAddress); private: QMutex outputQueueMutex; Servatrice *servatrice; @@ -91,10 +92,14 @@ private: Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc); Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer &rc); Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc); + Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc); + Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /* rc */); Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc); Response::ResponseCode processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc); Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc); + + bool sendActivationTokenMail(const QString &nickname, const QString &recipient, const QString &token); public: ServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QObject *parent = 0); ~ServerSocketInterface(); diff --git a/servatrice/src/smtp/SmtpMime b/servatrice/src/smtp/SmtpMime new file mode 100644 index 00000000..940996b8 --- /dev/null +++ b/servatrice/src/smtp/SmtpMime @@ -0,0 +1,31 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef SMTPMIME_H +#define SMTPMIME_H + +#include "smtpclient.h" +#include "mimepart.h" +#include "mimehtml.h" +#include "mimeattachment.h" +#include "mimemessage.h" +#include "mimetext.h" +#include "mimeinlinefile.h" +#include "mimefile.h" + +#endif // SMTPMIME_H diff --git a/servatrice/src/smtp/emailaddress.cpp b/servatrice/src/smtp/emailaddress.cpp new file mode 100644 index 00000000..9480e5d4 --- /dev/null +++ b/servatrice/src/smtp/emailaddress.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "emailaddress.h" + +/* [1] Constructors and Destructors */ + +EmailAddress::EmailAddress(const QString & address, const QString & name) +{ + this->address = address; + this->name = name; +} + +EmailAddress::~EmailAddress() +{ +} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void EmailAddress::setName(const QString & name) +{ + this->name = name; + +} + +void EmailAddress::setAddress(const QString & address) +{ + this->address = address; +} + +const QString & EmailAddress::getName() const +{ + return name; +} + +const QString & EmailAddress::getAddress() const +{ + return address; +} + +/* [2] --- */ + diff --git a/servatrice/src/smtp/emailaddress.h b/servatrice/src/smtp/emailaddress.h new file mode 100644 index 00000000..4ecf6efc --- /dev/null +++ b/servatrice/src/smtp/emailaddress.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef EMAILADDRESS_H +#define EMAILADDRESS_H + +#include + +#include "smtpexports.h" + +class SMTP_EXPORT EmailAddress : public QObject +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + EmailAddress(); + EmailAddress(const QString & address, const QString & name=""); + + ~EmailAddress(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + void setName(const QString & name); + void setAddress(const QString & address); + + const QString & getName() const; + const QString & getAddress() const; + + /* [2] --- */ + + +private: + + /* [3] Private members */ + + QString name; + QString address; + + /* [3] --- */ +}; + +#endif // EMAILADDRESS_H diff --git a/servatrice/src/smtp/mimeattachment.cpp b/servatrice/src/smtp/mimeattachment.cpp new file mode 100644 index 00000000..0dea6222 --- /dev/null +++ b/servatrice/src/smtp/mimeattachment.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimeattachment.h" +#include + +/* [1] Constructors and Destructors */ + +MimeAttachment::MimeAttachment(QFile *file) + : MimeFile(file) +{ +} +MimeAttachment::MimeAttachment(const QByteArray& stream, const QString& fileName): MimeFile(stream, fileName) +{ + +} + +MimeAttachment::~MimeAttachment() +{ +} + +/* [1] --- */ + + +/* [2] Protected methods */ + +void MimeAttachment::prepare() +{ + this->header += "Content-disposition: attachment\r\n"; + + /* !!! IMPORTANT !!! */ + MimeFile::prepare(); +} + +/* [2] --- */ diff --git a/servatrice/src/smtp/mimeattachment.h b/servatrice/src/smtp/mimeattachment.h new file mode 100644 index 00000000..4a92e9a1 --- /dev/null +++ b/servatrice/src/smtp/mimeattachment.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEATTACHMENT_H +#define MIMEATTACHMENT_H + +#include +#include "mimepart.h" +#include "mimefile.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeAttachment : public MimeFile +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + MimeAttachment(QFile* file); + MimeAttachment(const QByteArray& stream, const QString& fileName); + + ~MimeAttachment(); + + /* [1] --- */ + +protected: + + /* [2] Protected methods */ + + virtual void prepare(); + + /* [2] --- */ +}; + +#endif // MIMEATTACHMENT_H diff --git a/servatrice/src/smtp/mimecontentformatter.cpp b/servatrice/src/smtp/mimecontentformatter.cpp new file mode 100644 index 00000000..7f5a6e43 --- /dev/null +++ b/servatrice/src/smtp/mimecontentformatter.cpp @@ -0,0 +1,66 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimecontentformatter.h" + +MimeContentFormatter::MimeContentFormatter(int max_length) : + max_length(max_length) +{} + +QString MimeContentFormatter::format(const QString &content, bool quotedPrintable) const { + + QString out; + + int chars = 0; + for (int i = 0; i < content.length() ; ++i) { + chars++; + if (!quotedPrintable) { + if (chars > max_length) { + out.append("\r\n"); + chars = 1; + } + } + else { + if (content[i] == '\n') { // new line + out.append(content[i]); + chars = 0; + continue; + } + + if ((chars > max_length - 1) + || ((content[i] == '=') && (chars > max_length - 3) )) { + out.append('='); + out.append("\r\n"); + chars = 1; + } + + } + out.append(content[i]); + } + + return out; + +} + +void MimeContentFormatter::setMaxLength(int l) { + max_length = l; +} + +int MimeContentFormatter::getMaxLength() const { + return max_length; +} diff --git a/servatrice/src/smtp/mimecontentformatter.h b/servatrice/src/smtp/mimecontentformatter.h new file mode 100644 index 00000000..bf751588 --- /dev/null +++ b/servatrice/src/smtp/mimecontentformatter.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMECONTENTFORMATTER_H +#define MIMECONTENTFORMATTER_H + +#include +#include + +#include "smtpexports.h" + +class SMTP_EXPORT MimeContentFormatter : public QObject +{ + Q_OBJECT +public: + MimeContentFormatter (int max_length = 76); + + void setMaxLength(int l); + int getMaxLength() const; + + QString format(const QString &content, bool quotedPrintable = false) const; + +protected: + int max_length; + +}; + +#endif // MIMECONTENTFORMATTER_H diff --git a/servatrice/src/smtp/mimefile.cpp b/servatrice/src/smtp/mimefile.cpp new file mode 100644 index 00000000..c6f313a8 --- /dev/null +++ b/servatrice/src/smtp/mimefile.cpp @@ -0,0 +1,70 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimefile.h" +#include + +/* [1] Constructors and Destructors */ + +MimeFile::MimeFile(QFile *file) +{ + this->file = file; + this->cType = "application/octet-stream"; + this->cName = QFileInfo(*file).fileName(); + this->cEncoding = Base64; +} + +MimeFile::MimeFile(const QByteArray& stream, const QString& fileName) +{ + this->cEncoding = Base64; + this->cType = "application/octet-stream"; + this->file = 0; + this->cName = fileName; + this->content = stream; +} + +MimeFile::~MimeFile() +{ + if (file) + delete file; +} + +/* [1] --- */ + + +/* [2] Getters and setters */ + +/* [2] --- */ + + +/* [3] Protected methods */ + +void MimeFile::prepare() +{ + if (this->file) + { + file->open(QIODevice::ReadOnly); + this->content = file->readAll(); + file->close(); + } + /* !!! IMPORTANT !!!! */ + MimePart::prepare(); +} + +/* [3] --- */ + diff --git a/servatrice/src/smtp/mimefile.h b/servatrice/src/smtp/mimefile.h new file mode 100644 index 00000000..13f444de --- /dev/null +++ b/servatrice/src/smtp/mimefile.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEFILE_H +#define MIMEFILE_H + +#include "mimepart.h" +#include + +#include "smtpexports.h" + +class SMTP_EXPORT MimeFile : public MimePart +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + MimeFile(const QByteArray& stream, const QString& fileName); + MimeFile(QFile *f); + ~MimeFile(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + QFile* file; + + /* [3] --- */ + + + /* [4] Protected methods */ + + virtual void prepare(); + + /* [4] --- */ + +}; + +#endif // MIMEFILE_H diff --git a/servatrice/src/smtp/mimehtml.cpp b/servatrice/src/smtp/mimehtml.cpp new file mode 100644 index 00000000..5594d3a4 --- /dev/null +++ b/servatrice/src/smtp/mimehtml.cpp @@ -0,0 +1,57 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimehtml.h" + +/* [1] Constructors and Destructors */ + +MimeHtml::MimeHtml(const QString &html) : MimeText(html) +{ + this->cType = "text/html"; +} + +MimeHtml::~MimeHtml() {} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void MimeHtml::setHtml(const QString & html) +{ + this->text = html; +} + +const QString & MimeHtml::getHtml() const +{ + return text; +} + + +/* [2] --- */ + + +/* [3] Protected methods */ + +void MimeHtml::prepare() +{ + /* !!! IMPORTANT !!! */ + MimeText::prepare(); +} + +/* [3] --- */ diff --git a/servatrice/src/smtp/mimehtml.h b/servatrice/src/smtp/mimehtml.h new file mode 100644 index 00000000..f3f54300 --- /dev/null +++ b/servatrice/src/smtp/mimehtml.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEHTML_H +#define MIMEHTML_H + +#include "mimetext.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeHtml : public MimeText +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + MimeHtml(const QString &html = ""); + ~MimeHtml(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + void setHtml(const QString & html); + + const QString& getHtml() const; + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + /* [3] --- */ + + + /* [4] Protected methods */ + + virtual void prepare(); + + /* [4] --- */ +}; + +#endif // MIMEHTML_H diff --git a/servatrice/src/smtp/mimeinlinefile.cpp b/servatrice/src/smtp/mimeinlinefile.cpp new file mode 100644 index 00000000..0823b0db --- /dev/null +++ b/servatrice/src/smtp/mimeinlinefile.cpp @@ -0,0 +1,52 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimeinlinefile.h" + +/* [1] Constructors and Destructors */ + +MimeInlineFile::MimeInlineFile(QFile *f) + : MimeFile(f) +{ +} + +MimeInlineFile::~MimeInlineFile() +{} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +/* [2] --- */ + + +/* [3] Protected methods */ + +void MimeInlineFile::prepare() +{ + this->header += "Content-Disposition: inline\r\n"; + + /* !!! IMPORTANT !!! */ + MimeFile::prepare(); +} + +/* [3] --- */ + + + diff --git a/servatrice/src/smtp/mimeinlinefile.h b/servatrice/src/smtp/mimeinlinefile.h new file mode 100644 index 00000000..98fe3cab --- /dev/null +++ b/servatrice/src/smtp/mimeinlinefile.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEINLINEFILE_H +#define MIMEINLINEFILE_H + +#include "mimefile.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeInlineFile : public MimeFile +{ +public: + + /* [1] Constructors and Destructors */ + + MimeInlineFile(QFile *f); + ~MimeInlineFile(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + /* [3] --- */ + + + /* [4] Protected methods */ + + virtual void prepare(); + + /* [4] --- */ +}; + +#endif // MIMEINLINEFILE_H diff --git a/servatrice/src/smtp/mimemessage.cpp b/servatrice/src/smtp/mimemessage.cpp new file mode 100644 index 00000000..0c4ebc2d --- /dev/null +++ b/servatrice/src/smtp/mimemessage.cpp @@ -0,0 +1,257 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimemessage.h" + +#include +#include "quotedprintable.h" +#include + +/* [1] Constructors and Destructors */ +MimeMessage::MimeMessage(bool createAutoMimeContent) : + hEncoding(MimePart::_8Bit) +{ + if (createAutoMimeContent) + this->content = new MimeMultiPart(); + + autoMimeContentCreated = createAutoMimeContent; +} + +MimeMessage::~MimeMessage() +{ + if (this->autoMimeContentCreated) + { + this->autoMimeContentCreated = false; + delete (this->content); + } +} + +/* [1] --- */ + + +/* [2] Getters and Setters */ +MimePart& MimeMessage::getContent() { + return *content; +} + +void MimeMessage::setContent(MimePart *content) { + if (this->autoMimeContentCreated) + { + this->autoMimeContentCreated = false; + delete (this->content); + } + this->content = content; +} + +void MimeMessage::setSender(EmailAddress* e) +{ + this->sender = e; +} + +void MimeMessage::addRecipient(EmailAddress* rcpt, RecipientType type) +{ + switch (type) + { + case To: + recipientsTo << rcpt; + break; + case Cc: + recipientsCc << rcpt; + break; + case Bcc: + recipientsBcc << rcpt; + break; + } +} + +void MimeMessage::addTo(EmailAddress* rcpt) { + this->recipientsTo << rcpt; +} + +void MimeMessage::addCc(EmailAddress* rcpt) { + this->recipientsCc << rcpt; +} + +void MimeMessage::addBcc(EmailAddress* rcpt) { + this->recipientsBcc << rcpt; +} + +void MimeMessage::setSubject(const QString & subject) +{ + this->subject = subject; +} + +void MimeMessage::addPart(MimePart *part) +{ + if (typeid(*content) == typeid(MimeMultiPart)) { + ((MimeMultiPart*) content)->addPart(part); + }; +} + +void MimeMessage::setHeaderEncoding(MimePart::Encoding hEnc) +{ + this->hEncoding = hEnc; +} + +const EmailAddress & MimeMessage::getSender() const +{ + return *sender; +} + +const QList & MimeMessage::getRecipients(RecipientType type) const +{ + switch (type) + { + default: + case To: + return recipientsTo; + case Cc: + return recipientsCc; + case Bcc: + return recipientsBcc; + } +} + +const QString & MimeMessage::getSubject() const +{ + return subject; +} + +const QList & MimeMessage::getParts() const +{ + if (typeid(*content) == typeid(MimeMultiPart)) { + return ((MimeMultiPart*) content)->getParts(); + } + else { + QList *res = new QList(); + res->append(content); + return *res; + } +} + +/* [2] --- */ + + +/* [3] Public Methods */ + +QString MimeMessage::toString() +{ + QString mime; + + /* =========== MIME HEADER ============ */ + + /* ---------- Sender / From ----------- */ + mime = "From:"; + if (sender->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append(sender->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append(sender->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + sender->getName(); + } + } + mime += " <" + sender->getAddress() + ">\r\n"; + /* ---------------------------------- */ + + + /* ------- Recipients / To ---------- */ + mime += "To:"; + QList::iterator it; int i; + for (i = 0, it = recipientsTo.begin(); it != recipientsTo.end(); ++it, ++i) + { + if (i != 0) { mime += ","; } + + if ((*it)->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append((*it)->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append((*it)->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + (*it)->getName(); + } + } + mime += " <" + (*it)->getAddress() + ">"; + } + mime += "\r\n"; + /* ---------------------------------- */ + + /* ------- Recipients / Cc ---------- */ + if (recipientsCc.size() != 0) { + mime += "Cc:"; + } + for (i = 0, it = recipientsCc.begin(); it != recipientsCc.end(); ++it, ++i) + { + if (i != 0) { mime += ","; } + + if ((*it)->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append((*it)->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append((*it)->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + (*it)->getName(); + } + } + mime += " <" + (*it)->getAddress() + ">"; + } + if (recipientsCc.size() != 0) { + mime += "\r\n"; + } + /* ---------------------------------- */ + + /* ------------ Subject ------------- */ + mime += "Subject: "; + + + switch (hEncoding) + { + case MimePart::Base64: + mime += "=?utf-8?B?" + QByteArray().append(subject).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += "=?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append(subject)).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += subject; + } + /* ---------------------------------- */ + + mime += "\r\n"; + mime += "MIME-Version: 1.0\r\n"; + + mime += content->toString(); + return mime; +} + +/* [3] --- */ diff --git a/servatrice/src/smtp/mimemessage.h b/servatrice/src/smtp/mimemessage.h new file mode 100644 index 00000000..18b4ec3b --- /dev/null +++ b/servatrice/src/smtp/mimemessage.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEMESSAGE_H +#define MIMEMESSAGE_H + +#include "mimepart.h" +#include "mimemultipart.h" +#include "emailaddress.h" +#include + +#include "smtpexports.h" + +class SMTP_EXPORT MimeMessage : public QObject +{ +public: + + enum RecipientType { + To, // primary + Cc, // carbon copy + Bcc // blind carbon copy + }; + + /* [1] Constructors and Destructors */ + + MimeMessage(bool createAutoMimeConent = true); + ~MimeMessage(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + void setSender(EmailAddress* e); + void addRecipient(EmailAddress* rcpt, RecipientType type = To); + void addTo(EmailAddress* rcpt); + void addCc(EmailAddress* rcpt); + void addBcc(EmailAddress* rcpt); + void setSubject(const QString & subject); + void addPart(MimePart* part); + + void setHeaderEncoding(MimePart::Encoding); + + const EmailAddress & getSender() const; + const QList & getRecipients(RecipientType type = To) const; + const QString & getSubject() const; + const QList & getParts() const; + + MimePart& getContent(); + void setContent(MimePart *content); + /* [2] --- */ + + + /* [3] Public methods */ + + virtual QString toString(); + + /* [3] --- */ + +protected: + + /* [4] Protected members */ + + EmailAddress* sender; + QList recipientsTo, recipientsCc, recipientsBcc; + QString subject; + MimePart *content; + bool autoMimeContentCreated; + + MimePart::Encoding hEncoding; + + /* [4] --- */ + + +}; + +#endif // MIMEMESSAGE_H diff --git a/servatrice/src/smtp/mimemultipart.cpp b/servatrice/src/smtp/mimemultipart.cpp new file mode 100644 index 00000000..19d47297 --- /dev/null +++ b/servatrice/src/smtp/mimemultipart.cpp @@ -0,0 +1,78 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimemultipart.h" +#include +#include + +const QString MULTI_PART_NAMES[] = { + "multipart/mixed", // Mixed + "multipart/digest", // Digest + "multipart/alternative", // Alternative + "multipart/related", // Related + "multipart/report", // Report + "multipart/signed", // Signed + "multipart/encrypted" // Encrypted +}; + +MimeMultiPart::MimeMultiPart(MultiPartType type) +{ + this->type = type; + this->cType = MULTI_PART_NAMES[this->type]; + this->cEncoding = _8Bit; + + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(QByteArray().append(qrand())); + cBoundary = md5.result().toHex(); +} + +MimeMultiPart::~MimeMultiPart() { + +} + +void MimeMultiPart::addPart(MimePart *part) { + parts.append(part); +} + +const QList & MimeMultiPart::getParts() const { + return parts; +} + +void MimeMultiPart::prepare() { + QList::iterator it; + + content = ""; + for (it = parts.begin(); it != parts.end(); it++) { + content += "--" + cBoundary + "\r\n"; + (*it)->prepare(); + content += (*it)->toString(); + }; + + content += "--" + cBoundary + "--\r\n"; + + MimePart::prepare(); +} + +void MimeMultiPart::setMimeType(const MultiPartType type) { + this->type = type; + this->cType = MULTI_PART_NAMES[type]; +} + +MimeMultiPart::MultiPartType MimeMultiPart::getMimeType() const { + return type; +} diff --git a/servatrice/src/smtp/mimemultipart.h b/servatrice/src/smtp/mimemultipart.h new file mode 100644 index 00000000..a8e9f599 --- /dev/null +++ b/servatrice/src/smtp/mimemultipart.h @@ -0,0 +1,75 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEMULTIPART_H +#define MIMEMULTIPART_H + +#include "mimepart.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeMultiPart : public MimePart +{ + Q_OBJECT +public: + + /* [0] Enums */ + enum MultiPartType { + Mixed = 0, // RFC 2046, section 5.1.3 + Digest = 1, // RFC 2046, section 5.1.5 + Alternative = 2, // RFC 2046, section 5.1.4 + Related = 3, // RFC 2387 + Report = 4, // RFC 6522 + Signed = 5, // RFC 1847, section 2.1 + Encrypted = 6 // RFC 1847, section 2.2 + }; + + /* [0] --- */ + + /* [1] Constructors and Destructors */ + MimeMultiPart(const MultiPartType type = Related); + + ~MimeMultiPart(); + + /* [1] --- */ + + /* [2] Getters and Setters */ + + void setMimeType(const MultiPartType type); + MultiPartType getMimeType() const; + + const QList & getParts() const; + + /* [2] --- */ + + /* [3] Public methods */ + + void addPart(MimePart *part); + + virtual void prepare(); + + /* [3] --- */ + +protected: + QList< MimePart* > parts; + + MultiPartType type; + +}; + +#endif // MIMEMULTIPART_H diff --git a/servatrice/src/smtp/mimepart.cpp b/servatrice/src/smtp/mimepart.cpp new file mode 100644 index 00000000..443f863e --- /dev/null +++ b/servatrice/src/smtp/mimepart.cpp @@ -0,0 +1,214 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimepart.h" +#include "quotedprintable.h" + +/* [1] Constructors and Destructors */ + +MimePart::MimePart() +{ + cEncoding = _7Bit; + prepared = false; + cBoundary = ""; +} + +MimePart::~MimePart() +{ + return; +} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void MimePart::setContent(const QByteArray & content) +{ + this->content = content; +} + +void MimePart::setHeader(const QString & header) +{ + this->header = header; +} + +void MimePart::addHeaderLine(const QString & line) +{ + this->header += line + "\r\n"; +} + +const QString& MimePart::getHeader() const +{ + return header; +} + +const QByteArray& MimePart::getContent() const +{ + return content; +} + +void MimePart::setContentId(const QString & cId) +{ + this->cId = cId; +} + +const QString & MimePart::getContentId() const +{ + return this->cId; +} + +void MimePart::setContentName(const QString & cName) +{ + this->cName = cName; +} + +const QString & MimePart::getContentName() const +{ + return this->cName; +} + +void MimePart::setContentType(const QString & cType) +{ + this->cType = cType; +} + +const QString & MimePart::getContentType() const +{ + return this->cType; +} + +void MimePart::setCharset(const QString & charset) +{ + this->cCharset = charset; +} + +const QString & MimePart::getCharset() const +{ + return this->cCharset; +} + +void MimePart::setEncoding(Encoding enc) +{ + this->cEncoding = enc; +} + +MimePart::Encoding MimePart::getEncoding() const +{ + return this->cEncoding; +} + +MimeContentFormatter& MimePart::getContentFormatter() +{ + return this->formatter; +} + +/* [2] --- */ + + +/* [3] Public methods */ + +QString MimePart::toString() +{ + if (!prepared) + prepare(); + + return mimeString; +} + +/* [3] --- */ + + +/* [4] Protected methods */ + +void MimePart::prepare() +{ + mimeString = QString(); + + /* === Header Prepare === */ + + /* Content-Type */ + mimeString.append("Content-Type: ").append(cType); + + if (cName != "") + mimeString.append("; name=\"").append(cName).append("\""); + + if (cCharset != "") + mimeString.append("; charset=").append(cCharset); + + if (cBoundary != "") + mimeString.append("; boundary=").append(cBoundary); + + mimeString.append("\r\n"); + /* ------------ */ + + /* Content-Transfer-Encoding */ + mimeString.append("Content-Transfer-Encoding: "); + switch (cEncoding) + { + case _7Bit: + mimeString.append("7bit\r\n"); + break; + case _8Bit: + mimeString.append("8bit\r\n"); + break; + case Base64: + mimeString.append("base64\r\n"); + break; + case QuotedPrintable: + mimeString.append("quoted-printable\r\n"); + break; + } + /* ------------------------ */ + + /* Content-Id */ + if (cId != NULL) + mimeString.append("Content-ID: <").append(cId).append(">\r\n"); + /* ---------- */ + + /* Addition header lines */ + + mimeString.append(header).append("\r\n"); + + /* ------------------------- */ + + /* === End of Header Prepare === */ + + /* === Content === */ + switch (cEncoding) + { + case _7Bit: + mimeString.append(QString(content).toLatin1()); + break; + case _8Bit: + mimeString.append(content); + break; + case Base64: + mimeString.append(formatter.format(content.toBase64())); + break; + case QuotedPrintable: + mimeString.append(formatter.format(QuotedPrintable::encode(content), true)); + break; + } + mimeString.append("\r\n"); + /* === End of Content === */ + + prepared = true; +} + +/* [4] --- */ diff --git a/servatrice/src/smtp/mimepart.h b/servatrice/src/smtp/mimepart.h new file mode 100644 index 00000000..8636035d --- /dev/null +++ b/servatrice/src/smtp/mimepart.h @@ -0,0 +1,114 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEPART_H +#define MIMEPART_H + +#include +#include "mimecontentformatter.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimePart : public QObject +{ + Q_OBJECT +public: + + /* [0] Enumerations */ + enum Encoding { + _7Bit, + _8Bit, + Base64, + QuotedPrintable + }; + + + /* [0] --- */ + + + /* [1] Constructors and Destructors */ + + MimePart(); + ~MimePart(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + const QString& getHeader() const; + const QByteArray& getContent() const; + + void setContent(const QByteArray & content); + void setHeader(const QString & header); + + void addHeaderLine(const QString & line); + + void setContentId(const QString & cId); + const QString & getContentId() const; + + void setContentName(const QString & cName); + const QString & getContentName() const; + + void setContentType(const QString & cType); + const QString & getContentType() const; + + void setCharset(const QString & charset); + const QString & getCharset() const; + + void setEncoding(Encoding enc); + Encoding getEncoding() const; + + MimeContentFormatter& getContentFormatter(); + + /* [2] --- */ + + + /* [3] Public methods */ + + virtual QString toString(); + + virtual void prepare(); + + /* [3] --- */ + + + +protected: + + /* [4] Protected members */ + + QString header; + QByteArray content; + + QString cId; + QString cName; + QString cType; + QString cCharset; + QString cBoundary; + Encoding cEncoding; + + QString mimeString; + bool prepared; + + MimeContentFormatter formatter; + + /* [4] --- */ +}; + +#endif // MIMEPART_H diff --git a/servatrice/src/smtp/mimetext.cpp b/servatrice/src/smtp/mimetext.cpp new file mode 100644 index 00000000..20b1b0a0 --- /dev/null +++ b/servatrice/src/smtp/mimetext.cpp @@ -0,0 +1,62 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimetext.h" + +/* [1] Constructors and Destructors */ + +MimeText::MimeText(const QString &txt) +{ + this->text = txt; + this->cType = "text/plain"; + this->cCharset = "utf-8"; + this->cEncoding = _8Bit; +} + +MimeText::~MimeText() { } + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void MimeText::setText(const QString & text) +{ + this->text = text; +} + +const QString & MimeText::getText() const +{ + return text; +} + +/* [2] --- */ + + +/* [3] Protected Methods */ + +void MimeText::prepare() +{ + this->content.clear(); + this->content.append(text); + + /* !!! IMPORTANT !!! */ + MimePart::prepare(); +} + +/* [3] --- */ diff --git a/servatrice/src/smtp/mimetext.h b/servatrice/src/smtp/mimetext.h new file mode 100644 index 00000000..c0d29835 --- /dev/null +++ b/servatrice/src/smtp/mimetext.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMETEXT_H +#define MIMETEXT_H + +#include "mimepart.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeText : public MimePart +{ +public: + + /* [1] Constructors and Destructors */ + + MimeText(const QString &text = ""); + ~MimeText(); + + /* [1] --- */ + + + /* [2] Getters and Setters*/ + + void setText(const QString & text); + + const QString & getText() const; + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + QString text; + /* [3] --- */ + + + /* [4] Protected methods */ + + void prepare(); + + /* [4] --- */ + +}; + +#endif // MIMETEXT_H diff --git a/servatrice/src/smtp/quotedprintable.cpp b/servatrice/src/smtp/quotedprintable.cpp new file mode 100644 index 00000000..aa3eda6a --- /dev/null +++ b/servatrice/src/smtp/quotedprintable.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "quotedprintable.h" + +QString QuotedPrintable::encode(const QByteArray &input) +{ + QString output; + + char byte; + const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + for (int i = 0; i < input.length() ; ++i) + { + byte = input[i]; + + if ((byte == 0x20) || ((byte >= 33) && (byte <= 126) && (byte != 61))) + { + output.append(byte); + } + else + { + output.append('='); + output.append(hex[((byte >> 4) & 0x0F)]); + output.append(hex[(byte & 0x0F)]); + } + } + + return output; +} + + +QByteArray QuotedPrintable::decode(const QString &input) +{ + // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F + const int hexVal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15}; + + QByteArray output; + + for (int i = 0; i < input.length(); ++i) + { + if (input.at(i).toLatin1() == '=') + { + output.append((hexVal[input.at(i + 1).toLatin1() - '0'] << 4) + hexVal[input.at(i + 2).toLatin1() - '0']); + i += 2; + } + else + { + output.append(input.at(i).toLatin1()); + } + } + + return output; +} diff --git a/servatrice/src/smtp/quotedprintable.h b/servatrice/src/smtp/quotedprintable.h new file mode 100644 index 00000000..03d3acf8 --- /dev/null +++ b/servatrice/src/smtp/quotedprintable.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef QUOTEDPRINTABLE_H +#define QUOTEDPRINTABLE_H + +#include +#include + +#include "smtpexports.h" + +class SMTP_EXPORT QuotedPrintable : public QObject +{ + Q_OBJECT +public: + + static QString encode(const QByteArray &input); + static QByteArray decode(const QString &input); + +private: + QuotedPrintable(); +}; + +#endif // QUOTEDPRINTABLE_H diff --git a/servatrice/src/smtp/smtpclient.cpp b/servatrice/src/smtp/smtpclient.cpp new file mode 100644 index 00000000..aaa59095 --- /dev/null +++ b/servatrice/src/smtp/smtpclient.cpp @@ -0,0 +1,482 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "smtpclient.h" + +#include +#include + + +/* [1] Constructors and destructors */ + +SmtpClient::SmtpClient(const QString & host, int port, ConnectionType connectionType) : + name("localhost"), + authMethod(AuthPlain), + connectionTimeout(5000), + responseTimeout(5000), + sendMessageTimeout(60000) +{ + setConnectionType(connectionType); + + this->host = host; + this->port = port; + + connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(socketStateChanged(QAbstractSocket::SocketState))); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(socketError(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(readyRead()), + this, SLOT(socketReadyRead())); +} + +SmtpClient::~SmtpClient() {} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void SmtpClient::setUser(const QString &user) +{ + this->user = user; +} + +void SmtpClient::setPassword(const QString &password) +{ + this->password = password; +} + +void SmtpClient::setAuthMethod(AuthMethod method) +{ + this->authMethod = method; +} + +void SmtpClient::setHost(const QString &host) +{ + this->host = host; +} + +void SmtpClient::setPort(int port) +{ + this->port = port; +} + +void SmtpClient::setConnectionType(ConnectionType ct) +{ + this->connectionType = ct; + + switch (connectionType) + { + case TcpConnection: + socket = new QTcpSocket(this); + break; + case SslConnection: + case TlsConnection: + socket = new QSslSocket(this); + } +} + +const QString& SmtpClient::getHost() const +{ + return this->host; +} + +const QString& SmtpClient::getUser() const +{ + return this->user; +} + +const QString& SmtpClient::getPassword() const +{ + return this->password; +} + +SmtpClient::AuthMethod SmtpClient::getAuthMethod() const +{ + return this->authMethod; +} + +int SmtpClient::getPort() const +{ + return this->port; +} + +SmtpClient::ConnectionType SmtpClient::getConnectionType() const +{ + return connectionType; +} + +const QString& SmtpClient::getName() const +{ + return this->name; +} + +void SmtpClient::setName(const QString &name) +{ + this->name = name; +} + +const QString & SmtpClient::getResponseText() const +{ + return responseText; +} + +int SmtpClient::getResponseCode() const +{ + return responseCode; +} + +QTcpSocket* SmtpClient::getSocket() { + return socket; +} + +int SmtpClient::getConnectionTimeout() const +{ + return connectionTimeout; +} + +void SmtpClient::setConnectionTimeout(int msec) +{ + connectionTimeout = msec; +} + +int SmtpClient::getResponseTimeout() const +{ + return responseTimeout; +} + +void SmtpClient::setResponseTimeout(int msec) +{ + responseTimeout = msec; +} +int SmtpClient::getSendMessageTimeout() const +{ + return sendMessageTimeout; +} +void SmtpClient::setSendMessageTimeout(int msec) +{ + sendMessageTimeout = msec; +} + +/* [2] --- */ + + +/* [3] Public methods */ + +bool SmtpClient::connectToHost() +{ + switch (connectionType) + { + case TlsConnection: + case TcpConnection: + socket->connectToHost(host, port); + break; + case SslConnection: + ((QSslSocket*) socket)->connectToHostEncrypted(host, port); + break; + + } + + // Tries to connect to server + if (!socket->waitForConnected(connectionTimeout)) + { + emit smtpError(ConnectionTimeoutError); + return false; + } + + try + { + // Wait for the server's response + waitForResponse(); + + // If the response code is not 220 (Service ready) + // means that is something wrong with the server + if (responseCode != 220) + { + emit smtpError(ServerError); + return false; + } + + // Send a EHLO/HELO message to the server + // The client's first command must be EHLO/HELO + sendMessage("EHLO " + name); + + // Wait for the server's response + waitForResponse(); + + // The response code needs to be 250. + if (responseCode != 250) { + emit smtpError(ServerError); + return false; + } + + if (connectionType == TlsConnection) { + // send a request to start TLS handshake + sendMessage("STARTTLS"); + + // Wait for the server's response + waitForResponse(); + + // The response code needs to be 220. + if (responseCode != 220) { + emit smtpError(ServerError); + return false; + }; + + ((QSslSocket*) socket)->startClientEncryption(); + + if (!((QSslSocket*) socket)->waitForEncrypted(connectionTimeout)) { + qDebug() << ((QSslSocket*) socket)->errorString(); + emit smtpError(ConnectionTimeoutError); + return false; + } + + // Send ELHO one more time + sendMessage("EHLO " + name); + + // Wait for the server's response + waitForResponse(); + + // The response code needs to be 250. + if (responseCode != 250) { + emit smtpError(ServerError); + return false; + } + } + } + catch (ResponseTimeoutException) + { + return false; + } + catch (SendMessageTimeoutException) + { + return false; + } + + // If no errors occured the function returns true. + return true; +} + +bool SmtpClient::login() +{ + return login(user, password, authMethod); +} + +bool SmtpClient::login(const QString &user, const QString &password, AuthMethod method) +{ + try { + if (method == AuthPlain) + { + // Sending command: AUTH PLAIN base64('\0' + username + '\0' + password) + sendMessage("AUTH PLAIN " + QByteArray().append((char) 0).append(user).append((char) 0).append(password).toBase64()); + + // Wait for the server's response + waitForResponse(); + + // If the response is not 235 then the authentication was faild + if (responseCode != 235) + { + emit smtpError(AuthenticationFailedError); + return false; + } + } + else if (method == AuthLogin) + { + // Sending command: AUTH LOGIN + sendMessage("AUTH LOGIN"); + + // Wait for 334 response code + waitForResponse(); + if (responseCode != 334) { emit smtpError(AuthenticationFailedError); return false; } + + // Send the username in base64 + sendMessage(QByteArray().append(user).toBase64()); + + // Wait for 334 + waitForResponse(); + if (responseCode != 334) { emit smtpError(AuthenticationFailedError); return false; } + + // Send the password in base64 + sendMessage(QByteArray().append(password).toBase64()); + + // Wait for the server's responce + waitForResponse(); + + // If the response is not 235 then the authentication was faild + if (responseCode != 235) + { + emit smtpError(AuthenticationFailedError); + return false; + } + } + } + catch (ResponseTimeoutException e) + { + // Responce Timeout exceeded + emit smtpError(AuthenticationFailedError); + return false; + } + catch (SendMessageTimeoutException) + { + // Send Timeout exceeded + emit smtpError(AuthenticationFailedError); + return false; + } + + return true; +} + +bool SmtpClient::sendMail(MimeMessage& email) +{ + try + { + // Send the MAIL command with the sender + sendMessage("MAIL FROM: <" + email.getSender().getAddress() + ">"); + + waitForResponse(); + + if (responseCode != 250) return false; + + // Send RCPT command for each recipient + QList::const_iterator it, itEnd; + // To (primary recipients) + for (it = email.getRecipients().begin(), itEnd = email.getRecipients().end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Cc (carbon copy) + for (it = email.getRecipients(MimeMessage::Cc).begin(), itEnd = email.getRecipients(MimeMessage::Cc).end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Bcc (blind carbon copy) + for (it = email.getRecipients(MimeMessage::Bcc).begin(), itEnd = email.getRecipients(MimeMessage::Bcc).end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Send DATA command + sendMessage("DATA"); + waitForResponse(); + + if (responseCode != 354) return false; + + sendMessage(email.toString()); + + // Send \r\n.\r\n to end the mail data + sendMessage("."); + + waitForResponse(); + + if (responseCode != 250) return false; + } + catch (ResponseTimeoutException) + { + return false; + } + catch (SendMessageTimeoutException) + { + return false; + } + + return true; +} + +void SmtpClient::quit() +{ + sendMessage("QUIT"); +} + +/* [3] --- */ + + +/* [4] Protected methods */ + +void SmtpClient::waitForResponse() throw (ResponseTimeoutException) +{ + do { + if (!socket->waitForReadyRead(responseTimeout)) + { + emit smtpError(ResponseTimeoutError); + throw ResponseTimeoutException(); + } + + while (socket->canReadLine()) { + // Save the server's response + responseText = socket->readLine(); + + // Extract the respose code from the server's responce (first 3 digits) + responseCode = responseText.left(3).toInt(); + + if (responseCode / 100 == 4) + emit smtpError(ServerError); + + if (responseCode / 100 == 5) + emit smtpError(ClientError); + + if (responseText[3] == ' ') { return; } + } + } while (true); +} + +void SmtpClient::sendMessage(const QString &text) throw (SendMessageTimeoutException) +{ + socket->write(text.toUtf8() + "\r\n"); + if (! socket->waitForBytesWritten(sendMessageTimeout)) + { + emit smtpError(SendDataTimeoutError); + throw SendMessageTimeoutException(); + } +} + +/* [4] --- */ + + +/* [5] Slots for the socket's signals */ + +void SmtpClient::socketStateChanged(QAbstractSocket::SocketState state) +{ +} + +void SmtpClient::socketError(QAbstractSocket::SocketError socketError) +{ +} + +void SmtpClient::socketReadyRead() +{ +} + +/* [5] --- */ + + + + diff --git a/servatrice/src/smtp/smtpclient.h b/servatrice/src/smtp/smtpclient.h new file mode 100644 index 00000000..f734aad6 --- /dev/null +++ b/servatrice/src/smtp/smtpclient.h @@ -0,0 +1,184 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef SMTPCLIENT_H +#define SMTPCLIENT_H + +#include +#include + +#include "mimemessage.h" +#include "smtpexports.h" + +class SMTP_EXPORT SmtpClient : public QObject +{ + Q_OBJECT +public: + + /* [0] Enumerations */ + + enum AuthMethod + { + AuthPlain, + AuthLogin + }; + + enum SmtpError + { + ConnectionTimeoutError, + ResponseTimeoutError, + SendDataTimeoutError, + AuthenticationFailedError, + ServerError, // 4xx smtp error + ClientError // 5xx smtp error + }; + + enum ConnectionType + { + TcpConnection, + SslConnection, + TlsConnection // STARTTLS + }; + + /* [0] --- */ + + + /* [1] Constructors and Destructors */ + + SmtpClient(const QString & host = "localhost", int port = 25, ConnectionType ct = TcpConnection); + + ~SmtpClient(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + const QString& getHost() const; + void setHost(const QString &host); + + int getPort() const; + void setPort(int port); + + const QString& getName() const; + void setName(const QString &name); + + ConnectionType getConnectionType() const; + void setConnectionType(ConnectionType ct); + + const QString & getUser() const; + void setUser(const QString &user); + + const QString & getPassword() const; + void setPassword(const QString &password); + + SmtpClient::AuthMethod getAuthMethod() const; + void setAuthMethod(AuthMethod method); + + const QString & getResponseText() const; + int getResponseCode() const; + + int getConnectionTimeout() const; + void setConnectionTimeout(int msec); + + int getResponseTimeout() const; + void setResponseTimeout(int msec); + + int getSendMessageTimeout() const; + void setSendMessageTimeout(int msec); + + QTcpSocket* getSocket(); + + + /* [2] --- */ + + + /* [3] Public methods */ + + bool connectToHost(); + + bool login(); + bool login(const QString &user, const QString &password, AuthMethod method = AuthLogin); + + bool sendMail(MimeMessage& email); + + void quit(); + + + /* [3] --- */ + +protected: + + /* [4] Protected members */ + + QTcpSocket *socket; + + QString host; + int port; + ConnectionType connectionType; + QString name; + + QString user; + QString password; + AuthMethod authMethod; + + int connectionTimeout; + int responseTimeout; + int sendMessageTimeout; + + + QString responseText; + int responseCode; + + + class ResponseTimeoutException {}; + class SendMessageTimeoutException {}; + + /* [4] --- */ + + + /* [5] Protected methods */ + + void waitForResponse() throw (ResponseTimeoutException); + + void sendMessage(const QString &text) throw (SendMessageTimeoutException); + + /* [5] --- */ + +protected slots: + + /* [6] Protected slots */ + + void socketStateChanged(QAbstractSocket::SocketState state); + void socketError(QAbstractSocket::SocketError error); + void socketReadyRead(); + + /* [6] --- */ + + +signals: + + /* [7] Signals */ + + void smtpError(SmtpClient::SmtpError e); + + /* [7] --- */ + +}; + +#endif // SMTPCLIENT_H diff --git a/servatrice/src/smtp/smtpexports.h b/servatrice/src/smtp/smtpexports.h new file mode 100644 index 00000000..0c4919f3 --- /dev/null +++ b/servatrice/src/smtp/smtpexports.h @@ -0,0 +1,10 @@ +#ifndef SMTPEXPORTS_H +#define SMTPEXPORTS_H + +#ifdef SMTP_BUILD +#define SMTP_EXPORT Q_DECL_EXPORT +#else +#define SMTP_EXPORT Q_DECL_IMPORT +#endif + +#endif // SMTPEXPORTS_H From 13b8fd451604735c2e0392ea745c45e36844d1d1 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 24 May 2015 23:15:23 +0200 Subject: [PATCH 11/12] gcc -Werror=unused-parameter --- servatrice/src/smtp/smtpclient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servatrice/src/smtp/smtpclient.cpp b/servatrice/src/smtp/smtpclient.cpp index aaa59095..37988871 100644 --- a/servatrice/src/smtp/smtpclient.cpp +++ b/servatrice/src/smtp/smtpclient.cpp @@ -463,11 +463,11 @@ void SmtpClient::sendMessage(const QString &text) throw (SendMessageTimeoutExcep /* [5] Slots for the socket's signals */ -void SmtpClient::socketStateChanged(QAbstractSocket::SocketState state) +void SmtpClient::socketStateChanged(QAbstractSocket::SocketState /* state */) { } -void SmtpClient::socketError(QAbstractSocket::SocketError socketError) +void SmtpClient::socketError(QAbstractSocket::SocketError /* socketError */) { } From 657a5164e9a5b012487cafda6d95a6212a839d10 Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Mon, 25 May 2015 17:07:47 +0200 Subject: [PATCH 12/12] Fix servatrice compilation with msvc --- servatrice/src/smtp/smtpexports.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/servatrice/src/smtp/smtpexports.h b/servatrice/src/smtp/smtpexports.h index 0c4919f3..c9234be5 100644 --- a/servatrice/src/smtp/smtpexports.h +++ b/servatrice/src/smtp/smtpexports.h @@ -1,10 +1,15 @@ #ifndef SMTPEXPORTS_H #define SMTPEXPORTS_H +/* #ifdef SMTP_BUILD #define SMTP_EXPORT Q_DECL_EXPORT #else #define SMTP_EXPORT Q_DECL_IMPORT #endif +*/ + +// Servatrice compiles this statically in, so there's no need to declare exports +#define SMTP_EXPORT #endif // SMTPEXPORTS_H