/************************************************************************/
/*                                                                      */
/*                                                                      */
/*                                                                      */
/************************************************************************/


#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

 
#include "shhopt.h"
#include "MLog.h"

/* Used by RCS and ident */
char SampleClient_rcsid[] = 
    "$Id: M4Cclient.c,v 1.1 1998/09/17 05:51:00 chris Exp $";
    
#define PROGNAME      "M4RLclient"
#define CLIENT_VERS   VERS

#define MK4D_MAXMSG   1000
#define MK4D_SERVICE  "Mk4d"

#define RTN_ERR            -1
#define RTN_OK              0

#define MAX_HOSTNAME      256

/* Globals */

static int     sock  = -1;

static char    arg_host[MAX_HOSTNAME];                          
static char    arg_cmd[256];                /* zero len = none  */
static int     arg_port             = -1;   /* -1 = unspecified */
static int     arg_connect_timeout  = 10;
static int     arg_retry_interval   = 1;
    
/* Prototypes */
int     Setup_Socket(void);
int     Send2Server(int sock, char message[]);
int     GetFromServer(int sock, char message[]);
int     IsLast(char buffer[]);
char*   rl_gets(char prompt[]);
void    ClientUsage(void);
void    ClientVersion(void);
int     ProcArgs(int argc, char *argv[]);
void    cleanup(int sig);
int     NonNullString(char line[]);
void    EchoServer(void);

int main(int argc, char *argv[])
{
    int     done;
    char*   line;
    char    buffer[MK4D_MAXMSG];
    char    PromptStr[60];
    int     res;

    /********************************************************************/
    /* Initialization stuff                                             */
    /********************************************************************/
    
    /* Catch signals that would otherwise terminate this process    */
    signal (SIGHUP,  cleanup);
    signal (SIGINT,  cleanup);
    signal (SIGQUIT, cleanup);
    signal (SIGTERM, cleanup);


    /* Process any options in argument list.    */
    ProcArgs(argc,argv);
 
    
    /* Sign on message */
    ml_printf(ML_VERBOSE, ML_INFORMATION, 
             "%s version %s\n", PROGNAME, CLIENT_VERS);
             

    /********************************************************************/
    /* Try to connect with the Mk4d server.  On return we will either   */
    /* be connected and ready to send on the socket or an error is      */
    /* returned.  A re-try on error should not be done as this was      */
    /* already attempted by the Setup_Socket function.                  */
    /********************************************************************/
    if (( sock = Setup_Socket() ) == 0)
    {
        /* unable to connect with server */
        ml_print(ML_MANDATORY, ML_ERROR,
                "Unable to connect to Mk4d - exiting\n");
        exit(1);
    }
    
    /* We are connected! Print a message if in "verbose" mode */
    ml_print(ML_VERBOSE, ML_INFORMATION,
            "Connected to Mk4d server\n");
 
            
    /********************************************************************/
    /* Mk4d protocol is pure ASCII so we can just accept keyboard input */
    /* and forward it to the Mk4d server.  This is not the nicest user  */
    /* interface but makes for simple example and a good test tool      */
    /* We just keep forwarding lines untill we are killed               */
    /*                                                                  */
    /* If we were passed a "-c" argument then we skip the reading       */
    /* from the keyboard stuff an just forward the one command  and     */
    /* exist cleanly.                                                   */
    /********************************************************************/
    
    if(0 != strlen(arg_cmd))
    {
        /* There was a "-c" argument.  Send command followed by a       */
        /* "logout" command                                             */
        snprintf(buffer, sizeof(buffer), "%s\nlogout\n", arg_cmd);

        res = Send2Server(sock, buffer);
        if (res < 0)
        {
            /* error sending data -- bail */
            ml_print(ML_VERBOSE, ML_INFORMATION, 
                "ERROR sending data.  Terminating\n");
            exit(1);
        }
        
        /* get and print all lines returned by server */
        EchoServer();
        
        cleanup(RTN_OK);
    }
    
    /* String used as a command prompt */
    snprintf(PromptStr, sizeof(PromptStr), "%s-->", PROGNAME);
       
    done = 0;
    while (!done)
    {
        /* Get command from keyboard */
        line = rl_gets(PromptStr);
        if (NonNullString(line))
        {
            if(0 == strcasecmp(line, "logout"))
            {
                /* This is the last command we will read */
                done = 1;
            }
            
            strncpy(buffer, line, sizeof(buffer));
            strncat(buffer, "\n", sizeof(buffer));

            res = Send2Server(sock, buffer);
            if (res < 0)
            {
                /* error sending data -- bail */
                ml_print(ML_VERBOSE, ML_INFORMATION, 
                    "ERROR sending data.  Terminating\n");
                exit(1);
            }
 
            /* get and print all lines returned by server */
            EchoServer();
        } 
    }
    
    cleanup(RTN_OK);
    
    /* "exit" never executes but required to make clean compile */
    exit(0);
}

