/* 
 * $Id: user.c,v 1.10 1999/03/19 20:50:15 labovit Exp $
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <netdb.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/utsname.h>

#include <mrt.h>
#include <trace.h>
#include <select.h>
#include <stdarg.h>
#include <user.h>
#include <config_file.h>
#include "timer.h"

/* these globals won't work with multiple threads !*/
int control = 0;

int uii_proccess_command (uii_connection_t * uii_connection);
int uii_destroy_connection (uii_connection_t * connection);
int uii_read_command (uii_connection_t * uii);
static int uii_command_help (uii_connection_t *uii, int alone);
static int uii_call_callback_fn (uii_connection_t *uii,  uii_command_t *candidate);
static void telnet_option_process (uii_connection_t *uii, char cp);
static char *uii_translate_machine_token_to_human (char *token);
int uii_token_match (char *format, char *command);
LINKED_LIST *find_matching_commands (int state, LINKED_LIST *ll_tokens, 
				     LINKED_LIST *ll_last, int alone);
LINKED_LIST *uii_tokenize (char *buffer, int len);
static int _uii_destroy_connection (uii_connection_t * connection);
LINKED_LIST *uii_tokenize_choices (char *buffer, int len);

/* globals */
uii_t *UII;



#define WRITE3(uii, a, b, c) { \
	char x, y, z; \
	x = a; y=b; z=c; \
	if ((uii->sockfd < 0)  || (write (uii->sockfd, &x, 1) < 1) || \
	    (write (uii->sockfd, &y, 1) < 1) || \
	    (write (uii->sockfd, &z, 1) < 1)) { uii_destroy_connection (uii);  }}

#define SAFE_WRITE(uii, a, n) {\
  if ((uii->sockfd < 0) || (write (uii->sockfd, a, n) < n)) { uii_destroy_connection (uii); }}
							       


/* Okay, signal the waiting uii that data is ready for it */
void signal_uii_data_ready (uii_connection_t *uii) {

#ifdef HAVE_LIBPTHREAD
  pthread_mutex_lock (&uii->mutex_cond_lock);
  pthread_cond_signal (&uii->cond);
  pthread_mutex_unlock (&uii->mutex_cond_lock);
#endif /* HAVE_LIBPTHREAD */
}

/*
 * The following routines expect some basic terminal capabilities:
 *     0x07 -- beep
 *     0x08 -- non destractive backspace
 *  vt100-like arrow keys: ^[[A, B, C, and D
 * caution: when exceeding the right end of line will be a problem
 */
static void uii_bs (uii_connection_t *uii, int n) {
  char bs = 0x08;

  if (uii->sockfd < 0) return;

  while (n--) {
    if (write (uii->sockfd, &bs, 1) < 1) {
      uii_destroy_connection (uii);
      return;
    }
  }
}


/* similar to bs, but erase the previous character */

static void uii_erase (uii_connection_t *uii, int n) {
  int i;
  uii_bs (uii, n);
  if (uii->sockfd < 0) return;
  for (i = 0; i < n; i++) {
    if (write (uii->sockfd, " ", 1) < 1) {
      uii_destroy_connection (uii);
      return;
    }
  }
  uii_bs (uii, n);
}


static void uii_bell (uii_connection_t *uii) {
  char bell = 0x07;

  if (uii->sockfd < 0) return;

  if (write (uii->sockfd, &bell, 1) < 1) {
    uii_destroy_connection (uii);
    return;
  }
}




void 
uii_schedule_receive_command (uii_connection_t * uii_connection)
{
    schedule_event2 ("uii_read_command",
		    uii_connection->schedule, (void_fn_t) uii_read_command, 1,
		    uii_connection);
}


/* _start_uii
 * uii thread on its own
 */
void start_uii (uii_connection_t * uii_connection)
{
/*
  sigset_t set;

  sigemptyset (&set);
  sigaddset (&set, SIGALRM);
  sigaddset (&set, SIGHUP);
  pthread_sigmask (SIG_BLOCK, &set, NULL);
*/
  init_mrt_thread_signals ();

  /* look for SENT IAC SB NAWS 0 133 (133) 0 58 (58)
   * to learn screen size
   */


  WRITE3 (uii_connection, 0xff, 0xfb, 0x01);
  WRITE3 (uii_connection, 0xff, 0xfb, 0x03);
  WRITE3 (uii_connection, 0xff, 0xfe, 0x21);

  /* usa naws -- screen size negotiation 255 253 31 */
  WRITE3 (uii_connection, 0xff, 0xfd, 0x1F);
  /* no lflow */
  WRITE3 (uii_connection, 0xff, 0xfe, 0x22);
  /* don't goahead */
  WRITE3 (uii_connection, 0xff, 0xfe, 0x03);

  /*uii_clear_screen (uii_connection->sockfd);*/

  if (uii_send_data (uii_connection, "MRT version %s\r\n", MRT_VERSION) < 1)
    return;

  /* state 0 is requires authentication */
  if (UII->initial_state == 0) {
    if (uii_send_data (uii_connection, "\r\nUser Access Verification\r\n\r\n") < 1)
      return;
  }
  else if (UII->initial_state == 1) {
    if (uii_send_data (uii_connection, "\r\nNo Access Verification\r\n\r\n") < 1)
      return;
  }

  uii_connection->cp = uii_connection->buffer; 
  uii_connection->end = uii_connection->buffer;
  uii_connection->end[0] = '\0';


#ifndef HAVE_LIBPTHREAD    
    select_add_fd (uii_connection->sockfd, 1,
		   uii_schedule_receive_command, uii_connection);
#endif /* HAVE_LIBPTHREAD */

    uii_send_data (uii_connection, UII->prompts[uii_connection->state]);
    uii_connection->telnet = 0;

#ifdef HAVE_LIBPTHREAD
    while (1) {
      struct timeval  tv;
      fd_set          read_fds;
      int	      ret;
      /* TIMEOUT of one minute */
      tv.tv_sec = 300;
      tv.tv_usec = 0;
      FD_ZERO (&read_fds);
      FD_SET (uii_connection->sockfd, &read_fds);
      /* This should be here because select() may change the value */
      ret = select (uii_connection->sockfd + 1, &read_fds, 0, 0, &tv);
      if (ret <=0) {
	trace (NORM, UII->trace,
	       "ERROR on UII select (before read). Closing connection (%s)\n",
	       strerror (errno));
	uii_send_data (uii_connection, "Connect closed -- timeout or error\r\n");
	uii_destroy_connection (uii_connection);
	return;
      }
      if (uii_read_command (uii_connection) < 0)
	return;
    }
#else
    return;
#endif /* HAVE_LIBPTHREAD */

}


/* uii_cept_connection
 * start thread for new connection and start connection timeout
 * timer.
 */
int 
uii_accept_connection ()
{
    int sockfd;
    int len, port, family;
    prefix_t *prefix;
#ifndef HAVE_IPV6
    struct sockaddr_in addr;
#else
   union sockaddr_any {
      struct sockaddr sa;
      struct sockaddr_in sin; 
      struct sockaddr_in6 sin6;
   } addr;
   /*struct sockaddr_in6 *sin6;*/
#endif /* HAVE_IPV6 */
    uii_connection_t *uii_connection;
    u_int one = 1;
    char tmp[MAXLINE];

    len = sizeof (addr);
    memset ((struct sockaddr *) &addr, 0, len);

    if ((sockfd =
	 accept (UII->sockfd, (struct sockaddr *) &addr, &len)) < 0) {
	trace (ERROR, UII->trace, "ERROR -- UII Accept failed (%s)\n",
	       strerror (errno));
	select_enable_fd (UII->sockfd);
	return (-1);
    }

    select_enable_fd (UII->sockfd);

    if (setsockopt (sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &one,
		    sizeof (one)) < 0) {
      trace (NORM | INFO, UII->trace, "UII setsockoptfailed\n");
      return (-1);
    }

#ifndef HAVE_IPV6
    if ((family = addr.sin_family) == AF_INET) {
#else
    if ((family = addr.sa.sa_family) == AF_INET) {
#endif /* HAVE_IPV6 */
      struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
      port = ntohs (sin->sin_port);
      prefix = New_Prefix (AF_INET, &sin->sin_addr, 32);
    }  
#ifdef HAVE_IPV6
    else if (family == AF_INET6) {
      struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr;
      port = ntohs (sin6->sin6_port);
      if (IN6_IS_ADDR_V4MAPPED (&sin6->sin6_addr))
         prefix = New_Prefix (AF_INET, ((char *)&sin6->sin6_addr)+12, 32);
      else
         prefix = New_Prefix (AF_INET6, &sin6->sin6_addr, 128);
    }
#endif /* HAVE_IPV6 */
    else {
      trace (ERROR, UII->trace, "UII ERROR unknown connection family = %d\n",
             family);
      close (sockfd);
      return (-1);
    }  

    trace (INFO, UII->trace, "UII accepting connection from %s\n",
	   prefix_toa (prefix));

    /* check if password exists -- if not AND state == 0, deny */
    if ((UII->password == NULL) && (!prefix_is_loopback (prefix))) {
      trace (NORM | INFO, UII->trace, 
	     "UII DENIED to %s -- no password specified\n",
	     prefix_toa (prefix));
      Deref_Prefix (prefix);
      close (sockfd);
      return (-1);
    }

    /* Apply access list (if one exists) */
    if (UII->access_list > 0) {
      if (!apply_access_list (UII->access_list, prefix)) {
	trace (NORM | INFO, UII->trace, "UII DENIED to %s\n",
	       prefix_toa (prefix));
	Deref_Prefix (prefix);
	close (sockfd);
	return (-1);
      }
    }

    uii_connection = New (uii_connection_t);
    uii_connection->state = UII->initial_state;
    uii_connection->prev_state = -1;
    uii_connection->schedule = New_Schedule ("uii_connection", UII->trace);
    uii_connection->sockfd = sockfd;
    uii_connection->cmdcp = uii_connection->cmdend = 0;
    uii_connection->from = prefix;
    uii_connection->sockfd_out = -1;
    uii_connection->ll_tokens = NULL;
    uii_connection->tty_max_lines = 23;
    uii_connection->telnet = 0;
    uii_connection->mutex_lock_p = NULL;
    uii_connection->more = NULL;

#ifdef HAVE_LIBPTHREAD
    pthread_cond_init (&uii_connection->cond, NULL);
    pthread_mutex_init (&uii_connection->mutex_cond_lock, NULL);
#endif /* HAVE_LIBPTHREAD */

    LL_Add (UII->ll_uii_connections, uii_connection);

    sprintf (tmp, "UII %s", prefix_toa (prefix));
    mrt_thread_create (tmp, uii_connection->schedule,
		       (thread_fn_t) start_uii, uii_connection);
    return (1);
}


static int
commands_compare (uii_command_t * a, uii_command_t * b)
{
    return (strcasecmp (a->string, b->string));
}


/* for compatibility */
int 
listen_uii ()
{
    return listen_uii2 ("mrt");
}


/*
 * begin listening for connections on a well known port
 */
int 
listen_uii2 (char *portname)
{
    struct sockaddr *sa;
    struct servent *service;
    int len, optval = 1;
    u_short port;
    int fd;

    struct sockaddr_in serv_addr;
#ifdef HAVE_IPV6
    struct sockaddr_in6 serv_addr6;
    memset (&serv_addr6, 0, sizeof (serv_addr6));
    serv_addr6.sin6_family = AF_INET6;
    if (UII->prefix && UII->prefix->family == AF_INET6) {
	memcpy (&serv_addr6.sin6_addr, prefix_toaddr6 (UII->prefix), 16);
    }
#endif /* HAVE_IPV6 */

    memset (&serv_addr, 0, sizeof (serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    if (UII->prefix && UII->prefix->family == AF_INET) {
	memcpy (&serv_addr.sin_addr, prefix_toaddr (UII->prefix), 4);
    }

    if (UII->disabled == 1) {
      trace (NORM, UII->trace, "UII Disabled -- port set to -1\n");
      return 0;
    }

    if (UII->port > 0) {
      port = htons (UII->port);
    }
    else {
      if (portname == NULL) {
	port = htons (UII_DEFAULT_PORT);
      }
      else {
        /* search /etc/services for port */
	if ((service = getservbyname (portname, NULL)) == NULL) {
	  int i = atoi (portname);	/* in case of number */
	  port = (i > 0) ? htons (i) : htons (UII_DEFAULT_PORT);
        }
        else
	  port = service->s_port;
      }
    }

    serv_addr.sin_port = port;
    sa = (struct sockaddr *) &serv_addr;
    len = sizeof (serv_addr);
#ifdef HAVE_IPV6 
    if (UII->prefix == NULL || UII->prefix->family == AF_INET6) {
        serv_addr6.sin6_port = port;
        sa = (struct sockaddr *) &serv_addr6;
        len = sizeof (serv_addr6);
    }
#endif /* HAVE_IPV6 */

    if ((fd = socket (sa->sa_family, SOCK_STREAM, 0)) < 0) {
#ifdef HAVE_IPV6 
	if (UII->prefix || sa->sa_family == AF_INET)
	    goto error;
	/* try with AF_INET */
    	sa = (struct sockaddr *) &serv_addr;
    	len = sizeof (serv_addr);
    	if ((fd = socket (sa->sa_family, SOCK_STREAM, 0)) < 0) {
error:
#endif /* HAVE_IPV6 */
	    trace (ERROR, UII->trace, 
	           "UII ERROR -- Could not get socket (%s)\n",
		    strerror (errno));
	    return (-1);
#ifdef HAVE_IPV6 
	}
#endif /* HAVE_IPV6 */
    }

    if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR,
		    (const char *) &optval, sizeof (optval)) < 0) {
	trace (ERROR, UII->trace, "UII ERROR -- Could setsocket (%s)\n",
	       strerror (errno));
    }

    if (bind (fd, sa, len) < 0) {
	if (UII->prefix)
	    trace (ERROR, UII->trace, 
	           "UII ERROR -- Could not bind to port %d at %s (%s)\n",
	           ntohs (port), prefix_toa (UII->prefix), strerror (errno));
	else
	    trace (ERROR, UII->trace, 
	           "UII ERROR -- Could not bind to port %d (%s)\n",
	           ntohs (port), strerror (errno));
	return (-1);
    }

    UII->sockfd = fd;
    listen (fd, 5);

    if (UII->prefix)
        trace (NORM, UII->trace,
	       "UII listening for connections on port %d at %s (socket %d)\n",
	       ntohs (port), prefix_toa (UII->prefix), UII->sockfd);
    else
        trace (NORM, UII->trace,
	       "UII listening for connections on port %d (socket %d)\n",
	       ntohs (port), UII->sockfd);

    select_add_fd (UII->sockfd, 1, (void_fn_t) uii_accept_connection, NULL);
    return (1);
}


static int
uii_quit_level (uii_connection_t * uii)
{
    if (uii->state > UII_CONFIG) {
	uii->state = UII_CONFIG;
    }
    else if (uii->state == UII_CONFIG) {
	end_config (uii);
	uii->state = UII_NORMAL;
    }
    else {
	uii->state = UII_EXIT;
    }
    return (1);
}


/* uii_read_command
 */
int uii_read_command (uii_connection_t * uii_connection) {
  int n;
  char *cp;

  if (uii_connection->sockfd < 0) return (-1);

if (uii_connection->state == UII_PROMPT_MORE) {
    assert (uii_connection->more);
    /* Grab an input to resume displaying */
    uii_send_bulk_data (uii_connection);
#ifndef HAVE_LIBPTHREAD
    select_enable_fd (uii_connection->sockfd);
#endif /* HAVE_LIBPTHREAD */
    uii_send_data (uii_connection, UII->prompts[uii_connection->state]);
    return (1);
}

  if ((n = read (uii_connection->sockfd, uii_connection->tmp, MAXLINE - 1)) <= 0) {
    trace (NORM, UII->trace, "UII read failed\n",
	   strerror (errno));
    uii_destroy_connection (uii_connection);
    return (-1);
  }
  uii_connection->tmp[n] = '\0';

#if 0
{ int i;
  fprintf (stderr, "uii receive: ");
  for (i = 0; i < n; i++)
    fprintf (stderr, "[%d]", uii_connection->tmp[i] & 0xff);
  fprintf (stderr, "\n");
}
#endif
  
  cp = uii_connection->tmp;
  while (n--) {
    if ((uii_connection->telnet > 0) || (*(u_char *)cp == 0xff)) {
      telnet_option_process (uii_connection, *cp);
      cp++;
      continue;
    }
		
    if (control == 1) {
      if (*cp == '[') {
	control = 2;
	cp++;
      }
      else {
	control = 0;
	cp++;
      }
    }
    else if (control == 2) {
      control = 0;

      /* up \020 */
      if (uii_connection->state >= UII_NORMAL && *cp == 'A') {
      up:
	if (--uii_connection->cmdcp < 0)
	  uii_connection->cmdcp = MAXHIST - 1;
	if (uii_connection->cmds[uii_connection->cmdcp][0] == '\0') {
	  uii_bell (uii_connection);
	  if (++uii_connection->cmdcp >= MAXHIST)
	    uii_connection->cmdcp = 0;
	}
	else {
	  int len, i, m;

	replace:
	  if (uii_connection->cp - uii_connection->buffer > 0)
	    uii_bs (uii_connection, uii_connection->cp - uii_connection->buffer);
	  strcpy (uii_connection->buffer, uii_connection->cmds[uii_connection->cmdcp]);
	  len = strlen (uii_connection->buffer);
	  SAFE_WRITE (uii_connection, uii_connection->buffer, len);
	  if ((m = uii_connection->end - uii_connection->buffer - len) > 0) {
	    for (i = 0; i < m; i++)
	      SAFE_WRITE(uii_connection, " ", 1);
	    uii_bs (uii_connection, m);
	  }
	  uii_connection->cp = uii_connection->end = uii_connection->buffer + len;
	}
      }
      /* down \016 */
      else if (uii_connection->state >= UII_NORMAL && *cp == 'B') {
      down:
	if (++uii_connection->cmdcp >= MAXHIST)
	  uii_connection->cmdcp = 0;
	if (uii_connection->cmds[uii_connection->cmdcp][0] == '\0') {
	  uii_bell (uii_connection);
	  if (--uii_connection->cmdcp < 0)
	    uii_connection->cmdcp = MAXHIST - 1;
	}
	else {
	  goto replace;
	}
      }
      /* left */
      else if (*cp == 'D') {
      left:
	if (uii_connection->cp <= uii_connection->buffer) {
	  uii_bell (uii_connection);
	}
	else {
	  uii_connection->cp--;
	  uii_bs (uii_connection, 1);
	}
      }
      /* right */
      else if (*cp == 'C') {
      right:
	if (uii_connection->cp >= uii_connection->end) {
	  uii_bell (uii_connection);
	}
	else {
	  SAFE_WRITE (uii_connection, uii_connection->cp, 1);
	  uii_connection->cp++;
	}
      }
      cp++;
    } /***** end if (control == 2) **************/
    /* ^B */
    else if (*cp == 'B' - '@') {
      goto left;
    }
    /* ^F */
    else if (*cp == 'F' - '@') {
      goto right;
    }
    /* ^N */
    else if (uii_connection->state >= UII_NORMAL && *cp == 'N' - '@') {
      goto down;
    }
    /* ^P */
    else if (uii_connection->state >= UII_NORMAL && *cp == 'P' - '@') {
      goto up;
    }
    /* ^K */
    else if (*cp == 'K' - '@') {
      int i, m;
      
      if ((m = uii_connection->end - uii_connection->cp) > 0) {
	for (i = 0; i < m; i++)
	  SAFE_WRITE (uii_connection, " ", 1);
	uii_bs (uii_connection, m);
	uii_connection->end = uii_connection->cp;
	uii_connection->end[0] = '\0';
      }
    }
    /* ^U */
    else if (*cp == 'U' - '@') {
      int i, m;
      
      if ((m = uii_connection->end - uii_connection->buffer) > 0) {
	uii_bs (uii_connection, uii_connection->cp - uii_connection->buffer);
	for (i = 0; i < m; i++)
	  SAFE_WRITE (uii_connection, " ", 1);
	uii_bs (uii_connection, m);
	uii_connection->cp = uii_connection->end = uii_connection->buffer;
	uii_connection->end[0] = '\0';
      }
    }
    /* ^D */
    else if (*cp == 'D' - '@') {
      
      if (uii_connection->cp >= uii_connection->end) {
	uii_bell (uii_connection);
      }
      else {
	memmove (uii_connection->cp, uii_connection->cp + 1, 
		 uii_connection->end - uii_connection->cp + 1);
	uii_connection->end--;
	SAFE_WRITE (uii_connection, uii_connection->cp, 
		    uii_connection->end - uii_connection->cp);
	SAFE_WRITE(uii_connection, " ", 1);
	uii_bs (uii_connection, uii_connection->end - uii_connection->cp + 1);
      }
    }
    /* Control-z */
    else if (uii_connection->state >= UII_CONFIG && *cp == '\032') {
      memcpy (uii_connection->buffer, "\r\n\0", 3);
      uii_connection->cp = uii_connection->buffer;

      uii_quit_level (uii_connection);
      uii_send_data (uii_connection, "\r\n");
      uii_send_data (uii_connection, UII->prompts[uii_connection->state]);
    }
    /* go to beginning of line */
    else if (*cp == 'A' - '@') {
      uii_bs (uii_connection, uii_connection->cp - uii_connection->buffer);
      uii_connection->cp = uii_connection->buffer;
    }
    /* goto end of line */
    else if (*cp == '\005') {
      while (uii_connection->cp < uii_connection->end) {
	char *star = "*";
	if (uii_connection->state == 0) {
	  SAFE_WRITE (uii_connection, star, 1); /* password -- don't echo */
	}
	else {
	  SAFE_WRITE (uii_connection, uii_connection->cp, 1);
	}
	uii_connection->cp++;
      }
    }
    else if (*cp == '\b' || *cp == '\177') {

      /* no more to delete -- send bell */
      if (uii_connection->cp <= uii_connection->buffer) {
	uii_bell (uii_connection);
      }
      else {
	uii_bs (uii_connection, 1);
	memmove (uii_connection->cp - 1, uii_connection->cp, 
		 uii_connection->end - uii_connection->cp);
	uii_connection->cp--;
	uii_connection->end--;
	SAFE_WRITE (uii_connection, uii_connection->cp,
		    uii_connection->end - uii_connection->cp);
	SAFE_WRITE (uii_connection, " ", 1);
	uii_bs (uii_connection, uii_connection->end - uii_connection->cp + 1);
      }
    }
	else if (uii_connection->state >= UII_NORMAL && *cp == '?') {
	  char stmp[MAXLINE];
	  memset (stmp, 0, MAXLINE);
	  uii_command_help (uii_connection, 
	    (uii_connection->cp <= uii_connection->buffer ||
		 uii_connection->cp[-1] == ' '));
	  uii_send_data (uii_connection, 
				    UII->prompts[uii_connection->state]);
	  memcpy (stmp, uii_connection->buffer, uii_connection->cp-uii_connection->buffer);
	  uii_send_data (uii_connection, "%s", stmp); 
	}
	else if (*cp == '\t') {
	  uii_command_tab_complete (uii_connection,
	    (uii_connection->cp <= uii_connection->buffer ||
		uii_connection->cp[-1] == ' '));
	}
	/* control character (escape followed by 2 bytes */
	else if (*cp == '\033') {
	    control = 1;
	    cp++;
	}
	/* ^C */
	else if (*cp == 'C' - '@') {
	    uii_connection->cp = uii_connection->end = uii_connection->buffer;
	    uii_connection->end[0] = '\0';
	    goto xxxx;
	}
	else if (*cp == '\r') {
	  uii_connection->end[0] = '\0';

	    /* remove tailing spaces */
	    cp = uii_connection->buffer + strlen (uii_connection->buffer) - 1;
	    while (cp >= uii_connection->buffer && isspace ((int) *cp)) {
		*cp = '\0';
		cp--;
	    }

	    /* remove heading spaces */
	    cp = uii_connection->buffer;
	    while (*cp && isspace ((int) *cp)) {
		cp++;
	    }
	    if (uii_connection->buffer != cp)
		strcpy (uii_connection->buffer, cp);

	    uii_connection->cp = uii_connection->buffer;
	  xxxx:
	    uii_send_data (uii_connection, "\n\r");

	    if (uii_connection->buffer[0] != '\0') {
		if (uii_connection->state >= UII_NORMAL /* not passwd */) {
		    strncpy (uii_connection->cmds[uii_connection->cmdend], 
			    uii_connection->buffer, sizeof (uii_connection->cmds[0]) -1);
		    uii_connection->cmds[uii_connection->cmdend]
				        [sizeof (uii_connection->cmds[0]) -1] = '\0';
		    if (++uii_connection->cmdend >= MAXHIST)
		        uii_connection->cmdend = 0;
		    uii_connection->cmdcp = uii_connection->cmdend;
		}
	    }
	    uii_proccess_command (uii_connection);
	    if (uii_connection->state == UII_EXIT) {
		/* user has quit or we've unexpetdly terminated */
		/* delete uii_connection memory here */
		uii_destroy_connection (uii_connection);
		return (-1);
	    }
	    uii_connection->cp = uii_connection->buffer;
	    uii_connection->end = uii_connection->buffer;
	    uii_connection->end[0] = '\0';

#ifndef HAVE_LIBPTHREAD
	    select_enable_fd (uii_connection->sockfd);
#endif /* HAVE_LIBPTHREAD */
	    /*uii_send_data (uii_connection, "[%2d] ", pthread_self ());*/
	    uii_send_data (uii_connection, 
				      UII->prompts[uii_connection->state]);
	    return (1);
	}
	else if (*(u_char *)cp == 0xff) {
	    /*printf ("telnet sequences\n"); */
	    cp += 3;
	    n -= 2;
	}
	else {
	    char *star = "*";
	    if ((*cp > 31) && (*cp < 127)) {
	      if (uii_connection->state == UII_UNPREV)	
			/* no echo -- password state */ {
		SAFE_WRITE (uii_connection, star, 1);
	      }
	      else {
		SAFE_WRITE (uii_connection, cp, 1);
	      }
	      if (uii_connection->end > uii_connection->cp) {
		SAFE_WRITE (uii_connection, uii_connection->cp, 
			    uii_connection->end - uii_connection->cp);
		uii_bs (uii_connection, uii_connection->end - uii_connection->cp);
		memmove (uii_connection->cp + 1, uii_connection->cp,
			 uii_connection->end - uii_connection->cp);
	      }
	      *(uii_connection->cp)++ = *cp;
	      uii_connection->end++;
	    }
	    cp++;
	}
    }

#ifndef HAVE_LIBPTHREAD
    select_enable_fd (uii_connection->sockfd);
#endif /* HAVE_LIBPTHREAD */

    return (1);
}


/* 
 * read command from socket and call appropraiet handling
 * function.
 */
int uii_proccess_command (uii_connection_t * uii_connection) {
    char *line, *token, *bp;
    uii_command_t *candidate = NULL;
    char word[MAXLINE];
    int ret = -1;

    line = uii_connection->buffer;
    uii_connection->cp = uii_connection->buffer;

    /* eat up initial white spaces */
    while ( (isspace ((int) *uii_connection->cp)) && *uii_connection->cp)
      uii_connection->cp++;
    
#ifdef notdef
    /* built-in functions */
    if ((!strcasecmp (uii_connection->cp, "exit")) ||
	(!strcasecmp (uii_connection->cp, "quit"))) {
	if (uii_connection->state > UII_CONFIG) {
	    uii_connection->state = UII_CONFIG;
	    return (1);
	}
	else if (uii_connection->state == UII_CONFIG) {
	    end_config (uii_connection);
	    uii_connection->state = UII_NORMAL;
	    return (1);
	}
	else {
	  uii_destroy_connection (uii_connection);
	}
	return (UII_EXIT);
    }
#endif

  if (uii_connection->state >= UII_NORMAL) {
    /* comment in the middle of line */
    if ((uii_connection->cp[0] != '!' && uii_connection->cp[0] != '#')
	    && ((bp = strchr (uii_connection->cp+1, '!')) ||
	        (bp = strchr (uii_connection->cp+1, '#')))) {
	*bp++ = '\0';
	uii_connection->comment = bp;
    }

    /*if (!strncasecmp (uii_connection->cp, "no ", 3)) {
	uii_connection->negative++;
	uii_connection->cp += 3;
	while (isspace (*uii_connection->cp && *uii_connection->cp))
	    uii_connection->cp++;
    }*/

    if ((bp = strchr (uii_connection->cp, '>')) && 
	 (uii_connection->state == UII_NORMAL)) {
        /* redirection */
	mode_t mode = O_WRONLY | O_TRUNC | O_CREAT;

	*bp++ = '\0';
	if (*bp == '>') {
	    mode |= O_APPEND;
	    mode &= ~O_TRUNC;
	    bp++;
	}
	if ((token = uii_parse_line2 (&bp, word)) && token[0]) {
	    char *xp;

	    if ((xp = strrchr (token, '/')))
	      xp = xp + 1;
	    else
		xp = token;

	    if (UII->redirect) {
		char name[MAXLINE];

		sprintf (name, "%s/%s", UII->redirect, xp);

	        if ((uii_connection->sockfd_out = open (name, mode)) < 0) {
		  uii_send_data (uii_connection, "%s: %s", name, strerror (errno));
		  return (-1);
	        }
	        else {
		    char tmps[MAXLINE];
	            if (BIT_TEST (mode, O_APPEND))
		        lseek (uii_connection->sockfd_out, 0, SEEK_END);
    	            sprintf (tmps,  "[%s]\r\n", name);
		    SAFE_WRITE (uii_connection, tmps, strlen (tmps));
	        }
	    }
	    else {
	      uii_send_data (uii_connection, 
			     "No redirection allowed! Use redirect in configuration\r\n");
		return (-1);
	    }
	}
    }
  }

    bp = uii_connection->cp + strlen (uii_connection->cp) - 1;
    while (bp >= uii_connection->cp && *bp && isspace ((int) *bp))
        *bp-- = '\0';

    if (uii_connection->state == UII_UNPREV) {
	ret = uii_check_passwd (uii_connection);
#define UII_MAX_PASSWORD_RETRY 3
	if (uii_connection->retries >= UII_MAX_PASSWORD_RETRY) {
	    uii_send_data (uii_connection, "Bad passwords\r\n");
	    uii_connection->state = UII_EXIT;
	    ret = -1;
	}
    }
    /* blank line */
    else if ((token = uii_parse_line2 (&line, word)) == NULL) {
	ret = 1;
    }
    else {

    /* find a candidate command -- returns NULL if no command, or ambiguous */
    candidate = uii_match_command (uii_connection); 

    if (candidate) {

      uii_connection->cp += strlen (candidate->string);
      trace (NORM, UII->trace, "UII command %s\n", candidate->string);
      uii_connection->negative = 0;
      if (strncasecmp (candidate->string, "no ", 3) == 0)
	uii_connection->negative++;

      ret = uii_call_callback_fn (uii_connection, candidate);
    }
  }

    if (uii_connection->sockfd_out >= 0) {
	close (uii_connection->sockfd_out);
	uii_connection->sockfd_out = -1;
    }
    
    /* default */
    return (ret);
}


/* uii_call_callback_fn
 * The config or user input has matched a command. Rally up the arguments (if
 * any and call the callback function of the selected command
 * -- It is the responsibility of called routine to delete memory of arguments
 */
static int uii_call_callback_fn (uii_connection_t *uii,  uii_command_t *candidate) {
  char *ctoken, *utoken, *cp;
  void *args[10];
  int nargs, ret;
  int optional_arg, optional;

  optional_arg = optional = 0;

  ret = nargs = 0;

  ctoken = LL_GetHead (candidate->ll_tokens);
  utoken = LL_GetHead (uii->ll_tokens);

  while ((ctoken != NULL) && (utoken != NULL)) {
    cp = ctoken;

    /* optional argument */
    if (cp[0] == '[') {
      optional_arg = nargs;
      optional++;
      cp++;
      nargs++;

      /* literal optional */
      if (*cp != '%')
	args[nargs++] = (void *) strdup (utoken);
    }

    /* argument of a set */
    if (cp[0] == '(') {
      args[nargs++] = strdup (utoken);
    }

    if (*cp == '%') {
      if (*(cp+1) == 'p' || *(cp+1) == 'a') /* prefix */
	args[nargs++] = ascii2prefix (AF_INET, utoken);
#ifdef HAVE_IPV6
      else if (*(cp+1) == 'P' || *(cp+1) == 'A') /* prefix */
	args[nargs++] = ascii2prefix (AF_INET6, utoken);
#endif /* HAVE_IPV6 */
      else if (*(cp+1) == 's' || *(cp+1) == 'n') /* string */
	args[nargs++] = strdup (utoken);
      else if (*(cp+1) == 'd' || *(cp+1) == 'D') /* integer */
	args[nargs++] = (void *) atoi (utoken);
      /* everything else -- leave in uii->cp for backword compat */ 
      else if (*(cp+1) == 'S') { 
	/* I don't know which program expects this -- masaki */
	uii->cp = strstr (uii->buffer, utoken);
	/* Instead, I need this. I don't want to use strstr() */
	{ char buff[MAXLINE], *cp = buff;
	    strcpy (cp, utoken);
	    cp += strlen (utoken);
	    while ((utoken = LL_GetNext (uii->ll_tokens, utoken)) != NULL) {
	        strcpy (cp, " ");
		cp ++;
		strcpy (cp, utoken);
		cp += strlen (utoken);
		assert (cp - buff <= MAXLINE);
	    }
	    args[nargs++] = strdup (buff);
	}
	break;
      }
      else if (*(cp+1) == 'm' || *(cp+1) == 'M') /* ipv4 or ipv6 prefix */ {
#ifdef HAVE_IPV6
	if (strchr (utoken, ':'))
	  args[nargs++] = ascii2prefix (AF_INET6, utoken);
	else
#endif /* HAVE_IPV6 */
	  args[nargs++] = ascii2prefix (AF_INET, utoken);
      }
    }
    
    ctoken = LL_GetNext (candidate->ll_tokens, ctoken);
    utoken = LL_GetNext (uii->ll_tokens, utoken);
  }

  /* no optional arguments */
  if ((ctoken != NULL) && (ctoken[0] == '[')) 
    args[nargs++] = 0;

  /* I'm not sure that how %S should be handled,
     but anyway, it is handled like optional,
     and passes here -- masaki */
  if ((ctoken != NULL) && strcmp (ctoken, "%S") == 0) 
    args[nargs++] = NULL;

  /* insert the number of optional arguments before any of the optional arguments */
  if (optional > 0) 
    args[optional_arg] = (void *) optional;

  assert (candidate->call_fn);
  assert (nargs <= 5); /* XXX */

if (candidate->schedule) {
  if (nargs == 0)
    ret = schedule_event_and_wait ("uii command", candidate->schedule,
                                   (void_fn_t) candidate->call_fn, 1, uii);
  else if (nargs == 1) 
    ret = schedule_event_and_wait ("uii command", candidate->schedule,
                                   (void_fn_t) candidate->call_fn, 2, uii, 
				   args[0]);
  else if (nargs == 2) 
    ret = schedule_event_and_wait ("uii command", candidate->schedule,
                                   (void_fn_t) candidate->call_fn, 3, uii, 
				   args[0], args[1]);
  else if (nargs == 3) 
    ret = schedule_event_and_wait ("uii command", candidate->schedule,
                                   (void_fn_t) candidate->call_fn, 4, uii, 
				   args[0], args[1], args[2]);
  else if (nargs == 4) 
    ret = schedule_event_and_wait ("uii command", candidate->schedule,
                                   (void_fn_t) candidate->call_fn, 5, uii, 
				   args[0], args[1], args[2], args[3]);
  else if (nargs == 5) 
    ret = schedule_event_and_wait ("uii command", candidate->schedule,
                                   (void_fn_t) candidate->call_fn, 6, uii, 
				   args[0], args[1], args[2], args[3],
				   args[4]);
}
else {
  if (nargs == 0)
    ret = candidate->call_fn (uii);
  else if (nargs == 1) 
    ret = candidate->call_fn (uii, args[0]);
  else if (nargs == 2) 
    ret = candidate->call_fn (uii, args[0], args[1]);
  else if (nargs == 3) 
    ret = candidate->call_fn (uii, args[0], args[1], args[2]);
  else if (nargs == 4) 
    ret = candidate->call_fn (uii, args[0], args[1], args[2], args[3]);
  else if (nargs == 5) 
    ret = candidate->call_fn (uii, args[0], args[1], args[2], args[3], args[4]);
}

  /* if return == 2 then add to the list of modules. We call this function
   * again when we do a show config
   */
  if (ret == 2) {
    /* add to config module list */
  }

  return (ret);
}


/* uii_add_command
 * Add a command (i.e. "show rip") and accompagnying call
 * function to list maintaned by user interface object
 */
int uii_add_command (int state, char *string, uii_call_fn_t call_fn) {

  uii_add_command2 (state, 0, string, call_fn, "No Explanation");
  return (1);
}


int 
uii_add_command_schedule (int state, int flag, char *string, 
      uii_call_fn_t call_fn, char *explanation, schedule_t *schedule) {
  uii_command_t *command;
  char *token;

  command = New (uii_command_t);
  memcpy (command->string, string, strlen (string));
  command->call_fn = call_fn;
  command->state = state;
  if (explanation) 
    command->explanation = explanation;
  else
    command->explanation = strdup ("No Explanation");
  command->flag = flag;
  command->ll_tokens =  LL_Create (LL_DestroyFunction, free, 0);
  command->schedule = schedule;

  /* tokenize */
  while ((token = uii_parse_line2 (&string, NULL))) {
    LL_Add (command->ll_tokens, token);
  }

  /* we should really iterate through and make sure a command 
   * isn't being added twice... 
   */
  LL_Add (UII->ll_uii_commands, command);
  
  return (1);
}


/* uii_add_command2
 * add a command to list of accept commands
 * the flag specifies: 0=default  1=don't display  2=display, 
     but only information--don't accept as a command 
 */
int 
uii_add_command2 (int state, int flag, char *string, 
		  uii_call_fn_t call_fn, char *explanation) {
    return (uii_add_command_schedule (state, flag, string,
				call_fn, explanation, NULL));
}


/* uii_destroy_connection
 */
int uii_destroy_connection (uii_connection_t * connection) {

  trace (NORM, UII->trace, "UII Closing a connection from %s\n", 
	 prefix_toa (connection->from));
	
#ifdef HAVE_LIBPTHREAD
  if (connection->sockfd >= 0) {
    close (connection->sockfd);
    connection->sockfd = -1;
  }
#else 
  /* Craig said that close should be called in mrt_select(),
     but our code is not built so. -- masaki */
  /* Craig's: select_delete_fd2 (connection->sockfd); */
  select_delete_fd (connection->sockfd); /* this calls close() */
#endif /* HAVE_LIBPTHREAD */

  if (connection->sockfd_out >= 0) {
    close (connection->sockfd_out);
    connection->sockfd_out = -1;
  }

  /* check if terminal monitor output is going through us */
  if (MRT->trace->uii == connection) {
    MRT->trace->uii = NULL;
  }

  if (connection->mutex_lock_p) {
     pthread_mutex_unlock (connection->mutex_lock_p);
     connection->mutex_lock_p = NULL;
  }

  if (connection->answer) {
     Delete (connection->answer);
     connection->answer = NULL; 
  }

#ifdef HAVE_LIBPTHREAD
  _uii_destroy_connection (connection);
#else
  schedule_event2 ("uii_destroy_connection",
		   connection->schedule, (void_fn_t) _uii_destroy_connection, 1, 
		   connection);
#endif /* HAVE_LIBPTHREAD */
  return (1);
}


/* _uii_destroy_connection
 * Delete connection memory
 */
static int _uii_destroy_connection (uii_connection_t * connection) {

  trace (NORM, UII->trace, "UII deleting connection from %s\n", 
	 prefix_toa (connection->from));
	 
   LL_RemoveFn (UII->ll_uii_connections, connection, 0);
   Deref_Prefix (connection->from);
   delete_schedule (connection->schedule);
   if (connection->ll_tokens != NULL)
     LL_Destroy (connection->ll_tokens);
   Delete (connection);
   
   mrt_thread_exit ();
   return (1);
}





/* set_trace
 */
int
set_uii (uii_t * tmp, int first,...)
{
    va_list ap;
    enum UII_ATTR attr;
    int state;

    if (tmp == NULL)
	return (-1);


    /* Process the Arguments */
    va_start (ap, first);
    for (attr = (enum UII_ATTR) first; attr;
	 attr = va_arg (ap, enum UII_ATTR)) {
	switch (attr) {
	case UII_PORT:
	  tmp->port = va_arg (ap, int);
	  break;
	case UII_ADDR:
	  if (tmp->prefix)
	    Deref_Prefix (tmp->prefix);
	  tmp->prefix = Ref_Prefix (va_arg (ap, prefix_t *));
	  break;
	case UII_PROMPT:
	    state = va_arg (ap, int);
	    if ((state < 0) || state > UII_MAX_STATE) {
		return (-1);
	    }
	    if (tmp->prompts[state])
		Delete (tmp->prompts[state]);
	    tmp->prompts[state] = strdup (va_arg (ap, char *));
	    /* XXX THIS IS A BUG at the caller
	       Many routines call here like 
		 set_uii (UII, UII_PROMPT, 1, "MRT-1.3A> ");
	       but it should have been like
		 set_uii (UII, UII_PROMPT, 1, "MRT-1.3A> ", 0);
	       AT the moment, multiple setting is disabled */
	    goto quit; /* XXX */
	    break;
	case UII_ACCESS_LIST:
	  tmp->access_list = va_arg (ap, int);
	  goto quit; /* XXX */
	  break;
	case UII_PASSWORD:
	  if (UII->password)
	    Delete (UII->password);
	  UII->password = strdup (va_arg (ap, char *));
	  /* disable UII connections if password set to none */
	  /*if (!strcmp (UII->password, "none")) {
	    select_disable_fd (UII->sockfd);
	    }*/
	  goto quit; /* XXX */
	  break;
	case UII_INITIAL_STATE:
	  UII->initial_state = va_arg (ap, int);
	  goto quit; /* XXX */
	  break;
        case UII_DISABLED:
	  UII->disabled = 1;
	  break;
	default:
	    break;
	}
    }
quit:
    va_end (ap);

    return (1);
}



int uii_add_bulk_output (uii_connection_t *uii, char *format, ...) {
  va_list args;
  int len;

  if (uii->answer == NULL) {
    uii->answer = NewArray (char, UII_OUTPUT_BUFFER_SIZE);
    uii->cp = uii->answer;
    uii->answer_len = 0;
  }

  if (uii->answer_len > (UII_OUTPUT_BUFFER_SIZE - 200)) {
    return (-1);
  }

  va_start (args, format);
  vsprintf (uii->cp, format, args);  

  len = strlen (uii->cp);
  uii->cp += len;
  uii->answer_len += len;

  return (1);
}


int uii_add_bulk_output_raw (uii_connection_t *uii, char *string) {
  int len;

  if (uii->answer == NULL) {
    uii->answer = NewArray (char, UII_OUTPUT_BUFFER_SIZE);
    uii->cp = uii->answer;
    uii->answer_len = 0;
  }

  if (uii->answer_len > (UII_OUTPUT_BUFFER_SIZE - 200)) {
    return (-1);
  }

  strcpy (uii->cp, string);
  len = strlen (uii->cp);
  uii->cp += len;
  uii->answer_len += len;
  return (1);
}


/* uii_send_bulk_data
 * A "more"  -- when we need to send pages of information (i.e. BGP table dumps)
 * out to the uii socket.
 *
 * When we get around to it, we should figure out how to
 *  1) get termcap string to clear screen
 *  2) and learn how big the screen is (how many lines). I think this comes during
 *     the telnet setup negotiations?
 *
 */
void uii_send_bulk_data (uii_connection_t * uii_connection) {
  char *newline, *cp;
  int len, lineno = 0;

  if (uii_connection->state == UII_PROMPT_MORE) {
    char c;

  assert (uii_connection->more != NULL);
  cp = uii_connection->more;
  uii_connection->more = NULL;
  uii_connection->state = uii_connection->prev_state;

      if (read (uii_connection->sockfd, &c, 1) != 1) {
	Delete (uii_connection->answer);
	uii_connection->answer = NULL;
	return;
      }

      /*uii_clear_screen (uii_connection->sockfd);*/
      uii_erase (uii_connection, strlen (UII->prompts[UII_PROMPT_MORE]));

      /* quit */
      if ((c == 'q') || (c == 'Q')) {
	Delete (uii_connection->answer);
	uii_connection->answer = NULL;
	return;
      }

}
else {
  cp = uii_connection->answer;
}

  if (cp == NULL) return;

  while ((cp - uii_connection->answer) < uii_connection->answer_len) {

    if ((newline = strchr (cp, '\n')) == NULL) {
	len = strlen (cp);
        if (uii_connection->sockfd_out >= 0) {
          if (write (uii_connection->sockfd_out, cp, len) != len) {
	    uii_connection->sockfd_out = -1;
	    uii_send_data (uii_connection, "write error (%s).\r\n", 
			   strerror (errno));
	  }
	}
	if (uii_connection->sockfd >= 0) {
      	  if (write (uii_connection->sockfd, cp, len) != len) {
            uii_destroy_connection (uii_connection);
	    return;
	  }
	}
	Delete (uii_connection->answer);
        uii_connection->answer = NULL;
        return;
    }

    /*
     * in the following lines, we need to handle "\r\n" and "\n"
     * because of the difference in a way how to code messages
     */
    len = newline - cp + 1;

    if (uii_connection->sockfd_out >= 0) {
	if (len >= 2 && cp[len-2] == '\r') {
          if (write (uii_connection->sockfd_out, cp, len - 2) != len - 2 ||
              write (uii_connection->sockfd_out, "\n", 1) != 1) {
	    uii_connection->sockfd_out = -1;
	    uii_send_data (uii_connection, "write error (%s).\r\n", 
			   strerror (errno));
	  }
	}
	else {
          if (write (uii_connection->sockfd_out, cp, len) != len) {
	    uii_connection->sockfd_out = -1;
	    uii_send_data (uii_connection, "write error (%s).\r\n", 
			   strerror (errno));
	  }
	}
    }

  if (uii_connection->sockfd >= 0) {
    if (len >= 2 && cp[len-2] == '\r') {
      if (write (uii_connection->sockfd, cp, len) != len) {
        uii_destroy_connection (uii_connection);
        return;
      }
    }
    else {
      if (write (uii_connection->sockfd, cp, len - 1) != len - 1 ||
          write (uii_connection->sockfd, "\r\n", 2) != 2) {
        uii_destroy_connection (uii_connection);
        return;
      }
    }
  }

    cp += len;
    lineno++;

  if (uii_connection->sockfd >= 0) {
    /* exceeded line limit at bottom of screen
     * wait for user input and
     * clear screen (what is the character code??)
     *
     */
    if (lineno >= (uii_connection->tty_max_lines -1)) {
      /*char ff = 0x0c;    form feed */
      /* SAFE_WRITE (uii_connection, more, strlen (more)); */
      uii_connection->prev_state = uii_connection->state;
      uii_connection->state = UII_PROMPT_MORE;
      uii_connection->more = cp;
      return;
    }
  }

  }
  Delete (uii_connection->answer);
  uii_connection->answer = NULL;
  return;
}


int uii_check_passwd (uii_connection_t *uii) {
  char tmp[MAXLINE], *cp = tmp;

  tmp[0] = '\0';
  if (parse_line (uii->cp, "%S", tmp) >= 1) {
    cp = strip_spaces (tmp);
  }
  if (UII->password == NULL || strcmp (UII->password, cp) == 0) {
    uii->state = UII_NORMAL;
    uii->retries = 0;
    return (1);
  }
  else {
    trace (TR_TRACE, UII->trace, "INVALID PASSWORD: [%s]\n", cp);
    uii->retries++;
  }

  return (0);
}



/* strip off the begining and trailing space characters */
char *strip_spaces (char *tmp) {
    char *cp = tmp, *pp;
    while (*cp && isspace ((int) *cp)) cp++;
    if (*cp) {
      pp = cp+strlen(cp)-1;
      while (pp > cp && isspace ((int) *pp)) pp--;
      pp[1] = '\0';
    }
    return (cp);
}


/* uii_match_command
 * we iterate through tokens for each command
 * user enters "sh ip bgp"
 *   show ip bgp 
 *   show ipv6 bgp
 */
uii_command_t *uii_match_command (uii_connection_t *uii) {
  LINKED_LIST *ll_match;
  uii_command_t *command, *best_command;

  if (uii->ll_tokens != NULL) {
    LL_Destroy (uii->ll_tokens);
  }
  uii->ll_tokens = uii_tokenize (uii->cp, strlen (uii->cp));
  

  ll_match = find_matching_commands (uii->state, uii->ll_tokens, NULL, 0);

  best_command = LL_GetHead (ll_match);

  /* find smallest */
  LL_Iterate (ll_match, command) {
    /* trace (TR_TRACE, UII->trace, "candidate: %s\n", command->string); */
    if (strlen (command->string) < strlen (best_command->string))
      best_command = command;
  }

#ifdef notdef
  /* now see if smallest is a subset of all other commands */
  LL_Iterate (ll_match, command) {
    if (strncasecmp (command->string, best_command->string, 
		     strlen (best_command->string)) != 0) {
      config_notice (NORM, uii, "Ambiguous Command: %s\r\n", uii->cp);
      if (ll_match != NULL) 
	LL_Destroy (ll_match);
      return (NULL);
    }
  }
#else
  /* The above is not enough. It doesn't distinguish "show rip routes" and
     "show ripng routes". */
  LL_Iterate (ll_match, command) {
    char *token, *btoken;

    btoken = LL_GetHead (best_command->ll_tokens);
    LL_Iterate (command->ll_tokens, token) {
        if (strncasecmp (token, btoken, strlen (btoken)) != 0) {
            config_notice (NORM, uii, "Ambiguous Command: %s\r\n", uii->cp);
            LL_Iterate (ll_match, command) {
                config_notice (NORM, uii, "  %s\r\n", command->string);
	    }
            if (ll_match != NULL) 
	        LL_Destroy (ll_match);
            return (NULL);
	}
	btoken = LL_GetNext (best_command->ll_tokens, btoken);
	if (btoken == NULL)
	    break;
    }
  }
#endif

  if (best_command == NULL) {
    config_notice (NORM, uii, 
		   "Unrecognized Command: \"%s\" at state %d\r\n", 
		   uii->cp, uii->state);
  }

  /* if format args, make sure exact match */
  if (best_command) {
    char *token;
    int format_arg = 0;
    int optional_arg = 0;

    LL_Iterate (best_command->ll_tokens, token) {
      if (token[0] == '%') {
	if (token[1] != 'S') /* Pff -- this is a hack. %S will go away soon... */
	  format_arg = 1;
	else {
	  format_arg = 0;
	  break;
	}
      }
      if (token[0] == '[')
	optional_arg = 1;
    }
    if (format_arg ) {
      if (optional_arg) {
	if (LL_GetCount (uii->ll_tokens) < (LL_GetCount (best_command->ll_tokens) - 1)) {
	  config_notice (ERROR, uii, "Command missing arguments\r\n");
	  best_command = NULL;
	}
      }
      else if (LL_GetCount (uii->ll_tokens) != LL_GetCount (best_command->ll_tokens)) {
	config_notice (ERROR, uii, "Command missing arguments\r\n");
	best_command = NULL;
      }
    }
  }

  if (ll_match != NULL) 
    LL_Destroy (ll_match);

  return (best_command);
}


int uii_show_timers (uii_connection_t *uii) {
  mtimer_t *timer;
  time_t now;

  pthread_mutex_lock (&TIMER_MASTER->mutex_lock);


  
  if (strlen (TIMER_MASTER->current_function) > 4) 
    uii_add_bulk_output (uii, "Timer Master: %s\r\n", TIMER_MASTER->current_function);

  time (&now);
  uii_add_bulk_output (uii, "Timer Master: Interval=(%d), NextFire=(%d)\r\n",
		       TIMER_MASTER->time_interval,
		       (TIMER_MASTER->time_next_fire - now));
  

   LL_Iterate (TIMER_MASTER->ll_timers, timer) {
     uii_add_bulk_output (uii, "Timer %s: Interval %d Base %d Exponent %d \r\n\t"
			       "Jitter %d [%d..%d] Flags 0x%x ",
			  timer->name, timer->time_interval,
			  timer->time_interval_base,
			  timer->time_interval_exponent, 
			  timer->jitter,
			  timer->time_jitter_low, timer->time_jitter_high, 
			  timer->flags);

     if (timer->time_next_fire > 0)
       uii_add_bulk_output (uii, "Timeleft %d\r\n", 
			    timer->time_next_fire -now);
     else
       uii_add_bulk_output (uii, "OFF\r\n");
   }

   pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
   uii_send_bulk_data (uii);
   return (1);
}


int uii_show_help (uii_connection_t *uii) {
  uii_send_data (uii, "\r\nHelp may be requested at any point in a command by entering\r\n");
  uii_send_data (uii, "a question mark '?'.  If nothing matches, the help list will\r\n");
  uii_send_data (uii, "be empty and you must backup until entering a '?' shows the\r\n");
  uii_send_data (uii, "available options.\r\n");
  uii_send_data (uii, "Two styles of help are provided:\r\n");
  uii_send_data (uii, "1. Full help is available when you are ready to enter a\r\n");
  uii_send_data (uii, "   command argument (e.g. 'show ?') and describes each possible\r\n");
  uii_send_data (uii, "   argument.\r\n");
  uii_send_data (uii,
		 "2. Partial help is provided when an abbreviated argument is entered\r\n");
  uii_send_data (uii, "and you want to know what arguments match the input\r\n");
  uii_send_data (uii, "(e.g. 'show pr?'.)\r\n\r\n");
  return (1);
}


/* uii_command_tab_complete
 * tab command completion
 */
void uii_command_tab_complete (uii_connection_t *uii, int alone) {
  LINKED_LIST *ll_match, *ll_tokens, *ll_last_tokens;
  char *completion, *cp, *utoken;
  int partial = 0;
  char *tail;

  /* I want to use Delete in place of free, but it's a macro. */
  ll_last_tokens = LL_Create (LL_DestroyFunction, free, 0);

  ll_tokens = uii_tokenize (uii->buffer, uii->end - uii->buffer);
  ll_match = find_matching_commands (uii->state, ll_tokens, ll_last_tokens, alone);

    completion = NULL;
    tail = LL_GetTail (ll_tokens);

    LL_Iterate (ll_last_tokens, cp) {
        LINKED_LIST *ll = uii_tokenize_choices (cp, strlen (cp));
	char *cp2;
        LL_Iterate (ll, cp2) {
	     /* filter out non-matching items from (..|..|..) */
	    if (!alone && strncasecmp (cp2, tail, strlen (tail)) != 0)
		continue;
	    if (strcmp (cp2, "<cr>") == 0 || cp2[0] == '%')
	        continue;
            else if (completion == NULL) {
          	completion = strdup (cp2);
	    }
            else {
		/* take the common string */
		char *cp3 = completion;
		char *cp4 = cp2;
		while (*cp3 && *cp4 && *cp3 == *cp4) {
		    cp3++;
		    cp4++;
		}
		if (*cp3 != '\0' || *cp4 != '\0')
		    partial++;
		if (*cp3 != '\0')
		    *cp3 = '\0';
	    }
	}
	LL_Destroy (ll);
    }

  /* nothing found */
  if (completion == NULL || *completion == '\0') {
    uii_bell (uii);
    LL_Destroy (ll_tokens);
    LL_Destroy (ll_last_tokens);
    LL_Destroy (ll_match);
    if (completion)
        Delete (completion);
    return;
  }

  if (alone) {
    sprintf (uii->cp, (partial)?"%s":"%s ", completion);
  }
  else {
    utoken = LL_GetTail (ll_tokens);
    cp = completion + (strlen (utoken));
    sprintf (uii->cp, (partial)?"%s":"%s ", cp);
  }

  uii_send_data (uii, "%s", uii->cp);
  uii->cp += strlen (uii->cp);
  uii->end = uii->cp;

  if (partial)
    uii_bell (uii);
  /* clean up memory used */
  LL_Destroy (ll_tokens);
  LL_Destroy (ll_last_tokens);
  LL_Destroy (ll_match);
    if (completion)
        Delete (completion);
}


/* 
 * telnet_option_process
 */
static void telnet_option_process (uii_connection_t *uii_connection, char cp) {

  /* store seqeunce */
  uii_connection->telnet_buf[uii_connection->telnet++] = cp;
  
  if (uii_connection->telnet == 1) return;

  /* DONT */
  if (uii_connection->telnet_buf[1] == 254) {
    if (uii_connection->telnet < 3) return;
    uii_connection->telnet = 0;
    return;
  }

  /* DO */
  if (uii_connection->telnet_buf[1] == 253) {
    if (uii_connection->telnet < 3) return;
    WRITE3 (uii_connection, 255, 251, cp); /* pff agree to do whatever */
    uii_connection->telnet = 0;
    return;
  }

  /* WONT */
  if (uii_connection->telnet_buf[1] == 252) {
    if (uii_connection->telnet < 3) return;
    uii_connection->telnet = 0;
    return;
  }

  /* WILL */
  if (uii_connection->telnet_buf[1] == 251) {
    if (uii_connection->telnet < 3) return;
    uii_connection->telnet = 0;
    return;
  }

  /* SB -- subnegotiation */
  if (uii_connection->telnet_buf[1] == 250) {
    if (uii_connection->telnet < 9) return;
    
    /* AC SB NAWS 0 80 0 64 IAC SE
     * [A window 80 characters wide, 64 characters high]
     */
    uii_connection->tty_max_lines = uii_connection->telnet_buf[6];
    uii_connection->telnet = 0;

    trace (NORM, UII->trace, "UII -- set tty to %d\n", uii_connection->tty_max_lines);
    return;
  }
  
  uii_connection->telnet = 0;
}





/* uii_translate_machine_command_to_human
 * FIX -- we really need to do bounds checking!!
 */
static char *uii_translate_machine_token_to_human (char *token) {
  static char tmp[MAXLINE];
  char *cp;

  cp = tmp;

  if (token[0] != '%') {
    sprintf (cp, "%s ", token);
    cp += strlen (cp);
    return (tmp);
  }

  if (token[1] == 'd') 
    sprintf (cp, "<0-99999> ");
  if (token[1] == 'D') 
    sprintf (cp, "<1-%d> ", MAX_ALIST-1);
  if (token[1] == 'p') 
    sprintf (cp, "<xx.xx.xx.xx/xx> ");
  if (token[1] == 'P') 
    sprintf (cp, "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/xx> ");
  if (token[1] == 'a') 
    sprintf (cp, "<xx.xx.xx.xx> ");
  if (token[1] == 'A') 
    sprintf (cp, "<xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx> ");
  if (token[1] == 'm') 
    sprintf (cp, "<V4 or V6 address/xx> ");
  if (token[1] == 'M') 
    sprintf (cp, "<V4 or V6 address> ");
  if (token[1] == 's') 
    sprintf (cp, "<string> ");
  if (token[1] == 'n') 
    sprintf (cp, "<name> ");
  if (token[1] == 'S') 
    sprintf (cp, "<string...> ");
  
  cp += strlen (cp);

  if (token[2] != '\0') {
    sprintf (cp, token+2);
    cp += strlen (cp);
  }

  return (tmp);
}


typedef struct _pair_string_t {
    char *a;
    char *b;
} pair_string_t;

/* uii_command_help
 * ? command expansion -- list commands that match 
 */ 
static int uii_command_help (uii_connection_t *uii, int alone) {
  uii_command_t *command;
  LINKED_LIST *ll_tokens, *ll_last, *ll_match, *ll_output;
  /*uii_command_t *uii_command = NULL;*/
  char *cp, *tail;
  int found;
  pair_string_t *pp;

  ll_last = LL_Create (LL_DestroyFunction, free, 0);
  ll_output = LL_Create (LL_DestroyFunction, free, 0);
  ll_tokens = uii_tokenize (uii->buffer, uii->end - uii->buffer);

  ll_match = find_matching_commands (uii->state, ll_tokens, ll_last, alone);

  /* make sure it is only added once */
  command = LL_GetHead (ll_match);
  tail = LL_GetTail (ll_tokens);
  LL_Iterate (ll_last, cp) {
    LINKED_LIST *ll;
    char *cp2;

if (!BIT_TEST (command->flag, COMMAND_NODISPLAY)) {
    ll = uii_tokenize_choices (cp, strlen (cp));
    LL_Iterate (ll, cp2) {
      /* pick up matching ones from (..|..|..) */
      if (!alone && strncasecmp (cp2, tail, strlen (tail)) != 0)
	continue;
      found = 0;
      LL_Iterate (ll_output, pp) {
        if (strcmp (pp->a, cp2) == 0) {
	  pp->b = NULL;
	  found = 1;
	  break;
	}
      }	  
      if (!found) {
	pp = New (pair_string_t);
	pp->a = strdup (cp2);
        if (LL_GetCount (command->ll_tokens) <= (LL_GetCount (ll_tokens) + 1))
	  pp->b = command->explanation;
        LL_Add (ll_output, pp);
      }
    }
    LL_Destroy (ll);
}
    command = LL_GetNext (ll_match, command);
  }

  uii_send_data (uii, "\r\n");
  LL_Iterate (ll_output, pp) {
    uii_send_data (uii, "  %-28s",
                   uii_translate_machine_token_to_human (pp->a));
    if (pp->b)
      uii_send_data (uii, "  %s\r\n", pp->b);
    else
      uii_send_data (uii, "  \r\n");
    Delete (pp->a);
  }

  /* clean up memory used */
  LL_Destroy (ll_tokens);
  LL_Destroy (ll_last);
  LL_Destroy (ll_output);
  LL_Destroy (ll_match);

  return (1);
}


/* show_version
 * Display version number. Usually called by UII
 */
static int show_version (uii_connection_t *uii) {
  struct utsname name;

  uname (&name);
  
  uii_send_data (uii, "%s\r\n", MRT_VERSION);
  uii_send_data (uii, "  Compiled  on %s\r\n", __DATE__);
  uii_send_data (uii, "  (%s %s %s %s)\r\n", name.sysname, 
		 name.release, name.version, name.machine);
  uii_send_data (uii, "  UP for %2.2f hours \r\n", 
		 ((float) (time (NULL) - MRT->start_time)) / (60.0*60.0));
  return (1);
}

/* show_threads
 * Display thread and schedule info. Usually called by UII
 */
static int show_threads (uii_connection_t *uii) {
  mrt_thread_t *mrt_thread;

#ifndef HAVE_LIBPTHREAD  
  uii_send_data (uii, "*** NO THREAD SUPPORT ***\r\n");
  uii_send_data (uii, "(Running in degraded mode)\r\n");
#endif /* HAVE_LIBPTHREAD */
  uii_send_data (uii, "Unix Process ID is %d\r\n", getpid ());

  uii_send_data (uii, "[id]\t%-25s\t%-15s%s\t%-15s\r\n", "Name", 
		 "Status", "Queue", "Peak");

  pthread_mutex_lock (&MRT->mutex_lock);
  LL_Iterate (MRT->ll_threads, mrt_thread) {
    int t; 
    char stmp[MAXLINE];

    if (mrt_thread->schedule == NULL)
	sprintf (stmp, "Resident");
    else if (mrt_thread->schedule->lastrun <= 0)
	sprintf (stmp, "Stopped");
    else if ((t = time (NULL) - mrt_thread->schedule->lastrun) < 60)
	sprintf (stmp, "Running");
    else if (t < 3600)
	sprintf (stmp, "Idle %dmins", t/60);
    else
	sprintf (stmp, "Idle %dhurs", t/3600);

    uii_send_data (uii, "[%-2d]\t%-25s\t%-15s%d\t%d\r\n",
		   mrt_thread->thread, mrt_thread->name, 
		   stmp, schedule_count (mrt_thread->schedule), 
		   (mrt_thread->schedule)? mrt_thread->schedule->maxnum: 0);
    
  }
  pthread_mutex_unlock (&MRT->mutex_lock);
  return (1);
}


static int show_sockets (uii_connection_t *uii) {
  Descriptor_Struct *tmp;

  LL_Iterate (SELECT_MASTER->ll_descriptors, tmp) {
     uii_add_bulk_output (uii, "    [%s] socket %2d mask 0x%x\r\n",
			  tmp->name? tmp->name: "noname",
			  tmp->fd,
			  tmp->type_mask);
  }

  
  uii_send_bulk_data (uii);
  return (1);
}


/* terminal monitor -- Ala cisco, divert logging to uii connection */
static int terminal_monitor (uii_connection_t *uii) {
  MRT->trace->uii = (void *) uii;
  uii_send_data (uii, "Logging to this vtty connection...\r\n");  
  return (1);
}

/* no_terminal_monitor
 * no terminal monitor
 */
static int no_terminal_monitor (uii_connection_t *uii) {
  MRT->trace->uii = NULL;
  uii_send_data (uii, "Stop logging to this vtty connection.\r\n");  
  return (1);
}


/* 
 * initialize user interactive interface 
 */
int 
init_uii (trace_t * ltrace)
{
    int i;

    assert (UII == NULL);
    UII = New (uii_t);
    UII->ll_uii_commands = LL_Create (LL_CompareFunction, commands_compare,
				      LL_AutoSort, True, 0);
    UII->ll_uii_connections = LL_Create (0);
    UII->trace = ltrace; 
    UII->port = 0; 
    UII->prefix = NULL; 

    /* initialize prompts */
    for (i = 0; i < UII_MAX_STATE; i++) {
	if (i == UII_PROMPT_MORE)
            UII->prompts[i] = strdup ("----- MORE -----");
	else
	    UII->prompts[i] = strdup ("MRT> ");
    }

   /* add some user interface hooks */
   uii_add_command2 (UII_ALL, COMMAND_NORM, "quit", 
		     uii_quit_level, "Quit from the current level");
   uii_add_command2 (UII_ALL, COMMAND_NORM, "exit", 
		     uii_quit_level, "Quit from the current level");
   uii_add_command2 (UII_NORMAL, COMMAND_NORM, "show threads", 
		     show_threads,
		     "Show status of threads");
   uii_add_command2 (UII_NORMAL, COMMAND_NORM, "show version", 
		     show_version,
		     "Show version and uptime");
   uii_add_command2 (UII_NORMAL, COMMAND_NODISPLAY, "show sockets", 
		     show_sockets,
		     "Show sockets");

   uii_add_command2 (UII_NORMAL, COMMAND_NODISPLAY, "terminal monitor", 
		     terminal_monitor,
		     "Set logging information to UII");
   uii_add_command2 (UII_NORMAL, COMMAND_NODISPLAY, "no terminal monitor", 
		     no_terminal_monitor,
		     "Turn off logging information to UII");

   uii_add_command2 (UII_NORMAL, COMMAND_NODISPLAY, "help", 
		     uii_show_help,
		     "Description of the interactive help system");
   uii_add_command2 (UII_NORMAL, COMMAND_NORM, "show timers", 
		    uii_show_timers,
                    "Show status of timers");
    return (1);
}


void 
print_error_list (uii_connection_t *uii, trace_t *tr)
{
    char *s;

    assert (tr);
    if (tr->error_list == NULL)
	return;

    pthread_mutex_lock (&tr->error_list->mutex_lock);
    LL_Iterate (tr->error_list->ll_errors, s) {
        uii_send_data (uii, "%s", s);
    }
    pthread_mutex_unlock (&tr->error_list->mutex_lock);
}

