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

processone / ejabberd / 1296

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

push

github

badlop
mod_conversejs: Cosmetic change: sort paths alphabetically

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

11245 existing lines in 174 files now uncovered.

15580 of 46421 relevant lines covered (33.56%)

1074.56 hits per line

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

39.25
/src/mod_register.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_register.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Inband registration support
5
%%% Created :  8 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2026   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_register).
27

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

30
-protocol({xep, 77, '2.4', '0.1.0', "complete", ""}).
31

32
-behaviour(gen_mod).
33

34
-export([start/2, stop/1, reload/3, stream_feature_register/2,
35
         c2s_unauthenticated_packet/2, try_register/4, try_register/5,
36
         process_iq/1, send_registration_notifications/3,
37
         mod_opt_type/1, mod_options/1, depends/2,
38
         format_error/1, mod_doc/0]).
39

40
-deprecated({try_register, 4}).
41

42
-include("logger.hrl").
43
-include_lib("xmpp/include/xmpp.hrl").
44
-include("translate.hrl").
45

46
start(_Host, _Opts) ->
47
    ejabberd_mnesia:create(?MODULE, mod_register_ip,
99✔
48
                        [{ram_copies, [node()]}, {local_content, true},
49
                         {attributes, [key, value]}]),
50
    {ok, [{iq_handler, ejabberd_local, ?NS_REGISTER, process_iq},
99✔
51
          {iq_handler, ejabberd_sm, ?NS_REGISTER, process_iq},
52
          {hook, c2s_pre_auth_features, stream_feature_register, 50},
53
          {hook, c2s_unauthenticated_packet, c2s_unauthenticated_packet, 50}]}.
54

55
stop(_Host) ->
56
    ok.
99✔
57

58
reload(_Host, _NewOpts, _OldOpts) ->
59
    ok.
×
60

61
depends(_Host, _Opts) ->
62
    [].
117✔
63

64
-spec stream_feature_register([xmpp_element()], binary()) -> [xmpp_element()].
65
stream_feature_register(Acc, Host) ->
66
    case {mod_register_opt:access(Host),
2,057✔
67
          mod_register_opt:ip_access(Host),
68
          mod_register_opt:redirect_url(Host)} of
69
        {none, _, undefined} -> Acc;
×
70
        {_, none, undefined} -> Acc;
