
/*
 * static char *rcsid_sockets_c =
 *    "$Id: socket.c,v 1.31 1997/04/24 09:43:33 master Exp $";
 */

/*
    CrossFire, A Multiplayer game for X-windows

    Copyright (C) 1992 Frank Tore Johansen

    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; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    The author can be reached via e-mail to frankj@ifi.uio.no.
*/

/*
 * The routines in socket.c were mainly written by eanders+@cmu.edu
 * I moved them to a separate file, since main() got too clobbered.  -Frank.
 */


#include <global.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
#ifdef SERVER
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#if defined(hpux) || defined(SVR4)
#include <unistd.h>
#endif
#if defined(_IBMR2) || defined(___IBMR2)
#include <sys/select.h>
#endif
#if defined(__sun__) && !defined(SVR4)
int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
#endif


int max_filedescriptors = 0;
struct timeval timeout;
int acceptfd,pollret;
struct protoent *protox;
struct sockaddr_in insock;
struct sockaddr_in addr;
int addrlen=sizeof(struct sockaddr);
int doneone=0;
#endif /* SERVER */

void init_socket() {
#ifdef SERVER
  if(settings.servermode == SERVER_ENABLED)
    LOG(llevDebug,"Opening add user socket on %d\n",settings.listen_port);

#if defined(hpux) || defined (SVR4)
  max_filedescriptors = sysconf(_SC_OPEN_MAX);
#else
  max_filedescriptors = getdtablesize();
#endif
  timeout.tv_sec = 0;
  timeout.tv_usec = 0;

  protox = getprotobyname("tcp");

  if (protox==NULL) {
    LOG(llevError,"Error getting protobyname\n");
    exit(1);
  }
  acceptfd = socket(PF_INET,SOCK_STREAM,protox->p_proto);
  if (acceptfd == (-1)) {
    perror("error on socket command");
    exit(-1);
  }

  insock.sin_family = AF_INET;
  insock.sin_port = htons(settings.listen_port);
  insock.sin_addr.s_addr = htonl(INADDR_ANY);

  {
    struct linger linger_opt;
    linger_opt.l_onoff = 0;
    linger_opt.l_linger = 0;
    if(setsockopt(acceptfd,SOL_SOCKET,SO_LINGER,(char *) &linger_opt,
       sizeof(struct linger)))
    perror("error on setsockopt LINGER");
  }

#if defined(__osf__) || defined(hpux) || defined(sgi) || defined(NeXT) || \
	defined(__sun__) || defined(linux) || defined(SVR4)
  {
    char tmp =1;

    if(setsockopt(acceptfd,SOL_SOCKET,SO_REUSEADDR, &tmp, sizeof(&tmp)))
      perror("error on setsockopt REUSEADDR");
  }
#else
  if(setsockopt(acceptfd,SOL_SOCKET,SO_REUSEADDR,(char *)NULL,0))
   perror("error on setsockopt REUSEADDR");
#endif
  if (bind(acceptfd,(struct sockaddr *)&insock,sizeof(insock)) == (-1)) {
    if(settings.servermode == SERVER_ENABLED || settings.debug)
      perror("error on bind command");
    abort_sockets();
    return;
  }

  if (listen(acceptfd,5) == (-1))  {
    perror("error on listen");
    abort_sockets();
    return;
  }

  if(settings.servermode == SERVER_ENABLED)
    block_until_new_connection();
#endif
}

void check_socket() {
#ifdef SERVER
  fd_set tmp_read;
  fd_set tmp_exceptions;
  sockets *next=NULL;

  if(settings.servermode == SERVER_DISABLED)
    return;

  FD_ZERO(&tmp_read);
  FD_ZERO(&tmp_exceptions);
  for(active_socket = first_socket; active_socket != (sockets *) NULL;
      active_socket = active_socket->next) {
    FD_SET(active_socket->fd,&tmp_read);
    FD_SET(active_socket->fd,&tmp_exceptions);
  }
  FD_SET(acceptfd,&tmp_read);

  pollret = select(max_filedescriptors,&tmp_read,NULL,&tmp_exceptions,&timeout);
  if (pollret == (-1)) {
    perror("select");
    return;
  }
  if (!pollret)	/* Nothing new */
    return;
  if (FD_ISSET(acceptfd,&tmp_read)) { /* try to accept any new connections */
    int fd = accept(acceptfd,(struct sockaddr *)&addr,&addrlen);
    if (fd == (-1))
      perror("accept");
    else {
      unsigned long from = ntohl(addr.sin_addr.s_addr);
      add_socket(fd, from);
    }
  }
  for(active_socket = first_socket; active_socket != (sockets *) NULL;
      active_socket = next) {
    next = active_socket->next;
    if(FD_ISSET(active_socket->fd,&tmp_exceptions)) {
      LOG(llevError,"Exception received at %d\n",active_socket->fd);
      remove_socket(active_socket);
      continue;
    }
    if(FD_ISSET(active_socket->fd,&tmp_read)) {
      int readct;
      char buf[SOCKET_BUFLEN+1];
      /* Have input */
      readct = read(active_socket->fd,buf,SOCKET_BUFLEN);
      if(readct == (-1)) {
        perror("read");
        continue;
      }
      if (readct==0) {
        close(active_socket->fd);
        remove_socket(active_socket);
        if (settings.servermode == SERVER_ABORTED) {
          LOG(llevError, "Server mode ended.\n");
          settings.servermode = SERVER_OFF;
        }
      } else {
	/* The following should never ever happen. */
        if (readct>SOCKET_BUFLEN) {
          LOG(llevError,"Error, Protocol Violation, input too long\n");
          draw_socket(active_socket->fd,"Line too long, ignored.");

	  /* Having this be fatal probably does not make much
	   * difference - if the buffer was overflowed, various
	   * areas of data will be corrupted in any case.
	   */
          /*return; *//* Why should this be fatal??? */
	  continue;
	}
        buf[readct]='\0';
        if (buf[readct-1] == '\n')
          buf[--readct] = '\0';
        if (buf[readct-1] == '\r')
          buf[--readct] = '\0';
        if (strncmp(buf, "dm ", 3)) /* Better not log the password */
          LOG(llevDebug,"socket[%s]:(%d) %s\n",active_socket->name,
	      readct,buf);
        if(buf[0]=='\0')
          continue; /* Empty line */

        parse_command(NULL,buf);
        if (active_socket->quit == 1)
        {
          close(active_socket->fd);
          remove_socket(active_socket);
        }
      }
    }
  }
  active_socket = (sockets *) NULL;
#endif /* SERVER */
}

