ejabberd分析(二) 用户注册

ejabberd中由ejabberd_c2s处理:

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

客户端发送

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><stream:stream to="localhost" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"></span>  
服务器端wait_for_stream 函数中经过

[plain]  view plain copy print ?
  1.    case xml:get_attr_s("xmlns:stream", Attrs) of  
  2.     ?NS_STREAM ->  
  3.             ......  
  4.                 case xml:get_attr_s("version", Attrs) of  
  5.                       "1.0" ->  
  6.                 send_header(StateData, Server, "1.0", DefaultLang),  
发送如下的响应给客户端

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><?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"></span>  

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

[plain]  view plain copy print ?
  1. case StateData#state.authenticated of  
  2.         false ->  
  3.            ......  
  4.             send_element(StateData,  
  5.                          {xmlelement, "stream:features", [],  
  6.                           TLSFeature ++ CompressFeature ++  
  7.                           [{xmlelement, "mechanisms",  
  8.                             [{"xmlns", ?NS_SASL}],  
  9.                             Mechs}] ++  
  10.                           ejabberd_hooks:run_fold(  
  11.                             c2s_stream_features,  
  12.                             Server,  
  13.                             [], [Server])}),  
  14.            fsm_next_state(wait_for_feature_request,  
  15.                            StateData#state{  
  16.                          server = Server,  
  17.                          sasl_state = SASLState,  
  18.                          lang = Lang});  
向客户端发送feature消息,并将当前状态设置为wait_for_feature_request

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><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></span>  

客户端发送给服务器端

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/></span>  

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

[plain]  view plain copy print ?
  1. {?NS_TLS, "starttls"} when TLS == true,  
  2.                    TLSEnabled == false,  
  3.                    SockMod == gen_tcp ->  

客户端发送给服务器端

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><stream:stream to="kinglong" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"></span>  
服务器端wait_for_stream中仍然经由以下路径:

[plain]  view plain copy print ?
  1. case xml:get_attr_s("xmlns:stream", Attrs) of  
  2.     ?NS_STREAM ->  
  3.         ......  
  4.             case xml:get_attr_s("version", Attrs) of  
  5.             "1.0" ->  
  6.                ......  
  7.                case StateData#state.authenticated of  
  8.                 false ->  
  9.                    ......  
  10.                    send_element(StateData,  
  11.                          {xmlelement, "stream:features", [],  
  12.                           TLSFeature ++ CompressFeature ++  
  13.                           [{xmlelement, "mechanisms",  
  14.                             [{"xmlns", ?NS_SASL}],  
  15.                             Mechs}] ++  
  16.                           ejabberd_hooks:run_fold(  
  17.                             c2s_stream_features,  
  18.                             Server,  
  19.                             [], [Server])}),  
send_element函数发送如下消息给客户端,展示服务器端可提供的鉴权方法,并再次将状态设置为wait_for_feature_request

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><?xml version='1.0' encoding='UTF-8'?>  
  2.  <stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="kinglong" id="fecabb98" xml:lang="en" version="1.0">  
  3.    <stream:features>  
  4.      <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">  
  5.         <mechanism>DIGEST-MD5</mechanism>  
  6.         <mechanism>JIVE-SHAREDSECRET</mechanism>  
  7.         <mechanism>PLAIN</mechanism>  
  8.         <mechanism>ANONYMOUS</mechanism>  
  9.         <mechanism>CRAM-MD5</mechanism>  
  10.      </mechanisms>  
  11.      <compression xmlns="http://jabber.org/features/compress">  
  12.         <method>zlib</method>  
  13.      </compression>  
  14.      <auth xmlns="http://jabber.org/features/iq-auth"/>  
  15.      <register xmlns="http://jabber.org/features/iq-register"/>  
  16.    </stream:features></span>  

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

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><iq id="2hSnG-4" to="kinglong" type="get"><query xmlns="jabber:iq:register"></query></iq></span>  

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

[plain]  view plain copy print ?
  1. case {xml:get_attr_s("xmlns", Attrs), Name} of  
  2.     {?NS_SASL, "auth"} when not ((SockMod == gen_tcp) and TLSRequired) ->  
  3.          ......  
  4.     {?NS_TLS, "starttls"} when TLS == true,  
  5.                    TLSEnabled == false,  
  6.                    SockMod == gen_tcp ->  
  7.          ......  
  8.     {?NS_COMPRESS, "compress"} when Zlib == true,  
  9.                     ((SockMod == gen_tcp) or  
  10.                      (SockMod == tls)) ->  
  11.          ......  
  12.     _ ->  
  13.          .......  
  14.          process_unauthenticated_stanza(StateData, El),  
由于客户端发送的是iq消息,xmlns会匹配到最后一项。

在process_unauthenticated_stanza函数中:

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

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

[plain]  view plain copy print ?
  1. start(Host, Opts) ->  
  2.     ......  
  3.     ejabberd_hooks:add(c2s_unauthenticated_iq, Host,  
  4.                ?MODULE, unauthenticated_iq_register, 50),  
  5.     ......  
