/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
 * This program is public domain.
 *
 * Tetrinet server code
 */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
/* Due to glibc brokenness, we can't blindly include this.  Yet another
 * reason to not use glibc. */
/* #include <netinet/protocols.h> */
#include <signal.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include "tetrinet.h"
#include "tetris.h"
#include "server.h"
#include "sockets.h"

/*************************************************************************/

static int linuxmode = 0;  /* 1: don't try to be compatible with Windows */
static int ipv6_only = 0;  /* 1: only use IPv6 (when available) */

static int quit = 0;

static int listen_sock = -1;
#ifdef HAVE_IPV6
static int listen_sock6 = -1;
#endif
static int player_socks[6] = {-1,-1,-1,-1,-1,-1};
static unsigned char player_ips[6][4];
static int player_modes[6];

/* Which players have already lost in the current game? */
static int player_lost[6];

/* We re-use a lot of variables from the main code */

/*************************************************************************/
/*************************************************************************/

/* Convert a 2-byte hex value to an integer. */

int xtoi(const char *buf)
{
    int val;

    if (buf[0] <= '9')
	val = (buf[0] - '0') << 4;
    else
	val = (toupper(buf[0]) - 'A' + 10) << 4;
    if (buf[1] <= '9')
	val |= buf[1] - '0';
    else
	val |= toupper(buf[1]) - 'A' + 10;
    return val;
}

/*************************************************************************/

/* Return a string containing the winlist in a format suitable for sending
 * to clients.
 */

static char *winlist_str()
{
    static char buf[1024];
    char *s;
    int i;

    s = buf;
    for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
	s += snprintf(s, sizeof(buf)-(s-buf),
			linuxmode ? " %c%s;%d;%d" : " %c%s;%d",
			winlist[i].team ? 't' : 'p',
			winlist[i].name, winlist[i].points, winlist[i].games);
    }
    return buf;
}

/*************************************************************************/
/*************************************************************************/

/* Read the configuration file. */

void read_config(void)
{
    char buf[1024], *s, *t;
    FILE *f;
    int i;

    s = getenv("HOME");
    if (!s)
	s = "/etc";
    snprintf(buf, sizeof(buf), "%s/.tetrinet", s);
    if (!(f = fopen(buf, "r")))
	return;
    while (fgets(buf, sizeof(buf), f)) {
	s = strtok(buf, " ");
	if (!s) {
	    continue;
	} else if (strcmp(s, "linuxmode") == 0) {
	    if ((s = strtok(NULL, " ")))
		linuxmode = atoi(s);
	} else if (strcmp(s, "ipv6_only") == 0) {
	    if ((s = strtok(NULL, " ")))
		ipv6_only = atoi(s);
	} else if (strcmp(s, "averagelevels") == 0) {
	    if ((s = strtok(NULL, " ")))
		level_average = atoi(s);
	} else if (strcmp(s, "classic") == 0) {
	    if ((s = strtok(NULL, " ")))
		old_mode = atoi(s);
	} else if (strcmp(s, "initiallevel") == 0) {
	    if ((s = strtok(NULL, " ")))
		initial_level = atoi(s);
	} else if (strcmp(s, "levelinc") == 0) {
	    if ((s = strtok(NULL, " ")))
		level_inc = atoi(s);
	} else if (strcmp(s, "linesperlevel") == 0) {
	    if ((s = strtok(NULL, " ")))
		lines_per_level = atoi(s);
	} else if (strcmp(s, "pieces") == 0) {
	    i = 0;
	    while (i < 7 && (s = strtok(NULL, " ")))
		piecefreq[i++] = atoi(s);
	} else if (strcmp(s, "specialcapacity") == 0) {
	    if ((s = strtok(NULL, " ")))
		special_capacity = atoi(s);
	} else if (strcmp(s, "specialcount") == 0) {
	    if ((s = strtok(NULL, " ")))
		special_count = atoi(s);
	} else if (strcmp(s, "speciallines") == 0) {
	    if ((s = strtok(NULL, " ")))
		special_lines = atoi(s);
	} else if (strcmp(s, "specials") == 0) {
	    i = 0;
	    while (i < 9 && (s = strtok(NULL, " ")))
		specialfreq[i++] = atoi(s);
	} else if (strcmp(s, "winlist") == 0) {
	    i = 0;
	    while (i < MAXWINLIST && (s = strtok(NULL, " "))) {
		t = strchr(s, ';');
		if (!t)
		    break;
		*t++ = 0;
		strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1);
		winlist[i].name[sizeof(winlist[i].name)-1] = 0;
		s = t;
		t = strchr(s, ';');
		if (!t) {
		    *winlist[i].name = 0;
		    break;
		}
		winlist[i].team = atoi(s);
		s = t+1;
		t = strchr(s, ';');
		if (!t) {
		    *winlist[i].name = 0;
		    break;
		}
		winlist[i].points = atoi(s);
		winlist[i].games = atoi(t+1);
		i++;
	    }
	}
    }
    fclose(f);
}

