snort-rule 1.0.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 814357ce2643e9bde5663248cb35dbd708c4616e
4
- data.tar.gz: ff24fe59ef9d47ff9bb6ff82daa4a4da497ed871
3
+ metadata.gz: 5769406b39c3f4e4d6f69f7e93dedb7480569ede
4
+ data.tar.gz: 87099f070762d3fe352aa9394dc9ee7faf366265
5
5
  SHA512:
6
- metadata.gz: b9f683cfb2fc3cfce9530091d43d5cead649e95d8aae5304f4c64adaa938f14ed369a5c4d8c43dc4f7e4466b01ccda720f0288d7ade910777f3fa76dda010466
7
- data.tar.gz: d3d8f96ae04a7b3276e2b7be1344c902bbe1e6f13067cd4903f8508bab110768336c7bf35a489a5468efae4de56c7da70b39d3a0941e6e8c213e7a7b9b4acd71
6
+ metadata.gz: 9985ac508c0dd80e357e6b7885537b9682f0a7ea0d1b7cacf5d3b3730c22f243d3cf861f36dd6194571e1dd8b78c3e1e798ebb4df0e22410affd2ba6fb2cc773
7
+ data.tar.gz: 588b14c3522b7283b13c1c481133c83c7fc50510abf73c74f0d87c2f8ae6d7cf995f3b023c8a066d0a19e75b974b948c211f251df13913ddf7f8f6d44d10ced2
data/README.md CHANGED
@@ -18,11 +18,12 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
- rule = Snort::Rule.new({:action => 'pass', :proto => 'udp', :src => '192.168.0.1', :sport => 'any', :dir => '<>', :dst => 'any', :dport => 53, :opts => {'sid' => 48, 'threshold' => 'type limit,track by_src,count 1,seconds 3600' }})
22
-
21
+ rule = Snort::Rule.new({:enabled => true, :action => 'pass', :proto => 'udp', :src => '192.168.0.1', :sport => 'any', :dir => '<>', :dst => 'any', :dport => 53, :opts => {'sid' => 48, 'threshold' => 'type limit,track by_src,count 1,seconds 3600' }})
22
+
23
23
  rule.to_s => "pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )"
24
24
 
25
25
  rule = Snort::Rule.new
26
+ rule.enabled = false
26
27
  rule.action = 'pass'
27
28
  rule.proto = 'udp'
28
29
  rule.src = '192.168.0.1'
@@ -30,8 +31,9 @@ Or install it yourself as:
30
31
  rule.dport = 53
31
32
  rule.opts['sid'] = 48
32
33
  rule.opts['threshold'] = 'type limit,track by_src,count 1,seconds 3600'
33
-
34
- rule.to_s => "pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )"
34
+
35
+ # if the rule is disabled, then it will begin with a #
36
+ rule.to_s => "#pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )"
35
37
 
36
38
  rule = Snort::Rule.parse("pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )")
37
39
  rule.to_s => "pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )"
@@ -43,3 +45,5 @@ Or install it yourself as:
43
45
  3. Commit your changes (`git commit -am 'Add some feature'`)
44
46
  4. Push to the branch (`git push origin my-new-feature`)
45
47
  5. Create new Pull Request
48
+
49
+ Thanks so much for those who have already contributed.
data/bin/snortrule CHANGED
@@ -4,8 +4,9 @@ require 'getoptlong'
4
4
  require 'snort/rule'
5
5
 
6
6
  def usage
7
- puts "Usage: #{$0} [-h] [-a <action>] [-p <protocol>] [-s <srcip>] [-x <srcport>] [-w <direction>] [-d <dstip>] [-c <dstport>] [-o <key:value>] [-o <key:value> ...]"
7
+ puts "Usage: #{$0} [-hE] [-a <action>] [-p <protocol>] [-s <srcip>] [-x <srcport>] [-w <direction>] [-d <dstip>] [-c <dstport>] [-o <key:value>] [-o <key:value> ...]"
8
8
  puts "-h This text."
