/*
 * $Id: rip.c,v 1.1 1995/01/05 09:14:58 root Exp root $
 */
#include "ipxroute.h"

IPXRouteEnt *add_local_route(char *dev,int frame,unsigned long net,IPXRouteEnt *p) {
  int skfd = socket(AF_IPX,SOCK_DGRAM,0);
  struct ipx_route_def rt;

  if (skfd < 0) { sysMsg(MSG_WARN,ENOENT,dev); return p; }
  strcpy(rt.ipx_device,dev);
  rt.ipx_network = htonl(net);
  rt.ipx_flags = frame;
  rt.ipx_router_network = IPX_ROUTE_NO_ROUTER;
  memset(rt.ipx_router_node,0,sizeof rt.ipx_router_node);
  if (verbose)
    sysMsg(MSG_WARN,ERR_OK,"Adding local route: %s %08x %x",
	   rt.ipx_device,net,rt.ipx_flags);

  if (ioctl(skfd,SIOCADDRT,&rt) < 0)
    sysMsg(MSG_WARN,ERR_ERRNO,"ipxroute-add");
  else {
    IPXRouteEnt *old = p;
    /* Ok, everything succeeded, lets create a new entry */
    p = (IPXRouteEnt *)malloc(sizeof(IPXRouteEnt));
    if (!p) {
      sysMsg(MSG_WARN,ENOMEM,"malloc");
      ioctl(skfd,SIOCDELRT,&rt);
      close(skfd);
      return old;
    }
    p->network = net;
    p->rnetwork = IPX_ROUTE_NO_ROUTER;
    memset(p->rnode,0,sizeof p->rnode);
    p->hops = 1;
    p->ticks = 1;
    p->age = 0;
    p->frame = frame;
    p->next = old;
  }
  close(skfd);
  return p;
}

void down_route(IPXRouteEnt *p) {
  int skfd = socket(AF_IPX,SOCK_DGRAM,0);
  struct ipx_route_def rt;

  if (skfd < 0) { sysMsg(MSG_WARN,ERR_ERRNO,"socket"); return; }
  memset(rt.ipx_device,0,sizeof rt.ipx_device);
  rt.ipx_network = htonl(p->network);
  rt.ipx_flags = p->frame;
  rt.ipx_router_network = htonl(p->rnetwork);
  memcpy(rt.ipx_router_node,p->rnode,sizeof rt.ipx_router_node);
  if (verbose)
    sysMsg(MSG_WARN,ERR_OK,"Removing route: %08X %X", p->network,
	   rt.ipx_flags);

  if (ioctl(skfd,SIOCDELRT,&rt)<0)  sysMsg(MSG_WARN,ERR_ERRNO,"ipxroute-del");
  close(skfd);
}

static void do_write_ipx(struct sockaddr_ipx *skp,unsigned char iobuf[],int size) {
  struct sockaddr_ipx sk;
  int i,sock = socket(AF_IPX,SOCK_DGRAM,0);
  if (sock < 0) sysMsg(MSG_FATAL,ERR_ERRNO,"sock-out");
  sk = *skp;
  sk.sipx_port = htons(IPXS_DYNAMIC);
  i = bind(sock,(struct sockaddr *)&sk,sizeof sk);
  if (i < 0) sysMsg(MSG_FATAL,ERR_ERRNO,"bind");


  if (sendto(sock,iobuf,size,0,(struct sockaddr *)skp,sizeof *skp) < 0)
    sysMsg(MSG_FATAL,ERR_ERRNO,"send-to");
  close (sock);
}

void write_ipx(struct sockaddr_ipx *skp,unsigned char iobuf[],int size) {
  if (skp->sipx_network)
    do_write_ipx(skp,iobuf,size);
  else {
    /* We don't know the destination network, so we output on all nets... */
    IPXRouteEnt *rte = net_table;
    while (rte) {
      skp->sipx_network = htonl(rte->network);
      do_write_ipx(skp,iobuf,size);
      rte = rte->next;
    }
  }
}

