• 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

22.89
/src/ejabberd_auth_sql.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : ejabberd_auth_sql.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose : Authentication via ODBC
5
%%% Created : 12 Dec 2004 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(ejabberd_auth_sql).
27

28

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

31
-behaviour(ejabberd_auth).
32
-behaviour(ejabberd_db_serialize).
33

34
-export([start/1, stop/1, set_password_multiple/3, try_register_multiple/3,
35
         get_users/2, count_users/2, get_password/2,
36
         remove_user/2, store_type/1, plain_password_required/1,
37
         export/1, which_users_exists/2, drop_password_type/2, set_password_instance/3]).
38
-export([sql_schemas/0]).
39
-export([serialize/3, deserialize_start/1, deserialize/2]).
40

41
-include_lib("xmpp/include/scram.hrl").
42
-include("logger.hrl").
43
-include("ejabberd_sql_pt.hrl").
44
-include("ejabberd_auth.hrl").
45
-include("ejabberd_db_serialize.hrl").
46

47
%%%----------------------------------------------------------------------
48
%%% API
49
%%%----------------------------------------------------------------------
50
start(Host) ->
UNCOV
51
    ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()),
6✔
UNCOV
52
    ok.
6✔
53

54
sql_schemas() ->
55
    [
UNCOV
56
        #sql_schema{
6✔
57
        version = 2,
58
        tables =
59
        [#sql_table{
60
            name = <<"users">>,
61
            columns =
62
            [#sql_column{name = <<"username">>, type = text},
63
                #sql_column{name = <<"server_host">>, type = text},
64
                #sql_column{name = <<"type">>, type = smallint},
65
                #sql_column{name = <<"password">>, type = text},
66
                #sql_column{name = <<"serverkey">>, type = {text, 128},
67
                    default = true},
68
                #sql_column{name = <<"salt">>, type = {text, 128},
69
                    default = true},
70
                #sql_column{name = <<"iterationcount">>, type = integer,
71
                    default = true},
72
                #sql_column{name = <<"created_at">>, type = timestamp,
73
                    default = true}],
74
            indices = [#sql_index{
75
                columns = [<<"server_host">>, <<"username">>, <<"type">>],
76
                unique = true}]}],
77
        update = [
78
            {add_column, <<"users">>, <<"type">>},
79
            {update_primary_key,<<"users">>,
80
                [<<"server_host">>, <<"username">>, <<"type">>]}
81
        ]},
82
    #sql_schema{
83
        version = 1,
84
        tables =
85
            [#sql_table{
86
                name = <<"users">>,
87
                columns =
88
                    [#sql_column{name = <<"username">>, type = text},
89
                     #sql_column{name = <<"server_host">>, type = text},
90
                     #sql_column{name = <<"password">>, type = text},
91
                     #sql_column{name = <<"serverkey">>, type = {text, 128},
92
                                 default = true},
93
                     #sql_column{name = <<"salt">>, type = {text, 128},
94
                                 default = true},
95
                     #sql_column{name = <<"iterationcount">>, type = integer,
96
                                 default = true},
97
                     #sql_column{name = <<"created_at">>, type = timestamp,
98
                                 default = true}],
99
                indices = [#sql_index{
100
                              columns = [<<"server_host">>, <<"username">>],
101
                              unique = true}]}]}].
102

103
stop(_Host) -> ok.
×
104

105
plain_password_required(Server) ->
UNCOV
106
    store_type(Server) == scram.
30✔
107

108
store_type(Server) ->
UNCOV
109
    ejabberd_auth:password_format(Server).
2,766✔
110

111
hash_to_num(plain) -> 1;
×
112
hash_to_num(sha) -> 2;
×
UNCOV
113
hash_to_num(sha256) -> 3;
16✔
114
hash_to_num(sha512) -> 4.
×
115

116
num_to_hash(2) -> sha;
×
117
num_to_hash(3) -> sha256;
×
118
num_to_hash(4) -> sha512.
×
119

120
set_password_instance(User, Server, #scram{hash = Hash, storedkey = SK, serverkey = SEK,
121
                                           salt = Salt, iterationcount = IC}) ->
122
    F = fun() ->
×
123
        set_password_scram_t(User, Server, Hash,
×
124
                             SK, SEK, Salt, IC)
125
        end,
126
    case ejabberd_sql:sql_transaction(Server, F) of
×
127
        {atomic, _} ->
128
            ok;
×
129
        {aborted, _} ->
130
            {error, db_failure}
×
131
    end;
132
set_password_instance(User, Server, Plain) ->
133
    F = fun() ->
×
134
        set_password_t(User, Server, Plain)
