/* This file contains code to implement the Routing Information Protocol (RIP)
 * and is derived from 4.2BSD code. Mike Karels of Berkeley has stated on
 * TCP-IP that the code may be freely used as long as UC Berkeley is
 * credited. (Well, here's some credit :-). AGB 4-28-88

 * Further documentation on the RIP protocol is now available in Charles
 * Hedrick's draft RFC, as yet unnumbered. AGB 5-6-88
 *
 * The RIP RFC has now been issued as RFC1058. AGB 7-23-88
 *
 * Code gutted and substantially rewritten. KA9Q 9/89
 *
 * Mods by PA0GRI
 *
 *  Changes Copyright (c) 1993 Jeff White - N0POY, All Rights Reserved.
 *  Permission granted for non-commercial copying and use, provided
 *  this notice is retained.
 *
 * Rehack for RIP-2 (RFC1388) by N0POY 4/1993
 *  Modules needed for changes:  rip.c, rip.h, ripcmd.c, ripdump.c, ip.h
 *                               commands.h, iface.h, iface.c, version.c
 *
 * Beta release 11/16/93 V0.95
 *
 * Bug fix that prevented split horizon routing to work fixed.
 * 2/19/94 release V1.0
 *
 * G8BPQ's RIP98 added - G4HIP, 29/3/97
 */

#include "global.h"
#ifdef RIP
#include <stdarg.h>
#include <time.h>
#include "mbuf.h"
#include "netuser.h"
#include "udp.h"
#include "rip.h"

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: rip.c,v 1.27 2001/05/06 16:32:58 brian Exp $";
#endif

struct rip_stat Rip_stat = {{{0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}}, 0, 0, 0, 0, 0, 0};
int16 Rip_trace;
FILE *Rip_trace_file = NULLFILE;
char *Rip_trace_fname = NULLCHAR;
int Rip_merge;
int32 Rip_ttl = RIP_TTL;
int16 Rip_ver_refuse = 0;
int Rip_default_refuse = FALSE;
int rip98allow = 1;
struct rip_list *Rip_list;
struct udp_cb *Rip_cb;
struct rip_auth *Rip_auth;
struct rip_refuse *Rip_refuse;

static int nbits (uint32 target);
static void rip_trigger (void);
static void rip_rx (struct iface * iface, struct udp_cb * sock, int16 cnt);
static void proc_rip (struct iface * iface, uint32 gateway,
	struct rip_route * ep, unsigned char version);
static char *putheader (unsigned char *cp, char command, char version, int16 domain);
static char *putentry (char *cp, int16 fam, int16 tag, uint32 target,
	uint32 targmask, uint32 router, int32 metric);
static char *put98entry (register char *cptr, uint32 target, int16 bits, int metric);
static void send_routes (uint32 dest, int16 port, int trig,
	int flags, unsigned char version, struct rip_list * rdata);

static void pullheader (struct rip_head * ep, struct mbuf ** bpp);
static int check_authentication (struct rip_auth * auth,
	struct mbuf ** bpp, struct rip_head * header, uint32 srcaddr, char const *ifcname,
	struct rip_authenticate * entry);
static char *putauth (char *cp, int16 authtype, char *authpass);
static void rip_trace (short level, char const *errstr,...);



/* Send RIP CMD_RESPONSE packet(s) to the specified rip_list entry */

void
rip_shout (void *p)
{
register struct rip_list *rl;

	rl = (struct rip_list *) p;
	stop_timer (&rl->rip_time);
	send_routes (rl->dest, RIP_PORT, 0, rl->flags, (unsigned char) rl->rip_version, rl);
	set_timer (&rl->rip_time, rl->interval * 1000L);
	start_detached_timer (&rl->rip_time);
}



/* Send the routing table. */
static void
send_routes (dest, port, trig, flags, version, rdata)
uint32 dest;			/* IP destination address to send to */
int16 port;
int trig;			/* Send only triggered updates? */
int flags;
unsigned char version;			/* Version of RIP packet */
struct rip_list *rdata;		/* Used for RIP-2 packets */
{
char *cp;
int i, bits, numroutes, maxroutes;
int16 pktsize;
int riplen = RIP_ENTRY;   
struct mbuf *bp;
struct route *rp;
struct socket lsock, fsock;
struct iface *iface;

	if (version == RIP_VERSION_98)  /* Fiddling to make RIP98 like latest official +1 */
		version = RIP_VERSION_X;
	if (version == RIP_VERSION_X)
		riplen = RIP98_ENTRY;
   
	if ((rp = rt_lookup (dest)) == NULLROUTE) {
		rip_trace (1, "No route to [%s] exists, cannot send", inet_ntoa (dest));
		return;		/* No route exists, can't do it */
	}
	iface = rp->iface;

	/* Compute maximum packet size and number of routes we can send */
	pktsize = ip_mtu (dest) - IPLEN;
	pktsize = min (pktsize, RIP_PKTSIZE);
	maxroutes = (pktsize - RIP_HEADER) / riplen;

	lsock.address = INADDR_ANY;
	lsock.port = RIP_PORT;
	fsock.address = dest;
	fsock.port = port;

	/* Allocate space for a full size RIP packet and generate header */
	if ((bp = alloc_mbuf (pktsize)) == NULLBUF)
		return;
	numroutes = 0;

	/* See if we know information about what to send out */

