mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-03-02 02:45:40 +08:00
v1.4.11
This commit is contained in:
parent
b30f894099
commit
1dc693d0fe
4 changed files with 169 additions and 135 deletions
|
@ -19,25 +19,28 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
get(attachmentId, callback) {
|
||||
this.gridfs.collection(this.bucketName + '.files').findOne({
|
||||
_id: attachmentId
|
||||
}, (err, attachmentData) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (!attachmentData) {
|
||||
return callback(new Error('This attachment does not exist'));
|
||||
}
|
||||
this.gridfs.collection(this.bucketName + '.files').findOne(
|
||||
{
|
||||
_id: attachmentId
|
||||
},
|
||||
(err, attachmentData) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (!attachmentData) {
|
||||
return callback(new Error('This attachment does not exist'));
|
||||
}
|
||||
|
||||
return callback(null, {
|
||||
contentType: attachmentData.contentType,
|
||||
transferEncoding: attachmentData.metadata.transferEncoding,
|
||||
length: attachmentData.length,
|
||||
count: attachmentData.metadata.c,
|
||||
hash: attachmentData._id,
|
||||
metadata: attachmentData.metadata
|
||||
});
|
||||
});
|
||||
return callback(null, {
|
||||
contentType: attachmentData.contentType,
|
||||
transferEncoding: attachmentData.metadata.transferEncoding,
|
||||
length: attachmentData.length,
|
||||
count: attachmentData.metadata.c,
|
||||
hash: attachmentData._id,
|
||||
metadata: attachmentData.metadata
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
create(attachment, hash, callback) {
|
||||
|
@ -111,69 +114,79 @@ class GridstoreStorage {
|
|||
|
||||
let tryCount = 0;
|
||||
let tryStore = () => {
|
||||
this.gridfs.collection(this.bucketName + '.files').findOneAndUpdate({
|
||||
_id: hash
|
||||
}, {
|
||||
$inc: {
|
||||
'metadata.c': 1,
|
||||
'metadata.m': attachment.magic
|
||||
}
|
||||
}, {
|
||||
returnOriginal: false
|
||||
}, (err, result) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (returned) {
|
||||
// might be already finished if retrying after delay
|
||||
return;
|
||||
}
|
||||
|
||||
if (result && result.value) {
|
||||
// already exists
|
||||
return callback(null, result.value._id);
|
||||
}
|
||||
|
||||
// try to insert it
|
||||
let store = this.gridstore.openUploadStreamWithId(id, null, {
|
||||
contentType: attachment.contentType,
|
||||
metadata
|
||||
});
|
||||
|
||||
store.once('error', err => {
|
||||
if (returned) {
|
||||
return;
|
||||
this.gridfs.collection(this.bucketName + '.files').findOneAndUpdate(
|
||||
{
|
||||
_id: hash
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
'metadata.c': 1,
|
||||
'metadata.m': attachment.magic
|
||||
}
|
||||
if (err.code === 11000) {
|
||||
// most probably a race condition, try again
|
||||
if (tryCount++ < 5) {
|
||||
if (/attachments\.chunks /.test(err.message)) {
|
||||
// partial chunks for a probably deleted message detected, try to clean up
|
||||
return setTimeout(() => this.cleanupGarbage(id, tryStore), 100 + 200 * Math.random());
|
||||
}
|
||||
return setTimeout(tryStore, 10);
|
||||
}
|
||||
},
|
||||
{
|
||||
returnOriginal: false
|
||||
},
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
returned = true;
|
||||
callback(err);
|
||||
});
|
||||
|
||||
store.once('finish', () => {
|
||||
if (returned) {
|
||||
return;
|
||||
if (result && result.value) {
|
||||
// already exists
|
||||
return callback(null, result.value._id);
|
||||
}
|
||||
returned = true;
|
||||
return callback(null, id);
|
||||
});
|
||||
|
||||
if (!metadata.decoded) {
|
||||
store.end(attachment.body);
|
||||
} else {
|
||||
let decoder = new libbase64.Decoder();
|
||||
decoder.pipe(store);
|
||||
decoder.once('error', err => {
|
||||
// pass error forward
|
||||
store.emit('error', err);
|
||||
// try to insert it
|
||||
let store = this.gridstore.openUploadStreamWithId(id, null, {
|
||||
contentType: attachment.contentType,
|
||||
metadata
|
||||
});
|
||||
decoder.end(attachment.body);
|
||||
|
||||
store.once('error', err => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
if (err.code === 11000) {
|
||||
// most probably a race condition, try again
|
||||
if (tryCount++ < 5) {
|
||||
if (/attachments\.chunks /.test(err.message)) {
|
||||
// partial chunks for a probably deleted message detected, try to clean up
|
||||
return setTimeout(() => this.cleanupGarbage(id, tryStore), 100 + 200 * Math.random());
|
||||
}
|
||||
return setTimeout(tryStore, 10);
|
||||
}
|
||||
}
|
||||
returned = true;
|
||||
callback(err);
|
||||
});
|
||||
|
||||
store.once('finish', () => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
returned = true;
|
||||
return callback(null, id);
|
||||
});
|
||||
|
||||
if (!metadata.decoded) {
|
||||
store.end(attachment.body);
|
||||
} else {
|
||||
let decoder = new libbase64.Decoder();
|
||||
decoder.pipe(store);
|
||||
decoder.once('error', err => {
|
||||
// pass error forward
|
||||
store.emit('error', err);
|
||||
});
|
||||
decoder.end(attachment.body);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
tryStore();
|
||||
|
@ -202,25 +215,29 @@ class GridstoreStorage {
|
|||
if (isNaN(magic) || typeof magic !== 'number') {
|
||||
errors.notify(new Error('Invalid magic "' + magic + '" for ' + id));
|
||||
}
|
||||
this.gridfs.collection(this.bucketName + '.files').findOneAndUpdate({
|
||||
_id: id
|
||||
}, {
|
||||
$inc: {
|
||||
'metadata.c': -1,
|
||||
'metadata.m': -magic
|
||||
}
|
||||
}, {
|
||||
returnOriginal: false
|
||||
}, (err, result) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
this.gridfs.collection(this.bucketName + '.files').findOneAndUpdate(
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
{
|
||||
$inc: {
|
||||
'metadata.c': -1,
|
||||
'metadata.m': -magic
|
||||
}
|
||||
},
|
||||
{
|
||||
returnOriginal: false
|
||||
},
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!result || !result.value) {
|
||||
return callback(null, false);
|
||||
}
|
||||
if (!result || !result.value) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
// disabled as it is preferred that attachments are not deleted immediately but
|
||||
// after a while by a cleanup process. This gives the opportunity to reuse the
|
||||
// attachment
|
||||
|
@ -235,8 +252,9 @@ class GridstoreStorage {
|
|||
}
|
||||
*/
|
||||
|
||||
return callback(null, true);
|
||||
});
|
||||
return callback(null, true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
update(ids, count, magic, callback) {
|
||||
|
@ -248,8 +266,8 @@ class GridstoreStorage {
|
|||
{
|
||||
_id: Array.isArray(ids)
|
||||
? {
|
||||
$in: ids
|
||||
}
|
||||
$in: ids
|
||||
}
|
||||
: ids
|
||||
},
|
||||
{
|
||||
|
@ -292,28 +310,34 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
// delete file entry first
|
||||
this.gridfs.collection(this.bucketName + '.files').deleteOne({
|
||||
_id: attachment._id,
|
||||
// make sure that we do not delete a message that is already re-used
|
||||
'metadata.c': 0,
|
||||
'metadata.m': 0
|
||||
}, err => {
|
||||
if (err) {
|
||||
return processNext();
|
||||
}
|
||||
|
||||
// delete data chunks
|
||||
this.gridfs.collection(this.bucketName + '.chunks').deleteMany({
|
||||
files_id: attachment._id
|
||||
}, err => {
|
||||
this.gridfs.collection(this.bucketName + '.files').deleteOne(
|
||||
{
|
||||
_id: attachment._id,
|
||||
// make sure that we do not delete a message that is already re-used
|
||||
'metadata.c': 0,
|
||||
'metadata.m': 0
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
// ignore as we don't really care if we have orphans or not
|
||||
return processNext();
|
||||
}
|
||||
|
||||
deleted++;
|
||||
processNext();
|
||||
});
|
||||
});
|
||||
// delete data chunks
|
||||
this.gridfs.collection(this.bucketName + '.chunks').deleteMany(
|
||||
{
|
||||
files_id: attachment._id
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
// ignore as we don't really care if we have orphans or not
|
||||
}
|
||||
|
||||
deleted++;
|
||||
processNext();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -321,27 +345,33 @@ class GridstoreStorage {
|
|||
}
|
||||
|
||||
cleanupGarbage(id, next) {
|
||||
this.gridfs.collection(this.bucketName + '.files').findOne({
|
||||
_id: id
|
||||
}, (err, file) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (file) {
|
||||
// attachment entry exists, do nothing
|
||||
return next(null, false);
|
||||
}
|
||||
|
||||
// orphaned attachment, delete data chunks
|
||||
this.gridfs.collection(this.bucketName + '.chunks').deleteMany({
|
||||
files_id: id
|
||||
}, (err, info) => {
|
||||
this.gridfs.collection(this.bucketName + '.files').findOne(
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
(err, file) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
next(null, info.deletedCount);
|
||||
});
|
||||
});
|
||||
if (file) {
|
||||
// attachment entry exists, do nothing
|
||||
return next(null, false);
|
||||
}
|
||||
|
||||
// orphaned attachment, delete data chunks
|
||||
this.gridfs.collection(this.bucketName + '.chunks').deleteMany(
|
||||
{
|
||||
files_id: id
|
||||
},
|
||||
(err, info) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
next(null, info.deletedCount);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,13 +35,15 @@ const defaultSpamHeaderKeys = [
|
|||
];
|
||||
|
||||
const spamScoreHeader = 'X-Rspamd-Score';
|
||||
const spamScoreValue = 15; // everything over this value is spam, under ham
|
||||
const spamScoreValue = 5.1; // everything over this value is spam, under ham
|
||||
|
||||
class FilterHandler {
|
||||
constructor(options) {
|
||||
this.db = options.db;
|
||||
this.messageHandler = options.messageHandler;
|
||||
|
||||
this.spamScoreValue = options.spamScoreValue || spamScoreValue;
|
||||
|
||||
this.spamChecks = options.spamChecks || tools.prepareSpamChecks(defaultSpamHeaderKeys);
|
||||
this.spamHeaderKeys = options.spamHeaderKeys || this.spamChecks.map(check => check.key);
|
||||
|
||||
|
@ -289,7 +291,7 @@ class FilterHandler {
|
|||
} else if (userData.spamLevel === 100) {
|
||||
isSpam = false;
|
||||
} else {
|
||||
isSpam = (userData.spamLevel / 100) * spamScoreValue * 2 <= spamScore;
|
||||
isSpam = (userData.spamLevel / 100) * this.spamScoreValue * 2 <= spamScore;
|
||||
}
|
||||
if (isSpam) {
|
||||
filterActions.set('spam', true);
|
||||
|
|
4
lmtp.js
4
lmtp.js
|
@ -25,6 +25,7 @@ config.on('reload', () => {
|
|||
if (filterHandler) {
|
||||
filterHandler.spamChecks = spamChecks;
|
||||
filterHandler.spamHeaderKeys = spamHeaderKeys;
|
||||
filterHandler.spamScoreValue = config.lmtp.spamScore;
|
||||
}
|
||||
|
||||
log.info('LMTP', 'Configuration reloaded');
|
||||
|
@ -226,7 +227,8 @@ module.exports = done => {
|
|||
sender: config.sender,
|
||||
messageHandler,
|
||||
spamHeaderKeys,
|
||||
spamChecks
|
||||
spamChecks,
|
||||
spamScoreValue: config.lmtp.spamScore
|
||||
});
|
||||
|
||||
let started = false;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "wildduck",
|
||||
"version": "1.4.10",
|
||||
"version": "1.4.11",
|
||||
"description": "IMAP/POP3 server built with Node.js and MongoDB",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
|
Loading…
Reference in a new issue