Sign 'Record-Route' in order to proxy unauthorized ACKs
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 1 Jun 2014 09:34:51 +0000 (13:34 +0400)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 1 Jun 2014 09:35:14 +0000 (13:35 +0400)
src/mod_sip.erl
src/mod_sip_proxy.erl

index 4b733c62387dde344484ae3ff29dfb2884cd0678..9c50c3241616c5000cbdee33beb9899b8c9a8011 100644 (file)
@@ -80,7 +80,9 @@ response(_Resp, _SIPSock) ->
 request(#sip{method = <<"ACK">>} = Req, SIPSock) ->
     case action(Req, SIPSock) of
        {relay, LServer} ->
-           mod_sip_proxy:route(Req, LServer, []);
+           mod_sip_proxy:route(Req, LServer, [{authenticated, true}]);
+       {proxy_auth, LServer} ->
+           mod_sip_proxy:route(Req, LServer, [{authenticated, false}]);
        _ ->
            error
     end;
@@ -112,20 +114,20 @@ request(Req, SIPSock, TrID, Action) ->
                    ?INFO_MSG("failed to proxy request ~p: ~p", [Req, Err]),
                     Err
             end;
-        {proxy_auth, Host} ->
+        {proxy_auth, LServer} ->
             make_response(
               Req,
               #sip{status = 407,
                    type = response,
                    hdrs = [{'proxy-authenticate',
-                            make_auth_hdr(Host)}]});
-        {auth, Host} ->
+                            make_auth_hdr(LServer)}]});
+        {auth, LServer} ->
             make_response(
               Req,
               #sip{status = 401,
                    type = response,
                    hdrs = [{'www-authenticate',
-                            make_auth_hdr(Host)}]});
+                            make_auth_hdr(LServer)}]});
         deny ->
             make_response(Req, #sip{status = 403,
                                     type = response});
@@ -169,7 +171,7 @@ action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
                                true ->
                                    register;
                                false ->
