/***************************************
  $Revision: 1.61 $

  Protocol whois module (pw).  Whois protocol.

  Status: NOT REVUED, TESTED

  ******************/ /******************
  Filename            : protocol_whois.c
  Authors             : ottrey@ripe.net - framework and draft implementation
                        marek@ripe.net - rewritten and extended.
  OSs Tested          : Solaris 2.6
  ******************/ /******************
  Copyright (c) 1999,2000,2001,2002               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 "rip.h"

#include <stdio.h>
#include <glib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>

/* XXX: what is this?  - shane */
#include "NAME"

#ifndef VERSION
#define VERSION "3"
#endif

/*+ Maximum size of input that can be recieved from the client. +*/
#define MAX_INPUT_SIZE  1024

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

void
display_file        opens a file and displays its contents to the 
                    connection described in conn. structure.


sk_conn_st *condat  pointer to connection structure

char *filename      file name

  ++++++++++++++++++++++++++++++++++++++*/
static void
display_file(sk_conn_st *condat, char *filename)
{
  FILE *fp;
  char *buffer;
  struct stat sb;
  int bytes;
  int p;

  /* open our file */
  fp = fopen(filename, "r");
  if (fp == NULL) {
    ER_perror( FAC_PW, PW_CNTOPN, "fopen() failure \"%s\" : %s (%d)", 
	       filename, strerror(errno), errno);
    return;
  }

  /* get the size of the file */
  if (fstat(fileno(fp), &sb) != 0) {
    ER_perror( FAC_PW, PW_CNTOPN, "fstat() failure \"%s\" : %s (%d)",
               filename, strerror(errno), errno);
    fclose(fp);
    return;
  }

  /* allocate a buffer for the file */
  buffer = UT_malloc(sb.st_size+1);

  /* read the file contents */
  bytes = fread(buffer, 1, sb.st_size, fp);
  fclose(fp);

  /* can't read more bytes that we asked for */
  dieif(bytes > sb.st_size);


  /* remove any newlines (actually any whitespace) at the end of the file */
  /* we do this because we can have ONLY ONE newline at the end of the */
  /* output - any more violates our OBJECT, "\n", OBJECT, "\n" format */
  p = bytes-1;
  while ((p>=0) && isspace((int)buffer[p])) {
      p--;
  }

  /* NUL-terminate our string */
  buffer[p+1] = '\0';

  /* output our resulting buffer */
  SK_cd_puts(condat, buffer);

  /* and enough blank lines */
  SK_cd_puts(condat, "\n\n");

  /* free the allocated memory */
  UT_free(buffer);
}/* display_file */


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

  static void 
  pw_log_query              logs the query to a file after it has finished.
                            Takes many parameters to have access to as much
			    information as possible, including the original 
			    query, accounting, response time, status of the 
			    client connection, etc.


  Query_environ *qe       query environment    

  Query_command *qc       query command structure 

  acc_st *copy_credit     numbers of objects returned / referrals made 
                          during this query
                          (calculated as original credit assigned before
			  the query minus what's left after the query).

  ut_timer_t begintime    time the processing began  

  ut_timer_t endtime      time the processing finished

  char *hostaddress       text address of the real IP

  char *input             original query (trailing whitespaces chopped off)

  ++++++++++++++++++++++++++++++++++++++*/
static 
void pw_log_query( Query_environ *qe, 
		   Query_command *qc, 
		   acc_st *copy_credit,   
		   ut_timer_t begintime,   
		   ut_timer_t endtime, 
		   char *hostaddress, 
		   char *input) 
{
  char *qrystat = AC_credit_to_string(copy_credit);
  float elapsed;	  
  char *qrytypestr =
    qc->query_type == QC_REAL ? "" : QC_get_qrytype(qc->query_type);
  
  
  elapsed = UT_timediff( &begintime, &endtime);
  
  /* log the connection/query/#results/time/denial to file */ 
  ER_inf_va(FAC_PW, ASP_PW_I_QRYLOG,
	    "<%s> %s%s %.2fs [%s] --  %s",
	    qrystat, 
	    qe->condat.rtc ? "INT " : "",
	    qrytypestr,
	    elapsed, hostaddress, input
	    );
  UT_free(qrystat);
} /* pw_log_query */

     


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

  void 
  PW_process_qc          processes the query commands determined in QC,
                         This is where all the real action of the query
			 part is invoked.

  Query_environ *qe      query environment

  Query_command *qc      query command structure 

  acc_st *acc_credit     credit assigned to this IP

  acl_st *acl_eip        current acl record applicable to this IP

  ++++++++++++++++++++++++++++++++++++++*/
void PW_process_qc(Query_environ *qe, 
		   Query_command *qc,
		   acc_st *acc_credit, 
		   acl_st *acl_eip )
{
  GList *qitem;
  Query_instructions *qis=NULL;
  er_ret_t err;

  switch( qc->query_type ) {
  case QC_SYNERR:
    SK_cd_puts(&(qe->condat), "\n");
    SK_cd_puts(&(qe->condat), USAGE);
    break;
  case QC_PARERR:
    /* parameter error. relevant error message is already printed */ 
    /* we still need an extra newline after this message though */
    SK_cd_puts(&(qe->condat), "\n");
    
    /* force disconnection on error */
    qe->k = 0;
    break;
  case QC_NOKEY:
    /* no key (this is OK for some operational stuff, like -k) */
    break;       
  case QC_EMPTY:
    /* The user didn't specify a key, so
       - print moron banner
       - force disconnection of the user. */
    SK_cd_puts(&(qe->condat), "\n");
    {
      char *rep = ca_get_pw_err_nokey ;
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
    }
    /* 
      EXTRA NEWLINE HERE, because we set condat.rtc, which prevents 
      further output to user, and we need to end our output with multiple
      blank lines.
     */
    SK_cd_puts(&(qe->condat), "\n\n");   
    qe->condat.rtc = SK_NOTEXT;
    break;
  case QC_HELP:
    SK_cd_puts(&(qe->condat), "\n");
    {
      char *rep = ca_get_pw_help_file ;
      display_file( &(qe->condat), rep);
      UT_free(rep);
    }
    break;
  case QC_TEMPLATE:
    SK_cd_puts(&(qe->condat), "\n");
    switch(qc->q) {
    case QC_Q_SOURCES:
      /* print source & mirroring info */
      {
	GString *srcs = PM_get_nrtm_sources( & qe->condat.rIP, NULL);
	SK_cd_puts(&(qe->condat), srcs->str);
	g_string_free (srcs, TRUE);
      }
      break;
    case QC_Q_VERSION:
      SK_cd_puts(&(qe->condat), "% RIP version " VERSION "\n\n"); 
      break;
    default: 
      /* EMPTY */;
    } /* -q */
    
    if (qc->t >= 0) {
      SK_cd_puts(&(qe->condat), DF_get_class_template(qc->t)); 
      SK_cd_puts(&(qe->condat), "\n"); 
    }
    if (qc->v >= 0) {
      SK_cd_puts(&(qe->condat), DF_get_class_template_v(qc->v)); 
      /* no need for newline here, because it's all broken for any */
      /* automated processor at this point anyway */
    }
    break;
    
  case QC_FILTERED:
    {
      char *rep = ca_get_pw_k_filter ;
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
    }
    /* FALLTROUGH */
  case QC_REAL:
    {
      char *rep = ca_get_pw_resp_header;
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
      SK_cd_puts(&(qe->condat), "\n");
    }

#if 1   

    qis = QI_new(qc,qe);
 
    /* go through all sources, 
       stop if connection broken - further action is meaningless */
    for( qitem = g_list_first(qe->sources_list);
	 qitem != NULL && qe->condat.rtc == 0;
	 qitem = g_list_next(qitem)) {


      /* QI will decrement the credit counters */
      err = QI_execute(qitem->data, qis, qe, acc_credit, acl_eip );      
      if( !NOERR(err) ) { 
	if( err == QI_CANTDB ) {
	  SK_cd_puts(&(qe->condat), "% WARNING: Failed to make connection to ");
	  SK_cd_puts(&(qe->condat), (char *)qitem->data);
	  SK_cd_puts(&(qe->condat), " database.\n\n");
	}
	break; /* quit the loop after any error */
      }/* if error*/

    }/* for every source */

    QI_free(qis);
    
#else
    /* test mode: do not run a query, make up some accounting values */
    {
      int i, m = random() & 0x0f;
      for( i=0 ; i<m ; i++ ) {
	AC_count_object( acc_credit, acl_eip, random() & 0x01 ); 
      }
    }

#endif
    
    if( AC_credit_isdenied(acc_credit) ) {
      /* host reached the limit of returned contact information */
      char *rep = ca_get_pw_limit_reached ;
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
      SK_cd_puts(&(qe->condat), "\n");
    }
    
    break;
  default: die;
  }
} /* PW_process_qc */

/* 
   Occasionally, we need to pause queries to the Whois database.  This
   occurs, for instance, when the database is reloaded for one of the
   databases we mirror without using NRTM.

   The way this works is the caller of PW_stopqueries() gets a "write
   lock" on queries.  Each query gets a "read lock".  The lock mechanism
   that favors writers is used.

   This means that no new read locks can start once PW_stopqueries() is
   called, and that it doesn't return until all read locks have been
   released.  At this point, queries are stopped and the caller can
   proceed to munge about the database safely.

   XXX: This is not the best possible solution, because on a very slow
   query (for instance one with a very common person name), the thread
   calling PW_stopqueries() as well as ALL query threads cannot proceed
   until that thread completes.  An alternative with one lock per
   database was considered, and may be pursued in the future, but for
   now it is not a big problem, since operation occurs normally, just
   possibly with a delay in response for some users.

   PW_startqueries() releases the write lock, and queries proceed
   normally.
 */

/* pause queries using a thread lock that favors writers */
static rw_lock_t queries_lock;

/* PW_stopqueries() */
void
PW_stopqueries()
{
    TH_acquire_write_lockw(&queries_lock);
}

/* PW_startqueries() */
void
PW_startqueries()
{
    TH_release_write_lockw(&queries_lock);
}

/* PW_record_query_start() */
void 
PW_record_query_start()
{
    TH_acquire_read_lockw(&queries_lock);
}

/* PW_record_query_end() */
void 
PW_record_query_end()
{
    TH_release_read_lockw(&queries_lock);
}



/*++++++++++++++++++++++++++++++++++++++
  
  void 
  PW_interact             Main loop for interaction with a single client.
                          The function sets up the accounting for the client,
			  invokes parsing, execution, logging and accounting
			  of the query.

  int sock                Socket that client is connected to.

  ++++++++++++++++++++++++++++++++++++++*/
void PW_interact(int sock) {
  char input[MAX_INPUT_SIZE];
  int read_result;
  char *hostaddress=NULL;
  acl_st acl_rip,   acl_eip;
  acc_st acc_credit, copy_credit;
  Query_environ *qe=NULL;
  Query_command *qc=NULL;
  ut_timer_t begintime, endtime;

  /* Get the IP of the client */
  hostaddress = SK_getpeername(sock);	  
  ER_dbg_va(FAC_PW, ASP_PW_CONN, "connection from %s", hostaddress);
  
  /* Initialize the query environment. */
  qe = QC_environ_new(hostaddress, sock);
  
  /* init the connection structure, set timeout for reading the query */
  SK_cd_make( &(qe->condat), sock, (unsigned) ca_get_keepopen); 

  TA_setcondat(&(qe->condat));

  /* see if we should be talking at all */
  /* check the acl using the realIP, get a copy applicable to this IP */
  AC_check_acl( &(qe->condat.rIP), NULL, &acl_rip);
  
  do {
    int unauth_pass=0;

    TA_setactivity("waiting for query");
    /* Read input */
    read_result = SK_cd_gets(&(qe->condat), input, MAX_INPUT_SIZE);
    /* trash trailing whitespaces(including \n) */
    ut_string_chop(input);
      
    TA_setactivity(input);
    TA_increment();

    UT_timeget( &begintime );
    
    qc = QC_create(input, qe);

    {
      /* print the greeting text before the query */
      char *rep = ca_get_pw_banner ; 
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
    }

    /* ADDRESS PASSING: check if -V option has passed IP in it */
    if( ! STRUCT_EQUAL(qe->pIP,IP_ADDR_UNSPEC)) {
      if(acl_rip.trustpass) {     
	acc_st pass_acc;

	/* accounting */
	memset(&pass_acc, 0, sizeof(acc_st));
	pass_acc.addrpasses=1;
	AC_commit( &qe->condat.rIP, &pass_acc, &acl_rip);

	/* set eIP to this IP */
	qe->condat.eIP = qe->pIP;                 
      }
      else {
	/* XXX shall we deny such user ? Now we can... */
	ER_inf_va(FAC_PW, ASP_PW_I_PASSUN, 
		  "unauthorised address passing by %s", hostaddress);
	unauth_pass = 1; /* keep in mind ... */
      }
    } /* if an address was passed */
    
    /* start setting counters in the connection acc from here on 
       decrement the credit counter (needed to prevent QI_execute from
       returning too many results */
    
    /* check ACL. Get the proper acl record. Calculate credit */
    AC_check_acl( &(qe->condat.eIP), &acc_credit, &acl_eip);
    /* save the original credit, later check how much was used */
    copy_credit = acc_credit;

    copy_credit.connections ++;

    /* printing notices */
    if( unauth_pass && ! acl_rip.deny ) {
      /* host not authorised to pass addresses with -V */
      char *rep = ca_get_pw_acl_addrpass ;
      SK_cd_puts(&(qe->condat), "\n");
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
      SK_cd_puts(&(qe->condat), "\n");
    }
    if( acl_eip.deny || acl_rip.deny ) {
      /* access from host has been permanently denied */
      char *rep = ca_get_pw_acl_permdeny ;
      SK_cd_puts(&(qe->condat), "\n");
      SK_cd_puts(&(qe->condat), rep);
      UT_free(rep);
      SK_cd_puts(&(qe->condat), "\n");
    }
    
    if( acl_eip.deny || acl_rip.deny || unauth_pass ) {
      copy_credit.denials ++; 
    }
    else {
      /************ ACTUAL PROCESSING IS HERE ***********/
      PW_record_query_start();
      PW_process_qc(qe, qc, &acc_credit, &acl_eip);
      PW_record_query_end();

      if( qc->query_type == QC_REAL ) {
	copy_credit.queries ++;
      }
    }/* if denied ... else */
      
    /* calc. the credit used, result  into copy_credit 
       This step MUST NOT be forgotten. It must complement
       the initial calculation of a credit, otherwise accounting
       will go bgzzzzzt.
    */
    AC_acc_addup(&copy_credit, &acc_credit, ACC_MINUS);
    
    /* now we can check how many results there were, etc. */

    /* can say 'nothing found' only if:
       - the query did not just cause denial
       - was a 'real' query
       - nothing was returned
    */

    if(  ! AC_credit_isdenied(&copy_credit)  
	 && (qc->query_type == QC_REAL || qc->query_type == QC_FILTERED)
	 && copy_credit.private_objects + copy_credit.public_objects
	 + copy_credit.referrals == 0 ) {
      
      /* now: if the rtc flag is zero, the query ran to completion */
      if( qe->condat.rtc == 0 ) {
	char *rep = ca_get_pw_notfound ;
	SK_cd_puts(&(qe->condat), rep);
	UT_free(rep);
	SK_cd_puts(&(qe->condat), "\n");
      }
      else {
	/* something happened. Hope for working socket and display message
	   (won't hurt even if socket not operable) 
	*/
	char *rep = ca_get_pw_connclosed ;
	SK_cd_puts(&(qe->condat), rep);
	UT_free(rep);
      }
    }
	

    UT_timeget(&endtime);
    /* query logging */
    pw_log_query(qe, qc, &copy_credit, begintime, endtime, 
		 hostaddress, input);
    
    /* Commit the credit. This will deny if bonus limit hit 
       and clear the copy */ 
    AC_commit(&(qe->condat.eIP), &copy_credit, &acl_eip); 
    
    /* end-of-result -> ONE empty line */
    SK_cd_puts(&(qe->condat), "\n");
      
    QC_free(qc);      
  } /* do */
  while( qe->k && qe->condat.rtc == 0 
	 && AC_credit_isdenied( &copy_credit ) == 0
	 && CO_get_whois_suspended() == 0);

  /* Free the hostaddress */
  UT_free(hostaddress);
  /* Free the connection struct's dynamic data */
  SK_cd_free(&(qe->condat));
  /* Free the query_environ */
  QC_environ_free(qe);

} /* PW_interact() */


/* *MUST* be called before any other PW functions */
void
PW_init()
{
    TH_init_read_write_lockw(&queries_lock);
}

