rcs-common 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)