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

processone / ejabberd / 1258

12 Dec 2025 03:57PM UTC coverage: 33.638% (-0.006%) from 33.644%
1258

push

github

badlop
Container: Apply commit a22c88a

ejabberdctl.template: Show meaningful error when ERL_DIST_PORT is in use

15554 of 46240 relevant lines covered (33.64%)

1078.28 hits per line

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

34.47
/src/mod_announce.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_announce.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Manage announce messages
5
%%% Created : 11 Aug 2003 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2025   ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
%%; definitions
27

28
%%% Implements a small subset of XEP-0133: Service Administration
29
%%% Version 1.1 (2005-08-19)
30

31
-module(mod_announce).
32
-author('alexey@process-one.net').
33

34
-behaviour(gen_server).
35
-behaviour(gen_mod).
36

37
-export([start/2, stop/1, reload/3, export/1, import_info/0,
38
         import_start/2, import/5, announce/1, send_motd/1, disco_identity/5,
39
         disco_features/5, disco_items/5, depends/2,
40
         send_announcement_to_all/3, announce_commands/4, mod_doc/0,
41
         announce_items/4, mod_opt_type/1, mod_options/1, clean_cache/1]).
42
-export([init/1, handle_call/3, handle_cast/2,
43
         handle_info/2, terminate/2, code_change/3]).
44
-export([announce_all/1,
45
         announce_all_hosts_all/1,
46
         announce_online/1,
47
         announce_all_hosts_online/1,
48
         announce_motd/1,
49
         announce_all_hosts_motd/1,
50
         announce_motd_update/1,
51
         announce_all_hosts_motd_update/1,
52
         announce_motd_delete/1,
53
         announce_all_hosts_motd_delete/1]).
54
%% ejabberd_commands
55
-export([announce_send_all/3,
56
         announce_send_online/3,
57
         get_stored_motd/1,
58
         announce_motd_set_online/3,
59
         announce_motd_update/3,
60
         announce_motd_delete_api/1,
61
         get_commands_spec/0]).
62
%% WebAdmin
63
-export([webadmin_menu/3, webadmin_page/3]).
64

65
-import(ejabberd_web_admin, [make_command/4, make_command/2]).
66

67
-include("logger.hrl").
68
-include_lib("xmpp/include/xmpp.hrl").
69
-include("ejabberd_commands.hrl").
70
-include("ejabberd_http.hrl").
71
-include("ejabberd_web_admin.hrl").
72
-include("mod_announce.hrl").
73
-include("translate.hrl").
74

75
-callback init(binary(), gen_mod:opts()) -> any().
76
-callback import(binary(), binary(), [binary()]) -> ok.
77
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> ok | {error, any()}.
78
-callback set_motd(binary(), xmlel()) -> ok | {error, any()}.
79
-callback delete_motd(binary()) -> ok | {error, any()}.
80
-callback get_motd(binary()) -> {ok, xmlel()} | error | {error, any()}.
81
-callback is_motd_user(binary(), binary()) -> {ok, boolean()} | {error, any()}.
82
-callback set_motd_user(binary(), binary()) -> ok | {error, any()}.
83
-callback use_cache(binary()) -> boolean().
84
-callback cache_nodes(binary()) -> [node()].
85

86
-optional_callbacks([use_cache/1, cache_nodes/1]).
87

88
-record(state, {host :: binary()}).
89

90
-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
91
                         <<"admin">>, <<Sub>>]).
92
-define(MOTD_CACHE, motd_cache).
93

94
tokenize(Node) -> str:tokens(Node, <<"/#">>).
1,906✔
95

96
%%====================================================================
97
%%; gen_mod callbacks
98

99
start(Host, Opts) ->
100
    case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
98✔
101
        false ->
102
            ejabberd_commands:register_commands(?MODULE, get_commands_spec());
11✔
103
        true ->
104
            ok
87✔
105
    end,
106
    ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, webadmin_menu, 50),
98✔
107
    ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, webadmin_page, 50),
98✔
108
    gen_mod:start_child(?MODULE, Host, Opts).
98✔
109

110
stop(Host) ->
111
    case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
98✔
112
        false ->
113
            ejabberd_commands:unregister_commands(get_commands_spec());
11✔
114
        true ->
115
            ok
87✔
116
    end,
117
    ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, webadmin_menu, 50),
98✔
118
    ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, webadmin_page, 50),
98✔
119
    gen_mod:stop_child(?MODULE, Host).
98✔
120

121
reload(Host, NewOpts, OldOpts) ->
122
    NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
×
123
    OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
×
124
    if NewMod /= OldMod ->
×
125
            NewMod:init(Host, NewOpts);
×
126
       true ->
127
            ok
×
128
    end,
129
    init_cache(NewMod, Host, NewOpts).
×
130

131
depends(_Host, _Opts) ->
132
    [{mod_adhoc, hard}].
116✔
133

134
%%====================================================================
135
%%; gen_server callbacks
136

137
init([Host|_]) ->
138
    process_flag(trap_exit, true),
98✔
139
    Opts = gen_mod:get_module_opts(Host, ?MODULE),
98✔
140
    Mod = gen_mod:db_mod(Opts, ?MODULE),
98✔
141
    Mod:init(Host, Opts),
98✔
142
    init_cache(Mod, Host, Opts),
98✔
143
    ejabberd_hooks:add(local_send_to_resource_hook, Host,
98✔
144
                       ?MODULE, announce, 50),
145
    ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
98✔
146
    ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
98✔
147
    ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 50),
98✔
148
    ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, announce_items, 50),
98✔
149
    ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
98✔
150
    ejabberd_hooks:add(c2s_self_presence, Host,
98✔
151
                       ?MODULE, send_motd, 50),
152
    {ok, #state{host = Host}}.
98✔
153

154
handle_call(Request, From, State) ->
155
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
156
    {noreply, State}.
×
157

158
handle_cast({F, #message{from = From, to = To} = Pkt}, State) when is_atom(F) ->
159
    LServer = To#jid.lserver,
8✔
160
    Host = case F of
8✔
161
               announce_all -> LServer;
×
162
               announce_all_hosts_all -> global;
×
163
               announce_online -> LServer;
×
164
               announce_all_hosts_online -> global;
×
165
               announce_motd -> LServer;
8✔
166
               announce_all_hosts_motd -> global;
×
167
               announce_motd_update -> LServer;
×
168
               announce_all_hosts_motd_update -> global;
×
169
               announce_motd_delete -> LServer;
×
170
               announce_all_hosts_motd_delete -> global
×
171
           end,
172
    Access = get_access(Host),
8✔
173
    case acl:match_rule(Host, Access, From) of
8✔
174
        deny ->
175
            route_forbidden_error(Pkt);
×
176
        allow ->
177
            ?MODULE:F(Pkt)
8✔
178
    end,
179
    {noreply, State};
8✔
180
handle_cast(Msg, State) ->
181
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
182
    {noreply, State}.
×
183

184

185
handle_info(Info, State) ->
186
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
187
    {noreply, State}.
×
188

189
terminate(_Reason, #state{host = Host}) ->
190
    ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
98✔
191
    ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, announce_items, 50),
98✔
192
    ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50),
98✔
193
    ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
98✔
194
    ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50),
98✔
195
    ejabberd_hooks:delete(local_send_to_resource_hook, Host, ?MODULE, announce, 50),
98✔
196
    ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, send_motd, 50).
