此篇为http://bluehua.org/2010/12/01/1484.html在jabberd2上的延伸。
jabberd2的sasl验证部分底层使用了第三方库,同时支持Cyrus和gsasl,configure时可以通过--with-sasl=指定一个。
认证发生在两种情况,客户端连接c2s组件,组件连接router。由于我不想再去分析一个客户端,所以c2s连接router为例分析认证过程。
先简单描述下jabberd2的启动过程:
jabberd2有四个可执行文件
router:路由器
c2s:组件,负责跟客户端建立连接,注册和验证帐号
sm:组件,会话管理器
s2s:组件,负责和其他xmpp服务器通信
router做为路由器首先启动,启动后会默认监听5347,等待包处理组件连接。
c2s,sm,s2s启动,并连接router,以c2s为例,分析一下往返的xml数据(C代表c2s做为客户端,S代表router做为服务端):
c2s跟router建立连接之后,大家互相寒暄一下,c2s说我吃了,router说他也吃了(互发stream头)
C:
S:
之后router给出自己支持的验证方式列表,说它只支持DIGEST—MD5
S:DIGEST-MD5
c2s回应说它接受DIGEST-MD5方式
C:
router给出challenge code
S:cmVhbG09ImphYmJlcmQtcm91dGVyIiwgbm9uY2U9Ii8xb0tDempxMXRNSUlLUHlOYTNCSHc9PSIsIHFvcD0iYXV0aCIsIGNoYXJzZXQ9dXRmLTgsIGFsZ29yaXRobT1tZDUtc2Vzcw==
c2s回应:
C:dXNlcm5hbWU9ImphYmJlcmQiLCByZWFsbT0iamFiYmVyZC1yb3V0ZXIiLCBub25jZT0iLzFvS0N6anExdE1JSUtQeU5hM0JIdz09IiwgY25vbmNlPSJueFZNempncTI1VUZtTjZmdUozdEZ3PT0iLCBuYz0wMDAwMDAwMSwgcW9wPWF1dGgsIGRpZ2VzdC11cmk9ImphYmJlcmQtcm91dGVyL2lwLTEwLTEzMC0xODctMTU1IiwgcmVzcG9uc2U9Yzg5NjJlNDgwZTVlYmM0ZjFkNjZiZDhlNDBiNjZlZjIsIGNoYXJzZXQ9dXRmLTg=
router再出一招
S:cnNwYXV0aD1hM2IyMGI0YzZkMzgxNmE3MzVjMWJhNTRjZTVkYmFjYQ==
c2s接
C:
router说验证通过
S:
验证通过之后的逻辑跟这里就无关了,需要注意的是验证通过之后这个stream并没有继续利用,c2s会发一个新的stream头,和router重新开始一个stream会话。
疑问:组件和router之间为什么要有验证机制?
虽然c2s,sm,s2s都在本地运行,但是如果一个远程进程可以连接到5347端口,那么它就可以向router注册。所以验证是必要的,所以5347端口的保护也是必要的。
下面是各个阶段在代码中的体现,主要从c2s的角度分析:
在这之前你必须首先理解jabberd2的io逻辑:http://bluehua.org/2010/11/28/1479.html(虽然老大表示文采太差,但是我觉得要是画图都不行,那我实在没辙了)
sasl验证逻辑主要位于:sx/sasl_gsasl.c sx/sasl_cyrus.c,这里以sasl_gsasl.c为例
先画一下sasl在jabber2中的位置
+--------------------+ | callback(主要逻辑)| ----- +--------------------+ | | SX(逻辑通道) | ----- | plugins ------+-----+ +--------------------+ +---- sasl | callback | | +--------------------+ +---- | MIO(底层网络io) | | +--------------------+ |sasl层以sx插件的形式被初始化,下面为c2s/main.c中注册sasl插件的部分,router中同理
/* get sasl online */
c2s->sx_sasl = sx_env_plugin(c2s->sx_env, sx_sasl_init, "xmpp", _c2s_sx_sasl_callback, (void *) c2s);
看一下插件的初始化函数sasl_gsasl.c 906:int sx_sasl_init
//注册了以下几个回调函数
p->unload = _sx_sasl_unload;
//当数据送出时回调,貌似有的验证方法需要对送出的数据加密
p->wio = _sx_sasl_wio;
//当读入数据时回调,就是解密读入的数据
p->rio = _sx_sasl_rio;
//当一个strem建立时回调
p->stream = _sx_sasl_stream;
//当声明自己都有哪些特性时回调
p->features = _sx_sasl_features;
//当处理xml时回调
p->process = _sx_sasl_process;
下面描述一下c2s中的主要情形
....读取配置,加载插件
c2s/main.c 728行:
调用_c2s_router_connect初始化与router的sx通道
c2s/main.c 341行:
_c2s_router_connect的最后调用sx_client_init
sx/client.c 111行:
sx_client_init 构造一个stream头
sx/client.c 148行:
sx_client_init为即将写出的头加了一个回调,_sx_client_notify_header
sx写出strem头(具体怎么写还是看我画的那个图),并回调_sx_client_notify_header
sx/client.c 100行:
_sx_client_notify_header中为xml解析注册了两个回调
XML_SetElementHandler(s->expat, (void *) _sx_client_element_start, (void *) _sx_client_element_end);
mio层收到router回的stream头(具体怎么收到还是看我画的那个图...)
c2s/c2s.c 1308行:
回调到c2s_router_mio_callback
sx/io.c 181行:
c2s_router_mio_callback中调用sx_can_read
sx/io.c 24行:
sx_can_read调用_sx_process_read开始解收到的xml
解析起始标签时回调到_sx_client_element_start
sx/client.c 73行:
_sx_client_element_start解析到router给的stream头,改写xml解析回调
XML_SetElementHandler(s->expat, (void *) _sx_element_start, (void *) _sx_element_end);
sx/client.c 78行:
_sx_client_element_start 已经解析到router的strem头,strem建立,开始执行插件注册的strem回调
中间回调到sasl注册的process回调,_sx_sasl_process,但是不是它需要的包,没有动作.
由于sx_sasl_init前面注册了_sx_sasl_stream回调所以
_sx_sasl_stream用于在一个stream开始时确定是否已经做过sasl验证
sx/sasl_gsasl.c 312行 还没有gsasl session所以执行到316行便返回了
之后sx_can_read解完包触发event_PACKET,回调到c2s_router_sx_callback,没有任何动作
之后c2s收到router的stream:features包
DIGEST-MD5
经过mio又是层层回调,来到c2s_router_sx_callback
这回有动作了
c2s/c2s.c 787行:确定它收到了一个stream:features,调用sx_sasl_auth,初始化一个gsasl session
sx_sasl_auth(c2s->sx_sasl, s, "jabberd-router", "DIGEST-MD5", c2s->router_user, c2s->router_pass);
router_user和router_pass均从c2s.xml里读取
sx/sasl_gsasl.c 959行:sx_sasl_auth
初始化一个客户端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;
/* build the 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);
if(buf != NULL) {
nad_append_cdata(nad, buf, buflen, 1);
free(buf);
}
/* its away */
sx_nad_write(s, nad);
构造了一个
回应router
router回应
cmVhbG09ImphYmJlcmQtcm91dGVyIiwgbm9uY2U9Ii8xb0tDempxMXRNSUlLUHlOYTNCSHc9PSIsIHFvcD0iYXV0aCIsIGNoYXJzZXQ9dXRmLTgsIGFsZ29yaXRobT1tZDUtc2Vzcw==
c2s接受
c2s/c2s.c 1308行:
回调到c2s_router_mio_callback
sx/io.c 181行:
c2s_router_mio_callback中调用sx_can_read
sx/io.c 24行:
sx_can_read调用_sx_process_read开始解收到的xml
sx/io.c 142行:这次又回调到了_sx_sasl_process,这次有反应了,因为它匹配到xml中sasl的命名空间
sx/sasl_gsasl.c 700行:调用_sx_sasl_server_process
比较关键的一个函数,全贴出来吧
啥也不解释:看http://bluehua.org/2010/12/01/1484.html
唯一不同就是返回的结果的时候用_sx_sasl_response包成了xml
static void _sx_sasl_server_process(sx_t s, sx_plugin_t p, Gsasl_session *sd, char *in, int inlen) {
char *buf = NULL, *out = NULL;
size_t buflen, outlen;
int ret;
_sx_debug(ZONE, "data from client");
/* decode the response */
ret = gsasl_base64_from(in, inlen, &buf, &buflen);
if (ret == GSASL_OK) {
_sx_debug(ZONE, "decoded data: %.*s", buflen, buf);
/* process the data */
ret = gsasl_step(sd, buf, buflen, &out, &outlen);
if(buf != NULL) free(buf); buf = NULL;
/* in progress */
if(ret == GSASL_OK || ret == GSASL_NEEDS_MORE) {
_sx_debug(ZONE, "sasl handshake in progress (response: %.*s)", outlen, out);
/* encode the response */
ret = gsasl_base64_to(out, outlen, &buf, &buflen);
if (ret == GSASL_OK) {
_sx_nad_write(s, _sx_sasl_response(s, buf, buflen), 0);
}
if(out != NULL) free(out);
if(buf != NULL) free(buf);
return;
}
}
if(out != NULL) free(out);
if(buf != NULL) free(buf);
/* its over */
_sx_debug(ZONE, "sasl handshake aborted; (%d): %s", ret, gsasl_strerror(ret));
_sx_nad_write(s, _sx_sasl_abort(s), 0);
}
sasl_gsasl.c 611行的_sx_nad_write之后,这次处理就算结束了
之后router继续给出challenge,c2s重复上面处理
最后router给出结果
重复上面,但是回调到_sx_sasl_process,处理方式就不一样了,调用了sx_client_init(s, flags, ns, to, from, version); 重新开启了一个stream
/* success */
else if(NAD_ENAME_L(nad, 0) == 7 && strncmp("success", NAD_ENAME(nad, 0), NAD_ENAME_L(nad, 0)) == 0) {
_sx_debug(ZONE, "sasl handshake completed, resetting");
nad_free(nad);
/* save interesting bits */
flags = s->flags;
if(s->ns != NULL) ns = strdup(s->ns);
if(s->req_to != NULL) to = strdup(s->req_to);
if(s->req_from != NULL) from = strdup(s->req_from);
if(s->req_version != NULL) version = strdup(s->req_version);
/* reset state */
_sx_reset(s);
_sx_debug(ZONE, "restarting stream with sasl layer established");
/* second time round */
sx_client_init(s, flags, ns, to, from, version);
/* free bits */
if(ns != NULL) free(ns);
if(to != NULL) free(to);
if(from != NULL) free(from);
if(version != NULL) free(version);
return 0;
}
继续说下这个新起的stream把,它毫无疑问的回调到了sasl插件注册的_sx_sasl_stream,上次回调它什么都没做,是因为s->plugin_data[p->index]为NULL,但是前一个stream中在执行sx_sasl_auth时把一个gsasl seesion给了它,所以这次的逻辑就不一样了
sx/sasl_gsasl.c 326:它会继续调用_sx_sasl_open,来校验stream是否已经通过sasl验证.如果是它最终会执行
_sx_state(s, state_OPEN);
_sx_event(s, event_OPEN, NULL);
将这个通道的状态从state_STREAM变为 stat_OPEN,触发事件event_OPEN;
再看一下c2s_router_sx_callback中针对event_OPEN的处理吧
case event_OPEN:
log_write(c2s->log, LOG_NOTICE, "connection to router established");
/* set connection attempts counter */
c2s->retry_left = c2s->retry_lost;
nad = nad_new();
ns = nad_add_namespace(nad, uri_COMPONENT, NULL);
nad_append_elem(nad, ns, "bind", 0);
nad_append_attr(nad, -1, "name", c2s->id);
log_debug(ZONE, "requesting component bind for '%s'", c2s->id);
sx_nad_write(c2s->router, nad);
return 0;
开始一个绑定到router的bind请求!
OK,验证部分到此结束。。。,其他部分分析稍候
-----------
post by gmail~
想请教下, 你这个来回组件之间的数据沟通是如何得到的, 通过什么调试 ? 直接- D看LOG?