/************************************************************************/
/*									*/
/*  server_lib.c							*/
/*									*/
/************************************************************************/ 

/* Used by RCS and ident */
char server_lib_rcsid[] = 
    "$Id: server_lib.c,v 1.7 1998/09/29 06:44:33 chris Exp $";
    
    
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

#include "MLog.h"
#include "server_lib.h"
#include "tasks.h"


/************************************************************************/
/*									*/
/*		TABLE of CLIENTS					*/
/*									*/
/*  Every client that connects with this server gets an entry in this	*/
/*  table.    An entry with a socket = -1 indicates	                */
/*  an empty table entry.						*/
/*									*/
/************************************************************************/
static int		NumberClients = 0;	/* Entries in table	*/
static ClientDesTyp	ClientTable[MAX_CLIENTS];


/* prototypes for private functions. */
int ct_Socket2Index(int Socket);
int EvalRequestFilter(int ri);


/******* Prototyprs for STUBS TO BE WRITTEN LATER ***********************/
void sl_Send_ConnectionRefused(int sd_current);
void sl_Send_UnknownCommand(int CurrentSocket, char in_buffer[]);
int  ct_GetNextSocket();

/************************************************************************/
/*									*/
/*  Setup_Server_Socket							*/
/*                                                                      */
/*      Purpose:    Sets up a socket connected to a "well known port"   */
/*                  and listens on it.                                  */
/*									*/
/*      Inputs:     Port:   If < 0  look in "service" table then if     */
/*                                  not found use default               */
/*                          If > 0  Use this port, do not try to lookup */
/*                                                                      */
/************************************************************************/
int sl_Setup_Server_Socket(int Port)
{

        int			sd;
        struct   sockaddr_in	sin;
        struct   servent       *mk4d_servent;



        /* Create an internet domain socket */
        if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
        {
            /* Unable to create a socket. */
            ml_printf(ML_MANDATORY, ML_ERROR,
                "ERROR, create socket failed %s:%d %s\n",
                __FILE__, __LINE__, strerror(errno));
                return(0);
        }

        /* Fill in the socket name structure */
        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = INADDR_ANY;

        /* Was a port specified? */
        if ( Port > 0 )
        {
            /* Caller specified a port.  Use it */
            sin.sin_port = htons(Port);
        }
        else
        {

            /* Find the port number for the Mk4d server.  Extract from    */
            /* the /etc/services file.  If not found use default port     */
            mk4d_servent = getservbyname(MK4D_SERVICE, "tcp");
            if ( mk4d_servent )
            {
                sin.sin_port = mk4d_servent->s_port;
            }
            else
            {
                /* Failed to find the MK4D service.  Maybe the */
                /* /etc/services file does not have a mk4d     */
                /* entry in it?  Use the default port          */
                sin.sin_port = htons(MK4D_DEFPORT);
            }
            ml_printf(ML_VERBOSE, ML_INFORMATION,
                "Using port %d \n", ntohs(sin.sin_port));
        }

        /* bind the socket to the port number */
        if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
        {
            ml_printf(ML_MANDATORY, ML_ERROR,
                "ERROR, bind failed %s:%d %s\n",
                __FILE__, __LINE__, strerror(errno));
            return(0);
        }

        /* show that we are willing to listen */
        if (listen(sd, MAX_CLIENTS) == -1)
        {
            ml_printf(ML_MANDATORY, ML_ERROR,
                "ERROR, listen failed %s:%d %s\n",
                __FILE__, __LINE__, strerror(errno));
            return(0);
        }
        
        return(sd);
}


