当前位置: 首页 > 工具软件 > Jabberd2s > 使用案例 >

Jabberd2源代码分析:c2s与router SASL验证过程

周洋
2023-12-01

 

c2s启动后,主动连接router服务器的消息流程:

 

c2s->router: <?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns:ack='http://www.xmpp.org/extensions/xep-0198.html#ns'>

 

发送完<stream>数据后,c2s进入循环状态,在mio_run等待事件

 

router->c2s:

<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='svwkun4ushx8kvbc3b08loxd5gn1ng4fc0z7o42h'>

 

当c2s收到router发送的数据后mio_run函数内检测到有数据可读,这时调用:

c2s_router_mio_callback(mio_action_t = action_READ)

首先调用ioctl(fd, FIONREAD, &nbytes)得到socket缓冲区中有多少数据可读, 如果有数据可读调用sx_can_read函数。

 

在sx_can_read(sx_t s)函数中:

in = _sx_buffer_new()

创建in用于保存读到的数据

 

read = _sx_event(s, event_READ, in)

_sx_event会调用函数c2s_router_sx_callback(sx_event_t = event_READ)

case event_READ:

调用recv读取数据,读数据的长度是确定的,因为在c2s_router_mio_callback中action_READ的时候已经通过ioctl函数获取了目前缓冲区中的数据长度

 

out = _sx_buffer_new(in->data, in->len, in->notify, in->notify_arg)

创建一个in的拷贝,用于将out传入到插件函数中

 

_sx_chain_io_read(s, out)函数的目的是调用插件函数rio

 

插件函数中没有发生错误的话,调用数据解析函数

_sx_process_read(s, out)

在_sx_process_read函数中调用了XML_Parse来解析XML数据,expat解析的特点是解析的过程中会调用特定的回调函数。

 

还记得么,在sx_client_init函数中,_sx_buffer_new函数中传入了一个回调函数_sx_client_notify_header函数,在该回调函数中又调用了XML_SetElementHandler(s->expat, _sx_client_element_start, _sx_client_element_end)。

 

当XML_Parse解析到stream头的时候,调用_sx_client_element_start函数, 解析出stream头,重新设置解析回调函数

XML_SetElementHandler(s->expat, _sx_element_start, _sx_element_end)

XML_SetCharacterDataHandler(s->expat, _sx_cdata)

XML_SetStartNamespaceDeclHandler(s->expat, _sx_namespace_start)

 

调用插件函数stream

 

插件sasl:

sasl插件注册了函数stream,这里会调用sasl注册的函数_sx_sasl_stream,此时Gsasl_session指针为空,所以什么也不做,直接返回。

 

如果当前状态小于state_STREAM设置状态为state_STREAM,并且调用_sx_event(event_STREAM)。

 

router->c2s:

<stream:features xmlns:stream='http://etherx.jabber.org/streams'><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism></mechanisms></stream:features>

 

mio检测到有数据可读,经过层层回调到sx_can_read->_sx_process_read, 这次成功解析到<stream:features>,将解析完成后的结果保存在NAD中,并且插入到s->rnadq队列解析完成后,继续在_sx_process_read函数中。

此时s->state的状态为state_STREAM,从s->rnadq队列中取出一个NAD,对解析的数据做错误检测,如果没有错误发生调用函数:

sx_chain_nad_read(s, nad),该函数会调用插件函数 rnad

 

调用插件函数 process

 

sasl

sasl注册了process插件函数_sx_sasl_process.

在_sx_sasl_process函数中会判断命名空间,只有urn:ietf:params:xml:ns:xmpp-sasl的命名空间才会处理,其他命名空间的数据包直接返回不处理。

 

调用_sx_event(s, event_PACKET, nad), 会调用c2s_router_sx_callback

s->state = state_STREAM

调用sx_sasl_auth(c2s->sx_sasl, s, "jabberd-router", "DIGEST-MD5", c2s->router_user, c2s->router_pass)

 

如果采用gsasl的话

初始化一个客户端gsasl session

ret = gsasl_client_start(ctx->gsasl_ctx, mech, &sd);

设置验证需要的数据

gsasl_session_hook_set(sd, (void *) ctx);

gsasl_property_set(sd, GSASL_AUTHID, user);

gsasl_property_set(sd, GSASL_PASSWORD, pass);

 

gsasl_property_set(sd, GSASL_SERVICE, appname);

gsasl_property_set(sd, GSASL_HOSTNAME, hostname);

 

// 设置插件数据

s->plugin_data[p->index] = (void *) sd;

 

// 构造一个nad

    nad = nad_new();

    ns = nad_add_namespace(nad, uri_SASL, NULL);

    nad_append_elem(nad, ns, "auth", 0);

    nad_append_attr(nad, -1, "mechanism", mech);

 

sx_nad_write(s, nad)

 

调用插件函数 wnad

 

构成生成字符串:<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>

将要发送的数据放入队列s->wbufq中,调用_sx_event(event_WANT_WRITE)

 

c2s->router:

<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>

 

router->c2s:

<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09ImphYmJlcmQtcm91dGVyIiwgbm9uY2U9Ilo1SU4zSzRQNlVNYzViQjlEZ0RkdUE9PSIsIHFvcD0iYXV0aCIsIGNoYXJzZXQ9dXRmLTgsIGFsZ29yaXRobT1tZDUtc2Vzcw==</challenge>

 

进过一系列的回调函数,最终执行到_sx_process_read函数

调用插件函数process

这次还会调用sasl注册的process函数_sx_sasl_process, 这一次命名空间是一样的。继续处理。

一切顺利,将会调用_sx_sasl_response生成xml串发送给router

c2s->router:

<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dXNlcm5hbWU9ImphYmJlcmQiLCByZWFsbT0iamFiYmVyZC1yb3V0ZXIiLCBub25jZT0ibVF3VlRVbjRtUXBweVd0UEoyTmdoQT09IiwgY25vbmNlPSI2UkpSSm53dlZJcnEvOERFaHBqSTV3PT0iLCBuYz0wMDAwMDAwMSwgcW9wPWF1dGgsIGRpZ2VzdC11cmk9ImphYmJlcmQtcm91dGVyL2Vhc3lqYWJiZXJkLmNvbSIsIHJlc3BvbnNlPWM2NGZjYTA3YjAxMGE4ZmI4NmQ3OWVmZmFmOWY2OTg3LCBjaGFyc2V0PXV0Zi04</response>

 

只有router继续发送challenge, c2s处理机制同上,最终router发回:

router->c2s:

<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>

 

c2s处理机制同上,调用_sx_sasl_process函数,但这次处理不同的分支。收到success后,将当前的sx_t重置,并调用sx_client_init(s, flags, ns, to, from, version) 重新开启一个stream.

 

进行第二次stream的时候,处理流程和第一次发送stream的时候有点区别。

解析到router返回的stream的时候,c2s会调用插件函数stream, sasl注册了stream插件函数, 但第一次的时候判断Gsasl_session指针为空,所以什么也没有执行;但第二次这个指针就不为空了,这次继续往下执行,调用了函数_sx_sasl_open函数,来校验stream是否已经通过sasl验证,最终会执行到:

_sx_state(s, state_OPEN);

_sx_event(s, event_OPEN, NULL);

此时当前通道的状态已经从state_STREAM转变成state_OPEN。 并且触发事件event_OPEN;

 

在c2s_router_sx_callback的event_OPEN处理部分,开始一个新的请求:

c2s->router:

<bind xmlns='http://jabberd.jabberstudio.org/ns/component/1.0' name='c2s'/>

 

 

 

 

 

 类似资料: