/* Copyright 1986 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in file notice.h */
#include	"notice.h"

/* This file contains the routines to implement Berkeley's Routing
 * Information Protocol.  It is a routing protocol based on the XNS
 * routing protocol with the change that addresses are 4.2BSD
 * sockaddr's instead of XNS addresses.
 */

/*
 *------------------------------------------------------------------
 *
 * $Source: /afs/net.mit.edu/project/cgw/src/gw/in/RCS/rip.c,v $
 * $Revision: 1.9 $
 * $Date: 90/09/29 20:08:49 $
 * $State: Exp $
 * $Author: jon $
 * $Locker:  $
 *
 * $Log:	rip.c,v $
 * Revision 1.9  90/09/29  20:08:49  jon
 * don't advertise PPP host routes and use a variable for dir route metric
 * 
 * Revision 1.8  89/02/14  17:21:27  jon
 * add split horizon (with and without poisoned reverse)
 * 
 * Revision 1.7  88/08/08  16:46:36  jon
 * Change to ignore routes with destinations of 0.0.0.0 ... some bug is
 * showing its ugly head and this may help (maybe).
 * 
 * Revision 1.6  88/07/28  02:29:00  jon
 * Don't use metric from routing restrictions if the metric we hear is infinity.
 * 
 * Revision 1.5  88/07/26  19:55:32  jon
 * Use the metric information now returned by res_ingore_route.
 * 
 * Revision 1.4  88/07/24  17:37:24  jon
 * Makes use of routing restriction routines.
 * 
 * Revision 1.3  88/06/08  23:24:21  jon
 * Set ipr->src_gw when getting route updates.
 * 
 * Revision 1.2  87/07/15  00:53:44  jon
 * Registers ripflg with log_register_flag
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_rip_c = "$Header: /afs/net.mit.edu/project/cgw/src/gw/in/RCS/rip.c,v 1.9 90/09/29 20:08:49 jon Exp $";
#endif	lint

#include	<types.h>
#include	<sys.h>
#include	<sysext.h>
#include	<gw/src/const.h>
#include	<gw/src/param.h>
#include	<gw/src/defs.h>
#include	<gw/src/macs.h>
#include	<gw/src/net.h>
#include	<gw/src/ext.h>
#include	<gw/opcon/opcon.h>
#include	"in.h"
#include	"inext.h"
#include	"inudp.h"
#include	"inrte.h"

#include	"rip.h"

/* Logging definitions */
#undef MSGFLG
#define MSGFLG ripflg
ext bitf ripflg;		/* Message flag for RIP */
#define RL_RUPD 0x2000		/* Dump RIP updates to the routing table */
#define RL_TDUMP 0x4000		/* Dump transmitted RIP packets */
#define RL_RDUMP 0x8000		/* Dump received RIP packets */

#define BRD_TIMER 0		/* Flags to rip_brd_tbl(). */
#define BRD_UPDATE 1

ext int good_password;
bitf rip_brd_qd;		/* Set if a task has been queued to
				 * broadcast the routing table. */

/* Error messages */
static char ripbrdtbl[] = "RIP brd tbls\n";
static char ripbrdupd[] = "RIP brd upd\n";
static char ripbrdreq[] = "RIP brd req\n";
static char ripbdver[] = "RIP bad ver %d, frm %d.%d.%d.%d\n";
static char ripreq[] = "RIP req frm %d.%d.%d.%d\n";
static char ripgwreq[] = "RIP gw req frm %d.%d.%d.%d\n";
static char riptron[] = "RIP trc on frm %d.%d.%d.%d\n";
static char riptroff[] = "RIP trc off frm %d.%d.%d.%d\n";
static char riprsp[] = "RIP rsp frm %d.%d.%d.%d\n";
static char ripbdcmd[] = "RIP bd cmd %d, frm %d.%d.%d.%d\n";

/* Interface to command interpreter. */
void rip_ctl();

