modules/qi/query_instructions.c

/* [<][>]
[^][v][top][bottom][index][help] */

FUNCTIONS

This source file includes following functions.
  1. create_name_query
  2. add_filter
  3. create_query
  4. fast_output
  5. filter
  6. write_results
  7. write_objects
  8. insert_radix_serials
  9. write_radix_immediate
  10. map_qc2rx
  11. run_referral
  12. add_ref_name
  13. QI_execute
  14. instruction_free
  15. QI_free
  16. valid_query
  17. QI_new
  18. QI_queries_to_string

/***************************************
  $Revision: 1.37 $


  Sql module (sq).  This is a mysql implementation of an sql module.

  Status: NOT REVUED, NOT TESTED

  Note: this code has been heavily coupled to MySQL, and may need to be changed
  (to improve performance) if a new RDBMS is used.

  ******************/ /******************
  Filename            : query_instructions.c
  Author              : ottrey@ripe.net
  OSs Tested          : Solaris
  Problems            : Moderately linked to MySQL.  Not sure which inverse
                        attributes each option has.  Would like to modify this
                        after re-designing the objects module.
  Comments            : Not sure about the different keytypes.
  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/
#include <stdio.h>
#include <string.h>
#include <glib.h>

#include "which_keytypes.h"
#include "query_instructions.h"
#include "mysql_driver.h"
#include "rp.h"
#include "stubs.h"
#include "constants.h"
#include "memwrap.h"
#include "wh_queries.h"

/*+ String sizes +*/
#define STR_S   63
#define STR_M   255
#define STR_L   1023
#define STR_XL  4095
#define STR_XXL 16383

/* XXX this must be removed from here!!! a .h file must be 
   generated from xml */

#include "defs.h"

/* create_name_query() */
/*++++++++++++++++++++++++++++++++++++++
  Create an sql query for the names table. 

  char *query_str

  const char *sql_query

  const char *keys
   
  More:
  +html+ <PRE>
  Authors:
  ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void create_name_query(char *query_str, const char *sql_query, const char *keys) {
/* [<][>][^][v][top][bottom][index][help] */
  int i;
  /* Allocate stuff */
  GString *from_clause = g_string_sized_new(STR_XL);
  GString *where_clause = g_string_sized_new(STR_XL);
  gchar **words = g_strsplit(keys, " ", 0);

  /* double quotes " are used in queries to allow querying for 
     names like O'Hara */

  g_string_sprintfa(from_clause, "names N%.2d", 0);
  g_string_sprintfa(where_clause, "N%.2d.name=\"%s\"", 0, words[0]);

  for (i=1; words[i] != NULL; i++) {
    g_string_sprintfa(from_clause, ", names N%.2d", i);
    g_string_sprintfa(where_clause, " AND N%.2d.name=\"%s\" AND N00.object_id = N%.2d.object_id", i, words[i], i);
  }

  sprintf(query_str, sql_query, from_clause->str, where_clause->str);

  /* Free up stuff */
  g_strfreev(words);
  g_string_free(where_clause,/* CONSTCOND */ TRUE);
  g_string_free(from_clause, /* CONSTCOND */ TRUE);

} /* create_name_query() */




static void add_filter(char *query_str, const Query_command *qc) {
/* [<][>][^][v][top][bottom][index][help] */
  int i;
  int qlen;
  char filter_atom[STR_M];

/*
  if (MA_bitcount(qc->object_type_bitmap) > 0) { 
    g_string_sprintfa(query_str, " AND (");
    for (i=0; i < C_END; i++) {
      if (MA_isset(qc->object_type_bitmap, i)) {
        g_string_sprintfa(query_str, "i.object_type = %d OR ", DF_get_class_dbase_code(i));
      }
    }
    g_string_truncate(query_str, query_str->len-3);
    g_string_append_c(query_str, ')');
  }
*/
  if (MA_bitcount(qc->object_type_bitmap) > 0) { 
    strcat(query_str, " AND (");
    for (i=0; i < C_END; i++) {
      if (MA_isset(qc->object_type_bitmap, i)) {
        strcpy(filter_atom, "");
        sprintf(filter_atom, "i.object_type = %d OR ", i);
                        /* XXX class codes should be used instead:
                           DF_get_class_dbase_code(i)) 
                           but currently the tables contain values of enums
                           (C_IN, etc) and not codes
                        */
        strcat(query_str, filter_atom);
      }
    }
    qlen = strlen(query_str);
    query_str[qlen-3] = ')';
    query_str[qlen-2] = '\0';
    query_str[qlen-1] = '\0';
  }
  
} /* add_filter() */

