remailer 0.2.1 → 0.3.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.
@@ -0,0 +1,270 @@
1
+ class Remailer::Connection::SmtpInterpreter < Remailer::Interpreter
2
+ # == Constants ============================================================
3
+
4
+ LINE_REGEXP = /^.*?\r?\n/.freeze
5
+
6
+ # == Properties ===========================================================
7
+
8
+ # == Class Methods ========================================================
9
+
10
+ # Expands a standard SMTP reply into three parts: Numerical code, message
11
+ # and a boolean indicating if this reply is continued on a subsequent line.
12
+ def self.split_reply(reply)
13
+ reply.match(/(\d+)([ \-])(.*)/) and [ $1.to_i, $3, $2 == '-' ? :continued : nil ].compact
14
+ end
15
+
16
+ # Encodes the given user authentication paramters as a Base64-encoded
17
+ # string as defined by RFC4954
18
+ def self.encode_authentication(username, password)
19
+ base64("\0#{username}\0#{password}")
20
+ end
21
+
22
+ # Encodes the given data for an RFC5321-compliant stream where lines with
23
+ # leading period chracters are escaped.
24
+ def self.encode_data(data)
25
+ data.gsub(/((?:\r\n|\n)\.)/m, '\\1.')
26
+ end
27
+
28
+ # Encodes a string in Base64 as a single line
29
+ def self.base64(string)
30
+ [ string.to_s ].pack('m').chomp
31
+ end
32
+
33
+ # == State Mapping ========================================================
34
+
35
+ parse(LINE_REGEXP) do |data|
36
+ split_reply(data.chomp)
37
+ end
38
+
39
+ state :initialized do
40
+ interpret(220) do |message|
41
+ message_parts = message.split(/\s+/)
42
+ delegate.remote = message_parts.first
43
+
44
+ if (message_parts.include?('ESMTP'))
45
+ delegate.protocol = :esmtp
46
+ enter_state(:ehlo)
47
+ else
48
+ delegate.protocol = :smtp
49
+ enter_state(:helo)
50
+ end
51
+ end
52
+ end
53
+
54
+ state :helo do
55
+ enter do
56
+ delegate.send_line("HELO #{delegate.hostname}")
57
+ end
58
+
59
+ interpret(250) do
60
+ enter_state(:established)
61
+ end
62
+ end
63
+
64
+ state :ehlo do
65
+ enter do
66
+ delegate.send_line("EHLO #{delegate.hostname}")
67
+ end
68
+
69
+ interpret(250) do |message, continues|
70
+ message_parts = message.split(/\s+/)
71
+
72
+ case (message_parts[0].to_s.upcase)
73
+ when 'SIZE'
74
+ delegate.max_size = message_parts[1].to_i
75
+ when 'PIPELINING'
76
+ delegate.pipelining = true
77
+ when 'STARTTLS'
78
+ delegate.tls_support = true
79
+ when 'AUTH'
80
+ delegate.auth_support = message_parts[1, message_parts.length].inject({ }) do |h, v|
81
+ h[v] = true
82
+ h
83
+ end
84
+ end
85
+
86
+ unless (continues)
87
+ if (delegate.use_tls? and delegate.tls_support?)
88
+ enter_state(:starttls)
89
+ elsif (delegate.requires_authentication?)
90
+ enter_state(:auth)
91
+ else
92
+ enter_state(:established)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ state :starttls do
99
+ enter do
100
+ delegate.send_line("STARTTLS")
101
+ end
102
+
103
+ interpret(220) do
104
+ delegate.start_tls
105
+
106
+ if (delegate.requires_authentication?)
107
+ enter_state(:auth)
108
+ else
109
+ enter_state(:established)
110
+ end
111
+ end
112
+ end
113
+
114
+ state :auth do
115
+ enter do
116
+ delegate.send_line("AUTH PLAIN #{self.class.encode_authentication(delegate.options[:username], delegate.options[:password])}")
117
+ end
118
+
119
+ interpret(235) do
120
+ enter_state(:established)
121
+ end
122
+
123
+ interpret(535) do |message, continues|
124
+ if (@error)
125
+ @error << ' '
126
+
127
+ if (message.match(/^(\S+)/).to_s == @error.match(/^(\S+)/).to_s)
128
+ @error << message.sub(/^\S+/, '')
129
+ else
130
+ @error << message
131
+ end
132
+ else
133
+ @error = message
134
+ end
135
+
136
+ unless (continues)
137
+ enter_state(:quit)
138
+ end
139
+ end
140
+ end
141
+
142
+ state :established do
143
+ enter do
144
+ delegate.connect_notification(true)
145
+
146
+ enter_state(:ready)
147
+ end
148
+ end
149
+
150
+ state :ready do
151
+ enter do
152
+ delegate.after_ready
153
+ end
154
+ end
155
+
156
+ state :send do
157
+ enter do
158
+ enter_state(:mail_from)
159
+ end
160
+ end
161
+
162
+ state :mail_from do
163
+ enter do
164
+ delegate.send_line("MAIL FROM:#{delegate.active_message[:from]}")
165
+ end
166
+
167
+ interpret(250) do
168
+ enter_state(:rcpt_to)
169
+ end
170
+ end
171
+
172
+ state :rcpt_to do
173
+ enter do
174
+ delegate.send_line("RCPT TO:#{delegate.active_message[:to]}")
175
+ end
176
+
177
+ interpret(250) do
178
+ enter_state(:data)
179
+ end
180
+ end
181
+
182
+ state :data do
183
+ enter do
184
+ delegate.send_line("DATA")
185
+ end
186
+
187
+ interpret(354) do
188
+ enter_state(:sending)
189
+ end
190
+ end
191
+
192
+ state :sending do
193
+ enter do
194
+ data = delegate.active_message[:data]
195
+
196
+ delegate.debug_notification(:send, data.inspect)
197
+
198
+ delegate.send_data(self.class.encode_data(data))
199
+
200
+ # Ensure that a blank line is sent after the last bit of email content
201
+ # to ensure that the dot is on its own line.
202
+ delegate.send_line
203
+ delegate.send_line(".")
204
+ end
205
+
206
+ default do |reply_code, reply_message|
207
+ delegate_call(:after_message_sent, reply_code, reply_message)
208
+
209
+ enter_state(:sent)
210
+ end
211
+ end
212
+
213
+ state :sent do
214
+ enter do
215
+ enter_state(:ready)
216
+ end
217
+ end
218
+
219
+ state :quit do
220
+ enter do
221
+ delegate.send_line("QUIT")
222
+ end
223
+
224
+ interpret(221) do
225
+ enter_state(:terminated)
226
+ end
227
+ end
228
+
229
+ state :terminated do
230
+ enter do
231
+ delegate.close_connection
232
+ end
233
+ end
234
+
235
+ state :reset do
236
+ enter do
237
+ delegate.send_line("RESET")
238
+ end
239
+
240
+ interpret(250) do
241
+ enter_state(:ready)
242
+ end
243
+ end
244
+
245
+ state :noop do
246
+ enter do
247
+ delegate.send_line("NOOP")
248
+ end
249
+
250
+ interpret(250) do
251
+ enter_state(:ready)
252
+ end
253
+ end
254
+
255
+ on_error do |reply_code, reply_message|
256
+ delegate.send_callback(reply_code, reply_message)
257
+ delegate.debug_notification(:error, "[#{@state}] #{reply_code} #{reply_message}")
258
+ delegate.error_notification(reply_code, reply_message)
259
+
260
+ delegate.active_message = nil
261
+
262
+ enter_state(delegate.protocol ? :reset : :terminated)
263
+ end
264
+
265
+ # == Instance Methods =====================================================
266
+
267
+ def label
268
+ 'SMTP'
269
+ end
270
+ end
@@ -0,0 +1,186 @@
1
+ class Remailer::Connection::Socks5Interpreter < Remailer::Interpreter
2
+ # == Constants ============================================================
3
+
4
+ SOCKS5_VERSION = 5
5
+
6
+ SOCKS5_METHOD = {
7
+ :no_auth => 0,
8
+ :gssapi => 1,
9
+ :username_password => 2
10
+ }.freeze
11
+
12
+ SOCKS5_COMMAND = {
13
+ :connect => 1,
14
+ :bind => 2
15
+ }.freeze
16
+
17
+ SOCKS5_REPLY = {
18
+ 0 => 'Succeeded',
19
+ 1 => 'General SOCKS server failure',
20
+ 2 => 'Connection not allowed',
21
+ 3 => 'Network unreachable',
22
+ 4 => 'Host unreachable',
23
+ 5 => 'Connection refused',
24
+ 6 => 'TTL expired',
25
+ 7 => 'Command not supported',
26
+ 8 => 'Address type not supported'
27
+ }.freeze
28
+
29
+ SOCKS5_ADDRESS_TYPE = {
30
+ :ipv4 => 1,
31
+ :domainname => 3,
32
+ :ipv6 => 4
33
+ }.freeze
34
+
35
+ # == State Mapping ========================================================
36
+
37
+ state :initialized do
38
+ enter do
39
+ enter_state(:connect_to_proxy)
40
+ end
41
+ end
42
+
43
+ state :connect_to_proxy do
44
+ enter do
45
+ proxy_options = delegate.options[:proxy]
46
+
47
+ delegate.debug_notification(:proxy, "Initiating proxy connection through #{proxy_options[:host]}")
48
+
49
+ socks_methods = [ ]
50
+
51
+ if (proxy_options[:username])
52
+ socks_methods << SOCKS5_METHOD[:username_password]
53
+ end
54
+
55
+ delegate.send_data(
56
+ [
57
+ SOCKS5_VERSION,
58
+ socks_methods.length,
59
+ socks_methods
60
+ ].flatten.pack('CCC*')
61
+ )
62
+ end
63
+
64
+ parse do |s|
65
+ return unless (s.length >= 2)
66
+
67
+ version, method = s.slice!(0,2).unpack('CC')
68
+
69
+ method
70
+ end
71
+
72
+ interpret(SOCKS5_METHOD[:username_password]) do
73
+ enter_state(:authentication)
74
+ end
75
+
76
+ default do
77
+ enter_state(:resolving_destination)
78
+ end
79
+ end
80
+
81
+ state :resolving_destination do
82
+ enter do
83
+ # FIX: Use an async resolver here
84
+ @destination_address = delegate.resolve_hostname(delegate.options[:host])
85
+ enter_state(:connect_through_proxy)
86
+ end
87
+ end
88
+
89
+ state :connect_through_proxy do
90
+ enter do
91
+ delegate.debug_notification(:proxy, "Sending proxy connection request to #{delegate.options[:host]}:#{delegate.options[:port]}")
92
+
93
+ if (@destination_address)
94
+ delegate.send_data(
95
+ [
96
+ SOCKS5_VERSION,
97
+ SOCKS5_COMMAND[:connect],
98
+ 0,
99
+ SOCKS5_ADDRESS_TYPE[:ipv4],
100
+ @destination_address,
101
+ delegate.options[:port]
102
+ ].pack('CCCCA4n')
103
+ )
104
+ else
105
+ delegate.send_callback(:error_connecting, "Could not resolve hostname #{delegate.options[:host]}")
106
+ enter_state(:failed)
107
+ end
108
+ end
109
+
110
+ parse do |s|
111
+ return unless (s.length >= 10)
112
+
113
+ version, reply, reserved, address_type, address, port = s.slice!(0,10).unpack('CCCCNn')
114
+
115
+ [
116
+ reply,
117
+ {
118
+ :address => address,
119
+ :port => port,
120
+ :address_type => address_type
121
+ }
122
+ ]
123
+ end
124
+
125
+ interpret(0) do
126
+ enter_state(:connected)
127
+ end
128
+
129
+ default do |reply|
130
+ @reply = reply
131
+ enter_state(:failed)
132
+ end
133
+ end
134
+
135
+ state :authentication do
136
+ enter do
137
+ delegate.debug_notification(:proxy, "Sending proxy authentication")
138
+
139
+ proxy_options = delegate.options[:proxy]
140
+ username = proxy_options[:username]
141
+ password = proxy_options[:password]
142
+
143
+ send_data(
144
+ [
145
+ SOCKS5_VERSION,
146
+ username.length,
147
+ username,
148
+ password.length,
149
+ password
150
+ ].pack('CCA*CA*')
151
+ )
152
+ end
153
+
154
+ parse do |s|
155
+ end
156
+
157
+ interpret(0) do
158
+ enter_state(:connected)
159
+ end
160
+ end
161
+
162
+ state :connected do
163
+ enter do
164
+ delegate_call(:after_proxy_connected)
165
+ end
166
+ end
167
+
168
+ state :failed do
169
+ enter do
170
+ message = "Proxy server returned error code #{@reply}: #{SOCKS5_REPLY[@reply]}"
171
+ delegate.debug(:error, message)
172
+ delegate.connect_notification(false, message)
173
+ delegate.close_connection
174
+ end
175
+
176
+ terminate
177
+ end
178
+
179
+ # == Class Methods ========================================================
180
+
181
+ # == Instance Methods =====================================================
182
+
183
+ def label
184
+ 'SOCKS5'
185
+ end
186
+ end