/************************************************************************/
/*									*/
/************************************************************************/
void EchoServer(void)
{
    int     res;
    int     got_all;
    char    buffer[MK4D_MAXMSG];
    
    
    /************************************************************/
    /* The Mk4d server should send a responce back for every    */
    /* command.  A smarter client would                         */
    /*  1)  Remember what command was sent and know what to     */
    /*      expect.                                             */
    /*  2)  Do something other then simply print the data       */ 
    /* but here we just print it out.                           */
    /*                                                          */
    /* Note that the last line sent is always either of these:  */
    /*      <OK comandname>\n                                   */
    /*      <FAIL comandname>\n                                 */
    /*      <INVALID comandname>\n                              */
    /* We will keep reading and printing until we run into one  */
    /* of these lines.                                          */
    /************************************************************/
    got_all = 0;
    while (!got_all)
    {
        res = GetFromServer(sock, buffer);
        if (res < 0)
        {
            /* error getting data -- bail */
            ml_print(ML_VERBOSE, ML_INFORMATION, 
                "ERROR geting data.  Terminating\n");
            exit(1);
        }

        /* Added terminating zero */
        buffer[res] = 0;
        got_all = IsLast(buffer);

        printf(buffer);
    }
}
/************************************************************************/
/*									*/
/************************************************************************/
int ProcArgs(int argc, char *argv[])
{  
    int     arg_verbose;
    int     arg_debug;
    int     arg_syslog;
    int     msg_level;
    char*   arg_host_ptr;
    char*   arg_cmd_ptr;
    
    /********************************************************************/
    /* optStruct descibes the optional arguments accepted by this       */
    /* program.  "Long" parameter names are accepted.  For example      */
    /* either -h or --help will cause the usage mesage to be printed    */
    /********************************************************************/
    optStruct opt[] = {
      /* short long           type        var/func          special     */
        {'H', "help",        OPT_FLAG,   ClientUsage,      OPT_CALLFUNC},
        {'V', "version",     OPT_FLAG,   ClientVersion,    OPT_CALLFUNC},
        {'l', "syslog",      OPT_FLAG,   &arg_syslog,      0           },        
        {'h', "host",        OPT_STRING, &arg_host_ptr,    0           }, 
        {'p', "port",        OPT_INT,    &arg_port,        0           },       
        {'v', "verbose",     OPT_FLAG,   &arg_verbose,     0           },        
        {'d', "debug",       OPT_FLAG,   &arg_debug,       0           },        
        {'c', "command",     OPT_STRING, &arg_cmd_ptr,     0           },
        {0, 0, OPT_END, 0, 0 }  /* no more options */
    };
          
    /********************************************************************/
    /* Set default values then let arg list override.                   */
    /*                                                                  */
    /* If optParseOptions finds errors in the command line argument     */
    /* It will print an imformative message and abort the program       */
    /*									*/
    /* Next, set the "verboseity" level, print a sign-on message and	*/
    /* Set up an empty client table.					*/
    /*									*/
    /* Finaly, we create a socket, listen on it and connect up with any	*/
    /* clients that have been waiting for us to start up.  After this	*/
    /* we can start the "main loop."					*/
    /********************************************************************/
    arg_port	= -1; /* -1 = unspectified */
    arg_verbose =  0;
    arg_debug	=  0;
    arg_syslog	=  0;
    strncpy(arg_host, "localhost", sizeof(arg_host));
    strncpy(arg_cmd, "", sizeof(arg_host));
    
    optParseOptions(&argc, argv, opt, 0);
    
    msg_level = 0;
    if (arg_verbose) msg_level = 1;
    if (arg_debug)   msg_level = 2;
    
    ml_setup(msg_level, NULL, NULL);
    
    if (arg_syslog)
    {	
    	ml_use_syslog(PROGNAME);
    }
    
    if(arg_host_ptr)
    {
        strncpy(arg_host, arg_host_ptr, sizeof(arg_host));
    }
    
    if(arg_cmd_ptr)
    {
        strncpy(arg_cmd, arg_cmd_ptr, sizeof(arg_cmd));
    }
    
    ml_printf(ML_DEBUG, ML_INFORMATION, "  host: %s\n", arg_host);
    ml_printf(ML_DEBUG, ML_INFORMATION, "  port: %d\n", arg_port);
    
    return (RTN_OK);
}

