diff --git a/config/api.toml b/config/api.toml index 480aefbc..bb395671 100644 --- a/config/api.toml +++ b/config/api.toml @@ -8,7 +8,7 @@ secure=false # If set requires all API calls to have accessToken query argument with that value # http://localhost:8080/users?accessToken=somesecretvalue -#accessToken="somesecretvalue" +accessToken="somesecretvalue" [accessControl] # If true then require a valid access token to perform API calls diff --git a/lib/api/messages.js b/lib/api/messages.js index e815c6fa..b14a4f00 100644 --- a/lib/api/messages.js +++ b/lib/api/messages.js @@ -278,6 +278,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => { * @apiSuccess {String} results.bcc.address Address of the recipient * @apiSuccess {String} results.subject Message subject * @apiSuccess {String} results.date Datestring + * @apiSuccess {Number} results.size Message size in bytes * @apiSuccess {String} results.intro First 128 bytes of the message * @apiSuccess {Boolean} results.attachments Does the message have attachments * @apiSuccess {Boolean} results.seen Is this message alread seen or not @@ -317,6 +318,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => { * "date": "2003-10-24T06:28:34.000Z", * "intro": "Welcome to Ryan Finnie's MIME torture test. This message was designed to introduce a couple of the newer features of MIME-aware…", * "attachments": true, + * "size": 1234, * "seen": true, * "deleted": false, * "flagged": true, @@ -460,6 +462,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => { 'mimeTree.parsedHeader.content-type': true, 'mimeTree.parsedHeader.references': true, ha: true, + size: true, intro: true, unseen: true, undeleted: true, @@ -985,6 +988,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => { 'mimeTree.parsedHeader.references': true, ha: true, intro: true, + size: true, unseen: true, undeleted: true, flagged: true, @@ -2465,7 +2469,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => { let envelopeFrom = envelope.from; envelope.from = data.from.address = await validateFromAddress(userData, envelopeFrom); - if (!envelope.to.length && referencedMessage && ['reply', 'replyAll'].includes(result.value.reference.action)) { + if (!result.value.to && !envelope.to.length && referencedMessage && ['reply', 'replyAll'].includes(result.value.reference.action)) { envelope.to = envelope.to.concat(parseAddresses(referencedMessage.replyTo || [])).concat(parseAddresses(referencedMessage.replyCc || [])); data.to = [].concat(referencedMessage.replyTo || []); data.cc = [].concat(referencedMessage.replyCc || []); @@ -3246,6 +3250,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler) => { 'mimeTree.parsedHeader.references': true, ha: true, intro: true, + size: true, unseen: true, undeleted: true, flagged: true, @@ -3935,6 +3940,7 @@ function formatMessageListing(messageData) { date: messageData.hdate.toISOString(), intro: messageData.intro, attachments: !!messageData.ha, + size: messageData.size, seen: !messageData.unseen, deleted: !messageData.undeleted, flagged: messageData.flagged, diff --git a/lib/filter-handler.js b/lib/filter-handler.js index a4951b06..7c3bfbf3 100644 --- a/lib/filter-handler.js +++ b/lib/filter-handler.js @@ -483,9 +483,11 @@ class FilterHandler { filterResults.push({ delete: true }); return { - userData, - response: 'Message dropped by policy as ' + prepared.id.toString(), - error: err + response: { + userData, + response: 'Message dropped by policy as ' + prepared.id.toString(), + error: err + } }; } @@ -700,13 +702,7 @@ async function checkFilter(filterData, prepared, maildata) { } } - if ( - query.text && - maildata.text - .toLowerCase() - .replace(/\s+/g, ' ') - .indexOf(query.text.toLowerCase()) < 0 - ) { + if (query.text && maildata.text.toLowerCase().replace(/\s+/g, ' ').indexOf(query.text.toLowerCase()) < 0) { // message plaintext does not match the text field value return false; } @@ -733,10 +729,7 @@ function parseReceived(str) { } }); - let date = str - .split(';') - .pop() - .trim(); + let date = str.split(';').pop().trim(); if (date) { date = new Date(date); if (date.getTime()) { diff --git a/lib/message-handler.js b/lib/message-handler.js index 3ce3e510..2fb8cb4f 100644 --- a/lib/message-handler.js +++ b/lib/message-handler.js @@ -1,7 +1,7 @@ 'use strict'; const crypto = require('crypto'); -const uuidV1 = require('uuid/v1'); +const { v1: uuidV1 } = require('uuid'); const ObjectID = require('mongodb').ObjectID; const Indexer = require('../imap-core/lib/indexer/indexer'); const ImapNotifier = require('./imap-notifier'); @@ -470,10 +470,7 @@ class MessageHandler { let raw = options.rawchunks || options.raw; let processAudits = async () => { - let audits = await this.database - .collection('audits') - .find({ user: mailboxData.user }) - .toArray(); + let audits = await this.database.collection('audits').find({ user: mailboxData.user }).toArray(); let now = new Date(); for (let auditData of audits) { @@ -505,9 +502,7 @@ class MessageHandler { }; // do not use more suitable .finally() as it is not supported in Node v8 - return processAudits() - .then(next) - .catch(next); + return processAudits().then(next).catch(next); } ); }); @@ -600,7 +595,7 @@ class MessageHandler { _user: messageData.user, _mailbox: messageData.mailbox, _uid: messageData.uid, - _message_id: messageData._id, + _stored_id: messageData._id, _expires: messageData.rdate, _sess: options.session && options.session.id, _size: messageData.size @@ -1142,10 +1137,7 @@ class MessageHandler { .map(line => { line = Buffer.from(line, 'binary').toString(); - let key = line - .substr(0, line.indexOf(':')) - .trim() - .toLowerCase(); + let key = line.substr(0, line.indexOf(':')).trim().toLowerCase(); if (!INDEXED_HEADERS.includes(key)) { // do not index this header @@ -1180,17 +1172,13 @@ class MessageHandler { // trim long values as mongodb indexed fields can not be too long if (Buffer.byteLength(key, 'utf-8') >= 255) { - key = Buffer.from(key) - .slice(0, 255) - .toString(); + key = Buffer.from(key).slice(0, 255).toString(); key = key.substr(0, key.length - 4); } if (Buffer.byteLength(value, 'utf-8') >= 880) { // value exceeds MongoDB max indexed value length - value = Buffer.from(value) - .slice(0, 880) - .toString(); + value = Buffer.from(value).slice(0, 880).toString(); // remove last 4 chars to be sure we do not have any incomplete unicode sequences value = value.substr(0, value.length - 4); } @@ -1270,13 +1258,7 @@ class MessageHandler { .split(/\s+/) .map(id => id.replace(/[<>]/g, '').trim()) .filter(id => id) - .map(id => - crypto - .createHash('sha1') - .update(id) - .digest('base64') - .replace(/[=]+$/g, '') - ) + .map(id => crypto.createHash('sha1').update(id).digest('base64').replace(/[=]+$/g, '')) ); subject = this.normalizeSubject(subject, { @@ -1617,13 +1599,7 @@ class MessageHandler { if (/^content-type:/i.test(line)) { let parts = line.split(':'); let value = parts.slice(1).join(':'); - if ( - value - .split(';') - .shift() - .trim() - .toLowerCase() === 'multipart/encrypted' - ) { + if (value.split(';').shift().trim().toLowerCase() === 'multipart/encrypted') { // message is already encrypted, do nothing return callback(null, false); } diff --git a/lib/pop3/connection.js b/lib/pop3/connection.js index 0aa8982a..f879002c 100644 --- a/lib/pop3/connection.js +++ b/lib/pop3/connection.js @@ -470,7 +470,22 @@ class POP3Connection extends EventEmitter { // https://tools.ietf.org/html/rfc1939#section-6 command_QUIT() { - let finish = () => { + let deleted = this.session.listing.messages.filter(message => message.popped); + + let finish = err => { + if (!err) { + deleted.forEach(message => { + this._server.loggelf({ + short_message: '[POP3DELETE]', + _mail_action: 'pop3_delete', + _message_id: message.id, + _username: this.session.user && this.session.user.username, + _sess: this.id, + _ip: this.remoteAddress + }); + }); + } + this.session = false; this.send('+OK Bye'); this.close(); @@ -481,7 +496,6 @@ class POP3Connection extends EventEmitter { } this.session.state = 'UPDATE'; - let deleted = this.session.listing.messages.filter(message => message.popped); let seen = this.session.listing.messages.filter(message => !message.seen && message.fetched && !message.popped); if (!deleted.length && !seen.length) { @@ -813,9 +827,7 @@ class POP3Connection extends EventEmitter { return next(); } - let credentials = Buffer.from(plain, 'base64') - .toString() - .split('\x00'); + let credentials = Buffer.from(plain, 'base64').toString().split('\x00'); if (credentials.length !== 3) { this.send('-ERR malformed command'); return next(); diff --git a/package.json b/package.json index c0c4bb7f..bf38a54d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wildduck", - "version": "1.24.0", + "version": "1.25.0", "description": "IMAP/POP3 server built with Node.js and MongoDB", "main": "server.js", "scripts": { @@ -16,7 +16,7 @@ "license": "EUPL-1.1+", "devDependencies": { "ajv": "6.12.2", - "apidoc": "0.20.1", + "apidoc": "0.22.1", "browserbox": "0.9.1", "chai": "4.2.0", "eslint": "6.8.0", @@ -29,7 +29,7 @@ "grunt-shell-spawn": "0.4.0", "grunt-wait": "0.3.0", "mailparser": "2.7.7", - "mocha": "7.1.1", + "mocha": "7.1.2", "request": "2.88.2", "supertest": "4.0.2" }, @@ -55,8 +55,8 @@ "libqp": "1.1.0", "mailsplit": "4.6.4", "mobileconfig": "2.3.1", - "mongo-cursor-pagination": "7.2.0", - "mongodb": "3.5.6", + "mongo-cursor-pagination": "7.3.0", + "mongodb": "3.5.7", "mongodb-extended-json": "1.11.0", "node-forge": "0.9.1", "nodemailer": "6.4.6", @@ -72,8 +72,8 @@ "speakeasy": "2.0.0", "u2f": "0.1.3", "utf7": "1.0.2", - "uuid": "7.0.3", - "wild-config": "1.5.0", + "uuid": "8.0.0", + "wild-config": "1.5.1", "yargs": "15.3.1" }, "repository": { diff --git a/pop3.js b/pop3.js index 2cccac03..a723d3da 100644 --- a/pop3.js +++ b/pop3.js @@ -440,6 +440,8 @@ module.exports = done => { loggelf: message => loggelf(message) }); + server.loggelf = loggelf; + server.on('error', err => { if (!started) { started = true;