ftpd 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changelog.md +8 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +34 -4
- data/README.md +64 -40
- data/VERSION +1 -1
- data/doc/rfc-compliance.md +2 -2
- data/examples/example.rb +14 -6
- data/examples/example_spec.rb +93 -0
- data/features/ftp_server/eprt.feature +54 -0
- data/features/ftp_server/epsv.feature +36 -0
- data/features/ftp_server/get_ipv6.feature +43 -0
- data/features/ftp_server/list.feature +9 -0
- data/features/ftp_server/name_list.feature +9 -0
- data/features/ftp_server/pasv.feature +23 -0
- data/features/ftp_server/port.feature +7 -1
- data/features/ftp_server/step_definitions/test_server.rb +4 -0
- data/features/step_definitions/connect.rb +1 -1
- data/features/step_definitions/error_replies.rb +8 -0
- data/features/support/test_server.rb +2 -1
- data/ftpd.gemspec +16 -7
- data/lib/ftpd.rb +2 -0
- data/lib/ftpd/list_path.rb +28 -0
- data/lib/ftpd/protocols.rb +60 -0
- data/lib/ftpd/session.rb +76 -10
- data/lib/ftpd/tls_server.rb +19 -1
- data/spec/list_path_spec.rb +21 -0
- data/spec/protocols_spec.rb +139 -0
- data/spec/spec_helper.rb +1 -0
- metadata +34 -48
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 402da43ab955cc1f7ec47d5080556ae09c85da9d
|
4
|
+
data.tar.gz: 6cde23e0be18f61b46e544809127337623ee55e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2a434cd8438916a37900e2e6dc37a56c9b87d0628ed979f18e9ed8e1bba4f4a31252c012de9bfaa77b7078440212231ae1b073a848a5d80f5f43819277df0399
|
7
|
+
data.tar.gz: a9a3d3875c56cb3d8f5ba701291e9c2c22668b3d42534cdb34eb79fc81f781e49b00c42eb8cab8b5a13f67e47e2b846ddb93e7f8faf38086b4b14205a10118f1
|
data/Changelog.md
CHANGED
@@ -2,6 +2,14 @@ This is the change log for the main branch of ftpd, which supports
|
|
2
2
|
Ruby 1.9 and greater. For ruby 1.8.7, please use the latest version
|
3
3
|
before 0.8.0.
|
4
4
|
|
5
|
+
### 0.9.0
|
6
|
+
|
7
|
+
Enhancements
|
8
|
+
|
9
|
+
* Added example showing ftp used as a test harness with rspec
|
10
|
+
* Ignore LIST/NLST switches such as "-a"
|
11
|
+
* Support IPV6
|
12
|
+
|
5
13
|
### 0.8.0
|
6
14
|
|
7
15
|
Administration
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
addressable (2.3.5)
|
4
5
|
builder (3.2.2)
|
5
6
|
cucumber (1.3.8)
|
6
7
|
builder (>= 2.1.2)
|
@@ -10,20 +11,49 @@ GEM
|
|
10
11
|
multi_test (>= 0.0.2)
|
11
12
|
diff-lcs (1.2.4)
|
12
13
|
double-bag-ftps (0.1.1)
|
14
|
+
faraday (0.8.8)
|
15
|
+
multipart-post (~> 1.2.0)
|
13
16
|
gherkin (2.12.1)
|
14
17
|
multi_json (~> 1.3)
|
15
|
-
git (1.2.
|
16
|
-
|
18
|
+
git (1.2.6)
|
19
|
+
github_api (0.10.1)
|
20
|
+
addressable
|
21
|
+
faraday (~> 0.8.1)
|
22
|
+
hashie (>= 1.2)
|
23
|
+
multi_json (~> 1.4)
|
24
|
+
nokogiri (~> 1.5.2)
|
25
|
+
oauth2
|
26
|
+
hashie (2.0.5)
|
27
|
+
highline (1.6.19)
|
28
|
+
httpauth (0.2.0)
|
29
|
+
jeweler (1.8.7)
|
30
|
+
builder
|
17
31
|
bundler (~> 1.0)
|
18
32
|
git (>= 1.2.5)
|
33
|
+
github_api (= 0.10.1)
|
34
|
+
highline (>= 1.6.15)
|
35
|
+
nokogiri (= 1.5.10)
|
19
36
|
rake
|
20
37
|
rdoc
|
21
38
|
json (1.8.0)
|
39
|
+
jwt (0.1.8)
|
40
|
+
multi_json (>= 1.5)
|
22
41
|
memoizer (1.0.1)
|
23
42
|
multi_json (1.8.0)
|
24
43
|
multi_test (0.0.2)
|
44
|
+
multi_xml (0.5.5)
|
45
|
+
multipart-post (1.2.0)
|
46
|
+
nokogiri (1.5.10)
|
47
|
+
oauth2 (0.9.2)
|
48
|
+
faraday (~> 0.8)
|
49
|
+
httpauth (~> 0.2)
|
50
|
+
jwt (~> 0.1.4)
|
51
|
+
multi_json (~> 1.0)
|
52
|
+
multi_xml (~> 0.5)
|
53
|
+
rack (~> 1.2)
|
54
|
+
rack (1.5.2)
|
25
55
|
rake (10.1.0)
|
26
|
-
rdoc (4.0.
|
56
|
+
rdoc (4.0.1)
|
27
57
|
json (~> 1.4)
|
28
58
|
redcarpet (3.0.0)
|
29
59
|
rspec (2.14.1)
|
@@ -43,7 +73,7 @@ PLATFORMS
|
|
43
73
|
DEPENDENCIES
|
44
74
|
cucumber
|
45
75
|
double-bag-ftps
|
46
|
-
jeweler
|
76
|
+
jeweler
|
47
77
|
memoizer (~> 1.0.1)
|
48
78
|
rake
|
49
79
|
redcarpet
|
data/README.md
CHANGED
@@ -34,32 +34,35 @@ This is examples/hello_world.rb, a bare minimum FTP server. It allows
|
|
34
34
|
any user/password, and serves files in a temporary directory. It
|
35
35
|
binds to an ephemeral port on the local interface:
|
36
36
|
|
37
|
-
|
38
|
-
|
37
|
+
```ruby
|
38
|
+
require 'ftpd'
|
39
|
+
require 'tmpdir'
|
39
40
|
|
40
|
-
|
41
|
+
class Driver
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
def initialize(temp_dir)
|
44
|
+
@temp_dir = temp_dir
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
def authenticate(user, password)
|
48
|
+
true
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
51
|
+
def file_system(user)
|
52
|
+
Ftpd::DiskFileSystem.new(@temp_dir)
|
53
|
+
end
|
53
54
|
|
54
|
-
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
Dir.mktmpdir do |temp_dir|
|
58
|
+
driver = Driver.new(temp_dir)
|
59
|
+
server = Ftpd::FtpServer.new(driver)
|
60
|
+
server.interface = '127.0.0.1'
|
61
|
+
server.start
|
62
|
+
puts "Server listening on port #{server.bound_port}"
|
63
|
+
gets
|
64
|
+
end
|
65
|
+
```
|
63
66
|
|
64
67
|
A more full-featured example that allows TLS and takes options is in
|
65
68
|
examples/example.rb
|
@@ -115,15 +118,17 @@ Here are the methods a file system may expose:
|
|
115
118
|
|
116
119
|
Ftpd includes a disk based file system:
|
117
120
|
|
118
|
-
|
121
|
+
```ruby
|
122
|
+
class Driver
|
119
123
|
|
120
|
-
|
124
|
+
...
|
121
125
|
|
122
|
-
|
123
|
-
|
124
|
-
|
126
|
+
def file_system(user)
|
127
|
+
Ftpd::DiskFileSystem.new('/var/lib/ftp')
|
128
|
+
end
|
125
129
|
|
126
|
-
|
130
|
+
end
|
131
|
+
```
|
127
132
|
|
128
133
|
**Warning**: The DiskFileSystem allows file and directory modification
|
129
134
|
including writing, renaming, deleting, etc. If you want a read-only
|
@@ -146,25 +151,30 @@ allows only the operations you want, or which mixes the predefined
|
|
146
151
|
modules with your customizations, as in this silly example that allows
|
147
152
|
uploads but then throws them away.
|
148
153
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
154
|
+
```ruby
|
155
|
+
class BlackHole
|
156
|
+
def write(ftp_path, contents)
|
157
|
+
end
|
158
|
+
end
|
153
159
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
160
|
+
class CustomDiskFileSystem
|
161
|
+
include DiskFileSystem::Base
|
162
|
+
include DiskFileSystem::Read
|
163
|
+
include BlackHole
|
164
|
+
end
|
165
|
+
```
|
159
166
|
|
160
167
|
## Configuration
|
161
168
|
|
162
169
|
Configuration is done via accessors on {Ftpd::FtpServer}. For
|
163
170
|
example, to set the session timeout to 10 minutes:
|
164
171
|
|
165
|
-
|
166
|
-
|
167
|
-
|
172
|
+
```ruby
|
173
|
+
server = Ftpd::FtpServer.new(driver)
|
174
|
+
server.session_timeout = 10 * 60
|
175
|
+
server.interface = '127.0.0.1'
|
176
|
+
server.start
|
177
|
+
```
|
168
178
|
|
169
179
|
You can set any of these attributes before starting the server:
|
170
180
|
|
@@ -233,6 +243,9 @@ To log to a file:
|
|
233
243
|
* Implements [RFC-2389](http://tools.ietf.org/rfc/rfc2389.txt)
|
234
244
|
(Feature negotiation mechanism for the File Transfer Protocol)
|
235
245
|
|
246
|
+
* Implements [RFC-2428](http://tools.ietf.org/rfc/rfc2428.txt) (FTP
|
247
|
+
Extensions for IPv6 and NATs)
|
248
|
+
|
236
249
|
* Implements enough of
|
237
250
|
[RFC-4217](http://tools.ietf.org/rfc/rfc4217.txt) (Securing FTP with
|
238
251
|
TLS) to get by.
|
@@ -244,8 +257,8 @@ See [RFC Compliance](doc/rfc-compliance.md) for details
|
|
244
257
|
The tests pass with these Rubies:
|
245
258
|
|
246
259
|
* ruby-1.8.7-p371
|
247
|
-
* ruby-1.9.3-
|
248
|
-
* ruby-2.0.0-
|
260
|
+
* ruby-1.9.3-p448
|
261
|
+
* ruby-2.0.0-p247
|
249
262
|
|
250
263
|
For Ruby 1.8, use an ftpd version before 0.8. In your Gemfile:
|
251
264
|
|
@@ -312,6 +325,17 @@ Wayne Conrad <wconrad@yagni.com>
|
|
312
325
|
Thanks to Databill, LLC, which supported the creation of this library,
|
313
326
|
and granted permission to donate it to the community.
|
314
327
|
|
328
|
+
### Contributors
|
329
|
+
|
330
|
+
Among those who have improved ftpd are:
|
331
|
+
|
332
|
+
* Alfonso Cora
|
333
|
+
* Bjoern B. Dorra
|
334
|
+
* Larry. W. Cashdollar
|
335
|
+
* Michael de Silva
|
336
|
+
|
337
|
+
Thank you!
|
338
|
+
|
315
339
|
## See also
|
316
340
|
|
317
341
|
* [Changelog](Changelog.md)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.9.0
|
data/doc/rfc-compliance.md
CHANGED
@@ -213,8 +213,8 @@ use over various network protocols, and the new response codes 522 and
|
|
213
213
|
* [link](http://tools.ietf.org/rfc/rfc2428.txt)
|
214
214
|
|
215
215
|
<pre>
|
216
|
-
EPRT
|
217
|
-
EPSV
|
216
|
+
EPRT Yes 0.9.0 Set active data connection over IPv4 or IPv6
|
217
|
+
EPSV Yes 0.9.0 Set passive data connection over IPv4 or IPv6
|
218
218
|
</pre>
|
219
219
|
|
220
220
|
##RFC-2577 - FTP Security Considerations
|
data/examples/example.rb
CHANGED
@@ -5,6 +5,7 @@ unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
|
5
5
|
end
|
6
6
|
|
7
7
|
require 'ftpd'
|
8
|
+
require 'ipaddr'
|
8
9
|
require 'optparse'
|
9
10
|
|
10
11
|
module Example
|
@@ -26,7 +27,7 @@ module Example
|
|
26
27
|
attr_reader :user
|
27
28
|
|
28
29
|
def initialize(argv)
|
29
|
-
@interface = '
|
30
|
+
@interface = '127.0.0.1'
|
30
31
|
@tls = :explicit
|
31
32
|
@port = 0
|
32
33
|
@auth_level = 'password'
|
@@ -185,8 +186,6 @@ module Example
|
|
185
186
|
|
186
187
|
private
|
187
188
|
|
188
|
-
HOST = 'localhost'
|
189
|
-
|
190
189
|
def auth_level
|
191
190
|
Ftpd.const_get("AUTH_#{@args.auth_level.upcase}")
|
192
191
|
end
|
@@ -210,10 +209,10 @@ module Example
|
|
210
209
|
puts "Port: #{@server.bound_port}"
|
211
210
|
puts "User: #{user.inspect}"
|
212
211
|
puts "Pass: #{password.inspect}" if auth_level >= Ftpd::AUTH_PASSWORD
|
213
|
-
puts "
|
212
|
+
puts "Account: #{account.inspect}" if auth_level >= Ftpd::AUTH_ACCOUNT
|
214
213
|
puts "TLS: #{@args.tls}"
|
215
214
|
puts "Directory: #{@data_dir}"
|
216
|
-
puts "URI: ftp://#{
|
215
|
+
puts "URI: ftp://#{connection_host}:#{@server.bound_port}"
|
217
216
|
puts "PID: #{$$}"
|
218
217
|
end
|
219
218
|
|
@@ -221,7 +220,7 @@ module Example
|
|
221
220
|
command_path = '/tmp/connect-to-example-ftp-server.sh'
|
222
221
|
File.open(command_path, 'w') do |file|
|
223
222
|
file.puts "#!/bin/bash"
|
224
|
-
file.puts "ftp $FTP_ARGS #{
|
223
|
+
file.puts "ftp $FTP_ARGS #{connection_host} #{@server.bound_port}"
|
225
224
|
end
|
226
225
|
system("chmod +x #{command_path}")
|
227
226
|
puts "Connection script written to #{command_path}"
|
@@ -253,6 +252,15 @@ module Example
|
|
253
252
|
@args.debug && Logger.new($stdout)
|
254
253
|
end
|
255
254
|
|
255
|
+
def connection_host
|
256
|
+
addr = IPAddr.new(@server.interface)
|
257
|
+
if addr.ipv6?
|
258
|
+
'::1'
|
259
|
+
else
|
260
|
+
'127.0.0.1'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
256
264
|
end
|
257
265
|
end
|
258
266
|
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Authour: Michael de Silva <michael@mwdesilva.com>
|
4
|
+
# CEO @ http://omakaselabs.com / Mentor @ http://railsphd.com
|
5
|
+
# https://twitter.com/bsodmike / https://github.com/bsodmike
|
6
|
+
|
7
|
+
# This is an example for using Ftpd as a means for spec driving
|
8
|
+
# interaction with a 'dummy' ftp server via RSpec. In this example we
|
9
|
+
# assume the client is implemented via `Fetcher::FTPFetcher`.
|
10
|
+
|
11
|
+
unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
12
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'net/ftp'
|
16
|
+
require 'ftpd'
|
17
|
+
require 'tmpdir'
|
18
|
+
|
19
|
+
# This is an example client spec driven via the use of Ftpd within the
|
20
|
+
# specs. The specs spawn a 'dummy' Ftpd server and ensure this client
|
21
|
+
# operates as expected.
|
22
|
+
|
23
|
+
module Fetcher
|
24
|
+
|
25
|
+
# This is the code under test, a simple fetcher that logs into an
|
26
|
+
# FTP site, changes to a directlry, and gets a list of files.
|
27
|
+
|
28
|
+
class FTPFetcher
|
29
|
+
|
30
|
+
# @param host [String] ftp host to connect to.
|
31
|
+
# @param user [String] username.
|
32
|
+
# @param pwd [String] password.
|
33
|
+
# @param dir [String] remote directory to change to.
|
34
|
+
|
35
|
+
def initialize(host, user, pwd, dir)
|
36
|
+
@host = host
|
37
|
+
@user = user
|
38
|
+
@pwd = pwd
|
39
|
+
@dir = dir
|
40
|
+
@ftp = Net::FTP.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param port [Fixnum] port to connect to, 21 by default.
|
44
|
+
# @return [Array] list of files in the current directory.
|
45
|
+
|
46
|
+
def connect_and_list(port = 21)
|
47
|
+
@ftp.debug_mode = true if ENV['DEBUG'] == "true"
|
48
|
+
@ftp.passive = true
|
49
|
+
@ftp.connect @host, port
|
50
|
+
@ftp.login @user, @pwd
|
51
|
+
@ftp.chdir @dir
|
52
|
+
@ftp.nlst
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe Fetcher::FTPFetcher do
|
59
|
+
|
60
|
+
# This `Driver` tells Ftpd how to authenticate and how to interact
|
61
|
+
# with the file systme. In this example, the file system is
|
62
|
+
# read-only and contains a single file.
|
63
|
+
|
64
|
+
class Driver
|
65
|
+
def initialize
|
66
|
+
@data_dir = Dir.mktmpdir
|
67
|
+
at_exit {FileUtils.rm_rf(@data_dir)}
|
68
|
+
FileUtils.touch File.expand_path('report.txt', @data_dir)
|
69
|
+
end
|
70
|
+
def authenticate(user, pwd); true; end
|
71
|
+
def file_system(user); Ftpd::ReadOnlyDiskFileSystem.new(@data_dir); end
|
72
|
+
end
|
73
|
+
|
74
|
+
let(:server) do
|
75
|
+
server = Ftpd::FtpServer.new(Driver.new)
|
76
|
+
server.interface = "127.0.0.1"
|
77
|
+
server.start
|
78
|
+
server
|
79
|
+
end
|
80
|
+
|
81
|
+
let(:subject) do
|
82
|
+
Fetcher::FTPFetcher.new('127.0.0.1', 'user', 'password', '/')
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#connect_and_list" do
|
86
|
+
|
87
|
+
it "should connect to the FTP server and find 'report.txt' in the Array returned" do
|
88
|
+
result = subject.connect_and_list(server.bound_port)
|
89
|
+
expect(result).to include('report.txt')
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
Feature: EPRT
|
2
|
+
|
3
|
+
As a programmer
|
4
|
+
I want good error messages
|
5
|
+
So that I can correct problems
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the test server is started
|
9
|
+
|
10
|
+
Scenario: Port 1024
|
11
|
+
Given a successful login
|
12
|
+
Then the client successfully sends "EPRT |1|1.2.3.4|1024|"
|
13
|
+
|
14
|
+
Scenario: Port 1023; low ports disallowed
|
15
|
+
Given the test server disallows low data ports
|
16
|
+
And a successful login
|
17
|
+
When the client sends "EPRT |1|2.3.4.3|255|"
|
18
|
+
Then the server returns an unimplemented parameter error
|
19
|
+
|
20
|
+
Scenario: Port out of range
|
21
|
+
Given a successful login
|
22
|
+
When the client sends "EPRT |1|2.3.4.5|65536|"
|
23
|
+
Then the server returns an unimplemented parameter error
|
24
|
+
|
25
|
+
Scenario: Port 1023; low ports allowed
|
26
|
+
Given the test server allows low data ports
|
27
|
+
And a successful login
|
28
|
+
Then the client successfully sends "EPRT |1|2.3.4.3|255|"
|
29
|
+
|
30
|
+
Scenario: Not logged in
|
31
|
+
Given a successful connection
|
32
|
+
When the client sends "EPRT |1|2.3.4.5|6|"
|
33
|
+
Then the server returns a not logged in error
|
34
|
+
|
35
|
+
Scenario: Too few parts
|
36
|
+
Given a successful login
|
37
|
+
When the client sends "EPRT |1|2.3.4|"
|
38
|
+
Then the server returns a syntax error
|
39
|
+
|
40
|
+
Scenario: Too many parts
|
41
|
+
Given a successful login
|
42
|
+
When the client sends "EPRT |1|2.3.4|5|6|"
|
43
|
+
Then the server returns a syntax error
|
44
|
+
|
45
|
+
Scenario: Unknown network protocol
|
46
|
+
Given a successful login
|
47
|
+
When the client sends "EPRT |3|2.3.4.5|6|"
|
48
|
+
Then the server returns a network protocol not supported error
|
49
|
+
|
50
|
+
Scenario: After "EPSV ALL"
|
51
|
+
Given a successful login
|
52
|
+
Given the client successfully sends "EPSV ALL"
|
53
|
+
When the client sends "EPRT |1|2.3.4.5|6|"
|
54
|
+
Then the server sends a not allowed after epsv all error
|