mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-27 18:58:54 +08:00
Merge branch 'master' of github.com:nodemailer/wildduck
This commit is contained in:
commit
90a4603bd5
17 changed files with 214 additions and 125 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-02-05T18:57:01.438Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
});
|
||||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-02-26T12:32:31.900Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-02-05T18:57:01.438Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
}
|
||||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs",
"title": "WildDuck API",
"url": "https://api.wildduck.email",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2019-02-26T12:32:31.900Z",
"url": "http://apidocjs.com",
"version": "0.17.7"
}
}
|
||||
|
|
|
@ -37,7 +37,7 @@ function send() {
|
|||
|
||||
from: 'Kärbes 🐧 <andris@kreata.ee>',
|
||||
to: recipients
|
||||
.map((rcpt, i) => ({ name: 'Recipient #' + (i + 1), address: rcpt }))
|
||||
.map((rcpt) => ({ name: rcpt.split('@')[0], address: rcpt }))
|
||||
.concat('andris <andris.reinman@gmail.com>, andmekala <andmekala@hot.ee>'),
|
||||
cc: '"Juulius Orro" muna@gmail.com, kixgraft@gmail.com',
|
||||
subject: 'Test ööö message [' + Date.now() + ']',
|
||||
|
|
|
@ -13,7 +13,7 @@ const EventEmitter = require('events').EventEmitter;
|
|||
const packageInfo = require('../../package');
|
||||
const errors = require('../../lib/errors.js');
|
||||
|
||||
const SOCKET_TIMEOUT = 10 * 60 * 1000;
|
||||
const SOCKET_TIMEOUT = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Creates a handler for new socket
|
||||
|
@ -202,7 +202,40 @@ class IMAPConnection extends EventEmitter {
|
|||
*/
|
||||
send(payload, callback) {
|
||||
if (this._socket && this._socket.writable) {
|
||||
this[!this.compression ? '_socket' : '_deflate'].write(payload + '\r\n', 'binary', callback);
|
||||
try {
|
||||
this[!this.compression ? '_socket' : '_deflate'].write(payload + '\r\n', 'binary', (...args) => {
|
||||
if (args[0]) {
|
||||
// write error
|
||||
this.logger.error(
|
||||
{
|
||||
tnx: 'send',
|
||||
cid: this.id,
|
||||
err: args[0]
|
||||
},
|
||||
'[%s] Send error: %s',
|
||||
this.id,
|
||||
args[0].message || args[0]
|
||||
);
|
||||
return this.close();
|
||||
}
|
||||
if (typeof callback === 'function') {
|
||||
return callback(...args);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// write error
|
||||
this.logger.error(
|
||||
{
|
||||
tnx: 'send',
|
||||
cid: this.id,
|
||||
err
|
||||
},
|
||||
'[%s] Send error: %s',
|
||||
this.id,
|
||||
err.message || err
|
||||
);
|
||||
return this.close();
|
||||
}
|
||||
if (this.compression) {
|
||||
// make sure we transmit the message immediatelly
|
||||
this._deflate.flush();
|
||||
|
@ -216,6 +249,9 @@ class IMAPConnection extends EventEmitter {
|
|||
this.id,
|
||||
payload
|
||||
);
|
||||
} else {
|
||||
// socket is not there anymore
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,12 +259,33 @@ class IMAPConnection extends EventEmitter {
|
|||
* Close socket
|
||||
*/
|
||||
close(force) {
|
||||
if (this._closed || this._closing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._socket.destroyed && this._socket.writable) {
|
||||
this._socket[!force ? 'end' : 'destroy']();
|
||||
}
|
||||
|
||||
this._server.connections.delete(this);
|
||||
|
||||
if (!force) {
|
||||
// allow socket to close in 1500ms or force it to close
|
||||
this._closingTimeout = setTimeout(() => {
|
||||
if (this._closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._socket.destroy();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
setImmediate(() => this._onClose());
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
this._closing = true;
|
||||
if (force) {
|
||||
setImmediate(() => this._onClose());
|
||||
|
@ -263,6 +320,8 @@ class IMAPConnection extends EventEmitter {
|
|||
* @event
|
||||
*/
|
||||
_onClose(/* hadError */) {
|
||||
clearTimeout(this._closingTimeout);
|
||||
|
||||
if (this._closed) {
|
||||
return;
|
||||
}
|
||||
|
@ -401,7 +460,8 @@ class IMAPConnection extends EventEmitter {
|
|||
|
||||
if (this.idling) {
|
||||
// see if the connection still works
|
||||
this.send('* OK Still here');
|
||||
this.send('* OK Still here (' + Date.now() + ')');
|
||||
this._socket.setTimeout(this._server.options.socketTimeout || SOCKET_TIMEOUT, this._onTimeout.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -464,6 +524,12 @@ class IMAPConnection extends EventEmitter {
|
|||
*/
|
||||
setupNotificationListener() {
|
||||
let conn = this;
|
||||
|
||||
if (this._closing || this._closed) {
|
||||
// nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
let isSelected = mailbox => mailbox && conn.selected && conn.selected.mailbox && conn.selected.mailbox.toString() === mailbox.toString();
|
||||
|
||||
this._listenerData = {
|
||||
|
@ -471,6 +537,10 @@ class IMAPConnection extends EventEmitter {
|
|||
cleared: false,
|
||||
callback(message) {
|
||||
let selectedMailbox = conn.selected && conn.selected.mailbox;
|
||||
if (this._closing || this._closed) {
|
||||
conn.clearNotificationListener();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
// global triggers
|
||||
|
@ -791,14 +861,13 @@ class IMAPConnection extends EventEmitter {
|
|||
|
||||
switch (key) {
|
||||
case 'FLAGS':
|
||||
value = [].concat(value || []).map(
|
||||
flag =>
|
||||
flag && flag.value
|
||||
? flag
|
||||
: {
|
||||
type: 'ATOM',
|
||||
value: flag
|
||||
}
|
||||
value = [].concat(value || []).map(flag =>
|
||||
flag && flag.value
|
||||
? flag
|
||||
: {
|
||||
type: 'ATOM',
|
||||
value: flag
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
||||
|
|
2
imap.js
2
imap.js
|
@ -240,7 +240,7 @@ module.exports = done => {
|
|||
let iPos = 0;
|
||||
let startInterfaces = () => {
|
||||
if (iPos >= ifaceOptions.length) {
|
||||
return done();
|
||||
return db.redis.del('lim:imap', () => done());
|
||||
}
|
||||
let opts = ifaceOptions[iPos++];
|
||||
|
||||
|
|
|
@ -1955,7 +1955,7 @@ module.exports = (db, server, messageHandler, userHandler) => {
|
|||
* @apiParam {String} reference.mailbox Mailbox ID
|
||||
* @apiParam {Number} reference.id Message ID in Mailbox
|
||||
* @apiParam {String} reference.action Either <code>reply</code>, <code>replyAll</code> or <code>forward</code>
|
||||
* @apiParam {Boolean} reference.attachments=false If true, then also adds attachments from the original message
|
||||
* @apiParam {String[]} reference.attachments=false If true, then includes all attachments from the original message. If it is an array of attachment ID's includes attachments from the list
|
||||
* @apiParam {String} [sess] Session identifier for the logs
|
||||
* @apiParam {String} [ip] IP address for the logs
|
||||
*
|
||||
|
@ -2144,10 +2144,18 @@ module.exports = (db, server, messageHandler, userHandler) => {
|
|||
action: Joi.string()
|
||||
.valid('reply', 'replyAll', 'forward')
|
||||
.required(),
|
||||
attachments: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, ''])
|
||||
.default(false)
|
||||
attachments: Joi.alternatives().try(
|
||||
Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 'on', '1', 1])
|
||||
.falsy(['N', 'false', 'no', 'off', '0', 0, '']),
|
||||
Joi.array()
|
||||
.items(
|
||||
Joi.string()
|
||||
.regex(/^ATT\d+$/i)
|
||||
.uppercase()
|
||||
)
|
||||
.allow([])
|
||||
)
|
||||
}),
|
||||
|
||||
sess: Joi.string().max(255),
|
||||
|
@ -3566,6 +3574,10 @@ module.exports = (db, server, messageHandler, userHandler) => {
|
|||
// skip embedded images
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(options.reference.attachments) && !options.reference.attachments.includes(attachment.id)) {
|
||||
// skip attachments not listed in the API call
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
let attachmentId = messageData.mimeTree.attachmentMap && messageData.mimeTree.attachmentMap[attachment.id];
|
||||
|
|
|
@ -206,7 +206,11 @@ module.exports = (db, server, notifier) => {
|
|||
req.connection.on('error', done);
|
||||
};
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream' });
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Accel-Buffering': 'no'
|
||||
});
|
||||
|
||||
if (lastEventId) {
|
||||
loadJournalStream(db, req, res, user, lastEventId, (err, info) => {
|
||||
|
|
|
@ -352,6 +352,7 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiParam {Number} [imapMaxUpload] How many bytes can be uploaded via IMAP during 24 hour
|
||||
* @apiParam {Number} [imapMaxDownload] How many bytes can be downloaded via IMAP during 24 hour
|
||||
* @apiParam {Number} [pop3MaxDownload] How many bytes can be downloaded via POP3 during 24 hour
|
||||
* @apiParam {Number} [imapMaxConnections] How many parallel IMAP connections are alowed
|
||||
* @apiParam {Number} [receivedMax] How many messages can be received from MX during 60 seconds
|
||||
* @apiParam {Object} [mailboxes] Optional names for special mailboxes
|
||||
* @apiParam {String} [mailboxes.sent="Sent Mail"] Path of Sent Mail folder
|
||||
|
@ -486,6 +487,9 @@ module.exports = (db, server, userHandler) => {
|
|||
pop3MaxDownload: Joi.number()
|
||||
.min(0)
|
||||
.default(0),
|
||||
imapMaxConnections: Joi.number()
|
||||
.min(0)
|
||||
.default(0),
|
||||
receivedMax: Joi.number()
|
||||
.min(0)
|
||||
.default(0),
|
||||
|
@ -909,6 +913,8 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiSuccess {Number} limits.pop3Download.allowed How many bytes per 24 hours can be downloaded via POP3. Only message contents are counted, not protocol overhead.
|
||||
* @apiSuccess {Number} limits.pop3Download.used How many bytes are downloaded during current 24 hour period
|
||||
* @apiSuccess {Number} limits.pop3Download.ttl Time until the end of current 24 hour period
|
||||
* @apiSuccess {Number} limits.imapMaxConnections.allowed How many parallel IMAP connections are permitted
|
||||
* @apiSuccess {Number} limits.imapMaxConnections.used How many parallel IMAP connections are currenlty in use
|
||||
*
|
||||
* @apiSuccess {String[]} tags List of tags associated with the User
|
||||
* @apiSuccess {String[]} disabledScopes Disabled scopes for this user
|
||||
|
@ -1062,6 +1068,8 @@ module.exports = (db, server, userHandler) => {
|
|||
.get('pdw:' + userData._id.toString())
|
||||
.ttl('pdw:' + userData._id.toString())
|
||||
|
||||
.hget('lim:imap', userData._id.toString())
|
||||
|
||||
.exec();
|
||||
} catch (err) {
|
||||
// ignore
|
||||
|
@ -1089,6 +1097,8 @@ module.exports = (db, server, userHandler) => {
|
|||
let pop3Download = Number(response && response[10] && response[10][1]) || 0;
|
||||
let pop3DownloadTtl = Number(response && response[11] && response[11][1]) || 0;
|
||||
|
||||
let imapMaxConnections = Number(response && response[12] && response[12][1]) || 0;
|
||||
|
||||
let keyInfo;
|
||||
try {
|
||||
keyInfo = await getKeyInfo(userData.pubKey);
|
||||
|
@ -1161,6 +1171,11 @@ module.exports = (db, server, userHandler) => {
|
|||
allowed: Number(userData.pop3MaxDownload) || (config.pop3.maxDownloadMB || 10000) * 1024 * 1024,
|
||||
used: pop3Download,
|
||||
ttl: pop3DownloadTtl >= 0 ? pop3DownloadTtl : false
|
||||
},
|
||||
|
||||
imapMaxConnections: {
|
||||
allowed: Number(userData.imapMaxConnections) || config.imap.maxConnections,
|
||||
used: imapMaxConnections
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1210,6 +1225,7 @@ module.exports = (db, server, userHandler) => {
|
|||
* @apiParam {Number} [imapMaxUpload] How many bytes can be uploaded via IMAP during 24 hour
|
||||
* @apiParam {Number} [imapMaxDownload] How many bytes can be downloaded via IMAP during 24 hour
|
||||
* @apiParam {Number} [pop3MaxDownload] How many bytes can be downloaded via POP3 during 24 hour
|
||||
* @apiParam {Number} [imapMaxConnections] How many parallel IMAP connections are alowed
|
||||
* @apiParam {Number} [receivedMax] How many messages can be received from MX during 60 seconds
|
||||
* @apiParam {Boolean} [disable2fa] If true, then disables 2FA for this user
|
||||
* @apiParam {String[]} disabledScopes List of scopes that are disabled for this user ("imap", "pop3", "smtp")
|
||||
|
@ -1319,6 +1335,8 @@ module.exports = (db, server, userHandler) => {
|
|||
imapMaxUpload: Joi.number().min(0),
|
||||
imapMaxDownload: Joi.number().min(0),
|
||||
pop3MaxDownload: Joi.number().min(0),
|
||||
imapMaxConnections: Joi.number().min(0),
|
||||
|
||||
receivedMax: Joi.number().min(0),
|
||||
|
||||
disable2fa: Joi.boolean()
|
||||
|
|
|
@ -1382,6 +1382,8 @@ class UserHandler {
|
|||
imapMaxUpload: data.imapMaxUpload || 0,
|
||||
imapMaxDownload: data.imapMaxDownload || 0,
|
||||
pop3MaxDownload: data.pop3MaxDownload || 0,
|
||||
imapMaxConnections: data.imapMaxConnections || 0,
|
||||
|
||||
receivedMax: data.receivedMax || 0,
|
||||
|
||||
targets: [].concat(data.targets || []),
|
||||
|
@ -2831,6 +2833,7 @@ class UserHandler {
|
|||
['receivedMax', 'rl:rcpt']
|
||||
]);
|
||||
let flushKeys = [];
|
||||
let flushHKeys = [];
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
if (['user', 'existingPassword', 'hashedPassword', 'allowUnsafe', 'ip', 'sess'].includes(key)) {
|
||||
|
@ -2840,6 +2843,9 @@ class UserHandler {
|
|||
if (resetKeys.has(key)) {
|
||||
flushKeys.push(resetKeys.get(key) + ':' + user);
|
||||
}
|
||||
if (key === 'imapMaxConnections') {
|
||||
flushHKeys.push({ key: 'lim:imap', value: user.toString() });
|
||||
}
|
||||
|
||||
if (key === 'password') {
|
||||
if (!data[key]) {
|
||||
|
@ -3042,11 +3048,17 @@ class UserHandler {
|
|||
}
|
||||
|
||||
// check if we need to reset any ttl counters
|
||||
if (flushKeys.length) {
|
||||
if (flushKeys.length || flushHKeys.length) {
|
||||
let flushreq = this.redis.multi();
|
||||
|
||||
flushKeys.forEach(key => {
|
||||
flushreq = flushreq.del(key);
|
||||
});
|
||||
|
||||
flushHKeys.forEach(entry => {
|
||||
flushreq = flushreq.hdel(entry.key, entry.value);
|
||||
});
|
||||
|
||||
// just call the operations and hope for the best, no problems if fails
|
||||
flushreq.exec(() => false);
|
||||
}
|
||||
|
@ -3268,6 +3280,7 @@ class UserHandler {
|
|||
setImmediate(tryDelete);
|
||||
}
|
||||
|
||||
// returns a query to find an user based on address or username
|
||||
checkAddress(username, callback) {
|
||||
if (username.indexOf('@') < 0) {
|
||||
// not formatted as an address, assume regular username
|
||||
|
@ -3276,97 +3289,39 @@ class UserHandler {
|
|||
});
|
||||
}
|
||||
|
||||
let address = tools.normalizeAddress(username, false, {
|
||||
removeLabel: true,
|
||||
removeDots: true
|
||||
});
|
||||
let domain = address.substr(address.indexOf('@') + 1);
|
||||
username = address.substr(0, address.indexOf('@'));
|
||||
|
||||
let aliasDomain;
|
||||
let findAddressData = done => {
|
||||
this.users.collection('addresses').findOne(
|
||||
{
|
||||
addrview: address
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
user: true
|
||||
},
|
||||
maxTimeMS: consts.DB_MAX_TIME_USERS
|
||||
},
|
||||
(err, addressData) => {
|
||||
if (err) {
|
||||
err.code = 'InternalDatabaseError';
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (addressData) {
|
||||
return done(null, addressData);
|
||||
}
|
||||
|
||||
// check if the address uses an alias domain
|
||||
this.users.collection('domainaliases').findOne(
|
||||
{ alias: domain },
|
||||
{
|
||||
maxTimeMS: consts.DB_MAX_TIME_USERS
|
||||
},
|
||||
(err, aliasData) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (!aliasData) {
|
||||
// not an alias domain, nothing to check for
|
||||
return done();
|
||||
}
|
||||
|
||||
aliasDomain = aliasData.domain;
|
||||
this.users.collection('addresses').findOne(
|
||||
{
|
||||
addrview: username + '@' + aliasDomain
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
user: true
|
||||
},
|
||||
maxTimeMS: consts.DB_MAX_TIME_USERS
|
||||
},
|
||||
(err, addressData) => {
|
||||
if (err) {
|
||||
err.code = 'InternalDatabaseError';
|
||||
return done(err);
|
||||
}
|
||||
return done(null, addressData);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
this.resolveAddress(
|
||||
username,
|
||||
{
|
||||
wildcard: false,
|
||||
projection: {
|
||||
user: true
|
||||
}
|
||||
},
|
||||
(err, addressData) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
findAddressData((err, addressData) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (addressData && !addressData.user) {
|
||||
// found a non-user address
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
if (addressData && !addressData.user) {
|
||||
// found a non-user address
|
||||
return callback(null, false);
|
||||
}
|
||||
if (!addressData) {
|
||||
// fall back to username formatted as an address
|
||||
return callback(null, {
|
||||
unameview: tools.normalizeAddress(username, false, {
|
||||
removeLabel: true,
|
||||
removeDots: true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (!addressData) {
|
||||
// fall back to username formatted as an address
|
||||
return callback(null, {
|
||||
unameview: address
|
||||
callback(null, {
|
||||
_id: addressData.user
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
_id: addressData.user
|
||||
});
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
18
package.json
18
package.json
|
@ -15,11 +15,11 @@
|
|||
"author": "Andris Reinman",
|
||||
"license": "EUPL-1.1+",
|
||||
"devDependencies": {
|
||||
"ajv": "6.8.1",
|
||||
"ajv": "6.9.2",
|
||||
"apidoc": "0.17.7",
|
||||
"browserbox": "0.9.1",
|
||||
"chai": "4.2.0",
|
||||
"eslint": "5.13.0",
|
||||
"eslint": "5.14.1",
|
||||
"eslint-config-nodemailer": "1.2.0",
|
||||
"eslint-config-prettier": "4.0.0",
|
||||
"grunt": "1.0.3",
|
||||
|
@ -30,7 +30,7 @@
|
|||
"grunt-wait": "0.3.0",
|
||||
"icedfrisby": "1.5.0",
|
||||
"mailparser": "2.4.3",
|
||||
"mocha": "5.2.0",
|
||||
"mocha": "^5.2.0",
|
||||
"request": "2.88.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -54,18 +54,18 @@
|
|||
"libmime": "4.0.1",
|
||||
"libqp": "1.1.0",
|
||||
"mailsplit": "4.2.4",
|
||||
"mobileconfig": "2.1.0",
|
||||
"mobileconfig": "2.2.0",
|
||||
"mongo-cursor-pagination": "7.1.0",
|
||||
"mongodb": "3.1.13",
|
||||
"mongodb-extended-json": "1.10.1",
|
||||
"node-forge": "0.8.0",
|
||||
"node-forge": "0.8.1",
|
||||
"nodemailer": "5.1.1",
|
||||
"npmlog": "4.1.2",
|
||||
"openpgp": "4.4.6",
|
||||
"pem": "1.14.1",
|
||||
"openpgp": "4.4.7",
|
||||
"pem": "1.14.2",
|
||||
"pwnedpasswords": "1.0.4",
|
||||
"qrcode": "1.3.3",
|
||||
"restify": "7.7.0",
|
||||
"restify": "8.0.0",
|
||||
"restify-logger": "2.0.1",
|
||||
"seq-index": "1.1.0",
|
||||
"smtp-server": "3.5.0",
|
||||
|
@ -74,7 +74,7 @@
|
|||
"utf7": "1.0.2",
|
||||
"uuid": "3.3.2",
|
||||
"wild-config": "1.4.0",
|
||||
"yargs": "12.0.5"
|
||||
"yargs": "13.2.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -6,11 +6,11 @@ NODEREPO="node_10.x"
|
|||
MONGODB="3.6"
|
||||
CODENAME=`lsb_release -c -s`
|
||||
|
||||
WILDDUCK_COMMIT="dd5ea8b53e044fe535b4a72bbb6f46ae8acd51a4"
|
||||
ZONEMTA_COMMIT="a7ed55745eac8c96c12e68f58b5f721bff094c5b" # zone-mta-template
|
||||
WEBMAIL_COMMIT="6750c535a817af18b9c3046cd4e2ea143c661950"
|
||||
WILDDUCK_ZONEMTA_COMMIT="cf2cd036061367b8fb096d34bc2da02b297098f0"
|
||||
WILDDUCK_HARAKA_COMMIT="6e3bc18f253caf7b56a10a9ed033a0860b99f638"
|
||||
WILDDUCK_COMMIT="2048e1e36575f2f38589be2c0a74968d41570e59"
|
||||
ZONEMTA_COMMIT="2f43ae790600dd10c77eccb4149d95028401572c" # zone-mta-template
|
||||
WEBMAIL_COMMIT="a8be1f60be76faf1bb5e49599b97b7f67b240710"
|
||||
WILDDUCK_ZONEMTA_COMMIT="c5667b34a2bbb71811967135d1f7ac459d2063bb"
|
||||
WILDDUCK_HARAKA_COMMIT="f3b5df4ed53763fb08be4fc6db99cbf1ece2ac93"
|
||||
HARAKA_VERSION="2.8.23"
|
||||
|
||||
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
|
||||
|
|
|
@ -11,3 +11,9 @@ apt-get -q -y install pwgen git ufw build-essential libssl-dev dnsutils python s
|
|||
# rspamd
|
||||
apt-get -q -y --no-install-recommends install rspamd
|
||||
apt-get clean
|
||||
|
||||
# DMARC policy=reject rules
|
||||
echo 'actions = {
|
||||
quarantine = "add_header";
|
||||
reject = "reject";
|
||||
}' > /etc/rspamd/override.d/dmarc.conf
|
|
@ -53,6 +53,7 @@ echo "$HOSTNAME" > config/me
|
|||
echo "WildDuck MX" > config/smtpgreeting
|
||||
|
||||
echo "spf
|
||||
dkim_verify
|
||||
|
||||
## ClamAV is disabled by default. Make sure freshclam has updated all
|
||||
## virus definitions and clamav-daemon has successfully started before
|
||||
|
@ -61,7 +62,6 @@ echo "spf
|
|||
|
||||
rspamd
|
||||
tls
|
||||
#dkim_verify
|
||||
|
||||
# WildDuck plugin handles recipient checking and queueing
|
||||
wildduck" > config/plugins
|
||||
|
|
|
@ -38,7 +38,32 @@ echo "server {
|
|||
ssl_certificate /etc/wildduck/certs/fullchain.pem;
|
||||
ssl_certificate_key /etc/wildduck/certs/privkey.pem;
|
||||
|
||||
# special config for EventSource to disable gzip
|
||||
location /api/events {
|
||||
proxy_http_version 1.1;
|
||||
gzip off;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header HOST \$http_host;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
# special config for uploads
|
||||
location /webmail/send {
|
||||
client_max_body_size 15M;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header HOST \$http_host;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header HOST \$http_host;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# WildDuck Installer
|
||||
|
||||
Here you can find an example install script to install WildDuck with Haraka and ZoneMTA. The install script is self contained, you can upload to your server and start it as root. It fetches all required files from Github. After installation you should see exactly the same web interface as in https://wildduck.email/
|
||||
Here you can find an example install script to install WildDuck with Haraka and ZoneMTA. The install script is self contained, you can upload to your server and start it as root. It fetches all required files from Github. After installation you should see exactly the same web interface as in https://webmail.wildduck.email/
|
||||
|
||||
The install script is tested on Ubuntu 16.04 and the server must be blank. Blank meaning that there should be no existing software installed (eg. Apache, MySQL or Postfix). If the server already has something installed, then remove the extra applications before running this script. This also means that you should not run the install script in a VPS that you already use for other stuff.
|
||||
|
||||
|
|
Loading…
Reference in a new issue