98✔
197

198
code_change(_OldVsn, State, _Extra) ->
199
    {ok, State}.
×
200

201
%%====================================================================
202
%%; Announcing via messages to a custom resource
203

204
-spec announce(stanza()) -> ok | stop.
205
announce(#message{to = #jid{luser = <<>>} = To} = Packet) ->
206
    Proc = gen_mod:get_module_proc(To#jid.lserver, ?MODULE),
83✔
207
    Res = case To#jid.lresource of
83✔
208
              <<"announce/all">> ->
209
                  gen_server:cast(Proc, {announce_all, Packet});
×
210
              <<"announce/all-hosts/all">> ->
211
                  gen_server:cast(Proc, {announce_all_hosts_all, Packet});
×
212
              <<"announce/online">> ->
213
                  gen_server:cast(Proc, {announce_online, Packet});
×
214
              <<"announce/all-hosts/online">> ->
215
                  gen_server:cast(Proc, {announce_all_hosts_online, Packet});
×
216
              <<"announce/motd">> ->
217
                  gen_server:cast(Proc, {announce_motd, Packet});
8✔
218
              <<"announce/all-hosts/motd">> ->
219
                  gen_server:cast(Proc, {announce_all_hosts_motd, Packet});
×
220
              <<"announce/motd/update">> ->
221
                  gen_server:cast(Proc, {announce_motd_update, Packet});
×
222
              <<"announce/all-hosts/motd/update">> ->
223
                  gen_server:cast(Proc, {announce_all_hosts_motd_update, Packet});
×
224
              <<"announce/motd/delete">> ->
225
                  gen_server:cast(Proc, {announce_motd_delete, Packet});
×
226
              <<"announce/all-hosts/motd/delete">> ->
227
                  gen_server:cast(Proc, {announce_all_hosts_motd_delete, Packet});
×
228
              _ ->
229
                  undefined
75✔
230
          end,
231
    case Res of
83✔
232
        ok -> stop;
8✔
233
        _ -> ok
75✔
234
    end;
235
announce(_Packet) ->
236
    ok.
66✔
237

238
%%====================================================================
239
%%; Announcing via ad-hoc commands
240

241
%%====================================================================
242
%%; -- disco identity
243

244
-define(INFO_COMMAND(Lang, Node),
245
        [#identity{category = <<"automation">>,
246
                   type = <<"command-node">>,
247
                   name = get_title(Lang, Node)}]).
248

249
disco_identity(Acc, _From, _To, Node, Lang) ->
250
    LNode = tokenize(Node),
1,882✔
251
    case LNode of
1,882✔
252
        ?NS_ADMINL("announce") ->
253
            ?INFO_COMMAND(Lang, Node);
×
254
        ?NS_ADMINL("announce-allhosts") ->
255
            ?INFO_COMMAND(Lang, Node);
×
256
        ?NS_ADMINL("announce-all") ->
257
            ?INFO_COMMAND(Lang, Node);
×
258
        ?NS_ADMINL("announce-all-allhosts") ->
259
            ?INFO_COMMAND(Lang, Node);
×
260
        ?NS_ADMINL("set-motd") ->
261
            ?INFO_COMMAND(Lang, Node);
×
262
        ?NS_ADMINL("set-motd-allhosts") ->
263
            ?INFO_COMMAND(Lang, Node);
×
264
        ?NS_ADMINL("edit-motd") ->
265
            ?INFO_COMMAND(Lang, Node);
×
266
        ?NS_ADMINL("edit-motd-allhosts") ->
267
            ?INFO_COMMAND(Lang, Node);
×
268
        ?NS_ADMINL("delete-motd") ->
269
            ?INFO_COMMAND(Lang, Node);
×
270
        ?NS_ADMINL("delete-motd-allhosts") ->
271
            ?INFO_COMMAND(Lang, Node);
×
272
        _ ->
273
            Acc
1,882✔
274
    end.
275

276
%%====================================================================
277
%%; -- disco features
278

279
-define(INFO_RESULT(Allow, Feats, Lang),
280
        case Allow of
281
            deny ->
282
                {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
283
            allow ->
284
                {result, Feats}
285
        end).
286

287
disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) ->
288
    case gen_mod:is_loaded(LServer, mod_adhoc) of
×
289
        false ->
290
            Acc;
×
291
        _ ->
292
            Access1 = get_access(LServer),
×
293
            Access2 = get_access(global),
×
294
            case {acl:match_rule(LServer, Access1, From),
×
295
                  acl:match_rule(global, Access2, From)} of
296
                {deny, deny} ->
297
                    Txt = ?T("Access denied by service policy"),
×
298
                    {error, xmpp:err_forbidden(Txt, Lang)};
×
299
                _ ->
300
                    {result, []}
×
301
            end
302
    end;
303

304
disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
305
    case gen_mod:is_loaded(LServer, mod_adhoc) of
1,882✔
306
        false ->
307
            Acc;
×
308
        _ ->
309
            Access = get_access(LServer),
1,882✔
310
            Allow = acl:match_rule(LServer, Access, From),
1,882✔
311
            AccessGlobal = get_access(global),
1,882✔
312
            AllowGlobal = acl:match_rule(global, AccessGlobal, From),
1,882✔
313
            case Node of
1,882✔
314
                ?NS_ADMIN_ANNOUNCE ->
315
                    ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
316
                ?NS_ADMIN_ANNOUNCE_ALL ->
317
                    ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
318
                ?NS_ADMIN_SET_MOTD ->
319
                    ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
320
                ?NS_ADMIN_EDIT_MOTD ->
321
                    ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
322
                ?NS_ADMIN_DELETE_MOTD ->
323
                    ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
324
                ?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
325
                    ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
×
326
                ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
327
                    ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
×
328
                ?NS_ADMIN_SET_MOTD_ALLHOSTS ->
329
                    ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
×
330
                ?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
331
                    ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
×
332
                ?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
333
                    ?INFO_RESULT(AllowGlobal, [?NS_COMMANDS], Lang);
×
334
                _ ->
335
                    Acc
1,882✔
336
            end
337
    end.
338

339
%%====================================================================
340
%%; -- disco items
341

342
-define(NODE_TO_ITEM(Lang, Server, Node),
343
        #disco_item{jid = jid:make(Server),
344
                    node = Node,
345
                    name = get_title(Lang, Node)}).
