#include <xprobe.h>
#include "usi++/usi++.h"
#define _XPROBE_MODULE
#include "xplib.h"
#include "xprobe_module.h"
#include "xprobe_module_hdlr.h"
#include "interface.h"
#include "target.h"
#include "icmp_port_unreach.h"
#include "util.h"

extern Interface *ui;

int icmp_port_unreach::init (void) {
	
	xprobe_debug(XPROBE_DEBUG_MODULES, "[%s]: Initialized\n", get_name());
return OK;

}

int icmp_port_unreach::exec (Target *Tgt, OS_Matrix *osmtx) {

	Fingerprint *icmp_unr= new Fingerprint;

    if ((get_icmp_unreach(Tgt, icmp_unr)) > 0) {
    /*****************
    * FINGERPRINTING *
    *****************/
		for (iter = os2finger.begin(); iter != os2finger.end(); iter++) {

			if (icmp_unr->get_p_unreach_ttl() > iter->second.get_p_unreach_ttl() - TTL_ERROR &&
				icmp_unr->get_p_unreach_ttl() < iter->second.get_p_unreach_ttl() + TTL_ERROR){
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_icmp_prec_bits() == iter->second.get_icmp_prec_bits()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_icmp_df() == iter->second.get_icmp_df()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_echoed_size() == iter->second.get_echoed_size()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_echoed_udpsum() == iter->second.get_echoed_udpsum()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_echoed_ipsum() == iter->second.get_echoed_ipsum()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_echoed_ipid() == iter->second.get_echoed_ipid()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_echoed_totlen() == iter->second.get_echoed_totlen()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}
			if (icmp_unr->get_echoed_3bit() == iter->second.get_echoed_3bit()) {
				osmtx->add_result (get_id(), iter->first, XPROBE_MATCH_YES);	
			}	
		}
	}

	delete icmp_unr;
return OK;
}

int icmp_port_unreach::fini (void) {
	close (sock);
	xprobe_debug(XPROBE_DEBUG_MODULES, "%s module has been deinitilized\n", get_name());
return OK;

}


int icmp_port_unreach_init(Xprobe_Module_Hdlr *pt) {

	icmp_port_unreach *ttl_calc = new icmp_port_unreach;
	int i;
	extern char *keyarr[];
	xprobe_mdebug(XPROBE_DEBUG_MODULES, "Initializing the TTL precalculation module\n");
	/* register module and keywords */
	pt->register_module(ttl_calc);
	for (i = 0; keyarr[i] != NULL; i++) 
		pt->add_keyword (ttl_calc->get_id(), keyarr[i]);
return(OK);
}

