diff options
Diffstat (limited to 'src/ejabberd_c2s.erl')
-rw-r--r-- | src/ejabberd_c2s.erl | 5798 |
1 files changed, 2818 insertions, 2980 deletions
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index f92898d99..9853bb3ac 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -25,7 +25,9 @@ %%%---------------------------------------------------------------------- -module(ejabberd_c2s). + -author('alexey@process-one.net'). + -update_info({update, 0}). -define(GEN_FSM, p1_fsm). @@ -33,107 +35,106 @@ -behaviour(?GEN_FSM). %% External exports --export([start/2, - stop/1, - start_link/3, - send_text/2, - send_element/2, - socket_type/0, - get_presence/1, - get_aux_field/2, - set_aux_field/3, - del_aux_field/2, - get_subscription/2, - broadcast/4, - get_subscribed/1]). +-export([start/2, stop/1, start_link/3, send_text/2, + send_element/2, socket_type/0, get_presence/1, + get_aux_field/2, set_aux_field/3, del_aux_field/2, + get_subscription/2, broadcast/4, get_subscribed/1]). %% API: -export([add_rosteritem/3, del_rosteritem/2]). %% gen_fsm callbacks --export([init/1, - wait_for_stream/2, - wait_for_auth/2, - wait_for_feature_request/2, - wait_for_bind/2, - wait_for_session/2, - wait_for_sasl_response/2, - session_established/2, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - print_state/1, - migrate/3, - migrate_shutdown/3 - ]). +-export([init/1, wait_for_stream/2, wait_for_auth/2, + wait_for_feature_request/2, wait_for_bind/2, + wait_for_session/2, wait_for_sasl_response/2, + session_established/2, handle_event/3, + handle_sync_event/4, code_change/4, handle_info/3, + terminate/3, print_state/1, migrate/3, + migrate_shutdown/3]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("mod_privacy.hrl"). + -include("ejabberd_c2s.hrl"). %-define(DBGFSM, true). -ifdef(DBGFSM). + -define(FSMOPTS, [{debug, [trace]}]). + -else. + -define(FSMOPTS, []). + -endif. %% Module start with or without supervisor: -ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, - [SockData, Opts, FSMLimitOpts], - FSMLimitOpts ++ ?FSMOPTS)). + +-define(SUPERVISOR_START, + (?GEN_FSM):start(ejabberd_c2s, + [SockData, Opts, FSMLimitOpts], + FSMLimitOpts ++ (?FSMOPTS))). + -else. --define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup, - [SockData, Opts, FSMLimitOpts])). + +-define(SUPERVISOR_START, + supervisor:start_child(ejabberd_c2s_sup, + [SockData, Opts, FSMLimitOpts])). + -endif. -%% This is the timeout to apply between event when starting a new -%% session: -define(C2S_OPEN_TIMEOUT, 60000). + -define(C2S_HIBERNATE_TIMEOUT, 90000). -define(STREAM_HEADER, - "<?xml version='1.0'?>" - "<stream:stream xmlns='jabber:client' " - "xmlns:stream='http://etherx.jabber.org/streams' " - "id='~s' from='~s'~s~s>" - ). + <<"<?xml version='1.0'?><stream:stream " + "xmlns='jabber:client' xmlns:stream='http://et" + "herx.jabber.org/streams' id='~s' from='~s'~s~" + "s>">>). -define(FLASH_STREAM_HEADER, - "<?xml version='1.0'?>" - "<flash:stream xmlns='jabber:client' " - "xmlns:stream='http://etherx.jabber.org/streams' " - "id='~s' from='~s'~s~s>" - ). + <<"<?xml version='1.0'?><flash:stream xmlns='jab" + "ber:client' xmlns:stream='http://etherx.jabbe" + "r.org/streams' id='~s' from='~s'~s~s>">>). --define(STREAM_TRAILER, "</stream:stream>"). +-define(STREAM_TRAILER, <<"</stream:stream>">>). -define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE). + -define(INVALID_XML_ERR, ?SERR_XML_NOT_WELL_FORMED). + -define(HOST_UNKNOWN_ERR, ?SERR_HOST_UNKNOWN). + -define(POLICY_VIOLATION_ERR(Lang, Text), ?SERRT_POLICY_VIOLATION(Lang, Text)). + -define(INVALID_FROM, ?SERR_INVALID_FROM). --define(NS_P1_REBIND, "p1:rebind"). --define(NS_P1_PUSH, "p1:push"). --define(NS_P1_ACK, "p1:ack"). --define(NS_P1_PUSHED, "p1:pushed"). --define(NS_P1_ATTACHMENT, "http://process-one.net/attachement"). +-define(NS_P1_REBIND, <<"p1:rebind">>). + +-define(NS_P1_PUSH, <<"p1:push">>). + +-define(NS_P1_ACK, <<"p1:ack">>). + +-define(NS_P1_PUSHED, <<"p1:pushed">>). + +-define(NS_P1_ATTACHMENT, + <<"http://process-one.net/attachement">>). -define(C2S_P1_ACK_TIMEOUT, 10000). --define(MAX_OOR_TIMEOUT, 1440). %% Max allowed session duration 24h (24*60) + +-define(MAX_OOR_TIMEOUT, 1440). + -define(MAX_OOR_MESSAGES, 1000). -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -start(StateName, #state{fsm_limit_opts = Opts} = State) -> +start(StateName, + #state{fsm_limit_opts = Opts} = State) -> start(StateName, State, Opts); start(SockData, Opts) -> start(SockData, Opts, fsm_limit_opts(Opts)). @@ -142,33 +143,34 @@ start(SockData, Opts, FSMLimitOpts) -> ?SUPERVISOR_START. start_link(SockData, Opts, FSMLimitOpts) -> - ?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts, FSMLimitOpts], - FSMLimitOpts ++ ?FSMOPTS). + (?GEN_FSM):start_link(ejabberd_c2s, + [SockData, Opts, FSMLimitOpts], + FSMLimitOpts ++ (?FSMOPTS)). -socket_type() -> - xml_stream. +socket_type() -> xml_stream. -%% Return Username, Resource and presence information get_presence(FsmRef) -> - ?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000). + (?GEN_FSM):sync_send_all_state_event(FsmRef, + {get_presence}, 1000). add_rosteritem(FsmRef, IJID, ISubscription) -> - ?GEN_FSM:send_all_state_event(FsmRef, {add_rosteritem, IJID, ISubscription}). + (?GEN_FSM):send_all_state_event(FsmRef, + {add_rosteritem, IJID, ISubscription}). del_rosteritem(FsmRef, IJID) -> - ?GEN_FSM:send_all_state_event(FsmRef, {del_rosteritem, IJID}). + (?GEN_FSM):send_all_state_event(FsmRef, + {del_rosteritem, IJID}). get_aux_field(Key, #state{aux_fields = Opts}) -> case lists:keysearch(Key, 1, Opts) of - {value, {_, Val}} -> - {ok, Val}; - _ -> - error + {value, {_, Val}} -> {ok, Val}; + _ -> error end. -set_aux_field(Key, Val, #state{aux_fields = Opts} = State) -> +set_aux_field(Key, Val, + #state{aux_fields = Opts} = State) -> Opts1 = lists:keydelete(Key, 1, Opts), - State#state{aux_fields = [{Key, Val}|Opts1]}. + State#state{aux_fields = [{Key, Val} | Opts1]}. del_aux_field(Key, #state{aux_fields = Opts} = State) -> Opts1 = lists:keydelete(Key, 1, Opts), @@ -177,11 +179,13 @@ del_aux_field(Key, #state{aux_fields = Opts} = State) -> get_subscription(From = #jid{}, StateData) -> get_subscription(jlib:jid_tolower(From), StateData); get_subscription(LFrom, StateData) -> - LBFrom = setelement(3, LFrom, ""), - F = ?SETS:is_element(LFrom, StateData#state.pres_f) orelse - ?SETS:is_element(LBFrom, StateData#state.pres_f), - T = ?SETS:is_element(LFrom, StateData#state.pres_t) orelse - ?SETS:is_element(LBFrom, StateData#state.pres_t), + LBFrom = setelement(3, LFrom, <<"">>), + F = (?SETS):is_element(LFrom, StateData#state.pres_f) + orelse + (?SETS):is_element(LBFrom, StateData#state.pres_f), + T = (?SETS):is_element(LFrom, StateData#state.pres_t) + orelse + (?SETS):is_element(LBFrom, StateData#state.pres_t), if F and T -> both; F -> from; T -> to; @@ -191,8 +195,7 @@ get_subscription(LFrom, StateData) -> broadcast(FsmRef, Type, From, Packet) -> FsmRef ! {broadcast, Type, From, Packet}. -stop(FsmRef) -> - ?GEN_FSM:send_event(FsmRef, closed). +stop(FsmRef) -> (?GEN_FSM):send_event(FsmRef, closed). migrate(FsmRef, Node, After) -> erlang:send_after(After, FsmRef, {migrate, Node}). @@ -204,127 +207,105 @@ migrate_shutdown(FsmRef, Node, After) -> %%% Callback functions from gen_fsm %%%---------------------------------------------------------------------- -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- init([{SockMod, Socket}, Opts, FSMLimitOpts]) -> Access = case lists:keysearch(access, 1, Opts) of - {value, {_, A}} -> A; - _ -> all + {value, {_, A}} -> A; + _ -> all end, Shaper = case lists:keysearch(shaper, 1, Opts) of - {value, {_, S}} -> S; - _ -> none + {value, {_, S}} -> S; + _ -> none end, - XMLSocket = - case lists:keysearch(xml_socket, 1, Opts) of - {value, {_, XS}} -> XS; - _ -> false - end, + XMLSocket = case lists:keysearch(xml_socket, 1, Opts) of + {value, {_, XS}} -> XS; + _ -> false + end, Zlib = lists:member(zlib, Opts), StartTLS = lists:member(starttls, Opts), - StartTLSRequired = lists:member(starttls_required, Opts), + StartTLSRequired = lists:member(starttls_required, + Opts), TLSEnabled = lists:member(tls, Opts), - TLS = StartTLS orelse StartTLSRequired orelse TLSEnabled, - TLSOpts1 = - lists:filter(fun({certfile, _}) -> true; - (_) -> false - end, Opts), + TLS = StartTLS orelse + StartTLSRequired orelse TLSEnabled, + TLSOpts1 = lists:filter(fun ({certfile, _}) -> true; + (_) -> false + end, + Opts), TLSOpts = [verify_none | TLSOpts1], Redirect = case lists:keysearch(redirect, 1, Opts) of - {value, {_, true}} -> - true; - _ -> - false - end, + {value, {_, true}} -> true; + _ -> false + end, IP = case lists:keysearch(frontend_ip, 1, Opts) of - {value, {_, IP1}} -> - IP1; - _ -> - peerip(SockMod, Socket) + {value, {_, IP1}} -> IP1; + _ -> peerip(SockMod, Socket) end, - %% Check if IP is blacklisted: + FlashHack = ejabberd_config:get_local_option( + flash_hack, fun(V) -> V end, false), case is_ip_blacklisted(IP) of - true -> - ?INFO_MSG("Connection attempt from blacklisted IP: ~s (~w)", - [jlib:ip_to_list(IP), IP]), - {stop, normal}; - false -> - Socket1 = - if - TLSEnabled andalso SockMod /= ejabberd_frontend_socket -> - SockMod:starttls(Socket, TLSOpts); - true -> - Socket - end, - SocketMonitor = SockMod:monitor(Socket1), - StateData = #state{socket = Socket1, - sockmod = SockMod, - socket_monitor = SocketMonitor, - xml_socket = XMLSocket, - zlib = Zlib, - tls = TLS, - tls_required = StartTLSRequired, - tls_enabled = TLSEnabled, - tls_options = TLSOpts, - streamid = new_id(), - access = Access, - shaper = Shaper, - ip = IP, - redirect = Redirect, - fsm_limit_opts = FSMLimitOpts}, - erlang:send_after(?C2S_OPEN_TIMEOUT, self(), open_timeout), - case get_jid_from_opts(Opts) of - {ok, #jid{user = U, server = Server, resource = R} = JID} -> - ?GEN_FSM:send_event(self(), open_session), - {ok, wait_for_session, StateData#state{ - user = U, - server = Server, - resource = R, - jid = JID, - lang = ""}}; - _ -> - {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT} - end + true -> + ?INFO_MSG("Connection attempt from blacklisted " + "IP: ~s (~w)", + [jlib:ip_to_list(IP), IP]), + {stop, normal}; + false -> + Socket1 = if TLSEnabled andalso + SockMod /= ejabberd_frontend_socket -> + SockMod:starttls(Socket, TLSOpts); + true -> Socket + end, + SocketMonitor = SockMod:monitor(Socket1), + StateData = #state{socket = Socket1, sockmod = SockMod, + socket_monitor = SocketMonitor, + xml_socket = XMLSocket, zlib = Zlib, tls = TLS, + tls_required = StartTLSRequired, + tls_enabled = TLSEnabled, tls_options = TLSOpts, + streamid = new_id(), access = Access, + shaper = Shaper, ip = IP, redirect = Redirect, + flash_hack = FlashHack, + fsm_limit_opts = FSMLimitOpts}, + erlang:send_after(?C2S_OPEN_TIMEOUT, self(), + open_timeout), + case get_jid_from_opts(Opts) of + {ok, + #jid{user = U, server = Server, resource = R} = JID} -> + (?GEN_FSM):send_event(self(), open_session), + {ok, wait_for_session, + StateData#state{user = U, server = Server, resource = R, + jid = JID, lang = <<"">>}}; + _ -> {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT} + end end; init([StateName, StateData, _FSMLimitOpts]) -> - MRef = (StateData#state.sockmod):monitor(StateData#state.socket), + MRef = + (StateData#state.sockmod):monitor(StateData#state.socket), if StateName == session_established -> - Conn = get_conn_type(StateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, - {auth_module, StateData#state.auth_module}], - {Time, _} = StateData#state.sid, - SID = {Time, self()}, - Priority = case StateData#state.pres_last of - undefined -> - undefined; - El -> - get_priority_from_presence(El) - end, - ejabberd_sm:drop_session(StateData#state.sid), - ejabberd_sm:open_session( - SID, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - Priority, - Info), - %%ejabberd_sm:drop_session(StateData#state.sid), - NewStateData = StateData#state{sid = SID, socket_monitor = MRef}, - StateData2 = change_reception(NewStateData, true), - StateData3 = start_keepalive_timer(StateData2), - {ok, StateName, StateData3}; + Conn = (StateData#state.sockmod):get_conn_type( + StateData#state.socket), + Info = [{ip, StateData#state.ip}, {conn, Conn}, + {auth_module, StateData#state.auth_module}], + {Time, _} = StateData#state.sid, + SID = {Time, self()}, + Priority = case StateData#state.pres_last of + undefined -> undefined; + El -> get_priority_from_presence(El) + end, + ejabberd_sm:drop_session(StateData#state.sid), + ejabberd_sm:open_session(SID, StateData#state.user, + StateData#state.server, + StateData#state.resource, Priority, Info), + NewStateData = StateData#state{sid = SID, + socket_monitor = MRef}, + StateData2 = change_reception(NewStateData, true), + StateData3 = start_keepalive_timer(StateData2), + {ok, StateName, StateData3}; true -> - {ok, StateName, StateData#state{socket_monitor = MRef}} + {ok, StateName, StateData#state{socket_monitor = MRef}} end. -%% Return list of all available resources of contacts, get_subscribed(FsmRef) -> - ?GEN_FSM:sync_send_all_state_event(FsmRef, get_subscribed, 1000). + (?GEN_FSM):sync_send_all_state_event(FsmRef, + get_subscribed, 1000). %%---------------------------------------------------------------------- %% Func: StateName/2 @@ -333,1003 +314,1052 @@ get_subscribed(FsmRef) -> %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) -> - DefaultLang = case ?MYLANG of - undefined -> - "en"; - DL -> - DL - end, - - case {xml:get_attr_s("xmlns:stream", Attrs), - xml:get_attr_s("xmlns:flash", Attrs), - ?FLASH_HACK, - StateData#state.flash_connection} of - {_, ?NS_FLASH_STREAM, true, false} -> - %% Flash client connecting - attention! - %% Some of them don't provide an xmlns:stream attribute - - %% compensate for that. - wait_for_stream({xmlstreamstart, Name, [{"xmlns:stream", ?NS_STREAM}|Attrs]}, - StateData#state{flash_connection = true}); - {?NS_STREAM, _, _, _} -> - Server = jlib:nameprep(xml:get_attr_s("to", Attrs)), - case lists:member(Server, ?MYHOSTS) of - true -> - Lang = case xml:get_attr_s("xml:lang", Attrs) of - Lang1 when length(Lang1) =< 35 -> - %% As stated in BCP47, 4.4.1: - %% Protocols or specifications that - %% specify limited buffer sizes for - %% language tags MUST allow for - %% language tags of at least 35 characters. - Lang1; - _ -> - %% Do not store long language tag to - %% avoid possible DoS/flood attacks - "" - end, - change_shaper(StateData, jlib:make_jid("", Server, "")), - case xml:get_attr_s("version", Attrs) of - "1.0" -> - send_header(StateData, Server, "1.0", DefaultLang), - case StateData#state.authenticated of - false -> - SASLState = - cyrsasl:server_new( - "jabber", Server, "", [], - fun(U) -> - ejabberd_auth:get_password_with_authmodule( - U, Server) - end, - fun(U, P) -> - ejabberd_auth:check_password_with_authmodule( - U, Server, P) - end, - fun(U, P, D, DG) -> - ejabberd_auth:check_password_with_authmodule( - U, Server, P, D, DG) - end), - Mechs = lists:map( - fun(S) -> - {xmlelement, "mechanism", [], - [{xmlcdata, S}]} - end, cyrsasl:listmech(Server)), - SockMod = - (StateData#state.sockmod):get_sockmod( - StateData#state.socket), - Zlib = StateData#state.zlib, - CompressFeature = - case Zlib andalso - ((SockMod == gen_tcp) orelse - (SockMod == tls)) of - true -> - [{xmlelement, "compression", - [{"xmlns", ?NS_FEATURE_COMPRESS}], - [{xmlelement, "method", - [], [{xmlcdata, "zlib"}]}]}]; - _ -> - [] - end, - TLS = StateData#state.tls, - TLSEnabled = StateData#state.tls_enabled, - TLSRequired = StateData#state.tls_required, - TLSFeature = - case (TLS == true) andalso - (TLSEnabled == false) andalso - (SockMod == gen_tcp) of - true -> - case TLSRequired of - true -> - [{xmlelement, "starttls", - [{"xmlns", ?NS_TLS}], - [{xmlelement, "required", - [], []}]}]; - _ -> - [{xmlelement, "starttls", - [{"xmlns", ?NS_TLS}], []}] - end; - false -> - [] - end, - P1PushFeature = - [{xmlelement, "push", - [{"xmlns", ?NS_P1_PUSH}], []}], - P1RebindFeature = - [{xmlelement, "rebind", - [{"xmlns", ?NS_P1_REBIND}], []}], - P1AckFeature = - [{xmlelement, "ack", - [{"xmlns", ?NS_P1_ACK}], []}], - send_element(StateData, - {xmlelement, "stream:features", [], - TLSFeature ++ - CompressFeature ++ - P1PushFeature ++ - P1RebindFeature ++ - P1AckFeature ++ - [{xmlelement, "mechanisms", - [{"xmlns", ?NS_SASL}], - Mechs}] ++ - ejabberd_hooks:run_fold( - c2s_stream_features, - Server, - [], [Server])}), - fsm_next_state(wait_for_feature_request, - StateData#state{ - server = Server, - sasl_state = SASLState, - lang = Lang}); - _ -> - case StateData#state.resource of - "" -> - RosterVersioningFeature = - ejabberd_hooks:run_fold( - roster_get_versioning_feature, - Server, [], [Server]), - StreamFeatures = - [{xmlelement, "push", - [{"xmlns", ?NS_P1_PUSH}], []}, - {xmlelement, "bind", - [{"xmlns", ?NS_BIND}], []}, - {xmlelement, "session", - [{"xmlns", ?NS_SESSION}], []}] - ++ RosterVersioningFeature - ++ ejabberd_hooks:run_fold( - c2s_stream_features, - Server, - [], [Server]), - send_element( - StateData, - {xmlelement, "stream:features", [], - StreamFeatures}), - fsm_next_state(wait_for_bind, - StateData#state{ - server = Server, - lang = Lang}); - _ -> - send_element( - StateData, - {xmlelement, "stream:features", [], []}), - fsm_next_state(wait_for_session, - StateData#state{ - server = Server, - lang = Lang}) - end - end; +wait_for_stream({xmlstreamstart, Name, Attrs}, + StateData) -> + DefaultLang = ?MYLANG, + case {xml:get_attr_s(<<"xmlns:stream">>, Attrs), + xml:get_attr_s(<<"xmlns:flash">>, Attrs), + StateData#state.flash_hack, + StateData#state.flash_connection} + of + {_, ?NS_FLASH_STREAM, true, false} -> + wait_for_stream({xmlstreamstart, Name, + [{<<"xmlns:stream">>, ?NS_STREAM} | Attrs]}, + StateData#state{flash_connection = true}); + {?NS_STREAM, _, _, _} -> + Server = jlib:nameprep(xml:get_attr_s(<<"to">>, Attrs)), + case lists:member(Server, ?MYHOSTS) of + true -> + Lang = case xml:get_attr_s(<<"xml:lang">>, Attrs) of + Lang1 when byte_size(Lang1) =< 35 -> + %% As stated in BCP47, 4.4.1: + %% Protocols or specifications that + %% specify limited buffer sizes for + %% language tags MUST allow for + %% language tags of at least 35 characters. + Lang1; + _ -> + %% Do not store long language tag to + %% avoid possible DoS/flood attacks + <<"">> + end, + change_shaper(StateData, + jlib:make_jid(<<"">>, Server, <<"">>)), + case xml:get_attr_s(<<"version">>, Attrs) of + <<"1.0">> -> + send_header(StateData, Server, <<"1.0">>, DefaultLang), + case StateData#state.authenticated of + false -> + SASLState = cyrsasl:server_new(<<"jabber">>, Server, + <<"">>, [], + fun (U) -> + ejabberd_auth:get_password_with_authmodule(U, + Server) + end, + fun (U, P) -> + ejabberd_auth:check_password_with_authmodule(U, + Server, + P) + end, + fun (U, P, D, DG) -> + ejabberd_auth:check_password_with_authmodule(U, + Server, + P, + D, + DG) + end), + Mechs = lists:map(fun (S) -> + #xmlel{name = + <<"mechanism">>, + attrs = [], + children = + [{xmlcdata, + S}]} + end, + cyrsasl:listmech(Server)), + SockMod = + (StateData#state.sockmod):get_sockmod(StateData#state.socket), + Zlib = StateData#state.zlib, + CompressFeature = case Zlib andalso + (SockMod == gen_tcp orelse + SockMod == tls) + of + true -> + [#xmlel{name = + <<"compression">>, + attrs = + [{<<"xmlns">>, + ?NS_FEATURE_COMPRESS}], + children = + [#xmlel{name = + <<"method">>, + attrs = + [], + children + = + [{xmlcdata, + <<"zlib">>}]}]}]; + _ -> [] + end, + TLS = StateData#state.tls, + TLSEnabled = StateData#state.tls_enabled, + TLSRequired = StateData#state.tls_required, + TLSFeature = case TLS == true andalso + TLSEnabled == false andalso + SockMod == gen_tcp + of + true -> + case TLSRequired of + true -> + [#xmlel{name = + <<"starttls">>, + attrs = + [{<<"xmlns">>, + ?NS_TLS}], + children = + [#xmlel{name = + <<"required">>, + attrs = + [], + children + = + []}]}]; + _ -> + [#xmlel{name = + <<"starttls">>, + attrs = + [{<<"xmlns">>, + ?NS_TLS}], + children = []}] + end; + false -> [] + end, + P1PushFeature = [#xmlel{name = <<"push">>, + attrs = + [{<<"xmlns">>, + ?NS_P1_PUSH}], + children = []}], + P1RebindFeature = [#xmlel{name = <<"rebind">>, + attrs = + [{<<"xmlns">>, + ?NS_P1_REBIND}], + children = []}], + P1AckFeature = [#xmlel{name = <<"ack">>, + attrs = + [{<<"xmlns">>, + ?NS_P1_ACK}], + children = []}], + send_element(StateData, + #xmlel{name = <<"stream:features">>, + attrs = [], + children = + TLSFeature ++ + CompressFeature ++ + P1PushFeature ++ + P1RebindFeature ++ + P1AckFeature ++ + [#xmlel{name = + <<"mechanisms">>, + attrs = + [{<<"xmlns">>, + ?NS_SASL}], + children = + Mechs}] + ++ + ejabberd_hooks:run_fold(c2s_stream_features, + Server, + [], + [Server])}), + fsm_next_state(wait_for_feature_request, + StateData#state{server = Server, + sasl_state = + SASLState, + lang = Lang}); _ -> - send_header(StateData, Server, "", DefaultLang), - if - (not StateData#state.tls_enabled) and - StateData#state.tls_required -> - send_element( - StateData, - ?POLICY_VIOLATION_ERR( - Lang, - "Use of STARTTLS required")), - send_trailer(StateData), - {stop, normal, StateData}; - true -> - fsm_next_state(wait_for_auth, - StateData#state{ - server = Server, - lang = Lang}) + case StateData#state.resource of + <<"">> -> + RosterVersioningFeature = + ejabberd_hooks:run_fold(roster_get_versioning_feature, + Server, [], + [Server]), + StreamFeatures = [#xmlel{name = <<"push">>, + attrs = + [{<<"xmlns">>, + ?NS_P1_PUSH}], + children = []}, + #xmlel{name = <<"bind">>, + attrs = + [{<<"xmlns">>, + ?NS_BIND}], + children = []}, + #xmlel{name = <<"session">>, + attrs = + [{<<"xmlns">>, + ?NS_SESSION}], + children = []}] + ++ + RosterVersioningFeature ++ + ejabberd_hooks:run_fold(c2s_stream_features, + Server, + [], + [Server]), + send_element(StateData, + #xmlel{name = + <<"stream:features">>, + attrs = [], + children = + StreamFeatures}), + fsm_next_state(wait_for_bind, + StateData#state{server = + Server, + lang = Lang}); + _ -> + send_element(StateData, + #xmlel{name = + <<"stream:features">>, + attrs = [], + children = []}), + fsm_next_state(wait_for_session, + StateData#state{server = + Server, + lang = Lang}) end - end; - _ -> - send_header(StateData, ?MYNAME, "", DefaultLang), - send_element(StateData, ?HOST_UNKNOWN_ERR), - send_trailer(StateData), - {stop, normal, StateData} - end; - _ -> - case Name of - "policy-file-request" -> - send_text(StateData, flash_policy_string()), - {stop, normal, StateData}; - _ -> - send_header(StateData, ?MYNAME, "", DefaultLang), - send_element(StateData, ?INVALID_NS_ERR), - send_trailer(StateData), - {stop, normal, StateData} - end + end; + _ -> + send_header(StateData, Server, <<"">>, DefaultLang), + if not StateData#state.tls_enabled and + StateData#state.tls_required -> + send_element(StateData, + ?POLICY_VIOLATION_ERR(Lang, + <<"Use of STARTTLS required">>)), + send_trailer(StateData), + {stop, normal, StateData}; + true -> + fsm_next_state(wait_for_auth, + StateData#state{server = Server, + lang = Lang}) + end + end; + _ -> + send_header(StateData, ?MYNAME, <<"">>, DefaultLang), + send_element(StateData, ?HOST_UNKNOWN_ERR), + send_trailer(StateData), + {stop, normal, StateData} + end; + _ -> + case Name of + <<"policy-file-request">> -> + send_text(StateData, flash_policy_string()), + {stop, normal, StateData}; + _ -> + send_header(StateData, ?MYNAME, <<"">>, DefaultLang), + send_element(StateData, ?INVALID_NS_ERR), + send_trailer(StateData), + {stop, normal, StateData} + end end; - wait_for_stream(timeout, StateData) -> {stop, normal, StateData}; - wait_for_stream({xmlstreamelement, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_stream({xmlstreamend, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_stream({xmlstreamerror, _}, StateData) -> - send_header(StateData, ?MYNAME, "1.0", ""), + send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>), send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_stream(closed, StateData) -> {stop, normal, StateData}. - wait_for_auth({xmlstreamelement, El}, StateData) -> case is_auth_packet(El) of - {auth, _ID, get, {U, _, _, _}} -> - {xmlelement, Name, Attrs, _Els} = jlib:make_result_iq_reply(El), - case U of - "" -> - UCdata = []; - _ -> - UCdata = [{xmlcdata, U}] - end, - Res = case ejabberd_auth:plain_password_required( - StateData#state.server) of - false -> - {xmlelement, Name, Attrs, - [{xmlelement, "query", [{"xmlns", ?NS_AUTH}], - [{xmlelement, "username", [], UCdata}, - {xmlelement, "password", [], []}, - {xmlelement, "digest", [], []}, - {xmlelement, "resource", [], []} - ]}]}; - true -> - {xmlelement, Name, Attrs, - [{xmlelement, "query", [{"xmlns", ?NS_AUTH}], - [{xmlelement, "username", [], UCdata}, - {xmlelement, "password", [], []}, - {xmlelement, "resource", [], []} - ]}]} - end, - send_element(StateData, Res), - fsm_next_state(wait_for_auth, StateData); - {auth, _ID, set, {_U, _P, _D, ""}} -> - Err = jlib:make_error_reply( - El, - ?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData); - {auth, _ID, set, {U, P, D, R}} -> - JID = jlib:make_jid(U, StateData#state.server, R), - case (JID /= error) andalso - (acl:match_rule(StateData#state.server, - StateData#state.access, JID) == allow) of - true -> - DGen = fun(PW) -> - sha:sha(StateData#state.streamid ++ PW) end, - case ejabberd_auth:check_password_with_authmodule( - U, StateData#state.server, P, D, DGen) of - {true, AuthModule} -> - ?INFO_MSG( - "(~w) Accepted legacy authentication for ~s by ~p", - [StateData#state.socket, - jlib:jid_to_string(JID), AuthModule]), - case need_redirect(StateData#state{user = U}) of - {true, Host} -> - ?INFO_MSG("(~w) Redirecting ~s to ~s", - [StateData#state.socket, - jlib:jid_to_string(JID), Host]), - send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), - send_trailer(StateData), - {stop, normal, StateData}; - false -> - SID = {now(), self()}, - Conn = get_conn_type(StateData), - Res1 = jlib:make_result_iq_reply(El), - Res = setelement(4, Res1, []), - send_element(StateData, Res), - change_shaper(StateData, JID), - {Fs, Ts} = ejabberd_hooks:run_fold( - roster_get_subscription_lists, - StateData#state.server, - {[], []}, - [U, StateData#state.server]), - LJID = jlib:jid_tolower( - jlib:jid_remove_resource(JID)), - Fs1 = [LJID | Fs], - Ts1 = [LJID | Ts], - PrivList = - ejabberd_hooks:run_fold( - privacy_get_user_list, - StateData#state.server, - #userlist{}, - [U, StateData#state.server]), - NewStateData = - StateData#state{ - user = U, - resource = R, - jid = JID, - sid = SID, - conn = Conn, - auth_module = AuthModule, - pres_f = ?SETS:from_list(Fs1), - pres_t = ?SETS:from_list(Ts1), - privacy_list = PrivList}, - DebugFlag = ejabberd_hooks:run_fold( - c2s_debug_start_hook, - NewStateData#state.server, - false, - [self(), NewStateData]), - maybe_migrate(session_established, - NewStateData#state{debug=DebugFlag}) - end; - _ -> - ?INFO_MSG( - "(~w) Failed legacy authentication for ~s", - [StateData#state.socket, - jlib:jid_to_string(JID)]), - Err = jlib:make_error_reply( - El, ?ERR_NOT_AUTHORIZED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData) - end; - _ -> - if - JID == error -> - ?INFO_MSG( - "(~w) Forbidden legacy authentication for " - "username '~s' with resource '~s'", - [StateData#state.socket, U, R]), - Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData); - true -> - ?INFO_MSG( - "(~w) Forbidden legacy authentication for ~s", - [StateData#state.socket, - jlib:jid_to_string(JID)]), - Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), - send_element(StateData, Err), - fsm_next_state(wait_for_auth, StateData) - end - end; - _ -> - {xmlelement, Name, Attrs, _Els} = El, - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_P1_REBIND, "rebind"} -> - SJID = xml:get_path_s(El, [{elem, "jid"}, cdata]), - SID = xml:get_path_s(El, [{elem, "sid"}, cdata]), - case jlib:string_to_jid(SJID) of - error -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_P1_REBIND}], - [{xmlcdata, "Invalid JID"}]}), - fsm_next_state(wait_for_auth, - StateData); - JID -> - case rebind(StateData, JID, SID) of - {next_state, wait_for_feature_request, - NewStateData, Timeout} -> - {next_state, wait_for_auth, - NewStateData, Timeout}; - Res -> - Res - end - end; - _ -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_auth, StateData) - end + {auth, _ID, get, {U, _, _, _}} -> + #xmlel{name = Name, attrs = Attrs} = + jlib:make_result_iq_reply(El), + case U of + <<"">> -> UCdata = []; + _ -> UCdata = [{xmlcdata, U}] + end, + Res = case + ejabberd_auth:plain_password_required(StateData#state.server) + of + false -> + #xmlel{name = Name, attrs = Attrs, + children = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_AUTH}], + children = + [#xmlel{name = <<"username">>, + attrs = [], + children = UCdata}, + #xmlel{name = <<"password">>, + attrs = [], children = []}, + #xmlel{name = <<"digest">>, + attrs = [], children = []}, + #xmlel{name = <<"resource">>, + attrs = [], + children = []}]}]}; + true -> + #xmlel{name = Name, attrs = Attrs, + children = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_AUTH}], + children = + [#xmlel{name = <<"username">>, + attrs = [], + children = UCdata}, + #xmlel{name = <<"password">>, + attrs = [], children = []}, + #xmlel{name = <<"resource">>, + attrs = [], + children = []}]}]} + end, + send_element(StateData, Res), + fsm_next_state(wait_for_auth, StateData); + {auth, _ID, set, {_U, _P, _D, <<"">>}} -> + Err = jlib:make_error_reply(El, + ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData); + {auth, _ID, set, {U, P, D, R}} -> + JID = jlib:make_jid(U, StateData#state.server, R), + case JID /= error andalso + acl:match_rule(StateData#state.server, + StateData#state.access, JID) + == allow + of + true -> + DGen = fun (PW) -> + sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>) + end, + case ejabberd_auth:check_password_with_authmodule(U, + StateData#state.server, + P, D, DGen) + of + {true, AuthModule} -> + ?INFO_MSG("(~w) Accepted legacy authentication " + "for ~s by ~p", + [StateData#state.socket, + jlib:jid_to_string(JID), AuthModule]), + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, + jlib:jid_to_string(JID), Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + SID = {now(), self()}, + Conn = (StateData#state.sockmod):get_conn_type( + StateData#state.socket), + Res1 = jlib:make_result_iq_reply(El), + Res = Res1#xmlel{children = []}, + send_element(StateData, Res), + change_shaper(StateData, JID), + {Fs, Ts} = + ejabberd_hooks:run_fold(roster_get_subscription_lists, + StateData#state.server, + {[], []}, + [U, + StateData#state.server]), + LJID = + jlib:jid_tolower(jlib:jid_remove_resource(JID)), + Fs1 = [LJID | Fs], + Ts1 = [LJID | Ts], + PrivList = + ejabberd_hooks:run_fold(privacy_get_user_list, + StateData#state.server, + #userlist{}, + [U, + StateData#state.server]), + NewStateData = StateData#state{user = U, + resource = R, + jid = JID, sid = SID, + conn = Conn, + auth_module = + AuthModule, + pres_f = + (?SETS):from_list(Fs1), + pres_t = + (?SETS):from_list(Ts1), + privacy_list = + PrivList}, + DebugFlag = + ejabberd_hooks:run_fold(c2s_debug_start_hook, + NewStateData#state.server, + false, + [self(), NewStateData]), + maybe_migrate(session_established, + NewStateData#state{debug = DebugFlag}) + end; + _ -> + ?INFO_MSG("(~w) Failed legacy authentication for ~s", + [StateData#state.socket, + jlib:jid_to_string(JID)]), + Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData) + end; + _ -> + if JID == error -> + ?INFO_MSG("(~w) Forbidden legacy authentication " + "for username '~s' with resource '~s'", + [StateData#state.socket, U, R]), + Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData); + true -> + ?INFO_MSG("(~w) Forbidden legacy authentication " + "for ~s", + [StateData#state.socket, + jlib:jid_to_string(JID)]), + Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), + send_element(StateData, Err), + fsm_next_state(wait_for_auth, StateData) + end + end; + _ -> + #xmlel{name = Name, attrs = Attrs} = El, + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_P1_REBIND, <<"rebind">>} -> + SJID = xml:get_path_s(El, [{elem, <<"jid">>}, cdata]), + SID = xml:get_path_s(El, [{elem, <<"sid">>}, cdata]), + case jlib:string_to_jid(SJID) of + error -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = + [{<<"xmlns">>, ?NS_P1_REBIND}], + children = + [{xmlcdata, <<"Invalid JID">>}]}), + fsm_next_state(wait_for_auth, StateData); + JID -> + case rebind(StateData, JID, SID) of + {next_state, wait_for_feature_request, NewStateData, + Timeout} -> + {next_state, wait_for_auth, NewStateData, Timeout}; + Res -> Res + end + end; + _ -> + process_unauthenticated_stanza(StateData, El), + fsm_next_state(wait_for_auth, StateData) + end end; - wait_for_auth(timeout, StateData) -> {stop, normal, StateData}; - wait_for_auth({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - + send_trailer(StateData), {stop, normal, StateData}; wait_for_auth({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_auth(closed, StateData) -> {stop, normal, StateData}. - -wait_for_feature_request({xmlstreamelement, El}, StateData) -> - {xmlelement, Name, Attrs, Els} = El, +wait_for_feature_request({xmlstreamelement, El}, + StateData) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = El, Zlib = StateData#state.zlib, TLS = StateData#state.tls, TLSEnabled = StateData#state.tls_enabled, TLSRequired = StateData#state.tls_required, - SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket), - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_SASL, "auth"} when not ((SockMod == gen_tcp) and TLSRequired) -> - 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} -> - catch (StateData#state.sockmod):reset_stream( - StateData#state.socket), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - case need_redirect(StateData#state{user = U}) of - {true, Host} -> - ?INFO_MSG("(~w) Redirecting ~s to ~s", - [StateData#state.socket, U, Host]), - send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), - send_trailer(StateData), - {stop, normal, StateData}; - false -> - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U }) - end; - {continue, ServerOut, NewSASLState} -> - send_element(StateData, - {xmlelement, "challenge", - [{"xmlns", ?NS_SASL}], - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_sasl_response, - StateData#state{ - sasl_state = NewSASLState}); - {error, Error, Username} -> - ?INFO_MSG( - "(~w) Failed authentication for ~s@~s", - [StateData#state.socket, - Username, StateData#state.server]), - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - {next_state, wait_for_feature_request, StateData, - ?C2S_OPEN_TIMEOUT}; - {error, Error} -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end; - {?NS_TLS, "starttls"} when TLS == true, - TLSEnabled == false, - SockMod == gen_tcp -> - TLSOpts = case ejabberd_config:get_local_option( - {domain_certfile, StateData#state.server}) of - undefined -> - StateData#state.tls_options; - CertFile -> - [{certfile, CertFile} | - lists:keydelete( - certfile, 1, StateData#state.tls_options)] - end, - Socket = StateData#state.socket, - TLSSocket = (StateData#state.sockmod):starttls( - Socket, TLSOpts, - xml:element_to_binary( - {xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})), - fsm_next_state(wait_for_stream, - StateData#state{socket = TLSSocket, - streamid = new_id(), - tls_enabled = true - }); - {?NS_COMPRESS, "compress"} when Zlib == true, - ((SockMod == gen_tcp) or - (SockMod == tls)) -> - case xml:get_subtag(El, "method") of - false -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_COMPRESS}], - [{xmlelement, "setup-failed", [], []}]}), - fsm_next_state(wait_for_feature_request, StateData); - Method -> - case xml:get_tag_cdata(Method) of - "zlib" -> - Socket = StateData#state.socket, - ZlibSocket = (StateData#state.sockmod):compress( - Socket, - xml:element_to_binary( - {xmlelement, "compressed", - [{"xmlns", ?NS_COMPRESS}], []})), - fsm_next_state(wait_for_stream, - StateData#state{socket = ZlibSocket, - streamid = new_id() - }); - _ -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_COMPRESS}], - [{xmlelement, "unsupported-method", - [], []}]}), - fsm_next_state(wait_for_feature_request, - StateData) - end - end; - {?NS_P1_REBIND, "rebind"} -> - SJID = xml:get_path_s(El, [{elem, "jid"}, cdata]), - SID = xml:get_path_s(El, [{elem, "sid"}, cdata]), - case jlib:string_to_jid(SJID) of - error -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_P1_REBIND}], - [{xmlcdata, "Invalid JID"}]}), - fsm_next_state(wait_for_feature_request, - StateData); - JID -> - rebind(StateData, JID, SID) - end; - {?NS_P1_ACK, "ack"} -> - fsm_next_state(wait_for_feature_request, - StateData#state{ack_enabled = true}); - _ -> - if - (SockMod == gen_tcp) and TLSRequired -> - Lang = StateData#state.lang, - send_element(StateData, ?POLICY_VIOLATION_ERR( - Lang, - "Use of STARTTLS required")), - send_trailer(StateData), - {stop, normal, StateData}; - true -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_feature_request, StateData) - end + SockMod = + (StateData#state.sockmod):get_sockmod(StateData#state.socket), + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_SASL, <<"auth">>} + when not ((SockMod == gen_tcp) and TLSRequired) -> + 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} -> + catch + (StateData#state.sockmod):reset_stream(StateData#state.socket), + %U = xml:get_attr_s(username, Props), + U = proplists:get_value(username, Props, <<>>), + %AuthModule = xml:get_attr_s(auth_module, Props), + AuthModule = proplists:get_value(auth_module, Props, undefined), + ?INFO_MSG("(~w) Accepted authentication for ~s " + "by ~p", + [StateData#state.socket, U, AuthModule]), + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, U, Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}) + end; + {continue, ServerOut, NewSASLState} -> + send_element(StateData, + #xmlel{name = <<"challenge">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [{xmlcdata, + jlib:encode_base64(ServerOut)}]}), + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); + {error, Error, Username} -> + ?INFO_MSG("(~w) Failed authentication for ~s@~s", + [StateData#state.socket, Username, + StateData#state.server]), + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + {next_state, wait_for_feature_request, StateData, + ?C2S_OPEN_TIMEOUT}; + {error, Error} -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData) + end; + {?NS_TLS, <<"starttls">>} + when TLS == true, TLSEnabled == false, + SockMod == gen_tcp -> + TLSOpts = case + ejabberd_config:get_local_option( + {domain_certfile, StateData#state.server}, + fun iolist_to_binary/1) + of + undefined -> StateData#state.tls_options; + CertFile -> + [{certfile, CertFile} | lists:keydelete(certfile, 1, + StateData#state.tls_options)] + end, + Socket = StateData#state.socket, + TLSSocket = (StateData#state.sockmod):starttls(Socket, + TLSOpts, + xml:element_to_binary(#xmlel{name + = + <<"proceed">>, + attrs + = + [{<<"xmlns">>, + ?NS_TLS}], + children + = + []})), + fsm_next_state(wait_for_stream, + StateData#state{socket = TLSSocket, + streamid = new_id(), + tls_enabled = true}); + {?NS_COMPRESS, <<"compress">>} + when Zlib == true, + (SockMod == gen_tcp) or (SockMod == tls) -> + case xml:get_subtag(El, <<"method">>) of + false -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_COMPRESS}], + children = + [#xmlel{name = <<"setup-failed">>, + attrs = [], children = []}]}), + fsm_next_state(wait_for_feature_request, StateData); + Method -> + case xml:get_tag_cdata(Method) of + <<"zlib">> -> + Socket = StateData#state.socket, + ZlibSocket = (StateData#state.sockmod):compress(Socket, + xml:element_to_binary(#xmlel{name + = + <<"compressed">>, + attrs + = + [{<<"xmlns">>, + ?NS_COMPRESS}], + children + = + []})), + fsm_next_state(wait_for_stream, + StateData#state{socket = ZlibSocket, + streamid = new_id()}); + _ -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_COMPRESS}], + children = + [#xmlel{name = + <<"unsupported-method">>, + attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData) + end + end; + {?NS_P1_REBIND, <<"rebind">>} -> + SJID = xml:get_path_s(El, [{elem, <<"jid">>}, cdata]), + SID = xml:get_path_s(El, [{elem, <<"sid">>}, cdata]), + case jlib:string_to_jid(SJID) of + error -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_P1_REBIND}], + children = + [{xmlcdata, <<"Invalid JID">>}]}), + fsm_next_state(wait_for_feature_request, StateData); + JID -> rebind(StateData, JID, SID) + end; + {?NS_P1_ACK, <<"ack">>} -> + fsm_next_state(wait_for_feature_request, + StateData#state{ack_enabled = true}); + _ -> + if (SockMod == gen_tcp) and TLSRequired -> + Lang = StateData#state.lang, + send_element(StateData, + ?POLICY_VIOLATION_ERR(Lang, + <<"Use of STARTTLS required">>)), + send_trailer(StateData), + {stop, normal, StateData}; + true -> + process_unauthenticated_stanza(StateData, El), + fsm_next_state(wait_for_feature_request, StateData) + end end; - wait_for_feature_request(timeout, StateData) -> {stop, normal, StateData}; - -wait_for_feature_request({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - -wait_for_feature_request({xmlstreamerror, _}, StateData) -> +wait_for_feature_request({xmlstreamend, _Name}, + StateData) -> + send_trailer(StateData), {stop, normal, StateData}; +wait_for_feature_request({xmlstreamerror, _}, + StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_feature_request(closed, StateData) -> {stop, normal, StateData}. - -wait_for_sasl_response({xmlstreamelement, El}, StateData) -> - {xmlelement, Name, Attrs, Els} = El, - case {xml:get_attr_s("xmlns", Attrs), Name} of - {?NS_SASL, "response"} -> - ClientIn = jlib:decode_base64(xml:get_cdata(Els)), - case cyrsasl:server_step(StateData#state.sasl_state, - ClientIn) of - {ok, Props} -> - catch (StateData#state.sockmod):reset_stream( - StateData#state.socket), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - case need_redirect(StateData#state{user = U}) of - {true, Host} -> - ?INFO_MSG("(~w) Redirecting ~s to ~s", - [StateData#state.socket, U, Host]), - send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), - send_trailer(StateData), - {stop, normal, StateData}; - false -> - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U}) - end; - {ok, Props, ServerOut} -> - (StateData#state.sockmod):reset_stream( - StateData#state.socket), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - case need_redirect(StateData#state{user = U}) of - {true, Host} -> - ?INFO_MSG("(~w) Redirecting ~s to ~s", - [StateData#state.socket, U, Host]), - send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), - send_trailer(StateData), - {stop, normal, StateData}; - false -> - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U}) - end; - {continue, ServerOut, NewSASLState} -> - send_element(StateData, - {xmlelement, "challenge", - [{"xmlns", ?NS_SASL}], - [{xmlcdata, - jlib:encode_base64(ServerOut)}]}), - fsm_next_state(wait_for_sasl_response, - StateData#state{sasl_state = NewSASLState}); - {error, Error, Username} -> - ?INFO_MSG( - "(~w) Failed authentication for ~s@~s", - [StateData#state.socket, - Username, StateData#state.server]), - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - fsm_next_state(wait_for_feature_request, StateData); - {error, Error} -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_SASL}], - [{xmlelement, Error, [], []}]}), - fsm_next_state(wait_for_feature_request, StateData) - end; - _ -> - process_unauthenticated_stanza(StateData, El), - fsm_next_state(wait_for_feature_request, StateData) +wait_for_sasl_response({xmlstreamelement, El}, + StateData) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = El, + case {xml:get_attr_s(<<"xmlns">>, Attrs), Name} of + {?NS_SASL, <<"response">>} -> + ClientIn = jlib:decode_base64(xml:get_cdata(Els)), + case cyrsasl:server_step(StateData#state.sasl_state, + ClientIn) + of + {ok, Props} -> + catch + (StateData#state.sockmod):reset_stream(StateData#state.socket), +% U = xml:get_attr_s(username, Props), + U = proplists:get_value(username, Props, <<>>), +% AuthModule = xml:get_attr_s(auth_module, Props), + AuthModule = proplists:get_value(auth_module, Props, <<>>), + ?INFO_MSG("(~w) Accepted authentication for ~s " + "by ~p", + [StateData#state.socket, U, AuthModule]), + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, U, Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = []}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}) + end; + {ok, Props, ServerOut} -> + (StateData#state.sockmod):reset_stream(StateData#state.socket), +% U = xml:get_attr_s(username, Props), + U = proplists:get_value(username, Props, <<>>), +% AuthModule = xml:get_attr_s(auth_module, Props), + AuthModule = proplists:get_value(auth_module, Props, undefined), + ?INFO_MSG("(~w) Accepted authentication for ~s " + "by ~p", + [StateData#state.socket, U, AuthModule]), + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, U, Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + send_element(StateData, + #xmlel{name = <<"success">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [{xmlcdata, + jlib:encode_base64(ServerOut)}]}), + fsm_next_state(wait_for_stream, + StateData#state{streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}) + end; + {continue, ServerOut, NewSASLState} -> + send_element(StateData, + #xmlel{name = <<"challenge">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [{xmlcdata, + jlib:encode_base64(ServerOut)}]}), + fsm_next_state(wait_for_sasl_response, + StateData#state{sasl_state = NewSASLState}); + {error, Error, Username} -> + ?INFO_MSG("(~w) Failed authentication for ~s@~s", + [StateData#state.socket, Username, + StateData#state.server]), + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData); + {error, Error} -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_SASL}], + children = + [#xmlel{name = Error, attrs = [], + children = []}]}), + fsm_next_state(wait_for_feature_request, StateData) + end; + _ -> + process_unauthenticated_stanza(StateData, El), + fsm_next_state(wait_for_feature_request, StateData) end; - wait_for_sasl_response(timeout, StateData) -> {stop, normal, StateData}; - -wait_for_sasl_response({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - -wait_for_sasl_response({xmlstreamerror, _}, StateData) -> +wait_for_sasl_response({xmlstreamend, _Name}, + StateData) -> + send_trailer(StateData), {stop, normal, StateData}; +wait_for_sasl_response({xmlstreamerror, _}, + StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - 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,S}); - false -> - acceptnew + OptionRaw = case ejabberd_sm:is_existing_resource(U, S, + R) + of + true -> + ejabberd_config:get_local_option( + {resource_conflict, S}, + fun(setresource) -> setresource; + (closeold) -> closeold; + (closenew) -> closenew; + (acceptnew) -> acceptnew + end); + 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 + 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} + acceptnew -> {accept_resource, R}; + closenew -> closenew; + setresource -> + Rnew = iolist_to_binary([randoms:get_string() + | tuple_to_list(now())]), + {accept_resource, Rnew} end. wait_for_bind({xmlstreamelement, El}, StateData) -> case jlib:iq_query_info(El) of - #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = IQ -> - U = StateData#state.user, - R1 = xml:get_path_s(SubEl, [{elem, "resource"}, cdata]), - R = case jlib:resourceprep(R1) of - error -> error; - "" -> - lists:concat( - [randoms:get_string() | tuple_to_list(now())]); - Resource -> Resource - end, - case R of - error -> - Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), - send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData); - _ -> - %%Server = StateData#state.server, - %%RosterVersioningFeature = - %% ejabberd_hooks:run_fold( - %% roster_get_versioning_feature, Server, [], [Server]), - %%StreamFeatures = [{xmlelement, "session", - %% [{"xmlns", ?NS_SESSION}], []} | - %% RosterVersioningFeature], - %%send_element(StateData, {xmlelement, "stream:features", - %% [], StreamFeatures}), - case resource_conflict_action(U, StateData#state.server, R) of - closenew -> - Err = jlib:make_error_reply(El, ?STANZA_ERROR("409", "modify", "conflict")), - send_element(StateData, Err), - fsm_next_state(wait_for_bind, StateData); - {accept_resource, R2} -> - JID = jlib:make_jid(U, StateData#state.server, R2), - Res = IQ#iq{type = result, - sub_el = [{xmlelement, "bind", - [{"xmlns", ?NS_BIND}], - [{xmlelement, "jid", [], - [{xmlcdata, - jlib:jid_to_string(JID)}]}]}]}, - send_element(StateData, jlib:iq_to_xml(Res)), - fsm_next_state(wait_for_session, - StateData#state{resource = R2, jid = JID}) - end - end; - _ -> - fsm_next_state(wait_for_bind, StateData) + #iq{type = set, xmlns = ?NS_BIND, sub_el = SubEl} = + IQ -> + U = StateData#state.user, + R1 = xml:get_path_s(SubEl, + [{elem, <<"resource">>}, cdata]), + R = case jlib:resourceprep(R1) of + error -> error; + <<"">> -> + iolist_to_binary([randoms:get_string() + | tuple_to_list(now())]); + Resource -> Resource + end, + case R of + error -> + Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST), + send_element(StateData, Err), + fsm_next_state(wait_for_bind, StateData); + _ -> + case resource_conflict_action(U, StateData#state.server, + R) + of + closenew -> + Err = jlib:make_error_reply(El, + ?STANZA_ERROR(<<"409">>, + <<"modify">>, + <<"conflict">>)), + send_element(StateData, Err), + fsm_next_state(wait_for_bind, StateData); + {accept_resource, R2} -> + JID = jlib:make_jid(U, StateData#state.server, R2), + Res = IQ#iq{type = result, + sub_el = + [#xmlel{name = <<"bind">>, + attrs = [{<<"xmlns">>, ?NS_BIND}], + children = + [#xmlel{name = <<"jid">>, + attrs = [], + children = + [{xmlcdata, + jlib:jid_to_string(JID)}]}]}]}, + send_element(StateData, jlib:iq_to_xml(Res)), + fsm_next_state(wait_for_session, + StateData#state{resource = R2, jid = JID}) + end + end; + _ -> fsm_next_state(wait_for_bind, StateData) end; - wait_for_bind(timeout, StateData) -> {stop, normal, StateData}; - wait_for_bind({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - + send_trailer(StateData), {stop, normal, StateData}; wait_for_bind({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_bind(closed, StateData) -> {stop, normal, StateData}. - - wait_for_session({xmlstreamelement, El}, StateData) -> case jlib:iq_query_info(El) of - #iq{type = set, xmlns = ?NS_SESSION} -> - U = StateData#state.user, - %%R = StateData#state.resource, - JID = StateData#state.jid, - case acl:match_rule(StateData#state.server, - StateData#state.access, JID) of - allow -> - ?INFO_MSG("(~w) Opened session for ~s", - [StateData#state.socket, - jlib:jid_to_string(JID)]), - Res = jlib:make_result_iq_reply(El), - send_element(StateData, Res), - change_shaper(StateData, JID), - {Fs, Ts} = ejabberd_hooks:run_fold( - roster_get_subscription_lists, - StateData#state.server, - {[], []}, - [U, StateData#state.server]), - LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)), - Fs1 = [LJID | Fs], - Ts1 = [LJID | Ts], - PrivList = - ejabberd_hooks:run_fold( - privacy_get_user_list, StateData#state.server, - #userlist{}, - [U, StateData#state.server]), - SID = {now(), self()}, - Conn = get_conn_type(StateData), - %% Info = [{ip, StateData#state.ip}, {conn, Conn}, - %% {auth_module, StateData#state.auth_module}], - %% ejabberd_sm:open_session( - %% SID, U, StateData#state.server, R, Info), - NewStateData = - StateData#state{ - sid = SID, - conn = Conn, - pres_f = ?SETS:from_list(Fs1), - pres_t = ?SETS:from_list(Ts1), - privacy_list = PrivList}, - DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook, - NewStateData#state.server, - false, - [self(), NewStateData]), - maybe_migrate(session_established, NewStateData#state{debug=DebugFlag}); - _ -> - ejabberd_hooks:run(forbidden_session_hook, - StateData#state.server, [JID]), - ?INFO_MSG("(~w) Forbidden session for ~s", - [StateData#state.socket, - jlib:jid_to_string(JID)]), - Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), - send_element(StateData, Err), - fsm_next_state(wait_for_session, StateData) - end; - _ -> - fsm_next_state(wait_for_session, StateData) + #iq{type = set, xmlns = ?NS_SESSION} -> + U = StateData#state.user, + JID = StateData#state.jid, + case acl:match_rule(StateData#state.server, + StateData#state.access, JID) + of + allow -> + ?INFO_MSG("(~w) Opened session for ~s", + [StateData#state.socket, jlib:jid_to_string(JID)]), + Res = jlib:make_result_iq_reply(El), + send_element(StateData, Res), + change_shaper(StateData, JID), + {Fs, Ts} = + ejabberd_hooks:run_fold(roster_get_subscription_lists, + StateData#state.server, {[], []}, + [U, StateData#state.server]), + LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)), + Fs1 = [LJID | Fs], + Ts1 = [LJID | Ts], + PrivList = + ejabberd_hooks:run_fold(privacy_get_user_list, + StateData#state.server, #userlist{}, + [U, StateData#state.server]), + SID = {now(), self()}, + Conn = (StateData#state.sockmod):get_conn_type( + StateData#state.socket), + NewStateData = StateData#state{sid = SID, conn = Conn, + pres_f = (?SETS):from_list(Fs1), + pres_t = (?SETS):from_list(Ts1), + privacy_list = PrivList}, + DebugFlag = + ejabberd_hooks:run_fold(c2s_debug_start_hook, + NewStateData#state.server, false, + [self(), NewStateData]), + maybe_migrate(session_established, + NewStateData#state{debug = DebugFlag}); + _ -> + ejabberd_hooks:run(forbidden_session_hook, + StateData#state.server, [JID]), + ?INFO_MSG("(~w) Forbidden session for ~s", + [StateData#state.socket, jlib:jid_to_string(JID)]), + Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED), + send_element(StateData, Err), + fsm_next_state(wait_for_session, StateData) + end; + _ -> fsm_next_state(wait_for_session, StateData) end; - wait_for_session(open_session, StateData) -> - El = {xmlelement, "iq", [{"type", "set"}, {"id", "session"}], - [{xmlelement, "session", [{"xmlns", ?NS_SESSION}], []}]}, + El = #xmlel{name = <<"iq">>, + attrs = + [{<<"type">>, <<"set">>}, {<<"id">>, <<"session">>}], + children = + [#xmlel{name = <<"session">>, + attrs = [{<<"xmlns">>, ?NS_SESSION}], + children = []}]}, wait_for_session({xmlstreamelement, El}, StateData); - wait_for_session(timeout, StateData) -> {stop, normal, StateData}; - wait_for_session({xmlstreamend, _Name}, StateData) -> - send_trailer(StateData), - {stop, normal, StateData}; - + send_trailer(StateData), {stop, normal, StateData}; wait_for_session({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - wait_for_session(closed, StateData) -> {stop, normal, StateData}. - -session_established({xmlstreamelement, El}, StateData) -> +session_established({xmlstreamelement, El}, + StateData) -> FromJID = StateData#state.jid, - % Check 'from' attribute in stanza RFC 3920 Section 9.1.2 case check_from(El, FromJID) of - 'invalid-from' -> - send_element(StateData, ?INVALID_FROM), - send_trailer(StateData), - {stop, normal, StateData}; - _NewEl -> - NSD1 = change_reception(StateData, true), - NSD2 = start_keepalive_timer(NSD1), - session_established2(El, NSD2) + 'invalid-from' -> + send_element(StateData, ?INVALID_FROM), + send_trailer(StateData), + {stop, normal, StateData}; + _NewEl -> + NSD1 = change_reception(StateData, true), + NSD2 = start_keepalive_timer(NSD1), + session_established2(El, NSD2) end; - %% We hibernate the process to reduce memory consumption after a %% configurable activity timeout session_established(timeout, StateData) -> - %% TODO: Options must be stored in state: Options = [], proc_lib:hibernate(?GEN_FSM, enter_loop, [?MODULE, Options, session_established, StateData]), fsm_next_state(session_established, StateData); - session_established({xmlstreamend, _Name}, StateData) -> + send_trailer(StateData), {stop, normal, StateData}; +session_established({xmlstreamerror, + <<"XML stanza is too big">> = E}, + StateData) -> + send_element(StateData, + ?POLICY_VIOLATION_ERR((StateData#state.lang), E)), send_trailer(StateData), {stop, normal, StateData}; - -session_established({xmlstreamerror, "XML stanza is too big" = E}, StateData) -> - send_element(StateData, ?POLICY_VIOLATION_ERR(StateData#state.lang, E)), - send_trailer(StateData), - {stop, normal, StateData}; - session_established({xmlstreamerror, _}, StateData) -> send_element(StateData, ?INVALID_XML_ERR), send_trailer(StateData), {stop, normal, StateData}; - session_established(closed, StateData) -> - if - not StateData#state.reception -> - fsm_next_state(session_established, StateData); - (StateData#state.keepalive_timer /= undefined) -> - NewState1 = change_reception(StateData, false), - NewState = start_keepalive_timer(NewState1), - fsm_next_state(session_established, NewState); - true -> - {stop, normal, StateData} + if not StateData#state.reception -> + fsm_next_state(session_established, StateData); + StateData#state.keepalive_timer /= undefined -> + NewState1 = change_reception(StateData, false), + NewState = start_keepalive_timer(NewState1), + fsm_next_state(session_established, NewState); + true -> {stop, normal, StateData} end. -%% Process packets sent by user (coming from user on c2s XMPP -%% connection) session_established2(El, StateData) -> - {xmlelement, Name, Attrs, _Els} = El, + #xmlel{name = Name, attrs = Attrs} = El, User = StateData#state.user, Server = StateData#state.server, FromJID = StateData#state.jid, - To = xml:get_attr_s("to", Attrs), + To = xml:get_attr_s(<<"to">>, Attrs), ToJID = case To of - "" -> - jlib:make_jid(User, Server, ""); - _ -> - jlib:string_to_jid(To) + <<"">> -> jlib:make_jid(User, Server, <<"">>); + _ -> jlib:string_to_jid(To) end, - NewEl1 = jlib:remove_attr("xmlns", El), - NewEl = case xml:get_attr_s("xml:lang", Attrs) of - "" -> - case StateData#state.lang of - "" -> NewEl1; - Lang -> - xml:replace_tag_attr("xml:lang", Lang, NewEl1) - end; - _ -> - NewEl1 + NewEl1 = jlib:remove_attr(<<"xmlns">>, El), + NewEl = case xml:get_attr_s(<<"xml:lang">>, Attrs) of + <<"">> -> + case StateData#state.lang of + <<"">> -> NewEl1; + Lang -> + xml:replace_tag_attr(<<"xml:lang">>, Lang, NewEl1) + end; + _ -> NewEl1 end, - NewState = - case ToJID of - error -> - case xml:get_attr_s("type", Attrs) of - "error" -> StateData; - "result" -> StateData; - _ -> - Err = jlib:make_error_reply(NewEl, ?ERR_JID_MALFORMED), - send_element(StateData, Err), - StateData - end; - _ -> - case Name of - "presence" -> - PresenceEl = ejabberd_hooks:run_fold( - c2s_update_presence, - Server, - NewEl, - [User, Server]), - ejabberd_hooks:run( - user_send_packet, - Server, - [StateData#state.debug, FromJID, ToJID, PresenceEl]), - case ToJID of - #jid{user = User, - server = Server, - resource = ""} -> - ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)", - [FromJID, PresenceEl, StateData]), - presence_update(FromJID, PresenceEl, - StateData); - _ -> - presence_track(FromJID, ToJID, PresenceEl, - StateData) - end; - "iq" -> - case jlib:iq_query_info(NewEl) of - #iq{xmlns = Xmlns} = IQ - when Xmlns == ?NS_PRIVACY; - Xmlns == ?NS_BLOCKING -> - ejabberd_hooks:run( - user_send_packet, - Server, - [StateData#state.debug, FromJID, ToJID, NewEl]), - process_privacy_iq( - FromJID, ToJID, IQ, StateData); - #iq{xmlns = ?NS_P1_PUSH} = IQ -> - process_push_iq(FromJID, ToJID, IQ, StateData); - _ -> - ejabberd_hooks:run( - user_send_packet, - Server, - [StateData#state.debug, FromJID, ToJID, NewEl]), - check_privacy_route(FromJID, StateData, FromJID, ToJID, NewEl), - StateData - end; - "message" -> - ejabberd_hooks:run(user_send_packet, - Server, - [StateData#state.debug, FromJID, ToJID, NewEl]), - check_privacy_route(FromJID, StateData, FromJID, - ToJID, NewEl), - StateData; - "standby" -> - StandBy = xml:get_tag_cdata(NewEl) == "true", - change_standby(StateData, StandBy); - "a" -> - SCounter = xml:get_tag_attr_s("h", NewEl), - receive_ack(StateData, SCounter); - _ -> - StateData - end - end, - ejabberd_hooks:run(c2s_loop_debug, [{xmlstreamelement, El}]), + NewState = case ToJID of + error -> + case xml:get_attr_s(<<"type">>, Attrs) of + <<"error">> -> StateData; + <<"result">> -> StateData; + _ -> + Err = jlib:make_error_reply(NewEl, + ?ERR_JID_MALFORMED), + send_element(StateData, Err), + StateData + end; + _ -> + case Name of + <<"presence">> -> + PresenceEl = + ejabberd_hooks:run_fold(c2s_update_presence, + Server, NewEl, + [User, Server]), + ejabberd_hooks:run(user_send_packet, Server, + [StateData#state.debug, FromJID, + ToJID, PresenceEl]), + case ToJID of + #jid{user = User, server = Server, + resource = <<"">>} -> + ?DEBUG("presence_update(~p,~n\t~p,~n\t~p)", + [FromJID, PresenceEl, StateData]), + presence_update(FromJID, PresenceEl, + StateData); + _ -> + presence_track(FromJID, ToJID, PresenceEl, + StateData) + end; + <<"iq">> -> + case jlib:iq_query_info(NewEl) of + #iq{xmlns = Xmlns} = IQ + when Xmlns == (?NS_PRIVACY); + Xmlns == (?NS_BLOCKING) -> + ejabberd_hooks:run(user_send_packet, Server, + [StateData#state.debug, + FromJID, ToJID, NewEl]), + process_privacy_iq(FromJID, ToJID, IQ, + StateData); + #iq{xmlns = ?NS_P1_PUSH} = IQ -> + process_push_iq(FromJID, ToJID, IQ, StateData); + _ -> + ejabberd_hooks:run(user_send_packet, Server, + [StateData#state.debug, + FromJID, ToJID, NewEl]), + check_privacy_route(FromJID, StateData, + FromJID, ToJID, NewEl), + StateData + end; + <<"message">> -> + ejabberd_hooks:run(user_send_packet, Server, + [StateData#state.debug, FromJID, + ToJID, NewEl]), + check_privacy_route(FromJID, StateData, FromJID, + ToJID, NewEl), + StateData; + <<"standby">> -> + StandBy = xml:get_tag_cdata(NewEl) == <<"true">>, + change_standby(StateData, StandBy); + <<"a">> -> + SCounter = xml:get_tag_attr_s(<<"h">>, NewEl), + receive_ack(StateData, SCounter); + _ -> StateData + end + end, + ejabberd_hooks:run(c2s_loop_debug, + [{xmlstreamelement, El}]), fsm_next_state(session_established, NewState). - - %%---------------------------------------------------------------------- %% Func: StateName/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -1343,66 +1373,44 @@ session_established2(El, StateData) -> % Reply = ok, % {reply, Reply, state_name, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({add_rosteritem, IJID, ISubscription}, StateName, StateData) -> - NewStateData = roster_change(IJID, ISubscription, StateData), +handle_event({add_rosteritem, IJID, ISubscription}, + StateName, StateData) -> + NewStateData = roster_change(IJID, ISubscription, + StateData), fsm_next_state(StateName, NewStateData); - -handle_event({del_rosteritem, IJID}, StateName, StateData) -> +handle_event({del_rosteritem, IJID}, StateName, + StateData) -> NewStateData = roster_change(IJID, none, StateData), fsm_next_state(StateName, NewStateData); - -handle_event({xmlstreamcdata, _}, session_established = StateName, StateData) -> +handle_event({xmlstreamcdata, _}, + session_established = StateName, StateData) -> ?DEBUG("cdata ping", []), NSD1 = change_reception(StateData, true), NSD2 = start_keepalive_timer(NSD1), fsm_next_state(StateName, NSD2); - handle_event(_Event, StateName, StateData) -> fsm_next_state(StateName, StateData). -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({get_presence}, _From, StateName, StateData) -> +handle_sync_event({get_presence}, _From, StateName, + StateData) -> User = StateData#state.user, PresLast = StateData#state.pres_last, - Show = get_showtag(PresLast), Status = get_statustag(PresLast), Resource = StateData#state.resource, - Reply = {User, Resource, Show, Status}, fsm_reply(Reply, StateName, StateData); - -handle_sync_event(get_subscribed, _From, StateName, StateData) -> - Subscribed = ?SETS:to_list(StateData#state.pres_f), +handle_sync_event(get_subscribed, _From, StateName, + StateData) -> + Subscribed = (?SETS):to_list(StateData#state.pres_f), {reply, Subscribed, StateName, StateData}; - -handle_sync_event(_Event, _From, StateName, StateData) -> - Reply = ok, - fsm_reply(Reply, StateName, StateData). +handle_sync_event(_Event, _From, StateName, + StateData) -> + Reply = ok, fsm_reply(Reply, StateName, StateData). code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- handle_info({send_text, Text}, StateName, StateData) -> send_text(StateData, Text), ejabberd_hooks:run(c2s_loop_debug, [Text]), @@ -1410,514 +1418,580 @@ handle_info({send_text, Text}, StateName, StateData) -> handle_info(replaced, _StateName, StateData) -> Lang = StateData#state.lang, send_element(StateData, - ?SERRT_CONFLICT(Lang, "Replaced by new connection")), + ?SERRT_CONFLICT(Lang, + <<"Replaced by new connection">>)), send_trailer(StateData), - {stop, normal, StateData#state{authenticated = replaced}}; + {stop, normal, + StateData#state{authenticated = replaced}}; +handle_info({route, _From, _To, {broadcast, Data}}, + StateName, StateData) -> + ?DEBUG("broadcast~n~p~n", [Data]), + case Data of + {item, IJID, ISubscription} -> + fsm_next_state(StateName, + roster_change(IJID, ISubscription, StateData)); + {exit, Reason} -> + Lang = StateData#state.lang, + send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)), + catch send_trailer(StateData), + {stop, normal, StateData}; + {privacy_list, PrivList, PrivListName} -> + case ejabberd_hooks:run_fold(privacy_updated_list, + StateData#state.server, + false, + [StateData#state.privacy_list, + PrivList]) of + false -> + fsm_next_state(StateName, StateData); + NewPL -> + PrivPushIQ = #iq{type = set, + xmlns = ?NS_PRIVACY, + id = <<"push", + (randoms:get_string())/binary>>, + sub_el = + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, + ?NS_PRIVACY}], + children = + [#xmlel{name = <<"list">>, + attrs = [{<<"name">>, + PrivListName}], + children = []}]}]}, + PrivPushEl = jlib:replace_from_to( + jlib:jid_remove_resource(StateData#state.jid), + StateData#state.jid, + jlib:iq_to_xml(PrivPushIQ)), + send_element(StateData, PrivPushEl), + fsm_next_state(StateName, + StateData#state{privacy_list = NewPL}) + end; + {blocking, What} -> + route_blocking(What, StateData), + fsm_next_state(StateName, StateData); + {rebind, Pid2, StreamID2} -> + if StreamID2 == StateData#state.streamid -> + Pid2 ! {rebind, prepare_acks_for_rebind(StateData)}, + receive after 1000 -> ok end, + catch send_trailer(StateData), + {stop, normal, StateData#state{authenticated = rebinded}}; + true -> + Pid2 ! {rebind, false}, + fsm_next_state(StateName, StateData) + end; + _ -> + fsm_next_state(StateName, StateData) + end; %% Process Packets that are to be send to the user -handle_info({route, From, To, Packet}, StateName, StateData) -> - {xmlelement, Name, Attrs, Els} = Packet, - {Pass, NewAttrs, NewState} = - case Name of - "presence" -> - State = ejabberd_hooks:run_fold( - c2s_presence_in, StateData#state.server, - StateData, - [{From, To, Packet}]), - case xml:get_attr_s("type", Attrs) of - "probe" -> - LFrom = jlib:jid_tolower(From), - LBFrom = jlib:jid_remove_resource(LFrom), - NewStateData = - case ?SETS:is_element( - LFrom, State#state.pres_a) orelse - ?SETS:is_element( - LBFrom, State#state.pres_a) of - true -> - State; - false -> - case ?SETS:is_element( - LFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LFrom, - State#state.pres_a), - State#state{pres_a = A}; - false -> - case ?SETS:is_element( - LBFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LBFrom, - State#state.pres_a), - State#state{pres_a = A}; - false -> - State - end - end - end, - process_presence_probe(From, To, NewStateData), - {false, Attrs, NewStateData}; - "error" -> - NewA = remove_element(jlib:jid_tolower(From), - State#state.pres_a), - {true, Attrs, State#state{pres_a = NewA}}; - "invisible" -> - Attrs1 = lists:keydelete("type", 1, Attrs), - {true, [{"type", "unavailable"} | Attrs1], State}; - "subscribe" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - "subscribed" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - "unsubscribe" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - "unsubscribed" -> - SRes = is_privacy_allow(State, From, To, Packet, in), - {SRes, Attrs, State}; - _ -> - case privacy_check_packet(State, From, To, Packet, in) of - allow -> - LFrom = jlib:jid_tolower(From), - LBFrom = jlib:jid_remove_resource(LFrom), - case ?SETS:is_element( - LFrom, State#state.pres_a) orelse - ?SETS:is_element( - LBFrom, State#state.pres_a) of - true -> - {true, Attrs, State}; - false -> - case ?SETS:is_element( - LFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LFrom, - State#state.pres_a), - {true, Attrs, - State#state{pres_a = A}}; - false -> - case ?SETS:is_element( - LBFrom, State#state.pres_f) of - true -> - A = ?SETS:add_element( - LBFrom, - State#state.pres_a), - {true, Attrs, - State#state{pres_a = A}}; - false -> - {true, Attrs, State} - end - end - end; - deny -> - {false, Attrs, State} - end - end; - "broadcast" -> - ?DEBUG("broadcast~n~p~n", [Els]), - case Els of - [{item, IJID, ISubscription}] -> - {false, Attrs, - roster_change(IJID, ISubscription, - StateData)}; - [{exit, Reason}] -> - {exit, Attrs, Reason}; - [{privacy_list, PrivList, PrivListName}] -> - case ejabberd_hooks:run_fold( - privacy_updated_list, StateData#state.server, - false, - [StateData#state.privacy_list, - PrivList]) of - false -> - {false, Attrs, StateData}; - NewPL -> - PrivPushIQ = - #iq{type = set, xmlns = ?NS_PRIVACY, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_PRIVACY}], - [{xmlelement, "list", - [{"name", PrivListName}], - []}]}]}, - PrivPushEl = - jlib:replace_from_to( - jlib:jid_remove_resource( - StateData#state.jid), - StateData#state.jid, - jlib:iq_to_xml(PrivPushIQ)), - send_element(StateData, PrivPushEl), - {false, Attrs, StateData#state{privacy_list = NewPL}} - end; - [{blocking, What}] -> - route_blocking(What, StateData), - {false, Attrs, StateData}; - _ -> - {false, Attrs, StateData} - end; - rebind -> - {Pid2, StreamID2} = Els, - if - StreamID2 == StateData#state.streamid -> - Pid2 ! {rebind, prepare_acks_for_rebind(StateData)}, - receive after 1000 -> ok end, - {exit, Attrs, rebind}; - true -> - Pid2 ! {rebind, false}, - {false, Attrs, StateData} - end; - "iq" -> - IQ = jlib:iq_query_info(Packet), - case IQ of - #iq{xmlns = ?NS_LAST} -> - LFrom = jlib:jid_tolower(From), - LBFrom = jlib:jid_remove_resource(LFrom), - HasFromSub = (?SETS:is_element(LFrom, StateData#state.pres_f) orelse ?SETS:is_element(LBFrom, StateData#state.pres_f)) - andalso is_privacy_allow(StateData, To, From, {xmlelement, "presence", [], []}, out), - case HasFromSub of - true -> - case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - {true, Attrs, StateData}; - deny -> - {false, Attrs, StateData} - end; - _ -> - Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), - ejabberd_router:route(To, From, Err), - {false, Attrs, StateData} - end; - IQ when (is_record(IQ, iq)) or (IQ == reply) -> - case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - {true, Attrs, StateData}; - deny when is_record(IQ, iq) -> - Err = jlib:make_error_reply( - Packet, ?ERR_SERVICE_UNAVAILABLE), - ejabberd_router:route(To, From, Err), - {false, Attrs, StateData}; - deny when IQ == reply -> - {false, Attrs, StateData} - end; - IQ when (IQ == invalid) or (IQ == not_iq) -> - {false, Attrs, StateData} - end; - "message" -> - case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - if StateData#state.reception -> - case ejabberd_hooks:run_fold( - feature_check_packet, StateData#state.server, - allow, - [StateData#state.jid, - StateData#state.server, - StateData#state.pres_last, - {From, To, Packet}, - in]) of - allow -> - {true, Attrs, StateData}; - deny -> - {false, Attrs, StateData} - end; - true -> - {true, Attrs, StateData} - end; - deny -> - {false, Attrs, StateData} - end; - _ -> - {true, Attrs, StateData} - end, - if - Pass == exit -> - catch send_trailer(StateData), - case NewState of - rebind -> - {stop, normal, StateData#state{authenticated = rebinded}}; - _ -> - {stop, normal, StateData} - end; - Pass -> - Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From), - jlib:jid_to_string(To), - NewAttrs), - FixedPacket = {xmlelement, Name, Attrs2, Els}, - NewState2 = - if - NewState#state.reception and - not (NewState#state.standby and (Name /= "message")) -> - send_element(NewState, FixedPacket), - ack(NewState, From, To, FixedPacket); - true -> - NewState1 = send_out_of_reception_message( - NewState, From, To, Packet), - enqueue(NewState1, From, To, FixedPacket) - end, - ejabberd_hooks:run(user_receive_packet, - StateData#state.server, - [StateData#state.debug, StateData#state.jid, From, To, FixedPacket]), - ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]), - fsm_next_state(StateName, NewState2); - true -> - ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]), - fsm_next_state(StateName, NewState) +handle_info({route, From, To, + #xmlel{name = Name, attrs = Attrs, children = Els} = Packet}, + StateName, StateData) -> + {Pass, NewAttrs, NewState} = case Name of + <<"presence">> -> + State = + ejabberd_hooks:run_fold(c2s_presence_in, + StateData#state.server, + StateData, + [{From, To, + Packet}]), + case xml:get_attr_s(<<"type">>, Attrs) of + <<"probe">> -> + LFrom = jlib:jid_tolower(From), + LBFrom = + jlib:jid_remove_resource(LFrom), + NewStateData = case + (?SETS):is_element(LFrom, + State#state.pres_a) + orelse + (?SETS):is_element(LBFrom, + State#state.pres_a) + of + true -> State; + false -> + case + (?SETS):is_element(LFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LFrom, + State#state.pres_a), + State#state{pres_a + = + A}; + false -> + case + (?SETS):is_element(LBFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LBFrom, + State#state.pres_a), + State#state{pres_a + = + A}; + false -> + State + end + end + end, + process_presence_probe(From, To, + NewStateData), + {false, Attrs, NewStateData}; + <<"error">> -> + NewA = + remove_element(jlib:jid_tolower(From), + State#state.pres_a), + {true, Attrs, + State#state{pres_a = NewA}}; + <<"subscribe">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + <<"subscribed">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + <<"unsubscribe">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + <<"unsubscribed">> -> + SRes = is_privacy_allow(State, + From, To, + Packet, + in), + {SRes, Attrs, State}; + _ -> + case privacy_check_packet(State, + From, To, + Packet, + in) + of + allow -> + LFrom = + jlib:jid_tolower(From), + LBFrom = + jlib:jid_remove_resource(LFrom), + case + (?SETS):is_element(LFrom, + State#state.pres_a) + orelse + (?SETS):is_element(LBFrom, + State#state.pres_a) + of + true -> + {true, Attrs, State}; + false -> + case + (?SETS):is_element(LFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LFrom, + State#state.pres_a), + {true, Attrs, + State#state{pres_a + = + A}}; + false -> + case + (?SETS):is_element(LBFrom, + State#state.pres_f) + of + true -> + A = + (?SETS):add_element(LBFrom, + State#state.pres_a), + {true, + Attrs, + State#state{pres_a + = + A}}; + false -> + {true, + Attrs, + State} + end + end + end; + deny -> {false, Attrs, State} + end + end; + <<"iq">> -> + IQ = jlib:iq_query_info(Packet), + case IQ of + #iq{xmlns = ?NS_LAST} -> + LFrom = jlib:jid_tolower(From), + LBFrom = + jlib:jid_remove_resource(LFrom), + HasFromSub = + ((?SETS):is_element(LFrom, + StateData#state.pres_f) + orelse + (?SETS):is_element(LBFrom, + StateData#state.pres_f)) + andalso + is_privacy_allow(StateData, + To, From, + #xmlel{name + = + <<"presence">>, + attrs + = + [], + children + = + []}, + out), + case HasFromSub of + true -> + case + privacy_check_packet(StateData, + From, + To, + Packet, + in) + of + allow -> + {true, Attrs, + StateData}; + deny -> + {false, Attrs, + StateData} + end; + _ -> + Err = + jlib:make_error_reply(Packet, + ?ERR_FORBIDDEN), + ejabberd_router:route(To, + From, + Err), + {false, Attrs, StateData} + end; + IQ + when is_record(IQ, iq) or + (IQ == reply) -> + case + privacy_check_packet(StateData, + From, To, + Packet, in) + of + allow -> + {true, Attrs, StateData}; + deny when is_record(IQ, iq) -> + Err = + jlib:make_error_reply(Packet, + ?ERR_SERVICE_UNAVAILABLE), + ejabberd_router:route(To, + From, + Err), + {false, Attrs, StateData}; + deny when IQ == reply -> + {false, Attrs, StateData} + end; + IQ + when (IQ == invalid) or + (IQ == not_iq) -> + {false, Attrs, StateData} + end; + <<"message">> -> + case privacy_check_packet(StateData, + From, To, + Packet, in) + of + allow -> + if StateData#state.reception -> + case + ejabberd_hooks:run_fold(feature_check_packet, + StateData#state.server, + allow, + [StateData#state.jid, + StateData#state.server, + StateData#state.pres_last, + {From, + To, + Packet}, + in]) + of + allow -> + {true, Attrs, + StateData}; + deny -> + {false, Attrs, + StateData} + end; + true -> {true, Attrs, StateData} + end; + deny -> {false, Attrs, StateData} + end; + _ -> {true, Attrs, StateData} + end, + if Pass -> + Attrs2 = + jlib:replace_from_to_attrs(jlib:jid_to_string(From), + jlib:jid_to_string(To), NewAttrs), + FixedPacket = #xmlel{name = Name, attrs = Attrs2, + children = Els}, + NewState2 = if NewState#state.reception and + not + (NewState#state.standby and + (Name /= "message")) -> + send_element(NewState, FixedPacket), + ack(NewState, From, To, FixedPacket); + true -> + NewState1 = + send_out_of_reception_message(NewState, From, + To, Packet), + enqueue(NewState1, From, To, FixedPacket) + end, + ejabberd_hooks:run(user_receive_packet, + StateData#state.server, + [StateData#state.debug, StateData#state.jid, From, + To, FixedPacket]), + ejabberd_hooks:run(c2s_loop_debug, + [{route, From, To, Packet}]), + fsm_next_state(StateName, NewState2); + true -> + ejabberd_hooks:run(c2s_loop_debug, + [{route, From, To, Packet}]), + fsm_next_state(StateName, NewState) end; handle_info({timeout, Timer, _}, StateName, - #state{keepalive_timer = Timer, reception = true} = StateData) -> + #state{keepalive_timer = Timer, reception = true} = + StateData) -> NewState1 = change_reception(StateData, false), NewState = start_keepalive_timer(NewState1), fsm_next_state(StateName, NewState); handle_info({timeout, Timer, _}, _StateName, - #state{keepalive_timer = Timer, reception = false} = StateData) -> + #state{keepalive_timer = Timer, reception = false} = + StateData) -> {stop, normal, StateData}; handle_info({timeout, Timer, PrevCounter}, StateName, #state{ack_timer = Timer} = StateData) -> AckCounter = StateData#state.ack_counter, - NewState = - if - PrevCounter >= AckCounter -> - StateData#state{ack_timer = undefined}; - true -> - send_ack_request(StateData#state{ack_timer = undefined}) - end, + NewState = if PrevCounter >= AckCounter -> + StateData#state{ack_timer = undefined}; + true -> + send_ack_request(StateData#state{ack_timer = undefined}) + end, fsm_next_state(StateName, NewState); -handle_info({ack_timeout, Counter}, StateName, StateData) -> +handle_info({ack_timeout, Counter}, StateName, + StateData) -> AckQueue = StateData#state.ack_queue, case queue:is_empty(AckQueue) of - true -> - fsm_next_state(StateName, StateData); - false -> - C = element(1, queue:head(AckQueue)), - if - C =< Counter -> - {stop, normal, StateData}; - true -> - fsm_next_state(StateName, StateData) - end + true -> fsm_next_state(StateName, StateData); + false -> + C = element(1, queue:head(AckQueue)), + if C =< Counter -> {stop, normal, StateData}; + true -> fsm_next_state(StateName, StateData) + end end; handle_info(open_timeout, StateName, StateData) -> case StateName of - session_established -> - fsm_next_state(StateName, StateData); - _ -> - {stop, normal, StateData} + session_established -> + fsm_next_state(StateName, StateData); + _ -> {stop, normal, StateData} end; -handle_info({'DOWN', Monitor, _Type, _Object, _Info}, StateName, StateData) - when Monitor == StateData#state.socket_monitor -> - if - (StateName == session_established) and - (not StateData#state.reception) -> - fsm_next_state(StateName, StateData); - (StateName == session_established) and - (StateData#state.keepalive_timer /= undefined) -> - NewState1 = change_reception(StateData, false), - NewState = start_keepalive_timer(NewState1), - fsm_next_state(StateName, NewState); - true -> - {stop, normal, StateData} +handle_info({'DOWN', Monitor, _Type, _Object, _Info}, + StateName, StateData) + when Monitor == StateData#state.socket_monitor -> + if (StateName == session_established) and + not StateData#state.reception -> + fsm_next_state(StateName, StateData); + (StateName == session_established) and + (StateData#state.keepalive_timer /= undefined) -> + NewState1 = change_reception(StateData, false), + NewState = start_keepalive_timer(NewState1), + fsm_next_state(StateName, NewState); + true -> {stop, normal, StateData} end; handle_info(system_shutdown, StateName, StateData) -> case StateName of - wait_for_stream -> - send_header(StateData, ?MYNAME, "1.0", "en"), - send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), - send_trailer(StateData), - ok; - _ -> - send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), - send_trailer(StateData), - ok + wait_for_stream -> + send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>), + send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), + send_trailer(StateData), + ok; + _ -> + send_element(StateData, ?SERR_SYSTEM_SHUTDOWN), + send_trailer(StateData), + ok end, {stop, normal, StateData}; handle_info({force_update_presence, LUser}, StateName, - #state{user = LUser, server = LServer} = StateData) -> - NewStateData = - case StateData#state.pres_last of - {xmlelement, "presence", _Attrs, _Els} -> - PresenceEl = ejabberd_hooks:run_fold( - c2s_update_presence, - LServer, - StateData#state.pres_last, - [LUser, LServer]), - StateData2 = StateData#state{pres_last = PresenceEl}, - presence_update(StateData2#state.jid, - PresenceEl, - StateData2), - StateData2; - _ -> - StateData - end, + #state{user = LUser, server = LServer} = StateData) -> + NewStateData = case StateData#state.pres_last of + #xmlel{name = <<"presence">>} -> + PresenceEl = + ejabberd_hooks:run_fold(c2s_update_presence, + LServer, + StateData#state.pres_last, + [LUser, LServer]), + StateData2 = StateData#state{pres_last = PresenceEl}, + presence_update(StateData2#state.jid, PresenceEl, + StateData2), + StateData2; + _ -> StateData + end, {next_state, StateName, NewStateData}; handle_info({migrate, Node}, StateName, StateData) -> if Node /= node() -> - fsm_migrate(StateName, StateData, Node, 0); - true -> - fsm_next_state(StateName, StateData) + fsm_migrate(StateName, StateData, Node, 0); + true -> fsm_next_state(StateName, StateData) end; -handle_info({migrate_shutdown, Node, After}, StateName, StateData) -> - case StateData#state.sockmod == ejabberd_frontend_socket orelse - StateData#state.xml_socket == true orelse - is_remote_receiver(StateData#state.socket) of - true -> - migrate(self(), Node, After); - false -> - self() ! system_shutdown +handle_info({migrate_shutdown, Node, After}, StateName, + StateData) -> + case StateData#state.sockmod == ejabberd_frontend_socket + orelse + StateData#state.xml_socket == true orelse + (StateData#state.sockmod):is_remote_receiver( + StateData#state.socket) + of + true -> migrate(self(), Node, After); + false -> self() ! system_shutdown end, fsm_next_state(StateName, StateData); -handle_info({broadcast, Type, From, Packet}, StateName, StateData) -> - Recipients = ejabberd_hooks:run_fold( - c2s_broadcast_recipients, StateData#state.server, - [], - [StateData, Type, From, Packet]), - lists:foreach( - fun(USR) -> - ejabberd_router:route( - From, jlib:make_jid(USR), Packet) - end, lists:usort(Recipients)), +handle_info({broadcast, Type, From, Packet}, StateName, + StateData) -> + Recipients = + ejabberd_hooks:run_fold(c2s_broadcast_recipients, + StateData#state.server, [], + [StateData#state.server, StateData, + Type, From, Packet]), + lists:foreach(fun (USR) -> + ejabberd_router:route(From, jlib:make_jid(USR), + Packet) + end, + lists:usort(Recipients)), fsm_next_state(StateName, StateData); -handle_info({change_socket, Socket}, StateName, StateData) -> +handle_info({change_socket, Socket}, StateName, + StateData) -> erlang:demonitor(StateData#state.socket_monitor), - NewSocket = (StateData#state.sockmod):change_socket( - StateData#state.socket, Socket), + NewSocket = + (StateData#state.sockmod):change_socket(StateData#state.socket, + Socket), MRef = (StateData#state.sockmod):monitor(NewSocket), fsm_next_state(StateName, - StateData#state{socket = NewSocket, - socket_monitor = MRef}); + StateData#state{socket = NewSocket, + socket_monitor = MRef}); handle_info(Info, StateName, StateData) -> ?ERROR_MSG("Unexpected info: ~p", [Info]), fsm_next_state(StateName, StateData). +print_state(State = #state{pres_t = T, pres_f = F, + pres_a = A}) -> + State#state{pres_t = {pres_t, (?SETS):size(T)}, + pres_f = {pres_f, (?SETS):size(F)}, + pres_a = {pres_a, (?SETS):size(A)}}. -%%---------------------------------------------------------------------- -%% Func: print_state/1 -%% Purpose: Prepare the state to be printed on error log -%% Returns: State to print -%%---------------------------------------------------------------------- -print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) -> - State#state{pres_t = {pres_t, ?SETS:size(T)}, - pres_f = {pres_f, ?SETS:size(F)}, - pres_a = {pres_a, ?SETS:size(A)}, - pres_i = {pres_i, ?SETS:size(I)} - }. - -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- terminate({migrated, ClonePid}, StateName, StateData) -> ejabberd_hooks:run(c2s_debug_stop_hook, - StateData#state.server, - [self(), StateData]), + StateData#state.server, [self(), StateData]), if StateName == session_established -> - ?INFO_MSG("(~w) Migrating ~s to ~p on node ~p", - [StateData#state.socket, - jlib:jid_to_string(StateData#state.jid), - ClonePid, node(ClonePid)]), - ejabberd_sm:close_migrated_session(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource); - true -> - ok + ?INFO_MSG("(~w) Migrating ~s to ~p on node ~p", + [StateData#state.socket, + jlib:jid_to_string(StateData#state.jid), ClonePid, + node(ClonePid)]), + ejabberd_sm:close_migrated_session(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource); + true -> ok end, - (StateData#state.sockmod):change_controller( - StateData#state.socket, ClonePid), + (StateData#state.sockmod):change_controller(StateData#state.socket, + ClonePid), ok; terminate(_Reason, StateName, StateData) -> case StateName of - session_established -> - case StateData#state.authenticated of - replaced -> - ?INFO_MSG("(~w) Replaced session for ~s", - [StateData#state.socket, - jlib:jid_to_string(StateData#state.jid)]), - From = StateData#state.jid, - Packet = {xmlelement, "presence", - [{"type", "unavailable"}], - [{xmlelement, "status", [], - [{xmlcdata, "Replaced by new connection"}]}]}, - ejabberd_sm:close_session_unset_presence( - StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - "Replaced by new connection"), - presence_broadcast( - StateData, From, StateData#state.pres_a, Packet), - presence_broadcast( - StateData, From, StateData#state.pres_i, Packet); - rebinded -> - ejabberd_sm:close_migrated_session( - StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource), - ok; - _ -> - ?INFO_MSG("(~w) Close session for ~s", - [StateData#state.socket, - jlib:jid_to_string(StateData#state.jid)]), - - EmptySet = ?SETS:new(), - case StateData of - #state{pres_last = undefined, - pres_a = EmptySet, - pres_i = EmptySet, - pres_invis = false} -> - ejabberd_sm:close_session(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource); - _ -> - From = StateData#state.jid, - Packet = {xmlelement, "presence", - [{"type", "unavailable"}], []}, - ejabberd_sm:close_session_unset_presence( - StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - ""), - presence_broadcast( - StateData, From, StateData#state.pres_a, Packet), - presence_broadcast( - StateData, From, StateData#state.pres_i, Packet) - end - end, - case StateData#state.authenticated of - rebinded -> - ok; - _ -> - if - not StateData#state.reception, not StateData#state.oor_offline -> - SFrom = jlib:jid_to_string(StateData#state.jid), - ejabberd_hooks:run( - p1_push_notification, - StateData#state.server, - [StateData#state.server, - StateData#state.jid, - StateData#state.oor_notification, - "Instant messaging session expired", - 0, - false, - StateData#state.oor_appid, - SFrom]); - true -> - ok - end, - lists:foreach( - fun({_Counter, From, To, FixedPacket}) -> - ejabberd_router:route(From, To, FixedPacket) - end, queue:to_list(StateData#state.ack_queue)), - lists:foreach( - fun({From, To, FixedPacket}) -> - ejabberd_router:route(From, To, FixedPacket) - end, queue:to_list(StateData#state.queue)) - end, - bounce_messages(); - _ -> - ok + session_established -> + case StateData#state.authenticated of + replaced -> + ?INFO_MSG("(~w) Replaced session for ~s", + [StateData#state.socket, + jlib:jid_to_string(StateData#state.jid)]), + From = StateData#state.jid, + Packet = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = + [#xmlel{name = <<"status">>, attrs = [], + children = + [{xmlcdata, + <<"Replaced by new connection">>}]}]}, + ejabberd_sm:close_session_unset_presence(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, + <<"Replaced by new connection">>), + presence_broadcast(StateData, From, + StateData#state.pres_a, Packet); + rebinded -> + ejabberd_sm:close_migrated_session(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource), + ok; + _ -> + ?INFO_MSG("(~w) Close session for ~s", + [StateData#state.socket, + jlib:jid_to_string(StateData#state.jid)]), + EmptySet = (?SETS):new(), + case StateData of + #state{pres_last = undefined, pres_a = EmptySet} -> + ejabberd_sm:close_session(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource); + _ -> + From = StateData#state.jid, + Packet = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = []}, + ejabberd_sm:close_session_unset_presence(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, + <<"">>), + presence_broadcast(StateData, From, + StateData#state.pres_a, Packet) + end + end, + case StateData#state.authenticated of + rebinded -> ok; + _ -> + if not StateData#state.reception, + not StateData#state.oor_offline -> + SFrom = jlib:jid_to_string(StateData#state.jid), + ejabberd_hooks:run(p1_push_notification, + StateData#state.server, + [StateData#state.server, + StateData#state.jid, + StateData#state.oor_notification, + <<"Instant messaging session expired">>, + 0, false, StateData#state.oor_appid, + SFrom]); + true -> ok + end, + lists:foreach(fun ({_Counter, From, To, FixedPacket}) -> + ejabberd_router:route(From, To, + FixedPacket) + end, + queue:to_list(StateData#state.ack_queue)), + lists:foreach(fun ({From, To, FixedPacket}) -> + ejabberd_router:route(From, To, + FixedPacket) + end, + queue:to_list(StateData#state.queue)) + end, + bounce_messages(); + _ -> ok end, (StateData#state.sockmod):close(StateData#state.socket), ok. @@ -1929,1059 +2003,854 @@ terminate(_Reason, StateName, StateData) -> change_shaper(StateData, JID) -> Shaper = acl:match_rule(StateData#state.server, StateData#state.shaper, JID), - (StateData#state.sockmod):change_shaper(StateData#state.socket, Shaper). + (StateData#state.sockmod):change_shaper(StateData#state.socket, + Shaper). + +-spec send_text(c2s_state(), binary()) -> any(). -send_text(StateData, Text) when StateData#state.xml_socket -> - ?DEBUG("Send Text on stream = ~p", [lists:flatten(Text)]), +send_text(StateData, Text) + when StateData#state.xml_socket -> + ?DEBUG("Send Text on stream = ~p", [Text]), (StateData#state.sockmod):send_xml(StateData#state.socket, {xmlstreamraw, Text}); send_text(StateData, Text) -> ?DEBUG("Send XML on stream = ~p", [Text]), - Text1 = - if ?FLASH_HACK and StateData#state.flash_connection -> - %% send a null byte after each stanza to Flash clients - [Text, 0]; - true -> - Text - end, - (StateData#state.sockmod):send(StateData#state.socket, Text1). - -send_element(StateData, El) when StateData#state.xml_socket -> + Text1 = if StateData#state.flash_hack and + StateData#state.flash_connection -> + <<Text/binary, 0>>; + true -> Text + end, + (StateData#state.sockmod):send(StateData#state.socket, + Text1). + +-spec send_element(c2s_state(), xmlel()) -> any(). + +send_element(StateData, El) + when StateData#state.xml_socket -> ejabberd_hooks:run(feature_inspect_packet, - StateData#state.server, - [StateData#state.jid, - StateData#state.server, - StateData#state.pres_last, El]), + StateData#state.server, + [StateData#state.jid, StateData#state.server, + StateData#state.pres_last, El]), (StateData#state.sockmod):send_xml(StateData#state.socket, {xmlstreamelement, El}); send_element(StateData, El) -> ejabberd_hooks:run(feature_inspect_packet, - StateData#state.server, - [StateData#state.jid, - StateData#state.server, - StateData#state.pres_last, El]), + StateData#state.server, + [StateData#state.jid, StateData#state.server, + StateData#state.pres_last, El]), send_text(StateData, xml:element_to_binary(El)). -send_header(StateData,Server, Version, Lang) - when StateData#state.flash_connection -> +send_header(StateData, Server, Version, Lang) + when StateData#state.flash_connection -> Header = io_lib:format(?FLASH_STREAM_HEADER, - [StateData#state.streamid, - Server, - Version, - Lang]), - send_text(StateData, Header); - + [StateData#state.streamid, Server, Version, Lang]), + send_text(StateData, iolist_to_binary(Header)); send_header(StateData, Server, Version, Lang) - when StateData#state.xml_socket -> - VersionAttr = - case Version of - "" -> []; - _ -> [{"version", Version}] - end, - LangAttr = - case Lang of - "" -> []; - _ -> [{"xml:lang", Lang}] - end, - Header = - {xmlstreamstart, - "stream:stream", - VersionAttr ++ - LangAttr ++ - [{"xmlns", "jabber:client"}, - {"xmlns:stream", "http://etherx.jabber.org/streams"}, - {"id", StateData#state.streamid}, - {"from", Server}]}, - (StateData#state.sockmod):send_xml( - StateData#state.socket, Header); + when StateData#state.xml_socket -> + VersionAttr = case Version of + <<"">> -> []; + _ -> [{<<"version">>, Version}] + end, + LangAttr = case Lang of + <<"">> -> []; + _ -> [{<<"xml:lang">>, Lang}] + end, + Header = {xmlstreamstart, <<"stream:stream">>, + VersionAttr ++ + LangAttr ++ + [{<<"xmlns">>, <<"jabber:client">>}, + {<<"xmlns:stream">>, + <<"http://etherx.jabber.org/streams">>}, + {<<"id">>, StateData#state.streamid}, + {<<"from">>, Server}]}, + (StateData#state.sockmod):send_xml(StateData#state.socket, + Header); send_header(StateData, Server, Version, Lang) -> - VersionStr = - case Version of - "" -> ""; - _ -> [" version='", Version, "'"] - end, - LangStr = - case Lang of - "" -> ""; - _ -> [" xml:lang='", Lang, "'"] - end, + VersionStr = case Version of + <<"">> -> <<"">>; + _ -> [<<" version='">>, Version, <<"'">>] + end, + LangStr = case Lang of + <<"">> -> <<"">>; + _ -> [<<" xml:lang='">>, Lang, <<"'">>] + end, Header = io_lib:format(?STREAM_HEADER, - [StateData#state.streamid, - Server, - VersionStr, + [StateData#state.streamid, Server, VersionStr, LangStr]), - send_text(StateData, Header). + send_text(StateData, iolist_to_binary(Header)). -send_trailer(StateData) when StateData#state.xml_socket -> - (StateData#state.sockmod):send_xml( - StateData#state.socket, - {xmlstreamend, "stream:stream"}); +send_trailer(StateData) + when StateData#state.xml_socket -> + (StateData#state.sockmod):send_xml(StateData#state.socket, + {xmlstreamend, <<"stream:stream">>}); send_trailer(StateData) -> send_text(StateData, ?STREAM_TRAILER). - -new_id() -> - randoms:get_string(). - +new_id() -> randoms:get_string(). is_auth_packet(El) -> case jlib:iq_query_info(El) of - #iq{id = ID, type = Type, xmlns = ?NS_AUTH, sub_el = SubEl} -> - {xmlelement, _, _, Els} = SubEl, - {auth, ID, Type, - get_auth_tags(Els, "", "", "", "")}; - _ -> - false + #iq{id = ID, type = Type, xmlns = ?NS_AUTH, + sub_el = SubEl} -> + #xmlel{children = Els} = SubEl, + {auth, ID, Type, + get_auth_tags(Els, <<"">>, <<"">>, <<"">>, <<"">>)}; + _ -> false end. - -get_auth_tags([{xmlelement, Name, _Attrs, Els}| L], U, P, D, R) -> +get_auth_tags([#xmlel{name = Name, children = Els} | L], + U, P, D, R) -> CData = xml:get_cdata(Els), case Name of - "username" -> - get_auth_tags(L, CData, P, D, R); - "password" -> - get_auth_tags(L, U, CData, D, R); - "digest" -> - get_auth_tags(L, U, P, CData, R); - "resource" -> - get_auth_tags(L, U, P, D, CData); - _ -> - get_auth_tags(L, U, P, D, R) + <<"username">> -> get_auth_tags(L, CData, P, D, R); + <<"password">> -> get_auth_tags(L, U, CData, D, R); + <<"digest">> -> get_auth_tags(L, U, P, CData, R); + <<"resource">> -> get_auth_tags(L, U, P, D, CData); + _ -> get_auth_tags(L, U, P, D, R) end; get_auth_tags([_ | L], U, P, D, R) -> get_auth_tags(L, U, P, D, R); -get_auth_tags([], U, P, D, R) -> - {U, P, D, R}. - -%% Copied from ejabberd_socket.erl --record(socket_state, {sockmod, socket, receiver}). - -get_conn_type(StateData) -> - case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of - gen_tcp -> c2s; - tls -> c2s_tls; - ejabberd_zlib -> - if is_pid(StateData#state.socket) -> - unknown; - true -> - case ejabberd_zlib:get_sockmod( - (StateData#state.socket)#socket_state.socket) of - gen_tcp -> c2s_compressed; - tls -> c2s_compressed_tls - end - end; - ejabberd_http_poll -> http_poll; - ejabberd_http_ws -> http_ws; - ejabberd_http_bind -> http_bind; - _ -> unknown - end. +get_auth_tags([], U, P, D, R) -> {U, P, D, R}. process_presence_probe(From, To, StateData) -> LFrom = jlib:jid_tolower(From), - LBFrom = setelement(3, LFrom, ""), + LBFrom = setelement(3, LFrom, <<"">>), case StateData#state.pres_last of - undefined -> - ok; - _ -> - Cond1 = (not StateData#state.pres_invis) - andalso (?SETS:is_element(LFrom, StateData#state.pres_f) - orelse - ((LFrom /= LBFrom) andalso - ?SETS:is_element(LBFrom, StateData#state.pres_f))) - andalso (not - (?SETS:is_element(LFrom, StateData#state.pres_i) - orelse - ((LFrom /= LBFrom) andalso - ?SETS:is_element(LBFrom, StateData#state.pres_i)))), - Cond2 = StateData#state.pres_invis - andalso ?SETS:is_element(LFrom, StateData#state.pres_f) - andalso ?SETS:is_element(LFrom, StateData#state.pres_a), - if - Cond1 -> - Packet = - case StateData#state.reception of - true -> - StateData#state.pres_last; + undefined -> ok; + _ -> + Cond1 = + ((?SETS):is_element(LFrom, StateData#state.pres_f) + orelse + LFrom /= LBFrom andalso + (?SETS):is_element(LBFrom, StateData#state.pres_f)), + if Cond1 -> + Packet = case StateData#state.reception of + true -> StateData#state.pres_last; false -> case StateData#state.oor_show of - "" -> - StateData#state.pres_last; - _ -> - {xmlelement, _, PresAttrs, PresEls} = - StateData#state.pres_last, - PresEls1 = - lists:flatmap( - fun({xmlelement, Name, _, _}) - when Name == "show"; - Name == "status" -> - []; - (E) -> - [E] - end, PresEls), - make_oor_presence( - StateData, PresAttrs, PresEls1) + <<"">> -> StateData#state.pres_last; + _ -> + #xmlel{attrs = PresAttrs, + children = PresEls} = + StateData#state.pres_last, + PresEls1 = lists:flatmap(fun (#xmlel{name + = + Name}) + when Name + == + <<"show">>; + Name + == + <<"status">> -> + []; + (E) -> [E] + end, + PresEls), + make_oor_presence(StateData, PresAttrs, + PresEls1) end - end, - Timestamp = StateData#state.pres_timestamp, - Packet1 = maybe_add_delay(Packet, utc, To, "", Timestamp), - case ejabberd_hooks:run_fold( - privacy_check_packet, StateData#state.server, - allow, - [StateData#state.user, - StateData#state.server, - StateData#state.privacy_list, - {To, From, Packet1}, - out]) of - deny -> - ok; - allow -> - Pid=element(2, StateData#state.sid), - ejabberd_hooks:run(presence_probe_hook, StateData#state.server, [From, To, Pid]), - %% Don't route a presence probe to oneself - case From == To of - false -> - ejabberd_router:route(To, From, Packet1); - true -> - ok - end - end; - Cond2 -> - ejabberd_router:route(To, From, - {xmlelement, "presence", - [], - []}); - true -> - ok - end + end, + Timestamp = StateData#state.pres_timestamp, + Packet1 = maybe_add_delay(Packet, utc, To, <<"">>, + Timestamp), + case ejabberd_hooks:run_fold(privacy_check_packet, + StateData#state.server, allow, + [StateData#state.user, + StateData#state.server, + StateData#state.privacy_list, + {To, From, Packet1}, out]) + of + deny -> ok; + allow -> + Pid = element(2, StateData#state.sid), + ejabberd_hooks:run(presence_probe_hook, + StateData#state.server, + [From, To, Pid]), + case From == To of + false -> ejabberd_router:route(To, From, Packet1); + true -> ok + end + end; + true -> ok + end end. -%% User updates his presence (non-directed presence packet) presence_update(From, Packet, StateData) -> - {xmlelement, _Name, Attrs, _Els} = Packet, - case xml:get_attr_s("type", Attrs) of - "unavailable" -> - Status = case xml:get_subtag(Packet, "status") of - false -> - ""; - StatusTag -> - xml:get_tag_cdata(StatusTag) - end, - Info = [{ip, StateData#state.ip}, {conn, StateData#state.conn}, - {auth_module, StateData#state.auth_module}], - ejabberd_sm:unset_presence(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - Status, - Info), - presence_broadcast(StateData, From, StateData#state.pres_a, Packet), - presence_broadcast(StateData, From, StateData#state.pres_i, Packet), - StateData#state{pres_last = undefined, - pres_timestamp = undefined, - pres_a = ?SETS:new(), - pres_i = ?SETS:new(), - pres_invis = false}; - "invisible" -> - NewPriority = get_priority_from_presence(Packet), - update_priority(NewPriority, Packet, StateData), - NewState = - if - not StateData#state.pres_invis -> - presence_broadcast(StateData, From, - StateData#state.pres_a, - Packet), - presence_broadcast(StateData, From, - StateData#state.pres_i, - Packet), - S1 = StateData#state{pres_last = undefined, - pres_timestamp = undefined, - pres_a = ?SETS:new(), - pres_i = ?SETS:new(), - pres_invis = true}, - presence_broadcast_first(From, S1, Packet); - true -> - StateData - end, - NewState; - "error" -> - StateData; - "probe" -> - StateData; - "subscribe" -> - StateData; - "subscribed" -> - StateData; - "unsubscribe" -> - StateData; - "unsubscribed" -> - StateData; - _ -> - OldPriority = case StateData#state.pres_last of - undefined -> - 0; - OldPresence -> - get_priority_from_presence(OldPresence) - end, - NewPriority = get_priority_from_presence(Packet), - Timestamp = calendar:now_to_universal_time(now()), - update_priority(NewPriority, Packet, StateData), - FromUnavail = (StateData#state.pres_last == undefined) or - StateData#state.pres_invis, - ?DEBUG("from unavail = ~p~n", [FromUnavail]), - NewStateData = StateData#state{pres_last = Packet, - pres_invis = false, - pres_timestamp = Timestamp}, - NewState = - if - FromUnavail -> - ejabberd_hooks:run(user_available_hook, - NewStateData#state.server, - [NewStateData#state.jid]), - if NewPriority >= 0 -> - resend_offline_messages(NewStateData), - resend_subscription_requests(NewStateData); - true -> - ok + #xmlel{attrs = Attrs} = Packet, + case xml:get_attr_s(<<"type">>, Attrs) of + <<"unavailable">> -> + Status = case xml:get_subtag(Packet, <<"status">>) of + false -> <<"">>; + StatusTag -> xml:get_tag_cdata(StatusTag) + end, + Info = [{ip, StateData#state.ip}, + {conn, StateData#state.conn}, + {auth_module, StateData#state.auth_module}], + ejabberd_sm:unset_presence(StateData#state.sid, + StateData#state.user, + StateData#state.server, + StateData#state.resource, Status, Info), + presence_broadcast(StateData, From, + StateData#state.pres_a, Packet), + StateData#state{pres_last = undefined, + pres_timestamp = undefined, pres_a = (?SETS):new()}; + <<"error">> -> StateData; + <<"probe">> -> StateData; + <<"subscribe">> -> StateData; + <<"subscribed">> -> StateData; + <<"unsubscribe">> -> StateData; + <<"unsubscribed">> -> StateData; + _ -> + OldPriority = case StateData#state.pres_last of + undefined -> 0; + OldPresence -> get_priority_from_presence(OldPresence) end, - presence_broadcast_first(From, NewStateData, Packet); - true -> - presence_broadcast_to_trusted(NewStateData, - From, - NewStateData#state.pres_f, - NewStateData#state.pres_a, - Packet), - if OldPriority < 0, NewPriority >= 0 -> - resend_offline_messages(NewStateData); - true -> - ok - end, - NewStateData - end, - NewState + NewPriority = get_priority_from_presence(Packet), + Timestamp = calendar:now_to_universal_time(now()), + update_priority(NewPriority, Packet, StateData), + FromUnavail = (StateData#state.pres_last == undefined), + ?DEBUG("from unavail = ~p~n", [FromUnavail]), + NewStateData = StateData#state{pres_last = Packet, + pres_timestamp = Timestamp}, + NewState = if FromUnavail -> + ejabberd_hooks:run(user_available_hook, + NewStateData#state.server, + [NewStateData#state.jid]), + if NewPriority >= 0 -> + resend_offline_messages(NewStateData), + resend_subscription_requests(NewStateData); + true -> ok + end, + presence_broadcast_first(From, NewStateData, + Packet); + true -> + presence_broadcast_to_trusted(NewStateData, From, + NewStateData#state.pres_f, + NewStateData#state.pres_a, + Packet), + if OldPriority < 0, NewPriority >= 0 -> + resend_offline_messages(NewStateData); + true -> ok + end, + NewStateData + end, + NewState end. -%% User sends a directed presence packet presence_track(From, To, Packet, StateData) -> - {xmlelement, _Name, Attrs, _Els} = Packet, + #xmlel{attrs = Attrs} = Packet, LTo = jlib:jid_tolower(To), User = StateData#state.user, Server = StateData#state.server, - case xml:get_attr_s("type", Attrs) of - "unavailable" -> - check_privacy_route(From, StateData, From, To, Packet), - I = remove_element(LTo, StateData#state.pres_i), - A = remove_element(LTo, StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A}; - "invisible" -> - check_privacy_route(From, StateData, From, To, Packet), - I = ?SETS:add_element(LTo, StateData#state.pres_i), - A = remove_element(LTo, StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A}; - "subscribe" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, subscribe]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "subscribed" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, subscribed]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "unsubscribe" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, unsubscribe]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "unsubscribed" -> - ejabberd_hooks:run(roster_out_subscription, - Server, - [User, Server, To, unsubscribed]), - check_privacy_route(From, StateData, jlib:jid_remove_resource(From), - To, Packet), - StateData; - "error" -> - check_privacy_route(From, StateData, From, To, Packet), - StateData; - "probe" -> - check_privacy_route(From, StateData, From, To, Packet), - StateData; - _ -> - check_privacy_route(From, StateData, From, To, Packet), - I = remove_element(LTo, StateData#state.pres_i), - A = ?SETS:add_element(LTo, StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A} + case xml:get_attr_s(<<"type">>, Attrs) of + <<"unavailable">> -> + check_privacy_route(From, StateData, From, To, Packet), + A = remove_element(LTo, StateData#state.pres_a), + StateData#state{pres_a = A}; + <<"subscribe">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, subscribe]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"subscribed">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, subscribed]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"unsubscribe">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, unsubscribe]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"unsubscribed">> -> + ejabberd_hooks:run(roster_out_subscription, Server, + [User, Server, To, unsubscribed]), + check_privacy_route(From, StateData, + jlib:jid_remove_resource(From), To, Packet), + StateData; + <<"error">> -> + check_privacy_route(From, StateData, From, To, Packet), + StateData; + <<"probe">> -> + check_privacy_route(From, StateData, From, To, Packet), + StateData; + _ -> + check_privacy_route(From, StateData, From, To, Packet), + A = (?SETS):add_element(LTo, StateData#state.pres_a), + StateData#state{pres_a = A} end. -check_privacy_route(From, StateData, FromRoute, To, Packet) -> - case privacy_check_packet(StateData, From, To, Packet, out) of - deny -> - Lang = StateData#state.lang, - ErrText = "Your active privacy list has denied the routing of this stanza.", - Err = jlib:make_error_reply(Packet, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), - ejabberd_router:route(To, From, Err), - ok; - allow -> - ejabberd_router:route(FromRoute, To, Packet) +check_privacy_route(From, StateData, FromRoute, To, + Packet) -> + case privacy_check_packet(StateData, From, To, Packet, + out) + of + deny -> + Lang = StateData#state.lang, + ErrText = <<"Your active privacy list has denied " + "the routing of this stanza.">>, + Err = jlib:make_error_reply(Packet, + ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)), + ejabberd_router:route(To, From, Err), + ok; + allow -> ejabberd_router:route(FromRoute, To, Packet) end. -privacy_check_packet(StateData, From, To, Packet, Dir) -> - ejabberd_hooks:run_fold( - privacy_check_packet, StateData#state.server, - allow, - [StateData#state.user, - StateData#state.server, - StateData#state.privacy_list, - {From, To, Packet}, - Dir]). - -%% Check if privacy rules allow this delivery +privacy_check_packet(StateData, From, To, Packet, + Dir) -> + ejabberd_hooks:run_fold(privacy_check_packet, + StateData#state.server, allow, + [StateData#state.user, StateData#state.server, + StateData#state.privacy_list, {From, To, Packet}, + Dir]). + is_privacy_allow(StateData, From, To, Packet, Dir) -> - allow == privacy_check_packet(StateData, From, To, Packet, Dir). + allow == + privacy_check_packet(StateData, From, To, Packet, Dir). +%% Send presence when disconnecting presence_broadcast(StateData, From, JIDSet, Packet) -> - lists:foreach(fun(JID) -> - FJID = jlib:make_jid(JID), - case privacy_check_packet(StateData, From, FJID, Packet, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, FJID, Packet) - end - end, ?SETS:to_list(JIDSet)). - -presence_broadcast_to_trusted(StateData, From, T, A, Packet) -> - lists:foreach( - fun(JID) -> - case ?SETS:is_element(JID, T) of - true -> - FJID = jlib:make_jid(JID), - case privacy_check_packet(StateData, From, FJID, Packet, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, FJID, Packet) - end; - _ -> - ok - end - end, ?SETS:to_list(A)). + JIDs = ?SETS:to_list(JIDSet), + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2, Packet). +%% Send presence when updating presence +presence_broadcast_to_trusted(StateData, From, Trusted, JIDSet, Packet) -> + JIDs = ?SETS:to_list(JIDSet), + JIDs_trusted = [JID || JID <- JIDs, ?SETS:is_element(JID, Trusted)], + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs_trusted, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2, Packet). +%% Send presence when connecting presence_broadcast_first(From, StateData, Packet) -> - ?SETS:fold(fun(JID, X) -> - ejabberd_router:route( - From, - jlib:make_jid(JID), - {xmlelement, "presence", - [{"type", "probe"}], - []}), - X - end, - [], - StateData#state.pres_t), - if - StateData#state.pres_invis -> - StateData; - true -> - As = ?SETS:fold( - fun(JID, A) -> - FJID = jlib:make_jid(JID), - case privacy_check_packet(StateData, From, FJID, Packet, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, FJID, Packet) - end, - ?SETS:add_element(JID, A) - end, - StateData#state.pres_a, - StateData#state.pres_f), - StateData#state{pres_a = As} - end. + JIDsProbe = + ?SETS:fold( + fun(JID, L) -> [JID | L] end, + [], + StateData#state.pres_t), + PacketProbe = #xmlel{name = <<"presence">>, attrs = [{<<"type">>,<<"probe">>}], children = []}, + JIDs2Probe = format_and_check_privacy(From, StateData, Packet, JIDsProbe, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2Probe, PacketProbe), + {As, JIDs} = + ?SETS:fold( + fun(JID, {A, JID_list}) -> + {?SETS:add_element(JID, A), JID_list++[JID]} + end, + {StateData#state.pres_a, []}, + StateData#state.pres_f), + JIDs2 = format_and_check_privacy(From, StateData, Packet, JIDs, out), + Server = StateData#state.server, + send_multiple(From, Server, JIDs2, Packet), + StateData#state{pres_a = As}. + +format_and_check_privacy(From, StateData, Packet, JIDs, Dir) -> + FJIDs = [jlib:make_jid(JID) || JID <- JIDs], + lists:filter( + fun(FJID) -> + case ejabberd_hooks:run_fold( + privacy_check_packet, StateData#state.server, + allow, + [StateData#state.user, + StateData#state.server, + StateData#state.privacy_list, + {From, FJID, Packet}, + Dir]) of + deny -> false; + allow -> true + end + end, + FJIDs). + +send_multiple(From, Server, JIDs, Packet) -> + ejabberd_router_multicast:route_multicast(From, Server, JIDs, Packet). remove_element(E, Set) -> - case ?SETS:is_element(E, Set) of - true -> - ?SETS:del_element(E, Set); - _ -> - Set + case (?SETS):is_element(E, Set) of + true -> (?SETS):del_element(E, Set); + _ -> Set end. - roster_change(IJID, ISubscription, StateData) -> LIJID = jlib:jid_tolower(IJID), - IsFrom = (ISubscription == both) or (ISubscription == from), - IsTo = (ISubscription == both) or (ISubscription == to), - OldIsFrom = ?SETS:is_element(LIJID, StateData#state.pres_f), - FSet = if - IsFrom -> - ?SETS:add_element(LIJID, StateData#state.pres_f); - true -> - remove_element(LIJID, StateData#state.pres_f) + IsFrom = (ISubscription == both) or + (ISubscription == from), + IsTo = (ISubscription == both) or (ISubscription == to), + OldIsFrom = (?SETS):is_element(LIJID, + StateData#state.pres_f), + FSet = if IsFrom -> + (?SETS):add_element(LIJID, StateData#state.pres_f); + true -> remove_element(LIJID, StateData#state.pres_f) end, - TSet = if - IsTo -> - ?SETS:add_element(LIJID, StateData#state.pres_t); - true -> - remove_element(LIJID, StateData#state.pres_t) + TSet = if IsTo -> + (?SETS):add_element(LIJID, StateData#state.pres_t); + true -> remove_element(LIJID, StateData#state.pres_t) end, case StateData#state.pres_last of - undefined -> - StateData#state{pres_f = FSet, pres_t = TSet}; - P -> - ?DEBUG("roster changed for ~p~n", [StateData#state.user]), - From = StateData#state.jid, - To = jlib:make_jid(IJID), -% To = IJID, - Cond1 = (not StateData#state.pres_invis) and IsFrom - and (not OldIsFrom), - Cond2 = (not IsFrom) and OldIsFrom - and (?SETS:is_element(LIJID, StateData#state.pres_a) or - ?SETS:is_element(LIJID, StateData#state.pres_i)), - if - Cond1 -> - ?DEBUG("C1: ~p~n", [LIJID]), - case privacy_check_packet(StateData, From, To, P, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, To, P) - end, - A = ?SETS:add_element(LIJID, - StateData#state.pres_a), - StateData#state{pres_a = A, - pres_f = FSet, - pres_t = TSet}; - Cond2 -> - ?DEBUG("C2: ~p~n", [LIJID]), - PU = {xmlelement, "presence", - [{"type", "unavailable"}], []}, - case privacy_check_packet(StateData, From, To, PU, out) of - deny -> - ok; - allow -> - ejabberd_router:route(From, To, PU) - end, - I = remove_element(LIJID, - StateData#state.pres_i), - A = remove_element(LIJID, - StateData#state.pres_a), - StateData#state{pres_i = I, - pres_a = A, - pres_f = FSet, - pres_t = TSet}; - true -> - StateData#state{pres_f = FSet, pres_t = TSet} - end + undefined -> + StateData#state{pres_f = FSet, pres_t = TSet}; + P -> + ?DEBUG("roster changed for ~p~n", + [StateData#state.user]), + From = StateData#state.jid, + To = jlib:make_jid(IJID), + Cond1 = IsFrom andalso not OldIsFrom, + Cond2 = not IsFrom andalso OldIsFrom andalso + ((?SETS):is_element(LIJID, StateData#state.pres_a)), + if Cond1 -> + ?DEBUG("C1: ~p~n", [LIJID]), + case privacy_check_packet(StateData, From, To, P, out) + of + deny -> ok; + allow -> ejabberd_router:route(From, To, P) + end, + A = (?SETS):add_element(LIJID, StateData#state.pres_a), + StateData#state{pres_a = A, pres_f = FSet, + pres_t = TSet}; + Cond2 -> + ?DEBUG("C2: ~p~n", [LIJID]), + PU = #xmlel{name = <<"presence">>, + attrs = [{<<"type">>, <<"unavailable">>}], + children = []}, + case privacy_check_packet(StateData, From, To, PU, out) + of + deny -> ok; + allow -> ejabberd_router:route(From, To, PU) + end, + A = remove_element(LIJID, StateData#state.pres_a), + StateData#state{pres_a = A, pres_f = FSet, + pres_t = TSet}; + true -> StateData#state{pres_f = FSet, pres_t = TSet} + end end. - update_priority(Priority, Packet, StateData) -> - Info1 = [{ip, StateData#state.ip}, {conn, StateData#state.conn}, + Info1 = [{ip, StateData#state.ip}, + {conn, StateData#state.conn}, {auth_module, StateData#state.auth_module}], - Info = - case StateData#state.reception of - false -> - [{oor, true} | Info1]; - _ -> - Info1 - end, + Info = case StateData#state.reception of + false -> [{oor, true} | Info1]; + _ -> Info1 + end, ejabberd_sm:set_presence(StateData#state.sid, - StateData#state.user, - StateData#state.server, - StateData#state.resource, - Priority, - Packet, - Info). + StateData#state.user, StateData#state.server, + StateData#state.resource, Priority, Packet, Info). get_priority_from_presence(PresencePacket) -> - case xml:get_subtag(PresencePacket, "priority") of - false -> - 0; - SubEl -> - case catch list_to_integer(xml:get_tag_cdata(SubEl)) of - P when is_integer(P) -> - P; - _ -> - 0 - end + case xml:get_subtag(PresencePacket, <<"priority">>) of + false -> 0; + SubEl -> + case catch + jlib:binary_to_integer(xml:get_tag_cdata(SubEl)) + of + P when is_integer(P) -> P; + _ -> 0 + end end. process_privacy_iq(From, To, - #iq{type = Type, sub_el = SubEl} = IQ, - StateData) -> - {Res, NewStateData} = - case Type of - get -> - R = ejabberd_hooks:run_fold( - privacy_iq_get, StateData#state.server, - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}, - [From, To, IQ, StateData#state.privacy_list]), - {R, StateData}; - set -> - case ejabberd_hooks:run_fold( - privacy_iq_set, StateData#state.server, - {error, ?ERR_FEATURE_NOT_IMPLEMENTED}, - [From, To, IQ]) of - {result, R, NewPrivList} -> - {{result, R}, - StateData#state{privacy_list = NewPrivList}}; - R -> {R, StateData} - end - end, - IQRes = - case Res of - {result, Result} -> - IQ#iq{type = result, sub_el = Result}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end, - ejabberd_router:route( - To, From, jlib:iq_to_xml(IQRes)), + #iq{type = Type, sub_el = SubEl} = IQ, StateData) -> + {Res, NewStateData} = case Type of + get -> + R = ejabberd_hooks:run_fold(privacy_iq_get, + StateData#state.server, + {error, + ?ERR_FEATURE_NOT_IMPLEMENTED}, + [From, To, IQ, + StateData#state.privacy_list]), + {R, StateData}; + set -> + case ejabberd_hooks:run_fold(privacy_iq_set, + StateData#state.server, + {error, + ?ERR_FEATURE_NOT_IMPLEMENTED}, + [From, To, IQ]) + of + {result, R, NewPrivList} -> + {{result, R}, + StateData#state{privacy_list = + NewPrivList}}; + R -> {R, StateData} + end + end, + IQRes = case Res of + {result, Result} -> + IQ#iq{type = result, sub_el = Result}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end, + ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)), NewStateData. - resend_offline_messages(StateData) -> - case ejabberd_hooks:run_fold( - resend_offline_messages_hook, StateData#state.server, - [], - [StateData#state.user, StateData#state.server]) of - Rs when is_list(Rs) -> - lists:foreach( - fun({route, - From, To, {xmlelement, _Name, _Attrs, _Els} = Packet}) -> - Pass = case privacy_check_packet(StateData, From, To, Packet, in) of - allow -> - true; - deny -> - false - end, - if - Pass -> - %% Attrs2 = jlib:replace_from_to_attrs( - %% jlib:jid_to_string(From), - %% jlib:jid_to_string(To), - %% Attrs), - %% FixedPacket = {xmlelement, Name, Attrs2, Els}, - %% Use route instead of send_element to go through standard workflow - ejabberd_router:route(From, To, Packet); - %% send_element(StateData, FixedPacket), - %% ejabberd_hooks:run(user_receive_packet, - %% StateData#state.server, - %% [StateData#state.jid, - %% From, To, FixedPacket]); - true -> - ok - end - end, Rs) + case + ejabberd_hooks:run_fold(resend_offline_messages_hook, + StateData#state.server, [], + [StateData#state.user, StateData#state.server]) + of + Rs -> %%when is_list(Rs) -> + lists:foreach(fun ({route, From, To, + #xmlel{} = Packet}) -> + Pass = case privacy_check_packet(StateData, + From, To, + Packet, in) + of + allow -> true; + deny -> false + end, + if Pass -> + ejabberd_router:route(From, To, Packet); + %% send_element(StateData, FixedPacket), + %% ejabberd_hooks:run(user_receive_packet, + %% StateData#state.server, + %% [StateData#state.jid, + %% From, To, FixedPacket]); + true -> ok + end + end, + Rs) end. resend_subscription_requests(#state{user = User, - server = Server} = StateData) -> - PendingSubscriptions = ejabberd_hooks:run_fold( - resend_subscription_requests_hook, - Server, - [], - [User, Server]), - lists:foreach(fun(XMLPacket) -> - send_element(StateData, - XMLPacket) + server = Server} = + StateData) -> + PendingSubscriptions = + ejabberd_hooks:run_fold(resend_subscription_requests_hook, + Server, [], [User, Server]), + lists:foreach(fun (XMLPacket) -> + send_element(StateData, XMLPacket) end, PendingSubscriptions). -get_showtag(undefined) -> - "unavailable"; +get_showtag(undefined) -> <<"unavailable">>; get_showtag(Presence) -> - case xml:get_path_s(Presence, [{elem, "show"}, cdata]) of - "" -> "available"; - ShowTag -> ShowTag + case xml:get_path_s(Presence, + [{elem, <<"show">>}, cdata]) + of + <<"">> -> <<"available">>; + ShowTag -> ShowTag end. -get_statustag(undefined) -> - ""; +get_statustag(undefined) -> <<"">>; get_statustag(Presence) -> - case xml:get_path_s(Presence, [{elem, "status"}, cdata]) of - ShowTag -> ShowTag + case xml:get_path_s(Presence, + [{elem, <<"status">>}, cdata]) + of + ShowTag -> ShowTag end. process_unauthenticated_stanza(StateData, El) -> - NewEl = case xml:get_tag_attr_s("xml:lang", El) of - "" -> - case StateData#state.lang of - "" -> El; - Lang -> - xml:replace_tag_attr("xml:lang", Lang, El) - end; - _ -> - El + NewEl = case xml:get_tag_attr_s(<<"xml:lang">>, El) of + <<"">> -> + case StateData#state.lang of + <<"">> -> El; + Lang -> xml:replace_tag_attr(<<"xml:lang">>, Lang, El) + end; + _ -> El end, case jlib:iq_query_info(NewEl) of - #iq{} = IQ -> - Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, - StateData#state.server, - empty, - [StateData#state.server, IQ, - StateData#state.ip]), - case Res of - empty -> - % The only reasonable IQ's here are auth and register IQ's - % They contain secrets, so don't include subelements to response - ResIQ = IQ#iq{type = error, - sub_el = [?ERR_SERVICE_UNAVAILABLE]}, - Res1 = jlib:replace_from_to( - jlib:make_jid("", StateData#state.server, ""), - jlib:make_jid("", "", ""), - jlib:iq_to_xml(ResIQ)), - send_element(StateData, jlib:remove_attr("to", Res1)); - _ -> - send_element(StateData, Res) - end; - _ -> - % Drop any stanza, which isn't IQ stanza - ok + #iq{} = IQ -> + Res = ejabberd_hooks:run_fold(c2s_unauthenticated_iq, + StateData#state.server, empty, + [StateData#state.server, IQ, + StateData#state.ip]), + case Res of + empty -> + ResIQ = IQ#iq{type = error, + sub_el = [?ERR_SERVICE_UNAVAILABLE]}, + Res1 = jlib:replace_from_to(jlib:make_jid(<<"">>, + StateData#state.server, + <<"">>), + jlib:make_jid(<<"">>, <<"">>, + <<"">>), + jlib:iq_to_xml(ResIQ)), + send_element(StateData, + jlib:remove_attr(<<"to">>, Res1)); + _ -> send_element(StateData, Res) + end; + _ -> + % Drop any stanza, which isn't IQ stanza + ok end. peerip(SockMod, Socket) -> IP = case SockMod of - gen_tcp -> inet:peername(Socket); - _ -> SockMod:peername(Socket) + gen_tcp -> inet:peername(Socket); + _ -> SockMod:peername(Socket) end, case IP of - {ok, IPOK} -> IPOK; - _ -> undefined + {ok, IPOK} -> IPOK; + _ -> undefined end. maybe_migrate(StateName, StateData) -> PackedStateData = pack(StateData), - #state{user = U, server = S, resource = R, sid = SID} = StateData, - case ejabberd_cluster:get_node({jlib:nodeprep(U), jlib:nameprep(S)}) of - Node when Node == node() -> - Conn = get_conn_type(StateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, - {auth_module, StateData#state.auth_module}], - Presence = StateData#state.pres_last, - Priority = - case Presence of - undefined -> - undefined; - _ -> - get_priority_from_presence(Presence) - end, - ejabberd_sm:open_session(SID, U, S, R, Priority, Info), - StateData2 = change_reception(PackedStateData, true), - StateData3 = start_keepalive_timer(StateData2), - erlang:garbage_collect(), - fsm_next_state(StateName, StateData3); - Node -> - fsm_migrate(StateName, PackedStateData, Node, 0) + #state{user = U, server = S, resource = R, sid = SID} = + StateData, + case ejabberd_cluster:get_node({jlib:nodeprep(U), + jlib:nameprep(S)}) + of + Node when Node == node() -> + Conn = ejabberd_socket:get_conn_type(StateData#state.socket), + Info = [{ip, StateData#state.ip}, {conn, Conn}, + {auth_module, StateData#state.auth_module}], + Presence = StateData#state.pres_last, + Priority = case Presence of + undefined -> undefined; + _ -> get_priority_from_presence(Presence) + end, + ejabberd_sm:open_session(SID, U, S, R, Priority, Info), + StateData2 = change_reception(PackedStateData, true), + StateData3 = start_keepalive_timer(StateData2), + erlang:garbage_collect(), + fsm_next_state(StateName, StateData3); + Node -> fsm_migrate(StateName, PackedStateData, Node, 0) end. -%% fsm_next_state: Generate the next_state FSM tuple with different -%% timeout, depending on the future state fsm_next_state(session_established, StateData) -> - {next_state, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; + {next_state, session_established, StateData, + ?C2S_HIBERNATE_TIMEOUT}; fsm_next_state(StateName, StateData) -> {next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}. fsm_migrate(StateName, StateData, Node, Timeout) -> {migrate, StateData, - {Node, ?MODULE, start, [StateName, StateData]}, Timeout}. + {Node, ?MODULE, start, [StateName, StateData]}, + Timeout}. -%% fsm_reply: Generate the reply FSM tuple with different timeout, -%% depending on the future state fsm_reply(Reply, session_established, StateData) -> - {reply, Reply, session_established, StateData, ?C2S_HIBERNATE_TIMEOUT}; + {reply, Reply, session_established, StateData, + ?C2S_HIBERNATE_TIMEOUT}; fsm_reply(Reply, StateName, StateData) -> {reply, Reply, StateName, StateData, ?C2S_OPEN_TIMEOUT}. -%% Used by c2s blacklist plugins -is_ip_blacklisted(undefined) -> - false; -is_ip_blacklisted({IP,_Port}) -> +is_ip_blacklisted(undefined) -> false; +is_ip_blacklisted({IP, _Port}) -> ejabberd_hooks:run_fold(check_bl_c2s, false, [IP]). -%% Check from attributes -%% returns invalid-from|NewElement check_from(El, FromJID) -> - case xml:get_tag_attr("from", El) of - false -> - El; - {value, SJID} -> - JID = jlib:string_to_jid(SJID), - case JID of - error -> - 'invalid-from'; - #jid{} -> - if - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == FromJID#jid.lresource) -> - El; - (JID#jid.luser == FromJID#jid.luser) and - (JID#jid.lserver == FromJID#jid.lserver) and - (JID#jid.lresource == "") -> - El; - true -> - 'invalid-from' - end - end + case xml:get_tag_attr(<<"from">>, El) of + false -> El; + {value, SJID} -> + JID = jlib:string_to_jid(SJID), + case JID of + error -> 'invalid-from'; + #jid{} -> + if (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) + and (JID#jid.lresource == FromJID#jid.lresource) -> + El; + (JID#jid.luser == FromJID#jid.luser) and + (JID#jid.lserver == FromJID#jid.lserver) + and (JID#jid.lresource == <<"">>) -> + El; + true -> 'invalid-from' + end + end end. start_keepalive_timer(StateData) -> - if - is_reference(StateData#state.keepalive_timer) -> - cancel_timer(StateData#state.keepalive_timer); - true -> - ok + if is_reference(StateData#state.keepalive_timer) -> + cancel_timer(StateData#state.keepalive_timer); + true -> ok end, - Timeout = - if - StateData#state.reception -> StateData#state.keepalive_timeout; - true -> StateData#state.oor_timeout - end, - Timer = - if - is_integer(Timeout) -> - erlang:start_timer(Timeout * 1000, self(), []); - true -> - undefined - end, + Timeout = if StateData#state.reception -> + StateData#state.keepalive_timeout; + true -> StateData#state.oor_timeout + end, + Timer = if is_integer(Timeout) -> + erlang:start_timer(Timeout * 1000, self(), []); + true -> undefined + end, StateData#state{keepalive_timer = Timer}. -change_reception(#state{reception = Reception} = StateData, Reception) -> +change_reception(#state{reception = Reception} = + StateData, + Reception) -> StateData; -change_reception(#state{reception = true} = StateData, false) -> +change_reception(#state{reception = true} = StateData, + false) -> ?DEBUG("reception -> false", []), case StateData#state.oor_show of - "" -> - ok; - _ -> - Packet = make_oor_presence(StateData), - update_priority(0, Packet, StateData#state{reception = false}), - presence_broadcast_to_trusted( - StateData, - StateData#state.jid, - StateData#state.pres_f, - StateData#state.pres_a, - Packet) + <<"">> -> ok; + _ -> + Packet = make_oor_presence(StateData), + update_priority(0, Packet, + StateData#state{reception = false}), + presence_broadcast_to_trusted(StateData, + StateData#state.jid, + StateData#state.pres_f, + StateData#state.pres_a, Packet) end, StateData#state{reception = false}; -change_reception(#state{reception = false, standby = true} = StateData, true) -> +change_reception(#state{reception = false, + standby = true} = + StateData, + true) -> ?DEBUG("reception -> standby", []), - NewQueue = - lists:foldl( - fun({_From, _To, {xmlelement, "message", _, _} = FixedPacket}, Q) -> - send_element(StateData, FixedPacket), - Q; - (Item, Q) -> - queue:in(Item, Q) - end, queue:new(), queue:to_list(StateData#state.queue)), + NewQueue = lists:foldl(fun ({_From, _To, + #xmlel{name = <<"message">>} = FixedPacket}, + Q) -> + send_element(StateData, FixedPacket), Q; + (Item, Q) -> queue:in(Item, Q) + end, + queue:new(), queue:to_list(StateData#state.queue)), StateData#state{queue = NewQueue, - queue_len = queue:len(NewQueue), - reception = true, - oor_unread = 0, - oor_unread_users = ?SETS:new()}; -change_reception(#state{reception = false} = StateData, true) -> + queue_len = queue:len(NewQueue), reception = true, + oor_unread = 0, oor_unread_users = (?SETS):new()}; +change_reception(#state{reception = false} = StateData, + true) -> ?DEBUG("reception -> true", []), case StateData#state.oor_show of - "" -> - ok; - _ -> - Packet = StateData#state.pres_last, - NewPriority = get_priority_from_presence(Packet), - update_priority(NewPriority, Packet, - StateData#state{reception = true}), - presence_broadcast_to_trusted( - StateData, - StateData#state.jid, - StateData#state.pres_f, - StateData#state.pres_a, - Packet) + <<"">> -> ok; + _ -> + Packet = StateData#state.pres_last, + NewPriority = get_priority_from_presence(Packet), + update_priority(NewPriority, Packet, + StateData#state{reception = true}), + presence_broadcast_to_trusted(StateData, + StateData#state.jid, + StateData#state.pres_f, + StateData#state.pres_a, Packet) end, - lists:foreach( - fun({_From, _To, FixedPacket}) -> - send_element(StateData, FixedPacket) - end, queue:to_list(StateData#state.queue)), - lists:foreach( - fun(FixedPacket) -> - send_element(StateData, FixedPacket) - end, gb_trees:values(StateData#state.pres_queue)), - StateData#state{queue = queue:new(), - queue_len = 0, - pres_queue = gb_trees:empty(), - reception = true, - oor_unread = 0, - oor_unread_users = ?SETS:new()}. - -change_standby(#state{standby = StandBy} = StateData, StandBy) -> + lists:foreach(fun ({_From, _To, FixedPacket}) -> + send_element(StateData, FixedPacket) + end, + queue:to_list(StateData#state.queue)), + lists:foreach(fun (FixedPacket) -> + send_element(StateData, FixedPacket) + end, + gb_trees:values(StateData#state.pres_queue)), + StateData#state{queue = queue:new(), queue_len = 0, + pres_queue = gb_trees:empty(), reception = true, + oor_unread = 0, oor_unread_users = (?SETS):new()}. + +change_standby(#state{standby = StandBy} = StateData, + StandBy) -> StateData; -change_standby(#state{standby = false} = StateData, true) -> +change_standby(#state{standby = false} = StateData, + true) -> ?DEBUG("standby -> true", []), StateData#state{standby = true}; -change_standby(#state{standby = true} = StateData, false) -> +change_standby(#state{standby = true} = StateData, + false) -> ?DEBUG("standby -> false", []), - lists:foreach( - fun({_From, _To, FixedPacket}) -> - send_element(StateData, FixedPacket) - end, queue:to_list(StateData#state.queue)), - lists:foreach( - fun(FixedPacket) -> - send_element(StateData, FixedPacket) - end, gb_trees:values(StateData#state.pres_queue)), - StateData#state{queue = queue:new(), - queue_len = 0, - pres_queue = gb_trees:empty(), - standby = false}. + lists:foreach(fun ({_From, _To, FixedPacket}) -> + send_element(StateData, FixedPacket) + end, + queue:to_list(StateData#state.queue)), + lists:foreach(fun (FixedPacket) -> + send_element(StateData, FixedPacket) + end, + gb_trees:values(StateData#state.pres_queue)), + StateData#state{queue = queue:new(), queue_len = 0, + pres_queue = gb_trees:empty(), standby = false}. send_out_of_reception_message(StateData, From, To, - {xmlelement, "message", _, _} = Packet) -> - Type = xml:get_tag_attr_s("type", Packet), - if - (Type == "normal") or - (Type == "") or - (Type == "chat") or - (StateData#state.oor_send_groupchat and (Type == "groupchat"))-> - %Lang = case xml:get_tag_attr_s("xml:lang", Packet) of - % "" -> - % StateData#state.lang; - % L -> - % L - % end, - %Text = translate:translate( - % Lang, "User is temporarily out of reception"), - %MsgType = "error", - %Message = {xmlelement, "message", - % [{"type", MsgType}], - % [{xmlelement, "body", [], - % [{xmlcdata, Text}]}]}, - %ejabberd_router:route(To, From, Message), - Body1 = xml:get_path_s(Packet, [{elem, "body"}, cdata]), - Body = - case check_x_attachment(Packet) of + #xmlel{name = <<"message">>} = Packet) -> + Type = xml:get_tag_attr_s(<<"type">>, Packet), + if (Type == <<"normal">>) or (Type == <<"">>) or + (Type == <<"chat">>) + or + StateData#state.oor_send_groupchat and + (Type == <<"groupchat">>) -> + Body1 = xml:get_path_s(Packet, + [{elem, <<"body">>}, cdata]), + Body = case check_x_attachment(Packet) of true -> case Body1 of - "" -> [238, 128, 136]; - _ -> - [238, 128, 136, 32 | Body1] + <<"">> -> <<238, 128, 136>>; + _ -> <<238, 128, 136, 32, Body1/binary>> end; - false -> - Body1 - end, - Pushed = check_x_pushed(Packet), - if - Body == ""; - Pushed -> - StateData; - true -> - BFrom = jlib:jid_remove_resource(From), - LBFrom = jlib:jid_tolower(BFrom), - UnreadUsers = ?SETS:add_element( - LBFrom, - StateData#state.oor_unread_users), - IncludeBody = - case StateData#state.oor_send_body of - all -> - true; - first_per_user -> - not ?SETS:is_element( - LBFrom, - StateData#state.oor_unread_users); - first -> - StateData#state.oor_unread == 0; - none -> - false - end, - Unread = StateData#state.oor_unread + 1, - SFrom = jlib:jid_to_string(BFrom), - Msg = - if - IncludeBody -> - CBody = utf8_cut(Body, 100), - case StateData#state.oor_send_from of - jid -> SFrom ++ ": " ++ CBody; - username -> - UnescapedFrom = - unescape(BFrom#jid.user), - UnescapedFrom ++ ": " ++ CBody; - name -> - Name = get_roster_name( - StateData, BFrom), - Name ++ ": " ++ CBody; - _ -> CBody - end; - true -> - "" + false -> Body1 + end, + Pushed = check_x_pushed(Packet), + if Body == <<"">>; Pushed -> StateData; + true -> + BFrom = jlib:jid_remove_resource(From), + LBFrom = jlib:jid_tolower(BFrom), + UnreadUsers = (?SETS):add_element(LBFrom, + StateData#state.oor_unread_users), + IncludeBody = case StateData#state.oor_send_body of + all -> true; + first_per_user -> + not + (?SETS):is_element(LBFrom, + StateData#state.oor_unread_users); + first -> StateData#state.oor_unread == 0; + none -> false + end, + Unread = StateData#state.oor_unread + 1, + SFrom = jlib:jid_to_string(BFrom), + Msg = if IncludeBody -> + CBody = utf8_cut(Body, 100), + case StateData#state.oor_send_from of + jid -> <<SFrom/binary, ": ", (iolist_to_binary(CBody))/binary>>; + username -> + UnescapedFrom = unescape(BFrom#jid.user), + <<UnescapedFrom/binary, ": ", + (iolist_to_binary(CBody))/binary>>; + name -> + Name = get_roster_name(StateData, BFrom), + <<Name/binary, ": ", (iolist_to_binary(CBody))/binary>>; + _ -> <<(iolist_to_binary(CBody))/binary>> + end; + true -> <<"">> end, - Sound = IncludeBody, - AppID = StateData#state.oor_appid, - ejabberd_hooks:run( - p1_push_notification, - StateData#state.server, - [StateData#state.server, - StateData#state.jid, - StateData#state.oor_notification, - Msg, - Unread + StateData#state.oor_unread_client, - Sound, - AppID, - SFrom]), - %% This hook is intended to give other module a - %% chance to notify the sender that the message is - %% not directly delivered to the client (almost - %% equivalent to offline). - ejabberd_hooks:run(delayed_message_hook, - StateData#state.server, - [From, To, Packet]), - StateData#state{oor_unread = Unread, - oor_unread_users = UnreadUsers} - end; - true -> - StateData + Sound = IncludeBody, + AppID = StateData#state.oor_appid, + ejabberd_hooks:run(p1_push_notification, + StateData#state.server, + [StateData#state.server, + StateData#state.jid, + StateData#state.oor_notification, Msg, + Unread + + StateData#state.oor_unread_client, + Sound, AppID, SFrom]), + ejabberd_hooks:run(delayed_message_hook, + StateData#state.server, + [From, To, Packet]), + StateData#state{oor_unread = Unread, + oor_unread_users = UnreadUsers} + end; + true -> StateData end; -send_out_of_reception_message(StateData, _From, _To, _Packet) -> +send_out_of_reception_message(StateData, _From, _To, + _Packet) -> StateData. make_oor_presence(StateData) -> make_oor_presence(StateData, [], []). -make_oor_presence(StateData, PresenceAttrs, PresenceEls) -> - ShowEl = - case StateData#state.oor_show of - "available" -> []; - _ -> - [{xmlelement, "show", [], - [{xmlcdata, StateData#state.oor_show}]}] - end, - {xmlelement, "presence", PresenceAttrs, - ShowEl ++ - [{xmlelement, "status", [], - [{xmlcdata, StateData#state.oor_status}]}] - ++ PresenceEls}. - -utf8_cut(S, Bytes) -> - utf8_cut(S, [], [], Bytes + 1). - -utf8_cut(_S, _Cur, Prev, 0) -> - lists:reverse(Prev); -utf8_cut([], Cur, _Prev, _Bytes) -> - lists:reverse(Cur); -utf8_cut([C | S], Cur, Prev, Bytes) -> - if - C bsr 6 == 2 -> - utf8_cut(S, [C | Cur], Prev, Bytes - 1); - true -> - utf8_cut(S, [C | Cur], Cur, Bytes - 1) +make_oor_presence(StateData, PresenceAttrs, + PresenceEls) -> + ShowEl = case StateData#state.oor_show of + <<"available">> -> []; + _ -> + [#xmlel{name = <<"show">>, attrs = [], + children = [{xmlcdata, StateData#state.oor_show}]}] + end, + #xmlel{name = <<"presence">>, attrs = PresenceAttrs, + children = + ShowEl ++ + [#xmlel{name = <<"status">>, attrs = [], + children = [{xmlcdata, StateData#state.oor_status}]}] + ++ PresenceEls}. + +utf8_cut(S, Bytes) -> utf8_cut(S, <<>>, <<>>, Bytes + 1). + +utf8_cut(_S, _Cur, Prev, 0) -> Prev; +utf8_cut(<<>>, Cur, _Prev, _Bytes) -> Cur; +utf8_cut(<<C, S/binary>>, Cur, Prev, Bytes) -> + if C bsr 6 == 2 -> + utf8_cut(S, <<Cur/binary, C>>, Prev, Bytes - 1); + true -> utf8_cut(S, <<Cur/binary, C>>, Cur, Bytes - 1) end. -include("mod_roster.hrl"). @@ -2989,479 +2858,478 @@ utf8_cut([C | S], Cur, Prev, Bytes) -> get_roster_name(StateData, JID) -> User = StateData#state.user, Server = StateData#state.server, - RosterItems = ejabberd_hooks:run_fold( - roster_get, Server, [], [{User, Server}]), + RosterItems = ejabberd_hooks:run_fold(roster_get, + Server, [], [{User, Server}]), JUser = JID#jid.luser, JServer = JID#jid.lserver, - Item = - lists:foldl( - fun(_, Res = #roster{}) -> - Res; - (I, false) -> - case I#roster.jid of - {JUser, JServer, _} -> - I; - _ -> - false - end - end, false, RosterItems), + Item = lists:foldl(fun (_, Res = #roster{}) -> Res; + (I, false) -> + case I#roster.jid of + {JUser, JServer, _} -> I; + _ -> false + end + end, + false, RosterItems), case Item of - false -> - unescape(JID#jid.user); - #roster{} -> - Item#roster.name + false -> unescape(JID#jid.user); + #roster{} -> Item#roster.name end. -unescape("") -> ""; -unescape("\\20" ++ S) -> [$\s | unescape(S)]; -unescape("\\22" ++ S) -> [$" | unescape(S)]; -unescape("\\26" ++ S) -> [$& | unescape(S)]; -unescape("\\27" ++ S) -> [$' | unescape(S)]; -unescape("\\2f" ++ S) -> [$/ | unescape(S)]; -unescape("\\3a" ++ S) -> [$: | unescape(S)]; -unescape("\\3c" ++ S) -> [$< | unescape(S)]; -unescape("\\3e" ++ S) -> [$> | unescape(S)]; -unescape("\\40" ++ S) -> [$@ | unescape(S)]; -unescape("\\5c" ++ S) -> [$\\ | unescape(S)]; -unescape([C | S]) -> [C | unescape(S)]. - +unescape(<<"">>) -> <<"">>; +unescape(<<"\\20", S/binary>>) -> + <<"\s", (unescape(S))/binary>>; +unescape(<<"\\22", S/binary>>) -> + <<"\"", (unescape(S))/binary>>; +unescape(<<"\\26", S/binary>>) -> + <<"&", (unescape(S))/binary>>; +unescape(<<"\\27", S/binary>>) -> + <<"'", (unescape(S))/binary>>; +unescape(<<"\\2f", S/binary>>) -> + <<"/", (unescape(S))/binary>>; +unescape(<<"\\3a", S/binary>>) -> + <<":", (unescape(S))/binary>>; +unescape(<<"\\3c", S/binary>>) -> + <<"<", (unescape(S))/binary>>; +unescape(<<"\\3e", S/binary>>) -> + <<">", (unescape(S))/binary>>; +unescape(<<"\\40", S/binary>>) -> + <<"@", (unescape(S))/binary>>; +unescape(<<"\\5c", S/binary>>) -> + <<"\\", (unescape(S))/binary>>; +unescape(<<C, S/binary>>) -> <<C, (unescape(S))/binary>>. cancel_timer(Timer) -> erlang:cancel_timer(Timer), - receive - {timeout, Timer, _} -> - ok - after 0 -> - ok - end. + receive {timeout, Timer, _} -> ok after 0 -> ok end. enqueue(StateData, From, To, Packet) -> - IsPresence = - case Packet of - {xmlelement, "presence", _, _} -> - case xml:get_tag_attr_s("type", Packet) of - "subscribe" -> - false; - "subscribed" -> - false; - "unsubscribe" -> - false; - "unsubscribed" -> - false; - _ -> - true - end; - _ -> - false - end, - Messages = - StateData#state.queue_len + gb_trees:size(StateData#state.pres_queue), - if - Messages >= ?MAX_OOR_MESSAGES -> - self() ! {timeout, StateData#state.keepalive_timer, []}; - true -> - ok + IsPresence = case Packet of + #xmlel{name = <<"presence">>} -> + case xml:get_tag_attr_s(<<"type">>, Packet) of + <<"subscribe">> -> false; + <<"subscribed">> -> false; + <<"unsubscribe">> -> false; + <<"unsubscribed">> -> false; + _ -> true + end; + _ -> false + end, + Messages = StateData#state.queue_len + + gb_trees:size(StateData#state.pres_queue), + if Messages >= (?MAX_OOR_MESSAGES) -> + self() ! {timeout, StateData#state.keepalive_timer, []}; + true -> ok end, - if - IsPresence -> - LFrom = jlib:jid_tolower(From), - case is_own_presence(StateData#state.jid, LFrom) of - true -> StateData; - false -> - NewQueue = gb_trees:enter(LFrom, Packet, - StateData#state.pres_queue), - StateData#state{pres_queue = NewQueue} - end; - true -> - CleanPacket = xml:remove_subtags(Packet, "x", {"xmlns", ?NS_P1_PUSHED}), - Packet2 = - case CleanPacket of - {xmlelement, "message", _, _} -> - xml:append_subtags( - maybe_add_delay(CleanPacket, utc, To, ""), - [{xmlelement, "x", [{"xmlns", ?NS_P1_PUSHED}], []}]); - _ -> - Packet - end, - NewQueue = queue:in({From, To, Packet2}, - StateData#state.queue), - NewQueueLen = StateData#state.queue_len + 1, - StateData#state{queue = NewQueue, - queue_len = NewQueueLen} + if IsPresence -> + LFrom = jlib:jid_tolower(From), + case jlib:jid_tolower(StateData#state.jid) == LFrom of + true -> StateData; + false -> + NewQueue = gb_trees:enter(LFrom, Packet, + StateData#state.pres_queue), + StateData#state{pres_queue = NewQueue} + end; + true -> + CleanPacket = xml:remove_subtags(Packet, <<"x">>, + {<<"xmlns">>, ?NS_P1_PUSHED}), + Packet2 = case CleanPacket of + #xmlel{name = <<"message">>} -> + xml:append_subtags(maybe_add_delay(CleanPacket, utc, + To, <<"">>), + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_P1_PUSHED}], + children = []}]); + _ -> Packet + end, + NewQueue = queue:in({From, To, Packet2}, + StateData#state.queue), + NewQueueLen = StateData#state.queue_len + 1, + StateData#state{queue = NewQueue, + queue_len = NewQueueLen} end. -%% Is my own presence packet ? -is_own_presence(MyFullJID, MyFullJID) -> - true; -is_own_presence(_MyFullJID, _LFrom) -> - false. - ack(StateData, From, To, Packet) -> - if - StateData#state.ack_enabled -> - NeedsAck = - case Packet of - {xmlelement, "presence", _, _} -> - case xml:get_tag_attr_s("type", Packet) of - "subscribe" -> - true; - "subscribed" -> - true; - "unsubscribe" -> - true; - "unsubscribed" -> - true; - _ -> - false - end; - {xmlelement, "message", _, _} -> - true; - _ -> - false - end, - if - NeedsAck -> - Counter = StateData#state.ack_counter + 1, - NewAckQueue = queue:in({Counter, From, To, Packet}, - StateData#state.ack_queue), - send_ack_request(StateData#state{ack_queue = NewAckQueue, - ack_counter = Counter}); - true -> - StateData - end; - true -> - StateData + if StateData#state.ack_enabled -> + NeedsAck = case Packet of + #xmlel{name = <<"presence">>} -> + case xml:get_tag_attr_s(<<"type">>, Packet) of + <<"subscribe">> -> true; + <<"subscribed">> -> true; + <<"unsubscribe">> -> true; + <<"unsubscribed">> -> true; + _ -> false + end; + #xmlel{name = <<"message">>} -> true; + _ -> false + end, + if NeedsAck -> + Counter = StateData#state.ack_counter + 1, + NewAckQueue = queue:in({Counter, From, To, Packet}, + StateData#state.ack_queue), + send_ack_request(StateData#state{ack_queue = + NewAckQueue, + ack_counter = Counter}); + true -> StateData + end; + true -> StateData end. send_ack_request(StateData) -> case StateData#state.ack_timer of - undefined -> - AckCounter = StateData#state.ack_counter, - AckTimer = - erlang:start_timer(?C2S_P1_ACK_TIMEOUT, self(), AckCounter), - AckTimeout = StateData#state.keepalive_timeout + - StateData#state.oor_timeout, - erlang:send_after(AckTimeout * 1000, self(), - {ack_timeout, AckTimeout}), - send_element( - StateData, - {xmlelement, "r", - [{"h", integer_to_list(AckCounter)}], []}), - StateData#state{ack_timer = AckTimer}; - _ -> - StateData + undefined -> + AckCounter = StateData#state.ack_counter, + AckTimer = erlang:start_timer(?C2S_P1_ACK_TIMEOUT, + self(), AckCounter), + AckTimeout = StateData#state.keepalive_timeout + + StateData#state.oor_timeout, + erlang:send_after(AckTimeout * 1000, self(), + {ack_timeout, AckTimeout}), + send_element(StateData, + #xmlel{name = <<"r">>, + attrs = + [{<<"h">>, + iolist_to_binary(integer_to_list(AckCounter))}], + children = []}), + StateData#state{ack_timer = AckTimer}; + _ -> StateData end. receive_ack(StateData, SCounter) -> - case catch list_to_integer(SCounter) of - Counter when is_integer(Counter) -> - NewQueue = clean_queue(StateData#state.ack_queue, Counter), - StateData#state{ack_queue = NewQueue}; - _ -> - StateData + case catch jlib:binary_to_integer(SCounter) of + Counter when is_integer(Counter) -> + NewQueue = clean_queue(StateData#state.ack_queue, + Counter), + StateData#state{ack_queue = NewQueue}; + _ -> StateData end. clean_queue(Queue, Counter) -> case queue:is_empty(Queue) of - true -> - Queue; - false -> - C = element(1, queue:head(Queue)), - if - C =< Counter -> - clean_queue(queue:tail(Queue), Counter); - true -> - Queue - end + true -> Queue; + false -> + C = element(1, queue:head(Queue)), + if C =< Counter -> + clean_queue(queue:tail(Queue), Counter); + true -> Queue + end end. prepare_acks_for_rebind(StateData) -> AckQueue = StateData#state.ack_queue, case queue:is_empty(AckQueue) of - true -> - StateData; - false -> - Unsent = - lists:map( - fun({_Counter, From, To, FixedPacket}) -> - {From, To, FixedPacket} - end, queue:to_list(AckQueue)), - NewQueue = queue:join(queue:from_list(Unsent), - StateData#state.queue), - StateData#state{queue = NewQueue, - queue_len = queue:len(NewQueue), - ack_queue = queue:new(), - reception = false} + true -> StateData; + false -> + Unsent = lists:map(fun ({_Counter, From, To, + FixedPacket}) -> + {From, To, FixedPacket} + end, + queue:to_list(AckQueue)), + NewQueue = queue:join(queue:from_list(Unsent), + StateData#state.queue), + StateData#state{queue = NewQueue, + queue_len = queue:len(NewQueue), + ack_queue = queue:new(), reception = false} end. - rebind(StateData, JID, StreamID) -> case JID#jid.lresource of - "" -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_P1_REBIND}], - [{xmlcdata, "Invalid JID"}]}), - fsm_next_state(wait_for_feature_request, - StateData); - _ -> - ejabberd_sm:route( - ?MODULE, JID, - {xmlelement, rebind, [], {self(), StreamID}}), - receive - {rebind, false} -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_P1_REBIND}], - [{xmlcdata, "Session not found"}]}), - fsm_next_state(wait_for_feature_request, - StateData); - {rebind, NewStateData} -> - ?INFO_MSG("(~w) Reopened session for ~s", - [StateData#state.socket, - jlib:jid_to_string(JID)]), - SID = {now(), self()}, - StateData2 = - NewStateData#state{ - socket = StateData#state.socket, - sockmod = StateData#state.sockmod, - socket_monitor = StateData#state.socket_monitor, - sid = SID, - ip = StateData#state.ip, - keepalive_timer = StateData#state.keepalive_timer, - ack_timer = undefined - }, - send_element(StateData2, - {xmlelement, "rebind", - [{"xmlns", ?NS_P1_REBIND}], - []}), - maybe_migrate(session_established, StateData2) + <<"">> -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_P1_REBIND}], + children = [{xmlcdata, <<"Invalid JID">>}]}), + fsm_next_state(wait_for_feature_request, StateData); + _ -> + ejabberd_sm:route(jlib:make_jid(<<"">>, StateData#state.server, <<"">>), + JID, {broadcast, {rebind, self(), StreamID}}), + receive + {rebind, false} -> + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = [{<<"xmlns">>, ?NS_P1_REBIND}], + children = + [{xmlcdata, <<"Session not found">>}]}), + fsm_next_state(wait_for_feature_request, StateData); + {rebind, NewStateData} -> + ?INFO_MSG("(~w) Reopened session for ~s", + [StateData#state.socket, jlib:jid_to_string(JID)]), + SID = {now(), self()}, + StateData2 = NewStateData#state{socket = + StateData#state.socket, + sockmod = + StateData#state.sockmod, + socket_monitor = + StateData#state.socket_monitor, + sid = SID, + ip = StateData#state.ip, + keepalive_timer = + StateData#state.keepalive_timer, + ack_timer = undefined}, + send_element(StateData2, + #xmlel{name = <<"rebind">>, + attrs = [{<<"xmlns">>, ?NS_P1_REBIND}], + children = []}), + maybe_migrate(session_established, StateData2) after 1000 -> - send_element(StateData, - {xmlelement, "failure", - [{"xmlns", ?NS_P1_REBIND}], - [{xmlcdata, "Session not found"}]}), - fsm_next_state(wait_for_feature_request, - StateData) - end + send_element(StateData, + #xmlel{name = <<"failure">>, + attrs = + [{<<"xmlns">>, ?NS_P1_REBIND}], + children = + [{xmlcdata, + <<"Session not found">>}]}), + fsm_next_state(wait_for_feature_request, StateData) + end end. process_push_iq(From, To, - #iq{type = _Type, sub_el = El} = IQ, - StateData) -> - {Res, NewStateData} = - case El of - {xmlelement, "push", _, _} -> - SKeepAlive = - xml:get_path_s(El, [{elem, "keepalive"}, {attr, "max"}]), - SOORTimeout = - xml:get_path_s(El, [{elem, "session"}, {attr, "duration"}]), - Status = xml:get_path_s(El, [{elem, "status"}, cdata]), - Show = xml:get_path_s(El, [{elem, "status"}, {attr, "type"}]), - SSendBody = xml:get_path_s(El, [{elem, "body"}, {attr, "send"}]), - SendBody = - case SSendBody of - "all" -> all; - "first-per-user" -> first_per_user; - "first" -> first; - "none" -> none; - _ -> none - end, - SendGroupchat = - xml:get_path_s(El, [{elem, "body"}, - {attr, "groupchat"}]) == "true", - SendFrom = send_from(El), - AppID = xml:get_path_s(El, [{elem, "appid"}, cdata]), - {Offline, Keep} = - case xml:get_path_s(El, [{elem, "offline"}, cdata]) of - "true" -> {true, false}; - "keep" -> {false, true}; - _ -> {false, false} - end, - Notification1 = xml:get_path_s(El, [{elem, "notification"}]), - Notification = - case Notification1 of - {xmlelement, _, _, _} -> - Notification1; - _ -> - {xmlelement, "notification", [], - [{xmlelement, "type", [], - [{xmlcdata, "none"}]}]} - end, - case catch {list_to_integer(SKeepAlive), - list_to_integer(SOORTimeout)} of - {KeepAlive, OORTimeout} - when OORTimeout =< ?MAX_OOR_TIMEOUT -> - if - Offline -> - ejabberd_hooks:run( - p1_push_enable_offline, - StateData#state.server, - [StateData#state.jid, - Notification, SendBody, SendFrom, AppID]); - Keep -> - ok; - true -> - ejabberd_hooks:run( - p1_push_disable, - StateData#state.server, - [StateData#state.jid, - Notification, - AppID]) - end, - NSD1 = - StateData#state{keepalive_timeout = KeepAlive, - oor_timeout = OORTimeout * 60, - oor_status = Status, - oor_show = Show, - oor_notification = Notification, - oor_send_body = SendBody, - oor_send_groupchat = SendGroupchat, - oor_send_from = SendFrom, - oor_appid = AppID, - oor_offline = Offline}, - NSD2 = start_keepalive_timer(NSD1), - {{result, []}, NSD2}; - _ -> - {{error, ?ERR_BAD_REQUEST}, StateData} - end; - {xmlelement, "disable", _, _} -> - ejabberd_hooks:run( - p1_push_disable, - StateData#state.server, - [StateData#state.jid, - StateData#state.oor_notification, - StateData#state.oor_appid]), - NSD1 = - StateData#state{keepalive_timeout = undefined, - oor_timeout = undefined, - oor_status = "", - oor_show = "", - oor_notification = undefined, - oor_send_body = all}, - NSD2 = start_keepalive_timer(NSD1), - {{result, []}, NSD2}; - {xmlelement, "badge", _, _} -> - SBadge = xml:get_path_s(El, [{attr, "unread"}]), - Badge = - case catch list_to_integer(SBadge) of - B when is_integer(B) -> - B; - _ -> - 0 - end, - NSD1 = - StateData#state{oor_unread_client = Badge}, - {{result, []}, NSD1}; - _ -> - {{error, ?ERR_BAD_REQUEST}, StateData} - end, - IQRes = - case Res of - {result, Result} -> - IQ#iq{type = result, sub_el = Result}; - {error, Error} -> - IQ#iq{type = error, sub_el = [El, Error]} - end, - ejabberd_router:route( - To, From, jlib:iq_to_xml(IQRes)), + #iq{type = _Type, sub_el = El} = IQ, StateData) -> + {Res, NewStateData} = case El of + #xmlel{name = <<"push">>} -> + SKeepAlive = xml:get_path_s(El, + [{elem, + <<"keepalive">>}, + {attr, + <<"max">>}]), + SOORTimeout = xml:get_path_s(El, + [{elem, + <<"session">>}, + {attr, + <<"duration">>}]), + Status = xml:get_path_s(El, + [{elem, <<"status">>}, + cdata]), + Show = xml:get_path_s(El, + [{elem, <<"status">>}, + {attr, <<"type">>}]), + SSendBody = xml:get_path_s(El, + [{elem, <<"body">>}, + {attr, + <<"send">>}]), + SendBody = case SSendBody of + <<"all">> -> all; + <<"first-per-user">> -> + first_per_user; + <<"first">> -> first; + <<"none">> -> none; + _ -> none + end, + SendGroupchat = xml:get_path_s(El, + [{elem, + <<"body">>}, + {attr, + <<"groupchat">>}]) + == <<"true">>, + SendFrom = send_from(El), + AppID = xml:get_path_s(El, + [{elem, <<"appid">>}, + cdata]), + {Offline, Keep} = case xml:get_path_s(El, + [{elem, + <<"offline">>}, + cdata]) + of + <<"true">> -> {true, false}; + <<"keep">> -> {false, true}; + _ -> {false, false} + end, + Notification1 = xml:get_path_s(El, + [{elem, + <<"notification">>}]), + Notification = case Notification1 of + #xmlel{} -> Notification1; + _ -> + #xmlel{name = + <<"notification">>, + attrs = [], + children = + [#xmlel{name = + <<"type">>, + attrs = + [], + children + = + [{xmlcdata, + <<"none">>}]}]} + end, + case catch + {jlib:binary_to_integer(SKeepAlive), + jlib:binary_to_integer(SOORTimeout)} + of + {KeepAlive, OORTimeout} + when OORTimeout =< (?MAX_OOR_TIMEOUT) -> + if Offline -> + ejabberd_hooks:run(p1_push_enable_offline, + StateData#state.server, + [StateData#state.jid, + Notification, + SendBody, + SendFrom, + AppID]); + Keep -> ok; + true -> + ejabberd_hooks:run(p1_push_disable, + StateData#state.server, + [StateData#state.jid, + Notification, + AppID]) + end, + NSD1 = StateData#state{keepalive_timeout = + KeepAlive, + oor_timeout = + OORTimeout * + 60, + oor_status = + Status, + oor_show = Show, + oor_notification = + Notification, + oor_send_body = + SendBody, + oor_send_groupchat + = + SendGroupchat, + oor_send_from = + SendFrom, + oor_appid = AppID, + oor_offline = + Offline}, + NSD2 = start_keepalive_timer(NSD1), + {{result, []}, NSD2}; + _ -> {{error, ?ERR_BAD_REQUEST}, StateData} + end; + #xmlel{name = <<"disable">>} -> + ejabberd_hooks:run(p1_push_disable, + StateData#state.server, + [StateData#state.jid, + StateData#state.oor_notification, + StateData#state.oor_appid]), + NSD1 = StateData#state{keepalive_timeout = + undefined, + oor_timeout = undefined, + oor_status = <<"">>, + oor_show = <<"">>, + oor_notification = + undefined, + oor_send_body = all}, + NSD2 = start_keepalive_timer(NSD1), + {{result, []}, NSD2}; + #xmlel{name = <<"badge">>} -> + SBadge = xml:get_path_s(El, + [{attr, <<"unread">>}]), + Badge = case catch + jlib:binary_to_integer(SBadge) + of + B when is_integer(B) -> B; + _ -> 0 + end, + NSD1 = StateData#state{oor_unread_client = + Badge}, + {{result, []}, NSD1}; + _ -> {{error, ?ERR_BAD_REQUEST}, StateData} + end, + IQRes = case Res of + {result, Result} -> + IQ#iq{type = result, sub_el = Result}; + {error, Error} -> + IQ#iq{type = error, sub_el = [El, Error]} + end, + ejabberd_router:route(To, From, jlib:iq_to_xml(IQRes)), NewStateData. -check_x_pushed({xmlelement, _Name, _Attrs, Els}) -> +check_x_pushed(#xmlel{children = Els}) -> check_x_pushed1(Els). -check_x_pushed1([]) -> - false; +check_x_pushed1([]) -> false; check_x_pushed1([{xmlcdata, _} | Els]) -> check_x_pushed1(Els); check_x_pushed1([El | Els]) -> - case xml:get_tag_attr_s("xmlns", El) of - ?NS_P1_PUSHED -> - true; - _ -> - check_x_pushed1(Els) + case xml:get_tag_attr_s(<<"xmlns">>, El) of + ?NS_P1_PUSHED -> true; + _ -> check_x_pushed1(Els) end. -check_x_attachment({xmlelement, _Name, _Attrs, Els}) -> +check_x_attachment(#xmlel{children = Els}) -> check_x_attachment1(Els). -check_x_attachment1([]) -> - false; +check_x_attachment1([]) -> false; check_x_attachment1([{xmlcdata, _} | Els]) -> check_x_attachment1(Els); check_x_attachment1([El | Els]) -> - case xml:get_tag_attr_s("xmlns", El) of - ?NS_P1_ATTACHMENT -> - true; - _ -> - check_x_attachment1(Els) + case xml:get_tag_attr_s(<<"xmlns">>, El) of + ?NS_P1_ATTACHMENT -> true; + _ -> check_x_attachment1(Els) end. -%% TODO: Delete XEP-0091 stuff once it is Obsolete maybe_add_delay(El, TZ, From, Desc) -> - maybe_add_delay(El, TZ, From, Desc, calendar:now_to_universal_time(now())). -maybe_add_delay({xmlelement, _, _, Els} = El, TZ, From, Desc, TimeStamp) -> - HasOldTS = lists:any( - fun({xmlelement, "x", Attrs, _}) -> - xml:get_attr_s("xmlns", Attrs) == ?NS_DELAY91; - (_) -> - false - end, Els), - HasNewTS = lists:any( - fun({xmlelement, "delay", Attrs, _}) -> - xml:get_attr_s("xmlns", Attrs) == ?NS_DELAY; - (_) -> - false - end, Els), + maybe_add_delay(El, TZ, From, Desc, + calendar:now_to_universal_time(now())). + +maybe_add_delay(#xmlel{children = Els} = El, TZ, From, + Desc, TimeStamp) -> + HasOldTS = lists:any(fun (#xmlel{name = <<"x">>, + attrs = Attrs}) -> + xml:get_attr_s(<<"xmlns">>, Attrs) == + (?NS_DELAY91); + (_) -> false + end, + Els), + HasNewTS = lists:any(fun (#xmlel{name = <<"delay">>, + attrs = Attrs}) -> + xml:get_attr_s(<<"xmlns">>, Attrs) == + (?NS_DELAY); + (_) -> false + end, + Els), El1 = if not HasOldTS -> - xml:append_subtags(El, [jlib:timestamp_to_xml(TimeStamp)]); - true -> - El - end, + xml:append_subtags(El, + [jlib:timestamp_to_xml(TimeStamp)]); + true -> El + end, if not HasNewTS -> - xml:append_subtags( - El1, [jlib:timestamp_to_xml(TimeStamp, TZ, From, Desc)]); - true -> - El1 + xml:append_subtags(El1, + [jlib:timestamp_to_xml(TimeStamp, TZ, From, + Desc)]); + true -> El1 end. send_from(El) -> - %% First test previous version attribute: - case xml:get_path_s(El, [{elem, "body"}, {attr, "jid"}]) of - "false" -> - none; - "true" -> - jid; - "" -> - case xml:get_path_s(El, [{elem, "body"}, {attr, "from"}]) of - "jid" -> jid; - "username" -> username; - "name" -> name; - "none" -> none; - _ -> jid - end + case xml:get_path_s(El, + [{elem, <<"body">>}, {attr, <<"jid">>}]) + of + <<"false">> -> none; + <<"true">> -> jid; + <<"">> -> + case xml:get_path_s(El, + [{elem, <<"body">>}, {attr, <<"from">>}]) + of + <<"jid">> -> jid; + <<"username">> -> username; + <<"name">> -> name; + <<"none">> -> none; + _ -> jid + end end. fsm_limit_opts(Opts) -> case lists:keysearch(max_fsm_queue, 1, Opts) of - {value, {_, N}} when is_integer(N) -> - [{max_queue, N}]; - _ -> - case ejabberd_config:get_local_option(max_fsm_queue) of - N when is_integer(N) -> - [{max_queue, N}]; - _ -> - [] - end + {value, {_, N}} when is_integer(N) -> [{max_queue, N}]; + _ -> + case ejabberd_config:get_local_option( + max_fsm_queue, + fun(I) when is_integer(I), I > 0 -> I end) of + undefined -> []; + N -> [{max_queue, N}] + end end. bounce_messages() -> receive - {route, From, To, El} -> - ejabberd_router:route(From, To, El), - bounce_messages() - after 0 -> - ok + {route, From, To, El} -> + ejabberd_router:route(From, To, El), bounce_messages() + after 0 -> ok end. %%%---------------------------------------------------------------------- @@ -3469,40 +3337,40 @@ bounce_messages() -> %%%---------------------------------------------------------------------- route_blocking(What, StateData) -> - SubEl = - case What of - {block, JIDs} -> - {xmlelement, "block", - [{"xmlns", ?NS_BLOCKING}], - lists:map( - fun(JID) -> - {xmlelement, "item", - [{"jid", jlib:jid_to_string(JID)}], - []} - end, JIDs)}; - {unblock, JIDs} -> - {xmlelement, "unblock", - [{"xmlns", ?NS_BLOCKING}], - lists:map( - fun(JID) -> - {xmlelement, "item", - [{"jid", jlib:jid_to_string(JID)}], - []} - end, JIDs)}; - unblock_all -> - {xmlelement, "unblock", - [{"xmlns", ?NS_BLOCKING}], []} - end, - PrivPushIQ = - #iq{type = set, xmlns = ?NS_BLOCKING, - id = "push", - sub_el = [SubEl]}, + SubEl = case What of + {block, JIDs} -> + #xmlel{name = <<"block">>, + attrs = [{<<"xmlns">>, ?NS_BLOCKING}], + children = + lists:map(fun (JID) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(JID)}], + children = []} + end, + JIDs)}; + {unblock, JIDs} -> + #xmlel{name = <<"unblock">>, + attrs = [{<<"xmlns">>, ?NS_BLOCKING}], + children = + lists:map(fun (JID) -> + #xmlel{name = <<"item">>, + attrs = + [{<<"jid">>, + jlib:jid_to_string(JID)}], + children = []} + end, + JIDs)}; + unblock_all -> + #xmlel{name = <<"unblock">>, + attrs = [{<<"xmlns">>, ?NS_BLOCKING}], children = []} + end, + PrivPushIQ = #iq{type = set, xmlns = ?NS_BLOCKING, + id = <<"push">>, sub_el = [SubEl]}, PrivPushEl = - jlib:replace_from_to( - jlib:jid_remove_resource( - StateData#state.jid), - StateData#state.jid, - jlib:iq_to_xml(PrivPushIQ)), + jlib:replace_from_to(jlib:jid_remove_resource(StateData#state.jid), + StateData#state.jid, jlib:iq_to_xml(PrivPushIQ)), send_element(StateData, PrivPushEl), %% No need to replace active privacy list here, %% blocking pushes are always accompanied by @@ -3513,113 +3381,83 @@ route_blocking(What, StateData) -> %%% JID Set memory footprint reduction code %%%---------------------------------------------------------------------- -%% Try to reduce the heap footprint of the four presence sets -%% by ensuring that we re-use strings and Jids wherever possible. -pack(S = #state{pres_a=A, - pres_i=I, - pres_f=F, - pres_t=T}) -> - {NewA, Pack1} = pack_jid_set(A, gb_trees:empty()), - {NewI, Pack2} = pack_jid_set(I, Pack1), +pack(S = #state{pres_a = A, pres_f = F, + pres_t = T}) -> + {NewA, Pack2} = pack_jid_set(A, gb_trees:empty()), {NewF, Pack3} = pack_jid_set(F, Pack2), {NewT, _Pack4} = pack_jid_set(T, Pack3), - %% Throw away Pack4 so that if we delete references to - %% Strings or Jids in any of the sets there will be - %% no live references for the GC to find. - S#state{pres_a=NewA, - pres_i=NewI, - pres_f=NewF, - pres_t=NewT}. + S#state{pres_a = NewA, pres_f = NewF, + pres_t = NewT}. pack_jid_set(Set, Pack) -> - Jids = ?SETS:to_list(Set), + Jids = (?SETS):to_list(Set), {PackedJids, NewPack} = pack_jids(Jids, Pack, []), - {?SETS:from_list(PackedJids), NewPack}. + {(?SETS):from_list(PackedJids), NewPack}. pack_jids([], Pack, Acc) -> {Acc, Pack}; -pack_jids([{U,S,R}=Jid | Jids], Pack, Acc) -> +pack_jids([{U, S, R} = Jid | Jids], Pack, Acc) -> case gb_trees:lookup(Jid, Pack) of - {value, PackedJid} -> - pack_jids(Jids, Pack, [PackedJid | Acc]); - none -> - {NewU, Pack1} = pack_string(U, Pack), - {NewS, Pack2} = pack_string(S, Pack1), - {NewR, Pack3} = pack_string(R, Pack2), - NewJid = {NewU, NewS, NewR}, - NewPack = gb_trees:insert(NewJid, NewJid, Pack3), - pack_jids(Jids, NewPack, [NewJid | Acc]) + {value, PackedJid} -> + pack_jids(Jids, Pack, [PackedJid | Acc]); + none -> + {NewU, Pack1} = pack_string(U, Pack), + {NewS, Pack2} = pack_string(S, Pack1), + {NewR, Pack3} = pack_string(R, Pack2), + NewJid = {NewU, NewS, NewR}, + NewPack = gb_trees:insert(NewJid, NewJid, Pack3), + pack_jids(Jids, NewPack, [NewJid | Acc]) end. pack_string(String, Pack) -> case gb_trees:lookup(String, Pack) of - {value, PackedString} -> - {PackedString, Pack}; - none -> - {String, gb_trees:insert(String, String, Pack)} + {value, PackedString} -> {PackedString, Pack}; + none -> {String, gb_trees:insert(String, String, Pack)} end. - -%% @spec () -> string() -%% @doc Build the content of a Flash policy file. -%% It specifies as domain "*". -%% It specifies as to-ports the ports that serve ejabberd_c2s. flash_policy_string() -> - Listen = ejabberd_config:get_local_option(listen), - ClientPortsDeep = ["," ++ integer_to_list(Port) - || {{Port,_,_}, ejabberd_c2s, _Opts} <- Listen], - %% NOTE: The function string:join/2 was introduced in Erlang/OTP R12B-0 - %% so it can't be used yet in ejabberd. - ToPortsString = case lists:flatten(ClientPortsDeep) of - [$, | Tail] -> Tail; - _ -> [] + Listen = ejabberd_config:get_local_option(listen, fun(V) -> V end), + ClientPortsDeep = [<<",", + (iolist_to_binary(integer_to_list(Port)))/binary>> + || {{Port, _, _}, ejabberd_c2s, _Opts} <- Listen], + ToPortsString = case iolist_to_binary(lists:flatten(ClientPortsDeep)) of + <<",", Tail/binary>> -> Tail; + _ -> <<>> end, - - "<?xml version=\"1.0\"?>\n" - "<!DOCTYPE cross-domain-policy SYSTEM " - "\"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" - "<cross-domain-policy>\n" - " <allow-access-from domain=\"*\" to-ports=\"" - ++ ToPortsString ++ - "\"/>\n" - "</cross-domain-policy>\n\0". - -need_redirect(#state{redirect = true, user = User, server = Server}) -> + <<"<?xml version=\"1.0\"?>\n<!DOCTYPE cross-doma" + "in-policy SYSTEM \"http://www.macromedia.com/" + "xml/dtds/cross-domain-policy.dtd\">\n<cross-d" + "omain-policy>\n <allow-access-from " + "domain=\"*\" to-ports=\"", + ToPortsString/binary, + "\"/>\n</cross-domain-policy>\n\000">>. + +need_redirect(#state{redirect = true, user = User, + server = Server}) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), case ejabberd_cluster:get_node({LUser, LServer}) of - Node when node() == Node -> - false; - Node -> - case rpc:call(Node, ejabberd_config, - get_local_option, [hostname], 5000) of - Host when is_list(Host) -> - {true, Host}; - _ -> - false - end + Node when node() == Node -> false; + Node -> + case rpc:call(Node, ejabberd_config, get_local_option, + [hostname], 5000) + of + Host when is_binary(Host) -> {true, Host}; + _ -> false + end end; -need_redirect(_) -> - false. +need_redirect(_) -> false. get_jid_from_opts(Opts) -> case lists:keysearch(jid, 1, Opts) of - {value, {_, JIDValue}} -> - JID = case JIDValue of - {_U, _S, _R} -> - jlib:make_jid(JIDValue); - _ when is_binary(JIDValue) -> - jlib:string_to_jid(binary_to_list(JIDValue)); - _ when is_list(JIDValue) -> - jlib:string_to_jid(JIDValue); - _ -> - JIDValue - end, - {ok, JID}; - _ -> - error + {value, {_, JIDValue}} -> + JID = case JIDValue of + {_U, _S, _R} -> jlib:make_jid(JIDValue); + _ when is_list(JIDValue) -> + jlib:string_to_jid(list_to_binary(JIDValue)); + _ when is_binary(JIDValue) -> + jlib:string_to_jid(JIDValue); + _ -> JIDValue + end, + {ok, JID}; + _ -> error end. - -is_remote_receiver(#socket_state{receiver = Pid}) when is_pid(Pid) -> - node(Pid) /= node(); -is_remote_receiver(_) -> - false. |