/*
 *  EtherDump 2.0
 *  IPv4 packet sniffer using raw sockets
 *
 *  Changes by Peter Willis since 2.0-pre1:
 *    * Slightly modified by Peter Willis to add functionality and
 *      fix bugs.
 *    * Now can export raw frames as binary or ASCII hex for
 *      ethereal's text2pcap to export into pcap format.
 *    * Win32 support was removed as I didn't want to maintain it.
 *    * Name changed to "EtherDump".
 *    * Added crap tcpdump-like outputting.
 *
 *  Changes by Erik Andersen:
 *    * Converted to getopt instead of a million strcmps
 *    * cleanup
 *
 *  Todo:
 *    * Parse the tcp flags and add to the output.
 *    * Same for udp.
 *
 *  Copyright (C) 2004  Christophe Devine
 *  Copyright (C) 2004  Peter Willis <psyphreak@phreaker.net>
 *  Copyright (C) 2004  Erik Andersen <andersen@codepoet.org>
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <netpacket/packet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/time.h>
#include <ctype.h>
#include <getopt.h>

#define ETH_P_ALL 0x0003
#define ETH_P_IP  0x0800

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

struct ip_hdr
{
    unsigned char  hlv;         /* +00 - header len. & version  */
    unsigned char  tos;         /* +01 - type of service        */
    unsigned short tot_len;     /* +02 - total packet length    */
    unsigned short id;          /* +04 - identification         */
    unsigned short frag_off;    /* +06 - fragment offset field  */
    unsigned char  ttl;         /* +08 - time to live           */
    unsigned char  protocol;    /* +09 - ip protocol            */
    unsigned short check;       /* +10 - ip checksum            */
    unsigned long int saddr;    /* +12 - source address         */
    unsigned long int daddr;    /* +16 - destination address    */
};

struct icmp_hdr
{
    unsigned char  type;        /* +00 - message type           */
    unsigned char  code;        /* +01 - type sub-code          */
    unsigned short checksum;    /* +02 - icmp checksum          */
    unsigned short id;          /* +04 - identification         */
    unsigned short sequence;    /* +06 - sequence number        */
};

struct tcp_hdr
{
    unsigned short source;      /* +00 - source port            */
    unsigned short dest;        /* +02 - destination port       */
    unsigned long  seq;         /* +04 - sequence number        */
    unsigned long  ack_seq;     /* +08 - ack seq. number        */
    unsigned char  unused;      /* +12 - unused field           */
    unsigned char  flags;       /* +13 - tcp flags              */
    unsigned short window;      /* +14 - tcp window             */
    unsigned short check;       /* +16 - tcp checksum           */
    unsigned short urp_ptr;     /* +18 - urgent pointer         */
};

struct udp_hdr
{
    unsigned short source;      /* +00 - source port            */
    unsigned short dest;        /* +02 - destination port       */
    unsigned short len;         /* +04 - message length         */
    unsigned short check;       /* +06 - udp checksum           */
};

int show_usage() {
    printf( "\n" );
    printf( "  usage: etherdump [options] <interface>\n" );
    printf( "\n" );
    printf( "   options:\n"
	    "    -o,--output\t\toutput to a file instead of stdout\n"
	    "    -f,--frame\t\toutput the raw frames\n"
	    "    -r,--raw\t\toutput in raw binary\n"
	    "    -h,--hex\t\toutput in ASCII hex [default]\n"
	    "    -d,--display\tdisplays tcp, udp and icmp packets\n"
	    "    -t,--tcpdump\tprint out in tcpdump-like format\n"
	    "    -p,--packetdump\tprint out in packetdump format\n"
	    "    \t\t\t[default]\n"
	    "\n"
	    "   <interface> may be any interface (eth0, ppp0, etc)\n"
	  );
    printf( "\n" );
    return(0);
}

static const struct option etherdump_long_options[] = {
	{ "output",	1, NULL, 'o' },
	{ "frame",	0, NULL, 'f' },
	{ "raw",	0, NULL, 'r' },
	{ "hex",	0, NULL, 'h' },
	{ "display",	0, NULL, 'd' },
	{ "tcpdump",	0, NULL, 't' },
	{ "packetdump",	0, NULL, 'p' },
	{ 0,		0, 0, 0 }
};

int main( int argc, char *argv[] )
{
    unsigned short sport, dport;
    unsigned char buffer[4096];
    int raw_sock, n;

    struct sockaddr_ll sll;
    struct packet_mreq mr;
    struct ifreq ifr;
    int socktype = SOCK_DGRAM;

    int i;
    struct protoent *proto;
    char *outputfile = NULL;
    char *interface = NULL;
    FILE *F_logfd = stdout; /* guess. :P */
    int logfd = 1; /* default to stdout */
    int printformat = 0; /* 0 for packetdump format, 1 for tcpdump */
    int dumptype = 0; /* 0 for disabled, 1 for ASCII hex, 2 for raw binary */

    /* check the arguments */
    if( argc < 2 ) {
	show_usage();
	return( 1 );
    }

    while ((i = getopt_long (argc, argv, "fhrdto:",
		    etherdump_long_options, NULL)) > 0)
    {
	switch (i) {
	    case 'f':
		socktype = SOCK_RAW;
		dumptype = 1; /* frame dump defaults to hex output */
		break;
	    case 'h':
		socktype = SOCK_RAW;
		dumptype = 1;
		break;
	    case 'r':
		socktype = SOCK_RAW;
		dumptype = 2;
		break;
	    case 'o':
		outputfile = optarg;
		break;
	    case 'd':
		socktype = SOCK_DGRAM;
		dumptype = 0;
		break;
	    case 'p':
		socktype = SOCK_DGRAM;
		dumptype = 0;
		printformat = 0;
		break;
	    case 't':
		socktype = SOCK_DGRAM;
		dumptype = 0;
		printformat = 1;
	    default:
		show_usage();
	}
    }

    if (argc - optind < 1) {
	show_usage();
	return(1);
    }

    interface = argv[optind];
    printf("interface %s\n", interface);


    /* create the raw socket */
    if( ( raw_sock = socket( PF_PACKET, socktype, htons( ETH_P_ALL ) ) ) < 0 ) {
	perror( "socket" );
	return( 1 );
    }

    /* find the interface index */
    memset( &ifr, 0, sizeof( ifr ) );
    strncpy( ifr.ifr_name, interface, sizeof( ifr.ifr_name ) );
    if( ioctl( raw_sock, SIOCGIFINDEX, &ifr ) < 0 ) {
	perror( "ioctl(SIOCGIFINDEX)" );
	return( 1 );
    }

    /* bind the raw socket to the interface */
    memset( &sll, 0, sizeof( sll ) );
    sll.sll_family   = AF_PACKET;
    sll.sll_ifindex  = ifr.ifr_ifindex;
    sll.sll_protocol = htons( ETH_P_ALL );
    if( bind( raw_sock, (struct sockaddr *) &sll, sizeof( sll ) ) < 0 ) {
	perror( "bind" );
	return( 1 );
    }

    /* enable promiscuous mode */
    memset( &mr, 0, sizeof( mr ) );
    mr.mr_ifindex = ifr.ifr_ifindex;
    mr.mr_type    = PACKET_MR_PROMISC;
    if( setsockopt( raw_sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof( mr ) ) < 0 ) {
	perror( "setsockopt" );
	return( 1 );
    }

    /* (optionally) open a log file */
    if (outputfile != NULL) {
	if ( (logfd = open(outputfile, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR )) == -1) {
	    perror ( "open" );
	    return( 1 );
	}
	if ( (F_logfd = fdopen(logfd, "w")) == NULL ) {
	    perror ( "fdopen" );
	    return( 1 );
	}
    }

    while( 1 ) {

	struct icmp_hdr *icmp;
	struct udp_hdr *udp;
	struct tcp_hdr *tcp;
	struct ip_hdr *ip;
	struct in_addr src;
	struct in_addr dst;
	struct tm *lt;
	struct timeval tv;
	struct sockaddr_ll from;
	int fromlen;
	char srcaddr[16];
	char dstaddr[16];
	char tmpproto[16];

	memset(buffer,  0, sizeof(buffer));
	memset(srcaddr, 0, sizeof(srcaddr));
	memset(dstaddr, 0, sizeof(dstaddr));

	/* wait for packets */
	fromlen = sizeof( from );
	if( ( n = recvfrom( raw_sock, buffer, 4096, 0, (struct sockaddr *) &from, &fromlen ) ) < 0 ) {
	    if( errno == ENETDOWN ) {
		fprintf(stderr, "sleeping for 30 secs\n");
		sleep( 30 );
		continue;
	    } else {
		perror( "recvfrom" );
		return( 1 );
	    }
	}

	/* skip duplicate packets on the loopback interface */
	if( from.sll_pkttype == PACKET_OUTGOING && ! strcmp( interface, "lo" ) ) {
	    continue;
	}

	if (dumptype == 1) {
	    for (i=0; i<=n; i++) {
		if ( (i % 16) == 0 ) {
		    fprintf(F_logfd, i == 0 ? "%.4x" : "\n%.4x", i);
		    fflush(NULL);
		}
		fprintf(F_logfd, " %.2X", buffer[i]);
		fflush(NULL);
	    }
	    fprintf(F_logfd, "\n");
	    fflush(NULL);

	} else if (dumptype == 2) {
	    write(logfd, buffer, n);

	} else {
	    /* we're only interested in standard IPv4 packets */
	    if( ntohs( from.sll_protocol ) != ETH_P_IP ) {
		continue;
	    }

	    /* have a look inside the IP header */
	    ip = (struct ip_hdr *) buffer;
	    src.s_addr = ip->saddr;
	    dst.s_addr = ip->daddr;
	    strncpy(srcaddr, inet_ntoa(src), sizeof(srcaddr));
	    strncpy(dstaddr, inet_ntoa(dst), sizeof(dstaddr));

	    /* get the time */
	    gettimeofday(&tv, NULL);
	    lt = localtime( &tv.tv_sec );
	    /* get the ip protocol */
	    proto = getprotobynumber(ip->protocol);
	    strncpy(tmpproto, proto->p_name, sizeof(tmpproto));
	    /* convert to upper case */
	    for (i=0; i < (int)sizeof(tmpproto); i++) {
		tmpproto[i] = toupper(tmpproto[i]);
	    }

	    switch( ip->protocol )
	    {
		case 6: /* SOL_TCP */
		    tcp = (struct tcp_hdr *) ( (unsigned char *) ip + ( (ip->hlv & 0x0F) << 2 ) );
		    sport = htons( tcp->source );
		    dport = htons( tcp->dest );
		    goto common;

		case 17: /* SOL_UDP */
		    udp = (struct udp_hdr *) ( (unsigned char *) ip + ( (ip->hlv & 0x0F) << 2 ) );
		    sport = htons( udp->source );
		    dport = htons( udp->dest );
		    goto common;

		case 1: /* SOL_ICMP */
		    icmp = (struct icmp_hdr *) ( (unsigned char *) ip + ( (ip->hlv & 0x0F) << 2 ) );
		    sport = icmp->type;
		    dport = icmp->code;
		    goto common;

common:
		    if (printformat == 1) { /* tcpdump */
			printf("%02d:%02d:%02d.%d %s.%d > %s.%d: (%d)\n", lt->tm_hour, lt->tm_min,
				lt->tm_sec, (int)tv.tv_usec, srcaddr, sport, dstaddr, dport, n);
		    } else { /* packetdump */
			printf("%02d-%02d %02d:%02d:%02d %4s: %15s : %-5d -> %15s : %-5d  %4d\n",
				lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec,
				tmpproto, srcaddr, sport, dstaddr, dport, n);
		    }
		    break;

		default :
		    printf( " unsupported IP protocol %d from %s to %s\n", ip->protocol,
			    srcaddr, dstaddr );
		    break;
	    }
	}
    }

    return( 0 );
}
