/***************************************
  $Revision: 1.12 $

  Error reporting (er) er_macro.c - simple macro processor

  Status: NOT REVUED, PARTLY TESTED

  Design and implementation by: Marek Bukowy

  ******************/ /******************
  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 <string.h>
#include <glib.h>


/*++++++++++++++++++++++++++++++++++++++
  
  processes a macro call, i.e. executes one of the predefined macros
  selected by the 0th word of the array, using other words as
  arguments to that macro. Uses the er_macro_array[] to find the
  macro definition.  Allocates the result string and stores the
  pointer to it in **output.
  
  int ER_process_split    returns 0 on success, non-0 on failure.

  int argc                number of words in the word array
  
  char **argv             word array (pointers to strings)
  
  char **output           storage for the result pointer (to allocated text)
  ++++++++++++++++++++++++++++++++++++++*/
int
ER_process_split(int argc, char **argv, char **output)
{
  unsigned char *ch;
  char *pattern;
  GString *result = g_string_new("");
  int retval = 0;

  TH_acquire_write_lock( &er_paths_lock );

  if( /* if called without the macro name */
     argc == 0  
     /* or macro can not be found */
     || (pattern = g_hash_table_lookup(er_macro_hash, argv[0])) == NULL ) {

    retval = -1;
  }
  else {
    /* copy the macro definition by portions, substituting the $([0-9]) 
       entries with arguments. Error if not enough arguments.
    */
    do {
      
      if( (ch = (unsigned char *) strstr( pattern, "$(" )) == NULL ) {
	/* no more entries. copy the rest */
	g_string_append ( result, pattern );
	break;
      }
      else {
	/* pass the string between here and ch */
	while( pattern != (char *)ch ) {
	  g_string_append_c ( result, *pattern );
	  pattern++;
	}
	/* check the next 3 characters exist, break the look if not */
	if( *(ch+2) == '\0' ||  *(ch+3) == '\0') {
	  break;
	}

	/* look for the digit and ")", pass the $( through if not present */
	if( ! isdigit(*(ch+2)) || *(ch+3) != ')' ) {
	  /* not need to do anything to make it pass through */
	  ;
	}
	else {
	  /* substitute the $(?) with the appropriate argument.
	     error if not enough arguments or $(0) is used.*/
	  int a = *(ch+2) - '0';
	
	  if( argc < a || a==0) {
	    retval = -1;
	    break;
	  }
	  g_string_append( result, argv[a]);
	  /* advance the pattern pointer */
	  pattern += strlen("$(1)");
	}
      }
    } while(1);
  }

  /* copy the pointer, free the orig structure, keep the text */
    
  *output = (result->str); 
  
  g_string_free( result, FALSE );

  TH_release_write_lock( &er_paths_lock );

  return retval;
}


/*++++++++++++++++++++++++++++++++++++++
  
  Take a text line and parse it as an error specification
  line. Optionally, if the first word is a macro, run the macro using
  other words as its arguments.  This is basically a wrapper around
  ER_process_split() that splits the string into argv and calls the
  ER_parse.

  sets the errbuf to the result of ER_parse_spec.

  int ER_macro_spec      returns 0 on success, non-0 on failure.  

  char *input            input line

  char **errbuf          storage for the result pointer (to allocated text)
  ++++++++++++++++++++++++++++++++++++++*/
int
ER_macro_spec(char *input, char **errbuf)
{
  char *copy = ut_string_compress(input);
  char **argv = g_strsplit(copy, " ", 0);
  int argc = 0, ret;
  char *fullspec;

  while( argv[argc] != NULL ) {
    argc++;
  }
  

  if( ER_process_split(argc, argv, &fullspec) != 0 ) {
    /* macro unknown. That's OK, just parse that text now */

    fullspec = UT_strdup(input);
  }

  ret = ER_parse_spec(fullspec, errbuf);

  UT_free(fullspec);
  UT_free(copy);
  g_strfreev(argv);

  return ret;
  
}


/*++++++++++++++++++++++++++++++++++++++
  (Re)Define a macro.
  
  char *name     macro name
  
  char *def      macro contents
  ++++++++++++++++++++++++++++++++++++++*/
void
ER_make_macro(char *name, char *def)
{
  char *cp_name = UT_strdup(name);
  char *cp_def  = UT_strdup(def);

  void *oldkey, *oldval;

  TH_acquire_write_lock( &er_paths_lock );

  /* cleanup on redefinition */
  if( g_hash_table_lookup_extended(er_macro_hash, name, 
				   &oldkey, &oldval) == TRUE ) {
    g_hash_table_remove(er_macro_hash, name);
    UT_free(oldkey);
    UT_free(oldval);
  }
  
  g_hash_table_insert(er_macro_hash, cp_name, cp_def);

  TH_release_write_lock( &er_paths_lock );
}


/*++++++++++++++++++++++++++++++++++++++
  
  predefine some macros useful for the whois_rip server.
  XXX - this should not be here, it should be done via the CA module!

  ++++++++++++++++++++++++++++++++++++++*/