void broadcast_ipx(unsigned char iobuf[],unsigned long network,
		   unsigned port,int type,int size) {
  struct sockaddr_ipx sk;
  long soopt;
  int sock = socket(AF_IPX,SOCK_DGRAM,0);
  if (sock < 0) sysMsg(MSG_FATAL,ERR_ERRNO,"sock-out");

  sk.sipx_family = AF_IPX;
  sk.sipx_network = htonl(network);
  memset(sk.sipx_node,0xff,sizeof sk.sipx_node);
  sk.sipx_port = htons(IPXS_DYNAMIC);
  sk.sipx_type = type;

  soopt = bind(sock,(struct sockaddr *)&sk,sizeof sk);
  if (soopt < 0) sysMsg(MSG_FATAL,ERR_ERRNO,"bind");
  soopt = TRUE;
  /*
   * Set the sock options
   */
  if ( setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&soopt,sizeof soopt) < 0)
    sysMsg(MSG_FATAL,ERR_ERRNO,"setsockopt");

  sk.sipx_family = AF_IPX;	/* Set IPX dest socket */
  sk.sipx_network = htonl(network);
  sk.sipx_port = htons(port);
  sk.sipx_type = type;
  memset(sk.sipx_node,0xff,sizeof sk.sipx_node);

  if (sendto(sock,iobuf,size,0,(struct sockaddr *)&sk,sizeof sk) < 0)
    sysMsg(MSG_FATAL,ERR_ERRNO,"sendto");
  close (sock);
}

void send_ripinfo(IPXRouteEnt *rtes,unsigned long net) {
  unsigned char iobuf[MAXNETBUF];
  IPXRipEnt *riptab = (IPXRipEnt *)(&iobuf[2]);
  int i=0;
  iobuf[0] = 0; iobuf[1] = IPX_RESP;	/* Header info */
  while (rtes) {
    if (rtes->network != net && rtes->rnetwork != net) {
      riptab[i].network = htonl(rtes->network);
      riptab[i].hops = htons(rtes->hops);
      riptab[i].ticks = htons(rtes->ticks);
      if (++i >= MAX_IPX_INFO) {
	broadcast_ipx(iobuf,net,IPXS_RIP,IPXT_RIP, (sizeof *riptab)*i+2);
	i = 0;
      }
    }
    rtes = rtes->next;
  }
  if (i) broadcast_ipx(iobuf,net,IPXS_RIP,IPXT_RIP, (sizeof *riptab)*i+2);
}

void broadcast_rip(IPXRouteEnt *rtes) {
  IPXRouteEnt *nets = net_table;
  while (nets) {
    send_ripinfo(rtes,nets->network);
    nets = nets->next;
  }
}

/*
 * Open socket
 */
int opensock(unsigned int port,int type) {
  struct sockaddr_ipx sk;
  long soopt=TRUE;
  int sock=socket(AF_IPX,SOCK_DGRAM,0);
  if (sock < 0) sysMsg(MSG_FATAL,ERR_ERRNO,"socket");

  memset(&sk,0,sizeof sk);
  sk.sipx_family = AF_IPX;
  sk.sipx_port = htons(port);
  sk.sipx_type = type;

  if (bind(sock,(struct sockaddr *)&sk,sizeof sk) < 0)
    sysMsg(MSG_FATAL,ERR_ERRNO,"bind");
  /*
   * Set the sock options
   */
  if ( setsockopt(sock,SOL_SOCKET,SO_BROADCAST,&soopt,sizeof soopt) < 0)
    sysMsg(MSG_FATAL,ERR_ERRNO,"setsockopt");
  return sock;
}

IPXRouteEnt *findRoute(unsigned long net,IPXRouteEnt *p) {
  while (p) {
    if (p->network == net) return p;
    p = p->next;
  }
  return NULL;
}

IPXRouteEnt *delRoute(IPXRouteEnt *todel,IPXRouteEnt *net) {
  IPXRouteEnt *prev = NULL, *p = net;

  while (p) {
    if (todel == p) {
      if (prev) {
	prev->next = p->next;
	return net;
      }
      else
	return p->next;
    }
    prev = p;
    p = p->next;
  }
  return net;
}

