XEP-0114中定义了Jabber组件协议(Jabber Componet Protocol)。XMPP网络外的可信组件可以使用这个协议和XMPP网络内实体进行通信。
组件协议定义了两种模式:
- accept:外部组件向XMPP服务器发起连接
- connect:XMPP服务器向外部组件发起连接
其中, accept方式使用比较广泛,ejabberd中只实现了accept方式。
组件协议像XMPP一样,也是基于XML流,使用的XMLNS为jabber:componet:accept
或者jabber:component:connect
。
accept方式的协议流程:
外部组件建立到XMPP服务器的TCP连接,发送流头。
1
2
3
4<stream:stream
xmlns='jabber:component:accept'
xmlns:stream='http://etherx.jabber.org/streams'
to='plays.shakespeare.lit'>XMPP服务器回应,也发送流头,其中必须包括流ID属性:
1
2
3
4
5<stream:stream
xmlns:stream='http://etherx.jabber.org/streams'
xmlns='jabber:component:accept'
from='plays.shakespeare.lit'
id='3BF96D32'>外部组件发送身份验证摘要信息。
1
<handshake>aaee83c26aeeafcbabeabfcbcd50df997e0a2a1e</handshake>
组件协议身份验证不使用SASL,也不使用已废弃的XEP-0078。它使用双方共享密钥计算摘要信息来验证身份。计算方法如下:
- 将服务器流头中的流ID属性和共享密钥拼接成字符串
- 计算该字符串的SHA1哈希值,并转换成小写16进制字符串
XMPP服务器用同样方法计算进行校验。通过后,返回一个空的handshake元素。
1
<handshake/>
至此,外部组件和XMPP服务器就可以交换XMPP消息了。
我们来看ejabberd中组件协议实现,位于ejabberd_service.erl模块中。
ejabberd中的ejabberd_service的默认配置为:
1 | {8888, ejabberd_service, [ |
ejabberd_service是端口8888的处理模块。当有ejabberd接收端口上的TCP连接后,ejabberd_socket:start/4调用处理模块的socket_type/0, 根据返回值进行不同处理。ejabberd_service:socket_type/0返回xml_stream。它的处理流程和ejabberd_c2s模块相同。ejabberd为每个TCP连接分别创建一个receiver进程和一个处理进程(这里是ejabberd_service进程)。receiver进程接收消息并解析,然后发送相应的消息给处理进程。具体不再详述,请参考:ejabberd消息处理流程分析
。
service进程为gen_fsm进程,初始状态为wait_for_stream。receiver进程接收到XML流头后发送xmlstreamstart消息给service进程。service进程调用wait_for_stream函数进行处理。
1 | wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> |
wait_for_stream检测到XML流头中XMLNS为”jabber:component:accept”后,向组件发送流头,状态变更为wait_for_handshake。
receiver进程收到handshake消息后,发送xmlstreamelement消息给service进程,service调用wait_for_handshake处理。
1 | wait_for_handshake({xmlstreamelement, El}, StateData) -> |
wait_for_handshake使用XML流ID和密码计算身份验证摘要,和组件所发的摘要信息进行对比判断是否通过。检查通过后,发送空的handshake元素。然后调用ejabberd_router:register_route/1依次注册配置的所有service域名。这样,XMPP实体发往这些域名的消息都将被ejabberd_router路由给该service进程。service进程状态变更为stream_established。
至此,外部组件就可以和XMPP服务器交换XMPP消息了。
组件向XMPP服务器发送消息后,receiver进程解析后向service进程发送xmlstreamelement消息,service进程调用stream_established处理。
1 | stream_established({xmlstreamelement, El}, StateData) -> |
stream_established进行一系列检查后,调用ejabberd_router:route转发消息。
XMPP实体发送给service域名的消息会由ejabberd_router以route消息的格式发给service进程。service进程调用handle_info处理。
1 | handle_info({route, From, To, Packet}, StateName, StateData) -> |
handle_info首先进行ACL检查,通过后,修改From和To属性,将消息发送给组件。
使用telnet演示简单登录过程:
1 | [root@flygoast flygoast]# telnet 127.0.0.1 8888 |
身份校验信息计算:
1 | [root@flygoast flygoast]# echo -n "2744762983secret" | sha1sum |