/*
 * Copyright (c) 1990 The Ohio State University.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by The Ohio State University and its contributors''
 * in the documentation or other materials provided with the distribution
 * and in all advertising materials mentioning features or use of this
 * software. Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "cons.h"
#include <sys/file.h>
#include <stdio.h>
#include <fcntl.h>
#include <termio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <pwd.h>

#define puke(msg) { perror(msg); exit(-1); }

#define drop(q) {tyme=time(NULL);\
    fprintf(clog,"%-9s%-14.14s session ended  %-14s %s",\
        ctbl[q].name,ctbl[q].from,\
        console[group][ctbl[q].mmb].server,ctime(&tyme));\
    fflush(clog);\
    close(ctbl[q].fd);\
    for(zyx= q ; zyx<nc; ++zyx){\
      ctbl[zyx]=ctbl[zyx+1];cnct_port[zyx]=cnct_port[zyx+1];\
    } --nc; --i; goto next_conn;}

/* flags bit values */
#define IC1   0001  /* first interrupt character received */
#define IC2   0002  /* second interrupt character received */
#define HC1   0010  /* first break character received */
#define HC2   0020  /* second break character received */
#define CES   0100  /* change escape sequence command received */
#define CC1   0200  /* first char of new escape sequence received */

#ifndef FD_SET
#define FD_ZERO(a) {*(a)=0;}
#define FD_SET(d,a) {*(a) |= (1 << (d));}
#define FD_ISSET(d,a) (*(a) & (1 << (d)))
#define OLDSEL
#endif

struct sockaddr_in master_port, lstn_port, response_port;
struct sockaddr_in cnct_port[MAXMEMB*3], in_port;
struct hostent *hp;
int sfd, sin, m[MAXGRP], procnum, zyx;
char host[64], rootpass[32], conspass[32];
int glargc;
char **glargv;
FILE *fp, *clog;
struct consent {  /* console information */
  char server[32];   /* server name */
  char dfile[32];    /* device file */
  char lfile[32];    /* log file    */
  int port;
  int pid;
} console[MAXGRP][MAXMEMB];

main(argc,argv)
int argc;
char *argv[];
{
  char ch[BUFSIZ], serv[32], dev[32], log[32];
  int group;
  long tyme;

  glargc=argc;
  glargv=argv;

  /* get the root pw entry */
  if (PASSENTRY != NULL) strcpy(conspass,getpwnam(PASSENTRY)->pw_passwd);
  else conspass[0]='\0';
  strcpy(rootpass,getpwnam("root")->pw_passwd);

  /* read the config file */
  if ((fp=fopen(CONFIG,"r")) == NULL) puke ("fopen");
  group=0; procnum=0;
  while(fgets(ch,127,fp) != NULL) {
    if (ch[0] != '#') {
      sscanf(ch,"%[^:]:%[^:]:%[^:]:%d\n",serv,dev,log,&group);
      if (group > procnum) {
        procnum = group;
        m[group]=0;
      }
      strcpy(console[group][m[group]].server,serv);
      strcpy(console[group][m[group]].dfile,dev);
      strcpy(console[group][m[group]].lfile,log);
      ++m[group];
fprintf(stderr,"%s  %s  %s  %d\n",serv,dev,log,group);
    }
  }
  fclose(fp);
fprintf(stderr,"done reading config file\n");

  /* open the console usage log file */
  if ((clog=fopen(CLOG,"a")) == NULL) puke ("fopen");
  tyme=time(NULL);
  fprintf(clog,"Starting Console Server at %s",ctime(&tyme));
  fflush(clog);

  gethostname(host,63);
  hp=gethostbyname(host);
  if (hp == NULL) puke ("gethostbyname");

  /* spawn all the children */
  for (group=1; group<=procnum; ++group) {
    spawn(group);                 /* child never returns */
  }

  master();
  exit(1); /* should never get here */
}