×
71
        {_, _, _} -> [#feature_register{}|Acc]
2,057✔
72
    end.
73

74
c2s_unauthenticated_packet(#{ip := IP, server := Server} = State,
75
                           #iq{type = T, sub_els = [_]} = IQ)
76
  when T == set; T == get ->
UNCOV
77
    try xmpp:try_subtag(IQ, #register{}) of
17✔
78
        #register{} = Register ->
UNCOV
79
            {Address, _} = IP,
16✔
UNCOV
80
            IQ1 = xmpp:set_els(IQ, [Register]),
16✔
UNCOV
81
            IQ2 = xmpp:set_from_to(IQ1, jid:make(<<>>), jid:make(Server)),
16✔
UNCOV
82
            ResIQ = process_iq(IQ2, Address),
16✔
UNCOV
83
            ResIQ1 = xmpp:set_from_to(ResIQ, jid:make(Server), undefined),
16✔
UNCOV
84
            {stop, ejabberd_c2s:send(State, ResIQ1)};
16✔
85
        false ->
UNCOV
86
            State
1✔
87
    catch _:{xmpp_codec, Why} ->
88
            Txt = xmpp:io_format_error(Why),
×
89
            Lang = maps:get(lang, State),
×
90
            Err = make_stripped_error(IQ, xmpp:err_bad_request(Txt, Lang)),
×
91
            {stop, ejabberd_c2s:send(State, Err)}
×
92
    end;
93
c2s_unauthenticated_packet(State, _) ->
UNCOV
94
    State.
2✔
95

96
process_iq(#iq{from = From} = IQ) ->
97
    process_iq(IQ, jid:tolower(From)).
25✔
98

99
process_iq(#iq{from = From, to = To} = IQ, Source) ->
100
    IsCaptchaEnabled =
41✔
101
        case mod_register_opt:captcha_protected(To#jid.lserver) of
102
            true -> true;
×
103
            false -> false
41✔
104
        end,
105
    Server = To#jid.lserver,
41✔
106
    Access = mod_register_opt:access_remove(Server),
41✔
107
    Remove = case {acl:match_rule(Server, Access, From), From#jid.lserver} of
41✔
108
                 {allow, Server} ->
109
                     allow;
25✔
110
                 {_, _} ->
UNCOV
111
                     deny
16✔
112
             end,
113
    process_iq(IQ, Source, IsCaptchaEnabled, Remove == allow).
41✔
114

115
process_iq(#iq{type = set, lang = Lang,
116
               sub_els = [#register{remove = true}]} = IQ,
117
           _Source, _IsCaptchaEnabled, _AllowRemove = false) ->
118
    Txt = ?T("Access denied by service policy"),
×
119
    make_stripped_error(IQ, xmpp:err_forbidden(Txt, Lang));
×
120
process_iq(#iq{type = set, lang = Lang, to = To, from = From,
121
               sub_els = [#register{remove = true,
122
                                    username = User,
123
                                    password = Password}]} = IQ,
124
           _Source, _IsCaptchaEnabled, _AllowRemove = true) ->
125
    Server = To#jid.lserver,
9✔
126
    if is_binary(User) ->
9✔
127
            case From of
×
128
                #jid{user = User, lserver = Server} ->
129
                    ResIQ = xmpp:make_iq_result(IQ),
×
130
                    ejabberd_router:route(ResIQ),
×
131
                    ejabberd_auth:remove_user(User, Server),
×
132
                    ignore;
×
133
                _ ->
134
                    if is_binary(Password) ->
×
135
                            case ejabberd_auth:check_password(
×
136
                                   User, <<"">>, Server, Password) of
137
                                true ->
138
                                    ResIQ = xmpp:make_iq_result(IQ),
×
139
                                    ejabberd_router:route(ResIQ),
×
140
                                    ejabberd_auth:remove_user(User, Server),
×
141
                                    ignore;
×
142
                                false ->
143
                                    Txt = ?T("Incorrect password"),
×
144
                                    make_stripped_error(
×
145
                                      IQ, xmpp:err_forbidden(Txt, Lang))
146
                            end;
147
                       true ->
148
                            Txt = ?T("No 'password' found in this query"),
×
149
                            make_stripped_error(IQ, xmpp:err_bad_request(Txt, Lang))
×
150
                    end
151
            end;
152
       true ->
153
            case From of
9✔
154
                #jid{luser = LUser, lserver = Server} ->
155
                    ResIQ = xmpp:make_iq_result(IQ),
9✔
156
                    ejabberd_router:route(xmpp:set_from_to(ResIQ, From, From)),
9✔
157
                    ejabberd_auth:remove_user(LUser, Server),
9✔
158
                    ignore;
9✔
159
                _ ->
160
                    Txt = ?T("The query is only allowed from local users"),
×
161
                    make_stripped_error(IQ, xmpp:err_not_allowed(Txt, Lang))
×
162
            end
163
    end;
164
process_iq(#iq{type = set, to = To,
165
               sub_els = [#register{username = User,
166
                                    password = Password}]} = IQ,
167
           Source, IsCaptchaEnabled, _AllowRemove) when is_binary(User),
168
                                                        is_binary(Password) ->
UNCOV
169
    Server = To#jid.lserver,
24✔
UNCOV
170
    try_register_or_set_password(
24✔
171
      User, Server, Password, IQ, Source, not IsCaptchaEnabled);
172
process_iq(#iq{type = set, to = To,
173
               lang = Lang, sub_els = [#register{xdata = #xdata{} = X}]} = IQ,
174
           Source, true, _AllowRemove) ->
175
    Server = To#jid.lserver,
×
176
    XdataC = xmpp_util:set_xdata_field(
×
177
           #xdata_field{
178
              var = <<"FORM_TYPE">>,
179
              type = hidden, values = [?NS_CAPTCHA]},
180
           X),
181
    case ejabberd_captcha:process_reply(XdataC) of
×
182
        ok ->
183
            case process_xdata_submit(X) of
×
184
                {ok, User, Password} ->
185
                    try_register_or_set_password(
×
186
                      User, Server, Password, IQ, Source, true);
187
                _ ->
188
                    Txt = ?T("Incorrect data form"),
×
189
                    make_stripped_error(IQ, xmpp:err_bad_request(Txt, Lang))
×
190
            end;
191
        {error, malformed} ->
192
            Txt = ?T("Incorrect CAPTCHA submit"),
×
193
            make_stripped_error(IQ, xmpp:err_bad_request(Txt, Lang));
×
194
        _ ->
195
            ErrText = ?T("The CAPTCHA verification has failed"),
×
196
            make_stripped_error(IQ, xmpp:err_not_allowed(ErrText, Lang))
×
197
    end;
198
process_iq(#iq{type = set} = IQ, _Source, _IsCaptchaEnabled, _AllowRemove) ->
199
    make_stripped_error(IQ, xmpp:err_bad_request());
×
200
process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
201
           Source, IsCaptchaEnabled, _AllowRemove) ->
UNCOV
202
    Server = To#jid.lserver,
8✔
UNCOV
203
    {IsRegistered, Username} =
8✔
204
        case From of
205
            #jid{user = User, lserver = Server} ->
206
                case ejabberd_auth:user_exists(User, Server) of
×
207
                    true ->
208
                        {true, User};
×
209
                    false ->
210
                        {false, User}
×
211
                end;
212
            _ ->
UNCOV
213
                {false, <<"">>}
8✔
214
        end,
UNCOV
215
    Instr = translate:translate(
8✔
216
              Lang, ?T("Choose a username and password to register "
217
                       "with this server")),
UNCOV
218
    URL = mod_register_opt:redirect_url(Server),
8✔
UNCOV
219
    if (URL /= undefined) and not IsRegistered ->
8✔
220
            Desc = str:translate_and_format(Lang, ?T("To register, visit ~s"), [URL]),
×
221
            xmpp:make_iq_result(
×
222
              IQ, #register{instructions = Desc,
223
                            sub_els = [#oob_x{url = URL}]});
224
       IsCaptchaEnabled and not IsRegistered ->
225
            TopInstr = translate:translate(
×
226
                         Lang, ?T("You need a client that supports x:data "
227
                                  "and CAPTCHA to register")),
228
            UField = #xdata_field{type = 'text-single',
×
229
                                  label = translate:translate(Lang, ?T("User")),
230
                                  var = <<"username">>,
231
                                  required = true},
232
            PField = #xdata_field{type = 'text-private',
×
233
                                  label = translate:translate(Lang, ?T("Password")),
234
                                  var = <<"password">>,
235
                                  required = true},
236
            X = #xdata{type = form, instructions = [Instr],
×
237
                       fields = [UField, PField]},
238
            case ejabberd_captcha:create_captcha_x(ID, To, Lang, Source, X) of
×
239
                {ok, CaptchaEls} ->
240
                    {value, XdataC, CaptchaEls2} = lists:keytake(xdata, 1, CaptchaEls),
×
241
                    Xdata = xmpp_util:set_xdata_field(
×
242
                               #xdata_field{
243
                                  var = <<"FORM_TYPE">>,
244
                                  type = hidden, values = [?NS_REGISTER]},
245
                               XdataC),
246
                    xmpp:make_iq_result(
×
247
                      IQ, #register{instructions = TopInstr,
248
                                    sub_els = [Xdata | CaptchaEls2]});
249
                {error, limit} ->
250
                    ErrText = ?T("Too many CAPTCHA requests"),
×
251
                    make_stripped_error(
×
252
                      IQ, xmpp:err_resource_constraint(ErrText, Lang));
253
                _Err ->
254
                    ErrText = ?T("Unable to generate a CAPTCHA"),
×
255
                    make_stripped_error(
×
256
                      IQ, xmpp:err_internal_server_error(ErrText, Lang))
257
            end;
258
       true ->
UNCOV
259
            xmpp:make_iq_result(
8✔
260
              IQ,
261
              #register{instructions = Instr,
262
                        username = Username,
263
                        password = <<"">>,
264
                        registered = IsRegistered})
