/***************************************
  $Revision: 1.31 $

  Query command module (qc).  This is what the whois query gets stored as in
  memory.

  Status: NOT REVUED, TESTED

  ******************/ /******************
  Filename            : query_command.c
  Authors             : ottrey@ripe.net
                        marek@ripe.net
  ******************/ /******************
  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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define QC_IMPL

#include "query_command.h"
#include "defs.h"
#include "constants.h"
#include "which_keytypes.h"
#include "memwrap.h"

#define MAX_OPT_ARG_C 20

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

#ifdef HAVE_STRSEP
/* good */
#else 
#  if defined(HAVE_STRTOK_R) && !defined(HAVE_STRSEP)
/* emulate strsep with strtok_r 
   by making first arg to strtok_r point to the last 
*/
char *
strsep(char **stringp, const char *delim)
{
  return strtok_r( *stringp, delim, stringp);
}
#  else
#  error "must have strsep or strtok_r"
#  endif
#endif


/* my_getopt() */
/*++++++++++++++++++++++++++++++++++++++
  A thread safe version of getopt, used to get the options from the whois
  query.

  int opt_argc The number of query arguments.
  
  char **opt_argv The query arguments.
  
  char *optstring The string containing valid options.
  
  int *my_optind_ptr A pointer to the index into the options of the option
  returned.
  
  char **my_optarg_ptr A pointer to the arguments to be returned.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+     <LI>man getopt
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static int my_getopt(int opt_argc, char **opt_argv, char *optstring, int *my_optind_ptr, char **my_optarg_ptr) {
  int c='?';
  int i, j;
  int no_options;
  int optind = *my_optind_ptr;
  char option[4];
  int option_matched=0;
  
  /* Get the number of options in the option string */
  for(i=0, no_options=0; i < strlen(optstring) ; i++) {
    if (optstring[i] != ':') {
      no_options++;
    }
  }

  /* Iterate through all the option until it matches the current opt_argv */
  /* Ie. opt_argv[optind] */
  for (i=0, j=0; i <= no_options; i++, j++) {
    /* Construct one option from the optstring */
    option[0] = '-';
    if (optstring[j] == ':') {
      j++;
    }
    option[1] = optstring[j];
    if ( optstring[j+1] == ':' ) {
      option[2] = ':';
    }
    else {
      option[2] = '\0';
    }
    option[3] = '\0';

    if (optind < opt_argc) {
      if (strlen(opt_argv[optind]) > 0) {
        
	  /*	  printf("opt_argv[%d] == option <==> %s == %s\n", 
		  optind, opt_argv[optind], option); */
        
        if (strncmp(opt_argv[optind], option, 2) == 0) {
          /* Does the option have arguments. */
          if (option[2] == ':') {
            /* If the option has arguments */
            if (strlen(opt_argv[optind]) > 2) {
              /* If the arguments are in this token */
              *my_optarg_ptr = (opt_argv[optind])+2;
            }
            else {
              /* If the arguments are in the next token */
              *my_optarg_ptr = opt_argv[optind+1];
              optind++;
            }
          }
          else {
            /* There are no arguments to this token */
            *my_optarg_ptr = NULL;
          }
          /* Option matched - break out of the search */
          option_matched = 1;
          break;
        }
      }
    }
  } /* for() */
  
  if ( option_matched == 1 ) {
    /* This option was matched, return it. */
    c = option[1];

    /* Move to the next opt_argv */
    optind++;
    *my_optind_ptr = optind;
  }
  else {
    /* Discontinue search */
    c = EOF;
  }

  return c;

} /* my_getopt() */

/* QC_environ_to_string() */
/*++++++++++++++++++++++++++++++++++++++
  Convert the query_environ to a string.

  Query_environ *query_environ The query_environ to be converted.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
char *QC_environ_to_string(Query_environ qe) {
  char *result;
  char *str1;
  char str2[IP_ADDRSTR_MAX];
  char result_buf[STR_XL];

  str1 = CO_sources_list_to_string(qe.sources_list);
  
  if( IP_addr_b2a( &(qe.pIP), str2, IP_ADDRSTR_MAX) != IP_OK ) { 
    *str2 = '\0';
  }
  
  sprintf(result_buf, "host=%s, keep_connection=%s, sources=%s, version=%s%s%s", qe.condat.ip, 
          qe.k?"on":"off", 
          str1, 
          (qe.version == NULL) ? "?" : qe.version,
          *str2 == '\0' ? "" : ", passedIP=",
          *str2 == '\0' ? "" : str2
          );
  
  wr_free(str1);

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

  strcpy(result, result_buf);
  
  return result;
  
} /* QC_environ_to_string() */

