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.
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
@@ -0,0 +1,237 @@
1
+ class Remailer::SMTP::Server::Interpreter < Remailer::Interpreter
2
+ # == State Definitions ====================================================
3
+
4
+ default do |error|
5
+ delegate.send_line("500 Invalid command")
6
+ end
7
+
8
+ state :initialized do
9
+ enter do
10
+ self.send_banner
11
+
12
+ enter_state(:reset)
13
+ end
14
+ end
15
+
16
+ state :reset do
17
+ enter do
18
+ self.reset_transaction!
19
+
20
+ enter_state(:ready)
21
+ end
22
+ end
23
+
24
+ state :ready do
25
+ interpret(/^\s*EHLO\s+(\S+)\s*$/) do |remote_host|
26
+ delegate.validate_hostname(remote_host) do |valid|
27
+ if (valid)
28
+ delegate.log(:debug, "#{delegate.remote_ip}:#{delegate.remote_port} to #{delegate.local_ip}:#{delegate.local_port} Accepting connection from #{remote_host}")
29
+ @remote_host = remote_host
30
+
31
+ delegate.send_line("250-#{delegate.server_name} Hello #{delegate.remote_host} [#{delegate.remote_ip}]")
32
+ delegate.send_line("250-AUTH PLAIN")
33
+ delegate.send_line("250-SIZE 35651584")
34
+ delegate.send_line("250-STARTTLS") if (delegate.tls?)
35
+ delegate.send_line("250 OK")
36
+ else
37
+ delegate.log(:debug, "#{delegate.remote_ip}:#{delegate.remote_port} to #{delegate.local_ip}:#{delegate.local_port} Rejecting connection from #{remote_host} because of invalid FQDN")
38
+ delegate.send_line("504 Need fully qualified hostname")
39
+ end
40
+ end
41
+ end
42
+
43
+ interpret(/^\s*HELO\s+(\S+)\s*$/) do |remote_host|
44
+ delegate.validate_hostname(remote_host) do |valid|
45
+ if (valid)
46
+ delegate.log(:debug, "#{delegate.remote_ip}:#{delegate.remote_port} to #{delegate.local_ip}:#{delegate.local_port} Accepting connection from #{remote_host}")
47
+ @remote_host = remote_host
48
+
49
+ delegate.send_line("250 #{delegate.server_name} Hello #{delegate.remote_host} [#{delegate.remote_ip}]")
50
+ else
51
+ delegate.log(:debug, "#{delegate.remote_ip}:#{delegate.remote_port} to #{delegate.local_ip}:#{delegate.local_port} Rejecting connection from #{remote_host} because of invalid FQDN")
52
+ delegate.send_line("504 Need fully qualified hostname")
53
+ end
54
+ end
55
+ end
56
+
57
+ interpret(/^\s*MAIL\s+FROM:\s*<([^>]+)>\s*/) do |address|
58
+ if (Remailer::EmailAddress.valid?(address))
59
+ accept, message = will_accept_sender(address)
60
+
61
+ if (accept)
62
+ @transaction.sender = address
63
+ end
64
+
65
+ delegate.send_line(message)
66
+ else
67
+ delegate.send_line("501 Email address is not RFC compliant")
68
+ end
69
+ end
70
+
71
+ interpret(/^\s*RCPT\s+TO:\s*<([^>]+)>\s*/) do |address|
72
+ if (@transaction.sender)
73
+ if (Remailer::EmailAddress.valid?(address))
74
+ accept, message = will_accept_recipient(address)
75
+
76
+ if (accept)
77
+ @transaction.recipients ||= [ ]
78
+ @transaction.recipients << address
79
+ end
80
+
81
+ delegate.send_line(message)
82
+ else
83
+ delegate.send_line("501 Email address is not RFC compliant")
84
+ end
85
+ else
86
+ delegate.send_line("503 Sender not specified")
87
+ end
88
+ end
89
+
90
+ interpret(/^\s*AUTH\s+PLAIN\s+(.*)\s*$/) do |auth|
91
+ # 235 2.7.0 Authentication successful
92
+ delegate.send("235 whatever")
93
+ end
94
+
95
+ interpret(/^\s*AUTH\s+PLAIN\s*$/) do
96
+ # Multi-line authentication method
97
+ enter_state(:auth_plain)
98
+ end
99
+
100
+ interpret(/^\s*STARTTLS\s*$/) do
101
+ if (@tls_started)
102
+ delegate.send_line("454 TLS already started")
103
+ elsif (delegate.tls?)
104
+ delegate.send_line("220 TLS ready to start")
105
+ delegate.start_tls(
106
+ :private_key_file => Remailer::SMTP::Server.private_key_path,
107
+ :cert_chain_file => Remailer::SMTP::Server.ssl_cert_path
108
+ )
109
+
110
+ @tls_started = true
111
+ else
112
+ delegate.send_line("421 TLS not supported")
113
+ end
114
+ end
115
+
116
+ interpret(/^\s*DATA\s*$/) do
117
+ if (@transaction.sender)
118
+ else
119
+ delegate.send_line("503 valid RCPT command must precede DATA")
120
+ end
121
+
122
+ enter_state(:data)
123
+ delegate.send_line("354 Supply message data")
124
+ end
125
+
126
+ interpret(/^\s*NOOP\s*$/) do |remote_host|
127
+ delegate.send_line("250 OK")
128
+ end
129
+
130
+ interpret(/^\s*RSET\s*$/) do |remote_host|
131
+ delegate.send_line("250 Reset OK")
132
+
133
+ enter_state(:reset)
134
+ end
135
+
136
+ interpret(/^\s*QUIT\s*$/) do
137
+ delegate.send_line("221 #{delegate.server_name} closing connection")
138
+
139
+ delegate.close_connection(true)
140
+ end
141
+ end
142
+
143
+ state :data do
144
+ interpret(/^\.$/) do
145
+ accept, message = will_accept_transaction(@transaction)
146
+
147
+ if (accept)
148
+ accept, message = delegate.receive_transaction(@transaction)
149
+
150
+ delegate.send_line(message)
151
+ else
152
+ delegate.send_line(message)
153
+ end
154
+
155
+ self.reset_transaction!
156
+
157
+ enter_state(:ready)
158
+ end
159
+
160
+ default do |line|
161
+ # RFC5321 4.5.2 - Leading dot is removed if line has content
162
+
163
+ @transaction.data << (line.sub(/^\./, '') + Remailer::Constants::CRLF)
164
+ end
165
+ end
166
+
167
+ state :auth_plain do
168
+ # Receive a single line of authentication
169
+ # ...
170
+ end
171
+
172
+ state :reply do
173
+ enter do
174
+ # Random delay if required
175
+ delegate.send_line(@reply)
176
+ end
177
+
178
+ default do
179
+ delegate.send_line("554 SMTP Synchronization Error")
180
+ enter_state(:ready)
181
+ end
182
+ end
183
+
184
+ state :timeout do
185
+ enter do
186
+ delegate.send_line("420 Idle connection closed")
187
+
188
+ delegate.close_connection(true)
189
+ end
190
+ end
191
+
192
+ # == Instance Methods =====================================================
193
+
194
+ def reset_transaction!
195
+ @transaction = Remailer::SMTP::Server::Transaction.new
196
+ end
197
+
198
+ def send_banner
199
+ delegate.send_line("220 #{delegate.server_name} Remailer ESMTP Server Ready")
200
+ end
201
+
202
+ def reset_ttl!
203
+ @timeout_at = Time.now + self.connection_ttl
204
+ end
205
+
206
+ def enter_state(state)
207
+ self.reset_ttl!
208
+
209
+ super(state)
210
+ end
211
+
212
+ def connection_ttl
213
+ 10
214
+ end
215
+
216
+ def ttl_expired?
217
+ @timeout_at ? (Time.now > @timeout_at) : false
218
+ end
219
+
220
+ def check_for_timeout!
221
+ if (self.ttl_expired?)
222
+ enter_state(:timeout)
223
+ end
224
+ end
225
+
226
+ def will_accept_sender(sender)
227
+ [ true, "250 Accepted" ]
228
+ end
229
+
230
+ def will_accept_recipient(recipient)
231
+ [ true, "250 Accepted" ]
232
+ end
233
+
234
+ def will_accept_transaction(transaction)
235
+ [ true, "250 Accepted" ]
236
+ end
237
+ end
@@ -0,0 +1,29 @@
1
+ class Remailer::SMTP::Server::Transaction
2
+ # == Constants ============================================================
3
+
4
+ ATTRIBUTES = [ :sender, :recipients, :data ].freeze
5
+
6
+ # == Properties ===========================================================
7
+
8
+ attr_accessor *ATTRIBUTES
9
+
10
+ # == Class Methods ========================================================
11
+
12
+ # == Instance Methods =====================================================
13
+
14
+ def initialize(options = nil)
15
+ case (options)
16
+ when Remailer::SMTP::Server::Transaction
17
+ ATTRIBUTES.each do |attribute|
18
+ instance_variable_set("@#{attribute}", options.send(attribute))
19
+ end
20
+ when Hash
21
+ ATTRIBUTES.each do |attr|
22
+ instance_variable_set("@#{attribute}", options[attribute])
23
+ end
24
+ end
25
+
26
+ self.recipients = [ self.recipients ].compact.flatten
27
+ self.data ||= ''
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module Remailer::SOCKS5
2
+ # == Submodules ===========================================================
3
+
4
+ autoload(:Client, 'remailer/socks5/client')
5
+ end
@@ -0,0 +1,5 @@
1
+ module Remailer::SOCKS5::Client
2
+ # == Submodules ===========================================================
3
+
4
+ autoload(:Interpreter, 'remailer/socks5/client/interpreter')
5
+ end
@@ -1,6 +1,7 @@
1
- class Remailer::Connection::Socks5Interpreter < Remailer::Interpreter
1
+ class Remailer::SOCKS5::Client::Interpreter < Remailer::Interpreter
2
2
  # == Constants ============================================================
