mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 01:47:22 +08:00
db9d998de3
https://meta.discourse.org/t/improving-mailman-email-parsing/253041 When mirroring a public mailling list which uses mailman, there were some cases where the incoming email was not associated to the proper user. As it happens, for various (undertermined) reasons, the email from the sender is often not in the `From` header but can be in any of the following headers: `Reply-To`, `CC`, `X-Original-From`, `X-MailFrom`. It might be in other headers as well, but those were the ones we found the most reliable.
263 lines
8.7 KiB
Ruby
263 lines
8.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
def process_popmail(popmail)
|
|
begin
|
|
mail_string = popmail.pop
|
|
Email::Receiver.new(mail_string).process
|
|
rescue StandardError
|
|
putc "!"
|
|
else
|
|
putc "."
|
|
end
|
|
end
|
|
|
|
desc "use this task to import a mailbox into Discourse"
|
|
task "emails:import" => :environment do
|
|
begin
|
|
unless SiteSetting.email_in
|
|
puts "ERROR: you should enable the 'email_in' site setting before running this task"
|
|
exit(1)
|
|
end
|
|
|
|
address = ENV["ADDRESS"].presence || "pop.gmail.com"
|
|
port = (ENV["PORT"].presence || 995).to_i
|
|
ssl = (ENV["SSL"].presence || "1") == "1"
|
|
username = ENV["USERNAME"].presence
|
|
password = ENV["PASSWORD"].presence
|
|
|
|
if username.blank?
|
|
puts "ERROR: expecting USERNAME=<username> rake emails:import"
|
|
exit(2)
|
|
elsif password.blank?
|
|
puts "ERROR: expecting PASSWORD=<password> rake emails:import"
|
|
exit(3)
|
|
end
|
|
|
|
RateLimiter.disable
|
|
|
|
mails_left = 1
|
|
pop3 = Net::POP3.new(address, port)
|
|
pop3.enable_ssl(max_version: OpenSSL::SSL::TLS1_2_VERSION) if ssl
|
|
|
|
while mails_left > 0
|
|
pop3.start(username, password) do |pop|
|
|
pop.delete_all { |p| process_popmail(p) }
|
|
mails_left = pop.n_mails
|
|
end
|
|
end
|
|
|
|
puts "Done"
|
|
rescue Net::POPAuthenticationError
|
|
puts "AUTH EXCEPTION: please make sure your credentials are correct."
|
|
exit(10)
|
|
ensure
|
|
RateLimiter.enable
|
|
end
|
|
end
|
|
|
|
desc "Check if SMTP connection is successful and send test message"
|
|
task "emails:test", [:email] => [:environment] do |_, args|
|
|
email = args[:email]
|
|
message = "OK"
|
|
begin
|
|
smtp = Discourse::Application.config.action_mailer.smtp_settings
|
|
|
|
puts <<~TEXT if smtp[:address].match(/smtp\.gmail\.com/)
|
|
#{smtp}
|
|
============================== WARNING ==============================
|
|
|
|
Sending mail with Gmail is a violation of their terms of service.
|
|
|
|
Sending with G Suite might work, but it is not recommended. For information see:
|
|
https://meta.discourse.org/t/discourse-aws-ec2-g-suite-troubleshooting/62931?u=pfaffman
|
|
|
|
========================= CONTINUING TEST ============================
|
|
TEXT
|
|
|
|
puts "Testing sending to #{email} using #{smtp[:address]}:#{smtp[:port]}, username:#{smtp[:user_name]} with #{smtp[:authentication]} auth."
|
|
|
|
# We are not formatting the messages using EmailSettingsExceptionHandler here
|
|
# because we are doing custom messages in the rake task with more details.
|
|
EmailSettingsValidator.validate_smtp(
|
|
host: smtp[:address],
|
|
port: smtp[:port],
|
|
domain: smtp[:domain] || "localhost",
|
|
username: smtp[:user_name],
|
|
password: smtp[:password],
|
|
authentication: smtp[:authentication] || "plain",
|
|
)
|
|
rescue Exception => e
|
|
if e.to_s.match(/execution expired/)
|
|
message = <<~TEXT
|
|
======================================== ERROR ========================================
|
|
Connection to port #{smtp[:port]} failed.
|
|
====================================== SOLUTION =======================================
|
|
The most likely problem is that your server has outgoing SMTP traffic blocked.
|
|
If you are using a service like Mailgun or Sendgrid, try using port 2525.
|
|
=======================================================================================
|
|
TEXT
|
|
elsif e.to_s.match(/530.*STARTTLS/)
|
|
# We can't run a preliminary test with STARTTLS, we'll just try sending the test email.
|
|
message = "OK"
|
|
elsif e.to_s.match(/535/)
|
|
message = <<~TEXT
|
|
======================================== ERROR ========================================
|
|
AUTHENTICATION FAILED
|
|
|
|
#{e}
|
|
|
|
====================================== SOLUTION =======================================
|
|
The most likely problem is that your SMTP username and/or Password is incorrect.
|
|
Check them and try again.
|
|
=======================================================================================
|
|
TEXT
|
|
elsif e.to_s.match(/Connection refused/)
|
|
message = <<~TEXT
|
|
======================================== ERROR ========================================
|
|
CONNECTION REFUSED
|
|
|
|
#{e}
|
|
|
|
====================================== SOLUTION =======================================
|
|
The most likely problem is that you have chosen the wrong port or a network problem is
|
|
blocking access from the Docker container.
|
|
|
|
Check the port and your networking configuration.
|
|
=======================================================================================
|
|
TEXT
|
|
elsif e.to_s.match(/service not known/)
|
|
message = <<~TEXT
|
|
======================================== ERROR ========================================
|
|
SMTP SERVER NOT FOUND
|
|
|
|
#{e}
|
|
|
|
====================================== SOLUTION =======================================
|
|
The most likely problem is that the host name of your SMTP server is incorrect.
|
|
Check it and try again.
|
|
=======================================================================================
|
|
TEXT
|
|
else
|
|
message = <<~TEXT
|
|
======================================== ERROR ========================================
|
|
UNEXPECTED ERROR
|
|
|
|
#{e}
|
|
|
|
====================================== SOLUTION =======================================
|
|
This is not a common error. No recommended solution exists!
|
|
|
|
Please report the exact error message above to https://meta.discourse.org/
|
|
(And a solution, if you find one!)
|
|
=======================================================================================
|
|
TEXT
|
|
end
|
|
end
|
|
if message == "OK"
|
|
puts "SMTP server connection successful."
|
|
else
|
|
puts message
|
|
exit
|
|
end
|
|
begin
|
|
puts "Sending to #{email}. . . "
|
|
email_log = Email::Sender.new(TestMailer.send_test(email), :test_message).send
|
|
case email_log
|
|
when SkippedEmailLog
|
|
puts <<~TEXT
|
|
Mail was not sent.
|
|
|
|
Reason: #{email_log.reason}
|
|
TEXT
|
|
when EmailLog
|
|
puts <<~TEXT
|
|
Mail accepted by SMTP server.
|
|
Message-ID: #{email_log.message_id}
|
|
|
|
If you do not receive the message, check your SPAM folder
|
|
or test again using a service like http://www.mail-tester.com/.
|
|
|
|
If the message is not delivered it is not a problem with Discourse.
|
|
Check the SMTP server logs for the above Message ID to see why it
|
|
failed to deliver the message.
|
|
TEXT
|
|
when nil
|
|
puts <<~TEXT
|
|
Mail was not sent.
|
|
|
|
Verify the status of the `disable_emails` site setting.
|
|
TEXT
|
|
else
|
|
puts <<~TEXT
|
|
SCRIPT BUG: Got back a #{email_log.class}
|
|
#{email_log.inspect}
|
|
|
|
Mail may or may not have been sent. Check the destination mailbox.
|
|
TEXT
|
|
end
|
|
rescue => error
|
|
puts "Sending mail failed."
|
|
puts error.message
|
|
end
|
|
|
|
puts <<~TEXT if SiteSetting.disable_emails != "no"
|
|
|
|
### WARNING
|
|
The `disable_emails` site setting is currently set to #{SiteSetting.disable_emails}.
|
|
Consider changing it to 'no' before performing any further troubleshooting.
|
|
TEXT
|
|
end
|
|
|
|
desc "run this to fix users associated to emails mirrored from a mailman mailing list"
|
|
task "emails:fix_mailman_users" => :environment do
|
|
if !SiteSetting.enable_staged_users
|
|
puts "Please enable staged users first"
|
|
exit 1
|
|
end
|
|
|
|
def find_or_create_user(email, name)
|
|
user = nil
|
|
|
|
User.transaction do
|
|
unless user = User.find_by_email(email)
|
|
username = UserNameSuggester.sanitize_username(name) if name.present?
|
|
username = UserNameSuggester.suggest(username.presence || email)
|
|
name = name.presence || User.suggest_name(email)
|
|
|
|
begin
|
|
user = User.create!(email: email, username: username, name: name, staged: true)
|
|
rescue PG::UniqueViolation, ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
|
|
end
|
|
end
|
|
end
|
|
|
|
user
|
|
end
|
|
|
|
IncomingEmail
|
|
.includes(:user, :post)
|
|
.where("raw LIKE '%X-Mailman-Version: %'")
|
|
.find_each do |ie|
|
|
next unless ie.post.present?
|
|
|
|
mail = Mail.new(ie.raw)
|
|
email, name = Email::Receiver.extract_email_address_and_name_from_mailman(mail)
|
|
|
|
if email.blank? || email == ie.user.email
|
|
putc "."
|
|
elsif new_owner = find_or_create_user(email, name)
|
|
PostOwnerChanger.new(
|
|
post_ids: [ie.post_id],
|
|
topic_id: ie.post.topic_id,
|
|
new_owner: new_owner,
|
|
acting_user: Discourse.system_user,
|
|
skip_revision: true,
|
|
).change_owner!
|
|
putc "#"
|
|
else
|
|
putc "X"
|
|
end
|
|
end
|
|
nil
|
|
end
|