aboutsummaryrefslogblamecommitdiff
path: root/src/ejabberd_c2s.erl
blob: 0ac39518fe88e69e7adf6316f94b0f5c100cf16c (plain) (tree)
1
2
3
4
5

                                                                          

   
                                                  









                                                                      
   


                                                                           
   
                                                                      
                      
                           
                            
                            



                            
                                                
                            
                                                                    


                                                     


                                                                               
                                                                            
                                                    
                                                                        


                                                                     


                                                           

                                                              
                                                                  

                                                       
 
                         
                     
                       
 
                       
 
                       
                        
 


                                                                      
                        






                                                                       



                                                                    
 

                
 


                                                                      



                                                                  


                                     

                                        
                                  
 
                                                                           





                                                                              





                       
                                        

                                                     
                                    
 

                                
             

                              



                                        
 

                                   







                                               

                                                                                
                                 
                                                           

        






                                                                               




                                 



                                                 

                              








                                                                       
 











                                                                          



                                                                



                                                                












                                                                            
                                                                           
 






                                                                           

                                                        
                                                       




                                                               


                                                                      
                                                               








                                                                






                                                               
              

                  
                                             

                                             
                                                  
        
                            

                                                    
 



                                                   


                                             
 


                                        

                                                                                

                                          
                                                    
                                                             





                                                                     

                                                                           

                                                                 
                                                             

                          

                           
                                                          
                                                                 


                                                                   

                                     



                                                                      


                                                                 
                                                 
                                                       
                                            



                                                                       
                                                




                                                                  
                                                         




                                                                             
                                               




                                                                
                                               



                                                                
                                                                      


                                                        
        
 

                                               
 


                                         




                                           




                                    
                                                         
                                                                           
 
                                                       
                                                                            
 
                                                
                                                                                





                                                                               













                                                                                       
 

                               
                                                                 

                                                                   



                                                




                                                                       

                                                                                 





                                                                               
                                                               
                                                                     
                                 

                                                                               
                                                                  
                                                                     


                                                                   

        
                                                                  
                                               
                
                                                                       
               


                                                                
        
 
                                                           

                                                                   

                                           

                                                               
                                                                                    
                                                       
                                                      
                                                                    
                                               
                                                                            

                                       

                                                               
                                                                
                                        


                                                                      
                                                                            
                                                                            
 
                                                             
                                                                        
 
                                                                    
                                                                               
 
                                                                                           
                                                     
                                                   


                                                                  
                                                              
                                                             
                              


                                                                      

                   
                                         
                                                          
                                  
                                                            
                    
                                                          
                

                                                             
                                                
                      
                                               
            
                                                  
        
 

                                                      



                                                                        
 

                                                                            
 
                      

                                                     




                                            
                                                    

                                


                                                                

                                                    

                                                              
                                          
                                            
                                                
                                              



                                            
                                 

                                      
                                       

                                      
                                                            
 
                                                         

                                                    
                                                      

                                                                           
               




                                                                
                                                            
                            
                                                        
 
                                                  
                                                                    
 
                                                   
                                                                     
 

                                                                      
 

                                      
 


                                                                      





                                                           
                                                                            
                          
        
 

                                                                     




                                                                   
                                                


                                                  
                           




                                                     

                                                          

                          
        
 
                                                                       
                                                                    
                                                                        


                                                                              
                                                  




                                                            
            
                                                         
                        
                                                          


                                     


               
                                                     
                                                                  












                                                                





                                                              

                                                                  
                               







                              
 
                                                           

                                                                          
                                                                            
                          
                                                  



                                                              
                                         

                                                               
                                                                              
                                                 
                                                              


                                                            
                                                 

                                                               
                                               
                                                                 
                                                         
                                                                                
                         

                                                  
                                        
                  
                
                                        
                            

                                                                
                    
                               
        
 
                                                            
                                                                   





                                                                       



                                                                       
                                                    
                                                            

                                                         


                                                                       

                                                                   
                                                                 
                                      
          
















                                                                              
                                                                    
                                                              
                                                










                                                                         
                                                             

                                                         
               
                                                              

                                                         
                                        
                
                                       
                 
        
 
                                                                        
                                                                
                                                                                     
 
                                                          



                                                         
        
 
                                                                  
                                                        









                                                            
                                                       
                                                   

                                                                        
 





                                                                             
                                                             

                                 
                    

                                            
                                                                             
                                      
                                       






                                          

        

                                   

                                       




                                  




                                                                      
 









                                                                            

        


                                                       










                                                                                  


                           
                                   
                                                                   
                                                                  
                            




                                                                   

                                                                   



                                                
               
                  
                
                                                    
                       

                                                          
                        





                                                                   
                       
               
        
 


                                                 





                                  
                      
                                
 


                                     









                                                                                       



                                                   





                                                    





                                     




                                                         
              
                                           

                                                                  
 
















                                                                          

                                                            




                                          











                                                                         
                                        




                                            





                                                                          
                     



                                                               
                                                                 
               

                                                                         

                
%%%-------------------------------------------------------------------
%%% Created :  8 Dec 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017   ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_c2s).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_socket).

-protocol({rfc, 6121}).

%% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]).
%% ejabberd_config callbacks
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
	 handle_info/2, terminate/2, code_change/3]).
