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