/*
 * route	This file contains an implementation of the command
 *		that manages the IP routing table in the kernel.
 *
 * Usage:	route [-nv] [ {add|del} [{-net|-host}] target
 *		      [ gw ] [ metric ] ]
 *
 * Version:	@(#)route.c	1.41	07/18/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.41 (07/18/93)";


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


static void
usage(void)
{
  fprintf(stderr, "Usage: route [-nv] [ {add|del} [{-net|-host}] target ");
  fprintf(stderr, "[ gw ] [ metric ] ]\n");
  exit(-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) {
	in->s_addr = htonl(np->n_net);
	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 xflag, isnet;

  xflag = 0;
  if (*args == NULL) usage();
  strcpy(target, *args++);
  if (!strcmp(target, "-net")) {
	xflag = 1;
	if (*args == NULL) usage();
	strcpy(target, *args++);
  } else if (!strcmp(target, "-host")) {
	xflag = 2;
	if (*args == NULL) usage();
	strcpy(target, *args++);
  }

  memset((char *) &trg, 0, sizeof(struct sockaddr_in));
  if ((isnet = resolve(target, &trg.sin_addr)) < 0) {
	herror(target);
	return(-1);
  }
  switch(xflag) {
	case 1:
		isnet = 1;
		break;

	case 2:
		isnet = 0;
		break;

	default:
		break;
  }
  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(skfd, 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(skfd, SIOCDELRT, &rt) < 0) {
	fprintf(stderr, "SIOCDELRT: %s\n", strerror(errno));
	return(-1);
  }
  return(0);
}


int
main(int argc, char **argv)
{
  int i;
  char *s;

  /* Fetch the command-line arguments. */
  argv++;
  while ((s = *argv) != NULL) {
	if (*s != '-') break;
	while (*++s != '\0') switch(*s) {
		case 'n':
			opt_n = 1;
			break;

		case 'v':
			opt_v = 1;
			break;

		default:
			usage();
	}
	argv++;
  }

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

  /* Fetch the command. */
  if (strcmp(*argv, "add") && strcmp(*argv, "del")) usage();

  /* Create a socket to the INET kernel. */
  if ((skfd = open(_PATH_DEV_SOCKET, O_RDWR)) < 0) {
	perror("socket");
	exit(-1);
  }

  /* See what we have to do here. */
  if (!strcmp(*argv, "add")) i = rt_add(++argv);
    else i = rt_del(++argv);

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

  return(i);
}
