/*
 * $Id: util.c,v 1.25 1999/11/10 04:11:59 gerald Exp $
 * originally Id: util.c,v 1.51 1998/08/07 19:48:58 gerald Exp 
 */

#include <stdio.h>
#include <string.h>
#include <mrt.h>
#include <trace.h>
#include <time.h>
#include <signal.h>
#include <config_file.h>
#include <limits.h>
#include "irrd.h"
#include <fcntl.h>
#include <ctype.h>

extern key_label_t key_info[][2];
extern trace_t *default_trace;
extern m_command_t m_info [];

/* find_database
 * Given the name of a database (e.g. "mae-east") return a pointer to
 * the irr_database_t sructure, or NULL if it does not exist
 */
irr_database_t *find_database (char *name) {
  irr_database_t *db;
  int len;
  
  LL_Iterate (IRR.ll_database, db) {
    len = strlen (db->name);
    if (strlen (name) > len)
      len = strlen (name);

    if (!strncasecmp (db->name, name, len))
      return (db);
  }
  return (NULL);
}

/* irr_lock_all
 * Lock down all IRR database used by this IRR connection
 */
void irr_lock_all (irr_connection_t *irr) {
  irr_database_t *database;

  /* Avoid deadlock, only 1 routine can get all locks at one time */
  trace (TRACE, default_trace, "About to lock  --lock_all_mutex_lock--\n");
  if (pthread_mutex_lock (&IRR.lock_all_mutex_lock) != 0)
    trace (NORM, default_trace, "Error locking --lock_all_mutex_lock--: %s\n", 
	   strerror (errno));
  else
    trace (TRACE, default_trace, "Locked --lock_all_mutex_lock--\n");

  LL_ContIterate (irr->ll_database, database) {
    irr_lock (database);
  }

  if (pthread_mutex_unlock (&IRR.lock_all_mutex_lock) != 0)
    trace (NORM, default_trace, "Error unlocking --lock_all_mutex_lock--: %s\n",
           strerror (errno));
  else
    trace (TRACE, default_trace, "Unlocked --lock_all_mutex_lock--\n");

  trace (TRACE, default_trace, "Done with lock_all\n");
}


/* irr_unlock_all
 * Unlock down all IRR database used by this IRR connection
 */
void irr_unlock_all (irr_connection_t *irr) {
  irr_database_t *database;

  LL_ContIterate (irr->ll_database, database) {
    irr_unlock (database);
  }
}

void irr_lock (irr_database_t *database) {
  trace (TRACE, default_trace, "About to lock database %s\n", database->name);


  /*if (rwl_writelock (&database->rwlock) != 0)*/
  if (pthread_mutex_lock (&database->mutex_lock) != 0)
    trace (NORM, default_trace, "Error locking database %s : %s\n", 
	   database->name, strerror (errno));
  else
    trace (TRACE, default_trace, "Locked database %s\n", database->name);
}

void irr_unlock (irr_database_t *database) {

  if (pthread_mutex_unlock (&database->mutex_lock) != 0)
  /*if (rwl_writeunlock (&database->rwlock) != 0) */
    trace (NORM, default_trace, "Error unlocking database %s : %s\n", 
	   database->name, strerror (errno));
  else
    trace (TRACE, default_trace, "Unlocked database %s\n", database->name);

}

void Delete_RT_Object (irr_route_object_t *attr) {
  Delete (attr);
}

int irr_comp (char *s1, char *s2) {
  return (strcmp (s1, s2));
}


int get_prefix_from_disk (FILE *fd, u_long offset, char *buffer) {
  char *p, temp[MAXLINE], *last; 

  if (fseek (fd, offset, SEEK_SET) < 0) 
    trace (NORM, default_trace, "** Error ** fseek failed in get_prefix");
    
  if (fgets (temp, sizeof (temp) - 1, fd) != NULL) {
    if (strtok_r (temp, " ", &last) != NULL && 
	(p = strtok_r (NULL, " ", &last)) != NULL) {
      if (*(p + strlen (p) - 1) ==  '\n')
        *(p + strlen (p) - 1) = '\0';
      strcat (buffer + strlen (buffer), p);
      return 1;
    }
    else 
      trace (NORM, default_trace, 
	     "ERROR -- get_prefix_from_disk(): bad route field (%s)\n", temp);
  }
  else
    trace (NORM, default_trace, 
	   "ERROR -- get_prefix_from_disk(): offset is bad %lu\n", offset);

  return -1;
}


/* copy_irr_object
 * Copy an object from one <DB>.db file to another
 * this is used in updates and in resyching database
 */
