<?php

/* 
    Kismet to kml (Google Earth) log file converter alias "Kismet Earth"
    Released by Philippe Niquille, philippe at niquille dot com, www.niquille.com
        
    ******************************
    *   version 0.1 beta3 - 28.9.2005
    ******************************
        
    features
    --------
    parses Kismet log files (.xml and .gps) to generate a google earth kml file
    - draws WarDrive Path as tesselated line
    - adds discovered access points to specific location on the map, parses meta information (mac, channel, packets, clients, etc.)
    - draws 3D polygons instead of simple AP images. The shape should represent the signal area as far as every corner has been
      captured correctly. The height depends on the channel. The color is yellow (open), green (closed) or blue when open and cloaked.
    - parses ap_manuf and client_manuf for vendor information
    - saves .kml as .kmz (zipped) upon request
    
    requirements
    ------------
    PHP5 because of simple_xml, file_get_content functions
    
    usage
    -----
    Use it as command line script as followed:
    /usr/bin/php -f parse.php <Kismet-Sep-16-2005-5> <kmz>
    
    The first argument is the kismet log file name without extension. The second one is optional.
        
    changelog
    ---------
    v0.1 beta3 - added pack as kmz functionality (zip compression)
    v0.1 beta2 - replaced real slow for loop with foreach and gps coordinates index array, huge speed boost
               - added vendor information parser, added to description
               - tweaked description section
    v0.1 beta1 - initial release
        
    todo/wishlist
    -------------
    - parse default password list
    - give the WarSession a name
    - merge multiple log files
    - get better icons
    - better polygons... (interpolation?)
    - do dynamic (network connection) stuff

*/

/*******************
*   set vars
********************/
$ap_manuf_file "ap_manuf";
$client_manuf_file "client_manuf";

//$argv[1] = "Kismet-20041223-10"; //debug
//$argv[2] = "kmz";

/*******************
*   execution time
********************/
$mtime explode(" ",microtime());
$mtime $mtime[1] + $mtime[0];
$starttime $mtime;

/*******************
*   open files
********************/
if(isset($argv[1]))//get filenames
{
    
$xml_file $argv[1].'.xml';
    
$gps_file $argv[1].'.gps';
}else
    exit(
"No argument specified\n");
    
if(
file_exists($xml_file) && file_exists($gps_file))
{
    
$xml simplexml_load_file($xml_file); //load kismet log
    
$gps simplexml_load_file($gps_file); //load corresponding detailed gps log
}else
    exit(
"Files couldn't be opened: ".$xml_file.", ".$gps_file."\n");

if(
file_exists($ap_manuf_file) && file_exists($client_manuf_file))
{
    
$ap_manuf parse_vendor($ap_manuf_file); //load access point vendor data
    
$client_manuf parse_vendor($client_manuf_file); //load client vendor data
}else
    exit(
"Files couldn't be opened: ".$ap_manuf_file.", ".$client_manuf_file."\n");

$array object2array($xml); //convert simplexml object to php array, easier to work on..

/*******************
*   do meta stuff
********************/

$meta['session-duration'] = strtotime($xml['end-time'])-strtotime($xml['start-time']); //get duration (unix timestamp, in seconds)
$meta['session-duration'] = floor($meta['session-duration']/3600).' h, '.floor($meta['session-duration']/60).' min'//format

$meta['net-count'] = count($array['wireless-network']);
$meta['parsed'] = $meta['net-count'];

/*******************
*   parse .gps file and build gps coordinates index
********************/
foreach($gps->{'gps-point'} as $value)
{
    
$coordinates[(string) $value['bssid']][] = array((string) $value['lon'], (string) $value['lat']);
}

