/* 
 * $Id: user_util.c,v 1.3 1999/01/26 16:29:14 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 <mrt.h>
#include <trace.h>
#include <select.h>
#include <stdarg.h>
#include <user.h>


#if 0
static void add_last_token (char *ctoken, char *last_ctoken, char *last_utoken,
			    LINKED_LIST *ll_last);
#endif

/* uii_tokenize
 * tokenize a command string (e.g. "show ip access")
 */
LINKED_LIST *uii_tokenize (char *buffer, int len) {
  char command[MAXLINE], *utoken;
#ifdef HAVE_LIBPTHREAD
  char *last;
#endif /* HAVE_LIBPTHREAD */
  LINKED_LIST *ll;

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

  /* tokenize uii string */
  memset (command, 0, MAXLINE);
  memcpy (command, buffer, len);

  utoken = strtok_r (command, " ", &last);
  while (utoken != NULL) {
    LL_Add (ll, strdup (utoken));
    utoken = strtok_r (NULL, " ", &last);
  }

  return (ll);
}



/* uii_tokenize
 * tokenize a command string (e.g. "show ip access")
 */
LINKED_LIST *uii_tokenize_choices (char *buffer, int len) {
  char command[MAXLINE], *utoken, *cp;
#ifdef HAVE_LIBPTHREAD
  char *last;
#endif /* HAVE_LIBPTHREAD */
  LINKED_LIST *ll;

  ll = LL_Create (LL_DestroyFunction, free, 0);
  assert (sizeof (command) > len);
  memcpy (command, buffer, len);
  command[len] = '\0';

  if (*buffer != '(' && *buffer != '[') {
    LL_Add (ll, strdup (command));
    return (ll);
  }
  /* tokenize uii string */

  cp = command +1;

  utoken = strtok_r (cp, "|", &last);
  while (utoken != NULL) {
    LL_Add (ll, strdup (utoken));
    utoken = strtok_r (NULL, (*buffer == '(')? "|)": "|]", &last);
  }

  return (ll);
}


/* uii_token_match
 */
int uii_token_match (char *ctoken, char *user_token) {
  char *cp, *buf;
#ifdef HAVE_LIBPTHREAD
  char *last;
#endif /* HAVE_LIBPTHREAD */
  int okay;

  /* match one in the set */
  if (ctoken[0] == '(') {
    cp = buf = strdup ((ctoken +1));
    cp[strlen(cp)-1] = '\0'; /* get rid of terminating ) */
    cp = strtok_r (cp, "|", &last);
    
    okay = 0;
    while (cp != NULL) {
      if (uii_token_match (cp, user_token)) {
	okay = 1;
	break;
      }
      cp = strtok_r (NULL, "|", &last);
    }

    Delete (buf);
    return (okay);
  }


  if (ctoken[0] == '%') {
    int i;

    switch (ctoken[1]) {
    case 'p': /* ipv4 prefix */
      if (is_ipv4_prefix (user_token))
	return (1);
      break;
#ifdef HAVE_IPV6
    case 'P': 
      if (is_ipv6_prefix (user_token)) 
	return (1);
      break;
#endif /* HAVE_IPV6 */
    case 'a': /* ipv4 prefix */
      if (is_ipv4_prefix (user_token) && !strchr (user_token, '/'))
	return (1);
      break;
#ifdef HAVE_IPV6
    case 'A': 
      if (is_ipv6_prefix (user_token) && !strchr (user_token, '/')) 
	return (1);
      break;
#endif /* HAVE_IPV6 */
    case 'M': 
      if (strchr (user_token, '/'))
	return (0);
    /* FALL THROUGH */
    case 'm': 
      if (is_ipv4_prefix (user_token))
	return (1);
#ifdef HAVE_IPV6
      if (is_ipv6_prefix (user_token)) 
	return (1);
#endif /* HAVE_IPV6 */
      break;
    case 'n': /* name */
      if (!isalpha (user_token[0]))
	return (0);
      for (i = 1; user_token[i]; i++) {
        if (!isalnum (user_token[i]))
	    return (0);
      }
      return (1);
    case 's': /* any string */
      return (1);
    case 'D': /* integer (1..MAX_ALIST) */
      for (i = 0; user_token[i]; i++) {
        if (!isdigit (user_token[i]))
	    return (0);
      }
      i = atoi (user_token);
      if (i > 0 && i < MAX_ALIST)
        return (1);
      break;
    case 'd': /* integer */
      for (i = 0; user_token[i]; i++) {
        if (!isdigit (user_token[i]))
	    return (0);
      }
      return (1);
    default:
      break;
    }
    return (0);
  }

  /* a literal */
  if (!strncasecmp (ctoken, user_token, strlen (user_token))) 
    return (1);

  return (0);
}