其对应于mod_register模块中的unathenticated_iq_register方法
[plain]  view plain copy print ?
  1. unauthenticated_iq_register(_Acc,  
  2.                 Server, #iq{xmlns = ?NS_REGISTER} = IQ, IP) ->  
  3.     Address = case IP of  
  4.          {A, _Port} -> A;  
  5.           _ -> undefined  
  6.           end,  
  7.     <span style="color:#000099;">ResIQ = process_iq(jlib:make_jid("", "", ""),  
  8.                jlib:make_jid("", Server, ""),  
  9.                IQ,  
  10.                Address),</span>  
  11.     Res1 = jlib:replace_from_to(jlib:make_jid("", Server, ""),  
  12.                 jlib:make_jid("", "", ""),  
  13.                 jlib:iq_to_xml(ResIQ)),  
  14.     jlib:remove_attr("to", Res1);  
以上标蓝的部分即为具体的处理函数。process_iq 将iq按照type 分为两类来处理:

[plain]  view plain copy print ?
  1. process_iq(From, To,  
  2.        #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,  
  3.        Source) ->  
  4.            ......  
  5.               case Type of  
  6.                  set ->  
  7.            <span style="color:#000099;">......  
  8.                  get -></span>  
  9.                

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

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

[plain]  view plain copy print ?
  1. if IsCaptchaEnabled and not IsRegistered ->  
  2.    .....  
  3.   
  4.    true ->  
  5.             IQ#iq{type = result,  
  6.               sub_el = [{xmlelement,  
  7.                      "query",  
  8.                      [{"xmlns", "jabber:iq:register"}],  
  9.                      [{xmlelement, "instructions", [],  
  10.                        [{xmlcdata,  
  11.                      translate:translate(  
  12.                        Lang,  
  13.                        "Choose a username and password "  
  14.                        "to register with this server")}]},  
  15.                       {xmlelement, "username", [], UsernameSubels},  
  16.                       {xmlelement, "password", [], []}  
  17.                       | QuerySubels]}]}  
于是服务器端发送类似如下的响应给客户端:

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><iq type="result" id="2hSnG-4" from="kinglong">  
  2. <query xmlns="jabber:iq:register">  
  3. <username/><password/>  
  4. <email/><name/>  
  5. <x xmlns="jabber:x:data" type="form">  
  6. <title>XMPP Client Registration</title>  
  7. <instructions>Choose a username and password to register with this server</instructions>  
  8. <field var="FORM_TYPE" type="hidden">  
  9. <value>jabber:iq:register</value>  
  10. </field>  
  11. <field var="username" type="text-single" label="Username">  
  12. <required/>  
  13. </field>  
  14. <field var="name" type="text-single" label="Full name"/>  
  15. <field var="email" type="text-single" label="Email"/>  
  16. <field var="password" type="text-private" label="Password">  
  17. <required/>  
  18. </field>  
  19. </x>  
  20. </query>  
  21. </iq></span>  
注意,我们在函数process_unauthenticated_stanza处理完当前的iq后状态仍然设置为了wait_for_feature_request。

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

[plain]  view plain copy print ?
  1. <span style="color:#FF0000;"><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></span>  
由于我们的状态没变,消息同样也是iq消息,type=set 所以本次轮到了set的处理:

[plain]  view plain copy print ?
  1. process_iq(From, To,  
  2.        #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,  
  3.        Source) ->  
  4.            ......  
  5.              <span style="color:#3333FF;"> case Type of  
  6.                  </span><span style="color:#3333FF;">set -></span>  
  7.            ......  
  8.                  get ->  
set 项是一个if 结构的语句:

[plain]  view plain copy print ?
  1. if (UTag /= false) and (RTag /= false) and AllowRemove ->  
  2.        ......  
  3.    (UTag == false) and (RTag /= false) and AllowRemove ->  
  4.        ......  
  5.    (UTag /= false) and (PTag /= false) ->  
  6.       .......  
  7.    IsCaptchaEnabled ->  
  8.       .......  
  9.    true ->  
  10.             IQ#iq{type = error,  
  11.               sub_el = [SubEl, ?ERR_BAD_REQUEST]}  
UTag、PTag、RTag 分别对应于username,password,remove 

正常的注册流程走

[plain]  view plain copy print ?
  1. (UTag == false) and (RTag /= false) and AllowRemove ->  
  2.   
  3.  ......  
  4. 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 中看到如下的代码:

[plain]  view plain copy print ?
  1. F = fun() ->  
  2.             case mnesia:read({passwd, US}) of  
  3.                 [] ->  
  4.                 Password2 = case is_scrammed() and is_list(Password) of  
  5.                         true -> password_to_scram(Password);  
  6.                         false -> Password  
  7.                         end,  
  8.                 mnesia:write(#passwd{us = US,  
  9.                              password = Password2}),  
  10.                 mnesia:dirty_update_counter(  
  11.                             reg_users_counter,  
  12.                             LServer, 1),  
  13.                 ok;  
  14.                 [_E] ->  
  15.                 exists  
  16.             end  
  17.         end,  
  18.         mnesia:transaction(F)  

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



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值