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.

data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # FTPD
2
2
 
3
3
  ftpd is a pure Ruby FTP server library. It supports implicit and
4
- explicit TLS, and can be used by a text fixture or FTP daemon.
4
+ explicit TLS, suitlble for use by a program such as a test fixture or
5
+ FTP daemon.
5
6
 
6
7
  ## UNFINISHED
7
8
 
@@ -20,6 +21,21 @@ TLS is only supported in passive mode, not active. Either the FTPS
20
21
  client used by the test doesn't work in active mode, or this server
21
22
  doesn't work in FTPS active mode (or both).
22
23
 
24
+ ## DEVELOPMENT
25
+
26
+ ### TESTS
27
+
28
+ To run the cucumber tests:
29
+
30
+ $ rake features
31
+
32
+ To run the stand-alone example:
33
+
34
+ $ examples/example.rb
35
+
36
+ The example prints its port, username and password to the console.
37
+ You can connect to the stand-alone example with any FTP client.
38
+
23
39
  ## REFERENCES
24
40
 
25
41
  (This list of references comes from the README of the em-ftpd gem,
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0.pre1
1
+ 0.0.0.pre2
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
+ end
6
+
7
+ require 'ftpd'
8
+
9
+ class Example
10
+
11
+ def initialize
12
+ @data_dir = Ftpd::TempDir.new
13
+ create_files
14
+ @server = Ftpd::FtpServer.new(@data_dir.path)
15
+ set_credentials
16
+ display_connection_info
17
+ create_connection_script
18
+ end
19
+
20
+ def run
21
+ wait_until_stopped
22
+ end
23
+
24
+ private
25
+
26
+ HOST = 'localhost'
27
+
28
+ def create_files
29
+ create_file 'README',
30
+ "Temporary directory created by ftpd sample program\n"
31
+ end
32
+
33
+ def create_file(path, contents)
34
+ full_path = File.expand_path(path, @data_dir.path)
35
+ FileUtils.mkdir_p File.dirname(full_path)
36
+ File.open(full_path, 'w') do |file|
37
+ file.write contents
38
+ end
39
+ end
40
+
41
+ def set_credentials
42
+ @server.user = ENV['LOGNAME']
43
+ @server.password = ''
44
+ end
45
+
46
+ def display_connection_info
47
+ puts "Host: #{HOST}"
48
+ puts "Port: #{@server.port}"
49
+ puts "User: #{@server.user}"
50
+ puts "Pass: #{@server.password}"
51
+ puts "Directory: #{@data_dir.path}"
52
+ puts "URI: ftp://#{HOST}:#{@server.port}"
53
+ end
54
+
55
+ def create_connection_script
56
+ command_path = '/tmp/connect-to-example-ftp-server.sh'
57
+ File.open(command_path, 'w') do |file|
58
+ file.puts "#!/bin/bash"
59
+ file.puts "ftp $FTP_ARGS #{HOST} #{@server.port}"
60
+ end
61
+ system("chmod +x #{command_path}")
62
+ puts "Connection script written to #{command_path}"
63
+ end
64
+
65
+ def wait_until_stopped
66
+ puts "FTP server started. Press ENTER or c-C to stop it"
67
+ $stdout.flush
68
+ begin
69
+ gets
70
+ rescue Interrupt
71
+ puts "Interrupt"
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ Example.new.run if $0 == __FILE__
@@ -6,6 +6,10 @@ When /^the client connects( with TLS)?$/ do |with_tls|
6
6
  @client.connect(@server.host, @server.port)
7
7
  end
8
8
 
9
+ After do
10
+ @client.close if @client
11
+ end
12
+
9
13
  Then /^the connection is closed$/ do
10
14
  @client.should be_closed
11
15
  end
@@ -7,11 +7,16 @@ class TestClient
7
7
  include FileUtils
8
8
 
9
9
  def initialize(opts = {})
10
- @temp_dir = Dir.mktmpdir
10
+ @temp_dir = Ftpd::TempDir.new
11
11
  @ftp = make_ftp(opts)
12
12
  @templates = TestFileTemplates.new
13
13
  end
14
14
 
15
+ def close
16
+ @ftp.close
17
+ @temp_dir.rm
18
+ end
19
+
15
20
  def_delegators :@ftp,
16
21
  :chdir,
17
22
  :connect,
@@ -61,7 +66,7 @@ class TestClient
61
66
  end
62
67
 
63
68
  def temp_path(path)
64
- File.expand_path(path, @temp_dir)
69
+ File.expand_path(path, @temp_dir.path)
65
70
  end
66
71
 
67
72
  def make_ftp(opts)
@@ -7,14 +7,14 @@ class TestServer
7
7
  include FileUtils
8
8
 
9
9
  def initialize
10
- @temp_dir = Dir.mktmpdir
11
- @server = FakeFtpServer.new(@temp_dir)
10
+ @temp_dir = Ftpd::TempDir.new
11
+ @server = Ftpd::FtpServer.new(@temp_dir.path)
12
12
  @templates = TestFileTemplates.new
13
13
  end
14
14
 
15
15
  def close
16
16
  @server.close
17
- rm_rf @temp_dir
17
+ @temp_dir.rm
18
18
  end
19
19
 
20
20
  def host
@@ -46,7 +46,7 @@ class TestServer
46
46
  private
47
47
 
48
48
  def temp_path(path)
49
- File.expand_path(path, @temp_dir)
49
+ File.expand_path(path, @temp_dir.path)
50
50
  end
51
51
 
52
52
  end
data/ftpd.gemspec CHANGED
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ftpd"
8
- s.version = "0.0.0.pre1"
8
+ s.version = "0.0.0.pre2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Wayne Conrad"]
12
12
  s.date = "2013-02-10"
