vidibus-pureftpd 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +74 -35
- data/Rakefile +1 -1
- data/config/locales/de.yml +8 -0
- data/config/locales/en.yml +8 -0
- data/lib/vidibus-pureftpd.rb +3 -1
- data/lib/vidibus/pureftpd.rb +15 -52
- data/lib/vidibus/pureftpd/user.rb +106 -29
- metadata +36 -2
data/README.md
CHANGED
@@ -1,43 +1,69 @@
|
|
1
1
|
# Vidibus::Pureftpd
|
2
2
|
|
3
|
-
|
3
|
+
Provides an ActiveModel-based abstraction of Pure-FTPd's [virtual users](http://download.pureftpd.org/pub/pure-ftpd/doc/README.Virtual-Users). [Pure-FTPd](http://www.pureftpd.org/project/pure-ftpd) is a free, secure, production-quality and standard-conformant FTP server.
|
4
4
|
|
5
|
-
This gem is part of [Vidibus](http://vidibus.org), an open source toolset for building distributed (video) applications.
|
6
|
-
|
7
|
-
## Installation
|
8
|
-
|
9
|
-
Add the dependency to the Gemfile of your application: `gem 'vidibus-pureftpd'`. Then call bundle install on your console.
|
5
|
+
This gem is part of [Vidibus](http://vidibus.org), an open source toolset for building distributed (video) applications. It has been tested with Ruby 1.8.7 and 1.9.3.
|
10
6
|
|
11
7
|
|
12
8
|
## Usage
|
13
9
|
|
14
|
-
|
10
|
+
Basic CRUD operations are available for `Vidibus::Pureftpd::User`:
|
15
11
|
|
16
|
-
```
|
17
|
-
Vidibus::Pureftpd.
|
18
|
-
:login => '
|
12
|
+
```ruby
|
13
|
+
user = Vidibus::Pureftpd::User.new(
|
14
|
+
:login => 'my_user',
|
19
15
|
:password => 'verysecret',
|
20
|
-
:directory => '/
|
21
|
-
|
22
|
-
|
16
|
+
:directory => '/home/my_user'
|
17
|
+
)
|
18
|
+
# => returns a new user instance
|
23
19
|
|
24
|
-
|
20
|
+
user = Vidibus::Pureftpd::User.create(
|
21
|
+
:login => 'my_user',
|
22
|
+
:password => 'verysecret',
|
23
|
+
:directory => '/home/my_user'
|
24
|
+
)
|
25
|
+
# => creates a new user and returns the instance
|
25
26
|
|
27
|
+
user = Vidibus::Pureftpd::User.find_by_login('my_user')
|
28
|
+
# => reads user from database and returns an user instance
|
29
|
+
|
30
|
+
user.save
|
31
|
+
# => saves user to database and returns true
|
32
|
+
|
33
|
+
user.destroy
|
34
|
+
# => removes user from database
|
26
35
|
```
|
27
|
-
Vidibus::Pureftpd.delete_user(:login => 'someuser')
|
28
|
-
```
|
29
36
|
|
30
|
-
|
37
|
+
Additionally, some methods are provided for your convenience:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
user.valid?
|
41
|
+
# => returns true if user is valid
|
42
|
+
|
43
|
+
user.persisted?
|
44
|
+
# => returns true if user has been saved to database
|
31
45
|
|
46
|
+
user.reload
|
47
|
+
# => reloads user from database
|
32
48
|
```
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
49
|
+
|
50
|
+
By default, `Vidibus::Pureftpd` will use these settings which you may override:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Vidibus::Pureftpd.settings[:sysuser] # => 'pureftpd_user'
|
54
|
+
Vidibus::Pureftpd.settings[:sysgroup] # => 'pureftpd_group'
|
55
|
+
Vidibus::Pureftpd.settings[:password_file] # => '/etc/pure-ftpd/pureftpd.passwd'
|
37
56
|
```
|
38
57
|
|
39
58
|
|
40
|
-
##
|
59
|
+
## Installation
|
60
|
+
|
61
|
+
Add the dependency to the Gemfile of your application: `gem 'vidibus-pureftpd'`. Then call bundle install on your console.
|
62
|
+
|
63
|
+
Installation of the Pure-FTPd server itself is quite simple:
|
64
|
+
|
65
|
+
|
66
|
+
### Install Pure-FTPd on Debian Lenny
|
41
67
|
|
42
68
|
Get the package:
|
43
69
|
|
@@ -112,13 +138,13 @@ Finally, (re)start Pure-FTPd:
|
|
112
138
|
For more instructions, please [check this resource](http://linux.justinhartman.com/PureFTPd_Installation_and_Setup).
|
113
139
|
|
114
140
|
|
115
|
-
|
141
|
+
### Install Pure-FTPd on OSX (required for testing this gem)
|
116
142
|
|
117
143
|
```
|
118
144
|
brew install pure-ftpd
|
119
145
|
```
|
120
146
|
|
121
|
-
Create the user `pureftpd_user` with ID 483:
|
147
|
+
In order to perform the tests, a certain user is required. Create the user `pureftpd_user` with ID 483:
|
122
148
|
|
123
149
|
```
|
124
150
|
sudo dscl . create /Users/pureftpd_user uid 483
|
@@ -127,7 +153,7 @@ sudo dscl . create /Users/pureftpd_user UserShell /etc/pure-ftpd
|
|
127
153
|
sudo dscl . create /Users/pureftpd_user NFSHomeDirectory /dev/null
|
128
154
|
```
|
129
155
|
|
130
|
-
|
156
|
+
Then create the group `pureftpd_group`, also with ID 483:
|
131
157
|
|
132
158
|
```
|
133
159
|
sudo dscl . create /Groups/pureftpd_group gid 483
|
@@ -141,21 +167,14 @@ dscacheutil -q user
|
|
141
167
|
dscacheutil -q group
|
142
168
|
```
|
143
169
|
|
144
|
-
Ensure that the database exists and is writable for the user that executes RSpec:
|
145
|
-
|
146
|
-
```
|
147
|
-
sudo mkdir /etc/pure-ftpd/
|
148
|
-
sudo touch /etc/pure-ftpd/pureftpd.passwd
|
149
|
-
sudo chown -R `whoami` /etc/pure-ftpd/
|
150
|
-
```
|
151
170
|
|
152
|
-
|
171
|
+
Not needed for testing, but to start the server, type:
|
153
172
|
|
154
173
|
```
|
155
174
|
sudo /usr/local/sbin/pure-ftpd &
|
156
175
|
```
|
157
176
|
|
158
|
-
You should be able to connect via ftp:
|
177
|
+
You should now be able to connect via ftp:
|
159
178
|
|
160
179
|
```
|
161
180
|
ftp localhost
|
@@ -164,7 +183,27 @@ ftp localhost
|
|
164
183
|
Shut it down with:
|
165
184
|
|
166
185
|
```
|
167
|
-
pkill pure-ftpd
|
186
|
+
sudo pkill pure-ftpd
|
187
|
+
```
|
188
|
+
|
189
|
+
|
190
|
+
## TODO
|
191
|
+
|
192
|
+
Implement all user options offered by Pure-FTPd:
|
193
|
+
|
194
|
+
```
|
195
|
+
-t <download bandwidth>
|
196
|
+
-T <upload bandwidth>
|
197
|
+
-n <max number of files>
|
198
|
+
-N <max Mbytes>
|
199
|
+
-q <upload ratio>
|
200
|
+
-Q <download ratio>
|
201
|
+
-r <allow client host>
|
202
|
+
-R <deny client host>
|
203
|
+
-i <allow local host>
|
204
|
+
-I <deny local host>
|
205
|
+
-y <max number of concurrent sessions>
|
206
|
+
-z <hhmm>-<hhmm>
|
168
207
|
```
|
169
208
|
|
170
209
|
|
data/Rakefile
CHANGED
data/lib/vidibus-pureftpd.rb
CHANGED
data/lib/vidibus/pureftpd.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module Vidibus
|
2
2
|
module Pureftpd
|
3
|
-
VERSION = '0.1.0'
|
4
|
-
|
5
3
|
class Error < StandardError; end
|
6
4
|
|
7
5
|
class << self
|
@@ -17,60 +15,25 @@ module Vidibus
|
|
17
15
|
}
|
18
16
|
end
|
19
17
|
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# :login, :password, :directory
|
23
|
-
def add_user(options)
|
24
|
-
unless options.keys?(:login, :password, :directory)
|
25
|
-
raise ArgumentError.new('Required options are :login, :password, :directory')
|
26
|
-
end
|
27
|
-
password = options.delete(:password)
|
28
|
-
cmd = 'pure-pw useradd %{login} -f %{password_file} -u %{sysuser} -g %{sysgroup} -d %{directory} -m' % settings.merge(options)
|
29
|
-
perform(cmd) do |stdin, stdout, stderr|
|
30
|
-
stdin.puts(password)
|
31
|
-
stdin.puts(password)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Deletes an existing user.
|
36
|
-
# Required options:
|
37
|
-
# :login
|
38
|
-
def delete_user(options)
|
39
|
-
unless options.key?(:login)
|
40
|
-
raise ArgumentError.new('Required option is :login')
|
41
|
-
end
|
42
|
-
cmd = 'pure-pw userdel %{login} -f %{password_file} -m' % settings.merge(options)
|
43
|
-
perform(cmd)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Changes password of existing user.
|
47
|
-
# Required options:
|
48
|
-
# :login, :password
|
49
|
-
def change_password(options)
|
50
|
-
unless options.keys?(:login, :password)
|
51
|
-
raise ArgumentError.new('Required options are :login, :password')
|
52
|
-
end
|
53
|
-
password = options.delete(:password)
|
54
|
-
cmd = 'pure-pw passwd %{login} -f %{password_file} -m' % settings.merge(options)
|
55
|
-
perform(cmd) do |stdin, stdout, stderr|
|
56
|
-
stdin.puts(password)
|
57
|
-
stdin.puts(password)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
protected
|
62
|
-
|
63
|
-
# Performs given command. Accepts a block with |stdin, stdout, stderr|.
|
18
|
+
# Performs given command prefixed with pure-pw.
|
19
|
+
# Accepts a block with |stdin, stdout, stderr|.
|
64
20
|
def perform(cmd, &block)
|
21
|
+
cmd = "pure-pw #{cmd}"
|
65
22
|
error = ''
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
23
|
+
output = nil
|
24
|
+
|
25
|
+
pid, stdin, stdout, stderr = POSIX::Spawn::popen4(cmd)
|
26
|
+
yield(stdin, stdout, stderr) if block_given?
|
27
|
+
error = stderr.read
|
28
|
+
output = stdout.read
|
29
|
+
|
70
30
|
unless error == ''
|
71
|
-
raise Error.new("
|
31
|
+
raise Error.new("Pure-FTPd returned an error:\n#{cmd}\n\n#{error}")
|
72
32
|
end
|
73
|
-
|
33
|
+
output
|
34
|
+
ensure
|
35
|
+
[stdin, stdout, stderr].each { |io| io.close if !io.closed? }
|
36
|
+
Process::waitpid(pid)
|
74
37
|
end
|
75
38
|
end
|
76
39
|
end
|
@@ -4,27 +4,56 @@ module Vidibus
|
|
4
4
|
include ActiveModel::Validations
|
5
5
|
include ActiveModel::Dirty
|
6
6
|
|
7
|
-
|
7
|
+
class Error < Vidibus::Pureftpd::Error; end
|
8
|
+
class DocumentNotFound < Error; end
|
9
|
+
|
10
|
+
ACCESSSORS = [:login, :password, :directory]
|
11
|
+
|
12
|
+
ACCESSSORS.each do |attribute|
|
13
|
+
class_eval <<-EOS
|
14
|
+
def #{attribute}=(value)
|
15
|
+
unless @#{attribute} == value
|
16
|
+
#{attribute}_will_change!
|
17
|
+
@#{attribute} = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{attribute}
|
22
|
+
@#{attribute}
|
23
|
+
end
|
24
|
+
EOS
|
25
|
+
end
|
8
26
|
|
9
|
-
|
10
|
-
|
27
|
+
define_attribute_methods ACCESSSORS
|
28
|
+
|
29
|
+
validates :password, :directory, :presence => true
|
30
|
+
validates :login, :format => { :with => /^[a-z_]+$/ }
|
31
|
+
validate :unique_login?, :if => :login_changed?
|
32
|
+
validate :valid_directory?, :if => :directory_changed?
|
11
33
|
|
12
34
|
def initialize(values = {})
|
13
|
-
attributes = values
|
35
|
+
self.attributes = values
|
36
|
+
@persisted = false
|
14
37
|
end
|
15
38
|
|
16
39
|
def save
|
40
|
+
return false unless valid? && changed?
|
17
41
|
persisted? ? update : create
|
42
|
+
@previously_changed = changes
|
43
|
+
@changed_attributes.clear
|
44
|
+
@persisted = true
|
18
45
|
end
|
19
46
|
|
20
47
|
def destroy
|
21
|
-
|
48
|
+
return false unless persisted? && login_was
|
49
|
+
cmd = "userdel #{login_was} -f %{password_file} -m" % settings
|
22
50
|
perform(cmd)
|
23
|
-
|
51
|
+
instance_variable_set('@persisted', false)
|
52
|
+
self
|
24
53
|
end
|
25
54
|
|
26
55
|
def persisted?
|
27
|
-
@
|
56
|
+
@persisted
|
28
57
|
end
|
29
58
|
|
30
59
|
def attributes
|
@@ -35,50 +64,91 @@ module Vidibus
|
|
35
64
|
hash
|
36
65
|
end
|
37
66
|
|
38
|
-
def attributes=(
|
39
|
-
|
67
|
+
def attributes=(hash)
|
68
|
+
hash.each do |key, value|
|
69
|
+
send("#{key}=", value)
|
70
|
+
end
|
40
71
|
end
|
41
72
|
|
42
73
|
def reload
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
74
|
+
if persisted? && login_was && (user = User.find_by_login(login_was))
|
75
|
+
self.attributes = user.attributes
|
76
|
+
self
|
77
|
+
else
|
78
|
+
raise DocumentNotFound
|
79
|
+
end
|
48
80
|
end
|
49
81
|
|
50
82
|
class << self
|
51
83
|
|
52
|
-
def
|
53
|
-
cmd =
|
54
|
-
|
84
|
+
def find_by_login(login)
|
85
|
+
cmd = "show #{login} -f %{password_file}" %
|
86
|
+
Vidibus::Pureftpd.settings
|
87
|
+
begin
|
88
|
+
data = Vidibus::Pureftpd.perform(cmd)
|
89
|
+
rescue Vidibus::Pureftpd::Error => e
|
90
|
+
if e.message.match('Unable to fetch info about user')
|
91
|
+
return nil
|
92
|
+
else
|
93
|
+
raise e
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
55
97
|
attributes = {}
|
98
|
+
data.scan(/(#{ACCESSSORS.join('|')})\s*:\s*(.+)/i).each do |key, value|
|
99
|
+
attributes[key.downcase] = value
|
100
|
+
end
|
101
|
+
|
56
102
|
User.new(attributes).tap do |user|
|
57
|
-
user.instance_variable_set('@
|
103
|
+
user.instance_variable_set('@persisted', true)
|
58
104
|
end
|
59
105
|
end
|
60
106
|
|
61
107
|
def create(attributes)
|
62
|
-
|
108
|
+
new(attributes).tap do |user|
|
109
|
+
user.save
|
110
|
+
end
|
63
111
|
end
|
64
112
|
end
|
65
113
|
|
66
114
|
private
|
67
115
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
116
|
+
def settings
|
117
|
+
attributes.merge(Vidibus::Pureftpd.settings)
|
118
|
+
end
|
119
|
+
|
120
|
+
def unique_login?
|
121
|
+
return unless login.present?
|
122
|
+
if User.find_by_login(login)
|
123
|
+
self.errors.add(:login, :taken)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def valid_directory?
|
128
|
+
return unless directory.present?
|
129
|
+
if !File.exist?(directory)
|
130
|
+
self.errors.add(:directory, :not_existent)
|
131
|
+
elsif !File.directory?(directory)
|
132
|
+
self.errors.add(:directory, :not_a_directory)
|
133
|
+
elsif !File.readable?(directory)
|
134
|
+
self.errors.add(:directory, :not_readable)
|
135
|
+
elsif !File.writable?(directory)
|
136
|
+
self.errors.add(:directory, :not_writable)
|
137
|
+
end
|
71
138
|
end
|
72
139
|
|
73
140
|
def update
|
74
141
|
if login_changed?
|
75
142
|
a = attributes
|
143
|
+
destroy
|
144
|
+
User.create(a)
|
76
145
|
end
|
77
146
|
if password_changed?
|
78
147
|
update_password
|
79
148
|
end
|
80
|
-
|
81
|
-
|
149
|
+
if changes.except(:login, :password).any?
|
150
|
+
update_attributes
|
151
|
+
end
|
82
152
|
end
|
83
153
|
|
84
154
|
def create
|
@@ -87,14 +157,22 @@ module Vidibus
|
|
87
157
|
end
|
88
158
|
|
89
159
|
def update_password
|
90
|
-
cmd = '
|
160
|
+
cmd = 'passwd %{login} -f %{password_file} -m' % settings
|
91
161
|
perform_with_password_input(cmd)
|
92
162
|
end
|
93
163
|
|
164
|
+
def update_attributes
|
165
|
+
cmd = 'usermod %{login} -d %{directory} -f %{password_file} -m' % settings
|
166
|
+
perform(cmd)
|
167
|
+
end
|
168
|
+
|
169
|
+
# TODO: Dry this up. But args << &Proc.new isn't possible.
|
94
170
|
def perform(cmd)
|
95
|
-
|
96
|
-
|
97
|
-
|
171
|
+
if block_given?
|
172
|
+
Vidibus::Pureftpd.perform(cmd, &Proc.new)
|
173
|
+
else
|
174
|
+
Vidibus::Pureftpd.perform(cmd)
|
175
|
+
end
|
98
176
|
end
|
99
177
|
|
100
178
|
def perform_with_password_input(cmd)
|
@@ -103,7 +181,6 @@ module Vidibus
|
|
103
181
|
stdin.puts(password)
|
104
182
|
end
|
105
183
|
end
|
106
|
-
|
107
184
|
end
|
108
185
|
end
|
109
186
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vidibus-pureftpd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,6 +11,22 @@ bindir: bin
|
|
11
11
|
cert_chain: []
|
12
12
|
date: 2012-09-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: posix-spawn
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: vidibus-core_extensions
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -27,6 +43,22 @@ dependencies:
|
|
27
43
|
- - ! '>='
|
28
44
|
- !ruby/object:Gem::Version
|
29
45
|
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activemodel
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
30
62
|
- !ruby/object:Gem::Dependency
|
31
63
|
name: bundler
|
32
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -133,6 +165,8 @@ files:
|
|
133
165
|
- lib/vidibus/pureftpd/version.rb
|
134
166
|
- lib/vidibus/pureftpd.rb
|
135
167
|
- lib/vidibus-pureftpd.rb
|
168
|
+
- config/locales/de.yml
|
169
|
+
- config/locales/en.yml
|
136
170
|
- LICENSE
|
137
171
|
- README.md
|
138
172
|
- Rakefile
|
@@ -150,7 +184,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
150
184
|
version: '0'
|
151
185
|
segments:
|
152
186
|
- 0
|
153
|
-
hash:
|
187
|
+
hash: 848917452145310950
|
154
188
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
189
|
none: false
|
156
190
|
requirements:
|