./ct_report/coverage/ejabberd_socket.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_socket.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Socket with zlib and TLS support library
5 %%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 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
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(ejabberd_socket).
27 -author('alexey@process-one.net').
28
29 -behaviour(mongoose_transport).
30
31 %% API
32 -export([start/5,
33 connect/3,
34 connect/4,
35 starttls/2,
36 starttls/3,
37 compress/3,
38 send/2,
39 send_xml/2,
40 change_shaper/2,
41 monitor/1,
42 get_sockmod/1,
43 get_peer_certificate/1,
44 close/1,
45 sockname/1,
46 peername/1,
47 get_socket/1,
48 format_socket/1]).
49
50 -ignore_xref([change_shaper/2, compress/3, connect/3, get_peer_certificate/1,
51 get_sockmod/1, sockname/1]).
52
53 -include("mongoose.hrl").
54
55 -record(socket_state, {sockmod :: ejabberd:sockmod(),
56 socket :: term(),
57 receiver :: pid() | atom() | tuple(),
58 connection_details :: mongoose_tcp_listener:connection_details()
59 }).
60 -type socket_state() :: #socket_state{}.
61
62 %%====================================================================
63 %% API
64 %%====================================================================
65 %%--------------------------------------------------------------------
66 %% Function:
67 %% Description:
68 %%--------------------------------------------------------------------
69 -spec start(module(), ejabberd:sockmod(), gen_tcp:socket(),
70 mongoose_tcp_listener:options(),
71 mongoose_tcp_listener:connection_details()) -> ok.
72 start(Module, SockMod, Socket, Opts, ConnectionDetails) ->
73 418 case mongoose_listener:socket_type(Module) of
74 xml_stream ->
75 418 start_xml_stream(Module, SockMod, Socket, Opts, ConnectionDetails);
76 independent ->
77
:-(
ok;
78 raw ->
79
:-(
start_raw_stream(Module, SockMod, Socket, Opts, ConnectionDetails)
80 end.
81
82 -spec start_raw_stream(module(), ejabberd:sockmod(),
83 Socket :: port(), mongoose_tcp_listener:options(),
84 mongoose_tcp_listener:connection_details()) -> ok.
85 start_raw_stream(Module, SockMod, Socket, Opts, _ConnectionDetails) ->
86
:-(
case Module:start({SockMod, Socket}, Opts) of
87 {ok, Pid} ->
88
:-(
case SockMod:controlling_process(Socket, Pid) of
89 ok ->
90
:-(
ok;
91 {error, _Reason} ->
92
:-(
SockMod:close(Socket)
93 end;
94 {error, _Reason} ->
95
:-(
SockMod:close(Socket)
96 end.
97
98 -spec start_xml_stream(atom() | tuple(), ejabberd:sockmod(),
99 Socket :: port(), mongoose_tcp_listener:options(),
100 mongoose_tcp_listener:connection_details()) -> ok.
101 start_xml_stream(Module, SockMod, Socket, Opts, ConnectionDetails) ->
102 418 {ReceiverMod, Receiver, RecRef} =
103 case catch SockMod:custom_receiver(Socket) of
104 {receiver, RecMod, RecPid} ->
105
:-(
{RecMod, RecPid, RecMod};
106 _ ->
107 418 RecPid = ejabberd_receiver:start(Socket, SockMod, none, Opts),
108 418 {ejabberd_receiver, RecPid, RecPid}
109 end,
110 418 SocketData = #socket_state{sockmod = SockMod,
111 socket = Socket,
112 receiver = RecRef,
113 connection_details = ConnectionDetails},
114 %% set receiver as socket's controlling process before
115 %% the M:start/2 call, that is required for c2s legacy
116 %% TLS connection support.
117 418 case SockMod:controlling_process(Socket, Receiver) of
118 ok ->
119 418 case Module:start({?MODULE, SocketData}, Opts) of
120 {ok, Pid} ->
121 260 ReceiverMod:become_controller(Receiver, Pid);
122 {error, _Reason} ->
123 158 SockMod:close(Socket),
124 158 case ReceiverMod of
125 ejabberd_receiver ->
126 158 ReceiverMod:close(Receiver);
127 _ ->
128
:-(
ok
129 end
130 end;
131 {error, _Reason} ->
132
:-(
SockMod:close(Socket)
133 end.
134
135 -type option_value() :: 'asn1' | 'cdr' | 'false' | 'fcgi' | 'http' | 'http_bin'
136 | 'line' | 'once' | 'raw' | 'sunrm' | 'tpkt' | 'true'
137 | integer() | inet:ip_address().
138 -type option() :: 'binary' | 'inet' | 'inet6' | 'list'
139 | {atom(), option_value()}
140 | {'raw', non_neg_integer(), non_neg_integer(), binary()}.
141 -spec connect(Addr :: atom() | string() | inet:ip_address(),
142 Port :: inet:port_number(),
143 Opts :: [option()]) -> {'error', atom()} | {'ok', socket_state()}.
144 connect(Addr, Port, Opts) ->
145
:-(
connect(Addr, Port, Opts, infinity).
146
147
148 -spec connect(Addr :: atom() | string() | inet:ip_address(),
149 Port :: inet:port_number(),
150 Opts :: [option()],
151 Timeout :: non_neg_integer() | infinity
152 ) -> {'error', atom()} | {'ok', socket_state()}.
153 connect(Addr, Port, Opts, Timeout) ->
154
:-(
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
155 {ok, Socket} ->
156 %% Receiver options are configurable only for listeners
157 %% It might make sense to make them configurable for outgoing s2s connections as well
158
:-(
ReceiverOpts = #{max_stanza_size => infinity, hibernate_after => 0},
159
:-(
Receiver = ejabberd_receiver:start(Socket, gen_tcp, none, ReceiverOpts),
160
:-(
{SrcAddr, SrcPort} = case inet:sockname(Socket) of
161
:-(
{ok, {A, P}} -> {A, P};
162
:-(
{error, _} -> {unknown, unknown}
163 end,
164
:-(
SocketData = #socket_state{sockmod = gen_tcp,
165 socket = Socket,
166 receiver = Receiver,
167 connection_details = #{proxy => false,
168 src_address => SrcAddr,
169 src_port => SrcPort,
170 dest_address => Addr,
171 dest_port => Port}},
172
:-(
Pid = self(),
173
:-(
case gen_tcp:controlling_process(Socket, Receiver) of
174 ok ->
175
:-(
ejabberd_receiver:become_controller(Receiver, Pid),
176
:-(
{ok, SocketData};
177 {error, _Reason} = Error ->
178
:-(
gen_tcp:close(Socket),
179
:-(
Error
180 end;
181 {error, _Reason} = Error ->
182
:-(
Error
183 end.
184
185
186 -spec tcp_to_tls(socket_state(), list()) -> ejabberd_tls:tls_socket().
187 tcp_to_tls(#socket_state{receiver = Receiver}, TLSOpts) ->
188 318 SanitizedTLSOpts = case lists:keyfind(protocol_options, 1, TLSOpts) of
189 318 false -> [{protocol_options, "no_sslv2|no_sslv3|no_tlsv1|no_tlsv1_1"} | TLSOpts];
190 {_, ProtoOpts} ->
191
:-(
NewProtoOpts = {protocol_options, string:join(ProtoOpts, "|")},
192
:-(
lists:keyreplace(protocol_options, 1, TLSOpts, NewProtoOpts)
193 end,
194 318 ejabberd_receiver:starttls(Receiver, SanitizedTLSOpts).
195
196 get_tls_socket(#socket_state{receiver = Receiver}) ->
197 318 case ejabberd_receiver:get_socket(Receiver) of
198 160 {ok, TLSSocket} -> TLSSocket;
199 158 _ -> invalid_socket
200 end.
201
202
203 -spec starttls(socket_state(), list()) -> socket_state().
204 starttls(SocketData, TLSOpts) ->
205 318 tcp_to_tls(SocketData, TLSOpts),
206 318 case get_tls_socket(SocketData) of
207 invalid_socket ->
208 158 exit(invalid_socket_after_upgrade_to_tls);
209 NewSocket ->
210 160 SocketData#socket_state{socket = NewSocket, sockmod = ejabberd_tls}
211 end.
212
213
214 -spec starttls(socket_state(), _, _) -> socket_state().
215 starttls(SocketData, TLSOpts, Data) ->
216
:-(
tcp_to_tls(SocketData, TLSOpts),
217
:-(
send(SocketData, Data), %% send last negotiation chunk via tcp
218
:-(
NewSocket = get_tls_socket(SocketData),
219
:-(
SocketData#socket_state{socket = NewSocket, sockmod = ejabberd_tls}.
220
221 -spec compress(socket_state(), integer(), _) -> socket_state().
222 compress(SocketData, InflateSizeLimit, Data) ->
223
:-(
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
224 SocketData#socket_state.sockmod,
225 SocketData#socket_state.socket,
226 InflateSizeLimit),
227
:-(
ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket),
228
:-(
send(SocketData, Data),
229
:-(
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
230
231 %% @doc sockmod=gen_tcp|fast_tls|ejabberd_zlib (ejabberd:sockmod())
232 send(SocketData, Data) ->
233 767 case catch (SocketData#socket_state.sockmod):send(
234 SocketData#socket_state.socket, Data) of
235 767 ok -> ok;
236 {error, timeout} ->
237
:-(
?LOG_INFO(#{what => socket_error, reason => timeout,
238
:-(
socket => SocketData#socket_state.sockmod}),
239
:-(
exit(normal);
240 Error ->
241
:-(
?LOG_INFO(#{what => socket_error, reason => Error,
242
:-(
socket => SocketData#socket_state.sockmod}),
243
:-(
exit(normal)
244 end.
245
246
247 %% @doc Can only be called when in c2s StateData#state.xml_socket is true
248 %% This function is used for HTTP bind
249 %% sockmod=mod_bosh_socket|mod_websockets or any custom module
250 -spec send_xml(socket_state(), mongoose_transport:send_xml_input()) -> ok.
251 send_xml(SocketData, Data) ->
252
:-(
catch (SocketData#socket_state.sockmod):send_xml(
253 SocketData#socket_state.socket, Data).
254
255
256 -spec change_shaper(#socket_state{receiver::atom() | pid() | tuple()}, _) -> any().
257 change_shaper(SocketData, Shaper)
258 when is_pid(SocketData#socket_state.receiver) ->
259 224 ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper);
260 change_shaper(SocketData, Shaper)
261 when is_atom(SocketData#socket_state.receiver) ->
262
:-(
(SocketData#socket_state.receiver):change_shaper(
263 SocketData#socket_state.socket, Shaper).
264
265
266 -spec monitor(socket_state()) -> reference().
267 monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
268 260 erlang:monitor(process, SocketData#socket_state.receiver);
269 monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
270
:-(
(SocketData#socket_state.receiver):monitor(
271 SocketData#socket_state.socket).
272
273
274 -spec get_sockmod(socket_state()) -> ejabberd:sockmod().
275 get_sockmod(SocketData) ->
276 612 SocketData#socket_state.sockmod.
277
278
279 -spec get_peer_certificate(socket_state()) -> mongoose_transport:peercert_return().
280 get_peer_certificate(#socket_state{sockmod = ejabberd_tls, socket = Socket}) ->
281
:-(
ejabberd_tls:get_peer_certificate(Socket);
282 get_peer_certificate(_SocketData) ->
283 100 no_peer_cert.
284
285
286 -spec close(socket_state()) -> ok.
287 close(SocketData) ->
288 260 ejabberd_receiver:close(SocketData#socket_state.receiver).
289
290
291 -spec sockname(socket_state()) -> mongoose_transport:peername_return().
292 sockname(#socket_state{connection_details = #{dest_address := DestAddr,
293 dest_port := DestPort}}) ->
294
:-(
{ok, {DestAddr, DestPort}};
295 sockname(#socket_state{sockmod = gen_tcp, socket = Socket}) ->
296
:-(
inet:sockname(Socket);
297 sockname(#socket_state{sockmod = SockMod, socket = Socket}) ->
298
:-(
SockMod:sockname(Socket).
299
300
301 -spec peername(socket_state()) -> mongoose_transport:peername_return().
302 peername(#socket_state{connection_details = #{src_address := SrcAddr,
303 src_port := SrcPort}}) ->
304 419 {ok, {SrcAddr, SrcPort}};
305 peername(#socket_state{sockmod = gen_tcp, socket = Socket}) ->
306
:-(
inet:peername(Socket);
307 peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
308
:-(
SockMod:peername(Socket).
309
310 -spec get_socket(socket_state()) -> term().
311 get_socket(#socket_state{socket = Socket}) ->
312
:-(
Socket.
313
314
315 format_socket(#socket_state{sockmod = Mod, socket = Socket,
316 receiver = Receiver, connection_details = Info}) ->
317
:-(
Info2 = format_details(Info),
318
:-(
Info2#{socket_module => Mod,
319 socket => format_term(Socket),
320 receiver => format_term(Receiver)};
321 format_socket(_) ->
322
:-(
#{}.
323
324
:-(
format_term(X) -> iolist_to_binary(io_lib:format("~0p", [X])).
325
326 format_details(Info = #{dest_address := DestAddr, src_address := SrcAddr}) ->
327
:-(
Info#{dest_address => inet:ntoa(DestAddr),
328 src_address => inet:ntoa(SrcAddr)}.
Line Hits Source