/* create_query() */
/*++++++++++++++++++++++++++++++++++++++
  Create an sql query from the query_command and the matching keytype and the
  selected inverse attributes.
  Note this clears the first inv_attribute it sees, so is called sequentially
  until there are no inv_attributes left.

  WK_Type keytype The matching keytype.

  const Query_command *qc The query command.

  mask_t *inv_attrs_bitmap The selected inverse attributes.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static char *create_query(const Query_t q, const Query_command *qc) {
/* [<][>][^][v][top][bottom][index][help] */
  char *result=NULL;
  char result_buff[STR_XL];
  Q_Type_t querytype;
  int conduct_test = 0;

  if (MA_bitcount(qc->inv_attrs_bitmap) > 0) {
    querytype = Q_INVERSE;
  }
  else {
    querytype = Q_LOOKUP;
  }

  if ( (q.query != NULL) 
    && (q.querytype == querytype) ) {
    conduct_test=1;
  }

  if (conduct_test == 1) {

    if (q.keytype == WK_NAME) { 
      /* Name queries require special treatment. */
       create_name_query(result_buff, q.query, qc->keys);
    }
    else if( q.keytype == WK_IPADDRESS ) {  /* ifaddr sql lookups */
        ip_range_t myrang;
        unsigned   begin, end;
        ip_keytype_t key_type;

        if (NOERR(IP_smart_range(qc->keys, &myrang, IP_EXPN, &key_type))) {
            if(IP_rang_b2_space(&myrang) == IP_V4 ) {
                IP_rang_b2v4(&myrang, &begin, &end);
                sprintf(result_buff, q.query, begin, end);
            }
            else {
                die;
            }
        }
    }
    else {
      sprintf(result_buff, q.query, qc->keys);
    }

    if (q.class == -1) {
      /* It is class type ANY so add the object filtering */
      add_filter(result_buff, qc);
    }

    dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);  
    strcpy(result, result_buff);
  }
  
  return result;
} /* create_query() */

/* fast_output() */
/*++++++++++++++++++++++++++++++++++++++
  This is for the '-F' flag.
  It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.

  Fast isn't fast anymore - it's just there for compatibility reasons.
  This could be speed up if there were breaks out of the loops, once it matched something.
  (Wanna add a goto Marek?  :-) ).

  const char *string The string to be "fast outputed".
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/

char *fast_output(const char *str) 
/* [<][>][^][v][top][bottom][index][help] */
{
int i,j;
char *result;
char result_bit[STR_L];
char result_buff[STR_XL];
gchar **lines = g_strsplit(str, "\n", 0);
char * const *attribute_names;
gboolean filtering_an_attribute = FALSE;
char *value;

attribute_names = DF_get_attribute_names();

strcpy(result_buff, "");
 
 for(i=0; attribute_names[i] != NULL; i++) {
   for (j=0; lines[j] != NULL; j++) {
     if (strncmp(attribute_names[i], lines[j], strlen(attribute_names[i])) == 0) {
       strcpy(result_bit, "");
       /* This is the juicy bit that converts the likes of; "source: RIPE" to "*so: RIPE" */
       value = strchr(lines[j], ':');
       value++;
       /* Now get rid of whitespace. */
       while (*value == ' ' || *value == '\t') {
         value++;
       }
       sprintf(result_bit, "*%s: %s\n", DF_get_attribute_code(i), value);
       strcat(result_buff, result_bit);
     }
     /* CONSTCOND */
     else if (filtering_an_attribute == TRUE) {
       switch (lines[j][0]) {
       case ' ':
       case '\t':
       case '+':
         strcpy(result_bit, "");
         sprintf(result_bit, "%s\n", lines[j]);
         strcat(result_buff, result_bit);
         break;
         
       default:
         filtering_an_attribute = FALSE;
       }
     }
   }
 }
 

 dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);

 strcpy(result, result_buff);
 
 return result;
} /* fast_output() */

/* filter() */
/*++++++++++++++++++++++++++++++++++++++
  Basically it's for the '-K' flag for non-set (and non-radix) objects.
  It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.

  This could be speed up if there were breaks out of the loops, once it matched something.
  (Wanna add a goto Marek?  :-) ).

  const char *string The string to be filtered.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
char *filter(const char *str) {
/* [<][>][^][v][top][bottom][index][help] */
  int i,j, passed=0;
  char *result;
  char result_bit[STR_L];
  char result_buff[STR_XL];
  gchar **lines = g_strsplit(str, "\n", 0);
  char * const *filter_names;
  gboolean filtering_an_attribute = FALSE;
  
  filter_names = DF_get_filter_names();

  strcpy(result_buff, "");
  for (i=0; filter_names[i] != NULL; i++) {
    for (j=0; lines[j] != NULL; j++) {
      if (strncmp(filter_names[i], lines[j], strlen(filter_names[i])) == 0) {
        strcpy(result_bit, "");
        sprintf(result_bit, "%s\n", lines[j]);
        strcat(result_buff, result_bit);
        passed++;
        
        /* can someone explain where %^&()! lint sees the condition here ? */
        /* CONSTCOND */
        filtering_an_attribute = TRUE;
      }
      /* CONSTCOND */
      else if (filtering_an_attribute == TRUE) {
        switch (lines[j][0]) {
          case ' ':
          case '\t':
          case '+':
            strcpy(result_bit, "");
            sprintf(result_bit, "%s\n", lines[j]);
            strcat(result_buff, result_bit);
          break;

          default:
            filtering_an_attribute = FALSE;
        }
      }
    }
  }

  if(passed) {
    strcat(result_buff, "\n");
  }

  dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);
  strcpy(result, result_buff);

  return result;
} /* filter() */