265
    end.
266

267
try_register_or_set_password(User, Server, Password,
268
                             #iq{from = From, lang = Lang} = IQ,
269
                             Source, CaptchaSucceed) ->
UNCOV
270
    case {jid:nodeprep(User), From} of
24✔
271
        {error, _} ->
272
            Err = xmpp:err_jid_malformed(format_error(invalid_jid), Lang),
×
273
            make_stripped_error(IQ, Err);
×
274
        {UserP, #jid{user = User2, lserver = Server}} when UserP == User2 ->
UNCOV
275
            try_set_password(User, Server, Password, IQ);
16✔
276
        _ when CaptchaSucceed ->
UNCOV
277
            case check_from(From, Server) of
8✔
278
                allow ->
UNCOV
279
                    case try_register(User, Server, Password, Source, ?MODULE, Lang) of
8✔
280
                        ok ->
UNCOV
281
                            xmpp:make_iq_result(IQ);
8✔
282
                        {error, Error} ->
283
                            make_stripped_error(IQ, Error)
×
284
                    end;
285
                deny ->
286
                    Txt = ?T("Access denied by service policy"),
×
287
                    make_stripped_error(IQ, xmpp:err_forbidden(Txt, Lang))
×
288
            end;
289
        _ ->
290
            make_stripped_error(IQ, xmpp:err_not_allowed())
×
291
    end.
292

293
try_set_password(User, Server, Password) ->
UNCOV
294
    case is_strong_password(Server, Password) of
16✔
295
        true ->
UNCOV
296
            ejabberd_auth:set_password(User, Server, Password);
16✔
297
        error_preparing_password ->
298
            {error, invalid_password};
×
299
        false ->
300
            {error, weak_password}
×
301
    end.
302

303
try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) ->
UNCOV
304
    case try_set_password(User, Server, Password) of
