1 #include "server_protocolhandler.h"
2 
3 #include "featureset.h"
4 #include "get_pb_extension.h"
5 #include "pb/commands.pb.h"
6 #include "pb/event_game_joined.pb.h"
7 #include "pb/event_list_rooms.pb.h"
8 #include "pb/event_notify_user.pb.h"
9 #include "pb/event_room_say.pb.h"
10 #include "pb/event_server_message.pb.h"
11 #include "pb/event_user_message.pb.h"
12 #include "pb/response.pb.h"
13 #include "pb/response_get_games_of_user.pb.h"
14 #include "pb/response_get_user_info.pb.h"
15 #include "pb/response_join_room.pb.h"
16 #include "pb/response_list_users.pb.h"
17 #include "pb/response_login.pb.h"
18 #include "pb/serverinfo_user.pb.h"
19 #include "server_database_interface.h"
20 #include "server_game.h"
21 #include "server_player.h"
22 #include "server_room.h"
23 
24 #include <QDateTime>
25 #include <QDebug>
26 #include <google/protobuf/descriptor.h>
27 #include <math.h>
28 
Server_ProtocolHandler(Server * _server,Server_DatabaseInterface * _databaseInterface,QObject * parent)29 Server_ProtocolHandler::Server_ProtocolHandler(Server *_server,
30                                                Server_DatabaseInterface *_databaseInterface,
31                                                QObject *parent)
32     : QObject(parent), Server_AbstractUserInterface(_server), deleted(false), databaseInterface(_databaseInterface),
33       authState(NotLoggedIn), acceptsUserListChanges(false), acceptsRoomListChanges(false),
34       idleClientWarningSent(false), timeRunning(0), lastDataReceived(0), lastActionReceived(0)
35 
36 {
37     connect(server, SIGNAL(pingClockTimeout()), this, SLOT(pingClockTimeout()));
38 }
39 
~Server_ProtocolHandler()40 Server_ProtocolHandler::~Server_ProtocolHandler()
41 {
42 }
43 
44 // This function must only be called from the thread this object lives in.
45 // The thread must not hold any server locks when calling this (e.g. clientsLock, roomsLock).
prepareDestroy()46 void Server_ProtocolHandler::prepareDestroy()
47 {
48     if (deleted)
49         return;
50     deleted = true;
51 
52     QMapIterator<int, Server_Room *> roomIterator(rooms);
53     while (roomIterator.hasNext())
54         roomIterator.next().value()->removeClient(this);
55 
56     QMap<int, QPair<int, int>> tempGames(getGames());
57 
58     server->roomsLock.lockForRead();
59     QMapIterator<int, QPair<int, int>> gameIterator(tempGames);
60     while (gameIterator.hasNext()) {
61         gameIterator.next();
62 
63         Server_Room *r = server->getRooms().value(gameIterator.value().first);
64         if (!r)
65             continue;
66         r->gamesLock.lockForRead();
67         Server_Game *g = r->getGames().value(gameIterator.key());
68         if (!g) {
69             r->gamesLock.unlock();
70             continue;
71         }
72         g->gameMutex.lock();
73         Server_Player *p = g->getPlayers().value(gameIterator.value().second);
74         if (!p) {
75             g->gameMutex.unlock();
76             r->gamesLock.unlock();
77             continue;
78         }
79 
80         p->disconnectClient();
81 
82         g->gameMutex.unlock();
83         r->gamesLock.unlock();
84     }
85     server->roomsLock.unlock();
86 
87     server->removeClient(this);
88 
89     deleteLater();
90 }
91 
sendProtocolItem(const Response & item)92 void Server_ProtocolHandler::sendProtocolItem(const Response &item)
93 {
94     ServerMessage msg;
95     msg.mutable_response()->CopyFrom(item);
96     msg.set_message_type(ServerMessage::RESPONSE);
97 
98     transmitProtocolItem(msg);
99 }
100 
sendProtocolItem(const SessionEvent & item)101 void Server_ProtocolHandler::sendProtocolItem(const SessionEvent &item)
102 {
103     ServerMessage msg;
104     msg.mutable_session_event()->CopyFrom(item);
105     msg.set_message_type(ServerMessage::SESSION_EVENT);
106 
107     transmitProtocolItem(msg);
108 }
109 
sendProtocolItem(const GameEventContainer & item)110 void Server_ProtocolHandler::sendProtocolItem(const GameEventContainer &item)
111 {
112     ServerMessage msg;
113     msg.mutable_game_event_container()->CopyFrom(item);
114     msg.set_message_type(ServerMessage::GAME_EVENT_CONTAINER);
115 
116     transmitProtocolItem(msg);
117 }
118 
sendProtocolItem(const RoomEvent & item)119 void Server_ProtocolHandler::sendProtocolItem(const RoomEvent &item)
120 {
121     ServerMessage msg;
122     msg.mutable_room_event()->CopyFrom(item);
123     msg.set_message_type(ServerMessage::ROOM_EVENT);
124 
125     transmitProtocolItem(msg);
126 }
127 
processSessionCommandContainer(const CommandContainer & cont,ResponseContainer & rc)128 Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(const CommandContainer &cont,
129                                                                               ResponseContainer &rc)
130 {
131     Response::ResponseCode finalResponseCode = Response::RespOk;
132     for (int i = cont.session_command_size() - 1; i >= 0; --i) {
133         Response::ResponseCode resp = Response::RespInvalidCommand;
134         const SessionCommand &sc = cont.session_command(i);
135         const int num = getPbExtension(sc);
136         if (num != SessionCommand::PING) {      // don't log ping commands
137             if (num == SessionCommand::LOGIN) { // log login commands, but hide passwords
138                 SessionCommand debugSc(sc);
139                 debugSc.MutableExtension(Command_Login::ext)->clear_password();
140                 logDebugMessage(QString::fromStdString(debugSc.ShortDebugString()));
141             } else if (num == SessionCommand::REGISTER) {
142                 SessionCommand logSc(sc);
143                 logSc.MutableExtension(Command_Register::ext)->clear_password();
144                 logDebugMessage(QString::fromStdString(logSc.ShortDebugString()));
145             } else
146                 logDebugMessage(QString::fromStdString(sc.ShortDebugString()));
147         }
148         switch ((SessionCommand::SessionCommandType)num) {
149             case SessionCommand::PING:
150                 resp = cmdPing(sc.GetExtension(Command_Ping::ext), rc);
151                 break;
152             case SessionCommand::LOGIN:
153                 resp = cmdLogin(sc.GetExtension(Command_Login::ext), rc);
154                 break;
155             case SessionCommand::MESSAGE:
156                 resp = cmdMessage(sc.GetExtension(Command_Message::ext), rc);
157                 break;
158             case SessionCommand::GET_GAMES_OF_USER:
159                 resp = cmdGetGamesOfUser(sc.GetExtension(Command_GetGamesOfUser::ext), rc);
160                 break;
161             case SessionCommand::GET_USER_INFO:
162                 resp = cmdGetUserInfo(sc.GetExtension(Command_GetUserInfo::ext), rc);
163                 break;
164             case SessionCommand::LIST_ROOMS:
165                 resp = cmdListRooms(sc.GetExtension(Command_ListRooms::ext), rc);
166                 break;
167             case SessionCommand::JOIN_ROOM:
168                 resp = cmdJoinRoom(sc.GetExtension(Command_JoinRoom::ext), rc);
169                 break;
170             case SessionCommand::LIST_USERS:
171                 resp = cmdListUsers(sc.GetExtension(Command_ListUsers::ext), rc);
172                 break;
173             default:
174                 resp = processExtendedSessionCommand(num, sc, rc);
175         }
176         if (resp != Response::RespOk)
177             finalResponseCode = resp;
178     }
179     return finalResponseCode;
180 }
181 
processRoomCommandContainer(const CommandContainer & cont,ResponseContainer & rc)182 Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const CommandContainer &cont,
183                                                                            ResponseContainer &rc)
184 {
185     if (authState == NotLoggedIn)
186         return Response::RespLoginNeeded;
187 
188     QReadLocker locker(&server->roomsLock);
189     Server_Room *room = rooms.value(cont.room_id(), 0);
190     if (!room)
191         return Response::RespNotInRoom;
192 
193     resetIdleTimer();
194 
195     Response::ResponseCode finalResponseCode = Response::RespOk;
196     for (int i = cont.room_command_size() - 1; i >= 0; --i) {
197         Response::ResponseCode resp = Response::RespInvalidCommand;
198         const RoomCommand &sc = cont.room_command(i);
199         const int num = getPbExtension(sc);
200         logDebugMessage(QString::fromStdString(sc.ShortDebugString()));
201         switch ((RoomCommand::RoomCommandType)num) {
202             case RoomCommand::LEAVE_ROOM:
203                 resp = cmdLeaveRoom(sc.GetExtension(Command_LeaveRoom::ext), room, rc);
204                 break;
205             case RoomCommand::ROOM_SAY:
206                 resp = cmdRoomSay(sc.GetExtension(Command_RoomSay::ext), room, rc);
207                 break;
208             case RoomCommand::CREATE_GAME:
209                 resp = cmdCreateGame(sc.GetExtension(Command_CreateGame::ext), room, rc);
210                 break;
211             case RoomCommand::JOIN_GAME:
212                 resp = cmdJoinGame(sc.GetExtension(Command_JoinGame::ext), room, rc);
213                 break;
214         }
215         if (resp != Response::RespOk)
216             finalResponseCode = resp;
217     }
218     return finalResponseCode;
219 }
220 
processGameCommandContainer(const CommandContainer & cont,ResponseContainer & rc)221 Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const CommandContainer &cont,
222                                                                            ResponseContainer &rc)
223 {
224     static QList<GameCommand::GameCommandType> antifloodCommandsWhiteList =
225         QList<GameCommand::GameCommandType>()
226         // draw/undo card draw (example: drawing 10 cards one by one from the deck)
227         << GameCommand::DRAW_CARDS
228         << GameCommand::UNDO_DRAW
229         // create, delete arrows (example: targeting with 10 cards during an attack)
230         << GameCommand::CREATE_ARROW
231         << GameCommand::DELETE_ARROW
232         // set card attributes (example: tapping 10 cards at once)
233         << GameCommand::SET_CARD_ATTR
234         // increment / decrement counter (example: -10 life points one by one)
235         << GameCommand::INC_COUNTER
236         // mulling lots of hands in a row
237         << GameCommand::MULLIGAN
238         // allows a user to sideboard without receiving flooding message
239         << GameCommand::MOVE_CARD;
240 
241     if (authState == NotLoggedIn)
242         return Response::RespLoginNeeded;
243 
244     QMap<int, QPair<int, int>> gameMap = getGames();
245     if (!gameMap.contains(cont.game_id()))
246         return Response::RespNotInRoom;
247     const QPair<int, int> roomIdAndPlayerId = gameMap.value(cont.game_id());
248 
249     QReadLocker roomsLocker(&server->roomsLock);
250     Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first);
251     if (!room)
252         return Response::RespNotInRoom;
253 
254     QReadLocker roomGamesLocker(&room->gamesLock);
255     Server_Game *game = room->getGames().value(cont.game_id());
256     if (!game) {
257         if (room->getExternalGames().contains(cont.game_id())) {
258             server->sendIsl_GameCommand(cont, room->getExternalGames().value(cont.game_id()).server_id(),
259                                         userInfo->session_id(), roomIdAndPlayerId.first, roomIdAndPlayerId.second);
260             return Response::RespNothing;
261         }
262         return Response::RespNotInRoom;
263     }
264 
265     QMutexLocker gameLocker(&game->gameMutex);
266     Server_Player *player = game->getPlayers().value(roomIdAndPlayerId.second);
267     if (!player)
268         return Response::RespNotInRoom;
269 
270     resetIdleTimer();
271 
272     int commandCountingInterval = server->getCommandCountingInterval();
273     int maxCommandCountPerInterval = server->getMaxCommandCountPerInterval();
274     GameEventStorage ges;
275     Response::ResponseCode finalResponseCode = Response::RespOk;
276     for (int i = cont.game_command_size() - 1; i >= 0; --i) {
277         const GameCommand &sc = cont.game_command(i);
278         logDebugMessage(QString("game %1 player %2: ").arg(cont.game_id()).arg(roomIdAndPlayerId.second) +
279                         QString::fromStdString(sc.ShortDebugString()));
280 
281         if (commandCountingInterval > 0) {
282             int totalCount = 0;
283             if (commandCountOverTime.isEmpty())
284                 commandCountOverTime.prepend(0);
285 
286             if (!antifloodCommandsWhiteList.contains((GameCommand::GameCommandType)getPbExtension(sc)))
287                 ++commandCountOverTime[0];
288 
289             for (int i = 0; i < commandCountOverTime.size(); ++i)
290                 totalCount += commandCountOverTime[i];
291 
292             if (totalCount > maxCommandCountPerInterval)
293                 return Response::RespChatFlood;
294         }
295 
296         Response::ResponseCode resp = player->processGameCommand(sc, rc, ges);
297 
298         if (resp != Response::RespOk)
299             finalResponseCode = resp;
300     }
301     ges.sendToGame(game);
302 
303     return finalResponseCode;
304 }
305 
processModeratorCommandContainer(const CommandContainer & cont,ResponseContainer & rc)306 Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer(const CommandContainer &cont,
307                                                                                 ResponseContainer &rc)
308 {
309     if (!userInfo)
310         return Response::RespLoginNeeded;
311     if (!(userInfo->user_level() & ServerInfo_User::IsModerator))
312         return Response::RespLoginNeeded;
313 
314     resetIdleTimer();
315 
316     Response::ResponseCode finalResponseCode = Response::RespOk;
317     for (int i = cont.moderator_command_size() - 1; i >= 0; --i) {
318         Response::ResponseCode resp = Response::RespInvalidCommand;
319         const ModeratorCommand &sc = cont.moderator_command(i);
320         const int num = getPbExtension(sc);
321         logDebugMessage(QString::fromStdString(sc.ShortDebugString()));
322 
323         resp = processExtendedModeratorCommand(num, sc, rc);
324         if (resp != Response::RespOk)
325             finalResponseCode = resp;
326     }
327     return finalResponseCode;
328 }
329 
processAdminCommandContainer(const CommandContainer & cont,ResponseContainer & rc)330 Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(const CommandContainer &cont,
331                                                                             ResponseContainer &rc)
332 {
333     if (!userInfo)
334         return Response::RespLoginNeeded;
335     if (!(userInfo->user_level() & ServerInfo_User::IsAdmin))
336         return Response::RespLoginNeeded;
337 
338     resetIdleTimer();
339 
340     Response::ResponseCode finalResponseCode = Response::RespOk;
341     for (int i = cont.admin_command_size() - 1; i >= 0; --i) {
342         Response::ResponseCode resp = Response::RespInvalidCommand;
343         const AdminCommand &sc = cont.admin_command(i);
344         const int num = getPbExtension(sc);
345         logDebugMessage(QString::fromStdString(sc.ShortDebugString()));
346 
347         resp = processExtendedAdminCommand(num, sc, rc);
348         if (resp != Response::RespOk)
349             finalResponseCode = resp;
350     }
351     return finalResponseCode;
352 }
353 
processCommandContainer(const CommandContainer & cont)354 void Server_ProtocolHandler::processCommandContainer(const CommandContainer &cont)
355 {
356     // Command processing must be disabled after prepareDestroy() has been called.
357     if (deleted)
358         return;
359 
360     lastDataReceived = timeRunning;
361 
362     ResponseContainer responseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1);
363     Response::ResponseCode finalResponseCode;
364 
365     if (cont.game_command_size())
366         finalResponseCode = processGameCommandContainer(cont, responseContainer);
367     else if (cont.room_command_size())
368         finalResponseCode = processRoomCommandContainer(cont, responseContainer);
369     else if (cont.session_command_size())
370         finalResponseCode = processSessionCommandContainer(cont, responseContainer);
371     else if (cont.moderator_command_size())
372         finalResponseCode = processModeratorCommandContainer(cont, responseContainer);
373     else if (cont.admin_command_size())
374         finalResponseCode = processAdminCommandContainer(cont, responseContainer);
375     else
376         finalResponseCode = Response::RespInvalidCommand;
377 
378     if ((finalResponseCode != Response::RespNothing))
379         sendResponseContainer(responseContainer, finalResponseCode);
380 }
381 
pingClockTimeout()382 void Server_ProtocolHandler::pingClockTimeout()
383 {
384 
385     int cmdcountinterval = server->getCommandCountingInterval();
386     int msgcountinterval = server->getMessageCountingInterval();
387     int pingclockinterval = server->getClientKeepAlive();
388 
389     int interval = server->getMessageCountingInterval();
390     if (interval > 0) {
391         if (pingclockinterval > 0) {
392             messageSizeOverTime.prepend(0);
393             if (messageSizeOverTime.size() > (msgcountinterval / pingclockinterval))
394                 messageSizeOverTime.removeLast();
395             messageCountOverTime.prepend(0);
396             if (messageCountOverTime.size() > (msgcountinterval / pingclockinterval))
397                 messageCountOverTime.removeLast();
398         }
399     }
400 
401     interval = server->getCommandCountingInterval();
402     if (interval > 0) {
403         if (pingclockinterval > 0) {
404             commandCountOverTime.prepend(0);
405             if (commandCountOverTime.size() > (cmdcountinterval / pingclockinterval))
406                 commandCountOverTime.removeLast();
407         }
408     }
409 
410     if (timeRunning - lastDataReceived > server->getMaxPlayerInactivityTime())
411         prepareDestroy();
412 
413     if (!userInfo || QString::fromStdString(userInfo->privlevel()).toLower() == "none") {
414         if ((server->getIdleClientTimeout() > 0) && (idleClientWarningSent)) {
415             if (timeRunning - lastActionReceived > server->getIdleClientTimeout()) {
416                 prepareDestroy();
417             }
418         }
419 
420         if (((timeRunning - lastActionReceived) >= ceil(server->getIdleClientTimeout() * .9)) &&
421             (!idleClientWarningSent) && (server->getIdleClientTimeout() > 0)) {
422             Event_NotifyUser event;
423             event.set_type(Event_NotifyUser::IDLEWARNING);
424             SessionEvent *se = prepareSessionEvent(event);
425             sendProtocolItem(*se);
426             delete se;
427             idleClientWarningSent = true;
428         }
429     }
430 
431     ++timeRunning;
432 }
433 
cmdPing(const Command_Ping &,ResponseContainer &)434 Response::ResponseCode Server_ProtocolHandler::cmdPing(const Command_Ping & /*cmd*/, ResponseContainer & /*rc*/)
435 {
436     return Response::RespOk;
437 }
438 
cmdLogin(const Command_Login & cmd,ResponseContainer & rc)439 Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd, ResponseContainer &rc)
440 {
441 
442     QString userName = QString::fromStdString(cmd.user_name()).simplified();
443     QString clientId = QString::fromStdString(cmd.clientid()).simplified();
444     QString clientVersion = QString::fromStdString(cmd.clientver()).simplified();
445 
446     if (userInfo != 0)
447         return Response::RespContextError;
448 
449     // check client feature set against server feature set
450     FeatureSet features;
451     QMap<QString, bool> receivedClientFeatures;
452     QMap<QString, bool> missingClientFeatures;
453 
454     for (int i = 0; i < cmd.clientfeatures().size(); ++i)
455         receivedClientFeatures.insert(QString::fromStdString(cmd.clientfeatures(i)).simplified(), false);
456 
457     missingClientFeatures =
458         features.identifyMissingFeatures(receivedClientFeatures, server->getServerRequiredFeatureList());
459 
460     if (!missingClientFeatures.isEmpty()) {
461         if (features.isRequiredFeaturesMissing(missingClientFeatures, server->getServerRequiredFeatureList())) {
462             Response_Login *re = new Response_Login;
463             re->set_denied_reason_str("Client upgrade required");
464             QMap<QString, bool>::iterator i;
465             for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i)
466                 re->add_missing_features(i.key().toStdString().c_str());
467             rc.setResponseExtension(re);
468             return Response::RespClientUpdateRequired;
469         }
470     }
471 
472     QString reasonStr;
473     int banSecondsLeft = 0;
474     QString connectionType = getConnectionType();
475     AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr,
476                                                  banSecondsLeft, clientId, clientVersion, connectionType);
477     switch (res) {
478         case UserIsBanned: {
479             Response_Login *re = new Response_Login;
480             re->set_denied_reason_str(reasonStr.toStdString());
481             if (banSecondsLeft != 0)
482                 re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsLeft).toTime_t());
483             rc.setResponseExtension(re);
484             return Response::RespUserIsBanned;
485         }
486         case NotLoggedIn:
487             return Response::RespWrongPassword;
488         case WouldOverwriteOldSession:
489             return Response::RespWouldOverwriteOldSession;
490         case UsernameInvalid: {
491             Response_Login *re = new Response_Login;
492             re->set_denied_reason_str(reasonStr.toStdString());
493             rc.setResponseExtension(re);
494             return Response::RespUsernameInvalid;
495         }
496         case RegistrationRequired:
497             return Response::RespRegistrationRequired;
498         case ClientIdRequired:
499             return Response::RespClientIdRequired;
500         case UserIsInactive:
501             return Response::RespAccountNotActivated;
502         default:
503             authState = res;
504     }
505 
506     // limit the number of non-privileged users that can connect to the server based on configuration settings
507     if (!userInfo || QString::fromStdString(userInfo->privlevel()).toLower() == "none") {
508         if (server->getMaxUserLimitEnabled()) {
509             if (server->getUsersCount() > server->getMaxUserTotal()) {
510                 qDebug() << "Max Users Total Limit Reached, please increase the max_users_total setting.";
511                 return Response::RespServerFull;
512             }
513         }
514     }
515 
516     userName = QString::fromStdString(userInfo->name());
517     Event_ServerMessage event;
518     event.set_message(server->getLoginMessage().toStdString());
519     rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event));
520 
521     Response_Login *re = new Response_Login;
522     re->mutable_user_info()->CopyFrom(copyUserInfo(true));
523 
524     if (authState == PasswordRight) {
525         QMapIterator<QString, ServerInfo_User> buddyIterator(databaseInterface->getBuddyList(userName));
526         while (buddyIterator.hasNext())
527             re->add_buddy_list()->CopyFrom(buddyIterator.next().value());
528 
529         QMapIterator<QString, ServerInfo_User> ignoreIterator(databaseInterface->getIgnoreList(userName));
530         while (ignoreIterator.hasNext())
531             re->add_ignore_list()->CopyFrom(ignoreIterator.next().value());
532     }
533 
534     // return to client any missing features the server has that the client does not
535     if (!missingClientFeatures.isEmpty()) {
536         QMap<QString, bool>::iterator i;
537         for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i)
538             re->add_missing_features(i.key().toStdString().c_str());
539     }
540 
541     joinPersistentGames(rc);
542     databaseInterface->removeForgotPassword(userName);
543     rc.setResponseExtension(re);
544     return Response::RespOk;
545 }
546 
cmdMessage(const Command_Message & cmd,ResponseContainer & rc)547 Response::ResponseCode Server_ProtocolHandler::cmdMessage(const Command_Message &cmd, ResponseContainer &rc)
548 {
549     if (authState == NotLoggedIn)
550         return Response::RespLoginNeeded;
551 
552     QReadLocker locker(&server->clientsLock);
553 
554     QString receiver = QString::fromStdString(cmd.user_name());
555     Server_AbstractUserInterface *userInterface = server->findUser(receiver);
556     if (!userInterface)
557         return Response::RespNameNotFound;
558     if (databaseInterface->isInIgnoreList(receiver, QString::fromStdString(userInfo->name())))
559         return Response::RespInIgnoreList;
560 
561     Event_UserMessage event;
562     event.set_sender_name(userInfo->name());
563     event.set_receiver_name(cmd.user_name());
564     event.set_message(cmd.message());
565 
566     SessionEvent *se = prepareSessionEvent(event);
567     userInterface->sendProtocolItem(*se);
568     rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, se);
569 
570     databaseInterface->logMessage(userInfo->id(), QString::fromStdString(userInfo->name()),
571                                   QString::fromStdString(userInfo->address()), QString::fromStdString(cmd.message()),
572                                   Server_DatabaseInterface::MessageTargetChat, userInterface->getUserInfo()->id(),
573                                   receiver);
574     resetIdleTimer();
575     return Response::RespOk;
576 }
577 
cmdGetGamesOfUser(const Command_GetGamesOfUser & cmd,ResponseContainer & rc)578 Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_GetGamesOfUser &cmd,
579                                                                  ResponseContainer &rc)
580 {
581     if (authState == NotLoggedIn)
582         return Response::RespLoginNeeded;
583 
584     // We don't need to check whether the user is logged in; persistent games should also work.
585     // The client needs to deal with an empty result list.
586 
587     Response_GetGamesOfUser *re = new Response_GetGamesOfUser;
588     server->roomsLock.lockForRead();
589     QMapIterator<int, Server_Room *> roomIterator(server->getRooms());
590     while (roomIterator.hasNext()) {
591         Server_Room *room = roomIterator.next().value();
592         room->gamesLock.lockForRead();
593         room->getInfo(*re->add_room_list(), false, true);
594         QListIterator<ServerInfo_Game> gameIterator(room->getGamesOfUser(QString::fromStdString(cmd.user_name())));
595         while (gameIterator.hasNext())
596             re->add_game_list()->CopyFrom(gameIterator.next());
597         room->gamesLock.unlock();
598     }
599     server->roomsLock.unlock();
600 
601     rc.setResponseExtension(re);
602     return Response::RespOk;
603 }
604 
cmdGetUserInfo(const Command_GetUserInfo & cmd,ResponseContainer & rc)605 Response::ResponseCode Server_ProtocolHandler::cmdGetUserInfo(const Command_GetUserInfo &cmd, ResponseContainer &rc)
606 {
607     if (authState == NotLoggedIn)
608         return Response::RespLoginNeeded;
609 
610     QString userName = QString::fromStdString(cmd.user_name());
611     Response_GetUserInfo *re = new Response_GetUserInfo;
612     if (userName.isEmpty())
613         re->mutable_user_info()->CopyFrom(*userInfo);
614     else {
615 
616         QReadLocker locker(&server->clientsLock);
617 
618         ServerInfo_User_Container *infoSource = server->findUser(userName);
619         if (!infoSource) {
620             re->mutable_user_info()->CopyFrom(databaseInterface->getUserData(userName, true));
621         } else {
622             re->mutable_user_info()->CopyFrom(
623                 infoSource->copyUserInfo(true, false, userInfo->user_level() & ServerInfo_User::IsModerator));
624         }
625     }
626 
627     rc.setResponseExtension(re);
628     return Response::RespOk;
629 }
630 
cmdListRooms(const Command_ListRooms &,ResponseContainer & rc)631 Response::ResponseCode Server_ProtocolHandler::cmdListRooms(const Command_ListRooms & /*cmd*/, ResponseContainer &rc)
632 {
633     if (authState == NotLoggedIn)
634         return Response::RespLoginNeeded;
635 
636     Event_ListRooms event;
637     QMapIterator<int, Server_Room *> roomIterator(server->getRooms());
638     while (roomIterator.hasNext())
639         roomIterator.next().value()->getInfo(*event.add_room_list(), false);
640     rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event));
641 
642     acceptsRoomListChanges = true;
643     return Response::RespOk;
644 }
645 
cmdJoinRoom(const Command_JoinRoom & cmd,ResponseContainer & rc)646 Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoom &cmd, ResponseContainer &rc)
647 {
648     if (authState == NotLoggedIn)
649         return Response::RespLoginNeeded;
650 
651     if (rooms.contains(cmd.room_id()))
652         return Response::RespContextError;
653 
654     QReadLocker serverLocker(&server->roomsLock);
655     Server_Room *r = server->getRooms().value(cmd.room_id(), 0);
656     if (!r)
657         return Response::RespNameNotFound;
658 
659     if (!(userInfo->user_level() & ServerInfo_User::IsModerator))
660         if (!(r->userMayJoin(*userInfo)))
661             return Response::RespUserLevelTooLow;
662 
663     r->addClient(this);
664     rooms.insert(r->getId(), r);
665 
666     Event_RoomSay joinMessageEvent;
667     joinMessageEvent.set_message(r->getJoinMessage().toStdString());
668     joinMessageEvent.set_message_type(Event_RoomSay::Welcome);
669     rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent));
670 
671     QReadLocker chatHistoryLocker(&r->historyLock);
672     QList<ServerInfo_ChatMessage> chatHistory = r->getChatHistory();
673     ServerInfo_ChatMessage chatMessage;
674     for (int i = 0; i < chatHistory.size(); ++i) {
675         chatMessage = chatHistory.at(i);
676         Event_RoomSay roomChatHistory;
677         roomChatHistory.set_message(chatMessage.sender_name() + ": " + chatMessage.message());
678         roomChatHistory.set_message_type(Event_RoomSay::ChatHistory);
679         roomChatHistory.set_time_of(
680             QDateTime::fromString(QString::fromStdString(chatMessage.time())).toMSecsSinceEpoch());
681         rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(roomChatHistory));
682     }
683 
684     Response_JoinRoom *re = new Response_JoinRoom;
685     r->getInfo(*re->mutable_room_info(), true);
686 
687     rc.setResponseExtension(re);
688     return Response::RespOk;
689 }
690 
cmdListUsers(const Command_ListUsers &,ResponseContainer & rc)691 Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUsers & /*cmd*/, ResponseContainer &rc)
692 {
693     if (authState == NotLoggedIn)
694         return Response::RespLoginNeeded;
695 
696     Response_ListUsers *re = new Response_ListUsers;
697     server->clientsLock.lockForRead();
698     QMapIterator<QString, Server_ProtocolHandler *> userIterator = server->getUsers();
699     while (userIterator.hasNext())
700         re->add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(false));
701     QMapIterator<QString, Server_AbstractUserInterface *> extIterator = server->getExternalUsers();
702     while (extIterator.hasNext())
703         re->add_user_list()->CopyFrom(extIterator.next().value()->copyUserInfo(false));
704 
705     acceptsUserListChanges = true;
706     server->clientsLock.unlock();
707 
708     rc.setResponseExtension(re);
709     return Response::RespOk;
710 }
711 
712 Response::ResponseCode
cmdLeaveRoom(const Command_LeaveRoom &,Server_Room * room,ResponseContainer &)713 Server_ProtocolHandler::cmdLeaveRoom(const Command_LeaveRoom & /*cmd*/, Server_Room *room, ResponseContainer & /*rc*/)
714 {
715     rooms.remove(room->getId());
716     room->removeClient(this);
717     return Response::RespOk;
718 }
719 
720 Response::ResponseCode
cmdRoomSay(const Command_RoomSay & cmd,Server_Room * room,ResponseContainer &)721 Server_ProtocolHandler::cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room, ResponseContainer & /*rc*/)
722 {
723     QString msg = QString::fromStdString(cmd.message());
724 
725     if (server->getMessageCountingInterval() > 0) {
726         int totalSize = 0, totalCount = 0;
727         if (messageSizeOverTime.isEmpty())
728             messageSizeOverTime.prepend(0);
729         messageSizeOverTime[0] += msg.size();
730         for (int i = 0; i < messageSizeOverTime.size(); ++i)
731             totalSize += messageSizeOverTime[i];
732 
733         if (messageCountOverTime.isEmpty())
734             messageCountOverTime.prepend(0);
735         ++messageCountOverTime[0];
736         for (int i = 0; i < messageCountOverTime.size(); ++i)
737             totalCount += messageCountOverTime[i];
738 
739         if ((totalSize > server->getMaxMessageSizePerInterval()) ||
740             (totalCount > server->getMaxMessageCountPerInterval()))
741             return Response::RespChatFlood;
742     }
743     msg.replace(QChar('\n'), QChar(' '));
744 
745     room->say(QString::fromStdString(userInfo->name()), msg);
746 
747     databaseInterface->logMessage(userInfo->id(), QString::fromStdString(userInfo->name()),
748                                   QString::fromStdString(userInfo->address()), msg,
749                                   Server_DatabaseInterface::MessageTargetRoom, room->getId(), room->getName());
750 
751     return Response::RespOk;
752 }
753 
754 Response::ResponseCode
cmdCreateGame(const Command_CreateGame & cmd,Server_Room * room,ResponseContainer & rc)755 Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room *room, ResponseContainer &rc)
756 {
757     if (authState == NotLoggedIn)
758         return Response::RespLoginNeeded;
759     const int gameId = databaseInterface->getNextGameId();
760     if (gameId == -1)
761         return Response::RespInternalError;
762 
763     if (server->getMaxGamesPerUser() > 0)
764         if (room->getGamesCreatedByUser(QString::fromStdString(userInfo->name())) >= server->getMaxGamesPerUser())
765             return Response::RespContextError;
766 
767     if (cmd.join_as_judge() && !server->permitCreateGameAsJudge() &&
768         !(userInfo->user_level() & ServerInfo_User::IsJudge)) {
769         return Response::RespContextError;
770     }
771 
772     QList<int> gameTypes;
773     for (int i = cmd.game_type_ids_size() - 1; i >= 0; --i)
774         gameTypes.append(cmd.game_type_ids(i));
775 
776     QString description = QString::fromStdString(cmd.description());
777     if (description.size() > 60)
778         description = description.left(60);
779 
780     // When server doesn't permit registered users to exist, do not honor only-reg setting
781     bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
782     Server_Game *game = new Server_Game(
783         copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes,
784         cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(),
785         cmd.spectators_can_talk(), cmd.spectators_see_everything(), room);
786 
787     game->addPlayer(this, rc, false, cmd.join_as_judge(), false);
788     room->addGame(game);
789 
790     return Response::RespOk;
791 }
792 
793 Response::ResponseCode
cmdJoinGame(const Command_JoinGame & cmd,Server_Room * room,ResponseContainer & rc)794 Server_ProtocolHandler::cmdJoinGame(const Command_JoinGame &cmd, Server_Room *room, ResponseContainer &rc)
795 {
796     if (authState == NotLoggedIn)
797         return Response::RespLoginNeeded;
798 
799     return room->processJoinGameCommand(cmd, rc, this);
800 }
801 
resetIdleTimer()802 void Server_ProtocolHandler::resetIdleTimer()
803 {
804     lastActionReceived = timeRunning;
805     idleClientWarningSent = false;
806 }
807