long copy_irr_object (FILE *src_fd, long offset, irr_database_t *database,
                     u_long obj_length) {
  char buffer[MAXLINE];
  long start_offset = 0;
  int bytes_read   = 0;
  int i;

  if ((database->fd == (void *) -11) || 
      (database->fd == (void *) -1)  ||
      (database->fd == NULL))
    return -1L;
  
  if ((fseek (src_fd, offset, SEEK_SET) < 0) ||
      (fseek (database->fd, 0, SEEK_END) < 0)) 
    trace (NORM, default_trace, "** Error ** fseek failed in copy_irr_object");
  start_offset = ftell (database->fd);

  bytes_read = 0;
  while (fgets (buffer, sizeof (buffer)-1, src_fd) != NULL) {
    if ((i = strlen (buffer)) < 2) break;

    if (bytes_read < obj_length) {
      fwrite (buffer, 1, (size_t) i, database->fd);
      database->bytes += i;
    }
     bytes_read += i;
  }
  sprintf (buffer, "\n");
  fwrite (buffer, 1, strlen (buffer), database->fd);
  database->bytes += strlen (buffer);

  return start_offset;
}


/* Delete_IRR_Object
 * Delete generic object used to hold data during scanning
 */
void Delete_IRR_Object (irr_object_t *object) {
/* ll_as and ll_mbr_by_ref are saved as data for the hash */
  if (object->name) 
    Delete (object->name);
  if (object->nic_hdl)
    Delete (object->nic_hdl);
  if (object->ll_mnt_by) 
    LL_Destroy (object->ll_mnt_by);
  if (object->ll_as) 
    LL_Destroy (object->ll_as);
  if (object->ll_keys) 
    LL_Destroy (object->ll_keys);
  if (object->ll_community)
    LL_Destroy (object->ll_community);
  if (object->ll_mbr_by_ref) 
    LL_Destroy (object->ll_mbr_by_ref);
  if (object->ll_mbr_of) 
    LL_Destroy (object->ll_mbr_of);
  if (object->ll_prefix) 
    LL_Destroy (object->ll_prefix);
  Delete (object);
}

/* New_IRR_Object
 * Create generic object used to hold data during scanning
 */
irr_object_t *New_IRR_Object (char *buffer, u_long position, u_long mode) {
  irr_object_t *irr_object;

  irr_object = New (irr_object_t);
  irr_object->offset = position;
  irr_object->mode = mode;
  irr_object->type = NO_FIELD;
  irr_object->ll_keys = LL_Create (LL_DestroyFunction, free, 0);
  irr_object->ll_prefix = LL_Create (LL_DestroyFunction, free, 0);
  irr_object->filter_val = XXX_F;

  return (irr_object);
}


/* init_key
 * Pick-off the primary key field (ie, the usual first object field)
 * You cannot assume for example that 'route:' will be the first field
 * as this is *not* an rpsl or ripe181 requirement.  
 */
void init_key (char *buf, int curr_f, irr_object_t *object, enum DB_SYNTAX syntax) {
  char *cp;

  if (object->name == NULL) { /* could get here twice if bad syntax object */
    cp = buf + key_info[curr_f][syntax].len;
    whitespace_remove (cp);
    object->name = strdup (cp);
    object->type = curr_f;
    object->filter_val = key_info[curr_f][syntax].filter_val;
  }
}

void Delete_Ref_keys (reference_key_t *ref_item) {
  Delete (ref_item->key);
  Delete (ref_item);
}

void foldin_key_list (LINKED_LIST **ll, char *key, int type) {
  int found;
  reference_key_t *ref_item;

  if (key == NULL)
    return;

  if (*ll == NULL) /* this way we know if *ll != NULL, list is non-empty */
    *ll = LL_Create (LL_DestroyFunction, Delete_Ref_keys, NULL);

  found = 0;
  LL_ContIterate ((*ll), ref_item) {
    if (!strcmp (ref_item->key, key)) {
      found = 1;
      break;
    }
  }

  if (!found) {
    ref_item = New (reference_key_t);
    ref_item->key = strdup (key);
    ref_item->type = type;
    LL_Add ((*ll), ref_item);
  }
}