/* write_results() */
/*++++++++++++++++++++++++++++++++++++++
  Write the results to the client socket.

  SQ_result_set_t *result The result set returned from the sql query.
  unsigned filtered       if the objects should go through a filter (-K)
  sk_conn_st *condat      Connection data for the client    
  int maxobjects          max # of objects to write

  XXX NB. this is very dependendant on what rows are returned in the result!!!
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static int write_results(SQ_result_set_t *result, 
/* [<][>][^][v][top][bottom][index][help] */
                         unsigned filtered,
                         unsigned fast,
                         sk_conn_st *condat,
                         acc_st    *acc_credit,
                         acl_st    *acl
                         ) {
  SQ_row_t *row;
  char *str;
  char *filtrate;
  char *fasted;
  int retrieved_objects=0;
  char *objt;
  int type;

  /* Get all the results - one at a time */
  if (result != NULL) {
    /* here we are making use of the mysql_store_result capability
       of interrupting the cycle of reading rows. mysql_use_result
       does not allow that, must be read until end */
    
      while ( (row = SQ_row_next(result)) != NULL  &&  acc_credit->denials == 0 ) {
          if (  (str = SQ_get_column_string(result, row, 0)) == NULL
             || (objt = SQ_get_column_string(result, row, 3)) == NULL )  { 
          /* handle it somehow ? */
          die; 
      }
      else  { 
          /* get + add object type */
          type = atoi(objt);
          
          /* ASP_QI_LAST_DET */
          ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
                    "Retrieved serial id = %d , type = %s", atoi(str), objt);
          
          wr_free(str);
          wr_free(objt);
      }

      /* decrement credit for accounting purposes */
      
      /* XXX the definition of private/public should go into the defs (xml) */
      switch( type ) {
      case C_PN:
      case C_RO: 
        if( acc_credit->private_objects <= 0 && acl->maxbonus != -1 ) {
          /* must be negative, will be subtracted */
          acc_credit->denials = -1;
          continue; /* go to the head of the loop */
        }
        acc_credit->private_objects --;
        break;
      default:
        if( acc_credit->public_objects <= 0 && acl->maxpublic != -1 ) {
          acc_credit->denials = -1;
          continue; /* go to the head of the loop */
        }
        acc_credit->public_objects --;
      }
      
      if ((str = SQ_get_column_string(result, row, 2)) == NULL) { die; } 
      else {
        
        /* The fast output stage */
        if (fast == 1) {
          fasted = fast_output(str);
          wr_free(str);
          str = fasted;
        }
        
        /* The filtering stage */
        if (filtered == 0) {
          SK_cd_puts(condat, str);
          SK_cd_puts(condat, "\n");
        }
        else { 
          
          /* XXX accounting should be done AFTER that, and not for objects
             filtered out */

          filtrate = filter(str);
          SK_cd_puts(condat, filtrate);
          wr_free(filtrate);
        }
        retrieved_objects++;
      }
      wr_free(str);
    }
  }
  
  return retrieved_objects;
} /* write_results() */

/* write_objects() */
/*++++++++++++++++++++++++++++++++++++++
  This is linked into MySQL by the fact that MySQL doesn't have sub selects
  (yet).  The queries are done in two stages.  Make some temporary tables and
  insert into them.  Then use them in the next select.

  SQ_connection_t *sql_connection The connection to the database.

  char *id_table The id of the temporary table (This is a result of the hacky
                  way we've tried to get MySQL to do sub-selects.)

  sk_conn_st *condat  Connection data for the client

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  ++++++++++++++++++++++++++++++++++++++*/
static void write_objects(SQ_connection_t *sql_connection, 
/* [<][>][^][v][top][bottom][index][help] */
                          char *id_table, 
                          unsigned int filtered, 
                          unsigned int fast, 
                          sk_conn_st *condat,
                          acc_st    *acc_credit,
                          acl_st    *acl
                          ) 
{
  /* XXX This should really return a linked list of the objects */

  SQ_result_set_t *result;
  int retrieved_objects=0;
  char sql_command[STR_XL];
      
  /* XXX These may and should change a lot. */
  sprintf(sql_command, Q_OBJECTS, id_table);
  dieif(SQ_execute_query(sql_connection, sql_command, &result) == -1 );
    
  retrieved_objects = write_results(result, filtered, fast, condat, 
                                    acc_credit, acl);
  SQ_free_result(result);
    
} /* write_objects() */

