+2003-03-12 Alexey Shchepin <alexey@sevcom.net>
+
+ * src/ejabberd_s2s_out.erl: Fixed ports leak
+ * src/ejabberd_listener.erl: Likewise
+
+ * src/ejabberd_c2s.erl: Fixes for SASL support
+
+ * src/cyrsasl.erl: Fixes
+
+ * src/cyrsasl_digest.erl: DIGEST-MD5 SASL mechanism support
+
2003-03-09 Alexey Shchepin <alexey@sevcom.net>
* src/cyrsasl*.erl: SASL support (currently support only PLAIN
public,
{keypos, #sasl_mechanism.mechanism}]),
cyrsasl_plain:start([]),
+ cyrsasl_digest:start([]),
ok.
register_mechanism(Mechanism, Module) ->
server_start(State, Mech, ClientIn) ->
case ets:lookup(sasl_mechanism, Mech) of
[#sasl_mechanism{module = Module}] ->
- MechState = Module:mech_new(),
+ {ok, MechState} = Module:mech_new(),
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);
--- /dev/null
+%%%----------------------------------------------------------------------
+%%% 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,
+ mech_new/0,
+ mech_step/2]).
+
+-behaviour(cyrsasl).
+%-behaviour(gen_mod).
+
+-record(state, {step, nonce, username}).
+
+start(Opts) ->
+ cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE),
+ ok.
+
+stop() ->
+ ok.
+
+mech_new() ->
+ {ok, #state{step = 1,
+ nonce = randoms:get_string()}}.
+
+mech_step(#state{step = 1, nonce = Nonce} = State, "") ->
+ {continue,
+ "nonce=\"" ++ jlib:encode_base64(Nonce) ++
+ "\",qop=\"auth,auth-int\",charset=utf-8,algorithm=md5-sess",
+ State#state{step = 3}};
+mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
+ case parse(ClientIn) of
+ bad ->
+ {error, "454"};
+ KeyVals ->
+ UserName = xml:get_attr_s("username", KeyVals),
+ case ejabberd_auth:get_password(UserName) of
+ false ->
+ {error, "454"};
+ Passwd ->
+ Response = response(KeyVals, UserName, Passwd,
+ "AUTHENTICATE"),
+ case xml:get_attr_s("response", KeyVals) of
+ Response ->
+ RspAuth = response(KeyVals, UserName, Passwd, ""),
+ {continue,
+ "rspauth=" ++ RspAuth,
+ State#state{step = 5, username = UserName}};
+ _ ->
+ {error, "454"}
+ end
+ end
+ end;
+mech_step(#state{step = 5, username = UserName} = State, "") ->
+ {ok, [{username, UserName}]};
+mech_step(A, B) ->
+ io:format("SASL DIGEST: A ~p B ~p", [A,B]),
+ {error, "454"}.
+
+
+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);
+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.
+
+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]).
+
+
+response(KeyVals, User, Passwd, A2Prefix) ->
+ Realm = xml:get_attr_s("realm", KeyVals),
+ Nonce = xml:get_attr_s("nonce", 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),
+ A1 = binary_to_list(crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
+ ":" ++ Nonce ++ ":" ++ CNonce,
+ case QOP of
+ "auth" ->
+ A2 = A2Prefix ++ ":" ++ DigestURI;
+ _ ->
+ A2 = A2Prefix ++ ":" ++ DigestURI ++
+ ":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))).
+
+
+
check_password/4,
try_register/2,
dirty_get_registered_users/0,
+ get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
+get_password(User) ->
+ LUser = jlib:tolower(User),
+ case catch mnesia:dirty_read(passwd, LUser) of
+ [#passwd{password = Password}] ->
+ Password;
+ _ ->
+ false
+ end.
+
get_password_s(User) ->
LUser = jlib:tolower(User),
case catch mnesia:dirty_read(passwd, LUser) of
wait_for_auth/2,
wait_for_sasl_auth/2,
wait_for_resource_auth/2,
+ wait_for_sasl_response/2,
session_established/2,
handle_event/3,
handle_sync_event/4,
[{"xmlns", ?NS_SASL_MECHANISMS}],
[{xmlcdata,
jlib:encode_base64(ServerOut)}]}),
- {next_state, wait_for_sasl_response, StateData};
+ {next_state, wait_for_sasl_response,
+ StateData#state{sasl_state = NewSASLState}};
{error, Code} ->
send_element(StateData#state.socket,
{xmlelement, "failure",
% TODO: wait_for_sasl_response
+wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
+ {xmlelement, Name, Attrs, Els} = El,
+ case {xml:get_attr_s("xmlns", Attrs), Name} of
+ {?NS_SASL_MECHANISMS, "response"} ->
+ ClientIn = jlib:decode_base64(xml:get_cdata(Els)),
+ case cyrsasl:server_step(StateData#state.sasl_state,
+ ClientIn) of
+ {ok, Props} ->
+ send_element(StateData#state.socket,
+ {xmlelement, "success",
+ [{"xmlns", ?NS_SASL_MECHANISMS}], []}),
+ {next_state, wait_for_resource_auth,
+ StateData#state{user = xml:get_attr_s(username, Props)}};
+ {continue, ServerOut, NewSASLState} ->
+ send_element(StateData#state.socket,
+ {xmlelement, "challenge",
+ [{"xmlns", ?NS_SASL_MECHANISMS}],
+ [{xmlcdata,
+ jlib:encode_base64(ServerOut)}]}),
+ {next_state, wait_for_sasl_response,
+ StateData#state{sasl_state = NewSASLState}};
+ {error, Code} ->
+ send_element(StateData#state.socket,
+ {xmlelement, "failure",
+ [{"xmlns", ?NS_SASL_MECHANISMS},
+ {"code", Code}],
+ []}),
+ {next_state, wait_for_sasl_auth, StateData}
+ end;
+ _ ->
+ case jlib:iq_query_info(El) of
+ {iq, ID, Type, ?NS_REGISTER, SubEl} ->
+ ResIQ = mod_register:process_iq(
+ {"", "", ""}, {"", ?MYNAME, ""},
+ {iq, ID, Type, ?NS_REGISTER, SubEl}),
+ Res1 = jlib:replace_from_to({"", ?MYNAME, ""},
+ {"", "", ""},
+ jlib:iq_to_xml(ResIQ)),
+ Res = jlib:remove_attr("to", Res1),
+ send_element(StateData#state.socket, Res),
+ {next_state, wait_for_sasl_auth, StateData};
+ _ ->
+ {next_state, wait_for_sasl_auth, StateData}
+ end
+ end;
+
+wait_for_sasl_response({xmlstreamend, Name}, StateData) ->
+ send_text(StateData#state.socket, ?STREAM_TRAILER),
+ {stop, normal, StateData};
+
+wait_for_sasl_response({xmlstreamerror, _}, StateData) ->
+ send_text(StateData#state.socket, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
+ {stop, normal, StateData};
+
+wait_for_sasl_response(closed, StateData) ->
+ {stop, normal, StateData}.
+
accept(ListenSocket, Module, Fun, Opts) ->
case gen_tcp:accept(ListenSocket) of
- {ok,Socket} ->
- apply(Module, Fun, [{gen_tcp, Socket}, Opts]),
+ {ok, Socket} ->
+ {ok, Pid} = apply(Module, Fun, [{gen_tcp, Socket}, Opts]),
+ gen_tcp:controlling_process(Socket, Pid),
accept(ListenSocket, Module, Fun, Opts)
end.
accept_ssl(ListenSocket, Module, Fun, Opts) ->
case ssl:accept(ListenSocket) of
- {ok,Socket} ->
+ {ok, Socket} ->
apply(Module, Fun, [{ssl, Socket}, Opts]),
accept_ssl(ListenSocket, Module, Fun, Opts)
end.
-behaviour(gen_fsm).
%% External exports
--export([start/3, receiver/2, send_text/2, send_element/2]).
+-export([start/3, send_text/2, send_element/2]).
%% gen_fsm callbacks
-export([init/1,
undefined ->
ok;
Socket ->
- gen_tcp:close(Socket)
+ gen_tcp:close(Socket),
+ exit(StateData#state.xmlpid, closed)
end,
ok.
%%% Internal functions
%%%----------------------------------------------------------------------
-receiver(Socket, C2SPid) ->
- XMLStreamPid = xml_stream:start(C2SPid),
- receiver(Socket, C2SPid, XMLStreamPid).
-
-receiver(Socket, C2SPid, XMLStreamPid) ->
- case gen_tcp:recv(Socket, 0) of
- {ok, Text} ->
- xml_stream:send_text(XMLStreamPid, Text),
- receiver(Socket, C2SPid, XMLStreamPid);
- {error, Reason} ->
- exit(XMLStreamPid, closed),
- gen_fsm:send_event(C2SPid, closed),
- ok
- end.
-
send_text(Socket, Text) ->
gen_tcp:send(Socket,Text).
spawn(?MODULE, init, [CallbackPid]).
init(CallbackPid) ->
+ link(CallbackPid),
Port = open_port({spawn, expat_erl}, [binary]),
loop(CallbackPid, Port, []).