/*
 * $Id: rpsl_commands.c,v 1.10 2002/10/17 20:02:31 ljb Exp $
 * originally Id: rpsl_commands.c,v 1.22 1998/07/31 15:42:39 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 <sys/types.h>
#include <ctype.h>
#include "radix.h"
#include "hash.h"
#include "stack.h"
#include <fcntl.h>
#include "irrd.h"

extern trace_t *default_trace;
/* a list of prefix range types */
enum EXPAND_TYPE {
  NO_EXPAND, ROUTE_SET_EXPAND, OTHER_EXPAND };

/* local routines */
void update_members_list (irr_database_t *database, char *range_op,
			enum EXPAND_TYPE expand_flag, 
			LINKED_LIST *ll_member_examined, 
			LINKED_LIST *ll_setlist, LINKED_LIST *ll_set_names, 
			STACK *stack, irr_connection_t *irr);
void mbrs_by_ref_set (irr_database_t *database, char *range_op,
	enum EXPAND_TYPE expand_flag, LINKED_LIST *ll_setlist,
	char *set_name, LINKED_LIST *ll_mbr_by_ref, irr_connection_t *irr);
char *rpsl_macro_expand_add (char *range, char *name,
				irr_connection_t *irr, char *dbname);
void SL_Add (LINKED_LIST *ll_setlist, char *member, char *range_op, enum EXPAND_TYPE expand_flag, irr_connection_t *irr, irr_database_t *database);
int set_name (char *);

/* as-set/rs-set expansion !ias-bar */
void irr_set_expand (irr_connection_t *irr, char *name) {
  irr_database_t *database;
  STACK *stack;
  LINKED_LIST *ll_member_examined, *ll_setlist;
  char *set_name, *last_set_name, *mstr, *db;
  char *lasts, *range_op, key[BUFSIZE];
  int first, dup, expand_flag = NO_EXPAND;
  hash_spec_t *hash_spec;

  if (strchr(name, ',') != NULL) {
    strtok_r(name, ",", &lasts);
    /* check if we are expanding a route-set */
    if (strchr(name, ':') != NULL)
      set_name = strchr(name,':') + 1;
    else
      set_name = name;
    if (!strncasecmp (set_name, "rs-", 3))
      expand_flag = ROUTE_SET_EXPAND;
    else
      expand_flag = OTHER_EXPAND;
  }

  convert_toupper (name);
  stack = Create_STACK (500);
  ll_member_examined = LL_Create (LL_DestroyFunction, free, NULL);
  ll_setlist = LL_Create (LL_AutoSort, True, LL_CompareFunction, irr_comp, 
			 LL_DestroyFunction, free, NULL);
  mstr = rpsl_macro_expand_add (" ", name, irr, NULL);
  Push (stack, mstr);
  LL_Add (ll_member_examined, strdup (name));

  while (Get_STACK_Count (stack) > 0) {
    mstr = (char *) Pop (stack);
    /* might want to check the examined list to see if this set name
       has been examined already */
    first = 1;
    lasts = NULL;
    range_op = strtok_r (mstr, ",", &lasts);
    if (!strcmp (range_op, " ")) range_op = NULL;
    set_name = strtok_r (NULL, ",", &lasts);

    while ((db = strtok_r (NULL, ",", &lasts)) != NULL) {
      if ((database = find_database (db)) == NULL) {
        trace (ERROR, default_trace, "irr_set_expand(): Database not found %s\n", db);
        break;
      }
      irr_lock (database);
      make_setobj_key (key, set_name);
      if ((hash_spec = fetch_hash_spec (database, key, UNPACK)) != NULL) {

          first = 0; 
	  update_members_list (database, range_op, expand_flag, ll_member_examined, 
			       ll_setlist, hash_spec->ll_1, stack, irr);
	  mbrs_by_ref_set (database, range_op, expand_flag, ll_setlist, 
                              set_name, hash_spec->ll_2, irr);
	  Delete_hash_spec (hash_spec); 
      }
      irr_unlock (database);
      if (first == 0)
        break;
    }
    free (mstr);
  }

  first = 1;
  dup = 0;
  last_set_name = "";
  LL_ContIterate (ll_setlist, set_name) {
    if (!first) {
       /* since list is sorted, any duplicates should be consecutive */
      if (strcmp (last_set_name, set_name) == 0)
        dup = 1;
      else /* add a space before each item */
        irr_add_answer (irr, " ");
    }
    if (!dup) { /* only print this if not a duplicate */
      irr_add_answer (irr, "%s", set_name);
      last_set_name = set_name;
    } else
      dup = 0;
    first = 0;
  }
  irr_send_answer (irr);
  LL_Destroy (ll_setlist);
  LL_Destroy (ll_member_examined);
  Destroy_STACK (stack);
}

