Linux 命令行发送邮件:安全的本地 MTA、SMTP 与 API 指南

这篇文章发布于 2014 年,原题是《Linux下邮件(mail)查收》,正文却同时混合了本地邮箱读取、发送测试邮件和多用户终端通信。当前维护版聚焦一个更明确的问题:怎样从 Linux 命令行安全、可审计地发送一封已获授权的邮件。本地邮件查收、IMAP/POP3 接收以及 write 终端通信是不同主题,不能用同一组命令互相替代。

先说边界:本文只适用于你拥有或获准使用的发件账号、域名、主机和收件地址。它不提供开放中继、伪造发件人、绕过反滥用控制、批量骚扰邮件或隐藏来源的方法。不要为了“先跑通”而关闭 TLS 证书校验、把密码放进参数或 URL,或把生产队列指向未经同意的收件人。

文末完整保留 source_export 的历史可见正文。归档中的旧邮箱地址和消息标识符已作窄范围脱敏;旧 HTTP 链接只作为来源证据,不是当前推荐。

先选正确的发送路径

路径 适用场景 信任边界 不适合
本机 MTA 的 sendmail 兼容接口 受管理员管理的服务器、系统通知、已有队列与退信流程 应用把消息交给本机 Postfix、Exim、Sendmail 等实现;MTA 再负责投递 在个人主机上临时搭建公网 SMTP、绕过组织策略
认证 SMTP submission 单用户脚本、桌面/服务器客户端,供应商明确提供 submission 主机 客户端验证 TLS 并向供应商认证;供应商处理后续投递 直连任意收件域的 25 端口、共享长期密码
供应商邮件 API 应用需要结构化响应、幂等键、模板或事件状态 应用持有最小范围 API 凭据,并遵循供应商数据与重试规则 自造通用端点、把令牌放在命令行、规避配额

mail/mailx 是邮件用户代理的家族名称,具体实现与参数会随发行版变化;sendmail 也常只是兼容接口,并不说明后台是哪一种 MTA。先确认系统所有者、软件包来源和责任人,不要凭二进制名称猜测配置。

变更前取得供应商事实

只从组织管理员或邮件供应商的当前官方文档记录以下内容,并给记录加日期:

  • submission 主机名,以及 587/STARTTLS 或 465/隐式 TLS 的准确组合;
  • 支持的认证方式:OAuth 2.0、短期令牌、应用专用密码或其他组织批准方式;
  • 已验证的 From: 身份、允许的 envelope-from/Return-Path 域和 DKIM 签名责任方;
  • 单封大小、速率、并发、每日配额、收件人数与重试限制;
  • 退信、投诉、抑制名单、日志和内容的保留位置与期限;
  • API 的区域、版本、权限范围、幂等与撤销流程(如果使用 API)。

普通账号密码未必允许 SMTP AUTH;启用多因素认证后也不代表可以复用登录密码。OAuth 与应用专用密码的可用性取决于供应商和组织策略。没有官方参数、发送授权或受控测试收件箱时,先停止。

分清信封与消息头

SMTP 信封与用户看到的消息头是两层数据。RFC 5321 定义传输信封,RFC 5322 定义消息格式;二者没有自动相等的保证。

字段 用途 安全要求
envelope MAIL FROM 退信地址;SPF/DMARC 可能使用其域 使用供应商批准且可处理退信的身份,不在脚本中任意伪造
envelope RCPT TO 真正决定投递目标 只来自固定允许列表或经过验证的业务数据,拒绝换行注入
From: 收件人看到的作者身份,DMARC 的 Author Domain 来源 必须是获授权身份,并与供应商的 SPF/DKIM 方案对齐
To: / Cc: 可见的展示收件人 不一定等于信封收件人;不得泄漏其他人的地址
Bcc: 用信封投递但不应保留在成品头中 确认客户端会移除该头;群发应使用合规列表系统而非大 Bcc
Message-ID: / Date: 去重、串联与诊断 由可信客户端/MTA 生成,日志共享前脱敏

永远不要把未经信任的文本直接拼进头部;CR/LF 可以制造额外头或收件人。本文示例固定使用 RFC 保留的 .invalid 域,因此默认无法真实投递。

