#define I_SYS
#define I_SOCKET
#define I_ERRNO
#define I_IOCTL
#define I_STRING

#include "includes.h"

#include "debug.h"
#include <sys/stat.h>
#include <arpa/inet.h>

#ifndef S_ISREG
#define S_ISREG(a) (((a) & S_IFMT) == S_IFREG)
#define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
#endif

#ifdef X_DEBUG
static int x_debug = 0;
#endif

#ifndef USE_SETGID
#define swapgid setregid
#define swapuid setreuid
#else
int swapgid(int a,int b) {setgid(b);return setegid(a);}
int swapuid(int a,int b) {setuid(b);return(seteuid(a));}
#endif

#ifndef USE_SETGID
#define unswapgid setregid
#define unswapuid setreuid
#else
int unswapgid(int b,int a) {setegid(b);return(setgid(a));}
int unswapuid(int b,int a) {seteuid(b);return(setuid(a));}
#endif

extern do_stats(un_char *, int, struct Client *);

static char *sockaddr_to_str(struct sockaddr *addr){
  struct sockaddr_in *addr_in;
  static char portname[59];
  char *a, *p;
  unsigned long int j;

#define UC(y) (int) ((int) 0xff & (int) (y))
  addr_in = (struct sockaddr_in *) addr;
  p = (char *)&addr_in->sin_port;
  if(addr_in->sin_family == AF_INET ){
    if( ! addr_in->sin_addr.s_addr ){
      j = inet_addr("127.0.0.1");
      a = (char *)&j;
    }else{
      a = (char *)&addr_in->sin_addr.s_addr;
    };
    (void) sprintf(portname, "%u,%u,%u,%u,%u,%u",
             UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
  }else{
    strcpy(portname,"127,0,0,1,0,0");
  };
  return portname;
}


static char *hostent_to_str(struct hostent *addr){
  static char hostname[300];
  struct in_addr *addr_in;

  if(addr->h_addrtype==AF_INET){
    addr_in = (struct in_addr *) addr->h_addr;
    sprintf(hostname,"%lu %s",ntohl(addr_in->s_addr),addr->h_name);
  }else{
    sprintf(hostname,"0 %s",addr->h_name);
  };
  return hostname;
}

/*
 * This modules handles multiplexing the clients onto the serial stream.
 * 
 * do_link_in() is called when there are in packets waiting, and
 * do_link_out() is called when there is something in the link_out buffer and
 * 	the serial out buffer is empty.
 */

/*-----------------------------------------------------------------------*/
/* Local function protypes */
int get_data(un_char *, int);
void put_data(un_char *, int);
int get_client_data(struct Client *);
void put_client_data(struct Client *, int);
/*-----------------------------------------------------------------------*/
/* Data */

int curr_in_stream = -1, 
  curr_out_stream = -1 ;
/* we have this in the open to peek to see if compression is 
 * wanted...  croutons
 */
static struct Client *curr_client = 0;
int new_packet = 0;

/*-----------------------------------------------------------------------*/
void do_link_out(void) {
  int len;
  /* Add another packet to the out packet list */
  
  if (p_out_num >= window_size) {
				/* naff off. The packet window is full. */
				/* This is actually normal, so don't */
				/* print a message. */
#if 0
    DEBUG_LINK(stderr, "Tried to do link_out with p_out_num == %d\n",
	       p_out_num);
#endif
    return;
  }
  /* put some data in the packet. Get up to 'max' bytes. */
  /* returns the length. If the length is -ve then it has been compressed */
  
  new_packet = 1;

  len = get_data(p_out[p_out_s].data, out_mask - 2);

  if (!len) {
    /* All the data waiting was control data for local daemon */
    /* We can handle this. */
    return;
  }

  p_out[p_out_s].timeout = 0; /* Transmit it right away.. */
  p_out[p_out_s].trans = 0;
  p_out[p_out_s].len = len > 0 ? len : -len;
  p_out[p_out_s].type = (len > 0) ? (seven_bit_out ? 2 : 0) : 1;
  DEBUG_LINK(stderr, "%s:Added pack %d to out Q\n", term_server, p_out_s);
  p_out_s = (p_out_s + 1) & 31;
  p_out_num ++;
}


void do_link_in(void) {
  /* Takes packet of the in packet list and feeds it to clients. */
  static un_char uncomp_buff[2049];
  int l;
  while (p_in[p_in_e].type >= 0) {
    /* feed data out */
    DEBUG_LINK(stderr, "%s: Handleing p %d off in Q\n", term_server, p_in_e);
    if (p_in[p_in_e].type == 1) {
      extern int stat_uncomp_in, stat_uncomp_out;
      l =uncompress(p_in[p_in_e].data , p_in[p_in_e].len,
		    uncomp_buff);
      stat_uncomp_in += p_in[p_in_e].len;
      stat_uncomp_out += l;
      put_data(uncomp_buff, l);	
    } else if (p_in[p_in_e].type == 0) {
      put_data(p_in[p_in_e].data, p_in[p_in_e].len);
    } else {
      l = s_2_e_buff(p_in[p_in_e].data, uncomp_buff,
		     p_in[p_in_e].len);
      put_data(uncomp_buff, l);
    }

    p_in[p_in_e].type = -1;
    p_in_e = (p_in_e + 1) & 31;
    p_in_num --;
  }
}

/*---------------------------------------------------------------------------*/

/* This is where compression will eventually get done */
/* For now , we just get some bytes */
/* we are compressing now. */
int get_data(un_char *b, int len) {
  extern int tok_byte_width_out;
  int i, j, k;
  /* this is a horrible kludge, but without major reorignization, 
   * this is the simplest way.  we need to know if the current 
   * packet for the current client should be compressed, so we 
   * need to know the client.  we get the first byte (which forces
   * which client we are getting data from) and then peek and 
   * see what that client wants. we then pass along the byte
   * that we read.. (this is the REAL kludge).
   * croutons.
   */
				/* Get a byte, so we we have a client. */
  k = get_client_byte();
  if ( k < 0 )			/* no data */
  	return 0;
				/* Now that we have a client, we can */
				/* check to see wether we want to */
				/* compress the data or not. */
  if (curr_client->compress && (j = compress(b, len-1, k)) > 0)  {
    j = ( j + tok_byte_width_out - 1) / tok_byte_width_out;
    return -j;
				/* If we only have a seven bit output */
				/* line, then we want to pack 8 bytes */
				/* to 7 seven bit bytes. */
  } else if (seven_bit_out) {
    len = (len * 7) / 8;
    b[0] = 0;
    j = e_2_s_put(b, (unsigned) k, 0);
    while ((j>>3) < len) {
      if ((i = get_client_byte()) < 0) 
	break;
      j = e_2_s_put(b, (unsigned) i, j);
    }
    return (j>>3) + 1;
  } else {			/* Else, just dump the data, we can */
				/* handle it. */
    b[0] = k; /* put the first byte in the array */
    j = 1;
  
    while (j < len) {
      i = get_client_byte();
      if (i < 0) break;
      b[j ++ ]  = i;
    }
  }
  return j;
}
/*---------------------------------------------------------------------------*/

void ADD_BUFF(struct Client *clt, un_char c)
{
  add_to_buffer( &((clt)->out_buff), c);
}

void ADD_IN_BUFF(struct Client *clt, un_char c)
{
  add_to_buffer( &((clt)->in_buff), c);
}

void clear_buffers(struct Client *cl) {
  cl->in_buff.size = cl->in_buff.start = cl->in_buff.end = 0;
  cl->out_buff.size = cl->out_buff.start = cl->out_buff.end = 0;

  add_to_buffer (& (cl->in_buff), 0);
  get_from_buffer (& (cl->in_buff ) );

  add_to_buffer (& (cl->out_buff), 0);
  get_from_buffer (& (cl->out_buff ) );
}

void add_ret_buff(struct Client *cl, int which, int byte)  {
  if (!which)
    ADD_IN_BUFF(cl, (un_char) byte);
  else
    put_client_data(cl, byte);
}

void add_ret_buff_str(struct Client *cl, int which, char *s) {
  int i;
  for (i = 0; s[i];++i)
    add_ret_buff(cl, which, (int) s[i]);
}

void ret_fail(struct Client *cl, int which, int fatal, char *p) {
  add_ret_buff(cl, which, SWITCH);
  add_ret_buff(cl, which, SWITCH-3);
  add_ret_buff(cl, which, I_FAIL);
  if (p) {
    add_ret_buff_str(cl, which, p);
    add_ret_buff_str(cl, which, ": ");
  }
  add_ret_buff_str(cl, which, strerror(errno));
  add_ret_buff(cl, which, 0);
  if(!fatal) return;

  add_ret_buff(cl, which, SWITCH);
  add_ret_buff(cl, which, SWITCH-2);
  add_ret_buff(cl, which, C_CLOSE);
  add_ret_buff(cl, which, 0);
}

void ret_ok(struct Client *cl, int which) {
  add_ret_buff(cl, which, SWITCH);
  add_ret_buff(cl, which, SWITCH-3);
  add_ret_buff(cl, which, I_OK);
  add_ret_buff(cl, which, 0);
}

void do_control(int local , struct Client *cl, un_char *c) {
  int i;
#ifdef SHAREDIR
  int g,ge,u,ue;
#endif
#if defined(SYSV) && !defined(DYNIXPTX)
  struct utsname unam;
#endif
  DEBUG_FP(stderr, "%s:do_control on client %d:%s:\n", term_server,
	   cl->number, c);
  switch(c[0]) {
  case C_NAME:
    DEBUG_FP(stderr, "%s:C_NAME\n", term_server);
    sprintf(cl->name, "%s", c+1);
    ret_ok(cl, local);
    break;
  case C_CLOSE:
    DEBUG_FP(stderr, "%s:C_CLOSE\n", term_server);
    if (cl->state == 1)
      cl->state = 3;		/* Go to flush buffers and close. */
    break;
  case C_CLCLOSE:
    DEBUG_FP(stderr, "%s:C_CLCLOSE\n", term_server);
    if (cl->state == 1)
      cl->state = 4;
    ret_ok(cl, local);
    break;
  case C_DUMB:
    DEBUG_FP(stderr, "%s:C_DUMB\n", term_server);
    cl->type &= ~T_SMART;
    cl->dump_count = 0;
    break;
  case C_DUMP:
    DEBUG_FP(stderr, "%s: C_DUMP %d\n", term_server, atoi((char *) (c+1)));
    ret_ok(cl, local);
    cl->type &= ~T_SMART;
    cl->dump_count = atoi((char *) (c+1))+1;
    break;
  case C_OPEN:
    if (cl->fd >=0)
      close(cl->fd);
    DEBUG_FP(stderr,"%s:got C_OPEN\n", term_server);
#ifdef SHAREDIR
    g=getgid();ge=getegid();
    if(g!=ge) swapgid(g,ge);
    u=getuid();ue=geteuid();
    if(u!=ue) swapgid(u,ue);
#endif /* SHAREDIR */
    cl->fd = open((char *) (c + 1), O_RDWR | O_CREAT, 0600);
#ifdef SHAREDIR
    if(g!=ge) unswapgid(ge,g);
    if(u!=ue) unswapuid(ue,u);
#endif /* SHAREDIR */
    DEBUG_FP(stderr, "%s:name was %s\n", term_server, c + 1);
    if (cl->fd < 0) {
      DEBUG_FP(stderr, "%s: open failed\n",term_server);
      ret_fail(cl,local,1, "open() failed");
      break;
    }
    cl->type= T_WRFILE;
    cl->cl_type = CL_FILE;
    cl->state = 1;
    ret_ok(cl,local);
    break;
  case C_UPLOAD:
    if (cl->fd>=0) close(cl->fd);
    DEBUG_FP(stderr,"%s:got C_UPLOAD\n", term_server);
#ifdef SHAREDIR
    ge=getegid();g=getgid();
    if(g!=ge)swapgid(g,ge);
    ue=geteuid();u=getuid();
    if(u!=ue)swapuid(u,ue);
#endif /* SHAREDIR */
    cl->fd = open((char *) (c + 1), O_WRONLY | O_CREAT | O_TRUNC,
			      0600 );
#ifdef SHAREDIR
    if(g!=ge)unswapgid(ge,g);
    if(u!=ue)unswapuid(ue,u);
#endif /* SHAREDIR */
    DEBUG_FP(stderr, "%s:name was %s\n", term_server, c + 1);
    if (cl->fd < 0) {
      DEBUG_FP(stderr, "%s: open failed\n",term_server);
      ret_fail(cl, local, 1, "open() failed");
      break;
    }
    cl->type = T_WRFILE;
    cl->cl_type = CL_FILE;
    cl->state = 1;
    ret_ok(cl, local);
    break;
    
  case C_DOWNLOAD:
    if (cl->fd>=0) close(cl->fd);
    DEBUG_FP(stderr,"%s:got C_DOWNLOAD\n", term_server);
#ifdef SHAREDIR
    ge=getegid();g=getegid();
    if(g!=ge)swapgid(g,ge);
    ue=geteuid();u=geteuid();
    if(u!=ue)swapgid(u,ue);
#endif /* SHAREDIR */
    cl->fd = open((char *) (c + 1), O_RDONLY );
#ifdef SHAREDIR
    if(g!=ge)unswapgid(ge,g);
    if(u!=ue)unswapuid(ue,u);
#endif /* SHAREDIR */
    DEBUG_FP(stderr, "%s:name was %s\n", term_server, c + 1);
    if (cl->fd < 0) {
      DEBUG_FP(stderr, "%s: open failed\n",term_server);
      ret_fail(cl,local, 1, "open() failed");
      break;
    }
    cl->type = T_RDFILE;
    cl->cl_type = CL_FILE;
    cl->state = 1;
    ret_ok(cl,local);
    break;
   
  case C_PTYEXEC:
  case C_EXEC:
    if (cl->fd>=0) close(cl->fd);
    DEBUG_FP(stderr, "%s: %s on client %d (%s) \n", term_server,
	     c[0]==C_PTYEXEC?"C_PTYEXEC":"C_EXEC", cl->number, c+1); 
    if (! rshtype) cl->fd = -5;
    else {
#ifndef NO_PTYEXEC
      if (c[0] == C_PTYEXEC)
        cl->fd = open_pty((char *)(c + 1));
      else 
#endif
        cl->fd = open_socket((char *)(c + 1));
    }
    if (cl->fd < 0) {
      char *p;
      DEBUG_FP(stderr, "%s: failed to open client: error: %d\n",
	       term_server, cl->fd); 

      switch (cl->fd) {
      case -1: p = "Couldn't get pty"; break;
      case -2: p = "fchmod() failed"; break;
      case -3: p = "fork() failed"; break;
      case -4: p = "socketpair() failed"; break;
      case -5: p = "Permission denied"; break;
      default: p = "Unknown failure"; break;
      }
      ret_fail(cl, local, 1, p);
      break;
    }
    DEBUG_FP(stderr, "%s: opened client\n", term_server);
    cl->type = T_WRFILE | T_RDFILE;
    cl->cl_type = CL_CHILD;
    cl->state = 1;
    cl->pid = pty_pid;
    DEBUG_FP(stderr, "%s: Got pid %d\n", term_server, pty_pid);
    ret_ok(cl, local);
    break;

  case C_BIND:
    DEBUG_FP(stderr, "%s: C_BIND %s\n", term_server, c +1);
    {
      int port;
      int s;
      port = atoi((char *) (c+1));
      s = bind_tcp(port);
      if (s < 0) {
	ret_fail(cl, local , 1, "bind_tcp() failed");
	DEBUG_FP(stderr, "%s:Bind_tcp failed (%d)\n", term_server,
		 port);
      }
      set_nonblock(s);
      if (cl->fd>=0) close(cl->fd);
      cl->fd = s;
      cl->cl_type = CL_BOUND;
      cl->type = T_RDFILE | T_WRFILE;
      cl->state = 1;
      ret_ok(cl, local);
    }
    break;
  case C_ACCEPT:
    {
      struct sockaddr_in addr_in;

      int ain = sizeof(addr_in);
      int s;

      cl->type |= T_RDFILE;

      DEBUG_FP(stderr, "%s: C_ACCEPT %s\n", term_server, c+1);
				/* Get the socket to try and accept() */
				/* on. */
      s = atoi((char *) (c+1));
				/* Error checking..  */
      if (s <  0 || s >= MAX_CLIENTS || clients[s].fd < 0 ||
	   clients[s].cl_type != CL_BOUND) {
	errno = 0;
	ret_fail(cl, local, 1, "Client out of range");
	break;
      }else /* The bound port can accept more connections... */
        clients[s].type |= T_RDFILE | T_WRFILE;  

				/* try the actual accept(). */
      s = accept(clients[s].fd , (struct sockaddr *) &addr_in, &ain);
      if (s < 0) {
	ret_fail(cl, local, 1, "Accept failed");
	break;
      }

      set_nonblock(s);
      add_ret_buff(cl, local,SWITCH);
      add_ret_buff(cl, local,SWITCH-3);
      add_ret_buff(cl, local,I_OK);
      add_ret_buff_str(cl,local,sockaddr_to_str((struct sockaddr *)&addr_in));
      add_ret_buff(cl, local,0);

      DEBUG_FP(stderr,"%s:got C_ACCEPT\n", term_server);
      if (cl->fd>=0) close(cl->fd);
      cl->fd = s;
      cl->cl_type = CL_SOCKET;
      DEBUG_FP(stderr, "%s:name is %s\n", term_server, c + 1);
      cl->type = T_RDFILE | T_WRFILE;
      cl->state = 1;
    }
    break;
#ifndef X_STREAMS_PIPE
  case C_X_SERVER:
#endif
  case C_SOCKET:
    DEBUG_FP(stderr, "%s: C_SOCKET %s\n", term_server, c+1);
    {

      int s;
      
      s = open_unix((char *)(c+1));
      if (s < 0) {
	DEBUG_FP(stderr, "%s:Open_unix failed (%s)\n", term_server,
		 c+1);
	ret_fail(cl, local, 1, "open_unix() failed");
	break;
      }

      set_nonblock(s);
      DEBUG_FP(stderr,"%s:got C_SOCKET\n", term_server);
      if (cl->fd>=0) close(cl->fd);
      cl->fd = s;
      cl->cl_type = CL_SOCKET;
      DEBUG_FP(stderr, "%s:name is %s\n", term_server, c + 1);
      cl->type = T_RDFILE | T_WRFILE;
      cl->state = 1;
      ret_ok(cl, local);
    }
    break;
#ifdef X_STREAMS_PIPE
  case C_X_SERVER:
    DEBUG_FP(stderr, "%s: C_X_SERVER %s\n", term_server, c+1);
    {

      int s;
      int display_num;
      char *display, *screen;

      /* check DISPLAY variable for screen number */

      display = getenv("DISPLAY");
      if (display == 0)
	display = ":0";

      screen = strchr(display, ':');
      if (screen != 0)
	display_num = atoi(screen + 1);
      else
	display_num = 0;

      s = MakeStreamPipeConnection(display_num);
      if (s < 0) {
	DEBUG_FP(stderr, "%s: X connection failed (%s)\n", term_server,
		 c+1);
	ret_fail(cl, local, 1, "X connection failed");
	break;
      }

      set_nonblock(s);
      DEBUG_FP(stderr,"%s:got C_X_SERVER\n", term_server);
      if (cl->fd>=0) close(cl->fd);
      cl->fd = s;
      cl->cl_type = CL_SPIPE;
      DEBUG_FP(stderr, "%s:name is %s\n", term_server, c + 1);
      cl->type = T_RDFILE | T_WRFILE;
      cl->state = 1;
      ret_ok(cl, local);
    }
    break;
#endif
  case C_PORT:
    DEBUG_FP(stderr, "%s: C_PORT %s\n", term_server, c+1);
    {
      struct sockaddr_in addr_in;
      struct hostent *hp, *gethostbyname(), hs;
      char hostname[258];
      int s;
      char *colon, *portname;
      
      if ((s = socket(AF_INET, SOCK_STREAM, 0 )) < 0) {
	ret_fail(cl, local, 1, "Socket() failed");
	break;
      }

      strcpy(hostname, (char *) (c+1));
      colon = strchr(hostname, ':');
      if (colon) {
	*colon = '\0';
	portname = colon + 1;
      }
      else {
#if defined(SYSV) && !defined(DYNIXPTX)
	uname(&unam);
	strcpy(hostname, unam.nodename);
#else
	gethostname (hostname, sizeof(hostname));
#endif
	portname = (char *)(c+1);
      }
      if (hostname[0] >= '0' && hostname[0] <= '9') {
	addr_in.sin_family = AF_INET;
	addr_in.sin_addr.s_addr = inet_addr(hostname);
      }
      else {
	hp=gethostbyname(hostname);
	if (!hp) {
	  ret_fail(cl, local, 1, "gethostbyname() failed");
	  perror ("Term: gethostbyname");
	  close(s);
	  break;
	}
	hs = *hp;
	addr_in.sin_family = hs.h_addrtype;
	addr_in.sin_addr = * ((struct in_addr *) hs.h_addr);
      }
      
      addr_in.sin_port = htons(atoi(portname));
      
      DEBUG_FP(stderr, "Connecting to host %s port %d\n", hostname, 
	atoi(portname) );

#ifdef CONN_NONBLOCK
      /* Set nonblock mode before connecting so connect() returns right away */
      set_nonblock(s);
#endif

      if (connect(s,(struct sockaddr *)&addr_in,sizeof(struct
						    sockaddr_in))<0) {
#ifdef CONN_NONBLOCK
	if (errno != EINPROGRESS) {
#endif
	  ret_fail(cl,local, 1, "connect() failed");
	  perror ("Term: connect");
	  close(s);
	  break;
#ifdef CONN_NONBLOCK
	}
#endif
      }
      
#ifndef CONN_NONBLOCK
      set_nonblock(s);
#endif

      DEBUG_FP(stderr,"%s:got C_PORT\n", term_server);
      if (cl->fd>=0) close(cl->fd);
      cl->fd = s;
      cl->cl_type = CL_SOCKET;
      cl->type = T_RDFILE | T_WRFILE;
#ifdef CONN_NONBLOCK
      cl->state = 5;
      cl->timeout = current_time + 20*30;	/* 30-second connect timeout */
#else
      cl->state = 1;
      ret_ok(cl, local);
#endif
    }
    break;
  case C_SEND: /* Send a message */
    /* Not written yet */
    DEBUG_FP(stderr, "%s: C_SEND (not implimented yet) %d\n", term_server, atoi((char *) (c+1)));
    {
      ret_ok(cl, local);
    }
    break;
  case C_PRIORITY:
    DEBUG_FP(stderr, "%s: C_PRIORITY %d\n", term_server, atoi((char *) (c+1)));
    cl->priority = atoi((char *) (c+1));
    ret_ok(cl, local);
    break;
  case C_COMPRESS:
    DEBUG_FP(stderr, "%s: C_COMPRESS %c\n", term_server, c[1]);
    
    switch(c[1]) {
    case 'y':	/* yes */
    case 'c':	/* compress */
    case 'Y':	/* caps too */
    case 'C':   
    case 1: 	/* true */
    case '1':	/* in ascii */
      cl->compress = 1;
      ret_ok(cl, local);
      break;
    case 'n':	/* no */
    case 'u':	/* uncompress */
    case 'r':	/* raw */
    case 'N':	/* caps too */
    case 'U':	
    case 'R':
    case 0:  	/* false */
    case '0':  	/* in ascii */
      cl->compress = 0;
      ret_ok(cl, local);
      break;
    default:
      ret_fail(cl, local, 0, "Invalid argument");
      break;
    }
    break;
  case C_STAT:
    {
      struct stat st;
      int type;
#ifndef __NetBSD__
      int permissions;
#else
      mode_t permissions;
#endif
      char buff[10];
      DEBUG_FP(stderr, "%s: C_STAT %s\n", term_server, c+1);
      if (stat((char *) (c+1), &st)< 0) {
	ret_fail(cl, local, 0, "stat() failed");
	DEBUG_FP(stderr, "%s: stat() failed\n", term_server);
	break;
      }
      add_ret_buff(cl, local,SWITCH);
      add_ret_buff(cl, local,SWITCH-3);
      add_ret_buff(cl, local,I_OK);
				/* Get type */
      if (S_ISREG(st.st_mode)) type = 0;
      else if (S_ISDIR(st.st_mode)) type = 1;
      else type = 2;
				/* Now get permissions. */
      permissions = st.st_mode;
      if (getuid() == st.st_uid)
	permissions >>=6;
      else if (getgid() == st.st_gid)
	permissions >>=3;
      permissions &= 07;

#ifndef __NetBSD__      
      sprintf(buff, "%ld %d %d", 
	      st.st_size, type, permissions);
#else
      sprintf(buff, "%d %d %d", 
	      (int)st.st_size, type, permissions);
#endif
      add_ret_buff_str(cl, local, buff);
      add_ret_buff(cl, local,0);
      break;
    }
  case C_STATS: 
    {
      int opt;
      un_char ret[2000];
/*      extern do_stats(un_char *, int, struct Client *); */

      DEBUG_FP(stderr, "%s:C_STATS\n", term_server);
      opt = atoi((char *) (c+1));
      add_ret_buff(cl, local, SWITCH);
      add_ret_buff(cl, local, SWITCH-3);
      add_ret_buff(cl, local, I_OK);

      do_stats(ret, opt, cl);

      add_ret_buff_str(cl, local, (char *)ret);
      add_ret_buff(cl, local, 0);
      break;
    }
  case C_SEEK:
    DEBUG_FP(stderr, "%s:C_SEEK %s\n", term_server, c+1);
    i = atoi((char *) (c+1));
#ifndef __NetBSD__
    if (lseek(cl->fd, i, 0)< 0) {
#else
    if (lseek(cl->fd, (off_t)i, (off_t)0)< 0) {
#endif
      ret_fail(cl, local, 0, "lseek() failed");
      DEBUG_FP(stderr, "C_seek failed\n");
      break;
    }
    ret_ok(cl, local);
    break;
#ifdef USE_SIGWINCH
  case C_RESIZE:
    DEBUG_FP(stderr, "%s:C_RESIZE %s\n", term_server, c+1);
    {
      void do_resize(int number, int rows, int cols, int ypixels, int xpixels);
      int number;
      int rows, cols, ypixels, xpixels;
      sscanf((char *) (c+1), "%d %d %d %d %d",
				&number, &rows, &cols, &ypixels, &xpixels);
      do_resize(number, rows, cols, ypixels, xpixels);
      ret_ok(cl, local);
    }
    break;
#endif	/* USE_SIGWINCH */

  case C_BINDN: /* ftp special -ot */
    DEBUG_FP(stderr, "%s: C_BINDN %s\n", term_server,c+1);
    {
      int s, k;
      struct hostent *hp, *gethostbyname();
      struct sockaddr_in addr_in;
      char hostname[258];
      
#if defined(SYSV) && !defined(DYNIXPTX)
      uname(&unam);
      strcpy(hostname, unam.nodename);
#else
      gethostname(hostname, sizeof(hostname));
#endif /* SYSV */
      
      hp=gethostbyname(hostname);
      if (!hp) {
      ret_fail(cl, local, 1, "gethostbyname() failed");
      perror ("Term: gethostbyname");
      break;
      }
      
      s = bind_tcp(atoi(c+1));
      if (s < 0) {
      ret_fail(cl, local , 1, "bind_tcp() failed");
      DEBUG_FP(stderr, "%s:Bind_tcp failed\n", term_server);
      break;
      }
      k=sizeof(addr_in);
      if (getsockname(s, (struct sockaddr *)&addr_in, &k) < 0) {
      ret_fail(cl, local, 1, "getsockname() failed");
      DEBUG_FP(stderr, "%s:getsockname failed\n", term_server);
      break;
      }
      
      DEBUG_FP(stderr, "%s:sockname returned %lX,%u\n", term_server,
             addr_in.sin_addr.s_addr, addr_in.sin_port);
      ret_ok(cl, local);

      addr_in.sin_addr.s_addr = *(unsigned long int *)hp->h_addr;

      add_ret_buff_str(cl,local,sockaddr_to_str((struct sockaddr *)&addr_in));
      add_ret_buff(cl, local,0);

      set_nonblock(s);
      if (cl->fd>=0) close(cl->fd);
      cl->fd = s;
      cl->cl_type = CL_BOUND;
      cl->type = T_RDFILE | T_WRFILE;
      cl->state = 1;
    }
    break;

  case C_GETSOCKNAME: /* Return the local sockname -warlord */
    DEBUG_FP(stderr, "%s: C_GETSOCKNAME %s\n", term_server,(c+1));
    {
      int s, k, kt;
      struct sockaddr addr;
     
      if(! *(c+1)){
        s = cl->fd;
        kt= cl->cl_type;
      } else {
        k = atoi(c+1);
        if (k <  0 || k >= MAX_CLIENTS){
     	  ret_fail(cl, local, 1, "Client out of range");
          DEBUG_FP(stderr, "%s:Invalid client\n", term_server);
	  break;
        };
        s=clients[k].fd;
        kt=clients[k].cl_type;
      };

      if ( s < 0 ) {
        ret_fail(cl, local, 1, "Client closed");
        DEBUG_FP(stderr, "%s:Client closed\n", term_server);
        break;
      } 
				/* Error checking..  */
      if (kt != CL_BOUND && kt != CL_SOCKET) {
        ret_fail(cl, local, 1, "Invalid client type");
        DEBUG_FP(stderr, "%s:Invalid client type\n", term_server);
        break;
      }
      
      k=sizeof(addr);
      if (getsockname(s, &addr, &k) < 0) {
        ret_fail(cl, local, 1, "getsockname() failed");
        DEBUG_FP(stderr, "%s:getsockname failed\n", term_server);
        break;
      }
      
      /* Convert this to a string */
      
      /* Set up the return buffer */
      add_ret_buff(cl, local, SWITCH);
      add_ret_buff(cl, local, SWITCH-3);
      add_ret_buff(cl, local, I_OK);
      add_ret_buff_str(cl, local, sockaddr_to_str(&addr));
      add_ret_buff(cl, local,0);
      
      DEBUG_FP(stderr, "%s:sockname returned\n", term_server);
    }
    break;

  case C_GETPEERNAME: /* Return the peername -warlord */
    DEBUG_FP(stderr, "%s: C_GETPEERNAME\n", term_server);
    {
      int s, k, kt;
      struct sockaddr addr;
      
      if(! *(c+1)){
        s = cl->fd;
        kt = cl->cl_type;
      } else {
        k = atoi(c+1);
        if (k <  0 || k >= MAX_CLIENTS){
     	  ret_fail(cl, local, 1, "Client out of range");
          DEBUG_FP(stderr, "%s:Invalid client\n", term_server);
	  break;
        };
        s=clients[k].fd;
        kt=clients[k].cl_type;
      };
      
      if (kt != CL_SOCKET) {
        ret_fail(cl, local, 1, "Invalid client type");
        DEBUG_FP(stderr, "%s:Invalid client type\n", term_server);
        break;
      }
      
      k=sizeof(addr);
      if (getpeername(s, &addr, &k) < 0) {
        ret_fail(cl, local, 1, "getpeername() failed");
        DEBUG_FP(stderr, "%s:getpeername failed\n", term_server);
        break;
      }
      
      /* Set up the return buffer */
      add_ret_buff(cl, local, SWITCH);
      add_ret_buff(cl, local, SWITCH-3);
      add_ret_buff(cl, local, I_OK);
      add_ret_buff_str(cl, local, sockaddr_to_str(&addr));
      add_ret_buff(cl, local,0);
      
      DEBUG_FP(stderr, "%s:peername returned\n", term_server);
    }
    break;

  case C_GETHOSTNAME:   /* Cannonical hostname */
    DEBUG_FP(stderr, "%s: C_GETHOSTNAME %s\n", term_server,
             ((*(c+1)) ? (char *) (c+1) : "<none>"));
    {
      struct hostent *hp;
      char hostname[258];
      
      if (*(c+1)) {
        strcpy(hostname, (char *) (c+1));
      } else {
#if defined(SYSV) && !defined(DYNIXPTX)
        uname(&unam);
        strcpy(hostname, unam.nodename);
#else
        gethostname(hostname, sizeof(hostname));
#endif /* SYSV */
      }
      
      hp=gethostbyname(hostname);
      if (!hp) {
      ret_fail(cl, local, 1, "gethostbyname() failed");
      perror ("gethostbyname");
      break;
      }
      
      add_ret_buff(cl, local, SWITCH);
      add_ret_buff(cl, local, SWITCH-3);
      add_ret_buff(cl, local, I_OK);
      add_ret_buff_str(cl, local, hostent_to_str(hp));
      add_ret_buff(cl, local,0);
    }
    break;
    
  case C_BINDS: /* One time tcp term socket */
    DEBUG_FP(stderr, "%s: C_BINDS\n", term_server);
    {
      int k;
      struct hostent *hp, *gethostbyname();
      struct sockaddr_in addr_in;
      char port[10], hostname[258];
     
      if(onetime_term_socket>=0) close(onetime_term_socket);
      onetime_term_socket = -1;
 
#if defined(SYSV) && !defined(DYNIXPTX)
      uname(&unam);
      strcpy(hostname, unam.nodename);
#else
      gethostname(hostname, sizeof(hostname));
#endif /* SYSV */
      
      hp=gethostbyname(hostname);
      if (!hp) {
      ret_fail(cl, local, 1, "gethostbyname() failed");
      perror ("Term: gethostbyname");
      break;
      }
      
      onetime_term_socket = bind_tcp(0);
      if (onetime_term_socket < 0) {
      ret_fail(cl, local , 1, "bind_tcp() failed");
      DEBUG_FP(stderr, "%s:Bind_tcp failed\n", term_server);
      break;
      }
      k=sizeof(addr_in);
      if (getsockname(onetime_term_socket, (struct sockaddr *)&addr_in, &k) < 0) {
      close(onetime_term_socket);
      onetime_term_socket = -1;
      ret_fail(cl, local, 1, "getsockname() failed");
      DEBUG_FP(stderr, "%s:getsockname failed\n", term_server);
      break;
      }
      
      DEBUG_FP(stderr, "%s:sockname returned %lX,%u\n", term_server,
             addr_in.sin_addr.s_addr, addr_in.sin_port);
      ret_ok(cl, local);
      (void) sprintf(port, "%d", ntohs(addr_in.sin_port));

      add_ret_buff(cl, local,SWITCH);
      add_ret_buff(cl, local,SWITCH-3);
      add_ret_buff(cl, local,I_OK);
      
      add_ret_buff_str(cl, local, port);
      add_ret_buff(cl, local,0);
    }
    break;

  case C_QUIT:
    DEBUG_FP(stderr, "%s: C_QUIT\n", term_server);
    do_shutdown = 1;
    ret_ok(cl, local);
    break;

  default:
    break;
  } /* switch */
} /* function */

void init_client(struct Client *cl) {
  extern int compressing; /* the default from main */
  cl->type = T_RDFILE | T_WRFILE;
  cl->dump_count = 0;
  cl->cl_type = CL_SOCKET;
  cl->state = 1;
  cl->compress = compressing;
  cl->c_state = 0;
  cl->number = cl - &clients[0];
  cl->priority = 2;		/* default priority. Higher is better. */
  cl->name[0] = 0;
  clear_buffers(cl);
  DEBUG_LINK(stderr, "Init client %d\n", cl->number);
}

/*---------------------------------------------------------------------------*/
/* Returns next client to read. Will be beefed up later to support priorities*/
/* A client with a priority of n will get n/(sum) of the packets available   */
/* maybe :) */

struct Client * get_next_client(void) { 
  int i, h = -10000, j=0;
  static int c = 0;
				/* Check to see if any of the clients */
				/* are closeing. This gets priority. */
  for (i = 0; i < MAX_CLIENTS;++i)
    if (clients[i].state == 2) {
      DEBUG_STATE(stderr, "get_n_c ret cl %d\n", i);
      return &clients[i];
    }
				/* Then run down the clients looking */
				/* for the next ready client with the */
				/* highest priority. */
  for (i = 0; i < MAX_CLIENTS;++i, c = (c+1) % MAX_CLIENTS)
    if (clients[c].in_buff.size) {
      if (clients[c].priority > h) {
	h = clients[c].priority;
	j = c;
      }
    }

  if (h == -10000) return 0;	/* No client was ready. */

  c = (j+1) % MAX_CLIENTS;	/* Start searching at next client next */
				/* time. */
  return &clients[j];
}

int get_client_data(struct Client *cl) {
  int i;			/* If nothing ready, signal that. */
  SANITY(cl);
  if (!cl->in_buff.size) 
    return -1;
  SANITY(cl->in_buff.end < cl->in_buff.alloced);
  SANITY(cl->in_buff.end >= 0);
  SANITY(cl->in_buff.size >=0);
  SANITY(cl->in_buff.size < cl->in_buff.alloced);

				/* get the next byte from the buffer.*/
  
  
  i = cl->in_buff.data[cl->in_buff.end++];
  if (cl->in_buff.end == cl->in_buff.alloced)	/* Wrap the buffer */
				/* round if we have */
				/* hit the end. */
    cl->in_buff.end = 0;
  cl->in_buff.size --;		/* Update count of bytes left in buffer. */

  SANITY(cl->dump_count >=0 );
  if (cl->dump_count) {		/* If we are currently dumping, update */
				/* the dump count, and go smart if we */
				/* have finished. */
    if (!--cl->dump_count)
      cl->type |= T_SMART;
  }

  DEBUG_STATE(stderr, "\tgcd:%d\n", i);
  return i;
}

void put_client_data(struct Client *cl, int i) {
  while (1) {
    DEBUG_STATE(stderr, "p_c_d: %d: state %d i %d c_len %d\n", 
		cl->number, cl->c_state, i, cl->c_len);
    switch (cl->c_state) {
    case 0:
      if (i == SWITCH) {
	cl->c_state = 1;
	return;
      }
      
      ADD_BUFF(cl, (un_char) i);
      return;
    case 1:
      if (i == SWITCH) {
	ADD_BUFF(cl, (un_char) i);
	cl->c_state = 0;
	return ;
      }
      cl->c_len = 0; /* yes. We do want to throw this byte away */
      /* It is just a remote control message flag */
      if (i == SWITCH - 3) {	/* It is a result message. */
	if (cl->type & T_SMART) {
	  ADD_BUFF(cl, (un_char) SWITCH);
	  ADD_BUFF(cl, SWITCH-3);
	}
	cl->c_state = 3;
      } else {
	cl->c_state = 2;
      }
      return;
    case 2:
      cl->control[cl->c_len++] = i;
      if (i) return;
      do_control(0, cl , cl->control);
      cl->c_state = 0;
      return;
    case 3:
      if (cl->type & T_SMART) ADD_BUFF(cl, (un_char) i);
      if (i) return;
      cl->c_state = 0;
      return;
    default:
      cl->c_state = 0;
      break;
    }
  }
}



/* Return the next byte that should go down the serial link */
/* Another bloody finite state machine. ;) */
/* This was a bitch to write. Sigh. More things than I thought needed */
/* to be handled. And it still isn't perfect. :( */

int get_client_byte() {
  static int state = 0,
  next, max;
  
  static char control[255];
  struct Client *cl;
  int i;

  while (1) {
    DEBUG_STATE(stderr, "get_c_b: state %d next %d max %d cl %d\n", state,
		next, max, !curr_client ? -1 : curr_client->number);

    switch(state) {
    case 0: /* looking for new client */
      new_packet = 0;
      cl = get_next_client();
      if (cl == 0) 
	return -1;
      
      if (!cl->in_buff.size && cl->state == 2) { /* closeing down */
				/* We have emptied the buffers, so */
				/* just tell the remote end that , and */
				/* finish off. */
	DEBUG_FP(stderr, "%s:sending C_CLOSE\n", term_server);
	/* ++kay: should be state 3, not -1 */
	cl->state = 3;
	state = 4;
	sprintf(control, "%c%c%c%c%c%c", SWITCH, cl->number + SWITCH + 1,
		SWITCH, SWITCH - 2, C_CLOSE, 0);
	curr_client = cl;
	next = 0;
	max = 6;
	break;
      }
      
      if (cl != curr_client) {
	curr_client = cl;
	state = 1; 
	return SWITCH;
      }
      state = 2;
      break;    
    case 1:
      state = 2;
      return curr_client->number + SWITCH + 1;
      break;
    case 2:
				/* If we are a new packet, then check */
				/* to see if there is a new client */
				/* with a greater probability. */
      if (new_packet) {
	new_packet = 0;
	state = 0;
	break;
      }

      i = get_client_data(curr_client);
      if (i == SWITCH) {
	if (!(curr_client->type & T_SMART)) {
	  state = 4;
	  max = 1; next = 0;
	  control[0] = SWITCH;	
	  return SWITCH;
	}
	state = 3;
	break;
      } else if (i < 0) {
	state = 0;
	break;
      }
      {
	extern int stat_cooked_out;
	++stat_cooked_out;
      }
      return i;
      break;
    case 3:
      i = get_client_data(curr_client);
      if (i < 0)
	return -1;
      
      if (i == SWITCH) {
	/* It is an escaped escape code */
	state = 4;	
 
	max = 1; next = 0;
	return SWITCH;
      }
      /* ok. We have some sort of control message */
      if ( i  > SWITCH ) {
	/* Hmm. It is trying to switch streams on it's own. welllll. ok. */
	/* we'll let it. Note that is can only reliably insert 1 byte at */
	/* a time */
	control[0] = (i-SWITCH-1<MAX_CLIENTS) ? SWITCH-1 : i;
	next  = 0;
	max = 1;
	state = 4;
	return SWITCH;
	break;
      }
      if ( i != SWITCH - 1) {
	/* stuff for remote. Just pass it thru */
	/* might not be SWITCH - 2, but if it isn't, we don't want */
	/* to know. ;) */
	control[0] = i;
	next = 0; max = 1;
	state = 4;
	return SWITCH;
	break;
      }
      /* ok. a real control message for us. */
      state = 5;
      next = 0;
      break;
    case 4:
      if (next + 1 == max) 
	state = 2;
      return control[next++];
      break;
    case 5: /* A local control message */
      /* note that there is a nasty bit here. All other streams block while */
      /* we are waiting for this control message. I thought about */
      /* programming around this but decided that it was too messy. */
   
      /* note that most local control messages don't make much sense. */
      i = get_client_data(curr_client);
      if (i < 0)
	return -1;
      
      control[next++] = i;
				/* If this isn't the end of the */
				/* message, keep going.. */
      if (i)
	break;

      do_control(1, curr_client, (un_char *) control);
      state = 2;
      break;
    } /* switch */
  } /* while */
}

/*----------------------------------------------------------------------*/
/* Transfers the next 'len' bytes from the link, to clients.  Note that */
/* we handle control information here. */
void put_data(un_char *b, int len) {
  static struct Client *curr_client = 0;
  static int state = 0;
  int i, d;
  
  i = 0;
  while (i < len) {
    DEBUG_STATE(stderr, "put_d: s %d, cl %d d %d\n", state, 
		!curr_client ? 0 : curr_client->number, b[i]);
    switch(state) {
    case 0:
      d = b[i++];
      if (d == SWITCH) {
	state = 1;
	break;
      }
      /* ok. Just put data to current client */
      if (!curr_client)
	break;
      put_client_data(curr_client, d);
      break;
    case 1:
      d = b[i++];
      /* checked for escaped escape */
      if (d == SWITCH) {
	if (curr_client) {
	  put_client_data(curr_client, d);
	  put_client_data(curr_client, d);
	}
       	state = 0;
	break;
      }
      /* check for stream switch */
      if (d > SWITCH && d - SWITCH - 1 < MAX_CLIENTS ) {
	curr_client = &clients[d - SWITCH - 1];
	if (curr_client->state < 0) 
	  init_client(curr_client);
	DEBUG_LINK(stderr, "Stream switch to %d\n", d - SWITCH - 1);
	state = 0;
	break;
      }
      if (curr_client) {
	put_client_data(curr_client, SWITCH);
	put_client_data(curr_client, d);
      }
      state = 0;
      break;
    default:
      state = 0;
      break;
    } /* switch */
  } /* while */
} /* function */

