ftpd 0.0.0.pre1 → 0.0.0.pre2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ftpd might be problematic. Click here for more details.

@@ -0,0 +1,59 @@
1
+ require 'socket'
2
+
3
+ module Ftpd
4
+ class Server
5
+
6
+ def initialize
7
+ @server_socket = make_server_socket
8
+ @server_thread = make_server_thread
9
+ end
10
+
11
+ def port
12
+ @server_socket.addr[1]
13
+ end
14
+
15
+ def close
16
+ # An apparent race condition causes this to sometimes not stop the
17
+ # thread. When this happens, the thread remains blocked in the
18
+ # accept method; I hypothesize that this happens whenever the
19
+ # close happens first. Once this bug is fixed, join on the
20
+ # thread.
21
+ @server_socket.close
22
+ end
23
+
24
+ private
25
+
26
+ def make_server_socket
27
+ return TCPServer.new('localhost', 0)
28
+ end
29
+
30
+ def make_server_thread
31
+ Thread.new do
32
+ Thread.abort_on_exception = true
33
+ loop do
34
+ begin
35
+ begin
36
+ socket = accept
37
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINVAL
38
+ IO.select([@server_socket])
39
+ sleep(0.2)
40
+ retry
41
+ end
42
+ begin
43
+ session(socket)
44
+ ensure
45
+ socket.close
46
+ end
47
+ rescue IOError
48
+ break
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def accept
55
+ @server_socket.accept
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,56 @@
1
+ require 'tempfile'
2
+
3
+ module Ftpd
4
+ class TempDir
5
+
6
+ attr_reader :path
7
+
8
+ class << self
9
+
10
+ def make(basename = nil)
11
+ temp_dir = TempDir.new(basename)
12
+ begin
13
+ yield(temp_dir)
14
+ ensure
15
+ temp_dir.rm unless temp_dir.kept
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ attr_reader :kept
22
+
23
+ def initialize(basename = nil)
24
+ @path = unique_path(basename)
25
+ @kept = false
26
+ ObjectSpace.define_finalizer(self, TempDir.cleanup(path))
27
+ Dir.mkdir(@path)
28
+ end
29
+
30
+ def keep
31
+ @kept = true
32
+ ObjectSpace.undefine_finalizer(self)
33
+ end
34
+
35
+ def rm
36
+ keep
37
+ system("rm -rf #{path.inspect}")
38
+ end
39
+
40
+ private
41
+
42
+ def unique_path(basename)
43
+ tempfile = Tempfile.new(File.basename(basename || $0 || ''))
44
+ path = tempfile.path
45
+ tempfile.close!
46
+ path
47
+ end
48
+
49
+ def TempDir.cleanup(path)
50
+ proc { |id|
51
+ system("/bin/rm -rf #{path.inspect}")
52
+ }
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,57 @@
1
+ require 'openssl'
2
+ require File.expand_path('server', File.dirname(__FILE__))
3
+
4
+ module Ftpd
5
+ class TlsServer < Server
6
+
7
+ def initialize
8
+ @ssl_context = make_ssl_context
9
+ super
10
+ end
11
+
12
+ private
13
+
14
+ def make_server_socket
15
+ ssl_server_socket = OpenSSL::SSL::SSLServer.new(super, @ssl_context);
16
+ ssl_server_socket.start_immediately = false
17
+ ssl_server_socket
18
+ end
19
+
20
+ def accept
21
+ socket = @server_socket.accept
22
+ add_tls_methods_to_socket(socket)
23
+ socket
24
+ end
25
+
26
+ def make_ssl_context
27
+ context = OpenSSL::SSL::SSLContext.new
28
+ File.open(certfile_path) do |certfile|
29
+ context.cert = OpenSSL::X509::Certificate.new(certfile)
30
+ certfile.rewind
31
+ context.key = OpenSSL::PKey::RSA.new(certfile)
32
+ end
33
+ context
34
+ end
35
+
36
+ def certfile_path
37
+ File.expand_path('../../insecure-test-cert.pem',
38
+ File.dirname(__FILE__))
39
+ end
40
+
41
+ def add_tls_methods_to_socket(socket)
42
+ context = @ssl_context
43
+ class << socket
44
+ def ssl_context
45
+ context
46
+ end
47
+ def encrypted?
48
+ !!cipher
49
+ end
50
+ def encrypt
51
+ accept
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ftpd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.pre1
4
+ version: 0.0.0.pre2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-02-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cucumber
16
- requirement: &68632020 !ruby/object:Gem::Requirement
16
+ requirement: &70369190 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.2.1
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *68632020
24
+ version_requirements: *70369190
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: double-bag-ftps
27
- requirement: &68631220 !ruby/object:Gem::Requirement
27
+ requirement: &70366630 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.1.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *68631220
35
+ version_requirements: *70366630
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jeweler
38
- requirement: &68630210 !ruby/object:Gem::Requirement
38
+ requirement: &70397920 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.8.4
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *68630210
46
+ version_requirements: *70397920
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rake
49
- requirement: &68629160 !ruby/object:Gem::Requirement
49
+ requirement: &70395740 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 10.0.3
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *68629160
57
+ version_requirements: *70395740
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rspec
60
- requirement: &68627620 !ruby/object:Gem::Requirement
60
+ requirement: &70395170 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,9 +65,9 @@ dependencies:
65
65
  version: 2.0.1
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *68627620
68
+ version_requirements: *70395170
69
69
  description: ftpd is a pure Ruby FTP server library. It supports implicit and explicit
