/*

db.c

Functions to manage the MD5 database that holds information about
original MD5 signatures in TCP segments. The cracking function
db_crack() is also defined here.

$Id: db.c,v 1.6 2003/07/15 19:10:59 peloy Exp $

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcap.h>
#include <openssl/md5.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/ether.h>
#include <arpa/inet.h>

#include "bgpcrack.h"

struct md5_state {
	int frame_number;
	char original_md5sum[MD5_DIGEST_LENGTH];
	MD5_CTX partial_md5sum;
} *md5_states = NULL;

int nframes; /* Number of frames processed */
int npackets; /* Number of packets with TCP signatures */

/*
This function is called for each frame in the trace file. The function
checks that:

- All the packet was captured.
- Frame has a TCP segment with options.
- TCP option 19 is present.

Then it extracts the MD5 digest from TCP option 19 and stores it in a
dynamic array. It also calculates a *partial* MD5 digest of the TCP
segment and stores it in the same dynamic array. The db_crack() function
will then use the TCP signatures in this array to try to guess the
password.
*/
void process_packet(u_char *unused, const struct pcap_pkthdr *header,
		const u_char *packet)
{
	struct ether_header *ether;
	struct iphdr *ip;
	struct tcphdr *tcp;
	int optlen;
	u_char *options;
	u_char *tcpsig = NULL;

	nframes++;

	if (header->caplen != header->len) {
		/*
		 * Whoever ran tcpdump or ethereal to capture packets didn't
		 * capture the whole packet, just the first bytes of eack pkt.
		 */
		debug("%d != %d!!! Don't have complete packet. Skipping.\n",
			   header->caplen, header->len);
		return;
	}

	ether = (struct ether_header *) packet;
	if (ntohs(ether->ether_type) != ETHERTYPE_IP) {
		/*
		 * This is obvious: packet is not IP there's nothing to do
		 * here.
		 */
		debug("Packet %d: protocol is not IP. Skipping.\n",
		      nframes);
		return;
	}

	ip = (struct iphdr *) (packet + sizeof(struct ether_header) );
	if (ip->protocol != IPPROTO_TCP) {
		/*
		 * BGP runs over TCP. If the packet doesn't have TCP we don't
		 * do anything here either
		 */
		debug("Packet %d: not a TCP segment. Skipping.\n",
		      nframes);
		return;
	}

	tcp = (struct tcphdr *) ( (char *) ip + ip->ihl*4);
	if (tcp->doff*4 == sizeof(struct tcphdr) ) {
		debug("Packet %d: TCP segment has no TCP options. Skipping.\n",
		      nframes);
		return;
	}

	/*
	 * Cycle through all TCP options; we're looking for option 19, so we
	 * ignore any other options we find.
	 */
	for (options = (u_char *) tcp + sizeof(struct tcphdr);
		 options[0] != TCPOPT_EOL; options += optlen) {

		/*
		 * TCP option 1 is no-op, and has a lenght of 1. All other
		 * options should have the length in bytes in the byte 
		 * following the option type. However, I need to check if 
		 * there are other cases where the option is only 1 byte 
		 * long (I am sure there are, I am just being lazy.)
		 */
		optlen = options[0] == TCPOPT_NOP ? 1 : options[1];

		if (options[0] == TCPOPT_SIGNATURE) {
			/*
			 * OK, we found the TCP MD5 signature - let's leave
			 * the loop
			 */
			tcpsig = &options[2];
			break;
		}
	}

	if (!tcpsig) {
		debug("Packet %d: there is no TCP signature option. "
		      "Skipping.\n",
		      nframes);
		return;
	}

#if DEBUG
	printf("Cracked TCP sig.: ");
	print_md5_digest(new_md5_digest);
	printf("\n");
		
	printf("Packet TCP sig.:  ");
	print_md5_digest(tcpsig);
	printf("\n-------\n");
#endif

	/* Resize array of MD5 information - this is really tricky!!! */
	if (!(md5_states = realloc(md5_states,
	    (npackets + 1)*sizeof(struct md5_state))))
		error(1, "realloc()");

	/* Initialize new element in the md5_stats[] array */
	md5_states[npackets].frame_number = nframes;
	memcpy(&md5_states[npackets].original_md5sum, tcpsig,
	       MD5_DIGEST_LENGTH);
	memcpy(&md5_states[npackets].partial_md5sum, tcp_sig(ip, NULL),
	       sizeof(MD5_CTX) );

	npackets++;
}

int db_init(char *dumpfile, char *pcap_expression)
{
	/* libpcap-related stuff */
	char errbuf[PCAP_ERRBUF_SIZE];
	pcap_t *pcap;
	int optimize = 1; /* Optimize compilation of pcap expression? */
	bpf_u_int32 netmask = 0; /* for pcap_compile() */
	struct bpf_program fcode;

	int n;

	if ( (pcap = pcap_open_offline(dumpfile, errbuf) ) == NULL)
		error(1, "%s", errbuf);

	debug("libcap version: %d.%d\n\n", pcap_major_version(pcap),
		pcap_minor_version(pcap) );

	if (pcap_compile(pcap, &fcode, pcap_expression, optimize, netmask) < 0)
		error(1, "%s", pcap_geterr(pcap) );

	if (pcap_setfilter(pcap, &fcode) < 0)
		error(1, "%s", pcap_geterr(pcap) );

	/*
	 * The BPF program is not needed anymore - let's free up allocated
	 * mem.
	 */
	pcap_freecode(&fcode);

	if (options.segments_to_test <= 0)
		/* Process all frames in the trace file */
		pcap_loop(pcap, -1, process_packet, NULL);
	else {
		/*
		 * Process a specific number of frames with TCP sigs.
		 * Don't know why pcap_loop() doesn't work.
		 */
		while ( (n = pcap_dispatch(pcap, 1, process_packet, NULL) ) > 0
		       && npackets < options.segments_to_test);

		if (n < 0)
			error(1, "%s", pcap_geterr(pcap) );
	}

	pcap_close(pcap);

	printf("%d frames have been processed.\n"
	       "There are %d TCP segments with MD5 signatures.\n"
	       "Using %d bytes for storage of MD5 data.\n",
	       nframes, npackets, npackets*sizeof(struct md5_state));

	return md5_states == NULL;
}

void db_flush(void)
{
	if (md5_states) {
		free(md5_states);
		md5_states = NULL;
	}
}

void db_crack(char *key)
{
	int i;
	char md5_digest[MD5_DIGEST_LENGTH];
	MD5_CTX partial_md5sum;

	/* Just in case db_init() hasn't been called */
	if (!md5_states) {
		fprintf(stderr, "db_init() has not been called.\n");
		return;
	}

	for (i = 0; i < npackets; i++) {
		count++;

		/*
		 * Calculate MD5 sum of password (step 4 in section 2.0 of
		 * RFC2385)
		 */
		partial_md5sum = md5_states[i].partial_md5sum;
		MD5_Update(&partial_md5sum, key, strlen(key) );

		MD5_Final(md5_digest, &partial_md5sum);

		if (!memcmp(md5_digest, md5_states[i].original_md5sum,
		    MD5_DIGEST_LENGTH) ) {
			printf("Found a match in frame %d.\n"
			       "Password is '%s'. Bye.\n",
			       md5_states[i].frame_number, key);
			exit(1);
		}
	}
}