void pick_off_indirect_references (irr_answer_t *irr_answer, LINKED_LIST **ll) {
  char *cp, buf[MAXLINE];
  int state  = BLANK_LINE, curr_f = NO_FIELD;
    
  if (irr_answer->type == ROUTE || irr_answer->type == PERSON)
    return;

  if (irr_answer->len == 0 ||
      fseek (irr_answer->fd, irr_answer->offset, SEEK_SET) < 0)
    return;

  do {
    cp = fgets (buf, MAXLINE - 1, irr_answer->fd);

    if ((state = get_state (buf, cp, state)) == START_F) {
      curr_f = get_curr_f (irr_answer->db_syntax, buf, state, curr_f);

      /* all fields here must be *single valued*, else code won't work */
      if ((irr_answer->type == IPV6_SITE && 
	   (curr_f == PREFIX || 
	    curr_f == CONTACT)) ||
	  (curr_f == ADMIN_C || curr_f == TECH_C)) {
	cp = buf + key_info[curr_f][irr_answer->db_syntax].len;
	whitespace_newline_remove (cp);
	foldin_key_list (ll, cp, curr_f);
      }
    }
  } while (state != BLANK_LINE && state != DB_EOF);

  return;
}

void lookup_object_references (irr_connection_t *irr) {
  irr_answer_t *irr_answer;
  LINKED_LIST *ll = NULL;
  reference_key_t *ref;

  LL_ContIterate (irr->ll_answer, irr_answer)
    pick_off_indirect_references (irr_answer, &ll);
  
  if (ll) {
    LL_ContIterate (ll, ref) {
      /*
printf("JW: indirect key lookup-(%s,%d)\n",ref->key,ref->type);
*/
      irr_database_find_matches (irr, ref->key, SECONDARY, 
				 RAWHOISD_MODE, ref->type, NULL, NULL);
    }
    LL_Destroy (ll);
  }
}

void lookup_route_exact (irr_connection_t *irr, char *key) {
  irr_database_t *database;
  u_long offset, len = 0;
  char *last, *prefix, *str_orig;
  u_short origin;

  while (*key != '\0' && isspace ((int) *key)) key++;
  if (*key == '\0')
    return;

  if (strchr (key, '-') != NULL)
    prefix = strtok_r (key, "-", &last);
  else 
    prefix = strtok_r (key, " ", &last);

  if (prefix == NULL)
    return;

  if ((str_orig = strtok_r (NULL, " ", &last)) == NULL)
    return;

  while (*str_orig != '\0' && !isdigit ((int) *str_orig)) str_orig++;
  if (*str_orig == '\0')
    return;

  origin = (u_short) atoi (str_orig);
  LL_Iterate (irr->ll_database, database) {
    if (seek_route_object (database, prefix, origin, &offset, 
			   &len, irr->withdrawn) > 0)
      break;
  }

  if (len > 0)
    irr_build_answer (irr, database->fd, ROUTE, offset, len, NULL, database->db_syntax);
}

/* convert a string to an unsigned long
 * return 1 if no errors found
 * otherwise return -1
 */
int convert_to_lu (char *strval, u_long *uval) {
  char *p;
  u_long d;
  
  /* see if strval points to anything */
  if ((p = strval) == NULL)
    return (-1);

  /* check for any non-numeric characters */
  for (; *p != '\0' && isdigit ((int) *p); p++);
  if (*p != '\0')
    return (-1);

  /* make sure the value is in range and has non-zero chars */
  d = strtol (strval, NULL, 10);  
  if (d == ULONG_MAX || p == strval)
    return (-1);

  *uval = d;
  return (1);
}



irr_hash_string_t *new_irr_hash_string (char *str) {
  irr_hash_string_t *tmp;
  tmp = New (irr_hash_string_t);
  tmp->string = strdup (str);
  return (tmp);
}


void delete_irr_hash_string (irr_hash_string_t *str) {
  Delete (str->string);
  Delete (str);
}

/*
 * -s flag has been found, now return the token after the '-s'.
 * Return 1 if something is found after the -s, else return -1.
 */
int ripe_set_source (irr_connection_t *irr, char **cp) {
  char *p;
 
  while (**cp != '\0' && isspace ((int) **cp)) (*cp)++;

  if (**cp == '\0')
    return -1;
  else
    p = *cp;

  while (**cp != '\0' && isgraph ((int) **cp)) (*cp)++;

  strncpy (irr->ripe_tmp, p, *cp - p);
  irr->ripe_tmp[*cp - p] = '\0';

  return 1;
}

/*
 * convert a "<object type>" in a '-t' flag from string to enum IRR_OJBECT.
 * return 1 if a valid object type is found, else return -1;
 */
