文章标签 » jabberd2

install proxy65 on centOS 5

再次对centos的python版本表示无语

wget http://www.python.org/ftp/python/2.6.6/Python-2.6.6.tar.bz2
tar jxvf Python-2.6.6.tar.bz2
cd Python-2.6.6
./configure --prefix=/opt/python26
make
make install
 
cd ..
#试过10.2.0版本,不能用。。。
wget http://tmrc.mit.edu/mirror/twisted/Twisted/10.1/Twisted-10.1.0.tar.bz2
tar jxvf Twisted-10.1.0.tar.bz2
cd Twisted-10.1.0
/opt/python26/bin/python setup.py install
 
cd ..
wget http://www.zope.org/Products/ZopeInterface/3.3.0/zope.interface-3.3.0.tar.gz
tar zxvf zope.interface-3.3.0.tar.gz
cd zope.interface-3.3.0
/opt/python26/bin/python setup.py install
 
cd ..
wget http://proxy65.googlecode.com/files/Proxy65-1.2.0.tar.gz
tar zxvf Proxy65-1.2.0.tar.gz
cd Proxy65-1.2.0
/opt/python26/bin/python setup.py install
 
#OK
/opt/python26/bin/python /opt/python26/bin/twistd proxy65 --jid=proxy.xxx.com --secret=secret --rhost=127.0.0.1 --rport=5347 --proxyips=127.0.0.1:7777

———–
post by gmail~

ubuntu下安装proxy65

proxy65安装时报错:
exceptions.ImportError: No module named words.protocols.jabber

文档里只说python setup.py install就OK了
解决:
sudo apt-get install python-twisted

———–
post by gmail~

jabberd2源码分析:sasl认证部分

此篇为https://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:<?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'>
S:<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' id='yqvecsv64nooedcwo2mbt6f8x08krmlpch3oazve'>

之后router给出自己支持的验证方式列表,说它只支持DIGEST—MD5

S:<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>

c2s回应说它接受DIGEST-MD5方式

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

router给出challenge code

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

c2s回应:

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

router再出一招

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

c2s接

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

router说验证通过

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

验证通过之后的逻辑跟这里就无关了,需要注意的是验证通过之后这个stream并没有继续利用,c2s会发一个新的stream头,和router重新开始一个stream会话。

疑问:组件和router之间为什么要有验证机制?
虽然c2s,sm,s2s都在本地运行,但是如果一个远程进程可以连接到5347端口,那么它就可以向router注册。所以验证是必要的,所以5347端口的保护也是必要的。

下面是各个阶段在代码中的体现,主要从c2s的角度分析:
在这之前你必须首先理解jabberd2的io逻辑:https://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中同理

