Implemented issue #2092 - Send postmaster email to sender if email is too big
This commit is contained in:
parent
bc38f1c1ae
commit
fae194918e
13 changed files with 661 additions and 31 deletions
|
@ -172,6 +172,7 @@ test:integration:email_helper_deliver:
|
||||||
- bundle exec rails test test/integration/email_helper_test.rb
|
- bundle exec rails test test/integration/email_helper_test.rb
|
||||||
- bundle exec rails test test/integration/email_deliver_test.rb
|
- bundle exec rails test test/integration/email_deliver_test.rb
|
||||||
- bundle exec rails test test/integration/email_keep_on_server_test.rb
|
- bundle exec rails test test/integration/email_keep_on_server_test.rb
|
||||||
|
- bundle exec rails test test/integration/email_postmaster_to_sender.rb
|
||||||
|
|
||||||
test:integration:facebook:
|
test:integration:facebook:
|
||||||
<<: *test_integration_definition
|
<<: *test_integration_definition
|
||||||
|
|
|
@ -206,6 +206,7 @@ example
|
||||||
count = 0
|
count = 0
|
||||||
count_fetched = 0
|
count_fetched = 0
|
||||||
count_max = 5000
|
count_max = 5000
|
||||||
|
too_large_messages = []
|
||||||
active_check_interval = 20
|
active_check_interval = 20
|
||||||
notice = ''
|
notice = ''
|
||||||
message_ids.each do |message_id|
|
message_ids.each do |message_id|
|
||||||
|
@ -226,13 +227,6 @@ example
|
||||||
# ignore verify messages
|
# ignore verify messages
|
||||||
next if !messages_is_too_old_verify?(message_meta, count, count_all)
|
next if !messages_is_too_old_verify?(message_meta, count, count_all)
|
||||||
|
|
||||||
# ignore to big messages
|
|
||||||
info = too_big?(message_meta, count, count_all)
|
|
||||||
if info
|
|
||||||
notice += "#{info}\n"
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
# ignore deleted messages
|
# ignore deleted messages
|
||||||
next if deleted?(message_meta, count, count_all)
|
next if deleted?(message_meta, count, count_all)
|
||||||
|
|
||||||
|
@ -251,7 +245,24 @@ example
|
||||||
end
|
end
|
||||||
next if !msg
|
next if !msg
|
||||||
|
|
||||||
process(channel, msg, false)
|
# do not process too big messages, instead download & send postmaster reply
|
||||||
|
too_large_info = too_large?(message_meta)
|
||||||
|
if too_large_info
|
||||||
|
if Setting.get('postmaster_send_reject_if_mail_too_large') == true
|
||||||
|
info = " - download message #{count}/#{count_all} - ignore message because it's too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
|
||||||
|
Rails.logger.info info
|
||||||
|
notice += "#{info}\n"
|
||||||
|
process_oversized_mail(channel, msg)
|
||||||
|
else
|
||||||
|
info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{too_large_info[0]} MB/max:#{too_large_info[1]} MB)"
|
||||||
|
Rails.logger.info info
|
||||||
|
notice += "#{info}\n"
|
||||||
|
too_large_messages.push info
|
||||||
|
next
|
||||||
|
end
|
||||||
|
else
|
||||||
|
process(channel, msg, false)
|
||||||
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
timeout(FETCH_MSG_TIMEOUT) do
|
timeout(FETCH_MSG_TIMEOUT) do
|
||||||
|
@ -282,6 +293,11 @@ example
|
||||||
if count.zero?
|
if count.zero?
|
||||||
Rails.logger.info ' - no message'
|
Rails.logger.info ' - no message'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if too_large_messages.present?
|
||||||
|
raise too_large_messages.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
Rails.logger.info 'done'
|
Rails.logger.info 'done'
|
||||||
{
|
{
|
||||||
result: 'ok',
|
result: 'ok',
|
||||||
|
@ -420,7 +436,7 @@ returns
|
||||||
|
|
||||||
check if email is to big
|
check if email is to big
|
||||||
|
|
||||||
Channel::Driver::IMAP.too_big?(message_meta, count, count_all)
|
Channel::Driver::IMAP.too_large?(message_meta, count, count_all)
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
||||||
|
@ -428,14 +444,13 @@ returns
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def too_big?(message_meta, count, count_all)
|
def too_large?(message_meta)
|
||||||
max_message_size = Setting.get('postmaster_max_size').to_f
|
max_message_size = Setting.get('postmaster_max_size').to_f
|
||||||
real_message_size = message_meta.attr['RFC822.SIZE'].to_f / 1024 / 1024
|
real_message_size = message_meta.attr['RFC822.SIZE'].to_f / 1024 / 1024
|
||||||
if real_message_size > max_message_size
|
if real_message_size > max_message_size
|
||||||
info = " - ignore message #{count}/#{count_all} - because message is too big (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
return [real_message_size, max_message_size]
|
||||||
Rails.logger.info info
|
|
||||||
return info
|
|
||||||
end
|
end
|
||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,7 @@ returns
|
||||||
count_all = mails.size
|
count_all = mails.size
|
||||||
count = 0
|
count = 0
|
||||||
count_fetched = 0
|
count_fetched = 0
|
||||||
|
too_large_messages = []
|
||||||
active_check_interval = 20
|
active_check_interval = 20
|
||||||
notice = ''
|
notice = ''
|
||||||
mails.first(2000).each do |m|
|
mails.first(2000).each do |m|
|
||||||
|
@ -165,18 +166,28 @@ returns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ignore to big messages
|
# do not process too large messages, instead download and send postmaster reply
|
||||||
max_message_size = Setting.get('postmaster_max_size').to_f
|
max_message_size = Setting.get('postmaster_max_size').to_f
|
||||||
real_message_size = mail.size.to_f / 1024 / 1024
|
real_message_size = mail.size.to_f / 1024 / 1024
|
||||||
if real_message_size > max_message_size
|
if real_message_size > max_message_size
|
||||||
info = " - ignore message #{count}/#{count_all} - because message is too big (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
if Setting.get('postmaster_send_reject_if_mail_too_large') == true
|
||||||
Rails.logger.info info
|
info = " - download message #{count}/#{count_all} - ignore message because it's too large (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
||||||
notice += "#{info}\n"
|
Rails.logger.info info
|
||||||
next
|
notice += "#{info}\n"
|
||||||
end
|
process_oversized_mail(channel, mail)
|
||||||
|
else
|
||||||
|
info = " - ignore message #{count}/#{count_all} - because message is too large (is:#{real_message_size} MB/max:#{max_message_size} MB)"
|
||||||
|
Rails.logger.info info
|
||||||
|
notice += "#{info}\n"
|
||||||
|
too_large_messages.push info
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
# delete email from server after article was created
|
# delete email from server after article was created
|
||||||
process(channel, m.pop, false)
|
else
|
||||||
|
process(channel, m.pop, false)
|
||||||
|
end
|
||||||
|
|
||||||
m.delete
|
m.delete
|
||||||
count_fetched += 1
|
count_fetched += 1
|
||||||
end
|
end
|
||||||
|
@ -184,6 +195,11 @@ returns
|
||||||
if count.zero?
|
if count.zero?
|
||||||
Rails.logger.info ' - no message'
|
Rails.logger.info ' - no message'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if too_large_messages.present?
|
||||||
|
raise too_large_messages.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
Rails.logger.info 'done'
|
Rails.logger.info 'done'
|
||||||
{
|
{
|
||||||
result: 'ok',
|
result: 'ok',
|
||||||
|
|
|
@ -116,18 +116,15 @@ returns
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
# store unprocessable email for bug reporting
|
# store unprocessable email for bug reporting
|
||||||
path = Rails.root.join('tmp', 'unprocessable_mail')
|
filename = archive_mail('unprocessable_mail', msg)
|
||||||
FileUtils.mkpath path
|
|
||||||
md5 = Digest::MD5.hexdigest(msg)
|
|
||||||
filename = "#{path}/#{md5}.eml"
|
|
||||||
message = "ERROR: Can't process email, you will find it for bug reporting under #{filename}, please create an issue at https://github.com/zammad/zammad/issues"
|
message = "ERROR: Can't process email, you will find it for bug reporting under #{filename}, please create an issue at https://github.com/zammad/zammad/issues"
|
||||||
|
|
||||||
p message # rubocop:disable Rails/Output
|
p message # rubocop:disable Rails/Output
|
||||||
p 'ERROR: ' + e.inspect # rubocop:disable Rails/Output
|
p 'ERROR: ' + e.inspect # rubocop:disable Rails/Output
|
||||||
Rails.logger.error message
|
Rails.logger.error message
|
||||||
Rails.logger.error e
|
Rails.logger.error e
|
||||||
File.open(filename, 'wb') do |file|
|
|
||||||
file.write msg
|
|
||||||
end
|
|
||||||
return false if exception == false
|
return false if exception == false
|
||||||
|
|
||||||
raise e.inspect + "\n" + e.backtrace.join("\n")
|
raise e.inspect + "\n" + e.backtrace.join("\n")
|
||||||
|
@ -486,6 +483,19 @@ process unprocessable_mails (tmp/unprocessable_mail/*.eml) again
|
||||||
files
|
files
|
||||||
end
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
process oversized emails by:
|
||||||
|
1. Archiving the oversized mail as tmp/oversized_mail/timestamp_md5.eml
|
||||||
|
2. Reply with a postmaster message to inform the sender
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def process_oversized_mail(channel, msg)
|
||||||
|
archive_mail('oversized_mail', msg)
|
||||||
|
postmaster_response(channel, msg)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def message_header_hash(mail)
|
def message_header_hash(mail)
|
||||||
|
@ -783,6 +793,70 @@ process unprocessable_mails (tmp/unprocessable_mail/*.eml) again
|
||||||
|
|
||||||
[attach]
|
[attach]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Archive the given message as tmp/folder/timestamp_md5.eml
|
||||||
|
def archive_mail(folder, msg)
|
||||||
|
path = Rails.root.join('tmp', folder)
|
||||||
|
FileUtils.mkpath path
|
||||||
|
|
||||||
|
# MD5 hash the msg and save it as "timestamp_md5.eml"
|
||||||
|
md5 = Digest::MD5.hexdigest(msg)
|
||||||
|
filename = "#{Time.zone.now.iso8601}_#{md5}.eml"
|
||||||
|
file_path = Rails.root.join('tmp', folder, filename)
|
||||||
|
|
||||||
|
File.open(file_path, 'wb') do |file|
|
||||||
|
file.write msg
|
||||||
|
end
|
||||||
|
|
||||||
|
file_path
|
||||||
|
end
|
||||||
|
|
||||||
|
# Auto reply as the postmaster to oversized emails with:
|
||||||
|
# [ALERT] Message too large
|
||||||
|
def postmaster_response(channel, msg)
|
||||||
|
begin
|
||||||
|
reply_mail = compose_postmaster_reply(msg)
|
||||||
|
rescue NotificationFactory::FileNotFoundError => e
|
||||||
|
Rails.logger.error 'No valid postmaster email_oversized template found. Skipping postmaster reply. ' + e.inspect
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.logger.error "Send mail too large postmaster message to: #{reply_mail[:to]}"
|
||||||
|
reply_mail[:from] = EmailAddress.find_by(channel: channel).email
|
||||||
|
channel.deliver(reply_mail)
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error "Error during sending of postmaster oversized email auto-reply: #{e.inspect}\n#{e.backtrace}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compose a "Message too large" reply to the given message
|
||||||
|
def compose_postmaster_reply(raw_incoming_mail, locale = nil)
|
||||||
|
parsed_incoming_mail = Channel::EmailParser.new.parse(raw_incoming_mail)
|
||||||
|
|
||||||
|
# construct a dummy mail object
|
||||||
|
mail = OpenStruct.new
|
||||||
|
mail.from_display_name = parsed_incoming_mail[:from_display_name]
|
||||||
|
mail.subject = parsed_incoming_mail[:subject]
|
||||||
|
mail.msg_size = format('%.2f', raw_incoming_mail.size.to_f / 1024 / 1024)
|
||||||
|
|
||||||
|
reply = NotificationFactory::Mailer.template(
|
||||||
|
template: 'email_oversized',
|
||||||
|
locale: locale,
|
||||||
|
format: 'txt',
|
||||||
|
objects: {
|
||||||
|
mail: mail,
|
||||||
|
},
|
||||||
|
raw: true, # will not add application template
|
||||||
|
standalone: true, # default: false - will send header & footer
|
||||||
|
)
|
||||||
|
|
||||||
|
reply.merge(
|
||||||
|
to: parsed_incoming_mail[:from_email],
|
||||||
|
body: reply[:body].gsub(/\n/, "\r\n"),
|
||||||
|
content_type: 'text/plain',
|
||||||
|
References: parsed_incoming_mail[:message_id],
|
||||||
|
'In-Reply-To': parsed_incoming_mail[:message_id],
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module Mail
|
module Mail
|
||||||
|
|
12
app/views/mailer/email_oversized/de.txt.erb
Normal file
12
app/views/mailer/email_oversized/de.txt.erb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[Unzustellbar] Nachricht zu groß
|
||||||
|
Hallo #{mail.from_display_name},
|
||||||
|
|
||||||
|
Ihre E-Mail mit dem Betreff "#{mail.subject}" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||||
|
|
||||||
|
Die Nachricht hatte eine Größe von #{mail.msg_size} MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu #{config.postmaster_max_size} MB.
|
||||||
|
|
||||||
|
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
|
|
||||||
|
Postmaster von #{config.fqdn}
|
12
app/views/mailer/email_oversized/en.txt.erb
Normal file
12
app/views/mailer/email_oversized/en.txt.erb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[ALERT] Message too large
|
||||||
|
Dear #{mail.from_display_name},
|
||||||
|
|
||||||
|
Unfortunately your email titled "#{mail.subject}" could not be delivered to one or more recipients.
|
||||||
|
|
||||||
|
Your message was #{mail.msg_size} MB but we only accept messages up to #{config.postmaster_max_size} MB.
|
||||||
|
|
||||||
|
Please reduce the message size and try again. Thank you for your understanding.
|
||||||
|
|
||||||
|
Regretfully,
|
||||||
|
|
||||||
|
Postmaster of #{config.fqdn}
|
|
@ -0,0 +1,34 @@
|
||||||
|
class SettingPostmasterSendRejectEmail < ActiveRecord::Migration[5.2]
|
||||||
|
def up
|
||||||
|
|
||||||
|
# return if it's a new setup
|
||||||
|
return if !Setting.find_by(name: 'system_init_done')
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Send postmaster mail if mail too large',
|
||||||
|
name: 'postmaster_send_reject_if_mail_too_large',
|
||||||
|
area: 'Email::Base',
|
||||||
|
description: 'Send postmaster reject mail to sender of mail if mail is too large.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'postmaster_send_reject_if_mail_too_large',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
true => 'yes',
|
||||||
|
false => 'no',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: true,
|
||||||
|
preferences: {
|
||||||
|
online_service_disable: true,
|
||||||
|
permission: ['admin.channel_email'],
|
||||||
|
},
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -2582,6 +2582,33 @@ Setting.create_if_not_exists(
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Send postmaster mail if mail too large',
|
||||||
|
name: 'postmaster_send_reject_if_mail_too_large',
|
||||||
|
area: 'Email::Base',
|
||||||
|
description: 'Send postmaster reject mail to sender of mail if mail is too large.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'postmaster_send_reject_if_mail_too_large',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
true => 'yes',
|
||||||
|
false => 'no',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: true,
|
||||||
|
preferences: {
|
||||||
|
online_service_disable: true,
|
||||||
|
permission: ['admin.channel_email'],
|
||||||
|
},
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
title: 'Notification Sender',
|
title: 'Notification Sender',
|
||||||
name: 'notification_sender',
|
name: 'notification_sender',
|
||||||
|
|
|
@ -29,17 +29,26 @@ returns
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
|
class FileNotFoundError < StandardError; end
|
||||||
|
|
||||||
def self.template_read(data)
|
def self.template_read(data)
|
||||||
template = File.readlines(template_path(data))
|
template_path = template_path(data)
|
||||||
|
|
||||||
|
template = File.readlines(template_path)
|
||||||
|
|
||||||
{ subject: template.shift, body: template.join }
|
{ subject: template.shift, body: template.join }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.template_path(data)
|
def self.template_path(data)
|
||||||
template_filenames(data)
|
candidates = template_filenames(data)
|
||||||
.map { |filename| data.merge(filename: filename) }
|
.map { |filename| data.merge(filename: filename) }
|
||||||
.map { |data_hash| TEMPLATE_PATH_STRING % data_hash }
|
.map { |data_hash| TEMPLATE_PATH_STRING % data_hash }
|
||||||
.find(&File.method(:exist?))
|
|
||||||
|
found = candidates.find(&File.method(:exist?))
|
||||||
|
|
||||||
|
raise FileNotFoundError, "Missing template files #{candidates}!" if !found
|
||||||
|
|
||||||
|
found
|
||||||
end
|
end
|
||||||
private_class_method :template_path
|
private_class_method :template_path
|
||||||
|
|
||||||
|
|
|
@ -281,7 +281,7 @@ returns
|
||||||
template = NotificationFactory.template_read(
|
template = NotificationFactory.template_read(
|
||||||
locale: data[:locale] || Setting.get('locale_default') || 'en-us',
|
locale: data[:locale] || Setting.get('locale_default') || 'en-us',
|
||||||
template: data[:template],
|
template: data[:template],
|
||||||
format: 'html',
|
format: data[:format] || 'html',
|
||||||
type: 'mailer',
|
type: 'mailer',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -292,6 +292,10 @@ returns
|
||||||
template: template[:subject],
|
template: template[:subject],
|
||||||
escape: false
|
escape: false
|
||||||
).render
|
).render
|
||||||
|
|
||||||
|
# strip off the extra newline at the end for the subjects of plaintext templates
|
||||||
|
message_subject.chomp! if data[:format] == 'txt'
|
||||||
|
|
||||||
message_body = NotificationFactory::Renderer.new(
|
message_body = NotificationFactory::Renderer.new(
|
||||||
objects: data[:objects],
|
objects: data[:objects],
|
||||||
locale: data[:locale],
|
locale: data[:locale],
|
||||||
|
|
93
spec/lib/notification_factory/mailer_spec.rb
Normal file
93
spec/lib/notification_factory/mailer_spec.rb
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
require 'ostruct'
|
||||||
|
|
||||||
|
RSpec.describe NotificationFactory::Mailer do
|
||||||
|
describe '#template' do
|
||||||
|
context 'for postmaster oversized mail' do
|
||||||
|
let(:raw_incoming_mail) { File.read(Rails.root.join('test', 'data', 'mail', 'mail010.box')) }
|
||||||
|
|
||||||
|
let(:parsed_incoming_mail) { Channel::EmailParser.new.parse raw_incoming_mail }
|
||||||
|
|
||||||
|
let(:incoming_mail) do
|
||||||
|
mail = OpenStruct.new
|
||||||
|
mail.from_display_name = parsed_incoming_mail[:from_display_name]
|
||||||
|
mail.subject = parsed_incoming_mail[:subject]
|
||||||
|
mail.msg_size = format('%.2f', raw_incoming_mail.size.to_f / 1024 / 1024)
|
||||||
|
mail
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:en_expected_subject) { '[ALERT] Message too large' }
|
||||||
|
|
||||||
|
let(:en_expected_body) do
|
||||||
|
<<~BODY
|
||||||
|
Dear Smith Sepp,
|
||||||
|
|
||||||
|
Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
|
||||||
|
|
||||||
|
Your message was 0.01 MB but we only accept messages up to 10 MB.
|
||||||
|
|
||||||
|
Please reduce the message size and try again. Thank you for your understanding.
|
||||||
|
|
||||||
|
Regretfully,
|
||||||
|
|
||||||
|
Postmaster of zammad.example.com
|
||||||
|
BODY
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'plaintext mail templating' do
|
||||||
|
it 'templates correctly' do
|
||||||
|
result = described_class.template(
|
||||||
|
template: 'email_oversized',
|
||||||
|
locale: locale,
|
||||||
|
format: 'txt',
|
||||||
|
objects: {
|
||||||
|
mail: incoming_mail,
|
||||||
|
},
|
||||||
|
raw: true, # will not add application template
|
||||||
|
standalone: true, # default: false - will send header & footer
|
||||||
|
)
|
||||||
|
expect(result[:subject]).to eq(expected_subject)
|
||||||
|
expect(result[:body]).to eq(expected_body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'English locale (en)' do
|
||||||
|
include_examples 'plaintext mail templating' do
|
||||||
|
let(:locale) { 'en' }
|
||||||
|
let(:expected_subject) { en_expected_subject }
|
||||||
|
let(:expected_body) { en_expected_body }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'German locale (de)' do
|
||||||
|
include_examples 'plaintext mail templating' do
|
||||||
|
let(:locale) { 'de' }
|
||||||
|
let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
|
||||||
|
let(:expected_body) do
|
||||||
|
<<~BODY
|
||||||
|
Hallo Smith Sepp,
|
||||||
|
|
||||||
|
Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||||
|
|
||||||
|
Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
|
||||||
|
|
||||||
|
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
|
|
||||||
|
Postmaster von zammad.example.com
|
||||||
|
BODY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'unsupported locale, which defaults back to English locale (en)' do
|
||||||
|
include_examples 'plaintext mail templating' do
|
||||||
|
let(:locale) { 'UNSUPPORTED_LOCALE' }
|
||||||
|
let(:expected_subject) { en_expected_subject }
|
||||||
|
let(:expected_body) { en_expected_body }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -997,4 +997,124 @@ RSpec.describe Channel::EmailParser, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#compose_postmaster_reply' do
|
||||||
|
let(:raw_incoming_mail) { File.read(Rails.root.join('test', 'data', 'mail', 'mail010.box')) }
|
||||||
|
|
||||||
|
shared_examples 'postmaster reply' do
|
||||||
|
it 'composes postmaster reply' do
|
||||||
|
reply = Channel::EmailParser.new.send(:compose_postmaster_reply, raw_incoming_mail, locale)
|
||||||
|
expect(reply[:to]).to eq('smith@example.com')
|
||||||
|
expect(reply[:content_type]).to eq('text/plain')
|
||||||
|
expect(reply[:subject]).to eq(expected_subject)
|
||||||
|
expect(reply[:body]).to eq(expected_body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for English locale (en)' do
|
||||||
|
include_examples 'postmaster reply' do
|
||||||
|
let(:locale) { 'en' }
|
||||||
|
let(:expected_subject) { '[ALERT] Message too large' }
|
||||||
|
let(:expected_body) do
|
||||||
|
body = <<~BODY
|
||||||
|
Dear Smith Sepp,
|
||||||
|
|
||||||
|
Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
|
||||||
|
|
||||||
|
Your message was 0.01 MB but we only accept messages up to 10 MB.
|
||||||
|
|
||||||
|
Please reduce the message size and try again. Thank you for your understanding.
|
||||||
|
|
||||||
|
Regretfully,
|
||||||
|
|
||||||
|
Postmaster of zammad.example.com
|
||||||
|
BODY
|
||||||
|
body.gsub(/\n/, "\r\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for German locale (de)' do
|
||||||
|
include_examples 'postmaster reply' do
|
||||||
|
let(:locale) { 'de' }
|
||||||
|
let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
|
||||||
|
let(:expected_body) do
|
||||||
|
body = <<~BODY
|
||||||
|
Hallo Smith Sepp,
|
||||||
|
|
||||||
|
Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||||
|
|
||||||
|
Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
|
||||||
|
|
||||||
|
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
|
|
||||||
|
Postmaster von zammad.example.com
|
||||||
|
BODY
|
||||||
|
body.gsub(/\n/, "\r\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#compose_postmaster_reply' do
|
||||||
|
let(:raw_incoming_mail) { File.read(Rails.root.join('test', 'data', 'mail', 'mail010.box')) }
|
||||||
|
|
||||||
|
shared_examples 'postmaster reply' do
|
||||||
|
it 'composes postmaster reply' do
|
||||||
|
reply = Channel::EmailParser.new.send(:compose_postmaster_reply, raw_incoming_mail, locale)
|
||||||
|
expect(reply[:to]).to eq('smith@example.com')
|
||||||
|
expect(reply[:content_type]).to eq('text/plain')
|
||||||
|
expect(reply[:subject]).to eq(expected_subject)
|
||||||
|
expect(reply[:body]).to eq(expected_body)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for English locale (en)' do
|
||||||
|
include_examples 'postmaster reply' do
|
||||||
|
let(:locale) { 'en' }
|
||||||
|
let(:expected_subject) { '[ALERT] Message too large' }
|
||||||
|
let(:expected_body) do
|
||||||
|
body = <<~BODY
|
||||||
|
Dear Smith Sepp,
|
||||||
|
|
||||||
|
Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
|
||||||
|
|
||||||
|
Your message was 0.01 MB but we only accept messages up to 10 MB.
|
||||||
|
|
||||||
|
Please reduce the message size and try again. Thank you for your understanding.
|
||||||
|
|
||||||
|
Regretfully,
|
||||||
|
|
||||||
|
Postmaster of zammad.example.com
|
||||||
|
BODY
|
||||||
|
body.gsub(/\n/, "\r\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for German locale (de)' do
|
||||||
|
include_examples 'postmaster reply' do
|
||||||
|
let(:locale) { 'de' }
|
||||||
|
let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
|
||||||
|
let(:expected_body) do
|
||||||
|
body = <<~BODY
|
||||||
|
Hallo Smith Sepp,
|
||||||
|
|
||||||
|
Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
|
||||||
|
|
||||||
|
Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
|
||||||
|
|
||||||
|
Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen
|
||||||
|
|
||||||
|
Postmaster von zammad.example.com
|
||||||
|
BODY
|
||||||
|
body.gsub(/\n/, "\r\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
213
test/integration/email_postmaster_to_sender.rb
Normal file
213
test/integration/email_postmaster_to_sender.rb
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
require 'test_helper'
|
||||||
|
require 'net/imap'
|
||||||
|
|
||||||
|
class EmailPostmasterToSender < ActiveSupport::TestCase
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Setting.set('postmaster_max_size', 0.1)
|
||||||
|
|
||||||
|
@test_id = rand(999_999_999)
|
||||||
|
|
||||||
|
# setup the IMAP account info for Zammad
|
||||||
|
if ENV['MAIL_SERVER'].blank?
|
||||||
|
raise "Need MAIL_SERVER as ENV variable like export MAIL_SERVER='mx.example.com'"
|
||||||
|
end
|
||||||
|
if ENV['MAIL_SERVER_ACCOUNT'].blank?
|
||||||
|
raise "Need MAIL_SERVER_ACCOUNT as ENV variable like export MAIL_SERVER_ACCOUNT='user:somepass'"
|
||||||
|
end
|
||||||
|
|
||||||
|
@server_address = ENV['MAIL_SERVER']
|
||||||
|
@server_login = ENV['MAIL_SERVER_ACCOUNT'].split(':')[0]
|
||||||
|
@server_password = ENV['MAIL_SERVER_ACCOUNT'].split(':')[1]
|
||||||
|
|
||||||
|
@folder = "postmaster_to_sender_#{@test_id}"
|
||||||
|
|
||||||
|
if ENV['MAIL_SERVER_EMAIL'].blank?
|
||||||
|
raise "Need MAIL_SERVER_EMAIL as ENV variable like export MAIL_SERVER_EMAIL='master@example.com'"
|
||||||
|
end
|
||||||
|
|
||||||
|
@sender_email_address = ENV['MAIL_SERVER_EMAIL']
|
||||||
|
|
||||||
|
@email_address = EmailAddress.create!(
|
||||||
|
realname: 'me Helpdesk',
|
||||||
|
email: "me#{@test_id}@example.com",
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
group = Group.create_or_update(
|
||||||
|
name: 'PostmasterToSenderTest',
|
||||||
|
email_address_id: @email_address.id,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
@channel = Channel.create!(
|
||||||
|
area: 'Email::Account',
|
||||||
|
group_id: group.id,
|
||||||
|
options: {
|
||||||
|
inbound: {
|
||||||
|
adapter: 'imap',
|
||||||
|
options: {
|
||||||
|
host: @server_address,
|
||||||
|
user: @server_login,
|
||||||
|
password: @server_password,
|
||||||
|
ssl: true,
|
||||||
|
folder: @folder,
|
||||||
|
keep_on_server: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outbound: {
|
||||||
|
adapter: 'smtp',
|
||||||
|
options: {
|
||||||
|
host: @server_address,
|
||||||
|
port: 25,
|
||||||
|
start_tls: true,
|
||||||
|
user: @server_login,
|
||||||
|
password: @server_password,
|
||||||
|
email: @email_address.email
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
active: true,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
@email_address.channel_id = @channel.id
|
||||||
|
@email_address.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'postmaster reply with email on oversized incoming emails' do
|
||||||
|
imap = Net::IMAP.new(@server_address, 993, true, nil, false)
|
||||||
|
imap.login(@server_login, @server_password)
|
||||||
|
imap.create(@folder)
|
||||||
|
imap.select(@folder)
|
||||||
|
|
||||||
|
# put a very large message in it
|
||||||
|
large_message = "Subject: Oversized Email Message
|
||||||
|
From: Max Mustermann <#{@sender_email_address}>
|
||||||
|
To: shugo@example.com
|
||||||
|
Message-ID: <#{@test_id}@zammad.test.com>
|
||||||
|
|
||||||
|
Oversized Email Message Body #{'#' * 120_000}
|
||||||
|
".gsub(/\n/, "\r\n")
|
||||||
|
|
||||||
|
large_message_md5 = Digest::MD5.hexdigest(large_message)
|
||||||
|
large_message_size = format('%.2f', large_message.size.to_f / 1024 / 1024)
|
||||||
|
|
||||||
|
imap.append(@folder, large_message, [], Time.zone.now)
|
||||||
|
|
||||||
|
@channel.fetch(true)
|
||||||
|
|
||||||
|
# 1. verify that the oversized email has been saved locally to:
|
||||||
|
# /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
|
||||||
|
path = Rails.root.join('tmp', 'oversized_mail')
|
||||||
|
target_files = Dir.entries(path).select do |filename|
|
||||||
|
filename =~ /^.+_#{large_message_md5}\.eml$/
|
||||||
|
end
|
||||||
|
assert(target_files.present?, 'Large message .eml log file must be present.')
|
||||||
|
|
||||||
|
# pick the latest file that matches the criteria
|
||||||
|
target_file = target_files.max
|
||||||
|
|
||||||
|
# verify that the file is byte for byte identical to the sent message
|
||||||
|
file_path = Rails.root.join('tmp', 'oversized_mail', target_file)
|
||||||
|
eml_data = File.read(file_path)
|
||||||
|
assert_equal(large_message, eml_data)
|
||||||
|
|
||||||
|
# 2. verify that a postmaster response email has been sent to the sender
|
||||||
|
imap.select('inbox')
|
||||||
|
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||||
|
assert(message_ids.count.positive?, 'Must have received a reply from the postmaster')
|
||||||
|
imap_message_id = message_ids.last
|
||||||
|
msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
|
||||||
|
assert(msg.present?, 'Must have received a reply from the postmaster')
|
||||||
|
imap.store(imap_message_id, '+FLAGS', [:Deleted])
|
||||||
|
imap.expunge()
|
||||||
|
|
||||||
|
# parse the reply mail and verify the various headers
|
||||||
|
parser = Channel::EmailParser.new
|
||||||
|
mail = parser.parse(msg)
|
||||||
|
assert_equal(mail[:from_email], @email_address.email)
|
||||||
|
assert_equal(mail[:subject], '[ALERT] Message too large')
|
||||||
|
assert_equal("<#{@test_id}@zammad.test.com>",
|
||||||
|
mail['references'],
|
||||||
|
'Reply\'s Referecnes header must match the send message ID')
|
||||||
|
assert_equal("<#{@test_id}@zammad.test.com>",
|
||||||
|
mail['in-reply-to'],
|
||||||
|
'Reply\'s In-Reply-To header must match the send message ID')
|
||||||
|
|
||||||
|
# verify the reply mail body content
|
||||||
|
body = mail[:body]
|
||||||
|
assert(body.start_with?('Dear Max Mustermann'), 'Body must contain sender name')
|
||||||
|
assert(body.include?('Oversized Email Message'), 'Body must contain original subject')
|
||||||
|
assert(body.include?('0.1 MB'), 'Body must contain max allowed message size')
|
||||||
|
assert(body.include?("#{large_message_size} MB"), 'Body must contain the original message size')
|
||||||
|
assert(body.include?(Setting.get('fqdn')), 'Body must contain the Zammad instance name')
|
||||||
|
|
||||||
|
# 3. check if original mail got removed
|
||||||
|
imap.select(@folder)
|
||||||
|
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||||
|
assert_equal(message_ids.count, 0, 'Original customer mail must be deleted.')
|
||||||
|
|
||||||
|
# final clean up
|
||||||
|
imap.delete(@folder)
|
||||||
|
@channel.destroy!
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'postmaster reply with no email on oversized incoming emails' do
|
||||||
|
Setting.set('postmaster_send_reject_if_mail_too_large', false)
|
||||||
|
imap = Net::IMAP.new(@server_address, 993, true, nil, false)
|
||||||
|
imap.login(@server_login, @server_password)
|
||||||
|
|
||||||
|
imap.select('inbox')
|
||||||
|
message_count = imap.sort(['DATE'], ['ALL'], 'US-ASCII').count
|
||||||
|
|
||||||
|
imap.create(@folder)
|
||||||
|
imap.select(@folder)
|
||||||
|
|
||||||
|
# put a very large message in it
|
||||||
|
large_message = "Subject: Oversized Email Message
|
||||||
|
From: Max Mustermann <#{@sender_email_address}>
|
||||||
|
To: shugo@example.com
|
||||||
|
Message-ID: <#{@test_id}@zammad.test.com>
|
||||||
|
|
||||||
|
Oversized Email Message Body #{'#' * 120_000}
|
||||||
|
".gsub(/\n/, "\r\n")
|
||||||
|
|
||||||
|
imap.append(@folder, large_message, [], Time.zone.now)
|
||||||
|
|
||||||
|
@channel.fetch(true)
|
||||||
|
|
||||||
|
# 1. verify that the oversized email has been saved locally to:
|
||||||
|
# /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
|
||||||
|
path = Rails.root.join('tmp', 'oversized_mail')
|
||||||
|
target_files = Dir.entries(path).select do |filename|
|
||||||
|
filename =~ /^.+?\.eml$/
|
||||||
|
end
|
||||||
|
assert_not(target_files.blank?, 'Large message .eml log file must be blank.')
|
||||||
|
|
||||||
|
# 2. verify that a postmaster response email has been sent to the sender
|
||||||
|
imap.select('inbox')
|
||||||
|
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||||
|
assert_equal(message_ids.count, message_count, 'Must not have received a reply from the postmaster')
|
||||||
|
|
||||||
|
# 3. check if original mail got removed
|
||||||
|
imap.select(@folder)
|
||||||
|
message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
|
||||||
|
imap_message_id = message_ids.last
|
||||||
|
msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
|
||||||
|
imap.store(imap_message_id, '+FLAGS', [:Deleted])
|
||||||
|
imap.expunge()
|
||||||
|
assert(msg.present?, 'Oversized Email Message')
|
||||||
|
assert_equal(message_ids.count, 1, 'Original customer mail must be deleted.')
|
||||||
|
|
||||||
|
# final clean up
|
||||||
|
imap.delete(@folder)
|
||||||
|
@channel.destroy!
|
||||||
|
end
|
||||||
|
|
||||||
|
teardown do
|
||||||
|
Setting.set('postmaster_max_size', 10)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue