1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
13 #include <sys/types.h>
14 #include <netinet/in.h>
15 /* Due to glibc brokenness, we can't blindly include this. Yet another
16 * reason to not use glibc. */
17 /* #include <netinet/protocols.h> */
19 #include <sys/socket.h>
27 /*************************************************************************/
29 static int linuxmode
= 0; /* 1: don't try to be compatible with Windows */
30 static int ipv6_only
= 0; /* 1: only use IPv6 (when available) */
34 static int listen_sock
= -1;
36 static int listen_sock6
= -1;
38 static int player_socks
[6] = {-1,-1,-1,-1,-1,-1};
39 static unsigned char player_ips
[6][4];
40 static int player_modes
[6];
42 /* Which players have already lost in the current game? */
43 static int player_lost
[6];
45 /* We re-use a lot of variables from the main code */
47 /*************************************************************************/
48 /*************************************************************************/
50 /* Convert a 2-byte hex value to an integer. */
52 int xtoi(const char *buf
)
57 val
= (buf
[0] - '0') << 4;
59 val
= (toupper(buf
[0]) - 'A' + 10) << 4;
63 val
|= toupper(buf
[1]) - 'A' + 10;
67 /*************************************************************************/
69 /* Return a string containing the winlist in a format suitable for sending
73 static char *winlist_str()
75 static char buf
[1024];
80 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
81 s
+= snprintf(s
, sizeof(buf
)-(s
-buf
),
82 linuxmode
? " %c%s;%d;%d" : " %c%s;%d",
83 winlist
[i
].team
? 't' : 'p',
84 winlist
[i
].name
, winlist
[i
].points
, winlist
[i
].games
);
89 /*************************************************************************/
90 /*************************************************************************/
92 /* Read the configuration file. */
94 void read_config(void)
96 char buf
[1024], *s
, *t
;
103 snprintf(buf
, sizeof(buf
), "%s/.tetrinet", s
);
104 if (!(f
= fopen(buf
, "r")))
106 while (fgets(buf
, sizeof(buf
), f
)) {
107 s
= strtok(buf
, " ");
110 } else if (strcmp(s
, "linuxmode") == 0) {
111 if ((s
= strtok(NULL
, " ")))
113 } else if (strcmp(s
, "ipv6_only") == 0) {
114 if ((s
= strtok(NULL
, " ")))
116 } else if (strcmp(s
, "averagelevels") == 0) {
117 if ((s
= strtok(NULL
, " ")))
118 level_average
= atoi(s
);
119 } else if (strcmp(s
, "classic") == 0) {
120 if ((s
= strtok(NULL
, " ")))
122 } else if (strcmp(s
, "initiallevel") == 0) {
123 if ((s
= strtok(NULL
, " ")))
124 initial_level
= atoi(s
);
125 } else if (strcmp(s
, "levelinc") == 0) {
126 if ((s
= strtok(NULL
, " ")))
128 } else if (strcmp(s
, "linesperlevel") == 0) {
129 if ((s
= strtok(NULL
, " ")))
130 lines_per_level
= atoi(s
);
131 } else if (strcmp(s
, "pieces") == 0) {
133 while (i
< 7 && (s
= strtok(NULL
, " ")))
134 piecefreq
[i
++] = atoi(s
);
135 } else if (strcmp(s
, "specialcapacity") == 0) {
136 if ((s
= strtok(NULL
, " ")))
137 special_capacity
= atoi(s
);
138 } else if (strcmp(s
, "specialcount") == 0) {
139 if ((s
= strtok(NULL
, " ")))
140 special_count
= atoi(s
);
141 } else if (strcmp(s
, "speciallines") == 0) {
142 if ((s
= strtok(NULL
, " ")))
143 special_lines
= atoi(s
);
144 } else if (strcmp(s
, "specials") == 0) {
146 while (i
< 9 && (s
= strtok(NULL
, " ")))
147 specialfreq
[i
++] = atoi(s
);
148 } else if (strcmp(s
, "winlist") == 0) {
150 while (i
< MAXWINLIST
&& (s
= strtok(NULL
, " "))) {
155 strncpy(winlist
[i
].name
, s
, sizeof(winlist
[i
].name
)-1);
156 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
160 *winlist
[i
].name
= 0;
163 winlist
[i
].team
= atoi(s
);
167 *winlist
[i
].name
= 0;
170 winlist
[i
].points
= atoi(s
);
171 winlist
[i
].games
= atoi(t
+1);
179 /*************************************************************************/
181 /* Re-write the configuration file. */
183 void write_config(void)
192 snprintf(buf
, sizeof(buf
), "%s/.tetrinet", s
);
193 if (!(f
= fopen(buf
, "w")))
196 fprintf(f
, "winlist");
197 for (i
= 0; i
< MAXSAVEWINLIST
&& *winlist
[i
].name
; i
++) {
198 fprintf(f
, " %s;%d;%d;%d", winlist
[i
].name
, winlist
[i
].team
,
199 winlist
[i
].points
, winlist
[i
].games
);
203 fprintf(f
, "classic %d\n", old_mode
);
205 fprintf(f
, "initiallevel %d\n", initial_level
);
206 fprintf(f
, "linesperlevel %d\n", lines_per_level
);
207 fprintf(f
, "levelinc %d\n", level_inc
);
208 fprintf(f
, "averagelevels %d\n", level_average
);
210 fprintf(f
, "speciallines %d\n", special_lines
);
211 fprintf(f
, "specialcount %d\n", special_count
);
212 fprintf(f
, "specialcapacity %d\n", special_capacity
);
214 fprintf(f
, "pieces");
215 for (i
= 0; i
< 7; i
++)
216 fprintf(f
, " %d", piecefreq
[i
]);
219 fprintf(f
, "specials");
220 for (i
= 0; i
< 9; i
++)
221 fprintf(f
, " %d", specialfreq
[i
]);
224 fprintf(f
, "linuxmode %d\n", linuxmode
);
225 fprintf(f
, "ipv6_only %d\n", ipv6_only
);
230 /*************************************************************************/
231 /*************************************************************************/
233 /* Send a message to a single player. */
235 static void send_to(int player
, const char *format
, ...)
240 va_start(args
, format
);
241 vsnprintf(buf
, sizeof(buf
), format
, args
);
242 if (player_socks
[player
-1] >= 0)
243 sockprintf(player_socks
[player
-1], "%s", buf
);
246 /*************************************************************************/
248 /* Send a message to all players. */
250 static void send_to_all(const char *format
, ...)
256 va_start(args
, format
);
257 vsnprintf(buf
, sizeof(buf
), format
, args
);
258 for (i
= 0; i
< 6; i
++) {
259 if (player_socks
[i
] >= 0)
260 sockprintf(player_socks
[i
], "%s", buf
);
264 /*************************************************************************/
266 /* Send a message to all players but the given one. */
268 static void send_to_all_but(int player
, const char *format
, ...)
274 va_start(args
, format
);
275 vsnprintf(buf
, sizeof(buf
), format
, args
);
276 for (i
= 0; i
< 6; i
++) {
277 if (i
+1 != player
&& player_socks
[i
] >= 0)
278 sockprintf(player_socks
[i
], "%s", buf
);
282 /*************************************************************************/
284 /* Send a message to all players but those on the same team as the given
288 static void send_to_all_but_team(int player
, const char *format
, ...)
293 char *team
= teams
[player
-1];
295 va_start(args
, format
);
296 vsnprintf(buf
, sizeof(buf
), format
, args
);
297 for (i
= 0; i
< 6; i
++) {
298 if (i
+1 != player
&& player_socks
[i
] >= 0 &&
299 (!team
|| !teams
[i
] || strcmp(teams
[i
], team
) != 0))
300 sockprintf(player_socks
[i
], "%s", buf
);
304 /*************************************************************************/
305 /*************************************************************************/
307 /* Add points to a given player's [team's] winlist entry, or make a new one
311 static void add_points(int player
, int points
)
315 if (!players
[player
-1])
317 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
318 if (!winlist
[i
].team
&& !teams
[player
-1]
319 && strcmp(winlist
[i
].name
, players
[player
-1]) == 0)
321 if (winlist
[i
].team
&& teams
[player
-1]
322 && strcmp(winlist
[i
].name
, teams
[player
-1]) == 0)
325 if (i
== MAXWINLIST
) {
326 for (i
= 0; i
< MAXWINLIST
&& winlist
[i
].points
>= points
; i
++)
331 if (!*winlist
[i
].name
) {
332 if (teams
[player
-1]) {
333 strncpy(winlist
[i
].name
, teams
[player
-1], sizeof(winlist
[i
].name
)-1);
334 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
337 strncpy(winlist
[i
].name
, players
[player
-1], sizeof(winlist
[i
].name
)-1);
338 winlist
[i
].name
[sizeof(winlist
[i
].name
)-1] = 0;
342 winlist
[i
].points
+= points
;
345 /*************************************************************************/
347 /* Add a game to a given player's [team's] winlist entry. */
349 static void add_game(int player
)
353 if (!players
[player
-1])
355 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
356 if (!winlist
[i
].team
&& !teams
[player
-1]
357 && strcmp(winlist
[i
].name
, players
[player
-1]) == 0)
359 if (winlist
[i
].team
&& teams
[player
-1]
360 && strcmp(winlist
[i
].name
, teams
[player
-1]) == 0)
363 if (i
== MAXWINLIST
|| !*winlist
[i
].name
)
368 /*************************************************************************/
370 /* Sort the winlist. */
372 static void sort_winlist()
374 int i
, j
, best
, bestindex
;
376 for (i
= 0; i
< MAXWINLIST
&& *winlist
[i
].name
; i
++) {
377 best
= winlist
[i
].points
;
379 for (j
= i
+1; j
< MAXWINLIST
&& *winlist
[j
].name
; j
++) {
380 if (winlist
[j
].points
> best
) {
381 best
= winlist
[j
].points
;
385 if (bestindex
!= i
) {
387 memcpy(&tmp
, &winlist
[i
], sizeof(WinInfo
));
388 memcpy(&winlist
[i
], &winlist
[bestindex
], sizeof(WinInfo
));
389 memcpy(&winlist
[bestindex
], &tmp
, sizeof(WinInfo
));
394 /*************************************************************************/
396 /* Take care of a player losing (which may end the game). */
398 static void player_loses(int player
)
400 int i
, j
, order
, end
= 1, winner
= -1, second
= -1, third
= -1;
402 if (player
< 1 || player
> 6 || player_socks
[player
-1] < 0)
405 for (i
= 1; i
<= 6; i
++) {
406 if (player_lost
[i
-1] > order
)
407 order
= player_lost
[i
-1];
409 player_lost
[player
-1] = order
+1;
410 for (i
= 1; i
<= 6; i
++) {
411 if (player_socks
[i
-1] >= 0 && !player_lost
[i
-1]) {
414 } else if (!teams
[winner
-1] || !teams
[i
-1]
415 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0) {
422 send_to_all("endgame");
424 /* Catch the case where no players are left (1-player game) */
426 send_to_all("playerwon %d", winner
);
427 add_points(winner
, 3);
429 for (i
= 1; i
<= 6; i
++) {
430 if (player_lost
[i
-1] > order
431 && (!teams
[winner
-1] || !teams
[i
-1]
432 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0)) {
433 order
= player_lost
[i
-1];
438 add_points(second
, 2);
439 player_lost
[second
-1] = 0;
442 for (i
= 1; i
<= 6; i
++) {
443 if (player_lost
[i
-1] > order
444 && (!teams
[winner
-1] || !teams
[i
-1]
445 || strcasecmp(teams
[winner
-1],teams
[i
-1]) != 0)
446 && (!teams
[second
-1] || !teams
[i
-1]
447 || strcasecmp(teams
[second
-1],teams
[i
-1]) != 0)) {
448 order
= player_lost
[i
-1];
453 add_points(third
, 1);
454 for (i
= 1; i
<= 6; i
++) {
456 for (j
= 1; j
< i
; j
++) {
457 if (teams
[j
-1] && strcasecmp(teams
[i
-1],teams
[j
-1])==0)
463 if (player_socks
[i
-1] >= 0)
469 send_to_all("winlist %s", winlist_str());
471 /* One more possibility: the only player playing left the game, which
472 * means there are now no players left. */
473 if (!players
[0] && !players
[1] && !players
[2] && !players
[3]
474 && !players
[4] && !players
[5])
478 /*************************************************************************/
479 /*************************************************************************/
481 /* Parse a line from a client. Destroys the buffer it's given as a side
482 * effect. Return 0 if the command is unknown (or bad syntax), else 1.
485 static int server_parse(int player
, char *buf
)
488 int i
, tetrifast
= 0;
490 cmd
= strtok(buf
, " ");
495 } else if (strcmp(cmd
, "tetrisstart") == 0) {
497 s
= strtok(NULL
, " ");
498 t
= strtok(NULL
, " ");
501 for (i
= 1; i
<= 6; i
++) {
502 if (players
[i
-1] && strcasecmp(s
, players
[i
-1]) == 0) {
503 send_to(player
, "noconnecting Nickname already exists on server!");
507 players
[player
-1] = strdup(s
);
509 free(teams
[player
-1]);
510 teams
[player
-1] = NULL
;
511 player_modes
[player
-1] = tetrifast
;
512 send_to(player
, "%s %d", tetrifast
? ")#)(!@(*3" : "playernum", player
);
513 send_to(player
, "winlist %s", winlist_str());
514 for (i
= 1; i
<= 6; i
++) {
515 if (i
!= player
&& players
[i
-1]) {
516 send_to(player
, "playerjoin %d %s", i
, players
[i
-1]);
517 send_to(player
, "team %d %s", i
, teams
[i
-1] ? teams
[i
-1] : "");
521 send_to(player
, "ingame");
522 player_lost
[player
-1] = 1;
524 send_to_all_but(player
, "playerjoin %d %s", player
, players
[player
-1]);
526 } else if (strcmp(cmd
, "tetrifaster") == 0) {
530 } else if (strcmp(cmd
, "team") == 0) {
531 s
= strtok(NULL
, " ");
532 t
= strtok(NULL
, "");
533 if (!s
|| atoi(s
) != player
)
538 teams
[player
] = strdup(t
);
540 teams
[player
] = NULL
;
541 send_to_all_but(player
, "team %d %s", player
, t
? t
: "");
543 } else if (strcmp(cmd
, "pline") == 0) {
544 s
= strtok(NULL
, " ");
545 t
= strtok(NULL
, "");
546 if (!s
|| atoi(s
) != player
)
550 send_to_all_but(player
, "pline %d %s", player
, t
);
552 } else if (strcmp(cmd
, "plineact") == 0) {
553 s
= strtok(NULL
, " ");
554 t
= strtok(NULL
, "");
555 if (!s
|| atoi(s
) != player
)
559 send_to_all_but(player
, "plineact %d %s", player
, t
);
561 } else if (strcmp(cmd
, "startgame") == 0) {
563 char piecebuf
[101], specialbuf
[101];
565 for (i
= 1; i
< player
; i
++) {
566 if (player_socks
[i
-1] >= 0)
569 s
= strtok(NULL
, " ");
570 t
= strtok(NULL
, " ");
574 if ((i
&& playing_game
) || (!i
&& !playing_game
))
576 if (!i
) { /* end game */
577 send_to_all("endgame");
582 for (i
= 0; i
< 7; i
++) {
584 memset(piecebuf
+total
, '1'+i
, piecefreq
[i
]);
585 total
+= piecefreq
[i
];
589 send_to_all("plineact 0 cannot start game: Piece frequencies do not total 100 percent!");
593 for (i
= 0; i
< 9; i
++) {
595 memset(specialbuf
+total
, '1'+i
, specialfreq
[i
]);
596 total
+= specialfreq
[i
];
600 send_to_all("plineact 0 cannot start game: Special frequencies do not total 100 percent!");
605 for (i
= 1; i
<= 6; i
++) {
606 if (player_socks
[i
-1] < 0)
608 /* XXX First parameter is stack height */
609 send_to(i
, "%s %d %d %d %d %d %d %d %s %s %d %d",
610 player_modes
[i
-1] ? "*******" : "newgame",
611 0, initial_level
, lines_per_level
, level_inc
,
612 special_lines
, special_count
, special_capacity
,
613 piecebuf
, specialbuf
, level_average
, old_mode
);
615 memset(player_lost
, 0, sizeof(player_lost
));
617 } else if (strcmp(cmd
, "pause") == 0) {
620 s
= strtok(NULL
, " ");
625 i
= 1; /* to make sure it's not anything else */
626 if ((i
&& game_paused
) || (!i
&& !game_paused
))
629 send_to_all("pause %d", i
);
631 } else if (strcmp(cmd
, "playerlost") == 0) {
632 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
634 player_loses(player
);
636 } else if (strcmp(cmd
, "f") == 0) { /* field */
637 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
639 if (!(s
= strtok(NULL
, "")))
641 send_to_all_but(player
, "f %d %s", player
, s
);
643 } else if (strcmp(cmd
, "lvl") == 0) {
644 if (!(s
= strtok(NULL
, " ")) || atoi(s
) != player
)
646 if (!(s
= strtok(NULL
, " ")))
648 levels
[player
] = atoi(s
);
649 send_to_all_but(player
, "lvl %d %d", player
, levels
[player
]);
651 } else if (strcmp(cmd
, "sb") == 0) {
655 if (!(s
= strtok(NULL
, " ")))
658 if (!(type
= strtok(NULL
, " ")))
660 if (!(s
= strtok(NULL
, " ")))
665 if (to
< 0 || to
> 6 || player_socks
[to
-1] < 0 || player_lost
[to
-1])
668 send_to_all_but_team(player
, "sb %d %s %d", to
, type
, from
);
670 send_to_all_but(player
, "sb %d %s %d", to
, type
, from
);
672 } else if (strcmp(cmd
, "gmsg") == 0) {
673 if (!(s
= strtok(NULL
, "")))
675 send_to_all("gmsg %s", s
);
677 } else { /* unrecognized command */
685 /*************************************************************************/
686 /*************************************************************************/
688 static void sigcatcher(int sig
)
692 signal(SIGHUP
, sigcatcher
);
693 send_to_all("winlist %s", winlist_str());
694 } else if (sig
== SIGTERM
|| sig
== SIGINT
) {
696 signal(sig
, SIG_IGN
);
700 /*************************************************************************/
702 /* Returns 0 on success, desired program exit code on failure */
706 struct sockaddr_in sin
;
708 struct sockaddr_in6 sin6
;
712 /* Set up some sensible defaults */
713 *winlist
[0].name
= 0;
721 special_capacity
= 18;
739 /* (Try to) read the config file */
742 /* Catch some signals */
743 signal(SIGHUP
, sigcatcher
);
744 signal(SIGINT
, sigcatcher
);
745 signal(SIGTERM
, sigcatcher
);
747 /* Set up a listen socket */
749 listen_sock
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
750 if (listen_sock
>= 0){
752 if (setsockopt(listen_sock
,SOL_SOCKET
,SO_REUSEADDR
,&i
,sizeof(i
))==0) {
753 memset(&sin
, 0, sizeof(sin
));
754 sin
.sin_family
= AF_INET
;
755 sin
.sin_port
= htons(31457);
756 if (bind(listen_sock
, (struct sockaddr
*)&sin
, sizeof(sin
)) == 0) {
757 if (listen(listen_sock
, 5) == 0) {
770 /* Set up an IPv6 listen socket if possible */
771 listen_sock6
= socket(AF_INET6
, SOCK_STREAM
, IPPROTO_TCP
);
772 if (listen_sock6
>= 0) {
774 if (setsockopt(listen_sock6
,SOL_SOCKET
,SO_REUSEADDR
,&i
,sizeof(i
))==0) {
775 memset(&sin6
, 0, sizeof(sin6
));
776 sin6
.sin6_family
= AF_INET6
;
777 sin6
.sin6_port
= htons(31457);
778 if (bind(listen_sock6
,(struct sockaddr
*)&sin6
,sizeof(sin6
))==0) {
779 if (listen(listen_sock6
, 5) == 0) {
790 #else /* !HAVE_IPV6 */
792 fprintf(stderr
,"ipv6_only specified but IPv6 support not available\n");
795 #endif /* HAVE_IPV6 */
808 /*************************************************************************/
810 static void check_sockets()
816 if (listen_sock
>= 0)
817 FD_SET(listen_sock
, &fds
);
820 if (listen_sock6
>= 0)
821 FD_SET(listen_sock6
, &fds
);
822 if (listen_sock6
> maxfd
)
823 maxfd
= listen_sock6
;
825 for (i
= 0; i
< 6; i
++) {
826 if (player_socks
[i
] != -1) {
827 if (player_socks
[i
] < 0)
828 fd
= (~player_socks
[i
]) - 1;
830 fd
= player_socks
[i
];
837 if (select(maxfd
+1, &fds
, NULL
, NULL
, NULL
) <= 0)
840 if (listen_sock
>= 0 && FD_ISSET(listen_sock
, &fds
)) {
841 struct sockaddr_in sin
;
842 int len
= sizeof(sin
);
843 fd
= accept(listen_sock
, (struct sockaddr
*)&sin
, &len
);
845 for (i
= 0; i
< 6 && player_socks
[i
] != -1; i
++)
848 sockprintf(fd
, "noconnecting Too many players on server!");
851 player_socks
[i
] = ~(fd
+1);
852 memcpy(player_ips
[i
], &sin
.sin_addr
, 4);
855 } /* if (FD_ISSET(listen_sock)) */
858 if (listen_sock6
>= 0 && FD_ISSET(listen_sock6
, &fds
)) {
859 struct sockaddr_in6 sin6
;
860 int len
= sizeof(sin6
);
861 fd
= accept(listen_sock6
, (struct sockaddr
*)&sin6
, &len
);
863 for (i
= 0; i
< 6 && player_socks
[i
] != -1; i
++)
866 sockprintf(fd
, "noconnecting Too many players on server!");
869 player_socks
[i
] = ~(fd
+1);
870 memcpy(player_ips
[i
], (char *)(&sin6
.sin6_addr
)+12, 4);
873 } /* if (FD_ISSET(listen_sock6)) */
876 for (i
= 0; i
< 6; i
++) {
879 if (player_socks
[i
] == -1)
881 if (player_socks
[i
] < 0)
882 fd
= (~player_socks
[i
]) - 1;
884 fd
= player_socks
[i
];
885 if (!FD_ISSET(fd
, &fds
))
887 sgets(buf
, sizeof(buf
), fd
);
888 if (player_socks
[i
] < 0) {
889 /* Messy decoding stuff */
890 char iphashbuf
[16], newbuf
[1024];
894 if (strlen(buf
) < 2*13) { /* "tetrisstart " + initial byte */
896 player_socks
[i
] = -1;
900 sprintf(iphashbuf
, "%d", ip
[0]*54 + ip
[1]*41 + ip
[2]*29 + ip
[3]*17);
902 for (j
= 2; buf
[j
] && buf
[j
+1]; j
+= 2) {
904 temp
= d
= xtoi(buf
+j
);
905 d
^= iphashbuf
[((j
/2)-1) % strlen(iphashbuf
)];
912 if (strncmp(newbuf
, "tetrisstart ", 12) != 0) {
914 player_socks
[i
] = -1;
917 /* Buffers should be the same size, but let's be paranoid */
918 strncpy(buf
, newbuf
, sizeof(buf
));
919 buf
[sizeof(buf
)-1] = 0;
920 player_socks
[i
] = fd
; /* Has now registered */
921 } /* if client not registered */
922 if (!server_parse(i
+1, buf
)) {
924 player_socks
[i
] = -1;
926 send_to_all("playerleave %d", i
+1);
937 } /* for each player socket */
940 /*************************************************************************/
950 if ((i
= init()) != 0)
955 if (listen_sock
>= 0)
958 if (listen_sock6
>= 0)
961 for (i
= 0; i
< 6; i
++)
962 close(player_socks
[i
]);
966 /*************************************************************************/