Skip to content
This repository was archived by the owner on Dec 12, 2024. It is now read-only.

Commit 98c3fc8

Browse files
committed
feat: integrated @spamscanner for phishing, viruses, executables, and arbitary filtering
1 parent 10733cf commit 98c3fc8

File tree

9 files changed

+1609
-112
lines changed

9 files changed

+1609
-112
lines changed

app.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ if (!module.parent) {
1616

1717
(async () => {
1818
try {
19+
await app.spamscanner.load();
1920
await app.listen(port);
20-
// await app.spamscanner.load()
2121
if (process.send) process.send('ready');
2222
logger.info(
2323
`ForwardEmail server listening on ${

index.js

+28-28
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ const dns = require('dns');
33
const fs = require('fs');
44
const util = require('util');
55

6-
// const SpamScanner = require('spamscanner');
7-
86
const DKIM = require('nodemailer/lib/dkim');
97
const Limiter = require('ratelimiter');
108
const MimeNode = require('nodemailer/lib/mime-node');
119
const RE2 = require('re2');
1210
const Redis = require('@ladjs/redis');
11+
const SpamScanner = require('spamscanner');
1312
const _ = require('lodash');
1413
const addressParser = require('nodemailer/lib/addressparser');
1514
const arrayJoinConjunction = require('array-join-conjunction');
16-
const zoneMTABounces = require('zone-mta/lib/bounces');
1715
const bytes = require('bytes');
1816
const dnsbl = require('dnsbl');
1917
const getFQDN = require('get-fqdn');
@@ -31,6 +29,7 @@ const sharedConfig = require('@ladjs/shared-config');
3129
const splitLines = require('split-lines');
3230
const superagent = require('superagent');
3331
const validator = require('validator');
32+
const zoneMTABounces = require('zone-mta/lib/bounces');
3433
const { Iconv } = require('iconv');
3534
const { SMTPServer } = require('smtp-server');
3635
const { SRS } = require('sender-rewriting-scheme');
@@ -257,7 +256,7 @@ class ForwardEmail {
257256
},
258257
sendingZone: 'bounces',
259258
userAgent: `${pkg.name}/${pkg.version}`,
260-
// spamScanner: {},
259+
spamScanner: {},
261260
ttlMs: ms('7d'),
262261
maxRetry: 5,
263262
messageIdDomain: env.MESSAGE_ID_DOMAIN,
@@ -341,7 +340,7 @@ class ForwardEmail {
341340
});
342341

343342
// expose spamscanner
344-
// this.scanner = new SpamScanner(this.config.spamScanner);
343+
this.scanner = new SpamScanner(this.config.spamScanner);
345344

346345
this.listen = this.listen.bind(this);
347346
this.close = this.close.bind(this);
@@ -958,7 +957,7 @@ class ForwardEmail {
958957
// 2) X ensure all email headers were parsed
959958
// 3) X reverse SRS bounces
960959
// 4) X prevent replies to [email protected] (no bottleneck)
961-
// 5) O check for spam
960+
// 5) X check for spam
962961
// 6) X validate SPF, DKIM, DMARC, and ARC
963962
// 7) X lookup forwarding recipients recursively
964963
// 8) X normalize recipients by host and without "+" symbols
@@ -1065,52 +1064,49 @@ class ForwardEmail {
10651064
//
10661065
// 5) check for spam
10671066
//
1068-
/*
1069-
let results;
1067+
let scan;
10701068
try {
1071-
results = await this.scanner.scan(originalRaw);
1069+
scan = await this.scanner.scan(originalRaw);
1070+
if (scan.is_spam)
1071+
this.config.logger.fatal(`spam detected: ${JSON.stringify(scan.results)}`);
10721072
} catch (err) {
1073-
logger.error(err);
1073+
this.config.logger.fatal(err);
10741074
}
10751075

1076-
if (results) {
1077-
if (results.is_spam)
1078-
logger.error('spam detected', {
1079-
originalRaw: originalRaw.toString(),
1080-
results
1081-
});
1082-
1076+
if (_.isObject(scan) && _.isObject(scan.results)) {
10831077
//
10841078
// NOTE: until we are confident with the accuracy
10851079
// we are not utilizing classification right now
10861080
// however we still want to use other detections
10871081
//
10881082
const messages = [];
10891083

1090-
if (
1091-
_.isObject(results.phishing) &&
1092-
_.isArray(results.phishing.messages)
1093-
)
1094-
for (const message of results.phishing.messages) {
1084+
if (_.isArray(scan.results.phishing))
1085+
for (const message of scan.results.phishing) {
1086+
messages.push(message);
1087+
}
1088+
1089+
if (_.isArray(scan.results.executables)) {
1090+
for (const message of scan.results.executables) {
10951091
messages.push(message);
10961092
}
1093+
}
10971094

1098-
if (_.isArray(results.executables)) {
1099-
for (const message of results.executables) {
1095+
if (_.isArray(scan.results.arbitrary)) {
1096+
for (const message of scan.results.arbitrary) {
11001097
messages.push(message);
11011098
}
11021099
}
11031100

1104-
if (_.isArray(results.arbitrary)) {
1105-
for (const message of results.arbitrary) {
1101+
if (_.isArray(scan.results.viruses)) {
1102+
for (const message of scan.results.viruses) {
11061103
messages.push(message);
11071104
}
11081105
}
11091106

11101107
if (messages.length > 0)
11111108
throw new CustomError(messages.join(' '), 554);
11121109
}
1113-
*/
11141110

11151111
//
11161112
// 6) validate SPF, DKIM, DMARC, and ARC
@@ -1820,7 +1816,7 @@ class ForwardEmail {
18201816
err.message = err.message.split('msg:')[1];
18211817
}
18221818

1823-
err.message += ` If you need help please forward this email to ${this.config.email} or visit ${this.config.website}`;
1819+
err.message += ` If you need help please forward this email to ${this.config.email} or visit ${this.config.website}.`;
18241820
fn(err);
18251821
});
18261822

@@ -1976,6 +1972,7 @@ class ForwardEmail {
19761972
if (verifications.length > 0) {
19771973
if (verifications.length > 1)
19781974
throw new CustomError(
1975+
// TODO: we may want to replace this with "Invalid Recipients"
19791976
`Domain ${domain} has multiple verification TXT records of "${this.config.recordPrefix}-site-verification" and should only have one`
19801977
);
19811978
// if there was a verification record then perform lookup
@@ -2007,6 +2004,7 @@ class ForwardEmail {
20072004
// if the record was blank then throw an error
20082005
if (!isSANB(record))
20092006
throw new CustomError(
2007+
// TODO: we may want to replace this with "Invalid Recipients"
20102008
`${address} domain of ${domain} has a blank "${this.config.recordPrefix}" TXT record or has zero aliases configured`,
20112009
420
20122010
);
@@ -2025,6 +2023,7 @@ class ForwardEmail {
20252023

20262024
if (addresses.length === 0)
20272025
throw new CustomError(
2026+
// TODO: we may want to replace this with "Invalid Recipients"
20282027
`${address} domain of ${domain} has zero forwarded addresses configured in the TXT record with "${this.config.recordPrefix}"`,
20292028
420
20302029
);
@@ -2083,6 +2082,7 @@ class ForwardEmail {
20832082
!validator.isURL(addr[1], this.config.isURLOptions))
20842083
)
20852084
throw new CustomError(
2085+
// TODO: we may want to replace this with "Invalid Recipients"
20862086
`${lowerCaseAddress} domain of ${domain} has an invalid "${this.config.recordPrefix}" TXT record due to an invalid email address of "${element}"`
20872087
);
20882088

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"dnsbl": "^3.2.0",
4242
"get-fqdn": "^0.0.4",
4343
"get-port": "^5.1.1",
44-
"get-stream": "^5.1.0",
44+
"get-stream": "^5.2.0",
4545
"iconv": "^3.0.0",
4646
"ip": "^1.1.5",
4747
"is-string-and-not-blank": "^0.0.2",
@@ -60,6 +60,7 @@
6060
"sender-rewriting-scheme": "^1.0.0",
6161
"signale": "^1.4.0",
6262
"smtp-server": "^3.7.0",
63+
"spamscanner": "^0.0.8",
6364
"split-lines": "^2.0.0",
6465
"superagent": "^6.0.0",
6566
"uuid-by-string": "^3.0.2",

test/fixtures/eicar.com.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

test/fixtures/executable.eml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Return-Path: <[email protected]>
2+
3+
4+
Subject: test Sat, 26 Jan 2019 12:04:58 +0100
5+
Message-Id: <[email protected]>
6+
Date: Sat, 26 Jan 2019 12:04:58 +0100
7+
MIME-Version: 1.0
8+
Content-Type: multipart/mixed; boundary="----=_MIME_BOUNDARY_000_15328"
9+
10+
------=_MIME_BOUNDARY_000_15328
11+
Content-Type: text/plain
12+
13+
This is a test mailing
14+
------=_MIME_BOUNDARY_000_15328
15+
Content-Type: application/octet-stream; name="hello_world.exe"
16+
Content-Description: hello_world.exe
17+
Content-Disposition: attachment; filename="hello_world.exe"
18+
Content-Transfer-Encoding: BASE64
19+
20+
f0VMRgIBAQAAAAAAAAAAAAMAPgABAAAAUBAAAAAAAABAAAAAAAAAAGA5AAAAAAAAAAAAAEAAOAAL
21+
22+
------=_MIME_BOUNDARY_000_15328--

test/fixtures/idn.eml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
Content-Type: text/plain
3+
4+
http://мойсайт.рф

test/fixtures/phishing.eml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Content-type: text/html
2+
3+
lol <a href="http://www.example.net">http://www.myspace.com</a>

0 commit comments

Comments
 (0)