"A Graphical Internet Chat"
by  Andrew Davison
Web Techniques,  January 1998

Web Techniques grants permission to use these listings and code for private or
commercial use provided that credit to Web Techniques and the author is
maintained within the comments of the source. For questions, contact
editors@web-techniques.com.       

This file consists of three listings that accompany the article "A Graphical Internet Chat", published in the 
January 1998 issue of Web Techniques:

Listing 1  cli.c
Listing 2  cli2.c
Listing 3 cli3.c

Also included are chat.c and chat2.c - two versions of the complete listings of the code described in the 
article.

      
     
--------------------------------------------------------------
     
LISTING 1.  cli.c
=================
     
/* 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 */
}
     
/*=================================================================*/
    
LISTING 2.   cli2.c
= = = = = = = = =  =
      
/* cli2.c */
/* Andrew Davison (ad@ratree.psu.ac.th) */ 
/* 20th January 1997 */
     
/* Usage:                                        
     cli2 (dotted-decimal-address | actual-name) [port]
   e.g.
     cli2 catsix  
     cli2 203.154.146.136
     cli2 catsix.coe.psu.ac.th 2002
*/
/* A simple client application for communicating with a
   *chat* server at a specified address and port.
     
   Information about the room currently occupied by the user is 
   printed out as circumstances change (e.g. people enter/leave, 
   talk, change their mood and/or body form). These details
   is extracted from the messages sent to the client by the 
   Chat server.
     
   Room messages are annotated output from the server. 
   They have the form:
      <letter>$ text...
   The letters, and the meaning of the associated text:
      w     text contains information on the people in the room
      e     text is about another person entering the user's current 
room 
      l     text is about another person leaving the user's current 
room 
      m     text is about a person's new mood
      f     text is about a person's new body form
      u     text is about user's name -- appears only at start-up 
      t     text is about another person speaking
*/
/* Extensions to cli.c:
    * Visitor and Room data structures
    * moods and forms
    * monitoring of room messages to update the Room d.s
*/
     
#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 */
     
#define ROOM_SIZE 5        /* maximum number of people allowed in a 
room */ 
#define MAXMOODS 3         /* max number of moods */
#define MAXFORMS 7         /* max number of body forms*/
     
enum Mood {OK, HAPPY, SAD};
enum Form {NEWBIE, DANDY, HACKER, MANAGER, BOBBIE, WOOF, GRAD};
     
typedef struct {
  char name[MAXLEN];  /* visitor's name */ 
  enum Mood mood;
  enum Form form;
} Visitor;           /* information about a room visitor */
     
typedef struct {
  char rname[MAXLEN];       /* room name */
  int curr_speaker;         /* position of speaker in vs[] */ 
  int uposn;                /* position of user in vs[] */ 
  Visitor vs[ROOM_SIZE];    /* room visitors */
} Room;              /* room information */
     
     
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, Room *r); 
void quit_cmd(int sfd);
void process_server(int sfd, Room *r); 
int read_line(int sfd, char str[]);
void catch_interrupt(int signo);
     
/* Room Operations */
void init_room(Room *r);
void set_speaker(int spot, Room *r); 
int room_message(char str[]);
void update_room(char str[], Room *r); 
void who_details(char str[], Room *r);
void get_word_before(char c, char str[], int *posn, char word[]); 
void get_visitor(char str[], int *posn, Room *r, int numv);
void print_status(Room *r);
void enter_details(char str[], Room *r); 
int find_empty(Room *r);
void leave_details(char str[], Room *r); 
int find_spot(char name[], Room *r); 
void mood_details(char str[], Room *r); 
enum Mood map_mood(char str[]);
void form_details(char str[], Room *r); 
enum Form map_form(char str[]);
void user_details(char str[], Room *r); 
void talk_details(char str[], Room *r);
     
/* Global Data */
int ssockfd;               /* global server socket descriptor;
                              used by catch_interrupt() */
/* Must correspond to the enumeration types above */ 
char *moods[MAXMOODS] = {"ok", "happy", "sad"};
char *forms[MAXFORMS] = {"newbie", "dandy", "hacker", "manager", 
"bobbie",
                         "woof", "grad"}; 
     
     
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()
*/
{
  Room r;
  struct sockaddr_in netaddr;
  fd_set readmask;
     
  signal(SIGINT, catch_interrupt);      /* catch ctrl-c */
     
  netaddr = get_addr(argc, argv);
  ssockfd = open_socket(netaddr);
     
  init_room(&r);
  while(1) {
    wait_contact(ssockfd, &readmask);
     
    if (FD_ISSET(ssockfd, &readmask))     /* data to read from server 
*/
      process_server(ssockfd, &r);
    if (FD_ISSET(0, &readmask))           /* data to read from user */
      process_user(ssockfd, &r);
  }
  /* 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;           /* described on p.264 */ 
  struct hostent *hp;             /* p.393 */
  int port;
     
  if ((argc < 2) || (argc > 3)) {
    fprintf(stderr, "Usage: cli2 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 or 
alias */
    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, Room *r)
/* 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 */ 
      set_speaker(r->uposn, r);   /* NEW: the user is speaking */
    }
  }
}
     
     
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, Room *r)
/* Read the server input and print it out unaltered. 
   Process any room messages in the input.
*/
{
  char str[MAXLEN];
     
  if (read_line(sfd, str) <= 0) {
    fprintf(stderr, "Server connection has been lost. 
Terminating...\n"); 
    exit(1);
  }
     
  if (room_message(str))    /* NEW: is str a room message? */
    update_room(str, r); 
  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 */
}
     
     
/* Room operations */
     
void init_room(Room *r)
/* Initialise the room to be empty */ 
{
  int i;
     
  r->rname[0] = '\0';
  r->curr_speaker = -1;
  r->uposn = -1;
  for (i=0; i < ROOM_SIZE; i++) {
    r->vs[i].name[0] = '\0';
    r->vs[i].mood = (enum Mood)0;
    r->vs[i].form = (enum Form)0;
  }
}
     
     
void set_speaker(int spot, Room *r)
/* The if-test is used to stop the room details being updated
   when the user first communicates with the server to
   input his/her name. At that point. the user is not in Chat, 
   and so not in a room.
*/
{
  if (r->rname[0] != '\0') {    /* if in a room */
    r->curr_speaker = spot;
    printf("New speaker in %s: %s\n", r->rname, r->vs[spot].name);
  }
}
     
     
int room_message(char str[])
/* A room message starts a single line with:
     <letter>$ text...
*/
{
  if (str[0] == '\n')    /* the server message is only a newline */
    return 0;
     
  if ((str[1] == '$') && (str[2] == ' '))
    return 1;
  else
    return 0;
}
     
     
void update_room(char str[], Room *r) 
/* str[] has the form:
     <letter>$ text...
   The text is processed by a different "_details" function 
   depending on the letter value.
*/
{
  if (str[0] == 'w')
    who_details(str+3, r);
  else if (str[0] == 'e')
    enter_details(str+3, r);
  else if (str[0] == 'l')
    leave_details(str+3, r);
  else if (str[0] == 'm')
    mood_details(str+3, r);
  else if (str[0] == 'f')
    form_details(str+3, r);
  else if (str[0] == 'u')
    user_details(str+3, r);
  else if (str[0] == 't')
    talk_details(str+3, r);
  else
    fprintf(stderr, "Room message code \'%c\' not understood\n", 
str[0]);
}
     
     
void who_details(char str[], Room *r) 
/* 
   When the user first enters a room (or when a ".who" command is 
   issued), the server sends the client a 'w' message which is 
   processed here. The room details in r are reinitialised. 
   However the visitors list in str[] may not match the one stored 
   in vs[]. Thus, the user's name is extracted from vs[] before r 
   is updated so that he can be identified afterwards. Also, the 
   current speaker is set to -1 (no one is talking).
   Format of str[] is:
      text... <room>:  <visitor-name> (<mood-digit>.<form-digit>)  
...\n
*/
{
  char uname[MAXLEN];
  int posn = 0, numv = 0, uposn = -1;
     
  strcpy(uname, r->vs[r->uposn].name);   /* remember user's name */ 
  init_room(r);
     
  get_word_before(':', str, &posn, r->rname);
  posn = posn + 3;        /* skip over ':' and 2 spaces */
     
  while ((numv < ROOM_SIZE) && (str[posn] != '\n')) {
    get_visitor(str, &posn, r, numv);
    if (strcmp(r->vs[numv].name, uname) == 0)
      uposn = numv;          /* remember user's new room position */
    numv++;
  }
     
  r->uposn = uposn;
  if ((numv == ROOM_SIZE) && (str[posn] != '\n'))
    fprintf(stderr, "Too many visitors in the room, ignoring some\n");
     
  print_status(r);
}
     
     
void get_word_before(char c, char str[], int *posn, char word[]) 
/* Get the word before character c in str[]. Start searching from
   str[*posn] */
{
  int i, j, left;
     
  while (str[*posn] != c)  /* find c */
    (*posn)++;
     
  left = *posn - 1;        /* one char before c */ 
  while ((left >= 0) && (str[left] != ' '))
    left--;
     
  i = left + 1;            /* one char after space */ 
  j = 0;
  while ((i < *posn) && (j < MAXLEN-1))
    word[j++] = str[i++];
  word[j] = '\0';
}
     
     
void get_visitor(char str[], int *posn, Room *r, int numv) 
/* Format of this part of str[] is:
      <visitor-name> (<mood-digit>.<form-digit>)
   followed by two spaces
*/
{
  get_word_before(' ', str, posn, r->vs[numv].name); 
  *posn = *posn + 2;           /* skip space and '(' */ 
  r->vs[numv].mood = (enum Mood)(str[*posn] - '0');
  *posn = *posn + 2;           /* skip mood-digit and '.' */ 
  r->vs[numv].form = (enum Form)(str[*posn] - '0');
  *posn = *posn + 4;           /* ship form-digit, ')' and 2 spaces */
}
     
     
void print_status(Room *r)
/* Print the contents of r. Useful for debugging. */ 
{
  int i;
     
  printf("----Room Status----\n");
  if (r->rname[0] == '\0')
    printf("No current room\n");
  else
    printf("Room: %s\n", r->rname);
     
  if (r->curr_speaker == -1)
    printf("No current speaker\n");
  else
    printf("Current Speaker: %s\n", r->vs[r->curr_speaker].name);
     
  printf("User position: %d\n", r->uposn);
     
  printf("Visitors:  ");
  for (i=0; i < ROOM_SIZE; i++)
    if (r->vs[i].name[0] != '\0')
      printf("%s (%d.%d)  ", r->vs[i].name, r->vs[i].mood, r-
>vs[i].form);
  printf("\n--------\n");
}
     
     
void enter_details(char str[], Room *r)
/* Record info about new person entering the user's room.
   Format of str[] is:
     <visitor-name> (<mood-digit>.<form-digit>) text...
*/
{
  int posn = 0, spot;
     
  if ((spot = find_empty(r)) == -1)
    fprintf(stderr, "Room too full; ignoring new person\n");
  else {
    printf("Adding new person to room details\n"); 
    get_visitor(str, &posn, r, spot);
    print_status(r);
  }
}
     
     
int find_empty(Room *r)
/* Find an empty spot in r->vs[], which is identified by
   the name field containing only '\0'. Return the index 
   position. If there is no free space then return -1.
*/
{
  int i = 0;
     
  while (i < ROOM_SIZE) {
    if (r->vs[i].name[0] == '\0')
      return i;
    i++;
  }
  return -1;
}
     
     
void leave_details(char str[], Room *r)
/* Record departure of a person from the user's room.
   Format of str[]:
     <name> text...
*/
{
  int posn = 0, spot;
  char name[MAXLEN];
     
  get_word_before(' ', str, &posn, name);
     
  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else {
    printf("Removing person from room details\n"); 
    r->vs[spot].name[0] = '\0';
    print_status(r);
  }
}
     
     
int find_spot(char name[], Room *r)
/* Return the index position in r->vs[] which contains a name;
   return -1 if the name cannot be found. */
{
  int i = 0;
     
  while (i < ROOM_SIZE) {
    if (strcmp(r->vs[i].name, name) == 0)
      return i;
    i++;
  }
  return -1;
}
     
     
     
void mood_details(char str[], Room *r)
/* Record the new mood of a person in the room (which may be
   the user). Format of str[]:
     <name>'s mood: <mood-word>\n
*/
{
  int posn = 0, spot;
  char name[MAXLEN], moodstr[MAXLEN];
     
  get_word_before('\'', str, &posn, name);
     
  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else {
    printf("Updating %s's mood information\n", name); 
    get_word_before('\n', str, &posn, moodstr); 
    r->vs[spot].mood = map_mood(moodstr); 
    print_status(r);
  }
}
     
     
enum Mood map_mood(char str[])
/* str[] contains a mood word, which must be converted to
   an enum Mood value. */
{
  int i;
  for (i=0; i < MAXMOODS; i++)
     if (str[0] == moods[i][0])
       return (enum Mood) i;
     
  fprintf(stderr, "%s is not a recognised mood; setting to \"%s\"\n", 
                                    str, moods[0]);
  return (enum Mood) 0;        /* first mood is the default */
}
     
     
void form_details(char str[], Room *r)
/* Record the new body form of a person in the room (which may be
   the user). Format of str[]:
     <name>'s form: <form-word>\n
*/
{
  int posn = 0, spot;
  char name[MAXLEN], formstr[MAXLEN];
     
  get_word_before('\'', str, &posn, name);
     
  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else {
    printf("Updating %s's form information\n", name); 
    get_word_before('\n', str, &posn, formstr); 
    r->vs[spot].form = map_form(formstr); 
    print_status(r);
  }
}
     
     
enum Form map_form(char str[])
/* str[] contains a form word, which must be converted to
   an enum Form value. */
{
  int i;
  for (i=0; i < MAXFORMS; i++)
     if (str[0] == forms[i][0])
       return (enum Form) i;
     
  fprintf(stderr, "%s is not a recognised form; setting to \"%s\"\n", 
                                    str, forms[0]);
  return (enum Form) 0;        /* first body form is the default */
}
     
     
void user_details(char str[], Room *r)
/* This function is always called at the beginning of the user's
   session with Chat, and so it can assume that vs[0] is empty. 
   Format of str[]:
     <name>, text...
*/
{
  int posn = 0;
     
  get_word_before(',', str, &posn, r->vs[0].name); 
  printf("User's name: %s\n", r->vs[0].name); 
  r->uposn = 0;
}
     
     
void talk_details(char str[], Room *r)
/* Someone (other than the user) has spoken.
   Format of str[]:
     <name>: text...
*/
{
  int posn = 0, spot;
  char name[MAXLEN];
     
  get_word_before(':', str, &posn, name);
     
  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else
    set_speaker(spot, r);
}
     
     

/*=================================================================*/


LISTING 3. cli3.c
= = = = == =  = = 


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

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

   Information about the room currently occupied by the user is
   represented as a changing image. This changes when:
     * the user changes room
     * someone enters/leaves the user's room
     * somebody changes their body form or mood
     * someone speaks

   The information is extracted from visualisation messages sent
   to the client by the server. The messages have the form:
      <letter>$ text...
   The letters, and the meaning of the associated text:
      w     text contains information on the people in the room
      e     text is about another person entering the user's current 
room
      l     text is about another person leaving the user's current 
room
      m     text is about a person's new mood
      f     text is about a person's new body form
      u     text is about user's name -- appears only at start-up
      t     text is about another person speaking

   The image is created using the gd graphics library and
   displayed using the UNIX xv graphics library.
*/
/* Makes use of the gd graphics library, by
   Thomas Boutell and the Quest Protein Database Center
   at Cold Spring Harbor Labs.
   COPYRIGHT 1994,1995 BY THE QUEST PROTEIN DATABASE CENTER
   AT COLD SPRING HARBOR LABS.
*/
/* Extensions to cli2.c:
    * graphics operations
    * commented out print_status() calls, and other debugging printf()s
    * compilation: 
        \gcc -Wall cli3.c -o cli3 -L/home/ad/gd1.2 -lgd -lm
*/

#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>
#include "/home/ad/gd1.2/gd.h"
#include "/home/ad/gd1.2/gdfontl.h"   /* Large font */


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

#define ROOM_SIZE 5        /* maximum number of people allowed in a 
room */
#define MAXMOODS 3         /* max number of moods */
#define MAXFORMS 7         /* max number of body forms*/

#define MAX_ROOMS 7         /* max number of rooms */
#define PICDIR "/home/ad/chat/client/pics/"   /* pics directory */
#define OPIC "room.gif"                        /* output picture */
#define BUBBLE "bubble.gif"                    /* speech bubble picture 
*/
#define WIDTH 160          /* width of figure space in picture */
#define Y_BUBBLE 160       /* height of bubble in picture */
#define Y_BODY 300         /* height of figure in picture */
#define Y_NAME 40          /* height of name space in picture */


enum Mood {OK, HAPPY, SAD};
enum Form {NEWBIE, DANDY, HACKER, MANAGER, BOBBIE, WOOF, GRAD};

typedef struct {
  char name[MAXLEN];  /* visitor's name */
  enum Mood mood;
  enum Form form;
} Visitor;           /* information about a room visitor */

typedef struct {
  char rname[MAXLEN];       /* room name */
  int curr_speaker;         /* position of speaker in vs[] */
  int uposn;                /* position of user in vs[] */ 
  Visitor vs[ROOM_SIZE];    /* room visitors */
} Room;              /* room information */


void start_client(int argc, char *argv[]);
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, Room *r);
void quit_cmd(int sfd);
void process_server(int sfd, Room *r);
int read_line(int sfd, char str[]);
void catch_interrupt(int signo);

