wildduck/lmtp.js

279 lines
8.1 KiB
JavaScript
Raw Normal View History

'use strict';
2017-04-13 02:59:30 +08:00
// Simple LMTP server that accepts all messages for valid recipients
2017-07-16 19:37:33 +08:00
const config = require('wild-config');
const log = require('npmlog');
2017-10-18 17:42:51 +08:00
const ObjectID = require('mongodb').ObjectID;
const SMTPServer = require('smtp-server').SMTPServer;
const tools = require('./lib/tools');
const MessageHandler = require('./lib/message-handler');
2017-12-01 21:04:32 +08:00
const UserHandler = require('./lib/user-handler');
2017-10-18 17:42:51 +08:00
const FilterHandler = require('./lib/filter-handler');
const db = require('./lib/db');
const certs = require('./lib/certs');
2018-10-18 15:37:32 +08:00
const Gelf = require('gelf');
const os = require('os');
let messageHandler;
2017-12-01 21:04:32 +08:00
let userHandler;
2017-10-18 17:42:51 +08:00
let filterHandler;
2018-10-18 15:37:32 +08:00
let loggelf;
config.on('reload', () => {
log.info('LMTP', 'Configuration reloaded');
});
2017-04-12 19:52:29 +08:00
const serverOptions = {
lmtp: true,
2017-12-01 16:02:40 +08:00
secure: config.lmtp.secure,
secured: config.lmtp.secured,
// log to console
logger: {
info(...args) {
args.shift();
2017-04-12 19:52:29 +08:00
log.info('LMTP', ...args);
},
debug(...args) {
args.shift();
2017-04-12 19:52:29 +08:00
log.silly('LMTP', ...args);
},
error(...args) {
args.shift();
2017-04-12 19:52:29 +08:00
log.error('LMTP', ...args);
}
},
2017-09-11 03:53:12 +08:00
name: config.lmtp.name || false,
// not required but nice-to-have
2018-01-02 21:04:01 +08:00
banner: config.lmtp.banner || 'Welcome to WildDuck Mail Server',
2017-09-11 03:53:12 +08:00
disabledCommands: ['AUTH'].concat(config.lmtp.disableSTARTTLS ? 'STARTTLS' : []),
2017-03-30 01:06:09 +08:00
onMailFrom(address, session, callback) {
// reset session entries
2017-04-12 19:52:29 +08:00
session.users = [];
2017-03-30 01:06:09 +08:00
2017-04-12 19:52:29 +08:00
// accept alls sender addresses
2017-03-30 01:06:09 +08:00
return callback();
},
// Validate RCPT TO envelope address. Example allows all addresses that do not start with 'deny'
// If this method is not set, all addresses are allowed
onRcptTo(rcpt, session, callback) {
let originalRecipient = tools.normalizeAddress(rcpt.address);
2017-12-01 21:04:32 +08:00
userHandler.get(
originalRecipient,
{
name: true,
forwards: true,
2018-01-21 03:38:56 +08:00
targets: true,
2017-12-01 21:04:32 +08:00
autoreply: true,
encryptMessages: true,
encryptForwarded: true,
2018-01-30 22:14:15 +08:00
pubKey: true,
spamLevel: true
2017-12-01 21:04:32 +08:00
},
(err, userData) => {
if (err) {
log.error('LMTP', err);
return callback(new Error('Database error'));
}
if (!userData) {
return callback(new Error('Unknown recipient'));
2017-11-28 19:57:38 +08:00
}
2017-12-01 21:04:32 +08:00
if (!session.users) {
session.users = [];
}
2017-11-28 19:57:38 +08:00
2017-12-01 21:04:32 +08:00
session.users.push({
recipient: originalRecipient,
user: userData
});
2017-11-28 19:57:38 +08:00
2017-12-01 21:04:32 +08:00
callback();
}
);
},
// Handle message stream
onData(stream, session, callback) {
let chunks = [];
let chunklen = 0;
2017-04-13 16:35:39 +08:00
stream.on('readable', () => {
let chunk;
2017-04-13 16:35:39 +08:00
while ((chunk = stream.read()) !== null) {
2017-04-12 19:52:29 +08:00
chunks.push(chunk);
chunklen += chunk.length;
}
});
stream.once('error', err => {
2017-04-12 19:52:29 +08:00
log.error('LMTP', err);
callback(new Error('Error reading from stream'));
});
2017-04-13 16:35:39 +08:00
stream.once('end', () => {
2017-06-03 14:51:58 +08:00
let sender = tools.normalizeAddress((session.envelope.mailFrom && session.envelope.mailFrom.address) || '');
2017-04-12 19:52:29 +08:00
let responses = [];
let users = session.users;
let stored = 0;
2017-04-13 16:35:39 +08:00
2017-10-18 17:42:51 +08:00
let transactionId = new ObjectID();
let prepared = false;
let storeNext = () => {
if (stored >= users.length) {
return callback(
null,
responses.map(r => r.response)
);
}
2017-04-12 19:52:29 +08:00
let rcptData = users[stored++];
let recipient = rcptData.recipient;
2017-09-16 00:11:42 +08:00
let userData = rcptData.user;
2017-04-12 19:52:29 +08:00
2017-09-16 00:11:42 +08:00
let response = responses.filter(r => r.userData === userData);
2017-04-12 19:52:29 +08:00
if (response.length) {
responses.push(response[0]);
return storeNext();
}
2017-10-18 17:42:51 +08:00
filterHandler.process(
{
mimeTree: prepared && prepared.mimeTree,
maildata: prepared && prepared.maildata,
2017-10-18 17:42:51 +08:00
user: userData,
sender,
recipient,
chunks,
chunklen,
meta: {
transactionId,
source: 'MX',
2017-10-18 17:42:51 +08:00
from: sender,
2017-10-20 18:43:44 +08:00
to: [recipient],
2017-10-18 17:42:51 +08:00
origin: session.remoteAddress,
originhost: session.clientHostname,
transhost: session.hostNameAppearsAs,
transtype: session.transmissionType,
2017-10-20 18:43:44 +08:00
time: new Date()
2017-10-18 17:42:51 +08:00
}
},
(err, response, preparedResponse) => {
if (err) {
2017-10-18 17:42:51 +08:00
// ???
}
2017-10-18 17:42:51 +08:00
if (response) {
responses.push(response);
}
if (!prepared && preparedResponse) {
prepared = preparedResponse;
}
2017-10-18 17:42:51 +08:00
setImmediate(storeNext);
}
);
};
storeNext();
});
}
2017-04-12 19:52:29 +08:00
};
certs.loadTLSOptions(serverOptions, 'lmtp');
2017-04-12 19:52:29 +08:00
const server = new SMTPServer(serverOptions);
certs.registerReload(server, 'lmtp');
module.exports = done => {
2017-04-12 19:52:29 +08:00
if (!config.lmtp.enabled) {
return setImmediate(() => done(null, false));
}
2018-10-18 15:37:32 +08:00
const component = config.log.gelf.component || 'wildduck';
const hostname = config.log.gelf.hostname || os.hostname();
const gelf =
config.log.gelf && config.log.gelf.enabled
? new Gelf(config.log.gelf.options)
: {
// placeholder
emit: (key, message) => log.info('Gelf', JSON.stringify(message))
2018-10-18 15:37:32 +08:00
};
loggelf = message => {
if (typeof message === 'string') {
message = {
short_message: message
};
}
message = message || {};
2018-10-18 16:53:14 +08:00
if (!message.short_message || message.short_message.indexOf(component.toUpperCase()) !== 0) {
message.short_message = component.toUpperCase() + ' ' + (message.short_message || '');
}
2018-10-18 15:37:32 +08:00
message.facility = component; // facility is deprecated but set by the driver if not provided
message.host = hostname;
message.timestamp = Date.now() / 1000;
message._component = component;
Object.keys(message).forEach(key => {
if (!message[key]) {
delete message[key];
}
});
gelf.emit('gelf.log', message);
};
2017-08-07 02:25:10 +08:00
messageHandler = new MessageHandler({
database: db.database,
users: db.users,
redis: db.redis,
gridfs: db.gridfs,
2018-10-18 15:37:32 +08:00
attachments: config.attachments,
loggelf: message => loggelf(message)
2017-08-07 02:25:10 +08:00
});
2017-12-01 21:04:32 +08:00
userHandler = new UserHandler({
database: db.database,
users: db.users,
redis: db.redis,
2018-10-18 15:37:32 +08:00
authlogExpireDays: config.log.authlogExpireDays,
loggelf: message => loggelf(message)
2017-12-01 21:04:32 +08:00
});
2017-10-18 17:42:51 +08:00
filterHandler = new FilterHandler({
2017-12-21 20:24:28 +08:00
db,
sender: config.sender,
2017-10-18 17:42:51 +08:00
messageHandler,
2018-10-18 15:37:32 +08:00
loggelf: message => loggelf(message)
2017-10-18 17:42:51 +08:00
});
let started = false;
server.on('error', err => {
if (!started) {
started = true;
return done(err);
}
2017-04-12 19:52:29 +08:00
log.error('LMTP', err);
});
2017-04-12 19:52:29 +08:00
server.listen(config.lmtp.port, config.lmtp.host, () => {
if (started) {
return server.close();
}
started = true;
done(null, server);
});
};