class Groat::SMTPD::SMTP

Public Class Methods

new(*args) click to toggle source
Calls superclass method
# File lib/groat/smtpd/smtp.rb, line 36
def initialize(*args)
  @hostname = "groat.example" if @hostname.nil?
  super(*args)
end

Public Instance Methods

check_command_group() click to toggle source

No pipelining allowed (not in RFC5321)

# File lib/groat/smtpd/smtp.rb, line 105
def check_command_group
  if clientdata?
    response_bad_sequence
  end
end
deliver!() click to toggle source
# File lib/groat/smtpd/smtp.rb, line 41
def deliver!
end
esmtp?() click to toggle source
# File lib/groat/smtpd/smtp.rb, line 129
def esmtp?
  @esmtp
end
handle_hello(args, keyword) click to toggle source

Generic handler for hello action Keyword determines Mail Service Type See: www.iana.org/assignments/mail-parameters

# File lib/groat/smtpd/smtp.rb, line 136
def handle_hello(args, keyword)
  keyword = keyword.to_s.upcase.intern
  check_command_group
  response_syntax_error if args.empty?
  hello, hello_extra = args.split(" ", 2)
  hello =~ DOMAIN_OR_LITERAL
  if $~.nil?
    respond_syntax_error :message=>"Syntax Error: expected hostname or IP literal"
  elsif hello.start_with? '[' and not valid_address_literal(hello)
    respond_syntax_error :message=>"Syntax Error: invalid IP literal"
  else
    @hello = hello
    @hello_extra = hello_extra
  end
  reset_buffers
  response_text = ["#{@hostname} at your service"]
  if (keyword == :EHLO)
    @esmtp = true
    ehlo_keywords.each do |kw, params|
      param_str = params.to_a.join(' ')
      if param_str.empty?
        response_text << "#{kw}"
      else
        response_text << "#{kw} #{param_str}"
      end
    end
  end
  reply :code => 250, :message => response_text
end
in_mail_transaction?() click to toggle source
# File lib/groat/smtpd/smtp.rb, line 125
def in_mail_transaction?
  not @mailfrom.nil?
end
reset_buffers() click to toggle source
# File lib/groat/smtpd/smtp.rb, line 119
def reset_buffers
  @mailfrom = nil
  @rcptto = []
  @message = ""
end
reset_connection() click to toggle source
Calls superclass method
# File lib/groat/smtpd/smtp.rb, line 111
def reset_connection
  @hello = nil 
  @hello_extra = nil
  @esmtp = false
  reset_buffers
  super