/* Room Operations */
void init_room(Room *r);
void set_speaker(int spot, Room *r);
int room_message(char str[]);
void update_room(char str[], Room *r);
void who_details(char str[], Room *r);
void get_word_before(char c, char str[], int *posn, char word[]);
void get_visitor(char str[], int *posn, Room *r, int numv);
void print_status(Room *r);
void enter_details(char str[], Room *r);
int find_empty(Room *r);
void leave_details(char str[], Room *r);
int find_spot(char name[], Room *r);
void mood_details(char str[], Room *r);
enum Mood map_mood(char str[]);
void form_details(char str[], Room *r);
enum Form map_form(char str[]);
void user_details(char str[], Room *r);
void talk_details(char str[], Room *r);

/* Graphic Operations */
void speak(int new_posn, Room *r);
gdImagePtr load_image(char nm[]);
int wipe_area(gdImagePtr im, int posn, int ystart, int ylength, Room 
*r);
void get_room_pic(char rnm[], char gifnm[]);
gdImagePtr load_room(char nm[]);
int draw_bubble(gdImagePtr im, int posn);
void store_image(gdImagePtr im);
void draw_scene(Room *r);
void draw_figure(int posn, Room *r);
void draw_name(gdImagePtr im, int posn, Room *r);
int draw_body(gdImagePtr im, int posn, Room *r);
void get_body_pic(enum Form f, enum Mood m, char gifnm[]);
void erase_figure(int posn, Room *r);
void change_body(int posn, Room *r);
void init_pic(void);


/* Global Data */
int ssockfd;               /* global server socket descriptor;
                              used by catch_interrupt() */
/* Must correspond to the enumeration types above */
char *moods[MAXMOODS] = {"ok", "happy", "sad"};
char *forms[MAXFORMS] = {"newbie", "dandy", "hacker", "manager", 
"bobbie", 
                         "woof", "grad"}; 
char *rooms[MAX_ROOMS] = {"foyer", "garden", "road", "beach", 
"mountain",
                          "tvroom", "bedroom"};


void main(int argc, char *argv[])
/* Fork a child process to do the main client actions,
   and invoke xv in the parent. This allows the child to
   later terminate the parent via its process ID in quit_cmd().
   xv is put into polling mode (-poll) to reload the OPIC image
   whenever the file changes.
*/
{
  int pid;
  char pic_fnm[MAXLEN];

  init_pic();           /* clear PIC */
  pid = fork();
  if (pid == 0)         /* child process */
    start_client(argc, argv);
  else if (pid > 0) {   /* parent process */
    sprintf(pic_fnm, "%s%s", PICDIR, OPIC);
    printf("Parent: starting xv with %s ...\n", pic_fnm);
    if (execl("/usr/bin/X11/xv", "xv", "-viewonly", "-poll",
                            pic_fnm, (char *)0) == -1) {
      fprintf(stderr, "Parent: Cannot start xv\n");
      exit(1);
    }
  }
}


void start_client(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(). Uses the main() code in cli2.c.
*/
{
  Room r;
  struct sockaddr_in netaddr;
  fd_set readmask;

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

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

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

    if (FD_ISSET(ssockfd, &readmask))     /* data to read from server 
*/
      process_server(ssockfd, &r);
    if (FD_ISSET(0, &readmask))           /* data to read from user */
      process_user(ssockfd, &r);
  }
  /* 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;           /* described on p.264 */
  struct hostent *hp;             /* p.393 */
  int port;

  if ((argc < 2) || (argc > 3)) {
    fprintf(stderr, "Usage: cli3 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 or 
alias */
    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, Room *r)
/* 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 */
      set_speaker(r->uposn, r);     /* the user is speaking */
    }
  }
}


void quit_cmd(int sfd)
/* Terminate the parent process and xv, 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.
*/
{
  int ppid;

  printf("Terminating xv...\n");
  ppid = getppid();       /* get parent's process ID */
  kill(ppid, SIGINT);

  write(sfd, ".quit\n", 6);
  close(sfd);
  printf("Sent \".quit\" to server. Now terminating...\n");
  exit(0);
}


void process_server(int sfd, Room *r)
/* Read the server input and print it out unaltered. 
   Process any room messages in the input.
*/
{
  char str[MAXLEN];

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

  if (room_message(str))
    update_room(str, r);                                    
  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 */
}


/* Room operations */

void init_room(Room *r)
/* Initialise the room to be empty */
{
  int i;

  r->rname[0] = '\0';
  r->curr_speaker = -1;
  r->uposn = -1;
  for (i=0; i < ROOM_SIZE; i++) {
    r->vs[i].name[0] = '\0';
    r->vs[i].mood = (enum Mood)0;
    r->vs[i].form = (enum Form)0;
  }
}


void set_speaker(int spot, Room *r)
/* The if-test is used to stop the room details being updated
   when the user first communicates with the server to
   input his/her name. At that point. the user is not in Chat,
   and so not in a room.
*/
{
  if (r->rname[0] != '\0') {    /* if in a room */
    speak(spot, r);
    r->curr_speaker = spot;
    /* printf("New speaker in %s: %s\n", r->rname, r->vs[spot].name); 
*/
  }
}


int room_message(char str[])
/* A room message starts a single line with:
     <letter>$ text...
*/
{
  if (str[0] == '\n')    /* message is only a newline */
    return 0;

  if ((str[1] == '$') && (str[2] == ' '))
    return 1;
  else
    return 0;
}


void update_room(char str[], Room *r)
/* str[] has the form:                                       
     <letter>$ text...                                
   The text is processed by a different "_details" function   
   depending on the letter value.
*/
{
  if (str[0] == 'w')
    who_details(str+3, r);
  else if (str[0] == 'e')
    enter_details(str+3, r);
  else if (str[0] == 'l')
    leave_details(str+3, r);
  else if (str[0] == 'm')
    mood_details(str+3, r);
  else if (str[0] == 'f')
    form_details(str+3, r);
  else if (str[0] == 'u')
    user_details(str+3, r);
  else if (str[0] == 't')
    talk_details(str+3, r);
  else
    fprintf(stderr, "Room message code \'%c\' not understood\n", 
str[0]);
}


void who_details(char str[], Room *r)
/*
   When the user first enters a room (or when a ".who" command is
   issued), the server sends the client a 'w' message which is
   processed here. The room details in r are reinitialised.
   However the visitors list in str[] may not match the one stored
   in vs[]. Thus, the user's name is extracted from vs[] before r
   is updated so that he can be identified afterwards. Also, the
   current speaker is set to -1 (no one is talking).
   Format of str[] is:
      text... <room>:  <visitor-name> (<mood-digit>.<form-digit>)  
...\n
*/
{
  char uname[MAXLEN];
  int posn = 0, numv = 0, uposn = -1;

  strcpy(uname, r->vs[r->uposn].name);   /* remember user's name */
  init_room(r);

  get_word_before(':', str, &posn, r->rname);
  posn = posn + 3;        /* skip over ':' and 2 spaces */
  
  while ((numv < ROOM_SIZE) && (str[posn] != '\n')) {
    get_visitor(str, &posn, r, numv);
    if (strcmp(r->vs[numv].name, uname) == 0)
      uposn = numv;          /* remember user's new room position */
    numv++;
  }

  r->uposn = uposn;
  if ((numv == ROOM_SIZE) && (str[posn] != '\n'))
    fprintf(stderr, "Too many visitors in the room, ignoring some\n");

  /* print_status(r); */
  draw_scene(r);
}


void get_word_before(char c, char str[], int *posn, char word[])
/* Get the word before character c in str[]. Start searching from
   str[*posn] */                                                        
{
  int i, j, left;

  while (str[*posn] != c)  /* find c */
    (*posn)++;
  
  left = *posn - 1;        /* one char before c */
  while ((left >= 0) && (str[left] != ' '))
    left--;

  i = left + 1;            /* one char after space */
  j = 0;
  while ((i < *posn) && (j < MAXLEN-1))
    word[j++] = str[i++];
  word[j] = '\0';
}


void get_visitor(char str[], int *posn, Room *r, int numv)
/* Format of this part of str[] is:
      <visitor-name> (<mood-digit>.<form-digit>)
   followed by two spaces
*/
{
  get_word_before(' ', str, posn, r->vs[numv].name);
  *posn = *posn + 2;           /* skip space and '(' */
  r->vs[numv].mood = (enum Mood)(str[*posn] - '0');
  *posn = *posn + 2;           /* skip mood-digit and '.' */
  r->vs[numv].form = (enum Form)(str[*posn] - '0');
  *posn = *posn + 4;           /* ship form-digit, ')' and 2 spaces */
}


void print_status(Room *r)
{
  int i;

  printf("----Room Status----\n");
  if (r->rname[0] == '\0')
    printf("No current room\n");
  else
    printf("Room: %s\n", r->rname);

  if (r->curr_speaker == -1)
    printf("No current speaker\n");
  else
    printf("Current Speaker: %s\n", r->vs[r->curr_speaker].name);

  printf("User position: %d\n", r->uposn);

  printf("Visitors:  ");
  for (i=0; i < ROOM_SIZE; i++)
    if (r->vs[i].name[0] != '\0')
      printf("%s (%d.%d)  ", r->vs[i].name, r->vs[i].mood, r-
>vs[i].form);
  printf("\n--------\n");
}


void enter_details(char str[], Room *r)
/* Record info about new person entering the user's room.
   Format of str[] is:
     <visitor-name> (<mood-digit>.<form-digit>) text...
*/
{
  int posn = 0, spot;

  if ((spot = find_empty(r)) == -1)
    fprintf(stderr, "Room too full; ignoring new person\n");
  else {
    /* printf("Adding new person to room details\n"); */
    get_visitor(str, &posn, r, spot);
    /* print_status(r); */
    draw_figure(spot, r);
  }
}


int find_empty(Room *r)
/* Find an empty spot in r->vs[], which is identified by              
   the name field containing only '\0'. Return the index
   position. If there is no free space then return -1.
*/
{
  int i = 0;

  while (i < ROOM_SIZE) {
    if (r->vs[i].name[0] == '\0')
      return i;
    i++;
  }
  return -1;
}


void leave_details(char str[], Room *r)
/* Record departure of a person from the user's room.
   Format of str[]:
     <name> text...
*/
{
  int posn = 0, spot;
  char name[MAXLEN];

  get_word_before(' ', str, &posn, name);

  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else {
    /* printf("Removing person from room details\n"); */
    erase_figure(spot, r);
    r->vs[spot].name[0] = '\0';
    /* print_status(r); */
  }
}


int find_spot(char name[], Room *r)
/* Return the index position in r->vs[] which contains a name;
   return -1 if the name cannot be found. */
{
  int i = 0;

  while (i < ROOM_SIZE) {
    if (strcmp(r->vs[i].name, name) == 0)
      return i;
    i++;
  }
  return -1;
}



void mood_details(char str[], Room *r)
/* Record the new mood of a person in the room (which may be
   the user). Format of str[]:
     <name>'s mood: <mood-word>\n
*/
{
  int posn = 0, spot;
  char name[MAXLEN], moodstr[MAXLEN];

  get_word_before('\'', str, &posn, name);

  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else {
    /* printf("Updating %s's mood information\n", name); */
    get_word_before('\n', str, &posn, moodstr);
    r->vs[spot].mood = map_mood(moodstr);
    /* print_status(r); */
    change_body(spot, r);
  }
}


enum Mood map_mood(char str[])
/* str[] contains a mood word, which must be converted to
   an enum Mood value. */
{
  int i;
  for (i=0; i < MAXMOODS; i++)
     if (str[0] == moods[i][0])
       return (enum Mood) i;

  fprintf(stderr, "%s is not a recognised mood; setting to \"%s\"\n", 
                                    str, moods[0]);
  return (enum Mood) 0;        /* first mood is the default */
}


void form_details(char str[], Room *r)
/* Record the new body form of a person in the room (which may be
   the user). Format of str[]:
     <name>'s form: <form-word>\n
*/
{
  int posn = 0, spot;
  char name[MAXLEN], formstr[MAXLEN];

  get_word_before('\'', str, &posn, name);

  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else {
    /* printf("Updating %s's form information\n", name); */
    get_word_before('\n', str, &posn, formstr);
    r->vs[spot].form = map_form(formstr);
    /* print_status(r); */
    change_body(spot, r);
  }
}


enum Form map_form(char str[])
/* str[] contains a form word, which must be converted to
   an enum Form value. */
{
  int i;
  for (i=0; i < MAXFORMS; i++)
     if (str[0] == forms[i][0])
       return (enum Form) i;

  fprintf(stderr, "%s is not a recognised form; setting to \"%s\"\n", 
                                    str, forms[0]);
  return (enum Form) 0;        /* first body form is the default */
}


void user_details(char str[], Room *r)
/* This function is always called at the beginning of the user's
   session with Chat, and so it can assume that vs[0] is empty.
   Format of str[]:
     <name>, text...
*/
{
  int posn = 0;

  get_word_before(',', str, &posn, r->vs[0].name);
  printf("User's name: %s\n", r->vs[0].name);
  r->uposn = 0;
}


void talk_details(char str[], Room *r)
/* Someone (other than the user) has spoken.
   Format of str[]:
     <name>: text...
*/
{
  int posn = 0, spot;
  char name[MAXLEN];

  get_word_before(':', str, &posn, name);

  if ((spot = find_spot(name, r)) == -1)
    fprintf(stderr, "%s is not recorded in the room details\n", name);
  else
    set_speaker(spot, r);
}


/* Graphics Operations */

void speak(int new_posn, Room *r)
/* Remove old speech bubble; draw new one.
   Assume that the old speaker position is the one in r->curr_speaker.
*/
{
  gdImagePtr im;
  int old_posn = r->curr_speaker;

  if ((im = load_image(OPIC)) == NULL) {
    fprintf(stderr, "Cannot open output picture file: %s\n", OPIC);
    return;
  }
  if ((wipe_area(im, old_posn, 0, Y_BUBBLE, r)) &&   /* wipe old bubble 
*/
      (draw_bubble(im, new_posn)) )                  /* draw new one */
    store_image(im);
  gdImageDestroy(im);
}


gdImagePtr load_image(char nm[])
/* Extract the image from the file nm, located in PICDIR. */
{
  char fnm[MAXLEN];
  FILE *fp;
  gdImagePtr im;

  sprintf(fnm, "%s%s", PICDIR, nm);
  if ((fp = fopen(fnm, "rb")) == NULL)
    return NULL;
  else {
    im = gdImageCreateFromGif(fp);
    fclose(fp);
    return im;
  }
}


int wipe_area(gdImagePtr im, int posn, int ystart, int ylength, Room 
*r)
/* Erase the image at posn starting from ystart, down ylength pixels */
{
  gdImagePtr roomim;
  char roompic[MAXLEN];
  int xcoord = posn*WIDTH;

  get_room_pic(r->rname, roompic);
  if ((roomim = load_room(roompic)) == NULL) {
    fprintf(stderr, "Cannot open room picture file: %s\n", roompic);
    return 0;
  }
  gdImageCopy(im, roomim, xcoord, ystart, xcoord, ystart, WIDTH, 
ylength);
  gdImageDestroy(roomim);
  return 1;
}


void get_room_pic(char rname[], char gifnm[])
/* Get the GIF filename corresponing to the room name. This
   does *not* include the directory location.
*/
{
  int i;

  for (i=0; i < MAX_ROOMS; i++)
    if (strcmp(rname, rooms[i]) == 0)
      break;
  if (i < MAX_ROOMS)
    strcpy(gifnm, rooms[i]);
  else
    strcpy(gifnm, rooms[0]);     /* first room is default */
  strcat(gifnm, ".gif");
}


gdImagePtr load_room(char nm[])
/* Extract the room image from the file nm, located in PICDIR.
   This differs from load_image() in that the image is scaled to 
   the size expected by OPIC.
*/
{
  char fnm[MAXLEN];
  FILE *fp;
  gdImagePtr im, roomim;
  int xlen, ylen, white;

  sprintf(fnm, "%s%s", PICDIR, nm);
  if ((fp = fopen(fnm, "rb")) == NULL)
    return NULL;
  else {
    im = gdImageCreateFromGif(fp);
    fclose(fp);
    xlen = ROOM_SIZE*WIDTH;             /* width of OPIC */
    ylen = Y_BUBBLE + Y_BODY + Y_NAME;  /* height of OPIC */
    roomim = gdImageCreate(xlen, ylen);
    white = gdImageColorAllocate(roomim, 255, 255, 255);
    gdImageCopyResized(roomim, im, 0, 0, 0, 0, xlen, ylen, im->sx, im-
>sy);
    gdImageDestroy(im);
    return roomim;
  } 
}


