module Msf
  module Ui
    module Gtk2
      
      ###
      #
      # This class provides an assistant to configure module
      #
      ###
      class MsfAssistant

        ###
        #
        # This class perform an assistant to configure and launch exploit
        #
        ###
        class Exploit < Msf::Ui::Gtk2::Assistant

          # to stock our values
          WIZARD = {}

          WizardStruct = Struct.new('Wizard',
          :description, :page,
          :target_state, :payload_state, :options_state, :review_state)

          ARRAY = [
            ['Target',
              ["Select your target", "intro", true, false, false, false],
            ],
            ['Payload',
              ["Select your payload", "payload", true, true, false, false],
            ],
            ['Options',
              ["Select your options", "option", true, true, true, false],
            ],
            ['Review',
              ["Confirm settings", "end", true, true, true, true],
            ],
          ].collect do |item, state|
            WIZARD[item] = WizardStruct.new(
            state[0],
            state[1],
            state[2],
            state[3],
            state[4],
            state[5]
            )
          end

          include Msf::Ui::Gtk2::MyControls

          def initialize(active_module)
            @active_module = active_module
            @session_tree  = $gtk2driver.session_tree
            @job_tree   = $gtk2driver.job_tree
            @hash = {}

            # Call the parent
            super(@active_module.name)

            # Initialize exploit driver's exploit instance
            @mydriver = Msf::ExploitDriver.new(framework)
            @mydriver.exploit = framework.exploits.create(@active_module.refname)

            # Populate the datastore if possible
            @mydriver.exploit.load_config

			initialize_options()

            # Begin the wizard
            target_completion()

            # Build the left frame
            populate_frame(
            [
              @label_target = create_label(	WIZARD['Target'].target_state,
              WIZARD['Target'].description
              ),
              @label_payload = create_label(	WIZARD['Target'].payload_state,
              WIZARD['Payload'].description
              ),
              @label_options = create_label(	WIZARD['Target'].options_state,
              WIZARD['Options'].description
              ),
              @label_review = create_label(	WIZARD['Target'].review_state,
              WIZARD['Review'].description
              )
            ]
            )

            self.show_all
          end

          #
          # Save configuration for MsfAssistant
          #
          def save
            dump_to_hash()

            @mydriver.exploit.datastore.import_options_from_hash(@hash, imported = false)

            # If payload
            begin
              @mydriver.payload.share_datastore(@mydriver.exploit.datastore)
            rescue
              nil
            end

            # TODO: choose the $gtk2driver or @mydriver.exploit ?
            $gtk2driver.active_module = @mydriver.exploit
            $gtk2driver.save_config

            # Save the framework's datastore
            framework.save_config
            @mydriver.exploit.datastore.to_file(Msf::Config.config_file,  @mydriver.exploit.refname)
			MsfDialog::Information.new(self, 
				"Configuration Saved", 
				"Settings for exploit module #{@mydriver.exploit.refname} have been saved to #{Msf::Config.config_file}. " +
				"These settings will be loaded the next time this module is accessed."
			)
			
            $gtk2driver.append_log_view("Saved configuration to: #{Msf::Config.config_file}\n")
          end

          #
          # Action when Forward button was clicked
          #
          def next_page
            if (self.page == "intro")
              self.page = "payload"
              refresh_label(	[@label_target], 			# historic
              [@label_payload], 			# actual
              [@label_options, @label_review]	# next
              )
              display()
              payload_completion()
            elsif (self.page == "payload")
              self.page = "options"
              refresh_label(	[@label_target, @label_payload],	# historic
              [@label_options], 			# actual
              [@label_review]				# next
              )
              display()
			  initialize_options()
              options_completion()
            elsif (self.page == "options")
              if not validate()
                self.page = "options"
                refresh_label(	[@label_target, @label_payload],	# historic
                [@label_options], 			# actual
                [@label_review]				# next
                )
                display()
				initialize_options()
                options_completion()
              else
                self.page = "end"
                refresh_label(	[@label_target, @label_payload, @label_options],
                [@label_review],
                nil
                )
                display()
                review_completion()
              end
            end
          end

          #
          # Action when Back button was clicked
          #
          def back_page
            if (self.page == "payload")
              self.page = "intro"
              refresh_label(	nil,
              [@label_target],
              [@label_payload, @label_options, @label_review]
              )
              display()
              target_completion()
            elsif (self.page == "options")
              self.page = "payload"
              refresh_label(	[@label_target],			# historic
              [@label_payload], 			          # actual
              [@label_options, @label_review]	  # next
              )
              display()
              payload_completion()
            elsif (self.page == "end")
              self.page = "options"
              refresh_label(	[@label_target, @label_payload],
              [@label_options],
              [@label_review]
              )
              display()
			  initialize_options()
              options_completion()
            end
          end

          #
          # Display the target view
          #
          def target_completion

            # Model for Gtk::Combo
            model_target = Gtk::ListStore.new(String, Object)

            # Add iter to Gtk::Combo
            @active_module.targets.each_with_index do |target, idx|
              iter = model_target.append
              iter[0] = target.name
              iter[1] = idx
            end

            # Gtk::ComboBox
            combo_target = Gtk::ComboBox.new(model_target)
            begin
              combo_target.active = @mydriver.exploit.datastore['TARGET'].to_i
            rescue
              combo_target.active = 0
            end

            # Pack & renderer combo_target
            renderer = Gtk::CellRendererText.new
            combo_target.pack_start(renderer, true)
            combo_target.set_attributes(renderer, :text => 0)

            # Define default value
            @hash["TARGET"] = combo_target.active_iter[1]

            # Signal for combo payload
            combo_target.signal_connect('changed') do ||
              @hash["TARGET"] = combo_target.active_iter[1]
            end

            self.main.pack_start(combo_target, true, false, 0)
            self.main.show_all
          end

          #
          # Display the payload view
          #
          def payload_completion

            # Model for Gtk::Combox
            model_all = Gtk::ListStore.new(String, Object)

            index = ""

            # Add iter to Model
            @active_module.compatible_payloads.each_with_index do |key, idx|
              iter = model_all.append

              # refname
              iter[0] = key[0]
              # payload
              iter[1] = key[1]

              if ( @mydriver.exploit.datastore['PAYLOAD'] == key[0])
                index = idx
              end
            end

            # Gtk::ComboBox
            combo_all = Gtk::ComboBox.new(model_all)
            begin
              combo_all.active = index
            rescue
              combo_all.active = 0
            end
            
            # Pack & renderer combo_all
            renderer = Gtk::CellRendererText.new
            combo_all.pack_start(renderer, true)
            combo_all.set_attributes(renderer, :text => 0)

            # Pack combo
            self.main.pack_start(combo_all, true, false, 0)

            # Stuff for description payload
            textscroll = Gtk::ScrolledWindow.new
            textscroll.shadow_type = Gtk::SHADOW_IN
            textscroll.hscrollbar_policy = Gtk::POLICY_AUTOMATIC
            textscroll.vscrollbar_policy = Gtk::POLICY_AUTOMATIC
            buffer = Gtk::TextBuffer.new
            textview = Gtk::TextView.new(buffer)
            textview.set_editable(false)
            textview.set_cursor_visible(false)
            textscroll.add(textview)

            # Pack description
            self.main.pack_start(textscroll, true, false, 0)

            # Define default value
            buffer.set_text(combo_all.active_iter[1].new.description)
            @hash["PAYLOAD"] = combo_all.active_iter[0]

            # Signal for combo payload
            combo_all.signal_connect('changed') do
              buffer.set_text(combo_all.active_iter[1].new.description)
              @hash["PAYLOAD"] = combo_all.active_iter[0]
            end

            self.main.show_all
          end


		#
		# Instantiate the options page controls
		#
		def initialize_options
			@frame_standard = Gtk::Expander.new("Standard")
			@frame_advanced = Gtk::Expander.new("Advanced")
			@frame_evasion  = Gtk::Expander.new("Evasion")
			@options_standard = Gtk::VBox.new(false, 0)
			@options_advanced = Gtk::VBox.new(false, 0)
			@options_evasion  = Gtk::VBox.new(false, 0)

			@framer   = Gtk::VBox.new(false, 10)
			@scroller = Gtk::ScrolledWindow.new
			@scroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) 
			@scroller.set_size_request(580, 420)

			@viewport = Gtk::Viewport.new(@scroller.hadjustment, @scroller.vadjustment)

			@frame_standard.expanded = true	
		end
		
		#
		# Display options view
		#
		def options_completion
			self.page = "options"

			# Title and three sets of options
			title = Gtk::Label.new
			title.set_markup("<big><b>#{@mydriver.exploit.name}</b></big>")
			@framer.pack_start(title, false, true, 5)	
			@framer.pack_start(@frame_standard, false, false, 10)
			@framer.pack_start(@frame_advanced, false, false, 10)
			@framer.pack_start(@frame_evasion, false, false, 10)

			uniq = {}
			
			# Exploit options
			@mydriver.exploit.options.sorted.each do |key, opt|
				uniq[key] ||= true
				if(opt.evasion?)
					@options_evasion.pack_start(add_option(key, opt, @mydriver.exploit.datastore[key]), false, false, 10)
				elsif(opt.advanced?)
					@options_advanced.pack_start(add_option(key, opt, @mydriver.exploit.datastore[key]), false, false, 10)
				else
					@options_standard.pack_start(add_option(key, opt, @mydriver.exploit.datastore[key]), false, false, 10)
				end	
			end

			# Payload options
			@mydriver.payload = framework.payloads.create(@hash["PAYLOAD"])
			@mydriver.payload.options.each do |key, opt|
				next if uniq[key]
				if(opt.evasion?)
					@options_evasion.pack_start(add_option(key, opt, @mydriver.exploit.datastore[key]), false, false, 10)
				elsif(opt.advanced?)
					@options_advanced.pack_start(add_option(key, opt, @mydriver.exploit.datastore[key]), false, false, 10)
				else
					@options_standard.pack_start(add_option(key, opt, @mydriver.exploit.datastore[key]), false, false, 10)
				end	

			end
			
			# Display
			indent = Gtk::HBox.new(false, 5)
			indent.pack_start(Gtk::Label.new(""), false, false, 5)
			indent.pack_start(@options_standard, false, false, 0)
			indent.pack_start(Gtk::Label.new(""),true, true, 5)
			@frame_standard.add(indent)

			indent = Gtk::HBox.new(false, 5)
			indent.pack_start(Gtk::Label.new(""), false, false, 5)
			indent.pack_start(@options_advanced, false, false, 0)
			indent.pack_start(Gtk::Label.new(""),true, true, 5)
			@frame_advanced.add(indent)

			indent = Gtk::HBox.new(false, 5)				
			indent.pack_start(Gtk::Label.new(""), false, false, 5)				
			indent.pack_start(@options_evasion, false, false, 0)			
			indent.pack_start(Gtk::Label.new(""),true, true, 5)						
			@frame_evasion.add(indent)

			labels = ["Standard", "Advanced", "Evasion"]
			[@frame_standard, @frame_advanced, @frame_evasion].each do |obj|
				txt = labels.shift
				obj.spacing = 10
				obj.use_markup = true
				obj.label = "<big><b>#{txt}</b></big>"
			end

			# Stuff it into a viewport
			@viewport.add(@framer)

			# Stuff the viewport into a scrolledwindow
			@scroller.add(@viewport)

			# Stuff this into main and call it done
			self.main.pack_start(@scroller, true, true, 10)	
			self.main.show_all
		end
		
 
          #
          # Put all values in a hash
          #
          def dump_to_hash
            @options_standard.each do |widget|
              name, value = widget.get_pair
              begin
                @hash[name] = value
              rescue
                nil
              end
            end

            @options_advanced.each do |widget|
              name, value = widget.get_pair
              begin
                if (@mydriver.exploit.options[name].default.to_s == value)
                  nil
                else
                  @hash[name] = value
                end
              rescue
                nil
              end
            end

            @options_evasion.each do |widget|
              name, value = widget.get_pair
              begin
                if (@mydriver.exploit.options[name].default.to_s == value)
                  nil
                else
                  @hash[name] = value
                end
              rescue
                nil
              end
            end
          end

          #
          # Validate options in datastore
          #
          def validate
            dump_to_hash()

            errors = []
            @mydriver.exploit.datastore.import_options_from_hash(@hash)

            @mydriver.exploit.options.each_pair do |name, option|
              if (!option.valid?(@mydriver.exploit.datastore[name]))
                errors << name

                # If the option is valid, normalize its format to the correct type.
              elsif ((val = option.normalize(@mydriver.exploit.datastore[name])) != nil)
                @mydriver.exploit.datastore.update_value(name, val)
              end
            end

            if (errors.empty? == false)
              MsfDialog::Error.new(self, "Failed to validate: #{errors.join(', ')}")
              false
            else
              true
            end
          end

          #
          # Display the review page
          #
          def review_completion
            warning = Gtk::Label.new
            warning.set_markup("Review your configuration before clicking the <b>apply</b> button")
            self.main.pack_start(warning, false, false, 0)

            label = Gtk::Label.new
            review = "\n\n"
            @hash.each do |key, value|
              review << "<b>#{key}</b> : #{value}\n"
            end
            label.set_markup(review)

            self.main.pack_start(label, false, false, 0)

            self.main.show_all
          end

          #
          # Fire !!
          #
          def apply
            # Import options from the supplied assistant
            @mydriver.exploit.datastore.import_options_from_hash(@hash)

            # Share the exploit's datastore with the payload
            @mydriver.payload.share_datastore(@mydriver.exploit.datastore)

            @mydriver.target_idx = (@mydriver.exploit.datastore['TARGET']).to_i

            @pipe = Msf::Ui::Gtk2::GtkConsolePipe.new

            @mydriver.exploit.init_ui(@pipe, @pipe)
            @mydriver.payload.init_ui(@pipe, @pipe)

            @mydriver.use_job = true

            @pipe.create_subscriber_proc() do |msg|
              $gtk2driver.append_log_view(@mydriver.exploit.refname.split("/")[-1] + " " + msg)
            end

            @pipe.print_status("Launching exploit #{@mydriver.exploit.refname}...")

            begin
              @mydriver.run
            rescue ::Exception => e
				@mydriver.exploit.print_error("Exploit failed: #{$!}")
				elog("Exploit failed: #{$!}", 'core', LEV_0)
				dlog("Call stack:\n#{$@.join("\n")}", 'core', LEV_3)
            end
            select(nil, nil, nil, 0.01)
            @pipe.print_status("Exploit #{@mydriver.exploit.refname} completed.") if not @mydriver.use_job
          end
          
        end # MsfAssistant::Exploit

      end # Exploit

    end
  end
end
