root/trunk/lib/protocols/smtpserver.rb

Revision 668, 15.8 kB (checked in by blackhedd, 1 year ago)

migrated version_0 to trunk

  • Property svn:keywords set to Id
Line 
1 # $Id$
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 = ($'.dup).unpack("m").first # 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                         receive_reset
303                         send_data "250 Ok\r\n"
304                 end
305
306                 def unbind
307                         connection_ended
308                 end
309
310                 #--
311                 # STARTTLS may not be issued before EHLO, or unless the user has chosen
312                 # to support it.
313                 # TODO, must support user-supplied certificates.
314                 #
315                 def process_starttls
316                         if @@parms[:starttls]
317                                 if @state.include?(:starttls)
318                                         send_data "503 TLS Already negotiated\r\n"
319                                 elsif ! @state.include?(:ehlo)
320                                         send_data "503 EHLO required before STARTTLS\r\n"
321                                 else
322                                         send_data "220 Start TLS negotiation\r\n"
323                                         start_tls
324                                         @state << :starttls
325                                 end
326                         else
327                                 process_unknown
328                         end
329                 end
330
331
332                 #--
333                 # Requiring TLS is touchy, cf RFC2784.
334                 # Requiring AUTH seems to be much more reasonable.
335                 # We don't currently support any notion of deriving an authentication from the TLS
336                 # negotiation, although that would certainly be reasonable.
337                 # We DON'T allow MAIL FROM to be given twice.
338                 # We DON'T enforce all the various rules for validating the sender or
339                 # the reverse-path (like whether it should be null), and notifying the reverse
340                 # path in case of delivery problems. All of that is left to the calling application.
341                 #
342                 def process_mail_from sender
343                         if (@@parms[:starttls]==:required and !@state.include?(:starttls))
344                                 send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
345                         elsif (@@parms[:auth]==:required and !@state.include?(:auth))
346                                 send_data "550 This server requires authentication before MAIL FROM\r\n"
347                         elsif @state.include?(:mail_from)
348                                 send_data "503 MAIL already given\r\n"
349                         else
350                                 unless receive_sender sender
351                                         send_data "550 sender is unacceptable\r\n"
352                                 else
353                                         send_data "250 Ok\r\n"
354                                         @state << :mail_from
355                                 end
356                         end
357                 end
358
359                 #--
360                 # Since we require :mail_from to have been seen before we process RCPT TO,
361                 # we don't need to repeat the tests for TLS and AUTH.
362                 # Note that we don't remember or do anything else with the recipients.
363                 # All of that is on the user code.
364                 # TODO: we should enforce user-definable limits on the total number of
365                 # recipients per transaction.
366                 # We might want to make sure that a given recipient is only seen once, but
367                 # for now we'll let that be the user's problem.
368                 #
369                 # User-written code can return a deferrable from receive_recipient.
370                 #
371                 def process_rcpt_to rcpt
372                         unless @state.include?(:mail_from)
373                                 send_data "503 MAIL is required before RCPT\r\n"
374                         else
375                                 succeeded = proc {
376                                         send_data "250 Ok\r\n"
377                                         @state << :rcpt unless @state.include?(:rcpt)
378                                 }
379                                 failed = proc {
380                                         send_data "550 recipient is unacceptable\r\n"
381                                 }
382
383                                 d = receive_recipient rcpt
384
385                                 if d.respond_to?(:set_deferred_status)
386                                         d.callback &succeeded
387                                         d.errback &failed
388                                 else
389                                         (d ? succeeded : failed).call
390                                 end
391
392 =begin
393                                 unless receive_recipient rcpt
394                                         send_data "550 recipient is unacceptable\r\n"
395                                 else
396                                         send_data "250 Ok\r\n"
397                                         @state << :rcpt unless @state.include?(:rcpt)
398                                 end
399 =end
400                         end
401                 end
402
403
404                 # Send the incoming data to the application one chunk at a time, rather than
405                 # one line at a time. That lets the application be a little more flexible about
406                 # storing to disk, etc.
407                 # Since we clear the chunk array every time we submit it, the caller needs to be
408                 # aware to do things like dup it if he wants to keep it around across calls.
409                 #
410                 # DON'T reset the transaction upon disposition of the incoming message.
411                 # This means another DATA command can be accepted with the same sender and recipients.
412                 # If the client wants to reset, he can call RSET.
413                 # Not sure whether the standard requires a transaction-reset at this point, but it
414                 # appears not to.
415                 #
416                 # User-written code can return a Deferrable as a response from receive_message.
417                 #
418                 def process_data_line ln
419                         if ln == "."
420                                 if @databuffer.length > 0
421                                         receive_data_chunk @databuffer
422                                         @databuffer.clear
423                                 end
424
425
426                                 succeeded = proc {
427                                         send_data "250 Message accepted\r\n"
428                                 }
429                                 failed = proc {
430                                         send_data "550 Message rejected\r\n"
431                                 }
432
433                                 d = receive_message
434
435                                 if d.respond_to?(:set_deferred_status)
436                                         d.callback &succeeded
437                                         d.errback &failed
438                                 else
439                                         (d ? succeeded : failed).call
440                                 end
441
442                                 @state.delete :data
443                         else
444                                 # slice off leading . if any
445                                 ln.slice!(0...1) if ln[0] == 46
446                                 @databuffer << ln
447                                 if @databuffer.length > @@parms[:chunksize]
448                                         receive_data_chunk @databuffer
449                                         @databuffer.clear
450                                 end
451                         end
452                 end
453
454
455                 #------------------------------------------
456                 # Everything from here on can be overridden in user code.
457
458                 # The greeting returned in the initial connection message to the client.
459                 def get_server_greeting
460                         "EventMachine SMTP Server"
461                 end
462                 # The domain name returned in the first line of the response to a
463                 # successful EHLO or HELO command.
464                 def get_server_domain
465                         "Ok EventMachine SMTP Server"
466                 end
467
468                 # A false response from this user-overridable method will cause a
469                 # 550 error to be returned to the remote client.
470                 #
471                 def receive_ehlo_domain domain
472                         true
473                 end
474
475                 # Return true or false to indicate that the authentication is acceptable.
476                 def receive_plain_auth user, password
477                         true
478                 end
479
480                 # Receives the argument of the MAIL FROM command. Return false to
481                 # indicate to the remote client that the sender is not accepted.
482                 # This can only be successfully called once per transaction.
483                 #
484                 def receive_sender sender
485                         true
486                 end
487
488                 # Receives the argument of a RCPT TO command. Can be given multiple
489                 # times per transaction. Return false to reject the recipient.
490                 #
491                 def receive_recipient rcpt
492                         true
493                 end
494
495                 # Sent when the remote peer issues the RSET command.
496                 # Since RSET is not allowed to fail (according to the protocol),
497                 # we ignore any return value from user overrides of this method.
498                 #
499                 def receive_reset
500                 end
501
502                 # Sent when the remote peer has ended the connection.
503                 #
504                 def connection_ended
505                 end
506
507                 # Called when the remote peer sends the DATA command.
508                 # Returning false will cause us to send a 550 error to the peer.
509                 # This can be useful for dealing with problems that arise from processing
510                 # the whole set of sender and recipients.
511                 #
512                 def receive_data_command
513                         true
514                 end
515
516                 # Sent when data from the remote peer is available. The size can be controlled
517                 # by setting the :chunksize parameter. This call can be made multiple times.
518                 # The goal is to strike a balance between sending the data to the application one
519                 # line at a time, and holding all of a very large message in memory.
520                 #
521                 def receive_data_chunk data
522                         @smtps_msg_size ||= 0
523                         @smtps_msg_size += data.join.length
524                         STDERR.write "<#{@smtps_msg_size}>"
525                 end
526
527                 # Sent after a message has been completely received. User code
528                 # must return true or false to indicate whether the message has
529                 # been accepted for delivery.
530                 def receive_message
531                         @@parms[:verbose] and $>.puts "Received complete message"
532                         true
533                 end
534
535                 # This is called when the protocol state is reset. It happens
536                 # when the remote client calls EHLO/HELO or RSET.
537                 def receive_transaction
538                 end
539         end
540 end
541 end
542
543
Note: See TracBrowser for help on using the browser.