/* $Id: icmp_inforeq.cc,v 1.1 2002/07/22 22:26:32 fygrave Exp $ */
/*
** Copyright (C) 2001 Fyodor Yarochkin <fygrave@tigerteam.net>,
**                    Ofir Arkin       <ofir@sys-security.com>
**
** 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.
**
** All material for nonprofit, educational use only.
**
** 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 "xprobe.h"
#include "usi++/usi++.h"
#include <signal.h>
#include <setjmp.h>
#define _XPROBE_MODULE
#include "xplib.h"
#include "xprobe_module.h"
#include "xprobe_module_hdlr.h"
#include "interface.h"
#include "target.h"
#include "icmp_inforeq.h"

#define TIMEOUT 5
extern Interface *ui;

/* initialization function */

int icmp_inforeq_mod_init(Xprobe_Module_Hdlr *pt) {

    ICMP_Inforeq_Mod *module = new ICMP_Inforeq_Mod;

    xprobe_mdebug(XPROBE_DEBUG_MODULES, "Initializing the ICMP Inforeq module\n");
    pt->register_module(module);
    pt->add_keyword(module->get_id(),"icmp_info_reply");
    pt->add_keyword(module->get_id(),"icmp_info_reply_ttl");

return OK;
}



int ICMP_Inforeq_Mod::init(void) {

    xprobe_debug(XPROBE_DEBUG_MODULES, "%s module initialized\n", get_name());
    return OK;
}


int ICMP_Inforeq_Mod::exec(Target *tg, OS_Matrix *os) {
    
    xprobe_debug(XPROBE_DEBUG_MODULES, "--%s module has been executed against: %s\n", get_name(),
            inet_ntoa(tg->get_addr()));

    current_os = os;
    do_icmp_query(tg);
    
    return OK;
}

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

void ICMP_Inforeq_Mod::sig_insert(int os_id, int val) {

    if (sig.find(os_id) != sig.end()) {
        ui->msg("OS %i - dublicate signature\n", os_id);
        return;
    }
    sig.insert(pair <int, int>(os_id, val));

}

void ICMP_Inforeq_Mod::sig_ttl_insert(int os_id, int vall, int valh) {
    ttl_val_s t;

    if (sig_ttl.find(os_id) != sig_ttl.end()) {
        ui->msg("OS %i - dublicate signature\n", os_id);
        return;
    }

    t.high = valh;
    t.low  = vall;

    sig_ttl.insert(pair <int, ttl_val_s>(os_id, t));

}




int ICMP_Inforeq_Mod::parse_keyword(int os_id, const char *kwd, const char *val)  {
    
    xprobe_debug(XPROBE_DEBUG_MODULES, "Parsing for %i : %s  = %s\n",
                                                        os_id,  kwd, val);
    /* XXX: this is lamyer-ass parser will be replaced with a real class
     * later */

    if (!strcmp(kwd, "icmp_info_reply"))  {
        switch (val[0]) {
            case 'n':
                sig_insert(os_id, 0);
               return OK;
            case 'y':
                sig_insert(os_id, 1);
                return OK;
            default:
                ui->msg("ICMP Inforeq module: %s = unknown value %s\n", kwd, val);
        }
        return FAIL;
    } else if (!strcmp(kwd, "icmp_info_reply_ttl")) {
        if (val[0] == '<')
            sig_ttl_insert(os_id, 0, atoi(val+1));
        else if (val[0] == '>')
            sig_ttl_insert(os_id, atoi(val+1), 256);
        else 
            sig_ttl_insert(os_id, atoi(val) - 1, atoi(val) + 1);
        
        return OK;     
        
    } else {
        /* we shouldn't be here ... still..*/
        /* fuck it for the mom ;-) */
        return OK;
    }
           
};



int ICMP_Inforeq_Mod::do_icmp_query(Target *tg) {

    char buf[1024];
    struct timeval tv;
    int ret;
    int done;
    unsigned short int icmpp_id;
    struct in_addr local;

/* our lamyer randomizer ;-p */
    srand(time(NULL));
    icmpp_id = rand();
    local = tg->get_interface_addr();

    ICMP icmpp(inet_ntoa(tg->get_addr()));
    ICMP sn(inet_ntoa(local));
    sn.init_device(tg->get_interface(), 0, 1500);

    tv.tv_sec = TIMEOUT;
    tv.tv_usec = 0;

    icmpp.set_src(inet_ntoa(tg->get_interface_addr()));
    icmpp.set_icmpId(icmpp_id);
    icmpp.set_type(ICMP_INFO_REQUEST);
    fflush(stderr);
    ret = -1;
    
    icmpp.timeout(tv);
    sn.timeout(tv);
    ret = icmpp.sendpack("XPROBE2-probe");
    done = 0;
    while (!done) {
        ret = sn.sniffpack(buf, sizeof(buf));
        /* packet response */
        if (ret > 0 && sn.get_src() != local.s_addr 
            && sn.get_icmpId() == icmpp_id) done = 1;
        if (ret < 1) done = 1; /* timeout */    
    }
    
    do_response_check(ret);
    if (ret > 0) 
        do_ttl_check(sn.get_ttl());

    return OK;

}


void ICMP_Inforeq_Mod::do_response_check(int res) {
    map <int, int>::iterator sig_i;

    for (sig_i = sig.begin(); sig_i != sig.end(); sig_i++) {
    
        if (res > 0 && (*sig_i).second == 1)
            current_os->add_result(get_id(), (*sig_i).first, XPROBE_MATCH_YES);
        else if (res < 1 && (*sig_i).second == 0) {
            current_os->add_result(get_id(), (*sig_i).first, XPROBE_MATCH_YES);
            /* XXX: ideally we should double-check that ttl sigs don't exist
             * */
            current_os->add_result(get_id(), (*sig_i).first, XPROBE_MATCH_YES);
        } else    
            current_os->add_result(get_id(), (*sig_i).first, XPROBE_MATCH_NO);

    }
}

void ICMP_Inforeq_Mod::do_ttl_check(int ttl) {
    map <int, ttl_val_s>::iterator sig_i;

    for (sig_i = sig_ttl.begin(); sig_i != sig_ttl.end(); sig_i++) {
    
        if ((*sig_i).second.low < ttl && ttl < (*sig_i).second.high)
            current_os->add_result(get_id(), (*sig_i).first, XPROBE_MATCH_YES);
        else    
            current_os->add_result(get_id(), (*sig_i).first, XPROBE_MATCH_NO);

    }
}
