Last active
December 13, 2018 02:59
-
-
Save ScottHelme/5c6869e17c3e9c8b2034dc8fc13e180b to your computer and use it in GitHub Desktop.
Convert XML files from Kismet into KML for Google Maps or Fusion Tables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# coding=utf-8 | |
# | |
# Converts netxml files from Kismet Newcore into KML or KMZ files for Google Earth | |
# | |
# Author: Patrick Salecker | |
# URL: http://www.salecker.org/software/netxml2kml/en | |
# Last modified: 13.06.2011 | |
import os | |
import time | |
import zipfile | |
import xml.parsers.expat | |
import optparse | |
class WirelessNetwork: | |
def __init__(self,type,firsttime,lasttime): | |
self.type=type | |
self.firsttime=firsttime | |
self.lasttime=lasttime | |
self.bssid="" | |
self.manuf="" | |
self.ssid=[] | |
self.freqmhz={} | |
self.maxrate=0 | |
self.maxseenrate=0 | |
self.packets={} | |
self.snr={} # Signal-to-noise ratio | |
self.datasize=0 | |
self.channel=0 | |
self.carrier="" | |
self.bsstimestamp=0 | |
self.gps={} | |
self.ipaddress={} | |
def get_from_ssid(self,key): | |
result=[] | |
for ssid in self.ssid: | |
if key in ssid and ssid[key]!="": | |
if type(ssid[key])!=type({}): | |
return ssid[key] | |
else: | |
for bla in ssid[key]: | |
if bla not in result: | |
result+=[bla,] | |
if len(result)>0: | |
return result | |
else: | |
return "" | |
def update(self,new): | |
"""Update a network | |
Compare a existing network with a new and update the existing | |
""" | |
if len(self.gps)==0 and len(new.gps)>0: | |
self.gps=new.gps | |
return True | |
KML_PLACEMARK=""" | |
<Placemark><styleUrl>#%s</styleUrl>%s | |
<Point><coordinates>%s,%s</coordinates></Point> | |
<description><![CDATA[ | |
SSID: %s<br /> | |
MAC: %s<br /> | |
Manuf: %s<br /> | |
Type: %s<br /> | |
Channel: %s<br /> | |
Encryption: <FONT color=%s>%s</FONT><br /> | |
Last time: %s<br /> | |
GPS: %s,%s]]></description></Placemark> | |
""" | |
KML_FOLDER = """ | |
<Folder> | |
<name>%s: %s APs</name> | |
<Style id="%s"><IconStyle><scale>0.5</scale> | |
<Icon>") | |
<href>http://files.salecker.org/netxml2kml/images/%s.gif</href> | |
</Icon></IconStyle></Style> | |
%s | |
</Folder> | |
""" | |
class netxml: | |
def __init__(self): | |
self.networks={} | |
self.outputname="" | |
self.target=None | |
self.disable_names = False | |
def main(self): | |
usage=self.main_usage() | |
parser = optparse.OptionParser(usage) | |
parser.add_option("-o", dest="outputname", | |
help="Filename without extension") | |
parser.add_option("--kml", dest="kml", action="store_true", | |
help="Create a KML file for Google Earth <outputname>.kml") | |
parser.add_option("--kmz", dest="kmz", action="store_true", | |
help="Create a KMZ file for Google Earth <outputname>.kmz") | |
parser.add_option("--disable-names", dest="names", action="store_true", | |
help="Disable names in KML/KMZ") | |
(options, args) = parser.parse_args() | |
# Input | |
if len(args)>0: | |
for filename in args: | |
if os.path.isdir(filename): | |
self.parse_dir(filename) | |
elif os.path.isfile(filename): | |
self.parse(filename) | |
else: | |
print "Invalid name: %s"%filename | |
if options.outputname==None: | |
print "Output name not defined, try '-h'" | |
else: | |
self.outputname=options.outputname | |
print "Outputfile: %s.*" % self.outputname | |
print "" | |
if options.names is True: | |
self.disable_names = True | |
# Output | |
if len(self.networks)>0: | |
if self.outputname!="": | |
if options.kml is True: | |
self.output_kml() | |
if options.kmz is True: | |
self.output_kml(kmz=True) | |
else: | |
print "No networks" | |
def main_usage(self): | |
return """ | |
python netxml [options] [file1] [file2] [dir1] [dir2] [...] | |
./netxml [options] [dir1] [dir2] [file1] [file2] [...] | |
Example: | |
python netxml.py --kmz --kml -o today somefile.netxml /mydir""" | |
def parse_dir(self,parsedir): | |
"""Parse all files in a directory | |
""" | |
print "Parse .netxml files in Directory:",parsedir | |
starttime=time.time() | |
files=0 | |
if not parsedir.endswith(os.sep): | |
parsedir+=os.sep | |
for filename in os.listdir(parsedir): | |
if os.path.splitext(filename)[1]==".netxml": | |
self.parse(parsedir + filename) | |
files+=1 | |
print "Directory done, %s sec, %s files" % ( | |
round(time.time()-starttime,2),files) | |
def parse(self,filename): | |
"""Parse a netxml file generated by Kismet Newcore | |
""" | |
self.parser={ | |
"update":0, | |
"new":0, | |
"laststart":"", | |
"parents":[], | |
"wn":None, | |
"ssid":None | |
} | |
p = xml.parsers.expat.ParserCreate() | |
p.buffer_text=True #avoid chunked data | |
p.returns_unicode=False #disabled Unicode support is much faster | |
p.StartElementHandler = self.parse_start_element | |
p.EndElementHandler = self.parse_end_element | |
p.CharacterDataHandler = self.parse_char_data | |
if os.path.isfile(filename): | |
p.ParseFile(open(filename)) | |
else: | |
print "Parser: filename is not a file:" % filename | |
print "Parser: %s, %s new, %s old" % ( | |
filename,self.parser["new"],self.parser["update"]) | |
def parse_start_element(self,name, attrs): | |
"""<name attr=""> | |
""" | |
#print 'Start element:', name, attrs | |
if name=="wireless-network": | |
self.parser["wn"]=WirelessNetwork( | |
attrs["type"], | |
attrs["first-time"], | |
attrs["last-time"]) | |
elif name=="essid" and 'cloaked' in attrs: | |
self.parser["ssid"]['cloaked']=attrs['cloaked'] | |
elif name=="SSID": | |
self.parser["ssid"]={"encryption":{}} | |
self.parser["parents"].insert(0,self.parser["laststart"]) | |
self.parser["laststart"]=name | |
def parse_end_element(self,name): | |
"""</name> | |
""" | |
#print 'End element:', name | |
if name=="wireless-network": | |
if self.parser["wn"].bssid in self.networks: | |
self.networks[self.parser["wn"].bssid].update(self.parser["wn"]) | |
self.parser["update"]+=1 | |
else: | |
self.networks[self.parser["wn"].bssid]=self.parser["wn"] | |
self.parser["new"]+=1 | |
elif name=="SSID": | |
if len(self.parser["ssid"])>0 and "type" in self.parser["ssid"]: | |
if self.parser["parents"][0]=="wireless-network": | |
self.parser["wn"].ssid.append(self.parser["ssid"]) | |
del self.parser["ssid"] | |
self.parser["laststart"]=self.parser["parents"].pop(0) | |
def parse_char_data(self,data): | |
"""<self.parser["laststart"]>data</self.parser["laststart"]> | |
""" | |
if data.strip()=="": | |
return | |
if self.parser["parents"][0]=="SSID": | |
if self.parser["laststart"]=="encryption": | |
self.parser["ssid"]["encryption"][data]=True | |
elif self.parser["laststart"] in("type","ssid","essid","max-rate","packets","beaconrate","info"): | |
self.parser["ssid"][self.parser["laststart"]]=data | |
elif self.parser["parents"][1]=="wireless-network": | |
if self.parser["parents"][0]=="gps-info": | |
self.parser["wn"].gps[self.parser["laststart"]]=float(data) | |
"""elif self.parser["parents"][0]=="packets": | |
self.parser["wn"].packets[self.parser["laststart"]]=data | |
elif self.parser["parents"][0]=="snr-info": | |
self.parser["wn"].snr[self.parser["laststart"]]=data | |
elif self.parser["parents"][0]=="ip-address": | |
self.parser["wn"].ipaddress[self.parser["laststart"]]=data""" | |
elif self.parser["parents"][0]=="wireless-network": | |
if self.parser["laststart"]=="BSSID": | |
self.parser["wn"].bssid=data | |
elif self.parser["laststart"]=="channel": | |
self.parser["wn"].channel=int(data) | |
elif self.parser["laststart"]=="manuf": | |
self.parser["wn"].manuf=data | |
"""elif self.parser["laststart"]=="freqmhz": | |
self.parser["wn"].freqmhz[data]=True | |
elif self.parser["laststart"]=="carrier": | |
self.parser["wn"].carrier=data | |
elif self.parser["laststart"]=="maxseenrate": | |
self.parser["wn"].maxseenrate=data | |
elif self.parser["laststart"]=="bsstimestamp": | |
self.parser["wn"].bsstimestamp=data | |
elif self.parser["laststart"]=="datasize": | |
self.parser["wn"].datasize=data""" | |
def output_kml(self,kmz=False): | |
"""Output KML for Google Earth | |
""" | |
print "%s export..." % ("KML" if not kmz else "KMZ") | |
#starttime=time.time() | |
if kmz is True: | |
target=CreateKMZ(self.outputname) | |
else: | |
target=CreateKML(self.outputname) | |
target.add("<?xml version='1.0' encoding='UTF-8'?>\r\n") | |
target.add("<kml xmlns='http://earth.google.com/kml/2.1'>\r\n") | |
target.add("<Document>\r\n") | |
target.add("<name>netxml2kml</name>\r\n") | |
target.add("<open>1</open>") | |
count={"WPA":0,"WEP":0,"None":0,"Other":0} | |
folders, route = self.output_kml_fill_folders(count) | |
for crypt in ("WPA","WEP","None","Other"): | |
if crypt=="WPA": | |
pic="WPA" | |
elif crypt=="WEP": | |
pic="WEP" | |
else: | |
pic="Open" | |
target.add(KML_FOLDER %( | |
crypt, | |
count[crypt], | |
crypt, | |
pic, | |
"".join(folders[crypt]) | |
)) | |
print "%s\t%s" % (crypt,count[crypt]) | |
target.add(self.output_kml_route(route)) | |
target.add("\r\n</Document>\r\n</kml>") | |
target.close() | |
print "Done. %s networks" % sum(count.values()) | |
#round(time.time()-starttime,2) | |
def output_kml_fill_folders(self,count): | |
folders={"WPA":[],"WEP":[],"None":[],"Other":[]} | |
colors={"WPA":"red","WEP":"orange","None":"green","Other":"grey"} | |
route = {} | |
for net in self.networks: | |
wn=self.networks[net] | |
if len(wn.gps)==0: | |
continue | |
essid = wn.get_from_ssid('essid').replace("<","<").replace(">",">").replace("&","&") | |
if not self.disable_names: | |
name = "<name>%s</name>" % essid | |
else: | |
name = "" | |
encryption=wn.get_from_ssid('encryption') | |
crypt=self.categorize_encryption(encryption) | |
if len(encryption)!=0: | |
encryption.sort(reverse=True) | |
encryption=" ".join(encryption) | |
folders[crypt].append(KML_PLACEMARK %( | |
crypt,name,wn.gps['avg-lon'],wn.gps['avg-lat'], | |
essid,wn.bssid,wn.manuf,wn.type, | |
wn.channel,colors[crypt],encryption,wn.lasttime, | |
wn.gps['avg-lat'],wn.gps['avg-lon'], | |
)) | |
count[crypt]+=1 | |
sec_first = int(time.mktime(time.strptime(wn.firsttime))) | |
sec_last = int(time.mktime(time.strptime(wn.lasttime))) | |
if sec_last - sec_first < 300: | |
route[sec_last] = (wn.gps['avg-lat'],wn.gps['avg-lon']) | |
return folders, route | |
def output_kml_route(self, route): | |
output = [] | |
num = 1 | |
last_second = 0 | |
output.append("<Folder><name>Routes</name>") | |
for second in sorted(route): | |
lat, lon = route[second] | |
if second - last_second > 1800: | |
if len(output) > 1: | |
output.append("</coordinates></LineString><name>Route %s (end %s)</name></Placemark>\n" % (num, time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime(last_second)))) | |
num += 1 | |
output.append("<Placemark><Style><LineStyle><color>7f00ff00</color><width>3</width></LineStyle></Style><LineString><coordinates>\n") | |
last_second = second | |
output.append("%s,%s \n" % (lon, lat)) | |
output.append("</coordinates></LineString><name>Route %s (end %s)</name></Placemark>\n" % (num, time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime(last_second)))) | |
output.append("</Folder>") | |
return "".join(output) | |
def categorize_encryption(self,encryption): | |
for c in encryption: | |
if c.startswith("WPA"): | |
return "WPA" | |
if "WEP" in encryption: | |
return "WEP" | |
elif "None" in encryption: | |
return "None" | |
else: | |
return "Other" | |
class CreateKML: | |
"""Write the KML data direct into a file | |
""" | |
def __init__(self,outputname): | |
self.file=open("%s.kml" % outputname, 'w') | |
def add(self,data): | |
self.file.write(data) | |
def close(self): | |
self.file.close() | |
class CreateKMZ: | |
"""Store the KML data in a list and write it into a zipfile in close() | |
""" | |
def __init__(self,outputname): | |
self.data=[] | |
self.zip=zipfile.ZipFile("%s.kmz" % outputname, "w") | |
def add(self,data): | |
self.data.append(data) | |
def close(self): | |
zinfo = zipfile.ZipInfo("netxml2kml.kml") | |
zinfo.compress_type = zipfile.ZIP_DEFLATED | |
self.zip.writestr(zinfo,"".join(self.data)) | |
self.zip.close() | |
if __name__ == "__main__": | |
converter=netxml() | |
converter.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment