diff --git a/doc/guide.tex b/doc/guide.tex index 890b1bff7..47bed9f72 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -1243,6 +1243,16 @@ defines in what format the users passwords are stored: This format allows clients to authenticate using: \term{SASL PLAIN} and \term{SASL SCRAM-SHA-1}. \end{description} +The option \option{resource\_conflict} defines the action when a client attempts to +login to an account with a resource that is already connected. +The option syntax is: +\esyntax{\{resource\_conflict, setresource|closenew|closeold\}.} +The possible values match exactly the three possibilities described in +\footahref{http://tools.ietf.org/html/rfc6120\#section-7.7.2.2}{XMPP Core: section 7.7.2.2}. +The default value is \term{closeold}. +If the client uses old Jabber Non-SASL authentication (\xepref{0078}), +then this option is not respected, and the action performed is \term{closeold}. + Examples: \begin{itemize} \item To use internal Mnesia storage with hashed passwords on all virtual hosts: diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 7c2872e65..d4df773c2 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -907,6 +907,29 @@ wait_for_sasl_response(closed, StateData) -> {stop, normal, StateData}. +resource_conflict_action(U, S, R) -> + OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of + true -> + ejabberd_config:get_local_option({resource_conflict,binary_to_list(S)}); + false -> + acceptnew + end, + Option = case OptionRaw of + setresource -> setresource; + closeold -> acceptnew; %% ejabberd_sm will close old session + closenew -> closenew; + acceptnew -> acceptnew; + _ -> acceptnew %% default ejabberd behavior + end, + case Option of + acceptnew -> + {accept_resource, R}; + closenew -> + closenew; + setresource -> + Rnew = lists:concat([randoms:get_string() | tuple_to_list(now())]), + {accept_resource, Rnew} + end. wait_for_bind({xmlstreamelement, El}, StateData) -> try @@ -923,11 +946,17 @@ wait_for_bind({xmlstreamelement, El}, StateData) -> %%send_element(StateData, %% exmpp_stream:features([exmpp_server_session:feature() %% | RosterVersioningFeature])), - JID = exmpp_jid:make(StateData#state.user, StateData#state.server, R), - Res = exmpp_server_binding:bind(El, JID), - send_element(StateData, Res), - fsm_next_state(wait_for_session, + case resource_conflict_action(StateData#state.user, StateData#state.server, list_to_binary(R)) of + closenew -> + send_element(StateData, ?SERRT_CONFLICT), %% (Lang, "Replaced by new connection")), + fsm_next_state(wait_for_bind, StateData); + {accept_resource, R2} -> + JID = exmpp_jid:make(StateData#state.user, StateData#state.server, R2), + Res = exmpp_server_binding:bind(El, JID), + send_element(StateData, Res), + fsm_next_state(wait_for_session, StateData#state{resource = exmpp_jid:resource(JID), jid = JID}) + end catch throw:{stringprep, resourceprep, _, _} -> Err = exmpp_server_binding:error(El, 'bad-request'), diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index dc476355e..0a46d0c61 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -59,6 +59,7 @@ get_session_pid/1, get_user_info/3, get_user_ip/1, + is_existing_resource/3, migrate/1 ]). @@ -727,16 +728,12 @@ check_for_sessions_to_replace(JID) -> check_max_sessions(JID). check_existing_resources(JID) -> - USR = {exmpp_jid:prep_node(JID), - exmpp_jid:prep_domain(JID), - exmpp_jid:prep_resource(JID)}, %% A connection exist with the same resource. We replace it: - SIDs = mnesia:dirty_select( - session, - [{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]), + SIDs = get_resource_sessions(JID), if SIDs == [] -> ok; true -> + %% A connection exist with the same resource. We replace it: MaxSID = lists:max(SIDs), lists:foreach( fun({_, Pid} = S) when S /= MaxSID -> @@ -745,6 +742,17 @@ check_existing_resources(JID) -> end, SIDs) end. +is_existing_resource(U, S, R) -> + [] /= get_resource_sessions(exmpp_jid:make(U, S, R)). + +get_resource_sessions(JID) -> + USR = {exmpp_jid:prep_node(JID), + exmpp_jid:prep_domain(JID), + exmpp_jid:prep_resource(JID)}, + mnesia:dirty_select( + session, + [{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]). + check_max_sessions(JID) -> %% If the max number of sessions for a given is reached, we replace the %% first one