#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  lib/eapeak/parse.py
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following disclaimer
#    in the documentation and/or other materials provided with the
#    distribution.
#  * Neither the name of the project nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
#  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
#  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
#  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

import base64
import binascii
import datetime
import os
import sys
import struct
import time
from xml.dom import minidom
from xml.etree import ElementTree

try:
	import curses
	CURSES_CAPABLE = True
except ImportError:
	CURSES_CAPABLE = False

from M2Crypto import X509

from scapy.utils import PcapReader
import scapy.packet  # pylint: disable=unused-import
import scapy.layers.all  # pylint: disable=unused-import
from scapy.layers.l2 import EAP
from eapeak.scapylayers.l2 import eap_types as EAP_TYPES

from eapeak.common import get_bssid, get_source, get_destination, EXPANDED_EAP_VENDOR_IDS, __version__
import eapeak.networks
import eapeak.clients

# Statics
UNKNOWN_SSID_NAME = 'UNKNOWN_SSID'
XML_FILE_NAME = 'eapeak.xml'
SSID_SEARCH_RECURSION = 5
CURSES_LINE_BREAK = (0, '')
CURSES_REFRESH_FREQUENCY = 0.10
CURSES_LOWER_REFRESH_FREQUENCY = 5  # Also used for calls to exportXML
CURSES_MIN_X = 99
CURSES_MIN_Y = 25
TAB_LENGTH = 4
TAB_DEPTH_2 = 2 * TAB_LENGTH
TAB_DEPTH_3 = 3 * TAB_LENGTH
TAB_DEPTH_4 = 4 * TAB_LENGTH

USER_MARKER = '=> '
USER_MARKER_OFFSET = 8
SSID_MAX_LENGTH = 32
EAP_TYPES[0] = 'NONE'

def merge_wireless_networks(source, destination):
	"""
	Merge information about two wireless networks, used to preserve
	information when one is un-orphaned.
	"""
	for bssid in source.bssids:
		destination.add_BSSID(bssid)

	for clientobj in source.clients.values():
		destination.add_client(clientobj)

	for eaptype in source.eapTypes:
		destination.addEapType(eaptype)

	for cert in source.x509certs:
		destination.add_certificate(cert)
	return destination

class wpsDataHolder(dict):
	"""
	This wraps a dictionary and a few key methods to allow types to be
	retreived from either their numerical cylon value or thier alphabetical
	human value

	Keys are not case sensitive because I like it that way.
	"""
	__h_to_c__ = {
		'authentication type flags': 0x1004,
		'authenticator': 0x1005,
		'configuration error': 0x1009,
		'encryption type flags': 0x1010,
		'device name': 0x1011,
		'encrypted settings': 0x1018,
		'enrollee nonce': 0x101a,
		'manufacturer': 0x1021,
		'message type': 0x1022,
		'model name': 0x1023,
		'model number': 0x1024,
		'os version': 0x102d,
		'registrar nonce': 0x1039,
		'uuid': 0x1048,
		'version': 0x104a,
	}

	def __getitem__(self, index):
		if isinstance(index, str):
			if index.lower() in self.__h_to_c__:
				index = self.__h_to_c__[index.lower()]
			else:
				raise KeyError(index)
		return dict.__getitem__(self, index)

	def __setitem__(self, name, value):
		if isinstance(name, str):
			if name.lower() in self.__h_to_c__:
				name = self.__h_to_c__[name.lower()]
			else:
				raise KeyError(name)
		return dict.__setitem__(self, name, value)

	def get(self, item):
		if isinstance(item, str):
			if item.lower() in self.__h_to_c__:
				item = self.__h_to_c__[item.lower()]
			else:
				return None
		return dict.get(self, item)

	def has_key(self, item):
		if isinstance(item, str):
			if item.lower() in self.__h_to_c__:
				item = self.__h_to_c__[item.lower()]
			else:
				return False
		return dict.has_key(self, item)

	def keys(self):
		keys = dict.keys(self)
		new_keys = []
		for key, value in self.__h_to_c__.items():
			if value in keys:
				new_keys.append(key)
		keys.extend(new_keys)
		return keys

def parse_wps_data(wpsdata, trimStrings=True):
	"""
	Take raw WPS data string and return a dictionary of types and values
	"""
	data = wpsDataHolder()
	while wpsdata:
		if len(wpsdata) < 4:
			raise Exception('invalid/corrupted WPS data')
		_type = struct.unpack('>H', wpsdata[:2])[0]
		length = struct.unpack('>H', wpsdata[2:4])[0]
		if len(wpsdata) < (length + 4):
			raise Exception('invalid/corrupted WPS data')
		value = wpsdata[4:(4 + length)]
		wpsdata = wpsdata[(4 + length):]
		if trimStrings and _type in [0x1011, 0x1021, 0x1023, 0x1024]:
			value = value.replace('\x00', '')
			if not len(value):
				continue
		data[_type] = value
	return data

def parse_rsn_data(rsndata):
	"""
	Take raw RSN data and return a dictionary representing it's values
	Tag Number and Tag length are removed
	"""
	rsn = {}
	rsn['version'] = struct.unpack('<H', rsndata[:2])[0]
	rsn['grp_cipher'] = rsndata[2:6]

	pair_ciphers = []
	nbr_pair_cipher = struct.unpack('<H', rsndata[6:8])[0]
	rsndata = rsndata[8:]
	while nbr_pair_cipher and len(rsndata):
		pair_ciphers.append(rsndata[:4])
		rsndata = rsndata[4:]
		nbr_pair_cipher -= 1
	rsn['pair_ciphers'] = pair_ciphers

	auth_key_mgmt = []
	nbr_auth_key_mgmt = struct.unpack('<H', rsndata[:2])[0]
	rsndata = rsndata[2:]
	while nbr_auth_key_mgmt and len(rsndata):
		auth_key_mgmt.append(rsndata[:4])
		rsndata = rsndata[4:]
		nbr_auth_key_mgmt -= 1
	rsn['auth_key_mgmts'] = auth_key_mgmt
	rsn['capabilities'] = rsndata
	return rsn

