aboutsummaryrefslogtreecommitdiff
path: root/src/cyrsasl_digest.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cyrsasl_digest.erl')
-rw-r--r--src/cyrsasl_digest.erl298
1 files changed, 155 insertions, 143 deletions
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 557e498cd..3bb88431b 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -25,134 +25,145 @@
%%%----------------------------------------------------------------------
-module(cyrsasl_digest).
+
-author('alexey@sevcom.net').
--export([start/1,
- stop/0,
- mech_new/4,
- mech_step/2]).
+-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
-include("ejabberd.hrl").
-behaviour(cyrsasl).
--record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
- host, hostfqdn}).
+-type get_password_fun() :: fun((binary()) -> {false, any()} |
+ {binary(), atom()}).
+
+-type check_password_fun() :: fun((binary(), binary(), binary(),
+ fun((binary()) -> binary())) ->
+ {boolean(), any()} |
+ false).
+
+-record(state, {step = 1 :: 1 | 3 | 5,
+ nonce = <<"">> :: binary(),
+ username = <<"">> :: binary(),
+ authzid = <<"">> :: binary(),
+ get_password = fun(_) -> {false, <<>>} end :: get_password_fun(),
+ check_password = fun(_, _, _, _) -> false end :: check_password_fun(),
+ auth_module :: atom(),
+ host = <<"">> :: binary(),
+ hostfqdn = <<"">> :: binary()}).
start(_Opts) ->
Fqdn = get_local_fqdn(),
- ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~p", [Fqdn]),
- cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
+ ?INFO_MSG("FQDN used to check DIGEST-MD5 SASL authentication: ~s",
+ [Fqdn]),
+ cyrsasl:register_mechanism(<<"DIGEST-MD5">>, ?MODULE,
+ digest).
-stop() ->
- ok.
+stop() -> ok.
-mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
- {ok, #state{step = 1,
- nonce = randoms:get_string(),
- host = Host,
- hostfqdn = get_local_fqdn(),
- get_password = GetPassword,
- check_password = CheckPasswordDigest}}.
+mech_new(Host, GetPassword, _CheckPassword,
+ CheckPasswordDigest) ->
+ {ok,
+ #state{step = 1, nonce = randoms:get_string(),
+ host = Host, hostfqdn = get_local_fqdn(),
+ get_password = GetPassword,
+ check_password = CheckPasswordDigest}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue,
- "nonce=\"" ++ Nonce ++
- "\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
+ <<"nonce=\"", Nonce/binary,
+ "\",qop=\"auth\",charset=utf-8,algorithm=md5-sess">>,
State#state{step = 3}};
-mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
+mech_step(#state{step = 3, nonce = Nonce} = State,
+ ClientIn) ->
case parse(ClientIn) of
- bad ->
- {error, "bad-protocol"};
- KeyVals ->
- DigestURI = xml:get_attr_s("digest-uri", KeyVals),
- UserName = xml:get_attr_s("username", KeyVals),
- case is_digesturi_valid(DigestURI, State#state.host, State#state.hostfqdn) of
- false ->
- ?DEBUG("User login not authorized because digest-uri "
- "seems invalid: ~p (checking for Host ~p, FQDN ~p)", [DigestURI,
- State#state.host, State#state.hostfqdn]),
- {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} ->
- case (State#state.check_password)(UserName, "",
- xml:get_attr_s("response", KeyVals),
- fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
- "AUTHENTICATE") end) of
- {true, _} ->
- RspAuth = response(KeyVals,
- UserName, Passwd,
- Nonce, AuthzId, ""),
- {continue,
- "rspauth=" ++ RspAuth,
- State#state{step = 5,
- auth_module = AuthModule,
- username = UserName,
- authzid = AuthzId}};
- false ->
- {error, "not-authorized", UserName};
- {false, _} ->
- {error, "not-authorized", UserName}
- end
- end
- end
+ bad -> {error, <<"bad-protocol">>};
+ KeyVals ->
+ DigestURI = proplists:get_value(<<"digest-uri">>, KeyVals, <<>>),
+ %DigestURI = xml:get_attr_s(<<"digest-uri">>, KeyVals),
+ UserName = proplists:get_value(<<"username">>, KeyVals, <<>>),
+ %UserName = xml:get_attr_s(<<"username">>, KeyVals),
+ case is_digesturi_valid(DigestURI, State#state.host,
+ State#state.hostfqdn)
+ of
+ false ->
+ ?DEBUG("User login not authorized because digest-uri "
+ "seems invalid: ~p (checking for Host "
+ "~p, FQDN ~p)",
+ [DigestURI, State#state.host, State#state.hostfqdn]),
+ {error, <<"not-authorized">>, UserName};
+ true ->
+ AuthzId = proplists:get_value(<<"authzid">>, KeyVals, <<>>),
+ %AuthzId = xml:get_attr_s(<<"authzid">>, KeyVals),
+ case (State#state.get_password)(UserName) of
+ {false, _} -> {error, <<"not-authorized">>, UserName};
+ {Passwd, AuthModule} ->
+ case (State#state.check_password)(UserName, <<"">>,
+ proplists:get_value(<<"response">>, KeyVals, <<>>),
+ %xml:get_attr_s(<<"response">>, KeyVals),
+ fun (PW) ->
+ response(KeyVals,
+ UserName,
+ PW,
+ Nonce,
+ AuthzId,
+ <<"AUTHENTICATE">>)
+ end)
+ of
+ {true, _} ->
+ RspAuth = response(KeyVals, UserName, Passwd, Nonce,
+ AuthzId, <<"">>),
+ {continue, <<"rspauth=", RspAuth/binary>>,
+ State#state{step = 5, auth_module = AuthModule,
+ username = UserName,
+ authzid = AuthzId}};
+ false -> {error, <<"not-authorized">>, UserName};
+ {false, _} -> {error, <<"not-authorized">>, UserName}
+ end
+ end
+ end
end;
-mech_step(#state{step = 5,
- auth_module = AuthModule,
- username = UserName,
- authzid = AuthzId}, "") ->
- {ok, [{username, UserName}, {authzid, AuthzId},
- {auth_module, AuthModule}]};
+mech_step(#state{step = 5, auth_module = AuthModule,
+ username = UserName, authzid = AuthzId},
+ <<"">>) ->
+ {ok,
+ [{username, UserName}, {authzid, AuthzId},
+ {auth_module, AuthModule}]};
mech_step(A, B) ->
- ?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
- {error, "bad-protocol"}.
+ ?DEBUG("SASL DIGEST: A ~p B ~p", [A, B]),
+ {error, <<"bad-protocol">>}.
-parse(S) ->
- parse1(S, "", []).
+parse(S) -> parse1(binary_to_list(S), "", []).
parse1([$= | Cs], S, Ts) ->
parse2(Cs, lists:reverse(S), "", Ts);
-parse1([$, | Cs], [], Ts) ->
- parse1(Cs, [], Ts);
-parse1([$\s | Cs], [], Ts) ->
- parse1(Cs, [], Ts);
-parse1([C | Cs], S, Ts) ->
- parse1(Cs, [C | S], Ts);
-parse1([], [], T) ->
- lists:reverse(T);
-parse1([], _S, _T) ->
- bad.
-
-parse2([$\" | Cs], Key, Val, Ts) ->
+parse1([$, | Cs], [], Ts) -> parse1(Cs, [], Ts);
+parse1([$\s | Cs], [], Ts) -> parse1(Cs, [], Ts);
+parse1([C | Cs], S, Ts) -> parse1(Cs, [C | S], Ts);
+parse1([], [], T) -> lists:reverse(T);
+parse1([], _S, _T) -> bad.
+
+parse2([$" | Cs], Key, Val, Ts) ->
parse3(Cs, Key, Val, Ts);
parse2([C | Cs], Key, Val, Ts) ->
parse4(Cs, Key, [C | Val], Ts);
-parse2([], _, _, _) ->
- bad.
+parse2([], _, _, _) -> bad.
-parse3([$\" | Cs], Key, Val, Ts) ->
+parse3([$" | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts);
parse3([$\\, C | Cs], Key, Val, Ts) ->
parse3(Cs, Key, [C | Val], Ts);
parse3([C | Cs], Key, Val, Ts) ->
parse3(Cs, Key, [C | Val], Ts);
-parse3([], _, _, _) ->
- bad.
+parse3([], _, _, _) -> bad.
parse4([$, | Cs], Key, Val, Ts) ->
- parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
+ parse1(Cs, "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]);
parse4([$\s | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts);
parse4([C | Cs], Key, Val, Ts) ->
parse4(Cs, Key, [C | Val], Ts);
parse4([], Key, Val, Ts) ->
- 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.
@@ -162,14 +173,17 @@ parse4([], Key, Val, Ts) ->
%% xmpp/server3.example.org/jabber.example.org, xmpp/server3.example.org and
%% xmpp/jabber.example.org
%% The last version is not actually allowed by the RFC, but implemented by popular clients
-is_digesturi_valid(DigestURICase, JabberDomain, JabberFQDN) ->
+ parse1([], "", [{list_to_binary(Key), list_to_binary(lists:reverse(Val))} | Ts]).
+
+is_digesturi_valid(DigestURICase, JabberDomain,
+ JabberFQDN) ->
DigestURI = stringprep:tolower(DigestURICase),
- case catch string:tokens(DigestURI, "/") of
- ["xmpp", Host] ->
- IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
+ case catch str:tokens(DigestURI, <<"/">>) of
+ [<<"xmpp">>, Host] ->
+ IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
(Host == JabberDomain) or IsHostFqdn;
- ["xmpp", Host, ServName] ->
- IsHostFqdn = is_host_fqdn(Host, JabberFQDN),
+ [<<"xmpp">>, Host, ServName] ->
+ IsHostFqdn = is_host_fqdn(binary_to_list(Host), binary_to_list(JabberFQDN)),
(ServName == JabberDomain) and IsHostFqdn;
_ ->
false
@@ -185,62 +199,60 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
is_host_fqdn(Host, FqdnTail).
get_local_fqdn() ->
- case (catch get_local_fqdn2()) of
- Str when is_list(Str) -> Str;
- _ -> "unknown-fqdn, please configure fqdn option in ejabberd.cfg!"
+ case catch get_local_fqdn2() of
+ Str when is_binary(Str) -> Str;
+ _ ->
+ <<"unknown-fqdn, please configure fqdn "
+ "option in ejabberd.cfg!">>
end.
+
get_local_fqdn2() ->
- case ejabberd_config:get_local_option(fqdn) of
- ConfiguredFqdn when is_list(ConfiguredFqdn) ->
- ConfiguredFqdn;
- _undefined ->
- {ok, Hostname} = inet:gethostname(),
- {ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
- Fqdn
+ case ejabberd_config:get_local_option(
+ fqdn, fun iolist_to_binary/1) of
+ ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
+ ConfiguredFqdn;
+ undefined ->
+ {ok, Hostname} = inet:gethostname(),
+ {ok, {hostent, Fqdn, _, _, _, _}} =
+ inet:gethostbyname(Hostname),
+ list_to_binary(Fqdn)
end.
-digit_to_xchar(D) when (D >= 0) and (D < 10) ->
- D + 48;
-digit_to_xchar(D) ->
- D + 87.
-
hex(S) ->
- hex(S, []).
-
-hex([], Res) ->
- lists:reverse(Res);
-hex([N | Ns], Res) ->
- hex(Ns, [digit_to_xchar(N rem 16),
- digit_to_xchar(N div 16) | Res]).
+ sha:to_hexlist(S).
+proplists_get_bin_value(Key, Pairs, Default) ->
+ case proplists:get_value(Key, Pairs, Default) of
+ L when is_list(L) ->
+ list_to_binary(L);
+ L2 ->
+ L2
+ end.
-response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
- Realm = xml:get_attr_s("realm", KeyVals),
- CNonce = xml:get_attr_s("cnonce", KeyVals),
- DigestURI = xml:get_attr_s("digest-uri", KeyVals),
- NC = xml:get_attr_s("nc", KeyVals),
- QOP = xml:get_attr_s("qop", KeyVals),
+response(KeyVals, User, Passwd, Nonce, AuthzId,
+ A2Prefix) ->
+ Realm = proplists_get_bin_value(<<"realm">>, KeyVals, <<>>),
+ CNonce = proplists_get_bin_value(<<"cnonce">>, KeyVals, <<>>),
+ DigestURI = proplists_get_bin_value(<<"digest-uri">>, KeyVals, <<>>),
+ NC = proplists_get_bin_value(<<"nc">>, KeyVals, <<>>),
+ QOP = proplists_get_bin_value(<<"qop">>, KeyVals, <<>>),
+ MD5Hash = crypto:md5(<<User/binary, ":", Realm/binary, ":",
+ Passwd/binary>>),
A1 = case AuthzId of
- "" ->
- binary_to_list(
- crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
- ":" ++ Nonce ++ ":" ++ CNonce;
- _ ->
- binary_to_list(
- crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
- ":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
+ <<"">> ->
+ <<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary>>;
+ _ ->
+ <<MD5Hash/binary, ":", Nonce/binary, ":", CNonce/binary, ":",
+ AuthzId/binary>>
end,
A2 = case QOP of
- "auth" ->
- A2Prefix ++ ":" ++ DigestURI;
- _ ->
- A2Prefix ++ ":" ++ DigestURI ++
- ":00000000000000000000000000000000"
+ <<"auth">> ->
+ <<A2Prefix/binary, ":", DigestURI/binary>>;
+ _ ->
+ <<A2Prefix/binary, ":", DigestURI/binary,
+ ":00000000000000000000000000000000">>
end,
- T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++
- NC ++ ":" ++ CNonce ++ ":" ++ QOP ++ ":" ++
- hex(binary_to_list(crypto:md5(A2))),
- hex(binary_to_list(crypto:md5(T))).
-
-
-
+ T = <<(hex((crypto:md5(A1))))/binary, ":", Nonce/binary,
+ ":", NC/binary, ":", CNonce/binary, ":", QOP/binary,
+ ":", (hex((crypto:md5(A2))))/binary>>,
+ hex((crypto:md5(T))).