cisco_acl_intp 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 +17 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +3 -0
- data/.yardopts +4 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +132 -0
- data/Rakefile +78 -0
- data/acl_examples/err-acl.txt +49 -0
- data/acl_examples/named-ext-acl.txt +12 -0
- data/acl_examples/named-std-acl.txt +6 -0
- data/acl_examples/numd-acl.txt +21 -0
- data/cisco_acl_intp.gemspec +31 -0
- data/lib/cisco_acl_intp/ace.rb +432 -0
- data/lib/cisco_acl_intp/ace_ip.rb +136 -0
- data/lib/cisco_acl_intp/ace_other_qualifiers.rb +102 -0
- data/lib/cisco_acl_intp/ace_port.rb +146 -0
- data/lib/cisco_acl_intp/ace_proto.rb +319 -0
- data/lib/cisco_acl_intp/ace_srcdst.rb +114 -0
- data/lib/cisco_acl_intp/ace_tcp_flags.rb +65 -0
- data/lib/cisco_acl_intp/acl.rb +272 -0
- data/lib/cisco_acl_intp/acl_base.rb +111 -0
- data/lib/cisco_acl_intp/parser.rb +3509 -0
- data/lib/cisco_acl_intp/parser.ry +1397 -0
- data/lib/cisco_acl_intp/scanner.rb +176 -0
- data/lib/cisco_acl_intp/scanner_special_token_handler.rb +66 -0
- data/lib/cisco_acl_intp/version.rb +5 -0
- data/lib/cisco_acl_intp.rb +9 -0
- data/spec/cisco_acl_intp/ace_ip_spec.rb +111 -0
- data/spec/cisco_acl_intp/ace_other_qualifier_spec.rb +63 -0
- data/spec/cisco_acl_intp/ace_port_spec.rb +214 -0
- data/spec/cisco_acl_intp/ace_proto_spec.rb +200 -0
- data/spec/cisco_acl_intp/ace_spec.rb +605 -0
- data/spec/cisco_acl_intp/ace_srcdst_spec.rb +296 -0
- data/spec/cisco_acl_intp/ace_tcp_flags_spec.rb +38 -0
- data/spec/cisco_acl_intp/acl_spec.rb +523 -0
- data/spec/cisco_acl_intp/cisco_acl_intp_spec.rb +7 -0
- data/spec/cisco_acl_intp/parser_spec.rb +53 -0
- data/spec/cisco_acl_intp/scanner_spec.rb +122 -0
- data/spec/conf/extacl_objgrp_token_seq.yml +36 -0
- data/spec/conf/extacl_token_seq.yml +88 -0
- data/spec/conf/extended_acl.yml +226 -0
- data/spec/conf/scanner_spec_data.yml +120 -0
- data/spec/conf/single_tokens.yml +235 -0
- data/spec/conf/stdacl_token_seq.yml +8 -0
- data/spec/conf/tokens1.yml +158 -0
- data/spec/conf/tokens2.yml +206 -0
- data/spec/parser_fullfill_patterns.rb +145 -0
- data/spec/spec_helper.rb +54 -0
- data/tools/check_acl.rb +48 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ca5d93222e696b7d5cbe8af83c2993454a7ce56f
|
4
|
+
data.tar.gz: e6d9b5ce240ae6a494b11f46a461bdc6ac90ac27
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0e7d8aa8410bd44bc5406424548936d6a245f76c4c473c64dfe9bb2834e9f8348ddb797b513317a699e3615b455a68a3e201e6a60ef464faf0c4a2df9303e921
|
7
|
+
data.tar.gz: 37a36a2086e4106d327b08d6c157d829af96b2dfe807fd8fb008c52be07ddd6be0a6b221446d79e975f76a89a8428b1a34610f5f56b161cb94f95e1afffd3b9d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in cisco_acl_intp.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :development, :test do
|
7
|
+
gem 'racc', '~> 1.4.11'
|
8
|
+
gem 'rake', '~> 10.1.1'
|
9
|
+
gem 'rspec', '~> 2.14.1'
|
10
|
+
gem 'rubocop', '~> 0.16.0' if RUBY_VERSION >= '1.9.0'
|
11
|
+
gem 'simplecov', '~> 0.8.2' if RUBY_VERSION >= '1.9.0'
|
12
|
+
gem 'yard', '~> 0.8.7'
|
13
|
+
end
|
14
|
+
|
15
|
+
### Local variables:
|
16
|
+
### mode: Ruby
|
17
|
+
### coding: utf-8-unix
|
18
|
+
### indent-tabs-mode: nil
|
19
|
+
### End:
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 stereocat
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# CiscoAclIntp
|
2
|
+
|
3
|
+
CiscoAclIntp is a interpreter of Cisco IOS access control list (ACL).
|
4
|
+
|
5
|
+
## Features Overview
|
6
|
+
|
7
|
+
CiscoAclIntp can...
|
8
|
+
|
9
|
+
* parse ACL types of below
|
10
|
+
* Numbered ACL (standard/extended)
|
11
|
+
* Named ACL (standard/extended)
|
12
|
+
* parse almost ACL syntaxes.
|
13
|
+
* basic IPv4 acl (protocol `ip`/`tcp`/`udp`)
|
14
|
+
|
15
|
+
CiscoAclIntp *CANNOT*...
|
16
|
+
|
17
|
+
* handle IPv4 tcp-flags-qualifier, object-groups, and other specific
|
18
|
+
qualifiers (`dscp`, `ttl`, `tos`, ...). These features are not
|
19
|
+
implemented yet.
|
20
|
+
* handle IPv6 ACL (`ip access-list ipv6`) (not implemented yet)
|
21
|
+
|
22
|
+
Supports
|
23
|
+
|
24
|
+
* Ruby/1.9 or later. (Development and testing is being conducted in
|
25
|
+
Ruby/2.0.0 and *NOT* supported Ruby/1.8.x)
|
26
|
+
* Racc/1.4.9 or later.
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add this line to your application's Gemfile:
|
31
|
+
|
32
|
+
gem 'cisco_acl_intp'
|
33
|
+
|
34
|
+
And then execute:
|
35
|
+
|
36
|
+
$ bundle
|
37
|
+
|
38
|
+
Or install it yourself as:
|
39
|
+
|
40
|
+
$ gem install cisco_acl_intp
|
41
|
+
|
42
|
+
## Sample Application
|
43
|
+
|
44
|
+
### ACL Validator
|
45
|
+
|
46
|
+
#### Usage
|
47
|
+
|
48
|
+
One of application using CiscoAclIntp is in `tools/check_acl.rb`.
|
49
|
+
The script works as ACL validator. It reads a ACL file, parse it with
|
50
|
+
CiscoAclIntp parser and output parser results.
|
51
|
+
|
52
|
+
In directory `acl_examples`, there are some Cisco IOS ACL sample
|
53
|
+
files. Run `check_acl.rb` with ACL sample files, like below.
|
54
|
+
|
55
|
+
$ ~/cisco_acl_intp$ ruby tools/check_acl.rb -c -f acl_examples/numd-acl.txt
|
56
|
+
acl name : 1
|
57
|
+
access-list 1 permit 192.168.0.0 0.0.255.255
|
58
|
+
access-list 1 deny any log
|
59
|
+
acl name : 100
|
60
|
+
access-list 100 remark General Internet Access
|
61
|
+
access-list 100 permit icmp any any
|
62
|
+
access-list 100 permit ip 192.168.0.0 0.0.255.255 any
|
63
|
+
access-list 100 remark NTP
|
64
|
+
access-list 100 permit tcp any host 210.197.74.200
|
65
|
+
access-list 100 permit udp any eq ntp any eq ntp
|
66
|
+
access-list 100 remark 6to4
|
67
|
+
access-list 100 permit 41 any host 192.88.99.1
|
68
|
+
access-list 100 permit ip any host 192.88.99.1
|
69
|
+
access-list 100 remark others
|
70
|
+
access-list 100 permit tcp any eq 0 any eq 0
|
71
|
+
access-list 100 permit udp any eq 0 any eq 0
|
72
|
+
access-list 100 deny ip any any log
|
73
|
+
acl name : 110
|
74
|
+
access-list 110 remark SPLIT_VPN
|
75
|
+
access-list 110 permit ip 192.168.0.0 0.0.255.255 any
|
76
|
+
$ ~/cisco_acl_intp$
|
77
|
+
|
78
|
+
By putting `-c` (`--color`) option, `check_acl.rb` outputs
|
79
|
+
**color-coded ACL** according to type of each word. It can parse
|
80
|
+
multiple ACLs at the same time. In addition, in the case of the
|
81
|
+
parsing of a ACL that contains errors, CiscoAclIntp parser outputs
|
82
|
+
corresponding error messages. Please try to run using sample ACL file,
|
83
|
+
`acl_examples/err-acl.txt`, that contains some kind of errors.
|
84
|
+
|
85
|
+
You can get short usage with `-h` option. If it runs without `-f`
|
86
|
+
(`--file`) option, it reads ACLs from standard input.
|
87
|
+
|
88
|
+
#### Codes
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
require 'optparse'
|
92
|
+
require 'cisco_acl_intp'
|
93
|
+
|
94
|
+
## CUT: option handling
|
95
|
+
|
96
|
+
parser = CiscoAclIntp::Parser.new(popts)
|
97
|
+
|
98
|
+
# read acl from file or STDIN
|
99
|
+
if opts[:file]
|
100
|
+
parser.parse_file opts[:file]
|
101
|
+
else
|
102
|
+
parser.parse_file $stdin
|
103
|
+
end
|
104
|
+
|
105
|
+
# print acl data
|
106
|
+
aclt = parser.acl_table
|
107
|
+
aclt.each do |name, acl|
|
108
|
+
puts "acl name : #{name}"
|
109
|
+
puts acl.to_s
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
In the script, generate `CiscoAclIntp::Parser` instance and it reads
|
114
|
+
ACLs from a file (or `$stdin`). The `parser` instance generate ACL
|
115
|
+
objects (as Hash table of ACL name and ACL objects). An element of the
|
116
|
+
table is "ACL object". "ACL object" is build by ACL components. For
|
117
|
+
example, source/destination address obj, action obj, tcp/udp protocol
|
118
|
+
obj,... See more detail in documents (see also, Documents section)
|
119
|
+
|
120
|
+
## Documents
|
121
|
+
|
122
|
+
It can generate documents with YARD.
|
123
|
+
|
124
|
+
$ rake yard
|
125
|
+
|
126
|
+
## Contributing
|
127
|
+
|
128
|
+
1. Fork it
|
129
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
130
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
131
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
132
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
LIB_DIR = './lib'
|
6
|
+
PACKAGE_NAME = 'cisco_acl_intp'
|
7
|
+
SPEC_ORIG_DIR = 'spec'
|
8
|
+
SPEC_DIR = "#{SPEC_ORIG_DIR}/#{PACKAGE_NAME}/"
|
9
|
+
SPEC_DATA_DIR = "#{SPEC_ORIG_DIR}/data"
|
10
|
+
CLASS_DIR = "#{LIB_DIR}/#{PACKAGE_NAME}"
|
11
|
+
CLASS_GRAPH_DOT = "doc/#{PACKAGE_NAME}.dot"
|
12
|
+
CLASS_GRAPH_PNG = "doc/#{PACKAGE_NAME}.png"
|
13
|
+
PARSER_RACC = "#{CLASS_DIR}/parser.ry"
|
14
|
+
PARSER_RUBY = "#{CLASS_DIR}/parser.rb"
|
15
|
+
|
16
|
+
CLEAN.include(
|
17
|
+
"#{SPEC_DATA_DIR}/*.*",
|
18
|
+
"#{LIB_DIR}/*.output"
|
19
|
+
)
|
20
|
+
CLOBBER.include(
|
21
|
+
PARSER_RUBY,
|
22
|
+
CLASS_GRAPH_DOT,
|
23
|
+
CLASS_GRAPH_PNG
|
24
|
+
)
|
25
|
+
|
26
|
+
task default: [:parser, :spec]
|
27
|
+
task parser: [PARSER_RUBY]
|
28
|
+
task spec: [SPEC_DATA_DIR]
|
29
|
+
|
30
|
+
task :fullfill do
|
31
|
+
# generate full-fill pattern test scripts
|
32
|
+
sh "ruby #{SPEC_ORIG_DIR}/parser_fullfill_patterns.rb"
|
33
|
+
end
|
34
|
+
|
35
|
+
directory SPEC_DATA_DIR
|
36
|
+
file PARSER_RUBY => [PARSER_RACC] do
|
37
|
+
sh "racc -v -t #{PARSER_RACC} -o #{PARSER_RUBY}"
|
38
|
+
end
|
39
|
+
|
40
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
41
|
+
spec.pattern = FileList["#{SPEC_DIR}/*_spec.rb"]
|
42
|
+
spec.rspec_opts = '--format documentation --color'
|
43
|
+
end
|
44
|
+
|
45
|
+
RSpec::Core::RakeTask.new(fullspec: [:fullfill]) do |spec|
|
46
|
+
spec.pattern = FileList["#{SPEC_ORIG_DIR}/**/*_spec.rb"]
|
47
|
+
spec.rspec_opts = '--format documentation --color'
|
48
|
+
end
|
49
|
+
|
50
|
+
# documentation by yard
|
51
|
+
require 'yard'
|
52
|
+
require 'yard/rake/yardoc_task'
|
53
|
+
YARD::Rake::YardocTask.new do |task|
|
54
|
+
# yardoc options in .yardopts
|
55
|
+
task.files = ["#{LIB_DIR}/**/*.rb"]
|
56
|
+
end
|
57
|
+
|
58
|
+
task :docgraph do
|
59
|
+
# need to install graphviz package
|
60
|
+
sh "yard graph --full -f #{CLASS_GRAPH_DOT}"
|
61
|
+
sh "dot -Tpng #{CLASS_GRAPH_DOT} -o #{CLASS_GRAPH_PNG}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# rubocop settings
|
65
|
+
if RUBY_VERSION >= '1.9.0'
|
66
|
+
task quality: :rubocop
|
67
|
+
require 'rubocop/rake_task'
|
68
|
+
Rubocop::RakeTask.new do |task|
|
69
|
+
# file patterns in ".rubocop.yml"
|
70
|
+
task.fail_on_error = false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
### Local variables:
|
75
|
+
### mode: Ruby
|
76
|
+
### coding: utf-8-unix
|
77
|
+
### indent-tabs-mode: nil
|
78
|
+
### End:
|
@@ -0,0 +1,49 @@
|
|
1
|
+
access-list 1 permit 192.168.0.0 0.0.255.255
|
2
|
+
access-list 1 deny any log
|
3
|
+
access-list 100 remark General Internet Access
|
4
|
+
access-list 100 permit icmp any any
|
5
|
+
access-list 100 permit ip 192.168.0.0 0.0.255.255 any
|
6
|
+
access-list 100 permit tcp any host 210.197.74.200
|
7
|
+
access-list 100 remark !wrong acl number!
|
8
|
+
access-list 10 permit udp any eq ntp any eq ntp
|
9
|
+
access-list 100 remark !------cleared------!
|
10
|
+
access-list 100 remark !wrong header! caccess-list
|
11
|
+
caccess-list 100 remark 6to4
|
12
|
+
access-list 100 remark !------cleared------!
|
13
|
+
access-list 100 permit 41 any host 192.88.99.1
|
14
|
+
access-list 100 remark !wrong ip proto number!
|
15
|
+
access-list 100 permit 256 any host 192.88.99.1
|
16
|
+
access-list 100 remark !------cleared------!
|
17
|
+
access-list 100 remark !wrong ip proto!
|
18
|
+
access-list 100 permit hoge any host 192.88.99.1
|
19
|
+
access-list 100 remark !------cleared------!
|
20
|
+
access-list 100 permit ip any host 192.88.99.1
|
21
|
+
access-list 100 remark others
|
22
|
+
access-list 100 permit tcp any eq 0 any eq 0
|
23
|
+
access-list 100 permit udp any eq 0 any eq 0
|
24
|
+
access-list 100 deny ip any any log
|
25
|
+
access-list 110 remark SPLIT_VPN
|
26
|
+
access-list 110 permit ip 192.168.0.0 0.0.255.255 any
|
27
|
+
|
28
|
+
ip access-list extended FA8-OUT
|
29
|
+
deny udp any any eq bootpc
|
30
|
+
deny udp any any eq bootps
|
31
|
+
remark !argment error! 65536
|
32
|
+
permit tcp host 192.168.3.4 173.30.240.0 0.0.0.255 range 32768 65536
|
33
|
+
remark !------cleared------!
|
34
|
+
remark !argment error! 255 => 256
|
35
|
+
deny udp 192.168.3.0 0.0.240.256 lt 1024 any eq 80
|
36
|
+
remark !------cleared------!
|
37
|
+
remark network access-list remark!!
|
38
|
+
permit tcp any any established
|
39
|
+
deny tcp any any syn rst
|
40
|
+
remark !syntax error! tcp -> tp (typo)
|
41
|
+
deny up any any log-input hoge
|
42
|
+
remark !------cleared------!
|
43
|
+
permit ip any any log
|
44
|
+
!
|
45
|
+
ip access-list standard remote-ipv4
|
46
|
+
permit 192.168.0.0 0.0.255.255
|
47
|
+
remark standard access-list last deny!?
|
48
|
+
deny any log
|
49
|
+
!
|
@@ -0,0 +1,12 @@
|
|
1
|
+
ip access-list extended FA8-OUT
|
2
|
+
deny udp any any eq bootpc
|
3
|
+
deny udp any any eq bootps
|
4
|
+
permit tcp host 192.168.3.4 173.30.240.0 0.0.0.255 range 32768 65535
|
5
|
+
deny udp 192.168.3.0 0.0.240.255 lt 1024 any eq 80
|
6
|
+
remark network access-list remark!!
|
7
|
+
permit tcp any any established
|
8
|
+
deny tcp any any syn rst
|
9
|
+
deny udp any any log-input hoge
|
10
|
+
permit ip any any log
|
11
|
+
!
|
12
|
+
!
|
@@ -0,0 +1,21 @@
|
|
1
|
+
access-list 1 permit 192.168.0.0 0.0.255.255
|
2
|
+
access-list 1 deny any log
|
3
|
+
access-list 100 remark General Internet Access
|
4
|
+
access-list 100 permit icmp any any
|
5
|
+
access-list 100 permit ip 192.168.0.0 0.0.255.255 any
|
6
|
+
access-list 100 remark NTP
|
7
|
+
access-list 100 permit tcp any host 210.197.74.200
|
8
|
+
access-list 100 permit udp any eq ntp any eq ntp
|
9
|
+
access-list 100 remark 6to4
|
10
|
+
access-list 100 permit 41 any host 192.88.99.1
|
11
|
+
access-list 100 permit ip any host 192.88.99.1
|
12
|
+
access-list 100 remark others
|
13
|
+
access-list 100 permit tcp any eq 0 any eq 0
|
14
|
+
access-list 100 permit udp any eq 0 any eq 0
|
15
|
+
access-list 100 deny ip any any log
|
16
|
+
# comment
|
17
|
+
|
18
|
+
! comment
|
19
|
+
access-list 110 remark SPLIT_VPN
|
20
|
+
access-list 110 permit ip 192.168.0.0 0.0.255.255 any
|
21
|
+
!! test
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'cisco_acl_intp/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'cisco_acl_intp'
|
9
|
+
spec.version = CiscoAclIntp::VERSION
|
10
|
+
spec.authors = ['stereocat']
|
11
|
+
spec.email = ['stereocat@gmail.com']
|
12
|
+
spec.description = %q{Cisco Access List Interpreter}
|
13
|
+
spec.summary = %q{Cisco Access List Interpreter}
|
14
|
+
spec.homepage = ''
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split("\n")
|
18
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'netaddr', '~> 1.5.0'
|
23
|
+
spec.add_runtime_dependency 'term-ansicolor', '~> 1.2.2'
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
25
|
+
end
|
26
|
+
|
27
|
+
### Local variables:
|
28
|
+
### mode: Ruby
|
29
|
+
### coding: utf-8-unix
|
30
|
+
### indent-tabs-mode: nil
|
31
|
+
### End:
|