module Shells::BashCommon

Provides some common functionality for bash-like shells.

Public Instance Methods

read_file(path, use_method = nil) click to toggle source

Reads from a file on the device.

# File lib/shells/bash_common.rb, line 10
def read_file(path, use_method = nil)
  if use_method
    use_method = use_method.to_sym
    raise ArgumentError, "use_method (#{use_method.inspect}) is not a valid method." unless file_methods.include?(use_method)
    raise Shells::ShellError, "The #{use_method} binary is not available with this shell." unless which(use_method)
    send "read_file_#{use_method}", path
  elsif default_file_method
    return send "read_file_#{default_file_method}", path
  else
    raise Shells::ShellError, 'No supported binary to encode/decode files.'
  end
end
sudo_exec(command, options = {}, &block) click to toggle source

Executes an elevated command using the 'sudo' command.

# File lib/shells/bash_common.rb, line 40
def sudo_exec(command, options = {}, &block)
  sudo_prompt = '[sp:'
  sudo_match = /\n\[sp:$/m
  sudo_strip = /\[sp:[^\n]*\n/m
  ret = exec("sudo -p \"#{sudo_prompt}\" bash -c \"#{command.gsub('"', '\\"')}\"", options) do |data,type|
    test_data = data.to_s
    desired_length = sudo_prompt.length + 1 # prefix a NL before the prompt.

    # pull from the current stdout to get the full test data, but only if we received some new data.
    if test_data.length > 0 && test_data.length < desired_length
      test_data = stdout[-desired_length..-1].to_s
    end

    if test_data =~ sudo_match
      self.options[:password]
    else
      if block
        block.call(data, type)
      else
        nil
      end
    end
  end
  # remove the sudo prompts.
  ret.gsub(sudo_strip, '')
end
sudo_exec_for_code(command, options = {}, &block) click to toggle source

Executes a command specifically for the exit code.

Does not return the output of the command, only the exit code.

# File lib/shells/bash_common.rb, line 71
def sudo_exec_for_code(command, options = {}, &block)
  options = (options || {}).merge(retrieve_exit_code: true, on_non_zero_exit_code: :ignore)
  sudo_exec command, options, &block
  last_exit_code
end
sudo_exec_ignore_code(command, options = {}, &block) click to toggle source

Executes a command ignoring any exit code.

Returns the output of the command and does not even retrieve the exit code.

# File lib/shells/bash_common.rb, line 81
def sudo_exec_ignore_code(command, options = {}, &block)
  options = (options || {}).merge(retrieve_exit_code: false, on_non_zero_exit_code: :ignore)
  sudo_exec command, options, &block
end
write_file(path, data, use_method = nil) click to toggle source

Writes to a file on the device.

# File lib/shells/bash_common.rb, line 25
def write_file(path, data, use_method = nil)
  if use_method
    use_method = use_method.to_sym
    raise ArgumentError, "use_method (#{use_method.inspect}) is not a valid method." unless file_methods.include?(use_method)
    raise Shells::ShellError, "The #{use_method} binary is not available with this shell." unless which(use_method)
    send "write_file_#{use_method}", path, data
  elsif default_file_method
    return send "write_file_#{default_file_method}", path, data
  else
    raise Shells::ShellError, 'No supported binary to encode/decode files.'
  end
end

Protected Instance Methods

which(program) click to toggle source

Gets the path to a program, or nil if not found.

# File lib/shells/bash_common.rb, line 120
def which(program)
  ret = exec("which #{program} 2>/dev/null").strip
  ret == '' ? nil : ret
end

Private Instance Methods

default_file_method() click to toggle source
# File lib/shells/bash_common.rb, line 135
def default_file_method
  # Find the first method that should work.
  unless instance_variable_defined?(:@default_file_method)
    @default_file_method = file_methods.find { |meth| which(meth) }
  end
  @default_file_method
end
file_methods() click to toggle source
# File lib/shells/bash_common.rb, line 127
def file_methods
  @file_methods ||= [
      :base64,
      :openssl,
      :perl
  ]
end
read_file_base64(path) click to toggle source
# File lib/shells/bash_common.rb, line 209
def read_file_base64(path)
  data = exec "base64 -w 0 #{path.inspect}", retrieve_exit_code: true, on_non_zero_exit_code: :ignore, command_timeout: 30
  return nil if last_exit_code != 0
  Base64.decode64 data
end
read_file_openssl(path) click to toggle source
# File lib/shells/bash_common.rb, line 215
def read_file_openssl(path)
  data = exec "openssl base64 < #{path.inspect}", retrieve_exit_code: true, on_non_zero_exit_code: :ignore, command_timeout: 30
  return nil if last_exit_code != 0
  Base64.decode64 data
end
read_file_perl(path) click to toggle source
# File lib/shells/bash_common.rb, line 221
def read_file_perl(path)
  data = exec "perl -MMIME::Base64 -ne 'print encode_base64($_)' < #{path.inspect}", retrieve_exit_code: true, on_non_zero_exit_code: :ignore, command_timeout: 30
  return nil if last_exit_code != 0
  Base64.decode64 data
end
with_b64_file(path, data, &block) click to toggle source
# File lib/shells/bash_common.rb, line 143
def with_b64_file(path, data, &block)
  data = Base64.encode64(data)

  max_cmd_length = 2048

  # Send 1 line at a time (this will be SLOW for large files).
  lines = data.gsub("\r\n", "\n").split("\n")

  # Construct a temporary filename.
  b64path = path + '.b64'
  if exec_for_code("[ -f #{b64path.inspect} ]") == 0
    # File exists.
    cnt = 2
    while exec_for_code("[ -f #{(b64path + cnt.to_s).inspect} ]") == 0
      cnt += 1
    end
    b64path += cnt.to_s
  end

  debug "Writing #{lines.count} lines to #{b64path}..."

  # Create/overwrite file with the first line.
  first_line = lines.delete_at 0
  exec "echo #{first_line} > #{b64path.inspect}"

  # Create a queue.
  cmds = []
  lines.each do |line|
    cmds << "echo #{line} >> #{b64path.inspect}"
  end

  # Process the queue sending as many at a time as possible.
  while cmds.any?
    cmd = cmds.delete(cmds.first)
    while cmds.any? && cmd.length + cmds.first.length + 4 <= max_cmd_length
      cmd += ' && ' + cmds.delete(cmds.first)
    end
    exec cmd
  end


  ret = block.call(b64path)

  exec "rm #{b64path.inspect}"

  ret
end
write_file_base64(path, data) click to toggle source
# File lib/shells/bash_common.rb, line 191
def write_file_base64(path, data)
  with_b64_file path, data do |b64path|
    exec_for_code "base64 -d #{b64path.inspect} > #{path.inspect}", command_timeout: 30
  end
end
write_file_openssl(path, data) click to toggle source
# File lib/shells/bash_common.rb, line 197
def write_file_openssl(path, data)
  with_b64_file path, data do |b64path|
    exec_for_code "openssl base64 -d < #{b64path.inspect} > #{path.inspect}"
  end
end
write_file_perl(path, data) click to toggle source
# File lib/shells/bash_common.rb, line 203
def write_file_perl(path, data)
  with_b64_file path, data do |b64path|
    exec_for_code "perl -MMIME::Base64 -ne 'print decode_base64($_)' < #{b64path.inspect} > #{path.inspect}"
  end
end