ftpd 0.0.1.pre → 0.1.0

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.

Files changed (70) hide show
  1. data/Gemfile +3 -1
  2. data/Gemfile.lock +14 -14
  3. data/README.md +71 -23
  4. data/Rakefile +9 -3
  5. data/VERSION +1 -1
  6. data/examples/example.rb +132 -53
  7. data/examples/hello_world.rb +32 -0
  8. data/features/example/example.feature +18 -0
  9. data/features/example/step_definitions/example_server.rb +3 -0
  10. data/features/{command_errors.feature → ftp_server/command_errors.feature} +3 -0
  11. data/features/ftp_server/concurrent_sessions.feature +14 -0
  12. data/features/ftp_server/debug.feature +15 -0
  13. data/features/{delete.feature → ftp_server/delete.feature} +23 -2
  14. data/features/{directory_navigation.feature → ftp_server/directory_navigation.feature} +17 -4
  15. data/features/ftp_server/file_structure.feature +43 -0
  16. data/features/{get.feature → ftp_server/get.feature} +21 -10
  17. data/features/ftp_server/get_tls.feature +18 -0
  18. data/features/{list.feature → ftp_server/list.feature} +24 -30
  19. data/features/ftp_server/list_tls.feature +21 -0
  20. data/features/{login.feature → ftp_server/login.feature} +4 -2
  21. data/features/ftp_server/mode.feature +43 -0
  22. data/features/{name_list.feature → ftp_server/name_list.feature} +25 -31
  23. data/features/ftp_server/name_list_tls.feature +22 -0
  24. data/features/{noop.feature → ftp_server/noop.feature} +3 -0
  25. data/features/{port.feature → ftp_server/port.feature} +3 -0
  26. data/features/{put.feature → ftp_server/put.feature} +19 -11
  27. data/features/ftp_server/put_tls.feature +18 -0
  28. data/features/{quit.feature → ftp_server/quit.feature} +3 -0
  29. data/features/ftp_server/step_definitions/debug.rb +8 -0
  30. data/features/ftp_server/step_definitions/test_server.rb +12 -0
  31. data/features/{syntax_errors.feature → ftp_server/syntax_errors.feature} +3 -0
  32. data/features/ftp_server/type.feature +56 -0
  33. data/features/step_definitions/error.rb +10 -3
  34. data/features/step_definitions/list.rb +21 -4
  35. data/features/step_definitions/login.rb +0 -2
  36. data/features/step_definitions/server_files.rb +4 -0
  37. data/features/step_definitions/stop_server.rb +3 -0
  38. data/features/support/example_server.rb +58 -0
  39. data/features/support/test_client.rb +1 -1
  40. data/features/support/test_server.rb +106 -24
  41. data/features/support/test_server_files.rb +30 -0
  42. data/ftpd.gemspec +56 -25
  43. data/lib/ftpd.rb +22 -4
  44. data/lib/ftpd/disk_file_system.rb +137 -0
  45. data/lib/ftpd/error.rb +9 -0
  46. data/lib/ftpd/exception_translator.rb +29 -0
  47. data/lib/ftpd/exceptions.rb +13 -0
  48. data/lib/ftpd/file_system_error_translator.rb +21 -0
  49. data/lib/ftpd/ftp_server.rb +8 -645
  50. data/lib/ftpd/insecure_certificate.rb +10 -0
  51. data/lib/ftpd/server.rb +15 -12
  52. data/lib/ftpd/session.rb +569 -0
  53. data/lib/ftpd/temp_dir.rb +10 -11
  54. data/lib/ftpd/tls_server.rb +27 -15
  55. data/lib/ftpd/translate_exceptions.rb +44 -0
  56. data/rake_tasks/cucumber.rake +4 -2
  57. data/rake_tasks/default.rake +1 -0
  58. data/rake_tasks/spec.rake +3 -0
  59. data/rake_tasks/test.rake +2 -0
  60. data/sandbox/em-server.rb +37 -0
  61. data/spec/disk_file_system_spec.rb +239 -0
  62. data/spec/exception_translator_spec.rb +35 -0
  63. data/spec/spec_helper.rb +5 -0
  64. data/spec/translate_exceptions_spec.rb +40 -0
  65. metadata +143 -115
  66. data/features/concurrent_sessions.feature +0 -11
  67. data/features/file_structure.feature +0 -40
  68. data/features/mode.feature +0 -40
  69. data/features/step_definitions/server.rb +0 -7
  70. data/features/type.feature +0 -53
@@ -0,0 +1,9 @@
1
+ module Ftpd
2
+ module Error
3
+
4
+ def error(message)
5
+ raise CommandError, message
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ module Ftpd
2
+
3
+ # Translate specific exceptions to FileSystemError.
4
+
5
+ class ExceptionTranslator
6
+
7
+ def initialize
8
+ @exceptions = []
9
+ end
10
+
11
+ # Register an exception class.
12
+
13
+ def register_exception(e)
14
+ @exceptions << e
15
+ end
16
+
17
+ # Run a block, translating specific exceptions to FileSystemError.
18
+
19
+ def translate_exceptions
20
+ begin
21
+ return yield
22
+ rescue *@exceptions => e
23
+ raise FileSystemError, e.message
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,13 @@
1
+ module Ftpd
2
+
3
+ class FtpServerError < StandardError ; end
4
+
5
+ def self.ftp_server_error(class_name)
6
+ const_set class_name, Class.new(FtpServerError)
7
+ end
8
+
9
+ ftp_server_error :CommandError
10
+ ftp_server_error :DriverError
11
+ ftp_server_error :FileSystemError
12
+
13
+ end
@@ -0,0 +1,21 @@
1
+ module Ftpd
2
+ class FileSystemErrorTranslator
3
+
4
+ include Error
5
+
6
+ def initialize(file_system)
7
+ @file_system = file_system
8
+ end
9
+
10
+ def respond_to?(method)
11
+ @file_system.respond_to?(method)
12
+ end
13
+
14
+ def method_missing(method, *args)
15
+ @file_system.send(method, *args)
16
+ rescue FileSystemError => e
17
+ error "450 #{e}"
18
+ end
19
+
20
+ end
21
+ end
@@ -1,664 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'fileutils'
4
- require 'openssl'
5
- require 'pathname'
6
- require 'ftpd/tls_server'
7
- require 'ftpd/temp_dir'
8
-
9
3
  module Ftpd
10
4
  class FtpServer < TlsServer
11
5
 
12
- attr_accessor :user
13
- attr_accessor :password
14
6
  attr_accessor :debug_path
7
+ attr_accessor :debug
15
8
  attr_accessor :response_delay
16
- attr_accessor :implicit_tls
17
9
 
18
- def initialize(data_path)
10
+ def initialize(driver)
19
11
  super()
20
- self.user = 'user'
21
- self.password = 'password'
22
- self.debug_path = '/dev/stdout'
23
- @data_path = Pathname.new(data_path)
12
+ @driver = driver
13
+ @debug_path = '/dev/stdout'
14
+ @debug = false
24
15
  @response_delay = 0
25
- @implicit_tls = false
26
16
  end
27
17
 
28
18
  def session(socket)
29
19
  Session.new(:socket => socket,
30
- :user => user,
31
- :password => password,
32
- :data_path => @data_path,
20
+ :driver => @driver,
21
+ :debug => @debug,
33
22
  :debug_path => debug_path,
34
23
  :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
-
24
+ :tls => @tls).run
662
25
  end
663
26
 
664
27
  end