int ripe_obj_type (irr_connection_t *irr, char **cp) {
  int i, j, slen;

  while (**cp != '\0' && isspace ((int) **cp)) (*cp)++;
  
  if (**cp == '\0')
    return -1;
  
  
  slen = strlen (*cp);
  for (i = 0; i < IRR_MAX_MCMDS; i++) {
    j = strlen (m_info[i].command);
    if (slen < j &&
        !strncasecmp (*cp, m_info[i].command, j - 1)) {
      irr->ripe_type = m_info[i].type;
      (*cp) += strlen (m_info[i].command) - 1;
      return 1;
    }
  }

  return -1;
}

void ignore_flag_param (irr_connection_t *irr, char **cp) {

  while (**cp != '\0' && isgraph ((int) **cp)) (*cp)++;
}

/*
 * Parse the ripe whois command line and set the corresponding flag
 * (and flag value if necessary).
 * This routine moves the cursor pointer used by the ripe command
 * processing routine.
 *
 * RETURN values:
 *
 *   return 1 if no flags encountered or supplied flags were
 * parsed without error.
 *
 *   return -1 if an error is encountered parsing
 * a flag. 
 */
int parse_ripe_flags (irr_connection_t *irr, char **cp) {
  int non_flag_token;
  char buf[MAXLINE];
  char *p = *cp; /* save pointer to input line for trace output */

  /* buffer for error messages */
  buf[0] = '\0';

  for (non_flag_token = 0; **cp != '\0'; (*cp)++) {
    if (**cp == '-') {
      (*cp)++;
      switch (**cp) {
      case 'F': irr->ripe_flags |= FAST_OUT;    break;
      case 'r': irr->ripe_flags |= RECURS_OFF;  break;
      case 'l': irr->ripe_flags |= LESS_ONE;    break;
      case 'L': irr->ripe_flags |= LESS_ALL;    break;
      case 'm': irr->ripe_flags |= MORE_ONE;    break;
      case 'M': irr->ripe_flags |= MORE_ALL;    break;
      case 'a': irr->ripe_flags |= SOURCES_ALL; break;
      case 's': (*cp)++;
                if (ripe_set_source (irr, cp) > 0)
                  irr->ripe_flags |= SET_SOURCE;  
                else {
                  strcpy (buf, "%%  Required DB source not specified after \"-s\" flag.\n");
                  non_flag_token = 1;
                }
                break;
      case 't': (*cp)++;
                if (ripe_obj_type (irr, cp) > 0)
                  irr->ripe_flags |= TEMPLATE;
                else {
		  strcpy (buf, "%%  Required object type not specified after \"-t\" flag or unrecognized type.\n");
		  non_flag_token = 1;
		}
                break;
      case 'T': (*cp)++;
                if (ripe_obj_type (irr, cp) > 0)
                  irr->ripe_flags |= OBJ_TYPE;
                else {
		  strcpy (buf, "%%  Required object type not specified after \"-T\" flag or unrecognized type.\n");
		  non_flag_token = 1;
		}
                break;
      case 'V': ignore_flag_param (irr, cp);
                break;
      default : non_flag_token = 1;             
                sprintf (buf, "%%  Unrecognized flag \"-%c\".\n", **cp);
	        trace (NORM, default_trace, 
	        "ERROR:parse_ripe_flags(): unrecognized flag '%c'\n", **cp);
	        trace (NORM, default_trace, 
	        "ERROR:parse_ripe_flags(): rest of input line (%s)", p);
		(*cp)--;
		break;
      }
    }
    else if (**cp == ' ' ||
	     **cp == '\t')
      continue;
    else 
      non_flag_token = 1;

    if (non_flag_token)
      break;
  }

  if (buf[0] != '\0') { /* Got an error, tell the user */
    irr_write (irr, irr->sockfd, buf, strlen (buf));
    return -1;
  }

  return 1;
}

void IRRD_Delete_Node (radix_node_t *node) {
  LINKED_LIST *ll_attr;

  ll_attr = (LINKED_LIST *) node->data;
  if (ll_attr) LL_Destroy (ll_attr);

  if (node->prefix)
    Delete_Prefix (node->prefix);
  Delete (node);
}

radix_str_t *new_radix_str (radix_node_t *node) {
  radix_str_t *tmp;
  tmp = New (radix_str_t);
  tmp->ptr = node;
  return (tmp);
}


void delete_radix_str (radix_str_t *str) {
  IRRD_Delete_Node (str->ptr);
  Delete (str);
}




/* prefix_tobitstring
 * convert a prefix to 110000111
 * This is used so we don't need a radix tree and can store prefixes
 * in a dbm file
 * Probably better ways to write this code...
 */
