2017-06-28 17:06:12 -05:00
'use strict' ;
exports = module . exports = {
getStatus : getStatus ,
2019-02-28 16:46:30 -08:00
checkConfiguration : checkConfiguration ,
2017-06-28 17:06:12 -05:00
2018-04-03 14:37:52 -07:00
getDomains : getDomains ,
2018-01-20 18:56:17 -08:00
2018-04-03 14:37:52 -07:00
getDomain : getDomain ,
addDomain : addDomain ,
removeDomain : removeDomain ,
2018-12-07 14:35:04 -08:00
clearDomains : clearDomains ,
2018-01-24 21:15:58 -08:00
2019-02-15 10:55:15 -08:00
removePrivateFields : removePrivateFields ,
2018-07-25 10:44:59 -07:00
setDnsRecords : setDnsRecords ,
2019-02-26 19:43:18 -08:00
onMailFqdnChanged : onMailFqdnChanged ,
2018-03-08 20:08:01 -08:00
2018-05-24 16:25:32 -07:00
validateName : validateName ,
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 ,
2019-02-28 10:41:58 -08:00
restartMail : restartMail ,
2019-03-04 15:20:58 -08:00
handleCertChanged : handleCertChanged ,
2018-01-20 18:56:17 -08:00
2018-01-23 16:10:23 -08:00
sendTestMail : sendTestMail ,
2018-12-06 10:23:10 -08:00
listMailboxes : listMailboxes ,
2018-02-11 01:18:29 -08:00
removeMailboxes : removeMailboxes ,
2018-04-03 12:18:26 -07:00
getMailbox : getMailbox ,
addMailbox : addMailbox ,
2018-12-06 11:41:16 -08:00
updateMailboxOwner : updateMailboxOwner ,
2018-04-03 12:18:26 -07:00
removeMailbox : removeMailbox ,
2018-01-24 13:11:35 +01:00
2018-04-03 12:18:26 -07:00
listAliases : listAliases ,
2018-01-25 18:03:02 +01:00
getAliases : getAliases ,
2018-04-03 12:18:26 -07:00
setAliases : setAliases ,
2018-01-25 18:03:02 +01:00
2018-01-26 10:22:50 +01:00
getLists : getLists ,
getList : getList ,
addList : addList ,
2018-04-03 14:12:43 -07:00
updateList : updateList ,
2018-01-26 10:22:50 +01:00
removeList : removeList ,
2018-01-25 15:38:29 -08:00
_readDkimPublicKeySync : readDkimPublicKeySync ,
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' ) ,
2019-02-15 10:55:15 -08:00
constants = require ( './constants.js' ) ,
2018-01-20 18:56:17 -08:00
DatabaseError = require ( './databaseerror.js' ) ,
debug = require ( 'debug' ) ( 'box:mail' ) ,
2018-02-08 10:21:31 -08:00
dns = require ( './native-dns.js' ) ,
2018-01-20 20:34:30 -08:00
domains = require ( './domains.js' ) ,
2018-11-09 18:45:44 -08:00
eventlog = require ( './eventlog.js' ) ,
2018-12-01 21:15:42 -08:00
hat = require ( './hat.js' ) ,
2018-01-20 20:34:30 -08:00
infra = require ( './infra_version.js' ) ,
2018-01-24 13:11:35 +01:00
mailboxdb = require ( './mailboxdb.js' ) ,
2018-01-20 22:56:45 -08:00
maildb = require ( './maildb.js' ) ,
2018-01-23 16:10:23 -08:00
mailer = require ( './mailer.js' ) ,
2017-06-28 17:06:12 -05:00
net = require ( 'net' ) ,
nodemailer = require ( 'nodemailer' ) ,
2018-01-24 21:30:06 -08:00
path = require ( 'path' ) ,
2018-01-20 18:56:17 -08:00
paths = require ( './paths.js' ) ,
2018-01-30 12:23:27 -08:00
reverseProxy = require ( './reverseproxy.js' ) ,
2017-06-28 21:38:51 -05:00
safe = require ( 'safetydance' ) ,
2019-07-26 10:49:29 -07:00
settings = require ( './settings.js' ) ,
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-04-29 10:58:45 -07:00
users = require ( './users.js' ) ,
2017-06-28 17:06:12 -05:00
util = require ( 'util' ) ,
2019-09-11 12:44:15 -07:00
validator = require ( 'validator' ) ,
2017-06-28 17:06:12 -05:00
_ = require ( 'underscore' ) ;
2019-03-25 11:43:01 -07:00
const DNS _OPTIONS = { 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' ;
2018-05-29 13:31:40 +02:00
MailError . EXTERNAL _ERROR = 'External Error' ;
2018-01-20 18:19:26 -08:00
MailError . BAD _FIELD = 'Bad Field' ;
2018-01-24 15:38:19 +01:00
MailError . ALREADY _EXISTS = 'Already Exists' ;
2018-01-21 00:16:58 -08:00
MailError . NOT _FOUND = 'Not Found' ;
2018-02-11 00:04:28 -08:00
MailError . IN _USE = 'In Use' ;
2017-06-28 17:06:12 -05:00
2018-04-03 09:36:41 -07:00
function validateName ( name ) {
assert . strictEqual ( typeof name , 'string' ) ;
2018-01-25 18:03:02 +01:00
2018-04-03 09:36:41 -07:00
if ( name . length < 1 ) return new MailError ( MailError . BAD _FIELD , 'mailbox name must be atleast 1 char' ) ;
if ( name . length >= 200 ) return new MailError ( MailError . BAD _FIELD , 'mailbox name too long' ) ;
2018-01-25 18:03:02 +01:00
2018-07-26 23:48:18 -07:00
// also need to consider valid LDAP characters here (e.g '+' is reserved)
if ( /[^a-zA-Z0-9.-]/ . test ( name ) ) return new MailError ( MailError . BAD _FIELD , 'mailbox name can only contain alphanumerals and dot' ) ;
2018-01-25 18:03:02 +01:00
return null ;
}
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.comcast.net' ,
2018-02-08 11:48:55 -08:00
'smtp.1und1.de' ,
2017-06-28 21:38:51 -05:00
] ) ;
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 ;
2019-01-25 10:27:44 -08:00
relay . value = ` Connect to ${ smtpServer } timed out. Check if port 25 (outbound) is blocked ` ;
2017-06-28 21:38:51 -05:00
client . destroy ( ) ;
callback ( new Error ( 'Timeout' ) , relay ) ;
} ) ;
client . on ( 'error' , function ( error ) {
relay . status = false ;
2019-01-25 10:27:44 -08:00
relay . value = ` Connect to ${ smtpServer } failed: ${ error . message } . Check if port 25 (outbound) is blocked ` ;
2017-06-28 21:38:51 -05:00
client . destroy ( ) ;
callback ( error , relay ) ;
} ) ;
}
function checkSmtpRelay ( relay , callback ) {
var result = {
value : 'OK' ,
status : false
} ;
2017-06-28 17:06:12 -05:00
2019-04-22 14:41:44 +02:00
var options = {
2018-07-23 17:05:15 -07:00
connectionTimeout : 5000 ,
greetingTimeout : 5000 ,
2017-06-28 17:06:12 -05:00
host : relay . host ,
2019-04-22 14:41:44 +02:00
port : relay . port
} ;
// only set auth if either username or password is provided, some relays auth based on IP (range)
if ( relay . username || relay . password ) {
options . auth = {
2017-06-28 17:06:12 -05:00
user : relay . username ,
pass : relay . password
2019-04-22 14:41:44 +02:00
} ;
}
2019-04-23 15:19:33 -07:00
if ( relay . acceptSelfSignedCerts ) options . tls = { rejectUnauthorized : false } ;
2019-04-22 14:41:44 +02:00
var transporter = nodemailer . createTransport ( smtpTransport ( options ) ) ;
2017-06-28 17:06:12 -05:00
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-03-08 23:23:02 -08:00
// we used to verify cloudron-smtp with checkOutboundPort25 but that is unreliable given that we just
// randomly select some smtp server
2019-03-15 09:45:42 -07:00
if ( relay . provider === 'cloudron-smtp' || relay . provider === 'noop' ) return callback ( ) ;
2017-06-28 17:06:12 -05:00
2018-03-08 23:23:02 -08:00
checkSmtpRelay ( relay , 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
2019-06-10 12:23:29 -07:00
function checkDkim ( mailDomain , callback ) {
assert . strictEqual ( typeof mailDomain , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
const domain = mailDomain . domain ;
let dkim = {
domain : ` ${ mailDomain . dkimSelector } ._domainkey. ${ domain } ` ,
name : ` ${ mailDomain . dkimSelector } ._domainkey ` ,
2017-06-28 21:38:51 -05:00
type : 'TXT' ,
expected : null ,
value : null ,
status : false
} ;
2017-06-28 17:06:12 -05:00
2018-01-25 14:51:07 -08:00
var dkimKey = readDkimPublicKeySync ( domain ) ;
2017-06-28 21:38:51 -05:00
if ( ! dkimKey ) return callback ( new Error ( 'Failed to read dkim public key' ) , dkim ) ;
2017-06-28 17:06:12 -05:00
2018-02-08 10:21:31 -08:00
dkim . expected = 'v=DKIM1; t=s; p=' + dkimKey ;
2017-06-28 17:06:12 -05:00
2018-02-08 10:21:31 -08:00
dns . resolve ( dkim . domain , dkim . type , DNS _OPTIONS , function ( error , txtRecords ) {
2017-06-28 21:38:51 -05:00
if ( error ) return callback ( error , dkim ) ;
2017-06-28 17:06:12 -05:00
2018-02-08 10:21:31 -08:00
if ( txtRecords . length !== 0 ) {
dkim . value = txtRecords [ 0 ] . join ( '' ) ;
2017-06-28 21:38:51 -05:00
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
2019-01-31 15:08:14 -08:00
function checkSpf ( domain , mailFqdn , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-06-28 21:38:51 -05:00
var spf = {
2018-01-24 11:33:09 -08:00
domain : domain ,
2018-07-24 14:03:39 -07:00
name : '@' ,
2017-06-28 21:38:51 -05:00
type : 'TXT' ,
value : null ,
2019-01-31 15:08:14 -08:00
expected : 'v=spf1 a:' + mailFqdn + ' ~all' ,
2017-06-28 21:38:51 -05:00
status : false
} ;
2018-02-08 10:21:31 -08:00
dns . resolve ( spf . domain , spf . type , DNS _OPTIONS , function ( error , txtRecords ) {
2017-06-28 21:38:51 -05:00
if ( error ) return callback ( error , spf ) ;
var i ;
for ( i = 0 ; i < txtRecords . length ; i ++ ) {
2018-02-08 10:21:31 -08:00
let txtRecord = txtRecords [ i ] . join ( '' ) ; // https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
if ( txtRecord . indexOf ( 'v=spf1 ' ) !== 0 ) continue ; // not SPF
spf . value = txtRecord ;
2019-07-26 10:49:29 -07:00
spf . status = spf . value . indexOf ( ' a:' + settings . adminFqdn ( ) ) !== - 1 ;
2017-06-28 21:38:51 -05:00
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 ) {
2019-07-26 10:49:29 -07:00
spf . expected = 'v=spf1 a:' + settings . adminFqdn ( ) + ' ' + spf . value . slice ( 'v=spf1 ' . length ) ;
2017-06-28 21:38:51 -05:00
}
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
2019-01-31 15:08:14 -08:00
function checkMx ( domain , mailFqdn , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-06-28 21:38:51 -05:00
var mx = {
2018-01-24 11:33:09 -08:00
domain : domain ,
2018-07-24 14:03:39 -07:00
name : '@' ,
2017-06-28 21:38:51 -05:00
type : 'MX' ,
value : null ,
2019-01-31 15:08:14 -08:00
expected : '10 ' + mailFqdn + '.' ,
2017-06-28 21:38:51 -05:00
status : false
} ;
2018-02-08 10:21:31 -08:00
dns . resolve ( mx . domain , mx . type , DNS _OPTIONS , function ( error , mxRecords ) {
2017-06-28 21:38:51 -05:00
if ( error ) return callback ( error , mx ) ;
2019-05-20 17:56:16 -07:00
if ( mxRecords . length === 0 ) return callback ( null , mx ) ;
2017-06-28 21:38:51 -05:00
2019-05-20 17:56:16 -07:00
mx . status = mxRecords . length == 1 && mxRecords [ 0 ] . exchange === mailFqdn ;
mx . value = mxRecords . map ( function ( r ) { return r . priority + ' ' + r . exchange + '.' ; } ) . join ( ' ' ) ;
if ( mx . status ) return callback ( null , mx ) ; // MX record is "my."
// cloudflare might create a conflict subdomain (https://support.cloudflare.com/hc/en-us/articles/360020296512-DNS-Troubleshooting-FAQ)
dns . resolve ( mxRecords [ 0 ] . exchange , 'A' , DNS _OPTIONS , function ( error , mxIps ) {
if ( error || mxIps . length !== 1 ) return callback ( null , mx ) ;
sysinfo . getPublicIp ( function ( error , ip ) {
if ( error ) return callback ( null , mx ) ;
mx . status = mxIps [ 0 ] === ip ;
2017-06-28 17:06:12 -05:00
2019-05-20 17:56:16 -07:00
callback ( null , mx ) ;
} ) ;
} ) ;
2017-06-28 21:38:51 -05:00
} ) ;
}
2017-06-28 17:06:12 -05:00
2018-08-12 13:43:45 -07:00
function txtToDict ( txt ) {
var dict = { } ;
txt . split ( ';' ) . forEach ( function ( v ) {
var p = v . trim ( ) . split ( '=' ) ;
dict [ p [ 0 ] ] = p [ 1 ] ;
} ) ;
return dict ;
}
2018-01-24 11:33:09 -08:00
function checkDmarc ( domain , callback ) {
2017-06-28 21:38:51 -05:00
var dmarc = {
2018-01-24 11:33:09 -08:00
domain : '_dmarc.' + domain ,
2018-07-24 14:03:39 -07:00
name : '_dmarc' ,
2017-06-28 21:38:51 -05:00
type : 'TXT' ,
value : null ,
2018-02-08 10:21:31 -08:00
expected : 'v=DMARC1; p=reject; pct=100' ,
2017-06-28 21:38:51 -05:00
status : false
} ;
2018-02-08 10:21:31 -08:00
dns . resolve ( dmarc . domain , dmarc . type , DNS _OPTIONS , function ( error , txtRecords ) {
2017-06-28 21:38:51 -05:00
if ( error ) return callback ( error , dmarc ) ;
2018-02-08 10:21:31 -08:00
if ( txtRecords . length !== 0 ) {
dmarc . value = txtRecords [ 0 ] . join ( '' ) ;
2018-08-12 13:43:45 -07:00
// allow extra fields in dmarc like rua
const actual = txtToDict ( dmarc . value ) , expected = txtToDict ( dmarc . expected ) ;
dmarc . status = Object . keys ( expected ) . every ( k => expected [ k ] === actual [ k ] ) ;
2017-06-28 21:38:51 -05:00
}
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
2019-01-31 15:08:14 -08:00
function checkPtr ( mailFqdn , callback ) {
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-06-28 21:38:51 -05:00
var ptr = {
domain : null ,
type : 'PTR' ,
value : null ,
2019-01-31 15:08:14 -08:00
expected : mailFqdn , // any trailing '.' is added by client software (https://lists.gt.net/spf/devel/7918)
2017-06-28 21:38:51 -05:00
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
2018-02-08 11:52:43 -08:00
dns . resolve ( ptr . domain , 'PTR' , DNS _OPTIONS , function ( error , ptrRecords ) {
2017-06-28 21:38:51 -05:00
if ( error ) return callback ( error , ptr ) ;
2017-06-28 17:06:12 -05:00
2018-02-08 10:21:31 -08:00
if ( ptrRecords . length !== 0 ) {
2017-06-28 21:38:51 -05:00
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 = [
2018-03-05 14:26:53 -08:00
{
'name' : 'Abuse.ch' ,
'dns' : 'spam.abuse.ch' ,
'site' : 'http://abuse.ch/'
} ,
2017-09-08 11:50:11 -07:00
{
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
} ,
{
2018-03-05 14:26:53 -08:00
'name' : 'Composite Blocking List' ,
'dns' : 'cbl.abuseat.org' ,
'site' : 'http://www.abuseat.org'
} ,
{
'name' : 'Multi SURBL' ,
'dns' : 'multi.surbl.org' ,
'site' : 'http://www.surbl.org'
} ,
{
'name' : 'Passive Spam Block List' ,
'dns' : 'psbl.surriel.com' ,
'site' : 'https://psbl.org'
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
} ,
{
2018-03-05 14:26:53 -08:00
'name' : 'SpamCop' ,
'dns' : 'bl.spamcop.net' ,
'site' : 'http://spamcop.net'
} ,
{
'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' : '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
}
] ;
2018-01-24 11:33:09 -08:00
// this function currently only looks for black lists based on IP. TODO: also look up by domain
function checkRblStatus ( domain , 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 ) {
2018-02-08 10:21:31 -08:00
dns . resolve ( flippedIp + '.' + rblServer . dns , 'A' , DNS _OPTIONS , function ( error , records ) {
2017-09-08 11:50:11 -07:00
if ( error || ! records ) return iteratorDone ( null , null ) ; // not listed
2018-01-24 11:33:09 -08:00
debug ( 'checkRblStatus: %s (ip: %s) is in the blacklist of %j' , domain , flippedIp , rblServer ) ;
2017-09-08 11:50:11 -07:00
var result = _ . extend ( { } , rblServer ) ;
2018-02-08 10:21:31 -08:00
dns . resolve ( flippedIp + '.' + rblServer . dns , 'TXT' , DNS _OPTIONS , function ( error , txtRecords ) {
result . txtRecords = error || ! txtRecords ? 'No txt record' : txtRecords . map ( x => x . join ( '' ) ) ;
2017-09-08 11:50:11 -07:00
2018-01-24 11:33:09 -08:00
debug ( 'checkRblStatus: %s (error: %s) (txtRecords: %j)' , domain , error , txtRecords ) ;
2017-09-08 11:50:11 -07:00
return iteratorDone ( null , result ) ;
} ) ;
} ) ;
} , function ( ignoredError , blacklistedServers ) {
blacklistedServers = blacklistedServers . filter ( function ( b ) { return b !== null ; } ) ;
2018-01-24 11:33:09 -08:00
debug ( 'checkRblStatus: %s (ip: %s) servers: %j' , domain , ip , blacklistedServers ) ;
2017-09-13 22:39:42 -07:00
return callback ( null , { status : blacklistedServers . length === 0 , ip : ip , servers : blacklistedServers } ) ;
} ) ;
} ) ;
}
2018-01-21 00:40:30 -08:00
function getStatus ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2017-09-13 22:39:42 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-23 12:00:24 +01:00
// ensure we always have a valid toplevel properties for the api
var results = {
2019-02-28 16:46:30 -08:00
dns : { } , // { mx: { expected, value }, dmarc: { expected, value }, dkim: { expected, value }, spf: { expected, value }, ptr: { expected, value } }
2019-03-06 19:48:18 -08:00
rbl : { } , // { status, ip, servers: [{name,site,dns}]} optional. only for cloudron-smtp
relay : { } // { status, value } always checked
2018-01-23 12:00:24 +01:00
} ;
2017-09-13 22:39:42 -07:00
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
2019-07-26 10:49:29 -07:00
const mailFqdn = settings . mailFqdn ( ) ;
2019-01-31 15:08:14 -08:00
2019-06-10 12:23:29 -07:00
getDomain ( domain , function ( error , mailDomain ) {
2017-09-13 22:39:42 -07:00
if ( error ) return callback ( error ) ;
2017-09-08 11:50:11 -07:00
2019-02-06 15:23:41 -08:00
let checks = [ ] ;
2019-06-10 12:23:29 -07:00
if ( mailDomain . enabled ) {
2019-02-06 15:23:41 -08:00
checks . push (
recordResult ( 'dns.mx' , checkMx . bind ( null , domain , mailFqdn ) ) ,
recordResult ( 'dns.dmarc' , checkDmarc . bind ( null , domain ) )
) ;
}
2017-09-13 22:39:42 -07:00
2019-06-10 12:23:29 -07:00
if ( mailDomain . 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 (
2019-01-31 15:08:14 -08:00
recordResult ( 'dns.spf' , checkSpf . bind ( null , domain , mailFqdn ) ) ,
2019-06-10 12:23:29 -07:00
recordResult ( 'dns.dkim' , checkDkim . bind ( null , mailDomain ) ) ,
2019-01-31 15:08:14 -08:00
recordResult ( 'dns.ptr' , checkPtr . bind ( null , mailFqdn ) ) ,
2017-09-13 22:39:42 -07:00
recordResult ( 'relay' , checkOutboundPort25 ) ,
2018-01-24 11:33:09 -08:00
recordResult ( 'rbl' , checkRblStatus . bind ( null , domain ) )
2017-09-13 22:39:42 -07:00
) ;
2019-06-10 12:23:29 -07:00
} else if ( mailDomain . relay . provider !== 'noop' ) {
checks . push ( recordResult ( 'relay' , checkSmtpRelay . bind ( null , mailDomain . 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
2019-02-28 16:46:30 -08:00
function checkConfiguration ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
let messages = { } ;
domains . getAll ( function ( error , allDomains ) {
if ( error ) return callback ( error ) ;
async . eachSeries ( allDomains , function ( domainObject , iteratorCallback ) {
getStatus ( domainObject . domain , function ( error , result ) {
if ( error ) return iteratorCallback ( error ) ;
let message = [ ] ;
Object . keys ( result . dns ) . forEach ( ( type ) => {
const record = result . dns [ type ] ;
if ( ! record . status ) message . push ( ` ${ type . toUpperCase ( ) } DNS record did not match. Expected: \` ${ record . expected } \` . Actual: \` ${ record . value } \` ` ) ;
} ) ;
2019-03-17 17:48:14 -07:00
if ( result . relay && result . relay . status === false ) message . push ( ` Relay error: ${ result . relay . value } ` ) ;
2019-02-28 16:46:30 -08:00
if ( result . rbl && result . rbl . status === false ) { // rbl field contents is optional
const servers = result . rbl . servers . map ( ( bs ) => ` [ ${ bs . name } ]( ${ bs . site } ) ` ) ; // in markdown
message . push ( ` This server's IP \` ${ result . rbl . ip } \` is blacklisted in the following servers - ${ servers . join ( ', ' ) } ` ) ;
}
if ( message . length ) messages [ domainObject . domain ] = message ;
iteratorCallback ( null ) ;
} ) ;
} , function ( error ) {
if ( error ) return callback ( error ) ;
// create bulleted list for each domain
let markdownMessage = '' ;
Object . keys ( messages ) . forEach ( ( domain ) => {
markdownMessage += ` ** ${ domain } ** \n ` ;
markdownMessage += messages [ domain ] . map ( ( m ) => ` * ${ m } \n ` ) . join ( '' ) ;
markdownMessage += '\n\n' ;
} ) ;
2019-08-04 15:35:42 -07:00
if ( markdownMessage ) markdownMessage += 'Email Status is checked every 30 minutes.\n See the [troubleshooting docs](https://cloudron.io/documentation/troubleshooting/#mail-dns) for more information.\n' ;
2019-03-01 11:24:10 -08:00
2019-03-07 11:50:49 -08:00
callback ( null , markdownMessage ) ; // empty message means all status checks succeeded
2019-02-28 16:46:30 -08:00
} ) ;
} ) ;
}
2019-09-12 16:08:29 -07:00
function createMailConfig ( mailFqdn , mailDomain , callback ) {
2019-01-31 15:08:14 -08:00
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
2019-09-12 16:08:29 -07:00
assert . strictEqual ( typeof mailDomain , 'string' ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'createMailConfig: generating mail config' ) ;
2018-04-03 14:37:52 -07:00
getDomains ( function ( error , mailDomains ) {
2018-01-24 11:33:09 -08:00
if ( error ) return callback ( error ) ;
2018-01-20 18:56:17 -08:00
2019-05-07 11:29:59 -07:00
const mailOutDomains = mailDomains . filter ( d => d . relay . provider !== 'noop' ) . map ( d => d . domain ) . join ( ',' ) ;
const mailInDomains = mailDomains . filter ( function ( d ) { return d . enabled ; } ) . map ( function ( d ) { return d . domain ; } ) . join ( ',' ) ;
2018-02-05 15:02:34 -08:00
2019-09-12 16:08:29 -07:00
// mail_domain is used for SRS
2019-05-07 11:29:59 -07:00
if ( ! safe . fs . writeFileSync ( path . join ( paths . ADDON _CONFIG _DIR , 'mail/mail.ini' ) ,
2019-09-12 16:08:29 -07:00
` mail_in_domains= ${ mailInDomains } \n mail_out_domains= ${ mailOutDomains } \n mail_server_name= ${ mailFqdn } \n mail_domain= ${ mailDomain } \n \n ` , 'utf8' ) ) {
2019-05-07 11:29:59 -07:00
return callback ( new Error ( 'Could not create mail var file:' + safe . error . message ) ) ;
}
2018-01-20 18:56:17 -08:00
2019-05-07 11:29:59 -07:00
// enable_outbound makes plugin forward email for relayed mail. non-relayed mail always hits LMTP plugin first
if ( ! safe . fs . writeFileSync ( path . join ( paths . ADDON _CONFIG _DIR , 'mail/smtp_forward.ini' ) , 'enable_outbound=false\ndomain_selector=mail_from\n' , 'utf8' ) ) {
return callback ( new Error ( 'Could not create smtp forward file:' + safe . error . message ) ) ;
}
2018-01-20 18:56:17 -08:00
2019-05-07 11:29:59 -07:00
// create sections for per-domain configuration
mailDomains . forEach ( function ( domain ) {
const catchAll = domain . catchAll . map ( function ( c ) { return ` ${ c } @ ${ domain . domain } ` ; } ) . join ( ',' ) ;
const mailFromValidation = domain . mailFromValidation ;
2018-01-20 18:56:17 -08:00
2019-05-07 11:29:59 -07:00
if ( ! safe . fs . appendFileSync ( path . join ( paths . ADDON _CONFIG _DIR , 'mail/mail.ini' ) ,
` [ ${ domain . domain } ] \n catch_all= ${ catchAll } \n mail_from_validation= ${ mailFromValidation } \n \n ` , 'utf8' ) ) {
return callback ( new Error ( 'Could not create mail var file:' + safe . error . message ) ) ;
2018-02-05 15:02:34 -08:00
}
2018-01-20 18:56:17 -08:00
2019-05-07 11:29:59 -07:00
const relay = domain . relay ;
2018-02-05 15:02:34 -08:00
2019-05-07 11:29:59 -07:00
const enableRelay = relay . provider !== 'cloudron-smtp' && relay . provider !== 'noop' ,
host = relay . host || '' ,
port = relay . port || 25 ,
2019-05-15 16:23:19 -07:00
authType = relay . username ? 'plain' : '' ,
2019-05-07 11:29:59 -07:00
username = relay . username || '' ,
password = relay . password || '' ;
2018-02-06 14:36:11 -08:00
2019-05-07 11:29:59 -07:00
if ( ! enableRelay ) return ;
2018-01-20 18:56:17 -08:00
2019-05-07 11:29:59 -07:00
if ( ! safe . fs . appendFileSync ( paths . ADDON _CONFIG _DIR + '/mail/smtp_forward.ini' ,
2019-05-15 16:23:19 -07:00
` [ ${ domain . domain } ] \n enable_outbound=true \n host= ${ host } \n port= ${ port } \n enable_tls=true \n auth_type= ${ authType } \n auth_user= ${ username } \n auth_pass= ${ password } \n \n ` , 'utf8' ) ) {
2019-05-07 11:29:59 -07:00
return callback ( new Error ( 'Could not create mail var file:' + safe . error . message ) ) ;
}
2018-01-20 18:56:17 -08:00
} ) ;
2019-05-07 11:29:59 -07:00
callback ( null , mailInDomains . length !== 0 /* allowInbound */ ) ;
2018-01-20 18:56:17 -08:00
} ) ;
}
2019-02-04 17:10:07 -08:00
function configureMail ( mailFqdn , mailDomain , callback ) {
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
assert . strictEqual ( typeof mailDomain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-20 20:34:30 -08:00
// 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
const tag = infra . images . mail . tag ;
2018-11-11 10:45:56 -08:00
const memoryLimit = 4 * 256 ;
2018-12-28 13:32:37 -08:00
const cloudronToken = hat ( 8 * 128 ) , relayToken = hat ( 8 * 128 ) ;
2018-01-20 20:34:30 -08:00
2019-02-04 17:10:07 -08:00
reverseProxy . getCertificate ( mailFqdn , mailDomain , function ( error , bundle ) {
2018-01-20 20:34:30 -08:00
if ( error ) return callback ( error ) ;
// the setup script copies dhparams.pem to /addons/mail
2018-01-31 18:20:29 -08:00
const mailCertFilePath = path . join ( paths . ADDON _CONFIG _DIR , 'mail/tls_cert.pem' ) ;
2018-01-31 22:31:14 -08:00
const mailKeyFilePath = path . join ( paths . ADDON _CONFIG _DIR , 'mail/tls_key.pem' ) ;
2018-01-31 18:20:29 -08:00
if ( ! safe . child _process . execSync ( ` cp ${ bundle . certFilePath } ${ mailCertFilePath } ` ) ) return callback ( new Error ( 'Could not create cert file:' + safe . error . message ) ) ;
if ( ! safe . child _process . execSync ( ` cp ${ bundle . keyFilePath } ${ mailKeyFilePath } ` ) ) return callback ( new Error ( 'Could not create key file:' + safe . error . message ) ) ;
2018-01-20 20:34:30 -08:00
2018-11-23 10:57:54 -08:00
shell . exec ( 'startMail' , 'docker rm -f mail || true' , function ( error ) {
2018-01-24 11:33:09 -08:00
if ( error ) return callback ( error ) ;
2018-01-20 20:34:30 -08:00
2019-09-12 16:08:29 -07:00
createMailConfig ( mailFqdn , mailDomain , function ( error , allowInbound ) {
2018-11-23 10:57:54 -08:00
if ( error ) return callback ( error ) ;
var ports = allowInbound ? '-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 \
-- log - driver syslog \
-- log - opt syslog - address = udp : //127.0.0.1:2514 \
-- log - opt syslog - format = rfc5424 \
-- log - opt tag = mail \
- m $ { memoryLimit } m \
-- memory - swap $ { memoryLimit * 2 } m \
-- dns 172.18 . 0.1 \
-- dns - search = . \
2018-12-01 21:15:42 -08:00
- e CLOUDRON _MAIL _TOKEN = "${cloudronToken}" \
2018-12-28 13:32:37 -08:00
- e CLOUDRON _RELAY _TOKEN = "${relayToken}" \
2018-11-23 10:57:54 -08:00
- v "${paths.MAIL_DATA_DIR}:/app/data" \
- v "${paths.PLATFORM_DATA_DIR}/addons/mail:/etc/mail" \
$ { ports } \
- p 127.0 . 0.1 : 2020 : 2020 \
-- label isCloudronManaged = true \
-- read - only - v / run - v / tmp $ { tag } ` ;
shell . exec ( 'startMail' , cmd , callback ) ;
} ) ;
2018-01-20 20:34:30 -08:00
} ) ;
} ) ;
}
2019-02-04 17:10:07 -08:00
function restartMail ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
if ( process . env . BOX _ENV === 'test' && ! process . env . TEST _CREATE _INFRA ) return callback ( ) ;
2019-07-26 10:49:29 -07:00
debug ( ` restartMail: restarting mail container with ${ settings . mailFqdn ( ) } ${ settings . adminDomain ( ) } ` ) ;
configureMail ( settings . mailFqdn ( ) , settings . adminDomain ( ) , callback ) ;
2019-02-04 17:10:07 -08:00
}
2018-09-26 10:26:33 -07:00
function restartMailIfActivated ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-11-10 18:08:08 -08:00
users . isActivated ( function ( error , activated ) {
2018-09-26 10:26:33 -07:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-11-10 18:08:08 -08:00
if ( ! activated ) {
2018-09-26 10:26:33 -07:00
debug ( 'restartMailIfActivated: skipping restart of mail container since Cloudron is not activated yet' ) ;
return callback ( ) ; // not provisioned yet, do not restart container after dns setup
}
restartMail ( callback ) ;
} ) ;
}
2019-03-04 15:20:58 -08:00
function handleCertChanged ( callback ) {
2019-03-04 18:11:07 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'handleCertChanged: will restart if activated' ) ;
2019-03-04 15:20:58 -08:00
restartMailIfActivated ( callback ) ;
}
2018-04-03 14:37:52 -07:00
function getDomain ( domain , callback ) {
2018-01-20 22:56:45 -08:00
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-04-03 14:37:52 -07:00
function getDomains ( callback ) {
2018-01-24 11:33:09 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-06 11:41:16 -08:00
maildb . list ( function ( error , results ) {
2018-01-24 11:33:09 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
return callback ( null , results ) ;
} ) ;
}
2018-01-25 13:48:53 -08:00
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
2019-01-31 15:08:14 -08:00
function txtRecordsWithSpf ( domain , mailFqdn , callback ) {
2018-02-06 23:04:27 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2019-01-31 15:08:14 -08:00
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
2018-01-25 13:48:53 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-02-08 12:05:29 -08:00
domains . getDnsRecords ( '' , domain , 'TXT' , function ( error , txtRecords ) {
2018-09-06 12:26:11 -07:00
if ( error ) return new MailError ( MailError . EXTERNAL _ERROR , error . message ) ;
2018-01-25 13:48:53 -08:00
debug ( 'txtRecordsWithSpf: current txt records - %j' , txtRecords ) ;
var i , matches , validSpf ;
for ( i = 0 ; i < txtRecords . length ; i ++ ) {
matches = txtRecords [ i ] . match ( /^("?v=spf1) / ) ; // DO backend may return without quotes
if ( matches === null ) continue ;
// this won't work if the entry is arbitrarily "split" across quoted strings
2019-01-31 15:08:14 -08:00
validSpf = txtRecords [ i ] . indexOf ( 'a:' + mailFqdn ) !== - 1 ;
2018-01-25 13:48:53 -08:00
break ; // there can only be one SPF record
}
if ( validSpf ) return callback ( null , null ) ;
if ( ! matches ) { // no spf record was found, create one
2019-01-31 15:08:14 -08:00
txtRecords . push ( '"v=spf1 a:' + mailFqdn + ' ~all"' ) ;
2018-01-25 13:48:53 -08:00
debug ( 'txtRecordsWithSpf: adding txt record' ) ;
} else { // just add ourself
2019-01-31 15:08:14 -08:00
txtRecords [ i ] = matches [ 1 ] + ' a:' + mailFqdn + txtRecords [ i ] . slice ( matches [ 1 ] . length ) ;
2018-01-25 13:48:53 -08:00
debug ( 'txtRecordsWithSpf: inserting txt record' ) ;
}
return callback ( null , txtRecords ) ;
} ) ;
}
2019-06-10 12:23:29 -07:00
function ensureDkimKeySync ( mailDomain ) {
assert . strictEqual ( typeof mailDomain , 'object' ) ;
2018-03-08 12:04:32 -08:00
2019-06-10 12:23:29 -07:00
const domain = mailDomain . domain ;
2018-03-08 17:26:07 -08:00
const dkimPath = path . join ( paths . MAIL _DATA _DIR , ` dkim/ ${ domain } ` ) ;
const dkimPrivateKeyFile = path . join ( dkimPath , 'private' ) ;
const dkimPublicKeyFile = path . join ( dkimPath , 'public' ) ;
const dkimSelectorFile = path . join ( dkimPath , 'selector' ) ;
2018-03-08 15:29:18 -08:00
2018-03-08 17:26:07 -08:00
if ( safe . fs . existsSync ( dkimPublicKeyFile ) &&
safe . fs . existsSync ( dkimPublicKeyFile ) &&
safe . fs . existsSync ( dkimPublicKeyFile ) ) {
debug ( ` Reusing existing DKIM keys for ${ domain } ` ) ;
return null ;
}
debug ( ` Generating new DKIM keys for ${ domain } ` ) ;
2018-03-08 12:04:32 -08:00
if ( ! safe . fs . mkdirSync ( dkimPath ) && safe . error . code !== 'EEXIST' ) {
debug ( 'Error creating dkim.' , safe . error ) ;
return new MailError ( MailError . INTERNAL _ERROR , safe . error ) ;
}
if ( ! safe . child _process . execSync ( 'openssl genrsa -out ' + dkimPrivateKeyFile + ' 1024' ) ) return new MailError ( MailError . INTERNAL _ERROR , safe . error ) ;
if ( ! safe . child _process . execSync ( 'openssl rsa -in ' + dkimPrivateKeyFile + ' -out ' + dkimPublicKeyFile + ' -pubout -outform PEM' ) ) return new MailError ( MailError . INTERNAL _ERROR , safe . error ) ;
2019-06-10 12:23:29 -07:00
if ( ! safe . fs . writeFileSync ( dkimSelectorFile , mailDomain . dkimSelector , 'utf8' ) ) return new MailError ( MailError . INTERNAL _ERROR , safe . error ) ;
2018-03-08 12:04:32 -08:00
2019-06-06 14:37:57 -07:00
// if the 'yellowtent' user of OS and the 'cloudron' user of mail container don't match, the keys become inaccessible by mail code
if ( ! safe . fs . chmodSync ( dkimPrivateKeyFile , 0o644 ) ) return new MailError ( MailError . INTERNAL _ERROR , safe . error ) ;
2018-03-08 12:04:32 -08:00
return null ;
}
2018-01-25 14:51:07 -08:00
function readDkimPublicKeySync ( domain ) {
assert . strictEqual ( typeof domain , 'string' ) ;
var dkimPath = path . join ( paths . MAIL _DATA _DIR , ` dkim/ ${ domain } ` ) ;
var dkimPublicKeyFile = path . join ( dkimPath , 'public' ) ;
var publicKey = safe . fs . readFileSync ( dkimPublicKeyFile , 'utf8' ) ;
if ( publicKey === null ) {
debug ( 'Error reading dkim public key.' , safe . error ) ;
return null ;
}
// remove header, footer and new lines
publicKey = publicKey . split ( '\n' ) . slice ( 1 , - 2 ) . join ( '' ) ;
return publicKey ;
}
2019-02-04 20:51:26 -08:00
function upsertDnsRecords ( domain , mailFqdn , callback ) {
2018-01-25 13:48:53 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2019-01-31 15:08:14 -08:00
assert . strictEqual ( typeof mailFqdn , 'string' ) ;
2018-01-25 13:48:53 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-04-08 12:23:11 -07:00
debug ( ` upsertDnsRecords: updating mail dns records of domain ${ domain } and mail fqdn ${ mailFqdn } ` ) ;
2019-06-10 12:23:29 -07:00
maildb . get ( domain , function ( error , mailDomain ) {
2018-07-25 10:44:59 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND ) ) ;
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2019-06-10 12:23:29 -07:00
error = ensureDkimKeySync ( mailDomain ) ;
2018-07-25 10:44:59 -07:00
if ( error ) return callback ( error ) ;
2018-03-08 12:04:32 -08:00
2018-07-25 10:44:59 -07:00
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
2018-03-08 16:02:13 -08:00
2018-07-25 10:44:59 -07:00
var dkimKey = readDkimPublicKeySync ( domain ) ;
if ( ! dkimKey ) return callback ( new MailError ( MailError . INTERNAL _ERROR , new Error ( 'Failed to read dkim public key' ) ) ) ;
2018-01-25 13:48:53 -08:00
2018-07-25 10:44:59 -07:00
// t=s limits the domainkey to this domain and not it's subdomains
2019-06-10 12:23:29 -07:00
var dkimRecord = { subdomain : ` ${ mailDomain . dkimSelector } ._domainkey ` , domain : domain , type : 'TXT' , values : [ '"v=DKIM1; t=s; p=' + dkimKey + '"' ] } ;
2018-01-25 13:48:53 -08:00
2018-07-25 10:44:59 -07:00
var records = [ ] ;
records . push ( dkimRecord ) ;
2019-06-10 12:23:29 -07:00
if ( mailDomain . enabled ) {
2018-07-25 11:41:42 -07:00
records . push ( { subdomain : '_dmarc' , domain : domain , type : 'TXT' , values : [ '"v=DMARC1; p=reject; pct=100"' ] } ) ;
2019-01-31 15:08:14 -08:00
records . push ( { subdomain : '' , domain : domain , type : 'MX' , values : [ '10 ' + mailFqdn + '.' ] } ) ;
2018-07-25 10:44:59 -07:00
}
2018-01-25 13:48:53 -08:00
2019-02-04 20:24:28 -08:00
txtRecordsWithSpf ( domain , mailFqdn , function ( error , txtRecords ) {
2018-07-25 10:44:59 -07:00
if ( error ) return callback ( error ) ;
2018-01-25 13:48:53 -08:00
2018-07-25 10:44:59 -07:00
if ( txtRecords ) records . push ( { subdomain : '' , domain : domain , type : 'TXT' , values : txtRecords } ) ;
2018-01-25 13:48:53 -08:00
2019-02-04 20:51:26 -08:00
debug ( 'upsertDnsRecords: will update %j' , records ) ;
2018-01-25 13:48:53 -08:00
2018-07-25 10:44:59 -07:00
async . mapSeries ( records , function ( record , iteratorCallback ) {
domains . upsertDnsRecords ( record . subdomain , record . domain , record . type , record . values , iteratorCallback ) ;
} , function ( error , changeIds ) {
2018-09-06 12:26:11 -07:00
if ( error ) {
2019-02-04 20:51:26 -08:00
debug ( ` upsertDnsRecords: failed to update: ${ error } ` ) ;
2018-09-06 12:26:11 -07:00
return callback ( new MailError ( MailError . EXTERNAL _ERROR , error . message ) ) ;
}
2019-02-04 20:51:26 -08:00
debug ( 'upsertDnsRecords: records %j added with changeIds %j' , records , changeIds ) ;
2018-01-25 13:48:53 -08:00
2018-09-06 12:26:11 -07:00
callback ( null ) ;
2018-07-25 10:44:59 -07:00
} ) ;
2018-01-25 13:48:53 -08:00
} ) ;
} ) ;
}
2019-02-04 20:51:26 -08:00
function setDnsRecords ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-07-26 10:49:29 -07:00
upsertDnsRecords ( domain , settings . mailFqdn ( ) , callback ) ;
2019-02-04 20:51:26 -08:00
}
2019-02-26 19:43:18 -08:00
function onMailFqdnChanged ( callback ) {
2019-02-04 14:49:51 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-07-26 10:49:29 -07:00
const mailFqdn = settings . mailFqdn ( ) ,
mailDomain = settings . adminDomain ( ) ;
2019-02-26 19:43:18 -08:00
2019-02-04 14:49:51 -08:00
domains . getAll ( function ( error , allDomains ) {
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
async . eachOfSeries ( allDomains , function ( domainObject , idx , iteratorDone ) {
2019-02-04 20:51:26 -08:00
upsertDnsRecords ( domainObject . domain , mailFqdn , iteratorDone ) ;
2019-02-04 14:49:51 -08:00
} , function ( error ) {
if ( error ) return callback ( new MailError ( MailError . EXTERNAL _ERROR , error . message ) ) ;
configureMail ( mailFqdn , mailDomain , callback ) ;
} ) ;
} ) ;
}
2018-04-03 14:37:52 -07:00
function addDomain ( domain , callback ) {
2018-01-24 21:15:58 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-07-26 10:49:29 -07:00
const dkimSelector = domain === settings . adminDomain ( ) ? 'cloudron' : ( 'cloudron-' + settings . adminDomain ( ) . replace ( /\./g , '' ) ) ;
2019-06-10 12:23:29 -07:00
maildb . add ( domain , { dkimSelector } , function ( error ) {
2018-03-08 12:04:32 -08:00
if ( error && error . reason === DatabaseError . ALREADY _EXISTS ) return callback ( new MailError ( MailError . ALREADY _EXISTS , 'Domain already exists' ) ) ;
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'No such domain' ) ) ;
2018-01-24 21:15:58 -08:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-03-12 21:14:45 -07:00
async . series ( [
2019-07-26 10:49:29 -07:00
upsertDnsRecords . bind ( null , domain , settings . mailFqdn ( ) ) , // do this first to ensure DKIM keys
2018-09-26 10:26:33 -07:00
restartMailIfActivated
2018-03-12 21:14:45 -07:00
] , NOOP _CALLBACK ) ; // do these asynchronously
2018-01-25 13:48:53 -08:00
2018-03-08 12:04:32 -08:00
callback ( ) ;
2018-01-24 21:15:58 -08:00
} ) ;
}
2018-04-03 14:37:52 -07:00
function removeDomain ( domain , callback ) {
2018-01-24 21:15:58 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-07-26 10:49:29 -07:00
if ( domain === settings . adminDomain ( ) ) return callback ( new MailError ( MailError . IN _USE ) ) ;
2018-09-05 17:29:18 -07:00
2018-03-12 21:14:45 -07:00
maildb . del ( domain , function ( error ) {
if ( error && error . reason === DatabaseError . IN _USE ) return callback ( new MailError ( MailError . IN _USE ) ) ;
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , error . message ) ) ;
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-02-10 10:34:30 -08:00
2018-03-12 21:14:45 -07:00
restartMail ( NOOP _CALLBACK ) ;
2018-02-10 10:34:30 -08:00
2018-03-12 21:14:45 -07:00
callback ( ) ;
2018-01-24 21:15:58 -08:00
} ) ;
}
2018-12-07 14:35:04 -08:00
function clearDomains ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
maildb . clear ( function ( error ) {
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( ) ;
} ) ;
}
2019-02-15 10:55:15 -08:00
// remove all fields that should never be sent out via REST API
function removePrivateFields ( domain ) {
let result = _ . pick ( domain , 'domain' , 'enabled' , 'mailFromValidation' , 'catchAll' , 'relay' ) ;
2019-02-15 11:44:33 -08:00
if ( result . relay . provider !== 'cloudron-smtp' ) {
if ( result . relay . username === result . relay . password ) result . relay . username = constants . SECRET _PLACEHOLDER ;
result . relay . password = constants . SECRET _PLACEHOLDER ;
}
2019-02-15 10:55:15 -08:00
return result ;
}
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 ) ) ;
2018-02-17 18:17:28 -08:00
restartMail ( NOOP _CALLBACK ) ; // have to restart mail container since haraka cannot watch symlinked config files (mail.ini)
2018-01-20 18:56:17 -08:00
callback ( null ) ;
} ) ;
}
2018-04-12 12:35:56 +02:00
function setCatchAllAddress ( domain , addresses , callback ) {
2018-01-20 23:17:39 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-04-12 12:35:56 +02:00
assert ( Array . isArray ( addresses ) ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-12 12:35:56 +02:00
maildb . update ( domain , { catchAll : addresses } , 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 ) ) ;
2018-02-17 18:17:28 -08:00
restartMail ( NOOP _CALLBACK ) ; // have to restart mail container since haraka cannot watch symlinked config files (mail.ini)
2018-01-20 18:56:17 -08:00
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' ) ;
2019-02-15 10:55:15 -08:00
getDomain ( domain , function ( error , result ) {
2018-01-20 18:56:17 -08:00
if ( error ) return callback ( error ) ;
2019-02-15 11:44:33 -08:00
// inject current username/password
if ( result . relay . provider === relay . provider ) {
if ( relay . username === constants . SECRET _PLACEHOLDER ) relay . username = result . relay . username ;
if ( relay . password === constants . SECRET _PLACEHOLDER ) relay . password = result . relay . password ;
}
2018-01-20 18:56:17 -08:00
2019-02-15 10:55:15 -08:00
verifyRelay ( relay , function ( error ) {
if ( error ) return callback ( error ) ;
2018-01-20 18:56:17 -08:00
2019-02-15 10:55:15 -08:00
maildb . update ( domain , { relay : relay } , function ( error ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND ) ) ;
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
restartMail ( NOOP _CALLBACK ) ;
callback ( null ) ;
} ) ;
2018-01-20 18:56:17 -08:00
} ) ;
} ) ;
}
2018-11-09 18:51:58 -08:00
function setMailEnabled ( domain , enabled , auditSource , callback ) {
2018-01-20 23:17:39 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof enabled , 'boolean' ) ;
2018-11-09 18:51:58 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-20 18:56:17 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-06-09 18:26:00 -07:00
maildb . update ( domain , { enabled : enabled } , function ( error ) {
2018-06-13 07:51:22 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND ) ) ;
2018-06-09 18:26:00 -07:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-01-24 11:33:09 -08:00
2018-06-09 18:26:00 -07:00
restartMail ( NOOP _CALLBACK ) ;
2018-01-24 11:33:09 -08:00
2018-11-10 00:32:27 -08:00
eventlog . add ( enabled ? eventlog . ACTION _MAIL _ENABLED : eventlog . ACTION _MAIL _DISABLED , auditSource , { domain } ) ;
2018-11-09 18:51:58 -08:00
2018-07-25 10:29:26 -07:00
callback ( null ) ;
} ) ;
}
2018-01-23 16:10:23 -08:00
function sendTestMail ( domain , to , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2018-02-03 18:27:55 -08:00
assert . strictEqual ( typeof to , 'string' ) ;
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-23 16:10:23 -08:00
2018-04-03 14:37:52 -07:00
getDomain ( domain , function ( error , result ) {
2018-01-23 16:10:23 -08:00
if ( error ) return callback ( error ) ;
2019-04-15 16:47:43 -07:00
mailer . sendTestMail ( result . domain , to , function ( error ) {
if ( error ) return callback ( new MailError ( MailError . EXTERNAL _ERROR , error . message ) ) ;
2018-01-23 16:10:23 -08:00
2019-04-15 16:47:43 -07:00
callback ( ) ;
} ) ;
2018-01-23 16:10:23 -08:00
} ) ;
2018-01-24 13:11:35 +01:00
}
2019-10-22 10:11:35 -07:00
function listMailboxes ( domain , page , perPage , callback ) {
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2019-10-22 10:11:35 -07:00
assert . strictEqual ( typeof page , 'number' ) ;
assert . strictEqual ( typeof perPage , 'number' ) ;
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-10-22 10:11:35 -07:00
mailboxdb . listMailboxes ( domain , page , perPage , function ( error , result ) {
2018-01-24 13:11:35 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( null , result ) ;
} ) ;
}
2018-02-11 01:18:29 -08:00
function removeMailboxes ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
mailboxdb . delByDomain ( domain , function ( error ) {
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( ) ;
} ) ;
}
2018-04-03 12:18:26 -07:00
function getMailbox ( name , domain , callback ) {
assert . strictEqual ( typeof name , 'string' ) ;
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-03 12:18:26 -07:00
mailboxdb . getMailbox ( name , domain , function ( error , result ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such mailbox' ) ) ;
2018-01-24 13:11:35 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-04-03 12:18:26 -07:00
callback ( null , result ) ;
2018-01-24 13:11:35 +01:00
} ) ;
}
2018-11-09 18:45:44 -08:00
function addMailbox ( name , domain , userId , auditSource , callback ) {
2018-04-03 12:18:26 -07:00
assert . strictEqual ( typeof name , 'string' ) ;
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof userId , 'string' ) ;
2018-11-09 18:45:44 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-05 16:07:51 -07:00
name = name . toLowerCase ( ) ;
2018-01-24 13:11:35 +01:00
2018-04-05 16:07:51 -07:00
var error = validateName ( name ) ;
if ( error ) return callback ( error ) ;
2018-04-03 14:27:09 -07:00
2018-12-06 21:08:19 -08:00
mailboxdb . addMailbox ( name , domain , userId , function ( error ) {
2018-04-06 16:51:37 +02:00
if ( error && error . reason === DatabaseError . ALREADY _EXISTS ) return callback ( new MailError ( MailError . ALREADY _EXISTS , ` mailbox ${ name } already exists ` ) ) ;
2018-04-05 16:07:51 -07:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-11-09 18:49:55 -08:00
eventlog . add ( eventlog . ACTION _MAIL _MAILBOX _ADD , auditSource , { name , domain , userId } ) ;
2018-11-09 18:45:44 -08:00
2018-04-05 16:07:51 -07:00
callback ( null ) ;
2018-01-24 13:11:35 +01:00
} ) ;
}
2018-12-06 11:41:16 -08:00
function updateMailboxOwner ( name , domain , userId , callback ) {
2018-04-03 14:12:43 -07:00
assert . strictEqual ( typeof name , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof userId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-05 16:07:51 -07:00
name = name . toLowerCase ( ) ;
2018-12-06 21:08:19 -08:00
mailboxdb . updateMailboxOwner ( name , domain , userId , function ( error ) {
2018-04-03 14:12:43 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such mailbox' ) ) ;
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
2018-11-09 18:45:44 -08:00
function removeMailbox ( name , domain , auditSource , callback ) {
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-04-03 12:18:26 -07:00
assert . strictEqual ( typeof name , 'string' ) ;
2018-11-09 18:45:44 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-24 13:11:35 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-03 12:18:26 -07:00
mailboxdb . del ( name , domain , function ( error ) {
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such mailbox' ) ) ;
2018-01-24 13:11:35 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-11-09 18:49:55 -08:00
eventlog . add ( eventlog . ACTION _MAIL _MAILBOX _REMOVE , auditSource , { name , domain } ) ;
2018-11-09 18:45:44 -08:00
2018-04-03 12:18:26 -07:00
callback ( null ) ;
2018-01-24 13:11:35 +01:00
} ) ;
}
2018-01-25 18:03:02 +01:00
2019-10-22 10:11:35 -07:00
function listAliases ( domain , page , perPage , callback ) {
2018-04-01 18:23:54 +02:00
assert . strictEqual ( typeof domain , 'string' ) ;
2019-10-22 10:11:35 -07:00
assert . strictEqual ( typeof page , 'number' ) ;
assert . strictEqual ( typeof perPage , 'number' ) ;
2018-04-01 18:23:54 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-10-22 10:11:35 -07:00
mailboxdb . listAliases ( domain , page , perPage , function ( error , result ) {
2018-04-03 12:18:26 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , error . message ) ) ;
2018-04-01 18:23:54 +02:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( null , result ) ;
} ) ;
}
2018-04-03 12:18:26 -07:00
function getAliases ( name , domain , callback ) {
assert . strictEqual ( typeof name , 'string' ) ;
2018-01-25 18:03:02 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-03 12:18:26 -07:00
getMailbox ( name , domain , function ( error ) {
if ( error ) return callback ( error ) ;
2018-01-25 18:03:02 +01:00
2018-04-03 12:18:26 -07:00
mailboxdb . getAliasesForName ( name , domain , function ( error , aliases ) {
2018-01-25 18:03:02 +01:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such mailbox' ) ) ;
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( null , aliases ) ;
} ) ;
} ) ;
}
2018-04-03 12:18:26 -07:00
function setAliases ( name , domain , aliases , callback ) {
assert . strictEqual ( typeof name , 'string' ) ;
2018-01-25 18:03:02 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert ( Array . isArray ( aliases ) ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
for ( var i = 0 ; i < aliases . length ; i ++ ) {
aliases [ i ] = aliases [ i ] . toLowerCase ( ) ;
2018-04-03 09:36:41 -07:00
var error = validateName ( aliases [ i ] ) ;
2018-01-25 18:03:02 +01:00
if ( error ) return callback ( error ) ;
}
2018-04-03 12:18:26 -07:00
mailboxdb . setAliasesForName ( name , domain , aliases , function ( error ) {
2018-04-13 12:18:27 +02:00
if ( error && error . reason === DatabaseError . ALREADY _EXISTS && error . message . indexOf ( 'mailboxes_name_domain_unique_index' ) !== - 1 ) {
2018-06-09 18:26:00 -07:00
var aliasMatch = error . message . match ( new RegExp ( ` ^ER_DUP_ENTRY: Duplicate entry '(.*)- ${ domain } ' for key 'mailboxes_name_domain_unique_index' $ ` ) ) ;
2018-04-13 12:18:27 +02:00
if ( ! aliasMatch ) return callback ( new MailError ( MailError . ALREADY _EXISTS , error . message ) ) ;
return callback ( new MailError ( MailError . ALREADY _EXISTS , ` Mailbox, mailinglist or alias for ${ aliasMatch [ 1 ] } already exists ` ) ) ;
}
2018-04-03 12:18:26 -07:00
if ( error && error . reason === DatabaseError . ALREADY _EXISTS ) return callback ( new MailError ( MailError . ALREADY _EXISTS , error . message ) ) ;
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such mailbox' ) ) ;
2018-01-25 18:03:02 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-04-03 12:18:26 -07:00
callback ( null ) ;
2018-01-25 18:03:02 +01:00
} ) ;
}
2018-01-26 10:22:50 +01:00
function getLists ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-08-23 15:09:06 -07:00
mailboxdb . getLists ( domain , function ( error , result ) {
2018-01-26 10:22:50 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( null , result ) ;
} ) ;
}
2018-04-03 09:34:33 -07:00
function getList ( domain , listName , callback ) {
2018-01-26 10:22:50 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-04-03 09:34:33 -07:00
assert . strictEqual ( typeof listName , 'string' ) ;
2018-01-26 10:22:50 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-08-23 15:09:06 -07:00
mailboxdb . getList ( listName , domain , function ( error , result ) {
2018-04-03 09:34:33 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such list' ) ) ;
2018-01-26 10:22:50 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-04-03 09:34:33 -07:00
callback ( null , result ) ;
2018-01-26 10:22:50 +01:00
} ) ;
}
2018-11-09 18:49:55 -08:00
function addList ( name , domain , members , auditSource , callback ) {
2018-01-26 10:22:50 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-04-05 16:07:51 -07:00
assert . strictEqual ( typeof name , 'string' ) ;
2018-04-05 15:46:13 -07:00
assert ( Array . isArray ( members ) ) ;
2018-11-09 18:49:55 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-26 10:22:50 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-05 16:07:51 -07:00
name = name . toLowerCase ( ) ;
var error = validateName ( name ) ;
if ( error ) return callback ( error ) ;
for ( var i = 0 ; i < members . length ; i ++ ) {
2019-09-11 14:09:36 -07:00
if ( ! validator . isEmail ( members [ i ] ) ) return callback ( new MailError ( MailError . BAD _FIELD , 'Invalid mail member: ' + members [ i ] ) ) ;
2018-04-05 16:07:51 -07:00
}
2019-08-23 15:09:06 -07:00
mailboxdb . addList ( name , domain , members , function ( error ) {
2018-04-05 15:46:13 -07:00
if ( error && error . reason === DatabaseError . ALREADY _EXISTS ) return callback ( new MailError ( MailError . ALREADY _EXISTS , 'list already exits' ) ) ;
2018-01-26 10:22:50 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-11-09 18:49:55 -08:00
eventlog . add ( eventlog . ACTION _MAIL _LIST _ADD , auditSource , { name , domain } ) ;
2018-04-05 15:46:13 -07:00
callback ( ) ;
2018-01-26 10:22:50 +01:00
} ) ;
}
2018-04-05 15:46:13 -07:00
function updateList ( name , domain , members , callback ) {
2018-04-03 14:12:43 -07:00
assert . strictEqual ( typeof name , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
2018-04-05 15:46:13 -07:00
assert ( Array . isArray ( members ) ) ;
2018-04-03 14:12:43 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-04-05 16:07:51 -07:00
name = name . toLowerCase ( ) ;
var error = validateName ( name ) ;
if ( error ) return callback ( error ) ;
for ( var i = 0 ; i < members . length ; i ++ ) {
2019-09-11 14:09:36 -07:00
if ( ! validator . isEmail ( members [ i ] ) ) return callback ( new MailError ( MailError . BAD _FIELD , 'Invalid email: ' + members [ i ] ) ) ;
2018-04-05 16:07:51 -07:00
}
2018-04-05 15:46:13 -07:00
mailboxdb . updateList ( name , domain , members , function ( error ) {
2018-04-03 14:12:43 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such mailbox' ) ) ;
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
}
2018-11-09 18:49:55 -08:00
function removeList ( name , domain , auditSource , callback ) {
assert . strictEqual ( typeof name , 'string' ) ;
2018-01-26 10:22:50 +01:00
assert . strictEqual ( typeof domain , 'string' ) ;
2018-11-09 18:49:55 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-26 10:22:50 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-11-09 18:49:55 -08:00
mailboxdb . del ( name , domain , function ( error ) {
2018-04-03 09:34:33 -07:00
if ( error && error . reason === DatabaseError . NOT _FOUND ) return callback ( new MailError ( MailError . NOT _FOUND , 'no such list' ) ) ;
2018-01-26 10:22:50 +01:00
if ( error ) return callback ( new MailError ( MailError . INTERNAL _ERROR , error ) ) ;
2018-11-09 18:49:55 -08:00
eventlog . add ( eventlog . ACTION _MAIL _LIST _ADD , auditSource , { name , domain } ) ;
2018-04-03 09:34:33 -07:00
callback ( ) ;
2018-01-26 10:22:50 +01:00
} ) ;
}