Class: Groat::SMTPD::SMTPSyntax
- Inherits:
-
Base
- Object
- Base
- Groat::SMTPD::SMTPSyntax
show all
- Defined in:
- lib/groat/smtpd/smtpsyntax.rb
Direct Known Subclasses
SMTP
Constant Summary
collapse
- 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
/\A[A-Za-z0-9]([A-Za-z0-9-]*)\Z/
- R_Let_dig =
Path handling functions From RFC5321 section 4.1.2
'[0-9a-z]'
- R_Ldh_str =
"[0-9a-z-]*#{R_Let_dig}"
- R_sub_domain =
"#{R_Let_dig}(#{R_Ldh_str})?"
- R_Domain =
"#{R_sub_domain}(\\.#{R_sub_domain})*"
- R_RHS_Domain =
"#{R_sub_domain}(\\.#{R_sub_domain})+"
- R_At_domain =
"@#{R_Domain}"
- R_A_d_l =
"#{R_At_domain}(,#{R_At_domain})*"
- R_atext =
"[a-z0-9!\#$%&'*+\\/=?^_`{|}~-]"
- R_Atom =
"#{R_atext}+"
- R_Dot_string =
"#{R_Atom}(\\.#{R_Atom})*"
- R_qtextSMTP =
"[\\040-\\041\\043-\\133\\135-\\176]"
- R_quoted_pairSMTP =
"\\134[\\040-\\176]"
- R_Quoted_string =
"\"(#{R_qtextSMTP}|#{R_quoted_pairSMTP})*\""
- R_Local_part =
"(#{R_Dot_string}|#{R_Quoted_string})"
- R_Snum =
This should really be 0-255 with no leading zeros
"(0|[1-9][0-9]{0,2})"
- R_IPv4_address_literal =
"#{R_Snum}(\.#{R_Snum}){3}"
- R_IPv6_hex =
"[0-9a-f]{1,4}"
- R_IPv6_full =
"#{R_IPv6_hex}(:#{R_IPv6_hex}){7}"
- R_IPv6_comp =
"(#{R_IPv6_hex}(:#{R_IPv6_hex}){0,5})?::(#{R_IPv6_hex}(:#{R_IPv6_hex}){0,5})?"
- R_IPv6v4_full =
"#{R_IPv6_hex}(:#{R_IPv6_hex}){3}:#{R_IPv4_address_literal}"
- R_IPv6v4_comp =
"(#{R_IPv6_hex}(:#{R_IPv6_hex}){0,3})?::(#{R_IPv6_hex}(:#{R_IPv6_hex}){0,3})?:#{R_IPv4_address_literal}"
- R_IPv6_address_literal =
"IPv6:(#{R_IPv6_full}|#{R_IPv6_comp}|#{R_IPv6v4_full}|#{R_IPv6v4_comp})"
- 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_IPv4_address_literal}|#{R_IPv6_address_literal})\\]"
- R_Mailbox =
"#{R_Local_part}@(#{R_RHS_Domain}|#{R_address_literal})"
- DOMAIN_OR_LITERAL =
For example, the EHLO/HELO parameter
/\A(#{R_Domain}|#{R_address_literal})\Z/i
- R_Path =
"<(#{R_A_d_l}:)?#{R_Mailbox}>"
- DOT_STRING =
For example, an unquoted local part of a mailbox
/\A#{R_Dot_string}\Z/i
- MAILBOX =
MatchData is the local part and [4] is the domain or address literal
/\A#{R_Mailbox}\Z/i
- 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
/\A#{R_Path}/i
- PATH =
Only has path (vs. starts with path) Same MatchData as PATH_PART
/\A#{R_Path}\Z/i
- EXCESSIVE_QUOTE =
/\134([^\041\134])/
- R_xchar_list =
Defined in RFC 3461 § 4, referenced in RFC 5321 § 4.1.2
"\\041-\\052\\054\\074\\076-\\176"
- R_xtext_hexchar =
"\\053[0-9A-F]{2}"
- XTEXT =
/\A([#{R_xchar_list}]|#{R_xtext_hexchar})*\Z/
- XTEXT_HEXSEQ =
/#{R_xtext_hexchar}/
- XTEXT_NOT_XCHAR =
/[^#{R_xchar_list}]/
Class Method Summary
collapse
-
.after_verb(name, method = nil, &block) ⇒ Object
-
.before_verb(name, method = nil, &block) ⇒ Object
-
.ehlo_keyword(keyword, params = [], condition = nil) ⇒ Object
-
.ehlo_keyword_known?(kw) ⇒ Boolean
-
.mail_param(name, method) ⇒ Object
-
.run_verb_hook_for(hook, verb, scope, *args) ⇒ Object
-
.validate_verb(name, method = nil, &block) ⇒ Object
-
.verb(name, method) ⇒ Object
Instance Method Summary
collapse
Methods inherited from Base
#clientdata?, #fromclient, #getdata, #getline, #log_line, #reply, #reset_connection, #run, #secure?, #send_greeting, #serve, #service_shutdown, #set_socket, #sockop_timeout, #toclient
Constructor Details
#initialize(*args) ⇒ SMTPSyntax
56
57
58
59
|
# File 'lib/groat/smtpd/smtpsyntax.rb', line 56
def initialize(*args)
super(*args)
@response_class = SMTPResponse
end
|
Class Method Details
.after_verb(name, method = nil, &block) ⇒ Object
131
132
133
134
135
136
137
|
# 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) ⇒ Object
123
124
125
126
127
128
129
|
# 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) ⇒ Object
78
79
80
81
|
# 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) ⇒ Boolean
83
84
85
86
|
# 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) ⇒ Object
153
154
155
156
|
# 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
|
.run_verb_hook_for(hook, verb, scope, *args) ⇒ Object
111
112
113
114
115
116
117
118
119
120
121
|
# 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) ⇒ Object
139
140
141
142
143
144
145
|
# 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) ⇒ Object
147
148
149
150
151
|
# 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
|
Instance Method Details
#authenticated? ⇒ Boolean
Did the client successfully authenticate?
210
211
212
|
# File 'lib/groat/smtpd/smtpsyntax.rb', line 210
def authenticated?
false
end
|
#do_garbage(garbage) ⇒ Object
Lines which do not have a valid verb
184
185
186
|
# 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) ⇒ Object
173
174
175
176
177
178
179
180
181
|
# 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 ⇒ Object
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
# 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? ⇒ Boolean
Does the client support SMTP extensions?
205
206
207
|
# File 'lib/groat/smtpd/smtpsyntax.rb', line 205
def esmtp?
false
end
|
#from_xtext(str) ⇒ Object
366
367
368
369
370
|
# 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 ⇒ Object
162
163
164
|
# 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) ⇒ Object
191
192
193
194
195
196
|
# 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) ⇒ Object
338
339
340
341
342
343
344
|
# 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) ⇒ Object
354
355
356
357
|
# File 'lib/groat/smtpd/smtpsyntax.rb', line 354
def normalize_mailbox(addr)
addr =~ MAILBOX
normalize_local_part($~[1]) + "@" + $~[4].downcase
end
|
#normalize_path(path) ⇒ Object
Remove the leading ‘<’, trailing ‘>’, switch domains lower case and remove unnecessary quoting in the localpart
348
349
350
351
352
|
# 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) ⇒ Object
241
242
243
244
245
246
247
248
249
|
# 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) ⇒ Object
227
228
229
230
231
232
233
234
235
236
237
238
239
|
# 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) ⇒ Object
198
199
200
201
202
|
# 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 ⇒ Object
Return the protocol name for use with “WITH” in the Received: header
215
216
217
218
219
220
|
# File 'lib/groat/smtpd/smtpsyntax.rb', line 215
def protocol
(esmtp? ? "E" : "") + "SMTP" + (secure? ? "S" : "") + (authenticated? ? "A" : "")
end
|
#run_verb_hook(hook, verb, *args) ⇒ Object
158
159
160
|
# 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) ⇒ Object
166
167
168
169
170
171
|
# 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) ⇒ Object
325
326
327
328
329
330
331
332
333
334
335
336
|
# 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) ⇒ Object
372
373
374
|
# 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) ⇒ Object
314
315
316
317
318
319
320
321
322
323
|
# 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) ⇒ Object
188
189
|
# File 'lib/groat/smtpd/smtpsyntax.rb', line 188
def verb_missing(verb, parameters)
end
|