allow multiple imap interfaces

This commit is contained in:
Andris Reinman 2017-11-06 18:00:09 +02:00
parent b677747ae7
commit 8dbb31916c
3 changed files with 154 additions and 94 deletions

View file

@ -29,6 +29,14 @@ ignoredHosts=[]
#version="1.0.0" #version="1.0.0"
#vendor="Wild Duck" #vendor="Wild Duck"
# Add extra IMAP interfaces
#[[interface]]
#enabled=true
#port=9143
#host="0.0.0.0"
#secure=false
#ignoreSTARTTLS=true
[setup] [setup]
# Public configuration for IMAP # Public configuration for IMAP
hostname="localhost" hostname="localhost"

View file

@ -138,9 +138,12 @@ class IMAPConnection extends EventEmitter {
tnx: 'connect', tnx: 'connect',
cid: this.id cid: this.id
}, },
'[%s] Connection from %s', '[%s] %s from %s to %s:%s',
this.id, this.id,
this.clientHostname this.secure ? 'Secure connection' : 'Connection',
this.clientHostname,
this._socket && this._socket.localAddress,
this._socket && this._socket.localPort
); );
this.send( this.send(

233
imap.js
View file

@ -38,46 +38,23 @@ const onSearch = require('./lib/handlers/on-search');
const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root'); const onGetQuotaRoot = require('./lib/handlers/on-get-quota-root');
const onGetQuota = require('./lib/handlers/on-get-quota'); const onGetQuota = require('./lib/handlers/on-get-quota');
// Setup server let logger = {
const serverOptions = { info(...args) {
secure: config.imap.secure, args.shift();
disableSTARTTLS: config.imap.disableSTARTTLS, log.info('IMAP', ...args);
ignoreSTARTTLS: config.imap.ignoreSTARTTLS,
useProxy: !!config.imap.useProxy,
ignoredHosts: config.imap.ignoredHosts,
id: {
name: config.imap.name || 'Wild Duck IMAP Server',
version: config.imap.version || packageData.version,
vendor: config.imap.vendor || 'Kreata'
}, },
debug(...args) {
logger: { args.shift();
info(...args) { log.silly('IMAP', ...args);
args.shift();
log.info('IMAP', ...args);
},
debug(...args) {
args.shift();
log.silly('IMAP', ...args);
},
error(...args) {
args.shift();
log.error('IMAP', ...args);
}
}, },
error(...args) {
maxMessage: config.imap.maxMB * 1024 * 1024, args.shift();
maxStorage: config.maxStorage * 1024 * 1024 log.error('IMAP', ...args);
}
}; };
certs.loadTLSOptions(serverOptions, 'imap'); let indexer;
let notifier;
const server = new IMAPServer(serverOptions);
certs.registerReload(server, 'imap');
let messageHandler; let messageHandler;
let userHandler; let userHandler;
let mailboxHandler; let mailboxHandler;
@ -91,7 +68,7 @@ function clearExpiredMessages() {
// First, acquire the lock. This prevents multiple connected clients for deleting the same messages // First, acquire the lock. This prevents multiple connected clients for deleting the same messages
gcLock.acquireLock('gc_expired', Math.round(consts.GC_INTERVAL * 1.2) /* Lock expires if not released */, (err, lock) => { gcLock.acquireLock('gc_expired', Math.round(consts.GC_INTERVAL * 1.2) /* Lock expires if not released */, (err, lock) => {
if (err) { if (err) {
server.logger.error( logger.error(
{ {
tnx: 'gc', tnx: 'gc',
err err
@ -111,7 +88,7 @@ function clearExpiredMessages() {
let done = () => { let done = () => {
gcLock.releaseLock(lock, err => { gcLock.releaseLock(lock, err => {
if (err) { if (err) {
server.logger.error( logger.error(
{ {
tnx: 'gc', tnx: 'gc',
err err
@ -156,7 +133,7 @@ function clearExpiredMessages() {
// delete all attachments that do not have any active links to message objects // delete all attachments that do not have any active links to message objects
messageHandler.attachmentStorage.deleteOrphaned(() => { messageHandler.attachmentStorage.deleteOrphaned(() => {
if (deleted) { if (deleted) {
server.logger.debug( logger.debug(
{ {
tnx: 'gc' tnx: 'gc'
}, },
@ -182,7 +159,7 @@ function clearExpiredMessages() {
return clear(); return clear();
} }
server.logger.info( logger.info(
{ {
tnx: 'gc', tnx: 'gc',
err err
@ -217,6 +194,84 @@ function clearExpiredMessages() {
}); });
} }
let createInterface = (ifaceOptions, callback) => {
// Setup server
const serverOptions = {
secure: ifaceOptions.secure,
disableSTARTTLS: ifaceOptions.disableSTARTTLS,
ignoreSTARTTLS: ifaceOptions.ignoreSTARTTLS,
useProxy: !!config.imap.useProxy,
ignoredHosts: config.imap.ignoredHosts,
id: {
name: config.imap.name || 'Wild Duck IMAP Server',
version: config.imap.version || packageData.version,
vendor: config.imap.vendor || 'Kreata'
},
logger,
maxMessage: config.imap.maxMB * 1024 * 1024,
maxStorage: config.maxStorage * 1024 * 1024
};
certs.loadTLSOptions(serverOptions, 'imap');
const server = new IMAPServer(serverOptions);
certs.registerReload(server, 'imap');
let started = false;
server.on('error', err => {
if (!started) {
started = true;
return callback(err);
}
logger.error(
{
err
},
'%s',
err.message
);
});
server.indexer = indexer;
server.notifier = notifier;
// setup command handlers for the server instance
server.onFetch = onFetch(server, messageHandler);
server.onAuth = onAuth(server, userHandler);
server.onList = onList(server);
server.onLsub = onLsub(server);
server.onSubscribe = onSubscribe(server);
server.onUnsubscribe = onUnsubscribe(server);
server.onCreate = onCreate(server, mailboxHandler);
server.onRename = onRename(server, mailboxHandler);
server.onDelete = onDelete(server, mailboxHandler);
server.onOpen = onOpen(server);
server.onStatus = onStatus(server);
server.onAppend = onAppend(server, messageHandler);
server.onStore = onStore(server);
server.onExpunge = onExpunge(server, messageHandler);
server.onCopy = onCopy(server, messageHandler);
server.onMove = onMove(server, messageHandler);
server.onSearch = onSearch(server);
server.onGetQuotaRoot = onGetQuotaRoot(server);
server.onGetQuota = onGetQuota(server);
// start listening
server.listen(ifaceOptions.port, ifaceOptions.host, () => {
if (started) {
return server.close();
}
started = true;
callback(null, server);
});
};
module.exports = done => { module.exports = done => {
if (!config.imap.enabled) { if (!config.imap.enabled) {
return setImmediate(() => done(null, false)); return setImmediate(() => done(null, false));
@ -231,12 +286,12 @@ module.exports = done => {
gcTimeout.unref(); gcTimeout.unref();
let start = () => { let start = () => {
server.indexer = new Indexer({ indexer = new Indexer({
database: db.database database: db.database
}); });
// setup notification system for updates // setup notification system for updates
server.notifier = new ImapNotifier({ notifier = new ImapNotifier({
database: db.database, database: db.database,
redis: db.redis redis: db.redis
}); });
@ -259,61 +314,55 @@ module.exports = done => {
database: db.database, database: db.database,
users: db.users, users: db.users,
redis: db.redis, redis: db.redis,
notifier: server.notifier notifier
}); });
let started = false; let ifaceOptions = [
{
server.on('error', err => { enabled: true,
if (!started) { secure: config.imap.secure,
started = true; disableSTARTTLS: config.imap.disableSTARTTLS,
return done(err); ignoreSTARTTLS: config.imap.ignoreSTARTTLS,
host: config.imap.host,
port: config.imap.port
} }
server.logger.error( ]
{ .concat(config.imap.interface || [])
err .filter(iface => iface.enabled);
},
'%s',
err.message
);
});
// start listening let iPos = 0;
server.listen(config.imap.port, config.imap.host, () => { let startInterfaces = () => {
if (started) { if (iPos >= ifaceOptions.length) {
return server.close(); return done();
} }
started = true; let opts = ifaceOptions[iPos++];
done(null, server);
});
// setup command handlers for the server instance createInterface(opts, err => {
server.onFetch = onFetch(server, messageHandler); if (err) {
server.onAuth = onAuth(server, userHandler); logger.error(
server.onList = onList(server); {
server.onLsub = onLsub(server); err,
server.onSubscribe = onSubscribe(server); tnx: 'bind'
server.onUnsubscribe = onUnsubscribe(server); },
server.onCreate = onCreate(server, mailboxHandler); 'Failed starting %sIMAP interface %s:%s. %s',
server.onRename = onRename(server, mailboxHandler); opts.secure ? 'secure ' : '',
server.onDelete = onDelete(server, mailboxHandler); opts.host,
server.onOpen = onOpen(server); opts.port,
server.onStatus = onStatus(server); err.message
server.onAppend = onAppend(server, messageHandler); );
server.onStore = onStore(server); return done(err);
server.onExpunge = onExpunge(server, messageHandler); }
server.onCopy = onCopy(server, messageHandler); setImmediate(startInterfaces);
server.onMove = onMove(server, messageHandler); });
server.onSearch = onSearch(server); };
server.onGetQuotaRoot = onGetQuotaRoot(server); setImmediate(startInterfaces);
server.onGetQuota = onGetQuota(server);
}; };
let collections = setupIndexes.collections; let collections = setupIndexes.collections;
let collectionpos = 0; let collectionpos = 0;
let ensureCollections = next => { let ensureCollections = next => {
if (collectionpos >= collections.length) { if (collectionpos >= collections.length) {
server.logger.info( logger.info(
{ {
tnx: 'mongo' tnx: 'mongo'
}, },
@ -325,7 +374,7 @@ module.exports = done => {
let collection = collections[collectionpos++]; let collection = collections[collectionpos++];
db[collection.type || 'database'].createCollection(collection.collection, collection.options, err => { db[collection.type || 'database'].createCollection(collection.collection, collection.options, err => {
if (err) { if (err) {
server.logger.error( logger.error(
{ {
err, err,
tnx: 'mongo' tnx: 'mongo'
@ -345,7 +394,7 @@ module.exports = done => {
let indexpos = 0; let indexpos = 0;
let ensureIndexes = next => { let ensureIndexes = next => {
if (indexpos >= indexes.length) { if (indexpos >= indexes.length) {
server.logger.info( logger.info(
{ {
tnx: 'mongo' tnx: 'mongo'
}, },
@ -357,7 +406,7 @@ module.exports = done => {
let index = indexes[indexpos++]; let index = indexes[indexpos++];
db[index.type || 'database'].collection(index.collection).createIndexes([index.index], (err, r) => { db[index.type || 'database'].collection(index.collection).createIndexes([index.index], (err, r) => {
if (err) { if (err) {
server.logger.error( logger.error(
{ {
err, err,
tnx: 'mongo' tnx: 'mongo'
@ -368,7 +417,7 @@ module.exports = done => {
err.message err.message
); );
} else if (r.numIndexesAfter !== r.numIndexesBefore) { } else if (r.numIndexesAfter !== r.numIndexesBefore) {
server.logger.debug( logger.debug(
{ {
tnx: 'mongo' tnx: 'mongo'
}, },
@ -377,7 +426,7 @@ module.exports = done => {
JSON.stringify(index.collection + '.' + index.index.name) JSON.stringify(index.collection + '.' + index.index.name)
); );
} else { } else {
server.logger.debug( logger.debug(
{ {
tnx: 'mongo' tnx: 'mongo'
}, },
@ -394,7 +443,7 @@ module.exports = done => {
gcLock.acquireLock('db_indexes', 1 * 60 * 1000, (err, lock) => { gcLock.acquireLock('db_indexes', 1 * 60 * 1000, (err, lock) => {
if (err) { if (err) {
server.logger.error( logger.error(
{ {
tnx: 'gc', tnx: 'gc',
err err
@ -413,7 +462,7 @@ module.exports = done => {
setTimeout(() => { setTimeout(() => {
gcLock.releaseLock(lock, err => { gcLock.releaseLock(lock, err => {
if (err) { if (err) {
server.logger.error( logger.error(
{ {
tnx: 'gc', tnx: 'gc',
err err