#
# Web assessment for the metasploit framework
# Efrain Torres    - et[ ] metasploit.com  2012
#

require 'rabal/tree'
require 'msf/core/rpc/v10/client'

module Msf

class Plugin::Wmap < Msf::Plugin
  class WmapCommandDispatcher

    attr_accessor :wmapmodules  # Enabled Wmap modules
    attr_accessor :targets      # Targets
    attr_accessor :lastsites    # Temp location of previously obtained sites
    attr_accessor :rpcarr       # Array or rpc connections
    attr_accessor :njobs        # Max number of jobs
    attr_accessor :nmaxdisplay  # Flag to stop displaying the same mesg
    attr_accessor :runlocal		# Flag to run local modules only
    attr_accessor :masstop		# Flag to stop everything
    attr_accessor :killwhenstop # Kill process when exiting

    include Msf::Ui::Console::CommandDispatcher

    def name
      "wmap"
    end

    #
    # The initial command set
    #
    def commands
      {
        "wmap_targets"  => "Manage targets",
        "wmap_sites"   => "Manage sites",
        "wmap_nodes" => "Manage nodes",
        "wmap_run"   => "Test targets",
        "wmap_modules"  => "Manage wmap modules",
        "wmap_vulns"  => "Display web vulns",
      }
    end

    def cmd_wmap_vulns(*args)
      args.push("-h") if args.length == 0

      while (arg = args.shift)
        case arg
        when '-l'
          view_vulns
          return
        when '-h'
          print_status("Usage: wmap_vulns [options]")
          print_line("\t-h 		Display this help text")
          print_line("\t-l 		Display web vulns table")

          print_line("")
          return
        else
          print_error("Unknown flag.")
          return
        end
      end
    end


    def cmd_wmap_modules(*args)
      args.push("-h") if args.length == 0

      while (arg = args.shift)
        case arg
        when '-l'
          view_modules
          return
        when '-r'
          load_wmap_modules(true)
          return
        when '-h'
          print_status("Usage: wmap_modules [options]")
          print_line("\t-h 		Display this help text")
          print_line("\t-l  		List all wmap enabled modules")
          print_line("\t-r		Reload wmap modules")

          print_line("")
          return
        else
          print_error("Unknown flag.")
          return
        end
      end
    end

    def cmd_wmap_targets(*args)
      args.push("-h") if args.length == 0

      while (arg = args.shift)
        case arg
        when '-c'
          self.targets = Hash.new()
        when '-l'
          view_targets
          return
        when '-t'
          process_urls(args.shift)
        when '-d'
          process_ids(args.shift)
        when '-h'
          print_status("Usage: wmap_targets [options]")
          print_line("\t-h 		Display this help text")
          print_line("\t-t [urls]	Define target sites (vhost1,url[space]vhost2,url) ")
          print_line("\t-d [ids]	Define target sites (id1, id2, id3 ...)")
          print_line("\t-c 		Clean target sites list")
          print_line("\t-l  		List all target sites")

          print_line("")
          return
        else
          print_error("Unknown flag.")
          return
        end
      end
    end

    def cmd_wmap_sites(*args)
      args.push("-h") if args.length == 0

      while (arg = args.shift)
        case arg
        when '-a'
          s = add_web_site(args.shift)
          if s
            print_status("Site created.")
          else
            print_error("Unable to create site")
          end
        when '-d'
          del_idx = args
          if del_idx
            delete_sites(del_idx.select {|d| d =~ /^[0-9]*$/}.map(&:to_i).uniq)
            return
          else
            print_error("Provide index of site to delete")
          end
        when '-l'
          view_sites
          return
        when '-s'
          u = args.shift
          l = args.shift
          s = args.shift

          if not u
            return
          end

          if l == nil or l.empty?
            l = 200
            s = true
          else
            l = l.to_i
            s = false
          end

          if u.include? 'http'
            # Parameters are in url form
            view_site_tree(u,l,s)
          else
              # Parameters are digits
              if !self.lastsites or self.lastsites.length == 0
              view_sites
              print_status ("Web sites ids. referenced from previous table.")
            end

            target_whitelist = []
            ids = u.to_s.split(/,/)

            ids.each do |id|
              next if id.to_s.strip.empty?

              if id.to_i > self.lastsites.length
                print_error("Skipping id #{id}...")
              else
                target_whitelist << self.lastsites[id.to_i]
                #print_status("Loading #{self.lastsites[id.to_i]}.")
              end
            end

            # Skip the DB entirely if no matches
            return if target_whitelist.length == 0

            if not self.targets
              self.targets = Hash.new()
            end

            target_whitelist.each do |ent|
              view_site_tree(ent,l,s)
            end
          end
          return
        when '-h'
          print_status("Usage: wmap_sites [options]")
          print_line("\t-h        Display this help text")
          print_line("\t-a [url]  Add site (vhost,url)")
          print_line("\t-d [ids]  Delete sites (separate ids with space)")
          print_line("\t-l        List all available sites")
          print_line("\t-s [id]   Display site structure (vhost,url|ids) (level)")

          print_line("")
          return
        else
          print_error("Unknown flag.")
          return
        end
      end
    end

    def cmd_wmap_nodes(*args)

      if not self.rpcarr
        self.rpcarr=Hash.new()
      end

      args.push("-h") if args.length == 0

      while (arg = args.shift)
        case arg
        when '-a'
          h = args.shift
          r = args.shift
          s = args.shift
          u = args.shift
          p = args.shift

          res = rpc_add_node(h,r,s,u,p,false)
          if res
            print_status("Node created.")
          else
            print_error("Unable to create node")
          end
        when '-c'
          idref = args.shift

          if not idref
            print_error("No id defined")
            return
          end
          if idref.upcase == 'ALL'
            print_status("All nodes removed")
            self.rpcarr = Hash.new()
          else
            idx=0
            self.rpcarr.each do |k,v|
              if idx == idref.to_i
                self.rpcarr.delete(k)
                print_status("Node deleted #{k}")
              end
              idx += 1
            end
          end
        when '-d'
          host = args.shift
          port = args.shift
          user = args.shift
          pass = args.shift
          dbname = args.shift

          res = rpc_db_nodes(host,port,user,pass,dbname)
          if res
            print_status("OK.")
          else
            print_error("Error")
          end
        when '-l'
          rpc_list_nodes
          return
        when '-j'
          rpc_view_jobs
          return
        when '-k'
          node = args.shift
          jid = args.shift
          rpc_kill_node(node,jid)
          return
        when '-h'
          print_status("Usage: wmap_nodes [options]")
          print_line("\t-h                            Display this help text")
          print_line("\t-c id                         Remove id node (Use ALL for ALL nodes")
          print_line("\t-a host port ssl user pass    Add node")
          print_line("\t-d host port user pass db     Force all nodes to connect to db")
          print_line("\t-j                            View detailed jobs")
          print_line("\t-k ALL|id ALL|job_id          Kill jobs on node")
          print_line("\t-l                            List all current nodes")

          print_line("")
          return
        else
          print_error("Unknown flag.")
          return
        end
      end
    end

    def cmd_wmap_run(*args)
      # Stop everything
      self.masstop = false
      self.killwhenstop  = true

      trap("INT") {
        print_error("Stopping execution...")
        self.masstop = true
        if self.killwhenstop
          rpc_kill_node('ALL','ALL')
        end
      }

      # Max numbers of concurrent jobs per node
      self.njobs = 25
      self.nmaxdisplay = false
      self.runlocal = false

      # Formating
      sizeline = 60

      wmap_show = 2**0
      wmap_expl = 2**1

      # Exclude files can be modified by setting datastore['WMAP_EXCLUDE']
      wmap_exclude_files = '.*\.(gif|jpg|png*)$'

      run_wmap_ssl = true
      run_wmap_server = true
      run_wmap_dir_file = true
      run_wmap_query = true
      run_wmap_unique_query = true
      run_wmap_generic = true

      # If module supports datastore['VERBOSE']
      moduleverbose = false

      showprogress = false

      if not self.rpcarr
        self.rpcarr = Hash.new()
      end

      if not run_wmap_ssl
        print_status("Loading of wmap ssl modules disabled.")
      end
      if not run_wmap_server
        print_status("Loading of wmap server modules disabled.")
      end
      if not run_wmap_dir_file
        print_status("Loading of wmap dir and file modules disabled.")
      end
      if not run_wmap_query
        print_status("Loading of wmap query modules disabled.")
      end
      if not run_wmap_unique_query
        print_status("Loading of wmap unique query modules disabled.")
      end
      if not run_wmap_generic
        print_status("Loading of wmap generic modules disabled.")
      end

      stamp = Time.now.to_f
      mode  = 0

      eprofile = []
      using_p = false
      using_m = false
      usinginipath = false

      mname = ''
      inipathname = '/'

      args.push("-h") if args.length == 0

      while (arg = args.shift)
        case arg
        when '-t'
          mode |= wmap_show
        when '-e'
          mode |= wmap_expl

          profile = args.shift

          if profile
            print_status("Using profile #{profile}.")

            begin
              File.open(profile).each do |str|
                if not str.include? '#'
                  # Not a comment
                  modname = str.strip
                  if not modname.empty?
                    eprofile << modname
                  end
                end
                using_p = true
              end
            rescue
              print_error("Profile not found or invalid.")
              return
            end
          else
            print_status("Using ALL wmap enabled modules.")
          end
        when '-m'
          mode |= wmap_expl

          mname = args.shift

          if mname
            print_status("Using module #{mname}.")
          end
          using_m = true
        when '-p'
          mode |= wmap_expl

          inipathname = args.shift

          if inipathname
            print_status("Using initial path #{inipathname}.")
          end
          usinginipath = true

        when '-h'
          print_status("Usage: wmap_run [options]")
          print_line("\t-h                        Display this help text")
          print_line("\t-t                        Show all enabled modules")
          print_line("\t-m [regex]                Launch only modules that name match provided regex.")
          print_line("\t-p [regex]                Only test path defined by regex.")
          print_line("\t-e [/path/to/profile]     Launch profile modules against all matched targets.")
          print_line("\t                          (No profile file runs all enabled modules.)")
          print_line("")
          return
        else
          print_error("Unknown flag")
          return
        end
      end

      if (self.rpcarr.length == 0) and (mode & wmap_show == 0)
        print_error("NO WMAP NODES DEFINED. Executing local modules")
        self.runlocal = true
      end

      if self.targets == nil
        print_error("Targets have not been selected.")
        return
      end

      if self.targets.keys.length == 0
        print_error("Targets have not been selected.")
        return
      end

      execmod = true
      if (mode & wmap_show != 0)
        execmod = false
      end

      self.targets.each_with_index do |t, idx|

        selected_host = t[1][:host]
        selected_port = t[1][:port]
        selected_ssl  = t[1][:ssl]
        selected_vhost = t[1][:vhost]

        print_status ("Testing target:")
        print_status ("\tSite: #{selected_vhost} (#{selected_host})")
        print_status ("\tPort: #{selected_port} SSL: #{selected_ssl}")
        print_line '='* sizeline
        print_status("Testing started. #{(Time.now )}")

        if not selected_ssl
          run_wmap_ssl = false
          #print_status ("Target is not SSL. SSL modules disabled.")
        end

        # wmap_dir, wmap_file
        matches = Hash.new()

        # wmap_server
        matches1 = Hash.new()

        # wmap_query
        matches2 = Hash.new()

        # wmap_ssl
        matches3 = Hash.new()

        # wmap_unique_query
        matches5 = Hash.new()

        # wmap_generic
        matches10 = Hash.new()

        # OPTIONS
        opt_str = nil
        jobify  = false

        # This will be clean later
        load_wmap_modules(false)

        self.wmapmodules.each do |w|
          case w[2]
          when :wmap_server
            if run_wmap_server
              matches1[w]=true
            end
          when :wmap_query
            if run_wmap_query
              matches2[w]=true
            end
          when :wmap_unique_query
            if run_wmap_unique_query
              matches5[w]=true
            end
          when :wmap_generic
            if run_wmap_generic
              matches10[w]=true
            end
          when :wmap_dir, :wmap_file
            if run_wmap_dir_file
              matches[w]=true
            end
          when :wmap_ssl
            if run_wmap_ssl
              matches3[w]=true
            end
          else
            # Black Hole
          end
        end

        # Execution order (orderid)
        matches = sort_by_orderid(matches)
        matches1 = sort_by_orderid(matches1)
        matches2 = sort_by_orderid(matches2)
        matches3 = sort_by_orderid(matches3)
        matches5 = sort_by_orderid(matches5)
        matches10 = sort_by_orderid(matches10)

        #
        # Handle modules that need to be run before all tests IF SERVER is SSL, once usually again the SSL web server.
        # :wmap_ssl
        #

        print_status "\n=[ SSL testing ]="
        print_line "=" * sizeline

        if not selected_ssl
          print_status ("Target is not SSL. SSL modules disabled.")
        end

        idx = 0
        matches3.each_key do |xref|
          if self.masstop
            print_error("STOPPED.")
            return
          end

          # Module not part of profile or not match
          if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
            idx += 1

            begin
              # Module options hash
              modopts = Hash.new()

              #
              # The code is just a proof-of-concept and will be expanded in the future
              #
              print_status "Module #{xref[0]}"

              if (mode & wmap_expl != 0)

                #
                # For modules to have access to the global datastore
                # i.e. set -g DOMAIN test.com
                #
                self.framework.datastore.each do |gkey,gval|
                  modopts[gkey]=gval
                end

                #
                # Parameters passed in hash xref
                #
                modopts['RHOST'] = selected_host
                modopts['RHOSTS'] = selected_host
                modopts['RPORT'] = selected_port.to_s
                modopts['SSL'] = selected_ssl
                modopts['VHOST'] = selected_vhost.to_s
                modopts['VERBOSE'] = moduleverbose
                modopts['ShowProgress'] = showprogress
                modopts['RunAsJob'] = jobify

                begin
                  if execmod
                    rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
                  end
                rescue ::Exception
                  print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                end
              end

            rescue ::Exception
              print_status(" >> Exception from #{xref[0]}: #{$!}")
            end
          end
        end

        #
        # Handle modules that need to be run before all tests, once usually again the web server.
        # :wmap_server
        #
        print_status "\n=[ Web Server testing ]="
        print_line "=" * sizeline

        idx = 0
        matches1.each_key do |xref|

          if self.masstop
            print_error("STOPPED.")
            return
          end

          # Module not part of profile or not match
          if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
            idx += 1

            begin
              # Module options hash
              modopts = Hash.new()

              #
              # The code is just a proof-of-concept and will be expanded in the future
              #

              print_status "Module #{xref[0]}"

              if (mode & wmap_expl != 0)

                #
                # For modules to have access to the global datastore
                # i.e. set -g DOMAIN test.com
                #
                self.framework.datastore.each do |gkey,gval|
                  modopts[gkey]=gval
                end

                #
                # Parameters passed in hash xref
                #
                modopts['RHOST'] = selected_host
                modopts['RHOSTS'] = selected_host
                modopts['RPORT'] = selected_port.to_s
                modopts['SSL'] = selected_ssl
                modopts['VHOST'] = selected_vhost.to_s
                modopts['VERBOSE'] = moduleverbose
                modopts['ShowProgress'] = showprogress
                modopts['RunAsJob'] = jobify

                begin
                  if execmod
                    rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
                  end
                rescue ::Exception
                  print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                end
              end

            rescue ::Exception
              print_status(" >> Exception from #{xref[0]}: #{$!}")
            end
          end
        end

        #
        # Handle modules to be run at every path/file
        # wmap_dir, wmap_file
        #
        print_status "\n=[ File/Dir testing ]="
        print_line "=" * sizeline

        idx = 0
        matches.each_key do |xref|

          if self.masstop
            print_error("STOPPED.")
            return
          end

          # Module not part of profile or not match
          if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
            idx+=1

            begin
              # Module options hash
              modopts = Hash.new()

              #
              # The code is just a proof-of-concept and will be expanded in the future
              #

              print_status "Module #{xref[0]}"

              if (mode & wmap_expl != 0)
                #
                # For modules to have access to the global datastore
                # i.e. set -g DOMAIN test.com
                #
                self.framework.datastore.each do |gkey,gval|
                  modopts[gkey]=gval
                end

                #
                # Parameters passed in hash xref
                #
                modopts['RHOST'] = selected_host
                modopts['RHOSTS'] = selected_host
                modopts['RPORT'] = selected_port.to_s
                modopts['SSL'] = selected_ssl
                modopts['VHOST'] = selected_vhost.to_s
                modopts['VERBOSE'] = moduleverbose
                modopts['ShowProgress'] = showprogress
                modopts['RunAsJob'] = jobify

                #
                # Run the plugins that only need to be
                # launched once.
                #

                wtype = xref[2]

                h = self.framework.db.workspace.hosts.find_by_address(selected_host)
                s = h.services.find_by_port(selected_port)
                w = s.web_sites.find_by_vhost(selected_vhost)

                test_tree = load_tree(w)
                test_tree.each do |node|

                  if self.masstop
                    print_error("STOPPED.")
                    return
                  end

                  p = node.current_path
                  testpath = Pathname.new(p)
                  strpath = testpath.cleanpath(false).to_s

                  #
                  # Fixing paths
                  #

                  if node.is_leaf? and not node.is_root?
                    #
                    # Later we can add here more checks to see if its a file
                    #
                  else
                    if node.is_root?
                      strpath = "/"
                    else
                      strpath = strpath.chomp + "/"
                    end
                  end

                  strpath = strpath.gsub("//", "/")
                  #print_status("Testing path: #{strpath}")

                  #
                  # Launch plugin depending module type.
                  # Module type depends on main input type.
                  # Code may be the same but it depend on final
                  # versions of plugins
                  #

                  case wtype
                  when :wmap_file
                    if node.is_leaf? and not node.is_root?
                      #
                      # Check if an exclusion regex has been defined
                      #
                      if self.framework.datastore['WMAP_EXCLUDE']
                        excludefilestr = self.framework.datastore['WMAP_EXCLUDE']
                      else
                        excludefilestr = wmap_exclude_files
                      end

                      if not strpath.match(excludefilestr)
                        if (not usinginipath) or (usinginipath and strpath.match(inipathname))
                          modopts['PATH'] = strpath
                          print_status("Path: #{strpath}")

                          begin
                            if execmod
                              rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
                            end
                          rescue ::Exception
                            print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                          end
                        end
                      end
                    end
                  when :wmap_dir
                    if (node.is_leaf? and not strpath.include? ".") or node.is_root? or not node.is_leaf?
                      if (not usinginipath) or (usinginipath and strpath.match(inipathname))

                        modopts['PATH'] = strpath
                        print_status("Path: #{strpath}")

                        begin
                          if execmod
                            rpcnode = rpc_round_exec(xref[0],xref[1], modopts, njobs)
                          end
                        rescue ::Exception
                          print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                        end
                      end
                    end
                  end
                end
              end
            rescue ::Exception
              print_status(" >> Exception from #{xref[0]}: #{$!}")
            end
          end
        end

        #
        # Run modules for each request to play with URI with UNIQUE query parameters.
        # wmap_unique_query
        #
        print_status "\n=[ Unique Query testing ]="
        print_line "=" * sizeline

        idx = 0
        matches5.each_key do |xref|

          if self.masstop
            print_error("STOPPED.")
            return
          end

          # Module not part of profile or not match
          if ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
            idx += 1

            begin
              # Module options hash
              modopts = Hash.new()

              #
              # The code is just a proof-of-concept and will be expanded in the future
              #

              print_status "Module #{xref[0]}"

              if (mode & wmap_expl != 0)
                #
                # For modules to have access to the global datastore
                # i.e. set -g DOMAIN test.com
                #
                self.framework.datastore.each do |gkey,gval|
                  modopts[gkey]=gval
                end

                #
                # Parameters passed in hash xref
                #

                modopts['RHOST'] = selected_host
                modopts['RHOSTS'] = selected_host
                modopts['RPORT'] = selected_port.to_s
                modopts['SSL'] = selected_ssl
                modopts['VHOST'] = selected_vhost.to_s
                modopts['VERBOSE'] = moduleverbose
                modopts['ShowProgress'] = showprogress
                modopts['RunAsJob'] = jobify

                #
                # Run the plugins for each request that have a distinct
                # GET/POST  URI QUERY string.
                #

                utest_query = Hash.new()

                h = self.framework.db.workspace.hosts.find_by_address(selected_host)
                s = h.services.find_by_port(selected_port)
                w = s.web_sites.find_by_vhost(selected_vhost)

                w.web_forms.each do |form|

                  if self.masstop
                    print_error("STOPPED.")
                    return
                  end

                  #
                  # Only test unique query strings by comparing signature to previous tested signatures 'path,p1,p2,pn'
                  #

                  datastr = ""
                  typestr = ""

                  temparr = []

                  #print_status "---------"
                  #print_status form.params
                  #print_status "+++++++++"

                  form.params.each do |p|
                    pn, pv, pt = p
                    if pn
                      if not pn.empty?
                        if not pv or pv.empty?
                          #TODO add value based on param name
                          pv = "aaa"
                        end

                        #temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
                        temparr << pn.to_s + "=" + pv.to_s
                      end
                    else
                      print_error("Blank parameter name. Form #{form.path}")
                    end
                  end

                  datastr = temparr.join("&") if (temparr and not temparr.empty?)

                  if (utest_query.has_key?(signature(form.path,datastr)) == false)

                    modopts['METHOD'] = form.method.upcase
                    modopts['PATH'] =  form.path
                    modopts['QUERY'] = form.query
                    if form.method.upcase == 'GET'
                      modopts['QUERY'] = datastr
                      modopts['DATA'] = ""
                    end
                    if form.method.upcase == 'POST'
                      modopts['DATA'] = datastr
                    end
                    modopts['TYPES'] = typestr

                    #
                    # TODO: Add headers, etc.
                    #
                    if (not usinginipath) or (usinginipath and form.path.match(inipathname))

                      print_status "Path #{form.path}"
                      #print_status("Unique PATH #{modopts['PATH']}")
                      #print_status("Unique GET #{modopts['QUERY']}")
                      #print_status("Unique POST #{modopts['DATA']}")
                      #print_status("MODOPTS: #{modopts}")

                      begin
                        if execmod
                          rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
                        end
                        utest_query[signature(form.path,datastr)]=1
                      rescue ::Exception
                        print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                      end
                    end
                  else
                    #print_status("Already tested")
                  end
                end
              end

            rescue ::Exception
              print_status(" >> Exception from #{xref[0]}: #{$!}")
            end
          end
        end

        #
        # Run modules for each request to play with URI query parameters.
        # This approach will reduce the complexity of the Tree used before
        # and will make this shotgun implementation much simple.
        # wmap_query
        #
        print_status "\n=[ Query testing ]="
        print_line "=" * sizeline

        idx = 0
        matches2.each_key do |xref|

          if self.masstop
            print_error("STOPPED.")
            return
          end

          # Module not part of profile or not match
          if not ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
            idx += 1

            begin
              # Module options hash
              modopts = Hash.new()

              #
              # The code is just a proof-of-concept and will be expanded in the future
              #

              print_status "Module #{xref[0]}"

              if (mode & wmap_expl != 0)

                #
                # For modules to have access to the global datastore
                # i.e. set -g DOMAIN test.com
                #
                self.framework.datastore.each do |gkey,gval|
                  modopts[gkey]=gval
                end

                #
                # Parameters passed in hash xref
                #

                modopts['RHOST'] = selected_host
                modopts['RHOSTS'] = selected_host
                modopts['RPORT'] = selected_port.to_s
                modopts['SSL'] = selected_ssl
                modopts['VHOST'] = selected_vhost.to_s
                modopts['VERBOSE'] = moduleverbose
                modopts['ShowProgress'] = showprogress
                modopts['RunAsJob'] = jobify

                #
                # Run the plugins for each request that have a distinct
                # GET/POST  URI QUERY string.
                #

                h = self.framework.db.workspace.hosts.find_by_address(selected_host)
                s = h.services.find_by_port(selected_port)
                w = s.web_sites.find_by_vhost(selected_vhost)

                w.web_forms.each do |req|

                  if self.masstop
                    print_error("STOPPED.")
                    return
                  end

                  datastr = ""
                  typestr = ""

                  temparr = []

                  req.params.each do |p|
                    pn, pv, pt = p
                    if pn
                      if not pn.empty?
                        if not pv or pv.empty?
                          #TODO add value based on param name
                          pv = "aaa"
                        end
                        #temparr << pn.to_s + "=" + Rex::Text.uri_encode(pv.to_s)
                        temparr << pn.to_s + "=" + pv.to_s
                      end
                    else
                      print_error("Blank parameter name. Form #{req.path}")
                    end
                  end

                  datastr = temparr.join("&") if (temparr and not temparr.empty?)

                  modopts['METHOD'] = req.method.upcase
                  modopts['PATH'] =  req.path
                  if req.method.upcase == 'GET'
                    modopts['QUERY'] = datastr
                    modopts['DATA'] = ""
                  end
                  modopts['DATA'] = datastr if req.method.upcase == 'POST'
                  modopts['TYPES'] = typestr

                  #
                  # TODO: Add method, headers, etc.
                  #
                  if (not usinginipath) or (usinginipath and req.path.match(inipathname))

                    print_status "Path #{req.path}"
                    #print_status("Query PATH #{modopts['PATH']}")
                    #print_status("Query GET #{modopts['QUERY']}")
                    #print_status("Query POST #{modopts['DATA']}")
                    #print_status("Query TYPES #{typestr}")

                    begin
                      if execmod
                        rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
                      end
                    rescue ::Exception
                      print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                    end
                  end
                end
              end

            rescue ::Exception
              print_status(" >> Exception from #{xref[0]}: #{$!}")
            end
          end
        end

        #
        # Handle modules that need to be after all tests, once.
        # Good place to have modules that analize the test results and/or
        # launch exploits.
        # :wmap_generic
        #
        print_status "\n=[ General testing ]="
        print_line "=" * sizeline

        idx = 0
        matches10.each_key do |xref|

          if self.masstop
            print_error("STOPPED.")
            return
          end

          # Module not part of profile or not match
          if not ( using_p and eprofile.include? xref[0].split('/').last ) or (using_m and xref[0].to_s.match(mname)) or (not using_m and not using_p)
            idx += 1


            begin
              # Module options hash
              modopts = Hash.new()

              #
              # The code is just a proof-of-concept and will be expanded in the future
              #

              print_status "Module #{xref[0]}"

              if (mode & wmap_expl != 0)

                #
                # For modules to have access to the global datastore
                # i.e. set -g DOMAIN test.com
                #
                self.framework.datastore.each do |gkey,gval|
                  modopts[gkey]=gval
                end

                #
                # Parameters passed in hash xref
                #

                modopts['RHOST'] = selected_host
                modopts['RHOSTS'] = selected_host
                modopts['RPORT'] = selected_port.to_s
                modopts['SSL'] = selected_ssl
                modopts['VHOST'] = selected_vhost.to_s
                modopts['VERBOSE'] = moduleverbose
                modopts['ShowProgress'] = showprogress
                modopts['RunAsJob'] = jobify

                #
                # Run the plugins that only need to be
                # launched once.
                #

                begin
                  if execmod
                    rpcnode = rpc_round_exec(xref[0],xref[1], modopts, self.njobs)
                  end
                rescue ::Exception
                  print_status(" >> Exception during launch from #{xref[0]}: #{$!}")
                end
              end

            rescue ::Exception
              print_status(" >> Exception from #{xref[0]}: #{$!}")
            end
          end
        end


        if (mode & wmap_expl != 0)
          print_line "+" * sizeline

          if not self.runlocal
            if execmod
              rpc_list_nodes()
              print_status("Note: Use wmap_nodes -l to list node status for completion")
            end
          end

          print_line("Launch completed in #{(Time.now.to_f - stamp)} seconds.")
          print_line "+" * sizeline
        end

        print_status("Done.")
      end

    # EOM
    end

    def view_targets
      if self.targets == nil or self.targets.keys.length == 0
        print_status "No targets have been defined"
        return
      end

      indent = '     '

      tbl = Rex::Ui::Text::Table.new(
        'Indent'  => indent.length,
        'Header'  => 'Defined targets',
        'Columns' =>
          [
            'Id',
            'Vhost',
            'Host',
            'Port',
            'SSL',
            'Path',
          ])

      self.targets.each_with_index { |t, idx|
        tbl << [ idx.to_s, t[1][:vhost], t[1][:host], t[1][:port], t[1][:ssl], "\t"+t[1][:path].to_s ]
      }

      print_status tbl.to_s + "\n"
    end

    def delete_sites(wmap_index)
      idx  = 0
      to_del = {}
      # Rebuild the index from wmap_sites -l
      self.framework.db.hosts.each do |bdhost|
        bdhost.services.each do |serv|
          serv.web_sites.each do |web|
            # If the index of this site matches any deletion index,
            # add to our hash, saving the index for later output
            to_del[idx] = web if wmap_index.any? {|w| w.to_i == idx}
            idx += 1
          end
        end
      end
      to_del.each do |widx,wsite|
        if wsite.delete
          print_status("Deleted #{wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
        else
          print_error("Could note delete {wsite.vhost} on #{wsite.service.host.address} at index #{widx}")
        end
      end
    end


    def view_sites
      # Clean temporary sites list
      self.lastsites = []

      indent = '     '

      tbl = Rex::Ui::Text::Table.new(
        'Indent'  => indent.length,
        'Header'  => 'Available sites',
        'Columns' =>
          [
            'Id',
            'Host',
            'Vhost',
            'Port',
            'Proto',
            '# Pages',
            '# Forms',
          ])

      idx  = 0
      self.framework.db.hosts.each do |bdhost|
        bdhost.services.each do |serv|
          serv.web_sites.each do |web|
            c = web.web_pages.count
            f = web.web_forms.count
            tbl << [ idx.to_s, bdhost.address, web.vhost, serv.port, serv.name, c.to_s, f.to_s ]
            idx += 1

            turl = web.vhost + "," + serv.name + "://" +bdhost.address.to_s + ":" + serv.port.to_s + "/"
            self.lastsites << turl
          end
        end
      end

      print_status tbl.to_s + "\n"

    end

    # Reusing code from hdmoore
    #
    # Allow the URL to be supplied as VHOST,URL if a custom VHOST
    # should be used. This allows for things like:
    # localhost,http://192.168.0.2/admin/

    def add_web_site(url)

        vhost = nil

        # Allow the URL to be supplied as VHOST,URL if a custom VHOST
        # should be used. This allows for things like:
        #   localhost,http://192.168.0.2/admin/

        if url !~ /^http/
          vhost,url = url.split(",", 2)
          if url.to_s.empty?
            url = vhost
            vhost = nil
          end
        end

        # Prefix http:// when the URL has no specified parameter
        if url !~ /^[a-z0-9A-Z]+:\/\//
          url = "http://" + url
        end

        uri = URI.parse(url) rescue nil
        if not uri
          print_error("Could not understand URL: #{url}")
          return
        end

        if uri.scheme !~ /^https?/
          print_error("Only http and https URLs are accepted: #{url}")
          return
        end

        ssl = false
        if uri.scheme == 'https'
          ssl = true
        end

        site = self.framework.db.report_web_site(:wait => true, :host => uri.host, :port => uri.port, :vhost => vhost, :ssl => ssl)

        return site
    end

    # Code by hdm. Modified two lines by et
    #
    def process_urls(urlstr)

      target_whitelist = []

      urls = urlstr.to_s.split(/\s+/)


      urls.each do |url|
        next if url.to_s.strip.empty?
        vhost = nil

        # Allow the URL to be supplied as VHOST,URL if a custom VHOST
        # should be used. This allows for things like:
        #   localhost,http://192.168.0.2/admin/

        if url !~ /^http/
          vhost,url = url.split(",", 2)
          if url.to_s.empty?
            url = vhost
            vhost = nil
          end
        end

        # Prefix http:// when the URL has no specified parameter
        if url !~ /^[a-z0-9A-Z]+:\/\//
          url = "http://" + url
        end

        uri = URI.parse(url) rescue nil
        if not uri
          print_error("Could not understand URL: #{url}")
          next
        end

        if uri.scheme !~ /^https?/
          print_error("Only http and https URLs are accepted: #{url}")
          next
        end

        target_whitelist << [vhost || uri.host, uri]
      end

      # Skip the DB entirely if no matches
      return if target_whitelist.length == 0

      if not self.targets
        # First time targets are defined
        self.targets = Hash.new()
      end

      target_whitelist.each do |ent|
        vhost,target = ent

        host = self.framework.db.workspace.hosts.find_by_address(target.host)
        if not host
          print_error("No matching host for #{target.host}")
          next
        end
        serv = host.services.find_by_port_and_proto(target.port, 'tcp')
        if not serv
          print_error("No matching service for #{target.host}:#{target.port}")
          next
        end

        #print_status "aaa"
        #print_status framework.db.workspace.name

        sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)

        sites.each do |site|
          # Initial defaul path
          inipath = target.path
          if target.path.empty?
            inipath = '/'
          end

          #site.web_forms.where(path: target.path).each do |form|
            ckey = [ site.vhost, host.address, serv.port, inipath].join("|")

            if not self.targets[ckey]
              self.targets[ckey] = WebTarget.new
              self.targets[ckey].merge!({
                :vhost => site.vhost,
                :host  => host.address,
                :port  => serv.port,
                :ssl   => (serv.name == "https"),
                :path  => inipath
              })
              #self.targets[ckey][inipath] = []
            else
              print_status("Target already set in targets list.")
            end

            # Store the form object in the hash for this path
            #self.targets[ckey][inipath] << inipath
          #end
        end
      end
    end

    # Code by hdm. Modified two lines by et
    # lastsites contains a temporary array with vhost,url strings so the id can be
    # referenced in the array and prevent new sites added in the db to corrupt previous id list.
    def process_ids(idsstr)

      if !self.lastsites or self.lastsites.length == 0
        view_sites
        print_status ("Web sites ids. referenced from previous table.")
      end

      target_whitelist = []
      ids = idsstr.to_s.split(/,/)

      ids.each do |id|
        next if id.to_s.strip.empty?

        if id.to_i > self.lastsites.length
          print_error("Skipping id #{id}...")
        else
          target_whitelist << self.lastsites[id.to_i]
          print_status("Loading #{self.lastsites[id.to_i]}.")
        end
      end

      # Skip the DB entirely if no matches
      return if target_whitelist.length == 0

      if not self.targets
        self.targets = Hash.new()
      end

      target_whitelist.each do |ent|
        process_urls(ent)
      end
    end

    def view_site_tree(urlstr, md, ld)
      if not urlstr
        return
      end

      site_whitelist = []

      urls = urlstr.to_s.split(/\s+/)

      urls.each do |url|
        next if url.to_s.strip.empty?
        vhost = nil

        # Allow the URL to be supplied as VHOST,URL if a custom VHOST
        # should be used. This allows for things like:
        #   localhost,http://192.168.0.2/admin/

        if url !~ /^http/
          vhost,url = url.split(",", 2)

          if url.to_s.empty?
            url = vhost
            vhost = nil
          end
        end

        # Prefix http:// when the URL has no specified parameter
        if url !~ /^[a-z0-9A-Z]+:\/\//
          url = "http://" + url
        end

        uri = URI.parse(url) rescue nil
        if not uri
          print_error("Could not understand URL: #{url}")
          next
        end

        if uri.scheme !~ /^https?/
          print_error("Only http and https URLs are accepted: #{url}")
          next
        end

        site_whitelist << [vhost || uri.host, uri]
      end

      # Skip the DB entirely if no matches
      return if site_whitelist.length == 0

      vsites = Hash.new()

      site_whitelist.each do |ent|
        vhost,target = ent

        host = self.framework.db.workspace.hosts.find_by_address(target.host)
        if not host
          print_error("No matching host for #{target.host}")
          next
        end
        serv = host.services.find_by_port_and_proto(target.port, 'tcp')
        if not serv
          print_error("No matching service for #{target.host}:#{target.port}")
          next
        end

        sites = serv.web_sites.where('vhost = ? and service_id = ?', vhost, serv.id)

        sites.each do |site|
          t = load_tree(site)
          print_tree(t,target.host,md,ld)
          print_line("\n")
        end
      end
    end

    #
    # Load website structure into a tree
    #

    def load_tree(s)

      pathchr = '/'

      wtree = Tree.new(s.vhost)

      # Load site pages
      s.web_pages.order('path asc').each do |req|
        tarray = req.path.to_s.split(pathchr)
        tarray.delete("")
        tpath = Pathname.new(pathchr)
        tarray.each do |df|
          wtree.add_at_path(tpath.to_s,df)
          tpath = tpath + Pathname.new(df.to_s)
        end
      end

      # Load site forms
      s.web_forms.each do |req|
        tarray = req.path.to_s.split(pathchr)
        tarray.delete("")
        tpath = Pathname.new(pathchr)
        tarray.each do |df|
          wtree.add_at_path(tpath.to_s,df)
          tpath = tpath + Pathname.new(df.to_s)
        end
      end

      return wtree
    end

    #
    # Print Tree structure. Still ugly
    #

    def print_tree(tree, ip, maxlevel, limitlevel)
      initab = " " * 4
      indent = 6
      if  tree != nil and tree.depth <= maxlevel
        print initab + (" " * indent * tree.depth)
        if tree.depth > 0
          print "|"+("-" * (indent-1))+"/"
        end
        if tree.depth >= 0
          if tree.depth == 0
            print "[#{tree.name}] (#{ip})\n"+initab+(" " * indent)+"\n"

          else
            c = tree.children.count
            if c > 0
              print tree.name	+ " (" + c.to_s+")\n"
            else
              print tree.name	+ "\n"
            end
          end
        end

        tree.children.each_pair do |name,child|
          print_tree(child,ip,maxlevel,limitlevel)
        end

      end
    end

    def signature(fpath,fquery)
      hsig = Hash.new()

      hsig = queryparse(fquery)

      #
      # Signature of the form ',p1,p2,pn' then to be appended to path: path,p1,p2,pn
      #

      sigstr = fpath + "," + hsig.map{|p| p[0].to_s}.join(",")
    end

    def queryparse(query)
      params = Hash.new()

      query.split(/[&;]/n).each do |pairs|
        key, value = pairs.split('=',2)
        if params.has_key?(key)
          #Error
        else
          params[key] = value
        end
      end
      params
    end

    def rpc_add_node(host,port,ssl,user,pass,bypass_exist)

      if not self.rpcarr
        self.rpcarr = Hash.new()
      end

      begin
        istr = "#{host}|#{port}|#{ssl}|#{user}|#{pass}"
        if self.rpcarr.has_key?(istr) and not bypass_exist and self.rpcarr[istr] != nil
          print_error("Connection already exists #{istr}")
        else
          begin
            temprpc = ::Msf::RPC::Client.new(
              :host => host,
              :port => port,
              :ssl => ssl
            )
          rescue
            print_error "Unable to connect"
            #raise ConnectionError
            return
          end

          res = temprpc.login( user , pass)

          if not res
            print_error("Unable to authenticate to #{host}:#{port}.")
            return
          else
            res = temprpc.call('core.version')
          end

          print_status("Connected to #{host}:#{port} [#{res['version']}].")
          self.rpcarr[istr] = temprpc
        end
      rescue
        print_error("Unable to connect")
      end
    end

    def local_module_exec(mod,mtype, opts, nmaxjobs)
      jobify = false

      modinst = framework.modules.create(mod)

      if(not modinst)
        print_error("Unknown module")
        return
      end

      sess = nil

      case mtype
        when 'auxiliary'
          Msf::Simple::Auxiliary.run_simple(modinst, {
            'Action'   => opts['ACTION'],
            'LocalOutput' => driver.output,
            'RunAsJob' => jobify,
            'Options'  => opts
          })
        when 'exploit'
          if not opts['PAYLOAD']
            opts['PAYLOAD'] = WmapCommandDispatcher::Exploit.choose_payload(modinst, opts['TARGET'])
          end

          sess = Msf::Simple::Exploit.exploit_simple(modinst, {
            'Payload'  => opts['PAYLOAD'],
            'Target'   => opts['TARGET'],
            'LocalOutput' => driver.output,
            'RunAsJob' => jobify,
            'Options'  => opts
          })
        else
          print_error("Wrong mtype.")
      end

      if sess
        if (jobify == false and sess.interactive?)
          print_line
          driver.run_single("sessions -q -i #{sess.sid}")
        else
          print_status("Session #{sess.sid} created in the background.")
        end
      end
    end

    def rpc_round_exec(mod,mtype, opts, nmaxjobs)

      res = nil
      idx = 0

      if active_rpc_nodes == 0
        if not self.runlocal
          print_error("All active nodes not working or removed")
          return
        end
        res = true
      else
        rpc_reconnect_nodes()
      end

      if self.masstop
        return
      end

      while not res
        if active_rpc_nodes == 0
          print_error("All active nodes not working or removed")
          return
        end

        #find the node with less jobs load.
        minjobs = nmaxjobs
        minconn = nil
        nid = 0
        self.rpcarr.each do |k,rpccon|
          if not rpccon
            print_error("Skipping inactive node #{nid} #{k}")
          else
            begin
              currentjobs = rpccon.call('job.list').length

              if currentjobs < minjobs
                minconn = rpccon
                minjobs = currentjobs
              end

              if currentjobs == nmaxjobs
                if self.nmaxdisplay == false
                  print_error("Node #{nid} reached max number of jobs #{nmaxjobs}")
                  print_error("Waiting for available node/slot...")
                  self.nmaxdisplay = true
                end
              end
              #print_status("Node #{nid}   #currentjobs #{currentjobs} #min #{minjobs}")
            rescue
              print_error("Unable to connect. Node #{tarr[0]}:#{tarr[1]}")
              self.rpcarr[k]=nil

              if active_rpc_nodes == 0
                print_error("All active nodes ,not working or removed")
                return
              else
                print_error("Sending job to next node")
                next
              end
            end
          end
          nid += 1
        end

        if minjobs < nmaxjobs
          res=minconn.call('module.execute', mtype, mod, opts)
          self.nmaxdisplay = false
          #print_status(">>>#{res} #{mod}")

          if res
            if res.has_key?("job_id")
              return
            else
              print_error("Unable to execute module in node #{k} #{res}")
            end
          end
        else
          #print_status("Max number of jobs #{nmaxjobs} reached in node #{k}")
        end

        idx += 1
      end

      if self.runlocal and not self.masstop
        local_module_exec(mod,mtype, opts, nmaxjobs)
      end
    end

    def rpc_db_nodes(host,port,user,pass,name)
      rpc_reconnect_nodes()

      if active_rpc_nodes == 0
        print_error("No active nodes at this time")
        return
      end

      self.rpcarr.each do |k,v|
        if v
          res = v.call('db.driver',{:driver => 'postgresql'})
          res = v.call('db.connect',{:database => name, :host => host, :port => port, :username => user, :password => pass})
          res = v.call('db.status')

          if res['db'] == name
            print_status("db_connect #{res} #{host}:#{port} OK")
          else
            print_error("Error db_connect #{res}  #{host}:#{port}")
          end
        else
          print_error("No connection to node #{k}")
        end
      end
    end

    def rpc_reconnect_nodes()
      begin
        # Sucky 5 mins token timeout.

        idx = nil
        self.rpcarr.each do |k,rpccon|
          if rpccon
            idx = k
            begin
              currentjobs = rpccon.call('job.list').length
            rescue
              tarr = k.split("|")
              rflag = false

              res = rpccon.login(tarr[3],tarr[4])

              if res
                rflag = true
                print_error("Reauth to node #{tarr[0]}:#{tarr[1]}")
                break
              else
                raise ConnectionError
              end
            end
          end
        end
      rescue
        print_error("ERROR CONNECTING TO NODE.  Disabling #{idx} use wmap_nodes -a to reconnect")
        self.rpcarr[idx] = nil
        if active_rpc_nodes == 0
          print_error("No active nodes")
          self.masstop = true
        else
          #blah
        end
      end
    end

    def rpc_kill_node(i,j)

      if not i
        print_error("Nodes not defined")
        return
      end

      if not j
        print_error("Node jobs defined")
        return
      end

      rpc_reconnect_nodes()

      if active_rpc_nodes == 0
        print_error("No active nodes at this time")
        return
      end

      idx=0
      self.rpcarr.each do |k,rpccon|
        if idx == i.to_i or i.upcase == 'ALL'
          #begin
            if not rpccon
              print_error("No connection to node #{idx}")
            else
              n = rpccon.call('job.list')
              n.each do |id,name|
                if j==id.to_s or j.upcase == 'ALL'
                  rpccon.call('job.stop',id)
                  print_status("Node #{idx} Killed job id #{id} #{name}")
                end
              end
            end
          #rescue
          #	print_error("No connection")
          #end
        end
        idx += 1
      end
    end

    def rpc_view_jobs()
      indent = '     '

      rpc_reconnect_nodes()

      if active_rpc_nodes == 0
        print_error("No active nodes at this time")
        return
      end

      idx=0
      self.rpcarr.each do |k,rpccon|
        if not rpccon
          print_status("[Node ##{idx}: #{k} DISABLED/NO CONNECTION]")
        else

          arrk = k.split('|')
          print_status("[Node ##{idx}: #{arrk[0]} Port:#{arrk[1]} SSL:#{arrk[2]} User:#{arrk[3]}]")

          begin
            n = rpccon.call('job.list')

            tbl = Rex::Ui::Text::Table.new(
              'Indent'  => indent.length,
              'Header'  => 'Jobs',
              'Columns' =>
                [
                  'Id',
                  'Job name',
                  'Target',
                  'PATH',
                ])

            n.each do |id,name|
              jinfo = rpccon.call('job.info',id)
              dstore = jinfo['datastore']
              tbl << [ id.to_s, name,dstore['VHOST']+":"+dstore['RPORT'],dstore['PATH']]
            end

            print_status tbl.to_s + "\n"

          rescue
            print_status("[Node ##{idx} #{k} DISABLED/NO CONNECTION]")
          end
        end
        idx += 1
      end
    end


    # Modified from http://stackoverflow.com/questions/946738/detect-key-press-non-blocking-w-o-getc-gets-in-ruby
    def quit?
      begin
        while c = driver.input.read_nonblock(1)
          print_status("Quited")
          return true if c == 'Q'
        end
        false
      rescue Errno::EINTR
        false
      rescue Errno::EAGAIN
        false
      rescue EOFError
        true
      end
    end

    def rpc_mon_nodes()
      # Pretty monitor

      color = self.opts["ConsoleDriver"].output.supports_color? rescue false

      colors = [
                 '%grn',
             '%blu',
             '%yel',
             '%whi'
           ]

      #begin
        loop do
          rpc_reconnect_nodes()

          idx = 0
          self.rpcarr.each do |k,rpccon|

            arrk = k.split('|')

            v = "NOCONN"
            n = 1
            c = '%red'

            if not rpccon
              v = "NOCONN"
              n = 1
              c = '%red'
            else
              begin
                v = ""
                c = '%blu'
              rescue
                v = "ERROR"
                c = '%red'
              end

              begin
                n = rpccon.call('job.list').length
                c = '%blu'
              rescue
                n = 1
                v = "NOCONN"
                c = '%red'
              end
            end

            #begin
              if not @stdio
                @stdio = Rex::Ui::Text::Output::Stdio.new
              end

              if color == true
                @stdio.auto_color
              else
                @stdio.disable_color
              end
              msg = "[#{idx}] #{"%bld#{c}||%clr"*n} #{n} #{v}\n"
              @stdio.print_raw(@stdio.substitute_colors(msg))

            #rescue
              #blah
            #end
            sleep(2)
            idx += 1
          end
        end
      #rescue
      #	print_status("End.")
      #end
    end

    def rpc_list_nodes()
      indent = '     '

      tbl = Rex::Ui::Text::Table.new(
        'Indent'  => indent.length,
        'Header'  => 'Nodes',
        'Columns' =>
          [
            'Id',
            'Host',
            'Port',
            'SSL',
            'User',
            'Pass',
            'Status',
            '#jobs',
          ])

      idx=0

      rpc_reconnect_nodes()

      self.rpcarr.each do |k,rpccon|

        arrk = k.split('|')

        if not rpccon
          v = "NOCONN"
          n = ""
        else
          begin
            v = rpccon.call('core.version')['version']
          rescue
            v = "ERROR"
          end

          begin
            n = rpccon.call('job.list').length
          rescue
            n = ""
          end
        end

        tbl << [ idx.to_s, arrk[0], arrk[1], arrk[2], arrk[3], arrk[4], v, n]
        idx += 1
      end

      print_status tbl.to_s + "\n"
    end

    def active_rpc_nodes
      if self.rpcarr.length == 0
        return 0
      else
        idx = 0
        self.rpcarr.each do |k,conn|
          if conn
            idx += 1
          end
        end
        return idx
      end
    end

    def view_modules
      indent = '     '

      wmaptype = [:wmap_ssl,
            :wmap_server,
            :wmap_dir,
            :wmap_file,
            :wmap_unique_query,
            :wmap_query,
            :wmap_generic
            ]

      if not self.wmapmodules
        load_wmap_modules(true)
      end

      wmaptype.each do |modt|

        tbl = Rex::Ui::Text::Table.new(
          'Indent'  => indent.length,
          'Header'  =>  modt.to_s,
          'Columns' =>
            [
              'Name',
              'OrderID',
            ])

        idx = 0
        self.wmapmodules.each do |w|
          oid = w[3]
          if w[3] == 0xFFFFFF
            oid = ":last"
          end

          if w[2] == modt
            tbl << [w[0],oid]
            idx += 1
          end
        end

        print_status tbl.to_s + "\n"
      end
    end

    # Yes sorting hashes dont make sense but actually it does when you are enumerating one. And
    # sort_by of a hash returns an array so this is the reason for this ugly piece of code
    def sort_by_orderid(m)
      temphash=Hash.new()
      temparr=[]

      temparr = m.sort_by do |xref,v|
          xref[3]
      end

      temparr.each do |b|
        temphash[b[0]] = b[1]
      end
      temphash
    end

    # Load all wmap modules
    def load_wmap_modules(reload)
      if reload or not self.wmapmodules
        print_status("Loading wmap modules...")

        self.wmapmodules=[]

        idx = 0
        [ [ framework.auxiliary, 'auxiliary' ], [framework.exploits, 'exploit' ] ].each do |mtype|
          # Scan all exploit modules for matching references
          mtype[0].each_module do |n,m|
            e = m.new

            # Only include wmap_enabled plugins
            if e.respond_to?("wmap_enabled")
              penabled = e.wmap_enabled

              if penabled
                self.wmapmodules << [mtype[1]+'/'+n,mtype[1],e.wmap_type,e.orderid]
                idx += 1
              end
            end
          end
        end
        print_status("#{idx} wmap enabled modules loaded.")
      end
    end

    def view_vulns
      framework.db.hosts.each do |host|
        host.services.each do |serv|
          serv.web_sites.each do |site|
            site.web_vulns.each do |wv|
              print_status("+ [#{host.address}] (#{site.vhost}): #{wv.category} #{wv.path}")
              print_status("\t#{wv.name} #{wv.description}")
              print_status("\t#{wv.method} #{wv.proof}")
            end
          end
        end
      end
    end
  end

  class WebTarget < ::Hash
    def to_url
      proto = self[:ssl] ? "https" : "http"
      "#{proto}://#{self[:host]}:#{self[:port]}#{self[:path]}"
    end
  end


  def initialize(framework, opts)
    super

    color = self.opts["ConsoleDriver"].output.supports_color? rescue false

    wmapversion = '1.5.1'

    wmapbanner =  "%red\n.-.-.-..-.-.-..---..---.%clr\n"
    wmapbanner += "%red| | | || | | || | || |-'%clr\n"
    wmapbanner += "%red`-----'`-'-'-'`-^-'`-'%clr\n"
    wmapbanner += "[WMAP #{wmapversion}] ===  et [  ] metasploit.com 2012\n"

    if not @stdio
      @stdio = Rex::Ui::Text::Output::Stdio.new
    end

    if color == true
      @stdio.auto_color
    else
      @stdio.disable_color
    end

    @stdio.print_raw(@stdio.substitute_colors(wmapbanner))

    add_console_dispatcher(WmapCommandDispatcher)
    #print_status("#{wmapbanner}")
  end

  def cleanup
    remove_console_dispatcher('wmap')
  end

  def name
    "wmap"
  end

  def desc
    "Web assessment plugin"
  end

protected
end

end
