/*
 * $Id: commands.c,v 1.40 2002/10/22 18:47:47 ljb Exp $
 * originally Id: commands.c,v 1.101 1998/08/07 00:13:37 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"
#include <errno.h>
#ifndef NT
#include <regex.h>
#endif /* NT */

extern trace_t *default_trace;
extern char *obj_template[];

/* m_info [] uses 'enum IRR_OBJECTS' in scan.h as the second
 * field in the struct.  This means changes to m_info []
 * or 'enum IRR_OBJECTS' need to be coordinated.  For example,
 * if a new object type is being added then a new entry should
 * be appended to this array and a new type added to 'enum IRR_OBJECTS'.
 *
 * key_info [] in scan.c and object_templates [] in templates.c 
 * also depend on 'enum IRR_OBJECTS' and so changes to any one
 * of these structs needs to be coordinated.
 */
m_command_t m_info [] = {
  { "an,", AUT_NUM },             /* 0 */
  { "aut-num,", AUT_NUM },        /* 1 */
  { "am,", AS_MACRO },            /* 2 */
  { "as-macro,", AS_MACRO },      /* 3 */
  { "as-set,", AS_SET },          /* 4 */
  { "route-set,", RS_SET },       /* 5 */
  { "cm,", COMMUNITY },           /* 6 */
  { "community,", COMMUNITY },    /* 7 */
  { "mt,", MAINT },               /* 8 */
  { "mntner,", MAINT },           /* 9 */
  { "ir,", INET_RTR },            /* 10 */
  { "inet-rtr,", INET_RTR },      /* 11 */
  { "ro,", ROLE },                /* 12 */
  { "role,", ROLE },              /* 13 */
  { "pn,", PERSON },              /* 14 */
  { "person,", PERSON },          /* 15 */
  { "in,", INETNUM },             /* 16 */
  { "inetnum,", INETNUM },        /* 17 */
  { "rt,", ROUTE},                /* 18 */
  { "route,", ROUTE},             /* 19 */  
  { "filter-set,", FILTER_SET},   /* 20 */
  { "rtr-set,", RTR_SET},         /* 21 */
  { "peering-set,", PEERING_SET}, /* 22 */
  { "key-cert,", KEY_CERT},       /* 23 */
  { "dictionary,", DICTIONARY},   /* 24 */
  { "repository,", REPOSITORY},   /* 25 */
  { "i6,", INET6NUM},             /* 26 */
  { "inet6num,", INET6NUM},       /* 27 */
  { "dom-prefix,", DOMAIN_PREFIX},/* 28 */
  { "dp,", DOMAIN_PREFIX},        /* 29 */
  { "domain,", DOMAIN},           /* 30 */
  { "dn,", DOMAIN}                /* 31 */
/* defined in irrd.h
#define IRR_MAX_MCMDS           32 
*/
};

/* Handler routines for IRR lookups
 * the show_xxx functions are human readable UII commands
 * the irr_xxx are used by the RAWhoisd machine telnet interface
 */

static void return_version (irr_connection_t *irr);
void update_autnum_list (int expand_flag, LINKED_LIST *ll_macro_examined, 
			 LINKED_LIST *ll_aslist, LINKED_LIST *ll_as,
			 STACK **stack, char *db, irr_connection_t *irr);
void irr_exact (irr_connection_t *irr, prefix_t *prefix, int flag);
void irr_less_all (irr_connection_t *irr, prefix_t *prefix, 
		       int flag, int mode);
void irr_show_sources (irr_connection_t *irr);
int irr_set_sources (irr_connection_t *irr, char *sources, int mode);
int irr_set_ALL_sources (irr_connection_t *irr, int mode); 
void irr_more_all (irr_connection_t *irr, prefix_t *prefix, int mode);
void irr_ripewhois(irr_connection_t *irr);
void irr_m_command (irr_connection_t *irr);
void irr_mntbyobjs (irr_connection_t *irr, enum IRR_OBJECTS type, int mode, char *key);
void show_gas_answer (irr_connection_t *irr, char *key); 
void irr_journal_range (irr_connection_t *irr, char *db);
void irr_journal_add_answer (irr_connection_t *irr);

/* irr_process_command
 * read/parse the !command and call the appropriate handler
 */
