rodsec 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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rvmrc +60 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +15 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/examples/body.yml +5 -0
- data/examples/modsec.ru +40 -0
- data/ext/msc_intervention/extconf.rb +8 -0
- data/ext/msc_intervention/msc_intervention.cc +48 -0
- data/lib/rodsec.rb +18 -0
- data/lib/rodsec/modsec.rb +60 -0
- data/lib/rodsec/rack.rb +152 -0
- data/lib/rodsec/read_config.rb +80 -0
- data/lib/rodsec/rule_set.rb +69 -0
- data/lib/rodsec/string_pointers.rb +10 -0
- data/lib/rodsec/transaction.rb +168 -0
- data/lib/rodsec/version.rb +3 -0
- data/lib/rodsec/wrapper.rb +136 -0
- data/rodsec.gemspec +40 -0
- data/spec/config/crs-setup.conf +787 -0
- data/spec/config/modsecurity.conf +264 -0
- metadata +156 -0
data/lib/rodsec/rack.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require_relative '../rodsec.rb'
|
2
|
+
|
3
|
+
module Rodsec
|
4
|
+
# Thanks to rack-contrib/deflect for the basic idea, and some of the docs.
|
5
|
+
class Rack
|
6
|
+
# === Required Options:
|
7
|
+
#
|
8
|
+
# :config Proc, or the directory containing the ModSecurity config files
|
9
|
+
# modsecurity.conf and crs-setup.conf. If it's a Proc, which
|
10
|
+
# must return a Rodsec::Ruleset instance containing all the
|
11
|
+
# rules you want.
|
12
|
+
#
|
13
|
+
# === Optional Options:
|
14
|
+
#
|
15
|
+
# :rules the directory containing the ModSecurity rules files.
|
16
|
+
# Defaults to ${config}/rules. Ignored if you pass a proc to config
|
17
|
+
#
|
18
|
+
# :logger must respond_to #puts which takes a string. Defaults to a StringIO at #logger
|
19
|
+
#
|
20
|
+
# :log_blk a callable that takes |tag,string| Defaults to sending
|
21
|
+
# only the string to logger. The ModSecurity logs are highly
|
22
|
+
# structured and you might want to parse them, so the tag
|
23
|
+
# helps disambiguate the source of the logs.
|
24
|
+
#
|
25
|
+
# ? :msi_blk called with [status, headers, body] if there's an intervention from ModSecurity.
|
26
|
+
#
|
27
|
+
#
|
28
|
+
# === Examples:
|
29
|
+
#
|
30
|
+
# use Rodsec::Rack, config: 'your_config_path', log: (mylogger = StringIO.new)
|
31
|
+
# use Rodsec::Rack, config: 'your_config_path', log_blk: -> src_class, str { my_funky_parse_msi_to_hash str }
|
32
|
+
def initialize app, config:, rules: nil, logger: nil, log_blk: nil
|
33
|
+
@app = app
|
34
|
+
|
35
|
+
@log_blk = log_blk || -> _tag, str{self.logger.puts str}
|
36
|
+
@msc = Rodsec::Modsec.new{|tag,str| @log_blk.call tag, str}
|
37
|
+
|
38
|
+
@logger = logger || StringIO.new
|
39
|
+
|
40
|
+
@log_blk.call self.class, "#{self.class} starting with #{@msc.version_info}"
|
41
|
+
|
42
|
+
set_rules config, rules
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :log_blk, :logger
|
46
|
+
|
47
|
+
include ReadConfig
|
48
|
+
|
49
|
+
protected def set_rules config, rules
|
50
|
+
case config
|
51
|
+
when Proc
|
52
|
+
@rules = config.call
|
53
|
+
else
|
54
|
+
@rules = read_config config, rules, &log_blk
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
REQUEST_URI = 'REQUEST_URI'.freeze
|
59
|
+
REMOTE_HOST = 'REMOTE_HOST'.freeze
|
60
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
61
|
+
SERVER_NAME = 'SERVER_NAME'.freeze
|
62
|
+
HTTP_HOST = 'HTTP_HOST'.freeze
|
63
|
+
SERVER_PORT = 'SERVER_PORT'.freeze
|
64
|
+
HTTP_VERSION = 'HTTP_VERSION'.freeze
|
65
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
66
|
+
SLASH = '/'.freeze
|
67
|
+
HTTP_HEADER_RX = /HTTP_(.*)|(CONTENT_.*)/.freeze
|
68
|
+
DASH = '-'.freeze
|
69
|
+
UNDERSCORE = '_'.freeze
|
70
|
+
EMPTY = String.new.freeze
|
71
|
+
|
72
|
+
RACK_INPUT = 'rack.input'.freeze
|
73
|
+
|
74
|
+
def call env
|
75
|
+
txn = Rodsec::Transaction.new @msc, @rules, txn_log_tag: env[REQUEST_URI]
|
76
|
+
|
77
|
+
################
|
78
|
+
# incoming
|
79
|
+
|
80
|
+
# uri! scope for variables
|
81
|
+
lambda do
|
82
|
+
remote_addr = env[REMOTE_HOST] || env[REMOTE_ADDR]
|
83
|
+
server_addr = env[HTTP_HOST] || env[SERVER_NAME]
|
84
|
+
txn.connection! remote_addr, 0, server_addr, (env[SERVER_PORT] || 0)
|
85
|
+
|
86
|
+
_, version = env[HTTP_VERSION]&.split(SLASH)
|
87
|
+
|
88
|
+
txn.uri! env[REQUEST_URI], env[REQUEST_METHOD], version
|
89
|
+
end.call
|
90
|
+
|
91
|
+
# request_headers! - another scope for variables
|
92
|
+
lambda do
|
93
|
+
http_headers = env.map do |key,val|
|
94
|
+
key =~ HTTP_HEADER_RX or next
|
95
|
+
header_name = $1 || $2
|
96
|
+
dashified = header_name.split(UNDERSCORE).map(&:capitalize).join(DASH)
|
97
|
+
[dashified, val]
|
98
|
+
end.compact.to_h
|
99
|
+
|
100
|
+
txn.request_headers! http_headers
|
101
|
+
end.call
|
102
|
+
|
103
|
+
# request_body! MUST be called (even with an empty body is fine),
|
104
|
+
# otherwise ModSecurity never triggers the rules, even though ModSecurity
|
105
|
+
# can detect something dodgy in the headers. That needs what they call
|
106
|
+
# self-contained mode.
|
107
|
+
env[RACK_INPUT].tap do |rack_input|
|
108
|
+
# ruby-2.3 syntax :-|
|
109
|
+
begin
|
110
|
+
# What about a DOS from a very large body?
|
111
|
+
#
|
112
|
+
# Rack spec says rack.input must be rewindable at the http-server
|
113
|
+
# level, so it's all in memory by now anyway, nothing we can do to
|
114
|
+
# affect that here.
|
115
|
+
txn.request_body! rack_input
|
116
|
+
ensure
|
117
|
+
# Have to rewind input, otherwise other rack apps can't get the content
|
118
|
+
rack_input.rewind
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
################
|
123
|
+
# rack chain
|
124
|
+
status, headers, body = @app.call env
|
125
|
+
|
126
|
+
################
|
127
|
+
# outgoing
|
128
|
+
txn.response_headers! status, env[HTTP_VERSION], headers
|
129
|
+
|
130
|
+
# TODO handle hijacking? Not sure.
|
131
|
+
# body is an Enumerable, which response_body! will handle
|
132
|
+
txn.response_body! body
|
133
|
+
|
134
|
+
# Logging. From ModSecurity's point of view this could be in a separate
|
135
|
+
# thread. Dunno how rack will handle that though. Also, there's no way to
|
136
|
+
# wait for a thread doing that logging. So it would have to be spawned and
|
137
|
+
# then left to die. Alone. In the rain.
|
138
|
+
txn.logging
|
139
|
+
|
140
|
+
# all ok
|
141
|
+
return status, headers, body
|
142
|
+
|
143
|
+
rescue Rodsec::Intervention => iex
|
144
|
+
log_blk.call :intervention, iex.msi.log
|
145
|
+
# rack interface specification says we have to call close on the body, if
|
146
|
+
# it responds to close
|
147
|
+
body.respond_to?(:close) && body.close
|
148
|
+
# Intervention!
|
149
|
+
return iex.msi.status, {'Content-Type' => 'text/plain'}, [ ::Rack::Utils::HTTP_STATUS_CODES[iex.msi.status] ].compact
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Rodsec
|
2
|
+
module ReadConfig
|
3
|
+
# log_blk takes a tag and a string
|
4
|
+
module_function def read_config config, rules, &log_blk
|
5
|
+
config_dir = Pathname config
|
6
|
+
rules_dir = Pathname(rules || config_dir + 'rules')
|
7
|
+
|
8
|
+
# NOTE the first two config files MUST be loaded before the rules files
|
9
|
+
config_rules = RuleSet.new
|
10
|
+
config_rules.add_file config_dir + 'modsecurity.conf'
|
11
|
+
config_rules.add_file config_dir + 'crs-setup.conf'
|
12
|
+
|
13
|
+
# Now load the rules files
|
14
|
+
rules_files = rules_dir.children.select{|p| p.to_s =~ /.*conf$/}.sort
|
15
|
+
|
16
|
+
# merge rules files.
|
17
|
+
rules_files.reduce config_rules do |ax, fn|
|
18
|
+
# ruby 2.3.x syntax :-|
|
19
|
+
begin
|
20
|
+
log_blk&.call self.class, "loading rules file: #{fn}"
|
21
|
+
rules = RuleSet.new tag: fn
|
22
|
+
rules.add_file fn
|
23
|
+
ax.merge rules
|
24
|
+
rescue
|
25
|
+
log_blk&.call $!.class, "error loading rules file: #{$!}"
|
26
|
+
ax
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Hacky Workaround for the bug that's tickled by merging Rules.
|
32
|
+
#
|
33
|
+
# What we do is read all the files separately. Check each one to see that it
|
34
|
+
# has no syntax errors. If that works, append the file contents to one giant
|
35
|
+
# string containing all the rule files. When we've read all the rule files,
|
36
|
+
# create one RuleSet from the combined string. That way we don't need to
|
37
|
+
# merge rulesets.
|
38
|
+
module_function def read_combined_config config, rules, &log_blk
|
39
|
+
# part of the hacky workaround, so it's self-contained when we remove it.
|
40
|
+
require 'stringio'
|
41
|
+
config_dir = Pathname config
|
42
|
+
rules_dir = Pathname(rules || config_dir + 'rules')
|
43
|
+
|
44
|
+
# NOTE the first two config files MUST be loaded before the rules files
|
45
|
+
files = [(config_dir + 'modsecurity.conf'), (config_dir + 'crs-setup.conf')]
|
46
|
+
|
47
|
+
# Now add the rules files
|
48
|
+
files.concat rules_dir.children.select{|p| p.to_s =~ /.*conf$/}.sort
|
49
|
+
|
50
|
+
# merge rules files.
|
51
|
+
combined_rules = files.each_with_object StringIO.new do |fn, sio|
|
52
|
+
begin
|
53
|
+
log_blk&.call self.class, "loading rules file: #{fn}"
|
54
|
+
|
55
|
+
# syntax check rule set
|
56
|
+
RuleSet.new.add_file fn.to_s
|
57
|
+
|
58
|
+
File.open fn do |io| IO.copy_stream io, sio end
|
59
|
+
rescue
|
60
|
+
log_blk&.call $!.class, "error loading rules file: #{$!}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# make sure the rules can access their *.data files - we lose the file
|
65
|
+
# location information when we use this approach.
|
66
|
+
save_dir = Dir.pwd
|
67
|
+
Dir.chdir rules_dir
|
68
|
+
|
69
|
+
# add the combined rules
|
70
|
+
log_blk&.call self.class, 'loading combined rules'
|
71
|
+
p size: combined_rules.string.length
|
72
|
+
rules = RuleSet.new
|
73
|
+
rules.add combined_rules.string
|
74
|
+
rules
|
75
|
+
ensure
|
76
|
+
# restore original directory
|
77
|
+
save_dir and Dir.chdir save_dir
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
require_relative 'wrapper'
|
4
|
+
require_relative 'string_pointers'
|
5
|
+
|
6
|
+
module Rodsec
|
7
|
+
class RuleSet
|
8
|
+
def initialize tag: nil
|
9
|
+
@tag = tag
|
10
|
+
|
11
|
+
@rules_ptr = Wrapper.msc_create_rules_set
|
12
|
+
@rules_ptr.free = Wrapper['msc_rules_cleanup']
|
13
|
+
|
14
|
+
# just mirroring the c-api, not sure if it's actually useful
|
15
|
+
@rule_count = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
# srsly, don't mess with this
|
19
|
+
attr_reader :rules_ptr
|
20
|
+
|
21
|
+
attr_reader :tag, :rule_count
|
22
|
+
|
23
|
+
include StringPointers
|
24
|
+
|
25
|
+
# add rules from the given file
|
26
|
+
# return number of rules added? I think?
|
27
|
+
def add_file conf_pathname
|
28
|
+
conf_pathname = Pathname conf_pathname
|
29
|
+
err = Fiddle::Pointer[0]
|
30
|
+
rv = Wrapper.msc_rules_add_file rules_ptr, (strptr conf_pathname.realpath.to_s), err.ref
|
31
|
+
|
32
|
+
raise Error, [conf_pathname, err.to_s] if rv < 0
|
33
|
+
@rule_count += rv
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# dump rules to stdout. No way to redirect that, from what I can see.
|
38
|
+
def dump
|
39
|
+
Wrapper.msc_rules_dump rules_ptr
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def add rules_text
|
44
|
+
err = Fiddle::Pointer[0]
|
45
|
+
rv = Wrapper.msc_rules_add rules_ptr, (strptr rules_text.to_s), err.ref
|
46
|
+
raise Error, err.to_s if rv < 0
|
47
|
+
@rule_count += rv
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_url key, url
|
52
|
+
err = Fiddle::Pointer[0]
|
53
|
+
rv = Wrapper.msc_rules_add_remote rules_ptr, (strptr key), (strptr uri), err.ref
|
54
|
+
raise Error, err.to_s if rv < 0
|
55
|
+
@rule_count += rv
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# merge other rules with self
|
60
|
+
def merge other
|
61
|
+
raise "must be a #{self.class.name}" unless self.class === other
|
62
|
+
err = Fiddle::Pointer[0]
|
63
|
+
rv = Wrapper.msc_rules_merge rules_ptr, other.rules_ptr, err.ref
|
64
|
+
@rule_count += rv
|
65
|
+
raise Error, err.to_s if rv < 0
|
66
|
+
self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require_relative 'wrapper'
|
2
|
+
require_relative 'string_pointers'
|
3
|
+
|
4
|
+
module Rodsec
|
5
|
+
class Transaction
|
6
|
+
# txn_log_tag must be convertible to a string. Defaults to self.object_id.to_s
|
7
|
+
# it shows up in as the first argument passed to Modsec's log_blk.
|
8
|
+
def initialize msc, ruleset, txn_log_tag: nil
|
9
|
+
raise Error, "msc must be a #{Modsec}" unless Modsec === msc
|
10
|
+
raise Error, "ruleset must be a #{RuleSet}" unless RuleSet === ruleset
|
11
|
+
|
12
|
+
@msc, @ruleset = msc, ruleset
|
13
|
+
@txn_log_tag = Fiddle::Pointer[(txn_log_tag || object_id).to_s]
|
14
|
+
@txn_ptr = Wrapper.msc_new_transaction msc.msc_ptr, ruleset.rules_ptr, @txn_log_tag
|
15
|
+
@txn_ptr.free = Wrapper['msc_transaction_cleanup']
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :txn_ptr
|
19
|
+
|
20
|
+
attr_reader :msc, :ruleset
|
21
|
+
|
22
|
+
include StringPointers
|
23
|
+
|
24
|
+
# Raise an Intervention(ModSecurityIntervention) if necessary, or return self.
|
25
|
+
#
|
26
|
+
# ModSecurity will only populate the intervention structure if it detects
|
27
|
+
# something 'disruptive' in the SecRules.
|
28
|
+
protected def intervention!
|
29
|
+
# Check for Intervention
|
30
|
+
msi = Wrapper::ModSecurityIntervention.new Wrapper.msc_new_intervention
|
31
|
+
rv = Wrapper.msc_intervention txn_ptr, msi
|
32
|
+
raise Intervention, msi if rv > 0
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
##################################
|
37
|
+
# Phase CONNECTION / SecRules 0
|
38
|
+
# check for intervention afterwards
|
39
|
+
def connection! client_host, client_port, server_host, server_port
|
40
|
+
rv = Wrapper.msc_process_connection \
|
41
|
+
txn_ptr,
|
42
|
+
(strptr client_host), (Integer client_port),
|
43
|
+
(strptr server_host), (Integer server_port)
|
44
|
+
|
45
|
+
rv == 1 or raise Error, "msc_process_connection failed for #{[client_host, client_port, server_host, server_port].inspect}"
|
46
|
+
|
47
|
+
intervention!
|
48
|
+
end
|
49
|
+
|
50
|
+
##################################
|
51
|
+
# Phase URI / 1.5
|
52
|
+
# check for intervention afterwards
|
53
|
+
# verb is GET POST etc
|
54
|
+
# http_version is '1.1', '1.2' etc
|
55
|
+
def uri! uri, verb, http_version
|
56
|
+
rv = Wrapper.msc_process_uri txn_ptr, (strptr uri), (strptr verb), (strptr http_version)
|
57
|
+
rv == 1 or raise Error "msc_process_uri failed for #{[uri, verb, http_version].inspect}"
|
58
|
+
|
59
|
+
intervention!
|
60
|
+
end
|
61
|
+
|
62
|
+
##################################
|
63
|
+
# Phase REQUEST_HEADERS. SecRules 1
|
64
|
+
def request_headers! header_hash
|
65
|
+
errors = header_hash.each_with_object [] do |(key, val), errors|
|
66
|
+
key = key.to_s; val = val.to_s
|
67
|
+
rv = Wrapper.msc_add_n_request_header txn_ptr, (strptr key), key.bytesize, (strptr val), val.bytesize
|
68
|
+
rv == 1 or errors << "msc_add_n_request_header failed adding #{[key,val].inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
raise Error errors if errors.any?
|
72
|
+
|
73
|
+
rv = Wrapper.msc_process_request_headers txn_ptr
|
74
|
+
rv == 1 or raise "msc_process_request_headers failed"
|
75
|
+
|
76
|
+
intervention!
|
77
|
+
end
|
78
|
+
|
79
|
+
protected def enum_of_body body
|
80
|
+
case
|
81
|
+
when NilClass === body
|
82
|
+
['']
|
83
|
+
when String === body
|
84
|
+
[body]
|
85
|
+
when body.respond_to?(:each)
|
86
|
+
body
|
87
|
+
else
|
88
|
+
raise "dunno about #{body}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##################################
|
93
|
+
# Phase REQUEST_BODY. SecRules 2
|
94
|
+
#
|
95
|
+
# body can be a String, or an Enumerable of strings
|
96
|
+
def request_body! body
|
97
|
+
enum_of_body(body).each do |body_part|
|
98
|
+
body_part = body_part.to_s
|
99
|
+
rv = Wrapper.msc_append_request_body txn_ptr, (strptr body_part), body_part.bytesize
|
100
|
+
rv == 1 or raise Error, "msc_append_request_body failed"
|
101
|
+
end
|
102
|
+
|
103
|
+
# This MUST be called, otherwise rules aren't triggered.
|
104
|
+
rv = Wrapper.msc_process_request_body txn_ptr
|
105
|
+
rv == 1 or raise Error, "msc_process_request_body failed"
|
106
|
+
|
107
|
+
intervention!
|
108
|
+
end
|
109
|
+
|
110
|
+
# This is probably only used when appending a body in chunks. We don't use it.
|
111
|
+
# extern 'size_t msc_get_request_body_length(Transaction *transaction)'
|
112
|
+
|
113
|
+
##################################
|
114
|
+
# Phase RESPONSE_HEADERS. SecRules 3
|
115
|
+
# http_status_code is one of the 200, 401, 404 etc codes
|
116
|
+
# http_with_version seems to be things like 'HTTP 1.2', not entirely sure.
|
117
|
+
def response_headers! http_status_code = 200, http_with_version = 'HTTP 1.1', header_hash
|
118
|
+
errors = header_hash.each_with_object [] do |(key, val), errors|
|
119
|
+
key = key.to_s; val = val.to_s
|
120
|
+
rv = Wrapper.msc_add_n_response_header txn_ptr, (strptr key), key.bytesize, (strptr val), val.bytesize
|
121
|
+
rv == 1 or errors << "msc_add_n_response_header failed adding #{[key,val].inspect}"
|
122
|
+
end
|
123
|
+
|
124
|
+
raise Error, errors if errors.any?
|
125
|
+
|
126
|
+
rv = Wrapper.msc_process_response_headers txn_ptr, (Integer http_status_code), (strptr http_with_version)
|
127
|
+
rv == 1 or raise "msc_process_response_headers failed"
|
128
|
+
|
129
|
+
intervention!
|
130
|
+
end
|
131
|
+
|
132
|
+
# Called after msc_process_response_headers "to inform a new response code"
|
133
|
+
# Not mandatory. Not sure what it means really. Maybe it affects the intervention values?
|
134
|
+
# extern 'int msc_update_status_code(Transaction *transaction, int status)'
|
135
|
+
|
136
|
+
##################################
|
137
|
+
# Phase RESPONSE_BODY. SecRules 4
|
138
|
+
#
|
139
|
+
# body can be a String, or an Enumerable of strings
|
140
|
+
def response_body! body
|
141
|
+
enum_of_body(body).each do |body_part|
|
142
|
+
body_part = body_part.to_s
|
143
|
+
rv = Wrapper.msc_append_response_body txn_ptr, (strptr body_part), body_part.bytesize
|
144
|
+
rv == 1 or raise Error, 'msc_append_response_body failed'
|
145
|
+
end
|
146
|
+
|
147
|
+
# This MUST be called, otherwise rules aren't triggered
|
148
|
+
rv = Wrapper.msc_process_response_body txn_ptr
|
149
|
+
rv == 1 or raise Error, "msc_process_response_body failed"
|
150
|
+
|
151
|
+
intervention!
|
152
|
+
end
|
153
|
+
|
154
|
+
# Needed if ModSecurity modifies the outgoing body. We don't make use of that.
|
155
|
+
# extern 'const char *msc_get_response_body(Transaction *transaction)'
|
156
|
+
# extern 'size_t msc_get_response_body_length(Transaction *transaction)'
|
157
|
+
|
158
|
+
##################################
|
159
|
+
# Phase LOGGING. SecRules 5.
|
160
|
+
# just logs all information. Response can be sent prior to this, or concurrently.
|
161
|
+
def logging
|
162
|
+
rv = Wrapper.msc_process_logging txn_ptr
|
163
|
+
rv == 1 or raise 'msc_process_logging failed'
|
164
|
+
|
165
|
+
self
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|