	if ((version >= RIP_VERSION_2) && (rdata != NULLRL) && (version != RIP_VERSION_X)) {
		cp = putheader (bp->data, RIPCMD_RESPONSE, (char) version, rdata->domain);
		/* See if we need to put an authentication header on */
		if (flags & RIP_AUTHENTICATE) {
			cp = putauth (cp, RIP_AUTH_SIMPLE, rdata->rip_auth_code);
			numroutes++;
		}
	} else	{
		if (version != RIP_VERSION_X)
			cp = putheader (bp->data, RIPCMD_RESPONSE, (char) version, 0);
		else
			cp = putheader (bp->data, RIPCMD_RESPONSE, RIP_VERSION_98, 0);
	}

	/* Emit route to ourselves, if requested */
	if ((flags & RIP_US) && !trig) {        /* Except on a trigger - G4HIP */
		switch (version) {
			case RIP_VERSION_1:
				cp = putentry (cp, RIP_AF_INET, 0, iface->addr, 0, 0, 1);
				break;

			case RIP_VERSION_X:
				cp = put98entry (cp, iface->addr, 32, 1);
				break;
			default:
				cp = putentry (cp, RIP_AF_INET, 0, iface->addr, 0xFFFFFFFFUL, 0, 1);
				break;
		}
		numroutes ++;
	}
	/* Emit default route, if appropriate */

	if (R_default.iface != NULLIF && !(R_default.flags & RTPRIVATE)
	    && (!trig || (R_default.flags & RTTRIG))) {
		if (!(flags & RIP_SPLIT) || iface != R_default.iface) {
			switch (version) {
				case RIP_VERSION_1:
					cp = putentry (cp, RIP_AF_INET, 0, 0, 0, 0, R_default.metric);
					break;
		      
				case RIP_VERSION_X:
					cp = put98entry (cp, 0, 0, R_default.metric);
					break;
		      
				default:
					cp = putentry (cp, RIP_AF_INET, R_default.route_tag, 0, 0, 0, R_default.metric);
					break;
			}
			numroutes++;
		} else if (trig && (flags & RIP_POISON)) {
			/* Poisoned reverse */
			switch (version) {
				case RIP_VERSION_1:
					cp = putentry (cp, RIP_AF_INET, 0, 0, 0, 0, RIP_METRIC_UNREACHABLE);
					break;

				case RIP_VERSION_X:
					cp = put98entry (cp, 0, 0, RIP_METRIC_UNREACHABLE);
					break;
		      
				default:
					cp = putentry (cp, RIP_AF_INET, R_default.route_tag, 0, 0, 0, RIP_METRIC_UNREACHABLE);
					break;
			}
			numroutes++;
		}
	}
	for (bits = 0; bits < 32; bits++) {
		for (i = 0; i < HASHMOD; i++) {
			for (rp = Routes[bits][i]; rp != NULLROUTE; rp = rp->next) {
				if ((rp->flags & RTPRIVATE) || (trig && !(rp->flags & RTTRIG)))
					continue;
			        /* With RIP 98, don't send a host entries where we have it marked as GW */
				if ((version == RIP_VERSION_X) && (rp->gateway == dest))
					continue;

				if (numroutes >= maxroutes) {
					/* Packet full, flush and make another */
					bp->cnt = (int16) (RIP_HEADER + numroutes * riplen);
					(void) send_udp (&lsock, &fsock, 0, 0, bp, bp->cnt, 0, 0);
					Rip_stat.vdata[(int) version].output++;
					if ((bp = alloc_mbuf (pktsize)) == NULLBUF)
						return;
					numroutes = 0;

					if ((version >= RIP_VERSION_2) && (rdata != NULLRL) && (version != RIP_VERSION_X)) {
						cp = putheader (bp->data, RIPCMD_RESPONSE, (char) version,
							rdata->domain);
						/* See if we need to put an authentication header on */
						if (flags & RIP_AUTHENTICATE) {
							cp = putauth (cp, RIP_AUTH_SIMPLE, rdata->rip_auth_code);
							numroutes++;
						}
					} else	{
						if (version != RIP_VERSION_X)
							cp = putheader (bp->data, RIPCMD_RESPONSE, (char) version, 0);
						else
							cp = putheader (bp->data, RIPCMD_RESPONSE, RIP_VERSION_98, 0);
					}
				}
				if (!(flags & RIP_SPLIT) || iface != rp->iface) {
					switch (version) {
						case RIP_VERSION_1:
							cp = putentry (cp, RIP_AF_INET, 0, rp->target, 0, 0, rp->metric);
							break;

						case RIP_VERSION_X:
							cp = put98entry (cp, rp->target, (int16) rp->bits, rp->metric);
							break;
				      
						default:
							cp = putentry (cp, RIP_AF_INET, rp->route_tag, rp->target,
								(0xFFFFFFFFUL << (32 - rp->bits)), rdata ? rdata->proxy_route : 0,
								rp->metric);
							break;
					}
					numroutes++;
				} else if (trig && (flags & RIP_POISON)) {
					switch (version) { 
						case RIP_VERSION_1:
							cp = putentry (cp, RIP_AF_INET, 0, rp->target, 0, 0, RIP_METRIC_UNREACHABLE);
							break;
				      
						case RIP_VERSION_X:
							cp = put98entry (cp, rp->target, (int16) rp->bits, RIP_METRIC_UNREACHABLE);
							break;
				      
						default:
							cp = putentry (cp, RIP_AF_INET, rp->route_tag, rp->target,
								(0xFFFFFFFFUL << (32 - rp->bits)), rdata ? rdata->proxy_route : 0,
								RIP_METRIC_UNREACHABLE);
							break;
					}
					numroutes++;
				}
			}
		}
	}
	if (numroutes != 0) {
		bp->cnt = (int16) (RIP_HEADER + numroutes * riplen);
		(void) send_udp (&lsock, &fsock, 0, 0, bp, bp->cnt, 0, 0);
		Rip_stat.vdata[(int) version].output++;
	} else
		free_p (bp);
}



