|
/** |
|
* @overview: Stores email into a single maildir folder, optionally obeying rspamd-score. |
|
* Needs Node v10 |
|
*/ |
|
|
|
const os = require('os'); |
|
const fs = require('fs'); |
|
const path = require('path'); |
|
const util = require('util'); |
|
const stream = require('stream'); |
|
|
|
const link = util.promisify(fs.link); |
|
const unlink = util.promisify(fs.unlink); |
|
const pipeline = util.promisify(stream.pipeline); |
|
const mkdir = util.promisify(fs.mkdir); |
|
|
|
exports.register = function() { |
|
this.load_maildir_ini(); |
|
}; |
|
|
|
/** |
|
* Saves email when the smtp server enqueues it. |
|
*/ |
|
exports.hook_queue = function(next, connection) { |
|
const plugin = this; |
|
const cfg = plugin.cfg.main; |
|
const txn = connection.transaction; |
|
const options = {}; |
|
|
|
// If message is SPAM, send to Junk folder |
|
const rspamd_header = txn.header.headers_decoded['x-rspamd-score']; |
|
if (rspamd_header && cfg.rspamd_move_to_spam) { |
|
const spam_score = rspamd_header[0]; |
|
if (spam_score > cfg.rspamd_spam_threshold) { |
|
plugin.loginfo("Email is spam, move to spam folder. Score: " + util.inspect(spam_score)); |
|
options.folder = ".Junk"; |
|
} |
|
} |
|
|
|
plugin.deposit(options, connection).then(() => next(OK), err => { |
|
plugin.logerror(`Failed to deliver mail: ${(err && err.stack) || err}`); |
|
next(DENYSOFT); |
|
}); |
|
}; |
|
|
|
exports.deposit = async function(params, connection) { |
|
const plugin = this; |
|
const cfg = plugin.cfg.main; |
|
const dirMode = parseInt(cfg.dir_mode, 8); |
|
const fileMode = parseInt(cfg.file_mode, 8); |
|
|
|
let maildir = cfg.path; |
|
//const [name, domain] = params.user.split('@'); |
|
//maildir = maildir.replace('%d', domain); |
|
//maildir = maildir.replace('%n', name); |
|
|
|
const folder = params.folder ? path.join(maildir, params.folder) : maildir; |
|
const fileName = getFileName(connection); |
|
|
|
// Create subdirectories if needed |
|
const f = {}; |
|
await Promise.all(['tmp', 'cur', 'new'].map(async dir => { |
|
const subfolder = path.join(folder, dir); |
|
f[dir] = path.join(subfolder, fileName); |
|
await mkdir(subfolder, { mode: dirMode, recursive: true }) |
|
.catch(err => err.code === 'EEXIST' || Promise.reject(err)) |
|
})); |
|
|
|
// Write the file |
|
const fileStream = fs.createWriteStream(f['tmp'], { flags: 'w', mode: fileMode }); |
|
await pipeline(connection.transaction.message_stream, fileStream); |
|
await link(f['tmp'], f['new']); |
|
await unlink(f['tmp']); |
|
}; |
|
|
|
exports.load_maildir_ini = function() { |
|
const plugin = this; |
|
plugin.cfg = plugin.config.get('maildir.ini', 'ini', () => { |
|
plugin.load_maildir_ini(); |
|
}); |
|
}; |
|
|
|
/** |
|
* Unique name of the file inside the maildir. |
|
* For filename uniqueness, connection uuid is used. |
|
* Thanks: http://cr.yp.to/proto/maildir.html |
|
* @return {string} |
|
*/ |
|
const getFileName = (connection) => |
|
`${Date.now()}.${connection.uuid}.${os.hostname()}`; |