/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
 *   A server for a multi-player version of Tetris
 *
 *   Copyright (C) 1996 Roger Espel Llima <roger.espel.llima@pobox.com>
 *
 *   Started: 10 Oct 1996
 * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 *
 *  rewritten as a multi-cleint text echo server
 *  
 *   2008 rue_mohr
 *   
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation. See the file COPYING for details.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>

// echo
#define DEFAULTPORT 77

#define RESTARTDELAY 4


int clients = 0;
int is_daemon = 0;
int verbose = 0;


#define BUFSIZE 4096

struct user {
  int fd;
  unsigned char number;
  struct user *next, *nextvictim;
  unsigned char readbuf[BUFSIZE];
  int nread;
  unsigned char writbuf[BUFSIZE];
  int nwrite;
};

struct user *user0 = NULL;

#define NEXT(u) ((u)->next ? (u)->next : user0)

int port = DEFAULTPORT;
struct sockaddr_in saddr;
int lfd;
unsigned char realbuf[512], *buf = realbuf + 4;

int interrupt;
int tcp = -1;

fd_set fds;

/* like memcpy, but guaranteed to handle overlap when s <= t */
void copydown(char *s, char *t, int n) {
  for (; n; n--)
    *(s++) = *(t++);
}

void fatal(char *s) {
  if (!is_daemon)
    fprintf(stderr, "%s.\n", s);
  exit(1);
}

void syserr(char *s) {
  if (!is_daemon)
    fprintf(stderr, "fatal: %s failed.\n", s);
  exit(1);
}

void addtobuffer(struct user *u, unsigned char *b, int len) {
  if (u->nwrite + len >= BUFSIZE)
    fatal("Internal error: send buffer overflow");
  memcpy(u->writbuf + u->nwrite, b, len);
  u->nwrite += len;
}

void flushuser(struct user *u) {
  if (u->nwrite) {
    write(u->fd, u->writbuf, u->nwrite);
    u->nwrite = 0;
  }
}

void broadcast(struct user *except, int len, int activeonly) {
  struct user *u;

  for (u=user0; u; u=u->next)
    if (u != except )
      addtobuffer(u, realbuf+4, len);
}

void sendtoone(struct user *to, int len) {
  addtobuffer(to, realbuf + 4, len);
}

void dropuser(struct user *u) {
  struct user *v, *w;
  
  if (verbose)
    printf("dropping client %d \n", u->number);
  if (u == user0)
    user0 = u->next;
  else {
    for (v=user0; v; v=v->next)
      if (v->next && v->next == u) {
	v->next = u->next;
	break;
      }
  }
  close(u->fd);

  for (v=user0; v; v=v->next) {
    if (v->nextvictim == u) {

      if (v->nextvictim == u)
  	      v->nextvictim = NULL;
    }
  }

  free(u);
  clients--;

}


void new_connect(int fd) {
  struct user *u, *v;
  unsigned char nxn;

  u = malloc(sizeof (struct user));
  if (!u)
    fatal("Out of memory");
  u->fd = fd;
  u->next = user0;
  u->nextvictim = NULL;
  u->nread = 0;
  u->nwrite = 0;
  user0 = u;

  nxn = 1;
again:
  v = u->next;
  while(v) {
    if (v->number == nxn) {
      nxn++;
      goto again;
    }
    v = v->next;
  }
  u->number = nxn;
  
  if (verbose)
    printf("client %d connecting from %s\n", nxn, inet_ntoa(saddr.sin_addr));
    
  clients++;
  
  sprintf(buf, "CID:%d\n", nxn);
  sendtoone(u, strlen(buf));
}

int main(int argc, char *argv[]) {
  int i, sl, on;
  struct user *u, *v, *w;
  int mfd;
  int r; 
  unsigned int len;
  char *opt;
  struct protoent *tcpproto;
  struct timeval tv;
  struct sigaction sact;

  if ((tcpproto = getprotobyname("tcp")) != NULL)
    tcp = tcpproto->p_proto;

  sact.sa_handler = SIG_IGN;
  sigemptyset(&sact.sa_mask);
  sact.sa_flags = 0;
  sigaction(SIGPIPE, &sact, NULL);

  while (argc >= 2) {
    opt = argv[1];
    if (opt[0] == '-' && opt[1] == '-')
      opt++;
    if (strcmp(opt, "-h") == 0 || strcmp(opt, "-help") == 0) {
      printf(
      
"Use: xtserv [options]\n"
"Options:   -once       --  exit when all clients disconnect\n"
"           -v          --  be verbose\n"
"           -daemon     --  run as a daemn\n"
"           -p port     --  set server on given port\n"
"           -norestart  --  don't auto-start games\n"
"           -nospeedup  --  don't increase the speed automatically\n"

);
      exit(0);
    } else if (strcmp(opt, "-daemon") == 0) {
      argv++;
      argc--;
      is_daemon = 1;
    } else if (strcmp(opt, "-p") == 0) {
      if (argc < 3)
	fatal("Missing argument for -p");
      port = atoi(argv[2]);
      argv += 2;
      argc -= 2;
    } else if (strcmp(opt, "-v") == 0) {
      verbose = 1;
      argv++;
      argc--;
    } else fatal("Unrecognized option, try -h for help");
  }

  lfd = socket(PF_INET, SOCK_STREAM, 0);
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = htonl(INADDR_ANY);
  saddr.sin_port = htons(port);

  if (lfd < 0)
    syserr("socket");
  on = 1;

  setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(int));
  if (bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    syserr("bind");

  listen(lfd, 5);

  if (is_daemon) {
    /* become a daemon, breaking all ties with the controlling terminal */
    verbose = 0;
    for (i=0; i<255; i++) {
      if (i != lfd)
	close(i);
    }
    if (fork())
      exit(0);
    setsid();
    if (fork())
      exit(0);
    chdir("/");
    /* open a fake stdin, stdout, stderr, just in case */
    open("/dev/null", O_RDONLY);
    open("/dev/null", O_WRONLY);
    open("/dev/null", O_WRONLY);
  }

  if (verbose)
    printf("H.A.M.S. started up, listening on port %d\n\n", port);

  while(1) {
    interrupt = 0;


    for (u=user0; u; u=u->next)
      flushuser(u);

    FD_ZERO(&fds);
    mfd = lfd;
    u = user0;
    while (u) {
      FD_SET(u->fd, &fds);
      if (u->fd > mfd)
	mfd = u->fd;
      u = u->next;
    }
    FD_SET(lfd, &fds);
    tv.tv_sec = 0;
    tv.tv_usec = 500000;
    if ((sl = select(mfd + 1, &fds, NULL, NULL, &tv)) < 0)
      if (errno != EINTR)
	syserr("select");
      else continue;
    
    if (sl < 0)
      continue;

    if (sl == 0)
      continue;

    if (FD_ISSET(lfd, &fds)) {
      int newfd, slen;

      slen = sizeof(saddr);
      newfd = accept(lfd, (struct sockaddr *)&saddr, &slen);
      if (newfd < 0) {
	if (errno != EINTR)
	  syserr("accept");
      } else {
	if (tcp != -1) {
	  on = 1;
	  setsockopt(newfd, tcp, TCP_NODELAY, (char *)&on, sizeof(int));
	}
	new_connect(newfd);
      }
      continue;
    }

    u = user0;
    do {
      if (FD_ISSET(u->fd, &fds)) {
	     r = read(u->fd, u->readbuf , BUFSIZE );
	     if (r <= 0) {
	       if (verbose)
	         printf("EOF from client %d \n", u->number);
	       dropuser(u);
	       interrupt = 1;
	       break;
	     }
        
        strncpy(buf, u->readbuf, r);
      //  sendtoone(u, r);
        broadcast(u, r, 0);
  
      }
      if (u && !interrupt)
	     u = u->next;
    } while (u && !interrupt);
  }
}

