大致流程如图:
ejabberd 的用户注册主要由 ejabberd_c2s
模块处理。该模块实现了一个有限状态机,即 gen_fsm
.
有限状态机可以用如下的公式表示:
即在 S 状态时如果有事件 E 发生,那么执行动作 A 后把状态调整到 S'.
以 Register Module 为例来跟踪 ejabberd 中对消息的处理:
启动
ejabberd 启动时初始状态为 wait_for_stream
,即等待来自 Client 的消息。此时收到消息由 wait_for_stream
函数处理。
wait_for_stream
由于是注册模块,因此只追踪未鉴权的消息。
此时应向客户端发送 feature 消息,展示当前支持的 sasl 机制,并将当前状态设置为 wait_for_feature_request
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});
```
### wait_for_feature_request
收到服务端提供的 sasl mechanisms 消息后,由于是注册流程,客户端并不会选择任何一种 mechanism 开始 sasl 认证,而是会向服务端发送如下形式的 iq 消息请求注册参数:
```XML
<iq id="xxxx" to="xxx" type="get"><query xmlns="jabber:iq:register"></query></iq>
wait_for_feature_request
函数获取 xmlns 属性,并一直匹配到 process_unauthenticated_stanza
:
wait_for_feature_request({xmlstreamelement, El},
StateData) ->
#xmlel{name = Name, attrs = Attrs, children = Els} = El,
Zlib = StateData#state.zlib,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
SockMod =
(StateData#state.sockmod):get_sockmod(StateData#state.socket),
case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of
%%% ......
_ ->
if (SockMod == gen_tcp) and TLSRequired ->
Lang = StateData#state.lang,
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
send_trailer(StateData),
{stop, normal, StateData};
true ->
process_unauthenticated_stanza(StateData, El),
fsm_next_state(wait_for_feature_request, StateData)
end
end;
process_unauthenticated_stanza
在 process_unauthenticated_stanza
函数中,匹配到 iq 消息调用 c2s_unauthenticated_iq
(该函数位于 mod_register
模块中)处理。
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_unauthenticated_iq
该函数实际上通过 hook 的方式调用了 mod_register
模块中的 unauthenticated_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 消息的函数,它将消息分为 get
和 set
两种类型:
process_iq(From, To,
#iq{type = Type, lang = Lang, sub_el = SubEl, id = ID} =
IQ,
Source) ->
%%% ......
case Type of
set ->
%%% ......
get ->
%%% ......
客户端首先发送 get
类型的消息,服务端收到后会发送包含注册所需参数的消息给客户端(此时状态会重置为 wait_for_feature_request
).
客户端按照参数发送注册 iq 消息给服务端,此时的 type
将设置为 set
.经过同样的处理路由,进入注册流程。
IsCaptchaEnabled ->
case ejabberd_captcha:process_reply(SubEl) of
ok ->
case process_xdata_submit(SubEl) of
{ok, User, Password} ->
try_register_or_set_password(User, Server,
Password, From, IQ,
SubEl, Source, Lang,
true);
_ ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_BAD_REQUEST]}
end;
{error, malformed} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
_ ->
ErrText = <<"The CAPTCHA verification has failed">>,
IQ#iq{type = error,
sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, ErrText)]}
end;
try_register_or_set_password
由于是注册流程,会进入 try_register
函数中,并调用 ejabberd_auth
模块中的 try_register
函数:
try_register(_User, _Server, <<"">>) ->
{error, not_allowed};
try_register(User, Server, Password) ->
case is_user_exists(User, Server) of
true -> {atomic, exists};
false ->
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
true ->
Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res;
(M, _) ->
M:try_register(User, Server, Password)
end,
{error, not_allowed}, auth_modules(Server)),
case Res of
{atomic, ok} ->
ejabberd_hooks:run(register_user, Server,
[User, Server]),
{atomic, ok};
_ -> Res
end;
false -> {error, not_allowed}
end
end.
该函数会根据选择的配置来调用不同的模块注册(这些模块都以 ejabberd_auth_
开头),比如 ejabberd_auth_internal
中的 try_register/3
,且在调用注册方法之前会先调用各模块中的 is_user_exists
来判断给出的用户信息是否允许注册:
try_register(User, Server, PasswordList) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Password = iolist_to_binary(PasswordList),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
true ->
F = fun () ->
case mnesia:read({passwd, US}) of
[] ->
Password2 = case is_scrammed() and
is_binary(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)
end.
至此完成了整个用户的注册流程,状态依然重置为 wait_for_feature_request
.