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

processone / ejabberd / 1212

18 Nov 2025 12:37PM UTC coverage: 33.784% (+0.003%) from 33.781%
1212

push

github

badlop
mod_conversejs: Improve link to conversejs in WebAdmin (#4495)

Until now, the WebAdmin menu included a link to the first request handler
with mod_conversejs that the admin configured in ejabberd.yml
That link included the authentication credentials hashed as URI arguments
if using HTTPS. Then process/2 extracted those arguments and passed them
as autologin options to Converse.

From now, mod_conversejs automatically adds a request_handler nested in
webadmin subpath. The webadmin menu links to that converse URI; this allows
to access the HTTP auth credentials, no need to explicitly pass them.
process/2 extracts this HTTP auth and passes autologin options to Converse.
Now scram password storage is supported too.

This minimum configuration allows WebAdmin to access Converse:

listen:
  -
    port: 5443
    module: ejabberd_http
    tls: true
    request_handlers:
      /admin: ejabberd_web_admin
      /ws: ejabberd_http_ws
modules:
  mod_conversejs:
    conversejs_resources: "/home/conversejs/12.0.0/dist"

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

11290 existing lines in 174 files now uncovered.

15515 of 45924 relevant lines covered (33.78%)

1277.8 hits per line

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

51.26
/src/mod_proxy65_service.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_proxy65_service.erl
3
%%% Author  : Evgeniy Khramtsov <xram@jabber.ru>
4
%%% Purpose : SOCKS5 Bytestreams XMPP service.
5
%%% Created : 12 Oct 2006 by Evgeniy Khramtsov <xram@jabber.ru>
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_proxy65_service).
27

28
-author('xram@jabber.ru').
29

30
-behaviour(gen_server).
31

32
%% gen_server callbacks.
33
-export([init/1, handle_info/2, handle_call/3,
34
         handle_cast/2, terminate/2, code_change/3]).
35

36
-export([start_link/1, reload/3, add_listener/2, process_disco_info/1,
37
         process_disco_items/1, process_vcard/1, process_bytestreams/1,
38
         delete_listener/1, route/1]).
39

40
-include("logger.hrl").
41
-include_lib("xmpp/include/xmpp.hrl").
42
-include("translate.hrl").
43

44

45
-define(PROCNAME, ejabberd_mod_proxy65_service).
46

47
-record(state, {myhosts = [] :: [binary()]}).
48

49
%%%------------------------
50
%%% gen_server callbacks
51
%%%------------------------
52

53
start_link(Host) ->
54
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
144✔
55
    gen_server:start_link({local, Proc}, ?MODULE, [Host], []).
144✔
56

57
reload(Host, NewOpts, OldOpts) ->
58
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
×
59
    gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}).
×
60

61
init([Host]) ->
62
    process_flag(trap_exit, true),
144✔
63
    Opts = gen_mod:get_module_opts(Host, mod_proxy65),
144✔
64
    MyHosts = gen_mod:get_opt_hosts(Opts),
144✔
65
    lists:foreach(
144✔
66
      fun(MyHost) ->
67
              gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
144✔
68
                                            ?MODULE, process_disco_info),
69
              gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
144✔
70
                                            ?MODULE, process_disco_items),
71
              gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
144✔
72
                                            ?MODULE, process_vcard),
73
              gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
144✔
74
                                            ?MODULE, process_bytestreams),
75
              ejabberd_router:register_route(
144✔
76
                MyHost, Host, {apply, ?MODULE, route})
77
      end, MyHosts),
