diff --git a/ChangeLog b/ChangeLog index 3481b35e9..71b8021e6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2003-06-07 Alexey Shchepin + + * src/ejabberd_c2s.erl: SASL support updated to xmpp-core-13 + +2003-06-06 Alexey Shchepin + + * src/cyrsasl*.erl: Support for authzid + 2003-06-03 Alexey Shchepin * src/msgs/fr.msg: New french translation (thanks to Vincent Ricard) diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index bc6a80be9..b87133621 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -40,6 +40,30 @@ register_mechanism(Mechanism, Module) -> ets:insert(sasl_mechanism, #sasl_mechanism{mechanism = Mechanism, module = Module}). +% TODO: use callbacks +-include("ejabberd.hrl"). +-include("jlib.hrl"). +check_authzid(State, Props) -> + AuthzId = xml:get_attr_s(authzid, Props), + case jlib:string_to_jid(AuthzId) of + error -> + {error, "invalid-authzid"}; + JID -> + LUser = xml:get_attr_s(username, Props), + {U, S, R} = jlib:jid_tolower(JID), + case R of + "" -> + {error, "invalid-authzid"}; + _ -> + case {LUser, ?MYNAME} of + {U, S} -> + ok; + _ -> + {error, "invalid-authzid"} + end + end + end. + listmech() -> ets:select(sasl_mechanism, [{#sasl_mechanism{mechanism = '$1', _ = '_'}, [], ['$1']}]). @@ -58,7 +82,7 @@ server_start(State, Mech, ClientIn) -> mech_state = MechState}, ClientIn); _ -> - {error, "454"} + {error, "no-mechanism"} end. server_step(State, ClientIn) -> @@ -66,13 +90,16 @@ server_step(State, ClientIn) -> MechState = State#sasl_state.mech_state, case Module:mech_step(MechState, ClientIn) of {ok, Props} -> - {ok, Props}; + case check_authzid(State, Props) of + ok -> + {ok, Props}; + {error, Error} -> + {error, Error} + end; {continue, ServerOut, NewMechState} -> {continue, ServerOut, State#sasl_state{mech_state = NewMechState}}; - {error, Code} -> - {error, Code} + {error, Error} -> + {error, Error} end. - - diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index c4593c36d..b9f0730d0 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -18,7 +18,7 @@ -behaviour(cyrsasl). %-behaviour(gen_mod). --record(state, {step, nonce, username}). +-record(state, {step, nonce, username, authzid}). start(Opts) -> cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE), @@ -39,31 +39,38 @@ mech_step(#state{step = 1, nonce = Nonce} = State, "") -> mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) -> case parse(ClientIn) of bad -> - {error, "454"}; + {error, "bad-protocol"}; KeyVals -> UserName = xml:get_attr_s("username", KeyVals), + AuthzId = xml:get_attr_s("authzid", KeyVals), case ejabberd_auth:get_password(UserName) of false -> - {error, "454"}; + {error, "no-user"}; Passwd -> - Response = response(KeyVals, UserName, Passwd, + Response = response(KeyVals, UserName, Passwd, AuthzId, "AUTHENTICATE"), case xml:get_attr_s("response", KeyVals) of Response -> - RspAuth = response(KeyVals, UserName, Passwd, ""), + RspAuth = response(KeyVals, + UserName, Passwd, + AuthzId, ""), {continue, "rspauth=" ++ RspAuth, - State#state{step = 5, username = UserName}}; + State#state{step = 5, + username = UserName, + authzid = AuthzId}}; _ -> - {error, "454"} + {error, "bad-auth"} end end end; -mech_step(#state{step = 5, username = UserName} = State, "") -> - {ok, [{username, UserName}]}; +mech_step(#state{step = 5, + username = UserName, + authzid = AuthzId} = State, "") -> + {ok, [{username, UserName}, {authzid, AuthzId}]}; mech_step(A, B) -> io:format("SASL DIGEST: A ~p B ~p", [A,B]), - {error, "454"}. + {error, "bad-protocol"}. parse(S) -> @@ -119,15 +126,23 @@ hex([N | Ns], Res) -> digit_to_xchar(N div 16) | Res]). -response(KeyVals, User, Passwd, A2Prefix) -> +response(KeyVals, User, Passwd, AuthzId, 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, + 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, case QOP of "auth" -> A2 = A2Prefix ++ ":" ++ DigestURI; diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index 46461e1c0..71e4a6baf 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -27,15 +27,15 @@ mech_new() -> mech_step(State, ClientIn) -> case parse(ClientIn) of - [_, User, Password] -> + [AuthzId, User, Password] -> case ejabberd_auth:check_password(User, Password) of true -> - {ok, [{username, User}]}; + {ok, [{username, User}, {authzid, AuthzId}]}; _ -> - {error, "454"} + {error, "bad-auth"} end; _ -> - {error, "454"} + {error, "bad-protocol"} end. diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 23b350d63..fb6bec275 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -20,7 +20,7 @@ wait_for_stream/2, wait_for_auth/2, wait_for_sasl_auth/2, - wait_for_resource_auth/2, + wait_for_session/2, wait_for_sasl_response/2, session_established/2, handle_event/3, @@ -40,6 +40,7 @@ sasl_state, access, shaper, + authentificated = false, user = "", server = ?MYNAME, resource = "", pres_t = ?SETS:new(), pres_f = ?SETS:new(), @@ -129,13 +130,18 @@ wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> {xmlelement, "mechanism", [], [{xmlcdata, S}]} end, cyrsasl:listmech()), - send_element(StateData, - {xmlelement, "stream:features", [], - [{xmlelement, "mechanisms", - [{"xmlns", ?NS_SASL_MECHANISMS}], - Mechs}]}), - {next_state, wait_for_sasl_auth, - StateData#state{sasl_state = SASLState}}; + case StateData#state.authentificated of + false -> + send_element(StateData, + {xmlelement, "stream:features", [], + [{xmlelement, "mechanisms", + [{"xmlns", ?NS_SASL}], + Mechs}]}), + {next_state, wait_for_sasl_auth, + StateData#state{sasl_state = SASLState}}; + _ -> + {next_state, wait_for_session, StateData} + end; _ -> Header = io_lib:format( ?STREAM_HEADER, @@ -245,32 +251,37 @@ wait_for_auth(closed, StateData) -> wait_for_sasl_auth({xmlstreamelement, El}, StateData) -> {xmlelement, Name, Attrs, Els} = El, case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_SASL_MECHANISMS, "auth"} -> + {?NS_SASL, "auth"} -> Mech = xml:get_attr_s("mechanism", Attrs), ClientIn = jlib:decode_base64(xml:get_cdata(Els)), case cyrsasl:server_start(StateData#state.sasl_state, Mech, ClientIn) of {ok, Props} -> + StateData#state.receiver ! reset_stream, send_element(StateData, {xmlelement, "success", - [{"xmlns", ?NS_SASL_MECHANISMS}], []}), - {next_state, wait_for_resource_auth, - StateData#state{user = xml:get_attr_s(username, Props)}}; + [{"xmlns", ?NS_SASL}], []}), + {U, _, R} = jlib:string_to_jid( + xml:get_attr_s(authzid, Props)), + {next_state, wait_for_stream, + StateData#state{authentificated = true, + user = U, + resource = R + }}; {continue, ServerOut, NewSASLState} -> send_element(StateData, {xmlelement, "challenge", - [{"xmlns", ?NS_SASL_MECHANISMS}], + [{"xmlns", ?NS_SASL}], [{xmlcdata, jlib:encode_base64(ServerOut)}]}), {next_state, wait_for_sasl_response, StateData#state{sasl_state = NewSASLState}}; - {error, Code} -> + {error, Error} -> send_element(StateData, {xmlelement, "failure", - [{"xmlns", ?NS_SASL_MECHANISMS}, - {"code", Code}], - []}), + [{"xmlns", ?NS_SASL}], + [{xmlelement, Error, [], []}]}), {next_state, wait_for_sasl_auth, StateData} end; _ -> @@ -302,110 +313,38 @@ wait_for_sasl_auth(closed, StateData) -> {stop, normal, StateData}. -wait_for_resource_auth({xmlstreamelement, El}, StateData) -> - case is_auth_packet(El) of - {auth, ID, get, {"", _, _, _}} -> - Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), - send_element(StateData, Err), - {next_state, wait_for_resource_auth, StateData}; - {auth, ID, get, {U, _, _, _}} -> - {xmlelement, Name, Attrs, Els} = jlib:make_result_iq_reply(El), - Res = {xmlelement, Name, Attrs, - [{xmlelement, "query", [{"xmlns", ?NS_AUTH}], - [{xmlelement, "username", [], - [{xmlcdata, StateData#state.user}]}, - {xmlelement, "resource", [], []} - ]}]}, - send_element(StateData, Res), - {next_state, wait_for_resource_auth, StateData}; - {auth, ID, set, {U, _, _, ""}} -> - Err = jlib:make_error_reply(El, ?ERR_AUTH_NO_RESOURCE_PROVIDED), - send_element(StateData, Err), - {next_state, wait_for_resource_auth, StateData}; - {auth, ID, set, {U, _, _, R}} -> - case StateData#state.user of - U -> - io:format("SASLAUTH: ~p~n", [{U, R}]), - JID = {U, ?MYNAME, R}, - case acl:match_rule(StateData#state.access, JID) of - allow -> - ejabberd_sm:open_session(U, R), - Res = jlib:make_result_iq_reply(El), - send_element(StateData, Res), - change_shaper(StateData, JID), - {Fs, Ts} = mod_roster:get_subscription_lists(U), - {next_state, session_established, - StateData#state{user = U, - resource = R, - pres_f = ?SETS:from_list(Fs), - pres_t = ?SETS:from_list(Ts)}}; - _ -> - Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), - send_element(StateData, Err), - {next_state, wait_for_resource_auth, StateData} - end; - _ -> - Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), - send_element(StateData, Err), - {next_state, wait_for_resource_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, Res), - {next_state, wait_for_resource_auth, StateData}; - _ -> - {next_state, wait_for_resource_auth, StateData} - end - end; - -wait_for_resource_auth({xmlstreamend, Name}, StateData) -> - send_text(StateData, ?STREAM_TRAILER), - {stop, normal, StateData}; - -wait_for_resource_auth({xmlstreamerror, _}, StateData) -> - send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER), - {stop, normal, StateData}; - -wait_for_resource_auth(closed, StateData) -> - {stop, normal, StateData}. - - -% 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"} -> + {?NS_SASL, "response"} -> ClientIn = jlib:decode_base64(xml:get_cdata(Els)), case cyrsasl:server_step(StateData#state.sasl_state, ClientIn) of {ok, Props} -> + StateData#state.receiver ! reset_stream, send_element(StateData, {xmlelement, "success", - [{"xmlns", ?NS_SASL_MECHANISMS}], []}), - {next_state, wait_for_resource_auth, - StateData#state{user = xml:get_attr_s(username, Props)}}; + [{"xmlns", ?NS_SASL}], []}), + {U, _, R} = jlib:string_to_jid( + xml:get_attr_s(authzid, Props)), + {next_state, wait_for_stream, + StateData#state{authentificated = true, + user = U, + resource = R + }}; {continue, ServerOut, NewSASLState} -> send_element(StateData, {xmlelement, "challenge", - [{"xmlns", ?NS_SASL_MECHANISMS}], + [{"xmlns", ?NS_SASL}], [{xmlcdata, jlib:encode_base64(ServerOut)}]}), {next_state, wait_for_sasl_response, StateData#state{sasl_state = NewSASLState}}; - {error, Code} -> + {error, Error} -> send_element(StateData, {xmlelement, "failure", - [{"xmlns", ?NS_SASL_MECHANISMS}, - {"code", Code}], - []}), + [{"xmlns", ?NS_SASL}], + [{xmlelement, Error, [], []}]}), {next_state, wait_for_sasl_auth, StateData} end; _ -> @@ -438,6 +377,56 @@ wait_for_sasl_response(closed, StateData) -> +wait_for_session({xmlstreamelement, El}, StateData) -> + case jlib:iq_query_info(El) of + {iq, ID, set, ?NS_SESSION, SubEl} -> + U = StateData#state.user, + R = StateData#state.resource, + io:format("SASLAUTH: ~p~n", [{U, R}]), + JID = {U, ?MYNAME, R}, + case acl:match_rule(StateData#state.access, JID) of + allow -> + ejabberd_sm:open_session(U, R), + Res = jlib:make_result_iq_reply(El), + send_element(StateData, Res), + change_shaper(StateData, JID), + {Fs, Ts} = mod_roster:get_subscription_lists(U), + {next_state, session_established, + StateData#state{pres_f = ?SETS:from_list(Fs), + pres_t = ?SETS:from_list(Ts)}}; + _ -> + Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), + send_element(StateData, Err), + {next_state, wait_for_session, StateData} + end; + % TODO: is this needed? + {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, Res), + {next_state, wait_for_session, StateData}; + _ -> + {next_state, wait_for_session, StateData} + end; + +wait_for_session({xmlstreamend, Name}, StateData) -> + send_text(StateData, ?STREAM_TRAILER), + {stop, normal, StateData}; + +wait_for_session({xmlstreamerror, _}, StateData) -> + send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER), + {stop, normal, StateData}; + +wait_for_session(closed, StateData) -> + {stop, normal, StateData}. + + + session_established({xmlstreamelement, El}, StateData) -> {xmlelement, Name, Attrs, Els} = El, @@ -659,8 +648,15 @@ receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamPid, Timeout) -> ShaperState end, NewShaperState = shaper:update(ShaperSt1, size(Text)), - xml_stream:send_text(XMLStreamPid, Text), - receiver(Socket, SockMod, NewShaperState, C2SPid, XMLStreamPid, + XMLStreamPid1 = receive + reset_stream -> + exit(XMLStreamPid, closed), + xml_stream:start(C2SPid) + after 0 -> + XMLStreamPid + end, + xml_stream:send_text(XMLStreamPid1, Text), + receiver(Socket, SockMod, NewShaperState, C2SPid, XMLStreamPid1, Timeout); {error, timeout} -> receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamPid, diff --git a/src/jlib.hrl b/src/jlib.hrl index db35bba46..3c2dbc5b1 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -32,7 +32,8 @@ -define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas"). -define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams"). --define(NS_SASL_MECHANISMS, "urn:ietf:params:xml:ns:xmpp-sasl"). +-define(NS_SASL, "urn:ietf:params:xml:ns:xmpp-sasl"). +-define(NS_SESSION, "urn:ietf:params:xml:ns:xmpp-session"). % TODO: remove "code" attribute (currently it used for backward-compatibility) -define(STANZA_ERROR(Code, Class, Condition),