void ClientUsage(void)
{
    printf("M4RLclient usage:  M4RLclient <options>...\n\n");
    printf("options my be the traditional signle dash, single letter style\n");
    printf("or the GNU style double dash, word style.  Both styles can be\n");
    printf("used interchangably.  The valid options are:\n\n");
    printf(" -V --version               Print version number, exit\n");
    printf(" -l --syslog                route messages to syslog\n");
    printf(" -h --host <hostname>       server's host machine\n");
    printf(" -p --port <portnumber>     server's port number\n");
    printf(" -v --verbose               print progress messages\n");
    printf(" -d --debug                 print debug messages\n");
    printf(" -c --command <command>     execute <command> then exit\n");
    
    exit(0);
}

void ClientVersion(void)
{
    printf("%s Version %s Compiled  %s %s\n",
            PROGNAME, CLIENT_VERS, __DATE__, __TIME__);
     
    exit(0);
}


/************************************************************************/
/*  Setup_Socket will return a Socket that is conectd to the Mk4d       */
/*  server.  It will return zero on failure.                            */
/************************************************************************/
int Setup_Socket(void)
{
    int    sd = 0;
    char   default_server[] = "localhost";
    char*  Mk4d_host;

/*
 *     struct sockaddr_in sin;
 */ 
    struct sockaddr_in pin;
    struct hostent *hp;
    struct servent *Mk4d_servent;

    int connected_to_server = 0;
    int wait_seconds        = 0;
    int sleep_seconds       = arg_retry_interval;
    int unslept_seconds;

    /********************************************************************/
    /* Get the server host name.                                        */
    /* First check if hostname was set on the command line.  If so use  */
    /* that.  If not set on the command line get the server host name   */
    /* from Mk4d_HOST environment variable.  If not specified in        */
    /* either place we will use "localhost" as the hostname.            */
    /********************************************************************/
    
    if ( strlen(arg_host) > 0 )
    {
        /* Host name specified on the command line */
        Mk4d_host = arg_host;
    }
    else
    {
        /* Hostname not found on the command line.  Look at the         */
        /* MK4D_HOST environment variable.                              */
        Mk4d_host = getenv("MK4D_HOST");
        if (Mk4d_host == (char*)NULL)
        {
                /* The env var was not found and there was no hostname  */
                /* on the command line either.  Use localhost           */
                Mk4d_host = default_server;
        }
    }
    ml_printf(ML_DEBUG, ML_INFORMATION,
        "using hostname: %s\n", Mk4d_host);


    /********************************************************************/
    /* We now have a hostname in text but we need the address of the    */
    /* server.  So go get a hostent structure.                          */
    /* The gethostbyname function will use whatever service is required */
    /* to find a host, /etc/hosts, NIS or DNS. So the host could be     */
    /* no the local machine or anywhere out on the Internet.            */
    /* Either way we get the IP address and stuff it into a socket      */
    /* address structure.                                               */
    /********************************************************************/
    if ((hp = gethostbyname(Mk4d_host)) == 0)
    {
        /* Hostname lookup failed.  Print error and bail */
        ml_printf(ML_MANDATORY, ML_ERROR,
            "Hostname lookup failed: %s\n",
            strerror(errno));
        return(0);
    }

    /* fill in the socket name with host information */
    memset(&pin, 0, sizeof(pin));
    pin.sin_family = AF_INET;
    pin.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr))->s_addr;

    /********************************************************************/
    /* Determine which port number to use for the Mk4d server           */
    /* normally this information is kept in the /ect/services file      */
    /* If NIS is running it can also be contained in the services map   */
    /* The command line argument -p or --port overrides whatever is in  */
    /* the above system location.  This may be good for testing.        */
    /* In any case to port number must be combined with the address to  */
    /* fully specify the socket with which to conect.                   */
    /********************************************************************/
    if (arg_port < 0)
    {
        /* There was NO port number on the command line. */
        if ((Mk4d_servent = getservbyname(MK4D_SERVICE, "tcp")) == NULL)
        {
            /* Failed to find the Mk4d service.  Maybe the   */
            /* /etc/services file does not have a Mk4d       */
            /* entry in it?                                  */
            ml_printf(ML_MANDATORY, ML_ERROR,
                "Failed to find the Mk4d service: %s\n",
                strerror(errno));
            return(0);
        }
        pin.sin_port = Mk4d_servent->s_port;
    }
    else
    {
        /* There WAS a port number on the command line. */
         pin.sin_port = htons(arg_port);
    }

    /* Print the port number */ 
    ml_printf(ML_DEBUG, ML_INFORMATION,
        "Using port %d \n", ntohs(pin.sin_port));
    
    /********************************************************************/
    /* Create an Internet Domain Socket and atempt to connect with      */
    /* another one of these owned by the server.  All of the above was  */
    /* to locate that server.  Now we do the create and connect.        */
    /********************************************************************/

    /* The "socket" function will create a socket */
    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        /* If the above call fails we are in deep you know */
        /* what.  Print an error  and bail.                */
            ml_printf(ML_MANDATORY, ML_ERROR,
                "Failed to create socket: %s\n",
                strerror(errno));
        return(0);
    }

    /********************************************************************/
    /* Try to connect to the Mk4d server on Mk4d_host.                  */
    /* There are many ways this can fail but the most likey is that the */
    /* the server is simply not running.  So for now we will just keep  */
    /* trying until we reach a timeout limit.                           */
    /* Later I'll add code to check for unrecoverable errors and bail   */
    /* out without haveing to wait for a potentialy very long timeout   */
    /********************************************************************/
    while ((! connected_to_server) &&
           (wait_seconds < arg_connect_timeout))
    {
        if (connect(sd, (struct sockaddr *)&pin, sizeof(pin)) == -1)
        {
            /* Failed to connect.  Likely server is not running */
            ml_printf(ML_MANDATORY, ML_ERROR,
                "Failed to connect with server (is it running?): %s\n",
                strerror(errno));
            
            if ( 0 )                    /* <<<<<<<<<<<<<< fix later     */
            {
                /* non-recoverable error */
                /* print a message       */
                return(0);
            }
            else
            {
                /* recoverable error, retry */
                unslept_seconds = sleep(sleep_seconds);
                wait_seconds = wait_seconds + sleep_seconds
                               - unslept_seconds;
            }
        }
        else
        {
            /* Connected!! */
            connected_to_server = 1;
        }
    }
 

    /********************************************************************/
    /* Return to caller with either:                                    */
    /*      A) a valid socket. or B) sd = 0                             */
    /********************************************************************/
    if (! connected_to_server)
    {
        /* Failed to connect.  Return error */
        ml_print(ML_DEBUG, ML_INFORMATION,
            "Failed to connect with server\n");
        return(0);
    }    
    /* we made it!  Connected. */
    ml_print(ML_DEBUG, ML_INFORMATION,
        "Connected to server\n");
    return(sd);
}


