2022-01-01 13:38:12 +00:00
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
2021-06-01 12:20:20 +00:00
2020-02-05 12:50:49 +00:00
class EmailHelper
2015-08-23 20:21:04 +00:00
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 {
2018-12-19 17:31:51 +00:00
result : 'invalid' ,
2015-08-23 20:21:04 +00:00
messages : {
2020-01-31 21:40:55 +00:00
email : " Invalid email ' #{ params [ :email ] } '. "
2015-08-23 20:21:04 +00:00
} ,
}
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 )
2020-09-30 09:07:01 +00:00
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
2021-05-12 11:37:44 +00:00
next if ! domain_to_check . match? ( %r{ #{ settings [ :domain ] } }i )
2015-08-23 20:21:04 +00:00
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 {
2020-10-29 14:58:36 +00:00
result : 'ok' ,
content_messages : result_inbound [ :content_messages ] ,
archive_possible : result_inbound [ :archive_possible ] ,
archive_week_range : result_inbound [ :archive_week_range ] ,
setting : settings ,
2015-08-23 20:21:04 +00:00
}
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 = {
2018-12-19 17:31:51 +00:00
result : 'ok' ,
2015-09-06 07:50:51 +00:00
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
2020-10-29 14:58:36 +00:00
success = true
result [ :setting ] [ :inbound ] = config
result [ :content_messages ] = result_inbound [ :content_messages ]
result [ :archive_possible ] = result_inbound [ :archive_possible ]
result [ :archive_week_range ] = result_inbound [ :archive_week_range ]
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
2021-04-13 14:42:26 +00:00
Rails . logger . debug { " 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' ,
2020-05-28 13:28:07 +00:00
options : {
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 {
2018-12-19 17:31:51 +00:00
result : 'failed' ,
2015-08-30 11:58:05 +00:00
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
2019-01-06 18:41:29 +00:00
driver_class = " Channel::Driver:: #{ adapter . to_classname } " . constantize
2015-08-28 00:53:14 +00:00
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
2020-10-05 12:27:23 +00:00
Rails . logger . debug { e }
2020-10-02 07:28:00 +00:00
2015-09-06 07:50:51 +00:00
return {
2018-12-19 17:31:51 +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 {
2018-12-19 17:31:51 +00:00
result : 'failed' ,
2015-08-30 11:58:05 +00:00
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 } " ,
2021-11-15 15:58:19 +00:00
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. " ) ,
2016-01-15 17:22:57 +00:00
}
else
{
from : email ,
to : 'emailtrytest@znuny.com' ,
2021-11-15 15:58:19 +00:00
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. " ) ,
2016-01-15 17:22:57 +00:00
}
end
2019-01-14 14:53:26 +00:00
if subject . present?
2015-09-06 07:50:51 +00:00
mail [ 'X-Zammad-Test-Message' ] = subject
end
2015-09-20 00:16:22 +00:00
mail [ 'X-Zammad-Ignore' ] = 'true'
2019-01-14 14:53:26 +00:00
mail [ 'X-Zammad-Fqdn' ] = Setting . get ( 'fqdn' )
mail [ 'X-Zammad-Verify' ] = 'true'
mail [ 'X-Zammad-Verify-Time' ] = Time . zone . now . iso8601
2015-09-20 00:16:22 +00:00
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
2019-01-06 18:41:29 +00:00
driver_class = " Channel::Driver:: #{ adapter . to_classname } " . constantize
2015-08-28 00:53:14 +00:00
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
2020-10-05 12:27:23 +00:00
Rails . logger . debug { e }
2015-08-23 20:21:04 +00:00
# check if sending email was ok, but mailserver rejected
if ! subject
white_map = {
2018-12-19 17:31:51 +00:00
'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
2021-05-12 11:37:44 +00:00
next if ! e . message . match? ( %r{ #{ Regexp . escape ( key ) } }i )
2015-08-23 20:21:04 +00:00
2015-09-06 07:50:51 +00:00
return {
2018-12-19 17:31:51 +00:00
result : 'ok' ,
2015-08-23 20:21:04 +00:00
settings : params ,
2018-12-19 17:31:51 +00:00
notice : e . message ,
2015-08-23 20:21:04 +00:00
}
2017-10-01 12:25:52 +00:00
end
2015-08-23 20:21:04 +00:00
end
2020-10-02 07:28:00 +00:00
2015-09-06 07:50:51 +00:00
return {
2018-12-19 17:31:51 +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 |
2021-05-12 11:37:44 +00:00
return fields if message_backend . match? ( %r{ #{ 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 |
2021-05-12 11:37:44 +00:00
return message_human if message_backend . match? ( %r{ #{ 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
{
2021-11-15 15:58:19 +00:00
'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!' ) ,
'authentication not enabled' = > __ ( 'Authentication not possible (not offered by the service)' ) ,
'getaddrinfo: nodename nor servname provided, or not known' = > __ ( 'Hostname not found!' ) ,
'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!' ) ,
2015-08-23 20:21:04 +00:00
}
end
end
end