• 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

6.79
/src/mod_configure.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_configure.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Support for online configuration of ejabberd using XEP-0050
5
%%% Created : 19 Jan 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
-module(mod_configure).
27

28
-author('alexey@process-one.net').
29

30
-protocol({xep, 133, '1.3.1', '13.10', "partial", ""}).
31

32
-behaviour(gen_mod).
33

34
-export([start/2, stop/1, reload/3, get_local_identity/5,
35
         get_local_features/5, get_local_items/5,
36
         adhoc_local_items/4, adhoc_local_commands/4,
37
         get_sm_identity/5, get_sm_features/5, get_sm_items/5,
38
         adhoc_sm_items/4, adhoc_sm_commands/4, mod_options/1,
39
         mod_opt_type/1,
40
         depends/2, mod_doc/0]).
41

42
-include("logger.hrl").
43
-include_lib("xmpp/include/xmpp.hrl").
44
-include("ejabberd_sm.hrl").
45
-include("translate.hrl").
46
-include_lib("stdlib/include/ms_transform.hrl").
47

48
start(_Host, _Opts) ->
49
    {ok, [{hook, disco_local_items, get_local_items, 50},
99✔
50
          {hook, disco_local_features, get_local_features, 50},
51
          {hook, disco_local_identity, get_local_identity, 50},
52
          {hook, disco_sm_items, get_sm_items, 50},
53
          {hook, disco_sm_features, get_sm_features, 50},
54
          {hook, disco_sm_identity, get_sm_identity, 50},
55
          {hook, adhoc_local_items, adhoc_local_items, 50},
56
          {hook, adhoc_local_commands, adhoc_local_commands, 50},
57
          {hook, adhoc_sm_items, adhoc_sm_items, 50},
58
          {hook, adhoc_sm_commands, adhoc_sm_commands, 50}]}.
59

60
stop(_Host) ->
61
    ok.
99✔
62

63
reload(_Host, _NewOpts, _OldOpts) ->
64
    ok.
×
65

66
depends(_Host, _Opts) ->
67
    [{mod_adhoc, hard}, {mod_last, soft}].
117✔
68

69
%%%-----------------------------------------------------------------------
70

71
-define(INFO_IDENTITY(Category, Type, Name, Lang),
72
        [#identity{category = Category, type = Type, name = tr(Lang, Name)}]).
73

74
-define(INFO_COMMAND(Name, Lang),
75
        ?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
76
                       Name, Lang)).
77

78
-define(NODEJID(To, Name, Node),
79
        #disco_item{jid = To, name = tr(Lang, Name), node = Node}).
80

81
-define(NODE(Name, Node),
82
        #disco_item{jid = jid:make(Server),
83
                    node = Node,
84
                    name = tr(Lang, Name)}).
85

86
-define(NS_ADMINX(Sub),
87
        <<(?NS_ADMIN)/binary, "#", Sub/binary>>).
88

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

93
-spec tokenize(binary()) -> [binary()].
94
tokenize(Node) -> str:tokens(Node, <<"/#">>).
3,791✔
95

96
acl_match_rule(Host, From) ->
97
    Access = mod_configure_opt:access(Host),
1,949✔
98
    acl:match_rule(Host, Access, From).
1,949✔
99

100
-spec get_sm_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()].
101
get_sm_identity(Acc, _From, _To, Node, Lang) ->
102
    case Node of
56✔
103
      <<"config">> ->
104
          ?INFO_COMMAND(?T("Configuration"), Lang);
×
105
      _ -> Acc
56✔
106
    end.
107

108
-spec get_local_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()].
109
get_local_identity(Acc, _From, _To, Node, Lang) ->
110
    LNode = tokenize(Node),
1,883✔
111
    case LNode of
1,883✔
112
      [<<"running nodes">>, ENode] ->
113
          ?INFO_IDENTITY(<<"ejabberd">>, <<"node">>, ENode, Lang);
×
114
      [<<"running nodes">>, _ENode, <<"DB">>] ->
115
          ?INFO_COMMAND(?T("Database"), Lang);
×
116
      [<<"running nodes">>, _ENode, <<"backup">>,
117
       <<"backup">>] ->
118
          ?INFO_COMMAND(?T("Backup"), Lang);
×
119
      [<<"running nodes">>, _ENode, <<"backup">>,
120
       <<"restore">>] ->
121
          ?INFO_COMMAND(?T("Restore"), Lang);
×
122
      [<<"running nodes">>, _ENode, <<"backup">>,
123
       <<"textfile">>] ->
124
          ?INFO_COMMAND(?T("Dump to Text File"), Lang);
×
125
      [<<"running nodes">>, _ENode, <<"import">>,
126
       <<"file">>] ->
127
          ?INFO_COMMAND(?T("Import File"), Lang);
×
128
      [<<"running nodes">>, _ENode, <<"import">>,
129
       <<"dir">>] ->
130
          ?INFO_COMMAND(?T("Import Directory"), Lang);
×
131
      [<<"running nodes">>, _ENode, <<"restart">>] ->
132
          ?INFO_COMMAND(?T("Restart Service"), Lang);
×
133
      [<<"running nodes">>, _ENode, <<"shutdown">>] ->
134
          ?INFO_COMMAND(?T("Shut Down Service"), Lang);
×
135
      ?NS_ADMINL(<<"add-user">>) ->
136
          ?INFO_COMMAND(?T("Add User"), Lang);
×
137
      ?NS_ADMINL(<<"delete-user">>) ->
138
          ?INFO_COMMAND(?T("Delete User"), Lang);
×
139
      ?NS_ADMINL(<<"disable-user">>) ->
140
          ?INFO_COMMAND(?T("Disable User"), Lang);
×
141
      ?NS_ADMINL(<<"reenable-user">>) ->
142
          ?INFO_COMMAND(?T("Re-Enable User"), Lang);
×
143
      ?NS_ADMINL(<<"end-user-session">>) ->
144
          ?INFO_COMMAND(?T("End User Session"), Lang);
×
145
      ?NS_ADMINL(<<"change-user-password">>) ->
146
          ?INFO_COMMAND(?T("Change User Password"), Lang);
×
147
      ?NS_ADMINL(<<"get-user-roster">>) ->
148
          ?INFO_COMMAND(?T("Get User Roster"), Lang);
×
149
      ?NS_ADMINL(<<"get-user-lastlogin">>) ->
150
          ?INFO_COMMAND(?T("Get User Last Login Time"), Lang);
×
151
      ?NS_ADMINL(<<"user-stats">>) ->
152
          ?INFO_COMMAND(?T("Get User Statistics"), Lang);
×
153
      ?NS_ADMINL(<<"get-registered-users-num">>) ->
154
          ?INFO_COMMAND(?T("Get Number of Registered Users"),
×
155
                        Lang);
156
      ?NS_ADMINL(<<"get-disabled-users-num">>) ->
157
          ?INFO_COMMAND(?T("Get Number of Disabled Users"),
×
158
                        Lang);
159
      ?NS_ADMINL(<<"get-online-users-num">>) ->
160
          ?INFO_COMMAND(?T("Get Number of Online Users"), Lang);
×
161
      ?NS_ADMINL(<<"get-active-users-num">>) ->
162
          ?INFO_COMMAND(?T("Get Number of Active Users"), Lang);
×
163
      ?NS_ADMINL(<<"get-idle-users-num">>) ->
164
          ?INFO_COMMAND(?T("Get Number of Idle Users"), Lang);
×
165
      ?NS_ADMINL(<<"get-registered-users-list">>) ->
166
          ?INFO_COMMAND(?T("Get List of Registered Users"),
×
167
                        Lang);
168
      ?NS_ADMINL(<<"get-disabled-users-list">>) ->
169
          ?INFO_COMMAND(?T("Get List of Disabled Users"),
×
170
                        Lang);
171
      ?NS_ADMINL(<<"get-online-users-list">>) ->
172
          ?INFO_COMMAND(?T("Get List of Online Users"), Lang);
×
173
      ?NS_ADMINL(<<"get-active-users">>) ->
174
          ?INFO_COMMAND(?T("Get List of Active Users"), Lang);
×
175
      ?NS_ADMINL(<<"get-idle-users">>) ->
176
          ?INFO_COMMAND(?T("Get List of Idle Users"), Lang);
×
177
      ?NS_ADMINL(<<"restart">>) ->
178
          ?INFO_COMMAND(?T("Restart Service"), Lang);
×
179
      ?NS_ADMINL(<<"shutdown">>) ->
180
          ?INFO_COMMAND(?T("Shut Down Service"), Lang);
×
181
      _ -> Acc
1,883✔
182
    end.
183

184
%%%-----------------------------------------------------------------------
185

186
-define(INFO_RESULT(Allow, Feats, Lang),
187
        case Allow of
188
          deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
189
          allow -> {result, Feats}
190
        end).
191

192
-spec get_sm_features(mod_disco:features_acc(), jid(), jid(),
193
                      binary(), binary()) -> mod_disco:features_acc().