346

347
-define(ITEMS_RESULT(Allow, Items, Lang),
348
        case Allow of
349
            deny ->
350
                {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
351
            allow ->
352
                {result, Items}
353
        end).
354

355
disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, <<"">>, Lang) ->
356
    case gen_mod:is_loaded(LServer, mod_adhoc) of
1✔
357
        false ->
358
            Acc;
×
359
        _ ->
360
            Access1 = get_access(LServer),
1✔
361
            Access2 = get_access(global),
1✔
362
            case {acl:match_rule(LServer, Access1, From),
1✔
363
                  acl:match_rule(global, Access2, From)} of
364
                {deny, deny} ->
365
                    Acc;
1✔
366
                _ ->
367
                    Items = case Acc of
×
368
                                {result, I} -> I;
×
369
                                _ -> []
×
370
                            end,
371
                    Nodes = [?NODE_TO_ITEM(Lang, Server, <<"announce">>)],
×
372
                    {result, Items ++ Nodes}
×
373
            end
374
    end;
375

376
disco_items(Acc, From, #jid{lserver = LServer} = To, <<"announce">>, Lang) ->
377
    case gen_mod:is_loaded(LServer, mod_adhoc) of
×
378
        false ->
379
            Acc;
×
380
        _ ->
381
            announce_items(Acc, From, To, Lang)
×
382
    end;
383

384
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
385
    case gen_mod:is_loaded(LServer, mod_adhoc) of
1✔
386
        false ->
387
            Acc;
×
388
        _ ->
389
            Access = get_access(LServer),
1✔
390
            Allow = acl:match_rule(LServer, Access, From),
1✔
391
            AccessGlobal = get_access(global),
1✔
392
            AllowGlobal = acl:match_rule(global, AccessGlobal, From),
1✔
393
            case Node of
1✔
394
                ?NS_ADMIN_ANNOUNCE ->
395
                    ?ITEMS_RESULT(Allow, [], Lang);
×
396
                ?NS_ADMIN_ANNOUNCE_ALL ->
397
                    ?ITEMS_RESULT(Allow, [], Lang);
×
398
                ?NS_ADMIN_SET_MOTD ->
399
                    ?ITEMS_RESULT(Allow, [], Lang);
×
400
                ?NS_ADMIN_EDIT_MOTD ->
401
                    ?ITEMS_RESULT(Allow, [], Lang);
×
402
                ?NS_ADMIN_DELETE_MOTD ->
403
                    ?ITEMS_RESULT(Allow, [], Lang);
×
404
                ?NS_ADMIN_ANNOUNCE_ALLHOSTS ->
405
                    ?ITEMS_RESULT(AllowGlobal, [], Lang);
×
406
                ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS ->
407
                    ?ITEMS_RESULT(AllowGlobal, [], Lang);
×
408
                ?NS_ADMIN_SET_MOTD_ALLHOSTS ->
409
                    ?ITEMS_RESULT(AllowGlobal, [], Lang);
×
410
                ?NS_ADMIN_EDIT_MOTD_ALLHOSTS ->
411
                    ?ITEMS_RESULT(AllowGlobal, [], Lang);
×
412
                ?NS_ADMIN_DELETE_MOTD_ALLHOSTS ->
413
                    ?ITEMS_RESULT(AllowGlobal, [], Lang);
×
414
                _ ->
415
                    Acc
1✔
416
            end
417
    end.
418

419
%%====================================================================
420
%%; -- announce items
421

422
-spec announce_items(empty | {error, stanza_error()} | {result, [disco_item()]},
423
                        jid(), jid(), binary()) -> {error, stanza_error()} |
424
                                                   {result, [disco_item()]} |
425
                                                   empty.
426
announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
427
    Access1 = get_access(LServer),
×
428
    Nodes1 = case acl:match_rule(LServer, Access1, From) of
×
429
                 allow ->
430
                     [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE),
×
431
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALL),
432
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_SET_MOTD),
433
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_EDIT_MOTD),
434
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_DELETE_MOTD)];
435
                 deny ->
436
                     []
×
437
             end,
438
    Access2 = get_access(global),
×
439
    Nodes2 = case acl:match_rule(global, Access2, From) of
×
440
                 allow ->
441
                     [?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALLHOSTS),
×
442
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS),
443
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_SET_MOTD_ALLHOSTS),
444
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS),
445
                      ?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS)];
446
                 deny ->
447
                     []
×
448
             end,
449
    case {Nodes1, Nodes2} of
×
450
        {[], []} ->
451
            Acc;
×
452
        _ ->
453
            Items = case Acc of
×
454
                        {result, I} -> I;
×
455
                        _ -> []
×
456
                    end,
457
            {result, Items ++ Nodes1 ++ Nodes2}
×
458
    end.
459

460
%%====================================================================
461
%%; -- commands
462

463
commands_result(Allow, From, To, Request) ->
464
    case Allow of
×
465
        deny ->
466
            Lang = Request#adhoc_command.lang,
×
467
            {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
×
468
        allow ->
469
            announce_commands(From, To, Request)
×
470
    end.
471

472
-spec announce_commands(empty | adhoc_command(), jid(), jid(), adhoc_command()) ->
473
                               adhoc_command() | {error, stanza_error()}.
474
announce_commands(Acc, From, #jid{lserver = LServer} = To,
475
                  #adhoc_command{node = Node} = Request) ->
476
    LNode = tokenize(Node),
24✔
477
    F = fun() ->
24✔
478
                Access = get_access(global),
×
479
                Allow = acl:match_rule(global, Access, From),
×
480
                commands_result(Allow, From, To, Request)
×
481
        end,
482
    R = case LNode of
24✔
483
            ?NS_ADMINL("announce-allhosts") -> F();
×
484
            ?NS_ADMINL("announce-all-allhosts") -> F();
×
485
            ?NS_ADMINL("set-motd-allhosts") -> F();
×
486
            ?NS_ADMINL("edit-motd-allhosts") -> F();
×
487
            ?NS_ADMINL("delete-motd-allhosts") -> F();
×
488
            _ ->
489
                Access = get_access(LServer),
24✔
490
                Allow = acl:match_rule(LServer, Access, From),
24✔
491
                case LNode of
24✔
492
                    ?NS_ADMINL("announce") ->
493
                        commands_result(Allow, From, To, Request);
×
494
                    ?NS_ADMINL("announce-all") ->
495
                        commands_result(Allow, From, To, Request);
×
496
                    ?NS_ADMINL("set-motd") ->
497
                        commands_result(Allow, From, To, Request);
×
498
                    ?NS_ADMINL("edit-motd") ->
499
                        commands_result(Allow, From, To, Request);
×
500
                    ?NS_ADMINL("delete-motd") ->
501
                        commands_result(Allow, From, To, Request);
×
502
                    _ ->
503
                        unknown
24✔
504
                end
505
        end,
506
    case R of
24✔
507
        unknown -> Acc;
24✔
508
        _ -> {stop, R}
×
509
    end.
510

511
%%-------------------------------------------------------------------------
512

513
announce_commands(From, To,
514
                  #adhoc_command{lang = Lang,
515
                                 node = Node,
516
                                 sid = SID,
517
                                 xdata = XData,
518
                                 action = Action} = Request) ->
519
    if Action == cancel ->
×
520
            %% User cancels request
521
            #adhoc_command{status = canceled, lang = Lang, node = Node,
×
522
                           sid = SID};