void irr_process_command (irr_connection_t * irr) {
  char tmp[BUFSIZE];
  char *return_str = NULL;
  int update_done = 0, i;
  int n_updates;
  int tmp_fd;
  
  if (irr->state == IRR_MODE_LOAD_UPDATE) {
    irr->begin_line = !irr->line_cont;
    i = strlen (irr->cp);
    irr->line_cont = (*(irr->cp + i - 1) != '\n');
    
    /* This section of code requires a bit of explanation.  The 
     * irr_read_command () function in telnet.c uses a fixed 
     * size memory buffer.  So to get around the problem of a 
     * !us...!ue line exceeding the buffer size, irr_read_command () 
     * may send an incomplete line (ie, a line *not* terminated by a 
     * '\n').  This means we need to detect line continuation so we 
     * can check for the update termination sequence "!ue\n" as 
     * "!ue\n" could occur on a boundry at the end of a read and 
     * the begining of the next read.  Finally, irr_read_command () 
     * will send lines terminate by a "\n" unless the line fills the 
     * buffer.  This simplifies the processing of the code below.
     */
    if (irr->begin_line) {
      irr->ue_test[0] = '\0';
      if (irr->line_cont) {
	if (i < 4) {
	  strcpy (irr->ue_test, irr->cp);
	  return;
	}
      }
      else if (!strncasecmp (irr->cp, "!ue", 3))
	update_done = 1;
    }
    else if (!irr->line_cont && i < 4) {
      i = strlen (irr->ue_test);
      strcat (irr->ue_test, irr->cp);
      if (!strncasecmp (irr->ue_test, "!ue", 3))
	update_done = 1;
      irr->ue_test[i] = '\0';
    }
    
    if (update_done) {
      
      fwrite ("\n%END\n", 1, 6, irr->update_fp);
      irr->state = 0;
      irr_update_lock (irr->database);
      
      /* rewind */
      if (fseek (irr->update_fp, 0L, SEEK_SET) < 0) {
	trace (ERROR, default_trace, "IRRd ERROR: !ue file rewind positioning"
	       " error.\n");
	irr_update_unlock (irr->database);
	irr_send_error (irr, "IRRd ERROR: Transaction aborted!  "
			"!ue file rewind positioning error.");
	fclose (irr->update_fp);
	unlink (irr->update_file_name);
	return;
      }
      
      /* atomic transaction support */
      if (atomic_trans) {
	/* build the transaction file which can be used to 
	 * restore the DB to it's original state before the transaction */
	if ((return_str = build_transaction_file (irr->database, irr->update_fp, 
					 irr->update_file_name, tmp, 
					 &n_updates)) != NULL) {
	  trace (ERROR, default_trace, "Could not create transaction files: "
		 "%s\n", return_str);
	  irr_update_unlock (irr->database);
	  irr_send_error (irr, "IRRd ERROR: Transaction aborted!  "
			  "Could not build transaction files.");
	  fclose (irr->update_fp);
	  unlink (irr->update_file_name);
	  return;
	}
      }
      
      /* update the DB */
      return_str = scan_irr_file (irr->database, "update", 1, irr->update_fp);

      /* rollback the DB to it's original state if the transaction
       * could not be applied successfully in it's entirety */
      if (atomic_trans) {
	if (return_str != NULL) {
	  /* restore the DB to it's original state 
	   * then rebuild the indexes */
	  db_rollback (irr->database, tmp);
	  if (!irr_reload_database (irr->database->name, NULL, NULL))
	    trace (ERROR, default_trace, "DB rollback operation: index "
		   "rebuild failed for DB (%s)\n", irr->database->name);
	}
	/* update the *.JOURNAL file for authoritative or mirror DB's; 
	 * rpsdist will manage the journal in the other case */
	else if (((irr->database->flags & IRR_AUTHORITATIVE) ||
		 irr->database->mirror_prefix != NULL)            &&
		 !update_journal (irr->update_fp, irr->database, n_updates)) {
	  trace (ERROR, default_trace, "error in updating journal for DB (%s) "
		 "rolling back transaction\n", irr->database->name);
	  db_rollback (irr->database, tmp);
	  journal_rollback (irr->database, tmp);
	  return_str = "Transaction abort!  Internal journaling error.";
	}

	/* remove the transaction file */
	remove (tmp);
      }
      
      fclose (irr->update_fp);
      irr_update_unlock (irr->database);
      
      /* Send the client the transaction result */
      if (return_str == NULL) {
	irr_send_okay(irr);
	irr->database->last_update = time (NULL);
      }
      else
	irr_send_error (irr, return_str);
      
      /* remove the update file */
      remove (irr->update_file_name);
    }
    else { /* save the update to file */
      if (irr->ue_test[0] != '\0') {
	fwrite (irr->ue_test, 1, strlen (irr->ue_test), irr->update_fp);
	irr->ue_test[0] = '\0';
      }
      fwrite (irr->cp, 1, strlen (irr->cp), irr->update_fp);
    }
    
    return;
  }
  
  trace (NORM, default_trace, "Command: %s\n", irr->cp);

  if ( strlen(irr->cp) > IRR_MAXCMDLEN ) {
      trace (ERROR, default_trace, "query exceeds max length\n",irr->cp);
      irr_send_error (irr, "Command/Query exceeds max length!");
      return;
  }
  
  if (irr->stay_open == 0 && 
      (!strncmp (irr->cp, "!!", 2) ||
       !strncmp (irr->cp, "-k", 2))) {
    irr->stay_open = 1;
    irr->short_fields = 1; /* this is what rawhoisd does */
    return;
  }
  
  /* quit or stay open */
  if (!strcasecmp (irr->cp, "quit") ||
      !strcasecmp (irr->cp, "exit") ||
      !strcasecmp (irr->cp, "!q")   ||
      !strcasecmp (irr->cp, "q")    ||
      !strcasecmp (irr->cp, "-k")) {
    irr->stay_open = 0;
    return;
  }

  /* update !us<database> - DB we're authoritative for
   * ADD DEL object
   * terminated by a !ue
   *
   * We assume database files have already gone through irr_checker and
   * irr_auth. We return success or failure info to be used 
   * by irr_notify
   *
   */
  if ( !strncasecmp (irr->cp, "!us", 3) ){
    irr->cp += 3;
    irr->state = 0; /* reset state */
    
    /* see if we know of this DB */
    if ((irr->database = find_database (irr->cp)) == NULL) {
      trace (ERROR, default_trace, "Database not found %s\n",irr->cp);
      irr_send_error (irr, "Database not found!");
      return;
    }
    
    /* check authoritative if !us*/
    if ( !(irr->database->flags & IRR_AUTHORITATIVE) ) {
      trace (ERROR, default_trace, 
	     "Request to update non-authoritative database %s with !us\n",irr->cp);
      irr_send_error (irr, "Request to update non-authoritative database!");
      return;
    }
    
    /* check security -- general */
    if ((irr->database->access_list > 0) &&
	(!apply_access_list (irr->database->access_list, irr->from))) {
      trace (NORM | INFO, default_trace, "Updated access to %s denied for %s...\n",
	     irr->database->name, prefix_toa (irr->from));
      irr_send_error (irr, "General access permission to denied!");
      return;
    }
    
    /* 
     * check security -- write access 
     */
    if (irr->database->write_access_list == 0) {
      if (!prefix_is_loopback (irr->from)) {
	trace (NORM | INFO, default_trace, "No write access list sepcified -- only loopback allowed\n");
	trace (NORM | INFO, default_trace, "Updated access to %s denied for %s...\n",
	       irr->database->name, prefix_toa (irr->from));
	irr_send_error (irr, "Write permission denied!");
	return;
      }
    }
    
    if ((irr->database->write_access_list > 0) &&
	(!apply_access_list (irr->database->write_access_list, irr->from))) {
      trace (NORM | INFO, default_trace, "Updated access to %s denied for %s...\n",
	     irr->database->name, prefix_toa (irr->from));
      irr_send_error (irr, "Write permission denied!");
      return;
    }
    
/*    trace (NORM, default_trace, "START update from %s to %s\n",
	   prefix_toa(irr->from), irr->database->name); */
    
    sprintf (irr->update_file_name, "%s/%s.update.XXXXXX", 
	     IRR.database_dir, irr->database->name);

    /* create a file to dump the update to */
    if ((tmp_fd = mkstemp (irr->update_file_name)) < 0) {
      irr_send_error (irr, "IRRd error: initial !us file mkstemp () error");
      trace (ERROR, default_trace, "IRRd error: initial !us mkstemp () error\n");
      return;
    }
    
    if ((irr->update_fp = fdopen (tmp_fd, "r+")) == NULL) {
      /* open up the temp update file */
      snprintf (tmp, BUFSIZE, "IRRd error: initial !us file fdopen () error:"
		" %s\n", strerror (errno));
      irr_send_error (irr, tmp);
      trace (ERROR, default_trace, "%s", tmp);
      return;
    }
    
    irr->line_cont = 0;
    irr->ue_test[0] = '\0';
    irr->state = IRR_MODE_LOAD_UPDATE;
    irr_send_okay (irr);
    return;
  }
  
  /* withdrawn routes global flag */
  /* =1 -> show withdrawns's; =0 -> don't show */
  if (!strncmp (irr->cp, "!uwd", 4)) {
    irr->cp +=4;
    if (!strncmp (irr->cp, "=1", 2)) {
      irr->withdrawn = 1;
      irr_send_okay(irr);
    }
    else if (!strncmp (irr->cp, "=0", 2)) {
      irr->withdrawn = 0;
      irr_send_okay(irr);
    }
    else {
      irr_send_error (irr, NULL);
    }
    return;
  }
  
  /* full object flag */
  /* =1 -> show full object (default) ; =0 -> show <source> <key> only */
  /* affects the !r command only */
  if (!strncmp (irr->cp, "!ufo", 4)) {
    irr->cp +=4;
    if (!strncasecmp (irr->cp, "=1", 2)) {
      irr->full_obj = 1;
      irr_send_okay(irr);
    }
    else if (!strncmp (irr->cp, "=0", 2)) {
      irr->full_obj = 0;
      irr_send_okay(irr);
    }
    else {
      irr_send_error (irr, NULL);
    }
    return;
  }
  
  /* long or short fields global flag */
  /* =1 -> short fields (fast!); =0 -> long fields (default)*/
  /* this will go away in rpsl because short fields are not used */
  if (!strncmp (irr->cp, "!uF", 3)) {
    irr->cp +=3;
    if (!strncmp (irr->cp, "=0", 2)) {
      irr->short_fields = 0;
      irr->ripe_flags &= ~(FAST_OUT | RECURS_OFF);
      irr_send_okay(irr);
    }
    else if (!strncmp (irr->cp, "=1", 2)) {
      irr->short_fields = 1;
      irr->ripe_flags |= FAST_OUT | RECURS_OFF;
      irr_send_okay(irr);
    }
    else
      irr_send_error (irr, NULL);
    
    return;
  }
  
  /* set timeout, !t<seconds> (for programs like Roe, Aoe, etc */
  if (!strncasecmp (irr->cp, "!t", 2)) {
    irr->cp +=2;
    irr->timeout = atoi (irr->cp);
    
    if ((irr->timeout < 0) || (irr->timeout > 30*60)) {
      irr_send_error (irr, NULL);
      irr->timeout = 5*60;
    }
    else
      irr_send_okay (irr);
    return;
  }
  
  /* reload a database
   * !B<DB list ...> | ALL
   * !eg, !Brgnet,ripe 
   */
  if (!strncasecmp (irr->cp, "!B", 2)) {
    irr_database_t *database;
    char           *name, names[BUFSIZE], *last, *tmp_dir;
    
    irr->cp +=2;
    strcpy (names, irr->cp);
    if (!strcasecmp ("ALL", names)) {
      names[0] = '\0';
      LL_IntrIterate (IRR.ll_database, database) {
	strcat (names,",");
	strcat (names, database->name);
      }
    }

    /* expect the DB's to be in our irrd.conf defined tmp dir */
    tmp_dir = ((IRR.tmp_dir == NULL) ? "/var/tmp" : IRR.tmp_dir);
    name = strtok_r(names, ",", &last);
    while (name != NULL) {
      
      /* do we know about this DB? */
      if ((irr->database = find_database (name)) == NULL) {
	trace (ERROR, default_trace, "Database not found %s\n", name);
	irr_send_error (irr, NULL);
	break;
      }
      
      /* general access */
      if ((irr->database->access_list > 0) &&
          (!apply_access_list(irr->database->access_list, irr->from))) {
	trace (NORM | INFO, default_trace, "Reload of %s denied for %s...\n",
	       irr->database->name, prefix_toa(irr->from));
	irr_send_error (irr, NULL);
	break;
      }
      
      /* write access */
      if ((irr->database->write_access_list > 0) &&
	  (!apply_access_list(irr->database->write_access_list, irr->from))) {
	trace (NORM | INFO, default_trace, 
	       "Reload/write access to %s denied for %s...\n",
	       irr->database->name, prefix_toa(irr->from));
	irr_send_error (irr, NULL);
	break;
      }
      
      /* atomic reload */
      if (!irr_reload_database (name, NULL, tmp_dir))
	irr_send_error (irr, NULL);
      else
	irr_send_okay (irr);

      name = strtok_r(NULL, ",", &last);
    }

    return;
  }

  /* Get list of objects associated with a maintainer.  !omaint-as237 */
  if (!strncasecmp (irr->cp, "!o", 2)) {
    irr->cp += 2;
    irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);
    irr_lock_all (irr);
    irr_mntbyobjs (irr, NO_FIELD, RAWHOISD_MODE, irr->cp);
    return;
  }
  
  /* Get routes with specified origin.   !gas1234 */
  if (!strncasecmp (irr->cp, "!gas", 4)) {
    char gas_key[BUFSIZE];

    irr->cp += 4;
    make_gas_key (gas_key, irr->cp);
    show_gas_answer (irr, gas_key);
    return;
  }
  
  /* AS-SET/RS-SET expansion !iAS-ESNETEU[,1] */
  if (!strncasecmp (irr->cp, "!i", 2)) {
    irr->cp += 2;
    irr_set_expand (irr, irr->cp);
    return;
  }
  
  /* rawhoisd match command !man,as1243 */
  if (!strncasecmp (irr->cp, "!m", 2)) {
    irr->cp += 2;
    irr_m_command (irr);
    return;
  }
  
  /* return a version */
  if ((!strncasecmp (irr->cp, "!ver", 4)) || 
      (!strcasecmp (irr->cp, "!v"))       || 
      (!strcasecmp (irr->cp, "!-v"))      ||
      (!strcasecmp (irr->cp, "version"))) {
    irr->cp += 5;
    return_version(irr);
    return;
  }
  
  /* name of tool, !nroe  -- this should already be logged earlier */
  if (!strncasecmp (irr->cp, "!n", 2)) { 
    irr_send_okay (irr);
    return;
  }
  
  /* Route searches.
   * Default finds exact prefix/len match.
   *  o - return origin of exact match(es)
   *  l - one-level less specific
   * eg, !r141.211.128/24,l
   */
  if (!strncasecmp (irr->cp, "!r", 2)) {
    char *cp = NULL;
    prefix_t *prefix;
    
    irr->cp += 2;
    cp = strrchr(irr->cp, ',');
    
    if (cp == NULL) {
      if ((prefix = ascii2prefix (AF_INET, irr->cp)) == NULL)
	irr_send_error (irr, NULL);
      else {
	irr_exact (irr, prefix, SHOW_FULL_OBJECT);
	Delete_Prefix (prefix);
      }
    }
    else if (!strncmp (",l", cp, 2)) {
      *cp = '\0';
      if ((prefix = ascii2prefix (AF_INET, irr->cp)) == NULL) {
	irr_send_error (irr, NULL);
      }
      else {
	irr_less_all (irr, prefix, SEARCH_ONE_LEVEL, RAWHOISD_MODE);
	Delete_Prefix (prefix);
      }
    }
    else if (!strncmp (",L", cp, 2)) {
      *cp = '\0';
      if ((prefix = ascii2prefix (AF_INET, irr->cp)) == NULL) {
	irr_send_error (irr, NULL);
      }
      else {
	irr_less_all (irr, prefix, SEARCH_ALL_LEVELS, RAWHOISD_MODE);
	Delete_Prefix (prefix);
      }
    }
    else if (!strncasecmp (",o", cp, 2)) {
      *cp = '\0';
      if ((prefix = ascii2prefix (AF_INET, irr->cp)) == NULL) {
	irr_send_error (irr, NULL);
      }
      else {
	irr_exact (irr, prefix, SHOW_JUST_ORIGIN);
	Delete_Prefix (prefix);
      }
    }
    else if (!strncasecmp (",M", cp, 2)) {
      *cp = '\0';
      if ((prefix = ascii2prefix (AF_INET, irr->cp)) == NULL) {
	irr_send_error (irr, NULL);
      }
      else {
	irr_more_all (irr, prefix, RAWHOISD_MODE);
	Delete_Prefix (prefix);
      }
    }
    else {
      /* error */
      irr_send_answer (irr);
      return;
    }
    return;
  }
  
  /* s command Set the sources to the specified list.
   * Default is all sources.	eg, !sradb,ans 
   */
  if (!strncasecmp (irr->cp, "!s", 2)) {
    irr->cp += 2;
    if (!strncasecmp (irr->cp, "-lc", 3))
      irr_show_sources (irr);
    else if (!strncasecmp (irr->cp, "-*", 2))
      irr_set_ALL_sources (irr, RAWHOISD_MODE); 
    else
      irr_set_sources (irr, irr->cp, RAWHOISD_MODE);
    return;
  }
  
  /* j command.  Show the journal ranges.  See irr_journal_range for
     more info.  -* is valid, just like !s, but is implemented in
     the function. */
  if (!strncasecmp (irr->cp, "!j", 2)) {
    irr_journal_range (irr, irr->cp+2);
    return;
  }
  
  /* we are a mirror server, e.g. -g RADB:1:232-LAST */
  if (!strncasecmp (irr->cp, "-g", 2)) {
    irr_service_mirror_request (irr, irr->cp+2);
    return;
  }

  /* Process ripe whois commands.
   * ie, command does not start with "!". 
   */
  if (strncasecmp(irr->cp, "!", 1)) {
    irr_ripewhois(irr);
    return;      
  }
  
  /* error -- command unrecognized */
  sprintf (tmp, "F\n");
  irr_write_nobuffer (irr, tmp, strlen (tmp));
  trace (NORM, default_trace, "Returned F -- unrecognized command: %s\n", irr->cp);
  
}

/* do a inverse lookup on mnt-by name to get all associated objects  */
void irr_mntbyobjs (irr_connection_t *irr, enum IRR_OBJECTS obj_type, int mode,  char *key) {
  char maint_key[BUFSIZE];
  irr_database_t *db;
  hash_spec_t *hash_sval;
  maint_objlist_t *maint_obj_p;
  LINKED_LIST *ll;

  ll = LL_Create (LL_DestroyFunction, Delete_hash_spec, 0);

  make_mntobj_key (maint_key, key);
  LL_ContIterate (irr->ll_database, db) {
    if ((hash_sval = fetch_hash_spec (db, maint_key, UNPACK)) != NULL) {
      LL_Iterate (hash_sval->ll_1, maint_obj_p) {
        if (obj_type == NO_FIELD || obj_type == maint_obj_p->type) 
	  irr_build_answer (irr, db->db_fp, maint_obj_p->type, maint_obj_p->offset, 
				maint_obj_p->len, NULL, db->db_syntax);
      }
      LL_Add (ll, hash_sval);
    }
  }

  send_dbobjs_answer (irr, DISK_INDEX, mode);
  irr_unlock_all (irr);
  irr_write_buffer_flush (irr);
  LL_Destroy (irr->ll_answer);
  LL_Destroy (ll);
}

