Move SMTP server from messages plugin into core
This commit is contained in:
parent
2264351cf5
commit
7d594bf391
9 changed files with 117 additions and 92 deletions
1
Gemfile
1
Gemfile
|
@ -48,6 +48,7 @@ gem 'roo-xls'
|
||||||
gem 'spreadsheet'
|
gem 'spreadsheet'
|
||||||
gem 'gaffe'
|
gem 'gaffe'
|
||||||
gem 'ruby-filemagic'
|
gem 'ruby-filemagic'
|
||||||
|
gem 'midi-smtp-server'
|
||||||
|
|
||||||
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
# we use the git version of acts_as_versioned, and need to include it in this Gemfile
|
||||||
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
|
gem 'acts_as_versioned', git: 'https://github.com/technoweenie/acts_as_versioned.git'
|
||||||
|
|
|
@ -24,9 +24,7 @@ PATH
|
||||||
foodsoft_messages (0.0.1)
|
foodsoft_messages (0.0.1)
|
||||||
base32
|
base32
|
||||||
deface (~> 1.0)
|
deface (~> 1.0)
|
||||||
gserver
|
|
||||||
mail
|
mail
|
||||||
mini-smtp-server
|
|
||||||
rails
|
rails
|
||||||
|
|
||||||
PATH
|
PATH
|
||||||
|
@ -172,7 +170,6 @@ GEM
|
||||||
git-version-bump (0.15.1)
|
git-version-bump (0.15.1)
|
||||||
globalid (0.3.7)
|
globalid (0.3.7)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
gserver (0.0.1)
|
|
||||||
haml (4.0.7)
|
haml (4.0.7)
|
||||||
tilt
|
tilt
|
||||||
haml-rails (0.9.0)
|
haml-rails (0.9.0)
|
||||||
|
@ -240,10 +237,10 @@ GEM
|
||||||
rack-contrib (~> 1.1)
|
rack-contrib (~> 1.1)
|
||||||
railties (>= 3.0.0, < 5.1.0)
|
railties (>= 3.0.0, < 5.1.0)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
|
midi-smtp-server (2.1.2)
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mini-smtp-server (0.0.2)
|
|
||||||
mini_portile2 (2.2.0)
|
mini_portile2 (2.2.0)
|
||||||
minitest (5.10.1)
|
minitest (5.10.1)
|
||||||
mono_logger (1.1.0)
|
mono_logger (1.1.0)
|
||||||
|
@ -518,6 +515,7 @@ DEPENDENCIES
|
||||||
localize_input!
|
localize_input!
|
||||||
mailcatcher
|
mailcatcher
|
||||||
meta_request
|
meta_request
|
||||||
|
midi-smtp-server
|
||||||
mysql2
|
mysql2
|
||||||
prawn
|
prawn
|
||||||
prawn-table
|
prawn-table
|
||||||
|
|
45
lib/foodsoft_mail_receiver.rb
Normal file
45
lib/foodsoft_mail_receiver.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
require 'mail'
|
||||||
|
require 'midi-smtp-server'
|
||||||
|
|
||||||
|
class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
|
||||||
|
|
||||||
|
@@registered_classes = Set.new
|
||||||
|
|
||||||
|
def self.register(klass)
|
||||||
|
@@registered_classes.add klass
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.received(recipient, data)
|
||||||
|
m = /(?<foodcoop>[^@\.]+)\.(?<address>[^@]+)(@(?<hostname>[^@]+))?/.match recipient
|
||||||
|
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]
|
||||||
|
FoodsoftConfig.select_multifoodcoop m[:foodcoop]
|
||||||
|
|
||||||
|
@@registered_classes.each do |klass|
|
||||||
|
klass_m = klass.regexp.match(m[:address])
|
||||||
|
return klass.new.received klass_m, data if klass_m
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "invalid format for recipient"
|
||||||
|
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
|
|
@ -43,6 +43,21 @@ namespace :foodsoft do
|
||||||
rake_say "created until #{created_until}"
|
rake_say "created until #{created_until}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc "Parse incoming email on stdin (options: RECIPIENT=foodcoop.handling)"
|
||||||
|
task :parse_reply_email => :environment do
|
||||||
|
FoodsoftMailReceiver.received ENV['RECIPIENT'], STDIN.read
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Start STMP server for incoming email (options: SMTP_SERVER_PORT=2525, SMTP_SERVER_HOST=0.0.0.0)"
|
||||||
|
task :reply_email_smtp_server => :environment do
|
||||||
|
port = ENV['SMTP_SERVER_PORT'].present? ? ENV['SMTP_SERVER_PORT'].to_i : 2525
|
||||||
|
host = ENV['SMTP_SERVER_HOST']
|
||||||
|
rake_say "Started SMTP server for incoming email on port #{port}."
|
||||||
|
server = FoodsoftMailReceiver.new port, host
|
||||||
|
server.start
|
||||||
|
server.join
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Helper
|
# Helper
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
class MessagesMailReceiver
|
||||||
|
|
||||||
|
def self.regexp
|
||||||
|
/(?<message_id>\d+)\.(?<user_id>\d+)\.(?<hash>\w+)/
|
||||||
|
end
|
||||||
|
|
||||||
|
def received(match, data)
|
||||||
|
original_message = Message.find_by_id(match[:message_id])
|
||||||
|
user = User.find_by_id(match[:user_id])
|
||||||
|
|
||||||
|
raise "Message could not be found" if original_message.nil?
|
||||||
|
raise "User could not be found" if user.nil?
|
||||||
|
|
||||||
|
hash = original_message.mail_hash_for_user user
|
||||||
|
raise "Hash does not match expectations" unless hash.casecmp(match[:hash]) == 0
|
||||||
|
|
||||||
|
mail = Mail.new data
|
||||||
|
|
||||||
|
mail_part = nil
|
||||||
|
if mail.multipart?
|
||||||
|
for part in mail.parts
|
||||||
|
mail_part = part if MIME::Type.simplified(part.content_type) == "text/plain"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
mail_part = mail
|
||||||
|
end
|
||||||
|
|
||||||
|
body = mail_part.body.decoded
|
||||||
|
unless mail_part.content_type_parameters.nil?
|
||||||
|
body = body.force_encoding mail_part.content_type_parameters[:charset]
|
||||||
|
end
|
||||||
|
|
||||||
|
message = user.send_messages.new body: body,
|
||||||
|
group: original_message.group,
|
||||||
|
private: original_message.private,
|
||||||
|
received_email: received_email,
|
||||||
|
subject: mail.subject.gsub("[#{FoodsoftConfig[:name]}] ", "")
|
||||||
|
if original_message.reply_to
|
||||||
|
message.reply_to_message = original_message.reply_to_message
|
||||||
|
else
|
||||||
|
message.reply_to_message = original_message
|
||||||
|
end
|
||||||
|
message.add_recipients original_message.recipients
|
||||||
|
message.add_recipients [original_message.sender]
|
||||||
|
|
||||||
|
message.save!
|
||||||
|
Resque.enqueue(MessageNotifier, FoodsoftConfig.scope, "message_deliver", message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -20,8 +20,6 @@ Gem::Specification.new do |s|
|
||||||
s.add_dependency "base32"
|
s.add_dependency "base32"
|
||||||
s.add_dependency "deface", "~> 1.0"
|
s.add_dependency "deface", "~> 1.0"
|
||||||
s.add_dependency "mail"
|
s.add_dependency "mail"
|
||||||
s.add_dependency "mini-smtp-server"
|
|
||||||
s.add_dependency "gserver"
|
|
||||||
|
|
||||||
s.add_development_dependency "sqlite3"
|
s.add_development_dependency "sqlite3"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require "foodsoft_messages/engine"
|
require "foodsoft_messages/engine"
|
||||||
|
require "foodsoft_messages/mail_receiver"
|
||||||
require "foodsoft_messages/user_link"
|
require "foodsoft_messages/user_link"
|
||||||
require "deface"
|
require "deface"
|
||||||
|
|
||||||
|
|
3
plugins/messages/lib/foodsoft_messages/mail_receiver.rb
Normal file
3
plugins/messages/lib/foodsoft_messages/mail_receiver.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
ActiveSupport.on_load(:after_initialize) do
|
||||||
|
FoodsoftMailReceiver.register MessagesMailReceiver
|
||||||
|
end
|
|
@ -1,86 +0,0 @@
|
||||||
require "mail"
|
|
||||||
require "mini-smtp-server"
|
|
||||||
|
|
||||||
class ReplyEmailSmtpServer < MiniSmtpServer
|
|
||||||
|
|
||||||
def new_message_event(message_hash)
|
|
||||||
m = /<(?<recipient>[^<>]+)>/.match(message_hash[:to])
|
|
||||||
raise "invalid format for RCPT TO" if m.nil?
|
|
||||||
hande_mail(m[:recipient], message_hash[:data])
|
|
||||||
rescue => error
|
|
||||||
rake_say error.message
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace :foodsoft do
|
|
||||||
desc "Parse incoming email on stdin (options: RECIPIENT=f.1.2.a1b2c3d3e5)"
|
|
||||||
task :parse_reply_email => :environment do
|
|
||||||
hande_mail(ENV['RECIPIENT'], STDIN.read)
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Start STMP server for incoming email (options: PORT=25, HOST=0.0.0.0)"
|
|
||||||
task :reply_email_smtp_server => :environment do
|
|
||||||
port = ENV['PORT'].to_i
|
|
||||||
host = ENV['HOST']
|
|
||||||
rake_say "Started SMTP server for incomming email on port #{port}."
|
|
||||||
server = ReplyEmailSmtpServer.new(port, host)
|
|
||||||
server.start
|
|
||||||
server.join
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hande_mail(recipient, received_email)
|
|
||||||
m = /(?<foodcoop>[^@]*)\.(?<message_id>\d+)\.(?<user_id>\d+)\.(?<hash>\w+)(@(?<hostname>.*))?/.match(recipient)
|
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
FoodsoftConfig.select_multifoodcoop m[:foodcoop]
|
|
||||||
original_message = Message.find_by_id(m[:message_id])
|
|
||||||
user = User.find_by_id(m[:user_id])
|
|
||||||
|
|
||||||
raise "Message could not be found" if original_message.nil?
|
|
||||||
raise "User could not be found" if user.nil?
|
|
||||||
|
|
||||||
hash = original_message.mail_hash_for_user user
|
|
||||||
raise "Hash does not match expectations" unless hash.casecmp(m[:hash]) == 0
|
|
||||||
|
|
||||||
mail = Mail.new received_email
|
|
||||||
|
|
||||||
mail_part = nil
|
|
||||||
if mail.multipart?
|
|
||||||
for part in mail.parts
|
|
||||||
mail_part = part if MIME::Type.simplified(part.content_type) == "text/plain"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
mail_part = mail
|
|
||||||
end
|
|
||||||
|
|
||||||
body = mail_part.body.decoded
|
|
||||||
unless mail_part.content_type_parameters.nil?
|
|
||||||
body = body.force_encoding mail_part.content_type_parameters[:charset]
|
|
||||||
end
|
|
||||||
|
|
||||||
message = user.send_messages.new body: body,
|
|
||||||
group: original_message.group,
|
|
||||||
private: original_message.private,
|
|
||||||
received_email: received_email,
|
|
||||||
subject: mail.subject.gsub("[#{FoodsoftConfig[:name]}] ", "")
|
|
||||||
if original_message.reply_to
|
|
||||||
message.reply_to_message = original_message.reply_to_message
|
|
||||||
else
|
|
||||||
message.reply_to_message = original_message
|
|
||||||
end
|
|
||||||
message.add_recipients original_message.recipients
|
|
||||||
message.add_recipients [original_message.sender]
|
|
||||||
|
|
||||||
message.save!
|
|
||||||
Resque.enqueue(MessageNotifier, FoodsoftConfig.scope, "message_deliver", message.id)
|
|
||||||
rake_say "Handled reply email from #{user.display}."
|
|
||||||
end
|
|
||||||
|
|
||||||
# Helper
|
|
||||||
def rake_say(message)
|
|
||||||
puts message unless Rake.application.options.silent
|
|
||||||
end
|
|
Loading…
Reference in a new issue