70
- TLS, and can be used by a text fixture or FTP daemon.
70
+ TLS, suitlble for use by a program such as a test fixture or FTP daemon.
71
71
  email: wconrad@yagni.com
72
72
  executables: []
73
73
  extensions: []
@@ -81,6 +81,7 @@ files:
81
81
  - README.md
82
82
  - Rakefile
83
83
  - VERSION
84
+ - examples/example.rb
84
85
  - features/command_errors.feature
85
86
  - features/delete.feature
86
87
  - features/directory_navigation.feature
@@ -128,12 +129,10 @@ files:
128
129
  - ftpd.gemspec
129
130
  - insecure-test-cert.pem
130
131
  - lib/ftpd.rb
131
- - lib/ftpd/FakeFtpServer.rb
132
- - lib/ftpd/FakeServer.rb
133
- - lib/ftpd/FakeTlsServer.rb
134
- - lib/ftpd/ObjectUtil.rb
135
- - lib/ftpd/TempDir.rb
136
- - lib/ftpd/q.rb
132
+ - lib/ftpd/ftp_server.rb
133
+ - lib/ftpd/server.rb
134
+ - lib/ftpd/temp_dir.rb
135
+ - lib/ftpd/tls_server.rb
137
136
  - rake_tasks/cucumber.rake
138
137
  - rake_tasks/jeweler.rake
139
138
  homepage: http://github.com/wconrad/ftpd