/************************************************************************/
/*									*/
/*  sl_ConnentToNextClient						*/
/*									*/
/************************************************************************/
int sl_ConnentToNextClient(int socket)
{
    struct sockaddr_in	pin;
    int     		addrlen;
    int			sd_current;
    ClientDesTyp        Client;
    
    
    sd_current = accept(socket, (struct sockaddr *)&pin, &addrlen);

    if (sd_current == -1)
    {
        /* No new clients. */

        switch (errno)
        {
            case EWOULDBLOCK:
            {
		        /* The  socket  is  marked  non-blocking  and	*/ 
		        /* no connections are present to be accepted.	*/
		        /* This is normal and not concidered and error.	*/
		return 0;
	    }
            case EBADF:		/* The descriptor is invalid.	*/
            case ENOTSOCK:  	/* The descriptor references a 	*/
                		/* file, not a socket.		*/ 			
            case EOPNOTSUPP: 	/* The referenced socket is 	*/
                		/* not of type SOCK_STREAM.	*/
            case EFAULT:	/* The addr parameter is not in	*/
                		/* a writable part of the user	*/
                		/* address space.		*/
            default:
            {
                /* We should never get any of the above cases.	*/
                ml_printf(ML_MANDATORY, ML_ERROR,
                    "WARNING, accept failed %s:%d %s\n",
                    __FILE__, __LINE__, strerror(errno));
                return(0);
            }

        /* Return with the number of clients we connected with - Zero.	*/    
        return (0);
	}
    }
    else
    {	
/*
 *         int rp;
 *         rp = pin.sin_port;
 *         rp = pin.sin_family;
 */ 
        
        
        ml_print(ML_VERBOSE, ML_INFORMATION, "Accepted client\n");
        Client.ClientSocket     = sd_current;
        strcpy(Client.Hostname,   "");
        Client.IP_Address       = 0;
        strcpy(Client.ClientName, "");
        strcpy(Client.Username,   "");
        Client.Authenticated    = 0;
        
	/* Got a new client.  Attempt to add it to the client 		*/
	/* table.  If table is full drop client. 			*/
	if(ct_AddClient(&Client) == -1)
	{
	    /* Could not add client to table.  Perhaps table is     */
	    /* full.  This in not a fatal error for us but we       */
	    /* should notify the client.  Client may want to        */
	    /* retry later after other clients are killed.          */
	    ml_print(ML_VERBOSE, ML_INFORMATION, 
		"Could not add new client to client table, dropping client\n");
	    sl_Send_ConnectionRefused(sd_current);
	    close(sd_current);
	    ml_print(ML_VERBOSE, ML_INFORMATION, 
	                     "Refused client connection. \n");

	    /* Return with the number of clients we connected with 	*/
	    /* - Zero.							*/    
            return (0);
         }

    }
    /* We connected to a client and put it into the client table.   */
    /* Return with the number of clients we connected with -- One.  */
    ml_print(ML_VERBOSE, ML_INFORMATION, 
	"Added new client to table.\n");
    return (1);
}