×
135
        end,
136
    case ejabberd_sql:sql_transaction(Server, F) of
×
137
        {atomic, _} ->
138
            ok;
×
139
        {aborted, _} ->
140
            {error, db_failure}
×
141
    end.
142

143
set_password_multiple(User, Server, Passwords) ->
UNCOV
144
    F =
10✔
145
    fun() ->
UNCOV
146
        ejabberd_sql:sql_query_t(
10✔
UNCOV
147
            ?SQL("delete from users where username=%(User)s and %(Server)H")),
10✔
UNCOV
148
        lists:foreach(
10✔
149
            fun(#scram{hash = Hash, storedkey = SK, serverkey = SEK,
150
                       salt = Salt, iterationcount = IC}) ->
UNCOV
151
                set_password_scram_t(
6✔
152
                    User, Server, Hash,
153
                    SK, SEK, Salt, IC);
154
               (Plain) ->
UNCOV
155
                   set_password_t(User, Server, Plain)
10✔
156
            end, Passwords)
157
    end,
UNCOV
158
    case ejabberd_sql:sql_transaction(Server, F) of
10✔
159
        {atomic, _} ->
UNCOV
160
            {cache, {ok, Passwords}};
10✔
161
        {aborted, _} ->
162
            {nocache, {error, db_failure}}
×
163
    end.
164

165
try_register_multiple(User, Server, Passwords) ->
UNCOV
166
    F =
30✔
167
        fun() ->
UNCOV
168
            case ejabberd_sql:sql_query_t(
30✔
UNCOV
169
                ?SQL("select @(count(*))d from users where username=%(User)s and %(Server)H")) of
30✔
170
                {selected, [{0}]} ->
UNCOV
171
                    lists:foreach(
30✔
172
                        fun(#scram{hash = Hash, storedkey = SK, serverkey = SEK,
173
                                   salt = Salt, iterationcount = IC}) ->
UNCOV
174
                            set_password_scram_t(
10✔
175
                                User, Server, Hash,
176
                                SK, SEK, Salt, IC);
177
                           (Plain) ->
UNCOV
178
                               set_password_t(User, Server, Plain)
30✔
179
                        end, Passwords),
UNCOV
180
                    {cache, {ok, Passwords}};
30✔
181
                {selected, _} ->
182
                    {nocache, {error, exists}};
×
183
                _ ->
184
                    {nocache, {error, db_failure}}
×
185
            end
186
        end,
UNCOV
187
    case ejabberd_sql:sql_transaction(Server, F) of
30✔
188
        {atomic, Res} ->
UNCOV
189
            Res;
30✔
190
        {aborted, _} ->
191
            {nocache, {error, db_failure}}
×
192
    end.
193

194
get_users(Server, Opts) ->
UNCOV
195
    case list_users(Server, Opts) of
6✔
196
        {selected, Res} ->
UNCOV
197
            [{U, Server} || {U} <- Res];
6✔
198
        _ -> []
×
199
    end.
200

201
count_users(Server, Opts) ->
UNCOV
202
    case users_number(Server, Opts) of
×
203
        {selected, [{Res}]} ->
UNCOV
204
            Res;
×
205
        _Other -> 0
×
206
    end.
207

208
get_password(User, Server) ->
UNCOV
209
    case get_password_scram(Server, User) of
48✔
210
        {selected, []} ->
UNCOV
211
            {cache, error};
48✔
212
        {selected, Passwords} ->
213
            Converted = lists:map(
×
214
                fun({0, Password, <<>>, <<>>, 0}) ->
215
                    update_password_type(User, Server, 1),
×
216
                    Password;
×
217
                   ({_, Password, <<>>, <<>>, 0}) ->
218
                       Password;
×
219
                   ({0, StoredKey, ServerKey, Salt, IterationCount}) ->
220
                       {Hash, SK} = case StoredKey of
×
221
                                        <<"sha256:", Rest/binary>> ->
222
                                            update_password_type(User, Server, 3, Rest),
×
223
                                            {sha256, Rest};
×
224
                                        <<"sha512:", Rest/binary>> ->
225
                                            update_password_type(User, Server, 4, Rest),
×
226
                                            {sha512, Rest};
×
227
                                        Other ->
228
                                            update_password_type(User, Server, 2),
×
229
                                            {sha, Other}
×
230
                                    end,
231
                       #scram{storedkey = SK,
×
232
                              serverkey = ServerKey,
233
                              salt = Salt,
234
                              hash = Hash,
235
                              iterationcount = IterationCount};
236
                   ({Type, StoredKey, ServerKey, Salt, IterationCount}) ->
