@bumail/smtp 0.4.0 → 0.6.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 (43) hide show
  1. package/README.md +64 -5
  2. package/dist/chunks/{index-753q702a.js → index-n4xwep5m.js} +3 -3
  3. package/dist/chunks/{index-753q702a.js.map → index-n4xwep5m.js.map} +3 -3
  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 +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +617 -71
  10. package/dist/index.js.map +15 -8
  11. package/dist/server/admission.d.ts +37 -0
  12. package/dist/server/admission.d.ts.map +1 -0
  13. package/dist/server/listener.d.ts +17 -8
  14. package/dist/server/listener.d.ts.map +1 -1
  15. package/dist/server/options.d.ts +21 -1
  16. package/dist/server/options.d.ts.map +1 -1
  17. package/dist/server/proxy/address.d.ts +17 -0
  18. package/dist/server/proxy/address.d.ts.map +1 -0
  19. package/dist/server/proxy/header.d.ts +26 -0
  20. package/dist/server/proxy/header.d.ts.map +1 -0
  21. package/dist/server/proxy/reader.d.ts +15 -0
  22. package/dist/server/proxy/reader.d.ts.map +1 -0
  23. package/dist/server/proxy/tls.d.ts +50 -0
  24. package/dist/server/proxy/tls.d.ts.map +1 -0
  25. package/dist/server/proxy/trusted.d.ts +9 -0
  26. package/dist/server/proxy/trusted.d.ts.map +1 -0
  27. package/dist/server/raw-socket.d.ts +9 -0
  28. package/dist/server/raw-socket.d.ts.map +1 -0
  29. package/dist/server/received.d.ts +2 -0
  30. package/dist/server/received.d.ts.map +1 -1
  31. package/dist/server/server.d.ts +9 -1
  32. package/dist/server/server.d.ts.map +1 -1
  33. package/dist/server/settings.d.ts +5 -0
  34. package/dist/server/settings.d.ts.map +1 -1
  35. package/dist/server/tls-context.d.ts +46 -0
  36. package/dist/server/tls-context.d.ts.map +1 -0
  37. package/dist/server/transport.d.ts +2 -2
  38. package/dist/server/transport.d.ts.map +1 -1
  39. package/docs/README.md +2 -2
  40. package/docs/guide.md +417 -13
  41. package/docs/roadmap.md +19 -0
  42. package/docs/troubleshooting.md +368 -1
  43. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  decodeLoginStep2,
8
8
  LineSplitter,
9
9
  Outgoing
10
- } from "./chunks/index-753q702a.js";
10
+ } from "./chunks/index-n4xwep5m.js";
11
11
  // src/protocol/command.ts
12
12
  function parseCommand(line) {
13
13
  const space = line.indexOf(" ");
@@ -474,6 +474,7 @@ async function runCommand(connection, line) {
474
474
  }
475
475
 
476
476
  // src/server/received.ts
477
+ import { isIPv6 } from "node:net";
477
478
  var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
478
479
  var MONTHS = [
479
480
  "Jan",
@@ -498,6 +499,10 @@ function protocolOf(esmtp, secure, authenticated) {
498
499
  return "SMTP";
499
500
  return `ESMTP${secure ? "S" : ""}${authenticated ? "A" : ""}`;
500
501
  }
502
+ function addressLiteral(address) {
503
+ const bare = address.replace(/%.*$/, "");
504
+ return isIPv6(bare) ? `IPv6:${bare}` : printable(address);
505
+ }
501
506
  function printable(text) {
502
507
  let out = "";
503
508
  for (const char of text) {
@@ -512,7 +517,7 @@ function plain(address) {
512
517
  function receivedField(connection, recipients, id, now = new Date) {
513
518
  const { state, transport, settings } = connection;
514
519
  const lines = [
515
- `Received: from ${printable(state.helo ?? "unknown")} ([${printable(transport.remoteAddress)}])`,
520
+ `Received: from ${printable(state.helo ?? "unknown")} ([${addressLiteral(transport.remoteAddress)}])`,
516
521
  ` by ${settings.options.hostname} with ${protocolOf(state.esmtp, state.secure, state.user !== undefined)} id ${id}`
517
522
  ];
518
523
  const [recipient] = recipients;
@@ -1124,19 +1129,33 @@ class SocketTransport {
1124
1129
  }
1125
1130
 
1126
1131
  // src/server/listener.ts
1132
+ function received(socket, settings, chunk) {
1133
+ socket.data.transport?.received();
1134
+ socket.timeout(settings.timeout);
1135
+ socket.data.connection?.receive(chunk);
1136
+ }
1127
1137
  function handlers(settings) {
1128
1138
  const idle = reply(421, "4.4.2", `${settings.options.hostname} Idle too long, closing`);
1129
1139
  return {
1130
1140
  data(socket, chunk) {
1131
- if (!socket.data || socket.data.upgraded)
1141
+ const state = socket.data;
1142
+ if (!state || state.upgraded)
1132
1143
  return;
1133
- socket.data.transport?.received();
1134
- socket.timeout(settings.timeout);
1135
- socket.data.connection?.receive(chunk);
1144
+ if (state.header)
1145
+ return state.header.receive(chunk);
1146
+ if (!state.tls)
1147
+ return received(socket, settings, chunk);
1148
+ state.transport?.received();
1149
+ state.tls.receive(chunk);
1136
1150
  },
1137
1151
  drain(socket) {
1138
- if (socket.data && !socket.data.upgraded)
1139
- socket.data.transport?.drain();
1152
+ const state = socket.data;
1153
+ if (!state || state.upgraded)
1154
+ return;
1155
+ if (state.tls)
1156
+ state.tls.rawDrain();
1157
+ else
1158
+ state.transport?.drain();
1140
1159
  },
1141
1160
  error(socket) {
1142
1161
  socket.data?.connection?.close();
@@ -1154,15 +1173,15 @@ function handlers(settings) {
1154
1173
  }
1155
1174
  };
1156
1175
  }
1157
- function turnAway(socket, secure, line) {
1158
- const refused = new SocketTransport(socket, secure, () => {});
1176
+ function turnAway(socket, raw, secure, line) {
1177
+ const refused = new SocketTransport(raw, secure, () => {});
1159
1178
  socket.data.transport = refused;
1160
1179
  refused.write(line);
1161
1180
  refused.end();
1162
1181
  }
1163
- function transportOf(socket, settings, secure, slots) {
1164
- const transport = new SocketTransport(socket, secure, () => {
1165
- const tls = settings.options.tls;
1182
+ function transportOf(socket, raw, address, settings, secure, slots) {
1183
+ const transport = new SocketTransport(raw, secure, () => {
1184
+ const tls = settings.tls?.options;
1166
1185
  const connection = socket.data.connection;
1167
1186
  if (!tls || !connection)
1168
1187
  return;
@@ -1180,53 +1199,341 @@ function transportOf(socket, settings, secure, slots) {
1180
1199
  }
1181
1200
  });
1182
1201
  encrypted.timeout(settings.timeout);
1183
- const next = new SocketTransport(encrypted, true, () => {}, socket.remoteAddress);
1202
+ const next = new SocketTransport(encrypted, true, () => {}, address);
1184
1203
  encrypted.data.transport = next;
1185
1204
  connection.useTransport(next);
1186
- });
1205
+ }, address);
1187
1206
  socket.data.transport = transport;
1188
1207
  return transport;
1189
1208
  }
1190
- function listenerHandlers(settings, slots, handshaking, secure) {
1209
+
1210
+ // src/server/proxy/address.ts
1211
+ import { isIP as isIP2 } from "node:net";
1212
+ function addressBytes(text) {
1213
+ if (text.includes("%"))
1214
+ return;
1215
+ const kind = isIP2(text);
1216
+ if (kind === 4)
1217
+ return new Uint8Array(text.split(".").map(Number));
1218
+ if (kind !== 6)
1219
+ return;
1220
+ return unmapped(ipv6Bytes(text.toLowerCase()));
1221
+ }
1222
+ function ipv6Bytes(text) {
1223
+ const bytes = new Uint8Array(16);
1224
+ let tail = text;
1225
+ const dotted = /(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(text);
1226
+ if (dotted) {
1227
+ bytes.set(dotted.slice(1).map(Number), 12);
1228
+ tail = `${text.slice(0, dotted.index)}0:0`;
1229
+ }
1230
+ const [head = "", rest] = tail.split("::");
1231
+ const left = head === "" ? [] : head.split(":");
1232
+ const right = rest === undefined || rest === "" ? [] : rest.split(":");
1233
+ const groups = rest === undefined ? left : [...left, ...Array(8 - left.length - right.length).fill("0"), ...right];
1234
+ const view = new DataView(bytes.buffer);
1235
+ groups.forEach((group, at) => {
1236
+ if (!dotted || at < 6)
1237
+ view.setUint16(at * 2, Number.parseInt(group, 16));
1238
+ });
1239
+ return bytes;
1240
+ }
1241
+ function unmapped(bytes) {
1242
+ if (bytes.length !== 16)
1243
+ return bytes;
1244
+ const mapped = bytes.subarray(0, 10).every((byte) => byte === 0) && bytes[10] === 255 && bytes[11] === 255;
1245
+ return mapped ? bytes.slice(12) : bytes;
1246
+ }
1247
+ function formatAddress(raw) {
1248
+ const bytes = unmapped(raw);
1249
+ if (bytes.length === 4)
1250
+ return bytes.join(".");
1251
+ const view = new DataView(bytes.buffer, bytes.byteOffset, 16);
1252
+ const groups = Array.from({ length: 8 }, (_, at) => view.getUint16(at * 2));
1253
+ let best = { at: -1, length: 1 };
1254
+ for (let at = 0;at < 8; ) {
1255
+ let end = at;
1256
+ while (end < 8 && groups[end] === 0)
1257
+ end++;
1258
+ if (end - at > best.length)
1259
+ best = { at, length: end - at };
1260
+ at = end === at ? at + 1 : end;
1261
+ }
1262
+ const hex = groups.map((group) => group.toString(16));
1263
+ if (best.at < 0)
1264
+ return hex.join(":");
1265
+ const left = hex.slice(0, best.at).join(":");
1266
+ const right = hex.slice(best.at + best.length).join(":");
1267
+ return `${left}::${right}`;
1268
+ }
1269
+
1270
+ // src/server/proxy/header.ts
1271
+ var MAX_V1_LENGTH = 107;
1272
+ var MAX_TLV_BYTES = 2048;
1273
+ var MAX_HEADER_LENGTH = 16 + 216 + MAX_TLV_BYTES;
1274
+ var V1_PREFIX = new TextEncoder().encode("PROXY ");
1275
+ var V2_SIGNATURE = new Uint8Array([
1276
+ 13,
1277
+ 10,
1278
+ 13,
1279
+ 10,
1280
+ 0,
1281
+ 13,
1282
+ 10,
1283
+ 81,
1284
+ 85,
1285
+ 73,
1286
+ 84,
1287
+ 10
1288
+ ]);
1289
+ var ADDRESS_LENGTH = [0, 12, 36, 216];
1290
+ var PARTIAL = { status: "partial" };
1291
+ var invalid = (reason) => ({ status: "invalid", reason });
1292
+ function startsAs(bytes, prefix) {
1293
+ const length = Math.min(bytes.length, prefix.length);
1294
+ for (let at = 0;at < length; at++) {
1295
+ if (bytes[at] !== prefix[at])
1296
+ return false;
1297
+ }
1298
+ return true;
1299
+ }
1300
+ function readProxyHeader(bytes) {
1301
+ if (bytes.length === 0)
1302
+ return PARTIAL;
1303
+ if (startsAs(bytes, V2_SIGNATURE))
1304
+ return readV2(bytes);
1305
+ if (startsAs(bytes, V1_PREFIX))
1306
+ return readV1(bytes);
1307
+ return invalid("no PROXY protocol signature");
1308
+ }
1309
+ function readV2(bytes) {
1310
+ if (bytes.length < 16)
1311
+ return PARTIAL;
1312
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1313
+ const versionCommand = view.getUint8(12);
1314
+ if (versionCommand >> 4 !== 2)
1315
+ return invalid("not version 2");
1316
+ const command = versionCommand & 15;
1317
+ if (command > 1)
1318
+ return invalid(`unknown command ${command}`);
1319
+ const family = view.getUint8(13) >> 4;
1320
+ const transport = view.getUint8(13) & 15;
1321
+ const length = view.getUint16(14);
1322
+ const addresses = command === 0 ? 0 : ADDRESS_LENGTH[family];
1323
+ if (addresses === undefined || transport > 2) {
1324
+ return invalid("unknown address family or transport");
1325
+ }
1326
+ if (length < addresses)
1327
+ return invalid("address block too short");
1328
+ if (length - addresses > MAX_TLV_BYTES)
1329
+ return invalid("TLVs too long");
1330
+ const total = 16 + length;
1331
+ if (bytes.length < total)
1332
+ return PARTIAL;
1333
+ if (command === 0)
1334
+ return { status: "complete", length: total };
1335
+ const tlvs = bytes.subarray(16 + addresses, total);
1336
+ if (!wellFormedTlvs(tlvs))
1337
+ return invalid("malformed TLV");
1338
+ if (transport !== 1 || family !== 1 && family !== 2) {
1339
+ return { status: "complete", length: total };
1340
+ }
1341
+ const size = family === 1 ? 4 : 16;
1342
+ const source = formatAddress(bytes.slice(16, 16 + size));
1343
+ return { status: "complete", length: total, source };
1344
+ }
1345
+ function wellFormedTlvs(block) {
1346
+ let at = 0;
1347
+ while (at < block.length) {
1348
+ if (at + 3 > block.length)
1349
+ return false;
1350
+ const length = block[at + 1] << 8 | block[at + 2];
1351
+ at += 3 + length;
1352
+ }
1353
+ return at === block.length;
1354
+ }
1355
+ var PORT = /^(0|[1-9]\d{0,4})$/;
1356
+ function readV1(bytes) {
1357
+ const end = bytes.indexOf(10);
1358
+ if (end < 0) {
1359
+ return bytes.length >= MAX_V1_LENGTH ? invalid("v1 line too long") : PARTIAL;
1360
+ }
1361
+ if (end + 1 > MAX_V1_LENGTH)
1362
+ return invalid("v1 line too long");
1363
+ if (bytes[end - 1] !== 13)
1364
+ return invalid("v1 line without CRLF");
1365
+ const line = bytes.subarray(0, end - 1);
1366
+ if (line.some((byte) => byte < 32 || byte > 126)) {
1367
+ return invalid("v1 line not printable");
1368
+ }
1369
+ const [, protocol, ...rest] = new TextDecoder().decode(line).split(" ");
1370
+ if (protocol === "UNKNOWN")
1371
+ return { status: "complete", length: end + 1 };
1372
+ const [source = "", destination = "", sourcePort = "", port = ""] = rest;
1373
+ const size = protocol === "TCP4" ? 4 : protocol === "TCP6" ? 16 : 0;
1374
+ if (size === 0)
1375
+ return invalid("v1 protocol not TCP4, TCP6 or UNKNOWN");
1376
+ const from = rawAddress(source, size);
1377
+ if (rest.length !== 4 || !from || !rawAddress(destination, size) || !validPort(sourcePort) || !validPort(port)) {
1378
+ return invalid("v1 addresses or ports malformed");
1379
+ }
1380
+ return { status: "complete", length: end + 1, source: formatAddress(from) };
1381
+ }
1382
+ function rawAddress(text, size) {
1383
+ if (size === 4 && text.includes(":"))
1384
+ return;
1385
+ if (size === 16 && !text.includes(":"))
1386
+ return;
1387
+ return addressBytes(text);
1388
+ }
1389
+ function validPort(text) {
1390
+ return PORT.test(text) && Number(text) <= 65535;
1391
+ }
1392
+
1393
+ // src/server/proxy/reader.ts
1394
+ class HeaderReader {
1395
+ #bytes = new Uint8Array(0);
1396
+ #timer;
1397
+ #settled = false;
1398
+ #done;
1399
+ #refuse;
1400
+ constructor(seconds, done, refuse) {
1401
+ this.#done = done;
1402
+ this.#refuse = refuse;
1403
+ this.#timer = setTimeout(() => this.#settle(refuse), seconds * 1000);
1404
+ this.#timer.unref?.();
1405
+ }
1406
+ receive(chunk) {
1407
+ if (this.#settled)
1408
+ return;
1409
+ const bytes = new Uint8Array(this.#bytes.length + chunk.length);
1410
+ bytes.set(this.#bytes);
1411
+ bytes.set(chunk, this.#bytes.length);
1412
+ this.#bytes = bytes;
1413
+ const header = readProxyHeader(bytes);
1414
+ if (header.status === "complete") {
1415
+ const rest = bytes.slice(header.length);
1416
+ this.#settle(() => this.#done(header.source, rest));
1417
+ } else if (header.status === "invalid" || bytes.length >= MAX_HEADER_LENGTH) {
1418
+ this.#settle(this.#refuse);
1419
+ }
1420
+ }
1421
+ cancel() {
1422
+ this.#settled = true;
1423
+ clearTimeout(this.#timer);
1424
+ this.#bytes = new Uint8Array(0);
1425
+ }
1426
+ #settle(then) {
1427
+ if (this.#settled)
1428
+ return;
1429
+ this.cancel();
1430
+ then();
1431
+ }
1432
+ }
1433
+
1434
+ // src/server/proxy/tls.ts
1435
+ import { Duplex } from "node:stream";
1436
+ import { TLSSocket } from "node:tls";
1437
+
1438
+ class ProxiedTls {
1439
+ #raw;
1440
+ #wire;
1441
+ #tls;
1442
+ #pending;
1443
+ #halfClose = true;
1444
+ constructor(raw, context, events) {
1445
+ this.#raw = raw;
1446
+ this.#wire = new Duplex({
1447
+ read() {},
1448
+ write: (chunk, _encoding, done) => this.#send(chunk, done),
1449
+ final: (done) => {
1450
+ this.#raw.shutdown(this.#halfClose);
1451
+ done();
1452
+ }
1453
+ });
1454
+ this.#tls = new TLSSocket(this.#wire, {
1455
+ isServer: true,
1456
+ secureContext: context
1457
+ });
1458
+ this.#tls.on("secure", () => events.secure());
1459
+ this.#tls.on("data", (bytes) => events.data(bytes));
1460
+ this.#tls.on("drain", () => events.drain());
1461
+ this.#tls.on("error", () => this.terminate());
1462
+ }
1463
+ get remoteAddress() {
1464
+ return this.#raw.remoteAddress;
1465
+ }
1466
+ receive(chunk) {
1467
+ this.#wire.push(Uint8Array.from(chunk));
1468
+ }
1469
+ rawDrain() {
1470
+ const pending = this.#pending;
1471
+ if (!pending)
1472
+ return;
1473
+ const written = Math.max(this.#raw.write(pending.bytes), 0);
1474
+ if (written < pending.bytes.length) {
1475
+ pending.bytes = pending.bytes.subarray(written);
1476
+ return;
1477
+ }
1478
+ this.#pending = undefined;
1479
+ pending.done();
1480
+ }
1481
+ #send(chunk, done) {
1482
+ const written = Math.max(this.#raw.write(chunk), 0);
1483
+ if (written < chunk.length) {
1484
+ this.#pending = { bytes: chunk.subarray(written), done };
1485
+ } else
1486
+ done();
1487
+ }
1488
+ write(bytes) {
1489
+ if (this.#tls.destroyed || this.#tls.writableNeedDrain)
1490
+ return 0;
1491
+ this.#tls.write(bytes);
1492
+ return bytes.length;
1493
+ }
1494
+ shutdown(halfClose = false) {
1495
+ this.#halfClose = halfClose;
1496
+ this.#tls.once("finish", () => this.#wire.end());
1497
+ this.#tls.end();
1498
+ }
1499
+ terminate() {
1500
+ this.#pending = undefined;
1501
+ this.#tls.destroy();
1502
+ this.#raw.terminate();
1503
+ }
1504
+ closed() {
1505
+ this.#pending = undefined;
1506
+ this.#tls.destroy();
1507
+ }
1508
+ pause() {
1509
+ this.#raw.pause();
1510
+ }
1511
+ resume() {
1512
+ this.#raw.resume();
1513
+ }
1514
+ timeout(seconds) {
1515
+ this.#raw.timeout(seconds);
1516
+ }
1517
+ }
1518
+
1519
+ // src/server/admission.ts
1520
+ function listenerHandlers(listening) {
1521
+ const { settings, slots, handshaking, secure } = listening;
1191
1522
  return {
1192
1523
  open(socket) {
1193
1524
  socket.data = { upgraded: false, handshaking: secure };
1194
1525
  socket.timeout(secure ? settings.handshakeTimeout : settings.timeout);
1195
- const begin = (start) => {
1196
- if (!secure)
1197
- return start();
1198
- handshaking.add(socket);
1199
- socket.data.start = start;
1200
- };
1201
- if (slots.size >= settings.maxConnections) {
1202
- return begin(() => turnAway(socket, secure, `421 4.3.2 ${settings.options.hostname} Too many connections, try later\r
1203
- `));
1204
- }
1205
- const key = clientKey(socket.remoteAddress);
1206
- if (slots.of(key) >= settings.maxConnectionsPerClient) {
1207
- return begin(() => turnAway(socket, secure, `421 4.7.0 ${settings.options.hostname} Too many connections from your address, try later\r
1208
- `));
1209
- }
1210
- const connection = new Connection(settings, transportOf(socket, settings, secure, slots));
1211
- slots.take(connection, key);
1212
- socket.data.connection = connection;
1213
- begin(() => void connection.open());
1214
- },
1215
- handshake(socket, success) {
1216
- const state = socket.data;
1217
- if (!state?.handshaking)
1218
- return;
1219
- handshaking.delete(socket);
1220
- if (!success)
1221
- return;
1222
- state.handshaking = false;
1223
- socket.timeout(settings.timeout);
1224
- const start = state.start;
1225
- delete state.start;
1226
- start?.();
1526
+ const peer = socket.remoteAddress;
1527
+ if (settings.trusts?.(peer))
1528
+ awaitHeader(listening, socket, peer);
1529
+ else
1530
+ admit(listening, socket, peer);
1227
1531
  },
1532
+ handshake: (socket, success) => handshake(listening, socket, success),
1228
1533
  close(socket) {
1229
1534
  handshaking.delete(socket);
1535
+ socket.data?.header?.cancel();
1536
+ socket.data?.tls?.closed();
1230
1537
  const connection = socket.data?.connection;
1231
1538
  if (connection)
1232
1539
  slots.release(connection);
@@ -1235,22 +1542,223 @@ function listenerHandlers(settings, slots, handshaking, secure) {
1235
1542
  }
1236
1543
  };
1237
1544
  }
1545
+ function handshake({ settings, handshaking }, socket, success) {
1546
+ const state = socket.data;
1547
+ if (!state?.handshaking)
1548
+ return;
1549
+ handshaking.delete(socket);
1550
+ if (!success)
1551
+ return;
1552
+ state.handshaking = false;
1553
+ socket.timeout(settings.timeout);
1554
+ const start = state.start;
1555
+ delete state.start;
1556
+ start?.();
1557
+ }
1558
+ function awaitHeader(listening, socket, peer) {
1559
+ const { settings, handshaking, secure } = listening;
1560
+ socket.data.handshaking = true;
1561
+ socket.timeout(settings.handshakeTimeout);
1562
+ handshaking.add(socket);
1563
+ socket.data.header = new HeaderReader(settings.handshakeTimeout, (source, rest) => {
1564
+ delete socket.data.header;
1565
+ handshaking.delete(socket);
1566
+ socket.data.handshaking = secure;
1567
+ if (!secure)
1568
+ socket.timeout(settings.timeout);
1569
+ admit(listening, socket, source ?? peer, rest);
1570
+ }, () => socket.terminate());
1571
+ }
1572
+ function admit(listening, socket, address, first) {
1573
+ const { settings, slots, handshaking, secure, proxied } = listening;
1574
+ let raw = socket;
1575
+ if (proxied) {
1576
+ raw = startTls2(listening, socket, proxied);
1577
+ if (first && first.length > 0)
1578
+ socket.data.tls?.receive(first);
1579
+ }
1580
+ const begin = (start) => {
1581
+ if (!secure)
1582
+ return start();
1583
+ handshaking.add(socket);
1584
+ socket.data.start = start;
1585
+ };
1586
+ const { hostname } = settings.options;
1587
+ const refuse = (line) => begin(() => turnAway(socket, raw, secure, `${line}\r
1588
+ `));
1589
+ if (slots.size >= settings.maxConnections) {
1590
+ refuse(`421 4.3.2 ${hostname} Too many connections, try later`);
1591
+ return;
1592
+ }
1593
+ const key = clientKey(address);
1594
+ if (slots.of(key) >= settings.maxConnectionsPerClient) {
1595
+ refuse(`421 4.7.0 ${hostname} Too many connections from your address, try later`);
1596
+ return;
1597
+ }
1598
+ const transport = transportOf(socket, raw, address, settings, secure, slots);
1599
+ const connection = new Connection(settings, transport);
1600
+ slots.take(connection, key);
1601
+ socket.data.connection = connection;
1602
+ if (first && first.length > 0 && !proxied) {
1603
+ received(socket, settings, first);
1604
+ }
1605
+ begin(() => void connection.open());
1606
+ }
1607
+ function startTls2(listening, socket, tls) {
1608
+ const { settings } = listening;
1609
+ socket.data.handshaking = true;
1610
+ socket.timeout(settings.handshakeTimeout);
1611
+ const proxied = new ProxiedTls(socket, tls.context, {
1612
+ secure: () => handshake(listening, socket, true),
1613
+ data: (bytes) => received(socket, settings, bytes),
1614
+ drain: () => socket.data.transport?.drain()
1615
+ });
1616
+ socket.data.tls = proxied;
1617
+ return proxied;
1618
+ }
1619
+ function upgradingHandlers(encrypted, settings) {
1620
+ const ignore = () => {};
1621
+ return {
1622
+ open(raw) {
1623
+ const tls = settings.tls?.options;
1624
+ if (!tls)
1625
+ return raw.terminate();
1626
+ try {
1627
+ raw.upgradeTLS({
1628
+ tls: { key: tls.key, cert: tls.cert },
1629
+ data: { upgraded: false },
1630
+ socket: encrypted
1631
+ });
1632
+ } catch {
1633
+ raw.terminate();
1634
+ }
1635
+ },
1636
+ data: ignore,
1637
+ drain: ignore,
1638
+ close: ignore,
1639
+ error: ignore,
1640
+ timeout: ignore
1641
+ };
1642
+ }
1643
+
1644
+ // src/server/proxy/trusted.ts
1645
+ function networkOf(entry) {
1646
+ if (typeof entry !== "string")
1647
+ return `${String(entry)} is not a string`;
1648
+ const [address = "", prefix, extra] = entry.split("/");
1649
+ const raw = addressBytes(address);
1650
+ if (!raw || extra !== undefined) {
1651
+ return `"${entry}" is neither an IP address nor a CIDR`;
1652
+ }
1653
+ const mapped = raw.length === 4 && address.includes(":");
1654
+ const width = raw.length * 8;
1655
+ if (prefix === undefined)
1656
+ return { bytes: raw, bits: width };
1657
+ let bits = /^\d{1,3}$/.test(prefix) ? Number(prefix) : -1;
1658
+ if (mapped)
1659
+ bits -= 96;
1660
+ if (bits < 0 || bits > width) {
1661
+ return `"${entry}" has a prefix length out of range`;
1662
+ }
1663
+ if (bits === 0) {
1664
+ return `"${entry}" has a prefix length of 0, which trusts every peer`;
1665
+ }
1666
+ return { bytes: raw, bits };
1667
+ }
1668
+ function contains(network, address) {
1669
+ if (network.bytes.length !== address.length)
1670
+ return false;
1671
+ const whole = network.bits >> 3;
1672
+ for (let at = 0;at < whole; at++) {
1673
+ if (network.bytes[at] !== address[at])
1674
+ return false;
1675
+ }
1676
+ const rest = network.bits & 7;
1677
+ if (rest === 0)
1678
+ return true;
1679
+ const mask = 255 << 8 - rest & 255;
1680
+ return (network.bytes[whole] & mask) === (address[whole] & mask);
1681
+ }
1682
+ function trustedPeers(entries, invalid) {
1683
+ if (!Array.isArray(entries) || entries.length === 0) {
1684
+ throw invalid("proxyProtocol.trusted must list the addresses or CIDRs of the proxies, at least one");
1685
+ }
1686
+ const networks = entries.map((entry) => {
1687
+ const network = networkOf(entry);
1688
+ if (typeof network === "string") {
1689
+ throw invalid(`proxyProtocol.trusted: ${network}`);
1690
+ }
1691
+ return network;
1692
+ });
1693
+ return (peer) => {
1694
+ const address = addressBytes(peer);
1695
+ return address !== undefined && networks.some((network) => contains(network, address));
1696
+ };
1697
+ }
1698
+
1699
+ // src/server/tls-context.ts
1700
+ import { createSecureContext } from "node:tls";
1701
+ async function readTls(tls, fail, caller) {
1702
+ const read = async (value) => typeof value === "string" ? value : Buffer.from(value instanceof Uint8Array ? value : await value.bytes());
1703
+ try {
1704
+ const key = await read(tls.key);
1705
+ const cert = await read(tls.cert);
1706
+ if (key.length === 0 || cert.length === 0) {
1707
+ throw new Error(`the ${key.length === 0 ? "key" : "cert"} is empty`);
1708
+ }
1709
+ return { key, cert, context: createSecureContext({ key, cert }) };
1710
+ } catch (cause) {
1711
+ const reason = cause instanceof Error ? cause.message : String(cause);
1712
+ throw fail(`${caller}: tls: { key, cert } cannot be used: ${reason}`, cause);
1713
+ }
1714
+ }
1715
+
1716
+ class TlsHolder {
1717
+ #current;
1718
+ #tail = Promise.resolve();
1719
+ #given;
1720
+ constructor(given) {
1721
+ this.#given = given;
1722
+ }
1723
+ get options() {
1724
+ return this.#current ?? this.#given;
1725
+ }
1726
+ get context() {
1727
+ if (!this.#current)
1728
+ throw new Error("the TLS pair was never read");
1729
+ return this.#current.context;
1730
+ }
1731
+ async load(fail, caller) {
1732
+ if (this.#current)
1733
+ return this.#current.context;
1734
+ const read = await readTls(this.#given, fail, caller);
1735
+ this.#current ??= read;
1736
+ return this.#current.context;
1737
+ }
1738
+ replace(tls, fail, caller) {
1739
+ const run = this.#tail.then(async () => {
1740
+ this.#current = await readTls(tls, fail, caller);
1741
+ });
1742
+ this.#tail = run.catch(() => {});
1743
+ return run;
1744
+ }
1745
+ }
1238
1746
 
1239
1747
  // src/server/settings.ts
1240
1748
  var MAX_TIMER_SECONDS = Math.floor(2147483647 / 1000);
1241
- var invalid = (message) => new SmtpError2("INVALID_OPTION", `createSmtpServer(): ${message}`);
1749
+ var invalid2 = (message) => new SmtpError2("INVALID_OPTION", `createSmtpServer(): ${message}`);
1242
1750
  function positive(name, value, fallback) {
1243
1751
  if (value === undefined)
1244
1752
  return fallback;
1245
1753
  if (!Number.isSafeInteger(value) || value < 1) {
1246
- throw invalid(`${name} must be a positive integer, not ${value}`);
1754
+ throw invalid2(`${name} must be a positive integer, not ${value}`);
1247
1755
  }
1248
1756
  return value;
1249
1757
  }
1250
1758
  function timer(name, value, fallback) {
1251
1759
  const seconds = positive(name, value, fallback);
1252
1760
  if (seconds > MAX_TIMER_SECONDS) {
1253
- throw invalid(`${name} must be at most ${MAX_TIMER_SECONDS} seconds, not ${seconds}`);
1761
+ throw invalid2(`${name} must be at most ${MAX_TIMER_SECONDS} seconds, not ${seconds}`);
1254
1762
  }
1255
1763
  return seconds;
1256
1764
  }
@@ -1258,7 +1766,7 @@ function nonNegative(name, value, fallback) {
1258
1766
  if (value === undefined)
1259
1767
  return fallback;
1260
1768
  if (!Number.isFinite(value) || value < 0) {
1261
- throw invalid(`${name} must be a number of seconds, 0 or more, not ${value}`);
1769
+ throw invalid2(`${name} must be a number of seconds, 0 or more, not ${value}`);
1262
1770
  }
1263
1771
  return value;
1264
1772
  }
@@ -1269,32 +1777,33 @@ function localLookup(local) {
1269
1777
  const domains = new Set(local.map((domain) => domain.toLowerCase()));
1270
1778
  return (domain) => domains.has(domain);
1271
1779
  }
1272
- throw invalid("localDomains must be an array of domains or a function");
1780
+ throw invalid2("localDomains must be an array of domains or a function");
1273
1781
  }
1274
1782
  function settingsOf(options) {
1275
1783
  if (typeof options.hostname !== "string" || !/^[A-Za-z0-9.-]+$/.test(options.hostname)) {
1276
- throw invalid(`"${options.hostname}" is not a host name`);
1784
+ throw invalid2(`"${options.hostname}" is not a host name`);
1277
1785
  }
1278
1786
  if (typeof options.onData !== "function") {
1279
- throw invalid("onData must be a function: it is where messages go");
1787
+ throw invalid2("onData must be a function: it is where messages go");
1280
1788
  }
1281
1789
  if (options.implicitTls && !options.tls) {
1282
- throw invalid("implicitTls needs tls: { key, cert }");
1790
+ throw invalid2("implicitTls needs tls: { key, cert }");
1283
1791
  }
1284
1792
  if (options.mode === "submission" && !options.authenticate) {
1285
- throw invalid("submission takes mail only from authenticated users, so it needs authenticate");
1793
+ throw invalid2("submission takes mail only from authenticated users, so it needs authenticate");
1286
1794
  }
1287
1795
  if (options.authenticate && !options.tls) {
1288
- throw invalid("authenticate needs tls: { key, cert }, since AUTH is offered only once encrypted");
1796
+ throw invalid2("authenticate needs tls: { key, cert }, since AUTH is offered only once encrypted");
1289
1797
  }
1290
1798
  const isLocal = localLookup(options.localDomains);
1291
1799
  const timeout = positive("timeout", options.timeout, 300);
1292
1800
  const greetingDelay = nonNegative("greetingDelay", options.greetingDelay, 0);
1293
1801
  if (greetingDelay >= timeout) {
1294
- throw invalid(`greetingDelay (${greetingDelay} s) must be shorter than timeout (${timeout} s), or every client times out before the greeting`);
1802
+ throw invalid2(`greetingDelay (${greetingDelay} s) must be shorter than timeout (${timeout} s), or every client times out before the greeting`);
1295
1803
  }
1296
1804
  return {
1297
1805
  options,
1806
+ ...options.tls ? { tls: new TlsHolder(options.tls) } : {},
1298
1807
  mode: options.mode ?? "mx",
1299
1808
  maxMessageSize: positive("maxMessageSize", options.maxMessageSize, 25 * 1024 * 1024),
1300
1809
  maxRecipients: positive("maxRecipients", options.maxRecipients, 100),
@@ -1305,6 +1814,7 @@ function settingsOf(options) {
1305
1814
  hookTimeout: timer("hookTimeout", options.hookTimeout, 60),
1306
1815
  handshakeTimeout: positive("handshakeTimeout", options.handshakeTimeout, 10),
1307
1816
  greetingDelay,
1817
+ ...options.proxyProtocol === undefined ? {} : { trusts: trustedPeers(options.proxyProtocol?.trusted, invalid2) },
1308
1818
  isLocal: (domain) => isLocal(domain.toLowerCase())
1309
1819
  };
1310
1820
  }
@@ -1343,32 +1853,68 @@ class Slots {
1343
1853
  }
1344
1854
 
1345
1855
  // src/server/server.ts
1856
+ var invalidTls = (message, cause) => new SmtpError2("INVALID_OPTION", message, { cause });
1857
+ var STOPPED = "listen(): stop() was called before the server bound its port";
1858
+ async function bind(listening, port, hostname, stopped) {
1859
+ const { tls } = listening.settings;
1860
+ if (listening.secure && tls)
1861
+ await tls.load(invalidTls, "listen()");
1862
+ if (stopped()) {
1863
+ throw new SmtpError2("STOPPED", STOPPED);
1864
+ }
1865
+ const proxied = listening.secure && listening.settings.trusts ? tls : undefined;
1866
+ const full = {
1867
+ ...handlers(listening.settings),
1868
+ ...listenerHandlers({
1869
+ ...listening,
1870
+ ...proxied ? { proxied } : {}
1871
+ })
1872
+ };
1873
+ return Bun.listen({
1874
+ hostname,
1875
+ port,
1876
+ socket: listening.secure && !proxied ? upgradingHandlers(full, listening.settings) : full
1877
+ });
1878
+ }
1346
1879
  function createSmtpServer(options) {
1347
1880
  const settings = settingsOf(options);
1348
1881
  let listener;
1349
1882
  const slots = new Slots;
1350
1883
  const handshaking = new Set;
1351
1884
  const secure = options.implicitTls === true;
1885
+ let starting = false;
1886
+ let stopRequested = false;
1352
1887
  return {
1353
1888
  get connections() {
1354
1889
  return slots.size;
1355
1890
  },
1891
+ async setTls(tls) {
1892
+ if (!settings.tls) {
1893
+ throw new SmtpError2("INVALID_OPTION", "setTls(): the server was made without tls, so it has none to replace");
1894
+ }
1895
+ await settings.tls.replace(tls, invalidTls, "setTls()");
1896
+ },
1356
1897
  async listen({ port, hostname = "0.0.0.0" }) {
1357
- if (listener) {
1358
- throw new SmtpError2("ALREADY_LISTENING", `listen(): the server is already listening on ${listener.hostname}:${listener.port}`);
1898
+ if (listener || starting) {
1899
+ throw new SmtpError2("ALREADY_LISTENING", listener ? `listen(): the server is already listening on ${listener.hostname}:${listener.port}` : "listen(): the server is already starting to listen");
1359
1900
  }
1360
- listener = Bun.listen({
1361
- hostname,
1362
- port,
1363
- ...secure && options.tls ? { tls: { key: options.tls.key, cert: options.tls.cert } } : {},
1364
- socket: {
1365
- ...handlers(settings),
1366
- ...listenerHandlers(settings, slots, handshaking, secure)
1901
+ starting = true;
1902
+ stopRequested = false;
1903
+ try {
1904
+ const bound = await bind({ settings, slots, handshaking, secure }, port, hostname, () => stopRequested);
1905
+ if (stopRequested) {
1906
+ bound.stop(true);
1907
+ throw new SmtpError2("STOPPED", STOPPED);
1367
1908
  }
1368
- });
1369
- return { port: listener.port, hostname: listener.hostname };
1909
+ listener = bound;
1910
+ return { port: listener.port, hostname: listener.hostname };
1911
+ } finally {
1912
+ starting = false;
1913
+ }
1370
1914
  },
1371
1915
  stop(closeConnections = false) {
1916
+ if (starting)
1917
+ stopRequested = true;
1372
1918
  listener?.stop(closeConnections);
1373
1919
  listener = undefined;
1374
1920
  if (!closeConnections)
@@ -1400,5 +1946,5 @@ export {
1400
1946
  reply
1401
1947
  };
1402
1948
 
1403
- //# debugId=388406E838895A3964756E2164756E21
1949
+ //# debugId=2DE3A3E5E10EAC4A64756E2164756E21
1404
1950
  //# sourceMappingURL=index.js.map