16✔
305
        ok ->
UNCOV
306
            ?INFO_MSG("~ts has changed password from ~ts",
16✔
307
                      [jid:encode({User, Server, <<"">>}),
308
                       ejabberd_config:may_hide_data(
UNCOV
309
                         misc:ip_to_list(maps:get(ip, M, {0,0,0,0})))]),
16✔
UNCOV
310
            xmpp:make_iq_result(IQ);
16✔
311
        {error, not_allowed} ->
312
            Txt = ?T("Changing password is not allowed"),
×
313
            make_stripped_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
314
        {error, invalid_jid = Why} ->
315
            make_stripped_error(IQ, xmpp:err_jid_malformed(format_error(Why), Lang));
×
316
        {error, invalid_password = Why} ->
317
            make_stripped_error(IQ, xmpp:err_not_allowed(format_error(Why), Lang));
×
318
        {error, weak_password = Why} ->
319
            make_stripped_error(IQ, xmpp:err_not_acceptable(format_error(Why), Lang));
×
320
        {error, db_failure = Why} ->
321
            make_stripped_error(IQ, xmpp:err_internal_server_error(format_error(Why), Lang))
×
322
    end.
323

324
try_register(User, Server, Password, SourceRaw, Module) ->
UNCOV
325
    Modules = mod_register_opt:allow_modules(Server),
8✔
UNCOV
326
    case (Modules == all) orelse lists:member(Module, Modules) of
8✔
UNCOV
327
        true -> try_register(User, Server, Password, SourceRaw);
8✔
328
        false -> {error, eaccess}
×
329
    end.
330

331
try_register(User, Server, Password, SourceRaw) ->
UNCOV
332
    case jid:is_nodename(User) of
8✔
333
        false ->
334
            {error, invalid_jid};
×
335
        true ->
UNCOV
336
            case check_access(User, Server, SourceRaw) of
8✔
337
                deny ->
338
                    {error, eaccess};
×
339
                allow ->
UNCOV
340
                    Source = may_remove_resource(SourceRaw),
8✔
UNCOV
341
                    case check_timeout(Source) of
8✔
342
                        true ->
UNCOV
343
                            case is_strong_password(Server, Password) of
8✔
344
                                true ->
UNCOV
345
                                    case ejabberd_auth:try_register(
8✔
346
                                           User, Server, Password) of
347
                                        ok ->
UNCOV
348
                                            ok;
8✔
349
                                        {error, _} = Err ->
