diff --git a/migrations/20180121061357-maildb-create-table.js b/migrations/20180121061357-maildb-create-table.js new file mode 100644 index 000000000..3d300c2e5 --- /dev/null +++ b/migrations/20180121061357-maildb-create-table.js @@ -0,0 +1,24 @@ +'use strict'; + +exports.up = function(db, callback) { + var cmd = 'CREATE TABLE IF NOT EXISTS maildb(' + + 'domain VARCHAR(128) NOT NULL UNIQUE,' + + 'enabled BOOLEAN DEFAULT 0,' + + 'mailFromValidation BOOLEAN DEFAULT 1,' + + 'catchAllJson TEXT,' + + 'relayJson TEXT,' + + 'FOREIGN KEY(domain) REFERENCES domains(domain),' + + 'PRIMARY KEY(domain)) CHARACTER SET utf8 COLLATE utf8_bin'; + + db.runSql(cmd, function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('DROP TABLE mailboxes', function (error) { + if (error) console.error(error); + callback(error); + }); +}; diff --git a/migrations/20180121062945-maildb-migrate-settings.js b/migrations/20180121062945-maildb-migrate-settings.js new file mode 100644 index 000000000..6fba66251 --- /dev/null +++ b/migrations/20180121062945-maildb-migrate-settings.js @@ -0,0 +1,34 @@ +'use strict'; + +exports.up = function(db, callback) { + db.all('SELECT * FROM domains', function (error, domains) { + if (error) return callback(error); + if (domains.length === 0) return callback(); + + db.all('SELECT * FROM settings', function (error, allSettings) { + if (error) return callback(error); + + // defaults + var mailFromValidation = true; + var catchAll = [ ]; + var relay = { provider: 'cloudron-smtp' }; + var mailEnabled = false; + + allSettings.forEach(function (setting) { + switch (setting.name) { + case 'mail_from_validation': mailFromValidation = !!setting.value; break; + case 'catch_all_address': catchAll = setting.value; break; + case 'mail_relay': relay = setting.value; break; + case 'mail_config': mailEnabled = JSON.parse(setting.value).enabled; break; + } + }); + + db.runSql('INSERT INTO maildb (domain, enabled, mailFromValidation, catchAllJson, relayJson) VALUES (?, ?, ?, ?, ?)', + [ domains[0].domain, mailEnabled, mailFromValidation, JSON.stringify(catchAll), JSON.stringify(relay) ], callback); + }); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index f41a86950..f42a93847 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -157,3 +157,18 @@ CREATE TABLE IF NOT EXISTS domains( /* the default db collation is utf8mb4_unicode_ci but for the app table domain constraint we have to use the old one */ CHARACTER SET utf8 COLLATE utf8_bin; + +CREATE TABLE IF NOT EXISTS maildb( + domain VARCHAR(128) NOT NULL UNIQUE, + + enabled BOOLEAN DEFAULT 0, /* MDA enabled */ + mailFromValidation BOOLEAN DEFAULT 1, + catchAllJson TEXT, + relayJson TEXT, + + FOREIGN KEY(domain) REFERENCES domains(domain), + PRIMARY KEY(domain)) + + CHARACTER SET utf8 COLLATE utf8_bin; + + diff --git a/src/mailboxdb.js b/src/mailboxdb.js index c5fb8448f..dd540429f 100644 --- a/src/mailboxdb.js +++ b/src/mailboxdb.js @@ -178,7 +178,7 @@ function setAliasesForName(name, domain, aliases, callback) { queries.push({ query: 'DELETE FROM mailboxes WHERE aliasTarget = ? AND domain = ?', args: [ name, domain ] }); aliases.forEach(function (alias) { queries.push({ query: 'INSERT INTO mailboxes (name, domain, aliasTarget, ownerId, ownerType) VALUES (?, ?, ?, ?, ?)', - args: [ alias, domain, name, results[0].ownerId, results[0].ownerType ] }); + args: [ alias, domain, name, results[0].ownerId, results[0].ownerType ] }); }); database.transaction(queries, function (error) { diff --git a/src/maildb.js b/src/maildb.js new file mode 100644 index 000000000..8f2b63c4b --- /dev/null +++ b/src/maildb.js @@ -0,0 +1,108 @@ +'use strict'; + +exports = module.exports = { + add: add, + del: del, + get: get, + update: update, + + _clear: clear, + + TYPE_USER: 'user', + TYPE_APP: 'app', + TYPE_GROUP: 'group' +}; + +var assert = require('assert'), + database = require('./database.js'), + DatabaseError = require('./databaseerror.js'), + safe = require('safetydance'); + +var MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson' ].join(','); + +function postProcess(data) { + data.enabled = !!data.enabled; // int to boolean + data.mailFromValidation = !!data.mailFromValidation; // int to boolean + + data.catchAll = safe.JSON.parse(data.catchAllJson) || [ ]; + delete data.catchAllJson; + + data.relay = safe.JSON.parse(data.relayJson) || { provider: 'cloudron-smtp' }; + delete data.relayJson; + + return data; +} + +function add(domain, callback) { + assert.strictEqual(typeof domain, 'string'); + + database.query('INSERT INTO maildb (domain) VALUES (?)', [ domain ], function (error) { + if (error && error.code === 'ER_DUP_ENTRY') return callback(new DatabaseError(DatabaseError.ALREADY_EXISTS, 'mail domain already exists')); + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + callback(null); + }); +} + +function clear(callback) { + assert.strictEqual(typeof callback, 'function'); + + database.query('TRUNCATE TABLE maildb', [], function (error) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + callback(null); + }); +} + +function del(domain, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof callback, 'function'); + + // deletes aliases as well + database.query('DELETE FROM maildb WHERE domain=?', [ domain ], function (error, result) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + if (result.affectedRows === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + + callback(null); + }); +} + +function get(domain, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query('SELECT ' + MAILDB_FIELDS + ' FROM maildb WHERE domain = ?', [ domain ], function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + + callback(null, postProcess(results[0])); + }); +} + +function update(domain, data, callback) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof data, 'object'); + assert.strictEqual(typeof callback, 'function'); + + var args = [ ]; + var fields = [ ]; + for (var k in data) { + if (k === 'catchAll') { + fields.push('catchAllJson = ?'); + args.push(JSON.stringify(data[k])); + } else if (k === 'relay') { + fields.push('relayJson = ?'); + args.push(JSON.stringify(data[k])); + } else { + fields.push(k + ' = ?'); + args.push(data[k]); + } + } + args.push(domain); + + database.query('UPDATE maildb SET ' + fields.join(', ') + ' WHERE domain=?', args, function (error) { + if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + callback(null); + }); +}