/* find_matching_commands
 */
LINKED_LIST *find_matching_commands (int state, LINKED_LIST *ll_tokens,
				     LINKED_LIST *ll_last, int alone) {
  char *utoken, *ctoken;
  LINKED_LIST *ll_match;
  uii_command_t *uii_command;
  char *last_ctoken= NULL, *last_utoken = NULL;
  int skip = 0;
  int optional;

  ll_match = LL_Create (0);

  /* okay, now run through the list of commands and see who matches most */
  LL_Iterate (UII->ll_uii_commands, uii_command) {
    if (state >= UII_NORMAL && uii_command->state == UII_ALL) {
	/* OK */
    }
    else if (state >= UII_CONFIG && uii_command->state == UII_CONFIG_ALL) {
	/* OK */
    }
    else if (state != uii_command->state)
      continue;

    last_ctoken = ctoken = LL_GetHead (uii_command->ll_tokens);
    last_utoken = utoken = LL_GetHead (ll_tokens);
    skip = 0;

    /* for password and comments -- see if any of the first part of command matches */
    if ((utoken != NULL) && (uii_command->flag & COMMAND_MATCH_FIRST)) {
      if (!strncasecmp (uii_command->string, utoken, strlen (uii_command->string))) {
	 LL_Add (ll_match, uii_command);
	 continue;
      }
    }

    /* command may be prefaced with a no -- special case */
    /* if ((utoken != NULL) && (!strcasecmp ("no", utoken)))*/
    /* last_utoken = utoken = LL_GetNext (ll_tokens, utoken);*/

    optional = 0;
    while ((utoken != NULL) && (ctoken != NULL)) {

      /* accept everything else to end of line*/
      if (strcmp (ctoken, "%S") == 0) {
        last_ctoken = ctoken;
        last_utoken = utoken;
        ctoken = NULL; /* shouldn't follow anything */
        utoken = NULL; /* simulate eating up to the last */
	break;
      }
      
      /* optional command */
      if (ctoken[0] == '[') {
	if (!uii_token_match ((ctoken+1), utoken)) {
	  skip = 1;
	  break;
	}
      }
      /* mandatory command */
      else if (!uii_token_match (ctoken, utoken)) {
	skip = 1;
	break;
      }

      last_ctoken = ctoken;
      last_utoken = utoken;
      ctoken = LL_GetNext (uii_command->ll_tokens, ctoken);
      utoken = LL_GetNext (ll_tokens, utoken);
    }

    if (skip == 1) continue;
    if ((utoken != NULL) && (ctoken == NULL)) continue;

    LL_Add (ll_match, uii_command);
    if (ll_last == NULL)
      continue;

    if (alone) {
        assert (utoken == NULL);
        if (ctoken == NULL) {
            LL_Add (ll_last, strdup ("<cr>"));
        }
        else if (ctoken[0] == '(' || ctoken[0] == '[') {
	    LL_Add (ll_last, strdup (ctoken));
        }
        else {
	    LL_Add (ll_last, strdup (ctoken));
        }
    }
    else {
        assert (last_ctoken);
        if (last_ctoken[0] == '(' || last_ctoken[0] == '[') {
	    LL_Add (ll_last, strdup (last_ctoken));
        }
        else {
	    LL_Add (ll_last, strdup (last_ctoken));
        }
    }
  }

  return (ll_match);
}


