@push.rocks/smartmta 13.1.0 → 14.0.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 +33 -0
- package/dist_rust/mailer-bin_linux_amd64_musl +0 -0
- package/dist_rust/mailer-bin_linux_amd64_musl.third-party-notices.json +0 -51
- package/dist_rust/mailer-bin_linux_amd64_musl.third-party-notices.txt +1 -291
- package/dist_rust/mailer-bin_linux_amd64_musl.tsrust-build.json +5 -5
- package/dist_rust/mailer-bin_linux_arm64_musl +0 -0
- package/dist_rust/mailer-bin_linux_arm64_musl.third-party-notices.json +0 -51
- package/dist_rust/mailer-bin_linux_arm64_musl.third-party-notices.txt +1 -291
- package/dist_rust/mailer-bin_linux_arm64_musl.tsrust-build.json +5 -5
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/mail/routing/classes.unified.email.server.d.ts +17 -14
- package/dist_ts/mail/routing/classes.unified.email.server.js +80 -71
- package/dist_ts/security/classes.rustsecuritybridge.d.ts +108 -1
- package/dist_ts/security/classes.rustsecuritybridge.js +187 -50
- package/native-notices/manifest.json +1 -64
- package/native-notices/readme.md +3 -3
- package/package.json +9 -10
- package/readme.md +20 -12
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/mail/routing/classes.unified.email.server.ts +82 -75
- package/ts/security/classes.rustsecuritybridge.ts +264 -49
- package/native-notices/crates/charset-0.1.5/copyright +0 -12
- package/native-notices/crates/charset-0.1.5/license-apache +0 -202
- package/native-notices/crates/charset-0.1.5/license-mit +0 -25
- package/native-notices/crates/mailparse-0.16.1/license +0 -13
- package/native-notices/crates/quoted_printable-0.5.2/license +0 -13
|
@@ -546,6 +546,66 @@ interface IEmailData {
|
|
|
546
546
|
base64: string;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
/** A mailbox of the parsed-message bridge JSON. */
|
|
550
|
+
export interface IBridgeMailAddress {
|
|
551
|
+
email: string;
|
|
552
|
+
name?: string;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** The header fields of an inline nested message (never produced with smartmta's setting). */
|
|
556
|
+
export interface IBridgeMessageHeader {
|
|
557
|
+
from: IBridgeMailAddress[];
|
|
558
|
+
to: IBridgeMailAddress[];
|
|
559
|
+
cc: IBridgeMailAddress[];
|
|
560
|
+
bcc: IBridgeMailAddress[];
|
|
561
|
+
subject?: string;
|
|
562
|
+
date?: string;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** One body group: the text and HTML sections of one multipart/alternative or one body part. */
|
|
566
|
+
export interface IBridgeMailBodyGroup {
|
|
567
|
+
text?: string;
|
|
568
|
+
html?: string;
|
|
569
|
+
messageHeader?: IBridgeMessageHeader;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface IBridgeParsedAttachment {
|
|
573
|
+
filename?: string;
|
|
574
|
+
contentType: string;
|
|
575
|
+
disposition?: 'attachment' | 'inline';
|
|
576
|
+
contentId?: string;
|
|
577
|
+
/** Standard base64 of the transfer-decoded bytes. */
|
|
578
|
+
content: string;
|
|
579
|
+
size: number;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* A message as the Rust mailer-mime crate parsed it (docs/cleanroom/mailer-mime.md section 2.3).
|
|
584
|
+
* Each body travels once, in `bodyGroups`; the flattened From list is the concatenation of
|
|
585
|
+
* `fromFields`.
|
|
586
|
+
*/
|
|
587
|
+
export interface IBridgeParsedMessage {
|
|
588
|
+
headers: Array<{ key: string; name: string; value: string }>;
|
|
589
|
+
headerLines: Array<{ key: string; line: string }>;
|
|
590
|
+
messageId?: string;
|
|
591
|
+
inReplyTo?: string;
|
|
592
|
+
references: string[];
|
|
593
|
+
/** ISO 8601 UTC with milliseconds. */
|
|
594
|
+
date?: string;
|
|
595
|
+
subject?: string;
|
|
596
|
+
sender?: IBridgeMailAddress;
|
|
597
|
+
replyTo: IBridgeMailAddress[];
|
|
598
|
+
to: IBridgeMailAddress[];
|
|
599
|
+
cc: IBridgeMailAddress[];
|
|
600
|
+
bcc: IBridgeMailAddress[];
|
|
601
|
+
bodyGroups: IBridgeMailBodyGroup[];
|
|
602
|
+
attachments: IBridgeParsedAttachment[];
|
|
603
|
+
/** One flattened mailbox list per From field, in field order. */
|
|
604
|
+
fromFields: IBridgeMailAddress[][];
|
|
605
|
+
partFilenames: string[];
|
|
606
|
+
abruptHeaderEnd: boolean;
|
|
607
|
+
}
|
|
608
|
+
|
|
549
609
|
interface IEmailReceivedEvent {
|
|
550
610
|
correlationId: string;
|
|
551
611
|
sessionId: string;
|
|
@@ -557,6 +617,8 @@ interface IEmailReceivedEvent {
|
|
|
557
617
|
secure: boolean;
|
|
558
618
|
authenticatedUser: string | null;
|
|
559
619
|
securityResults: IInboundEmailSecurityResult | null;
|
|
620
|
+
/** The one parse Rust made at DATA. */
|
|
621
|
+
parsed: IBridgeParsedMessage;
|
|
560
622
|
}
|
|
561
623
|
|
|
562
624
|
interface IRcptToRequestEvent {
|
|
@@ -641,6 +703,10 @@ type TMailerCommands = {
|
|
|
641
703
|
};
|
|
642
704
|
result: IEmailSecurityResult;
|
|
643
705
|
};
|
|
706
|
+
parseMessage: {
|
|
707
|
+
params: { data: string };
|
|
708
|
+
result: { parsed: IBridgeParsedMessage };
|
|
709
|
+
};
|
|
644
710
|
startSmtpServer: {
|
|
645
711
|
params: ISmtpServerConfig;
|
|
646
712
|
result: { started: boolean };
|
|
@@ -726,6 +792,8 @@ type TMailerCommands = {
|
|
|
726
792
|
};
|
|
727
793
|
};
|
|
728
794
|
|
|
795
|
+
type TMailerBridge = InstanceType<typeof plugins.smartrust.RustBridge<TMailerCommands>>;
|
|
796
|
+
|
|
729
797
|
// ---------------------------------------------------------------------------
|
|
730
798
|
// Bridge state machine
|
|
731
799
|
// ---------------------------------------------------------------------------
|
|
@@ -773,7 +841,18 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
773
841
|
private static instance: RustSecurityBridge | null = null;
|
|
774
842
|
private static _resilienceConfig: IBridgeResilienceConfig = { ...DEFAULT_RESILIENCE_CONFIG };
|
|
775
843
|
|
|
776
|
-
|
|
844
|
+
/** The server default maximum message size, used until `setMaxMessageSize` is called. */
|
|
845
|
+
private static readonly defaultMaxMessageSize = 10 * 1024 * 1024;
|
|
846
|
+
|
|
847
|
+
/** The smartrust bridge of the running process; a new one is created at every spawn. */
|
|
848
|
+
private bridge: TMailerBridge | null = null;
|
|
849
|
+
private readonly localPaths: string[];
|
|
850
|
+
/** The IPC line cap the next spawn uses: 4 × the maximum message size. */
|
|
851
|
+
private _lineCap = 4 * RustSecurityBridge.defaultMaxMessageSize;
|
|
852
|
+
/** The line cap of the running process, which both transport ends were created with. */
|
|
853
|
+
private _runningLineCap: number | null = null;
|
|
854
|
+
/** Management event handlers, attached to every bridge this singleton creates. */
|
|
855
|
+
private readonly managementHandlers = new Map<string, Set<(data: any) => void>>();
|
|
777
856
|
private _running = false;
|
|
778
857
|
private _state: BridgeState = BridgeState.Idle;
|
|
779
858
|
private _restartAttempts = 0;
|
|
@@ -785,7 +864,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
785
864
|
private _smtpServerConfig: ISmtpServerConfig | null = null;
|
|
786
865
|
|
|
787
866
|
/**
|
|
788
|
-
* Map Node.js process.platform / process.arch to the
|
|
867
|
+
* Map Node.js process.platform / process.arch to the rsrepo target suffix
|
|
789
868
|
* of the shipped binaries. Linux binaries are static musl builds, e.g.
|
|
790
869
|
* mailer-bin_linux_amd64_musl.
|
|
791
870
|
*/
|
|
@@ -804,7 +883,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
804
883
|
const suffix = RustSecurityBridge.getPlatformSuffix();
|
|
805
884
|
const localPaths: string[] = [];
|
|
806
885
|
|
|
807
|
-
// dist_rust/ candidates (
|
|
886
|
+
// dist_rust/ candidates (rsrepo cross-compiled output)
|
|
808
887
|
if (suffix) {
|
|
809
888
|
localPaths.push(plugins.path.join(paths.packageDir, 'dist_rust', `mailer-bin_${suffix}`));
|
|
810
889
|
}
|
|
@@ -813,24 +892,37 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
813
892
|
localPaths.push(plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'mailer-bin'));
|
|
814
893
|
localPaths.push(plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'mailer-bin'));
|
|
815
894
|
|
|
816
|
-
this.
|
|
895
|
+
this.localPaths = localPaths;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Creates the smartrust bridge for one spawn. The line cap is fixed per process at both ends
|
|
900
|
+
* of the transport: smartrust's `maxPayloadSize` and `mailer-bin --max-line-bytes` get the
|
|
901
|
+
* one computed value. Lifecycle listeners and every registered management handler are
|
|
902
|
+
* attached, so a handler registered before a spawn or restart keeps receiving events.
|
|
903
|
+
*/
|
|
904
|
+
private createBridge(): TMailerBridge {
|
|
905
|
+
const cap = this._lineCap;
|
|
906
|
+
const bridge: TMailerBridge = new plugins.smartrust.RustBridge<TMailerCommands>({
|
|
817
907
|
binaryName: 'mailer-bin',
|
|
818
|
-
cliArgs: ['--management'],
|
|
908
|
+
cliArgs: ['--management', '--max-line-bytes', String(cap)],
|
|
819
909
|
// A full SMTP transaction can legitimately span several socket waits.
|
|
820
910
|
// Health checks retain their own short timeout and run concurrently.
|
|
821
911
|
requestTimeoutMs: 300_000,
|
|
822
912
|
readyTimeoutMs: 10_000,
|
|
823
|
-
|
|
913
|
+
maxPayloadSize: cap,
|
|
914
|
+
localPaths: this.localPaths,
|
|
824
915
|
searchSystemPath: false,
|
|
825
916
|
});
|
|
826
917
|
|
|
827
|
-
|
|
828
|
-
|
|
918
|
+
bridge.on('ready', () => {
|
|
919
|
+
if (bridge !== this.bridge) return;
|
|
829
920
|
this._running = true;
|
|
830
921
|
logger.log('info', 'Rust security bridge is ready');
|
|
831
922
|
});
|
|
832
923
|
|
|
833
|
-
|
|
924
|
+
bridge.on('exit', (code: number | null, signal: string | null) => {
|
|
925
|
+
if (bridge !== this.bridge) return;
|
|
834
926
|
this._running = false;
|
|
835
927
|
logger.log('error', `Rust security bridge exited (code=${code}, signal=${signal})`);
|
|
836
928
|
|
|
@@ -845,9 +937,41 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
845
937
|
}
|
|
846
938
|
});
|
|
847
939
|
|
|
848
|
-
|
|
940
|
+
bridge.on('stderr', (line: string) => {
|
|
849
941
|
logger.log('debug', `[rust-bridge] ${line}`);
|
|
850
942
|
});
|
|
943
|
+
|
|
944
|
+
for (const [eventName, handlers] of this.managementHandlers) {
|
|
945
|
+
for (const handler of handlers) {
|
|
946
|
+
bridge.on(eventName, handler);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return bridge;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** Replaces the bridge with a new one for the current cap and spawns it. */
|
|
953
|
+
private async spawnBridge(): Promise<boolean> {
|
|
954
|
+
const previous = this.bridge;
|
|
955
|
+
if (previous) {
|
|
956
|
+
previous.removeAllListeners();
|
|
957
|
+
previous.kill();
|
|
958
|
+
}
|
|
959
|
+
const bridge = this.createBridge();
|
|
960
|
+
this.bridge = bridge;
|
|
961
|
+
this._runningLineCap = null;
|
|
962
|
+
const ok = await bridge.spawn();
|
|
963
|
+
if (ok && bridge === this.bridge) {
|
|
964
|
+
this._runningLineCap = this._lineCap;
|
|
965
|
+
}
|
|
966
|
+
return ok;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/** The bridge of the running process; commands are guarded by ensureRunning() first. */
|
|
970
|
+
private get activeBridge(): TMailerBridge {
|
|
971
|
+
if (!this.bridge) {
|
|
972
|
+
throw new Error('Rust bridge has not been started yet. Call start() first.');
|
|
973
|
+
}
|
|
974
|
+
return this.bridge;
|
|
851
975
|
}
|
|
852
976
|
|
|
853
977
|
// -----------------------------------------------------------------------
|
|
@@ -952,7 +1076,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
952
1076
|
this.setState(BridgeState.Starting);
|
|
953
1077
|
|
|
954
1078
|
try {
|
|
955
|
-
const ok = await this.
|
|
1079
|
+
const ok = await this.spawnBridge();
|
|
956
1080
|
this._running = ok;
|
|
957
1081
|
if (ok) {
|
|
958
1082
|
this.setState(BridgeState.Running);
|
|
@@ -988,7 +1112,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
988
1112
|
return;
|
|
989
1113
|
}
|
|
990
1114
|
try {
|
|
991
|
-
this.bridge
|
|
1115
|
+
this.bridge?.kill();
|
|
992
1116
|
this._running = false;
|
|
993
1117
|
this.setState(BridgeState.Stopped);
|
|
994
1118
|
logger.log('info', 'Rust security bridge stopped');
|
|
@@ -997,6 +1121,71 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
997
1121
|
}
|
|
998
1122
|
}
|
|
999
1123
|
|
|
1124
|
+
// -----------------------------------------------------------------------
|
|
1125
|
+
// IPC line cap
|
|
1126
|
+
// -----------------------------------------------------------------------
|
|
1127
|
+
|
|
1128
|
+
/** The IPC line cap the next spawn uses (4 × the maximum message size). */
|
|
1129
|
+
public get lineCap(): number {
|
|
1130
|
+
return this._lineCap;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Sets the SMTP maximum message size the IPC line cap derives from: `cap = 4 × n`, the one
|
|
1135
|
+
* value used as smartrust's `maxPayloadSize` and as `mailer-bin --max-line-bytes`. Outbound
|
|
1136
|
+
* requests (`sendEmail`, `sendRawEmail`, `signDkim`, `verifyDkim`) are limited by the same
|
|
1137
|
+
* cap. When the process runs with a different cap it is restarted deliberately (no crash
|
|
1138
|
+
* count, no backoff) and a stored listener configuration is restored; otherwise the value is
|
|
1139
|
+
* recorded for the next spawn. Throws, changing nothing, for an `n` that is not a positive
|
|
1140
|
+
* safe integer or whose cap is not a safe integer.
|
|
1141
|
+
*/
|
|
1142
|
+
public async setMaxMessageSize(maxMessageSize: number): Promise<void> {
|
|
1143
|
+
const cap = 4 * maxMessageSize;
|
|
1144
|
+
if (!Number.isSafeInteger(maxMessageSize) || maxMessageSize <= 0 || !Number.isSafeInteger(cap)) {
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
`maxMessageSize must be a positive safe integer whose 4x line cap is a safe integer, got ${maxMessageSize}`,
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
this._lineCap = cap;
|
|
1150
|
+
if (this._running && this._state === BridgeState.Running && this._runningLineCap !== cap) {
|
|
1151
|
+
await this.restartForLineCap();
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/** A deliberate restart for a changed line cap; it is not a crash. */
|
|
1156
|
+
private async restartForLineCap(): Promise<void> {
|
|
1157
|
+
logger.log('info', `Restarting Rust bridge for IPC line cap ${this._lineCap}`);
|
|
1158
|
+
this.stopHealthCheck();
|
|
1159
|
+
this.setState(BridgeState.Restarting);
|
|
1160
|
+
this._spawnInFlight = true;
|
|
1161
|
+
try {
|
|
1162
|
+
const previous = this.bridge;
|
|
1163
|
+
if (previous) {
|
|
1164
|
+
previous.removeAllListeners();
|
|
1165
|
+
await previous.terminate();
|
|
1166
|
+
}
|
|
1167
|
+
this._running = false;
|
|
1168
|
+
const ok = await this.spawnBridge();
|
|
1169
|
+
this._running = ok;
|
|
1170
|
+
if (!ok) {
|
|
1171
|
+
throw new Error('spawn returned false');
|
|
1172
|
+
}
|
|
1173
|
+
if (!(await this.restoreAfterRestart())) {
|
|
1174
|
+
throw new Error('SMTP server restore failed');
|
|
1175
|
+
}
|
|
1176
|
+
this.setState(BridgeState.Running);
|
|
1177
|
+
this.startHealthCheck();
|
|
1178
|
+
logger.log('info', `Rust bridge running with IPC line cap ${this._lineCap}`);
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
logger.log('error', `Rust bridge restart for a new line cap failed: ${(error as Error).message}`);
|
|
1181
|
+
// The process did not come back: from here on it is a failure, recovered like a crash.
|
|
1182
|
+
this.attemptRestart();
|
|
1183
|
+
throw error;
|
|
1184
|
+
} finally {
|
|
1185
|
+
this._spawnInFlight = false;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1000
1189
|
// -----------------------------------------------------------------------
|
|
1001
1190
|
// Auto-restart with exponential backoff
|
|
1002
1191
|
// -----------------------------------------------------------------------
|
|
@@ -1042,7 +1231,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1042
1231
|
|
|
1043
1232
|
this._spawnInFlight = true;
|
|
1044
1233
|
try {
|
|
1045
|
-
const ok = await this.
|
|
1234
|
+
const ok = await this.spawnBridge();
|
|
1046
1235
|
this._running = ok;
|
|
1047
1236
|
if (!ok) {
|
|
1048
1237
|
throw new Error('spawn returned false');
|
|
@@ -1054,7 +1243,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1054
1243
|
// and go through another restart cycle instead of sitting half-alive.
|
|
1055
1244
|
logger.log('error', 'Rust bridge restarted but the SMTP server could not be restored — killing process to retry');
|
|
1056
1245
|
try {
|
|
1057
|
-
this.bridge
|
|
1246
|
+
this.bridge?.kill();
|
|
1058
1247
|
} catch {
|
|
1059
1248
|
// Already dead
|
|
1060
1249
|
}
|
|
@@ -1086,7 +1275,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1086
1275
|
}
|
|
1087
1276
|
try {
|
|
1088
1277
|
logger.log('info', 'Restoring SMTP server after bridge restart');
|
|
1089
|
-
const result = await this.
|
|
1278
|
+
const result = await this.activeBridge.sendCommand('startSmtpServer', this._smtpServerConfig);
|
|
1090
1279
|
if (result?.started) {
|
|
1091
1280
|
logger.log('info', 'SMTP server restored after bridge restart');
|
|
1092
1281
|
return true;
|
|
@@ -1114,7 +1303,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1114
1303
|
}
|
|
1115
1304
|
|
|
1116
1305
|
try {
|
|
1117
|
-
const pongPromise = this.
|
|
1306
|
+
const pongPromise = this.activeBridge.sendCommand('ping', {} as any);
|
|
1118
1307
|
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
1119
1308
|
setTimeout(() => reject(new Error('Health check timeout')), config.healthCheckTimeoutMs),
|
|
1120
1309
|
);
|
|
@@ -1134,7 +1323,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1134
1323
|
logger.log('error', `Rust bridge health check failed twice in a row: ${(err as Error).message}. Killing process to trigger restart.`);
|
|
1135
1324
|
this._healthCheckStrikes = 0;
|
|
1136
1325
|
try {
|
|
1137
|
-
this.bridge
|
|
1326
|
+
this.bridge?.kill();
|
|
1138
1327
|
} catch {
|
|
1139
1328
|
// Already dead
|
|
1140
1329
|
}
|
|
@@ -1157,20 +1346,20 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1157
1346
|
/** Ping the Rust process. */
|
|
1158
1347
|
public async ping(): Promise<boolean> {
|
|
1159
1348
|
this.ensureRunning();
|
|
1160
|
-
const res = await this.
|
|
1349
|
+
const res = await this.activeBridge.sendCommand('ping', {} as any);
|
|
1161
1350
|
return res?.pong === true;
|
|
1162
1351
|
}
|
|
1163
1352
|
|
|
1164
1353
|
/** Get version information for all Rust crates. */
|
|
1165
1354
|
public async getVersion(): Promise<IVersionInfo> {
|
|
1166
1355
|
this.ensureRunning();
|
|
1167
|
-
return this.
|
|
1356
|
+
return this.activeBridge.sendCommand('version', {} as any);
|
|
1168
1357
|
}
|
|
1169
1358
|
|
|
1170
1359
|
/** Validate an email address. */
|
|
1171
1360
|
public async validateEmail(email: string): Promise<IValidationResult> {
|
|
1172
1361
|
this.ensureRunning();
|
|
1173
|
-
return this.
|
|
1362
|
+
return this.activeBridge.sendCommand('validateEmail', { email });
|
|
1174
1363
|
}
|
|
1175
1364
|
|
|
1176
1365
|
/** Detect bounce type from SMTP response / diagnostic code. */
|
|
@@ -1180,7 +1369,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1180
1369
|
statusCode?: string;
|
|
1181
1370
|
}): Promise<IBounceDetection> {
|
|
1182
1371
|
this.ensureRunning();
|
|
1183
|
-
return this.
|
|
1372
|
+
return this.activeBridge.sendCommand('detectBounce', opts);
|
|
1184
1373
|
}
|
|
1185
1374
|
|
|
1186
1375
|
/** Scan email content for threats (phishing, spam, malware, etc.). */
|
|
@@ -1191,19 +1380,19 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1191
1380
|
attachmentNames?: string[];
|
|
1192
1381
|
}): Promise<IContentScanResult> {
|
|
1193
1382
|
this.ensureRunning();
|
|
1194
|
-
return this.
|
|
1383
|
+
return this.activeBridge.sendCommand('scanContent', opts);
|
|
1195
1384
|
}
|
|
1196
1385
|
|
|
1197
1386
|
/** Check IP reputation via DNSBL. */
|
|
1198
1387
|
public async checkIpReputation(ip: string): Promise<IReputationResult> {
|
|
1199
1388
|
this.ensureRunning();
|
|
1200
|
-
return this.
|
|
1389
|
+
return this.activeBridge.sendCommand('checkIpReputation', { ip });
|
|
1201
1390
|
}
|
|
1202
1391
|
|
|
1203
1392
|
/** Verify DKIM signatures on a raw email message. */
|
|
1204
1393
|
public async verifyDkim(rawMessage: string): Promise<IDkimVerificationResult[]> {
|
|
1205
1394
|
this.ensureRunning();
|
|
1206
|
-
return this.
|
|
1395
|
+
return this.activeBridge.sendCommand('verifyDkim', { rawMessage });
|
|
1207
1396
|
}
|
|
1208
1397
|
|
|
1209
1398
|
/** Sign an email with DKIM (RSA or Ed25519). */
|
|
@@ -1215,7 +1404,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1215
1404
|
keyType?: string;
|
|
1216
1405
|
}): Promise<{ header: string; signedMessage: string }> {
|
|
1217
1406
|
this.ensureRunning();
|
|
1218
|
-
return this.
|
|
1407
|
+
return this.activeBridge.sendCommand('signDkim', opts);
|
|
1219
1408
|
}
|
|
1220
1409
|
|
|
1221
1410
|
/** Check SPF for a sender. */
|
|
@@ -1226,7 +1415,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1226
1415
|
mailFrom: string;
|
|
1227
1416
|
}): Promise<ISpfResult> {
|
|
1228
1417
|
this.ensureRunning();
|
|
1229
|
-
return this.
|
|
1418
|
+
return this.activeBridge.sendCommand('checkSpf', opts);
|
|
1230
1419
|
}
|
|
1231
1420
|
|
|
1232
1421
|
/**
|
|
@@ -1243,7 +1432,18 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1243
1432
|
mailFrom: string;
|
|
1244
1433
|
}): Promise<IEmailSecurityResult> {
|
|
1245
1434
|
this.ensureRunning();
|
|
1246
|
-
return this.
|
|
1435
|
+
return this.activeBridge.sendCommand('verifyEmail', opts);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* Parses stored message bytes with the parser and setting of the DATA path (`parseMessage`,
|
|
1440
|
+
* ruling Q1), so a replay builds the same Email the live path built. Rejects with the
|
|
1441
|
+
* parser's error text when a parser limit is exceeded or the answer would exceed the line cap.
|
|
1442
|
+
*/
|
|
1443
|
+
public async parseMessage(rawMessage: Buffer): Promise<IBridgeParsedMessage> {
|
|
1444
|
+
this.ensureRunning();
|
|
1445
|
+
const result = await this.activeBridge.sendCommand('parseMessage', { data: rawMessage.toString('base64') });
|
|
1446
|
+
return result.parsed;
|
|
1247
1447
|
}
|
|
1248
1448
|
|
|
1249
1449
|
// -----------------------------------------------------------------------
|
|
@@ -1254,7 +1454,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1254
1454
|
public async sendOutboundEmail(opts: ISmtpSendOptions): Promise<ISmtpSendResult> {
|
|
1255
1455
|
this.ensureRunning();
|
|
1256
1456
|
try {
|
|
1257
|
-
const result = await this.
|
|
1457
|
+
const result = await this.activeBridge.sendCommand('sendEmail', opts);
|
|
1258
1458
|
return {
|
|
1259
1459
|
...result,
|
|
1260
1460
|
transactionLog: normalizeSmtpTranscript(result.transactionLog),
|
|
@@ -1271,7 +1471,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1271
1471
|
public async sendRawEmail(opts: TSmtpSendRawOptions): Promise<ISmtpSendResult> {
|
|
1272
1472
|
this.ensureRunning();
|
|
1273
1473
|
try {
|
|
1274
|
-
const result = await this.
|
|
1474
|
+
const result = await this.activeBridge.sendCommand('sendRawEmail', opts);
|
|
1275
1475
|
|
|
1276
1476
|
return {
|
|
1277
1477
|
...result,
|
|
@@ -1290,30 +1490,30 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1290
1490
|
mailFrom: string;
|
|
1291
1491
|
}): Promise<IVerifyCapturedOutboundEmailResult> {
|
|
1292
1492
|
this.ensureRunning();
|
|
1293
|
-
return this.
|
|
1493
|
+
return this.activeBridge.sendCommand('verifyCapturedOutboundEmail', opts);
|
|
1294
1494
|
}
|
|
1295
1495
|
|
|
1296
1496
|
public async discardCapturedOutboundEmail(captureId: string): Promise<void> {
|
|
1297
1497
|
this.ensureRunning();
|
|
1298
|
-
await this.
|
|
1498
|
+
await this.activeBridge.sendCommand('discardCapturedOutboundEmail', { captureId });
|
|
1299
1499
|
}
|
|
1300
1500
|
|
|
1301
1501
|
/** Verify connectivity to an SMTP server. */
|
|
1302
1502
|
public async verifySmtpConnection(opts: ISmtpVerifyOptions): Promise<ISmtpVerifyResult> {
|
|
1303
1503
|
this.ensureRunning();
|
|
1304
|
-
return this.
|
|
1504
|
+
return this.activeBridge.sendCommand('verifySmtpConnection', opts);
|
|
1305
1505
|
}
|
|
1306
1506
|
|
|
1307
1507
|
/** Close a specific connection pool (or all pools if no key is given). */
|
|
1308
1508
|
public async closeSmtpPool(poolKey?: string): Promise<void> {
|
|
1309
1509
|
this.ensureRunning();
|
|
1310
|
-
await this.
|
|
1510
|
+
await this.activeBridge.sendCommand('closeSmtpPool', poolKey ? { poolKey } : ({} as any));
|
|
1311
1511
|
}
|
|
1312
1512
|
|
|
1313
1513
|
/** Get status of all SMTP client connection pools. */
|
|
1314
1514
|
public async getSmtpPoolStatus(): Promise<ISmtpPoolStatus> {
|
|
1315
1515
|
this.ensureRunning();
|
|
1316
|
-
return this.
|
|
1516
|
+
return this.activeBridge.sendCommand('getSmtpPoolStatus', {} as any);
|
|
1317
1517
|
}
|
|
1318
1518
|
|
|
1319
1519
|
// -----------------------------------------------------------------------
|
|
@@ -1328,7 +1528,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1328
1528
|
public async startSmtpServer(config: ISmtpServerConfig): Promise<boolean> {
|
|
1329
1529
|
this.ensureRunning();
|
|
1330
1530
|
this._smtpServerConfig = config;
|
|
1331
|
-
const result = await this.
|
|
1531
|
+
const result = await this.activeBridge.sendCommand('startSmtpServer', config);
|
|
1332
1532
|
return result?.started === true;
|
|
1333
1533
|
}
|
|
1334
1534
|
|
|
@@ -1336,7 +1536,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1336
1536
|
public async stopSmtpServer(): Promise<void> {
|
|
1337
1537
|
this.ensureRunning();
|
|
1338
1538
|
this._smtpServerConfig = null;
|
|
1339
|
-
await this.
|
|
1539
|
+
await this.activeBridge.sendCommand('stopSmtpServer', {} as any);
|
|
1340
1540
|
}
|
|
1341
1541
|
|
|
1342
1542
|
/**
|
|
@@ -1351,7 +1551,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1351
1551
|
smtpMessage?: string;
|
|
1352
1552
|
}): Promise<void> {
|
|
1353
1553
|
this.ensureRunning();
|
|
1354
|
-
await this.
|
|
1554
|
+
await this.activeBridge.sendCommand('emailProcessingResult', opts);
|
|
1355
1555
|
}
|
|
1356
1556
|
|
|
1357
1557
|
/**
|
|
@@ -1364,7 +1564,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1364
1564
|
smtpMessage?: string;
|
|
1365
1565
|
}): Promise<void> {
|
|
1366
1566
|
this.ensureRunning();
|
|
1367
|
-
await this.
|
|
1567
|
+
await this.activeBridge.sendCommand('rcptToResult', opts);
|
|
1368
1568
|
}
|
|
1369
1569
|
|
|
1370
1570
|
/**
|
|
@@ -1376,7 +1576,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1376
1576
|
message?: string;
|
|
1377
1577
|
}): Promise<void> {
|
|
1378
1578
|
this.ensureRunning();
|
|
1379
|
-
await this.
|
|
1579
|
+
await this.activeBridge.sendCommand('authResult', opts);
|
|
1380
1580
|
}
|
|
1381
1581
|
|
|
1382
1582
|
/**
|
|
@@ -1392,32 +1592,47 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1392
1592
|
serverKey?: string;
|
|
1393
1593
|
}): Promise<void> {
|
|
1394
1594
|
this.ensureRunning();
|
|
1395
|
-
await this.
|
|
1595
|
+
await this.activeBridge.sendCommand('scramCredentialResult', opts);
|
|
1396
1596
|
}
|
|
1397
1597
|
|
|
1398
1598
|
/** Update rate limit configuration at runtime. */
|
|
1399
1599
|
public async configureRateLimits(config: IRateLimitConfig): Promise<void> {
|
|
1400
1600
|
this.ensureRunning();
|
|
1401
|
-
await this.
|
|
1601
|
+
await this.activeBridge.sendCommand('configureRateLimits', config);
|
|
1402
1602
|
}
|
|
1403
1603
|
|
|
1404
1604
|
// -----------------------------------------------------------------------
|
|
1405
1605
|
// Event registration — delegates to the underlying bridge EventEmitter
|
|
1406
1606
|
// -----------------------------------------------------------------------
|
|
1407
1607
|
|
|
1608
|
+
private addManagementHandler(eventName: string, handler: (data: any) => void): void {
|
|
1609
|
+
let handlers = this.managementHandlers.get(eventName);
|
|
1610
|
+
if (!handlers) {
|
|
1611
|
+
handlers = new Set();
|
|
1612
|
+
this.managementHandlers.set(eventName, handlers);
|
|
1613
|
+
}
|
|
1614
|
+
handlers.add(handler);
|
|
1615
|
+
this.bridge?.on(eventName, handler);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
private removeManagementHandler(eventName: string, handler: (data: any) => void): void {
|
|
1619
|
+
this.managementHandlers.get(eventName)?.delete(handler);
|
|
1620
|
+
this.bridge?.off(eventName, handler);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1408
1623
|
/**
|
|
1409
1624
|
* Register a handler for emailReceived events from the Rust SMTP server.
|
|
1410
1625
|
* These events fire when a complete email has been received and needs processing.
|
|
1411
1626
|
*/
|
|
1412
1627
|
public onEmailReceived(handler: (data: IEmailReceivedEvent) => void): void {
|
|
1413
|
-
this.
|
|
1628
|
+
this.addManagementHandler('management:emailReceived', handler);
|
|
1414
1629
|
}
|
|
1415
1630
|
|
|
1416
1631
|
/**
|
|
1417
1632
|
* Register a handler for RCPT TO policy requests from the Rust SMTP server.
|
|
1418
1633
|
*/
|
|
1419
1634
|
public onRcptToRequest(handler: (data: IRcptToRequestEvent) => void): void {
|
|
1420
|
-
this.
|
|
1635
|
+
this.addManagementHandler('management:rcptToRequest', handler);
|
|
1421
1636
|
}
|
|
1422
1637
|
|
|
1423
1638
|
/**
|
|
@@ -1425,7 +1640,7 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1425
1640
|
* The handler must call sendAuthResult() with the correlationId.
|
|
1426
1641
|
*/
|
|
1427
1642
|
public onAuthRequest(handler: (data: IAuthRequestEvent) => void): void {
|
|
1428
|
-
this.
|
|
1643
|
+
this.addManagementHandler('management:authRequest', handler);
|
|
1429
1644
|
}
|
|
1430
1645
|
|
|
1431
1646
|
/**
|
|
@@ -1433,27 +1648,27 @@ export class RustSecurityBridge extends EventEmitter {
|
|
|
1433
1648
|
* The handler must call sendScramCredentialResult() with the correlationId.
|
|
1434
1649
|
*/
|
|
1435
1650
|
public onScramCredentialRequest(handler: (data: IScramCredentialRequestEvent) => void): void {
|
|
1436
|
-
this.
|
|
1651
|
+
this.addManagementHandler('management:scramCredentialRequest', handler);
|
|
1437
1652
|
}
|
|
1438
1653
|
|
|
1439
1654
|
/** Remove an emailReceived event handler. */
|
|
1440
1655
|
public offEmailReceived(handler: (data: IEmailReceivedEvent) => void): void {
|
|
1441
|
-
this.
|
|
1656
|
+
this.removeManagementHandler('management:emailReceived', handler);
|
|
1442
1657
|
}
|
|
1443
1658
|
|
|
1444
1659
|
/** Remove an RCPT TO policy request handler. */
|
|
1445
1660
|
public offRcptToRequest(handler: (data: IRcptToRequestEvent) => void): void {
|
|
1446
|
-
this.
|
|
1661
|
+
this.removeManagementHandler('management:rcptToRequest', handler);
|
|
1447
1662
|
}
|
|
1448
1663
|
|
|
1449
1664
|
/** Remove an authRequest event handler. */
|
|
1450
1665
|
public offAuthRequest(handler: (data: IAuthRequestEvent) => void): void {
|
|
1451
|
-
this.
|
|
1666
|
+
this.removeManagementHandler('management:authRequest', handler);
|
|
1452
1667
|
}
|
|
1453
1668
|
|
|
1454
1669
|
/** Remove a scramCredentialRequest event handler. */
|
|
1455
1670
|
public offScramCredentialRequest(handler: (data: IScramCredentialRequestEvent) => void): void {
|
|
1456
|
-
this.
|
|
1671
|
+
this.removeManagementHandler('management:scramCredentialRequest', handler);
|
|
1457
1672
|
}
|
|
1458
1673
|
}
|
|
1459
1674
|
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
charset is copyright 2013-2016 Mozilla Foundation.
|
|
2
|
-
|
|
3
|
-
Licensed under the Apache License, Version 2.0
|
|
4
|
-
<LICENSE-APACHE or
|
|
5
|
-
https://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
|
6
|
-
license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
|
|
7
|
-
at your option. All files in the project carrying such
|
|
8
|
-
notice may not be copied, modified, or distributed except
|
|
9
|
-
according to those terms.
|
|
10
|
-
|
|
11
|
-
Test code within encoding_rs is dedicated to the Public Domain when so
|
|
12
|
-
designated (see the individual files for PD/CC0-dedicated sections).
|