eventmachine 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- # $Id: eventmachine_version.rb 482 2007-07-28 19:11:03Z blackhedd $
1
+ # $Id: eventmachine_version.rb 536 2007-09-16 23:55:03Z blackhedd $
2
2
  #
3
3
  # Author:: Francis Cianfrocca (gmail: blackhedd)
4
4
  # Homepage:: http://rubyeventmachine.com
@@ -25,7 +25,7 @@
25
25
 
26
26
  module EventMachine
27
27
 
28
- VERSION = "0.8.1"
28
+ VERSION = "0.9.0"
29
29
 
30
30
  end
31
31
 
@@ -1,4 +1,4 @@
1
- # $Id: httpclient.rb 398 2007-07-11 02:46:22Z blackhedd $
1
+ # $Id: httpclient.rb 518 2007-08-30 10:17:02Z blackhedd $
2
2
  #
3
3
  # Author:: Francis Cianfrocca (gmail: blackhedd)
4
4
  # Homepage:: http://rubyeventmachine.com
@@ -132,6 +132,12 @@ class HttpClient < Connection
132
132
  req << "Content-length: #{postcontent.length}"
133
133
  end
134
134
 
135
+ # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
136
+ # Eventually we will want to deal intelligently with arrays and hashes.
137
+ if args[:cookie]
138
+ req << "Cookie: #{args[:cookie]}"
139
+ end
140
+
135
141
  req << ""
136
142
  reqstring = req.map {|l| "#{l}\r\n"}.join
137
143
  send_data reqstring
