/* chat.c --- Multiuser chat utility

    Copyright (C) 1994  Andreas Matthias

    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 1, 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.

    Bugs, fixes, comments to: andy@titan.central.de (Andreas Matthias)

*/

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include "fdes.h"

#define VERSION "0.2"
#define DEFAULT_PROTOCOL 0

/* The number of simultaneous users we can handle */
#define MAXUSER 15

/* Internal defines - No need to change these */
#define EVAL_CLOSE -1
#define EVAL_OUTTHIS -2
#define EVAL_UNKNOWNUSER -3
#define EVAL_NOCOMMAND -4
#define EVAL_STOPPRIVATE -5
#define EVAL_SHOWMODE -6

/* Global variables */
char names[MAXUSER][1024];

/* Function prototypes */
int eval_command( char *buffer );
int name2num( char *user );
char* num2name( int num, char *user );


/* Converts a user name to his index in the Fd arrays */
/* Returns -1 if the user does not exist */
int name2num( char *user )
{
  int number, i;
  
  for (i=0; i<MAXUSER; i++)
    if ( strcmp( names[i], user )==0 )
      return i;
  
  return -1;
}


/* Converts a user index to his name */
/* Returns a zero-length string if the index is unused */
char* num2name( int num, char *user )
{
  strcpy( user, names[num] );
  return user;
}


/* Evaluates an internal command */
/* Returns one of the eval constants (<0) or a user index (>=0) */
int eval_command( char *buffer )
{
  char s1[128], *p, user[128];
  int i, usernumber;

  /* Strip '%' away */
  buffer++;
  
  /* Extract first word of command */
  for( i=0, p=buffer; ((*p!=' ')&&(*p!='\0')&&(*p!='\r')); i++, p++ )
    s1[i]=*p;

  s1[i]='\0';

  /* Exit */
  if ( (strcmp( s1, "exit" )==0)||(strcmp( s1, "quit" )==0) )
    return EVAL_CLOSE;
  
  /* Private message */
  if (strcmp( s1, "p" )==0)
  {
    /* Get the user name */
    for (p=&buffer[i+1]; *p==' '; p++)  /* Skip spaces */
      ;
    for( i=0; ((*p!=' ')&&(*p!='\0')&&(*p!='\r')); i++, p++ )
      user[i]=*p;
    user[i]='\0';  

    usernumber=name2num( user );
    if ( usernumber==-1 )
      return EVAL_STOPPRIVATE;
    else
      return usernumber;
  }
  
  /* List all users */
  if (strcmp( s1, "who" )==0)
  {
    buffer--;
    buffer[0]='\0';
    for (i=0; i<MAXUSER; i++ )
      if ( strlen(num2name( i, user ))!=0 )
      {
	strcat( buffer, user );
	strcat( buffer, "\t" );
      }
    return EVAL_OUTTHIS;
  }

  /* Show mode information */
  if (strcmp( s1, "?" )==0)
    return EVAL_SHOWMODE;

  return EVAL_NOCOMMAND;
}