static subcommand rip_scmd[] = {
    { "rcv", "Enable RIP input from net `n'", rip_ctl, 0 },
    { "-rcv", "Disable RIP input from net `n'", rip_ctl, 1 },
    { "snd", "Enable RIP sending to net `n'", rip_ctl, 2 },
    { "-snd", "Disable RIP sending to net `n'", rip_ctl, 3 },
    { "pr", "Enable poisoned reverse to net 'n'", rip_ctl, 4},
    { "-pr", "Disable poisoned reverse to net 'n'", rip_ctl, 5},
    { "stat", "Status of RIP", rip_ctl, -1 },
    { NULL, NULL, NULL, 0 },
};
static command rip_cmd = { NULL, "rip",
			  "Routing Information Protocol", rip_scmd };

/* The initialization and timer startup of the protocol. */
RIP()
{
    void rip_req_brd(), rip_brd_tbl();

    opcon_register(&rip_cmd);
    log_register_flag ("rip", &MSGFLG);

    /* Wait for the world to settle and broadcast a request for routing
     * information. */
    stime(rip_req_brd, NULL, 1, 0110);
    
    /* Now enter timer loop. */
    itime(rip_brd_tbl, BRD_TIMER, TIMER_RATE, RIPBRDPRI);
}

/* This routine gets called each time the timer fires and when the
 * routing tables are modified.  It broadcasts the current routing
 * tables on all connected nets that want it.  If flag is BRD_UPDATE
 * then only send those routes which have changed since the last
 * broadcast.
 */
rip_brd_tbl(flag)
int flag;
{
    net		*netp;
    inia	*inina;

    niflog(L_TRCO) {
	if (flag == BRD_UPDATE)
	  dolog(ripbrdupd);
	else
	  dolog(ripbrdtbl);
    }

    for (netp = nets; netp < lstnet; netp++)
      for (inina = iniatlst[netp->n_net]; inina; inina = inina->inia_link) {
	  if ((inina->inia_flags & IF_RIP) == 0)
	    continue;
	  rip_snd_tbl(inina->inia_addr, ANYHOST, netp, 0, flag);
      }
    rip_brd_qd = 0;
}

/* Sends a copy of the routing table to the specified dst and net.  If
 * the dst is ANYHOST, the packet is broadcast on that net.  src is
 * used to decide what (if any) subnet information should be released
 * and in a later routine is used to set the source address and
 * destination address if it's to be broadcast.  If gwinfo is set then
 * the next hop gateway is included.
 */
