RFC(The Requests for Comments)是用来规定互联网工作标准的文档。我们使用的时候并没有注意到这些协议在我们的邮件通信过程中默默的发挥着的作用,这丝毫也不能减低这些作用的重要性。邮件内部还有很多不为人知的秘密。
在RFC822中规定一封信包括一个必须的多个头部域(header fields)和一个可选的体部(body)组成。从一封信头开始至第一个空行都是头部。头部定义了一个邮件的各项基本要素,路由信息等内容。
在Outlook Express中选定一封信看它的属性。在详细资料选项卡中显示的就是这封邮件的头部内容。也可以选定一封信,另存为一个.eml文件。由于文件是一个纯文本文件,用一般的编辑器打开就可以看到邮件的内容。
头部有各个头部域组成,每一个头部域都包括域名(field-name)和域体(field-body),它们之间以":"分隔。每一个头部域都可以看作由ASCII码字符组成的的文本。常见的头部域包括:"Return-Path", "Received", "Date", "From", "Subject", "Sender等。各头部域之间没有规定顺序。
就像各个域的名字一样。他们表示的具体意义也不同。
Return-Path域表示退信的地址。
Received域表示路由信息。Internet上的信件可能是由多个服务器依靠协议传递到达最终的目的邮箱的。每一个服务器都会把自己的一段 Received域信息添加近信件。所以可能有多段Received域,依传递次序排列。这个域的内容很有意思,我们可以根据每一段的内容来跟踪一封信在internet上的传递过程。网管人员也可以根据这些信息做出很有价值的判断。
例如一段代码中
Received: from unknown (unknown [202.108.44.208]) by mx7.163.com (Postfix) with SMTP id 74E0E8 for; Fri, 28 Feb 2003 18:10:25 +0800 (CST)中,from 表示发送主机,by表示接受主机,via物理路径(本例没有),with表示使用的协议,id表示接受消息号,for表示目的邮件地址,分号后面表示时间。
Date域表示建立信件的时间。
From域表示邮件作者。
Subject域就是邮件的主题。
Sender域表示邮件的实际发送者。
To,cc域都表示接受的邮件地址。
MIME-Version域表示MIME的版本(以后扩充的域)
还有一个域—Content-Type。标识了一个重要的概念:邮件内容的格式。这个域的内容根据互联网的发展产生,使邮件可以用来传输非文本内容,在RFC822中并没有定义,而是在后续的RFC2045,RFC2046等文档中有定义。
还有一些不太常见的头部域,如"References域。以及一些由不同厂家定义的自己的域(域名以"X-"开头)。
知道了这些知识,就可以看懂邮件头部。了解到那些邮件的秘密。
比如这是一封邮件的头部。
Return-Path: "sea"
Received: from unknown (unknown [202.108.44.208])
by mx7.163.com (Postfix) with SMTP id 74E0E8
for ; Fri, 28 Feb 2003 18:10:25 +0800 (CST)
Received: from smtp.netcs.com (unknown [211.150.100.6])
by 192.168.1.208 (Coremail:www.163.com) with SMTP id cAsAAJY1Xz7kAmQG.1
for ; Fri, 28 Feb 2003 18:10:30 +0800 (CST)
Received: from sea (unknown [192.168.18.160])
by smtp.netcs.com (Postfix) with ESMTP id E55D02675F
for ; Fri, 28 Feb 2003 18:09:58 +0800 (CST)
Message-ID: <000901c2df11$2fcac090$a012a8c0@sea>
From: "sea"
To: "=?gb2312?B?usK6vQ==?="
Message-ID: <000901c2df11$2fcac090$a012a8c0@sea>
References: <000c01c2decb$9c7b7860$a012a8c0@sea>
<004601c2dee2$28a348c0$2400a8c0@haostation>
<001501c2deeb$352b34a0$a012a8c0@sea>
Subject: Re: hello
Date: Fri, 28 Feb 2003 18:07:22 +0800
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="----=_NextPart_000_0006_01C2DF54.3DDEBE50"
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2600.0000
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2600.0000
通过辨认就可以知道这是一封从(From域)"sea" 写给(To域)"=?gb2312?B?usK6vQ==?=" 的Id(Message-ID域)为<000901c2df11$2fcac090$a012a8c0@sea>的回信。退信的地址(Return-Path域)是haibo.yang@netcscom可以看出(References域)这封信已经经过了连续三次回复。主题(Subject域)是Re: hello。时间(Date域)是2003年的3月28日。邮件优先级(X-Priority域)是普通。邮件的传递依次经过了三个服务器(Received域):smtp.netcs.com;unknown;和mx7.163.com。还可以看到各服务器的协议和到达各服务器的时间。其中Postfix和Coremail都是常见的邮件系统。其中邮件接收地址中的内容由于包含汉字进行了编码。
一封邮件首先建立的是核心的几个域:From,To,Message-Id,Subject,Date等,在传输过程中每通过一个服务器再由此服务器在邮件头部添加一行Received信息,最后一个服务器添加退信地址(Return-Path)。
邮件头的格式制定的细节是根据信息交流和网络传输的特点制定的。阅读它们不仅可以知道邮件传输的秘密,还可以让我们体会到协议制定与实现间的互相关联的紧密关系。可以通过http://www.rfc.net/ 查询到RFC822,同时可以查询到其它重要的RFC文档。
MX记录的应用
在DNS服务器上除了可以建立主机名与IP地址的映射外,还可以建立其他多种映射,例如,建立某个主机名与其别名的映射;建立某个域名与其SMTP服务器的映射。在DNS服务器上创建的各项映射关系称为记录,一项映射关系就是一条记录,在DNS服务器上创建的主机名与IP地址的映射关系称为A记录,主机名与别名的映射关系称为CNAME记录,域名与其SMTP服务器的映射关系称为MX记录。
在DNS服务器上为什么要建立MX记录呢?即为什么要建立域名与其SMTP服务器的映射关系呢?这与电子邮件地址的表示形式和工作原理有关。邮件地址后缀部分表示的通常都是一个域名,而不是接收邮件的服务器的主机名,例如,邮件地址“zxx@it315.org”中的“it315.org”对应的就是一个域名。域只是一个逻辑组合概念,它并不代表真正的计算机,对于使用某个域名作为后缀的邮件地址,外界发送给它的电子邮件必须由一台专门的SMTP服务器来进行接收和处理,接收和处理某个域的电子邮件的SMTP服务器即为该域的SMTP服务器,外界发送给某个域的电子邮件实际上都是发送给该域的SMTP服务器。外界如何知道一个域的SMTP服务器的地址呢?这就是通过管理该域的DNS服务器上的MX记录来获得的,这也就是在DNS服务器上为什么要建立域名与其SMTP服务器的映射关系的原因。
当某台SMTP服务器要给“zxx@it315.org”发送一封电子邮件时,该SMTP服务器将根据邮件地址的后缀部分而去查询“it315.org”这个域的MX记录,得到这个域的SMTP服务器的主机名为“mail.it315.org”,然后将邮件发送给“mail.it315.org”这个SMTP服务器。
动手实践 根据域名查询SMTP服务器
Windows操作系统自带的nslookup命令可以查询一个域的MX记录。要想知道负责接收某个电子邮箱的邮件的SMTP服务器,可以先根据该邮件地址的后缀部分推断出其域名,然后使用nslookup命令进行查询即可。下面以查找负责接收“zxx@sina.com”这个邮件地址的SMTP服务器的信息为例进行讲解。
启动Windows下的DOS命令行窗口,执行nslookup命令,结果如图1.16所示。
图1.16中“ns2.bjgwbn.net.cn”表示执行nslookup命令的这台计算机当前所使用的DNS查询服务器的主机名,“211.161.46.84”则表示该DNS服务器的IP地址。
在图1.16中的“>”提示符后面先输入“set type=mx”命令,设置所要查询的记录类型为MX,然后输入域名“sina.com”,结果如图1.17所示。
图1.16 图1.17
图1.17中显示了“sina.com”这个域的SMTP服务器的主机名和IP地址,其中“sinamx.sina.com.cn”为SMTP服务器的主机名,并且该主机名对应了3个IP地址。这说明“sina.com”为了扩充其邮件处理能力,使用了3台SMTP服务器来接收外界发送给该域的邮件,这3台服务器的主机名都为“sinamx.sina.com.cn”,外界的SMTP服务器可以选择其中任何一台进行连接,然后将邮件发送给该服务器。
在图1.17中的“>”提示符后面再次输入“sina.com”,nslookup命令将再次查询该域的MX记录,结果如图1.18所示。
比较图1.18中的用方框标识的部分,可以看到这两次查询出的“sinamx.sina.com.cn”所对应的3个IP地址的排列顺序并不相同,第一次查询时“202.108.3.187”排列在最前面,而第二次查询时却变成了“202.108.3.188”排列在最前面。如果继续查询“sina.com”域的MX记录,可以看到“sinamx.sina.com.cn”所对应的3个IP地址的排列顺序总是在改变。这是由于sina在管理“sina.com.cn”域的DNS服务器上进行了专门的设置,当外界每次查询“sinamx.sina.com.cn”这个主机名的IP地址时,管理“sina.com.cn”域的DNS服务器都将对该主机名所对应的3个IP地址进行轮循排列后再返回。
图1.18
对于查询到一个主机名对应多个IP地址的情况,外界的计算机通常都是选择其中的第一个IP地址来进行通信,只要管理“sina.com.cn”域的DNS服务器每次都将“sinamx.sina.com.cn”所对应的3个IP地址进行轮循排列后再返回给外界,外界的SMTP服务器将会分别选择到不同的IP地址来进行通信,sina通过这种简单的方式就在“sinamx.sina.com.cn”所对应的3台SMTP服务器之间实现了负载均衡。
提示:使用nslookup查询“sina.com”域的MX记录时,读者可能会遇到DNS服务器只返回SMTP服务器的主机名,而不返回该主机名所对应的IP地址的情况,这时候可以在原来的nslookup命令窗口中输入“set type=a”命令,即将要查询的记录类型设置为A,然后输入前面查询到的SMTP服务器的主机名就可以得到其IP地址,如图1.19所示。
图1.19
电子邮件的工作原理
下面以sina和sohu这两个电子邮局为例来讲解电子邮件的传输过程和工作原理。假设sina邮箱的账户为lisi@sina.com,sohu邮箱的账户为wangwu@sohu.com,它们之间的邮件收发过程如图1.20所示。
图1.20
图1.20中实线部分表示lisi@sina.com账户向wangwu@sohu.com账户发送邮件的过程,虚线部分表示wangwu@sohu.com账户向lisi@sina.com账户发送邮件的过程。下面通过分析lisi@sina.com账户向wangwu@sohu.com账户发送邮件的过程,来具体讲解一封邮件从发送到接收所涉及的环节。
lisi@sina.com的邮件客户端程序(这里假设为Outlook Express)与sina的SMTP服务器建立网络连接,并以lisi的用户名和密码进行登录后,使用SMTP协议把邮件发送给sina的SMTP服务器。
sina的SMTP服务器收到lisi@sina.com提交的电子邮件后,首先根据收件人的地址后缀判断接收者的邮件地址是否属于该SMTP服务器的管辖范围,如果是的话就直接把邮件存储到收件人的邮箱中,否则,sina的SMTP服务器向DNS服务器查询收件人的邮件地址后缀(sohu.com)所表示的域名的MX记录,从而得到sohu的SMTP服务器信息,然后与sohu的SMTP服务器建立连接并采用SMTP协议把邮件发送给sohu的SMTP服务器。
sohu的SMTP服务器收到sina的SMTP服务器发来的电子邮件后,也将根据收件人的地址判断该邮件是否属于该SMTP服务器的管辖范围,如果是的话就直接把邮件存储到收件人的邮箱中,否则(一般不会出现这种情况),sohu的SMTP服务器可能继续转发这封电子邮件,也可能丢弃这封电子邮件。
拥有wangwu@sohu.com账户的用户通过邮件客户端程序(这里假设也为Outlook Express)与sohu的POP3/IMAP服务器建立网络连接,并以wangwu的用户名和密码进行登录后,就可以通过POP3或IMAP协议查看wangwu@sohu.com邮箱中是否有新邮件,如果有的话,则使用POP3或IMAP协议读取邮箱中的邮件。
图1.20中的虚线部分表示wangwu@sohu.com账户向lisi@sina. com账户发送邮件的过程,此过程与lisi@sina.com账户向wangwu@ sohu.com账户发送邮件的过程类似,这里不再复述。
多想一想 邮件客户端程序和SMTP服务器
分别如何发送邮件
邮件客户端软件与SMTP服务器之间,以及两台SMTP服务器之间都采用SMTP协议进行通信。邮件客户端软件只采用SMTP协议发送邮件,即邮件客户端软件只作为SMTP协议的发送方。SMTP服务器既要采用SMTP协议向其他SMTP服务器发送邮件,又要采用SMTP协议接收其他SMTP服务器或邮件客户端软件发送来的邮件,即SMTP服务器既作为SMTP协议的发送方,又作为SMTP协议的接收方。邮件客户端软件与SMTP服务器都可以使用SMTP协议发送邮件,仅发送邮件这一功能而言,它们在技术实现上有何差异呢?它们的差异主要体现在以下两个方面:
(1)SMTP服务器接收到邮件客户端软件发送来的邮件后,需要根据收件人地址的域名将邮件转发给目标域的SMTP服务器,而大量的收件人地址中肯定会出现各种可能的域名,因此,SMTP服务器涉及到要与其他多台不能事先确定的SMTP服务器进行通信,它需要具有根据收件人地址的域名查询出该域的SMTP服务器的功能,即需要具有查询域名的MX记录的功能。邮件客户端软件被设置为与固定的SMTP服务器通信,它可以直接与指定的SMTP服务器建立网络连接,因此它不需要具有根据收件人地址的域名来查询该域的SMTP服务器的功能。
(2)对于来自某个邮件客户端软件的邮件传输请求,SMTP服务器可能需要对发件人的用户账号信息进行验证,因此,邮件客户端软件需要具有向SMTP服务器传送用户账号信息的功能。SMTP服务器能够直接接受来自其他SMTP服务器的邮件传输请求,因此,SMTP服务器在发送邮件时根本不需要传送用户账号信息的功能。
RFC822邮件格式
邮件内容的格式在RFC822文档中定义,它包括两个主要的组成部分:邮件头和邮件体。在第2章的SMTP协议的实验中,it315_test @sohu.com邮箱中接收到的it315_test@sina.com发送来的邮件的最终内容如例程3-1所示,其中的行号不属于邮件内容,是作者为了便于讲解而加上的。
例程3-1 test.eml
1.Return-Path: 2.Delivered-To: it315_test@mx72.mail.sohu.com 3.Received: from smtp.sina.com.cn (unknown [202.108.3.177]) 4. by sohumx139.sohu.com (Postfix) with SMTP id E4F9802C1249 5. for 6.Received: (qmail 49221 invoked from network); 10 Nov 2005 08:39: 33 -0000 7.Received: from unknown (HELO it315?test) (218.246.5.151) 8. by smtp.sina.com.cn with SMTP; 10 Nov 2005 08:39:33 -0000 9. From: it315_test@sina.com 10.To: it315_test@sohu.com 11.subject:test 12.Message-Id: <20051110083950.E4F9802C1249@sohumx139.sohu.com> 13.Date: Thu, 10 Nov 2005 16:39:50 +0800 (CST) 14.Status: RO 15.X-UIDL: 1131611863.21509_77.mx72 16. 17.test!!! 例程3-1中的第1~15行是邮件的邮件头,第17行是邮件的邮件体,邮件头和邮件体之间以一个空行进行分隔。这封邮件的邮件体内容非常少,只有一行“test!!!”文本。邮件头部分由多个头字段和字段内容组成,各种头字段分别用于表示邮件的发件人、收件人、发件时间和主题等信息。细心的读者可能已经看到,例程3-1中的邮件头部分比在第2章的SMTP协议的实验中实际发送的邮件头多出了一些头字段,这些头字段是各个SMTP服务器在传递邮件的过程加上的。SMTP服务器在传递邮件时,会把一些相关信息增加到邮件的邮件头中,这种情况有点类似于现实生活中的邮局在处理邮件时,通常都会在信封上加盖邮戳一样,表示这封邮件在什么时候经过了哪个邮局和由哪个工作人员经手处理。SMTP服务器按从下往上的方式添加各个字段,即先添加的字段位于后添加的字段的下面,例如,例程3-1中的邮件是由sina发送给sohu的,它首先经过sina的SMTP服务器,然后经过sohu的SMTP服务器,所以,sohu的SMTP服务器添加的头字段(1~5行)位于sina的SMTP服务器添加的头字段(6~8行)的上面。另外,POP3服务器也会在邮件头中增加一些头字段,例如,例程3-1中的12~15行。 每一个邮件头以“字段名:字段值”的格式出现,即每一行邮件头的内容依次由字段名、冒号、空格、字段值、回车换行符组成。RFC822文档中定义了多个标准的邮件头字段,每一个邮件头字段表示一种特定的信息。邮件头中也可以包含自定义的头字段,这种自定义的头字段通常是某个组织或机构内部专用的。下面是对例程3-1中出现的一些主要的邮件头字段的解释: — Return-Path 该字段代表邮件的回复地址,该字段内容由接收邮件的SMTP服务器填写,接收邮件的SMTP服务器从邮件发送程序发出的mail form命令中获得该字段内容。 — Received 该字段的基本格式为Received from A by B for C,其中A为发送方,B为接收方,C为收件人的邮箱地址。该字段的内容由接收邮件的SMTP服务器填写,常常被用来追踪邮件传输的路线和分析邮件的来源,例如,从例程3-1中的各个Received字段中,可以知道这封邮件的传输路径:从IP地址为【218.246.5.151】的机器上发出→【smtp.sina.com.cn】→【sohumx139.sohu.com】→【it315_test@sohu.com】。例程3-1中的第6行的“Received: (qmail 49221 invoked from network)”是sina的SMTP服务器内部调用的一个邮件发送模块添加的,它说明sina的SMTP服务器接收到邮件后再通过这个邮件发送模块将邮件转发出去。显然,通过分析一封邮件的源内容,是可以知道发件人的IP地址的。 — From 该字段用于指定的发件人地址,邮件阅读程序显示的发件人地址就来源于这个字段。From字段中指定的发件人地址可以随意乱写,甚至不写,所以,邮件阅读程序显示的发件人地址不一定是真实的,这通常可以通过查看邮件头中的Return-Path字段来判断发件人的真实性。注意,SMTP协议中mail from命令中指定的发件人地址也可以伪造,所以,邮件头中的Return-Path字段也不是可以完全信赖的,对于比较重要的邮件,最好还是通过电话确认一下。 — to 该字段用于指定收件人地址。 — subject 该字段用于指定邮件的主题,如果主题内容中包含有ASCII码以外的字符,通常要对其内容进行编码。 — date 该字段用于指定邮件的发送时间。 邮件头中除了包含上面的这些字段外,还有另外两个比较常用的字段: — cc 该字段用于指定邮件的抄送地址。 — bcc 该字段用于指定邮件的暗送地址。 抄送地址和暗送地址的区别在于,邮件阅读程序通常都不显示暗送地址,而会显示抄送地址。 邮件阅读程序在显示邮件信息时,所显示出来的发件人、收件人和主题等信息就是从相应的邮件头字段中提取出来的,例程3-1的内容在OutLook中的显示效果如图3.1所示。 图3.1 动手实践 在Outlook中查看邮件的源内容 选中Outlook收件箱内的一封邮件,单击鼠标右键,然后单击弹出菜单中的“属性”菜单项,如图3.2所示。 在打开的属性对话框中,单击“详细信息”标签,然后单击“邮件来源…”按钮,就可以看到邮件的源文件内容了,如图3.3所示。 图3.2 图3.3 RFC822文档定义了邮件内容的主体结构和各种邮件头字段的详细细节,但是,它没有定义邮件体的格式,RFC822文档定义的邮件体部分通常都只能用于表述一段普通的文本,而无法表达出图片、声音等二进制数据。另外,SMTP服务器在接收邮件内容时,当接收到只有一个“.”字符的单独行时,就会认为邮件内容已经结束,如果一封邮件正文中正好有内容仅为一个“.”字符的单独行,SMTP服务器就会丢弃掉该行后面的内容,从而导致信息丢失。 由于Internet的迅猛发展,人们已不满足于电子邮件仅仅是用来交换文本信息,而希望使用电子邮件来交换更为丰富多彩的多媒体信息,例如,在邮件中嵌入图片、声音、动画和附件。但是,由于图片和声音等内容是非ASCII码的二进制数据,而RFC822邮件格式只适合用来表达纯文本的邮件内容,所以,要使用RFC822邮件格式发送这些非ASCII码的二进制数据时,必须先采用某种编码方式将它们“编码”成可打印的ASCII字符后再作为RFC822邮件格式的内容。邮件阅读程序在读取到这种经过编码处理的邮件后,再按照相应的解码方式解码出原始的二进制数据,这样就可以借助RFC822邮件格式来传递多媒体数据了。这种做法需要解决一下两个技术问题: (1)邮件阅读程序如何知道邮件中嵌入的原始二进制数据所采用的编码方式; (2)邮件阅读程序如何知道每个嵌入的图像或其他资源在整个邮件内容中的起止位置。 针对这个问题,人们后来专门为此定义了MIME(Multipurpose Internet Mail Extension,多用途Internet邮件扩展)协议。 MIME协议用于定义复杂邮件体的格式,它可以表达多段平行的文本内容和非文本的邮件内容,例如,在邮件体中内嵌的图像数据和邮件附件等。另外,MIME协议的数据格式也可以避免邮件内容在传输过程中发生信息丢失。MIME协议不是对RFC822邮件格式的升级和替代,而是基于RFC822邮件格式的扩展应用。一言以蔽之,RFC822定义了邮件内容的格式和邮件头字段的详细细节,MIME协议则是定义了如何在邮件体部分表达出的丰富多样的数据内容。 一个采用了MIME协议的电子邮件就叫做MIME邮件,MIME邮件在RFC822文档中定义的邮件头字段的基础上,扩充了一些自己专用的邮件头字段,例如,使用MIME-Version头字段指定MIME协议的版本,使用Content-Type头字段指定邮件体的MIME类型,使用Content-Transfer-Encoding头字段指定编码方法,如下所示: MIME-Version: 1.0 Content-Type:multipart/mixed;boundary="----=_NextPart_000_0050_01C" 其中,“multipart/mixed”部分说明邮件体中包含有多段数据,每段数据之间使用boundary属性中指定的字符文本作为分隔标识符。 另外,MIME邮件也扩展了RFC822文档中已经定义了的邮件头字段的内涵,例如,定义了subject头字段中的值内容的格式,以便通过编码的方式让邮件主题中也可以使用非ASCII码的字符。subject头字段中的值嵌套在一对“=?”和“?=”标记符之间,标记符之间的内容由三部分组成:邮件主题的原始内容的字符集、当前采用的编码方式、编码后的结果,这三部分之间使用“?”进行分隔。下面是一个对包含有非ASCII码字符的邮件主题进行了编码后的结果: Subject: =?gb2312?B?TUlNRdCt0unLtcP308q8/g==?= 其中,“gb2312”部分说明邮件主题的原始内容为gb2312编码的字符文本,“B”部分说明对邮件主题的原始内容按照BASE方式进行了编码,“TUlNRdCt0unLtcP308q8/g==”为对邮件主题的原始内容进行了BASE编码后的结果。 可见,MIME邮件与普通的RFC822邮件的关系犹如Java编程语言中的子类与父类的关系,子类是对父类的扩展,子类功能更强大,但子类离不父类的支持。如果需要了解MIME的详细细节,可以查阅RFC 2045~2049系列文档。 MIME邮件的组织结构 一封MIME邮件可以由多个不同类型的MIME消息组合而成,一个MIME消息表示邮件中的一个基本MIME资源或若干基本MIME消息的组合体。每个MIME消息的数据格式与RFC822数据格式相似,也包括头和体两部分,分别称为MIME消息头和MIME消息体,它们之间使用空行分隔。MIME消息体中包含了资源的具体内容,MIME消息头中则包含了对资源的描述信息。多个相同层次的MIME消息必须形成一个MIME组合消息,它们共同作为所形成的MIME组合消息的MIME消息体,相互之间采用某种分隔标识符进行分隔,MIME组合消息的消息头中需要描述其中的多个MIME消息的组合类型和分隔标识符。一个MIME组合消息还可以再与其他MIME消息共同形成一个更大的MIME组合消息,这样就形成了一种多层嵌套的组合关系,一封MIME邮件就是按这种组合方式所形成的一个最顶层的MIME组合消息。 MIME协议采用这种多层组合方式可以表达出复杂的邮件内容,例如,对于在OutLook Express中显示出的如图3.4所示效果的一封复杂邮件,可以采用图3.5所示的MIME组合结构来表达。 图3.4中的邮件正文为一段HTML格式的文本和其引用的一个图片资源,该邮件还包含两个附件:一个是WAV声音文件,一个是EXE可执行文件。该邮件的MIME结构如图3.5所示,其中的HTML文本内容、图片资源和两个附件用四个的MIME消息表示,HTML文本内容和图片资源又组合成一个表示邮件正文的MIME组合消息,这个MIME组合消息再与两个附件对应的MIME消息组合成了一个更大的MIME组合消息,这就形成了整封邮件的MIME消息。 图3.4 图3.5 MIME消息的头字段 1: Content-Type 对于表示某个具体资源的MIME消息,它的消息头中需要指定资源的数据类型;对于MIME组合消息,它的消息头中需要指定组合关系。具体资源的数据类型和组合消息的组合关系,都是通过消息头中的Content-Type头字段来指定的。Content-Type字段中的内容以“主类型/子类型”的形式出现,主类型有text、image、audio、video、application、multipart、message等,分别表示文本、图片、音频、视频、应用程序、组合结构、消息等。每个主类型下面都有多个子类型,例如text主类型包含plain、html、xml、css等子类型。multipart主类型用于表示MIME组合消息,它是MIME协议中最重要的一种类型。一封MIME邮件中的MIME消息可以有三种组合关系:混合、关联、选择,它们对应MIME类型如下: — multipart/mixed 表示消息体中的内容是混和组合类型,内容可以是文本、声音和附件等不同邮件内容的混和体,例如图3.5中的整封邮件的MIME类型就必须定义为multipart/mixed。 — multipart/related 表示消息体中的内容是关联(依赖)组合类型,例如图3.5中的邮件正文要使用HTML代码引用内嵌的图片资源,它们组合成的MIME消息的MIME类型就应该定义为multipart/related,表示其中某些资源(HTML代码)要引用(依赖)另外的资源(图像数据),引用资源与被引用的资源必须组合成multipart/related类型的MIME组合消息。 — multipart/alternative 表示消息体中的内容是选择组合类型,例如一封邮件的邮件正文同时采用HTML格式和普通文本格式进行表达时,就可以将它们嵌套在一个multipart/alternative类型的MIME组合消息中。这种做法的好处在于如果邮件阅读程序不支持HTML格式时,可以采用其中的文本格式进行替代。 一封最复杂的电子邮件的基本情况为:含有邮件正文和邮件附件,邮件正文可以同时使用HTML格式和普通文本格式表示,并且HTML格式的正文中又引用了其他的内嵌资源。对于这种最复杂的电子邮件,可以采用如图3.6所示的MIME消息结构进行描述。 图3.6 从图3.6中可以看出,如果要在邮件中要添加附件,就必须将整封邮件的MIME类型定义为multipart/mixed;如果要在HTML格式的正文中引用内嵌资源,那就要定义multipart/related类型的MIME消息;如果普通文本内容与HTML文本内容共存,那就要定义multipart/alternative类型的MIME消息。 注意:如果整封邮件中只有普通文本内容与HTML文本内容,那么整封邮件的MIME类型则应定义为multipart/ alternative;如果整封邮件中包含有HTML文本内容和内嵌资源,但不包含附件,那么整封邮件的MIME类型则应定义为multipart/related。 在Content-type头字段中除了可以定义消息体的MIME类型外,还可以在MIME类型后面包含相应的属性,属性以“属性名=属性值”的形式出现,属性与MIME类型之间采用分号(;)分隔,如下所示: Content-Type:multipart/mixed;boundary="----=_NextPart_000_0050_01C" 常用的属性如表3.1所示。 表3.1 除了Content-Type头字段之外,MIME协议中还定义Content- Transfer-Encoding、Content-Disposition、Content-ID、Content-Location、Content-Base等几个重要的头字段,这几个头字段需要与Content-type头字段配合使用,它们的作用如下: — Content-Transfer-Encoding头字段 Content-Transfer-Encoding头字段用于指定MIME消息体中的内容所采用的邮件编码方式,详细细节请参看3.4节的讲解。 — Content-Disposition头字段 Content-Disposition头字段用于指定邮件阅读程序处理数据内容的方式,有inline和attachment两种标准方式,inline表示直接处理,而attachment表示当做附件处理。如果将Content-Disposition设置为attachment,在其后还可以指定filename属性,如下所示: 上面的MIME头字段表示MIME消息体的内容为邮件附件,附件名。 — Content-ID头字段 Content-ID头字段用于为“multipart/related”组合消息中的内嵌资源指定一个唯一标识号,在HTML格式的正文中可以使用这个唯一标识号来引用该内嵌资源。例如,假设将一个表示内嵌图片的MIME消息的Content-ID头字段设置为如下形式: Content-ID: it315logo_gif 那么,在HTML正文中就需要使用如下HTML语句来引用该图片资源: 注意,在引用Content-ID头字段标识的内嵌资源时,要在资源的唯一标识号前面加上“cid:”,以说明要采用唯一标识号对资源进行引用。 — Content-Location头字段 Content-Location头字段用于为内嵌资源设置一个URI地址,这个URI地址可以是绝对或相对的。当使用Content- Location头字段为一个内嵌资源指定一个URI地址后,在HTML格式的正文中也可以使用这个URI来引用该内嵌资源。例如,假设将一个表示内嵌图片的MIME消息的Content- Location头字段设置为如下形式: 那么,在HTML正文中就可以使用如下HTML语句来引用该图片资源: — Content-Base头字段 Content-Base头字段用于为内嵌资源设置一个基准路径,只有这样,Content-Location头字段中设置的URI才可以采用相对地址。例如,假设将一个表示内嵌图片的MIME消息的Content-Base和Content-Location头字段设置为如下形式: Content-Base: http://www.it315.org/images/ 那么,内嵌资源的完整路径就是Content-Base头字段设置的基准路径与Content-Location头字段设置的相对路径相加的结果,在HTML正文中就可以使用如下HTML语句来引用该图片资源: MIME邮件的编码方式 由于每个ASCII码字符只占用一个字节(8个bit位),且最高bit位总为0,即ASCII码字符中的有真正意义的信息只是后面的7个低bit位,而传统的SMTP协议又是基于ASCII码字符设计的,因此,一些基于传统SMTP协议设计的SMTP服务器在处理邮件内容时只取出每个字节中的7个低bit位进行处理,而将最高bit位忽略不计。显然,这样的SMTP服务器在处理包含有非ASCII码字符的邮件内容时,会出现严重的问题,这就了邮件中只能出现英文的ASCII码字符,而不能出现中文字符或二进制数据。 为了能够在邮件内容中包含中文、图像或声音等非ASCII字符的数据,人们想到了采用某种编码方式将非ASCII字符的数据转换成可打印的ASCII字符后再发送,邮件阅读程序则按照相应的解码方式从邮件中还原出原始数据即可,比较常用的两种邮件编码方式为BASE和Quoted-printable。后来的扩展SMTP协议允许直接在邮件中传递二进制数据,而不用对它们进行邮件编码,人们将这种没有进行邮件编码的二进制数据的邮件内容称为8bit编码,为了与此相区别,人们将没有进行邮件编码的纯ASCII码字符的邮件称为7bit编码。MIME消息体的邮件编码方式通过MIME消息头中的Content- Transfer- Encoding头字段指定,每种邮件编码方式的介绍如下: — 7Bit 指消息体内容全部是没有经过编码的ASCII字符。 — 8Bit 指消息体内容是没有经过编码的原始数据,且其中包含有非ASCII字符的数据。现在的邮件服务器基本上都支持8Bit编码,使用支持8Bit编码的邮件服务器可以简化邮件的处理过程。 — BASE Base是将二进制数据转换成可打印的ASCII字符的一种最常见的编码方式,它的基本原理是将一组连续的字节数据按6个bit位进行分组,然后对每组数据用一个ASCII字符来表示。6个bit位最多能表示26=个数值,因此可以使用个ASCII字符来对应这个数值,这个ASCII字符为: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567+/" 其中每个字符表示的数值就是该字符在上面的排列中的索引号,索引号从0开始编号。假设在内存中有如下三个连续的字节数据: [0110,0001] [0110,0010] [0110,0011] 将它们按6个bit位进行分组后的形式如下: [0110,00] [01,0110,] [0010,01] [10,0011] 分组后得到了四组数据,每组数据对应的十进制数值分别为24、22、9、35,它们分别对应Y、W、J、j这四个字符,所以,对[0110,0001] [0110,0010] [0110,0011]这三个字节的数据进行BASE编码后的结果是“YWJj”。 BASE编码要求把3个8位字节(即24个bit)的数据转化为4个6位字节(也是24个bit)的数据,如果原来的8位字节数据的字节个数不能被3整除,其余数只能是1或2,那么如何对余下的1个或2个8位字节数据进行处理呢?对于这种情况,仍然按6个bit位对剩余的字节进行分组,在最后不够6个bit位的内容后面添加几个为0的bit位来凑成6个bit位,例如,如果最后剩下的一个8位字节的内容如下: [0110,0001] 对它进行分组后的结果如下: [0110,00] [01,0000] 其中用黑斜体标识的0为填充的bit位,所以,最后剩下的这个字节的BASE编码结果为“YQ”。BASE编码还有规定,如果编码后的整个结果文本的字符个数不是4的整数倍,那么需要在最后填充“=”字符来凑成4的倍数,所以,在最后这个字节编码的结果后面还要添加两个“=”字符,即“YQ==”。显然,如果最后剩下两个8位字节的内容,它可以被编码成三个字符,最后还需要添加一个“=”字符。对一大段数据进行BASE编码时,可以在编码结果中的适当位置加入回车换行,MIME规范建议BASE编码结果中的每行最多76个字符。 — Quoted-printable Quoted-printable也是一种将二进制数据转换成可打印的ASCII字符的编码方式,它对ASCII字符不进行转换,只对非ASCII字符的数据进行编码转化。每个非ASCII字符的字节数据,都被转换成一个"="号后跟这个字节的十六进制数据,例如,“ab中国”的Quoted-printable编码结果为“ab=d6=d0=b9= fa”。显然,由于"="号在Quoted-printable编码中具有的特殊意义,所以,原始数据中的"="号字符也需要进行编码转换,用“=3d”表示。 对一大段数据进行Quoted-printable编码时,可以在编码结果中的适当位置加入回车换行,在回车换行前需要额外再加入一个“=”字符,以表示后面的换行是因编码而造成的软回车,而非原始数据中原有的回车换行。例如,对于下面一段Quoted-printable编码后的数据: =D5=E2=CA=C7=CD=A8=D0=C5=B5=C4=B3=CC=D0= =F2, =C7=EB=D6=B8=BD=CC! 在第一行末尾的“=”字符和换行,都是由于编码后生成的。 MIME实例分析 了解MIME协议的基本组织结构后,下面用Outlook Express撰写出一封显示效果如图3.4所示的电子邮件,然后分析该邮件的源文件,以便读者更加深入地了解MIME协议。 启动Outlook Express,单击工具栏中的“创建邮件”按钮,在打开的“新邮件”对话框中输入收件人地址、主题和邮件正文,然后选中邮件正文,单击编辑窗口的工具栏上的“》”按钮,在弹出的菜单栏中单击表示超链接的图标,如图3.7所示。 图3.7 在打开的“超级链接”对话框中输入如图3.8所示的内容,然后单击“OK”按纽。 图3.8 再次单击编辑窗口的工具栏上的“》”按钮,在打开的如图3.7所示的菜单栏中单击表示图片的图标,在打开的“图片”对话框中单击“浏览”按钮,然后通过打开的对话框选择一个图片文件,结果如图3.9所示。 单击图3.9中的“OK”按钮,结果如图3.10所示。 图3.9 图3.10 单击图3.10所示窗口中的“插入”à“文件附件”菜单项,在打开的“插入附件”对话框窗口中选择需要发送的附件,如图3.11所示。 单击“附件”按钮,插入所选中的附件。 单击图3.10所示窗口中的“文件”à“保存”菜单项,在Outlook Express主窗口的“草稿”目录中就可以看到这封邮件,如图3.12所示。 图3.11 图3.12 将这封邮件从“草稿”目录中移动到“发件箱”目录中,接着按照3.1节中讲解的查看邮件源文件步骤,打开刚才撰写的这封邮件的源文件。或者在“草稿”目录中选中这封邮件,将它另存为一个eml文件,再用任意一种文本编辑程序打开这个eml文件,这样也可以查看到刚才撰写的这封邮件的源文件。为了便于读者看清邮件内容的组织结构关系,笔者专门为此画了一个描述邮件中的各个MIME消息分隔符的层次关系的示意图,如图3.13所示。 从图3.13中可以看出,在MIME组合消息的消息体中的每个消息单元都要以一个分割符开始,在组合消息的消息体结束时还需要用一个结束分割符。MIME消息中的分隔符的层次关系与一篇文章中的标题之间的层次关系非常相似,只是在每个组合消息结束时还要增加一个结束“标题”。为了便于讲解,笔者对刚才撰写的邮件源文件内容进行了适当修改,并添加了相应的注释,如下所示: 图3.13 1:From: "it315" 2:To: 3:Subject: =?gb2312?B?TUlNRdCt0unLtcP308q8/g==?= 4:Date: Thu, 1 Dec 2005 20:46:53 +0800 5:MIME-Version: 1.0 6:Content-Type: multipart/mixed;//定义邮件体类型为mixed 7: boundary="----=_NextPart_000_0050_01C"//定义整个邮件内容的分隔符 8:X-Priority: 3 9:X-MSMail-Priority: Normal 10:X-Mailer: Microsoft Outlook Express 6.00.2900.2670 11:X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2670 12: 13:This is a multi-part message in MIME format.//邮件注释 14: //整个邮件内容的第一部分(即邮件正文)的开始标记 15:------=_NextPart_000_0050_01C 16:Content-Type: multipart/related; 17: type="multipart/alternative"; 18: boundary="----=_NextPart_001_0051_01C"//定义邮件正文内部的分隔符 19: //邮件正文内部的第一部分的开始标记 20:------=_NextPart_001_0051_01C 21:Content-Type: multipart/alternative; //定义邮件正文内部的第一部分的内部分隔符 22: boundary="----=_NextPart_002_0052_01C" 23: //邮件正文内部的第一部分的第一部分的开始标记 24:------=_NextPart_002_0052_01C 25:Content-Type: text/plain; 26: charset="gb2312" 27:Content-Transfer-Encoding: base 28: 29:u7bTrbTzvNK3w87KztLDx7XEzfjVvg0KDQog //经BASE编码后的文本格式的邮件正文 30: //邮件正文内部的第一部分的第二部分的开始标记 31:------=_NextPart_002_0052_01C 32:Content-Type: text/html; 33: charset="gb2312" 34:Content-Transfer-Encoding: base 35: 36:PCFET0NUWVBFIEhUTUwgUFVC......//经BASE编码后的HTML格式的邮件正文 37: //邮件正文内部的第一部分的的结束标记 38:------=_NextPart_002_0052_01C-- 39: //邮件正文内部的第二部分(HTML中的内嵌资源)的开始标记 40:------=_NextPart_001_0051_01C 41:Content-Type: image/gif; 42: 43:Content-Transfer-Encoding: base 44:Content-ID: <004f01c5f675$4e210300$b501a8c0@zxx> 45: 46:R0lGODlh/ABGAOYAADdhmaekZjxsq0N4vHik 47:xK27z9CtCJWpxCZEa3aczejr7...... // HTML中内嵌的经BASE编码后的图片数据 48: //邮件正文的结束标记 49:------=_NextPart_001_0051_01C— 50: //整个邮件内容的第二部分(第一个附件)的开始标记 51:------=_NextPart_000_0050_01C 52:Content-Type: application/x-msdownload; 53: name="daemon.exe" 54:Content-Transfer-Encoding: base 55:Content-Disposition: attachment;//声明内容类型为附件 56: filename="daemon.exe" 57: 58:TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA 59:AAAA0AAAAA4fug4AtAnNAAAAAA......// 第一个邮件附件内容 60: //整个邮件内容的第三部分(第二个附件)的开始标记 61:------=_NextPart_000_0050_01C 62:Content-Type: audio/wav; 63: name="sndrec.wav" :Content-Transfer-Encoding: base 65:Content-Disposition: attachment;//声明内容类型为附件 66: filename="sndrec.wav" 67: 68:UklGRjIAAABXQVZFZm10IBIAAAABAAEAIlYAACJWAAABAAgAKABmYWN0 69:AA==AAAAAAAAAAAAAAAAAACCC......//第二个邮件附件内容 70: //整个邮件内容的结束标记 71:------=_NextPart_000_0050_01C5F6B8.5C492500-- 源文件中第1~11行为邮件的邮件头,第15~71行为邮件体。邮件体中的15~49行为邮件正文,第51-59行为邮件的第一个附件,第61~71行为第二个附件。读者只要对照图3.13来阅读,就很容易看出这个邮件源文件内容的组织结构。关于上述的邮件源文件内容,还需要做出如下三点补充解释: (1)每个MIME组合消息的Content-type头字段中的boundary属性用于定义其中嵌套的各个MIME消息之间的分隔符,如源文件中的第7行、第18行和第22行等。在各个MIME组合消息内部的起始分隔符是在该MIME组合消息的Content-type头字段中的boundary属性值前面增加了两个减号(-)字符而形成的,如源文件中的第15行、第20行和第24行等;而每个MIME组合消息的结束分隔符则是在其起始分隔符的后面又添加两个减号(-)字符而形成的,如源文件中的第38行、第49行和第71行。 (2)在每个MIME组合消息的消息体之前(即第一个开始分隔符之前),可以有一些附加的文本行,这些文本行相当于MIME消息的注释,在解码时将被忽略,如源文件的13行。 (3)源文件中的第44行使用Content-ID头字段为内嵌的图片资源指定了一个唯一标识号,在HTML格式的正文中需要使用这个唯一标识号来引用相应的内嵌资源,其引用语句为004f01c5f675$4e210300$b501a8c0@zxx">,但是由于整个HTML正文部分采用了Base编码,所以在源文件的HTML正文部分无法看到原始的引用语句。 多学两招 邮件传播病毒的原理 MIME协议其实就是一种邮件内容的组织协议,支持MIME协议的邮件阅读程序将根据MIME消息头中定义的MIME类型,调用相应的解析程序来处理消息体中的数据。例如MIME消息头中定义为邮件附件时,邮件阅读程序会提示用户保存消息体中的数据,如果定义为图像文件时,邮件阅读程序则把消息体中的数据作为一个图像文件自动打开。 由于邮件数据通常是经过BASE编码后的ASCII码数据,邮件阅读程序只能通过分析数据的MIME消息头来获知数据类型,无法通过分析数据本身来获知数据的类型,因此,一些病毒制造者就可以把病毒程序进行BASE编码后,再附加在邮件的MIME消息体中,然后在MIME消息头中将其MIME类型定义为图片或声音等类型,而文件名的扩展名却为.exe。这样,当邮件阅读程序解码带有病毒程序的MIME消息体后,将执行解码后得到的病毒程序。前些年曾经在全球范围内流行的Nimda病毒,就是通过这种方式进行传播的,其示意源代码如下: MIME-Version: 1.0 Content-Type: multipart/related;//声明所包含内容为内嵌资源 type="multipart/alternative"; boundary="====_ABC12345670DEF_====" --====_ABC12345670DEF_==== Content-Type: multipart/alternative; boundary="====_ABC0987654321DEF_====" --====_ABC0987654321DEF_==== Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: 7bit --====_ABC0987654321DEF_====-- --====_ABC12345670DEF_==== Content-Type: audio/x-wav; name="readme.exe" //把exe文件定义成一个wav文件 Content-Transfer-Encoding: base Content-ID: TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAA……//经BASE编码后的病毒源代码 --====_ABC12345670DEF_==== 这封邮件传播病毒的运行原理非常简单,如上面的源文件中的黑体字部分及相应注释所示,它就是将一个可执行文件(readme.exe)作为一个audio/x-wav类型的声音文件嵌入到HTML页面中。由于最初的Outlook Express程序没有检查MIME消息的Content-Type头字段定义的MIME类型与其中的name属性指定的文件扩展名是否匹配,于是导致用户打开邮件时将执行解码后的readme.exe程序,从而感染上了病毒。这个病毒程序又利用Outlook Express中的地址簿向别人发送带毒的邮件,这样一传十,十传百,Nimda蠕虫就大行其道了。下载本文
2: 其他头字段主 类 型 属 性 名 说 明 text charset 用于说明文本内容的字符集编码 image name 用于说明图片文件的文件名 application name 用于说明应用程序的文件名 multipart boundary 用于定义MIME消息之间的分隔符