/* !m... command, eg "!man,as1234" */
void irr_m_command (irr_connection_t *irr) {
  int found = 0, i;

  for (i = 0; i < IRR_MAX_MCMDS; i++) {
    if (!strncasecmp (irr->cp, m_info[i].command, strlen (m_info[i].command))) {
      found = 1;
      irr->cp += strlen (m_info[i].command);
      irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);

      irr_lock_all (irr);

      if (m_info[i].type == ROUTE)
        lookup_route_exact (irr, irr->cp);
      else
        irr_database_find_matches (irr, irr->cp, PRIMARY, 
                                   RAWHOISD_MODE|TYPE_MODE, m_info[i].type, 
                                   NULL, NULL);
      break;
    }
  }

  if (found) {
    send_dbobjs_answer (irr, DISK_INDEX, RAWHOISD_MODE);
    irr_unlock_all (irr);
    irr_write_buffer_flush (irr);
    LL_Destroy (irr->ll_answer);
  }
  else  {
    char tmp[4];

    sprintf (tmp, "D\n");
    irr_write_nobuffer (irr, tmp, strlen (tmp));
  }

  return;
}

/* irr_ripewhois
 * Ripe whois commands, eg "whois AS2"
 */
void irr_ripewhois (irr_connection_t *irr) {
  prefix_t *prefix;
  int lookup_mode;
  char *key = irr->cp;
  enum IRR_OBJECTS lookup_type;

  /* New ripe flag stuff */
  /* want to retain fast output and recursive-off choices for
     ripe stay open mode while turning all other flags off */
  irr->ripe_flags &= (FAST_OUT | RECURS_OFF);
  if (parse_ripe_flags (irr, &key) < 0)
    return;

  if (irr->ripe_flags & TEMPLATE) {
    if (irr->ripe_type >= 0 && irr->ripe_type < IRR_MAX_KEYS)
      irr_write_nobuffer (irr, obj_template[irr->ripe_type], 
						 strlen (obj_template[irr->ripe_type]));
      return;
  }
  
  if (irr->ripe_flags & FAST_OUT)
    irr->short_fields = 1; 

  if ((irr->ripe_flags & SOURCES_ALL) &&
      (irr_set_ALL_sources (irr, RIPEWHOIS_MODE) == 0))
    return;

  if ((irr->ripe_flags & SET_SOURCE) && 
      (irr_set_sources (irr, irr->ripe_sources, RIPEWHOIS_MODE) == 0))
    return;

  irr_lock_all (irr);
  irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);

  if (*key >= '0' && *key <= '9' &&
      (((irr->ripe_flags & OBJ_TYPE) == 0) ||
       irr->ripe_type == ROUTE)) {
    if (strchr (key, ':') != NULL)
      prefix = ascii2prefix (AF_INET6, key);
    else
      prefix = ascii2prefix (AF_INET, key);

    if (prefix) {
      /* New RIPE stuff */
      if (irr->ripe_flags & LESS_ALL)
        irr_less_all (irr, prefix, SEARCH_ALL_LEVELS, RIPEWHOIS_MODE);
      else if (irr->ripe_flags & (MORE_ONE | MORE_ALL))
        irr_more_all (irr, prefix, RIPEWHOIS_MODE);
      else
        irr_less_all (irr, prefix, SEARCH_ONE_LEVEL, RIPEWHOIS_MODE);
      Delete_Prefix (prefix);
    }
  }

  if (irr->ripe_flags & OBJ_TYPE)  {
    lookup_mode = TYPE_MODE;
    lookup_type = irr->ripe_type;
  }  else {
    lookup_mode = 0;
    lookup_type = NO_FIELD;
  }

  if ( (irr->ripe_flags & INVERSE_ATTR) && irr->inverse_type == MNT_BY)  /* check for inverse mnt-by lookup */
    irr_mntbyobjs(irr, lookup_type, RIPEWHOIS_MODE, key); 
  else {
    irr_database_find_matches (irr, key, PRIMARY, lookup_mode, lookup_type, NULL, NULL);

    if ((irr->ripe_flags & (FAST_OUT | RECURS_OFF | OBJ_TYPE | INVERSE_ATTR)) == 0)
      lookup_object_references (irr);  /* dupe ripe whois behavior */

    send_dbobjs_answer (irr, DISK_INDEX, RIPEWHOIS_MODE);
    irr_unlock_all (irr);
    irr_write_buffer_flush (irr);
    LL_Destroy (irr->ll_answer);
  }
}

