aboutsummaryrefslogblamecommitdiff
path: root/src/mod_sip.erl
blob: c018924fc1d3cd739960ec2f2111d734522d2ae9 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                          
                                   








                                                            








































                                                                        
                            









                                                    

                            
 

                          
















                                                                    

                                                                           































                                                                            


                                                                      










































                                                                                   






                                                                                            

                                                                                      
                                                            






                                                                  






                                                                                    






















































































































                                                                                 
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @copyright (C) 2014, Evgeny Khramtsov
%%% @doc
%%%
%%% @end
%%% Created : 21 Apr 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(mod_sip).

-behaviour(gen_mod).
-behaviour(esip).

%% API
-export([start/2, stop/1, prepare_request/1, make_response/2,
	 add_via/3, at_my_host/1]).

%% esip_callbacks
-export([data_in/2, data_out/2, message_in/2, message_out/2,
	 request/2, request/3, response/2, locate/1]).

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

%%%===================================================================
%%% API
%%%===================================================================
start(_Host, _Opts) ->
    ejabberd:start_app(esip),
    esip:set_config_value(max_server_transactions, 10000),
    esip:set_config_value(max_client_transactions, 10000),
    esip:set_config_value(software, <<"ejabberd ", (?VERSION)/binary>>),
    esip:set_config_value(module, ?MODULE),
    Spec = {mod_sip_registrar, {mod_sip_registrar, start_link, []},
	    transient, 2000, worker, [mod_sip_registrar]},
    TmpSupSpec = {mod_sip_proxy_sup,
		  {ejabberd_tmp_sup, start_link,
		   [mod_sip_proxy_sup, mod_sip_proxy]},
		  permanent, infinity, supervisor, [ejabberd_tmp_sup]},
    supervisor:start_child(ejabberd_sup, Spec),
    supervisor:start_child(ejabberd_sup, TmpSupSpec),
    ok.

stop(_Host) ->
    ok.

data_in(Data, #sip_socket{type = Transport,
                          addr = {MyIP, MyPort},
                          peer = {PeerIP, PeerPort}}) ->
    ?DEBUG(
       "SIP [~p/in] ~s:~p -> ~s:~p:~n~s",
       [Transport, inet_parse:ntoa(PeerIP), PeerPort,
	inet_parse:ntoa(MyIP), MyPort, Data]).

data_out(Data, #sip_socket{type = Transport,
                           addr = {MyIP, MyPort},
                           peer = {PeerIP, PeerPort}}) ->
    ?DEBUG(
       "SIP [~p/out] ~s:~p -> ~s:~p:~n~s",
       [Transport, inet_parse:ntoa(MyIP), MyPort,
	inet_parse:ntoa(PeerIP), PeerPort, Data]).

message_in(#sip{type = request, method = M} = Req, SIPSock)
  when M /= <<"ACK">>, M /= <<"CANCEL">> ->
    case action(Req, SIPSock) of
        {relay, _LServer} ->
            ok;
        Action ->
            request(Req, SIPSock, undefined, Action)
    end;
message_in(_, _) ->
    ok.

message_out(_, _) ->
    ok.

response(_Resp, _SIPSock) ->
    ok.

request(_Req, _SIPSock) ->
    error.

request(Req, SIPSock, TrID) ->
    request(Req, SIPSock, TrID, action(Req, SIPSock)).

request(Req, SIPSock, TrID, Action) ->
    case Action of
        to_me ->
            process(Req, SIPSock);
        register ->
            mod_sip_registrar:request(Req, SIPSock);
        loop ->
            make_response(Req, #sip{status = 483, type = response});
        {unsupported, Require} ->
            make_response(Req, #sip{status = 420,
                                    type = response,
                                    hdrs = [{'unsupported',
                                             Require}]});
        {relay, LServer} ->
            case mod_sip_proxy:start(LServer, add_certfile(LServer, [])) of
                {ok, Pid} ->
                    mod_sip_proxy:route(Req, SIPSock, TrID, Pid),
                    {mod_sip_proxy, route, [Pid]};
                Err ->
		    ?INFO_MSG("failed to proxy request ~p: ~p", [Req, Err]),
                    Err
            end;
        {proxy_auth, Host} ->
            make_response(
              Req,
              #sip{status = 407,
                   type = response,
                   hdrs = [{'proxy-authenticate',
                            make_auth_hdr(Host)}]});
        {auth, Host} ->
            make_response(
              Req,
              #sip{status = 401,
                   type = response,
                   hdrs = [{'www-authenticate',
                            make_auth_hdr(Host)}]});
        deny ->
            make_response(Req, #sip{status = 403,
                                    type = response});
        not_found ->
            make_response(Req, #sip{status = 480,
                                    type = response})
    end.

locate(_SIPMsg) ->
    ok.