/* insert_radix_serials() */
/*++++++++++++++++++++++++++++++++++++++
  Insert the radix serial numbers into a temporary table in the database.

  mask_t bitmap The bitmap of attribute to be converted.
   
  SQ_connection_t *sql_connection The connection to the database.

  char *id_table The id of the temporary table (This is a result of the hacky
                  way we've tried to get MySQL to do sub-selects.)
  
  GList *datlist The list of data from the radix tree.

  XXX Hmmmmm this isn't really a good place to free things... infact it's quite nasty.  :-(
  
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
             <LI><A HREF="http://www.gtk.org/rdp/glib/glib-doubly-linked-lists.html">Glist</A>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void insert_radix_serials(SQ_connection_t *sql_connection, char *id_table, GList *datlist) {
/* [<][>][^][v][top][bottom][index][help] */
  GList    *qitem;
  char sql_command[STR_XL];
  int serial;

  for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
    rx_datcpy_t *datcpy = qitem->data;

    serial = datcpy->leafcpy.data_key;

    sprintf(sql_command, "INSERT INTO %s values (%d)", id_table, serial);
    dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1);

    wr_free(datcpy->leafcpy.data_ptr);
  }

  wr_clear_list( &datlist );

} /* insert_radix_serials() */


/* write_radix_immediate() */
/*++++++++++++++++++++++++++++++++++++++
  Display the immediate data carried with the objects returned by the
  radix tree.

  GList *datlist      The linked list of dataleaf copies
  sk_conn_st *condat  Connection data for the client
  acc_st  *acc_credit Accounting struct

More:
  +html+ <PRE>
  Authors:
        marek
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>
  

  Also free the list of answers.
*/
static void write_radix_immediate(GList *datlist, 
/* [<][>][^][v][top][bottom][index][help] */
                                  sk_conn_st *condat,
                                  acc_st    *acc_credit) 
{
  GList    *qitem;
  
  for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
    rx_datcpy_t *datcpy = qitem->data;

    SK_cd_puts(condat, datcpy->leafcpy.data_ptr );
    SK_cd_puts(condat, "\n");
    
    wr_free(datcpy->leafcpy.data_ptr);
    
    acc_credit->public_objects --;
  }
  
  wr_clear_list( &datlist );
} /* write_radix_immediate() */


/* map_qc2rx() */
/*++++++++++++++++++++++++++++++++++++++
  The mapping between a query_command and a radix query.

  Query_instruction *qi The Query Instruction to be created from the mapping
                        of the query command.

  const Query_command *qc The query command to be mapped.

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static int map_qc2rx(Query_instruction *qi, const Query_command *qc) {
/* [<][>][^][v][top][bottom][index][help] */
  int result=1;

  qi->rx_keys = qc->keys;

  if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_EXLESS;
      qi->rx_par_a = 0;
  }
  else if ( (qc->L == 1) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_LESS;
    qi->rx_par_a = RX_ALL_DEPTHS;
  }
  else if ( (qc->L == 0) && (qc->M == 1) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_MORE;
      qi->rx_par_a = RX_ALL_DEPTHS;
  }
  else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 1) && (qc->m == 0) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_LESS;
    qi->rx_par_a = 1;
  }
  else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 1) && (qc->x == 0) ) {
    qi->rx_srch_mode = RX_SRCH_MORE;
    qi->rx_par_a = 1;
  }
  else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 1) ) {
    qi->rx_srch_mode = RX_SRCH_EXACT;
    qi->rx_par_a = 0;
  }
  else {
      /* user error  ( XXX : this should have been checked before) */
      
      ER_dbg_va(FAC_QI, ASP_QI_SKIP, 
                "ERROR in qc2rx mapping: bad combination of flags");
      result = 0;
  }
  
  return result;
  
} /* map_qc2rx() */