void show_gas_answer (irr_connection_t *irr, char *key) {
  irr_database_t *db;
  int empty_answer = 1;
  hash_spec_t *hash_item;
  LINKED_LIST *ll;
  
  irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);
  ll = LL_Create (LL_DestroyFunction, Delete_hash_spec, 0);

  irr_lock_all (irr);
  LL_ContIterate (irr->ll_database, db) {
    if ((hash_item = fetch_hash_spec (db, key, FAST)) != NULL) {
      if (hash_item->len1 > 0) {
        if (!empty_answer) /* need to add a space between AS'es */
          irr_build_answer (irr, NULL, NO_FIELD, 0, 1, " ", RIPE181);
        irr_build_answer (irr, db->db_fp, NO_FIELD, 0, 
			hash_item->len1 - 1, hash_item->gas_answer, RIPE181);
      }
      LL_Add (ll, hash_item);
      empty_answer = 0;
    }
  }

  /* tack on a carriage return */
  if (!empty_answer)
    irr_build_answer (irr, NULL, NO_FIELD, 0, 1, "\n", RIPE181);
  send_dbobjs_answer (irr, MEM_INDEX, RAWHOISD_MODE);
  irr_unlock_all (irr);
  irr_write_buffer_flush (irr);
  LL_Destroy (irr->ll_answer);
  LL_Destroy (ll);
} 


