/*
 * Copyright (c) 2007-2012, Paul Meng (mirnshi@gmail.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met:
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
 * THE POSSIBILITY OF SUCH DAMAGE.
**/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/time.h>

#include "ip.h"
#include "dump.h"
#include "dns.h"

static void dmp_ip(void *dat);
static void dmp_ip6(void *dat);
static void dmp_arp(void *dat);
static void dmp_dns(void *dat);
static char *dmp_dns_timestr(u_int s);

static struct timeval gtv = {0, 0};

int dmp_packet(const struct packet *m, const int flag)
{
	struct timeval tv;
	uint32_t usec;
	int i, j, pos0, pos1;
	char x0[96], x1[17];
	u_char *p = (u_char *)m->data;
	int len = m->len;
	int left;
	ethdr *eh = (ethdr *)m->data;
	int cr = 0;

	if (flag == 0)
		return flag;
	if (gtv.tv_sec == 0)
		gettimeofday(&gtv, 0);
	
	gettimeofday(&tv, 0);
	usec = (tv.tv_sec - gtv.tv_sec) * 1000000 + tv.tv_usec - gtv.tv_usec;
	printf("\n\033[32m%04u.%u\033[0m", usec / 1000000, usec % 1000000);
	if (flag & DMP_MAC) {
		printf("  ");
		printf("\033[33m");
		cr = 1;
		PRINT_MAC(p + 6);
		printf(" -> ");
		PRINT_MAC(p);
		printf("\n");
	}	
	len -= 14;
	p += 14;
	
	if (flag & DMP_RAW) {	
		i = 0;
		if (!cr) {
			printf("\n");
			printf("\033[33m");
			cr = 1;
		}
		while (i < len) {
			pos0 = pos1 = 0;
			left = 40;
			for (j = i; (j < i + 16 && j < len); j += 2) {
				
				pos0 += sprintf(x0 + pos0, "%2.2x", *(p + j));
				left -= 2;
				if (isprint(*(p + j)))
					pos1 += sprintf(x1 + pos1, "%c", *(p + j));
				else
					pos1 += sprintf(x1 + pos1, ".");
				
				pos0 += sprintf(x0 + pos0, "%2.2x ", *(p + j + 1));
				left -= 3;
				if (isprint(*(p + j + 1)))
					pos1 += sprintf(x1 + pos1, "%c", *(p + j + 1));
				else
					pos1 += sprintf(x1 + pos1, ".");
			}
	
			for (pos1 = 0; pos1 < left; pos1++)
				pos0 += sprintf(x0 + pos0, " ");
			printf("%s   %s\n", x0, x1);
			i += (j - i);
		}
		printf("\n");
	}
	if (flag & DMP_DETAIL) {
		if (!cr) {
			printf("\n");
			printf("\033[33m");
			cr = 1;
		}
		
		if (eh->type == htons(ETHERTYPE_IP))
			dmp_ip(eh + 1);
		else if (eh->type == htons(ETHERTYPE_IPV6))
			dmp_ip6(eh + 1);
		else if (eh->type == htons(ETHERTYPE_ARP))
			dmp_arp(eh + 1);
	}
	if (cr)
		printf("\033[0m");
	
	return 1;
}