路径一:现有本机 MTA 的 sendmail 接口

只做只读清点:

command -v sendmail
command -v msmtp
msmtp --version

如果 /usr/sbin/sendmail 存在,确认它属于哪个已维护软件包、由谁管理、是否仅监听预期接口、是否有认证/中继限制、队列和退信由谁处理。应用通常可用如下兼容调用把一封已检查的消息交给本机队列:

/usr/sbin/sendmail -t -oi < "$HOME/mail-test.eml"

-t 通常从消息头读取收件人,-oi 避免只含一个点的正文行提前结束;但最终语义应以本机实现手册为准。命令返回成功只说明本机 MTA 接受了消息,不等于远端送达。不要自行开放公网监听、配置无认证中继、放宽 mynetworks/ACL,或用 -f 冒用域名。没有管理员认可的队列、退信和日志流程时,改用组织提供的 submission/API。

路径二:认证 SMTP submission

RFC 6409 把消息提交与服务器间中继分开;普通客户端应使用供应商提供的 submission 服务,而不是尝试向远端 MX 的 25 端口直投。RFC 8314 接受两种安全部署:

模式 常用端口 TLS 何时开始 必须验证
STARTTLS submission 587 先建立 SMTP,再通过 STARTTLS 升级;升级失败就停止 主机名、证书链、有效期、STARTTLS 成功、随后才认证
隐式 TLS submission 465 TCP 建立后立即进行 TLS 主机名、证书链、有效期,TLS 完成后才认证

端口只是惯例,供应商官方配置才是事实来源。系统时钟或 CA 库错误也会导致验证失败;修复根因,不要使用 trust-all、tls_certcheck off、忽略主机名或随手固定未知证书指纹。

在不发送凭据或消息的前提下,可以按供应商给出的主机名做 TLS 探测。先确认本机 OpenSSL 版本支持这些选项:

openssl s_client -starttls smtp -connect smtp.example.invalid:587 -servername smtp.example.invalid -verify_hostname smtp.example.invalid -verify_return_error -brief </dev/null
openssl s_client -connect smtp.example.invalid:465 -servername smtp.example.invalid -verify_hostname smtp.example.invalid -verify_return_error -brief </dev/null

探测成功只证明当时的网络/TLS 路径,不证明账号可认证、发件身份获授权或邮件最终送达。出现证书主机名不匹配、未知 CA、降级/代理页面、587 无 STARTTLS 或意外跳到其他主机时立即停止。

用隔离的 msmtp 配置保存非秘密参数

msmtp 是维护中的 SMTP 客户端,并提供 sendmail 兼容接口。先用发行版受支持的软件包并核对版本,再创建一个不覆盖默认账号的隔离文件:

ConfigDir="$HOME/.config/msmtp"
ConfigFile="$ConfigDir/config-controlled"
test ! -L "$ConfigDir" || { echo "Stop: $ConfigDir is a symlink"; exit 1; }
test ! -e "$ConfigFile" && test ! -L "$ConfigFile" || { echo "Stop: $ConfigFile already exists"; exit 1; }
umask 077
install -d -m 700 -- "$ConfigDir"
install -m 600 /dev/null "$ConfigFile"

确认目录与文件均由当前用户拥有。用编辑器填入以下 587/STARTTLS 模板;所有值仍是不可投递占位符:

account controlled
host smtp.example.invalid
port 587
auth on
user sender@example.invalid
from sender@example.invalid
allow_from_override off
tls on
tls_starttls on
tls_trust_file system
set_from_header on
set_date_header auto
set_msgid_header auto
remove_bcc_headers on

该模板按 msmtp 1.8.34 官方手册编写;当地版本的官方手册若没有 set_msgid_header,不要猜测或忽略报错,而应升级受支持的包,或让经核对的 MUA/供应商生成 Message-ID:

若且仅若供应商明确要求 465/隐式 TLS,把两行改为:

port 465
tls_starttls off

msmtp 中,tls_starttls off 配合 tls on 表示从连接开始就在 TLS 隧道中,不是关闭加密。保留默认的证书校验;不要加入 tls_certcheck off

