diff --git a/net/arp-scan/Makefile b/net/arp-scan/Makefile new file mode 100644 index 000000000..f0e2e7338 --- /dev/null +++ b/net/arp-scan/Makefile @@ -0,0 +1,8 @@ +PLUGIN_NAME= ARP Scan +PLUGIN_VERSION= 0.1 +PLUGIN_COMMENT= Get all peers connected to a LAN +PLUGIN_MAINTAINER= giuseppe.demarco@unical.it +PLUGIN_DEPENDS= arp-scan +PLUGINS_DEVEL= yes + +.include "../../Mk/plugins.mk" diff --git a/net/arp-scan/pkg-descr b/net/arp-scan/pkg-descr new file mode 100644 index 000000000..71c2c3c88 --- /dev/null +++ b/net/arp-scan/pkg-descr @@ -0,0 +1,16 @@ +ARP Scan sends ARP packets to hosts on the local network and displays +any responses that are received. + +The Address Resolution Protocol (ARP) uses a simple message format containing +one address resolution request or response (usually IPv4). + +ARP Scan is a simple tool yet very powerful and it represent the only way +to find a device using an arp response. Once you’ve found the MAC +address, you can find more info about that device by matching that MAC +address to it’s vendor. + +Arp scan techniques are very important to understand ARP/MAC responses +for penetration tester and diagnosys purpose. It is the first tool for +network monitoring, especially against ip collisions, arpspoof and all +kind of hijack techniques, like Man-In-The-Middle Attack. It also helps +in cases when someone is spoofing IP address and DoS-ing your server. diff --git a/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/Api/ServiceController.php b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/Api/ServiceController.php new file mode 100644 index 000000000..af7b58d6e --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/Api/ServiceController.php @@ -0,0 +1,91 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED ``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 + * AUTHOR 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. + */ + +namespace OPNsense\ARPscanner\Api; + +use \OPNsense\Base\ApiControllerBase; +use \OPNsense\Core\Backend; + +class ServiceController extends ApiControllerBase +{ + + public function startAction() + { + if ($this->request->isPost()) { + $ifname = escapeshellarg($_POST['interface']); + $networks = escapeshellarg($_POST['networks']); + //~ return 'arpscanner start '.$ifname.' '.$networks; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun('arpscanner start '.$ifname.' '.$networks)), true); + return $result; + } + return array("message" => "unable to run config action"); + } + + public function statusAction() + { + if ($this->request->isPost()) { + $ifname = escapeshellarg($_POST['interface']); + //~ return 'arpscanner start '.$ifname.' '.$networks; + $backend = new Backend(); + $result = json_decode(trim($backend->configdRun('arpscanner status '.$ifname)), true); + return $result; + } + return array("message" => "this action must be called using the POST method"); + } + + public function stopAction() + { + if ($this->request->isPost()) { + $ifname = escapeshellarg($_POST['interface']); + $backend = new Backend(); + $bckresult = trim($backend->configdRun("arpscanner stop ".$ifname)); + if ($bckresult !== null) { + // only return valid json type responses + return $bckresult; + } + return array("message" => "error"); + } + } + + public function checkAction() + { + if ($this->request->isPost()) { + $ifname = escapeshellarg($_POST['interface']); + // test: "configctl arpscanner check em0" + $backend = new Backend(); + $bckresult = json_decode(trim($backend->configdRun("arpscanner check ".$ifname)), true); + if ($bckresult !== null) { + // only return valid json type responses + return $bckresult; + } + return array("message" => "error"); + } + } + +} diff --git a/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/Api/SettingsController.php b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/Api/SettingsController.php new file mode 100644 index 000000000..9361fe723 --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/Api/SettingsController.php @@ -0,0 +1,103 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED ``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 + * AUTHOR 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. + * +**/ +namespace OPNsense\ARPscanner\Api; + +use \OPNsense\Base\ApiControllerBase; +use \OPNsense\ARPscanner\ARPscanner; +use \OPNsense\Core\Config; +use \OPNsense\Core\Backend; + +class SettingsController extends ApiControllerBase +{ + /* retrieve general settings + * @return array general settings + */ + public function getAction() + { + // define list of configurable settings + $result = array(); + if ($this->request->isGet()) { + $mdl = new ARPscanner(); + $result['arpscanner'] = $mdl->getNodes(); + // returns: {"arpscanner":{"general":{"interface": + // {"lan":{"value":"lan","selected":1}},"networks": + // {"10.0.1.0\/24":{"value":"10.0.1.0\/24","selected":1}}}}} + + $backend = new Backend(); + $bckresult = trim($backend->configdRun("arpscanner interfaces")); + $ifnames = json_decode($bckresult); + + $result['arpscanner']['general']['interface'] = array(); + + if (is_array($ifnames) || is_object($ifnames)) + { + foreach ($ifnames as &$arr) { + $ifname = $arr[0]; + $result['arpscanner']['general']['interface'][$ifname] = array(); + $result['arpscanner']['general']['interface'][$ifname]['value'] = join(", ", array($ifname, " (".$arr[2].")") ); + } + } + // $result['arpscanner']['general']['networks'] = '192.168.1.0/24,172.16.45.0/25'; + } + return $result; + } + + /** + * update arpscan settings + * @return array status + */ + public function setAction() + { + $result = array("result"=>"failed"); + if ($this->request->isPost()) { + // load model and update with provided data + $mdl = new ARPscanner(); + $mdl->setNodes($this->request->getPost("arpscanner")); + + // perform validation + $valMsgs = $mdl->performValidation(); + foreach ($valMsgs as $field => $msg) { + if (!array_key_exists("validations", $result)) { + $result["validations"] = array(); + } + $result["validations"]["general.".$msg->getField()] = $msg->getMessage(); + } + + // serialize model to config and save + if ($valMsgs->count() == 0) { + $mdl->serializeToConfig(); + Config::getInstance()->save(); + $result["result"] = "saved"; + } + } + return $result; + } + + +} diff --git a/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/IndexController.php b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/IndexController.php new file mode 100644 index 000000000..ec5c9f438 --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/IndexController.php @@ -0,0 +1,51 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED ``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 + * AUTHOR 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. + * + */ + +namespace OPNsense\ARPscanner; + +/** + * Class IndexController + * @package OPNsense\ARPscanner + */ +class IndexController extends \OPNsense\Base\IndexController +{ + /** + * ARP scanner index page + * @throws \Exception + */ + public function indexAction() + { + $this->view->title = gettext('ARP Scan'); + $this->view->general = $this->getForm("general"); + $this->view->pick('OPNsense/ARPscanner/index'); + + $this->view->generalForm = $this->getForm("general"); + } +} diff --git a/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/forms/general.xml b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/forms/general.xml new file mode 100644 index 000000000..e7cd44a1d --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/controllers/OPNsense/ARPscanner/forms/general.xml @@ -0,0 +1,15 @@ +
+ + arpscanner.general.interface + + dropdown + Interface where to find the networks logically linked to. + + + arpscanner.general.networks + + text + Network to scan, default: localnet. + + +
diff --git a/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ACL/ACL.xml b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ACL/ACL.xml new file mode 100644 index 000000000..9c1dd4d6c --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ACL/ACL.xml @@ -0,0 +1,9 @@ + + + Diagnostics: ARP Scan + + ui/arpscanner/* + api/arpscanner/* + + + diff --git a/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ARPscanner.php b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ARPscanner.php new file mode 100644 index 000000000..dc22b94eb --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ARPscanner.php @@ -0,0 +1,42 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED ``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 + * AUTHOR 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. + * + */ + +namespace OPNsense\ARPscanner; + +use OPNsense\Base\BaseModel; + +class ARPscanner extends BaseModel +{ + public function test(){ + $command="/sbin/ifconfig -l -u"; + exec($command, $output); + return $output; + } +} diff --git a/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ARPscanner.xml b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ARPscanner.xml new file mode 100644 index 000000000..906e66e49 --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/ARPscanner.xml @@ -0,0 +1,19 @@ + + //OPNsense/ARPscanner + 1.0.0 + ARP Scan + + + + lan + Y + + + + N + Scan a different ipv4 network, instead of localnet + /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}(,)?)+$/ + + + + diff --git a/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/Menu/Menu.xml b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/Menu/Menu.xml new file mode 100644 index 000000000..46853c31f --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/models/OPNsense/ARPscanner/Menu/Menu.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/net/arp-scan/src/opnsense/mvc/app/views/OPNsense/ARPscanner/index.volt b/net/arp-scan/src/opnsense/mvc/app/views/OPNsense/ARPscanner/index.volt new file mode 100644 index 000000000..1b3a3b30a --- /dev/null +++ b/net/arp-scan/src/opnsense/mvc/app/views/OPNsense/ARPscanner/index.volt @@ -0,0 +1,208 @@ +{# + +Copyright © 2017 Giuseppe De Marco +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. 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. + +THIS SOFTWARE IS PROVIDED “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 +AUTHOR 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. + +#} + + + + +
+ + + + +
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_GeneralSettings'])}} + +
+ + + +
+
+
+ +
+
+
+ + + + + + + + + + + + + + +
{{ lang._('Interface name') }}{{ lang._('Started') }}{{ lang._('Last update') }}

+
+ + + + + + + + + + + +
{{ lang._('IP') }}{{ lang._('MAC') }}{{ lang._('Vendor') }}
+
+
+
diff --git a/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py new file mode 100644 index 000000000..5c3474e1f --- /dev/null +++ b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python2.7 + +""" + Copyright (c) 2017 Giuseppe De Marco + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. 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. + + THIS SOFTWARE IS PROVIDED ``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 + AUTHOR 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. +""" +from subprocess import Popen, PIPE +import datetime +import os +import time +import json +import sys +import re + +# imports from custom cls +from ProcessIO import ProcessIO +from FileIO import FileIO + +class ArpScanner(ProcessIO): + os_command_filter = """ps ax | \ + grep "ARPscanner\|arp-scan -I" | \ + grep {} | \ + grep -v "ps ax "| \ + grep -E '^[ 0-9]+'| \ + awk -F' ' '{{print $1}}'""" + + def __init__(self, ifname, network): + """ + netif='eth0' + network_list=['192.168.0.0/24', ...] + """ + self.ifname = ifname + self.network = network + self.result = {} # filtered output containing only what needed + # regexp used to retrieve data from arp-scan system command stdout + self.regexp = '([0-9\.]+)[\t]*([\dA-F]{2}(?:[-:][\dA-F]{2}){5})[\t]*([A-Za-z0-9\ \.\-\,\'\(\)]*)' + # os_command_filter needs '{}'.format(ifname) + self._DEBUG = False + + # FileIO contains all the IO files + tmp_fileio_path = '/tmp/ARPscanner' + self.tmp = tmp_fileio_path + + self.result['peers'] = [] + self.result['network'] = network + self.result['interface'] = self.ifname + self.result['started'] = datetime.datetime.now().isoformat() + self.result['last_modify'] = datetime.datetime.now().isoformat() + + def status(self): + """ + if arp-scan is running: parse .current file + else: parse .last file + returns json parsing of arp-scan output + """ + fout = os.path.sep.join((self.tmp, self.ifname))+'.out' + if not os.path.exists(fout): + return + + self.result['last'] = time.ctime(os.path.getmtime(fout)) + self.result['started'] = time.ctime(os.path.getctime(fout)) + + with open(fout, 'r') as f: + fcont = f.read() + #~ print(fcont) + regexp = re.findall(self.regexp , fcont, re.I) + if self._DEBUG: print(regexp) + for netfound in regexp: + self.result['peers'].append( + (netfound[0].replace('\t', ''), + netfound[1], netfound[2])) + #~ return self.result + + def start(self): + """ + returns 1 if started + returns 0 if already running + """ + running = self.check_run(self.ifname, self.os_command_filter) + if running: return self.status() + + fileio = FileIO(self.ifname, self.tmp) + os_command = ["arp-scan", "-I", self.ifname, self.network, + "--retry", "5"] + # run a child and detach + osc = Popen(os_command, + stdout=fileio.out, + stderr=fileio.err, + bufsize=0, + shell=False) + + def get_json(self): + return json.dumps(self.result) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-i', nargs='?', required=True, + help="network interface") + parser.add_argument('-net', nargs='?', help="""network to scan""") + parser.add_argument('-check', action="store_true", required=False, + help="check if arp-san is running on that interface") + parser.add_argument('-start', action="store_true", required=False, + help="starts arp-scan") + parser.add_argument('-stop', action="store_true", required=False, + help="Stops scanning on that interfaces") + parser.add_argument('-status', action="store_true", required=False, + help="Parse arp-scan stdout and return json") + + args = parser.parse_args() + + if not args.net: + args.net = '--localnet' + + if args.check: + pids = ArpScanner.check_run(args.i, ArpScanner.os_command_filter) + print(pids) + sys.exit() + + if args.stop: + killed = ArpScanner.stop(args.i, ArpScanner.os_command_filter) + print(killed) + sys.exit() + + ap = ArpScanner(args.i, args.net) + + if args.start: + ap.start() + + ap.status() + print(ap.get_json()) + diff --git a/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/FileIO.py b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/FileIO.py new file mode 100644 index 000000000..3b02e34bc --- /dev/null +++ b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/FileIO.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python2.7 + +""" + Copyright (c) 2017 Giuseppe De Marco + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. 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. + + THIS SOFTWARE IS PROVIDED ``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 + AUTHOR 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 os + +class FileIO(object): + """ + This class manages files where data is read + .out and .err file, where os_command stdout and stderr is stored + """ + def __init__(self, name, path): + # file names + self.nerr = '{}.err'.format(name) + self.nout = '{}.out'.format(name) + + # file paths + self.epath = os.path.sep.join((path, self.nerr)) + self.opath = os.path.sep.join((path, self.nout)) + + if not os.path.exists(path): + os.makedirs(path) + + # file obj, 1 means the buffer size, small as possibile to flush + # data soon as possible :) + # this feature would be better with python3 + self.err = open(self.epath, 'w', buffering=0) + self.out = open(self.opath, 'w', buffering=0) + + + def close(self): + self.err.close() + self.out.close() diff --git a/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/IPtools.py b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/IPtools.py new file mode 100644 index 000000000..6b2eea820 --- /dev/null +++ b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/IPtools.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +#~ Copyright © 2017 Giuseppe De Marco +#~ All rights reserved. +#~ +#~ Redistribution and use in source and binary forms, with or without modification, +#~ are permitted provided that the following conditions are met: +#~ +#~ 1. Redistributions of source code must retain the above copyright notice, +#~ this list of conditions and the following disclaimer. +#~ +#~ 2. 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. +#~ +#~ THIS SOFTWARE IS PROVIDED “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 +#~ AUTHOR 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 socket +import fcntl +import struct +import array +import subprocess + +# get_all_interfaces +from collections import namedtuple +import re +import subprocess +import json + +# From linux/sockios.h +#~ SIOCGIFCONF = 0x8912 +#~ SIOCGIFINDEX = 0x8933 +SIOCGIFFLAGS = 0x8913 +#~ SIOCSIFFLAGS = 0x8914 +SIOCGIFHWADDR = 0x8927 +SIOCSIFHWADDR = 0x8924 +SIOCGIFADDR = 0x8915 +#~ SIOCSIFADDR = 0x8916 +#~ SIOCGIFNETMASK = 0x891B +#~ SIOCSIFNETMASK = 0x891C +#~ SIOCETHTOOL = 0x8946 + +# From linux/if.h +IFF_UP = 0x1 + + +# From linux/socket.h +AF_UNIX = 1 +AF_INET = 2 + +class IPtools(object): + + @staticmethod + def get_ip_address(ifname): + # python2 only + ifname = str.encode(ifname) + # + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + SIOCGIFADDR, + struct.pack('256s', ifname[:15]) + )[20:24]) + + @staticmethod + def get_netmask(ifname): + # python2 only + ifname = str.encode(ifname) + # + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 35099, + struct.pack('256s', ifname))[20:24]) + + @staticmethod + def is_up(ifname): + ''' Return True if the interface is up, False otherwise. ''' + # python2 only + ifname = str.encode(ifname) + # + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + # Get existing device flags + ifreq = struct.pack('16sh', ifname, 0) + flags = struct.unpack('16sh', + fcntl.ioctl(s.fileno(), + SIOCGIFFLAGS, + ifreq))[1] + + # Set new flags + if flags & IFF_UP: + return True + else: + return False + + @staticmethod + def get_mac(ifname): + ''' Obtain the device's mac address. ''' + # python2 only + ifname = str.encode(ifname) + # + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + ifreq = struct.pack('16sH14s', ifname, AF_UNIX, b'\x00'*14) + res = fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, ifreq) + address = struct.unpack('16sH14s', res)[2] + mac = struct.unpack('6B8x', address) + + return ":".join(['%02X' % i for i in mac]) + + @staticmethod + def set_mac(ifname, newmac): + ''' Set the device's mac address. Device must be down for this to + succeed. ''' + # python2 only + ifname = str.encode(ifname) + # + macbytes = [int(i, 16) for i in newmac.split(':')] + ifreq = struct.pack('16sH6B8x', ifname, AF_UNIX, *macbytes) + fcntl.ioctl(s.fileno(), SIOCSIFHWADDR, ifreq) + + + @staticmethod + def get_interfaces(json_output=False): + """ + Get a list of network interfaces on Linux. + """ + name_pattern = "^(\w+)\s" + mac_pattern = ".*?HWaddr[ ]([0-9A-Fa-f:]{17})" + ip_pattern = ".*?\n\s+inet[ ]addr:((?:\d+\.){3}\d+)" + pattern = re.compile("".join((name_pattern, + mac_pattern, + ip_pattern, + )), + flags=re.MULTILINE) + + ifconfig = subprocess.check_output("ifconfig").decode() + interfaces = pattern.findall(ifconfig) + Interface = namedtuple("Interface", "name {mac} {ip}".format( + mac="mac", + ip="ip")) + res = [Interface(*interface) for interface in interfaces] + if json_output: return json.dumps(res) + return res + + @staticmethod + def get_interfaces_bsd(json_output=False): + """ + Get a list of network interfaces on BSD. + """ + name_pattern = "^(\w+).+[\n\t\s]*.*[\n\t\s]*" + mac_pattern = ".*?ether[ ]([0-9A-Fa-f:]{17})[\n\t\s]*" + ip_pattern = ".*?\n\s+inet[ ]((?:\d+\.){3}\d+)" + pattern = re.compile("".join((name_pattern, + mac_pattern, + ip_pattern, + )), + flags=re.MULTILINE) + + ifconfig = subprocess.check_output(["ifconfig", "-u"]).decode() + interfaces = pattern.findall(ifconfig) + Interface = namedtuple("Interface", "name {mac} {ip}".format( + mac="mac", + ip="ip")) + res = [Interface(*interface) for interface in interfaces] + if json_output: return json.dumps(res) + return res + +if __name__ == '__main__': + print(IPtools.get_interfaces_bsd(json_output=True)) diff --git a/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/ProcessIO.py b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/ProcessIO.py new file mode 100644 index 000000000..984477a77 --- /dev/null +++ b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/ProcessIO.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python2.7 + +""" + Copyright (c) 2017 Giuseppe De Marco + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. 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. + + THIS SOFTWARE IS PROVIDED ``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 + AUTHOR 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. +""" +from subprocess import Popen, PIPE +from os import kill, getpid, linesep + +_DEBUG = False + +class ProcessIO(object): + @staticmethod + def check_run(ifname, os_command_filter): + """ + returns PID if running on that ifname + else return 0 + """ + mypid = getpid() + os_command = os_command_filter.format(ifname) + osc = Popen(os_command, + stdin=PIPE, + stdout=PIPE, + stderr=PIPE, + shell=True) + output, err = osc.communicate() + if _DEBUG: print(output) + if output: + pids = [] + for pid in output.split(linesep): + if pid and int(pid) != mypid: + pids.append(int(pid)) + return pids[:] + return 0 + + @classmethod + def stop(cls, ifname, os_command_filter): + """ stop scanning on that interface """ + mypid = getpid() + chkrun = cls.check_run(ifname, os_command_filter) + if not chkrun: return [] + pids = [int(i) for i in chkrun] + killed = [] + for pid in pids: + if pid == mypid: continue + try: + kill(pid, 9) + killed.append(pid) + except Exception as e: + pass + return killed diff --git a/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/__init__.py b/net/arp-scan/src/opnsense/scripts/OPNsense/ARPscanner/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/net/arp-scan/src/opnsense/service/conf/actions.d/actions_arpscanner.conf b/net/arp-scan/src/opnsense/service/conf/actions.d/actions_arpscanner.conf new file mode 100644 index 000000000..a672b4a37 --- /dev/null +++ b/net/arp-scan/src/opnsense/service/conf/actions.d/actions_arpscanner.conf @@ -0,0 +1,30 @@ +[start] +command:python2.7 -u /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py +parameters:-i %s -net %s -start +type:script_output +message:start arp-scan + +[stop] +#~ command:ps ax | grep "ARPscanner\|arp-scan" | grep -E '[ 0-9]+' | awk -F' ' '{print $1}' | xargs | xargs kill -TERM +command:python2.7 /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py +parameters:-i %s -stop +type:script +message:stop arp-scan + +[status] +command:python2.7 -u /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py +parameters:-i %s +type:script_output +message:status + +[interfaces] +command:python2.7 /usr/local/opnsense/scripts/OPNsense/ARPscanner/IPtools.py +parameters: +type:script_output +message:get network interfaces + +[check] +command:python2.7 /usr/local/opnsense/scripts/OPNsense/ARPscanner/ARPscanner.py +parameters:-i %s -check +type:script_output +message:check if arp scan is running on %s interface diff --git a/net/arp-scan/src/opnsense/service/templates/OPNsense/ARPscanner/+TARGETS b/net/arp-scan/src/opnsense/service/templates/OPNsense/ARPscanner/+TARGETS new file mode 100644 index 000000000..322d2c801 --- /dev/null +++ b/net/arp-scan/src/opnsense/service/templates/OPNsense/ARPscanner/+TARGETS @@ -0,0 +1 @@ +ARPscanner.conf:/usr/local/etc/ARPscanner/ARPscanner.conf diff --git a/net/arp-scan/src/opnsense/service/templates/OPNsense/ARPscanner/ARPscanner.conf b/net/arp-scan/src/opnsense/service/templates/OPNsense/ARPscanner/ARPscanner.conf new file mode 100644 index 000000000..80b065eb1 --- /dev/null +++ b/net/arp-scan/src/opnsense/service/templates/OPNsense/ARPscanner/ARPscanner.conf @@ -0,0 +1,3 @@ +[general] +Interface={{ OPNsense.arpscanner.general.Interface|default("") }} +Networks={{ OPNsense.arpscanner.general.Networks|default('192.168.1.0/24,172.16.45.0/25') }}