@bumail/smtp 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -4
- package/dist/chunks/{index-3423jxf9.js → index-753q702a.js} +9 -3
- package/dist/chunks/index-753q702a.js.map +14 -0
- package/dist/client/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +226 -108
- package/dist/index.js.map +10 -7
- package/dist/protocol/command.d.ts +4 -2
- package/dist/protocol/command.d.ts.map +1 -1
- package/dist/protocol/path.d.ts +13 -2
- package/dist/protocol/path.d.ts.map +1 -1
- package/dist/server/client-key.d.ts +15 -0
- package/dist/server/client-key.d.ts.map +1 -0
- package/dist/server/listener.d.ts +37 -0
- package/dist/server/listener.d.ts.map +1 -0
- package/dist/server/options.d.ts +25 -1
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/settings.d.ts +2 -0
- package/dist/server/settings.d.ts.map +1 -1
- package/dist/server/slots.d.ts +14 -0
- package/dist/server/slots.d.ts.map +1 -0
- package/dist/server/transaction.d.ts.map +1 -1
- package/docs/guide.md +130 -8
- package/docs/roadmap.md +44 -0
- package/docs/troubleshooting.md +84 -3
- package/package.json +2 -2
- package/dist/chunks/index-3423jxf9.js.map +0 -14
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SmtpError2,
|
|
3
|
+
POSTMASTER,
|
|
3
4
|
isHelloName,
|
|
4
5
|
parsePath2,
|
|
5
6
|
decodePlain2,
|
|
6
7
|
decodeLoginStep2,
|
|
7
8
|
LineSplitter,
|
|
8
9
|
Outgoing
|
|
9
|
-
} from "./chunks/index-
|
|
10
|
+
} from "./chunks/index-753q702a.js";
|
|
10
11
|
// src/protocol/command.ts
|
|
11
12
|
function parseCommand(line) {
|
|
12
13
|
const space = line.indexOf(" ");
|
|
@@ -21,7 +22,8 @@ function parsePathCommand(argument, keyword) {
|
|
|
21
22
|
const match = new RegExp(`^${keyword}:\\s*(<[^>]*>)(.*)$`, "i").exec(argument);
|
|
22
23
|
if (!match)
|
|
23
24
|
return;
|
|
24
|
-
const
|
|
25
|
+
const text = match[1];
|
|
26
|
+
const path = keyword === "TO" && text.toLowerCase() === "<postmaster>" ? POSTMASTER : parsePath2(text, keyword === "FROM");
|
|
25
27
|
if (!path)
|
|
26
28
|
return;
|
|
27
29
|
const parameters = {};
|
|
@@ -123,6 +125,40 @@ function formatReply(reply, enhanced = true) {
|
|
|
123
125
|
}).join("");
|
|
124
126
|
}
|
|
125
127
|
var reply = (code, status, text) => status === undefined ? { code, text } : { code, status, text };
|
|
128
|
+
// src/server/client-key.ts
|
|
129
|
+
import { isIP } from "node:net";
|
|
130
|
+
function groups(ip) {
|
|
131
|
+
const dotted = /(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(ip);
|
|
132
|
+
let text = ip;
|
|
133
|
+
if (dotted !== null) {
|
|
134
|
+
const [a, b, c, d] = dotted.slice(1).map(Number);
|
|
135
|
+
text = `${ip.slice(0, dotted.index)}${(a << 8 | b).toString(16)}:${(c << 8 | d).toString(16)}`;
|
|
136
|
+
}
|
|
137
|
+
const [head = "", tail] = text.split("::");
|
|
138
|
+
const left = head === "" ? [] : head.split(":");
|
|
139
|
+
const right = tail === undefined || tail === "" ? [] : tail.split(":");
|
|
140
|
+
const all = tail === undefined ? left : [...left, ...Array(8 - left.length - right.length).fill("0"), ...right];
|
|
141
|
+
return all.map((group) => Number.parseInt(group, 16));
|
|
142
|
+
}
|
|
143
|
+
function ipv4Of(g) {
|
|
144
|
+
const [high = 0, low = 0] = g.slice(6);
|
|
145
|
+
return [high >> 8, high & 255, low >> 8, low & 255].join(".");
|
|
146
|
+
}
|
|
147
|
+
function clientKey(ip) {
|
|
148
|
+
if (typeof ip !== "string")
|
|
149
|
+
return;
|
|
150
|
+
const address = ip.replace(/^\[|\]$/g, "").replace(/%.*$/, "");
|
|
151
|
+
if (isIP(address) === 4)
|
|
152
|
+
return address;
|
|
153
|
+
if (isIP(address) !== 6)
|
|
154
|
+
return;
|
|
155
|
+
const g = groups(address.toLowerCase());
|
|
156
|
+
const prefix = g.slice(0, 6).join(":");
|
|
157
|
+
if (prefix === "0:0:0:0:0:65535" || prefix === "100:65435:0:0:0:0") {
|
|
158
|
+
return ipv4Of(g);
|
|
159
|
+
}
|
|
160
|
+
return `${g.slice(0, 4).map((group) => group.toString(16)).join(":")}::/64`;
|
|
161
|
+
}
|
|
126
162
|
// src/server/guard.ts
|
|
127
163
|
var LOCAL_ERROR = reply(451, "4.3.0", "Local error in processing");
|
|
128
164
|
var TIMED_OUT = Symbol("timed out");
|
|
@@ -326,7 +362,7 @@ async function rcpt(connection, argument) {
|
|
|
326
362
|
if (state.transaction.to.length >= settings.maxRecipients) {
|
|
327
363
|
return connection.send(reply(452, "4.5.3", "Too many recipients"));
|
|
328
364
|
}
|
|
329
|
-
if (state.user === undefined) {
|
|
365
|
+
if (state.user === undefined && path.postmaster !== true) {
|
|
330
366
|
const local = await connection.check("localDomains", () => settings.isLocal(path.domain));
|
|
331
367
|
if (local.failed)
|
|
332
368
|
return connection.send(LOCAL_ERROR);
|
|
@@ -952,77 +988,6 @@ class Connection {
|
|
|
952
988
|
}
|
|
953
989
|
}
|
|
954
990
|
|
|
955
|
-
// src/server/settings.ts
|
|
956
|
-
var MAX_TIMER_SECONDS = Math.floor(2147483647 / 1000);
|
|
957
|
-
var invalid = (message) => new SmtpError2("INVALID_OPTION", `createSmtpServer(): ${message}`);
|
|
958
|
-
function positive(name, value, fallback) {
|
|
959
|
-
if (value === undefined)
|
|
960
|
-
return fallback;
|
|
961
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
962
|
-
throw invalid(`${name} must be a positive integer, not ${value}`);
|
|
963
|
-
}
|
|
964
|
-
return value;
|
|
965
|
-
}
|
|
966
|
-
function timer(name, value, fallback) {
|
|
967
|
-
const seconds = positive(name, value, fallback);
|
|
968
|
-
if (seconds > MAX_TIMER_SECONDS) {
|
|
969
|
-
throw invalid(`${name} must be at most ${MAX_TIMER_SECONDS} seconds, not ${seconds}`);
|
|
970
|
-
}
|
|
971
|
-
return seconds;
|
|
972
|
-
}
|
|
973
|
-
function nonNegative(name, value, fallback) {
|
|
974
|
-
if (value === undefined)
|
|
975
|
-
return fallback;
|
|
976
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
977
|
-
throw invalid(`${name} must be a number of seconds, 0 or more, not ${value}`);
|
|
978
|
-
}
|
|
979
|
-
return value;
|
|
980
|
-
}
|
|
981
|
-
function localLookup(local) {
|
|
982
|
-
if (typeof local === "function")
|
|
983
|
-
return local;
|
|
984
|
-
if (Array.isArray(local) && local.every((domain) => typeof domain === "string")) {
|
|
985
|
-
const domains = new Set(local.map((domain) => domain.toLowerCase()));
|
|
986
|
-
return (domain) => domains.has(domain);
|
|
987
|
-
}
|
|
988
|
-
throw invalid("localDomains must be an array of domains or a function");
|
|
989
|
-
}
|
|
990
|
-
function settingsOf(options) {
|
|
991
|
-
if (typeof options.hostname !== "string" || !/^[A-Za-z0-9.-]+$/.test(options.hostname)) {
|
|
992
|
-
throw invalid(`"${options.hostname}" is not a host name`);
|
|
993
|
-
}
|
|
994
|
-
if (typeof options.onData !== "function") {
|
|
995
|
-
throw invalid("onData must be a function: it is where messages go");
|
|
996
|
-
}
|
|
997
|
-
if (options.implicitTls && !options.tls) {
|
|
998
|
-
throw invalid("implicitTls needs tls: { key, cert }");
|
|
999
|
-
}
|
|
1000
|
-
if (options.mode === "submission" && !options.authenticate) {
|
|
1001
|
-
throw invalid("submission takes mail only from authenticated users, so it needs authenticate");
|
|
1002
|
-
}
|
|
1003
|
-
if (options.authenticate && !options.tls) {
|
|
1004
|
-
throw invalid("authenticate needs tls: { key, cert }, since AUTH is offered only once encrypted");
|
|
1005
|
-
}
|
|
1006
|
-
const isLocal = localLookup(options.localDomains);
|
|
1007
|
-
const timeout = positive("timeout", options.timeout, 300);
|
|
1008
|
-
const greetingDelay = nonNegative("greetingDelay", options.greetingDelay, 0);
|
|
1009
|
-
if (greetingDelay >= timeout) {
|
|
1010
|
-
throw invalid(`greetingDelay (${greetingDelay} s) must be shorter than timeout (${timeout} s), or every client times out before the greeting`);
|
|
1011
|
-
}
|
|
1012
|
-
return {
|
|
1013
|
-
options,
|
|
1014
|
-
mode: options.mode ?? "mx",
|
|
1015
|
-
maxMessageSize: positive("maxMessageSize", options.maxMessageSize, 25 * 1024 * 1024),
|
|
1016
|
-
maxRecipients: positive("maxRecipients", options.maxRecipients, 100),
|
|
1017
|
-
maxConnections: positive("maxConnections", options.maxConnections, 1000),
|
|
1018
|
-
maxErrors: positive("maxErrors", options.maxErrors, 10),
|
|
1019
|
-
timeout,
|
|
1020
|
-
hookTimeout: timer("hookTimeout", options.hookTimeout, 60),
|
|
1021
|
-
greetingDelay,
|
|
1022
|
-
isLocal: (domain) => isLocal(domain.toLowerCase())
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
991
|
// src/server/transport.ts
|
|
1027
992
|
var CLOSE_GRACE_MS = 5000;
|
|
1028
993
|
var LINGER_QUIET_MS = 20;
|
|
@@ -1158,27 +1123,29 @@ class SocketTransport {
|
|
|
1158
1123
|
}
|
|
1159
1124
|
}
|
|
1160
1125
|
|
|
1161
|
-
// src/server/
|
|
1126
|
+
// src/server/listener.ts
|
|
1162
1127
|
function handlers(settings) {
|
|
1163
1128
|
const idle = reply(421, "4.4.2", `${settings.options.hostname} Idle too long, closing`);
|
|
1164
1129
|
return {
|
|
1165
1130
|
data(socket, chunk) {
|
|
1166
|
-
if (socket.data.upgraded)
|
|
1131
|
+
if (!socket.data || socket.data.upgraded)
|
|
1167
1132
|
return;
|
|
1168
1133
|
socket.data.transport?.received();
|
|
1169
1134
|
socket.timeout(settings.timeout);
|
|
1170
1135
|
socket.data.connection?.receive(chunk);
|
|
1171
1136
|
},
|
|
1172
1137
|
drain(socket) {
|
|
1173
|
-
if (!socket.data.upgraded)
|
|
1138
|
+
if (socket.data && !socket.data.upgraded)
|
|
1174
1139
|
socket.data.transport?.drain();
|
|
1175
1140
|
},
|
|
1176
1141
|
error(socket) {
|
|
1177
|
-
socket.data
|
|
1142
|
+
socket.data?.connection?.close();
|
|
1178
1143
|
},
|
|
1179
1144
|
timeout(socket) {
|
|
1180
|
-
if (socket.data.upgraded)
|
|
1145
|
+
if (!socket.data || socket.data.upgraded)
|
|
1181
1146
|
return;
|
|
1147
|
+
if (socket.data.handshaking)
|
|
1148
|
+
return socket.terminate();
|
|
1182
1149
|
const { connection, transport } = socket.data;
|
|
1183
1150
|
if (connection?.closed)
|
|
1184
1151
|
transport?.abort();
|
|
@@ -1187,7 +1154,13 @@ function handlers(settings) {
|
|
|
1187
1154
|
}
|
|
1188
1155
|
};
|
|
1189
1156
|
}
|
|
1190
|
-
function
|
|
1157
|
+
function turnAway(socket, secure, line) {
|
|
1158
|
+
const refused = new SocketTransport(socket, secure, () => {});
|
|
1159
|
+
socket.data.transport = refused;
|
|
1160
|
+
refused.write(line);
|
|
1161
|
+
refused.end();
|
|
1162
|
+
}
|
|
1163
|
+
function transportOf(socket, settings, secure, slots) {
|
|
1191
1164
|
const transport = new SocketTransport(socket, secure, () => {
|
|
1192
1165
|
const tls = settings.options.tls;
|
|
1193
1166
|
const connection = socket.data.connection;
|
|
@@ -1200,6 +1173,7 @@ function transportOf(socket, settings, secure) {
|
|
|
1200
1173
|
socket: {
|
|
1201
1174
|
...handlers(settings),
|
|
1202
1175
|
close: (s) => {
|
|
1176
|
+
slots.release(connection);
|
|
1203
1177
|
s.data.transport?.closed();
|
|
1204
1178
|
connection.close();
|
|
1205
1179
|
}
|
|
@@ -1213,14 +1187,171 @@ function transportOf(socket, settings, secure) {
|
|
|
1213
1187
|
socket.data.transport = transport;
|
|
1214
1188
|
return transport;
|
|
1215
1189
|
}
|
|
1190
|
+
function listenerHandlers(settings, slots, handshaking, secure) {
|
|
1191
|
+
return {
|
|
1192
|
+
open(socket) {
|
|
1193
|
+
socket.data = { upgraded: false, handshaking: secure };
|
|
1194
|
+
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?.();
|
|
1227
|
+
},
|
|
1228
|
+
close(socket) {
|
|
1229
|
+
handshaking.delete(socket);
|
|
1230
|
+
const connection = socket.data?.connection;
|
|
1231
|
+
if (connection)
|
|
1232
|
+
slots.release(connection);
|
|
1233
|
+
socket.data?.transport?.closed();
|
|
1234
|
+
connection?.close();
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/server/settings.ts
|
|
1240
|
+
var MAX_TIMER_SECONDS = Math.floor(2147483647 / 1000);
|
|
1241
|
+
var invalid = (message) => new SmtpError2("INVALID_OPTION", `createSmtpServer(): ${message}`);
|
|
1242
|
+
function positive(name, value, fallback) {
|
|
1243
|
+
if (value === undefined)
|
|
1244
|
+
return fallback;
|
|
1245
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1246
|
+
throw invalid(`${name} must be a positive integer, not ${value}`);
|
|
1247
|
+
}
|
|
1248
|
+
return value;
|
|
1249
|
+
}
|
|
1250
|
+
function timer(name, value, fallback) {
|
|
1251
|
+
const seconds = positive(name, value, fallback);
|
|
1252
|
+
if (seconds > MAX_TIMER_SECONDS) {
|
|
1253
|
+
throw invalid(`${name} must be at most ${MAX_TIMER_SECONDS} seconds, not ${seconds}`);
|
|
1254
|
+
}
|
|
1255
|
+
return seconds;
|
|
1256
|
+
}
|
|
1257
|
+
function nonNegative(name, value, fallback) {
|
|
1258
|
+
if (value === undefined)
|
|
1259
|
+
return fallback;
|
|
1260
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1261
|
+
throw invalid(`${name} must be a number of seconds, 0 or more, not ${value}`);
|
|
1262
|
+
}
|
|
1263
|
+
return value;
|
|
1264
|
+
}
|
|
1265
|
+
function localLookup(local) {
|
|
1266
|
+
if (typeof local === "function")
|
|
1267
|
+
return local;
|
|
1268
|
+
if (Array.isArray(local) && local.every((domain) => typeof domain === "string")) {
|
|
1269
|
+
const domains = new Set(local.map((domain) => domain.toLowerCase()));
|
|
1270
|
+
return (domain) => domains.has(domain);
|
|
1271
|
+
}
|
|
1272
|
+
throw invalid("localDomains must be an array of domains or a function");
|
|
1273
|
+
}
|
|
1274
|
+
function settingsOf(options) {
|
|
1275
|
+
if (typeof options.hostname !== "string" || !/^[A-Za-z0-9.-]+$/.test(options.hostname)) {
|
|
1276
|
+
throw invalid(`"${options.hostname}" is not a host name`);
|
|
1277
|
+
}
|
|
1278
|
+
if (typeof options.onData !== "function") {
|
|
1279
|
+
throw invalid("onData must be a function: it is where messages go");
|
|
1280
|
+
}
|
|
1281
|
+
if (options.implicitTls && !options.tls) {
|
|
1282
|
+
throw invalid("implicitTls needs tls: { key, cert }");
|
|
1283
|
+
}
|
|
1284
|
+
if (options.mode === "submission" && !options.authenticate) {
|
|
1285
|
+
throw invalid("submission takes mail only from authenticated users, so it needs authenticate");
|
|
1286
|
+
}
|
|
1287
|
+
if (options.authenticate && !options.tls) {
|
|
1288
|
+
throw invalid("authenticate needs tls: { key, cert }, since AUTH is offered only once encrypted");
|
|
1289
|
+
}
|
|
1290
|
+
const isLocal = localLookup(options.localDomains);
|
|
1291
|
+
const timeout = positive("timeout", options.timeout, 300);
|
|
1292
|
+
const greetingDelay = nonNegative("greetingDelay", options.greetingDelay, 0);
|
|
1293
|
+
if (greetingDelay >= timeout) {
|
|
1294
|
+
throw invalid(`greetingDelay (${greetingDelay} s) must be shorter than timeout (${timeout} s), or every client times out before the greeting`);
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
options,
|
|
1298
|
+
mode: options.mode ?? "mx",
|
|
1299
|
+
maxMessageSize: positive("maxMessageSize", options.maxMessageSize, 25 * 1024 * 1024),
|
|
1300
|
+
maxRecipients: positive("maxRecipients", options.maxRecipients, 100),
|
|
1301
|
+
maxConnections: positive("maxConnections", options.maxConnections, 1000),
|
|
1302
|
+
maxConnectionsPerClient: positive("maxConnectionsPerClient", options.maxConnectionsPerClient, 10),
|
|
1303
|
+
maxErrors: positive("maxErrors", options.maxErrors, 10),
|
|
1304
|
+
timeout,
|
|
1305
|
+
hookTimeout: timer("hookTimeout", options.hookTimeout, 60),
|
|
1306
|
+
handshakeTimeout: positive("handshakeTimeout", options.handshakeTimeout, 10),
|
|
1307
|
+
greetingDelay,
|
|
1308
|
+
isLocal: (domain) => isLocal(domain.toLowerCase())
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// src/server/slots.ts
|
|
1313
|
+
class Slots {
|
|
1314
|
+
#open = new Map;
|
|
1315
|
+
#perClient = new Map;
|
|
1316
|
+
get size() {
|
|
1317
|
+
return this.#open.size;
|
|
1318
|
+
}
|
|
1319
|
+
of(key) {
|
|
1320
|
+
return key === undefined ? 0 : this.#perClient.get(key) ?? 0;
|
|
1321
|
+
}
|
|
1322
|
+
take(connection, key) {
|
|
1323
|
+
this.#open.set(connection, key);
|
|
1324
|
+
if (key !== undefined)
|
|
1325
|
+
this.#perClient.set(key, this.of(key) + 1);
|
|
1326
|
+
}
|
|
1327
|
+
release(connection) {
|
|
1328
|
+
if (!this.#open.has(connection))
|
|
1329
|
+
return;
|
|
1330
|
+
const key = this.#open.get(connection);
|
|
1331
|
+
this.#open.delete(connection);
|
|
1332
|
+
if (key === undefined)
|
|
1333
|
+
return;
|
|
1334
|
+
const left = this.of(key) - 1;
|
|
1335
|
+
if (left > 0)
|
|
1336
|
+
this.#perClient.set(key, left);
|
|
1337
|
+
else
|
|
1338
|
+
this.#perClient.delete(key);
|
|
1339
|
+
}
|
|
1340
|
+
connections() {
|
|
1341
|
+
return [...this.#open.keys()];
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/server/server.ts
|
|
1216
1346
|
function createSmtpServer(options) {
|
|
1217
1347
|
const settings = settingsOf(options);
|
|
1218
1348
|
let listener;
|
|
1219
|
-
const
|
|
1349
|
+
const slots = new Slots;
|
|
1350
|
+
const handshaking = new Set;
|
|
1220
1351
|
const secure = options.implicitTls === true;
|
|
1221
1352
|
return {
|
|
1222
1353
|
get connections() {
|
|
1223
|
-
return
|
|
1354
|
+
return slots.size;
|
|
1224
1355
|
},
|
|
1225
1356
|
async listen({ port, hostname = "0.0.0.0" }) {
|
|
1226
1357
|
if (listener) {
|
|
@@ -1232,29 +1363,7 @@ function createSmtpServer(options) {
|
|
|
1232
1363
|
...secure && options.tls ? { tls: { key: options.tls.key, cert: options.tls.cert } } : {},
|
|
1233
1364
|
socket: {
|
|
1234
1365
|
...handlers(settings),
|
|
1235
|
-
|
|
1236
|
-
socket.data = { upgraded: false };
|
|
1237
|
-
socket.timeout(settings.timeout);
|
|
1238
|
-
if (open.size >= settings.maxConnections) {
|
|
1239
|
-
const refused = new SocketTransport(socket, secure, () => {});
|
|
1240
|
-
socket.data.transport = refused;
|
|
1241
|
-
refused.write(`421 4.3.2 ${options.hostname} Too many connections, try later\r
|
|
1242
|
-
`);
|
|
1243
|
-
refused.end();
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
const connection = new Connection(settings, transportOf(socket, settings, secure));
|
|
1247
|
-
open.add(connection);
|
|
1248
|
-
socket.data.connection = connection;
|
|
1249
|
-
connection.open();
|
|
1250
|
-
},
|
|
1251
|
-
close(socket) {
|
|
1252
|
-
const connection = socket.data.connection;
|
|
1253
|
-
if (connection)
|
|
1254
|
-
open.delete(connection);
|
|
1255
|
-
socket.data.transport?.closed();
|
|
1256
|
-
socket.data.connection?.close();
|
|
1257
|
-
}
|
|
1366
|
+
...listenerHandlers(settings, slots, handshaking, secure)
|
|
1258
1367
|
}
|
|
1259
1368
|
});
|
|
1260
1369
|
return { port: listener.port, hostname: listener.hostname };
|
|
@@ -1264,14 +1373,23 @@ function createSmtpServer(options) {
|
|
|
1264
1373
|
listener = undefined;
|
|
1265
1374
|
if (!closeConnections)
|
|
1266
1375
|
return;
|
|
1267
|
-
|
|
1268
|
-
|
|
1376
|
+
const stuck = new Set;
|
|
1377
|
+
for (const socket of [...handshaking]) {
|
|
1378
|
+
stuck.add(socket.data?.connection);
|
|
1379
|
+
handshaking.delete(socket);
|
|
1380
|
+
socket.terminate();
|
|
1381
|
+
}
|
|
1382
|
+
for (const connection of slots.connections()) {
|
|
1383
|
+
if (!stuck.has(connection))
|
|
1384
|
+
connection.close();
|
|
1385
|
+
}
|
|
1269
1386
|
}
|
|
1270
1387
|
};
|
|
1271
1388
|
}
|
|
1272
1389
|
export {
|
|
1273
1390
|
DataReader,
|
|
1274
1391
|
SmtpError2 as SmtpError,
|
|
1392
|
+
clientKey,
|
|
1275
1393
|
createSmtpServer,
|
|
1276
1394
|
decodeLoginStep2 as decodeLoginStep,
|
|
1277
1395
|
decodePlain2 as decodePlain,
|
|
@@ -1282,5 +1400,5 @@ export {
|
|
|
1282
1400
|
reply
|
|
1283
1401
|
};
|
|
1284
1402
|
|
|
1285
|
-
//# debugId=
|
|
1403
|
+
//# debugId=388406E838895A3964756E2164756E21
|
|
1286
1404
|
//# sourceMappingURL=index.js.map
|