/***************************************
  $Revision: 1.44 $

  Protocol whois module (pw).  Whois protocol.

  Status: NOT REVUED, TESTED

  ******************/ /******************
  Filename            : protocol_whois.c
  Authors             : ottrey@ripe.net
                        marek@ripe.net
  OSs Tested          : Solaris
  ******************/ /******************
  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 <glib.h>

#include "NAME"

#include "defs.h"
#include "protocol_whois.h"
#include "mysql_driver.h"
#include "query_command.h"
#include "query_instructions.h"
#include "constants.h"

#include "access_control.h"
#include "sk.h"
#include "stubs.h"

#include "ca_configFns.h"
#include "ca_dictSyms.h"
#include "ca_macros.h"
#include "ca_srcAttribs.h"

#include "protocol_mirror.h"

#include "ta.h"
#include "timediff.h"

/* open a file and display its contents to the connection (condat) */
static void
display_file(sk_conn_st *condat, char *filename)
{
  FILE *fp;
#define READBUFSIZE 148
  char buffer[READBUFSIZE+1];
  int bytes;

  if( (fp=fopen( filename, "r" )) == NULL ) {
    ER_perror( FAC_PW, PW_CNTOPN, "%s : %s (%d)", 
	       filename, strerror(errno), errno);
  }
  else {
    while( (bytes=fread(buffer, 1, READBUFSIZE, fp)) > 0 ) {
      buffer[bytes] = 0;
      SK_cd_puts(condat, buffer);
    }
    fclose(fp);
  }
}

void
strchop(char *input)
{
  unsigned char *co = (unsigned char *)strchr(input, '\0');

  while( co != (unsigned char *)input && (isspace(*co) || *co == '\0') ) {
    *co = '\0';
    co--;
  }
}


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
	    );
  wr_free(qrystat);
}

     


er_ret_t 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), USAGE);
    /* FALLTHROUGH */
  case QC_PARERR:
    /* parameter error. relevant error message is already printed */ 
    
    /* 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. */
    {
      char *rep = ca_get_pw_err_nokey ;
      SK_cd_puts(&(qe->condat), rep);
      wr_free(rep);
    }
    qe->condat.rtc = SK_NOTEXT;
    break;
  case QC_HELP:
    {
      char *rep = ca_get_pw_help_file ;
      display_file( &(qe->condat), rep);
      wr_free(rep);
    }
    break;
  case QC_TEMPLATE:
    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)); 
    }
    if (qc->v >= 0) {
      SK_cd_puts(&(qe->condat), DF_get_class_template_v(qc->v)); 
    }
    break;
    
  case QC_FILTERED:
    {
      char *rep = ca_get_pw_k_filter ;
      SK_cd_puts(&(qe->condat), rep);
      wr_free(rep);
    }
    /* FALLTROUGH */
  case QC_REAL:
    {
      char *rep = ca_get_pw_resp_header;
      SK_cd_puts(&(qe->condat), rep);
      wr_free(rep);
      SK_cd_puts(&(qe->condat), "\n");
    }

    qis = QI_new(qc,qe);
    
    /* stop as soon as further action considered 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 */
      
    }/* for every source */
    
    QI_free(qis);
    
    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);
      wr_free(rep);
    }
    
    break;
  default: die;
  }
  
  return err;
}

/* PW_interact() */
/*++++++++++++++++++++++++++++++++++++++
  Interact with the client.

  int sock Socket that client is connected to.

  More:
  +html+ <PRE>
  Authors:
        ottrey (original CP/M version)
	marek

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

  ++++++++++++++++++++++++++++++++++++++*/
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 to zeros */
  memset( &(qe->condat), 0, sizeof(sk_conn_st));
  TA_setcondat(&(qe->condat));

  /* set the connection data: both rIP and eIP to real IP */
  qe->condat.sock = sock;
  qe->condat.ip = hostaddress;
  SK_getpeerip(sock, &(qe->condat.rIP));
  qe->condat.eIP = qe->condat.rIP;

  qe->condat.rd_timeout.tv_sec = ca_get_keepopen; /* read timeout */

  /* 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) */
    strchop(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);
      wr_free(rep);
      SK_cd_puts(&(qe->condat), "\n");
    }

    /* 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), rep);
      wr_free(rep);
    }
    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), rep);
      wr_free(rep);
    }
    
    if( acl_eip.deny || acl_rip.deny || unauth_pass ) {
      copy_credit.denials ++; 
    }
    else {
      /************ ACTUAL PROCESSING IS HERE ***********/
      PW_process_qc(qe, qc, &acc_credit, &acl_eip);

      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);
	wr_free(rep);
      }
      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);
	wr_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 -> two empty lines */
    SK_cd_puts(&(qe->condat), "\n\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 */
  wr_free(hostaddress);

  /* Free the query_environ */
  QC_environ_free(qe);

} /* PW_interact() */
