rsync_config 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +8 -0
- data/lib/rsync_config/config.rb +112 -0
- data/lib/rsync_config/module.rb +32 -0
- data/lib/rsync_config/parser/config_file.treetop +98 -0
- data/lib/rsync_config/parser/node_extension.rb +28 -0
- data/lib/rsync_config/parser/secrets_file.treetop +59 -0
- data/lib/rsync_config/parser.rb +1 -0
- data/lib/rsync_config/propertiable.rb +68 -0
- data/lib/rsync_config/user_management.rb +86 -0
- data/lib/rsync_config/version.rb +3 -0
- data/lib/rsync_config.rb +20 -0
- data/rsync_config.gemspec +26 -0
- data/test/etc/out/.keep +0 -0
- data/test/etc/rsyncd.conf +10 -0
- data/test/etc/rsyncd_with_secrets.conf +12 -0
- data/test/etc/secrets_ftp.conf +4 -0
- data/test/etc/secrets_top.conf +2 -0
- data/test/test_rsync_config.rb +233 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2eec2099c07ff331d789ae499f3487d9cbbf2371
|
4
|
+
data.tar.gz: d0af2a1154084f0fff7aab650f68cbdf0a0cff9c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44661fc4d9f091e2760e11a16e85219cb779b21c769728006e3b6fd7e74a7ab6c005db042aa8a3e04f61753c6b7617890173762b0b2423ed1e6e137c23556314
|
7
|
+
data.tar.gz: 8bb05a02d50ed6e99acfdefb4b873677cdc57a268f584e5d8df551fafb212b806ed7933cc8f78171f0adccaf25f7a40bcd91cc5ad03d58ecd5916b3f58e8a1ce
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Guillaume Bodi
|
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,94 @@
|
|
1
|
+
# RsyncConfig
|
2
|
+
|
3
|
+
This is a little utility gem to help manage the rsyncd config files on GNU/Linux systems.
|
4
|
+
It is still in infancy but is usable as is.
|
5
|
+
|
6
|
+
[![Code Climate](https://codeclimate.com/github/kuma-giyomu/rsync_config.png)](https://codeclimate.com/github/kuma-giyomu/rsync_config)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'rsync_config'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install rsync_config
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
You can load a config file using `RsyncConfig::load_config <file_path>` or create a config from scratch with `RsyncConfig::Config.new`.
|
25
|
+
Alternatively, you can use `RsyncConfig::parse <content>` if you prefer to provide the input from a random source.
|
26
|
+
|
27
|
+
### Properties
|
28
|
+
|
29
|
+
The `RsyncConfig::Config` instance provides access to properties via the `#[]` and `#[]=` methods. So for instance
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
config = RsyncConfig::Config.new
|
33
|
+
config['uid'] = 'alice' # setter
|
34
|
+
puts config['uid'] # getter
|
35
|
+
```
|
36
|
+
|
37
|
+
Note that the class accepts symbols as alternative `config[:uid]` is valid too.
|
38
|
+
But I felt it was best to keep as strings for the config options with spaces such as `secrets file`.
|
39
|
+
|
40
|
+
### Modules
|
41
|
+
|
42
|
+
The `RsyncConfig::Config` instance provides access to modules via the `#module` method
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
config = RsyncConfig::Config.new
|
46
|
+
foo_module = config.module :foo
|
47
|
+
bar_module = config.module 'bar'
|
48
|
+
```
|
49
|
+
|
50
|
+
Here again, symbols are converted to strings (rsync allows modules with spaces and whatnot)
|
51
|
+
|
52
|
+
Properties can be assigned to modules in the same way they are added to the `RsyncConfig::Config` object.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
foo_module['uid'] = 'alice'
|
56
|
+
```
|
57
|
+
|
58
|
+
`RsyncConfig::Module` instances do not have modules themselves.
|
59
|
+
|
60
|
+
### Users
|
61
|
+
|
62
|
+
Both `RsyncConfig::Config` and `RsyncConfig::Module` instances can define a list of users that have access to the modules.
|
63
|
+
They are defined using the `#users` hash accessor method.
|
64
|
+
Additionally a test method `#user?` can be used to probe for a give user's existence.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
config = RsyncConfig::Config.new
|
68
|
+
config.users = {'alice' => 'wonder'}
|
69
|
+
puts config.user?('alice') # true
|
70
|
+
config.users['bob'] = 'march'
|
71
|
+
```
|
72
|
+
|
73
|
+
### Writing to disk
|
74
|
+
|
75
|
+
Use `RsyncConfig::Config#write_to <main_output_file>`.
|
76
|
+
The gem assumes that you have write permissions on the file and a `RuntimeError` will be raised otherwise.
|
77
|
+
This method will automatically try to write the secrets files when defined.
|
78
|
+
|
79
|
+
For a raw String output, just call `RsyncConfig::Config#to_config_file` instead.
|
80
|
+
Note that this method will not return the content of the secrets file (not sure if this will be useful).
|
81
|
+
|
82
|
+
### Limitations
|
83
|
+
|
84
|
+
- currently the gem does not process the directives `&include` and `&merge`.
|
85
|
+
- the values for the properties are not checked/validated in any way.
|
86
|
+
- the way secrets files are handled is simplistic since the files will be written/read multiple times if specified more than once
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
class Config
|
4
|
+
|
5
|
+
include Propertiable
|
6
|
+
include UserManagement
|
7
|
+
|
8
|
+
# global properties
|
9
|
+
allow_properties :motd_file, :pid_file, :port, :address, :socket_options, :listen_backlog
|
10
|
+
|
11
|
+
# module properties
|
12
|
+
allow_properties :comment, :path, :use_chroot, :numeric_ids, :munge_symlinks, :charset
|
13
|
+
allow_properties :max_connections, :log_file, :syslog_facility, :max_verbosity, :lock_file
|
14
|
+
allow_properties :read_only, :write_only, :list, :uid, :gid, :fake_super, :filter, :exclude, :include
|
15
|
+
allow_properties :exclude_from, :include_from, :incoming_chmod, :outgoing_chmod, :auth_users
|
16
|
+
allow_properties :secrets_file, :strict_modes, :hosts_allow, :hosts_deny, :reverse_lookup
|
17
|
+
allow_properties :forward_lookup, :ignore_errors, :ignore_nonreadable, :transfer_logging
|
18
|
+
allow_properties :log_format, :timeout, :refuse_options, :dont_compress, 'pre-xfer exec', 'post-xfer exec'
|
19
|
+
|
20
|
+
def self.load_file(config_file)
|
21
|
+
raise 'File does not exist' unless File.exists? config_file
|
22
|
+
raise 'File is not readable' unless File.readable? config_file
|
23
|
+
|
24
|
+
content = nil
|
25
|
+
|
26
|
+
File.open(config_file, 'r') do |file|
|
27
|
+
content = file.read
|
28
|
+
end
|
29
|
+
|
30
|
+
Config.parse content
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.parse(content)
|
34
|
+
raise 'Cannot process nil' if content.nil?
|
35
|
+
|
36
|
+
Treetop.load 'lib/rsync_config/parser/config_file'
|
37
|
+
parser = RsyncConfigFileParser.new
|
38
|
+
p = parser.parse content
|
39
|
+
|
40
|
+
unless p.nil?
|
41
|
+
return p.to_config.after_parse
|
42
|
+
else
|
43
|
+
raise RuntimeError, parser.failure_reason
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def module(key)
|
48
|
+
raise ArgumentError if key.nil?
|
49
|
+
key = key.to_s if key.is_a? Symbol
|
50
|
+
key = key.strip
|
51
|
+
raise ArgumentError if key.length == 0
|
52
|
+
|
53
|
+
modules[key] ||= Module.new(self, key)
|
54
|
+
|
55
|
+
modules[key]
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
to_config_file
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_config_file
|
63
|
+
out = []
|
64
|
+
properties_to_s = properties_to_a.join("\n")
|
65
|
+
out << properties_to_s if properties_to_s.length > 0
|
66
|
+
|
67
|
+
modules.each do |key, mod|
|
68
|
+
out << mod.to_config_file
|
69
|
+
end
|
70
|
+
|
71
|
+
out.join "\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_to(file_path)
|
75
|
+
File.open(file_path, 'w') do |file|
|
76
|
+
file.write to_config_file
|
77
|
+
end
|
78
|
+
|
79
|
+
write_secrets_files
|
80
|
+
end
|
81
|
+
|
82
|
+
def module_names
|
83
|
+
modules.keys
|
84
|
+
end
|
85
|
+
|
86
|
+
def after_parse
|
87
|
+
batch_process_secrets_files :load_secrets_file
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def modules
|
93
|
+
@modules ||= {}
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_secrets_files
|
97
|
+
batch_process_secrets_files :write_secrets_file
|
98
|
+
end
|
99
|
+
|
100
|
+
def batch_process_secrets_files(method_name)
|
101
|
+
send method_name, self.[]('secrets file', true)
|
102
|
+
|
103
|
+
modules.each_value do |rmodule|
|
104
|
+
rmodule.send method_name, rmodule.[]('secrets file', true)
|
105
|
+
end
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
class Module
|
4
|
+
|
5
|
+
include Propertiable
|
6
|
+
include UserManagement
|
7
|
+
|
8
|
+
# module properties
|
9
|
+
allow_properties :comment, :path, :use_chroot, :numeric_ids, :munge_symlinks, :charset
|
10
|
+
allow_properties :max_connections, :log_file, :syslog_facility, :max_verbosity, :lock_file
|
11
|
+
allow_properties :read_only, :write_only, :list, :uid, :gid, :fake_super, :filter, :exclude, :include
|
12
|
+
allow_properties :exclude_from, :include_from, :incoming_chmod, :outgoing_chmod, :auth_users
|
13
|
+
allow_properties :secrets_file, :strict_modes, :hosts_allow, :hosts_deny, :reverse_lookup
|
14
|
+
allow_properties :forward_lookup, :ignore_errors, :ignore_nonreadable, :transfer_logging
|
15
|
+
allow_properties :log_format, :timeout, :refuse_options, :dont_compress, 'pre-xfer exec', 'post-xfer exec'
|
16
|
+
|
17
|
+
def initialize(parent_config, name)
|
18
|
+
@parent_config = parent_config
|
19
|
+
@name = name
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
to_config_file
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_config_file
|
27
|
+
(["[#{@name}]"] + properties_to_a.map {|p| " #{p}"}).join "\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'rsync_config/config'
|
2
|
+
|
3
|
+
module RsyncConfig
|
4
|
+
|
5
|
+
grammar RsyncConfigFile
|
6
|
+
|
7
|
+
rule start
|
8
|
+
global_section? module_section? EOF
|
9
|
+
{
|
10
|
+
attr_accessor :config, :active_module
|
11
|
+
|
12
|
+
def to_config
|
13
|
+
@config = ::RsyncConfig::Config.new
|
14
|
+
@active_module = nil
|
15
|
+
|
16
|
+
crawl self
|
17
|
+
|
18
|
+
@config
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
rule global_section
|
24
|
+
((comment / space? option?) EOL)+
|
25
|
+
end
|
26
|
+
|
27
|
+
rule module_section
|
28
|
+
(module_header module_body*)+
|
29
|
+
end
|
30
|
+
|
31
|
+
rule module_body
|
32
|
+
(comment / space? option?) EOL
|
33
|
+
end
|
34
|
+
|
35
|
+
rule option
|
36
|
+
property space? '=' space? value space?
|
37
|
+
{
|
38
|
+
def action(top_node)
|
39
|
+
receiver = top_node.active_module.nil? ? top_node.config : top_node.active_module
|
40
|
+
receiver[property.text_value] = value.text_value
|
41
|
+
|
42
|
+
# interrupt subtree crawling
|
43
|
+
false
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
rule property
|
49
|
+
[^=\n]+
|
50
|
+
end
|
51
|
+
|
52
|
+
rule value
|
53
|
+
[^\n]* ('\\' space? EOL value)?
|
54
|
+
{
|
55
|
+
def value
|
56
|
+
text_value.gsub(/\\\s*\n/, ' ')
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
rule module_header
|
62
|
+
'[' module_label:([^/\]]+) ']' space?
|
63
|
+
{
|
64
|
+
def value
|
65
|
+
module_label.text_value.gsub /\s+/, ' '
|
66
|
+
end
|
67
|
+
|
68
|
+
def action(top_node)
|
69
|
+
top_node.active_module = top_node.config.module(module_label.text_value)
|
70
|
+
|
71
|
+
# interrupt subtree crawling
|
72
|
+
false
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
rule comment
|
78
|
+
space? '#' [^"\n"]*
|
79
|
+
end
|
80
|
+
|
81
|
+
rule space
|
82
|
+
[ \t]+
|
83
|
+
end
|
84
|
+
|
85
|
+
rule EOL
|
86
|
+
"\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
rule EOF
|
90
|
+
!.
|
91
|
+
end
|
92
|
+
|
93
|
+
rule EOS
|
94
|
+
EOL / EOF
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
module Node
|
4
|
+
|
5
|
+
module Crawlable
|
6
|
+
|
7
|
+
def crawl *args
|
8
|
+
continue = true
|
9
|
+
continue = action(*args) if respond_to? :action
|
10
|
+
|
11
|
+
return if !continue || elements.nil?
|
12
|
+
|
13
|
+
elements.each do |elt|
|
14
|
+
elt.crawl(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class Treetop::Runtime::SyntaxNode
|
21
|
+
|
22
|
+
include ::RsyncConfig::Node::Crawlable
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
module RsyncConfig
|
3
|
+
|
4
|
+
grammar RsyncSecretsFile
|
5
|
+
|
6
|
+
rule start
|
7
|
+
lines
|
8
|
+
{
|
9
|
+
def to_hash
|
10
|
+
users = {}
|
11
|
+
crawl users
|
12
|
+
|
13
|
+
users
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
rule lines
|
19
|
+
line? (EOL+ lines)?
|
20
|
+
end
|
21
|
+
|
22
|
+
rule line
|
23
|
+
comment
|
24
|
+
/ user_record
|
25
|
+
/ whitespace+
|
26
|
+
end
|
27
|
+
|
28
|
+
rule comment
|
29
|
+
'#' [^\n]*
|
30
|
+
end
|
31
|
+
|
32
|
+
rule user_record
|
33
|
+
user:([^:]+) ':' password:([^\t\r\n\ ]+)
|
34
|
+
{
|
35
|
+
def action users
|
36
|
+
users[user_value] = password_value
|
37
|
+
end
|
38
|
+
|
39
|
+
def user_value
|
40
|
+
user.text_value
|
41
|
+
end
|
42
|
+
|
43
|
+
def password_value
|
44
|
+
password.text_value
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
rule whitespace
|
50
|
+
[ \t\r\n]
|
51
|
+
end
|
52
|
+
|
53
|
+
rule EOL
|
54
|
+
"\r\n" / "\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rsync_config/parser/node_extension'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
module Propertiable
|
4
|
+
|
5
|
+
def self.included base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def allow_properties *properties
|
12
|
+
properties.each do |property|
|
13
|
+
property = property.to_s if property.is_a? Symbol
|
14
|
+
property = property.downcase.strip.gsub(/_/, ' ')
|
15
|
+
allowed_properties.push property
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def allowed_properties
|
20
|
+
@allowed_properties ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def allowed_property? property
|
24
|
+
allowed_properties.include? property
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key, local_only = false)
|
30
|
+
key = sanitize_key(key)
|
31
|
+
return nil if key.nil?
|
32
|
+
|
33
|
+
value = properties[key]
|
34
|
+
return @parent_config[key] if !local_only && value.nil? && @parent_config.respond_to?(:[])
|
35
|
+
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
39
|
+
def sanitize_key(key)
|
40
|
+
key = key.to_s unless key.is_a? String
|
41
|
+
key = key.strip.downcase
|
42
|
+
return nil if key.length == 0
|
43
|
+
return nil unless self.class.allowed_property? key
|
44
|
+
key
|
45
|
+
end
|
46
|
+
|
47
|
+
def []=(key, value)
|
48
|
+
key = sanitize_key(key)
|
49
|
+
if value.nil?
|
50
|
+
properties.delete key
|
51
|
+
else
|
52
|
+
properties[key] = value.to_s unless value.nil?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def properties_to_a
|
57
|
+
properties.map { |key, value| "#{key} = #{value}" }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def properties
|
63
|
+
@properties ||= {}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RsyncConfig
|
2
|
+
|
3
|
+
module UserManagement
|
4
|
+
|
5
|
+
def self.included base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def parse_secrets_file(content)
|
12
|
+
raise 'Cannot process nil' if content.nil?
|
13
|
+
|
14
|
+
Treetop.load 'lib/rsync_config/parser/secrets_file'
|
15
|
+
parser = RsyncSecretsFileParser.new
|
16
|
+
p = parser.parse content
|
17
|
+
|
18
|
+
unless p.nil?
|
19
|
+
return p.to_hash
|
20
|
+
else
|
21
|
+
raise RuntimeError, parser.failure_reason
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def users
|
28
|
+
return @parent_config.users if @parent_config && @users.nil?
|
29
|
+
local_users
|
30
|
+
end
|
31
|
+
|
32
|
+
def local_users
|
33
|
+
@users ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def user? identifier
|
37
|
+
users.include? identifier
|
38
|
+
end
|
39
|
+
|
40
|
+
def users= (users_list)
|
41
|
+
@users = users_list if users_list.is_a?(Hash) || users_list.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def local_user_list
|
45
|
+
# we need to output only what is related to this specific node
|
46
|
+
local_users.keys.map do |user|
|
47
|
+
"#{user}:#{local_users[user]}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_secrets_file(file)
|
52
|
+
return if file.nil?
|
53
|
+
raise "Cannot write secrets file #{file}" if File.exists?(file) && ! File.writable?(file)
|
54
|
+
raise "Cannot create secrets file #{file}" if !File.exist?(file) && ! ( Dir.exists?(File.dirname(file)) && File.writable?(File.dirname(file)))
|
55
|
+
|
56
|
+
File.open(file, 'w') do |file|
|
57
|
+
file.write local_user_list.join "\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
correct_file_permissions file
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_secrets_file(file)
|
64
|
+
# fails silently if no parameter is found OR the file does not exist on disk
|
65
|
+
return if file.nil? || !(File.exist? file)
|
66
|
+
|
67
|
+
# fails is the file exists but cannot be read, since it's suspicious
|
68
|
+
raise "Cannot load secrets file #{file}" unless File.readable?(file)
|
69
|
+
|
70
|
+
File.open(file, 'r') do |file|
|
71
|
+
self.users = self.class.parse_secrets_file(file.read)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def correct_file_permissions(file)
|
78
|
+
if File.world_readable?(file) || File.world_writable?(file)
|
79
|
+
# reuse existing permissions but remove everything from world
|
80
|
+
File.chmod (File.stat(file).mode & 0770), file
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/lib/rsync_config.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
require 'rsync_config/version'
|
4
|
+
require 'rsync_config/parser'
|
5
|
+
require 'rsync_config/propertiable'
|
6
|
+
require 'rsync_config/user_management'
|
7
|
+
require 'rsync_config/module'
|
8
|
+
require 'rsync_config/config'
|
9
|
+
|
10
|
+
module RsyncConfig
|
11
|
+
|
12
|
+
def self.load_file(config_file)
|
13
|
+
Config.load_file config_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(content)
|
17
|
+
Config.parse content
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rsync_config/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rsync_config"
|
8
|
+
spec.version = RsyncConfig::VERSION
|
9
|
+
spec.authors = ["Guillaume Bodi"]
|
10
|
+
spec.email = ["bodi.giyomu@gmail.com"]
|
11
|
+
spec.description = %q{Utility gem to manage rsyncd config files}
|
12
|
+
spec.summary = %q{Utility gem to manage rsyncd config files}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "GPLv3"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "test-unit"
|
24
|
+
|
25
|
+
spec.add_dependency 'treetop', '~>1.4.14'
|
26
|
+
end
|
data/test/etc/out/.keep
ADDED
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
uid = nobody
|
2
|
+
gid = nobody
|
3
|
+
use chroot = no
|
4
|
+
max connections = 4
|
5
|
+
syslog facility = local5
|
6
|
+
pid file = /run/rsyncd.pid
|
7
|
+
secrets file = test/etc/secrets_top.conf
|
8
|
+
|
9
|
+
[ftp]
|
10
|
+
path = /srv/ftp
|
11
|
+
comment = ftp area
|
12
|
+
secrets file = test/etc/secrets_ftp.conf
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'test-unit'
|
2
|
+
require 'rsync_config'
|
3
|
+
|
4
|
+
class RsyncConfigTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
TEST_INPUT_FILE = File.join(__dir__, 'etc', 'rsyncd.conf')
|
7
|
+
|
8
|
+
TEST_INPUT_FILE_WITH_SECRETS = File.join(__dir__, 'etc', 'rsyncd_with_secrets.conf')
|
9
|
+
|
10
|
+
TEST_OUTPUT_FILE = File.join(__dir__, 'etc', 'out', 'rsyncd.conf')
|
11
|
+
|
12
|
+
TEST_SECRETS_FILE = File.join(__dir__, 'etc', 'out', 'secrets.conf')
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
File.delete TEST_OUTPUT_FILE if File.exists? TEST_OUTPUT_FILE
|
16
|
+
File.delete TEST_SECRETS_FILE if File.exists? TEST_SECRETS_FILE
|
17
|
+
end
|
18
|
+
|
19
|
+
def make_new_config
|
20
|
+
RsyncConfig::Config.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def make_simple_config
|
24
|
+
RsyncConfig.load_file TEST_INPUT_FILE
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_secrets_config
|
28
|
+
RsyncConfig.load_file TEST_INPUT_FILE_WITH_SECRETS
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_load_missing_file
|
32
|
+
assert_raise RuntimeError, 'Did not throw an error for missing file' do
|
33
|
+
RsyncConfig.load_file 'idontexist.conf'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_load_file_does_not_crash
|
38
|
+
config = nil
|
39
|
+
assert_nothing_raised RuntimeError do
|
40
|
+
config = make_simple_config
|
41
|
+
end
|
42
|
+
|
43
|
+
assert_equal config.class, RsyncConfig::Config, 'Did not return a config object' end
|
44
|
+
|
45
|
+
def test_accessing_missing_properties_returns_nil
|
46
|
+
config = make_new_config
|
47
|
+
|
48
|
+
assert_nil config[:i_dont_exist]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_accessing_a_property_returns_a_string
|
52
|
+
config = make_new_config
|
53
|
+
config[:uid] = 'nobody'
|
54
|
+
|
55
|
+
assert_equal 'nobody', config[:uid]
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_one_module_output
|
59
|
+
config = make_new_config
|
60
|
+
ftp_module = config.module :ftp
|
61
|
+
ftp_module[:path] = 'local'
|
62
|
+
expected = <<EOS
|
63
|
+
[ftp]
|
64
|
+
path = local
|
65
|
+
EOS
|
66
|
+
|
67
|
+
assert_equal expected.strip, config.to_config_file
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_module_listing
|
71
|
+
config = make_new_config
|
72
|
+
config.module :ftp
|
73
|
+
config.module :smb
|
74
|
+
config.module :something
|
75
|
+
|
76
|
+
assert_equal ['ftp', 'smb', 'something'], config.module_names
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_easy_accessor_complete
|
80
|
+
config = make_new_config
|
81
|
+
config[:uid] = 'nut'
|
82
|
+
config[:gid] = 'kiwi'
|
83
|
+
config[:uid] = config[:gid]
|
84
|
+
|
85
|
+
assert_equal 'kiwi', config[:uid]
|
86
|
+
assert_equal config[:gid], config[:uid]
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_remove_property
|
90
|
+
config = make_new_config
|
91
|
+
config[:uid] = 'true'
|
92
|
+
config[:comment] = 'false'
|
93
|
+
|
94
|
+
# remove the comment config
|
95
|
+
config[:comment] = nil
|
96
|
+
expected = <<EOS
|
97
|
+
uid = true
|
98
|
+
EOS
|
99
|
+
assert_equal expected.strip, config.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_module_inherits_config
|
103
|
+
config = make_new_config
|
104
|
+
config[:uid] = 'true'
|
105
|
+
ftp_module = config.module :ftp
|
106
|
+
|
107
|
+
assert_equal 'true', ftp_module[:uid]
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_global_users_accessor_exists
|
111
|
+
config = make_new_config
|
112
|
+
|
113
|
+
assert_not_nil config.users
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_global_users_assignment
|
117
|
+
config = make_new_config
|
118
|
+
config.users = {'john' => 'password'}
|
119
|
+
|
120
|
+
assert_true config.user?('john')
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_global_user_existence
|
124
|
+
config = make_new_config
|
125
|
+
config.users = {'john' => 'password'}
|
126
|
+
|
127
|
+
assert_true config.user?('john')
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_global_user_inexistence
|
131
|
+
config = make_new_config
|
132
|
+
|
133
|
+
assert_false config.user?('georges')
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_module_user_existence
|
137
|
+
config = make_new_config
|
138
|
+
ftp_module = config.module :ftp
|
139
|
+
ftp_module.users = {'john' => 'password'}
|
140
|
+
|
141
|
+
assert_true ftp_module.user? 'john'
|
142
|
+
assert_false ftp_module.user? 'george'
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_module_user_inexistence
|
146
|
+
config = make_new_config
|
147
|
+
ftp_module = config.module :ftp
|
148
|
+
|
149
|
+
assert_false ftp_module.user? 'george'
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_module_user_inheritance
|
153
|
+
config = make_new_config
|
154
|
+
config.users = {'john' => 'password'}
|
155
|
+
ftp_module = config.module :ftp
|
156
|
+
ftp_module.users = {'george' => 'password'}
|
157
|
+
|
158
|
+
assert_false ftp_module.user? 'john'
|
159
|
+
assert_true ftp_module.user? 'george'
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_write_to_file_succeeds
|
163
|
+
config = make_new_config
|
164
|
+
config.write_to TEST_OUTPUT_FILE
|
165
|
+
|
166
|
+
assert_true File.exists? TEST_OUTPUT_FILE
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_write_to_file_has_correct_content
|
170
|
+
config = make_new_config
|
171
|
+
config[:uid] = 'test'
|
172
|
+
|
173
|
+
config.write_to TEST_OUTPUT_FILE
|
174
|
+
expected = <<EOL
|
175
|
+
uid = test
|
176
|
+
EOL
|
177
|
+
|
178
|
+
assert_equal expected.strip, File.read(TEST_OUTPUT_FILE).strip
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_write_to_secrets_file_succeeds
|
182
|
+
config = make_simple_config
|
183
|
+
config['secrets file'] = TEST_SECRETS_FILE
|
184
|
+
config.users['john'] = 'doe'
|
185
|
+
|
186
|
+
config.write_to TEST_OUTPUT_FILE
|
187
|
+
|
188
|
+
assert_true File.exists? TEST_SECRETS_FILE
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_write_to_secrets_file_has_correct_permissions
|
192
|
+
config = make_new_config
|
193
|
+
config['secrets file'] = TEST_SECRETS_FILE
|
194
|
+
config.users['john'] = 'doe'
|
195
|
+
|
196
|
+
config.write_to TEST_OUTPUT_FILE
|
197
|
+
|
198
|
+
assert_nil File.world_readable? TEST_SECRETS_FILE
|
199
|
+
assert_nil File.world_writable? TEST_SECRETS_FILE
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_write_correct_content_to_secrets_file
|
203
|
+
config = make_new_config
|
204
|
+
config['secrets file'] = TEST_SECRETS_FILE
|
205
|
+
config.users['john'] = 'doe'
|
206
|
+
|
207
|
+
config.write_to TEST_OUTPUT_FILE
|
208
|
+
|
209
|
+
secrets_expected = <<EOL
|
210
|
+
john:doe
|
211
|
+
EOL
|
212
|
+
assert_equal secrets_expected.strip, File.read(TEST_SECRETS_FILE).strip
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_module_secrets_user_exists
|
216
|
+
config = make_secrets_config
|
217
|
+
|
218
|
+
assert_true config.module(:ftp).user? 'john'
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_global_secrets_user_exists
|
222
|
+
config = make_secrets_config
|
223
|
+
|
224
|
+
assert_true config.user? 'bob'
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_module_overriden_secrets_does_not_include_global_users
|
228
|
+
config = make_secrets_config
|
229
|
+
|
230
|
+
assert_false config.module(:ftp).user? 'bob'
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rsync_config
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillaume Bodi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: treetop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.4.14
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.4.14
|
69
|
+
description: Utility gem to manage rsyncd config files
|
70
|
+
email:
|
71
|
+
- bodi.giyomu@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/rsync_config.rb
|
82
|
+
- lib/rsync_config/config.rb
|
83
|
+
- lib/rsync_config/module.rb
|
84
|
+
- lib/rsync_config/parser.rb
|
85
|
+
- lib/rsync_config/parser/config_file.treetop
|
86
|
+
- lib/rsync_config/parser/node_extension.rb
|
87
|
+
- lib/rsync_config/parser/secrets_file.treetop
|
88
|
+
- lib/rsync_config/propertiable.rb
|
89
|
+
- lib/rsync_config/user_management.rb
|
90
|
+
- lib/rsync_config/version.rb
|
91
|
+
- rsync_config.gemspec
|
92
|
+
- test/etc/out/.keep
|
93
|
+
- test/etc/rsyncd.conf
|
94
|
+
- test/etc/rsyncd_with_secrets.conf
|
95
|
+
- test/etc/secrets_ftp.conf
|
96
|
+
- test/etc/secrets_top.conf
|
97
|
+
- test/test_rsync_config.rb
|
98
|
+
homepage: ''
|
99
|
+
licenses:
|
100
|
+
- GPLv3
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.0.5
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Utility gem to manage rsyncd config files
|
122
|
+
test_files:
|
123
|
+
- test/etc/out/.keep
|
124
|
+
- test/etc/rsyncd.conf
|
125
|
+
- test/etc/rsyncd_with_secrets.conf
|
126
|
+
- test/etc/secrets_ftp.conf
|
127
|
+
- test/etc/secrets_top.conf
|
128
|
+
- test/test_rsync_config.rb
|