241 lines
7.6 KiB
Erlang
241 lines
7.6 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : mod_deny_omemo.erl
|
|
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
|
%%% Purpose : Prevent OMEMO sessions from being established
|
|
%%% Created : 18 Mar 2018 by Holger Weiss <holger@zedat.fu-berlin.de>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2018-2020 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(mod_deny_omemo).
|
|
-author('holger@zedat.fu-berlin.de').
|
|
-behavior(gen_mod).
|
|
|
|
%% gen_mod callbacks.
|
|
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2, mod_options/1,
|
|
mod_doc/0]).
|
|
|
|
%% ejabberd_hooks callbacks.
|
|
-export([user_receive_packet/1, user_send_packet/1]).
|
|
|
|
-include("logger.hrl").
|
|
-include_lib("xmpp/include/xmpp.hrl").
|
|
|
|
-define(NS_AXOLOTL, "eu.siacs.conversations.axolotl").
|
|
-define(DEVICELIST_NODE, ?NS_AXOLOTL ".devicelist").
|
|
|
|
-type c2s_state() :: ejabberd_c2s:state().
|
|
-type hook_result() :: {stanza() | drop, c2s_state()} |
|
|
{stop, {drop, c2s_state()}}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% gen_mod callbacks.
|
|
%%--------------------------------------------------------------------
|
|
-spec start(binary(), gen_mod:opts()) -> ok.
|
|
start(Host, _Opts) ->
|
|
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
|
user_send_packet, 50),
|
|
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
|
user_receive_packet, 50).
|
|
|
|
-spec stop(binary()) -> ok.
|
|
stop(Host) ->
|
|
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
|
user_send_packet, 50),
|
|
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE,
|
|
user_receive_packet, 50).
|
|
|
|
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
|
reload(_Host, _NewOpts, _OldOpts) ->
|
|
ok.
|
|
|
|
-spec mod_opt_type(atom()) -> econf:validator().
|
|
mod_opt_type(access) ->
|
|
econf:acl().
|
|
|
|
-spec mod_options(binary()) -> [{atom(), any()}].
|
|
mod_options(_Host) ->
|
|
[{access, omemo}].
|
|
|
|
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
|
depends(_Host, _Opts) ->
|
|
[{mod_pubsub, hard}].
|
|
|
|
mod_doc() ->
|
|
#{}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% ejabberd_hooks callbacks.
|
|
%%--------------------------------------------------------------------
|
|
-spec user_send_packet({stanza() | drop, c2s_state()}) -> hook_result().
|
|
user_send_packet({#message{}, _C2SState} = Acc) ->
|
|
maybe_reject_msg(Acc);
|
|
user_send_packet({#iq{type = set,
|
|
from = #jid{luser = LUser, lserver = LServer},
|
|
to = #jid{luser = LUser, lserver = LServer,
|
|
lresource = <<>>},
|
|
sub_els = [SubEl]}, _C2SState} = Acc) ->
|
|
try xmpp:decode(SubEl) of
|
|
#pubsub{publish = Publish} ->
|
|
maybe_reject_iq(Publish, Acc);
|
|
_ ->
|
|
Acc
|
|
catch _:{xmpp_codec, _Reason} ->
|
|
Acc
|
|
end;
|
|
user_send_packet({#iq{type = get,
|
|
from = #jid{luser = FromU, lserver = FromS},
|
|
to = #jid{luser = ToU, lserver = ToS,
|
|
lresource = <<>>},
|
|
sub_els = [SubEl]}, _C2SState} = Acc)
|
|
when FromU /= ToU;
|
|
FromS /= ToS ->
|
|
try xmpp:decode(SubEl) of
|
|
#pubsub{items = Items} ->
|
|
maybe_reject_iq(Items, Acc);
|
|
_ ->
|
|
Acc
|
|
catch _:{xmpp_codec, _Reason} ->
|
|
Acc
|
|
end;
|
|
user_send_packet(Acc) ->
|
|
Acc.
|
|
|
|
-spec user_receive_packet({stanza() | drop, c2s_state()}) -> hook_result().
|
|
user_receive_packet({#message{} = Msg,
|
|
#{lserver := LServer, jid := JID} = C2SState} = Acc) ->
|
|
case xmpp:get_subtag(Msg, #ps_event{}) of
|
|
#ps_event{items = Items} ->
|
|
Access = gen_mod:get_module_opt(LServer, ?MODULE, access),
|
|
case acl:match_rule(LServer, Access, JID) of
|
|
allow ->
|
|
Acc;
|
|
deny ->
|
|
case find_omemo_nodes(Items) of
|
|
[] ->
|
|
Acc;
|
|
_Nodes ->
|
|
?DEBUG("Dropping devicelist update sent to ~s",
|
|
[jid:encode(JID)]),
|
|
{stop, {drop, C2SState}}
|
|
end
|
|
end;
|
|
_ ->
|
|
maybe_reject_msg(Acc)
|
|
end;
|
|
user_receive_packet(Acc) ->
|
|
Acc.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Internal functions.
|
|
%%--------------------------------------------------------------------
|
|
-spec maybe_reject_msg({message(), c2s_state()})
|
|
-> {message(), c2s_state()} | {stop, {drop, c2s_state()}}.
|
|
maybe_reject_msg({#message{lang = Lang} = Msg,
|
|
#{lserver := LServer, jid := JID} = C2SState} = Acc) ->
|
|
Access = gen_mod:get_module_opt(LServer, ?MODULE, access),
|
|
case acl:match_rule(LServer, Access, JID) of
|
|
allow ->
|
|
Acc;
|
|
deny ->
|
|
case is_omemo_msg(Msg) of
|
|
true ->
|
|
?DEBUG("Rejecting message from ~s", [jid:encode(JID)]),
|
|
bounce_error(Msg, Lang),
|
|
{stop, {drop, C2SState}};
|
|
false ->
|
|
Acc
|
|
end
|
|
end.
|
|
|
|
-spec maybe_reject_iq(ps_publish() | ps_items(), {iq(), c2s_state()})
|
|
-> {iq(), c2s_state()} | {stop, {drop, c2s_state()}}.
|
|
maybe_reject_iq(El, {#iq{type = Type, lang = Lang} = IQ,
|
|
#{lserver := LServer, jid := JID} = C2SState} = Acc) ->
|
|
Access = gen_mod:get_module_opt(LServer, ?MODULE, access),
|
|
case acl:match_rule(LServer, Access, JID) of
|
|
allow ->
|
|
Acc;
|
|
deny ->
|
|
case find_omemo_nodes(El) of
|
|
[] ->
|
|
Acc;
|
|
Nodes ->
|
|
?DEBUG("Rejecting IQ ~s of ~s", [Type, jid:encode(JID)]),
|
|
case Type of
|
|
set -> delete_nodes(JID, Nodes);
|
|
get -> ok
|
|
end,
|
|
bounce_error(IQ, Lang),
|
|
{stop, {drop, C2SState}}
|
|
end
|
|
end.
|
|
|
|
-spec find_omemo_nodes(ps_publish() | ps_items()) -> [binary()].
|
|
find_omemo_nodes(#ps_items{node = Node, items = Items}) ->
|
|
find_omemo_nodes(Node, Items);
|
|
find_omemo_nodes(#ps_publish{node = Node, items = Items}) ->
|
|
find_omemo_nodes(Node, Items);
|
|
find_omemo_nodes(_) ->
|
|
[].
|
|
|
|
-spec find_omemo_nodes(binary(), [ps_item()]) -> [binary()].
|
|
find_omemo_nodes(<<?DEVICELIST_NODE>> = Node, [Item]) ->
|
|
[Node | find_bundle_nodes(Item)];
|
|
find_omemo_nodes(<<?DEVICELIST_NODE>> = Node, []) ->
|
|
[Node];
|
|
find_omemo_nodes(_Node, _Item) ->
|
|
[].
|
|
|
|
-spec find_bundle_nodes(ps_item()) -> [binary()].
|
|
find_bundle_nodes(#ps_item{sub_els = [#xmlel{name = <<"list">>,
|
|
attrs = [{<<"xmlns">>,
|
|
<<?NS_AXOLOTL>>}],
|
|
children = Devices}]}) ->
|
|
[<<?NS_AXOLOTL, ".bundles:", ID/binary>>
|
|
|| #xmlel{name = <<"device">>, attrs = [{<<"id">>, ID}]} <- Devices];
|
|
find_bundle_nodes(_Item) ->
|
|
[].
|
|
|
|
-spec delete_nodes(jid(), [binary()]) -> ok.
|
|
delete_nodes(JID, Nodes) ->
|
|
LJID = jid:remove_resource(jid:tolower(JID)),
|
|
?DEBUG("Removing the following nodes of ~s: ~p",
|
|
[jid:encode(LJID), Nodes]),
|
|
lists:foreach(fun(Node) ->
|
|
mod_pubsub:delete_node(LJID, Node, JID)
|
|
end, Nodes).
|
|
|
|
-spec is_omemo_msg(message()) -> boolean().
|
|
is_omemo_msg(#message{type = error}) ->
|
|
false;
|
|
is_omemo_msg(#message{sub_els = SubEls}) ->
|
|
case lists:keyfind(<<"encrypted">>, #xmlel.name, SubEls) of
|
|
#xmlel{attrs = Attrs} ->
|
|
lists:member({<<"xmlns">>, <<?NS_AXOLOTL>>}, Attrs);
|
|
false ->
|
|
false
|
|
end.
|
|
|
|
-spec bounce_error(stanza(), binary()) -> ok.
|
|
bounce_error(Pkt, Lang) ->
|
|
Txt = <<"OMEMO is disabled">>,
|
|
Err = xmpp:err_policy_violation(Txt, Lang),
|
|
ejabberd_router:route_error(Pkt, Err).
|