%%%===================================================================
%%% Internal functions
%%%===================================================================
action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
            uri = #uri{user = <<"">>} = URI} = Req, SIPSock) ->
    case at_my_host(URI) of
	true ->
	    case esip:get_hdrs('require', Hdrs) of
		[_|_] = Require ->
		    {unsupported, Require};
		_ ->
		    {_, ToURI, _} = esip:get_hdr('to', Hdrs),
		    case at_my_host(ToURI) of
			true ->
			    case check_auth(Req, 'authorization', SIPSock) of
				true ->
				    register;
				false ->
				    {auth, ToURI#uri.host}
			    end;
			false ->
			    deny
		    end
	    end;
	false ->
	    deny
    end;
action(#sip{method = Method, hdrs = Hdrs, type = request} = Req, SIPSock) ->
    case esip:get_hdr('max-forwards', Hdrs) of
        0 when Method == <<"OPTIONS">> ->
            to_me;
        0 ->
            loop;
        _ ->
            case esip:get_hdrs('proxy-require', Hdrs) of
                [_|_] = Require ->
                    {unsupported, Require};
                _ ->
                    {_, ToURI, _} = esip:get_hdr('to', Hdrs),
                    {_, FromURI, _} = esip:get_hdr('from', Hdrs),
		    case at_my_host(FromURI) of
			true ->
			    case check_auth(Req, 'proxy-authorization', SIPSock) of
                                true ->
				    case at_my_host(ToURI) of
					true ->
					    case ToURI#uri.user of
						<<"">> ->
						    to_me;
						_ ->
						    LServer = jlib:nameprep(ToURI#uri.host),
						    {relay, LServer}
					    end;
					false ->
					    LServer = jlib:nameprep(FromURI#uri.host),
					    {relay, LServer}
				    end;
                                false ->
                                    {proxy_auth, FromURI#uri.host}
                            end;
			false ->
			    case at_my_host(ToURI) of
				true ->
				    case ToURI#uri.user of
					<<"">> ->
					    to_me;
					_ ->
					    LServer = jlib:nameprep(ToURI#uri.host),
					    {relay, LServer}
				    end;
				false ->
				    deny
			    end
                    end
            end
    end.

check_auth(#sip{method = <<"CANCEL">>}, _, _SIPSock) ->
    true;
check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) ->
    
    Issuer = case AuthHdr of
                 'authorization' ->
                     to;
                 'proxy-authorization' ->
                     from
             end,
    {_, #uri{user = User, host = Host}, _} = esip:get_hdr(Issuer, Hdrs),
    LUser = jlib:nodeprep(User),
    LServer = jlib:nameprep(Host),
    case lists:filter(
           fun({_, Params}) ->
                   Username = esip:get_param(<<"username">>, Params),
                   Realm = esip:get_param(<<"realm">>, Params),
                   (LUser == esip:unquote(Username))
                       and (LServer == esip:unquote(Realm))
           end, esip:get_hdrs(AuthHdr, Hdrs)) of
        [Auth|_] ->
	    case ejabberd_auth:get_password_s(LUser, LServer) of
		<<"">> ->
		    false;
		Password ->
		    esip:check_auth(Auth, Method, Body, Password)
	    end;
        [] ->
            false
    end.

allow() ->
    [<<"OPTIONS">>, <<"REGISTER">>].

process(#sip{method = <<"OPTIONS">>} = Req, _) ->
    make_response(Req, #sip{type = response, status = 200,
                            hdrs = [{'allow', allow()}]});
process(#sip{method = <<"REGISTER">>} = Req, _) ->
    make_response(Req, #sip{type = response, status = 400});
process(Req, _) ->
    make_response(Req, #sip{type = response, status = 405,
			    hdrs = [{'allow', allow()}]}).

prepare_request(#sip{hdrs = Hdrs1} = Req) ->
    MF = esip:get_hdr('max-forwards', Hdrs1),
    Hdrs2 = esip:set_hdr('max-forwards', MF-1, Hdrs1),
    Hdrs3 = lists:filter(
              fun({'proxy-authorization', {_, Params}}) ->
                      Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
		      not is_my_host(jlib:nameprep(Realm));
                 (_) ->
                      true
              end, Hdrs2),
    Req#sip{hdrs = Hdrs3}.

make_auth_hdr(LServer) ->
    Realm = jlib:nameprep(LServer),
    {<<"Digest">>, [{<<"realm">>, esip:quote(Realm)},
                    {<<"qop">>, esip:quote(<<"auth">>)},
                    {<<"nonce">>, esip:quote(esip:make_hexstr(20))}]}.

make_response(Req, Resp) ->
    esip:make_response(Req, Resp, esip:make_tag()).

at_my_host(#uri{host = Host}) ->
    is_my_host(jlib:nameprep(Host)).

is_my_host(LServer) ->
    gen_mod:is_loaded(LServer, ?MODULE).

add_certfile(LServer, Opts) ->
    case ejabberd_config:get_option({domain_certfile, LServer},
				    fun iolist_to_binary/1) of
	CertFile when is_binary(CertFile), CertFile /= <<"">> ->
	    [{certfile, CertFile}|Opts];
	_ ->
	    Opts
    end.

add_via(#sip_socket{type = Transport}, LServer, #sip{hdrs = Hdrs} = Req) ->
    ConfiguredVias = get_configured_vias(LServer),
    {ViaHost, ViaPort} = proplists:get_value(
			   Transport, ConfiguredVias, {LServer, undefined}),
    ViaTransport = case Transport of
		       tls -> <<"TLS">>;
		       tcp -> <<"TCP">>;
		       udp -> <<"UDP">>
		   end,
    Via = #via{transport = ViaTransport,
	       host = ViaHost,
	       port = ViaPort,
	       params = [{<<"branch">>, esip:make_branch()},
			 {<<"rport">>, <<"">>}]},
    Req#sip{hdrs = [{'via', [Via]}|Hdrs]}.

get_configured_vias(LServer) ->
    gen_mod:get_module_opt(
      LServer, ?MODULE, via,
      fun(L) ->
	      lists:map(
		fun(Opts) ->
			Type = proplists:get_value(type, Opts),
			Host = proplists:get_value(host, Opts),
			Port = proplists:get_value(port, Opts),
			true = (Type == tcp) or (Type == tls) or (Type == udp),
			true = is_binary(Host) and (Host /= <<"">>),
			true = (is_integer(Port)
				and (Port > 0) and (Port < 65536))
			    or (Port == undefined),
			{Type, {Host, Port}}
		end, L)
      end, []).