void mbrs_by_ref_set (irr_database_t *database, char *range_op,
	enum EXPAND_TYPE expand_flag, LINKED_LIST *ll_setlist,
	char *set_name, LINKED_LIST *ll_mbr_by_ref, irr_connection_t *irr) {
  char *member, *maint, key[BUFSIZE];
  hash_spec_t *hash_spec;

  if (ll_mbr_by_ref == NULL) 
    return;

  if (expand_flag == NO_EXPAND) {
    LL_ContIterate (ll_mbr_by_ref, member) {
      LL_Add(ll_setlist, strdup (member));
    }
    return;
  }

  /* Find all the route or autnum's which reference the set name 
   * (via 'members-of:').
   */
  LL_ContIterate (ll_mbr_by_ref, maint) {
    make_spec_key (key, maint, set_name);
    if ((hash_spec = fetch_hash_spec (database, key, UNPACK)) != NULL) {
      LL_ContIterate (hash_spec->ll_1, member) {
	SL_Add (ll_setlist, member, range_op, expand_flag, irr, database);
      }
      Delete_hash_spec (hash_spec);
    }
  }
}

void update_members_list (irr_database_t *database, char *range_op, enum EXPAND_TYPE expand_flag, 
			  LINKED_LIST *ll_member_examined, 
			  LINKED_LIST *ll_setlist, LINKED_LIST *ll_set_names, 
			  STACK *stack, irr_connection_t *irr) {
  char *member, *p, *r;
  char buffer[BUFSIZE], range_buf[512];
  int already_examined, len = 0;

  if (ll_set_names == NULL)
    return;

  LL_ContIterate (ll_set_names, member) {
    if ((expand_flag == NO_EXPAND) || !set_name (member)) {
      SL_Add (ll_setlist, member, range_op, expand_flag, irr, database);
    } else { /* we have a set name */
      already_examined = 0;
      LL_ContIterate (ll_member_examined, p) {
	if (!strcmp (p, member)) {
	  already_examined = 1;
	  break;
	}
      }
      if (!already_examined) {
	strcpy(range_buf, " "); /* initialize to empty range */
	r = member;
	/* Need to seperate the range op from the set 
	   name for rpsl_macro_expand_add() */
	if ((p = strchr (member, '^')) != NULL) {
	  strncpy (buffer, member, p - member);
	  buffer[p - member] = '\0';
          len = strlen(p);
	  if (len < 512) /* don't overflow buffer */
	     strcpy(range_buf,p);
	  r = buffer; /* this is the set name without the range op */
        } 
        if (range_op != NULL) {	  /* append existing range_op */
          if ( (len + strlen(range_op)) < 512 ) {
            if (p == NULL)
	      range_buf[0] = '\0';
	    strcat(range_buf, range_op);
          }
        }
        p = rpsl_macro_expand_add (range_buf, r, irr, database->name); 
        Push (stack, p);
        LL_Add (ll_member_examined, strdup (r));
      }
    }
  } 
} /* void update_members_list() */

/* a list of prefix range types */
enum PREFIX_RANGE_TYPE {
  INVALID_RANGE, EXCLUSIVE_RANGE, INCLUSIVE_RANGE, VALUE_RANGE };

enum PREFIX_RANGE_TYPE prefix_range_parse( char *range, unsigned int *start, unsigned int *end ) {
  char *p;

  p = range;
  if (*p == '+')
    return INCLUSIVE_RANGE;
  if (*p == '-')
    return EXCLUSIVE_RANGE;
  if (strchr(p, '-') != NULL) {
    if (sscanf(p, "%u-%u", start, end) != 2)
      return INVALID_RANGE;
    else
      return VALUE_RANGE;
  }
  if (sscanf(p, "%u", start) != 1)
    return INVALID_RANGE;
  else {
    *end = *start;
    return VALUE_RANGE;
  }
}
 
/*
 * Sorted and unique linked list add.  *p is the potential item to be added.
 * Also appends a range operator to *p.
 *
 */
void SL_Add (LINKED_LIST *ll_setlist, char *member, char *range_op, enum EXPAND_TYPE expand_flag, irr_connection_t *irr, irr_database_t *database) {
  char buffer[BUFSIZE], rangestr[32];
  char *q, *temp_ptr, *last, *range_ptr;
  hash_spec_t *hash_spec;
  irr_database_t *db;
  unsigned int bitlen;
  enum  PREFIX_RANGE_TYPE range_op_type, prefix_range_type;
  unsigned int range_op_start, range_op_end, prefix_range_start, prefix_range_end;

  /* if performing a route-set expansion, check for AS numbers and lookup route prefixes which list the AS as their origin */
  if ( expand_flag == ROUTE_SET_EXPAND && !strncasecmp(member, "AS", 2)) {
    make_gas_key(buffer, member + 2);
    LL_ContIterate (irr->ll_database, db) { /* search over all databases */
      if (db != database)
        irr_lock(db);	/* don't lock if searching current database */
      if ((hash_spec = fetch_hash_spec(db, buffer, FAST)) != NULL) {
        if (hash_spec->len1 > 0) {
          q = strdup(hash_spec->gas_answer);
          temp_ptr = strtok_r(q, " ", &last);
	  while (temp_ptr != NULL && *temp_ptr != '\0') {
	    SL_Add(ll_setlist, temp_ptr, range_op, expand_flag, irr, database);
	    temp_ptr = strtok_r(NULL, " ", &last);
	  }
	  free(q);
          Delete_hash_spec(hash_spec);
        }
      }
      if (db != database)
        irr_unlock(db);
    }
    return;
  }

  strcpy (buffer, member);
  if (range_op == NULL)
    goto add_member;
  if (*range_op != '^') {	/* should start with a '^' */
    trace (ERROR, default_trace, "SL_Add(): range_op does not start with a '^' : %s\n", range_op);
    goto add_member;
  }
  range_ptr = strdup(range_op + 1);
  q = strtok_r(range_ptr, "^", &last);
  while (q != NULL && *q != '\0') {
    range_op_type = prefix_range_parse(q, &range_op_start, &range_op_end);
    if (range_op_type == INVALID_RANGE) {
      trace (ERROR, default_trace, "SL_Add(): range_op is invalid : %s\n", range_op);
      return;
    }
    /* should have a prefix length to be legal */
    if ( (temp_ptr = strchr(member, '/')) != NULL )
      bitlen = atoi(temp_ptr + 1);
    else {
      trace (ERROR, default_trace, "SL_Add(): prefix missing: %s\n", member);
      free(range_ptr);
      return;
    }

    if ( (temp_ptr = strchr(buffer, '^')) == NULL ) /* check for range on prefix */
      prefix_range_start = (prefix_range_end = bitlen);
    else {
      prefix_range_type = prefix_range_parse(temp_ptr + 1, &prefix_range_start, &prefix_range_end);
      if (prefix_range_type == INVALID_RANGE) {
        free(range_ptr);
        trace (ERROR, default_trace, "SL_Add(): prefix range is invalid : %s\n", buffer);
        return;
      }
      *temp_ptr = 0;	/* terminate string at range */
      if (prefix_range_type == EXCLUSIVE_RANGE) {
	prefix_range_start = bitlen + 1;
	prefix_range_end = 32;
      } else if (prefix_range_type == INCLUSIVE_RANGE) {
	prefix_range_start = bitlen;
	prefix_range_end = 32;
      }
    }

    if (range_op_type == INCLUSIVE_RANGE) {
	range_op_start = prefix_range_start;
	range_op_end = 32;
    } else if (range_op_type == EXCLUSIVE_RANGE) {
	range_op_start = prefix_range_start + 1;
	range_op_end = 32;
    } else if (range_op_type == VALUE_RANGE) {
	if (range_op_end < prefix_range_start) {
          free(range_ptr);
	  return;  /* apply an less specific range to a more specific one */
	}
	if (prefix_range_start > range_op_start)
	  range_op_start = prefix_range_start;
    }

    if (range_op_start > range_op_end) {
        free(range_ptr);
	return; /* specific range exceeds maximum, toss prefix */
    }
    if ( (bitlen == range_op_start) && (bitlen == range_op_end) )
	strcpy(rangestr,"");
    else if (range_op_end == range_op_start)
	sprintf(rangestr,"^%d", range_op_start);
    else if (range_op_end == 32 && range_op_start == bitlen)
	strcpy(rangestr,"^+");
    else if (range_op_end == 32 && range_op_start == (bitlen + 1))
	strcpy(rangestr,"^-");
    else
	sprintf(rangestr,"^%d-%d",range_op_start, range_op_end);
    strcat(buffer, rangestr);
    q = strtok_r(NULL, "^", &last);
  }
  free(range_ptr);

add_member:
  LL_Add(ll_setlist, strdup (buffer));
}

char *rpsl_macro_expand_add (char *range, char *name, irr_connection_t *irr, 
                             char *dbname) {
  irr_database_t *database;
  char buffer[BUFSIZE];
 
  strcpy (buffer, range);
  strcat (buffer, ",");
  strcat (buffer, name);
  convert_toupper (buffer); /* canonicalize the key for hash lookups */

  if (dbname != NULL) {
    strcat (buffer, ",");
    strcat (buffer, dbname);
  }

  LL_ContIterate (irr->ll_database, database) {
    if (dbname == NULL || strcasecmp (database->name, dbname)) {
      strcat (buffer, ",");
      strcat (buffer, database->name);
    }
  }

  return (strdup (buffer));
}

int set_name (char *name) {

  if (name == NULL)
    return 0;

  if (!strncasecmp (name, "as-", 3)   ||
      !strncasecmp (name, "rs-", 3)   ||
      !strncasecmp (name, "rtrs-", 5) ||
      !strncasecmp (name, "fltr-", 5) ||
      !strncasecmp (name, "prng-", 5) ||
      strchr (name, ':'))
    return 1;

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1