haraka wildduck
Este commit está contenido en:
409
production/haraka-wildduck/api-client.js
Archivo normal
409
production/haraka-wildduck/api-client.js
Archivo normal
@@ -0,0 +1,409 @@
|
|||||||
|
/* eslint no-console: 0 */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const config = require('wild-config');
|
||||||
|
const request = require('request');
|
||||||
|
const restify = require('restify-clients');
|
||||||
|
const client = restify.createJsonClient({
|
||||||
|
url: config.api.url
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
users: {
|
||||||
|
create(data, callback) {
|
||||||
|
_exec('post', '/users', false, data, config.api.accessToken, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(user, callback) {
|
||||||
|
_exec('get', '/users/{user}', { user: user.id }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(user, updates, callback) {
|
||||||
|
_exec('put', '/users/{user}', { user: user.id }, updates, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
logout(user, callback) {
|
||||||
|
console.log(user.token);
|
||||||
|
_exec('del', '/authenticate', {}, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
authenticate(username, password, sess, ip, callback) {
|
||||||
|
_exec(
|
||||||
|
'post',
|
||||||
|
'/authenticate',
|
||||||
|
false,
|
||||||
|
{
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
protocol: 'API',
|
||||||
|
scope: 'master',
|
||||||
|
appId: config.u2f.appId,
|
||||||
|
token: true,
|
||||||
|
sess: sess,
|
||||||
|
ip: ip
|
||||||
|
},
|
||||||
|
config.api.accessToken,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addresses: {
|
||||||
|
list(user, callback) {
|
||||||
|
_exec('get', '/users/{user}/addresses', { user: user.id }, false, user.token, (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null, (response && response.results) || []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
create(user, data, callback) {
|
||||||
|
_exec('post', '/users/{user}/addresses', { user: user.id }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(user, address, callback) {
|
||||||
|
_exec('get', '/users/{user}/addresses/{address}', { user: user.id, address }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(user, address, updates, callback) {
|
||||||
|
_exec('put', '/users/{user}/addresses/{address}', { user: user.id, address }, updates, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
del(user, address, callback) {
|
||||||
|
_exec('del', '/users/{user}/addresses/{address}', { user: user.id, address }, false, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
asps: {
|
||||||
|
list(user, callback) {
|
||||||
|
_exec('get', '/users/{user}/asps', { user: user.id }, false, user.token, (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null, (response && response.results) || []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
create(user, data, callback) {
|
||||||
|
_exec('post', '/users/{user}/asps', { user: user.id }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
del(user, asp, sess, ip, callback) {
|
||||||
|
_exec('del', '/users/{user}/asps/{asp}?ip={ip}', { user: user.id, asp, sess, ip }, false, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'2fa': {
|
||||||
|
setupTotp(user, issuer, ip, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/totp/setup', { user: user.id }, { issuer, ip }, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkTotp(user, token, sess, ip, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/totp/check', { user: user.id }, { token, sess, ip }, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
verifyTotp(user, token, sess, ip, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/totp/enable', { user: user.id }, { token, sess, ip }, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
disable(user, sess, ip, callback) {
|
||||||
|
_exec('del', '/users/{user}/2fa?ip={ip}', { user: user.id, sess, ip }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
setupU2f(user, ip, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/u2f/setup', { user: user.id }, { ip, appId: config.u2f.appId }, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
enableU2f(user, requestData, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/u2f/enable', { user: user.id }, requestData, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
disableU2f(user, sess, ip, callback) {
|
||||||
|
_exec('del', '/users/{user}/2fa/u2f?ip={ip}', { user: user.id, sess, ip }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
startU2f(user, ip, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/u2f/start', { user: user.id }, { ip, appId: config.u2f.appId }, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkU2f(user, requestData, callback) {
|
||||||
|
_exec('post', '/users/{user}/2fa/u2f/check', { user: user.id }, requestData, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
authlog: {
|
||||||
|
list(user, data, callback) {
|
||||||
|
_exec(
|
||||||
|
'get',
|
||||||
|
'/users/{user}/authlog?next={next}&previous={previous}&page={page}',
|
||||||
|
{ user: user.id, next: data.next || '', previous: data.previous || '', page: data.page },
|
||||||
|
false,
|
||||||
|
user.token,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(user, event, callback) {
|
||||||
|
_exec('get', '/users/{user}/authlog/{event}', { user: user.id, event }, false, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
filters: {
|
||||||
|
list(user, callback) {
|
||||||
|
_exec('get', '/users/{user}/filters', { user: user.id }, false, user.token, (err, response) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null, (response && response.results) || []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get(user, filter, callback) {
|
||||||
|
_exec('get', '/users/{user}/filters/{filter}', { user: user.id, filter }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
create(user, data, callback) {
|
||||||
|
_exec('post', '/users/{user}/filters', { user: user.id }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(user, filter, data, callback) {
|
||||||
|
_exec('put', '/users/{user}/filters/{filter}', { user: user.id, filter }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
del(user, filter, callback) {
|
||||||
|
_exec('del', '/users/{user}/filters/{filter}', { user: user.id, filter }, false, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mailboxes: {
|
||||||
|
list(user, counters, callback) {
|
||||||
|
_exec(
|
||||||
|
'get',
|
||||||
|
'/users/{user}/mailboxes?counters={counters}',
|
||||||
|
{ user: user.id, counters: counters ? 'true' : 'false' },
|
||||||
|
false,
|
||||||
|
user.token,
|
||||||
|
(err, response) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback(null, (response && response.results) || []);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(user, mailbox, args, callback) {
|
||||||
|
if (typeof args === 'function' && !callback) {
|
||||||
|
callback = args;
|
||||||
|
args = false;
|
||||||
|
}
|
||||||
|
args = args || {};
|
||||||
|
args.user = user.id;
|
||||||
|
args.mailbox = mailbox;
|
||||||
|
_exec('get', '/users/{user}/mailboxes/{mailbox}', args, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(user, mailbox, callback) {
|
||||||
|
_exec('del', '/users/{user}/mailboxes/{mailbox}', { user: user.id, mailbox }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(user, mailbox, data, callback) {
|
||||||
|
_exec('put', '/users/{user}/mailboxes/{mailbox}', { user: user.id, mailbox }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
create(user, data, callback) {
|
||||||
|
_exec('post', '/users/{user}/mailboxes', { user: user.id }, data, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoreply: {
|
||||||
|
get(user, callback) {
|
||||||
|
_exec('get', '/users/{user}/autoreply', { user: user.id }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(user, data, callback) {
|
||||||
|
_exec('put', '/users/{user}/autoreply', { user: user.id }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(user, callback) {
|
||||||
|
_exec('del', '/users/{user}/autoreply', { user: user.id }, false, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
restore: {
|
||||||
|
create(user, data, callback) {
|
||||||
|
_exec('post', '/users/{user}/archived/restore', { user: user.id }, data, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
messages: {
|
||||||
|
list(user, mailbox, data, callback) {
|
||||||
|
data = data || {};
|
||||||
|
_exec(
|
||||||
|
'get',
|
||||||
|
'/users/{user}/mailboxes/{mailbox}/messages?next={next}&previous={previous}&page={page}&unseen={unseen}',
|
||||||
|
{
|
||||||
|
user: user.id,
|
||||||
|
mailbox,
|
||||||
|
next: data.next || '',
|
||||||
|
previous: data.previous || '',
|
||||||
|
page: data.page || '',
|
||||||
|
limit: data.limit || '',
|
||||||
|
unseen: data.unseen ? 'true' : ''
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
user.token,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
search(user, args, callback) {
|
||||||
|
args = args || {};
|
||||||
|
args.user = user.id;
|
||||||
|
_exec('get', '/users/{user}/search', args, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(user, mailbox, message, callback) {
|
||||||
|
_exec(
|
||||||
|
'get',
|
||||||
|
'/users/{user}/mailboxes/{mailbox}/messages/{message}?markAsSeen=true',
|
||||||
|
{ user: user.id, mailbox, message },
|
||||||
|
false,
|
||||||
|
user.token,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
raw(req, res, user, mailbox, message) {
|
||||||
|
let options = {
|
||||||
|
url: config.api.url + _render('/users/{user}/mailboxes/{mailbox}/messages/{message}/message.eml', { user: user.id, mailbox, message }),
|
||||||
|
headers: {
|
||||||
|
'X-Access-Token': user.token
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request(options).pipe(res);
|
||||||
|
},
|
||||||
|
|
||||||
|
update(user, mailbox, data, callback) {
|
||||||
|
_exec('put', '/users/{user}/mailboxes/{mailbox}/messages', { user: user.id, mailbox }, data, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(user, mailbox, message, callback) {
|
||||||
|
_exec('del', '/users/{user}/mailboxes/{mailbox}/messages/{message}', { user: user.id, mailbox, message }, false, user.token, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
submit(user, data, callback) {
|
||||||
|
_exec('post', '/users/{user}/submit', { user: user.id }, data, user.token, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
attachment: {
|
||||||
|
get(req, res, user, mailbox, message, attachment) {
|
||||||
|
let options = {
|
||||||
|
url:
|
||||||
|
config.api.url +
|
||||||
|
_render('/users/{user}/mailboxes/{mailbox}/messages/{message}/attachments/{attachment}', { user: user.id, mailbox, message, attachment }),
|
||||||
|
headers: {
|
||||||
|
'X-Access-Token': user.token
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request(options).pipe(res);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updates: {
|
||||||
|
stream(req, res, user) {
|
||||||
|
let options = {
|
||||||
|
url: config.api.url + _render('/users/{user}/updates', { user: user.id }),
|
||||||
|
headers: {
|
||||||
|
'X-Access-Token': user.token
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let stream = request(options);
|
||||||
|
stream.pipe(
|
||||||
|
res,
|
||||||
|
{ end: false }
|
||||||
|
);
|
||||||
|
let stopped = false;
|
||||||
|
let stop = err => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
if (stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stopped = true;
|
||||||
|
try {
|
||||||
|
stream.abort();
|
||||||
|
} catch (E) {
|
||||||
|
console.error(E);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
req.once('end', stop);
|
||||||
|
req.once('close', stop);
|
||||||
|
req.once('error', stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function _render(path, args) {
|
||||||
|
args = args || {};
|
||||||
|
let found = new Set();
|
||||||
|
let url = path.replace(/\{([^}]+)\}/g, (match, key) => {
|
||||||
|
if (key in args) {
|
||||||
|
found.add(key);
|
||||||
|
return encodeURIComponent(args[key]);
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
let get = Object.keys(args || {})
|
||||||
|
.filter(key => !found.has(key) && args[key])
|
||||||
|
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(args[key]))
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
url = url + (get.length ? (url.indexOf('?') < 0 ? '?' : '&') + get : '');
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _exec(method, path, args, body, token, callback) {
|
||||||
|
let req = [
|
||||||
|
{
|
||||||
|
path: _render(path, args)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
req[0].headers = {
|
||||||
|
'X-Access-Token': token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
req.push(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.push((err, req, res, obj) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
if (obj.error) {
|
||||||
|
let err = new Error(obj.error);
|
||||||
|
if (obj.code) {
|
||||||
|
err.code = obj.code;
|
||||||
|
}
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obj.success) {
|
||||||
|
return callback(new Error('Invalid response state'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null, obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
client[method](...req);
|
||||||
|
}
|
||||||
454
production/haraka-wildduck/maildropper.js
Archivo normal
454
production/haraka-wildduck/maildropper.js
Archivo normal
@@ -0,0 +1,454 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const SeqIndex = require('seq-index');
|
||||||
|
const RelaxedBody = require('nodemailer/lib/dkim/relaxed-body');
|
||||||
|
const MessageSplitter = require('./message-splitter');
|
||||||
|
const seqIndex = new SeqIndex();
|
||||||
|
const GridFSBucket = require('mongodb').GridFSBucket;
|
||||||
|
const uuid = require('uuid');
|
||||||
|
const os = require('os');
|
||||||
|
const hostname = 'hatthieves.es'; // os.hostname().toLowerCase();
|
||||||
|
const addressparser = require('nodemailer/lib/addressparser');
|
||||||
|
const punycode = require('punycode');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const tools = require('./tools');
|
||||||
|
|
||||||
|
class Maildropper {
|
||||||
|
constructor(options) {
|
||||||
|
this.options = options || {};
|
||||||
|
this.db = options.db;
|
||||||
|
this.zone = options.zone;
|
||||||
|
this.collection = options.collection;
|
||||||
|
this.gfs = options.gfs;
|
||||||
|
|
||||||
|
this.gridstore =
|
||||||
|
options.gridstore ||
|
||||||
|
new GridFSBucket(this.db.senderDb, {
|
||||||
|
bucketName: this.gfs
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLoop(envelope, deliveries) {
|
||||||
|
if (envelope.reason !== 'forward') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (envelope.headers.get('Received').length >= 30) {
|
||||||
|
envelope.looped = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.options.loopSecret) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loopKey = 'X-WildDuck-Seen';
|
||||||
|
const algo = 'sha256';
|
||||||
|
const secret = this.options.loopSecret;
|
||||||
|
|
||||||
|
const targetStr = JSON.stringify(deliveries);
|
||||||
|
|
||||||
|
const loopFields = envelope.headers.getDecoded(loopKey);
|
||||||
|
|
||||||
|
// check existing loop headers (max 100 to avoid checking too many hashes)
|
||||||
|
for (let i = 0, len = Math.min(loopFields.length, 100); i < len; i++) {
|
||||||
|
let field = (loopFields[i].value || '').toLowerCase().trim();
|
||||||
|
let salt = field.substr(0, 12);
|
||||||
|
let hash = field.substr(12);
|
||||||
|
let hmac = crypto.createHmac(algo, secret);
|
||||||
|
hmac.update(salt);
|
||||||
|
hmac.update(targetStr);
|
||||||
|
let result = hmac.digest('hex');
|
||||||
|
if (result.toLowerCase() === hash) {
|
||||||
|
// Loop detected!
|
||||||
|
envelope.looped = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const salt = crypto.randomBytes(6).toString('hex').toLowerCase();
|
||||||
|
const loopHeader = salt + crypto.createHmac(algo, secret).update(salt).update(targetStr).digest('hex').toLocaleLowerCase();
|
||||||
|
envelope.headers.add(loopKey, loopHeader);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(options, callback) {
|
||||||
|
let id = options.id || seqIndex.get();
|
||||||
|
let seq = 0;
|
||||||
|
let documents = [];
|
||||||
|
|
||||||
|
let envelope = {
|
||||||
|
id,
|
||||||
|
|
||||||
|
from: options.from || '',
|
||||||
|
to: Array.isArray(options.to) ? options.to : [].concat(options.to || []),
|
||||||
|
|
||||||
|
interface: options.interface || 'maildrop',
|
||||||
|
transtype: 'API',
|
||||||
|
time: Date.now(),
|
||||||
|
|
||||||
|
dkim: {
|
||||||
|
hashAlgo: 'sha256'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.parentId) {
|
||||||
|
envelope.parentId = options.parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.reason) {
|
||||||
|
envelope.reason = options.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
let deliveries = [];
|
||||||
|
|
||||||
|
if (options.targets) {
|
||||||
|
options.targets.forEach(target => {
|
||||||
|
switch (target.type) {
|
||||||
|
case 'mail':
|
||||||
|
deliveries.push({
|
||||||
|
to: target.value,
|
||||||
|
forwardedFor: target.recipient
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'relay':
|
||||||
|
{
|
||||||
|
let recipients = new Set([].concat(options.to || []).concat(target.recipient || []));
|
||||||
|
recipients.forEach(to => {
|
||||||
|
let relayData = target.value;
|
||||||
|
if (typeof relayData === 'string') {
|
||||||
|
relayData = tools.getRelayData(relayData);
|
||||||
|
}
|
||||||
|
deliveries.push({
|
||||||
|
to,
|
||||||
|
mx: relayData.mx,
|
||||||
|
mxPort: relayData.mxPort,
|
||||||
|
mxAuth: relayData.mxAuth,
|
||||||
|
mxSecure: relayData.mxSecure,
|
||||||
|
skipSRS: true,
|
||||||
|
forwardedFor: target.recipient
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'http':
|
||||||
|
{
|
||||||
|
let recipients = new Set([].concat(options.to || []).concat(target.recipient || []));
|
||||||
|
recipients.forEach(to => {
|
||||||
|
deliveries.push({
|
||||||
|
to,
|
||||||
|
http: true,
|
||||||
|
targetUrl: target.value,
|
||||||
|
skipSRS: true,
|
||||||
|
forwardedFor: target.recipient
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deliveries.length) {
|
||||||
|
deliveries = envelope.to.map(to => ({
|
||||||
|
to
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deliveries.length) {
|
||||||
|
let err = new Error('No valid recipients');
|
||||||
|
err.code = 'ENORECIPIENTS';
|
||||||
|
setImmediate(() => callback(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let messageSplitter = new MessageSplitter();
|
||||||
|
let dkimStream = new RelaxedBody(envelope.dkim);
|
||||||
|
|
||||||
|
messageSplitter.once('headers', headers => {
|
||||||
|
envelope.headers = headers;
|
||||||
|
this.updateHeaders(envelope, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
dkimStream.on('hash', bodyHash => {
|
||||||
|
// store relaxed body hash for signing
|
||||||
|
envelope.dkim.bodyHash = bodyHash;
|
||||||
|
envelope.bodySize = dkimStream.byteLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
messageSplitter.once('error', err => dkimStream.emit('error', err));
|
||||||
|
|
||||||
|
this.store(id, dkimStream, err => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.checkLoop(envelope, deliveries)) {
|
||||||
|
// looped message
|
||||||
|
let err = new Error('Message loop detected');
|
||||||
|
err.code = 'ELOOP';
|
||||||
|
return this.removeMessage(id, () => callback(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
envelope.headers = envelope.headers.getList();
|
||||||
|
this.setMeta(id, envelope, err => {
|
||||||
|
if (err) {
|
||||||
|
return this.removeMessage(id, () => callback(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
let date = new Date();
|
||||||
|
|
||||||
|
for (let i = 0, len = deliveries.length; i < len; i++) {
|
||||||
|
let recipient = deliveries[i];
|
||||||
|
|
||||||
|
let deliveryZone = options.zone || this.zone || 'default';
|
||||||
|
let recipientDomain = recipient.to.substr(recipient.to.lastIndexOf('@') + 1).replace(/[[\]]/g, '');
|
||||||
|
|
||||||
|
seq++;
|
||||||
|
let deliverySeq = (seq < 0x100 ? '0' : '') + (seq < 0x10 ? '0' : '') + seq.toString(16);
|
||||||
|
let delivery = {
|
||||||
|
id,
|
||||||
|
seq: deliverySeq,
|
||||||
|
|
||||||
|
// Actual delivery data
|
||||||
|
domain: recipientDomain,
|
||||||
|
sendingZone: deliveryZone,
|
||||||
|
|
||||||
|
assigned: 'no',
|
||||||
|
|
||||||
|
// actual recipient address
|
||||||
|
recipient: recipient.to,
|
||||||
|
|
||||||
|
locked: false,
|
||||||
|
lockTime: 0,
|
||||||
|
|
||||||
|
// earliest time to attempt delivery, defaults to now
|
||||||
|
queued: options.sendTime || date,
|
||||||
|
|
||||||
|
// queued date might change but created should not
|
||||||
|
created: date
|
||||||
|
};
|
||||||
|
|
||||||
|
if (recipient.http) {
|
||||||
|
delivery.http = recipient.http;
|
||||||
|
delivery.targetUrl = recipient.targetUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
['mx', 'mxPort', 'mxAuth', 'mxSecure'].forEach(key => {
|
||||||
|
if (recipient[key]) {
|
||||||
|
delivery[key] = recipient[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recipient.skipSRS) {
|
||||||
|
delivery.skipSRS = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
documents.push(delivery);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.db.senderDb.collection(this.collection).insertMany(
|
||||||
|
documents,
|
||||||
|
{
|
||||||
|
w: 1,
|
||||||
|
ordered: false
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, envelope);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
messageSplitter.pipe(dkimStream);
|
||||||
|
return messageSplitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertAddresses(addresses, withNames, addressList) {
|
||||||
|
addressList = addressList || new Map();
|
||||||
|
|
||||||
|
this.flatten(addresses || []).forEach(address => {
|
||||||
|
if (address.address) {
|
||||||
|
let normalized = this.normalizeAddress(address, withNames);
|
||||||
|
let key = typeof normalized === 'string' ? normalized : normalized.address;
|
||||||
|
addressList.set(key, normalized);
|
||||||
|
} else if (address.group) {
|
||||||
|
this.convertAddresses(address.group, withNames, addressList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return addressList;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAddressList(headers, key, withNames) {
|
||||||
|
return this.parseAddressses(
|
||||||
|
headers.getDecoded(key).map(header => header.value),
|
||||||
|
withNames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAddressses(headerList, withNames) {
|
||||||
|
let map = this.convertAddresses(
|
||||||
|
headerList.map(address => {
|
||||||
|
if (typeof address === 'string') {
|
||||||
|
address = addressparser(address);
|
||||||
|
}
|
||||||
|
return address;
|
||||||
|
}),
|
||||||
|
withNames
|
||||||
|
);
|
||||||
|
return Array.from(map).map(entry => entry[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeDomain(domain) {
|
||||||
|
domain = domain.toLowerCase().trim();
|
||||||
|
try {
|
||||||
|
domain = punycode.toASCII(domain);
|
||||||
|
} catch (E) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to flatten arrays
|
||||||
|
flatten(arr) {
|
||||||
|
let flat = [].concat(...arr);
|
||||||
|
return flat.some(Array.isArray) ? this.flatten(flat) : flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() + '@' + this.normalizeDomain(domain);
|
||||||
|
|
||||||
|
if (withNames) {
|
||||||
|
return {
|
||||||
|
name: address.name || '',
|
||||||
|
address: addr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHeaders(envelope, options) {
|
||||||
|
let updateDate = options && options.updateDate;
|
||||||
|
// Fetch sender and receiver addresses
|
||||||
|
envelope.parsedEnvelope = {
|
||||||
|
from: this.parseAddressList(envelope.headers, 'from').shift() || false,
|
||||||
|
to: this.parseAddressList(envelope.headers, 'to'),
|
||||||
|
cc: this.parseAddressList(envelope.headers, 'cc'),
|
||||||
|
bcc: this.parseAddressList(envelope.headers, 'bcc'),
|
||||||
|
replyTo: this.parseAddressList(envelope.headers, 'reply-to').shift() || false,
|
||||||
|
sender: this.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 (updateDate || !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');
|
||||||
|
}
|
||||||
|
|
||||||
|
store(id, stream, callback) {
|
||||||
|
let returned = false;
|
||||||
|
let store = this.gridstore.openUploadStream('message ' + id, {
|
||||||
|
fsync: true,
|
||||||
|
contentType: 'message/rfc822',
|
||||||
|
metadata: {
|
||||||
|
created: new Date()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.once('error', err => {
|
||||||
|
if (returned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
returned = true;
|
||||||
|
|
||||||
|
store.once('finish', () => {
|
||||||
|
this.removeMessage(id, () => callback(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
store.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
store.once('error', err => {
|
||||||
|
if (returned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
returned = true;
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
store.once('finish', () => {
|
||||||
|
if (returned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
returned = true;
|
||||||
|
|
||||||
|
return callback(null, id);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.pipe(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(id, callback) {
|
||||||
|
this.gridstore.delete('message ' + id, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeta(id, data, callback) {
|
||||||
|
this.db.senderDb.collection(this.gfs + '.files').findOneAndUpdate(
|
||||||
|
{
|
||||||
|
filename: 'message ' + id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
'metadata.data': data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Maildropper;
|
||||||
54
production/haraka-wildduck/wildduck-mta/config/plugins/wildduck.toml
Archivo normal
54
production/haraka-wildduck/wildduck-mta/config/plugins/wildduck.toml
Archivo normal
@@ -0,0 +1,54 @@
|
|||||||
|
# plugins/wildduck.toml
|
||||||
|
["modules/zonemta-wildduck"]
|
||||||
|
enabled=["receiver", "sender"]
|
||||||
|
|
||||||
|
# to which SMTP interfaces this plugin applies to. Use "*" for all interfaces
|
||||||
|
interfaces=["feeder"]
|
||||||
|
|
||||||
|
# optional hostname to be used in headers
|
||||||
|
# defaults to os.hostname()
|
||||||
|
hostname="hatthieves.es"
|
||||||
|
|
||||||
|
# How long to keep auth records in log
|
||||||
|
authlogExpireDays=30
|
||||||
|
|
||||||
|
|
||||||
|
disableUploads=false # if true then messages are not uploaded to Sent Mail folder
|
||||||
|
uploadAll=false # if false then messages from Outlook are not uploaded to Sent Mail folder
|
||||||
|
|
||||||
|
# SRS settings for forwarded emails
|
||||||
|
# ---------------------------------
|
||||||
|
|
||||||
|
["modules/zonemta-wildduck".srs]
|
||||||
|
# Handle rewriting of forwarded emails. If false then SRS is not used
|
||||||
|
# Only affect messages that have interface set to "forwarder"
|
||||||
|
enabled=true
|
||||||
|
|
||||||
|
# SRS secret value. Must be the same as in the MX side
|
||||||
|
secret="a secret cat"
|
||||||
|
|
||||||
|
# SRS domain, must resolve back to MX
|
||||||
|
rewriteDomain="hatthieves.es"
|
||||||
|
|
||||||
|
# DKIM Settings
|
||||||
|
# -------------
|
||||||
|
|
||||||
|
["modules/zonemta-wildduck".dkim]
|
||||||
|
# If true then also adds a signature for the outbound domain
|
||||||
|
signTransportDomain=true
|
||||||
|
|
||||||
|
# If set then decrypt encrypted DKIM keys using this password
|
||||||
|
#secret="a secret cat"
|
||||||
|
|
||||||
|
# Cipher to use to decrypt encrypted DKIM keys
|
||||||
|
#cipher="aes192"
|
||||||
|
|
||||||
|
|
||||||
|
["modules/zonemta-wildduck".gelf]
|
||||||
|
enabled=false
|
||||||
|
component="mta"
|
||||||
|
|
||||||
|
["modules/zonemta-wildduck".gelf.options]
|
||||||
|
graylogPort=12201
|
||||||
|
graylogHostname='127.0.0.1'
|
||||||
|
connection='lan'
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[zonemta-wildduck]
|
||||||
|
enabled=true
|
||||||
|
#enabled="receiver"
|
||||||
|
interfaces=["feeder"]
|
||||||
1001
production/haraka-wildduck/zonemta-wildduck.js
Archivo normal
1001
production/haraka-wildduck/zonemta-wildduck.js
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar Diff
Referencia en una nueva incidencia
Block a user