/* Route searches.  L -  all level less specific eg, !r141.211.128/24,L
   Route searches.  l - one-level less specific eg, !r141.211.128/24,l
   Both these searches are inclusive (ie, the supplied route will be 
   returned if it exits.
   flag=0 -> "l" search; flag=1 -> "L" search */
void irr_less_all (irr_connection_t *irr, prefix_t *prefix, 
		   int flag, int mode) {
  radix_node_t *node = NULL;
  LINKED_LIST *ll_attr;
  irr_route_object_t *rt_object;
  irr_answer_t *irr_answer;
  irr_database_t *database;
  prefix_t *tmp_prefix = NULL;
  int route_found = 0;

  if (mode == RAWHOISD_MODE) {
     irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);
     irr_lock_all (irr);
  }
	
  LL_ContIterate (irr->ll_database, database) {
    route_found = 0;

    tmp_prefix = prefix;

    /* first check if this node exists */
    if ((node = route_search_exact (database, database->radix, prefix)) != NULL) {
      ll_attr = (LINKED_LIST *) node->data;
      LL_Iterate (ll_attr, rt_object) {
	if (mode == RIPEWHOIS_MODE ||
	    irr->withdrawn == 1    || 
	    rt_object->withdrawn == 0) {
	  if (mode == RAWHOISD_MODE && irr->full_obj == 0)
	    irr_build_key_answer (irr, database->db_fp, database->name, ROUTE, 
				  rt_object->offset, rt_object->origin);
	  else
	    irr_build_answer (irr, database->db_fp, ROUTE, rt_object->offset, 
			      rt_object->len, NULL, database->db_syntax);
	}
      }
    }

    /* now check all less specific */
    while ((flag == SEARCH_ALL_LEVELS || node == NULL) && 
	   (node = route_search_best (database, database->radix, 
				      tmp_prefix)) != NULL) {
      if (node != NULL) { 
	tmp_prefix = node->prefix;

	ll_attr = (LINKED_LIST *) node->data;
	LL_Iterate (ll_attr, rt_object) {
	  if (mode == RIPEWHOIS_MODE ||
	      irr->withdrawn == 1    || 
	      rt_object->withdrawn == 0) {
	    route_found = 1;
	    if (mode == RAWHOISD_MODE && irr->full_obj == 0)
	      irr_build_key_answer (irr, database->db_fp, database->name, ROUTE, 
				    rt_object->offset, rt_object->origin);
	    else {
	      irr_build_answer (irr, database->db_fp, ROUTE, rt_object->offset, 
				rt_object->len, NULL, database->db_syntax);
            }
	  }
	}
      }
      /* break out after one loop for "l" case */
      if (route_found && (flag == SEARCH_ONE_LEVEL)) break; 
    }
  }
  
  if (mode == RAWHOISD_MODE) {
    if (irr->full_obj == 0) {
      send_dbobjs_answer (irr, MEM_INDEX, RAWHOISD_MODE);
      LL_ContIterate (irr->ll_answer, irr_answer) {
	free (irr_answer->blob);
      }
    }
    else
      send_dbobjs_answer (irr, DISK_INDEX, RAWHOISD_MODE);
    irr_unlock_all (irr);
    irr_write_buffer_flush (irr);
    LL_Destroy(irr->ll_answer);
  }
}

/* Route searches. M - all more specific eg, !r199.208.0.0/16,M */
/* Also does m - one level only more specific */
void irr_more_all (irr_connection_t *irr, prefix_t *prefix, int mode) {
  radix_node_t *node, *start_node, *last_node;
  LINKED_LIST *ll_attr;
  irr_route_object_t *rt_object;
  irr_answer_t *irr_answer;
  irr_database_t *database;
  
  if (prefix->bitlen < 16) {
    irr_send_error (irr, "We only allow more searches >= /16");
    return;
  }

  if (mode == RAWHOISD_MODE) {
    irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);
    irr_lock_all (irr);
  }

  LL_ContIterate (irr->ll_database, database) {
    last_node = NULL;
    start_node = NULL;
    node = NULL;    

    /* memory  -- find the prefix, or the best large node */
    start_node = radix_search_exact_raw (database->radix, prefix);

    if (start_node != NULL) {
      RADIX_WALK (start_node, node) {
	trace (TRACE, default_trace, "  -M %s\n", prefix_toax (node->prefix));
	if ((node->prefix != NULL) &&
	    (node->prefix->bitlen >= prefix->bitlen) &&
	    (comp_with_mask ((void *) prefix_tochar (node->prefix), 
			     (void *) prefix_tochar (prefix),  prefix->bitlen))) {
	  ll_attr = (LINKED_LIST *) node->data;
	  LL_Iterate (ll_attr, rt_object) {
	    if ((irr->withdrawn == 1) || (rt_object->withdrawn == 0)) {
	      if (irr->full_obj == 0)
		irr_build_key_answer (irr, database->db_fp, database->name, ROUTE, 
				      rt_object->offset, rt_object->origin);
	      else
		irr_build_answer (irr, database->db_fp, ROUTE, rt_object->offset, 
				  rt_object->len, NULL, database->db_syntax);
	    }
	  }
	}
      }
      RADIX_WALK_END;
    }
  }
  
  if (mode == RAWHOISD_MODE) {
    if (irr->full_obj == 0) {
      send_dbobjs_answer (irr, MEM_INDEX, RAWHOISD_MODE);
      LL_ContIterate (irr->ll_answer, irr_answer) {
        free (irr_answer->blob);
      }
    }
    else
      send_dbobjs_answer (irr, DISK_INDEX, RAWHOISD_MODE);
    irr_unlock_all (irr);
    irr_write_buffer_flush (irr);
    LL_Destroy(irr->ll_answer);
  }

}

