@push.rocks/smartmta 5.1.3 → 5.2.1
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 +15 -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 +403 -0
- package/dist_ts/security/classes.rustsecuritybridge.js +502 -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/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 +964 -0
- package/ts/security/classes.securitylogger.ts +299 -0
- package/ts/security/index.ts +40 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import * as paths from '../../paths.js';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { logger } from '../../logger.js';
|
|
5
|
+
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
|
|
6
|
+
import { DKIMCreator } from '../security/classes.dkimcreator.js';
|
|
7
|
+
import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js';
|
|
8
|
+
import { EmailRouter } from './classes.email.router.js';
|
|
9
|
+
import { Email } from '../core/classes.email.js';
|
|
10
|
+
import { DomainRegistry } from './classes.domain.registry.js';
|
|
11
|
+
import { DnsManager } from './classes.dns.manager.js';
|
|
12
|
+
import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
|
13
|
+
import { MultiModeDeliverySystem } from '../delivery/classes.delivery.system.js';
|
|
14
|
+
import { UnifiedDeliveryQueue } from '../delivery/classes.delivery.queue.js';
|
|
15
|
+
import { UnifiedRateLimiter } from '../delivery/classes.unified.rate.limiter.js';
|
|
16
|
+
import { SmtpState } from '../delivery/interfaces.js';
|
|
17
|
+
import { EmailActionExecutor } from './classes.email.action.executor.js';
|
|
18
|
+
import { DkimManager } from './classes.dkim.manager.js';
|
|
19
|
+
/**
|
|
20
|
+
* Unified email server that handles all email traffic with pattern-based routing
|
|
21
|
+
*/
|
|
22
|
+
export class UnifiedEmailServer extends EventEmitter {
|
|
23
|
+
dcRouter;
|
|
24
|
+
options;
|
|
25
|
+
emailRouter;
|
|
26
|
+
domainRegistry;
|
|
27
|
+
servers = [];
|
|
28
|
+
stats;
|
|
29
|
+
// Add components needed for sending and securing emails
|
|
30
|
+
dkimCreator;
|
|
31
|
+
rustBridge;
|
|
32
|
+
bounceManager;
|
|
33
|
+
deliveryQueue;
|
|
34
|
+
deliverySystem;
|
|
35
|
+
rateLimiter; // TODO: Implement rate limiting in SMTP server handlers
|
|
36
|
+
// Extracted subsystems
|
|
37
|
+
actionExecutor;
|
|
38
|
+
dkimManager;
|
|
39
|
+
constructor(dcRouter, options) {
|
|
40
|
+
super();
|
|
41
|
+
this.dcRouter = dcRouter;
|
|
42
|
+
// Set default options
|
|
43
|
+
this.options = {
|
|
44
|
+
...options,
|
|
45
|
+
banner: options.banner || `${options.hostname} ESMTP UnifiedEmailServer`,
|
|
46
|
+
maxMessageSize: options.maxMessageSize || 10 * 1024 * 1024, // 10MB
|
|
47
|
+
maxClients: options.maxClients || 100,
|
|
48
|
+
maxConnections: options.maxConnections || 1000,
|
|
49
|
+
connectionTimeout: options.connectionTimeout || 60000, // 1 minute
|
|
50
|
+
socketTimeout: options.socketTimeout || 60000 // 1 minute
|
|
51
|
+
};
|
|
52
|
+
// Initialize Rust security bridge (singleton)
|
|
53
|
+
this.rustBridge = RustSecurityBridge.getInstance();
|
|
54
|
+
// Initialize DKIM creator with storage manager
|
|
55
|
+
this.dkimCreator = new DKIMCreator(paths.keysDir, dcRouter.storageManager);
|
|
56
|
+
// Initialize bounce manager with storage manager
|
|
57
|
+
this.bounceManager = new BounceManager({
|
|
58
|
+
maxCacheSize: 10000,
|
|
59
|
+
cacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
60
|
+
storageManager: dcRouter.storageManager
|
|
61
|
+
});
|
|
62
|
+
// Initialize domain registry
|
|
63
|
+
this.domainRegistry = new DomainRegistry(options.domains, options.defaults);
|
|
64
|
+
// Initialize email router with routes and storage manager
|
|
65
|
+
this.emailRouter = new EmailRouter(options.routes || [], {
|
|
66
|
+
storageManager: dcRouter.storageManager,
|
|
67
|
+
persistChanges: true
|
|
68
|
+
});
|
|
69
|
+
// Initialize rate limiter
|
|
70
|
+
this.rateLimiter = new UnifiedRateLimiter(options.rateLimits || {
|
|
71
|
+
global: {
|
|
72
|
+
maxConnectionsPerIP: 10,
|
|
73
|
+
maxMessagesPerMinute: 100,
|
|
74
|
+
maxRecipientsPerMessage: 50,
|
|
75
|
+
maxErrorsPerIP: 10,
|
|
76
|
+
maxAuthFailuresPerIP: 5,
|
|
77
|
+
blockDuration: 300000 // 5 minutes
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
// Initialize delivery components
|
|
81
|
+
const queueOptions = {
|
|
82
|
+
storageType: 'memory', // Default to memory storage
|
|
83
|
+
maxRetries: 3,
|
|
84
|
+
baseRetryDelay: 300000, // 5 minutes
|
|
85
|
+
maxRetryDelay: 3600000 // 1 hour
|
|
86
|
+
};
|
|
87
|
+
this.deliveryQueue = new UnifiedDeliveryQueue(queueOptions);
|
|
88
|
+
const deliveryOptions = {
|
|
89
|
+
globalRateLimit: 100, // Default to 100 emails per minute
|
|
90
|
+
concurrentDeliveries: 10,
|
|
91
|
+
processBounces: true,
|
|
92
|
+
bounceHandler: {
|
|
93
|
+
processSmtpFailure: this.processSmtpFailure.bind(this)
|
|
94
|
+
},
|
|
95
|
+
onDeliverySuccess: async (_item, _result) => {
|
|
96
|
+
// Delivery success recorded via delivery system
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
this.deliverySystem = new MultiModeDeliverySystem(this.deliveryQueue, deliveryOptions, this);
|
|
100
|
+
// Initialize action executor
|
|
101
|
+
this.actionExecutor = new EmailActionExecutor({
|
|
102
|
+
sendOutboundEmail: this.sendOutboundEmail.bind(this),
|
|
103
|
+
bounceManager: this.bounceManager,
|
|
104
|
+
deliveryQueue: this.deliveryQueue,
|
|
105
|
+
});
|
|
106
|
+
// Initialize DKIM manager
|
|
107
|
+
this.dkimManager = new DkimManager(this.dkimCreator, this.domainRegistry, dcRouter, this.rustBridge);
|
|
108
|
+
// Initialize statistics
|
|
109
|
+
this.stats = {
|
|
110
|
+
startTime: new Date(),
|
|
111
|
+
connections: {
|
|
112
|
+
current: 0,
|
|
113
|
+
total: 0
|
|
114
|
+
},
|
|
115
|
+
messages: {
|
|
116
|
+
processed: 0,
|
|
117
|
+
delivered: 0,
|
|
118
|
+
failed: 0
|
|
119
|
+
},
|
|
120
|
+
processingTime: {
|
|
121
|
+
avg: 0,
|
|
122
|
+
max: 0,
|
|
123
|
+
min: 0
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
// We'll create the SMTP servers during the start() method
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Send an outbound email via the Rust SMTP client.
|
|
130
|
+
* Uses connection pooling in the Rust binary for efficiency.
|
|
131
|
+
*/
|
|
132
|
+
async sendOutboundEmail(host, port, email, options) {
|
|
133
|
+
// Build DKIM config if domain has keys
|
|
134
|
+
let dkim;
|
|
135
|
+
if (options?.dkimDomain) {
|
|
136
|
+
try {
|
|
137
|
+
const { privateKey } = await this.dkimCreator.readDKIMKeys(options.dkimDomain);
|
|
138
|
+
dkim = { domain: options.dkimDomain, selector: options.dkimSelector || 'default', privateKey };
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
logger.log('warn', `Failed to read DKIM keys for ${options.dkimDomain}: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Serialize the Email to the outbound format
|
|
145
|
+
const outboundEmail = {
|
|
146
|
+
from: email.from,
|
|
147
|
+
to: email.to,
|
|
148
|
+
cc: email.cc || [],
|
|
149
|
+
bcc: email.bcc || [],
|
|
150
|
+
subject: email.subject || '',
|
|
151
|
+
text: email.text || '',
|
|
152
|
+
html: email.html || undefined,
|
|
153
|
+
headers: email.headers || {},
|
|
154
|
+
};
|
|
155
|
+
return this.rustBridge.sendOutboundEmail({
|
|
156
|
+
host,
|
|
157
|
+
port,
|
|
158
|
+
secure: port === 465,
|
|
159
|
+
domain: this.options.hostname,
|
|
160
|
+
auth: options?.auth,
|
|
161
|
+
email: outboundEmail,
|
|
162
|
+
dkim,
|
|
163
|
+
connectionTimeoutSecs: Math.floor((this.options.outbound?.connectionTimeout || 30000) / 1000),
|
|
164
|
+
socketTimeoutSecs: Math.floor((this.options.outbound?.socketTimeout || 120000) / 1000),
|
|
165
|
+
poolKey: `${host}:${port}`,
|
|
166
|
+
maxPoolConnections: this.options.outbound?.maxConnections || 10,
|
|
167
|
+
tlsOpportunistic: options?.tlsOpportunistic ?? (port === 25),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Start the unified email server
|
|
172
|
+
*/
|
|
173
|
+
async start() {
|
|
174
|
+
logger.log('info', `Starting UnifiedEmailServer on ports: ${this.options.ports.join(', ')}`);
|
|
175
|
+
try {
|
|
176
|
+
await this.startDeliveryPipeline();
|
|
177
|
+
await this.startRustBridge();
|
|
178
|
+
await this.initializeDkimAndDns();
|
|
179
|
+
this.registerBridgeEventHandlers();
|
|
180
|
+
await this.startSmtpServer();
|
|
181
|
+
logger.log('info', 'UnifiedEmailServer started successfully');
|
|
182
|
+
this.emit('started');
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
logger.log('error', `Failed to start UnifiedEmailServer: ${error.message}`);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async startDeliveryPipeline() {
|
|
190
|
+
await this.deliveryQueue.initialize();
|
|
191
|
+
logger.log('info', 'Email delivery queue initialized');
|
|
192
|
+
await this.deliverySystem.start();
|
|
193
|
+
logger.log('info', 'Email delivery system started');
|
|
194
|
+
}
|
|
195
|
+
async startRustBridge() {
|
|
196
|
+
const bridgeOk = await this.rustBridge.start();
|
|
197
|
+
if (!bridgeOk) {
|
|
198
|
+
throw new Error('Rust security bridge failed to start. The mailer-bin binary is required. Run "pnpm build" to compile it.');
|
|
199
|
+
}
|
|
200
|
+
logger.log('info', 'Rust security bridge started — Rust is the primary security backend');
|
|
201
|
+
this.rustBridge.on('stateChange', ({ oldState, newState }) => {
|
|
202
|
+
if (newState === 'failed')
|
|
203
|
+
this.emit('bridgeFailed');
|
|
204
|
+
else if (newState === 'restarting')
|
|
205
|
+
this.emit('bridgeRestarting');
|
|
206
|
+
else if (newState === 'running' && oldState === 'restarting')
|
|
207
|
+
this.emit('bridgeRecovered');
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async initializeDkimAndDns() {
|
|
211
|
+
await this.dkimManager.setupDkimForDomains();
|
|
212
|
+
logger.log('info', 'DKIM configuration completed for all domains');
|
|
213
|
+
const dnsManager = new DnsManager(this.dcRouter);
|
|
214
|
+
await dnsManager.ensureDnsRecords(this.domainRegistry.getAllConfigs(), this.dkimCreator);
|
|
215
|
+
logger.log('info', 'DNS records ensured for all configured domains');
|
|
216
|
+
this.applyDomainRateLimits();
|
|
217
|
+
logger.log('info', 'Per-domain rate limits configured');
|
|
218
|
+
await this.dkimManager.checkAndRotateDkimKeys();
|
|
219
|
+
logger.log('info', 'DKIM key rotation check completed');
|
|
220
|
+
}
|
|
221
|
+
registerBridgeEventHandlers() {
|
|
222
|
+
this.rustBridge.onEmailReceived(async (data) => {
|
|
223
|
+
try {
|
|
224
|
+
await this.handleRustEmailReceived(data);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
logger.log('error', `Error handling email from Rust SMTP: ${err.message}`);
|
|
228
|
+
try {
|
|
229
|
+
await this.rustBridge.sendEmailProcessingResult({
|
|
230
|
+
correlationId: data.correlationId,
|
|
231
|
+
accepted: false,
|
|
232
|
+
smtpCode: 451,
|
|
233
|
+
smtpMessage: 'Internal processing error',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (sendErr) {
|
|
237
|
+
logger.log('warn', `Could not send rejection back to Rust: ${sendErr.message}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
this.rustBridge.onAuthRequest(async (data) => {
|
|
242
|
+
try {
|
|
243
|
+
await this.handleRustAuthRequest(data);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
logger.log('error', `Error handling auth from Rust SMTP: ${err.message}`);
|
|
247
|
+
try {
|
|
248
|
+
await this.rustBridge.sendAuthResult({
|
|
249
|
+
correlationId: data.correlationId,
|
|
250
|
+
success: false,
|
|
251
|
+
message: 'Internal auth error',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (sendErr) {
|
|
255
|
+
logger.log('warn', `Could not send auth rejection back to Rust: ${sendErr.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
this.rustBridge.onScramCredentialRequest(async (data) => {
|
|
260
|
+
try {
|
|
261
|
+
await this.handleScramCredentialRequest(data);
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
logger.log('error', `Error handling SCRAM credential request: ${err.message}`);
|
|
265
|
+
try {
|
|
266
|
+
await this.rustBridge.sendScramCredentialResult({
|
|
267
|
+
correlationId: data.correlationId,
|
|
268
|
+
found: false,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (sendErr) {
|
|
272
|
+
logger.log('warn', `Could not send SCRAM credential rejection: ${sendErr.message}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
async startSmtpServer() {
|
|
278
|
+
const hasTlsConfig = this.options.tls?.keyPath && this.options.tls?.certPath;
|
|
279
|
+
let tlsCertPem;
|
|
280
|
+
let tlsKeyPem;
|
|
281
|
+
if (hasTlsConfig) {
|
|
282
|
+
try {
|
|
283
|
+
tlsKeyPem = plugins.fs.readFileSync(this.options.tls.keyPath, 'utf8');
|
|
284
|
+
tlsCertPem = plugins.fs.readFileSync(this.options.tls.certPath, 'utf8');
|
|
285
|
+
logger.log('info', 'TLS certificates loaded successfully');
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
logger.log('warn', `Failed to load TLS certificates: ${error.message}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const smtpPorts = this.options.ports.filter(p => p !== 465);
|
|
292
|
+
const securePort = this.options.ports.find(p => p === 465);
|
|
293
|
+
const started = await this.rustBridge.startSmtpServer({
|
|
294
|
+
hostname: this.options.hostname,
|
|
295
|
+
ports: smtpPorts,
|
|
296
|
+
securePort: securePort,
|
|
297
|
+
tlsCertPem,
|
|
298
|
+
tlsKeyPem,
|
|
299
|
+
maxMessageSize: this.options.maxMessageSize || 10 * 1024 * 1024,
|
|
300
|
+
maxConnections: this.options.maxConnections || this.options.maxClients || 100,
|
|
301
|
+
maxRecipients: 100,
|
|
302
|
+
connectionTimeoutSecs: this.options.connectionTimeout ? Math.floor(this.options.connectionTimeout / 1000) : 30,
|
|
303
|
+
dataTimeoutSecs: 60,
|
|
304
|
+
authEnabled: !!this.options.auth?.required || !!(this.options.auth?.users?.length),
|
|
305
|
+
maxAuthFailures: 3,
|
|
306
|
+
socketTimeoutSecs: this.options.socketTimeout ? Math.floor(this.options.socketTimeout / 1000) : 300,
|
|
307
|
+
processingTimeoutSecs: 30,
|
|
308
|
+
rateLimits: this.options.rateLimits ? {
|
|
309
|
+
maxConnectionsPerIp: this.options.rateLimits.global?.maxConnectionsPerIP || 50,
|
|
310
|
+
maxMessagesPerSender: this.options.rateLimits.global?.maxMessagesPerMinute || 100,
|
|
311
|
+
maxAuthFailuresPerIp: this.options.rateLimits.global?.maxAuthFailuresPerIP || 5,
|
|
312
|
+
windowSecs: 60,
|
|
313
|
+
} : undefined,
|
|
314
|
+
});
|
|
315
|
+
if (!started) {
|
|
316
|
+
throw new Error('Failed to start Rust SMTP server');
|
|
317
|
+
}
|
|
318
|
+
logger.log('info', `Rust SMTP server listening on ports: ${smtpPorts.join(', ')}${securePort ? ` + ${securePort} (TLS)` : ''}`);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Stop the unified email server
|
|
322
|
+
*/
|
|
323
|
+
async stop() {
|
|
324
|
+
logger.log('info', 'Stopping UnifiedEmailServer');
|
|
325
|
+
try {
|
|
326
|
+
// Stop the Rust SMTP server first
|
|
327
|
+
try {
|
|
328
|
+
await this.rustBridge.stopSmtpServer();
|
|
329
|
+
logger.log('info', 'Rust SMTP server stopped');
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
logger.log('warn', `Error stopping Rust SMTP server: ${err.message}`);
|
|
333
|
+
}
|
|
334
|
+
// Clear the servers array - servers will be garbage collected
|
|
335
|
+
this.servers = [];
|
|
336
|
+
// Remove bridge state change listener and stop bridge
|
|
337
|
+
this.rustBridge.removeAllListeners('stateChange');
|
|
338
|
+
await this.rustBridge.stop();
|
|
339
|
+
// Stop the delivery system
|
|
340
|
+
if (this.deliverySystem) {
|
|
341
|
+
await this.deliverySystem.stop();
|
|
342
|
+
logger.log('info', 'Email delivery system stopped');
|
|
343
|
+
}
|
|
344
|
+
// Shut down the delivery queue
|
|
345
|
+
if (this.deliveryQueue) {
|
|
346
|
+
await this.deliveryQueue.shutdown();
|
|
347
|
+
logger.log('info', 'Email delivery queue shut down');
|
|
348
|
+
}
|
|
349
|
+
// Close all Rust SMTP client connection pools
|
|
350
|
+
try {
|
|
351
|
+
await this.rustBridge.closeSmtpPool();
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// Bridge may already be stopped
|
|
355
|
+
}
|
|
356
|
+
logger.log('info', 'UnifiedEmailServer stopped successfully');
|
|
357
|
+
this.emit('stopped');
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
logger.log('error', `Error stopping UnifiedEmailServer: ${error.message}`);
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// -----------------------------------------------------------------------
|
|
365
|
+
// Rust SMTP server event handlers
|
|
366
|
+
// -----------------------------------------------------------------------
|
|
367
|
+
/**
|
|
368
|
+
* Handle an emailReceived event from the Rust SMTP server.
|
|
369
|
+
*/
|
|
370
|
+
async handleRustEmailReceived(data) {
|
|
371
|
+
const { correlationId, mailFrom, rcptTo, remoteAddr, clientHostname, secure, authenticatedUser } = data;
|
|
372
|
+
logger.log('info', `Rust SMTP received email from=${mailFrom} to=${rcptTo.join(',')} remote=${remoteAddr}`);
|
|
373
|
+
try {
|
|
374
|
+
// Decode the email data
|
|
375
|
+
let rawMessageBuffer;
|
|
376
|
+
if (data.data.type === 'inline' && data.data.base64) {
|
|
377
|
+
rawMessageBuffer = Buffer.from(data.data.base64, 'base64');
|
|
378
|
+
}
|
|
379
|
+
else if (data.data.type === 'file' && data.data.path) {
|
|
380
|
+
rawMessageBuffer = plugins.fs.readFileSync(data.data.path);
|
|
381
|
+
// Clean up temp file
|
|
382
|
+
try {
|
|
383
|
+
plugins.fs.unlinkSync(data.data.path);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
// Ignore cleanup errors
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
throw new Error('Invalid email data transport');
|
|
391
|
+
}
|
|
392
|
+
// Build a session-like object for processEmailByMode
|
|
393
|
+
const session = {
|
|
394
|
+
id: data.sessionId || 'rust-' + Math.random().toString(36).substring(2),
|
|
395
|
+
state: SmtpState.FINISHED,
|
|
396
|
+
mailFrom: mailFrom,
|
|
397
|
+
rcptTo: rcptTo,
|
|
398
|
+
emailData: rawMessageBuffer.toString('utf8'),
|
|
399
|
+
useTLS: secure,
|
|
400
|
+
connectionEnded: false,
|
|
401
|
+
remoteAddress: remoteAddr,
|
|
402
|
+
clientHostname: clientHostname || '',
|
|
403
|
+
secure: secure,
|
|
404
|
+
authenticated: !!authenticatedUser,
|
|
405
|
+
envelope: {
|
|
406
|
+
mailFrom: { address: mailFrom, args: {} },
|
|
407
|
+
rcptTo: rcptTo.map(addr => ({ address: addr, args: {} })),
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
if (authenticatedUser) {
|
|
411
|
+
session.user = { username: authenticatedUser };
|
|
412
|
+
}
|
|
413
|
+
// Attach pre-computed security results from Rust in-process pipeline
|
|
414
|
+
if (data.securityResults) {
|
|
415
|
+
session._precomputedSecurityResults = data.securityResults;
|
|
416
|
+
}
|
|
417
|
+
// Process the email through the routing system
|
|
418
|
+
await this.processEmailByMode(rawMessageBuffer, session);
|
|
419
|
+
// Send acceptance back to Rust
|
|
420
|
+
await this.rustBridge.sendEmailProcessingResult({
|
|
421
|
+
correlationId,
|
|
422
|
+
accepted: true,
|
|
423
|
+
smtpCode: 250,
|
|
424
|
+
smtpMessage: '2.0.0 Message accepted for delivery',
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
logger.log('error', `Failed to process email from Rust SMTP: ${err.message}`);
|
|
429
|
+
await this.rustBridge.sendEmailProcessingResult({
|
|
430
|
+
correlationId,
|
|
431
|
+
accepted: false,
|
|
432
|
+
smtpCode: 550,
|
|
433
|
+
smtpMessage: `5.0.0 Processing failed: ${err.message}`,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Handle an authRequest event from the Rust SMTP server.
|
|
439
|
+
*/
|
|
440
|
+
async handleRustAuthRequest(data) {
|
|
441
|
+
const { correlationId, username, password, remoteAddr } = data;
|
|
442
|
+
logger.log('info', `Rust SMTP auth request for user=${username} from=${remoteAddr}`);
|
|
443
|
+
// Check against configured users
|
|
444
|
+
const users = this.options.auth?.users || [];
|
|
445
|
+
const matched = users.find(u => u.username === username && u.password === password);
|
|
446
|
+
if (matched) {
|
|
447
|
+
await this.rustBridge.sendAuthResult({
|
|
448
|
+
correlationId,
|
|
449
|
+
success: true,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
logger.log('warn', `Auth failed for user=${username} from=${remoteAddr}`);
|
|
454
|
+
await this.rustBridge.sendAuthResult({
|
|
455
|
+
correlationId,
|
|
456
|
+
success: false,
|
|
457
|
+
message: 'Invalid credentials',
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Handle a SCRAM credential request from the Rust SMTP server.
|
|
463
|
+
* Computes SCRAM-SHA-256 credentials from the stored password for the given user.
|
|
464
|
+
*/
|
|
465
|
+
async handleScramCredentialRequest(data) {
|
|
466
|
+
const { correlationId, username, remoteAddr } = data;
|
|
467
|
+
const crypto = await import('crypto');
|
|
468
|
+
logger.log('info', `SCRAM credential request for user=${username} from=${remoteAddr}`);
|
|
469
|
+
const users = this.options.auth?.users || [];
|
|
470
|
+
const matched = users.find(u => u.username === username);
|
|
471
|
+
if (!matched) {
|
|
472
|
+
await this.rustBridge.sendScramCredentialResult({
|
|
473
|
+
correlationId,
|
|
474
|
+
found: false,
|
|
475
|
+
});
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Compute SCRAM-SHA-256 credentials from plaintext password
|
|
479
|
+
const salt = crypto.randomBytes(16);
|
|
480
|
+
const iterations = 4096;
|
|
481
|
+
// SaltedPassword = PBKDF2-HMAC-SHA256(password, salt, iterations, 32)
|
|
482
|
+
const saltedPassword = crypto.pbkdf2Sync(matched.password, salt, iterations, 32, 'sha256');
|
|
483
|
+
// ClientKey = HMAC-SHA256(SaltedPassword, "Client Key")
|
|
484
|
+
const clientKey = crypto.createHmac('sha256', saltedPassword).update('Client Key').digest();
|
|
485
|
+
// StoredKey = SHA256(ClientKey)
|
|
486
|
+
const storedKey = crypto.createHash('sha256').update(clientKey).digest();
|
|
487
|
+
// ServerKey = HMAC-SHA256(SaltedPassword, "Server Key")
|
|
488
|
+
const serverKey = crypto.createHmac('sha256', saltedPassword).update('Server Key').digest();
|
|
489
|
+
await this.rustBridge.sendScramCredentialResult({
|
|
490
|
+
correlationId,
|
|
491
|
+
found: true,
|
|
492
|
+
salt: salt.toString('base64'),
|
|
493
|
+
iterations,
|
|
494
|
+
storedKey: storedKey.toString('base64'),
|
|
495
|
+
serverKey: serverKey.toString('base64'),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Verify inbound email security (DKIM/SPF/DMARC) using pre-computed Rust results
|
|
500
|
+
* or falling back to IPC call if no pre-computed results are available.
|
|
501
|
+
*/
|
|
502
|
+
async verifyInboundSecurity(email, session) {
|
|
503
|
+
try {
|
|
504
|
+
// Check for pre-computed results from Rust in-process security pipeline
|
|
505
|
+
const precomputed = session._precomputedSecurityResults;
|
|
506
|
+
let result;
|
|
507
|
+
if (precomputed) {
|
|
508
|
+
logger.log('info', 'Using pre-computed security results from Rust in-process pipeline');
|
|
509
|
+
result = precomputed;
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
// Fallback: IPC round-trip to Rust (for backward compat)
|
|
513
|
+
const rawMessage = session.emailData || email.toRFC822String();
|
|
514
|
+
result = await this.rustBridge.verifyEmail({
|
|
515
|
+
rawMessage,
|
|
516
|
+
ip: session.remoteAddress,
|
|
517
|
+
heloDomain: session.clientHostname || '',
|
|
518
|
+
hostname: this.options.hostname,
|
|
519
|
+
mailFrom: session.envelope?.mailFrom?.address || session.mailFrom || '',
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
// Apply DKIM result headers
|
|
523
|
+
if (result.dkim && result.dkim.length > 0) {
|
|
524
|
+
const dkimSummary = result.dkim
|
|
525
|
+
.map((d) => `${d.status}${d.domain ? ` (${d.domain})` : ''}`)
|
|
526
|
+
.join(', ');
|
|
527
|
+
email.addHeader('X-DKIM-Result', dkimSummary);
|
|
528
|
+
}
|
|
529
|
+
// Apply SPF result header
|
|
530
|
+
if (result.spf) {
|
|
531
|
+
email.addHeader('Received-SPF', `${result.spf.result} (domain: ${result.spf.domain}, ip: ${result.spf.ip})`);
|
|
532
|
+
// Mark as spam on SPF hard fail
|
|
533
|
+
if (result.spf.result === 'fail') {
|
|
534
|
+
email.mightBeSpam = true;
|
|
535
|
+
logger.log('warn', `SPF fail for ${session.remoteAddress} — marking as potential spam`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Apply DMARC result header and policy
|
|
539
|
+
if (result.dmarc) {
|
|
540
|
+
email.addHeader('X-DMARC-Result', `${result.dmarc.action} (policy=${result.dmarc.policy}, dkim=${result.dmarc.dkim_result}, spf=${result.dmarc.spf_result})`);
|
|
541
|
+
if (result.dmarc.action === 'reject') {
|
|
542
|
+
email.mightBeSpam = true;
|
|
543
|
+
logger.log('warn', `DMARC reject for domain ${result.dmarc.domain} — marking as spam`);
|
|
544
|
+
}
|
|
545
|
+
else if (result.dmarc.action === 'quarantine') {
|
|
546
|
+
email.mightBeSpam = true;
|
|
547
|
+
logger.log('info', `DMARC quarantine for domain ${result.dmarc.domain} — marking as potential spam`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Apply content scan results (from pre-computed pipeline)
|
|
551
|
+
if (result.contentScan) {
|
|
552
|
+
const scan = result.contentScan;
|
|
553
|
+
if (scan.threatScore > 0) {
|
|
554
|
+
email.addHeader('X-Spam-Score', String(scan.threatScore));
|
|
555
|
+
if (scan.threatType) {
|
|
556
|
+
email.addHeader('X-Spam-Type', scan.threatType);
|
|
557
|
+
}
|
|
558
|
+
if (scan.threatScore >= 50) {
|
|
559
|
+
email.mightBeSpam = true;
|
|
560
|
+
logger.log('warn', `Content scan threat score ${scan.threatScore} (${scan.threatType}) — marking as potential spam`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Apply IP reputation results (from pre-computed pipeline)
|
|
565
|
+
if (result.ipReputation) {
|
|
566
|
+
const rep = result.ipReputation;
|
|
567
|
+
email.addHeader('X-IP-Reputation-Score', String(rep.score));
|
|
568
|
+
if (rep.is_spam) {
|
|
569
|
+
email.mightBeSpam = true;
|
|
570
|
+
logger.log('warn', `IP ${rep.ip} flagged by reputation check (score=${rep.score}) — marking as potential spam`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
logger.log('info', `Inbound security verified for email from ${session.remoteAddress}: DKIM=${result.dkim?.[0]?.status ?? 'none'}, SPF=${result.spf?.result ?? 'none'}, DMARC=${result.dmarc?.action ?? 'none'}`);
|
|
574
|
+
}
|
|
575
|
+
catch (err) {
|
|
576
|
+
logger.log('warn', `Inbound security verification failed: ${err.message} — accepting email`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Process email based on routing rules
|
|
581
|
+
*/
|
|
582
|
+
async processEmailByMode(emailData, session) {
|
|
583
|
+
// Convert Buffer to Email if needed
|
|
584
|
+
let email;
|
|
585
|
+
if (Buffer.isBuffer(emailData)) {
|
|
586
|
+
// Parse the email data buffer into an Email object
|
|
587
|
+
try {
|
|
588
|
+
const parsed = await plugins.mailparser.simpleParser(emailData);
|
|
589
|
+
email = new Email({
|
|
590
|
+
from: parsed.from?.value[0]?.address || session.envelope.mailFrom.address,
|
|
591
|
+
to: session.envelope.rcptTo[0]?.address || '',
|
|
592
|
+
subject: parsed.subject || '',
|
|
593
|
+
text: parsed.text || '',
|
|
594
|
+
html: parsed.html || undefined,
|
|
595
|
+
attachments: parsed.attachments?.map(att => ({
|
|
596
|
+
filename: att.filename || '',
|
|
597
|
+
content: att.content,
|
|
598
|
+
contentType: att.contentType
|
|
599
|
+
})) || []
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
logger.log('error', `Error parsing email data: ${error.message}`);
|
|
604
|
+
throw new Error(`Error parsing email data: ${error.message}`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
email = emailData;
|
|
609
|
+
}
|
|
610
|
+
// Run inbound security verification (DKIM/SPF/DMARC) via Rust bridge
|
|
611
|
+
if (session.remoteAddress && session.remoteAddress !== '127.0.0.1') {
|
|
612
|
+
await this.verifyInboundSecurity(email, session);
|
|
613
|
+
}
|
|
614
|
+
// First check if this is a bounce notification email
|
|
615
|
+
const subject = email.subject || '';
|
|
616
|
+
const isBounceLike = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject);
|
|
617
|
+
if (isBounceLike) {
|
|
618
|
+
logger.log('info', `Email subject matches bounce notification pattern: "${subject}"`);
|
|
619
|
+
const isBounce = await this.processBounceNotification(email);
|
|
620
|
+
if (isBounce) {
|
|
621
|
+
logger.log('info', 'Successfully processed as bounce notification, skipping regular processing');
|
|
622
|
+
return email;
|
|
623
|
+
}
|
|
624
|
+
logger.log('info', 'Not a valid bounce notification, continuing with regular processing');
|
|
625
|
+
}
|
|
626
|
+
// Find matching route
|
|
627
|
+
const context = { email, session };
|
|
628
|
+
const route = await this.emailRouter.evaluateRoutes(context);
|
|
629
|
+
if (!route) {
|
|
630
|
+
throw new Error('No matching route for email');
|
|
631
|
+
}
|
|
632
|
+
// Store matched route in session
|
|
633
|
+
session.matchedRoute = route;
|
|
634
|
+
// Execute action based on route
|
|
635
|
+
await this.actionExecutor.executeAction(route.action, email, context);
|
|
636
|
+
// Return the processed email
|
|
637
|
+
return email;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Apply per-domain rate limits from domain configurations
|
|
641
|
+
*/
|
|
642
|
+
applyDomainRateLimits() {
|
|
643
|
+
const domainConfigs = this.domainRegistry.getAllConfigs();
|
|
644
|
+
for (const domainConfig of domainConfigs) {
|
|
645
|
+
if (domainConfig.rateLimits) {
|
|
646
|
+
const domain = domainConfig.domain;
|
|
647
|
+
const rateLimitConfig = {};
|
|
648
|
+
if (domainConfig.rateLimits.outbound) {
|
|
649
|
+
if (domainConfig.rateLimits.outbound.messagesPerMinute) {
|
|
650
|
+
rateLimitConfig.maxMessagesPerMinute = domainConfig.rateLimits.outbound.messagesPerMinute;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (domainConfig.rateLimits.inbound) {
|
|
654
|
+
if (domainConfig.rateLimits.inbound.messagesPerMinute) {
|
|
655
|
+
rateLimitConfig.maxMessagesPerMinute = domainConfig.rateLimits.inbound.messagesPerMinute;
|
|
656
|
+
}
|
|
657
|
+
if (domainConfig.rateLimits.inbound.connectionsPerIp) {
|
|
658
|
+
rateLimitConfig.maxConnectionsPerIP = domainConfig.rateLimits.inbound.connectionsPerIp;
|
|
659
|
+
}
|
|
660
|
+
if (domainConfig.rateLimits.inbound.recipientsPerMessage) {
|
|
661
|
+
rateLimitConfig.maxRecipientsPerMessage = domainConfig.rateLimits.inbound.recipientsPerMessage;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (Object.keys(rateLimitConfig).length > 0) {
|
|
665
|
+
this.rateLimiter.applyDomainLimits(domain, rateLimitConfig);
|
|
666
|
+
logger.log('info', `Applied rate limits for domain ${domain}:`, rateLimitConfig);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Generate SmartProxy routes for email ports
|
|
673
|
+
*/
|
|
674
|
+
generateProxyRoutes(portMapping) {
|
|
675
|
+
const routes = [];
|
|
676
|
+
const defaultPortMapping = {
|
|
677
|
+
25: 10025,
|
|
678
|
+
587: 10587,
|
|
679
|
+
465: 10465
|
|
680
|
+
};
|
|
681
|
+
const actualPortMapping = portMapping || defaultPortMapping;
|
|
682
|
+
for (const externalPort of this.options.ports) {
|
|
683
|
+
const internalPort = actualPortMapping[externalPort] || externalPort + 10000;
|
|
684
|
+
let routeName = 'email-route';
|
|
685
|
+
let tlsMode = 'passthrough';
|
|
686
|
+
switch (externalPort) {
|
|
687
|
+
case 25:
|
|
688
|
+
routeName = 'smtp-route';
|
|
689
|
+
tlsMode = 'passthrough';
|
|
690
|
+
break;
|
|
691
|
+
case 587:
|
|
692
|
+
routeName = 'submission-route';
|
|
693
|
+
tlsMode = 'passthrough';
|
|
694
|
+
break;
|
|
695
|
+
case 465:
|
|
696
|
+
routeName = 'smtps-route';
|
|
697
|
+
tlsMode = 'terminate';
|
|
698
|
+
break;
|
|
699
|
+
default:
|
|
700
|
+
routeName = `email-port-${externalPort}-route`;
|
|
701
|
+
}
|
|
702
|
+
routes.push({
|
|
703
|
+
name: routeName,
|
|
704
|
+
match: {
|
|
705
|
+
ports: [externalPort]
|
|
706
|
+
},
|
|
707
|
+
action: {
|
|
708
|
+
type: 'forward',
|
|
709
|
+
target: {
|
|
710
|
+
host: 'localhost',
|
|
711
|
+
port: internalPort
|
|
712
|
+
},
|
|
713
|
+
tls: {
|
|
714
|
+
mode: tlsMode
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
return routes;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Update server configuration
|
|
723
|
+
*/
|
|
724
|
+
updateOptions(options) {
|
|
725
|
+
const portsChanged = options.ports &&
|
|
726
|
+
(!this.options.ports ||
|
|
727
|
+
JSON.stringify(options.ports) !== JSON.stringify(this.options.ports));
|
|
728
|
+
if (portsChanged) {
|
|
729
|
+
this.stop().then(() => {
|
|
730
|
+
this.options = { ...this.options, ...options };
|
|
731
|
+
this.start();
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
this.options = { ...this.options, ...options };
|
|
736
|
+
if (options.domains) {
|
|
737
|
+
this.domainRegistry = new DomainRegistry(options.domains, options.defaults || this.options.defaults);
|
|
738
|
+
}
|
|
739
|
+
if (options.routes) {
|
|
740
|
+
this.emailRouter.updateRoutes(options.routes);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Update email routes
|
|
746
|
+
*/
|
|
747
|
+
updateEmailRoutes(routes) {
|
|
748
|
+
this.options.routes = routes;
|
|
749
|
+
this.emailRouter.updateRoutes(routes);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Get server statistics
|
|
753
|
+
*/
|
|
754
|
+
getStats() {
|
|
755
|
+
return { ...this.stats };
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Get domain registry
|
|
759
|
+
*/
|
|
760
|
+
getDomainRegistry() {
|
|
761
|
+
return this.domainRegistry;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Send an email through the delivery system
|
|
765
|
+
*/
|
|
766
|
+
async sendEmail(email, mode = 'mta', route, options) {
|
|
767
|
+
logger.log('info', `Sending email: ${email.subject} to ${email.to.join(', ')}`);
|
|
768
|
+
try {
|
|
769
|
+
if (!email.from) {
|
|
770
|
+
throw new Error('Email must have a sender address');
|
|
771
|
+
}
|
|
772
|
+
if (!email.to || email.to.length === 0) {
|
|
773
|
+
throw new Error('Email must have at least one recipient');
|
|
774
|
+
}
|
|
775
|
+
// Check if any recipients are on the suppression list
|
|
776
|
+
if (!options?.skipSuppressionCheck) {
|
|
777
|
+
const suppressedRecipients = email.to.filter(recipient => this.isEmailSuppressed(recipient));
|
|
778
|
+
if (suppressedRecipients.length > 0) {
|
|
779
|
+
const originalCount = email.to.length;
|
|
780
|
+
const suppressed = suppressedRecipients.map(recipient => {
|
|
781
|
+
const info = this.getSuppressionInfo(recipient);
|
|
782
|
+
return {
|
|
783
|
+
email: recipient,
|
|
784
|
+
reason: info?.reason || 'Unknown',
|
|
785
|
+
until: info?.expiresAt ? new Date(info.expiresAt).toISOString() : 'permanent'
|
|
786
|
+
};
|
|
787
|
+
});
|
|
788
|
+
logger.log('warn', `Filtering out ${suppressedRecipients.length} suppressed recipient(s)`, { suppressed });
|
|
789
|
+
if (suppressedRecipients.length === originalCount) {
|
|
790
|
+
throw new Error('All recipients are on the suppression list');
|
|
791
|
+
}
|
|
792
|
+
email.to = email.to.filter(recipient => !this.isEmailSuppressed(recipient));
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Sign with DKIM if configured
|
|
796
|
+
if (mode === 'mta' && route?.action.options?.mtaOptions?.dkimSign) {
|
|
797
|
+
const domain = email.from.split('@')[1];
|
|
798
|
+
await this.dkimManager.handleDkimSigning(email, domain, route.action.options.mtaOptions.dkimOptions?.keySelector || 'mta');
|
|
799
|
+
}
|
|
800
|
+
const id = plugins.uuid.v4();
|
|
801
|
+
await this.deliveryQueue.enqueue(email, mode, route);
|
|
802
|
+
logger.log('info', `Email queued with ID: ${id}`);
|
|
803
|
+
return id;
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
logger.log('error', `Failed to send email: ${error.message}`);
|
|
807
|
+
throw error;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// -----------------------------------------------------------------------
|
|
811
|
+
// Bounce/suppression methods
|
|
812
|
+
// -----------------------------------------------------------------------
|
|
813
|
+
async processBounceNotification(bounceEmail) {
|
|
814
|
+
logger.log('info', 'Processing potential bounce notification email');
|
|
815
|
+
try {
|
|
816
|
+
const bounceRecord = await this.bounceManager.processBounceEmail(bounceEmail);
|
|
817
|
+
if (bounceRecord) {
|
|
818
|
+
logger.log('info', `Successfully processed bounce notification for ${bounceRecord.recipient}`, {
|
|
819
|
+
bounceType: bounceRecord.bounceType,
|
|
820
|
+
bounceCategory: bounceRecord.bounceCategory
|
|
821
|
+
});
|
|
822
|
+
this.emit('bounceProcessed', bounceRecord);
|
|
823
|
+
SecurityLogger.getInstance().logEvent({
|
|
824
|
+
level: SecurityLogLevel.INFO,
|
|
825
|
+
type: SecurityEventType.EMAIL_VALIDATION,
|
|
826
|
+
message: `Bounce notification processed for recipient`,
|
|
827
|
+
domain: bounceRecord.domain,
|
|
828
|
+
details: {
|
|
829
|
+
recipient: bounceRecord.recipient,
|
|
830
|
+
bounceType: bounceRecord.bounceType,
|
|
831
|
+
bounceCategory: bounceRecord.bounceCategory
|
|
832
|
+
},
|
|
833
|
+
success: true
|
|
834
|
+
});
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
logger.log('info', 'Email not recognized as a bounce notification');
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
logger.log('error', `Error processing bounce notification: ${error.message}`);
|
|
844
|
+
SecurityLogger.getInstance().logEvent({
|
|
845
|
+
level: SecurityLogLevel.ERROR,
|
|
846
|
+
type: SecurityEventType.EMAIL_VALIDATION,
|
|
847
|
+
message: 'Failed to process bounce notification',
|
|
848
|
+
details: { error: error.message, subject: bounceEmail.subject },
|
|
849
|
+
success: false
|
|
850
|
+
});
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
async processSmtpFailure(recipient, smtpResponse, options = {}) {
|
|
855
|
+
logger.log('info', `Processing SMTP failure for ${recipient}: ${smtpResponse}`);
|
|
856
|
+
try {
|
|
857
|
+
const bounceRecord = await this.bounceManager.processSmtpFailure(recipient, smtpResponse, options);
|
|
858
|
+
logger.log('info', `Successfully processed SMTP failure for ${recipient} as ${bounceRecord.bounceCategory} bounce`, {
|
|
859
|
+
bounceType: bounceRecord.bounceType
|
|
860
|
+
});
|
|
861
|
+
this.emit('bounceProcessed', bounceRecord);
|
|
862
|
+
SecurityLogger.getInstance().logEvent({
|
|
863
|
+
level: SecurityLogLevel.INFO,
|
|
864
|
+
type: SecurityEventType.EMAIL_VALIDATION,
|
|
865
|
+
message: `SMTP failure processed for recipient`,
|
|
866
|
+
domain: bounceRecord.domain,
|
|
867
|
+
details: {
|
|
868
|
+
recipient: bounceRecord.recipient,
|
|
869
|
+
bounceType: bounceRecord.bounceType,
|
|
870
|
+
bounceCategory: bounceRecord.bounceCategory,
|
|
871
|
+
smtpResponse
|
|
872
|
+
},
|
|
873
|
+
success: true
|
|
874
|
+
});
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
878
|
+
logger.log('error', `Error processing SMTP failure: ${error.message}`);
|
|
879
|
+
SecurityLogger.getInstance().logEvent({
|
|
880
|
+
level: SecurityLogLevel.ERROR,
|
|
881
|
+
type: SecurityEventType.EMAIL_VALIDATION,
|
|
882
|
+
message: 'Failed to process SMTP failure',
|
|
883
|
+
details: { recipient, smtpResponse, error: error.message },
|
|
884
|
+
success: false
|
|
885
|
+
});
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
isEmailSuppressed(email) {
|
|
890
|
+
return this.bounceManager.isEmailSuppressed(email);
|
|
891
|
+
}
|
|
892
|
+
getSuppressionInfo(email) {
|
|
893
|
+
return this.bounceManager.getSuppressionInfo(email);
|
|
894
|
+
}
|
|
895
|
+
getBounceHistory(email) {
|
|
896
|
+
return this.bounceManager.getBounceInfo(email);
|
|
897
|
+
}
|
|
898
|
+
getSuppressionList() {
|
|
899
|
+
return this.bounceManager.getSuppressionList();
|
|
900
|
+
}
|
|
901
|
+
getHardBouncedAddresses() {
|
|
902
|
+
return this.bounceManager.getHardBouncedAddresses();
|
|
903
|
+
}
|
|
904
|
+
addToSuppressionList(email, reason, expiresAt) {
|
|
905
|
+
this.bounceManager.addToSuppressionList(email, reason, expiresAt);
|
|
906
|
+
logger.log('info', `Added ${email} to suppression list: ${reason}`);
|
|
907
|
+
}
|
|
908
|
+
removeFromSuppressionList(email) {
|
|
909
|
+
this.bounceManager.removeFromSuppressionList(email);
|
|
910
|
+
logger.log('info', `Removed ${email} from suppression list`);
|
|
911
|
+
}
|
|
912
|
+
recordBounce(domain, receivingDomain, bounceType, reason) {
|
|
913
|
+
const bounceRecord = {
|
|
914
|
+
id: `bounce_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
915
|
+
recipient: `user@${receivingDomain}`,
|
|
916
|
+
sender: `user@${domain}`,
|
|
917
|
+
domain: domain,
|
|
918
|
+
bounceType: bounceType === 'hard' ? BounceType.INVALID_RECIPIENT : BounceType.TEMPORARY_FAILURE,
|
|
919
|
+
bounceCategory: bounceType === 'hard' ? BounceCategory.HARD : BounceCategory.SOFT,
|
|
920
|
+
timestamp: Date.now(),
|
|
921
|
+
smtpResponse: reason,
|
|
922
|
+
diagnosticCode: reason,
|
|
923
|
+
statusCode: bounceType === 'hard' ? '550' : '450',
|
|
924
|
+
processed: false
|
|
925
|
+
};
|
|
926
|
+
this.bounceManager.processBounce(bounceRecord);
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Get the rate limiter instance
|
|
930
|
+
*/
|
|
931
|
+
getRateLimiter() {
|
|
932
|
+
return this.rateLimiter;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy51bmlmaWVkLmVtYWlsLnNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9jbGFzc2VzLnVuaWZpZWQuZW1haWwuc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQ0wsY0FBYyxFQUNkLGdCQUFnQixFQUNoQixpQkFBaUIsRUFDbEIsTUFBTSx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDakUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUFFbEYsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRXhELE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDOUQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxhQUFhLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBRTdGLE9BQU8sRUFBRSx1QkFBdUIsRUFBa0MsTUFBTSx3Q0FBd0MsQ0FBQztBQUNqSCxPQUFPLEVBQUUsb0JBQW9CLEVBQXNCLE1BQU0sdUNBQXVDLENBQUM7QUFDakcsT0FBTyxFQUFFLGtCQUFrQixFQUFnQyxNQUFNLDZDQUE2QyxDQUFDO0FBQy9HLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUV0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
|