Class: ScriptOutputParser

Inherits:
Object
  • Object
show all
Defined in:
lib/audit/lib/parser/script_output_parser.rb

Overview

This class parses the output generated by a sh script. Each output line is expected to start with the marker LINE_START and fields are also separated with SEPARATOR.

  1. The first field is expected to be the name of the script part that generated the message.

  2. The second field is expected to be a severity level defined in RuleSeverity (RuleSeverity).

  3. The third field is the message type. Currently there are message types defined for program names (ProgramNameCommand), file attachment (AttachFileCommand), end of a check (CheckFinishedCommand), and key-value style data (DataCommand).

Author

Jonas Zaddach, SecludIT (zaddach [at] eurecom [dot] fr)

Constant Summary collapse

LINE_START =

marker used to identify beginning of a parseable line

"%%"
SEPARATOR =

marker used to separate fields in the line

"%%"
@@log =

Logger.

Logger.new(STDOUT)
@@command_mapper =

Mapping of command names to commands. TODO: This should be moved into a factory class

{
AttachFileCommand::COMMAND => AttachFileCommand,
MessageCommand::COMMAND => MessageCommand,
ProgramNameCommand::COMMAND => ProgramNameCommand,
CpeNameCommand::COMMAND => CpeNameCommand,
CheckFinishedCommand::COMMAND => CheckFinishedCommand,
ListeningPortCommand::COMMAND => ListeningPortCommand,
DataCommand::COMMAND => DataCommand}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ ScriptOutputParser

Create a new script output parser and set up the connection object that output is forwarded to this parser.

  • benchmark Benchmark that generates the output to parse.

  • connection Connection to the remote host where the benchmark is executed.

  • attachment_dir A writable directory path as string where file attachments from AttachFileCommand are supposed to be stored.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/audit/lib/parser/script_output_parser.rb', line 65

def initialize(options)
	raise "Need parameter :benchmark" unless options[:benchmark]
	raise "Need parameter :connection" unless options[:connection]

	@connection = options[:connection]
	@benchmark = options[:benchmark]
	
	if options[:attachment_dir] then
		@attachment_dir = options[:attachment_dir][-1] == '/' ? options[:attachment_dir][0 ... -1] : options[:attachment_dir]
		if !File.exists?(@attachment_dir) || !File.writable?(@attachment_dir) then
			raise SecurityError, "attachment directory #{@attachment_dir} is not writable"
		end
	else
		@attachment_dir = nil
	end
	
	@@log.level = Logger::DEBUG
	@@log = options[:logger] if options[:logger]
	
	@check_results = {}
	@rule_results = {}

	@total_time_units = 0
	@done_time_units = 0
	@total_time_units = @benchmark.duration()

	@stdout_line_buffer = StdoutLineBuffer.new(connection)
	@stdout_line_buffer.on_line do |msg|
		consume_stdout(msg)
	end
	
	connection.on_stderr do|msg|
		consume_stderr(msg)
	end
end

Instance Attribute Details

#attachment_dirObject (readonly)

Path string of the directory where attachments are stored.



56
57
58
# File 'lib/audit/lib/parser/script_output_parser.rb', line 56

def attachment_dir
  @attachment_dir
end

#benchmarkObject (readonly)

benchmark that is run on the remote host



50
51
52
# File 'lib/audit/lib/parser/script_output_parser.rb', line 50

def benchmark
  @benchmark
end

#check_resultsObject (readonly)

Results of the benchmark. The results of each check are pushed into this array after the check has completed.



54
55
56
# File 'lib/audit/lib/parser/script_output_parser.rb', line 54

def check_results
  @check_results
end

#connectionObject (readonly)

connection that is used to connect to the remote host



48
49
50
# File 'lib/audit/lib/parser/script_output_parser.rb', line 48

def connection
  @connection
end

Instance Method Details

#consume_stderr(text) ⇒ Object

This method is called whenever data on stderr is encountered. Normally this should never happen, as all scripts are supposed to suppress output on stderr.



137
138
139
# File 'lib/audit/lib/parser/script_output_parser.rb', line 137

def consume_stderr(text)
  @@log.warn {"Unexpected line on stderr: '#{text}'. Verify that scripts are not broken."}
end

#consume_stdout(line) ⇒ Object

Called whenever a new line of output was received on the connection.

  • line The newly received output line.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/audit/lib/parser/script_output_parser.rb', line 103