/* this routine is used by the master console server process */
master()
{
  char *ptr, serv[32], ch[BUFSIZ];
  int msfd, so, tmpntr, i, j, prnum, found;
  long tyme;
  void fixkids(), killkids();

  /* set up signal handler */
  signal(SIGCHLD,fixkids);
  signal(SIGTERM,killkids);

  /* set up port for master to listen on */
  bzero((char *)&master_port, sizeof(master_port)); /*socket for listening*/
  bcopy(hp->h_addr, (char *)&master_port.sin_addr, hp->h_length);
  master_port.sin_port = PORT;
  master_port.sin_family = hp->h_addrtype;

  if ((msfd=socket(AF_INET,SOCK_STREAM,0)) < 0) puke("socket");
  if (setsockopt(msfd,SOL_SOCKET,SO_REUSEADDR,0,0)<0) puke("setsockopt");
  if (bind(msfd, &master_port, sizeof(master_port))<0) puke("bind");
  if (listen(msfd,SOMAXCONN)<0) puke("listen");
  so=sizeof(master_port);

  while(1) {
  sin=accept(msfd,&response_port,&so);

  /* handle the connection (port lookup) */
  ptr=ch;
  i=sizeof(ch);
  do {
    /* thanks to John P. Linderman <jpl@allegra.att.com> for buffer check */
    if (--i <= 0) {
      fprintf(stderr,"Master port lookup - buffer overrun.\n");
      goto done;
    }
    if (read(sin,ptr,1) == 0) {
      fprintf(stderr,"Master port lookup - connection lost.\n");
      goto done;
    }
  } while (*ptr++ != '\n');
  *ptr='\0';
  sscanf(ch,"%s",serv);

  if (strcmp(serv,"who") == 0) {
    for (i=1; i<=procnum; ++i) {
      sprintf(ch,"%d:",console[i][0].port);
      write(sin,ch,strlen(ch));
    }
    sprintf(ch,"\n");
    write(sin,ch,strlen(ch));
    goto done;
  }
  found=0;
  for (i=1; i<=procnum; ++i) {
    for (j=0; j<m[i]; ++j) {
      if (strncmp(serv,console[i][j].server,strlen(serv)) == 0) {
        prnum=console[i][0].port;
        ++found;
      }
    }
  }
  if (found == 1) {
    sprintf(ch,"%d\n",prnum);
    write(sin,ch,strlen(ch));
    goto done;
  }
  else if (found > 1) {
    sprintf(ch,"Ambigous server abbreviation\r\n");
    write(sin,ch,strlen(ch));
  }
  else {
    sprintf(ch,"Server not found\r\n");
    write(sin,ch,strlen(ch));
  }

  done: close(sin);
  }
}

/* routine used by the child processes.  Most of it is UGLY escape sequence
   parsing.  All of it is squirrely code, for which I most humbly apologize */
