RFC6120
(→重启) |
(→机制偏好) |
||
第1,911行: | 第1,911行: | ||
在SASL协商之后, 双方必须重启该流. | 在SASL协商之后, 双方必须重启该流. | ||
− | ==== | + | ====机制推荐==== |
+ | |||
+ | 任何将要扮演SASL客户端或SASL服务器的实体必须对于该客户端或该服务器维护一个它推荐的SASL机制的有序列表, 这个列表的顺序是根据本地策略或用户配置来的(它的顺序应该是根据验证能力越强排在越靠前). 初始化实体必须独立于接收方实体的推荐顺序来维护它自己的推荐顺序. 客户端必须以它自己的推荐顺序来尝试SASL机制. 例如, 如果服务器提供的顺序列表是"PLAIN SCRAM-SHA-1 GSSAPI" 或 "SCRAM-SHA-1 GSSAPI PLAIN" 而客户端的顺序列表是 "GSSAPI SCRAM-SHA-1", 客户端必须首先尝试 GSSAPI 然后尝试 SCRAM-SHA-1 而不能(MUST NOT)尝试 PLAIN (因为 PLAIN 不在它的列表中). | ||
+ | |||
====机制提供==== | ====机制提供==== | ||
====数据格式==== | ====数据格式==== |
2012年3月9日 (五) 05:34的版本
"本文的英文原文来自RFC 6120
互联网工程任务组(IETF) | P. Saint-Andre |
申请讨论: 6120 | Cisco |
取代: 3920 | 2011年3月 |
类别: 标准跟踪 | |
ISSN: 2070-1721 |
- 可扩展的消息和出席信息协议 (XMPP): 核心协议
摘要
- 可扩展的消息和出席信息协议(XMPP)是一个XML应用,让任何两个或多个网络实体之间进行结构化和可扩展的准实时信息交流. 本文定义了XMPP的核心协议方法: XML流的配置和解除, 通道加密, 验证, 错误处理, 以及消息通讯基础, 网络可用性 ("presence"), 和 请求-应答 交互. 本文取代了 RFC 3920.
本文的状态
- 这是一个互联网标准跟踪文档.
- 本文是互联网工程工作组(IETF)的一个成果. 它代表了IETF社区的一致意见. 它已经公开审核并由互联网工程控制组(IESG)批准发布了. 更多关于互联网标准的信息请参见RFC 5741第2章.
- 关于本文当前状态的信息, 任何错误, 以及如何对它提出反馈,请到 http://www.rfc-editor.org/info/rfc6120 .
版权通知
- Copyright (c) 2011 IETF Trust and the persons identified as the document authors. All rights reserved.
- This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.
序论
概述
可扩展的消息和出席信息协议(XMPP)是一个可扩展标记语言XML应用,让任何两个或多个网络实体之间进行结构化和可扩展的准实时信息交流. 本文定义了XMPP的核心协议方法: XML流的配置和解除, 通道加密, 验证, 错误处理, 以及消息通讯基础, 网络可用性 ("presence"), 和 请求-应答 交互.
历史
XMPP的基本语法和语义最开始是由Jabber开源社区开发的, 主要是在1999年. 2002年, 根据 IMP‑REQS ,XMPP工作组被允许基于Jabber协议开发一个适合IETF的即时消息和出席信息技术. 到了2004年10月, 发布了 RFC3920 和 RFC3921 , 意味着那时候XMPP的主要定义完成了.
从2004年开始,互联网社区已经获得了广泛的XMPP实现和布署经验, 包括XMPP标准基金会(XSF)主持下开展的正式的互操作性测试. 本文全面整合了从软件开发者和XMPP服务提供者得到的反馈, 包含了一系列向后兼容的修改,见 附录D . 结果是, 本文反映了互联网社区对于XMPP1.0核心功能的初步共识, 因此废止了RFC 3920.
功能汇总
这个不规范的章节提供了一个方便开发者的XMPP功能汇总; 接下来的其他章节则是XMPP的规范定义.
XMPP的目标是允许两个(或多个)实体通过网络来交换相关的小件结构化数据(所谓"XML节"). XMPP典型地使用分布式的 客户端-服务器 体系结构来实现, 这里客户端需要连接到一个服务器以获得对网络的访问,从而被允许和其他实体(可能在其他服务器上)交换XML节. 一个客户端连接到一个服务器,交换XML节,以及结束连接,这样的流程如下:
- 确定要连接的IP地址和端口号, 典型的做法是对一个合格的域名做出解析( 3.2 )
- 打开一个传输控制协议 TCP 连接
- 通过TCP打开一个XML流 4.2
- 握手最好使用传输层安全性 TLS 来进行通道加密( 5 )
- 使用简单验证和安全层 SASL 机制来验证 ( 6 )
- 绑定一个资源到这个流上 ( 7 )
- 和其他网络上的实体交换不限数量的XML节( 8 )
- 关闭XML流 ( 4.4 )
- 关闭TCP连接
在XMPP中, 一个服务器可以选择性地连接到另一个服务器以激活域间或服务器间的通讯. 这种情形下, 两个服务器需要在他们自身之间建立一个连接然后交换XML节; 这个过程所做的事情如下:
- 确定要连接的IP地址和端口号, 典型的做法是对一个合格的域名做出解析( 3.2 )
- 打开一个TCP连接
- 打开一个XML流 4.2
- 握手最好使用TLS来进行通道加密( 5 )
- 使用简单验证和安全层 SASL 机制来验证 ( 6 ) *
- 交换不限数量的XML节,可以服务器之间直接交换,也可以代表每台服务器上的相关实体来交换,例如那些连到服务器上的客户端 ( 8 )
- 关闭XML流 ( 4.4 )
- 关闭TCP连接
- 互操作性提示: 在本文写就的时候, 大多数已布署的服务器仍使用服务器回拨协议 XEP‑0220 来提供弱身份验证,而不是使用SASL的 PKIX证书来提供强验证, 特别在这些情况下,SASL握手无论如何将不会得到强验证 (例如, 因为TLS握手没有被对方服务器强制要求, 或因为当TLS握手时对方服务器提供的PKIX证书是自签名的并且之前没有被接受过); 细节请见 XEP‑0220 . 本文的解决方案显然提供了一个更高级别的安全性 (参见 13.6 ).
本文指定了客户端如何连接到服务器以及基本的XML节语义. 然而, 本文不定义一个连接成功建立之后可能用来交换的XML节的"载荷"; 反之, 那些载荷被定义在各种XMPP扩展之中. 例如, XMPP‑IM 定义了基本的即时消息和出席信息功能的扩展. 另外, XSF创造了各种扩展协议,即XEP系列 XEP‑0001 ,也为广泛的应用程序定义了扩展.
术语
本文中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", 和 "OPTIONAL" 的解释参见RFC 2119 关键字 .
特定的安全相关的术语的含义参见 安全术语 ; 这些术语包括但不限于, "assurance", "attack", "authentication", "authorization", "certificate", "certification authority", "certification path", "confidentiality", "credential", "downgrade", "encryption", "hash value", "identity", "integrity", "signature", "self-signed certificate", "sign", "spoof", "tamper", "trust", "trust anchor", "validate", and "verify".
特定的和证书,域名,应用服务身份相关的术语参见 TLS‑证书 ; 这包括但不限于, "PKIX certificate", "source domain", "derived domain", 以及身份类型 "CN-ID", "DNS-ID", 和 "SRV-ID".
其他安全相关的术语定义于参考协议中 (例如, "denial of service" (拒绝服务)定义于 DOS 或 "end entity certificate" (终端实体证书)定义于 PKIX ).
术语 "whitespace" (空格) 用于指代 XML 中任何匹配"S"的字符或字符串, 也就是说, 一个或多个满足 ABNF 定义的SP, HTAB, CR, 或 LF 规则的实例.
术语 "localpart" (本地部分), "domainpart" (域部分), 以及 "resourcepart" (资源部分)定义于 XMPP地址 .
术语 "bare JID" (纯JID) 指代一个格式为 <localpart@domainpart> (对于一个位于某个服务器上的帐户而言) 或 <domainpart> (对于一个服务器而言) 的XMPP地址.
术语 "full JID" (全JID) 指代一个格式为 <localpart@domainpart/resourcepart> (对一个典型的已授权客户端或和某个帐号相关的设备而言) 或 <domainpart/resourcepart> (对于一个典型的资源或和某个服务器相关的文字)的XMPP地址.
术语 "XML stream" (也称为 "stream" (流)) 定义于 4.1 .
术语 "XML stanza" (也称为 "stanza" (节)) 定义于 4.1 . 有三种 stanzas(节): message, presence, 和 IQ ("Info/Query"的简称). 这些通讯原语分别定义于 8.2.1 , 8.2.2 , 和 8.2.3 .
术语 "originating entity" (原实体)指的是第一次生成一个发送到XMPP网络的stanza(节)的实体(例如, 一个已连接的客户端, 一个附加的服务, 或一个服务器). 术语 "generated stanza" (生成的节)值的是生成的节那个节.
术语 "input stream" (输入流)指定这样一个XML流,服务器通过这个流从一个已连接的客户端或远端服务器接收数据, 而术语 "output stream" (输出流)指定这样一个流,服务器通过这个流发送数据到一个已连接的客户端或远程服务器. 以下术语指定一些动作,处理从输入流收到的数据时服务器可以执行这些动作:
route(路由): 传递数据到一个远端服务器让它自行处理或最终递送到一个和远端服务器关联的客户端 deliver(递送): 传递数据到一个已连接的客户端 ignore(忽略): 丢弃数据不做任何处理或返回一个错误给发送者sender
当术语 "ignore" (忽略)用于客户端处理收到的数据时, 短语 "without acting upon it" (不做任何处理)明确的包括不展示任何数据给使用者(人).
接下来的 "XML符号" 被 IRI 用于展示无法用仅用ASCII码呈现的字符, 本文的一些例子使用了类似 "&#x...." 的格式来表现 UNICODE 字符串 (例如, 字符串 "ř" 表示Unicode字符 LATIN SMALL LETTER R WITH CARON); 这种形式是绝对不会在XMPP系统将通过网络发送的.
和 URI 展现统一资源定位符的规则一样, XMPP地址文本也是用 '<' 和 '>' 括起来的(尽管基本上它们不属于 URIs).
例如, 被括起来的行是用来提高可读性的, "[...]" 表示省略, 并且还是用了以下预定义字符串 (这些预定义的字符串不会通过网络发送出去):
- C: = 客户端
- E: = 任何XMPP实体
- I: = 发起实体
- P: = 对端服务器
- R: = 接收实体
- S: = 服务器
- S1: = 服务器1
- S2: = 服务器2
读者需要注意这些例子不包括细节, 并且例子里的一些协议流程中, 展示的备用步骤不一定是由前一个步骤发送的确切的数据触发的; 本文或常用参考文档中的协议规范所用到的所有用例里面提供的例子都遵从上述规则. 所有例子都是虚构的并且交换的信息 (例如, 用户名和密码) 不代表任何现存的用户和服务器.
体系结构
XMPP提供一种异步的端到端的结构化数据交换技术,在一个分布式的可全球寻址和出席信息感知的客户端和服务器的网络中使用直接的持久XML流。这种体系结构形式包含了普遍的网络可用性的知识,以及在给定的客户端-服务器和服务器-服务器会话的时候,不限数量的并发信息交易的概念,所以我们把它称为 "并发交易可用性" ("Availability for Concurrent Transactions") (简称ACT) 来把它和来自WWW的 "Representational State Transfer" REST 体系结构形式区别开. 尽管XMPP的体系结构很大程度上类似于 email (参见 EMAIL‑ARCH, 它引入了一些变化以便于准实时通讯. ACT体系结构形式的独特特性如下.
全局地址
和email一样, 为了通过网络路由和递送消息,XMPP使用全球唯一地址(基于DNS). 所有XMPP实体可以在网络上被寻址, 大部分客户端和服务器以及很多外部服务可以被客户端和服务器访问. 通常, 服务器地址的格式为 <域部分> (例如, <im.example.com>), 属于某台服务器的帐号的格式为 <本地部分@域部分> (例如, <juliet@im.example.com>, 称为 "纯JID"), 而连接到一个特定的设备或资源并且已经被(服务器)授权可以和外部交互的客户端的格式为 <本地部分@域部分/资源部分> (例如, <juliet@im.example.com/balcony>, 称为 "全JID"). 因为历史原因, XMPP地址常被称为Jabber IDs 或 JIDs. 因为XMPP地址格式的正式规范依赖于国际化技术(本文撰写时正在制定中),这个格式定义于 XMPP‑ADDR 而非本文之中. 术语 "localpart"(本地部分), "domainpart"(域部分), 和 "resourcepart"(资源部分) 正式定义于 XMPP‑ADDR .
出席信息
XMPP让一个实体能够向其他实体声明它的网络可用性或者 "presence"(出席信息) . 在XMPP中, 这种可通讯状态是用端到端的专用通讯元素来标识的: 即 <presence/> 节. 尽管网络可用性对于XMPP消息交换并不是必需的, 它还是可以促进实时交互,因为消息发起者可以在发消息之前知道接收者在线并处于可通讯状态. 端到端的出席信息定义于 XMPP‑IM .
持久流
每个点对点的一"跳"都建立了基于TCP长连接的持久XML流来保持可通讯状态. 这些 "always-on" 客户端-服务器 和 服务器-服务器 流使得任何时间每方都能够推送数据到另一方并且立即路由和递送. XML流定义于 4 .
结构化数据
XMPP中基本的协议数据单元不是一个XML流 (它只是为点对点通讯提供传输层) 而是一个 XML 节("stanza"), 它是一个通过流发送的XML片段. 一个节的根元素包括路由属性 (类似 "from" 和 "to" 地址), 而节的子元素包含了递送给目标接收者的载荷. XML节定义于 8 .
客户端和服务器的分布式网络
在实践之中, XMPP是一个包含了很多互相通讯的客户端和服务器的网络(当然, 任何两个给定的布署服务器之间的通讯都是严格谨慎的并且也和本地服务策略有关). 因此, 例如, 与服务器 <im.example.com> 关联的用户 <juliet@im.example.com> 能够和服务器 <example.net> 关联的用户 <romeo@example.net> 交换消息,出席信息和其他结构化数据. 这个模式对使用全局地址的消息协议是很常见的, 例如email网络 (见 SMTP 和 EMAIL‑ARCH . 结果, 在XMPP中端到端的通讯是逻辑上的点对点,而物理结构则是 客户端-服务器-服务器-客户端, 如下图所示.
图1: 分布式客户端-服务器 体系结构
example.net <--------------> im.example.com ^ ^ | | v v romeo@example.net juliet@im.example.com
- 提示性备注: 体系结构使用 XML流 和 XML节 ,但是两个客户端之间直接建立端到端的连接则使用基于 LINKLOCAL 的技术, 不过那个体系结构没有定义在本协议之中,它只能说是 "类XMPP"; 详见 XEP‑0174 . 另外, XML流可以在任何可靠的传输层上建立端到端的连接, 包括XMPP本身的扩展; 无论如何, 这些方法没有包含在本文之中.
以下段落描述客户端和服务器们各自在网络中负责什么.
一个客户端就是一个实体,它先和它的注册帐号所在服务器建立XML流 (通过 SASL握手 ) , 然后完成 资源绑定 , 这样就能通过建好的流在客户端和服务器之间递送XML节. 客户端使用 XMPP 来和它的服务器, 其他客户端以及任何其他网络上的实体通讯, 这里服务器负责递送节到同一台服务器上其他已连接的客户端,或把它们路由到远程服务器上. 一个服务器上的注册帐号可以同时使用多个客户端连接到一台服务器上, 这里每个客户端的XMPP地址的 资源部分 是不同的 (例如, <juliet@im.example.com/balcony> 和 <juliet@im.example.com/chamber>), 定义于 XMPP‑ADDR 和 7
一个服务器是一个实体,主要负责以下事项:
取决于服务器的不同, 一个XMPP服务器的次要责任可能包括:
TCP绑定
范围
如本文定义的XMPP所述, 一个发起方实体在 (客户端或服务器) 和接收方实体协商XML流之前必须(MUST) 打开一个到接收方实体 (服务器) 的 TCP 连接. 然后在使用XML流期间双方一直保持那个TCP连接. 这个规则在下面章节中的TCP绑定要用到.
- 提示性备注: 不一定要把XML流建立在TCP上, 其他传输协议也是可以的. 例如, 两个实体可以通过 HTTP 互相连接(定义于 XEP‑0124 和 XEP‑0206 . 无论如何, 本协议只定义把XMPP绑定到TCP.
合格的全域名解析
因为XML流是是通过TCP发送的, 发起方实体在尝试打开一个XML流之前需要确定接收方实体的IPv4或IPv6地址(以及端口). 一般来说这是通过解析接收方实体的合格的全域名(简称FQDN,参见 DNS概念 )来实现的.
首选流程:SRV查询
FQDN解析的首选流程是如下使用 DNS‑SRV 记录:
- 发起方实体构造一个 DNS SRV 查询,参数如下:
- 一个 "xmpp-client" (用于 客户端-服务器 连接) 或 "xmpp-server" (用于 服务器-服务器 连接)服务
- 一个 "tcp" 协议
- 一个对应发起方实体希望连接的XMPP服务的 "原有域"( TLS‑CERTS )的名字 (例如, "example.net" 或 "im.example.com")
- 得到一个类似 "_xmpp-client._tcp.example.net." 或 "_xmpp-server._tcp.im.example.com." 的查询.
- 如果收到应答, 它将包含一个或多个FDQN和端口的组合, 每个都拥有权重和优先级 (如 DNS‑SRV 所述). (无论如何, 如果SRV查询的结果是一个单独的资源记录 ".", 即根域名, 那么发起方实体必须(MUST)在这时终止SRV处理,因为根据 DNS‑SRV ,这样一个结果意味着那个服务在本域中是不可用的)
- 发起方实体至少选择返回的FQDNs记录中的一个来解决 (根据 DNS‑SRV 规则,对FDQN执行 DNS "A" 或 "AAAA" 查询; 这将返回一个 IPv4 或 IPv6 的地址.
- 成功解析FDQN(包括SRV查询返回的相应的端口号)之后,发起方实体就使用IP地址来连接接收方实体.
- 如果发起方实体使用那个IP地址连接失败,而的 "A" 或 "AAAA" 记录查询返回了不止一个IP地址, 那么发起方实体使用那个FDQN的下一个解析好的IP地址作为连接地址.
- 如果发起方实体用给定的FDQN的所有解析出来的IP地址都无法连接, 那么它重复解析过程并使用基于优先级和权重的SRV查询(定义于 DNS SRV )返回的下一个FQDN来连接.
- 如果发起方实体接收到它的SRV应答但是无法使用接收到的应答数据来建立一个XMPP连接, 它不应该(SHOULD NOT)尝试下面描述的备用流程(这有助于防止入站和出站连接状态不匹配).
- 如果发起方实体不能从它的SRV查询接收到应答, 它应该(SHOULD)尝试下一节描述的备用流程.
后备流程
后备流程应该(SHOULD)是一个常规的 "A" 或 "AAAA" 地址记录解析以决定原始域的IPv4或IPv6地址, 而端口则为 "xmpp-client" 端口(5222)用于客户端-服务器连接或 "xmpp-server" 端口(5269)用于服务器-服务器连接 (这些是在IANA注册的缺省端口,14.7 .
如果通过TCP连接不成功, 发起方实体可能尝试找到并使用替代连接方法例如HTTP绑定 (见 XEP‑0124 和 XEP‑0206 , 它可能使用 DNS‑TXT 记录(参见 XEP‑0156) 来搜索.
什么时候不用SRV
如果发起方实体已经被显式地配置为一个关联到接收实体的原始域的一个特定的FQDN (以及潜在的端口) (比如,一个特定的原始域 example.net "写死" 到一个配置好的 apps.example.com 的 FQDN), 鼓励初始方实体使用配置好的名字而不是建议的对原始域的SRV解析流程.
附加服务使用SRV记录
很多XMPP服务器以可托管附加服务 (超出本文和 XMPP-IM 定义范围) 这种方式来实现,附加服务的DNS域名典型的形式是主XMPP服务的 "子域名" (例如, conference.example.net 用于 XEP‑0045 服务,相关的XMPP主服务为 example.net ) 或 底层服务的一级域名的 "子域名" (例如, muc.example.com 用于 XEP‑0045 服务,相关的XMPP主服务为 im.example.com ). 如果一个和远程XMPP服务关联的实体希望连接到这样一个附加服务上, 它将生成一个适当的XML节,而远程服务器将尝试通过一个SRV查询资源记录类似 "_xmpp-server._tcp.conference.example.net." 或 "_xmpp-server._tcp.muc.example.com." 来解析该附加服务的DNS域名. 所以, 如果一个XMPP服务的管理员希望让远程服务器相关的实体能访问这样的附加服务, 除了用于他们的主XMPP服务的 "_xmpp-server" 记录之外, 他们还需要声明适当的 "_xmpp-server" SRV 记录. 当 SRV 记录不可用的时候, 可使用后备方法 3.2.2 来为附加服务解析域名.
重连
XMPP服务器可能在会向连接的客户端和远端服务器提供TCP连接服务的时候意外掉线. 因为这些连接的数量可能非常大, 实体的重连机制寻求解决重连可能导致的对软件性能的冲剂和网络堵塞. 如果实体选择重连, 它:
- 应该把重连之前等待的秒数设置为0到60之间 (这有助于确保不会所有实体在掉线的同一个时间间隔之后同时尝试重连).
- 如果第一次尝试重连没有成功则随后的尝试重连的时间间隔应该越来越长 (例如, 按照 ETHERNET 描述的 "动态二进制指数后退算法" ).
建议重连的时候使用TLS会话恢复 TLS‑RESUME . 本文未来的某个版本, 或某个独立的协议, 可能会提供更多详细的关于加速重连过程的方法的指南.
可靠性
在XMPP使用常连的TCP连接意味着通过XML流发送的XML节可能不可靠, 因为长连的TCP的各方可能无法及时地了解掉线情况. 在XMPP应用层, 长连接掉线可能导致无法发送节. 尽管本文定义的核心XMPP技术未包括克服这一可靠性缺陷的特性, 有一个XMPP扩展在做这件事 (例如, XEP‑0198 ).
XML流
流基础
两个基本概念,使得XMPP实体之间的小的结构化信息有效载荷能快速地进行异步交换:XML流和XML节。这些术语的定义如下。
- XML流的定义:
- XML流是一个容器,用于任何两个实体通过网络进行XML元素的交换. XML流的开始明确表达为一个打开的 "流头" (即, 一个包含了适当树形和命名空间声明的 XML <stream> 标签), 而这个XML流的结尾明确表达为一个关闭的XML </stream> 标签. 在流的生存期间, 发起方实体可以通过这个流发送不限数量的XML元素, 这些元素或用来协商这个流 (例如, 完成 TLS协商 或 SASL协商 ) 或用于 XML节. "发起流" 是从发起方实体 (通常是一个客户端或服务器) 到接收方实体 (通常是一个服务器), 也可视为对应发起方 "连接到" 或 "和......开启会话" 接收方实体. 发起流允许从发起方实体到接收方实体的单向通讯; 为了让接收方实体能够向发起方实体发送节, 接收方实体必须(MUST) 协商一个相反的流 ("应答流").
- XML节的定义:
- XML节是一个XMPP中的基本语义单位. 一个节就是一个第一层元素 (在流的深度=1),它的元素名是 "message", "presence", 或 "iq" ,而它的合格命名空间是 'jabber:client' 或 'jabber:server'. 相比之下, 任何其他命名空间限定的第一层元素都不是一个XML节 (stream errors, stream features, TLS相关的元素, SASL相关的元素, 等等.), 由'jabber:client' 或 'jabber:server' 命名空间限定的 <message/>, <presence/>, 或 <iq/> 元素但不在第一层 (例如, 包含在一个扩展元素中的 <message/> 元素 ( 做报告用的 8.4 )也不是一个XML节, 不是命名空间 'jabber:client' 或 'jabber:server'限定的 <message/>, <presence/>, 或 <iq/> 元素也不是一个XML节. 一个XML节典型的包含一个或多个必要的子元素 (以及相关的属性, 元素, 和 XML 字符串数据) 来传达所需的信息, 子元素可以(MAY)使用任何XML命名空间 (见 XML‑NAMES 和本协议的 8.4).
有三种节: message, presence, 和 IQ ("Info/Query"的缩写). 这些节类型提供三种不同的通讯原语: 一个 "推送" 机制用于已生成的消息, 一个特定的 "发行-订阅" 机制用于广播网络可用性信息, 和一个 "请求-应答" 机制用于更结构化的数据交换 (类似 HTTP . 更多解释分别位于 8.2.1 , 8.2.2 , 和 8.2.3 .
考虑一个客户端连接到一个服务器的例子. 客户端通过发送一个流头来发起一个XML流到服务器, 最好在前面加上一个XML声明来指定XML版本和支持的字符串编码 (见 11.5 和 11.6 ). 遵循本地策略和服务设置, 该服务器接着以第二个XML流应答回客户端, 最好再次在前面加上一个XML声明. 一旦客户端完成 SASL协商 和 资源绑定 , 该客户端就能通过这个流来发送不限数量的XML节. 当客户端想要关闭这个流的时候, 它只要简单的发送一个关闭 </stream> 标签给服务器,如 4.4 .
于是, 从本质上讲, 一个XML流作为会话期间发送的XML节的信封, 而另一个XML流作为会话期间接收的XML节的信封. 我们可以用如下的简化模型做一个展示.
+--------------------+--------------------+ | INITIAL STREAM | RESPONSE STREAM | +--------------------+--------------------+ | <stream> | | |--------------------|--------------------| | | <stream> | |--------------------|--------------------| | <presence> | | | <show/> | | | </presence> | | |--------------------|--------------------| | <message to='foo'> | | | <body/> | | | </message> | | |--------------------|--------------------| | <iq to='bar' | | | type='get'> | | | <query/> | | | </iq> | | |--------------------|--------------------| | | <iq from='bar' | | | type='result'> | | | <query/> | | | </iq> | |--------------------|--------------------| | [ ... ] | | |--------------------|--------------------| | | [ ... ] | |--------------------|--------------------| | </stream> | | |--------------------|--------------------| | | </stream> | +--------------------+--------------------+
那些习惯于以文档为中心的方式看待XML的人可能会发现下面的类比是有益的:
无论如何, 这些描述只是类比, 因为XMPP不处理文档和片段而是处理流和节.
本节的其余部分定义XML流(连同相关主题)的以下几个方面:
打开流
连接到接收方实体的适当的IP地址和端口之后, 发起方实体通过发送一个流头 ("发起流头") 来打开到接收方实体的流.
I: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
然后接收方实体通过发送一个它自己的流头 ("应答流头") 来回复发起方实体.
R: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
接着实体们就可以再进行流协商过程的剩余步骤了.
流协商
基本概念
因为流的接收方实体可以说是它所服务的域的看门人, 它对客户端或对端服务器的发起的连接有一定的条件要求. 最低程度, 发起方实体在接收方实体被允许发送节之前需要验证接收方实体, (对客户-服务器流来说这意味着使用 6 中描述的SASL). 无论如何, 接收方实体可以考虑其他验证条件来强制协商, 例如使用 5 描述的TLS加密. 接收方实体通过交流"stream features"以通知发起方实体这些条件: 发起方需要在接收方接受它发送的XML节之前完成的一系列特别的协议交互, 以及任何自愿协商但是可以提高XML流处理的协议交互 (例如, XEP‑0138 描述的建立应用层压缩).
连接条件的存在意味着流需要协商. 层的顺序 (TCP, 然后是TLS, 然后是SASL, 然后是XMPP. 这些顺序如13.3描述) 意味着流协商是一个多阶段的过程. 进一步的结构由两个因素来施加: (1) 一个给定的流特性可以仅对特定的实体提供,或只在特定的其他特性已经被协商之后提供 (例如, 资源绑定仅在SASL验证之后提供), 和 (2) 流特性可能是强制协商也可能是自愿协商. 最后, 基于安全的原因一个流的参与者们在成功地完成用于特定特性的协议交互之后需要丢弃它们在协商过程中获得的知识 (例如, 在所有情况下的TLS和当可能建立一个安全层的情况下的SASL, 如有关SASL机制的规范所述). 通过刷新旧的流上下文和在现有的TCP连接上交换新的流头就可以做到这一点.
流特性格式
如果发起方实体包含在发起流头里的 'version' 属性值设为不低于 "1.0" (见 4.7.5 ), 接收方实体在发送应答流头之后必须发送一个<features/> 子元素 (通常使用 4.8.5 描述的流命名空间前缀作为前缀) 给发起方实体以声明使流协商过程继续下去的任何条件. 每个条件用 <features/> 元素的子元素的格式, 由一个不同于流命名空间和内容命名空间的命名空间来限定. <features/> 元素可以包含一个子元素,多个子元素,或者为空.
- 实现备注: 包含在任何给定的<features/>元素内的子元素的顺序不重要.
如果一个特殊的流特性是或者可以是强制协商的, 那个特性的定义需要做以下几件事之一:
- 声明这个特性总是强制协商的 (例如, XMPP客户端的资源绑定就是这样的); 或
- 为接收方实体指定一个方法来标记这个特性在这次交互中为强制协商 (例如, 对于 STARTTLS, 这是通过包含一个空的 <required/> 元素到流特性广告中来实现的, 但这不是对所有流特性的通用格式); 建议用于新的强制协商特性的流特性定义如 STARTTLS 所做的那样通过包含一个空的 <required/> 元素来实现.
- 提示性备注: 因为没有通用格式来说明一个特性是强制协商的, 有可能会出现一个发起方实体不能理解的特性而被接收方认为是强制协商的, 而导致流协商过程失败. 尽管这样一个结果是不可取的, 本工作组认为不需要通用格式的情况是很罕见的.
基于安全性的原因, 在某些流特性协商成功之后,发起方有必要发送一个新的发起流头 (例如, 任何情况下的TLS和当建立了安全层的情况下的SASL). 如果一个给定的流特性出现在这种情况下, 那个特性的定义需要指定该特性协商之后的流重启.
一个包含至少一个强制协商特性的<features/>元素表明了流协商没有完成, 发起方实体必须进行进一步的特性协商.
R: <stream:features> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'> <required/> </starttls> </stream:features>
一个<features/>元素可以包含不止一个强制协商特性. 这意味着发起方实体能在流协商过程的这个阶段中对强制协商特性进行选择. 举例来说, 可能一个将来的技术将执行和TLS一样的功能, 所以接收方实体可能在这次流协商过程中的同一个阶段声明同时支持TLS和这个将来的技术. 无论如何, 这只适用于流协商过程中的给定阶段而不适用于不同阶段的强制协商特性 (例如, 接收方实体不会声明同时支持STARTTLS和SASL作为强制性协商, 或同时声明SASL和资源绑定为强制协商, 因为TLS需要在SASL之前协商并且SASL需要在资源绑定之前协商).
一个同时包含强制协商和自愿协商特性的<features/>元素表明协商未完成, 发起方实体可以在它尝试协商强制协商特性之前完成自愿协商特性.
R: <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> <compression xmlns='http://jabber.org/features/compress'> <method>zlib</method> <method>lzw</method> </compression> </stream:features>
一个只包含自愿协商特性的<features/>元素表明流协商已经完成, 发起方实体可以开始发送XML节了. 但是如果发起方实体愿意可以协商更多特性.
R: <stream:features> <compression xmlns='http://jabber.org/features/compress'> <method>zlib</method> <method>lzw</method> </compression> </stream:features>
一个空的<features/>元素表明流协商已经完成, 发起方实体可以开始发送XML节了.
R: <stream:features/>
重启
在对一个需要流重启的特性成功协商之后, 双方都必须考虑前一个流将被取代, 但是必须不能发送一个关闭的</stream>标签并且必须不能终止底层的TCP连接; 反之, 双方必须重用现有的连接, 它可以处于一个新的状态(例如, 作为一个TLS协商的结果被加密). 然后发起方实体必须发送一个新的发起流头, 它之前应该放一个如 11.5 所述的XML声明. 当接收方实体接收到新的发起流头, 它必须在发送一个新的应答流头(它之前应该放一个如 11.5 所述的XML声明)之前生成一个新的流ID(而不是重用旧的流ID).
重发特性
接收方实体必须在一个流重启之后发送一个流特性的更新列表给发起方实体. 如果没有更多的特性要被声明,这个更新的流特性列表可以是空的,也可以包含任何特性的组合.
完成流协商
接收方实体通过发送一个空的<features/>元素或只包含自愿协商特性的<features/>元素来表明流协商过程的完成. 这样做之后, 接收方实体可以发送一个空的<features/> 元素(例如, 在这些自愿协商特性协商完成之后) 但是必须不能发送额外的流特性给发起方实体(如果接收方实体有新的特性提供, 最好仅限于强制协商或安全关键的特性, 它可以简单地以一个<reset/>流错误( 4.9.3.16 )来关闭流并且等发起方重新连接的时候声明新的特性, 最好以一个交错的方法来关闭现有的流,这样不会让所有的发起方同时进行重连). 一旦流协商完成, 发起方就可以一直通过这个流来发送XML节,只要双方都维持着这个流.
- 提示性备注: 在下面 第7章 定义的资源绑定是一个前述规则的一个历史性的例外, 因为对于客户端来说它是强制协商的但使用XML节来达成协商.
在流协商完成之前,发起方实体不能(MUST NOT)尝试发送 XML节 给非自身的实体(也就是是说, 客户端的已连接资源或客户端帐号的任何其他已验证的资源) 或给它连接的服务器. 即使发起方尝试这么做, 接收方实体也不能(MUST NOT)接受这些节并且必须以一个<not-authorized/>流错误( 4.9.3.12 )来关闭流. 这个规则只适用于XML节(也就是说, 由内容命名空间限定的 <message/>, <presence/>, 和 <iq/> 元素) 而不是用于流协商的XML元素(例如, 完成 TLS协商 或 SASL协商 的元素).
确定地址
在一个XML流的双方已经完成了流协商的适当步骤之后, 流的接收方实体必须确定发起方实体的JID.
对于客户端-服务器的通讯, 在服务器能确定客户端的地址之前,SASL协商 和 资源绑定 必须完成. 客户端的纯JID (<localpart@domainpart>) 必须是授权身份 (如 SASL 所定义的, 要么 (1) 是 SASL协商 期间客户端直接与之通讯的身份,要么 (2) 如果SASL协商期间没有指定授权身份,则是从服务器的验证身份指定的. 全JID(<localpart@domainpart/resourcepart>)的资源部分(resourcepart)必须是客户端和服务器在 资源绑定 期间协商得来的那个资源. 客户端必须不去尝试猜测它的JID而是必须相信在资源绑定期间服务器返回给它的JID. 服务器必须确保返回的这个JID (包含 本地部分(localpart), 域部分(domainpart), 资源部分(resourcepart), 以及分隔符) 遵循定义于 XMPP‑ADDR 的XMPP地址规范格式; 为了满足这个限定, 服务器可以把客户端的发来的JID替换成服务器确定的规范JID并在资源绑定期间使用那个JID来和客户端通讯.
对于服务器-服务器通讯, 发起方服务器的纯JID (<domainpart>) 必须是授权的身份 (定义于 SASL ), 要么 (1) 是SASL协商 期间发起方服务器直接与之通讯的身份,要么 (2) 如果SASL协商期间没有指定授权身份,则是从接收方服务器的验证身份指定的. 在缺少SASL协商的情况下, 接收方服务器可以认为授权身份是一个和相关确认协议协商的身份(例如, 在 服务器回拨XEP‑0220 中 <result/> 元素的'from'属性.
- 安全警告: 因为可能会有第三方在一个安全层例如TLS成功协商之前篡改流上发送的数据, 建议接收服务器谨慎对待这些未得到保护的信息; 这特别适用于由发起方实体发送的第一个发起流头中的'from'和'to'地址.
流程图
我们在接下来的非规范性的流程图里为流协商过程总结前述的规则, 以发起方实体的视角来展示.
+---------------------+ | 打开TCP连接 | +---------------------+ | v +---------------+ | 发送发起流头 |<-------------------------+ | | ^ +---------------+ | | | v | +------------------+ | | 接收应答流头 | | | | | +------------------+ | | | v | +----------------+ | | 接收流特性 | | +------------------>| | | ^ {可选的} +----------------+ | | | | | v | | +<-----------------+ | | | | | {空?} ----> {全部自愿? } ----> {一些强制性? } | | | 否 | 否 | | | | 是 | 是 | 是 | | | v v | | | +--------------------+ +-----------------+ | | | | 可以协商或不协商 | |必须协商一个特性 | | | | | 任何一个 | | | | | | +--------------------+ +-----------------+ | | v | | | | +---------+ v | | | | 完成 |<----- {协商? } | | | +---------+ 否 | | | | 是 | | | | v v | | +--------->+<---------+ | | | | | v | +<-------------------------- {强制性重新开始? } ------------>+ 否 是
- 图表3: 流协商流程图
关闭流
从一个实体到另一个实体的XML流可以在任何时候关闭, 可以是因为发生了一个特定的流错误 (4.9章)) 也可以没有任何错误(例如, 当客户端简单地结束它的会话).
通过发送一个关闭的</stream>标签来关闭流.
E: </stream:stream>
如果双方正在使用一个TCP连接上两个流, 或者两个TCP连接上两个流的通信方式, 发送关闭流标签的实体必须按以下步骤进行:
- 在终止底层的TCP连接之前等待另一方也关闭它的出站流; 这让另一方有机会在终止TCP连接之前发完任何到正在关闭的实体的出站数据.
- 避免通过它的出站流发送任何更多的数据到另一方, 但是继续处理从另一方实体已经接收到的数据(并且, 如果必要, 处理这些数据).
- 如果另一方没有在一个合理的时间(这里 "合理" 的定义取决于实现或布署)内发送它的关闭流标签则认为两个流都失效了.
- 从另一方接收到一个反向的关闭流标签之后, 或在等待一段合理的时间之后未收到应答, 终止底层的TCP连接.
- 安全警告: 根据 TLS 的7.2.1章节, 为了帮助防止截断攻击,正在关闭流的那一方在终止底层TCP连接之前必须发送一个 TLS close_notify警告, 并且必须从另一方接收到应答的 close_notify警告.
如果双方通过多重TCP连接使用多重流, 那么没有已定义的配对流, 因此其行为取决于实现.
方向性
一个XML流总是单向的, 这意味着只能从一个方向通过流来发送XML节(要么从发起方实体到接收方实体,要么从接收方实体到发起方实体).
取决于已协商的会话的类型以及涉及的实体的性质, 该实体可以使用:
- 在一个TCP连接上跑两个流, 这里第一个流协商的安全性上下文也适用于第二个流. 这对客户端-服务器是典型的情况, 并且服务器必须允许客户端为两个流使用同一个TCP连接.
- 在两个TCP连接上跑两个流, 这里每个流是独立保障安全的. 在这种方法下, 一个TCP连接用于发起方实体发送节到接收方实体的那个流, 另一个TCP连接用于接收方实体发送节到发起方实体的那个流. 这对服务器-服务器会话是典型的情况.
- 在两个或更多TCP连接上跑多个流, 这里每个流是独立保障安全的. 这个方法有时用于两个大的XMPP服务提供商之间的服务器-服务器通讯; 无论如何, 在 10.1 描述的情况下这将导致难于维护从多个流接收到的数据的一致性, 这是为什么如果远程服务器尝试如4.9.3.3 所述来协商多个流,本服务器可能以一个<conflict/>流错误(4.9.3.3)来关闭流.
这个方向性的概念只适用于节, 并且明确地不适用于根流(stream root)的第一级子元素, 那些根流是被用来启动或管理流的(例如, 用于TLS协商, SASL协商, 服务器回拨 XEP-0220 , 流管理 XEP-0198 的第一级元素).
上述的考虑暗示当完成 STARTTLS协商 和 [RFC6120#SASL协商|SASL协商]] 的时候,两个服务器将使用同一个TCP连接, 但是在流协商过程完成之后,原始的那个TCP连接将仅用于发起方服务器发送XML节到接收方服务器. 为了让接收方服务器能发送XML节给发起方服务器, 接收方服务器将需要反转角色并通过一个单独的TCP连接从接收方服务器向发起方服务器协商一个XML流. 然后这个单独的TCP连接用新一轮的 TLS 和/或 SASL 协商来保证安全性.
- 实现备注: 基于历史原因, 一个服务器-服务器会话总是使用两个TCP连接. 这个方法仍然在本文描述的标准行为之内, 类似 XEP‑0288 的扩展允许服务器们使用单一的TCP连接协商来进行双向的节交换.
- 提示性备注: 尽管XMPP开发者们有时对底层TCP连接使用了"单向"和"双向"的概念 (例如, 把用于客户端-服务器会话的TCP连接称为"双向的" 而用于服务器-服务器会话的TCP连接称为"单向的"), 严格来讲一个流总是单向的 (因为发起方实体和接收方实体总是最少有两个流, 每个方向一个) 并且一个TCP连接总是双向的 (因为TCP通讯能从两个方向发送). 方向性应用于TCP连接的应用层通讯, 而不是在TCP连接的传输层通讯本身.
无响应对端的处理
- 当连接某个流的实体在一段时间内没有接收到同样连接到该流的另一对等端发来的任何XMPP信息,那么该对等端可能是无响应的。有几个原因很可能引起这种情况发生:
- 底层的TCP连接死掉。
- 尽管当底层的TCP连接仍然是激活的时候,XML流被中断了。
- 对等端是空闲的,只是没有通过其连接的XML流发送XMPP信息到该实体。
- 这三个条件最好被分别对待,像下面章节描述的一样。
- 实现注意:为了处理无响应的对等端,我们把两个单向的TCP连接当作一个在概念上相当的双向的TCP连接(见4.5节(方向性));然而,实现者要知道在两个单向的TCP连接的情况下,在XMPP应用层对等端对通信的响应将从其第二个TCP连接返回。此外,在每个方向上的多个数据流的使用(大型的XMPP服务提供商间的服务器到服务器之间的连接经常这么部署)使得对XMPP流和底层TCP连接的应用级检查进一步复杂化了,因为任何给定的初始流和任何给定的响应流之间没有必然联系。
死连接
- 检查TCP连接的一个通用方法是在XML节之间发送一个空格符(U+0020),发送空格在XML流中是被允许的,下面第11.7节中将进行描述。发送这样的一个空格被称作“空格保持激活”(词语“whitespace ping”通常被使用,尽管事实上它不是一个ping,因为“pong”是不可能的)。然而,在TLS认证和SASL认证期间,发送空格符是不允许的,下面第5.3.3节和第6.3.5节将会描述。
中断的流
- 即使底层的TCP连接仍然是激活的,对等端很可能从来不对实体发出的XMPP通信请求作出响应,不管是正常的节还是例如在[XEP-0199]中所定义的应用程序级别ping那样的专门流通信检查,或者在[XEP-0198]中定义的更全面的流管理协议。在这种情况下,对实体来说恰当的做法是发送<connection-timeout/>流错误(第4.9.3.4节)来关闭中断的流。
空闲对端
- 即使底层的TCP连接仍然激活,并且流没有被中断,对等端很可能在一段时间内都没有发送XML节。在这种情况下,对等端可以(MAY)关闭流(像第4.4节描述的一样),而不是让不再使用的流打开着。如果空闲对等端没有关闭流,那么与该流关联的另一端或者可以(MAY)通过使用第4.4节描述的握手方式来关闭该流,或者发送一个流错误(例如:<resource-constraint/>(第4.9.3.17节),如果实体已经到了打开TCP连接数的限制,或者<policy-violation/>(第4.9.3.14节),如果连接已超出本地超时政策)来关闭该流。然而,层(下面第13.3节指定)的顺序要符合,在得出对等端处于空闲状态的结论前,另一端需要去核实底层TCP连接仍然是激活的并且流没有被中断(前面已描述)。此外,在接受空闲对等端时最好宽容一点,因为经验表明,这样做可以提高在XMPP网络上通信的可靠性,并且保持两个服务器之间的流比积极地去超时一个流通常更有效。
检查方法的使用
建议实现人员支持任何他们认为合适的流检查和连接检查方法, 但是要小心衡量这些方法对网络的冲击和及时发现中断的流和死TCP连接得到的好处. 使用的任何特定检查方法的时间间隔对本地服务策略都是一个大事情,并且严重依赖于网络环境和给定布署和连接类型的使用场景. 在撰写本文的时候, 建议任何这类检查的执行不要超过每5分钟一次, 并且理想的情况是, 这类检查由客户端发起而不是服务器来发起. 鼓励那些实现XMPP软件和布署XMPP服务的人对适当的流检查和连接检查时间间隔寻求其他的意见, 特别是当使用了功率受限的设备的时候 (例如, 在移动环境).
流属性
根<stream/>元素的属性定义在以下章节.
- 安全警告: 在流的保密性和安全性被 第5章 描述的TLS或一个相当的安全层(类似SASL GSSAPI机制)保护之前, 一个流头提供的属性们可能会被攻击者篡改.
- 实现备注: 根<stream/>元素的属性不是以一个命名空间前缀来前置是因为, 根据 XML‑NAMES 的解释, "[d]缺省的命名空间声明不直接应用用于属性名称; 没有前缀的属性的解释由它们出现的那个节来决定."
from
- 'from'属性指定发送流元素的实体的XMPP标识。
对客户机-服务器通信的发起流头而言,'from'属性是控制客户端的主要XMPP标识,如格式为<localpart@domainpart>的JID。客户端可能不知道XMPP标识,因为XMPP标识不是在XMPP应用层的等级上被分配的(如通用安全服务应用程序接口[GSS-API]中描述),或者从客户端提供的信息由服务器生成(像采用SASL EXTERNAL机制的终端用户证书部署)。此外,如果客户端认为XMPP标识是私人信息,那么在流的保密性和完整性被TLS或等效的安全层保护之前是不提倡包括一个'from'属性的。但是,如果客户端知道XMPP标识,它应该(SHOULD)在流的保密性和完整性被TLS或等效的安全层保护之后包括'from'属性。
I: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
- 对于服务器-服务器通信的发起流头而言,'from'属性是服务器配置的FQDN之一,如,格式如<domainpart>的JID。初始服务器可能有一个以上的XMPP标识,如,在服务器提供虚拟主机的情况下,所以它需要选择一个与此输出流关联的标识(如:基于触发流协商尝试的节的'to'属性)。因为服务器是XMPP网络上的“公共实体”,它必须(MUST)在流的保密性和完整性被TLS或等效的安全层保护之后包含'from'属性。
I: <?xml version='1.0'?> <stream:stream from='example.net' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'>
- 对于客户端-服务器和服务器-服务器通信的响应流头,接收实体必须(MUST)包括'from'属性并且必须(MUST)把该属性值设置为接收实体的FQDN之一(可以(MAY)是一个正式域名(FQDN),而不是发起流头中'to'属性指定的值,下面第4.9.1.3节和第4.9.3.6节将描述)。
R: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
无论'from'属性是否被包括,在和其它实体交换XML节之前,每个实体必须(MUST)核实其它实体的身份,下面第13.5节将描述。
- 互操作性备注:基于[RFC3920]的实现在任何流头(即使其保密性和完整性受到保护)中不包括'from'地址是可以的,一个实体在接收这样的流头时应该(SHOULD)宽容些。
to
- 对于客户端-服务器和服务器-服务器通信中的发起流头,发起实体必须(MUST)包含'to'属性,并且必须(MUST)使用发起实体知道或者期望接收实体提供服务的域名来设置'to'属性的值。(在其他方面,如[TLS-EXT]中所描述,在TLS协商期间的服务器名称显示,也可以提供类似的信息。)
I: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
- 对于客户端-服务器通信的响应流头,如果客户端在其发起流头中包含'from'属性,那么服务器在应答流头中必须(MUST)包含'to'属性,并且必须(MUST)使用初始化流头中'from'属性指定的裸JID来设置'to'属性的值。如果客户端在其初始化流头中不包含'from'属性,那么服务器在应答流中不必(MUST NOT)包含'to'属性。
R: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
- 对于服务器-服务器通信的响应流头,接收实体在应答流头中必须(MUST)包含'to'属性,并且必须(MUST)使用发起流头中'from'属性指定的域名部分来设置'to'属性的值。
R: <?xml version='1.0'?> <stream:stream from='im.example.com' id='g4qSvGvBxJ+xeAd7QKezOQJFFlw=' to='example.net' version='1.0' xml:lang='en' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'>
- 无论是否包含'to'属性,每个实体在与其它实体交换XML节之前必须(MUST)先核实其它实体的身份,如下面第13.5节所描述。
- 互操作性备注:基于[RFC3920]的实现在任何流头中不包括'to'地址是可以的,一个实体在接收这样的流头时应该(SHOULD)宽容些。
id
- 'id'属性是流的唯一标识符,称为“流ID”。流ID必须(MUST)由接收实体在发送应答流头时生成,并且在接收的应用(一般是一个服务器)内部,所生成的流ID必须(MUST)是唯一的。
- 对于初始流头,初始实体不必(MUST NOT)包含'id'属性;但是,如果包含'id'属性,接收实体必须(MUST)忽略它。
- 对于响应流头,接收实体必须(MUST)包括'id'属性。
R: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
- 互用性备注:在RFC 3920中,列入'id'属性的文字含糊不清的,导致一些实现的id属性脱离了响应流头。
xml:lang
'xml:lang'属性指定一个实体在该流上发送的任何可读XML字符串数据的首选或缺省语言(XML节也可以拥有'xml:lang'属性, 定义在 8.1.5 ). 这个属性的语法定义于 XML 的2.12节; 特别是, 'xml:lang'属性必须符合 NMTOKEN 数据类型 (定义于 XML 的2.3节) 同时必须符合定义于 LANGTAGS 的语言标识符.
对于发起流头, 发起方实体应该包含 'xml:lang' 属性.
I: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
对于应答流头, 接收方实体必须包含 'xml:lang' 属性. 应用以下规则:
- 如果发起方实体在它的发起流头包含了'xml:lang'属性,而接收方实体在它生成和发送给发起方实体的可读XML字符串数据(例如, 流和节错误中的<text/>元素)中支持那个语言, 'xml:lang'属性值必须是发起方实体首选语言的标识符(例如, "de-CH").
- 如果接收方实体根据定义于LANGMATCH 3.4节的 "lookup scheme"(例如, "de" 而不是 "de-CH")支持一种和发起方实体首选语言匹配的语言, 那么'xml:lang'属性值应该是匹配语言的标识符.
- 如果接收方实体不支持发起方实体的首选语言或根据查找方案匹配的语言(或者如果发起方实体未在其发起流头中包含'xml:lang'属性), 那么'xml:lang'属性值必须是接收方实体的缺省语言的标识符(例如, "en").
R: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
如果发起方实体在它的发起流头包含了'xml:lang'属性, 接收方实体应该记住那个值并以此作为发起方实体通过当前流发送的所有节的缺省 xml:lang . 如下面的 8.1.5 所述, 发起方实体可以在它通过这个流发送的任何XML节中包含'xml:lang'属性. 如果发起方实体在任何这类节中未包含'xml:lang'属性, 接收方实体在路由它到远程服务器或递送它到一个已连接的客户端时应该加上'xml:lang'属性, 而这个属性的值必须是发起方实体的首选语言的标识符(即使接收方实体生成和发送给发起方实体的可读XML字符串数据不支持该语言, 类似流或节错误等). 如果发起方实体在任何这类节中包含了'xml:lang'属性, 接收方实体在路由它到远程服务器或递送到已连接的客户端时必须不能修改和删除它.
version
- 本文规定的XMPP版本是“1.0”;尤其,XMPP 1.0 封装了流相关协议以及三个定义的XML节类型的基本语义(<message/>, <presence/>,和 <iq/>,下面8.2.1节,8.2.2节,8.2.3节分别描述)。
- XMPP版本编号方案是“<主版本>.<次版本>(‘<major>.<minor>’)”。主版本和次版本号必须(MUST)视为独立的整数,并且每个数可能(MAY)会以高于一个单一的数字的方式递增。因此,“XMPP 2.4”版本将低于“XMPP 2.13”,同理,“XMPP 2.13”版本将低于“XMPP 12.3”。接受方必须(MUST)忽略前导零(例如,“XMPP 6.01”),并且不能(MUST NOT)发送。
- 只有重大的新功能已被添加到核心协议(如:对message, presence, 或者IQ节新定义一个‘TYPE’属性值),次要版本号才递增。具有较小次版本号的实体必须(MUST)忽略版本比它大的次版本号,但是具有较大次版本号的实体为了一些信息目的可以使用版本比它小的次版本号(如:具有较大次版本号的实体将仅仅关注其通信器将不能理解‘TYPE’属性的值,因此不发送它)。
- 下述规则适用于流头内“version”属性的生成和处理:
- 初始化实体必须(MUST)在初始化流头中设置‘version’属性的值为它所支持的最高版本号(如:如果它支持的最高版本号是本文中定义的,它必须(MUST)设置该值为“1.0”)。
- 接收实体必须(MUST)在应答流头中设置‘version’属性的值或者为初始化实体提供的值,或者为接收实体所支持的最高版本号,以较低者为准。接收实体必须(MUST)执行主要和次要版本号的数值比较,而不是关于“<主版本>.<次版本>(‘<major>.<minor>’)”的字符串匹配。
- 如果在应答流头中包含的版本号至少在主版本号上比在初始化流头中包含的版本号低,那么较新版本的实体不能与旧版本的实体互操作,初始化实体应该(SHOULD)通过发送一个包含<unsupported-version/>元素的流错误(第4.9.3.25节)来关闭流。
- 如果任何一个实体接收到一个没有‘version’属性的流头,该实体必须(MUST)认为其他实体支持的版本是“0.9”,并且不应该在响应流头中包含‘version’属性。
流属性总结
- 下表总结了根元素<stream/>的属性。
+----------+--------------------------+-------------------------+ | | 初始实体 到 接收实体 | 接收实体 到 初始实体 | +----------+--------------------------+-------------------------+ | to | 接收实体JID | 初始实体JID | | from | 初始实体JID | 接收实体JID | | id | 忽 略 | 流标识 | | xml:lang | 默认语言 | 默认语言 | | version | XMPP 1.0+ supported | XMPP 1.0+ supported | +----------+--------------------------+-------------------------+
图4:流属性
XML命名空间
读者可以参考 XML‑NAMES 来完整地理解本章的概念, 特别是本协议的第三章和第6.2节的 "缺省命名空间" 的概念.
流命名空间
<stream/> 根元素 ("流头") 必须由 'http://etherx.jabber.org/streams' (即 "流命名空间") 命名空间来限定. 如果违反了这个规则, 接受到该流头的实体必须以一个流错误来关闭流, 这个流错误应该是 <invalid-namespace/> (4.9.3.10), 尽管一些现存的实现发送的是 <bad-format/> (4.9.3.1) .
内容命名空间
实体可以声明一个"内容命名空间" 作为缺省的命名空间用于在流上发送的数据 (也就是那些不属于由流命名空间限定的元素的数据). 如果这样做, (1) 内容命名空间必须不同于流命名空间, 并且 (2) 内容命名空间对于发起流和应答流必须是相同的,使得两个流的限定是一致的. 内容命名空间应用所有从流上发送的一级子元素,出非被显式地由另一个命名空间来限定 (即, 内容命名空间是缺省命名空间).
另外 (也就是说, 不定义内容命名空间作为缺省命名空间), 实体可以显式地为流的每个一级子元素限定命名空间, 使用所谓 "自由前缀规范". 这两种方式在下面的例子中展示.
当声明了一个内容命名空间作为缺省命名空间时, 大致来说一个流看卡里类似下面的例子.
<stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <message> <body>foo</body> </message> </stream:stream>
当没有定义内容命名空间作为缺省命名空间而使用所谓"自由前缀规范"时, 大致一个流看起来像以下例子.
<stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='http://etherx.jabber.org/streams'> <message xmlns='jabber:client'> <body>foo</body> </message> </stream>
传统上, 大多数XMPP实现在流头使用了 把内容命名空间作为缺省命名空间 的方式,而不是 自由前缀规范 的方式; 无论如何, 两种方式都是可以接受的,因为它们在语义上是等价的.
XMPP内容命名空间
本协议定义的XMPP使用两种内容命名空间: 'jabber:client' 和 'jabber:server'. 这些命名空间差不多相同但是用于不同的上下文 ('jabber:client'用于 客户端-服务器 通讯而'jabber:server' 用于 服务器-服务器 通讯). 两者之间唯一的不同是在通过'jabber:client'命名空间限定的XML流发送的节中 'to' 和 'from' 属性是可选的, 而在'jabber:server'限定的XML流发送的节中它们是必需的. 支持这些内容命名空间意味着支持常规属性和所有三个核心节类型 (message, presence, 和IQ)的基本语义.
一个实现可以支持不同于 'jabber:client' 或 'jabber:server' 的内容命名空间. 然而, 因为这些命名空间将定义不同于XMPP的应用, 它们将在单独的协议中定义.
一个实现可以拒绝支持任何与缺省命名空间不同的其他内容命名空间. 如果一个实体不支持它接收到的一个一级子元素的内容命名空间, 它必须以 <invalid-namespace/> 流错误(4.9.3.10)关闭这个流 .
客户端实现必须支持'jabber:client'内容命名空间作为缺省命名空间. 'jabber:server'内容命名空间超出了XMPP客户端的范畴, 并且客户端不能(MUST NOT)发送'jabber:server'命名空间限定的节.
服务器实现必须同时支持'jabber:client'命名空间(当这个流用于客户端和服务器之间通讯的时候)和'jabber:server'命名空间(当这个流用于两个服务器之间的通讯的时候)作为缺省命名空间. 和一个已连接的客户端通讯时,该服务器不能(MUST NOT)发送由'jabber:server'命名空间限定的节; 和一个对端服务器通讯时, 该服务器不能(MUST NOT)发送由'jabber:client'命名空间限定的节.
- 实现备注: 因为一个客户端通过一个内容命名空间为'jabber:client'的流发送节, 如果一个服务器路由一个它从一个已连接的客户端收到的节到另一个对端服务器,那么它需要为这个节 "重新界定范围(re-scope)" ,所以它的命名空间是'jabber:server'. 类似的, 如果一个服务器递送一个它从别的对端服务器收到的节到一个已连接的客户端,那么它需要给这个节 "重新界定范围" ,所以它的内容命名空间是'jabber:client'. 这个规则适用于4.1定义的XML节(即, 一个由'jabber:client'或'jabber:server'命名空间限定的顶级的 <message/>, <presence/>, 或 <iq/> 元素 ), 并且命名空间继承到一个节的所有子元素. 然而, 这个规则不适用于非'jabber:client'和'jabber:server'限定的元素以及它们的任何子元素(例如, 一个包含了用于报告的扩展元素(8.1)的 <message/>元素). 尽管不可能禁止一个实体在一个扩展元素中生成的节的子元素由'jabber:client'或'jabber:server'命名空间来限定, 现有的实现处理这类节的方法是不一致的; 因此, 建议实现者自己权衡选择降低互操作性还是损失这些节的功能. 最后, 建议服务器对于使用其他流连接方法和替代的XMPP连接方法而来的节重新界定范围, 例如那些定义在 XEP-0124 , XEP-0206 , XEP-0114 , 和 XEP-0225 中的节.
其他命名空间
参与一个流的双方都可以发送非内容命名空间和流命名空间限定的数据. 例如, 和TLS协商以及SASL协商相关的数据如何交换, 以及类似 流管理XEP‑0198和服务器回拨XEP‑0220的XMPP扩展.
- 互操作性备注: 由于历史的原因, 一些服务器实现预期有一个'jabber:server:dialback'命名空间的声明用于 服务器-服务器 流, 详见XEP‑0220.
无论如何, 一个XMPP服务器不能(MUST NOT)路由或递送这样一个从入站流中接收到的数据,如果那个数据 (a) 是由其他命名空间限定 并且 (b) 地址指向的实体不是一个服务器, 除非从出站流发送数据出来的另一方服务器显式地协商或声明支持从该服务器接收任意数据. 制定这个规则是因为XMPP是设计用来做XML节交换的(而不是任意XML数据), 也因为允许实体发送任意数据到其他实体可能会导致恶意数据交换显著增加. 作为本规则的例子, example.net 域主机将不会从 <romeo@example.net> 到 <juliet@example.com> 的路由一级XML元素:
<ns1:foo xmlns:ns1='http://example.org/ns1' from='romeo@example.net/resource1' to='juliet@example.com'> <ns1:bar/> </ns1:foo>
这个规则也适用于看起来像节但是命名空间不正确所以并不是真的节的一级元素(参见 4.8.5), 例如:
<ns2:message xmlns:ns2='http://example.org/ns2' from='romeo@example.net/resource1' to='juliet@example.com'> <body>hi</body> </ns2:message>
在从一个入站流中接收到任意一级XML元素之后, 服务器必须要么忽略该数据要么以一个流错误关闭这个流, 这个流错误应该是 <unsupported-stanza-type/> (4.9.3.24).
命名空间声明和前缀
因为内容命名空间不同于流命名空间, 如果把一个内容命名空间声明为缺省命名空间,那么以下论断为真:
- 流头需要包含一个同时用于内容命名空间和流命名空间的命名空间声明.
- 流命名空间的声明需要包含用于流命名空间的前缀.
- 互操作性备注: 由于历史原因, 一个实现可能只包含前缀'stream'用于流命名空间(包含了前缀的结果就像<stream:stream>和<stream:features>); 这个来自RFC3920的规范保留下来用于向后兼容. 实现如果使用不同于'stream'的前缀用于流命名空间将导致互操作性问题. 如果实体接收到一个流头而该流头的流命名空间前缀是不可接受的, 它必须以一个流错误来关闭这个流, 这个流错误应该是 <bad-namespace-prefix/> (4.9.3.2), 虽然一些现有的实现发送的是 <bad-format/> (4.9.3.1) .
一个实现不能(MUST NOT)为由内容命名空间限定的元素生成命名空间前缀(即, 在流上发送的数据的缺省命名空间),如果内容命名空间是'jabber:client'或'jabber:server'. 例如, 以下是非法的:
<stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <foo:message xmlns:foo='jabber:client'> <foo:body>foo</foo:body> </foo:message>
XMPP实体不应该接受违反这一规则的数据(特别是, XMPP服务器如果不首先纠正这个错误,就不能(MUST NOT)路由这类数据到另一个实体); 替代方案是,它应该要么忽略这个数据要么以流错误来关闭这个流, 这个流错误应该是 <bad-namespace-prefix/> (4.9.3.2).
在一个流头中对命名空间的声明必须只应用于那个流(例如, 'jabber:server:dialback' 命名空间用于服务器回拨 XEP‑0220 ). 特别是, 因为用于通过流路由或递送到其他实体的XML节将丢失在那些生成节的原始流头中的命名空间的上下文, 这些节中的扩展内容的命名空间不能(MUST NOT)在那个流头中声明(参见 8.4). 如果流的参与双方声明了这样的命名空间, 这个流的其他参与方应该以<invalid-namespace/>流错误关闭该流(4.9.3.10). 在任何情况下, 实体必须确保当从入站流路由或递送节到出战流的时候,这些命名空间(根据本章)被正确地声明 .
流错误
流的根元素可以包含一个由流命名空间限定的<error/>子元素. 这个错误子元素将由兼容的实体来发送,如果它发现发生了一个流错误.
规则
以下规则适用于流这一级的错误.
流错误是不可恢复的
流级别的错误是不可恢复的. 所以, 如果一个错误发生在流这个级别, 检测到这个错误的实体必须发送一个<error/>元素,其中包含适当的子元素以指明错误情况,并如4.4所述立刻关闭这个流.
C: <message><body>No closing tag!</message> S: <stream:error> <not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
接着收到这个流错误的实体将如4.4所述关闭这个流.
C: </stream:stream>
流错误可能发生在安装过程中
如果该错误是被初始化流头触发的, 那么接收方实体必须仍然发送打开的<stream>标签, 把这个<error/>元素作为该流元素的子元素, 并且发送关闭流的</stream>标签(最好在同一个TCP包里).
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://wrong.namespace.example.org/'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <invalid-namespace xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
当主机未定义或未知时会发生流错误
如果初始化实体未提供'to'属性或在'to'属性里提供了一个未知的主机并且这个错误发生在流安装的时候, 在接收方实体关闭这个流之前,由接收方实体返回给初始方实体的流头的'from'属性必须要么是接收方实体的可靠的完整域名要么是空字符串.
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='unknown.host.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
流错误发到哪
当在初始方实体和接收方实体之间使用了两个TCP连接(每个方向使用一个连接)而不是使用单独的双向连接时, 适用以下规则:
- 和初始方流相关的流级别错误由接收方实体在应答流中通过同一个TCP连接返回.
- 从初始方实体用同一个TCP连接通过初始流发送的出站节所触发的节错误(区别于流级别的错误),接收方实体应通过另一个("返回")TCP连接的应答流中返回,因为从初始方实体的角度来看这个(返回错误的)节是入站节.
语法
流错误的语法如下所示, 显示在中括号 '[' 和 ']' 的XML数据是可选的.
<stream:error> <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> [<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='langcode'> OPTIONAL descriptive text </text>] [OPTIONAL application-specific condition element] </stream:error>
"defined-condition" 必须对应 4.9.3 中定义的流错误条件之一. 然而, 因为将来肯能定义额外的错误条件, 如果实体接收到一个它不理解的流错误条件,那么它必须把未知的条件当作 <undefined-condition/> (4.9.3.21). 如果一个XMPP扩展的设计者或XMPP实现的开发者需要使用未在本协议中定义的流错误条件来通讯, 他们可以定义一个由应用层命名空间限定的应用特有的错误条件元素来达到这个目的.
<error/>元素:
- 必须包含一个对应已定义错误条件之一的子元素; 这个元素必须由 'urn:ietf:params:xml:ns:xmpp-streams' 命名空间限定.
- 可以包含一个内有XML字符串数据的 <text/> 子元素用来描述错误细节; 这个元素必须由 'urn:ietf:params:xml:ns:xmpp-streams' 命名空间限定并且应该拥有一个 'xml:lang' 属性来指定XML字符串数据的自然语言.
- 可以包含一个子元素用于应用特有的错误条件; 这个元素必须由一个应用定义的命名空间来限定,并且它的结构也由该命名空间来定义 (见 4.9.4).
<text/> 元素是可选的. 如果有它, 它必须只用于提供描述性或诊断性的信息,用来补充一个已定义的条件或应用特有的条件的含义. 它不能(MUST NOT)被应用程序解释执行. 它不能(MUST NOT)被用作向自然人用户展示的错误消息, 但是可以被用作和已定义条件元素(以及, 可选地, 应用特有的条件元素)相关的错误消息的额外信息.
已定义的流错误条件
以下是已定义的流级别的错误条件.
bad-format
实体发送了无法处理的XML.
(在以下例子中, 客户端发送了一个非XML的XMPP消息, 它也可能会触发一个 <not-well-formed/> 流错误( 4.9.3.13).)
C: <message> <body>No closing tag! </message> S: <stream:error> <bad-format xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
- 这个错误可以用更多特定的XML相关的错误来替代, 例如 <bad-namespace-prefix/>, <invalid-xml/>, <not-well-formed/>, <restricted-xml/>, 和 <unsupported-encoding/>. 无论如何, 建议使用更多特定的错误.
bad-namespace-prefix
实体发送了不被支持的命名空间前缀, 或在一个需要这样的前缀的元素中没有发送命名空间前缀 (见 11.2).
(在以下例子中, 客户端指定了一个命名空间前缀 "foobar" 用于XML流命名空间.)
C: <?xml version='1.0'?> <foobar:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:foobar='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <bad-namespace-prefix xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
conflict
服务器要么 (1) 关闭这个实体现存的流(如果和现有流冲突的新流已经被初始化了), 要么 (2) 拒绝这个实体的新流(如果允许这个新的流将导致和现有的流冲突(例如, 服务器限制来自同一IP地址的连接数量或对于给定的域对只允许一个 服务器-服务器 流,以确保10.1所述的顺序处理)).
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
如果客户端收到一个<conflict/>流错误( 4.9.3.3), 它尝试重新连接的时候绑定的资源不能(MUST NOT)和前一个会话的资源相同,而是必须选择一个不同的资源; 详见 第7章.
connection-timeout
如果一方有理由相性另一方永久地失去了通过某个流进行通讯的能力,它可以关闭这个流. 有很多办法可以查觉到对方丧失通讯能力, 类似 4.4 所述的空格符保持连接, 定义于 XEP‑0199 的XMPP级的ping, 以及定义于XEP‑0198 的XMPP流管理.
P: <stream:error> <connection-timeout xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
- 互操作性备注: RFC 3920指出,如果在一段时间内对端在某个流上没有产生任何流量,则使用<connection-timeout/>流错误(4.9.3.4). 那种行为已经不推荐了; 替代办法是, 只在已连接客户端或对端服务器不响应该流发送的数据时才使用该错误.
host-gone
在初始化流头中提供的'to'属性的值对应的完整合法域名(FQDN)不再由接收方实体提供服务.
(以下例子中, 当连接到"im.example.com"服务器时,对端指定了一个'to'地址"foo.im.example.com", 但是这个服务器不再为那个地址提供服务了.)
P: <?xml version='1.0'?> <stream:stream from='example.net' to='foo.im.example.com' version='1.0' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='g4qSvGvBxJ+xeAd7QKezOQJFFlw=' to='example.net' version='1.0' xml:lang='en' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <host-gone xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
host-unknown
初始流头中提供的'to'属性的值不对应接收方实体所服务的合法域名(FQDN).
(在下例中, 对端连接到"im.example.com"服务器的时候指定了一个'to'地址"example.org", 但是该服务器不知道这个地址.)
P: <?xml version='1.0'?> <stream:stream from='example.net' to='example.org' version='1.0' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='g4qSvGvBxJ+xeAd7QKezOQJFFlw=' to='example.net' version='1.0' xml:lang='en' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <host-unknown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
improper-addressing
在两服务器之间发送的节缺少'to'或'from'属性, 这个'from'或'to'属性没有值, 或它的值违反了XMPP地址XMPP‑ADDR的规则 .
(在下例中, 对端在服务器-服务器流中发送了一个不包含'to'地址的节.)
P: <message from='juliet@im.example.com'> <body>Wherefore art thou?</body> </message> S: <stream:error> <improper-addressing xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
internal-server-error
服务器配置错误或其他内部错误导致它无法服务于该流.
S: <stream:error> <internal-server-error xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
invalid-from
当 (1) 在两个服务器使用SASL或服务器回拨, 或 (2) 在客户端和服务器通过SASL验证和资源绑定的时候,协商时的'from'属性提供的数据不匹配已授权的JID或合法的域名.
(在下例中, 一个仅被授权为"example.net"的对端尝试以地址"example.org"发送节.)
P: <message from='romeo@example.org' to='juliet@im.example.com'> <body>Neither, fair saint, if either thee dislike.</body> </message> S: <stream:error> <invalid-from xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
invalid-namespace
流命名空间的名字不是"http://etherx.jabber.org/streams" (见 11.2) 或不支持把内容命名空间声明为缺省命名空间 (例如, 不同于"jabber:client"或"jabber:server").
(下例中, 客户端为流指定了一个命名空间'http://wrong.namespace.example.org/'.)
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://wrong.namespace.example.org/'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <invalid-namespace xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
invalid-xml
实体通过该流发送了非法的XML到一个执行验证的服务器上(见 11.4).
(下例中, 对端尝试发送一个类型为"subscribe"的IQ节, 但是XML schema没有这个'类型'的属性.)
P: <iq from='example.net' id='l3b1vs75' to='im.example.com' type='subscribe'> <ping xmlns='urn:xmpp:ping'/> </iq> S: <stream:error> <invalid-xml xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
not-authorized
实体尝试在流被验证之前发送XML节或其他出站数据, 或没有被授权执行一个和流协商有关的动作; 接收方实体在发送流错误之前不能(MUST NOT)处理这些数据.
(下例中, 客户端尝试在被服务器验证前发送XML节.)
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> C: <message to='romeo@example.net'> <body>Wherefore art thou?</body> </message> S: <stream:error> <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
not-well-formed
初始化实体发送了违反XML或XML‑NAMES的"良好格式"规则的XML.
(下例中, 客户端发送一个命名空间格式错误的XMPP消息.)
C: <message> <foo:body>What is this foo?</foo:body> </message> S: <stream:error> <not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
- 互操作性备注: 在RFC 3920中, 这个错误条件的名字是 "xml-not-well-formed" 而不是 "not-well-formed". 改名是因为元素名 <xml-not-well-formed/> 违反了XML第三章的约束 "以 (('X'|'x')('M'|'m')('L'|'l')) 打头的名字是保留给本协议的这个或下个版本的标准化用的".
policy-violation
实体违反一些本地服务策略 (例如, 一个节超出了配置的大小限制); 服务器可以选择在 <text/> 元素里或在一个应用特有的条件元素中指定这个策略.
(下例中, 客户端发送了一个据服务器的本地服务策略看来过大的XMPP消息.)
C: <message to='juliet@im.example.com' id='foo'> <body>[ ... the-emacs-manual ... ]</body> </message> S: <stream:error> <policy-violation xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> <stanza-too-big xmlns='urn:xmpp:errors'/> </stream:error> S: </stream:stream>
remote-connection-failed
服务器不能正确地连接到一个需要验证或授权的远程实体 (例如, 在和服务器回拨XEP-0220相关的特定场景); 当发生这个错误的是XMPP服务提供商有管理员权限的域时候,这个条件不被使用, 那种情况下更适合使用 <internal-server-error/> 条件.
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <remote-connection-failed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
reset
服务器在下列情况下关闭流,如提供了新的流特性(特别是关系安全的), 如为流建立安全上下文的密钥或者证书过期或在流的声明周期中被收回([[RFC6120#检查长连接流的证书|13.7.2.3), 如TLS序列号已经封装了(5.3.5), 等等. reset适用于流以及该流(例如, 通过TLS和 SASL)建立的的任何安全上下文, 这意味着新的流的加密和验证需要再次协商(例如, 不能使用TLS会话恢复了).
S: <stream:error> <reset xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
resource-constraint
服务器缺乏必要系统资源来服务于这个流.
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
restricted-xml
实体尝试发送受限的XML特性,例如 注释, 处理指令, DTD子集, 或XML实体参考(见 11.1).
(下例中, 客户端发送一个包含XML注释的XMPP消息.)
C: <message to='juliet@im.example.com'> <!--<subject/>--> <body>This message has no subject.</body> </message> S: <stream:error> <restricted-xml xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
see-other-host
服务器将不提供服务给初始化实体,但是重定向到同一个服务提供商管理控制下的另一台主机. 服务器返回的 <see-other-host/> 元素的XML字符串数据必须指定用来连接的替代的合法域名(FQDN)或IP地址, 它必须是一个合法的域部分或一个域部分加上一个端口号(通过"域名:端口号"中的':'字符来区分). 如果域部分和源域相同,或和派生域相同, 或解析出来的IPv4或IPv6地址和初始化实体原先连接的地址相同(只是端口号不同), 那么初始化实体应该简单地重新连接那个地址. (IPv6地址的格式必须遵循IPv6‑ADDR, 它包括如 URI 所定义的,把IPv6地址封闭在方括号'[' 和 ']'里.) 否则, 初始化实体必须解析<see-other-host/>元素中指定的合格域名(FQDN),如 解析合格域名3.2所述.
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <see-other-host xmlns='urn:ietf:params:xml:ns:xmpp-streams'> [2001:41D0:1:A49b::1]:9222 </see-other-host> </stream:error> </stream:stream>
当协商一个已经被重定向的流时, 初始化实体必须应用和它应用于初次连接时相同的策略 (例如, 一个必须使用TLS的策略), 必须在初始化流头中指定相同的'to'地址, 而且必须使用和初次尝试连接时相同的参考标识符来检查新地址的标识符 (符合 TLS‑CERTS). 即使接收方在流的保密和信任关系建立之前返回一个<see-other-host/>错误(从而产生 拒绝服务 攻击的可能性), 事实上初始化实体需要基于相同的参考标识符来检查XMPP服务的标识符,这意味着初始化实体将不会连接到一个恶意的实体. 为了避免 拒绝服务 攻击, (a) 在流的保密和信任关系由TLS或相当的安全层(例如 SASL GSSAPI 机制)保护起来立之前, 接收方实体不应该以<see-other-host/>流错误关闭流, 并且 (b) 只有当它已经被接收方实体验证了, 接收方才可以有一个下述的重定向策略. 另外, 初始化实体在特定数量的成功重定向之后应该放弃尝试连接(例如, 最少2次但不超过5次).
system-shutdown
服务器正在关闭且所有活跃的流正在被关闭.
S: <stream:error> <system-shutdown xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
undefined-condition
这个错误条件不是预定义的条件列表中的一个; 这个错误应该不被使用,除非结合一个应用特有的条件.
S: <stream:error> <undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> <app-error xmlns='http://example.org/ns'/> </stream:error> </stream:stream>
unsupported-encoding
初始化实体给流使用的编码不被服务器支持(见 11.6) 或没有正确地对流进行编码 (例如, 违反了 UTF‑8 编码规则).
(下例中, 客户端试图使用UTF-16编码而不是UTF-8.)
C: <?xml version='1.0' encoding='UTF-16'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <unsupported-encoding xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
unsupported-feature
接收方实体声明了一个 强制协商 的流特性,但是初始化实体不支持它, 并且提不出其他等价于不支持特性的 强制协商 特性.
(下例中, 接收方实体要求一个example特性, 但是初始化实体不支持这个特性.)
Rs: <stream:features> <example xmlns='urn:xmpp:example'> <required/> </example> </stream:features> I: <stream:error> <unsupported-feature xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
unsupported-stanza-type
初始化实体发送的流的顶级子元素不被服务器支持, 要么是因为接收方实体不理解命名空间要么因为接收方实体不理解适用的命名空间的元素名(可能是声明为缺省命名空间的内容命名空间).
(下例中, 客户端尝试发送一个由'jabber:client'命名空间限定的顶级子元素 <pubsub/> , 但是那个命名空间的schema没有定义这个元素.)
C: <pubsub xmlns='jabber:client'> <publish node='princely_musings'> <item id='ae890ac52d0df67ed7cfdf51b644e901'> <entry xmlns='http://www.w3.org/2005/Atom'> <title>Soliloquy</title> <summary> To be, or not to be: that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, And by opposing end them? </summary> <link rel='alternate' type='text/html' href='http://denmark.example/2003/12/13/atom03'/> <id>tag:denmark.example,2003:entry-32397</id> <published>2003-12-13T18:30:02Z</published> <updated>2003-12-13T18:30:02Z</updated> </entry> </item> </publish> </pubsub> S: <stream:error> <unsupported-stanza-type xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
unsupported-version
由初始化实体在流头中提供的'version'属性指定的XMPP版本不被服务器支持.
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='11.0' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> <stream:error> <unsupported-version xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
应用特有的条件
大家知道, 应用可以在错误元素中包含一个适当的命名空间子元素来提供应用特有的错误信息. 应用特有的元素应该补充或进一步限定一个已定义的元素. 因此, <error/> 元素将包含两个或三个子元素.
C: <message> <body> My keyboard layout is: QWERTYUIOP{}| ASDFGHJKL:" ZXCVBNM<>? </body> </message> S: <stream:error> <not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> <text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-streams'> Some special application diagnostic information! </text> <escape-your-data xmlns='http://example.org/ns'/> </stream:error> </stream:stream>
简化的流示例
这一仗包含两个客户端和服务器之间基于流的连接的高度简化的例子; 这些例子的目的是说明迄今为止介绍的那些概念, 但是读者需要注意这些例子省略了一些细节 (更完整的例子见 第9章).
一个基本的连接:
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> [ ... stream negotiation ... ] C: <message from='juliet@im.example.com/balcony' to='romeo@example.net' xml:lang='en'> <body>Art thou not Romeo, and a Montague?</body> </message> S: <message from='romeo@example.net/orchard' to='juliet@im.example.com/balcony' xml:lang='en'> <body>Neither, fair saint, if either thee dislike.</body> </message> C: </stream:stream> S: </stream:stream>
连接坏了:
C: <?xml version='1.0'?> <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <?xml version='1.0'?> <stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> [ ... stream negotiation ... ] C: <message from='juliet@im.example.com/balcony' to='romeo@example.net' xml:lang='en'> <body>No closing tag! </message> S: <stream:error> <not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
更多详细的例子请到第9章 .
STARTTLS协商
STARTTLS基础
XMPP包含了一个方法来保护流的安全使其免于被篡改和窃听. 这个通道加密的方法使用传输层安全 TLS 协议, 特别是"STARTTLS"扩展,这个扩展是以 USINGTLS中描述的 IMAP, POP3, 和 ACAP 协议中的类似扩展为蓝本的. STARTTLS扩展的XML命名空间是 'urn:ietf:params:xml:ns:xmpp-tls'.
支持
在XMPP客户端和服务器的实现中必须支持STARTTLS. 一个给定布署的管理员可以指定 客户端-服务器通讯 和/或 服务器-服务器通讯 中TLS是强制协商的. 一个初始化实体应该在开始SASL验证之前使用TLS保护和接收方之间的流的安全.
流协商规则
强制协商
如果接收方实体只声明了STARTTLS特性或接收方实体包含了5.4.1所述的<required/>子元素, 双方必须确保TLS是强制协商的. 如果TLS是强制协商的, 在流协商过程的初始化阶段接收方实体应该不(SHOULD NOT)声明支持任何STARTTLS以外的流特性, 因为在XMPP的层顺序中更多流特性可能依赖TLS的预先协商 (例如, 由接收方实体提供的特定的SASL机制将依赖于TLS是否完成协商).
重启
在TLS协商之后, 双方必须重启这个流.
数据格式
当STARTTLS协商时, 实体们不能(MUST NOT)在XML元素之间发送任何空格符号 (即, 由初始化实体发送的从'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的顶级<starttls/>元素的最后的字符, 到由接收方实体发送的'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的顶级<proceed/>元素的最后的字符). 这个禁令帮助确保适当的安全字节精度. 任何出现在本文提供的STARTTLS例子中的空格只是为了提高可读性.
TLS和SASL协商的顺序
如果初始化实体选择使用TLS, STARTTLS协商必须在SASL协商之前完成; 这个协商顺序对于帮助保护SASL协商期间发送的验证信息是必要的, 同时尽可能使用基于预先的TLS协商中提供的证书(或其他证件)的SASL EXTERNAL机制.
TLS重协商
TLS协议允许双方在一个 受TLS保护 的通道里初始化一个新的握手来建立新的加密参数(见 TLS‑NEG). 最常提及的案例如下:
- 刷新密钥
- 如TLS的6.1节所述封装TLS序列号.
- 在受保护的通道上先完成服务器验证再完成客户端验证以保护客户端证书.
因为在XMPP中建立一个流的代价相对低廉, 对于前面两个案例推荐使用XMPP流重置(如 4.9.3.16) 而不是执行TLS重协商.
第三个案例在TLS客户端(也可能是一个XMPP服务器)递交TLS证书给服务器时提高了安全特性. 如果和一个未验证的TLS服务器交换这类证书可能泄露隐私信息, 先完成让TLS客户端验证TLS服务器的TLS协商,再完成让TLS服务器验证TLS客户端的TLS协商,是适当的. 然而, 这个案例极为罕见,因为由一个扮演TLS客户端角色的XMPP服务器或XMPP客户端对外展现的证书几乎总是公开的(即, PKIX 证书), 所以在验证作为TLS服务器的XMPP服务器之前提供那些证书通常将不会泄露隐私信息.
作为结果, 鼓励实现者在他们的软件中支持TLS重协商之前小心地权衡它的开销和好处, 不鼓励扮演TLS客户端的XMPP实体尝试TLS重协商,除非已知要在TLS协商中发送的证书(或其他证件信息)是私有的.
对TLS重协商的支持是严格可选的. 然而, 支持TLS重协商的实现们必须实现和使用 TLS重协商扩展 TLS‑NEG.
如果一个不支持TLS重协商的实体察觉到一个重协商尝试, 那么它必须立刻关闭相关的TCP连接而不要返回任何流错误(因为这个违规可能发生在TLS层, 而不是XMPP层, 详见 13.3).
如果一个支持TLS重协商的实体察觉到一个未使用 TLS重协商扩展 TLS‑NEG 的TLS重协商尝试 , 那么它必须立刻关闭相关的TCP连接而不要返回任何流错误(因为这个违规可能发生在TLS层, 而不是XMPP层, 详见 13.3).
TLS扩展
一个流的双方可以在它自己的TLS协商时包含任何TLS扩展. 这是TLS层的事情, 不是XMPP层.
过程
流头和流特性交换
初始化实体如第三章所述解析接收实体的合格域名(FQDN), 打开一个到解析的IP地址和声明的端口的TCP连接, 并发送一个初始化流头给接收方流头.
I: <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
接收方实体必须通过初始化实体打开的那个TCP连接发送一个应答流头给初始化实体.
R: <stream:stream from='im.example.com' id='t7AMCin9zjMNwQKDnplntZPIDEI=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
接着接收方实体必须发送流特性给初始化实体. 如果接收方实体支持TLS, 流特性必须包含一个支持STARTTLS协商的声明, 即, 一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<starttls/>元素.
如果接收方实体认为STARTTLS协商是强制协商的, <starttls/>元素必须包含一个空的<required/>子元素.
R: <stream:features> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'> <required/> </starttls> </stream:features>
STARTTLS协商的初始化
STARTTLS命令
为了开始STARTTLS协商, 初始化实体发出STARTTLS指令(即, 一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<starttls/>元素)来指示接收方实体它希望开始一次STARTTLS协商以保护流.
I: <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
接收方实体必须以由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<proceed/>元素(继续进行的情况下)或<failure/>元素(失败的情况下)回复.
失败的情况
如果发生失败的情况, 接收方实体必须返回一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<failure/>元素, 关闭这个XML流, 并且终止当前的TCP连接.
R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> R: </stream:stream>
导致失败的情况包含但不限于以下几种:
- 初始化实体发送了一个异常的STARTTLS命令.
- 接收方实体在它的流特性中不提供STARTTLS特性 .
- 接收方实体因为内部错误而无法完成STARTTLS协商.
- 提示性备注: STARTTLS失败不会由TLS错误触发,例如 坏证书(bad_certificate)或 握手失败(handshake_failure), 它是由TLS协商本身生成和处理的,参见TLS.
如果发生了失败的情况, 初始化实体可以尝试重连,参见 3.3.
继续进行的情况
如果发生继续进行的情况, 接收方实体必须返回一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<proceed/>元素.
R: <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
接收方实体在发送了<proceed/>元素的关闭字符'>'之后必须认为TLS协商已经立刻开始了. 初始化实体在从接收方实体接收到<proceed/>元素的关闭字符'>'之后必须认为TLS协商已经立刻开始了.
实体现在继续进行TLS协商,如下一节所述.
TLS协商
规则
为了在TCP连接上完成TLS协商, 实体们必须跟随以下定义于TLS的过程 .
以下规则适用于:
- 实体们在TLS协商完成之前不能(MUST NOT)发送任何其他XML数据.
- 当使用定义于13.8的任何强制实现(MTI)的密码组时, 接收方实体必须出示一个证书.
- 所以证书相互验证是有可能的, 接收方实体应该发送一个证书请求给初始化实体, 而初始化实体应该发送一个证书给接收方实体(但是由于隐私的原因可能选择在接收方实体已经被初始化实体验证之后才发送自己的证书).
- 接收方实体应该基于包含在初始化流头中的'to'属性中的域部分选择哪个证书来展示(本质上, 这个域部分功能上等同于服务器名称指示 定义于TLS‑EXT).
- 为了确定TLS协商是否成功, 初始化实体必须尝试根据13.7.2定义的证书验证程序来验证接收方实体的证书.
- 如果初始化实体出示了一个证书, 接收方实体也必须尝试根据13.7.2定义的证书验证程序来验证初始化实体的证书.
- 随着TLS协商成功, 所有双方传送的更多的数据必须被协商的算法,密钥和秘密来保护(即, 加密, 完整性保护, 或都依赖于使用的密码组).
- 安全警告: 关于13.8提到的密码组必须被TLS所支持; 自然的, 其他密码组也可以被支持.
TLS失败
如果TLS协商结果是失败, 接收方实体必须终止该TCP连接.
在终止该TCP连接之前,接收方实体不能(MUST NOT)发送关闭标签</stream>(因为失败可能发生在TLS层, 而不是XMPP层,参见13.3所述).
初始化实体可以如3.3所述尝试重连, 尝试使用或不使用TLS协商(依照本地服务策略, 用户配置的偏好, 等等).
TLS成功
如果TLS协商是成功的, 那么实体们必须继续如下步骤.
- 1. 初始化实体必须忽略TLS生效之前在TCP上的不安全情况下从接收方实体收到的任何信息(例如, 接收方实体的'from'地址或从接收方实体收到的流ID以及流特性).
- 2. 接收方实体必须忽略TLS生效之前在TCP上的不安全情况下从初始化实体收到的任何信息(例如, 初始化实体的'from'地址).
- 3. 初始化实体必须通过加密的连接发送一个新的初始化流头给接收方实体(如4.3.3定义的, 在发送新的初始化流头之前初始化实体必须发送一个关闭标签</stream>, 因为接收方实体和初始化实体必须确定旧的流在TLS协商成功之后被替代了).
I: <stream:stream from='juliet@im.example.com' to='im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
- 4. 接收方实体必须通过加密连接以一个新的应答流头来应答(为此它必须生成一个新的流ID而不是重用旧的流ID).
R: <stream:stream from='im.example.com' id='vgKi/bkYME8OAj4rlXMkpucAqe4=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
- 5. 接收方实体也必须发送流特性给初始化实体, 不能(MUST NOT)包含STARTTLS特性但是应该包含SASL流特性,如第六章(特别是6.4.1中关于为什么不在这里提供SASL流特性的新的原因).
R: <stream:features> <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <mechanism>EXTERNAL</mechanism> <mechanism>SCRAM-SHA-1-PLUS</mechanism> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>PLAIN</mechanism> </mechanisms> </stream:features>
SASL协商
SASL基础
XMPP包含了一个某种意义上XMPP特有的简单验证和安全层协议(见SASL)用于验证一个流. SASL提供一个一般化的方法来给基于连接的协议添加验证支持, 而XMPP遵照SASL的解析要求来使用SASL的XML命名空间解析. SASL扩展的XML命名空间名称是'urn:ietf:params:xml:ns:xmpp-sasl'.
支持
XMPP客户端和服务器必须支持SASL协商.
流协商规则
强制协商
一个流双方bxu确认SASL是强制协商的.
重启
在SASL协商之后, 双方必须重启该流.
机制推荐
任何将要扮演SASL客户端或SASL服务器的实体必须对于该客户端或该服务器维护一个它推荐的SASL机制的有序列表, 这个列表的顺序是根据本地策略或用户配置来的(它的顺序应该是根据验证能力越强排在越靠前). 初始化实体必须独立于接收方实体的推荐顺序来维护它自己的推荐顺序. 客户端必须以它自己的推荐顺序来尝试SASL机制. 例如, 如果服务器提供的顺序列表是"PLAIN SCRAM-SHA-1 GSSAPI" 或 "SCRAM-SHA-1 GSSAPI PLAIN" 而客户端的顺序列表是 "GSSAPI SCRAM-SHA-1", 客户端必须首先尝试 GSSAPI 然后尝试 SCRAM-SHA-1 而不能(MUST NOT)尝试 PLAIN (因为 PLAIN 不在它的列表中).