194
get_sm_features(Acc, From,
195
                #jid{lserver = LServer} = _To, Node, Lang) ->
196
    case gen_mod:is_loaded(LServer, mod_adhoc) of
56✔
197
      false -> Acc;
×
198
      _ ->
199
          Allow = acl_match_rule(LServer, From),
56✔
200
          case Node of
56✔
201
            <<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
202
            _ -> Acc
56✔
203
          end
204
    end.
205

206
-spec get_local_features(mod_disco:features_acc(), jid(), jid(),
207
                         binary(), binary()) -> mod_disco:features_acc().
208
get_local_features(Acc, From,
209
                   #jid{lserver = LServer} = _To, Node, Lang) ->
210
    case gen_mod:is_loaded(LServer, mod_adhoc) of
1,883✔
211
      false -> Acc;
×
212
      _ ->
213
          LNode = tokenize(Node),
1,883✔
214
          Allow = acl_match_rule(LServer, From),
1,883✔
215
          case LNode of
1,883✔
216
            [<<"config">>] -> ?INFO_RESULT(Allow, [], Lang);
×
217
            [<<"user">>] -> ?INFO_RESULT(Allow, [], Lang);
×
218
            [<<"online users">>] -> ?INFO_RESULT(Allow, [], Lang);
×
219
            [<<"all users">>] -> ?INFO_RESULT(Allow, [], Lang);
×
220
            [<<"all users">>, <<$@, _/binary>>] ->
221
                ?INFO_RESULT(Allow, [], Lang);
×
222
            [<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, [], Lang);
×
223
            [<<"running nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
×
224
            [<<"stopped nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
×
225
            [<<"running nodes">>, _ENode] ->
226
                ?INFO_RESULT(Allow, [?NS_STATS], Lang);
×
227
            [<<"running nodes">>, _ENode, <<"DB">>] ->
228
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
229
            [<<"running nodes">>, _ENode, <<"backup">>] ->
230
                ?INFO_RESULT(Allow, [], Lang);
×
231
            [<<"running nodes">>, _ENode, <<"backup">>, _] ->
232
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
233
            [<<"running nodes">>, _ENode, <<"import">>] ->
234
                ?INFO_RESULT(Allow, [], Lang);
×
235
            [<<"running nodes">>, _ENode, <<"import">>, _] ->
236
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
237
            [<<"running nodes">>, _ENode, <<"restart">>] ->
238
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
239
            [<<"running nodes">>, _ENode, <<"shutdown">>] ->
240
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
241
            [<<"config">>, _] ->
242
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
243
            ?NS_ADMINL(<<"add-user">>) ->
244
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
245
            ?NS_ADMINL(<<"delete-user">>) ->
246
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
247
            ?NS_ADMINL(<<"disable-user">>) ->
248
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
249
            ?NS_ADMINL(<<"reenable-user">>) ->
250
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
251
            ?NS_ADMINL(<<"end-user-session">>) ->
252
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
253
            ?NS_ADMINL(<<"change-user-password">>) ->
254
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
255
            ?NS_ADMINL(<<"get-user-roster">>) ->
256
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
257
            ?NS_ADMINL(<<"get-user-lastlogin">>) ->
258
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
259
            ?NS_ADMINL(<<"user-stats">>) ->
260
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
261
            ?NS_ADMINL(<<"get-registered-users-num">>) ->
262
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
263
            ?NS_ADMINL(<<"get-disabled-users-num">>) ->
264
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
265
            ?NS_ADMINL(<<"get-online-users-num">>) ->
266
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
267
            ?NS_ADMINL(<<"get-active-users-num">>) ->
268
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
269
            ?NS_ADMINL(<<"get-idle-users-num">>) ->
270
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
271
            ?NS_ADMINL(<<"get-registered-users-list">>) ->
272
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
273
            ?NS_ADMINL(<<"get-disabled-users-list">>) ->
274
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
275
            ?NS_ADMINL(<<"get-online-users-list">>) ->
276
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
277
            ?NS_ADMINL(<<"get-active-users">>) ->
278
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
279
            ?NS_ADMINL(<<"get-idle-users">>) ->
280
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
281
            ?NS_ADMINL(<<"restart">>) ->
282
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
283
            ?NS_ADMINL(<<"shutdown">>) ->
284
                ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
×
285
            _ -> Acc
1,883✔
286
          end
287
    end.
288

289
%%%-----------------------------------------------------------------------
290
-spec adhoc_sm_items(mod_disco:items_acc(),
291
                     jid(), jid(), binary()) -> mod_disco:items_acc().
292
adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To,
293
               Lang) ->
294
    case acl_match_rule(LServer, From) of
×
295
      allow ->
296
          Items = case Acc of
×
297
                    {result, Its} -> Its;
×
298
                    empty -> []
×
299
                  end,
300
          Nodes = [#disco_item{jid = To, node = <<"config">>,
×
301
                               name = tr(Lang, ?T("Configuration"))}],
302
          {result, Items ++ Nodes};
×
303
      _ -> Acc
×
304
    end.
305

306
%%%-----------------------------------------------------------------------
307
-spec get_sm_items(mod_disco:items_acc(), jid(), jid(),
308
                   binary(), binary()) -> mod_disco:items_acc().
309
get_sm_items(Acc, From,
310
             #jid{user = User, server = Server, lserver = LServer} =
311
                 To,
312
             Node, Lang) ->
313
    case gen_mod:is_loaded(LServer, mod_adhoc) of
8✔
314
      false -> Acc;
×
315
      _ ->
316
          Items = case Acc of
8✔
317
                    {result, Its} -> Its;
×
318
                    empty -> []
8✔
319
                  end,
320
          case {acl_match_rule(LServer, From), Node} of
8✔
321
            {allow, <<"">>} ->
322
                Nodes = [?NODEJID(To, ?T("Configuration"),
×
323
                                  <<"config">>),
324
                         ?NODEJID(To, ?T("User Management"), <<"user">>)],
325
                {result,
×
326
                 Items ++ Nodes ++ get_user_resources(User, Server)};
327
            {allow, <<"config">>} -> {result, []};
×
328
            {_, <<"config">>} ->
329
                  {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
×
330
            _ -> Acc
8✔
331
          end
332
    end.
333

334
-spec get_user_resources(binary(), binary()) -> [disco_item()].
335
get_user_resources(User, Server) ->
336
    Rs = ejabberd_sm:get_user_resources(User, Server),
×
337
    lists:map(fun (R) ->
×
338
                      #disco_item{jid = jid:make(User, Server, R),
×
339
                                  name = User}
340
              end,
341
              lists:sort(Rs)).
342

343
%%%-----------------------------------------------------------------------
344

345
-spec adhoc_local_items(mod_disco:items_acc(),
346
                        jid(), jid(), binary()) -> mod_disco:items_acc().
347
adhoc_local_items(Acc, From,
348
                  #jid{lserver = LServer, server = Server} = To, Lang) ->
349
    case acl_match_rule(LServer, From) of
×
350
      allow ->
351
          Items = case Acc of
×
352
                    {result, Its} -> Its;
×
353
                    empty -> []
×
354
                  end,
355
          PermLev = get_permission_level(From, LServer),
×
356
          Nodes = recursively_get_local_items(PermLev, LServer,
×
357
                                              <<"">>, Server, Lang),
358
          Nodes1 = lists:filter(
×
359
                     fun (#disco_item{node = Nd}) ->
360
                             F = get_local_features(empty, From, To, Nd, Lang),
×
361
                             case F of
×
362
                                 {result, [?NS_COMMANDS]} -> true;
×
363
                                 _ -> false
×
364
                             end
365
                     end,
366
                     Nodes),
367
          {result, Items ++ Nodes1};
×
368
      _ -> Acc
×
369
    end.
370

371
-spec recursively_get_local_items(global | vhost, binary(), binary(),
372
                                  binary(), binary()) -> [disco_item()].
373
recursively_get_local_items(_PermLev, _LServer,
374
                            <<"online users">>, _Server, _Lang) ->
375
    [];
×
376
recursively_get_local_items(_PermLev, _LServer,
377
                            <<"all users">>, _Server, _Lang) ->
378
    [];
×
379
recursively_get_local_items(PermLev, LServer, Node,
380
                            Server, Lang) ->
381
    LNode = tokenize(Node),
×
382
    Items = case get_local_items({PermLev, LServer}, LNode,
×
383
                                 Server, Lang)
384
                of
385
              {result, Res} -> Res;
×
386
              {error, _Error} -> []
×
387
            end,
388
    lists:flatten(
×
389
      lists:map(
390
        fun(#disco_item{jid = #jid{server = S}, node = Nd} = Item) ->
391
                if (S /= Server) or
×
392
                   (Nd == <<"">>) ->
393
                        [];
×
394
                   true ->
395
                        [Item,
×
396
                         recursively_get_local_items(
397
                           PermLev, LServer, Nd, Server, Lang)]
398
                end
399
        end,
400
        Items)).
401

402
-spec get_permission_level(jid(), binary()) -> global | vhost.
403
get_permission_level(JID, Host) ->
404
    Access = mod_configure_opt:access(Host),
×
405
    case acl:match_rule(global, Access, JID) of
×
406
      allow -> global;
×
407
      deny -> vhost
×
408
    end.
409

410
%%%-----------------------------------------------------------------------
411

412
-define(ITEMS_RESULT(Allow, LNode, Fallback),
413
        case Allow of
414
          deny -> Fallback;
415
          allow ->
416
              PermLev = get_permission_level(From, LServer),
417
              case get_local_items({PermLev, LServer}, LNode,
418
                                   jid:encode(To), Lang)
419
                  of
420
                {result, Res} -> {result, Res};
421
                {error, Error} -> {error, Error}
422
              end
423
        end).
424

425
-spec get_local_items(mod_disco:items_acc(), jid(), jid(),
426
                      binary(), binary()) -> mod_disco:items_acc().
427
get_local_items(Acc, From, #jid{lserver = LServer} = To,
428
                <<"">>, Lang) ->
429
    case gen_mod:is_loaded(LServer, mod_adhoc) of
1✔
430
      false -> Acc;
×
431
      _ ->
432
          Items = case Acc of
1✔
433
                    {result, Its} -> Its;
1✔
434
                    empty -> []
×
435
                  end,
436
          Allow = acl_match_rule(LServer, From),
1✔
437
          case Allow of
1✔
438
            deny -> {result, Items};
1✔
439
            allow ->
440
                PermLev = get_permission_level(From, LServer),
×
441
                case get_local_items({PermLev, LServer}, [],
×
442
                                     jid:encode(To), Lang)
443
                    of
444
                  {result, Res} -> {result, Items ++ Res};
×
445
                  {error, _Error} -> {result, Items}
×
446
                end
447
          end
448
    end;
449
get_local_items(Acc, From, #jid{lserver = LServer} = To,
450
                Node, Lang) ->
451
    case gen_mod:is_loaded(LServer, mod_adhoc) of
1✔
452
      false -> Acc;
×
453
      _ ->
454
          LNode = tokenize(Node),
1✔
455
          Allow = acl_match_rule(LServer, From),
1✔
456
          Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang),
1✔
457
          case LNode of
1✔
458
            [<<"config">>] ->
459
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
460
            [<<"user">>] ->
461
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
462
            [<<"online users">>] ->
463
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
464
            [<<"all users">>] ->
465
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
466
            [<<"all users">>, <<$@, _/binary>>] ->
467
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
468
            [<<"outgoing s2s">> | _] ->
469
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
470
            [<<"running nodes">>] ->
471
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
472
            [<<"stopped nodes">>] ->
473
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
474
            [<<"running nodes">>, _ENode] ->
475
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
476
            [<<"running nodes">>, _ENode, <<"DB">>] ->
477
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
478
            [<<"running nodes">>, _ENode, <<"backup">>] ->
479
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
480
            [<<"running nodes">>, _ENode, <<"backup">>, _] ->
481
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
482
            [<<"running nodes">>, _ENode, <<"import">>] ->
483
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
484
            [<<"running nodes">>, _ENode, <<"import">>, _] ->
485
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
486
            [<<"running nodes">>, _ENode, <<"restart">>] ->
487
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
488
            [<<"running nodes">>, _ENode, <<"shutdown">>] ->
489
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
490
            [<<"config">>, _] ->
491
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
492
            ?NS_ADMINL(<<"add-user">>) ->
493
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
494
            ?NS_ADMINL(<<"delete-user">>) ->
495
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
496
            ?NS_ADMINL(<<"disable-user">>) ->
497
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
498
            ?NS_ADMINL(<<"reenable-user">>) ->
499
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
500
            ?NS_ADMINL(<<"end-user-session">>) ->
501
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
502
            ?NS_ADMINL(<<"change-user-password">>) ->
503
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
504
            ?NS_ADMINL(<<"get-user-roster">>) ->
505
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
506
            ?NS_ADMINL(<<"get-user-lastlogin">>) ->
507
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
508
            ?NS_ADMINL(<<"user-stats">>) ->
509
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
510
            ?NS_ADMINL(<<"get-registered-users-num">>) ->
511
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
512
            ?NS_ADMINL(<<"get-disabled-users-num">>) ->
513
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
514
            ?NS_ADMINL(<<"get-online-users-num">>) ->
515
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
516
            ?NS_ADMINL(<<"get-active-users-num">>) ->
517
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
518
            ?NS_ADMINL(<<"get-idle-users-num">>) ->
519
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
520
            ?NS_ADMINL(<<"get-registered-users-list">>) ->
521
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
522
            ?NS_ADMINL(<<"get-disabled-users-list">>) ->
523
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
524
            ?NS_ADMINL(<<"get-online-users-list">>) ->
525
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
526
            ?NS_ADMINL(<<"get-active-users">>) ->
527
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
528
            ?NS_ADMINL(<<"get-idle-users">>) ->
529
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
530
            ?NS_ADMINL(<<"restart">>) ->
531
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
532
            ?NS_ADMINL(<<"shutdown">>) ->
533
                ?ITEMS_RESULT(Allow, LNode, {error, Err});
×
534
            _ -> Acc
1✔
535
          end
536
    end.
537

538
%%%-----------------------------------------------------------------------
539
-spec get_local_items({global | vhost, binary()}, [binary()],
540
                      binary(), binary()) -> {result, [disco_item()]} | {error, stanza_error()}.
541
get_local_items(_Host, [], Server, Lang) ->
542
    {result,
×
543
     [?NODE(?T("Configuration"), <<"config">>),
544
      ?NODE(?T("User Management"), <<"user">>),
545
      ?NODE(?T("Online Users"), <<"online users">>),
546
      ?NODE(?T("All Users"), <<"all users">>),
547
      ?NODE(?T("Outgoing s2s Connections"),
548
            <<"outgoing s2s">>),
549
      ?NODE(?T("Running Nodes"), <<"running nodes">>),
550
      ?NODE(?T("Stopped Nodes"), <<"stopped nodes">>)]};
551
get_local_items(_Host, [<<"config">>, _], _Server,
552
                _Lang) ->
553
    {result, []};
×
554
get_local_items(_Host, [<<"user">>], Server, Lang) ->
555
    {result,
×
556
     [?NODE(?T("Add User"), (?NS_ADMINX(<<"add-user">>))),
557
      ?NODE(?T("Delete User"),
558
            (?NS_ADMINX(<<"delete-user">>))),
559
      ?NODE(?T("Disable User"),
560
            (?NS_ADMINX(<<"disable-user">>))),
561
      ?NODE(?T("Re-Enable User"),
562
            (?NS_ADMINX(<<"reenable-user">>))),
563
      ?NODE(?T("End User Session"),
564
            (?NS_ADMINX(<<"end-user-session">>))),
565
      ?NODE(?T("Change User Password"),
566
            (?NS_ADMINX(<<"change-user-password">>))),
567
      ?NODE(?T("Get User Roster"),
568
            (?NS_ADMINX(<<"get-user-roster">>))),
569
      ?NODE(?T("Get User Last Login Time"),
570
            (?NS_ADMINX(<<"get-user-lastlogin">>))),
571
      ?NODE(?T("Get User Statistics"),
572
            (?NS_ADMINX(<<"user-stats">>))),
573
      ?NODE(?T("Get Number of Registered Users"),
574
            (?NS_ADMINX(<<"get-registered-users-num">>))),
575
      ?NODE(?T("Get Number of Disabled Users"),
576
            (?NS_ADMINX(<<"get-disabled-users-num">>))),
577
      ?NODE(?T("Get Number of Online Users"),
578
            (?NS_ADMINX(<<"get-online-users-num">>))),
579
      ?NODE(?T("Get Number of Active Users"),
580
            (?NS_ADMINX(<<"get-active-users-num">>))),
581
      ?NODE(?T("Get Number of Idle Users"),
582
            (?NS_ADMINX(<<"get-idle-users-num">>))),
583
      ?NODE(?T("Get List of Registered Users"),
584
            (?NS_ADMINX(<<"get-registered-users-list">>))),
585
      ?NODE(?T("Get List of Disabled Users"),
586
            (?NS_ADMINX(<<"get-disabled-users-list">>))),
587
      ?NODE(?T("Get List of Online Users"),
588
            (?NS_ADMINX(<<"get-online-users-list">>))),
589
      ?NODE(?T("Get List of Active Users"),
590
            (?NS_ADMINX(<<"get-active-users">>))),
591
      ?NODE(?T("Get List of Idle Users"),
592
            (?NS_ADMINX(<<"get-idle-users">>)))
593
     ]};
594
get_local_items(_Host, [<<"http:">> | _], _Server,
595
                _Lang) ->
596
    {result, []};
×
597
get_local_items({_, Host}, [<<"online users">>],
598
                _Server, _Lang) ->
599
    {result, get_online_vh_users(Host)};
×
600
get_local_items({_, Host}, [<<"all users">>], _Server,
601
                _Lang) ->
602
    {result, get_all_vh_users(Host)};
×
603
get_local_items({_, Host},
604
                [<<"all users">>, <<$@, Diap/binary>>], _Server,
605
                _Lang) ->
606
    Users = ejabberd_auth:get_users(Host),
×
607
    SUsers = lists:sort([{S, U} || {U, S} <- Users]),
×
608
    try
×
609
        [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
×
610
        N1 = binary_to_integer(S1),
×
611
        N2 = binary_to_integer(S2),
×
612
        Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
×
613
        {result, lists:map(
×
614
                   fun({S, U}) ->
615
                           #disco_item{jid = jid:make(U, S),
×
616
                                       name = <<U/binary, $@, S/binary>>}
617
                   end, Sub)}
618
    catch _:_ ->
619
            {error, xmpp:err_not_acceptable()}
×
620
    end;
621
get_local_items({_, Host}, [<<"outgoing s2s">>],
622
                _Server, Lang) ->
623
    {result, get_outgoing_s2s(Host, Lang)};
×
624
get_local_items({_, Host}, [<<"outgoing s2s">>, To],
625
                _Server, Lang) ->
626
    {result, get_outgoing_s2s(Host, Lang, To)};
×
627
get_local_items(_Host, [<<"running nodes">>], Server,
628
                Lang) ->
629
    {result, get_running_nodes(Server, Lang)};
×
630
get_local_items(_Host, [<<"stopped nodes">>], _Server,
631
                Lang) ->
632
    {result, get_stopped_nodes(Lang)};
×
633
get_local_items({global, _Host},
634
                [<<"running nodes">>, ENode], Server, Lang) ->
635
    {result,
×
636
     [?NODE(?T("Database"),
637
            <<"running nodes/", ENode/binary, "/DB">>),
638
      ?NODE(?T("Backup Management"),
639
            <<"running nodes/", ENode/binary, "/backup">>),
640
      ?NODE(?T("Import Users From jabberd14 Spool Files"),
641
            <<"running nodes/", ENode/binary, "/import">>),
642
      ?NODE(?T("Restart Service"),
643
            (?NS_ADMINX(<<"restart">>))),
644
      ?NODE(?T("Shut Down Service"),
645
            (?NS_ADMINX(<<"shutdown">>))),
646
      ?NODE(?T("Restart Service"),
647
            <<"running nodes/", ENode/binary, "/restart">>),
648
      ?NODE(?T("Shut Down Service"),
649
            <<"running nodes/", ENode/binary, "/shutdown">>)]};
650
get_local_items(_Host,
651
                [<<"running nodes">>, _ENode, <<"DB">>], _Server,
652
                _Lang) ->
653
    {result, []};
×
654
get_local_items(_Host,
655
                [<<"running nodes">>, ENode, <<"backup">>], Server,
656
                Lang) ->
657
    {result,
×
658
     [?NODE(?T("Backup"),
659
            <<"running nodes/", ENode/binary, "/backup/backup">>),
660
      ?NODE(?T("Restore"),
661
            <<"running nodes/", ENode/binary, "/backup/restore">>),
662
      ?NODE(?T("Dump to Text File"),
663
            <<"running nodes/", ENode/binary,
664
              "/backup/textfile">>)]};
665
get_local_items(_Host,
666
                [<<"running nodes">>, _ENode, <<"backup">>, _], _Server,
667
                _Lang) ->
668
    {result, []};
×
669
get_local_items(_Host,
670
                [<<"running nodes">>, ENode, <<"import">>], Server,
671
                Lang) ->
672
    {result,
×
673
     [?NODE(?T("Import File"),
674
            <<"running nodes/", ENode/binary, "/import/file">>),
675
      ?NODE(?T("Import Directory"),
676
            <<"running nodes/", ENode/binary, "/import/dir">>)]};
677
get_local_items(_Host,
678
                [<<"running nodes">>, _ENode, <<"import">>, _], _Server,
679
                _Lang) ->
680
    {result, []};
×
681
get_local_items(_Host,
682
                [<<"running nodes">>, _ENode, <<"restart">>], _Server,
683
                _Lang) ->
684
    {result, []};
×
685
get_local_items(_Host,
686
                [<<"running nodes">>, _ENode, <<"shutdown">>], _Server,
687
                _Lang) ->
688
    {result, []};
×
689
get_local_items(_Host, _, _Server, _Lang) ->
690
    {error, xmpp:err_item_not_found()}.
×
691

692
-spec get_online_vh_users(binary()) -> [disco_item()].
693
get_online_vh_users(Host) ->
694
    USRs = ejabberd_sm:get_vh_session_list(Host),
×
695
    SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]),
×
696
    lists:map(
×
697
      fun({S, U, R}) ->
698
              #disco_item{jid = jid:make(U, S, R),
×
699
                          name = <<U/binary, "@", S/binary>>}
700
      end, SURs).
701

702
-spec get_all_vh_users(binary()) -> [disco_item()].
703
get_all_vh_users(Host) ->
704
    Users = ejabberd_auth:get_users(Host),
×
705
    SUsers = lists:sort([{S, U} || {U, S} <- Users]),
×
706
    case length(SUsers) of
×
707
        N when N =< 100 ->
708
            lists:map(fun({S, U}) ->
×
709
                              #disco_item{jid = jid:make(U, S),
×
710
                                          name = <<U/binary, $@, S/binary>>}
711
                      end, SUsers);
