
/* cli.c */
/* Andrew Davison (ad@ratree.psu.ac.th) */
/* 20th January 1997 */

/* Usage:
     cli (dotted-decimal-address | actual-name) [port]
   e.g.
     cli catsix
     cli 203.154.146.136
     cli catsix.coe.psu.ac.th 2002
*/
/* A simple client application for communicating with a
   *chat* server at a specified address and port.

   It assumes that it is a chat server only for termination
   purposes, when a ".quit" message is sent.

   As background information, many of the network related functions
   contain page references to 'Stevens', which means the text:
      "UNIX Network Programming", W. Richard Stevens,
      Prentice Hall, 1990.
   Naturally, any coding mistakes are mine.
*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>        /* inet_aton() */
#include <bstring.h>          /* bcopy() */
#include <unistd.h>           /* read(), write(), close() */
#include <time.h>

#define MAXLEN 120         /* max length of a string */
#define PORT 2001          /* default port number */


struct sockaddr_in get_addr(int argc, char *argv[]);
int open_socket(struct sockaddr_in naddr);
void wait_contact(int sfd, fd_set *rmp);
void process_user(int sfd);
void quit_cmd(int sfd);
void process_server(int sfd);
int read_line(int sfd, char str[]);
void catch_interrupt(int signo);


int ssockfd;               /* global server socket descriptor;
                              used by catch_interrupt() */


void main(int argc, char *argv[])
/* Create a socket link to the server and then repeatedly wait
   for user or server input. Ctrl-c's are dealt with by
   catch_interrupt()
*/
{
  struct sockaddr_in netaddr;
  fd_set readmask;

  signal(SIGINT, catch_interrupt);      /* catch ctrl-c */

  netaddr = get_addr(argc, argv);
  ssockfd = open_socket(netaddr);

  while(1) {
    wait_contact(ssockfd, &readmask);

    if (FD_ISSET(ssockfd, &readmask))     /* data to read from server */
      process_server(ssockfd);
    if (FD_ISSET(0, &readmask))           /* data to read from user */
      process_user(ssockfd);
  }
  /* execution never reaches here */
}


struct sockaddr_in get_addr(int argc, char *argv[])
/* Create address structure using the given internet address
   (either in dotted decimal form or an actual name) and 
   an optional port number.
   Similar to an example in Stevens p.397-399.
   inet_aton() may have to be replace by inet_addr() 
   in some UNIX's 
*/
{
  struct sockaddr_in addr;        /* described on p.264 */
  struct in_addr iaddr;           /* p.264 */
  struct hostent *hp;             /* p.393 */
  int port;

  if ((argc < 2) || (argc > 3)) {
    fprintf(stderr, "Usage: cli address [port]\n");
    exit(1);
  }

  if (argc == 2)
    port = PORT;      /* default value */
  else {      
    port = atoi(argv[2]);
    if (port < 0) {
      fprintf(stderr, "The port number cannot be < 0\n");
      exit(1);
    }
  }

  bzero(&addr, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);          /* htons(): see Stevens p.276 */

  if (inet_aton(argv[1], &iaddr) != -1)       /* dotted-decimal */
    bcopy(&iaddr, &addr.sin_addr, sizeof(iaddr));
  else if ((hp = gethostbyname(argv[1])) != NULL)  /* lookup name */
    bcopy(hp->h_addr, &addr.sin_addr.s_addr, hp->h_length);
  else {
    fprintf(stderr, "Unknown address\n");
    exit(1);
  }
  return addr;
}


int open_socket(struct sockaddr_in netaddr)
/* Create a socket data structure, initialise it with the
   server address details, and then try to connect to it. */
/* See Stevens p.286 */
{
  int sd;

  if ((sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
    fprintf(stderr, "socket() error\n");
    exit(1);
  }
  if (connect(sd, (struct sockaddr *)&netaddr, sizeof(netaddr)) < 0) {
    fprintf(stderr, "Can not connect\n");
    exit(1);
  }
  return sd;
}

void wait_contact(int sfd, fd_set *rmp)
/* Wait for input either from the user or the server */
{
  int maxfd, numfound;

  maxfd = sfd + 1;

  /* build read-mask for select(), see Stevens p.595 */
  FD_ZERO(rmp);
  FD_SET(0, rmp);        /* to monitor user input */
  FD_SET(sfd, rmp);      /* to monitor server */

  numfound = select(maxfd, rmp, (fd_set *)0, (fd_set *)0,
                                        (struct timeval *) 0);
  if (numfound < 0) {
    fprintf(stderr, "select() error\n");
    exit(1);
  }
}


void process_user(int sfd)
/* Read input from the user and pass it unchanged to the
   server except when a ".quit" command is typed. In that
   case, call quit_cmd().
*/
{
  int len;
  char str[MAXLEN];

  len = read_line(0, str);   /* 0 is input file descriptor */
 
  if (len < 0) {
    fprintf(stderr, "Fatal user error\n");
    quit_cmd(sfd);
  }
  else if (len == 0)
    fprintf(stderr, "Empty user input being ignored\n");
  else {
    if (str[0] == '.') {     /* input is a command */
      if ((str[1] == 'q') || (str[1] == 'Q'))    /* a ".quit" command */
        quit_cmd(sfd);
      else
        write(sfd, str, len);    /* pass other cmds straight to server */
    }
    else                       /* input is not a command */
      write(sfd, str, len);    /* pass straight to server */
  }
}


void quit_cmd(int sfd)
/* Send a ".quit" message to the server and then terminate.
   Do not wait for any server response (e.g. "goodbye").
   cli.c uses quit_cmd() to terminate.
*/
{
  write(sfd, ".quit\n", 6);
  close(sfd);
  printf("Sent \".quit\" to server. Now terminating...\n");
  exit(0);
}


void process_server(int sfd)
/* Read the server input and print it out unaltered. */
{
  char str[MAXLEN];

  if (read_line(sfd, str) <= 0) {
    fprintf(stderr, "Server connection has been lost. Terminating...\n");
    exit(1);
  }

  fputs(str, stdout); 
}


int read_line(int sfd, char str[])
/* Read up to and including the newline in the socket buffer
   by reading 1 character at a time. Return the line length.
*/
{
  int i = 0, len;

  while ((i < MAXLEN-1) && ((len = read(sfd, &str[i], 1)) == 1) &&
         (str[i] != '\n'))
    i++;

  if (len <= 0)
    return len;

  if (i == MAXLEN-1)
    fprintf(stderr, "Input string too long\n");
  else
    i++;      /* move past newline */
  str[i] = '\0';                                     
  return i;
}



void catch_interrupt(int signo)
/* This function is called when a ctrl-c is typed.
   Further ctrl-c's are ignored and the client 'gracefully'
   terminates by calling quit_cmd().
   This function may have to return int in some UNIX's.
   Signals are described in Stevens p.43-51 
*/
{
  signal(SIGINT, SIG_IGN);       /* ignore further ctrl-c's */
  quit_cmd(ssockfd);             /* uses global ssockfd */
}



