mirror of https://github.com/OISF/suricata
python: remove python implementation of suricatasc/suricatactl
parent
8fa347410e
commit
a0089190df
@ -1,39 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright (C) 2017-2022 Open Information Security Foundation
|
|
||||||
#
|
|
||||||
# You can copy, redistribute or modify this Program under the terms of
|
|
||||||
# the GNU General Public License version 2 as published by the Free
|
|
||||||
# Software Foundation.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
# version 2 along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
||||||
# 02110-1301, USA.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import site
|
|
||||||
|
|
||||||
exec_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
if os.path.exists(os.path.join(exec_dir, "..", "suricata", "ctl", "main.py")):
|
|
||||||
# Looks like we're running from the development directory.
|
|
||||||
sys.path.insert(0, ".")
|
|
||||||
else:
|
|
||||||
# Check if the Python modules are installed in the Suricata installation
|
|
||||||
# prefix.
|
|
||||||
version_info = sys.version_info
|
|
||||||
pyver = "%d.%d" % (version_info.major, version_info.minor)
|
|
||||||
path = os.path.realpath(os.path.join(
|
|
||||||
exec_dir, "..", "lib", "suricata", "python", "suricata"))
|
|
||||||
if os.path.exists(path):
|
|
||||||
sys.path.insert(0, os.path.dirname(path))
|
|
||||||
|
|
||||||
from suricata.ctl.main import main
|
|
||||||
sys.exit(main())
|
|
@ -1,100 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright(C) 2013-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.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
# Find the Python libdir.
|
|
||||||
exec_dir = os.path.dirname(__file__)
|
|
||||||
if os.path.exists(os.path.join(exec_dir, "..", "suricata", "ctl", "main.py")):
|
|
||||||
# Looks like we're running from the development directory.
|
|
||||||
sys.path.insert(0, ".")
|
|
||||||
else:
|
|
||||||
# Check if the Python modules are installed in the Suricata installation
|
|
||||||
# prefix.
|
|
||||||
version_info = sys.version_info
|
|
||||||
pyver = "%d.%d" % (version_info.major, version_info.minor)
|
|
||||||
path = os.path.realpath(os.path.join(
|
|
||||||
exec_dir, "..", "lib", "suricata", "python", "suricata"))
|
|
||||||
if os.path.exists(path):
|
|
||||||
sys.path.insert(0, os.path.dirname(path))
|
|
||||||
|
|
||||||
from suricata.sc import *
|
|
||||||
|
|
||||||
try:
|
|
||||||
from suricata.config import defaults
|
|
||||||
has_defaults = True
|
|
||||||
except:
|
|
||||||
has_defaults = False
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog='suricatasc', description='Client for Suricata unix socket')
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_const', const=True, help='verbose output (including JSON dump)')
|
|
||||||
parser.add_argument('-c', '--command', default=None, help='execute on single command and return JSON')
|
|
||||||
parser.add_argument('socket', metavar='socket', nargs='?', help='socket file to connect to', default=None)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.socket != None:
|
|
||||||
SOCKET_PATH = args.socket
|
|
||||||
elif has_defaults:
|
|
||||||
SOCKET_PATH = os.path.join(defaults.localstatedir, "suricata-command.socket")
|
|
||||||
else:
|
|
||||||
print("Unable to determine path to suricata-command.socket.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
sc = SuricataSC(SOCKET_PATH, verbose=args.verbose)
|
|
||||||
try:
|
|
||||||
sc.connect()
|
|
||||||
except SuricataNetException as err:
|
|
||||||
print("Unable to connect to socket %s: %s" % (SOCKET_PATH, err), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
except SuricataReturnException as err:
|
|
||||||
print("Unable to negotiate version with server: %s" % (err), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if args.command:
|
|
||||||
try:
|
|
||||||
(command, arguments) = sc.parse_command(args.command)
|
|
||||||
except SuricataCommandException as err:
|
|
||||||
print(err.value)
|
|
||||||
sys.exit(1)
|
|
||||||
try:
|
|
||||||
res = sc.send_command(command, arguments)
|
|
||||||
except (SuricataCommandException, SuricataReturnException) as err:
|
|
||||||
print(err.value)
|
|
||||||
sys.exit(1)
|
|
||||||
print(json.dumps(res))
|
|
||||||
sc.close()
|
|
||||||
if res['return'] == 'OK':
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
sc.interactive()
|
|
||||||
except SuricataNetException as err:
|
|
||||||
print("Communication error: %s" % (err))
|
|
||||||
sys.exit(1)
|
|
||||||
except SuricataReturnException as err:
|
|
||||||
print("Invalid return from server: %s" % (err))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("[+] Quit command client")
|
|
||||||
|
|
||||||
sc.close()
|
|
@ -1,129 +0,0 @@
|
|||||||
# Copyright (C) 2018 Open Information Security Foundation
|
|
||||||
#
|
|
||||||
# You can copy, redistribute or modify this Program under the terms of
|
|
||||||
# the GNU General Public License version 2 as published by the Free
|
|
||||||
# Software Foundation.
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# version 2 along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
||||||
# 02110-1301, USA.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("filestore")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidAgeFormatError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def register_args(parser):
|
|
||||||
subparser = parser.add_subparsers(help="sub-command help")
|
|
||||||
prune_parser = subparser.add_parser("prune",
|
|
||||||
help="Remove files in specified directory older than specified age")
|
|
||||||
required_args = prune_parser.add_argument_group("required arguments")
|
|
||||||
required_args.add_argument("-d", "--directory",
|
|
||||||
help="filestore directory", required=True)
|
|
||||||
required_args.add_argument("--age",
|
|
||||||
help="prune files older than age, units: s, m, h, d")
|
|
||||||
prune_parser.add_argument(
|
|
||||||
"-n", "--dry-run", action="store_true", default=False,
|
|
||||||
help="only print what would happen")
|
|
||||||
prune_parser.add_argument(
|
|
||||||
"-v", "--verbose", action="store_true",
|
|
||||||
default=False, help="increase verbosity")
|
|
||||||
prune_parser.add_argument(
|
|
||||||
"-q", "--quiet", action="store_true", default=False,
|
|
||||||
help="be quiet, log warnings and errors only")
|
|
||||||
prune_parser.set_defaults(func=prune)
|
|
||||||
|
|
||||||
|
|
||||||
def is_fileinfo(path):
|
|
||||||
return path.endswith(".json")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_age(age):
|
|
||||||
matched_age = re.match(r"(\d+)\s*(\w+)", age)
|
|
||||||
if not matched_age:
|
|
||||||
raise InvalidAgeFormatError(age)
|
|
||||||
val = int(matched_age.group(1))
|
|
||||||
unit = matched_age.group(2)
|
|
||||||
ts_units = ["s", "m", "h", "d"]
|
|
||||||
try:
|
|
||||||
idx = ts_units.index(unit)
|
|
||||||
except ValueError:
|
|
||||||
raise InvalidAgeFormatError("bad unit: %s" % (unit))
|
|
||||||
multiplier = 60 ** idx if idx != 3 else 24 * 60 ** 2
|
|
||||||
return val * multiplier
|
|
||||||
|
|
||||||
|
|
||||||
def get_filesize(path):
|
|
||||||
return os.stat(path).st_size
|
|
||||||
|
|
||||||
|
|
||||||
def remove_file(path, dry_run):
|
|
||||||
size = 0
|
|
||||||
size += get_filesize(path)
|
|
||||||
if not dry_run:
|
|
||||||
os.unlink(path)
|
|
||||||
return size
|
|
||||||
|
|
||||||
|
|
||||||
def set_logger_level(args):
|
|
||||||
if args.verbose:
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
if args.quiet:
|
|
||||||
logger.setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
|
|
||||||
def perform_sanity_checks(args):
|
|
||||||
set_logger_level(args)
|
|
||||||
err_msg = {
|
|
||||||
"directory": "filestore directory must be provided",
|
|
||||||
"age": "no age provided, nothing to do",
|
|
||||||
}
|
|
||||||
for val, msg in err_msg.items():
|
|
||||||
if not getattr(args, val):
|
|
||||||
print("Error: {}".format(msg), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
required_dirs = ["tmp", "00", "ff"]
|
|
||||||
for required_dir in required_dirs:
|
|
||||||
if not os.path.exists(os.path.join(args.directory, required_dir)):
|
|
||||||
logger.error("Provided directory is not a filestore directory")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def prune(args):
|
|
||||||
perform_sanity_checks(args)
|
|
||||||
age = parse_age(args.age)
|
|
||||||
now = time.time()
|
|
||||||
size = 0
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
for dirpath, dirnames, filenames in os.walk(args.directory, topdown=True):
|
|
||||||
# Do not go into the tmp directory.
|
|
||||||
if "tmp" in dirnames:
|
|
||||||
dirnames.remove("tmp")
|
|
||||||
for filename in filenames:
|
|
||||||
path = os.path.join(dirpath, filename)
|
|
||||||
mtime = os.path.getmtime(path)
|
|
||||||
this_age = now - mtime
|
|
||||||
if this_age > age:
|
|
||||||
logger.debug("Deleting %s; age=%ds", path, this_age)
|
|
||||||
size += remove_file(path, args.dry_run)
|
|
||||||
count += 1
|
|
||||||
logger.info("Removed %d files; %d bytes.", count, size)
|
|
||||||
return 0
|
|
@ -1,81 +0,0 @@
|
|||||||
# Copyright (C) 2017 Open Information Security Foundation
|
|
||||||
# Copyright (c) 2016 Jason Ish
|
|
||||||
#
|
|
||||||
# You can copy, redistribute or modify this Program under the terms of
|
|
||||||
# the GNU General Public License version 2 as published by the Free
|
|
||||||
# Software Foundation.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
# version 2 along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
||||||
# 02110-1301, USA.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
|
|
||||||
GREEN = "\x1b[32m"
|
|
||||||
BLUE = "\x1b[34m"
|
|
||||||
REDB = "\x1b[1;31m"
|
|
||||||
YELLOW = "\x1b[33m"
|
|
||||||
RED = "\x1b[31m"
|
|
||||||
YELLOWB = "\x1b[1;33m"
|
|
||||||
ORANGE = "\x1b[38;5;208m"
|
|
||||||
RESET = "\x1b[0m"
|
|
||||||
|
|
||||||
# A list of secrets that will be replaced in the log output.
|
|
||||||
secrets = {}
|
|
||||||
|
|
||||||
def add_secret(secret, replacement):
|
|
||||||
"""Register a secret to be masked. The secret will be replaced with:
|
|
||||||
<replacement>
|
|
||||||
"""
|
|
||||||
secrets[str(secret)] = str(replacement)
|
|
||||||
|
|
||||||
class SuriColourLogHandler(logging.StreamHandler):
|
|
||||||
"""An alternative stream log handler that logs with Suricata inspired
|
|
||||||
log colours."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_time(record):
|
|
||||||
local_time = time.localtime(record.created)
|
|
||||||
formatted_time = "%d/%d/%d -- %02d:%02d:%02d" % (local_time.tm_mday,
|
|
||||||
local_time.tm_mon,
|
|
||||||
local_time.tm_year,
|
|
||||||
local_time.tm_hour,
|
|
||||||
local_time.tm_min,
|
|
||||||
local_time.tm_sec)
|
|
||||||
return "%s" % (formatted_time)
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
|
|
||||||
if record.levelname == "ERROR":
|
|
||||||
level_prefix = REDB
|
|
||||||
message_prefix = REDB
|
|
||||||
elif record.levelname == "WARNING":
|
|
||||||
level_prefix = ORANGE
|
|
||||||
message_prefix = ORANGE
|
|
||||||
else:
|
|
||||||
level_prefix = YELLOW
|
|
||||||
message_prefix = ""
|
|
||||||
|
|
||||||
self.stream.write("%s%s%s - <%s%s%s> -- %s%s%s\n" % (
|
|
||||||
GREEN,
|
|
||||||
self.format_time(record),
|
|
||||||
RESET,
|
|
||||||
level_prefix,
|
|
||||||
record.levelname.title(),
|
|
||||||
RESET,
|
|
||||||
message_prefix,
|
|
||||||
self.mask_secrets(record.getMessage()),
|
|
||||||
RESET))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mask_secrets(msg):
|
|
||||||
for secret in secrets:
|
|
||||||
msg = msg.replace(secret, "<%s>" % secrets[secret])
|
|
||||||
return msg
|
|
@ -1,46 +0,0 @@
|
|||||||
# Copyright (C) 2018 Open Information Security Foundation
|
|
||||||
#
|
|
||||||
# You can copy, redistribute or modify this Program under the terms of
|
|
||||||
# the GNU General Public License version 2 as published by the Free
|
|
||||||
# Software Foundation.
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# version 2 along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
||||||
# 02110-1301, USA.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from suricata.ctl import filestore, loghandler
|
|
||||||
|
|
||||||
def init_logger():
|
|
||||||
""" Initialize logging, use colour if on a tty. """
|
|
||||||
if os.isatty(sys.stderr.fileno()):
|
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.setLevel(level=logging.INFO)
|
|
||||||
logger.addHandler(loghandler.SuriColourLogHandler())
|
|
||||||
else:
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format="%(asctime)s - <%(levelname)s> - %(message)s")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
init_logger()
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
subparsers = parser.add_subparsers(help='sub-command help')
|
|
||||||
fs_parser = subparsers.add_parser("filestore", help="Filestore related commands")
|
|
||||||
filestore.register_args(parser=fs_parser)
|
|
||||||
args = parser.parse_args()
|
|
||||||
try:
|
|
||||||
func = args.func
|
|
||||||
except AttributeError:
|
|
||||||
parser.error("too few arguments")
|
|
||||||
func(args)
|
|
@ -1,18 +0,0 @@
|
|||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from suricata.ctl import filestore
|
|
||||||
|
|
||||||
class PruneTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_parse_age(self):
|
|
||||||
self.assertEqual(filestore.parse_age("1s"), 1)
|
|
||||||
self.assertEqual(filestore.parse_age("1m"), 60)
|
|
||||||
self.assertEqual(filestore.parse_age("1h"), 3600)
|
|
||||||
self.assertEqual(filestore.parse_age("1d"), 86400)
|
|
||||||
|
|
||||||
with self.assertRaises(filestore.InvalidAgeFormatError):
|
|
||||||
filestore.parse_age("1")
|
|
||||||
with self.assertRaises(filestore.InvalidAgeFormatError):
|
|
||||||
filestore.parse_age("1y")
|
|
@ -1 +0,0 @@
|
|||||||
from suricata.sc.suricatasc import *
|
|
@ -1,228 +0,0 @@
|
|||||||
argsd = {
|
|
||||||
"pcap-file": [
|
|
||||||
{
|
|
||||||
"name": "filename",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "output-dir",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tenant",
|
|
||||||
"type": int,
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "continuous",
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete-when-done",
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"pcap-file-continuous": [
|
|
||||||
{
|
|
||||||
"name": "filename",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "output-dir",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "continuous",
|
|
||||||
"val": True,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tenant",
|
|
||||||
"type": int,
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "delete-when-done",
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"iface-stat": [
|
|
||||||
{
|
|
||||||
"name": "iface",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"conf-get": [
|
|
||||||
{
|
|
||||||
"name": "variable",
|
|
||||||
"required": 1,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"unregister-tenant-handler": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "htype",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hargs",
|
|
||||||
"type": int,
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"register-tenant-handler": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "htype",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hargs",
|
|
||||||
"type": int,
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"unregister-tenant": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"register-tenant": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "filename",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"reload-tenant": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "filename",
|
|
||||||
"required": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"add-hostbit": [
|
|
||||||
{
|
|
||||||
"name": "ipaddress",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hostbit",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "expire",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"remove-hostbit": [
|
|
||||||
{
|
|
||||||
"name": "ipaddress",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hostbit",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"list-hostbit": [
|
|
||||||
{
|
|
||||||
"name": "ipaddress",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"memcap-set": [
|
|
||||||
{
|
|
||||||
"name": "config",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "memcap",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"memcap-show": [
|
|
||||||
{
|
|
||||||
"name": "config",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"dataset-add": [
|
|
||||||
{
|
|
||||||
"name": "setname",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settype",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "datavalue",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"dataset-remove": [
|
|
||||||
{
|
|
||||||
"name": "setname",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settype",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "datavalue",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"get-flow-stats-by-id": [
|
|
||||||
{
|
|
||||||
"name": "flow_id",
|
|
||||||
"type": int,
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"dataset-clear": [
|
|
||||||
{
|
|
||||||
"name": "setname",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settype",
|
|
||||||
"required": 1,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dataset-lookup": [
|
|
||||||
{
|
|
||||||
"name": "setname",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "settype",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "datavalue",
|
|
||||||
"required": 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
@ -1,293 +0,0 @@
|
|||||||
# 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)
|
|
@ -1 +0,0 @@
|
|||||||
from suricata.sc import *
|
|
Loading…
Reference in New Issue