You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/python/suricata/sc/suricatasc.py

409 lines
16 KiB
Python

#!/usr/bin/python
# Copyright(C) 2012 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
try:
import simplejson as json
except:
import json
import re
import readline
from socket import socket, AF_UNIX, error
from time import sleep
import select
import sys
SURICATASC_VERSION = "1.0"
VERSION = "0.2"
INC_SIZE = 1024
class SuricataException(Exception):
"""
Generic class for suricatasc exception
"""
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
class SuricataNetException(SuricataException):
"""
Exception raised when network error occur.
"""
pass
class SuricataCommandException(SuricataException):
"""
Exception raised when command is not correct.
"""
pass
class SuricataReturnException(SuricataException):
"""
Exception raised when return message is not correct.
"""
pass
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
return None
class SuricataSC:
def __init__(self, sck_path, verbose=False):
self.cmd_list=['shutdown','quit','pcap-file','pcap-file-continuous','pcap-file-number','pcap-file-list','pcap-last-processed','pcap-interrupt','iface-list','iface-stat','register-tenant','unregister-tenant','register-tenant-handler','unregister-tenant-handler', 'add-hostbit', 'remove-hostbit', 'list-hostbit', 'memcap-set', 'memcap-show']
self.sck_path = sck_path
self.verbose = verbose
def json_recv(self):
cmdret = None
data = ""
while True:
if sys.version < '3':
data += self.socket.recv(INC_SIZE)
else:
data += self.socket.recv(INC_SIZE).decode('iso-8859-1')
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("No such command: %s", command)
cmdmsg = {}
cmdmsg['command'] = command
if (arguments != None):
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 cmdret == None:
raise SuricataReturnException("Unable to get message from server")
if self.verbose:
print("RCV: "+ json.dumps(cmdret))
return cmdret
def connect(self):
try:
self.socket = socket(AF_UNIX)
self.socket.connect(self.sck_path)
except error as err:
raise SuricataNetException(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 cmdret == None:
raise SuricataReturnException("Unable to get message from server")
if self.verbose:
print("RCV: "+ json.dumps(cmdret))
if cmdret["return"] == "NOK":
raise SuricataReturnException("Error: %s" % (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()
def parse_command(self, command):
arguments = None
if command.split(' ', 2)[0] in self.cmd_list:
if "pcap-file " in command:
try:
parts = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
cmd, filename, output = parts[0], parts[1], parts[2]
tenant = None
if len(parts) > 3:
tenant = parts[3]
continuous = None
if len(parts) > 4:
continuous = parts[4]
delete_when_done = None
if len(parts) > 5:
delete_when_done = parts[5]
if cmd != "pcap-file":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["filename"] = filename
arguments["output-dir"] = output
if tenant != None:
arguments["tenant"] = int(tenant)
if continuous != None:
arguments["continuous"] = continuous
if delete_when_done != None:
arguments["delete-when-done"] = delete_when_done
elif "pcap-file-continuous " in command:
try:
parts = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
cmd, filename, output = parts[0], parts[1], parts[2]
tenant = None
if len(parts) > 3:
tenant = parts[3]
delete_when_done = None
if len(parts) > 4:
delete_when_done = parts[4]
if cmd != "pcap-file":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["filename"] = filename
arguments["output-dir"] = output
arguments["continuous"] = True
if tenant != None:
arguments["tenant"] = int(tenant)
if delete_when_done != None:
arguments["delete-when-done"] = delete_when_done
elif "iface-stat" in command:
try:
[cmd, iface] = command.split(' ', 1)
except:
raise SuricataCommandException("Unable to split command '%s'" % (command))
if cmd != "iface-stat":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["iface"] = iface
elif "conf-get" in command:
try:
[cmd, variable] = command.split(' ', 1)
except:
raise SuricataCommandException("Unable to split command '%s'" % (command))
if cmd != "conf-get":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["variable"] = variable
elif "unregister-tenant-handler" in command:
try:
parts = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
cmd, tenantid, htype = parts[0], parts[1], parts[2]
hargs = None
if len(parts) > 3:
hargs = parts[3]
if cmd != "unregister-tenant-handler":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["id"] = int(tenantid)
arguments["htype"] = htype
if hargs != None:
arguments["hargs"] = int(hargs)
elif "register-tenant-handler" in command:
try:
parts = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
cmd, tenantid, htype = parts[0], parts[1], parts[2]
hargs = None
if len(parts) > 3:
hargs = parts[3]
if cmd != "register-tenant-handler":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["id"] = int(tenantid)
arguments["htype"] = htype
if hargs != None:
arguments["hargs"] = int(hargs)
elif "unregister-tenant" in command:
try:
[cmd, tenantid] = command.split(' ', 1)
except:
raise SuricataCommandException("Unable to split command '%s'" % (command))
if cmd != "unregister-tenant":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["id"] = int(tenantid)
elif "register-tenant" in command:
try:
[cmd, tenantid, filename] = command.split(' ', 2)
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "register-tenant":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["id"] = int(tenantid)
arguments["filename"] = filename
elif "reload-tenant" in command:
try:
[cmd, tenantid, filename] = command.split(' ', 2)
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "reload-tenant":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["id"] = int(tenantid)
arguments["filename"] = filename
elif "add-hostbit" in command:
try:
[cmd, ipaddress, hostbit, expire] = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "add-hostbit":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["ipaddress"] = ipaddress
arguments["hostbit"] = hostbit
arguments["expire"] = int(expire)
elif "remove-hostbit" in command:
try:
[cmd, ipaddress, hostbit] = command.split(' ', 2)
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "remove-hostbit":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["ipaddress"] = ipaddress
arguments["hostbit"] = hostbit
elif "list-hostbit" in command:
try:
[cmd, ipaddress] = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "list-hostbit":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["ipaddress"] = ipaddress
elif "memcap-set" in command:
try:
[cmd, config, memcap] = command.split(' ', 2)
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "memcap-set":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["config"] = config
arguments["memcap"] = memcap
elif "memcap-show" in command:
try:
[cmd, config] = command.split(' ')
except:
raise SuricataCommandException("Arguments to command '%s' is missing" % (command))
if cmd != "memcap-show":
raise SuricataCommandException("Invalid command '%s'" % (command))
else:
arguments = {}
arguments["config"] = config
else:
cmd = command
else:
raise SuricataCommandException("Unknown command '%s'" % (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;
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.connect()
except SuricataNetException as err:
print("Can't reconnect to suricata socket, discarding command")
continue
cmdret = self.send_command(cmd, arguments)
#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")