mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-09-20 15:26:03 +08:00
allow multiple imap interfaces
This commit is contained in:
parent
b677747ae7
commit
8dbb31916c
|
@ -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"
|
||||||
|
|
|
@ -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(
|
||||||
|
|
209
imap.js
209
imap.js
|
@ -38,22 +38,7 @@ 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 = {
|
|
||||||
secure: config.imap.secure,
|
|
||||||
disableSTARTTLS: config.imap.disableSTARTTLS,
|
|
||||||
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'
|
|
||||||
},
|
|
||||||
|
|
||||||
logger: {
|
|
||||||
info(...args) {
|
info(...args) {
|
||||||
args.shift();
|
args.shift();
|
||||||
log.info('IMAP', ...args);
|
log.info('IMAP', ...args);
|
||||||
|
@ -66,18 +51,10 @@ const serverOptions = {
|
||||||
args.shift();
|
args.shift();
|
||||||
log.error('IMAP', ...args);
|
log.error('IMAP', ...args);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
maxMessage: config.imap.maxMB * 1024 * 1024,
|
|
||||||
maxStorage: config.maxStorage * 1024 * 1024
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 => {
|
|
||||||
if (!started) {
|
|
||||||
started = true;
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
server.logger.error(
|
|
||||||
{
|
{
|
||||||
err
|
enabled: true,
|
||||||
|
secure: config.imap.secure,
|
||||||
|
disableSTARTTLS: config.imap.disableSTARTTLS,
|
||||||
|
ignoreSTARTTLS: config.imap.ignoreSTARTTLS,
|
||||||
|
host: config.imap.host,
|
||||||
|
port: config.imap.port
|
||||||
|
}
|
||||||
|
]
|
||||||
|
.concat(config.imap.interface || [])
|
||||||
|
.filter(iface => iface.enabled);
|
||||||
|
|
||||||
|
let iPos = 0;
|
||||||
|
let startInterfaces = () => {
|
||||||
|
if (iPos >= ifaceOptions.length) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
let opts = ifaceOptions[iPos++];
|
||||||
|
|
||||||
|
createInterface(opts, err => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(
|
||||||
|
{
|
||||||
|
err,
|
||||||
|
tnx: 'bind'
|
||||||
},
|
},
|
||||||
'%s',
|
'Failed starting %sIMAP interface %s:%s. %s',
|
||||||
|
opts.secure ? 'secure ' : '',
|
||||||
|
opts.host,
|
||||||
|
opts.port,
|
||||||
err.message
|
err.message
|
||||||
);
|
);
|
||||||
});
|
return done(err);
|
||||||
|
|
||||||
// start listening
|
|
||||||
server.listen(config.imap.port, config.imap.host, () => {
|
|
||||||
if (started) {
|
|
||||||
return server.close();
|
|
||||||
}
|
}
|
||||||
started = true;
|
setImmediate(startInterfaces);
|
||||||
done(null, server);
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
// setup command handlers for the server instance
|
setImmediate(startInterfaces);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
||||||
|
|
Loading…
Reference in a new issue