void add_socket(int fd, unsigned long from) {
#ifdef SERVER
  sockets *socket = (sockets *) malloc(sizeof(sockets));
  socket->fd = fd;
  sprintf(socket->host,"%ld.%ld.%ld.%ld",
          (from>>24)&255, (from>>16)&255, (from>>8)&255, from&255);
  LOG(llevDebug, "Adding socket %d from %s.\n",fd,socket->host);
  socket->listen_lev = 8;
  socket->wiz = 0;
  socket->use_pix = 0;
  socket->color_pix = 0;
  socket->split = 0;
  socket->debug = llevError;
  socket->protocol = 0;
  socket->quit = 0;
  socket->next = first_socket;
  first_socket = socket;
  if(fd>9999 || fd < 0)
    strcpy(socket->name,"anonym");
  else
    sprintf(socket->name,"anon%d",fd);
  draw_socket(socket->fd, "Welcome to a crossfire server.  Type 'help' if you need help.");
#endif
}

void set_protocol(sockets *s, int p) {
  if (p < 0 || p > 1) {
    char buf[MAX_BUF];
    sprintf(buf,"Invalid protocol, accepted protocols are: 0 1");
    draw_socket(s->fd, buf);
  }
  s->protocol = p;
  draw_socket(s->fd, "OK.");
}

void remove_socket(sockets *socket) {
#ifdef SERVER
  sockets *tmp, *prev = NULL;

  for(tmp = first_socket; tmp != NULL; prev = tmp, tmp = tmp->next)
    if(tmp == socket)
      break;
  if(tmp == NULL) {
    LOG(llevError,"remove_socket: no such socket\n");
    return;
  }
  if(prev == NULL)
    first_socket = tmp->next;
  else
    prev->next = tmp->next;
  LOG(llevDebug,"Removing socket %d\n",tmp->fd);
  free(tmp);
  if (first_player == (player *) NULL &&
      first_socket == (sockets *) NULL && settings.servermode == SERVER_ENABLED)
    block_until_new_connection();
#endif
}

void close_all_sockets() {
#ifdef SERVER
  info_all_sockets("Crossfire died, disconnecting...");
  settings.servermode = SERVER_ABORTED; /* Don't want to block */
  while(first_socket != (sockets *) NULL) {
    close(first_socket->fd);
    remove_socket(first_socket);
  }
#endif
}

void draw_socket(int fd, const char *str) {
#ifdef SERVER
  int i=0,len;
  char *buf = (char *) malloc(sizeof(char) * strlen(str) + 2);

  strcpy(buf,str);
  strcat(buf,"\n");
  str = buf;

  while ((len = strlen(str))) {
    i=write(fd,str,len);
    if(i < 0) {
      perror("draw_socket");
      break;
    }
    else if (i != len) {
      LOG(llevDebug, "draw_socket: write returned %d of %d\n", i, len);
    } else
      str+=i;
  }
  free(buf);
#endif
}

void info_all_sockets(char *txt) {
#if defined(SERVER)
  sockets *s;
  for(s = first_socket; s != (sockets *) NULL; s = s->next)
    if (s->protocol != 1 && s->listen_lev>0)
      draw_socket(s->fd,txt);
#endif
}

void abort_sockets() {
#ifdef SERVER
  if(settings.servermode != SERVER_ENABLED) {
    LOG(llevDebug,"Turning off sockets.\n");
    if(close(acceptfd))
      perror("Disabling sockets/close");
    settings.servermode = SERVER_DISABLED; /* No need to check them in the future */
    return;
  }
  exit(1);
#endif
}

void block_until_new_connection()
{
#ifdef SERVER
  fd_set readfs;

  FD_ZERO(&readfs);
  FD_SET(acceptfd, &readfs);
#ifdef ERIC_SERVER
  FD_SET(ericfd(),&readfs);
#endif

  LOG(llevError, "Waiting for connections...\n");
  (void) select(max_filedescriptors, &readfs, NULL, NULL, NULL);
  reset_sleep(); /* Or the game would go too fast */
#endif
}