78
    {ok, #state{myhosts = MyHosts}}.
144✔
79

80
terminate(_Reason, #state{myhosts = MyHosts}) ->
81
    lists:foreach(
144✔
82
      fun(MyHost) ->
83
              ejabberd_router:unregister_route(MyHost),
144✔
84
              unregister_handlers(MyHost)
144✔
85
      end, MyHosts).
86

87
handle_info({route, Packet}, State) ->
88
    try route(Packet)
×
89
    catch
90
        Class:Reason:StackTrace ->
91
            ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
×
92
                       [xmpp:pp(Packet),
93
                        misc:format_exception(2, Class, Reason, StackTrace)])
×
94
    end,
95
    {noreply, State};
×
96
handle_info(Info, State) ->
97
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
98
    {noreply, State}.
×
99

100
handle_call(Request, From, State) ->
101
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
102
    {noreply, State}.
×
103

104
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
105
    NewHosts = gen_mod:get_opt_hosts(NewOpts),
×
106
    OldHosts = gen_mod:get_opt_hosts(OldOpts),
×
107
    lists:foreach(
×
108
      fun(NewHost) ->
109
              ejabberd_router:register_route(NewHost, ServerHost),
×
110
              register_handlers(NewHost)
×
111
      end, NewHosts -- OldHosts),
112
    lists:foreach(
×
113
      fun(OldHost) ->
114
              ejabberd_router:unregister_route(OldHost),
×
115
              unregister_handlers(OldHost)
×
116
      end, OldHosts -- NewHosts),
117
    {noreply, State#state{myhosts = NewHosts}};
×
118
handle_cast(Msg, State) ->
119
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
120
    {noreply, State}.
×
121

122
code_change(_OldVsn, State, _Extra) -> {ok, State}.
×
123

124
-spec route(stanza()) -> ok.
125
route(#iq{} = IQ) ->
UNCOV
126
    ejabberd_router:process_iq(IQ);
10✔
127
route(_) ->
128
    ok.
×
129

130
%%%------------------------
131
%%% Listener management
132
%%%------------------------
133

134
add_listener(Host, Opts) ->
135
    {_, IP, _} = EndPoint = get_endpoint(Host),
144✔
136
    Opts1 = gen_mod:set_opt(server_host, Host, Opts),
144✔
137
    Opts2 = gen_mod:set_opt(ip, IP, Opts1),
144✔
138
    ejabberd_listener:add_listener(EndPoint, mod_proxy65_stream, Opts2).
144✔
139

140
delete_listener(Host) ->
141
    ejabberd_listener:delete_listener(get_endpoint(Host), mod_proxy65_stream).
16✔
142

143
%%%------------------------
144
%%% IQ Processing
145
%%%------------------------
146
-spec process_disco_info(iq()) -> iq().
147
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
148
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
149
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
150
process_disco_info(#iq{type = get, to = To, lang = Lang} = IQ) ->
UNCOV
151
    Host = ejabberd_router:host_of_route(To#jid.lserver),
4✔
UNCOV
152
    Name = mod_proxy65_opt:name(Host),
4✔
UNCOV
153
    Info = ejabberd_hooks:run_fold(disco_info, Host,
4✔
154
                                   [], [Host, ?MODULE, <<"">>, <<"">>]),
UNCOV
155
    xmpp:make_iq_result(
4✔
156
      IQ, #disco_info{xdata = Info,
157
                      identities = [#identity{category = <<"proxy">>,
158
                                              type = <<"bytestreams">>,
159
                                              name =  translate:translate(Lang, Name)}],
160
                      features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
161
                                  ?NS_VCARD, ?NS_BYTESTREAMS]}).
162

163
-spec process_disco_items(iq()) -> iq().
164
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
165
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
166
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
167
process_disco_items(#iq{type = get} = IQ) ->
168
    xmpp:make_iq_result(IQ, #disco_items{}).
×
169

170
-spec process_vcard(iq()) -> iq().
171
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
172
    Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
×
173
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
×
174
process_vcard(#iq{type = get, to = To, lang = Lang} = IQ) ->
UNCOV
175
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
2✔
UNCOV
176
    VCard = case mod_proxy65_opt:vcard(ServerHost) of
2✔
177
                undefined ->
178
                    #vcard_temp{fn = <<"ejabberd/mod_proxy65">>,
×
179
                                url = ejabberd_config:get_uri(),
180
                                desc = misc:get_descr(
181
                                         Lang, ?T("ejabberd SOCKS5 Bytestreams module"))};
182
                V ->
UNCOV
183
                    V
2✔
184
            end,
UNCOV
185
    xmpp:make_iq_result(IQ, VCard).
2✔
186

187
-spec process_bytestreams(iq()) -> iq().
188
process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) ->
UNCOV
189
    Host = To#jid.lserver,
2✔
UNCOV
190
    ServerHost = ejabberd_router:host_of_route(Host),
2✔
UNCOV
191
    ACL = mod_proxy65_opt:access(ServerHost),
2✔
UNCOV
192
    case acl:match_rule(ServerHost, ACL, JID) of
2✔
193
        allow ->
UNCOV
194
            StreamHost = get_streamhost(Host, ServerHost),
2✔
UNCOV
195
            xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]});
2✔
196
        deny ->
197
            xmpp:make_error(IQ, xmpp:err_forbidden(?T("Access denied by service policy"), Lang))
×
198
    end;
199
process_bytestreams(#iq{type = set, lang = Lang,
200
                        sub_els = [#bytestreams{sid = SID}]} = IQ)
201
  when SID == <<"">> orelse size(SID) > 128 ->
202
    Why = {bad_attr_value, <<"sid">>, <<"query">>, ?NS_BYTESTREAMS},
×
203
    Txt = xmpp:io_format_error(Why),
×
204
    xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
×
205
process_bytestreams(#iq{type = set, lang = Lang,
206
                        sub_els = [#bytestreams{activate = undefined}]} = IQ) ->
207
    Why = {missing_cdata, <<"">>, <<"activate">>, ?NS_BYTESTREAMS},
×
208
    Txt = xmpp:io_format_error(Why),
×
209
    xmpp:make_error(IQ, xmpp:err_jid_malformed(Txt, Lang));
×
210
process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To,
211
                        sub_els = [#bytestreams{activate = TargetJID,
212
                                                sid = SID}]} = IQ) ->
UNCOV
213
    ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
2✔
UNCOV
214
    ACL = mod_proxy65_opt:access(ServerHost),
2✔
UNCOV
215
    case acl:match_rule(ServerHost, ACL, InitiatorJID) of
2✔
216
        allow ->
UNCOV
217
            Node = ejabberd_cluster:get_node_by_id(To#jid.lresource),
2✔
UNCOV
218
            Target = jid:encode(jid:tolower(TargetJID)),
2✔
UNCOV
219
            Initiator = jid:encode(jid:tolower(InitiatorJID)),
2✔
UNCOV
220
            SHA1 = str:sha(<<SID/binary, Initiator/binary, Target/binary>>),
2✔
UNCOV
221
            Mod = gen_mod:ram_db_mod(global, mod_proxy65),
2✔
UNCOV
222
            MaxConnections = max_connections(ServerHost),
2✔
UNCOV
223
            case Mod:activate_stream(SHA1, Initiator, MaxConnections, Node) of
2✔
224
                {ok, InitiatorPid, TargetPid} ->
UNCOV
225
                    mod_proxy65_stream:activate(
2✔
226
                      {InitiatorPid, InitiatorJID}, {TargetPid, TargetJID}),
UNCOV
227
                    xmpp:make_iq_result(IQ);
2✔
228
                {error, notfound} ->
229
                    Txt = ?T("Failed to activate bytestream"),
×
230
                    xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
×
231
                {error, {limit, InitiatorPid, TargetPid}} ->
232
                    mod_proxy65_stream:stop(InitiatorPid),
×
233
                    mod_proxy65_stream:stop(TargetPid),
×
234
                    Txt = ?T("Too many active bytestreams"),
×
235
                    xmpp:make_error(IQ, xmpp:err_resource_constraint(Txt, Lang));
×
236
                {error, conflict} ->
237
                    Txt = ?T("Bytestream already activated"),
×
238
                    xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
×
239
                {error, Err} ->
240
                    ?ERROR_MSG("Failed to activate bytestream from ~ts to ~ts: ~p",
×
241
                               [Initiator, Target, Err]),
×
242
                    Txt = ?T("Database failure"),
×
243
                    xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
×
244
            end;
245
        deny ->
246
            Txt = ?T("Access denied by service policy"),
×
247
            xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
×
248
    end.
249

250
%%%-------------------------
251
%%% Auxiliary functions.
252
%%%-------------------------
253
-spec get_streamhost(binary(), binary()) -> streamhost().
254
get_streamhost(Host, ServerHost) ->
UNCOV
255
    {Port, IP, _} = get_endpoint(ServerHost),
2✔
UNCOV
256
    HostName = case mod_proxy65_opt:hostname(ServerHost) of
2✔
UNCOV
257
                   undefined -> misc:ip_to_list(IP);
2✔
258
                   Val -> Val
×
259
               end,
UNCOV
260
    Resource = ejabberd_cluster:node_id(),
2✔
UNCOV
261
    #streamhost{jid = jid:make(<<"">>, Host, Resource),
2✔
262
                host = HostName,
263
                port = Port}.
264

265
-spec get_endpoint(binary()) -> {inet:port_number(), inet:ip_address(), tcp}.
266
get_endpoint(Host) ->
267
    Port = mod_proxy65_opt:port(Host),
162✔
268
    IP = case mod_proxy65_opt:ip(Host) of
162✔
269
             undefined -> misc:get_my_ipv4_address();
162✔
270
             Addr -> Addr
×
271
         end,
272
    {Port, IP, tcp}.
162✔
273

274
max_connections(ServerHost) ->
UNCOV
275
    mod_proxy65_opt:max_connections(ServerHost).
2✔
276

277
register_handlers(Host) ->
278
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
×
279
                                  ?MODULE, process_disco_info),
280
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
×
281
                                  ?MODULE, process_disco_items),
282
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
×
283
                                  ?MODULE, process_vcard),
284
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS,
×
285
                                  ?MODULE, process_bytestreams).
286

287
unregister_handlers(Host) ->
288
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
144✔
289
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
144✔
290
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
144✔
291
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS).
144✔
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