/*************************************************************************/

/* Re-write the configuration file. */

void write_config(void)
{
    char buf[1024], *s;
    FILE *f;
    int i;

    s = getenv("HOME");
    if (!s)
	s = "/etc";
    snprintf(buf, sizeof(buf), "%s/.tetrinet", s);
    if (!(f = fopen(buf, "w")))
	return;

    fprintf(f, "winlist");
    for (i = 0; i < MAXSAVEWINLIST && *winlist[i].name; i++) {
	fprintf(f, " %s;%d;%d;%d", winlist[i].name, winlist[i].team,
				   winlist[i].points, winlist[i].games);
    }
    fputc('\n', f);

    fprintf(f, "classic %d\n", old_mode);

    fprintf(f, "initiallevel %d\n", initial_level);
    fprintf(f, "linesperlevel %d\n", lines_per_level);
    fprintf(f, "levelinc %d\n", level_inc);
    fprintf(f, "averagelevels %d\n", level_average);

    fprintf(f, "speciallines %d\n", special_lines);
    fprintf(f, "specialcount %d\n", special_count);
    fprintf(f, "specialcapacity %d\n", special_capacity);

    fprintf(f, "pieces");
    for (i = 0; i < 7; i++)
	fprintf(f, " %d", piecefreq[i]);
    fputc('\n', f);

    fprintf(f, "specials");
    for (i = 0; i < 9; i++)
	fprintf(f, " %d", specialfreq[i]);
    fputc('\n', f);

    fprintf(f, "linuxmode %d\n", linuxmode);
    fprintf(f, "ipv6_only %d\n", ipv6_only);

    fclose(f);
}

/*************************************************************************/
/*************************************************************************/

/* Send a message to a single player. */

static void send_to(int player, const char *format, ...)
{
    va_list args;
    char buf[1024];

    va_start(args, format);
    vsnprintf(buf, sizeof(buf), format, args);
    if (player_socks[player-1] >= 0)
	sockprintf(player_socks[player-1], "%s", buf);
}

/*************************************************************************/

/* Send a message to all players. */

static void send_to_all(const char *format, ...)
{
    va_list args;
    char buf[1024];
    int i;

    va_start(args, format);
    vsnprintf(buf, sizeof(buf), format, args);
    for (i = 0; i < 6; i++) {
	if (player_socks[i] >= 0)
	    sockprintf(player_socks[i], "%s", buf);
    }
}

/*************************************************************************/

/* Send a message to all players but the given one. */

static void send_to_all_but(int player, const char *format, ...)
{
    va_list args;
    char buf[1024];
    int i;

    va_start(args, format);
    vsnprintf(buf, sizeof(buf), format, args);
    for (i = 0; i < 6; i++) {
	if (i+1 != player && player_socks[i] >= 0)
	    sockprintf(player_socks[i], "%s", buf);
    }
}

/*************************************************************************/

/* Send a message to all players but those on the same team as the given
 * player.
 */

