class Groat::SMTPD::SMTPSyntax

Constants

DOMAIN_OR_LITERAL

For example, the EHLO/HELO parameter

DOT_STRING

For example, an unquoted local part of a mailbox

EXCESSIVE_QUOTE
MAILBOX

MatchData is the local part and [4] is the domain or address literal

PATH

Only has path (vs. starts with path) Same MatchData as PATH_PART

PATH_PART

If a string begins with a path (allows for characters after the path) MatchData is the Source Route, [9] is the local part, and

12

is the domain or address literal

R_A_d_l
R_At_domain
R_Atom
R_Domain
R_Dot_string
R_IPv4_address_literal
R_IPv6_address_literal
R_IPv6_comp
R_IPv6_full
R_IPv6_hex
R_IPv6v4_comp
R_IPv6v4_full
R_Ldh_str
R_Let_dig
Path handling functions

From RFC5321 section 4.1.2

R_Local_part
R_Mailbox
R_Path
R_Quoted_string
R_RHS_Domain

The RHS domain syntax is explicitly from RFC2821; see www.imc.org/ietf-smtp/mail-archive/msg05431.html

R_Snum

This should really be 0-255 with no leading zeros

R_address_literal
RFC 5321 § 4.1.3 "Standardized-tag MUST be specified in a i
Standards-Track RFC and registered with IANA
At this point, only "IPv6" has been register, which
already handled.  Therefore we are using a slightly simpler regex

R_dcontent = “[\041-\132\136-\176]” R_General_address_literal = “#{R_Ldh_str}:(#{R_dcontent}+)” R_address_literal = “\[(#{R_IPv4_address_literal}|#{R_IPv6_address_literal}|#{R_General_address_literal})\]”

R_atext
R_qtextSMTP
R_quoted_pairSMTP
R_sub_domain
R_xchar_list

Defined in RFC 3461 § 4, referenced in RFC 5321 § 4.1.2

R_xtext_hexchar
VERB

RFC 5321 § 2.2.2: “verbs […] are bound by the same rules as EHLO i keywords”; § 4.1.1.1 defines it as /A([A-Za-z0-9-]*)Z/ This splits the verb off and then finds the correct method to call

XTEXT
XTEXT_HEXSEQ
XTEXT_NOT_XCHAR

Public Class Methods

after_verb(name, method = nil, &block) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 131
def self.after_verb(name, method = nil, &block)
  sym = name.to_s.upcase.intern
  smtp_verbs[sym] = {} unless smtp_verbs.has_key? sym
  smtp_verbs[sym][:after] = [] unless smtp_verbs[sym].has_key? :after
  callback = block_given? ? block : method
  smtp_verbs[sym][:after] << callback
end
before_verb(name, method = nil, &block) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 123
def self.before_verb(name, method = nil, &block)
  sym = name.to_s.upcase.intern
  smtp_verbs[sym] = {} unless smtp_verbs.has_key? sym
  smtp_verbs[sym][:before] = [] unless smtp_verbs[sym].has_key? :before
  callback = block_given? ? block : method
  smtp_verbs[sym][:before] << callback
end
ehlo_keyword(keyword, params = [], condition = nil) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 78
def self.ehlo_keyword(keyword, params = [], condition = nil)
  sym = keyword.to_s.upcase.intern
  ehlo_keywords[sym] = {:params => params, :condition => condition}
end
ehlo_keyword_known?(kw) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 83
def self.ehlo_keyword_known?(kw)
  sym = kw.to_s.upcase.intern
  ehlo_keywords.has_key? sym
end
mail_param(name, method) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 153
def self.mail_param(name, method)
  sym = name.to_s.upcase.intern
  mail_parameters[sym] = method
end
new(*args) click to toggle source
Calls superclass method Groat::SMTPD::Base::new
# File lib/groat/smtpd/smtpsyntax.rb, line 56
def initialize(*args)
  super(*args)
  @response_class = SMTPResponse
end
run_verb_hook_for(hook, verb, scope, *args) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 111
def self.run_verb_hook_for(hook, verb, scope, *args)
  if not smtp_verbs[verb].nil? and not smtp_verbs[verb][hook].nil?
    smtp_verbs[verb][hook].each do |callback|
      if callback.kind_of? Symbol
        scope.send(callback, *args)
      else
        callback.call(*args)
      end
    end
  end
end
validate_verb(name, method = nil, &block) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 139
def self.validate_verb(name, method = nil, &block)
  sym = name.to_s.upcase.intern
  smtp_verbs[sym] = {} unless smtp_verbs.has_key? sym
  smtp_verbs[sym][:valid] = [] unless smtp_verbs[sym].has_key? :valid
  callback = block_given? ? block : method
  smtp_verbs[sym][:valid] << callback
end
verb(name, method) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 147
def self.verb(name, method)
  sym = name.to_s.upcase.intern
  smtp_verbs[sym] = {} unless smtp_verbs.has_key? sym
  smtp_verbs[sym][:method] = method
end

Public Instance Methods

authenticated?() click to toggle source

Did the client successfully authenticate?

