/*
 * rarp		This file contains an implementation of the command
 *		that maintains the kernel's RARP cache.  It is derived
 *              from Fred N. van Kempen's arp command.
 *
 * Usage:       rarp -d hostname                      Delete entry
 *		rarp -s hostname ethernet_address     Add entry
 *              rarp -a                               Print entries
 *
 * Rewritten: Phil Blundell <Philip.Blundell@pobox.com>  1997-08-03
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>

#include "config.h"
#include "net-locale.h"
#include "net-support.h"
#include "version.h"

#define PROC_NET_RARP		"/proc/net/rarp"
#define NO_RARP_MESSAGE         "This kernel does not support RARP.\n"

static char version_string[] = RELEASE "\nrarp 1.00 (1997-08-03)\n";

static struct hwtype *hardware = NULL;

/* Delete an entry from the ARP cache. */
static int arp_delete(int fd, struct hostent *hp)
{
  struct arpreq req;
  struct sockaddr_in *si;
  unsigned int found = 0;
  char **addr;

  /* The host can have more than one address, so we loop on them. */
  for (addr = hp->h_addr_list; *addr != NULL; addr++) { 
    memset((char *) &req, 0, sizeof(req));
    si = (struct sockaddr_in *) &req.arp_pa;
    si->sin_family = hp->h_addrtype;
    memcpy((char *) &si->sin_addr, *addr, hp->h_length);
    
    /* Call the kernel. */
    if (ioctl(fd, SIOCDRARP, &req) == 0) {
      found++;
    } else {
      switch (errno) {
      case ENXIO:
	break;
      case ENODEV:
	fputs(NO_RARP_MESSAGE, stderr);
	return 1;
      default:
	perror("SIOCDRARP");
	return 1;
      }
    }
  }

  if (found == 0) 
    printf(NLS_CATGETS(catfd, rarpSet, rarp_noentry, 
		       "no RARP entry for %s.\n"), hp->h_name);
  return 0;
}


/* Set an entry in the ARP cache. */
static int arp_set(int fd, struct hostent *hp, char *hw_addr)
{
  struct arpreq req;
  struct sockaddr_in *si;
  struct sockaddr sap;

  if (hardware->input(hw_addr, &sap)) {
    fprintf(stderr, "%s: bad hardware address\n", hw_addr);
    return 1;
  }

  /* Clear and fill in the request block. */
  memset((char *) &req, 0, sizeof(req));
  si = (struct sockaddr_in *) &req.arp_pa;
  si->sin_family = hp->h_addrtype;
  memcpy((char *) &si->sin_addr, hp->h_addr_list[0], hp->h_length);
  req.arp_ha.sa_family = hardware->type;
  memcpy(req.arp_ha.sa_data, sap.sa_data, hardware->alen);

  /* Call the kernel. */
  if (ioctl(fd, SIOCSRARP, &req) < 0) {
    if (errno == ENODEV)
      fputs(NO_RARP_MESSAGE, stderr);
    else
      perror("SIOCSRARP");
    return  1;
  }
  return 0;
}

static int display_cache(void)
{
  FILE *fd = fopen(PROC_NET_RARP, "r");
  char buffer[256];
  if (fd == NULL) {
    if (errno == ENOENT) 
      fputs(NO_RARP_MESSAGE, stderr);
    else
      perror(PROC_NET_RARP);
    return 1;
  }
  while (feof(fd) == 0) {
    if (fgets(buffer, 255, fd))
      fputs(buffer, stdout);
  }
  fclose(fd);
  return 0;
}

static void usage(void)
{
  fprintf(stderr, NLS_CATGETS(catfd, rarpSet, rarp_usage1,
  "Usage: rarp -a                               list entries in cache.\n"));
  fprintf(stderr, NLS_CATGETS(catfd, rarpSet, rarp_usage2,
  "       rarp -d hostname                      delete entry from cache.\n"));
  fprintf(stderr, NLS_CATGETS(catfd, rarpSet, rarp_usage3,
  "       rarp [-t hwtype] -s hostname hwaddr   add entry to cache.\n"));
  fprintf(stderr, NLS_CATGETS(catfd, rarpSet, rarp_usage4,
  "       rarp -V                               display program version.\n"));
  NLS_CATCLOSE(catfd)
  exit(-1);
}

#define MODE_DISPLAY   1
#define MODE_DELETE    2
#define MODE_SET       3

static struct option longopts[] = { 
  { "version", 0, NULL, 'V' },
  { "verbose", 0, NULL, 'v' },
  { "list",    0, NULL, 'a' },
  { "set",     0, NULL, 's' },
  { "delete",  0, NULL, 'd' },
  { "help",    0, NULL, 'h' },
  { NULL,      0, NULL, 0 }
};
  
int main(int argc, char **argv)
{
  int result = 0, mode = 0, c, nargs = 0, verbose = 0;
  char *args[3];
  struct hostent *hp;
  int fd;

#if NLS
  setlocale (LC_MESSAGES, "");
  catfd = catopen ("nettools", MCLoadBySet);
#endif

#if HAVE_HWETHER
  /* Get a default hardware type.  */
  hardware = get_hwtype("ether");
#endif

  do {
    c = getopt_long(argc, argv, "-ht:adsVv", longopts, NULL);
    switch (c) {
    case EOF:
      break;
    case 'h':
      usage();
    case 'V':
      fprintf(stderr, version_string);
      NLS_CATCLOSE(catfd)
	exit(1);
      break;
    case 'v':
      verbose++;
      break;
    case 'a':
    case 's':
    case 'd':
      if (mode) {
	fprintf(stderr, "%s: illegal option mix.\n", argv[0]);
	usage();
      } else {
	mode = (c == 'a'?MODE_DISPLAY:(c == 'd'?MODE_DELETE:MODE_SET));
      }
      break;
    case 't':
      if (optarg) {
	hardware = get_hwtype(optarg);
      } else {
	usage();
      }
      break;
    case 1:
      if (nargs == 2) {
	usage();
	exit(1);
      } else {
	args[nargs++] = optarg;
      }
      break;
    default:
      usage();
    }
  } while (c != EOF);

  if (nargs != (mode-1)) {
    usage();
  }

  if (hardware == NULL) {
    fprintf(stderr, NLS_CATGETS(catfd, rarpSet, rarp_unkn_hw,
				"rarp: %s: unknown hardware type.\n"), optarg);
    NLS_CATCLOSE(catfd)
      exit(1);
  }

  switch (mode) {
  case 0:
    usage();

  case MODE_DISPLAY:
    result = display_cache();
    break;

  case MODE_DELETE:
  case MODE_SET:
    if ((hp = gethostbyname(args[0])) == NULL) {
      fprintf(stderr, NLS_CATGETS(catfd, rarpSet, rarp_unkn_host, 
				  "rarp: %s: unknown host\n"), args[0]);
      NLS_CATCLOSE(catfd)
	exit(1);
    }
    if (fd = socket(PF_INET, SOCK_DGRAM, 0), fd < 0) {
      perror("socket");
      NLS_CATCLOSE(catfd)
	exit (1);
    }
    result = (mode == MODE_DELETE)?arp_delete(fd, hp):arp_set(fd, hp, args[1]);
    close(fd);
  }

  NLS_CATCLOSE(catfd)
    exit(result);
}