350
                                            remove_timeout(Source),
×
351
                                            Err
×
352
                                    end;
353
                                false ->
354
                                    remove_timeout(Source),
×
355
                                    {error, weak_password};
×
356
                                error_preparing_password ->
357
                                    remove_timeout(Source),
×
358
                                    {error, invalid_password}
×
359
                            end;
360
                        false ->
361
                            {error, wait}
×
362
                    end
363
            end
364
    end.
365

366
try_register(User, Server, Password, SourceRaw, Module, Lang) ->
UNCOV
367
    case try_register(User, Server, Password, SourceRaw, Module) of
8✔
368
        ok ->
UNCOV
369
            JID = jid:make(User, Server),
8✔
UNCOV
370
            Source = may_remove_resource(SourceRaw),
8✔
UNCOV
371
            ?INFO_MSG("The account ~ts was registered from IP address ~ts",
8✔
372
                      [jid:encode({User, Server, <<"">>}),
UNCOV
373
                       ejabberd_config:may_hide_data(ip_to_string(Source))]),
8✔
UNCOV
374
            send_welcome_message(JID),
8✔
UNCOV
375
            send_registration_notifications(?MODULE, JID, Source);
8✔
376
        {error, invalid_jid = Why} ->
377
            {error, xmpp:err_jid_malformed(format_error(Why), Lang)};
×
378
        {error, eaccess = Why} ->
379
            {error, xmpp:err_forbidden(format_error(Why), Lang)};
×
380
        {error, wait = Why} ->
381
            {error, xmpp:err_resource_constraint(format_error(Why), Lang)};
×
382
        {error, weak_password = Why} ->
383
            {error, xmpp:err_not_acceptable(format_error(Why), Lang)};
×
384
        {error, invalid_password = Why} ->
385
            {error, xmpp:err_not_acceptable(format_error(Why), Lang)};
×
386
        {error, not_allowed = Why} ->
387
            {error, xmpp:err_not_allowed(format_error(Why), Lang)};
×
388
        {error, exists = Why} ->
389
            {error, xmpp:err_conflict(format_error(Why), Lang)};
×
390
        {error, db_failure = Why} ->
391
            {error, xmpp:err_internal_server_error(format_error(Why), Lang)}
×
392
    end.
393

394
format_error(invalid_jid) ->
395
    ?T("Malformed username");
×
396
format_error(eaccess) ->
397
    ?T("Access denied by service policy");
×
398
format_error(wait) ->
399
    ?T("Users are not allowed to register accounts so quickly");
×
400
format_error(weak_password) ->
401
    ?T("The password is too weak");
×
402
format_error(invalid_password) ->
403
    ?T("The password contains unacceptable characters");
×
404
format_error(not_allowed) ->
405
    ?T("Not allowed");
×
406
format_error(exists) ->
407
    ?T("User already exists");
×
408
format_error(db_failure) ->
409
    ?T("Database failure");
×
410
format_error(Unexpected) ->
411
    list_to_binary(io_lib:format(?T("Unexpected error condition: ~p"), [Unexpected])).
×
412

413
send_welcome_message(JID) ->
UNCOV
414
    Host = JID#jid.lserver,
8✔
UNCOV
415
    case mod_register_opt:welcome_message(Host) of
8✔
416
      {<<"">>, <<"">>} -> ok;
×
417
      {Subj, Body} ->
UNCOV
418
          ejabberd_router:route(
8✔
419
            #message{from = jid:make(Host),
420
                     to = JID,
421
                     type = chat,
422
                     subject = xmpp:mk_text(Subj),
423
                     body = xmpp:mk_text(Body)})
424
    end.
425

426
send_registration_notifications(Mod, UJID, Source) ->
UNCOV
427
    Host = UJID#jid.lserver,
8✔
UNCOV
428
    case mod_register_opt:registration_watchers(Host) of
8✔
UNCOV
429
        [] -> ok;
8✔
430
        JIDs when is_list(JIDs) ->
431
            Body =
×
432
                (str:format("[~s] The account ~s was registered from "
433
                                               "IP address ~s on node ~w using ~p.",
434
                                               [get_time_string(),
435
                                                jid:encode(UJID),
436
                                                ejabberd_config:may_hide_data(
437
                                                  ip_to_string(Source)),
438
                                                node(), Mod])),