int draw_bubble(gdImagePtr im, int posn)
/* Draw a speech bubble, resized to fit the space */
{
  gdImagePtr bubbleim;
  int xcoord = posn*WIDTH;

  if ((bubbleim = load_image(BUBBLE)) == NULL) {
    fprintf(stderr, "Cannot open bubble picture file: %s\n", BUBBLE);
    return 0;
  }
  gdImageCopyResized(im, bubbleim, xcoord, 0, 0, 0, WIDTH, Y_BUBBLE,
                                 bubbleim->sx, bubbleim->sy);
  gdImageDestroy(bubbleim);
  return 1;
}


void store_image(gdImagePtr im)
/* Store the image in OPIC, the output picture file,
   located in the PICDIR directory. */
{
  char fnm[MAXLEN];
  FILE *fp;

  sprintf(fnm, "%s%s", PICDIR, OPIC);
  if ((fp = fopen(fnm, "wb")) == NULL)
    fprintf(stderr, "Cannot write to \"%s\"\n", fnm);
  else {
    gdImageGif(im, fp);
    fclose(fp);
  }
}


void draw_scene(Room *r)
/* Draw a complete scene: the background and all the figures */
{
  char roompic[MAXLEN];
  gdImagePtr im;
  int i;
  
  get_room_pic(r->rname, roompic);
  if ((im = load_room(roompic)) == NULL)
    fprintf(stderr, "Cannot load background image in \"%s\"\n", 
roompic);
  else {
    store_image(im);                   /* store background in output 
pic */
    gdImageDestroy(im);
    for (i=0; i < ROOM_SIZE; i++)
      if (r->vs[i].name[0] != '\0')    /* there is someone in the ith 
spot */
        draw_figure(i, r);
  }
}


void draw_figure(int posn, Room *r)
/* Draw a figure in OPIC (i.e. a body image and name). The function
   assumes that OPIC already contains a background, and that there
   is no figure already in that position.
   The vistor's name is written underneath the body image.
*/
{
  gdImagePtr im;

  if ((im = load_image(OPIC)) == NULL) {
    fprintf(stderr, "Cannot open output picture file: %s\n", OPIC);
    return;
  }
  if (draw_body(im, posn, r)) {
    draw_name(im, posn, r);
    store_image(im);
  }
  gdImageDestroy(im);
}


void draw_name(gdImagePtr im, int posn, Room *r)
/* Draw the name in a large black font centered in the
   name area of the image for posn. If the name is too
   wide then print noname instead. The name is printed
   with a white background to increase its visibility.
*/
{
  int black, white;                  /* colours used */
  int xposn, yposn, xright, yright;  /* coordinates */
  int msglen, nmsglen;               /* length of message and noname */
  char *noname = "***";

  black = gdImageColorAllocate(im, 0, 0, 0);
  white = gdImageColorAllocate(im, 255, 255, 255);

  yposn = Y_BUBBLE + Y_BODY + Y_NAME/2 - gdFontLarge->h/2;
  yright = yposn + gdFontLarge->h;

  msglen = strlen(r->vs[posn].name)*gdFontLarge->w;
  nmsglen = strlen(noname)*gdFontLarge->w;

  if (msglen > WIDTH) {     /* name is too wide */
    fprintf(stderr, "%s too wide for figure width\n", r-
>vs[posn].name);
    xposn = posn*WIDTH + WIDTH/2 - nmsglen/2;
    xright = xposn + nmsglen;
    gdImageFilledRectangle(im, xposn, yposn, xright, yright, white);
    gdImageString(im, gdFontLarge, xposn, yposn, noname, black);
  }
  else {    /* print name */
    xposn = posn*WIDTH + WIDTH/2 - msglen/2;
    xright = xposn + msglen;
    gdImageFilledRectangle(im, xposn, yposn, xright, yright, white);
    gdImageString(im, gdFontLarge, xposn, yposn, r->vs[posn].name, 
black);
  }
}


int draw_body(gdImagePtr im, int posn, Room *r)
/* Draw a body, resized to fit the space */
{
  gdImagePtr bodyim;
  char bodypic[MAXLEN];
  int xcoord = posn*WIDTH;

  get_body_pic(r->vs[posn].form, r->vs[posn].mood, bodypic);
  if ((bodyim = load_image(bodypic)) == NULL) {
    fprintf(stderr, "Cannot open body picture file: %s\n", bodypic);
    return 0;
  }
  gdImageCopyResized(im, bodyim, xcoord, Y_BUBBLE, 0, 0, WIDTH, Y_BODY,
                                bodyim->sx, bodyim->sy);
  gdImageDestroy(bodyim);
  return 1;
}


void get_body_pic(enum Form f, enum Mood m, char gifnm[])
/* Get the GIF filename for a given body form and mood.
   It has the form:
      <body-form><first-letter-of-mood>.gif
   The directory prefix is *NOT* added here.
*/
{
  int i, j, len;
  i = (int)f;
  j = (int)m;

  if ((i >= 0) && (i < MAXFORMS))
    strcpy(gifnm, forms[i]);
  else
    strcpy(gifnm, forms[0]);   /* default form is the first */
 
  len = strlen(gifnm);
  if ((j >= 0) && (j < MAXMOODS))
    gifnm[len] = moods[j][0];
  else
    gifnm[len] = moods[0][0];   /* default mood is the first */
  gifnm[len+1] = '\0';

  strcat(gifnm, ".gif");
}


void erase_figure(int posn, Room *r)
/* Erase the body, name and (any) speech bubble from the posn
   spot of OPIC. This is carried out by copying the relevant
   rectangle of the backgrouns into that figure area. */
{
  gdImagePtr im;

  if ((im = load_image(OPIC)) == NULL) {
    fprintf(stderr, "Cannot open picture file: %s\n", OPIC);
    return;
  }
  if (wipe_area(im, posn, 0, Y_BUBBLE+Y_BODY+Y_NAME, r))
    store_image(im);
  gdImageDestroy(im);
}


void change_body(int posn, Room *r)
/* Wipe old body; draw new one */
{
  gdImagePtr im;

  if ((im = load_image(OPIC)) == NULL) {
    fprintf(stderr, "Cannot open picture file: %s\n", OPIC);
    return;
  }
  if ((wipe_area(im, posn, Y_BUBBLE, Y_BODY, r)) &&    /* wipe old body 
*/
      (draw_body(im, posn, r)) )                       /* draw new body 
*/
    store_image(im);
  gdImageDestroy(im);
}


void init_pic(void)
/* The picture in OPIC is initialised as a white rectangle with 
   "Graphical Chat Interface" in a large black font in the middle.
*/
{
  gdImagePtr im;
  int white, black;
  char *msg = "Graphical Chat Interface";

  im = gdImageCreate(ROOM_SIZE*WIDTH, Y_BUBBLE+Y_BODY+Y_NAME);
  white = gdImageColorAllocate(im, 255, 255, 255);
  black = gdImageColorAllocate(im, 0, 0, 0);

  gdImageString(im, gdFontLarge, 
                   (im->sx/2) - (strlen(msg)*(gdFontLarge->w/2)),
                   (im->sy/2) - (gdFontLarge->h/2), msg, black);
  store_image(im);
  gdImageDestroy(im);
}


Full listing to chat.c:
===============


/* chat.c */
/* Andrew Davison (ad@ratree.psu.ac.th) */
/* 12th December 1996 */

/* Use:
     chat [port-number]
   e.g.
     chat
     chat 3001 &

   Before using a particular port number, you should check 
/etc/services 
   for ports used on your system. You should definitely avoid numbers 
   below 1024 which are reserved by UNIX. A default port number is 
stored 
   in PORT.

   Make sure you change SU_NAME so that no one can log-in as super-user 
   but you. Also change SU_EMAIL, so you get Chat users e-mail.

   Users can use telnet to connect to Chat at your site and port. 
   e.g.
     telnet catsix.coe.psu.ac.th 3001

   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.

   Chat user commands and their meanings:
      .quit                  : leave Chat
      .who                   : who is in the room with me?
      .go <room>             : go to <room>
      .help [<command>]      : give me help on <command>
      .private [<name>]      : initiate private conversation with
                               <name>, or end it
      .recent                : what were the recent public messages 
                               in the room?
      .email <message>       : send e-mail to the super-user
      .info                  : give information about Chat rooms

   Super-user commands are:
      .shutdown              : boot off all the Chat users (apart from 
you)
      .users                 : give information on all Chat users
      .warn <user> <message> : warn <user> with <message>
      .close <user>          : boot off <user>
      .ban <user>            : ban this user; actually ban their site

   All commands can be specified with just their first letter 
   (e.g. .q instead of .quit).

   Commands are implemented in functions beginning with the command
   name followed by '_cmd' (e.g. quit_cmd() ).

   General talk starts with anything other than a '.'
*/

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

#define MAX_USERS 20       /* max number of Chat users;
                              cannot exceed FD_SETSIZE used by select() 
*/
#define MAXLEN 120         /* max length of a string */
#define MAX_ROOMS 5        /* max number of Chat rooms */
#define MAX_MSGS 5         /* max number of recent messages stored in a 
room */

#define PORT 2001          /* default port for server */
                           /* should check /etc/services to avoid 
clashes */

#define WAIT_NAME 7        /* seconds given to user to type a name */

/* times are approximate due to behaviour of wait_contact() */
#define CLOSE_TIME 600     /* 10 mins of inactivity means a warning */
#define REPRIEVE_TIME 120  /* 2 mins to act before user shutdown */

#define ROOMS "rooms.chat"  /* file holding room descriptions */
#define BANNED "ban.chat"   /* file holding banned sites */
#define LOG "log.chat"      /* file holding logged information */

/* **CHANGE** these strings for your configuration */
/* Not changing SU_NAME leaves a security hole in Chat */
#define SU_NAME  "Andrew"                 /* super-user's chat name */
#define SU_EMAIL "ad@ratree.psu.ac.th"    /* address of SU contact */



enum Command {QUIT, WHO, GO, HELP, PRIVATE, RECENT, EMAIL, INFO,
              BAN, SHUTDOWN, USERS, WARN, CLOSE, UNKNOWN };
                             /* chat commands */

enum Status {USER, SU};      /* status of chat participants */


typedef struct {
  char name[MAXLEN];         /* user name */
  int sockfd;                /* their socket descriptor */
  struct sockaddr_in addr;   /* address of their site */
  enum Status status;        /* USER or SU */
  int room_loc;              /* user's location in terms of rooms[] 
index */
  int private_contact;       /* other user in terms of their users[] 
index */
  int warned;                /* 0 or 1 */
  time_t act_time;           /* time of last action */
} User;     /* info about a chat user */


typedef struct {
  char *msg[MAX_MSGS];    /* recent messages stored in a room */
  int first, last, size;  
} Messages;

typedef struct {
  char name[MAXLEN];             /* room name */
  char desc[MAXLEN];             /* description of the room */
  Messages talk;                 /* recent talk in the room */
  int curr_members[MAX_USERS];   /* elements hold 1 or 0; index denotes 
user */
} Room;     /* info about a room */


/* function prototypes */
int port_arg(int argc, char *argv[]);
void init_users(User users[]);
void init_rooms(Room rooms[]);
int obtain_socket(int port);
void wait_contact(User users[], int ssockfd, fd_set *readmaskp);

/* process a new user */
void new_user(User users[], Room rooms[], int ssockfd);
void all_users(int sfd, User users[]);
void store_name(User users[], Room rooms[], int csockfd, char *name,
                                          struct sockaddr_in cli_addr);
int get_name(int sfd, char name[], User users[]);
int valid(int sfd, char name[], User users[]);
int in_users(User users[], char *name);
int is_su(User users[], char *name);
void add_user(User users[], int posn, char *name, int sfd, 
                                      struct sockaddr_in addr);
void get_site(struct sockaddr_in *addr, char site[]);
int is_banned(char site[]);
int free_uspot(User users[]);

/* process an existing user */
void old_user(User users[], Room rooms[], fd_set *readmaskp);
void process_old(User users[], int posn, Room rooms[]);
enum Command find_command(char msg[], char **args);
void lower_case(char cmd[]);

/* User commands */
void quit_cmd(User users[], int posn, Room rooms[]);
void remove_user(User users[], int posn, Room rooms[]);
void leave_room(Room rooms[], int rno, User users[], int posn);
void who_cmd(User users[], int posn, Room rooms[]);
void go_cmd(User users[], int posn, Room rooms[], char *args);
void get_word(char *args, char wd[]);
int get_rnum(Room rooms[], char *rname);
void enter_room(Room rooms[], int rno, User users[], int posn);
void help_cmd(User u, char *args);
void private_cmd(User users[], int posn, char *args);
int get_uno(User users[], int posn, char *args);
void recent_cmd(User u, Room r);
void email_cmd(User u, char *args);
void info_cmd(User u, Room rooms[]);
int sum_members(int members[]);

/* Super-User commands */
void shutdown_cmd(User users[], int posn, Room rooms[]);
void users_cmd(User users[], int posn, Room rooms[]);
void warn_cmd(User users[], int posn, char *args);
void close_cmd(User users[], int posn, Room rooms[], char *args);
void ban_cmd(User users[], int posn, Room rooms[], char *args);
void add_to_ban(char *site);

void talk(User users[], int posn, char *args, Room rooms[]);
void add_msg(Messages *t, char *msg);
void check_activity(User users[], int posn, Room rooms[]);

/* socket I/O */
int dputs(int sfd, char *msg);
int dgets(int sfd, char msg[]);

/* logging */
void write_log(char *msg);



void main(int argc, char *argv[])
/* Initialise the two main Chat data structures (users and rooms).
   Create a server socket (ssockfd) and wait for connections.
   A new user connection must come through ssockfd, but later contact
   will be via dedicated sockets for each user.
*/
{
  User users[MAX_USERS];      /* chat users */
  Room rooms[MAX_ROOMS];      /* chat rooms */

  int port, ssockfd;
  fd_set readmask;

  printf("Chat Starting...\n");
  port = port_arg(argc, argv);
  init_users(users);
  init_rooms(rooms);

  signal(SIGPIPE, SIG_IGN);    /* avoid termination due to 
                                  socket write errors */

  ssockfd = obtain_socket(port);

  while(1) {
    wait_contact(users, ssockfd, &readmask);
    if (FD_ISSET(ssockfd, &readmask))      /* new user has contacted 
server */
      new_user(users, rooms, ssockfd);
    else                                   /* message from existing 
user */
      old_user(users, rooms, &readmask);
  }
  /* execution never gets here; chat must be killed by OS commands */
}


int port_arg(int argc, char *argv[])
/* Get a port number, either from the command line or from PORT */
{
  int p;

  if (argc > 2) {
    fprintf(stderr, "Usage: chat [port]\n");
    exit(1);
  }
  if (argc == 2) {
    p = atoi(argv[1]);
    if (p <= 1023) {     /* port numbers <= 1023 are reserved */
      fprintf(stderr, "Port must be > 1023; using %d\n", PORT);
      p = PORT;
    }
    else
      printf("Using port: %d\n", p);
  }
  else if (argc == 1) {
    printf("Using default port number: %d\n", PORT);
    p = PORT;
  }
  return p;
}


void init_users(User users[])
/* Initialise the users array to be 'empty'. 
   The super-user (SU) has his/her name placed in the first slot, so
   that Chat knows who he/she is when they initially connect. */
{
  int i;

  for(i=0; i < MAX_USERS; i++) {
    users[i].name[0] = '\0';            /* empty name */
    users[i].sockfd = -1;               /* no socket */
    users[i].private_contact = -1;      /* no private contact */
    users[i].status = USER;
    users[i].warned = 0;
    users[i].room_loc = 0;              /* first room in rooms[] */
  }

  /* record super-user name and status */
  strcpy(users[0].name, SU_NAME);  
  printf("Super-User Chat name is \"%s\"\n", users[0].name);
  users[0].status = SU;
}


void init_rooms(Room rooms[])
/* Initialise the rooms array by reading in names and descriptions
   from the ROOMS file. Its format is: 
      name description\n
            :
   We assume that the first line describes the room
   where people are placed when they first connect to Chat.
*/
{
  FILE *fp;
  int i = 0, j;

  if ((fp = fopen(ROOMS, "r")) == NULL) {
    fprintf(stderr, "No room descriptions file: %s\n", ROOMS);
    exit(1);
  }
  while ((i < MAX_ROOMS) && 
         (fscanf(fp, "%s %[^\n]", rooms[i].name, rooms[i].desc) == 2)) 
{
    rooms[i].talk.first = rooms[i].talk.last = rooms[i].talk.size = 0;
    for (j=0; j < MAX_USERS; j++)
      rooms[i].curr_members[j] = 0;     /* no occupants as yet */
    i++;
  }
  fclose(fp);
  while (i < MAX_ROOMS) {   /* more space in rooms[] than descriptions 
*/
    rooms[i].name[0] = '\0';
    i++;
  }
}


