2015-08-23 20:21:04 +00:00
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
2015-08-23 20:21:04 +00:00
)
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-23 20:21:04 +00:00
} ,
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-23 20:21:04 +00:00
} ,
2015-08-28 00:53:14 +00:00
}
2015-08-23 20:21:04 +00:00
}
returns on fail
result = {
result : 'failed' ,
}
= end
def self . full ( params )
user , domain = EmailHelper . parse_email ( params [ :email ] )
if ! user || ! domain
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
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 )
2017-11-23 08:09:44 +00:00
provider_map . each_value do | settings |
2017-10-01 12:25:52 +00:00
domains . each do | domain_to_check |
2015-08-23 20:21:04 +00:00
next if domain_to_check !~ / #{ settings [ :domain ] } /i
2016-03-10 13:41:42 +00:00
# add folder to config if needed
2017-11-23 08:09:44 +00:00
if params [ :folder ] . present? && settings [ :inbound ] && settings [ :inbound ] [ :options ]
2016-03-10 13:41:42 +00:00
settings [ :inbound ] [ :options ] [ :folder ] = params [ :folder ]
end
2015-08-23 20:21:04 +00:00
# probe inbound
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " INBOUND PROBE PROVIDER: #{ settings [ :inbound ] . inspect } " }
2015-09-06 07:50:51 +00:00
result_inbound = EmailHelper :: Probe . inbound ( settings [ :inbound ] )
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " INBOUND RESULT PROVIDER: #{ result_inbound . inspect } " }
2015-09-06 07:50:51 +00:00
next if result_inbound [ :result ] != 'ok'
2015-08-23 20:21:04 +00:00
# probe outbound
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " OUTBOUND PROBE PROVIDER: #{ settings [ :outbound ] . inspect } " }
2015-09-06 07:50:51 +00:00
result_outbound = EmailHelper :: Probe . outbound ( settings [ :outbound ] , params [ :email ] )
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " OUTBOUND RESULT PROVIDER: #{ result_outbound . inspect } " }
2015-09-06 07:50:51 +00:00
next if result_outbound [ :result ] != 'ok'
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
result : 'ok' ,
2015-09-06 07:50:51 +00:00
content_messages : result_inbound [ :content_messages ] ,
2015-08-23 20:21:04 +00:00
setting : settings ,
}
2017-10-01 12:25:52 +00:00
end
end
2015-08-23 20:21:04 +00:00
# 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
2015-09-06 07:50:51 +00:00
result = {
result : 'ok' ,
setting : { }
}
2015-08-23 20:21:04 +00:00
success = false
2017-10-01 12:25:52 +00:00
inbound_map . each do | config |
2016-03-10 13:41:42 +00:00
# add folder to config if needed
2017-11-23 08:09:44 +00:00
if params [ :folder ] . present? && config [ :options ]
2016-03-10 13:41:42 +00:00
config [ :options ] [ :folder ] = params [ :folder ]
end
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " INBOUND PROBE GUESS: #{ config . inspect } " }
2015-09-06 07:50:51 +00:00
result_inbound = EmailHelper :: Probe . inbound ( config )
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " INBOUND RESULT GUESS: #{ result_inbound . inspect } " }
2015-09-06 07:50:51 +00:00
next if result_inbound [ :result ] != 'ok'
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
success = true
result [ :setting ] [ :inbound ] = config
result [ :content_messages ] = result_inbound [ :content_messages ]
2015-08-23 20:21:04 +00:00
break
2017-10-01 12:25:52 +00:00
end
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
# give up, no possible inbound found
2015-08-23 20:21:04 +00:00
if ! success
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
result : 'failed' ,
2015-09-06 07:50:51 +00:00
reason : 'inbound failed' ,
2015-08-23 20:21:04 +00:00
}
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
2017-10-01 12:25:52 +00:00
outbound_map . each do | config |
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " OUTBOUND PROBE GUESS: #{ config . inspect } " }
2015-09-06 07:50:51 +00:00
result_outbound = EmailHelper :: Probe . outbound ( config , params [ :email ] )
2018-03-20 17:47:49 +00:00
Rails . logger . debug { " OUTBOUND RESULT GUESS: #{ result_outbound . inspect } " }
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
next if result_outbound [ :result ] != 'ok'
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
success = true
result [ :setting ] [ :outbound ] = config
2015-08-23 20:21:04 +00:00
break
2017-10-01 12:25:52 +00:00
end
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
# give up, no possible outbound found
2015-08-23 20:21:04 +00:00
if ! success
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
result : 'failed' ,
2015-09-06 07:50:51 +00:00
reason : 'outbound failed' ,
2015-08-23 20:21:04 +00:00
}
end
2015-09-06 11:39:16 +00:00
Rails . logger . info " PROBE FULL SUCCESS: #{ result . inspect } "
2015-09-06 07:50:51 +00:00
result
2015-08-23 20:21:04 +00:00
end
= begin
get result of inbound probe
result = EmailHelper :: Probe . inbound (
adapter : 'imap' ,
2015-08-28 00:53:14 +00:00
settings : {
2015-08-23 20:21:04 +00:00
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
2015-08-23 20:21:04 +00:00
}
)
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
2015-08-23 20:21:04 +00:00
} ,
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
2015-08-23 20:21:04 +00:00
2015-08-30 11:58:05 +00:00
# validate adapter
2015-08-31 23:12:51 +00:00
if ! EmailHelper . available_driver [ :inbound ] [ adapter . to_sym ]
2015-08-30 11:58:05 +00:00
return {
result : 'failed' ,
message : " Unknown adapter ' #{ adapter } ' " ,
}
end
2015-08-23 20:21:04 +00:00
# connection test
2015-09-06 07:50:51 +00:00
result_inbound = { }
2015-08-23 20:21:04 +00:00
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
2015-09-06 07:50:51 +00:00
result_inbound = driver_instance . fetch ( params [ :options ] , nil , 'check' )
2015-08-23 20:21:04 +00:00
rescue = > e
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
result : 'invalid' ,
settings : params ,
message : e . message ,
2015-08-29 11:46:48 +00:00
message_human : translation ( e . message ) ,
invalid_field : invalid_field ( e . message ) ,
2015-08-23 20:21:04 +00:00
}
end
2015-09-06 07:50:51 +00:00
result_inbound
2015-08-23 20:21:04 +00:00
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' ,
2015-08-23 20:21:04 +00:00
)
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
2015-08-23 20:21:04 +00:00
2015-08-30 11:58:05 +00:00
# validate adapter
2015-08-31 23:12:51 +00:00
if ! EmailHelper . available_driver [ :outbound ] [ adapter . to_sym ]
2015-08-30 11:58:05 +00:00
return {
result : 'failed' ,
message : " Unknown adapter ' #{ adapter } ' " ,
}
end
2015-08-28 00:53:14 +00:00
# prepare test email
2016-01-15 17:22:57 +00:00
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 \n You 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 \n If you see this email, you can ignore and delete it. " ,
}
end
2015-09-06 07:50:51 +00:00
if subject
mail [ 'X-Zammad-Test-Message' ] = subject
end
2015-09-20 00:16:22 +00:00
mail [ 'X-Zammad-Ignore' ] = 'true'
mail [ 'X-Loop' ] = 'yes'
mail [ 'Precedence' ] = 'bulk'
mail [ 'Auto-Submitted' ] = 'auto-generated'
mail [ 'X-Auto-Response-Suppress' ] = 'All'
2015-08-23 20:21:04 +00:00
# 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
)
2015-08-23 20:21:04 +00:00
rescue = > e
# check if sending email was ok, but mailserver rejected
if ! subject
white_map = {
'Recipient address rejected' = > true ,
2017-12-03 16:32:33 +00:00
'Sender address rejected: Domain not found' = > true ,
2015-08-23 20:21:04 +00:00
}
2017-11-23 08:09:44 +00:00
white_map . each_key do | key |
2015-08-23 20:21:04 +00:00
next if e . message !~ / #{ Regexp . escape ( key ) } /i
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
result : 'ok' ,
settings : params ,
notice : e . message ,
}
2017-10-01 12:25:52 +00:00
end
2015-08-23 20:21:04 +00:00
end
2015-09-06 07:50:51 +00:00
return {
2015-08-23 20:21:04 +00:00
result : 'invalid' ,
settings : params ,
message : e . message ,
2015-08-29 11:46:48 +00:00
message_human : translation ( e . message ) ,
invalid_field : invalid_field ( e . message ) ,
2015-08-23 20:21:04 +00:00
}
end
2015-09-06 07:50:51 +00:00
{
2015-08-23 20:21:04 +00:00
result : 'ok' ,
}
end
2015-08-29 11:46:48 +00:00
def self . invalid_field ( message_backend )
2017-10-01 12:25:52 +00:00
invalid_fields . each do | key , fields |
2017-11-23 08:09:44 +00:00
return fields if message_backend . match? ( / #{ Regexp . escape ( key ) } /i )
2017-10-01 12:25:52 +00:00
end
2015-08-29 11:46:48 +00:00
{ }
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 } ,
2015-08-29 11:46:48 +00:00
'Lookup failed' = > { user : true } ,
2015-08-29 22:58:21 +00:00
'Invalid credentials' = > { user : true , password : true } ,
2015-08-29 11:46:48 +00:00
'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 } ,
2016-03-11 05:58:27 +00:00
'Unknown Mailbox' = > { folder : true } ,
2015-08-29 11:46:48 +00:00
}
end
def self . translation ( message_backend )
2017-10-01 12:25:52 +00:00
translations . each do | key , message_human |
2017-11-23 08:09:44 +00:00
return message_human if message_backend . match? ( / #{ Regexp . escape ( key ) } /i )
2017-10-01 12:25:52 +00:00
end
2015-08-29 11:46:48 +00:00
nil
end
2015-08-23 20:21:04 +00:00
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!' ,
2015-08-23 20:21:04 +00:00
'No route to host' = > 'No route to host!' ,
'execution expired' = > 'Host not reachable!' ,
'Connection refused' = > 'Connection refused!' ,
}
end
end
end