#include "melange.h"

fd_set fd_read_set;
int server_socket;
int mRealServerSocket;
int max_descriptors;

static int MelangeServerLoop();
static int SetupNewConnection();
static int start_server(int quiet);
static void stop_server();


int main (int argc, char *argv[]) {
    int i;
    int quiet = 0;    /* If not zero, this program will start without printing anything
                     ** to stdout or stderr
                     **/

   /** mServerPort is a global int declared in melange.h
   *** It is used to decide what port melange will run on.
   *** The default PORT is #defined in melange.h as 6666,
   *** however this can be changed via a command line switch.
   *** Manhattan's custom.h has a MELANGE_PORT #define.  This
   *** simply controls the -p xxxx switch the "Start Melange" 
   *** option in Manhattan's admin system sends to this program.
   **/
    mServerPort=PORT;    

   /** Check for command line arguments.
   *** -p PORT number changes mServerPort
   *** -q if exists, starts this server without printing to stdout
   ***
   *** Manhattan's admin system starts this program with:
   ***
   ***   -p MELANGE_SERVER_PORT -q
   ***
   *** where MELANGE_SERVER_PORT is #defined in Manhattan's custom.h
   **/
    for (i=1;i<argc;i++) 
      {
	if ((strcasecmp(argv[i],"-p")==0)&&((i+1)<argc))   /* change server port */
    	    mServerPort=atoi(argv[i+1]);
        if(strcasecmp(argv[i], "-q") == 0)                /* start quietly */
            quiet = 1;
      }
    

   /** Initialize the major data structures for the chat, including:
   ***
   ***  - Clear out the mClient[] array - there are no users online
   ***  - Set the values in the mAdmin data structure, which specifies
   ***    the configuration for this program, by reading the configuration
   ***    file.
   ***  - Set up (open) the system log file
   ***
   **/
    util_ChatInit(CONFIGFILE, quiet);    /* util.c , CONFIGFILE is #defined in melange.h */
    
    if(start_server(quiet) == 0 )
      {
          signal_SetSignals();         
          MelangeServerLoop();
          stop_server();
      }    
    return(0);
}




