@push.rocks/smartmta 5.1.2 → 5.2.0
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/changelog.md +14 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -0
- package/dist_ts/logger.d.ts +17 -0
- package/dist_ts/logger.js +76 -0
- package/dist_ts/mail/core/classes.bouncemanager.d.ts +185 -0
- package/dist_ts/mail/core/classes.bouncemanager.js +569 -0
- package/dist_ts/mail/core/classes.email.d.ts +291 -0
- package/dist_ts/mail/core/classes.email.js +802 -0
- package/dist_ts/mail/core/classes.emailvalidator.d.ts +61 -0
- package/dist_ts/mail/core/classes.emailvalidator.js +184 -0
- package/dist_ts/mail/core/classes.templatemanager.d.ts +95 -0
- package/dist_ts/mail/core/classes.templatemanager.js +240 -0
- package/dist_ts/mail/core/index.d.ts +4 -0
- package/dist_ts/mail/core/index.js +6 -0
- package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +163 -0
- package/dist_ts/mail/delivery/classes.delivery.queue.js +488 -0
- package/dist_ts/mail/delivery/classes.delivery.system.d.ts +160 -0
- package/dist_ts/mail/delivery/classes.delivery.system.js +630 -0
- package/dist_ts/mail/delivery/classes.unified.rate.limiter.d.ts +200 -0
- package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +820 -0
- package/dist_ts/mail/delivery/index.d.ts +4 -0
- package/dist_ts/mail/delivery/index.js +6 -0
- package/dist_ts/mail/delivery/interfaces.d.ts +140 -0
- package/dist_ts/mail/delivery/interfaces.js +17 -0
- package/dist_ts/mail/index.d.ts +7 -0
- package/dist_ts/mail/index.js +12 -0
- package/dist_ts/mail/routing/classes.dkim.manager.d.ts +25 -0
- package/dist_ts/mail/routing/classes.dkim.manager.js +127 -0
- package/dist_ts/mail/routing/classes.dns.manager.d.ts +79 -0
- package/dist_ts/mail/routing/classes.dns.manager.js +415 -0
- package/dist_ts/mail/routing/classes.domain.registry.d.ts +54 -0
- package/dist_ts/mail/routing/classes.domain.registry.js +119 -0
- package/dist_ts/mail/routing/classes.email.action.executor.d.ts +33 -0
- package/dist_ts/mail/routing/classes.email.action.executor.js +137 -0
- package/dist_ts/mail/routing/classes.email.router.d.ts +171 -0
- package/dist_ts/mail/routing/classes.email.router.js +494 -0
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +241 -0
- package/dist_ts/mail/routing/classes.unified.email.server.js +935 -0
- package/dist_ts/mail/routing/index.d.ts +7 -0
- package/dist_ts/mail/routing/index.js +9 -0
- package/dist_ts/mail/routing/interfaces.d.ts +187 -0
- package/dist_ts/mail/routing/interfaces.js +2 -0
- package/dist_ts/mail/security/classes.dkimcreator.d.ts +72 -0
- package/dist_ts/mail/security/classes.dkimcreator.js +360 -0
- package/dist_ts/mail/security/classes.spfverifier.d.ts +62 -0
- package/dist_ts/mail/security/classes.spfverifier.js +87 -0
- package/dist_ts/mail/security/index.d.ts +2 -0
- package/dist_ts/mail/security/index.js +4 -0
- package/dist_ts/paths.d.ts +14 -0
- package/dist_ts/paths.js +39 -0
- package/dist_ts/plugins.d.ts +24 -0
- package/dist_ts/plugins.js +28 -0
- package/dist_ts/security/classes.contentscanner.d.ts +130 -0
- package/dist_ts/security/classes.contentscanner.js +338 -0
- package/dist_ts/security/classes.ipreputationchecker.d.ts +73 -0
- package/dist_ts/security/classes.ipreputationchecker.js +263 -0
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +398 -0
- package/dist_ts/security/classes.rustsecuritybridge.js +484 -0
- package/dist_ts/security/classes.securitylogger.d.ts +140 -0
- package/dist_ts/security/classes.securitylogger.js +235 -0
- package/dist_ts/security/index.d.ts +4 -0
- package/dist_ts/security/index.js +5 -0
- package/package.json +6 -1
- package/readme.md +52 -9
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/index.ts +3 -0
- package/ts/logger.ts +91 -0
- package/ts/mail/core/classes.bouncemanager.ts +731 -0
- package/ts/mail/core/classes.email.ts +942 -0
- package/ts/mail/core/classes.emailvalidator.ts +239 -0
- package/ts/mail/core/classes.templatemanager.ts +320 -0
- package/ts/mail/core/index.ts +5 -0
- package/ts/mail/delivery/classes.delivery.queue.ts +645 -0
- package/ts/mail/delivery/classes.delivery.system.ts +816 -0
- package/ts/mail/delivery/classes.unified.rate.limiter.ts +1053 -0
- package/ts/mail/delivery/index.ts +5 -0
- package/ts/mail/delivery/interfaces.ts +167 -0
- package/ts/mail/index.ts +17 -0
- package/ts/mail/routing/classes.dkim.manager.ts +157 -0
- package/ts/mail/routing/classes.dns.manager.ts +573 -0
- package/ts/mail/routing/classes.domain.registry.ts +139 -0
- package/ts/mail/routing/classes.email.action.executor.ts +175 -0
- package/ts/mail/routing/classes.email.router.ts +575 -0
- package/ts/mail/routing/classes.unified.email.server.ts +1207 -0
- package/ts/mail/routing/index.ts +9 -0
- package/ts/mail/routing/interfaces.ts +202 -0
- package/ts/mail/security/classes.dkimcreator.ts +447 -0
- package/ts/mail/security/classes.spfverifier.ts +126 -0
- package/ts/mail/security/index.ts +3 -0
- package/ts/paths.ts +48 -0
- package/ts/plugins.ts +53 -0
- package/ts/security/classes.contentscanner.ts +400 -0
- package/ts/security/classes.ipreputationchecker.ts +315 -0
- package/ts/security/classes.rustsecuritybridge.ts +943 -0
- package/ts/security/classes.securitylogger.ts +299 -0
- package/ts/security/index.ts +40 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import * as paths from '../paths.js';
|
|
3
|
+
import { logger } from '../logger.js';
|
|
4
|
+
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
|
5
|
+
import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
|
|
6
|
+
import { LRUCache } from 'lru-cache';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reputation check result information
|
|
10
|
+
*/
|
|
11
|
+
export interface IReputationResult {
|
|
12
|
+
score: number; // 0 (worst) to 100 (best)
|
|
13
|
+
isSpam: boolean; // true if the IP is known for spam
|
|
14
|
+
isProxy: boolean; // true if the IP is a known proxy
|
|
15
|
+
isTor: boolean; // true if the IP is a known Tor exit node
|
|
16
|
+
isVPN: boolean; // true if the IP is a known VPN
|
|
17
|
+
country?: string; // Country code (if available)
|
|
18
|
+
asn?: string; // Autonomous System Number (if available)
|
|
19
|
+
org?: string; // Organization name (if available)
|
|
20
|
+
blacklists?: string[]; // Names of blacklists that include this IP
|
|
21
|
+
timestamp: number; // When this result was created/retrieved
|
|
22
|
+
error?: string; // Error message if check failed
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reputation threshold scores
|
|
27
|
+
*/
|
|
28
|
+
export enum ReputationThreshold {
|
|
29
|
+
HIGH_RISK = 20, // Score below this is considered high risk
|
|
30
|
+
MEDIUM_RISK = 50, // Score below this is considered medium risk
|
|
31
|
+
LOW_RISK = 80 // Score below this is considered low risk (but not trusted)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* IP type classifications
|
|
36
|
+
*/
|
|
37
|
+
export enum IPType {
|
|
38
|
+
RESIDENTIAL = 'residential',
|
|
39
|
+
DATACENTER = 'datacenter',
|
|
40
|
+
PROXY = 'proxy',
|
|
41
|
+
TOR = 'tor',
|
|
42
|
+
VPN = 'vpn',
|
|
43
|
+
UNKNOWN = 'unknown'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Options for the IP Reputation Checker
|
|
48
|
+
*/
|
|
49
|
+
export interface IIPReputationOptions {
|
|
50
|
+
maxCacheSize?: number; // Maximum number of IPs to cache (default: 10000)
|
|
51
|
+
cacheTTL?: number; // TTL for cache entries in ms (default: 24 hours)
|
|
52
|
+
dnsblServers?: string[]; // List of DNSBL servers to check
|
|
53
|
+
highRiskThreshold?: number; // Score below this is high risk
|
|
54
|
+
mediumRiskThreshold?: number; // Score below this is medium risk
|
|
55
|
+
lowRiskThreshold?: number; // Score below this is low risk
|
|
56
|
+
enableLocalCache?: boolean; // Whether to persist cache to disk (default: true)
|
|
57
|
+
enableDNSBL?: boolean; // Whether to use DNSBL checks (default: true)
|
|
58
|
+
enableIPInfo?: boolean; // Whether to use IP info service (default: true)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* IP reputation checker — delegates DNSBL lookups to the Rust security bridge.
|
|
63
|
+
* Retains LRU caching and disk persistence in TypeScript.
|
|
64
|
+
*/
|
|
65
|
+
export class IPReputationChecker {
|
|
66
|
+
private static instance: IPReputationChecker;
|
|
67
|
+
private reputationCache: LRUCache<string, IReputationResult>;
|
|
68
|
+
private options: Required<IIPReputationOptions>;
|
|
69
|
+
private storageManager?: any;
|
|
70
|
+
|
|
71
|
+
private static readonly DEFAULT_OPTIONS: Required<IIPReputationOptions> = {
|
|
72
|
+
maxCacheSize: 10000,
|
|
73
|
+
cacheTTL: 24 * 60 * 60 * 1000,
|
|
74
|
+
dnsblServers: [],
|
|
75
|
+
highRiskThreshold: ReputationThreshold.HIGH_RISK,
|
|
76
|
+
mediumRiskThreshold: ReputationThreshold.MEDIUM_RISK,
|
|
77
|
+
lowRiskThreshold: ReputationThreshold.LOW_RISK,
|
|
78
|
+
enableLocalCache: true,
|
|
79
|
+
enableDNSBL: true,
|
|
80
|
+
enableIPInfo: true
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
constructor(options: IIPReputationOptions = {}, storageManager?: any) {
|
|
84
|
+
this.options = {
|
|
85
|
+
...IPReputationChecker.DEFAULT_OPTIONS,
|
|
86
|
+
...options
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.storageManager = storageManager;
|
|
90
|
+
|
|
91
|
+
this.reputationCache = new LRUCache<string, IReputationResult>({
|
|
92
|
+
max: this.options.maxCacheSize,
|
|
93
|
+
ttl: this.options.cacheTTL,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (this.options.enableLocalCache) {
|
|
97
|
+
this.loadCache().catch(error => {
|
|
98
|
+
logger.log('error', `Failed to load IP reputation cache during initialization: ${error.message}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public static getInstance(options: IIPReputationOptions = {}, storageManager?: any): IPReputationChecker {
|
|
104
|
+
if (!IPReputationChecker.instance) {
|
|
105
|
+
IPReputationChecker.instance = new IPReputationChecker(options, storageManager);
|
|
106
|
+
}
|
|
107
|
+
return IPReputationChecker.instance;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check an IP address's reputation via the Rust bridge
|
|
112
|
+
*/
|
|
113
|
+
public async checkReputation(ip: string): Promise<IReputationResult> {
|
|
114
|
+
try {
|
|
115
|
+
if (!this.isValidIPAddress(ip)) {
|
|
116
|
+
logger.log('warn', `Invalid IP address format: ${ip}`);
|
|
117
|
+
return this.createErrorResult(ip, 'Invalid IP address format');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check cache first
|
|
121
|
+
const cachedResult = this.reputationCache.get(ip);
|
|
122
|
+
if (cachedResult) {
|
|
123
|
+
logger.log('info', `Using cached reputation data for IP ${ip}`, {
|
|
124
|
+
score: cachedResult.score,
|
|
125
|
+
isSpam: cachedResult.isSpam
|
|
126
|
+
});
|
|
127
|
+
return cachedResult;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Delegate to Rust bridge
|
|
131
|
+
const bridge = RustSecurityBridge.getInstance();
|
|
132
|
+
const rustResult = await bridge.checkIpReputation(ip);
|
|
133
|
+
|
|
134
|
+
const result: IReputationResult = {
|
|
135
|
+
score: rustResult.score,
|
|
136
|
+
isSpam: rustResult.listed_count > 0,
|
|
137
|
+
isProxy: rustResult.ip_type === 'proxy',
|
|
138
|
+
isTor: rustResult.ip_type === 'tor',
|
|
139
|
+
isVPN: rustResult.ip_type === 'vpn',
|
|
140
|
+
blacklists: rustResult.dnsbl_results
|
|
141
|
+
.filter(d => d.listed)
|
|
142
|
+
.map(d => d.server),
|
|
143
|
+
timestamp: Date.now(),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
this.reputationCache.set(ip, result);
|
|
147
|
+
|
|
148
|
+
if (this.options.enableLocalCache) {
|
|
149
|
+
this.saveCache().catch(error => {
|
|
150
|
+
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.logReputationCheck(ip, result);
|
|
155
|
+
return result;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.log('error', `Error checking IP reputation for ${ip}: ${error.message}`, {
|
|
158
|
+
ip,
|
|
159
|
+
stack: error.stack
|
|
160
|
+
});
|
|
161
|
+
const errorResult = this.createErrorResult(ip, error.message);
|
|
162
|
+
// Cache error results to avoid repeated failing lookups
|
|
163
|
+
this.reputationCache.set(ip, errorResult);
|
|
164
|
+
return errorResult;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private createErrorResult(ip: string, errorMessage: string): IReputationResult {
|
|
169
|
+
return {
|
|
170
|
+
score: 50,
|
|
171
|
+
isSpam: false,
|
|
172
|
+
isProxy: false,
|
|
173
|
+
isTor: false,
|
|
174
|
+
isVPN: false,
|
|
175
|
+
timestamp: Date.now(),
|
|
176
|
+
error: errorMessage
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private isValidIPAddress(ip: string): boolean {
|
|
181
|
+
const ipv4Pattern = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
182
|
+
return ipv4Pattern.test(ip);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private logReputationCheck(ip: string, result: IReputationResult): void {
|
|
186
|
+
let logLevel = SecurityLogLevel.INFO;
|
|
187
|
+
if (result.score < this.options.highRiskThreshold) {
|
|
188
|
+
logLevel = SecurityLogLevel.WARN;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
SecurityLogger.getInstance().logEvent({
|
|
192
|
+
level: logLevel,
|
|
193
|
+
type: SecurityEventType.IP_REPUTATION,
|
|
194
|
+
message: `IP reputation check ${result.isSpam ? 'flagged spam' : 'completed'} for ${ip}`,
|
|
195
|
+
ipAddress: ip,
|
|
196
|
+
details: {
|
|
197
|
+
score: result.score,
|
|
198
|
+
isSpam: result.isSpam,
|
|
199
|
+
isProxy: result.isProxy,
|
|
200
|
+
isTor: result.isTor,
|
|
201
|
+
isVPN: result.isVPN,
|
|
202
|
+
country: result.country,
|
|
203
|
+
blacklists: result.blacklists
|
|
204
|
+
},
|
|
205
|
+
success: !result.isSpam
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async saveCache(): Promise<void> {
|
|
210
|
+
try {
|
|
211
|
+
const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({
|
|
212
|
+
ip,
|
|
213
|
+
data
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
if (entries.length === 0) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const cacheData = JSON.stringify(entries);
|
|
221
|
+
|
|
222
|
+
if (this.storageManager) {
|
|
223
|
+
await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
|
|
224
|
+
logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`);
|
|
225
|
+
} else {
|
|
226
|
+
const cacheDir = plugins.path.join(paths.dataDir, 'security');
|
|
227
|
+
await plugins.smartfs.directory(cacheDir).recursive().create();
|
|
228
|
+
const cacheFile = plugins.path.join(cacheDir, 'ip_reputation_cache.json');
|
|
229
|
+
await plugins.smartfs.file(cacheFile).write(cacheData);
|
|
230
|
+
logger.log('info', `Saved ${entries.length} IP reputation cache entries to disk`);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private async loadCache(): Promise<void> {
|
|
238
|
+
try {
|
|
239
|
+
let cacheData: string | null = null;
|
|
240
|
+
let fromFilesystem = false;
|
|
241
|
+
|
|
242
|
+
if (this.storageManager) {
|
|
243
|
+
try {
|
|
244
|
+
cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
|
|
245
|
+
|
|
246
|
+
if (!cacheData) {
|
|
247
|
+
const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
|
|
248
|
+
if (plugins.fs.existsSync(cacheFile)) {
|
|
249
|
+
logger.log('info', 'Migrating IP reputation cache from filesystem to StorageManager');
|
|
250
|
+
cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
|
|
251
|
+
fromFilesystem = true;
|
|
252
|
+
await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
|
|
253
|
+
logger.log('info', 'IP reputation cache migrated to StorageManager successfully');
|
|
254
|
+
try {
|
|
255
|
+
plugins.fs.unlinkSync(cacheFile);
|
|
256
|
+
logger.log('info', 'Old cache file removed after migration');
|
|
257
|
+
} catch (deleteError) {
|
|
258
|
+
logger.log('warn', `Could not delete old cache file: ${deleteError.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
logger.log('error', `Error loading from StorageManager: ${error.message}`);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
|
|
267
|
+
if (plugins.fs.existsSync(cacheFile)) {
|
|
268
|
+
cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
|
|
269
|
+
fromFilesystem = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (cacheData) {
|
|
274
|
+
const entries = JSON.parse(cacheData);
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
const validEntries = entries.filter(entry => {
|
|
277
|
+
const age = now - entry.data.timestamp;
|
|
278
|
+
return age < this.options.cacheTTL;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
for (const entry of validEntries) {
|
|
282
|
+
this.reputationCache.set(entry.ip, entry.data);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const source = fromFilesystem ? 'disk' : 'StorageManager';
|
|
286
|
+
logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries from ${source}`);
|
|
287
|
+
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
logger.log('error', `Failed to load IP reputation cache: ${error.message}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
public static getRiskLevel(score: number): 'high' | 'medium' | 'low' | 'trusted' {
|
|
294
|
+
if (score < ReputationThreshold.HIGH_RISK) {
|
|
295
|
+
return 'high';
|
|
296
|
+
} else if (score < ReputationThreshold.MEDIUM_RISK) {
|
|
297
|
+
return 'medium';
|
|
298
|
+
} else if (score < ReputationThreshold.LOW_RISK) {
|
|
299
|
+
return 'low';
|
|
300
|
+
} else {
|
|
301
|
+
return 'trusted';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
public updateStorageManager(storageManager: any): void {
|
|
306
|
+
this.storageManager = storageManager;
|
|
307
|
+
logger.log('info', 'IPReputationChecker storage manager updated');
|
|
308
|
+
|
|
309
|
+
if (this.options.enableLocalCache && this.reputationCache.size > 0) {
|
|
310
|
+
this.saveCache().catch(error => {
|
|
311
|
+
logger.log('error', `Failed to save cache to new storage manager: ${error.message}`);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|