This commit is contained in:
parent
73911f01ff
commit
6c863db8af
@ -17,7 +17,9 @@
|
||||
- [✓] 主界面使用中文组件语言,空列表、关闭对话框、关闭标签、数值按钮均有实际页面证据。
|
||||
- [✓] 配置、安装及管理员恢复相关 Linux 测试包在远程执行通过。
|
||||
- [✓] 恢复 socket 为 `smail:smail 0600`;非授权服务账号直接运行恢复工具被拒绝。
|
||||
- [!] 邮件实际投递未通过:网络连接与证书信任阻塞,未绕过校验。
|
||||
- [✓] 内部 25 端口真实收信、中文正文、多收件人、中文附件及下载内容通过。
|
||||
- [✓] 普通用户不能读取管理员专属邮件或下载其真实附件。
|
||||
- [!] 网页按 MX 路由的实际投递未通过:网络连接与证书信任阻塞,未绕过校验;按最新要求不再要求公网 25 端口连通。
|
||||
- [✓] 修复异步发信提前显示成功:网页提交显示正在投递,列表先显示待投递,手动刷新后显示实际失败。
|
||||
- [✓] 修复 587 端口误用隐式 TLS,远程 STARTTLS 已进入证书校验阶段。
|
||||
- [!] 当前仍使用原有定时与手动刷新机制;快速状态轮询、进程中断后的待投递恢复未验收。
|
||||
@ -33,12 +35,14 @@
|
||||
|
||||
`49ab24b` 修复异步邮件投递状态与 587 端口加密协议。邮件与用户关联先以既有待投递状态入库;列表接口返回真实状态,页面不再将错误文本为空当作成功;587 使用 STARTTLS,保留证书校验及禁止明文回退的边界。
|
||||
|
||||
`416b849` 修复标准 MIME 中文附件名称解析。原正则无法识别 RFC 2231 的 `filename*=`,导致真实收信附件名变成 `no_name_file`;改为使用现有 `go-message` 参数解析器,保留 `Content-Type name` 优先级,新增普通名称、带分号名称、UTF-8 扩展参数、分段参数、编码词、旧式名称、优先级、缺省及非法参数共九个用例。
|
||||
|
||||
两次清库安装后,前端修复及邮件状态修复均采用备份后保留数据库的升级部署,用于同轮故障回归和验证会话、配置保持,不宣称这些版本又进行了一轮全新清库安装。
|
||||
|
||||
最终 Smail 二进制 SHA-256:
|
||||
|
||||
```text
|
||||
20a0d08dd9f2c2b6bb60e7c1e36f9c09b571c0c32f35d787d06cbe5792c6a214
|
||||
e0455f8156f4bc0ff25136ffb39e42566b0b154165eb3ce8eb4fb937ba6fe1f6
|
||||
```
|
||||
|
||||
远程服务在部署后保持 active,HTTP 80、HTTPS 443 正常监听。`config.json`、`email.json`、`plugins.json`、`smtp.json`、`ssl.json` 均为 `smail:smail 0600`。
|
||||
@ -66,12 +70,37 @@
|
||||
- `setup.test`、`config.test` 在远程以 `smail` 身份执行通过。
|
||||
- `adminreset.test` 在远程以 `smail` 身份执行全部通过,覆盖调用身份、一次性挑战、防重放、过期、socket 权限、重置事务与凭据撤销。
|
||||
- `email.test`、`send.test`、`smtp.test` 在远程以 `smail` 身份执行全部通过,新增实际持久化函数待投递状态、关联写入失败回滚、列表接口三种状态回归。测试使用隔离内存数据库,不写部署业务库。
|
||||
- `parsemail.test` 在远程以 `smail` 身份执行全部通过,包含九个新增附件名称用例以及既有正文、隐私和 XSS 用例。
|
||||
- 配置测试曾因从 `/root` 继承工作目录而失败,改在服务账号可访问的测试目录执行后通过,没有放宽权限。
|
||||
- 前端生产构建与 Linux Go 编译通过;构建有编辑器块超过 500 kB 的提示。
|
||||
- 本次没有重新执行全部后端包、竞态测试或前端自动测试,不能沿用此前本机全量测试作为远程全量结论。
|
||||
- 桌面浏览器已检查页面快照、截图和发件页控制台,所查控制台没有错误;未进行移动端检查。
|
||||
- 键盘选择回归仅检查表单行为,未向不存在的选择测试地址发送邮件。
|
||||
|
||||
## 内部 25 端口验收
|
||||
|
||||
根据用户最新要求,仅从 47 服务器连接 `127.0.0.1:25`,不要求公网 25 端口连通。测试脚本为 `test/tools/internal_smtp_check.py`,本机没有执行该脚本。测试客户端显式信任服务器现有测试证书,仍校验 `mail.solit.cn` 主机名、证书有效期和证书链;没有修改应用证书校验、系统 CA、DNS 或中继配置。
|
||||
|
||||
21:26:17 最终一轮五项测试全部完成,全部使用 TLS 1.3,SMTP DATA 返回 250,随后核对真实 SQLite 数据与用户关联,并非仅以协议返回码判定成功:
|
||||
|
||||
| 用例 | 邮件 ID | 实际结果 |
|
||||
| --- | --- | --- |
|
||||
| 中文纯文本 | 15 | 仅普通用户收件,正文一致 |
|
||||
| HTML 与中文附件、多收件人 | 16 | 管理员和普通用户均关联,正文、附件名及附件字节一致 |
|
||||
| 管理员专属附件 | 17 | 仅管理员关联,用于附件越权对照 |
|
||||
| 未知本域收件人 | 18 | 当前过滤级别 0,转管理员收件箱 |
|
||||
| 未认证外域收件人 | 19 | 当前策略转管理员收件箱,仅有收信记录,没有发信记录 |
|
||||
|
||||
TLS 前未公布 AUTH,主动尝试明文 AUTH 返回 523。未认证外域邮件走收信分支,不调用发信路径;本轮没有公网抓包,不将该检查描述为所有中继场景的完整安全验收。当前未知地址和外域地址均不是在 RCPT 阶段拒绝,管理员兜底行为保持原样。
|
||||
|
||||
早期脚本误以为未知收件人必定丢弃,实际只读诊断确认当前过滤级别为 0、SPF 状态为通过、DKIM 状态为未通过,且邮件仅属于管理员。修订脚本显式要求当前过滤级别为 0,再验证准确归属,没有为让测试通过而修改业务策略。
|
||||
|
||||
桌面浏览器无缓存刷新后保持普通用户登录,列表可见中文邮件,详情显示中文 HTML 和“中文验收附件.txt”。附件 12 的下载内容与发送字节一致。管理员专属附件 17 在管理员会话中返回正确内容,在普通用户会话中返回业务错误“附件不可用”,没有附件数据。普通用户访问管理员邮件详情 13 返回“读取邮件失败”,自己的详情 12 正常返回。权限对照使用正常 CSRF 请求头;首次缺少该头的来源校验拒绝不作为用户隔离证据。
|
||||
|
||||
以上拒绝接口当前 HTTP 状态仍为 200,应依据业务错误码和无数据结果判断,不能仅看 HTTP 状态。详情页面未发现横向溢出。本轮未进行移动端验收,也未重新执行完整清库安装:保留同轮故障邮件和会话,完成备份后升级回归。
|
||||
|
||||
本轮内部 SMTP 结果及双库备份保留于 `/var/backups/smail-internal-smtp-20260916-212617`,目录内 `result.json` 可复核。本轮通过的是内部接收链路,不代表网页按 MX 路由发信闭环或公网受信任 TLS 已通过。
|
||||
|
||||
## 备份与保留
|
||||
|
||||
以下目录均位于远程服务器,包含敏感运行数据,不提交仓库:
|
||||
@ -80,13 +109,14 @@
|
||||
- `/var/backups/smail-before-reset-20260916-202943`:第二轮清库前备份。
|
||||
- `/var/backups/smail-before-reset-20260916-205033`:前端修复升级前备份,本次未执行清库。
|
||||
- `/var/backups/smail-before-reset-20260916-210318`:邮件状态及 STARTTLS 修复升级前备份,本次未执行清库。
|
||||
- `/var/backups/smail-before-reset-20260916-212132`:中文附件解析修复升级前完整备份,本次未执行清库。
|
||||
|
||||
备份均对完整归档进行比较与 SHA 校验。两份数据库 inode 保持为 `smail.db=915735`、`smail-state.db=915727`。旧配置及迁移源已归档,没有删除其他项目文件,没有操作同机 Scert、MySQL 或 PostgreSQL 数据。
|
||||
|
||||
最新备份归档 SHA-256:
|
||||
|
||||
```text
|
||||
27a81af2b52946f1988173eecfb290e505ceca50de3634ef3d77013a1f1279dd
|
||||
9f712cb881db6acdcdefb1c20c5244524107529409196be810f8bb4d4c832c58
|
||||
```
|
||||
|
||||
测试用户与失败邮件保留用于后续复核,密码、密钥及会话凭据不写入记录。
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""仅在指定远程部署机执行内部 SMTP 收信回归,不修改业务配置。"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import smtplib
|
||||
@ -74,14 +75,17 @@ def main():
|
||||
(backup / name).chmod(0o600)
|
||||
with sqlite3.connect(f'file:{ROOT / "data/smail.db"}?mode=ro', uri=True) as database:
|
||||
database.row_factory = sqlite3.Row
|
||||
policy = json.loads((ROOT / 'config/email.json').read_text(encoding='utf-8')).get('policy', {})
|
||||
assert policy.get('spamFilterLevel', 0) == 0, 'This fixture requires the current unfiltered policy'
|
||||
users = dict(database.execute("SELECT account,id FROM user WHERE account IN ('admin','remotecheck')"))
|
||||
assert set(users) == {'admin', 'remotecheck'}
|
||||
results = []
|
||||
cases = [
|
||||
('text', ['remotecheck@solit.cn'], False, {users['remotecheck']}),
|
||||
('multipart', ['remotecheck@solit.cn', 'admin@solit.cn'], True, set(users.values())),
|
||||
('unknown', [f'missing-{RUN}@solit.cn'], False, set()),
|
||||
('relay', ['nobody@external-check.invalid'], False, set()),
|
||||
('admin_only', ['admin@solit.cn'], True, {users['admin']}),
|
||||
('unknown', [f'missing-{RUN}@solit.cn'], False, {users['admin']}),
|
||||
('relay', ['nobody@external-check.invalid'], False, {users['admin']}),
|
||||
]
|
||||
for label, recipients, multipart, expected_users in cases:
|
||||
result = deliver(label, recipients, multipart)
|
||||
@ -98,13 +102,16 @@ def main():
|
||||
attachments = json.loads(email['attachments'])
|
||||
assert len(attachments) == 1
|
||||
assert attachments[0]['Filename'] == '中文验收附件.txt', repr(attachments[0]['Filename'])
|
||||
assert base64.b64decode(attachments[0]['Content']) == ATTACHMENT
|
||||
result.update(email_id=email['id'], owners=sorted(expected_users),
|
||||
mailbox_status=[row['status'] for row in relations], stored=True)
|
||||
if label in ('unknown', 'relay'):
|
||||
result['outcome'] = 'administrator_fallback_no_outgoing_record'
|
||||
else:
|
||||
assert not emails, f'{label}: unexpected stored message'
|
||||
result.update(stored=False, outcome='accepted_then_discarded')
|
||||
results.append(result)
|
||||
summary = {'run': RUN, 'backup': str(backup), 'target': '127.0.0.1:25',
|
||||
summary = {'run': RUN, 'backup': str(backup), 'target': '127.0.0.1:25', 'spam_filter_level': 0,
|
||||
'certificate_sha256': hashlib.sha256((ROOT / 'ssl/custom.crt').read_bytes()).hexdigest(),
|
||||
'results': results}
|
||||
(backup / 'result.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user