/* error.c -- some rudimentary error support */

/*
 *  srouted -- silent routing daemon
 *  Copyright (C) 1995 Kevin Buhr
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef lint
static char rcsid[] = "$Id: error.c,v 1.5 1995/02/17 17:43:27 buhr Exp $";
#endif /* not lint */

#include "defs.h"
#include "table.h"
#include "error.h"

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>


enum er_form {
   ERCF_,	/* void */
   ERCF_E,	/* int errno */
   ERCF_IE,	/* int ioctl, int errno */
   ERCF_RF,	/* struct rip *rip, int length, struct tb_address *from */
   ERCF_RFE,	/* struct rip *rip, int length, struct tb_address *from, int entry */
   ERCF_RFEM,	/* struct rip *rip, int length, struct tb_address *from, int entry, int metric */
   ERCF_RFEMR,	/* struct rip *rip, int length, struct tb_address *from, int entry, int metric, int route */
   ERCF_I,	/* struct iface *iface */
   ERCF_IR,	/* struct iface *iface, struct tb_route *route */
   ERCF_ID,	/* struct iface *iface, struct tb_address *dest */
   ERCF_SS,	/* int size, int size */
   ERCF_A,	/* struct tb_address *address */
   ERCF_P,	/* int port */
   ERCF_T,	/* no args---dump routing table */
   ERCF_TTT,	/* int now, int next, int pause */
   ERCF_R,	/* int route */
   ERCF_LAST
};

#define ERCN_MASK	0xF000
#define ERCN_SHIFT	12
#define ERCN_0		0x0000
#define ERCN_1		0x1000
#define ERCN_2		0x2000
#define ERCN_3		0x3000
#define ERCN_4		0x4000

static struct er_table {
   enum er_code ert_code;
   short ert_form;
   char *ert_text;
} er_table[] = {
   ERC_NOERROR,	ERCF_,		NULL,

/* input.c */
   ERCIN_BADFROM, ERCF_,	"Received RIP from invalid address",
   ERCIN_VERS0, ERCF_RF,	"Received RIP packet with version zero",
   ERCIN_CZ,	ERCF_RF,	"Received RIP packet with bad header reserved areas",
   ERCIN_CZE,	ERCF_RFE,	"Received RIP packet with bad entry reserved areas",
   ERCIN_CMD0,	ERCF_RF,	"Received RIP packet with command code zero",
   ERCIN_UNKCMD, ERCF_RF,	"Received RIP packet with unknown command code",
   ERCIN_CMD34,	ERCF_RF,	"Received RIP packet using obsolete trace commands",
   ERCIN_CMD5,	ERCF_RF,	"Received RIP packet using Sun's reserved command code 5",
   ERCIN_UNKAF,	ERCF_RFE,	"Received RIP packet entry for unknown address family",
   ERCIN_RESPPORT, ERCF_RF,	"Received RIP response from invalid port",
   ERCIN_RESPREMOTE, ERCF_RF,	"Received RIP response from remote source",
   ERCIN_BADMETRIC, ERCF_RFE,	"Received RIP response entry with invalid metric",
   ERCIN_BADADDR, ERCF_RFE,	"Received RIP response entry with bad address",
   ERCIN_RTFULL, ERCF_,		"Route table full",
   ERCIN_RTSUPER, ERCF_A,	"Received unsubnetted route for subnetted network",

/* output.c */
   ERCOUT_SENDTO, ERCF_E,	"\"sendto\" call failed",
   ERCOUT_FULLOUT, ERCF_,	"Filled output buffer",
   ERCOUT_BADIFACE, ERCF_I,	"Couldn't broadcast to interface",

/* kernel.c */
   ERCKR_NOIPSOCK, ERCF_E,	"Failed to open internet datagram socket",
   ERCKR_IOCTL,	ERCF_IE,	"Interface configuration IOCTL failed",
   ERCKR_IFFULL, ERCF_,		"Insufficient interface configuration entries to store interface configuration",
   ERCKR_XADDRT, ERCF_E,	"Failed to add route",
   ERCKR_XDELRT, ERCF_E,	"Failed to delete route",

/* table.c */
   ERCTB_RTFULL, ERCF_,		"Route table full",
   ERCTB_RTDUP,	ERCF_IR,	"Two interfaces had same destination",
   ERCTB_INVDEST, ERCF_ID,	"Interface destination not a valid IP address",
   ERCTB_BADSUPER, ERCF_A,	"Subnetted net appeared as destination in route table",

/* driver.c */
   ERCDR_SYNTAX, ERCF_,		"Syntax: srouted [-t ...]",
   ERCDR_NOTROOT, ERCF_,	"Must be root",
   ERCDR_BIGRIP, ERCF_SS,	"Received oversized packet",
   ERCDR_SELECT, ERCF_E,	"\"select\" call failed",
   ERCDR_ADDRSIZE, ERCF_SS,	"Packet source address had bad size",
   ERCDR_SOCKET, ERCF_E,        "\"socket\" call failed",
   ERCDR_FCNTL,	ERCF_E,	        "\"fcntl(O_NONBLOCK)\" failed",
   ERCDR_SSOPT, ERCF_E,	        "\"setsockopt(SO_BROADCAST)\" failed",
   ERCDR_BIND,	ERCF_E,	        "\"bind\" call failed",
   ERCDR_RECVFROM, ERCF_E,      "\"recvfrom\" call failed",
   ERCDR_GETSERV, ERCF_,        "Failed to get \"route\" service by name",

/* notification/tracing codes start here */
/* driver.c */
   ERCDR_USINGPORT, ERCF_P | ERCN_2,    "[driver] Using \"route\" port",
   ERCDR_GETIFCONF, ERCF_  | ERCN_1,    "[driver] Getting current interface configuration",
   ERCDR_INITROUTE, ERCF_  | ERCN_1,          "[driver] Constructing initial route table entries",
   ERCDR_GETTABLE, ERCF_   | ERCN_1,    "[driver] Broadcasting for copy of whole table",
   ERCDR_DUMPROUTE, ERCF_T | ERCN_0,    "[driver] dumproute==1, dumping routing table",
   ERCDR_SCHED,	ERCF_TTT   | ERCN_4,    "[driver] \"select\" loop scheduling status",
   ERCDR_TIMEOUT, ERCF_    | ERCN_4,    "[driver] \"select\" loop timed out",
   ERCDR_GOTINPUT, ERCF_   | ERCN_4,    "[driver] \"select\" loop got input",

/* input.c */
   ERCIN_GOTINPUT, ERCF_   | ERCN_4,    "[input] Processing received packet",
   ERCIN_IGNOREREQUEST, ERCF_RF|ERCN_4, "[input] Ignoring request received on \"route\" port",
   ERCIN_GOTREQUEST, ERCF_RF|ERCN_1,    "[input] Received a request packet",
   ERCIN_REQWHOLE, ERCF_   | ERCN_2,    "[input] Responding to request for whole routing table",
   ERCIN_REQENTRY, ERCF_RFEM|ERCN_3,    "[input] Processing request entry",
   ERCIN_REQANSWER, ERCF_  | ERCN_2,    "[input] Responding to normal request",
   ERCIN_GOTRESPONSE, ERCF_RF|ERCN_2,   "[input] Received a response packet",
   ERCIN_RESENTRY, ERCF_RFEMR|ERCN_3,   "[input] Processing response entry",
   ERCIN_IGNOREHOST, ERCF_R| ERCN_4,    "[input] Ignoring host route, had net route",
   ERCIN_RESADDED, ERCF_R  | ERCN_4,    "[input] Added route from response packet",
   ERCIN_HADROUTE, ERCF_   | ERCN_4,    "[input] Comparing new with existing route",
   ERCIN_REPEATROUTE, ERCF_| ERCN_4,    "[input] New and existing are identical, updating timer",
   ERCIN_RESDELETED, ERCF_R| ERCN_4,    "[input] Deleting route based on response entry",
   ERCIN_RESIGNORED, ERCF_ | ERCN_4,    "[input] Response entry route ignored",

/* output.c */
   ERCOUT_SENDING, ERCF_RF | ERCN_2,    "[output] Sending a RIP",
   ERCOUT_UPDATE, ERCF_    | ERCN_4,    "[output] Update triggered and ignored",
   ERCOUT_SENDTABLE, ERCF_A| ERCN_1,    "[output] Sending a copy of whole routing table",
   ERCOUT_GETTABLE, ERCF_ID| ERCN_1,    "[output] Broadcasting request for whole table",

/* kernel.c */
   ERCKR_DELRT,	ERCF_R 	   | ERCN_4,    "[kernel] Deleted kernel route",
   ERCKR_ADDRT, ERCF_R 	   | ERCN_4,    "[kernel] Added kernel route",

/* table.c */
   ERCTB_ADDEDSUPER, ERCF_R| ERCN_2,    "[table] Added dummy supernet route",
   ERCTB_IFDEFAULT, ERCF_ID| ERCN_2,    "[table] Constructed default route for interface",
   ERCTB_ADDRT,	ERCF_R 	   | ERCN_1,    "[table] Added route",
   ERCTB_DELRT,	ERCF_R 	   | ERCN_1,    "[table] Deleted route",
   ERCTB_KEPTRT, ERCF_R    | ERCN_1,    "[table] Keeping dead route",
   ERCTB_KILLRT, ERCF_R    | ERCN_1,    "[table] Killing dead route",

/* timer.c */
   ERCTM_SETTO,	ERCF_R 	   | ERCN_4,    "[timer] Setting timeout timer",
   ERCTM_SETGC, ERCF_R 	   | ERCN_4,    "[timer] Setting garbage collection timer",
   ERCTM_KILL, ERCF_R 	   | ERCN_4,    "[timer] Killing timers",
   ERCTM_TIMEOUT, ERCF_R   | ERCN_2,    "[timer] Timeout timer expired",
   ERCTM_GARBCOLL, ERCF_R  | ERCN_2,    "[timer] Garbage collection timer expired",
   ERCTM_NETO,	ERCF_R 	   | ERCN_4,    "[timer] Next event is \"timeout\"",
   ERCTM_NEGC,	ERCF_R 	   | ERCN_4,    "[timer] Next event is \"garbage collection\"",
   ERCTM_NENONE,ERCF_ 	   | ERCN_1,    "[timer] No events pending",

   ERC_LAST,	ERCF_,		        "Unknown error code received!!!"
};

static char *er_severity[] = { "note", "fatal error", "warn", "weakwarn", "note" };


/*
 *      Produce dot-form of a sockaddr
 */

static char *er_satoa( struct sockaddr *sa )
{
   return( inet_ntoa( ((struct sockaddr_in *) sa)->sin_addr ) );
}


/*
 *	Dump RIP parts
 */

void er_dumprip( struct rip *rip, int length, struct tb_address *from )
{
   fprintf(stderr, "    RIP packet of length %d address %s port %d:\n    RIP cmd %s(%d) vers %d\n",
	   length, er_satoa( &from->tba_addr ), from->tba_port,
	   rip->rip_cmd == RIPCMD_REQUEST ? "REQUEST"
	   : rip->rip_cmd == RIPCMD_RESPONSE ? "RESPONSE"
	   : "",
	   rip->rip_cmd, rip->rip_vers);
}
void er_dumpripe( struct rip *rip, int entry )
{
   fprintf(stderr, "      entry %d dest %s (af=%d) metric %lu\n",
	   entry,
	   er_satoa( &rip->rip_nets[entry].rip_dst ),
	   ntohs(rip->rip_nets[entry].rip_dst.sa_family),
	   ntohl(rip->rip_nets[entry].rip_metric) );
}


/*
 *	Log an error
 */

