Skip to content

Commit

Permalink
Fix an issue in the maildir plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
user committed Oct 8, 2017
1 parent 5e41129 commit b71a5b0
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 134 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"author": "Thomas Zilz",
"version": "2.0.0",
"main": "src/index.js",
"repository": "https://github.com/mofux/mail-io",
"license": "MIT",
"scripts": {
"test": "mocha"
},
Expand Down
240 changes: 106 additions & 134 deletions plugins/queue/maildir.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,165 +2,137 @@ module.exports = {
description: 'stores the message in the maildir format',
author: 'Thomas Zilz',
after: ['spamd'],
handler: function(req, res) {

// module dependencies
let fs = require('fs');
let async = require('async');
let mkdirp = require('mkdirp');
let path = require('path');
let uuid = require('node-uuid');

// a flag indicating if we have already answered
let answered = false;

// handler if something goes wrong
let onError = function(err) {
res.log.error('Error while storing message:', err);
if (!answered) {
res.reject(451, 'Requested action aborted - failed to store message.');
answered = true;
handler: async (req, res) => {

try {

// module dependencies
let fs = require('fs');
let path = require('path');
let uuid = require('node-uuid');
let SMTPUtil = require('../../src/smtp-util');

// a list of sender addresses (after extending them)
let senders = [];

// a list of recipient mail (after extending them)
let recipients = [];

// a list of mailboxes
let mailboxes = [];

// extend function, either from the config or a simple placeholder
let extend = typeof(req.config.extend) === 'function' ? req.config.extend : (address) => {
return [address];
}
}

// a list of sender addresses (after extending them)
let senders = [];

// a list of recipient mail (after extending them)
let recipients = [];

// extend function, either from the config or a simple placeholder
let extend = typeof(req.config.extend) === 'function' ? req.config.extend : (address, cb) => {
return cb([address]);
}

async.series({
extendSender: function(cb) {

// only store mails for senders that belong to our domain
if (req.session.config.domains.indexOf(req.session.envelope.from.split('@')[1]) === -1) return cb();

extend(req.session.envelope.from, function(addresses) {
if (addresses) senders = senders.concat(addresses);
cb();
});

},
extendRecipients: function(cb) {

async.each(req.session.envelope.to, (recipient, cb) => {

// only store mails for recipients that belong to our domain
if (req.session.config.domains.indexOf(recipient.split('@')[1]) === -1) return cb();

extend(recipient, (addresses) => {
if (addresses) recipients = recipients.concat(addresses);
cb();
});

}, cb);

},
processMailboxes: function(cb) {

// a list of mailboxes that will be processed
let mailboxes = [];

// add mailbox entries for senders
senders.forEach(function(address) {
mailboxes.push({
mailDir: req.config.mailDir.replace(/%n/g, address.split('@')[0]).replace(/%d/g, address.split('@')[1]),
folder: '.Sent'
});

// only store mails for senders that belong to our domain
if (req.session.config.domains.indexOf(req.session.envelope.from.split('@')[1]) !== -1) {
let extended = await extend(req.session.envelope.from);
if (extended) senders = senders.concat(extended);
}

// only store mails for recipients that belong to our domain
for (let recipient of req.session.envelope.to) {
if (req.session.config.domains.indexOf(recipient.split('@')[1]) === -1) continue;
let extended = await extend(recipient);
if (extended) recipients = recipients.concat(extended);
}

// add mailbox entries for senders
senders.forEach((address) => {
mailboxes.push({
mailDir: req.config.mailDir.replace(/%n/g, address.split('@')[0]).replace(/%d/g, address.split('@')[1]),
folder: '.Sent'
});
});

// add mailbox entries for recipients
recipients.forEach(function(address) {
mailboxes.push({
mailDir: req.config.mailDir.replace(/%n/g, address.split('@')[0]).replace(/%d/g, address.split('@')[1]),
folder: res.get('queue/spamd').spam ? '.Junk' : ''
});
// add mailbox entries for recipients
recipients.forEach((address) => {
mailboxes.push({
mailDir: req.config.mailDir.replace(/%n/g, address.split('@')[0]).replace(/%d/g, address.split('@')[1]),
folder: res.get('queue/spamd').spam ? '.Junk' : ''
});
});

// write message to the designated mailboxes
for (let mailbox of mailboxes) {

// store the messages to the mailboxes
async.each(mailboxes, function(mailbox, cb) {

// configure mailbox path
mailbox.path = path.join(mailbox.mailDir, mailbox.folder);

res.log.verbose('Storing mail to ' + mailbox.path);

// create mail dirs if they do not exist yet
let dirs = ['tmp', 'new', 'cur'];

async.each(dirs, function(dir, cb) {

// create the folder (if it does not exist)
mkdirp(path.join(mailbox.path, dir), cb);
// configure mailbox path
mailbox.path = path.join(mailbox.mailDir, mailbox.folder);
res.log.verbose('Storing mail to ' + mailbox.path);

}, function foldersCreated(err) {
// create mail dirs if they do not exist yet
let dirs = ['tmp', 'new', 'cur'];

if (err) return cb(err);
// create target directories if they do not yet exist
for (let dir of dirs) await SMTPUtil.mkdirp(path.join(mailbox.path, dir));

// stream the message to the file
await new Promise((resolve, reject) => {

// file names for different targets
let filename = new Date().getTime() + '.' + uuid.v1() + '.' + req.session.config.hostname;
let tmpFile = path.join(mailbox.path, 'tmp', filename);
let finalFile = path.join(mailbox.path, 'new', filename);

// file names for different targets
let filename = new Date().getTime() + '.' + uuid.v1() + '.' + req.session.config.hostname;
let tmpFile = path.join(mailbox.path, 'tmp', filename);
let finalFile = path.join(mailbox.path, 'new', filename);
// a reference to source mail
let message = fs.createReadStream(req.command.data);

// a reference to source mail
let message = fs.createReadStream(req.command.data);
// try to catch errors (e.g. we cannot read the message)
message.once('error', reject);

// try to catch errors (e.g. we cannot read the message)
message.once('error', onError);
// special handling for sent messages:
// put the mail to the cur instead of the new folder, and
// add the "Seen" (:2,S) attribute to the filename, so that
// a client does not show this message as unread
if (mailbox.folder === '.Sent') {
finalFile = path.join(mailbox.path, 'cur', filename + ':2,S');
}

// special handling for sent messages:
// put the mail to the cur instead of the new folder, and
// add the "Seen" (:2,S) attribute to the filename, so that
// a client does not show this message as unread
if (mailbox.folder === '.Sent') {
finalFile = path.join(mailbox.path, 'cur', filename + ':2,S');
}
// save the message to the file
let fileStream = fs.createWriteStream(tmpFile);

// save the message to the file
let fileStream = fs.createWriteStream(tmpFile);
// handle errors
fileStream.once('error', reject);

// handle errors
fileStream.once('error', onError);
// stream the message to the tmp folder
message.pipe(fileStream);

// stream the message to the tmp folder
message.pipe(fileStream);
// once the file has been written completely,
// copy it from the tmp folder to the new folder
fileStream.once('finish', () => {

// once the file has been written completely,
// copy it from the tmp folder to the new folder
fileStream.once('finish', () => {
fs.link(tmpFile, finalFile, (err) => {

fs.link(tmpFile, finalFile, (err) => {
if (err) return reject('Failed to move message from "' + tmpFile + '" to "' + finalFile + '": ' + err);

if (err) return cb('Failed to move message from "' + tmpFile + '" to "' + finalFile + '": ' + err);
// delete the message from tmp
fs.unlink(tmpFile, (err) => {

// delete the message from tmp
fs.unlink(tmpFile, (err) => {

return err ? cb('Failed to delete tmp message "' + tmpFile + '": ' + err) : cb();

});
return err ? reject('Failed to delete tmp message "' + tmpFile + '": ' + err) : resolve();

});

});


});

}, cb);

});
}
}, (err) => {

if (err) return onError(err);
if (!answered) res.accept();

});

// accept
res.accept(250, 'OK');

} catch (ex) {

// log the error and reject
res.log.error('Error while storing message:', err);
res.reject(451, 'Requested action aborted - failed to store message.');

}

}

}
1 change: 1 addition & 0 deletions plugins/queue/relay.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module.exports = {
res.log.error('Failed to submit message to relay queue: ', err);

});

}

}
Expand Down

0 comments on commit b71a5b0

Please sign in to comment.