static int SetupNewConnection() {

    struct hostent *gethostbyaddr(), *clientHostDesc;
    struct sockaddr_in mClientSocket; 
    int newClientSocket;

    socklen_t addrLength;
    int i=0;

    admin_KickIdleUsers();
    addrLength = sizeof(mClientSocket); 
    newClientSocket = accept(server_socket, (struct sockaddr *)&mClientSocket, &addrLength); 

    if (newClientSocket < 0) 
      { 
        snprintf(infoText,MAX_INFO_TEXT + 1, "melangeServerLoop::SetupNewConnection()::ACCEPT - failure %d: %s\n", errno, strerror(errno));
        *(infoText + MAX_INFO_TEXT) = '\0';
        util_WriteLog(2,infoText);
	return(1); 
      } 

    addrLength = sizeof(mClientSocket.sin_addr);
    clientHostDesc = gethostbyaddr ((char *)&mClientSocket.sin_addr,addrLength,AF_INET);

    if (clientHostDesc==NULL) 
      {
	if ( (admin_CheckIfAllowed(newClientSocket,(char *)inet_ntoa(mClientSocket.sin_addr),NULL)) !=YES) 
	  {
	    snprintf(infoText, MAX_INFO_TEXT + 1, "SECURITY: connection from %s denied.\r\n",(char *)inet_ntoa(mClientSocket.sin_addr));
	    *(infoText + MAX_INFO_TEXT) = '\0';
	    util_WriteLog(1,infoText);
	    return(2);
	 }	    
     }
    else 
      {
	if ( (admin_CheckIfAllowed(newClientSocket,(char *)inet_ntoa(mClientSocket.sin_addr),(char *)clientHostDesc->h_name)) !=YES) 
	  {
	    snprintf(infoText, MAX_INFO_TEXT + 1, "SECURITY: connection from %s (%s) denied.\r\n",(char *)inet_ntoa(mClientSocket.sin_addr),(char *)clientHostDesc->h_name);
	    *(infoText + MAX_INFO_TEXT) = '\0';
	    util_WriteLog(1,infoText);
	    return(2);
	 }
    }
    /* find first empty socket */
    while ((mClient[i].socket!=0)&&(i<maxClients)) i++; 

    if (i<maxClients) 
     {
        mClient[i].socket=newClientSocket;
        
        if ((clientHostDesc!=NULL)&&(strlen(((char *)clientHostDesc->h_name))<100))
    	    strncpy(mClient[i].host,(char *)clientHostDesc->h_name, MAX_HOST_NAME + 1);
	else
	    strncpy(mClient[i].host,(char *)inet_ntoa(mClientSocket.sin_addr), MAX_HOST_NAME + 1);
	mClient[i].host[MAX_HOST_NAME] = '\0';   /* make sure host is null-terminated */
	
	    
	mClient[i].channel=0;
        
	snprintf(infoText,MAX_INFO_TEXT + 1, "NEW CONNECTION: name unknown:%s (%s):line %d::socket %d:on port %d\r\n",
		inet_ntoa(mClientSocket.sin_addr),mClient[i].host, i, mClient[i].socket, ntohs(mClientSocket.sin_port)); 
       *(infoText + MAX_INFO_TEXT) = '\0';
	util_WriteLog(1,infoText);

	mClient[i].idle=time(NULL);
	if (mAdmin.uniqueNames==NO) 
	  {
	    sys_SendLoginCommand(i,YES);
	    
	    
	    /*** MANHATTAN: don't want to bother people announcing that 'unknown' joined 
	                    next two lines commented out 
	    snprintf (msgText,MAX_MSG_TEXT + 1, MSG_LOGIN,mClient[i].name,mClient[i].host,i);
	    comm_SendChannelBut(SYSMSG,mClient[i].channel,i,msgText);
	    util_WriteMsgLog(msgText);
            *****************************************************************************/
            
	    
	    mClient[i].online=YES;
    	    
    	    util_AdjustUserCount(1);     /*** MANHATTAN: this line added */
    	    snprintf (msgText,MAX_MSG_TEXT + 1, MSG_WELCOME,VERSION);
    	    *(msgText + MAX_MSG_TEXT) = '\0';
	    comm_SendTo(SYSMSG,i,msgText);
	    sys_SendUserUpdate(mClient[i].channel,SYSTEM);
	  } 
	  else 
	   {
	    snprintf (msgText,MAX_MSG_TEXT + 1, MSG_UNQWELCOME,VERSION);
	    *(msgText + MAX_MSG_TEXT) = '\0';
	    comm_SendTo(SYSMSG,i,msgText);
	   }
	   
	if (newClientSocket > max_descriptors) 
	    max_descriptors = newClientSocket; 
     } 
     else 
       {    /* more than maxClients already logged in */
        util_WriteLog(1,"SERVER FULL: connection denied\r\n");
        snprintf(msgText,MAX_MSG_TEXT + 1, "%s\nSorry, the server is full - please come back later.\r\n",VERSION);
        *(msgText + MAX_MSG_TEXT) = '\0';
        write(newClientSocket,msgText,strlen(msgText));
        close(newClientSocket);
      }
    return(0);
}
    
    
    


/** MANHATTAN - write the pid **/    
static void write_pid_file()
{
FILE *fp;

fp = fopen("melange.pid","w");
fprintf(fp,"%d\n",getpid());
fclose(fp);

}    


     
 
static void
get_socket_list()
{
 int i;
 
 FD_ZERO(&fd_read_set);  		/* empty the set completely */
 FD_SET(server_socket, &fd_read_set);   /* add the server socket */
 max_descriptors = server_socket;
 for(i=0; i < maxClients; i++)
  {
   if( (mClient[i].online == YES) && mClient[i].socket)
     {
      FD_SET(mClient[i].socket, &fd_read_set);
      if(mClient[i].socket > max_descriptors)
           max_descriptors = mClient[i].socket;
     }
  }           
}  


