• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1296

19 Jan 2026 11:25AM UTC coverage: 33.562% (+0.09%) from 33.468%
1296

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

0 of 4 new or added lines in 1 file covered. (0.0%)

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.96
/src/mod_mix.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : mod_mix.erl
3
%%% Author  : Evgeny Khramtsov <ekhramtsov@process-one.net>
4
%%% Created :  2 Mar 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
5
%%%
6
%%%
7
%%% ejabberd, Copyright (C) 2002-2026   ProcessOne
8
%%%
9
%%% This program is free software; you can redistribute it and/or
10
%%% modify it under the terms of the GNU General Public License as
11
%%% published by the Free Software Foundation; either version 2 of the
12
%%% License, or (at your option) any later version.
13
%%%
14
%%% This program is distributed in the hope that it will be useful,
15
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
16
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
%%% General Public License for more details.
18
%%%
19
%%% You should have received a copy of the GNU General Public License along
20
%%% with this program; if not, write to the Free Software Foundation, Inc.,
21
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
%%%
23
%%%----------------------------------------------------------------------
24
-module(mod_mix).
25
-behaviour(gen_mod).
26
-behaviour(gen_server).
27
-protocol({xep, 369, '0.14.1', '16.03', "complete", ""}).
28

29
%% API
30
-export([route/1]).
31
%% gen_mod callbacks
32
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]).
33
-export([mod_doc/0]).
34
%% gen_server callbacks
35
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
36
         terminate/2, code_change/3]).
37
%% Hooks
38
-export([process_disco_info/1,
39
         process_disco_items/1,
40
         process_mix_core/1,
41
         process_mam_query/1,
42
         process_pubsub_query/1]).
43

44
-include_lib("xmpp/include/xmpp.hrl").
45
-include("logger.hrl").
46
-include("translate.hrl").
47

48

49

50
-callback init(binary(), gen_mod:opts()) -> ok | {error, db_failure}.
51
-callback set_channel(binary(), binary(), binary(),
52
                      jid:jid(), boolean(), binary()) ->
53
    ok | {error, db_failure}.
54
-callback get_channels(binary(), binary()) ->
55
    {ok, [binary()]} | {error, db_failure}.
56
-callback get_channel(binary(), binary(), binary()) ->
57
    {ok, {jid(), boolean(), binary()}} |
58
    {error, notfound | db_failure}.
59
-callback set_participant(binary(), binary(), binary(), jid(), binary(), binary()) ->
60
    ok | {error, db_failure}.
61
-callback get_participant(binary(), binary(), binary(), jid()) ->
62
    {ok, {binary(), binary()}} | {error, notfound | db_failure}.
63

64
-record(state, {hosts :: [binary()],
65
                server_host :: binary()}).
66

67
%%%===================================================================
68
%%% API
69
%%%===================================================================
70
start(Host, Opts) ->
UNCOV
71
    gen_mod:start_child(?MODULE, Host, Opts).
4✔
72

73
stop(Host) ->
UNCOV
74
    gen_mod:stop_child(?MODULE, Host).
4✔
75

76
reload(Host, NewOpts, OldOpts) ->
77
    Proc = gen_mod:get_module_proc(Host, ?MODULE),
×
78
    gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}).
×
79

80
depends(_Host, _Opts) ->
UNCOV
81
    [{mod_mam, hard}].
4✔
82

83
mod_opt_type(access_create) ->
UNCOV
84
    econf:acl();
4✔
85
mod_opt_type(name) ->
UNCOV
86
    econf:binary();
4✔
87
mod_opt_type(host) ->
UNCOV
88
    econf:host();
4✔
89
mod_opt_type(hosts) ->
UNCOV
90
    econf:hosts();
4✔
91
mod_opt_type(db_type) ->
UNCOV
92
    econf:db_type(?MODULE).
4✔
93

94
mod_options(Host) ->
UNCOV
95
    [{access_create, all},
4✔
96
     {host, <<"mix.", Host/binary>>},
97
     {hosts, []},
98
     {name, ?T("Channels")},
99
     {db_type, ejabberd_config:default_db(Host, ?MODULE)}].
100

101
mod_doc() ->
102
    #{desc =>