kiddie(group)
int group;
{
  int i, j, k;
  char *ptr;
  u_char rem_addr[4];
  char ich1='', ich2='', hch1='l',  hch2='1';
  struct sgttyb sty;
  int s, nf, nr, so, tmpntr, softcar=1;
#ifdef OLDSEL
  long rmask, wmask;
#else
  fd_set rmask, wmask;
#endif
  int lfd[MAXMEMB], tfd[MAXMEMB], nc=0;
  char ch[BUFSIZ], och[256], serv[32], comm[32], user[32];
  char pass[32], args[128], *argp, *end;
  int argl;
  long tyme;

  struct cnctbl {    /* Connection Information: */
    int fd;          /* file descriptor */
    int mmb;         /* which member in group */
    char wren;       /* (client) write enable flag */
    char name[9];    /* name of user */
    char from[32];   /* location of user */
    long tym;        /* time of connect */
    char flags;      /* flags for interrupt control chars and halt chars */
    char ic[2];      /* two character escape sequence */
  } ctbl[MAXMEMB*3];


  /* turn off signals that master() turned on (only matters if respawned) */
  signal(SIGCHLD,SIG_DFL);
  signal(SIGTERM,SIG_DFL);
  so=sizeof(lstn_port);

  sprintf(args,"conserver");

  /* open all the files we need for the consoles in our group */
  for (i=0; i < m[group]; ++i) {
    lfd[i] = open (console[group][i].lfile,O_RDWR|O_CREAT|O_APPEND,0666);
    tfd[i] = open (console[group][i].dfile,O_RDWR|O_NDELAY);
    ioctl(tfd[i],TIOCSSOFTCAR,&softcar);
    ioctl(tfd[i],TIOCGETP,&sty);
    sty.sg_flags = (sty.sg_flags | RAW);
    sty.sg_flags = (sty.sg_flags & ~ECHO);
    sty.sg_flags = (sty.sg_flags & ~CRMOD);
    sty.sg_flags = (sty.sg_flags | TANDEM);
    sty.sg_ispeed=B9600;
    sty.sg_ospeed=B9600;
    ioctl(tfd[i],TIOCSETP,&sty);
    strcat(args," ");
    strcat(args,console[group][i].server);
  }

  /* reset the arg list - This is not a safe thing to do.  It works on our
       UNIX.  It may not work elsewhere. If the provided args for
       conserver aren't long enough, the new args will be truncated */
  if (glargc > 1) {
    argp=glargv[0];
    /* *argp++='-';  This would make it append the real name at end in ps */
    end = glargv[glargc-1]+strlen(glargv[glargc-1]);
    argl=strlen(args);
    if (argl > end-argp-2) {
      argl=end-argp-2;
      args[argl]='\0';
    }
    strcpy(argp,args);
    argp += argl;
    while (argp < end)
      *argp++ = ' ';
  }

  /* the MAIN loop of the console server */
  FD_ZERO(&wmask);
  while (1) {
    /* set up stuff for the select() call */
    FD_ZERO(&rmask);
    FD_SET(sfd,&rmask);
    for (i=0; i<m[group]; ++i)
      FD_SET(tfd[i],&rmask);
    for (i=0; i<nc; ++i)
      FD_SET(ctbl[i].fd,&rmask);

    nf = select(getdtablesize(),&rmask,&wmask,&wmask,NULL);

    /* anything from console? */
    for (i=0; i<m[group]; ++i) {
      if (FD_ISSET(tfd[i],&rmask)) {
        if ((nr=read(tfd[i],ch,BUFSIZ)) == 0) {   /* read terminal line */

          /* carrier lost */
fprintf(stderr,"Carrier Lost!\n");

        }
        else {
          for (j=0; j<nr; ++j) ch[j]&=127;    /* clear parity */
          write(lfd[i],ch,nr);  /* write to log file */
          for (j=0; j<nc; ++j) /* write to all connections on this server */
            if (ctbl[j].mmb == i)
              write(ctbl[j].fd,ch,nr);
        }
      }
    }


    /* anything from a connection? */
    for (i=0; i<nc; ++i) {
      if (FD_ISSET(ctbl[i].fd,&rmask)) {
        if ((nr=read(ctbl[i].fd,ch,BUFSIZ)) == 0) {  /* read connection */
          /* reached EOF - close connection */
          drop(i); 
        }
        else {
          tmpntr=0;
          for (j=0; j<nr; ++j) {
            /* are we in middle of halt sequence? */
            if (ctbl[i].flags & HC1) {
              if (ch[j] == hch2) {
                ctbl[i].flags=0;
                tmpntr=j+1;
                if (ctbl[i].wren)
                {
                  ioctl(tfd[ctbl[i].mmb],TCSBRK,NULL);

                  /*    THE OLD WAY
                  //  ioctl(tfd[ctbl[i].mmb],TIOCSBRK,NULL);
                  //  sleep(3);
                  //  ioctl(tfd[ctbl[i].mmb],TIOCCBRK,NULL);
                  */
                }
                else
                {
                  sprintf(och,"\r\nYou must be fully attached to halt machine.\r\n");
                  write(ctbl[i].fd,och,strlen(och));
                }
              }
              else {
                ctbl[i].flags = 0;
                if (ctbl[i].wren) {
                  write(tfd[ctbl[i].mmb],ctbl[i].ic,2); /* write intrpt chars */
                  write(tfd[ctbl[i].mmb],&hch1,1);  /* write halt char 1 */
                  tmpntr=j;
                }
              }
            }
            /* are we in middle of changing escape sequence? */
            else if (ctbl[i].flags & CES) {
              if (ctbl[i].flags & CC1) {  /* already have first one */
                tmpntr=j+1;
                ctbl[i].ic[1]=ch[j];
                ctbl[i].flags=0;
              }
              else {
                ctbl[i].ic[0]=ch[j];
                ctbl[i].flags |= CC1;
                tmpntr=j+1;
              }
            }
            /* do we have any of the escape sequence? */
            else if (ctbl[i].flags & IC1) { /* already have first char */
              if (ctbl[i].flags & IC2) { /* already have both chars */
                switch (ch[j]) {
                  case 'a' : /* attach */
                    for (k=0; k<nc; ++k) {
                      if (ctbl[k].mmb == ctbl[i].mmb && ctbl[k].wren && i!=k) {
                        sprintf(och,"\r\nSomeone is already attached.\r\n");
                        write(ctbl[i].fd,och,strlen(och));
                        break;
                      }
                    }
                    if (k == nc) {
                      tyme=time(NULL);
                      fprintf(clog,"%-9s%-14.14s  change attach %-14s %s",
                          ctbl[i].name,ctbl[i].from,
                          console[group][ctbl[i].mmb].server,ctime(&tyme));
                      fflush(clog);
                      ctbl[i].wren=1;
                      sprintf(och,"\r\n[attached]\r\n");
                      write(ctbl[i].fd,och,strlen(och));
                    }
                    break;
                  case 'f' : /* force attach */
                    for (k=0; k<nc; ++k) {
                      if (ctbl[k].mmb == ctbl[i].mmb && ctbl[k].wren && i!=k) {
                        ctbl[k].wren=0;
                        sprintf(och,"\r\nYour attach has been forced off by %s@%s.\r\n",ctbl[i].name,ctbl[i].from);
                        write(ctbl[k].fd,och,strlen(och));
                        sprintf(och,"You are now in 'spy' mode.\r\n");
                        write(ctbl[k].fd,och,strlen(och));
                        tyme=time(NULL);
                        fprintf(clog,"%-9s%-14.14s  forced to spy %-14s %s",
                            ctbl[i].name,ctbl[i].from,
                            console[group][ctbl[i].mmb].server,ctime(&tyme));
                        fflush(clog);
                      }
                    }
                    tyme=time(NULL);
                    fprintf(clog,"%-9s%-14.14s  change force  %-14s %s",
                        ctbl[i].name,ctbl[i].from,
                        console[group][ctbl[i].mmb].server,ctime(&tyme));
                    fflush(clog);
                    ctbl[i].wren=1;
                    sprintf(och,"\r\n[attached]\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    break;
                  case 's': /* spy mode */
                    tyme=time(NULL);
                    fprintf(clog,"%-9s%-14.14s  change spying %-14s %s",
                        ctbl[i].name,ctbl[i].from,
                        console[group][ctbl[i].mmb].server,ctime(&tyme));
                    fflush(clog);
                    ctbl[i].wren=0;
                    sprintf(och,"\r\n[spying]\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    break;
                  case '.':  /* disconnect */
                  case '':
                  case '':
                    ioctl(tfd[ctbl[i].mmb],TIOCGETP,&sty);  /* turn flow */
                    sty.sg_flags = (sty.sg_flags | TANDEM); /* control   */
                    ioctl(tfd[ctbl[i].mmb],TIOCSETP,&sty);  /* (back) on */
                    nr=0; tmpntr=0;
                    drop(i);
                    break;
                  case '': /* background */
                  case 'b':
                    sprintf(och,"\r\nNot implemented.\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    break;
                  case 'c':
                  case 19 :   /* ctrl-S */
                    ioctl(tfd[ctbl[i].mmb],TIOCGETP,&sty);
                    if (sty.sg_flags & TANDEM) {  /* if TANDEM is set */
                      sty.sg_flags = (sty.sg_flags & ~TANDEM);
                      sprintf(och,"\r\n[Control flow OFF]\r\n");
                      write(ctbl[i].fd,och,strlen(och));
                    }
                    else {
                      sty.sg_flags = (sty.sg_flags | TANDEM);
                      sprintf(och,"\r\n[Control flow ON]\r\n");
                      write(ctbl[i].fd,och,strlen(och));
                    }
                    ioctl(tfd[ctbl[i].mmb],TIOCSETP,&sty);
                    break;
                  case 'k': /* redefine escape keys */
                  case 'e':
                  case 'r':
                    ctbl[i].flags |= CES;
                    break;
                  case 'w': /* who */
                    sprintf(och,"\r\nok\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    for (k=0; k<nc; ++k) {
                      if (ctbl[k].mmb == ctbl[i].mmb) {
                        sprintf(och,"%-10s%-16.16s",ctbl[k].name,ctbl[k].from);
                        write(ctbl[i].fd,och,strlen(och));
                        if (ctbl[k].wren)
                          sprintf(och,"attached  since   %s\r",ctime(&(ctbl[k].tym)));
                        else
                          sprintf(och,"spying    since   %s\r",ctime(&(ctbl[k].tym)));
                        write(ctbl[i].fd,och,strlen(och));
                      }
                    }
                    break;
                  case 'l': /* halt character 1 */
                    ctbl[i].flags |= HC1;
                    break;
                  case 'h': /* help */
                  case '?':
                    sprintf(och,"\r\nCommand summary:\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"a             attach\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"f             force attach\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"s             unattach and spy\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,".    [^D][^C] disconnect\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"c    [^S]     toggle control flow (on by default)\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"l1            halt server\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"w             who else is connected\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"e    [r][k]   redefine escape sequence\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"h    [?]      help\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    sprintf(och,"<CR> [spc] continue\r\n");
                    write(ctbl[i].fd,och,strlen(och));
                    break;
                  case ' ':  /* abort escape sequence */
                  case '\n':
                    break;
                  default: /* false alarm, send escape sequence */
                    ctbl[i].flags = 0;
                    if (ctbl[i].wren) {
                      write(tfd[ctbl[i].mmb],ctbl[i].ic,2); /* interupt chars */
                      write(tfd[ctbl[i].mmb],&ch[j],1); /*write current char*/
                    }
                    break;
                }
                tmpntr=j+1;
                if (!(ctbl[i].flags & (HC1|CES) )) ctbl[i].flags=0;
              }
              else { /* only have first char */
                if (ch[j] == ctbl[i].ic[1]) {
                  ctbl[i].flags |= IC2;
                  tmpntr=j+1;
                }
                else {
                  ctbl[i].flags = 0;
                  tmpntr=j;
                  if (ctbl[i].wren)
                    write(tfd[ctbl[i].mmb],ctbl[i].ic,1); /*write intrpt char*/
                }
              }
            }
            /* is current char the first escape char? */
                /* the fact that this is an 'else' means we can NOT interrupt*/
                /* the escape sequence with the escape sequence */
                /* this has to be this way, because the first character */
                /* of the seqnc may be the same as the end of a command */
            else if (ch[j] == ctbl[i].ic[0]) {
              ctbl[i].flags |= IC1;
              tmpntr=j+1;
              if (ctbl[i].wren)
                write(tfd[ctbl[i].mmb] ,ch,j);  /* write up to interupt char */
            }
          }
          if (ctbl[i].wren && !ctbl[i].flags) /* if writable & no interrupt */
            write(tfd[ctbl[i].mmb],ch+tmpntr,nr-tmpntr);  /*write to terminal*/
        }
      }
      next_conn: continue;  /* re-entry point after a connection is dropped */
    }


    /* anything to accept? */
    if (FD_ISSET(sfd,&rmask)) {
      /* accept new connections and deal with them */
      ctbl[nc].fd=accept(sfd,&(cnct_port[nc]),&so);
      if (ctbl[nc].fd < 0) {
        if (errno == EMFILE)  /* too many open descriptors */
          continue;   /* continue the while (1) */
        else puke("accept");
      }
      ctbl[nc].flags=0;

      so=sizeof(lstn_port);
      getpeername(ctbl[nc].fd,&in_port,&so);
      bcopy(&in_port.sin_addr, rem_addr, hp->h_length);
      /* I meant to use this information to verify the source machine as being
         local.  Never got around to it. */

      /* figure out command and server */
      ptr=ch;
      i=sizeof(ch);
      do {
        if (--i <= 0) {
          fprintf(stderr,"Connection request - buffer overrun.\n");
          ch[0]='\n';
          *ptr='\n';
	}
        else if (read(ctbl[nc].fd,ptr,1) == 0) {
          fprintf(stderr,"Connection request - connection lost.\n");
          ch[0]='\n';
          *ptr='\n';
        }
      } while (*ptr++ != '\n');
      *ptr='\0';
      if (ch[0] == '\n') continue;    /* if connection was lost */
      pass[0]=NULL;
      sscanf(ch,"%[^:]:%[^:]:%[^:]:%[^:]:%s\n",serv,comm,ctbl[nc].name,ctbl[nc].from,pass);

      if (comm[0] != 'w')
        if ((strcmp(rootpass,crypt(pass,rootpass)) != 0)
        && (strcmp(conspass,crypt(pass,conspass)) != 0)) {
fprintf(stderr,"Bad password attempt\r\n");
          sprintf(och,"Sorry.\r\n");
          write(ctbl[nc].fd,och,strlen(och));
          close(ctbl[nc].fd);
          continue;
        }

      ctbl[nc].ic[0]=ich1; ctbl[nc].ic[1]=ich2;

      for (i=0; i<m[group]; ++i)
        if (strncmp(serv,console[group][i].server,strlen(serv)) == 0) {
          ctbl[nc].mmb=i;
          break;
        }

      switch(comm[0]) {
        case 'a' :
        case 'A' :
          ctbl[nc].wren=1;
          sprintf(och,"ok\r\n");
          for (i=0; i<nc; ++i)
            if (ctbl[i].mmb == ctbl[nc].mmb && ctbl[i].wren) {
              sprintf(och,"ok\r\n[Someone is already attached - spying]\r\n");
              ctbl[nc].wren=0;
              break;
            }
          ctbl[nc].tym=time(NULL);
          write(ctbl[nc].fd,och,strlen(och));
          if (comm[0] == 'A') replay(lfd[ctbl[nc].mmb],ctbl[nc].fd);
          fprintf(clog,"%-9s%-14.14s connect %s %-14s %s",
              ctbl[nc].name,ctbl[nc].from,((ctbl[nc].wren)?"attach":"spying"),
              console[group][ctbl[nc].mmb].server,ctime(&(ctbl[nc].tym)));
          fflush(clog);
          ++nc;
          break;
        case 'f' :
        case 'F' :
          for (i=0; i<nc; ++i)
            if (ctbl[i].mmb == ctbl[nc].mmb && ctbl[i].wren) {
              ctbl[i].wren=0;
              sprintf(och,"Your attach has been forced off by %s@%s.\r\n",ctbl[nc].name,ctbl[nc].from);
              write(ctbl[i].fd,och,strlen(och));
              sprintf(och,"You are now in 'spy' mode.\r\n");
              write(ctbl[i].fd,och,strlen(och));
              tyme=time(NULL);
              fprintf(clog,"%-9s%-14.14s  forced to spy %-14s %s",
                  ctbl[i].name,ctbl[i].from,
                  console[group][ctbl[i].mmb].server,ctime(&tyme));
              fflush(clog);
            }
            ctbl[nc].wren=1;
            ctbl[nc].tym=time(NULL);
            sprintf(och,"ok\r\n");
            write(ctbl[nc].fd,och,strlen(och));
            if (comm[0] == 'F') replay(lfd[ctbl[nc].mmb],ctbl[nc].fd);
            fprintf(clog,"%-9s%-14.14s connect forced %-14s %s",
                ctbl[nc].name,ctbl[nc].from,
                console[group][ctbl[nc].mmb].server,ctime(&(ctbl[nc].tym)));
            fflush(clog);
            ++nc;
          break;
        case 's' :
        case 'S' :
          ctbl[nc].tym=time(NULL);
          ctbl[nc].wren=0;
          sprintf(och,"ok\r\n");
          write(ctbl[nc].fd,och,strlen(och));
          if (comm[0] == 'S') replay(lfd[ctbl[nc].mmb],ctbl[nc].fd);
          fprintf(clog,"%-9s%-14.14s connect spying %-14s %s",
              ctbl[nc].name,ctbl[nc].from,
              console[group][ctbl[nc].mmb].server,ctime(&(ctbl[nc].tym)));
          fflush(clog);
          ++nc;
          break;
        case 'w' :
          sprintf(och,"ok\r\n");
          write(ctbl[nc].fd,och,strlen(och));
          for (i=0; i<nc; ++i) {
            if (strcmp(serv,"who") == 0 || ctbl[i].mmb == ctbl[nc].mmb) {
              sprintf(och,"%-9s%-14.14s",ctbl[i].name,ctbl[i].from);
              write(ctbl[nc].fd,och,strlen(och));
              if (ctbl[i].wren)
                sprintf(och,"attached to %-14s %s\r",console[group][ctbl[i].mmb].server,ctime(&(ctbl[i].tym)));
              else
                sprintf(och,"spying  on  %-14s %s\r",console[group][ctbl[i].mmb].server,ctime(&(ctbl[i].tym)));
              write(ctbl[nc].fd,och,strlen(och));
            }
          }
          close(ctbl[nc].fd);
          break;
        default:
          sprintf(och,"Command not understood.\n");
          write(ctbl[nc].fd,och,strlen(och));
          break;
      }
    }
  }
}

/* create a child process */
spawn(gr)
int gr;
{
  int pid, so;

  /* fork off a process for each group with an open socket for connections */
  bzero((char *)&lstn_port, sizeof(lstn_port)); /*socket for listening*/
  bcopy(hp->h_addr, (char *)&lstn_port.sin_addr, hp->h_length);
  lstn_port.sin_port = 0;
  lstn_port.sin_family = hp->h_addrtype;

  if ((sfd=socket(AF_INET,SOCK_STREAM,0)) < 0) puke("socket");
  if (setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,0,0)<0) puke("setsockopt");
  if (bind(sfd, &lstn_port, sizeof(lstn_port))<0) puke("bind");
  /* listen on socket prepared by master */
  if (listen(sfd,SOMAXCONN)<0) puke("listen");
  so=sizeof(lstn_port);

  getsockname(sfd,&lstn_port, &so);
  console[gr][0].port = lstn_port.sin_port;

fprintf(stderr,"group %d port number %d\n",gr,lstn_port.sin_port);

  if ((pid=fork()) != 0) {            /* if not child */
    close(sfd);
    console[gr][0].pid=pid;
    return(pid);
  }
  else kiddie(gr);
  exit(1); /* should never get here */
}

/* check all the kids and respawn as needed.  Called when master process
   receives SIGCHLD */
void fixkids()
{
  int gr;
  long tyme;

  for (gr=0; gr <= procnum; ++gr) {
    if (kill(console[gr][0].pid,0) == -1)
      if (errno == ESRCH) {
        spawn(gr);      /* if kid is dead, start another */
        tyme=time(NULL);
        fprintf(clog,"Restarting group %d at %s",gr,ctime(&tyme));
        fflush(clog);
      }
  }
  return;
}

/* kill all the kids and exit.  Called when master process receives SIGTERM */
void killkids()
{
  int gr;
  long tyme;

  signal(SIGCHLD,SIG_DFL);
  for (gr=0; gr <= procnum; ++gr) {
    kill(console[gr][0].pid,SIGTERM);
  }
  tyme=time(NULL);
  fprintf(clog,"Killed at %s",ctime(&tyme));
  fflush(clog);
  exit(0);
}

/* replay last 20 lines of the log file upon connect to kiddie */
/*  (blocks server while running) */
replay(ld,cd)
int ld, cd;
{
  int m, n, i, cr=0, tot=0;
  char c, dun=0, bf[BUFSIZ];

  n=lseek(ld,-1,L_XTND);  /* go to last character */
  if (n <= 0) return(0);
  while (!dun) {
    read(ld,&c,1);
    if (c == '\n') ++cr;
    if (cr >= 21) dun=1;
    else if((m=lseek(ld,-2,L_INCR)) == 0) dun=1;
    else if (++tot > 2000) dun=1;  /* guard against lack of cr's */
  }
  while (read(ld,&c,1)>0)
    write(cd,&c,1);
}