706
707
/* 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包

<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又是层层回调,来到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);
构造了一个

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

回应router

router回应

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

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
比较关键的一个函数,全贴出来吧
啥也不解释:看https://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给出结果

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

重复上面,但是回调到_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~

jabberd2源码分析备忘:mio和sx部分

jabberd2的mio和sx目录是灰常重要的两个目录,关系底层逻辑:
mio封装了一套多路io的实现,支持kqueue,epoll,poll,select,可以在configure的时候通过–enable-mio=指定。
sx目录的代码则在mio的上层实现了一条支持插件的逻辑通道,一些非核心xmpp协议都可以通过插件的形式加入,例如sx/compress.c可以实现流的压缩。

代码包装层次比较深,看起来比较绕,所以以c2s目录里的main.c为入口,画了一张c2s与router的第一次写读的流程,当然这里没有错误处理部分。基本上展现了jabberd2的io逻辑。

      +--------------------------------------------------------------+
      | main.c                                                       |
      | 初始化mio和sx                                                |
      | 调用sx_client_init                                           |
      | 调用注册client初始化的插件                                   |
      | 构造stream头                                                 |
      | 调用注册stream头处理的插件                                   |
      | 将头追加到写缓存                                             |
    +-+ 触发event_WANT_WRITE:_sx_event(s, event_WANT_WRITE, NULL);   |
    | |                                                              |
    | | main loop(mio_run())                                         |
    | | while true:                                                  |
 +--|-+>if FD(m,fd)->type == type_NORMAL && MIO_CAN_READ(m,iter)     |
 |  | | 触发:action_READ                                             |
 |  | | 回调:c2s_router_mio_callback                                 |
 |  | | 调用:sx_can_read                                             |
 |  | | 触发:event_READ                                       -------------------------+
 |  | | if 读入.length > 0                                           |                 |
 |  | | 调用注册io读的插件:_sx_chain_io_read(s, out);                |                 |
 |  | | 调用:_sx_process_read(s, out);                               |                 |
 |  | | xml->nad                                                     |                 |
 |  | | 调用注册xml处理的插件                                        |                 |
 |  | | 触发:event_PACKET -|                                         |                 |
 |  | +--------------------+-----------------------------------------+                 |
 |  |                      |                                                           |
 |  |                      |                                                           |
 |  |                      |                                                           |
 |  |                      v                                                           |
 |  | +-----------------------------------------+                                      |
 |  | |  event_PACKET                           |                                      |
 |  | +-----------------------------------------+                                      |
 |  | |  回调:c2s_router_sx_callback            |                                      |
 |  | |  接收到的xml在这里已经被解成nad结构体   |                                      |
 |  | |  正式逻辑开始                           |                                      |
 |  | +-----------------------------------------+                                      |
 |  |                                                                                  |
 |  |                                                                                  |
 |  |                                                                                  |
 |  | +--------------------------------+       +------------------------------+        |
 |  |>  event_WANT_WRITE               | +---> |  event_WRITE                 |        |
 |    +--------------------------------+ |     +------------------------------+        |
 |    | 回调:c2s_router_sx_callback    | |     |  回调:c2s_router_sx_callbac  |        |
 |    | 调用:_mio_write                | |     |  调用:send()将数据送出       |        | 
 |    | 触发:action_WRITE              | |     +------------------------------+        |
 |    | 回调:c2s_router_mio_callback   | |                                             |
 |    | 调用:sx_can_write              | |                                             |
 |    | if 写缓存.length > 0           | |                                             |
 |    | 触发:event_WRITE            -----+                                             |
 |    | endif                          |                                               |
 |    | 触发:event_WANT_READ        -------+                                           |
 |    |                                |   |                                           |
 |    +--------------------------------+   |                                           |
 |                                         |                                           |
 |    +----------------------------------+ |   +-------------------------------+       |
 |    | event_WANT_READ                 <+-+   | event_READ                   <+-------+
 |    +----------------------------------+     +-------------------------------+        
 |    | 回调:c2s_router_sx_callback      |     | 回调:c2s_router_sx_callback   |        
 |    | 调用:_mio_read                   |     | 调用:recv()将数据读入         |        
 +----+-调用:MIO_SET_READ(m, FD(m,fd));  |     |                               |        
      |                                  |     |                               |        
      +----------------------------------+     +-------------------------------+        

-----------
post by gmail~

centos5安装jabberd2备忘

11.29日更正配置文件编辑部份
————————————-
之前已经yum安装过gcc和mysql

rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-4.noarch.rpm
yum install expat expat-devel libidn libidn-devel udns udns-devel libgsasl libgsasl-devel openssl openssl-devel mysql-devel
cd ~
mkdir src
mkdir /opt/jabberd
cd src
wget http://codex.xiaoka.com/pub/jabberd2/releases/jabberd-2.2.11.tar.bz2
tar jxvf jabb*
cd jabb*
#初始化mysql
mysql -uroot -pxxxxx < tools/db-setup.mysql
./configure --prefix=/opt/jabberd --enable-mio=epoll --enable-mysql --enable-debug --enable-mio-debug
#因为我是开发之用所以加了调试选项--enable-debug --enable-mio-debug
make
make install
#为启动服务添加一个用户
useradd -d /dev/null jabber
cd /opt/jabberd
chown jabber:jabber -R ./
#然后按照文档改配置文件,下面直接写成sed表达式,方便以后自动化完成:http://www.jabberdoc.org/AdminGuide
#<id register-enable='true'>example.com</id>
sed -i "s/\(<id reg[^>]*>\)[^<]*/\1example.com/" etc/c2s.xml
#<id>example.com</id>
sed -i "s/\(<id>\)[^<]*/\1example.com/" etc/sm.xml
#存储方式改成mysql
sed -i "s/<module>sqlite<\/module>/<module>mysql<\/module>/" etc/c2s.xml etc/sm.xml
#设置用户名和密码
sed -i "30,\$s/<user>.*<\/user>/<user>DBUSER<\/user>/" etc/c2s.xml etc/sm.xml
sed -i "30,\$s/<pass>.*<\/pass>/<pass>DBPASS<\/pass>/" etc/c2s.xml etc/sm.xml

安装和配置到此结束,启动一下~
sudo -u jabber bin/jabberd
输出调试信息
sudo -u jabber bin/jabberd -D

———–
post by gmail~