IPXRouteEnt *new_route(IPXRouteEnt *net,unsigned long rnet,int hops,int ticks,
		       unsigned char *node) {
  int skfd = socket(AF_IPX,SOCK_DGRAM,0);
  struct ipx_route_def rt;
  IPXRouteEnt *p = NULL;

  if (skfd < 0) { sysMsg(MSG_WARN,ERR_ERRNO,"socket"); return NULL; }
  memset(&rt,0,sizeof rt);
  rt.ipx_network = htonl(rnet);
  rt.ipx_flags = net->frame | IPX_RT_ROUTED;
  rt.ipx_router_network = htonl(net->network);
  memcpy(rt.ipx_router_node,node,sizeof rt.ipx_router_node);
  if (verbose)
    sysMsg(MSG_WARN,ERR_OK,"Adding remote route: %08x %x %08x:%s",
	   rnet,rt.ipx_flags,net->network,ipx_ntoa(node));

  if (ioctl(skfd,SIOCADDRT,&rt) < 0) {
    sysMsg(MSG_WARN,ERR_ERRNO,"ipxroute-add");
    close(skfd);
    return NULL;
  }
  p = (IPXRouteEnt *)malloc(sizeof(IPXRouteEnt));
  if (!p) {
    sysMsg(MSG_WARN,ENOMEM,"addrte");
    ioctl(skfd,SIOCDELRT,&rt);
    close(skfd);
    return NULL;
  }
  p->network = rnet;
  p->rnetwork = net->network;
  memcpy(p->rnode,node,sizeof p->rnode);
  p->hops = hops;
  p->ticks = ticks;
  p->age = time(NULL);
  p->frame = net->frame;
  p->next = NULL;
  close(skfd);
  return p;
}


void process_req(struct sockaddr_ipx *sk,IPXRipEnt *p,int k,IPXRouteEnt *rtes) {
  unsigned char pkt[MAXNETBUF];
  IPXRipEnt *op = (IPXRipEnt *)&pkt[2];
  int i,j;
  unsigned long srcnet = ntohl(sk->sipx_network);
  int loop;

  pkt[0] = 0; pkt[1] = IPX_RESP;
  if (!k) k = 1;

  for (j=i=0;i<k;i++) {
    IPXRouteEnt *rte = net_table;
    loop = TRUE;

    while (rte) {
      if ((srcnet != rte->network && srcnet != rte->rnetwork &&
	   p[i].network==IPX_GEN_REQ) || ntohl(p[i].network)==rte->network) {
	op[j].network = htonl(rte->network);
	op[j].hops = htons(rte->hops);
	op[j].ticks = htons(rte->ticks);
	if (++j >= MAX_IPX_INFO) {
	  write_ipx(sk,pkt,j*sizeof(IPXRipEnt)+2);
	  j = 0;
	}
      }
      rte = rte->next;
      if (loop && !rte) {
	loop = FALSE;
	rte = rtes;
      }
    }
  }
  if (j) write_ipx(sk,pkt,j*sizeof(IPXRipEnt)+2);
}

