-behaviour(cyrsasl).
--record(state, {step, nonce, username, authzid, get_password, auth_module}).
+-record(state, {step, nonce, username, authzid, get_password, auth_module,
+ host}).
start(_Opts) ->
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
stop() ->
ok.
-mech_new(_Host, GetPassword, _CheckPassword) ->
+mech_new(Host, GetPassword, _CheckPassword) ->
{ok, #state{step = 1,
nonce = randoms:get_string(),
+ host = Host,
get_password = GetPassword}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
bad ->
{error, "bad-protocol"};
KeyVals ->
+ DigestURI = xml:get_attr_s("digest-uri", KeyVals),
UserName = xml:get_attr_s("username", KeyVals),
- AuthzId = xml:get_attr_s("authzid", KeyVals),
- case (State#state.get_password)(UserName) of
- {false, _} ->
+ case is_digesturi_valid(DigestURI, State#state.host) of
+ false ->
+ ?DEBUG("User login not authorized because digest-uri "
+ "seems invalid: ~p", [DigestURI]),
{error, "not-authorized", UserName};
- {Passwd, AuthModule} ->
- Response = response(KeyVals, UserName, Passwd,
- Nonce, AuthzId, "AUTHENTICATE"),
- case xml:get_attr_s("response", KeyVals) of
- Response ->
- RspAuth = response(KeyVals,
- UserName, Passwd,
- Nonce, AuthzId, ""),
- {continue,
- "rspauth=" ++ RspAuth,
- State#state{step = 5,
- auth_module = AuthModule,
- username = UserName,
- authzid = AuthzId}};
- _ ->
- {error, "not-authorized", UserName}
+ true ->
+ AuthzId = xml:get_attr_s("authzid", KeyVals),
+ case (State#state.get_password)(UserName) of
+ {false, _} ->
+ {error, "not-authorized", UserName};
+ {Passwd, AuthModule} ->
+ Response = response(KeyVals, UserName, Passwd,
+ Nonce, AuthzId, "AUTHENTICATE"),
+ case xml:get_attr_s("response", KeyVals) of
+ Response ->
+ RspAuth = response(KeyVals,
+ UserName, Passwd,
+ Nonce, AuthzId, ""),
+ {continue,
+ "rspauth=" ++ RspAuth,
+ State#state{step = 5,
+ auth_module = AuthModule,
+ username = UserName,
+ authzid = AuthzId}};
+ _ ->
+ {error, "not-authorized", UserName}
+ end
end
end
end;
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
{error, "bad-protocol"}.
-
parse(S) ->
parse1(S, "", []).
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
+%% @doc Check if the digest-uri is valid.
+%% RFC-2831 allows to provide the IP address in Host,
+%% however ejabberd doesn't allow that.
+%% If the service (for example jabber.example.org)
+%% is provided by several hosts (being one of them server3.example.org),
+%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
+%% In that case, ejabberd only checks the service name, not the host.
+is_digesturi_valid(DigestURICase, JabberHost) ->
+ DigestURI = stringprep:tolower(DigestURICase),
+ case catch string:tokens(DigestURI, "/") of
+ ["xmpp", Host] when Host == JabberHost ->
+ true;
+ ["xmpp", _Host, ServName] when ServName == JabberHost ->
+ true;
+ _ ->
+ false
+ end.