配置中故意没有 password。交互测试可让客户端从受支持的系统钥匙串读取或在 TTY 安全提示;无人值守时,只能按供应商和 msmtp 官方文档接入经审核的 OAuth 令牌助手、钥匙串或 passwordevalmsmtp 还可能查找旧 ~/.netrc;测试账号必须先确认没有意外匹配,且不得打印该文件。辅助命令必须最小权限、不会把秘密写到 stdout 之外、日志、环境快照或临时文件。不得把密码/令牌放进 argv、URL、Shell 历史、仓库、.netrc 范例或截图。

先做不发送的配置展开检查;--pretend 会以星号代替密码,但输出仍可能含账号、主机和路径,分享前继续脱敏:

msmtp --file="$HOME/.config/msmtp/config-controlled" --account=controlled --pretend

不要在真实账号上使用 --debug:官方手册明确警告,完整会话可能包含可轻易解码的密码信息。

先构造最小消息并送入受控 sink

把下列内容保存为权限为 600$HOME/mail-test.eml。先保持 .invalid,不要加入附件、个人数据、生产模板、真实 Bcc 或追踪像素:

From: sender@example.invalid
To: controlled-recipient@example.invalid
Subject: controlled submission test
Auto-Submitted: auto-generated

This is an authorized delivery test. No reply is required.

第一阶段应把独立测试账号指向组织批准的开发 SMTP sink/沙箱。sink 必须只绑定回环或受控测试网络、不能向公网继续中继,并有短保留期。验证它捕获到一封消息、头部正确、没有 Bcc/秘密后清空测试数据。--pretend 只检查客户端将采用的配置,不能代替 sink 测试。

第二阶段才把主机、发件人与唯一收件人替换为你控制且供应商已批准的值。逐行审查差异后,只发送一次:

msmtp --file="$HOME/.config/msmtp/config-controlled" --account=controlled --read-recipients < "$HOME/mail-test.eml"

该配置锁定 envelope-from,并从消息头读取信封收件人。不要把未验证地址作为位置参数传入,也不要在循环、计划任务或重试器中进行首次发送。交互凭据提示必须来自已核对的本机客户端,而非网页弹窗或陌生脚本。

路径三:供应商 API

API 适合需要结构化提交 ID、幂等或事件回调的应用,但不是绕过 SMTP 规则的捷径。只使用供应商当前官方 HTTPS 端点、SDK和证书验证;令牌放在操作系统秘密存储或只读运行时挂载中,限制到单一发送服务/域和必要动作,并设置轮换与失效时间。

不要复制“通用 curl”示例后把 Bearer token 写入命令行或环境。请求体、失败响应、代理和 APM 也可能记录收件人及正文。先使用供应商沙箱/验证模式;首个生产请求保持一个受控收件人和唯一幂等键。只按官方规则对临时错误做有上限的退避重试;永久拒绝、权限错误或配额错误不能盲目重放。

SPF、DKIM 与 DMARC 由域名所有者统筹

  • SPF 授权哪些基础设施可代表 envelope MAIL FROM/HELO 域发送;命令行客户端不能靠添加消息头“通过 SPF”。
  • DKIM 由受信任的 MTA/供应商用域密钥签名;私钥不应分发到普通脚本或工作站。
  • DMARC 使用可见 From: 的 Author Domain,并要求它与通过的 SPF 或 DKIM 标识对齐。当前 RFC 9989 已取代 RFC 7489;通过只证明域的使用获授权,不保证内容安全或一定进收件箱。

DNS 记录、选择器、报告地址和执行策略只能由域名所有者或明确委托方修改。先清点所有合法发送流、按供应商指导完成验证并观察报告,再分阶段调整策略;不要直接复制别人的 TXT 记录或贸然切到拒绝策略。DMARC 报告和退信可能包含地址、IP、头部或消息片段,应限制访问与保留。

交付证据、退信与隐私

证据 能证明什么 不能证明什么
客户端退出码/提交 ID 客户端或 submission/API 接受了请求 最终收件服务器已接受或进了收件箱
本机队列 ID 本机 MTA 已排队 队列最终成功、认证通过
远端 SMTP 250/供应商 delivered 事件 某个远端阶段接受了消息 用户阅读、内容可信、永不退信
收件箱原始头中的 ReceivedAuthentication-Results 测试消息实际路径以及 SPF/DKIM/DMARC 结果 所有供应商、所有后续邮件都相同
DSN/退信/投诉事件 特定失败或用户反馈 可无限重试或继续联系该地址

