remailer 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|