static
int start_server(int quiet)
{

   struct sockaddr_in mInternetSocket;  /* Internet socket name */
   struct sockaddr mRealSocket;

   socklen_t addrLength;

   int optval;
   struct linger mLinger;
    
    if (( server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) 
     {
      if(!quiet)
        {
          snprintf(infoText, MAX_INFO_TEXT + 1, "melange server socket failure %d: %s\n", errno, strerror(errno));
          *(infoText + MAX_INFO_TEXT) = '\0';
          util_printf(infoText);
        }  
      return(-1);
     }

    /* Initialize the fields in the Internet socket name structure. */
   
   mInternetSocket.sin_family = AF_INET;  				/* Internet address */
   mInternetSocket.sin_port = htons((u_short)mServerPort);	        /* "Listen port" */
   mInternetSocket.sin_addr.s_addr = htonl(INADDR_ANY);  		/* "Wildcard" */

   optval=1;
   if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval))<0) 
     {
     
      if(!quiet)
       {
          snprintf(infoText,MAX_INFO_TEXT + 1, "melange setsockopt REUSEADDR failure %d: %s\n", errno, strerror(errno));
          *(infoText + MAX_INFO_TEXT) = '\0';
           util_printf(infoText);
       }    
      return (-1);
   }
   
   
   /** SO_LINGER controls what will happen when a socket is closed.
   *** If this option is set (by setting l_onoff = 1) the process will
   *** wait up to l_linger seconds for any remaining data in the output
   *** buffer, targeted for that socket, to be sent to that socket.
   ***
   *** Within melange, it's not absolutely critical for every last byte of data to 
   *** be sent to a socket, since they are simply chat messages.  Also we often will
   *** need to close sockets that are mis-behaving. (For e.g. when a user has lost 
   *** their internet connection.)  For those reasons the l_linger time should be set
   *** low (just a few seconds).   Melange will seem to 'lock up' for l_linger seconds
   *** when a socket is closed, but there's no one at the receiving end to receive the
   *** data melange is trying to send before closing that socket.   
   **/ 
   

   mLinger.l_onoff = 1;    /* turn linger on */
   mLinger.l_linger = 1;   /* linger no more than x seconds on socket close */
   if (setsockopt(server_socket, SOL_SOCKET, SO_LINGER, (char *)&mLinger, sizeof(mLinger))<0) 
     {
      if(!quiet)
        {
         snprintf(infoText,MAX_INFO_TEXT + 1, "melange setsockopt SO_LINGER failure %d: %s\n", errno, strerror(errno));
        *(infoText + MAX_INFO_TEXT) = '\0';
         util_printf(infoText);
        }  
      return (-1);
   }
   
   
   optval=0; 
   if (setsockopt(server_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&optval, sizeof(optval))<0) 
    {
      if(!quiet)
       {
         snprintf(infoText,MAX_INFO_TEXT + 1, "melange setsockopt KEEPALIVE failure %d: %s\n", errno, strerror(errno));
        *(infoText + MAX_INFO_TEXT) = '\0';
         util_printf(infoText);
       }  
      return (-1);
   }


   /* Bind the Internet address to the Internet socket */
   if (bind(server_socket, (struct sockaddr*) &mInternetSocket, sizeof(mInternetSocket)) < 0 ) 
     {
      close(server_socket);	  					/* defensive programming  */
      
      if(!quiet)
        {
         snprintf(infoText,MAX_INFO_TEXT + 1, "melange server bind failure %d: %s\n",errno, strerror(errno));
         *(infoText + MAX_INFO_TEXT) = '\0';
         util_printf(infoText);
        } 
      return(-2);
   }

   /*	Get the port number assigned to the Internet socket. */

   addrLength = sizeof(mRealSocket); 				/* need int for return value */
   if ( ( mRealServerSocket = getsockname(server_socket, &mRealSocket, &addrLength) ) < 0 ) 
     {
      
      if(!quiet)
       {
         snprintf(infoText,MAX_INFO_TEXT + 1, "melange server getsockname failure %d: %s\n",errno, strerror(errno));
         *(infoText + MAX_INFO_TEXT) = '\0';
          util_printf(infoText);
       }   
      close (server_socket);
      return(-3);
   }

   /*  Mark the socket as a "listen-only" or passive socket */

   if ( listen ( server_socket, 10 ) < 0 ) 
    {
      
      if(!quiet)
        {
           snprintf(infoText,MAX_INFO_TEXT + 1, "melange server bind failure %d: %s\n", errno, strerror(errno));
           *(infoText + MAX_INFO_TEXT) = '\0';
           util_printf(infoText);
        }   
      close (server_socket);
      return(-4);
   }
    
   /*	Now "advertise" the port number assigned to the	socket and 
        identify the server process + put some fancy message on the screen. */
   if(!quiet)
      printf ("--> server running on port %d (PID: %d)\r\n\n",mServerPort,getpid());
   else
      write_pid_file();
      
   return 0;    
         
 

}



static void stop_server()
{
   admin_ShutDown();
   close(server_socket);
   close(mRealServerSocket);
   FD_ZERO(&fd_read_set);
   util_ResetLog(KILL);
   signal_ResetSignals();
   unlink("melange.pid");   /* MANHATTAN - delete the PID file */
}   