int main(int argc, char *argv[])
{
  int serverFd, clientFd[MAXUSER];
  struct sockaddr_in serverAddress;
  struct sockaddr* serverSockAddrPtr;
  struct sockaddr_in clientAddress;
  struct sockaddr* clientSockAddrPtr;
  int port;
  int serverLen, clientLen;
  char buffer[8192], line[1024], msg[1024], tmp[1024];
  int i, j, eval, msgfile, target[MAXUSER];
  char msgfilename[1024];
  FILE *f;

  signal(SIGCHLD, SIG_IGN);
  signal(SIGPIPE, SIG_IGN);

  for( i=0; i<MAXUSER; i++ )
  {
    names[i][0]='\0';
    target[i]=-1;
  }

  sprintf( msg, "CHAT %s (C) 1994 by Andreas Matthias\nSee file COPYING for details\n", VERSION );
  fputs( msg, stdout );

  if (((argc != 2)&&(argc != 3)) || (sscanf(argv[1], "%d", &port) != 1))
  {
    fputs("Usage: chat <socket> [<messagefile>]\n", stderr);
    exit(1);
  }

  if ( argc == 3 )
  {
    /* There is a message file */
    msgfile=1;
    strcpy( msgfilename, argv[2] );
  }
  else
    msgfile=0;

  serverFd = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
  serverLen = sizeof(serverAddress);

  bzero((char*) &serverAddress, serverLen);
  serverAddress.sin_family = AF_INET;
  serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
  serverAddress.sin_port = htons(port);

  serverSockAddrPtr = (struct sockaddr*) &serverAddress;
  if (bind(serverFd, serverSockAddrPtr, serverLen) != 0)
  {
    perror(argv[0]);
    exit(1);
  }

  listen(serverFd, MAXUSER);

  for (i = 0; i < MAXUSER; i++)
    clientFd[i]=-1;

  while(1)
  {
    for (i = 0; i < MAXUSER; i++)
    {
      if (clientFd[i]==-1)
      {
	fcntl(serverFd, F_SETFL, O_NONBLOCK);
	clientSockAddrPtr = (struct sockaddr*) &clientAddress;
	clientLen = sizeof(clientAddress);
	if ((clientFd[i] = accept(serverFd, clientSockAddrPtr, &clientLen))!=-1)
	{
	  fcntl(clientFd[i], F_SETFL, O_NONBLOCK);
	  sprintf( buffer, "Welcome to CHAT (C) 1994 by A.Matthias <andy@titan.central.de>" );
	  dputs(clientFd[i], buffer);
	  sprintf( buffer, "Your name? " );
	  dputs(clientFd[i], buffer);
	  while (dgets(clientFd[i], buffer, sizeof(buffer)) <= 0)
	    ;
	  strcpy( names[i], buffer );

	  /* Remove newline after name */
	  names[i][strlen(names[i])-2]='\0';

	  /* Inform all other users */
	  for (j = 0; j < MAXUSER; j++)
	    if ( (clientFd[j]!=-1) && (j!=i) )
	    {
	      sprintf( line, "NEW PARTICIPANT: %s", names[i] );
	      dputs(clientFd[j], line);
	    }

	  /* And print the message file to the new user */
	  if (msgfile==1)
	    if ( ( f=fopen( msgfilename, "r" ) ) != NULL )
	    {
	      while (!feof(f))
	      {
		fgets( msg, sizeof(msg), f );
		if (!feof(f))
		{
		  /* Remove the trailing newline */
		  msg[strlen(msg)-1]='\0';
		  dputs( clientFd[i], msg );
		}
	      }
	    }

	}
      }
      
      if (clientFd[i]!=-1)
	if (dgets(clientFd[i], buffer, sizeof(buffer)) > 0)
	{
	  if ( buffer[0]=='%' ) 
	  {
	    /* It's a command */
	    eval = eval_command( buffer );

	    switch ( eval )
	    {
	    case EVAL_CLOSE:
	      /* Log out user */
	      shutdown( clientFd[i], 2 );
	      clientFd[i]=-1;
	      names[i][0]='\0';
	      target[i]=-1;
	      break;
	    case EVAL_OUTTHIS:
	      /* Output the value of buffer to this user */
	      dputs(clientFd[i], buffer);	      
	      break;
	    case EVAL_UNKNOWNUSER:
	      /* Print an error message */
	      dputs(clientFd[i], "Unknown user name" );	      
	      break;
	    case EVAL_NOCOMMAND:
	      /* Print an error message */
	      dputs(clientFd[i], "Ignoring unrecognized command" );	      
	      break;
	    case EVAL_STOPPRIVATE:
	      dputs(clientFd[i], "Returning to GLOBAL mode" );	      
	      target[i]=-1;
	      break;
	    case EVAL_SHOWMODE:
	      if ( target[i]==-1 )
		sprintf( buffer, "You are in GLOBAL mode" );
	      else
		sprintf( buffer, "You are in PRIVATE mode with %s", 
			num2name( target[i], tmp ) );
	      dputs(clientFd[i], buffer);	      
	      break;
	    default:
	      /* Private message to a user requested */
	      dputs(clientFd[i], "Entering PRIVATE mode" );	      
	      target[i]=eval;
	      break;
	    }

	  }
	  else
	  {
	    for (j = 0; j < MAXUSER; j++)
	      if ( (clientFd[j]!=-1) && (j!=i) )
	      {
		if (target[i]==-1)
		  sprintf( line, "%s: %s", names[i], buffer );
		else
		  sprintf( line, "Private from %s: %s", names[i], buffer );
		
		if ((target[i]==-1) || (target[i]==j))
		  dputs(clientFd[j], line);
	      }
	  }
	}
    }
  }
}