@@ -0,0 +1,276 @@
1
+ # $Id: smtpclient.rb 535 2007-09-16 19:35:05Z blackhedd $
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 16 July 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+
26
+
27
+ require 'base64'
28
+
29
+ module EventMachine
30
+ module Protocols
31
+
32
+
33
+ class SmtpClient < Connection
34
+ include EventMachine::Deferrable
35
+ include EventMachine::Protocols::LineText2
36
+
37
+ # This is the external entry point.
38
+ #
39
+ # The argument is a hash containing these values:
40
+ # :host => a string containing the IP address or host name of the SMTP server to connect to.
41
+ # :port => optional, defaults to 25.
42
+ # :domain => required String. This is passed as the argument to the EHLO command.
43
+ # :starttls => optional. If it evaluates true, then the client will initiate STARTTLS with
44
+ # the server, and abort the connection if the negotiation doesn't succeed.
45
+ # TODO, need to be able to pass certificate parameters with this option.
46
+ # :auth => optional hash of auth parameters. If not given, then no auth will be attempted.
47
+ # (In that case, the connection will be aborted if the server requires auth.)
48
+ # Specify the hash value :type to determine the auth type, along with additional parameters
49
+ # depending on the type.
50
+ # Currently only :type => :plain is supported. Pass additional parameters :username (String),
51
+ # and :password (either a String or a Proc that will be called at auth-time).
52
+ # Example: :auth => {:type=>:plain, :username=>"mickey@disney.com", :password=>"mouse"}
53
+ # :from => required String. Specifies the sender of the message. Will be passed as the argument
54
+ # to the MAIL FROM. Do NOT enclose the argument in angle-bracket (<>) characters.
55
+ # The connection will abort if the server rejects the value.
56
+ # :to => required String or Array of Strings. The recipient(s) of the message. Do NOT enclose
57
+ # any of the values in angle-brackets (<>) characters. It's NOT a fatal error if one or more
58
+ # recipients are rejected by the server. (Of course, if ALL of them are, the server will most
59
+ # likely trigger an error when we try to send data.) An array of codes containing the status
60
+ # of each requested recipient is available after the call completes. TODO, we should define
61
+ # an overridable stub that will be called on rejection of a recipient or a sender, giving
62
+ # user code the chance to try again or abort the connection.
63
+ # :header => Required hash of values to be transmitted in the header of the message. The hash
64
+ # keys are the names of the headers (do NOT append a trailing colon), and the values are strings
65
+ # containing the header values. TODO, support Arrays of header values, which would cause us to
66
+ # send that specific header line more than once.
67
+ # Example: :header => {"Subject" => "Bogus", "CC" => "myboss@example.com"}
68
+ # :body => Optional string, defaults blank. This will be passed as the body of the email message.
69
+ # TODO, this needs to be significantly beefed up. As currently written, this requires the caller
70
+ # to properly format the input into CRLF-delimited lines of 7-bit characters in the standard
71
+ # SMTP transmission format. We need to be able to automatically convert binary data, and add
72
+ # correct line-breaks to text data. I think the :body parameter should remain as it is, and we
73
+ # should add a :content parameter that contains autoconversions and/or conversion parameters.
74
+ # Then we can check if either :body or :content is present and do the right thing.
75
+ # :verbose => Optional. If true, will cause a lot of information (including the server-side of the
76
+ # conversation) to be dumped to $>.
77
+ #
78
+ def self.send args={}
79
+ args[:port] ||= 25
80
+ args[:body] ||= ""
81
+ begin
82
+ EventMachine.connect( args[:host], args[:port], self) {|c|
83
+ # According to the EM docs, we will get here AFTER post_init is called.
84
+ c.args = args
85
+ c.set_comm_inactivity_timeout 60
86
+ }
87
+ rescue
88
+ # We'll get here on a connect error. This code mimics the effect
89
+ # of a call to invoke_internal_error. Would be great to DRY this up.
90
+ # (Actually, it may be that we never get here, if EM#connect catches
91
+ # its errors internally.)
92
+ d = EM::DefaultDeferrable.new
93
+ d.set_deferred_status(:failed, {:error=>[:connect, 500, "unable to connect to server"]})
94
+ d
95
+ end
96
+ end
97
+
98
+ attr_writer :args
99
+
100
+ def post_init
101
+ @return_values = {}
102
+ @return_values[:start_time] = Time.now
103
+ end
104
+
105
+ def connection_completed
106
+ @responder = :receive_signon
107
+ @msg = []
108
+ end
109
+
110
+ # We can get here in a variety of ways, all of them being failures unless
111
+ # the @succeeded flag is set. If a protocol success was recorded, then don't
112
+ # set a deferred success because the caller will already have done it
113
+ # (no need to wait until the connection closes to invoke the callbacks).
114
+ #
115
+ def unbind
116
+ unless @succeeded
117
+ @return_values[:elapsed_time] = Time.now - @return_values[:start_time]
118
+ @return_values[:error] = [@responder, @code, @msg]
119
+ set_deferred_status(:failed, @return_values)
120
+ end
121
+ end
122
+
123
+ def receive_line ln
124
+ $>.puts ln if @args[:verbose]
125
+ @range = ln[0...1].to_i
126
+ @code = ln[0...3].to_i
127
+ @msg << ln[4..-1]
128
+ unless ln[3...4] == '-'
129
+ $>.puts @responder if @args[:verbose]
130
+ send @responder
131
+ @msg.clear
132
+ end
133
+ end
134
+
135
+ # We encountered an error from the server and will close the connection.
136
+ # Use the error and message the server returned.
137
+ #
138
+ def invoke_error
139
+ set_deferred_status :failed, @responder, @code, @msg
140
+ send_data "QUIT\r\n"
141
+ close_connection_after_writing
142
+ end
143
+
144
+ # We encountered an error on our side of the protocol and will close the connection.
145
+ # Use an extra-protocol error code (900) and use the message from the caller.
146
+ #
147
+ def invoke_internal_error msg = "???"
148
+ set_deferred_status :failed, @responder, 900, msg
149
+ send_data "QUIT\r\n"
150
+ close_connection_after_writing
151
+ end
152
+
153
+ def receive_signon
154
+ return invoke_error unless @range == 2
155
+ send_data "EHLO #{@args[:domain]}\r\n"
156
+ @responder = :receive_ehlo_response
157
+ end
158
+
159
+ def receive_ehlo_response
160
+ return invoke_error unless @range == 2
161
+ @server_caps = @msg
162
+ invoke_starttls
163
+ end
164
+
165
+ def invoke_starttls
166
+ if @args[:starttls]
167
+ # It would be more sociable to first ask if @server_caps contains
168
+ # the string "STARTTLS" before we invoke it, but hey, life's too short.
169
+ send_data "STARTTLS\r\n"
170
+ @responder = :receive_starttls_response
171
+ else
172
+ invoke_auth
173
+ end
174
+ end
175
+ def receive_starttls_response
176
+ return invoke_error unless @range == 2
177
+ start_tls
178
+ invoke_auth
179
+ end
180
+
181
+
182
+
183
+ # Perform an authentication. If the caller didn't request one, then fall through
184
+ # to the mail-from state.
185
+ def invoke_auth
186
+ if @args[:auth]
187
+ if @args[:auth][:type] == :plain
188
+ psw = @args[:auth][:password]
189
+ if psw.respond_to?(:call)
190
+ psw = psw.call
191
+ end
192
+ str = Base64::encode64("\0#{@args[:auth][:username]}\0#{psw}").chomp
193
+ send_data "AUTH PLAIN #{str}\r\n"
194
+ @responder = :receive_auth_response
195
+ else
196
+ return invoke_internal_error("unsupported auth type")
197
+ end
198
+ else
199
+ invoke_mail_from
200
+ end
201
+ end
202
+ def receive_auth_response
203
+ return invoke_error unless @range == 2
204
+ invoke_mail_from
205
+ end
206
+
207
+ def invoke_mail_from
208
+ send_data "MAIL FROM: <#{@args[:from]}>\r\n"
209
+ @responder = :receive_mail_from_response
210
+ end
211
+ def receive_mail_from_response
212
+ return invoke_error unless @range == 2
213
+ invoke_rcpt_to
214
+ end
215
+
216
+ def invoke_rcpt_to
217
+ @rcpt_responses ||= []
218
+ l = @rcpt_responses.length
219
+ to = @args[:to].is_a?(Array) ? @args[:to] : [@args[:to].to_s]
220
+ if l < to.length
221
+ send_data "RCPT TO: <#{to[l]}>\r\n"
222
+ @responder = :receive_rcpt_to_response
223
+ else
224
+ invoke_data
225
+ end
226
+ end
227
+ def receive_rcpt_to_response
228
+ @rcpt_responses << [@code, @msg]
229
+ invoke_rcpt_to
230
+ end
231
+
232
+ def invoke_data
233
+ send_data "DATA\r\n"
234
+ @responder = :receive_data_response
235
+ end
236
+ def receive_data_response
237
+ return invoke_error unless @range == 3
238
+
239
+ # The data to send can be given either in @args[:content] (an array or string of raw data
240
+ # which MUST be in correct SMTP body format, including a trailing dot line), or a header and
241
+ # body given in @args[:header] and @args[:body].
242
+ #
243
+ if @args[:content]
244
+ send_data @args[:content].to_s
245
+ else
246
+ # The header can be a hash or an array.
247
+ if @args[:header].is_a?(Hash)
248
+ (@args[:header] || {}).each {|k,v| send_data "#{k}: #{v}\r\n" }
249
+ else
250
+ send_data @args[:header].to_s
251
+ end
252
+ send_data "\r\n"
253
+
254
+ if @args[:body].is_a?(Array)
255
+ @args[:body].each {|e| send_data e}
256
+ else
257
+ send_data @args[:body].to_s
258
+ end
259
+
260
+ send_data "\r\n.\r\n"
261
+ end
262
+
263
+ @responder = :receive_message_response
264
+ end
265
+ def receive_message_response
266
+ return invoke_error unless @range == 2
267
+ send_data "QUIT\r\n"
268
+ close_connection_after_writing
269
+ @succeeded = true
270
+ @return_values[:elapsed_time] = Time.now - @return_values[:start_time]
271
+ set_deferred_status :succeeded, @return_values
272
+ end
273
+ end
274
+ end
275
+ end
276
+
@@ -0,0 +1,514 @@
1
+ # $Id: smtpserver.rb 532 2007-09-13 21:44:23Z blackhedd $
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 16 July 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+
26
+
27
+ require 'base64'
28
+
29
+ module EventMachine
30
+ module Protocols
31
+
32
+
33
+ =begin
34
+ This is a protocol handler for the server side of SMTP.
35
+ It's NOT a complete SMTP server obeying all the semantics of servers conforming to
36
+ RFC2821. Rather, it uses overridable method stubs to communicate protocol states
37
+ and data to user code. User code is responsible for doing the right things with the
38
+ data in order to get complete and correct SMTP server behavior.
39
+
40
+ Useful paragraphs in RFC-2821:
41
+ 4.3.2: Concise list of command-reply sequences, in essence a text representation
42
+ of the command state-machine.
43
+
44
+ STARTTLS is defined in RFC2487.
45
+ Observe that there are important rules governing whether a publicly-referenced server
46
+ (meaning one whose Internet address appears in public MX records) may require the
47
+ non-optional use of TLS.
48
+ Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS.
49
+
50
+ =end
51
+
52
+ class SmtpServer < EventMachine::Connection
53
+ include Protocols::LineText2
54
+
55
+ HeloRegex = /\AHELO\s*/i
56
+ EhloRegex = /\AEHLO\s*/i
57
+ QuitRegex = /\AQUIT/i
58
+ MailFromRegex = /\AMAIL FROM:\s*/i
59
+ RcptToRegex = /\ARCPT TO:\s*/i
60
+ DataRegex = /\ADATA/i
61
+ NoopRegex = /\ANOOP/i
62
+ RsetRegex = /\ARSET/i
63
+ VrfyRegex = /\AVRFY\s+/i
64
+ ExpnRegex = /\AEXPN\s+/i
65
+ HelpRegex = /\AHELP/i
66
+ StarttlsRegex = /\ASTARTTLS/i
67
+ AuthRegex = /\AAUTH\s+/i
68
+
69
+
70
+ # Class variable containing default parameters that can be overridden
71
+ # in application code.
72
+ # Individual objects of this class will make an instance-local copy of
73
+ # the class variable, so that they can be reconfigured on a per-instance
74
+ # basis.
75
+ #
76
+ # Chunksize is the number of data lines we'll buffer before
77
+ # sending them to the application. TODO, make this user-configurable.
78
+ #
79
+ @@parms = {
80
+ :chunksize => 4000,
81
+ :verbose => false
82
+ }
83
+ def self.parms= parms={}
84
+ @@parms.merge!(parms)
85
+ end
86
+
87
+
88
+
89
+ def initialize *args
90
+ super
91
+ @parms = @@parms
92
+ init_protocol_state
93
+ end
94
+
95
+ def parms= parms={}
96
+ @parms.merge!(parms)
97
+ end
98
+
99
+ # In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM,
100
+ # #post_init will execute BEFORE the block passed to #start_server, for any
101
+ # given accepted connection. Since in this class we'll probably be getting
102
+ # a lot of initialization parameters, we want the guts of post_init to
103
+ # run AFTER the application has initialized the connection object. So we
104
+ # use a spawn to schedule the post_init to run later.
105
+ # It's a little weird, I admit. A reasonable alternative would be to set
106
+ # parameters as a class variable and to do that before accepting any connections.
107
+ #
108
+ # OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration.
109
+ #
110
+ def post_init
111
+ #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL)
112
+ #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self)
113
+ (EM.spawn {|x| x.send_server_greeting}).notify(self)
114
+ end
115
+
116
+ def send_server_greeting
117
+ send_data "220 #{get_server_greeting}\r\n"
118
+ end
119
+
120
+ def receive_line ln
121
+ @@parms[:verbose] and $>.puts ">>> #{ln}"
122
+ if @state.include?(:data)
123
+ process_data_line ln
124
+ elsif ln =~ EhloRegex
125
+ process_ehlo $'.dup
126
+ elsif ln =~ HeloRegex
127
+ process_helo $'.dup
128
+ elsif ln =~ MailFromRegex
129
+ process_mail_from $'.dup
130
+ elsif ln =~ RcptToRegex
131
+ process_rcpt_to $'.dup
132
+ elsif ln =~ DataRegex
133
+ process_data
134
+ elsif ln =~ RsetRegex
135
+ process_rset
136
+ elsif ln =~ VrfyRegex
137
+ process_vrfy
138
+ elsif ln =~ ExpnRegex
139
+ process_expn
140
+ elsif ln =~ HelpRegex
141
+ process_help
142
+ elsif ln =~ NoopRegex
143
+ process_noop
144
+ elsif ln =~ QuitRegex
145
+ process_quit
146
+ elsif ln =~ StarttlsRegex
147
+ process_starttls
148
+ elsif ln =~ AuthRegex
149
+ process_auth $'.dup
150
+ else
151
+ process_unknown
152
+ end
153
+ end
154
+
155
+
156
+
157
+ #--
158
+ # This is called at several points to restore the protocol state
159
+ # to a pre-transaction state. In essence, we "forget" having seen
160
+ # any valid command except EHLO and STARTTLS.
161
+ # We also have to callback user code, in case they're keeping track
162
+ # of senders, recipients, and whatnot.
163
+ #
164
+ # We try to follow the convention of avoiding the verb "receive" for
165
+ # internal method names except receive_line (which we inherit), and
166
+ # using only receive_xxx for user-overridable stubs.
167
+ #
168
+ # init_protocol_state is called when we initialize the connection as
169
+ # well as during reset_protocol_state. It does NOT call the user
170
+ # override method. This enables us to promise the users that they
171
+ # won't see the overridable fire except after EHLO and RSET, and
172
+ # after a message has been received. Although the latter may be wrong.
173
+ # The standard may allow multiple DATA segments with the same set of
174
+ # senders and recipients.
175
+ #
176
+ def reset_protocol_state
177
+ init_protocol_state
178
+ s,@state = @state,[]
179
+ @state << :starttls if s.include?(:starttls)
180
+ @state << :ehlo if s.include?(:ehlo)
181
+ receive_transaction
182
+ end
183
+ def init_protocol_state
184
+ @state ||= []
185
+ end
186
+
187
+
188
+ #--
189
+ # EHLO/HELO is always legal, per the standard. On success
190
+ # it always clears buffers and initiates a mail "transaction."
191
+ # Which means that a MAIL FROM must follow.
192
+ #
193
+ # Per the standard, an EHLO/HELO or a RSET "initiates" an email
194
+ # transaction. Thereafter, MAIL FROM must be received before
195
+ # RCPT TO, before DATA. Not sure what this specific ordering
196
+ # achieves semantically, but it does make it easier to
197
+ # implement. We also support user-specified requirements for
198
+ # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM
199
+ # without fulfilling tls and/or auth, if the user specified either
200
+ # or both as required. We need to check the extension standard
201
+ # for auth to see if a credential is discarded after a RSET along
202
+ # with all the rest of the state. We'll behave as if it is.
203
+ # Now clearly, we can't discard tls after its been negotiated
204
+ # without dropping the connection, so that flag doesn't get cleared.
205
+ #
206
+ def process_ehlo domain
207
+ if receive_ehlo_domain domain
208
+ send_data "250-#{get_server_domain}\r\n"
209
+ if @@parms[:starttls]
210
+ send_data "250-STARTTLS\r\n"
211
+ end
212
+ if @@parms[:auth]
213
+ send_data "250-AUTH PLAIN LOGIN\r\n"
214
+ end
215
+ send_data "250-NO-SOLICITING\r\n"
216
+ # TODO, size needs to be configurable.
217
+ send_data "250 SIZE 20000000\r\n"
218
+ reset_protocol_state
219
+ @state << :ehlo
220
+ else
221
+ send_data "550 Requested action not taken\r\n"
222
+ end
223
+ end
224
+
225
+ def process_helo domain
226
+ if receive_ehlo_domain domain.dup
227
+ send_data "250 #{get_server_domain}\r\n"
228
+ reset_protocol_state
229
+ @state << :ehlo
230
+ else
231
+ send_data "550 Requested action not taken\r\n"
232
+ end
233
+ end
234
+
235
+ def process_quit
236
+ send_data "221 Ok\r\n"
237
+ close_connection_after_writing
238
+ end
239
+
240
+ def process_noop
241
+ send_data "250 Ok\r\n"
242
+ end
243
+
244
+ def process_unknown
245
+ send_data "500 Unknown command\r\n"
246
+ end
247
+
248
+ #--
249
+ # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well.
250
+ # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx
251
+ # response and a continuation of the auth conversation.
252
+ #
253
+ def process_auth str
254
+ if @state.include?(:auth)
255
+ send_data "503 auth already issued\r\n"
256
+ elsif str =~ /\APLAIN\s+/i
257
+ plain = Base64::decode64($'.dup)
258
+ discard,user,psw = plain.split("\000")
259
+ if receive_plain_auth user,psw
260
+ send_data "235 authentication ok\r\n"
261
+ @state << :auth
262
+ else
263
+ send_data "535 invalid authentication\r\n"
264
+ end
265
+ #elsif str =~ /\ALOGIN\s+/i
266
+ else
267
+ send_data "504 auth mechanism not available\r\n"
268
+ end
269
+ end
270
+
271
+ #--
272
+ # Unusually, we can deal with a Deferrable returned from the user application.
273
+ # This was added to deal with a special case in a particular application, but
274
+ # it would be a nice idea to add it to the other user-code callbacks.
275
+ #
276
+ def process_data
277
+ unless @state.include?(:rcpt)
278
+ send_data "503 Operation sequence error\r\n"
279
+ else
280
+ succeeded = proc {
281
+ send_data "354 Send it\r\n"
282
+ @state << :data
283
+ @databuffer = []
284
+ }
285
+ failed = proc {
286
+ send_data "550 Operation failed\r\n"
287
+ }
288
+
289
+ d = receive_data_command
290
+
291
+ if d.respond_to?(:callback)
292
+ d.callback &succeeded
293
+ d.errback &failed
294
+ else
295
+ (d ? succeeded : failed).call
296
+ end
297
+ end
298
+ end
299
+
300
+ def process_rset
301
+ reset_protocol_state
302
+ send_data "250 Ok\r\n"
303
+ end
304
+
305
+ def unbind
306
+ connection_ended
307
+ end
308
+
309
+ #--
310
+ # STARTTLS may not be issued before EHLO, or unless the user has chosen
311
+ # to support it.
312
+ # TODO, must support user-supplied certificates.
313
+ #
314
+ def process_starttls
315
+ if @@parms[:starttls]
316
+ if @state.include?(:starttls)
317
+ send_data "503 TLS Already negotiated\r\n"
318
+ elsif ! @state.include?(:ehlo)
319
+ send_data "503 EHLO required before STARTTLS\r\n"
320
+ else
321
+ send_data "220 Start TLS negotiation\r\n"
322
+ start_tls
323
+ @state << :starttls
324
+ end
325
+ else
326
+ process_unknown
327
+ end
328
+ end
329
+
330
+
331
+ #--
332
+ # Requiring TLS is touchy, cf RFC2784.
333
+ # Requiring AUTH seems to be much more reasonable.
334
+ # We don't currently support any notion of deriving an authentication from the TLS
335
+ # negotiation, although that would certainly be reasonable.
336
+ # We DON'T allow MAIL FROM to be given twice.
337
+ # We DON'T enforce all the various rules for validating the sender or
338
+ # the reverse-path (like whether it should be null), and notifying the reverse
339
+ # path in case of delivery problems. All of that is left to the calling application.
340
+ #
341
+ def process_mail_from sender
342
+ if (@@parms[:starttls]==:required and !@state.include?(:starttls))
343
+ send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
344
+ elsif (@@parms[:auth]==:required and !@state.include?(:auth))
345
+ send_data "550 This server requires authentication before MAIL FROM\r\n"
346
+ elsif @state.include?(:mail_from)
347
+ send_data "503 MAIL already given\r\n"
348
+ else
349
+ unless receive_sender sender
350
+ send_data "550 sender is unacceptable\r\n"
351
+ else
352
+ send_data "250 Ok\r\n"
353
+ @state << :mail_from
354
+ end
355
+ end
356
+ end
357
+
358
+ #--
359
+ # Since we require :mail_from to have been seen before we process RCPT TO,
360
+ # we don't need to repeat the tests for TLS and AUTH.
361
+ # Note that we don't remember or do anything else with the recipients.
362
+ # All of that is on the user code.
363
+ # TODO: we should enforce user-definable limits on the total number of
364
+ # recipients per transaction.
365
+ # We might want to make sure that a given recipient is only seen once, but
366
+ # for now we'll let that be the user's problem.
367
+ #
368
+ def process_rcpt_to rcpt
369
+ unless @state.include?(:mail_from)
370
+ send_data "503 MAIL is required before RCPT\r\n"
371
+ else
372
+ unless receive_recipient rcpt
373
+ send_data "550 recipient is unacceptable\r\n"
374
+ else
375
+ send_data "250 Ok\r\n"
376
+ @state << :rcpt unless @state.include?(:rcpt)
377
+ end
378
+ end
379
+ end
380
+
381
+
382
+ # Send the incoming data to the application one chunk at a time, rather than
383
+ # one line at a time. That lets the application be a little more flexible about
384
+ # storing to disk, etc.
385
+ # Since we clear the chunk array every time we submit it, the caller needs to be
386
+ # aware to do things like dup it if he wants to keep it around across calls.
387
+ #
388
+ # DON'T reset the transaction upon disposition of the incoming message.
389
+ # This means another DATA command can be accepted with the same sender and recipients.
390
+ # If the client wants to reset, he can call RSET.
391
+ # Not sure whether the standard requires a transaction-reset at this point, but it
392
+ # appears not to.
393
+ #
394
+ # User-written code can return a Deferrable as a response from receive_message.
395
+ #
396
+ def process_data_line ln
397
+ if ln == "."
398
+ if @databuffer.length > 0
399
+ receive_data_chunk @databuffer
400
+ @databuffer.clear
401
+ end
402
+
403
+
404
+ succeeded = proc {
405
+ send_data "250 Message accepted\r\n"
406
+ }
407
+ failed = proc {
408
+ send_data "550 Message rejected\r\n"
409
+ }
410
+
411
+ d = receive_message
412
+
413
+ if d.respond_to?(:set_deferred_status)
414
+ d.callback &succeeded
415
+ d.errback &failed
416
+ else
417
+ (d ? succeeded : failed).call
418
+ end
419
+
420
+ @state.delete :data
421
+ else
422
+ # slice off leading . if any
423
+ ln.slice!(0...1) if ln[0] == 46
424
+ @databuffer << ln
425
+ if @databuffer.length > @@parms[:chunksize]
426
+ receive_data_chunk @databuffer
427
+ @databuffer.clear
428
+ end
429
+ end
430
+ end
431
+
432
+
433
+ #------------------------------------------
434
+ # Everything from here on can be overridden in user code.
435
+
436
+ # The greeting returned in the initial connection message to the client.
437
+ def get_server_greeting
438
+ "EventMachine SMTP Server"
439
+ end
440
+ # The domain name returned in the first line of the response to a
441
+ # successful EHLO or HELO command.
442
+ def get_server_domain
443
+ "Ok EventMachine SMTP Server"
444
+ end
445
+
446
+ # A false response from this user-overridable method will cause a
447
+ # 550 error to be returned to the remote client.
448
+ #
449
+ def receive_ehlo_domain domain
450
+ true
451
+ end
452
+
453
+ # Return true or false to indicate that the authentication is acceptable.
454
+ def receive_plain_auth user, password
455
+ true
456
+ end
457
+
458
+ # Receives the argument of the MAIL FROM command. Return false to
459
+ # indicate to the remote client that the sender is not accepted.
460
+ # This can only be successfully called once per transaction.
461
+ #
462
+ def receive_sender sender
463
+ true
464
+ end
465
+
466
+ # Receives the argument of a RCPT TO command. Can be given multiple
467
+ # times per transaction. Return false to reject the recipient.
468
+ #
469
+ def receive_recipient rcpt
470
+ true
471
+ end
472
+
473
+ # Sent when the remote peer has ended the connection.
474
+ #
475
+ def connection_ended
476
+ end
477
+
478
+ # Called when the remote peer sends the DATA command.
479
+ # Returning false will cause us to send a 550 error to the peer.
480
+ # This can be useful for dealing with problems that arise from processing
481
+ # the whole set of sender and recipients.
482
+ #
483
+ def receive_data_command
484
+ true
485
+ end
486
+
487
+ # Sent when data from the remote peer is available. The size can be controlled
488
+ # by setting the :chunksize parameter. This call can be made multiple times.
489
+ # The goal is to strike a balance between sending the data to the application one
490
+ # line at a time, and holding all of a very large message in memory.
491
+ #
492
+ def receive_data_chunk data
493
+ @smtps_msg_size ||= 0
494
+ @smtps_msg_size += data.join.length
495
+ STDERR.write "<#{@smtps_msg_size}>"
496
+ end
497
+
498
+ # Sent after a message has been completely received. User code
499
+ # must return true or false to indicate whether the message has
500
+ # been accepted for delivery.
501
+ def receive_message
502
+ @@parms[:verbose] and $>.puts "Received complete message"
503
+ true
504
+ end
505
+
506
+ # This is called when the protocol state is reset. It happens
507
+ # when the remote client calls EHLO/HELO or RSET.
508
+ def receive_transaction
509
+ end
510
+ end
511
+ end
512
+ end
513
+
514
+