root/trunk/lib/protocols/smtpserver.rb

Revision 788, 16.2 kB (checked in by raggi, 8 months ago)

Merge of branches/raggi
Most notable work and patches by Aman Gupta, Roger Pack, and James Tucker.
Patches / Tickets also submitted by: Jeremy Evans, aanand, darix, mmmurf,
danielaquino, macournoyer.

  • Moved docs into docs/ dir
  • Major refactor of rakefile, added generic rakefile helpers in tasks
  • Added example CPP build rakefile in tasks/cpp.rake
  • Moved rake tests out to tasks/tests.rake
  • Added svn ignores where appropriate
  • Fixed jruby build on older java platforms
  • Gem now builds from Rakefile rather than directly via extconf
  • Gem unified for jruby, C++ and pure ruby.
  • Correction for pure C++ build, removing ruby dependency
  • Fix for CYGWIN builds on ipv6
  • Major refactor for extconf.rb
  • Working mingw builds
  • extconf optionally uses pkg_config over manual configuration
  • extconf builds for 1.9 on any system that has 1.9
  • extconf no longer links pthread explicitly
  • looks for kqueue on all *nix systems
  • better error output on std::runtime_error, now says where it came from
  • Fixed some tests on jruby
  • Added test for general send_data flaw, required for a bugfix in jruby build
  • Added timeout to epoll tests
  • Added fixes for java reactor ruby api
  • Small addition of some docs in httpclient.rb and httpcli2.rb
  • Some refactor and fixes in smtpserver.rb
  • Added parenthesis where possible to avoid excess ruby warnings
  • Refactor of $eventmachine_library logic for accuracy and maintenance, jruby
  • EM::start_server now supports unix sockets
  • EM::connect now supports unix sockets
  • EM::defer @threadqueue now handled more gracefully
  • Added better messages on exceptions raised
  • Fix edge case in timer fires
  • Explicitly require buftok.rb
  • Add protocols to autoload, rather than require them all immediately
  • Fix a bug in pr_eventmachine for outbound_q
  • Refactors to take some of the use of defer out of tests.
  • Fixes in EM.defer under start/stop conditions. Reduced scope of threads.
  • 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                        
123                         return process_data_line ln if @state.include?(:data)
124                        
125                         case ln
126                         when EhloRegex
127                                 process_ehlo $'.dup
128                         when HeloRegex
129                                 process_helo $'.dup
130                         when MailFromRegex
131                                 process_mail_from $'.dup
132                         when RcptToRegex
133                                 process_rcpt_to $'.dup
134                         when DataRegex
135                                 process_data
136                         when RsetRegex
137                                 process_rset
138                         when VrfyRegex
139                                 process_vrfy
140                         when ExpnRegex
141                                 process_expn
142                         when HelpRegex
143                                 process_help
144                         when NoopRegex
145                                 process_noop
146                         when QuitRegex
147                                 process_quit
148                         when StarttlsRegex
149                                 process_starttls
150                         when AuthRegex
151                                 process_auth $'.dup
152                         else
153                                 process_unknown
154                         end
155                 end
156
157     # TODO - implement this properly, the implementation is a stub!
158     def process_vrfy
159       send_data "250 Ok, but unimplemented\r\n"
160     end
161     # TODO - implement this properly, the implementation is a stub!
162     def process_help
163       send_data "250 Ok, but unimplemented\r\n"
164     end
165     # TODO - implement this properly, the implementation is a stub!
166     def process_expn
167       send_data "250 Ok, but unimplemented\r\n"
168     end
169
170                 #--
171                 # This is called at several points to restore the protocol state
172                 # to a pre-transaction state. In essence, we "forget" having seen
173                 # any valid command except EHLO and STARTTLS.
174                 # We also have to callback user code, in case they're keeping track
175                 # of senders, recipients, and whatnot.
176                 #
177                 # We try to follow the convention of avoiding the verb "receive" for
178                 # internal method names except receive_line (which we inherit), and
179                 # using only receive_xxx for user-overridable stubs.
180                 #
181                 # init_protocol_state is called when we initialize the connection as
182                 # well as during reset_protocol_state. It does NOT call the user
183                 # override method. This enables us to promise the users that they
184                 # won't see the overridable fire except after EHLO and RSET, and
185                 # after a message has been received. Although the latter may be wrong.
186                 # The standard may allow multiple DATA segments with the same set of
187                 # senders and recipients.
188                 #
189                 def reset_protocol_state
190                         init_protocol_state
191                         s,@state = @state,[]
192                         @state << :starttls if s.include?(:starttls)
193                         @state << :ehlo if s.include?(:ehlo)
194                         receive_transaction
195                 end
196                 def init_protocol_state
197                         @state ||= []
198                 end
199
200
201                 #--
202                 # EHLO/HELO is always legal, per the standard. On success
203                 # it always clears buffers and initiates a mail "transaction."
204                 # Which means that a MAIL FROM must follow.
205                 #
206                 # Per the standard, an EHLO/HELO or a RSET "initiates" an email
207                 # transaction. Thereafter, MAIL FROM must be received before
208                 # RCPT TO, before DATA. Not sure what this specific ordering
209                 # achieves semantically, but it does make it easier to
210                 # implement. We also support user-specified requirements for
211                 # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM
212                 # without fulfilling tls and/or auth, if the user specified either
213                 # or both as required. We need to check the extension standard
214                 # for auth to see if a credential is discarded after a RSET along
215                 # with all the rest of the state. We'll behave as if it is.
216                 # Now clearly, we can't discard tls after its been negotiated
217                 # without dropping the connection, so that flag doesn't get cleared.
218                 #
219                 def process_ehlo domain
220                         if receive_ehlo_domain domain
221                                 send_data "250-#{get_server_domain}\r\n"
222                                 if @@parms[:starttls]
223                                         send_data "250-STARTTLS\r\n"
224                                 end
225                                 if @@parms[:auth]
226                                         send_data "250-AUTH PLAIN LOGIN\r\n"
227                                 end
228                                 send_data "250-NO-SOLICITING\r\n"
229                                 # TODO, size needs to be configurable.
230                                 send_data "250 SIZE 20000000\r\n"
231                                 reset_protocol_state
232                                 @state << :ehlo
233                         else
234                                 send_data "550 Requested action not taken\r\n"
235                         end
236                 end
237
238                 def process_helo domain
239                         if receive_ehlo_domain domain.dup
240                                 send_data "250 #{get_server_domain}\r\n"
241                                 reset_protocol_state
242                                 @state << :ehlo
243                         else
244                                 send_data "550 Requested action not taken\r\n"
245                         end
246                 end
247
248                 def process_quit
249                         send_data "221 Ok\r\n"
250                         close_connection_after_writing
251                 end
252
253                 def process_noop
254                         send_data "250 Ok\r\n"
255                 end
256
257                 def process_unknown
258                         send_data "500 Unknown command\r\n"
259                 end
260
261                 #--
262                 # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well.
263                 # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx
264                 # response and a continuation of the auth conversation.
265                 #
266                 def process_auth str
267                         if @state.include?(:auth)
268                                 send_data "503 auth already issued\r\n"
269                         elsif str =~ /\APLAIN\s+/i
270                                 plain = ($'.dup).unpack("m").first # Base64::decode64($'.dup)
271                                 discard,user,psw = plain.split("\000")
272                                 if receive_plain_auth user,psw
273                                         send_data "235 authentication ok\r\n"
274                                         @state << :auth
275                                 else
276                                         send_data "535 invalid authentication\r\n"
277                                 end
278                         #elsif str =~ /\ALOGIN\s+/i
279                         else
280                                 send_data "504 auth mechanism not available\r\n"
281                         end
282                 end
283
284                 #--
285                 # Unusually, we can deal with a Deferrable returned from the user application.
286                 # This was added to deal with a special case in a particular application, but
287                 # it would be a nice idea to add it to the other user-code callbacks.
288                 #
289                 def process_data
290                         unless @state.include?(:rcpt)
291                                 send_data "503 Operation sequence error\r\n"
292                         else
293                                 succeeded = proc {
294                                         send_data "354 Send it\r\n"
295                                         @state << :data
296                                         @databuffer = []
297                                 }
298                                 failed = proc {
299                                         send_data "550 Operation failed\r\n"
300                                 }
301
302                                 d = receive_data_command
303
304                                 if d.respond_to?(:callback)
305                                         d.callback(&succeeded)
306                                         d.errback(&failed)
307                                 else
308                                         (d ? succeeded : failed).call
309                                 end
310                         end
311                 end
312
313                 def process_rset
314                         reset_protocol_state
315                         receive_reset
316                         send_data "250 Ok\r\n"
317                 end
318
319                 def unbind
320                         connection_ended
321                 end
322
323                 #--
324                 # STARTTLS may not be issued before EHLO, or unless the user has chosen
325                 # to support it.
326                 # TODO, must support user-supplied certificates.
327                 #
328                 def process_starttls
329                         if @@parms[:starttls]
330                                 if @state.include?(:starttls)
331                                         send_data "503 TLS Already negotiated\r\n"
332                                 elsif ! @state.include?(:ehlo)
333                                         send_data "503 EHLO required before STARTTLS\r\n"
334                                 else
335                                         send_data "220 Start TLS negotiation\r\n"
336                                         start_tls
337                                         @state << :starttls
338                                 end
339                         else
340                                 process_unknown
341                         end
342                 end
343
344
345                 #--
346                 # Requiring TLS is touchy, cf RFC2784.
347                 # Requiring AUTH seems to be much more reasonable.
348                 # We don't currently support any notion of deriving an authentication from the TLS
349                 # negotiation, although that would certainly be reasonable.
350                 # We DON'T allow MAIL FROM to be given twice.
351                 # We DON'T enforce all the various rules for validating the sender or
352                 # the reverse-path (like whether it should be null), and notifying the reverse
353                 # path in case of delivery problems. All of that is left to the calling application.
354                 #
355                 def process_mail_from sender
356                         if (@@parms[:starttls]==:required and !@state.include?(:starttls))
357                                 send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
358                         elsif (@@parms[:auth]==:required and !@state.include?(:auth))
359                                 send_data "550 This server requires authentication before MAIL FROM\r\n"
360                         elsif @state.include?(:mail_from)
361                                 send_data "503 MAIL already given\r\n"
362                         else
363                                 unless receive_sender sender
364                                         send_data "550 sender is unacceptable\r\n"
365                                 else
366                                         send_data "250 Ok\r\n"
367                                         @state << :mail_from
368                                 end
369                         end
370                 end
371
372                 #--
373                 # Since we require :mail_from to have been seen before we process RCPT TO,
374                 # we don't need to repeat the tests for TLS and AUTH.
375                 # Note that we don't remember or do anything else with the recipients.
376                 # All of that is on the user code.
377                 # TODO: we should enforce user-definable limits on the total number of
378                 # recipients per transaction.
379                 # We might want to make sure that a given recipient is only seen once, but
380                 # for now we'll let that be the user's problem.
381                 #
382                 # User-written code can return a deferrable from receive_recipient.
383                 #
384                 def process_rcpt_to rcpt
385                         unless @state.include?(:mail_from)
386                                 send_data "503 MAIL is required before RCPT\r\n"
387                         else
388                                 succeeded = proc {
389                                         send_data "250 Ok\r\n"
390                                         @state << :rcpt unless @state.include?(:rcpt)
391                                 }
392                                 failed = proc {
393                                         send_data "550 recipient is unacceptable\r\n"
394                                 }
395
396                                 d = receive_recipient rcpt
397
398                                 if d.respond_to?(:set_deferred_status)
399                                         d.callback(&succeeded)
400                                         d.errback(&failed)
401                                 else
402                                         (d ? succeeded : failed).call
403                                 end
404
405 =begin
406                                 unless receive_recipient rcpt
407                                         send_data "550 recipient is unacceptable\r\n"
408                                 else
409                                         send_data "250 Ok\r\n"
410                                         @state << :rcpt unless @state.include?(:rcpt)
411                                 end
412 =end
413                         end
414                 end
415
416
417                 # Send the incoming data to the application one chunk at a time, rather than
418                 # one line at a time. That lets the application be a little more flexible about
419                 # storing to disk, etc.
420                 # Since we clear the chunk array every time we submit it, the caller needs to be
421                 # aware to do things like dup it if he wants to keep it around across calls.
422                 #
423                 # DON'T reset the transaction upon disposition of the incoming message.
424                 # This means another DATA command can be accepted with the same sender and recipients.
425                 # If the client wants to reset, he can call RSET.
426                 # Not sure whether the standard requires a transaction-reset at this point, but it
427                 # appears not to.
428                 #
429                 # User-written code can return a Deferrable as a response from receive_message.
430                 #
431                 def process_data_line ln
432                         if ln == "."
433                                 if @databuffer.length > 0
434                                         receive_data_chunk @databuffer
435                                         @databuffer.clear
436                                 end
437
438
439                                 succeeded = proc {
440                                         send_data "250 Message accepted\r\n"
441                                 }
442                                 failed = proc {
443                                         send_data "550 Message rejected\r\n"
444                                 }
445
446                                 d = receive_message
447
448                                 if d.respond_to?(:set_deferred_status)
449                                         d.callback(&succeeded)
450                                         d.errback(&failed)
451                                 else
452                                         (d ? succeeded : failed).call
453                                 end
454
455                                 @state.delete :data
456                         else
457                                 # slice off leading . if any
458                                 ln.slice!(0...1) if ln[0] == 46
459                                 @databuffer << ln
460                                 if @databuffer.length > @@parms[:chunksize]
461                                         receive_data_chunk @databuffer
462                                         @databuffer.clear
463                                 end
464                         end
465                 end
466
467
468                 #------------------------------------------
469                 # Everything from here on can be overridden in user code.
470
471                 # The greeting returned in the initial connection message to the client.
472                 def get_server_greeting
473                         "EventMachine SMTP Server"
474                 end
475                 # The domain name returned in the first line of the response to a
476                 # successful EHLO or HELO command.
477                 def get_server_domain
478                         "Ok EventMachine SMTP Server"
479                 end
480
481                 # A false response from this user-overridable method will cause a
482                 # 550 error to be returned to the remote client.
483                 #
484                 def receive_ehlo_domain domain
485                         true
486                 end
487
488                 # Return true or false to indicate that the authentication is acceptable.
489                 def receive_plain_auth user, password
490                         true
491                 end
492
493                 # Receives the argument of the MAIL FROM command. Return false to
494                 # indicate to the remote client that the sender is not accepted.
495                 # This can only be successfully called once per transaction.
496                 #
497                 def receive_sender sender
498                         true
499                 end
500
501                 # Receives the argument of a RCPT TO command. Can be given multiple
502                 # times per transaction. Return false to reject the recipient.
503                 #
504                 def receive_recipient rcpt
505                         true
506                 end
507
508                 # Sent when the remote peer issues the RSET command.
509                 # Since RSET is not allowed to fail (according to the protocol),
510                 # we ignore any return value from user overrides of this method.
511                 #
512                 def receive_reset
513                 end
514
515                 # Sent when the remote peer has ended the connection.
516                 #
517                 def connection_ended
518                 end
519
520                 # Called when the remote peer sends the DATA command.
521                 # Returning false will cause us to send a 550 error to the peer.
522                 # This can be useful for dealing with problems that arise from processing
523                 # the whole set of sender and recipients.
524                 #
525                 def receive_data_command
526                         true
527                 end
528
529                 # Sent when data from the remote peer is available. The size can be controlled
530                 # by setting the :chunksize parameter. This call can be made multiple times.
531                 # The goal is to strike a balance between sending the data to the application one
532                 # line at a time, and holding all of a very large message in memory.
533                 #
534                 def receive_data_chunk data
535                         @smtps_msg_size ||= 0
536                         @smtps_msg_size += data.join.length
537                         STDERR.write "<#{@smtps_msg_size}>"
538                 end
539
540                 # Sent after a message has been completely received. User code
541                 # must return true or false to indicate whether the message has
542                 # been accepted for delivery.
543                 def receive_message
544                         @@parms[:verbose] and $>.puts "Received complete message"
545                         true
546                 end
547
548                 # This is called when the protocol state is reset. It happens
549                 # when the remote client calls EHLO/HELO or RSET.
550                 def receive_transaction
551                 end
552         end
553 end
554 end
555
556
Note: See TracBrowser for help on using the browser.