/*
 * 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
 *
 *  Ross D. Martin  <martin@trcsun3.eas.asu.edu>
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>

int sockfd;				/* active socket descriptor	*/

/* Input an Ethernet address and convert to binary. */
unsigned char *in_ether(char *ptr)
{
  static unsigned char buff[6];
  unsigned int temp[6];
  int num;

  num=sscanf(ptr,"%x:%x:%x:%x:%x:%x",
	     &temp[0],&temp[1],&temp[2],&temp[3],&temp[4],&temp[5]);
  if(num!=6)
    {
      fprintf(stderr,"Invalid Ethernet address: %s\n",ptr);
    }
  
  for(num=0;num<6;num++)
    buff[num]=temp[num];

  return(buff);
}


/* This structure defines hardware protocols and their handlers. */
struct hwtype {
  char		*name;
  int		type;
  int		hlen;
  unsigned char	*(*input)(char *);
};

struct hwtype hwtypes[] = {
  { "ether",  ARPHRD_ETHER,  6, in_ether  },
#ifdef HAVE_ARPHRD_PRONET
  { "pronet", ARPHRD_PRONET, 2, in_pronet },
#endif
#ifdef HAVE_ARPHRD_AX25
  { "ax25",   ARPHRD_AX25,  12, in_ax25   },
#endif
  { NULL,     -1,            0, NULL      }
};

struct hwtype *hardware;

/* Check our hardware type table for this type. */
struct hwtype *get_type(char *name)
{
  register struct hwtype *hwp;

  hwp = hwtypes;
  while (hwp->name != NULL) {
	if (!strcmp(hwp->name, name)) return(hwp);
	hwp++;
  }
  return(NULL);
}


/* Check our hardware type table for this type. */
struct hwtype *get_ntype(int type)
{
  register struct hwtype *hwp;

  hwp = hwtypes;
  while (hwp->name != NULL) {
	if (hwp->type == type) return(hwp);
	hwp++;
  }
  return(NULL);
}


/* Delete an entry from the ARP cache. */
int arp_del(char *host)
{
  struct hostent *hp;
  struct arpreq req;
  struct sockaddr_in *si;
  register int i;
  int found;

  /* Resolve the host name. */
  if ((hp = gethostbyname(host)) == NULL) {
	fprintf(stderr, "rarp: %s: unknown host\n", host);
	return(-1);
  }

  /* The host can have more than one address, so we loop on them. */
  i = -1;
  found = 0;
  while (hp->h_addr_list[++i] != NULL) {
	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[i], hp->h_length);

	/* Call the kernel. */
	if (ioctl(sockfd, SIOCDRARP, &req) < 0) {
		if (errno != ENXIO) {
			perror("SIOCDRARP");
			return(-1);
		} else continue;
	}
	found++;
  }

  if (found == 0) printf("No ARP entry for %s\n", hp->h_name);
  return(0);
}


/* Set an entry in the ARP cache. */
int arp_set(char *host, char *hw_addr)
{
  struct hostent *hp;
  struct arpreq req;
  struct sockaddr_in *si;
  unsigned char *ha;

  /* Resolve the host name. */
  if ((hp = gethostbyname(host)) == NULL) {
	fprintf(stderr, "rarp: %s: unknown host\n", host);
	return(-1);
  }

  if ((ha = hardware->input(hw_addr)) == NULL) 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, ha, hardware->hlen);

  /* Call the kernel. */
  if (ioctl(sockfd, SIOCSRARP, &req) < 0) {
	perror("SIOCSRARP");
	return(-1);
  }
  return(0);
}


static void usage(void)
{
  fprintf(stderr, 
	  "Usage: rarp -a                   List Entries in cache.     \n");
  fprintf(stderr, 
	  "       rarp -d hostname          Delete hostname from cache.\n");
  fprintf(stderr, 
	  "       rarp -s hostname hw_addr  Add hostname to cache.\n");
  exit(-1);
}


void main(argc, argv)
int argc;
char **argv;
{
  int result=0;

  /* Initialize variables... */
  hardware = get_type("ether");

  argv++;
  argc--;
  if(argc==0)
    usage();

 /* Fetch the command-line arguments. */

  if(argv[0][0]!='-')
    usage();

  if(argv[0][1]=='t')
    {
      if(argc<3)  /* Need -t, type, and then at least one further arg */
	usage();
	  
      hardware = get_type(argv[1]);
      if (hardware->type == -1) 
	{
	  fprintf(stderr,
		  "rarp: %s: unknown hardware type.\n", optarg);
	  exit(-1);
	}
      argv+=2;
      argc-=2;

      if(argv[0][0]!='-')
	usage();
    }

  switch(argv[0][1]) 
    {
    case 'a':
      if(argc!=1)
	usage();
      result=system("cat /proc/net/rarp");  /* This is lazy, but who cares? */
      break;

    case 'd':
      if(argc!=2)
	usage();

      if ((sockfd = socket(AF_INET,SOCK_DGRAM,0)) <0)
	{
	  perror("socket");
	  exit(-1);
	}

      result = arp_del(argv[1]);

      (void) close(sockfd);
      break;

    case 's':
      if(argc!=3)
	usage();

      if ((sockfd = socket(AF_INET,SOCK_DGRAM,0)) <0)
	{
	  perror("socket");
	  exit(-1);
	}
      result = arp_set(argv[1],argv[2]);

      (void) close(sockfd);
      break;

    default:
      usage();
  }

  exit(result);
}