9
+ puts "-E Not enabled. i.e., commented out"
9
10
  puts "-a <action> alert, log, pass, ... : alert"
10
11
  puts "-p <protocol> ip, udp, tcp, ... : ip"
11
12
  puts "-s <srcip> dotted quad IP address : any"
@@ -19,6 +20,8 @@ end
19
20
 
20
21
  opts = GetoptLong.new(
21
22
  [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
23
+ [ '--disabled', '-E', GetoptLong::NO_ARGUMENT ],
24
+ [ '--rule', '-r', GetoptLong::REQUIRED_ARGUMENT ],
22
25
  [ '--action', '-a', GetoptLong::REQUIRED_ARGUMENT ],
23
26
  [ '--proto', '-p', GetoptLong::REQUIRED_ARGUMENT ],
24
27
  [ '--src', '-s', GetoptLong::REQUIRED_ARGUMENT ],
@@ -34,6 +37,11 @@ opts.each do |opt, arg|
34
37
  case opt
35
38
  when '--help'
36
39
  usage
40
+ when '--disabled'
41
+ rule.enabled = false
42
+ when '--rule'
43
+ # TODO: regex sanity check here? better to fail gracefully.
44
+ rule = Snort::Rule.parse(arg)
37
45
  when '--action'
38
46
  rule.action = arg
39
47
  when '--proto'
@@ -1,5 +1,5 @@
1
1
  module Snort
2
2
  class Rule
3
- VERSION = "1.0.2"
3
+ VERSION = "1.1.1"
4
4
  end
5
5
  end
data/lib/snort/rule.rb CHANGED
@@ -2,18 +2,21 @@ require "snort/rule/version"
2
2
  require "snort/rule/option"
3
3
  # Generates and parses snort rules
4
4
  #
5
- # Authors:: Chris Lee (mailto:rubygems@chrislee.dhs.org), Will Green (will[ at ]hotgazpacho[ dot ]org)
5
+ # Authors:: Chris Lee (mailto:rubygems@chrislee.dhs.org),
6
+ # Will Green (will[ at ]hotgazpacho[ dot ]org),
7
+ # Justin Knox (jknox[ at ]indexzero[ dot ]org)
6
8
  # Copyright:: Copyright (c) 2011 Chris Lee
7
9
  # License:: Distributes under the same terms as Ruby
8
10
  module Snort
9
11
 
10
12
  # This class stores and generates the features of a snort rule
11
13
  class Rule
12
- attr_accessor :action, :proto, :src, :sport, :dir, :dst, :dport
14
+ attr_accessor :enabled, :action, :proto, :src, :sport, :dir, :dst, :dport
13
15
  attr_reader :options
14
16
 
15
17
  # Initializes the Rule
16
18
  # @param [Hash] kwargs The options to initialize the Rule with
19
+ # @option kwargs [String] :enabled true or false
17
20
  # @option kwargs [String] :action The action
18
21
  # @option kwargs [String] :proto The protocol
19
22
  # @option kwargs [String] :src The source IP
@@ -24,6 +27,10 @@ module Snort
24
27
  # @option kwargs[Array<Snort::RuleOption>] :options The better way of passing in options, using
25
28
  # option objects that know how to represent themselves as a string properly
26
29
  def initialize(kwargs={})
30
+ @enabled = true
31
+ if kwargs.has_key?(:enabled) and (not kwargs[:enabled] or ['false', 'no', 'off'].index(kwargs[:enabled].to_s.downcase))
32
+ @enabled = false
33
+ end
27
34
  @action = kwargs[:action] || 'alert'
28
35
  @proto = kwargs[:proto] || 'IP'
29
36
  @src = kwargs[:src] || 'any'
@@ -37,7 +44,10 @@ module Snort
37
44
  # Output the current object into a snort rule
38
45
  def to_s(options_only=false)
39
46
  rule = ""
40
- rule = [@action, @proto, @src, @sport, @dir, @dst, @dport].join(" ") unless options_only
47
+ if not @enabled
48
+ rule = "#"
49
+ end
50
+ rule += [@action, @proto, @src, @sport, @dir, @dst, @dport].join(" ") unless options_only
41
51
  if options.any?
42
52
  rule += " (" unless options_only
43
53
  rule += options.join(' ')
@@ -49,8 +59,18 @@ module Snort
49
59
  # Parse a snort rule to generate an object
50
60
  def Rule::parse(string)
51
61
  rule = Snort::Rule.new
62
+ # If the string begins with /^#+\s*/, then the rule is disabled.
63
+ # If disabled, let's scrub the disabling substring from the string.
64
+ if string.index(/^#+\s+/)
65
+ rule.enabled = false
66
+ string.gsub!(/^#+\s*/,'')
67
+ end
52
68
  rulepart, optspart = string.split(/\s*\(\s*/,2)
53
69
  rule.action, rule.proto, rule.src, rule.sport, rule.dir, rule.dst, rule.dport = rulepart.split(/\s+/)
70
+ if not ['<>', '<-', '->'].index(rule.dir)
71
+ # most likely, I have a parse error, maybe it's just a random comment
72
+ raise ArgumentError.new("Unable to parse rule, #{rulepart}")
73
+ end
54
74
  optspart.gsub(/;\s*\).*$/,'').split(/\s*;\s*/).each do |x|
55
75
  if x =~ /(.*?):(.*)/
56
76
  rule.options << Snort::RuleOption.new(*x.split(/:/,2))
data/snort-rule.gemspec CHANGED
@@ -23,6 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "minitest"
24
24
  spec.add_development_dependency "guard-minitest"
25
25
 
26
- spec.signing_key = "#{File.dirname(__FILE__)}/../gem-private_key.pem"
27
- spec.cert_chain = ["#{File.dirname(__FILE__)}/../gem-public_cert.pem"]
26
+ #spec.signing_key = "#{File.dirname(__FILE__)}/../gem-private_key.pem"
27
+ #spec.cert_chain = ["#{File.dirname(__FILE__)}/../gem-public_cert.pem"]
28
28
  end
data/test/helper.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'minitest/autorun'
2
2
  require 'minitest/pride'
3
- require File.expand_path('../../lib/snort/rule.rb', __FILE__)
3
+ require File.expand_path('../../lib/snort/rule.rb', __FILE__)
@@ -10,8 +10,8 @@ require_relative 'helper'
10
10
 
11
11
  class TestSnortRule < Minitest::Test
12
12
  def test_constructor_should_set_all_the_parameters_and_generate_the_correct_rule
13
- rule = Snort::Rule.new({:action => 'pass', :proto => 'udp', :src => '192.168.0.1', :sport => 'any', :dir => '<>',
14
- :dst => 'any', :dport => 53,
13
+ rule = Snort::Rule.new({:enabled => true, :action => 'pass', :proto => 'udp', :src => '192.168.0.1', :sport => 'any', :dir => '<>',
14
+ :dst => 'any', :dport => 53,
15
15
  :options => [Snort::RuleOption.new('sid', 48), Snort::RuleOption.new('threshold', 'type limit,track by_src,count 1,seconds 3600')]
16
16
  })
17
17
  assert_equal rule.to_s, "pass udp 192.168.0.1 any <> any 53 (sid:48; threshold:type limit,track by_src,count 1,seconds 3600;)"
@@ -19,6 +19,7 @@ class TestSnortRule < Minitest::Test
19
19
 
20
20
  def test_construct_a_default_rule_and_update_each_member_to_generate_the_correct_rule
21
21
  rule = Snort::Rule.new
22
+ rule.enabled = true
22
23
  rule.action = 'pass'
23
24
  rule.proto = 'udp'
24
25
  rule.src = '192.168.0.1'
@@ -31,6 +32,7 @@ class TestSnortRule < Minitest::Test
31
32
 
32
33
  def test_construct_a_default_rule_with_many_options_having_the_same_keyword
33
34
  rule = Snort::Rule.new
35
+ rule.enabled = true
34
36
  rule.action = 'alert'
35
37
  rule.proto = 'tcp'
36
38
  rule.src = '$HOME_NET'
@@ -52,8 +54,49 @@ class TestSnortRule < Minitest::Test
52
54
  assert_equal 'alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"HTTP Host www.baddomain.com"; content:"Host|3a|"; nocase; http_header; content:"www.baddomain.com"; nocase; http_header; pcre:"/^Host\x3a(.*\.|\s*)www\.baddomain\.com\s*$/mi"; flow:to_server,established; threshold:type limit, track by_src, count 1, seconds 300; classtype:bad-unknown; sid:100000000;)', rule.to_s
53
55
  end
54
56
 
57
+ def test_construct_a_disabled_default_rule_with_many_options_having_the_same_keyword
58
+ rule = Snort::Rule.new
59
+ rule.enabled = false
60
+ rule.action = 'alert'
61
+ rule.proto = 'tcp'
62
+ rule.src = '$HOME_NET'
63
+ rule.dir = '->'
64
+ rule.dst = '$EXTERNAL_NET'
65
+ rule.dport = '$HTTP_PORTS'
66
+ rule.options << Snort::RuleOption.new('msg', '"HTTP Host www.baddomain.com"')
67
+ rule.options << Snort::RuleOption.new('content', '"Host|3a|"')
68
+ rule.options << Snort::RuleOption.new('nocase')
69
+ rule.options << Snort::RuleOption.new('http_header')
70
+ rule.options << Snort::RuleOption.new('content', '"www.baddomain.com"')
71
+ rule.options << Snort::RuleOption.new('nocase')
72
+ rule.options << Snort::RuleOption.new('http_header')
73
+ rule.options << Snort::RuleOption.new('pcre', '"/^Host\\x3a(.*\\.|\\s*)www\\.baddomain\\.com\\s*$/mi"')
74
+ rule.options << Snort::RuleOption.new('flow', 'to_server,established')
75
+ rule.options << Snort::RuleOption.new('threshold', 'type limit, track by_src, count 1, seconds 300')
76
+ rule.options << Snort::RuleOption.new('classtype', 'bad-unknown')
77
+ rule.options << Snort::RuleOption.new('sid', '100000000')
78
+ assert_equal '#alert tcp $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS (msg:"HTTP Host www.baddomain.com"; content:"Host|3a|"; nocase; http_header; content:"www.baddomain.com"; nocase; http_header; pcre:"/^Host\x3a(.*\.|\s*)www\.baddomain\.com\s*$/mi"; flow:to_server,established; threshold:type limit, track by_src, count 1, seconds 300; classtype:bad-unknown; sid:100000000;)', rule.to_s
79
+ end
80
+
55
81
  def test_parse_an_existing_rule_and_generate_the_same_rule
56
82
  rule = Snort::Rule.parse("pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )")
57
83
  assert_equal rule.to_s, "pass udp 192.168.0.1 any <> any 53 (sid:48; threshold:type limit,track by_src,count 1,seconds 3600;)"
58
84
  end
85
+
86
+ def test_parse_an_existing_disabled_rule_and_generate_the_same_rule
87
+ rule = Snort::Rule.parse("#pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )")
88
+ assert_equal rule.to_s, "#pass udp 192.168.0.1 any <> any 53 (sid:48; threshold:type limit,track by_src,count 1,seconds 3600;)"
89
+ end
90
+
91
+ def test_parse_a_disabled_rule_and_generate_the_normalized_disabled_rule
92
+ rule = Snort::Rule.parse("### pass udp 192.168.0.1 any <> any 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )")
93
+ assert_equal rule.to_s, "#pass udp 192.168.0.1 any <> any 53 (sid:48; threshold:type limit,track by_src,count 1,seconds 3600;)"
94
+ end
95
+
96
+ def test_parse_a_garbled_rule_and_throws_an_exception
97
+ assert_raises ArgumentError do
98
+ Snort::Rule.parse("pass udp 192.168.0.1 bla bla bla 53 ( sid:48; threshold:type limit,track by_src,count 1,seconds 3600; )")
99
+ end
100
+ end
101
+
59
102
  end
@@ -37,4 +37,4 @@ class TestSnortRuleOption < Minitest::Test
37
37
  assert_equal option1.hash, option2.hash
38
38
  end
39
39
 
40
- end
40
+ end
metadata CHANGED
@@ -1,91 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: snort-rule
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - chrislee35
8
8
  autorequire:
9
9
  bindir: bin
10
- cert_chain:
11
- - |
12
- -----BEGIN CERTIFICATE-----
13
- MIIDYjCCAkqgAwIBAgIBADANBgkqhkiG9w0BAQUFADBXMREwDwYDVQQDDAhydWJ5
14
- Z2VtczEYMBYGCgmSJomT8ixkARkWCGNocmlzbGVlMRMwEQYKCZImiZPyLGQBGRYD
15
- ZGhzMRMwEQYKCZImiZPyLGQBGRYDb3JnMB4XDTEzMDUyMjEyNTk0N1oXDTE0MDUy
16
- MjEyNTk0N1owVzERMA8GA1UEAwwIcnVieWdlbXMxGDAWBgoJkiaJk/IsZAEZFghj
17
- aHJpc2xlZTETMBEGCgmSJomT8ixkARkWA2RoczETMBEGCgmSJomT8ixkARkWA29y
18
- ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANcPrx8BZiWIR9xWWG8I
19
- tqR538tS1t+UJ4FZFl+1vrtU9TiuWX3Vj37TwUpa2fFkziK0n5KupVThyEhcem5m
20
- OGRjvgrRFbWQJSSscIKOpwqURHVKRpV9gVz/Hnzk8S+xotUR1Buo3Ugr+I1jHewD
21
- Cgr+y+zgZbtjtHsJtsuujkOcPhEjjUinj68L9Fz9BdeJQt+IacjwAzULix6jWCht
22
- Uc+g+0z8Esryca2G6I1GsrgX6WHw8dykyQDT9dCtS2flCOwSC1R0K5T/xHW54f+5
23
- wcw8mm53KLNe+tmgVC6ZHyME+qJsBnP6uxF0aTEnGA/jDBQDhQNTF0ZP/abzyTsL
24
- zjUCAwEAAaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFO8w
25
- +aeP7T6kVJblCg6eusOII9DfMA0GCSqGSIb3DQEBBQUAA4IBAQBCQyRJLXsBo2Fy
26
- 8W6e/W4RemQRrlAw9DK5O6U71JtedVob2oq+Ob+zmS+PifE2+L+3RiJ2H6VTlOzi
27
- x+A061MUXhGraqVq4J2FC8kt4EQywAD0P0Ta5GU24CGSF08Y3GkJy1Sa4XqTC2YC
28
- o51s7JP+tkCCtpVYSdzJhTllieRAWBpGV1dtaoeUKE6tYPMBkosxSRcVGczk/Sc3
29
- 7eQCpexYy9JlUBI9u3BqIY9E+l+MSn8ihXSPmyK0DgrhaCu+voaSFVOX6Y+B5qbo
30
- jLXMQu2ZgISYwXNjNbGVHehut82U7U9oiHoWcrOGazaRUmGO9TXP+aJLH0gw2dcK
31
- AfMglXPi
32
- -----END CERTIFICATE-----
33
- date: 2014-05-02 00:00:00.000000000 Z
10
+ cert_chain: []
11
+ date: 2014-10-05 00:00:00.000000000 Z
34
12
  dependencies:
35
13
  - !ruby/object:Gem::Dependency
36
14
  name: bundler
37
15
  requirement: !ruby/object:Gem::Requirement
38
16
  requirements:
39
- - - ~>
17
+ - - "~>"
40
18
  - !ruby/object:Gem::Version
41
19
  version: '1.3'
42
20
  type: :development
43
21
  prerelease: false
44
22
  version_requirements: !ruby/object:Gem::Requirement
45
23
  requirements:
46
- - - ~>
24
+ - - "~>"
47
25
  - !ruby/object:Gem::Version
48
26
  version: '1.3'
49
27
  - !ruby/object:Gem::Dependency
50
28
  name: rake
51
29
  requirement: !ruby/object:Gem::Requirement
52
30
  requirements:
53
- - - '>='
31
+ - - ">="
54
32
  - !ruby/object:Gem::Version
55
33
  version: '0'
56
34
  type: :development
57
35
  prerelease: false
58
36
  version_requirements: !ruby/object:Gem::Requirement
59
37
  requirements:
60
- - - '>='
38
+ - - ">="
61
39
  - !ruby/object:Gem::Version
62
40
  version: '0'
63
41
  - !ruby/object:Gem::Dependency
64
42
  name: minitest
65
43
  requirement: !ruby/object:Gem::Requirement
66
44
  requirements:
67
- - - '>='
45
+ - - ">="
68
46
  - !ruby/object:Gem::Version
69
47
  version: '0'
70
48
  type: :development
71
49
  prerelease: false
72
50
  version_requirements: !ruby/object:Gem::Requirement
73
51
  requirements:
74
- - - '>='
52
+ - - ">="
75
53
  - !ruby/object:Gem::Version
76
54
  version: '0'
77
55
  - !ruby/object:Gem::Dependency
78
56
  name: guard-minitest
79
57
  requirement: !ruby/object:Gem::Requirement
80
58
  requirements:
81
- - - '>='
59
+ - - ">="
82
60
  - !ruby/object:Gem::Version
83
61
  version: '0'
84
62
  type: :development
85
63
  prerelease: false
86
64
  version_requirements: !ruby/object:Gem::Requirement
87
65
  requirements:
88
- - - '>='
66
+ - - ">="
89
67
  - !ruby/object:Gem::Version
90
68
  version: '0'
91
69
  description: Parses and generates Snort rules similar to PERL's Snort::Rule
@@ -96,7 +74,7 @@ executables:
96
74
  extensions: []
97
75
  extra_rdoc_files: []
98
76
  files:
99
- - .gitignore
77
+ - ".gitignore"
100
78
  - Gemfile
101
79
  - Guardfile
102
80
  - LICENSE.txt
@@ -120,17 +98,17 @@ require_paths:
120
98
  - lib
121
99
  required_ruby_version: !ruby/object:Gem::Requirement
122
100
  requirements:
123
- - - '>='
101
+ - - ">="
124
102
  - !ruby/object:Gem::Version
125
103
  version: '0'
126
104
  required_rubygems_version: !ruby/object:Gem::Requirement
127
105
  requirements:
128
- - - '>='
106
+ - - ">="
129
107
  - !ruby/object:Gem::Version
130
108
  version: '0'
131
109
  requirements: []
132
110
  rubyforge_project:
133
- rubygems_version: 2.1.11
111
+ rubygems_version: 2.2.2
134
112
  signing_key:
135
113
  specification_version: 4
136
114
  summary: Class for parsing and generating Snort Rules
checksums.yaml.gz.sig DELETED
Binary file
data.tar.gz.sig DELETED
Binary file
metadata.gz.sig DELETED
@@ -1,3 +0,0 @@
1
- �����<R@���%��������n�W��pWx��
2
- �ڏH
3
- s�`����if&Z@��������l�����9ʥky��:�����G�UA�6�@B��ck�ىs���W��� �����9��b�гˢ�J9|"ߑ�-� 8}�z˝���<��Y���̿��k�� �s�A��j`
4
- 0u� ��Ar,�%<���,C