remailer 0.4.21 → 0.5.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/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
|