#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
#  eapeak
#
#  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 argparse
import logging
import os
import select
import socket
import sys
import thread

sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
from eapeak.parse import CursesEapeakParsingEngine  # pylint: disable=no-name-in-module
from eapeak.parse import EapeakParsingEngine  # pylint: disable=no-name-in-module
from eapeak.common import __version__

try:
	from M2Crypto import X509
except ImportError:
	print('Error: Missing M2Crypto, Please Install The M2Crypto Libraries')
	print('Error: Now Exiting...')
	sys.exit(1)

from scapy.config import conf
from scapy.sendrecv import sniff

try:
	import curses  # pylint: disable=unused-import
except ImportError:
	curses_enabled = False
else:
	curses_enabled = True

__authors__ = ['Spencer McIntyre']

conf.ipv6_enabled = False

def main():
	parser = argparse.ArgumentParser(description='EAPeak: Analysis tool for EAP enabled wireless networks', conflict_handler='resolve')
	parser.add_argument('-f', '--file', dest='pcaps', nargs='+', action='store', default=[], help='the path to the PCap file(s)')
	parser.add_argument('-s', '--ssid', dest='target_ssids', nargs='+', action='store', help='specific SSID(s) to analyze')
	parser.add_argument('-b', '--bssid', dest='target_bssids', nargs='+', action='store', help='specific BSSID(s) to analyze')
	parser.add_argument('-l', '--live', dest='capture_live', action='store_true', default=False, help='analyze packets live')
	if curses_enabled:
		parser.add_argument('-c', '--curses', dest='use_curses', action='store_true', default=False, help='use the BETA curses interface when capturing live')
	parser.add_argument('-i', '--iface', dest='interface', action='store', help='interface to use when capturing live')
	parser.add_argument('-v', '--version', action='version', version=parser.prog + ' Version: ' + __version__)

	save_xml_parser = parser.add_mutually_exclusive_group()
	save_xml_parser.add_argument('--no-xml', dest='dont_save_xml', action='store_true', default=False, help='don\'t export data to xml (default: save when capturing)')
	save_xml_parser.add_argument('--xml', dest='save_xml', action='store_true', default=False, help='export data to xml (default: save when capturing)')

	cert_opts = parser.add_argument_group('access-point certificate export options')
	cert_opts.add_argument('--export-x509', dest='export_x509', action='store_true', default=False, help='start EAPeak certificate export wizard')
	cert_opts.add_argument('--x509-format', dest='export_x509_format', action='store', choices=['der', 'pem'], default='pem', help='format to save certificates as, default: pem')

	results = parser.parse_args()
	stored_files = results.pcaps
	target_ssids = results.target_ssids
	target_bssids = results.target_bssids
	capture_live = results.capture_live
	interface = results.interface
	if capture_live and curses_enabled:
		use_curses = results.use_curses
	else:
		use_curses = False
	if not capture_live and not stored_files:
		parser.print_help()
		return 0

	save_to_xml = (((not results.dont_save_xml) & results.capture_live) | results.save_xml)
	export_x509 = results.export_x509
	export_x509_format = results.export_x509_format
	del results, parser, cert_opts

	if capture_live and not interface:
		print('Error: You must specify an interface when using live mode.')
		return 1
	if capture_live and os.getuid():
		print('Error: You must be root when using live mode.')
		return 1
	# pylint: disable=anomalous-backslash-in-string
	print("""
 ________ 
< eapeak >
 -------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\\/\\
                ||----w |
                ||     ||
""")
	print('Welcome To EAPeak\nVersion: ' + __version__ + '\n')

	# Adjust the scapy logs to make them quiet, a hat tip to Philippe Biondi for setting it up so nicely
	scapy_runtime_log = logging.getLogger("scapy.runtime")
	scapy_runtime_log.setLevel(logging.ERROR)

	if use_curses:
		if target_bssids:
			print("Sorry, BSSID filtering currently only supported by EAPeak Parsing Engine")
		eapeak_parser = CursesEapeakParsingEngine(target_ssids)
	else:
		eapeak_parser = EapeakParsingEngine(target_ssids, target_bssids)  # pylint: disable=redefined-variable-type
	del target_ssids, target_bssids

	xml_files = []
	pcap_files = []
	for filename in stored_files:
		if 'xml' in filename.split('.')[-1]:
			if filename in xml_files:
				continue
			if not os.access(filename, os.R_OK):
				print('Error: Could not read file \'' + filename + '\' (invalid permissions)')
				continue
			xml_files.append(filename)
		elif 'cap' in filename.split('.')[-1]:
			if filename in pcap_files:
				continue
			if not os.access(filename, os.R_OK):
				print('Error: Could not read file \'' + filename + '\' (invalid permissions)')
				continue
			pcap_files.append(filename)

	if xml_files:
		eapeak_parser.parse_xml_files(xml_files, False)
	if pcap_files:
		eapeak_parser.parse_pcap_files(pcap_files, False)
	print('')
	del pcap_files, xml_files, stored_files

	if capture_live:
		print('Begining Live Capture...')
		if use_curses:
			err_code = eapeak_parser.init_curses()
			if err_code:
				print({1: 'Screen must be at least 99x25 for Curses'}[err_code])
				return 1
			del err_code
			timeout = 1.5
			thread.start_new_thread(eapeak_parser.curses_interaction_handler, ())
			thread.start_new_thread(eapeak_parser.curses_screen_draw_handler, (save_to_xml, ))
		else:
			timeout = None
		while True:
			try:
				sniff(iface=interface, store=0, prn=lambda packet: eapeak_parser.parse_live_capture(packet, use_curses), timeout=timeout)
				if (use_curses and not eapeak_parser.curses_enabled) or not use_curses:
					break
			except KeyboardInterrupt:
				break
			except socket.error:
				if use_curses:
					eapeak_parser.cleanup_curses()
				print('Error: Invalid Interface')
				return 0
			except select.error:
				if use_curses and not eapeak_parser.curses_enabled:
					break
		del timeout
		if use_curses:
			eapeak_parser.cleanup_curses()
		print('\nStopping Live Capture...')
		print('')

	if not len(eapeak_parser.KnownNetworks):
		print('Found No Usable Information\nTry Again With A Larger Capture')
		return 0
	elif export_x509:
		networks = eapeak_parser.KnownNetworks.keys()
		networks.sort()
		certs = {}
		for network in networks:
			network = eapeak_parser.KnownNetworks[network]
			for cert in network.x509certs:
				certs[cert] = network.ssid
		if len(certs) == 0:
			print('No Certificates Are Available For Exporting')
			return 0
		output = '\nAvailable Certificates:\n'
		i = 1
		for cert in certs.keys():
			output += 'Certificate #' + str(i) + ' SSID: ' + certs[cert]
			output += '\n    Expiration Date: ' + str(cert.get_not_after())
			data = cert.get_issuer()
			output += '\n    Issuer:'
			for X509_Name_Entry_inst in data.get_entries_by_nid(13):  # 13 is CN
				output += '\n        CN: ' + X509_Name_Entry_inst.get_data().as_text()
			for X509_Name_Entry_inst in data.get_entries_by_nid(18):  # 18 is OU
				output += '\n        OU: ' + X509_Name_Entry_inst.get_data().as_text()
			data = cert.get_subject()
			output += '\n    Subject:'
			for X509_Name_Entry_inst in data.get_entries_by_nid(13):  # 13 is CN
				output += '\n        CN: ' + X509_Name_Entry_inst.get_data().as_text()
			for X509_Name_Entry_inst in data.get_entries_by_nid(18):  # 18 is OU
				output += '\n        OU: ' + X509_Name_Entry_inst.get_data().as_text()
			del data
			output += '\n\n'
			i += 1
		print(output[:-1])
		del output
		tries = 3
		while tries:
			try:
				target = raw_input('Certificate To Export (1 - ' + str(i - 1) + '): ')
			except KeyboardInterrupt:
				print('')
				return 0
			if target.isdigit() and 0 < int(target) < i:
				target = int(target)
				break
			else:
				print('Invalid Certificate.')
				target = None
			tries -= 1
		del i, tries
		if target is None:
			print('Now Exiting...')
			return 0
		cert = certs.keys()[target - 1]

		if export_x509_format in ['der', 'DER']:
			filename = certs.values()[target - 1] + '_cert.crt'
			try:
				cert.save(filename, X509.FORMAT_DER)
				print('Certificate Successfully Saved To: ' + filename)
			except:  # pylint: disable=bare-except
				print('There Was An Error Saving The Certificate...')
		elif export_x509_format in ['pem', 'PEM']:
			filename = certs.values()[target - 1] + '_cert.pem'
			try:
				cert.save(filename, X509.FORMAT_PEM)
				print('Certificate Successfully Saved To: ' + filename)
			except:  # pylint: disable=bare-except
				print('There Was An Error Saving The Certificate...')
		print('Now Exiting...')
		return 0

	if save_to_xml:
		eapeak_parser.export_xml()
	intro0 = 'EAPeak Summary of Wireless Networks'
	intro1 = 'Found ' + str(len(eapeak_parser.KnownNetworks)) + ' Network(s)'
	intro = '*' * len(intro0) + '****\n'
	intro += '* ' + intro0 + ' *\n'
	intro += '* ' + intro1 + ' ' * (len(intro0) - len(intro1)) + ' *\n'
	intro += '*' * len(intro0) + '****\n'
	print(intro)
	del intro, intro0, intro1
	networks = eapeak_parser.KnownNetworks.keys()
	networks.sort()
	for network in networks:
		print(eapeak_parser.KnownNetworks[network].show())
		print('')
	return 0


if __name__ == '__main__':
	main()