记录测试时间、配置版本、提交/队列 ID、脱敏状态码和最终结果即可。不要长期保存完整正文、OAuth token、AUTH 对话、完整收件列表或未脱敏原始头。退信地址必须有人处理;硬退信、投诉、退订或明确拒绝应进入抑制流程,而不是自动再投。

供应商的 4xx 通常表示临时状态,5xx 通常表示永久拒绝,但应以该供应商文档和增强状态码为准。重试必须有次数/时间上限、抖动和去重;不确定原请求是否被接受时先按提交 ID 查询,避免重复邮件。

常见故障与停止条件

现象 先查证据 下一步或停止条件
TLS 主机名/链/有效期失败 主机名、系统时间、CA 更新、代理 修复事实来源;绝不关闭校验
587 没有 STARTTLS 官方端口、EHLO 能力、网络拦截 停止认证;不在明文连接发送秘密
535/认证失败 认证类型、账号策略、令牌范围/过期 不连续猜密码;撤销暴露凭据并按官方流程重发
550 发件人或收件人拒绝 已验证身份、信封/头部、增强状态码 不伪造 From:,不轮换域名规避策略
本机命令成功但没有邮件 精确队列 ID、MTA 日志、退信路径 由管理员检查对应记录,不粗暴清空整个队列
SPF/DKIM/DMARC 失败 收件端原始头、DNS、签名域、对齐 由域名/供应商负责人修复,不在客户端伪造认证头
超配额或大量 4xx 供应商限制、队列深度、重复键 暂停生产者;不增加并发绕过限额
收件人/内容来源不明 授权、同意、数据来源和保留依据 不发送,直到责任人确认

严禁使用 tls_certcheck off--insecure/trust-all、明文 AUTH、密码参数、开放中继、来源伪造、购买/抓取地址列表、未获同意的批量邮件,或关闭供应商的反滥用/退订/抑制控制。

回滚、撤销与离职下线

  1. 首次上线前保存脱敏配置差异、负责人、测试地址、速率上限和回滚窗口。
  2. 异常时先停止产生新邮件的计划任务/队列入口;不要无差别删除其他应用的队列。
  3. 只按精确提交/队列 ID 处理本次测试,确认已接受的邮件是否仍可能送达。
  4. 恢复上一份已验证配置,撤销或轮换本次专用 token/应用密码,删除临时消息文件与 sink 数据。
  5. 下线账号时移除钥匙串/秘密挂载和服务权限,撤销 API/OAuth 凭据,核对 DKIM 选择器、DNS 授权、Webhook 与退信地址是否仍被其他流使用后再变更。
  6. 最后验证没有孤立计划任务、公开监听、未处理退信、积压队列或长期保留的敏感日志。

一封受控邮件的验收清单

  • 发件账号、域名、主机和唯一测试收件人均有明确授权。
  • 已从供应商当前官方文档核对主机、端口、TLS、认证、发件身份、配额和退信流程。
  • 已区分本机 MTA、认证 submission 和 API,没有直投任意 MX 或开放中继。
  • TLS 主机名和证书验证成功;凭据仅在加密通道后使用。
  • 密码/token 不在 argv、URL、仓库、消息、日志、截图或历史中。
  • envelope-from、From:、收件人和 SPF/DKIM/DMARC 所有权已对齐。
  • 已通过本地/供应商 sink,生产阶段只发送一封到受控地址。
  • 已保存脱敏提交 ID、远端结果和认证头证据,并区分“接受”与“进箱/阅读”。
  • 速率、重试、退信、投诉、退订、抑制和隐私保留均有负责人。
  • 已验证停止、精确回滚、凭据撤销和下线流程。

参考资料

历史原文归档