712
        N ->
713
            NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1,
×
714
            M = trunc(N / NParts) + 1,
×
715
            lists:map(
×
716
              fun (K) ->
717
                      L = K + M - 1,
×
718
                      Node = <<"@",
×
719
                               (integer_to_binary(K))/binary,
720
                               "-",
721
                               (integer_to_binary(L))/binary>>,
722
                      {FS, FU} = lists:nth(K, SUsers),
×
723
                      {LS, LU} = if L < N -> lists:nth(L, SUsers);
×
724
                                    true -> lists:last(SUsers)
×
725
                                 end,
726
                      Name = <<FU/binary, "@", FS/binary, " -- ",
×
727
                               LU/binary, "@", LS/binary>>,
728
                      #disco_item{jid = jid:make(Host),
×
729
                                  node = <<"all users/", Node/binary>>,
730
                                  name = Name}
731
              end, lists:seq(1, N, M))
732
    end.
733

734
-spec get_outgoing_s2s(binary(), binary()) -> [disco_item()].
735
get_outgoing_s2s(Host, Lang) ->
736
    Connections = ejabberd_s2s:dirty_get_connections(),
×
737
    DotHost = <<".", Host/binary>>,
×
738
    TConns = [TH || {FH, TH} <- Connections,
×
739
                    Host == FH orelse str:suffix(DotHost, FH)],
×
740
    lists:map(
×
741
      fun (T) ->
742
              Name = str:translate_and_format(Lang, ?T("To ~ts"),[T]),
×
743
              #disco_item{jid = jid:make(Host),
×
744
                          node = <<"outgoing s2s/", T/binary>>,
745
                          name = Name}