439
            lists:foreach(
×
440
              fun(JID) ->
441
                      ejabberd_router:route(
×
442
                        #message{from = jid:make(Host),
443
                                 to = JID,
444
                                 type = chat,
445
                                 body = xmpp:mk_text(Body)})
446
              end, JIDs)
447
    end.
448

449
check_from(#jid{user = <<"">>, server = <<"">>},
450
           _Server) ->
UNCOV
451
    allow;
8✔
452
check_from(JID, Server) ->
453
    Access = mod_register_opt:access_from(Server),
×
454
    acl:match_rule(Server, Access, JID).
×
455

456
check_timeout(undefined) -> true;
×
457
check_timeout(Source) ->
UNCOV
458
    Timeout = ejabberd_option:registration_timeout(),
8✔
UNCOV
459
    if is_integer(Timeout) ->
8✔
460
           Priority = -erlang:system_time(millisecond),
×
461
           CleanPriority = Priority + Timeout,
×
462
           F = fun () ->
×
463
                       Treap = case mnesia:read(mod_register_ip, treap, write)
×
464
                                   of
465
                                 [] -> treap:empty();
×
466
                                 [{mod_register_ip, treap, T}] -> T
×
467
                               end,
468
                       Treap1 = clean_treap(Treap, CleanPriority),
×
469
                       case treap:lookup(Source, Treap1) of
×
470
                         error ->
471
                             Treap2 = treap:insert(Source, Priority, [],
×
472
                                                   Treap1),
473
                             mnesia:write({mod_register_ip, treap, Treap2}),
×
474
                             true;
×
475
                         {ok, _, _} ->
476
                             mnesia:write({mod_register_ip, treap, Treap1}),
×
477
                             false
×
478
                       end
479
               end,
480
           case mnesia:transaction(F) of
×
481
             {atomic, Res} -> Res;
×
482
             {aborted, Reason} ->
483
                 ?ERROR_MSG("timeout check error: ~p~n", [Reason]),
×
484
                 true
×
485
           end;
UNCOV
486
       true -> true
8✔
487
    end.
488

489
clean_treap(Treap, CleanPriority) ->
490
    case treap:is_empty(Treap) of
×
491
      true -> Treap;
×
492
      false ->
493
          {_Key, Priority, _Value} = treap:get_root(Treap),
×
494
          if Priority > CleanPriority ->
×
495
                 clean_treap(treap:delete_root(Treap), CleanPriority);
×
496
             true -> Treap
×
497
          end
498
    end.
499

500
remove_timeout(undefined) -> true;
×
501
remove_timeout(Source) ->
502
    Timeout = ejabberd_option:registration_timeout(),
×
503
    if is_integer(Timeout) ->
×
504
           F = fun () ->
×
505
                       Treap = case mnesia:read(mod_register_ip, treap, write)
×
506
                                   of
507
                                 [] -> treap:empty();
×
508
                                 [{mod_register_ip, treap, T}] -> T
×
509
                               end,
510
                       Treap1 = treap:delete(Source, Treap),
×
511
                       mnesia:write({mod_register_ip, treap, Treap1}),
×
512
                       ok
×
513
               end,
514
           case mnesia:transaction(F) of
×
515
             {atomic, ok} -> ok;
×
516
             {aborted, Reason} ->
517
                 ?ERROR_MSG("Mod_register: timeout remove error: "
×
518
                            "~p~n",
519
                            [Reason]),
×
520
                 ok
×
521
           end;
522
       true -> ok
×
523
    end.
524

525
ip_to_string({_, _, _} = USR) ->
526
    jid:encode(USR);
×
527
ip_to_string(Source) when is_tuple(Source) ->
UNCOV
528
    misc:ip_to_list(Source);
8✔
529
ip_to_string(undefined) -> <<"undefined">>;
×
530
ip_to_string(_) -> <<"unknown">>.
×
531

532
get_time_string() -> write_time(erlang:localtime()).
×
533
%% Function copied from ejabberd_logger_h.erl and customized
534

535
write_time({{Y, Mo, D}, {H, Mi, S}}) ->
536
    io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