rip_snd_tbl(src, dst, netp, gwinfo, update)
inaddr	*src;
inaddr	*dst;
net	*netp;
intf	gwinfo;
int	update;
{
    reg	iorb	*iob;
    int		send_default;
    struct rip	*pkt;
    reg struct netinfo	*ptr;
    struct netinfo	*end;
    inaddr	srcnet;
    iprte	*ipr;
    iprte	**iprp;
    iprte	fake_ipr;
    unsl	age;
    unsl        metric;
    ext	iorb	*get_rip_pkt();
    ext int     DIRECT_ROUTE_RIP_METRIC;

    srcnet.i_long = src->i_long;
    srcnet.i_long &= net_mask(src);
    niflog(RL_TDUMP) {
	if (dst == ANYHOST)
	  dolog("RIP snd %d.%d.%d.%d -> BRD\n", mksina(src));
	else
	  dolog("RIP snd %d.%d.%d.%d -> %d.%d.%d.%d\n", mksina(src), mksina(dst));
    }
    iob = NULL;
    send_default = 0;
    for (iprp = &ip_routes[0]; iprp < &ip_routes[HASH_SIZE]; iprp++)
      for (ipr = *iprp; ipr != NULL; ipr = ipr->ipr_link) {
	  if ((ipr->ipr_type == T_NONE) &&
	      (srcnet.i_long == (ipr->ipr_addr.i_long & net_mask(&ipr->ipr_addr))))
	    continue;
	  if (is_subnet(ipr) &&
	      (srcnet.i_long != (ipr->ipr_addr.i_long & net_mask(&ipr->ipr_addr))))
	    continue;
	  if ((update) && !(ipr->ipr_flags & F_MODIFIED))
	    continue;

	  /* Don't advertise point to point host routes */
	  if (ipr->ipr_type == T_DIR &&
	      (iniatlst[ipr->ipr_netp->n_net]->inia_flags & IF_PPP_PROXY) &&
	      (ipr->ipr_addr.i_long == ipr->ipr_gw.i_long))
	    continue;

	  /* Are we administratively prohibited from announcing this
	     network? */
	  if (!res_advertise_route(F_RES_RIP, ipr->ipr_addr.i_long, 
				   netp->n_net, &metric)) {
	    niflog (L_INFC)
	      dolog ("RIP not advertising restricted net %d.%d.%d.%d (net %d).\n",
		     mksina (&(ipr->ipr_addr)), netp->n_net);
	    continue;
	  }

	  if (iob == NULL) {
	      if ((iob = get_rip_pkt()) == NULL)
		return;
	      pkt = (struct rip *)(iob->i_addr +
				   sizeof(inpkt) + sizeof(udppkt));
	      pkt->rip_cmd = gwinfo ? RIPCMD_RSPGW : RIPCMD_RESPONSE;
	      pkt->rip_vers = RIPVERSION;
	      ptr = pkt->rip_nets;
	      end = ((struct netinfo *)((byte *)pkt + MAXPACKETSIZE)) - 1;
	  }

	  /* Insert a `default' route of myself.  This is a crock to
	   * keep the Athena VAXen happy.  I suppose it's not all bad,
	   * but there really should be a real protocol to allow hosts
	   * to find a gateway. */
	  if (send_default == 0) {
	      send_default++;
	      ptr->rip_dst.sa_family = swab(AF_INET);
	      ptr->rip_dst.sa_addr.i_long = 0;
		    
	      /* The rest of the sockaddr has to be 0 to get around
	       * braindamage in the 4.2 BSD implementation. */
	      ptr->rip_dst.sa_port = 0;
	      ptr->rip_dst.sa_gw.i_long = 0;
	      ptr->rip_dst.sa_pad = 0;
	      
	      ptr->rip_metric = HOPCNT_INFINITY - 1;
	      ptr->rip_1unused = 0;
	      ptr->rip_2unused = 0;
	      niflog(RL_TDUMP)
		dolog("\t%3d: %d.%d.%d.%d\n", ptr->rip_metric, mksina(&ptr->rip_dst.sa_addr));
	      ptr++;
	  }		    

	  /* Update the route if it's owned by RIP */
	  switch (ipr->ipr_type) {
	  case T_NONE:
	  case T_DIR:
	      ptr->rip_metric = DIRECT_ROUTE_RIP_METRIC;
	      break;

	  case T_FLT:
	      if (ipr->ipr_owner != O_RIP)
		continue;

	      if (!update)
		ipr->ipr_flags &= ~F_MODIFIED;
	      age = systod - ipr->ipr_time;
	      if (age >= GARBAGE_TIME) {
		  fake_ipr.ipr_link = ipr->ipr_link;
		  niflog(L_TRCO)
		    dolog("RIP %d.%d.%d.%d deleted\n", mksina(&ipr->ipr_addr));
		  ipr_delete(ipr);
		  ipr = &fake_ipr;
		  continue;
	      }
	      else if (age >= EXPIRE_TIME)
		ipr->ipr_metric = HOPCNT_INFINITY;

	      /* Split horizon ... */
	      if (ipr->ipr_netp == netp) {
		  /* with poisoned reverse? */
		  if (iniatlst[netp->n_net]->inia_flags & IF_RIP_PR)
		    ptr->rip_metric = HOPCNT_INFINITY;
		else
		  continue;
	      }
	      else
		ptr->rip_metric = ipr->ipr_metric < HOPCNT_INFINITY ?
	      	  		  ipr->ipr_metric + 1 : HOPCNT_INFINITY;
	      break;
	  case T_FIX:
	      if (ipr->ipr_netp == netp)
		continue;
	      ptr->rip_metric = ipr->ipr_metric < HOPCNT_INFINITY ?
	      			ipr->ipr_metric + 1 : HOPCNT_INFINITY;
	      break;
	  default:
	      bughalt("RIP bad type in route");
	  }

	  /* do we have fixed metric from routing restrictions? */
	  if (metric != 0) ptr->rip_metric = metric;  

	  ptr->rip_dst.sa_family = swab(AF_INET);
	  ptr->rip_dst.sa_addr.i_long = ipr->ipr_addr.i_long;
	  niflog(RL_TDUMP)
	    dolog("\t%3d: %d.%d.%d.%d\n", ptr->rip_metric, mksina(&ptr->rip_dst.sa_addr));
	  
	  /* The rest of the sockaddr has to be 0 to get around
	   * braindamage in the 4.2 BSD implementation. */
	  ptr->rip_dst.sa_port = 0;
	  if (gwinfo)
	    ptr->rip_dst.sa_gw.i_long = ipr->ipr_gw.i_long;
	  else
	    ptr->rip_dst.sa_gw.i_long = 0;
	  ptr->rip_dst.sa_pad = 0;
	  ptr->rip_1unused = 0;
	  ptr->rip_2unused = 0;
	  ptr++;
	  if (ptr > end) {
	      rip_send(iob, src, dst, netp, (byte *)ptr - (byte *)pkt);
	      iob = NULL;
	  }
      }
    if (iob != NULL) {
	rip_send(iob, src, dst, netp, (byte *)ptr - (byte *)pkt);
	iob = NULL;
    }
}

