/*
 *  ipx_probe.c
 *
 *  Check the network for frames currently active
 *
 *  Copyright (C) 1996 by Volker Lendecke
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/ipx.h>
#include <linux/if.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>

static char *progname;
int verbose = 0;

static void
usage()
{
	fprintf(stderr, "usage: %s [options]\n", progname);
	fprintf(stderr, "type '%s -h' for help\n", progname);
}

static void
help()
{
	printf("\n"
	       "Probe an interface for ipx networks\n"
	       "\n");
	printf("usage: %s [options]\n", progname);
	printf("\n"
	       "-v              Verbose output\n"
	       "-i interface    Interface to probe, default: eth0\n"
	       "-t timeout      Seconds to wait for answer, default: 3\n"
	       "-h              Print this help text\n\n");
}

#define IPX_SAP_PTYPE (0x04)
#define IPX_SAP_NEAREST_QUERY (0x0003)
#define IPX_SAP_PORT  (0x0452)
#define IPX_BROADCAST_NODE ("\xff\xff\xff\xff\xff\xff")

#define BVAL(buf,pos) (((__u8 *)(buf))[pos])
#define BSET(buf,pos,val) (BVAL(buf,pos) = (val))
static inline void
WSET_HL(__u8 * buf, int pos, __u16 val)
{
	BSET(buf, pos, val >> 8);
	BSET(buf, pos + 1, val & 0xff);
}

struct frame_type
{
	char *ft_name;
	unsigned char ft_val;
};

static struct frame_type frame_types[] =
{
	{
		"802.2", IPX_FRAME_8022
	}
	,
#ifdef IPX_FRAME_TR_8022
	{
		"802.2TR", IPX_FRAME_TR_8022
	}
	,
#endif
	{
		"802.3", IPX_FRAME_8023
	}
	,
	{
		"SNAP", IPX_FRAME_SNAP
	}
	,
	{
		"EtherII", IPX_FRAME_ETHERII
	}
};

#define NFTYPES	(sizeof(frame_types)/sizeof(struct frame_type))

static char *
frame_name(int frame_type)
{
	int i;
	for (i = 0; i < NFTYPES; i++)
	{
		if (frame_types[i].ft_val == frame_type)
		{
			return frame_types[i].ft_name;
		}
	}
	return NULL;
}

static int
ipx_recvfrom(int sock, void *buf, int len, unsigned int flags,
	     struct sockaddr_ipx *sender, int *addrlen, int timeout,
	     long *err)
{
	fd_set rd, wr, ex;
	struct timeval tv;
	int result;

	FD_ZERO(&rd);
	FD_ZERO(&wr);
	FD_ZERO(&ex);
	FD_SET(sock, &rd);

	tv.tv_sec = timeout;
	tv.tv_usec = 0;

	if ((result = select(sock + 1, &rd, &wr, &ex, &tv)) == -1)
	{
		*err = errno;
		return -1;
	}
	if (FD_ISSET(sock, &rd))
	{
		result = recvfrom(sock, buf, len, flags,
				  (struct sockaddr *) sender, addrlen);
	} else
	{
		result = -1;
		errno = ETIMEDOUT;
	}
	if (result < 0)
	{
		*err = errno;
	}
	return result;
}

static int
ipx_recv(int sock, void *buf, int len, unsigned int flags, int timeout,
	 long *err)
{
	struct sockaddr_ipx sender;
	int addrlen = sizeof(sender);

	return ipx_recvfrom(sock, buf, len, flags, &sender, &addrlen,
			    timeout, err);
}

static int
probe_frame(char *interface, int frame_type, int timeout, unsigned long *net)
{
	int i, sock, opt;
	int result;
	long err;
	char errmsg[strlen(progname) + 20];
	char data[1024];

	static struct ifreq id;
	struct sockaddr_ipx *sipx = (struct sockaddr_ipx *) &id.ifr_addr;

	if (verbose != 0)
	{
		printf("probing %s on %s -- ", frame_name(frame_type),
		       interface);
		fflush(stdout);
	}

	sock = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
	if (sock < 0)
	{
		int old_errno = errno;

		sprintf(errmsg, "%s: socket", progname);
		perror(errmsg);
		if (old_errno == -EINVAL)
		{
			fprintf(stderr, "Probably you have no IPX support in "
				"your kernel\n");
		}
		close(sock);
		return -1;
	}

	memset(&id, 0, sizeof(id));
	strncpy(id.ifr_name, interface, sizeof(id.ifr_name)-1);
	sipx->sipx_family = AF_IPX;
	sipx->sipx_action = IPX_CRTITF;
	sipx->sipx_special = IPX_PRIMARY;
	sipx->sipx_network = 0L;
	sipx->sipx_type = frame_type;

	i = 0;
	do
	{
		result = ioctl(sock, SIOCSIFADDR, &id);
		i++;
	}
	while ((i < 5) && (result < 0) && (errno == EAGAIN));

	if (result < 0)
	{
		int old_errno = errno;
		close(sock);
		errno = old_errno;
		return result;
	}

	/* We do a GNS request on the new socket. If something comes
           back, we assume that the frame type is valid. */

	opt = 1;
	if ((result = setsockopt(sock, SOL_SOCKET,
				 SO_BROADCAST, &opt, sizeof(opt))) < 0)
	{
		int old_errno = errno;
		close(sock);
		errno = old_errno;
		return result;
	}

	memset(&id, 0, sizeof(id));
	sipx->sipx_family = AF_IPX;
	sipx->sipx_network = htonl(0x0);
	sipx->sipx_port = htons(0x0);
	sipx->sipx_type = IPX_SAP_PTYPE;

	if ((result = bind(sock, (struct sockaddr *)sipx,
			   sizeof(*sipx))) < 0 -1)
	{
		int old_errno = errno;
		close(sock);
		errno = old_errno;
		return result;
	}

	WSET_HL(data, 0, IPX_SAP_NEAREST_QUERY);
	WSET_HL(data, 2, 4);

	memset(&id, 0, sizeof(id));
	sipx->sipx_family = AF_IPX;
	sipx->sipx_port = htons(IPX_SAP_PORT);
	sipx->sipx_type = IPX_SAP_PTYPE;
	sipx->sipx_network = htonl(0x0);
	memcpy(sipx->sipx_node, IPX_BROADCAST_NODE, 6);

	if ((result = sendto(sock, data, 4, 0, (struct sockaddr *) sipx,
			     sizeof(*sipx))) < 0)
	{
		int old_errno = errno;
		close(sock);
		errno = old_errno;
		return result;
	}

	result = ipx_recv(sock, data, 1024, 0, timeout, &err);

	if (result > 0)
	{
		struct sockaddr_ipx sipx;
		int namelen = sizeof(sipx);

		if (getsockname(sock, (struct sockaddr *)&sipx,
				&namelen) < 0)
		{
			fprintf(stderr, "%s: Could not find socket address\n",
				progname);
			exit(1);
		}
		*net = ntohl(sipx.sipx_network);
	}

	memset(&id, 0, sizeof(id));
	strncpy(id.ifr_name, interface, sizeof(id.ifr_name)-1);
	sipx->sipx_family = AF_IPX;
	sipx->sipx_action = IPX_DLTITF;
	sipx->sipx_network = 0L;
	sipx->sipx_type = frame_type;
	result = ioctl(sock, SIOCSIFADDR, &id);
	close(sock);

	if (result < 0)
	{
		fprintf(stderr, "%s: could not delete interface\n",
			progname);
		exit(1);
	}

	if (err == ETIMEDOUT)
	{
		if (verbose != 0)
		{
			printf("no network found\n");
		}
		return -1;
	}

	if (verbose != 0)
	{
		printf("found IPX network %8.8lX\n", *net);
	}
	
	return 0;
}
	

