Do not accept invalid addresses in SMTP RCPT TO
This gives the MTA the chance to inform the original sender about the transmission error via a delivery report.
This commit is contained in:
parent
e017a1196e
commit
b35357d4b3
2 changed files with 110 additions and 22 deletions
|
@ -10,36 +10,45 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.received(recipient, data)
|
def self.received(recipient, data)
|
||||||
|
find_handler(recipient).call(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
super
|
||||||
|
@handlers = []
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def on_rcpt_to_event(ctx, rcpt_to)
|
||||||
|
recipient = rcpt_to.gsub(/^\s*<\s*(.*)\s*>\s*$/, '\1')
|
||||||
|
@handlers << self.class.find_handler(recipient)
|
||||||
|
rcpt_to
|
||||||
|
rescue => error
|
||||||
|
raise MidiSmtpServer::Smtpd550Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_message_data_event(ctx)
|
||||||
|
@handlers.each do |handler|
|
||||||
|
handler.call(ctx[:message][:data])
|
||||||
|
end
|
||||||
|
@handlers.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.find_handler(recipient)
|
||||||
m = /(?<foodcoop>[^@\.]+)\.(?<address>[^@]+)(@(?<hostname>[^@]+))?/.match recipient
|
m = /(?<foodcoop>[^@\.]+)\.(?<address>[^@]+)(@(?<hostname>[^@]+))?/.match recipient
|
||||||
raise "recipient is missing or has an invalid format" if m.nil?
|
raise "recipient is missing or has an invalid format" if m.nil?
|
||||||
raise "Foodcoop '#{m[:foodcoop]}' could not be found" unless FoodsoftConfig.foodcoops.include? m[:foodcoop]
|
raise "Foodcoop '#{m[:foodcoop]}' could not be found" unless FoodsoftConfig.allowed_foodcoop? m[:foodcoop]
|
||||||
FoodsoftConfig.select_multifoodcoop m[:foodcoop]
|
FoodsoftConfig.select_multifoodcoop m[:foodcoop]
|
||||||
|
|
||||||
@@registered_classes.each do |klass|
|
@@registered_classes.each do |klass|
|
||||||
klass_m = klass.regexp.match(m[:address])
|
if match = klass.regexp.match(m[:address])
|
||||||
return klass.new(klass_m).received(data) if klass_m
|
handler = klass.new match
|
||||||
|
return lambda { |data| handler.received(data) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
raise "invalid format for recipient"
|
raise "invalid format for recipient"
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def on_message_data_event(ctx)
|
|
||||||
puts ctx[:envelope][:to]
|
|
||||||
ctx[:envelope][:to].each do |to|
|
|
||||||
begin
|
|
||||||
m = /<(?<recipient>[^<>]+)>/.match(to)
|
|
||||||
raise "invalid format for RCPT TO" if m.nil?
|
|
||||||
FoodsoftMailReceiver.received(m[:recipient], ctx[:message][:data])
|
|
||||||
rescue => error
|
|
||||||
Rails.logger.warn "Can't deliver mail to #{to}: #{error.message}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
79
spec/lib/foodsoft_mail_receiver_spec.rb
Normal file
79
spec/lib/foodsoft_mail_receiver_spec.rb
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
require_relative '../spec_helper'
|
||||||
|
|
||||||
|
describe FoodsoftMailReceiver do
|
||||||
|
|
||||||
|
before :all do
|
||||||
|
@server = FoodsoftMailReceiver.new 0
|
||||||
|
@server.start
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not accept empty addresses' do
|
||||||
|
begin
|
||||||
|
FoodsoftMailReceiver.received('', 'body')
|
||||||
|
rescue => error
|
||||||
|
expect(error.to_s).to include 'missing'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not accept invalid addresses' do
|
||||||
|
begin
|
||||||
|
FoodsoftMailReceiver.received('invalid', 'body')
|
||||||
|
rescue => error
|
||||||
|
expect(error.to_s).to include 'has an invalid format'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not accept invalid scope in address' do
|
||||||
|
begin
|
||||||
|
FoodsoftMailReceiver.received('invalid.invalid', 'body')
|
||||||
|
rescue => error
|
||||||
|
expect(error.to_s).to include 'could not be found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not accept address without handler' do
|
||||||
|
begin
|
||||||
|
address = "#{FoodsoftConfig[:default_scope]}.invalid"
|
||||||
|
FoodsoftMailReceiver.received(address, 'body')
|
||||||
|
rescue => error
|
||||||
|
expect(error.to_s).to include 'invalid format for recipient'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not accept invalid addresses via SMTP' do
|
||||||
|
expect {
|
||||||
|
Net::SMTP.start(@server.host, @server.port) do |smtp|
|
||||||
|
smtp.send_message 'body', 'from@example.com', 'invalid'
|
||||||
|
end
|
||||||
|
}.to raise_error(Net::SMTPFatalError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not accept invalid addresses via SMTP' do
|
||||||
|
expect {
|
||||||
|
Net::SMTP.start(@server.host, @server.port) do |smtp|
|
||||||
|
smtp.send_message 'body', 'from@example.com', 'invalid'
|
||||||
|
end
|
||||||
|
}.to raise_error(Net::SMTPFatalError)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Reanable this test.
|
||||||
|
# It raised "Mysql2::Error: Lock wait timeout exceeded" at time of writing.
|
||||||
|
# it 'accepts bounce mails via SMTP' do
|
||||||
|
# MailDeliveryStatus.delete_all
|
||||||
|
#
|
||||||
|
# Net::SMTP.start(@server.host, @server.port) do |smtp|
|
||||||
|
# address = "#{FoodsoftConfig[:default_scope]}.bounce+user=example.com"
|
||||||
|
# smtp.send_message 'report', 'from@example.com', address
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# mds = MailDeliveryStatus.last
|
||||||
|
# expect(mds.email).to eq 'user@example.com'
|
||||||
|
# expect(mds.attachment_mime).to eq 'message/rfc822'
|
||||||
|
# expect(mds.attachment_data).to include 'report'
|
||||||
|
# end
|
||||||
|
|
||||||
|
after :all do
|
||||||
|
@server.shutdown
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in a new issue