/* Add an entry to the rip broadcast list */

int
rip_add (dest, interval, flags, version, authpass, domain, route_tag, proxy)
uint32 dest;
int32 interval;
char flags;
char version;
char authpass[RIP_AUTH_SIZE];
int16 domain;
int16 route_tag;
uint32 proxy;
{
register struct rip_list *rl;
struct route *rp;

	for (rl = Rip_list; rl != NULLRL; rl = rl->next)
		if ((rl->dest == dest) && (rl->domain == domain))
			return 0;

	if ((rp = rt_lookup (dest)) == NULLROUTE) {
		tprintf ("%s is unreachable\n", inet_ntoa (dest));
		return 0;
	}
	/* get a chunk of memory for the rip interface descriptor */
	rl = (struct rip_list *) callocw (1, sizeof (struct rip_list));

	/* tack this record on as the first in the list */
	rl->next = Rip_list;
	if (rl->next != NULLRL)
		rl->next->prev = rl;
	Rip_list = rl;

	rl->dest = dest;

	rip_trace (9, "Rip added V%d Flags %d Tag %d Proxy %s Domain %d Auth %s Interval %d",
		   version, flags, route_tag, inet_ntoa (proxy), domain, authpass, interval);

	/* and the interface ptr, tick interval and flags */
	rl->iface = rp->iface;
	rl->rip_version = version;
	rl->interval = interval;
	rl->flags = (int16) (int) flags;
	rl->proxy_route = proxy;
	rl->route_tag = route_tag;
	rl->domain = domain;
	memcpy (rl->rip_auth_code, authpass, RIP_AUTH_SIZE);

	/* and set up the timer stuff */
	set_timer (&rl->rip_time, interval * 1000L);
	rl->rip_time.func = rip_shout;
	rl->rip_time.arg = rl;
	start_detached_timer (&rl->rip_time);
	return 1;
}



/* add a gateway to the rip_refuse list which allows us to ignore their
 * advertisements
 */

int
riprefadd (uint32 gateway)
{
register struct rip_refuse *rl;

	for (rl = Rip_refuse; rl != NULLREF; rl = rl->next)
		if (rl->target == gateway)
			return 0;	/* Already in table */

	/* get a chunk of memory for the rip interface descriptor */
	rl = (struct rip_refuse *) callocw (1, sizeof (struct rip_refuse));

	/* tack this record on as the first in the list */
	rl->next = Rip_refuse;
	if (rl->next != NULLREF)
		rl->next->prev = rl;
	Rip_refuse = rl;

	/* fill in the gateway to ignore */
	rl->target = gateway;
	return 0;
}



/* Add an authentication type to an interface name */

int
ripauthadd (char const *ifcname, int16 domain, char const *password)
{
register struct rip_auth *ra;
int x;

	for (ra = Rip_auth; ra != NULLAUTH; ra = ra->next)
		if (!strcmp (ifcname, ra->ifc_name) && (ra->domain == domain))
			return 1;	/* Already in table */

	/* get a chunk of memory for the rip interface descriptor */
	ra = (struct rip_auth *) callocw (1, sizeof (struct rip_auth));

	/* tack this record on as the first in the list */
	ra->next = Rip_auth;
	if (ra->next != NULLAUTH)
		ra->next->prev = ra;
	Rip_auth = ra;

	/* fill in the data */
	ra->ifc_name = strdup (ifcname);
	ra->domain = domain;
	for (x = 0; x < RIP_AUTH_SIZE + 1; x++)
		ra->rip_auth_code[x] = '\0';
	strncpy (ra->rip_auth_code, password, RIP_AUTH_SIZE);
	return 0;
}



/* Drop an authentication to an interface name */

int
ripauthdrop (char *ifcname, int16 domain)
{
register struct rip_auth *ra;

	for (ra = Rip_auth; ra != NULLAUTH; ra = ra->next)
		if (!strcmp (ifcname, ra->ifc_name) && (ra->domain == domain))
			break;

	/* leave if we didn't find it */
	if (ra == NULLAUTH)
		return 0;

	/* Unlink from list */
	if (ra->next != NULLAUTH)
		ra->next->prev = ra->prev;
	if (ra->prev != NULLAUTH)
		ra->prev->next = ra->next;
	else
		Rip_auth = ra->next;

	free ((char *) ra->ifc_name);
	free ((char *) ra);
	return 0;
}



/* drop a RIP target */

int
rip_drop (uint32 dest, int16 domain)
{
register struct rip_list *rl;

	for (rl = Rip_list; rl != NULLRL; rl = rl->next)
		if ((rl->dest == dest) && (rl->domain == domain))
			break;

	/* leave if we didn't find it */
	if (rl == NULLRL)
		return 0;

	/* stop the timer */
	stop_timer (&rl->rip_time);

	/* Unlink from list */
	if (rl->next != NULLRL)
		rl->next->prev = rl->prev;
	if (rl->prev != NULLRL)
		rl->prev->next = rl->next;
	else
		Rip_list = rl->next;

	/* and deallocate the descriptor memory */
	free ((char *) rl);
	return 0;
}