def consume_stdout(line)
  @@log.warn {"incomplete line '#{line}' on stdout, not terminated by \\n"} if line[-1] != "\n"
  if /^#{LINE_START}\s*([A-Za-z0-9_]+)\s*#{SEPARATOR}\s*([A-Za-z]+)\s*#{SEPARATOR}\s*([A-Za-z0-9_]+)\s*#{SEPARATOR}\s*(.*)$/.match(line) then
    #parse check id
    check = @benchmark.item_repository[$1] or process_unknown_check_id(line, $1)
    severity = RuleSeverity::parse($2)

    cmd_class = @@command_mapper[$3.upcase]

    if cmd_class.nil? then
      process_unknown_reply_command(line, check, severity, $3)
    else
      @@log.debug {"processing command #{cmd_class.name}: #{line}"}
      process_command(cmd_class.new(check, severity,  $4.split(SEPARATOR)))
    end
  else
    process_unknown_output(line)
  end
end

#on_check_completed(&block) ⇒ Object

Set a handler that is called whenever a check has completed.

  • block The given block is supposed to accept one argument, that is a RuleResult instance of the just-finished check.



190
191
192
# File 'lib/audit/lib/parser/script_output_parser.rb', line 190

def on_check_completed(&block)
  @check_completed_handler = block
end

#on_finished(&block) ⇒ Object

Set a handler that is called when the benchmark has completed.

  • block The given block is supposed to accept two arguments, the first being the benchmark object, and the second an Array of RuleResult objects, one for each check completed.



198
199
200
# File 'lib/audit/lib/parser/script_output_parser.rb', line 198

def on_finished(&block)
  @finished_handler = block
end

#process_check_finished_command(cmd) ⇒ Object

Called whenever a CheckFinishedCommand was encountered.



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/audit/lib/parser/script_output_parser.rb', line 157

def process_check_finished_command(cmd)
  #calculate progress percentage
  check = cmd.result().check
  @done_time_units += check.duration

  @@log.info {"Check #{check.id} finished, progress is %.2f%" % [progress() * 100]}

  @rule_results[check.id] = RuleResult.new(check, @check_results[check])
  @check_completed_handler.call(@rule_results[check.id]) unless @check_completed_handler.nil?

  #check if benchmark is finished
  @finished_handler.call(@benchmark, @rule_results) if @finished_handler && @done_time_units == @total_time_units
end

#process_command(cmd) ⇒ Object

Called whenever a complete response line of a script has been read and the contained command was parsed successfully.



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/audit/lib/parser/script_output_parser.rb', line 143

def process_command(cmd)
  cmd.process(self)
  result = cmd.result()
  
  @@log.debug {"cmd: #{cmd.class.name}, check_id: #{cmd.check.id}"}

  (@check_results[result.check] = [result] unless
    @check_results[result.check]) or
      @check_results[result.check] << result

  process_check_finished_command(cmd) if cmd.class == CheckFinishedCommand
end

#process_unknown_check_id(line, check_id_string) ⇒ Object

Called whenever an unknown check id was encountered.

Raises:



178
179
180
# File 'lib/audit/lib/parser/script_output_parser.rb', line 178

def process_unknown_check_id(line, check_id_string)
  raise ParseException, "Script replied with unknown check id #{$1}"
end

#process_unknown_output(line) ⇒ Object

Called whenever unknown output (that does not fit the specification, i.e. does not start with %%) is encountered on stdout.



173
174
175
# File 'lib/audit/lib/parser/script_output_parser.rb', line 173

def process_unknown_output(line)
  @@log.warn {"Unexpected output line on stdout: '#{line}'. Verify that the scripts are not broken."}
end

#process_unknown_reply_command(line, check, severity, cmd_string) ⇒ Object

Called whenever an unknown reply command was encountered.



183
184
185
# File 'lib/audit/lib/parser/script_output_parser.rb', line 183

def process_unknown_reply_command(line, check, severity, cmd_string)
  @@log.warn {"Command not found: '#{line}'."}
end

#progressObject

Get the current progress of the benchmark execution as a float value between 0 and 1.



124
125
126
# File 'lib/audit/lib/parser/script_output_parser.rb', line 124

def progress()
  return 1.0 * @done_time_units / @total_time_units
end

#remaining_timeObject

Get the estimated remaining time in seconds. This value is very inexact.



130
131
132
# File 'lib/audit/lib/parser/script_output_parser.rb', line 130

def remaining_time()
  return @total_time_units - @done_time_units
end