Class: Groat::SMTPD::SMTP

Inherits:
SMTPSyntax show all
Defined in:
lib/groat/smtpd/smtp.rb

Constant Summary

Constants inherited from SMTPSyntax

Groat::SMTPD::SMTPSyntax::DOMAIN_OR_LITERAL, Groat::SMTPD::SMTPSyntax::DOT_STRING, Groat::SMTPD::SMTPSyntax::EXCESSIVE_QUOTE, Groat::SMTPD::SMTPSyntax::MAILBOX, Groat::SMTPD::SMTPSyntax::PATH, Groat::SMTPD::SMTPSyntax::PATH_PART, Groat::SMTPD::SMTPSyntax::R_A_d_l, Groat::SMTPD::SMTPSyntax::R_At_domain, Groat::SMTPD::SMTPSyntax::R_Atom, Groat::SMTPD::SMTPSyntax::R_Domain, Groat::SMTPD::SMTPSyntax::R_Dot_string, Groat::SMTPD::SMTPSyntax::R_IPv4_address_literal, Groat::SMTPD::SMTPSyntax::R_IPv6_address_literal, Groat::SMTPD::SMTPSyntax::R_IPv6_comp, Groat::SMTPD::SMTPSyntax::R_IPv6_full, Groat::SMTPD::SMTPSyntax::R_IPv6_hex, Groat::SMTPD::SMTPSyntax::R_IPv6v4_comp, Groat::SMTPD::SMTPSyntax::R_IPv6v4_full, Groat::SMTPD::SMTPSyntax::R_Ldh_str, Groat::SMTPD::SMTPSyntax::R_Let_dig, Groat::SMTPD::SMTPSyntax::R_Local_part, Groat::SMTPD::SMTPSyntax::R_Mailbox, Groat::SMTPD::SMTPSyntax::R_Path, Groat::SMTPD::SMTPSyntax::R_Quoted_string, Groat::SMTPD::SMTPSyntax::R_RHS_Domain, Groat::SMTPD::SMTPSyntax::R_Snum, Groat::SMTPD::SMTPSyntax::R_address_literal, Groat::SMTPD::SMTPSyntax::R_atext, Groat::SMTPD::SMTPSyntax::R_qtextSMTP, Groat::SMTPD::SMTPSyntax::R_quoted_pairSMTP, Groat::SMTPD::SMTPSyntax::R_sub_domain, Groat::SMTPD::SMTPSyntax::R_xchar_list, Groat::SMTPD::SMTPSyntax::R_xtext_hexchar, Groat::SMTPD::SMTPSyntax::VERB, Groat::SMTPD::SMTPSyntax::XTEXT, Groat::SMTPD::SMTPSyntax::XTEXT_HEXSEQ, Groat::SMTPD::SMTPSyntax::XTEXT_NOT_XCHAR

Instance Method Summary collapse

Methods inherited from SMTPSyntax

after_verb, #authenticated?, before_verb, #do_garbage, #do_verb, ehlo_keyword, ehlo_keyword_known?, #ehlo_keywords, #from_xtext, #known_verbs, mail_param, #mail_params_valid, #normalize_local_part, #normalize_mailbox, #normalize_path, #parse_params, #process_line, #process_mail_params, #protocol, #run_verb_hook, run_verb_hook_for, #smtp_verb, #split_path, #to_xtext, #valid_address_literal, validate_verb, verb

Methods inherited from Base

#clientdata?, #fromclient, #getdata, #getline, #log_line, #process_line, #reply, #run, #secure?, #serve, #set_socket, #sockop_timeout, #toclient

Constructor Details

#initialize(*args) ⇒ SMTP

Returns a new instance of SMTP.



36
37
38
39
# File 'lib/groat/smtpd/smtp.rb', line 36

def initialize(*args)
  @hostname = "groat.example" if @hostname.nil?
  super(*args)
end

Instance Method Details

#check_command_groupObject

No pipelining allowed (not in RFC5321)



105
106
107
108
109
# File 'lib/groat/smtpd/smtp.rb', line 105

def check_command_group
  if clientdata?
    response_bad_sequence
  end
end

#deliver!Object



41
42
# File 'lib/groat/smtpd/smtp.rb', line 41

def deliver!
end

#esmtp?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/groat/smtpd/smtp.rb', line 129

def esmtp?
  @esmtp
end

#handle_hello(args, keyword) ⇒ Object

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



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 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?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/groat/smtpd/smtp.rb', line 125

def in_mail_transaction?
  not @mailfrom.nil?
end

#reset_buffersObject



119
120
121
122
123
# File 'lib/groat/smtpd/smtp.rb', line 119

def reset_buffers
  @mailfrom = nil
  @rcptto = []
  @message = ""
end

#reset_connectionObject



111
112
113
114
115
116
117
# 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 = {}) ⇒ Object



51
52
53
54
# 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 = {}) ⇒ Object



67
68
69
70
# 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 = {}) ⇒ Object



77
78
79
80
# 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 = {}) ⇒ Object



61
62
63
64
65
# 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 = {}) ⇒ Object



72
73
74
75
# 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 = {}) ⇒ Object

Reply methods



46
47
48
49
# 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 = {}) ⇒ Object



82
83
84
85
86
# 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 = {}) ⇒ Object



56
57
58
59
# 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_greetingObject

Groat framework methods



90
91
92
# File 'lib/groat/smtpd/smtp.rb', line 90

def send_greeting
  reply :code => 220, :message => "#{@hostname} ESMTP Ready"
end

#service_shutdownObject



94
95
96
# File 'lib/groat/smtpd/smtp.rb', line 94

def service_shutdown
  response_service_shutdown
end

#smtp_verb_data(args) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# 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) ⇒ Object



171
172
173
# File 'lib/groat/smtpd/smtp.rb', line 171

def smtp_verb_ehlo(args)
  handle_hello(args, :EHLO)
end

#smtp_verb_helo(args) ⇒ Object

Verb handlers



167
168
169
# File 'lib/groat/smtpd/smtp.rb', line 167

def smtp_verb_helo(args)
  handle_hello(args, :HELO)
end

#smtp_verb_mail(args) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# 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) ⇒ Object

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



271
272
273
274
# File 'lib/groat/smtpd/smtp.rb', line 271

def smtp_verb_noop(args)
  check_command_group
  response_ok
end

#smtp_verb_quit(args) ⇒ Object



260
261
262
263
264
265
266
267
# 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) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# 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) ⇒ Object



253
254
255
256
257
258
# 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) ⇒ Object



98
99
100
# File 'lib/groat/smtpd/smtp.rb', line 98

def verb_missing(verb, parameters)
  response_bad_command :message => "Unknown command #{verb}"
end