@push.rocks/smartmta 6.5.1 → 7.0.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 +20 -0
- package/dist_rust/mailer-bin_linux_amd64 +0 -0
- package/dist_rust/mailer-bin_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -1
- package/dist_ts/mail/core/classes.bouncemanager.d.ts +5 -12
- package/dist_ts/mail/core/classes.bouncemanager.js +27 -92
- package/dist_ts/mail/core/classes.email.js +2 -2
- package/dist_ts/mail/core/classes.templatemanager.d.ts +10 -6
- package/dist_ts/mail/core/classes.templatemanager.js +35 -51
- package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +15 -20
- package/dist_ts/mail/delivery/classes.delivery.queue.js +196 -114
- package/dist_ts/mail/delivery/classes.delivery.system.js +5 -5
- package/dist_ts/mail/interfaces.storage.d.ts +37 -6
- package/dist_ts/mail/interfaces.storage.js +33 -3
- package/dist_ts/mail/routing/classes.dkim.manager.d.ts +2 -2
- package/dist_ts/mail/routing/classes.dkim.manager.js +2 -3
- package/dist_ts/mail/routing/classes.dns.manager.d.ts +2 -2
- package/dist_ts/mail/routing/classes.dns.manager.js +1 -1
- package/dist_ts/mail/routing/classes.email.router.d.ts +2 -2
- package/dist_ts/mail/routing/classes.email.router.js +4 -5
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +8 -6
- package/dist_ts/mail/routing/classes.unified.email.server.js +12 -41
- package/dist_ts/mail/routing/interfaces.d.ts +0 -2
- package/dist_ts/mail/security/classes.dkimcreator.d.ts +15 -45
- package/dist_ts/mail/security/classes.dkimcreator.js +66 -294
- package/dist_ts/paths.d.ts +0 -12
- package/dist_ts/paths.js +3 -36
- package/dist_ts/plugins.d.ts +2 -5
- package/dist_ts/plugins.js +3 -6
- package/dist_ts/security/classes.contentscanner.js +1 -2
- package/dist_ts/security/classes.ipreputationchecker.d.ts +6 -6
- package/dist_ts/security/classes.ipreputationchecker.js +24 -84
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +3 -3
- package/package.json +2 -3
- package/readme.md +12 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +4 -0
- package/ts/mail/core/classes.bouncemanager.ts +31 -110
- package/ts/mail/core/classes.email.ts +1 -1
- package/ts/mail/core/classes.templatemanager.ts +42 -57
- package/ts/mail/delivery/classes.delivery.queue.ts +264 -135
- package/ts/mail/delivery/classes.delivery.system.ts +7 -5
- package/ts/mail/interfaces.storage.ts +64 -10
- package/ts/mail/routing/classes.dkim.manager.ts +3 -3
- package/ts/mail/routing/classes.dns.manager.ts +3 -3
- package/ts/mail/routing/classes.email.router.ts +6 -6
- package/ts/mail/routing/classes.unified.email.server.ts +23 -44
- package/ts/mail/routing/interfaces.ts +0 -2
- package/ts/mail/security/classes.dkimcreator.ts +104 -352
- package/ts/paths.ts +2 -41
- package/ts/plugins.ts +1 -6
- package/ts/security/classes.contentscanner.ts +0 -1
- package/ts/security/classes.ipreputationchecker.ts +26 -90
- package/ts/security/classes.rustsecuritybridge.ts +3 -3
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import * as plugins from '../plugins.js';
|
|
2
|
-
import * as paths from '../paths.js';
|
|
3
1
|
import { logger } from '../logger.js';
|
|
4
|
-
import {
|
|
2
|
+
import type { IStorageManager } from '../mail/interfaces.storage.js';
|
|
5
3
|
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
|
|
6
4
|
import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
|
|
7
5
|
import { LRUCache } from 'lru-cache';
|
|
@@ -54,20 +52,20 @@ export interface IIPReputationOptions {
|
|
|
54
52
|
highRiskThreshold?: number; // Score below this is high risk
|
|
55
53
|
mediumRiskThreshold?: number; // Score below this is medium risk
|
|
56
54
|
lowRiskThreshold?: number; // Score below this is low risk
|
|
57
|
-
|
|
55
|
+
persistCache?: boolean; // Whether to persist cache through IStorageManager (default: true)
|
|
58
56
|
enableDNSBL?: boolean; // Whether to use DNSBL checks (default: true)
|
|
59
57
|
enableIPInfo?: boolean; // Whether to use IP info service (default: true)
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
/**
|
|
63
61
|
* IP reputation checker — delegates DNSBL lookups to the Rust security bridge.
|
|
64
|
-
* Retains LRU
|
|
62
|
+
* Retains an in-memory LRU cache and optionally persists it through managed storage.
|
|
65
63
|
*/
|
|
66
64
|
export class IPReputationChecker {
|
|
67
65
|
private static instance: IPReputationChecker;
|
|
68
66
|
private reputationCache: LRUCache<string, IReputationResult>;
|
|
69
67
|
private options: Required<IIPReputationOptions>;
|
|
70
|
-
private storageManager?:
|
|
68
|
+
private storageManager?: IStorageManager;
|
|
71
69
|
|
|
72
70
|
private static readonly DEFAULT_OPTIONS: Required<IIPReputationOptions> = {
|
|
73
71
|
maxCacheSize: 10000,
|
|
@@ -76,12 +74,12 @@ export class IPReputationChecker {
|
|
|
76
74
|
highRiskThreshold: ReputationThreshold.HIGH_RISK,
|
|
77
75
|
mediumRiskThreshold: ReputationThreshold.MEDIUM_RISK,
|
|
78
76
|
lowRiskThreshold: ReputationThreshold.LOW_RISK,
|
|
79
|
-
|
|
77
|
+
persistCache: true,
|
|
80
78
|
enableDNSBL: true,
|
|
81
79
|
enableIPInfo: true
|
|
82
80
|
};
|
|
83
81
|
|
|
84
|
-
constructor(options: IIPReputationOptions = {}, storageManager?:
|
|
82
|
+
constructor(options: IIPReputationOptions = {}, storageManager?: IStorageManager) {
|
|
85
83
|
this.options = {
|
|
86
84
|
...IPReputationChecker.DEFAULT_OPTIONS,
|
|
87
85
|
...options
|
|
@@ -94,14 +92,14 @@ export class IPReputationChecker {
|
|
|
94
92
|
ttl: this.options.cacheTTL,
|
|
95
93
|
});
|
|
96
94
|
|
|
97
|
-
if (this.options.
|
|
95
|
+
if (this.options.persistCache) {
|
|
98
96
|
this.loadCache().catch(error => {
|
|
99
97
|
logger.log('error', `Failed to load IP reputation cache during initialization: ${error.message}`);
|
|
100
98
|
});
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
public static getInstance(options: IIPReputationOptions = {}, storageManager?:
|
|
102
|
+
public static getInstance(options: IIPReputationOptions = {}, storageManager?: IStorageManager): IPReputationChecker {
|
|
105
103
|
if (!IPReputationChecker.instance) {
|
|
106
104
|
IPReputationChecker.instance = new IPReputationChecker(options, storageManager);
|
|
107
105
|
}
|
|
@@ -146,7 +144,7 @@ export class IPReputationChecker {
|
|
|
146
144
|
|
|
147
145
|
this.reputationCache.set(ip, result);
|
|
148
146
|
|
|
149
|
-
if (this.options.
|
|
147
|
+
if (this.options.persistCache) {
|
|
150
148
|
this.saveCache().catch(error => {
|
|
151
149
|
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
|
|
152
150
|
});
|
|
@@ -208,87 +206,25 @@ export class IPReputationChecker {
|
|
|
208
206
|
}
|
|
209
207
|
|
|
210
208
|
private async saveCache(): Promise<void> {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (entries.length === 0) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const cacheData = JSON.stringify(entries);
|
|
222
|
-
|
|
223
|
-
if (hasStorageManagerMethods(this.storageManager, ['set'])) {
|
|
224
|
-
await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
|
|
225
|
-
logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`);
|
|
226
|
-
} else {
|
|
227
|
-
const cacheDir = plugins.path.join(paths.dataDir, 'security');
|
|
228
|
-
await plugins.smartfs.directory(cacheDir).recursive().create();
|
|
229
|
-
const cacheFile = plugins.path.join(cacheDir, 'ip_reputation_cache.json');
|
|
230
|
-
await plugins.smartfs.file(cacheFile).write(cacheData);
|
|
231
|
-
logger.log('info', `Saved ${entries.length} IP reputation cache entries to disk`);
|
|
232
|
-
}
|
|
233
|
-
} catch (error) {
|
|
234
|
-
logger.log('error', `Failed to save IP reputation cache: ${error.message}`);
|
|
235
|
-
}
|
|
209
|
+
if (!this.storageManager) return;
|
|
210
|
+
const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({ ip, data }));
|
|
211
|
+
if (entries.length === 0) return;
|
|
212
|
+
await this.storageManager.set('/security/ip-reputation-cache.json', JSON.stringify(entries));
|
|
213
|
+
logger.log('info', `Saved ${entries.length} IP reputation cache entries`);
|
|
236
214
|
}
|
|
237
215
|
|
|
238
216
|
private async loadCache(): Promise<void> {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
|
|
249
|
-
if (plugins.fs.existsSync(cacheFile)) {
|
|
250
|
-
logger.log('info', 'Migrating IP reputation cache from filesystem to StorageManager');
|
|
251
|
-
cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
|
|
252
|
-
fromFilesystem = true;
|
|
253
|
-
await this.storageManager.set('/security/ip-reputation-cache.json', cacheData);
|
|
254
|
-
logger.log('info', 'IP reputation cache migrated to StorageManager successfully');
|
|
255
|
-
try {
|
|
256
|
-
plugins.fs.unlinkSync(cacheFile);
|
|
257
|
-
logger.log('info', 'Old cache file removed after migration');
|
|
258
|
-
} catch (deleteError) {
|
|
259
|
-
logger.log('warn', `Could not delete old cache file: ${deleteError.message}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
} catch (error) {
|
|
264
|
-
logger.log('error', `Error loading from StorageManager: ${error.message}`);
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json');
|
|
268
|
-
if (plugins.fs.existsSync(cacheFile)) {
|
|
269
|
-
cacheData = plugins.fs.readFileSync(cacheFile, 'utf8');
|
|
270
|
-
fromFilesystem = true;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (cacheData) {
|
|
275
|
-
const entries = JSON.parse(cacheData);
|
|
276
|
-
const now = Date.now();
|
|
277
|
-
const validEntries = entries.filter(entry => {
|
|
278
|
-
const age = now - entry.data.timestamp;
|
|
279
|
-
return age < this.options.cacheTTL;
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
for (const entry of validEntries) {
|
|
283
|
-
this.reputationCache.set(entry.ip, entry.data);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const source = fromFilesystem ? 'disk' : 'StorageManager';
|
|
287
|
-
logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries from ${source}`);
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
logger.log('error', `Failed to load IP reputation cache: ${error.message}`);
|
|
217
|
+
if (!this.storageManager) return;
|
|
218
|
+
const cacheData = await this.storageManager.get('/security/ip-reputation-cache.json');
|
|
219
|
+
if (!cacheData) return;
|
|
220
|
+
|
|
221
|
+
const entries = JSON.parse(cacheData);
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
const validEntries = entries.filter((entry) => now - entry.data.timestamp < this.options.cacheTTL);
|
|
224
|
+
for (const entry of validEntries) {
|
|
225
|
+
this.reputationCache.set(entry.ip, entry.data);
|
|
291
226
|
}
|
|
227
|
+
logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries`);
|
|
292
228
|
}
|
|
293
229
|
|
|
294
230
|
public static getRiskLevel(score: number): 'high' | 'medium' | 'low' | 'trusted' {
|
|
@@ -303,11 +239,11 @@ export class IPReputationChecker {
|
|
|
303
239
|
}
|
|
304
240
|
}
|
|
305
241
|
|
|
306
|
-
public updateStorageManager(storageManager:
|
|
242
|
+
public updateStorageManager(storageManager: IStorageManager): void {
|
|
307
243
|
this.storageManager = storageManager;
|
|
308
244
|
logger.log('info', 'IPReputationChecker storage manager updated');
|
|
309
245
|
|
|
310
|
-
if (this.options.
|
|
246
|
+
if (this.options.persistCache && this.reputationCache.size > 0) {
|
|
311
247
|
this.saveCache().catch(error => {
|
|
312
248
|
logger.log('error', `Failed to save cache to new storage manager: ${error.message}`);
|
|
313
249
|
});
|
|
@@ -159,6 +159,7 @@ interface ISmtpServerConfig {
|
|
|
159
159
|
additionalTlsCerts?: Array<{ domains: string[]; certPem: string; keyPem: string }>;
|
|
160
160
|
maxMessageSize?: number;
|
|
161
161
|
maxConnections?: number;
|
|
162
|
+
maxConcurrentMessages?: number;
|
|
162
163
|
maxRecipients?: number;
|
|
163
164
|
connectionTimeoutSecs?: number;
|
|
164
165
|
dataTimeoutSecs?: number;
|
|
@@ -183,9 +184,8 @@ interface IRateLimitConfig {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
interface IEmailData {
|
|
186
|
-
type: 'inline'
|
|
187
|
-
base64
|
|
188
|
-
path?: string;
|
|
187
|
+
type: 'inline';
|
|
188
|
+
base64: string;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
interface IEmailReceivedEvent {
|