<?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 beta2 - 26.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
    
    requirements
    ------------
    PHP5 because of simple_xml, file_get_content functions
        
    changelog
    ---------
    v0.1 beta1 - initial release
    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
        
    todo/wishlist
    -------------
    - parse default password list
    - save as kmZ
    - merge multiple log files
    - get better icons
    - better polygons...
    - do dynamic (network connection) stuff

*/

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

//$argv[1] = "Kismet-test-log"; //debug

/*******************
*   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?
    
{
        
$kml .= "\t<Style>\t<icon>http://www.niquille.com/wp-content/node_closed.png</icon></Style>";
        
$meta['wep-count']++;
    }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);

/*******************
*   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;
}

?>