746
      end, lists:usort(TConns)).
747

748
-spec get_outgoing_s2s(binary(), binary(), binary()) -> [disco_item()].
749
get_outgoing_s2s(Host, Lang, To) ->
750
    Connections = ejabberd_s2s:dirty_get_connections(),
×
751
    lists:map(
×
752
      fun ({F, _T}) ->
753
              Node = <<"outgoing s2s/", To/binary, "/", F/binary>>,
×
754
              Name = str:translate_and_format(Lang, ?T("From ~ts"), [F]),
×
755
              #disco_item{jid = jid:make(Host), node = Node, name = Name}
×
756
      end,
757
      lists:keysort(
758
        1,
759
        lists:filter(fun (E) -> element(2, E) == To end,
×
760
                     Connections))).
761

762
-spec get_running_nodes(binary(), binary()) -> [disco_item()].
763
get_running_nodes(Server, _Lang) ->
764
    DBNodes = mnesia:system_info(running_db_nodes),
×
765
    lists:map(
×
766
      fun (N) ->
767
              S = iolist_to_binary(atom_to_list(N)),
×
768
              #disco_item{jid = jid:make(Server),
×
769
                          node = <<"running nodes/", S/binary>>,
770
                          name = S}
771
      end, lists:sort(DBNodes)).
772

773
-spec get_stopped_nodes(binary()) -> [disco_item()].
774
get_stopped_nodes(_Lang) ->
775
    DBNodes = lists:usort(mnesia:system_info(db_nodes) ++
×
776
                              mnesia:system_info(extra_db_nodes))
777
        -- mnesia:system_info(running_db_nodes),
778
    lists:map(
×
779
      fun (N) ->
780
              S = iolist_to_binary(atom_to_list(N)),
×
781
              #disco_item{jid = jid:make(ejabberd_config:get_myname()),
×
782
                          node = <<"stopped nodes/", S/binary>>,
783
                          name = S}
784
      end, lists:sort(DBNodes)).
785

786
%%-------------------------------------------------------------------------
787

788
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
789
                        Request, Lang),
790
        case acl_match_rule(LServerOrGlobal, From) of
791
          deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
792
          allow -> adhoc_local_commands(From, To, Request)
793
        end).
794

795
-spec adhoc_local_commands(adhoc_command(), jid(), jid(), adhoc_command()) ->
796
                                  adhoc_command() | {error, stanza_error()}.
797
adhoc_local_commands(Acc, From,
798
                     #jid{lserver = LServer} = To,
799
                     #adhoc_command{node = Node, lang = Lang} = Request) ->
800
    LNode = tokenize(Node),
24✔
801
    case LNode of
24✔
802
      [<<"running nodes">>, _ENode, <<"DB">>] ->
803
          ?COMMANDS_RESULT(global, From, To, Request, Lang);
×
804
      [<<"running nodes">>, _ENode, <<"backup">>, _] ->
805
          ?COMMANDS_RESULT(global, From, To, Request, Lang);
×
806
      [<<"running nodes">>, _ENode, <<"import">>, _] ->
807
          ?COMMANDS_RESULT(global, From, To, Request, Lang);
×
808
      [<<"running nodes">>, _ENode, <<"restart">>] ->
809
          ?COMMANDS_RESULT(global, From, To, Request, Lang);
×
810
      [<<"running nodes">>, _ENode, <<"shutdown">>] ->
811
          ?COMMANDS_RESULT(global, From, To, Request, Lang);
×
812
      [<<"config">>, _] ->
813
          ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
×
814
      ?NS_ADMINL(_) ->
815
          ?COMMANDS_RESULT(LServer, From, To, Request, Lang);
×
816
      _ -> Acc
24✔
817
    end.
818

819
-spec adhoc_local_commands(jid(), jid(), adhoc_command()) -> adhoc_command() | {error, stanza_error()}.
820
adhoc_local_commands(From,
821
                     #jid{lserver = LServer} = _To,
822
                     #adhoc_command{lang = Lang, node = Node,
823
                                    sid = SessionID, action = Action,
824
                                    xdata = XData} = Request) ->
825
    LNode = tokenize(Node),
×
826
    ActionIsExecute = Action == execute orelse Action == complete,
×
827
    if Action == cancel ->
×
828
            #adhoc_command{status = canceled, lang = Lang,
×
829
                           node = Node, sid = SessionID};
830
       XData == undefined, ActionIsExecute ->
831
           case get_form(LServer, LNode, Lang) of
×
832
             {result, Form} ->
833
                 xmpp_util:make_adhoc_response(
×
834
                   Request,
835
                   #adhoc_command{status = executing, xdata = Form});
836
             {result, Status, Form} ->
837
                 xmpp_util:make_adhoc_response(
×
838
                   Request,
839
                   #adhoc_command{status = Status, xdata = Form});
840
             {error, Error} -> {error, Error}
×
841
           end;
842
       XData /= undefined, ActionIsExecute ->
843
            case set_form(From, LServer, LNode, Lang, XData) of
×
844
                {result, Res} ->
845
                    xmpp_util:make_adhoc_response(
×
846
                      Request,
847
                      #adhoc_command{xdata = Res, status = completed});
848
                %%{'EXIT', _} -> {error, xmpp:err_bad_request()};
849
                {error, Error} -> {error, Error}
×
850
            end;
851
       true ->
852
          {error, xmpp:err_bad_request(?T("Unexpected action"), Lang)}
×
853
    end.
854

855
-define(TVFIELD(Type, Var, Val),
856
        #xdata_field{type = Type, var = Var, values = [Val]}).
857

858
-define(HFIELD(),
859
        ?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))).
860

861
-define(TLFIELD(Type, Label, Var),
862
        #xdata_field{type = Type, label = tr(Lang, Label), var = Var}).
863

864
-define(XFIELD(Type, Label, Var, Val),
865
        #xdata_field{type = Type, label = tr(Lang, Label),
866
                     var = Var, values = [Val]}).
867

868
-define(XMFIELD(Type, Label, Var, Vals),
869
        #xdata_field{type = Type, label = tr(Lang, Label),
870
                     var = Var, values = Vals}).
871

872
-define(TABLEFIELD(Table, Val),
873
        #xdata_field{
874
           type = 'list-single',
875
           label = iolist_to_binary(atom_to_list(Table)),
876
           var = iolist_to_binary(atom_to_list(Table)),
877
           values = [iolist_to_binary(atom_to_list(Val))],
878
           options = [#xdata_option{label = tr(Lang, ?T("RAM copy")),
879
                                    value = <<"ram_copies">>},
880
                      #xdata_option{label = tr(Lang, ?T("RAM and disc copy")),
881
                                    value = <<"disc_copies">>},
882
                      #xdata_option{label = tr(Lang, ?T("Disc only copy")),
883
                                    value =  <<"disc_only_copies">>},
884
                      #xdata_option{label = tr(Lang, ?T("Remote copy")),
885
                                    value = <<"unknown">>}]}).
886

887
-spec get_form(binary(), [binary()], binary()) -> {result, xdata()} |
888
                                                  {result, completed, xdata()} |
889
                                                  {error, stanza_error()}.
890
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
891
         Lang) ->
892
    case search_running_node(ENode) of
×
893
      false ->
894
          Txt = ?T("No running node found"),
×
895
          {error, xmpp:err_item_not_found(Txt, Lang)};
×
896
      Node ->
897
          case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
×
898
            {badrpc, Reason} ->
899
                ?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
×
900
                           "~ts failed: ~p", [Node, Reason]),
×
901
                {error, xmpp:err_internal_server_error()};
×
902
            Tables ->
903
                STables = lists:sort(Tables),
×
904
                Title = <<(tr(Lang, ?T("Database Tables Configuration at ")))/binary,
×
905
                          ENode/binary>>,
906
                Instr = tr(Lang, ?T("Choose storage type of tables")),
×
907
                try
×
908
                    Fs = lists:map(
×
909
                           fun(Table) ->
910
                                   case ejabberd_cluster:call(
×
911
                                          Node, mnesia, table_info,
912
                                          [Table, storage_type]) of
913
                                       Type when is_atom(Type) ->
914
                                           ?TABLEFIELD(Table, Type)
×
915
                                   end
916
                           end, STables),
917
                    {result, #xdata{title = Title,
×
918
                                    type = form,
919
                                    instructions = [Instr],
920
                                    fields = [?HFIELD()|Fs]}}
921
                catch _:{case_clause, {badrpc, Reason}} ->
922
                        ?ERROR_MSG("RPC call mnesia:table_info/2 "
×
923
                                   "on node ~ts failed: ~p", [Node, Reason]),
×
924
                        {error, xmpp:err_internal_server_error()}
×
925
                end
926
          end
927
    end;
928
get_form(_Host,
929
         [<<"running nodes">>, ENode, <<"backup">>,
930
          <<"backup">>],
931
         Lang) ->
932
    {result,
×
933
     #xdata{title = <<(tr(Lang, ?T("Backup to File at ")))/binary, ENode/binary>>,
934
            type = form,
935
            instructions = [tr(Lang, ?T("Enter path to backup file"))],
936
            fields = [?HFIELD(),
937
                      ?XFIELD('text-single', ?T("Path to File"),
938
                              <<"path">>, <<"">>)]}};
939
get_form(_Host,
940
         [<<"running nodes">>, ENode, <<"backup">>,
941
          <<"restore">>],
942
         Lang) ->
943
    {result,
×
944
     #xdata{title = <<(tr(Lang, ?T("Restore Backup from File at ")))/binary,
945
                      ENode/binary>>,
946
            type = form,
947
            instructions = [tr(Lang, ?T("Enter path to backup file"))],
948
            fields = [?HFIELD(),
949
                      ?XFIELD('text-single', ?T("Path to File"),
950
                              <<"path">>, <<"">>)]}};
951
get_form(_Host,
952
         [<<"running nodes">>, ENode, <<"backup">>,
953
          <<"textfile">>],
954
         Lang) ->
955
    {result,
×
956
     #xdata{title = <<(tr(Lang, ?T("Dump Backup to Text File at ")))/binary,
957
                      ENode/binary>>,
958
            type = form,
959
            instructions = [tr(Lang, ?T("Enter path to text file"))],
960
            fields = [?HFIELD(),
961
                      ?XFIELD('text-single', ?T("Path to File"),
962
                              <<"path">>, <<"">>)]}};
963
get_form(_Host,
964
         [<<"running nodes">>, ENode, <<"import">>, <<"file">>],
965
         Lang) ->
966
    {result,
×
967
     #xdata{title = <<(tr(Lang, ?T("Import User from File at ")))/binary,
968
                      ENode/binary>>,
969
            type = form,
970
            instructions = [tr(Lang, ?T("Enter path to jabberd14 spool file"))],
971
            fields = [?HFIELD(),
972
                      ?XFIELD('text-single', ?T("Path to File"),
973
                              <<"path">>, <<"">>)]}};
974
get_form(_Host,
975
         [<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
976
         Lang) ->
977
    {result,
×
978
     #xdata{title = <<(tr(Lang, ?T("Import Users from Dir at ")))/binary,
979
                      ENode/binary>>,
980
            type = form,
981
            instructions = [tr(Lang, ?T("Enter path to jabberd14 spool dir"))],
982
            fields = [?HFIELD(),
983
                      ?XFIELD('text-single', ?T("Path to Dir"),
984
                              <<"path">>, <<"">>)]}};