×
103
          [?T("This module is an experimental implementation of "
104
              "https://xmpp.org/extensions/xep-0369.html"
105
              "[XEP-0369: Mediated Information eXchange (MIX)]. "
106
              "It's asserted that "
107
              "the MIX protocol is going to replace the MUC protocol "
108
              "in the future (see _`mod_muc`_)."), "",
109
           ?T("To learn more about how to use that feature, you can refer to "
110
              "our tutorial: _`../../tutorials/mix-010.md|Getting started with MIX`_"), "",
111
           ?T("The module depends on _`mod_mam`_.")],
112
      note => "added in 16.03 and improved in 19.02",
113
      opts =>
114
          [{access_create,
115
            #{value => ?T("AccessName"),
116
              desc =>
117
                  ?T("An access rule to control MIX channels creations. "
118
                     "The default value is 'all'.")}},
119
           {host,
120
            #{desc => ?T("Deprecated. Use 'hosts' instead.")}},
121
           {hosts,
122
            #{value => ?T("[Host, ...]"),
123
              desc =>
124
                  ?T("This option defines the Jabber IDs of the service. "
125
                     "If the 'hosts' option is not specified, the only Jabber ID will "
126
                     "be the hostname of the virtual host with the prefix '\"mix.\"'. "
127
                     "The keyword '@HOST@' is replaced with the real virtual host name.")}},
128
           {name,
129
            #{value => ?T("Name"),
130
              desc =>
131
                  ?T("A name of the service in the Service Discovery. "
132
                     "This will only be displayed by special XMPP clients. "
133
                     "The default value is 'Channels'.")}},
134
           {db_type,
135
            #{value => "mnesia | sql",
136
              desc =>
137
                  ?T("Same as top-level _`default_db`_ option, but applied to this module only.")}}]}.
138

139
-spec route(stanza()) -> ok.
140
route(#iq{} = IQ) ->
141
    ejabberd_router:process_iq(IQ);
×
142
route(#message{type = groupchat, id = ID, lang = Lang,
143
               to = #jid{luser = <<_, _/binary>>}} = Msg) ->
144
    case ID of
×
145
        <<>> ->
146
            Txt = ?T("Attribute 'id' is mandatory for MIX messages"),
×
147
            Err = xmpp:err_bad_request(Txt, Lang),
×
148
            ejabberd_router:route_error(Msg, Err);
×
149
        _ ->
150
            process_mix_message(Msg)
×
151
    end;
152
route(Pkt) ->
153
    ?DEBUG("Dropping packet:~n~ts", [xmpp:pp(Pkt)]).
×
154

155
-spec process_disco_info(iq()) -> iq().
156
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
157
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
158
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
159
process_disco_info(#iq{type = get, to = #jid{luser = <<>>} = To,
160
                       from = _From, lang = Lang,
161
                       sub_els = [#disco_info{node = <<>>}]} = IQ) ->
162
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
×
163
    X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
×
164
                                [ServerHost, ?MODULE, <<"">>, Lang]),
165
    Name = mod_mix_opt:name(ServerHost),
×
166
    Identity = #identity{category = <<"conference">>,
×
167
                         type = <<"mix">>,
168
                         name = translate:translate(Lang, Name)},
169
    Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MIX_CORE_0,
×
170
                ?NS_MIX_CORE_SEARCHABLE_0, ?NS_MIX_CORE_CREATE_CHANNEL_0,
171
                ?NS_MIX_CORE_1, ?NS_MIX_CORE_SEARCHABLE_1,
172
                ?NS_MIX_CORE_CREATE_CHANNEL_1],
173
    xmpp:make_iq_result(
×
174
      IQ, #disco_info{features = Features,
175
                      identities = [Identity],
176
                      xdata = X});
177
process_disco_info(#iq{type = get, to = #jid{luser = <<_, _/binary>>} = To,
178
                       sub_els = [#disco_info{node = Node}]} = IQ)
179
  when Node == <<"mix">>; Node == <<>> ->
180
    {Chan, Host, _} = jid:tolower(To),
×
181
    ServerHost = ejabberd_router:host_of_route(Host),
×
182
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
183
    case Mod:get_channel(ServerHost, Chan, Host) of
×
184
        {ok, _} ->
185
            Identity = #identity{category = <<"conference">>,
×
186
                                 type = <<"mix">>},
187
            Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
×
188
                        ?NS_MIX_CORE_0, ?NS_MIX_CORE_1, ?NS_MAM_2],
