diff --git a/CHANGES b/CHANGES index 5b9af8150..4950555fe 100644 --- a/CHANGES +++ b/CHANGES @@ -2078,4 +2078,4 @@ * spamassassin: custom configs and wl/bl * Do not automatically update to unstable release * scheduler: reduce container churn - +* mail: add API to set banner diff --git a/migrations/20200824004859-mail-add-bannerJson.js b/migrations/20200824004859-mail-add-bannerJson.js new file mode 100644 index 000000000..791575009 --- /dev/null +++ b/migrations/20200824004859-mail-add-bannerJson.js @@ -0,0 +1,15 @@ +'use strict'; + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE mail ADD COLUMN bannerJson TEXT', function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE mail DROP COLUMN bannerJson', function (error) { + if (error) console.error(error); + callback(error); + }); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index f37cee444..eac005405 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -161,6 +161,7 @@ CREATE TABLE IF NOT EXISTS mail( mailFromValidation BOOLEAN DEFAULT 1, catchAllJson TEXT, relayJson TEXT, + bannerJson TEXT, dkimSelector VARCHAR(128) NOT NULL DEFAULT "cloudron", diff --git a/setup/start.sh b/setup/start.sh index 24d373a3b..a2833bf3e 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -44,7 +44,7 @@ mkdir -p "${PLATFORM_DATA_DIR}/mysql" mkdir -p "${PLATFORM_DATA_DIR}/postgresql" mkdir -p "${PLATFORM_DATA_DIR}/mongodb" mkdir -p "${PLATFORM_DATA_DIR}/redis" -mkdir -p "${PLATFORM_DATA_DIR}/addons/mail" +mkdir -p "${PLATFORM_DATA_DIR}/addons/mail/banner" mkdir -p "${PLATFORM_DATA_DIR}/collectd/collectd.conf.d" mkdir -p "${PLATFORM_DATA_DIR}/logrotate.d" mkdir -p "${PLATFORM_DATA_DIR}/acme" diff --git a/src/infra_version.js b/src/infra_version.js index 1000de08e..fe828976a 100644 --- a/src/infra_version.js +++ b/src/infra_version.js @@ -20,7 +20,7 @@ exports = module.exports = { 'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:3.0.0@sha256:b00e5118a8f829c422234117bf113803be79a1d5102c51497c6d3005b041ce37' }, 'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:3.0.0@sha256:59e50b1f55e433ffdf6d678f8c658812b4119f631db8325572a52ee40d3bc562' }, 'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.3.0@sha256:0e31ec817e235b1814c04af97b1e7cf0053384aca2569570ce92bef0d95e94d2' }, - 'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.10.0@sha256:d22ad53a5275b8d7eda1b2c9e7d1406db42d9c443b527fbd7391331ad2ffdb72' }, + 'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.10.0@sha256:1c5a12ee5abc05f9cef692c1c7b33f0196420d13b4dc9c5bf2dcf9aca31eb1a4' }, 'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.3.0@sha256:b7bc1ca4f4d0603a01369a689129aa273a938ce195fe43d00d42f4f2d5212f50' }, 'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:2.0.2@sha256:cbd604eaa970c99ba5c4c2e7984929668e05de824172f880e8c576b2fb7c976d' } } diff --git a/src/mail.js b/src/mail.js index 4cb953d5e..deaa9f455 100644 --- a/src/mail.js +++ b/src/mail.js @@ -26,6 +26,7 @@ exports = module.exports = { setCatchAllAddress, setMailRelay, setMailEnabled, + setBanner, startMail: restartMail, restartMail, @@ -605,6 +606,9 @@ function createMailConfig(mailFqdn, mailDomain, callback) { `[${domain.domain}]\nenable_outbound=true\nhost=${host}\nport=${port}\nenable_tls=true\nauth_type=${authType}\nauth_user=${username}\nauth_pass=${password}\n\n`, 'utf8')) { return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create mail var file:' + safe.error.message)); } + + if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain}.text`, domain.banner.text || '')) return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create text banner file:' + safe.error.message)); + if (!safe.fs.writeFileSync(`${paths.ADDON_CONFIG_DIR}/mail/banner/${domain}.html`, domain.banner.html || '')) return iteratorDone(new BoxError(BoxError.FS_ERROR, 'Could not create html banner file:' + safe.error.message)); }, function (error) { if (error) return callback(error); @@ -996,7 +1000,7 @@ function clearDomains(callback) { // remove all fields that should never be sent out via REST API function removePrivateFields(domain) { - let result = _.pick(domain, 'domain', 'enabled', 'mailFromValidation', 'catchAll', 'relay'); + let result = _.pick(domain, 'domain', 'enabled', 'mailFromValidation', 'catchAll', 'relay', 'banner'); 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; @@ -1018,6 +1022,20 @@ function setMailFromValidation(domain, enabled, callback) { }); } +function setBanner(domain, banner, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof banner, 'object'); + assert.strictEqual(typeof callback, 'function'); + + maildb.update(domain, { banner }, function (error) { + if (error) return callback(error); + + restartMail(NOOP_CALLBACK); + + callback(null); + }); +} + function setCatchAllAddress(domain, addresses, callback) { assert.strictEqual(typeof domain, 'string'); assert(Array.isArray(addresses)); diff --git a/src/maildb.js b/src/maildb.js index 59b866b7b..38664ca56 100644 --- a/src/maildb.js +++ b/src/maildb.js @@ -17,7 +17,7 @@ var assert = require('assert'), database = require('./database.js'), safe = require('safetydance'); -var MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector' ].join(','); +var MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimSelector', 'bannerJson' ].join(','); function postProcess(data) { data.enabled = !!data.enabled; // int to boolean @@ -29,6 +29,9 @@ function postProcess(data) { data.relay = safe.JSON.parse(data.relayJson) || { provider: 'cloudron-smtp' }; delete data.relayJson; + data.banner = safe.JSON.parse(data.bannerJson) || { text: null, html: null }; + delete data.bannerJson; + return data; } @@ -74,8 +77,8 @@ function update(domain, data, callback) { var args = [ ]; var fields = [ ]; for (var k in data) { - if (k === 'catchAll') { - fields.push('catchAllJson = ?'); + if (k === 'catchAll' || k === 'banner') { + fields.push(`${k}Json = ?`); args.push(JSON.stringify(data[k])); } else if (k === 'relay') { fields.push('relayJson = ?'); diff --git a/src/routes/mail.js b/src/routes/mail.js index cff0707f8..30d15d5df 100644 --- a/src/routes/mail.js +++ b/src/routes/mail.js @@ -11,6 +11,7 @@ exports = module.exports = { setCatchAllAddress, setMailRelay, setMailEnabled, + setBanner, sendTestMail, @@ -261,6 +262,20 @@ function setAliases(req, res, next) { }); } +function setBanner(req, res, next) { + assert.strictEqual(typeof req.params.domain, 'string'); + assert.strictEqual(typeof req.body, 'object'); + + if (typeof req.body.text !== 'string') return res.status(400).send({ message: 'text must be a string' }); + if ('html' in req.body && typeof req.body.html !== 'string') return res.status(400).send({ message: 'html must be a string' }); + + mail.setBanner(req.params.domain, { text: req.body.text, html: req.body.html || null }, function (error) { + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(202)); + }); +} + function getLists(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); diff --git a/src/server.js b/src/server.js index 42e87f34f..412bfa612 100644 --- a/src/server.js +++ b/src/server.js @@ -266,6 +266,7 @@ function initializeExpressSync() { router.post('/api/v1/mail/:domain/relay', json, token, authorizeAdmin, routes.mail.setMailRelay); router.post('/api/v1/mail/:domain/enable', json, token, authorizeAdmin, routes.mail.setMailEnabled); router.post('/api/v1/mail/:domain/dns', json, token, authorizeAdmin, routes.mail.setDnsRecords); + router.post('/api/v1/mail/:domain/banner', json, token, authorizeAdmin, routes.mail.setBanner); router.post('/api/v1/mail/:domain/send_test_mail', json, token, authorizeAdmin, routes.mail.sendTestMail); router.get ('/api/v1/mail/:domain/mailbox_count', token, authorizeAdmin, routes.mail.getMailboxCount); router.get ('/api/v1/mail/:domain/mailboxes', token, authorizeAdmin, routes.mail.listMailboxes); diff --git a/src/test/database-test.js b/src/test/database-test.js index 0de8feb49..407d81ad1 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -1963,7 +1963,8 @@ describe('database', function () { relay: { provider: 'cloudron-smtp' }, catchAll: [ ], mailFromValidation: true, - dkimSelector: 'cloudron' + dkimSelector: 'cloudron', + banner: { html: null, text: null } }; before(function (done) {