@bumail/smtp 0.3.0 → 0.5.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.
Files changed (52) hide show
  1. package/README.md +105 -7
  2. package/dist/chunks/{index-3423jxf9.js → index-n4xwep5m.js} +10 -4
  3. package/dist/chunks/index-n4xwep5m.js.map +14 -0
  4. package/dist/client/index.js +1 -1
  5. package/dist/errors.d.ts +3 -1
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +724 -126
  10. package/dist/index.js.map +19 -9
  11. package/dist/protocol/command.d.ts +4 -2
  12. package/dist/protocol/command.d.ts.map +1 -1
  13. package/dist/protocol/path.d.ts +13 -2
  14. package/dist/protocol/path.d.ts.map +1 -1
  15. package/dist/server/admission.d.ts +29 -0
  16. package/dist/server/admission.d.ts.map +1 -0
  17. package/dist/server/client-key.d.ts +15 -0
  18. package/dist/server/client-key.d.ts.map +1 -0
  19. package/dist/server/listener.d.ts +46 -0
  20. package/dist/server/listener.d.ts.map +1 -0
  21. package/dist/server/options.d.ts +45 -1
  22. package/dist/server/options.d.ts.map +1 -1
  23. package/dist/server/proxy/address.d.ts +17 -0
  24. package/dist/server/proxy/address.d.ts.map +1 -0
  25. package/dist/server/proxy/header.d.ts +26 -0
  26. package/dist/server/proxy/header.d.ts.map +1 -0
  27. package/dist/server/proxy/reader.d.ts +15 -0
  28. package/dist/server/proxy/reader.d.ts.map +1 -0
  29. package/dist/server/proxy/tls.d.ts +50 -0
  30. package/dist/server/proxy/tls.d.ts.map +1 -0
  31. package/dist/server/proxy/trusted.d.ts +9 -0
  32. package/dist/server/proxy/trusted.d.ts.map +1 -0
  33. package/dist/server/raw-socket.d.ts +9 -0
  34. package/dist/server/raw-socket.d.ts.map +1 -0
  35. package/dist/server/received.d.ts +2 -0
  36. package/dist/server/received.d.ts.map +1 -1
  37. package/dist/server/server.d.ts.map +1 -1
  38. package/dist/server/settings.d.ts +4 -0
  39. package/dist/server/settings.d.ts.map +1 -1
  40. package/dist/server/slots.d.ts +14 -0
  41. package/dist/server/slots.d.ts.map +1 -0
  42. package/dist/server/tls-context.d.ts +16 -0
  43. package/dist/server/tls-context.d.ts.map +1 -0
  44. package/dist/server/transaction.d.ts.map +1 -1
  45. package/dist/server/transport.d.ts +2 -2
  46. package/dist/server/transport.d.ts.map +1 -1
  47. package/docs/README.md +2 -2
  48. package/docs/guide.md +472 -12
  49. package/docs/roadmap.md +57 -0
  50. package/docs/troubleshooting.md +406 -4
  51. package/package.json +2 -2
  52. package/dist/chunks/index-3423jxf9.js.map +0 -14
@@ -13,8 +13,10 @@ export interface PathCommand {
13
13
  }
14
14
  /**
15
15
  * Parses the argument of `MAIL` or `RCPT`: `FROM:<path> params` (or `TO:`).
16
- * Tolerates a space after the colon, which many clients send. `undefined`
17
- * when the syntax is wrong.
16
+ * Tolerates a space after the colon, which many clients send. `TO:` also
17
+ * takes `<postmaster>` with no domain, in any case (RFC 5321 §4.1.1.3,
18
+ * §4.5.1), as a path whose `postmaster` is `true`. `undefined` when the
19
+ * syntax is wrong.
18
20
  */
19
21
  export declare function parsePathCommand(argument: string, keyword: 'FROM' | 'TO'): PathCommand | undefined;
20
22
  //# sourceMappingURL=command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/protocol/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAa,MAAM,QAAQ,CAAC;AAE9C,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,oGAAoG;IACpG,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,IAAI,GACpB,WAAW,GAAG,SAAS,CAgBzB"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/protocol/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAyB,MAAM,QAAQ,CAAC;AAE1D,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,oGAAoG;IACpG,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,IAAI,GACpB,WAAW,GAAG,SAAS,CAoBzB"}
@@ -1,11 +1,22 @@
1
1
  /** An envelope address, as `MAIL FROM` and `RCPT TO` give it. */
2
2
  export interface Path {
3
- /** `local@domain`; `''` for the null reverse-path `<>`. */
3
+ /**
4
+ * `local@domain`; `''` for the null reverse-path `<>`; `'postmaster'`
5
+ * for `RCPT TO:<postmaster>`, which has no domain.
6
+ */
4
7
  readonly address: string;
5
8
  readonly local: string;
6
- /** Lower case. */
9
+ /** Lower case; `''` for `<>` and for `<postmaster>`. */
7
10
  readonly domain: string;
11
+ /**
12
+ * `true` for `RCPT TO:<postmaster>`, in any case, with no domain (RFC
13
+ * 5321 §4.5.1): the postmaster of this server, whatever domains it
14
+ * hosts. Absent for every other path, `<postmaster@domain>` included.
15
+ */
16
+ readonly postmaster?: true;
8
17
  }
18
+ /** The bare `<postmaster>` path `RCPT TO` takes (RFC 5321 §4.1.1.3, §4.5.1). */
19
+ export declare const POSTMASTER: Path;
9
20
  /**
10
21
  * An address literal (§4.1.3): `[IPv6:…]`, its tag in any case (a quoted
11
22
  * string in RFC 5234 is), or `[a.b.c.d]` with each octet at most 255.
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/protocol/path.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,IAAI;IACpB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kBAAkB;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAwCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIjD;AAUD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAgB/C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,WAAW,GAAE,WAAuB,GAClC,IAAI,GAAG,SAAS,CAuBlB"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/protocol/path.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,WAAW,IAAI;IACpB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;CAC3B;AAED,gFAAgF;AAChF,eAAO,MAAM,UAAU,EAAE,IAKvB,CAAC;AAwCH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIjD;AAUD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAgB/C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,WAAW,GAAE,WAAuB,GAClC,IAAI,GAAG,SAAS,CAuBlB"}
@@ -0,0 +1,29 @@
1
+ import type { SecureContext } from 'node:tls';
2
+ import type { Socket, SocketHandler } from 'bun';
3
+ import { Connection } from './connection';
4
+ import { type SocketState } from './listener';
5
+ import type { Settings } from './settings';
6
+ import type { Slots } from './slots';
7
+ /**
8
+ * The listener's own handlers: `open` counts a socket and greets it or
9
+ * turns it away, `handshake` lets implicit TLS start once encrypted,
10
+ * `close` frees the slot.
11
+ *
12
+ * Behind a trusted proxy (`proxyProtocol`), `open` only starts reading the
13
+ * header: the socket is counted, by the client's address, once the header
14
+ * named it. On implicit TLS with `proxyProtocol`, the listener is a clear
15
+ * one and `proxied` is the TLS context: every socket's TLS runs through
16
+ * `ProxiedTls`, after the header from a trusted proxy, from the first byte
17
+ * from anyone else.
18
+ */
19
+ export interface Listening {
20
+ readonly settings: Settings;
21
+ readonly slots: Slots<Connection>;
22
+ /** Sockets still in their handshake or awaiting a PROXY header, for `stop(true)`. */
23
+ readonly handshaking: Set<Socket<SocketState>>;
24
+ /** TLS from the first byte: implicit TLS. */
25
+ readonly secure: boolean;
26
+ readonly proxied?: SecureContext;
27
+ }
28
+ export declare function listenerHandlers(listening: Listening): Pick<SocketHandler<SocketState>, 'open' | 'handshake' | 'close'>;
29
+ //# sourceMappingURL=admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../../src/server/admission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAY,KAAK,WAAW,EAAyB,MAAM,YAAY,CAAC;AAI/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAClC,qFAAqF;IACrF,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/C,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,wBAAgB,gBAAgB,CAC/B,SAAS,EAAE,SAAS,GAClB,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,CA0BlE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Which client an address counts as, for a limit per client: an IPv4
3
+ * address as it is; an IPv6 address that embeds one — IPv4-mapped
4
+ * (`::ffff:192.0.2.1`, in dotted or hex form, as a dual-stack listener
5
+ * sees an IPv4 client) or NAT64 (`64:ff9b::/96`) — as that IPv4 address;
6
+ * any other IPv6 address by its /64, which one machine usually holds
7
+ * whole, written as its first four groups in lower-case hex without
8
+ * leading zeros, every group written out, zeros included, then `::/64`:
9
+ * `2001:db8::1` is `2001:db8:0:0::/64`, never `2001:db8::/64`. Anything
10
+ * else — an empty string, a Unix socket — is `undefined`: no client that
11
+ * can be told apart, so not limited, rather than putting every such
12
+ * client in one bucket that one of them could fill for all.
13
+ */
14
+ export declare function clientKey(ip: string | undefined): string | undefined;
15
+ //# sourceMappingURL=client-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-key.d.ts","sourceRoot":"","sources":["../../src/server/client-key.ts"],"names":[],"mappings":"AA+BA;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAcpE"}
@@ -0,0 +1,46 @@
1
+ import type { Socket, SocketHandler } from 'bun';
2
+ import type { Connection } from './connection';
3
+ import type { HeaderReader } from './proxy/reader';
4
+ import type { ProxiedTls } from './proxy/tls';
5
+ import type { RawSocket } from './raw-socket';
6
+ import type { Settings } from './settings';
7
+ import type { Slots } from './slots';
8
+ import { SocketTransport } from './transport';
9
+ /**
10
+ * What a socket of the SMTP server does: the handlers the clear socket and
11
+ * the TLS one share, and the STARTTLS upgrade. The listener's own `open`,
12
+ * `handshake` and `close` are in `admission.ts`.
13
+ */
14
+ export interface SocketState {
15
+ connection?: Connection;
16
+ transport?: SocketTransport;
17
+ /** Bytes on the raw socket after STARTTLS are the TLS stream itself: ignored. */
18
+ upgraded: boolean;
19
+ /**
20
+ * Implicit TLS whose handshake has not completed: the socket is counted,
21
+ * but bounded by `handshakeTimeout`, and `start` waits for the handshake.
22
+ */
23
+ handshaking?: boolean;
24
+ /** What the server does once the handshake completes: greet, or turn away. */
25
+ start?: () => void;
26
+ /** A trusted proxy's PROXY header, still coming: nothing else is read meanwhile. */
27
+ header?: HeaderReader;
28
+ /** Implicit TLS behind a PROXY header, over this raw socket. */
29
+ tls?: ProxiedTls;
30
+ }
31
+ /** Bytes from the client, in clear or decrypted: they start the idle time again. */
32
+ export declare function received(socket: Socket<SocketState>, settings: Settings, chunk: Uint8Array): void;
33
+ /**
34
+ * The handlers both the clear socket and the TLS one share: input, drain,
35
+ * idle time. `socket.data` is read with `?.` throughout: Bun may report an
36
+ * error or a close on a socket whose `open` never ran.
37
+ */
38
+ export declare function handlers(settings: Settings): SocketHandler<SocketState>;
39
+ /** Writes one reply and hangs up, through a transport so the hang-up is bounded as every other is. */
40
+ export declare function turnAway(socket: Socket<SocketState>, raw: RawSocket, secure: boolean, line: string): void;
41
+ /**
42
+ * Wraps a socket — Bun's, or `ProxiedTls` over it — for the client at
43
+ * `address`; STARTTLS swaps it for the encrypted one.
44
+ */
45
+ export declare function transportOf(socket: Socket<SocketState>, raw: RawSocket, address: string, settings: Settings, secure: boolean, slots: Slots<Connection>): SocketTransport;
46
+ //# sourceMappingURL=listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/server/listener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AAEH,MAAM,WAAW,WAAW;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,oFAAoF;IACpF,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,gEAAgE;IAChE,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CACvB,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,EAC3B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,UAAU,GACf,IAAI,CAMN;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CAAC,WAAW,CAAC,CAoCvE;AAED,sGAAsG;AACtG,wBAAgB,QAAQ,CACvB,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,EAC3B,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,MAAM,GACV,IAAI,CAKN;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAC1B,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,EAC3B,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,GACtB,eAAe,CAmCjB"}
@@ -24,6 +24,7 @@ export interface Session {
24
24
  export interface Envelope {
25
25
  /** `''` for the null reverse-path `<>`: a bounce. */
26
26
  readonly from: string;
27
+ /** `local@domain` each; `'postmaster'`, with no domain, for `RCPT TO:<postmaster>`. */
27
28
  readonly to: readonly string[];
28
29
  /** The client asked for SMTPUTF8 (RFC 6531). */
29
30
  readonly smtputf8: boolean;
@@ -70,7 +71,12 @@ export interface ReceivedMessage {
70
71
  */
71
72
  export type HookResult = Reply | undefined | void;
72
73
  export interface SmtpHooks {
73
- /** A client connected. */
74
+ /**
75
+ * A client connected, and was let in by `maxConnections` and
76
+ * `maxConnectionsPerClient`. A reply refuses it: the server sends it
77
+ * instead of its greeting and closes. `session.remoteAddress` says who
78
+ * it is; `clientKey` groups it as the limits do.
79
+ */
74
80
  onConnect?(session: Session): HookResult | Promise<HookResult>;
75
81
  /** After the server's own checks of MAIL FROM. */
76
82
  onMailFrom?(path: Path, session: Session): HookResult | Promise<HookResult>;
@@ -89,6 +95,11 @@ export interface TlsOptions {
89
95
  readonly key: string | Uint8Array | Bun.BunFile;
90
96
  readonly cert: string | Uint8Array | Bun.BunFile;
91
97
  }
98
+ /** Where the PROXY protocol is read from: see `proxyProtocol`. */
99
+ export interface ProxyProtocolOptions {
100
+ /** The proxies' addresses or CIDRs, IPv4 or IPv6: `['10.0.0.5', '172.16.0.0/12']`. */
101
+ readonly trusted: readonly string[];
102
+ }
92
103
  export interface SmtpServerOptions extends SmtpHooks {
93
104
  /** The server's own name, in its greeting, its EHLO reply and its Received fields. */
94
105
  readonly hostname: string;
@@ -123,10 +134,43 @@ export interface SmtpServerOptions extends SmtpHooks {
123
134
  readonly maxRecipients?: number;
124
135
  /** Open connections at once. Default 1000. */
125
136
  readonly maxConnections?: number;
137
+ /**
138
+ * Open connections at once from one client, so one address cannot take
139
+ * every `maxConnections` slot. A client is an IPv4 address, or an IPv6
140
+ * address by its /64; an IPv4-mapped or NAT64 address counts as the
141
+ * IPv4 address inside it (see `clientKey`). One more is answered
142
+ * `421 4.7.0` and closed. A remote address that is not an IP address
143
+ * is not limited by it. Default 10.
144
+ */
145
+ readonly maxConnectionsPerClient?: number;
126
146
  /** Commands that fail before the server hangs up. Default 10. */
127
147
  readonly maxErrors?: number;
128
148
  /** Idle time before the server hangs up, in seconds; any byte from the client starts it again. Default 300, RFC 5321 §4.5.3.2.7's. */
129
149
  readonly timeout?: number;
150
+ /**
151
+ * Reads the PROXY protocol (versions 1 and 2) from the proxies listed in
152
+ * `trusted` — IPv4 and IPv6 addresses and CIDRs — so the server sees
153
+ * the client's address, not the proxy's: in `session.remoteAddress`,
154
+ * `maxConnectionsPerClient`, `onConnect` and the Received field. A
155
+ * trusted peer must send its header first, within `handshakeTimeout`,
156
+ * or it is closed without a word; any other peer is served as without
157
+ * this option, and a header it sends is just bad input. List only the
158
+ * proxies' own addresses: any peer listed can claim any address. Off by
159
+ * default.
160
+ */
161
+ readonly proxyProtocol?: ProxyProtocolOptions;
162
+ /**
163
+ * Seconds a client on implicit TLS has to complete its handshake, from
164
+ * the TCP connection on; past it, the socket is closed without a word.
165
+ * The socket holds its slot of `maxConnections` and
166
+ * `maxConnectionsPerClient` meanwhile. Bun's socket timer ticks in steps
167
+ * of about 4 s, so the close comes up to that much later. A STARTTLS
168
+ * handshake is bounded by `timeout`. With `proxyProtocol`, it also bounds a
169
+ * trusted proxy's PROXY header, from the TCP connection on, to the
170
+ * millisecond; behind the header, implicit TLS has as long again.
171
+ * Default 10.
172
+ */
173
+ readonly handshakeTimeout?: number;
130
174
  /**
131
175
  * Seconds the server waits, once `onConnect` accepted, before its 220
132
176
  * greeting. A client that talks in that time is refused with `554`
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * IP addresses as bytes, for the PROXY protocol: what `trusted` lists and
3
+ * what a header names. An IPv4-mapped IPv6 address (`::ffff:192.0.2.1`)
4
+ * is its IPv4 address, in both: a dual-stack listener reports an IPv4
5
+ * peer that way.
6
+ */
7
+ /** The 4 or 16 bytes of an IP address, or `undefined` for anything else, a zone included. */
8
+ export declare function addressBytes(text: string): Uint8Array | undefined;
9
+ /** An IPv4-mapped address as its 4 bytes; any other as it is. */
10
+ export declare function unmapped(bytes: Uint8Array): Uint8Array;
11
+ /**
12
+ * An address's text: dotted for IPv4 (an IPv4-mapped address included),
13
+ * RFC 5952's form for IPv6 — lower case, no leading zeros, the longest
14
+ * run of two zero groups or more as `::`.
15
+ */
16
+ export declare function formatAddress(raw: Uint8Array): string;
17
+ //# sourceMappingURL=address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/address.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAOjE;AAyBD,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAOtD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAiBrD"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The PROXY protocol header (HAProxy's proxy-protocol.txt, versions 1 and
3
+ * 2) a TCP proxy sends before anything of the client's, read from the
4
+ * bytes received so far. `source` is the client's address, or absent to
5
+ * keep the peer's: a v2 LOCAL (a health check from the proxy itself), an
6
+ * UNSPEC, UNIX or datagram address, a v1 UNKNOWN.
7
+ */
8
+ export type ProxyHeader = {
9
+ readonly status: 'partial';
10
+ } | {
11
+ readonly status: 'invalid';
12
+ readonly reason: string;
13
+ } | {
14
+ readonly status: 'complete';
15
+ /** Bytes of the header: what follows is the client's. */
16
+ readonly length: number;
17
+ readonly source?: string;
18
+ };
19
+ /** A v1 line, CRLF included, is 107 bytes at most. */
20
+ export declare const MAX_V1_LENGTH = 107;
21
+ /** v2's addresses take 216 bytes at most (two UNIX paths); TLVs past this are refused. */
22
+ export declare const MAX_TLV_BYTES = 2048;
23
+ /** The longest header read: past it, whatever the bytes, the header is refused. */
24
+ export declare const MAX_HEADER_LENGTH: number;
25
+ export declare function readProxyHeader(bytes: Uint8Array): ProxyHeader;
26
+ //# sourceMappingURL=header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/header.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACpB;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC9B;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvD;IACA,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEL,sDAAsD;AACtD,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,0FAA0F;AAC1F,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,QAA2B,CAAC;AAqB1D,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAK9D"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reads the PROXY header a trusted proxy sends first, chunk by chunk, within
3
+ * `seconds` from the TCP connection, however slowly it comes. `done` gets
4
+ * the client's address (`undefined` to keep the peer's) and what followed
5
+ * the header; `refuse` is called for a header that is malformed, too long
6
+ * or too late. Either is called once, and the reader then ignores input.
7
+ */
8
+ export declare class HeaderReader {
9
+ #private;
10
+ constructor(seconds: number, done: (source: string | undefined, rest: Uint8Array) => void, refuse: () => void);
11
+ receive(chunk: Uint8Array): void;
12
+ /** The socket closed first: no timer is left behind. */
13
+ cancel(): void;
14
+ }
15
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/reader.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,qBAAa,YAAY;;gBAQvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,KAAK,IAAI,EAC5D,MAAM,EAAE,MAAM,IAAI;IAQnB,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAkBhC,wDAAwD;IACxD,MAAM,IAAI,IAAI;CAWd"}
@@ -0,0 +1,50 @@
1
+ import { type SecureContext } from 'node:tls';
2
+ import type { Socket } from 'bun';
3
+ /** What the TLS session tells the listener. */
4
+ export interface TlsEvents {
5
+ /** The handshake completed. */
6
+ secure(): void;
7
+ /** Bytes from the client, decrypted. */
8
+ data(bytes: Uint8Array): void;
9
+ /** What was written has left: the transport may write more. */
10
+ drain(): void;
11
+ }
12
+ /**
13
+ * Implicit TLS behind a PROXY header. A listener with `tls` would take the
14
+ * header for a broken ClientHello, so the listener is a clear one, and TLS
15
+ * starts once the header is read. Bun's `socket.upgradeTLS` cannot be
16
+ * used: it reads only what arrives after it, and a proxy sends the
17
+ * client's ClientHello in the same segment as the header as often as not
18
+ * (Bun 1.4.2: the handshake then never completes). So the TLS session is
19
+ * `node:tls` over a stream this class feeds: what followed the header
20
+ * first, then every chunk of the raw socket's `data` (`receive`).
21
+ *
22
+ * It has the shape of the Bun socket a `SocketTransport` drives. `write`
23
+ * takes nothing while TLS holds more than its high-water mark, and
24
+ * `drain` comes once it took it all; the stream under it hands the raw
25
+ * socket what it takes and waits for the raw `drain` for the rest, so a
26
+ * client that reads slowly holds the server back, never its memory.
27
+ */
28
+ export declare class ProxiedTls {
29
+ #private;
30
+ constructor(raw: Socket<unknown>, context: SecureContext, events: TlsEvents);
31
+ get remoteAddress(): string;
32
+ /** The raw socket's `data`: TLS records from the client. */
33
+ receive(chunk: Uint8Array): void;
34
+ /** The raw socket's `drain`: it can take more of what TLS wrote. */
35
+ rawDrain(): void;
36
+ write(bytes: Uint8Array): number;
37
+ /**
38
+ * Ends TLS — a close_notify after everything written — then the raw
39
+ * socket, as `shutdown(halfClose)` would. Nothing written is lost, so
40
+ * the two differ only in the raw socket's close.
41
+ */
42
+ shutdown(halfClose?: boolean): void;
43
+ terminate(): void;
44
+ /** The raw socket closed: TLS has nowhere to go. */
45
+ closed(): void;
46
+ pause(): void;
47
+ resume(): void;
48
+ timeout(seconds: number): void;
49
+ }
50
+ //# sourceMappingURL=tls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tls.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/tls.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAa,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAElC,+CAA+C;AAC/C,MAAM,WAAW,SAAS;IACzB,+BAA+B;IAC/B,MAAM,IAAI,IAAI,CAAC;IACf,wCAAwC;IACxC,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,KAAK,IAAI,IAAI,CAAC;CACd;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;;gBASV,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS;IAqB3E,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,4DAA4D;IAC5D,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAIhC,oEAAoE;IACpE,QAAQ,IAAI,IAAI;IAmBhB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;IAMhC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,UAAQ,GAAG,IAAI;IAUjC,SAAS,IAAI,IAAI;IAMjB,oDAAoD;IACpD,MAAM,IAAI,IAAI;IAKd,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,IAAI;IAId,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG9B"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `proxyProtocol.trusted` as a test of a peer's address: IPv4 and IPv6
3
+ * addresses and CIDRs, an IPv4-mapped address or peer matched as its IPv4
4
+ * address. An entry that is neither throws, through `invalid`, and so does
5
+ * an empty list (it would trust nobody, which is no proxy at all), a zone
6
+ * (`fe80::1%eth0`) and a prefix of 0, which would trust everyone.
7
+ */
8
+ export declare function trustedPeers(entries: unknown, invalid: (message: string) => Error): (peer: string) => boolean;
9
+ //# sourceMappingURL=trusted.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted.d.ts","sourceRoot":"","sources":["../../../src/server/proxy/trusted.ts"],"names":[],"mappings":"AA8CA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,GACjC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAoB3B"}
@@ -0,0 +1,9 @@
1
+ import type { Socket } from 'bun';
2
+ /**
3
+ * The socket a transport drives: Bun's, or `ProxiedTls` — implicit TLS
4
+ * behind a PROXY header — which has its shape.
5
+ */
6
+ export type RawSocket = Pick<Socket<unknown>, 'remoteAddress' | 'shutdown' | 'terminate' | 'pause' | 'resume' | 'timeout'> & {
7
+ write(bytes: Uint8Array): number;
8
+ };
9
+ //# sourceMappingURL=raw-socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw-socket.d.ts","sourceRoot":"","sources":["../../src/server/raw-socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAElC;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,CAC3B,MAAM,CAAC,OAAO,CAAC,EACf,eAAe,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAC3E,GAAG;IAAE,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAA;CAAE,CAAC"}
@@ -3,6 +3,8 @@ import type { Connection } from './connection';
3
3
  export declare function dateTime(date: Date): string;
4
4
  /** RFC 3848: how the message came — SMTP, ESMTP, then S for TLS and A for AUTH. */
5
5
  export declare function protocolOf(esmtp: boolean, secure: boolean, authenticated: boolean): string;
6
+ /** The client's address as a domain literal (RFC 5321 §4.1.3): an IPv6 address is tagged `IPv6:`. */
7
+ export declare function addressLiteral(address: string): string;
6
8
  /**
7
9
  * The Received field the server puts on top (RFC 5321 §4.4): who sent the
8
10
  * message, from where, how, to whom when there is one recipient, and when.
@@ -1 +1 @@
1
- {"version":3,"file":"received.d.ts","sourceRoot":"","sources":["../../src/server/received.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAoB/C,0CAA0C;AAC1C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE3C;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CACzB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACpB,MAAM,CAGR;AAiBD;;;GAGG;AACH,wBAAgB,aAAa,CAC5B,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,EAAE,EAAE,MAAM,EACV,GAAG,OAAa,GACd,MAAM,CAYR"}
1
+ {"version":3,"file":"received.d.ts","sourceRoot":"","sources":["../../src/server/received.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAoB/C,0CAA0C;AAC1C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE3C;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CACzB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACpB,MAAM,CAGR;AAED,qGAAqG;AACrG,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAItD;AAiBD;;;GAGG;AACH,wBAAgB,aAAa,CAC5B,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,EAAE,EAAE,MAAM,EACV,GAAG,OAAa,GACd,MAAM,CAYR"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAWnD,MAAM,WAAW,UAAU;IAC1B,0FAA0F;IAC1F,MAAM,CAAC,OAAO,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,wEAAwE;IACxE,IAAI,CAAC,gBAAgB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,wBAAwB;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AA2ED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAqEvE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAgDnD,MAAM,WAAW,UAAU;IAC1B,0FAA0F;IAC1F,MAAM,CAAC,OAAO,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,wEAAwE;IACxE,IAAI,CAAC,gBAAgB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,wBAAwB;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAkEvE"}
@@ -6,10 +6,14 @@ export interface Settings {
6
6
  readonly maxMessageSize: number;
7
7
  readonly maxRecipients: number;
8
8
  readonly maxConnections: number;
9
+ readonly maxConnectionsPerClient: number;
9
10
  readonly maxErrors: number;
10
11
  readonly timeout: number;
11
12
  readonly hookTimeout: number;
13
+ readonly handshakeTimeout: number;
12
14
  readonly greetingDelay: number;
15
+ /** With `proxyProtocol`: whether a peer is a proxy trusted to send a PROXY header. */
16
+ readonly trusts?: (peer: string) => boolean;
13
17
  isLocal(domain: string): boolean | Promise<boolean>;
14
18
  }
15
19
  export declare function settingsOf(options: SmtpServerOptions): Settings;
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/server/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAkED,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,QAAQ,CA+C/D"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/server/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGnD,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,sFAAsF;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAkED,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,QAAQ,CA4D/D"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The connections a server holds, and how many each client holds, by
3
+ * `clientKey`. `release` is idempotent: every close handler calls it.
4
+ */
5
+ export declare class Slots<T> {
6
+ #private;
7
+ get size(): number;
8
+ /** How many connections the client `key` holds. */
9
+ of(key: string | undefined): number;
10
+ take(connection: T, key: string | undefined): void;
11
+ release(connection: T): void;
12
+ connections(): T[];
13
+ }
14
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../src/server/slots.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,KAAK,CAAC,CAAC;;IAInB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mDAAmD;IACnD,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;IAInC,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAKlD,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,IAAI;IAU5B,WAAW,IAAI,CAAC,EAAE;CAGlB"}
@@ -0,0 +1,16 @@
1
+ import { type SecureContext } from 'node:tls';
2
+ /** `key` or `cert` as `Bun.listen` takes them: text, bytes or a file. */
3
+ type TlsMaterial = string | Uint8Array | Bun.BunFile;
4
+ /**
5
+ * `tls` read whole — a file included — and checked as a `node:tls` context:
6
+ * what implicit TLS behind a proxy runs on, and, without a proxy, the check
7
+ * that makes a key or certificate that cannot be read or used fail
8
+ * `listen()` the same way. `fail` turns the reason into the package's own
9
+ * error, the `node:tls` or file error as its `cause`.
10
+ */
11
+ export declare function tlsContext(tls: {
12
+ readonly key: TlsMaterial;
13
+ readonly cert: TlsMaterial;
14
+ }, fail: (message: string, cause: unknown) => Error): Promise<SecureContext>;
15
+ export {};
16
+ //# sourceMappingURL=tls-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tls-context.d.ts","sourceRoot":"","sources":["../../src/server/tls-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAEnE,yEAAyE;AACzE,KAAK,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC;AAErD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC/B,GAAG,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,EAC9D,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,KAAK,GAC9C,OAAO,CAAC,aAAa,CAAC,CAcxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/server/transaction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAS/C,iFAAiF;AACjF,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAuDf;AAED,oFAAoF;AACpF,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAwCf"}
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/server/transaction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAS/C,iFAAiF;AACjF,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAuDf;AAED,oFAAoF;AACpF,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAyCf"}
@@ -1,4 +1,4 @@
1
- import type { Socket } from 'bun';
1
+ import type { RawSocket } from './raw-socket';
2
2
  /** What a connection needs from its socket: Bun's, or a fake one in specs. */
3
3
  export interface Transport {
4
4
  readonly remoteAddress: string;
@@ -52,7 +52,7 @@ export declare class SocketTransport implements Transport {
52
52
  #private;
53
53
  readonly remoteAddress: string;
54
54
  readonly secure: boolean;
55
- constructor(socket: Socket<unknown>, secure: boolean, startTls: () => void, remoteAddress?: string);
55
+ constructor(socket: RawSocket, secure: boolean, startTls: () => void, remoteAddress?: string);
56
56
  write(text: string): void;
57
57
  /** Bun's `drain`: the socket can take more. */
58
58
  drain(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAGlC,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uFAAuF;IACvF,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iDAAiD;IACjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB;;;;;OAKG;IACH,GAAG,IAAI,IAAI,CAAC;IACZ;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAC;IACd,kEAAkE;IAClE,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,uEAAuE;IACvE,QAAQ,IAAI,IAAI,CAAC;IACjB,kEAAkE;IAClE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,OAAQ,CAAC;AAEpC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;;GAIG;AACH,qBAAa,eAAgB,YAAW,SAAS;;IAEhD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBAmBxB,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,EACvB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,MAAM,IAAI,EACpB,aAAa,SAAuB;IASrC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKzB,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAIb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB;;;;;;OAMG;IACH,QAAQ,IAAI,IAAI;IAQhB,kDAAkD;IAClD,MAAM,IAAI,IAAI;IAOd,0EAA0E;IAC1E,GAAG,IAAI,IAAI;IAOX,KAAK,IAAI,IAAI;IA0Fb,KAAK,IAAI,IAAI;IAKb,MAAM,IAAI,IAAI;IAKd,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAGlC"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/server/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uFAAuF;IACvF,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iDAAiD;IACjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB;;;;;OAKG;IACH,GAAG,IAAI,IAAI,CAAC;IACZ;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAC;IACd,kEAAkE;IAClE,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,uEAAuE;IACvE,QAAQ,IAAI,IAAI,CAAC;IACjB,kEAAkE;IAClE,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,OAAQ,CAAC;AAEpC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC;;;;GAIG;AACH,qBAAa,eAAgB,YAAW,SAAS;;IAEhD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBAmBxB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,MAAM,IAAI,EACpB,aAAa,SAAuB;IASrC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKzB,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAIb,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB;;;;;;OAMG;IACH,QAAQ,IAAI,IAAI;IAQhB,kDAAkD;IAClD,MAAM,IAAI,IAAI;IAOd,0EAA0E;IAC1E,GAAG,IAAI,IAAI;IAOX,KAAK,IAAI,IAAI;IA0Fb,KAAK,IAAI,IAAI;IAKb,MAAM,IAAI,IAAI;IAKd,QAAQ,IAAI,IAAI;IAIhB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAGlC"}
package/docs/README.md CHANGED
@@ -5,6 +5,6 @@ long one.
5
5
 
6
6
  | Page | Read it when |
7
7
  | --- | --- |
8
- | [Guide](guide.md) | running an MX, a submission server or implicit TLS; reading what a reply means; writing hooks and keeping state in `Session.data`; handling what `onData` receives; delivering into `@bumail/store`; setting TLS, limits and `greetingDelay`; sending mail with `@bumail/smtp/client` and trying it with Mailpit; or checking which RFC a behaviour follows |
9
- | [Troubleshooting](troubleshooting.md) | `createSmtpServer` threw, the server will not listen, a client reports a reply you did not expect, or `sendMail` rejected |
8
+ | [Guide](guide.md) | running an MX, a submission server or implicit TLS; reading what a reply means; writing hooks and keeping state in `Session.data`; handling what `onData` receives; delivering into `@bumail/store`; setting TLS, limits and `greetingDelay`; running behind a TCP proxy such as Traefik with the PROXY protocol; sending mail with `@bumail/smtp/client` and trying it with Mailpit; or checking which RFC a behaviour follows |
9
+ | [Troubleshooting](troubleshooting.md) | `createSmtpServer` threw, the server will not listen, a client reports a reply you did not expect, connections through a proxy close or show the proxy's address, or `sendMail` rejected |
10
10
  | [Roadmap](roadmap.md) | wondering what is coming — connection reuse in the client, DSN, a ready-made delivery into the store — and what is not planned |