dorothy2 0.0.1

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.
@@ -0,0 +1,468 @@
1
+ # Copyright (C) 2010-2013 marco riccardi.
2
+ # This file is part of Dorothy - http://www.honeynet.it/dorothy
3
+ # See the file 'LICENSE' for copying permission.
4
+
5
+ #To install on MacOSX
6
+ #Go to postgres website and download the OSX installer
7
+ #none:ruby-pg-0.7.9.2008.01.28 root# gem install ruby-pg -- --with-pg-config="/Library/PostgreSQL/9.0/bin/pg_config" --with-pgsql-lib=/Library/PostgreSQL/9.0/lib --with-pgsql-include=/Library/PostgreSQL/9.0/include
8
+ #
9
+ #To install on Debian
10
+ #apt-get install postgres-8.3
11
+ #apt-get install libpq-dev
12
+ #sudo gem install pg -- --with-pgsql-lib=/usr/lib/postgresql/8.3/lib/ --with-pg-config=/usr/bin/pg_config
13
+
14
+
15
+ #include Pcap
16
+
17
+ module DoroParser
18
+
19
+ class Mydns
20
+ def initialize(data)
21
+ raw = data.to_s.gsub(/(\000|\001|\002|\003|\004|\005|\006|\007|\008|\009|\010|\011|\012|\013|\014|\015|\016|\017|\018|\019|\020|\021\022|\023|\024|\025|\026|\027)/, '.')
22
+ @query = raw.grep(/([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}/){$&}
23
+ end
24
+
25
+ def query
26
+ return @query if @query
27
+ end
28
+
29
+ def query?
30
+ return true if @query
31
+ end
32
+
33
+ def dns_class
34
+ #todo
35
+ end
36
+
37
+ def type
38
+ #todo
39
+ end
40
+
41
+ def response
42
+ #todo
43
+ end
44
+ end
45
+
46
+
47
+ class Parser_old
48
+
49
+ def initialize(data)
50
+ noutf = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')
51
+ if data and data =~ /(USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG |KCIK |rssr )(.*)\n/
52
+ @irc = true
53
+ @command = $1
54
+ begin
55
+ #@command2 = noutf.iconv($2).gsub(/"|'|\\/, "-")
56
+ rescue
57
+ @command2 = "null"
58
+ end
59
+ elsif data =~ /from\W*([0-9]{1,3}(\.[0-9]{1,3}){3}).*by\W*(([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}).*From:.*<(\S+)>\W*To:.*<(\S+)>.*Subject:(.*)\W*Date/m
60
+ @mail = true
61
+ @fromip = $1
62
+ @by =$3
63
+ @from = $6
64
+ @to = $7
65
+ @subj = $8.chomp.lstrip
66
+
67
+
68
+ elsif data and data =~ /^(GET|POST)\s+(\S+)/
69
+ @http = true
70
+ @method = $1
71
+ @path = $2
72
+
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+
79
+ class Parser
80
+ attr_reader :service
81
+
82
+ def self.guess(a)
83
+
84
+ if a =~ /USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG/
85
+ t = IRC.new a
86
+ elsif a =~ /from(.*)From(.*)Subject/m
87
+ t = SMTP.init a
88
+ elsif a =~ /(\d*)(.*)\n/
89
+ t = SMTP.new a
90
+ end
91
+
92
+ return t
93
+ end
94
+
95
+
96
+
97
+
98
+ class IRC
99
+ attr_reader :command
100
+ attr_reader :content
101
+
102
+
103
+ def initialize(data)
104
+ if data =~ /(USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG )(.*)\n/
105
+ noutf = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')
106
+ @command = $1
107
+ @content = noutf.iconv($2).gsub(/"|'|\\/, "-") #xcode bug ->>> ")
108
+ end
109
+ #return true if !@command.nil?
110
+ return self
111
+ end
112
+
113
+
114
+
115
+ end
116
+
117
+
118
+
119
+ class SMTP
120
+ #todo make a initialize class
121
+ attr_reader :hcmd
122
+ attr_reader :hcont
123
+ attr_accessor :body
124
+ attr_accessor :rdata
125
+
126
+ def self.body(data)
127
+
128
+ email = TMail::Mail.parse(data)
129
+ return email
130
+
131
+ end
132
+
133
+ def self.header?(data)
134
+
135
+ if data =~ /(MAIL FROM: |EHLO |HELO |TO: |RSET)(.*)\n/
136
+ return true
137
+ end
138
+
139
+ end
140
+
141
+ def self.hasbody?(data)
142
+
143
+ if data =~ /from(.*)From(.*)Subject/m
144
+ return true
145
+ end
146
+
147
+ end
148
+
149
+ def self.response(data)
150
+
151
+ if data =~ /(\d*)(.*)\n/
152
+ rcode = $1
153
+ rcont = $2
154
+ return rcode, rcont
155
+ end
156
+
157
+ end
158
+
159
+ def initialize a
160
+
161
+ if a =~ /(MAIL FROM: |EHLO |HELO |TO: |RSET)(.*)\n/
162
+ @hcmd = $1
163
+ @hcont = $2
164
+ end
165
+
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+
172
+ class Geoinfo
173
+ attr_reader :updated
174
+ attr_reader :country
175
+ attr_reader :coord
176
+ attr_reader :asn
177
+ attr_reader :city
178
+ attr_reader :updated
179
+
180
+
181
+ @updated = "null"
182
+ LOCALNET = "10.10.10.0/24"
183
+
184
+
185
+
186
+ def initialize(ip)
187
+ noutf = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')
188
+ @updated = 'null' #TODO take the creation file date of the .dat archive
189
+
190
+ #year = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$2}
191
+ #month = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$3}
192
+ #day = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$4}
193
+ #@updated = year.to_s + "/" + month.to_s + "/" + day.to_s
194
+ localnetwork = IPAddr.new(LOCALNET)
195
+
196
+ if !localnetwork.include?(ip)
197
+
198
+ begin
199
+
200
+ geoip = GeoIP.new(GEOIP)
201
+ geoasn = GeoIP.new(GEPASN)
202
+
203
+ if geoip.country(ip)
204
+ @city = noutf.iconv(geoip.country(ip).city_name).gsub(/"|'|\\/, "-") #xcode bug ->>> ")
205
+ @country = geoip.country(ip).country_code2
206
+ @coord = geoip.country(ip).latitude.to_s.gsub(/\(|\)/,'') + "," + geoip.country(ip).longitude.to_s.gsub(/\(|\)/,'')
207
+
208
+ else
209
+
210
+ @city, @country, @coord = "null", "null", "null"
211
+
212
+ end
213
+
214
+ @asn = (geoasn.asn(ip) ? geoasn.asn(ip).as_num.to_s.grep(/\d+/){$&} : "null" )
215
+
216
+ rescue
217
+ LOGGER_PARSER.fatal "GEO", "Error while fetching GeoIP dat file for IP: " + ip
218
+ LOGGER_PARSER.fatal "GEO", "#{$!}"
219
+ @city, @country, @coord, @asn = "null", "null", "null", "null"
220
+ end
221
+
222
+ else
223
+ @city, @country, @coord, @asn = "null", "null", "null", "null"
224
+
225
+ end
226
+ end
227
+
228
+ end
229
+
230
+
231
+ class DoroHttp
232
+ attr_reader :contype
233
+ attr_reader :method
234
+ attr_reader :uri
235
+ attr_reader :ssl
236
+ attr_reader :size
237
+ attr_accessor :data
238
+
239
+ def initialize(flowdeep)
240
+ @data = 'null'
241
+ @method = (flowdeep.values('http.request.method')[0] ? flowdeep.values('http.request.method')[0].value : 'null')
242
+ @ssl = false #TODO
243
+ @size = (flowdeep.values('http.content.length')[0] ? flowdeep.values('http.content.length')[0].value : 'null')
244
+ @uri = (flowdeep.values('http.request.uri')[0] ? flowdeep.values('http.request.uri')[0].value : 'null' )
245
+ @contype = (flowdeep.values('http.content.type')[0] ? flowdeep.values('http.content.type')[0].value : 'null')
246
+ end
247
+
248
+ end
249
+
250
+ class DoroDNS
251
+ attr_accessor :dns
252
+ attr_accessor :ttl
253
+ attr_accessor :name
254
+ attr_accessor :type
255
+ attr_accessor :type_i
256
+ attr_accessor :cls
257
+ attr_accessor :cls_i
258
+ attr_accessor :address
259
+ attr_accessor :data
260
+
261
+ def initialize(c)
262
+ @dns = Net::DNS::Packet::parse(c)
263
+ if qry? #is a query
264
+ q = @dns.question.first
265
+ @cls_i = q.qClass #Net::DNS::Question.parse(c[12..offset]).qClass
266
+ @name = q.qName
267
+ @type_i = q.qType
268
+ @ttl = 'null'
269
+ @address = 'null'
270
+ @data = 'null'
271
+
272
+ elsif !@dns.answer.empty? #contain an asnwer
273
+ #TODO only the first answer is parsed
274
+ a = @dns.answer.each.first
275
+ @ttl = a.ttl
276
+ @name = a.name
277
+
278
+ @type = a.type
279
+ @type_i = Net::DNS::RR::Types.new @type
280
+
281
+
282
+ @cls = a.cls
283
+ @cls_i = Net::DNS::RR::Classes.new @cls
284
+
285
+
286
+ case @type
287
+
288
+ when "A"
289
+ @address = @dns.answer.each.first.address
290
+ @data = 'null'
291
+
292
+ when "AAAA"
293
+ @address = @dns.answer.each.first.address
294
+ @data = 'null'
295
+
296
+ when "MX" then
297
+
298
+ @data = @dns.answer.each.first.exchange
299
+ @address = 'null'
300
+
301
+ when "CNAME" then
302
+
303
+ @data = @dns.answer.each.first.cname
304
+ @address = 'null'
305
+
306
+ else
307
+
308
+ @address = 'null'
309
+ @data = 'null'
310
+
311
+ end
312
+ end
313
+
314
+ end
315
+
316
+
317
+ def qry?
318
+ @dns.answer.empty?
319
+ end
320
+
321
+ end
322
+
323
+ class DoroFile
324
+ attr_accessor :sha2
325
+ attr_accessor :cont
326
+ attr_reader :path
327
+ attr_reader :date
328
+ attr_accessor :size
329
+
330
+ def initialize(hash)
331
+ repo = "./downloads"
332
+ @path = "#{repo}/#{hash}.exe"
333
+ @date = Time.new.strftime("%m/%d/%y %H:%M:%S")
334
+ end
335
+
336
+ def self.sha2(content)
337
+ @sha2 = Digest::SHA2.new
338
+ @sha2 << content
339
+ end
340
+ end
341
+
342
+
343
+ class Doroxtractr < Mu::Xtractr #PcaprLocal::Xtractr.new
344
+
345
+ #def lol(id)
346
+ # self.create "http://172.20.250.13:8080/pcaps/1/pcap/#{id}"
347
+ #end
348
+
349
+
350
+ def summaryhttp(fast=0, v=0)
351
+ ids = []
352
+ self.flows('flow.service:HTTP').each { |flow|
353
+ method = self.flows("flow.id:#{flow.id}").values('http.request.method')[0].value
354
+ if fast == 0
355
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{method} - #{flow.stream.flow.contents.first.body.length}"
356
+ else
357
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{method}"
358
+ end
359
+ ids.push(flow.id)
360
+ }
361
+ return ids
362
+ end
363
+
364
+ def flowinfo(id)
365
+ f = self.flows("flow.id:#{id}").first.inspect
366
+ f << self.flows("flow.id:#{id}").first.time.to_s
367
+ return f
368
+ end
369
+
370
+ #Find the HTTP requests made by the host (Zeus uses it to send stolen data to its dropzone)
371
+ #The biggest post refers to the STATS one (by default is sent every 20 min)
372
+ #the smallest post refers to the LOG one (by default is sent every minute)
373
+ #the biggest GET refers to the Configuration file downloaded by the Zeus C&C
374
+ def findzeusdata(re, type, cc='192.168.10.3')
375
+ flowids = {}
376
+ self.flows("flow.service:HTTP flow.dst: #{cc}").each do |flow|
377
+ method = self.flows("flow.id:#{flow.id}").values('http.request.method')[0].value
378
+ flowids[flow.id] = flow.stream.flow.contents.first.body.length if method =~ /#{Regexp.escape(re)}/
379
+ end
380
+ if type == "ping"
381
+ return flowids.sort {|a,b| a[1]<=>b[1]}.first
382
+ elsif type == "stat" || type == "conf"
383
+ return flowids.sort {|a,b| a[1]<=>b[1]}.last
384
+ else
385
+ puts "Error, choose one argument from: ping, stat, conf"
386
+ return 1
387
+ end
388
+ end
389
+
390
+
391
+ #Find the HTTP GET request made by the host (Zeus uses it to send stolen data to its dropzone)
392
+ #Is the first get request made to the C&C [!?]
393
+ def findconfget
394
+ self.flows("flow.service:HTTP flow.dst: #{cc}")
395
+ end
396
+
397
+ def summaryhttpmethod(re, fast=0)
398
+ self.flows('flow.service:HTTP').each { |flow|
399
+ flowdeep = self.flows("flow.id:#{flow.id}")
400
+ if fast == 0
401
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{flow.stream.flow.contents.first.body.length}" if flowdeep.values('http.request.method')[0] && flowdeep.values('http.request.method')[0].value =~ /#{Regexp.escape(re)}/
402
+ else
403
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address}" if flowdeep.values('http.request.method')[0] && flowdeep.values('http.request.method')[0].value =~ /#{Regexp.escape(re)}/
404
+ end
405
+ }
406
+ end
407
+
408
+ def flowsummary(verbose=0)
409
+ self.flows.each { |flow|
410
+ flowdeep = self.flows("flow.id:#{flow.id}")
411
+ if verbose == 1
412
+ puts "#{flow.id}: #{flow.time} : #{flow.src.address} > #{flow.dst.address} - #{flow.packets} - #{flow.bytes} - #{flow.duration} - #{flow.title}"
413
+ else
414
+ puts "| #{flow.id}: #{flow.src.address} > #{flow.service.name} > #{flow.dst.address} : #{flow.title}"
415
+ end
416
+ }
417
+ end
418
+
419
+
420
+ def summaryport(port)
421
+ self.flows("flow.dport:#{port}").each do |f|
422
+ f.contents.each do |c|
423
+ puts "#{f.id}: #{flow.id} #{flow.src.address} > #{flow.dst.address} #{f.title} : #{c.body.length}"
424
+ end
425
+ end
426
+ end
427
+
428
+ def flowgrep(id, re)
429
+ self.flows("flow.id:#{id}").each do |f|
430
+ @t = false
431
+ f.stream.each do |mex|
432
+ if mex.bytes =~ /#{re}/
433
+ puts "#{f.id}: > #{f.dst.address} - #{$1}"
434
+ @t = true
435
+ end
436
+ end
437
+ end
438
+ return @t
439
+ end
440
+
441
+ def streamdata(id)
442
+ data = []
443
+ self.flows("flow.id:#{id}").each do |f|
444
+ f.stream.each do |mex|
445
+ t = [mex.bytes, mex.dir]
446
+ data.push t
447
+ end
448
+ end
449
+ return data
450
+ end
451
+
452
+
453
+
454
+
455
+ #Retrieve the content of a specified flow-ID
456
+ def flowcontent(id)
457
+ body = ""
458
+ self.flows("flow.id:#{id}").each do |flow|
459
+ flow.contents.each do |c|
460
+ body << c.body
461
+ end
462
+ end
463
+ return body
464
+ end
465
+
466
+ end
467
+
468
+ end
@@ -0,0 +1,223 @@
1
+ # Copyright (C) 2010-2013 marco riccardi.
2
+ # This file is part of Dorothy - http://www.honeynet.it/dorothy
3
+ # See the file 'LICENSE' for copying permission.
4
+
5
+ module Dorothy
6
+
7
+ module Util
8
+
9
+ extend self
10
+
11
+ def write(file, string)
12
+ File.open(file , 'w') {|f| f.write(string) }
13
+ end
14
+
15
+ def exists?(file)
16
+ File.exist?(file)
17
+ end
18
+
19
+ def init_db(force=false)
20
+ LOGGER.warn "DB", "The database is going to be initialized, all the data present will be lost. Continue?(write yes)"
21
+ answ = "yes"
22
+ answ = gets.chop unless force
23
+
24
+ if answ == "yes"
25
+ begin
26
+ #ugly, I know, but couldn't find a better and easier way..
27
+ raise 'An error occurred' unless system "psql -h #{DoroSettings.dorothive[:dbhost]} -U #{DoroSettings.dorothive[:dbuser]} -f #{DoroSettings.dorothive[:ddl]}"
28
+ LOGGER.info "DB", "Database correctly initialized."
29
+ rescue => e
30
+ LOGGER.error "DB", $!
31
+ LOGGER.debug "DB", e.inspect
32
+ end
33
+ else
34
+ LOGGER.error "DB", "Database untouched, quitting."
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ module Ssh
41
+
42
+ extend self
43
+
44
+ def download(host, user, pass, file, dest, port=22)
45
+ Net::SSH.start(host, user, :password => pass, :port =>port) do |ssh|
46
+ ssh.scp.download! file, dest
47
+ end
48
+ end
49
+ end
50
+
51
+ class Insertdb
52
+
53
+ def initialize
54
+ @db = PGconn.open(:host=> DoroSettings.dorothive[:dbhost], :dbname=>DoroSettings.dorothive[:dbname], :user=>DoroSettings.dorothive[:dbuser], :password=>DoroSettings.dorothive[:dbpass])
55
+ end
56
+
57
+ def begin_t
58
+ @db.exec("BEGIN")
59
+ end
60
+
61
+ def commit
62
+ @db.exec("COMMIT")
63
+ end
64
+
65
+ def status
66
+ @db.transaction_status
67
+ end
68
+
69
+ def close
70
+ @db.close
71
+ end
72
+
73
+ def rollback
74
+ LOGGER.error "DB", "DB ROLLBACK"
75
+ @db.exec("ROLLBACK")
76
+ end
77
+
78
+
79
+ def insert(table,values)
80
+ n = 1
81
+ @sqlstring = ""
82
+
83
+ values.each { |value|
84
+ if value == "default"
85
+ value1 = value
86
+ elsif value == "null"
87
+ value1 = value
88
+ elsif value == "lastval()"
89
+ value1 = value
90
+ elsif value =~ /currval/
91
+ value1 = value
92
+ else
93
+ value1 = "'#{value}'"
94
+ end
95
+ if n == values.size
96
+ @sqlstring << value1
97
+ elsif
98
+ @sqlstring << value1 + ","
99
+ end
100
+ n += 1
101
+ }
102
+ #p "Inserting in dorothy.#{table}:"
103
+ #p "#{@sqlstring}"
104
+
105
+ begin
106
+ @db.exec("INSERT into dorothy.#{table} values (#{@sqlstring})")
107
+ rescue => e
108
+ LOGGER.debug "DB", $!
109
+ LOGGER.debug "DB", e.inspect
110
+ #self.rollback
111
+ return false
112
+ #exit 1
113
+ end
114
+
115
+ #p "Insertion OK"
116
+
117
+ end
118
+
119
+ def raw_insert(table, data)
120
+ begin
121
+ @db.exec("INSERT into dorothy.#{table} values (#{data})")
122
+ rescue
123
+ LOGGER.error "DB", "#{$!}"
124
+ #self.rollback
125
+ return false
126
+ #exit 1
127
+ end
128
+ end
129
+
130
+ def select(table, column, value, column2=nil, value2=nil, column3=nil, value3=nil)
131
+ column2&&value2 ? ( column3&&value3 ? chk = @db.exec("SELECT * from dorothy.#{table} where #{column} = '#{value}' AND #{column2} = '#{value2}' AND #{column3} = '#{value3}' ") : chk = @db.exec("SELECT * from dorothy.#{table} where #{column} = '#{value}' AND #{column2} = '#{value2}'")) : chk = @db.exec("SELECT * from dorothy.#{table} where #{column} = '#{value}'")
132
+
133
+ #puts ".::WARNING #{value} already present in dorothy.#{table}".red.bold if chk
134
+ return chk
135
+ end
136
+
137
+ def get_anal_id
138
+ @db.exec("SELECT nextval('dorothy.analyses_id_seq')").first["nextval"].to_i
139
+ end
140
+
141
+ def self.escape_bytea(data)
142
+ escaped = PGconn.escape_bytea data
143
+ return escaped
144
+ end
145
+
146
+ def table_empty?(table)
147
+ @db.exec("SELECT CASE WHEN EXISTS (SELECT * FROM dorothy.#{table} LIMIT 1) THEN FALSE ELSE TRUE END").first["case"] == "t" ? true : false
148
+ end
149
+
150
+ def update_proto(role, ip)
151
+ @db.exec("UPDATE dorothy.host_roles set app_protocol = '#{proto}' where id = currval('connections_id_seq')")
152
+ end
153
+
154
+ def set_analyzed(hash)
155
+ @db.exec("UPDATE dorothy.traffic_dumps set parsed = true where hash = '#{hash}'")
156
+ end
157
+
158
+ def find_seq(seq)
159
+ @db.exec("SELECT currval('dorothy.#{seq}')")
160
+ end
161
+
162
+ def flush_table(table)
163
+ @db.exec("TRUNCATE dorothy.#{table} CASCADE")
164
+ end
165
+
166
+ def malware_list
167
+ malwares = []
168
+ @db.exec("SELECT samples.hash FROM dorothy.samples").each do |q|
169
+ malwares.push q
170
+ end
171
+ return malwares
172
+ end
173
+
174
+ def find_pcap
175
+ @pcaps = []
176
+ begin
177
+ @db.exec("SELECT traffic_dumps.hash, traffic_dumps.pcapr_id, traffic_dumps.size, traffic_dumps.binary, traffic_dumps.parsed, samples.md5 as \"sample\", analyses.date as \"date\" FROM dorothy.traffic_dumps, dorothy.samples, dorothy.analyses WHERE analyses.traffic_dump = traffic_dumps.hash AND analyses.sample = samples.hash AND traffic_dumps.parsed = false").each do |q|
178
+ @pcaps.push q
179
+ end
180
+ rescue
181
+ LOGGER.error "DB","Error while fetching traffic_dumps table\n " + $!
182
+ end
183
+
184
+ end
185
+
186
+ def find_vm
187
+ vm = @db.exec("SELECT id, hostname, ipaddress, username, password FROM dorothy.sandboxes where is_available is true").first
188
+ if vm.nil?
189
+ LOGGER.warn "DB","At this time there are no free VM available"
190
+ return false
191
+ else
192
+ @db.exec("UPDATE dorothy.sandboxes set is_available = false where id = '#{vm["id"]}'")
193
+ return vm["id"].to_i, vm["hostname"], vm["ipaddress"], vm["username"], vm["password"]
194
+ end
195
+ end
196
+
197
+ def free_vm(vmid)
198
+ r = @db.exec("SELECT hostname FROM dorothy.sandboxes where id = '#{vmid}' AND is_available is false")
199
+ if !r.first.nil? #check if the issued VM is already free
200
+ begin
201
+ @db.exec("UPDATE dorothy.sandboxes set is_available = true where id = '#{vmid}'")
202
+ LOGGER.info "DB", "VM #{vmid} succesfully released"
203
+ return true
204
+ rescue
205
+ LOGGER.error "DB", "An error occurred while releasing the VM"
206
+ LOGGER.debug "DB", $!
207
+ return false
208
+ end
209
+ else
210
+ LOGGER.warn "DB", "Dorothy is trying to release the VM #{vmid} that is already available!!"
211
+ return false
212
+ end
213
+ end
214
+
215
+ def vm_init
216
+ @db.exec("UPDATE dorothy.sandboxes set is_available = true")
217
+ LOGGER.debug "DB", "All VM are now available"
218
+ #TODO - revert them too?
219
+ end
220
+
221
+ end
222
+
223
+ end
@@ -0,0 +1,29 @@
1
+ module Process
2
+ # Supply daemon for pre ruby 1.9
3
+ # Adapted from lib/active_support/core_ext/process/daemon.rb
4
+ def self.daemon(nochdir = nil, noclose = nil)
5
+ exit! if fork # Parent exits, child continues.
6
+ Process.setsid # Become session leader.
7
+ exit! if fork # Zap session leader. See [1].
8
+
9
+ unless nochdir
10
+ Dir.chdir "/" # Release old working directory.
11
+ end
12
+
13
+ unless noclose
14
+ STDIN.reopen "/dev/null" # Free file descriptors and
15
+ STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
16
+ STDERR.reopen '/dev/null', 'a'
17
+ end
18
+
19
+ trap("TERM") { exit }
20
+
21
+ return 0
22
+
23
+ end unless self.respond_to? :daemon
24
+ end
25
+
26
+ module Dorothy
27
+ ROOT = File.expand_path(File.dirname(File.dirname(__FILE__)))
28
+ $: << ROOT
29
+ end
@@ -0,0 +1,3 @@
1
+ module Dorothy2
2
+ VERSION = "0.0.1"
3
+ end