static int
readable_sockets_found()
{
 int descriptors;
 struct timeval select_timeout;

      /** 
      ***   - descriptors is the returned number of selected sockets, -1 on error, 0 on timeout
      ***   - param 1:max_descriptors is always the highest number of sockets in any of the sets
      ***   - param 2: the set of sockets we're testing for read (all of our clients, plus this server)
      ***   - param 3: the set of sockets to test for write (none)
      ***   - param 4: set of sockets to watch for exceptions (none)
      ***   - param 5: timeout - how long should select wait for?
      */
      
      select_timeout.tv_sec = 10;      /* wait for up to this number of seconds */
      select_timeout.tv_usec = 0;
      descriptors=select(max_descriptors+1, &fd_read_set, (fd_set *)0, (fd_set *)0, &select_timeout);
      
      /** did an error occur on select() ?**/
      if (descriptors < 0) 
      {
          if (errno==EINTR) 
          {
              util_WriteLog(3,"melangeServerLoop()::SELECT - interrupted system call\r\n");
	      return 0;
	  }
          snprintf(infoText,MAX_INFO_TEXT + 1, "melangeServerLoop()::SELECT - failure %d: %s\r\n", errno, strerror(errno));
          *(infoText + MAX_INFO_TEXT) = '\0';
          util_WriteLog(2,infoText);
	  return 0;
      } 
    else
      return descriptors;
}



static
void handle_server_socket()
{
   /* deal with new connections first */
   if (FD_ISSET(server_socket, &fd_read_set)) 
     { 
      /* a new connection is available on the connection socket */ 
      if (SetupNewConnection()!=0) 
	      util_WriteLog(4,"melangeServerLoop()::NEWCONNECTION - failure\r\n");
     }

}



static int
read_socket(int socket, int *line, char *buffer)
{
 int bytes_read;	         
 int saved_errno;
 
 /* what user is on this socket */ 
 *line = chat_GetLine(socket); 

 /* try to read from the socket  */ 
 bytes_read = read(socket, buffer, BUFSIZ - 1);

 if (bytes_read<=0)   /* then either client closed their end, or a read error occurred */
  { 
   saved_errno = errno;
   
  
   if (bytes_read == 0)   /* user closed their end */
      snprintf(infoText, MAX_INFO_TEXT + 1, "READ found EOF  at socket %d. (Client closed connection?)\r\n", socket);
   else                   /* a read error occurred */
     snprintf(infoText,MAX_INFO_TEXT + 1, "READ - failure %d: %s\r\n", saved_errno, strerror(saved_errno));
            	
  *(infoText + MAX_INFO_TEXT) = '\0';    
  util_WriteLog(2,infoText);
    
    
    
  if (saved_errno!=ENOENT)     /* ENOENT is "No such file or directory." Why????*/
       snprintf(infoText,MAX_INFO_TEXT + 1, "FORCED LOGOUT:%s:host %s:line %d:socket %d:User left without logging out properly?\r\n", 
		       			*line >= 0?mClient[*line].name:"unknown", *line >= 0?mClient[*line].host:"unknown", *line, socket );
 else
       snprintf(infoText,MAX_INFO_TEXT + 1, "FORCED LOGOUT:%s:host %s:line %d:socket %d:connection to client broken\r\n", 
		       			*line >= 0?mClient[*line].name:"unknown", *line >= 0?mClient[*line].host:"unknown", *line, socket );

     *(infoText + MAX_INFO_TEXT) = '\0';        
     util_WriteLog(1,infoText);			

  
   close(socket);
   if( (*line >= 0) && (*line < maxClients) )
          chat_Logout(*line,KILL);
  }
 else
    buffer[bytes_read] = '\0';   /* null terminate the string */ 
   
 return bytes_read;
 
}




static int MelangeServerLoop() 
{
  int socket;
  char socket_data_buffer[BUFSIZ];
  int line;

  int shutDownServer=NO;
   
   do {

      get_socket_list();
      if(readable_sockets_found())
       {
         handle_server_socket();
	
      	  /* look for other sockets that have data available */ 	
         for (socket=0; socket <= max_descriptors ; socket++) 
           {
	     if ((socket != server_socket) && FD_ISSET(socket, &fd_read_set)) 
	       { 
	         if(read_socket(socket, &line, socket_data_buffer) > 0)
	          {
		    chat_InterpretData(line,socket_data_buffer, &shutDownServer);
	          }
	       }
           }    /* for(socket=... */
        } /* if(readable_sockets_found...) */  
   } while(shutDownServer!=YES);
   
   return (0);
}

