1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
12 #include <sys/types.h>
13 #include <netinet/in.h>
14 /* Due to glibc brokenness, we can't blindly include this. Yet another
15 * reason to not use glibc. */
16 /* #include <netinet/protocols.h> */
18 #include <sys/socket.h>
25 /*************************************************************************/
27 static int linuxmode
= 0; /* 1: don't try to be compatible with Windows */
28 static int ipv6_only
= 0; /* 1: only use IPv6 (when available) */
32 static int listen_sock
= -1;
34 static int listen_sock6
= -1;
36 static int player_socks
[6] = {-1,-1,-1,-1,-1,-1};
37 static unsigned char player_ips
[6][4];
39 /* Which players have already lost in the current game? */
40 static int player_lost
[6];
42 /* We re-use a lot of variables from the main code */
44 /*************************************************************************/
45 /*************************************************************************/
47 /* Convert a 2-byte hex value to an integer. */
49 int xtoi(const char *buf
)
54 val
= (buf
[0] - '0') << 4;
56 val
= (toupper(buf
[0]) - 'A' + 10) << 4;
60 val
|= toupper(buf
[1]) - 'A' + 10;
64 /*************************************************************************/
66 /* Return a string containing the winlist in a format suitable for sending
70 static char *winlist_str()
72 static char buf
[1024];
77 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
78 s
+= snprintf(s
, sizeof(buf
)-(s
-buf
),
79 linuxmode
? " %c%s;%d;%d" : " %c%s;%d",
80 winlist
[i
].team
? 't' : 'p',
81 winlist
[i
].name
, winlist
[i
].points
, winlist
[i
].games
);
86 /*************************************************************************/
87 /*************************************************************************/
89 /* Read the configuration file. */
91 void read_config(void)
93 char buf
[1024], *s
, *t
;
100 snprintf(buf
, sizeof(buf
), "%s/.tetrinet", s
);
101 if (!(f
= fopen(buf
, "r")))
103 while (fgets(buf
, sizeof(buf
), f
)) {
104 s
= strtok(buf
, " ");
107 } else if (strcmp(s
, "linuxmode") == 0) {
108 if (s
= strtok(NULL
, " "))
110 } else if (strcmp(s
, "ipv6_only") == 0) {
111 if (s
= strtok(NULL
, " "))
113 } else if (strcmp(s
, "averagelevels") == 0) {
114 if (s
= strtok(NULL
, " "))
115 level_average
= atoi(s
);
116 } else if (strcmp(s
, "classic") == 0) {
117 if (s
= strtok(NULL
, " "))
119 } else if (strcmp(s
, "initiallevel") == 0) {
120 if (s
= strtok(NULL
, " "))
121 initial_level
= atoi(s
);
122 } else if (strcmp(s
, "levelinc") == 0) {
123 if (s
= strtok(NULL
, " "))
125 } else if (strcmp(s
, "linesperlevel") == 0) {
126 if (s
= strtok(NULL
, " "))
127 lines_per_level
= atoi(s
);
128 } else if (strcmp(s
, "pieces") == 0) {
130 while (i
< 7 && (s
= strtok(NULL
, " ")))
131 piecefreq
[i
++] = atoi(s
);
132 } else if (strcmp(s
, "specialcapacity") == 0) {
133 if (s
= strtok(NULL
, " "))
134 special_capacity
= atoi(s
);
135 } else if (strcmp(s
, "specialcount") == 0) {
136 if (s
= strtok(NULL
, " "))
137 special_count
= atoi(s
);
138 } else if (strcmp(s
, "speciallines") == 0) {
139 if (s
= strtok(NULL
, " "))
140 special_lines
= atoi(s
);
141 } else if (strcmp(s
, "specials") == 0) {
143 while (i
< 9 && (s
= strtok(NULL
, " ")))
144 specialfreq
[i
++] = atoi(s
);
145 } else if (strcmp(s
, "winlist") == 0) {
147 while (i
< MAXWINLIST
&& (s
= strtok(NULL
, " "))) {
152 strncpy(winlist
[i
].name
, s
, sizeof(winlist
[i
].name
)-1);
153 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
157 *winlist
[i
].name
= 0;
160 winlist
[i
].team
= atoi(s
);
164 *winlist
[i
].name
= 0;
167 winlist
[i
].points
= atoi(s
);
168 winlist
[i
].games
= atoi(t
+1);
176 /*************************************************************************/
178 /* Re-write the configuration file. */
180 void write_config(void)
189 snprintf(buf
, sizeof(buf
), "%s/.tetrinet", s
);
190 if (!(f
= fopen(buf
, "w")))
193 fprintf(f
, "winlist");
194 for (i
= 0; i
< MAXSAVEWINLIST
&& *winlist
[i
].name
; i
++) {
195 fprintf(f
, " %s;%d;%d;%d", winlist
[i
].name
, winlist
[i
].team
,
196 winlist
[i
].points
, winlist
[i
].games
);
200 fprintf(f
, "classic %d\n", old_mode
);
202 fprintf(f
, "initiallevel %d\n", initial_level
);
203 fprintf(f
, "linesperlevel %d\n", lines_per_level
);
204 fprintf(f
, "levelinc %d\n", level_inc
);
205 fprintf(f
, "averagelevels %d\n", level_average
);
207 fprintf(f
, "speciallines %d\n", special_lines
);
208 fprintf(f
, "specialcount %d\n", special_count
);
209 fprintf(f
, "specialcapacity %d\n", special_capacity
);
211 fprintf(f
, "pieces");
212 for (i
= 0; i
< 7; i
++)
213 fprintf(f
, " %d", piecefreq
[i
]);
216 fprintf(f
, "specials");
217 for (i
= 0; i
< 9; i
++)
218 fprintf(f
, " %d", specialfreq
[i
]);
221 fprintf(f
, "linuxmode %d\n", linuxmode
);
222 fprintf(f
, "ipv6_only %d\n", ipv6_only
);
227 /*************************************************************************/
228 /*************************************************************************/
230 /* Send a message to a single player. */
232 static void send_to(int player
, const char *format
, ...)
238 va_start(args
, format
);
239 vsnprintf(buf
, sizeof(buf
), format
, args
);
240 if (player_socks
[player
-1] >= 0)
241 sockprintf(player_socks
[player
-1], "%s", buf
);
244 /*************************************************************************/
246 /* Send a message to all players. */
248 static void send_to_all(const char *format
, ...)
254 va_start(args
, format
);
255 vsnprintf(buf
, sizeof(buf
), format
, args
);
256 for (i
= 0; i
< 6; i
++) {
257 if (player_socks
[i
] >= 0)
258 sockprintf(player_socks
[i
], "%s", buf
);
262 /*************************************************************************/
264 /* Send a message to all players but the given one. */
266 static void send_to_all_but(int player
, const char *format
, ...)
272 va_start(args
, format
);
273 vsnprintf(buf
, sizeof(buf
), format
, args
);
274 for (i
= 0; i
< 6; i
++) {
275 if (i
+1 != player
&& player_socks
[i
] >= 0)
276 sockprintf(player_socks
[i
], "%s", buf
);
280 /*************************************************************************/
282 /* Send a message to all players but those on the same team as the given
286 static void send_to_all_but_team(int player
, const char *format
, ...)
291 char *team
= teams
[player
-1];
293 va_start(args
, format
);
294 vsnprintf(buf
, sizeof(buf
), format
, args
);
295 for (i
= 0; i
< 6; i
++) {
296 if (i
+1 != player
&& player_socks
[i
] >= 0 &&
297 (!team
|| !teams
[i
] || strcmp(teams
[i
], team
) != 0))
298 sockprintf(player_socks
[i
], "%s", buf
);
302 /*************************************************************************/
303 /*************************************************************************/
305 /* Add points to a given player's [team's] winlist entry, or make a new one
309 static void add_points(int player
, int points
)
313 if (!players
[player
-1])
315 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
316 if (!winlist
[i
].team
&& !teams
[player
-1]
317 && strcmp(winlist
[i
].name
, players
[player
-1]) == 0)
319 if (winlist
[i
].team
&& teams
[player
-1]
320 && strcmp(winlist
[i
].name
, teams
[player
-1]) == 0)
323 if (i
== MAXWINLIST
) {
324 for (i
= 0; i
< MAXWINLIST
&& winlist
[i
].points
>= points
; i
++)
329 if (!*winlist
[i
].name
) {
330 if (teams
[player
-1]) {
331 strncpy(winlist
[i
].name
, teams
[player
-1], sizeof(winlist
[i
].name
)-1);
332 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
335 strncpy(winlist
[i
].name
, players
[player
-1], sizeof(winlist
[i
].name
)-1);
336 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
340 winlist
[i
].points
+= points
;
343 /*************************************************************************/
345 /* Add a game to a given player's [team's] winlist entry. */
347 static void add_game(int player
)
351 if (!players
[player
-1])
353 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
354 if (!winlist
[i
].team
&& !teams
[player
-1]
355 && strcmp(winlist
[i
].name
, players
[player
-1]) == 0)
357 if (winlist
[i
].team
&& teams
[player
-1]
358 && strcmp(winlist
[i
].name
, teams
[player
-1]) == 0)
361 if (i
== MAXWINLIST
|| !*winlist
[i
].name
)
366 /*************************************************************************/
368 /* Sort the winlist. */
370 static void sort_winlist()
372 int i
, j
, best
, bestindex
;
374 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
375 best
= winlist
[i
].points
;
377 for (j
= i
+1; j
< MAXWINLIST
&& *winlist
[j
].name
; j
++) {
378 if (winlist
[j
].points
> best
) {
379 best
= winlist
[j
].points
;
383 if (bestindex
!= i
) {
385 memcpy(&tmp
, &winlist
[i
], sizeof(WinInfo
));
386 memcpy(&winlist
[i
], &winlist
[bestindex
], sizeof(WinInfo
));
387 memcpy(&winlist
[bestindex
], &tmp
, sizeof(WinInfo
));
392 /*************************************************************************/
394 /* Take care of a player losing (which may end the game). */
396 static void player_loses(int player
)
398 int i
, j
, order
, end
= 1, winner
= -1, second
, third
;
400 if (player
< 1 || player
> 6 || player_socks
[player
-1] < 0)
403 for (i
= 1; i
<= 6; i
++) {
404 if (player_lost
[i
-1] > order
)
405 order
= player_lost
[i
-1];
407 player_lost
[player
-1] = order
+1;
408 for (i
= 1; i
<= 6; i
++) {
409 if (player_socks
[i
-1] >= 0 && !player_lost
[i
-1]) {
412 } else if (!teams
[winner
-1] || !teams
[i
-1]
413 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0) {
420 send_to_all("endgame");
422 /* Catch the case where no players are left (1-player game) */
424 send_to_all("playerwon %d", winner
);
425 add_points(winner
, 3);
427 for (i
= 1; i
<= 6; i
++) {
428 if (player_lost
[i
-1] > order
429 && (!teams
[winner
-1] || !teams
[i
-1]
430 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0)) {
431 order
= player_lost
[i
-1];
436 add_points(second
, 2);
437 player_lost
[second
-1] = 0;
440 for (i
= 1; i
<= 6; i
++) {
441 if (player_lost
[i
-1] > order
442 && (!teams
[winner
-1] || !teams
[i
-1]
443 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0)
444 && (!teams
[second
-1] || !teams
[i
-1]
445 || strcasecmp(teams
[second
-1],teams
[i
-1]) != 0)) {
446 order
= player_lost
[i
-1];
451 add_points(third
, 1);
452 for (i
= 1; i
<= 6; i
++) {
454 for (j
= 1; j
< i
; j
++) {
455 if (teams
[j
-1] && strcasecmp(teams
[i
-1],teams
[j
-1])==0)
461 if (player_socks
[i
-1] >= 0)
467 send_to_all("winlist %s", winlist_str());
469 /* One more possibility: the only player playing left the game, which
470 * means there are now no players left. */
471 if (!players
[0] && !players
[1] && !players
[2] && !players
[3]
472 && !players
[4] && !players
[5])
476 /*************************************************************************/
477 /*************************************************************************/
479 /* Parse a line from a client. Destroys the buffer it's given as a side
480 * effect. Return 0 if the command is unknown (or bad syntax), else 1.
483 static int server_parse(int player
, char *buf
)
488 cmd
= strtok(buf
, " ");
493 } else if (strcmp(cmd
, "tetrisstart") == 0) {
494 s
= strtok(NULL
, " ");
495 t
= strtok(NULL
, " ");
498 for (i
= 1; i
<= 6; i
++) {
499 if (players
[i
-1] && strcasecmp(s
, players
[i
-1]) == 0) {
500 send_to(player
, "noconnecting Nickname already exists on server!");
504 players
[player
-1] = strdup(s
);
506 free(teams
[player
-1]);
507 teams
[player
-1] = NULL
;
508 send_to(player
, "playernum %d", player
);
509 send_to(player
, "winlist %s", winlist_str());
510 for (i
= 1; i
<= 6; i
++) {
511 if (i
!= player
&& players
[i
-1]) {
512 send_to(player
, "playerjoin %d %s", i
, players
[i
-1]);
513 send_to(player
, "team %d %s", i
, teams
[i
-1] ? teams
[i
-1] : "");
517 send_to(player
, "ingame");
518 player_lost
[player
-1] = 1;
520 send_to_all_but(player
, "playerjoin %d %s", player
, players
[player
-1]);
522 } else if (strcmp(cmd
, "team") == 0) {
523 s
= strtok(NULL
, " ");
524 t
= strtok(NULL
, "");
525 if (!s
|| atoi(s
) != player
)
530 teams
[player
] = strdup(t
);
532 teams
[player
] = NULL
;
533 send_to_all_but(player
, "team %d %s", player
, t
? t
: "");
535 } else if (strcmp(cmd
, "pline") == 0) {
536 s
= strtok(NULL
, " ");
537 t
= strtok(NULL
, "");
538 if (!s
|| atoi(s
) != player
)
542 send_to_all_but(player
, "pline %d %s", player
, t
);
544 } else if (strcmp(cmd
, "plineact") == 0) {
545 s
= strtok(NULL
, " ");
546 t
= strtok(NULL
, "");
547 if (!s
|| atoi(s
) != player
)
551 send_to_all_but(player
, "plineact %d %s", player
, t
);
553 } else if (strcmp(cmd
, "startgame") == 0) {
555 char piecebuf
[101], specialbuf
[101];
557 for (i
= 1; i
< player
; i
++) {
558 if (player_socks
[i
-1] >= 0)
561 s
= strtok(NULL
, " ");
562 t
= strtok(NULL
, " ");
566 if ((i
&& playing_game
) || (!i
&& !playing_game
))
568 if (!i
) { /* end game */
569 send_to_all("endgame");
574 for (i
= 0; i
< 7; i
++) {
576 memset(piecebuf
+total
, '1'+i
, piecefreq
[i
]);
577 total
+= piecefreq
[i
];
581 send_to_all("plineact 0 cannot start game: Piece frequencies do not total 100 percent!");
585 for (i
= 0; i
< 9; i
++) {
587 memset(specialbuf
+total
, '1'+i
, specialfreq
[i
]);
588 total
+= specialfreq
[i
];
592 send_to_all("plineact 0 cannot start game: Special frequencies do not total 100 percent!");
597 for (i
= 1; i
<= 6; i
++) {
598 if (player_socks
[i
-1] < 0)
600 /* XXX First parameter is stack height */
601 send_to(i
, "newgame %d %d %d %d %d %d %d %s %s %d %d",
602 0, initial_level
, lines_per_level
, level_inc
,
603 special_lines
, special_count
, special_capacity
,
604 piecebuf
, specialbuf
, level_average
, old_mode
);
606 memset(player_lost
, 0, sizeof(player_lost
));
608 } else if (strcmp(cmd
, "pause") == 0) {
611 s
= strtok(NULL
, " ");
616 i
= 1; /* to make sure it's not anything else */
617 if ((i
&& game_paused
) || (!i
&& !game_paused
))
620 send_to_all("pause %d", i
);
622 } else if (strcmp(cmd
, "playerlost") == 0) {
623 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
625 player_loses(player
);
627 } else if (strcmp(cmd
, "f") == 0) { /* field */
628 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
630 if (!(s
= strtok(NULL
, "")))
632 send_to_all_but(player
, "f %d %s", player
, s
);
634 } else if (strcmp(cmd
, "lvl") == 0) {
635 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
637 if (!(s
= strtok(NULL
, " ")))
639 levels
[player
] = atoi(s
);
640 send_to_all_but(player
, "lvl %d %d", player
, levels
[player
]);
642 } else if (strcmp(cmd
, "sb") == 0) {
646 if (!(s
= strtok(NULL
, " ")))
649 if (!(type
= strtok(NULL
, " ")))
651 if (!(s
= strtok(NULL
, " ")))
656 if (to
< 0 || to
> 6 || player_socks
[to
-1] < 0 || player_lost
[to
-1])
659 send_to_all_but_team(player
, "sb %d %s %d", to
, type
, from
);
661 send_to_all_but(player
, "sb %d %s %d", to
, type
, from
);
663 } else if (strcmp(cmd
, "gmsg") == 0) {
664 if (!(s
= strtok(NULL
, "")))
666 send_to_all("gmsg %s", s
);
668 } else { /* unrecognized command */
676 /*************************************************************************/
677 /*************************************************************************/
679 static void sigcatcher(int sig
)
683 signal(SIGHUP
, sigcatcher
);
684 send_to_all("winlist %s", winlist_str());
685 } else if (sig
== SIGTERM
|| sig
== SIGINT
) {
687 signal(sig
, SIG_IGN
);
691 /*************************************************************************/
693 /* Returns 0 on success, desired program exit code on failure */
697 struct sockaddr_in sin
;
699 struct sockaddr_in6 sin6
;
703 /* Set up some sensible defaults */
704 *winlist
[0].name
= 0;
712 special_capacity
= 18;
730 /* (Try to) read the config file */
733 /* Catch some signals */
734 signal(SIGHUP
, sigcatcher
);
735 signal(SIGINT
, sigcatcher
);
736 signal(SIGTERM
, sigcatcher
);
738 /* Set up a listen socket */
740 listen_sock
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
741 if (listen_sock
>= 0){
743 if (setsockopt(listen_sock
,SOL_SOCKET
,SO_REUSEADDR
,&i
,sizeof(i
))==0) {
744 memset(&sin
, 0, sizeof(sin
));
745 sin
.sin_family
= AF_INET
;
746 sin
.sin_port
= htons(31457);
747 if (bind(listen_sock
, (struct sockaddr
*)&sin
, sizeof(sin
)) == 0) {
748 if (listen(listen_sock
, 5) == 0) {
761 /* Set up an IPv6 listen socket if possible */
762 listen_sock6
= socket(AF_INET6
, SOCK_STREAM
, IPPROTO_TCP
);
763 if (listen_sock6
>= 0) {
765 if (setsockopt(listen_sock6
,SOL_SOCKET
,SO_REUSEADDR
,&i
,sizeof(i
))==0) {
766 memset(&sin6
, 0, sizeof(sin6
));
767 sin6
.sin6_family
= AF_INET6
;
768 sin6
.sin6_port
= htons(31457);
769 if (bind(listen_sock6
,(struct sockaddr
*)&sin6
,sizeof(sin6
))==0) {
770 if (listen(listen_sock6
, 5) == 0) {
781 #else /* !HAVE_IPV6 */
783 fprintf(stderr
,"ipv6_only specified but IPv6 support not available\n");
786 #endif /* HAVE_IPV6 */
799 /*************************************************************************/
801 static void check_sockets()
807 if (listen_sock
>= 0)
808 FD_SET(listen_sock
, &fds
);
811 if (listen_sock6
>= 0)
812 FD_SET(listen_sock6
, &fds
);
813 if (listen_sock6
> maxfd
)
814 maxfd
= listen_sock6
;
816 for (i
= 0; i
< 6; i
++) {
817 if (player_socks
[i
] != -1) {
818 if (player_socks
[i
] < 0)
819 fd
= (~player_socks
[i
]) - 1;
821 fd
= player_socks
[i
];
828 if (select(maxfd
+1, &fds
, NULL
, NULL
, NULL
) <= 0)
831 if (listen_sock
>= 0 && FD_ISSET(listen_sock
, &fds
)) {
832 struct sockaddr_in sin
;
833 int len
= sizeof(sin
);
834 fd
= accept(listen_sock
, (struct sockaddr
*)&sin
, &len
);
836 for (i
= 0; i
< 6 && player_socks
[i
] != -1; i
++)
839 sockprintf(fd
, "noconnecting Too many players on server!");
842 player_socks
[i
] = ~(fd
+1);
843 memcpy(player_ips
[i
], &sin
.sin_addr
, 4);
846 } /* if (FD_ISSET(listen_sock)) */
849 if (listen_sock6
>= 0 && FD_ISSET(listen_sock6
, &fds
)) {
850 struct sockaddr_in6 sin6
;
851 int len
= sizeof(sin6
);
852 fd
= accept(listen_sock6
, (struct sockaddr
*)&sin6
, &len
);
854 for (i
= 0; i
< 6 && player_socks
[i
] != -1; i
++)
857 sockprintf(fd
, "noconnecting Too many players on server!");
860 player_socks
[i
] = ~(fd
+1);
861 memcpy(player_ips
[i
], (char *)(&sin6
.sin6_addr
)+12, 4);
864 } /* if (FD_ISSET(listen_sock6)) */
867 for (i
= 0; i
< 6; i
++) {
870 if (player_socks
[i
] == -1)
872 if (player_socks
[i
] < 0)
873 fd
= (~player_socks
[i
]) - 1;
875 fd
= player_socks
[i
];
876 if (!FD_ISSET(fd
, &fds
))
878 sgets(buf
, sizeof(buf
), fd
);
879 if (player_socks
[i
] < 0) {
880 /* Messy decoding stuff */
881 char iphashbuf
[16], newbuf
[1024];
885 if (strlen(buf
) < 2*13) { /* "tetrisstart " + initial byte */
887 player_socks
[i
] = -1;
891 sprintf(iphashbuf
, "%d", ip
[0]*54 + ip
[1]*41 + ip
[2]*29 + ip
[3]*17);
893 for (j
= 2; buf
[j
] && buf
[j
+1]; j
+= 2) {
895 temp
= d
= xtoi(buf
+j
);
896 d
^= iphashbuf
[((j
/2)-1) % strlen(iphashbuf
)];
903 if (strncmp(newbuf
, "tetrisstart ", 12) != 0) {
905 player_socks
[i
] = -1;
908 /* Buffers should be the same size, but let's be paranoid */
909 strncpy(buf
, newbuf
, sizeof(buf
));
910 buf
[sizeof(buf
)-1] = 0;
911 player_socks
[i
] = fd
; /* Has now registered */
912 } /* if client not registered */
913 if (!server_parse(i
+1, buf
)) {
915 player_socks
[i
] = -1;
917 send_to_all("playerleave %d", i
+1);
928 } /* for each player socket */
931 /*************************************************************************/
941 if ((i
= init()) != 0)
946 if (listen_sock
>= 0)
949 if (listen_sock6
>= 0)
952 for (i
= 0; i
< 6; i
++)
953 close(player_socks
[i
]);
957 /*************************************************************************/