class SHExecutor::Executor

Attributes

data_err[RW]
data_out[RW]
options[RW]
pid[RW]
result[RW]
stderr[RW]
stdout[RW]

Public Class Methods

new(options = ::SHExecutor::default_options) click to toggle source
# File lib/shexecutor.rb, line 56
def initialize(options = ::SHExecutor::default_options)
  # set default options
  @options = ::SHExecutor::default_options.dup

  # then apply specified options
  options.each do |key, value|
    @options[key] = value
  end

  @options
end

Public Instance Methods

execute() click to toggle source
# File lib/shexecutor.rb, line 97
def execute
  @pid = nil
  @stdout = nil
  @stderr = nil
  if (@options[:replace] == true)
    replace_process
  else
    if (@options[:wait_for_completion])
      if (@options[:timeout] <= 0)
        block_process
      else
        block_process_with_timeout
      end
    else
      fork_process
    end
  end
end
flush() click to toggle source
# File lib/shexecutor.rb, line 116
def flush
  return nil if @data_out.nil? or @data_err.nil?
  stdout_data = @data_out.string
  stderr_data = @data_err.string
  @stdout = stdout_data if stdout_data != ""
  @stderr = stderr_data if stderr_data != ""
  stdout_to_file if (@options[:stdout_path])
  stderr_to_file if (@options[:stderr_path])
end
status() click to toggle source
# File lib/shexecutor.rb, line 68
def status
  return "not executed" if @result.nil?
  return @result.status if @result.alive?
  return "no longer executing" if not @result.alive?
end
validate() click to toggle source
# File lib/shexecutor.rb, line 79
def validate
  errors = []
  if (@options[:protect_against_injection]) and (!@options[:application_path].nil? and @options[:application_path].strip != "")
    if (File.exists?(@options[:application_path]))
      errors << "Application path not executable" if !File.executable?(@options[:application_path])
    else
      errors << "Application path not found"
    end

   errors << "Suspected injection vulnerability due to space in application_path or the object being marked as 'tainted' by Ruby. Turn off strict checking if you are sure by setting :protect_against_injection to false" if possible_injection?(@options[:application_path])

  else
    errors << "No application path provided" if (@options[:application_path].nil?) or (@options[:application_path].strip == "")
  end

  raise ArgumentError.new(errors.join(',')) if errors.count > 0
end

Private Instance Methods

block_process() click to toggle source
# File lib/shexecutor.rb, line 220
def block_process
  validate
  @data_out, @data_err, @result = run_process(@options[:application_path], *@options[:params])
  @result.join
  @result.value
end
block_process_with_timeout() click to toggle source
# File lib/shexecutor.rb, line 227
def block_process_with_timeout
  validate
  begin
    @data_out, @data_err, @result = run_process(@options[:application_path], *@options[:params])
    raise Timeout::Error.new("execution expired") if @timeout_error
    @result.join
    @result.value
  rescue Timeout::Error => ex
    kill_process(@t0.pid)
    raise ex
  end
end
buffer_to_file(buffer, path, append) click to toggle source
# File lib/shexecutor.rb, line 132
def buffer_to_file(buffer, path, append)
  if not append
    FileUtils.rm_f(path)
  end
  File.write(path, buffer, buffer.size, mode: 'a')
end
fork_process() click to toggle source
# File lib/shexecutor.rb, line 261
def fork_process
  validate
  @stdin_stream, @stdout_stream, @stderr_stream, @result = Open3::popen3(@options[:application_path], *@options[:params]) 
  return @result, @stdout_stream, @stderr_stream
end
kill_process(pid) click to toggle source
# File lib/shexecutor.rb, line 249
def kill_process(pid)
  return if pid.nil?
  Process.kill("TERM", pid)
  count = 0
  while (count < (@options[:timeout_sig_kill_retry]/100) and process?(pid)) do
    sleep 0.1
  end
  Process.kill(9, pid) if process?(pid)
rescue Errno::ESRCH
  #done
end
possible_injection?(application_path) click to toggle source
# File lib/shexecutor.rb, line 128
def possible_injection?(application_path)
  (@options[:protect_against_injection]) and (@options[:application_path].include?(" ") or @options[:application_path].tainted?)
end
process?(pid) click to toggle source
# File lib/shexecutor.rb, line 240
def process?(pid)
  begin
    Process.getpgid(pid)
    true
  rescue Errno::ESRCH
    false
  end
end
replace_process() click to toggle source
# File lib/shexecutor.rb, line 147
def replace_process
  validate
  @options[:params].nil? ? exec(@options[:application_path]) : exec(@options[:application_path], *@options[:params]) 
end
run_process(application_path, options = "") click to toggle source
# File lib/shexecutor.rb, line 152
def run_process(application_path, options = "")
  data_out = StringIO.new
  data_err = StringIO.new
  @t0 = nil
  Open3::popen3(application_path, options) do |stdin, stdout, stderr, thr|
    @t0 = thr
    t1 = Thread.new do
      begin
        IO.copy_stream(stdout, data_out)
      rescue Exception => ex
        raise TimeoutError.new("execution expired") if @timeout_error
        raise ex
      end
    end
    t2 = Thread.new do
      begin
        IO.copy_stream(stderr, data_err)
      rescue Exception => ex
        raise TimeoutError.new("execution expired") if @timeout_error
        raise ex
      end
    end
    m = Mutex.new
    done = false
    timedout = false
    t3 = Thread.new do
      count = 0
      while (count < @options[:timeout]*10) and (@t0.alive?) do
        sleep 0.1
        count = count + 1
      end
      m.synchronize do
        if count >= @options[:timeout]*10
          timedout = true
        end
        done = true
      end
    end if should_timeout?
    stdin.close
    t1.abort_on_exception = true if should_timeout?
    t2.abort_on_exception = true if should_timeout?
    t3.abort_on_exception = true if should_timeout?
    t1.join if not should_timeout?
    t2.join if not should_timeout?
    if should_timeout?
      d = false
      m.synchronize do
        d = done
      end
      while not d do
        sleep 0.1
        m.synchronize do
          d = done
        end
      end

      if not timedout
        t1.join 
        t2.join
        t3.join
      else
        @timeout_error = true
      end
    end
  end
  return data_out, data_err, @t0
end
should_timeout?() click to toggle source
# File lib/shexecutor.rb, line 267
def should_timeout?
  @options[:timeout] > 0
end
stderr_to_file() click to toggle source
# File lib/shexecutor.rb, line 143
def stderr_to_file
  buffer_to_file(@data_err.string, @options[:stderr_path], @options[:append_stderr_path]) if @options[:stderr_path]
end
stdout_to_file() click to toggle source
# File lib/shexecutor.rb, line 139
def stdout_to_file
  buffer_to_file(@data_out.string, @options[:stdout_path], @options[:append_stdout_path]) if @options[:stdout_path]
end