2017-06-28 17:06:12 -05:00
'use strict' ;
exports = module . exports = {
getStatus : getStatus ,
2018-01-20 22:56:45 -08:00
get : get ,
2018-01-20 18:56:17 -08:00
2018-01-20 22:56:45 -08:00
setMailFromValidation : setMailFromValidation ,
2018-01-20 18:56:17 -08:00
setCatchAllAddress : setCatchAllAddress ,
setMailRelay : setMailRelay ,
2018-01-20 23:17:39 -08:00
setMailEnabled : setMailEnabled ,
2018-01-20 18:56:17 -08:00
2018-01-20 20:34:30 -08:00
startMail : restartMail ,
2018-01-20 18:56:17 -08:00
2018-01-20 18:19:26 -08:00
MailError : MailError
2017-06-28 17:06:12 -05:00
} ;
var assert = require ( 'assert' ) ,
async = require ( 'async' ) ,
2018-01-20 20:34:30 -08:00
certificates = require ( './certificates.js' ) ,
2017-06-28 17:06:12 -05:00
cloudron = require ( './cloudron.js' ) ,
config = require ( './config.js' ) ,
2018-01-20 18:56:17 -08:00
DatabaseError = require ( './databaseerror.js' ) ,
debug = require ( 'debug' ) ( 'box:mail' ) ,
2017-06-28 17:06:12 -05:00
dig = require ( './dig.js' ) ,
2018-01-20 20:34:30 -08:00
domains = require ( './domains.js' ) ,
infra = require ( './infra_version.js' ) ,
2018-01-20 22:56:45 -08:00
maildb = require ( './maildb.js' ) ,
2017-06-28 17:06:12 -05:00
net = require ( 'net' ) ,
nodemailer = require ( 'nodemailer' ) ,
2018-01-20 20:34:30 -08:00
os = require ( 'os' ) ,
2018-01-20 18:56:17 -08:00
paths = require ( './paths.js' ) ,
2017-06-28 21:38:51 -05:00
safe = require ( 'safetydance' ) ,
2018-01-20 20:34:30 -08:00
shell = require ( './shell.js' ) ,
2017-06-28 17:06:12 -05:00
smtpTransport = require ( 'nodemailer-smtp-transport' ) ,
sysinfo = require ( './sysinfo.js' ) ,
2018-01-20 18:56:17 -08:00
user = require ( './user.js' ) ,
2017-06-28 17:06:12 -05:00
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
2017-06-28 21:38:51 -05:00
const digOptions = { server : '127.0.0.1' , port : 53 , timeout : 5000 } ;
2018-01-20 18:56:17 -08:00
var NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
2018-01-20 18:19:26 -08:00
function MailError ( reason , errorOrMessage ) {
2017-06-28 17:06:12 -05:00
assert . strictEqual ( typeof reason , 'string' ) ;
assert ( errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined' ) ;
Error . call ( this ) ;
Error . captureStackTrace ( this , this . constructor ) ;
this . name = this . constructor . name ;
this . reason = reason ;
if ( typeof errorOrMessage === 'undefined' ) {
this . message = reason ;
} else if ( typeof errorOrMessage === 'string' ) {
this . message = errorOrMessage ;
} else {
this . message = 'Internal error' ;
this . nestedError = errorOrMessage ;
}
}
2018-01-20 18:19:26 -08:00
util . inherits ( MailError , Error ) ;
MailError . INTERNAL _ERROR = 'Internal Error' ;
MailError . BAD _FIELD = 'Bad Field' ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
function checkOutboundPort25 ( callback ) {
2017-06-28 17:06:12 -05:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-06-28 21:38:51 -05:00
var smtpServer = _ . sample ( [
'smtp.gmail.com' ,
'smtp.live.com' ,
'smtp.mail.yahoo.com' ,
'smtp.o2.ie' ,
'smtp.comcast.net' ,
'outgoing.verizon.net'
] ) ;
var relay = {
value : 'OK' ,
status : false
} ;
var client = new net . Socket ( ) ;
client . setTimeout ( 5000 ) ;
client . connect ( 25 , smtpServer ) ;
client . on ( 'connect' , function ( ) {
relay . status = true ;
relay . value = 'OK' ;
client . destroy ( ) ; // do not use end() because it still triggers timeout
callback ( null , relay ) ;
} ) ;
client . on ( 'timeout' , function ( ) {
relay . status = false ;
relay . value = 'Connect to ' + smtpServer + ' timed out' ;
client . destroy ( ) ;
callback ( new Error ( 'Timeout' ) , relay ) ;
} ) ;
client . on ( 'error' , function ( error ) {
relay . status = false ;
relay . value = 'Connect to ' + smtpServer + ' failed: ' + error . message ;
client . destroy ( ) ;
callback ( error , relay ) ;
} ) ;
}
function checkSmtpRelay ( relay , callback ) {
var result = {
value : 'OK' ,
status : false
} ;
2017-06-28 17:06:12 -05:00
var transporter = nodemailer . createTransport ( smtpTransport ( {
host : relay . host ,
port : relay . port ,
auth : {
user : relay . username ,
pass : relay . password
}
} ) ) ;
transporter . verify ( function ( error ) {
2017-06-29 10:11:55 -05:00
result . status = ! error ;
2017-06-28 21:38:51 -05:00
if ( error ) {
result . value = error . message ;
return callback ( error , result ) ;
}
2017-06-28 17:06:12 -05:00
2017-12-04 17:10:02 +05:30
callback ( null , result ) ;
2017-06-28 21:38:51 -05:00
} ) ;
2017-06-28 17:06:12 -05:00
}
2017-06-28 21:38:51 -05:00
function verifyRelay ( relay , callback ) {
assert . strictEqual ( typeof relay , 'object' ) ;
2017-06-28 17:06:12 -05:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-21 00:06:08 -08:00
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
2017-06-28 21:38:51 -05:00
var verifier = relay . provider === 'cloudron-smtp' ? checkOutboundPort25 : checkSmtpRelay . bind ( null , relay ) ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
verifier ( function ( error ) {
2018-01-20 18:19:26 -08:00
if ( error ) return callback ( new MailError ( MailError . BAD _FIELD , error . message ) ) ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
callback ( ) ;
} ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
function checkDkim ( callback ) {
var dkim = {
2017-10-31 10:19:52 -07:00
domain : config . dkimSelector ( ) + '._domainkey.' + config . fqdn ( ) ,
2017-06-28 21:38:51 -05:00
type : 'TXT' ,
expected : null ,
value : null ,
status : false
} ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
var dkimKey = cloudron . readDkimPublicKeySync ( ) ;
if ( ! dkimKey ) return callback ( new Error ( 'Failed to read dkim public key' ) , dkim ) ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
dkim . expected = '"v=DKIM1; t=s; p=' + dkimKey + '"' ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
dig . resolve ( dkim . domain , dkim . type , digOptions , function ( error , txtRecords ) {
if ( error && error . code === 'ENOTFOUND' ) return callback ( null , dkim ) ; // not setup
if ( error ) return callback ( error , dkim ) ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
if ( Array . isArray ( txtRecords ) && txtRecords . length !== 0 ) {
dkim . value = txtRecords [ 0 ] ;
dkim . status = ( dkim . value === dkim . expected ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
callback ( null , dkim ) ;
} ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
function checkSpf ( callback ) {
var spf = {
domain : config . fqdn ( ) ,
type : 'TXT' ,
value : null ,
expected : '"v=spf1 a:' + config . adminFqdn ( ) + ' ~all"' ,
status : false
} ;
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
dig . resolve ( spf . domain , spf . type , digOptions , function ( error , txtRecords ) {
if ( error && error . code === 'ENOTFOUND' ) return callback ( null , spf ) ; // not setup
if ( error ) return callback ( error , spf ) ;
if ( ! Array . isArray ( txtRecords ) ) return callback ( null , spf ) ;
var i ;
for ( i = 0 ; i < txtRecords . length ; i ++ ) {
if ( txtRecords [ i ] . indexOf ( '"v=spf1 ' ) !== 0 ) continue ; // not SPF
spf . value = txtRecords [ i ] ;
spf . status = spf . value . indexOf ( ' a:' + config . adminFqdn ( ) ) !== - 1 ;
break ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
if ( spf . status ) {
spf . expected = spf . value ;
} else if ( i !== txtRecords . length ) {
spf . expected = '"v=spf1 a:' + config . adminFqdn ( ) + ' ' + spf . value . slice ( '"v=spf1 ' . length ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
callback ( null , spf ) ;
} ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
function checkMx ( callback ) {
var mx = {
domain : config . fqdn ( ) ,
type : 'MX' ,
value : null ,
expected : '10 ' + config . mailFqdn ( ) + '.' ,
status : false
} ;
dig . resolve ( mx . domain , mx . type , digOptions , function ( error , mxRecords ) {
if ( error && error . code === 'ENOTFOUND' ) return callback ( null , mx ) ; // not setup
if ( error ) return callback ( error , mx ) ;
if ( Array . isArray ( mxRecords ) && mxRecords . length !== 0 ) {
mx . status = mxRecords . length == 1 && mxRecords [ 0 ] . exchange === ( config . mailFqdn ( ) + '.' ) ;
mx . value = mxRecords . map ( function ( r ) { return r . priority + ' ' + r . exchange ; } ) . join ( ' ' ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
callback ( null , mx ) ;
} ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
function checkDmarc ( callback ) {
var dmarc = {
domain : '_dmarc.' + config . fqdn ( ) ,
type : 'TXT' ,
value : null ,
expected : '"v=DMARC1; p=reject; pct=100"' ,
status : false
} ;
dig . resolve ( dmarc . domain , dmarc . type , digOptions , function ( error , txtRecords ) {
if ( error && error . code === 'ENOTFOUND' ) return callback ( null , dmarc ) ; // not setup
if ( error ) return callback ( error , dmarc ) ;
if ( Array . isArray ( txtRecords ) && txtRecords . length !== 0 ) {
dmarc . value = txtRecords [ 0 ] ;
dmarc . status = ( dmarc . value === dmarc . expected ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
callback ( null , dmarc ) ;
} ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
function checkPtr ( callback ) {
var ptr = {
domain : null ,
type : 'PTR' ,
value : null ,
expected : config . mailFqdn ( ) + '.' ,
status : false
} ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
sysinfo . getPublicIp ( function ( error , ip ) {
if ( error ) return callback ( error , ptr ) ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
ptr . domain = ip . split ( '.' ) . reverse ( ) . join ( '.' ) + '.in-addr.arpa' ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
dig . resolve ( ip , 'PTR' , digOptions , function ( error , ptrRecords ) {
if ( error && error . code === 'ENOTFOUND' ) return callback ( null , ptr ) ; // not setup
if ( error ) return callback ( error , ptr ) ;
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
if ( Array . isArray ( ptrRecords ) && ptrRecords . length !== 0 ) {
ptr . value = ptrRecords . join ( ' ' ) ;
ptr . status = ptrRecords . some ( function ( v ) { return v === ptr . expected ; } ) ;
}
2017-06-28 17:06:12 -05:00
2017-06-28 21:38:51 -05:00
return callback ( null , ptr ) ;
2017-06-28 17:06:12 -05:00
} ) ;
2017-06-28 21:38:51 -05:00
} ) ;
}
2017-06-28 17:06:12 -05:00
2017-09-08 11:50:11 -07:00
// https://raw.githubusercontent.com/jawsome/node-dnsbl/master/list.json
const RBL _LIST = [
{
2017-12-04 17:10:02 +05:30
'name' : 'Barracuda' ,
'dns' : 'b.barracudacentral.org' ,
'site' : 'http://www.barracudacentral.org/rbl/removal-request'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'SpamCop' ,
'dns' : 'bl.spamcop.net' ,
'site' : 'http://spamcop.net'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'Sorbs Aggregate Zone' ,
'dns' : 'dnsbl.sorbs.net' ,
'site' : 'http://dnsbl.sorbs.net/'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'Sorbs spam.dnsbl Zone' ,
'dns' : 'spam.dnsbl.sorbs.net' ,
'site' : 'http://sorbs.net'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'Composite Blocking List' ,
'dns' : 'cbl.abuseat.org' ,
'site' : 'http://www.abuseat.org'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'SpamHaus Zen' ,
'dns' : 'zen.spamhaus.org' ,
'site' : 'http://spamhaus.org'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'Multi SURBL' ,
'dns' : 'multi.surbl.org' ,
'site' : 'http://www.surbl.org'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'Spam Cannibal' ,
'dns' : 'bl.spamcannibal.org' ,
'site' : 'http://www.spamcannibal.org/cannibal.cgi'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'dnsbl.abuse.ch' ,
'dns' : 'spam.abuse.ch' ,
'site' : 'http://dnsbl.abuse.ch/'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'The Unsubscribe Blacklist(UBL)' ,
'dns' : 'ubl.unsubscore.com ' ,
'site' : 'http://www.lashback.com/blacklist/'
2017-09-08 11:50:11 -07:00
} ,
{
2017-12-04 17:10:02 +05:30
'name' : 'UCEPROTECT Network' ,
'dns' : 'dnsbl-1.uceprotect.net' ,
'site' : 'http://www.uceprotect.net/en'
2017-09-08 11:50:11 -07:00
}
] ;
function checkRblStatus ( callback ) {
2017-09-13 22:39:42 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-09-08 11:50:11 -07:00
sysinfo . getPublicIp ( function ( error , ip ) {
if ( error ) return callback ( error , ip ) ;
var flippedIp = ip . split ( '.' ) . reverse ( ) . join ( '.' ) ;
// https://tools.ietf.org/html/rfc5782
async . map ( RBL _LIST , function ( rblServer , iteratorDone ) {
dig . resolve ( flippedIp + '.' + rblServer . dns , 'A' , digOptions , function ( error , records ) {
if ( error || ! records ) return iteratorDone ( null , null ) ; // not listed
debug ( 'checkRblStatus: %s (ip: %s) is in the blacklist of %j' , config . fqdn ( ) , flippedIp , rblServer ) ;
var result = _ . extend ( { } , rblServer ) ;
dig . resolve ( flippedIp + '.' + rblServer . dns , 'TXT' , digOptions , function ( error , txtRecords ) {
result . txtRecords = error || ! txtRecords ? 'No txt record' : txtRecords ;
debug ( 'checkRblStatus: %s (error: %s) (txtRecords: %j)' , config . fqdn ( ) , error , txtRecords ) ;
return iteratorDone ( null , result ) ;
} ) ;
} ) ;
} , function ( ignoredError , blacklistedServers ) {
blacklistedServers = blacklistedServers . filter ( function ( b ) { return b !== null ; } ) ;
2017-09-13 22:39:42 -07:00
debug ( 'checkRblStatus: %s (ip: %s) servers: %j' , config . fqdn ( ) , ip , blacklistedServers ) ;
return callback ( null , { status : blacklistedServers . length === 0 , ip : ip , servers : blacklistedServers } ) ;
} ) ;
} ) ;
}
function getStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
var results = { } ;
function recordResult ( what , func ) {
return function ( callback ) {
func ( function ( error , result ) {
if ( error ) debug ( 'Ignored error - ' + what + ':' , error ) ;
safe . set ( results , what , result ) ;
callback ( ) ;
} ) ;
} ;
}
2017-09-08 11:50:11 -07:00
2018-01-20 22:56:45 -08:00
get ( config . fqdn ( ) , function ( error , mailConfig ) {
2017-09-13 22:39:42 -07:00
if ( error ) return callback ( error ) ;
2017-09-08 11:50:11 -07:00
2017-09-13 22:39:42 -07:00
var checks = [
recordResult ( 'dns.mx' , checkMx ) ,
recordResult ( 'dns.dmarc' , checkDmarc )
] ;
2018-01-20 22:56:45 -08:00
if ( mailConfig . relay . provider === 'cloudron-smtp' ) {
2017-09-13 22:39:42 -07:00
// these tests currently only make sense when using Cloudron's SMTP server at this point
checks . push (
recordResult ( 'dns.spf' , checkSpf ) ,
recordResult ( 'dns.dkim' , checkDkim ) ,
recordResult ( 'dns.ptr' , checkPtr ) ,
recordResult ( 'relay' , checkOutboundPort25 ) ,
recordResult ( 'rbl' , checkRblStatus )
) ;
} else {
2018-01-20 22:56:45 -08:00
checks . push ( recordResult ( 'relay' , checkSmtpRelay . bind ( null , mailConfig . relay ) ) ) ;
2017-09-13 22:39:42 -07:00
}
async . parallel ( checks , function ( ) {
callback ( null , results ) ;
2017-09-08 11:50:11 -07:00
} ) ;
} ) ;
}
2018-01-20 18:56:17 -08:00
function createMailConfig ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
const fqdn = config . fqdn ( ) ;
const mailFqdn = config . mailFqdn ( ) ;
const alertsFrom = 'no-reply@' + config . fqdn ( ) ;
debug ( 'createMailConfig: generating mail config' ) ;
user . getOwner ( function ( error , owner ) {
var alertsTo = config . provider ( ) === 'caas' ? [ 'support@cloudron.io' ] : [ ] ;
alertsTo . concat ( error ? [ ] : owner . email ) . join ( ',' ) ; // owner may not exist yet
2018-01-20 23:17:39 -08:00
get ( fqdn , function ( error , result ) {
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( error ) ;
2018-01-20 23:17:39 -08:00
var catchAll = result . catchAll . map ( function ( c ) { return ` ${ c } @ ${ fqdn } ` ; } ) . join ( ',' ) ;
var mailFromValidation = result . mailFromValidation ;
2018-01-20 18:56:17 -08:00
if ( ! safe . fs . writeFileSync ( paths . ADDON _CONFIG _DIR + '/mail/mail.ini' ,
` mail_domains= ${ fqdn } \n mail_default_domain= ${ fqdn } \n mail_server_name= ${ mailFqdn } \n alerts_from= ${ alertsFrom } \n alerts_to= ${ alertsTo } \n catch_all= ${ catchAll } \n mail_from_validation= ${ mailFromValidation } \n ` , 'utf8' ) ) {
return callback ( new Error ( 'Could not create mail var file:' + safe . error . message ) ) ;
}
2018-01-20 23:17:39 -08:00
var relay = result . relay ;
2018-01-20 18:56:17 -08:00
const enabled = relay . provider !== 'cloudron-smtp' ? true : false ,
host = relay . host || '' ,
port = relay . port || 25 ,
username = relay . username || '' ,
password = relay . password || '' ;
if ( ! safe . fs . writeFileSync ( paths . ADDON _CONFIG _DIR + '/mail/smtp_forward.ini' ,
` enable_outbound= ${ enabled } \n host= ${ host } \n port= ${ port } \n enable_tls=true \n auth_type=plain \n auth_user= ${ username } \n auth_pass= ${ password } ` , 'utf8' ) ) {
return callback ( new Error ( 'Could not create mail var file:' + safe . error . message ) ) ;
}
callback ( ) ;
} ) ;
} ) ;
}
2018-01-20 20:34:30 -08:00
function restartMail ( callback ) {
// mail (note: 2525 is hardcoded in mail container and app use this port)
// MAIL_SERVER_NAME is the hostname of the mailserver i.e server uses these certs
// MAIL_DOMAIN is the domain for which this server is relaying mails
// mail container uses /app/data for backed up data and /run for restart-able data
2018-01-21 00:06:08 -08:00
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
2018-01-20 20:34:30 -08:00
function onCertificateChanged ( domain ) {
if ( domain === '*.' + config . fqdn ( ) || domain === config . adminFqdn ( ) ) restartMail ( NOOP _CALLBACK ) ;
}
certificates . events . removeListener ( certificates . EVENT _CERT _CHANGED , onCertificateChanged ) ;
certificates . events . on ( certificates . EVENT _CERT _CHANGED , onCertificateChanged ) ;
const tag = infra . images . mail . tag ;
const memoryLimit = Math . max ( ( 1 + Math . round ( os . totalmem ( ) / ( 1024 * 1024 * 1024 ) / 4 ) ) * 128 , 256 ) ;
// admin and mail share the same certificate
certificates . getAdminCertificate ( function ( error , cert , key ) {
if ( error ) return callback ( error ) ;
// the setup script copies dhparams.pem to /addons/mail
if ( ! safe . fs . writeFileSync ( paths . ADDON _CONFIG _DIR + '/mail/tls_cert.pem' , cert ) ) return callback ( new Error ( 'Could not create cert file:' + safe . error . message ) ) ;
if ( ! safe . fs . writeFileSync ( paths . ADDON _CONFIG _DIR + '/mail/tls_key.pem' , key ) ) return callback ( new Error ( 'Could not create key file:' + safe . error . message ) ) ;
2018-01-20 22:56:45 -08:00
get ( config . fqdn ( ) , function ( error , mailConfig ) {
2018-01-20 20:34:30 -08:00
if ( error ) return callback ( error ) ;
shell . execSync ( 'startMail' , 'docker rm -f mail || true' ) ;
createMailConfig ( function ( error ) {
if ( error ) return callback ( error ) ;
var ports = mailConfig . enabled ? '-p 587:2525 -p 993:9993 -p 4190:4190 -p 25:2525' : '' ;
const cmd = ` docker run --restart=always -d --name="mail" \
-- net cloudron \
-- net - alias mail \
- m $ { memoryLimit } m \
-- memory - swap $ { memoryLimit * 2 } m \
-- dns 172.18 . 0.1 \
-- dns - search = . \
-- env ENABLE _MDA = $ { mailConfig . enabled } \
- v "${paths.MAIL_DATA_DIR}:/app/data" \
- v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
$ { ports } \
-- read - only - v / run - v / tmp $ { tag } ` ;
shell . execSync ( 'startMail' , cmd ) ;
if ( ! mailConfig . enabled || process . env . BOX _ENV === 'test' ) return callback ( ) ;
// Add MX and DMARC record. Note that DMARC policy depends on DKIM signing and thus works
// only if we use our internal mail server.
var records = [
{ subdomain : '_dmarc' , type : 'TXT' , values : [ '"v=DMARC1; p=reject; pct=100"' ] } ,
{ subdomain : '' , type : 'MX' , values : [ '10 ' + config . mailFqdn ( ) + '.' ] }
] ;
async . mapSeries ( records , function ( record , iteratorCallback ) {
domains . upsertDNSRecords ( record . subdomain , config . fqdn ( ) , record . type , record . values , iteratorCallback ) ;
} , NOOP _CALLBACK ) ; // do not crash if DNS creds do not work in startup sequence
callback ( ) ;
} ) ;
} ) ;
} ) ;
}
2018-01-20 22:56:45 -08:00
function get ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-20 22:56:45 -08:00
maildb . get ( domain , function ( error , result ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND ) ) ;
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-01-20 22:56:45 -08:00
return callback ( null , result ) ;
2018-01-20 18:56:17 -08:00
} ) ;
}
2018-01-20 23:17:39 -08:00
function setMailFromValidation ( domain , enabled , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof enabled , 'boolean' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-21 00:06:08 -08:00
maildb . update ( domain , { mailFromValidation : enabled } , function ( error ) {
2018-01-20 23:17:39 -08:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND ) ) ;
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
createMailConfig ( NOOP _CALLBACK ) ;
callback ( null ) ;
} ) ;
}
2018-01-20 23:17:39 -08:00
function setCatchAllAddress ( domain , address , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2018-01-20 18:56:17 -08:00
assert ( Array . isArray ( address ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-20 23:17:39 -08:00
maildb . update ( domain , { catchAll : address } , function ( error ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND ) ) ;
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
createMailConfig ( NOOP _CALLBACK ) ;
callback ( null ) ;
} ) ;
}
2018-01-20 23:17:39 -08:00
function setMailRelay ( domain , relay , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof relay , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
verifyRelay ( relay , function ( error ) {
if ( error ) return callback ( error ) ;
2018-01-20 23:17:39 -08:00
maildb . update ( domain , { relay : relay } , function ( error ) {
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-01-20 20:34:30 -08:00
restartMail ( NOOP _CALLBACK ) ;
2018-01-20 18:56:17 -08:00
callback ( null ) ;
} ) ;
} ) ;
}
2018-01-20 23:17:39 -08:00
function setMailEnabled ( domain , enabled , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof enabled , 'boolean' ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-20 23:17:39 -08:00
maildb . update ( domain , { enabled : enabled } , function ( error ) {
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-01-20 20:34:30 -08:00
restartMail ( NOOP _CALLBACK ) ;
2018-01-20 18:56:17 -08:00
callback ( null ) ;
} ) ;
}