#!/usr/bin/env python3
# coding=utf-8
#
# Converts netxml files from Kismet Newcore into KML or KMZ files for Google Earth
#
# Based on script from Patrick Salecker
# Converted to python3 by Marcel de Groot (mg.degroot@openmailbox.org)
#
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 = """
#%s%s
%s,%s
MAC: %s
Manuf: %s
Type: %s
Channel: %s
Encryption: %s
Last time: %s
GPS: %s,%s]]>
"""
KML_FOLDER = """
%s: %s APs
%s
"""
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 .kml")
parser.add_option("--kmz", dest="kmz", action="store_true",
help="Create a KMZ file for Google Earth .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:
filename = os.path.expanduser(filename)
if os.path.isdir(filename):
self.parse_dir(filename)
elif os.path.isfile(filename):
self.parse(filename)
else:
print("Invalid name: {}".format(filename))
if options.outputname == None:
print("Output name not defined, try '-h'")
else:
options.outputname = os.path.expanduser(options.outputname)
self.outputname = options.outputname
print("Outputfile: {}.*".format(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, {sec} sec, {files} files".format(sec=round(time.time() - starttime, 2), files=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, 'rb'))
else:
print("Parser: filename is not a file: " + filename)
print("Parser: {parser}, {new} new, {old} old".format(parser=filename, new=self.parser["new"],
old=self.parser["update"]))
def parse_start_element(self, name, attrs):
"""
"""
# 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):
"""
"""
# 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):
"""data
"""
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("\r\n")
target.add("\r\n")
target.add("\r\n")
target.add("netxml2kml\r\n")
target.add("1")
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("{crypt}\t{count}".format(crypt=crypt, count=count[crypt]))
target.add(self.output_kml_route(route))
target.add("\r\n\r\n")
target.close()
print("Done. {count} networks".format(count=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 = "%s" % 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("Routes")
for second in sorted(route):
lat, lon = route[second]
if second - last_second > 1800:
if len(output) > 1:
output.append("Route %s (end %s)\n" % (
num, time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime(last_second))))
num += 1
output.append(
"\n")
last_second = second
output.append("%s,%s \n" % (lon, lat))
output.append("Route %s (end %s)\n" % (
num, time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime(last_second))))
output.append("")
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()