#ifdef notdef
static void add_last_token (char *ctoken, char *last_ctoken, char *last_utoken,
			    LINKED_LIST *ll_last) {

  /* done with current command -- get next */
  if ((last_utoken == NULL) ||
      ((last_ctoken != NULL) && 
       ((!strcmp (last_ctoken, last_utoken)) || (last_ctoken[0] == '%')))) {
    if (ctoken != NULL)
      LL_Add (ll_last, strdup (ctoken));
    else
      LL_Add (ll_last, strdup ("<cr>"));
    return;
  }

  if (last_ctoken == NULL) {
    LL_Add (ll_last, strdup ("<cr>"));
    return;
  }
      
  LL_Add (ll_last, strdup (last_ctoken));
  return;
}
#endif



/* uii_send_data
 * send formatted data out a socket
 */
int uii_send_data (uii_connection_t * uii_connection, ...) {
    va_list ap;
    char *format, *newline;
    char line[MAXLINE];
    char *cp = line;

    if (uii_connection == NULL)
	return (-1);
    if (uii_connection->sockfd_out < 0 && uii_connection->sockfd < 0)
	return (-1);

    va_start (ap, uii_connection);
    format = va_arg (ap, char *);
    vsprintf (cp, format, ap);

    while ((newline = strchr (cp, '\n'))) {
        int len = newline - cp + 1;
	if (len >= 2 && cp[len-2] == '\r') {
            if (uii_connection->sockfd_out >= 0) {
	        if (write (uii_connection->sockfd_out, cp, len-2)  < len-2 ||
		    write (uii_connection->sockfd_out, "\n", 1) < 1) {
		    uii_connection->sockfd_out = -1; /* turn off redirection */
		    return (-1);
	        }
            }
            else if (uii_connection->sockfd >= 0) {
	        if (write (uii_connection->sockfd, cp, len) < len) {
		    uii_destroy_connection (uii_connection);
		    return (-1);
	        }
    	    }
	}
	else {
    	    if (uii_connection->sockfd_out >= 0) {
	        if (write (uii_connection->sockfd_out, cp, len) < len) {
		    uii_connection->sockfd_out = -1; /* turn off redirection */
		    return (-1);
	        }
            }
            else if (uii_connection->sockfd >= 0) {
	        if (write (uii_connection->sockfd, cp, len-1) < len-1 ||
	            write (uii_connection->sockfd, "\r\n", 2) < 2) {
		    uii_destroy_connection (uii_connection);
		    return (-1);
	        }
    	    }
	}
	cp = newline + 1;
    }
    if (uii_connection->sockfd_out >= 0) {
	int len = strlen (cp);
        if (write (uii_connection->sockfd_out, cp, len) < len) {
	    uii_connection->sockfd_out = -1; /* turn off redirection */
	    return (-1);
	}
    }
    else if (uii_connection->sockfd >= 0) {
	int len = strlen (cp);
        if (write (uii_connection->sockfd, cp, len) < len) {
	    uii_destroy_connection (uii_connection);
	    return (-1);
        }
    }
    return (1);
}


/* user_notice
 * Convenience function to send error message both to log file
 * and out to uii
 */
void
user_notice (int trace_flag, trace_t *tr, uii_connection_t *uii, 
	     char *format, ...)
{
    va_list args;
    char tmp1[MAXLINE], tmp2[MAXLINE], *cp1, *cp2;

    va_start (args, format);
    vsprintf (tmp1, format, args);  /* concatenate */

    /* we should take a more good way to handle a "\r\n" for a console
       just a "\n" for a file -- masaki */
    for (cp2 = tmp2, cp1 = tmp1; *cp1; cp1++) {
	if (*cp1 == '\r')
	    continue;
	*cp2++ = *cp1;
    }
    *cp2 = '\0';
    trace (trace_flag, tr, "%s", tmp2);

    for (cp2 = tmp2, cp1 = tmp1; *cp1; cp1++) {
	if (*cp1 == '\n' && (cp1 <= tmp1 || cp1[-1] != '\r'))
	    *cp2++ = '\r';
	*cp2++ = *cp1;
    }
    *cp2 = '\0';
    if (uii->sockfd >= 0)
      uii_send_data (uii, "%s", tmp2);
}

