This commit is contained in:
Andris Reinman 2018-01-30 16:14:15 +02:00
parent cc3cc4922a
commit 44d9fffda7
11 changed files with 147 additions and 11 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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": "2018-01-26T11:25:03.019Z", "url": "http://apidocjs.com", "version": "0.17.6" } });
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": "2018-01-30T14:14:01.100Z", "url": "http://apidocjs.com", "version": "0.17.6" } });

View file

@ -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": "2018-01-26T11:25:03.019Z", "url": "http://apidocjs.com", "version": "0.17.6" } }
{ "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": "2018-01-30T14:14:01.100Z", "url": "http://apidocjs.com", "version": "0.17.6" } }

View file

@ -36,8 +36,9 @@ function send() {
},
headers: {
// set to Yes to send this message to Junk folder
'x-rspamd-spam': 'No'
'X-Rspamd-Bar': '/',
'X-Rspamd-Report': 'R_PARTS_DIFFER(0.5) MIME_GOOD(-0.1) R_DKIM_ALLOW(-0.2) R_SPF_ALLOW(-0.2)',
'X-Rspamd-Score': '22.6'
},
from: 'Kärbes 🐧 <andris@kreata.ee>',

View file

@ -301,6 +301,7 @@ module.exports = (db, server, userHandler) => {
* @apiParam {String} [pubKey] Public PGP key for the User that is used for encryption. Use empty string to remove the key
* @apiParam {String} [language] Language code for the User
* @apiParam {String[]} [targets] An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25") or an URL where mail contents are POSTed to
* @apiParam {Number} [spamLevel=50] Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam
* @apiParam {Number} [quota] Allowed quota of the user in bytes
* @apiParam {Number} [recipients] How many messages per 24 hour can be sent
* @apiParam {Number} [forwards] How many messages per 24 hour can be forwarded
@ -381,6 +382,11 @@ module.exports = (db, server, userHandler) => {
})
),
spamLevel: Joi.number()
.min(0)
.max(100)
.default(50),
quota: Joi.number()
.min(0)
.default(0),
@ -652,6 +658,7 @@ module.exports = (db, server, userHandler) => {
* @apiSuccess {String} keyInfo.address E-mail address listed in public key
* @apiSuccess {String} keyInfo.fingerprint Fingerprint of the public key
* @apiSuccess {String[]} targets List of forwarding targets
* @apiSuccess {Number} spamLevel Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam
* @apiSuccess {Object} limits Account limits and usage
* @apiSuccess {Object} limits.quota Quota usage limits
* @apiSuccess {Number} limits.quota.allowed Allowed quota of the user in bytes
@ -805,6 +812,7 @@ module.exports = (db, server, userHandler) => {
encryptMessages: userData.encryptMessages,
encryptForwarded: userData.encryptForwarded,
pubKey: userData.pubKey,
spamLevel: userData.spamLevel,
keyInfo: getKeyInfo(userData.pubKey),
targets: [].concat(userData.targets || []),
@ -859,6 +867,7 @@ module.exports = (db, server, userHandler) => {
* @apiParam {String} [pubKey] Public PGP key for the User that is used for encryption. Use empty string to remove the key
* @apiParam {String} [language] Language code for the User
* @apiParam {String[]} [targets] An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25") or an URL where mail contents are POSTed to
* @apiParam {Number} [spamLevel] Relative scale for detecting spam. 0 means that everything is spam, 100 means that nothing is spam
* @apiParam {Number} [quota] Allowed quota of the user in bytes
* @apiParam {Number} [recipients] How many messages per 24 hour can be sent
* @apiParam {Number} [forwards] How many messages per 24 hour can be forwarded
@ -928,6 +937,10 @@ module.exports = (db, server, userHandler) => {
})
),
spamLevel: Joi.number()
.min(0)
.max(100),
pubKey: Joi.string()
.empty('')
.trim()

77
lib/draft-handler.js Normal file
View file

@ -0,0 +1,77 @@
'use strict';
// TODO: handle drafts
/*
{
_id: "aaaaa",
user: "bbbbb",
reference: 'message_id',
action: 'reply',
references: ['from-ref-message'],
inReplyTo: ['from-ref-message'],
messageId: '<draft.id@local>',
date: date_obj,
from: identity_ref,
to: [{name, address}],
cc: [{name, address}],
bcc: [{name, address}],
subject: 'test',
html: '<html>',
attachments: [
{
filename: 'aaa.jpg',
contentType: 'image/jpeg',
content: binary,
cid: 'only.for@embedded.images'
}
]
}
*/
class DraftHandler {
constructor(options) {
this.database = options.database;
this.redis = options.redis;
}
// should create a new Draft object and return ID
create(user, options, callback) {
options = options || {};
callback(new Error('Future feature'));
}
// should retrieve draft info
get(user, draft, callback) {
callback(new Error('Future feature'));
}
// should add new attachment to draft and return attachment ID
addAttachment(user, draft, attachmentData, callback) {
callback(new Error('Future feature'));
}
// should delete an attachment from a draft
deleteAttachment(user, draft, attachment, callback) {
callback(new Error('Future feature'));
}
// should submit message to queue and delete draft
send(user, draft, envelope, callback) {
callback(new Error('Future feature'));
}
// should cancel the draft and delete contents
discard(user, draft, callback) {
callback(new Error('Future feature'));
}
}
module.exports = DraftHandler;

View file

@ -20,13 +20,13 @@ const defaultSpamHeaderKeys = [
value: '^yes',
target: '\\Junk'
},
/*
{
key: 'X-Rspamd-Bar',
value: '^\\+{6}',
target: '\\Junk'
},
*/
{
key: 'X-Haraka-Virus',
value: '.',
@ -34,6 +34,9 @@ const defaultSpamHeaderKeys = [
}
];
const spamScoreHeader = 'X-Rspamd-Score';
const spamScoreValue = 15; // everything over this value is spam, under ham
class FilterHandler {
constructor(options) {
this.db = options.db;
@ -84,7 +87,8 @@ class FilterHandler {
autoreply: true,
encryptMessages: true,
encryptForwarded: true,
pubKey: true
pubKey: true,
spamLevel: true
};
if (collection === 'users') {
@ -254,6 +258,8 @@ class FilterHandler {
let matchingFilters = [];
let filterActions = new Map();
let spamScore = parseFloat([].concat(prepared.mimeTree.parsedHeader[spamScoreHeader.toLowerCase()] || []).shift(), 10) || 0;
filters
// apply all filters to the message
.map(filter => checkFilter(filter, prepared, maildata))
@ -279,6 +285,20 @@ class FilterHandler {
});
});
if (typeof userData.spamLevel === 'number' && userData.spamLevel >= 0 && !filterActions.has('spam')) {
let isSpam;
if (userData.spamLevel === 0) {
isSpam = true;
} else if (userData.spamLevel === 100) {
isSpam = false;
} else {
isSpam = userData.spamLevel / 100 * spamScoreValue * 2 <= spamScore;
}
if (isSpam) {
filterActions.set('spam', true);
}
}
let encryptMessage = (condition, next) => {
if (!condition || isEncrypted) {
return next();

View file

@ -878,6 +878,15 @@ class UserHandler {
let hash = data.password ? bcrypt.hashSync(data.password, consts.BCRYPT_ROUNDS) : '';
let id = new ObjectID();
// spamLevel is from 0 (everything is spam) to 100 (accept everything)
let spamLevel = 'spamLevel' in data && !isNaN(data.spamLevel) ? Number(data.spamLevel) : 50;
if (spamLevel < 0) {
spamLevel = 0;
}
if (spamLevel > 100) {
spamLevel = 100;
}
userData = {
_id: id,
@ -912,6 +921,8 @@ class UserHandler {
encryptMessages: !!data.encryptMessages,
encryptForwarded: !!data.encryptForwarded,
spamLevel,
// default retention for user mailboxes
retention: data.retention || 0,
@ -2163,6 +2174,19 @@ class UserHandler {
return;
}
if (key === 'spamLevel') {
// spamLevel is from 0 (everything is spam) to 100 (accept everything)
let spamLevel = !isNaN(data.spamLevel) ? Number(data.spamLevel) : 50;
if (spamLevel < 0) {
spamLevel = 0;
}
if (spamLevel > 100) {
spamLevel = 100;
}
$set.spamLevel = data.spamLevel;
return;
}
$set[key] = data[key];
updates = true;
});

View file

@ -80,7 +80,8 @@ const serverOptions = {
autoreply: true,
encryptMessages: true,
encryptForwarded: true,
pubKey: true
pubKey: true,
spamLevel: true
},
(err, userData) => {
if (err) {

View file

@ -1,6 +1,6 @@
{
"name": "wildduck",
"version": "1.0.112",
"version": "1.0.113",
"description": "IMAP server built with Node.js and MongoDB",
"main": "server.js",
"scripts": {