189
            xmpp:make_iq_result(
×
190
              IQ, #disco_info{node = Node,
191
                              features = Features,
192
                              identities = [Identity]});
193
        {error, notfound} ->
194
            xmpp:make_error(IQ, no_channel_error(IQ));
×
195
        {error, db_failure} ->
196
            xmpp:make_error(IQ, db_error(IQ))
×
197
    end;
198
process_disco_info(#iq{type = get, sub_els = [#disco_info{node = Node}]} = IQ) ->
199
    xmpp:make_iq_result(IQ, #disco_info{node = Node, features = [?NS_DISCO_INFO]});
×
200
process_disco_info(IQ) ->
201
    xmpp:make_error(IQ, unsupported_error(IQ)).
×
202

203
-spec process_disco_items(iq()) -> iq().
204
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
205
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
206
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
207
process_disco_items(#iq{type = get, to = #jid{luser = <<>>} = To,
208
                        sub_els = [#disco_items{node = <<>>}]} = IQ) ->
209
    Host = To#jid.lserver,
×
210
    ServerHost = ejabberd_router:host_of_route(Host),
×
211
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
212
    case Mod:get_channels(ServerHost, Host) of
×
213
        {ok, Channels} ->
214
            Items = [#disco_item{jid = jid:make(Channel, Host)}
×
215
                     || Channel <- Channels],
×
216
            xmpp:make_iq_result(IQ, #disco_items{items = Items});
×
217
        {error, db_failure} ->
218
            xmpp:make_error(IQ, db_error(IQ))
×
219
    end;
220
process_disco_items(#iq{type = get, to = #jid{luser = <<_, _/binary>>} = To,
221
                        sub_els = [#disco_items{node = Node}]} = IQ)
222
  when Node == <<"mix">>; Node == <<>> ->
223
    {Chan, Host, _} = jid:tolower(To),
×
224
    ServerHost = ejabberd_router:host_of_route(Host),
×
225
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
226
    case Mod:get_channel(ServerHost, Chan, Host) of
×
227
        {ok, _} ->
228
            BTo = jid:remove_resource(To),
×
229
            Items = [#disco_item{jid = BTo, node = N} || N <- known_nodes()],
×
230
            xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items});
×
231
        {error, notfound} ->
232
            xmpp:make_error(IQ, no_channel_error(IQ));
×
233
        {error, db_failure} ->
234
            xmpp:make_error(IQ, db_error(IQ))
×
235
    end;
236
process_disco_items(#iq{type = get, sub_els = [#disco_items{node = Node}]} = IQ) ->
237
    xmpp:make_iq_result(IQ, #disco_items{node = Node});
×
238
process_disco_items(IQ) ->
239
    xmpp:make_error(IQ, unsupported_error(IQ)).
×
240

241
-spec process_mix_core(iq()) -> iq().
242
process_mix_core(#iq{type = set, to = #jid{luser = <<>>},
243
                     sub_els = [#mix_create{}]} = IQ) ->
244
    process_mix_create(IQ);
×
245
process_mix_core(#iq{type = set, to = #jid{luser = <<>>},
246
                     sub_els = [#mix_destroy{}]} = IQ) ->
247
    process_mix_destroy(IQ);
×
248
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
249
                     sub_els = [#mix_join{}]} = IQ) ->
250
    process_mix_join(IQ);
×
251
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
252
                     sub_els = [#mix_leave{}]} = IQ) ->
253
    process_mix_leave(IQ);
×
254
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
255
                     sub_els = [#mix_setnick{}]} = IQ) ->
256
    process_mix_setnick(IQ);
×
257
process_mix_core(IQ) ->
258
    xmpp:make_error(IQ, unsupported_error(IQ)).
×
259

260
process_pubsub_query(#iq{type = get,
261
                         sub_els = [#pubsub{items = #ps_items{node = Node}}]} = IQ)
262
  when Node == ?NS_MIX_NODES_PARTICIPANTS ->
263
    process_participants_list(IQ);
×
264
process_pubsub_query(IQ) ->
265
    xmpp:make_error(IQ, unsupported_error(IQ)).
×
266

267
process_mam_query(#iq{from = From, to = To, type = T,
268
                      sub_els = [#mam_query{}]} = IQ)
269
  when T == get; T == set ->
270
    {Chan, Host, _} = jid:tolower(To),
×
271
    ServerHost = ejabberd_router:host_of_route(Host),
×
272
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
273
    case Mod:get_channel(ServerHost, Chan, Host) of
×
274
        {ok, _} ->
275
            BFrom = jid:remove_resource(From),
×
276
            case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
×
277
                {ok, _} ->
278
                    mod_mam:process_iq(ServerHost, IQ, mix);
×
279
                {error, notfound} ->
280
                    xmpp:make_error(IQ, not_joined_error(IQ));
×
281
                {error, db_failure} ->
282
                    xmpp:make_error(IQ, db_error(IQ))
×
283
            end;
284
        {error, notfound} ->
285
            xmpp:make_error(IQ, no_channel_error(IQ));
×
286
        {error, db_failure} ->
287
            xmpp:make_error(IQ, db_error(IQ))
×
288
    end;
289
process_mam_query(IQ) ->
290
    xmpp:make_error(IQ, unsupported_error(IQ)).
×
291

292
%%%===================================================================
293
%%% gen_server callbacks
294
%%%===================================================================
295
init([Host|_]) ->
UNCOV
296
    process_flag(trap_exit, true),
4✔
UNCOV
297
    Opts = gen_mod:get_module_opts(Host, ?MODULE),
4✔
UNCOV
298
    Mod = gen_mod:db_mod(Opts, ?MODULE),
4✔
UNCOV
299
    MyHosts = gen_mod:get_opt_hosts(Opts),
4✔
UNCOV
300
    case Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)) of
4✔
301
        ok ->
UNCOV
302
            lists:foreach(
4✔
303
              fun(MyHost) ->
UNCOV
304
                      ejabberd_router:register_route(
4✔
305
                        MyHost, Host, {apply, ?MODULE, route}),
UNCOV
306
                      register_iq_handlers(MyHost)
4✔
307
              end, MyHosts),
UNCOV
308
            {ok, #state{hosts = MyHosts, server_host = Host}};
4✔
309
        {error, db_failure} ->
310
            {stop, db_failure}
×
311
    end.
312

313
handle_call(Request, From, State) ->
314
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
315
    {noreply, State}.
×
316

317
handle_cast(Request, State) ->
318
    ?WARNING_MSG("Unexpected cast: ~p", [Request]),
×
319
    {noreply, State}.
×
320

321
handle_info({route, Packet}, State) ->
322
    try route(Packet)
×
323
    catch
324
        Class:Reason:StackTrace ->
325
            ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
×
326
                       [xmpp:pp(Packet),
327
                        misc:format_exception(2, Class, Reason, StackTrace)])
×
328
    end,
329
    {noreply, State};
×
330
handle_info(Info, State) ->
331
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
332
    {noreply, State}.
×
333

334
terminate(_Reason, State) ->
UNCOV
335
    lists:foreach(
4✔
336
      fun(MyHost) ->
UNCOV
337
              unregister_iq_handlers(MyHost),
4✔
UNCOV
338
              ejabberd_router:unregister_route(MyHost)
4✔
339
      end, State#state.hosts).
340

341
code_change(_OldVsn, State, _Extra) ->
342
    {ok, State}.
×
343

344
%%%===================================================================
345
%%% Internal functions
346
%%%===================================================================
347
-spec process_mix_create(iq()) -> iq().
348
process_mix_create(#iq{to = To, from = From,
349
                       sub_els = [#mix_create{channel = Chan, xmlns = XmlNs}]} = IQ) ->
350
    Host = To#jid.lserver,
×
351
    ServerHost = ejabberd_router:host_of_route(Host),
×
352
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
353
    Creator = jid:remove_resource(From),
×
354
    Chan1 = case Chan of
×
355
                <<>> -> p1_rand:get_string();
×
356
                _ -> Chan
×
357
            end,
358
    Ret = case Mod:get_channel(ServerHost, Chan1, Host) of
×
359
              {ok, {#jid{luser = U, lserver = S}, _, _}} ->
360
                  case {From#jid.luser, From#jid.lserver} of
×
361
                      {U, S} -> ok;
×
362
                      _ -> {error, conflict}
×
363
                  end;
364
              {error, notfound} ->
365
                  Key = xmpp_util:hex(p1_rand:bytes(20)),
×
366
                  Mod:set_channel(ServerHost, Chan1, Host,
×
367
                                  Creator, Chan == <<>>, Key);
368
              {error, db_failure} = Err ->
369
                  Err
×
370
          end,
371
    case Ret of
×
372
        ok ->
373
            xmpp:make_iq_result(IQ, #mix_create{channel = Chan1, xmlns = XmlNs});
×
374
        {error, conflict} ->
375
            xmpp:make_error(IQ, channel_exists_error(IQ));
×
376
        {error, db_failure} ->
377
            xmpp:make_error(IQ, db_error(IQ))
×
378
    end.
379

380
-spec process_mix_destroy(iq()) -> iq().
381
process_mix_destroy(#iq{to = To,
382
                        from = #jid{luser = U, lserver = S},
383
                        sub_els = [#mix_destroy{channel = Chan, xmlns = XmlNs}]} = IQ) ->
384
    Host = To#jid.lserver,
×
385
    ServerHost = ejabberd_router:host_of_route(Host),
×
386
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
387
    case Mod:get_channel(ServerHost, Chan, Host) of
×
388
        {ok, {#jid{luser = U, lserver = S}, _, _}} ->
389
            case Mod:del_channel(ServerHost, Chan, Host) of
×
390
                ok ->
391
                    xmpp:make_iq_result(IQ, #mix_destroy{channel = Chan, xmlns = XmlNs});
×
392
                {error, db_failure} ->
393
                    xmpp:make_error(IQ, db_error(IQ))
×
394
            end;
395
        {ok, _} ->
396
            xmpp:make_error(IQ, ownership_error(IQ));
×
397
        {error, notfound} ->
398
            xmpp:make_error(IQ, no_channel_error(IQ));
×
399
        {error, db_failure} ->
400
            xmpp:make_error(IQ, db_error(IQ))
×
401
    end.
402

403
-spec process_mix_join(iq()) -> iq().
404
process_mix_join(#iq{to = To, from = From,
405
                     sub_els = [#mix_join{xmlns = XmlNs} = JoinReq]} = IQ) ->
406
    Chan = To#jid.luser,
×
407
    Host = To#jid.lserver,
×
408
    ServerHost = ejabberd_router:host_of_route(Host),
×
409
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
410
    case Mod:get_channel(ServerHost, Chan, Host) of
×
411
        {ok, {_, _, Key}} ->
412
            ID = make_id(From, Key),
×
413
            Nick = JoinReq#mix_join.nick,
×
414
            BFrom = jid:remove_resource(From),
×
415
            Nodes = filter_nodes(JoinReq#mix_join.subscribe),
×
416
            try
×
417
                ok = Mod:set_participant(ServerHost, Chan, Host, BFrom, ID, Nick),
×
418
                ok = Mod:subscribe(ServerHost, Chan, Host, BFrom, Nodes),
×
419
                notify_participant_joined(Mod, ServerHost, To, From, ID, Nick),
×
420
                xmpp:make_iq_result(IQ, #mix_join{id = ID,
×
421
                                                  subscribe = Nodes,
422
                                                  jid = make_channel_id(To, ID),
423
                                                  nick = Nick,
424
                                                  xmlns = XmlNs})
425
            catch _:{badmatch, {error, db_failure}} ->
426
                    xmpp:make_error(IQ, db_error(IQ))
×
427
            end;
428
        {error, notfound} ->
429
            xmpp:make_error(IQ, no_channel_error(IQ));
×
430
        {error, db_failure} ->
431
            xmpp:make_error(IQ, db_error(IQ))
×
432
    end.
433

434
-spec process_mix_leave(iq()) -> iq().
435
process_mix_leave(#iq{to = To, from = From,
436
                      sub_els = [#mix_leave{xmlns = XmlNs}]} = IQ) ->
437
    {Chan, Host, _} = jid:tolower(To),
×
438
    ServerHost = ejabberd_router:host_of_route(Host),
×
439
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
440
    BFrom = jid:remove_resource(From),
×
441
    case Mod:get_channel(ServerHost, Chan, Host) of
×
442
        {ok, _} ->
443
            case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
×
444
                {ok, {ID, _}} ->
445
                    try
×
446
                        ok = Mod:unsubscribe(ServerHost, Chan, Host, BFrom),
×
447
                        ok = Mod:del_participant(ServerHost, Chan, Host, BFrom),
×
448
                        notify_participant_left(Mod, ServerHost, To, ID),
×
449
                        xmpp:make_iq_result(IQ, #mix_leave{})
×
450
                    catch _:{badmatch, {error, db_failure}} ->
451
                            xmpp:make_error(IQ, db_error(IQ))
×
452
                    end;
453
                {error, notfound} ->
454
                    xmpp:make_iq_result(IQ, #mix_leave{xmlns = XmlNs});
×
455
                {error, db_failure} ->
456
                    xmpp:make_error(IQ, db_error(IQ))
×
457
            end;
458
        {error, notfound} ->
459
            xmpp:make_iq_result(IQ, #mix_leave{});
×
460
        {error, db_failure} ->
461
            xmpp:make_error(IQ, db_error(IQ))
×
462
    end.
463

464
-spec process_mix_setnick(iq()) -> iq().
465
process_mix_setnick(#iq{to = To, from = From,
466
                        sub_els = [#mix_setnick{nick = Nick, xmlns = XmlNs}]} = IQ) ->
467
    {Chan, Host, _} = jid:tolower(To),
×
468
    ServerHost = ejabberd_router:host_of_route(Host),
×
469
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
470
    BFrom = jid:remove_resource(From),
×
471
    case Mod:get_channel(ServerHost, Chan, Host) of
×
472
        {ok, _} ->
473
            case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
×
474
                {ok, {_, Nick}} ->
475
                    xmpp:make_iq_result(IQ, #mix_setnick{nick = Nick, xmlns = XmlNs});
×
476
                {ok, {ID, _}} ->
477
                    case Mod:set_participant(ServerHost, Chan, Host, BFrom, ID, Nick) of
×
478
                        ok ->
479
                            notify_participant_joined(Mod, ServerHost, To, From, ID, Nick),
×
480
                            xmpp:make_iq_result(IQ, #mix_setnick{nick = Nick, xmlns = XmlNs});
×
481
                        {error, db_failure} ->
482
                            xmpp:make_error(IQ, db_error(IQ))
×
483
                    end;
484
                {error, notfound} ->
485
                    xmpp:make_error(IQ, not_joined_error(IQ));
×
486
                {error, db_failure} ->
487
                    xmpp:make_error(IQ, db_error(IQ))
×
488
            end;
489
        {error, notfound} ->
490
            xmpp:make_error(IQ, no_channel_error(IQ));
×
491
        {error, db_failure} ->
492
            xmpp:make_error(IQ, db_error(IQ))
×
493
    end.
494

495
-spec process_mix_message(message()) -> ok.
496
process_mix_message(#message{from = From, to = To,
497
                             id = SubmissionID} = Msg) ->
498
    {Chan, Host, _} = jid:tolower(To),
×
499
    {FUser, FServer, _} = jid:tolower(From),
×
500
    ServerHost = ejabberd_router:host_of_route(Host),
×
501
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
502
    case Mod:get_channel(ServerHost, Chan, Host) of
×
503
        {ok, _} ->
504
            BFrom = jid:remove_resource(From),
×
505
            case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
×
506
                {ok, {StableID, Nick}} ->
507
                    MamID = mod_mam:make_id(),
×
508
                    Msg1 = xmpp:set_subtag(
×
509
                             Msg#message{from = jid:replace_resource(To, StableID),
510
                                         to = undefined,
511
                                         id = integer_to_binary(MamID)},
512
                             #mix{jid = BFrom, nick = Nick}),
513
                    Msg2 = xmpp:put_meta(Msg1, stanza_id, MamID),
×
514
                    case ejabberd_hooks:run_fold(
×
515
                           store_mam_message, ServerHost, Msg2,
516
                           [Chan, Host, BFrom, Nick, groupchat, recv]) of
517
                        #message{} ->
518
                            multicast(Mod, ServerHost, Chan, Host,
×
519
                                      ?NS_MIX_NODES_MESSAGES,
520
                                      fun(#jid{luser = U, lserver = S})
521
                                            when U == FUser, S == FServer ->
522
                                              xmpp:set_subtag(
×
523
                                                Msg1, #mix{jid = BFrom,
524
                                                           nick = Nick,
525
                                                           submission_id = SubmissionID});
526
                                         (_) ->
527
                                              Msg1
×
528
                                      end);
529
                        _ ->
530
                            ok
×
531
                    end;
532
                {error, notfound} ->
533
                    ejabberd_router:route_error(Msg, not_joined_error(Msg));
×
534
                {error, db_failure} ->
535
                    ejabberd_router:route_error(Msg, db_error(Msg))
×
536
            end;
537
        {error, notfound} ->
538
            ejabberd_router:route_error(Msg, no_channel_error(Msg));
×
539
        {error, db_failure} ->
540
            ejabberd_router:route_error(Msg, db_error(Msg))
×
541
    end.
542

543
-spec process_participants_list(iq()) -> iq().
544
process_participants_list(#iq{from = From, to = To} = IQ) ->
545
    {Chan, Host, _} = jid:tolower(To),
×
546
    ServerHost = ejabberd_router:host_of_route(Host),
×
547
    Mod = gen_mod:db_mod(ServerHost, ?MODULE),
×
548
    case Mod:get_channel(ServerHost, Chan, Host) of
×
549
        {ok, _} ->
550
            BFrom = jid:remove_resource(From),
×
551
            case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
×
552
                {ok, _} ->
553
                    case Mod:get_participants(ServerHost, Chan, Host) of
×
554
                        {ok, Participants} ->
555
                            Items = items_of_participants(Participants),
×
556
                            Pubsub = #pubsub{
×
557
                                        items = #ps_items{
558
                                                   node = ?NS_MIX_NODES_PARTICIPANTS,
559
                                                   items = Items}},
560
                            xmpp:make_iq_result(IQ, Pubsub);
×
561
                        {error, db_failure} ->
562
                            xmpp:make_error(IQ, db_error(IQ))
×
563
                    end;
564
                {error, notfound} ->
565
                    xmpp:make_error(IQ, not_joined_error(IQ));
×
566
                {error, db_failure} ->
567
                    xmpp:make_error(IQ, db_error(IQ))
×
568
            end;
569
        {error, notfound} ->
570
            xmpp:make_error(IQ, no_channel_error(IQ));
×
571
        {error, db_failure} ->
572
            xmpp:make_error(IQ, db_error(IQ))
×
573
    end.
574

575
-spec items_of_participants([{jid(), binary(), binary()}]) -> [ps_item()].
576
items_of_participants(Participants) ->
577
    lists:map(
×
578
      fun({JID, ID, Nick}) ->
579
              Participant = #mix_participant{jid = JID, nick = Nick},
×
580
              #ps_item{id = ID,
×
581
                       sub_els = [xmpp:encode(Participant)]}
582
      end, Participants).
583

584
-spec known_nodes() -> [binary()].
585
known_nodes() ->
586
    [?NS_MIX_NODES_MESSAGES,
×
587
     ?NS_MIX_NODES_PARTICIPANTS].
588

589
-spec filter_nodes([binary()]) -> [binary()].
590
filter_nodes(Nodes) ->
591
    KnownNodes = known_nodes(),
×
592
    [Node || KnownNode <- KnownNodes, Node <- Nodes, KnownNode == Node].
×
593

594
-spec multicast(module(), binary(), binary(),
595
                binary(), binary(), fun((jid()) -> message())) -> ok.
596
multicast(Mod, LServer, Chan, Service, Node, F) ->
597
    case Mod:get_subscribed(LServer, Chan, Service, Node) of
×
598
        {ok, Subscribers} ->
599
            lists:foreach(
×
600
              fun(To) ->
601
                      Msg = xmpp:set_to(F(To), To),
×
602
                      ejabberd_router:route(Msg)
×
603
              end, Subscribers);
604
        {error, db_failure} ->
605
            ok
×
606
    end.
607

608
-spec notify_participant_joined(module(), binary(),
609
                                jid(), jid(), binary(), binary()) -> ok.
610
notify_participant_joined(Mod, LServer, To, From, ID, Nick) ->
611
    {Chan, Host, _} = jid:tolower(To),
×
612
    Participant = #mix_participant{jid = jid:remove_resource(From),
×
613
                                   nick = Nick},
614
    Item = #ps_item{id = ID,
×
615
                    sub_els = [xmpp:encode(Participant)]},
616
    Items = #ps_items{node = ?NS_MIX_NODES_PARTICIPANTS,
×
617
                      items = [Item]},
618
    Event = #ps_event{items = Items},
×
619
    Msg = #message{from = jid:remove_resource(To),
×
620
                   id = p1_rand:get_string(),
621
                   sub_els = [Event]},
622
    multicast(Mod, LServer, Chan, Host,
×
623
              ?NS_MIX_NODES_PARTICIPANTS,
624
              fun(_) -> Msg end).
×
625

626
-spec notify_participant_left(module(), binary(), jid(), binary()) -> ok.
627
notify_participant_left(Mod, LServer, To, ID) ->
628
    {Chan, Host, _} = jid:tolower(To),
×
629
    Items = #ps_items{node = ?NS_MIX_NODES_PARTICIPANTS,
×
630
                      retract = [ID]},
631
    Event = #ps_event{items = Items},
×
632
    Msg = #message{from = jid:remove_resource(To),
×
633
                   id = p1_rand:get_string(),
634
                   sub_els = [Event]},
635
    multicast(Mod, LServer, Chan, Host, ?NS_MIX_NODES_PARTICIPANTS,
×
636
             fun(_) -> Msg end).
×
637

638
-spec make_id(jid(), binary()) -> binary().
639
make_id(JID, Key) ->
640
    Data = jid:encode(jid:tolower(jid:remove_resource(JID))),
×
641
    xmpp_util:hex(crypto:macN(hmac, sha256, Data, Key, 10)).
×
642

643
-spec make_channel_id(jid(), binary()) -> jid().
644
make_channel_id(JID, ID) ->
645
        {U, S, R} = jid:split(JID),
×
646
        jid:make(<<ID/binary, $#, U/binary>>, S, R).
×
647

648
%%%===================================================================
649
%%% Error generators
650
%%%===================================================================
651
-spec db_error(stanza()) -> stanza_error().
652
db_error(Pkt) ->
653
    Txt = ?T("Database failure"),
×
654
    xmpp:err_internal_server_error(Txt, xmpp:get_lang(Pkt)).
×
655

656
-spec channel_exists_error(stanza()) -> stanza_error().
657
channel_exists_error(Pkt) ->
658
    Txt = ?T("Channel already exists"),
×
659
    xmpp:err_conflict(Txt, xmpp:get_lang(Pkt)).
×
660

661
-spec no_channel_error(stanza()) -> stanza_error().
662
no_channel_error(Pkt) ->
663
    Txt = ?T("Channel does not exist"),
×
664
    xmpp:err_item_not_found(Txt, xmpp:get_lang(Pkt)).
×
665

666
-spec not_joined_error(stanza()) -> stanza_error().
667
not_joined_error(Pkt) ->
668
    Txt = ?T("You are not joined to the channel"),
×
669
    xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
×
670

671
-spec unsupported_error(stanza()) -> stanza_error().
672
unsupported_error(Pkt) ->
673
    Txt = ?T("No module is handling this query"),
×
674
    xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)).
×
675

676
-spec ownership_error(stanza()) -> stanza_error().
677
ownership_error(Pkt) ->
678
    Txt = ?T("Owner privileges required"),
×
679
    xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
×
680

681
%%%===================================================================
682
%%% IQ handlers
683
%%%===================================================================
684
-spec register_iq_handlers(binary()) -> ok.
685
register_iq_handlers(Host) ->
UNCOV
686
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
4✔
687
                                  ?MODULE, process_disco_info),
UNCOV
688
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
4✔
689
                                  ?MODULE, process_disco_items),
UNCOV
690
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_0,
4✔
691
                                  ?MODULE, process_mix_core),
UNCOV
692
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_1,
4✔
693
                                  ?MODULE, process_mix_core),
UNCOV
694
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO,
4✔
695
                                  ?MODULE, process_disco_info),
UNCOV
696
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS,
4✔
697
                                  ?MODULE, process_disco_items),
UNCOV
698
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_0,
4✔
699
                                  ?MODULE, process_mix_core),
UNCOV
700
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_1,
4✔
701
                                  ?MODULE, process_mix_core),
UNCOV
702
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB,
4✔
703
                                  ?MODULE, process_pubsub_query),
UNCOV
704
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MAM_2,
4✔
705
                                  ?MODULE, process_mam_query).
706

707
-spec unregister_iq_handlers(binary()) -> ok.
708
unregister_iq_handlers(Host) ->
UNCOV
709
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
4✔
UNCOV
710
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
4✔
UNCOV
711
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_0),
4✔
UNCOV
712
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_1),
4✔
UNCOV
713
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
4✔
UNCOV
714
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
4✔
UNCOV
715
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_0),
4✔
UNCOV
716
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_1),
4✔
UNCOV
717
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
4✔
UNCOV
718
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_2).
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc