mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-10 15:14:40 +08:00
v1.0.27
This commit is contained in:
parent
b0a538c31e
commit
ceb0934549
5 changed files with 175 additions and 13 deletions
|
@ -10,7 +10,12 @@ module.exports = (options, callback) => {
|
|||
|
||||
let message = maildrop({
|
||||
from: options.sender,
|
||||
to: options.forward,
|
||||
to: options.recipient,
|
||||
|
||||
forward: options.forward,
|
||||
http: !!options.targetUrl,
|
||||
targeUrl: options.targetUrl,
|
||||
|
||||
interface: 'forwarder'
|
||||
}, callback);
|
||||
|
||||
|
|
146
lib/maildrop.js
146
lib/maildrop.js
|
@ -7,9 +7,113 @@ const DkimStream = require('./dkim-stream');
|
|||
const MessageSplitter = require('./message-splitter');
|
||||
const seqIndex = new SeqIndex();
|
||||
const GridFs = require('grid-fs');
|
||||
const uuid = require('uuid');
|
||||
const os = require('os');
|
||||
const hostname = os.hostname();
|
||||
const addressparser = require('addressparser');
|
||||
const punycode = require('punycode');
|
||||
|
||||
let gridstore;
|
||||
|
||||
function convertAddresses(addresses, withNames, addressList) {
|
||||
addressList = addressList || new Map();
|
||||
|
||||
flatten(addresses || []).forEach(address => {
|
||||
if (address.address) {
|
||||
let normalized = normalizeAddress(address, withNames);
|
||||
let key = typeof normalized === 'string' ? normalized : normalized.address;
|
||||
addressList.set(key, normalized);
|
||||
} else if (address.group) {
|
||||
convertAddresses(address.group, withNames, addressList);
|
||||
}
|
||||
});
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
function parseAddressList(headers, key, withNames) {
|
||||
return parseAddressses(headers.getDecoded(key).map(header => header.value), withNames);
|
||||
}
|
||||
|
||||
function parseAddressses(headerList, withNames) {
|
||||
let map = convertAddresses(headerList.map(address => {
|
||||
if (typeof address === 'string') {
|
||||
address = addressparser(address);
|
||||
}
|
||||
return address;
|
||||
}), withNames);
|
||||
return Array.from(map).map(entry => entry[1]);
|
||||
}
|
||||
|
||||
function normalizeDomain(domain) {
|
||||
return punycode.toASCII(domain.toLowerCase().trim());
|
||||
}
|
||||
|
||||
// helper function to flatten arrays
|
||||
function flatten(arr) {
|
||||
let flat = [].concat(...arr);
|
||||
return flat.some(Array.isArray) ? flatten(flat) : flat;
|
||||
}
|
||||
|
||||
function normalizeAddress(address, withNames) {
|
||||
if (typeof address === 'string') {
|
||||
address = {
|
||||
address
|
||||
};
|
||||
}
|
||||
if (!address || !address.address) {
|
||||
return '';
|
||||
}
|
||||
let user = address.address.substr(0, address.address.lastIndexOf('@'));
|
||||
let domain = address.address.substr(address.address.lastIndexOf('@') + 1);
|
||||
let addr = user.trim() + '@' + normalizeDomain(domain);
|
||||
|
||||
if (withNames) {
|
||||
return {
|
||||
name: address.name || '',
|
||||
address: addr
|
||||
};
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
function updateHeaders(envelope) {
|
||||
// Fetch sender and receiver addresses
|
||||
envelope.parsedEnvelope = {
|
||||
from: parseAddressList(envelope.headers, 'from').shift() || false,
|
||||
to: parseAddressList(envelope.headers, 'to'),
|
||||
cc: parseAddressList(envelope.headers, 'cc'),
|
||||
bcc: parseAddressList(envelope.headers, 'bcc'),
|
||||
replyTo: parseAddressList(envelope.headers, 'reply-to').shift() || false,
|
||||
sender: parseAddressList(envelope.headers, 'sender').shift() || false
|
||||
};
|
||||
|
||||
// Check Message-ID: value. Add if missing
|
||||
let mId = envelope.headers.getFirst('message-id');
|
||||
if (!mId) {
|
||||
mId = '<' + uuid.v4() + '@' + (envelope.from.substr(envelope.from.lastIndexOf('@') + 1) || hostname) + '>';
|
||||
|
||||
envelope.headers.remove('message-id'); // in case there's an empty value
|
||||
envelope.headers.add('Message-ID', mId);
|
||||
}
|
||||
envelope.messageId = mId;
|
||||
|
||||
// Check Date: value. Add if missing or invalid or future date
|
||||
let date = envelope.headers.getFirst('date');
|
||||
let dateVal = new Date(date);
|
||||
if (!date || dateVal.toString() === 'Invalid Date' || dateVal < new Date(1000)) {
|
||||
date = new Date().toUTCString().replace(/GMT/, '+0000');
|
||||
envelope.headers.remove('date'); // remove old empty or invalid values
|
||||
envelope.headers.add('Date', date);
|
||||
}
|
||||
|
||||
envelope.date = date;
|
||||
|
||||
// Remove BCC if present
|
||||
envelope.headers.remove('bcc');
|
||||
}
|
||||
|
||||
module.exports = (options, callback) => {
|
||||
if (!config.sender.enabled) {
|
||||
return callback(null, false);
|
||||
|
@ -22,7 +126,7 @@ module.exports = (options, callback) => {
|
|||
let envelope = {
|
||||
id,
|
||||
|
||||
from: options.from,
|
||||
from: options.from || '',
|
||||
to: Array.isArray(options.to) ? options.to : [].concat(options.to || []),
|
||||
|
||||
interface: options.interface || 'maildrop',
|
||||
|
@ -34,7 +138,31 @@ module.exports = (options, callback) => {
|
|||
}
|
||||
};
|
||||
|
||||
if (!envelope.to.length) {
|
||||
let deliveries = [];
|
||||
|
||||
if (options.targeUrl) {
|
||||
let targetUrls = [].concat(options.targeUrl || []).map(targetUrl => ({
|
||||
to: options.to,
|
||||
http: true,
|
||||
targetUrl
|
||||
}));
|
||||
deliveries = deliveries.concat(targetUrls);
|
||||
}
|
||||
|
||||
if (options.forward) {
|
||||
let forwards = [].concat(options.forward || []).map(forward => ({
|
||||
to: forward
|
||||
}));
|
||||
deliveries = deliveries.concat(forwards);
|
||||
}
|
||||
|
||||
if (!deliveries.length) {
|
||||
deliveries = envelope.to.map(to => ({
|
||||
to
|
||||
}));
|
||||
}
|
||||
|
||||
if (!deliveries.length) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
|
@ -42,7 +170,8 @@ module.exports = (options, callback) => {
|
|||
let dkimStream = new DkimStream();
|
||||
|
||||
messageSplitter.once('headers', headers => {
|
||||
envelope.headers = headers.getList();
|
||||
envelope.headers = headers;
|
||||
updateHeaders(envelope);
|
||||
});
|
||||
|
||||
dkimStream.on('hash', bodyHash => {
|
||||
|
@ -59,6 +188,7 @@ module.exports = (options, callback) => {
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
envelope.headers = envelope.headers.getList();
|
||||
setMeta(id, envelope, err => {
|
||||
if (err) {
|
||||
return removeMessage(id, () => callback(err));
|
||||
|
@ -66,11 +196,11 @@ module.exports = (options, callback) => {
|
|||
|
||||
let date = new Date();
|
||||
|
||||
for (let i = 0, len = envelope.to.length; i < len; i++) {
|
||||
for (let i = 0, len = deliveries.length; i < len; i++) {
|
||||
|
||||
let recipient = envelope.to[i];
|
||||
let recipient = deliveries[i];
|
||||
let deliveryZone = options.zone || config.sender.zone || 'default';
|
||||
let recipientDomain = recipient.substr(recipient.lastIndexOf('@') + 1).replace(/[\[\]]/g, '');
|
||||
let recipientDomain = recipient.to.substr(recipient.to.lastIndexOf('@') + 1).replace(/[\[\]]/g, '');
|
||||
|
||||
seq++;
|
||||
let deliverySeq = (seq < 0x100 ? '0' : '') + (seq < 0x10 ? '0' : '') + seq.toString(16);
|
||||
|
@ -83,7 +213,9 @@ module.exports = (options, callback) => {
|
|||
sendingZone: deliveryZone,
|
||||
|
||||
// actual recipient address
|
||||
recipient,
|
||||
recipient: recipient.to,
|
||||
http: recipient.http,
|
||||
targetUrl: recipient.targetUrl,
|
||||
|
||||
locked: false,
|
||||
lockTime: 0,
|
||||
|
|
|
@ -485,6 +485,14 @@ class UserHandler {
|
|||
name: data.name
|
||||
};
|
||||
|
||||
if (data.forward) {
|
||||
update.forward = data.forward;
|
||||
}
|
||||
|
||||
if (data.targetUrl) {
|
||||
update.targetUrl = data.targetUrl;
|
||||
}
|
||||
|
||||
if (data.password) {
|
||||
update.password = bcrypt.hashSync(data.password, 11);
|
||||
}
|
||||
|
|
24
lmtp.js
24
lmtp.js
|
@ -72,7 +72,8 @@ const serverOptions = {
|
|||
fields: {
|
||||
filters: true,
|
||||
forwards: true,
|
||||
forward: true
|
||||
forward: true,
|
||||
targetUrl: true
|
||||
}
|
||||
}, (err, user) => {
|
||||
if (err) {
|
||||
|
@ -174,6 +175,7 @@ const serverOptions = {
|
|||
} : []);
|
||||
|
||||
let forwardTargets = new Set();
|
||||
let forwardTargetUrls = new Set();
|
||||
let matchingFilters = [];
|
||||
let filterActions = new Map();
|
||||
|
||||
|
@ -195,6 +197,12 @@ const serverOptions = {
|
|||
forwardTargets.add(filter.action[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'targetUrl') {
|
||||
forwardTargetUrls.add(filter.action[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
// if a previous filter already has set a value then do not touch it
|
||||
if (!filterActions.has(key)) {
|
||||
filterActions.set(key, filter.action[key]);
|
||||
|
@ -209,13 +217,18 @@ const serverOptions = {
|
|||
forwardTargets.add(user.forward);
|
||||
}
|
||||
|
||||
if (user.targetUrl && !filterActions.get('delete')) {
|
||||
// forward to default URL only if the message is not deleted
|
||||
forwardTargetUrls.add(user.targetUrl);
|
||||
}
|
||||
|
||||
// never forward messages marked as spam
|
||||
if (!forwardTargets.size || filterActions.get('spam')) {
|
||||
if ((!forwardTargets.size && !forwardTargetUrls.size) || filterActions.get('spam')) {
|
||||
return setImmediate(done);
|
||||
}
|
||||
|
||||
// check limiting counters
|
||||
messageHandler.counters.ttlcounter('wdf:' + user._id.toString(), forwardTargets.size, user.forwards, (err, result) => {
|
||||
messageHandler.counters.ttlcounter('wdf:' + user._id.toString(), forwardTargets.size + forwardTargetUrls.size, user.forwards, (err, result) => {
|
||||
if (err) {
|
||||
// failed checks
|
||||
log.error('LMTP', 'FRWRDFAIL key=%s error=%s', 'wdf:' + user._id.toString(), err.message);
|
||||
|
@ -228,7 +241,10 @@ const serverOptions = {
|
|||
user,
|
||||
sender,
|
||||
recipient,
|
||||
forward: Array.from(forwardTargets),
|
||||
|
||||
forward: forwardTargets.size ? Array.from(forwardTargets): false,
|
||||
targetUrl: forwardTargetUrls.size ? Array.from(forwardTargetUrls) : false,
|
||||
|
||||
chunks
|
||||
}, done);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wildduck",
|
||||
"version": "1.0.26",
|
||||
"version": "1.0.27",
|
||||
"description": "IMAP server built with Node.js and MongoDB",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
@ -19,6 +19,7 @@
|
|||
"mocha": "^3.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"addressparser": "^1.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"config": "^1.25.1",
|
||||
"generate-password": "^1.3.0",
|
||||
|
|
Loading…
Add table
Reference in a new issue