/************************************************************************/
/*									*/
/************************************************************************/
int Send2Server(int sock, char message[])
{
    int len;
    
    
    len = strlen(message);
    if (send(sock, message, len, 0) == -1) 
    {
        /* the send failed. */
        ml_printf(ML_MANDATORY, ML_ERROR, 
            "Send failed in Send2Server: %s\n",
            strerror(errno));
        return(-1);
    }
    return(0);
}

/************************************************************************/
/*									*/
/************************************************************************/
int GetFromServer(int sock, char message[])
{
    int len;
    
    len = recv(sock, message, MK4D_MAXMSG, 0);
    
    if (len == -1)
    {  
        ml_printf(ML_MANDATORY, ML_ERROR, 
            "recv failed in GetFromerver: %s\n",
            strerror(errno));
    }   
    return len;
} 

/************************************************************************/
/*									*/
/************************************************************************/
int IsLast(char buffer[])
{
    if (strstr(buffer, " OK>\n"))      return 1;
    if (strstr(buffer, " FAIL>\n"))    return 1;
    if (strstr(buffer, " INVALID>\n")) return 1;
    return 0;
}


/************************************************************************/
/*									*/
/************************************************************************/
char *rl_gets(char prompt[])
{
    char *line          = (char *)NULL;
    char *expanded      = (char *)NULL;

    if (line)
    {
        free(line);
        line = (char *)NULL;
    }
    if (expanded)
    {
        free(expanded);
        expanded = (char *)NULL;
    }

    line = readline(prompt);

    if (line && *line)
    {
        history_expand(line, &expanded);
        add_history(expanded);
    }

    return (expanded);
}


