source: src/django_gheat/gheat/management/commands/import_kismet.py@ 9583

Last change on this file since 9583 was 9579, checked in by rick, 13 years ago

Map a SSID to an organization to play nice with differnt SSIDs.

  • Property svn:executable set to *
File size: 8.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Script for importing .gpsxml and .netxml files (Kismet output)
5#
6# Rick van der Zwet <info@rickvanderzwet.nl>
7#
8from django.core.management.base import BaseCommand,CommandError
9from django.db.utils import IntegrityError
10from optparse import OptionParser, make_option
11from gheat.models import *
12from lxml import etree
13import datetime
14import gzip
15import os
16import sys
17import logging
18
19from collections import defaultdict
20
21from import_droidstumbler import bulk_sql
22
23logger = logging.getLogger(__name__)
24logger.setLevel(logging.INFO)
25
26# Open files for reading
27def open_file(file):
28 if file.endswith('.gz'):
29 return gzip.open(file,'rb')
30 else:
31 return open(file,'rb')
32
33
34
35def import_kismet_netxml(netxml_file):
36 netxml_doc = etree.parse(open_file(netxml_file))
37
38 counters = { 'ap_added' : 0, 'ap_total' : 0, 'ap_failed' : 0, 'ap_ignored' : 0,
39 'client_added' : 0, 'client_total' : 0, 'client_failed' : 0, 'client_ignored' : 0}
40
41 # Prepare new accespoints and measurements
42 wnetworks = netxml_doc.findall('wireless-network')
43
44 # Temponary holders
45 ap_pool = {}
46 client_pool = {}
47
48 # Create all accesspoints and for caching validation purposes store them
49 # locally as well
50 for wnetwork in wnetworks:
51 bssid = wnetwork.find('BSSID').text
52 ap_type = wnetwork.attrib['type']
53 # Only access points and clients (for ignore listings)
54 if ap_type in ['infrastructure', 'data']:
55 counters['ap_total'] += 1
56 encryption = (wnetwork.find('SSID/encryption') != None)
57 ssid_node = wnetwork.find('SSID/essid[@cloaked="false"]')
58 ssid = ssid_node.text if ssid_node != None else 'hidden'
59
60 ap_pool[bssid] = (ssid, encryption)
61 elif ap_type in ['probe', 'ad-hoc']:
62 counters['client_total'] += 1
63 client_pool[bssid] = True
64 else:
65 logger.error('Unknown type %s - %s',bssid, wnetwork.attrib['type'])
66
67
68 # Determine which Accespoints to add
69 bssid_list_present = Accespoint.objects.filter(mac__in=ap_pool.keys()).values_list('mac', flat=True)
70 bssid_list_insert = set(ap_pool.keys()) - set(bssid_list_present)
71
72 # Create a bulk import list and import
73 if bssid_list_insert:
74 sql_values = []
75 for bssid in bssid_list_insert:
76 ssid, encryption = ap_pool[bssid]
77 # Special trick in SSID ts avoid escaping in later stage
78 item = str((bssid.upper(),ssid.replace('%','%%'),encryption,Accespoint.get_organization(ssid)))
79 sql_values.append(item)
80 counters['ap_added'] = bulk_sql('gheat_accespoint (`mac`, `ssid`, `encryptie`, `organization`)',sql_values)
81
82 # Determine which Wireless Clients to add
83 bssid_list_present = WirelessClient.objects.filter(mac__in=client_pool.keys()).values_list('mac', flat=True)
84 bssid_list_insert = set(client_pool.keys()) - set(bssid_list_present)
85
86 # Create a bulk import list and import
87 if bssid_list_insert:
88 sql_values = []
89 for bssid in bssid_list_insert:
90 sql_values.append("('%s')" % bssid.upper())
91 counters['client_added'] = bulk_sql('gheat_wirelessclient (`mac`)',sql_values)
92
93 return counters
94
95
96
97def import_kismet_gpsxml(gpsxml_file, meetrondje):
98 gpsxml_doc = etree.parse(open_file(gpsxml_file))
99
100 #Various statistics
101 counters = {'meting_added' : 0, 'meting_total' : 0, 'meting_failed' : 0, 'meting_ignored' :0}
102
103 bssid_failed = defaultdict(int)
104
105 # Prepare new accespoints and measurements
106 points = gpsxml_doc.findall('gps-point')
107
108 # Temponary holders
109 meting_pool = defaultdict(list)
110
111 for point in points:
112 counters['meting_total'] += 1
113 #XXX: This needs to be either the 'bssid' or the 'source',
114 #XXX: accesspoint from or too data.
115 bssid = point.attrib['bssid']
116 # XXX: Filter this in the beginning with XPath, but etree does not support
117 # that (yet).
118 if bssid in ['GP:SD:TR:AC:KL:OG','00:00:00:00:00:00']:
119 counters['meting_ignored'] =+ 1
120 continue
121 # XXX: Signal need properly be a relation of signal_dbm and noice_dbm
122 try:
123 level = point.attrib['signal_dbm']
124 except KeyError:
125 logger.debug("Point '%s' does not have signal strengh" % point)
126 counters['meting_failed'] += 1
127 continue
128 # We store all values found, avg or max will be done later on
129 key = (bssid, point.attrib['lat'], point.attrib['lon'])
130 signaal=100 + int(level)
131 meting_pool[key].append(signaal)
132
133 bssid_list = [x[0] for x in meting_pool.keys()]
134 # Build mapping for meting import
135 mac2id = {}
136 for mac,id in Accespoint.objects.filter(mac__in=bssid_list).values_list('mac','id'):
137 mac2id[mac] = int(id)
138
139 clients = {}
140 for mac in WirelessClient.objects.filter(mac__in=bssid_list).values_list('mac',flat=True):
141 clients[mac] = True
142
143 sql_values = []
144 for (bssid,lat,lon),signals in meting_pool.iteritems():
145 if clients.has_key(bssid):
146 counters['meting_ignored'] += len(signals)
147 elif not mac2id.has_key(bssid):
148 counters['meting_failed'] += len(signals)
149 bssid_failed[bssid] += len(signals)
150 else:
151 item = str((int(meetrondje.id),mac2id[bssid],float(lat),float(lon),max(signals)))
152 sql_values.append(item)
153
154 for bssid,count in sorted(bssid_failed.items(),
155 key=lambda item: item[1], reverse=True):
156 logger.debug("Missing BSSID %s found %3s times", bssid, count)
157
158 if sql_values:
159 counters['meting_added'] = bulk_sql('gheat_meting (`meetrondje_id`, `accespoint_id`, `lat`, `lng`, `signaal`)',sql_values)
160 return counters
161
162
163class Command(BaseCommand):
164 args = '<gpsxml|netxml>[.gz] [gpsxml2[.gz] gpsxml3[.gz] ...]'
165 option_list = BaseCommand.option_list + (
166 make_option('-k', '--kaart', dest='kaart', default='onbekend', help="Kaart gebruikt"),
167 make_option('-m', '--meetrondje', dest='meetrondje', default=None),
168 make_option('-g', '--gebruiker', dest='gebruiker', default='username',help='Naam van de persoon die de meting uitgevoerd heeft'),
169 make_option('-e', '--email', dest='email', default='foo@bar.org',help='Email van de persoon die de meting uitgevoerd heeft'),
170 make_option('-d', '--datum', dest='datum', default=None, help="Provide date \
171 in following format: '%Y%m%d-%H-%M-%S-1', by default it will be generated from \
172 the filename"),
173 )
174
175 def handle(self, *args, **options):
176 if len(args) == 0:
177 self.print_help(sys.argv[0],sys.argv[1])
178 raise CommandError("Not all arguments are provided")
179
180 # Please first the netxml and then the gpsxml files
181 sorted_args = [x for x in args if "netxml" in x] + [x for x in args if "gpsxml" in x]
182 remainder = list(set(args) - set(sorted_args))
183 args = sorted_args + remainder
184 logger.debug("Parsing files in the following order: %s", args)
185
186 for xml_file in args:
187 if not os.path.isfile(xml_file):
188 raise CommandError("xml file '%s' does not exists" % xml_file)
189
190 for xml_file in args:
191 logger.info("Processing '%s'" % xml_file)
192 if 'netxml' in xml_file:
193 counters = import_kismet_netxml(xml_file)
194 logger.info("summary accespoints: total:%(ap_total)-6s added:%(ap_added)-6s failed:%(ap_failed)-6s ignored:%(ap_ignored)-6s" % counters)
195 logger.info("summary client : total:%(client_total)-6s added:%(client_added)-6s failed:%(client_failed)-6s ignored:%(client_ignored)-6s" % counters)
196 elif 'gpsxml' in xml_file:
197 if options['datum'] == None:
198 datum = os.path.basename(xml_file).lstrip('Kismet-').rstrip('.gz').rstrip('.gpsxml').rstrip('.netxml')
199 else:
200 datum = options['datum']
201 try:
202 # Kismet-20110805-15-37-30-1
203 datum = datetime.datetime.strptime(datum,'%Y%m%d-%H-%M-%S-1')
204 except ValueError:
205 raise CommandError("Invalid date '%s'" % options['datum'])
206
207 # Meetrondje from filename if needed
208 if options['meetrondje'] == None:
209 meetrondje = os.path.basename(xml_file).rstrip('.gz').rstrip('.gpsxml')
210 else:
211 meetrondje = options['meetrondje']
212
213 # Create meetrondje object
214 g, created = Gebruiker.objects.get_or_create(naam=options['gebruiker'] , email=options['email'])
215 a, created = Apparatuur.objects.get_or_create(kaart=options['kaart'])
216 mr, created = MeetRondje.objects.get_or_create(datum=datum , naam=meetrondje , gebruiker=g , apparatuur=a)
217 logger.info('Meetrondje: %s @ %s' % (meetrondje, datum))
218 if not created:
219 logger.error("Meetrondje '%s' already imported" % mr)
220 continue
221 counters = import_kismet_gpsxml(xml_file, mr)
222 logger.info("summary metingen : total:%(meting_total)-6s added:%(meting_added)-6s failed:%(meting_failed)-6s ignored:%(meting_ignored)-6s" % counters)
223 else:
224 raise CommandError("xml file '%s' format not recognized" % xml_file)
Note: See TracBrowser for help on using the repository browser.