/* run_referral() */
/*
   invoked when no such domain found. Goes through the domain table
   and searches for shorter domains, then if it finds one with referral 
   it performs it, otherwise it just returns nothing.

   to perform referral, it actually composes the referral query 
   for a given host/port/type and calls the whois query function.

   Well, it returns nothing anyway (void). It just prints to the socket.

*/
void run_referral(SQ_connection_t *sql_connection, Query_instructions *qis,   Query_environ *qe, int qi_index) {
/* [<][>][^][v][top][bottom][index][help] */
  char *dot = qis->qc->keys;
  char querystr[STR_L];
  SQ_row_t *row;
  SQ_result_set_t *result;
  char sql_command[STR_XL];
  int stop_loop=0;
  char *ref_host;
  char *ref_type;
  char *ref_port;
  int  ref_port_int;

  strcpy(querystr,"");

  while( !stop_loop && (dot=index(dot,'.')) != NULL ) {
    dot++;

    ER_dbg_va(FAC_QI, ASP_QI_REF_DET, "run_referral: checking %s", dot);

    sprintf(sql_command, "SELECT domain.object_id, domain, type, port, host FROM domain, refer WHERE domain.object_id = refer.object_id AND domain = '%s'", dot);
    dieif( SQ_execute_query(sql_connection, sql_command, &result) == -1);

    switch( SQ_num_rows(result) ) {
      case 0: /* no such domain -> no action, will try next chunk */
      break;

      case 1: /* check for referral host and perform query if present
               in any case end the loop */
      stop_loop=1;
      assert( (row = SQ_row_next(result)) != NULL);
      
      ref_host = SQ_get_column_string(result, row, 4);

      ER_dbg_va(FAC_QI, ASP_QI_REF_GEN, "referral host is %s", ref_host); 

      if( ref_host != NULL && strlen(ref_host) > 0 ) {
        ref_type = SQ_get_column_string(result, row, 2);
        ref_port = SQ_get_column_string(result, row, 3);
        
        /* get the integer value, it should be correct */
        if( sscanf( ref_port, "%d",&ref_port_int) < 1 ) {
          die;
        }
         
        /* compose the query: */

        /* put -r if the reftype is RIPE and -r or -i were used */
        if( strcmp(ref_type,"RIPE") == 0 
            && (   Query[qis->instruction[qi_index]->queryindex]
                   .querytype == Q_INVERSE       
                   || qis->recursive > 0  )   ) {
          strcat(querystr," -r ");
        }

        /* prepend with -Vversion,IP for type CLIENTADDRESS */
        if( strcmp(ref_type,"CLIENTADDRESS") == 0 ) {
          char optv[STR_M];

          snprintf(optv,STR_M," -V%s,%s ","RIP0.88", qe->condat.ip);
          strcat(querystr,optv);
        }

        /* now set the search term - set to the stripped down version 
           for inverse query, full-length otherwise */
        if( Query[qis->instruction[qi_index]->queryindex].querytype == Q_INVERSE ) {
          strcat(querystr,dot);
        }
        else {
          strcat(querystr,qis->qc->keys);
        }
        
        SK_cd_puts(&(qe->condat), "% Please note: this information is not stored in the RIPE database\n%\n% connecting to the remote referral site ");
        SK_cd_puts(&(qe->condat), ref_host);
        SK_cd_puts(&(qe->condat), "\n\n");

        /* WH_sock(sock, host, port, query, maxlines, timeout)) */
        switch( WH_sock(qe->condat.sock, ref_host, ref_port_int, querystr,  25, 5) ) {
        case WH_TIMEOUT:
            SK_cd_puts(&(qe->condat),"referral timeout\n");
            break;
            
        case WH_MAXLINES:
            SK_cd_puts(&(qe->condat),"referral maxlines exceeded\n");
            break;
            
        case WH_BADHOST:
            SK_cd_puts(&(qe->condat),"referral host not found\n");
            break;

        case WH_CONNECT:
            SK_cd_puts(&(qe->condat),"referral host not responding\n");
            break;

        case WH_BIND:
        case WH_SOCKET:
            /* XXX internal server problem... what to do - wait ? */
        default:
          ;
        } /*switch WH_sock */
      }
      break;

      default: /* more than one domain in this file: something broken */
      die;
    }
    SQ_free_result(result);
  }
} /*run_referral*/

static
void 
add_ref_name(SQ_connection_t *sql_connection, 
/* [<][>][^][v][top][bottom][index][help] */
             char *rectable,
             char *allnames
             )
{
  /* construct the query, allow zero-length list */
  if( strlen(allnames) > 0 ) {
    char final_query[STR_XL];
    char select_query[STR_XL];

    create_name_query(select_query, "SELECT N00.object_id FROM %s WHERE %s "
                      "AND N00.object_type != 100 AND N00.thread_id = 0", 
                      allnames);
    
    sprintf(final_query, "INSERT INTO %s %s",
            rectable,
            select_query);
    
    dieif(SQ_execute_query(sql_connection, final_query, NULL) == -1 );

    allnames[0]=0;
  }
}


/* QI_execute() */
/*++++++++++++++++++++++++++++++++++++++
  Execute the query instructions.  This is called by a g_list_foreach
  function, so each of the sources in the "database source" list can be passed
  into this function.

  This function has bloated itself.  Can we split it up Marek?  (ottrey 13/12/99)

  void *database_voidptr Pointer to the database.
  
  void *qis_voidptr Pointer to the query_instructions.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
             <LI><A
             HREF="http://www.gtk.org/rdp/glib/glib-singly-linked-lists.html#G-SLIST-FOREACH">g_list_foreach</A>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
void QI_execute(void *database_voidptr, 
/* [<][>][^][v][top][bottom][index][help] */
                Query_instructions *qis, 
                Query_environ *qe,      
                acc_st *acc_credit,
                acl_st *acl
                ) {
  char *database = (char *)database_voidptr;
  Query_instruction **ins=NULL;
  char id_table[STR_S];
  char sql_command[STR_XL];
  GList *datlist=NULL;
  int i;
  SQ_row_t *row;
  SQ_result_set_t *result;
  SQ_connection_t *sql_connection=NULL;
  char *countstr;
  int   count,afcount;

  sql_connection = SQ_get_connection(CO_get_host(), CO_get_database_port(), database, CO_get_user(), CO_get_password() );

  if (sql_connection == NULL) {
    SK_cd_puts(&(qe->condat), "% WARNING: Failed to make connection to ");
    SK_cd_puts(&(qe->condat), database);
    SK_cd_puts(&(qe->condat), " database mirror.\n\n");

    /* XXX void prevents us from sending any error code back. It is OK ? */
    return;
  }
  
  /* XXX This is a really bad thing to do.  
     It should'nt _have_ to be called here.
     But unfortunately it does.   -- Sigh. */
  sprintf(id_table, "ID_%ld", mysql_thread_id(sql_connection) );

  /* create a table for id's of all objects found NOT NULL , UNIQUE(id) */
  sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", id_table);
  dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );

  /* create a table for special subqueries (domain only for now) */
  sprintf(sql_command, "CREATE TABLE %s_S ( id int ) TYPE=HEAP", id_table);
  dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
    
  /* Iterate through query instructions */
  ins = qis->instruction;
  for (i=0; ins[i] != NULL; i++) {
    Query_instruction *qi = ins[i];

    switch ( qi->search_type ) {
    case R_SQL:
      if ( qi->query_str != NULL ) {

        /* handle special cases first */
        if( Query[qi->queryindex].class == C_DN ) {

          /* XXX if any more cases than just domain appear, we will be
             cleaning the _S table from the previous query here 
             
             "DELETE FROM %s_S"
          */

          /* now query into the _S table */
          sprintf(sql_command, "INSERT INTO %s_S %s", id_table, qi->query_str);
          dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1);
          
          /* if any results - copy to the id's table. 
             Otherwise, run referral */
          
          sprintf(sql_command, "SELECT COUNT(*) FROM %s_S", id_table);
          dieif(SQ_execute_query(sql_connection, sql_command, &result) == -1 );
          row = SQ_row_next(result);
          countstr = SQ_get_column_string(result, row, 0);
          sscanf(countstr, "%d", &count);
          SQ_free_result(result);

          ER_dbg_va(FAC_QI, ASP_QI_COLL_DET, 
                    "DN lookup for %s found %d entries",
                    qis->qc->keys, count);
                 
          if( count ) {
            sprintf(sql_command, "INSERT INTO %s SELECT id FROM %s_S", 
                    id_table, id_table);
            dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1);
          }

          if( count == 0 
              || Query[qi->queryindex].querytype == Q_INVERSE ) {
            /* now: if the domain was not found, we run referral.
               unless prohibited by a flag 
              
               But for inverse queries we return the things that were
               or were not found AND also do the referral unless prohibited.
            */
            if (qis->qc->R == 0) {
              run_referral(sql_connection, qis, qe, i);
            }
          }
          
        }
        else {
          sprintf(sql_command, "INSERT INTO %s %s", id_table, qi->query_str);
           dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
        }
      }
      
      /* debug */
      afcount = SQ_get_affected_rows(sql_connection);
      
      ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
                "%d entries added in %s query for %s",
                afcount, Query[qi->queryindex].descr, qis->qc->keys
                );
      break;
      