3
3
 
4
+ # RFC1928 and RFC1929 define these values
4
5
  SOCKS5_VERSION = 5
5
6
 
6
7
  SOCKS5_METHOD = {
@@ -44,7 +45,7 @@ class Remailer::Connection::Socks5Interpreter < Remailer::Interpreter
44
45
  socks_methods << SOCKS5_METHOD[:username_password]
45
46
  end
46
47
 
47
- proxy_options[:port] ||= Remailer::Connection::SOCKS5_PORT
48
+ proxy_options[:port] ||= Remailer::Constants::SOCKS5_PORT
48
49
 
49
50
  delegate.debug_notification(:proxy, "Initiating proxy connection through #{proxy_options[:host]}:#{proxy_options[:port]}")
50
51
 
@@ -0,0 +1,5 @@
1
+ module Remailer::Support
2
+ def quoted(string)
3
+ string.to_s.inspect
4
+ end
5
+ end
data/remailer.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "remailer"
8
- s.version = "0.4.21"
8
+ s.version = "0.5.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Scott Tadman"]
12
- s.date = "2011-11-03"
12
+ s.date = "2012-03-19"
13
13
  s.description = "EventMachine SMTP Mail User Agent"
14
14
  s.email = "scott@twg.ca"
15
15
  s.extra_rdoc_files = [
@@ -21,24 +21,42 @@ Gem::Specification.new do |s|
21
21
  "Rakefile",
22
22
  "VERSION",
23
23
  "lib/remailer.rb",
24
- "lib/remailer/connection.rb",
25
- "lib/remailer/connection/smtp_interpreter.rb",
26
- "lib/remailer/connection/socks5_interpreter.rb",
24
+ "lib/remailer/abstract_connection.rb",
25
+ "lib/remailer/constants.rb",
26
+ "lib/remailer/email_address.rb",
27
+ "lib/remailer/imap.rb",
28
+ "lib/remailer/imap/client.rb",
29
+ "lib/remailer/imap/client/interpreter.rb",
30
+ "lib/remailer/imap/server.rb",
31
+ "lib/remailer/imap/server/interpreter.rb",
27
32
  "lib/remailer/interpreter.rb",
28
33
  "lib/remailer/interpreter/state_proxy.rb",
34
+ "lib/remailer/smtp.rb",
35
+ "lib/remailer/smtp/client.rb",
36
+ "lib/remailer/smtp/client/interpreter.rb",
37
+ "lib/remailer/smtp/server.rb",
38
+ "lib/remailer/smtp/server/interpreter.rb",
39
+ "lib/remailer/smtp/server/transaction.rb",
40
+ "lib/remailer/socks5.rb",
41
+ "lib/remailer/socks5/client.rb",
42
+ "lib/remailer/socks5/client/interpreter.rb",
43
+ "lib/remailer/support.rb",
29
44
  "remailer.gemspec",
30
45
  "test/config.example.rb",
31
46
  "test/helper.rb",
32
- "test/unit/remailer_connection_smtp_interpreter_test.rb",
33
- "test/unit/remailer_connection_socks5_interpreter_test.rb",
34
- "test/unit/remailer_connection_test.rb",
47
+ "test/unit/remailer_imap_client_interpreter_test.rb",
48
+ "test/unit/remailer_imap_client_test.rb",
35
49
  "test/unit/remailer_interpreter_state_proxy_test.rb",
36
50
  "test/unit/remailer_interpreter_test.rb",
51
+ "test/unit/remailer_smtp_client_interpreter_test.rb",
52
+ "test/unit/remailer_smtp_client_test.rb",
53
+ "test/unit/remailer_smtp_server_test.rb",
54
+ "test/unit/remailer_socks5_client_interpreter_test.rb",
37
55
  "test/unit/remailer_test.rb"
38
56
  ]
