remailer 0.4.21 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +25 -19
- data/VERSION +1 -1
- data/lib/remailer.rb +7 -1
- data/lib/remailer/{connection.rb → abstract_connection.rb} +63 -167
- data/lib/remailer/constants.rb +10 -0
- data/lib/remailer/email_address.rb +41 -0
- data/lib/remailer/imap.rb +6 -0
- data/lib/remailer/imap/client.rb +228 -0
- data/lib/remailer/imap/client/interpreter.rb +101 -0
- data/lib/remailer/imap/server.rb +15 -0
- data/lib/remailer/imap/server/interpreter.rb +2 -0
- data/lib/remailer/interpreter.rb +7 -6
- data/lib/remailer/interpreter/state_proxy.rb +20 -2
- data/lib/remailer/smtp.rb +6 -0
- data/lib/remailer/smtp/client.rb +329 -0
- data/lib/remailer/{connection/smtp_interpreter.rb → smtp/client/interpreter.rb} +4 -4
- data/lib/remailer/smtp/server.rb +130 -0
- data/lib/remailer/smtp/server/interpreter.rb +237 -0
- data/lib/remailer/smtp/server/transaction.rb +29 -0
- data/lib/remailer/socks5.rb +5 -0
- data/lib/remailer/socks5/client.rb +5 -0
- data/lib/remailer/{connection/socks5_interpreter.rb → socks5/client/interpreter.rb} +3 -2
- data/lib/remailer/support.rb +5 -0
- data/remailer.gemspec +27 -9
- data/test/unit/remailer_imap_client_interpreter_test.rb +14 -0
- data/test/unit/remailer_imap_client_test.rb +125 -0
- data/test/unit/{remailer_connection_smtp_interpreter_test.rb → remailer_smtp_client_interpreter_test.rb} +33 -33
- data/test/unit/{remailer_connection_test.rb → remailer_smtp_client_test.rb} +11 -11
- data/test/unit/remailer_smtp_server_test.rb +83 -0
- data/test/unit/{remailer_connection_socks5_interpreter_test.rb → remailer_socks5_client_interpreter_test.rb} +25 -17
- metadata +29 -11
data/README.rdoc
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
= remailer
|
2
2
|
|
3
|
-
|
3
|
+
Client/Server Mail Networking Library for SMTP and IMAP
|
4
4
|
|
5
5
|
== Overview
|
6
6
|
|
7
7
|
This is an EventMachine Connection implementation of a high-performance
|
8
8
|
asynchronous SMTP client. Although EventMachine ships with a built-in SMTP
|
9
|
-
client, that version is limited to sending a single email per
|
10
|
-
and since establishing a
|
9
|
+
client, that version is limited to sending a single email per client,
|
10
|
+
and since establishing a client can be the majority of the time required
|
11
11
|
to send email, this limits throughput considerably.
|
12
12
|
|
13
13
|
== Use
|
14
14
|
|
15
15
|
The Remailer system consists of the Remailer::Connection class which works
|
16
|
-
within the EventMachine environment. To use it, create a
|
16
|
+
within the EventMachine environment. To use it, create a client and then
|
17
17
|
make one or more requests to send email messages.
|
18
18
|
|
19
19
|
EventMachine.run do
|
20
|
-
# Establish a
|
20
|
+
# Establish a client to a particular SMTP server and send debugging
|
21
21
|
# messages to STDERR.
|
22
|
-
|
22
|
+
client = Remailer::SMTP::Client.open(
|
23
23
|
'smtp.google.com',
|
24
24
|
:debug => STDERR
|
25
25
|
)
|
26
26
|
|
27
|
-
# Send a single email message through the
|
28
|
-
# opportunity. Note that the
|
27
|
+
# Send a single email message through the client at the earliest
|
28
|
+
# opportunity. Note that the client will need to be fully
|
29
29
|
# established first and this may take upwards of ten seconds.
|
30
|
-
|
30
|
+
client.send_email(
|
31
31
|
'from@example.net',
|
32
32
|
'to@example.com',
|
33
33
|
email_content
|
34
34
|
)
|
35
35
|
|
36
|
-
# Send an additional message through the
|
36
|
+
# Send an additional message through the client. This will queue up
|
37
37
|
# until the first has been transmitted.
|
38
|
-
|
38
|
+
client.send_email(
|
39
39
|
'from@example.net',
|
40
40
|
'to@example.com',
|
41
41
|
email_content
|
42
42
|
)
|
43
43
|
|
44
|
-
# Tells the
|
45
|
-
|
44
|
+
# Tells the client to close out when finished.
|
45
|
+
client.close_when_complete!
|
46
46
|
end
|
47
47
|
|
48
48
|
A Proc can be supplied as the :debug option to Remailer::Connection.open and
|
49
49
|
in this case it will be called with two parameters, type and message. An
|
50
50
|
example is given here where the information is simply dumped on STDOUT:
|
51
51
|
|
52
|
-
|
52
|
+
client = Remailer::SMTP::Client.open(
|
53
53
|
'smtp.google.com',
|
54
54
|
:debug => lambda { |type, message|
|
55
55
|
puts "#{type}> #{message.inspect}"
|
@@ -62,17 +62,17 @@ The types defined include:
|
|
62
62
|
* :reply - Raw replies from the server
|
63
63
|
* :options - The finalized options used to connect to the server
|
64
64
|
|
65
|
-
This callback procedure can be defined or replaced after the
|
65
|
+
This callback procedure can be defined or replaced after the client is
|
66
66
|
initialized:
|
67
67
|
|
68
|
-
|
68
|
+
client.debug do |type, message|
|
69
69
|
STDERR.puts "%s> %s" % [ type, message.inspect ]
|
70
70
|
end
|
71
71
|
|
72
72
|
It's also possible to define a handler for when the message queue has been
|
73
73
|
exhausted:
|
74
74
|
|
75
|
-
|
75
|
+
client.after_complete do
|
76
76
|
STDERR.puts "Sending complete."
|
77
77
|
end
|
78
78
|
|
@@ -80,7 +80,7 @@ The call to send a message can also take a callback method which must receive
|
|
80
80
|
one parameter that will be the numerical status code returned by the SMTP
|
81
81
|
server. Success is defined as 250, errors vary:
|
82
82
|
|
83
|
-
|
83
|
+
client.send_email(
|
84
84
|
'from@example.net',
|
85
85
|
'to@example.com',
|
86
86
|
email_content
|
@@ -90,6 +90,12 @@ server. Success is defined as 250, errors vary:
|
|
90
90
|
|
91
91
|
A status code of nil is sent if the server timed out or the connection failed.
|
92
92
|
|
93
|
+
== Tests
|
94
|
+
|
95
|
+
In order to run tests, copy `test/config.example.rb` to `test/config.rb` and
|
96
|
+
adjust as required. For obvious reasons, passwords to SMTP test accounts are
|
97
|
+
not included in the source code of this library.
|
98
|
+
|
93
99
|
== Status
|
94
100
|
|
95
101
|
This software is currently experimental and is not recommended for production
|
@@ -98,4 +104,4 @@ release is made.
|
|
98
104
|
|
99
105
|
== Copyright
|
100
106
|
|
101
|
-
Copyright (c) 2010 Scott Tadman, The Working Group
|
107
|
+
Copyright (c) 2010-2012 Scott Tadman, The Working Group
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/remailer.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
module Remailer
|
2
2
|
# == Submodules ===========================================================
|
3
3
|
|
4
|
-
autoload(:
|
4
|
+
autoload(:AbstractConnection, 'remailer/abstract_connection')
|
5
|
+
autoload(:Constants, 'remailer/constants')
|
6
|
+
autoload(:EmailAddress, 'remailer/email_address')
|
5
7
|
autoload(:Interpreter, 'remailer/interpreter')
|
8
|
+
autoload(:IMAP, 'remailer/imap')
|
9
|
+
autoload(:SMTP, 'remailer/smtp')
|
10
|
+
autoload(:SOCKS5, 'remailer/socks5')
|
11
|
+
autoload(:Support, 'remailer/support')
|
6
12
|
end
|
@@ -1,23 +1,13 @@
|
|
1
|
-
|
2
|
-
require 'eventmachine'
|
3
|
-
|
4
|
-
class Remailer::Connection < EventMachine::Connection
|
1
|
+
class Remailer::AbstractConnection < EventMachine::Connection
|
5
2
|
# == Exceptions ===========================================================
|
6
3
|
|
7
4
|
class CallbackArgumentsRequired < Exception; end
|
8
5
|
|
9
|
-
# == Submodules ===========================================================
|
10
|
-
|
11
|
-
autoload(:SmtpInterpreter, 'remailer/connection/smtp_interpreter')
|
12
|
-
autoload(:Socks5Interpreter, 'remailer/connection/socks5_interpreter')
|
13
|
-
|
14
6
|
# == Constants ============================================================
|
15
7
|
|
16
|
-
|
17
|
-
DEFAULT_TIMEOUT = 5
|
8
|
+
include Remailer::Constants
|
18
9
|
|
19
|
-
|
20
|
-
SOCKS5_PORT = 1080
|
10
|
+
DEFAULT_TIMEOUT = 60
|
21
11
|
|
22
12
|
NOTIFICATIONS = [
|
23
13
|
:debug,
|
@@ -27,10 +17,6 @@ class Remailer::Connection < EventMachine::Connection
|
|
27
17
|
|
28
18
|
# == Properties ===========================================================
|
29
19
|
|
30
|
-
attr_accessor :active_message
|
31
|
-
attr_accessor :remote, :max_size, :protocol, :hostname
|
32
|
-
attr_accessor :pipelining, :tls_support, :auth_support
|
33
|
-
attr_accessor :timeout
|
34
20
|
attr_accessor :options
|
35
21
|
attr_reader :error, :error_message
|
36
22
|
|
@@ -39,13 +25,18 @@ class Remailer::Connection < EventMachine::Connection
|
|
39
25
|
include EventMachine::Deferrable
|
40
26
|
|
41
27
|
# == Class Methods ========================================================
|
28
|
+
|
29
|
+
# Defines the default timeout for connect operations.
|
30
|
+
def self.default_timeout
|
31
|
+
DEFAULT_TIMEOUT
|
32
|
+
end
|
42
33
|
|
43
|
-
# Opens a connection to a specific
|
44
|
-
# * port => Numerical port number
|
34
|
+
# Opens a connection to a specific server. Options can be specified:
|
35
|
+
# * port => Numerical port number
|
45
36
|
# * require_tls => If true will fail connections to non-TLS capable
|
46
37
|
# servers (default is false)
|
47
|
-
# * username => Username to authenticate with the
|
48
|
-
# * password => Password to authenticate with the
|
38
|
+
# * username => Username to authenticate with the server
|
39
|
+
# * password => Password to authenticate with the server
|
49
40
|
# * use_tls => Will use TLS if availble (default is true)
|
50
41
|
# * debug => Where to send debugging output (IO or Proc)
|
51
42
|
# * connect => Where to send a connection notification (IO or Proc)
|
@@ -56,10 +47,10 @@ class Remailer::Connection < EventMachine::Connection
|
|
56
47
|
# A block can be supplied in which case it will stand in as the :connect
|
57
48
|
# option. The block will recieve a first argument that is the status of
|
58
49
|
# the connection, and an optional second that is a diagnostic message.
|
59
|
-
def self.open(
|
50
|
+
def self.open(host, options = nil, &block)
|
60
51
|
options ||= { }
|
61
|
-
options[:host] =
|
62
|
-
options[:port] ||=
|
52
|
+
options[:host] = host
|
53
|
+
options[:port] ||= self.default_port
|
63
54
|
|
64
55
|
unless (options.key?(:use_tls))
|
65
56
|
options[:use_tls] = true
|
@@ -69,7 +60,7 @@ class Remailer::Connection < EventMachine::Connection
|
|
69
60
|
options[:connect] = block
|
70
61
|
end
|
71
62
|
|
72
|
-
host_name =
|
63
|
+
host_name = host
|
73
64
|
host_port = options[:port]
|
74
65
|
|
75
66
|
if (proxy_options = options[:proxy])
|
@@ -141,8 +132,7 @@ class Remailer::Connection < EventMachine::Connection
|
|
141
132
|
# allow for debugging, exceptions are dumped to STDERR as a last resort.
|
142
133
|
@options = options
|
143
134
|
@hostname = @options[:hostname] || Socket.gethostname
|
144
|
-
@timeout = @options[:timeout] ||
|
145
|
-
@protocol = :smtp
|
135
|
+
@timeout = @options[:timeout] || self.class.default_timeout
|
146
136
|
|
147
137
|
@messages = [ ]
|
148
138
|
|
@@ -158,12 +148,7 @@ class Remailer::Connection < EventMachine::Connection
|
|
158
148
|
|
159
149
|
reset_timeout!
|
160
150
|
|
161
|
-
|
162
|
-
@connecting_to_proxy = true
|
163
|
-
use_socks5_interpreter!
|
164
|
-
else
|
165
|
-
use_smtp_interpreter!
|
166
|
-
end
|
151
|
+
self.after_initialize
|
167
152
|
|
168
153
|
rescue Object => e
|
169
154
|
STDERR.puts "#{e.class}: #{e}"
|
@@ -175,15 +160,13 @@ class Remailer::Connection < EventMachine::Connection
|
|
175
160
|
end
|
176
161
|
|
177
162
|
# Returns true if the connection has advertised TLS support, or false if
|
178
|
-
# not availble or could not be detected.
|
179
|
-
# capable servers.
|
163
|
+
# not availble or could not be detected.
|
180
164
|
def tls_support?
|
181
165
|
!!@tls_support
|
182
166
|
end
|
183
167
|
|
184
168
|
# Returns true if the connection has advertised authentication support, or
|
185
|
-
# false if not availble or could not be detected.
|
186
|
-
# ESMTP capable servers.
|
169
|
+
# false if not availble or could not be detected.
|
187
170
|
def auth_support?
|
188
171
|
!!@auth_support
|
189
172
|
end
|
@@ -200,66 +183,6 @@ class Remailer::Connection < EventMachine::Connection
|
|
200
183
|
@options[:username] and !@options[:username].empty?
|
201
184
|
end
|
202
185
|
|
203
|
-
# This is used to create a callback that will be called if no more messages
|
204
|
-
# are schedueld to be sent.
|
205
|
-
def after_complete(&block)
|
206
|
-
@options[:after_complete] = block
|
207
|
-
end
|
208
|
-
|
209
|
-
# Closes the connection after all of the queued messages have been sent.
|
210
|
-
def close_when_complete!
|
211
|
-
@options[:close] = true
|
212
|
-
end
|
213
|
-
|
214
|
-
# Sends an email message through the connection at the earliest opportunity.
|
215
|
-
# A callback block can be supplied that will be executed when the message
|
216
|
-
# has been sent, an unexpected result occurred, or the send timed out.
|
217
|
-
def send_email(from, to, data, &block)
|
218
|
-
if (block_given?)
|
219
|
-
self.class.warn_about_arguments(block, 1..2)
|
220
|
-
end
|
221
|
-
|
222
|
-
message = {
|
223
|
-
:from => from,
|
224
|
-
:to => to,
|
225
|
-
:data => data,
|
226
|
-
:callback => block
|
227
|
-
}
|
228
|
-
|
229
|
-
@messages << message
|
230
|
-
|
231
|
-
# If the connection is ready to send...
|
232
|
-
if (@interpreter and @interpreter.state == :ready)
|
233
|
-
# ...send the message right away.
|
234
|
-
after_ready
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
# Tests the validity of an email address through the connection at the
|
239
|
-
# earliest opportunity. A callback block can be supplied that will be
|
240
|
-
# executed when the address has been tested, an unexpected result occurred,
|
241
|
-
# or the request timed out.
|
242
|
-
def test_email(from, to, &block)
|
243
|
-
if (block_given?)
|
244
|
-
self.class.warn_about_arguments(block, 1..2)
|
245
|
-
end
|
246
|
-
|
247
|
-
message = {
|
248
|
-
:from => from,
|
249
|
-
:to => to,
|
250
|
-
:test => true,
|
251
|
-
:callback => block
|
252
|
-
}
|
253
|
-
|
254
|
-
@messages << message
|
255
|
-
|
256
|
-
# If the connection is ready to send...
|
257
|
-
if (@interpreter and @interpreter.state == :ready)
|
258
|
-
# ...send the message right away.
|
259
|
-
after_ready
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
186
|
# Reassigns the timeout which is specified in seconds. Values equal to
|
264
187
|
# or less than zero are ignored and a default is used instead.
|
265
188
|
def timeout=(value)
|
@@ -267,14 +190,18 @@ class Remailer::Connection < EventMachine::Connection
|
|
267
190
|
@timeout = DEFAULT_TIMEOUT if (@timeout <= 0)
|
268
191
|
end
|
269
192
|
|
270
|
-
def proxy_connection_initiated
|
193
|
+
def proxy_connection_initiated!
|
271
194
|
@connecting_to_proxy = false
|
272
195
|
end
|
196
|
+
|
197
|
+
def proxy_connection_initiated?
|
198
|
+
!!@connecting_to_proxy
|
199
|
+
end
|
273
200
|
|
274
201
|
# This implements the EventMachine::Connection#completed method by
|
275
202
|
# flagging the connection as estasblished.
|
276
203
|
def connection_completed
|
277
|
-
reset_timeout!
|
204
|
+
self.reset_timeout!
|
278
205
|
end
|
279
206
|
|
280
207
|
# This implements the EventMachine::Connection#unbind method to capture
|
@@ -282,24 +209,9 @@ class Remailer::Connection < EventMachine::Connection
|
|
282
209
|
def unbind
|
283
210
|
return if (@unbound)
|
284
211
|
|
285
|
-
|
286
|
-
|
287
|
-
if (@active_message)
|
288
|
-
debug_notification(:disconnect, "Disconnected by remote before transaction could be completed.")
|
289
|
-
|
290
|
-
if (callback = @active_message[:callback])
|
291
|
-
callback.call(nil)
|
292
|
-
|
293
|
-
@active_message = nil
|
294
|
-
end
|
295
|
-
elsif (@closed)
|
296
|
-
debug_notification(:disconnect, "Disconnected from remote.")
|
297
|
-
elsif (!@established)
|
298
|
-
error_notification(:hangup, "Disconnected from remote before fully established.")
|
299
|
-
else
|
300
|
-
debug_notification(:disconnect, "Disconnected by remote while connection was idle.")
|
301
|
-
end
|
212
|
+
self.after_unbind
|
302
213
|
|
214
|
+
@unbound = true
|
303
215
|
@connected = false
|
304
216
|
@timeout_at = nil
|
305
217
|
@interpreter = nil
|
@@ -328,17 +240,18 @@ class Remailer::Connection < EventMachine::Connection
|
|
328
240
|
else
|
329
241
|
error_notification(:out_of_band, "Receiving data before a protocol has been established.")
|
330
242
|
end
|
243
|
+
|
244
|
+
rescue Object => e
|
245
|
+
STDERR.puts("[#{e.class}] #{e}")
|
331
246
|
end
|
332
247
|
|
333
248
|
def post_init
|
334
|
-
|
335
|
-
check_for_timeouts!
|
336
|
-
end
|
249
|
+
self.set_timer!
|
337
250
|
end
|
338
251
|
|
339
|
-
#
|
340
252
|
def detach
|
341
|
-
|
253
|
+
self.cancel_timer!
|
254
|
+
|
342
255
|
super
|
343
256
|
end
|
344
257
|
|
@@ -392,6 +305,19 @@ class Remailer::Connection < EventMachine::Connection
|
|
392
305
|
@timeout_at and (@timeout_at.to_i - Time.now.to_i)
|
393
306
|
end
|
394
307
|
|
308
|
+
def set_timer!
|
309
|
+
@timer = EventMachine.add_periodic_timer(1) do
|
310
|
+
self.check_for_timeouts!
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def cancel_timer!
|
315
|
+
if (@timer)
|
316
|
+
@timer.cancel
|
317
|
+
@timer = nil
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
395
321
|
# Checks for a timeout condition, and if one is detected, will close the
|
396
322
|
# connection and send appropriate callbacks.
|
397
323
|
def check_for_timeouts!
|
@@ -434,19 +360,7 @@ class Remailer::Connection < EventMachine::Connection
|
|
434
360
|
end
|
435
361
|
end
|
436
362
|
|
437
|
-
close_connection
|
438
|
-
end
|
439
|
-
|
440
|
-
# Returns true if pipelining support has been detected on the connection,
|
441
|
-
# false otherwise.
|
442
|
-
def pipelining?
|
443
|
-
!!@pipelining
|
444
|
-
end
|
445
|
-
|
446
|
-
# Returns true if pipelining support has been detected on the connection,
|
447
|
-
# false otherwise.
|
448
|
-
def tls_support?
|
449
|
-
!!@tls_support
|
363
|
+
self.close_connection
|
450
364
|
end
|
451
365
|
|
452
366
|
# Returns true if the connection has been closed, false otherwise.
|
@@ -484,47 +398,19 @@ class Remailer::Connection < EventMachine::Connection
|
|
484
398
|
end
|
485
399
|
alias_method :close, :close_connection
|
486
400
|
|
487
|
-
# Switches to use the SOCKS5 interpreter for all subsequent communication
|
488
|
-
def use_socks5_interpreter!
|
489
|
-
@interpreter = Remailer::Connection::Socks5Interpreter.new(:delegate => self)
|
490
|
-
end
|
491
|
-
|
492
|
-
# Switches to use the SMTP interpreter for all subsequent communication
|
493
|
-
def use_smtp_interpreter!
|
494
|
-
@interpreter = Remailer::Connection::SmtpInterpreter.new(:delegate => self)
|
495
|
-
end
|
496
|
-
|
497
|
-
# Callback receiver for when the proxy connection has been completed.
|
498
|
-
def after_proxy_connected
|
499
|
-
use_smtp_interpreter!
|
500
|
-
end
|
501
|
-
|
502
401
|
def after_ready
|
503
|
-
return if (@active_message)
|
504
|
-
|
505
402
|
@established = true
|
506
403
|
|
507
404
|
reset_timeout!
|
508
|
-
|
509
|
-
if (@active_message = @messages.shift)
|
510
|
-
if (@interpreter.state == :ready)
|
511
|
-
@interpreter.enter_state(:send)
|
512
|
-
end
|
513
|
-
elsif (@options[:close])
|
514
|
-
if (callback = @options[:after_complete])
|
515
|
-
callback.call
|
516
|
-
end
|
517
|
-
|
518
|
-
@interpreter.enter_state(:quit)
|
519
|
-
end
|
520
405
|
end
|
521
406
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
@active_message = nil
|
407
|
+
# Switches to use the SOCKS5 interpreter for all subsequent communication
|
408
|
+
def use_socks5_interpreter!
|
409
|
+
@interpreter = Remailer::SOCKS5::Client::Interpreter.new(:delegate => self)
|
526
410
|
end
|
527
411
|
|
412
|
+
# -- Callbacks and Notifications ------------------------------------------
|
413
|
+
|
528
414
|
def interpreter_entered_state(interpreter, state)
|
529
415
|
debug_notification(:state, "#{interpreter.label.downcase}=#{state}")
|
530
416
|
end
|
@@ -541,6 +427,16 @@ class Remailer::Connection < EventMachine::Connection
|
|
541
427
|
STDERR.puts("%s: %s" % [ code.to_s, message ])
|
542
428
|
end
|
543
429
|
end
|
430
|
+
|
431
|
+
# EventMachine: Enables TLS support on the connection.
|
432
|
+
def start_tls
|
433
|
+
debug_notification(:tls, "Started")
|
434
|
+
super
|
435
|
+
end
|
436
|
+
|
437
|
+
def connected?
|
438
|
+
@connected
|
439
|
+
end
|
544
440
|
|
545
441
|
def connect_notification(code, message = nil)
|
546
442
|
@connected = code
|