×
537
                  [Y, Mo, D, H, Mi, S]).
538

539
process_xdata_submit(X) ->
540
    case {xmpp_util:get_xdata_values(<<"username">>, X),
×
541
          xmpp_util:get_xdata_values(<<"password">>, X)} of
542
        {[User], [Pass]} -> {ok, User, Pass};
×
543
        _ -> error
×
544
    end.
545

546
is_strong_password(Server, Password) ->
UNCOV
547
    case jid:resourceprep(Password) of
24✔
548
        PP when is_binary(PP) ->
UNCOV
549
            is_strong_password2(Server, Password);
24✔
550
        error ->
551
            error_preparing_password
×
552
    end.
553

554
is_strong_password2(Server, Password) ->
UNCOV
555
    LServer = jid:nameprep(Server),
24✔
UNCOV
556
    case mod_register_opt:password_strength(LServer) of
24✔
557
        0 ->
UNCOV
558
            true;
24✔
559
        Entropy ->
560
            ejabberd_auth:entropy(Password) >= Entropy
×
561
    end.
562

563
make_stripped_error(IQ, Err) ->
564
    xmpp:make_error(xmpp:remove_subtag(IQ, #register{}), Err).
×
565

566
%%%
567
%%% ip_access management
568
%%%
569

570
may_remove_resource({_, _, _} = From) ->
571
    jid:remove_resource(From);
×
UNCOV
572
may_remove_resource(From) -> From.
16✔
573

574
get_ip_access(Host) ->
UNCOV
575
    mod_register_opt:ip_access(Host).
8✔
576

577
check_ip_access(Server, {User, Server, Resource}, IPAccess) ->
578
    case ejabberd_sm:get_user_ip(User, Server, Resource) of
×
579
        {IPAddress, _PortNumber} ->
580
            check_ip_access(Server, IPAddress, IPAccess);
×
581
        _ ->
582
            deny
×
583
    end;
584
check_ip_access(_Server, undefined, _IPAccess) ->
585
    deny;
×
586
check_ip_access(Server, IPAddress, IPAccess) ->
UNCOV
587
    acl:match_rule(Server, IPAccess, IPAddress).
8✔
588

589
check_access(User, Server, Source) ->
UNCOV
590
    JID = jid:make(User, Server),
8✔
UNCOV
591
    Access = mod_register_opt:access(Server),
8✔
UNCOV
592
    IPAccess = get_ip_access(Server),
8✔
UNCOV
593
    case acl:match_rule(Server, Access, JID) of
8✔
UNCOV
594
        allow -> check_ip_access(Server, Source, IPAccess);
8✔
595
        deny -> deny
×
596
    end.
597

598
mod_opt_type(access) ->
599
    econf:acl();
117✔
600
mod_opt_type(access_from) ->
601
    econf:acl();
117✔
602
mod_opt_type(access_remove) ->
603
    econf:acl();
117✔
604
mod_opt_type(allow_modules) ->
605
    econf:either(all, econf:list(econf:atom()));
117✔
606
mod_opt_type(captcha_protected) ->
607
    econf:bool();
117✔
608
mod_opt_type(ip_access) ->
609
    econf:acl();
117✔
610
mod_opt_type(password_strength) ->
611
    econf:number(0);
117✔
612
mod_opt_type(registration_watchers) ->
613
    econf:list(econf:jid());
117✔
614
mod_opt_type(welcome_message) ->
615
    econf:and_then(
117✔
616
      econf:options(
617
        #{subject => econf:binary(),
618
          body => econf:binary()}),
619
      fun(Opts) ->
620
              {proplists:get_value(subject, Opts, <<>>),
117✔
621
               proplists:get_value(body, Opts, <<>>)}
622
      end);
623
mod_opt_type(redirect_url) ->
624
    econf:url().
117✔
625

626
-spec mod_options(binary()) -> [{welcome_message, {binary(), binary()}} |
627
                                {atom(), term()}].
628
mod_options(_Host) ->
629
    [{access, all},
117✔
630
     {access_from, none},
631
     {access_remove, all},
632
     {allow_modules, all},
633
     {captcha_protected, false},
634
     {ip_access, all},
635
     {password_strength, 0},
636
     {registration_watchers, []},
637
     {redirect_url, undefined},
638
     {welcome_message, {<<>>, <<>>}}].
639

640
mod_doc() ->
641
    #{desc =>
×
642
          [?T("This module adds support for https://xmpp.org/extensions/xep-0077.html"
643
              "[XEP-0077: In-Band Registration]. "
644
              "This protocol enables end users to use an XMPP client to:"), "",
645
           ?T("* Register a new account on the server."), "",
646
           ?T("* Change the password from an existing account on the server."), "",
647
           ?T("* Delete an existing account on the server."), "",
648
           ?T("This module reads also the top-level _`registration_timeout`_ "
649
              "option defined globally for the server, "
650
              "so please check that option documentation too.")],
651
      opts =>
652
          [{access,
653
            #{value => ?T("AccessName"),
654
              desc =>
655
                  ?T("Specify rules to restrict what usernames can be registered. "
656
                     "If a rule returns 'deny' on the requested username, "
657
                     "registration of that user name is denied. There are no "
658
                     "restrictions by default. "
659
                     "If 'AccessName' is 'none', then registering new accounts using "
660
                     "In-Band Registration is disabled and the corresponding "
661
                     "stream feature is not announced to clients.")}},
662
           {access_from,
663
            #{value => ?T("AccessName"),
664
              desc =>
665
                  ?T("By default, ejabberd doesn't allow the client to register new accounts "
666
                     "from s2s or existing c2s sessions. You can change it by defining "
667
                     "access rule in this option. Use with care: allowing registration "
668
                     "from s2s leads to uncontrolled massive accounts creation by rogue users.")}},