523
       XData == undefined andalso Action == execute ->
524
            %% User requests form
525
            Form = generate_adhoc_form(Lang, Node, To#jid.lserver),
×
526
            xmpp_util:make_adhoc_response(
×
527
              #adhoc_command{status = executing, lang = Lang, node = Node,
528
                             sid = SID, xdata = Form});
529
       XData /= undefined andalso (Action == execute orelse Action == complete) ->
530
            case handle_adhoc_form(From, To, Request) of
×
531
                ok ->
532
                    #adhoc_command{lang = Lang, node = Node, sid = SID,
×
533
                                   status = completed};
534
                {error, _} = Err ->
535
                    Err
×
536
            end;
537
       true ->
538
            Txt = ?T("Unexpected action"),
×
539
            {error, xmpp:err_bad_request(Txt, Lang)}
×
540
    end.
541

542
vvaluel(Val) ->
543
    case Val of
×
544
        <<>> -> [];
×
545
        _ -> [Val]
×
546
    end.
547

548
%%====================================================================
549
%%; -- adhoc form
550

551
generate_adhoc_form(Lang, Node, ServerHost) ->
552
    LNode = tokenize(Node),
×
553
    {OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd"))
×
554
                               or (LNode == ?NS_ADMINL("edit-motd-allhosts")) ->
555
                                    get_stored_motd(ServerHost);
×
556
                               true ->
557
                                    {<<>>, <<>>}
×
558
                            end,
559
    Fs = if (LNode == ?NS_ADMINL("delete-motd"))
×
560
            or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
561
                 [#xdata_field{type = boolean,
×
562
                               var = <<"confirm">>,
563
                               label = translate:translate(
564
                                         Lang, ?T("Really delete message of the day?")),
565
                               values = [<<"true">>]}];
566
            true ->
567
                 [#xdata_field{type = 'text-single',
×
568
                               var = <<"subject">>,
569
                               label = translate:translate(Lang, ?T("Subject")),
570
                               values = vvaluel(OldSubject)},
571
                  #xdata_field{type = 'text-multi',
572
                               var = <<"body">>,
573
                               label = translate:translate(Lang, ?T("Message body")),
574
                               values = vvaluel(OldBody)}]
575
         end,
576
    #xdata{type = form,
×
577
           title = get_title(Lang, Node),
578
           fields = [#xdata_field{type = hidden, var = <<"FORM_TYPE">>,
579
                                  values = [?NS_ADMIN]}|Fs]}.
580

581
join_lines([]) ->
582
    <<>>;
×
583
join_lines(Lines) ->
584
    join_lines(Lines, []).
×
585
join_lines([Line|Lines], Acc) ->
586
    join_lines(Lines, [<<"\n">>,Line|Acc]);
×
587
join_lines([], Acc) ->
588
    %% Remove last newline
589
    iolist_to_binary(lists:reverse(tl(Acc))).
×
590

591
handle_adhoc_form(From, #jid{lserver = LServer} = To,
592
                  #adhoc_command{lang = Lang, node = Node,
593
                                 xdata = XData}) ->
594
    Confirm = case xmpp_util:get_xdata_values(<<"confirm">>, XData) of
×
595
                  [<<"true">>] -> true;
×
596
                  [<<"1">>] -> true;
×
597
                  _ -> false
×
598
              end,
599
    Subject = join_lines(xmpp_util:get_xdata_values(<<"subject">>, XData)),
×
600
    Body = join_lines(xmpp_util:get_xdata_values(<<"body">>, XData)),
×
601
    Packet = #message{from = From,
×
602
                      to = To,
603
                      type = headline,
604
                      body = xmpp:mk_text(Body),
605
                      subject = xmpp:mk_text(Subject)},
606
    Proc = gen_mod:get_module_proc(LServer, ?MODULE),
×
607
    case {Node, Body} of
×
608
        {?NS_ADMIN_DELETE_MOTD, _} ->
609
            if        Confirm ->
×
610
                    gen_server:cast(Proc, {announce_motd_delete, Packet});
×
611
                true ->
612
                    ok
×
613
            end;
614
        {?NS_ADMIN_DELETE_MOTD_ALLHOSTS, _} ->
615
            if        Confirm ->
×
616
                    gen_server:cast(Proc, {announce_all_hosts_motd_delete, Packet});
×
617
                true ->
618
                    ok
×
619
            end;
620
        {_, <<>>} ->
621
            %% An announce message with no body is definitely an operator error.
622
            %% Throw an error and give him/her a chance to send message again.
623
            {error, xmpp:err_not_acceptable(
×
624
                      ?T("No body provided for announce message"), Lang)};
625
        %% Now send the packet to ?MODULE.
626
        %% We don't use direct announce_* functions because it
627
        %% leads to large delay in response and <iq/> queries processing
628
        {?NS_ADMIN_ANNOUNCE, _} ->
629
            gen_server:cast(Proc, {announce_online, Packet});
×
630
        {?NS_ADMIN_ANNOUNCE_ALLHOSTS, _} ->
631
            gen_server:cast(Proc, {announce_all_hosts_online, Packet});
×
632
        {?NS_ADMIN_ANNOUNCE_ALL, _} ->
633
            gen_server:cast(Proc, {announce_all, Packet});
×
634
        {?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS, _} ->
635
            gen_server:cast(Proc, {announce_all_hosts_all, Packet});
×
636
        {?NS_ADMIN_SET_MOTD, _} ->
637
            gen_server:cast(Proc, {announce_motd, Packet});
×
638
        {?NS_ADMIN_SET_MOTD_ALLHOSTS, _} ->
