Included Modules

EventMachine::Protocols::SmtpServer

This is a protocol handler for the server side of SMTP. It’s NOT a complete SMTP server obeying all the semantics of servers conforming to RFC2821. Rather, it uses overridable method stubs to communicate protocol states and data to user code. User code is responsible for doing the right things with the data in order to get complete and correct SMTP server behavior.

Constants

HeloRegex
EhloRegex
QuitRegex
MailFromRegex
RcptToRegex
DataRegex
NoopRegex
RsetRegex
VrfyRegex
ExpnRegex
HelpRegex
StarttlsRegex
AuthRegex

Public Class Methods

new(*args) click to toggle source
    # File lib/em/protocols/smtpserver.rb, line 82
82:       def initialize *args
83:         super
84:         @parms = @@parms
85:         init_protocol_state
86:       end
parms=(parms={}) click to toggle source
    # File lib/em/protocols/smtpserver.rb, line 76
76:       def self.parms= parms={}
77:         @@parms.merge!(parms)
78:       end

Public Instance Methods

connection_ended() click to toggle source

Sent when the remote peer has ended the connection.

     # File lib/em/protocols/smtpserver.rb, line 510
510:       def connection_ended
511:       end
get_server_domain() click to toggle source

The domain name returned in the first line of the response to a successful EHLO or HELO command.

     # File lib/em/protocols/smtpserver.rb, line 470
470:       def get_server_domain
471:         "Ok EventMachine SMTP Server"
472:       end
get_server_greeting() click to toggle source

The greeting returned in the initial connection message to the client.

     # File lib/em/protocols/smtpserver.rb, line 465
465:       def get_server_greeting
466:         "EventMachine SMTP Server"
467:       end
init_protocol_state() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 189
189:       def init_protocol_state
190:         @state ||= []
191:       end
parms=(parms={}) click to toggle source
    # File lib/em/protocols/smtpserver.rb, line 88
88:       def parms= parms={}
89:         @parms.merge!(parms)
90:       end
post_init() click to toggle source

In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM, # will execute BEFORE the block passed to #, for any given accepted connection. Since in this class we’ll probably be getting a lot of initialization parameters, we want the guts of post_init to run AFTER the application has initialized the connection object. So we use a spawn to schedule the post_init to run later. It’s a little weird, I admit. A reasonable alternative would be to set parameters as a class variable and to do that before accepting any connections.

OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration.

     # File lib/em/protocols/smtpserver.rb, line 103
103:       def post_init
104:         #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL)
105:         #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self)
106:         (EM.spawn {|x| x.send_server_greeting}).notify(self)
107:       end
process_auth(str) click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 259
259:       def process_auth str
260:         if @state.include?(:auth)
261:           send_data "503 auth already issued\r\n"
262:         elsif str =~ /\APLAIN\s+/
263:           plain = ($'.dup).unpack("m").first # Base64::decode64($'.dup)
264:           discard,user,psw = plain.split("\0000")
265:           if receive_plain_auth user,psw
266:             send_data "235 authentication ok\r\n"
267:             @state << :auth
268:           else
269:             send_data "535 invalid authentication\r\n"
270:           end
271:           #elsif str =~ /\ALOGIN\s+/i
272:         else
273:           send_data "504 auth mechanism not available\r\n"
274:         end
275:       end
process_data() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 282
282:       def process_data
283:         unless @state.include?(:rcpt)
284:           send_data "503 Operation sequence error\r\n"
285:         else
286:           succeeded = proc {
287:             send_data "354 Send it\r\n"
288:             @state << :data
289:             @databuffer = []
290:           }
291:           failed = proc {
292:             send_data "550 Operation failed\r\n"
293:           }
294: 
295:           d = receive_data_command
296: 
297:           if d.respond_to?(:callback)
298:             d.callback(&succeeded)
299:             d.errback(&failed)
300:           else
301:             (d ? succeeded : failed).call
302:           end
303:         end
304:       end
process_data_line(ln) click to toggle source

Send the incoming data to the application one chunk at a time, rather than one line at a time. That lets the application be a little more flexible about storing to disk, etc. Since we clear the chunk array every time we submit it, the caller needs to be aware to do things like dup it if he wants to keep it around across calls.

DON’T reset the transaction upon disposition of the incoming message. This means another DATA command can be accepted with the same sender and recipients. If the client wants to reset, he can call RSET. Not sure whether the standard requires a transaction-reset at this point, but it appears not to.

User-written code can return a Deferrable as a response from receive_message.

     # File lib/em/protocols/smtpserver.rb, line 424