/*******************
*   loop trough kismet wireless nets - types: infrastructure, probe
********************/
foreach($array['wireless-network'] as $key => $network)
{
    if(
    (
$network['gps-info']['min-lat'] == '90.000000' && $network['gps-info']['min-lon'] == '180.000000'//remove nets with no gps coordinates, no sense to display them on a map
    
|| $network['BSSID'] == '00:00:00:00:00:00'
    
|| $xml->{'wireless-network'}[$key]['type'] != 'infrastructure'//remove zero bssid nets, just junk
    
{
        if(
$xml->{'wireless-network'}[$key]['type'] == 'probe'//count probes
            
$meta['probe-count']++;
        
        
$meta['net-count']--; //remove non infrastructure from total net count, exit foreach
        
continue;
       
    }elseif(!isset(
$lookatnet)) //get the first usable net to set LookAt coordinates
        
$lookatnet $key;
        
    
$kml .= "<Placemark>\n<name>";
    if(empty(
$network['SSID'])) $kml .= "no ssid";
    else 
$kml .= $network['SSID'];
    
$kml .= "</name>\n";
    
    
/*******************
    *   build description html
    ********************/
    
$seenfor strtotime($xml->{'wireless-network'}[$key]['last-time'])-strtotime($xml->{'wireless-network'}[$key]['first-time']);
    
    
$kml .= "\t<description><![CDATA[seen for ".floor($seenfor/60)." min (".floor($seenfor)." sec)<br>"//get duration
    
$kml .= "first seen: ".$xml->{'wireless-network'}[$key]['first-time']."<br>last seen: ".$xml->{'wireless-network'}[$key]['last-time']."<br><hr>\n";
    
$kml .= "BSSID: ".$network['BSSID']."<br>\nchannel: ".$network['channel']."<br>\n";
  
    if(
$xml->{'wireless-network'}[$key]['wep'] == 'true'//net encrypton on?
    
{
        if(
$network['encryption'] == 'WEP')
            
$kml .= "<font color=\"green\">encryption: WEP</font>";
            
        elseif(
is_array($network['encryption']))
        {
            
$kml .= "<font color=\"green\">encryption: ";

            foreach(
$network['encryption'] as $enc)
            {
               
$kml .= $enc." ";
            }
            
$kml .= "</font>";
        }
    }else
        
$kml .= "<font color=\"red\">no encryption</font>";
    
    
$kml .= "<br>\ncloaked: ".$xml->{'wireless-network'}['cloaked'];
    
$kml .= "<br>\nmax rate: ".$xml->{'wireless-network'}['maxrate'];
    
    if(isset(
$network['ip-address']))
        
$kml .= "<br>IP range: ".$network['ip-address']['ip-range'];
        
     
/*******************
    *   get vendor info
    ********************/
    
if(get_manuf((string) $network['BSSID'],$ap_manuf) != false)
    {
        
$manuf get_manuf((string) $network['BSSID'],$ap_manuf);
        
$kml .= "<hr>\n<b>Vendor Info</b><br>Type: ".$manuf[0]." ".$manuf[1];
        if(!empty(
$manuf[2]))
            
$kml .= "<br>default SSID: ".$manuf[2].", channel ".$manuf[3];
        if(!empty(
$manuf[4]))
            
$kml .= "<br>default ip range: ".$manuf[4];
    }
    
    
$kml .= "<hr>\n<b>GPS coordinates</b><br>\nfirst-seen: ".$network['gps-info']['min-lat']." lat, ".$network['gps-info']['min-lon']." lon, ".round($network['gps-info']['min-alt'],1)." alt";
    
$kml .= "\n<br>last-seen: ".$network['gps-info']['max-lat']." lat, ".$network['gps-info']['max-lon']." lon, ".round($network['gps-info']['max-alt'],1)." alt";
    
$kml .= "<hr>\n<b>captured packets</b><br>\n";
    
    
/*******************
    *   list the packets section (IV's, LLC, Data, etc.)
    ********************/
    
foreach($network['packets'] as $tag => $value)
    {
        
$kml .= $tag.": ".$value."<br>\n";
    }
    
$kml .= "total datasize captured: ".$network['datasize']."\n";
    
    
/*******************
    *   parse clients, if existant - types: fromds, tods
    ********************/
    
if(isset($network['wireless-client']))
    {
        
$kml .="<hr>\n<b>captured ".count($network['wireless-client'])." attached clients</b>\n<br>\n<ul>\n";
        
        foreach(
$network['wireless-client'] as $client//list all attached and captured clients
        
{
            
$kml .= "<li>MAC: ".$client['client-mac'];
            
            if(
get_manuf((string) $client['client-mac'],$client_manuf) != false)
            {
                
$manuf get_manuf((string) $client['client-mac'],$client_manuf);
                
$kml .= " (".$manuf[0]." ".$manuf[1].")";
            }
        
            
$kml .= "<br>IP: ";
            if(isset(
$client['client-ip-address'])) //IP discovered?
                
$kml .= $client['client-ip-address'];
            else
               
$kml .= "none discovered";
           
$kml .= "</li>\n";
        }
    }
    
    
$kml .= "</ul>]]>\n</description>\n\t<View>\n\t<longitude>".$network['gps-info']['min-lon']."</longitude>\n";
    
$kml .= "\t<latitude>".$network['gps-info']['min-lat']."</latitude>\n</View>\n";
    
    
$kml .= "\t<visibility>1</visibility>\n\t<styleUrl>root://styleMaps#default?iconId=0x307</styleUrl>\n";
    
    if(
$xml->{'wireless-network'}[$key]['wep'] == 'true'//net encrypton on?
    
{
        if(
$argv[2] == 'kmz'//if kmz requested point to local icons
            
$kml .= "\t<Style>\t<icon>node_closed.png</icon></Style>";
        else
            
$kml .= "\t<Style>\t<icon>http://www.niquille.com/wp-content/node_closed.png</icon></Style>";
            
        
$meta['wep-count']++;
    }else{
        if(
$argv[2] == 'kmz')
            
$kml .= "\t<Style>\t<icon>node_open.png</icon></Style>";
        else
            
$kml .= "\t<Style>\t<icon>http://www.niquille.com/wp-content/node_open.png</icon></Style>";
    }
    
    
$kml .= "\t<Point>\n\t<coordinates>".$network['gps-info']['min-lon'].", ".$network['gps-info']['min-lat'].", ".$network['gps-info']['min-alt']."</coordinates>\n</Point>\n</Placemark>\n";
    
    
/*******************
    *   add gps coordinates to path source
    ********************/
    
$line .= $network['gps-info']['min-lon'].", ".$network['gps-info']['min-lat'].", ".$network['gps-info']['min-alt']." ";
    
    
/*******************
    *   assemble polygon placemark
    ********************/
    
if($network['channel'] != '0'//we don't display networks with no height
    
{
    
    
$poly .= "<Placemark>\n<name>".$network['BSSID']."</name>\n<visibility>0</visibility>\n<open>0</open>\n";
    
$poly .= "<Style>\n\t<LineStyle>\n\t<width>1.5</width></LineStyle>\n\t<PolyStyle>";
    
    if(
$xml->{'wireless-network'}[$key]['wep'] == 'true'//set color 
        
$poly .= "<color>8f00ff00</color>\n"//green, closed
    
elseif($meta[$key]['cloaked'] == 'true')
        
$poly .= "<color>7dff0000</color>\n"//blue, cloaked
    
else
        
$poly .= "<color>7d00ffff</color>\n"//yellow, open
    
    
$poly .= "</PolyStyle>\n</Style>\n<Polygon>\n<extrude>1</extrude>\n<tessellate>0</tessellate>\n";
    
$poly .= "<altitudeMode>relativeToGround</altitudeMode>\n<outerBoundaryIs>\n<LinearRing>\n<extrude>0</extrude>";
    
$poly .= "<tessellate>0</tessellate>\n<altitudeMode>clampToGround</altitudeMode>\n<coordinates>";

    
$poly_alt $network['channel']*10;
    
    foreach(
$coordinates[$network['BSSID']] as $index => $gpoints)
    {
        if(
$gpoints[0] != $coordinates[$network['BSSID']][$index-1][0]
           && 
$gpoints[1] != $coordinates[$network['BSSID']][$index-1][1]) //remove duplicates
                
$poly .= $gpoints[0].", ".$gpoints[1].", ".$poly_alt." ";
    }
    
    
$poly .= $coordinates[$network['BSSID']][0][0].", ".$coordinates[$network['BSSID']][0][1].", ".$poly_alt." "//finish polygon at first coordinate
    
$poly .= "</coordinates>\n</LinearRing>\n</outerBoundaryIs>\n</Polygon>\n</Placemark>";
    
    }
//end foreach

/*******************
*   assemble final KML xml file
********************/
$meta['wep-count'] = ($meta['wep-count']/$meta['net-count'])*100//encrypted percentage of total nets

$kml_final "<?xml version='1.0' encoding='UTF-8'?>\n<kml xmlns='http://earth.google.com/kml/2.0'>\n<Folder>\n<name>";
$kml_final .= $meta['session-duration'].', '.$meta['net-count'].' nets, '.$meta['probe-count'].' probes ('.$meta['parsed'].' total parsed) - '.date('d.m.Y',strtotime($xml['start-time']));
$kml_final .= "</name>\n<visibility>1</visibility><description>encrypted: ".round($meta['wep-count'],2)."%</description>\n";

//fly to first network
//range: zoom level, tilt: view angle
$kml_final .= "<LookAt>\n\t<longitude>".$array['wireless-network'][$lookatnet]['gps-info']['min-lon']."</longitude>\n\t<latitude>".$array['wireless-network'][$lookatnet]['gps-info']['min-lat']."</latitude>";
$kml_final .= "<range>1000</range><tilt>54</tilt><heading>-35</heading></LookAt>";

// output the WarDrive Path in a separate placemark
$kml_final .= "\n<Placemark>\n<name>WarSession Path</name>\n<description></description>\n<visibility>0</visibility>\n";
$kml_final .= "\t<Style>\n\t<geomColor>BB0000FF</geomColor>\n<geomScale>3</geomScale>\n</Style>\n\t<LineString>";
$kml_final .= "\n\t<tessellate>1</tessellate>\n\t<coordinates>$line</coordinates>\n</LineString>\n</Placemark>\n"//tesselate adjusts to terrain

//output 3D visualized polygons
$kml_final .= "<Folder>\n<name>3D visualized view</name><open>0</open>\n";
$kml_final .= $poly;
$kml_final .= "</Folder>\n";

$kml_final .= $kml;

$kml_final .= "</Folder>\n</kml>";

//$kml_final = simplexml_load_string($kml_final); //check kml syntax (xml), fails because of <br>, <li> in description tag
//$kml_final->asXML('new.kml');

writeXML($argv[1].".kml",$kml_final); //write final XML file as KML to disk
echo $argv[1].".kml saved.\n";

if(
$argv[2] == 'kmz'//output as kmz (zipped) if second command line argument supplied
{
    
$zip = new zipfile();
    
$zip->addFiles(array($argv[1].".kml","node_open.png","node_closed.png")); //also include icons
    
$zip->output($argv[1].".kmz");
    echo 
$argv[1].".kmz saved.\n";
}

/*******************
*   execution time
********************/
$mtime explode(" ",microtime());
$mtime $mtime[1] + $mtime[0];
$endtime $mtime;
$totaltime = ($endtime $starttime);
echo 
"Parsed in ".round($totaltime,2)." seconds\n";

/*******************
*   functions
********************/
function parse_vendor($filename)
{
$array explode("\n",file_get_contents($filename));

foreach(
$array as $row)
{
    
$tabs explode("\t",$row);
    
$mac explode("/",$tabs[0]);
    
array_shift($tabs); //pop off first element (mac) 
    
    
if($mac[1] == "FF:FF:FF:FF:00:00")
        
$mac rtrim($mac[0],":00:00");
    elseif(
$mac[1] == "FF:FF:FF:00:00:00")
        
$mac rtrim($mac[0],":00:00:00").":*";
    
    
$macarray[(string) $mac] = $tabs;
    
//end foreach
return $macarray;
/*
get's array with all the different mac ranges and vendor/manufactor info:

Array ( [00:00:22:22] => 
    Array ( [0] => Lucent 
            [1] => Orinoco Silver 
            [2] => 
            [3] => 0 
            [4] => )
        etc.
*/
}

function 
get_manuf($mac,$macarray)
{
$longmac substr($mac,0,11); //format to array style
$shortmac substr($mac,0,9)."*";

if(isset(
$macarray[$longmac]))
    return 
$macarray[$longmac];
    
elseif(isset(
$macarray[$shortmac]))
    return 
$macarray[$shortmac];
    
else
    return 
false//no info found
}

function 
writeXML($filename$kml)
{
   
$handle fopen($filename,'w+'); //open xml file for writing
  // fwrite($handle, header("Content-type: text/xml")); //write header to xml file
   
fwrite($handle$kml);
   
fclose($handle);
}

/*******************
*   pulled from php.net
********************/
function object2array($object)
{
   
$return NULL;
      
   if(
is_array($object))
   {
       foreach(
$object as $key => $value)
           
$return[$key] = object2array($value);
   }
   else
   {
       
$var get_object_vars($object);
          
       if(
$var)
       {
           foreach(
$var as $key => $value)
               
$return[$key] = ($key && !$value) ? NULL object2array($value);
       }
       else return 
$object;
   }

   return 
$return;
}

/*
 * zipLib v1.1
 *
 * Last Modification and Extension By :
 *
 *  Hasin Hayder
 *  HomePage : www.hasinme.info
 *  Email : countdraculla@gmail.com
 *  IDE : PHP Designer 2005
 *
 *
 * Originally Based on :
 *
 *  http://www.zend.com/codex.php?id=535&single=1
 *  By Eric Mueller <eric@themepark.com>
 *
 *  http://www.zend.com/codex.php?id=470&single=1
 *  by Denis125 <webmaster@atlant.ru>
 *
 *  a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified
 *  date and time of the compressed file
*/

class zipfile
{
    
/**
     * Array to store compressed data
     *
     * @var  array    $datasec
     */
    
var $datasec      = array();

    
/**
     * Central directory
     *
     * @var  array    $ctrl_dir
     */
    
var $ctrl_dir     = array();

    
/**
     * End of central directory record
     *
     * @var  string   $eof_ctrl_dir
     */
    
var $eof_ctrl_dir "\x50\x4b\x05\x06\x00\x00\x00\x00";

    
/**
     * Last offset position
     *
     * @var  integer  $old_offset
     */
    
var $old_offset   0;


    
/**
     * Converts an Unix timestamp to a four byte DOS date and time format (date
     * in high two bytes, time in low two bytes allowing magnitude comparison).
     *
     * @param  integer  the current Unix timestamp
     *
     * @return integer  the current date in a four byte DOS format
     *
     * @access private
     */
    
function unix2DosTime($unixtime 0) {
        
$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);

        if (
$timearray['year'] < 1980) {
            
$timearray['year']    = 1980;
            
$timearray['mon']     = 1;
            
$timearray['mday']    = 1;
            
$timearray['hours']   = 0;
            
$timearray['minutes'] = 0;
            
$timearray['seconds'] = 0;
        } 
// end if

        
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
                (
$timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
    } 
// end of the 'unix2DosTime()' method


    /**
     * Adds "file" to archive
     *
     * @param  string   file contents
     * @param  string   name of the file in the archive (may contains the path)
     * @param  integer  the current timestamp
     *
     * @access public
     */
    
function addFile($data$name$time 0)
    {
        
$name     str_replace('\\''/'$name);

        
$dtime    dechex($this->unix2DosTime($time));
        
$hexdtime '\x' $dtime[6] . $dtime[7]
                  . 
'\x' $dtime[4] . $dtime[5]
                  . 
'\x' $dtime[2] . $dtime[3]
                  . 
'\x' $dtime[0] . $dtime[1];
        eval(
'$hexdtime = "' $hexdtime '";');

        
$fr   "\x50\x4b\x03\x04";
        
$fr   .= "\x14\x00";            // ver needed to extract
        
$fr   .= "\x00\x00";            // gen purpose bit flag
        
$fr   .= "\x08\x00";            // compression method
        
$fr   .= $hexdtime;             // last mod time and date

        // "local file header" segment
        
$unc_len strlen($data);
        
$crc     crc32($data);
        
$zdata   gzcompress($data);
        
$zdata   substr(substr($zdata0strlen($zdata) - 4), 2); // fix crc bug
        
$c_len   strlen($zdata);
        
$fr      .= pack('V'$crc);             // crc32
        
$fr      .= pack('V'$c_len);           // compressed filesize
        
$fr      .= pack('V'$unc_len);         // uncompressed filesize
        
$fr      .= pack('v'strlen($name));    // length of filename
        
$fr      .= pack('v'0);                // extra field length
        
$fr      .= $name;

        
// "file data" segment
        
$fr .= $zdata;

        
// "data descriptor" segment (optional but necessary if archive is not
        // served as file)
        
$fr .= pack('V'$crc);                 // crc32
        
$fr .= pack('V'$c_len);               // compressed filesize
        
$fr .= pack('V'$unc_len);             // uncompressed filesize

        // add this entry to array
        
$this -> datasec[] = $fr;

        
// now add to central directory record
        
$cdrec "\x50\x4b\x01\x02";
        
$cdrec .= "\x00\x00";                // version made by
        
$cdrec .= "\x14\x00";                // version needed to extract
        
$cdrec .= "\x00\x00";                // gen purpose bit flag
        
$cdrec .= "\x08\x00";                // compression method
        
$cdrec .= $hexdtime;                 // last mod time & date
        
$cdrec .= pack('V'$crc);           // crc32
        
$cdrec .= pack('V'$c_len);         // compressed filesize
        
$cdrec .= pack('V'$unc_len);       // uncompressed filesize
        
$cdrec .= pack('v'strlen($name) ); // length of filename
        
$cdrec .= pack('v');             // extra field length
        
$cdrec .= pack('v');             // file comment length
        
$cdrec .= pack('v');             // disk number start
        
$cdrec .= pack('v');             // internal file attributes
        
$cdrec .= pack('V'32 );            // external file attributes - 'archive' bit set

        
$cdrec .= pack('V'$this -> old_offset ); // relative offset of local header
        
$this -> old_offset += strlen($fr);

        
$cdrec .= $name;

        
// optional extra field, file comment goes here
        // save to central directory
        
$this -> ctrl_dir[] = $cdrec;
    } 
// end of the 'addFile()' method


    /**
     * Dumps out file
     *
     * @return  string  the zipped file
     *
     * @access public
     */
    
function file()
    {
        
$data    implode(''$this -> datasec);
        
$ctrldir implode(''$this -> ctrl_dir);

        return
            
$data .
            
$ctrldir .
            
$this -> eof_ctrl_dir .
            
pack('v'sizeof($this -> ctrl_dir)) .  // total # of entries "on this disk"
            
pack('v'sizeof($this -> ctrl_dir)) .  // total # of entries overall
            
pack('V'strlen($ctrldir)) .           // size of central dir
            
pack('V'strlen($data)) .              // offset to start of central dir
            
"\x00\x00";                             // .zip file comment length
    
// end of the 'file()' method
    

    /**
     * A Wrapper of original addFile Function
     *
     * Created By Hasin Hayder at 29th Jan, 1:29 AM
     *
     * @param array An Array of files with relative/absolute path to be added in Zip File
     *
     * @access public
     */
    
function addFiles($files /*Only Pass Array*/)
    {
        foreach(
$files as $file)
        {
        if (
is_file($file)) //directory check
        
{
            
$data implode("",file($file));
                    
$this->addFile($data,$file);
                }
        }
    }
    
    
/**
     * A Wrapper of original file Function
     *
     * Created By Hasin Hayder at 29th Jan, 1:29 AM
     *
     * @param string Output file name
     *
     * @access public
     */
    
function output($file)
    {
        
$fp=fopen($file,"w");
        
fwrite($fp,$this->file());
        
fclose($fp);
    }

    

// end of the 'zipfile' class

?>