639
            gen_server:cast(Proc, {announce_all_hosts_motd, Packet});
×
640
        {?NS_ADMIN_EDIT_MOTD, _} ->
641
            gen_server:cast(Proc, {announce_motd_update, Packet});
×
642
        {?NS_ADMIN_EDIT_MOTD_ALLHOSTS, _} ->
643
            gen_server:cast(Proc, {announce_all_hosts_motd_update, Packet});
×
644
        Junk ->
645
            %% This can't happen, as we haven't registered any other
646
            %% command nodes.
647
            ?ERROR_MSG("Unexpected node/body = ~p", [Junk]),
×
648
            {error, xmpp:err_internal_server_error()}
×
649
    end.
650

651
get_title(Lang, <<"announce">>) ->
652
    translate:translate(Lang, ?T("Announcements"));
×
653
get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL) ->
654
    translate:translate(Lang, ?T("Send announcement to all users"));
×
655
get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALL_ALLHOSTS) ->
656
    translate:translate(Lang, ?T("Send announcement to all users on all hosts"));
×
657
get_title(Lang, ?NS_ADMIN_ANNOUNCE) ->
658
    translate:translate(Lang, ?T("Send announcement to all online users"));
×
659
get_title(Lang, ?NS_ADMIN_ANNOUNCE_ALLHOSTS) ->
660
    translate:translate(Lang, ?T("Send announcement to all online users on all hosts"));
×
661
get_title(Lang, ?NS_ADMIN_SET_MOTD) ->
662
    translate:translate(Lang, ?T("Set message of the day and send to online users"));
×
663
get_title(Lang, ?NS_ADMIN_SET_MOTD_ALLHOSTS) ->
664
    translate:translate(Lang, ?T("Set message of the day on all hosts and send to online users"));
×
665
get_title(Lang, ?NS_ADMIN_EDIT_MOTD) ->
666
    translate:translate(Lang, ?T("Update message of the day (don't send)"));
×
667
get_title(Lang, ?NS_ADMIN_EDIT_MOTD_ALLHOSTS) ->
668
    translate:translate(Lang, ?T("Update message of the day on all hosts (don't send)"));
×
669
get_title(Lang, ?NS_ADMIN_DELETE_MOTD) ->
670
    translate:translate(Lang, ?T("Delete message of the day"));
×
671
get_title(Lang, ?NS_ADMIN_DELETE_MOTD_ALLHOSTS) ->
672
    translate:translate(Lang, ?T("Delete message of the day on all hosts")).
×
673

674
%%====================================================================
675
%%; -- ad-hoc commands implementation
676

