nexpose_servicenow 0.6.2 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/nexpose_servicenow.rb +58 -91
- data/lib/nexpose_servicenow/arg_parser.rb +80 -75
- data/lib/nexpose_servicenow/chunker.rb +0 -1
- data/lib/nexpose_servicenow/csv_compare.rb +17 -0
- data/lib/nexpose_servicenow/helpers/connection_helper.rb +79 -0
- data/lib/nexpose_servicenow/helpers/data_warehouse_helper.rb +134 -0
- data/lib/nexpose_servicenow/{nexpose_helper.rb → helpers/nexpose_console_helper.rb} +32 -85
- data/lib/nexpose_servicenow/historical_data.rb +46 -355
- data/lib/nexpose_servicenow/{queries.rb → queries/nexpose_queries.rb} +61 -90
- data/lib/nexpose_servicenow/queries/queries_base.rb +25 -0
- data/lib/nexpose_servicenow/queries/warehouse_queries.rb +330 -0
- data/lib/nexpose_servicenow/version.rb +1 -1
- data/nexpose_servicenow.gemspec +14 -11
- metadata +27 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba87a2439d290abbf57e7eb2d6792d691166f312
|
4
|
+
data.tar.gz: 0dcb0dc8fc8373431a3ccba7b6879903562a0fee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df8bcf8ccf412d0fe87aa41282233fc6e9739ccac94ec7398c42bd73842ec5de92ef7045ae300ad0e47999c808e5c327caf8fe974997066f349dfef54c45de63
|
7
|
+
data.tar.gz: 2f8dfbd40ea34d981d75da8b0bad547de128b4e3cd118c9bf3dec4883aeab01a61664f29d37a4b1156d60326864874fd3aa0e6a7d929510e356660cb425a2b76
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ Alternatively, it is also possible to call the following to see a list of parame
|
|
14
14
|
|
15
15
|
## Support
|
16
16
|
Please contact the following address for support queries:
|
17
|
-
[
|
17
|
+
[Rapid7 Support Portal](https://rapid7support.force.com/customers/login)
|
18
18
|
|
19
19
|
Please attach both the gem logs and relevant snippets from the agent logs.
|
20
20
|
|
data/lib/nexpose_servicenow.rb
CHANGED
@@ -2,8 +2,9 @@ require 'csv'
|
|
2
2
|
require 'optparse'
|
3
3
|
require 'nexpose'
|
4
4
|
require 'uri'
|
5
|
-
|
6
|
-
|
5
|
+
require_relative './nexpose_servicenow/helpers/connection_helper'
|
6
|
+
require_relative './nexpose_servicenow/helpers/nexpose_console_helper'
|
7
|
+
require_relative './nexpose_servicenow/helpers/data_warehouse_helper'
|
7
8
|
require 'nexpose_servicenow/arg_parser'
|
8
9
|
require 'nexpose_servicenow/chunker'
|
9
10
|
require 'nexpose_servicenow/nx_logger'
|
@@ -18,49 +19,54 @@ module NexposeServiceNow
|
|
18
19
|
@log = setup_logging(options)
|
19
20
|
|
20
21
|
censored_options = options.dup
|
21
|
-
censored_options[:
|
22
|
-
censored_options[:
|
22
|
+
censored_options[:username] = '*****'
|
23
|
+
censored_options[:password] = '*****'
|
23
24
|
@log.log_message("Options: #{censored_options}")
|
24
25
|
|
25
|
-
options[:
|
26
|
-
options[:
|
26
|
+
query = options[:query]
|
27
|
+
site_ids = options[:nexpose_ids]
|
28
|
+
|
29
|
+
# Filter out irrelevant sites
|
30
|
+
if query == :vulnerable_old_items
|
31
|
+
site_ids = get_historical_data(options).filter_ids(site_ids)
|
32
|
+
if site_ids.count == 0
|
33
|
+
puts 'No sites remaining for vulnerable old items query. Exiting.'
|
34
|
+
exit 0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
report_details = ConnectionHelper.get_report_names(query, site_ids)
|
27
39
|
|
28
|
-
report_details = NexposeHelper.get_report_names(options[:query],
|
29
|
-
options[:nexpose_ids])
|
30
40
|
report_details.each do |r|
|
31
|
-
r[:report_name] =
|
32
|
-
|
41
|
+
r[:report_name] = ConnectionHelper.get_filepath(r[:report_name],
|
42
|
+
options[:output_dir])
|
33
43
|
end
|
34
44
|
|
35
|
-
|
45
|
+
report_results = create_report(report_details, options)
|
36
46
|
|
37
|
-
|
47
|
+
# If data was returned, we can short circuit here
|
48
|
+
if !report_results.nil? and options[:mode] == 'chunk_info'
|
49
|
+
puts report_results
|
50
|
+
exit 0
|
51
|
+
end
|
38
52
|
|
39
53
|
@log.log_message("Initialising #{options[:mode]} mode")
|
40
54
|
self.send("#{options[:mode]}_mode", report_details, options)
|
41
55
|
end
|
42
56
|
|
43
57
|
def self.get_historical_data(options)
|
44
|
-
HistoricalData.new(options[:output_dir]
|
45
|
-
options[:nexpose_ids],
|
46
|
-
options[:id_type],
|
47
|
-
options[:start_time])
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.get_nexpose_helper(options)
|
51
|
-
NexposeHelper.new(options[:nexpose_url],
|
52
|
-
options[:nexpose_port],
|
53
|
-
options[:nexpose_username],
|
54
|
-
options[:nexpose_password])
|
58
|
+
HistoricalData.new(options[:output_dir])
|
55
59
|
end
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
return options[:nexpose_ids] if options[:nexpose_ids].first.to_s != '0'
|
61
|
+
def self.get_helper(options)
|
62
|
+
name = options[:conn_type].to_s.split('_').map(&:capitalize).join('')
|
60
63
|
|
61
|
-
|
62
|
-
helper
|
63
|
-
|
64
|
+
helper = eval("#{name}Helper")
|
65
|
+
helper.new(options[:url],
|
66
|
+
options[:port],
|
67
|
+
options[:username],
|
68
|
+
options[:password],
|
69
|
+
options[:database_name])
|
64
70
|
end
|
65
71
|
|
66
72
|
def self.setup_logging(options)
|
@@ -74,13 +80,6 @@ module NexposeServiceNow
|
|
74
80
|
log
|
75
81
|
end
|
76
82
|
|
77
|
-
# Merges in the details from the last time the integration ran reports.
|
78
|
-
def self.update_delta_files(options)
|
79
|
-
historical_data = get_historical_data(options)
|
80
|
-
historical_data.update_delta_file
|
81
|
-
historical_data.save_vuln_timestamp(filter_sites(options))
|
82
|
-
end
|
83
|
-
|
84
83
|
# Create a report if explicitly required or else an existing
|
85
84
|
# report file isn't found
|
86
85
|
def self.create_report(report_details, options)
|
@@ -89,76 +88,45 @@ module NexposeServiceNow
|
|
89
88
|
return
|
90
89
|
end
|
91
90
|
|
91
|
+
# Perform all queries if a file is missing, regardless of other settings
|
92
92
|
unless options[:gen_report]
|
93
|
-
# If any file is missing, perform all queries
|
94
93
|
return if report_details.all? { |f| File.exists? f[:report_name] }
|
95
94
|
end
|
96
95
|
|
97
|
-
credentials = %i{
|
96
|
+
credentials = %i{username password}
|
98
97
|
if credentials.any? { |cred| options[cred].to_s == '' }
|
99
98
|
@log.log_error_message 'Nexpose credentials necessary but not supplied.'
|
100
99
|
exit -1
|
101
100
|
end
|
102
101
|
|
103
|
-
#Filter it down to sites which actively need queried
|
104
|
-
sites_to_scan =
|
105
|
-
nexpose_helper = get_nexpose_helper(options)
|
106
|
-
hist_data = get_historical_data(options)
|
107
|
-
vuln_query = options[:query].to_s.start_with? 'vulnerabili'
|
102
|
+
# Filter it down to sites which actively need queried
|
103
|
+
sites_to_scan = options[:nexpose_ids].keys
|
108
104
|
|
109
|
-
|
110
|
-
query_options =
|
111
|
-
query_options[:vuln_query_date] = hist_data.last_vuln_run if vuln_query
|
105
|
+
query_options = { delta_values: options[:nexpose_ids] }
|
106
|
+
query_options[:vuln_query_date] = options[:vuln_query_date]
|
112
107
|
query_options[:filters] = options[:filters]
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
filename.map! { |f| File.expand_path(options[:output_dir], f) }
|
123
|
-
return filename.join("\n")
|
124
|
-
end
|
125
|
-
|
126
|
-
File.expand_path(options[:output_dir], filename)
|
127
|
-
end
|
128
|
-
|
129
|
-
def self.filter_sites(options)
|
130
|
-
# These queries always run to make sure certain data is up to date
|
131
|
-
exceptions = %w(vulnerabili asset_groups sites tags)
|
132
|
-
if exceptions.any? { |e| options[:query].to_s.start_with? e }
|
133
|
-
return options[:nexpose_ids]
|
134
|
-
end
|
135
|
-
|
136
|
-
#Always run the query for latest scans or vulnerabilities
|
137
|
-
if options[:mode] == 'latest_scans' ||
|
138
|
-
options[:mode] == 'get_chunk'
|
139
|
-
return options[:nexpose_ids]
|
140
|
-
end
|
141
|
-
|
142
|
-
historical_data = get_historical_data(options)
|
143
|
-
imported_sites_only = options[:query].to_s.eql? 'vulnerable_old_items'
|
144
|
-
sites_to_scan = historical_data.collections_to_import(imported_sites_only)
|
145
|
-
|
146
|
-
return sites_to_scan unless (sites_to_scan.nil? || sites_to_scan.empty?)
|
147
|
-
|
148
|
-
@log.log_message "Sites #{options[:nexpose_ids]} are up to date."
|
149
|
-
@log.log_message "Query requested was: #{options[:query]}."
|
150
|
-
exit 0
|
108
|
+
query_options[:page_size] = options[:row_limit]
|
109
|
+
query_options[:row_limit] = options[:row_limit]
|
110
|
+
report_helper = get_helper(options)
|
111
|
+
@log.log_message("Querying using the #{report_helper.class}.")
|
112
|
+
report_helper.generate_report(options[:query],
|
113
|
+
sites_to_scan,
|
114
|
+
options[:id_type],
|
115
|
+
options[:output_dir],
|
116
|
+
query_options)
|
151
117
|
end
|
152
118
|
|
153
119
|
# Print the chunk info
|
154
120
|
def self.chunk_info_mode(report_details, options)
|
155
|
-
|
121
|
+
site_ids = options[:nexpose_ids].keys
|
122
|
+
|
123
|
+
# Assign -1 to reports without site IDs
|
156
124
|
report_details = report_details.select do |d|
|
157
|
-
d[:id] == -1 or
|
125
|
+
d[:id] == -1 or site_ids.include? d[:id]
|
158
126
|
end
|
127
|
+
|
159
128
|
chunker = Chunker.new(report_details, options[:row_limit])
|
160
129
|
|
161
|
-
# TODO: Check why filtered_sites are passed in here
|
162
130
|
puts chunker.preprocess
|
163
131
|
end
|
164
132
|
|
@@ -166,16 +134,15 @@ module NexposeServiceNow
|
|
166
134
|
def self.get_chunk_mode(report_details, options)
|
167
135
|
#Get the byte offset and length
|
168
136
|
chunker = Chunker.new(report_details, options[:row_limit])
|
169
|
-
filtered_sites = filter_sites(options)
|
170
137
|
|
171
138
|
puts chunker.read_chunk(options[:chunk_start],
|
172
139
|
options[:chunk_length],
|
173
|
-
|
140
|
+
options[:nexpose_ids].keys.first)
|
174
141
|
end
|
175
142
|
|
176
143
|
def self.latest_scans_mode(report_details, options)
|
177
144
|
historical_data = get_historical_data(options)
|
178
|
-
puts historical_data.filter_report
|
145
|
+
puts historical_data.filter_report options[:nexpose_ids].keys
|
179
146
|
end
|
180
147
|
|
181
148
|
def self.remove_last_scan_mode(report_details, options)
|
@@ -202,7 +169,7 @@ module NexposeServiceNow
|
|
202
169
|
|
203
170
|
def self.remove_diff_comparison_mode(report_details, options)
|
204
171
|
historical_data = get_historical_data(options)
|
205
|
-
historical_data.
|
172
|
+
historical_data.remove_diff_files options[:output_dir]
|
206
173
|
end
|
207
174
|
end
|
208
175
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'json'
|
3
3
|
require 'time'
|
4
|
-
require_relative './queries'
|
4
|
+
require_relative './queries/nexpose_queries'
|
5
5
|
require_relative './nx_logger'
|
6
6
|
|
7
7
|
module NexposeServiceNow
|
8
8
|
class ArgParser
|
9
9
|
NX_ID_TYPES = %i[site asset_group]
|
10
|
+
NX_CONNECTION_TYPES = %i[nexpose_console data_warehouse]
|
10
11
|
MODES = %i[chunk_info get_chunk latest_scans
|
11
|
-
remove_last_scan
|
12
|
-
|
13
|
-
|
14
|
-
QUERY_NAMES = Queries.methods(false)
|
12
|
+
remove_last_scan remove_last_vuln]
|
13
|
+
REQUIRED_OPTIONS = %i[url port username password]
|
14
|
+
QUERY_NAMES = NexposeQueries.methods(false)
|
15
15
|
|
16
16
|
def self.parse(args)
|
17
17
|
options = Hash.new
|
@@ -46,44 +46,71 @@ module NexposeServiceNow
|
|
46
46
|
options[:query] = query
|
47
47
|
end
|
48
48
|
|
49
|
-
opts.on('-t', '--type
|
50
|
-
"
|
51
|
-
|
49
|
+
opts.on('-t', '--type ID~CONNECTION', 'Select ID type ' \
|
50
|
+
"(#{NX_ID_TYPES.join(', ')}) and connection type " \
|
51
|
+
"(#{NX_CONNECTION_TYPES.join(', ')})") do |types|
|
52
|
+
type = types.split('~')
|
53
|
+
options[:id_type] = type[0].intern
|
54
|
+
options[:conn_type] = type[1].intern
|
55
|
+
|
56
|
+
if options[:conn_type].equal? :data_warehouse
|
57
|
+
REQUIRED_OPTIONS << :database_name
|
58
|
+
end
|
52
59
|
end
|
53
60
|
|
54
|
-
opts.on('-i', '--items x,y,z', Array,
|
55
|
-
'IDs of the nexpose items to
|
56
|
-
|
61
|
+
opts.on('-i', '--items x~x,y~y,z~z', Array,
|
62
|
+
'IDs of the nexpose items to ' \
|
63
|
+
'scan, provided with their previous scan IDs or timestamp ' \
|
64
|
+
'of last scan') do |items|
|
65
|
+
options[:nexpose_ids] = {}
|
66
|
+
|
67
|
+
# Split the string up into site and scan pairs
|
68
|
+
items = items.map { |s| s.split('~') }
|
69
|
+
|
70
|
+
# Store the information in site:scan_id dict
|
71
|
+
items.each { |site, scan| options[:nexpose_ids][site] = scan }
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.on('-a', '--abs-vulntime TIMESTAMP',
|
75
|
+
'Timestamp of last vulnerability definition import') do |vulnt|
|
76
|
+
# TODO: Does the date need formatted?
|
77
|
+
options[:vuln_query_date] = vulnt
|
57
78
|
end
|
58
79
|
|
59
80
|
opts.separator ''
|
60
|
-
opts.separator '
|
81
|
+
opts.separator 'Connection options:'
|
61
82
|
|
62
|
-
opts.on('-n', '--nexpose-
|
63
|
-
'URL of the Nexpose server') do |url|
|
83
|
+
opts.on('-n', '--nexpose-datastore URL',
|
84
|
+
'URL of the Nexpose/Data Warehouse server') do |url|
|
64
85
|
port = url.slice!(/:(\d+)$/)
|
65
86
|
port.slice! ':' unless port.nil?
|
66
87
|
|
67
88
|
url.slice! 'https://'
|
68
|
-
options[:
|
69
|
-
options[:
|
89
|
+
options[:url] = url
|
90
|
+
options[:port] = port
|
70
91
|
end
|
71
92
|
|
72
93
|
opts.on('-u', '--user USER',
|
73
|
-
'Username for Nexpose
|
74
|
-
options[:
|
94
|
+
'Username for Nexpose/Data Warehouse') do |username|
|
95
|
+
options[:username] = username
|
75
96
|
end
|
76
97
|
|
77
98
|
opts.on('-p', '--password PASSWORD',
|
78
|
-
'Password for the Nexpose user') do |password|
|
79
|
-
options[:
|
99
|
+
'Password for the Nexpose/Data Warehouse user') do |password|
|
100
|
+
options[:password] = password
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on('-b', '--database DATABASE_NAME',
|
104
|
+
'The name of the Postgres Database '\
|
105
|
+
'(DataWarehouse Only)') do |database|
|
106
|
+
options[:database_name] = database
|
80
107
|
end
|
81
108
|
|
82
109
|
opts.separator ''
|
83
110
|
opts.separator 'Chunk info mode options:'
|
84
111
|
|
85
112
|
opts.on('-r', '--row-limit LIMIT',
|
86
|
-
'Maximum number of rows per chunk (
|
113
|
+
'Maximum number of rows per chunk (inc. header).') do |limit|
|
87
114
|
options[:row_limit] = limit.to_i
|
88
115
|
options[:row_limit] = 9_999_999 if options[:row_limit] <= 0
|
89
116
|
end
|
@@ -104,37 +131,7 @@ module NexposeServiceNow
|
|
104
131
|
opts.separator ''
|
105
132
|
opts.separator 'Filter options:'
|
106
133
|
|
107
|
-
=begin
|
108
|
-
# CVE filter is not currently supported.
|
109
|
-
opts.on('-v', '--vuln-identifier CVE',
|
110
|
-
'The CVE values for which to filter.') do |data|
|
111
|
-
# A value of 'none' means the user has left field blank
|
112
|
-
if data.to_s.downcase != 'none'
|
113
|
-
data = data.to_s.sub(' ', '').split(',')
|
114
|
-
|
115
|
-
invalid_cve = data.select { |f| (f =~ /(CVE-)?\d{4}-\d+/) == nil }
|
116
|
-
unless invalid_cve.empty?
|
117
|
-
error = "Invalid CVEs applied: #{invalid_cve}"
|
118
|
-
puts error
|
119
|
-
log.log_message error
|
120
|
-
exit -1
|
121
|
-
end
|
122
|
-
|
123
|
-
data = data.map do |c|
|
124
|
-
if c.start_with? 'CVE-'
|
125
|
-
c
|
126
|
-
else
|
127
|
-
"CVE-#{c}"
|
128
|
-
end
|
129
|
-
end
|
130
|
-
else
|
131
|
-
data = nil
|
132
|
-
end
|
133
134
|
|
134
|
-
options[:filters] ||= {}
|
135
|
-
options[:filters][:cve] = data
|
136
|
-
end
|
137
|
-
=end
|
138
135
|
opts.on('-c', '--cvss-score CVSS',
|
139
136
|
'The minimum CVSS score to import') do |data|
|
140
137
|
|
@@ -175,6 +172,10 @@ module NexposeServiceNow
|
|
175
172
|
dates[0] = dates[0] + ' 00:00:00'
|
176
173
|
dates[1] = dates[1] + ' 23:59:59'
|
177
174
|
|
175
|
+
# Remove rogue '' in datetime format
|
176
|
+
dates[0] = dates[0].gsub("'","")
|
177
|
+
dates[1] = dates[1].gsub("'","")
|
178
|
+
|
178
179
|
# Check for valid dates and placeholders
|
179
180
|
dates.map! do |d|
|
180
181
|
if d =~ /Y{4}-M{1,2}-D{1,2}/i
|
@@ -193,15 +194,6 @@ module NexposeServiceNow
|
|
193
194
|
options[:filters][:date] = dates
|
194
195
|
end
|
195
196
|
|
196
|
-
|
197
|
-
opts.separator ''
|
198
|
-
opts.separator 'Last scan file modification options:'
|
199
|
-
|
200
|
-
opts.on('-e', '--errata DATA',
|
201
|
-
'Date or scan ID to be inserted in last scan file.') do |data|
|
202
|
-
options[:last_scan_data] = data
|
203
|
-
end
|
204
|
-
|
205
197
|
opts.separator ''
|
206
198
|
opts.separator 'Common options:'
|
207
199
|
|
@@ -212,8 +204,8 @@ module NexposeServiceNow
|
|
212
204
|
end
|
213
205
|
|
214
206
|
opt_parser.parse!(args)
|
215
|
-
options = self.validate_input(options)
|
216
207
|
options = self.get_env_settings(options)
|
208
|
+
options = self.validate_input(options)
|
217
209
|
options
|
218
210
|
end
|
219
211
|
|
@@ -221,8 +213,10 @@ module NexposeServiceNow
|
|
221
213
|
#Insert defaults. Some are mode-specific.
|
222
214
|
options[:output_dir] ||= '.'
|
223
215
|
options[:row_limit] ||= 9_999_999
|
216
|
+
options[:vuln_query_date] ||= '1985-01-01 00:00:00'
|
224
217
|
options[:id_type] ||= 'site'
|
225
|
-
options[:
|
218
|
+
options[:conn_type] ||= :nexpose_console
|
219
|
+
options[:nexpose_ids] ||= {}
|
226
220
|
options[:filters] ||= {}
|
227
221
|
|
228
222
|
options[:query] = 'latest_scans' if options[:mode] == 'latest_scans'
|
@@ -233,36 +227,47 @@ module NexposeServiceNow
|
|
233
227
|
options[:mode] == 'latest_scans'
|
234
228
|
end
|
235
229
|
|
230
|
+
options[:port] ||= if options[:conn_type].equal? :nexpose_console
|
231
|
+
'3780'
|
232
|
+
else
|
233
|
+
'5432'
|
234
|
+
end
|
235
|
+
|
236
|
+
log = NexposeServiceNow::NxLogger.instance
|
237
|
+
|
236
238
|
if options[:mode].to_s == ''
|
237
|
-
log = NexposeServiceNow::NxLogger.instance
|
238
239
|
log.log_message('Script was called without mode.')
|
239
240
|
puts 'No mode selected. Use -h to see command line options.'
|
240
241
|
exit -1
|
241
242
|
end
|
242
243
|
|
244
|
+
#Only need to check these if a query is being performed
|
245
|
+
return options unless options[:gen_report]
|
246
|
+
|
247
|
+
REQUIRED_OPTIONS.each do |setting|
|
248
|
+
if options[setting].nil?
|
249
|
+
error = "Option #{setting} wasn't supplied."
|
250
|
+
log.log_error_message error
|
251
|
+
$stderr.puts "ERROR: #{error}"
|
252
|
+
exit -1
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
243
256
|
options
|
244
257
|
end
|
245
258
|
|
246
259
|
def self.get_env_settings(options)
|
247
260
|
#Only need these if a query is being performed
|
248
|
-
return options
|
261
|
+
return options unless options[:gen_report]
|
249
262
|
|
250
263
|
log = NexposeServiceNow::NxLogger.instance
|
251
264
|
log.log_message 'Retrieving environment variables.'
|
252
265
|
|
253
266
|
# Retrieve environment variable settings
|
254
|
-
|
267
|
+
REQUIRED_OPTIONS.each do |setting|
|
255
268
|
option = "nexpose_#{setting}"
|
256
|
-
|
257
|
-
|
258
|
-
options[sym] ||= setting
|
259
|
-
|
260
|
-
if options[sym].nil?
|
261
|
-
error = "Option #{sym} wasn't supplied."
|
262
|
-
log.log_error_message error
|
263
|
-
$stderr.puts "ERROR: #{error}"
|
264
|
-
exit -1
|
265
|
-
end
|
269
|
+
env_setting = ENV[option.upcase]
|
270
|
+
options[setting] ||= env_setting
|
266
271
|
end
|
267
272
|
|
268
273
|
options
|