int obtain_socket(int port)
/* Perform the first four steps of creating a server:
   create a socket, initialise the address data structure,
   bind the address to the socket, and wait for connections. 
   See Stevens, Figure 6.2., p.261 for a nice pictorial overview. 
   See Stevens p.284-285 for a server example.
*/
{
  int sockfd;
  struct sockaddr_in serv_addr;

  /* open a TCP socket */
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    fprintf(stderr, "Could not open a socket");
    exit(1);
  }

  /* initialise socket address */
  bzero((char *)&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(port);

  /* bind socket to address */
  if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 
0) {
    fprintf(stderr, "Could not bind socket to address\n");
    exit(1);
  }

  /* set socket to listen for incoming connections */
  /* allow a queue of 10 */
  if (listen(sockfd, 10) == -1) {
    fprintf(stderr, "listen error\n");
    exit(1);
  }
  return sockfd;
}


void wait_contact(User users[], int ssockfd, fd_set *readmaskp)
/* The server is set to wait until a message arrives from one of 
   the specified socket descriptors. This is achieved by supplying 
   a set of descriptors in readmaskp to a select() call. 
   See Stevens p.328-331 for background on select() and 
   p.594-596 for some example code.
*/
{
  int i, contact;
  
  FD_ZERO(readmaskp);                      /* initialise mask */
  for (i=0; i < MAX_USERS; i++)
    if (users[i].sockfd != -1)             /* if socket is active */
      FD_SET(users[i].sockfd, readmaskp);  /* add to mask */

  FD_SET(ssockfd, readmaskp);              /* add server socket */

  /* now wait for connection */
  contact = select(FD_SETSIZE, readmaskp, (fd_set *)0, (fd_set *)0,
                                               (struct timeval *)0);

  /* contact is bound to the number of clients who have
     sent messages to the server */
  if (contact < 0) {
    fprintf(stderr, "select error\n");
    exit(1);
  }
}


void new_user(User users[], Room rooms[], int ssockfd)
/* A new user has contacted the server, so accept the contact,
   and assigns a new socket to them (csockfd). Their details
   are then recorded.
   accept() is explained in Stevens p.272-274 */
{
  char name[MAXLEN];
  int csockfd, clilen;
  struct sockaddr_in cli_addr;

  clilen = sizeof(cli_addr);
  csockfd  = accept(ssockfd, (struct sockaddr *)&cli_addr, &clilen);
  if (csockfd < 0)
    fprintf(stderr, "accept error\n");
  else {
    dputs(csockfd, "Welcome to Chat\n");
    all_users(csockfd, users);
    if (get_name(csockfd, name, users))
      store_name(users, rooms, csockfd, name, cli_addr);
  }
}


void all_users(int sfd, User users[])
/* Print a list of all the chat users. This allows the new
   user to choose a unique name.  */
{
  char msg[MAXLEN];
  int i, found = 0;

  strcpy(msg, "Present Chat users: ");
  for (i=0; i < MAX_USERS; i++)
    if (users[i].sockfd != -1) {
      found = 1;    /* found someone to mention */
      strcat(msg, users[i].name);
      strcat(msg, "  ");
    }
  if (!found)    /* no chat users! */
    dputs(sfd, "No one in Chat at the moment!");
  else
    dputs(sfd, msg);
}


void store_name(User users[], Room rooms[], int csockfd, char *name,
                                          struct sockaddr_in cli_addr)