/* QC_query_command_to_string() */
/*++++++++++++++++++++++++++++++++++++++
  Convert the query_command to a string.

  Query_command *query_command The query_command to be converted.
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
char *QC_query_command_to_string(Query_command *query_command) {
  char *result;
  char result_buf[STR_XL];
  char *str1;
  char *str2;
  char *str3;

  str1 = MA_to_string(query_command->inv_attrs_bitmap, DF_get_attribute_names());
  str2 = MA_to_string(query_command->object_type_bitmap, DF_get_class_names());
  str3 = WK_to_string(query_command->keytypes_bitmap);
  
  sprintf(result_buf, "Query_command : inv_attrs=%s, recursive=%s, object_type=%s, (e=%d,g=%d,l=%d,m=%d,q=%d,t=%d,v=%d,x=%d,F=%d,K=%d,L=%d,M=%d,R=%d,S=%d), possible keytypes=%s, keys=[%s]",
          str1,
	  query_command->recursive?"y":"n",
          str2,
          query_command->e,
          query_command->g,
          query_command->l,
          query_command->m,
          query_command->q,
          query_command->t,
          query_command->v,
          query_command->x,
          query_command->fast,
          query_command->filtered,
          query_command->L,
          query_command->M,
          query_command->R,
          query_command->S,
          str3,
          query_command->keys);
  wr_free(str1);
  wr_free(str2);
  wr_free(str3);

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

  return result;
  
} /* QC_query_command_to_string() */

/* log_command() */
/*++++++++++++++++++++++++++++++++++++++
  Log the command.
  This is more to do with Tracing.  And should/will get merged with a tracing
  module (when it is finalized.)

  char *query_str
  
  Query_command *query_command
   
  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
static void log_command(char *query_str, Query_command *query_command) {
  char *str;

  if( ER_is_traced(FAC_QC, ASP_QC_BUILD) ) {
    str = QC_query_command_to_string(query_command);
    ER_dbg_va(FAC_QC, ASP_QC_BUILD,
	      "query=[%s]   %s", query_str, str);
    wr_free(str);
  }
} /* log_command() */

/* QC_environ_free() */
/*++++++++++++++++++++++++++++++++++++++
  Free the query_environ.

  Query_command *qc query_environ to be freed.

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

  ++++++++++++++++++++++++++++++++++++++*/
void QC_environ_free(Query_environ *qe) {
  if (qe != NULL) {
    if (qe->version != NULL) {
      wr_free(qe->version);
    }

    if (qe->sources_list != NULL) {
      g_list_free(qe->sources_list);
    }
    wr_free(qe);
  }
} /* QC_environ_free() */

/* QC_free() */
/*++++++++++++++++++++++++++++++++++++++
  Free the query_command.

  Query_command *qc query_command to be freed.

  XXX I'm not sure the bitmaps will get freed.
  qc->inv_attrs_bitmap
  qc->object_type_bitmap
  qc->keytypes_bitmap

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

  ++++++++++++++++++++++++++++++++++++++*/
void QC_free(Query_command *qc) {
  if (qc != NULL) {
    if (qc->keys != NULL) {
      wr_free(qc->keys);
    }
    wr_free(qc);
  }
} /* QC_free() */



/* QC_fill() */
/*++++++++++++++++++++++++++++++++++++++
  Create a new query_command.

  
  
  char *query_str The garden variety whois query string.

  Query_environ *qe the environment

  Pre-condition: 

  Returns -1 when query incorrect, 0 otherwise

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

  ++++++++++++++++++++++++++++++++++++++*/