end
response_bad_command(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 51
def response_bad_command(args = {})
  defaults = {:code => 500, :message => "Bad command"}
  reply defaults.merge(args)
end
response_bad_command_parameter(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 67
def response_bad_command_parameter(args = {})
  defaults = {:code => 504, :message => "Invalid parameter"}
  reply defaults.merge(args)
end
response_bad_parameter(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 77
def response_bad_parameter(args = {})
  defaults = {:code => 555, :message => "Parameter not recognized"}
  reply defaults.merge(args)
end
response_bad_sequence(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 61
def response_bad_sequence(args = {})
  defaults = {:code => 503, :message => "Bad sequence of commands", 
              :terminate => true }
  reply defaults.merge(args)
end
response_no_valid_rcpt(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 72
def response_no_valid_rcpt(args = {})
  defaults = {:code => 554, :message => "No valid recipients"}
  reply defaults.merge(args)
end
response_ok(args = {}) click to toggle source

Reply methods

# File lib/groat/smtpd/smtp.rb, line 46
def response_ok(args = {})
  defaults = {:code => 250, :message => "OK"}
  reply defaults.merge(args)
end
response_service_shutdown(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 82
def response_service_shutdown(args = {})
  defaults = {:code => 421, :message => "Server closing connection", 
        :terminate => true}
  reply defaults.merge(args)
end
response_syntax_error(args = {}) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 56
def response_syntax_error(args = {})
  defaults = {:code => 501, :message => "Syntax error"}
  reply defaults.merge(args)
end
send_greeting() click to toggle source

Groat framework methods

# File lib/groat/smtpd/smtp.rb, line 90
def send_greeting
  reply :code => 220, :message => "#{@hostname} ESMTP Ready"
end
service_shutdown() click to toggle source
# File lib/groat/smtpd/smtp.rb, line 94
def service_shutdown
  response_service_shutdown
end
smtp_verb_data(args) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 230
def smtp_verb_data(args)
  check_command_group
  response_syntax_error unless args.empty?
  return response_no_valid_rcpt if @rcptto.count < 1
  toclient "354 Enter message, ending with \".\" on a line by itself.\r\n"
  loop do
    line = fromclient
    # RFC 5321 § 4.1.1.4
    # "The custom of accepting lines ending only in <LF>, as a concession to
    #  non-conforming behavior on the part of some UNIX systems, has proven
    #  to cause more interoperability problems than it solves, and SMTP
    #   server systems MUST NOT do this, even in the name of improved
    #   robustness."
    break if line.chomp("\r\n").eql?('.')
    # RFC5321 sect 4.5.2, remove leading '.' if found
    line.slice!(0) if line.start_with? '.'
    @message << line
  end
  message = deliver!
  reset_buffers
  response_ok :message => message
end
smtp_verb_ehlo(args) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 171
def smtp_verb_ehlo(args)
  handle_hello(args, :EHLO)
end
smtp_verb_helo(args) click to toggle source

Verb handlers

# File lib/groat/smtpd/smtp.rb, line 167
def smtp_verb_helo(args)
  handle_hello(args, :HELO)
end
smtp_verb_mail(args) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 176
def smtp_verb_mail(args)
  check_command_group
  response_bad_sequence if @hello.nil?
  # This should be start_with? 'FROM:<', but Outlook puts a space
  # between the ':' and the '<'
  response_syntax_error unless args.upcase.start_with? 'FROM:'
  # Remove leading "FROM:" and following spaces
  args = args[5..-1].lstrip
  if args[0..2].rstrip.eql? '<>'
    path = '<>'
    param_str = args[3..-1].to_s
  else
    path, param_str = split_path(args)
    response_syntax_error :message => 'Path error' if path.nil?
  end
  unless param_str.strip.empty?
    response_syntax_error unless esmtp?
  end
  params = parse_params(param_str)
  response_bad_parameter unless mail_params_valid(params)
  # Validation complete
  # RFC5321 § 4.1.1.2
  # "This command clears the reverse-path buffer, the forward-path
  #  buffer, and the mail data buffer, and it inserts the reverse-path
  #  information from its argument clause into the reverse-path buffer."
  reset_buffers
  process_mail_params(params)
  mailfrom = normalize_path(path)
  run_hook :validate_mailfrom, mailfrom
  @mailfrom = mailfrom
  response_ok
end
smtp_verb_noop(args) click to toggle source

RFC 5321 § 4.1.1.9 “If a parameter string is specified, servers SHOULD ignore it.”

# File lib/groat/smtpd/smtp.rb, line 271
def smtp_verb_noop(args)
  check_command_group
  response_ok
end
smtp_verb_quit(args) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 260
def smtp_verb_quit(args)
  check_command_group
  response_syntax_error unless args.empty?
  reset_buffers
  reply :code=>221, 
        :message=>"#{@hostname} Service closing transmission channel", 
        :terminate=>true
end
smtp_verb_rcpt(args) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 210
def smtp_verb_rcpt(args)
  check_command_group
  response_bad_sequence if @mailfrom.nil?
  # This should be start_with? 'TO:<', but Outlook puts a space
  # between the ':' and the '<'
  response_syntax_error unless args.upcase.start_with? 'TO:'
  # Remove leading "TO:" and the following spaces
  args = args[3..-1].lstrip
  path, param_str = split_path(args)
  response_syntax error :message => 'Path error' if path.nil?
  unless param_str.strip.empty?
    response_syntax_error unless esmtp?
  end
  params = parse_params(param_str)
  rcptto = normalize_path(path)
  run_hook :validate_rcptto, rcptto
  @rcptto << rcptto
  response_ok
end
smtp_verb_rset(args) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 253
def smtp_verb_rset(args)
  check_command_group
  response_syntax_error unless args.empty?
  reset_buffers
  response_ok
end
verb_missing(verb, parameters) click to toggle source
# File lib/groat/smtpd/smtp.rb, line 98
def verb_missing(verb, parameters)
  response_bad_command :message => "Unknown command #{verb}"
end