39
57
  s.homepage = "http://github.com/twg/remailer"
40
58
  s.require_paths = ["lib"]
41
- s.rubygems_version = "1.8.10"
59
+ s.rubygems_version = "1.8.17"
42
60
  s.summary = "Reactor-Ready SMTP Mailer"
43
61
 
44
62
  if s.respond_to? :specification_version then
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class RemailerIMAPClientInterpreterTest < Test::Unit::TestCase
4
+ def test_split_list_definition
5
+ assert_mapping(
6
+ '(\HasChildren \HasNoChildren) "/" "[Gmail]/All Mail"' =>
7
+ [ %w[ \HasChildren \HasNoChildren ], "/", "[Gmail]/All Mail" ],
8
+ '(\HasNoChildren) "/" "INBOX"' =>
9
+ [ %w[ \HasNoChildren ], "/", "INBOX" ]
10
+ ) do |string|
11
+ Remailer::IMAP::Client::Interpreter.split_list_definition(string)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class RemailerIMAPClientTest < Test::Unit::TestCase
4
+ def setup
5
+ STDERR.sync = true
6
+ end
7
+
8
+ def test_connect
9
+ engine do
10
+ debug = { }
11
+
12
+ client = Remailer::IMAP::Client.open(
13
+ TestConfig.imap_server[:host],
14
+ :debug => STDERR,
15
+ :connect => lambda { |success, host| connected_host = host }
16
+ )
17
+
18
+ assert client
19
+
20
+ assert_eventually(30) do
21
+ client.connected?
22
+ end
23
+
24
+ capabilities = nil
25
+
26
+ client.capability do |list|
27
+ capabilities = list
28
+ end
29
+
30
+ assert_eventually(10) do
31
+ capabilities
32
+ end
33
+
34
+ assert_equal %w[ IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH ], capabilities
35
+
36
+ # -- LOGIN ------------------------------------------------------------
37
+
38
+ login_status = nil
39
+
40
+ client.login(TestConfig.smtp_server[:username], TestConfig.smtp_server[:password]) do |status, message|
41
+ login_status = status
42
+ end
43
+
44
+ assert_eventually(10) do
45
+ login_status
46
+ end
47
+
48
+ assert_equal 'OK', login_status
49
+
50
+ # -- LIST -------------------------------------------------------------
51
+
52
+ list = nil
53
+
54
+ client.list do |status, message, additional|
55
+ list = additional
56
+ end
57
+
58
+ assert_eventually(10) do
59
+ list
60
+ end
61
+
62
+ assert list.find { |i| i[0] == 'INBOX' }
63
+
64
+ # -- EXAMINE -----------------------------------------------------------
65
+
66
+ examine_status = nil
67
+
68
+ client.examine('INBOX') do |status, message, additional|
69
+ examine_status = status
70
+ end
71
+
72
+ assert_eventually(10) do
73
+ examine_status
74
+ end
75
+
76
+ assert_equal 'OK', examine_status
77
+
78
+ # -- SELECT -----------------------------------------------------------
79
+
80
+ select_status = nil
81
+
82
+ client.select('INBOX') do |status, message, additional|
83
+ select_status = status
84
+ end
85
+
86
+ assert_eventually(10) do
87
+ select_status
88
+ end
89
+
90
+ assert_equal 'OK', select_status
91
+
92
+ # -- IDLE ------------------------------------------------------------
93
+
94
+ idle_status = nil
95
+
96
+ idle = client.idle do |status|
97
+ idle_status = status
98
+ end
99
+
100
+ assert_equal nil, idle_status
101
+
102
+ idle.call
103
+
104
+ assert_eventually(5) do
105
+ idle_status
106
+ end
107
+
108
+ assert_equal 'OK', idle_status
109
+
110
+ # -- FETCH -----------------------------------------------------------
111
+
112
+ fetch_status = nil
113
+
114
+ client.fetch(2..5) do |status, message, additional|
115
+ fetch_status = status
116
+ end
117
+
118
+ assert_eventually(10) do
119
+ fetch_status
120
+ end
121
+
122
+ assert_equal 'OK', fetch_status
123
+ end
124
+ end
125
+ end