static void send_to_all_but_team(int player, const char *format, ...)
{
    va_list args;
    char buf[1024];
    int i;
    char *team = teams[player-1];

    va_start(args, format);
    vsnprintf(buf, sizeof(buf), format, args);
    for (i = 0; i < 6; i++) {
	if (i+1 != player && player_socks[i] >= 0 &&
			(!team || !teams[i] || strcmp(teams[i], team) != 0))
	    sockprintf(player_socks[i], "%s", buf);
    }
}

/*************************************************************************/
/*************************************************************************/

/* Add points to a given player's [team's] winlist entry, or make a new one
 * if they rank.
 */

static void add_points(int player, int points)
{
    int i;

    if (!players[player-1])
	return;
    for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
	if (!winlist[i].team && !teams[player-1]
	 && strcmp(winlist[i].name, players[player-1]) == 0)
	    break;
	if (winlist[i].team && teams[player-1]
	 && strcmp(winlist[i].name, teams[player-1]) == 0)
	    break;
    }
    if (i == MAXWINLIST) {
	for (i = 0; i < MAXWINLIST && winlist[i].points >= points; i++)
	    ;
    }
    if (i == MAXWINLIST)
	return;
    if (!*winlist[i].name) {
	if (teams[player-1]) {
	    strncpy(winlist[i].name, teams[player-1], sizeof(winlist[i].name)-1);
	    winlist[i].name[sizeof(winlist[i].name)-1] = 0;
	    winlist[i].team = 1;
	} else {
	    strncpy(winlist[i].name, players[player-1], sizeof(winlist[i].name)-1);
	    winlist[i].name[sizeof(winlist[i].name)-1] = 0;
	    winlist[i].team = 0;
	}
    }
    winlist[i].points += points;
}

/*************************************************************************/

/* Add a game to a given player's [team's] winlist entry. */

static void add_game(int player)
{
    int i;

    if (!players[player-1])
	return;
    for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
	if (!winlist[i].team && !teams[player-1]
	 && strcmp(winlist[i].name, players[player-1]) == 0)
	    break;
	if (winlist[i].team && teams[player-1]
	 && strcmp(winlist[i].name, teams[player-1]) == 0)
	    break;
    }
    if (i == MAXWINLIST || !*winlist[i].name)
	return;
    winlist[i].games++;
}

/*************************************************************************/

/* Sort the winlist. */

static void sort_winlist()
{
    int i, j, best, bestindex;

    for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) {
	best = winlist[i].points;
	bestindex = i;
	for (j = i+1; j < MAXWINLIST && *winlist[j].name; j++) {
	    if (winlist[j].points > best) {
		best = winlist[j].points;
		bestindex = j;
	    }
	}
	if (bestindex != i) {
	    WinInfo tmp;
	    memcpy(&tmp, &winlist[i], sizeof(WinInfo));
	    memcpy(&winlist[i], &winlist[bestindex], sizeof(WinInfo));
	    memcpy(&winlist[bestindex], &tmp, sizeof(WinInfo));
	}
    }
}

/*************************************************************************/

/* Take care of a player losing (which may end the game). */

