require 'thor'
require 'dawn/utils'

module Dawn
  module Cli
    # This class is responsible for the "dawn kb" command and related
    # subcommands.
    class Kb < Thor
      package_name "dawn"
      class_option :verbose, :type=>:boolean
      class_option :debug, :type=>:boolean

      no_commands{
        def init_globals
          $debug = true if options[:debug]
          $verbose = true if options[:verbose]
        end
      }

      desc "find", "Searches the knowledge base for a given vulnerability"
      def find(string)
        init_globals
        kb = Dawn::KnowledgeBase.instance
        kb.find(string)
      end

      desc "lint", "Checks knowledge base content for correcteness"
      def lint
        init_globals
        kb = Dawn::KnowledgeBase.instance
        kb.load(true)
      end

      desc "unpack", "Unpacks security checks in KB library path"
      def unpack
        init_globals
        $logger.helo APPNAME, Dawn::VERSION
        kb = Dawn::KnowledgeBase.instance
        kb.unpack
        $logger.bye
        Kernel.exit(0)
      end

      desc "status", "Checks the status of the knowledge base"
      def status
        init_globals
        $logger.helo APPNAME, Dawn::VERSION
        Dawn::KnowledgeBase.enabled_checks=[:bulletin, :generic_check]
        kb = Dawn::KnowledgeBase.instance
        kb.load
        if kb.security_checks.empty?
          $logger.error(kb.error)
        end
        $logger.info("" + kb.security_checks.count.to_s + " security checks loaded")
        if kb.is_packed?
          $logger.error "The knowledge base is packed. It must be unpacked with the 'unpack' command before it can be used"
        end
        $logger.bye
        Kernel.exit(0)
      end

      desc "list gem_name[gem_version]", "List all security issues affecting a gem passed as argument (the version string is optional)."
      def list(gem_name, gem_version=nil)
        init_globals
        to_check="#{gem_name}"
        to_check += ":#{gem_version}" unless gem_version.nil?

        Dawn::KnowledgeBase.enabled_checks=[:bulletin]
        kb = Dawn::KnowledgeBase.instance
        kb.load
        if kb.security_checks.empty?
          $logger.error(kb.error)
        end
        issues = kb.find_issues_by_gem(to_check)

        issues.each do |issue|
          puts "#{issue.name} "
        end
      end
    end

    class DawnCli < Thor
      package_name "dawn"
      class_option :verbose, :type=>:boolean
      class_option :debug, :type=>:boolean

      map %w[--version -v] => :__print_version

      desc "--version, -v", "Prints the dawnscanner version"
      def __print_version
        puts Dawn::VERSION
        Kernel.exit(0)
      end

      desc "kb SUBCOMMAND ... ARGS", "Interacts with the knowledge base"
      subcommand "kb", Dawn::Cli::Kb

      desc "scan", "scans a ruby written web application for security issues"
      method_option :config_file,   :type=>:string,   :default=>"",     :aliases => "-c", :desc=>"tells dawn to load configuration from filename"
      method_option :gemfile,       :type=>:boolean,  :default=>true,   :aliases => "-G", :desc => "uses Gemfile.lock to detect MVC"
      method_option :skip,          :type=>:array,                      :aliases => "-S", :desc => "specify a list of security checks to be skipped"
      method_option :report_format, :type=>:string,                     :aliases => "-F", :desc=>"specify the report format (text, html, json). Default is plain text files."
      method_option :exit_on_warn,  :type=>:boolean,  :default=>false,  :aliases => "-z", :desc =>"return number of found vulnerabilities as exit code"
      method_option :count,         :type=>:boolean,  :default=>false,  :aliases => "-C", :desc=>"count vulnerabilities (useful for scripts)"
      method_option :output,        :type=>:string,                     :aliases => "-O", :desc=>"write output to a file with the name specified by the parameter"
      method_option :dependencies,  :type=>:boolean,  :default=>false,  :aliases => "-d", :desc=>"scan only for vulnerabilities affecting dependencies in Gemfile.lock"

      def scan(target)
        $logger.helo APPNAME, Dawn::VERSION
        trap("INT") { $logger.die('[INTERRUPTED]') }

        $logger.die("invalid directory (#{target})") unless Dawn::Core.is_good_target?(target)

        $debug = true if options[:debug]
        $verbose = true if options[:verbose]
        checks_to_be_skipped = []
        checks_to_be_skipped = options[:skip] unless options[:skip].nil?

        debug_me("scanning #{target}")

        $config_file= Dawn::Core.find_conf(true) if options[:config_file].nil?
        $config = Dawn::Core.read_conf($config_file)

        debug_me($config)

        engine = Dawn::Core.detect_mvc(target) unless options[:gemfile]
        engine = Dawn::GemfileLock.new(target) if options[:gemfile]

        if engine.nil?
          $logger.error("MVC detection failure. Please open an issue at https://github.com/thesp0nge/dawnscanner/issues")
          $logger.die('ruby framework auto detect failed.')
        end

        if options[:exit_on_warn]
          Kernel.at_exit do
            if engine.count_vulnerabilities != 0
              Kernel.exit(engine.count_vulnerabilities)
            end
          end
        end


        engine.load_knowledge_base

        ret = engine.apply_all(checks_to_be_skipped)


        if options[:report_format] and options[:report_format].eql? "json"
          STDERR.puts (ret)? {:status=>"OK", :vulnerabilities_count=>engine.count_vulnerabilities}.to_json : {:status=>"KO", :vulnerabilities_count=>-1}.to_json
          $logger.bye
          Kernel.exit(0)
        end

        $logger.info("#{engine.count_vulnerabilities} issues found")
        $logger.info("#{engine.checks.count} checks applied")

        Dawn::Reporter.new({:engine=>engine, :apply_all_code=>ret}).report
        $logger.bye

        Kernel.exit(0)

      end
    end
  end
end
