require 'nokogiri' require 'digest' require_relative '../objects/system' require_relative '../objects/module' require_relative 'xml_reader.rb' class SystemReader < XMLReader # uses nokogiri to extract all system information from scenario.xml # This includes module filters, which are module objects that contain filters for selecting # from the actual modules that are available # @return [Array] Array containing Systems objects def self.read_scenario(scenario_file, options) systems = [] # Parse and validate the schema doc = parse_doc(scenario_file, SCENARIO_SCHEMA_FILE, 'scenario') doc.xpath('/scenario/system').each_with_index do |system_node, system_index| module_selectors = [] system_name = system_node.at_xpath('system_name').text Print.verbose "system: #{system_name}" # system attributes, such as basebox selection system_attributes = read_attributes(system_node) # literal values to store directly in a datastore system_node.xpath('*[@into_datastore]/value').each do |value| name = value.xpath('../@into_datastore').to_s ($datastore[name] ||= []).push(value.text) end # datastore in a datastore if system_node.xpath('//*[@into_datastore]/datastore').to_s != "" Print.err "WARNING: a datastore cannot capture the values from another datastore (this will be ignored)" Print.err "The scenario has datastore(s) that try to save directly into another datastore -- currently this is only possible via an encoder" sleep 2 end # for each module selection system_node.xpath('//vulnerability | //service | //utility | //build | //network | //base | //encoder | //generator').each do |module_node| # create a selector module, which is a regular module instance used as a placeholder for matching requirements module_selector = Module.new(module_node.name) # create a unique id for tracking variables between modules module_selector.unique_id = module_node.path.gsub(/[^a-zA-Z0-9]/, '') # check if we need to be sending the module output to another module module_node.xpath('parent::input').each do |input| # Parent is input -- track that we need to send write value somewhere # if we need to feed results to parent module if input.xpath('@into').to_s input.xpath('..').each do |input_parent| module_selector.write_output_variable = input.xpath('@into').to_s module_selector.write_to_module_with_id = input_parent.path.gsub(/[^a-zA-Z0-9]/, '') end end # check if we need to send the module output to a datastore if input.xpath('@into_datastore').to_s != '' module_selector.write_to_datastore = input.xpath('@into_datastore').to_s end # check if we need to send the module path to a datastore (to ensure unique module selection) if input.xpath('@unique_module_list').to_s != '' module_selector.write_module_path_to_datastore = input.xpath('@unique_module_list').to_s end end # check if we are being passed an input *literal value* module_node.xpath('input/value').each do |input_value| variable = input_value.xpath('../@into').to_s value = input_value.text Print.verbose " -- literal value: #{variable} = #{value}" (module_selector.received_inputs[variable] ||= []).push(value) end # check if we are being passed a datastore as input module_node.xpath('input/datastore').each do |input_value| access = input_value.xpath('@access').to_s if access == '' access = 'all' end access_json = input_value.xpath('@access_json').to_s variable = input_value.xpath('../@into').to_s value = input_value.text Print.verbose " -- datastore: #{variable} = #{value}" (module_selector.received_datastores[variable] ||= []).push('variablename' => value, 'access' => access, 'access_json' => access_json) end module_node.xpath('@*').each do |attr| module_selector.attributes["#{attr.name}"] = [attr.text] unless attr.text.nil? || attr.text == '' end Print.verbose " #{module_node.name} (#{module_selector.unique_id}), selecting based on:" module_selector.attributes.each do |attr| if attr[0] && attr[1] && attr[0].to_s != "module_type" Print.verbose " - #{attr[0].to_s} ~= #{attr[1].to_s}" end end # If this module is for this system if module_selector.system_number == (system_index + 1) # insert into module list # if this module feeds output to another, ensure list order makes sense for processing... if module_selector.write_output_variable != nil Print.verbose " -- writes to: #{module_selector.write_to_module_with_id} - #{module_selector.write_output_variable}" # insert into module list before the module we are writing to insert_pos = -1 # end of list for i in 0..module_selectors.size-1 if module_selector.write_to_module_with_id == module_selectors[i].unique_id # found position of earlier module this one feeds into, so put this one first insert_pos = i end end module_selectors.insert(insert_pos, module_selector) else # otherwise just append module to end of list module_selectors << module_selector end end end # Create new system object before reading goals as we need the hostname system = System.new(system_name, system_attributes, module_selectors, scenario_file, options) # Parse goals system_node.xpath("goals").each do |goals_doc| goals_doc.elements.each {|node| goal_type = node.name goal_hash = {'goal_type' => goal_type, } node.children.each {|subnode| unless subnode.text? goal_hash.merge!({subnode.name => subnode.content.strip}) end } goal_hash.merge!({'hostname' => system.get_hostname}) unless goal_hash.has_key? 'hostname' system.goals << goal_hash } end systems << system end return systems end end