/* Storing a name is complicated by having to check whether the
   person is the super-user, and by checking that they are not
   contacting chat from a banned site. */
{
  char site[MAXLEN];
  int posn;

  if ((posn = is_su(users, name)) != -1) {
    dputs(csockfd, "Hello, Mr. Super-User");
    add_user(users, posn, name, csockfd, cli_addr);
    enter_room(rooms, 0, users, posn);      /* rooms[0] is the Chat 
entry */
  }
  else {
    get_site(&cli_addr, site);
    if (is_banned(site)) {
      dputs(csockfd, "Your site is banned... goodbye");
      close(csockfd);
    }
    else {    /* not banned */
      if ((posn = free_uspot(users)) != -1) {
        add_user(users, posn, name, csockfd, cli_addr);
        enter_room(rooms, 0, users, posn);
      }
      else {
        dputs(csockfd, "Sorry, no space in chat at the moment. Try 
later.");
        close(csockfd);
      }
    }
  }
}


int get_name(int sfd, char name[], User users[])
/* get_name() gets the user to enter a name.  
   This could be coded by simply calling dgets(), but
   the normal blocking behaviour of a socket means that a
   dgets() call could be kept waiting indefinitely if the user
   did not enter a name. If dgets() is held up, then so will the
   entire chat system. 

   Instead, select() is used to monitor the socket for a finite 
   amount of time (specified in WAIT_NAME). If a message is sent to
   the socket (i.e. a name is typed) then the wait is terminated
   and dgets() is called to read the name. It is then tested for
   validity by valid().
   If nothing has arrived at the socket after the timeout then 
   get_name() returns 0.
*/
{
  int maxsock, nummesg;
  char msg[MAXLEN];
  struct timeval timeout;
  fd_set readmask;

  timeout.tv_sec = WAIT_NAME;  /* seconds delay */
  timeout.tv_usec = 0;         /* microseconds delay */
  FD_ZERO(&readmask);
  FD_SET(sfd, &readmask);
  maxsock = sfd+1;

  sprintf(msg, "You have %d seconds to enter a unique name: ", 
WAIT_NAME);
  dputs(sfd, msg);

  nummesg = select(maxsock, &readmask, (fd_set *)0, (fd_set *)0, 
&timeout);

  if (nummesg == 0) {                 /* timeout exhausted; no message 
*/
    dputs(sfd, "Sorry, too slow... goodbye");
    close(sfd);
    return 0;
  }

  if (FD_ISSET(sfd, &readmask)) {     /* message waiting on socket */
    if (dgets(sfd, name) == -1) {     /* read error */
      dputs(sfd, "Chat input error... goodbye");
      close(sfd);
      return 0;
    }
    else {                            /* read is okay */
      if (!valid(sfd, name, users)) { /* name is not valid */
        dputs(sfd, "... goodbye");
        close(sfd);
        return 0;
      }
      else                            /* name is valid */
        return 1;
    }
  }

  /* should never reach this point */
  fprintf(stderr, "select() error in get_name\n");
  dputs(sfd, "Chat input selection error... goodbye");
  close(sfd);
  return 0;
}



int valid(int sfd, char name[], User users[])
/* Validity criteria:
   A name consisting of only '\0' is not valid. A name must
   not be used already (unless it is the super-user name).  */
{
  int posn;

  if (name[0] == '\0') {
    dputs(sfd, "An empty name is not valid");
    return 0;
  }
  if ((posn = in_users(users, name)) != -1) {    /* is name already 
used? */
    if ((users[posn].status == SU) && (users[posn].sockfd == -1))  
      return 1;         /* super-user's name & not already connected */
    else {
      dputs(sfd, "A person already has that name in Chat");
      return 0;
    }
  }
  else      /* name is not already used in users[] */
    return 1;
}


int in_users(User users[], char *name)
/* Is name already used in users? The search assumes that name 
   is not '\0', since empty users slots use '\0' in the name field.
   Return the index position if name is found.
*/
{
  int i;
  for (i=0; i < MAX_USERS; i++)
    if (strcmp(users[i].name, name) == 0)
      return i;
  return -1;
}


int is_su(User users[], char *name)
/* Is this the super-user name? Return its index position in users
   if it is found. */
{
  int i;
  for (i=0; i < MAX_USERS; i++)
    if (users[i].status == SU)
      if (strcmp(users[i].name, name) == 0)
        return i;
  return -1;
}

void add_user(User users[], int posn, char *name, int sfd, 
                                          struct sockaddr_in addr)
/* Add a new user to the users[] array.
   Log the user's entry into Chat.
*/
{
  char msg[MAXLEN];
  time_t t;

  strcpy(users[posn].name, name);
  users[posn].sockfd = sfd;
  users[posn].addr = addr;
  users[posn].act_time = time(NULL);
  dputs(sfd, "\nYou have been added to Chat\n");

  t = time(NULL);
  sprintf(msg, "%s added at %s", users[posn].name, ctime(&t));
  write_log(msg);
}


void get_site(struct sockaddr_in *addr, char site[])
/* Given the socket address of the client (addr), store its name
   in site[]. If the name cannot be found, store its dotted decimal 
   version. */
{
  struct hostent *host;

  host = gethostbyaddr((char *) &(addr->sin_addr), sizeof(struct 
in_addr),
                                AF_INET);   /* see Stevens p.395, 
p.649-650 */
  if (host != NULL)
    strcpy(site, host->h_name);   /* name of client's site (Stevens 
p.393) */
  else
    strcpy(site, inet_ntoa(addr->sin_addr));
                           /* client's site in dotted decimal form 
(p.277) */
}


int is_banned(char site[])
/* Is this site banned? Check against a list stored in the BANNED file. 
   This mechanism is fairly simple-minded, since site names can be 
   readily forged. It is also unfair to other users at the same site.
*/
{
  FILE *fp;
  char bansite[MAXLEN];

  if ((fp = fopen(BANNED, "r")) == NULL)
    fprintf(stderr, "Could not read %s\n", BANNED);
  else {
    while (fgets(bansite, MAXLEN, fp) != NULL) {
      bansite[strlen(bansite)-1] = '\0';   /* overwrite \n */
      if (strcmp(bansite, site) == 0) {
        fclose(fp);
        return 1;
      }
    }
    fclose(fp);
  }
  return 0;
}


int free_uspot(User users[])
/* Is there a free spot in users[] for a new user?
   Detect an empty spot by looking at the sockfd field. */
{
  int i;
  for (i=0; i < MAX_USERS; i++)
    if ((users[i].status == USER) && (users[i].sockfd == -1))
      return i;
  return -1;
}



/* Process existing Chat users */

void old_user(User users[], Room rooms[], fd_set *readmaskp)
/* old_user() is called after select() has been woken up.
   select() can be triggered by messages from more
   than one client, and so old_user() should choose fairly between
   multiple messages so that every user eventually gets processed.

   old_user() cycles through users[], always starting from the 
beginning
   of the array. Thus, for a single old_user() call, every user will
   eventually be processed, although users at the end of the array
   will always be serviced after ones at the beginning.

   Another issue with this coding approach is that one user is 
processed 
   at a time. However, the delay for other users is fairly small since
   all user messages (e.g. ".who") can be processed by Chat without
   waiting for further input. The one exception is when a user
   first connects to Chat and is prompted for their name. See 
get_name()
   for details.

   Users with no waiting messages are checked for activity --
   too long with no activity means they are disconnected.
*/
{
  int i;

  for (i=0; i < MAX_USERS; i++)
    if (users[i].sockfd != -1) {
      if (FD_ISSET(users[i].sockfd, readmaskp))
        process_old(users, i, rooms);
      else
        check_activity(users, i, rooms);
    }
}


void process_old(User users[], int posn, Room rooms[])
/* The user in slot posn of users[] has his message processed.
   Problems with the socket are dealt with first, then empty messages.
   A message starting with a '.' is a command, otherwise it is
   public (or private) talk. */
{
  char msg[MAXLEN], *args;
  enum Command cmd;

  if (dgets(users[posn].sockfd, msg) == -1) {    /* socket error */
    remove_user(users, posn, rooms);
    return;
  };
  if (msg[0] == '\0') {                 /* ignore empty messages */
    dputs(users[posn].sockfd, "I'll pass on that one!");
    return;
  }

  users[posn].act_time = time(NULL);    /* record this activity's time 
*/

  if (msg[0] == '.') {                  /* message is a command */
    cmd = find_command(msg, &args);
    switch(cmd) {
      case QUIT: 
        quit_cmd(users, posn, rooms); break;
      case WHO:  
        who_cmd(users, posn, rooms); break;
      case GO: 
        go_cmd(users, posn, rooms, args); break;
      case HELP:  
        help_cmd(users[posn], args); break;
      case PRIVATE: 
        private_cmd(users, posn, args); break;
      case RECENT:  
        recent_cmd(users[posn], rooms[users[posn].room_loc]); break;
      case EMAIL: 
        email_cmd(users[posn], args); break;
      case INFO:
        info_cmd(users[posn], rooms); break;                                    
      case SHUTDOWN: 
        shutdown_cmd(users, posn, rooms); break;
      case USERS:  
        users_cmd(users, posn, rooms); break;
      case WARN: 
        warn_cmd(users, posn, args); break;
      case CLOSE:  
        close_cmd(users, posn, rooms, args); break;
      case BAN: 
        ban_cmd(users, posn, rooms, args); break;
      case UNKNOWN:
        dputs(users[posn].sockfd, "Chat did not understand that 
command"); break;
      default:
        dputs(users[posn].sockfd, "No way, Punk"); break;
    }
    if (cmd != QUIT)
      dputs(users[posn].sockfd, "");     /* newline after Chat's 
response */
  }
  else
    talk(users, posn, msg, rooms);       /* public or private talk */
}


enum Command find_command(char msg[], char **args)
/* The general format of a command message is:
     .command  [args]
   Extract the command from the message, and point args to
   any arguments after the command.
*/
{
  enum Command c;
  char cmd[MAXLEN];    /* command string is stored in here */
  int i = 1, j = 0;

  while ((msg[i] != ' ') && (msg[i] != '\0') &&
         (msg[i] != '\r') && (i < MAXLEN))
    cmd[j++] = msg[i++];
  cmd[j] = '\0';
  lower_case(cmd);
  
  /* all the present Chat commands start with a unique letter, 
     so choosing between them could be coded less expensively */

  if ((strcmp(cmd,"quit") == 0) || (strcmp(cmd,"q") == 0))
    c = QUIT;
  else if ((strcmp(cmd,"who") == 0) || (strcmp(cmd,"w") == 0))
    c = WHO;
  else if ((strcmp(cmd,"go") == 0) || (strcmp(cmd,"g") == 0))
    c = GO;
  else if ((strcmp(cmd,"help") == 0) || (strcmp(cmd,"h") == 0))
    c = HELP;
  else if ((strcmp(cmd,"private") == 0) || (strcmp(cmd,"p") == 0))
    c = PRIVATE;
  else if ((strcmp(cmd,"recent") == 0) || (strcmp(cmd,"r") == 0))
    c = RECENT;
  else if ((strcmp(cmd,"email") == 0) || (strcmp(cmd,"e-mail") == 0) ||
           (strcmp(cmd,"e") == 0))
    c = EMAIL;
  else if ((strcmp(cmd,"info") == 0) || (strcmp(cmd,"i") == 0))
    c = INFO;
  else if ((strcmp(cmd,"ban") == 0) || (strcmp(cmd,"b") == 0))
    c = BAN;
  else if ((strcmp(cmd,"shutdown") == 0) || (strcmp(cmd,"shut") == 0) 
||
           (strcmp(cmd,"s") == 0))
    c = SHUTDOWN;
  else if ((strcmp(cmd,"warn") == 0) || (strcmp(cmd,"w") == 0))
    c = WARN;
  else if ((strcmp(cmd,"close") == 0) || (strcmp(cmd,"c") == 0))
    c = CLOSE;
  else if ((strcmp(cmd,"users") == 0) || (strcmp(cmd,"u") == 0))
    c = USERS;
  else
    c = UNKNOWN;

  while (msg[i] == ' ')  /* skip to end or to start of arguments */
    i++;
  *args = &msg[i];       /* arguments or '\0' */

  return c;
}


void lower_case(char wd[])
/* convert word to lowercase letters */
{
  int i = 0;
  while (wd[i] != '\0') {
    wd[i] = tolower(wd[i]);
    i++;
  }
}


/* User commands start here. These include:
      quit, who, go, help, private, recent, email, info
   The super-user commands start after these.
*/


void quit_cmd(User users[], int posn, Room rooms[])
/* Quit Chat */
{
  dputs(users[posn].sockfd, "Goodbye, come back soon");
  remove_user(users, posn, rooms);
}


void remove_user(User users[], int posn, Room rooms[])
/* First the user is removed from the room, then their
   socket link is closed. Finally, their details are 
   deleted from the users[] array. We log their departure as well.
*/
{
  char msg[MAXLEN];
  time_t t;

  leave_room(rooms, users[posn].room_loc, users, posn);

  /* log the departure of a user */
  t = time(NULL);
  sprintf(msg, "%s left at %s", users[posn].name, ctime(&t));
  write_log(msg);

  close(users[posn].sockfd); 

  if (users[posn].status != SU)
    users[posn].name[0] = '\0';
  users[posn].sockfd = -1;
  users[posn].private_contact = -1;
  users[posn].warned = 0;
}


void leave_room(Room rooms[], int rno, User users[], int posn)
/* Leaving a room means updating the curr_members array in
   rooms[]. Also, everyone else in the room must be told of the
   departure. */
{
  int i;
  char msg[MAXLEN];

  rooms[rno].curr_members[posn] = 0;  /* remove from room */
  users[posn].room_loc = -1;
  sprintf(msg, "You have left the %s", rooms[rno].name);
  dputs(users[posn].sockfd, msg);
  sprintf(msg, "%s has left the room", users[posn].name);
  for (i=0; i < MAX_USERS; i++)
    if (rooms[rno].curr_members[i])
      dputs(users[i].sockfd, msg);
}


void who_cmd(User users[], int posn, Room rooms[])
/* Who else is in the user's current room? */
{
  char msg[MAXLEN];
  int i, rloc, found = 0;

  rloc = users[posn].room_loc;
  sprintf(msg, "Other people in the %s:  ", rooms[rloc].name);
  for (i=0; i < MAX_USERS; i++)
    if ((rooms[rloc].curr_members[i]) && (i != posn)) {
      found = 1;   /* found someone */
      strcat(msg, users[i].name);
      strcat(msg, "  ");
    }
  if (!found)    /* no members */
    dputs(users[posn].sockfd, "Only you!");
  else
    dputs(users[posn].sockfd, msg);
}



void go_cmd(User users[], int posn, Room rooms[], char *args)
/* Go to another room. The destination room is extracted from
   the argument part of the command line.
*/
{
  char to_room[MAXLEN], msg[MAXLEN];
  int new_rm, old_rm;

  get_word(args, to_room);
  new_rm = get_rnum(rooms, to_room);   /* get rooms[] index of to_room 
*/
  if (new_rm != -1) {
    old_rm = users[posn].room_loc;
    leave_room(rooms, old_rm, users, posn);
    enter_room(rooms, new_rm, users, posn);
  }
  else {
    sprintf(msg, "No room called %s", to_room);
    dputs(users[posn].sockfd, msg);
  }
}


void get_word(char *args, char wd[])
/* A word is any text up to a space or null char */
{
  int i = 0;

  while ((*args != ' ') && (*args != '\0') && (i < MAXLEN)) {
    wd[i++] = *args;
    args++;
  }
  wd[i] = '\0';
}


int get_rnum(Room rooms[], char *rname)
/* Get the index in rooms[] of the room called rname */
{
  int i;
  for (i=0; i < MAX_ROOMS; i++)
    if (rooms[i].name[0] != '\0')
      if (strcmp(rooms[i].name, rname) == 0)
        return i;
  return -1;
}



void enter_room(Room rooms[], int rno, User users[], int posn)
/* Enter a room. Firstly tell everyone in the room, and then
   update rooms[] and users[]. */
{
  int i;
  char msg[MAXLEN];

  sprintf(msg, "%s has entered the %s\n", users[posn].name, 
                                rooms[rno].name);
  for (i=0; i < MAX_USERS; i++)
    if (rooms[rno].curr_members[i])
      dputs(users[i].sockfd, msg);

  rooms[rno].curr_members[posn] = 1;      /* add to room */
  users[posn].room_loc = rno;

  sprintf(msg, "You have entered the %s", rooms[rno].name);
  dputs(users[posn].sockfd, msg);
  dputs(users[posn].sockfd, rooms[rno].desc);
  dputs(users[posn].sockfd, "");  /* new line */
}



void help_cmd(User u, char *args)
/* Help command. Fairly basic, but better than nothing. */
{
  char hcmd[MAXLEN];

  get_word(args, hcmd);
  if (hcmd[0] == '\0') {   /* no argument to help */
    dputs(u.sockfd, "Type .help <cmd>, where <cmd> is one of:");
    dputs(u.sockfd, "  quit, who, go, private, recent, email, info");
  }
  else if (strcmp(hcmd, "quit") == 0)
    dputs(u.sockfd, "Causes you to leave Chat");
  else if (strcmp(hcmd, "who") == 0)
    dputs(u.sockfd, "Lists the other people in the room");
  else if (strcmp(hcmd, "go") == 0)
    dputs(u.sockfd, ".go <room> takes you to <room>");
  else if (strcmp(hcmd, "private") == 0) {
    dputs(u.sockfd, ".private <name> puts you into private mode with 
<user>");
    dputs(u.sockfd, "<user> must do the same with you for private 
conversation to occur");
    dputs(u.sockfd, "\n.private puts you into public mode");
  }
  else if (strcmp(hcmd, "recent") == 0)
    dputs(u.sockfd, "Lists the recent public messages in the room");
  else if (strcmp(hcmd, "email") == 0)
    dputs(u.sockfd, ".email <text> sends <text> to the super-user");
  else if (strcmp(hcmd, "info") == 0)
    dputs(u.sockfd, "Gives information about all the Chat rooms");
  else
    dputs(u.sockfd, "Sorry, Chat does not understand that help 
request");
}



void private_cmd(User users[], int posn, char *args)
/* The private command has two functions. 1) If the user requests 
   .private <name> when in public mode then they go into provisional 
   private mode with <name>. Only when <name> also goes into private 
   mode with the user will private communication be allowed.
   2) If the user types .private when in private mode, then they
   go back into public mode, and the other person can no longer send 
   them private messages. This two-part aspect of private communication 
   means that both people must agree before private messages can 
happen.

   The code is lengthened by all the messages that have to be 
   sent to the two participants.
*/
{
  char msg[MAXLEN];
  int other_no;

  if (users[posn].private_contact == -1) { /* public mode --> private 
*/
    if ((other_no = get_uno(users, posn, args)) != -1) {
      users[posn].private_contact = other_no;
      if (users[other_no].private_contact != posn) {
        sprintf(msg, "%s is not yet in private mode with you", 
users[other_no].name);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "%s has asked to be in private mode with you", 
users[posn].name);
        dputs(users[other_no].sockfd, msg);
      }
      else {  
        sprintf(msg, "%s is in private mode with you", 
users[other_no].name);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "%s is in private mode with you", 
users[posn].name);
        dputs(users[other_no].sockfd, msg);
      }
    }
  }
  else {  /* private mode --> public */
    sprintf(msg, "%s is terminating private mode", users[posn].name);
    dputs(users[users[posn].private_contact].sockfd, msg);
    users[posn].private_contact = -1;
    dputs(users[posn].sockfd, "You are back in public mode");
  }
}



int get_uno(User users[], int posn, char *args)
/* Extract a name from the arguments string, and find the user with 
   that name in users[]. Return his/her position, or -1 as an error. */
{
  char uname[MAXLEN], msg[MAXLEN];
  int i;

  get_word(args, uname);
  if (uname[0] == '\0') {
    dputs(users[posn].sockfd, "You must give a name");
    return -1;
  }

  for (i=0; i < MAX_USERS; i++)
    if (strcmp(users[i].name, uname) == 0)
      break;
  if (i == MAX_USERS) {
    sprintf(msg, "%s is not in Chat at the moment", uname);
    dputs(users[posn].sockfd, msg);
    return -1;
  }
  return i;
}


void recent_cmd(User u, Room r)
/* List the recent public mesages in the user's current room.
   The oldest message is listed first.
*/
{
  int i, count;

  if (r.talk.size == 0)
    dputs(u.sockfd, "Sorry, no recent public messages in here");
  else {
    dputs(u.sockfd, "Recent public messages in this room: ");
    i = r.talk.first;
    count = r.talk.size;
    while (count > 0) {
      dputs(u.sockfd, r.talk.msg[i]);
      i = (i+1) % MAX_MSGS;
      count--;
    }
  }
}


void email_cmd(User u, char *args)
/* Send an e-mail message to the super-user.
   The idea is for users to make comments, report problems
   without the super-user having to constantly check a 
   comments file or reports file.  */
{
  char msg[MAXLEN];
  FILE *fp;

  sprintf(msg, "/usr/bin/mail -s \'Chat Message\' %s", SU_EMAIL);
  fp = popen(msg, "w");
  if (fp == NULL) {
    dputs(u.sockfd, "Sorry, mail is not available");
    fprintf(stderr, "popen() error for e-mail\n");
  }
  else {
    fprintf(fp, "%s\n", args);   /* the user's text */
    fclose(fp);
    dputs(u.sockfd, "Your message has been e-mailed to the super-
user");
  }
}


void info_cmd(User u, Room rooms[])
/* Obtain information about Chat rooms. This includes a
   list of all the room names with the number of occupants.
   The user's current room location is also printed.  */
{
  int i, sum, total;
  char msg[MAXLEN];

  dputs(u.sockfd, "Chat rooms with numbers of visitors");
  
  i = 0; total = 0;
  while ((rooms[i].name[0] != '\0') && (i < MAX_ROOMS)) {  /* end of 
rooms */
    sum = sum_members(rooms[i].curr_members);
    if (sum == 0)
      sprintf(msg, "%s: No one!", rooms[i].name);
    else
      sprintf(msg, "%s: %d", rooms[i].name, sum);
    dputs(u.sockfd, msg);
    total = total + sum;
    i++;
  }
  dputs(u.sockfd, "---------");
  sprintf(msg, "Total: %d", total);
  dputs(u.sockfd, msg);
  sprintf(msg, "You are in the %s", rooms[u.room_loc].name);
  dputs(u.sockfd, msg);
}


int sum_members(int curr_members[])
/* Return the number of people in the room. The curr_members array
   contains 0's and 1's, and the index position in curr_members
   corresponds to the index in the users array. So, a '1' in
   curr_members[3] means that users[3] is present in the room. 
*/
{
  int i, sum = 0;
  for (i=0; i < MAX_USERS; i++)
    sum = sum + curr_members[i];    /* contains 0 or 1 */
  return sum;
}


/* super-user commands start here. These include:
       shutdown, users, warn, close, and ban
   An ordinary user is not allowed to execute these commands. 
*/


void shutdown_cmd(User users[], int posn, Room rooms[])
/* Shutdown all the other users, apart from the super-user. */
{
  int i;

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have shutdown rights");
  else {
    for (i=0; i < MAX_USERS; i++)
      if ((users[i].sockfd != -1) && (i != posn)) {
        dputs(users[i].sockfd, "Sorry, Chat is closing 
down...Goodbye");
        remove_user(users, i, rooms);
      }
    dputs(users[posn].sockfd, "User Shutdown completed");
  }
}


void users_cmd(User users[], int posn, Room rooms[])
/* List information about all Chat users. This includes their
   users[] position, name, socket descriptor, private contact
   value, room name, user status (user=0, su=1), client site name,
   and last activity time.  */
{
  int i;
  char msg[MAXLEN], site[MAXLEN];
  time_t t;

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have users info rights");
  else {
    for (i=0; i < MAX_USERS; i++)
      if (users[i].sockfd != -1) {
        sprintf(msg, "\n  posn: %d \t name: %s", i, users[i].name);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "  sockfd: %d \t private_contact: %d", 
                 users[i].sockfd, users[i].private_contact);
        dputs(users[posn].sockfd, msg);
        get_site(&(users[i].addr), site);
        sprintf(msg, "  room: %s \t status: %d \t addr: %s", 
                 rooms[users[i].room_loc].name, users[i].status, site);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "  act_time: %s", ctime(&(users[i].act_time)));
        dputs(users[posn].sockfd, msg);
      }
    t = time(NULL);
    sprintf(msg, "Current time: %s", ctime(&t) );
    dputs(users[posn].sockfd, msg);
  }
}


void warn_cmd(User users[], int posn, char *args)
/* Warn a user. The message comes from args which
   contains the string "user message".  */
{
  int user_no;
  char msg[MAXLEN];

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have warning rights");
  else {
    if ((user_no = get_uno(users, posn, args)) != -1) {
      sprintf(msg, "Super-User WARNING: %s", args);
      dputs(users[user_no].sockfd, msg);
      sprintf(msg, "%s has been warned", users[user_no].name);
      dputs(users[posn].sockfd, msg);
    }
  }
}


void close_cmd(User users[], int posn, Room rooms[], char *args)
/* Close down a user */
{
  int user_no;

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have close rights");
  else {
    if ((user_no = get_uno(users, posn, args)) != -1) {
      dputs(users[user_no].sockfd, 
             "Sorry, but the Super-User is closing your Chat 
connection... Goodbye");
      remove_user(users, user_no, rooms);
    }
  }
}


void ban_cmd(User users[], int posn, Room rooms[], char *args)
/* Ban a user; actually it is their site which is banned by
   being added to the BANNED file. A side-effect of banning
   is that the user is disconnected.  */
{
  int user_no;
  char msg[MAXLEN], site[MAXLEN];

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have banning rights");
  else {
    if ((user_no = get_uno(users, posn, args)) != -1) {
      get_site( &(users[user_no].addr), site);
      add_to_ban(site);
      dputs(users[user_no].sockfd, 
                "The Super-User has banned you from Chat... Goodbye");
      sprintf(msg, "%s has been banned", users[user_no].name);
      dputs(users[posn].sockfd, msg);
      remove_user(users, user_no, rooms);
    }
  }
}


void add_to_ban(char *site)
/* Add site to the BANNED file */
{
  FILE *fp;

  if ((fp = fopen(BANNED, "a")) == NULL)
    fprintf(stderr, "Could not append to %s\n", BANNED);
  else {
    fprintf(fp, "%s\n", site);
    fclose(fp);
  }
}


void talk(User users[], int posn, char *args, Room rooms[])
/* General talk (i.e. an input line that does not start with a '.'). 
   A public message is sent to everyone else in the room
   prefixed with the users name, and is also stored in the
   recent messages queue for that room. 
   A private message is only sent to the other person, and is not 
   stored in the recents queue.
*/
{
  char msg[MAXLEN];
  int i, rloc;

  /* record public and private (!) talk in the log file */
  sprintf(msg, "talk: %s: %s\n", users[posn].name, args);
  fputs(msg, stdout);
  write_log(msg);

  rloc = users[posn].room_loc;
  sprintf(msg, "%s: %s", users[posn].name, args);
  
  if (users[posn].private_contact == -1) {   /* public message */
    for (i=0; i < MAX_USERS; i++)
      if ((rooms[rloc].curr_members[i]) && (i != posn)) 
        dputs(users[i].sockfd, msg);
    add_msg(&(rooms[rloc].talk), msg);
  }
  else {              /* private message */
    i = users[posn].private_contact;
    if (users[i].private_contact == posn)   /* both are private */
      dputs(users[i].sockfd, msg);
    else {
      sprintf(msg, "Sorry, but %s is not in private mode with you",
                           users[i].name);
      dputs(users[posn].sockfd, msg);
    }
  }
}


void add_msg(Messages *t, char *msg)
/* t points to a circular queue of MAX_MSGS elements */
{
  if (t->size == MAX_MSGS) {   /* full; so delete first (oldest) 
message */
    free(t->msg[t->first]);
    t->msg[t->first] = NULL;
    t->first = (t->first + 1) % MAX_MSGS;
    (t->size)--;
  }

  t->msg[t->last] =             /* add msg to the end of the queue */
    (char *) malloc((strlen(msg)+1)*sizeof(char));
  strcpy(t->msg[t->last], msg);
  t->last = (t->last + 1) % MAX_MSGS;
  t->size++;
}



void check_activity(User users[], int posn, Room rooms[])
/* Check to see how inactive a user has been by comparing
   their act_time field in users[] with the current time.
   If they have been inactive for more than CLOSE_TIME seconds
   then give them a warning, and make their act_time REPRIEVE_TIME
   seconds later. The next time that they are inactive for more
   than CLOSE_TIME seconds, they are disconnected.

   The effectivness of this code is reduced by the way that the Chat 
server
   suspends waiting for input. This means that a user may be 
   able to stay connected for quite some time if there has been no
   other user input, and so no chance to call check_activity().
*/
{
  char msg[MAXLEN];
  double inactive_time;

  if (users[posn].status != SU) {           /* never check super-user 
*/
    inactive_time = difftime(time(NULL), users[posn].act_time);
    if (inactive_time > CLOSE_TIME) {       /* inactive too long */
      if (!users[posn].warned) {            /* not warned yet */
        sprintf(msg, "Chat WARNING: You have been inactive for over %d 
mins, do something NOW!", CLOSE_TIME/60);
        dputs(users[posn].sockfd, msg);
        users[posn].warned = 1;
        users[posn].act_time = users[posn].act_time + REPRIEVE_TIME;
      }
      else {    /* warned before */
        dputs(users[posn].sockfd, "Chat Message: No activity means... 
Goodbye");
        remove_user(users, posn, rooms);
      }
    }
  }
}


/* socket I/O */

int dputs(int sfd, char *msg)
/* Write msg to the sfd socket. Return the number of characters
   actually written, although this feature is not used in the
   rest of the Chat code. If no characters were written then 
   there is something wrong with the socket link.
*/
{
  int i;

  i = write(sfd, msg, strlen(msg));
  write(sfd, "\n", 1);

  if (i < 0)
    fprintf(stderr, "Write error to socket %d for msg:\"%s\"\n", sfd, 
msg);
  return (i >= 0) ? (i+1) : i;
}


int dgets(int sfd, char msg[])
/* Read at most MAXLEN characters from the sfd socket.
   read() returns the number of characters actually read,
   which is used to detect some errors. 
   The input is read into buf[] which is copied into msg[]
   with any non-printable characters removed (e.g. control
   characters). This may mean that msg[] is empty. The length
   of msg[] is returned.
*/
{
  char buf[MAXLEN]; 
  int i, len = 0, plen = 0;

  len = read(sfd, buf, MAXLEN);
  if (len == 0) {
    fprintf(stderr, "Socket %d has unexpectedly closed\n", sfd);
    return -1;
  }
  if (len == -1) { 
    fprintf(stderr, "Socket %d read error\n", sfd);
    return -1;
  }

  for (i = 0; i < len; i++)     /* get rid of any nasty chars */
    if (isprint(buf[i]))
      msg[plen++] = buf[i];
  msg[plen] = '\0'; 

  return plen;        /* length of printable characters in msg */
}



/* for logging */
/* Currently this is used to log three forms of activity:
    1. Users entering Chat
    2. Users leaving Chat
    3. General talk (both public and private)
*/

void write_log(char *msg)
/* Append msg to the end of the LOG file */
{
  FILE *fp;

  if ((fp = fopen(LOG, "a")) == NULL)
    fprintf(stderr, "Could not append to %s\n", LOG);
  else {
    fputs(msg, fp);
    fclose(fp);
  }
}


Full listing to chat2.c
================


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

/* Use:
     chat2 [port-number]
   e.g.
     chat2
     chat2 3001 &

   Before using a particular port number, you should check 
/etc/services 
   for ports used on your system. You should definitely avoid numbers 
   below 1024 which are reserved by UNIX. A default port number is 
stored 
   in PORT.

   Make sure you change SU_NAME so that no one can log-in as super-user 
   but you. Also change SU_EMAIL, so you get Chat users e-mail.

   Users can use telnet to connect to Chat at your site and port. 
   e.g.
     telnet catsix.coe.psu.ac.th 3001

   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.

   Chat user commands and their meanings:
      .quit                  : leave Chat
      .who                   : who is in the room with me?
      .go <room>             : go to <room>
      .help [<command>]      : give me help on <command>
      .private [<name>]      : initiate private conversation with
                               <name>, or end it
      .recent                : what were the recent public messages 
                               in the room?
      .email <message>       : send e-mail to the super-user
      .info                  : give information about Chat rooms
      .mood <mood-type>      : NEW. change your mood; see moods[] array
                               for mood types
      .form <form-type>      : NEW. change your body form; see forms[]
                               array for form types

   Super-user commands are:
      .shutdown              : boot off all the Chat users (apart from 
you)
      .users                 : give information on all Chat users
      .warn <user> <message> : warn <user> with <message>
      .close <user>          : boot off <user>
      .ban <user>            : ban this user; actually ban their site

   All commands can be specified with just their first letter 
   (e.g. .q instead of .quit).

   Commands are implemented in functions beginning with the command
   name followed by '_cmd' (e.g. quit_cmd() ).

   General talk starts with anything other than a '.'

   Server output includes messages with visualisation cues. These
   may be used by graphical clients to generate an image of their
   user's chat room. Cues are simply a letter and "$ " at the start
   of messages to help the client find the visual info. The following
   list gives the cue letter, the type of message it prefixes, and
   the function that contains it.

   Cue     Type of info output                    function name
   ---     -------------------                    -------------
   w       the people currently in the room       who_cmd()
   e       who has just entered the room          enter_room()
   l       who has just left the room             leave_room()
   m       who has just changed their mood (and to what)  mood_cmd()
   f       who has just changed their form (and to what)  form_cmd()
   u       the user's name                        add_user()
   t       who is speaking (other than the user)  talk()

*/
/* Changes from chat.c
   * bug fix: the warning flag is now switched off in process_old()
     when the user does something
   * dputs() now uses only 1 write() call to output a message,
     which may avoid bugs with multiple write()s per message in some 
UNIXs
   * who_cmd() altered to list every user. enter_room() now calls it.

   * added mood and body form to User data structure; several functions
     modified, including find_command(), process_old(), help_cmd(), 
     users_cmd(), remove_user()
   * included visualisation cues in some output messages
*/

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

#define MAX_USERS 20       /* max number of Chat users;
                              cannot exceed FD_SETSIZE used by select() 
*/
#define MAXLEN 120         /* max length of a string */
#define MAX_ROOMS 7        /* max number of Chat rooms */
#define MAXMOODS 3         /* max number of moods */
#define MAXFORMS 7         /* max number of body forms*/
#define MAX_MSGS 5         /* max number of recent messages stored in a 
room */

#define PORT 2001          /* default port for server */
                           /* should check /etc/services to avoid 
clashes */

#define WAIT_NAME 7        /* seconds given to user to type a name */

/* times are approximate due to behaviour of wait_contact() */
#define CLOSE_TIME 600     /* 10 mins of inactivity means a warning */
#define REPRIEVE_TIME 120  /* 2 mins to act before user shutdown */

#define ROOMS "rooms.chat"  /* file holding room descriptions */
#define BANNED "ban.chat"   /* file holding banned sites */
#define LOG "log.chat"      /* file holding logged information */

/* **CHANGE** these strings for your configuration */
/* Not changing SU_NAME leaves a security hole in Chat */
#define SU_NAME  "Andrew"                 /* super-user's chat name */
#define SU_EMAIL "ad@ratree.psu.ac.th"    /* address of SU contact */



enum Command {QUIT, WHO, GO, HELP, PRIVATE, RECENT, EMAIL, INFO,
              BAN, SHUTDOWN, USERS, WARN, CLOSE, MOOD, FORM, UNKNOWN };
                             /* chat commands */

enum Status {USER, SU};      /* status of chat participants */
enum Mood {OK, HAPPY, SAD};  /* different user moods */
enum Form {NEWBIE, DANDY, HACKER, MANAGER, BOBBIE, WOOF, GRAD};   
                             /* different user body forms */

typedef struct {
  char name[MAXLEN];         /* user name */
  int sockfd;                /* their socket descriptor */
  struct sockaddr_in addr;   /* address of their site */
  enum Status status;        /* USER or SU */
  int room_loc;              /* user's location in terms of rooms[] 
index */
  int private_contact;       /* other user in terms of their users[] 
index */
  int warned;                /* 0 or 1 */
  time_t act_time;           /* time of last action */
  enum Mood mood;            /* mood (NEW) */
  enum Form form;            /* body form (NEW) */
} User;     /* info about a chat user */


typedef struct {
  char *msg[MAX_MSGS];    /* recent messages stored in a room */
  int first, last, size;  
} Messages;

typedef struct {
  char name[MAXLEN];             /* room name */
  char desc[MAXLEN];             /* description of the room */
  Messages talk;                 /* recent talk in the room */
  int curr_members[MAX_USERS];   /* elements hold 1 or 0; index denotes 
user */
} Room;     /* info about a room */


/* function prototypes */
int port_arg(int argc, char *argv[]);
void init_users(User users[]);
void init_rooms(Room rooms[]);
int obtain_socket(int port);
void wait_contact(User users[], int ssockfd, fd_set *readmaskp);

/* process a new user */
void new_user(User users[], Room rooms[], int ssockfd);
void all_users(int sfd, User users[]);
void store_name(User users[], Room rooms[], int csockfd, char *name,
                                          struct sockaddr_in cli_addr);
int get_name(int sfd, char name[], User users[]);
int valid(int sfd, char name[], User users[]);
int in_users(User users[], char *name);
int is_su(User users[], char *name);
void add_user(User users[], int posn, char *name, int sfd, 
                                      struct sockaddr_in addr);
void get_site(struct sockaddr_in *addr, char site[]);
int is_banned(char site[]);
int free_uspot(User users[]);

/* process an existing user */
void old_user(User users[], Room rooms[], fd_set *readmaskp);
void process_old(User users[], int posn, Room rooms[]);
enum Command find_command(char msg[], char **args);
void lower_case(char cmd[]);

/* User commands */
void quit_cmd(User users[], int posn, Room rooms[]);
void remove_user(User users[], int posn, Room rooms[]);
void leave_room(Room rooms[], int rno, User users[], int posn);
void who_cmd(User users[], int posn, Room rooms[]);
void go_cmd(User users[], int posn, Room rooms[], char *args);
void get_word(char *args, char wd[]);
int get_rnum(Room rooms[], char *rname);
void enter_room(Room rooms[], int rno, User users[], int posn);
void help_cmd(User u, char *args);
void private_cmd(User users[], int posn, char *args);
int get_uno(User users[], int posn, char *args);
void recent_cmd(User u, Room r);
void email_cmd(User u, char *args);
void info_cmd(User u, Room rooms[]);
int sum_members(int members[]);
void mood_cmd(User users[], int posn, Room rooms[], char *args);
void form_cmd(User users[], int posn, Room rooms[], char *args);

/* Super-User commands */
void shutdown_cmd(User users[], int posn, Room rooms[]);
void users_cmd(User users[], int posn, Room rooms[]);
void warn_cmd(User users[], int posn, char *args);
void close_cmd(User users[], int posn, Room rooms[], char *args);
void ban_cmd(User users[], int posn, Room rooms[], char *args);
void add_to_ban(char *site);

void talk(User users[], int posn, char *args, Room rooms[]);
void add_msg(Messages *t, char *msg);
void check_activity(User users[], int posn, Room rooms[]);

/* socket I/O */
int dputs(int sfd, char *msg);
int dgets(int sfd, char msg[]);

/* logging */
void write_log(char *msg);


/* Global Data */
/* Must correspond to the enumeration types above */
char *moods[MAXMOODS] = {"ok", "happy", "sad"};
char *forms[MAXFORMS] = {"newbie", "dandy", "hacker", 
                         "manager", "bobbie", "woof", "grad"};


void main(int argc, char *argv[])
/* Initialise the two main Chat data structures (users and rooms).
   Create a server socket (ssockfd) and wait for connections.
   A new user connection must come through ssockfd, but later contact
   will be via dedicated sockets for each user.
*/
{
  User users[MAX_USERS];      /* chat users */
  Room rooms[MAX_ROOMS];      /* chat rooms */

  int port, ssockfd;
  fd_set readmask;

  printf("Chat Starting...\n");
  port = port_arg(argc, argv);
  init_users(users);
  init_rooms(rooms);

  signal(SIGPIPE, SIG_IGN);    /* avoid termination due to 
                                  socket write errors */

  ssockfd = obtain_socket(port);

  while(1) {
    wait_contact(users, ssockfd, &readmask);
    if (FD_ISSET(ssockfd, &readmask))      /* new user has contacted 
server */
      new_user(users, rooms, ssockfd);
    else                                   /* message from existing 
user */
      old_user(users, rooms, &readmask);
  }
  /* execution never gets here; chat must be killed by OS commands */
}


int port_arg(int argc, char *argv[])
/* Get a port number, either from the command line or from PORT */
{
  int p;

  if (argc > 2) {
    fprintf(stderr, "Usage: chat [port]\n");
    exit(1);
  }
  if (argc == 2) {
    p = atoi(argv[1]);
    if (p <= 1023) {     /* port numbers <= 1023 are reserved */
      fprintf(stderr, "Port must be > 1023; using %d\n", PORT);
      p = PORT;
    }
    else
      printf("Using port: %d\n", p);
  }
  else if (argc == 1) {
    printf("Using default port number: %d\n", PORT);
    p = PORT;
  }
  return p;
}


void init_users(User users[])
/* Initialise the users array to be 'empty'. 
   The super-user (SU) has his/her name placed in the first slot, so
   that Chat knows who he/she is when they initially connect. */
{
  int i;

  for(i=0; i < MAX_USERS; i++) {
    users[i].name[0] = '\0';            /* empty name */
    users[i].sockfd = -1;               /* no socket */
    users[i].private_contact = -1;      /* no private contact */
    users[i].status = USER;
    users[i].warned = 0;
    users[i].room_loc = 0;              /* first room in rooms[] */
    users[i].mood = (enum Mood)0;
    users[i].form = (enum Form)0;
  }

  /* record super-user name and status */
  strcpy(users[0].name, SU_NAME);  
  printf("Super-User Chat name is \"%s\"\n", users[0].name);
  users[0].status = SU;
}


void init_rooms(Room rooms[])
/* Initialise the rooms array by reading in names and descriptions
   from the ROOMS file. Its format is: 
      name description\n
            :
   We assume that the first line describes the room
   where people are placed when they first connect to Chat.
*/
{
  FILE *fp;
  int i = 0, j;

  if ((fp = fopen(ROOMS, "r")) == NULL) {
    fprintf(stderr, "No room descriptions file: %s\n", ROOMS);
    exit(1);
  }
  while ((i < MAX_ROOMS) && 
         (fscanf(fp, "%s %[^\n]", rooms[i].name, rooms[i].desc) == 2)) 
{
    rooms[i].talk.first = rooms[i].talk.last = rooms[i].talk.size = 0;
    for (j=0; j < MAX_USERS; j++)
      rooms[i].curr_members[j] = 0;     /* no occupants as yet */
    i++;
  }
  fclose(fp);
  while (i < MAX_ROOMS) {   /* more space in rooms[] than descriptions 
*/
    rooms[i].name[0] = '\0';
    i++;
  }
}


int obtain_socket(int port)
/* Perform the first four steps of creating a server:
   create a socket, initialise the address data structure,
   bind the address to the socket, and wait for connections. 
   See Stevens, Figure 6.2., p.261 for a nice pictorial overview. 
   See Stevens p.284-285 for a server example.
*/
{
  int sockfd;
  struct sockaddr_in serv_addr;

  /* open a TCP socket */
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    fprintf(stderr, "Could not open a socket");
    exit(1);
  }

  /* initialise socket address */
  bzero((char *)&serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(port);

  /* bind socket to address */
  if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 
0) {
    fprintf(stderr, "Could not bind socket to address\n");
    exit(1);
  }

  /* set socket to listen for incoming connections */
  /* allow a queue of 10 */
  if (listen(sockfd, 10) == -1) {
    fprintf(stderr, "listen error\n");
    exit(1);
  }
  return sockfd;
}


void wait_contact(User users[], int ssockfd, fd_set *readmaskp)
/* The server is set to wait until a message arrives from one of 
   the specified socket descriptors. This is achieved by supplying 
   a set of descriptors in readmaskp to a select() call. 
   See Stevens p.328-331 for background on select() and 
   p.594-596 for some example code.
*/
{
  int i, contact;
  
  FD_ZERO(readmaskp);                      /* initialise mask */
  for (i=0; i < MAX_USERS; i++)
    if (users[i].sockfd != -1)             /* if socket is active */
      FD_SET(users[i].sockfd, readmaskp);  /* add to mask */

  FD_SET(ssockfd, readmaskp);              /* add server socket */

  /* now wait for connection */
  contact = select(FD_SETSIZE, readmaskp, (fd_set *)0, (fd_set *)0,
                                               (struct timeval *)0);

  /* contact is bound to the number of clients who have
     sent messages to the server */
  if (contact < 0) {
    fprintf(stderr, "select error\n");
    exit(1);
  }
}


void new_user(User users[], Room rooms[], int ssockfd)
/* A new user has contacted the server, so accept the contact,
   and assigns a new socket to them (csockfd). Their details
   are then recorded.
   accept() is explained in Stevens p.272-274 */
{
  char name[MAXLEN];
  int csockfd, clilen;
  struct sockaddr_in cli_addr;

  clilen = sizeof(cli_addr);
  csockfd  = accept(ssockfd, (struct sockaddr *)&cli_addr, &clilen);
  if (csockfd < 0)
    fprintf(stderr, "accept error\n");
  else {
    dputs(csockfd, "Welcome to Chat\n");
    all_users(csockfd, users);
    if (get_name(csockfd, name, users))
      store_name(users, rooms, csockfd, name, cli_addr);
  }
}


void all_users(int sfd, User users[])
/* Print a list of all the chat users. This allows the new
   user to choose a unique name.  */
{
  char msg[MAXLEN];
  int i, found = 0;

  strcpy(msg, "Present Chat users: ");
  for (i=0; i < MAX_USERS; i++)
    if (users[i].sockfd != -1) {
      found = 1;    /* found someone to mention */
      strcat(msg, users[i].name);
      strcat(msg, "  ");
    }
  if (!found)    /* no chat users! */
    dputs(sfd, "No one in Chat at the moment!");
  else
    dputs(sfd, msg);
}


void store_name(User users[], Room rooms[], int csockfd, char *name,
                                          struct sockaddr_in cli_addr)
/* Storing a name is complicated by having to check whether the
   person is the super-user, and by checking that they are not
   contacting chat from a banned site. */
{
  char site[MAXLEN];
  int posn;

  if ((posn = is_su(users, name)) != -1) {
    dputs(csockfd, "Hello, Mr. Super-User");
    add_user(users, posn, name, csockfd, cli_addr);
    enter_room(rooms, 0, users, posn);      /* rooms[0] is the Chat 
entry */
  }
  else {
    get_site(&cli_addr, site);
    if (is_banned(site)) {
      dputs(csockfd, "Your site is banned... goodbye");
      close(csockfd);
    }
    else {    /* not banned */
      if ((posn = free_uspot(users)) != -1) {
        add_user(users, posn, name, csockfd, cli_addr);
        enter_room(rooms, 0, users, posn);
      }
      else {
        dputs(csockfd, "Sorry, no space in chat at the moment. Try 
later.");
        close(csockfd);
      }
    }
  }
}


int get_name(int sfd, char name[], User users[])
/* get_name() gets the user to enter a name.  
   This could be coded by simply calling dgets(), but
   the normal blocking behaviour of a socket means that a
   dgets() call could be kept waiting indefinitely if the user
   did not enter a name. If dgets() is held up, then so will the
   entire chat system. 

   Instead, select() is used to monitor the socket for a finite 
   amount of time (specified in WAIT_NAME). If a message is sent to
   the socket (i.e. a name is typed) then the wait is terminated
   and dgets() is called to read the name. It is then tested for
   validity by valid().
   If nothing has arrived at the socket after the timeout then 
   get_name() returns 0.
*/
{
  int maxsock, nummesg;
  char msg[MAXLEN];
  struct timeval timeout;
  fd_set readmask;

  timeout.tv_sec = WAIT_NAME;  /* seconds delay */
  timeout.tv_usec = 0;         /* microseconds delay */
  FD_ZERO(&readmask);
  FD_SET(sfd, &readmask);
  maxsock = sfd+1;

  sprintf(msg, "You have %d seconds to enter a unique name: ", 
WAIT_NAME);
  dputs(sfd, msg);

  nummesg = select(maxsock, &readmask, (fd_set *)0, (fd_set *)0, 
&timeout);

  if (nummesg == 0) {                 /* timeout exhausted; no message 
*/
    dputs(sfd, "Sorry, too slow... goodbye");
    close(sfd);
    return 0;
  }

  if (FD_ISSET(sfd, &readmask)) {     /* message waiting on socket */
    if (dgets(sfd, name) == -1) {     /* read error */
      dputs(sfd, "Chat input error... goodbye");
      close(sfd);
      return 0;
    }
    else {                            /* read is okay */
      if (!valid(sfd, name, users)) { /* name is not valid */
        dputs(sfd, "... goodbye");
        close(sfd);
        return 0;
      }
      else                            /* name is valid */
        return 1;
    }
  }

  /* should never reach this point */
  fprintf(stderr, "select() error in get_name\n");
  dputs(sfd, "Chat input selection error... goodbye");
  close(sfd);
  return 0;
}



int valid(int sfd, char name[], User users[])
/* Validity criteria:
   A name consisting of only '\0' is not valid. A name must
   not be used already (unless it is the super-user name).  */
{
  int posn;

  if (name[0] == '\0') {
    dputs(sfd, "An empty name is not valid");
    return 0;
  }
  if ((posn = in_users(users, name)) != -1) {    /* is name already 
used? */
    if ((users[posn].status == SU) && (users[posn].sockfd == -1))  
      return 1;         /* super-user's name & not already connected */
    else {
      dputs(sfd, "A person already has that name in Chat");
      return 0;
    }
  }
  else      /* name is not already used in users[] */
    return 1;
}


int in_users(User users[], char *name)
/* Is name already used in users? The search assumes that name 
   is not '\0', since empty users slots use '\0' in the name field.
   Return the index position if name is found.
*/
{
  int i;
  for (i=0; i < MAX_USERS; i++)
    if (strcmp(users[i].name, name) == 0)
      return i;
  return -1;
}


int is_su(User users[], char *name)
/* Is this the super-user name? Return its index position in users
   if it is found. */
{
  int i;
  for (i=0; i < MAX_USERS; i++)
    if (users[i].status == SU)
      if (strcmp(users[i].name, name) == 0)
        return i;
  return -1;
}

void add_user(User users[], int posn, char *name, int sfd, 
                                          struct sockaddr_in addr)
/* Add a new user to the users[] array.
   Log the user's entry into Chat.
*/
{
  char msg[MAXLEN];
  time_t t;

  strcpy(users[posn].name, name);
  users[posn].sockfd = sfd;
  users[posn].addr = addr;
  users[posn].act_time = time(NULL);
  sprintf(msg, "u$ %s, you have been added to Chat\n", 
users[posn].name);
  dputs(sfd, msg);

  t = time(NULL);
  sprintf(msg, "%s added at %s", users[posn].name, ctime(&t));
  write_log(msg);
}


void get_site(struct sockaddr_in *addr, char site[])
/* Given the socket address of the client (addr), store its name
   in site[]. If the name cannot be found, store its dotted decimal 
   version. */
{
  struct hostent *host;

  host = gethostbyaddr((char *) &(addr->sin_addr), sizeof(struct 
in_addr),
                                AF_INET);   /* see Stevens p.395, 
p.649-650 */
  if (host != NULL)
    strcpy(site, host->h_name);   /* name of client's site (Stevens 
p.393) */
  else
    strcpy(site, inet_ntoa(addr->sin_addr));
                           /* client's site in dotted decimal form 
(p.277) */
}


int is_banned(char site[])
/* Is this site banned? Check against a list stored in the BANNED file. 
   This mechanism is fairly simple-minded, since site names can be 
   readily forged. It is also unfair to other users at the same site.
*/
{
  FILE *fp;
  char bansite[MAXLEN];

  if ((fp = fopen(BANNED, "r")) == NULL)
    fprintf(stderr, "Could not read %s\n", BANNED);
  else {
    while (fgets(bansite, MAXLEN, fp) != NULL) {
      bansite[strlen(bansite)-1] = '\0';   /* overwrite \n */
      if (strcmp(bansite, site) == 0) {
        fclose(fp);
        return 1;
      }
    }
    fclose(fp);
  }
  return 0;
}


int free_uspot(User users[])
/* Is there a free spot in users[] for a new user?
   Detect an empty spot by looking at the sockfd field. */
{
  int i;
  for (i=0; i < MAX_USERS; i++)
    if ((users[i].status == USER) && (users[i].sockfd == -1))
      return i;
  return -1;
}



/* Process existing Chat users */

void old_user(User users[], Room rooms[], fd_set *readmaskp)
/* old_user() is called after select() has been woken up.
   select() can be triggered by messages from more
   than one client, and so old_user() should choose fairly between
   multiple messages so that every user eventually gets processed.

   old_user() cycles through users[], always starting from the 
beginning
   of the array. Thus, for a single old_user() call, every user will
   eventually be processed, although users at the end of the array
   will always be serviced after ones at the beginning.

   Another issue with this coding approach is that one user is 
processed 
   at a time. However, the delay for other users is fairly small since
   all user messages (e.g. ".who") can be processed by Chat without
   waiting for further input. The one exception is when a user
   first connects to Chat and is prompted for their name. See 
get_name()
   for details.

   Users with no waiting messages are checked for activity --
   too long with no activity means they are disconnected.
*/
{
  int i;

  for (i=0; i < MAX_USERS; i++)
    if (users[i].sockfd != -1) {
      if (FD_ISSET(users[i].sockfd, readmaskp))
        process_old(users, i, rooms);
      else
        check_activity(users, i, rooms);
    }
}


void process_old(User users[], int posn, Room rooms[])
/* The user in slot posn of users[] has his message processed.
   Problems with the socket are dealt with first, then empty messages.
   A message starting with a '.' is a command, otherwise it is
   public (or private) talk. */
{
  char msg[MAXLEN], *args;
  enum Command cmd;

  if (dgets(users[posn].sockfd, msg) == -1) {    /* socket error */
    remove_user(users, posn, rooms);
    return;
  };
  if (msg[0] == '\0') {                 /* ignore empty messages */
    dputs(users[posn].sockfd, "I'll pass on that one!");
    return;
  }

  users[posn].act_time = time(NULL);    /* record this activity's time 
*/
  users[posn].warned = 0;

  if (msg[0] == '.') {                  /* message is a command */
    cmd = find_command(msg, &args);
    switch(cmd) {
      case QUIT: 
        quit_cmd(users, posn, rooms); break;
      case WHO:  
        who_cmd(users, posn, rooms); break;
      case GO: 
        go_cmd(users, posn, rooms, args); break;
      case HELP:  
        help_cmd(users[posn], args); break;
      case PRIVATE: 
        private_cmd(users, posn, args); break;
      case RECENT:  
        recent_cmd(users[posn], rooms[users[posn].room_loc]); break;
      case EMAIL: 
        email_cmd(users[posn], args); break;
      case INFO:
        info_cmd(users[posn], rooms); break;                                    
      case SHUTDOWN: 
        shutdown_cmd(users, posn, rooms); break;
      case USERS:  
        users_cmd(users, posn, rooms); break;
      case WARN: 
        warn_cmd(users, posn, args); break;
      case CLOSE:  
        close_cmd(users, posn, rooms, args); break;
      case BAN: 
        ban_cmd(users, posn, rooms, args); break;
      case MOOD:
        mood_cmd(users, posn, rooms, args); break;
      case FORM:
        form_cmd(users, posn, rooms, args); break;
      case UNKNOWN:
        dputs(users[posn].sockfd, "Chat did not understand that 
command"); break;
      default:
        dputs(users[posn].sockfd, "No way, Punk"); break;
    }
    if (cmd != QUIT)
      dputs(users[posn].sockfd, "");     /* newline after Chat's 
response */
  }
  else
    talk(users, posn, msg, rooms);       /* public or private talk */
}


enum Command find_command(char msg[], char **args)
/* The general format of a command message is:
     .command  [args]
   Extract the command from the message, and point args to
   any arguments after the command.
*/
{
  enum Command c;
  char cmd[MAXLEN];    /* command string is stored in here */
  int i = 1, j = 0;

  while ((msg[i] != ' ') && (msg[i] != '\0') &&
         (msg[i] != '\r') && (i < MAXLEN))
    cmd[j++] = msg[i++];
  cmd[j] = '\0';
  lower_case(cmd);
  
  /* all the present Chat commands start with a unique letter, 
     so choosing between them could be coded less expensively */

  if ((strcmp(cmd,"quit") == 0) || (strcmp(cmd,"q") == 0))
    c = QUIT;
  else if ((strcmp(cmd,"who") == 0) || (strcmp(cmd,"w") == 0))
    c = WHO;
  else if ((strcmp(cmd,"go") == 0) || (strcmp(cmd,"g") == 0))
    c = GO;
  else if ((strcmp(cmd,"help") == 0) || (strcmp(cmd,"h") == 0))
    c = HELP;
  else if ((strcmp(cmd,"private") == 0) || (strcmp(cmd,"p") == 0))
    c = PRIVATE;
  else if ((strcmp(cmd,"recent") == 0) || (strcmp(cmd,"r") == 0))
    c = RECENT;
  else if ((strcmp(cmd,"email") == 0) || (strcmp(cmd,"e-mail") == 0) ||
           (strcmp(cmd,"e") == 0))
    c = EMAIL;
  else if ((strcmp(cmd,"info") == 0) || (strcmp(cmd,"i") == 0))
    c = INFO;
  else if ((strcmp(cmd,"ban") == 0) || (strcmp(cmd,"b") == 0))
    c = BAN;
  else if ((strcmp(cmd,"shutdown") == 0) || (strcmp(cmd,"shut") == 0) 
||
           (strcmp(cmd,"s") == 0))
    c = SHUTDOWN;
  else if ((strcmp(cmd,"warn") == 0) || (strcmp(cmd,"w") == 0))
    c = WARN;
  else if ((strcmp(cmd,"close") == 0) || (strcmp(cmd,"c") == 0))
    c = CLOSE;
  else if ((strcmp(cmd,"users") == 0) || (strcmp(cmd,"u") == 0))
    c = USERS;
  else if ((strcmp(cmd,"mood") == 0) || (strcmp(cmd,"m") == 0))
    c = MOOD;
  else if ((strcmp(cmd,"form") == 0) || (strcmp(cmd,"f") == 0))
    c = FORM;
  else
    c = UNKNOWN;

  while (msg[i] == ' ')  /* skip to end or to start of arguments */
    i++;
  *args = &msg[i];       /* arguments or '\0' */

  return c;
}


void lower_case(char wd[])
/* convert word to lowercase letters */
{
  int i = 0;
  while (wd[i] != '\0') {
    wd[i] = tolower(wd[i]);
    i++;
  }
}


/* User commands start here. These are:
      quit, who, go, help, private, recent, email, info, mood, form
   The super-user commands start after these.
*/


void quit_cmd(User users[], int posn, Room rooms[])
/* Quit Chat */
{
  dputs(users[posn].sockfd, "Goodbye, come back soon");
  remove_user(users, posn, rooms);
}


void remove_user(User users[], int posn, Room rooms[])
/* First the user is removed from the room, then their
   socket link is closed. Finally, their details are 
   deleted from the users[] array. We log their departure as well.
*/
{
  char msg[MAXLEN];
  time_t t;

  leave_room(rooms, users[posn].room_loc, users, posn);

  /* log the departure of a user */
  t = time(NULL);
  sprintf(msg, "%s left at %s", users[posn].name, ctime(&t));
  write_log(msg);

  close(users[posn].sockfd); 

  if (users[posn].status != SU)
    users[posn].name[0] = '\0';
  users[posn].sockfd = -1;
  users[posn].private_contact = -1;
  users[posn].warned = 0;
  users[posn].mood = (enum Mood)0;
  users[posn].form = (enum Form)0;
}


void leave_room(Room rooms[], int rno, User users[], int posn)
/* Leaving a room means updating the curr_members array in
   rooms[]. Also, everyone else in the room must be told of the
   departure. */
{
  int i;
  char msg[MAXLEN];

  rooms[rno].curr_members[posn] = 0;  /* remove from room */
  users[posn].room_loc = -1;
  sprintf(msg, "You have left the %s", rooms[rno].name);
  dputs(users[posn].sockfd, msg);
  sprintf(msg, "l$ %s has left the room", users[posn].name);
  for (i=0; i < MAX_USERS; i++)
    if (rooms[rno].curr_members[i])
      dputs(users[i].sockfd, msg);
}


void who_cmd(User users[], int posn, Room rooms[])
/* Who is in the user's current room (including him)? */
{
  char msg[MAXLEN], details[MAXLEN];
  int i, rloc;

  rloc = users[posn].room_loc;
  sprintf(msg, "w$ People in the %s:  ", rooms[rloc].name);
  for (i=0; i < MAX_USERS; i++)
    if (rooms[rloc].curr_members[i]) {
      sprintf(details, "%s (%d.%d)  ", users[i].name, 
                           users[i].mood, users[i].form);
      strcat(msg, details);
    }
  dputs(users[posn].sockfd, msg);
}



void go_cmd(User users[], int posn, Room rooms[], char *args)
/* Go to another room. The destination room is extracted from
   the argument part of the command line.
*/
{
  char to_room[MAXLEN], msg[MAXLEN];
  int new_rm, old_rm;

  get_word(args, to_room);
  new_rm = get_rnum(rooms, to_room);   /* get rooms[] index of to_room 
*/
  if (new_rm != -1) {
    old_rm = users[posn].room_loc;
    leave_room(rooms, old_rm, users, posn);
    enter_room(rooms, new_rm, users, posn);
  }
  else {
    sprintf(msg, "No room called %s", to_room);
    dputs(users[posn].sockfd, msg);
  }
}


void get_word(char *args, char wd[])
/* A word is any text up to a space or null char */
{
  int i = 0;

  while ((*args != ' ') && (*args != '\0') && (i < MAXLEN)) {
    wd[i++] = *args;
    args++;
  }
  wd[i] = '\0';
}


int get_rnum(Room rooms[], char *rname)
/* Get the index in rooms[] of the room called rname */
{
  int i;
  for (i=0; i < MAX_ROOMS; i++)
    if (rooms[i].name[0] != '\0')
      if (strcmp(rooms[i].name, rname) == 0)
        return i;
  return -1;
}



void enter_room(Room rooms[], int rno, User users[], int posn)
/* Enter a room. Firstly tell everyone in the room, and then
   update rooms[] and users[]. The people in the room are also
   listed. */
{
  int i;
  char msg[MAXLEN];

  sprintf(msg, "e$ %s (%d.%d) has entered the %s\n", 
                 users[posn].name, users[posn].mood, users[posn].form,
                                      rooms[rno].name);
  for (i=0; i < MAX_USERS; i++)
    if (rooms[rno].curr_members[i])
      dputs(users[i].sockfd, msg);

  rooms[rno].curr_members[posn] = 1;      /* add to room */
  users[posn].room_loc = rno;

  sprintf(msg, "You have entered the %s", rooms[rno].name);
  dputs(users[posn].sockfd, msg);
  dputs(users[posn].sockfd, rooms[rno].desc);

  who_cmd(users, posn, rooms);
  dputs(users[posn].sockfd, "");  /* new line */
}



void help_cmd(User u, char *args)
/* Help command. Fairly basic, but better than nothing. */
{
  char hcmd[MAXLEN];

  get_word(args, hcmd);
  if (hcmd[0] == '\0') {   /* no argument to help */
    dputs(u.sockfd, "Type .help <cmd>, where <cmd> is one of:");
    dputs(u.sockfd, "  quit, who, go, private, recent, email, info, 
mood, form");
  }
  else if (strcmp(hcmd, "quit") == 0)
    dputs(u.sockfd, "Causes you to leave Chat");
  else if (strcmp(hcmd, "who") == 0)
    dputs(u.sockfd, "Lists the other people in the room");
  else if (strcmp(hcmd, "go") == 0)
    dputs(u.sockfd, ".go <room> takes you to <room>");
  else if (strcmp(hcmd, "private") == 0) {
    dputs(u.sockfd, ".private <name> puts you into private mode with 
<user>");
    dputs(u.sockfd, "<user> must do the same with you for private 
conversation to occur");
    dputs(u.sockfd, "\n.private puts you into public mode");
  }
  else if (strcmp(hcmd, "recent") == 0)
    dputs(u.sockfd, "Lists the recent public messages in the room");
  else if (strcmp(hcmd, "email") == 0)
    dputs(u.sockfd, ".email <text> sends <text> to the super-user");
  else if (strcmp(hcmd, "info") == 0)
    dputs(u.sockfd, "Gives information about all the Chat rooms");
  else if (strcmp(hcmd, "mood") == 0){
    dputs(u.sockfd, ".mood <type> changes your mood");
    dputs(u.sockfd, "  Recognised moods: ok, happy, sad");
  }
  else if (strcmp(hcmd, "form") == 0) {
    dputs(u.sockfd, ".form <type> changes your body form");
    dputs(u.sockfd, "  Recognised forms: newbie, dandy, hacker, 
manager, bobbie, woof, grad");
  }
  else
    dputs(u.sockfd, "Sorry, Chat does not understand that help 
request");
}



void private_cmd(User users[], int posn, char *args)
/* The private command has two functions. 1) If the user requests 
   .private <name> when in public mode then they go into provisional 
   private mode with <name>. Only when <name> also goes into private 
   mode with the user will private communication be allowed.
   2) If the user types .private when in private mode, then they
   go back into public mode, and the other person can no longer send 
   them private messages. This two-part aspect of private communication 
   means that both people must agree before private messages can 
happen.

   The code is lengthened by all the messages that have to be 
   sent to the two participants.
*/
{
  char msg[MAXLEN];
  int other_no;

  if (users[posn].private_contact == -1) { /* public mode --> private 
*/
    if ((other_no = get_uno(users, posn, args)) != -1) {
      users[posn].private_contact = other_no;
      if (users[other_no].private_contact != posn) {
        sprintf(msg, "%s is not yet in private mode with you", 
users[other_no].name);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "%s has asked to be in private mode with you", 
users[posn].name);
        dputs(users[other_no].sockfd, msg);
      }
      else {  
        sprintf(msg, "%s is in private mode with you", 
users[other_no].name);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "%s is in private mode with you", 
users[posn].name);
        dputs(users[other_no].sockfd, msg);
      }
    }
  }
  else {  /* private mode --> public */
    sprintf(msg, "%s is terminating private mode", users[posn].name);
    dputs(users[users[posn].private_contact].sockfd, msg);
    users[posn].private_contact = -1;
    dputs(users[posn].sockfd, "You are back in public mode");
  }
}



int get_uno(User users[], int posn, char *args)
/* Extract a name from the arguments string, and find the user with 
   that name in users[]. Return his/her position, or -1 as an error. */
{
  char uname[MAXLEN], msg[MAXLEN];
  int i;

  get_word(args, uname);
  if (uname[0] == '\0') {
    dputs(users[posn].sockfd, "You must give a name");
    return -1;
  }

  for (i=0; i < MAX_USERS; i++)
    if (strcmp(users[i].name, uname) == 0)
      break;
  if (i == MAX_USERS) {
    sprintf(msg, "%s is not in Chat at the moment", uname);
    dputs(users[posn].sockfd, msg);
    return -1;
  }
  return i;
}


void recent_cmd(User u, Room r)
/* List the recent public mesages in the user's current room.
   The oldest message is listed first.
*/
{
  int i, count;

  if (r.talk.size == 0)
    dputs(u.sockfd, "Sorry, no recent public messages in here");
  else {
    dputs(u.sockfd, "Recent public messages in this room: ");
    i = r.talk.first;
    count = r.talk.size;
    while (count > 0) {
      dputs(u.sockfd, r.talk.msg[i]);
      i = (i+1) % MAX_MSGS;
      count--;
    }
  }
}


void email_cmd(User u, char *args)
/* Send an e-mail message to the super-user.
   The idea is for users to make comments, report problems
   without the super-user having to constantly check a 
   comments file or reports file.  */
{
  char msg[MAXLEN];
  FILE *fp;

  sprintf(msg, "/usr/bin/mail -s \'Chat Message\' %s", SU_EMAIL);
  fp = popen(msg, "w");
  if (fp == NULL) {
    dputs(u.sockfd, "Sorry, mail is not available");
    fprintf(stderr, "popen() error for e-mail\n");
  }
  else {
    fprintf(fp, "%s\n", args);   /* the user's text */
    fclose(fp);
    dputs(u.sockfd, "Your message has been e-mailed to the super-
user");
  }
}


void info_cmd(User u, Room rooms[])
/* Obtain information about Chat rooms. This includes a
   list of all the room names with the number of occupants.
   The user's current room location is also printed.  */
{
  int i, sum, total;
  char msg[MAXLEN];

  dputs(u.sockfd, "Chat rooms with numbers of visitors");
  
  i = 0; total = 0;
  while ((rooms[i].name[0] != '\0') && (i < MAX_ROOMS)) {  /* end of 
rooms */
    sum = sum_members(rooms[i].curr_members);
    if (sum == 0)
      sprintf(msg, "%s: No one!", rooms[i].name);
    else
      sprintf(msg, "%s: %d", rooms[i].name, sum);
    dputs(u.sockfd, msg);
    total = total + sum;
    i++;
  }
  dputs(u.sockfd, "---------");
  sprintf(msg, "Total: %d", total);
  dputs(u.sockfd, msg);
  sprintf(msg, "You are in the %s", rooms[u.room_loc].name);
  dputs(u.sockfd, msg);
}


int sum_members(int curr_members[])
/* Return the number of people in the room. The curr_members array
   contains 0's and 1's, and the index position in curr_members
   corresponds to the index in the users array. So, a '1' in
   curr_members[3] means that users[3] is present in the room. 
*/
{
  int i, sum = 0;
  for (i=0; i < MAX_USERS; i++)
    sum = sum + curr_members[i];    /* contains 0 or 1 */
  return sum;
}


void mood_cmd(User users[], int posn, Room rooms[], char *args)
/* Set the user's mood to the one specified in the
     .mood <mood-type>
   command. Choice only depends on the first letter.
*/
{
  char mood[MAXLEN], msg[MAXLEN];
  int i, rloc;

  get_word(args, mood);
  lower_case(mood);

  for (i=0; i < MAXMOODS; i++)
    if (mood[0] == moods[i][0]) {
      users[posn].mood = (enum Mood)i;
      break;
    }

  if (i == MAXMOODS) {   /* specified mood isn't in moods array */
    dputs(users[posn].sockfd, "Chat doesn't understand that mood");
    dputs(users[posn].sockfd, "Possible moods are: ok, happy, sad");
  }
  else {
    sprintf(msg, "m$ %s's mood: %s", users[posn].name, moods[i]);
    rloc = users[posn].room_loc;
    for (i=0; i < MAX_USERS; i++)
      if (rooms[rloc].curr_members[i])
        dputs(users[i].sockfd, msg);
  }
}


void form_cmd(User users[], int posn, Room rooms[], char *args)
/* Set the user's body form to the one specified in the
     .form <form-type>
   command. Choice only depends on the first letter.
*/
{
  char form[MAXLEN], msg[MAXLEN];
  int i, rloc;

  get_word(args, form);
  lower_case(form);

  for (i=0; i < MAXFORMS; i++)
    if (form[0] == forms[i][0]) {
      users[posn].form = (enum Form)i;
      break;
    }

  if (i == MAXFORMS) {   /* specified body form isn't in forms array */
    dputs(users[posn].sockfd, "Chat doesn't understand that form");
    dputs(users[posn].sockfd, "Possible forms are: newbie, dandy, 
hacker, manager, bobbie, woof, grad");
  }
  else {
    sprintf(msg, "f$ %s's form: %s", users[posn].name, forms[i]);
    rloc = users[posn].room_loc;
    for (i=0; i < MAX_USERS; i++)
      if (rooms[rloc].curr_members[i])
        dputs(users[i].sockfd, msg);
  }
}



/* super-user commands start here. These include:
       shutdown, users, warn, close, and ban
   An ordinary user is not allowed to execute these commands. 
*/


void shutdown_cmd(User users[], int posn, Room rooms[])
/* Shutdown all the other users, apart from the super-user. */
{
  int i;

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have shutdown rights");
  else {
    for (i=0; i < MAX_USERS; i++)
      if ((users[i].sockfd != -1) && (i != posn)) {
        dputs(users[i].sockfd, "Sorry, Chat is closing 
down...Goodbye");
        remove_user(users, i, rooms);
      }
    dputs(users[posn].sockfd, "User Shutdown completed");
  }
}


void users_cmd(User users[], int posn, Room rooms[])
/* List information about all Chat users. This includes their
   users[] position, name, socket descriptor, private contact
   value, room name, user status (user=0, su=1), client site name,
   and last activity time.  */
{
  int i;
  char msg[MAXLEN], site[MAXLEN];
  time_t t;

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have users info rights");
  else {
    for (i=0; i < MAX_USERS; i++)
      if (users[i].sockfd != -1) {
        sprintf(msg, "\n  posn: %d \t name: %s", i, users[i].name);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "  sockfd: %d \t private_contact: %d", 
                 users[i].sockfd, users[i].private_contact);
        dputs(users[posn].sockfd, msg);
        get_site(&(users[i].addr), site);
        sprintf(msg, "  room: %s \t status: %d \t addr: %s", 
                 rooms[users[i].room_loc].name, users[i].status, site);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "  mood: %d \t form: %d", users[i].mood, 
users[i].form);
        dputs(users[posn].sockfd, msg);
        sprintf(msg, "  act_time: %s", ctime(&(users[i].act_time)));
        dputs(users[posn].sockfd, msg);     /* newline added by ctime() 
*/
      }
    t = time(NULL);
    sprintf(msg, "Current time: %s", ctime(&t) );
    dputs(users[posn].sockfd, msg);
  }
}


void warn_cmd(User users[], int posn, char *args)
/* Warn a user. The message comes from args which
   contains the string "user message".  */
{
  int user_no;
  char msg[MAXLEN];

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have warning rights");
  else {
    if ((user_no = get_uno(users, posn, args)) != -1) {
      sprintf(msg, "Super-User WARNING: %s", args);
      dputs(users[user_no].sockfd, msg);
      sprintf(msg, "%s has been warned", users[user_no].name);
      dputs(users[posn].sockfd, msg);
    }
  }
}


void close_cmd(User users[], int posn, Room rooms[], char *args)
/* Close down a user */
{
  int user_no;

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have close rights");
  else {
    if ((user_no = get_uno(users, posn, args)) != -1) {
      dputs(users[user_no].sockfd, 
             "Sorry, but the Super-User is closing your Chat 
connection... Goodbye");
      remove_user(users, user_no, rooms);
    }
  }
}


void ban_cmd(User users[], int posn, Room rooms[], char *args)
/* Ban a user; actually it is their site which is banned by
   being added to the BANNED file. A side-effect of banning
   is that the user is disconnected.  */
{
  int user_no;
  char msg[MAXLEN], site[MAXLEN];

  if (users[posn].status != SU)
    dputs(users[posn].sockfd, "You do not have banning rights");
  else {
    if ((user_no = get_uno(users, posn, args)) != -1) {
      get_site( &(users[user_no].addr), site);
      add_to_ban(site);
      dputs(users[user_no].sockfd, 
                "The Super-User has banned you from Chat... Goodbye");
      sprintf(msg, "%s has been banned", users[user_no].name);
      dputs(users[posn].sockfd, msg);
      remove_user(users, user_no, rooms);
    }
  }
}


void add_to_ban(char *site)
/* Add site to the BANNED file */
{
  FILE *fp;

  if ((fp = fopen(BANNED, "a")) == NULL)
    fprintf(stderr, "Could not append to %s\n", BANNED);
  else {
    fprintf(fp, "%s\n", site);
    fclose(fp);
  }
}


void talk(User users[], int posn, char *args, Room rooms[])
/* General talk (i.e. an input line that does not start with a '.'). 
   A public message is sent to everyone else in the room
   prefixed with the users name, and is also stored in the
   recent messages queue for that room. 
   A private message is only sent to the other person, and is not 
   stored in the recents queue.
*/
{
  char msg[MAXLEN];
  int i, rloc;

  /* record public and private (!) talk in the log file */
  sprintf(msg, "talk: %s: %s\n", users[posn].name, args);
  fputs(msg, stdout);
  write_log(msg);

  rloc = users[posn].room_loc;
  sprintf(msg, "t$ %s: %s", users[posn].name, args);
  
  if (users[posn].private_contact == -1) {   /* public message */
    for (i=0; i < MAX_USERS; i++)
      if ((rooms[rloc].curr_members[i]) && (i != posn)) 
        dputs(users[i].sockfd, msg);
    add_msg(&(rooms[rloc].talk), msg);
  }
  else {              /* private message */
    i = users[posn].private_contact;
    if (users[i].private_contact == posn)   /* both are private */
      dputs(users[i].sockfd, msg);
    else {
      sprintf(msg, "Sorry, but %s is not in private mode with you",
                           users[i].name);
      dputs(users[posn].sockfd, msg);
    }
  }
}


void add_msg(Messages *t, char *msg)
/* t points to a circular queue of MAX_MSGS elements */
{
  if (t->size == MAX_MSGS) {   /* full; so delete first (oldest) 
message */
    free(t->msg[t->first]);
    t->msg[t->first] = NULL;
    t->first = (t->first + 1) % MAX_MSGS;
    (t->size)--;
  }

  t->msg[t->last] =             /* add msg to the end of the queue */
    (char *) malloc((strlen(msg)+1)*sizeof(char));
  strcpy(t->msg[t->last], msg);
  t->last = (t->last + 1) % MAX_MSGS;
  t->size++;
}



void check_activity(User users[], int posn, Room rooms[])
/* Check to see how inactive a user has been by comparing
   their act_time field in users[] with the current time.
   If they have been inactive for more than CLOSE_TIME seconds
   then give them a warning, and make their act_time REPRIEVE_TIME
   seconds later. The next time that they are inactive for more
   than CLOSE_TIME seconds, they are disconnected.

   The effectivness of this code is reduced by the way that the Chat 
server
   suspends waiting for input. This means that a user may be 
   able to stay connected for quite some time if there has been no
   other user input, and so no chance to call check_activity().
*/
{
  char msg[MAXLEN];
  double inactive_time;

  if (users[posn].status != SU) {           /* never check super-user 
*/
    inactive_time = difftime(time(NULL), users[posn].act_time);
    if (inactive_time > CLOSE_TIME) {       /* inactive too long */
      if (!users[posn].warned) {            /* not warned yet */
        sprintf(msg, "Chat WARNING: You have been inactive for over %d 
mins, do something NOW!", CLOSE_TIME/60);
        dputs(users[posn].sockfd, msg);
        users[posn].warned = 1;
        users[posn].act_time = users[posn].act_time + REPRIEVE_TIME;
      }
      else {    /* warned before */
        dputs(users[posn].sockfd, "Chat Message: No activity means... 
Goodbye");
        remove_user(users, posn, rooms);
      }
    }
  }
}


/* socket I/O */

int dputs(int sfd, char *msg)
/* Write str (msg and a newline) to the sfd socket. 
   Return the number of characters
   actually written, although this feature is not used in the
   rest of the Chat code. If no characters were written then 
   there is something wrong with the socket link.
*/
{
  int i, len;
  char *str;

  len = strlen(msg);
  str = (char *) malloc( sizeof(char)*(len+2) );   /* for '\n' */
  strcpy(str, msg);
  str[len] = '\n';
  str[len+1] = '\0';
  i = write(sfd, str, len+1);

  if (i < 0)
    fprintf(stderr, "Write error to socket %d for msg:\"%s\"\n", sfd, 
str);
  return (i >= 0) ? (i+1) : i;
}


int dgets(int sfd, char msg[])
/* Read at most MAXLEN characters from the sfd socket.
   read() returns the number of characters actually read,
   which is used to detect some errors. 
   The input is read into buf[] which is copied into msg[]
   with any non-printable characters removed (e.g. control
   characters). This may mean that msg[] is empty. The length
   of msg[] is returned.
*/
{
  char buf[MAXLEN]; 
  int i, len = 0, plen = 0;

  len = read(sfd, buf, MAXLEN);
  if (len == 0) {
    fprintf(stderr, "Socket %d has unexpectedly closed\n", sfd);
    return -1;
  }
  if (len == -1) { 
    fprintf(stderr, "Socket %d read error\n", sfd);
    return -1;
  }

  for (i = 0; i < len; i++)     /* get rid of any nasty chars */
    if (isprint(buf[i]))
      msg[plen++] = buf[i];
  msg[plen] = '\0'; 

  return plen;        /* length of printable characters in msg */
}



/* for logging */
/* Currently this is used to log three forms of activity:
    1. Users entering Chat
    2. Users leaving Chat
    3. General talk (both public and private)
*/

void write_log(char *msg)
/* Append msg to the end of the LOG file */
{
  FILE *fp;

  if ((fp = fopen(LOG, "a")) == NULL)
    fprintf(stderr, "Could not append to %s\n", LOG);
  else {
    fputs(msg, fp);
    fclose(fp);
  }
}