ejabberd分析(二) 用户注册


ejabberd中由ejabberd_c2s处理:

ejabberd_c2s模块启动后gen_fsm的状态位于wait_for_stream

客户端发送

<stream:stream to="localhost" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
服务器端wait_for_stream 函数中经过

   case xml:get_attr_s("xmlns:stream", Attrs) of
	?NS_STREAM ->
            ......
                case xml:get_attr_s("version", Attrs) of
                      "1.0" ->
                send_header(StateData, Server, "1.0", DefaultLang),
发送如下的响应给客户端

<?xml version='1.0' encoding='UTF-8'?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="kinglong" id="fecabb98" xml:lang="en" version="1.0">

 由于客户端在注册时并未通过鉴权,所以wait_for_stream 中经过如下路径

case StateData#state.authenticated of
		false ->
           ......
            send_element(StateData,
                         {xmlelement, "stream:features", [],
                          TLSFeature ++ CompressFeature ++
                          [{xmlelement, "mechanisms",
                            [{"xmlns", ?NS_SASL}],
                            Mechs}] ++
                          ejabberd_hooks:run_fold(
                            c2s_stream_features,
                            Server,
                            [], [Server])}),
           fsm_next_state(wait_for_feature_request,
                           StateData#state{
                         server = Server,
                         sasl_state = SASLState,
                         lang = Lang});
向客户端发送feature消息,并将当前状态设置为wait_for_feature_request

<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism><mechanism>JIVE-SHAREDSECRET</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism><mechanism>CRAM-MD5</mechanism></mechanisms><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><auth xmlns="http://jabber.org/features/iq-auth"/><register xmlns="http://jabber.org/features/iq-register"/></stream:features>

客户端发送给服务器端

<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>

服务器端函数wait_for_feature_request中经过如下路径,并将状态再次设置为wait_for_stream:

{?NS_TLS, "starttls"} when TLS == true,
				   TLSEnabled == false,
				   SockMod == gen_tcp ->

客户端发送给服务器端

<stream:stream to="kinglong" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">
服务器端wait_for_stream中仍然经由以下路径:

case xml:get_attr_s("xmlns:stream", Attrs) of
	?NS_STREAM ->
        ......
            case xml:get_attr_s("version", Attrs) of
            "1.0" ->
               ......
               case StateData#state.authenticated of
                false ->
                   ......
                   send_element(StateData,
                         {xmlelement, "stream:features", [],
                          TLSFeature ++ CompressFeature ++
                          [{xmlelement, "mechanisms",
                            [{"xmlns", ?NS_SASL}],
                            Mechs}] ++
                          ejabberd_hooks:run_fold(
                            c2s_stream_features,
                            Server,
                            [], [Server])}),

send_element函数发送如下消息给客户端,展示服务器端可提供的鉴权方法,并再次将状态设置为wait_for_feature_request

<?xml version='1.0' encoding='UTF-8'?>
 <stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="kinglong" id="fecabb98" xml:lang="en" version="1.0">
   <stream:features>
     <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
        <mechanism>DIGEST-MD5</mechanism>
        <mechanism>JIVE-SHAREDSECRET</mechanism>
        <mechanism>PLAIN</mechanism>
        <mechanism>ANONYMOUS</mechanism>
        <mechanism>CRAM-MD5</mechanism>
     </mechanisms>
     <compression xmlns="http://jabber.org/features/compress">
        <method>zlib</method>
     </compression>
     <auth xmlns="http://jabber.org/features/iq-auth"/>
     <register xmlns="http://jabber.org/features/iq-register"/>
   </stream:features>

由于是注册过程,所以客户端并不会从中挑一种开始鉴权过程,而是发送如下的iq消息给服务器端。以下消息查询服务器端注册时所需要的参数

<iq id="2hSnG-4" to="kinglong" type="get"><query xmlns="jabber:iq:register"></query></iq>

服务器端的wait_for_feature_request 函数中按照xmlns属性处理:

case {xml:get_attr_s("xmlns", Attrs), Name} of
    {?NS_SASL, "auth"} when not ((SockMod == gen_tcp) and TLSRequired) ->
         ......
    {?NS_TLS, "starttls"} when TLS == true,
                   TLSEnabled == false,
                   SockMod == gen_tcp ->
         ......
    {?NS_COMPRESS, "compress"} when Zlib == true,
                    ((SockMod == gen_tcp) or
                     (SockMod == tls)) ->
         ......
    _ ->
         .......
         process_unauthenticated_stanza(StateData, El),
由于客户端发送的是iq消息,xmlns会匹配到最后一项。

在process_unauthenticated_stanza函数中:

