%PDF- %PDF-
| Direktori : /backups/router/usr/local/lib/suricata/python/suricata/sc/ |
| Current File : //backups/router/usr/local/lib/suricata/python/suricata/sc/suricatasc.py |
# Copyright(C) 2012-2023 Open Information Security Foundation
# 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, version 2 of the License.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
try:
import simplejson as json
except ImportError:
import json
import readline
import select
import sys
from socket import AF_UNIX, error, socket
from inspect import currentframe
from suricata.sc.specs import argsd
SURICATASC_VERSION = "1.0"
VERSION = "0.2"
INC_SIZE = 1024
def get_linenumber():
cf = currentframe()
return cf.f_back.f_lineno
class SuricataException(Exception):
"""
Generic class for suricatasc exception
"""
def __init__(self, value):
super(SuricataException, self).__init__(value)
self.value = value
def __str__(self):
return str(self.value)
class SuricataNetException(SuricataException):
"""
Exception raised when a network error occurs
"""
class SuricataCommandException(SuricataException):
"""
Exception raised when the command is incorrect
"""
class SuricataReturnException(SuricataException):
"""
Exception raised when return message is incorrect
"""
class SuricataCompleter:
def __init__(self, words):
self.words = words
self.generator = None
def complete(self, text):
for word in self.words:
if word.startswith(text):
yield word
def __call__(self, text, state):
if state == 0:
self.generator = self.complete(text)
try:
return next(self.generator)
except StopIteration:
return None
class SuricataSC:
def __init__(self, sck_path, verbose=False):
self.basic_commands = [
"shutdown",
"quit",
"pcap-file-number",
"pcap-file-list",
"pcap-last-processed",
"pcap-interrupt",
"iface-list",
"reload-tenants",
]
self.fn_commands = [
"pcap-file",
"pcap-file-continuous",
"iface-stat",
"conf-get",
"unregister-tenant-handler",
"register-tenant-handler",
"unregister-tenant",
"register-tenant",
"reload-tenant",
"add-hostbit",
"remove-hostbit",
"list-hostbit",
"memcap-set",
"memcap-show",
"dataset-add",
"dataset-remove",
"get-flow-stats-by-id",
"dataset-clear",
"dataset-lookup",
]
self.cmd_list = self.basic_commands + self.fn_commands
self.sck_path = sck_path
self.verbose = verbose
self.socket = socket(AF_UNIX)
def json_recv(self):
cmdret = None
data = ""
while True:
if sys.version < '3':
received = self.socket.recv(INC_SIZE)
else:
received = self.socket.recv(INC_SIZE).decode('iso-8859-1')
if not received:
break
data += received
if data.endswith('\n'):
cmdret = json.loads(data)
break
return cmdret
def send_command(self, command, arguments=None):
if command not in self.cmd_list and command != 'command-list':
raise SuricataCommandException("L{}: Command not found: {}".format(get_linenumber(), command))
cmdmsg = {}
cmdmsg['command'] = command
if arguments:
cmdmsg['arguments'] = arguments
if self.verbose:
print("SND: " + json.dumps(cmdmsg))
cmdmsg_str = json.dumps(cmdmsg) + "\n"
if sys.version < '3':
self.socket.send(cmdmsg_str)
else:
self.socket.send(bytes(cmdmsg_str, 'iso-8859-1'))
ready = select.select([self.socket], [], [], 600)
if ready[0]:
cmdret = self.json_recv()
else:
cmdret = None
if not cmdret:
raise SuricataReturnException("L{}: Unable to get message from server".format(get_linenumber))
if self.verbose:
print("RCV: "+ json.dumps(cmdret))
return cmdret
def connect(self):
try:
if self.socket is None:
self.socket = socket(AF_UNIX)
self.socket.connect(self.sck_path)
except error as err:
raise SuricataNetException("L{}: {}".format(get_linenumber(), err))
self.socket.settimeout(10)
#send version
if self.verbose:
print("SND: " + json.dumps({"version": VERSION}))
if sys.version < '3':
self.socket.send(json.dumps({"version": VERSION}))
else:
self.socket.send(bytes(json.dumps({"version": VERSION}), 'iso-8859-1'))
ready = select.select([self.socket], [], [], 600)
if ready[0]:
cmdret = self.json_recv()
else:
cmdret = None
if not cmdret:
raise SuricataReturnException("L{}: Unable to get message from server".format(get_linenumber()))
if self.verbose:
print("RCV: "+ json.dumps(cmdret))
if cmdret["return"] == "NOK":
raise SuricataReturnException("L{}: Error: {}".format(get_linenumber(), cmdret["message"]))
cmdret = self.send_command("command-list")
# we silently ignore NOK as this means server is old
if cmdret["return"] == "OK":
self.cmd_list = cmdret["message"]["commands"]
self.cmd_list.append("quit")
def close(self):
self.socket.close()
self.socket = None
def execute(self, command):
full_cmd = command.split()
cmd = full_cmd[0]
cmd_specs = argsd[cmd]
required_args_count = len([d["required"] for d in cmd_specs if d["required"] and not "val" in d])
arguments = dict()
for c, spec in enumerate(cmd_specs, 1):
spec_type = str if "type" not in spec else spec["type"]
if spec["required"]:
if spec.get("val"):
arguments[spec["name"]] = spec_type(spec["val"])
continue
try:
arguments[spec["name"]] = spec_type(full_cmd[c])
except IndexError:
phrase = " at least" if required_args_count != len(cmd_specs) else ""
msg = "Missing arguments: expected{} {}".format(phrase, required_args_count)
raise SuricataCommandException("L{}: {}".format(get_linenumber(), msg))
except ValueError as ve:
raise SuricataCommandException("L{}: Erroneous arguments: {}".format(get_linenumber(), ve))
elif c < len(full_cmd):
arguments[spec["name"]] = spec_type(full_cmd[c])
return cmd, arguments
def parse_command(self, command):
arguments = None
cmd = command.split()[0] if command else None
if cmd in self.cmd_list:
if cmd in self.fn_commands:
cmd, arguments = getattr(self, "execute")(command=command)
else:
raise SuricataCommandException("L{}: Unknown command: {}".format(get_linenumber(), command))
return cmd, arguments
def interactive(self):
print("Command list: " + ", ".join(self.cmd_list))
try:
readline.set_completer(SuricataCompleter(self.cmd_list))
readline.set_completer_delims(";")
readline.parse_and_bind('tab: complete')
while True:
if sys.version < '3':
command = raw_input(">>> ").strip()
else:
command = input(">>> ").strip()
if command == "quit":
break
if len(command.strip()) == 0:
continue
try:
cmd, arguments = self.parse_command(command)
except SuricataCommandException as err:
print(err)
continue
try:
cmdret = self.send_command(cmd, arguments)
except IOError as err:
# try to reconnect and resend command
print("Connection lost, trying to reconnect")
try:
self.close()
self.connect()
except (SuricataNetException, SuricataReturnException) as err:
print(err.value)
continue
cmdret = self.send_command(cmd, arguments)
except (SuricataCommandException, SuricataReturnException) as err:
print("An exception occured: " + str(err.value))
continue
#decode json message
if cmdret["return"] == "NOK":
print("Error:")
print(json.dumps(cmdret["message"], sort_keys=True, indent=4, separators=(',', ': ')))
else:
print("Success:")
print(json.dumps(cmdret["message"], sort_keys=True, indent=4, separators=(',', ': ')))
except KeyboardInterrupt:
print("[!] Interrupted")
sys.exit(0)