以下是 source_export 中 2014 年可见正文的完整惰性归档。为避免重新暴露可识别的旧邮箱与单封邮件标识,做了 6 处窄范围替换:1 个邮箱路径用户名、1 个列表中的邮箱、1 个本地身份、2 个邮件地址、1 个 Message-ID。用户名 frank/renee 的一般教程文字、历史域名和 2 个旧 HTTP 来源链接保持原样;链接仅为出处证据,不是当前服务、下载或配置建议。原文没有行尾空白,除此之外未改动。围栏内所有命令、地址和做法均不得执行。


Table of Contents

Toggle

- [Linux下mail使用技巧](https://blog.lazying.art/en/html/computer_internet/unix_linux/command_shell_software/1143/linux%e4%b8%8b%e9%82%ae%e4%bb%b6mail%e6%9f%a5%e6%94%b6.html/#Linux%E4%B8%8Bmail%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7)

# Linux下mail使用技巧

登录LINUX系统后,经常会看到”you have mail”,却苦于不知道如何查看,相信菜鸟们都遇到过,偶在网上用“linux mail”找了很久,但大都是介绍[mail服务器](http://earnfs.sinaapp.com/html/1141.htm)的,黄天总算没负有心人,在洪恩在找到一篇介绍基础的文章,不敢独享。

系统提供了用户之间通信的邮件系统,当用户打开终端注册登录时发现系统给出如下信息:
 you have mail.

这时用户可通过键入mail命令读取信件:

$ mail

mail程序将逐个显示用户的信件,并依照时间顺序,显示最新的信件。每显示一段信件,mail都询问用户是否要对该信件作些处理。若用户回答d,则表示 删除信件;若仅按回车键,表示对信件不作任何改动(信件仍旧保存,下次还可读这一信件);若回答p,则要求重复显示信件;s filename表示要把信件存入所命名的文件;若回答q,表示要从mail退出。

我们在本章的第一个例子中演示了如何写一封信,作为练习,你可送信件给自己,然后键入mail读取自己发的信件,看看会有什么效果。(发信给自己是一种设置备忘录的方法)。

$mail frank 给自己写信

subject: test

This is a mail test

CRL-d

EOT

$

$mail 查看信件

“/var/spool/mail/[historical user redacted]:”1 message 1 new

>N[historical email redacted]Thu Mar 25 11:00 13/403 “test”

&

Message 1:

From frank Thu Mar 25 11:00:25 1999/3/25

Received: ([historical local identity redacted])

by xteam.xteamlinux.com(8.8.4/8.8.4)

id LAA05170 for frank;Thu 25 Mar 1999 11:00:25 GMT

Date: Thu,25 Mar 1999 11:00:25 GMT

From:RHS Linux User <[historical email redacted]>

Message-Id:<[historical message identifier redacted]>

To:[historical email redacted]

Subject:test

Status:R

This is a mail test

&

mail命令还有很多其它用法,例如发送事先准备好的信件,或一次送信给若干人。还可以用其它方法送信件。

另附message的使用技巧:

当Linux系统处于多用户的情况下,有时在终端上会突然显示出下述信息:

Message from renee tty2…

并伴随出现一阵嘟嘟响声。这是用户renee想和你通话而产生的信号。若你用如下命令响应他:

$ write renee

这就建立起了你和renee的通信线路,renee在他的终端上键入的内容同时显示在你的终端上,反之你键入的内容也显示在renee的终端上。为区分终 端上哪些是你输入的,哪些是renee输入的,我们使用如下通话协议:(o)表示一段话说完,并让对方发话,(oo)代表通话结束并退出程序。

renee’s terminal: frank terminal:

[renee@xteam renee]$ write frank

$ Message from renee tty2…

$write renee

[renee@xteam renee]$Message from you tty1…

did you forget lunch? (o)

did you forgeet lunch? (o)

ten minutes (o)

ten minutes (o)

ok (oo)

ok (oo)

ctl-d

EOF

Ctl-d

EOF

[renee@xteam renee]$ $

除CTL-d键外,也可以使用DELETE退出write命令。

如果你不愿意别人干扰你的工作,可以使用mesg命令拒绝接受通话。当你向一个拒绝接收通话的用户发写命令、或者向没有注册的用户要求通话时,write命令会显示不能通话的原因。

转自:http://edu.codepub.com/2010/0413/21978.php

Leave a Reply