669
           {access_remove,
670
            #{value => ?T("AccessName"),
671
              desc =>
672
                  ?T("Specify rules to restrict access for user unregistration. "
673
                     "By default any user is able to unregister their account.")}},
674
           {allow_modules,
675
            #{value => "all | [Module, ...]",
676
              note => "added in 21.12",
677
              desc =>
678
                  ?T("List of modules that can register accounts, or 'all'. "
679
                     "The default value is 'all', which is equivalent to "
680
                     "something like '[mod_register, mod_register_web]'.")}},
681
           {captcha_protected,
682
            #{value => "true | false",
683
              desc =>
684
                  ?T("Protect registrations with _`basic.md#captcha|CAPTCHA`_. "
685
                     "The default is 'false'.")}},
686
           {ip_access,
687
            #{value => ?T("AccessName"),
688
              desc =>
689
                  ?T("Define rules to allow or deny account registration depending "
690
                     "on the IP address of the XMPP client. The 'AccessName' should "
691
                     "be of type 'ip'. The default value is 'all'.")}},
692
           {password_strength,
693
            #{value => "Entropy",
694
              desc =>
695
                  ?T("This option sets the minimum "
696
                     "https://en.wikipedia.org/wiki/Entropy_(information_theory)"
697
                     "[Shannon entropy] for passwords. The value 'Entropy' is a "
698
                     "number of bits of entropy. The recommended minimum is 32 bits. "
699
                     "The default is '0', i.e. no checks are performed.")}},
700
           {registration_watchers,
701
            #{value => "[JID, ...]",
702
              desc =>
703
                  ?T("This option defines a list of JIDs which will be notified each "
704
                     "time a new account is registered.")}},
705
           {redirect_url,
706
            #{value => ?T("URL"),
707
              desc =>
708
                  ?T("This option enables registration redirection as described in "
709
                     "https://xmpp.org/extensions/xep-0077.html#redirect"
710
                     "[XEP-0077: In-Band Registration: Redirection].")}},
711
           {welcome_message,
712
            #{value => "{subject: Subject, body: Body}",
713
              desc =>
714
                  ?T("Set a welcome message that is sent to each newly registered account. "
715
                     "The message will have subject 'Subject' and text 'Body'."),
716
              example =>
717
                    ["modules:",
718
                     "  mod_register:",
719
                     "    welcome_message:",
720
                     "      subject: \"Welcome!\"",
721
                     "      body: |-",
722
                     "        Hi!",
723
                     "        Welcome to this XMPP server"]}}
724
          ]}.
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