##
# driver_command_commands.rb
# Created September 13, 2015
# By Ron Bowes
#
# See: LICENSE.md
##

require 'libs/command_helpers'

module DriverCommandCommands
  def _register_commands()
    @commander.register_alias('sessions', 'windows')
    @commander.register_alias('session',  'window')
    @commander.register_alias('q',        'quit')
    @commander.register_alias('exit',     'quit')
    @commander.register_alias('h',        'help')
    @commander.register_alias('?',        'help')
    @commander.register_alias('up',       'suspend')
    @commander.register_alias('back',     'suspend')
    @commander.register_alias('pause',    'suspend')
    @commander.register_alias('run',      'exec')
    @commander.register_alias('execute',  'exec')

    @commander.register_command('help',
      Trollop::Parser.new do
        banner("Shows a help menu")
      end,

      Proc.new do |opts, optval|
        @commander.help(@window)
      end,
    )

    @commander.register_command('echo',
      Trollop::Parser.new do
        banner("Print stuff to the terminal, including $variables")
      end,

      Proc.new do |opts, optarg|
        @window.puts(optarg)
      end
    )

    @commander.register_command("ping",
      Trollop::Parser.new do
        banner("Sends a 'ping' to the remote host to make sure it's still alive)")
        opt :length, "length", :type => :integer, :required => false, :default => 256
      end,
      Proc.new do |opts|
        ping = CommandPacket.new({
          :is_request => true,
          :request_id => request_id(),
          :command_id => CommandPacket::COMMAND_PING,
          :data       => (0...opts[:length]).map { ('A'.ord + rand(26)).chr }.join()
        })

        _send_request(ping, Proc.new() do |request, response|
          if(request.get(:data) != response.get(:data))
            @window.puts("The server didn't return the same ping data we sent!")
            @window.puts("Expected: #{request.get(:data)}")
            @window.puts("Received: #{response.get(:data)}")
          else
            @window.puts("Pong!")
          end
        end)

        @window.puts("Ping!")
      end,
    )

    @commander.register_command("clear",
      Trollop::Parser.new do
        banner("Clears the display")
      end,
      Proc.new do |opts|
        0.upto(100) do @window.puts() end
      end,
    )


    @commander.register_command("shell",
      Trollop::Parser.new do
        banner("Spawn a shell on the remote host")
        opt :name, "Name", :type => :string, :required => false, :default => nil
      end,

      Proc.new do |opts|
        shell = CommandPacket.new({
          :is_request => true,
          :request_id => request_id(),
          :command_id => CommandPacket::COMMAND_SHELL,
          :name       => opts[:name] || "shell"
        })

        _send_request(shell, Proc.new() do |request, response|
          @window.puts("Shell session created!")
        end)

        @window.puts("Sent request to execute a shell")
      end,
    )

    @commander.register_command("exec",
      Trollop::Parser.new do
        banner("Execute a program on the remote host")
        opt :command, "Command", :type => :string, :required => false, :default => nil
        opt :name,    "Name",    :type => :string, :required => false, :default => nil
      end,

      Proc.new do |opts, optarg|
        command = opts[:command] || optarg
        name    = opts[:name]    || command

        if(name == "")
          @window.puts("No command given!")
          @window.puts()
          raise(Trollop::HelpNeeded)
        end

        @window.puts("command = #{command} #{command.class}")
        exec = CommandPacket.new({
          :is_request => true,
          :request_id => request_id(),
          :command_id => CommandPacket::COMMAND_EXEC,
          :command => command,
          :name => name,
        })

        _send_request(exec, Proc.new() do |request, response|
          @window.puts("Executed \"#{request.get(:command)}\"")
        end)

        @window.puts("Sent request to execute \"#{command}\"")
      end,
    )

    @commander.register_command("suspend",
      Trollop::Parser.new do
        banner("Go back to the parent session")
      end,

      Proc.new do |opts, optarg|
        @window.deactivate()
      end,
    )

    @commander.register_command("download",
      Trollop::Parser.new do
        banner("Download a file from the other side. Usage: download <from> [to]")
      end,

      Proc.new do |opts, optarg|
        # Get the two files
        remote_file, local_file = Shellwords.shellwords(optarg)

        # Sanity check
        if(remote_file.nil? || remote_file == "")
          @window.puts("Usage: download <from> [to]")
        else
          # Make sure we have a local file
          if(local_file.nil? || local_file == "")
            # I only want the filename to prevent accidental traversal
            local_file = File.basename(remote_file)
          end

          download = CommandPacket.new({
            :is_request => true,
            :request_id => request_id(),
            :command_id => CommandPacket::COMMAND_DOWNLOAD,
            :filename => remote_file,
          })

          _send_request(download, Proc.new() do |request, response|
            File.open(local_file, "wb") do |f|
              f.write(response.get(:data))
              @window.puts("Wrote #{response.get(:data).length} bytes from #{request.get(:filename)} to #{local_file}!")
            end
          end)

          @window.puts("Attempting to download #{remote_file} to #{local_file}")
        end
      end
    )

    @commander.register_command("upload",
      Trollop::Parser.new do
        banner("Upload a file to the other side. Usage: upload <from> <to>")
      end,

      Proc.new do |opts, optarg|
        # Get the two files
        local_file, remote_file = Shellwords.shellwords(optarg)

        # Sanity check
        if(local_file.nil? || local_file == "" || remote_file.nil? || remote_file == "")
          @window.puts("Usage: upload <from> <to>")
        else
          data = IO.read(local_file)

          upload = CommandPacket.new({
            :is_request => true,
            :request_id => request_id(),
            :command_id => CommandPacket::COMMAND_UPLOAD,
            :filename => remote_file,
            :data => data,
          })

          _send_request(upload, Proc.new() do |request, response|
            @window.puts("#{data.length} bytes uploaded from #{local_file} to #{remote_file}")
          end)

          @window.puts("Attempting to upload #{local_file} to #{remote_file}")
        end
      end
    )

    @commander.register_command("shutdown",
      Trollop::Parser.new do
        banner("Shut down the remote session")
      end,

      Proc.new do |opts, optarg|
        shutdown = CommandPacket.new({
          :is_request => true,
          :request_id => request_id(),
          :command_id => CommandPacket::COMMAND_SHUTDOWN,
        })

        _send_request(shutdown, Proc.new() do |request, response|
          @window.puts("Shutdown response received")
        end)
        @window.puts("Attempting to shut down remote session(s)...")

        # TODO: Doing cleanup here is a bit of a hack; unfortunately, the
        # client doesn't tell us when it successfully shuts down, it just dies
        # (it's non-trivial delaying the death, as well...), so we free up our
        # resources here. :)
        request_stop()
      end
    )

    @commander.register_command("delay",
      Trollop::Parser.new do
        banner("Change the delay of the remote session")
      end,

      Proc.new do |opts, optarg|
	if optarg.nil? || optarg.to_i < 1
          @window.puts("Usage: delay <seconds>\n\nYou can only use values greater than one second.")
        else
          delay = CommandPacket.new({
            :is_request => true,
            :request_id => request_id(),
            :command_id => CommandPacket::COMMAND_DELAY,
            :delay => optarg.to_i * 1000,
          })

          _send_request(delay, Proc.new() do |request, response|
            @window.puts("Delay response received")
          end)
          @window.puts("Attempting to change delay to #{optarg.to_i}s...")
	end
      end
    )

    # This is almost the same as 'set' from 'controller', except it uses the
    # local settings and recurses into global if necessary
    @commander.register_command("set",
      Trollop::Parser.new do
        banner("set <name>=<value>")
      end,

      Proc.new do |opts, optarg|
        if(optarg.length == 0)
          @window.puts("Usage: set <name>=<value>")
          @window.puts()
          @window.puts("** Global options:")
          @window.puts()
          Settings::GLOBAL.each_setting() do |name, value, docs, default|
            @window.puts("%s => %s [default = %s]" % [name, CommandHelpers.format_field(value), CommandHelpers.format_field(default)])
            @window.puts(CommandHelpers.wrap(docs, 72, 4))
            @window.puts()
          end

          @window.puts()
          @window.puts("** Session options:")
          @window.puts()
          @settings.each_setting() do |name, value, docs, default|
            @window.puts("%s => %s [default = %s]" % [name, CommandHelpers.format_field(value), CommandHelpers.format_field(default)])
            @window.puts(CommandHelpers.wrap(docs, 72, 4))
            @window.puts()
          end

          next
        end

        # Split at the '=' sign
        namevalue = optarg.split("=", 2)

        if(namevalue.length != 2)
          namevalue = optarg.split(" ", 2)
        end

        if(namevalue.length != 2)
          @window.puts("Bad argument! Expected: 'set <name>=<value>' or 'set name value'!")
          @window.puts()
          raise(Trollop::HelpNeeded)
        end

        begin
          @settings.set(namevalue[0], namevalue[1], true)
        rescue Settings::ValidationError => e
          @window.puts("Failed to set the new value: #{e}")
        end
      end
    )

    @commander.register_command("unset",
      Trollop::Parser.new do
        banner("unset <name>")
      end,

      Proc.new do |opts, optarg|
        @settings.unset(optarg)
      end
    )

    @commander.register_command('windows',
      Trollop::Parser.new do
        banner("Lists the current active windows under the current window")
        opt :all, "Show closed windows", :type => :boolean, :required => false
      end,

      Proc.new do |opts, optarg|
        @window.puts()
        @window.puts("Windows active in this session (to see all windows, go to")
        @window.puts("the main window by pressing ctrl-z):")
        @window.puts()
        CommandHelpers.display_windows(@window, opts[:all], @window)
      end,
    )

    @commander.register_command("window",
      Trollop::Parser.new do
        banner("Interact with a window")
        opt :i, "Interact with the chosen window", :type => :string, :required => false
      end,

      Proc.new do |opts, optarg|
        if(opts[:i].nil?)
          @window.puts()
          @window.puts("Windows active in this session (to see all windows, go to")
          @window.puts("the main window by pressing ctrl-z):")
          @window.puts()
          CommandHelpers.display_windows(@window, opts[:all], @window)
          next
        end

        window = SWindow.get(opts[:i])
        if(window.nil?)
          @window.puts("Window #{opts[:i]} not found!")
          @window.puts()
          @window.puts("Windows active in this session (to see all windows, go to")
          @window.puts("the main window by pressing ctrl-z):")
          @window.puts()
          CommandHelpers.display_windows(@window, false, @window)
          next
        end

        window.activate()
      end
    )

    @commander.register_command("quit",
      Trollop::Parser.new do
        banner("Close all sessions and exit dnscat2")
      end,

      Proc.new do |opts, optarg|
        exit(0)
      end
    )
  end
end