/* Allocate a packet, setting i_addr correctly. */
iorb	*get_rip_pkt()
{
    reg	iorb	*iob;

    if ((iob = getbuf()) == NULL) {
	niflog(L_ERRU)
	  dolog("RIP: couldn't alloc pkt\n");
	return iob;
    }
    iob->i_addr = ((byte *)iob) + pktoff;
    return iob;
}

/* Fills in the internet and udp headers of a rip packet and then sends
 * the packet off.
 */
rip_send(iob, src, dst, netp, len)
reg	iorb	*iob;
inaddr	*src;
reg	inaddr	*dst;
net	*netp;
int	len;
{
    int		ret_code;
    reg	udppkt	*upkt;
    inaddr	adr;
    inaddr	*idst;
    
    upkt = mkudp(iob->i_addr + sizeof(inpkt));
    upkt->u_src = swab(RIPUDPSOCK);
    upkt->u_dst = swab(RIPUDPSOCK);
    upkt->u_len = swab(len + sizeof(udppkt));
    upkt->u_chk = 0;	/* filled in after the internet header is done */
    
    /* Set the destination address to the broadcast address for that subnet. */
    if (dst == ANYHOST) {
	reg unsl mask;
	iprte *ipr;

	mask = net_mask(src);
	ipr = ipr_lookup(src->i_long & mask);
	mask |= ipr->ipr_subnet_mask.i_long;
	adr.i_long = INLNBRD;
	adr.i_long &= ~mask;
	adr.i_long |= mask & src->i_long;
	idst = &adr;
    }
    else
      idst = dst;
    
    mkinpkt(iob, src, idst, INUDP, len + sizeof(udppkt));
    upkt->u_chk = udp_cksum(iob->i_addr);
    
    if (dst == ANYHOST) {
	if ((ret_code = (*netp->n_send)(iob, netp, P_IN, dst)) != D_OK) {
	    niflog(L_ERRU)
	      dolog("RIP err %d snding pkt to net %d\n",
		    ret_code, netp->n_net);
	    freebuf(iob);
	}
    }
    else
      inxaddq(iob);
}

/* Broadcast a request for routing information to all connected internet nets.
 */