985
get_form(_Host,
986
         [<<"running nodes">>, _ENode, <<"restart">>], Lang) ->
987
    Make_option =
×
988
        fun (LabelNum, LabelUnit, Value) ->
989
                #xdata_option{
×
990
                   label = <<LabelNum/binary, (tr(Lang, LabelUnit))/binary>>,
991
                   value = Value}
992
        end,
993
    {result,
×
994
     #xdata{title = tr(Lang, ?T("Restart Service")),
995
            type = form,
996
            fields = [?HFIELD(),
997
                      #xdata_field{
998
                         type = 'list-single',
999
                         label = tr(Lang, ?T("Time delay")),
1000
                         var = <<"delay">>,
1001
                         required = true,
1002
                         options =
1003
                             [Make_option(<<"">>, <<"immediately">>, <<"1">>),
1004
                              Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
1005
                              Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
1006
                              Make_option(<<"60 ">>, <<"seconds">>, <<"60">>),
1007
                              Make_option(<<"90 ">>, <<"seconds">>, <<"90">>),
1008
                              Make_option(<<"2 ">>, <<"minutes">>, <<"120">>),
1009
                              Make_option(<<"3 ">>, <<"minutes">>, <<"180">>),
1010
                              Make_option(<<"4 ">>, <<"minutes">>, <<"240">>),
1011
                              Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
1012
                              Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
1013
                              Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
1014
                              Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]},
1015
                      #xdata_field{type = fixed,
1016
                                   label = tr(Lang,
1017
                                              ?T("Send announcement to all online users "
1018
                                                 "on all hosts"))},
1019
                      #xdata_field{var = <<"subject">>,
1020
                                   type = 'text-single',
1021
                                   label = tr(Lang, ?T("Subject"))},
1022
                      #xdata_field{var = <<"announcement">>,
1023
                                   type = 'text-multi',
1024
                                   label = tr(Lang, ?T("Message body"))}]}};
1025
get_form(_Host,
1026
         [<<"running nodes">>, _ENode, <<"shutdown">>], Lang) ->
1027
    Make_option =
×
1028
        fun (LabelNum, LabelUnit, Value) ->
1029
                #xdata_option{
×
1030
                   label = <<LabelNum/binary, (tr(Lang, LabelUnit))/binary>>,
1031
                   value = Value}
1032
        end,
1033
    {result,
×
1034
     #xdata{title = tr(Lang, ?T("Shut Down Service")),
1035
            type = form,
1036
            fields = [?HFIELD(),
1037
                      #xdata_field{
1038
                         type = 'list-single',
1039
                         label = tr(Lang, ?T("Time delay")),
1040
                         var = <<"delay">>,
1041
                         required = true,
1042
                         options =
1043
                             [Make_option(<<"">>, <<"immediately">>, <<"1">>),
1044
                              Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
1045
                              Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
1046
                              Make_option(<<"60 ">>, <<"seconds">>, <<"60">>),
1047
                              Make_option(<<"90 ">>, <<"seconds">>, <<"90">>),
1048
                              Make_option(<<"2 ">>, <<"minutes">>, <<"120">>),
1049
                              Make_option(<<"3 ">>, <<"minutes">>, <<"180">>),
1050
                              Make_option(<<"4 ">>, <<"minutes">>, <<"240">>),
1051
                              Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
1052
                              Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
1053
                              Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
1054
                              Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]},
1055
                      #xdata_field{type = fixed,
1056
                                   label = tr(Lang,
1057
                                              ?T("Send announcement to all online users "
1058
                                                 "on all hosts"))},
1059
                      #xdata_field{var = <<"subject">>,
1060
                                   type = 'text-single',
1061
                                   label = tr(Lang, ?T("Subject"))},
1062
                      #xdata_field{var = <<"announcement">>,
1063
                                   type = 'text-multi',
1064
                                   label = tr(Lang, ?T("Message body"))}]}};
1065
get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) ->
1066
    {result,
×
1067
     #xdata{title = tr(Lang, ?T("Add User")),
1068
            type = form,
1069
            fields = [?HFIELD(),
1070
                      #xdata_field{type = 'jid-single',
1071
                                   label = tr(Lang, ?T("Jabber ID")),
1072
                                   required = true,
1073
                                   var = <<"accountjid">>},
1074
                      #xdata_field{type = 'text-private',
1075
                                   label = tr(Lang, ?T("Password")),
1076
                                   required = true,
1077
                                   var = <<"password">>},
1078
                      #xdata_field{type = 'text-private',
1079
                                   label = tr(Lang, ?T("Password Verification")),
1080
                                   required = true,
1081
                                   var = <<"password-verify">>}]}};
1082
get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) ->
1083
    {result,
×
1084
     #xdata{title = tr(Lang, ?T("Delete User")),
1085
            type = form,
1086
            fields = [?HFIELD(),
1087
                      #xdata_field{type = 'jid-multi',
1088
                                   label = tr(Lang, ?T("Jabber ID")),
1089
                                   required = true,
1090
                                   var = <<"accountjids">>}]}};
1091
get_form(_Host, ?NS_ADMINL(<<"disable-user">>), Lang) ->
1092
    {result,
×
1093
     #xdata{title = tr(Lang, ?T("Disable User")),
1094
            type = form,
1095
            fields = [?HFIELD(),
1096
                      #xdata_field{type = 'jid-multi',
1097
                                   label = tr(Lang, ?T("Jabber ID")),
1098
                                   required = true,
1099
                                   var = <<"accountjids">>}]}};
1100
get_form(_Host, ?NS_ADMINL(<<"reenable-user">>), Lang) ->
1101
    {result,
×
1102
     #xdata{title = tr(Lang, ?T("Re-Enable User")),
1103
            type = form,
1104
            fields = [?HFIELD(),
1105
                      #xdata_field{type = 'jid-multi',
1106
                                   label = tr(Lang, ?T("Jabber ID")),
1107
                                   required = true,
1108
                                   var = <<"accountjids">>}]}};
1109
get_form(_Host, ?NS_ADMINL(<<"end-user-session">>),
1110
         Lang) ->
1111
    {result,
×
1112
     #xdata{title = tr(Lang, ?T("End User Session")),
1113
            type = form,
1114
            fields = [?HFIELD(),
1115
                      #xdata_field{type = 'jid-single',
1116
                                   label = tr(Lang, ?T("Jabber ID")),
1117
                                   required = true,
1118
                                   var = <<"accountjid">>}]}};
1119
get_form(_Host, ?NS_ADMINL(<<"change-user-password">>),
1120
         Lang) ->
1121
    {result,
×
1122
     #xdata{title = tr(Lang, ?T("Change User Password")),
1123
            type = form,
1124
            fields = [?HFIELD(),
1125
                      #xdata_field{type = 'jid-single',
1126
                                   label = tr(Lang, ?T("Jabber ID")),
1127
                                   required = true,
1128
                                   var = <<"accountjid">>},
1129
                      #xdata_field{type = 'text-private',
1130
                                   label = tr(Lang, ?T("Password")),
1131
                                   required = true,
1132
                                   var = <<"password">>}]}};
1133
get_form(_Host, ?NS_ADMINL(<<"get-user-roster">>),
1134
         Lang) ->
1135
    {result,
×
1136
     #xdata{title = tr(Lang, ?T("Get User Roster")),
1137
            type = form,
1138
            fields = [?HFIELD(),
1139
                      #xdata_field{type = 'jid-multi',
1140
                                   label = tr(Lang, ?T("Jabber ID")),
1141
                                   required = true,
1142
                                   var = <<"accountjids">>}]}};
1143
get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>),
1144
         Lang) ->
1145
    {result,
×
1146
     #xdata{title = tr(Lang, ?T("Get User Last Login Time")),
1147
            type = form,
1148
            fields = [?HFIELD(),
1149
                      #xdata_field{type = 'jid-single',
1150
                                   label = tr(Lang, ?T("Jabber ID")),
1151
                                   var = <<"accountjid">>,
1152
                                   required = true}]}};
1153
get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) ->
1154
    {result,
×
1155
     #xdata{title = tr(Lang, ?T("Get User Statistics")),
1156
            type = form,
1157
            fields = [?HFIELD(),
1158
                      #xdata_field{type = 'jid-single',
1159
                                   label = tr(Lang, ?T("Jabber ID")),
1160
                                   var = <<"accountjid">>,
1161
                                   required = true}]}};
1162
get_form(Host,
1163
         ?NS_ADMINL(<<"get-registered-users-num">>), Lang) ->
1164
    Num = integer_to_binary(ejabberd_auth:count_users(Host)),
×
1165
    {result, completed,
×
1166
     #xdata{type = form,
1167
            fields = [?HFIELD(),
1168
                      #xdata_field{type = 'text-single',
1169
                                   label = tr(Lang, ?T("Number of registered users")),
1170
                                   var = <<"registeredusersnum">>,
1171
                                   values = [Num]}]}};
1172
get_form(Host,
1173
         ?NS_ADMINL(<<"get-disabled-users-num">>), Lang) ->
1174
    Num = integer_to_binary(mod_admin_extra:count_banned(Host)),
×
1175
    {result, completed,
×
1176
     #xdata{type = form,
1177
            fields = [?HFIELD(),
1178
                      #xdata_field{type = 'text-single',
1179
                                   label = tr(Lang, ?T("Number of disabled users")),
1180
                                   var = <<"disabledusersnum">>,
1181
                                   values = [Num]}]}};
1182
get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>),
1183
         Lang) ->
1184
    Num = integer_to_binary(ejabberd_sm:get_vh_session_number(Host)),
×
1185
    {result, completed,
×
1186
     #xdata{type = form,
1187
            fields = [?HFIELD(),
1188
                      #xdata_field{type = 'text-single',
1189
                                   label = tr(Lang, ?T("Number of online users")),
1190
                                   var = <<"onlineusersnum">>,
1191
                                   values = [Num]}]}};
1192
get_form(Host, ?NS_ADMINL(<<"get-active-users-num">>),
1193
         Lang) ->
1194
    Num = integer_to_binary(mod_admin_extra:status_num(Host, [<<"available">>,
×
1195
                                                              <<"chat">>,
1196
                                                              <<"dnd">>])),
1197
    {result, completed,
×
1198
     #xdata{type = form,
1199
            fields = [?HFIELD(),
1200
                      #xdata_field{type = 'text-single',
1201
                                   label = tr(Lang, ?T("Number of active users")),
1202
                                   var = <<"activeusersnum">>,
1203
                                   values = [Num]}]}};
1204
get_form(Host, ?NS_ADMINL(<<"get-idle-users-num">>),
1205
         Lang) ->
1206
    Num = integer_to_binary(mod_admin_extra:status_num(Host, [<<"away">>,
×
1207
                                                              <<"xa">>])),
1208
    {result, completed,
×
1209
     #xdata{type = form,
1210
            fields = [?HFIELD(),
1211
                      #xdata_field{type = 'text-single',
1212
                                   label = tr(Lang, ?T("Number of idle users")),
1213
                                   var = <<"idleusersnum">>,
1214
                                   values = [Num]}]}};