/* fingerptr - is a what we return */
int icmp_port_unreach::get_icmp_unreach(Target *Tgt, Fingerprint *fingerptr) {

	u_int sniffedbytes;
    u_char *payload;
	u_char echoedpack[1024];
	int iphlen=0;
    char icmpfilter[100];
    struct timeval timeo;
    struct in_addr local = Tgt->get_interface_addr(),
                target = Tgt->get_addr();
	struct ip *iph;
	struct udphdr *udph;

	memset (echoedpack, 0, sizeof(echoedpack));
    timeo.tv_usec = 0;
    timeo.tv_sec = TIMEOUT;
    /* XXX: we need a datagram of 70 bytes size */
    if ( (payload = (u_char *) malloc (/* sizeof (struct DNSHEADER) + 1) */ 70)) == NULL ) {
        ui->error ("icmp_port_unreach: failed to allocate memory for payload\n");
        return 0;
    }
    build_DNS_reply(payload);
    UDP udp(inet_ntoa(target));
    ICMP sn(inet_ntoa(local));
	sn.init_device (Tgt->get_interface(), 0, 1024);
    snprintf (icmpfilter, sizeof(icmpfilter), "icmp[0] == 3 and src %s",
                inet_ntoa(target));
    sn.setfilter(icmpfilter);
    udp.set_src(inet_ntoa(local));
	udp.set_ttl(255);
	udp.set_tos(0);
	udp.set_id(getrandom(U_INTMAX));
	udp.set_fragoff(IP_DF);
    udp.set_srcport(53);
    udp.set_dstport(Tgt->get_port (IPPROTO_UDP, XPROBE_TARGETP_CLOSED));
    udp.timeout(timeo);
    udp.sendpack((char *)payload,/* sizeof (struct DNSHEADER) */ 70);
    sn.timeout(timeo);
sniff_again:
    sniffedbytes = sn.sniffpack (echoedpack, sizeof(echoedpack));
    if (sn.timeout()) { /* timedout */
		free(payload);
        return 0;
	}
/* mark */
	/*** tos ***/
	xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ICMP IP ToS is: 0x%x\n", get_name(), sn.get_tos());
	fingerptr->put_icmp_prec_bits(sn.get_tos());
	/*** DF ***/
	if (sn.get_fragoff() & IP_DF) {
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ICMP IP DF bit set\n", get_name());
		fingerptr->put_icmp_df("1");
	} else {
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ICMP IP DF bit not set\n", get_name());
		fingerptr->put_icmp_df("0");
	}
		
	/* check if we have enuff for ip header */
	if (sniffedbytes >= sizeof (struct ip)) {
		iph = (struct ip *) echoedpack;
    	if (iph->ip_dst.s_addr != target.s_addr) /* not our icmp unreach */
        	goto sniff_again;   /* labels/gotos are bad :) */
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED Header len: %d IP checksum: 0x%x orig IP sum: 0x%x\n", 
						get_name(), iph->ip_hl<<2, iph->ip_sum, udp.get_sum());
		iphlen = iph->ip_hl<<2;
		/*** ip checksum ***/
		if (udp.get_sum() == iph->ip_sum) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP checksum OK\n", get_name());
			fingerptr->put_echoed_ipsum("OK");
		} else if (iph->ip_sum == 0) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP checksum = 0\n", get_name());
			fingerptr->put_echoed_ipsum("0");
		} else {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s](not implemented yet) ECHOED IP checksum BAD\n", get_name());
			fingerptr->put_echoed_ipsum("BAD");
		}
		/*** ttl ***/
		if (Tgt->get_distance() > 0) {
			fingerptr->put_p_unreach_ttl(sn.get_ttl() + Tgt->get_distance() - 1);
		} else {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] Figuring distance out from ICMP port unr\n", get_name());
			Tgt->set_distance (udp.get_ttl() - iph->ip_ttl);
			fingerptr->put_p_unreach_ttl(sn.get_ttl() + Tgt->get_distance());
		}
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ICMP TTL is: %d\n", get_name(), fingerptr->get_p_unreach_ttl());
		/*** ip id ***/
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP ID: %d Orig IP ID: %d \n", 
					get_name(), ntohs(iph->ip_id), udp.get_id());
		if (ntohs(iph->ip_id) == udp.get_id()) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP ID OK\n", get_name());
			fingerptr->put_echoed_ipid("OK");
		} else 
		if (ntohs(iph->ip_id) == flipp(udp.get_id())) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP ID FLIPPED\n", get_name()); 
			fingerptr->put_echoed_ipid("FLIPPED");
		}
		/*** ip len ***/
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP LEN: %d Orig IP LEN: %d\n", 
					get_name(), ntohs(iph->ip_len), udp.get_totlen());
		if (ntohs (iph->ip_len) == udp.get_totlen()) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP Len OK\n", get_name());
			fingerptr->put_echoed_totlen("OK");
		} else 
		if (ntohs (iph->ip_len) == udp.get_totlen() - 20) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP totlen < 20\n", get_name());
			fingerptr->put_echoed_totlen("<");
		} else
		if (ntohs (iph->ip_len) == udp.get_totlen() + 20) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP totlen > 20\n", get_name());
			fingerptr->put_echoed_totlen(">");
		} else {
            xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED IP totlen is unexpected (%i)\n", get_name(), ntohs(iph->ip_len));
        }
		/*** 3bit flags ***/
		if (ntohs (iph->ip_off) == udp.get_fragoff()) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED Frag Off Ok\n", get_name());
			fingerptr->put_echoed_3bit("OK");
		} else
		if (ntohs (iph->ip_off) == flipp (udp.get_fragoff())) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED Frag Off FLIPPED\n", get_name());
			fingerptr->put_echoed_3bit("FLIPPED");
		}

	}
	/* check if we have enuff for udp header */
	if (sniffedbytes >= (sizeof (struct ip) + sizeof (struct udphdr))) {

		/*** echoed size ***/
		if (sniffedbytes == 64)
			fingerptr->put_echoed_size(sniffedbytes);
		else if (sniffedbytes > 64)
			fingerptr->put_echoed_size(">64");
        
		udph = (struct udphdr *) (echoedpack+iphlen);
		xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED UDP checksum: 0x%x Original UDP checksum: 0x%x\n", 
			 get_name(), udph->check, udp.get_udpsum());
		/*** udp checksum ***/
		if (udph->check == udp.get_udpsum()) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED UPD checksum OK\n", get_name());
			fingerptr->put_echoed_udpsum("OK");
		} else
		if (udph->check == 0) {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED UPD checksum  = 0\n", get_name());
			fingerptr->put_echoed_udpsum("0");
		} else {
			xprobe_debug(XPROBE_DEBUG_MODULES, "[%s] ECHOED UPD checksum  BAD\n", get_name());
			fingerptr->put_echoed_udpsum("BAD");
		}
	}
	/* if we have only 8 bytes echoed doesn't give us anything */
	if (sniffedbytes == 8){
		fingerptr->put_echoed_size (sniffedbytes);
		return (1);
	}

return 1;
}