rip_req_brd()
{
    reg	int	i;
    net		*netp;
    inia	*inina;
    reg	iorb	*iob;
    reg	struct rip	*pkt;

    niflog(L_TRCO)
      dolog(ripbrdreq);

    for (netp = nets; netp < lstnet; netp++)
      for (inina = iniatlst[netp->n_net]; inina; inina = inina->inia_link) {
	  if (inina->inia_flags & IF_RIP == 0)
	    continue;

	  if ((iob = get_rip_pkt()) == NULL)
	    return;
	  pkt = (struct rip *)(iob->i_addr +
			       sizeof(inpkt) + sizeof(udppkt));
	  pkt->rip_cmd = RIPCMD_REQUEST;
	  pkt->rip_vers = RIPVERSION;
	  pkt->rip_nets[0].rip_1unused = 0;
	  pkt->rip_nets[0].rip_2unused = 0;
	  pkt->rip_nets[0].rip_metric = HOPCNT_INFINITY;
	  for (i = 0; i < sizeof(struct sockaddr); i++)
	    ((char *)(&pkt->rip_nets[0].rip_dst))[i] = 0;
	  rip_send(iob, inina, ANYHOST, netp, sizeof(struct rip));
      }
}

/* Parses a received RIP packet and updates the routing tables accordingly.
 */