/************************************************************************/
/*									*/
/*  sl_GetNextClientMessage						*/
/*									*/
/*  Purpose:								*/
/*      Reads data from specified socket.                               */
/*      The data is buffered in the client table untill a complete      */
/*      message is recieved.                                            */
/*									*/
/************************************************************************/
int sl_GetClientMessage(int CurrentSocket, char in_buffer[])
{
    int		msg_len;
    int		command_len;
    int         space;
    int         ci;
    char        local_buff[MK4D_MAXMSG];
    char*       newline_ptr;
    
    
    /* lookup the client in the client table */
    ci = ct_Socket2Index(CurrentSocket);
    if (ci < 0)
    {
        /* This should never happen.  It means we got data from */
        /* some client not in ou table                          */
        ml_print(ML_MANDATORY, ML_ERROR,
            "INTERNAL ERROR: client not in table\n");
    }
    
    msg_len = recv(CurrentSocket, local_buff, MK4D_MAXMSG, 0);
    if (msg_len == -1)
    {
        /* We got no message.  Let's see why.  Could simply be that     */
        /* this client did not send anything.                           */
        if (errno == EWOULDBLOCK)
        {
            /* Client did not send message */
            return 0;
        }
        else
        {
            /* Some wierd error.   Best just to keep going...	*/
            ml_printf(ML_MANDATORY, ML_ERROR,
	        "WARNING recv error %s:%d %s\n",
                __FILE__, __LINE__, strerror(errno));
	    return 0;
	}
    }
    else if (msg_len == 0)
    {
        /* End of file on a socket.  Must be that client is gone    */
        ml_printf(ML_VERBOSE, ML_INFORMATION,
            "Client connection %d dropped\n", ci);
        ct_RemoveClient(CurrentSocket);
    }
    
    /* We got a message.  Append to client's buffer while checking for  */
    /* any possable overflow. First add terminating zero                */
    local_buff[msg_len] = 0;
        
    space = sizeof(ClientTable[ci].buffer) - 
                (strlen(ClientTable[ci].buffer) + 1);
    if (space <= 0)
    {   
        /* the buffer is full so wipe it out.  drop those bytes on      */
        /* the floor to make room for new bytes.                        */
        ClientTable[ci].buffer[0] = (char)0;
        ml_print(ML_MANDATORY, ML_ERROR,
            "WARNING: Client buffer overflow\n");
        return 0;
    }    
    strncat (ClientTable[ci].buffer, local_buff, space);
        
    /* A message is concidered complete if it contains a newline    */
    /* character.  That's an LF for you DOS guys.                   */
    /* When the message is complete we copy it to the buffer in the */
    /* argument list and return the string lenght                   */
    /* Note that there may be multiple commands in one returned     */
    /* buffer.                                                      */
    newline_ptr = strrchr(ClientTable[ci].buffer, '\n');
    if (newline_ptr)
    {
        /* We have a complete message */
        command_len = 1 + (int)newline_ptr - (int)ClientTable[ci].buffer;
        memcpy(in_buffer, ClientTable[ci].buffer, command_len);
        in_buffer[command_len] = 0; /* terminate string */
        
        strncpy(ClientTable[ci].buffer, newline_ptr+1,
            sizeof(ClientTable[ci].buffer)); 
        return (strlen(in_buffer));
    }
    
    /* non-complete message */
    return 0;
}

/************************************************************************/
/*									*/
/*  sl_	Send2Client                                                     */
/*									*/
/*  Purpose:								*/
/*      Sends a text string to a client.                                */
/*									*/
/************************************************************************/
int sl_Send2Client(int sd, char message[])
{
    int len;
     
     
    len = strlen(message);
    if (send(sd, message, len, 0) == -1) 
    {
        /* the send failed. */
        ml_printf(ML_MANDATORY, ML_ERROR, 
            "Send failed in Send2Client: %s\n",
            strerror(errno));
        return(-1);
    }
    return(0);
}
   
    
/************************************************************************/
/*									*/
/*  ct_Initialize							*/
/*									*/
/*  Purpose:								*/
/*	This should be called before any other "ct" function            */
/*									*/
/************************************************************************/
void ct_Initialize()
{
    int i;

    /************************************/
    /* Initialize Table of Clients 	*/
    /************************************/
    NumberClients = 0;
    bzero(ClientTable, sizeof(ClientTable));
   
    /* Set all sockets to -1  -- Make table empty*/
    for (i=0; i<MAX_CLIENTS; i++)
    {
        ClientTable[i].ClientSocket = -1;
    } 
}   
    
/************************************************************************/
/*									*/
/*  ct_ClientCount()                                                    */
/*									*/
/*  Purpose:								*/
/*	Returns the number of connected clients                         */
/*									*/
/************************************************************************/ 
int ct_ClientCount(void)
{
    return NumberClients;
}  
    
