此篇为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~