/*
 *
 *	arp.c:	ARP interrogator for Linux
 *
 *	(C) R.P.Bellis 1992
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/sock_ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#include "psdata.h"

/* The following two macros and structure must be
   identical to those in the net/tcp directory */

#define ARP_TABLE_SIZE 16
#define MAX_ADDR_LEN 6

struct arp_table
{
  struct arp_table *next;
  unsigned long last_used;
  unsigned long ip;
  unsigned char hlen;
  unsigned char hard[MAX_ADDR_LEN];
};

void		dump(void);
void		getdel_host(const char *, int action);
void		set_host(const char *, const char *);

static void	printether(const unsigned char *);

static void usage(void)
{
	fprintf(stderr,
		"usage:\n"
		"  arp -a [hostname ...]\n"
		"  arp -d <hostname> [hostname ...]\n"
		"  arp -s <hostname> <ether_addr>\n"
	);

	exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
	int		arg;

	/* Check for number of arguments */
	if (argc < 2) usage();

	/* Check that the first argument is an option */
	if (argv[1][0] != '-' || argv[1][2] != '\0') usage();

	/* Act on option */
	switch (argv[1][1]) {
		case 'a':
			if (argc == 2) {
				dump();
			} else {
				for (arg = 2; arg < argc; ++arg) {
					getdel_host(argv[arg], SIOCGARP);
				}
			}
			break;
		case 'd':
			if (argc == 2) {
				usage();
			} else {
				for (arg = 2; arg < argc; ++arg) {
					getdel_host(argv[arg], SIOCDARP);
				}
			}
			break;
		case 's':
			if (argc != 4) {
				usage();
			} else {
				set_host(argv[2], argv[3]);
			}
			break;
		default:
			usage();
	}

	return EXIT_SUCCESS;
}

void dump(void)
{
	int			i;
	off_t			table, list;
	struct arp_table	arp;
	struct hostent		*hp;

	/* Open the PS database */
	if (open_psdb() != 0) {
		perror("open_psdb");
		exit(EXIT_FAILURE);
	}

	/* Find the address of the table itself */
	table = k_addr("_arp_table");

	for (i = 0; i < ARP_TABLE_SIZE; ++i) {

		/* Read the first pointer stored at that address */
		kmemread(&list, table + i * sizeof(void *), sizeof(void *));

		while (list) {
			/* Read the entry */
			kmemread(&arp, list, sizeof(arp));

			/* Get the address of the next entry */
			list = (off_t)arp.next;

			if (!arp.hlen) break;

			/* Extract the hostname */
			hp = gethostbyaddr((char *)&arp.ip, 4, AF_INET);
			if (hp) {
				printf("%s ", hp->h_name);
			} else {
				printf("[unknown] ");
			}

			/* Print the entry */
			printf("(%s) at ", inet_ntoa(*(struct in_addr *)&arp.ip));
			printether(arp.hard);
			printf("\n");
		}
	}

	close_psdb();
}

void getdel_host(const char *hostname, int action)
{
	struct hostent		*hp;
	struct arpreq		req;
	char			*ip;
	int			c = 0;
	int			r, s;

	/* Open the socket for ioctl() */
	s = socket(AF_INET, SOCK_STREAM, 0);

	/* Get the host's information */
	if (!(hp = gethostbyname(hostname))) {
		perror("gethostbyname");
		return;
	}

	while (ip = hp->h_addr_list[c++]) {
		memset(&req, 0, sizeof(req));

		req.arp_ha.sa_family = AF_UNSPEC;
		req.arp_pa.sa_family = AF_INET;
		memcpy(req.arp_pa.sa_data, ip, 4);

		r = ioctl(s, action, &req);
		if (r == -1) {
			if (errno != ENXIO) perror("ioctl");
			break;
		}
		if (action == SIOCGARP) {
			printf("%s (%s) at ", hp->h_name,
				inet_ntoa(*(struct in_addr *)req.arp_pa.sa_data));
			printether(req.arp_ha.sa_data);
			printf("\n");
		}
	}
}

void set_host(const char *hostname, const char *mac)
{
}

static void printether(const unsigned char *ptr)
{
	int			j;

	for (j = 0; j < 6; j++) {
		if (j != 0) putchar(':');
		printf("%x", ptr[j]);
	}
}