def build_rsn_data(rsn):
	version = rsn.get('version') or 1
	rsndata = struct.pack('<H', version)
	rsndata += rsn['grp_cipher']
	rsndata += struct.pack('<H', 1)
	rsndata += rsn['pair_ciphers'][0]
	rsndata += struct.pack('<H', 1)
	rsndata += rsn['auth_key_mgmts'][0]
	rsndata += rsn.get('capabilities') or '\x00\x00'
	return rsndata

class EapeakParsingEngine:
	"""
	This is the main parsing engine that manages all of the networks.

	Notable attributes:
	KnownNetworks: holds wireless network objects, indexed by SSID if available, BSSID if orphaned
	BSSIDToSSIDMap: holds SSIDs, indexed by BSSIDS, so you can obtain network objects by BSSID
	OrphanedBSSIDs: holds BSSIDs that are not associated with a known SSID
	fragment_buffer: holds buffers (lists), indexed by connection strings (src_mac + ' ' + dst_mac)
	"""
	def __init__(self, targetSSIDs=None, targetBSSIDs=None):
		self.KnownNetworks = {}  # Holds wireless network objects, indexed by SSID if available, BSSID if orphaned
		self.BSSIDToSSIDMap = {}  # Holds SSIDs, indexed by BSSIDS, so you can obtain network objects by BSSID
		self.OrphanedBSSIDs = []  # holds BSSIDs that are not associated with a known SSID
		self.packets = []
		self.targetSSIDs = targetSSIDs
		self.targetBSSIDs = targetBSSIDs
		self.packetCounter = 0
		self.fragment_buffer = {}  # Holds buffers (lists), indexed by connection strings (src_mac + ' ' + dst_mac)

	def parse_live_capture(self, packet, quite=True):
		"""
		Function is meant to be passed to Scapy's sniff() function similar to:
		lambda packet: eapeakParser.parseLiveCapture(packet, use_curses)

		sniff(iface = 'mon0', prn = lambda packet: eapeakParser.parseLiveCapture(packet, False) )
		"""
		self.parse_wireless_packet(packet)
		if quite:
			return
		sys.stdout.write('Packets: ' + str(self.packetCounter) + ' Wireless Networks: ' + str(len(self.KnownNetworks)) + '\r')
		sys.stdout.flush()

	def parse_pcap_files(self, pcapFiles, quite=True):
		"""
		Take one more more (list, or tuple) of pcap files and parse them
		into the engine.
		"""
		if not hasattr(pcapFiles, '__iter__'):
			if isinstance(pcapFiles, str):
				pcapFiles = [pcapFiles]
			else:
				return
		for i in range(0, len(pcapFiles)):
			pcap = pcapFiles[i]
			pcapName = os.path.split(pcap)[1]
			if not quite:
				sys.stdout.write("Reading PCap File: {0}\r".format(pcapName))
				sys.stdout.flush()
			if not os.path.isfile(pcap):
				if not quite:
					sys.stdout.write("Skipping File {0}: File Not Found\n".format(pcap))
					sys.stdout.flush()
				continue
			elif not os.access(pcap, os.R_OK):
				if not quite:
					sys.stdout.write("Skipping File {0}: Permissions Issue\n".format(pcap))
					sys.stdout.flush()
				continue
			pcapr = PcapReader(pcap)  # pylint: disable=no-value-for-parameter
			packet = pcapr.read_packet()
			i = 1
			try:
				while packet:
					if not quite:
						sys.stdout.write('Parsing File: ' + pcap + ' Packets Done: ' + str(i) + '\r')
						sys.stdout.flush()
					self.parse_wireless_packet(packet)
					packet = pcapr.read_packet()
					i += 1
				i -= 1
				if not quite:
					sys.stdout.write((' ' * len('Parsing File: ' + pcap + ' Packets Done: ' + str(i))) + '\r')
					sys.stdout.write('Done With File: ' + pcap + ' Read ' + str(i) + ' Packets\n')
					sys.stdout.flush()
			except KeyboardInterrupt:
				if not quite:
					sys.stdout.write("Skipping File {0} Due To Ctl+C\n".format(pcap))
					sys.stdout.flush()
			except:  # pylint: disable=bare-except
				if not quite:
					sys.stdout.write("Skipping File {0} Due To Scapy Exception\n".format(pcap))
					sys.stdout.flush()
			self.fragment_buffer = {}
			pcapr.close()

	def parse_xml_files(self, xmlFiles, quite=True):
		"""
		Load EAPeak/Kismet style XML files for information.  This is
		faster than parsing large PCap files.
		"""
		if not hasattr(xmlFiles, '__iter__'):
			if isinstance(xmlFiles, str):
				xmlFiles = [xmlFiles]
			else:
				return
		for xmlfile in xmlFiles:
			if not os.path.isfile(xmlfile):
				if not quite:
					sys.stdout.write("Skipping File {0}: File Not Found\n".format(xmlfile))
					sys.stdout.flush()
				continue
			elif not os.access(xmlfile, os.R_OK):
				if not quite:
					sys.stdout.write("Skipping File {0}: Permissions Issue\n".format(xmlfile))
					sys.stdout.flush()
				continue
			sys.stdout.write("Parsing XML  File: {0}".format(xmlfile))
			sys.stdout.flush()
			e = ElementTree.parse(xmlfile)
			for network in e.findall('wireless-network'):
				ssid = network.find('SSID')
				if not ElementTree.iselement(ssid) or not ElementTree.iselement(ssid.find('type')):
					continue
				elif ssid.find('type').text.strip() != 'Beacon':
					continue
				ssid = ssid.find('essid')
				if ElementTree.iselement(ssid):
					if ssid.text is None:
						ssid = UNKNOWN_SSID_NAME
					else:
						ssid = ssid.text.strip()
					newNetwork = eapeak.networks.WirelessNetwork(ssid)
				else:
					continue
				self.get_network_info(network, newNetwork, ElementTree, ssid)
				for client in network.findall('wireless-client'):
					bssid = client.find('client-bssid')
					if ElementTree.iselement(bssid):
						bssid = bssid.text.strip()
					else:
						continue
					client_mac = client.find('client-mac').text.strip()
					newClient = eapeak.clients.WirelessClient(bssid, client_mac)
					self.get_client_info(client, newClient, ElementTree)
					newNetwork.add_client(newClient)
				self.find_certs(network, newNetwork)
				if ssid != UNKNOWN_SSID_NAME:
					self.KnownNetworks[ssid] = newNetwork
				else:
					self.KnownNetworks[bssid] = newNetwork