static void dmp_arp(void *dat)
{
	vpcs_arphdr *ah = (vpcs_arphdr *)dat;
	struct in_addr in;
	u_char broadcast[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	u_int *si, *di;
	
	printf("ARP, OpCode: %d (%s)", ntohs(ah->op), 
	    ((ntohs(ah->op) == ARPOP_REQUEST) ? "Request" : "Reply"));
	
	si = (u_int *)ah->sip;
	di = (u_int *)ah->dip;
	if (si[0] == di[0] && di[0] != 0)
		printf("    Gratuitous ARP");    	
	printf("\nEther Address: ");
	PRINT_MAC(ah->sea);
	printf(" -> ");
	if (memcmp(ah->dea, broadcast, ETH_ALEN) == 0)
		printf("Broadcast");
	else
		PRINT_MAC(ah->dea);
	printf("\n");
	
	if ((ntohs(ah->op) == ARPOP_REQUEST)) { 
		in.s_addr = di[0];
		printf("Who has %s? Tell ", inet_ntoa(in));
		in.s_addr = si[0];
		printf("%s", inet_ntoa(in));
	} else if ((ntohs(ah->op) == ARPOP_REPLY)) {
		in.s_addr = si[0];
		printf("%s is at ", inet_ntoa(in));
		PRINT_MAC(ah->sea);
	}
	printf("\n");
}

static void dmp_ip(void *dat)
{
	iphdr *iph = (iphdr *)dat;
	icmphdr *icmp = (icmphdr *)(iph + 1);
	udphdr *uh = (udphdr *)(iph + 1);
	tcphdr *th = (tcphdr *)(iph + 1);
	u_char *data;
	struct in_addr in;
	
	printf("IPv%d, id: %x, length: %d, ttl: %d, sum: %4.4x", iph->ver, 
	    ntohs(iph->id), ntohs(iph->len), iph->ttl, ntohs(iph->cksum));
	
	if ((ntohs(iph->frag) & IP_DF) == IP_DF)
		printf(", DF");
	if (ntohs(iph->frag) & (IP_MF | IP_OFFMASK))
		printf(", MF/%d", (ntohs(iph->frag) << 3) & 0xffff);
		
	in.s_addr = iph->sip;
	printf("\nAddress: %s -> ", inet_ntoa(in));
	in.s_addr = iph->dip;
	printf("%s\n", inet_ntoa(in));
	
	if ((ntohs(iph->frag) << 3) & 0xffff)
		return;
	
	if (iph->proto == IPPROTO_ICMP) {
		printf("Proto: icmp, ");
		printf("type: %d, ", icmp->type);
		printf("code: %d\n", icmp->code);
		printf("Desc: %s\n", icmpTypeCode2String(iph->ver, icmp->type, icmp->code));
	} else if (iph->proto == IPPROTO_UDP) {
		printf("Proto: udp, len: %d, sum: %4.4x\n", ntohs(uh->len), ntohs(uh->cksum));
		printf("Port: %d -> %d\n", ntohs(uh->sport), ntohs(uh->dport));
		
		if (ntohs(uh->sport) == 53 || ntohs(uh->dport) == 53)
			return dmp_dns(dat);
			
		if (ntohs(uh->sport) == ntohs(uh->dport) && 
		    ntohs(uh->sport) == 520 && iph->dip == 0x90000e0) {
		    	data = (u_char *)(uh + 1);
			printf("Desc: RIP%d %s message\n", *(data + 1),
			    (*data == 1) ? "request" : "response");
		}
		
	} else if (iph->proto == IPPROTO_TCP) {
		printf("Proto: tcp, sum: %4.4x, ack: %8.8x, seq: %8.8x, ", 
		    ntohs(th->th_sum), ntohl(th->th_ack), ntohl(th->th_seq));
		printf("flags: ");
		if (th->th_flags & TH_FIN)
			printf("F");
		if (th->th_flags & TH_SYN)
			printf("S");
		if (th->th_flags & TH_RST)
			printf("R");
		if (th->th_flags & TH_PUSH)
			printf("P");
		if (th->th_flags & TH_ACK)
			printf("A");
		if (th->th_flags & TH_URG)
			printf("U");
		if (th->th_flags & TH_ECE)
			printf("E");
		if (th->th_flags & TH_CWR)
			printf("C");	
		printf("\n");

		printf("Port: %d -> %d\n", ntohs(th->th_sport), ntohs(th->th_dport));
	}
}

static void dmp_ip6(void *dat)
{
	ip6hdr *iph = (ip6hdr *)dat;
	icmp6hdr *icmp = (icmp6hdr *)(iph + 1);
	udphdr *uh = (udphdr *)(iph + 1);
	tcphdr *th = (tcphdr *)(iph + 1);
	u_char *data;
	char *p;
	
	printf("IPv6, flowid: %x, length: %d, ttl: %d\n", 
	    ntohl(iph->ip6_flow & IPV6_FLOWLABEL_MASK), ntohs(iph->ip6_plen), iph->ip6_hlim);
	
	p = ip6tostr(iph->src.addr8);
	printf("Address: %s -> ", p);
	p = ip6tostr(iph->dst.addr8);
	printf("%s\n", p);
	
	if (iph->ip6_nxt == IPPROTO_ICMPV6) {
		printf("Proto: icmp, ");
		printf("type: %d, ", icmp->type);
		printf("code: %d", icmp->code);
		if (icmp->type == ICMP6_PACKET_TOO_BIG)
			printf(", mtu: %d", ntohl(icmp->icmp6_mtu));
		printf("\n");
		printf("Desc: %s\n", icmpTypeCode2String(6, icmp->type, icmp->code));
	} else if (iph->ip6_nxt == IPPROTO_UDP) {
		printf("Proto: udp, len: %d, sum: %4.4x\n", ntohs(uh->len), ntohs(uh->cksum));
		printf("Port: %d -> %d\n", ntohs(uh->sport), ntohs(uh->dport));
		if (ntohs(uh->sport) == ntohs(uh->dport) && ntohs(uh->sport) == 521 &&
		    iph->dst.addr32[0] == IPV6_ADDR_INT32_MLL &&
		    iph->dst.addr32[1] == 0 && iph->dst.addr32[2] == 0 &&
		    iph->dst.addr32[3] == 0x09000000) {
		    	data = (u_char *)(uh + 1);
			printf("Desc: RIP%d %s message\n", *(data + 1),
			    (*data == 1) ? "request" : "response");
		}
	} else if (iph->ip6_nxt == IPPROTO_TCP) {
		printf("Proto: tcp, sum: %4.4x, ack: %8.8x, seq: %8.8x, ", 
		    ntohs(th->th_sum), ntohl(th->th_ack), ntohl(th->th_seq));
		printf("flags: ");
		if (th->th_flags & TH_FIN)
			printf("F");
		if (th->th_flags & TH_SYN)
			printf("S");
		if (th->th_flags & TH_RST)
			printf("R");
		if (th->th_flags & TH_PUSH)
			printf("P");
		if (th->th_flags & TH_ACK)
			printf("A");
		if (th->th_flags & TH_URG)
			printf("U");
		if (th->th_flags & TH_ECE)
			printf("E");
		if (th->th_flags & TH_CWR)
			printf("C");	
		printf("\n");

		printf("Port: %d -> %d\n", ntohs(th->th_sport), ntohs(th->th_dport));
	}
}

void
dmp_dns(void *dat)
{
	iphdr *iph = (iphdr *)dat;
	udphdr *uh = (udphdr *)(iph + 1);
	int iplen;	
	char *p, *q;
	dnshdr *dh;
	u_short *sp;	
	char name[256];
	int i, j;
	int rlen;
	u_char c;
	u_short ptr, type, classt;
	u_int ttl;
	struct in_addr in;
	const char *rcode[6] = {
		"No error",
		"Format error",
		"Server failure",
		"Name error",
		"Not implement",
		"Refused"};
	const char *typestr[16] = {"A", "NS", "MD", "MF", "CNAME", "SOA", "MB", 
		"MG", "MR", "NULL", "WKS", "PTR", "HINFO", "MINFO", "MX", "TXT"};
	const char *classtr[4] = {"IN", "CS", "CH", "HS"};
	/* make gcc happy */
	union u_dnsflags {
		u_short flags;
		dnsflags dflags;
	} u_dnsflags;
	dnsflags *dflags;
	
	iplen = ntohs(iph->len);
	dh = (dnshdr *)(uh + 1);
	
	printf("DNS: QueryID = %x", dh->id);
	if (ntohs(uh->dport) == 53) {
		printf(", QueryFlags = %x\n", dh->flags);
		p = (char *)(dh + 1);
		i = j = 0;
		name[0] = '\0';
		while (p + j - (char *)iph < iplen) {
			i = *(p + j);
			strncat(name, (char *)(p + j + 1), i);
			j += i + 1;
			if (*(p + j) == '\0')
				break;
			strcat(name, ".");	
		}
		printf("     Host: %s", name);
		p += j + 1;
		type = *(u_short *)(p);
		classt = *((u_short *)(p) + 1);
		if (type == 0x0100)
			printf(", type = %s", typestr[0]);
		if (classt == 0x0100)
			printf(", class = IN");
		printf("\n");
	} else if (ntohs(uh->sport) == 53){
		printf(", RespFlags = %x, ", dh->flags);
		u_dnsflags.flags = dh->flags;
		dflags = &u_dnsflags.dflags;
		if (dflags->rc != 0) {
			if (dflags->rc)
				printf("rc: %s", rcode[dflags->rc]);
			else
				printf("rc: %d", dflags->rc);
		}
		printf("\n");
		printf("     Query = %d, Answer = %d, Auth = %d, Add = %d\n", 
		    ntohs(dh->query), ntohs(dh->answer), ntohs(dh->author), ntohs(dh->addition));
			
		p = (char *)(dh + 1);
		j = dmp_dns_rname(p, (char *)iph + iplen, name);
		printf("     QueryHost: %s", name);
		
		p += j + 1;
		type = *(u_short *)(p);
		classt = *((u_short *)(p) + 1);
		if (type == 0x0100)
			printf(", type = %s", typestr[0]);
		if (classt == 0x0100)
			printf(", class = IN");	
		printf("\n");
		
		p += 4;
		
		/* unpack record */
		while (p - (char *)iph < iplen) {
			sp = (u_short *)p;
			ptr = ntohs(*sp);
			
			if ((ptr & 0xc000) == 0xc000) {
				q = (char *)(dh) + (ptr & 0x3fff);
				dmp_dns_rname(q, (char *)iph + iplen, name);
				printf("     RR: name = %s\n", name);
			} else {
				printf("     Only support compression scheme\n");
				return;
			}
			sp ++;
			p += 2; 

			type = ntohs(*sp);
			classt = ntohs(*(sp + 1));
			ttl = ntohl(*((u_int *)(sp + 4)));
			if (type < 1 || type > 16)
				printf("         Invalid type (%d)", type);
			else
				printf("         type = %s", typestr[type - 1]);
				
			if (classt < 1 || classt > 4)
				printf(", Invalid class (%d)", classt);	
			else	
				printf(", class = %s", classtr[classt - 1]);
			
			printf(", TTL = %d", ttl);
			
			p += 2 + 2 + 4;
			sp = (u_short *)p;
			rlen = ntohs(*sp);
			if (type == 1 && rlen == 4) {
				in.s_addr = ((u_int *)(p + 2))[0];
				printf(", addr = %s", inet_ntoa(in));
			} else if (type == 2) {
				/* ns */	
				i = j = 0;
				memset(name, 0, sizeof(name));
				q = p + 2;
				c = *(q + j);
				while (j < rlen) {
					strncat(name, (char *)(q + j + 1), c);
					j += i + 1;
					c = *(q + j);
					if (*(q + j) == '\0' || c > 64)
						break;
					strcat(name, ".");	
				}
				printf("\n         ns = %s", name);
			} else if (type == 5) {
				/* cname */
				printf(", data length = %d\n", rlen);
				printf("         Cname: ");

				/* output cname name */
				q = p + 2;
				c = *q;
				i = 0;
				while (1) {
					q++;
					for (j = 0; j < c; j++, i++)
						printf("%c", *(q + j));
					q += c;
					c = *q;
					if (c == 0 || c > 64)
						break;
					printf(".");
					i++;
				}
				if (i + 1 < rlen && c > 64) {
					sp = (u_short *)q;
					dmp_dns_rname((char *)(dh) + (ntohs(*sp) & 0x3fff), 
					    (char *)iph + iplen, name);
					printf(".%s", name);
				}
			} else if (type == 6) {
				printf(", data length = %d\n", rlen);
				printf("         Name server: ");

				/* output ns server name */
				q = p + 2;
				c = *q;
				while (1) {
					q++;
					for (j = 0; j < c; j++)
						printf("%c", *(q + j));
					q += c;
					c = *q;
					if (c > 64)
						break;
					printf(".");
				}
				dmp_dns_rname((char *)(dh) + (ptr & 0x3fff), (char *)iph + iplen, name);
				printf(".%s\n", name);
				
				sp = (u_short *)q;
				/* output mail server name */
				printf("         Responsible Mailbox: ");
				q = q + 2;
				c = *q;
				while (1) {
					q++;
					for (j = 0; j < c; j++)
						printf("%c", *(q + j));
					q += c;
					c = *q;
					if (c > 64)
						break;
					printf(".");
				}
				dmp_dns_rname((char *)(dh) + (ntohs(*sp) & 0x3fff), (char *)iph + iplen, name);
				printf(".%s\n", name);
				
				/*output serial number */
				q = q + 2;
				printf("         Serial number: %x\n", ntohl(((u_int *)q)[0]));
				printf("         Refresh interval: %s\n", 
				    dmp_dns_timestr(((u_int *)q)[1]));
				printf("         Retry interval: %s\n", 
				    dmp_dns_timestr(((u_int *)q)[2]));
				printf("         Expiration time: %s\n", 
				    dmp_dns_timestr(((u_int *)q)[3]));
				printf("         Minimum TTL: %s", 
				    dmp_dns_timestr(((u_int *)q)[4]));
			}
			p += 2 + rlen;
			printf("\n");
		} /* while */
	} /* if */
}


static char *
dmp_dns_timestr(u_int s)
{
	static char buf[64];
	int off = 0;
	
	buf[0] = '\0';
	s = ntohl(s);
	if (s / (3600 * 24)) {
		off += snprintf(buf + off, sizeof(buf), "%d days", s / (3600 * 24));
		s -= (s / (3600 * 24)) * (3600 * 24);
	}
	
	if (s / (3600)) {
		off += snprintf(buf + off, sizeof(buf), "%d hours", s / (3600));
		s -= (s / (3600)) * (3600);
	}
	
	if (s / (60)) {
		off += snprintf(buf + off, sizeof(buf), "%d minutes", s / (60));
		s -= (s / (60)) * (60);
	}
	if (s) {
		off += snprintf(buf + off, sizeof(buf), "%d minutes", s / (60));
	}
	
	return buf;
}

FILE *
open_dmpfile(const char *fname)
{
	char tfname[1024];
	FILE *fp;
	pcap_hdr_t phdr;
	time_t t0;
	struct tm *tm;
				
	t0 = time(0);
	tm = localtime(&t0);
		
	snprintf(tfname, sizeof(tfname), "%s_%4d%02d%02d%02d%02d%02d.pcap", 
	    fname, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, 
	    tm->tm_hour, tm->tm_min, tm->tm_sec);
				    
	fp = fopen(tfname, "ab");
	if (!fp)
		return NULL;
        		
	phdr.magic_number = 0xa1b2c3d4;
	phdr.version_major = 2;
	phdr.version_minor = 4;
	phdr.thiszone =  0;
	phdr.sigfigs = 0;
	phdr.snaplen = 0xffff;
	phdr.network = 1;
	
	fwrite(&phdr, sizeof(phdr), 1, fp);
	fflush(fp);
	return fp;
}

void 
close_dmpfile(FILE *fp)
{
	fclose(fp);
}

int 
dmp_packet2file(const struct packet *m, FILE *fp)
{
	pcaprec_hdr_t phdr;
	struct timeval ts;
	
	gettimeofday(&(ts), (void*)0);
	
	phdr.ts_sec = ts.tv_sec;
	phdr.ts_usec = ts.tv_usec;
	phdr.incl_len = m->len;
	phdr.orig_len = m->len;
	
	fwrite(&phdr, sizeof(phdr), 1, fp);
	fwrite(m->data, m->len, 1, fp);
	fflush(fp);
	
	return 0;
}

int 
dmp_buffer2file(const char *m, int len, FILE *fp)
{
	pcaprec_hdr_t phdr;
	struct timeval ts;
	
	gettimeofday(&(ts), (void*)0);
	
	phdr.ts_sec = ts.tv_sec;
	phdr.ts_usec = ts.tv_usec;
	phdr.incl_len = len;
	phdr.orig_len = len;
	
	if (!fp)
		return 0;

	fwrite(&phdr, sizeof(phdr), 1, fp);
	fwrite(m, len, 1, fp);
	fflush(fp);
	
	return 0;	
	
}