424:       def process_data_line ln
425:         if ln == "."
426:           if @databuffer.length > 0
427:             receive_data_chunk @databuffer
428:             @databuffer.clear
429:           end
430: 
431: 
432:           succeeded = proc {
433:             send_data "250 Message accepted\r\n"
434:           }
435:           failed = proc {
436:             send_data "550 Message rejected\r\n"
437:           }
438: 
439:           d = receive_message
440: 
441:           if d.respond_to?(:set_deferred_status)
442:             d.callback(&succeeded)
443:             d.errback(&failed)
444:           else
445:             (d ? succeeded : failed).call
446:           end
447: 
448:           @state.delete :data
449:         else
450:           # slice off leading . if any
451:           ln.slice!(0...1) if ln[0] == 46
452:           @databuffer << ln
453:           if @databuffer.length > @@parms[:chunksize]
454:             receive_data_chunk @databuffer
455:             @databuffer.clear
456:           end
457:         end
458:       end
process_ehlo(domain) click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 212
212:       def process_ehlo domain
213:         if receive_ehlo_domain domain
214:           send_data "250-#{get_server_domain}\r\n"
215:           if @@parms[:starttls]
216:             send_data "250-STARTTLS\r\n"
217:           end
218:           if @@parms[:auth]
219:             send_data "250-AUTH PLAIN LOGIN\r\n"
220:           end
221:           send_data "250-NO-SOLICITING\r\n"
222:           # TODO, size needs to be configurable.
223:           send_data "250 SIZE 20000000\r\n"
224:           reset_protocol_state
225:           @state << :ehlo
226:         else
227:           send_data "550 Requested action not taken\r\n"
228:         end
229:       end
process_expn() click to toggle source

TODO - implement this properly, the implementation is a stub!

     # File lib/em/protocols/smtpserver.rb, line 159
159:       def process_expn
160:         send_data "250 Ok, but unimplemented\r\n"
161:       end
process_helo(domain) click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 231
231:       def process_helo domain
232:         if receive_ehlo_domain domain.dup
233:           send_data "250 #{get_server_domain}\r\n"
234:           reset_protocol_state
235:           @state << :ehlo
236:         else
237:           send_data "550 Requested action not taken\r\n"
238:         end
239:       end
process_help() click to toggle source

TODO - implement this properly, the implementation is a stub!

     # File lib/em/protocols/smtpserver.rb, line 155
155:       def process_help
156:         send_data "250 Ok, but unimplemented\r\n"
157:       end
process_mail_from(sender) click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 348
348:       def process_mail_from sender
349:         if (@@parms[:starttls]==:required and !@state.include?(:starttls))
350:           send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
351:         elsif (@@parms[:auth]==:required and !@state.include?(:auth))
352:           send_data "550 This server requires authentication before MAIL FROM\r\n"
353:         elsif @state.include?(:mail_from)
354:           send_data "503 MAIL already given\r\n"
355:         else
356:           unless receive_sender sender
357:             send_data "550 sender is unacceptable\r\n"
358:           else
359:             send_data "250 Ok\r\n"
360:             @state << :mail_from
361:           end
362:         end
363:       end
process_noop() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 246
246:       def process_noop
247:         send_data "250 Ok\r\n"
248:       end
process_quit() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 241
241:       def process_quit
242:         send_data "221 Ok\r\n"
243:         close_connection_after_writing
244:       end
process_rcpt_to(rcpt) click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 377
377:       def process_rcpt_to rcpt
378:         unless @state.include?(:mail_from)
379:           send_data "503 MAIL is required before RCPT\r\n"
380:         else
381:           succeeded = proc {
382:             send_data "250 Ok\r\n"
383:             @state << :rcpt unless @state.include?(:rcpt)
384:           }
385:           failed = proc {
386:             send_data "550 recipient is unacceptable\r\n"
387:           }
388: 
389:           d = receive_recipient rcpt
390: 
391:           if d.respond_to?(:set_deferred_status)
392:             d.callback(&succeeded)
393:             d.errback(&failed)
394:           else
395:             (d ? succeeded : failed).call
396:           end
397: 
398:         unless receive_recipient rcpt          send_data "550 recipient is unacceptable\r\n"        else          send_data "250 Ok\r\n"          @state << :rcpt unless @state.include?(:rcpt)        end=end
399:         end
400:       end
process_rset() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 306
306:       def process_rset
307:         reset_protocol_state
308:         receive_reset
309:         send_data "250 Ok\r\n"
310:       end
process_starttls() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 321
321:       def process_starttls
322:         if @@parms[:starttls]
323:           if @state.include?(:starttls)
324:             send_data "503 TLS Already negotiated\r\n"
325:           elsif ! @state.include?(:ehlo)
326:             send_data "503 EHLO required before STARTTLS\r\n"
327:           else
328:             send_data "220 Start TLS negotiation\r\n"
329:             start_tls
330:             @state << :starttls
331:           end
332:         else
333:           process_unknown
334:         end
335:       end
process_unknown() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 250
250:       def process_unknown
251:         send_data "500 Unknown command\r\n"
252:       end
process_vrfy() click to toggle source

