- 浏览: 11392588 次
文章分类
最新评论
-
wahahachuang8:
我觉得这种东西自己开发太麻烦了,就别自己捣鼓了,找个第三方,方 ...
WebSocket和node.js -
xhpscdx:
写的这么详细,全面,对架构师的工作职责,个人能力都进行了梳理。 ...
架构师之路---王泽宾谈架构师的职责 -
xgbzsc:
是http://www.haoservice.com 吗?
android WIFI定位 -
lehehe:
http://www.haoservice.com/docs/ ...
android WIFI定位 -
lehehe:
http://www.haoservice.com/docs/ ...
android WIFI定位
RFC6120——可扩展的消息和出席信息协议 (XMPP): 核心协议
原文:http://wiki.jabbercn.org/RFC6120
RFC6120
"本文的英文原文来自RFC 6120
互联网工程任务组(IETF) | P. Saint-Andre |
申请讨论: 6120 | Cisco |
取代: 3920 | 2011年3月 |
类别: 标准跟踪 | |
ISSN: 2070-1721 |
摘要
本文的状态
版权通知
序论
概述
可扩展的消息和出席信息协议(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流 (通过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发送的, 发起方实体在尝试打开一个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节。这些术语的定义如下。
有三种节: 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/> 元素可以包含一个子元素,多个子元素,或者为空.
如果一个特殊的流特性是或者可以是强制协商的, 那个特性的定义需要做以下几件事之一:
- 声明这个特性总是强制协商的 (例如, 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节,只要双方都维持着这个流.
在流协商完成之前,发起方实体不能(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'属性.
流程图
我们在接下来的非规范性的流程图里为流协商过程总结前述的规则, 以发起方实体的视角来展示.
+---------------------+ | 打开TCP连接 | +---------------------+ | v +---------------+ | 发送发起流头 |<-------------------------+ | | ^ +---------------+ | | | v | +------------------+ | | 接收应答流头 | | | | | +------------------+ | | | v | +----------------+ | | 接收流特性 | | +------------------>| | | ^ {可选的} +----------------+ | | | | | v | | +<-----------------+ | | | | | {空?} ----> {全部自愿? } ----> {一些强制性? } | | | 否 | 否 | | | | 是 | 是 | 是 | | | v v | | | +--------------------+ +-----------------+ | | | | 可以协商或不协商 | |必须协商一个特性 | | | | | 任何一个 | | | | | | +--------------------+ +-----------------+ | | v | | | | +---------+ v | | | | 完成 |<----- {协商? } | | | +---------+ 否 | | | | 是 | | | | v v | | +--------->+<---------+ | | | | | v | +<-------------------------- {强制性重新开始? } ------------>+ 否 是
关闭流
从一个实体到另一个实体的XML流可以在任何时候关闭, 可以是因为发生了一个特定的流错误 (4.9章)) 也可以没有任何错误(例如, 当客户端简单地结束它的会话).
通过发送一个关闭的</stream>标签来关闭流.
E: </stream:stream>
如果双方正在使用一个TCP连接上两个流, 或者两个TCP连接上两个流的通信方式, 发送关闭流标签的实体必须按以下步骤进行:
- 在终止底层的TCP连接之前等待另一方也关闭它的出站流; 这让另一方有机会在终止TCP连接之前发完任何到正在关闭的实体的出站数据.
- 避免通过它的出站流发送任何更多的数据到另一方, 但是继续处理从另一方实体已经接收到的数据(并且, 如果必要, 处理这些数据).
- 如果另一方没有在一个合理的时间(这里 "合理" 的定义取决于实现或布署)内发送它的关闭流标签则认为两个流都失效了.
- 从另一方接收到一个反向的关闭流标签之后, 或在等待一段合理的时间之后未收到应答, 终止底层的TCP连接.
如果双方通过多重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连接死掉。
- 尽管当底层的TCP连接仍然是激活的时候,XML流被中断了。
- 对等端是空闲的,只是没有通过其连接的XML流发送XMPP信息到该实体。
死连接
中断的流
空闲对端
检查方法的使用
建议实现人员支持任何他们认为合适的流检查和连接检查方法, 但是要小心衡量这些方法对网络的冲击和及时发现中断的流和死TCP连接得到的好处. 使用的任何特定检查方法的时间间隔对本地服务策略都是一个大事情,并且严重依赖于网络环境和给定布署和连接类型的使用场景. 在撰写本文的时候, 建议任何这类检查的执行不要超过每5分钟一次, 并且理想的情况是, 这类检查由客户端发起而不是服务器来发起. 鼓励那些实现XMPP软件和布署XMPP服务的人对适当的流检查和连接检查时间间隔寻求其他的意见, 特别是当使用了功率受限的设备的时候 (例如, 在移动环境).
流属性
根<stream/>元素的属性定义在以下章节.
from
对客户机-服务器通信的发起流头而言,'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'>
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'>
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节将描述。
to
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'>
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'>
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'>
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").
- 如果接收方实体根据定义于LANGMATCH3.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
- 初始化实体必须(MUST)在初始化流头中设置‘version’属性的值为它所支持的最高版本号(如:如果它支持的最高版本号是本文中定义的,它必须(MUST)设置该值为“1.0”)。
- 接收实体必须(MUST)在应答流头中设置‘version’属性的值或者为初始化实体提供的值,或者为接收实体所支持的最高版本号,以较低者为准。接收实体必须(MUST)执行主要和次要版本号的数值比较,而不是关于“<主版本>.<次版本>(‘<major>.<minor>’)”的字符串匹配。
- 如果在应答流头中包含的版本号至少在主版本号上比在初始化流头中包含的版本号低,那么较新版本的实体不能与旧版本的实体互操作,初始化实体应该(SHOULD)通过发送一个包含<unsupported-version/>元素的流错误(第4.9.3.25节)来关闭流。
- 如果任何一个实体接收到一个没有‘version’属性的流头,该实体必须(MUST)认为其他实体支持的版本是“0.9”,并且不应该在响应流头中包含‘version’属性。
流属性总结
+----------+--------------------------+-------------------------+ | | 初始实体 到 接收实体 | 接收实体 到 初始实体 | +----------+--------------------------+-------------------------+ | 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'命名空间限定的节.
其他命名空间
参与一个流的双方都可以发送非内容命名空间和流命名空间限定的数据. 例如, 和TLS协商以及SASL协商相关的数据如何交换, 以及类似 流管理XEP‑0198和服务器回拨XEP‑0220的XMPP扩展.
无论如何, 一个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).
命名空间声明和前缀
因为内容命名空间不同于流命名空间, 如果把一个内容命名空间声明为缺省命名空间,那么以下论断为真:
- 流头需要包含一个同时用于内容命名空间和流命名空间的命名空间声明.
- 流命名空间的声明需要包含用于流命名空间的前缀.
一个实现不能(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>
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>
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>
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协商.
如果发生了失败的情况, 初始化实体可以尝试重连,参见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协商成功, 所有双方传送的更多的数据必须被协商的算法,密钥和秘密来保护(即, 加密, 完整性保护, 或都依赖于使用的密码组).
TLS失败
如果TLS协商结果是失败, 接收方实体必须终止该TCP连接.
在终止该TCP连接之前,接收方实体不能(MUST NOT)发送关闭标签</stream>(因为失败可能发生在TLS层, 而不是XMPP层,参见13.3所述).
初始化实体可以如3.3所述尝试重连, 尝试使用或不使用TLS协商(依照本地服务策略, 用户配置的偏好, 等等).
TLS成功
如果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'>
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'>
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 不在它的列表中).
机制提供
如果接收方实体在它接受特定的SASL机制之前确定TLS协商是强制协商, 它不能(MUST NOT)在完成TLS协商之前在它的可用SASL机制列表中声明那个机制.
如果发生以下两种情况,接收方实体应该提供 SASL EXTERNAL 机制,:
- 当初始化实体在TLS协商中出示了一个证书,这个证书被接收方实体接受了,接收方根据本地服务策略把它用于强身份验证(例如, 因为证书没有过期,没有撤销,并且被锚定到一个接收方实体信任的root账户).
- 接收方实体期望初始化实体能够验证和授权这个证书所提供的身份; 在服务器-服务器流的情形下, 接收方实体可能有这样一个预期,因为初始化实体的证书所展示的DNS域名和初始化流头中相应的'from'属性是匹配的, 这里使用TLS‑CERTS的匹配规则; 在客户端-服务器流的情形下, 接收方实体可能有这样一个预期,是因为在初始化实体的证书中展示的纯JID和在这个服务器上注册的一个用户帐号匹配,或者因为其他包含在初始化实体证书中的信息和被允许使用该服务器访问XMPP网络的某个实体相匹配.
无论如何, 接收方实体在其他情况下也一样可以提供 SASL EXTERNAL 机制.
当接收方实体提供 SASL EXTERNAL 机制, 接收方实体应该首先列出它提供的SASL机制的 EXTERNAL 机制列表,而初始化实体应该尝试首先使用EXTERNAL机制来进行SASL协商(这个选择往往会增加双方相互进行证书验证的可能性).
13.8定义了必须支持的SASL机制; 自然的, 也一样可以支持其他的SASL机制.
数据格式
以下数据格式规则适用于SASL协商:
- 当SASL协商时, 实体不能(MUST NOT)在XML元素之间发送任何空格符号(即, 从初始化实体发送的'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<auth/>顶级元素的最后一个字符, 到接收方实体发送的'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<success/>顶级元素的最后一个字符). 这个禁令帮助确保正确的安全层字节精确度. 本文中任何在SASL例子中出现的这类空格只是为了增加可读性.
- 任何包含在XML元素中的XML字符串数据必须使用base 64编码, 这里的编码要坚持BASE64第四章的定义,并且填充位设为零.
- 作为附录A.4下的XML schema中正式指定的 'urn:ietf:params:xml:ns:xmpp-sasl' 命名空间, 接收方实体可以在<mechanisms/>元素中包含一个或多个应用特有的子元素来提供初始化实体使用提供的一个或多个机制进行成功的SASL协商而可能需要的信息; 无论如何, 所有这类元素的语法和语义超出了本协议的范围(见XEP‑0233的例子).
安全层
涉及安全层协商的SASL协商成功后, 初始化实体和接收方实体都必须丢弃任何应用层状态(即, 来自XMPP层的状态, 不包括来自TLS协商或SASL协商的状态).
简单用户名
一些SASL机制(例如, CRAM-MD5, DIGEST-MD5, 和 SCRAM) 指定了在这些机制的上下文中使用的验证身份是一个"简单用户名" (见SASL的第二章以及SASLPREP). 在任何特定的机制或部署中简单用户名的准确格式都是一个本地事务, 并且简单用户名不需要映射到一个应用身份例如JID或JID部件(例如, 本地部分). 无论如何, 在缺乏由服务器提供的本地信息的情况下, 一个XMPP客户端应该假定一个SASL机制的验证身份是等于该用户的JID的本地部分的简单用户名.
授权身份
授权身份是一个由初始化实体提供的可选的身份,用来定义它扮演的身份(见SASL第二章). 在客户端-服务器流中, 它大部分被管理员用于代表另一个用户来执行一些管理任务, 而在服务器-服务器流它大部分被用于在XMPP服务上指定一个特定的附加服务(例如, 一个多用户聊天服务器conference.example.com寄宿在example.com的XMPP服务上). 如果初始化实体希望代表另一个实体并且所选择的SASL机制支持授权身份的传输, 该初始化实体必须在SASL协商时提供一个授权身份. 如果初始化实体不希望代表另一个实体的身份, 它不能(MUST NOT)提供授权身份.
在客户端-服务器通讯的情况下, 授权身份的值必须是一个纯JID(<本地部分@域部分>) 而不是一个全JID(<本地部分@域部分/资源部分>).
在服务器-服务器通讯的情况下, 授权身份的值必须且只能是一个域部分(<域部分>).
如果初始化实体在SASL协商时提供一个授权身份, 接收方实体负责验证初始化实体是否事实上被允许承担指定的授权身份; 如果不是, 接收方实体必须返回一个<invalid-authzid/> SASL错误,如6.5.6所述.
领域
在以特定SASL机制协商的时候接受方实体可以包含一个领域(例如, GSSAPI 和 DIGEST-MD5 机制都允许验证交换的信息中包含领域, 不过其他方式, 如 EXTERNAL, SCRAM, 和 PLAIN 机制不支持这个特性). 如果接受方实体不以一个领域来通讯, 则初始化实体不能(MUST NOT)假定任何领域的存在. 领域必须只被用于验证的目的; 特别是, 初始化实体不能(MUST NOT)尝试从接受方实体提供的领域信息来派生出一个XMPP域部分.
回合
SASL规定,一个使用中的协议(例如XMPP)可以定义两个方法,这样协议可以节省批准SASL机制的回合:
- 当SASL客户端(XMPP "初始化实体") 请求一个验证交换时, 如果使用了适当的SASL机制,它可以在它的请求中包含 "初始化应答" 数据. 在XMPP中, 要实现这一点,就把初始化应答作为XML字符串数据包含在<auth/>元素中.
- 在验证交换的结尾, 如果使用了适当的SASL机制,SASL服务器(XMPP "接受方实体") 可以包含 "成功附带的额外数据". 在XMPP中, 要实现这一点,就把额外数据作为XML字符串数据包含在<success/>元素中.
为了协议的效率, 要求客户端和服务器必须支持这些方法并且建议使用它们; 无论如何, 客户端和服务器也必须支持低效的模式.
过程
SASL协商过程如下.
流头和流特性交换
如果SASL协商紧跟在成功的STARTTLS协商之后, 那么SASL协商发生在已经协商过的受保护的流上. 否则, 初始化实体如第三章所述解析接受方实体的完整域名(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'>
当接受方实体处理来自初始化实体的初始化流的时候, 它必须发送一个应答流头给初始化实体(为此它必须生成一个唯一的流ID. 如果TLS协商已经成功, 那么这个流ID必须不同于TLS协商成功之前发送的那个流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'>
接受方实体也必须发送流特性给初始化实体. 流特性应该包含一个声明用来支持SASL协商, 即, 一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<mechanisms/>元素. 典型的,只有三种情况下对SASL协商的支持不需要在这里声明:
- TLS协商需要在提供SASL之前发生(即, TLS是必需的并且接受方实体已经在接收到的这次连接尝试的初次的初始化流头中应答过了).
- SASL协商不可能发生在一个 服务器-服务器 连接中(即, 初始化服务器未提供一个证书以进行验证并且因而接受方实体回滚到使用服务器回拨协议XEP‑0220进行弱身份验证).
- SASL已经协商过了(即, 接受方实体在成功进行SASL协商之后应答一个以流重启的方式发送的初始化流头).
<mechanisms/>元素必须为接受方实体提供给初始化实体的每个验证机制包含一个<mechanism/>子元素. 大家知道, 在XML中的<mechanism/>元素的顺序表示来自接受方实体的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协商, 初始化实体发送一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<auth/>元素并在'mechanism'属性包含一个适当的值, 从而开始使用特定的验证机制进行握手. 这个元素可以包含XML字符串数据(用SASL术语来说, 就是"初始化应答"),如果这个机制支持或必须要它的话. 如果初始化实体需要发送一个长度为零的初始化应答, 它必须以单个等号字符("=")来传输这个应答, 这表示那个应答是当前的但是不包含数据.
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AGp1bGlldAByMG0zMG15cjBtMzA=</auth>
如果初始化实体后来发送另一个<auth/>元素而正在进行的验证握手还没完成, 接收方实体必须丢弃正在进行的握手而必须为后来请求的SASL机制处理新的握手.
挑战-应答序列
如果必要, 接收方实体通过发送一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<challenge/>元素来挑战初始化实体; 这个元素可以包含XML字符串数据(它必须根据被初始化实体选择的SASL机制的定义来生成).
初始化实体通过发送一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<response/>元素来应答这个挑战; 这个元素可以包含XML字符串数据(它必须根据被初始化实体选择的SASL机制的定义来生成).
如果必要y, 接收方实体发送更多挑战而初始化实体发送更多应答.
这一系列的 挑战/应答 对 一直持续直到发生以下三件事情之一:
- 初始化实体退出这个验证机制的握手.
- 接收方实体报告握手失败.
- 接收方实体报告握手成功.
这些场景具体描述在接下来的章节.
放弃
初始化实体通过发送一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<abort/>元素放弃为这个验证机制所做的握手.
I: <abort xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
在接收到一个<abort/>元素之后, 接收方实体必须返回一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<failure/>元素并在其中包含一个<aborted/>子元素.
R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <aborted/> </failure>
SASL失败
接收方实体通过发送一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<failure/>元素来汇报这个验证机制握手失败(特定的失败原因必须放进<failure/>元素的适当子元素,如6.5定义的).
R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <not-authorized/> </failure>
为了选择适当的SASL机制, 接收方实体应该允许一个可配置的但是合理的重试次数(至少2次但不超过5次); 这让初始化实体能(例如, 一个终端用户客户端)容忍不正确的凭证(例如, 一个输错的密码)而不要强制重新连接(如果接收方实体立刻返回SASL失败并关闭流).
如果初始化实体对同一个SASL机制尝试了合理的重试次数并且都失败了, 它可以回滚到顺序列表中的下一个机制,只要发送一个新的<auth/>请求给接收方实体, 从而开始那个机制的新的握手. 如果所有握手都失败了并且在初始化实体支持和可接受的机制列表里没有剩余的机制了, 初始化实体应该简单地关闭这个流,如4.4所述(而不是等待这个流超时).
如果初始化实体超出了重试次数, 接收方实体必须以一个流错误关闭流, 它应该是<policy-violation/>(4.9.3.14), 不过一些现有的实现发送的是<not-authorized/>(4.9.3.12).
SASL成功
在确定SASL握手成功之前, 如果初始化实体在一个其保密和诚信得到TLS或同等的安全层(例如SASL GSSAPI机制)保护的初始化流头提供了一个'from'属性,那么接收方实体应该把这个验证身份结果关联到来自SASL协商的'from'地址; 如果这两个身份不匹配,那么接收方实体应该终止连接尝试(然而, 接收方实体可以有合法的理由不终止这个连接尝试, 例如, 因为它覆盖了一个连接的客户端的地址来纠正JID格式或根据终端用户的证书授予一个JID).
接收方实体通过发送一个由'urn:ietf:params:xml:ns:xmpp-sasl'命名空间限定的<success/>元素来汇报握手成功; 这个元素可以包含XML字符串数据(在SASL 属于中, 是"成功的附加数据"), 如果选择的SASL机制支持或者要求它. 如果接收方实体需要发送零长度的附加数据, 它必须传送一个单独的等号字符("=")数据.
R: <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
一旦接收到<success/>元素, 初始化实体必须在现有的TCP连接上发送一个新的初始化流头到接收方实体来初始化一个新的流(如4.3.3所述, 在发送新的初始化流头之前,初始化实体不能(MUST NOT)发送一个关闭</stream>标签, 因为接收方实体和初始化实体必须确定原始的流被替换成SASL协商成功之后的流).
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'>
一旦从初始化实体接收到新的初始化流头, 接收方实体必须发送一个新的流头给初始化实体来应答(为此它必须生成一个新的流ID而不是重用旧的流ID).
R: <stream:stream from='im.example.com' id='gPybzaOzBmaADgxKXu9UClbprp0=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>
接收方实体也必须发送流特性, 包含任何更多的可用特性或不包含特性(通过一个空的<features/>元素).
R: <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> </stream:features>
SASL错误
SASL错误的语法如下, 那些用方括号 '[' 和 ']' 括起来的XML数据是可选的.
<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <defined-condition/> [<text xml:lang='langcode'> OPTIONAL descriptive text </text>] </failure>
"defined-condition" 必须是在接下来的章节里的定义的SASL相关的错误条件之一. 然而, 因为将来可能定义额外的错误条件, 如果一个实体收到一个它不理解的SASL错误条件,那么它必须把这个未知的条件视为一个通用的验证错误, 即, 等同于 <not-authorized/> (6.5.10).
内含的<text/>元素是可选的, 并且可被用于提供关于这个错误条件的应用特有的信息, 这个信息可以显示给人看但只是作为已定义的条件的补充.
因为XMPP本身定义了一个SASL应用范本并且不期望有更多专门的XMPP应用建立在SASL之上, 所以SASL错误格式不会像在XML流(4.9.4)和XML节(8.3.4)的做法一样,为应用特有的错误提供扩展性.
aborted
接收方实体确认验证握手已经被初始化实体放弃; 对<abort/>元素发送应答.
I: <abort xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <aborted/> </failure>
account-disabled
初始化实体的帐号已经被暂时禁用; 对<auth/>元素或<response/>元素发送应答(可以包含或不包含初始化应答数据).
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AGp1bGlldAByMG0zMG15cjBtMzA=</auth> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <account-disabled/> <text xml:lang='en'>Call 212-555-1212 for assistance.</text> </failure>
credentials-expired
因为初始化实体提供的证书过期而验证失败; 对<response/>元素或<auth/>元素发送包含初始化应答数据的应答.
I: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> [ ... ] </response> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <credentials-expired/> </failure>
encryption-required
初始化实体请求的机制不能使用,出非当前的流的保密性和完整性收到保护(典型的是通过TLS); 对<auth/>元素发送应答(包含或不包含初始化应答数据).
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AGp1bGlldAByMG0zMG15cjBtMzA=</auth> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <encryption-required/> </failure>
incorrect-encoding
初始化实体提供的数据无法被处理,因为 base 64 编码不正确(例如, 因为编码没有遵循BASE64的第四章的定义); 对<response/>元素或<auth/>元素发送包含初始化应答数据的应答.
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'>[ ... ]</auth> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <incorrect-encoding/> </failure>
invalid-authzid
初始化实体提供的authzid是非法的, 要么因为它格式不正确要么因为初始化实体没有权限授权那个ID; 对<response/>元素或<auth/>元素发送包含初始化应答数据的应答.
I: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> [ ... ] </response> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <invalid-authzid/> </failure>
invalid-mechanism
初始化实体没有指定一个机制, 或请求的机制不被接收方实体支持; 对<auth/>元素发送应答.
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='CRAM-MD5'/> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <invalid-mechanism/> </failure>
malformed-request
请求是不良的(例如, <auth/>元素包含了初始化应答数据但是机制不允许这个, 或被发送的数据违反了指定的SASL机制的语法); 对<abort/>, <auth/>, <challenge/>, 或 <response/> 元素发送应答.
(下例中, <auth/>元素的XML字符串数据包含了多于255个UTF-8编码的Unicode字符,所以违反了定义于ANONYMOUS的SASL ANONYMOUS的"token"生产.)
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'>[ ... some-long-token ... ]</auth> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <malformed-request/> </failure>
mechanism-too-weak
初始化实体请求的机制弱于服务器策略允许初始化实体使用的机制; 对<auth/>元素发送应答(包含或不包含初始化应答数据).
I: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AGp1bGlldAByMG0zMG15cjBtMzA=</auth> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <mechanism-too-weak/> </failure>
not-authorized
验证失败,因为初始化实体没有提供正确的证书, 或因为发生了一些普通的验证失败而接收方实体不希望泄露失败原因的特定信息; 对<response/>元素或<auth/>元素发送包含初始化应答数据的应答.
I: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> [ ... ] </response> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <not-authorized/> </failure>
temporary-auth-failure
验证失败,因为接收方实体的临时性错误, 可以建议初始化实体晚点再试; 对<auth/>元素或<response/>元素发送应答.
I: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> [ ... ] </response> R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <temporary-auth-failure/> </failure>
SASL定义
SASL的范本需求里面要求使用中的协议定义必须提供以下信息.
资源绑定
原理
在客户端从一个服务器验证之后, 它必须绑定一个特定的资源到这个流,这样服务器才能正确地对客户端寻址. 就是说, 必须有一个XMPP资源关联到客户端的纯JID (<localpart@domainpart>), 所以在那个流上使用的地址是一个全JID,格式为<localpart@domainpart/resource> (包含资源部分). 这确保服务器可以向客户端相关的实体而不是服务器本身或客户端的帐号递送XML节和从客户端相关的实体而不是服务器本身或客户端的帐号接收XML节, 详见第十章.
在客户端已经绑定了一个资源到该流之后, 它被视为一个 "已连接的资源". 服务器应该允许一个实体同时维持多个已连接资源, 每个已连接的资源关联到一个唯一的XML流并且和其他已连接的资源的资源部分是不同的.
如果, 在完成资源绑定步骤之前, 客户端尝试发送一个XML节给另一个不是服务器本身或客户端的帐号的实体, 服务器不能(MUST NOT)处理这个节而必须以<not-authorized/>流错误(4.9.3.12)关闭这个流.
资源绑定扩展的XML命名空间是 'urn:ietf:params:xml:ns:xmpp-bind'.
支持
在XMPP客户端和服务器实现中,对于资源绑定的支持是必需的.
流协商规则
强制协商
流的双方必须确保资源绑定是强制协商的.
重启
在资源绑定之后, 双方不能(MUST NOT)重启该流.
声明支持
在SASL协商成功之后,服务器发送一个新的应答流头给客户端, 这时服务器必须在它展示给客户端的流特性中包含一个由'urn:ietf:params:xml:ns:xmpp-bind'命名空间限定的<bind/>元素.
服务器不能(MUST NOT)包含资源绑定流特性,直到客户端验证之后, 通常就是SASL协商成功之后.
S: <stream:stream from='im.example.com' id='gPybzaOzBmaADgxKXu9UClbprp0=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> </stream:features>
在得到资源绑定是强制协商的通知之后, 客户端必须绑定一个资源到流上,如下面章节所述.
资源标识符的生成
最低限度,资源部分在<localpart@domainpart>已连接的资源中必须是唯一的. 这个强制性策略是由服务器来负责的.
服务器生成的资源标识符
一个服务器必须能代替客户端生成XMPP资源部分. 由服务器生成的资源部分必须是随机的(参见RANDOM).
成功情形
客户端,通过发送一个类型为"set"并包含了一个由'urn:ietf:params:xml:ns:xmpp-bind'命名空间限定的空的<bind/>元素的IQ节(见8.2.3)来请求一个服务器生成的资源部分.
C: <iq id='tn281v37' type='set'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> </iq>
一旦服务器为该客户端生成了一个XMPP部分, 它必须返回一个类型为"result"的IQ节给该客户端, 这个节里面必须包含一个<jid/>元素来指定服务器决定的已连接资源的全JID.
S: <iq id='tn281v37' type='result'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid> juliet@im.example.com/4db06f06-1ea4-11dc-aca3-000bcd821bfb </jid> </bind> </iq>
错误情形
当一个客户端在资源绑定时请求服务器生成一个资源部分, 定义了以下节错误条件:
- 该帐号已经达到了被允许的并发资源连接数量限制.
- 该客户端不被允许绑定一个资源到该流.
自然的, 可能有这里没定义的错误条件发生, 如8.3所述.
资源约束
如果帐号已经达到被允许的并发连接资源数限制, 服务器必须返回一个<resource-constraint/>节错误(8.3.3.18).
S: <iq id='tn281v37' type='error'> <error type='wait'> <resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
不允许
如果客户端不被允许绑定一个资源到该流, 服务器必须返回一个<not-allowed/>节错误(8.3.3.10).
S: <iq id='tn281v37' type='error'> <error type='cancel'> <not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
客户端提交的资源标识符
不同于请求服务器代替自己生成一个资源部分, 一个客户端可以尝试提交一个它自己生成的或受控制的用户已经提供的资源部分.
成功情形
客户端发送类型为"set"包含<bind/>元素以及拥有非空XML字符串数据的<resource/>子元素的IQ节,以请求它的服务器接受一个客户端提交的资源部分.
C: <iq id='wy2xa82b4' type='set'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>balcony</resource> </bind> </iq>
该服务器应该接受这个客户端提交的资源部分. 它返回一个类型为"result"的IQ节给该客户端, 其中包含一个<jid/>子元素来为已连接的资源指定全JID并包含未修改的客户端提交的文本.
S: <iq id='wy2xa82b4' type='result'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid>juliet@im.example.com/balcony</jid> </bind> </iq>
或者, 基于本地服务策略,该服务器可以拒绝客户端提交的资源部分并以服务器生成的资源部分覆盖它.
S: <iq id='wy2xa82b4' type='result'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid> juliet@im.example.com/balcony 4db06f06-1ea4-11dc-aca3-000bcd821bfb </jid> </bind> </iq>
错误情形
当一个客户端在资源绑定期间尝试提交它自己的XMPP资源部分, 除了7.6.2还定义了以下节错误条件:
- 所提供的资源部分无法被服务器处理.
- 所提供的资源部分已经被使用.
自然的, 有一些未在这里定义的错误条件可能发生, 如8.3所述.
坏请求
如果提供的资源部分无法被服务器处理(例如, 因为它长度为零或因为它违反了定义于XMPP‑ADDR的资源部分的其他规则 ), 该服务器可能返回一个<bad-request/>节错误(8.3.3.1)而不应该处理这个资源部分,这样就保持了一致性.
S: <iq id='wy2xa82b4' type='error'> <error type='modify'> <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
冲突
如果当前有一个已连接的客户端的会话被新连接的客户端请求了, 服务器必须做以下事情之一(该服务器做的这些事情之一对于实现或者本地服务策略是一个麻烦, 尽管下面提供了一些建议).
- 以一个服务器生成的资源部分覆盖新连接的客户端提供的资源部分. 这一行为是被提倡的, 因为对于客户端实现来说它简化了资源绑定过程.
- 不允许新连接的客户端的资源绑定并保持当前已连接客户端的会话. 这一行为既不提倡也不反对, 尽管实际上它在RFC 3920中被隐性地提倡; 然而, 请注意对<conflict/>错误的处理并不总是被现有的客户端实现支持的, 它经常被当成一个验证错误并且当收到这个错误的时候丢弃缓存的凭证.
- 中止当前已连接的客户端的会话并允许新连接的客户端的资源绑定尝试. 尽管这是早期XMPP服务器实现的传统行为, 现在不提倡这么做了,因为它可能导致两个客户端互相挂掉多方的无线循环; 无论如何, 注意这个行为在某些布署场景中可能是适当的,要么如果服务器知道当前已连接的客户端有一个死连接,要么有一个4.6所述的断裂的流.
如果服务器遵循1号行为, 它返回一个类型为"result"的<iq/>节给新连接的客户端, 这里的<bind/>元素的<jid/>子元素包含XML字符串数据指定该客户端的全JID, 包含服务器生成的资源部分.
S: <iq id='wy2xa82b4' type='result'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid> juliet@im.example.com/balcony 4db06f06-1ea4-11dc-aca3-000bcd821bfb </jid> </bind> </iq>
如果服务器遵循2号行为, 它发送一个<conflict/>节错误(8.3.3.2)应答新连接的客户端的资源绑定尝试但是保持这个XML流,这样新连接的客户端有机会去协商一个不冲突的资源部分(即, 新连接的客户端在做下一次绑定资源的尝试之前需要选择一个不同的资源部分).
S: <iq id='wy2xa82b4' type='error'> <error type='modify'> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
如果服务器遵循3号行为, 它返回一个<conflict/>流错误(4.9.3.3)给当前的已连接客户端(如4.9.3.3所述)并返回一个类型为"result"的IQ节(表示成功)新连接的应答资源绑定尝试.
S: <iq id='wy2xa82b4' type='result'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid> juliet@im.example.com/balcony </jid> </bind> </iq>
重试
如果客户端提交资源部分的时候发生了一个错误, 服务器应该允许可配置的但是合理的重试次数(至少5次且不高于10次); 这让客户端能够不需要被迫重新连接就可以纠正不正确提通的资源部分(例如, 坏的数据格式或重复的文本字符串).
在客户端达到重试次数限制之后, 服务器必须以<policy-violation/>流错误(4.9.3.14)关闭这个流.
XML节
在一个客户端和一个服务器(或两个服务器)完成了流协商之后, 双方就可以发送XML节了. 对于'jabber:client'和'jabber:server'命名空间定义了三种XML节: <message/>, <presence/>, 和 <iq/>. 另外, 这些节类型有五种常见属性. 这些常见属性, 以及这三种节类型的基本语义, 定义于本协议; 更多即时消息和联机状态应用相关以及有关XMPP扩展协议的应用的XML节语法的详细信息在XMPP‑IM里提供.
XMPP客户端和服务器实现必须支持本协议所定义的XML节语法和语义.
常见属性
以下五种属性常见于 message, presence, 和 IQ 节.
to
'to'属性指定该节期望的接收者的JID.
<message to='romeo@example.net'> <body>Art thou not Romeo, and a Montague?</body> </message>
关于基于'to'地址的入站和出站XML节的服务器处理的信息, 参考第十章.
客户端-服务器流
以下规则适用于已连接客户端通过一个'jabber:client'命名空间限定的XML流发送给它的服务器的节中包含的'to'属性.
- 一个拥有特定接收者(例如, 一个会话伙伴, 一个远程服务, 该服务器本身, 甚至该用户的纯JID的另一个资源)的节必须拥有一个'to'属性,它的值是一个XMPP地址.
- 一个从客户端发送到服务器的由该服务器直接处理的节(例如,XMPP‑IM所述的好友列表处理或发送给服务器用来广播给其他实体的的联机状态信息)不能(MUST NOT)拥有'to'属性.
以下规则适用于服务器通过一个'jabber:client'命名空间限定的XML流发送到已连接客户端的节中包含的'to'属性..
- 如果该服务器从另一个已连接客户端或从一个对端服务器接收到该节, 在递送该节给该客户端之前该服务器不能(MUST NOT)修改'to'地址.
- 如果该服务器本身生成了这个节(例如, 对类型为"get"或"set"的IQ节的应答, 即使该节不包含一个'to'地址), 这个节可以包含一个'to'地址, 这个地址必须是该客户端的全JID, 如果这个节不包含'to'地址,那么该客户端必须把'to'地址视为等同于该客户端的全JID.
服务器-服务器流
以下规则适用于一个'jabber:server'命名空间限定的XML流(即,服务器-服务器 流)的上下文中包含的'to'属性.
from
'from'属性指定发送者的JID.
<message from='juliet@im.example.com/balcony' to='romeo@example.net'> <body>Art thou not Romeo, and a Montague?</body> </message>
客户端-服务器流
以下规则适用于被'jabber:client'命名空间限定的XML流(即, 客户端-服务器 流)上下文中的'from'属性.
- 当服务器从一个已连接客户端接收到一个XML节, 该服务器必须给这个节添加一个'from'属性或覆盖这个由客户端指定的'from'属性, 这里'from'属性的值必须是服务器针对生成这个节的已连接资源确定的全JID (<localpart@domainpart/resource>) (见4.3.6), 或在和订阅相关的联机状态信息节(见XMPP‑IM)的情况下则是纯JID (<localpart@domainpart>).
- 当服务器为它自己生成一个从服务器本身发送给客户端的节的时候, 这个节必须包含一个'from'属性,它的值是服务器在流协商中同意的纯JID (即, <domainpart>)(例如, 基于初始化流头中的'to'属性).
- 当服务器生成一个从该服务器递送到已连接客户端的帐号本身的节的时候(例如, 在服务器代表客户端提供的数据存储服务的上下文中), 该节要么 (a) 不包含'from'属性,要么 (b) 包含一个值为该帐号纯JID(<localpart@domainpart>)的'from'属性.
- 服务器不能(MUST NOT)给客户端发送不包含'from'属性的节,如果该节不是由服务器代表它本身生成的(例如, 如果它是由另一个客户端或对端服务器生成的,而该服务器仅仅递送它到客户端或一些其他的实体); 所以, 当一个客户端接收到一个不包含'from'属性的节的时候, 它必须假定这个节是从该用户帐号所在服务器发出的.
服务器-服务器流
以下规则适用于一个'jabber:server'命名空间限定的XML流(即,服务器-服务器 流)的上下文中包含的'from'属性.
强制执行这些规则有助于组织特定的如13.12所述的拒绝服务攻击.
id
'id'属性是由发起方实体用来跟踪可能从其他实体(类似中间服务器或预期的接收者)收到的和它生成的节有关的任何应答或错误节.
这个'id'属性仅在当前流保持唯一性还是全局保持唯一性,取决于发起方实体本身.
对于<message/>和<presence/>节来说, 建议发起方实体包含一个'id'属性; 对于<iq/>节来说, 它是必需的.
如果生成的节包含一个'id'属性,那么对于其相应的应答或错误节来说,也必须包含一个'id'属性, 这个'id'属性的值必须和生成的节的那个'id'属性值匹配.
IQ节语义强加了额外的约束,参见8.2.3.
type
'type'属性指定该消息,联机状态或IQ节的用途或上下文. 'type'属性的特定的允许值依赖于这个节是一个消息, 联机状态, 还是IQ节. 为消息和联机状态节定义的值用于即时消息和联机状态应用,所以定义于XMPP‑IM中, 而为IQ节定义的值指定所有结构化请求-应答交换中的语义部分(无论载荷是什么), 所以它定义于8.2.3. 唯一通用于所有三种节的'type'值是"error",定义于8.3.
xml:lang
一个节应该拥有'xml:lang'属性(定义于的2.12节XML),如果这个节包含了XML字符串数据打算展示给用户(如CHARSETS所解释的, "可读国际化"). 'xml:lang'属性的值指定任何这类可读XML字符串数据的缺省语言.
<presence from='romeo@example.net/orchard' xml:lang='en'> <show>dnd</show> <status>Wooing Juliet</status> </presence>
'xml:lang'属性的指可以被特定子元素的'xml:lang'属性覆写.
<presence from='romeo@example.net/orchard' xml:lang='en'> <show>dnd</show> <status>Wooing Juliet</status> <status xml:lang='cs'>Dvořím se Julii</status> </presence>
如果一个由客户端生成的出站节不拥有'xml:lang'属性, 该客户端的服务器应该添加一个'xml:lang'属性,其值为客户端的出站流所指定的值,如4.7.4所述.
C: <presence from='romeo@example.net/orchard'> <show>dnd</show> <status>Wooing Juliet</status> </presence> S: <presence from='romeo@example.net/orchard' to='juliet@im.example.com' xml:lang='en'> <show>dnd</show> <status>Wooing Juliet</status> </presence>
如果一个被客户端或服务器接收到的入站节不拥有'xml:lang'属性, 一个实现必须假定缺省语言是该实体的输入流所指定的值,如4.7.4所述.
'xml:lang'属性的值必须遵循 NMTOKEN 数据类型(定义于XML的2.3节) 并且必须遵循定义于LANGTAGS的格式.
服务器不能(MUST NOT)修改或删除它从其他实体收到的节的'xml:lang'属性.
基本语义
消息语义
<message/>节是一个"推送"机制,这里一个实体推送信息到另一个实体, 类似发生在email系统里的通讯一样. 所有消息节将拥有'to'属性用来指定该消息期望的接收者 (见8.1.1和10.3), 除非消息是被一个已连接的客户端帐号的纯JID发送的. 接收到一个带有'to'地址的消息节之后, 服务器应该尝试路由或递送它到期望的接收者那里(见第十章里和XML节相关的通用路由和递送规则).
联机状态语义
<presence/>节是一个特定的"广播"或"发布-订阅"机制, 这里多个实体接收关于他们订阅的一个实体的信息(在这个案例中, 是网络可用性信息). 通常, 发布客户端应该发送一个不带有'to'属性的联机状态节, 这种情况下该客户端连接的那个服务器将广播那个节给所有已订阅的实体. 然而, 发布客户端也可以发送一个带有'to'属性的联机状态节, 这种情况下该服务器将路由或递送那个节到期望的接收者. 尽管<presence/>节大部分情况下是由XMPP客户端使用, 它也可能被服务器, 附加服务, 以及任何其他类型呃XMPP实体使用. 参见第十章中和XML节相关的通用路由和递送规则, 以及XMPP‑IM中联机状态应用的特定规则.
IQ语义
信息查询(Info/Query),或IQ, 是一个"请求-应答"机制, 类似某些情况下的超文本传输协议HTTP. IQ的语义允许一个实体对另一个实体做出一个请求, 并接收一个应答. 这个请求和应答的数据内容由schema或其他限定IQ元素的直接子元素的XML命名空间相关的结构化定义(见8.4)来限定, 发出请求的实体使用'id'属性来跟踪交互过程. 所以, IQ交互沿用了结构化数据交换的常见模式,类似 get/result 或 set/result (尽管适当的时候对于某个请求会返回一个error):
请求实体 应答实体 ---------- ---------- | | | <iq id='1' type='get'> | | [ ... payload ... ] | | </iq> | | -------------------------> | | | | <iq id='1' type='result'> | | [ ... payload ... ] | | </iq> | | <------------------------- | | | | <iq id='2' type='set'> | | [ ... payload ... ] | | </iq> | | -------------------------> | | | | <iq id='2' type='error'> | | [ ... condition ... ] | | </iq> | | <------------------------- | | |
为强制这些语义, 以下规则适用:
- get -- 该节请求信息, 查询需要什么数据以完成更多操作, 等等.
- set -- 该节为完成某个操作提供需要的数据, 设置新值, 取代旧值, 等等.
- result -- 该节是对成功的get或set请求的应答.
- error -- 该节报告关于处理或递送一个get或set请求时发生的错误(见8.3).
节错误
节相关的错误处理的方式类似流错误流错误, 但是不像流错误那样,节错误是可恢复的; 所以, 他们不会导致XML和当前TCP连接的中止. 反之, 发现错误条件的实体返回一个错误节, 它是一个这样的节:
- 是和触发这个错误的已生成的节同种类型(message, presence, 或 IQ)
- 'type'属性值设为"error"
- 通常是把已生成的节的'from'和'to'互换
- 镜像触发这个错误的已生成的节的'id'属性(如果有的话)
- 包含一个<error/>子元素以指明错误条件并且对发送者可以采取的补救措施提供一个暗示(然而, 不可能总是能够不补救这个错误)
规则
以下规则适用于节错误:
- 检测到和节相关的错误条件的接收或处理实体应该返回一个错误节(对于IQ节必须这么做).
- 该错误节应该简单地把生成的节中的'from'和'to'地址互换, 除非这么做将会 (1) 导致信息泄漏(参见[RFC6120#信息泄露|13.10]])或其他违反安全, 或 (2) 强迫错误节的发送者在该错误节的'from'或'to'地址中包含一个异常的JID.
- 如果生成的节是<message/>或<presence/>并且包含了'id'属性,那么该错误节必须也包含'id'属性. 如果生成的节是<iq/>,那么该错误节必须包含一个'id'属性. 在所有情况下, 'id'属性的值必须和生成节的相同(或者是空,如果生成的节没有包含'id'属性).
- 错误节必须包含一个<error/>子元素.
- 返回错误节的实体可以传递它的JID给生成节的发送者(例如, 为了诊断或跟踪的目的),通过附加一个'by'属性到<error/>子元素.
- 返回错误节的实体可以包含被发送的原始XML,这样发送者能够检查, 如果必要的话, 并在尝试重发之前纠正该XML(然而, 这只是出于礼貌,并且原实体不能(MUST NOT)依赖接收到的原始载荷). 自然地, 该实体不能(MUST NOT)包含原始数据,如果它不是格式良好的XML, 违反XMPP的XML限制(见11.1), 或反而是有害的(例如, 超出大小限制).
- 如果'type'属性值不是"error"(或如果没有'type'属性), 不能(MUST NOT)包含一个<error/>子元素.
- 接收到错误节的实体不能(MUST NOT)以更多的错误节来应答这个节; 这有助于防止死循环.
语法
节相关的错误的语法如下, 这里展示的用方括号'['和']'括起来的XML数据是可选的, 'intended-recipient' 是原始节指定的地址的那个实体的JID, 'sender' 是原始实体的JID, 而 'error-generator' 是检测到错误的并方会错误节的那个实体.
<stanza-kind from='intended-recipient' to='sender' type='error'> [OPTIONAL to include sender XML here] <error [by='error-generator'] type='error-type'> <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> [<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' xml:lang='langcode'> OPTIONAL descriptive text </text>] [OPTIONAL application-specific condition element] </error> </stanza-kind>
"stanza-kind"必须是 message, presence, 或 iq 之一.
"error-type" 必须是以下之一:
- auth -- 在提供身份之后重试
- cancel -- 不要重试 (该错误不能加以弥补)
- continue -- 继续 (这个条件只是个警告)
- modify -- 在修改发送的数据之后重试
- wait -- 等待之后重试 (该错误是暂时的)
"defined-condition"必须符合8.3.3定义的节错误条件之一. 然而, 因为将来可能会发生额外的错误条件, 如果实体接受到一个它不理解的节错误条件,那么它必须把这个未知的条件当成<undefined-condition/> (8.3.3.21). 如果一个XMPP协议扩展的设计者或一个XMPP实现的开发者需要未在本协议中定义的节错误条件的通讯, 他们可以定义应用特有的命名空间所限定的应用特有的错误条件元素来实现这个目标.
<error/>元素:
- 必须包含一个已定义的条件元素.
- 可以包含一个包含XML字符串数据的<text/>子元素,用来描述错误的详细信息; 这个元素必须由'urn:ietf:params:xml:ns:xmpp-stanzas'命名空间来限定并且应该拥有'xml:lang'属性来指定该XML字符串数据的自然语言.
- 可以包含一个用于应用特有的错误条件的子元素; 这个元素必须由一个应用特有的命名空间来限定,以定义该元素的语法和语义.
<text/>元素是可选的. 如果包含了它, 它仅被用于提供描述和诊断信息以补充说明已定义条件或应用特有的条件的含义. 它不能(MUST NOT)被应用程序当成编程信息来解释. 它不应该被用于向自然人用户展示错误消息, 但是可以被附加在该已定义条件元素(以及, 可选的, 应用特有的条件元素)的错误消息上展示.
已定义的条件
以下条件是已定义好用于节错误的.
error-type 的值是被推荐用于每个已定义的条件通常预期的类型; 无论如何, 在某些情况下不同的类型可能更合适.
bad-request
发送者发送的节里包含的XML不符合适当的schema或不能被拥有(例如, IQ节的'type'属性包含一个不能识别的值, 或一个被已知的命名空间限定的元素但是违反了该元素的已定义的语法); 相关的错误类型应该是"modify".
C: <iq from='juliet@im.example.com/balcony' id='zj3v142b' to='im.example.com' type='subscribe'> <ping xmlns='urn:xmpp:ping'/> </iq> S: <iq from='im.example.com' id='zj3v142b' to='juliet@im.example.com/balcony' type='error'> <error type='modify'> <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
conflict
访问未被授权,因为一个现存的资源使用了相同的名字或地址; 相关的错误类型应该是"cancel".
C: <iq id='wy2xa82b4' type='set'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>balcony</resource> </bind> </iq> S: <iq id='wy2xa82b4' type='error'> <error type='cancel'> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
feature-not-implemented
出现在XML节里的特性没有被预定的接收方或中间服务器实现,所以该节无法被处理(例如, 该实体知道该命名空间但是不认识元素名); 相关的错误类型应该是"cancel" 或 "modify".
C: <iq from='juliet@im.example.com/balcony' id='9u2bax16' to='pubsub.example.com' type='get'> <pubsub xmlns='http://jabber.org/protocol/pubsub'> <subscriptions/> </pubsub> </iq> E: <iq from='pubsub.example.com' id='9u2bax16' to='juliet@im.example.com/balcony' type='error'> <error type='cancel'> <feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> <unsupported xmlns='http://jabber.org/protocol/pubsub#errors' feature='retrieve-subscriptions'/> </error> </iq>
forbidden
请求的实体没有必要的许可来执行一个只允许特定授权角色或个体来完成的动作(即, 它通常和授权而不是验证有关); 相关的错误类型应该是"auth".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony' type='error'> <error type='auth'> <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
gone
接收者或服务器无法再用这个地址联系到, 通常是永久意义上的(和<redirect/>错误条件相反, 它被用于临时的地址失败); 相关的错误类型应该是"cancel"并且该错误节应该包含一个新的地址(如果可用的话)作为<gone/>元素的XML字符串数据(它必须是一个实体可以联系的唯一资源标识符URI或国际化资源标识符IRI, 典型的是一个XMPP‑URI定义的XMPP IRI.
C: <message from='juliet@im.example.com/churchyard' id='sj2b371v' to='romeo@example.net' type='chat'> <body>Thy lips are warm.</body> </message> S: <message from='romeo@example.net' id='sj2b371v' to='juliet@im.example.com/churchyard' type='error'> <error by='example.net' type='cancel'> <gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'> xmpp:romeo@afterlife.example.net </gone> </error> </message>
internal-server-error
服务器发生了错误的配置或其他阻止它处理改节的内部错误; 相关的错误类型应该是"cancel".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony' type='error'> <error type='cancel'> <internal-server-error xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
item-not-found
找不到请求的JID地址或条目; 相关的错误类型应该是"cancel".
C: <presence from='userfoo@example.com/bar' id='pwb2n78i' to='nosuchroom@conference.example.org/foo'/> S: <presence from='nosuchroom@conference.example.org/foo' id='pwb2n78i' to='userfoo@example.com/bar' type='error'> <error type='cancel'> <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
jid-malformed
发送的实体所提供(例如, 在资源绑定的时候)或与之通讯(例如, 一个节的'to'地址)的XMPP地址或其中一部分违反了XMPP‑ADDR定义的规则; 相关的错误类型应该是"modify".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='ch@r@cters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='ch@r@cters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony' type='error'> <error by='muc.example.com' type='modify'> <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
not-acceptable
接收者或服务器理解该请求但是不能处理它,因为该请求不符合该接收者或服务器的标准(例如, 请求订阅信息但是未同时包含接收者需要的配置参数); 相关的错误类型应该是"modify".
C: <message to='juliet@im.example.com' id='yt2vs71m'> <body>[ ... the-emacs-manual ... ]</body> </message> S: <message from='juliet@im.example.com' id='yt2vs71m'> <error type='modify'> <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
not-allowed
接收者或服务器不允许任何实体执行该动作(例如, 向列入黑名单的域发送消息); 相关的错误类型应该是"cancel".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony' type='error'> <error type='cancel'> <not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
not-authorized
发送者在被允许执行某动作之前需要提供凭证, 或已经提供了错误的凭证("not-authorized"的提法, 来源于HTTP的"401 Unauthorized"错误, 可能导致读者认为这个条件是和授权相关的, 但其实它通常用于验证相关的领域); 相关的错误类型应该是"auth".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony'> <error type='auth'> <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
policy-violation
实体违反了一些本地服务策略(例如, 一个消息包含了服务禁止的单词)而服务器可以选择在<text/>元素里或在应用特有的条件元素里指定策略; 相关的错误类型应该是"modify"或"wait",取决于被违反的策略.
(在下例中, 客户端发送一个包含了根据服务器的本地服务策略被禁止的单词的XMPP消息.)
C: <message from='romeo@example.net/foo' to='bill@im.example.com' id='vq71f4nb'> <body>%#&@^!!!</body> </message> S: <message from='bill@im.example.com' id='vq71f4nb' to='romeo@example.net/foo'> <error by='example.net' type='modify'> <policy-violation xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
recipient-unavailable
预期的接收者暂时不可用, 正在维护, 等等; 相关的错误类型应该是"wait".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony'> <error type='wait'> <recipient-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
redirect
接收者或服务器重定向该信息的请求到另一个实体, 典型的临时发生的情形(和<gone/>错误条件相反, 它用于永久性的地址错误); 相关的错误类型应该是"modify",并且该错误节应该在<redirect/>元素的XML字符串数据中包含替代的地址(它必须是一个发送者可以与之通讯的URI或IRI, 通常是一个XMPP‑URI定义的XMPP IRI).
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony' type='error'> <error type='modify'> <redirect xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'> xmpp:characters@conference.example.org </redirect> </error> </presence>
registration-required
请求的实体没有被授权访问请求的服务,因为需要事先注册(提前注册的例子包括XMPP多用户聊天XEP-0045中仅限会员的房间和到非XMPP即时消息服务的网关, 传统上使用网关XEP‑0100是需要注册的); 相关的错误类型应该是"auth".
C: <presence from='juliet@im.example.com/balcony' id='y2bs71v4' to='characters@muc.example.com/JulieC'> <x xmlns='http://jabber.org/protocol/muc'/> </presence> E: <presence from='characters@muc.example.com/JulieC' id='y2bs71v4' to='juliet@im.example.com/balcony'> <error type='auth'> <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
remote-server-not-found
一个远程服务器或预期的接收者的JID的一部分所代表的服务不存在或不能解析(例如, 没有 _xmpp-server._tcp DNS SRV记录, A记录或AAAA记录解析也失败了, 或A/AAAA查询成功了但是在IANA注册了的端口5269上没有应答); 相关错误类型应该是"cancel".
C: <message from='romeo@example.net/home' id='ud7n1f4h' to='bar@example.org' type='chat'> <body>yt?</body> </message> E: <message from='bar@example.org' id='ud7n1f4h' to='romeo@example.net/home' type='error'> <error type='cancel'> <remote-server-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
remote-server-timeout
远程服务器或作为预定的接收者的全JID的一部分(或履行请求所需要的)的服务能被解析但是无法在合理的时间内与之建立通讯(例如, 无法在解析到的IP地址和端口上建立一个XML流, 或可以建立一个XML流但是因为TLS, SASL, Server Dialback的问题导致流协商失败, 等等); 相关的错误类型应该是"wait" (除非该错误是更永久性的, 例如, 远程服务器能被找到但是无法被认证或它违反了安全策略).
C: <message from='romeo@example.net/home' id='ud7n1f4h' to='bar@example.org' type='chat'> <body>yt?</body> </message> E: <message from='bar@example.org' id='ud7n1f4h' to='romeo@example.net/home' type='error'> <error type='wait'> <remote-server-timeout xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
resource-constraint
服务器或接收者忙或缺乏必要的系统资源来服务该请求; 相关的错误类型应该是"wait".
C: <iq from='romeo@example.net/foo' id='kj4vz31m' to='pubsub.example.com' type='get'> <pubsub xmlns='http://jabber.org/protocol/pubsub'> <items node='my_musings'/> </pubsub> </iq> E: <iq from='pubsub.example.com' id='kj4vz31m' to='romeo@example.net/foo' type='error'> <error type='wait'> <resource-constraint xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
service-unavailable
服务器或接收者当前未提供被请求的服务; 相关的错误类型应该是"cancel".
C: <message from='romeo@example.net/foo' to='juliet@im.example.com'> <body>Hello?</body> </message> S: <message from='juliet@im.example.com/foo' to='romeo@example.net'> <error type='cancel'> <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
subscription-required
提出请求的实体没有被授权访问所请求的服务,因为需要事先订阅(事先订阅的例子包括授权接收XMPP‑IM定义的联机状态信息和用于XEP‑0060定义的XMPP发布-订阅的 opt-in 数据种子); 相关的错误类型应该是"auth".
C: <message from='romeo@example.net/orchard' id='pa73b4n7' to='playwright@shakespeare.example.com' type='chat'> <subject>ACT II, SCENE II</subject> <body>help, I forgot my lines!</body> </message> E: <message from='playwright@shakespeare.example.com' id='pa73b4n7' to='romeo@example.net/orchard' type='error'> <error type='auth'> <subscription-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </message>
undefined-condition
该错误条件不在本列表中的其他错误条件之中; 任何错误类型都可能和本条件有关, 并且除非和应用特有的条件联合在一起,它应该不被使用.
C: <message from='northumberland@shakespeare.example' id='richard2-4.1.247' to='kingrichard@royalty.england.example'> <body>My lord, dispatch; read o'er these articles.</body> <amp xmlns='http://jabber.org/protocol/amp'> <rule action='notify' condition='deliver' value='stored'/> </amp> </message> S: <message from='example.org' id='amp1' to='northumberland@example.net/field' type='error'> <amp xmlns='http://jabber.org/protocol/amp' from='kingrichard@example.org' status='error' to='northumberland@example.net/field'> <rule action='error' condition='deliver' value='stored'/> </amp> <error type='modify'> <undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> <failed-rules xmlns='http://jabber.org/protocol/amp#errors'> <rule action='error' condition='deliver' value='stored'/> </failed-rules> </error> </message>
unexpected-request
接收者或服务器理解这个请求但是不希望它在这个时候出现(即, 该请求顺序错了); 相关的错误类型应该是"wait"或"modify".
C: <iq from='romeo@example.net/foo' id='o6hsv25z' to='pubsub.example.com' type='set'> <pubsub xmlns='http://jabber.org/protocol/pubsub'> <unsubscribe node='my_musings' jid='romeo@example.net'/> </pubsub> </iq> E: <iq from='pubsub.example.com' id='o6hsv25z' to='romeo@example.net/foo' type='error'> <error type='modify'> <unexpected-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> <not-subscribed xmlns='http://jabber.org/protocol/pubsub#errors'/> </error> </iq>
应用特有的条件
大家知道, 一个应用可以提供应用特有的节错误信息,通过在错误元素中包含一个正确的命名空间的子元素. 典型的, 该应用特有的元素补充或进一步限定一个已定义的元素. 从而, 该<error/>元素将包含两个或三个子元素.
<iq id='ixc3v1b9' type='error'> <error type='modify'> <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> <too-many-parameters xmlns='http://example.org/ns'/> </error> </iq> <message type='error' id='7h3baci9'> <error type='modify'> <undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> <text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'> [ ... application-specific information ... ] </text> <too-many-parameters xmlns='http://example.org/ns'/> </error> </message>
一个接收到它不理解的应用特有的错误条件的实体必须忽略那个条件但适当处理该错误节的其他部分.
扩展内容
尽管 message, presence, 和 IQ 节为消息,可用性,和 请求-应答 交互 提供了基本的语义, XMPP还使用XML命名空间 (见XML‑NAMES) 扩展基本的节语法来提供附加的功能.
一个 message 或 presence 节可以包含一个或多个可选的子元素来指定扩展消息含义的内容(例如,XEP‑0071所述的的消息主体的XHTML格式版本), 并且一个类型为"get"或"set"的IQ节必须包含一个这样的子元素. 这样一个子元素可以使用任何名称并且必须拥有一个命名空间声明(不同于 "jabber:client", "jabber:server", 或 "http://etherx.jabber.org/streams") 来定义子元素中的数据. 这样一个子元素被成为一个 "扩展元素". 扩展元素可能被包含在节的直属子元素中,也可能包含在任何混合的层级里面.
类似的, "扩展属性" 也是允许的. 表示说: 一个节本身 (即, 一个 <iq/>, <message/>, 或 <presence/> 元素,并由 "jabber:client" 或 "jabber:server" 内容命名空间限定) 或这样一个节的任何子元素 (一个由内容命名空间限定的扩展元素或子元素) 也可以包含一个或多个由不同于内容命名空间或保留的 "http://www.w3.org/XML/1998/namespace" 命名空间的其他命名空间(包括所谓 "空命名空间",如果属性没有如XML‑NAMES所说的那样做前缀的话)限定的属性.
一个扩展元素或扩展属性被称为 "扩展内容" 并且限定这样一个元素或属性的命名空间被称为"扩展命名空间".
为了说明这些概念, 下面有些例子.
以下的节包含一个直接子元素,它的扩展命名空间是 'jabber:iq:roster':
<iq from='juliet@capulet.com/balcony' id='h83vxa4c' type='get'> <query xmlns='jabber:iq:roster'/> </iq>
以下节包含两个不同扩展命名空间的子元素.
<presence from='juliet@capulet.com/balcony'> <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://code.google.com/p/exodus' ver='QgayPKawpkPSDYmwT/WM94uAlu0='/> <x xmlns='vcard-temp:x:update'> <photo>sha1-hash-of-image</photo> </x> </presence>
以下节包含两个子元素, 其中一个被 "jabber:client" 或 "jabber:server" 内容命名空间限定,另一个被一个扩展命名空间限定; 而该扩展元素包含了一个由另一个扩展命名空间限定的子元素.
<message to='juliet@capulet.com'> <body>Hello?</body> <html xmlns='http://jabber.org/protocol/xhtml-im'> <body xmlns='http://www.w3.org/1999/xhtml'> <p style='font-weight:bold'>Hello?</p> </body> </html> </message>
实现不为扩展命名空间限定的元素生成命名空间前缀在XMPP社区是常见的(在XML社区, 这个惯例有时被称为"免前缀标准化"). 无论如何, 如果一个实现生成了这类命名空间前缀那么它必须在该节本身或该节的一个子元素中包含该命名空间的声明, 而不是在流头声明(见4.8.4).
路由实体(典型的是服务器)处理序列化XML节的时候应该尝试保持前缀, 但是接收实体不能(MUST NOT)依赖该前缀字符串来获取任何特定的值(对于'stream'前缀的许可, 如4.8.5所述, 是这个规则的一个例外, 尽管它是用于流而不是节的).
在任何实现的特定部分对任何给定的扩展命名空间的支持都是可选的. 如果一个实体不理解这样一个命名空间, 该实体的的预期行为依赖于该实体是 (1) 接收者 还是 (2) 一个路由或递送该节给接收者的服务器.
如果一个接收者收到一个包含了不理解的元素或属性的节, 它不能(MUST NOT)尝试处理那个XML数据,而必须按以下方式处理.
- 如果一个预定的接收者受到一个message节,它的唯一子元素由不理解的命名空间限定, 那么根据XMPP应用它必须要么忽略整个节要么返回一个节错误, 节错误应该是 <service-unavailable/> (8.3.3.19).
- 如果预定的接收者接收到一个presence节,它的唯一子元素由它不理解的命名空间限定, 那么它必须忽略该子元素,把它当成没有子元素的联机状态信息节.
- 如果预定的接收者收到一个message或presence节,它包含的XML数据由它不理解的命名空间来限定, 那么它必须忽略节的由未知的命名空间限定的那部分.
- 如果预定的接收者接收到一个类型为"get"或"set"的IQ节,它包含的一个子元素由它不理解的命名空间限定, 那么实体必须返回一个类型为"error"且错误条件为<service-unavailable/>的IQ节.
如果一个服务器持有一个节,是用来递送到另一个实体的,并且这个节包含了一个该服务器不理解的子元素, 它必须不加修改地路由或递送该节到一个和本地账户关联的已连接客户端.
详细示例
本章的详细示例用来进一步说明本标准所定义的协议.
客户端-服务器示例
以下例子展示客户端和服务器协商XML流, 交换XML节, 和关闭已协商的流的XMPP数据流. 服务器是"im.example.com", 该服务器要求使用TLS, 客户端验证使用SASL SCRAM-SHA-1机制,客户端帐号是<juliet@im.example.com>而密码是"r0m30myr0m30", 并且客户端在这个流上提交了一个资源绑定请求. 我们假设在发送初始化流头之前, 客户端已经解析了_xmpp‑client._tcp.im.example.com的SRV记录并已经打开一个TCP连接到已解析的IP地址和声明的端口上.
TLS
第一步: 客户端初始化流到服务器:
C: <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: <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'>
第三步: 服务器发送流特性给客户端(在这个点上只有STARTTLS扩展, 它是强制协商的):
S: <stream:features> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'> <required/> </starttls> </stream:features>
第四步: 客户端发送STARTTLS命令给服务器:
C: <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
第五步: 服务器通知客户端允许继续:
S: <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
第五步(替代): 服务器通知客户端STARTTLS协商失败, 关闭XML流, 并中止TCP连接(所以, 流协商处理以不成功而结束并且双方不再进入下一步):
S: <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> </stream:stream>
第六步: 客户端和服务器尝试通过现有的TCP连接完成TLS协商(详见TLS).
第七步: 如果TLS协商成功, 客户端通过TLS保护的TCP连接初始化一个新的流到服务器:
C: <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'>
第七步(替代): 如果TLS协商不成功, 服务器关闭TCP连接(所以, 流协商处理以不成功而结束并且双方不再进入下一步):
SASL
第八步: 服务器发送流头给客户端并带上任何可用的流特性来应答:
S: <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'> S: <stream:features> <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <mechanism>SCRAM-SHA-1-PLUS</mechanism> <mechanism>SCRAM-SHA-1</mechanism> <mechanism>PLAIN</mechanism> </mechanisms> </stream:features>
第九步: 客户端选择一个验证机制(在这个场景中, 是 SCRAM-SHA-1), 包含初始化应答数据:
C: <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="SCRAM-SHA-1"> biwsbj1qdWxpZXQscj1vTXNUQUF3QUFBQU1BQUFBTlAwVEFBQUFBQUJQVTBBQQ== </auth>
解码之后的 base 64 数据是 "n,,n=juliet,r=oMsTAAwAAAAMAAAANP0TAAAAAABPU0AA".
第十步: 服务器发送挑战:
S: <challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> cj1vTXNUQUF3QUFBQU1BQUFBTlAwVEFBQUFBQUJQVTBBQWUxMjQ2OTViLTY5Y TktNGRlNi05YzMwLWI1MWIzODA4YzU5ZSxzPU5qaGtZVE0wTURndE5HWTBaaT AwTmpkbUxUa3hNbVV0TkRsbU5UTm1ORE5rTURNeixpPTQwOTY= </challenge>
解码后的 base 64 数据是 "r=oMsTAAwAAAAMAAAANP0TAAAAAABPU0AAe124695b-69a9-4de6-9c30-b51b3808c59e,s=NjhkYTM0MDgtNGY0Zi00NjdmLTkxMmUtNDlmNTNmNDNkMDMz,i=4096" (实际数据中是没有换行的).
第十一步: 客户端发送一个应答:
C: <response xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> Yz1iaXdzLHI9b01zVEFBd0FBQUFNQUFBQU5QMFRBQUFBQUFCUFUwQUFlMTI0N jk1Yi02OWE5LTRkZTYtOWMzMC1iNTFiMzgwOGM1OWUscD1VQTU3dE0vU3ZwQV RCa0gyRlhzMFdEWHZKWXc9 </response>
解码后的 base 64 数据是 "c=biws,r=oMsTAAwAAAAMAAAANP0TAAAAAABPU0 AAe124695b-69a9-4de6-9c30-b51b3808c59e,p=UA57tM/ SvpATBkH2FXs0WDXvJYw=" (实际数据中是没有换行的).
第十二步: 服务器通知客户端成功了, 并且包含了额外的数据:
S: <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> dj1wTk5ERlZFUXh1WHhDb1NFaVc4R0VaKzFSU289 </success>
解码后的 base 64 数据是 "v=pNNDFVEQxuXxCoSEiW8GEZ+1RSo=".
第十二步(替代): 服务器返回一个SASL错误给客户端(所以, 流协商处理以不成功结束并且双方不再进行下一步):
S: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <not-authorized/> </failure> </stream>
第十三步: 客户端初始化一个新的流到服务器:
C: <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: <stream:stream from='im.example.com' id='gPybzaOzBmaADgxKXu9UClbprp0=' to='juliet@im.example.com' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'> S: <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> </stream:features>
在被通知资源绑定是强制协商之后, 客户端需要绑定一个资源到流; 这里我们假定客户端提交了一个自然人可读的文本字符串.
第十五步: 客户端绑定一个资源:
C: <iq id='yhc13a95' type='set'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>balcony</resource> </bind> </iq>
第十六步: 服务器接受提交的资源部分并通知客户端资源绑定成功:
S: <iq id='yhc13a95' type='result'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid> juliet@im.example.com/balcony </jid> </bind> </iq>
第十六步(替代): 服务器返回错误给客户端(所以, 流协商处理以不成功结束并且双方不再进入下一步):
S: <iq id='yhc13a95' type='error'> <error type='cancel'> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
节交换
现在客户端被允许通过协商好的流发送XML节了.
C: <message from='juliet@im.example.com/balcony' id='ju2ba41c' to='romeo@example.net' type='chat' xml:lang='en'> <body>Art thou not Romeo, and a Montague?</body> </message>
如果必要, 发送者的服务器和预定的接收者的服务器协商XML流(见9.2).
预定的接收者应答, 并且消息被递送到客户端.
E: <message from='romeo@example.net/orchard' id='ju2ba41c' to='juliet@im.example.com/balcony' type='chat' xml:lang='en'> <body>Neither, fair saint, if either thee dislike.</body> </message>
客户端随后可以通过这个流继续发送和接收不限数量的XML节.
关闭
不想发送更多的消息, 客户端关闭它到服务器的流,不在等待来自服务器的入站数据了.
C: </stream:stream>
和4.4一致, 服务器可能发送额外的数据给客户端然后才关闭到该客户端的流.
S: </stream:stream>
客户端现在发送一个 TLS close_notify 警告, 从服务器接收到一个 close_notify 警告应答, 然后中止当前的TCP连接.
服务器-服务器示例
以下示例展示一个服务器和对端服务器协商XML流,交换XML节, 和关闭已协商的流的数据流. 初始化服务器("Server1")是im.example.com; 接收服务器("Server2")是example.net 并且要求使用TLS; im.example.com递交一个证书并通过SASL EXTERNAL机制验证. 假定在发送初始化流头之前, Server1已经解析了一个SRV记录_xmpp-server._tcp.example.net并且已经打开了一个TCP连接到已解析的IP地址的声明的端口上. 注意Server1怎样声明内容命名空间"jabber:server"作为缺省的命名空间并为流相关的元素使用前缀, 反之Server2使用免前缀标准.
TLS
第一步: Server1初始化流到Server2:
S1: <stream:stream from='im.example.com' to='example.net' version='1.0' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'>
第二步: Server2发送一个应答流头到Server1来应答:
S2: <stream from='example.net' id='hTiXkW+ih9k2SqdGkk/AZi0OJ/Q=' to='im.example.com' version='1.0' xmlns='http://etherx.jabber.org/streams'>
第三步: Server2发送流特性给Server1(在这个点上只有STARTTLS扩展, 它是强制协商的):
S2: <features xmlns='http://etherx.jabber.org/streams'> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'> <required/> </starttls> </features>
第四步: Server1发送STARTTLS指令给Server2:
S1: <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
第五步: Server2通知Server1它被允许继续:
S2: <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
第五步(替代): Server2通知Server1 STARTTLS协商失败了, 关闭流, 并中止TCP连接(于是, 流协商过程以不成功结束并且双方不再进行下一步):
S2: <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> </stream>
第六步: Server1和Server2尝试通过TCP完成TLS协商(详见TLS).
第七步: 如果TLS协商成功了, Server1在受TLS保护的TCP连接上初始化一个新流到Server2:
S1: <stream:stream from='im.example.com' to='example.net' version='1.0' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'>
第七步(替代): 如果TLS协商不成功, Server2关闭TCP连接(所以, 流协商过程以不成功结束并且双方不再进行下一步).
SASL
第八步: Server2发送一个应答流头给Server1并带上可用的流特性(包括优先的SASL EXTERNAL机制):
S2: <stream from='example.net' id='RChdjlgj/TIBcbT9Keu31zDihH4=' to='im.example.com' version='1.0' xmlns='http://etherx.jabber.org/streams'> S2: <features xmlns='http://etherx.jabber.org/streams'> <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <mechanism>EXTERNAL</mechanism> </mechanisms> </features>
第九步: Server1选择EXTERNAL机制(包含一个"="的空应答):
S1: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='EXTERNAL'>=</auth>
第十步: Server2返回成功:
S2: <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
第十步(替代): Server2通知Server1验证失败了(所以, 流协商过程以不成功结束并且双方不再进行下一步):
S2: <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'> <not-authorized/> </failure> </stream>
第十一步: Server1初始化一个新流到Server2:
S1: <stream:stream from='im.example.com' to='example.net' version='1.0' xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams'>
第十二步: Server2发送一个流头给并带上任何附加的特性(或, 在这个例子中, 一个空的特性元素)来应答:
S2: <stream from='example.net' id='MbbV2FeojySpUIP6J91qaa+TWHM=' to='im.example.com' version='1.0' xmlns='http://etherx.jabber.org/streams'> S2: <features xmlns='http://etherx.jabber.org/streams'/>
节交换
现在Server1被允许通过已协商的从im.example.com到example.net的流发送XML节给Server2; 这里我们假定被传输的节就是前面演示的那些客户端-服务器通讯的节, 尽管是在一个服务器-服务器由'jabber:server'命名空间限定的流上.
Server1发送XML节给Server2:
S1: <message from='juliet@im.example.com/balcony' id='ju2ba41c' to='romeo@example.net' type='chat' xml:lang='en'> <body>Art thou not Romeo, and a Montague?</body> </message>
关闭
想不再发送更多消息, Server1关闭它到Server2的流但是等待从Server2的入站数据. (实践中, 流大部分时候保持打开一段时间, 因为Server1和Server2不是立刻知道流是否需要更多通讯.)
S1: </stream:stream>
和建议的流关闭握手一致, Server2同样关闭流:
S2: </stream>
Server1现在发送一个TLS close_notify警告, 从Server2接收一个close_notify警告应答, 然后中止当前的TCP连接.
处理XML节的服务器规则
每个服务器实现将包含它自己的处理接受的节的逻辑. 这写逻辑决定服务器是需要路由一个给定的节到其他域, 还是把它递送到一个本地实体(典型的是一个和本地帐号相关联的已连接客户端), 或者直接由服务器本身处理它. 本章提供处理XML节的通用规则. 然而, 特殊的XMPP应用可以定义递送规则来修改或补充以下规则(例如, 定义于XMPP‑IM的用于即时消息和联机状态信息应用的一系列递送规则).
相关推荐
此文档定义了可扩展消息出席协议(XMPP)的核心特性:协议使用XML元素在任意两个网络端点间近实时的交换结构化信息。当XMPP为交换XML数据提供一般化,可扩展的框架时,它主要用于建立满足RFC2779的即时消息与出席...
本文为互联网社区定义了一个互联网标准跟踪协议,并且申请讨论协议和提出了改进的建议。请参照“互联网官方协议标准”的最新版本(STD 1)获得这个协议的标准化进程和状态。本文可以不受限制的分发。
中文版,xmpp协议之 可扩展消息出席协议:核心 RFC3920 IM即时通讯必备 助你成功
RFC3920可扩展消息出席协议(XMPP):核心,
可扩展的消息和出席信息协议(XMPP)是一个XML应用,让任何两个或多个网络实体之间进行结构化和可扩展的准实时信息交流. 本文定义了XMPP的核心协议方法: XML流的配置和解除, 通道加密, 验证, 错误处理, 以及消息通讯...
可扩展的消息和出席信息协议(XMPP)是一个XML应用,让任何两个或多个网络实体之间进行结构化和可扩展的准实时信息交流. 本文定义了XMPP的核心协议方法: XML流的配置和解除, 通道加密, 验证, 错误处理, 以及消息通讯...
XMPP是一个流化XML[XML]元素的协议,用于准实时的交换消息和出席信息。XMPP的核心功能定义在... 本文描述XMPP核心功能的扩展和应用,XMPP核心功能提供了RFC 2779 [IMP-REQS]定义的基本的即时消息和出席信息功能。
RFC3920 可扩展的消息和出席信息协议 (XMPP): 核心协议 翻译中文版
XMPP协议之RFC6120,英文版,用户可以在此获取并查看
可扩展的消息和出席信息协议(XMPP): 核心协议 关于本文的说明 本文为互联网社区定义了一个互联网标准跟踪协议,并且申请讨论协议和提出了改 进的建议。请参照“互联网官方协议标准”的最新版本(STD 1)获得这个协议...
包含①《Instant Messaging in Java,The Jabber Protocols》、②《Developing ...④本文定义了可扩展消息和出席信息协议(XMPP)的核心功能的扩展和应用,XMPP提供了RFC 2779 定义的基本的即时消息和出席信息功能。
XMPP是一个开放式的XML...在 RFC 2779 [IMP-REQS] 中指定的提供即时消息和出席信息功能的扩展,定义在 XMPP-IM 协议 [the Extensible Messaging and Presence Protocol (XMPP): Instant Messaging and Presence] 中。
本文定义了可扩展消息和出席信息协议(XMPP)的核心功能,这个协议采用XML流实现在任意两个网络终端接近实时的交换结构化信息。XMPP提供一个通用的可扩展的框架来交换XML数据,它主要用来建立即时消息和出席信息应用...
本文定义了可扩展消息和出席信息协议(XMPP)的核心功能,这个协议采用 XML 流实现在任意两个网络终端接近实时的交换结构化信息。XMPP 提供一个通用的可 扩展的框架来交换XML数据,它主要用来建立即时消息和出席信息...
本文定义了可扩展消息和出席信息协议(XMPP)的核心功能,这个协议采用XML流实现在任意两个网络终端接近实时的交换结构化信息。XMPP提供一个通用的可扩展的框架来交换XML数据,它主要用来建立即时消息和出席信息应用...
支持的文件: RFC6120 :可扩展消息和状态协议(XMPP):核心RFC6121 :可扩展消息和状态协议(XMPP):即时消息和状态XEP-0198 :流管理XEP-0085 :聊天状态通知XEP-0318 :客户端启动的状态探针的最佳实践XEP-...
Ftp协议:RFC959和HTTP协议:RFC2616
本文定义了可扩展消息和出席信息协议(XMPP)的核心功能,这个协议采用XML流实现在任意两个网络终端接近实时的交换结构化信息。XMPP提供一个通用的可扩展的框架来交换XML数据,它主要用来建立即时消息和出席信息应用...
中文版的SIP协议,RFC3261 嫌英文看着麻烦的可以试试
本文定义提供了遵循RFC2779要求的基本的即时消息(IM)和出席信息功能的可扩展的消息和出席信息协议(XMPP)的核心功能的扩展. 本文取代了 RFC 3921