You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
11 KiB

# Version 4.0
import os
import re
import sys
import subprocess
from subprocess import PIPE, STDOUT
from builtins import range
from future.moves.urllib.parse import quote as urllib_quote
import splunk.clilib.cli_common as comm
import splunk.Intersplunk
import splunk.mining.dcutils as dcu
logger = dcu.getLogger()
mswindows = (sys.platform == "win32")
results,dummyresults,settings = splunk.Intersplunk.getOrganizedResults()
# These values will be sent to the shell script:
# $0 = scriptname
# $1 = number of events returned
# $2 = search terms
# $3 = fully qualified query string
# $4 = name of saved splunk
# $5 = trigger reason (i.e. "The number of events was greater than 1")
# $6 = link to saved search
# $7 = DEPRECATED - empty string argument
# $8 = file where the results for this search are stored(contains raw results)
# 5 corresponds to the 6th arg passed to this python script which includes the
# name of this script and the path where the user's script is located
# The script will also receive args via stdin - currently only the session
# key which it can use to communicate back to splunkd is sent via stdin.
# The format for stdin args is as follows:
# <url-encoded-name>=<url-encoded-value>\n
# e.g.
# sessionKey=0729f8e0d4edf7ae18327da6a9976596
# otherArg=123456
# <eof>
if len(sys.argv) < 10:
splunk.Intersplunk.generateErrorResults("Missing arguments to operator 'runshellscript', expected at least 10, got %i." % len(sys.argv))
exit(1)
script = sys.argv[1]
if not script:
splunk.Intersplunk.generateErrorResults("Empty string is not a valid script name")
exit(2)
# Remove possible enclosing single quotes
if script[0] == "'" and script[-1] == "'":
script = script[1:-1]
sharedStorage = settings.get('sharedStorage', splunk.Intersplunk.splunkHome())
etcSubdir = None
if 'sharedStorage' in settings:
etcSubdir = os.path.join(sharedStorage, 'etc')
else:
etcSubdir = os.environ['SPLUNK_ETC']
if len(sys.argv) > 10:
path = sys.argv[10] # the tenth arg is going to be the file
else:
baseStorage = os.path.join(sharedStorage, 'var', 'run', 'splunk')
path = os.path.join(baseStorage, 'dispatch', sys.argv[9], 'results.csv.gz')
# ensure nothing dangerous
# keep this rule in agreement with etc/system/default/restmap.conf for sane UI
# experience in manager when editing alerts (SPL-49225)
disallowed = ('..', '/', '\\', ':')
if any(i in script for i in disallowed):
display_string = ', '.join(['"{}"'.format(i) for i in disallowed[:-1]])
msg = 'Script location cannot contain {}, or "{}"'.format(display_string, disallowed[-1])
results = splunk.Intersplunk.generateErrorResults(msg)
else:
# look for scripts first in the app's bin/scripts/ dir, if that fails try SPLUNK_HOME/bin/scripts
namespace = settings.get("namespace", None)
sessionKey = settings.get("sessionKey", None)
scriptName = script
if namespace:
script = os.path.join(etcSubdir,comm.getAppDir(),namespace,"bin","scripts",scriptName)
if not os.path.exists(script):
script = os.path.join(etcSubdir,"apps",namespace,"bin","scripts",scriptName)
# if we fail to find script in SPLUNK_HOME/etc/apps/<app>/bin/scripts - look in SPLUNK_HOME/bin/scripts
if not namespace or not os.path.exists(script):
script = os.path.join(splunk.Intersplunk.splunkHome(),"bin","scripts",scriptName)
if not os.path.exists(script):
results = splunk.Intersplunk.generateErrorResults('Cannot find script at ' + script)
else:
stdin_data = ''
cmd_args = sys.argv[1:] # drop 'runshellscript'
# make sure cmd_args has length of 9
cmd_args = cmd_args[:9]
for i in range(9-len(cmd_args)):
cmd_args.append("")
cmd_args[0] = script
cmd_args[8] = path
stdin_data = "sessionKey=" + urllib_quote(sessionKey) + "\n"
# strip any single/double quoting
for i in range(len(cmd_args)):
if len(cmd_args[i]) > 2 and ((cmd_args[i][0] == '"' and cmd_args[i][-1] == '"') or (cmd_args[i][0] == "'" and cmd_args[i][-1] == "'")):
cmd_args[i] = cmd_args[i][1:-1]
# python's call(..., shell=True,...) - is broken so we emulate it ourselves
shell_cmd = ["/bin/sh"]
cmd_args_ms = []
# by default using cmd.exe shell
use_cmd_exe = True
if mswindows:
# escape all special characters of cmd.exe
cmd_args_ms = []
# do not escape the first argument, since this is a script name which was validated above for existence
# Or the last, else we'll have too much escaping in the path of the results to do even simple things like DIR
cmd_args_ms.append(cmd_args[0])
for i in range(1, len(cmd_args) - 1):
# 1. cmd.exe uses the following characters for escaping other characters: \,%,^. Escape them with themselves, e.g. %->%%, ^->^^, \->\\
temp = re.sub(r'([\\%^])', r'\1\1', cmd_args[i])
# 2. cmd.exe will split commandlines on these if thier encountered, drop them
temp = re.sub('(\\n|\\r|\\t)',' ', temp)
# 3. Replace double quotes with single quotes (Nested quotes don't seem to work on cmd.exe, no matter how much escaping you do)
temp = re.sub('(\")', '\'', temp)
# 4. cmd.exe escapes set &><|'`,;=()[]+~ using ^ and escapes ! with ^^.
temp = re.sub(r"([&><|'`,;=()\[\]+~ ])",r'^\1', temp) # &><|'`,;=()[]+~
temp = re.sub('!','^^!', temp) # !
cmd_args_ms.append(temp)
cmd_args_ms.append(cmd_args[8])
logger.info(str(cmd_args_ms))
cmd_args = cmd_args_ms
else:
logger.info(str(cmd_args))
# try to read the interpreter from the first line of the file
try:
f = open(script)
line = f.readline().rstrip("\r\n")
f.close()
if line.startswith("#!"):
use_cmd_exe = False
# Emulate UNIX rules for "#!" lines:
# 1. Any whitespace (just space and tab, actually) after
# the "!" is ignored. Also whitespace at the end of
# the line is dropped
# 2. Anything up to the next whitespace is the interpreter
# 3. If there is anything after this whitespace it's
# considered to be the argument to pass to the interpreter.
# Note that this parsing is very simple -- no quoting
# is interpreted and only one argument is parsed. This
# is to match
line = line[2:].strip(" \t")
if line:
arg_loc = line.replace("\t", " ").find(" ")
if arg_loc == -1:
shell_cmd = [ line ]
else:
# The second half here is incorrect; and will break if
# the #! includes more than one arg, on unix,
# line.split() would be correct
shell_cmd = [ line[0:arg_loc], line[arg_loc + 1:].lstrip(" \t") ]
elif script.endswith(".py"):
# Try to support python scripts in a portable way.
# Note, if the user specified a #!, then that takes precedence
# (eg, run with system python)
use_cmd_exe = False # python won't understand the escaped stuff
if mswindows:
python_exe = "python.exe"
else:
python_exe = "python"
python_path = os.path.join(sharedStorage, "bin", python_exe)
shell_cmd = [ python_path ]
except Exception as e:
pass
# pass args as env variables too - this is to ensure that args are properly passed in windows
for i in range(len(cmd_args)):
os.environ['SPLUNK_ARG_' + str(i)] = cmd_args[i]
try:
p = None
if mswindows and use_cmd_exe:
# SPL-142755 [Triggering external Windows script failing with
# [Error 193] %1 is not a valid Win32 application]
# Quote first argument if it contains a space.
if cmd_args[0].find(' ') != -1:
cmd_args[0] = '"' + cmd_args[0] + '"'
#
# Windows cmd.exe only allows 8191 characters on the command line.
# Including possible additional quotes and spaces, this comes down to 8155
# Trim down the end of the positional arguments until it's below this.
if sum(len(arg) for arg in cmd_args) > 8155:
results = splunk.Intersplunk.generateErrorResults("Positional arguments for script : " + cmd_args[0] + " are too long, truncating")
# Start with the likely long argument (search string)
cmd2run_ptr = 2
while (sum(len(arg) for arg in cmd_args) > 8155) and cmd2run_ptr < 9:
cmd_args[cmd2run_ptr] = "ARGUMENT_TOO_LONG"
cmd2run_ptr += 1
#
# Need to give Popen a string otherwise it will do its own escaping too
cmd_args_ms_str = ""
for i in range(len(cmd_args)):
if cmd_args[i].find("^") != -1 or cmd_args[i] == '':
cmd_args_ms_str += " \"" + cmd_args[i] + "\" "
else:
cmd_args_ms_str += cmd_args[i] + " "
p = subprocess.Popen(cmd_args_ms_str, stdin=PIPE, stdout=PIPE, stderr=STDOUT, shell=False)
else:
logger.info("runshellscript: " + str(shell_cmd + cmd_args))
if mswindows: # windows doesn't support close_fds param
p = subprocess.Popen(shell_cmd + cmd_args, stdin=PIPE, stdout=PIPE, stderr=STDOUT, shell=False)
else:
p = subprocess.Popen(shell_cmd + cmd_args, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True, shell=False)
if p:
if sys.version_info >= (3, 0):
stdin_data = stdin_data.encode()
p.communicate(input=stdin_data)
code = p.returncode
if code!=0:
results = splunk.Intersplunk.generateErrorResults("Script: " + cmd_args[0] + " exited with status code: " + str(code))
except OSError as e:
results = splunk.Intersplunk.generateErrorResults('Error while executing script ' + str(e))
splunk.Intersplunk.outputResults( results )

Powered by BW's shoe-string budget.