-export([tls_options/1, tls_required/1, tls_verify/1, tls_enabled/1,
	 compress_methods/1, bind/2, sasl_mechanisms/2,
	 get_password_fun/1, check_password_fun/1, check_password_digest_fun/1,
	 unauthenticated_stream_features/1, authenticated_stream_features/1,
	 handle_stream_start/2, handle_stream_end/2,
	 handle_unauthenticated_packet/2, handle_authenticated_packet/2,
	 handle_auth_success/4, handle_auth_failure/4, handle_send/3,
	 handle_recv/3, handle_cdata/2, handle_unbinded_packet/2]).
%% Hooks
-export([handle_unexpected_cast/2,
	 reject_unauthenticated_packet/2, process_closed/2,
	 process_terminated/2, process_info/2]).
%% API
-export([get_presence/1, get_subscription/2, get_subscribed/1,
	 open_session/1, call/3, send/2, close/1, close/2, stop/1,
	 reply/2, copy_state/2, set_timeout/2, route/2,
	 host_up/1, host_down/1]).

-include("ejabberd.hrl").
-include("xmpp.hrl").
-include("logger.hrl").

-define(SETS, gb_sets).

-type state() :: map().
-export_type([state/0]).

%%%===================================================================
%%% ejabberd_socket API
%%%===================================================================
start(SockData, Opts) ->
    case proplists:get_value(supervisor, Opts, true) of
	true ->
	    supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]);
	_ ->
	    xmpp_stream_in:start(?MODULE, [SockData, Opts],
				 ejabberd_config:fsm_limit_opts(Opts))
    end.

start_link(SockData, Opts) ->
    xmpp_stream_in:start_link(?MODULE, [SockData, Opts],
			      ejabberd_config:fsm_limit_opts(Opts)).

socket_type() ->
    xml_stream.

%%%===================================================================
%%% Common API
%%%===================================================================
-spec call(pid(), term(), non_neg_integer() | infinity) -> term().
call(Ref, Msg, Timeout) ->
    xmpp_stream_in:call(Ref, Msg, Timeout).

reply(Ref, Reply) ->
    xmpp_stream_in:reply(Ref, Reply).

-spec get_presence(pid()) -> presence().
get_presence(Ref) ->
    call(Ref, get_presence, 1000).

-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none.
get_subscription(#jid{} = From, State) ->
    get_subscription(jid:tolower(From), State);
get_subscription(LFrom, #{pres_f := PresF, pres_t := PresT}) ->
    LBFrom = jid:remove_resource(LFrom),
    F = ?SETS:is_element(LFrom, PresF) orelse ?SETS:is_element(LBFrom, PresF),
    T = ?SETS:is_element(LFrom, PresT) orelse ?SETS:is_element(LBFrom, PresT),
    if F and T -> both;
       F -> from;
       T -> to;
       true -> none
    end.

-spec get_subscribed(pid()) -> [ljid()].
%% Return list of all available resources of contacts
get_subscribed(Ref) ->
    call(Ref, get_subscribed, 1000).

-spec close(pid()) -> ok;
	   (state()) -> state().
close(Ref) ->
    xmpp_stream_in:close(Ref).

-spec close(pid(), atom()) -> ok;
	   (state(), atom()) -> state().
close(Ref, Reason) ->
    xmpp_stream_in:close(Ref, Reason).

-spec stop(pid()) -> ok;
	  (state()) -> no_return().
stop(Ref) ->
    xmpp_stream_in:stop(Ref).

-spec send(pid(), xmpp_element()) -> ok;
	  (state(), xmpp_element()) -> state().
send(Pid, Pkt) when is_pid(Pid) ->
    xmpp_stream_in:send(Pid, Pkt);
send(#{lserver := LServer} = State, Pkt) ->
    Pkt1 = fix_from_to(Pkt, State),
    case ejabberd_hooks:run_fold(c2s_filter_send, LServer, {Pkt1, State}, []) of
	{drop, State1} -> State1;
	{Pkt2, State1} -> xmpp_stream_in:send(State1, Pkt2)
    end.

-spec send_error(state(), xmpp_element(), stanza_error()) -> state().
send_error(#{lserver := LServer} = State, Pkt, Err) ->
    case ejabberd_hooks:run_fold(c2s_filter_send, LServer, {Pkt, State}, []) of
	{drop, State1} -> State1;
	{Pkt1, State1} -> xmpp_stream_in:send_error(State1, Pkt1, Err)
    end.

-spec route(pid(), term()) -> ok.
route(Pid, Term) ->
    Pid ! Term,
    ok.

-spec set_timeout(state(), timeout()) -> state().
set_timeout(State, Timeout) ->
    xmpp_stream_in:set_timeout(State, Timeout).

-spec host_up(binary()) -> ok.
host_up(Host) ->
    ejabberd_hooks:add(c2s_closed, Host, ?MODULE, process_closed, 100),
    ejabberd_hooks:add(c2s_terminated, Host, ?MODULE,
		       process_terminated, 100),
    ejabberd_hooks:add(c2s_unauthenticated_packet, Host, ?MODULE,
		       reject_unauthenticated_packet, 100),
    ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
		       process_info, 100),
    ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
		       handle_unexpected_cast, 100).

-spec host_down(binary()) -> ok.
host_down(Host) ->
    ejabberd_hooks:delete(c2s_closed, Host, ?MODULE, process_closed, 100),
    ejabberd_hooks:delete(c2s_terminated, Host, ?MODULE,
			  process_terminated, 100),
    ejabberd_hooks:delete(c2s_unauthenticated_packet, Host, ?MODULE,
			  reject_unauthenticated_packet, 100),
    ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
			  process_info, 100),
    ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
			  handle_unexpected_cast, 100).

%% Copies content of one c2s state to another.
%% This is needed for session migration from one pid to another.
-spec copy_state(state(), state()) -> state().
copy_state(#{owner := Owner} = NewState,
	   #{jid := JID, resource := Resource, sid := {Time, _},
	     auth_module := AuthModule, lserver := LServer,
	     pres_t := PresT, pres_a := PresA,
	     pres_f := PresF} = OldState) ->
    State1 = case OldState of
		 #{pres_last := Pres, pres_timestamp := PresTS} ->
		     NewState#{pres_last => Pres, pres_timestamp => PresTS};
		 _ ->
		     NewState
	     end,
    Conn = get_conn_type(State1),
    State2 = State1#{jid => JID, resource => Resource,
		     conn => Conn,
		     sid => {Time, Owner},
		     auth_module => AuthModule,
		     pres_t => PresT, pres_a => PresA,
		     pres_f => PresF},
    ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]).

-spec open_session(state()) -> {ok, state()} | state().
open_session(#{user := U, server := S, resource := R,
	       sid := SID, ip := IP, auth_module := AuthModule} = State) ->
    JID = jid:make(U, S, R),
    change_shaper(State),
    Conn = get_conn_type(State),
    State1 = State#{conn => Conn, resource => R, jid => JID},
    Prio = case maps:get(pres_last, State, undefined) of
	       undefined -> undefined;
	       Pres -> get_priority_from_presence(Pres)
	   end,
    Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthModule}],
    ejabberd_sm:open_session(SID, U, S, R, Prio, Info),
    xmpp_stream_in:establish(State1).

%%%===================================================================
%%% Hooks
%%%===================================================================
process_info(#{lserver := LServer} = State, {route, Packet}) ->
    {Pass, State1} = case Packet of
			 #presence{} ->
			     process_presence_in(State, Packet);
			 #message{} ->
			     process_message_in(State, Packet);
			 #iq{} ->
			     process_iq_in(State, Packet)
		     end,
    if Pass ->
	    {Packet1, State2} = ejabberd_hooks:run_fold(
				  user_receive_packet, LServer,
				  {Packet, State1}, []),
	    case Packet1 of
		drop -> State2;
		_ -> send(State2, Packet1)
	    end;
       true ->
	    State1
    end;
process_info(State, force_update_presence) ->
    case maps:get(pres_last, State, error) of
	error -> State;
	Pres -> process_self_presence(State, Pres)
    end;
process_info(State, Info) ->
    ?WARNING_MSG("got unexpected info: ~p", [Info]),
    State.

handle_unexpected_cast(State, Msg) ->
    ?WARNING_MSG("got unexpected cast: ~p", [Msg]),
    State.

reject_unauthenticated_packet(State, _Pkt) ->
    Err = xmpp:serr_not_authorized(),
    send(State, Err).

process_closed(State, Reason) ->
    stop(State#{stop_reason => Reason}).

process_terminated(#{sid := SID, sockmod := SockMod, socket := Socket,
		     jid := JID, user := U, server := S, resource := R} = State,
		   Reason) ->
    Status = format_reason(State, Reason),
    ?INFO_MSG("(~s) Closing c2s session for ~s: ~s",
	      [SockMod:pp(Socket), jid:encode(JID), Status]),
    State1 = case maps:is_key(pres_last, State) of
		 true ->
		     Pres = #presence{type = unavailable,
				      status = xmpp:mk_text(Status),
				      from = JID,
				      to = jid:remove_resource(JID)},
		     ejabberd_sm:close_session_unset_presence(SID, U, S, R,
							      Status),
		     broadcast_presence_unavailable(State, Pres);
		 false ->
		     ejabberd_sm:close_session(SID, U, S, R),
		     State
	     end,
    bounce_message_queue(),
    State1;
process_terminated(#{sockmod := SockMod, socket := Socket,
		     stop_reason := {tls, _}} = State, Reason) ->
    ?ERROR_MSG("(~s) Failed to secure c2s connection: ~s",
	       [SockMod:pp(Socket), format_reason(State, Reason)]),
    State;
process_terminated(State, _Reason) ->
    State.

%%%===================================================================
%%% xmpp_stream_in callbacks
%%%===================================================================
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
    TLSOpts1 = case ejabberd_config:get_option(
		      {c2s_certfile, LServer},
		      ejabberd_config:get_option(
			{domain_certfile, LServer})) of
		   undefined -> DefaultOpts;
		   CertFile -> lists:keystore(certfile, 1, DefaultOpts,
					      {certfile, CertFile})
	       end,
    TLSOpts2 = case ejabberd_config:get_option(
                      {c2s_ciphers, LServer}) of
                   undefined -> TLSOpts1;
                   Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
					     {ciphers, Ciphers})
               end,
    TLSOpts3 = case ejabberd_config:get_option(
                      {c2s_protocol_options, LServer}) of
                   undefined -> TLSOpts2;
                   ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
					       {protocol_options, ProtoOpts})
               end,
    TLSOpts4 = case ejabberd_config:get_option(
                      {c2s_dhfile, LServer}) of
                   undefined -> TLSOpts3;
                   DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
					    {dhfile, DHFile})
               end,
    TLSOpts5 = case ejabberd_config:get_option(
		      {c2s_cafile, LServer}) of
		   undefined -> TLSOpts4;
		   CAFile -> lists:keystore(cafile, 1, TLSOpts4,
					    {cafile, CAFile})
	       end,
    case ejabberd_config:get_option({c2s_tls_compression, LServer}) of
	undefined -> TLSOpts5;
	false -> [compression_none | TLSOpts5];
	true -> lists:delete(compression_none, TLSOpts5)
    end.

tls_required(#{tls_required := TLSRequired}) ->
    TLSRequired.

tls_verify(#{tls_verify := TLSVerify}) ->
    TLSVerify.

tls_enabled(#{tls_enabled := TLSEnabled,
	      tls_required := TLSRequired,
	      tls_verify := TLSVerify}) ->
    TLSEnabled or TLSRequired or TLSVerify.

compress_methods(#{zlib := true}) ->
    [<<"zlib">>];
compress_methods(_) ->
    [].

unauthenticated_stream_features(#{lserver := LServer}) ->
    ejabberd_hooks:run_fold(c2s_pre_auth_features, LServer, [], [LServer]).

authenticated_stream_features(#{lserver := LServer}) ->
    ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).

sasl_mechanisms(Mechs, #{lserver := LServer}) ->
    Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
    Mechs2 = case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer) of
		 true -> Mechs1;
		 false -> [<<"ANONYMOUS">>|Mechs1]
	     end,
    Mechs -- Mechs2.

get_password_fun(#{lserver := LServer}) ->
    fun(U) ->
	    ejabberd_auth:get_password_with_authmodule(U, LServer)
    end.

check_password_fun(#{lserver := LServer}) ->
    fun(U, AuthzId, P) ->
	    ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P)
    end.

check_password_digest_fun(#{lserver := LServer}) ->
    fun(U, AuthzId, P, D, DG) ->
	    ejabberd_auth:check_password_with_authmodule(U, AuthzId, LServer, P, D, DG)
    end.

bind(<<"">>, State) ->
    bind(new_uniq_id(), State);
bind(R, #{user := U, server := S, access := Access, lang := Lang,
	  lserver := LServer, sockmod := SockMod, socket := Socket,
	  ip := IP} = State) ->
    case resource_conflict_action(U, S, R) of
	closenew ->
	    {error, xmpp:err_conflict(), State};
	{accept_resource, Resource} ->
	    JID = jid:make(U, S, Resource),
	    case acl:access_matches(Access,
				    #{usr => jid:split(JID), ip => IP},
				    LServer) of
		allow ->
		    State1 = open_session(State#{resource => Resource,
						 sid => ejabberd_sm:make_sid()}),
		    LBJID = jid:remove_resource(jid:tolower(JID)),
		    PresF = ?SETS:add_element(LBJID, maps:get(pres_f, State1)),
		    PresT = ?SETS:add_element(LBJID, maps:get(pres_t, State1)),
		    State2 = State1#{pres_f => PresF, pres_t => PresT},
		    State3 = ejabberd_hooks:run_fold(
			       c2s_session_opened, LServer, State2, []),
		    ?INFO_MSG("(~s) Opened c2s session for ~s",
			      [SockMod:pp(Socket), jid:encode(JID)]),
		    {ok, State3};
		deny ->
		    ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
		    ?INFO_MSG("(~s) Forbidden c2s session for ~s",
			      [SockMod:pp(Socket), jid:encode(JID)]),
		    Txt = <<"Denied by ACL">>,
		    {error, xmpp:err_not_allowed(Txt, Lang), State}
	    end
    end.

handle_stream_start(StreamStart, #{lserver := LServer} = State) ->
    case ejabberd_router:is_my_host(LServer) of
	false ->
	    send(State#{lserver => ?MYNAME}, xmpp:serr_host_unknown());
	true ->
	    change_shaper(State),
	    ejabberd_hooks:run_fold(
	      c2s_stream_started, LServer, State, [StreamStart])
    end.

handle_stream_end(Reason, #{lserver := LServer} = State) ->
    State1 = State#{stop_reason => Reason},
    ejabberd_hooks:run_fold(c2s_closed, LServer, State1, [Reason]).

handle_auth_success(User, Mech, AuthModule,
		    #{socket := Socket, sockmod := SockMod,
		      ip := IP, lserver := LServer} = State) ->
    ?INFO_MSG("(~s) Accepted c2s ~s authentication for ~s@~s by ~s backend from ~s",
	      [SockMod:pp(Socket), Mech, User, LServer,
	       ejabberd_auth:backend_type(AuthModule),
	       ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
    State1 = State#{auth_module => AuthModule},
    ejabberd_hooks:run_fold(c2s_auth_result, LServer, State1, [true, User]).

handle_auth_failure(User, Mech, Reason,
		    #{socket := Socket, sockmod := SockMod,
		      ip := IP, lserver := LServer} = State) ->
    ?INFO_MSG("(~s) Failed c2s ~s authentication ~sfrom ~s: ~s",
	      [SockMod:pp(Socket), Mech,
	       if User /= <<"">> -> ["for ", User, "@", LServer, " "];
		  true -> ""
	       end,
	       ejabberd_config:may_hide_data(misc:ip_to_list(IP)), Reason]),
    ejabberd_hooks:run_fold(c2s_auth_result, LServer, State, [false, User]).

handle_unbinded_packet(Pkt, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_unbinded_packet, LServer, State, [Pkt]).

handle_unauthenticated_packet(Pkt, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_unauthenticated_packet, LServer, State, [Pkt]).

handle_authenticated_packet(Pkt, #{lserver := LServer} = State) when not ?is_stanza(Pkt) ->
    ejabberd_hooks:run_fold(c2s_authenticated_packet,
			    LServer, State, [Pkt]);
handle_authenticated_packet(Pkt, #{lserver := LServer, jid := JID,
				   ip := {IP, _}} = State) ->
    Pkt1 = xmpp:put_meta(Pkt, ip, IP),
    State1 = ejabberd_hooks:run_fold(c2s_authenticated_packet,
				     LServer, State, [Pkt1]),
    #jid{luser = LUser} = JID,
    {Pkt2, State2} = ejabberd_hooks:run_fold(
		       user_send_packet, LServer, {Pkt1, State1}, []),
    case Pkt2 of
	drop ->
	    State2;
	#iq{type = set, sub_els = [_]} ->
	    case xmpp:get_subtag(Pkt2, #xmpp_session{}) of
		#xmpp_session{} ->
		    send(State2, xmpp:make_iq_result(Pkt2));
		_ ->
		    check_privacy_then_route(State2, Pkt2)
	    end;
	#presence{to = #jid{luser = LUser, lserver = LServer,
			    lresource = <<"">>}} ->
	    process_self_presence(State2, Pkt2);
	#presence{} ->
	    process_presence_out(State2, Pkt2);
	_ ->
	    check_privacy_then_route(State2, Pkt2)
    end.

handle_cdata(Data, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_handle_cdata, LServer,
			    State, [Data]).

handle_recv(El, Pkt, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_handle_recv, LServer, State, [El, Pkt]).

handle_send(Pkt, Result, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_handle_send, LServer, State, [Pkt, Result]).

init([State, Opts]) ->
    Access = proplists:get_value(access, Opts, all),
    Shaper = proplists:get_value(shaper, Opts, none),
    TLSOpts1 = lists:filter(
		 fun({certfile, _}) -> true;
		    ({ciphers, _}) -> true;
		    ({dhfile, _}) -> true;
		    ({cafile, _}) -> true;
		    ({protocol_options, _}) -> true;
		    (_) -> false
		 end, Opts),
    TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
                   false -> [compression_none | TLSOpts1];
                   true -> TLSOpts1
               end,
    TLSEnabled = proplists:get_bool(starttls, Opts),
    TLSRequired = proplists:get_bool(starttls_required, Opts),
    TLSVerify = proplists:get_bool(tls_verify, Opts),
    Zlib = proplists:get_bool(zlib, Opts),
    State1 = State#{tls_options => TLSOpts2,
		    tls_required => TLSRequired,
		    tls_enabled => TLSEnabled,
		    tls_verify => TLSVerify,
		    pres_a => ?SETS:new(),
		    pres_f => ?SETS:new(),
		    pres_t => ?SETS:new(),
		    zlib => Zlib,
		    lang => ?MYLANG,
		    server => ?MYNAME,
		    lserver => ?MYNAME,
		    access => Access,
		    shaper => Shaper},
    ejabberd_hooks:run_fold(c2s_init, {ok, State1}, [Opts]).

handle_call(get_presence, From, #{jid := JID} = State) ->
    Pres = case maps:get(pres_last, State, error) of
	       error ->
		   BareJID = jid:remove_resource(JID),
		   #presence{from = JID, to = BareJID, type = unavailable};
	       P -> P
	   end,
    reply(From, Pres),
    State;
handle_call(get_subscribed, From, #{pres_f := PresF} = State) ->
    reply(From, ?SETS:to_list(PresF)),
    State;
handle_call(Request, From, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(
      c2s_handle_call, LServer, State, [Request, From]).

handle_cast(Msg, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_handle_cast, LServer, State, [Msg]).

handle_info(Info, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_handle_info, LServer, State, [Info]).

terminate(Reason, #{lserver := LServer} = State) ->
    ejabberd_hooks:run_fold(c2s_terminated, LServer, State, [Reason]).

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec process_iq_in(state(), iq()) -> {boolean(), state()}.
process_iq_in(State, #iq{} = IQ) ->
    case privacy_check_packet(State, IQ, in) of
	allow ->
	    {true, State};
	deny ->
	    ejabberd_router:route_error(IQ, xmpp:err_service_unavailable()),
	    {false, State}
    end.

-spec process_message_in(state(), message()) -> {boolean(), state()}.
process_message_in(State, #message{type = T} = Msg) ->
    %% This function should be as simple as process_iq_in/2,
    %% however, we don't route errors to MUC rooms in order
    %% to avoid kicking us, because having a MUC room's JID blocked
    %% most likely means having only some particular participant
    %% blocked, i.e. room@conference.server.org/participant.
    case privacy_check_packet(State, Msg, in) of
	allow ->
	    {true, State};
	deny when T == groupchat; T == headline ->
	    {false, State};
	deny ->
	    case xmpp:has_subtag(Msg, #muc_user{}) of
		true ->
		    ok;
		false ->
		    ejabberd_router:route_error(
		      Msg, xmpp:err_service_unavailable())
	    end,
	    {false, State}
    end.

-spec process_presence_in(state(), presence()) -> {boolean(), state()}.
process_presence_in(#{lserver := LServer, pres_a := PresA} = State0,
		    #presence{from = From, to = To, type = T} = Pres) ->
    State = ejabberd_hooks:run_fold(c2s_presence_in, LServer, State0, [Pres]),
    case T of
	probe ->
	    NewState = add_to_pres_a(State, From),
	    route_probe_reply(From, To, NewState),
	    {false, NewState};
	error ->
	    A = ?SETS:del_element(jid:tolower(From), PresA),
	    {true, State#{pres_a => A}};
	_ ->
	    case privacy_check_packet(State, Pres, in) of
		allow ->
		    NewState = add_to_pres_a(State, From),
		    {true, NewState};
		deny ->
		    {false, State}
	    end
    end.

-spec route_probe_reply(jid(), jid(), state()) -> ok.
route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
			      pres_last := LastPres,
			      pres_timestamp := TS} = State) ->
    LFrom = jid:tolower(From),
    LBFrom = jid:remove_resource(LFrom),
    case ?SETS:is_element(LFrom, PresF)
	orelse ?SETS:is_element(LBFrom, PresF) of
	true ->
	    %% To is my JID
	    Packet = xmpp_util:add_delay_info(LastPres, To, TS),
	    case privacy_check_packet(State, Packet, out) of
		deny ->
		    ok;
		allow ->
		    ejabberd_hooks:run(presence_probe_hook,
				       LServer,
				       [From, To, self()]),
		    %% Don't route a presence probe to oneself
		    case From == To of
			false ->
			    ejabberd_router:route(
			      xmpp:set_from_to(Packet, To, From));
			true ->
			    ok
		    end
	    end;
	false ->
	    ok
    end;
route_probe_reply(_, _, _) ->
    ok.

-spec process_presence_out(state(), presence()) -> state().
process_presence_out(#{user := User, server := Server, lserver := LServer,
		       jid := JID, lang := Lang, pres_a := PresA} = State,
		     #presence{from = From, to = To, type = Type} = Pres) ->
    LTo = jid:tolower(To),
    case privacy_check_packet(State, Pres, out) of
	deny ->
            ErrText = <<"Your active privacy list has denied "
			"the routing of this stanza.">>,
	    Err = xmpp:err_not_acceptable(ErrText, Lang),
	    send_error(State, Pres, Err);
	allow when Type == subscribe; Type == subscribed;
		   Type == unsubscribe; Type == unsubscribed ->
	    Access = gen_mod:get_module_opt(LServer, mod_roster, access, all),
	    MyBareJID = jid:remove_resource(JID),
	    case acl:match_rule(LServer, Access, MyBareJID) of
		deny ->
		    ErrText = <<"Denied by ACL">>,
		    Err = xmpp:err_forbidden(ErrText, Lang),
		    send_error(State, Pres, Err);
		allow ->
		    ejabberd_hooks:run(roster_out_subscription,
				       LServer,
				       [User, Server, To, Type]),
		    BareFrom = jid:remove_resource(From),
		    ejabberd_router:route(xmpp:set_from_to(Pres, BareFrom, To)),
		    State
	    end;
	allow when Type == error; Type == probe ->
	    ejabberd_router:route(Pres),
	    State;
	allow ->
	    ejabberd_router:route(Pres),
	    A = case Type of
		    available -> ?SETS:add_element(LTo, PresA);
		    unavailable -> ?SETS:del_element(LTo, PresA)
		end,
	    State#{pres_a => A}
    end.

-spec process_self_presence(state(), presence()) -> state().
process_self_presence(#{ip := IP, conn := Conn, lserver := LServer,
			auth_module := AuthMod, sid := SID,
			user := U, server := S,	resource := R} = State,
		      #presence{type = unavailable} = Pres) ->
    Status = xmpp:get_text(Pres#presence.status),
    Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
    ejabberd_sm:unset_presence(SID, U, S, R, Status, Info),
    {Pres1, State1} = ejabberd_hooks:run_fold(
			c2s_self_presence, LServer, {Pres, State}, []),
    State2 = broadcast_presence_unavailable(State1, Pres1),
    maps:remove(pres_last, maps:remove(pres_timestamp, State2));
process_self_presence(#{lserver := LServer} = State,
		      #presence{type = available} = Pres) ->
    PreviousPres = maps:get(pres_last, State, undefined),
    update_priority(State, Pres),
    {Pres1, State1} = ejabberd_hooks:run_fold(
			c2s_self_presence, LServer, {Pres, State}, []),
    State2 = State1#{pres_last => Pres1,
		     pres_timestamp => p1_time_compat:timestamp()},
    FromUnavailable = PreviousPres == undefined,
    broadcast_presence_available(State2, Pres1, FromUnavailable);
process_self_presence(State, _Pres) ->
    State.

-spec update_priority(state(), presence()) -> ok.
update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
		  sid := SID, user := U, server := S, resource := R},
		Pres) ->
    Priority = get_priority_from_presence(Pres),
    Info = [{ip, IP}, {conn, Conn}, {auth_module, AuthMod}],
    ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).

-spec broadcast_presence_unavailable(state(), presence()) -> state().
broadcast_presence_unavailable(#{pres_a := PresA} = State, Pres) ->
    JIDs = filter_blocked(State, Pres, PresA),
    route_multiple(State, JIDs, Pres),
    State#{pres_a => ?SETS:new()}.

-spec broadcast_presence_available(state(), presence(), boolean()) -> state().
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF,
			       pres_t := PresT, jid := JID} = State,
			     Pres, _FromUnavailable = true) ->
    Probe = #presence{from = JID, type = probe},
    TJIDs = filter_blocked(State, Probe, PresT),
    FJIDs = filter_blocked(State, Pres, PresF),
    route_multiple(State, TJIDs, Probe),
    route_multiple(State, FJIDs, Pres),
    State#{pres_a => ?SETS:union(PresA, PresF)};
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF} = State,
			     Pres, _FromUnavailable = false) ->
    JIDs = filter_blocked(State, Pres, ?SETS:intersection(PresA, PresF)),
    route_multiple(State, JIDs, Pres),
    State.

-spec check_privacy_then_route(state(), stanza()) -> state().
check_privacy_then_route(#{lang := Lang} = State, Pkt) ->
    case privacy_check_packet(State, Pkt, out) of
        deny ->
            ErrText = <<"Your active privacy list has denied "
			"the routing of this stanza.">>,
	    Err = xmpp:err_not_acceptable(ErrText, Lang),
	    send_error(State, Pkt, Err);
        allow ->
	    ejabberd_router:route(Pkt),
	    State
    end.

-spec privacy_check_packet(state(), stanza(), in | out) -> allow | deny.
privacy_check_packet(#{lserver := LServer} = State, Pkt, Dir) ->
    ejabberd_hooks:run_fold(privacy_check_packet, LServer, allow, [State, Pkt, Dir]).

-spec get_priority_from_presence(presence()) -> integer().
get_priority_from_presence(#presence{priority = Prio}) ->
    case Prio of
	undefined -> 0;
	_ -> Prio
    end.

-spec filter_blocked(state(), presence(), ?SETS:set()) -> [jid()].
filter_blocked(#{jid := From} = State, Pres, LJIDSet) ->
    ?SETS:fold(
       fun(LJID, Acc) ->
	       To = jid:make(LJID),
	       Pkt = xmpp:set_from_to(Pres, From, To),
	       case privacy_check_packet(State, Pkt, out) of
		   allow -> [To|Acc];
		   deny -> Acc
	       end
       end, [], LJIDSet).

-spec route_multiple(state(), [jid()], stanza()) -> ok.
route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
    From = xmpp:get_from(Pkt),
    ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).

-spec resource_conflict_action(binary(), binary(), binary()) ->
				      {accept_resource, binary()} | closenew.
resource_conflict_action(U, S, R) ->
    OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
		    true ->
			ejabberd_config:get_option(
			  {resource_conflict, S}, acceptnew);
		    false ->
			acceptnew
		end,
    Option = case OptionRaw of
		 setresource -> setresource;
		 closeold -> acceptnew; %% ejabberd_sm will close old session
		 closenew -> closenew;
		 acceptnew -> acceptnew
	     end,
    case Option of
	acceptnew -> {accept_resource, R};
	closenew -> closenew;
	setresource ->
	    Rnew = new_uniq_id(),
	    {accept_resource, Rnew}
    end.

-spec bounce_message_queue() -> ok.
bounce_message_queue() ->
    receive {route, Pkt} ->
	    ejabberd_router:route(Pkt),
	    bounce_message_queue()
    after 0 ->
	    ok
    end.

-spec new_uniq_id() -> binary().
new_uniq_id() ->
    iolist_to_binary(
      [randoms:get_string(),
       integer_to_binary(p1_time_compat:unique_integer([positive]))]).

-spec get_conn_type(state()) -> c2s | c2s_tls | c2s_compressed | websocket |
				c2s_compressed_tls | http_bind.
get_conn_type(State) ->
    case xmpp_stream_in:get_transport(State) of
	tcp -> c2s;
	tls -> c2s_tls;
	tcp_zlib -> c2s_compressed;
	tls_zlib -> c2s_compressed_tls;
	http_bind -> http_bind;
	websocket -> websocket
    end.

-spec fix_from_to(xmpp_element(), state()) -> stanza().
fix_from_to(Pkt, #{jid := JID}) when ?is_stanza(Pkt) ->
    #jid{luser = U, lserver = S, lresource = R} = JID,
    case xmpp:get_from(Pkt) of
	undefined ->
	    Pkt;
	From ->
	    From1 = case jid:tolower(From) of
			{U, S, R} -> JID;
			{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
			_ -> From
		    end,
	    xmpp:set_from_to(Pkt, From1, JID)
    end;
fix_from_to(Pkt, _State) ->
    Pkt.

-spec change_shaper(state()) -> ok.
change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
		user := U, server := S, resource := R} = State) ->
    JID = jid:make(U, S, R),
    Shaper = acl:access_matches(ShaperName,
				#{usr => jid:split(JID), ip => IP},
				LServer),
    xmpp_stream_in:change_shaper(State, Shaper).

-spec add_to_pres_a(state(), jid()) -> state().
add_to_pres_a(#{pres_a := PresA, pres_f := PresF} = State, From) ->
    LFrom = jid:tolower(From),
    LBFrom = jid:remove_resource(LFrom),
    case (?SETS):is_element(LFrom, PresA) orelse
	 (?SETS):is_element(LBFrom, PresA) of
	true ->
	    State;
	false ->
	    case (?SETS):is_element(LFrom, PresF) of
		true ->
		    A = (?SETS):add_element(LFrom, PresA),
		    State#{pres_a => A};
		false ->
		    case (?SETS):is_element(LBFrom, PresF) of
			true ->
			    A = (?SETS):add_element(LBFrom, PresA),
			    State#{pres_a => A};
			false ->
			    State
		    end
	    end
    end.

-spec format_reason(state(), term()) -> binary().
format_reason(#{stop_reason := Reason}, _) ->
    xmpp_stream_in:format_error(Reason);
format_reason(_, normal) ->
    <<"unknown reason">>;
format_reason(_, shutdown) ->
    <<"stopped by supervisor">>;
format_reason(_, {shutdown, _}) ->
    <<"stopped by supervisor">>;
format_reason(_, _) ->
    <<"internal server error">>.

transform_listen_option(Opt, Opts) ->
    [Opt|Opts].

-type resource_conflict() :: setresource | closeold | closenew | acceptnew.
-spec opt_type(c2s_certfile) -> fun((binary()) -> binary());
	      (c2s_ciphers) -> fun((binary()) -> binary());
	      (c2s_dhfile) -> fun((binary()) -> binary());
	      (c2s_cafile) -> fun((binary()) -> binary());
	      (c2s_protocol_options) -> fun(([binary()]) -> binary());
	      (c2s_tls_compression) -> fun((boolean()) -> boolean());
	      (resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
	      (disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
	      (atom()) -> [atom()].
opt_type(c2s_certfile) -> fun misc:try_read_file/1;
opt_type(c2s_ciphers) -> fun misc:try_read_file/1;
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
opt_type(c2s_protocol_options) ->
    fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(c2s_tls_compression) ->
    fun (true) -> true;
	(false) -> false
    end;
opt_type(resource_conflict) ->
    fun (setresource) -> setresource;
	(closeold) -> closeold;
	(closenew) -> closenew;
	(acceptnew) -> acceptnew
    end;
opt_type(disable_sasl_mechanisms) ->
    fun (V) when is_list(V) ->
	    lists:map(fun (M) -> str:to_upper(M) end, V);
	(V) -> [str:to_upper(V)]
    end;
opt_type(_) ->
    [c2s_certfile, c2s_ciphers, c2s_cafile,
     c2s_protocol_options, c2s_tls_compression, resource_conflict,
     disable_sasl_mechanisms].

-spec listen_opt_type(access) -> fun((any()) -> any());
		     (shaper) -> fun((any()) -> any());
		     (certfile) -> fun((binary()) -> binary());
		     (ciphers) -> fun((binary()) -> binary());
		     (dhfile) -> fun((binary()) -> binary());
		     (cafile) -> fun((binary()) -> binary());
		     (protocol_options) -> fun(([binary()]) -> binary());
		     (tls_compression) -> fun((boolean()) -> boolean());
		     (tls) -> fun((boolean()) -> boolean());
		     (starttls) -> fun((boolean()) -> boolean());
		     (tls_verify) -> fun((boolean()) -> boolean());
		     (zlib) -> fun((boolean()) -> boolean());
		     (supervisor) -> fun((boolean()) -> boolean());
		     (max_stanza_size) -> fun((timeout()) -> timeout());
		     (max_fsm_queue) -> fun((timeout()) -> timeout());
		     (stream_management) -> fun((boolean()) -> boolean());
		     (atom()) -> [atom()].
listen_opt_type(access) -> fun acl:access_rules_validator/1;
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile) ->
    fun(S) ->
	    ejabberd_pkix:add_certfile(S),
	    iolist_to_binary(S)
    end;
listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
listen_opt_type(cafile) -> opt_type(c2s_cafile);
listen_opt_type(protocol_options) -> opt_type(c2s_protocol_options);
listen_opt_type(tls_compression) -> opt_type(c2s_tls_compression);
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(max_stanza_size) ->
    fun(I) when is_integer(I), I>0 -> I;
       (unlimited) -> infinity;
       (infinity) -> infinity
    end;
listen_opt_type(max_fsm_queue) ->
    fun(I) when is_integer(I), I>0 -> I end;
%% The following hack should be removed in future releases: it is intended
%% for backward compatibility with ejabberd 17.01 or older
listen_opt_type(stream_management) ->
    ?WARNING_MSG("listening option 'stream_management' is deprecated: "
		 "use mod_stream_mgmt module", []),
    fun(B) when is_boolean(B) -> B end;
listen_opt_type(O) ->
    case mod_stream_mgmt:mod_opt_type(O) of
	L when is_list(L) ->
	    [access, shaper, certfile, ciphers, dhfile, cafile,
	     protocol_options, tls, tls_compression, starttls,
	     starttls_required, tls_verify, zlib, max_fsm_queue];
	VFun ->
	    ?WARNING_MSG("listening option '~s' is deprecated: use '~s' "
			 "option from mod_stream_mgmt module", [O, O]),
	    VFun
    end.