remailer 0.4.21 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/README.rdoc +25 -19
  2. data/VERSION +1 -1
  3. data/lib/remailer.rb +7 -1
  4. data/lib/remailer/{connection.rb → abstract_connection.rb} +63 -167
  5. data/lib/remailer/constants.rb +10 -0
  6. data/lib/remailer/email_address.rb +41 -0
  7. data/lib/remailer/imap.rb +6 -0
  8. data/lib/remailer/imap/client.rb +228 -0
  9. data/lib/remailer/imap/client/interpreter.rb +101 -0
  10. data/lib/remailer/imap/server.rb +15 -0
  11. data/lib/remailer/imap/server/interpreter.rb +2 -0
  12. data/lib/remailer/interpreter.rb +7 -6
  13. data/lib/remailer/interpreter/state_proxy.rb +20 -2
  14. data/lib/remailer/smtp.rb +6 -0
  15. data/lib/remailer/smtp/client.rb +329 -0
  16. data/lib/remailer/{connection/smtp_interpreter.rb → smtp/client/interpreter.rb} +4 -4
  17. data/lib/remailer/smtp/server.rb +130 -0
  18. data/lib/remailer/smtp/server/interpreter.rb +237 -0
  19. data/lib/remailer/smtp/server/transaction.rb +29 -0
  20. data/lib/remailer/socks5.rb +5 -0
  21. data/lib/remailer/socks5/client.rb +5 -0
  22. data/lib/remailer/{connection/socks5_interpreter.rb → socks5/client/interpreter.rb} +3 -2
  23. data/lib/remailer/support.rb +5 -0
  24. data/remailer.gemspec +27 -9
  25. data/test/unit/remailer_imap_client_interpreter_test.rb +14 -0
  26. data/test/unit/remailer_imap_client_test.rb +125 -0
  27. data/test/unit/{remailer_connection_smtp_interpreter_test.rb → remailer_smtp_client_interpreter_test.rb} +33 -33
  28. data/test/unit/{remailer_connection_test.rb → remailer_smtp_client_test.rb} +11 -11
  29. data/test/unit/remailer_smtp_server_test.rb +83 -0
  30. data/test/unit/{remailer_connection_socks5_interpreter_test.rb → remailer_socks5_client_interpreter_test.rb} +25 -17
  31. metadata +29 -11
data/README.rdoc CHANGED
@@ -1,55 +1,55 @@
1
1
  = remailer
2
2
 
3
- Reactor-Ready Mailer Engine
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 connection,
10
- and since establishing a connection can be the majority of the time required
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 connection and then
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 connection to a particular SMTP server and send debugging
20
+ # Establish a client to a particular SMTP server and send debugging
21
21
  # messages to STDERR.
22
- connection = Remailer::Connection.open(
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 connection at the earliest
28
- # opportunity. Note that the connection will need to be fully
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
- connection.send_email(
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 connection. This will queue up
36
+ # Send an additional message through the client. This will queue up
37
37
  # until the first has been transmitted.
38
- connection.send_email(
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 connection to close out when finished.
45
- connection.close_when_complete!
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
- connection = Remailer::Connection.open(
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 connection is
65
+ This callback procedure can be defined or replaced after the client is
66
66
  initialized:
67
67
 
68
- connection.debug do |type, message|
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
- connection.after_complete do
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
- connection.send_email(
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.4.21
1
+ 0.5.0
data/lib/remailer.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  module Remailer
2
2
  # == Submodules ===========================================================
3
3
 
4
- autoload(:Connection, 'remailer/connection')
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
- require 'socket'
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
- CRLF = "\r\n".freeze
17
- DEFAULT_TIMEOUT = 5
8
+ include Remailer::Constants
18
9
 
19
- SMTP_PORT = 25
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 SMTP server. Options can be specified:
44
- # * port => Numerical port number (default is 25)
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 SMTP server (optional)
48
- # * password => Password to authenticate with the SMTP server (optional)
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(smtp_server, options = nil, &block)
50
+ def self.open(host, options = nil, &block)
60
51
  options ||= { }
61
- options[:host] = smtp_server
62
- options[:port] ||= 25
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 = smtp_server
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] || DEFAULT_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
- if (using_proxy?)
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. This will only work with ESMTP
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. This will only work with
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
- @unbound = true
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
- @timer = EventMachine.add_periodic_timer(1) do
335
- check_for_timeouts!
336
- end
249
+ self.set_timer!
337
250
  end
338
251
 
339
- #
340
252
  def detach
341
- @timer.cancel
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
- def after_message_sent(reply_code, reply_message)
523
- message_callback(reply_code, reply_message)
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