rip_in(iob)
iorb	*iob;
{
    reg	struct netinfo	*data;
    reg	struct rip	*pkt;
    reg iprte		*ipr;
    inpkt		*ipkt;
    udppkt		*upkt;
    struct netinfo	*end;
    inaddr		*iaddr;	/* address of the gateway that sent this
				 * packet */
    net		*netp;
    int		update_flag;
    inia	*inina;
    int         metric;

    update_flag = 0;
    ipkt = mkin(iob->i_addr);
    netp = mknet(iob->i_usr2);
    iaddr = &ipkt->i_src;

    /* If this packet came from me or from a net with an address
     * marked to ignore RIP packets then ignore it. */
    inina = iniatlst[netp->n_net];
    while (inina != NULL) {
	if (iaddr->i_long == mkina(inina->inia_addr)->i_long ||
	    ((inina->inia_flags & IF_RIP_IGN) != 0)) {
		freebuf(iob);
		return;
	    }
	inina = inina->inia_link;
    }

    pkt = (struct rip *)((byte *)ipkt + sizeof(inpkt) + sizeof(udppkt));
    if (pkt->rip_vers != RIPVERSION) {
	niflog(L_ERRU)
	  dolog(ripbdver, pkt->rip_vers, mksina(iaddr));
	freebuf(iob);
	return;
    }

    switch (pkt->rip_cmd) {
    case RIPCMD_REQUEST:
	niflog(L_TRCO)
	  dolog(ripreq, mksina(iaddr));
	rip_snd_tbl(iniatlst[netp->n_net]->inia_addr, iaddr, netp, 0);
	break;

    case RIPCMD_TRACEON:
	niflog(L_ERRU)
	  dolog(riptron, mksina(iaddr));
	break;
	
    case RIPCMD_TRACEOFF:
	niflog(L_ERRU)
	  dolog(riptroff, mksina(iaddr));
	break;

    case RIPCMD_REQGW:
	niflog(L_TRCO)
	  dolog(ripgwreq, mksina(iaddr));
	rip_snd_tbl(iniatlst[netp->n_net]->inia_addr, iaddr, netp, 1);
	break;
	
    default:
	niflog(L_ERRU)
	  dolog(ripbdcmd, pkt->rip_cmd, mksina(iaddr));
	break;

    case RIPCMD_RESPONSE:
	niflog(L_TRCO)
	  dolog(riprsp, mksina(iaddr));

	upkt = mkudp((byte *)ipkt + sizeof(inpkt));
	end = (struct netinfo *)
	  ((byte *)pkt + swab(upkt->u_len) - sizeof(udppkt));

	for (data = pkt->rip_nets; data < end; data++) {
	    if (swab(data->rip_dst.sa_family) != AF_INET)
	      continue;
	    niflog(RL_RDUMP)
	      dolog("\n\t%d.%d.%d.%d: %d",
		    mksina(&data->rip_dst.sa_addr),
		    data->rip_metric);
	    
	    /* Ignore default route */
	    if (data->rip_dst.sa_addr.i_long == 0)
	      continue;

	    /* Protect against broken implementations */
	    if (data->rip_metric > HOPCNT_INFINITY ||
		data->rip_metric <= 0)
	      data->rip_metric = HOPCNT_INFINITY;

	    /* Check routing restrictions first ... if we don't want
	       to hear about this route no need to do anything more ... */
	    if (res_ignore_route (F_RES_RIP,
				  data->rip_dst.sa_addr.i_long, 
				  netp->n_net,
				  &metric)) {
	      niflog (L_INFC)
		dolog ("RIP ingoring admin prohibited route to %d.%d.%d.%d from %d.%d.%d.%d (net %d).\n",
		       mksina (&(data->rip_dst.sa_addr.i_long)),
		       mksina (iaddr), netp->n_net);
	      continue;
	    }

	    /* if the routing restriction had a metric we use that, both
	       for the decision whether or not to take this update and
	       for future advertising ... this is dangerous, but useful */
	    if ((metric != 0 && (data->rip_metric != HOPCNT_INFINITY)))
	      data->rip_metric = metric;

	    /* Get the routing entry.  If it's a deleted route, then
	     * don't create a routing entry if one doesn't already
	     * exist. */
	    if (data->rip_metric < HOPCNT_INFINITY) {
		ipr = ipr_make(data->rip_dst.sa_addr.i_long);
		if (ipr->ipr_type == T_NONE) {
		    ipr->ipr_metric = HOPCNT_INFINITY;
		    ipr->ipr_type = T_FLT;
		    ipr->ipr_owner = O_RIP;
		}
		else
		  if (ipr->ipr_metric == HOPCNT_INFINITY)
		    continue;
	    }
	    else
	      if ((ipr = ipr_lookup(data->rip_dst.sa_addr.i_long)) == NULL ||
		  ipr->ipr_netp == NULL)
		continue;
	    
	    /* If the route to this net isn't managed by RIP, then skip it. */
	    if (ipr->ipr_owner != O_RIP || ipr->ipr_type != T_FLT)
	      continue;

	    /* If someone is sending out routes for subnets of a net
	     * that I don't know about, ignore them.  I had code here
	     * to try and keep the least cost subnet as the cost for
	     * the whole net, but that had problems that I couldn't
	     * get around. */
	    if (ipr->ipr_addr.i_long != data->rip_dst.sa_addr.i_long)
	      continue;

	    /* If the target destination is 0.0.0.0 punt.  There's something
	       rotten going on here ... */
	    if (iaddr->i_long == 0) {
	      niflog (L_ERRU) 
		dolog ("RIP Route for %d.%d.%d.%d through 0.0.0.0! ign.\n",
		       mksina (&ipr->ipr_addr.i_long));
	      continue;
	    }

	    /* The route is updated if:
	     * (1) There is no entry for that net and the hopcount given
	     *     in the routing packet is less than infinite.
	     * (2) The gateway in the routing table is the same as the source
	     *     of this routing packet.
	     * (3) The entry is older than UPDATE_TIME and the new route is
	     *     at least as good as the old one.
	     * (4) The new route is better than the old one.
	     */
	    if ((ipr->ipr_netp == NULL) ||
		(ipr->ipr_gw.i_long == iaddr->i_long) ||
		(systod - ipr->ipr_time >= UPDATE_TIME &&
		 data->rip_metric <= ipr->ipr_metric) ||
		(data->rip_metric < ipr->ipr_metric)) {
		    /* Don't update if setting an infinite
		     * route to infinity. */
		    if (data->rip_metric == HOPCNT_INFINITY &&
			ipr->ipr_metric == HOPCNT_INFINITY)
		      continue;
		    ipr->ipr_time = systod;
		    if ((ipr->ipr_netp != netp) ||
			(ipr->ipr_metric != data->rip_metric)) {
			    niflog(RL_RUPD)
dolog("RIP Rte. to %d.%d.%d.%d\tgw: %d.%d.%d.%d cst: %d ==>\n\
\t\t\tgw: %d.%d.%d.%d cst:%d\n", mksina(&ipr->ipr_addr.i_long),
      mksina(&ipr->ipr_gw.i_long), ipr->ipr_metric,
      mksina(&iaddr->i_long), data->rip_metric);
			    ipr->ipr_netp = netp;
			    ipr->ipr_gw.i_long = iaddr->i_long;
			    ipr->ipr_metric = data->rip_metric;
			    ipr->ipr_flags |= F_MODIFIED;
			    ipr->ipr_src_gw.i_long = iaddr->i_long;
			    /* this should be don't better! but how? */
			    ipr->ipr_autonomous_system = 0;
			    update_flag = 1;
			}
		}
	}

	/* If the table was updated tell the world about it so changes
	 * propagate quickly. A flag is set so if there is
	 * already a broadcast queued, we don't queue up another one. */
	if (update_flag && !rip_brd_qd) {
	    rip_brd_qd = 1;
	    addtsk(syshnd, RIPBRDPRI, rip_brd_tbl, BRD_UPDATE);
	    /* There is a delay before doing the broadcast to try and
	     * keep the broadcast rate down to a more manageable
	     * level.  I really don't like this patch to the routing
	     * wars problem.  It means that routing loops stay around
	     * for a much longer time than I like. */
/*	    stime(rip_brd_tbl, BRD_UPDATE, RIP_BRD_DELAY, RIPBRDPRI); */
	}
	break;
    }
    freebuf(iob);
}

