2003-03-12 20:48:05 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : cyrsasl_digest.erl
|
|
|
|
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
|
|
|
%%% Purpose : DIGEST-MD5 SASL mechanism
|
|
|
|
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
|
|
|
|
%%% Id : $Id$
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-module(cyrsasl_digest).
|
|
|
|
-author('alexey@sevcom.net').
|
|
|
|
-vsn('$Revision$ ').
|
|
|
|
|
|
|
|
-export([start/1,
|
|
|
|
stop/0,
|
2005-04-17 20:08:34 +02:00
|
|
|
mech_new/2,
|
2003-03-12 20:48:05 +01:00
|
|
|
mech_step/2]).
|
|
|
|
|
|
|
|
-behaviour(cyrsasl).
|
|
|
|
|
2005-04-17 20:08:34 +02:00
|
|
|
-record(state, {step, nonce, username, authzid, get_password}).
|
2003-03-12 20:48:05 +01:00
|
|
|
|
2005-04-17 20:08:34 +02:00
|
|
|
start(_Opts) ->
|
2005-07-13 05:24:13 +02:00
|
|
|
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
|
2003-03-12 20:48:05 +01:00
|
|
|
|
|
|
|
stop() ->
|
|
|
|
ok.
|
|
|
|
|
2005-04-17 20:08:34 +02:00
|
|
|
mech_new(GetPassword, _CheckPassword) ->
|
2003-03-12 20:48:05 +01:00
|
|
|
{ok, #state{step = 1,
|
2005-04-17 20:08:34 +02:00
|
|
|
nonce = randoms:get_string(),
|
|
|
|
get_password = GetPassword}}.
|
2003-03-12 20:48:05 +01:00
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
2003-03-12 20:48:05 +01:00
|
|
|
{continue,
|
2003-10-16 20:17:44 +02:00
|
|
|
"nonce=\"" ++ Nonce ++
|
2003-10-07 22:31:44 +02:00
|
|
|
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
|
2003-03-12 20:48:05 +01:00
|
|
|
State#state{step = 3}};
|
|
|
|
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
|
|
|
case parse(ClientIn) of
|
|
|
|
bad ->
|
2003-06-07 19:30:25 +02:00
|
|
|
{error, "bad-protocol"};
|
2003-03-12 20:48:05 +01:00
|
|
|
KeyVals ->
|
|
|
|
UserName = xml:get_attr_s("username", KeyVals),
|
2003-06-07 19:30:25 +02:00
|
|
|
AuthzId = xml:get_attr_s("authzid", KeyVals),
|
2005-04-17 20:08:34 +02:00
|
|
|
case (State#state.get_password)(UserName) of
|
2003-03-12 20:48:05 +01:00
|
|
|
false ->
|
2004-08-13 00:34:19 +02:00
|
|
|
{error, "not-authorized"};
|
2003-03-12 20:48:05 +01:00
|
|
|
Passwd ->
|
2003-10-16 20:17:44 +02:00
|
|
|
Response = response(KeyVals, UserName, Passwd,
|
|
|
|
Nonce, AuthzId, "AUTHENTICATE"),
|
2003-03-12 20:48:05 +01:00
|
|
|
case xml:get_attr_s("response", KeyVals) of
|
|
|
|
Response ->
|
2003-06-07 19:30:25 +02:00
|
|
|
RspAuth = response(KeyVals,
|
|
|
|
UserName, Passwd,
|
2003-10-16 20:17:44 +02:00
|
|
|
Nonce, AuthzId, ""),
|
2003-03-12 20:48:05 +01:00
|
|
|
{continue,
|
|
|
|
"rspauth=" ++ RspAuth,
|
2003-06-07 19:30:25 +02:00
|
|
|
State#state{step = 5,
|
|
|
|
username = UserName,
|
|
|
|
authzid = AuthzId}};
|
2003-03-12 20:48:05 +01:00
|
|
|
_ ->
|
2004-08-13 00:34:19 +02:00
|
|
|
{error, "not-authorized"}
|
2003-03-12 20:48:05 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end;
|
2003-06-07 19:30:25 +02:00
|
|
|
mech_step(#state{step = 5,
|
|
|
|
username = UserName,
|
2005-04-17 20:08:34 +02:00
|
|
|
authzid = AuthzId}, "") ->
|
2003-06-07 19:30:25 +02:00
|
|
|
{ok, [{username, UserName}, {authzid, AuthzId}]};
|
2003-03-12 20:48:05 +01:00
|
|
|
mech_step(A, B) ->
|
|
|
|
io:format("SASL DIGEST: A ~p B ~p", [A,B]),
|
2003-06-07 19:30:25 +02:00
|
|
|
{error, "bad-protocol"}.
|
2003-03-12 20:48:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
parse(S) ->
|
|
|
|
parse1(S, "", []).
|
|
|
|
|
|
|
|
parse1([$= | Cs], S, Ts) ->
|
|
|
|
parse2(Cs, lists:reverse(S), "", Ts);
|
|
|
|
parse1([C | Cs], S, Ts) ->
|
|
|
|
parse1(Cs, [C | S], Ts);
|
|
|
|
parse1([], [], T) ->
|
|
|
|
lists:reverse(T);
|
2005-04-17 20:08:34 +02:00
|
|
|
parse1([], _S, _T) ->
|
2003-03-12 20:48:05 +01:00
|
|
|
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.
|
|
|
|
|
|
|
|
parse3([$" | Cs], Key, Val, Ts) ->
|
|
|
|
parse4(Cs, Key, Val, Ts);
|
|
|
|
parse3([C | Cs], Key, Val, Ts) ->
|
|
|
|
parse3(Cs, Key, [C | Val], Ts);
|
|
|
|
parse3([], _, _, _) ->
|
|
|
|
bad.
|
|
|
|
|
|
|
|
parse4([$, | Cs], Key, Val, Ts) ->
|
|
|
|
parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
|
|
|
|
parse4([C | Cs], Key, Val, Ts) ->
|
|
|
|
parse4(Cs, Key, [C | Val], Ts);
|
|
|
|
parse4([], Key, Val, Ts) ->
|
|
|
|
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]).
|
|
|
|
|
|
|
|
|
2003-10-16 20:17:44 +02:00
|
|
|
response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
|
2003-03-12 20:48:05 +01:00
|
|
|
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),
|
2003-06-07 19:30:25 +02:00
|
|
|
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
|
|
|
|
end,
|
2005-04-17 20:08:34 +02:00
|
|
|
A2 = case QOP of
|
|
|
|
"auth" ->
|
|
|
|
A2Prefix ++ ":" ++ DigestURI;
|
|
|
|
_ ->
|
|
|
|
A2Prefix ++ ":" ++ DigestURI ++
|
|
|
|
":00000000000000000000000000000000"
|
|
|
|
end,
|
2003-03-12 20:48:05 +01:00
|
|
|
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))).
|
|
|
|
|
|
|
|
|
|
|
|
|