1 /*******************************************************************************
2  *
3  * Copyright (c) 2004-2012 Guillaume Cottenceau
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2, as
7  * published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  ******************************************************************************/
19 
20 /*
21  * this file holds game operations: create, join, list etc.
22  * it should be as far away as possible from network operations
23  */
24 
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <stdarg.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <sys/socket.h>
32 #include <regex.h>
33 
34 #include <glib.h>
35 
36 #include "net.h"
37 #include "tools.h"
38 #include "log.h"
39 #include "game.h"
40 
41 enum game_status { GAME_STATUS_OPEN, GAME_STATUS_CLOSED, GAME_STATUS_PLAYING };
42 
43 #define MAX_PLAYERS_PER_GAME 5
44 struct game
45 {
46         enum game_status status;
47         int players_number;
48         int players_conn[MAX_PLAYERS_PER_GAME];
49         char* players_nick[MAX_PLAYERS_PER_GAME];
50         int players_started[MAX_PLAYERS_PER_GAME];
51 };
52 
53 static GList * games = NULL;
54 static GList * open_players = NULL;
55 
56 static ssize_t amount_transmitted = 0;
57 
58 static char ok_pong[] = "PONG";
59 static char ok_player_joined[] = "JOINED: %s";
60 static char ok_player_parted[] = "PARTED: %s";
61 static char ok_player_kicked[] = "KICKED: %s";
62 static char ok_talk[] = "TALK: %s";
63 static char ok_can_start[] = "GAME_CAN_START: %s";
64 
65 static char wn_unknown_command[] = "UNKNOWN_COMMAND";
66 static char wn_missing_arguments[] = "MISSING_ARGUMENTS";
67 static char wn_nick_invalid[] = "INVALID_NICK";
68 static char wn_nick_in_use[] = "NICK_IN_USE";
69 static char wn_no_such_game[] = "NO_SUCH_GAME";
70 static char wn_game_full[] = "GAME_FULL";
71 static char wn_already_in_game[] = "ALREADY_IN_GAME";
72 static char wn_max_open_games[] = "ALREADY_MAX_OPEN_GAMES";
73 static char wn_not_started[] = "NOT_STARTED";
74 static char wn_already_ok_started[] = "ALREADY_OK_STARTED";
75 static char wn_not_in_game[] = "NOT_IN_GAME";
76 static char wn_alone_in_the_dark[] = "ALONE_IN_THE_DARK";
77 static char wn_not_creator[] = "NOT_CREATOR";
78 static char wn_no_such_player[] = "NO_SUCH_PLAYER";
79 static char wn_denied[] = "DENIED";
80 static char wn_flooding[] = "FLOODING";
81 static char wn_others_not_ready[] = "OTHERS_NOT_READY";
82 
83 static char fl_line_unrecognized[] = "MISSING_FB_PROTOCOL_TAG";
84 static char fl_proto_mismatch[] = "INCOMPATIBLE_PROTOCOL";
85 
86 char* nick[256];
87 char* geoloc[256];
88 char* IP[256];
89 int remote_proto_minor[256];
90 int admin_authorized[256];
91 
92 // calculate the list of players for a given game
list_game(const struct game * g)93 static char* list_game(const struct game * g)
94 {
95         char list_game_str[8192] = "";
96         int i;
97         for (i = 0; i < g->players_number; i++) {
98                 strconcat(list_game_str, g->players_nick[i], sizeof(list_game_str));
99                 if (i < g->players_number - 1)
100                         strconcat(list_game_str, ",", sizeof(list_game_str));
101         }
102         return memdup(list_game_str, strlen(list_game_str) + 1);
103 }
104 
105 // calculate the list of players for a given game with geolocation
list_game_with_geolocation(const struct game * g)106 static char* list_game_with_geolocation(const struct game * g)
107 {
108         char list_game_str[8192] = "";
109         int i;
110         char* n;
111         for (i = 0; i < g->players_number; i++) {
112                 strconcat(list_game_str, g->players_nick[i], sizeof(list_game_str));
113                 n = geoloc[g->players_conn[i]];
114                 if (n != NULL) {
115                         strconcat(list_game_str, ":", sizeof(list_game_str));
116                         strconcat(list_game_str, n, sizeof(list_game_str));
117                 }
118                 if (i < g->players_number - 1)
119                         strconcat(list_game_str, ",", sizeof(list_game_str));
120         }
121         return memdup(list_game_str, strlen(list_game_str) + 1);
122 }
123 
124 static char list_games_str[16384] __attribute__((aligned(4096))) = "";
125 static char list_playing_geolocs_str[16384] __attribute__((aligned(4096))) = "";
126 static int players_in_game;
127 static int games_open;
128 static int games_running;
list_open_nicks_aux(gpointer data,gpointer user_data)129 static void list_open_nicks_aux(gpointer data, gpointer user_data)
130 {
131         char* n = nick[GPOINTER_TO_INT(data)];
132         if (n == NULL)
133                 return;
134         strconcat(list_games_str, n, sizeof(list_games_str));
135         n = geoloc[GPOINTER_TO_INT(data)];
136         if (n != NULL) {
137                 strconcat(list_games_str, ":", sizeof(list_games_str));
138                 strconcat(list_games_str, n, sizeof(list_games_str));
139         }
140         strconcat(list_games_str, ",", sizeof(list_games_str));
141 }
list_games_aux(gpointer data,gpointer user_data)142 static void list_games_aux(gpointer data, gpointer user_data)
143 {
144         const struct game* g = data;
145         if (g->status == GAME_STATUS_OPEN) {
146                 char* game;
147                 games_open++;
148                 strconcat(list_games_str, "[", sizeof(list_games_str));
149                 game = list_game(g);
150                 strconcat(list_games_str, game, sizeof(list_games_str));
151                 free(game);
152                 strconcat(list_games_str, "]", sizeof(list_games_str));
153         } else {
154                 int i;
155                 char* geo;
156                 players_in_game += g->players_number;
157                 games_running++;
158                 for (i = 0; i < g->players_number; i++) {
159                         geo = geoloc[g->players_conn[i]];
160                         if (geo != NULL) {
161                                 strconcat(list_playing_geolocs_str, geo, sizeof(list_playing_geolocs_str));
162                                 strconcat(list_playing_geolocs_str, ",", sizeof(list_playing_geolocs_str));
163                         }
164                 }
165                 return;
166         }
167 }
168 /* Game list is of the following scheme:
169  * 1.1 protocol:
170  * <list-of-open-players format="NICK|NICK:GEOLOC"> [<list-of-open-games format=<list-of-players format="NICK">>] free:%d games:%d playing:%d at:<list-of-playing-geolocs>
171  * 1.0 protocol:
172  * <list-of-open-players format="NICK|NICK:GEOLOC"> [<list-of-open-games format=<list-of-players format="NICK">>] free:%d games:%d playing:%d
173  */
calculate_list_games(void)174 void calculate_list_games(void)
175 {
176         char * free_players;
177         list_games_str[0] = '\0';
178         list_playing_geolocs_str[0] = '\0';
179         players_in_game = 0;
180         games_open = 0;
181         games_running = 0;
182         g_list_foreach(open_players, list_open_nicks_aux, NULL);
183         strconcat(list_games_str, " ", sizeof(list_games_str));
184         g_list_foreach(games, list_games_aux, NULL);
185         free_players = asprintf_(" free:%d games:%d playing:%d at:%s", conns_nb() - players_in_game - 1, games_running, players_in_game, list_playing_geolocs_str);  // 1: don't count myself
186         strconcat(list_games_str, free_players, sizeof(list_games_str));
187         free(free_players);
188 }
189 
create_game(int fd,char * nick)190 static void create_game(int fd, char* nick)
191 {
192         struct game * g = malloc_(sizeof(struct game));
193         g->players_number = 1;
194         g->players_conn[0] = fd;
195         g->players_nick[0] = nick;
196         g->status = GAME_STATUS_OPEN;
197         games = g_list_append(games, g);
198         open_players = g_list_remove(open_players, GINT_TO_POINTER(fd));
199         calculate_list_games();
200 }
201 
add_player(struct game * g,int fd,char * nick)202 static int add_player(struct game * g, int fd, char* nick)
203 {
204         char joined_msg[1000];
205         int i;
206         if (g->players_number < MAX_PLAYERS_PER_GAME) {
207                 /* inform other players */
208                 snprintf(joined_msg, sizeof(joined_msg), ok_player_joined, nick);
209                 for (i = 0; i < g->players_number; i++)
210                         send_line_log_push(g->players_conn[i], joined_msg);
211 
212                 g->players_conn[g->players_number] = fd;
213                 g->players_nick[g->players_number] = nick;
214                 g->players_number++;
215                 open_players = g_list_remove(open_players, GINT_TO_POINTER(fd));
216                 calculate_list_games();
217                 return 1;
218         } else {
219                 free(nick);
220                 return 0;
221         }
222 }
223 
find_game_by_nick_aux(gconstpointer game,gconstpointer nick)224 static int find_game_by_nick_aux(gconstpointer game, gconstpointer nick)
225 {
226         const struct game * g = game;
227         if (g->status == GAME_STATUS_OPEN
228             && streq(g->players_nick[0], (char *) nick))
229                 return 0;
230         else
231                 return 1;
232 }
find_game_by_nick(char * nick)233 static struct game* find_game_by_nick(char* nick)
234 {
235         return GListp2data(g_list_find_custom(games, nick, find_game_by_nick_aux));
236 }
237 
find_game_by_fd_aux(gconstpointer game,gconstpointer fd)238 static int find_game_by_fd_aux(gconstpointer game, gconstpointer fd)
239 {
240         const struct game* g = game;
241         int fd_ = GPOINTER_TO_INT(fd);
242         int i;
243         for (i = 0; i < g->players_number; i++)
244                 if (g->players_conn[i] == fd_)
245                         return 0;
246         return 1;
247 }
find_game_by_fd(int fd)248 static struct game* find_game_by_fd(int fd)
249 {
250         return GListp2data(g_list_find_custom(games, GINT_TO_POINTER(fd), find_game_by_fd_aux));
251 }
252 
find_player_number(struct game * g,int fd)253 int find_player_number(struct game *g, int fd)
254 {
255         int i;
256         for (i = 0; i < g->players_number; i++)
257                 if (g->players_conn[i] == fd)
258                         return i;
259         l0(OUTPUT_TYPE_ERROR, "Internal error");
260         exit(EXIT_FAILURE);
261 }
262 
real_start_game(struct game * g)263 static void real_start_game(struct game* g)
264 {
265         int i;
266         char mapping_str[4096] = "";
267         char can_start_msg[1000];
268         for (i = 0; i < g->players_number; i++) {
269                 int len = strlen(mapping_str);
270                 if (len >= sizeof(mapping_str)-1)
271                         return;
272                 mapping_str[len] = g->players_conn[i];
273                 mapping_str[len+1] = '\0';
274                 strconcat(mapping_str, g->players_nick[i], sizeof(mapping_str));
275                 if (i < g->players_number - 1)
276                         strconcat(mapping_str, ",", sizeof(mapping_str));
277         }
278         snprintf(can_start_msg, sizeof(can_start_msg), ok_can_start, mapping_str);
279         for (i = 0; i < g->players_number; i++) {
280                 send_line_log_push_binary(g->players_conn[i], can_start_msg, ok_can_start);
281                 g->players_started[i] = 0;
282         }
283         g->status = GAME_STATUS_PLAYING;
284 }
285 
start_game(int fd)286 static void start_game(int fd)
287 {
288         struct game * g = find_game_by_fd(fd);
289         if (g) {
290                 if (g->players_conn[0] == fd) {
291                         if (g->players_number == 1) {
292                                 send_line_log(fd, wn_alone_in_the_dark, "START");
293                                 return;
294                         }
295                         send_ok(fd, "START");
296                         real_start_game(g);
297                         calculate_list_games();
298                         l2(OUTPUT_TYPE_INFO, "running games increments to: %d (%d players)", games_running, players_in_game);
299                 } else {
300                         send_line_log(fd, wn_not_creator, "START");
301                 }
302 
303         } else {
304                 l0(OUTPUT_TYPE_ERROR, "Internal error");
305                 exit(EXIT_FAILURE);
306         }
307 }
308 
close_game(int fd)309 static void close_game(int fd)
310 {
311         struct game * g = find_game_by_fd(fd);
312         if (g) {
313                 if (g->players_conn[0] == fd) {
314                         if (g->players_number == 1) {
315                                 send_line_log(fd, wn_alone_in_the_dark, "CLOSE");
316                                 return;
317                         }
318                         send_ok(fd, "CLOSE");
319                         g->status = GAME_STATUS_CLOSED;
320                         calculate_list_games();
321                 } else {
322                         send_line_log(fd, wn_not_creator, "CLOSE");
323                 }
324 
325         } else {
326                 l0(OUTPUT_TYPE_ERROR, "Internal error");
327                 exit(EXIT_FAILURE);
328         }
329 }
330 
min_protocol_level(struct game * g)331 static int min_protocol_level(struct game* g)
332 {
333         int i;
334         int minor = remote_proto_minor[g->players_conn[0]];
335         for (i = 1; i < g->players_number; i++)
336                 minor = MIN(minor, remote_proto_minor[g->players_conn[i]]);
337         return minor;
338 }
339 
setoptions(int fd,char * options)340 static void setoptions(int fd, char* options)
341 {
342         struct game * g = find_game_by_fd(fd);
343         if (g) {
344                 if (g->players_conn[0] == fd) {
345                         int i;
346                         char* msg;
347                         send_ok(fd, "SETOPTIONS");
348                         msg = asprintf_("OPTIONS: %s,PROTOCOLLEVEL:%d", options, min_protocol_level(g));
349                         for (i = 0; i < g->players_number; i++)
350                                 if (remote_proto_minor[g->players_conn[i]] >= 1)
351                                         send_line_log_push(g->players_conn[i], msg);
352                         free(msg);
353                 } else {
354                         send_line_log(fd, wn_not_creator, "SETOPTIONS");
355                 }
356 
357         } else {
358                 l0(OUTPUT_TYPE_ERROR, "Internal error");
359                 exit(EXIT_FAILURE);
360         }
361 }
362 
leader_check_game_start(int fd)363 static void leader_check_game_start(int fd)
364 {
365         struct game * g = find_game_by_fd(fd);
366         if (g) {
367                 if (g->status == GAME_STATUS_PLAYING) {
368                         int i;
369                         for (i = 0; i < g->players_number; i++) {
370                                 if (fd != g->players_conn[i]) {
371                                         if (!g->players_started[i]) {
372                                                 send_line_log(fd, wn_others_not_ready, "LEADER_CHECK_GAME_START");
373                                                 return;
374                                         }
375                                 }
376                         }
377                         send_ok(fd, "LEADER_CHECK_GAME_START");
378                 } else {
379                         send_line_log(fd, wn_not_started, "LEADER_CHECK_GAME_START");
380                 }
381         } else {
382                 l0(OUTPUT_TYPE_ERROR, "Internal error");
383                 exit(EXIT_FAILURE);
384         }
385 }
386 
ok_start_game(int fd)387 static void ok_start_game(int fd)
388 {
389         struct game * g = find_game_by_fd(fd);
390         if (g) {
391                 if (g->status == GAME_STATUS_PLAYING) {
392                         int i;
393                         for (i = 0; i < g->players_number; i++) {
394                                 if (g->players_conn[i] == fd) {
395                                         if (!g->players_started[i]) {
396                                                 if (remote_proto_minor[g->players_conn[i]] >= 1)
397                                                         send_ok(fd, "OK_GAME_START");
398                                                 g->players_started[i] = 1;
399                                                 l1(OUTPUT_TYPE_DEBUG, "[%d] entering prio mode", g->players_conn[i]);
400                                                 add_prio(g->players_conn[i]);
401                                         } else {
402                                                 send_line_log(fd, wn_already_ok_started, "OK_GAME_START");
403                                         }
404                                 }
405                         }
406                 } else {
407                         send_line_log(fd, wn_not_started, "OK_GAME_START");
408                 }
409         } else {
410                 l0(OUTPUT_TYPE_ERROR, "Internal error");
411                 exit(EXIT_FAILURE);
412         }
413 }
414 
kick_player(int fd,struct game * g,char * nick)415 static void kick_player(int fd, struct game * g, char * nick)
416 {
417         int i;
418         for (i = 0; i < g->players_number; i++) {
419                 if (g->players_conn[i] != fd && streq(g->players_nick[i], nick)) {
420                         send_ok(fd, "KICK");
421                         send_line_log_push(g->players_conn[i], "KICKED");
422                         player_part_game_(g->players_conn[i], ok_player_kicked);
423                         return;
424                 }
425         }
426         send_line_log(fd, wn_no_such_player, "KICK");
427 }
428 
player_connects(int fd)429 void player_connects(int fd)
430 {
431         open_players = g_list_append(open_players, GINT_TO_POINTER(fd));
432 }
433 
player_disconnects(int fd)434 void player_disconnects(int fd)
435 {
436         open_players = g_list_remove(open_players, GINT_TO_POINTER(fd));
437 }
438 
talk_serverwide_aux(gpointer data,gpointer user_data)439 static void talk_serverwide_aux(gpointer data, gpointer user_data)
440 {
441         send_line_log_push(GPOINTER_TO_INT(data), user_data);
442 }
443 
check_match_alert_words(gconstpointer data,gconstpointer user_data)444 static gboolean check_match_alert_words(gconstpointer data, gconstpointer user_data)
445 {
446         const regex_t* preg = data;
447         const char* msg = user_data;
448         if (regexec(preg, msg, 0, NULL, 0) == 0) {
449                 return TRUE;
450         }
451         return FALSE;
452 }
453 
talk(int fd,char * msg)454 static void talk(int fd, char* msg)
455 {
456         struct game * g = find_game_by_fd(fd);
457         char talk_msg[1000];
458 
459         if (g_list_any(alert_words, check_match_alert_words, msg))
460                 l2(OUTPUT_TYPE_INFO, "message '%s' from %s matches alert words!", msg, IP[fd]);
461 
462         amount_talk_flood[fd]++;
463         if (amount_talk_flood[fd] == 15) {
464                 l1(OUTPUT_TYPE_INFO, "'%s' is flooding!", IP[fd]);
465                 send_line_log(fd, wn_flooding, msg);
466                 conn_terminated(fd, "flooding");
467                 return;
468         }
469 
470         snprintf(talk_msg, sizeof(talk_msg), ok_talk, msg);
471         if (g) {
472                 // player is in a game, it's a game-only chat
473                 int i;
474                 for (i = 0; i < g->players_number; i++)
475                         send_line_log_push(g->players_conn[i], talk_msg);
476         } else {
477                 // player is not in a game, it's a server-wide chat
478                 g_list_foreach(open_players, talk_serverwide_aux, talk_msg);
479         }
480 }
481 
status(int fd,char * msg)482 static void status(int fd, char* msg)
483 {
484         struct game * g = find_game_by_fd(fd);
485         if (g) {
486                 char* game = list_game(g);
487                 send_line_log(fd, game, msg);
488                 free(game);
489         } else {
490                 send_line_log(fd, wn_not_in_game, msg);
491         }
492 }
493 
status_geo(int fd,char * msg)494 static void status_geo(int fd, char* msg)
495 {
496         struct game * g = find_game_by_fd(fd);
497         if (g) {
498                 char* game = list_game_with_geolocation(g);
499                 send_line_log(fd, game, msg);
500                 free(game);
501         } else {
502                 send_line_log(fd, wn_not_in_game, msg);
503         }
504 }
505 
protocol_level(int fd,char * msg)506 static void protocol_level(int fd, char* msg)
507 {
508         // Find the smallest minor protocol level among players in game
509         struct game * g = find_game_by_fd(fd);
510         if (g) {
511                 char* response;
512                 int level = min_protocol_level(g);
513                 response = asprintf_("%d", level);
514                 send_line_log(fd, response, msg);
515                 free(response);
516         } else {
517                 send_line_log(fd, wn_not_in_game, msg);
518         }
519 }
520 
nick_available_aux(gconstpointer data,gconstpointer user_data)521 static gboolean nick_available_aux(gconstpointer data, gconstpointer user_data)
522 {
523         const struct game* g = data;
524         const char* nick = user_data;
525         int i;
526         for (i = 0; i < g->players_number; i++)
527                 if (streq(g->players_nick[i], nick))
528                         return TRUE;
529         return FALSE;
530 }
nick_available(char * nick)531 static int nick_available(char* nick)
532 {
533         return !g_list_any(games, nick_available_aux, nick);
534 }
535 
already_in_game_aux(gconstpointer data,gconstpointer user_data)536 static gboolean already_in_game_aux(gconstpointer data, gconstpointer user_data)
537 {
538         const struct game* g = data;
539         int fd = GPOINTER_TO_INT(user_data);
540         int i;
541         for (i = 0; i < g->players_number; i++)
542                 if (g->players_conn[i] == fd)
543                         return TRUE;
544         return FALSE;
545 }
already_in_game(int fd)546 static int already_in_game(int fd)
547 {
548         return g_list_any(games, already_in_game_aux, GINT_TO_POINTER(fd));
549 }
550 
is_nick_ok(char * nick)551 static int is_nick_ok(char* nick)
552 {
553         int i;
554         if (strlen(nick) > 10)
555                 return 0;
556         for (i = 0; i < strlen(nick); i++) {
557                 if (!((nick[i] >= 'a' && nick[i] <= 'z')
558                       || (nick[i] >= 'A' && nick[i] <= 'Z')
559                       || (nick[i] >= '0' && nick[i] <= '9')
560                       || nick[i] == '-' || nick[i] == '_')) {
561                         return 0;
562                 }
563         }
564         return 1;
565 }
566 
567 /* true return value indicates that connection must be closed */
process_msg(int fd,char * msg)568 int process_msg(int fd, char* msg)
569 {
570         int client_proto_major;
571         int client_proto_minor;
572         char * args;
573         char * ptr, * ptr2;
574         char * msg_orig;
575 
576         /* check for leading protocol tag */
577         if (!str_begins_static_str(msg, "FB/")
578             || strlen(msg) < 8) {  // 8 stands for "FB/M.m f"(oo)
579                 send_line_log(fd, fl_line_unrecognized, msg);
580                 return 1;
581         }
582 
583         /* check if client protocol is compatible; for simplicity, we don't support client protocol more recent
584          * than server protocol, we suppose that our servers are upgraded when a new release appears (but of
585          * course client protocol older is supported within the major protocol) */
586         client_proto_major = charstar_to_int(msg + 3);
587         client_proto_minor = charstar_to_int(msg + 5);
588         if (client_proto_major != proto_major
589             || client_proto_minor > proto_minor) {
590                 send_line_log(fd, fl_proto_mismatch, msg);
591                 return 1;
592         }
593 
594         if (remote_proto_minor[fd] == -1)
595                 remote_proto_minor[fd] = client_proto_minor;
596 
597         msg_orig = strdup(msg);
598 
599         /* after protocol, first word is command, then possible args */
600         current_command = msg + 7; // 7 stands for "FB/M.m "
601         if ((ptr = strchr(current_command, ' '))) {
602                 *ptr = '\0';
603                 args = current_command + strlen(current_command) + 1;
604         } else
605                 args = NULL;
606 
607         if (streq(current_command, "PING")) {
608                 send_line_log(fd, ok_pong, msg_orig);
609         } else if (streq(current_command, "NICK")) {
610                 if (!args) {
611                         send_line_log(fd, wn_missing_arguments, msg_orig);
612                 } else {
613                         if ((ptr = strchr(args, ' ')))
614                                 *ptr = '\0';
615                         if (strlen(args) > 10)
616                                 args[10] = '\0';
617                         if (!is_nick_ok(args)) {
618                                 send_line_log(fd, wn_nick_invalid, msg_orig);
619                         } else {
620                                 if (nick[fd] != NULL) {
621                                         free(nick[fd]);
622                                 }
623                                 nick[fd] = strdup(args);
624                                 calculate_list_games();
625                                 send_ok(fd, msg_orig);
626                         }
627                 }
628         } else if (streq(current_command, "GEOLOC")) {
629                 if (!args) {
630                         send_line_log(fd, wn_missing_arguments, msg_orig);
631                 } else {
632                         if ((ptr = strchr(args, ' ')))
633                                 *ptr = '\0';
634                         if (strlen(args) > 13)  // sign, 4 digits, dot, colon, sign, 4 digits, dot
635                                 args[13] = '\0';
636                         if (geoloc[fd] != NULL) {
637                                 free(geoloc[fd]);
638                         }
639                         geoloc[fd] = strdup(args);
640                         calculate_list_games();
641                         send_ok(fd, msg_orig);
642                 }
643         } else if (streq(current_command, "CREATE")) {
644                 if (!args) {
645                         send_line_log(fd, wn_missing_arguments, msg_orig);
646                 } else {
647                         if ((ptr = strchr(args, ' ')))
648                                 *ptr = '\0';
649                         if (strlen(args) > 10)
650                                 args[10] = '\0';
651                         if (!is_nick_ok(args)) {
652                                 send_line_log(fd, wn_nick_invalid, msg_orig);
653                         } else if (!nick_available(args)) {
654                                 send_line_log(fd, wn_nick_in_use, msg_orig);
655                         } else if (already_in_game(fd)) {
656                                 send_line_log(fd, wn_already_in_game, msg_orig);
657                         } else if (games_open == 16) {  // FB client can display 16 max
658                                 send_line_log(fd, wn_max_open_games, msg_orig);
659                         } else {
660                                 create_game(fd, strdup(args));
661                                 send_ok(fd, msg_orig);
662                         }
663                 }
664         } else if (streq(current_command, "JOIN")) {
665                 if (!args || !(ptr = strchr(args, ' '))) {
666                         send_line_log(fd, wn_missing_arguments, msg_orig);
667                 } else {
668                         struct game * g;
669                         char* nick = ptr + 1;
670                         *ptr = '\0';
671                         if ((ptr2 = strchr(ptr, ' ')))
672                                 *ptr2 = '\0';
673                         if (strlen(nick) > 10)
674                                 nick[10] = '\0';
675                         if (!is_nick_ok(nick)) {
676                                 send_line_log(fd, wn_nick_invalid, msg_orig);
677                         } else if (!nick_available(nick)) {
678                                 send_line_log(fd, wn_nick_in_use, msg_orig);
679                         } else if (already_in_game(fd)) {
680                                 send_line_log(fd, wn_already_in_game, msg_orig);
681                         } else if (!(g = find_game_by_nick(args))) {
682                                 send_line_log(fd, wn_no_such_game, msg_orig);
683                         } else {
684                                 if (add_player(g, fd, strdup(nick)))
685                                         send_ok(fd, msg_orig);
686                                 else
687                                         send_line_log(fd, wn_game_full, msg_orig);
688                         }
689                 }
690         } else if (streq(current_command, "KICK")) {
691                 if (!args) {
692                         send_line_log(fd, wn_missing_arguments, msg_orig);
693                 } else {
694                         if ((ptr = strchr(args, ' ')))
695                                 *ptr = '\0';
696                         if (strlen(args) > 10)
697                                 args[10] = '\0';
698                         if (!already_in_game(fd)) {
699                                 send_line_log(fd, wn_not_in_game, msg_orig);
700                         } else {
701                                 struct game * g = find_game_by_fd(fd);
702                                 if (g->players_conn[0] != fd) {
703                                         send_line_log(fd, wn_not_creator, msg_orig);
704                                 } else {
705                                         kick_player(fd, g, args);
706                                 }
707                         }
708                 }
709         } else if (streq(current_command, "PART")) {
710                 if (!already_in_game(fd)) {
711                         send_line_log(fd, wn_not_in_game, msg_orig);
712                 } else {
713                         player_part_game(fd);
714                         send_ok(fd, msg_orig);
715                 }
716         } else if (streq(current_command, "LIST")) {
717                 send_line_log(fd, list_games_str, msg_orig);
718         } else if (streq(current_command, "STATUS")) {  // 1.0 command
719                 if (!already_in_game(fd)) {
720                         send_line_log(fd, wn_not_in_game, msg_orig);
721                 } else {
722                         status(fd, msg_orig);
723                 }
724         } else if (streq(current_command, "STATUSGEO")) {
725                 if (!already_in_game(fd)) {
726                         send_line_log(fd, wn_not_in_game, msg_orig);
727                 } else {
728                         status_geo(fd, msg_orig);
729                 }
730         } else if (streq(current_command, "PROTOCOL_LEVEL")) {
731                 if (!already_in_game(fd)) {
732                         send_line_log(fd, wn_not_in_game, msg_orig);
733                 } else {
734                         protocol_level(fd, msg_orig);
735                 }
736         } else if (streq(current_command, "TALK")) {
737                 if (!args) {
738                         send_line_log(fd, wn_missing_arguments, msg_orig);
739                 } else {
740                         talk(fd, args);
741                 }
742         } else if (streq(current_command, "START")) {
743                 if (!already_in_game(fd)) {
744                         send_line_log(fd, wn_not_in_game, msg_orig);
745                 } else {
746                         start_game(fd);
747                 }
748         } else if (streq(current_command, "CLOSE")) {
749                 if (!already_in_game(fd)) {
750                         send_line_log(fd, wn_not_in_game, msg_orig);
751                 } else {
752                         close_game(fd);
753                 }
754         } else if (streq(current_command, "SETOPTIONS")) {
755                 if (!args) {
756                         send_line_log(fd, wn_missing_arguments, msg_orig);
757                 } else if (!already_in_game(fd)) {
758                         send_line_log(fd, wn_not_in_game, msg_orig);
759                 } else {
760                         setoptions(fd, args);
761                 }
762         } else if (streq(current_command, "LEADER_CHECK_GAME_START")) {
763                 if (!already_in_game(fd)) {
764                         send_line_log(fd, wn_not_in_game, msg_orig);
765                 } else {
766                         leader_check_game_start(fd);
767                 }
768         } else if (streq(current_command, "OK_GAME_START")) {
769                 if (!already_in_game(fd)) {
770                         send_line_log(fd, wn_not_in_game, msg_orig);
771                 } else {
772                         ok_start_game(fd);
773                 }
774         } else if (streq(current_command, "ADMIN_REREAD")) {
775                 if (!admin_authorized[fd]) {
776                         send_line_log(fd, wn_denied, msg_orig);
777                 } else {
778                         reread();
779                         send_ok(fd, "ADMIN_REREAD");
780                 }
781         } else {
782                 send_line_log(fd, wn_unknown_command, msg);
783         }
784 
785         free(msg_orig);
786         current_command = NULL;
787 
788         return 0;
789 }
790 
791 
get_reset_amount_transmitted(void)792 ssize_t get_reset_amount_transmitted(void)
793 {
794         ssize_t ret = amount_transmitted;
795         amount_transmitted = 0;
796         return ret;
797 }
798 
conn_to_terminate_helper(gpointer data,gpointer user_data)799 static void conn_to_terminate_helper(gpointer data, gpointer user_data)
800 {
801         conn_terminated(GPOINTER_TO_INT(data), "system error on send (probably peer shutdown or try again)");
802 }
803 
process_msg_prio_(int fd,char * msg,ssize_t len,struct game * g)804 void process_msg_prio_(int fd, char* msg, ssize_t len, struct game* g)
805 {
806         GList * conn_to_terminate = NULL;
807         if (!g)
808                 g = find_game_by_fd(fd);
809         if (g) {
810                 int i;
811                 for (i = 0; i < g->players_number; i++) {
812                         // Pings are for the server only. Don't broadcast them to save bandwidth.
813                         if (len == 3 && msg[1] == 'p') {
814                                 // nada
815 
816                         // Emitter wants to receive synchro message as well
817                         } else if (g->players_conn[i] == fd && len > 2 && msg[1] == '!') {
818                                 char synchro4self[] = "?!\n";
819                                 ssize_t retval;
820                                 synchro4self[0] = fd;
821                                 l1(OUTPUT_TYPE_DEBUG, "[%d] sending self synchro", g->players_conn[i]);
822                                 retval = send(g->players_conn[i], synchro4self, sizeof(synchro4self) - 1, MSG_NOSIGNAL|MSG_DONTWAIT);
823                                 if (retval != sizeof(synchro4self) - 1) {
824                                         if (retval != -1) {
825                                                 l4(OUTPUT_TYPE_INFO, "[%d] short send of %zd instead of %zd bytes from %d - destination is not reading data "
826                                                                      "(illegal FB client) or our upload bandwidth is saturated - sorry, cannot continue serving "
827                                                                      "this client in this situation, closing connection",
828                                                                      g->players_conn[i], retval, sizeof(synchro4self) - 1, fd);
829                                         }
830                                         conn_to_terminate = g_list_append(conn_to_terminate, GINT_TO_POINTER(g->players_conn[i]));
831                                 }
832 
833                         } else if (g->players_conn[i] != fd) {
834                                 ssize_t retval;
835                                 l3(OUTPUT_TYPE_DEBUG, "[%d] sending %zd bytes to %d", fd, len, g->players_conn[i]);
836                                 retval = send(g->players_conn[i], msg, len, MSG_NOSIGNAL|MSG_DONTWAIT);
837                                 if (retval != len) {
838                                         if (retval != -1) {
839                                                 l4(OUTPUT_TYPE_INFO, "[%d] short send of %zd instead of %zd bytes from %d - destination is not reading data "
840                                                                      "(illegal FB client) or our upload bandwidth is saturated - sorry, cannot continue serving "
841                                                                      "this client in this situation, closing connection",
842                                                                      g->players_conn[i], retval, len, fd);
843                                         }
844                                         conn_to_terminate = g_list_append(conn_to_terminate, GINT_TO_POINTER(g->players_conn[i]));
845                                 }
846                         }
847                 }
848                 if (conn_to_terminate) {
849                         g_list_foreach(conn_to_terminate, conn_to_terminate_helper, NULL);
850                         g_list_free(conn_to_terminate);
851                 }
852         } else {
853                 l1(OUTPUT_TYPE_ERROR, "Internal error: could not find game by fd: %d", fd);
854                 exit(EXIT_FAILURE);
855         }
856 }
857 
process_msg_prio(int fd,char * msg,ssize_t len)858 void process_msg_prio(int fd, char* msg, ssize_t len)
859 {
860         process_msg_prio_(fd, msg, len, NULL);
861 }
862 
player_part_game(int fd)863 void player_part_game(int fd)
864 {
865         player_part_game_(fd, NULL);
866 }
867 
player_part_game_(int fd,char * reason)868 void player_part_game_(int fd, char* reason)
869 {
870         struct game * g = find_game_by_fd(fd);
871         if (g) {
872                 char * save_nick;
873                 int j;
874                 int i = find_player_number(g, fd);
875 
876                 // remove parting player from game
877                 save_nick = g->players_nick[i];
878                 for (j = i; j < g->players_number - 1; j++) {
879                         g->players_conn[j] = g->players_conn[j + 1];
880                         g->players_nick[j] = g->players_nick[j + 1];
881                         g->players_started[j] = g->players_started[j + 1];
882                 }
883                 g->players_number--;
884 
885                 // completely remove game if empty
886                 if (g->players_number == 0) {
887                         int was_running = g->status == GAME_STATUS_PLAYING;
888                         games = g_list_remove(games, g);
889                         free(g);
890                         calculate_list_games();
891                         if (was_running)
892                                 l2(OUTPUT_TYPE_INFO, "running games decrements to: %d (%d players)", games_running, players_in_game);
893 
894                 } else {
895                         if (g->status == GAME_STATUS_PLAYING) {
896                                 // inform other players, playing state
897                                 char leave_player_prio_msg[] = "?l\n";
898                                 leave_player_prio_msg[0] = fd;
899                                 process_msg_prio_(fd, leave_player_prio_msg, strlen(leave_player_prio_msg), g);
900                         } else {
901                                 char parted_msg[1000];
902                                 // inform other players, non-playing state
903                                 snprintf(parted_msg, sizeof(parted_msg), reason ? reason : ok_player_parted, save_nick);
904                                 for (j = 0; j < g->players_number; j++)
905                                         send_line_log_push(g->players_conn[j], parted_msg);
906                         }
907                         calculate_list_games();
908                 }
909                 free(save_nick);
910 
911                 open_players = g_list_append(open_players, GINT_TO_POINTER(fd));
912         }
913 }
914