#define RIPE_REG 17
    case R_RADIX:
        if ( RP_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, qi->rx_keys, 
                           RIPE_REG, Query[qi->queryindex].attribute, 
                           &datlist, RX_ANS_ALL) == RX_OK ) {
          if( ER_is_traced(FAC_QI, ASP_QI_COLL_DET ) ) {
            /* prevent unnecessary g_list_length call */

            ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
                      "%d entries after %s (mode %d par %d reg %d) query for %s",
                      g_list_length(datlist),
                      Query[qi->queryindex].descr,
                      qi->rx_srch_mode, qi->rx_par_a, 
                      RIPE_REG, 
                      qi->rx_keys);
          }
        }
        else {
            die;
        }
      break;

    default: die;
    } /* switch */
  } /* for every instruction */

  /* post-processing */



  /* display */

  if( qis->filtered == 0 ) {
    /* add radix results to the end */
    insert_radix_serials(sql_connection, id_table, datlist);

    /* fetch recursive objects (ac,tc,zc,ah) */
    if ( qis->recursive ) {
      char rec_table[32];
      SQ_result_set_t *result;
      SQ_row_t *row;
      int thisid = 0;
      int oldid = 0;
      char allnames[STR_M];

      sprintf(rec_table, "%s_R", id_table);
      
      /* a temporary table for recursive data must be created, because
         a query using the same table as a source and target is illegal
         ( like: INSERT into ID_123 SELECT * FROM ID_123,admin_c WHERE ... )
       */
      sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", rec_table);
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
      
      /* find the contacts */      
      sprintf(sql_command, Q_REC, rec_table, id_table, "author");
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );

      sprintf(sql_command, Q_REC, rec_table, id_table, "admin_c");
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
      
      sprintf(sql_command, Q_REC, rec_table, id_table, "tech_c" );
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
      
      sprintf(sql_command, Q_REC, rec_table, id_table, "zone_c" );
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );


      /* replace references to dummies by references by name */
      sprintf(sql_command, 
              " SELECT id, name    FROM %s IDS STRAIGHT_JOIN names "
              " WHERE IDS.id = names.object_id "
              "      AND names.object_type = 100"
              " ORDER BY id",
              rec_table);

      dieif(SQ_execute_query(sql_connection, sql_command, &result) == -1 );

      allnames[0]=0;
      /* now go through the results and collect names */
      while ( (row = SQ_row_next(result)) != NULL ) {
        char *id   = SQ_get_column_string(result, row, 0);
        char *name = SQ_get_column_string(result, row, 1);
        
        thisid = atoi(id);

        /* when the id changes, the name is complete */
        if( thisid != oldid && oldid != 0 ) {
          add_ref_name( sql_connection, rec_table, allnames);
        }

        strcat(allnames, name);
        strcat(allnames, " ");
        oldid = thisid;
        wr_free(id);
        wr_free(name);
      }
      /* also do the last name */
      add_ref_name( sql_connection, rec_table, allnames);

      SQ_free_result(result);

      /* now copy things back to the main temporary table   */
      sprintf(sql_command, "INSERT INTO %s SELECT * FROM %s_R", 
              id_table, id_table);
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
      
      /* Now drop the IDS recursive table */
      sprintf(sql_command, "DROP TABLE %s_R", id_table);
      dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
    } /* if recursive */
  }
  else {
    /* -K filtering */
    /* right now only filtering, no expanding sets */
    /* implies no recursion */
    
    /* XXX write_set_objects() ?? */
    
    /* display the immediate data from the radix tree */
    /* XXX pass+decrease credit here */
    write_radix_immediate(datlist, &(qe->condat), acc_credit );
  }

  /* display objects */
  write_objects(sql_connection, id_table, qis->filtered,
                qis->fast, &(qe->condat), acc_credit, acl);


  /* Now drop the _S table */
  sprintf(sql_command, "DROP TABLE %s_S", id_table);
  dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );

  /* Now drop the IDS table */
  sprintf(sql_command, "DROP TABLE %s", id_table);
  dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
  SQ_close_connection(sql_connection);

  
} /* QI_execute() */



/* instruction_free() */
/*++++++++++++++++++++++++++++++++++++++
  Free the instruction.

  Query_instruction *qi query_instruction to be freed.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void instruction_free(Query_instruction *qi) {
/* [<][>][^][v][top][bottom][index][help] */
  if (qi != NULL) {
    if (qi->query_str != NULL) {
      wr_free(qi->query_str);
    }
    wr_free(qi);
  }
} /* instruction_free() */

/* QI_free() */
/*++++++++++++++++++++++++++++++++++++++
  Free the query_instructions.

  Query_instructions *qis Query_instructions to be freed.
   
  XXX This isn't working too well at the moment.

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
void QI_free(Query_instructions *qis) {
/* [<][>][^][v][top][bottom][index][help] */
  int i;

  for (i=0; qis->instruction[i] != NULL; i++) {
    instruction_free(qis->instruction[i]);
  } 

  if (qis != NULL) {
    wr_free(qis);
  }

} /* QI_free() */

/*++++++++++++++++++++++++++++++++++++++
  Determine if this query should be conducted or not.

  If it was an inverse query - it the attribute appears in the query command's bitmap.
  If it was a lookup query - if the attribute appears in the object type bitmap or
                             disregard if there is no object_type bitmap (Ie object filter).

  mask_t bitmap The bitmap of attribute to be converted.
   
  const Query_command *qc The query_command that the instructions are created
                          from.
  
  const Query_t q The query being investigated.

  ++++++++++++++++++++++++++++++++++++++*/