-                                   {auth, ToURI#uri.host}
+                                   {auth, jlib:nameprep(ToURI#uri.host)}
                            end;
                        false ->
                            deny
@@ -259,8 +261,7 @@ process(Req, _) ->
                            hdrs = [{'allow', allow()}]}).
 
 make_auth_hdr(LServer) ->
-    Realm = jlib:nameprep(LServer),
-    {<<"Digest">>, [{<<"realm">>, esip:quote(Realm)},
+    {<<"Digest">>, [{<<"realm">>, esip:quote(LServer)},
                     {<<"qop">>, esip:quote(<<"auth">>)},
                     {<<"nonce">>, esip:quote(esip:make_hexstr(20))}]}.
 
index f25acbd4e7350bb11505330c6b6324a2a813af8d..2294ba452d68c9ae2fb658fd508da58abfc25451 100644 (file)
@@ -23,6 +23,8 @@
 -include("logger.hrl").
 -include("esip.hrl").
 
+-define(SIGN_LIFETIME, 300). %% in seconds.
+
 -record(state, {host = <<"">>  :: binary(),
                opts = []      :: [{certfile, binary()}],
                orig_trid,
@@ -42,7 +44,33 @@ start_link(LServer, Opts) ->
 route(SIPMsg, _SIPSock, TrID, Pid) ->
     ?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
 
-route(Req, LServer, Opts) ->
+route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
+    case proplists:get_bool(authenticated, Opts) of
+       true ->
+           route_statelessly(Req, LServer, Opts);
+       false ->
+           ConfiguredRoute = get_configured_route(LServer),
+           ConfiguredBareRoute = ConfiguredRoute#uri{user = <<"">>},
+           case esip:get_hdrs('route', Hdrs) of
+               [{_, URI, _}|_] ->
+                   BareURI = URI#uri{user = <<"">>},
+                   case cmp_uri(BareURI, ConfiguredBareRoute) of
+                       true ->
+                           case is_signed_by_me(URI#uri.user, Hdrs) of
+                               true ->
+                                   route_statelessly(Req, LServer, Opts);
+                               false ->
+                                   error
+                           end;
+                       false ->
+                           error
+                   end;
+               [] ->
+                   error
+           end
+    end.
+
+route_statelessly(Req, LServer, Opts) ->
     Req1 = prepare_request(LServer, Req),
     case connect(Req1, add_certfile(LServer, Opts)) of
        {ok, SIPSocketsWithURIs} ->
@@ -253,7 +281,11 @@ add_record_route_and_set_uri(URI, LServer, #sip{hdrs = Hdrs} = Req) ->
     case is_request_within_dialog(Req) of
        false ->
            RR_URI = get_configured_route(LServer),
-           Hdrs1 = [{'record-route', [{<<>>, RR_URI, []}]}|Hdrs],
+           {MSecs, Secs, _} = now(),
+           TS = list_to_binary(integer_to_list(MSecs*1000000 + Secs)),
+           Sign = make_sign(TS, Hdrs),
+           NewRR_URI = RR_URI#uri{user = <<TS/binary, $-, Sign/binary>>},
+           Hdrs1 = [{'record-route', [{<<>>, NewRR_URI, []}]}|Hdrs],
            Req#sip{uri = URI, hdrs = Hdrs1};
        true ->
            Req
@@ -263,6 +295,31 @@ is_request_within_dialog(#sip{hdrs = Hdrs}) ->
     {_, _, Params} = esip:get_hdr('to', Hdrs),
     esip:has_param(<<"tag">>, Params).
 
+make_sign(TS, Hdrs) ->
+    {_, #uri{user = FUser, host = FServer}, FParams} = esip:get_hdr('from', Hdrs),
+    {_, #uri{user = TUser, host = TServer}, _} = esip:get_hdr('to', Hdrs),
+    LFUser = jlib:nodeprep(FUser),
+    LTUser = jlib:nodeprep(TUser),
+    LFServer = jlib:nameprep(FServer),
+    LTServer = jlib:nameprep(TServer),
+    FromTag = esip:get_param(<<"tag">>, FParams),
+    CallID = esip:get_hdr('call-id', Hdrs),
+    SharedKey = ejabberd_config:get_option(shared_key, fun(V) -> V end),
+    p1_sha:sha([SharedKey, LFUser, LFServer, LTUser, LTServer,
+               FromTag, CallID, TS]).
+
+is_signed_by_me(TS_Sign, Hdrs) ->
+    try
+       [TSBin, Sign] = str:tokens(TS_Sign, <<"-">>),
+       TS = list_to_integer(binary_to_list(TSBin)),
+       {MSecs, Secs, _} = now(),
+       NowTS = MSecs*1000000 + Secs,
+       true = (NowTS - TS) =< ?SIGN_LIFETIME,
+       Sign == make_sign(TSBin, Hdrs)
+    catch _:_ ->
+           false
+    end.
+
 get_configured_vias(LServer) ->
     gen_mod:get_module_opt(
       LServer, mod_sip, via,
@@ -323,12 +380,14 @@ cmp_uri(_, _) ->
 
 prepare_request(LServer, #sip{hdrs = Hdrs} = Req) ->
     ConfiguredRoute = get_configured_route(LServer),
+    ConfiguredBareRoute = ConfiguredRoute#uri{user = <<"">>},
     Hdrs1 = lists:flatmap(
              fun({Hdr, HdrList}) when Hdr == 'route';
                                       Hdr == 'record-route' ->
                      case lists:filter(
                             fun({_, URI, _}) ->
-                                    not cmp_uri(URI, ConfiguredRoute)
+                                    BareURI = URI#uri{user = <<"">>},
+                                    not cmp_uri(BareURI, ConfiguredBareRoute)
                             end, HdrList) of
                          [] ->
                              [];