@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,484 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import * as paths from '../paths.js';
|
|
3
|
+
import { logger } from '../logger.js';
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Bridge state machine
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export var BridgeState;
|
|
9
|
+
(function (BridgeState) {
|
|
10
|
+
BridgeState["Idle"] = "idle";
|
|
11
|
+
BridgeState["Starting"] = "starting";
|
|
12
|
+
BridgeState["Running"] = "running";
|
|
13
|
+
BridgeState["Restarting"] = "restarting";
|
|
14
|
+
BridgeState["Failed"] = "failed";
|
|
15
|
+
BridgeState["Stopped"] = "stopped";
|
|
16
|
+
})(BridgeState || (BridgeState = {}));
|
|
17
|
+
const DEFAULT_RESILIENCE_CONFIG = {
|
|
18
|
+
maxRestartAttempts: 5,
|
|
19
|
+
healthCheckIntervalMs: 30_000,
|
|
20
|
+
restartBackoffBaseMs: 1_000,
|
|
21
|
+
restartBackoffMaxMs: 30_000,
|
|
22
|
+
healthCheckTimeoutMs: 5_000,
|
|
23
|
+
};
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// RustSecurityBridge — singleton wrapper around smartrust.RustBridge
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* Bridge between TypeScript and the Rust `mailer-bin` binary.
|
|
29
|
+
*
|
|
30
|
+
* Uses `@push.rocks/smartrust` for JSON-over-stdin/stdout IPC.
|
|
31
|
+
* Singleton — access via `RustSecurityBridge.getInstance()`.
|
|
32
|
+
*
|
|
33
|
+
* Features resilience via auto-restart with exponential backoff,
|
|
34
|
+
* periodic health checks, and a state machine that tracks the
|
|
35
|
+
* bridge lifecycle.
|
|
36
|
+
*/
|
|
37
|
+
export class RustSecurityBridge extends EventEmitter {
|
|
38
|
+
static instance = null;
|
|
39
|
+
static _resilienceConfig = { ...DEFAULT_RESILIENCE_CONFIG };
|
|
40
|
+
bridge;
|
|
41
|
+
_running = false;
|
|
42
|
+
_state = BridgeState.Idle;
|
|
43
|
+
_restartAttempts = 0;
|
|
44
|
+
_restartTimer = null;
|
|
45
|
+
_healthCheckTimer = null;
|
|
46
|
+
_deliberateStop = false;
|
|
47
|
+
_smtpServerConfig = null;
|
|
48
|
+
constructor() {
|
|
49
|
+
super();
|
|
50
|
+
this.bridge = new plugins.smartrust.RustBridge({
|
|
51
|
+
binaryName: 'mailer-bin',
|
|
52
|
+
cliArgs: ['--management'],
|
|
53
|
+
requestTimeoutMs: 30_000,
|
|
54
|
+
readyTimeoutMs: 10_000,
|
|
55
|
+
localPaths: [
|
|
56
|
+
plugins.path.join(paths.packageDir, 'dist_rust', 'mailer-bin'),
|
|
57
|
+
plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'mailer-bin'),
|
|
58
|
+
plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'mailer-bin'),
|
|
59
|
+
],
|
|
60
|
+
searchSystemPath: false,
|
|
61
|
+
});
|
|
62
|
+
// Forward lifecycle events
|
|
63
|
+
this.bridge.on('ready', () => {
|
|
64
|
+
this._running = true;
|
|
65
|
+
logger.log('info', 'Rust security bridge is ready');
|
|
66
|
+
});
|
|
67
|
+
this.bridge.on('exit', (code, signal) => {
|
|
68
|
+
this._running = false;
|
|
69
|
+
logger.log('warn', `Rust security bridge exited (code=${code}, signal=${signal})`);
|
|
70
|
+
if (this._deliberateStop) {
|
|
71
|
+
this.setState(BridgeState.Stopped);
|
|
72
|
+
}
|
|
73
|
+
else if (this._state === BridgeState.Running) {
|
|
74
|
+
// Unexpected exit — attempt restart
|
|
75
|
+
this.attemptRestart();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
this.bridge.on('stderr', (line) => {
|
|
79
|
+
logger.log('debug', `[rust-bridge] ${line}`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// -----------------------------------------------------------------------
|
|
83
|
+
// Static configuration & singleton
|
|
84
|
+
// -----------------------------------------------------------------------
|
|
85
|
+
/** Get or create the singleton instance. */
|
|
86
|
+
static getInstance() {
|
|
87
|
+
if (!RustSecurityBridge.instance) {
|
|
88
|
+
RustSecurityBridge.instance = new RustSecurityBridge();
|
|
89
|
+
}
|
|
90
|
+
return RustSecurityBridge.instance;
|
|
91
|
+
}
|
|
92
|
+
/** Reset the singleton instance (for testing). */
|
|
93
|
+
static resetInstance() {
|
|
94
|
+
if (RustSecurityBridge.instance) {
|
|
95
|
+
RustSecurityBridge.instance.stopHealthCheck();
|
|
96
|
+
if (RustSecurityBridge.instance._restartTimer) {
|
|
97
|
+
clearTimeout(RustSecurityBridge.instance._restartTimer);
|
|
98
|
+
RustSecurityBridge.instance._restartTimer = null;
|
|
99
|
+
}
|
|
100
|
+
RustSecurityBridge.instance.removeAllListeners();
|
|
101
|
+
}
|
|
102
|
+
RustSecurityBridge.instance = null;
|
|
103
|
+
}
|
|
104
|
+
/** Configure resilience parameters. Can be called before or after getInstance(). */
|
|
105
|
+
static configure(config) {
|
|
106
|
+
RustSecurityBridge._resilienceConfig = {
|
|
107
|
+
...RustSecurityBridge._resilienceConfig,
|
|
108
|
+
...config,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// -----------------------------------------------------------------------
|
|
112
|
+
// State management
|
|
113
|
+
// -----------------------------------------------------------------------
|
|
114
|
+
/** Current bridge state. */
|
|
115
|
+
get state() {
|
|
116
|
+
return this._state;
|
|
117
|
+
}
|
|
118
|
+
/** Whether the Rust process is currently running and accepting commands. */
|
|
119
|
+
get running() {
|
|
120
|
+
return this._running;
|
|
121
|
+
}
|
|
122
|
+
setState(newState) {
|
|
123
|
+
const oldState = this._state;
|
|
124
|
+
if (oldState === newState)
|
|
125
|
+
return;
|
|
126
|
+
this._state = newState;
|
|
127
|
+
logger.log('info', `Rust bridge state: ${oldState} -> ${newState}`);
|
|
128
|
+
this.emit('stateChange', { oldState, newState });
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Throws a descriptive error if the bridge is not in Running state.
|
|
132
|
+
* Called at the top of every command method.
|
|
133
|
+
*/
|
|
134
|
+
ensureRunning() {
|
|
135
|
+
if (this._state === BridgeState.Running && this._running) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
switch (this._state) {
|
|
139
|
+
case BridgeState.Idle:
|
|
140
|
+
throw new Error('Rust bridge has not been started yet. Call start() first.');
|
|
141
|
+
case BridgeState.Starting:
|
|
142
|
+
throw new Error('Rust bridge is still starting. Wait for start() to resolve.');
|
|
143
|
+
case BridgeState.Restarting:
|
|
144
|
+
throw new Error('Rust bridge is restarting after a crash. Commands will resume once it recovers.');
|
|
145
|
+
case BridgeState.Failed:
|
|
146
|
+
throw new Error('Rust bridge has failed after exhausting all restart attempts.');
|
|
147
|
+
case BridgeState.Stopped:
|
|
148
|
+
throw new Error('Rust bridge has been stopped. Call start() to restart it.');
|
|
149
|
+
default:
|
|
150
|
+
throw new Error(`Rust bridge is not running (state=${this._state}).`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// -----------------------------------------------------------------------
|
|
154
|
+
// Lifecycle
|
|
155
|
+
// -----------------------------------------------------------------------
|
|
156
|
+
/**
|
|
157
|
+
* Spawn the Rust binary and wait for the ready signal.
|
|
158
|
+
* @returns `true` if the binary started successfully, `false` otherwise.
|
|
159
|
+
*/
|
|
160
|
+
async start() {
|
|
161
|
+
if (this._running && this._state === BridgeState.Running) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
this._deliberateStop = false;
|
|
165
|
+
this._restartAttempts = 0;
|
|
166
|
+
this.setState(BridgeState.Starting);
|
|
167
|
+
try {
|
|
168
|
+
const ok = await this.bridge.spawn();
|
|
169
|
+
this._running = ok;
|
|
170
|
+
if (ok) {
|
|
171
|
+
this.setState(BridgeState.Running);
|
|
172
|
+
this.startHealthCheck();
|
|
173
|
+
logger.log('info', 'Rust security bridge started');
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
this.setState(BridgeState.Failed);
|
|
177
|
+
logger.log('warn', 'Rust security bridge failed to start (binary not found or timeout)');
|
|
178
|
+
}
|
|
179
|
+
return ok;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
this.setState(BridgeState.Failed);
|
|
183
|
+
logger.log('error', `Failed to start Rust security bridge: ${err.message}`);
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Kill the Rust process deliberately. */
|
|
188
|
+
async stop() {
|
|
189
|
+
this._deliberateStop = true;
|
|
190
|
+
// Cancel any pending restart
|
|
191
|
+
if (this._restartTimer) {
|
|
192
|
+
clearTimeout(this._restartTimer);
|
|
193
|
+
this._restartTimer = null;
|
|
194
|
+
}
|
|
195
|
+
this.stopHealthCheck();
|
|
196
|
+
this._smtpServerConfig = null;
|
|
197
|
+
if (!this._running) {
|
|
198
|
+
this.setState(BridgeState.Stopped);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
this.bridge.kill();
|
|
203
|
+
this._running = false;
|
|
204
|
+
this.setState(BridgeState.Stopped);
|
|
205
|
+
logger.log('info', 'Rust security bridge stopped');
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
logger.log('error', `Error stopping Rust security bridge: ${err.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// -----------------------------------------------------------------------
|
|
212
|
+
// Auto-restart with exponential backoff
|
|
213
|
+
// -----------------------------------------------------------------------
|
|
214
|
+
attemptRestart() {
|
|
215
|
+
const config = RustSecurityBridge._resilienceConfig;
|
|
216
|
+
this._restartAttempts++;
|
|
217
|
+
if (this._restartAttempts > config.maxRestartAttempts) {
|
|
218
|
+
logger.log('error', `Rust bridge exceeded max restart attempts (${config.maxRestartAttempts}). Giving up.`);
|
|
219
|
+
this.setState(BridgeState.Failed);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
this.setState(BridgeState.Restarting);
|
|
223
|
+
this.stopHealthCheck();
|
|
224
|
+
const delay = Math.min(config.restartBackoffBaseMs * Math.pow(2, this._restartAttempts - 1), config.restartBackoffMaxMs);
|
|
225
|
+
logger.log('info', `Rust bridge restart attempt ${this._restartAttempts}/${config.maxRestartAttempts} in ${delay}ms`);
|
|
226
|
+
this._restartTimer = setTimeout(async () => {
|
|
227
|
+
this._restartTimer = null;
|
|
228
|
+
// Guard: if stop() was called while we were waiting, don't restart
|
|
229
|
+
if (this._deliberateStop) {
|
|
230
|
+
this.setState(BridgeState.Stopped);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const ok = await this.bridge.spawn();
|
|
235
|
+
this._running = ok;
|
|
236
|
+
if (ok) {
|
|
237
|
+
logger.log('info', 'Rust bridge restarted successfully');
|
|
238
|
+
this._restartAttempts = 0;
|
|
239
|
+
this.setState(BridgeState.Running);
|
|
240
|
+
this.startHealthCheck();
|
|
241
|
+
await this.restoreAfterRestart();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
logger.log('warn', 'Rust bridge restart failed (spawn returned false)');
|
|
245
|
+
this.attemptRestart();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
logger.log('error', `Rust bridge restart failed: ${err.message}`);
|
|
250
|
+
this.attemptRestart();
|
|
251
|
+
}
|
|
252
|
+
}, delay);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Restore state after a successful restart:
|
|
256
|
+
* - Re-send startSmtpServer command if the SMTP server was running
|
|
257
|
+
*/
|
|
258
|
+
async restoreAfterRestart() {
|
|
259
|
+
if (this._smtpServerConfig) {
|
|
260
|
+
try {
|
|
261
|
+
logger.log('info', 'Restoring SMTP server after bridge restart');
|
|
262
|
+
const result = await this.bridge.sendCommand('startSmtpServer', this._smtpServerConfig);
|
|
263
|
+
if (result?.started) {
|
|
264
|
+
logger.log('info', 'SMTP server restored after bridge restart');
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
logger.log('warn', 'SMTP server failed to restore after bridge restart');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
logger.log('error', `Failed to restore SMTP server after restart: ${err.message}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// -----------------------------------------------------------------------
|
|
276
|
+
// Health check
|
|
277
|
+
// -----------------------------------------------------------------------
|
|
278
|
+
startHealthCheck() {
|
|
279
|
+
this.stopHealthCheck();
|
|
280
|
+
const config = RustSecurityBridge._resilienceConfig;
|
|
281
|
+
this._healthCheckTimer = setInterval(async () => {
|
|
282
|
+
if (this._state !== BridgeState.Running || !this._running) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const pongPromise = this.bridge.sendCommand('ping', {});
|
|
287
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), config.healthCheckTimeoutMs));
|
|
288
|
+
const res = await Promise.race([pongPromise, timeoutPromise]);
|
|
289
|
+
if (!res?.pong) {
|
|
290
|
+
throw new Error('Health check: unexpected ping response');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
logger.log('warn', `Rust bridge health check failed: ${err.message}. Killing process to trigger restart.`);
|
|
295
|
+
try {
|
|
296
|
+
this.bridge.kill();
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Already dead
|
|
300
|
+
}
|
|
301
|
+
// The exit handler will trigger attemptRestart()
|
|
302
|
+
}
|
|
303
|
+
}, config.healthCheckIntervalMs);
|
|
304
|
+
}
|
|
305
|
+
stopHealthCheck() {
|
|
306
|
+
if (this._healthCheckTimer) {
|
|
307
|
+
clearInterval(this._healthCheckTimer);
|
|
308
|
+
this._healthCheckTimer = null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// -----------------------------------------------------------------------
|
|
312
|
+
// Commands — thin typed wrappers over sendCommand
|
|
313
|
+
// -----------------------------------------------------------------------
|
|
314
|
+
/** Ping the Rust process. */
|
|
315
|
+
async ping() {
|
|
316
|
+
this.ensureRunning();
|
|
317
|
+
const res = await this.bridge.sendCommand('ping', {});
|
|
318
|
+
return res?.pong === true;
|
|
319
|
+
}
|
|
320
|
+
/** Get version information for all Rust crates. */
|
|
321
|
+
async getVersion() {
|
|
322
|
+
this.ensureRunning();
|
|
323
|
+
return this.bridge.sendCommand('version', {});
|
|
324
|
+
}
|
|
325
|
+
/** Validate an email address. */
|
|
326
|
+
async validateEmail(email) {
|
|
327
|
+
this.ensureRunning();
|
|
328
|
+
return this.bridge.sendCommand('validateEmail', { email });
|
|
329
|
+
}
|
|
330
|
+
/** Detect bounce type from SMTP response / diagnostic code. */
|
|
331
|
+
async detectBounce(opts) {
|
|
332
|
+
this.ensureRunning();
|
|
333
|
+
return this.bridge.sendCommand('detectBounce', opts);
|
|
334
|
+
}
|
|
335
|
+
/** Scan email content for threats (phishing, spam, malware, etc.). */
|
|
336
|
+
async scanContent(opts) {
|
|
337
|
+
this.ensureRunning();
|
|
338
|
+
return this.bridge.sendCommand('scanContent', opts);
|
|
339
|
+
}
|
|
340
|
+
/** Check IP reputation via DNSBL. */
|
|
341
|
+
async checkIpReputation(ip) {
|
|
342
|
+
this.ensureRunning();
|
|
343
|
+
return this.bridge.sendCommand('checkIpReputation', { ip });
|
|
344
|
+
}
|
|
345
|
+
/** Verify DKIM signatures on a raw email message. */
|
|
346
|
+
async verifyDkim(rawMessage) {
|
|
347
|
+
this.ensureRunning();
|
|
348
|
+
return this.bridge.sendCommand('verifyDkim', { rawMessage });
|
|
349
|
+
}
|
|
350
|
+
/** Sign an email with DKIM (RSA or Ed25519). */
|
|
351
|
+
async signDkim(opts) {
|
|
352
|
+
this.ensureRunning();
|
|
353
|
+
return this.bridge.sendCommand('signDkim', opts);
|
|
354
|
+
}
|
|
355
|
+
/** Check SPF for a sender. */
|
|
356
|
+
async checkSpf(opts) {
|
|
357
|
+
this.ensureRunning();
|
|
358
|
+
return this.bridge.sendCommand('checkSpf', opts);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Compound email security verification: DKIM + SPF + DMARC in one IPC call.
|
|
362
|
+
*
|
|
363
|
+
* This is the preferred method for inbound email verification — it avoids
|
|
364
|
+
* 3 sequential round-trips and correctly passes raw mail-auth types internally.
|
|
365
|
+
*/
|
|
366
|
+
async verifyEmail(opts) {
|
|
367
|
+
this.ensureRunning();
|
|
368
|
+
return this.bridge.sendCommand('verifyEmail', opts);
|
|
369
|
+
}
|
|
370
|
+
// -----------------------------------------------------------------------
|
|
371
|
+
// SMTP Client — outbound email delivery via Rust
|
|
372
|
+
// -----------------------------------------------------------------------
|
|
373
|
+
/** Send a structured email via the Rust SMTP client. */
|
|
374
|
+
async sendOutboundEmail(opts) {
|
|
375
|
+
this.ensureRunning();
|
|
376
|
+
return this.bridge.sendCommand('sendEmail', opts);
|
|
377
|
+
}
|
|
378
|
+
/** Send a pre-formatted raw email via the Rust SMTP client. */
|
|
379
|
+
async sendRawEmail(opts) {
|
|
380
|
+
this.ensureRunning();
|
|
381
|
+
return this.bridge.sendCommand('sendRawEmail', opts);
|
|
382
|
+
}
|
|
383
|
+
/** Verify connectivity to an SMTP server. */
|
|
384
|
+
async verifySmtpConnection(opts) {
|
|
385
|
+
this.ensureRunning();
|
|
386
|
+
return this.bridge.sendCommand('verifySmtpConnection', opts);
|
|
387
|
+
}
|
|
388
|
+
/** Close a specific connection pool (or all pools if no key is given). */
|
|
389
|
+
async closeSmtpPool(poolKey) {
|
|
390
|
+
this.ensureRunning();
|
|
391
|
+
await this.bridge.sendCommand('closeSmtpPool', poolKey ? { poolKey } : {});
|
|
392
|
+
}
|
|
393
|
+
/** Get status of all SMTP client connection pools. */
|
|
394
|
+
async getSmtpPoolStatus() {
|
|
395
|
+
this.ensureRunning();
|
|
396
|
+
return this.bridge.sendCommand('getSmtpPoolStatus', {});
|
|
397
|
+
}
|
|
398
|
+
// -----------------------------------------------------------------------
|
|
399
|
+
// SMTP Server lifecycle
|
|
400
|
+
// -----------------------------------------------------------------------
|
|
401
|
+
/**
|
|
402
|
+
* Start the Rust SMTP server.
|
|
403
|
+
* The server will listen on the configured ports and emit events for
|
|
404
|
+
* emailReceived and authRequest that must be handled by the caller.
|
|
405
|
+
*/
|
|
406
|
+
async startSmtpServer(config) {
|
|
407
|
+
this.ensureRunning();
|
|
408
|
+
this._smtpServerConfig = config;
|
|
409
|
+
const result = await this.bridge.sendCommand('startSmtpServer', config);
|
|
410
|
+
return result?.started === true;
|
|
411
|
+
}
|
|
412
|
+
/** Stop the Rust SMTP server. */
|
|
413
|
+
async stopSmtpServer() {
|
|
414
|
+
this.ensureRunning();
|
|
415
|
+
this._smtpServerConfig = null;
|
|
416
|
+
await this.bridge.sendCommand('stopSmtpServer', {});
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Send the result of email processing back to the Rust SMTP server.
|
|
420
|
+
* This resolves a pending correlation-ID callback, allowing the Rust
|
|
421
|
+
* server to send the SMTP response to the client.
|
|
422
|
+
*/
|
|
423
|
+
async sendEmailProcessingResult(opts) {
|
|
424
|
+
this.ensureRunning();
|
|
425
|
+
await this.bridge.sendCommand('emailProcessingResult', opts);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Send the result of authentication validation back to the Rust SMTP server.
|
|
429
|
+
*/
|
|
430
|
+
async sendAuthResult(opts) {
|
|
431
|
+
this.ensureRunning();
|
|
432
|
+
await this.bridge.sendCommand('authResult', opts);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Send SCRAM credentials back to the Rust SMTP server.
|
|
436
|
+
* Values (salt, storedKey, serverKey) must be base64-encoded.
|
|
437
|
+
*/
|
|
438
|
+
async sendScramCredentialResult(opts) {
|
|
439
|
+
this.ensureRunning();
|
|
440
|
+
await this.bridge.sendCommand('scramCredentialResult', opts);
|
|
441
|
+
}
|
|
442
|
+
/** Update rate limit configuration at runtime. */
|
|
443
|
+
async configureRateLimits(config) {
|
|
444
|
+
this.ensureRunning();
|
|
445
|
+
await this.bridge.sendCommand('configureRateLimits', config);
|
|
446
|
+
}
|
|
447
|
+
// -----------------------------------------------------------------------
|
|
448
|
+
// Event registration — delegates to the underlying bridge EventEmitter
|
|
449
|
+
// -----------------------------------------------------------------------
|
|
450
|
+
/**
|
|
451
|
+
* Register a handler for emailReceived events from the Rust SMTP server.
|
|
452
|
+
* These events fire when a complete email has been received and needs processing.
|
|
453
|
+
*/
|
|
454
|
+
onEmailReceived(handler) {
|
|
455
|
+
this.bridge.on('management:emailReceived', handler);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Register a handler for authRequest events from the Rust SMTP server.
|
|
459
|
+
* The handler must call sendAuthResult() with the correlationId.
|
|
460
|
+
*/
|
|
461
|
+
onAuthRequest(handler) {
|
|
462
|
+
this.bridge.on('management:authRequest', handler);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Register a handler for scramCredentialRequest events from the Rust SMTP server.
|
|
466
|
+
* The handler must call sendScramCredentialResult() with the correlationId.
|
|
467
|
+
*/
|
|
468
|
+
onScramCredentialRequest(handler) {
|
|
469
|
+
this.bridge.on('management:scramCredentialRequest', handler);
|
|
470
|
+
}
|
|
471
|
+
/** Remove an emailReceived event handler. */
|
|
472
|
+
offEmailReceived(handler) {
|
|
473
|
+
this.bridge.off('management:emailReceived', handler);
|
|
474
|
+
}
|
|
475
|
+
/** Remove an authRequest event handler. */
|
|
476
|
+
offAuthRequest(handler) {
|
|
477
|
+
this.bridge.off('management:authRequest', handler);
|
|
478
|
+
}
|
|
479
|
+
/** Remove a scramCredentialRequest event handler. */
|
|
480
|
+
offScramCredentialRequest(handler) {
|
|
481
|
+
this.bridge.off('management:scramCredentialRequest', handler);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0c2VjdXJpdHlicmlkZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9zZWN1cml0eS9jbGFzc2VzLnJ1c3RzZWN1cml0eWJyaWRnZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEtBQUssS0FBSyxNQUFNLGFBQWEsQ0FBQztBQUNyQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUE4VHRDLDhFQUE4RTtBQUM5RSx1QkFBdUI7QUFDdkIsOEVBQThFO0FBRTlFLE1BQU0sQ0FBTixJQUFZLFdBT1g7QUFQRCxXQUFZLFdBQVc7SUFDckIsNEJBQWEsQ0FBQTtJQUNiLG9DQUFxQixDQUFBO0lBQ3JCLGtDQUFtQixDQUFBO0lBQ25CLHdDQUF5QixDQUFBO0lBQ3pCLGdDQUFpQixDQUFBO0lBQ2pCLGtDQUFtQixDQUFBO0FBQ3JCLENBQUMsRUFQVyxXQUFXLEtBQVgsV0FBVyxRQU90QjtBQVVELE1BQU0seUJBQXlCLEdBQTRCO0lBQ3pELGtCQUFrQixFQUFFLENBQUM7SUFDckIscUJBQXFCLEVBQUUsTUFBTTtJQUM3QixvQkFBb0IsRUFBRSxLQUFLO0lBQzNCLG1CQUFtQixFQUFFLE1BQU07SUFDM0Isb0JBQW9CLEVBQUUsS0FBSztDQUM1QixDQUFDO0FBRUYsOEVBQThFO0FBQzlFLHFFQUFxRTtBQUNyRSw4RUFBOEU7QUFFOUU7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBTSxPQUFPLGtCQUFtQixTQUFRLFlBQVk7SUFDMUMsTUFBTSxDQUFDLFFBQVEsR0FBOEIsSUFBSSxDQUFDO0lBQ2xELE1BQU0sQ0FBQyxpQkFBaUIsR0FBNEIsRUFBRSxHQUFHLHlCQUF5QixFQUFFLENBQUM7SUFFckYsTUFBTSxDQUFxRTtJQUMzRSxRQUFRLEdBQUcsS0FBSyxD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
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log level for security events
|
|
3
|
+
*/
|
|
4
|
+
export declare enum SecurityLogLevel {
|
|
5
|
+
INFO = "info",
|
|
6
|
+
WARN = "warn",
|
|
7
|
+
ERROR = "error",
|
|
8
|
+
CRITICAL = "critical"
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Security event types for categorization
|
|
12
|
+
*/
|
|
13
|
+
export declare enum SecurityEventType {
|
|
14
|
+
AUTHENTICATION = "authentication",
|
|
15
|
+
ACCESS_CONTROL = "access_control",
|
|
16
|
+
EMAIL_VALIDATION = "email_validation",
|
|
17
|
+
EMAIL_PROCESSING = "email_processing",
|
|
18
|
+
EMAIL_FORWARDING = "email_forwarding",
|
|
19
|
+
EMAIL_DELIVERY = "email_delivery",
|
|
20
|
+
DKIM = "dkim",
|
|
21
|
+
SPF = "spf",
|
|
22
|
+
DMARC = "dmarc",
|
|
23
|
+
RATE_LIMIT = "rate_limit",
|
|
24
|
+
RATE_LIMITING = "rate_limiting",
|
|
25
|
+
SPAM = "spam",
|
|
26
|
+
MALWARE = "malware",
|
|
27
|
+
CONNECTION = "connection",
|
|
28
|
+
DATA_EXPOSURE = "data_exposure",
|
|
29
|
+
CONFIGURATION = "configuration",
|
|
30
|
+
IP_REPUTATION = "ip_reputation",
|
|
31
|
+
REJECTED_CONNECTION = "rejected_connection"
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Security event interface
|
|
35
|
+
*/
|
|
36
|
+
export interface ISecurityEvent {
|
|
37
|
+
timestamp: number;
|
|
38
|
+
level: SecurityLogLevel;
|
|
39
|
+
type: SecurityEventType;
|
|
40
|
+
message: string;
|
|
41
|
+
details?: any;
|
|
42
|
+
ipAddress?: string;
|
|
43
|
+
userId?: string;
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
emailId?: string;
|
|
46
|
+
domain?: string;
|
|
47
|
+
action?: string;
|
|
48
|
+
result?: string;
|
|
49
|
+
success?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Security logger for enhanced security monitoring
|
|
53
|
+
*/
|
|
54
|
+
export declare class SecurityLogger {
|
|
55
|
+
private static instance;
|
|
56
|
+
private securityEvents;
|
|
57
|
+
private maxEventHistory;
|
|
58
|
+
private enableNotifications;
|
|
59
|
+
private constructor();
|
|
60
|
+
/**
|
|
61
|
+
* Get singleton instance
|
|
62
|
+
*/
|
|
63
|
+
static getInstance(options?: {
|
|
64
|
+
maxEventHistory?: number;
|
|
65
|
+
enableNotifications?: boolean;
|
|
66
|
+
}): SecurityLogger;
|
|
67
|
+
/**
|
|
68
|
+
* Log a security event
|
|
69
|
+
* @param event The security event to log
|
|
70
|
+
*/
|
|
71
|
+
logEvent(event: Omit<ISecurityEvent, 'timestamp'>): void;
|
|
72
|
+
/**
|
|
73
|
+
* Get recent security events
|
|
74
|
+
* @param limit Maximum number of events to return
|
|
75
|
+
* @param filter Filter for specific event types
|
|
76
|
+
* @returns Recent security events
|
|
77
|
+
*/
|
|
78
|
+
getRecentEvents(limit?: number, filter?: {
|
|
79
|
+
level?: SecurityLogLevel;
|
|
80
|
+
type?: SecurityEventType;
|
|
81
|
+
fromTimestamp?: number;
|
|
82
|
+
toTimestamp?: number;
|
|
83
|
+
}): ISecurityEvent[];
|
|
84
|
+
/**
|
|
85
|
+
* Get events by security level
|
|
86
|
+
* @param level The security level to filter by
|
|
87
|
+
* @param limit Maximum number of events to return
|
|
88
|
+
* @returns Security events matching the level
|
|
89
|
+
*/
|
|
90
|
+
getEventsByLevel(level: SecurityLogLevel, limit?: number): ISecurityEvent[];
|
|
91
|
+
/**
|
|
92
|
+
* Get events by security type
|
|
93
|
+
* @param type The event type to filter by
|
|
94
|
+
* @param limit Maximum number of events to return
|
|
95
|
+
* @returns Security events matching the type
|
|
96
|
+
*/
|
|
97
|
+
getEventsByType(type: SecurityEventType, limit?: number): ISecurityEvent[];
|
|
98
|
+
/**
|
|
99
|
+
* Get security events for a specific IP address
|
|
100
|
+
* @param ipAddress The IP address to filter by
|
|
101
|
+
* @param limit Maximum number of events to return
|
|
102
|
+
* @returns Security events for the IP address
|
|
103
|
+
*/
|
|
104
|
+
getEventsByIP(ipAddress: string, limit?: number): ISecurityEvent[];
|
|
105
|
+
/**
|
|
106
|
+
* Get security events for a specific domain
|
|
107
|
+
* @param domain The domain to filter by
|
|
108
|
+
* @param limit Maximum number of events to return
|
|
109
|
+
* @returns Security events for the domain
|
|
110
|
+
*/
|
|
111
|
+
getEventsByDomain(domain: string, limit?: number): ISecurityEvent[];
|
|
112
|
+
/**
|
|
113
|
+
* Send a notification for critical security events
|
|
114
|
+
* @param event The security event to notify about
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
private sendNotification;
|
|
118
|
+
/**
|
|
119
|
+
* Clear event history
|
|
120
|
+
*/
|
|
121
|
+
clearEvents(): void;
|
|
122
|
+
/**
|
|
123
|
+
* Get statistical summary of security events
|
|
124
|
+
* @param timeWindow Optional time window in milliseconds
|
|
125
|
+
* @returns Summary of security events
|
|
126
|
+
*/
|
|
127
|
+
getEventsSummary(timeWindow?: number): {
|
|
128
|
+
total: number;
|
|
129
|
+
byLevel: Record<SecurityLogLevel, number>;
|
|
130
|
+
byType: Record<SecurityEventType, number>;
|
|
131
|
+
topIPs: Array<{
|
|
132
|
+
ip: string;
|
|
133
|
+
count: number;
|
|
134
|
+
}>;
|
|
135
|
+
topDomains: Array<{
|
|
136
|
+
domain: string;
|
|
137
|
+
count: number;
|
|
138
|
+
}>;
|
|
139
|
+
};
|
|
140
|
+
}
|