static void player_loses(int player)
{
    int i, j, order, end = 1, winner = -1, second = -1, third = -1;

    if (player < 1 || player > 6 || player_socks[player-1] < 0)
	return;
    order = 0;
    for (i = 1; i <= 6; i++) {
	if (player_lost[i-1] > order)
	    order = player_lost[i-1];
    }
    player_lost[player-1] = order+1;
    for (i = 1; i <= 6; i++) {
	if (player_socks[i-1] >= 0 && !player_lost[i-1]) {
	    if (winner < 0) {
		winner = i;
	    } else if (!teams[winner-1] || !teams[i-1]
			|| strcasecmp(teams[winner-1],teams[i-1]) != 0) {
		end = 0;
		break;
	    }
	}
    }
    if (end) {
	send_to_all("endgame");
	playing_game = 0;
	/* Catch the case where no players are left (1-player game) */
	if (winner > 0) {
	    send_to_all("playerwon %d", winner);
	    add_points(winner, 3);
	    order = 0;
	    for (i = 1; i <= 6; i++) {
		if (player_lost[i-1] > order
			&& (!teams[winner-1] || !teams[i-1]
			    || strcasecmp(teams[winner-1],teams[i-1]) != 0)) {
		    order = player_lost[i-1];
		    second = i;
		}
	    }
	    if (order) {
		add_points(second, 2);
		player_lost[second-1] = 0;
	    }
	    order = 0;
	    for (i = 1; i <= 6; i++) {
		if (player_lost[i-1] > order
			&& (!teams[winner-1] || !teams[i-1]
			    || strcasecmp(teams[winner-1],teams[i-1]) != 0)
			&& (!teams[second-1] || !teams[i-1]
			    || strcasecmp(teams[second-1],teams[i-1]) != 0)) {
		    order = player_lost[i-1];
		    third = i;
		}
	    }
	    if (order)
		add_points(third, 1);
	    for (i = 1; i <= 6; i++) {
		if (teams[i-1]) {
		    for (j = 1; j < i; j++) {
			if (teams[j-1] && strcasecmp(teams[i-1],teams[j-1])==0)
			    break;
		    }
		    if (j < i)
			continue;
		}
		if (player_socks[i-1] >= 0)
		    add_game(i);
	    }
	}
	sort_winlist();
	write_config();
	send_to_all("winlist %s", winlist_str());
    }
    /* One more possibility: the only player playing left the game, which
     * means there are now no players left. */
    if (!players[0] && !players[1] && !players[2] && !players[3]
                    && !players[4] && !players[5])
	playing_game = 0;
}

/*************************************************************************/
/*************************************************************************/

/* Parse a line from a client.  Destroys the buffer it's given as a side
 * effect.  Return 0 if the command is unknown (or bad syntax), else 1.
 */

