"use strict"; const tls = require("node:tls"); function sendSmtpMail({ to, subject, approvalUrl, order, text, smtpHost, smtpPort, smtpUser, smtpPass }) { if (!to || !smtpUser || !smtpPass || (!text && !approvalUrl)) return Promise.resolve(false); const plain = text || [ "光湖小湖灯安全协议系统 · 授权请求", "", `人格体: ${order.persona.name} (${order.persona.pid})`, `目标节点: ${order.target}`, `授权范围: ${order.scope}`, `登记动作: ${order.action}`, `绑定资源: ${order.resource || "无"}`, "有效期: 批准后三小时,仅限这台服务器与本申请范围", "", "打开以下链接查看工单并确认授权:", approvalUrl, "", "如果不是你发起的操作,请不要点击。", ].join("\n"); const html = approvalUrl ? approvalEmailHtml({ approvalUrl, order }) : ""; const boundary = `guanghu-lake-lamp-${Date.now().toString(36)}`; const body = html ? [ `--${boundary}`, 'Content-Type: text/plain; charset="utf-8"', "Content-Transfer-Encoding: base64", "", encodeBase64(plain), `--${boundary}`, 'Content-Type: text/html; charset="utf-8"', "Content-Transfer-Encoding: base64", "", encodeBase64(html), `--${boundary}--`, ].join("\r\n") : [ 'Content-Type: text/plain; charset="utf-8"', "Content-Transfer-Encoding: base64", "", encodeBase64(plain), ].join("\r\n"); const message = [ `From: =?UTF-8?B?${Buffer.from("光湖小湖灯").toString("base64")}?= <${smtpUser}>`, `To: <${to}>`, `Subject: =?UTF-8?B?${Buffer.from(subject).toString("base64")}?=`, "MIME-Version: 1.0", ...(html ? [`Content-Type: multipart/alternative; boundary="${boundary}"`] : []), "", body, ".", "", ].join("\r\n"); return new Promise(resolve => { let settled = false; const done = value => { if (!settled) { settled = true; resolve(value); } }; const socket = tls.connect({ host: smtpHost, port: smtpPort, servername: smtpHost, rejectUnauthorized: true }); let stage = 0; let buffer = ""; socket.setTimeout(15000, () => { socket.destroy(); done(false); }); socket.on("error", () => done(false)); socket.on("data", data => { buffer += data.toString(); const lines = buffer.split("\r\n"); buffer = lines.pop(); for (const line of lines) { if (!/^\d{3}[ -]/.test(line) || line[3] === "-") continue; const code = Number(line.slice(0, 3)); if (code >= 400) { socket.end(); done(false); return; } if (stage === 0 && code === 220) { stage = 1; socket.write("EHLO guanghu-lake-lamp\r\n"); } else if (stage === 1 && code === 250) { stage = 2; socket.write("AUTH LOGIN\r\n"); } else if (stage === 2 && code === 334) { stage = 3; socket.write(`${Buffer.from(smtpUser).toString("base64")}\r\n`); } else if (stage === 3 && code === 334) { stage = 4; socket.write(`${Buffer.from(smtpPass).toString("base64")}\r\n`); } else if (stage === 4 && code === 235) { stage = 5; socket.write(`MAIL FROM:<${smtpUser}>\r\n`); } else if (stage === 5 && code === 250) { stage = 6; socket.write(`RCPT TO:<${to}>\r\n`); } else if (stage === 6 && code === 250) { stage = 7; socket.write("DATA\r\n"); } else if (stage === 7 && code === 354) { stage = 8; socket.write(message); } else if (stage === 8 && code === 250) { socket.write("QUIT\r\n"); socket.end(); done(true); } } }); socket.on("close", () => done(false)); }); } function approvalEmailHtml({ approvalUrl, order }) { const persona = escapeHtml(order.persona.name); const personaId = escapeHtml(order.persona.pid); const target = escapeHtml(order.target); const scope = escapeHtml(order.scope); const action = escapeHtml(order.action); const allowedActions = escapeHtml((order.allowed_actions || [order.action]).join(" · ")); const description = escapeHtml(order.description || "未附加说明"); const resource = escapeHtml(order.resource || "无"); const link = escapeHtml(approvalUrl); return `
LAKE LAMP · THREE-HOUR OPS SESSION

一盏小湖灯,正在等你确认

${persona}(${personaId})申请打开三小时受限运维会话。请核对申请者、服务器、能力范围和绑定资源,再决定是否开门。

${persona}
${personaId}
目标节点${target}
授权范围${scope}
进入动作${action}
绑定资源${resource}
会话能力${allowedActions}
申请目的${description}
打开三小时受限运维会话

人格体持续执行上方已登记且绑定不变的任务时,会话会自动续期,最长不超过二十四小时。切换服务器、扩大范围、改变绑定资源或停止活动后过期才需重新授权。若这不是你发起的操作,请忽略本邮件。

按钮无法打开时:${link}

光湖 · 小湖灯安全协议系统

`; } function encodeBase64(value) { return Buffer.from(value).toString("base64").replace(/(.{76})/g, "$1\r\n"); } function escapeHtml(value) { return String(value).replace(/[&<>"']/g, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); } module.exports = { sendSmtpMail };