diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index c74b205f0b..bbe8b7f790 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -2172,35 +2172,63 @@ class DBManager end - # - # Find or create a task matching this type/data - # + # TODO This method does not attempt to find. It just creates + # a report based on the passed params. def find_or_create_report(opts) report_report(opts) end + # Creates a Report based on passed parameters. Does not handle + # child artifacts. + # @param opts [Hash] + # @return [Integer] ID of created report def report_report(opts) return if not active ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - path = opts.delete(:path) || (raise RuntimeError, "A report :path is required") - ret = {} - user = opts.delete(:user) - options = opts.delete(:options) - rtype = opts.delete(:rtype) - report = wspace.reports.new - report.created_by = user - report.options = options - report.rtype = rtype - report.path = path - msf_import_timestamps(opts,report) - report.save! + report = Report.new(opts) + unless report.valid? + errors = report.errors.full_messages.join('; ') + raise RuntimeError "Report to be imported is not valid: #{errors}" + end + report.state = :complete # Presume complete since it was exported + report.save - ret[:task] = report + report.id } end + # Creates a ReportArtifact based on passed parameters. + # @param opts [Hash] of ReportArtifact attributes + def report_artifact(opts) + artifacts_dir = Report::ARTIFACT_DIR + tmp_path = opts[:file_path] + artifact_name = File.basename tmp_path + new_path = File.join(artifacts_dir, artifact_name) + + unless File.exists? tmp_path + raise DBImportError 'Report artifact file to be imported does not exist.' + end + + unless (File.directory?(artifacts_dir) && File.writable?(artifacts_dir)) + raise DBImportError "Could not move report artifact file to #{artifacts_dir}." + end + + if File.exists? new_path + unique_basename = "#{(Time.now.to_f*1000).to_i}_#{artifact_name}" + new_path = File.join(artifacts_dir, unique_basename) + end + + FileUtils.copy(tmp_path, new_path) + opts[:file_path] = new_path + artifact = ReportArtifact.new(opts) + unless artifact.valid? + errors = artifact.errors.full_messages.join('; ') + raise RuntimeError "Artifact to be imported is not valid: #{errors}" + end + artifact.save + end + # # This methods returns a list of all reports in the database # @@ -3793,43 +3821,55 @@ class DBManager # Import Reports doc.elements.each("/#{btag}/reports/report") do |report| - tmp = args[:ifd][:zip_tmp] - report_info = {} - report_info[:workspace] = args[:wspace] - # Should user be imported (original) or declared (the importing user)? - report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip) - report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip) - report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip) - report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip) - report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip) - report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip) - report_info[:task] = args[:task] - report_info[:orig_path].gsub!(/^\./, tmp) if report_info[:orig_path] - - # Only report a report if we actually have it. - # TODO: Copypasta. Seperate this out. - if ::File.exists? report_info[:orig_path] - reports_dir = ::File.join(basedir,"reports") - report_file = ::File.split(report_info[:orig_path]).last - if ::File.exists? reports_dir - unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir)) - raise DBImportError.new("Could not move files to #{reports_dir}") - end - else - ::FileUtils.mkdir_p(reports_dir) - end - new_report = ::File.join(reports_dir,report_file) - report_info[:path] = new_report - if ::File.exists?(new_report) - ::File.unlink new_report - else - report_report(report_info) - end - ::FileUtils.copy(report_info[:orig_path], new_report) - yield(:msf_report, new_report) if block - end + import_report(report, args, basedir) end + end + # @param report [REXML::Element] to be imported + # @param args [Hash] + # @param base_dir [String] + def import_report(report, args, base_dir) + tmp = args[:ifd][:zip_tmp] + report_info = {} + + report.elements.each do |e| + node_name = e.name + node_value = e.text + + # These need to be converted back to arrays: + array_attrs = %w|addresses file-formats options sections| + if array_attrs.member? node_name + node_value = JSON.parse(node_value) + end + # Don't restore these values: + skip_nodes = %w|id workspace-id artifacts| + next if skip_nodes.member? node_name + + report_info[node_name.parameterize.underscore.to_sym] = node_value + end + # Use current workspace + report_info[:workspace_id] = args[:wspace].id + + # Create report, need new ID to record artifacts + report_id = report_report(report_info) + + # Handle artifacts + report.elements['artifacts'].elements.each do |artifact| + artifact_opts = {} + artifact.elements.each do |attr| + skip_nodes = %w|id accessed-at| + next if skip_nodes.member? attr.name + + symboled_attr = attr.name.parameterize.underscore.to_sym + artifact_opts[symboled_attr] = attr.text + end + # Use new Report as parent + artifact_opts[:report_id] = report_id + # Update to full path + artifact_opts[:file_path].gsub!(/^\./, tmp) + + report_artifact(artifact_opts) + end end # Convert the string "NULL" to actual nil