1215
get_form(Host, ?NS_ADMINL(<<"get-registered-users-list">>), Lang) ->
1216
    Values = [jid:encode(jid:make(U, Host))
×
1217
              || {U, _} <- ejabberd_auth:get_users(Host)],
×
1218
    {result, completed,
×
1219
     #xdata{type = form,
1220
            fields = [?HFIELD(),
1221
                      #xdata_field{type = 'jid-multi',
1222
                                   label = tr(Lang, ?T("The list of all users")),
1223
                                   var = <<"registereduserjids">>,
1224
                                   values = Values}]}};
1225
get_form(Host, ?NS_ADMINL(<<"get-disabled-users-list">>), Lang) ->
1226
    Values = mod_admin_extra:list_banned(Host),
×
1227
    {result, completed,
×
1228
     #xdata{type = form,
1229
            fields = [?HFIELD(),
1230
                      #xdata_field{type = 'jid-multi',
1231
                                   label = tr(Lang, ?T("The list of all disabled users")),
1232
                                   var = <<"disableduserjids">>,
1233
                                   values = Values}]}};
1234
get_form(Host, ?NS_ADMINL(<<"get-online-users-list">>), Lang) ->
1235
    Accounts = [jid:encode(jid:make(U, Host))
×
1236
              || {U, _, _} <- ejabberd_sm:get_vh_session_list(Host)],
×
1237
    Values = lists:usort(Accounts),
×
1238
    {result, completed,
×
1239
     #xdata{type = form,
1240
            fields = [?HFIELD(),
1241
                      #xdata_field{type = 'jid-multi',
1242
                                   label = tr(Lang, ?T("The list of all online users")),
1243
                                   var = <<"onlineuserjids">>,
1244
                                   values = Values}]}};
1245
get_form(Host, ?NS_ADMINL(<<"get-active-users">>), Lang) ->
1246
    RR = mod_admin_extra:status_list(Host, [<<"available">>, <<"chat">>, <<"dnd">>]),
×
1247
    Accounts = [jid:encode(jid:make(U, S))
×
1248
              || {U, S, _Resource, _Priority, _StatusText} <- RR],
×
1249
    Values = lists:usort(Accounts),
×
1250
    {result, completed,
×
1251
     #xdata{type = form,
1252
            fields = [?HFIELD(),
1253
                      #xdata_field{type = 'jid-multi',
1254
                                   label = tr(Lang, ?T("The list of all active users")),
1255
                                   var = <<"activeuserjids">>,
1256
                                   values = Values}]}};
1257
get_form(Host, ?NS_ADMINL(<<"get-idle-users">>), Lang) ->
1258
    RR = mod_admin_extra:status_list(Host, [<<"away">>, <<"xa">>]),
×
1259
    Accounts = [jid:encode(jid:make(U, S))
×
1260
              || {U, S, _Resource, _Priority, _StatusText} <- RR],
×
1261
    Values = lists:usort(Accounts),
×
1262
    {result, completed,
×
1263
     #xdata{type = form,
1264
            fields = [?HFIELD(),
1265
                      #xdata_field{type = 'jid-multi',
1266
                                   label = tr(Lang, ?T("The list of all idle users")),
1267
                                   var = <<"idleuserjids">>,
1268
                                   values = Values}]}};
1269
get_form(Host, ?NS_ADMINL(<<"restart">>), Lang) ->
1270
    get_form(Host,
×
1271
         [<<"running nodes">>, misc:atom_to_binary(node()), <<"restart">>], Lang);
1272
get_form(Host, ?NS_ADMINL(<<"shutdown">>), Lang) ->
1273
    get_form(Host,
×
1274
         [<<"running nodes">>, misc:atom_to_binary(node()), <<"shutdown">>], Lang);
1275
get_form(_Host, _, _Lang) ->
1276
    {error, xmpp:err_service_unavailable()}.
×
1277

1278
-spec set_form(jid(), binary(), [binary()], binary(), xdata()) -> {result, xdata() | undefined} |
1279
                                                                  {error, stanza_error()}.
1280
set_form(_From, _Host,
1281
         [<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
1282
    case search_running_node(ENode) of
×
1283
      false ->
1284
          Txt = ?T("No running node found"),
×
1285
          {error, xmpp:err_item_not_found(Txt, Lang)};
×
1286
      Node ->
1287
          lists:foreach(
×
1288
            fun(#xdata_field{var = SVar, values = SVals}) ->
1289
                    Table = misc:binary_to_atom(SVar),
×
1290
                    Type = case SVals of
×
1291
                               [<<"unknown">>] -> unknown;
×
1292
                               [<<"ram_copies">>] -> ram_copies;
×
1293
                               [<<"disc_copies">>] -> disc_copies;
×
1294
                               [<<"disc_only_copies">>] -> disc_only_copies;
×
1295
                               _ -> false
×
1296
                           end,
1297
                    if Type == false -> ok;
×
1298
                       Type == unknown ->
1299
                            mnesia:del_table_copy(Table, Node);
×
1300
                       true ->
1301
                            case mnesia:add_table_copy(Table, Node, Type) of
×
1302
                                {aborted, _} ->
1303
                                    mnesia:change_table_copy_type(
×
1304
                                      Table, Node, Type);
1305
                                _ -> ok
×
1306
                            end
1307
                    end
1308
            end, XData#xdata.fields),
1309
            {result, undefined}
×
1310
    end;
1311
set_form(_From, _Host,
1312
         [<<"running nodes">>, ENode, <<"backup">>,
1313
          <<"backup">>],
1314
         Lang, XData) ->
1315
    case search_running_node(ENode) of
×
1316
        false ->
1317
            Txt = ?T("No running node found"),
×
1318
            {error, xmpp:err_item_not_found(Txt, Lang)};
×
1319
        Node ->
1320
            case xmpp_util:get_xdata_values(<<"path">>, XData) of
×
1321
                [] ->
1322
                    Txt = ?T("No 'path' found in data form"),
×
1323
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
1324
                [String] ->
1325
                    case ejabberd_cluster:call(
×
1326
                           Node, mnesia, backup, [binary_to_list(String)],
1327
                           timer:minutes(10)) of
1328
                        {badrpc, Reason} ->
1329
                            ?ERROR_MSG("RPC call mnesia:backup(~ts) to node ~ts "
×
1330
                                       "failed: ~p", [String, Node, Reason]),
×
1331
                            {error, xmpp:err_internal_server_error()};
×
1332
                        {error, Reason} ->
1333
                            ?ERROR_MSG("RPC call mnesia:backup(~ts) to node ~ts "
×
1334
                                       "failed: ~p", [String, Node, Reason]),
×
1335
                            {error, xmpp:err_internal_server_error()};
×
1336
                        _ ->
1337
                            {result, undefined}
×
1338
                    end;
1339
                _ ->
1340
                    Txt = ?T("Incorrect value of 'path' in data form"),
×
1341
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1342
            end
1343
    end;
1344
set_form(_From, _Host,
1345
         [<<"running nodes">>, ENode, <<"backup">>,
1346
          <<"restore">>],
1347
         Lang, XData) ->
1348
    case search_running_node(ENode) of
×
1349
        false ->
1350
            Txt = ?T("No running node found"),
×
1351
            {error, xmpp:err_item_not_found(Txt, Lang)};
×
1352
        Node ->
1353
            case xmpp_util:get_xdata_values(<<"path">>, XData) of
×
1354
                [] ->
1355
                    Txt = ?T("No 'path' found in data form"),
×
1356
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
1357
                [String] ->
1358
                    case ejabberd_cluster:call(
×
1359
                           Node, ejabberd_admin, restore,
1360
                           [String], timer:minutes(10)) of
1361
                        {badrpc, Reason} ->
1362
                            ?ERROR_MSG("RPC call ejabberd_admin:restore(~ts) to node "
×
1363
                                       "~ts failed: ~p", [String, Node, Reason]),
×
1364
                            {error, xmpp:err_internal_server_error()};
×
1365
                        {error, Reason} ->
1366
                            ?ERROR_MSG("RPC call ejabberd_admin:restore(~ts) to node "
×
1367
                                       "~ts failed: ~p", [String, Node, Reason]),
×
1368
                            {error, xmpp:err_internal_server_error()};
×
1369
                        _ ->
1370
                            {result, undefined}
×
1371
                    end;
1372
                _ ->
1373
                    Txt = ?T("Incorrect value of 'path' in data form"),
×
1374
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1375
            end
1376
    end;
1377
set_form(_From, _Host,
1378
         [<<"running nodes">>, ENode, <<"backup">>,
1379
          <<"textfile">>],
1380
         Lang, XData) ->
1381
    case search_running_node(ENode) of
×
1382
        false ->
1383
            Txt = ?T("No running node found"),
×
1384
            {error, xmpp:err_item_not_found(Txt, Lang)};
×
1385
        Node ->
1386
            case xmpp_util:get_xdata_values(<<"path">>, XData) of
×
1387
                [] ->
1388
                    Txt = ?T("No 'path' found in data form"),
×
1389
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
1390
                [String] ->
1391
                    case ejabberd_cluster:call(
×
1392
                           Node, ejabberd_admin, dump_to_textfile,
1393
                           [String], timer:minutes(10)) of
1394
                        {badrpc, Reason} ->
1395
                            ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~ts) "
×
1396
                                       "to node ~ts failed: ~p", [String, Node, Reason]),
×
1397
                            {error, xmpp:err_internal_server_error()};
×
1398
                        {error, Reason} ->
1399
                            ?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~ts) "
×
1400
                                       "to node ~ts failed: ~p", [String, Node, Reason]),
×
1401
                            {error, xmpp:err_internal_server_error()};
×
1402
                        _ ->
1403
                            {result, undefined}
×
1404
                    end;
1405
                _ ->
1406
                    Txt = ?T("Incorrect value of 'path' in data form"),
×
1407
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1408
            end
1409
    end;
1410
set_form(_From, _Host,
1411
         [<<"running nodes">>, ENode, <<"import">>, <<"file">>],
1412
         Lang, XData) ->
1413
    case search_running_node(ENode) of
×
1414
        false ->
1415
            Txt = ?T("No running node found"),
×
1416
            {error, xmpp:err_item_not_found(Txt, Lang)};
×
1417
        Node ->
1418
            case xmpp_util:get_xdata_values(<<"path">>, XData) of
×
1419
                [] ->
1420
                    Txt = ?T("No 'path' found in data form"),
×
1421
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
1422
                [String] ->
1423
                    ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
×
1424
                    {result, undefined};
×
1425
                _ ->
1426
                    Txt = ?T("Incorrect value of 'path' in data form"),
×
1427
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1428
            end
1429
    end;
