/*
 * $Id: src/arpdig/arpdig.c,v 1.16 2006/05/06 10:08:42 marck Exp $
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <err.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "arpdig.h"


static const char __Version__[] __unused = "arpdig ver 0.5.1";

/* sorted table of ether ifaces */
typedef struct _iface {
	char	ifname[IFNAMSIZ];
	int	ifindex;
	/* host order */
	uint32_t addr;
	uint32_t mask;
} IFACE;

IFACE	*iftable;
int	nifaces = 0;

int noresolve = 0;

/* mostly stolen from ifconfig(8) */
static int isether(const struct sockaddr_dl *sdl)
{
	return (sdl->sdl_alen > 0 && sdl->sdl_type == IFT_ETHER &&
		sdl->sdl_alen == ETHER_ADDR_LEN);
}


static int mkaddrtable(void)
{
	int needed;
	uint32_t addr, mask;
	char *ifname;
	struct ifaddrs *ift, *p;
	struct sockaddr *t;
	IFACE *ifp;

	if ((iftable = calloc(MAXIFACES, sizeof(IFACE))) == NULL)
		errx(1, "Out of memory");
	if (getifaddrs(&ift) < 0)
		err(1, "getifaddrs");

	needed = 0;
	addr = 0;
	ifname = NULL;
	for (p = ift; p; p = p->ifa_next) {
		if ((t = p->ifa_addr) != NULL) {
			switch (t->sa_family) {
			case AF_LINK:
				/* XXX assume AF_LINK is always the first */
				addr = 0;
				needed = isether((struct sockaddr_dl *)t);
				ifname = p->ifa_name;
				break;
			case AF_INET:
				if (!needed)
					break;
				addr = ntohl(
				    ((struct sockaddr_in *)t)->sin_addr.s_addr);
				break;
			}
		}
		if ((t = p->ifa_netmask) != NULL) {
			switch (t->sa_family) {
			case AF_INET:
				if (!needed)
					break;
				mask = ntohl(
				    ((struct sockaddr_in *)t)->sin_addr.s_addr);
				/* skip /32 aliases */
				if (mask == INADDR_BROADCAST)
					break;
				/* XXX store address */
				ifp = &iftable[nifaces++];
				if (nifaces == MAXIFACES)
					errx(1,
					    "Too many interface addresses: %d",
					    nifaces);
				strncpy(ifp->ifname, ifname, IFNAMSIZ);
				ifp->ifname[IFNAMSIZ-1] = '\0';
				/* if_nametoindex uses getifaddrs itself. Use trick from there */
				ifp->ifindex = ((struct sockaddr_dl*)p->ifa_addr)->sdl_index;
				ifp->addr = addr;
				ifp->mask = mask;
				break;
			}
		}
	}
	freeifaddrs(ift);
	return 0;
}

/* #define DEBUG */

#ifdef DEBUG
/* XXX alocates memory; free to avoid leaks */
static char *_ntoa(u_long addr)
{
#define	MAXIPALEN	18
	char *buf, *s;
	int i;

	buf = malloc(MAXIPALEN);
	if ((buf = malloc(MAXIPALEN)) == NULL)
		return NULL;
	for (i=0, s=buf; i<4; i++) {
		s += sprintf(s, "%u", (unsigned int)(addr >> 24));
		addr <<= 8;
		*s++ = (i == 3) ? '\0' : '.';
	}
	return buf;
}

/*
 * convert netmask (hostorder) to prefixlen.
 * assumes consistent netmask.
 */
static int nmlen(u_long addr)
{
	int nm;

	if (!addr)
		return 0;
	nm = 1;
	while (addr <<= 1)
		nm++;
	return nm;
}

static void printaddrtable(void)
{
	int i;
	IFACE *ifp;

	for (i = 0; i < nifaces; i++) {
		ifp = &iftable[i];
		printf("%s\t", ifp->ifname);
		printf("%s:%s (/%d)\n", 
		    _ntoa(ifp->addr), _ntoa(ifp->mask), nmlen(ifp->mask));
	}
}
#endif

static char *ip2ifname(struct in_addr ia, struct in_addr *if_ia)
{
	int i;
	IFACE *ifp;
	
	for (i=0, ifp=iftable; i < nifaces; i++, ifp++)
		if ((ntohl(ia.s_addr) & ifp->mask) == 
		    (ifp->addr & ifp->mask)) {
			if (if_ia)
				if_ia->s_addr = htonl(ifp->addr);
			return ifp->ifname;
		}
	return NULL;
}

static void probeaddr(struct in_addr ia, struct in_addr if_ia)
{
	char *ifname;
	struct in_addr t_ia;

	t_ia = if_ia;
	if ((ifname = ip2ifname(ia, &if_ia)) == NULL)
		warnx("%s seems not directly connected",
			inet_ntoa(ia));
	if (t_ia.s_addr != 0 )
		if_ia = t_ia;
	if (ifname) {
#ifdef DEBUG
		printf("probing %s at %s\n", inet_ntoa(ia), ifname);
#endif
		arpdig(ifname, if_ia, ia, 32);
	}
}

static void probeaddrs(int argc, char *argv[], struct in_addr if_ia)
{
	struct in_addr ia;

	for (; argc; argc--, argv++) {
		if ((ia.s_addr = inet_addr(*argv)) == INADDR_NONE)
			warnx("Bad IP address: %s", *argv);
		else
			/* 
			 * XXX direct probe. 
			 * should make table to multiplex probes
			 */
			probeaddr(ia, if_ia);
	}
}

static void digaddrs(char *ifname, char *block, struct in_addr if_ia)
{
	char	*s;
	int	iplen;
	struct	in_addr ipstart, t_ia;

	iplen = 0;
	if ((s = strchr(block, '/')) != NULL) {
		*s++ = '\0';
		while (isdigit(*s))
			iplen = iplen*10 + (*s++ - '0');
	}
	if ( iplen <= 0 || iplen > 32 || *s != '\0' ||
	    (ipstart.s_addr = inet_addr(block)) == INADDR_NONE)
		errx(1, "Bad netblock presentation: %s", block);

	/* round ip address by masklen */
	ipstart.s_addr = htonl(ntohl(ipstart.s_addr) &
	    ((in_addr_t)-1 << (32 - iplen)));

	t_ia = if_ia;
	if (ifname == NULL && (ifname = ip2ifname(ipstart, &if_ia)) == NULL)
		warnx("%s seems not directly connected",
			inet_ntoa(ipstart));
	if (t_ia.s_addr != 0)
		if_ia = t_ia;
	printf("Digging %s:%s", ifname ? ifname : "default", inet_ntoa(if_ia));
	printf(" for %s/%d\n", inet_ntoa(ipstart), iplen);

	if (arpdig(ifname, if_ia, ipstart, iplen))
		err(2, "runtime error");
}

static char usagestr[] = 
	"usage: arpdig [-n] [-i ifname] [-F fromaddr] { netblock/len | "
	"-p [-f file] ipaddr ... }";

int main(int argc, char *argv[])
{
	int	argvlen, ch, probemode;
	char	*addrfn, *ifname, *s, **pp;
	FILE	*fp;
	struct	in_addr fromaddr;
static	char	buf[BUFLEN];

	mkaddrtable();
#ifdef DEBUG
	printaddrtable();
#endif
	
	/* paranoia */
	if (getuid() == 0)
		seteuid(-1);

	probemode = 0;
	ifname = NULL;
	addrfn = NULL;
	fromaddr.s_addr = 0;
	while ((ch = getopt(argc, argv, "f:F:i:np")) != -1)
		switch(ch) {
		case 'f':
			addrfn = optarg;
			break;
		case 'F':
			if ((fromaddr.s_addr = inet_addr(optarg)) == INADDR_NONE)
				errx(1, "Bad from ip address: %s", optarg);
			break;
		case 'i':
			ifname = optarg;
			break;
		case 'n':
			noresolve = 1;
			break;
		case 'p':
			probemode = 1;
			break;
		default:
			errx(1, usagestr);
		}
	argc -= optind;
	argv += optind;

	if (probemode) {
		if(argc == 0 && addrfn == NULL)
			errx(1, usagestr);
		if (addrfn) {
			if ((fp = fopen(addrfn, "r")) == NULL)
				err(1, "Can't open %s", addrfn);
			argvlen = (argc < 16) ? 16 : argc;
			if ((pp = malloc(argvlen * sizeof(char *))) == NULL)
				err(1, "Out of memory");
			if (argc)
				memcpy(pp, argv, argc * sizeof(char *));
			argv = pp;
			while (fgets(buf, BUFLEN, fp) != NULL) {
				if (*(s = &buf[strlen(buf) - 1]) == '\n')
					*s = '\0';
				if (s == buf)
					continue;
				if ((s = strdup(buf)) == NULL)
					err(1, "Out of memory");
				if (argc >= argvlen && 
				    (argv = realloc(argv, (argvlen*=2) * sizeof(char *))) == NULL)
					err(1, "Out of memory");
				argv[argc++] = s;
			}				
			fclose(fp);
		}
		probeaddrs(argc, argv, fromaddr);
	} else {
		if (argc != 1)
			errx(1, usagestr);
		digaddrs(ifname, argv[0], fromaddr);
	}
	return 0;
}

/* End of $RCSfile: arpdig.c,v $ */


syntax highlighted by Code2HTML, v. 0.9.1