void er_log( int level, enum er_code code, ... )
{
   struct tb_address *address;
   struct tb_route *route;
   struct tb_iface *iface;
   struct rip *rip;
   struct er_table *error;
   int errno, ioctl, length, length2, rti;
   time_t now, next, pause;
   short form;

   va_list arg;
   va_start( arg, code );

   if( level > g_tracelevel )
      return;

   for( error=&er_table[0]; error->ert_code!=ERC_LAST; error++ ) {
      if( error->ert_code == code )
	 break;
   }

   if( error->ert_code == ERC_LAST )
      level = ERL_DIE;

   if( level == ERL_NOTE
      && ((error->ert_form & ERCN_MASK) >> ERCN_SHIFT) > g_tracenlevel )
      return;

   form = error->ert_form & ~ERCN_MASK;

   fprintf( stderr, "srouted %s: %s",
	   er_severity[level+1],
	   error->ert_text );

   switch( form ) {

   case ERCF_:
      fprintf(stderr, "\n");
      break;

   case ERCF_E:
      fprintf(stderr, ": %s\n", strerror( va_arg(arg, int) ) );
      break;

   case ERCF_IE:
      ioctl=va_arg(arg,int);
      errno=va_arg(arg,int);

      fprintf(stderr, ": ioctl %d: %s\n", ioctl, strerror( errno ) );
      break;

   case ERCF_RF:
   case ERCF_RFE:
   case ERCF_RFEM:
   case ERCF_RFEMR:
      rip = va_arg( arg, struct rip * );
      length = va_arg( arg, int );
      address = va_arg( arg, struct tb_address * );
      fprintf( stderr, ":\n");
      er_dumprip( rip, length, address );
      if( form != ERCF_RF ) {
	 er_dumpripe( rip, va_arg( arg, int ) );
	 if( form != ERCF_RFE ) {
	    fprintf( stderr, "    new cost %d\n", va_arg( arg, int ) );
	    if( form != ERCF_RFEM ) {
	       rti = va_arg( arg, int );
	       if( rti == -1 ) {
		  fprintf( stderr, "    no existing route known\n" );
	       } else {
		  er_dumproute( rti );
	       }
	    }
	 }
      }
      break;

   case ERCF_ID:
      iface = va_arg( arg, struct tb_iface * );
      address = va_arg( arg, struct tb_address * );
      fprintf( stderr, ": iface %s, dest %s\n",
	      iface->tbif_dev,
	      er_satoa( &address->tba_addr ) );
      break;

   case ERCF_I:
      iface = va_arg( arg, struct tb_iface * );
      fprintf( stderr, ": iface %s\n",
	      iface->tbif_dev );
      break;

   case ERCF_IR:
      iface = va_arg( arg, struct tb_iface * );
      route = va_arg( arg, struct tb_route * );
      fprintf( stderr, ": iface-1 %s, iface-2 %s, route %s\n",
	      iface->tbif_dev,
	      route->tbrt_iface == -1 ? "Oops!" :
	            tb_iface[route->tbrt_iface].tbif_dev,
	      er_satoa( &address->tba_addr ) );
      break;

   case ERCF_SS:
      length = va_arg( arg, int );
      length2 = va_arg( arg, int );
      fprintf( stderr, ": got %d, expected %d\n", length, length2 );
      break;

   case ERCF_A:
      address = va_arg( arg, struct tb_address * );
      fprintf( stderr, ": address %s\n",
	      er_satoa( &address->tba_addr ) );
      break;

   case ERCF_P:
      fprintf(stderr, ": port %hu\n", va_arg( arg, unsigned short) );
      break;

   case ERCF_T:
      fprintf(stderr,":\n");
      er_dumproute( -1 );  /* dump the routing table */
      break;

   case ERCF_R:
      fprintf(stderr,":\n");
      er_dumproute( va_arg( arg, int ) );  /* dump one route entry */
      break;

   case ERCF_TTT:
      now = va_arg( arg, time_t );
      next = va_arg( arg, time_t );
      pause = va_arg( arg, time_t );
      fprintf(stderr, ": now=%ds, next=%ds, pause=%ds\n",
	      (int) now, (int) next, (int) pause);
      break;

   default:
      break;  /* this is an error of course, but let's ignore it */
   }

   if( level == ERL_DIE ) {
      exit(1);
   }

}


/*
 *	Dump ONE route table row
 */

static void er_dumparoute( int i )
{
   fprintf(stderr, "  %15.15s ", er_satoa( &tb_route[i].tbrt_dst ) );
   fprintf(stderr, "%15.15s ", er_satoa( &tb_route[i].tbrt_gateway ) );
   fprintf(stderr, "%15.15s ", er_satoa( &tb_route[i].tbrt_mask ) );
   fprintf(stderr, "%04X    %5d    %8.8s\n",
	   tb_route[i].tbrt_flags,
	   tb_route[i].tbrt_metric,
	   tb_route[i].tbrt_iface != -1 ?
	   tb_iface[tb_route[i].tbrt_iface].tbif_dev :
	   "none" );
}


/*
 *	Dump the internal routing table
 */

void er_dumproute( int rti )
{
   int i;

   fprintf(stderr, "  Destination     Gateway         Mask            Flags   Metric    Iface\n");

   if( rti == -1 ) {
      for( i=0; i<TB_ROUTE_SIZE; i++ ) {
	 if( (tb_route[i].tbrt_flags & TBRTF_USED)==0 )
	    continue;
	 er_dumparoute( i );
      }
   } else {
      er_dumparoute( rti );
   }
}


/*
 *	Notify user of trace level change
 */

void er_tracechange( void )
{
   fprintf(stderr,"srouted note: [error] TRACE LEVEL CHANGE: tracelevel=%d, tracenlevel=%d\n", g_tracelevel, g_tracenlevel);
}
