remailer 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/VERSION +1 -1
- data/lib/remailer.rb +1 -0
- data/lib/remailer/connection.rb +132 -377
- data/lib/remailer/connection/smtp_interpreter.rb +270 -0
- data/lib/remailer/connection/socks5_interpreter.rb +186 -0
- data/lib/remailer/interpreter.rb +253 -0
- data/lib/remailer/interpreter/state_proxy.rb +43 -0
- data/remailer.gemspec +19 -3
- data/test/config.example.rb +17 -0
- data/test/helper.rb +61 -2
- data/test/unit/remailer_connection_smtp_interpreter_test.rb +347 -0
- data/test/unit/remailer_connection_socks5_interpreter_test.rb +116 -0
- data/test/unit/remailer_connection_test.rb +287 -0
- data/test/unit/remailer_interpreter_state_proxy_test.rb +86 -0
- data/test/unit/remailer_interpreter_test.rb +153 -0
- data/test/unit/remailer_test.rb +2 -223
- metadata +20 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/remailer.rb
CHANGED
data/lib/remailer/connection.rb
CHANGED
@@ -6,46 +6,19 @@ class Remailer::Connection < EventMachine::Connection
|
|
6
6
|
|
7
7
|
class CallbackArgumentsRequired < Exception; end
|
8
8
|
|
9
|
+
# == Submodules ===========================================================
|
10
|
+
|
11
|
+
autoload(:SmtpInterpreter, 'remailer/connection/smtp_interpreter')
|
12
|
+
autoload(:Socks5Interpreter, 'remailer/connection/socks5_interpreter')
|
13
|
+
|
9
14
|
# == Constants ============================================================
|
10
15
|
|
11
|
-
DEFAULT_TIMEOUT = 5
|
12
16
|
CRLF = "\r\n".freeze
|
13
|
-
|
17
|
+
DEFAULT_TIMEOUT = 5
|
14
18
|
|
15
19
|
SMTP_PORT = 25
|
16
20
|
SOCKS5_PORT = 1080
|
17
21
|
|
18
|
-
SOCKS5_VERSION = 5
|
19
|
-
|
20
|
-
SOCKS5_METHOD = {
|
21
|
-
:no_auth => 0,
|
22
|
-
:gssapi => 1,
|
23
|
-
:username_password => 2
|
24
|
-
}.freeze
|
25
|
-
|
26
|
-
SOCKS5_COMMAND = {
|
27
|
-
:connect => 1,
|
28
|
-
:bind => 2
|
29
|
-
}.freeze
|
30
|
-
|
31
|
-
SOCKS5_REPLY = {
|
32
|
-
0 => 'Succeeded',
|
33
|
-
1 => 'General SOCKS server failure',
|
34
|
-
2 => 'Connection not allowed',
|
35
|
-
3 => 'Network unreachable',
|
36
|
-
4 => 'Host unreachable',
|
37
|
-
5 => 'Connection refused',
|
38
|
-
6 => 'TTL expired',
|
39
|
-
7 => 'Command not supported',
|
40
|
-
8 => 'Address type not supported'
|
41
|
-
}.freeze
|
42
|
-
|
43
|
-
SOCKS5_ADDRESS_TYPE = {
|
44
|
-
:ipv4 => 1,
|
45
|
-
:domainname => 3,
|
46
|
-
:ipv6 => 4
|
47
|
-
}.freeze
|
48
|
-
|
49
22
|
NOTIFICATIONS = [
|
50
23
|
:debug,
|
51
24
|
:error,
|
@@ -54,10 +27,10 @@ class Remailer::Connection < EventMachine::Connection
|
|
54
27
|
|
55
28
|
# == Properties ===========================================================
|
56
29
|
|
30
|
+
attr_accessor :remote, :max_size, :protocol, :hostname
|
31
|
+
attr_accessor :pipelining, :tls_support
|
57
32
|
attr_accessor :timeout
|
58
|
-
attr_reader :state, :mode
|
59
33
|
attr_accessor :options
|
60
|
-
attr_reader :remote, :max_size, :protocol
|
61
34
|
|
62
35
|
# == Extensions ===========================================================
|
63
36
|
|
@@ -70,11 +43,24 @@ class Remailer::Connection < EventMachine::Connection
|
|
70
43
|
# * require_tls => If true will fail connections to non-TLS capable
|
71
44
|
# servers (default is false)
|
72
45
|
# * use_tls => Will use TLS if availble (default is true)
|
73
|
-
|
46
|
+
# * debug => Where to send debugging output (IO or Proc)
|
47
|
+
# * connect => Where to send a connection notification (IO or Proc)
|
48
|
+
# * error => Where to send errors (IO or Proc)
|
49
|
+
# A block can be supplied in which case it will stand in as the :connect
|
50
|
+
# option. The block will recieve a first argument that is the status of
|
51
|
+
# the connection, and an optional second that is a diagnostic message.
|
52
|
+
def self.open(smtp_server, options = nil, &block)
|
74
53
|
options ||= { }
|
75
54
|
options[:host] = smtp_server
|
76
55
|
options[:port] ||= 25
|
77
|
-
|
56
|
+
|
57
|
+
unless (options.key?(:use_tls))
|
58
|
+
options[:use_tls] = true
|
59
|
+
end
|
60
|
+
|
61
|
+
if (block_given?)
|
62
|
+
options[:connect] = block
|
63
|
+
end
|
78
64
|
|
79
65
|
host_name = smtp_server
|
80
66
|
host_port = options[:port]
|
@@ -84,21 +70,17 @@ class Remailer::Connection < EventMachine::Connection
|
|
84
70
|
host_port = proxy_options[:port] || SOCKS5_PORT
|
85
71
|
end
|
86
72
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
def self.base64(string)
|
95
|
-
[ string ].pack('m').chomp
|
96
|
-
end
|
97
|
-
|
98
|
-
def self.encode_authentication(username, password)
|
99
|
-
base64("\0#{username}\0#{password}")
|
73
|
+
begin
|
74
|
+
EventMachine.connect(host_name, host_port, self, options)
|
75
|
+
rescue EventMachine::ConnectionError => e
|
76
|
+
options[:connect].is_a?(Proc) and options[:connect].call(false, e.to_s)
|
77
|
+
options[:debug].is_a?(Proc) and options[:debug].call(:error, e.to_s)
|
78
|
+
options[:error].is_a?(Proc) and options[:error].call(:connect_error, e.to_s)
|
79
|
+
end
|
100
80
|
end
|
101
81
|
|
82
|
+
# Warns about supplying a Proc which does not appear to accept the required
|
83
|
+
# number of arguments.
|
102
84
|
def self.warn_about_arguments(proc, range)
|
103
85
|
unless (range.include?(proc.arity) or proc.arity == -1)
|
104
86
|
STDERR.puts "Callback must accept #{[ range.min, range.max ].uniq.join(' to ')} arguments but accepts #{proc.arity}"
|
@@ -107,30 +89,42 @@ class Remailer::Connection < EventMachine::Connection
|
|
107
89
|
|
108
90
|
# == Instance Methods =====================================================
|
109
91
|
|
92
|
+
# EventMachine will call this constructor and it is not to be called
|
93
|
+
# directly. Use the Remailer::Connection.open method to facilitate the
|
94
|
+
# correct creation of a new connection.
|
110
95
|
def initialize(options)
|
111
96
|
# Throwing exceptions inside this block is going to cause EventMachine
|
112
97
|
# to malfunction in a spectacular way and hide the actual exception. To
|
113
98
|
# allow for debugging, exceptions are dumped to STDERR as a last resort.
|
114
|
-
|
115
|
-
|
99
|
+
@options = options
|
100
|
+
@hostname = @options[:hostname] || Socket.gethostname
|
116
101
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
callback = @options[type]
|
102
|
+
@messages = [ ]
|
103
|
+
|
104
|
+
NOTIFICATIONS.each do |type|
|
105
|
+
callback = @options[type]
|
122
106
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
107
|
+
if (callback.is_a?(Proc))
|
108
|
+
self.class.warn_about_arguments(callback, (2..2))
|
126
109
|
end
|
127
|
-
|
128
|
-
debug_notification(:options, @options.inspect)
|
129
|
-
|
130
|
-
reset_timeout!
|
131
|
-
rescue Object => e
|
132
|
-
STDERR.puts "#{e.class}: #{e}"
|
133
110
|
end
|
111
|
+
|
112
|
+
debug_notification(:options, @options.inspect)
|
113
|
+
|
114
|
+
reset_timeout!
|
115
|
+
|
116
|
+
if (using_proxy?)
|
117
|
+
use_socks5_interpreter!
|
118
|
+
else
|
119
|
+
use_smtp_interpreter!
|
120
|
+
end
|
121
|
+
|
122
|
+
rescue Object => e
|
123
|
+
STDERR.puts "#{e.class}: #{e}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def use_tls?
|
127
|
+
!!@options[:use_tls]
|
134
128
|
end
|
135
129
|
|
136
130
|
# Returns true if the connection has advertised TLS support, or false if
|
@@ -181,9 +175,9 @@ class Remailer::Connection < EventMachine::Connection
|
|
181
175
|
@messages << message
|
182
176
|
|
183
177
|
# If the connection is ready to send...
|
184
|
-
if (@state == :ready)
|
178
|
+
if (@interpreter and @interpreter.state == :ready)
|
185
179
|
# ...send the message right away.
|
186
|
-
|
180
|
+
after_ready
|
187
181
|
end
|
188
182
|
end
|
189
183
|
|
@@ -204,20 +198,12 @@ class Remailer::Connection < EventMachine::Connection
|
|
204
198
|
# flagging the connection as estasblished.
|
205
199
|
def connection_completed
|
206
200
|
@timeout_at = nil
|
207
|
-
|
208
|
-
if (using_proxy?)
|
209
|
-
enter_proxy_init_state!
|
210
|
-
else
|
211
|
-
connect_notification(true, "Connection completed")
|
212
|
-
@state = :connected
|
213
|
-
@connected = true
|
214
|
-
end
|
215
201
|
end
|
216
202
|
|
217
203
|
# This implements the EventMachine::Connection#unbind method to capture
|
218
204
|
# a connection closed event.
|
219
205
|
def unbind
|
220
|
-
@
|
206
|
+
@interpreter = nil
|
221
207
|
|
222
208
|
if (@active_message)
|
223
209
|
if (callback = @active_message[:callback])
|
@@ -226,355 +212,135 @@ class Remailer::Connection < EventMachine::Connection
|
|
226
212
|
end
|
227
213
|
end
|
228
214
|
|
215
|
+
# This implements the EventMachine::Connection#receive_data method that
|
216
|
+
# is called each time new data is received from the socket.
|
229
217
|
def receive_data(data)
|
230
|
-
|
231
|
-
|
232
|
-
case (state)
|
233
|
-
when :proxy_init
|
234
|
-
version, method = data.unpack('CC')
|
235
|
-
|
236
|
-
if (method == SOCKS5_METHOD[:username_password])
|
237
|
-
enter_proxy_authentication_state!
|
238
|
-
else
|
239
|
-
enter_proxy_connecting_state!
|
240
|
-
end
|
241
|
-
when :proxy_connecting
|
242
|
-
version, reply, reserved, address_type, address, port = data.unpack('CCCCNn')
|
243
|
-
|
244
|
-
case (reply)
|
245
|
-
when 0
|
246
|
-
@state = :connected
|
247
|
-
@connected = true
|
248
|
-
connect_notification(true, "Connection completed")
|
249
|
-
else
|
250
|
-
debug(:error, "Proxy server returned error code #{reply}: #{SOCKS5_REPLY[reply]}")
|
251
|
-
connect_notification(false, "Proxy server returned error code #{reply}: #{SOCKS5_REPLY[reply]}")
|
252
|
-
close_connection
|
253
|
-
@state = :failed
|
254
|
-
end
|
255
|
-
when :proxy_authenticating
|
256
|
-
# Decode response of authentication request...
|
257
|
-
|
258
|
-
# ...
|
259
|
-
else
|
260
|
-
# Data is received in arbitrary sized chunks, so there is no guarantee
|
261
|
-
# a whole line will be ready to process, or that there is only one line.
|
262
|
-
@buffer ||= ''
|
263
|
-
@buffer << data
|
264
|
-
|
265
|
-
while (line_index = @buffer.index(CRLF))
|
266
|
-
if (line_index > 0)
|
267
|
-
receive_reply(@buffer[0, line_index])
|
268
|
-
end
|
218
|
+
reset_timeout!
|
269
219
|
|
270
|
-
|
220
|
+
@buffer ||= ''
|
221
|
+
@buffer << data
|
222
|
+
|
223
|
+
if (@interpreter)
|
224
|
+
@interpreter.process(@buffer) do |reply|
|
225
|
+
debug_notification(:receive, "[#{@interpreter.label}] #{reply.inspect}")
|
271
226
|
end
|
227
|
+
else
|
228
|
+
error_notification(:out_of_band, "Receiving data before a protocol has been established.")
|
272
229
|
end
|
273
230
|
end
|
274
231
|
|
275
232
|
def post_init
|
276
|
-
@state = :connecting
|
277
|
-
|
278
233
|
EventMachine.add_periodic_timer(1) do
|
279
234
|
check_for_timeouts!
|
280
235
|
end
|
281
236
|
end
|
237
|
+
|
238
|
+
def state
|
239
|
+
@interpreter and @interpreter.state
|
240
|
+
end
|
282
241
|
|
283
|
-
protected
|
284
242
|
def send_line(line = '')
|
243
|
+
reset_timeout!
|
244
|
+
|
285
245
|
send_data(line + CRLF)
|
286
246
|
|
287
247
|
debug_notification(:send, line.inspect)
|
288
248
|
end
|
289
249
|
|
290
|
-
# Returns true if the reply has been completed, or false if it is still
|
291
|
-
# in the process of being received.
|
292
|
-
def reply_complete?
|
293
|
-
!!@reply_complete
|
294
|
-
end
|
295
|
-
|
296
250
|
def resolve_hostname(hostname)
|
297
251
|
# FIXME: Elminitate this potentially blocking call by using an async
|
298
252
|
# resolver if available.
|
299
253
|
record = Socket.gethostbyname(hostname)
|
300
254
|
|
301
|
-
|
255
|
+
# FIXME: IPv6 Support here
|
256
|
+
debug_notification(:resolved, record && record.last.unpack('CCCC').join('.'))
|
302
257
|
|
303
258
|
record and record.last
|
304
259
|
rescue
|
305
260
|
nil
|
306
261
|
end
|
307
262
|
|
308
|
-
def
|
309
|
-
|
310
|
-
|
311
|
-
return unless (reply)
|
312
|
-
|
313
|
-
if (reply.match(/(\d+)([ \-])(.*)/))
|
314
|
-
reply_code = $1.to_i
|
315
|
-
@reply_complete = $2 != '-'
|
316
|
-
reply_message = $3
|
317
|
-
|
318
|
-
debug_notification(:recv, reply)
|
319
|
-
end
|
320
|
-
|
321
|
-
# The connection itself will be in a particular state.
|
322
|
-
case (state)
|
323
|
-
when :connected
|
324
|
-
case (reply_code)
|
325
|
-
when 220
|
326
|
-
reply_parts = reply_message.split(/\s+/)
|
327
|
-
@remote = reply_parts.first
|
328
|
-
|
329
|
-
if (reply_parts.include?('ESMTP'))
|
330
|
-
@state = :sent_ehlo
|
331
|
-
@protocol = :esmtp
|
332
|
-
send_line("EHLO #{@options[:hostname]}")
|
333
|
-
else
|
334
|
-
@state = :sent_helo
|
335
|
-
@protocol = :smtp
|
336
|
-
send_line("HELO #{@options[:hostname]}")
|
337
|
-
end
|
338
|
-
else
|
339
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
340
|
-
end
|
341
|
-
when :sent_ehlo
|
342
|
-
case (reply_code)
|
343
|
-
when 250
|
344
|
-
reply_parts = reply_message.split(/\s+/)
|
345
|
-
case (reply_parts[0].to_s.upcase)
|
346
|
-
when 'SIZE'
|
347
|
-
@max_size = reply_parts[1].to_i
|
348
|
-
when 'PIPELINING'
|
349
|
-
@pipelining = true
|
350
|
-
when 'STARTTLS'
|
351
|
-
@tls_support = true
|
352
|
-
end
|
353
|
-
|
354
|
-
if (@reply_complete)
|
355
|
-
if (@options[:use_tls])
|
356
|
-
send_line("STARTTLS")
|
357
|
-
@state = :sent_starttls
|
358
|
-
elsif (requires_authentication?)
|
359
|
-
enter_sent_auth_state!
|
360
|
-
else
|
361
|
-
enter_ready_state!
|
362
|
-
end
|
363
|
-
end
|
364
|
-
else
|
365
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
366
|
-
end
|
367
|
-
when :sent_helo
|
368
|
-
case (reply_code)
|
369
|
-
when 250
|
370
|
-
enter_ready_state!
|
371
|
-
else
|
372
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
373
|
-
end
|
374
|
-
when :sent_starttls
|
375
|
-
case (reply_code)
|
376
|
-
when 220
|
377
|
-
start_tls
|
378
|
-
|
379
|
-
if (requires_authentication?)
|
380
|
-
enter_sent_auth_state!
|
381
|
-
else
|
382
|
-
enter_ready_state!
|
383
|
-
end
|
384
|
-
else
|
385
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
386
|
-
end
|
387
|
-
when :sent_auth
|
388
|
-
case (reply_code)
|
389
|
-
when 235
|
390
|
-
enter_ready_state!
|
391
|
-
else
|
392
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
393
|
-
end
|
394
|
-
when :sent_mail_from
|
395
|
-
case (reply_code)
|
396
|
-
when 250
|
397
|
-
@state = :sent_rcpt_to
|
398
|
-
send_line("RCPT TO:#{@active_message[:to]}")
|
399
|
-
else
|
400
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
401
|
-
end
|
402
|
-
when :sent_rcpt_to
|
403
|
-
case (reply_code)
|
404
|
-
when 250
|
405
|
-
@state = :sent_data
|
406
|
-
send_line("DATA")
|
407
|
-
|
408
|
-
@data_offset = 0
|
409
|
-
else
|
410
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
411
|
-
end
|
412
|
-
when :sent_data
|
413
|
-
case (reply_code)
|
414
|
-
when 354
|
415
|
-
@state = :data_sending
|
416
|
-
|
417
|
-
transmit_data!
|
418
|
-
else
|
419
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
420
|
-
end
|
421
|
-
when :sent_data_content
|
422
|
-
send_callback(reply_code, reply_message)
|
423
|
-
|
424
|
-
enter_ready_state!
|
425
|
-
when :sent_quit
|
426
|
-
case (reply_code)
|
427
|
-
when 221
|
428
|
-
@state = :closed
|
429
|
-
close_connection
|
430
|
-
else
|
431
|
-
fail_unanticipated_response!(reply_code, reply_message)
|
432
|
-
end
|
433
|
-
when :sent_reset
|
434
|
-
case (reply_code)
|
435
|
-
when 250
|
436
|
-
enter_ready_state!
|
437
|
-
end
|
438
|
-
end
|
263
|
+
def reset_timeout!
|
264
|
+
@timeout_at = Time.now + (@options[:timeout] || DEFAULT_TIMEOUT)
|
439
265
|
end
|
440
266
|
|
441
|
-
def
|
442
|
-
|
267
|
+
def check_for_timeouts!
|
268
|
+
return if (!@timeout_at or Time.now < @timeout_at)
|
443
269
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
270
|
+
error_notification(:timeout, "Connection timed out")
|
271
|
+
debug_notification(:timeout, "Connection timed out")
|
272
|
+
send_callback(:timeout, "Connection timed out before send could complete")
|
273
|
+
|
274
|
+
unless (@connected)
|
275
|
+
connect_notification(false, "Connection timed out")
|
448
276
|
end
|
449
|
-
|
450
|
-
send_data(
|
451
|
-
[
|
452
|
-
SOCKS5_VERSION,
|
453
|
-
socks_methods.length,
|
454
|
-
socks_methods
|
455
|
-
].flatten.pack('CCC*')
|
456
|
-
)
|
457
277
|
|
458
|
-
|
278
|
+
close_connection
|
459
279
|
end
|
460
280
|
|
461
|
-
def
|
462
|
-
|
463
|
-
# avoid connecting needlessly.
|
464
|
-
|
465
|
-
debug_notification(:proxy, "Sending proxy connection request to #{@options[:host]}:#{@options[:port]}")
|
466
|
-
|
467
|
-
if (ip_address = resolve_hostname(@options[:host]))
|
468
|
-
send_data(
|
469
|
-
[
|
470
|
-
SOCKS5_VERSION,
|
471
|
-
SOCKS5_COMMAND[:connect],
|
472
|
-
0,
|
473
|
-
SOCKS5_ADDRESS_TYPE[:ipv4],
|
474
|
-
ip_address,
|
475
|
-
@options[:port]
|
476
|
-
].pack('CCCCA4n')
|
477
|
-
)
|
478
|
-
|
479
|
-
@state = :proxy_connecting
|
480
|
-
else
|
481
|
-
send_callback(:error_connecting, "Could not resolve hostname #{@options[:host]}")
|
482
|
-
|
483
|
-
@state = :failed
|
484
|
-
close_connection
|
485
|
-
end
|
281
|
+
def pipelining?
|
282
|
+
!!@pipelining
|
486
283
|
end
|
487
|
-
|
488
|
-
def enter_proxy_authenticating_state!
|
489
|
-
debug_notification(:proxy, "Sending proxy authentication")
|
490
284
|
|
491
|
-
|
492
|
-
|
493
|
-
password = proxy_options[:password]
|
494
|
-
|
495
|
-
send_data(
|
496
|
-
[
|
497
|
-
SOCKS5_VERSION,
|
498
|
-
username.length,
|
499
|
-
username,
|
500
|
-
password.length,
|
501
|
-
password
|
502
|
-
].pack('CCA*CA*')
|
503
|
-
)
|
504
|
-
|
505
|
-
@state = :proxy_authenticating
|
285
|
+
def tls_support?
|
286
|
+
!!@tls_support
|
506
287
|
end
|
507
288
|
|
508
|
-
def
|
509
|
-
|
510
|
-
|
511
|
-
send_queued_message!
|
289
|
+
def closed?
|
290
|
+
!!@closed
|
512
291
|
end
|
513
292
|
|
514
|
-
def
|
515
|
-
|
516
|
-
|
293
|
+
def start_tls
|
294
|
+
debug_notification(:tls, "Started")
|
295
|
+
super
|
517
296
|
end
|
518
297
|
|
519
|
-
def
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
# configured to support 'writable' notifications on the active socket.
|
525
|
-
chunk = data[@data_offset, chunk_size]
|
526
|
-
debug_notification(:send, chunk.inspect)
|
527
|
-
send_data(self.class.encode_data(data))
|
528
|
-
@data_offset += chunk_size
|
529
|
-
|
530
|
-
if (@data_offset >= data.length)
|
531
|
-
@state = :sent_data_content
|
298
|
+
def close_connection
|
299
|
+
debug_notification(:closed, "Connection closed")
|
300
|
+
super
|
301
|
+
@closed = true
|
302
|
+
end
|
532
303
|
|
533
|
-
|
534
|
-
|
535
|
-
send_line
|
536
|
-
send_line(".")
|
537
|
-
end
|
304
|
+
def use_socks5_interpreter!
|
305
|
+
@interpreter = Remailer::Connection::Socks5Interpreter.new(:delegate => self)
|
538
306
|
end
|
539
|
-
|
540
|
-
def
|
541
|
-
@
|
307
|
+
|
308
|
+
def use_smtp_interpreter!
|
309
|
+
@interpreter = Remailer::Connection::SmtpInterpreter.new(:delegate => self)
|
542
310
|
end
|
543
311
|
|
544
|
-
def
|
312
|
+
def after_proxy_connected
|
313
|
+
use_smtp_interpreter!
|
314
|
+
end
|
315
|
+
|
316
|
+
def after_ready
|
545
317
|
return if (@active_message)
|
546
318
|
|
547
319
|
reset_timeout!
|
548
|
-
|
320
|
+
|
549
321
|
if (@active_message = @messages.shift)
|
550
|
-
@state
|
551
|
-
|
322
|
+
if (@interpreter.state == :ready)
|
323
|
+
@interpreter.enter_state(:send)
|
324
|
+
end
|
552
325
|
elsif (@options[:close])
|
553
326
|
if (callback = @options[:after_complete])
|
554
327
|
callback.call
|
555
328
|
end
|
556
|
-
|
557
|
-
|
558
|
-
@state = :sent_quit
|
329
|
+
|
330
|
+
@interpreter.enter_state(:quit)
|
559
331
|
end
|
560
332
|
end
|
561
333
|
|
562
|
-
def
|
563
|
-
|
564
|
-
|
565
|
-
error_notification(:timeout, "Connection timed out")
|
566
|
-
debug_notification(:timeout, "Connection timed out")
|
567
|
-
send_callback(:timeout, "Connection timed out before send could complete")
|
568
|
-
|
569
|
-
@state = :timeout
|
570
|
-
|
571
|
-
unless (@connected)
|
572
|
-
connect_notification(false, "Connection timed out")
|
573
|
-
end
|
334
|
+
def after_message_sent(reply_code, reply_message)
|
335
|
+
send_callback(reply_code, reply_message)
|
574
336
|
|
575
|
-
|
337
|
+
@active_message = nil
|
576
338
|
end
|
577
339
|
|
340
|
+
def interpreter_entered_state(interpreter, state)
|
341
|
+
debug_notification(:state, "#{interpreter.label.downcase}=#{state}")
|
342
|
+
end
|
343
|
+
|
578
344
|
def send_notification(type, code, message)
|
579
345
|
case (callback = @options[type])
|
580
346
|
when nil, false
|
@@ -588,8 +354,8 @@ protected
|
|
588
354
|
end
|
589
355
|
end
|
590
356
|
|
591
|
-
def connect_notification(code, message)
|
592
|
-
send_notification(:connect, code, message)
|
357
|
+
def connect_notification(code, message = nil)
|
358
|
+
send_notification(:connect, code, message || self.remote)
|
593
359
|
end
|
594
360
|
|
595
361
|
def error_notification(code, message)
|
@@ -612,15 +378,4 @@ protected
|
|
612
378
|
end
|
613
379
|
end
|
614
380
|
end
|
615
|
-
|
616
|
-
def fail_unanticipated_response!(reply_code, reply_message)
|
617
|
-
send_callback(reply_code, reply_message)
|
618
|
-
debug_notification(:error, "[#{@state}] #{reply_code} #{reply_message}")
|
619
|
-
error_notification(reply_code, reply_message)
|
620
|
-
|
621
|
-
@active_message = nil
|
622
|
-
|
623
|
-
@state = :sent_reset
|
624
|
-
send_line("RESET")
|
625
|
-
end
|
626
381
|
end
|