eventmachine 0.8.1 → 0.9.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.
@@ -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
+