uptimeify-dnsbl 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -7
- package/examples/test-dqs.js +89 -0
- package/examples/test.js +13 -1
- package/package.json +1 -1
- package/src/definitions.js +86 -44
- package/src/index.d.ts +17 -0
- package/src/index.js +86 -5
package/README.md
CHANGED
|
@@ -26,9 +26,9 @@ We intentionally **do not** perform the DNS lookups internally. This allows you
|
|
|
26
26
|
We maintain definitions for the most reliable and widely used lists:
|
|
27
27
|
|
|
28
28
|
- **Spamhaus ZEN** (SBL, CSS, XBL, PBL) - _The gold standard_
|
|
29
|
+
- **Spamhaus DQS (ZEN)** - Same result parsing, but query via Spamhaus DQS (requires a DQS key)
|
|
29
30
|
- **Barracuda** (BRBL)
|
|
30
31
|
- **SpamCop**
|
|
31
|
-
- **Abusix Mail Intelligence**
|
|
32
32
|
- **SORBS** (Aggregate)
|
|
33
33
|
- **UCEPROTECT** (Level 1)
|
|
34
34
|
- **Hostkarma**
|
|
@@ -38,7 +38,6 @@ We maintain definitions for the most reliable and widely used lists:
|
|
|
38
38
|
- **DroneBL**
|
|
39
39
|
- **Spam Eating Monkey** (SEM-BLACK)
|
|
40
40
|
- **URIBL Black**
|
|
41
|
-
- **Madavi DNSBL**
|
|
42
41
|
- **RV-SOFT Technology**
|
|
43
42
|
- **ZapBL**
|
|
44
43
|
- **Suomispam Reputation**
|
|
@@ -52,7 +51,6 @@ We maintain definitions for the most reliable and widely used lists:
|
|
|
52
51
|
- **Abuse.ch Combined**
|
|
53
52
|
- **UCEPROTECT Level 2**
|
|
54
53
|
- **Abuse.ch Drone**
|
|
55
|
-
- **OrveDB AuBads**
|
|
56
54
|
- **0spam RBL**
|
|
57
55
|
- **Singular TTK PTE**
|
|
58
56
|
- **SpamRats Spam**
|
|
@@ -61,7 +59,6 @@ We maintain definitions for the most reliable and widely used lists:
|
|
|
61
59
|
- **Woody's SMTP Blacklist**
|
|
62
60
|
- **WPBL**
|
|
63
61
|
- **UCEPROTECT Level 3**
|
|
64
|
-
- **Duinv AuPads**
|
|
65
62
|
- **Gweep Proxy**
|
|
66
63
|
- **Gweep Relays**
|
|
67
64
|
- **Abuse.ch Spam**
|
|
@@ -77,7 +74,6 @@ We maintain definitions for the most reliable and widely used lists:
|
|
|
77
74
|
- **Mailspike Z**
|
|
78
75
|
- **Anonmails.de**
|
|
79
76
|
- **Pedantic.org**
|
|
80
|
-
- **Swinog**
|
|
81
77
|
- **GBUdb Truncate**
|
|
82
78
|
- **LashBack UBL**
|
|
83
79
|
|
|
@@ -97,8 +93,15 @@ import { resolve4 } from "node:dns/promises";
|
|
|
97
93
|
|
|
98
94
|
async function check(ip) {
|
|
99
95
|
// 1. Get the list of domains to query
|
|
100
|
-
const checks = getLookupDomains(ip
|
|
96
|
+
const checks = getLookupDomains(ip, {
|
|
97
|
+
spamhaus: {
|
|
98
|
+
// Default: "zen" (public). Use "dqs" to query Spamhaus via DQS.
|
|
99
|
+
mode: process.env.SPAMHAUS_DQS_KEY ? "dqs" : "zen",
|
|
100
|
+
dqsKey: process.env.SPAMHAUS_DQS_KEY,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
101
103
|
// Returns array: [{ address: "4.3.2.1.zen.spamhaus.org", listKey: "zen.spamhaus.org" }, ...]
|
|
104
|
+
// Or with DQS: [{ address: "4.3.2.1.<DQS_KEY>.zen.dq.spamhaus.net", listKey: "zen.dq.spamhaus.net" }, ...]
|
|
102
105
|
|
|
103
106
|
// 2. Run your DNS lookups
|
|
104
107
|
// We use typical Promise handling here, but you can use any async pattern
|
|
@@ -133,10 +136,28 @@ check("127.0.0.2");
|
|
|
133
136
|
|
|
134
137
|
## API
|
|
135
138
|
|
|
136
|
-
### `getLookupDomains(ip)`
|
|
139
|
+
### `getLookupDomains(ip, options?)`
|
|
137
140
|
|
|
138
141
|
Returns an array of objects containing the fully qualified domain to query (`address`) and the identifier key (`listKey`).
|
|
139
142
|
|
|
143
|
+
#### Spamhaus DQS
|
|
144
|
+
|
|
145
|
+
You can switch Spamhaus from the public DNSBL hostnames to Spamhaus DQS by passing an options object.
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
const checks = getLookupDomains("1.2.3.4", {
|
|
149
|
+
spamhaus: {
|
|
150
|
+
mode: "dqs",
|
|
151
|
+
dqsKey: process.env.SPAMHAUS_DQS_KEY,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Notes:
|
|
157
|
+
|
|
158
|
+
- When `mode: "dqs"` is set, this library will generate DQS lookup domains for Spamhaus (and will not query the public Spamhaus DNSBL hostnames).
|
|
159
|
+
- Keep the DQS key secret (don’t commit it to git or log it).
|
|
160
|
+
|
|
140
161
|
### `parseLookupResult(listKey, resultCode)`
|
|
141
162
|
|
|
142
163
|
Takes the list identifier and the IP address returned by the DNS query (e.g., `127.0.0.2`) and returns a rich object with the listing name, reason, and delist URL.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uptimeify DNSBL - Spamhaus DQS Example
|
|
3
|
+
*
|
|
4
|
+
* This script demonstrates how to query Spamhaus via DQS using a DQS key.
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT:
|
|
7
|
+
* - Treat the key as a secret in real projects (use env vars / secret manager).
|
|
8
|
+
* - The key below is a placeholder example provided by the user.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { getLookupDomains, parseLookupResult } from "../src/index.js";
|
|
12
|
+
import { resolve4 } from "node:dns/promises";
|
|
13
|
+
|
|
14
|
+
const DQS_KEY = process.env.SPAMHAUS_DQS_KEY;
|
|
15
|
+
|
|
16
|
+
async function checkIP(ip) {
|
|
17
|
+
console.log(`\n🔎 Checking IP (Spamhaus DQS): ${ip}`);
|
|
18
|
+
|
|
19
|
+
const domains = getLookupDomains(ip, {
|
|
20
|
+
spamhaus: {
|
|
21
|
+
mode: "dqs",
|
|
22
|
+
dqsKey: DQS_KEY,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Reduce output noise: only show Spamhaus checks here.
|
|
27
|
+
const spamhausOnly = domains.filter(
|
|
28
|
+
(d) => d.listKey === "zen.dq.spamhaus.net" || d.listKey === "sbl.dq.spamhaus.net",
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
console.log(` Using DQS key: ${DQS_KEY.slice(0, 4)}… (masked)`);
|
|
32
|
+
console.log(` Querying ${spamhausOnly.length} Spamhaus list(s) via DQS...`);
|
|
33
|
+
|
|
34
|
+
const results = await Promise.all(
|
|
35
|
+
spamhausOnly.map(async ({ address, listKey }) => {
|
|
36
|
+
try {
|
|
37
|
+
const [code] = await resolve4(address);
|
|
38
|
+
return parseLookupResult(listKey, code);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error.code === "ENOTFOUND") {
|
|
41
|
+
return {
|
|
42
|
+
name: listKey,
|
|
43
|
+
listed: false,
|
|
44
|
+
reason: "Not Listed",
|
|
45
|
+
code: null,
|
|
46
|
+
delistUrl: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
name: listKey,
|
|
52
|
+
listed: false,
|
|
53
|
+
reason: `DNS Error: ${error.code || error.message}`,
|
|
54
|
+
code: null,
|
|
55
|
+
delistUrl: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const listed = results.filter((r) => r.listed);
|
|
62
|
+
const notListed = results.filter((r) => !r.listed);
|
|
63
|
+
|
|
64
|
+
if (listed.length > 0) {
|
|
65
|
+
console.log(`\n❌ [LISTED] Found in ${listed.length} Spamhaus list(s):`);
|
|
66
|
+
for (const res of listed) {
|
|
67
|
+
console.log(` - ${res.name}`);
|
|
68
|
+
console.log(` Reason: ${res.reason}`);
|
|
69
|
+
console.log(` Code: ${res.code}`);
|
|
70
|
+
if (res.delistUrl) console.log(` Delist: ${res.delistUrl}`);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
console.log("\n✅ [CLEAN] Not listed in Spamhaus (via DQS).");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Show non-listed results too, because DQS can return useful block/policy messages.
|
|
77
|
+
if (notListed.length > 0) {
|
|
78
|
+
console.log("\nℹ️ Other results:");
|
|
79
|
+
for (const res of notListed) {
|
|
80
|
+
console.log(` - ${res.name}: ${res.reason}${res.code ? ` (${res.code})` : ""}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log("--- Spamhaus DQS Verification Utility ---");
|
|
86
|
+
|
|
87
|
+
// Example IPs (replace as needed)
|
|
88
|
+
await checkIP("213.209.159.159");
|
|
89
|
+
await checkIP("2a06:4880:8000::99");
|
package/examples/test.js
CHANGED
|
@@ -21,10 +21,22 @@ import { performance } from "node:perf_hooks";
|
|
|
21
21
|
async function checkIP(ip) {
|
|
22
22
|
console.log(`\n🔎 Checking IP: ${ip}`);
|
|
23
23
|
|
|
24
|
+
const lookupOptions = process.env.SPAMHAUS_DQS_KEY
|
|
25
|
+
? {
|
|
26
|
+
spamhaus: {
|
|
27
|
+
mode: "dqs",
|
|
28
|
+
dqsKey: process.env.SPAMHAUS_DQS_KEY,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
: undefined;
|
|
32
|
+
console.log(
|
|
33
|
+
` Spamhaus mode: ${lookupOptions ? "dqs" : "zen"}${lookupOptions ? " (via SPAMHAUS_DQS_KEY)" : ""}`,
|
|
34
|
+
);
|
|
35
|
+
|
|
24
36
|
try {
|
|
25
37
|
// 1. Get the list of all DNSBL domains to query for this IP
|
|
26
38
|
const startGetDomains = performance.now();
|
|
27
|
-
const domains = getLookupDomains(ip);
|
|
39
|
+
const domains = getLookupDomains(ip, lookupOptions);
|
|
28
40
|
const endGetDomains = performance.now();
|
|
29
41
|
console.log(
|
|
30
42
|
` Preparing to query ${domains.length} blacklists... (took ${(endGetDomains - startGetDomains).toFixed(5)}ms)`,
|
package/package.json
CHANGED
package/src/definitions.js
CHANGED
|
@@ -14,6 +14,29 @@ export const blacklists = {
|
|
|
14
14
|
name: "Spamhaus ZEN",
|
|
15
15
|
delistUrl: "https://check.spamhaus.org/",
|
|
16
16
|
mappings: {
|
|
17
|
+
"127.255.255.254":
|
|
18
|
+
"Query blocked by Spamhaus (rate limit / missing authorization / policy)",
|
|
19
|
+
"127.0.0.2": "Listed in SBL (Spamhaus Block List - Direct spam sources)",
|
|
20
|
+
"127.0.0.3":
|
|
21
|
+
"Listed in CSS (Combating Spam Syndicate - Compromised hosts)",
|
|
22
|
+
"127.0.0.4":
|
|
23
|
+
"Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
|
|
24
|
+
"127.0.0.5":
|
|
25
|
+
"Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
|
|
26
|
+
"127.0.0.6":
|
|
27
|
+
"Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
|
|
28
|
+
"127.0.0.7":
|
|
29
|
+
"Listed in XBL/PBL (Exploits/Policy Block List - Botnets, open proxies, dynamic IPs)",
|
|
30
|
+
"127.0.0.10": "PBL - ISP Policy",
|
|
31
|
+
"127.0.0.11": "PBL - ISP Policy",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"zen.dq.spamhaus.net": {
|
|
35
|
+
name: "Spamhaus DQS (ZEN)",
|
|
36
|
+
delistUrl: "https://check.spamhaus.org/",
|
|
37
|
+
mappings: {
|
|
38
|
+
"127.255.255.254":
|
|
39
|
+
"Query blocked by Spamhaus (rate limit / missing authorization / policy)",
|
|
17
40
|
"127.0.0.2": "Listed in SBL (Spamhaus Block List - Direct spam sources)",
|
|
18
41
|
"127.0.0.3":
|
|
19
42
|
"Listed in CSS (Combating Spam Syndicate - Compromised hosts)",
|
|
@@ -43,15 +66,16 @@ export const blacklists = {
|
|
|
43
66
|
"127.0.0.2": "Listed in SpamCop",
|
|
44
67
|
},
|
|
45
68
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
69
|
+
// Disabled as Abusix RBLs are now paid services
|
|
70
|
+
// "combined.mail.abusix.zone": {
|
|
71
|
+
// name: "Abusix Mail Intelligence",
|
|
72
|
+
// delistUrl: "https://abusix.com/lookup/",
|
|
73
|
+
// mappings: {
|
|
74
|
+
// "127.0.0.2": "Blacklisted (Generic)",
|
|
75
|
+
// "127.0.0.3": "Blacklisted (External)",
|
|
76
|
+
// "127.0.0.4": "Blacklisted (Dynamic/Policy)",
|
|
77
|
+
// },
|
|
78
|
+
//},
|
|
55
79
|
"dnsbl.sorbs.net": {
|
|
56
80
|
name: "SORBS Aggregate",
|
|
57
81
|
delistUrl: "http://www.sorbs.net/lookup.shtml",
|
|
@@ -139,16 +163,19 @@ export const blacklists = {
|
|
|
139
163
|
name: "URIBL Black",
|
|
140
164
|
delistUrl: "https://lookup.uribl.com/",
|
|
141
165
|
mappings: {
|
|
166
|
+
"127.0.0.1": "Query blocked, possibly due to high volume",
|
|
142
167
|
"127.0.0.2": "Listed in URIBL Black",
|
|
168
|
+
"127.0.0.255": "Query blocked",
|
|
143
169
|
},
|
|
144
170
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
171
|
+
// Dead on arrival, not reliable
|
|
172
|
+
// "dnsbl.madavi.de": {
|
|
173
|
+
// name: "Madavi DNSBL",
|
|
174
|
+
// delistUrl: "https://www.madavi.de/en/dnsbl.html",
|
|
175
|
+
// mappings: {
|
|
176
|
+
// "127.0.0.2": "Listed in Madavi DNSBL",
|
|
177
|
+
// },
|
|
178
|
+
// },
|
|
152
179
|
"dnsbl.rv-soft.info": {
|
|
153
180
|
name: "RV-SOFT Technology DNSBL",
|
|
154
181
|
delistUrl: "http://dnsbl.rv-soft.info",
|
|
@@ -229,6 +256,17 @@ export const blacklists = {
|
|
|
229
256
|
name: "Spamhaus SBL",
|
|
230
257
|
delistUrl: "https://check.spamhaus.org/",
|
|
231
258
|
mappings: {
|
|
259
|
+
"127.255.255.254":
|
|
260
|
+
"Query blocked by Spamhaus (rate limit / missing authorization / policy)",
|
|
261
|
+
"127.0.0.2": "Listed in SBL (Spamhaus Block List)",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
"sbl.dq.spamhaus.net": {
|
|
265
|
+
name: "Spamhaus DQS (SBL)",
|
|
266
|
+
delistUrl: "https://check.spamhaus.org/",
|
|
267
|
+
mappings: {
|
|
268
|
+
"127.255.255.254":
|
|
269
|
+
"Query blocked by Spamhaus (rate limit / missing authorization / policy)",
|
|
232
270
|
"127.0.0.2": "Listed in SBL (Spamhaus Block List)",
|
|
233
271
|
},
|
|
234
272
|
},
|
|
@@ -246,13 +284,14 @@ export const blacklists = {
|
|
|
246
284
|
"127.0.0.2": "Listed in Pedantic.org",
|
|
247
285
|
},
|
|
248
286
|
},
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
},
|
|
287
|
+
// Disabled as Swinog DNSBL is not stable/reliable
|
|
288
|
+
//"swinog.spam.dnsbl.ch": {
|
|
289
|
+
// name: "Swinog DNSBL",
|
|
290
|
+
// delistUrl: "https://www.swinog.ch/spam/",
|
|
291
|
+
// mappings: {
|
|
292
|
+
// "127.0.0.2": "Listed in Swinog",
|
|
293
|
+
// },
|
|
294
|
+
//},
|
|
256
295
|
"truncate.gbudb.net": {
|
|
257
296
|
name: "GBUdb Truncate",
|
|
258
297
|
delistUrl: "http://www.gbudb.com/truncate/",
|
|
@@ -295,13 +334,14 @@ export const blacklists = {
|
|
|
295
334
|
"127.0.0.2": "Listed in Abuse.ch Drone",
|
|
296
335
|
},
|
|
297
336
|
},
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
},
|
|
337
|
+
// Dead project
|
|
338
|
+
//"orvedb.aupads.org": {
|
|
339
|
+
// name: "OrveDB AuBads",
|
|
340
|
+
// delistUrl: "https://aupads.org",
|
|
341
|
+
// mappings: {
|
|
342
|
+
// "127.0.0.2": "Listed in OrveDB",
|
|
343
|
+
// },
|
|
344
|
+
//},
|
|
305
345
|
"rbl.0spam.org": {
|
|
306
346
|
name: "0spam RBL",
|
|
307
347
|
delistUrl: "https://0spam.org",
|
|
@@ -344,13 +384,14 @@ export const blacklists = {
|
|
|
344
384
|
"127.0.0.2": "Listed in Woody's",
|
|
345
385
|
},
|
|
346
386
|
},
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
},
|
|
387
|
+
// Often overloaded/unreliable
|
|
388
|
+
// "db.wpbl.info": {
|
|
389
|
+
// name: "WPBL",
|
|
390
|
+
// delistUrl: "http://wpbl.info",
|
|
391
|
+
// mappings: {
|
|
392
|
+
// "127.0.0.2": "Listed in WPBL",
|
|
393
|
+
// },
|
|
394
|
+
// },
|
|
354
395
|
"dnsbl-3.uceprotect.net": {
|
|
355
396
|
name: "UCEPROTECT Level 3",
|
|
356
397
|
delistUrl: "http://www.uceprotect.net/en/rblcheck.php",
|
|
@@ -358,13 +399,14 @@ export const blacklists = {
|
|
|
358
399
|
"127.0.0.2": "Listed in UCEPROTECT Level 3",
|
|
359
400
|
},
|
|
360
401
|
},
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
},
|
|
402
|
+
// Dead project
|
|
403
|
+
// "duinv.aupads.org": {
|
|
404
|
+
// name: "Duinv AuPads",
|
|
405
|
+
// delistUrl: "https://aupads.org",
|
|
406
|
+
// mappings: {
|
|
407
|
+
// "127.0.0.2": "Listed in Duinv",
|
|
408
|
+
// },
|
|
409
|
+
// },
|
|
368
410
|
"proxy.bl.gweep.ca": {
|
|
369
411
|
name: "Gweep Proxy",
|
|
370
412
|
delistUrl: "http://gweep.ca",
|
package/src/index.d.ts
CHANGED
|
@@ -27,6 +27,15 @@ export interface BlacklistDefinition {
|
|
|
27
27
|
mappings: Record<string, string>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface GetLookupDomainsOptions {
|
|
31
|
+
spamhaus?: {
|
|
32
|
+
/** Use Spamhaus public DNSBLs (zen) or Spamhaus DQS (dqs). Default: zen */
|
|
33
|
+
mode?: "zen" | "dqs";
|
|
34
|
+
/** Required when mode is 'dqs' */
|
|
35
|
+
dqsKey?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
/**
|
|
31
40
|
* Definition of known blacklists and their return codes.
|
|
32
41
|
*/
|
|
@@ -40,6 +49,14 @@ export const blacklists: Record<string, BlacklistDefinition>;
|
|
|
40
49
|
*/
|
|
41
50
|
export function getLookupDomains(ip: string): LookupDomain[];
|
|
42
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Generates the list of domains to query, with optional Spamhaus DQS support.
|
|
54
|
+
*/
|
|
55
|
+
export function getLookupDomains(
|
|
56
|
+
ip: string,
|
|
57
|
+
options?: GetLookupDomainsOptions,
|
|
58
|
+
): LookupDomain[];
|
|
59
|
+
|
|
43
60
|
/**
|
|
44
61
|
* Parses the result from a DNS A-record lookup.
|
|
45
62
|
*
|
package/src/index.js
CHANGED
|
@@ -67,9 +67,13 @@ function reverseIP(ip) {
|
|
|
67
67
|
* Generates the list of domains to query.
|
|
68
68
|
*
|
|
69
69
|
* @param {string} ip - The IPv4 or IPv6 address to check.
|
|
70
|
+
* @param {Object} [options]
|
|
71
|
+
* @param {Object} [options.spamhaus]
|
|
72
|
+
* @param {"zen"|"dqs"} [options.spamhaus.mode] - Use Spamhaus public DNSBLs (zen) or Spamhaus DQS (dqs).
|
|
73
|
+
* @param {string} [options.spamhaus.dqsKey] - Required when mode is 'dqs'.
|
|
70
74
|
* @returns {Array<LookupDomain>} List of domains to query.
|
|
71
75
|
*/
|
|
72
|
-
export function getLookupDomains(ip) {
|
|
76
|
+
export function getLookupDomains(ip, options = undefined) {
|
|
73
77
|
if (!ip || typeof ip !== "string") {
|
|
74
78
|
throw new Error("IP address must be a non-empty string");
|
|
75
79
|
}
|
|
@@ -79,10 +83,50 @@ export function getLookupDomains(ip) {
|
|
|
79
83
|
throw new Error(`Invalid IP address: ${ip}`);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
const spamhausMode = options?.spamhaus?.mode || "zen";
|
|
87
|
+
const dqsKeyRaw = options?.spamhaus?.dqsKey;
|
|
88
|
+
const dqsKey = typeof dqsKeyRaw === "string" ? dqsKeyRaw.trim() : "";
|
|
89
|
+
|
|
90
|
+
if (spamhausMode !== "zen" && spamhausMode !== "dqs") {
|
|
91
|
+
throw new Error("options.spamhaus.mode must be either 'zen' or 'dqs'");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (spamhausMode === "dqs") {
|
|
95
|
+
if (!dqsKey) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"options.spamhaus.dqsKey is required when options.spamhaus.mode is 'dqs'",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
// DQS keys are used as a DNS label; reject dots/whitespace and other characters.
|
|
101
|
+
if (!/^[a-z0-9-]+$/i.test(dqsKey)) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
"options.spamhaus.dqsKey must be a valid DNS label (letters/numbers/hyphen)",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const spamhausPublicKeys = new Set(["zen.spamhaus.org", "sbl.spamhaus.org"]);
|
|
109
|
+
const spamhausDqsKeys = new Set(["zen.dq.spamhaus.net", "sbl.dq.spamhaus.net"]);
|
|
110
|
+
|
|
111
|
+
const listKeys = Object.keys(blacklists).filter((listKey) => {
|
|
112
|
+
if (spamhausMode === "zen") {
|
|
113
|
+
return !spamhausDqsKeys.has(listKey);
|
|
114
|
+
}
|
|
115
|
+
if (spamhausMode === "dqs") {
|
|
116
|
+
return !spamhausPublicKeys.has(listKey);
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return listKeys.map((listKey) => {
|
|
122
|
+
const address =
|
|
123
|
+
spamhausMode === "dqs" && spamhausDqsKeys.has(listKey)
|
|
124
|
+
? `${reversed}.${dqsKey}.${listKey}`
|
|
125
|
+
: `${reversed}.${listKey}`;
|
|
126
|
+
|
|
83
127
|
return {
|
|
84
|
-
address
|
|
85
|
-
listKey
|
|
128
|
+
address,
|
|
129
|
+
listKey,
|
|
86
130
|
};
|
|
87
131
|
});
|
|
88
132
|
}
|
|
@@ -119,6 +163,41 @@ export function parseLookupResult(listKey, resultCode) {
|
|
|
119
163
|
}
|
|
120
164
|
|
|
121
165
|
try {
|
|
166
|
+
// Some lists use A-record responses to indicate query errors/blocks.
|
|
167
|
+
// Treat these as NOT listed, otherwise they become false positives.
|
|
168
|
+
const spamhausKeys = new Set([
|
|
169
|
+
"zen.spamhaus.org",
|
|
170
|
+
"sbl.spamhaus.org",
|
|
171
|
+
"zen.dq.spamhaus.net",
|
|
172
|
+
"sbl.dq.spamhaus.net",
|
|
173
|
+
]);
|
|
174
|
+
if (spamhausKeys.has(listKey) && resultCode === "127.255.255.254") {
|
|
175
|
+
return {
|
|
176
|
+
name: listDef.name,
|
|
177
|
+
listed: false,
|
|
178
|
+
reason:
|
|
179
|
+
listDef.mappings?.[resultCode] ||
|
|
180
|
+
"Query blocked by Spamhaus (rate limit / missing authorization / policy)",
|
|
181
|
+
code: resultCode,
|
|
182
|
+
delistUrl: listDef.delistUrl,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
listKey === "black.uribl.com" &&
|
|
188
|
+
(resultCode === "127.0.0.1" || resultCode === "127.0.0.255")
|
|
189
|
+
) {
|
|
190
|
+
return {
|
|
191
|
+
name: listDef.name,
|
|
192
|
+
listed: false,
|
|
193
|
+
reason:
|
|
194
|
+
listDef.mappings?.[resultCode] ||
|
|
195
|
+
"Query blocked by URIBL (possibly due to high volume)",
|
|
196
|
+
code: resultCode,
|
|
197
|
+
delistUrl: listDef.delistUrl,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
122
201
|
if (listKey === "nodes.junkemailfilter.com" && resultCode === "127.0.0.1") {
|
|
123
202
|
return {
|
|
124
203
|
name: listDef.name,
|
|
@@ -129,7 +208,9 @@ export function parseLookupResult(listKey, resultCode) {
|
|
|
129
208
|
};
|
|
130
209
|
}
|
|
131
210
|
|
|
132
|
-
const reason =
|
|
211
|
+
const reason =
|
|
212
|
+
listDef.mappings?.[resultCode] ||
|
|
213
|
+
`Listed (Unmapped return code: ${resultCode})`;
|
|
133
214
|
|
|
134
215
|
return {
|
|
135
216
|
name: listDef.name,
|