/* drop a RIP-refuse target from the rip_refuse list */

int
riprefdrop (uint32 gateway)
{
register struct rip_refuse *rl;

	for (rl = Rip_refuse; rl != NULLREF; rl = rl->next)
		if (rl->target == gateway)
			break;

	/* leave if we didn't find it */
	if (rl == NULLREF)
		return 0;

	/* Unlink from list */
	if (rl->next != NULLREF)
		rl->next->prev = rl->prev;
	if (rl->prev != NULLREF)
		rl->prev->next = rl->next;
	else
		Rip_refuse = rl->next;

	/* and deallocate the structure memory */
	free ((char *) rl);
	return 0;
}



/* function to output a RIP CMD_RESPONSE packet for the rip_trigger list */

static void
rip_trigger ()
{
register struct rip_list *rl;
int bits, i;
struct route *rp;

	for (rl = Rip_list; rl != NULLRL; rl = rl->next)
		send_routes (rl->dest, RIP_PORT, 1, rl->flags, uchar(rl->rip_version), rl);

	/* Clear the trigger list */
	R_default.flags &= ~RTTRIG;
	for (bits = 0; bits < 32; bits++) {
		for (i = 0; i < HASHMOD; i++) {
			for (rp = Routes[bits][i]; rp != NULLROUTE; rp = rp->next)
				rp->flags &= ~RTTRIG;
		}
	}
}



/* Start RIP agent listening at local RIP UDP port */
int
rip_init ()
{
struct socket lsock;

	lsock.address = INADDR_ANY;
	lsock.port = RIP_PORT;

	if (Rip_cb == NULLUDP)
		Rip_cb = open_udp (&lsock, rip_rx);

	Rip_trace = 0;

	/* Add the 0 domain with no password */

	(void) ripauthadd (DEFAULTIFC, 0, RIP_NO_AUTH);

	return 0;
}