void
ER_macro_predef(void)
{

#define DBUPDLOG_FORMAT "  FORMAT SEVCHAR|FACSYMB|TEXTLONG|DATETIME|PIDFULL|PROGNAME|MNEMONIC  "
#define RIPLOG_FORMAT   "  FORMAT SEVCHAR|FACSYMB|TEXTLONG|DATETIME|PIDFULL|PROGNAME|THR_ID|MNEMONIC  "

  /* catch-all for dbupdate */
  ER_make_macro("DBUPERR", "CREATE dbuperr {"
		DBUPDLOG_FORMAT "NAME '$(1)' DATE}"
		" ( FAC MM|UP SEV W- )");

  /* catch-all for rip */
  ER_make_macro("ALLRIPERR", "CREATE allriperr { " 
		RIPLOG_FORMAT "NAME '$(1)' DATE}" 
		" (FAC ALL SEV W- )");

  /* selected: errors in ripupdate */
  ER_make_macro("RIPUPERR", "CREATE ripuperr {" 
		RIPLOG_FORMAT "NAME '$(1)' DATE}" 
		" (FAC UD SEV W- )");

  /* querylog: logs all rip queries */
  ER_make_macro("QRYLOG", "CREATE qrylog {" 
		RIPLOG_FORMAT "NAME '$(1)' DATE}" 
		" (FAC PW ASP PW_I_QRYLOG SEV I )");

  /* audit: any security related messages from RIP */
  ER_make_macro("RIPAUDIT", "CREATE ripaudit {"
		RIPLOG_FORMAT "NAME '$(1)' DATE}"
		"( FAC PW ASP PW_I_PASSUN SEV i )" 
		" (  FAC AC ASP AC_I_PERMBAN SEV I )");

  /* ripupdlog: logs all update transactions */
  ER_make_macro("RIPUPDLOG", "CREATE 'ripupdlog_$(2)' {" 
		RIPLOG_FORMAT "NAME '$(1)_$(2)' DATE}" 
		" ( FAC UD ASP 0xffffffff SEV I THR self)");

  /* ripmirlog */
  ER_make_macro("RIPMIRLOG", "CREATE ripmirlog {"  
		RIPLOG_FORMAT "NAME '$(1)' DATE }"
		"( FAC PM ASP 0xffffffff SEV I )");

  /* server log: all administration by SV (startup, shutdown, etc) and errors */
  ER_make_macro("RIPSVRLOG", "CREATE ripsvrlog {" 
		RIPLOG_FORMAT "NAME '$(1)' DATE}" 
		" ( FAC SV ASP 0xffffffff SEV I-F )");											      
  /* dbase log: all errors of SQ */
  ER_make_macro("SQLOG", " CREATE sqlog {" 
		RIPLOG_FORMAT "NAME '$(1)' DATE}" 
		" ( FAC SQ SEV W- )");
  
}


/*++++++++++++++++++++++++++++++++++++++
  
  Prints the arguments (key and value of a hash) to the given
  connection (used for listing the defined macros)

  void * key       hash key

  void * value     hash value

  void *condat     connection data structure
  ++++++++++++++++++++++++++++++++++++++*/
static
void er_macro_list_hook (void* key, void * value, void *condat)
{
  SK_cd_printf(condat, "%s: %s\n", (char *) key, (char *) value);
}
     


/*++++++++++++++++++++++++++++++++++++++
  
  Lists all currently defined macros to the given connection.

  sk_conn_st *condat  connection data structure
  ++++++++++++++++++++++++++++++++++++++*/
void 
ER_macro_list(sk_conn_st *condat)
{
  TH_acquire_read_lock( &er_paths_lock );
  g_hash_table_foreach(er_macro_hash, er_macro_list_hook, condat );
  TH_release_read_lock( &er_paths_lock );
}



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

 Defines the macros with the definitions from the config file,
 overriding any currently defined ones if the same name is used.
 
 ++++++++++++++++++++++++++++++++++++++*/
void 
ER_proc_ca_macro(void)
{
  char *alldef = ca_get_er_macro ;
  char *this_line = alldef;
  char *defname, *defbody, *end_line;
  
  /* alldef is a copy of the configured value. so we can modify it
     if it helps us to do it line by line */
  
  /* ER_MACRO may not be present in the configuration, in which case 
     ca_get_er_macro returns NULL */

  if( alldef != NULL ) {
    
    while( *this_line != '\0' ) {
      /* separate the line */
      end_line = strchr(this_line, '\n');
      *end_line = '\0';
      
      /* advance to non-whitespace */
      while( isspace(* (unsigned char*) this_line) ) {
	this_line++;
      }
      
      /* find the name and body of the definition */
      defname = strsep(&this_line, " \t");
      defbody = this_line;
      
      /* fire */
      dieif( defname == NULL || defbody == NULL );
      ER_make_macro( defname, defbody );
      
      this_line = end_line + 1;
    }
    
    UT_free(alldef);
  } 
  
}


/*++++++++++++++++++++++++++++++++++++++
  
  Processes the error definitions from the config file. The
  definitions can be specified with the use of a macro or without.
  
  ++++++++++++++++++++++++++++++++++++++*/
void 
ER_proc_ca_err(void)
{
  char *alldef = ca_get_er_def ;
  char *this_line = alldef;
  char *end_line;
  char *erret = NULL;
  int res;
 
    /* alldef is a copy of the configured value. so we can modify it
       if it helps us to do it line by line */

  /* ER_DEF may not be present in the configuration, in which case 
     ca_get_er_def returns NULL */
  if( alldef != NULL ) {
    
    while( *this_line != '\0' ) {
      /* separate the line */
      end_line = strchr(this_line, '\n');
      *end_line = '\0';
      
      /* fire */      
      if( (res = ER_macro_spec(this_line, &erret)) != 0 ) {
	fputs(erret, stderr);
	die;
      }
      
      UT_free(erret); 
      
      this_line = end_line + 1;
    }
    
    UT_free(alldef);
  }
}
