# This plugin provides integration with OpenVAS.
# Written by kost and averagesecurityguy.
#
# Distributed under MIT license:
# http://www.opensource.org/licenses/mit-license.php

require 'openvas-omp'

module Msf
  class Plugin::OpenVAS < Msf::Plugin
    class OpenVASCommandDispatcher
      include Msf::Ui::Console::CommandDispatcher

      def name
        'OpenVAS'
      end

      def commands
        {
          'openvas_help' => 'Displays help',
          'openvas_version' => 'Display the version of the OpenVAS server',
          'openvas_debug' => 'Enable/Disable debugging',
          'openvas_connect' => 'Connect to an OpenVAS manager using OMP',
          'openvas_disconnect' => 'Disconnect from OpenVAS manager',

          'openvas_task_create' => 'Create a task (name, comment, target, config)',
          'openvas_task_delete' => 'Delete task by ID',
          'openvas_task_list' => 'Display list of tasks',
          'openvas_task_start' => 'Start task by ID',
          'openvas_task_stop' => 'Stop task by ID',
          'openvas_task_pause' => 'Pause task by ID',
          'openvas_task_resume' => 'Resume task by ID',
          'openvas_task_resume_or_start' => 'Resume task or start task by ID',

          'openvas_target_create' => 'Create target (name, hosts, comment)',
          'openvas_target_delete' => 'Delete target by ID',
          'openvas_target_list' => 'Display list of targets',

          'openvas_config_list' => 'Quickly display list of configs',

          'openvas_format_list' => 'Display list of available report formats',

          'openvas_report_list' => 'Display a list of available report formats',
          'openvas_report_delete' => 'Delete a report specified by ID',
          'openvas_report_download' => 'Save a report to disk',
          'openvas_report_import' => 'Import report specified by ID into framework'
        }
      end

      def cmd_openvas_help
        print_status('openvas_help                  Display this help')
        print_status('openvas_debug                 Enable/Disable debugging')
        print_status('openvas_version               Display the version of the OpenVAS server')
        print_status
        print_status('CONNECTION')
        print_status('==========')
        print_status('openvas_connect               Connects to OpenVAS')
        print_status('openvas_disconnect            Disconnects from OpenVAS')
        print_status
        print_status('TARGETS')
        print_status('=======')
        print_status('openvas_target_create         Create target')
        print_status('openvas_target_delete         Deletes target specified by ID')
        print_status('openvas_target_list           Lists targets')
        print_status
        print_status('TASKS')
        print_status('=====')
        print_status('openvas_task_create           Create task')
        print_status('openvas_task_delete           Delete a task and all associated reports')
        print_status('openvas_task_list             Lists tasks')
        print_status('openvas_task_start            Starts task specified by ID')
        print_status('openvas_task_stop             Stops task specified by ID')
        print_status('openvas_task_pause            Pauses task specified by ID')
        print_status('openvas_task_resume           Resumes task specified by ID')
        print_status('openvas_task_resume_or_start  Resumes or starts task specified by ID')
        print_status
        print_status('CONFIGS')
        print_status('=======')
        print_status('openvas_config_list           Lists scan configurations')
        print_status
        print_status('FORMATS')
        print_status('=======')
        print_status('openvas_format_list           Lists available report formats')
        print_status
        print_status('REPORTS')
        print_status('=======')
        print_status('openvas_report_list           Lists available reports')
        print_status('openvas_report_delete         Delete a report specified by ID')
        print_status('openvas_report_import         Imports an OpenVAS report specified by ID')
        print_status('openvas_report_download       Downloads an OpenVAS report specified by ID')
      end

      # Verify the database is connected and usable
      def database?
        if !(framework.db && framework.db.usable)
          return false
        else
          return true
        end
      end

      # Verify there is an active OpenVAS connection
      def openvas?
        if @ov
          return true
        else
          print_error('No OpenVAS connection available. Please use openvas_connect.')
          return false
        end
      end

      # Verify correct number of arguments and verify -h was not given. Return
      # true if correct number of arguments and help was not requested.
      def args?(args, min = 1, max = nil)
        if !max then max = min end
        if ((args.length < min) || (args.length > max) || (args[0] == '-h'))
          return false
        end

        return true
      end

      #--------------------------
      # Basic Functions
      #--------------------------
      def cmd_openvas_debug(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.debug(args[0].to_i)
            print_good(resp)
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage:')
          print_status('openvas_debug integer')
        end
      end

      def cmd_openvas_version
        return unless openvas?

        begin
          ver = @ov.version_get
          print_good("Using OMP version #{ver}")
        rescue OpenVASOMP::OMPError => e
          print_error(e.to_s)
        end
      end

      #--------------------------
      # Connection Functions
      #--------------------------
      def cmd_openvas_connect(*args)
        # Is the database configured?
        if !database?
          print_error('No database has been configured.')
          return
        end

        # Don't allow duplicate sessions
        if @ov
          print_error('Session already open, please use openvas_disconnect first.')
          return
        end

        # Make sure the correct number of arguments are present.
        if args?(args, 4, 5)

          user, pass, host, port, sslv = args

          # SSL warning. User is required to confirm.
          if ((host != 'localhost') && (host != '127.0.0.1') && (sslv != 'ok'))
            print_error('Warning: SSL connections are not verified in this release, it is possible for an attacker')
            print_error('         with the ability to man-in-the-middle the OpenVAS traffic to capture the OpenVAS')
            print_error("         credentials. If you are running this on a trusted network, please pass in 'ok'")
            print_error('         as an additional parameter to this command.')
            return
          end

          begin
            print_status("Connecting to OpenVAS instance at #{host}:#{port} with username #{user}...")
            ov = OpenVASOMP::OpenVASOMP.new('user' => user, 'password' => pass, 'host' => host, 'port' => port)
          rescue OpenVASOMP::OMPAuthError => e
            print_error("Authentication failed: #{e.reason}")
            return
          rescue OpenVASOMP::OMPConnectionError => e
            print_error("Connection failed: #{e.reason}")
            return
          end
          print_good('OpenVAS connection successful')
          @ov = ov

        else
          print_status('Usage:')
          print_status('openvas_connect username password host port <ssl-confirm>')
        end
      end

      # Disconnect from an OpenVAS manager
      def cmd_openvas_disconnect
        return unless openvas?

        @ov.logout
        @ov = nil
      end

      #--------------------------
      # Target Functions
      #--------------------------
      def cmd_openvas_target_create(*args)
        return unless openvas?

        if args?(args, 3)
          begin
            resp = @ov.target_create('name' => args[0], 'hosts' => args[1], 'comment' => args[2])
            print_status(resp)
            cmd_openvas_target_list
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end

        else
          print_status('Usage: openvas_target_create <name> <hosts> <comment>')
        end
      end

      def cmd_openvas_target_delete(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.target_delete(args[0])
            print_status(resp)
            cmd_openvas_target_list
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_target_delete <target_id>')
        end
      end

      def cmd_openvas_target_list(*_args)
        return unless openvas?

        begin
          tbl = Rex::Text::Table.new(
            'Columns' => ['ID', 'Name', 'Hosts', 'Max Hosts', 'In Use', 'Comment']
          )
          @ov.target_get_all.each do |target|
            tbl << [
              target['id'], target['name'], target['hosts'], target['max_hosts'],
              target['in_use'], target['comment']
            ]
          end
          print_good('OpenVAS list of targets')
          print_line
          print_line tbl.to_s
          print_line
        rescue OpenVASOMP::OMPError => e
          print_error(e.to_s)
        end
      end

      #--------------------------
      # Task Functions
      #--------------------------
      def cmd_openvas_task_create(*args)
        return unless openvas?

        if args?(args, 4)
          begin
            resp = @ov.task_create('name' => args[0], 'comment' => args[1], 'config' => args[2], 'target' => args[3])
            print_status(resp)
            cmd_openvas_task_list
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end

        else
          print_status('Usage: openvas_task_create <name> <comment> <config_id> <target_id>')
        end
      end

      def cmd_openvas_task_delete(*args)
        return unless openvas?

        if args?(args, 2)

          # User is required to confirm before deleting task.
          if (args[1] != 'ok')
            print_error('Warning: Deleting a task will also delete all reports associated with the ')
            print_error("task, please pass in 'ok' as an additional parameter to this command.")
            return
          end

          begin
            resp = @ov.task_delete(args[0])
            print_status(resp)
            cmd_openvas_task_list
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_task_delete <id> ok')
          print_error('This will delete the task and all associated reports.')
        end
      end

      def cmd_openvas_task_list(*_args)
        return unless openvas?

        begin
          tbl = Rex::Text::Table.new(
            'Columns' => ['ID', 'Name', 'Comment', 'Status', 'Progress']
          )
          @ov.task_get_all.each do |task|
            tbl << [ task['id'], task['name'], task['comment'], task['status'], task['progress'] ]
          end
          print_good('OpenVAS list of tasks')
          print_line
          print_line tbl.to_s
          print_line
        rescue OpenVASOMP::OMPError => e
          print_error(e.to_s)
        end
      end

      def cmd_openvas_task_start(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.task_start(args[0])
            print_status(resp)
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_task_start <id>')
        end
      end

      def cmd_openvas_task_stop(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.task_stop(args[0])
            print_status(resp)
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_task_stop <id>')
        end
      end

      def cmd_openvas_task_pause(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.task_pause(args[0])
            print_status(resp)
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_task_pause <id>')
        end
      end

      def cmd_openvas_task_resume(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.task_resume_paused(args[0])
            print_status(resp)
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_task_resume <id>')
        end
      end

      def cmd_openvas_task_resume_or_start(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.task_resume_or_start(args[0])
            print_status(resp)
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_task_resume_or_start <id>')
        end
      end

      #--------------------------
      # Config Functions
      #--------------------------
      def cmd_openvas_config_list(*_args)
        return unless openvas?

        begin
          tbl = Rex::Text::Table.new(
            'Columns' => [ 'ID', 'Name' ]
          )

          @ov.config_get_all.each do |config|
            tbl << [ config['id'], config['name'] ]
          end
          print_good('OpenVAS list of configs')
          print_line
          print_line tbl.to_s
          print_line
        rescue OpenVASOMP::OMPError => e
          print_error(e.to_s)
        end
      end

      #--------------------------
      # Format Functions
      #--------------------------
      def cmd_openvas_format_list(*_args)
        return unless openvas?

        begin
          tbl = Rex::Text::Table.new(
            'Columns' => ['ID', 'Name', 'Extension', 'Summary']
          )
          format_get_all.each do |format|
            tbl << [ format['id'], format['name'], format['extension'], format['summary'] ]
          end
          print_good('OpenVAS list of report formats')
          print_line
          print_line tbl.to_s
          print_line
        rescue OpenVASOMP::OMPError => e
          print_error(e.to_s)
        end
      end

      #--------------------------
      # Report Functions
      #--------------------------
      def cmd_openvas_report_list(*_args)
        return unless openvas?

        begin
          tbl = Rex::Text::Table.new(
            'Columns' => ['ID', 'Task Name', 'Start Time', 'Stop Time']
          )

          resp = @ov.report_get_raw

          resp.elements.each('//get_reports_response/report') do |report|
            report_id = report.elements['report'].attributes['id']
            report_task = report.elements['task/name'].get_text
            report_start_time = report.elements['creation_time'].get_text
            report_stop_time = report.elements['modification_time'].get_text

            tbl << [ report_id, report_task, report_start_time, report_stop_time ]
          end
          print_good('OpenVAS list of reports')
          print_line
          print_line tbl.to_s
          print_line
        rescue OpenVASOMP::OMPError => e
          print_error(e.to_s)
        end
      end

      def cmd_openvas_report_delete(*args)
        return unless openvas?

        if args?(args)
          begin
            resp = @ov.report_delete(args[0])
            print_status(resp)
            cmd_openvas_report_list
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_report_delete <id>')
        end
      end

      def cmd_openvas_report_download(*args)
        return unless openvas?

        if args?(args, 4)
          begin
            report = @ov.report_get_raw('report_id' => args[0], 'format' => args[1])
            ::FileUtils.mkdir_p(args[2])
            name = ::File.join(args[2], args[3])
            print_status("Saving report to #{name}")
            output = ::File.new(name, 'w')
            data = nil
            report.elements.each('//get_reports_response') { |r| data = r.to_s }
            output.puts(data)
            output.close
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_report_download <report_id> <format_id> <path> <report_name>')
        end
      end

      def cmd_openvas_report_import(*args)
        return unless openvas?

        if args?(args, 2)
          begin
            report = @ov.report_get_raw('report_id' => args[0], 'format' => args[1])
            data = nil
            report.elements.each('//get_reports_response') { |r| data = r.to_s }
            print_status('Importing report to database.')
            framework.db.import({ data: data })
          rescue OpenVASOMP::OMPError => e
            print_error(e.to_s)
          end
        else
          print_status('Usage: openvas_report_import <report_id> <format_id>')
          print_status('Only the NBE and XML formats are supported for importing.')
        end
      end

      #--------------------------
      # Format Functions
      #--------------------------
      # Get a list of report formats
      def format_get_all
        resp = @ov.omp_request_xml('<get_report_formats/>')
        if @debug then print resp end

        list = Array.new
        resp.elements.each('//get_report_formats_response/report_format') do |report|
          td = Hash.new
          td['id'] = report.attributes['id']
          td['name'] = report.elements['name'].text
          td['extension'] = report.elements['extension'].text
          td['summary'] = report.elements['summary'].text
          list.push td
        end
        @formats = list
        return list
      rescue StandardError
        raise OMPResponseError
      end

    end

    #------------------------------
    # Plugin initialization
    #------------------------------

    def initialize(framework, opts)
      super
      add_console_dispatcher(OpenVASCommandDispatcher)
      print_status('Welcome to OpenVAS integration by kost and averagesecurityguy.')
      print_status
      print_status('OpenVAS integration requires a database connection. Once the ')
      print_status('database is ready, connect to the OpenVAS server using openvas_connect.')
      print_status('For additional commands use openvas_help.')
      print_status
      @ov = nil
      @formats = nil
      @debug = nil
    end

    def cleanup
      remove_console_dispatcher('OpenVAS')
    end

    def name
      'OpenVAS'
    end

    def desc
      'Integrates with the OpenVAS - open source vulnerability management'
    end
  end
end
