Email MIME
2018-05-19
说明
MIME 是 Multipurpose Internet Mail Extensions 的缩写,多用途互联网邮件扩展 的意思。
最初的电子邮件标准 RFC 822 只支持发送 ASCII 字符文本内容,通过 MIME 这个拓展(RFC 2822),可以发送所有类型的内容。
后面的 HTTP 协议也是在 MIME 这个框架内构建的,Web 开发者比较熟悉的那几个 Content-XXX 头就是从 MIME 里面来的。
规范文件
| RFC No. |
Type |
Title |
| RFC 2045 |
Standards Track |
Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies |
| RFC 2046 |
Standards Track |
Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types |
| RFC 2047 |
Standards Track |
MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text |
| RFC 2048 |
Best Current Practice |
Multipurpose Internet Mail Extensions (MIME) Part Four: Registration Procedures |
| RFC 2049 |
Standards Track |
Multipurpose Internet Mail Extensions (MIME) Part Five: Conformance Criteria and Examples |
语法
5 个核心头(RFC 2045)和 Content-Disposition 扩展(RFC 2183):
MIME-Version: 1.0 // MIME 版本
Content-Type: [type]/[subtype]; parameter // 内容类型
Content-Transfer-Encoding: [encoding] // 内容传输编码
Content-ID: xxx
Content-Description: xxx // some descriptive information
Content-Disposition: [disposition] // 内容配置
内容类型 Content-Type
又叫互联网媒体类型(Internet media type)或者 MIME 类型(MIME type)。
类型信息的注册事宜,由 IANA(Internet Assigned Numbers Authority)统一管理。
按照注册来源分成几种类型,只用关心标准数,这个了解一下就行了:
- 标准树
类型名 / 子类型名 [ + 后缀 ] [ ; 可选参数 ]
- 厂商树
类型名 / vnd.子类型名 [ + 后缀 ] [ ; 可选参数 ],例如:application/vnd.debian.binary-package
- 个人树
类型名 / prs.子类型名 [ + 后缀 ] [ ; 可选参数 ]
- 未注册的 x.树
类型名 / x.子类型名 [ + 后缀 ] [ ; 可选参数 ]
常见的类型
详细类型在 IANA 官网有,链接在下面参考资料部分我贴了一个。
| Type |
Subtype |
Description |
| text |
plain |
文本 |
| text |
html |
HTML |
| text |
xml |
XML |
| text |
javascript |
JavaScript |
| text |
css |
CSS |
| text |
csv |
CSV |
| text |
vcard |
vCard 电子名片 |
| image |
bmp |
|
| image |
jpeg |
|
| image |
png |
|
| image |
gif |
|
| image |
webp |
|
| image |
svg+xml |
|
| image |
icon |
|
| audio |
mpeg |
|
| audio |
mp4 |
|
| audio |
ogg |
|
| audio |
webm |
|
| audio |
flac |
|
| video |
mpeg |
|
| video |
mp4 |
|
| video |
ogg |
|
| video |
webm |
|
| application |
xml |
XML |
| application |
json |
JSON |
| application |
ecmascript |
|
| application |
javascript |
|
| application |
zip |
|
| application |
gzip |
|
| application |
pdf |
|
| application |
rss+xml |
|
| application |
atom+xml |
|
| application |
octet-stream |
|
内容传输编码
编码类型主要是这几种:“7bit”,“8bit”,“binary”,“quoted-printable”,“base64”。
SMTP Email
2018-05-18
最常见的三种 SMTP 认证方法:
Email
2018-05-08
Return-Path 头就是收信方记录 SMTP 会话 MAIL FROM 地址用的,这个地址不会显示在邮件中,只是在邮件需要回退的时候才会起到作用。
Python SMTP PythonSimpleServer Email
2018-05-07
Python2
python2 -m smtpd -h
# An RFC 2821 smtp proxy.
# Usage: /usr/lib/python2.7/smtpd.py [options] [localhost:localport [remotehost:remoteport]]
# --nosetuid, -n
# --version, -V
# --class classname, -c classname
# --debug, -d
# --help, -h
Python3
python -m smtpd -h
# An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
# Usage: /usr/lib/python3.9/smtpd.py [options] [localhost:localport [remotehost:remoteport]]
# --nosetuid, -n 默认会设置用户为 nobody,如果不是 root 会因权限不足失败
# --version, -V
# --class classname, -c classname 默认: PureProxy
# --size limit, -s limit 消息大小限制(RFC 1870 SIZE extension),默认是 33554432 字节,即 32MB
# --smtputf8, -u 启用 SMTPUTF8 扩展(RFC 6531)
# --debug, -d
# --help, -h
# 如果不指定主机,就使用 localhost
# 如果主机是 localhost,端口使用 8025
# 如果是其他主机,端口使用 25
python3 -m smtpd -n
# 默认的 PureProxy 会给转信出去,正常情况会被服务器拒绝
python3 -m smtpd -n -c smtpd.DebuggingServer
Python 3.9 的 PureProxy 有 BUG,会报 process_message() got an unexpected keyword argument 'mail_options'。
自定义黑洞服务器
blackhole.py
import smtpd
import time
class BlackHoleServer(smtpd.SMTPServer):
def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
print('%s %s %s -> %s' % (time.strftime('%Y-%m-%d %H:%M:%S'), peer, mailfrom, rcpttos))
setup.py
import setuptools
setuptools.setup(name="blackhole", py_modules=["blackhole"])
附件下载:blackhole.zip
python setup.py install --user
python -m smtpd -n -c blackhole.BlackHoleServer
测试
import smtplib
smtp = smtplib.SMTP('localhost', 8025)
from_addr = 'admin@markjour.com'
to_addr = 'you@markjour.com'
smtp.sendmail(from_addr, to_addr,
f"""From: {from_addr}\nTo: {to_addr}\nSubject: just4fun\n\nhello, world!""")
Python FTP PythonSimpleServer
2018-05-07
FTP 需要 pyftpdlib 包的支持。
Python HTTP PythonSimpleServer
2018-05-07
Python2
在当前目录起 HTTP 服务,可以用于测试和临时性的文件下载服务。
# Default bind to 0.0.0.0:8000
python -m SimpleHTTPServer
# Maybe you want to use port 8080
python -m SimpleHTTPServer 8080
Python3
除了可以指定端口,还可以指定绑定地址、工作目录。
# Also bind to 0.0.0.0:8000
python -m http.server
python -m http.server -h
# usage: server.py [-h] [--cgi] [--bind ADDRESS] [--directory DIRECTORY] [port]
#
# positional arguments:
# port Specify alternate port [default: 8000]
#
# optional arguments:
# -h, --help show this help message and exit
# --cgi Run as CGI Server
# --bind ADDRESS, -b ADDRESS
# Specify alternate bind address [default: all interfaces]
# --directory DIRECTORY, -d DIRECTORY
# Specify alternative directory [default:current directory]
python -m http.server 9999
python -m http.server --bind=127.0.0.1
python -m http.server --bind=127.0.0.1 9999
python -m http.server -d ~/Pictures
Python Redis
2018-05-06
如果可以的话,使用 redis-cli monitor 命令来输出所有 Redis 命令也很方便。
有时,条件不允许,或者 Redis 需要处理其他的连接,我希望将自己代码调用的 Redis 命令输出到日志中,方便调试。
Email
2018-05-05
相关的文章:
邮件是由纯文本组成,其详细的格式有很多 RFC 规范需要遵守。我这里只能对我所了解的,也是基础的 —— 或者说最核心的 —— 格式做一个说明。
最核心的部分是 1982 年的 RFC 822 (STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES),之后又做过一些更新, 比如 RFC 2822 和 RFC 5322 (Internet Message Format) 和一堆补丁更新。本文要讲的基本格式,从开始到现在并没有什么明显变化。
-
邮件是一种纯文本格式,最开始只包含 ASCII 字符,后来引入了 MIME 之后,可以制定别的编码,比如 UTF-8 等。
-
换行符是 \r\n,也就是 CR + LF。
-
整体来说,一封邮件由邮件头(Headers)和邮件体(Payload)组成。
-
邮件头包含若干个头字段
-
邮件头和邮件体之间用一个空行隔开
-
RFC2882 和 RFC5322 都规定了电子邮件每一行的长度,排除行末 CRLF,不可以超过 998 个字符,建议不超过 78 个字符。
There are two limits that this specification places on the number of
characters in a line. Each line of characters MUST be no more than
998 characters, and SHOULD be no more than 78 characters, excluding
the CRLF.
如果太长,应该拆分成多行,下一行行首加上至少一个空格或者制表符,表示是上一行的延续。
邮件示例
From: Bob <bob@markjour.com>
To: Mark <mark@markjour.com>
Subject: Hello
Hello, Bob,
Would you like to join me for dinner?
--
Mark
RFC 822 中关于字符的定义
; ( Octal, Decimal.)
# 字符
CHAR = <any ASCII character> ; ( 0-177, 0.-127.)
# 字母
ALPHA = <any ASCII alphabetic character>
; (101-132, 65.- 90.)
; (141-172, 97.-122.)
# 数字
DIGIT = <any ASCII decimal digit> ; ( 60- 71, 48.- 57.)
# 控制字符
CTL = <any ASCII control ; ( 0- 37, 0.- 31.)
character and DEL> ; ( 177, 127.)
# 回车
CR = <ASCII CR, carriage return> ; ( 15, 13.)
# 换行
LF = <ASCII LF, linefeed> ; ( 12, 10.)
# 空格
SPACE = <ASCII SP, space> ; ( 40, 32.)
# 制表符
HTAB = <ASCII HT, horizontal-tab> ; ( 11, 9.)
# 引号
<"> = <ASCII quote mark> ; ( 42, 34.)
# 回车换行
CRLF = CR LF
# 空白
LWSP-char = SPACE / HTAB ; semantics = SPACE
# 连贯空白, 折行空白
linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE
; CRLF => folding
# 特殊字符
specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted-
/ "," / ";" / ":" / "\" / <"> ; string, to use
/ "." / "[" / "]" ; within a word.
# 分隔符
delimiters = specials / linear-white-space / comment
# 文本
text = <any CHAR, including bare ; => atoms, specials,
CR & bare LF, but NOT ; comments and
including CRLF> ; quoted-strings are
; NOT recognized.
# 原子字符
atom = 1*<any CHAR except specials, SPACE and CTLs>
quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or
; quoted chars.
qtext = <any CHAR excepting <">, ; => may be folded
"\" & CR, and including
linear-white-space>
domain-literal = "[" *(dtext / quoted-pair) "]"
dtext = <any CHAR excluding "[", ; => may be folded
"]", "\" & CR, & including
linear-white-space>
# 注释
comment = "(" *(ctext / quoted-pair / comment) ")"
ctext = <any CHAR excluding "(", ; => may be folded
")", "\" & CR, & including
linear-white-space>
quoted-pair = "\" CHAR ; may quote any char
phrase = 1*word ; Sequence of words
word = atom / quoted-string
对应上 ASCII:
0 - 31 控制字符, 其中包括常用的:
- HT ( 9) 水平制表符
- LF (10) 换行
- CR (13) 回车
32 空格
33 - 47 符号 !"#$%&'()*+,-./
48 - 57 数字
58 - 64 符号 :;<=>?@
65 - 90 大写字母
91 - 96 符号 [\]^_`
97 - 122 小写字母
123 - 126 符号 {|}~
127 控制字符(DEL)
CHAR 0-127
CTL 0-37 + 127
符号中:
()<>[]@,;:\".
13 个被视作特殊字符,需要转义
!#$%&'*+-/=?^_`
15 个就是普通符号
atom = 数字 + 字母 + 普通符号
邮件头格式
field = field-name ":" [ field-body ] CRLF
field-name = 1*<any CHAR, excluding CTLs, SPACE, and ":">
field-body = field-body-contents
[CRLF LWSP-char field-body]
field-body-contents =
<the ASCII characters making up the field-body, as
defined in the following sections, and consisting
of combinations of atom, quoted-string, and
specials tokens, or else consisting of texts>
字段名称允许使用的字符范围非常宽泛,而且大小写不敏感,
但是一般实践中:
常用字段
Received MTA 轨迹(传输过程中的相关信息)
Date 发信时间,格式:Fri, 21 Nov 1997 09:55:06 -0600
Sender Mail From 地址
From 发件人
Subject 邮件标题
To 收件人
Cc 抄送
Bcc 密送
Reply-To 回复地址
Message-ID 邮件标识
References 回复邮件标识,逗号隔开
In-Reply-To 回复邮件标识(会话发起的第一封)
Return-Path 发信任地址(2020/07/31,邮件的 Return-Path 头是什么)
Comments 说明
Keywords 关键字
一般采用 X- 开头的字段名称表示自定义字段,或者叫拓展字段:
常见的拓展字段:
日志 Linux
2018-05-03
配置
# cat /etc/logrotate.conf
weekly
su root adm
rotate 4
create
#dateext
#compress
include /etc/logrotate.d
以 Nginx 配置为例:
# cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # 按日切割
missingok # 如果文件不存在,则不创建
rotate 14 # 最多保留 14 个日志文件
compress # 压缩
delaycompress # 延迟压缩
notifempty # 如果文件为空,则不创建
create 0640 www-data adm
# 可能一次切割多个日志,
# 但是后面遇到的每个脚本都只执行一次,
# 在所有日志切割之前或之后
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
其他常用选项:
dateext 部分日志需要添加日期后缀
lastaction/endscript 最后执行的指令,很有用,比如最后将日志备份到某些地方
比如:
rsync -au *.gz 192.168.64.234:/backup/nginx-logs/`hostname -s`/
参考资料与拓展阅读
架构 DNS
2018-05-02
历史
从阿帕网(ARPANET)时代一直到互联网的早期,网络节点比较少,都是通过本地 hosts 文件来实现主机名到 IP 地址的映射。
根据维基百科的信息,斯坦福研究所(SRI-NIC,Stanford Research Institute Network Information Center)负责维护一个公共 HOSTS.TXT 文件(RFC 606、RFC 608),各个网络节点定期通过 FTP 下载最新版本进行同步。
PS:这个时候如果有主机名重复了谁来管?打电话过去让他们改名?
这套机制一直运行了十几年。随着互联网规模扩大,公共 HOSTS.TXT 文件变得越来越大,变化也越来越频繁。每当有新的主机加入网络、IP 地址发生变化,都需要重新同步整个文件。
这种模式存在几个明显问题:
- 中央管理机构压力越来越大;
- 全网频繁同步效率较低;
- 主机数量增长后难以维护;
- 名称冲突和管理问题越来越复杂。
后来人们开始设计域名和域名相关的公共设施。RFC 819《The Domain Naming Convention for Internet User Applications》和 RFC 881《The Domain Names Plan and Schedule》提出了域名系统的早期设计思路。
最终在 1983 年形成了下面两个 RFC 文档:
- RFC 882, DOMAIN NAMES - CONCEPTS and FACILITIES
- RFC 883, DOMAIN NAMES - IMPLEMENTATION and SPECIFICATION
几年后(1987),正式的 DNS 标准 RFC 1034 和 RFC 1035 推出。我们现在常见的 A / MX / NS / CNAME / PTR / SOA 等记录类型都是在这个标准中定义的。
- RFC 1034, Domain Names Concepts and Facilities
- RFC 1035, Domain Names Implementation and Specification
这套标准一直运行到现在。虽然几十年来不断增加新的记录类型、支持国际化域名(IDN)、引入 DNSSEC、安全加密传输等扩展能力,但其核心设计思想基本没有改变:
DNS 可以说是互联网历史上寿命最长、最成功的基础设施之一。并且,在可以预见的未来,DNS 仍然会是互联网最重要的基础设施之一。
DNS over TCP
最初 DNS 主要运行于 UDP 53 端口。RFC 1035 同时规定了 TCP 支持,最早 TCP 主要用于:
今天绝大多数 DNS 服务仍同时支持 UDP 和 TCP。
DNS 的管理权问题
互联网本是美国国防部高级研究计划局(DARPA)的产物,早期域名分配几乎由南加州大学教授乔恩·波斯特尔一人代管。1998年,克林顿政府为避免让联合国下属的国际电信联盟(ITU)——一个由各国政府和官营电信主导的机构——接管互联网,刻意设计了一条"私有化"路线:成立非营利组织 ICANN,负责全球域名系统(DNS)和顶级域名分配,与美国商务部签合同但通过"私人身份"运作,以此保持互联网开放、不受单一政府直接操控。然而 ICANN 总部在加州、受美国法律管辖,全球13台根服务器多数也在美国手里,等于事实上的互联网域名最高仲裁者仍是美国。这让包括中国、俄罗斯在内的大量国家长期不满,伊拉克战争期间".iq"域名被暂停解析一事更被视为美国"悬剑在手"的象征。
2013年斯诺登曝光美国大规模监听后,国际社会对"美国垄断互联网命门"的反弹到达顶点,联合国层面甚至出现推动另建政府主导治理体系的呼声。为消解这一合法性危机、顺应互联网去中心化叙事,奥巴马政府于2014年宣布启动移交程序——将美国商务部对 ICANN 的"合同监管权"解除。2016年3月,ICANN 正式提交脱离美国政府联系的过渡计划。
真正的问题不在"美国是否善良",而在于:这根命门本来就不该由任何一个国家独握。移交的本质是把制度合法性从"美国庇护下的开放"转向"多方利益攸关体(multi‑stakeholder)自我维持的透明与问责"。但争议也正在于此——保守派担心脱离美国后,ICANN 会被威权国家通过政府咨询渠道逐步蚕食;支持者则认为根服务器的分布式结构和自下而上的共识机制本身就是防火墙。美国让出的不是善意,而是一张越来越护不住的垄断椅子。
新的发展
| 年份 |
技术/标准 |
RFC |
主要创新 |
| 1983 |
DNS 概念与实现 |
RFC 882, RFC 883 |
首次提出 DNS,替代 HOSTS.TXT |
| 1987 |
DNS 正式标准 |
RFC 1034, RFC 1035 |
建立现代 DNS 架构,至今仍是核心基础 |
| 1997 |
动态更新(DDNS) |
RFC 2136 |
DNS UPDATE 接口,允许客户端动态修改 DNS 记录 |
| 1998 |
NOTIFY |
RFC 1996 |
主 DNS 主动通知从 DNS 更新,不用定时轮询 |
| 1999 |
IXFR(增量同步) |
RFC 1995 |
只同步变更部分,降低 Zone Transfer 成本 |
| 1999 |
EDNS0 |
RFC 2671 -> 6891 |
打破 UDP 512 字节限制(拓展到 KB 级别) |
| 2000 |
SRV 记录 |
RFC 2782 |
DNS 服务发现机制 |
| 2003 |
国际化域名(IDNA) |
RFC 3490 系列 |
支持中文、日文等 Unicode 域名 |
| 2005 |
DNSSEC |
RFC 4033/4034/4035 |
DNS 数据签名与验证 |
| 2008 |
Source Port Randomization |
RFC 5452 |
提高抗缓存投毒能力 |
| 2010 |
NSEC3 |
RFC 5155 |
防止 DNSSEC Zone Walking(枚举所有域名) |
| 2011 |
CAA 记录 |
RFC 6844 -> 8659 |
允许域名所有者声明:仅承认某些 CA 给该域名颁发的证书 |
| 2013 |
DNS Cookies |
RFC 7873 |
防御 DNS 放大攻击 |
| 2016 |
DNS over TLS (DoT) |
RFC 7858 |
DNS 加密传输 |
| 2018 |
QNAME Minimization |
RFC 7816 |
降低 DNS 查询隐私泄露 |
| 2018 |
DNS over HTTPS (DoH) |
RFC 8484 |
DNS 走 HTTPS 通道 |
| 2020 |
SVCB / HTTPS RR |
RFC 9460 |
服务发现和 HTTPS 配置优化 |
| 2021 |
Oblivious DoH (ODoH) |
RFC 9230 |
隐藏客户端 IP |
| 2022 |
DNS over QUIC (DoQ) |
RFC 9250 |
基于 QUIC 的 DNS |
| 2023 |
DDR |
RFC 9462 |
自动发现 DoH/DoT 服务 |
RFC 2136: Dynamic Updates in the Domain Name System (DNS UPDATE)
复用 DNS 报文格式,但把 Opcode 设为 5(UPDATE),直接在 UDP/TCP 53 端口上向权威 DNS 服务器发送增删改指令(四段式结构):
| 段 |
作用 |
| Zone |
声明你要改哪个 zone(如 home.arpa.或 example.com.) |
| Prerequisite |
前置条件——"只有当 host A == 1.2.3.4时才更新"(原子 compare-and-swap,防竞态) |
| Update |
实际操作——ADD / DELETE 某条 RR |
| Additional |
认证载体——通常是 TSIG(RFC 2845)或 GSS-TSIG(RFC 3645, Kerberos) |
注意:DDNS 本身可以说是一个需求,就是动态调整 DNS,虽然 RFC 2136 经常被称之为 DDNS,但实质是 DDNS 的一种技术方案。
有一些脚本或者路由器插件轮询 IP 然后通过云服务商 API 来动态修改 DNS 记录,在很多场景下也被称之为 DDNS,不要混淆。
云服务商 DNS 都不对外提供 UPDATE 操作,它们通过 HTTP API(IAM/Key 鉴权)来提供 DNS 记录更新。
RFC 2136 DDNS 的典型阵地是你自有/私有部署的受控权威服务器(BIND、PowerDNS、Windows AD DNS、Infoblox 等),其中最常见生产场景是:
客户端发起 DHCP 请求时会带上 hostname(Option 12),DHCP 服务器分配地址后,通过 TSIG/GSS-TSIG 向可写 zone 发 UPDATE,自动注册内部名 A 记录及对应 PTR(反向区也要可被管理)。
-
DNSSEC
安全
-
DNSCrypt
2011 年提出。
DNSCrypt 在 DNS 请求和解析服务器之间建立加密通信,并验证服务器身份。
它并非 IETF 标准,但曾经被 OpenDNS 等服务广泛采用。
-
DNS over TLS(DoT)
2016 年成为标准。RFC 7858 Specification for DNS over Transport Layer Security (TLS)
DNS 全程加密,默认使用 TCP 853 端口。
-
DNS over HTTPS(DoH)
2018 年成为标准。RFC 8484 DNS Queries over HTTPS (DoH)
特点:
- 基于 HTTPS;
- 默认使用 HTTPS 443 端口;
- 更容易穿透网络限制;
-
可以复用现有 Web 基础设施。
-
DNS over QUIC(DoQ)
RFC 9250。
基于 QUIC,兼具 UDP 的低延迟和 TLS 的安全性。
-
DNS over Tor
通过 Tor 网络转发 DNS 查询,其主要目标是隐藏客户端来源地址,提高匿名性。
它不是 DNS 标准协议,而是一种部署方案。
-
Oblivious DNS-over-HTTPS(ODoH)
ODoH 在 DoH 基础上增加代理节点,确保代理服务器看不到查询内容,DNS 服务器看不到客户端 IP,从而进一步提升隐私保护能力。
参考资料与拓展阅读