static int
file_lines(char *name)
{
	FILE *f = fopen(name, "r");
	char buf[100];
	int lines = 0;

	if (f == NULL)
	{
		return -errno;
	}
	while (fgets(buf, sizeof(buf), f) != NULL)
	{
		lines += 1;
	}
	fclose(f);
	return lines;
}

static int
ipx_interfaces(void)
{
	int result = file_lines("/proc/net/ipx_interface");
	if (result == 0)
	{
		result = -EIO;
	}
	return result - 1;
}

static int
ipx_auto_off(void)
{
	int s;
	char errmsg[strlen(progname) + 20];
	int val = 0;

	s = socket(AF_IPX, SOCK_DGRAM, AF_IPX);
	if (s < 0)
	{
		int old_errno = errno;

		sprintf(errmsg, "%s: socket", progname);
		perror(errmsg);
		if (old_errno == -EINVAL)
		{
			fprintf(stderr, "Probably you have no IPX support in "
				"your kernel\n");
		}
		close(s);
		return -1;
	}
	sprintf(errmsg, "%s: ioctl", progname);

	if (ioctl(s, SIOCAIPXPRISLT, &val) < 0)
	{
		perror(errmsg);
		close(s);
		return -1;
	}
	if (ioctl(s, SIOCAIPXITFCRT, &val) < 0)
	{
		perror(errmsg);
		close(s);
		return -1;
	}
	close(s);
	return 0;
}

int
main(int argc, char *argv[])
{
	int interfaces;
	char *interface = "eth0";
	int opt;
	int timeout = 3;

	unsigned long network[5] = { 0, };

	progname = argv[0];

	while ((opt = getopt(argc, argv, "vi:ht:")) != EOF)
	{
		switch(opt)
		{
		case 'v':
			verbose = 1;
			break;
		case 'i':
			interface = optarg;
			break;
		case 't':
			timeout = atoi(optarg);
			break;
		case 'h':
			help();
			exit(1);
		default:
			usage();
			exit(1);
		}
	}

	if (ipx_auto_off() < 0)
	{
		exit(1);
	}
	interfaces = ipx_interfaces();
	if (interfaces > 0)
	{
		fprintf(stderr, "%s must be run with no interfaces configured."
			" Found %d interface%s.\n",
			progname, interfaces,
			interfaces == 1 ? "" : "s");
		exit(1);
	}
	if (interfaces < 0)
	{
		fprintf(stderr, "%s: %s\n", progname, strerror(interfaces));
		exit(1);
	}

	probe_frame(interface, IPX_FRAME_8022, timeout, &(network[0]));
	probe_frame(interface, IPX_FRAME_8023, timeout, &(network[1]));
	probe_frame(interface, IPX_FRAME_SNAP, timeout, &(network[2]));
	probe_frame(interface, IPX_FRAME_ETHERII, timeout, &(network[3]));

	if (verbose == 0)
	{
		if (network[0] != 0)
		{
			printf("%s %8.8lX\n",
			       frame_name(IPX_FRAME_8022), network[0]);
		}
		if (network[1] != 0)
		{
			printf("%s %8.8lX\n",
			       frame_name(IPX_FRAME_8023), network[1]);
		}
		if (network[2] != 0)
		{
			printf("%s %8.8lX\n",
			       frame_name(IPX_FRAME_SNAP), network[2]);
		}
		if (network[3] != 0)
		{
			printf("%s %8.8lX\n",
			       frame_name(IPX_FRAME_ETHERII), network[3]);
		}
	}
	return 0;
}