@@ -1,736 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'fileutils'
4
- require 'openssl'
5
- require 'pathname'
6
- require File.expand_path('FakeTlsServer', File.dirname(__FILE__))
7
- require File.expand_path('TempDir', File.dirname(__FILE__))
8
- require File.expand_path('q', File.dirname(__FILE__))
9
-
10
- class FakeFtpServer < FakeTlsServer
11
-
12
- attr_accessor :user
13
- attr_accessor :password
14
- attr_accessor :debug_path
15
- attr_accessor :response_delay
16
- attr_accessor :implicit_tls
17
-
18
- def initialize(data_path)
19
- super()
20
- self.user = 'user'
21
- self.password = 'password'
22
- self.debug_path = '/dev/stdout'
23
- @data_path = Pathname.new(data_path)
24
- @response_delay = 0
25
- @implicit_tls = false
26
- end
27
-
28
- def session(socket)
29
- Session.new(:socket => socket,
30
- :user => user,
31
- :password => password,
32
- :data_path => @data_path,
33
- :debug_path => debug_path,
34
- :response_delay => response_delay,
35
- :implicit_tls => @implicit_tls).run
36
- end
37
-
38
- private
39
-
40
- class Session
41
-
42
- def initialize(args)
43
- @socket = args[:socket]
44
- @socket.encrypt if args[:implicit_tls]
45
- @expected_user = args[:user]
46
- @expected_password = args[:password]
47
- @data_path = @cwd = args[:data_path].realpath
48
- @debug_path = args[:debug_path]
49
- @data_type = 'A'
50
- @mode = 'S'
51
- @format = 'N'
52
- @structure = 'F'
53
- @response_delay = args[:response_delay]
54
- @data_channel_protection_level = :clear
55
- end
56
-
57
- def run
58
- reply "220 FakeFtpServer"
59
- @state = :user
60
- catch :done do
61
- loop do
62
- begin
63
- s = get_command
64
- syntax_error unless s =~ /^(\w+)(?: (.*))?$/
65
- command, argument = $1.downcase, $2
66
- unless VALID_COMMANDS.include?(command)
67
- error "500 Syntax error, command unrecognized: #{s}"
68
- end
69
- method = 'cmd_' + command
70
- unless self.class.private_method_defined?(method)
71
- error "502 Command not implemented: #{command}"
72
- end
73
- send(method, argument)
74
- rescue Error => e
75
- reply e.message
76
- rescue Errno::ECONNRESET, Errno::EPIPE
77
- end
78
- end
79
- end
80
- end
81
-
82
- private
83
-
84
- class Error < StandardError
85
- end
86
-
87
- VALID_COMMANDS = [
88
- "abor",
89
- "acct",
90
- "allo",
91
- "appe",
92
- "auth",
93
- "pbsz",
94
- "cdup",
95
- "cwd",
96
- "dele",
97
- "help",
98
- "list",
99
- "mkd",
100
- "mode",
101
- "nlst",
102
- "noop",
103
- "pass",
104
- "pasv",
105
- "port",
106
- "prot",
107
- "pwd",
108
- "quit",
109
- "rein",
110
- "rest",
111
- "retr",
112
- "rmd",
113
- "rnfr",
114
- "rnto",
115
- "site",
116
- "smnt",
117
- "stat",
118
- "stor",
119
- "stou",
120
- "stru",
121
- "syst",
122
- "type",
123
- "user",
124
- ]
125
-
126
- def cmd_user(argument)
127
- syntax_error unless argument
128
- bad_sequence unless @state == :user
129
- @user = argument
130
- @state = :password
131
- reply "331 Password required"
132
- end
133
-
134
- def bad_sequence
135
- error "503 Bad sequence of commands"
136
- end
137
-
138
- def cmd_pass(argument)
139
- syntax_error unless argument
140
- bad_sequence unless @state == :password
141
- password = argument
142
- if @user != @expected_user || password != @expected_password
143
- @state = :user
144
- error "530 Login incorrect"
145
- end
146
- reply "230 Logged in"
147
- @state = :logged_in
148
- end
149
-
150
- def cmd_quit(argument)
151
- syntax_error if argument
152
- check_logged_in
153
- reply "221 Byebye"
154
- @state = :user
155
- end
156
-
157
- def syntax_error
158
- error "501 Syntax error"
159
- end
160
-
161
- def cmd_port(argument)
162
- check_logged_in
163
- pieces = argument.split(/,/)
164
- syntax_error unless pieces.size == 6
165
- pieces.collect! do |s|
166
- syntax_error unless s =~ /^\d{1,3}$/
167
- i = s.to_i
168
- syntax_error unless (0..255) === i
169
- i
170
- end
171
- @data_hostname = pieces[0..3].join('.')
172
- @data_port = pieces[4] << 8 | pieces[5]
173
- reply "200 PORT command successful"
174
- end
175
-
176
- def cmd_stor(argument)
177
- close_data_server_socket_when_done do
178
- check_logged_in
179
- path = argument
180
- syntax_error unless path
181
- target = target_path(path)
182
- ensure_path_is_in_data_dir(target)
183
- contents = receive_file(path)
184
- write_file(target, contents)
185
- reply "226 Transfer complete"
186
- end
187
- end
188
-
189
- def cmd_retr(argument)
190
- close_data_server_socket_when_done do
191
- check_logged_in
192
- path = argument
193
- syntax_error unless path
194
- target = target_path(path)
195
- ensure_path_is_in_data_dir(target)
196
- contents = read_file(target)
197
- transmit_file(contents)
198
- end
199
- end
200
-
201
- def cmd_dele(argument)
202
- check_logged_in
203
- path = argument
204
- error "501 Path required" unless path
205
- target = target_path(path)
206
- ensure_path_is_in_data_dir(target)
207
- ensure_path_exists target
208
- File.unlink(target)
209
- reply "250 DELE command successful"
210
- end
211
-
212
- def cmd_list(argument)
213
- ls(argument, '-l')
214
- end
215
-
216
- def cmd_nlst(argument)
217
- ls(argument, '-1')
218
- end
219
-
220
- def ls(path, option)
221
- close_data_server_socket_when_done do
222
- check_logged_in
223
- ls_dir, ls_path = get_ls_dir_and_path(path)
224
- list = get_file_list(ls_dir, ls_path, option)
225
- transmit_file(list, 'A')
226
- end
227
- end
228
-
229
- def get_ls_dir_and_path(path)
230
- path = path || '.'
231
- target = target_path(path)
232
- target = realpath(target)
233
- ensure_path_is_in_data_dir(target)
234
- if target.to_s.index(@cwd.to_s) == 0
235
- ls_dir = @cwd
236
- ls_path = target.to_s[@cwd.to_s.length..-1]
237
- else
238
- raise
239
- end
240
- if ls_path =~ /^\//
241
- ls_path = $'
242
- end
243
- [ls_dir, ls_path]
244
- end
245
-
246
- def get_file_list(ls_dir, ls_path, option)
247
- command = [
248
- 'ls',
249
- option,
250
- ls_path,
251
- '2>&1',
252
- ].compact.join(' ')
253
- list = Dir.chdir(ls_dir) do
254
- `#{command}`
255
- end
256
- list = "" if $? != 0
257
- list = list.gsub(/^total \d+\n/, '')
258
- list
259
- end
260
-
261
- def realpath(pathname)
262
- handle_system_error do
263
- basename = File.basename(pathname.to_s)
264
- if is_glob?(basename)
265
- pathname.dirname.realpath + basename
266
- else
267
- pathname.realpath
268
- end
269
- end
270
- end
271
-
272
- def is_glob?(filename)
273
- filename =~ /[.*]/
274
- end
275
-
276
- def cmd_type(argument)
277
- check_logged_in
278
- syntax_error unless argument =~ /^(\S)(?: (\S+))?$/
279
- type_code = $1
280
- format_code = $2
281
- set_type(type_code)
282
- set_format(format_code)
283
- reply "200 Type set to #{@data_type}"
284
- end
285
-
286
- def set_type(type_code)
287
- name, implemented = DATA_TYPES[type_code]
288
- error "504 Invalid type code" unless name
289
- error "504 Type not implemented" unless implemented
290
- @data_type = type_code
291
- end
292
-
293
- def set_format(format_code)
294
- format_code ||= 'N'
295
- name, implemented = FORMAT_TYPES[format_code]
296
- error "504 Invalid format code" unless name
297
- error "504 Format not implemented" unless implemented
298
- @data_format = format_code
299
- end
300
-
301
- def cmd_mode(argument)
302
- syntax_error unless argument
303
- check_logged_in
304
- name, implemented = TRANSMISSION_MODES[argument]
305
- error "504 Invalid mode code" unless name
306
- error "504 Mode not implemented" unless implemented
307
- @mode = argument
308
- reply "200 Mode set to #{name}"
309
- end
310
-
311
- def cmd_stru(argument)
312
- syntax_error unless argument
313
- check_logged_in
314
- name, implemented = FILE_STRUCTURES[argument]
315
- error "504 Invalid structure code" unless name
316
- error "504 Structure not implemented" unless implemented
317
- @structure = argument
318
- reply "200 File structure set to #{name}"
319
- end
320
-
321
- def cmd_noop(argument)
322
- syntax_error if argument
323
- reply "200 Nothing done"
324
- end
325
-
326
- def cmd_pasv(argument)
327
- check_logged_in
328
- if @data_server
329
- reply "200 Already in passive mode"
330
- else
331
- @data_server = TCPServer.new('localhost', 0)
332
- ip = @data_server.addr[3]
333
- port = @data_server.addr[1]
334
- quads = [
335
- ip.scan(/\d+/),
336
- port >> 8,
337
- port & 0xff,
338
- ].flatten.join(',')
339
- reply "227 Entering passive mode (#{quads})"
340
- end
341
- end
342
-
343
- def cmd_cwd(argument)
344
- check_logged_in
345
- target = if argument =~ %r"^/(.*)$"
346
- @data_path + $1
347
- else
348
- @cwd + argument
349
- end
350
- ensure_path_is_in_data_dir(target)
351
- restore_cwd_on_error do
352
- @cwd = target
353
- pwd
354
- end
355
- end
356
-
357
- def cmd_cdup(argument)
358
- check_logged_in
359
- cmd_cwd('..')
360
- end
361
-
362
- def cmd_pwd(argument)
363
- check_logged_in
364
- pwd
365
- end
366
-
367
- def cmd_auth(security_scheme)
368
- if @socket.encrypted?
369
- raise Error, "503 AUTH already done"
370
- end
371
- unless security_scheme =~ /^TLS(-C)?$/i
372
- raise Error, "500 Security scheme not implemented: #{security_scheme}"
373
- end
374
- reply "234 AUTH #{security_scheme} OK."
375
- @socket.encrypt
376
- end
377
-
378
- def cmd_pbsz(buffer_size)
379
- syntax_error unless buffer_size =~ /^\d+$/
380
- buffer_size = buffer_size.to_i
381
- unless @socket.encrypted?
382
- raise Error, "503 PBSZ must be preceded by AUTH"
383
- end
384
- unless buffer_size == 0
385
- raise Error, "501 PBSZ=0"
386
- end
387
- reply "200 PBSZ=0"
388
- @protection_buffer_size_set = true
389
- end
390
-
391
- def cmd_prot(level_arg)
392
- level_code = level_arg.upcase
393
- unless @protection_buffer_size_set
394
- raise Error, "503 PROT must be preceded by PBSZ"
395
- end
396
- level = DATA_CHANNEL_PROTECTION_LEVELS[level_code]
397
- unless level
398
- raise Error, "504 Unknown protection level"
399
- end
400
- unless level == :private
401
- raise Error, "536 Unsupported protection level #{level}"
402
- end
403
- @data_channel_protection_level = level
404
- reply "200 Data protection level #{level_code}"
405
- end
406
-
407
- def pwd
408
- reply %Q(257 "#{sanitized_cwd}" is current directory)
409
- end
410
-
411
- def relative_to_data_path(path)
412
- data_path = realpath(@data_path).to_s
413
- path = realpath(path).to_s
414
- path = path.gsub(data_path, '')
415
- path = '/' if path.empty?
416
- path
417
- end
418
-
419
- def sanitized_cwd
420
- relative_to_data_path(@cwd)
421
- end
422
-
423
- def error(message)
424
- raise Error, message
425
- end
426
-
427
- TRANSMISSION_MODES = {
428
- 'B'=>['Block', false],
429
- 'C'=>['Compressed', false],
430
- 'S'=>['Stream', true],
431
- }
432
-
433
- FORMAT_TYPES = {
434
- 'N'=>['Non-print', true],
435
- 'T'=>['Telnet format effectors', false],
436
- 'C'=>['Carriage Control (ASA)', false],
437
- }
438
-
439
- DATA_TYPES = {
440
- 'A'=>['ASCII', true],
441
- 'E'=>['EBCDIC', false],
442
- 'I'=>['BINARY', true],
443
- 'L'=>['LOCAL', false],
444
- }
445
-
446
- FILE_STRUCTURES = {
447
- 'R'=>['Record', false],
448
- 'F'=>['File', true],
449
- 'P'=>['Page', false],
450
- }
451
-
452
- DATA_CHANNEL_PROTECTION_LEVELS = {
453
- 'C'=>:clear,
454
- 'S'=>:safe,
455
- 'E'=>:confidential,
456
- 'P'=>:private
457
- }
458
-
459
- def check_logged_in
460
- return if @state == :logged_in
461
- error "530 Not logged in"
462
- end
463
-
464
- def ensure_path_is_in_data_dir(path)
465
- unless child_path_of?(@data_path, path)
466
- error "550 Access denied"
467
- end
468
- end
469
-
470
- def ensure_path_exists(path)
471
- unless File.exists?(path)
472
- error '450 No such file or directory'
473
- end
474
- end
475
-
476
- def child_path_of?(parent, child)
477
- child.cleanpath.to_s.index(parent.cleanpath.to_s) == 0
478
- end
479
-
480
- def target_path(path)
481
- path = Pathname.new(path)
482
- base, path = if path.to_s =~ /^\/(.*)/
483
- [@data_path, $1]
484
- else
485
- [@cwd, path]
486
- end
487
- base + path
488
- end
489
-
490
- def read_file(path)
491
- handle_system_error do
492
- File.open(path, 'rb') do |file|
493
- file.read
494
- end
495
- end
496
- end
497
-
498
- def write_file(dest, contents)
499
- handle_system_error do
500
- File.open(dest, 'w') do |file|
501
- file.write(contents)
502
- end
503
- end
504
- end
505
-
506
- def handle_system_error
507
- begin
508
- yield
509
- rescue SystemCallError => e
510
- error "550 #{e}"
511
- end
512
- end
513
-
514
- def transmit_file(contents, data_type = @data_type)
515
- open_data_connection do |data_socket|
516
- contents = unix_to_nvt_ascii(contents) if data_type == 'A'
517
- data_socket.write(contents)
518
- debug("Sent #{contents.size} bytes")
519
- reply "226 Transfer complete"
520
- end
521
- end
522
-
523
- def receive_file(path)
524
- open_data_connection do |data_socket|
525
- contents = data_socket.read
526
- contents = nvt_ascii_to_unix(contents) if @data_type == 'A'
527
- debug("Received #{contents.size} bytes")
528
- contents
529
- end
530
- end
531
-
532
- def unix_to_nvt_ascii(s)
533
- return s if s =~ /\r\n/
534
- s.gsub(/\n/, "\r\n")
535
- end
536
-
537
- def nvt_ascii_to_unix(s)
538
- s.gsub(/\r\n/, "\n")
539
- end
540
-
541
- def open_data_connection(&block)
542
- reply "150 Opening #{data_connection_description}"
543
- if @data_server
544
- if encrypt_data?
545
- open_passive_tls_data_connection(&block)
546
- else
547
- open_passive_data_connection(&block)
548
- end
549
- else
550
- if encrypt_data?
551
- open_active_tls_data_connection(&block)
552
- else
553
- open_active_data_connection(&block)
554
- end
555
- end
556
- end
557
-
558
- def data_connection_description
559
- [
560
- DATA_TYPES[@data_type][0],
561
- "mode data connection",
562
- ("(TLS)" if encrypt_data?)
563
- ].compact.join(' ')
564
- end
565
-
566
- def encrypt_data?
567
- @data_channel_protection_level != :clear
568
- end
569
-
570
- def open_active_data_connection
571
- data_socket = TCPSocket.new(@data_hostname, @data_port)
572
- begin
573
- yield(data_socket)
574
- ensure
575
- data_socket.close
576
- end
577
- end
578
-
579
- def open_active_tls_data_connection
580
- open_active_data_connection do |socket|
581
- make_tls_connection(socket) do |ssl_socket|
582
- yield(ssl_socket)
583
- end
584
- end
585
- end
586
-
587
- def open_passive_data_connection
588
- data_socket = @data_server.accept
589
- begin
590
- yield(data_socket)
591
- ensure
592
- data_socket.close
593
- end
594
- end
595
-
596
- def close_data_server_socket_when_done
597
- yield
598
- ensure
599
- close_data_server_socket
600
- end
601
-
602
- def close_data_server_socket
603
- return unless @data_server
604
- @data_server.close
605
- @data_server = nil
606
- end
607
-
608
- def open_passive_tls_data_connection
609
- open_passive_data_connection do |socket|
610
- make_tls_connection(socket) do |ssl_socket|
611
- yield(ssl_socket)
612
- end
613
- end
614
- end
615
-
616
- def make_tls_connection(socket)
617
- ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, @socket.ssl_context)
618
- ssl_socket.accept
619
- begin
620
- yield(ssl_socket)
621
- ensure
622
- ssl_socket.close
623
- end
624
- end
625
-
626
- def get_command
627
- s = @socket.gets
628
- throw :done if s.nil?
629
- s = s.chomp
630
- debug(s)
631
- s
632
- end
633
-
634
- def reply(s)
635
- if @response_delay.to_i != 0
636
- debug "#{@response_delay} second delay before replying"
637
- sleep @response_delay
638
- end
639
- debug(s)
640
- @socket.puts(s)
641
- end
642
-
643
- def debug(*s)
644
- return unless debug?
645
- File.open(@debug_path, 'a') do |file|
646
- file.puts(*s)
647
- end
648
- end
649
-
650
- def debug?
651
- ENV['DEBUG'].to_i != 0
652
- end
653
-
654
- def restore_cwd_on_error
655
- orig_cwd = @cwd
656
- yield
657
- rescue
658
- @cwd = orig_cwd
659
- raise
660
- end
661
-
662
- end
663
-
664
- end
665
-
666
- class Scaffold
667
-
668
- def initialize
669
- @data_dir = TempDir.new
670
- create_files
671
- @server = FakeFtpServer.new(@data_dir.path)
672
- set_credentials
673
- display_connection_info
674
- create_connection_script
675
- end
676
-
677
- def run
678
- wait_until_stopped
679
- end
680
-
681
- private
682
-
683
- HOST = 'localhost'
684
-
685
- def create_files
686
- [
687
- 'README',
688
- 'outgoing/getme',
689
- ].each do |path|
690
- base_name = File.basename(path)
691
- dir_name = File.dirname(path)
692
- dir_path = File.join(@data_dir.path, dir_name)
693
- file_path = File.join(dir_path, base_name)
694
- FileUtils.mkdir_p(dir_path)
695
- File.open(file_path, 'w') do |file|
696
- file.puts "Contents of #{path}"
697
- end
698
- end
699
- end
700
-
701
- def set_credentials
702
- @server.user = ENV['LOGNAME']
703
- @server.password = ''
704
- end
705
-
706
- def display_connection_info
707
- puts "Host: #{HOST}"
708
- puts "Port: #{@server.port}"
709
- puts "User: #{@server.user}"
710
- puts "Pass: #{@server.password}"
711
- puts "Directory: #{@data_dir.path}"
712
- end
713
-
714
- def create_connection_script
715
- command_path = '/tmp/connect_to_fake_ftp_server.sh'
716
- File.open(command_path, 'w') do |file|
717
- file.puts "#!/bin/bash"
718
- file.puts "ftp $FTP_ARGS #{HOST} #{@server.port}"
719
- end
720
- system("chmod +x #{command_path}")
721
- puts "Connection script written to #{command_path}"
722
- end
723
-
724
- def wait_until_stopped
725
- puts "FTP server started. Press ENTER or c-C to stop it"
726
- $stdout.flush
727
- begin
728
- gets
729
- rescue Interrupt
730
- puts "Interrupt"
731
- end
732
- end
733
-
734
- end
735
-
736
- Scaffold.new.run if $0 == __FILE__