static int server_parse(int player, char *buf)
{
    char *cmd, *s, *t;
    int i, tetrifast = 0;

    cmd = strtok(buf, " ");

    if (!cmd) {
	return 1;

    } else if (strcmp(cmd, "tetrisstart") == 0) {
newplayer:
	s = strtok(NULL, " ");
	t = strtok(NULL, " ");
	if (!t)
	    return 0;
	for (i = 1; i <= 6; i++) {
	    if (players[i-1] && strcasecmp(s, players[i-1]) == 0) {
		send_to(player, "noconnecting Nickname already exists on server!");
		return 0;
	    }
	}
	players[player-1] = strdup(s);
	if (teams[player-1])
	    free(teams[player-1]);
	teams[player-1] = NULL;
	player_modes[player-1] = tetrifast;
	send_to(player, "%s %d", tetrifast ? ")#)(!@(*3" : "playernum", player);
	send_to(player, "winlist %s", winlist_str());
	for (i = 1; i <= 6; i++) {
	    if (i != player && players[i-1]) {
		send_to(player, "playerjoin %d %s", i, players[i-1]);
		send_to(player, "team %d %s", i, teams[i-1] ? teams[i-1] : "");
	    }
	}
	if (playing_game) {
	    send_to(player, "ingame");
	    player_lost[player-1] = 1;
	}
	send_to_all_but(player, "playerjoin %d %s", player, players[player-1]);

    } else if (strcmp(cmd, "tetrifaster") == 0) {
	tetrifast = 1;
	goto newplayer;

    } else if (strcmp(cmd, "team") == 0) {
	s = strtok(NULL, " ");
	t = strtok(NULL, "");
	if (!s || atoi(s) != player)
	    return 0;
	if (teams[player])
	    free(teams[player]);
	if (t)
	    teams[player] = strdup(t);
	else
	    teams[player] = NULL;
	send_to_all_but(player, "team %d %s", player, t ? t : "");

    } else if (strcmp(cmd, "pline") == 0) {
	s = strtok(NULL, " ");
	t = strtok(NULL, "");
	if (!s || atoi(s) != player)
	    return 0;
	if (!t)
	    t = "";
	send_to_all_but(player, "pline %d %s", player, t);

    } else if (strcmp(cmd, "plineact") == 0) {
	s = strtok(NULL, " ");
	t = strtok(NULL, "");
	if (!s || atoi(s) != player)
	    return 0;
	if (!t)
	    t = "";
	send_to_all_but(player, "plineact %d %s", player, t);

    } else if (strcmp(cmd, "startgame") == 0) {
	int total;
	char piecebuf[101], specialbuf[101];

	for (i = 1; i < player; i++) {
	    if (player_socks[i-1] >= 0)
		return 1;
	}
	s = strtok(NULL, " ");
	t = strtok(NULL, " ");
	if (!s)
	    return 1;
	i = atoi(s);
	if ((i && playing_game) || (!i && !playing_game))
	    return 1;
	if (!i) {  /* end game */
	    send_to_all("endgame");
	    playing_game = 0;
	    return 1;
	}
	total = 0;
	for (i = 0; i < 7; i++) {
	    if (piecefreq[i])
		memset(piecebuf+total, '1'+i, piecefreq[i]);
	    total += piecefreq[i];
	}
	piecebuf[100] = 0;
	if (total != 100) {
	    send_to_all("plineact 0 cannot start game: Piece frequencies do not total 100 percent!");
	    return 1;
	}
	total = 0;
	for (i = 0; i < 9; i++) {
	    if (specialfreq[i])
		memset(specialbuf+total, '1'+i, specialfreq[i]);
	    total += specialfreq[i];
	}
	specialbuf[100] = 0;
	if (total != 100) {
	    send_to_all("plineact 0 cannot start game: Special frequencies do not total 100 percent!");
	    return 1;
	}
	playing_game = 1;
	game_paused = 0;
	for (i = 1; i <= 6; i++) {
	    if (player_socks[i-1] < 0)
		continue;
	    /* XXX First parameter is stack height */
	    send_to(i, "%s %d %d %d %d %d %d %d %s %s %d %d",
			player_modes[i-1] ? "*******" : "newgame",
			0, initial_level, lines_per_level, level_inc,
			special_lines, special_count, special_capacity,
			piecebuf, specialbuf, level_average, old_mode);
	}
	memset(player_lost, 0, sizeof(player_lost));

    } else if (strcmp(cmd, "pause") == 0) {
	if (!playing_game)
	    return 1;
	s = strtok(NULL, " ");
	if (!s)
	    return 1;
	i = atoi(s);
	if (i)
	    i = 1;	/* to make sure it's not anything else */
	if ((i && game_paused) || (!i && !game_paused))
	    return 1;
	game_paused = i;
	send_to_all("pause %d", i);

    } else if (strcmp(cmd, "playerlost") == 0) {
	if (!(s = strtok(NULL, " ")) || atoi(s) != player)
	    return 1;
	player_loses(player);

    } else if (strcmp(cmd, "f") == 0) {   /* field */
	if (!(s = strtok(NULL, " ")) || atoi(s) != player)
	    return 1;
	if (!(s = strtok(NULL, "")))
	    s = "";
	send_to_all_but(player, "f %d %s", player, s);

    } else if (strcmp(cmd, "lvl") == 0) {
	if (!(s = strtok(NULL, " ")) || atoi(s) != player)
	    return 1;
	if (!(s = strtok(NULL, " ")))
	    return 1;
	levels[player] = atoi(s);
	send_to_all_but(player, "lvl %d %d", player, levels[player]);

    } else if (strcmp(cmd, "sb") == 0) {
	int from, to;
	char *type;

	if (!(s = strtok(NULL, " ")))
	    return 1;
	to = atoi(s);
	if (!(type = strtok(NULL, " ")))
	    return 1;
	if (!(s = strtok(NULL, " ")))
	    return 1;
	from = atoi(s);
	if (from != player)
	    return 1;
	if (to < 0 || to > 6 || player_socks[to-1] < 0 || player_lost[to-1])
	    return 1;
	if (to == 0)
	    send_to_all_but_team(player, "sb %d %s %d", to, type, from);
	else
	    send_to_all_but(player, "sb %d %s %d", to, type, from);

    } else if (strcmp(cmd, "gmsg") == 0) {
	if (!(s = strtok(NULL, "")))
	    return 1;
	send_to_all("gmsg %s", s);

    } else {  /* unrecognized command */
	return 0;

    }

    return 1;
}