1430
set_form(_From, _Host,
1431
         [<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
1432
         Lang, XData) ->
1433
    case search_running_node(ENode) of
×
1434
        false ->
1435
            Txt = ?T("No running node found"),
×
1436
            {error, xmpp:err_item_not_found(Txt, Lang)};
×
1437
        Node ->
1438
            case xmpp_util:get_xdata_values(<<"path">>, XData) of
×
1439
                [] ->
1440
                    Txt = ?T("No 'path' found in data form"),
×
1441
                    {error, xmpp:err_bad_request(Txt, Lang)};
×
1442
                [String] ->
1443
                    ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
×
1444
                    {result, undefined};
×
1445
                _ ->
1446
                    Txt = ?T("Incorrect value of 'path' in data form"),
×
1447
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1448
            end
1449
    end;
1450
set_form(From, Host,
1451
         [<<"running nodes">>, ENode, <<"restart">>], _Lang,
1452
         XData) ->
1453
    stop_node(From, Host, ENode, restart, XData);
×
1454
set_form(From, Host,
1455
         [<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
1456
         XData) ->
1457
    stop_node(From, Host, ENode, stop, XData);
×
1458
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
1459
         XData) ->
1460
    AccountString = get_value(<<"accountjid">>, XData),
×
1461
    Password = get_value(<<"password">>, XData),
×
1462
    Password = get_value(<<"password-verify">>, XData),
×
1463
    AccountJID = jid:decode(AccountString),
×
1464
    User = AccountJID#jid.luser,
×
1465
    Server = AccountJID#jid.lserver,
×
1466
    true = lists:member(Server, ejabberd_option:hosts()),
×
1467
    true = Server == Host orelse
×
1468
             get_permission_level(From, Host) == global,
×
1469
    case ejabberd_auth:try_register(User, Server, Password) of
×
1470
        ok -> {result, undefined};
×
1471
        {error, exists} -> {error, xmpp:err_conflict()};
×
1472
        {error, not_allowed} -> {error, xmpp:err_not_allowed()}
×
1473
    end;
1474
set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
1475
         _Lang, XData) ->
1476
    AccountStringList = get_values(<<"accountjids">>,
×
1477
                                   XData),
1478
    [_ | _] = AccountStringList,
×
1479
    ASL2 = lists:map(fun (AccountString) ->
×
1480
                             JID = jid:decode(AccountString),
×
1481
                             User = JID#jid.luser,
×
1482
                             Server = JID#jid.lserver,
×
1483
                             true = Server == Host orelse
×
1484
                                      get_permission_level(From, Host) == global,
×
1485
                             true = ejabberd_auth:user_exists(User, Server),
×
1486
                             {User, Server}
×
1487
                     end,
1488
                     AccountStringList),
1489
    [ejabberd_auth:remove_user(User, Server)
×
1490
     || {User, Server} <- ASL2],
×
1491
    {result, undefined};
×
1492
set_form(From, Host, ?NS_ADMINL(<<"disable-user">>),
1493
         _Lang, XData) ->
1494
    AccountStringList = get_values(<<"accountjids">>,
×
1495
                                   XData),
1496
    [_ | _] = AccountStringList,
×
1497
    ASL2 = lists:map(fun (AccountString) ->
×
1498
                             JID = jid:decode(AccountString),
×
1499
                             User = JID#jid.luser,
×
1500
                             Server = JID#jid.lserver,
×
1501
                             true = Server == Host orelse
×
1502
                                      get_permission_level(From, Host) == global,
×
1503
                             true = ejabberd_auth:user_exists(User, Server),
×
1504
                             {User, Server}
×
1505
                     end,
1506
                     AccountStringList),
1507
    [mod_admin_extra:ban_account_v2(User, Server, <<"">>)
×
1508
     || {User, Server} <- ASL2],
×
1509
    {result, undefined};
×
1510
set_form(From, Host, ?NS_ADMINL(<<"reenable-user">>),
1511
         _Lang, XData) ->
1512
    AccountStringList = get_values(<<"accountjids">>,
×
1513
                                   XData),
1514
    [_ | _] = AccountStringList,
×
1515
    ASL2 = lists:map(fun (AccountString) ->
×
1516
                             JID = jid:decode(AccountString),
×
1517
                             User = JID#jid.luser,
×
1518
                             Server = JID#jid.lserver,
×
1519
                             true = Server == Host orelse
×
1520
                                      get_permission_level(From, Host) == global,
×
1521
                             true = ejabberd_auth:user_exists(User, Server),
×
1522
                             {User, Server}
×
1523
                     end,
1524
                     AccountStringList),
1525
    [mod_admin_extra:unban_account(User, Server)
×
1526
     || {User, Server} <- ASL2],
×
1527
    {result, undefined};
×
1528
set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
1529
         _Lang, XData) ->
1530
    AccountString = get_value(<<"accountjid">>, XData),
×
1531
    JID = jid:decode(AccountString),
×
1532
    LServer = JID#jid.lserver,
×
1533
    true = LServer == Host orelse
×
1534
             get_permission_level(From, Host) == global,
×
1535
    case JID#jid.lresource of
×
1536
        <<>> ->
1537
            ejabberd_sm:kick_user(JID#jid.luser, JID#jid.lserver);
×
1538
        R ->
1539
            ejabberd_sm:kick_user(JID#jid.luser, JID#jid.lserver, R)
×
1540
    end,
1541
    {result, undefined};
×
1542
set_form(From, Host,
1543
         ?NS_ADMINL(<<"change-user-password">>), _Lang, XData) ->
1544
    AccountString = get_value(<<"accountjid">>, XData),
×
1545
    Password = get_value(<<"password">>, XData),
×
1546
    JID = jid:decode(AccountString),
×
1547
    User = JID#jid.luser,
×
1548
    Server = JID#jid.lserver,
×
1549
    true = Server == Host orelse
×
1550
             get_permission_level(From, Host) == global,
×
1551
    true = ejabberd_auth:user_exists(User, Server),
×
1552
    ejabberd_auth:set_password(User, Server, Password),
×
1553
    {result, undefined};
×
1554
set_form(From, Host,
1555
         ?NS_ADMINL(<<"get-user-roster">>), Lang, XData) ->
1556
    AccountStringList = get_values(<<"accountjids">>,
×
1557
                                   XData),
1558
    [_ | _] = AccountStringList,
×
1559
    ASL2 = lists:map(fun (AccountString) ->
×
1560
                             JID = jid:decode(AccountString),
×
1561
                             User = JID#jid.luser,
×
1562
                             Server = JID#jid.lserver,
×
1563
                             true = Server == Host orelse
×
1564
                                      get_permission_level(From, Host) == global,
×
1565
                             true = ejabberd_auth:user_exists(User, Server),
×
1566
                             {User, Server}
×
1567
                     end,
1568
                     AccountStringList),
1569
    Contacts = [mod_admin_extra:get_roster(User, Server) || {User, Server} <- ASL2],
×
1570
    Jids = [lists:join(<<"; ">>, [Jid, Name, Subscription, lists:join(<<", ">>, Groups)])
×
1571
            || {Jid, Name, Subscription, _, Groups} <- lists:flatten(Contacts)],
×
1572
    {result,
×
1573
     #xdata{type = result,
1574
            fields = [?HFIELD(),
1575
                      ?XMFIELD('jid-multi', ?T("Jabber ID"),
1576
                              <<"accountjids">>, AccountStringList),
1577
                      ?XMFIELD('text-multi', ?T("Contacts"),
1578
                               <<"contacts">>, Jids)]}};
1579
set_form(From, Host,
1580
         ?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) ->
1581
    AccountString = get_value(<<"accountjid">>, XData),
×
1582
    JID = jid:decode(AccountString),
×
1583
    User = JID#jid.luser,
×
1584
    Server = JID#jid.lserver,
×
1585
    true = Server == Host orelse
×
1586
             get_permission_level(From, Host) == global,
×
1587
    FLast = case ejabberd_sm:get_user_resources(User,
×
1588
                                                Server)
1589
                of
1590
              [] ->
1591
                  case get_last_info(User, Server) of
×
1592
                    not_found -> tr(Lang, ?T("Never"));
×
1593
                    {ok, Timestamp, _Status} ->
1594
                        Shift = Timestamp,
×
1595
                        TimeStamp = {Shift div 1000000, Shift rem 1000000, 0},
×
1596
                        {{Year, Month, Day}, {Hour, Minute, Second}} =
×
1597
                            calendar:now_to_local_time(TimeStamp),
1598
                        (str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
×
1599
                                                       [Year, Month, Day, Hour,
1600
                                                        Minute, Second]))
1601
                  end;
1602
              _ -> tr(Lang, ?T("Online"))
×
1603
            end,
1604
    {result,
×
1605
     #xdata{type = result,
1606
            fields = [?HFIELD(),
1607
                      ?XFIELD('jid-single', ?T("Jabber ID"),
1608
                              <<"accountjid">>, AccountString),
1609
                      ?XFIELD('text-single', ?T("Last login"),
1610
                              <<"lastlogin">>, FLast)]}};
1611
set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang,
1612
         XData) ->
1613
    AccountString = get_value(<<"accountjid">>, XData),
×
1614
    JID = jid:decode(AccountString),
×
1615
    User = JID#jid.luser,
×
1616
    Server = JID#jid.lserver,
×
1617
    true = Server == Host orelse
×
1618
             get_permission_level(From, Host) == global,
×
1619
    Resources = ejabberd_sm:get_user_resources(User,
×
1620
                                               Server),
1621
    IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource)
×
1622
            || Resource <- Resources],
×
1623
    IPs = [<<(misc:ip_to_list(IP))/binary, ":",
×
1624
             (integer_to_binary(Port))/binary>>
1625
               || {IP, Port} <- IPs1],
×
1626
    Items = ejabberd_hooks:run_fold(roster_get, Server, [],
×
1627
                                    [{User, Server}]),
1628
    Rostersize = integer_to_binary(erlang:length(Items)),
×
1629
    {result,
×
1630
     #xdata{type = result,
1631
            fields = [?HFIELD(),
1632
                      ?XFIELD('jid-single', ?T("Jabber ID"),
1633
                              <<"accountjid">>, AccountString),
1634
                      ?XFIELD('text-single', ?T("Roster size"),
1635
                              <<"rostersize">>, Rostersize),
1636
                      ?XMFIELD('text-multi', ?T("IP addresses"),
1637
                               <<"ipaddresses">>, IPs),
1638
                      ?XMFIELD('text-multi', ?T("Resources"),
1639
                               <<"onlineresources">>, Resources)]}};
1640
set_form(From, Host, ?NS_ADMINL(<<"restart">>), Lang,
1641
         XData) ->
1642
    set_form(From, Host,
×
1643
         [<<"running nodes">>, misc:atom_to_binary(node()), <<"restart">>], Lang, XData);
1644
set_form(From, Host, ?NS_ADMINL(<<"shutdown">>), Lang,
1645
         XData) ->
1646
    set_form(From, Host,
×
1647
         [<<"running nodes">>, misc:atom_to_binary(node()), <<"shutdown">>], Lang, XData);
1648
set_form(_From, _Host, _, _Lang, _XData) ->
1649
    {error, xmpp:err_service_unavailable()}.
×
1650

1651
-spec get_value(binary(), xdata()) -> binary().
1652
get_value(Field, XData) ->
1653
    hd(get_values(Field, XData)).
×
1654

1655
-spec get_values(binary(), xdata()) -> [binary()].
1656
get_values(Field, XData) ->
1657
    xmpp_util:get_xdata_values(Field, XData).
×
1658

1659
-spec search_running_node(binary()) -> false | node().
1660
search_running_node(SNode) ->
1661
    search_running_node(SNode,
×
1662
                        mnesia:system_info(running_db_nodes)).
1663

1664
-spec search_running_node(binary(), [node()]) -> false | node().
1665
search_running_node(_, []) -> false;
×
1666
search_running_node(SNode, [Node | Nodes]) ->
1667
    case atom_to_binary(Node, utf8) of