case jlib:iq_query_info(NewEl) of
	#iq{} = IQ ->
        Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq,
                      StateData#state.server,
                      empty,
                      [StateData#state.server, IQ,
                       StateData#state.ip]),
调用含有名为c2s_unauathenticated_iq回调函数的模块来处理iq消息。

此回调函数在mod_register模块中定义:

start(Host, Opts) ->
    ......
    ejabberd_hooks:add(c2s_unauthenticated_iq, Host,
 		       ?MODULE, unauthenticated_iq_register, 50),
    ......
其对应于mod_register模块中的unathenticated_iq_register方法
unauthenticated_iq_register(_Acc,
			    Server, #iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
    Address = case IP of
		 {A, _Port} -> A;
		  _ -> undefined
	      end,
    ResIQ = process_iq(jlib:make_jid("", "", ""),
 		       jlib:make_jid("", Server, ""),
 		       IQ,
		       Address),
    Res1 = jlib:replace_from_to(jlib:make_jid("", Server, ""),
 				jlib:make_jid("", "", ""),
 				jlib:iq_to_xml(ResIQ)),
    jlib:remove_attr("to", Res1);
以上标蓝的部分即为具体的处理函数。process_iq 将iq按照type 分为两类来处理:

process_iq(From, To,
	   #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,
	   Source) ->
           ......
              case Type of
                 set ->
           ......
                 get ->
             

本次客户端发送的iq type 为get ,所以匹配到get

至于下面的判断我们目前可以直接无视,匹配到true就OK。

if IsCaptchaEnabled and not IsRegistered ->
   .....

   true ->
            IQ#iq{type = result,
              sub_el = [{xmlelement,
                     "query",
                     [{"xmlns", "jabber:iq:register"}],
                     [{xmlelement, "instructions", [],
                       [{xmlcdata,
                     translate:translate(
                       Lang,
                       "Choose a username and password "
                       "to register with this server")}]},
                      {xmlelement, "username", [], UsernameSubels},
                      {xmlelement, "password", [], []}
                      | QuerySubels]}]}
于是服务器端发送类似如下的响应给客户端:

<iq type="result" id="2hSnG-4" from="kinglong">
<query xmlns="jabber:iq:register">
<username/><password/>
<email/><name/>
<x xmlns="jabber:x:data" type="form">
<title>XMPP Client Registration</title>
<instructions>Choose a username and password to register with this server</instructions>
<field var="FORM_TYPE" type="hidden">
<value>jabber:iq:register</value>
</field>
<field var="username" type="text-single" label="Username">
<required/>
</field>
<field var="name" type="text-single" label="Full name"/>
<field var="email" type="text-single" label="Email"/>
<field var="password" type="text-private" label="Password">
<required/>
</field>
</x>
</query>
</iq>
注意,我们在函数process_unauthenticated_stanza处理完当前的iq后状态仍然设置为了wait_for_feature_request。

客户端按照服务器要求的参数发送注册信息给服务器:

<iq id="2hSnG-5" to="kinglong" type="set"><query xmlns="jabber:iq:register"><username>15555215557</username><email></email><name></name><password>123</password></query></iq>
由于我们的状态没变,消息同样也是iq消息,type=set 所以本次轮到了set的处理:

process_iq(From, To,
	   #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,
	   Source) ->
           ......
              case Type of
                 set ->
           ......
                 get ->
set 项是一个if 结构的语句:

if (UTag /= false) and (RTag /= false) and AllowRemove ->
       ......
   (UTag == false) and (RTag /= false) and AllowRemove ->
       ......
   (UTag /= false) and (PTag /= false) ->
      .......
   IsCaptchaEnabled ->
      .......
   true ->
            IQ#iq{type = error,
              sub_el = [SubEl, ?ERR_BAD_REQUEST]}
UTag、PTag、RTag 分别对应于username,password,remove

正常的注册流程走

(UTag == false) and (RTag /= false) and AllowRemove ->

 ......
try_register_or_set_password( User, Server, Password, From, IQ, SubEl, Source, Lang, not IsCaptchaEnabled)
这里有一个比较关键的变量IsCaptchaEnabled 他是模块的配置参数之一,默认为false。

所以我们在调用try_register_or_set_password 时会匹配到如下代码:

_ when CaptchaSucceed ->

具体注册由try_register 函数完成。

经过ip验证后 调用ejabberd_auth:try_register(
                       User, Server, Password)

ejabberd_auth中遍历配置文件中的每个MOD,并调用try_register/3 方法。注意:这里配置文件中写的只是模块名称的一部分,完整的为:ejabberd_auth_XXXX

例如ejabberd.cfg 中配置为{auth_method, internal}.那么实际调用的为ejabberd_auth_internal:try_register/3

最终我们在try_register/3 中看到如下的代码:

F = fun() ->
			case mnesia:read({passwd, US}) of
			    [] ->
				Password2 = case is_scrammed() and is_list(Password) of
						true -> password_to_scram(Password);
						false -> Password
					    end,
				mnesia:write(#passwd{us = US,
						     password = Password2}),
				mnesia:dirty_update_counter(
						    reg_users_counter,
						    LServer, 1),
				ok;
			    [_E] ->
				exists
			end
		end,
	    mnesia:transaction(F)

这就是最终注册的代码了。








  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值