/* Route searches.  o - return origin of exact match(es) eg, !r141.211.128/24,o 
 * flag == 1 means just return the ASorigin. If not 1, return the full object 
 */
void irr_exact (irr_connection_t *irr, prefix_t *prefix, int flag) {
  radix_node_t *node = NULL;
  LINKED_LIST *ll_attr;
  irr_route_object_t *rt_object;
  irr_database_t *database;
  irr_answer_t *irr_answer;
  char buffer[BUFSIZE], *p;
  int first = 1;

  if (flag == SHOW_FULL_OBJECT)
    irr->ll_answer = LL_Create (LL_DestroyFunction, free, 0);
  else
    buffer[0] = '\0';

  irr_lock_all (irr);
  LL_ContIterate (irr->ll_database, database) {

    node = route_search_exact (database, database->radix, prefix);

    if (node != NULL) { 
      ll_attr = (LINKED_LIST *) node->data;
      LL_Iterate (ll_attr, rt_object) {
	if (irr->withdrawn == 1 || rt_object->withdrawn == 0) {
	  if (flag == SHOW_JUST_ORIGIN) {
	    if (irr->full_obj == 0) {
	      if (first != 1) irr_add_answer (irr, "\n");
	      first = 0;
	      irr_add_answer (irr, "%s AS%d", database->name, rt_object->origin);
	    }
	    else {
	      /* irr_add_answer (irr, "AS%d", rt_object->origin); */
	      p = buffer + strlen (buffer);
	      sprintf (p, "AS%d ", rt_object->origin);
	    }
	  }
	  else {
	    if (irr->full_obj == 1)
	      irr_build_answer (irr, database->db_fp, ROUTE, rt_object->offset, 
				rt_object->len, NULL, database->db_syntax);
	    else 
	      irr_build_key_answer (irr, database->db_fp, database->name, ROUTE, 
				    rt_object->offset, rt_object->origin);
	  }
	}
      }
    }
  }
  
  if (flag == SHOW_JUST_ORIGIN) {
    if (irr->full_obj == 1 && strlen (buffer) > 0)
      irr_add_answer (irr, "%s", buffer); 
    irr_send_answer (irr);
  }
  else {
    if (irr->full_obj == 1)
      send_dbobjs_answer (irr, DISK_INDEX, RAWHOISD_MODE);
    else {
      send_dbobjs_answer (irr, MEM_INDEX, RAWHOISD_MODE);
      LL_ContIterate (irr->ll_answer, irr_answer) {
	free (irr_answer->blob);
      }
    }
  }   

  irr_unlock_all (irr);
  irr_write_buffer_flush (irr);
  if (flag == SHOW_FULL_OBJECT)
    LL_Destroy(irr->ll_answer);
}

/* s command Set the sources to the specified list.
 * eg, !sradb,ans 
 *
 * RETURN values:
 *
 * return number of sources set
 */

int irr_set_sources (irr_connection_t *irr, char *sources, int mode) {
  irr_database_t *database, *dup_db;
  int found = 0, dup;
  int ret_code = 0, old_ret_code;
  char tmp[BUFSIZE], buf[BUFSIZE], *last;
  int space_left = BUFSIZE - 1;
  char *db;

  buf[0] = buf[BUFSIZE - 1] = '\0';
  db = strtok_r(sources, ",", &last);
 
  while (db != NULL) {
    old_ret_code = ret_code;
    dup = 0;
    LL_IntrIterate (IRR.ll_database, database) {
      if (!strcasecmp (database->name, db)) {

	/* access-control  check */
	if ((database->access_list > 0) &&
	    (!apply_access_list (database->access_list, irr->from))) {
	  trace (NORM | INFO, default_trace, "Access to %s denied...\n",
		 prefix_toa (irr->from));
	  if (mode == RIPEWHOIS_MODE) {
            snprintf (tmp, BUFSIZE, "%%  Access denied for db source \"%s\".\n", database->name);
	    strncat (buf, tmp, space_left);
	    space_left = MAX((0), (space_left - strlen(tmp)));
          }
	}
	else {
	  /* hose the current list on our first DB match only */
	  if (!found) {
	    LL_Clear (irr->ll_database);
	    found = 1;
	  }

	  /* Don't add duplicate sources to the list */
	  LL_Iterate (irr->ll_database, dup_db) {
	    if (!strcmp (dup_db->name, database->name)) {
	      dup = 1;
	      break;
	    }
	  }

	  /* add database->name if it has not yet been added */
	  if (!dup) {
	    LL_Add (irr->ll_database, database);
	    ret_code++;
	  }
        }
        break;
      }
    }

    if (old_ret_code == ret_code       && 
	mode         == RIPEWHOIS_MODE) {
      if (dup)
	snprintf (tmp, BUFSIZE, "%%  Duplicate source \"%s\" ignored.\n", db);
      else
	snprintf (tmp, BUFSIZE, "%%  Source \"%s\" not found.\n", db);
      strncat (buf, tmp, space_left);
      space_left = MAX((0), (space_left - strlen(tmp)));
    }

    db = strtok_r(NULL, ",", &last);
  }

  if (mode == RAWHOISD_MODE) {
    if (ret_code == 0) 
      irr_send_error (irr, NULL);
    else
      irr_send_okay (irr);
  }
  else if (buf[0] != '\0') /* in RIPEWHOIS_MODE and errors, tell user */
    irr_write_nobuffer (irr, buf, strlen (buf));

  return (ret_code);
}

/* Set db query list to all sources
 * ie, !s-*
 *
 * RETURN values:
 *
 * return number of sources set
 */
