ejabberd中由ejabberd_c2s处理:
ejabberd_c2s模块启动后gen_fsm的状态位于wait_for_stream
客户端发送
- <span style="color:#FF0000;"><stream:stream to="localhost" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"></span>
- 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),
- <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 中经过如下路径
- 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});
- <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>
客户端发送给服务器端
- <span style="color:#FF0000;"><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/></span>
服务器端函数wait_for_feature_request中经过如下路径,并将状态再次设置为wait_for_stream:
- {?NS_TLS, "starttls"} when TLS == true,
- TLSEnabled == false,
- SockMod == gen_tcp ->
客户端发送给服务器端
- <span style="color:#FF0000;"><stream:stream to="kinglong" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"></span>
- 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])}),
- <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">
- <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></span>
由于是注册过程,所以客户端并不会从中挑一种开始鉴权过程,而是发送如下的iq消息给服务器端。以下消息查询服务器端注册时所需要的参数
- <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属性处理:
- 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),
在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]),
此回调函数在mod_register模块中定义:
- start(Host, Opts) ->
- ......
- ejabberd_hooks:add(c2s_unauthenticated_iq, Host,
- ?MODULE, unauthenticated_iq_register, 50),
- ......
- unauthenticated_iq_register(_Acc,
- Server, #iq{xmlns = ?NS_REGISTER} = IQ, IP) ->
- Address = case IP of
- {A, _Port} -> A;
- _ -> undefined
- end,
- <span style="color:#000099;">ResIQ = process_iq(jlib:make_jid("", "", ""),
- jlib:make_jid("", Server, ""),
- IQ,
- Address),</span>
- Res1 = jlib:replace_from_to(jlib:make_jid("", Server, ""),
- jlib:make_jid("", "", ""),
- jlib:iq_to_xml(ResIQ)),
- jlib:remove_attr("to", Res1);
- process_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,
- Source) ->
- ......
- case Type of
- set ->
- <span style="color:#000099;">......
- get -></span>
本次客户端发送的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]}]}
- <span style="color:#FF0000;"><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></span>
客户端按照服务器要求的参数发送注册信息给服务器:
- <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>
- process_iq(From, To,
- #iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} = IQ,
- Source) ->
- ......
- <span style="color:#3333FF;"> case Type of
- </span><span style="color:#3333FF;">set -></span>
- ......
- get ->
- 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 == false) and (RTag /= false) and AllowRemove ->
- ......
- try_register_or_set_password( User, Server, Password, From, IQ, SubEl, Source, Lang, not IsCaptchaEnabled)
所以我们在调用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)
这就是最终注册的代码了。