/* Process RIP input received from 'interface'. */
static void
rip_rx (struct iface *iface, struct udp_cb *sock, int16 cnt OPTIONAL)
{
int riplen = RIP_ENTRY;
struct mbuf *bp;
struct socket fsock;
struct rip_refuse *rfl;
struct rip_route entry;
struct rip_head header;
struct route *rp;
struct rip_list *rl;
   
	/* receive the RIP packet */
	(void) recv_udp (sock, &fsock, &bp);

	/* check the gateway of this packet against the rip_refuse list and
	 * discard it if a match is found
	 */

	for (rfl = Rip_refuse; rfl != NULLREF; rfl = rfl->next) {
		if (fsock.address == rfl->target) {
			Rip_stat.refusals++;
			rip_trace (2, "RIP refused from %s", inet_ntoa (fsock.address));
			free_p (bp);
			return;
		}
	}

	pullheader (&header, &bp);

	/* This is a fiddle to make RIP98 look like latest rip +1 */
	if (header.rip_vers == RIP_VERSION_98) {
		header.rip_vers = RIP_VERSION_X;
		riplen = RIP98_ENTRY;
	}

	/* increment the rcvd cnt */
	Rip_stat.vdata[header.rip_vers].rcvd++;

	/* Check to see if we'll accept this version on this interface */

	if (header.rip_vers <= Rip_ver_refuse) {
		if (header.rip_vers != RIP_VERSION_X)
			rip_trace (3, "RIP version %d refused from [%s]", header.rip_vers, inet_ntoa (fsock.address));
		else
			rip_trace (3, "RIP version %d refused from [%s]", RIP_VERSION_98, inet_ntoa (fsock.address));
		Rip_stat.refusals++;
		free_p (bp);
		return;
	}

	if ((header.rip_vers == RIP_VERSION_X) && rip98allow == 0) {
		rip_trace (1, "RIP-98 frame received - rip98rx off");
		Rip_stat.refusals++;
		free_p(bp);
		return;
	}
	        
	/* Check the version of the frame */
	switch (header.rip_vers) {
		case RIP_VERSION_2:
			break;

		case RIP_VERSION_0:
			rip_trace (1, "RIP Version 0 refused from [%s]", inet_ntoa (fsock.address));
			Rip_stat.version++;
			free_p (bp);
			return;

		case RIP_VERSION_1:
			/* Toss RIP if header is bogus for V1 */
			if (header.rip_domain != 0) {
				rip_trace (1, "RIP-1 bogus header, data in null fields from [%s]",
					   inet_ntoa (fsock.address));
				Rip_stat.vdata[RIP_VERSION_1].unknown++;
				free_p (bp);
				return;
			}
			break;
	   
		case RIP_VERSION_X:
	                
			if ((header.rip_cmd != RIPCMD_RESPONSE) && (header.rip_cmd != RIPCMD_REQUEST)) {
				rip_trace (1, "RIP-98 invalid header received from [%s]\n", inet_ntoa (fsock.address));
				Rip_stat.vdata[RIP_VERSION_X].unknown++;
				free_p (bp);
				return;
			}
			break;
		default:
			break;
	}
        if (header.rip_vers != RIP_VERSION_X)
		rip_trace (2, "RIP Packet version %d processing", header.rip_vers);
	else
		rip_trace (2, "RIP Packet version %d processing", RIP_VERSION_98);
   
	switch (header.rip_cmd) {
		case RIPCMD_RESPONSE:
			rip_trace (2, "RIPCMD_RESPONSE from [%s] domain %d", inet_ntoa (fsock.address),
				   header.rip_domain);

			Rip_stat.vdata[header.rip_vers].response++;
			if (header.rip_vers != RIP_VERSION_X)
				pullentry (&entry, &bp);
			else
				pull98entry (&entry, &bp);

			if (header.rip_vers >= RIP_VERSION_2) {
				if (header.rip_vers != RIP_VERSION_X) {
					/* We still have the authentication entry from above */

					if (!check_authentication (Rip_auth, &bp, &header, fsock.address,
						iface->name, (struct rip_authenticate *) &entry)) {
						free_p (bp);
						return;				   
					}
				}
			}
			proc_rip (iface, fsock.address, &entry, header.rip_vers);

			while (len_p (bp) >= riplen) {
				if (header.rip_vers != RIP_VERSION_X)
					pullentry (&entry, &bp);
				else
					pull98entry (&entry, &bp);

				proc_rip (iface, fsock.address, &entry, header.rip_vers);
			}

			/* If we can't reach the sender of this update, or if
			 * our existing route is not through the interface we
			 * got this update on, add him as a host specific entry
			 */
			if ((rp = rt_blookup (fsock.address, 32)) != NULLROUTE)
				/* Host-specific route already exists, refresh it */
				start_detached_timer (&rp->timer);
			else if ((rp = rt_lookup (fsock.address)) == NULLROUTE
				 || rp->iface != iface) {
				entry.rip_family = RIP_AF_INET;
				entry.rip_tag = 0;
				entry.rip_dest = fsock.address;
				if (header.rip_vers > RIP_VERSION_1)
					entry.rip_dest_mask = 32;
				else
					entry.rip_dest_mask = 0;
				entry.rip_router = 0;
				entry.rip_metric = 0;	/* will get incremented to 1 */
				proc_rip (iface, fsock.address, &entry, header.rip_vers);
			}
			if (Rip_merge)
				rt_merge (Rip_trace);
			rip_trigger ();
			break;

		case RIPCMD_REQUEST:
			rip_trace (2, "RIPCMD_REQUEST from [%s] domain %d", inet_ntoa (fsock.address),
				   header.rip_domain);

			Rip_stat.vdata[header.rip_vers].request++;
			/* For now, just send the whole table with split horizon
			 * enabled when the source port is RIP_PORT, and send
			 * the whole table with split horizon disable when another
			 * source port is used. This should be replaced with a more
			 * complete implementation that checks for non-global requests
			 */

		    if ((rp = rt_lookup (fsock.address)) == NULLROUTE) { /* G4HIP */
			    rip_trace (2, "RIPCMD_REQUEST - no route to [%s]", inet_ntoa (fsock.address));
			    break;
		    }
			if (header.rip_vers > RIP_VERSION_1) {
				/* RIP-2, let's see if we know something about this guy */
				for (rl = Rip_list; rl != NULLRL; rl = rl->next)
					if ((rl->dest == rp->target) && (rl->domain == header.rip_domain))
						break;

				if (rl == NULLRL)
					if (fsock.port == RIP_PORT)
						send_routes (fsock.address, fsock.port, 0, (RIP_BROADCAST | RIP_SPLIT |
							RIP_POISON), header.rip_vers, NULLRL);
					else
						send_routes (fsock.address, fsock.port, 0, (RIP_BROADCAST),
							header.rip_vers, NULLRL);
				else if (fsock.port == RIP_PORT)
					send_routes (fsock.address, fsock.port, 0, rl->flags, header.rip_vers, rl);
				else
					send_routes (fsock.address, fsock.port, 0, (rl->flags & ~(RIP_SPLIT | RIP_POISON)),
						header.rip_vers, rl);
			} else {
				if (fsock.port == RIP_PORT)
					send_routes (fsock.address, fsock.port, 0, (RIP_SPLIT | RIP_BROADCAST | RIP_POISON),
						header.rip_vers, NULLRL);
				else
					send_routes (fsock.address, fsock.port, 0, RIP_BROADCAST,
						header.rip_vers, NULLRL);
			}
			break;

		default:
			rip_trace (1, "RIPCMD Unknown or not implemented from [%s] command %d",
				 inet_ntoa (fsock.address), header.rip_cmd);
			Rip_stat.vdata[header.rip_vers].unknown++;
			break;
	}			/* switch */
	free_p (bp);
}



/* Apply a set of heuristics for determining the number of significant bits
 * (i.e., the address mask) in the target address. Needed since RIP doesn't
 * include the address mask for each entry.  Applies only to RIP-1 packets.
 */

static int
nbits (uint32 target)
{
int bits = 0;

	if (target == 0)
		return 0;	/* Special case: 0.0.0.0 is the default route */

	/* Check the host-part bytes of
	 * the address to check for byte-wide zeros
	 * which we'll consider to be subnet routes.
	 * e.g.  44.80.0.0 will be considered to be equal to 44.80/16
	 * whereas 44.80.1.0 will be considered to be 44.80.1/24
	 */
	switch (hibyte (hiword (target)) >> 6) {
		case 3:	/* Class C address */
			/* is it a host address ? i.e. are there any 1's in the host part ? */
			if (target & 0xff)
				bits = 32;
			else
				bits = 24;
			break;
		case 2:	/* Class B address */
			if (target & 0xff)
				bits = 32;
			else if (target & 0xff00)
				bits = 24;
			else
				bits = 16;
			break;
		case 0:	/* Class A address */
		case 1:
		default:
			if (target & 0xff)
				bits = 32;
			else if (target & 0xff00)
				bits = 24;
			else if (target & 0xff0000L)
				bits = 16;
			else
				bits = 8;
	}

	return bits;
}



