ronin-rb/ronin-post_ex

View on GitHub
lib/ronin/post_ex/remote_process.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true
#
# ronin-post_ex - a Ruby API for Post-Exploitation.
#
# Copyright (c) 2007-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-post_ex is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-post_ex 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-post_ex.  If not, see <https://www.gnu.org/licenses/>.
#

require_relative 'resource'

require 'fake_io'

module Ronin
  module PostEx
    #
    # The {RemoteProcess} class represents a command being executed on a remote
    # system. The {RemoteProcess} class wraps around the `process_popen` and
    # `process_read`, `process_write`, and `process_close` methods defined in
    # the API object.
    #
    class RemoteProcess < Resource

      include FakeIO
      include Enumerable

      # The command string.
      #
      # @return [String]
      attr_reader :command

      #
      # Creates a new remote process.
      #
      # @param [Sessions::Session] session
      #   The object controlling command execution.
      #
      # @param [String] command
      #   The command to run.
      #
      # @raise [NotImplementedError]
      #   The session object does not define `process_popen`.
      #
      def initialize(session,command)
        unless session.respond_to?(:process_popen)
          raise(NotImplementedError,"#{session.inspect} must define #process_popen for #{self.class}")
        end

        @session = session
        @command = command

        super()
      end

      #
      # Reopens the command.
      #
      # @param [String] command
      #   The new command to run.
      #
      # @return [RemoteProcess]
      #   The new command.
      #
      def reopen(command)
        close

        @command = command

        return open
      end
      resource_method :reopen, [:process_popen]

      #
      # Converts the command to a `String`.
      #
      # @return [String]
      #   The process'es command.
      #
      def to_s
        @command
      end

      #
      # Inspects the command.
      #
      # @return [String]
      #   The inspected command listing the program name and arguments.
      #
      def inspect
        "#<#{self.class}: #{self}>"
      end

      private

      #
      # Executes and opens the command for reading.
      #
      # @return [Enumerator]
      #   The enumerator that wraps around `process_popen`.
      #
      def io_open
        @session.enum_for(:process_popen,@command)
      end
      resource_method :open, [:process_popen]

      # Default block size to read process output with.
      BLOCK_SIZE = 4096

      #
      # Reads a line of output from the command.
      #
      # @return [String]
      #   A line of output.
      #
      # @raise [EOFError]
      #   The end of the output stream has been reached.
      #
      def io_read
        if @session.respond_to?(:process_read)
          @session.process_write(@fd,BLOCK_SIZE)
        end
      end
      resource_method :read

      #
      # Writes data to the shell.
      #
      # @param [String] data
      #   The data to write to the shell.
      #
      # @return [Integer]
      #   The number of bytes written.
      #
      def io_write(data)
        if @session.respond_to?(:process_write)
          @session.process_write(@fd,data)
        else
          raise(IOError,"#{@session.inspect} does not support writing to the shell")
        end
      end
      resource_method :write, [:process_write]

      #
      # Attempts calling `process_close` from the API object to close
      # the file.
      #
      # @note
      #   This method may use the `process_close` method, if {#session} defines it.
      #
      def io_close
        if @session.respond_to?(:process_close)
          @session.process_close(@fd)
        end
      end
      resource_method :close

    end
  end
end