13
- s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, and can be used by a text fixture or FTP daemon."
13
+ s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, suitlble for use by a program such as a test fixture or FTP daemon."
14
14
  s.email = "wconrad@yagni.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.md",
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  "README.md",
24
24
  "Rakefile",
25
25
  "VERSION",
26
+ "examples/example.rb",
26
27
  "features/command_errors.feature",
27
28
  "features/delete.feature",
28
29
  "features/directory_navigation.feature",
@@ -70,12 +71,10 @@ Gem::Specification.new do |s|
70
71
  "ftpd.gemspec",
71
72
  "insecure-test-cert.pem",
72
73
  "lib/ftpd.rb",
73
- "lib/ftpd/FakeFtpServer.rb",
74
- "lib/ftpd/FakeServer.rb",
75
- "lib/ftpd/FakeTlsServer.rb",
76
- "lib/ftpd/ObjectUtil.rb",
77
- "lib/ftpd/TempDir.rb",
78
- "lib/ftpd/q.rb",
74
+ "lib/ftpd/ftp_server.rb",
75
+ "lib/ftpd/server.rb",
76
+ "lib/ftpd/temp_dir.rb",
77
+ "lib/ftpd/tls_server.rb",
79
78
  "rake_tasks/cucumber.rake",
80
79
  "rake_tasks/jeweler.rake"
81
80
  ]
data/lib/ftpd.rb CHANGED
@@ -1,6 +1,4 @@
1
- require 'ftpd/q'
2
- require 'ftpd/ObjectUtil'
3
- require 'ftpd/TempDir'
4
- require 'ftpd/FakeServer'
5
- require 'ftpd/FakeTlsServer'
6
- require 'ftpd/FakeFtpServer'
1
+ require 'ftpd/ftp_server'
2
+ require 'ftpd/server'
3
+ require 'ftpd/temp_dir'
4
+ require 'ftpd/tls_server'
@@ -0,0 +1,665 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'openssl'
5
+ require 'pathname'
6
+ require File.expand_path('tls_server', File.dirname(__FILE__))
7
+ require File.expand_path('temp_dir', File.dirname(__FILE__))
8
+
9
+ module Ftpd
10
+ class FtpServer < TlsServer
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
+ end