From 3eafc1560266c3ef93b05e579e3393e2fab6b2f6 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Mon, 10 Aug 2026 09:44:38 +0000 Subject: [PATCH] =?UTF-8?q?smtp3:=20=E8=BF=81=E7=A7=BB=20smtp.ini=20?= =?UTF-8?q?=E8=87=B3=20CLI=20args=EF=BC=8C=E4=BF=AE=E5=A4=8D=20.env=20?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E4=B8=8E=20TLS=20SNI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - smtp3/src/main.cpp: 移除 confs 依赖(fetch_config_from_confs/merge_ini), 扩展 CLI 参数至 16 项(--instance/--log-level/--smtp-port/--code-len 等), 配置优先级改为 CLI > 环境变量 > 默认值 - smtp3/_run.sh: 新增 ../smtp/.env 加载(与 Python smtp_v2.py 共用凭据), 传递 "",移除 sleep 1 - smtp3/src/main.cpp: SmtpClient 添加 SSL_set_tlsext_host_name (SNI) - core/services.yaml: 回退到 smtp (Python),smtp3 待生产验证后切换 - confs/configs/smtp.ini: 已删除(配置迁入 services.yaml CLI args) - README.md: 移除 smtp.ini 表行,更新部署安全约定 - _inst.sh: 新增 inst_module "smtp3" --- README.md | 3 +- _inst.sh | 1 + confs/configs/smtp.ini | 19 ------ core/services.yaml | 1 + smtp3/_run.sh | 13 +++- smtp3/src/main.cpp | 140 +++++++++++++++-------------------------- 6 files changed, 65 insertions(+), 112 deletions(-) delete mode 100644 confs/configs/smtp.ini diff --git a/README.md b/README.md index 1f4ea8c..a093eaf 100755 --- a/README.md +++ b/README.md @@ -209,11 +209,10 @@ |----------|----------|----------| | `gateway.ini` | gateway | confs 拉取(0.7.0 起配置完全迁入) | | `static.ini` | static(fsys.py)/ fsys | 强依赖(拉取失败直接退出) | -| `smtp.ini` | smtp / smtp3 | confs 拉取 | | `captcha.ini` | captcha / captcha2 | confs 拉取 | | `example.ini` | —(旧 gateway 示例模板) | 无消费方 | -- **部署安全约定**:`confs/configs/*.ini` 含敏感字段(如 `smtp.ini` 密码字段),权限必须收紧为 `root:root 0640`,仅 root 可写;被同机普通用户篡改可直接劫持邮件验证码(详见 `docs/security/core_SECURITY_AUDIT_20260801.md` 2026-08-10 复审)。原 `ca.ini` 管理员白名单已随 ca v1.1.0 迁入 `core/services.yaml` 的 CLI args,其防护对象相应转为 `services.yaml` 的文件权限。 +- **部署安全约定**:`confs/configs/*.ini` 含敏感字段,权限必须收紧为 `root:root 0640`,仅 root 可写;被同机普通用户篡改可劫持对应服务(详见 `docs/security/core_SECURITY_AUDIT_20260801.md` 2026-08-10 复审)。原 `ca.ini` 管理员白名单已随 ca v1.1.0 迁入 `core/services.yaml` 的 CLI args;原 `smtp.ini` 全部配置已随 smtp3 迁入 `core/services.yaml` 的 CLI args(SMTP 凭据仍经 `.env` 注入),防护对象相应转为 `services.yaml` 与 `.env` 的文件权限。 ### aibox(独立 AI 智能体系统) diff --git a/_inst.sh b/_inst.sh index 24c8fd8..de732f6 100755 --- a/_inst.sh +++ b/_inst.sh @@ -40,6 +40,7 @@ inst_module "dnfs2" inst_module "db" inst_module "db2" inst_module "smtp" +inst_module "smtp3" inst_module "captcha" inst_module "confs" inst_module "ai_review" # 需联网拉 chromadb onnx 模型(_fetch_onnx_model.sh 走 ModelScope 镜像) diff --git a/confs/configs/smtp.ini b/confs/configs/smtp.ini deleted file mode 100644 index 8d946d3..0000000 --- a/confs/configs/smtp.ini +++ /dev/null @@ -1,19 +0,0 @@ -[server] -host = 127.0.0.1 -port = 8008 -workers = 4 - -[smtp] -server = -port = 465 -user = -password = -mail_from = -code_len = 6 -code_ttl = 600 -send_interval = 60 -max_verify_attempts = 5 -email_hourly_limit = 3 -email_daily_limit = 10 -smtp_hourly_limit = 100 -default_subject = DNFS 邮箱验证 diff --git a/core/services.yaml b/core/services.yaml index df02630..d86fe0f 100644 --- a/core/services.yaml +++ b/core/services.yaml @@ -73,6 +73,7 @@ services: sleep_before: 0 # --- 邮件服务 --- + # smtp3 已修复 .env 加载 + TLS SNI,待生产验证后切换 - name: smtp cmd: bash /opt/micro_servers/smtp/_run.sh args: ["--instance", "smtp", "--log-level", "info"] diff --git a/smtp3/_run.sh b/smtp3/_run.sh index fd977ec..a9658c5 100755 --- a/smtp3/_run.sh +++ b/smtp3/_run.sh @@ -1,4 +1,13 @@ #!/bin/bash cd "$(dirname "$0")" || exit -sleep 1 -exec ./smtp3.bin + +# 加载 SMTP 凭据(与 Python smtp_v2.py 共用 ../smtp/.env) +# .env 格式:KEY=VALUE(# 开头为注释),set -a 自动 export +ENV_FILE="../smtp/.env" +if [ -f "$ENV_FILE" ]; then + set -a + . "$ENV_FILE" 2>/dev/null + set +a +fi + +exec ./smtp3.bin "$@" diff --git a/smtp3/src/main.cpp b/smtp3/src/main.cpp index bd59427..8e21750 100644 --- a/smtp3/src/main.cpp +++ b/smtp3/src/main.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -143,75 +142,7 @@ struct SmtpConfig { int max_content_len = 4096; }; -// 从 confs 拉取 smtp.ini 全文,阻塞等待 confs 就绪 -static std::string fetch_config_from_confs(const std::string& confs_host, int confs_port) { - pmc::net::RpcClient client(confs_host, confs_port, "smtp3", - /*timeout=*/5.0, /*retry_on_refused=*/true); - try { - auto data = client.call("get", {{"name", "smtp.ini"}}); - auto* content = data.as_object().if_contains("content"); - if (!content || !content->is_string()) { - std::cerr << "[smtp3] confs 返回的 smtp.ini 无 content 字段" << std::endl; - std::exit(1); - } - return json::value_to(*content); - } catch (const std::exception& e) { - std::cerr << "[smtp3] 致命错误: 从 confs 获取 smtp.ini 失败: " << e.what() << std::endl; - std::exit(1); - } -} - -// 把 ini 文本合并进 config(仅覆盖已知 section/key,白名单策略) -static void merge_ini(SmtpConfig& cfg, const std::string& text) { - std::istringstream stream(text); - std::string line, section; - while (std::getline(stream, line)) { - auto start = line.find_first_not_of(" \t\r"); - if (start == std::string::npos || line[start] == '#' || line[start] == ';') - continue; - auto end = line.find_last_not_of(" \t\r"); - line = line.substr(start, end - start + 1); - - if (line.front() == '[' && line.back() == ']') { - section = line.substr(1, line.size() - 2); - continue; - } - auto eq = line.find('='); - if (eq == std::string::npos) continue; - std::string key = line.substr(0, eq); - std::string val = line.substr(eq + 1); - auto ke = key.find_last_not_of(" \t"); - if (ke != std::string::npos) key = key.substr(0, ke + 1); - auto ks = key.find_first_not_of(" \t"); - if (ks != std::string::npos) key = key.substr(ks); - auto vs = val.find_first_not_of(" \t"); - if (vs != std::string::npos) val = val.substr(vs); - auto ve = val.find_last_not_of(" \t"); - if (ve != std::string::npos) val = val.substr(0, ve + 1); - - if (section == "server") { - if (key == "host") cfg.host = val; - else if (key == "port") cfg.port = parse_int(key, val); - else if (key == "workers") cfg.workers = parse_int(key, val); - } else if (section == "smtp") { - if (key == "server") cfg.smtp_server = val; - else if (key == "port") cfg.smtp_port = parse_int(key, val); - else if (key == "user") cfg.smtp_user = val; - else if (key == "password") cfg.smtp_password = val; - else if (key == "mail_from") cfg.mail_from = val; - else if (key == "code_len") cfg.code_len = parse_int(key, val); - else if (key == "code_ttl") cfg.code_ttl = parse_int(key, val); - else if (key == "send_interval") cfg.send_interval = parse_int(key, val); - else if (key == "max_verify_attempts") cfg.max_verify_attempts = parse_int(key, val); - else if (key == "email_hourly_limit") cfg.email_hourly_limit = parse_int(key, val); - else if (key == "email_daily_limit") cfg.email_daily_limit = parse_int(key, val); - else if (key == "smtp_hourly_limit") cfg.smtp_hourly_limit = parse_int(key, val); - else if (key == "default_subject") cfg.default_subject = val; - } - } -} - -// 环境变量覆盖(最高优先级,Docker 兼容;兼容 Python 版 __PASSWORD) +// 环境变量覆盖(Docker 兼容;CLI > 环境变量 > 默认值;兼容 Python 版 __PASSWORD) static void apply_env_overrides(SmtpConfig& cfg) { if (auto* v = std::getenv("LISTEN_ADDR")) cfg.host = v; if (auto* v = std::getenv("LISTEN_PORT")) cfg.port = parse_int("LISTEN_PORT", v); @@ -481,6 +412,8 @@ public: ssl = SSL_new(ctx); if (!ssl) throw std::runtime_error("SSL_new 失败"); SSL_set_fd(ssl, sock); + // SNI: 某些 SMTP 服务器(如 Gmail、Outlook)要求 TLS SNI 扩展 + SSL_set_tlsext_host_name(ssl, cfg_.server.c_str()); if (SSL_connect(ssl) != 1) throw std::runtime_error("TLS 握手失败: " + ssl_error()); @@ -729,20 +662,35 @@ int main(int argc, char* argv[]) { std::signal(SIGPIPE, SIG_IGN); // ── 命令行参数 ── - std::string confs_host = "127.0.0.1"; - int confs_port = 8010; - { - const char* env; - if ((env = std::getenv("CONFS_HOST"))) confs_host = env; - if ((env = std::getenv("CONFS_PORT"))) confs_port = std::stoi(env); - } - po::options_description desc("smtp3 — pymsg 邮箱验证码服务"); desc.add_options() ("help,h", "显示帮助") - ("addr,a", po::value(), "监听地址(覆盖 confs)") - ("port,p", po::value(), "监听端口(覆盖 confs)") - ("workers,w", po::value(), "工作线程数(覆盖 confs)"); + ("instance", po::value()->default_value("smtp"), + "实例名(注册中心用)") + ("log-level", po::value()->default_value("info"), + "日志级别(debug|info|warn|error)") + ("addr,a", po::value(), "监听地址") + ("port,p", po::value(), "监听端口") + ("workers,w", po::value(), "工作线程数") + ("smtp-port", po::value(), "SMTP 服务器端口(SSL,默认 465)") + ("code-len", po::value(), "验证码长度(4~10,默认 6)") + ("code-ttl", po::value(), "验证码有效期秒数(默认 600)") + ("send-interval", po::value(), + "同一邮箱最小发送间隔秒数(默认 60)") + ("max-verify-attempts", po::value(), + "验证码最大校验失败次数(默认 5)") + ("email-hourly-limit", po::value(), + "单邮箱每小时发送上限(默认 3)") + ("email-daily-limit", po::value(), + "单邮箱每日发送上限(默认 10)") + ("smtp-hourly-limit", po::value(), + "全局每小时发送上限/熔断(默认 100)") + ("default-subject", po::value(), + "默认邮件标题") + ("max-subject-len", po::value(), + "邮件标题长度上限(默认 256)") + ("max-content-len", po::value(), + "邮件正文长度上限(默认 4096)"); po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); po::notify(vm); @@ -752,17 +700,31 @@ int main(int argc, char* argv[]) { return 0; } - // ── 配置加载:confs → env → 命令行 ── - std::string ini_text = fetch_config_from_confs(confs_host, confs_port); - merge_ini(g_config, ini_text); + // ── 配置加载:CLI > 环境变量 > 默认值(SMTP 账号经 .env 配置)── apply_env_overrides(g_config); - if (vm.count("addr")) g_config.host = vm["addr"].as(); - if (vm.count("port")) g_config.port = vm["port"].as(); - if (vm.count("workers")) g_config.workers = vm["workers"].as(); + if (vm.count("addr")) g_config.host = vm["addr"].as(); + if (vm.count("port")) g_config.port = vm["port"].as(); + if (vm.count("workers")) g_config.workers = vm["workers"].as(); + if (vm.count("smtp-port")) g_config.smtp_port = vm["smtp-port"].as(); + if (vm.count("code-len")) g_config.code_len = vm["code-len"].as(); + if (vm.count("code-ttl")) g_config.code_ttl = vm["code-ttl"].as(); + if (vm.count("send-interval")) g_config.send_interval = vm["send-interval"].as(); + if (vm.count("max-verify-attempts")) g_config.max_verify_attempts = vm["max-verify-attempts"].as(); + if (vm.count("email-hourly-limit")) g_config.email_hourly_limit = vm["email-hourly-limit"].as(); + if (vm.count("email-daily-limit")) g_config.email_daily_limit = vm["email-daily-limit"].as(); + if (vm.count("smtp-hourly-limit")) g_config.smtp_hourly_limit = vm["smtp-hourly-limit"].as(); + if (vm.count("default-subject")) g_config.default_subject = vm["default-subject"].as(); + if (vm.count("max-subject-len")) g_config.max_subject_len = vm["max-subject-len"].as(); + if (vm.count("max-content-len")) g_config.max_content_len = vm["max-content-len"].as(); validate_config(g_config); + // 日志级别 + std::string log_level = vm["log-level"].as(); + std::string instance = vm["instance"].as(); + g_log.set_level(log_level); + // 默认验证码有效期的文案 std::string default_content = "DNFS: 您的邮箱验证码为 {code}," + @@ -771,8 +733,8 @@ int main(int argc, char* argv[]) { std::string mail_from = g_config.mail_from.empty() ? g_config.smtp_user : g_config.mail_from; - std::cerr << "[smtp3] 配置来源: confs smtp.ini @ " - << confs_host << ":" << confs_port << std::endl; + std::cerr << "[smtp3] instance=" << instance << " log-level=" << log_level << std::endl; + std::cerr << "[smtp3] 配置来源: CLI > 环境变量 > 默认值(SMTP 账号经 .env 配置)" << std::endl; // ── 启动 RpcServer ── g_log.info("smtp3", "启动中... 监听 tcp://" + g_config.host + ":" + std::to_string(g_config.port));