IPXRouteEnt *read_rip(int sock,IPXRouteEnt *rtes) {
  unsigned char pkt[MAXNETBUF];
  struct sockaddr_ipx sk;
  int frlen = sizeof sk;
  IPXRouteEnt *modRtes = NULL;
  int doreq = FALSE;

  int read_ret=recvfrom(sock,pkt,sizeof pkt,0,(struct sockaddr *)&sk,&frlen);

  if (read_ret < 0) { sysMsg(MSG_WARN,ERR_ERRNO,"recv-rip"); return rtes; }

  if (read_ret > 0) {
    int pktype = pkt[0] * 256 + pkt[1], i = 2;
    IPXRouteEnt *netinfo = findRoute(ntohl(sk.sipx_network),net_table);

    switch (pktype) {
    case IPX_RESP:
      if (!netinfo) return rtes;
      while (i < read_ret) {
	IPXRipEnt *rip = (IPXRipEnt *)(pkt+i);
	unsigned long network =ntohl(rip->network);
	unsigned short hops = ntohs(rip->hops)+1;
	unsigned short ticks = ntohs(rip->ticks)+1;
	IPXRouteEnt *rp = findRoute(network,net_table);

	i += sizeof(IPXRipEnt);

	if (rp) return rtes;
	rp = findRoute(network,rtes);
	if (rp) {	/* A route to that network exists already */
	  if ( ntohl(sk.sipx_network) == rp->rnetwork &&
	      !memcmp(sk.sipx_node,rp->rnode,sizeof sk.sipx_node)) {
	    /* Ok, this is info about the current route */
	    if (hops >= IPX_HOP_LIMIT) {	/* We wanna delete it... */
	      rtes = delRoute(rp,rtes);
	      rp->next = modRtes;
	      rp->hops = IPX_HOP_LIMIT;
	      modRtes = rp;
	      doreq = TRUE;
	      down_route(rp);
	    }
	    else {
	      rp->hops = hops+1;	/* Refresh route */
	      rp->ticks = ticks+1;
	      rp->age = time(NULL);
	    }
	    continue;
	  }
	  /* Hey a better route! */
	  else if (ticks+1 < rp->ticks || 
		   (ticks+1 == rp->ticks && hops+1 < rp->hops)) {
	    rtes = delRoute(rp,rtes);
	    down_route(rp);
	    free(rp);
	    /* We just get rid of the entry and add it as new... */
	  }
	  else continue;	/* Useless broadcast for us...*/
	}
	else if (hops >= IPX_HOP_LIMIT) continue;

	rp = new_route(netinfo,network,hops,ticks,sk.sipx_node);
	if (rp) {	/* Created a new route... */
	  rp->next = rtes;
	  rtes = rp;
	  rp = (IPXRouteEnt *)malloc(sizeof(IPXRouteEnt));
	  if (rp) {	/* Add it to the modification table */
	    *rp = *rtes;
	    rp->next = modRtes;
	    modRtes = rp;
	  }
	}
      }
      if (modRtes) {
	broadcast_rip(modRtes);	/* Broadcast changes */
	/* and delete the modified routes table */
	for (netinfo = modRtes; netinfo ; netinfo = modRtes) {
	  modRtes = netinfo->next;
	  free(netinfo);
	}
      }
      break;
    case IPX_REQ:
      process_req(&sk,(IPXRipEnt *)&pkt[2],(read_ret-2)/sizeof(IPXRipEnt),rtes);
      break;
    default:
      sysMsg(MSG_WARN,EINVAL,"Unrecognized IPX/RIP packet: %04x",pktype);
    }
  }
  if (doreq) {
    alarm(1);
    signal(SIGALRM,general_request);
  }
  dump_ipxtab(rtes);
  return rtes;
}

IPXRouteEnt *age_rip(IPXRouteEnt *rtes) {
  unsigned long now = time(NULL);
  IPXRouteEnt *rmved = NULL, *p = rtes;

  while (p) {
    if (now - p->age > IPX_AGE_LIMIT) {
      IPXRouteEnt *rp = p;
      p = p->next;
      rtes = delRoute(rp,rtes);
      down_route(rp);
      rp->next = rmved;
      rp->hops = IPX_HOP_LIMIT;
      rmved = rp;
    }
    else p = p->next;
  }
  if (rmved) {
    IPXRouteEnt *next;
    broadcast_rip(rmved);
    /* and free routes table */
    for (p = rmved; p ; p = next) {
      next = p->next;
      free(p);
    }
    alarm(1);
    signal(SIGALRM,general_request);
  }
  dump_ipxtab(rtes);
  return rtes;
}

/* Dump IPX table... */
void dump_ipx(IPXRouteEnt *p, FILE *fp) {
  while (p) {
    char rte[23], *tm, *zm;
    if (p->rnetwork) {
      zm = rte;
      sprintf(rte,"%08lX:%s",p->rnetwork,ipx_ntoa(p->rnode));
    }
    else  zm = "(local)";

    tm = p->age ? ctime(&p->age) : "(static)\n";
    fprintf(fp,"%08lX %-21s %4d %4d %s", p->network, zm, p->hops,p->ticks,tm);
    p = p->next;
  }
}

void dump_ipxtab(IPXRouteEnt *rtes) {
  FILE *fp = fopen(IPXDUMP_FILE,"w");
  if (!fp) return;

  fprintf(fp,"IPXRIPD PID=%d\n",getpid());
  fputs("Network  Router                Hops Tcks Age\n",fp);
  fputs("======== ===================== ==== ==== ====================\n",fp);
  dump_ipx(net_table,fp);
  dump_ipx(rtes,fp);
  fclose(fp);
}

