trabajo-afectivo/lib/email_helper/probe.rb

413 lines
12 KiB
Ruby
Raw Normal View History

module EmailHelper
class Probe
=begin
get result of probe
result = EmailHelper::Probe.full(
email: 'znuny@example.com',
password: 'somepassword',
2016-03-10 13:41:42 +00:00
folder: 'some_folder', # optional im imap
)
returns on success
{
result: 'ok',
2015-08-28 00:53:14 +00:00
settings: {
inbound: {
adapter: 'imap',
options: {
host: 'imap.gmail.com',
port: 993,
ssl: true,
user: 'some@example.com',
password: 'password',
2016-03-10 13:41:42 +00:00
folder: 'some_folder', # optional im imap
},
},
2015-08-28 00:53:14 +00:00
outbound: {
adapter: 'smtp',
options: {
host: 'smtp.gmail.com',
port: 25,
ssl: true,
user: 'some@example.com',
password: 'password',
},
},
2015-08-28 00:53:14 +00:00
}
}
returns on fail
result = {
result: 'failed',
}
=end
def self.full(params)
user, domain = EmailHelper.parse_email(params[:email])
if !user || !domain
return {
result: 'invalid',
messages: {
email: 'Invalid email.'
},
}
end
# probe provider based settings
provider_map = EmailHelper.provider(params[:email], params[:password])
domains = [domain]
# get mx records, try to find provider based on mx records
mx_records = EmailHelper.mx_records(domain)
domains = domains.concat(mx_records)
provider_map.each {|_provider, settings|
domains.each {|domain_to_check|
next if domain_to_check !~ /#{settings[:domain]}/i
2016-03-10 13:41:42 +00:00
# add folder to config if needed
if !params[:folder].empty? && settings[:inbound] && settings[:inbound][:options]
settings[:inbound][:options][:folder] = params[:folder]
end
# probe inbound
Rails.logger.debug "INBOUND PROBE PROVIDER: #{settings[:inbound].inspect}"
result_inbound = EmailHelper::Probe.inbound(settings[:inbound])
Rails.logger.debug "INBOUND RESULT PROVIDER: #{result_inbound.inspect}"
next if result_inbound[:result] != 'ok'
# probe outbound
Rails.logger.debug "OUTBOUND PROBE PROVIDER: #{settings[:outbound].inspect}"
result_outbound = EmailHelper::Probe.outbound(settings[:outbound], params[:email])
Rails.logger.debug "OUTBOUND RESULT PROVIDER: #{result_outbound.inspect}"
next if result_outbound[:result] != 'ok'
return {
result: 'ok',
content_messages: result_inbound[:content_messages],
setting: settings,
}
}
}
# probe guess settings
# probe inbound
inbound_mx = EmailHelper.provider_inbound_mx(user, params[:email], params[:password], mx_records)
inbound_guess = EmailHelper.provider_inbound_guess(user, params[:email], params[:password], domain)
inbound_map = inbound_mx + inbound_guess
result = {
result: 'ok',
setting: {}
}
success = false
inbound_map.each {|config|
2016-03-10 13:41:42 +00:00
# add folder to config if needed
if !params[:folder].empty? && config[:options]
config[:options][:folder] = params[:folder]
end
Rails.logger.debug "INBOUND PROBE GUESS: #{config.inspect}"
result_inbound = EmailHelper::Probe.inbound(config)
Rails.logger.debug "INBOUND RESULT GUESS: #{result_inbound.inspect}"
next if result_inbound[:result] != 'ok'
success = true
result[:setting][:inbound] = config
result[:content_messages] = result_inbound[:content_messages]
break
}
# give up, no possible inbound found
if !success
return {
result: 'failed',
reason: 'inbound failed',
}
end
# probe outbound
outbound_mx = EmailHelper.provider_outbound_mx(user, params[:email], params[:password], mx_records)
outbound_guess = EmailHelper.provider_outbound_guess(user, params[:email], params[:password], domain)
outbound_map = outbound_mx + outbound_guess
success = false
outbound_map.each {|config|
Rails.logger.debug "OUTBOUND PROBE GUESS: #{config.inspect}"
result_outbound = EmailHelper::Probe.outbound(config, params[:email])
Rails.logger.debug "OUTBOUND RESULT GUESS: #{result_outbound.inspect}"
next if result_outbound[:result] != 'ok'
success = true
result[:setting][:outbound] = config
break
}
# give up, no possible outbound found
if !success
return {
result: 'failed',
reason: 'outbound failed',
}
end
Rails.logger.info "PROBE FULL SUCCESS: #{result.inspect}"
result
end
=begin
get result of inbound probe
result = EmailHelper::Probe.inbound(
adapter: 'imap',
2015-08-28 00:53:14 +00:00
settings: {
host: 'imap.gmail.com',
port: 993,
ssl: true,
user: 'some@example.com',
password: 'password',
2016-03-10 13:41:42 +00:00
folder: 'some_folder', # optional
}
)
returns on success
{
result: 'ok'
}
returns on fail
result = {
result: 'invalid',
settings: {
host: 'imap.gmail.com',
port: 993,
ssl: true,
user: 'some@example.com',
password: 'password',
2016-03-10 13:41:42 +00:00
folder: 'some_folder', # optional im imap
},
message: 'error message from used lib',
message_human: 'translated error message, readable for humans',
}
=end
def self.inbound(params)
2015-08-28 00:53:14 +00:00
adapter = params[:adapter].downcase
# validate adapter
2015-08-31 23:12:51 +00:00
if !EmailHelper.available_driver[:inbound][adapter.to_sym]
return {
result: 'failed',
message: "Unknown adapter '#{adapter}'",
}
end
# connection test
result_inbound = {}
begin
2015-08-28 00:53:14 +00:00
require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
driver_instance = driver_class.new
result_inbound = driver_instance.fetch(params[:options], nil, 'check')
2015-08-28 00:53:14 +00:00
rescue => e
return {
result: 'invalid',
settings: params,
message: e.message,
message_human: translation(e.message),
invalid_field: invalid_field(e.message),
}
end
result_inbound
end
=begin
get result of outbound probe
result = EmailHelper::Probe.outbound(
{
adapter: 'smtp',
options: {
host: 'smtp.gmail.com',
port: 25,
ssl: true,
user: 'some@example.com',
password: 'password',
}
},
2015-08-28 00:53:14 +00:00
'sender_and_recipient_of_test_email@example.com',
'subject of probe email',
)
returns on success
{
result: 'ok'
}
returns on fail
result = {
result: 'invalid',
settings: {
host: 'stmp.gmail.com',
port: 25,
ssl: true,
user: 'some@example.com',
password: 'password',
},
message: 'error message from used lib',
message_human: 'translated error message, readable for humans',
}
=end
def self.outbound(params, email, subject = nil)
2015-08-28 00:53:14 +00:00
adapter = params[:adapter].downcase
# validate adapter
2015-08-31 23:12:51 +00:00
if !EmailHelper.available_driver[:outbound][adapter.to_sym]
return {
result: 'failed',
message: "Unknown adapter '#{adapter}'",
}
end
2015-08-28 00:53:14 +00:00
# prepare test email
mail = if subject
{
from: email,
to: email,
subject: "Zammad Getting started Test Email #{subject}",
body: "This is a Test Email of Zammad to check if sending and receiving is working correctly.\n\nYou can ignore or delete this email.",
}
else
{
from: email,
to: 'emailtrytest@znuny.com',
subject: 'This is a Test Email',
body: "This is a Test Email of Zammad to verify if Zammad can send emails to an external address.\n\nIf you see this email, you can ignore and delete it.",
}
end
if subject
mail['X-Zammad-Test-Message'] = subject
end
mail['X-Zammad-Ignore'] = 'true'
mail['X-Loop'] = 'yes'
mail['Precedence'] = 'bulk'
mail['Auto-Submitted'] = 'auto-generated'
mail['X-Auto-Response-Suppress'] = 'All'
# test connection
begin
2015-08-28 00:53:14 +00:00
require "channel/driver/#{adapter.to_filename}"
driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}")
driver_instance = driver_class.new
driver_instance.send(
2015-08-28 01:02:21 +00:00
params[:options],
mail,
2015-08-28 00:53:14 +00:00
)
rescue => e
# check if sending email was ok, but mailserver rejected
if !subject
white_map = {
'Recipient address rejected' => true,
}
white_map.each {|key, _message|
next if e.message !~ /#{Regexp.escape(key)}/i
return {
result: 'ok',
settings: params,
notice: e.message,
}
}
end
return {
result: 'invalid',
settings: params,
message: e.message,
message_human: translation(e.message),
invalid_field: invalid_field(e.message),
}
end
{
result: 'ok',
}
end
def self.invalid_field(message_backend)
invalid_fields.each {|key, fields|
return fields if message_backend =~ /#{Regexp.escape(key)}/i
}
{}
end
def self.invalid_fields
{
2015-08-29 22:58:21 +00:00
'authentication failed' => { user: true, password: true },
'Username and Password not accepted' => { user: true, password: true },
'Incorrect username' => { user: true, password: true },
'Lookup failed' => { user: true },
2015-08-29 22:58:21 +00:00
'Invalid credentials' => { user: true, password: true },
'getaddrinfo: nodename nor servname provided, or not known' => { host: true },
'getaddrinfo: Name or service not known' => { host: true },
'No route to host' => { host: true },
'execution expired' => { host: true },
'Connection refused' => { host: true },
2016-03-10 13:41:42 +00:00
'Mailbox doesn\'t exist' => { folder: true },
'Folder doesn\'t exist' => { folder: true },
}
end
def self.translation(message_backend)
translations.each {|key, message_human|
return message_human if message_backend =~ /#{Regexp.escape(key)}/i
}
nil
end
def self.translations
{
'authentication failed' => 'Authentication failed!',
'Username and Password not accepted' => 'Authentication failed!',
'Incorrect username' => 'Authentication failed, username incorrect!',
'Lookup failed' => 'Authentication failed, username incorrect!',
'Invalid credentials' => 'Authentication failed, invalid credentials!',
'getaddrinfo: nodename nor servname provided, or not known' => 'Hostname not found!',
2015-08-23 21:06:21 +00:00
'getaddrinfo: Name or service not known' => 'Hostname not found!',
'No route to host' => 'No route to host!',
'execution expired' => 'Host not reachable!',
'Connection refused' => 'Connection refused!',
}
end
end
end