/* Remove and process a RIP response entry from a packet */

static void
proc_rip (struct iface *iface, uint32 gateway, register struct rip_route *ep, unsigned char version)
{
int32 interval;
uint32 target;
unsigned int bits;
register struct route *rp;
struct rip_list *rl;
int add = 0;		/* action flags */
int drop = 0;
int trigger = 0;

	if (ep->rip_family != RIP_AF_INET) {
		if (ep->rip_family == RIP_AF_AUTH)
			return;
		/* Skip non-IP addresses */
		rip_trace (1, "RIP_rx: Not an IP family packet\n");
		Rip_stat.addr_family++;
		return;
	}

	/* RIP-1 says all unused fields must be zero */
	if (version == RIP_VERSION_1) {
		if (ep->rip_tag != 0 || ep->rip_dest_mask != 0 || ep->rip_router != 0) {
			rip_trace (1, "RIP_rx: RIP-1 entry bad, data in null fields");
			Rip_stat.vdata[version].unknown++;
		}
		/* Guess at the mask, since it's not explicit for RIP-1 */
		bits = (unsigned int) nbits (ep->rip_dest);
		target = ep->rip_dest;
	} else {
		/* Assume RIP-2 */
		if (!ep->rip_dest_mask) {
			/* No netmask, guess */
			bits = (unsigned int) nbits (ep->rip_dest);
		} else
			bits = (unsigned int) mask2width (ep->rip_dest_mask);
		target = ep->rip_dest;
		/* Check for "proxy" rip */
		if (ep->rip_router) {
			rip_trace (3, "Proxy rip pointing to [%s]", inet_ntoa (ep->rip_router));
			gateway = ep->rip_router;
		}
	}

	/* Don't ever add a route to myself through somebody! */
	if (bits == 32 && ismyaddr (target) != NULLIF) {
		rip_trace (2, "Route to self [%s]/32 metric %lu", inet_ntoa (target),
			   ep->rip_metric);
		return;
	}
	/* Check to see if we'll take a default route, zero bits mean default */

	if (Rip_default_refuse && bits == 0) {
		rip_trace (2, "Default route refused from [%s]", inet_ntoa (target));
		return;
	}
	/* Update metric to reflect link cost */
	ep->rip_metric += iface->iface_metric;
	ep->rip_metric = min (ep->rip_metric, RIP_METRIC_UNREACHABLE);

	/* Find existing entry, if any */
	rp = rt_blookup (target, bits);

	/* Don't touch private routes */
	if (rp != NULLROUTE && (rp->flags & RTPRIVATE)) {
		rip_trace (3, "Route to [%s]/%u unchanged, private", inet_ntoa (target),
			   bits);
		return;
	}
        if (rp == NULLROUTE) {
		if (ep->rip_metric < RIP_METRIC_UNREACHABLE) {
			/* New route; add it and trigger an update */
			add++;
			trigger++;
		}
	} else if (rp->metric == RIP_METRIC_UNREACHABLE) {
		/* Route is in hold-down; ignore this guy */
		rip_trace (2, "Route to [%s]/%u ignored (hold-down) metric %lu",
			   inet_ntoa (target), bits, ep->rip_metric);
	} else if (rp->gateway == gateway && rp->iface == iface) {
		/* This is the gateway for the entry we already have, restart the timer */
		start_detached_timer (&rp->timer);
		if (rp->metric != ep->rip_metric) {
			/* Metric has changed. Update it and trigger an
			 * update. If route has become unavailable, start
			 * the hold-down timeout.
			 */
			rip_trace (3, "Metric change [%s]/%u %lu -> %lu", inet_ntoa (target),
				   bits, rp->metric, ep->rip_metric);
			if (ep->rip_metric == RIP_METRIC_UNREACHABLE)
				rt_timeout (rp);	/* Enter hold-down timeout */
			else
				rp->metric = ep->rip_metric;
			trigger++;
		}
	} else {
		/* Entry is from a different gateway than the current route */
		if (ep->rip_metric < rp->metric) {
			/* Switch to a new gateway */
			rip_trace (3, "Metric better [%s]/%u new: %lu old: %lu", inet_ntoa (target),
				   bits, ep->rip_metric, rp->metric);
			drop++;
			add++;
			trigger++;
		} else {
			/* Metric is no better, stay with current route */
			rip_trace (3, "Metric not better [%s]/%u new: %lu old: %lu", inet_ntoa (target),
				   bits, ep->rip_metric, rp->metric);
		}
	}
	if (drop) {
		/* Switching to a better gateway; delete old entry */
		rip_trace (2, "Route drop [%s]/%u", inet_ntoa (target), bits);
		(void) rt_drop (target, bits);
	}
	if (add) {
		/* Add a new entry */
		interval = Rip_ttl;
		for (rl = Rip_list; rl != NULLRL; rl = rl->next) {
			if (rl->iface == iface) {
				interval = rl->interval * 4;
				break;
			}
		}
		rip_trace (2, "Route add [%s]/%u through %s via ",
			   inet_ntoa (target), bits, iface->name);
		rip_trace (2, "[%s] metric %lu", inet_ntoa (gateway),
			   ep->rip_metric);

		rp = rt_add (target, (unsigned) bits, gateway, iface,
			     (int) ep->rip_metric, interval, 0);

		/* Add in the routing tag for RIP-2 */

		if (version >= RIP_VERSION_2)
			rp->route_tag = ep->rip_tag;
		else
			rp->route_tag = 0;
	}
	/* If the route changed, mark it for a triggered update */
	if (trigger && rp)
		rp->flags |= RTTRIG;
}



/* Send a RIP request packet to the specified destination */

int
ripreq (uint32 dest, int16 replyport, int16 version)
{
struct mbuf *bp;
struct socket lsock, fsock;
char *cp;
register struct rip_list *rl;

	lsock.address = INADDR_ANY;
	lsock.port = replyport;

	/* if we were given a valid dest addr, ask it (the routers on that net)
	 * for a default gateway
	 */

	if (dest == 0)
		return 0;

	fsock.address = dest;
	fsock.port = RIP_PORT;

	/* Send out one RIP Request packet as a broadcast to 'dest'  */
	if ((bp = alloc_mbuf (RIP_HEADER + (2*RIP_ENTRY))) == NULLBUF)
		return -1;

	/* Check to see if we already know something about who we're   */
	/* requesting the RIP from */

	for (rl = Rip_list; rl != NULLRL; rl = rl->next)
		if (rl->dest == dest)
			break;

	bp->cnt = RIP_HEADER + RIP_ENTRY;
	if (rl != NULLRL) {
		cp = putheader (bp->data, RIPCMD_REQUEST, rl->rip_version, rl->domain);
		if (rl->rip_version >= RIP_VERSION_2) {
			if (rl->flags & RIP_AUTHENTICATE) {
				cp = putauth (cp, RIP_AUTH_SIMPLE, rl->rip_auth_code);
				bp->cnt += RIP_ENTRY;
			}
		}
		Rip_stat.vdata[(int) rl->rip_version].output++;
	} else {
		cp = putheader (bp->data, RIPCMD_REQUEST, (char) version, 0);
		Rip_stat.vdata[version].output++;
	}

        if (version == RIP_VERSION_98 || ((rl != NULLRL) && (rl->rip_version == RIP_VERSION_98))) {
			cp = put98entry(cp, 0, 0, RIP_METRIC_UNREACHABLE);
	        bp->cnt = RIP_HEADER + RIP98_ENTRY;
	} else
	cp = putentry (cp, 0, 0, 0L, 0L, 0L, RIP_METRIC_UNREACHABLE);
	(void) send_udp (&lsock, &fsock, 0, 0, bp, bp->cnt, 0, 0);
	return 0;
}



/* Write the authentication packet */

static char *
putauth (register char *cptr, int16 authtype, char *authpass)
{
int x;
unsigned char *cp = (unsigned char *) cptr;

	cp = put16 (cp, 0xFFFF);
	cp = put16 (cp, authtype);

	/* Put the password in big-endian (network) byte order */
	/* This probably is not the best way to do this, since it */
	/* would hose up on a real big-endian machine.  Oh well */
	/* Something to fix in the future.  Whip me, beat me, make */
	/* me use an Intel micro brain.  -N0POY */

	for (x = 0; x < RIP_AUTH_SIZE; x += 4) {
		*cp++ = uchar (authpass[x + 3]);
		*cp++ = uchar (authpass[x + 2]);
		*cp++ = uchar (authpass[x + 1]);
		*cp++ = uchar (authpass[x]);
	}
	return ((char *) cp);
}



/* Write the header of a RIP packet */

static char *
putheader (register unsigned char *cp, char command, char version, int16 domain)
{
	*cp++ = uchar(command);
	*cp++ = uchar(version);
	return (char *) put16 ((unsigned char *) cp, domain);
}



/* Write a single entry into a rip packet */

static char *
putentry (register char *cptr, int16 fam, int16 tag, uint32 target, uint32 targmask,
	  uint32 router, int32 metric)
{
unsigned char *cp = (unsigned char *) cptr;

	cp = put16 (cp, fam);
	cp = put16 (cp, tag);
	cp = put32 (cp, target);
	cp = put32 (cp, targmask);
	cp = put32 (cp, router);
	return (char *) put32 (cp, (unsigned long) metric);
}



/* Write a single entry into a rip98 packet */

static char *
put98entry (register char *cptr, uint32 target, int16 bits, int metric)
{
unsigned char *cp = (unsigned char *) cptr;

	cp = put32 (cp, target);
	*cp++ = uchar (bits);
	*cp++ = uchar (metric);
	return (char *) cp;
}



/* Check the authentication of RIP-II packets */
static int
check_authentication (struct rip_auth *auth, struct mbuf **bpp OPTIONAL, struct rip_head *header,
	uint32 srcaddr, char const *ifcname, struct rip_authenticate *entry)
{
struct rip_auth *rd;

	for (rd = auth; rd != NULLAUTH; rd = rd->next) {
		if ((strcmp (ifcname, rd->ifc_name) == 0) ||
		    (strcmp (DEFAULTIFC, rd->ifc_name) == 0)) {
			if (rd->domain == header->rip_domain) {
				/* We'll take this domain, check against a NULL password */
				if (strcmp (rd->rip_auth_code, RIP_NO_AUTH) == 0) {
					rip_trace (3, "RIP-2 taken due to no password from [%s] domain %d",
						   inet_ntoa (srcaddr), header->rip_domain);
					return (TRUE);
				} else {

					/* Okay, we need an authentication */

					if (entry->rip_family != RIP_AF_AUTH) {
						/* It doesn't have an authentication packet */
						rip_trace (2, "RIP-2 lacking authentication packet from [%s] domain %d",
							   inet_ntoa (srcaddr), header->rip_domain);
						Rip_stat.auth_fail++;
						return (FALSE);
					}
					if (entry->rip_auth_type != RIP_AUTH_SIMPLE) {
						/* Only support simple authentication */
						rip_trace (2, "RIP-2 wrong type of authentication from [%s]",
						       inet_ntoa (srcaddr));
						Rip_stat.auth_fail++;
						return (FALSE);
					}
					if (memcmp (rd->rip_auth_code, entry->rip_auth_str, RIP_AUTH_SIZE) == 0) {
						rip_trace (3, "RIP-2 authenticated from [%s] domain %d",
							   inet_ntoa (srcaddr), header->rip_domain);
						return (TRUE);
					} else {
						rip_trace (2, "RIP-2 authentication failed from [%s] domain %d,\n attempted password '%.16s' right password '%.16s'",
							   inet_ntoa (srcaddr), header->rip_domain, entry->rip_auth_str, rd->rip_auth_code);
						Rip_stat.auth_fail++;
						return (FALSE);
					}
				}
			}
		}
	}
	/* Didn't find the right routing domain for this packet */
	rip_trace (3, "RIP-2 domain %d not accepted from [%s]", header->rip_domain,
		   inet_ntoa (srcaddr));
	Rip_stat.wrong_domain++;
	return (FALSE);
}



/* Route timeout handler. If route has already been marked for deletion
 * then delete it. Otherwise mark for deletion and restart timer.
 */
void
rt_timeout (void *s)
{
register struct route *rp = (struct route *) s;

	stop_timer (&rp->timer);
	if (rp->metric < RIP_METRIC_UNREACHABLE) {
		rip_trace (5, "RIP:  route to [%s]/%d expired - hold down", inet_ntoa (rp->target),
			   rp->bits);
		rp->metric = RIP_METRIC_UNREACHABLE;
		if (dur_timer (&rp->timer) == 0)
			set_timer (&rp->timer, Rip_ttl * 1000L);
		/* wait 2/3 of timeout before garbage collect */
		set_timer (&rp->timer, dur_timer (&rp->timer) * 2 / 3);
		rp->timer.func = rt_timeout;
		rp->timer.arg = (void *) rp;
		start_detached_timer (&rp->timer);
		/* Route changed; mark it for triggered update */
		rp->flags |= RTTRIG;
		rip_trigger ();
	} else {
		rip_trace (5, "RIP:  route to [%s]/%d expired - dropped", inet_ntoa (rp->target),
			   rp->bits);
		(void) rt_drop (rp->target, rp->bits);
	}
}



static void
pullheader (struct rip_head *ep, struct mbuf **bpp)
{
	ep->rip_cmd = uchar(pullchar (bpp));
	ep->rip_vers = uchar(pullchar (bpp));
	ep->rip_domain = pull16 (bpp);
}



static void
rip_trace (short level, char const *errstr,...)
{
	if ((int16) level <= Rip_trace) {
		char *timestr;
		time_t timer;
		va_list argptr;

		if (Rip_trace_fname != NULLCHAR) {
			(void) time (&timer);
			timestr = ctime (&timer);
			timestr[24] = '\0';
			Rip_trace_file = fopen (Rip_trace_fname, APPEND_TEXT);
			fprintf (Rip_trace_file, "%s - ", timestr);

			va_start (argptr, errstr);		/*lint !e718 !e746 */
			(void) vfprintf (Rip_trace_file, errstr, argptr);
			va_end (argptr);
			fprintf (Rip_trace_file, "\n");
			(void) fclose (Rip_trace_file);
		} else {
			va_start (argptr, errstr);
			(void) usvprintf (Curproc->output, errstr, argptr);
			va_end (argptr);
			tprintf ("\n");
		}
	}
}



void
pullentry (register struct rip_route *ep, struct mbuf **bpp)
{
	ep->rip_family = pull16 (bpp);
	ep->rip_tag = pull16 (bpp);
	ep->rip_dest = pull32 (bpp);
	ep->rip_dest_mask = pull32 (bpp);
	ep->rip_router = pull32 (bpp);
	ep->rip_metric = (long) pull32 (bpp);
}



/* Pulls a RIP98 Entry and converts to "more standard" RIP */
void
pull98entry (register struct rip_route *ep, struct mbuf **bpp)
{
unsigned int mask_bits;
int metric_bits;   

	ep->rip_family = RIP_AF_INET;
	ep->rip_tag = 0;
	ep->rip_dest = pull32 (bpp);
	mask_bits = (unsigned int) pullchar (bpp);
	metric_bits = pullchar (bpp);
	ep->rip_dest_mask = (0xffffffffLU << (32-mask_bits));
	ep->rip_metric = metric_bits;
	ep->rip_router = 0;

#if 0		/* Used in debugging, but left for anyone who has need ! */
	rip_trace (9, "Rip98: Destination [%s] : mask %d : metric %d", inet_ntoa( ep->rip_dest), mask_bits, metric_bits);
#endif
}


#endif /* RIP */