static int valid_query(const Query_command *qc, const Query_t q) {
/* [<][>][^][v][top][bottom][index][help] */
  int result=0;

  if (MA_isset(qc->keytypes_bitmap, q.keytype) == 1) {
    if (q.query != NULL) {
      switch (q.querytype) {
        case Q_INVERSE:
          if (MA_isset(qc->inv_attrs_bitmap, q.attribute) ) {
            result = 1;
          }
        break;

        case Q_LOOKUP:
          if (MA_bitcount(qc->object_type_bitmap) == 0) {
            result=1;
          }
          else if (q.class<0 || MA_isset(qc->object_type_bitmap, q.class)) {
            result=1;
          }
        break;

        default:
          fprintf(stderr, "qi:valid_query() -> Bad querytype\n");
      }
    }
  }

  return result;
} /* valid_query() */

/* QI_new() */
/*++++++++++++++++++++++++++++++++++++++
  Create a new set of query_instructions.

  const Query_command *qc The query_command that the instructions are created
                          from.

  const Query_environ *qe The environmental variables that they query is being
                          performed under.
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
Query_instructions *QI_new(const Query_command *qc, const Query_environ *qe) {
/* [<][>][^][v][top][bottom][index][help] */
  Query_instructions *qis=NULL;
  Query_instruction *qi=NULL;
  int i_no=0;
  int i;
  char *query_str;

  dieif(wr_calloc( (void **) & qis, 1, sizeof(Query_instructions)) != UT_OK);

  qis->filtered = qc->filtered;
  qis->fast = qc->fast;
  qis->recursive = qc->recursive;
  qis->qc = (qc);

  
  for (i=0; Query[i].query != NULL; i++) {

    /* If a valid query. */
    if ( valid_query(qc, Query[i]) == 1) {

      dieif( wr_calloc((void **) &qi, 1, sizeof(Query_instruction)) != UT_OK);

      qi->queryindex = i;

      /* SQL Query */
      if ( Query[i].refer == R_SQL) {
        qi->search_type = R_SQL;
        query_str = create_query(Query[i], qc);

        if (query_str!= NULL) {
          qi->query_str = query_str;
          qis->instruction[i_no++] = qi;
        }
      }
      /* Radix Query */
      else if (Query[i].refer == R_RADIX) {
        qi->search_type = R_RADIX;
        
        if (map_qc2rx(qi, qc) == 1) {
          int j;
          int found=0;
          
          /* check that there is no such query yet, for example if
             more than one keytype (wk) matched */
          for (j=0; j<i_no; j++) {
            Query_instruction *qij = qis->instruction[j];
            
            if(    qij->search_type == R_RADIX
                   && Query[qij->queryindex].attribute 
                   == Query[qi ->queryindex].attribute) {
              
              found=1;
              break;
            }
          }
          
          if ( found ) {
            /* Discard the Query Instruction */
            wr_free(qi);
          } 
          else {
            /* Add the query_instruction to the array */
            qis->instruction[i_no++] = qi;
          }
        }
      }
      else {
          /* ERROR: bad search_type */
          die;
      }
    }
  }
  qis->instruction[i_no++] = NULL;


  {  /* tracing */
      char *descrstr = QI_queries_to_string(qis);

      ER_dbg_va(FAC_QI, ASP_QI_COLL_GEN, "Queries: %s", descrstr );
      wr_free( descrstr );
  }

  return qis;

} /* QI_new() */

/* QI_queries_to_string() 
   
   returns a list of descriptions for queries that will be performed.
*/

char *QI_queries_to_string(Query_instructions *qis)
/* [<][>][^][v][top][bottom][index][help] */
{
   Query_instruction *qi;
   int i;
   char *resstr = NULL;

   dieif( wr_realloc((void **)&resstr, resstr, 2 ) != UT_OK);
   strcpy(resstr, "{");

   for( i = 0; ( qi=qis->instruction[i] ) != NULL;  i++ ) {
       char *descr = Query[qi->queryindex].descr;
       int oldres = strlen( resstr );
       
       dieif( wr_realloc((void **)&resstr, resstr, oldres+strlen(descr)+2) != UT_OK);
       strcat(resstr, descr);
       strcat(resstr, ",");
   }
   if( i>0 ) {
       /* cancel the last comma */
       resstr[strlen(resstr)-1] = 0;
   }

   dieif( wr_realloc((void **)&resstr, resstr, strlen( resstr ) + 2 ) 
          != UT_OK);
   strcat(resstr, "}");
   
   return resstr;
}

/* [<][>][^][v][top][bottom][index][help] */