/*************************************************************************/
/*************************************************************************/

static void sigcatcher(int sig)
{
    if (sig == SIGHUP) {
	read_config();
	signal(SIGHUP, sigcatcher);
	send_to_all("winlist %s", winlist_str());
    } else if (sig == SIGTERM || sig == SIGINT) {
	quit = 1;
	signal(sig, SIG_IGN);
    }
}

/*************************************************************************/

/* Returns 0 on success, desired program exit code on failure */

static int init()
{
    struct sockaddr_in sin;
#ifdef HAVE_IPV6
    struct sockaddr_in6 sin6;
#endif
    int i;

    /* Set up some sensible defaults */
    *winlist[0].name = 0;
    old_mode = 1;
    initial_level = 1;
    lines_per_level = 2;
    level_inc = 1;
    level_average = 1;
    special_lines = 1;
    special_count = 1;
    special_capacity = 18;
    piecefreq[0] = 14;
    piecefreq[1] = 14;
    piecefreq[2] = 15;
    piecefreq[3] = 14;
    piecefreq[4] = 14;
    piecefreq[5] = 14;
    piecefreq[6] = 15;
    specialfreq[0] = 18;
    specialfreq[1] = 18;
    specialfreq[2] = 3;
    specialfreq[3] = 12;
    specialfreq[4] = 0;
    specialfreq[5] = 16;
    specialfreq[6] = 3;
    specialfreq[7] = 12;
    specialfreq[8] = 18;

    /* (Try to) read the config file */
    read_config();

    /* Catch some signals */
    signal(SIGHUP, sigcatcher);
    signal(SIGINT, sigcatcher);
    signal(SIGTERM, sigcatcher);

    /* Set up a listen socket */
    if (!ipv6_only)
	listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listen_sock >= 0){
	i = 1;
	if (setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))==0) {
	    memset(&sin, 0, sizeof(sin));
	    sin.sin_family = AF_INET;
	    sin.sin_port = htons(31457);
	    if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) == 0) {
		if (listen(listen_sock, 5) == 0) {
		    goto ipv4_success;
		}
	    }
	}
	i = errno;
	close(listen_sock);
	errno = i;
	listen_sock = -1;
    }
  ipv4_success:

#ifdef HAVE_IPV6
    /* Set up an IPv6 listen socket if possible */
    listen_sock6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
    if (listen_sock6 >= 0) {
	i = 1;
	if (setsockopt(listen_sock6,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))==0) {
	    memset(&sin6, 0, sizeof(sin6));
	    sin6.sin6_family = AF_INET6;
	    sin6.sin6_port = htons(31457);
	    if (bind(listen_sock6,(struct sockaddr *)&sin6,sizeof(sin6))==0) {
		if (listen(listen_sock6, 5) == 0) {
		    goto ipv6_success;
		}
	    }
	}
	i = errno;
	close(listen_sock6);
	errno = i;
	listen_sock6 = -1;
    }
  ipv6_success:
#else  /* !HAVE_IPV6 */
    if (ipv6_only) {
	fprintf(stderr,"ipv6_only specified but IPv6 support not available\n");
	return 1;
    }
#endif  /* HAVE_IPV6 */

    if (listen_sock < 0
#ifdef HAVE_IPV6
     && listen_sock6 < 0
#endif
    ) {
	return 1;
    }

    return 0;
}

/*************************************************************************/