/************************************************************************/
/*									*/
/*  ct_get_fdset                                                        */
/*									*/
/*  Purpose:								*/
/*	Scan the client table and build the parameters usable in a      */
/*      select system call.                                             */
/*                                                                      */
/*  Returns:                                                            */
/*      function value = number of clients in table                     */
/*      FD_limit, FDs  = usable by select.                              */
/*									*/
/************************************************************************/
int ct_get_fdset(int *FD_limit, fd_set *FDs)
{
    int		ci;
    fd_set	set;
    int		FD_max;
    int		FD_count;
    int		Client_FD;
    
    /* if the are no clients there can be no data */
    if (NumberClients < 1)
    {
        FD_ZERO(&set);
        *FD_limit = 0;
        *FDs      = set;
        return 0;
    }
    
    /* build an FD bitmap for the select call */
    FD_max   = 0;
    FD_count = 0;
    FD_ZERO(&set);
    
    /* loop over client table */
    for (ci = 0; ci < MAX_CLIENTS; ci++)
    {
        Client_FD = ClientTable[ci].ClientSocket;
        if (Client_FD >= 0)
        {
            /* Found a client in table */                            
            FD_SET(Client_FD, &set);
            if (Client_FD > FD_max) FD_max = Client_FD;
	    FD_count++;
        }
    }
    
    *FD_limit = FD_max + 1;
    *FDs      = set;
    return FD_count;
}

/************************************************************************/
/*									*/
/*  ct_AddClient							*/
/*									*/
/*  Purpose:								*/
/*	Add client's socket to client table.				*/
/*									*/
/*  Return:								*/
/*	Client Index (>0) on success					*/
/*	-1 on failure (table full)					*/
/*									*/
/************************************************************************/
int ct_AddClient(ClientDesTyp *NewClient)
{
    int ci;
    
    
    if ( (NewClient -> ClientSocket) <= 0 )
    {
        /* Can not add invalid socket */
        ml_print(ML_MANDATORY, ML_ERROR, 
                 "INTERNAL ERROR:  ct_AddClient 1\n");
        return (-1);
    } 
    
    /* Are we are are at the limit? */    
    if (NumberClients < MAX_CLIENTS)
    {
        /* There is room for one more client.  Search for first empty	*/
        /* entry in client table.					*/  
        for (ci=0; ci<MAX_CLIENTS; ci++)
        {
           if(ClientTable[ci].ClientSocket == -1)
           {
               /* We found a free table entry, fill it in */
               ClientTable[ci].ClientSocket = NewClient -> ClientSocket;
               strncpy(ClientTable[ci].ClientName, (NewClient -> ClientName),
                   sizeof(ClientTable[ci].ClientName));
               NumberClients++;
               return ci;
           }
        }
    }
    
    /* The table is full */
    return -1;
}


/************************************************************************/
/*									*/
/*  ct_SetClientInfo							*/
/*									*/
/*  Purpose:								*/
/*	Modify a client's table entry.                                  */
/*									*/
/*  Return:								*/
/*	Client Index (>0) on success					*/
/*	-1 on failure (table full)					*/
/*									*/
/************************************************************************/
int ct_SetClientInfo(int ClientSocket,
                     char hostname[], char clientname[],
                     char username[], int Authenticated)
{
    int ci;
    
    /* Search for client in table */ 
    ci = ct_Socket2Index(ClientSocket);             
    if (ci == -1)
    {
        /* Socket not found in table, cannot remove */
        ml_print(ML_MANDATORY, ML_ERROR, 
                 "INTERNAL ERROR:  ct_PutClientInfo\n");
        return (-1);
    }
    
    /* Found client, update it. */
    strncpy(ClientTable[ci].Hostname, hostname, 
        sizeof(ClientTable[ci].Hostname));
        
    strncpy(ClientTable[ci].ClientName, clientname, 
        sizeof(ClientTable[ci].ClientName));
        
    strncpy(ClientTable[ci].Username, username, 
        sizeof(ClientTable[ci].Username));
        
    ClientTable[ci].Authenticated = Authenticated;  

    return ci;
}