static
int QC_fill(char *query_str, 
	     Query_command *query_command,
	     Query_environ *qe) {
  char *my_optarg;
  int my_optind = 0;
  int c;
  int errflg = 0;
  char *inv_attrs_str = NULL;
  char *object_types_str = NULL;
  char *sources_str = NULL;
  int opt_argc;
  gchar **opt_argv;
  char *value;
  char *tmp_query_str;
  int key_length;
  int i;
  int index;
  int type;
  int attr;

  char str_buf[STR_XL];

  GList *first_source;

  query_command->e = 0;
  query_command->g = 0;
  query_command->inv_attrs_bitmap = MA_new(MA_END);
  query_command->recursive = 1;  /* Recursion is on by default. */
  query_command->l = 0;
  query_command->m = 0;
  query_command->q = -1;
  query_command->t = -1;
  query_command->v = -1;
  query_command->x = 0;
  query_command->fast = 0;
  query_command->filtered = 0;
  query_command->L = 0;
  query_command->M = 0;
  query_command->R = 0;
  query_command->S = 0;
  query_command->object_type_bitmap = MA_new(MA_END);
  /*
  query_command->keytypes_bitmap = MA_new(MA_END);
  */
  query_command->keys = NULL;

  /* This is so Marek can't crash me :-) */
  /* Side Effect - query keys are subsequently cut short to STR_S size. */

  dieif( wr_calloc((void **)&tmp_query_str, 1, STR_S+1) != UT_OK);  
  strncpy(tmp_query_str, query_str, STR_S);

  /* Create the arguments. */
  /* This allows only a maximum of MAX_OPT_ARG_C words in the query. */
  opt_argv = g_strsplit(tmp_query_str, " ", MAX_OPT_ARG_C);

  /* Determine the number of arguments. */
  for (opt_argc=0; opt_argv[opt_argc] != NULL; opt_argc++);

  while ((c = my_getopt(opt_argc, opt_argv, "aegi:klrmq:s:t:v:xFKLMRST:V:", &my_optind, &my_optarg)) != EOF) {
    switch (c) {
      case 'a':
        /* Remove any user specified sources from the sources list. */
        while ((first_source = g_list_first(qe->sources_list)) != NULL) {
          qe->sources_list = g_list_remove(qe->sources_list, first_source->data);
        }
#if 0 
        /* Add all the config sources to the sources list. */
        for (i=0; CO_get_source(i) != NULL; i++) {
          qe->sources_list = g_list_append(qe->sources_list, (void *)CO_get_source_database(i));
        }
#else
	qe->sources_list = g_list_append(qe->sources_list,  CO_get_database() );
#endif

      break;

      case 'e':
        query_command->e=1;
      break;

      case 'g':
        query_command->g=1;
      break;

      case 'i':
        if (my_optarg != NULL) {
          inv_attrs_str = my_optarg;
          /* Now a really stupid hard-coded hack to support "pn" being a synonym for "ac,tc,zc,ah" */
          /* I particularly object to this because it references attributes that should only be 
             defined in XML - but I don't see a simplier more robust way of doing this hack.
             :-( - ottrey 8/12/99 */
          if (strcmp(inv_attrs_str, "pn") == 0) {
	    dieif( wr_malloc((void **)&inv_attrs_str, 12) != UT_OK);  
            strcpy(inv_attrs_str, "ac,tc,zc,ah");
          }
          else if (strcmp(inv_attrs_str, "ro") == 0) {
	    dieif( wr_malloc((void **)&inv_attrs_str, 12) != UT_OK); 
            strcpy(inv_attrs_str, "ac,tc,zc,ah");
          }
          while (*inv_attrs_str) {
            index = getsubopt(&inv_attrs_str, DF_get_attribute_aliases(), &value);
            if (index == -1) {
              attr = -1;
              strcpy(str_buf, "");
              sprintf(str_buf, "Unknown attribute encountered.\n"); 
              SK_cd_puts(&(qe->condat), str_buf);
              errflg++;
            }
            else {
              mask_t inv_attr_mask = MA_new(INV_ATTR_MASK);
              attr = DF_get_attribute_index(index);
              if ( MA_isset(inv_attr_mask, attr) == 1 ) {
                /* Add the attr to the bitmap. */
                MA_set(&(query_command->inv_attrs_bitmap), attr, 1);
              }
              else {
                strcpy(str_buf, "");
                sprintf(str_buf, "\"%s\" does not belong to inv_attr.\n", (DF_get_attribute_aliases())[index]); 
                SK_cd_puts(&(qe->condat), str_buf);
                errflg++;
              }
            } 
          } /* while () */
        } /* if () */
      break;

      case 'k':
        /* This is a tricky XOR operation....  ;-)
           State transition for k_flag:
           0 -> 0 then k flag = 0, connected = 0
           0 -> 1 then k flag = 1, connected = 1
           1 -> 0 then k flag = 1, connected = 1
           1 -> 1 then k flag = 0, connected = 0
        */
        qe->k ^= 1;
      break;

      case 'r':
        query_command->recursive=0;       /* Unset recursion */
      break;

      case 'l':
        query_command->l=1;
      break;

      case 'm':
        query_command->m=1;
      break;

      case 'q':
        if (my_optarg != NULL) {
          index = getsubopt(&my_optarg, DF_get_server_queries(), &value);
          if (index == -1) {
            errflg++;
          }
          else {
            query_command->q = index;
          } 
        } /* if () */
      break;

      case 's':
        if (my_optarg != NULL) {
          sources_str = my_optarg;
          /* Remove any sources from the sources list. */
          while ((first_source = g_list_first(qe->sources_list)) != NULL) {
            qe->sources_list = g_list_remove(qe->sources_list, first_source->data);
          }
          while (*sources_str) {
            index = getsubopt(&sources_str, CO_get_sources(), &value);
            if (index == -1) {
              strcpy(str_buf, "");
              sprintf(str_buf, "Unknown source encountered.\nNot one of: %s\n", CO_sources_to_string()); 
              SK_cd_puts(&(qe->condat), str_buf);

              /* Put the default source back in. */
              SK_cd_puts(&(qe->condat), "Reverting to default source - ");
              SK_cd_puts(&(qe->condat), CO_get_database());
              SK_cd_puts(&(qe->condat), "\n");
              qe->sources_list = g_list_append(qe->sources_list, (void *)CO_get_database());
              errflg++;
            }
            else {
              qe->sources_list = g_list_append(qe->sources_list, (void *)CO_get_source_database(index));
            } 
          } /* while () */
        } /* if () */
      break;

      case 't':
        if (my_optarg != NULL) {
          object_types_str = my_optarg;
          while (*object_types_str) {
            index = getsubopt(&object_types_str, DF_get_class_aliases(), &value);
            if (index == -1) {
              strcpy(str_buf, "");
              sprintf(str_buf, "Unknown object encountered.\n"); 
              SK_cd_puts(&(qe->condat), str_buf);
              errflg++;
            }
            else {
              type = DF_get_class_index(index);
              query_command->t=type;
            }
          }
        }
      break;

      case 'v':
        if (my_optarg != NULL) {
          object_types_str = my_optarg;
          if (*object_types_str) {
            index = getsubopt(&object_types_str, DF_get_class_aliases(), &value);
            if (index == -1) {
              strcpy(str_buf, "");
              sprintf(str_buf, "Unknown object encountered.\n"); 
              SK_cd_puts(&(qe->condat), str_buf);
              errflg++;
            }
            else {
              type = DF_get_class_index(index);
              query_command->v=type;
            }
          }
        }
      break;

      case 'x':
        query_command->x=1;
      break;

      case 'F':
        query_command->fast=1;
	query_command->recursive=0; /* implies no recursion */
      break;

      case 'K':
        query_command->filtered=1;
	query_command->recursive=0; /* implies no recursion */
      break;

      case 'L':
        query_command->L=1;
      break;

      case 'M':
        query_command->M=1;
      break;

      case 'R':
        query_command->R=1;
      break;

      case 'S':
        query_command->S=1;
      break;

      case 'T':
        if (my_optarg != NULL) {
          object_types_str = my_optarg;
          while (*object_types_str) {
            index = getsubopt(&object_types_str, DF_get_class_aliases(), &value);
            if (index == -1) {
              strcpy(str_buf, "");
              sprintf(str_buf, "Unknown class encountered.\n"); 
              SK_cd_puts(&(qe->condat), str_buf);
              errflg++;
            }
            else {
              type = DF_get_class_index(index);
              /* Add the type to the bitmap. */
              MA_set(&(query_command->object_type_bitmap), type, 1);
            }
          }
        }
      break;

      case 'V':
        if (qe->version != NULL) {
          /* free up the old client info */
          wr_free(qe->version);
        }
        
        {
          char *token, *cursor = my_optarg;
          while( (token = strsep( &cursor, "," )) != NULL ) {
            if(IP_addr_e2b( & (qe->pIP), token) 
               != IP_OK ) {
              /* means it was not an IP -> it was a version */
              dieif( wr_malloc( (void **)&(qe->version), 
				strlen(token)+1) != UT_OK);  
              strcpy(qe->version, token);
            }
          }
        }
      break;

      case '?':
        errflg++;
      break;

      default:
        errflg++;
    }
  }

  /* XXX Report the error.  This could be improved. */
  /*  if (opt_argv[my_optind] != NULL) { */

  /* this needed improvement, MB */
  if( my_optind < opt_argc  &&  opt_argv[my_optind] != NULL) {
    if ( (errflg) || (strncmp(opt_argv[my_optind], "-", 1) == 0) ) {
      return -1;
    }
  }
  else {
    if (errflg) {
      return -1;
    }
  }
    

  /* Work out the length of space needed */
  key_length = 0;
  for (i=my_optind ; i < opt_argc; i++) {
    /* length for the string + 1 for the '\0'+ 1 for the ' ' 
       [MB removed: + 1 for good luck.] */
    if (opt_argv[i] != NULL) {
      key_length += strlen(opt_argv[i])+2;
    }
  }

  dieif( wr_calloc((void **)&(query_command->keys), 1, key_length+1) != UT_OK);  
  strcpy(query_command->keys, "");
  if (errflg == 0) {
    for (i=my_optind; i < opt_argc; i++) {
      strcat(query_command->keys, opt_argv[i]);
      if ( (i + 1) < opt_argc) {
        strcat(query_command->keys, " ");
      }
    }
  } /* XXX - Be careful about where this brace goes. */

  /* Now convert the key to uppercase. */
  for (i=0; i <= key_length; i++) {
    query_command->keys[i] = toupper(query_command->keys[i]);
  }

  /* Now make the keytypes_bitmap. */
  query_command->keytypes_bitmap = WK_new(query_command->keys);

  if ( CO_get_comnd_logging() == 1 ) {
    log_command(tmp_query_str, query_command);
  }

  /* Now we don't need this anymore */
  wr_free(tmp_query_str);

  return 0;

} /* QC_fill() */