int irr_set_ALL_sources (irr_connection_t *irr, int mode) {
  irr_database_t *database;
  int ret_code = 0;
  char tmp[BUFSIZE], buf[BUFSIZE];

  buf[0] = '\0';

  LL_Clear (irr->ll_database);
  LL_IntrIterate (IRR.ll_database, database) {

    /* access-control  check */
    if ((database->access_list > 0) &&
	(!apply_access_list (database->access_list, irr->from))) {
      trace (NORM | INFO, default_trace, "Access to %s denied for %s...\n",
	     database->name, prefix_toa (irr->from));
      if (mode == RIPEWHOIS_MODE) {
        sprintf (tmp, "%%  Access denied for db source \"%s\".\n", database->name);
        strcat (buf, tmp);
      }
    }  
    else {
      LL_Add (irr->ll_database, database);
      ret_code++;
    }
  }

  if (mode == RAWHOISD_MODE) {
    if (ret_code == 0) 
      irr_send_error (irr, NULL);
    else
      irr_send_okay (irr);
  }
  else if (buf[0] != '\0')  /* in RIPEWHOIS_MODE and errors, tell user */
      irr_write_nobuffer (irr, buf, strlen (buf));

  return (ret_code);
}

/* !s-lc */
void irr_show_sources (irr_connection_t *irr) {
  irr_database_t *database;
  int first = 1;

  LL_Iterate (irr->ll_database, database) {
    if (first != 1) { irr_add_answer (irr, ",");}
    first = 0;
    irr_add_answer (irr, "%s", database->name);
  }

  irr_send_answer (irr);
}


/* Return a version string. eg, !ver */
static void return_version (irr_connection_t *irr) {
  irr_add_answer (irr, "# IRRd -- version %s ", IRRD_VERSION);
  irr_send_answer (irr);
}

/*
 * irr_journal_range
 * !jRADB,RIPE,FOO,BAR
 * !j-*
 * Use irr_add_answer/irr_sender answer to return:
 * A<n>
 * RADB:Y:1000-2000:1500
 * RIPE:N:0-666
 * FOO:X:<explanatory text - optional>
 * BAR:X:<explanatory text - optional>
 * C
 *
 * Y means that the database is mirrorable.
 * N means that the database is not mirrorable, but we're reporting
 *   the current serial number.  You can use this to check for updates.
 *   The first number will _always_ be zero.  The second number may be
 *   zero if the CURRENTSERIAL file doesn't exist.
 * X means that the database doesn't exist, or we're denying information
 *   about an existing database for administrative reasons.
 *
 * Returned DB's are canonicalized to upper case.
 */
void irr_journal_range (irr_connection_t *irr, char *db) {
  char *p_db, *last;

  p_db = strtok_r(db, ",", &last);

  /* Iterate through the list of dbs */
  while (p_db != NULL) {

    /* Special case, "-*" */
    if (!strncmp(p_db, "-*", 2)) {
      LL_IntrIterate (IRR.ll_database, irr->database) {
        irr_journal_add_answer(irr);
      }
    }
    else {
      /* Does the database exist? */
      if ((irr->database = find_database(p_db)) == NULL) {
	char db_canon[BUFSIZE];

	db_canon[BUFSIZE - 1] = '\0';
	strncpy (db_canon, db, BUFSIZE-1);
	convert_toupper(db_canon);

	trace (ERROR, default_trace, "Database not found %s\n", db_canon);
	irr_add_answer (irr, "%s:X:Database does not exist.\n", db_canon);
	p_db = strtok_r(NULL, ",", &last);
	continue;
      }

      irr_journal_add_answer(irr);
    }

    p_db = strtok_r(NULL, ",", &last);
  }

  irr_send_answer (irr);
}

void irr_journal_add_answer (irr_connection_t *irr) {
  enum { UNDETERMINED, DENIED, READONLY, MIRRORABLE } status = UNDETERMINED;
  char db_canon[BUFSIZE], *p_last_export;
  u_long oldest_serial, current_serial, last_export = 0L;

  db_canon[BUFSIZE - 1] = '\0';
  strncpy (db_canon, irr->database->name, BUFSIZE-1);
  convert_toupper(db_canon);

  /* If we're going to administratively deny access, do it here */

  /* Is it not authoritative or not a mirror? */
  if (!(((irr->database->flags & IRR_AUTHORITATIVE) == IRR_AUTHORITATIVE) ||
        (irr->database->mirror_prefix != NULL))) {
    status = READONLY;
  }
  else {
    status = MIRRORABLE;
  }

  /* We can set the db to be not-mirrorable even if it is for 
     administrative reasons.  This would be a good spot to check
     for ACLs on mirroring. */

  if ((irr->database->mirror_access_list > 0) && 
      !apply_access_list (irr->database->mirror_access_list, irr->from)) {
    status = READONLY;
    /* We don't really need to log the fact that they can't mirror the db.
       We're telling them they can't.  We'll log it if they request 
       mirroring. */
  }

  /* Fetch the lowest journal number */
  if (status == MIRRORABLE) {
    if (find_oldest_serial(irr->database->name, JOURNAL_OLD, &oldest_serial) != 1)
      if (find_oldest_serial(irr->database->name, JOURNAL_NEW, &oldest_serial) != 1)
	 oldest_serial = 0;
   }
   else {
     oldest_serial = 0;
   }
  
  /* Fetch the current serial number */
  current_serial = irr->database->serial_number;

  /* Get the last export from the StatusFile, if any */
  if ((p_last_export = GetStatusString(IRR.statusfile, db_canon, "lastexport")) != NULL) {
     last_export = atol(p_last_export);
  }

  if (last_export != 0L) {
    irr_add_answer(irr, "%s:%s:%lu-%lu:%lu\n", db_canon,
		   ( status == READONLY ) ? "N" : "Y",
		   oldest_serial, current_serial, last_export);
  }
  else {
    irr_add_answer(irr, "%s:%s:%lu-%lu\n", db_canon,
		   ( status == READONLY ) ? "N" : "Y",
		   oldest_serial, current_serial);
  }

}



syntax highlighted by Code2HTML, v. 0.9.1