static void check_sockets()
{
    fd_set fds;
    int i, fd, maxfd;

    FD_ZERO(&fds);
    if (listen_sock >= 0)
	FD_SET(listen_sock, &fds);
    maxfd = listen_sock;
#ifdef HAVE_IPV6
    if (listen_sock6 >= 0)
	FD_SET(listen_sock6, &fds);
    if (listen_sock6 > maxfd)
	maxfd = listen_sock6;
#endif
    for (i = 0; i < 6; i++) {
	if (player_socks[i] != -1) {
	    if (player_socks[i] < 0)
		fd = (~player_socks[i]) - 1;
	    else
		fd = player_socks[i];
	    FD_SET(fd, &fds);
	    if (fd > maxfd)
		maxfd = fd;
	}
    }

    if (select(maxfd+1, &fds, NULL, NULL, NULL) <= 0)
	return;

    if (listen_sock >= 0 && FD_ISSET(listen_sock, &fds)) {
	struct sockaddr_in sin;
	int len = sizeof(sin);
	fd = accept(listen_sock, (struct sockaddr *)&sin, &len);
	if (fd >= 0) {
	    for (i = 0; i < 6 && player_socks[i] != -1; i++)
		;
	    if (i == 6) {
		sockprintf(fd, "noconnecting Too many players on server!");
		close(fd);
	    } else {
		player_socks[i] = ~(fd+1);
		memcpy(player_ips[i], &sin.sin_addr, 4);
	    }
	}
    } /* if (FD_ISSET(listen_sock)) */

#ifdef HAVE_IPV6
    if (listen_sock6 >= 0 && FD_ISSET(listen_sock6, &fds)) {
	struct sockaddr_in6 sin6;
	int len = sizeof(sin6);
	fd = accept(listen_sock6, (struct sockaddr *)&sin6, &len);
	if (fd >= 0) {
	    for (i = 0; i < 6 && player_socks[i] != -1; i++)
		;
	    if (i == 6) {
		sockprintf(fd, "noconnecting Too many players on server!");
		close(fd);
	    } else {
		player_socks[i] = ~(fd+1);
		memcpy(player_ips[i], (char *)(&sin6.sin6_addr)+12, 4);
	    }
	}
    } /* if (FD_ISSET(listen_sock6)) */
#endif

    for (i = 0; i < 6; i++) {
	char buf[1024];

	if (player_socks[i] == -1)
	    continue;
	if (player_socks[i] < 0)
	    fd = (~player_socks[i]) - 1;
	else
	    fd = player_socks[i];
	if (!FD_ISSET(fd, &fds))
	    continue;
	sgets(buf, sizeof(buf), fd);
	if (player_socks[i] < 0) {
	    /* Messy decoding stuff */
	    char iphashbuf[16], newbuf[1024];
	    unsigned char *ip;
	    int j, c, d;

	    if (strlen(buf) < 2*13) {	/* "tetrisstart " + initial byte */
		close(fd);
		player_socks[i] = -1;
		continue;
	    }
	    ip = player_ips[i];
	    sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17);
	    c = xtoi(buf);
	    for (j = 2; buf[j] && buf[j+1]; j += 2) {
		int temp;
		temp = d = xtoi(buf+j);
		d ^= iphashbuf[((j/2)-1) % strlen(iphashbuf)];
		d += 255 - c;
		d %= 255;
		newbuf[j/2-1] = d;
		c = temp;
	    }
	    newbuf[j/2-1] = 0;
	    if (strncmp(newbuf, "tetrisstart ", 12) != 0) {
		close(fd);
		player_socks[i] = -1;
		continue;
	    }
	    /* Buffers should be the same size, but let's be paranoid */
	    strncpy(buf, newbuf, sizeof(buf));
	    buf[sizeof(buf)-1] = 0;
	    player_socks[i] = fd;  /* Has now registered */
	} /* if client not registered */
	if (!server_parse(i+1, buf)) {
	    close(fd);
	    player_socks[i] = -1;
	    if (players[i]) {
		send_to_all("playerleave %d", i+1);
		if (playing_game)
		    player_loses(i+1);
		free(players[i]);
		players[i] = NULL;
		if (teams[i]) {
		    free(teams[i]);
		    teams[i] = NULL;
		}
	    }
	}
    } /* for each player socket */
}

/*************************************************************************/

#ifdef SERVER_ONLY
int main()
#else
int server_main()
#endif
{
    int i;

    if ((i = init()) != 0)
	return i;
    while (!quit)
	check_sockets();
    write_config();
    if (listen_sock >= 0)
	close(listen_sock);
#ifdef HAVE_IPV6
    if (listen_sock6 >= 0)
	close(listen_sock6);
#endif
    for (i = 0; i < 6; i++)
	close(player_socks[i]);
    return 0;
}

/*************************************************************************/
