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
# 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
# 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
# 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
# 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
# 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
Groat::SMTPD::Base::new
# File lib/groat/smtpd/smtpsyntax.rb, line 56 def initialize(*args) super(*args) @response_class = SMTPResponse end
# 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
# 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
# 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
Did the client successfully authenticate?
# File lib/groat/smtpd/smtpsyntax.rb, line 210 def authenticated? false end
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
# 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
# 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
Does the client support SMTP
extensions?
# File lib/groat/smtpd/smtpsyntax.rb, line 205 def esmtp? false end
# 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
# 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
# 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
# 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
# File lib/groat/smtpd/smtpsyntax.rb, line 354 def normalize_mailbox(addr) addr =~ MAILBOX normalize_local_part($~[1]) + "@" + $~[4].downcase end
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
# 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
# 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
# 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
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
# 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
# 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
# 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
# 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
# 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
# File lib/groat/smtpd/smtpsyntax.rb, line 188 def verb_missing(verb, parameters) end