/************************************************************************/
/*									*/
/*  ct_GetClientAuthentication						*/
/*									*/
/*  Purpose:								*/
/*	Modify a client's table entry.                                  */
/*									*/
/*  Return:								*/
/*	Client Index (>0) on success					*/
/*	-1 on failure (table full)					*/
/*									*/
/************************************************************************/
int ct_GetClientAuthentication(int ClientSocket, int *Authenticated)
{
    int ci;
    
    /* Search for client in table */ 
    ci = ct_Socket2Index(ClientSocket);             
    if (ci == -1)
    {
        /* Socket not found in table, cannot remove */
        ml_print(ML_MANDATORY, ML_ERROR, 
                 "INTERNAL ERROR:  ct_GetClientAuthentication\n");
        return (-1);
    }
    
    /* Found client. */        
    *Authenticated = ClientTable[ci].Authenticated;  

    return ci;
}


/************************************************************************/
/*									*/
/*  ct_RemoveClient							*/
/*									*/
/*  Purpose:								*/
/*	Delete a client from the client table.				*/
/*									*/
/*  Return:								*/
/*	1  on success							*/
/*	-1 on failure							*/
/*									*/
/************************************************************************/
int ct_RemoveClient(int OldSocket)
{
    int ci;
    
    if (OldSocket <= 0)
    {
        /* can not remove invalid socket */
        ml_print(ML_MANDATORY, ML_ERROR, 
                 "INTERNAL ERROR:  ct_RemoveClient 1\n");
        return (-1);
    }
    
    /* Client may have scheduled some tasks.  Unschedule them now   */
    RemoveTaskBySocket(OldSocket);

    /* Search for client in table                                   */ 
    ci = ct_Socket2Index(OldSocket);             
    if (ci == -1)
    {
        /* Socket not found in table, cannot remove */
        ml_print(ML_MANDATORY, ML_ERROR, 
                 "INTERNAL ERROR:  ct_RemoveClient 2\n");
        return (-1);
    }
    
    /* Found client, now zap it. */
    close(ClientTable[ci].ClientSocket);
    
    bzero(&ClientTable[ci], sizeof(ClientDesTyp));
    ClientTable[ci].ClientSocket = -1;
    NumberClients--;  

    return 1;
}



/************************************************************************/
/*									*/
/*  ct_RemoveAllClients							*/
/*									*/
/*  Purpose:								*/
/*	Zap all clients                                                 */
/*									*/
/*  Return:								*/
/*	1  on success							*/
/*	-1 on failure							*/
/*									*/
/************************************************************************/
int ct_RemoveAllClients(void)
{
    int ci;

    /* Search for client in table					*/ 
    for (ci=0; ci<NumberClients; ci++)
    {             
        if (ClientTable[ci].ClientSocket != -1)
        {    
            /* Found client, now zap it. */
            close(ClientTable[ci].ClientSocket);

            bzero(&ClientTable[ci], sizeof(ClientDesTyp));
            ClientTable[ci].ClientSocket = -1;
        }
    }
    /* Just to be safe, let's clear the table */
    ct_Initialize();

    return 1;
}        

/************************************************************************/
/*************************** private functions **************************/
/************************************************************************/


/************************************************************************/
/*									*/
/*  ct_Socket2Index							*/
/*									*/
/*  Purpose:								*/
/*	Given a socket lookup the table index				*/
/*									*/
/*  Return:								*/
/*	Index to client or,						*/
/*	-1 on failure							*/
/*									*/
/************************************************************************/
int ct_Socket2Index(int Socket)
{
    int ci;
    
    /* lookup socket in client table */
    for (ci = 0; ci < MAX_CLIENTS; ci++)
    {
        if (ClientTable[ci].ClientSocket == Socket)
        {
            /* Found socket in table */                            
            return(ci);
        }
    }
    /* did not find socket in table */
    return (-1);
}




/************** STUBS TO BE WRITTEN LATER ***********************/
void sl_Send_ConnectionRefused(int sd_current){}
void sl_Send_UnknownCommand(int CurrentSocket, char in_buffer[]){}
int  ct_GetNextSocket(){return -1;}