/* QC_environ_new() */
/*++++++++++++++++++++++++++++++++++++++
  Create a new query environment.

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

  ++++++++++++++++++++++++++++++++++++++*/
Query_environ *QC_environ_new(char *ip, unsigned sock) {
  Query_environ *qe;


  dieif( wr_calloc((void **)&qe, 1, sizeof(Query_environ)+1 ) != UT_OK);  
  qe->condat.ip = ip;
  qe->condat.sock = sock;

  /* The source is initialized to be the one defined in the config by default. */
  qe->sources_list = g_list_append(qe->sources_list, (void *)CO_get_database());

  return qe;

} /* QC_environ_new() */

Query_command *QC_create(char *input, Query_environ *qe)
{
  Query_command *qc;


  dieif( wr_calloc((void **)&qc, 1, sizeof(Query_command)+1) != UT_OK);
  
  if ( strlen(input) == 0) {
    /* An empty query (Ie return) was sent */
    qc->query_type = QC_EMPTY;
  } 
  else {        /* else <==> input_length > 0 ) */
    /* parse query */
    
    if( QC_fill(input, qc, qe) < 0 ) {
      qc->query_type = QC_ERROR;
    }
    else {
      /* Update the query environment */
      /* qe = QC_environ_update(qc, qe); */
      
      /* Only do a query if there are keys. */
      if (qc->keys == NULL || strlen(qc->keys) == 0 ) {
	if( strlen(qc->keys) == 0 
	    && ( qc->q != -1 || qc->t != -1 || qc->v != -1 ) ) {
	  qc->query_type = QC_TEMPLATE;
	}
	else {
	  qc->query_type = QC_NOKEY;
	}
      }
      else {
	if ( strcmp(qc->keys, "HELP") == 0 ) {
	  qc->query_type = QC_HELP;
	}
	/* So, a real query */
	else if( qc->filtered ) {
	  qc->query_type = QC_FILTERED;
	}
	else {
	  qc->query_type = QC_REAL;
	}
      }
    }
  }
  return qc;
}


char *QC_get_qrytype(qc_qtype_t qrytype) {
  dieif(qrytype >= QC_TYPE_MAX);

  return qrytype_str[qrytype];
}