/* RIP control for the command interpreter.  Allows an operator to
 * enable or disable RIP listening or sending on each net
 * individually. */
rip_ctl(devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
    reg inia *inina;
    int netn;
    int ign;

    if (arg == -1 || jcl == NULL) {
	for (netn = 0; netn < nnets; netn++) {
	    ign = 0;
	    for (inina = iniatlst[netn];
		 inina != NULL;
		 inina = inina->inia_link)
	      if (inina->inia_flags & IF_RIP_IGN) {
		  ign++;
		  break;
	      }
	    fprintf(devp, "Net %3d %s %s %s\n", netn,
		    iniatlst[netn]->inia_flags & IF_RIP ? " snd" : "-snd",
		    ign ? "-rcv" : " rcv",
		    iniatlst[netn]->inia_flags & IF_RIP_PR ? "pr" : "-pr");
	}
	return;
    }

    if (!good_password) {
	fprintf(devp, "Not allowed\n");
	return;
    }

    if ((netn = atoi(jcl)) >= nnets) {
	fprintf(devp, "Net %d, too large\n", netn);
	return;
    }
    
    switch (arg) {
    case 0:			/* Enable receive */
	iniatlst[netn]->inia_flags &= ~IF_RIP_IGN;
	break;
    case 1:			/* Disable receive */
	iniatlst[netn]->inia_flags |= IF_RIP_IGN;
	break;
    case 2:			/* Enable send */
	iniatlst[netn]->inia_flags |= IF_RIP;
	break;
    case 3:                     /* Disable send */
	iniatlst[netn]->inia_flags &= ~IF_RIP;
	break;
    case 4:                     /* Enable poisoned reverse */
        iniatlst[netn]->inia_flags |=  IF_RIP_PR;
	break;
    case 5:                     /* Disable poisoned reverse */
       iniatlst[netn]->inia_flags &=  ~IF_RIP_PR;
       break;
    default:
	fprintf(devp, "Error!:  unknown arg %d to rip_cmd\n", arg);
	break;
    }
}