#				if ssid == UNKNOWN_SSID_NAME and len(network.findall('BSSID')) > 1:
#					there will be an issue with where to store the single network object.
#					If there is a client and the network is added to KnownNetworks each time this occurs then the client will appear to under each network but only
#					be associated with the single BSSID.  This problem needs to be addressed and throughly tested.
			sys.stdout.write(" Done\n")
			sys.stdout.flush()

	def get_network_info(self, network, newNetwork, _ElementTree, ssid):
		for bssid in network.findall('BSSID'):
			bssid = bssid.text.strip()
			newNetwork.add_BSSID(bssid)
			if ssid != UNKNOWN_SSID_NAME:
				self.BSSIDToSSIDMap[bssid] = ssid
			else:
				self.BSSIDToSSIDMap[bssid] = bssid
				self.OrphanedBSSIDs.append(bssid)
		eaptypes = network.find('SSID').find('eap-types')
		if ElementTree.iselement(eaptypes):
			for eaptype in eaptypes.text.strip().split(','):
				if eaptype.isdigit():
					newNetwork.addEapType(int(eaptype))
		expandedVendorIDs = network.find('SSID').find('expanded-vendor-ids')
		if ElementTree.iselement(expandedVendorIDs):
			for vendorid in expandedVendorIDs.text.strip().split(','):
				if vendorid.isdigit():
					newNetwork.add_expanded_vendor_id(int(vendorid))
		wpsXMLData = network.find('wps-data')
		if ElementTree.iselement(wpsXMLData):
			wpsData = wpsDataHolder()
			for elem in wpsXMLData:
				key = elem.tag.replace('-', ' ')
				value = elem.text.strip()
				encoding = elem.get('encoding')
				if encoding == 'hex':
					wpsData[key] = binascii.a2b_hex(value)
				elif encoding == 'base64':
					wpsData[key] = base64.standard_b64decode(value)
				else:
					wpsData[key] = value
			if len(wpsData):
				newNetwork.wpsData = wpsData

	def get_client_info(self, client, newClient, _ElementTree):
		eaptypes = client.find('eap-types')
		if ElementTree.iselement(eaptypes):
			eaptypes = eaptypes.text
			if eaptypes != None:
				for eaptype in eaptypes.strip().split(','):
					if eaptype.isdigit():
						newClient.addEapType(int(eaptype))
		identities = client.findall('identity') or []
		for identity in identities:
			tmp = identity.get('eap-type')
			if tmp.isdigit():
				newClient.add_identity(int(tmp), identity.text.strip())
		mschaps = client.findall('mschap') or []
		for mschap in mschaps:
			newClient.add_ms_chap_info(
				int(mschap.get('eap-type')),
				binascii.a2b_hex(mschap.find('challenge').text.strip().replace(':', '')),
				binascii.a2b_hex(mschap.find('response').text.strip().replace(':', '')),
				mschap.get('identity')
			)
		wpsXMLData = client.find('wps-data')
		if ElementTree.iselement(wpsXMLData):
			wpsData = wpsDataHolder()
			for elem in wpsXMLData:
				key = elem.tag.replace('-', ' ')
				value = elem.text.strip()
				if elem.get('encoding') == 'hex':
					wpsData[key] = binascii.a2b_hex(value)
				elif elem.get('encoding') == 'base64':
					wpsData[key] = base64.standard_b64decode(value)
				else:
					wpsData[key] = value
			if len(wpsData):
				newClient.wpsData = wpsData

	def find_certs(self, network, newNetwork):
		for cert in network.findall('certificate'):
			if cert.get('encoding') == 'DER':
				newNetwork.add_certificate(X509.load_cert_string(base64.standard_b64decode(cert.text.strip()), X509.FORMAT_DER))
			elif cert.get('encoding') == 'PEM':
				newNetwork.add_certificate(X509.load_cert_string(base64.standard_b64decode(cert.text.strip()), X509.FORMAT_PEM))

	def export_xml(self, filename=XML_FILE_NAME):
		"""
		Exports an XML file that can be reimported with the parseXMLFiles
		function.
		"""
		eapeakXML = ElementTree.Element('detection-run')
		eapeakXML.set('eapeak-version', __version__)
		eapeakXML.append(ElementTree.Comment(' Summary: Found ' + str(len(self.KnownNetworks)) + ' Network(s) '))
		eapeakXML.append(ElementTree.Comment(datetime.datetime.now().strftime(' Created %A %m/%d/%Y %H:%M:%S ')))
		networks = self.KnownNetworks.keys()
		if not networks:
			return
		networks.sort()
		for network in networks:
			eapeakXML.append(self.KnownNetworks[network].get_xml())
		xmldata = minidom.parseString(ElementTree.tostring(eapeakXML)).toprettyxml()
		if xmldata:
			tmpfile = open(filename, 'w')
			tmpfile.write(xmldata)
			tmpfile.close()

	def update_maps(self, packet):
		tmp = packet
		for x in range(0, SSID_SEARCH_RECURSION):  # pylint: disable=unused-variable
			if 'ID' in tmp.fields and tmp.fields['ID'] == 0 and 'info' in tmp.fields:  # Verifies that we found an SSID
				if tmp.fields['info'] == '\x00':
					break
				bssid = get_bssid(packet)
				if (self.targetSSIDs and tmp.fields['info'] not in self.targetSSIDs) or (self.targetBSSIDs and bssid not in self.targetBSSIDs):  # Obi says: These are not the SSIDs you are looking for...
					break
				if not bssid:
					return
				ssid = ''.join([c for c in tmp.fields['info'] if (ord(c) > 31 or ord(c) == 9) and ord(c) < 128])
				if self.targetBSSIDs:
					if not self.targetSSIDs:
						self.targetSSIDs = []
					if ssid not in self.targetSSIDs:
						self.targetSSIDs.append(ssid)
				if not ssid:
					return
				if bssid in self.OrphanedBSSIDs:  # If this info is relating to a BSSID that was previously considered to be orphaned
					newNetwork = self.KnownNetworks[bssid]  # Retrieve the old one
					del self.KnownNetworks[bssid]  # Delete the old network's orphaned reference
					self.OrphanedBSSIDs.remove(bssid)
					self.BSSIDToSSIDMap[bssid] = ssid  # Changes the map from BSSID -> BSSID (for orphans) to BSSID -> SSID
					newNetwork.update_SSID(ssid)
					if ssid in self.KnownNetworks:
						newNetwork = merge_wireless_networks(newNetwork, self.KnownNetworks[ssid])
				elif bssid in self.BSSIDToSSIDMap:
					continue
				elif ssid in self.KnownNetworks:  # If this is a BSSID from a probe for an SSID we've seen before
					newNetwork = self.KnownNetworks[ssid]  # Pick up where we left off by using the curent state of the WirelessNetwork object
				elif bssid:
					newNetwork = eapeak.networks.WirelessNetwork(ssid)
					self.BSSIDToSSIDMap[bssid] = ssid
				newNetwork.add_BSSID(bssid)

				self.KnownNetworks[ssid] = newNetwork
				del bssid, ssid
				break
			tmp = tmp.payload
			if tmp is None:
				break

	def parse_wireless_packet(self, packet):
		"""
		This is the core packet parsing routine.  It takes a Scapy style
		packet object as an argument.
		"""
		if packet.name == 'RadioTap dummy':
			packet = packet.payload  # Offset it so we start with the Dot11 header
		shouldStop = False
		self.packetCounter += 1
		# this section finds SSIDs in Bacons
		if packet.haslayer('Dot11Beacon') or packet.haslayer('Dot11ProbeResp') or packet.haslayer('Dot11AssoReq'):
			self.update_maps(packet)
			shouldStop = True
		if shouldStop:
			return

		# This section extracts useful EAP info
		cert_layer = None
		if 'EAP' in packet:
			fields = packet.getlayer('EAP').fields
			if fields['code'] not in [1, 2]:
				return
			eaptype = fields['type']
			for x in range(1, 4):
				addr = 'addr' + str(x)
				if not addr in packet.fields:
					return
			bssid = get_bssid(packet)
			if not bssid:
				return
			if bssid and not bssid in self.BSSIDToSSIDMap:
				self.BSSIDToSSIDMap[bssid] = bssid
				self.OrphanedBSSIDs.append(bssid)
				self.KnownNetworks[bssid] = eapeak.networks.WirelessNetwork(UNKNOWN_SSID_NAME)
				self.KnownNetworks[bssid].add_BSSID(bssid)
			network = self.KnownNetworks[self.BSSIDToSSIDMap[bssid]]
			client_mac = get_source(packet)
			from_AP = False
			if client_mac == bssid:
				client_mac = get_destination(packet)
				from_AP = True
			if not bssid or not client_mac:
				return
			if network.has_client(client_mac):
				client = network.get_client(client_mac)
			else:
				client = eapeak.clients.WirelessClient(bssid, client_mac)
			if from_AP:
				network.addEapType(eaptype)
			elif eaptype > 4:
				client.addEapType(eaptype)
			elif eaptype == 3 and fields['code'] == 2:  # Parses NAKs and attempts to harvest the desired EAP types, RFC 3748
				self.get_client_eap_types(fields, client)
			if eaptype == 254 and packet.haslayer('EAP_Expanded'):
				network.add_expanded_vendor_id(packet.getlayer('EAP_Expanded').vendor_id)
			if from_AP:
				if packet.haslayer('LEAP'):
					self.get_leap_from_ap_data(packet, client)
				elif packet.getlayer(EAP).payload.name in ['EAP_TLS', 'EAP_TTLS', 'PEAP', 'EAP_Fast']:
					cert_layer = self.get_eap_data(packet, bssid, client_mac)
				elif packet.haslayer('EAP_Expanded') and packet.getlayer('EAP_Expanded').vendor_type == 1 and packet.haslayer('WPS') and packet.getlayer('WPS').opcode == 4:
					try:
						self.get_wps_data(packet, network)
					except:  # pylint: disable=bare-except
						pass

			else:
				if eaptype == 1 and 'identity' in fields:
					client.add_identity(1, fields['identity'])
				if packet.haslayer('LEAP'):
					self.get_leap_data(packet, client)
				elif packet.haslayer('EAP_Expanded') and packet.getlayer('EAP_Expanded').vendor_type == 1 and packet.haslayer('WPS') and packet.getlayer('WPS').opcode == 4:
					try:
						self.get_client_wps_data(packet, client)
					except:  # pylint: disable=bare-except
						pass  # Data is corrupted
			network.add_client(client)
			if not cert_layer:
				shouldStop = True
		if shouldStop:
			return

		if cert_layer and 'certificate' in cert_layer.fields:
			self.get_cert_data(network, cert_layer)
		return

	def get_cert_data(self, network, cert_layer):
		cert_data = cert_layer.certificate[3:]
		tmp_certs = []
		while cert_data:
			if len(cert_data) < 4:
				break  # Length and 1 byte are at least 4 bytes
			tmp_length = struct.unpack('!I', '\x00' + cert_data[:3])[0]
			cert_data = cert_data[3:]
			if len(cert_data) < tmp_length:
				break  # I smell corruption
			tmp_certs.append(cert_data[:tmp_length])
			cert_data = cert_data[tmp_length:]
		for certificate in tmp_certs:
			try:
				certificate = X509.load_cert_string(certificate, X509.FORMAT_DER)
			except X509.X509Error:
				pass
			network.add_certificate(certificate)
	def get_client_eap_types(self, fields, client):
		if 'eap_types' in fields:
			for eap in fields['eap_types']:
				client.addEapType(eap)
			del eap  # pylint: disable=undefined-loop-variable

	def get_client_wps_data(self, packet, client):
		wpsData = parse_wps_data(packet.getlayer('WPS').data)
		if client.wpsData is None:
			client.wpsData = wpsData
		else:
			client.wpsData.update(wpsData)

	def get_wps_data(self, packet, network):
		wpsData = parse_wps_data(packet.getlayer('WPS').data)
		if network.wpsData is None:
			network.wpsData = wpsData
		else:
			network.wpsData.update(wpsData)

	def get_eap_data(self, packet, bssid, client_mac):
		cert_layer = None
		eap_layer = packet.getlayer(EAP).payload
		conn_string = bssid + ' ' + client_mac
		frag_flag, len_flag = {'EAP_TLS':(64, 128), 'EAP_TTLS':(8, 16), 'PEAP':(16, 32), 'EAP_Fast':(8, 16)}[eap_layer.name]
		if eap_layer.flags & frag_flag and eap_layer.flags & len_flag:
			self.fragment_buffer[conn_string] = [eap_layer]
		elif eap_layer.flags & frag_flag:
			if conn_string in self.fragment_buffer:
				self.fragment_buffer[conn_string].append(eap_layer.payload)
		elif eap_layer.flags == 0 and conn_string in self.fragment_buffer:
			eap_layer = eap_layer.__class__(''.join([x.do_build() for x in self.fragment_buffer[conn_string]]) + eap_layer.payload.do_build())  # Take that people trying to read my code! Spencer 1, you 0.
			del self.fragment_buffer[conn_string]
		if eap_layer.haslayer('TLSv1Certificate'):  # At this point, if possible, we should have a fully assembled packet
			cert_layer = eap_layer.getlayer('TLSv1Certificate')
		del eap_layer, conn_string, frag_flag, len_flag
		return cert_layer

	def get_leap_data(self, packet, client):
		leap_fields = packet.getlayer('LEAP').fields
		identity = None
		if 'name' in leap_fields:
			identity = leap_fields['name']
			client.add_identity(17, identity)
		if 'data' in leap_fields and len(leap_fields['data']) == 24:
			client.add_ms_chap_info(17, response=leap_fields['data'], identity=identity)
		del leap_fields, identity

	def get_leap_from_ap_data(self, packet, client):
		leap_fields = packet.getlayer('LEAP').fields
		if 'data' in leap_fields and len(leap_fields['data']) == 8:
			client.add_ms_chap_info(17, challenge=leap_fields['data'], identity=leap_fields['name'])
		del leap_fields

