/* ipxdump.c */ 

/* Copyright 1996 Volker Lendecke, Goettingen, Germany
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if_ether.h>
#include <strings.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <signal.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <netinet/protocols.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include "ipxutil.h"

struct ipx_address
{
	unsigned long net;
	unsigned char  node[IPX_NODE_LEN]; 
	unsigned short sock;
};

struct ipx_packet
{
	unsigned short	ipx_checksum;
#define IPX_NO_CHECKSUM	0xFFFF
	unsigned short  ipx_pktsize;
	unsigned char   ipx_tctrl;
	unsigned char   ipx_type;
#define IPX_TYPE_UNKNOWN	0x00
#define IPX_TYPE_RIP		0x01	/* may also be 0 */
#define IPX_TYPE_SAP		0x04	/* may also be 0 */
#define IPX_TYPE_SPX		0x05	/* Not yet implemented */
#define IPX_TYPE_NCP		0x11	/* $lots for docs on this (SPIT) */
#define IPX_TYPE_PPROP		0x14	/* complicated flood fill brdcast [Not supported] */
	struct ipx_address ipx_dest __attribute__ ((packed));
	struct ipx_address ipx_source __attribute__ ((packed));
};


void handle_frame (unsigned char *buf, int length, struct sockaddr *saddr);
void handle_ipx(char *frame, unsigned char *buf);

static int filter = 0;
static IPXNode filter_node;

static int exit_request = 0;
static void
int_handler()
{
	exit_request = 1;
}

void
main (int argc, char *argv[])
{
	int sd;
	struct ifreq ifr, oldifr;
	char *device = "eth0";
	struct sockaddr saddr;
	int sizeaddr;
	unsigned char buf[4096];
	int length;

	signal(SIGINT, int_handler);

	if (argc > 1)
	{
		if (ipx_sscanf_node(argv[1], filter_node) != 0)
		{
			fprintf(stderr, "usage: %s [node]\n", argv[0]);
			exit(1);
		}
		filter = 1;
	}

	if ((sd = socket (AF_INET, SOCK_PACKET, htons (ETH_P_ALL))) < 0)
	{
		perror ("Can't get socket");
		fprintf(stderr, "You must run %s as root\n", argv[0]);
		exit (1);
	}

	/* SET PROMISC */

	strcpy (oldifr.ifr_name, device);
	if (ioctl (sd, SIOCGIFFLAGS, &oldifr) < 0)
	{
		close (sd);
		perror ("Can't get flags");
		exit (2);
	}

	/* This should be rewritten to cooperate with other net tools */
	ifr = oldifr;
	ifr.ifr_flags |= IFF_PROMISC;

	if (ioctl (sd, SIOCSIFFLAGS, &ifr) < 0)
	{
		close (sd);
		perror ("Can't set flags");
		exit (3);
	}

	while ( exit_request == 0 )
	{
		/* This is the main data-gathering loop; keep it small
		   and fast */
		sizeaddr = sizeof(saddr);
		length = recvfrom (sd, buf, sizeof(buf), 0,
				   &saddr, &sizeaddr);
		if (length < 0 ) continue;
		handle_frame (buf, length, &saddr);
	}

	/* This should be rewritten to cooperate with other net tools */
	if (ioctl (sd, SIOCSIFFLAGS, &oldifr) < 0)
	{
		close (sd);
		perror ("Can't set flags");
		exit (4);
	}

	close (sd);
	exit (0);
}

void
handle_ipx (char *frame, unsigned char *buf)
{
	int i;
	struct ipx_packet *h = (struct ipx_packet *)buf;
	struct sockaddr_ipx s_addr;
	struct sockaddr_ipx d_addr;
	int length = ntohs(h->ipx_pktsize);


	memset(&s_addr, 0, sizeof(s_addr));
	memset(&d_addr, 0, sizeof(d_addr));

	memcpy(s_addr.sipx_node, h->ipx_source.node, sizeof(s_addr.sipx_node));
	s_addr.sipx_port = h->ipx_source.sock;
	s_addr.sipx_network  = h->ipx_source.net;

	memcpy(d_addr.sipx_node, h->ipx_dest.node, sizeof(d_addr.sipx_node));
	d_addr.sipx_port = h->ipx_dest.sock;
	d_addr.sipx_network  = h->ipx_dest.net;

	if (filter != 0)
	{
		if (   (memcmp(filter_node, s_addr.sipx_node,
			       sizeof(filter_node)) != 0)
		    && (memcmp(filter_node, d_addr.sipx_node,
			       sizeof(filter_node)) != 0))
		{
			/* Not for us */
			return;
		}
	}

	printf("%s ", frame);

	for (i = 0; i < length; i++)
	{
		printf("%2.2X", buf[i]);
	}
	printf("\n");
	if (!isatty(STDOUT_FILENO))
	{
		fflush(stdout);
	}
}

void
handle_other (unsigned char *buf, int length, struct sockaddr *saddr)
{
	struct ethhdr *eth = (struct ethhdr *)buf;
	unsigned char *p = &(buf[sizeof(struct ethhdr)]);

	if (ntohs(eth->h_proto) < 1536)
	{
		/* This is a magic hack to spot IPX packets. Older
		 * Novell breaks the protocol design and runs IPX over
		 * 802.3 without an 802.2 LLC layer. We look for FFFF
		 * which isnt a used 802.2 SSAP/DSAP. This won't work
		 * for fault tolerant netware but does for the rest.
		 */
		
		if (*(unsigned short *)p == 0xffff)
		{
			handle_ipx("802.3", p);
			return;
		}

		if (   (*(unsigned short *)p == htons(0xe0e0))
		    && (p[2] == 0x03))
		{
			handle_ipx("802.2", p+3);
			return;
		}

		if (memcmp(p, "\252\252\003\000\000\000\201\067", 8) == 0)
		{
			handle_ipx("snap", p+8);
			return;
		}
	}
}

void
handle_frame (unsigned char *buf, int length, struct sockaddr *saddr)
{
	/* Ethernet packet type ID field */
	unsigned short packet_type = ((struct ethhdr *)buf)->h_proto;
	switch( packet_type )
	{
	case __constant_ntohs(ETH_P_IPX):
		handle_ipx("EtherII", &(buf[sizeof(struct ethhdr)]));
		break;
	default:
		handle_other(buf, length, saddr);
		break;
	}
}