# File lib/groat/smtpd/smtpsyntax.rb, line 210
def authenticated?
  false
end
do_garbage(garbage) click to toggle source

Lines which do not have a valid verb

# File lib/groat/smtpd/smtpsyntax.rb, line 184
def do_garbage(garbage)
  response_syntax_error :message=>"syntax error - invalid character"
end
do_verb(verb, args) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 173
def do_verb(verb, args)
  args = args.to_s.strip
  run_verb_hook :validate, verb, args
  if smtp_verb(verb).nil?
    verb_missing verb, args
  else
    send smtp_verb(verb), args
  end
end
ehlo_keywords() click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 88
def ehlo_keywords
  list = {}
  self.class.ehlo_keywords.each do |k, v|
    valid = false

    if v[:condition].nil?
      valid = true
    else
      valid = send v[:condition]
    end

    if valid
      if v[:params].kind_of? Symbol
        params = send v[:params]
      else
        params = v[:params]
      end
      list[k] = list[k].to_a|params.to_a
    end
  end
  list
end
esmtp?() click to toggle source

Does the client support SMTP extensions?

# File lib/groat/smtpd/smtpsyntax.rb, line 205
def esmtp?
  false
end
from_xtext(str) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 366
def from_xtext(str)
  if str =~ XTEXT
    str.gsub!(XTEXT_HEXSEQ) {|s| s[1..2].hex.chr }
  end
end
known_verbs() click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 162
def known_verbs
  self.class.smtp_verbs.map{|k, v| k if v.has_key?(:method)}.compact
end
mail_params_valid(params) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 191
def mail_params_valid(params)
  params.each do |name, value|
    return false unless self.class.mail_parameters.has_key? name
  end
  true
end
normalize_local_part(local) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 338
def normalize_local_part(local)
  if local.start_with? '"'
    local.gsub!(EXCESSIVE_QUOTE, '\1')
    local = local[1..-2] if local[1..-2] =~ DOT_STRING
  end
  local
end
normalize_mailbox(addr) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 354
def normalize_mailbox(addr)
  addr =~ MAILBOX
  normalize_local_part($~[1]) + "@" + $~[4].downcase
end
normalize_path(path) click to toggle source

Remove the leading ‘<’, trailing ‘>’, switch domains lower case and remove unnecessary quoting in the localpart

# File lib/groat/smtpd/smtpsyntax.rb, line 348
def normalize_path(path)
  return '' if path.eql? '<>'
  path =~ PATH
  $~[1].to_s.downcase + normalize_local_part($~[9]) + "@" + $~[12].downcase
end
parse_params(param_str) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 241
def parse_params(param_str)
  params = {}
  param_str.split(' ').each do |p|
    k, v = p.split('=', 2)
    k = k.intern
    params[k] = v
  end
  params
end
process_line(line) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 227
def process_line(line)
  k, v = line.chomp.split(' ', 2)
  if k.to_s !~ VERB
      run :do_garbage, line
  end
  k = k.to_s.upcase.tr('-', '_').intern
  run_hook :before_all_verbs, k
  run_verb_hook :before, k
  res = run :do_verb, k, v.to_s.strip
  run_verb_hook :after, k 
  run_hook :after_all_verbs, k
  res
end
process_mail_params(params) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 198
def process_mail_params(params)
  params.each do |name, value|
    send self.class.mail_parameters[name], value
  end
end
protocol() click to toggle source

Return the protocol name for use with “WITH” in the Received: header

# File lib/groat/smtpd/smtpsyntax.rb, line 215
def protocol
  # This could return "SMTPS" which is non-standard is two cases:
  #   - Client sends EHLO -> STARTTLS -> HELO sequence
  #   - If using implicit TLS (i.e. non-standard port 465)
  (esmtp? ? "E" : "") + "SMTP" + (secure? ? "S" : "") + (authenticated? ? "A" : "")
end
run_verb_hook(hook, verb, *args) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 158
def run_verb_hook(hook, verb, *args)
  self.class.run_verb_hook_for(hook, verb, self, *args)
end
smtp_verb(verb) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 166
def smtp_verb(verb)
  hooks = self.class.smtp_verbs[verb]
  unless hooks.nil?
    hooks[:method]
  end
end
split_path(args) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 325
def split_path(args)
  m = args =~ PATH_PART
  if m.nil?
    [nil, args]
  else
    response = [$~.to_s, $'.strip]
    if $~[12].start_with? '['
      return [nil, args] unless valid_address_literal $~[12]
    end
    response
  end
end
to_xtext(str) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 372
def to_xtext(str)
  str.gsub!(XTEXT_NOT_XCHAR) {|s| '+' + s[0].to_s(16).upcase } 
end
valid_address_literal(literal) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 314
def valid_address_literal(literal)
  return false unless literal.start_with? '['
  return false unless literal.end_with? ']'
  begin
    IPAddr.new(literal[1..-2])
  rescue ::ArgumentError
    return false
  end
  true
end
verb_missing(verb, parameters) click to toggle source
# File lib/groat/smtpd/smtpsyntax.rb, line 188
def verb_missing(verb, parameters)
end