class CursesEapeakParsingEngine(EapeakParsingEngine):
	"""
	This engine contains additional methods necessary for the Curses UI.
	It is seperate from the other class to not degrade performance when
	Curses is not being used.
	"""
	def init_curses(self):
		"""
		This initializes the screen for curses useage.  It must be
		called before Curses can be used.
		"""
		self.user_marker_pos = 1  # Used with curses
		self.curses_row_offset = 0  # Used for marking the visible rows on the screen to allow scrolling
		self.curses_row_offset_store = 0  # Used for storing the row offset when switching from detailed to non-detailed view modes
		self.curses_detailed = None  # Used with curses
		self.screen = curses.initscr()
		curses.start_color()
		curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_WHITE)
		size = self.screen.getmaxyx()
		if size[0] < CURSES_MIN_Y or size[1] < CURSES_MIN_X:
			curses.endwin()
			return 1
		self.curses_max_rows = size[0] - 2  # Minus 2 for the border on the top and bottom
		self.curses_max_columns = size[1] - 2

		self.screen.border(0)
		self.screen.addstr(2, TAB_LENGTH, 'EAPeak Capturing Live')
		self.screen.addstr(3, TAB_LENGTH, 'Found 0 Networks')
		self.screen.addstr(4, TAB_LENGTH, 'Processed 0 Packets')
		self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, USER_MARKER)
		self.screen.refresh()
		try:
			curses.curs_set(1)
			curses.curs_set(0)
		except curses.error:  # Ignore exceptions from terminals that don't support setting the cursor's visibility
			pass
		curses.noecho()
		curses.cbreak()
		self.curses_enabled = True
		self.curses_lower_refresh_counter = 1
		return 0

	def curses_interaction_handler(self, garbage=None):
		"""
		This is a function meant to be run in a seperate thread to
		handle human interaction with the curses interface.
		"""
		while self.curses_enabled:
			c = self.screen.getch()
			if self.curses_lower_refresh_counter == 0:
				continue
			size = self.screen.getmaxyx()
			if size[0] < CURSES_MIN_Y or size[1] < CURSES_MIN_X:
				if not self.resize_dialog():
					break
				continue
			if c in [65, 117, 85] and len(self.KnownNetworks):  # 117 = ord('u')
				if self.curses_detailed:
					if self.curses_row_offset > 0:
						self.curses_row_offset -= 1
						self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY  # Trigger a redraw by adjusting the counter
				else:
					self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, ' ' * len(USER_MARKER))
					if self.user_marker_pos == 1 and self.curses_row_offset == 0:
						pass  # Ceiling
					elif self.user_marker_pos == 1 and self.curses_row_offset:
						self.curses_row_offset -= 1
						self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY
					else:
						self.user_marker_pos -= 1
					self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, USER_MARKER)
			elif c in [66, 100, 68] and len(self.KnownNetworks):  # 100 = ord('d')
				if self.curses_detailed:
					self.curses_row_offset += 1
					self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY  # Trigger a redraw by adjusting the counter
				else:
					if self.user_marker_pos + self.curses_row_offset == len(self.KnownNetworks):
						continue  # Floor
					self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, ' ' * len(USER_MARKER))
					if self.user_marker_pos + USER_MARKER_OFFSET == self.curses_max_rows - 1:
						self.curses_row_offset += 1
						self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY
					else:
						self.user_marker_pos += 1
					self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, USER_MARKER)
			elif c in [10, 105, 73]:  # 105 = ord('i')
				self.curses_row_offset_store = (self.curses_row_offset_store ^ self.curses_row_offset)
				self.curses_row_offset = (self.curses_row_offset ^ self.curses_row_offset_store)
				self.curses_row_offset_store = (self.curses_row_offset_store ^ self.curses_row_offset)
				if self.curses_detailed:
					self.curses_detailed = None
					self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, USER_MARKER)
					self.screen.refresh()
					self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY  # Trigger a redraw by adjusting the counter
				elif 0 <= (self.user_marker_pos - 1 + self.curses_row_offset) < len(self.KnownNetworks):
					self.curses_detailed = self.KnownNetworks.keys()[(self.user_marker_pos - 1) + self.curses_row_offset_store]
					self.screen.refresh()
					self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY  # Trigger a redraw by adjusting the counter
			elif c in [113, 81]:  # 113 = ord('q')
				self.curses_lower_refresh_counter = 0
				subwindow = self.screen.subwin(6, 40, (self.curses_max_rows / 2 - 3), (self.curses_max_columns / 2 - 20))
				subwindow.erase()
				subwindow.addstr(2, 11, 'Really Quit? (y/N)')
				subwindow.border(0)
				subwindow.refresh()
				subwindow.overlay(self.screen)
				c = subwindow.getch()
				if c in [121, 89]:
					break
				self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY
			elif c in [104, 72]:  # 113 = ord('h')
				self.curses_lower_refresh_counter = 0
				subwindow = self.screen.subwin(10, 40, (self.curses_max_rows / 2 - 5), (self.curses_max_columns / 2 - 20))
				subwindow.erase()
				subwindow.addstr(1, 15, 'Help Menu')
				subwindow.addstr(2, 9, 'EAPeak Version: ' + __version__)
				subwindow.addstr(4, 2, 'i/Enter : Toggle View')
				subwindow.addstr(5, 2, 'q       : Quit')
				subwindow.addstr(6, 2, 'e       : Export Users For The')
				subwindow.addstr(7, 2, '          Selected Network')
				subwindow.border(0)
				subwindow.refresh()
				subwindow.overlay(self.screen)
				c = subwindow.getch()
				self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY
			elif c in [101, 69]:  # 101 = ord('e')
				usernames = []
				if self.curses_detailed in self.KnownNetworks:
					network = self.KnownNetworks[self.curses_detailed]
				else:
					network = self.KnownNetworks.values()[self.user_marker_pos - 1 + self.curses_row_offset]
				filename = network.ssid + '_users.txt'
				if network.clients:
					for client in network.clients.values():
						usernames.extend(client.identities.keys())
					try:
						filehandle = open(filename, 'w')
						filehandle.write("\n".join(usernames) + '\n')
						filehandle.close()
						message = 'Successfully Saved'
					except:  # pylint: disable=bare-except
						message = 'Failed To Save'
				else:
					message = 'No ID Strings'
				self.curses_lower_refresh_counter = 0
				subwindow = self.screen.subwin(10, 40, (self.curses_max_rows / 2 - 5), (self.curses_max_columns / 2 - 20))
				subwindow.erase()
				subwindow.addstr(2, 2, 'File: ' + filename)
				subwindow.addstr(3, 2, message)
				subwindow.addstr(6, 8, 'Press Any Key To Continue')
				subwindow.border(0)
				subwindow.refresh()
				subwindow.overlay(self.screen)
				c = subwindow.getch()
				self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY
		self.cleanup_curses()
		return

	def curses_screen_draw_handler(self, save_to_xml):
		"""
		This is a function meant to be run in a seperate thread to
		handle drawing the curses interface to the screen.
		"""
		while self.curses_enabled:
			time.sleep(CURSES_REFRESH_FREQUENCY)
			if self.curses_lower_refresh_counter == 0:  # used to trigger pauses
				continue
			size = self.screen.getmaxyx()
			if size[0] < CURSES_MIN_Y or size[1] < CURSES_MIN_X:
				if not self.resize_dialog():
					break
				continue
			self.screen.refresh()
			self.screen.addstr(2, 4, 'EAPeak Capturing Live')  # This is all static, so don't use the messages queue
			self.screen.addnstr(3, 4, 'Found ' + str(len(self.KnownNetworks)) + ' Networks', 25)
			self.screen.addnstr(4, 4, "Processed {0} Packets".format(self.packetCounter), 30)
			self.screen.addstr(6, 4, 'Network Information:')
			if self.curses_lower_refresh_counter == CURSES_LOWER_REFRESH_FREQUENCY:
				self.curses_lower_refresh_counter = 1
				self.screen.move(7, 0)
				self.screen.clrtobot()
				if save_to_xml:
					self.export_xml()
			else:
				self.curses_lower_refresh_counter += 1
				continue

			messages = []
			ssids = self.KnownNetworks.keys()
			if self.curses_detailed and self.curses_detailed in self.KnownNetworks:
				network = self.KnownNetworks[self.curses_detailed]
				messages.append((TAB_LENGTH, 'SSID: ' + network.ssid))
				messages.append(CURSES_LINE_BREAK)

				messages.append((TAB_LENGTH, 'BSSIDs:'))
				for bssid in network.bssids:
					messages.append((TAB_DEPTH_2, bssid))
				messages.append(CURSES_LINE_BREAK)
				self.get_network_info(messages, network)
				messages.append(CURSES_LINE_BREAK)
				self.get_network_data(messages, network)
				if network.x509certs:
					messages.append(CURSES_LINE_BREAK)
					messages.append((TAB_LENGTH, 'Certificates:'))
					i = 1
					self.get_certs(messages, network, i)
					messages.pop()  # trash the trailing line break
				# message queue is built, now adjust it to be printed to the screen
				self.set_max_offset(len(messages) - (self.curses_max_rows - 7))
				for i in range(0, self.curses_row_offset):
					messages.pop(0)
				self.screen.border(0)
			else:
				messages.append((TAB_DEPTH_2, 'SSID:' + ' ' * (SSID_MAX_LENGTH + 2) + 'EAP Types:'))
				if self.curses_row_offset:
					messages.append((TAB_DEPTH_2, '[ MORE ]'))
				else:
					messages.append((TAB_DEPTH_2, '        '))
				for i in range(self.curses_row_offset, len(ssids)):
					if len(messages) > self.curses_max_rows - 8:
						messages.append((TAB_DEPTH_2, '[ MORE ]'))
						break
					network = self.KnownNetworks[ssids[i]]
					self.get_network_eap(network, messages, i)
				if not len(messages) > self.curses_max_rows - 2:
					messages.append((TAB_DEPTH_2, '        '))
				self.screen.border(0)
				self.screen.addstr(self.user_marker_pos + USER_MARKER_OFFSET, TAB_LENGTH, USER_MARKER)
			line = 7
			try:
				for message in messages:
					self.screen.addnstr(line, message[0], message[1], self.curses_max_columns - message[0])
					line += 1
					if line > self.curses_max_rows:
						break  # Fail safe
			except curses.error:
				pass
		self.cleanup_curses()
		return

	def get_network_data(self, messages, network):
		if network.wpsData:
			the_cheese_stands_alone = True
			for piece in ['Manufacturer', 'Model Name', 'Model Number', 'Device Name']:
				if network.wpsData.has_key(piece):
					if the_cheese_stands_alone:
						messages.append((TAB_LENGTH, 'WPS Information:'))
						the_cheese_stands_alone = False
					messages.append((TAB_DEPTH_2, piece + ': ' + network.wpsData[piece]))
			if not the_cheese_stands_alone:
				messages.append(CURSES_LINE_BREAK)
			del the_cheese_stands_alone, piece # pylint: disable=undefined-loop-variable
		if network.clients:
			messages.append((TAB_LENGTH, 'Clients:         '))
			clients = network.clients.values()
			for i in range(0, len(clients)):
				client = clients[i]
				messages.append((TAB_DEPTH_2, 'Client ' + str(i + 1) + ') MAC: ' + client.mac))
				if client.eapTypes:
					self.get_client_eap(client, messages)
				else:
					messages.append((TAB_DEPTH_2, 'EAP Types: [ UNKNOWN ]'))
				if client.identities:
					messages.append((TAB_DEPTH_2, 'Identities:'))
				for ident, eap in client.identities.items():
					messages.append((TAB_DEPTH_3, '(' + EAP_TYPES[eap] + ') ' + ident))
				if client.mschap:
					first = True
					for value in client.mschap:
						if 'r' not in value:
							continue
						if first:
							messages.append((TAB_DEPTH_2, 'MSChap:'))
							first = False
						messages.append((TAB_DEPTH_3, 'EAP Type: ' + EAP_TYPES[value['t']] + ', Identity: ' + value['i']))
						messages.append((TAB_DEPTH_3, 'C: ' + value['c']))
						messages.append((TAB_DEPTH_3, 'R: ' + value['r']))
					del first
				if client.wpsData:
					the_cheese_stands_alone = True
					for piece in ['Manufacturer', 'Model Name', 'Model Number', 'Device Name']:
						if client.wpsData.has_key(piece):
							if the_cheese_stands_alone:
								messages.append((TAB_DEPTH_2, 'WPS Information:'))
								the_cheese_stands_alone = False
							messages.append((TAB_DEPTH_3, piece + ': ' + client.wpsData[piece]))
					del the_cheese_stands_alone, piece # pylint: disable=undefined-loop-variable
				messages.append(CURSES_LINE_BREAK)
			messages.pop()	# trash the trailing line break
			del clients # pylint: disable=undefined-loop-variable
		else:
			messages.append((TAB_LENGTH, 'Clients: [ NONE ]'))

	def get_network_info(self, messages, network):
		tmpEapTypes = []
		if network.eapTypes:
			for eType in network.eapTypes:
				if eType in EAP_TYPES:
					tmpEapTypes.append(EAP_TYPES[eType])
				else:
					tmpEapTypes.append(str(eType))
		if tmpEapTypes:
			messages.append((TAB_LENGTH, 'EAP Types: ' + ", ".join(tmpEapTypes)))
		else:
			messages.append((TAB_LENGTH, 'EAP Types: [ NONE ]'))
		tmpVendorIDs = []
		if network.expandedVendorIDs:
			for vType in network.expandedVendorIDs:
				if vType in EXPANDED_EAP_VENDOR_IDS:
					tmpVendorIDs.append(EXPANDED_EAP_VENDOR_IDS[vType])
				else:
					tmpVendorIDs.append(str(vType))
		if tmpVendorIDs:
			messages.append((TAB_LENGTH, 'Expanded Vendor IDs: ' + ", ".join(tmpVendorIDs)))
		del tmpEapTypes, tmpVendorIDs

	def set_max_offset(self, max_offset):
		if max_offset < 0:
			max_offset = 0
		if self.curses_row_offset > max_offset:
			self.curses_row_offset = max_offset

	def get_network_eap(self, network, messages, i):
		tmpEapTypes = []
		if network.eapTypes:
			for eType in network.eapTypes:
				if eType in EAP_TYPES:
					tmpEapTypes.append(EAP_TYPES[eType])
				else:
					tmpEapTypes.append(str(eType))
		if i < 9:
			messages.append((TAB_DEPTH_2, str(i + 1) + ')  ' + network.ssid + ' ' * (SSID_MAX_LENGTH - len(network.ssid) + 3) + ", ".join(tmpEapTypes)))
		else:
			messages.append((TAB_DEPTH_2, str(i + 1) + ') ' + network.ssid + ' ' * (SSID_MAX_LENGTH - len(network.ssid) + 3) + ", ".join(tmpEapTypes)))

	def get_client_eap(self, client, messages):
		tmpEapTypes = []
		for y in client.eapTypes:
			if y in EAP_TYPES:
				tmpEapTypes.append(EAP_TYPES[y])
			else:
				tmpEapTypes.append(str(y))
		messages.append((TAB_DEPTH_2, 'EAP Types: ' + ", ".join(tmpEapTypes)))

	def get_certs(self, messages, network, i):
		for cert in network.x509certs:
			messages.append((TAB_DEPTH_2, 'Certificate ' + str(i) + ') Expiration Date: ' + str(cert.get_not_after())))
			data = cert.get_issuer()
			messages.append((TAB_DEPTH_2, 'Issuer:'))
			for X509_Name_Entry_inst in data.get_entries_by_nid(13): 	# 13 is CN
				messages.append((TAB_DEPTH_3, 'CN: ' + X509_Name_Entry_inst.get_data().as_text()))
			for X509_Name_Entry_inst in data.get_entries_by_nid(18): 	# 18 is OU
				messages.append((TAB_DEPTH_3, 'OU: ' + X509_Name_Entry_inst.get_data().as_text()))
			data = cert.get_subject()
			messages.append((TAB_DEPTH_2, 'Subject:'))
			for X509_Name_Entry_inst in data.get_entries_by_nid(13): 	# 13 is CN
				messages.append((TAB_DEPTH_3, 'CN: ' + X509_Name_Entry_inst.get_data().as_text()))
			for X509_Name_Entry_inst in data.get_entries_by_nid(18): 	# 18 is OU
				messages.append((TAB_DEPTH_3, 'OU: ' + X509_Name_Entry_inst.get_data().as_text()))
			del data
			i += 1
			messages.append(CURSES_LINE_BREAK)

	def parse_live_capture(self, packet, quite=True):
		"""
		Function is meant to be passed to Scapy's sniff() function similar to:
		lambda packet: eapeakParser.parseLiveCapture(packet, use_curses)

		sniff(iface = 'mon0', prn = lambda packet: eapeakParser.parseLiveCapture(packet, False) )
		"""
		self.parse_wireless_packet(packet)
		if self.curses_enabled or quite:
			return
		sys.stdout.write('Packets: ' + str(self.packetCounter) + ' Wireless Networks: ' + str(len(self.KnownNetworks)) + '\r')
		sys.stdout.flush()

	def resize_dialog(self):
		"""
		This is a dialog to be used to warn the user when a screen
		resize event has been used to make the screen to small for use.
		"""
		self.curses_lower_refresh_counter = 0
		size = self.screen.getmaxyx()
		self.screen.erase()
		self.screen.addstr(0, 0, 'Screen Too Small, Requires')
		self.screen.addstr(1, 0, 'At Least: ' + str(CURSES_MIN_X) + 'x' + str(CURSES_MIN_Y))
		self.screen.refresh()
		while size[0] < CURSES_MIN_Y or size[1] < CURSES_MIN_X:
			if size[0] < 2 or size[1] < 26:
				return False
			size = self.screen.getmaxyx()
			self.screen.refresh()  # This has to be here
		self.screen.erase()
		self.screen.refresh()
		self.curses_lower_refresh_counter = CURSES_LOWER_REFRESH_FREQUENCY  # Trigger a redraw by adjusting the counter
		self.curses_max_rows = size[0] - 2  # Minus 2 for the border on the top and bottom
		self.curses_max_columns = size[1] - 2
		return True

	def cleanup_curses(self):
		"""
		This cleans up the curses interface and resets things back to
		normal.
		"""
		if not self.curses_enabled:
			return
		self.screen.erase()
		del self.screen
		curses.endwin()
		curses.echo()
		self.curses_enabled = False