×
1668
        SNode -> Node;
×
1669
        _ -> search_running_node(SNode, Nodes)
×
1670
    end.
1671

1672
-spec stop_node(jid(), binary(), binary(), restart | stop, xdata()) -> {result, undefined}.
1673
stop_node(From, Host, ENode, Action, XData) ->
1674
    Delay = binary_to_integer(get_value(<<"delay">>, XData)),
×
1675
    Subject = case get_values(<<"subject">>, XData) of
×
1676
                  [] ->
1677
                      [];
×
1678
                  [S|_] ->
1679
                      [#xdata_field{var = <<"subject">>, values = [S]}]
×
1680
              end,
1681
    Announcement = case get_values(<<"announcement">>, XData) of
×
1682
                       [] ->
1683
                           [];
×
1684
                       As ->
1685
                           [#xdata_field{var = <<"body">>, values = As}]
×
1686
                   end,
1687
    case Subject ++ Announcement of
×
1688
        [] ->
1689
            ok;
×
1690
        Fields ->
1691
            Request = #adhoc_command{node = ?NS_ADMINX(<<"announce-allhosts">>),
×
1692
                                     action = complete,
1693
                                     xdata = #xdata{type = submit,
1694
                                                    fields = Fields}},
1695
            To = jid:make(Host),
×
1696
            mod_announce:announce_commands(empty, From, To, Request)
×
1697
    end,
1698
    Time = timer:seconds(Delay),
×
1699
    Node = misc:binary_to_atom(ENode),
×
1700
    {ok, _} = timer:apply_after(Time, ejabberd_cluster, call, [Node, init, Action, []]),
×
1701
    {result, undefined}.
×
1702

1703
-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} | not_found.
1704
get_last_info(User, Server) ->
1705
    case gen_mod:is_loaded(Server, mod_last) of
×
1706
      true -> mod_last:get_last_info(User, Server);
×
1707
      false -> not_found
×
1708
    end.
1709

1710
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1711
-spec adhoc_sm_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> adhoc_command() |
1712
                                                                           {error, stanza_error()}.
1713
adhoc_sm_commands(_Acc, From,
1714
                  #jid{user = User, server = Server, lserver = LServer},
1715
                  #adhoc_command{lang = Lang, node = <<"config">>,
1716
                                 action = Action, xdata = XData} = Request) ->
1717
    case acl_match_rule(LServer, From) of
×
1718
        deny ->
1719
            {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
×
1720
        allow ->
1721
            ActionIsExecute = Action == execute orelse Action == complete,
×
1722
            if Action == cancel ->
×
1723
                    xmpp_util:make_adhoc_response(
×
1724
                      Request, #adhoc_command{status = canceled});
1725
               XData == undefined, ActionIsExecute ->
1726
                    case get_sm_form(User, Server, <<"config">>, Lang) of
×
1727
                        {result, Form} ->
1728
                            xmpp_util:make_adhoc_response(
×
1729
                              Request, #adhoc_command{status = executing,
1730
                                                      xdata = Form});
1731
                        {error, Error} ->
1732
                            {error, Error}
×
1733
                    end;
1734
               XData /= undefined, ActionIsExecute ->
1735
                    set_sm_form(User, Server, <<"config">>, Request);
×
1736
               true ->
1737
                    Txt = ?T("Unexpected action"),
×
1738
                    {error, xmpp:err_bad_request(Txt, Lang)}
×
1739
            end
1740
    end;
1741
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
×
1742

1743
-spec get_sm_form(binary(), binary(), binary(), binary()) -> {result, xdata()} |
1744
                                                             {error, stanza_error()}.
1745
get_sm_form(User, Server, <<"config">>, Lang) ->
1746
    {result,
×
1747
     #xdata{type = form,
1748
            title = <<(tr(Lang, ?T("Administration of ")))/binary, User/binary>>,
1749
            fields =
1750
                [?HFIELD(),
1751
                 #xdata_field{
1752
                    type = 'list-single',
1753
                    label = tr(Lang, ?T("Action on user")),
1754
                    var = <<"action">>,
1755
                    values = [<<"edit">>],
1756
                    options = [#xdata_option{
1757
                                  label = tr(Lang, ?T("Edit Properties")),
1758
                                  value = <<"edit">>},
1759
                               #xdata_option{
1760
                                  label = tr(Lang, ?T("Remove User")),
1761
                                  value = <<"remove">>}]},
1762
                 ?XFIELD('text-private', ?T("Password"),
1763
                         <<"password">>,
1764
                         ejabberd_auth:get_password_s(User, Server))]}};
1765
get_sm_form(_User, _Server, _Node, _Lang) ->
1766
    {error, xmpp:err_service_unavailable()}.
×
1767

1768
-spec set_sm_form(binary(), binary(), binary(), adhoc_command()) -> adhoc_command() |
1769
                                                                    {error, stanza_error()}.
1770
set_sm_form(User, Server, <<"config">>,
1771
            #adhoc_command{lang = Lang, node = Node,
1772
                           sid = SessionID, xdata = XData}) ->
1773
    Response = #adhoc_command{lang = Lang, node = Node,
×
1774
                              sid = SessionID, status = completed},
1775
    case xmpp_util:get_xdata_values(<<"action">>, XData) of
×
1776
        [<<"edit">>] ->
1777
            case xmpp_util:get_xdata_values(<<"password">>, XData) of
×
1778
                [Password] ->
1779
                    ejabberd_auth:set_password(User, Server, Password),
×
1780
                    xmpp_util:make_adhoc_response(Response);
×
1781
                _ ->
1782
                    Txt = ?T("No 'password' found in data form"),
×
1783
                    {error, xmpp:err_not_acceptable(Txt, Lang)}
×
1784
            end;
1785
        [<<"remove">>] ->
1786
            ejabberd_auth:remove_user(User, Server),
×
1787
            xmpp_util:make_adhoc_response(Response);
×
1788
        _ ->
1789
            Txt = ?T("Incorrect value of 'action' in data form"),
×
1790
            {error, xmpp:err_not_acceptable(Txt, Lang)}
×
1791
    end;
1792
set_sm_form(_User, _Server, _Node, _Request) ->
1793
    {error, xmpp:err_service_unavailable()}.
×
1794

1795
-spec tr(binary(), binary()) -> binary().
1796
tr(Lang, Text) ->
1797
    translate:translate(Lang, Text).
×
1798

1799
-spec mod_opt_type(atom()) -> econf:validator().
1800
mod_opt_type(access) ->
1801
    econf:acl().
117✔
1802

1803
-spec mod_options(binary()) -> [{services, [tuple()]} | {atom(), any()}].
1804
mod_options(_Host) ->
1805
    [{access, configure}].
117✔
1806

1807
%% All ad-hoc commands implemented by mod_configure are available as API Commands:
1808
%% - add-user                  -> register
1809
%% - delete-user               -> unregister
1810
%% - disable-user              -> ban_account
1811
%% - reenable-user             -> unban_account
1812
%% - end-user-session          -> kick_session / kick_user
1813
%% - change-user-password      -> change_password
1814
%% - get-user-lastlogin        -> get_last
1815
%% - get-user-roster           -> get_roster
1816
%% - get-user-lastlogin        -> get_last
1817
%% - user-stats                -> user_sessions_info
1818
%% - edit-blacklist            -> not ad-hoc or API command available !!!
1819
%% - edit-whitelist            -> not ad-hoc or API command available !!!
1820
%% - get-registered-users-num  -> stats
1821
%% - get-disabled-users-num    -> count_banned
1822
%% - get-online-users-num      -> stats
1823
%% - get-active-users-num      -> status_num
1824
%% - get-idle-users-num        -> status_num
1825
%% - get-registered-users-list -> registered_users
1826
%% - get-disabled-users-list   -> list_banned
1827
%% - get-online-users-list     -> connected_users
1828
%% - get-active-users          -> status_list
1829
%% - get-idle-users            -> status_list
1830
%% - stopped nodes             -> list_cluster_detailed
1831
%% - DB                        -> mnesia_list_tables and mnesia_table_change_storage
1832
%% - edit-admin                -> not ad-hoc or API command available !!!
1833
%% - restart                   -> restart_kindly
1834
%% - shutdown                  -> stop_kindly
1835
%% - backup                    -> backup
1836
%% - restore                   -> restore
1837
%% - textfile                  -> dump
1838
%% - import/file               -> import_file
1839
%% - import/dir                -> import_dir
1840

1841
%%
1842
%% An exclusive feature available only in this module is to list items and discover them:
1843
%% - outgoing s2s
1844
%% - online users
1845
%% - all users
1846

1847
mod_doc() ->
1848
    #{desc =>
×
1849
          [?T("The module provides server configuration functionalities using "
1850
              "https://xmpp.org/extensions/xep-0030.html[XEP-0030: Service Discovery] and "
1851
              "https://xmpp.org/extensions/xep-0050.html[XEP-0050: Ad-Hoc Commands]:"),
1852
           "",
1853
           "- List and discover outgoing s2s, online client sessions and all registered accounts",
1854
           "- Most of the ad-hoc commands defined in "
1855
           "  https://xmpp.org/extensions/xep-0133.html[XEP-0133: Service Administration]",
1856
           "- Additional custom ad-hoc commands specific to ejabberd",
1857
           "",
1858
           ?T("Ad-hoc commands from XEP-0133 that behave differently to the XEP:"),
1859
           "",
1860
           " - `get-user-roster`: returns standard fields instead of roster items that client cannot display",
1861
           "",
1862
           ?T("Those ad-hoc commands from XEP-0133 do not include in the response "
1863
              "the client that executed the command:"),
1864
           "",
1865
           " - `get-active-users-num`",
1866
           " - `get-idle-users-num`",
1867
           " - `get-active-users`",
1868
           " - `get-idle-users`",
1869
           "",
1870
           ?T("Those ad-hoc commands from XEP-0133 are not implemented:"),
1871
           "",
1872
           " - `edit-blacklist`",
1873
           " - `edit-whitelist`",
1874
           " - `edit-admin`",
1875
           "",
1876
           ?T("This module requires _`mod_adhoc`_ (to execute the commands), "
1877
              "and recommends _`mod_disco`_ (to discover the commands). "),
1878
           "",
1879
           ?T("Please notice that all the ad-hoc commands implemented by this module "
1880
              "have an equivalent "
1881
              "https://docs.ejabberd.im/developer/ejabberd-api/[API Command] "
1882
              "that you can execute using _`mod_adhoc_api`_ or any other API frontend.")],
1883
      note => "improved in 25.10",
1884
      opts =>
1885
          [{access,
1886
            #{value => ?T("AccessName"),
1887
              note => "added in 25.03",
1888
              desc =>
1889
                  ?T("This option defines which access rule will be used to "
1890
                     "control who is allowed to access the features provided by this module. "
1891
                     "The default value is 'configure'.")}}],
1892
      example =>
1893
          ["acl:",
1894
           "  admin:",
1895
           "    user: sun@localhost",
1896
           "",
1897
           "access_rules:",
1898
           "  configure:",
1899
           "    allow: admin",
1900
           "",
1901
           "modules:",
1902
           "  mod_configure:",
1903
           "    access: configure"]}.
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