677
announce_all(#message{to = To} = Packet) ->
678
    Local = jid:make(To#jid.server),
×
679
    lists:foreach(
×
680
      fun({User, Server}) ->
681
              Dest = jid:make(User, Server),
×
682
              ejabberd_router:route(
×
683
                xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
684
      end, ejabberd_auth:get_users(To#jid.lserver)).
685

686
announce_all_hosts_all(#message{to = To} = Packet) ->
687
    Local = jid:make(To#jid.server),
×
688
    lists:foreach(
×
689
      fun({User, Server}) ->
690
              Dest = jid:make(User, Server),
×
691
              ejabberd_router:route(
×
692
                xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
693
      end, ejabberd_auth:get_users()).
694

695
announce_online(#message{to = To} = Packet) ->
696
    announce_online1(ejabberd_sm:get_vh_session_list(To#jid.lserver),
×
697
                     To#jid.server, Packet).
698

699
announce_all_hosts_online(#message{to = To} = Packet) ->
700
    announce_online1(ejabberd_sm:dirty_get_sessions_list(),
×
701
                     To#jid.server, Packet).
702

703
announce_online1(Sessions, Server, Packet) ->
704
    Local = jid:make(Server),
8✔
705
    lists:foreach(
8✔
706
      fun({U, S, R}) ->
707
              Dest = jid:make(U, S, R),
16✔
708
              ejabberd_router:route(xmpp:set_from_to(Packet, Local, Dest))
16✔
709
      end, Sessions).
710

711
announce_motd(#message{to = To} = Packet) ->
712
    announce_motd(To#jid.lserver, Packet).
8✔
713

714
announce_all_hosts_motd(Packet) ->
715
    Hosts = ejabberd_option:hosts(),
×
716
    [announce_motd(Host, Packet) || Host <- Hosts].
×
717

718
announce_motd(Host, Packet) ->
719
    LServer = jid:nameprep(Host),
8✔
720
    announce_motd_update(LServer, Packet),
8✔
721
    Sessions = ejabberd_sm:get_vh_session_list(LServer),
8✔
722
    announce_online1(Sessions, LServer, Packet),
8✔
723
    Mod = gen_mod:db_mod(LServer, ?MODULE),
8✔
724
    Mod:set_motd_users(LServer, Sessions).
8✔
725

726
announce_motd_update(#message{to = To} = Packet) ->
727
    announce_motd_update(To#jid.lserver, Packet).
×
728

729
announce_all_hosts_motd_update(Packet) ->
730
    Hosts = ejabberd_option:hosts(),
×
731
    [announce_motd_update(Host, Packet) || Host <- Hosts].
×
732

733
announce_motd_update(LServer, Packet) ->
734
    Mod = gen_mod:db_mod(LServer, ?MODULE),
8✔
735
    delete_motd(Mod, LServer),
8✔
736
    set_motd(Mod, LServer, xmpp:encode(Packet)).
8✔
737

738
announce_motd_delete(#message{to = To}) ->
739
    LServer = To#jid.lserver,
×
740
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
741
    delete_motd(Mod, LServer).
×
742

743
announce_all_hosts_motd_delete(_Packet) ->
744
    lists:foreach(
×
745
      fun(Host) ->
746
              Mod = gen_mod:db_mod(Host, ?MODULE),
×
747
              delete_motd(Mod, Host)
×
748
      end, ejabberd_option:hosts()).
749

750
-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}.
751
send_motd({_, #{pres_last := _}} = Acc) ->
752
    %% This is just a presence update, nothing to do
753
    Acc;
24✔
754
send_motd({#presence{type = available},
755
           #{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc)
756
  when LUser /= <<>> ->
757
    Mod = gen_mod:db_mod(LServer, ?MODULE),
240✔
758
    case get_motd(Mod, LServer) of
240✔
759
        {ok, Packet} ->
760
            CodecOpts = ejabberd_config:codec_options(),
8✔
761
            try xmpp:decode(Packet, ?NS_CLIENT, CodecOpts) of
8✔
762
                Msg ->
763
                    case is_motd_user(Mod, LUser, LServer) of
8✔
764
                        false ->
765
                            Local = jid:make(LServer),
×
766
                            ejabberd_router:route(
×
767
                              xmpp:set_from_to(Msg, Local, JID)),
768
                            set_motd_user(Mod, LUser, LServer);
×
769
                        true ->
770
                            ok
8✔
771
                    end
772
            catch _:{xmpp_codec, Why} ->
773
                    ?ERROR_MSG("Failed to decode motd packet ~p: ~ts",
×
774
                               [Packet, xmpp:format_error(Why)])
×
775
            end;
776
        _ ->
777
            ok
232✔
778
    end,
779
    Acc;
240✔
780
send_motd(Acc) ->
781
    Acc.
×
782

783
-spec get_motd(module(), binary()) -> {ok, xmlel()} | error | {error, any()}.
784
get_motd(Mod, LServer) ->
785
    case use_cache(Mod, LServer) of
240✔
786
        true ->
787
            ets_cache:lookup(
240✔
788
              ?MOTD_CACHE, {<<"">>, LServer},
789
              fun() -> Mod:get_motd(LServer) end);
9✔
790
        false ->
791
            Mod:get_motd(LServer)
×
792
    end.
793

794
-spec set_motd(module(), binary(), xmlel()) -> any().
795
set_motd(Mod, LServer, XML) ->
796
    case use_cache(Mod, LServer) of
8✔
797
        true ->
798
            ets_cache:update(
8✔
799
              ?MOTD_CACHE, {<<"">>, LServer}, {ok, XML},
800
              fun() -> Mod:set_motd(LServer, XML) end,
8✔
801
              cache_nodes(Mod, LServer));
802
        false ->
803
            Mod:set_motd(LServer, XML)
×
804
    end.
805

806
-spec is_motd_user(module(), binary(), binary()) -> boolean().
807
is_motd_user(Mod, LUser, LServer) ->
808
    Res = case use_cache(Mod, LServer) of
8✔
809
              true ->
810
                  ets_cache:lookup(
8✔
811
                    ?MOTD_CACHE, {LUser, LServer},
812
                    fun() -> Mod:is_motd_user(LUser, LServer) end);
8✔
813
              false ->
814
                  Mod:is_motd_user(LUser, LServer)
×
815
          end,
816
    case Res of
8✔
817
        {ok, Bool} -> Bool;
8✔
818
        _ -> false
×
819
    end.
820

821
-spec set_motd_user(module(), binary(), binary()) -> any().
822
set_motd_user(Mod, LUser, LServer) ->
823
    case use_cache(Mod, LServer) of
×
824
        true ->
825
            ets_cache:update(
×
826
              ?MOTD_CACHE, {LUser, LServer}, {ok, true},
827
              fun() -> Mod:set_motd_user(LUser, LServer) end,
×
828
              cache_nodes(Mod, LServer));
829
        false ->
830
            Mod:set_motd_user(LUser, LServer)
×
831
    end.
832

833
-spec delete_motd(module(), binary()) -> ok | {error, any()}.
834
delete_motd(Mod, LServer) ->
835
    case Mod:delete_motd(LServer) of
8✔
836
        ok ->
837
            case use_cache(Mod, LServer) of
8✔
838
                true ->
839
                    ejabberd_cluster:eval_everywhere(
8✔
840
                      ?MODULE, clean_cache, [LServer]);
841
                false ->
842
                    ok
×
843
            end;
844
        Err ->
845
            Err
×
846
    end.
847

848
get_stored_motd(LServer) ->
849
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
850
    case get_motd(Mod, LServer) of
×
851
        {ok, Packet} ->
852
            CodecOpts = ejabberd_config:codec_options(),
×
853
            try xmpp:decode(Packet, ?NS_CLIENT, CodecOpts) of
×
854
                #message{body = Body, subject = Subject} ->
855
                    {xmpp:get_text(Subject), xmpp:get_text(Body)}
×
856
            catch _:{xmpp_codec, Why} ->
857
                    ?ERROR_MSG("Failed to decode motd packet ~p: ~ts",
×
858
                               [Packet, xmpp:format_error(Why)])
×
859
            end;
860
        _ ->
861
            {<<>>, <<>>}
×
862
    end.
863

864
%% This function is similar to others, but doesn't perform any ACL verification
865
send_announcement_to_all(Host, SubjectS, BodyS) ->
866
    Packet = #message{type = headline,
×
867
                      body = xmpp:mk_text(BodyS),
868
                      subject = xmpp:mk_text(SubjectS)},
869
    Sessions = ejabberd_sm:dirty_get_sessions_list(),
×
870
    Local = jid:make(Host),
×
871
    lists:foreach(
×
872
      fun({U, S, R}) ->
873
              Dest = jid:make(U, S, R),
×
874
              ejabberd_router:route(
×
875
                xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
876
      end, Sessions).
877

878
-spec get_access(global | binary()) -> atom().
879

880
get_access(Host) ->
881
    mod_announce_opt:access(Host).
3,800✔
882

883
-spec add_store_hint(stanza()) -> stanza().
884
add_store_hint(El) ->
885
    xmpp:set_subtag(El, #hint{type = store}).
×
886

887
-spec route_forbidden_error(stanza()) -> ok.
888
route_forbidden_error(Packet) ->
889
    Lang = xmpp:get_lang(Packet),
×
890
    Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang),
×
891
    ejabberd_router:route_error(Packet, Err).
×
892

893

894
%%====================================================================
895
%%; Announcing via API commands
896

897
%% @format-begin
898

899
-spec get_commands_spec() -> [ejabberd_commands()].
900
get_commands_spec() ->
901
    HostAll = "If HOST is `all`, send to all hosts. ",
22✔
902
    BodyNew = "You can use ' \\n ' in the message body to write a newline.",
22✔
903
    [#ejabberd_commands{name = announce_send_all,
22✔
904
                        tags = [announce],
905
                        desc = "Send announcement to all users",
906
                        longdesc = HostAll ++ BodyNew,
907
                        module = ?MODULE,
908
                        function = announce_send_all,
909
                        note = "added in 25.10",
910
                        args = [{host, binary}, {subject, binary}, {body, binary}],
911
                        result = {res, rescode}},
912
     #ejabberd_commands{name = announce_send_online,
913
                        tags = [announce],
914
                        desc = "Send announcement to online users",
915
                        longdesc = HostAll ++ BodyNew,
916
                        module = ?MODULE,
917
                        function = announce_send_online,
918
                        note = "added in 25.10",
919
                        args = [{host, binary}, {subject, binary}, {body, binary}],
920
                        result = {res, rescode}},
921
     #ejabberd_commands{name = announce_motd_get,
922
                        tags = [announce],
923
                        desc = "Get Message Of The Day",
924
                        longdesc = BodyNew,
925
                        module = ?MODULE,
926
                        function = get_stored_motd,
927
                        note = "added in 25.10",
928
                        args = [{host, binary}],
929
                        result = {motd, {tuple, [{subject, string}, {body, string}]}}},
930
     #ejabberd_commands{name = announce_motd_set_online,
931
                        tags = [announce],
932
                        desc = "Set Message Of The Day and send to online users",
933
                        longdesc = HostAll ++ BodyNew,
934
                        module = ?MODULE,
935
                        function = announce_motd_set_online,
936
                        note = "added in 25.10",
937
                        args = [{host, binary}, {subject, binary}, {body, binary}],
938
                        result = {res, rescode}},
939
     #ejabberd_commands{name = announce_motd_update,
940
                        tags = [announce],
941
                        desc = "Update Message Of The Day",
942
                        longdesc = HostAll ++ BodyNew,
943
                        module = ?MODULE,
944
                        function = announce_motd_update,
945
                        note = "added in 25.10",
946
                        args = [{host, binary}, {subject, binary}, {body, binary}],
947
                        result = {res, rescode}},
948
     #ejabberd_commands{name = announce_motd_delete,
949
                        tags = [announce],
950
                        desc = "Delete Message Of The Day",
951
                        longdesc = HostAll,
952
                        module = ?MODULE,
953
                        function = announce_motd_delete_api,
954
                        note = "added in 25.10",
955
                        args = [{host, binary}],
956
                        result = {res, rescode}}].
957

958
announce_send_all(<<"all">>, Subject, Body) ->
959
    Host = hd(ejabberd_option:hosts()),
×
960
    announce_all_hosts_all(make_packet(Host, Body, Subject));
×
961
announce_send_all(Host, Subject, Body) ->
962
    announce_all(make_packet(Host, Body, Subject)).
×
963

964
announce_send_online(<<"all">>, Subject, Body) ->
965
    Host = hd(ejabberd_option:hosts()),
×
966
    announce_all_hosts_online(make_packet(Host, Body, Subject));
×
967
announce_send_online(Host, Subject, Body) ->
968
    announce_online(make_packet(Host, Body, Subject)).
×
969

970
announce_motd_set_online(<<"all">>, Subject, Body) ->
971
    Host = hd(ejabberd_option:hosts()),
×
972
    announce_all_hosts_motd(make_packet(Host, Body, Subject));
×
973
announce_motd_set_online(Host, Subject, Body) ->
974
    announce_motd(make_packet(Host, Body, Subject)).
×
975

976
announce_motd_update(<<"all">>, Subject, Body) ->
977
    Host = hd(ejabberd_option:hosts()),
×
978
    [{ok, _} | _] = announce_all_hosts_motd_update(make_packet(Host, Body, Subject)),
×
979
    ok;
×
980
announce_motd_update(Host, Subject, Body) ->
981
    {ok, _} = announce_motd_update(make_packet(Host, Body, Subject)),
×
982
    ok.
×
983

984
announce_motd_delete_api(<<"all">>) ->
985
    Host = hd(ejabberd_option:hosts()),
×
986
    announce_all_hosts_motd_delete(make_packet(Host));
×
987
announce_motd_delete_api(Host) ->
988
    announce_motd_delete(make_packet(Host)).
×
989

990
make_packet(Host) ->
991
    From = To = jid:make(Host),
×
992
    #message{from = From, to = To}.
×
993

994
make_packet(Host, Body, Subject) ->
995
    From = To = jid:make(Host),
×
996
    Body2 = binary:replace(Body, <<"\\n">>, <<"\n">>, [global]),
×
997
    #message{from = From,
×
998
             to = To,
999
             type = headline,
1000
             body = xmpp:mk_text(Body2),
1001
             subject = xmpp:mk_text(Subject)}.
1002

1003
%%====================================================================
1004
%%; Announcing via WebAdmin
1005

1006
webadmin_menu(Acc, _Host, Lang) ->
1007
    [{<<"announce">>, translate:translate(Lang, ?T("Announcements"))} | Acc].
32✔
1008

1009
webadmin_page(_,
1010
              Host,
1011
              #request{us = _US,
1012
                       path = [<<"announce">>],
1013
                       lang = Lang} =
1014
                  R) ->
1015
    PageTitle = translate:translate(Lang, ?T("Announcements")),
×
1016
    Head = ?H1GL(PageTitle, <<"modules/#mod_announce">>, <<"mod_announce">>),
×
1017
    Ann = [?X(<<"hr">>),
×
1018
           ?XE(<<"blockquote">>,
1019
               [make_command(announce_send_all, R, [{<<"host">>, Host}], []),
1020
                make_command(announce_send_online, R, [{<<"host">>, Host}], [])])],
1021
    SetMotd =
×
1022
        [make_command(announce_motd_set_online, R, [{<<"host">>, Host}], []),
1023
         make_command(announce_motd_update, R, [{<<"host">>, Host}], []),
1024
         make_command(announce_motd_delete, R, [{<<"host">>, Host}], [{style, danger}])],
1025
    GetMotd = [make_command(announce_motd_get, R, [{<<"host">>, Host}], [])],
×
1026
    Motd =
×
1027
        [?X(<<"hr">>),
1028
         ?XAC(<<"h2">>, [{<<"id">>, <<"motd">>}], <<"Message Of The Day">>),
1029
         ?XE(<<"blockquote">>, GetMotd ++ SetMotd)],
1030
    {stop, Head ++ Ann ++ Motd};
×
1031
webadmin_page(Acc, _, _) ->
1032
    Acc.
×
1033
%% @format-end
1034

1035
%%====================================================================
1036
%%; Cache management
1037

1038
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
1039
init_cache(Mod, Host, Opts) ->
1040
    case use_cache(Mod, Host) of
98✔
1041
        true ->
1042
            CacheOpts = cache_opts(Opts),
98✔
1043
            ets_cache:new(?MOTD_CACHE, CacheOpts);
98✔
1044
        false ->
1045
            ets_cache:delete(?MOTD_CACHE)
×
1046
    end.
1047

1048
-spec cache_opts(gen_mod:opts()) -> [proplists:property()].
1049
cache_opts(Opts) ->
1050
    MaxSize = mod_announce_opt:cache_size(Opts),
98✔
1051
    CacheMissed = mod_announce_opt:cache_missed(Opts),
98✔
1052
    LifeTime = mod_announce_opt:cache_life_time(Opts),
98✔
1053
    [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
98✔
1054

1055
-spec use_cache(module(), binary()) -> boolean().
1056
use_cache(Mod, Host) ->
1057
    case erlang:function_exported(Mod, use_cache, 1) of
362✔
1058
        true -> Mod:use_cache(Host);
×
1059
        false -> mod_announce_opt:use_cache(Host)
362✔
1060
    end.
1061

1062
-spec cache_nodes(module(), binary()) -> [node()].
1063
cache_nodes(Mod, Host) ->
1064
    case erlang:function_exported(Mod, cache_nodes, 1) of
8✔
1065
        true -> Mod:cache_nodes(Host);
×
1066
        false -> ejabberd_cluster:get_nodes()
8✔
1067
    end.
1068

1069
-spec clean_cache(binary()) -> non_neg_integer().
1070
clean_cache(LServer) ->
1071
    ets_cache:filter(
8✔
1072
      ?MOTD_CACHE,
1073
      fun({_, S}, _) -> S /= LServer end).
8✔
1074

1075

1076
%%====================================================================
1077
%%; ejd2sql callbacks
1078

1079
export(LServer) ->
1080
    Mod = gen_mod:db_mod(LServer, ?MODULE),
×
1081
    Mod:export(LServer).
×
1082

1083
import_info() ->
1084
    [{<<"motd">>, 3}].
×
1085

1086
import_start(LServer, DBType) ->
1087
    Mod = gen_mod:db_mod(DBType, ?MODULE),
×
1088
    Mod:init(LServer, []).
×
1089

1090
import(LServer, {sql, _}, DBType, Tab, List) ->
1091
    Mod = gen_mod:db_mod(DBType, ?MODULE),
×
1092
    Mod:import(LServer, Tab, List).
×
1093

1094
%%====================================================================
1095
%%; Options and Documentation
1096

1097
mod_opt_type(access) ->
1098
    econf:acl();
116✔
1099
mod_opt_type(db_type) ->
1100
    econf:db_type(?MODULE);
116✔
1101
mod_opt_type(use_cache) ->
1102
    econf:bool();
116✔
1103
mod_opt_type(cache_size) ->
1104
    econf:pos_int(infinity);
116✔
1105
mod_opt_type(cache_missed) ->
1106
    econf:bool();
116✔
1107
mod_opt_type(cache_life_time) ->
1108
    econf:timeout(second, infinity).
116✔
1109

1110
mod_options(Host) ->
1111
    [{access, none},
116✔
1112
     {db_type, ejabberd_config:default_db(Host, ?MODULE)},
1113
     {use_cache, ejabberd_option:use_cache(Host)},
1114
     {cache_size, ejabberd_option:cache_size(Host)},
1115
     {cache_missed, ejabberd_option:cache_missed(Host)},
1116
     {cache_life_time, ejabberd_option:cache_life_time(Host)}].
1117

1118
mod_doc() ->
1119
    #{desc =>
×
1120
          [?T("This module enables configured users to broadcast "
1121
              "announcements and to set the message of the day (MOTD). "
1122
              "Configured users can perform these actions with an XMPP "
1123
              "client either using Ad-Hoc Commands or sending messages "
1124
              "to specific JIDs. Equivalent API commands are also available."), "",
1125
           ?T("NOTE: This module can be resource intensive on large "
1126
              "deployments as it may broadcast a lot of messages. This module "
1127
              "should be disabled for instances of ejabberd with hundreds of "
1128
              "thousands users."), "",
1129
           ?T("To send announcements using "
1130
              "https://xmpp.org/extensions/xep-0050.html[XEP-0050: Ad-Hoc Commands], "
1131
              "this module requires _`mod_adhoc`_ (to execute the commands), "
1132
              "and recommends _`mod_disco`_ (to discover the commands)."), "",
1133
           ?T("To send announcements by sending messages to specific JIDs, these are the destination JIDs:"), "",
1134
           "- 'example.org/announce/all':",
1135
           ?T("Send the message to all registered users in that vhost. If the user is "
1136
              "online and connected to several resources, only the resource "
1137
              "with the highest priority will receive the message. "
1138
              "If the registered user is not connected, the message is "
1139
              "stored offline in assumption that offline storage (see _`mod_offline`_) "
1140
              "is enabled."),
1141
           "- 'example.org/announce/online':",
1142
           ?T("Send the message to all connected users. If the user is "
1143
              "online and connected to several resources, all resources will "
1144
              "receive the message."),
1145
           "- 'example.org/announce/motd':",
1146
           ?T("Set the message of the day (MOTD) that is sent "
1147
              "to users when they login. Also sends the message to all "
1148
              "connected users (similar to 'announce/online')."),
1149
           "- 'example.org/announce/motd/update':",
1150
           ?T("Set the message of the day (MOTD) that is sent to users "
1151
              "when they login. This does not send the message to any currently connected user."),
1152
           "- 'example.org/announce/motd/delete':",
1153
           ?T("Remove the existing message of the day (MOTD) by sending a message to this JID."), "",
1154
           ?T("There are similar destination JIDs to apply to all virtual hosts in ejabberd:"), "",
1155
           "- 'example.org/announce/all-hosts/all': send to all registered accounts",
1156
           "- 'example.org/announce/all-hosts/online': send to online sessions",
1157
           "- 'example.org/announce/all-hosts/motd': set MOTD and send to online",
1158
           "- 'example.org/announce/all-hosts/motd/update': update MOTD",
1159
           "- 'example.org/announce/all-hosts/motd/delete': delete MOTD"],
1160
      opts =>
1161
          [{access,
1162
            #{value => ?T("AccessName"),
1163
              desc =>
1164
                  ?T("This option specifies who is allowed to send announcements "
1165
                     "and to set the message of the day. The default value is 'none' "
1166
                     "(i.e. nobody is able to send such messages).")}},
1167
           {db_type,
1168
            #{value => "mnesia | sql",
1169
              desc =>
1170
                  ?T("Same as top-level _`default_db`_ option, but applied to this module only.")}},
1171
           {use_cache,
1172
            #{value => "true | false",
1173
              desc =>
1174
                  ?T("Same as top-level _`use_cache`_ option, but applied to this module only.")}},
1175
           {cache_size,
1176
            #{value => "pos_integer() | infinity",
1177
              desc =>
1178
                  ?T("Same as top-level _`cache_size`_ option, but applied to this module only.")}},
1179
           {cache_missed,
1180
            #{value => "true | false",
1181
              desc =>
1182
                  ?T("Same as top-level _`cache_missed`_ option, but applied to this module only.")}},
1183
           {cache_life_time,
1184
            #{value => "timeout()",
1185
              desc =>
1186
                  ?T("Same as top-level _`cache_life_time`_ option, but applied to this module only.")}}]}.
1187

1188
%%% vim: set foldmethod=marker foldmarker=%%;,%%=:
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