237
                       Hash = num_to_hash(Type),
×
238
                       #scram{storedkey = StoredKey,
×
239
                              serverkey = ServerKey,
240
                              salt = Salt,
241
                              hash = Hash,
242
                              iterationcount = IterationCount}
243
                end, Passwords),
244
            {cache, {ok, Converted}};
×
245
        _ ->
246
            {nocache, error}
×
247
    end.
248

249
remove_user(User, Server) ->
UNCOV
250
    case del_user(Server, User) of
12✔
251
        {updated, _} ->
UNCOV
252
            ok;
12✔
253
        _ ->
254
            {error, db_failure}
×
255
    end.
256

257
drop_password_type(LServer, Hash) ->
258
    Type = hash_to_num(Hash),
×
259
    ejabberd_sql:sql_query(
×
260
        LServer,
261
        ?SQL("delete from users"
×
262
             " where type=%(Type)d and %(LServer)H")).
263

264
set_password_scram_t(LUser, LServer, Hash,
265
                     StoredKey, ServerKey, Salt, IterationCount) ->
UNCOV
266
    Type = hash_to_num(Hash),
16✔
UNCOV
267
    ?SQL_UPSERT_T(
16✔
268
       "users",
269
       ["!username=%(LUser)s",
270
        "!server_host=%(LServer)s",
271
        "!type=%(Type)d",
272
        "password=%(StoredKey)s",
273
        "serverkey=%(ServerKey)s",
274
        "salt=%(Salt)s",
275
        "iterationcount=%(IterationCount)d"]).
276

277
set_password_t(LUser, LServer, Password) ->
UNCOV
278
    ?SQL_UPSERT_T(
40✔
279
       "users",
280
       ["!username=%(LUser)s",
281
        "!server_host=%(LServer)s",
282
        "!type=1",
283
        "password=%(Password)s",
284
        "serverkey=''",
285
        "salt=''",
286
        "iterationcount=0"]).
287

288
update_password_type(LUser, LServer, Type, Password) ->
289
    ejabberd_sql:sql_query(
×
290
        LServer,
291
        ?SQL("update users set type=%(Type)d, password=%(Password)s"
×
292
             " where username=%(LUser)s and type=0 and %(LServer)H")).
293

294
update_password_type(LUser, LServer, Type) ->
295
    ejabberd_sql:sql_query(
×
296
        LServer,
297
        ?SQL("update users set type=%(Type)d"
×
298
             " where username=%(LUser)s and type=0 and %(LServer)H")).
299

300
get_password_scram(LServer, LUser) ->
UNCOV
301
    ejabberd_sql:sql_query(
48✔
302
      LServer,
UNCOV
303
      ?SQL("select @(type)d, @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
48✔
304
           " from users"
305
           " where username=%(LUser)s and %(LServer)H")).
306

307
del_user(LServer, LUser) ->
UNCOV
308
    ejabberd_sql:sql_query(
12✔
309
      LServer,
UNCOV
310
      ?SQL("delete from users where username=%(LUser)s and %(LServer)H")).
12✔
311

312
list_users(LServer, []) ->
UNCOV
313
    ejabberd_sql:sql_query(
6✔
314
      LServer,
UNCOV
315
      ?SQL("select @(distinct username)s from users where %(LServer)H"));
6✔
316
list_users(LServer, [{from, Start}, {to, End}])
317
    when is_integer(Start) and is_integer(End) ->
318
    list_users(LServer,
×
319
               [{limit, End - Start + 1}, {offset, Start - 1}]);
320
list_users(LServer,
321
           [{prefix, Prefix}, {from, Start}, {to, End}])
322
    when is_binary(Prefix) and is_integer(Start) and
323
           is_integer(End) ->
324
    list_users(LServer,
×
325
               [{prefix, Prefix}, {limit, End - Start + 1},
326
                {offset, Start - 1}]);
327
list_users(LServer, [{limit, Limit}, {offset, Offset}])
328
    when is_integer(Limit) and is_integer(Offset) ->
329
    ejabberd_sql:sql_query(
×
330
      LServer,
331
      ?SQL("select @(distinct username)s from users "
×
332
           "where %(LServer)H "
333
           "order by username "
334
           "limit %(Limit)d offset %(Offset)d"));
335
list_users(LServer,
336
           [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
337
    when is_binary(Prefix) and is_integer(Limit) and
338
           is_integer(Offset) ->
339
    SPrefix = ejabberd_sql:escape_like_arg(Prefix),
×
340
    SPrefix2 = <<SPrefix/binary, $%>>,
×
341
    ejabberd_sql:sql_query(
×
342
      LServer,
343
      ?SQL("select @(distinct username)s from users "
×
344
           "where username like %(SPrefix2)s %ESCAPE and %(LServer)H "
345
           "order by username "
346
           "limit %(Limit)d offset %(Offset)d")).
347

348
users_number(LServer) ->
UNCOV
349
    ejabberd_sql:sql_query(
×
350
      LServer,
351
      fun(pgsql, _) ->
UNCOV
352
              case
×
353
                  ejabberd_option:pgsql_users_number_estimate(LServer) of
354
                  true ->
355
                      ejabberd_sql:sql_query_t(
×
356
                        ?SQL("select @(reltuples :: bigint)d from pg_class"
×
357
                             " where oid = 'users'::regclass::oid"));
358
                  _ ->
UNCOV
359
                      ejabberd_sql:sql_query_t(
×
UNCOV
360
                        ?SQL("select @(count(distinct username))d from users where %(LServer)H"))
×
361
          end;
362
         (_Type, _) ->
UNCOV
363
              ejabberd_sql:sql_query_t(
×
UNCOV
364
                ?SQL("select @(count(distinct username))d from users where %(LServer)H"))
×
365
      end).
366

367
users_number(LServer, [{prefix, Prefix}])
368
    when is_binary(Prefix) ->
369
    SPrefix = ejabberd_sql:escape_like_arg(Prefix),
×
370
    SPrefix2 = <<SPrefix/binary, $%>>,
×
371
    ejabberd_sql:sql_query(
×
372
      LServer,
373
      ?SQL("select @(count(distinct username))d from users "
×
374
           "where username like %(SPrefix2)s %ESCAPE and %(LServer)H"));
375
users_number(LServer, []) ->
UNCOV
376
    users_number(LServer).
×
377

378
which_users_exists(LServer, LUsers) when length(LUsers) =< 100 ->
379
    try ejabberd_sql:sql_query(
×
380
        LServer,
381
        ?SQL("select @(distinct username)s from users where username in %(LUsers)ls")) of
×
382
        {selected, Matching} ->
383
            [U || {U} <- Matching];
×
384
        {error, _} = E ->
385
            E
×
386
    catch _:B ->
387
        {error, B}
×
388
    end;
389
which_users_exists(LServer, LUsers) ->
390
    {First, Rest} = lists:split(100, LUsers),
×
391
    case which_users_exists(LServer, First) of
×
392
        {error, _} = E ->
393
            E;
×
394
        V ->
395
            case which_users_exists(LServer, Rest) of
×
396
                {error, _} = E2 ->
397
                    E2;
×
398
                V2 ->
399
                    V ++ V2
×
400
            end
401
    end.
402

403
export(_Server) ->
404
    [{passwd,
×
405
      fun(Host, #passwd{us = {LUser, LServer, plain}, password = Password})
406
            when LServer == Host,
407
                 is_binary(Password) ->
408
              [?SQL("delete from users where username=%(LUser)s and type=1 and %(LServer)H;"),
×
409
               ?SQL_INSERT(
×
410
                  "users",
411
                  ["username=%(LUser)s",
412
                   "server_host=%(LServer)s",
413
                   "type=1",
414
                   "password=%(Password)s"])];
415
         (Host, {passwd, {LUser, LServer, _},
416
                         {scram, StoredKey, ServerKey, Salt, IterationCount}})
417
            when LServer == Host ->
418
              Hash = sha,
×
419
              Type = hash_to_num(Hash),
×
420
              [?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"),
×
421
               ?SQL_INSERT(
×
422
                  "users",
423
                  ["username=%(LUser)s",
424
                   "server_host=%(LServer)s",
425
                   "type=%(Type)d",
426
                   "password=%(StoredKey)s",
427
                   "serverkey=%(ServerKey)s",
428
                   "salt=%(Salt)s",
429
                   "iterationcount=%(IterationCount)d"])];
430
         (Host, #passwd{us = {LUser, LServer, _}, password = #scram{} = Scram})
431
            when LServer == Host ->
432
              StoredKey = Scram#scram.storedkey,
×
433
              ServerKey = Scram#scram.serverkey,
×
434
              Salt = Scram#scram.salt,
×
435
              IterationCount = Scram#scram.iterationcount,
×
436
              Type = hash_to_num(Scram#scram.hash),
×
437
              [?SQL("delete from users where username=%(LUser)s and type=%(Type)d and %(LServer)H;"),
×
438
               ?SQL_INSERT(
×
439
                  "users",
440
                  ["username=%(LUser)s",
441
                   "server_host=%(LServer)s",
442
                   "type=%(Type)d",
443
                   "password=%(StoredKey)s",
444
                   "serverkey=%(ServerKey)s",
445
                   "salt=%(Salt)s",
446
                   "iterationcount=%(IterationCount)d"])];
447
         (_Host, _R) ->
448
              []
×
449
      end}].
450

451

452
serialize(LServer, BatchSize, Last) ->
453
    Offset = case Last of
×
454
                 undefined -> 0;
×
455
                 _ -> Last
×
456
             end,
457
    case ejabberd_sql:sql_query(
×
458
           LServer,
459
           ?SQL("select @(username)s, @(type)d, @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d  from users "
×
460
                "where %(LServer)H "
461
                "order by username, type "
462
                "limit %(BatchSize)d offset %(Offset)d")) of
463
        {selected, Rows} ->
464
            Data = lists:map(
×
465
                     fun({Username, _, Password, <<>>, <<>>, 0}) ->
466
                             #serialize_auth_v1{serverhost = LServer, username = Username, passwords = [Password]};
×
467
                        ({Username, 1, Password, _, _, _}) ->
468
                             #serialize_auth_v1{serverhost = LServer, username = Username, passwords = [Password]};
×
469
                        ({Username, 0, Password, ServerKey, Salt, IterationCount}) ->
470
                             {Hash, SK} = case Password of
×
471
                                              <<"sha256:", Rest/binary>> ->
472
                                                  {sha256, Rest};
×
473
                                              <<"sha512:", Rest/binary>> ->
474
                                                  {sha512, Rest};
×
475
                                              Other ->
476
                                                  {sha, Other}
×
477
                                          end,
478
                             #serialize_auth_v1{
×
479
                               serverhost = LServer,
480
                               username = Username,
481
                               passwords = [{Hash, SK, ServerKey, Salt, IterationCount}]
482
                              };
483
                        ({Username, Type, StoredKey, ServerKey, Salt, IterationCount}) ->
484
                             #serialize_auth_v1{
×
485
                               serverhost = LServer,
486
                               username = Username,
487
                               passwords = [{num_to_hash(Type),
488
                                             StoredKey,
489
                                             ServerKey,
490
                                             Salt,
491
                                             IterationCount}]
492
                              }
493
                     end,
494
                     Rows),
495
            Data2 = case length(Rows) < BatchSize of
×
496
                        true -> Data ++ [#serialize_auth_v1{serverhost = <<>>, username = <<>>, passwords = []}];
×
497
                        _ -> Data
×
498
                    end,
499
            {_, Data3, _, RC2} = lists:foldl(
×
500
                                   fun(Next, {undefined, Res, _, _}) ->
501
                                           {Next, Res, 1, 0};
×
502
                                      (#serialize_auth_v1{username = U1, passwords = [P1]},
503
                                       {#serialize_auth_v1{username = U2, passwords = P2} = Next, Res, NC, RC})
504
                                         when U1 == U2 ->
505
                                           {Next#serialize_auth_v1{passwords = [P1 | P2]}, Res, NC + 1, RC};
×
506
                                      (Current, {Next, Acc, NC, RC}) ->
507
                                           {Current, [Next | Acc], 1, RC + NC}
×
508
                                   end,
509
                                   {undefined, [], 0, 0},
510
                                   Data2),
511
            {ok, Data3, Offset + RC2};
×
512
        _ ->
513
            {error, io_lib:format("Error when retrieving passwords data from database", [])}
×
514
    end.
515

516

517
deserialize_start(LServer) ->
518
    ejabberd_sql:sql_query(
×
519
      LServer,
520
      ?SQL("delete from users where %(LServer)H")).
×
521

522

523
deserialize(LServer, Batch) ->
524
    case ejabberd_sql:sql_transaction(LServer,
×
525
                                      fun() ->
526
                                          lists:foreach(
×
527
                                              fun(#serialize_auth_v1{username = Username, passwords = Passwords}) ->
528
                                                  lists:foreach(
×
529
                                                      fun({Hash, StoredKey, ServerKey, Salt, IterationCount}) ->
530
                                                          set_password_scram_t(Username,
×
531
                                                                               LServer,
532
                                                                               Hash,
533
                                                                               StoredKey,
534
                                                                               ServerKey,
535
                                                                               Salt,
536
                                                                               IterationCount);
537
                                                         (Password) ->
538
                                                             set_password_t(Username, LServer, Password)
×
539
                                                      end,
540
                                                      Passwords)
541
                                              end,
542
                                              Batch)
543
                                      end) of
544
        {atomic, _} -> ok;
×
545
        _ ->
546
            {error, io_lib:format("Error when storing passwords in database", [])}
×
547
    end.
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