char *prefix_tobitstring (prefix_t *prefix) {
  char *tmp, *cp, *p;
  u_char bit;
  u_long i, b;
  int n, masklen;


  p = tmp = malloc (34);
  cp = prefix_tochar (prefix);
  masklen = 0;

  for (n=0; n<= 3; n++) {
    b = cp[n];
    bit = 128;
    i = 0;

    while (i++ <= 7) {
      if (masklen == prefix->bitlen) 
	goto done;

      if ( (b & bit)) {
        sprintf (p, "1"); p += strlen (p);
      }
      else
        sprintf (p, "0"); p += strlen (p);
    
      bit = bit >> 1;
      masklen++;
    }
  }

done:
  while (masklen++ < 32) {
    sprintf (p, "-"); p += strlen (p);
  }

  return (tmp);
}




void convert_toupper(char *_z) {

  while (*_z != '\0') {
    *_z = toupper((int) *_z);
    _z++;
  }
}


#undef ONLY_NEEDED_WITH_QUANTIFY_FOR_SOME_REASON 

#ifdef ONLY_NEEDED_WITH_QUANTIFY_FOR_SOME_REASON
const char *
inet_ntop (int af, const void *src, char *dst, size_t size)
{
    char *buf;
    struct in_addr in;
    int len;

    /* We can only support IPv4 packets */
    if (af == AF_INET) {
	bcopy(src, &in, 4);
	buf = inet_ntoa(in);

	/* Check length of returned answer and truncate if necessary */
	len = strlen(buf) + 1;
	bcopy(buf, dst, (size < len) ? size : len);

	/* Null terminate dst buffer if necessary */
	if (size < len) {
	    dst[size-1] = 0;
	    errno = ENOSPC;
	    return NULL;
	} else
	    return dst;
    } else {
	errno = EAFNOSUPPORT;
	return NULL;
    }
}


int
inet_pton (int af, const char *src, void *dst)
{
    u_long result;  

    if (af == AF_INET) {
	result = inet_addr(src);
	if (result == -1)
	    return 0;
	else {
	    bcopy(&result, dst, 4);
	    return 1;
	}
    } else {
	errno = EAFNOSUPPORT;
	return -1;
    }
}
#endif



/* convert time_t to something more readable */
void nice_time (long seconds, char *buf) {

  if (seconds <= 0) {
    sprintf (buf, "--:--:--");
    return;
  }

  if (seconds < 200) {
    sprintf (buf, "%ld seconds", seconds);
    return;
  }

  if (seconds / 3600 > 99)
    sprintf (buf, "%02lddy%02ldhr", 
	     seconds / (3600 * 24), (seconds % (3600 * 24)) / 3600);
  else
    sprintf (buf, "%02ld:%02ld:%02ld", 
	     seconds / 3600, (seconds / 60) % 60, seconds % 60);

  return;
}



/* my_fgets
 * Read buffered I/O in large chunks (and fewer system calls)
 * then line-by-line fgets
 */
char *my_fgets (irr_database_t *db, FILE *fd) {
  char *cp, *start;
  int n;
  
  /* first time -- initialize */
  if (db->read_cur_ptr == NULL) {
    db->read_buf = malloc (IRRD_FAST_BUF_SIZE + 2);
    n = fread (db->read_buf, 1,IRRD_FAST_BUF_SIZE, fd);
    db->read_cur_ptr = db->read_buf;
    db->read_end_ptr = db->read_buf + n;
    cp = db->read_cur_ptr;
  }
  /* another pass */
  else {
    cp = db->read_cur_ptr;
    *cp = (char) db->last_char;
  }

  while (1) {
    if ((cp < db->read_end_ptr) && (*cp == '\n')) {
      cp++; /* advance end */
      db->last_char = *cp; /* save off last character */
      *cp = '\0'; /* null terminate */
      start = db->read_cur_ptr;
      db->read_cur_ptr = cp;
      return (start);
    }

    /* 
     * at end of buffer -- repopulate 
     */
    if (cp >= db->read_end_ptr) {
      n = db->read_end_ptr- db->read_cur_ptr;

      if (n < 0) {n = 0;}

      memcpy (db->read_buf, db->read_cur_ptr, n);
      db->read_cur_ptr = db->read_buf;
      
      cp = db->read_buf + n;
      if ((n = IRRD_FAST_BUF_SIZE - (cp - db->read_buf)) > 0) {
	n = fread (cp, 1, n, fd);
	/*printf ("read %d\n", n);*/
	if (n == 0) {return (NULL);}
      }

      /* check if eof */
      db->read_end_ptr = cp + n;
      db->read_cur_ptr = db->read_buf;
      continue;
    }


    /*if (*cp == '\0') {
      printf ("ZEWOOO \n");
      exit (0);
      }*/

    cp++;
  }


}
