这篇文章发布于 2014 年,原题是《Linux下邮件(mail)查收》,正文却同时混合了本地邮箱读取、发送测试邮件和多用户终端通信。当前维护版聚焦一个更明确的问题:怎样从 Linux 命令行安全、可审计地发送一封已获授权的邮件。本地邮件查收、IMAP/POP3 接收以及 write 终端通信是不同主题,不能用同一组命令互相替代。
先说边界:本文只适用于你拥有或获准使用的发件账号、域名、主机和收件地址。它不提供开放中继、伪造发件人、绕过反滥用控制、批量骚扰邮件或隐藏来源的方法。不要为了“先跑通”而关闭 TLS 证书校验、把密码放进参数或 URL,或把生产队列指向未经同意的收件人。
文末完整保留
source_export的历史可见正文。归档中的旧邮箱地址和消息标识符已作窄范围脱敏;旧 HTTP 链接只作为来源证据,不是当前推荐。
Table of Contents
先选正确的发送路径
| 路径 | 适用场景 | 信任边界 | 不适合 |
|---|---|---|---|
| 本机 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 令牌助手、钥匙串或 passwordeval。msmtp 还可能查找旧 ~/.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 事件 |
某个远端阶段接受了消息 | 用户阅读、内容可信、永不退信 |
收件箱原始头中的 Received、Authentication-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、密码参数、开放中继、来源伪造、购买/抓取地址列表、未获同意的批量邮件,或关闭供应商的反滥用/退订/抑制控制。
回滚、撤销与离职下线
- 首次上线前保存脱敏配置差异、负责人、测试地址、速率上限和回滚窗口。
- 异常时先停止产生新邮件的计划任务/队列入口;不要无差别删除其他应用的队列。
- 只按精确提交/队列 ID 处理本次测试,确认已接受的邮件是否仍可能送达。
- 恢复上一份已验证配置,撤销或轮换本次专用 token/应用密码,删除临时消息文件与 sink 数据。
- 下线账号时移除钥匙串/秘密挂载和服务权限,撤销 API/OAuth 凭据,核对 DKIM 选择器、DNS 授权、Webhook 与退信地址是否仍被其他流使用后再变更。
- 最后验证没有孤立计划任务、公开监听、未处理退信、积压队列或长期保留的敏感日志。
一封受控邮件的验收清单
- 发件账号、域名、主机和唯一测试收件人均有明确授权。
- 已从供应商当前官方文档核对主机、端口、TLS、认证、发件身份、配额和退信流程。
- 已区分本机 MTA、认证 submission 和 API,没有直投任意 MX 或开放中继。
- TLS 主机名和证书验证成功;凭据仅在加密通道后使用。
- 密码/token 不在 argv、URL、仓库、消息、日志、截图或历史中。
- envelope-from、
From:、收件人和 SPF/DKIM/DMARC 所有权已对齐。 - 已通过本地/供应商 sink,生产阶段只发送一封到受控地址。
- 已保存脱敏提交 ID、远端结果和认证头证据,并区分“接受”与“进箱/阅读”。
- 速率、重试、退信、投诉、退订、抑制和隐私保留均有负责人。
- 已验证停止、精确回滚、凭据撤销和下线流程。
参考资料
- RFC 6409:Message Submission for Mail
- RFC 8314:邮件提交/访问应使用 TLS
- RFC 5321:SMTP 与传输信封
- RFC 5322:Internet Message Format
- RFC 7628:OAuth 的 SASL 机制
- msmtp 1.8.34 官方手册
- Postfix 官方 `sendmail(1)` 兼容接口手册
- OpenSSL `s_client` 官方手册
- RFC 7208:SPF
- RFC 6376:DKIM
- RFC 9989:DMARC(取代 RFC 7489)
历史原文归档
以下是 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
