/* input.c -- input processing */

/*
 *  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: input.c,v 1.5 1995/02/20 17:45:38 buhr Exp $";
#endif /* not lint */

#include "defs.h"
#include "table.h"
#include "kernel.h"
#include "output.h"
#include "input.h"

#include <netinet/in.h>

static int in_czentry(struct rip *rip, int entry);


/*
 *	Count number of entries in a RIP packet
 */

int in_countentries( int length )
{
   return ( length - sizeof( ((struct rip *)NULL)->rip_cmd )
      - sizeof( ((struct rip *)NULL)->rip_vers )
	 - sizeof( ((struct rip *)NULL)->rip_res1 ) )
      / sizeof( ((struct rip *)NULL)->rip_nets[0] );
}


/*
 *	Check reserved areas in an entry
 */

static int in_czentry(struct rip *rip, int entry)
{
   int i;

   for(i=0; i<14; i++) {
      if(i==2) i=6;  /* skip IP address */
      if(rip->rip_nets[entry].rip_dst.sa_data[i]) return(1);
   }
   return(0);
}


/*
 *	Process a received RIP packet
 */

void in_processrip(struct rip *rip, int length, struct sockaddr *safrom)
{
   int entry, entry_count;
   struct netinfo *ni;
   int ev=0;  /* if 1, then packet is of "expected" version */
   struct tb_address from, dest;
   short cost;
   int route, netroute, samegw;

   from.tba_addr = *safrom;
   tb_chkaddr(&from);

   note0( ERCIN_GOTINPUT );

   /* this must be from a valid, single-host address */
   if( (from.tba_flags & TBAF_VALID)==0
      || (from.tba_flags & TBAF_HOST)==0 ) {
      warn0(ERCIN_BADFROM);
      return;
   }

#define CZI(thing) \
   if(ev && (thing)!=0) { warn3(ERCIN_CZ,rip,length,&from); goto ignore_rip; }
#define CZE(rip,entry) \
   if(ev && in_czentry((rip),(entry))) { warn4(ERCIN_CZE,rip,length,&from,entry); continue; }
#define CZEI(rip,entry) \
   if(ev && in_czentry((rip),(entry))) { warn4(ERCIN_CZE,rip,length,&from,entry); goto ignore_rip; }
   
   if(rip->rip_vers==0) {
      weakwarn3(ERCIN_VERS0,rip,length,&from);
      goto ignore_rip;
   }
   ev=(rip->rip_vers==RIPVERSION);
   CZI(rip->rip_res1[0]);
   CZI(rip->rip_res1[1]);
   if(rip->rip_cmd>RIPCMD_MAX) {
      if(ev) {
	 warn3(ERCIN_UNKCMD,rip,length,&from);
      } else {
	 weakwarn3(ERCIN_UNKCMD,rip,length,&from);
      }
      goto ignore_rip;
   }

   switch(rip->rip_cmd) {
   case 0:
      warn3(ERCIN_CMD0,rip,length,&from);
      goto ignore_rip;
   case RIPCMD_TRACEON:
   case RIPCMD_TRACEOFF:
      weakwarn3(ERCIN_CMD34,rip,length,&from);
      goto ignore_rip;
   case 5:
      weakwarn3(ERCIN_CMD5,rip,length,&from);
      goto ignore_rip;

   case RIPCMD_REQUEST:
      /* as silent process, we ignore rips from "normal" port */
      if(from.tba_port == g_routedport) {
	 note3( ERCIN_IGNOREREQUEST, rip, length, &from );
	 goto ignore_rip;
      }
      note3( ERCIN_GOTREQUEST, rip, length, &from );
      entry_count=in_countentries(length);
      if(entry_count==1 &&
	 ntohs(rip->rip_nets[0].rip_dst.sa_family)==AF_UNSPEC &&
	 ntohl(rip->rip_nets[0].rip_metric)==TBM_INFINITY) {
	 CZEI(rip,0);
	 note0( ERCIN_REQWHOLE );
	 out_table(&from);   /* send whole table */
      } else {
	 for(entry=0; entry<entry_count; entry++) {
	    CZE(rip,entry);
	    ni=&rip->rip_nets[entry];
	    if(ntohs(ni->rip_dst.sa_family) != AF_INET) {
	       weakwarn4(ERCIN_UNKAF,rip,length,&from,entry);
	       ni->rip_metric=htonl(TBM_INFINITY);
	       continue;
	    }
	    ni->rip_metric=htonl(tb_rtmetric(ni->rip_dst));
	    note5(ERCIN_REQENTRY,rip,length,&from,entry,ni->rip_metric);
	 }
	 note0( ERCIN_REQANSWER );
	 out_rip(rip,length,&from,RIPCMD_RESPONSE);
      }
      break;

   case RIPCMD_RESPONSE:
      if(from.tba_port != g_routedport) {
	 warn3(ERCIN_RESPPORT,rip,length,&from);
	 goto ignore_rip;
      }
      /*** could check if allowed neighbour here! ***/
      /* check that this came from someone directly connected */
      if(from.tba_iface==-1) {
	 warn3(ERCIN_RESPREMOTE,rip,length,&from);
	 goto ignore_rip;
      }
   /**********************************************************
    *   we can't be listening to ourselves, since we never   *
    *   respond to port g_routedport                         *
    **********************************************************/
      note3( ERCIN_GOTRESPONSE, rip, length, &from );
      entry_count=in_countentries(length);
      for(entry=0; entry<entry_count; entry++) {
	 CZE(rip,entry);
	 ni=&rip->rip_nets[entry];
	 if(ntohl(ni->rip_metric) == 0 || 
	    ntohl(ni->rip_metric) > TBM_INFINITY) {
	    warn4(ERCIN_BADMETRIC,rip,length,&from,entry);
	    continue;
	 }
	 dest.tba_addr=ni->rip_dst;
	 dest.tba_addr.sa_family=ntohs(dest.tba_addr.sa_family);
	 tb_chkaddr(&dest);
	 if(dest.tba_af != AF_INET) {
	    weakwarn4(ERCIN_UNKAF,rip,length,&from,entry);
	    continue;
	 }
	 if( (dest.tba_flags & TBAF_VALID) == 0 ||
	    ( (dest.tba_flags & TBAF_DEFAULT) == 0 &&
	     ( (dest.tba_flags & TBAF_CLASSABC) == 0 ||
	      dest.tba_subnet == 0 || dest.tba_subnet == 0x7f000000 ||
	      (dest.tba_flags & TBAF_BROADCAST) ) ) ) {
	    weakwarn4(ERCIN_BADADDR,rip,length,&from,entry);
	    continue;
	 }
	 cost = ntohl(ni->rip_metric) + 
	    tb_iface[from.tba_iface].tbif_metric;
	 if( cost > TBM_INFINITY ) cost=TBM_INFINITY;
	 route = tb_findroute( &dest.tba_addr );
	 note6(ERCIN_RESENTRY, rip, length, &from, entry, cost, route);
	 if( route != -1 && tb_route[route].tbrt_flags & TBRTF_SUPERNET ) {
	    warn1( ERCIN_RTSUPER, &dest );
	    continue;
	 }
	 if( route == -1 ) {
	    if(cost == TBM_INFINITY )
	       continue;
	    /* ignore host route if we have a decent net route */
	    if( dest.tba_flags & TBAF_HOST ) {
	       netroute = tb_findroute( &dest.tba_subnetsa );
	       if( netroute != -1 && tb_route[netroute].tbrt_metric <= cost ) {
		  note1( ERCIN_IGNOREHOST, netroute );
		  continue;
	       }
	    }
	    route = tb_newroute();
	    if( route == -1 ) {
	       warn0(ERCIN_RTFULL);
	       continue;
	    }
	    tb_addroute( route, &dest, &from, cost );
	    note1( ERCIN_RESADDED, route );
	 } else {
	    note0( ERCIN_HADROUTE );
	    samegw = tb_samehost( &tb_route[route].tbrt_gateway,
			 &from.tba_addr );
	    if(samegw && cost == tb_route[route].tbrt_metric
	       && (tb_route[route].tbrt_flags & TBRTF_DELETED) == 0
	       && (tb_route[route].tbrt_flags & TBRTF_KILLED) == 0 ) {
	       note0( ERCIN_REPEATROUTE );
	       tm_settimeout( route );
	    }
	    if( (samegw && cost != tb_route[route].tbrt_metric)
	       || cost < tb_route[route].tbrt_metric
	       || (tb_route[route].tbrt_flags & TBRTF_TENTATIVE)
	       || (tb_route[route].tbrt_flags & TBRTF_KILLED) ) {
	       if( cost == TBM_INFINITY ) {
		  note1( ERCIN_RESDELETED, route );
		  tb_delroute( route, &from );
	       } else {
		  tb_addroute( route, &dest, &from, cost );
		  note1( ERCIN_RESADDED, route );
	       }
	    } else {
	       note0( ERCIN_RESIGNORED );
	    }
	 }
      }
   }
 ignore_rip:
   return;
}