/************************************************************************/
/*									*/
/*  RETURNS:    0 if line is null or contains only white space          */
/*              1 if line contains "non-white" characters               */
/*                                                                      */
/************************************************************************/
int NonNullString(char line[])
{
    int len;
    
    
    /* check for NULL pointer */
    if(!line) return 0;
    
    /* check for zero lenght string */
    len = strlen(line);
    if (0 == len) return 0;
    
    /* check for all white characters */
    if (len == strspn(line, " /t")) return 0;
    
    /* if none of the above apply then we have a non-null string */
    return 1;
}

/************************************************************************/
/*									*/
/************************************************************************/
void cleanup(int sig)
{
    char    cause[32];
    int     rtn =0;
    
    if (sock != -1) close(sock);
    
    switch(sig)
    {
      case RTN_ERR:
          strncpy(cause, "(ERROR)", sizeof(cause));
          break;
          
      case RTN_OK:
          strncpy(cause, "(Normal shutdown)", sizeof(cause));
          break;

      case SIGHUP:
          strncpy(cause, "(SIGHUP)", sizeof(cause));
          break;

      case SIGINT:
          strncpy(cause, "(SIGINT)", sizeof(cause));
          break;

      case SIGQUIT:
          strncpy(cause, "(SIGQUIT)", sizeof(cause));
          break;

      case SIGTERM:
          strncpy(cause, "(SIGTERM)", sizeof(cause));
          break;
          
      default:
          strncpy(cause, "(ERROR)", sizeof(cause));
          rtn = 1;
    }
    
    
    /* Sign off message */
    ml_printf(ML_VERBOSE, ML_INFORMATION, 
             "%s terminated %s\n", PROGNAME, cause);
    exit(rtn);
}

