/*
 * route	This file contains an implementation of the command
 *		that manages the IP routing table in the kernel.
 *
 * Usage:	route [-nv] [ {add|del} target iface [ gw ] [ metric ] ]
 *
 * Version:	@(#)route.c	1.04	05/27/93
 *
 * Author:	Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netinet/in.h>
#include <net/if.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pathnames.h"


char *Version = "@(#) route 1.04 (05/27/93)";


int opt_n = 0;				/* numerical output flag	*/
int opt_v = 0;				/* debugging output flag	*/
int sock = -1;


int resolve(char *name, struct in_addr *in)
{
  struct hostent *hp;
  struct netent *np;

  /* Default is special, meaning 0.0.0.0. */
  if (!strcmp(name, "default")) {
	memset((char *) in, 0, sizeof(struct in_addr));
	return(1);
  }

  /* Try the NETWORKS database to see if this is a known network. */
  if ((np = getnetbyname(name)) != (struct netent *)NULL) {
/* KLUDGE: n_net doesn't seem to be set correctly (libc 4.4) */
	if ((in->s_addr = htonl(np->n_net)) == 0xffffffff)
		in->s_addr = inet_addr(np->n_name);
	strcpy(name, np->n_name);
	return(1);
  }

  if (opt_v) {
	_res.options |= RES_DEBUG;
	res_init();
  }

  if ((hp = gethostbyname(name)) == (struct hostent *)NULL) {
	return(-1);
  }
  memcpy((char *) in, (char *) hp->h_addr_list[0], hp->h_length);
  strcpy(name, hp->h_name);
  return(0);
}


static void rt_print(void)
{
  char buff[1024];

  sprintf(buff, "exec %s -r", _PATH_BIN_NETSTAT);
  if (opt_n == 1) strcat(buff, " -n");
  if (opt_v == 1) strcat(buff, " -d");
  (void) system(buff);
}


/* Add a routing table entry. */
int rt_add(char **args)
{
  char target[128], gateway[128];
  struct sockaddr_in trg, gw;
  struct rtentry rt;
  int isnet;

  strcpy(target, *args++);

  memset((char *) &trg, 0, sizeof(struct sockaddr_in));
  if ((isnet = resolve(target, &trg.sin_addr)) < 0) {
	herror(target);
	return(-1);
  }
  trg.sin_family = AF_INET;

  memset((char *) &gw, 0, sizeof(struct sockaddr_in));
  gw.sin_family = AF_INET;

  /* Clean out the RTREQ structure. */
  memset((char *) &rt, 0, sizeof(struct rtentry));
  rt.rt_flags = (RTF_UP | RTF_HOST);
  if (isnet) rt.rt_flags &= ~RTF_HOST;
  memcpy((char *) &rt.rt_dst, (char *) &trg, sizeof(struct sockaddr));

  /* Did we specify a GATEWAY entry? */
  if ((*args != (char *)NULL) && (!strcmp(*args, "gw"))) {
	strcpy(gateway, *++args);
	if ((isnet = resolve(gateway, &gw.sin_addr)) < 0) {
		herror(gateway);
		return(-1);
	}
	if (isnet) {
		fprintf(stderr, "%s: cannot use a NETWORK as gateway!\n",
								gateway);
		return(-1);
	}
	rt.rt_flags |= RTF_GATEWAY;
	args++;
  } else strcpy(gateway, "NONE");
  memcpy((char *) &rt.rt_gateway, (char *) &gw, sizeof(struct sockaddr));

  /* Did we specify a METRIC field? */
  if ((*args != (char *)NULL) && (!strcmp(*args, "metric"))) {
#ifdef	Not_Yet_Used
	rt.rt_metric = atoi(*++args);
#endif
  }

  /* Tell the kernel to accept this route. */
  if (ioctl(sock, SIOCADDRT, &rt) < 0) {
	fprintf(stderr, "SIOCADDRT: %s\n", strerror(errno));
	return(-1);
  }

  return(0);
}


/* Delete a routing table entry. */
int rt_del(char **args)
{
  char target[128];
  struct sockaddr_in trg;
  struct rtentry rt;

  strcpy(target, *args++);

  if (resolve(target, &trg.sin_addr) < 0) {
	herror(target);
	return(-1);
  }
  trg.sin_family = AF_INET;

  /* Clean out the RTREQ structure. */
  memset((char *) &rt, 0, sizeof(struct rtentry));
  memcpy((char *) &rt.rt_dst, (char *) &trg, sizeof(struct sockaddr));

  /* Tell the kernel to delete this route. */
  if (ioctl(sock, SIOCDELRT, &rt) < 0) {
	fprintf(stderr, "SIOCDELRT: %s\n", strerror(errno));
	return(-1);
  }
  return(0);
}


static void usage(void)
{
  fprintf(stderr, "Usage: route [-nv] [ {add|del} target ");
  fprintf(stderr, "[ gw ] [ metric ] ]\n");
  exit(-1);
}


void main(argc, argv)
int argc;
char *argv[];
{
  register int c;
  extern int getopt(), optind, opterr;

  /* Fetch the command-line arguments. */
  opterr = 0;
  while ((c = getopt(argc, argv, "nv")) != EOF) switch(c) {
	case 'n':
		opt_n = 1;
		break;
	case 'v':
		opt_v = 1;
		break;
	default:
		usage();
  }

  /* Do we have to show the contents of the routing table? */
  if (optind == argc) {
	rt_print();
	exit(0);
  }

  /* Fetch the command. */
  if ((optind >= argc) ||
      (strcmp(argv[optind], "add") && strcmp(argv[optind], "del")) ||
      (optind >= (argc - 1))) usage();

  /* Create a socket to the INET kernel. */
  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	perror("socket");
	exit(-1);
  }

  /* See what we have to do here. */
  if (!strcmp(argv[optind++], "add")) {
	c = rt_add(&argv[optind]);
  } else c = rt_del(&argv[optind]);

  /* Close the socket. */
  (void) close(sock);

  exit(c);
}