TODO - implement this properly, the implementation is a stub!

     # File lib/em/protocols/smtpserver.rb, line 151
151:       def process_vrfy
152:         send_data "250 Ok, but unimplemented\r\n"
153:       end
receive_data_chunk(data) click to toggle source

Sent when data from the remote peer is available. The size can be controlled by setting the :chunksize parameter. This call can be made multiple times. The goal is to strike a balance between sending the data to the application one line at a time, and holding all of a very large message in memory.

     # File lib/em/protocols/smtpserver.rb, line 527
527:       def receive_data_chunk data
528:         @smtps_msg_size ||= 0
529:         @smtps_msg_size += data.join.length
530:         STDERR.write "<#{@smtps_msg_size}>"
531:       end
receive_data_command() click to toggle source

Called when the remote peer sends the DATA command. Returning false will cause us to send a 550 error to the peer. This can be useful for dealing with problems that arise from processing the whole set of sender and recipients.

     # File lib/em/protocols/smtpserver.rb, line 518
518:       def receive_data_command
519:         true
520:       end
receive_ehlo_domain(domain) click to toggle source

A false response from this user-overridable method will cause a 550 error to be returned to the remote client.

     # File lib/em/protocols/smtpserver.rb, line 477
477:       def receive_ehlo_domain domain
478:         true
479:       end
receive_line(ln) click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 113
113:       def receive_line ln
114:         @@parms[:verbose] and $>.puts ">>> #{ln}"
115: 
116:         return process_data_line(ln) if @state.include?(:data)
117: 
118:         case ln
119:         when EhloRegex
120:           process_ehlo $'.dup
121:         when HeloRegex
122:           process_helo $'.dup
123:         when MailFromRegex
124:           process_mail_from $'.dup
125:         when RcptToRegex
126:           process_rcpt_to $'.dup
127:         when DataRegex
128:           process_data
129:         when RsetRegex
130:           process_rset
131:         when VrfyRegex
132:           process_vrfy
133:         when ExpnRegex
134:           process_expn
135:         when HelpRegex
136:           process_help
137:         when NoopRegex
138:           process_noop
139:         when QuitRegex
140:           process_quit
141:         when StarttlsRegex
142:           process_starttls
143:         when AuthRegex
144:           process_auth $'.dup
145:         else
146:           process_unknown
147:         end
148:       end
receive_message() click to toggle source

Sent after a message has been completely received. User code must return true or false to indicate whether the message has been accepted for delivery.

     # File lib/em/protocols/smtpserver.rb, line 536
536:       def receive_message
537:         @@parms[:verbose] and $>.puts "Received complete message"
538:         true
539:       end
receive_plain_auth(user, password) click to toggle source

Return true or false to indicate that the authentication is acceptable.

     # File lib/em/protocols/smtpserver.rb, line 482
482:       def receive_plain_auth user, password
483:         true
484:       end
receive_recipient(rcpt) click to toggle source

Receives the argument of a RCPT TO command. Can be given multiple times per transaction. Return false to reject the recipient.

     # File lib/em/protocols/smtpserver.rb, line 497
497:       def receive_recipient rcpt
498:         true
499:       end
receive_reset() click to toggle source

Sent when the remote peer issues the RSET command. Since RSET is not allowed to fail (according to the protocol), we ignore any return value from user overrides of this method.

     # File lib/em/protocols/smtpserver.rb, line 505
505:       def receive_reset
506:       end
receive_sender(sender) click to toggle source

Receives the argument of the MAIL FROM command. Return false to indicate to the remote client that the sender is not accepted. This can only be successfully called once per transaction.

     # File lib/em/protocols/smtpserver.rb, line 490
490:       def receive_sender sender
491:         true
492:       end
receive_transaction() click to toggle source

This is called when the protocol state is reset. It happens when the remote client calls EHLO/HELO or RSET.

     # File lib/em/protocols/smtpserver.rb, line 543
543:       def receive_transaction
544:       end
reset_protocol_state() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 182
182:       def reset_protocol_state
183:         init_protocol_state
184:         s,@state = @state,[]
185:         @state << :starttls if s.include?(:starttls)
186:         @state << :ehlo if s.include?(:ehlo)
187:         receive_transaction
188:       end
send_server_greeting() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 109
109:       def send_server_greeting
110:         send_data "220 #{get_server_greeting}\r\n"
111:       end
unbind() click to toggle source
     # File lib/em/protocols/smtpserver.rb, line 312
312:       def unbind
313:         connection_ended
314:       end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.