./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/4,
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(atom() | tuple(), ejabberd:sockmod(),
70 Socket :: port(), Opts :: [{atom(), _}]) -> ok.
71 start(Module, SockMod, Socket, Opts) ->
72 588 case Module:socket_type() of
73 xml_stream ->
74 588 start_xml_stream(Module, SockMod, Socket, Opts);
75 independent ->
76
:-(
ok;
77 raw ->
78
:-(
start_raw_stream(Module, SockMod, Socket, Opts)
79 end.
80
81 -spec start_raw_stream(atom() | tuple(), ejabberd:sockmod(),
82 Socket :: port(), Opts :: [{atom(), _}]) -> ok.
83 start_raw_stream(Module, SockMod, Socket, Opts) ->
84
:-(
case Module:start({SockMod, Socket}, Opts) of
85 {ok, Pid} ->
86
:-(
case SockMod:controlling_process(Socket, Pid) of
87 ok ->
88
:-(
ok;
89 {error, _Reason} ->
90
:-(
SockMod:close(Socket)
91 end;
92 {error, _Reason} ->
93
:-(
SockMod:close(Socket)
94 end.
95
96 -spec start_xml_stream(atom() | tuple(), ejabberd:sockmod(),
97 Socket :: port(), Opts :: [{atom(), _}]) -> ok.
98 start_xml_stream(Module, SockMod, Socket, Opts) ->
99 588 {ReceiverMod, Receiver, RecRef} =
100 case catch SockMod:custom_receiver(Socket) of
101 {receiver, RecMod, RecPid} ->
102
:-(
{RecMod, RecPid, RecMod};
103 _ ->
104 588 RecPid = ejabberd_receiver:start(Socket, SockMod, none, Opts),
105 588 {ejabberd_receiver, RecPid, RecPid}
106 end,
107 588 ConnectionDetails =
108 case lists:keyfind(connection_details, 1, Opts) of
109 588 {_, CD} -> CD;
110
:-(
_ -> throw(connection_details_not_available)
111 end,
112 588 SocketData = #socket_state{sockmod = SockMod,
113 socket = Socket,
114 receiver = RecRef,
115 connection_details = ConnectionDetails},
116 %% set receiver as socket's controlling process before
117 %% the M:start/2 call, that is required for c2s legacy
118 %% TLS connection support.
119 588 case SockMod:controlling_process(Socket, Receiver) of
120 ok ->
121 588 case Module:start({?MODULE, SocketData}, Opts) of
122 {ok, Pid} ->
123 430 ReceiverMod:become_controller(Receiver, Pid);
124 {error, _Reason} ->
125 158 SockMod:close(Socket),
126 158 case ReceiverMod of
127 ejabberd_receiver ->
128 158 ReceiverMod:close(Receiver);
129 _ ->
130
:-(
ok
131 end
132 end;
133 {error, _Reason} ->
134
:-(
SockMod:close(Socket)
135 end.
136
137 -type option_value() :: 'asn1' | 'cdr' | 'false' | 'fcgi' | 'http' | 'http_bin'
138 | 'line' | 'once' | 'raw' | 'sunrm' | 'tpkt' | 'true'
139 | integer() | inet:ip_address().
140 -type option() :: 'binary' | 'inet' | 'inet6' | 'list'
141 | {atom(), option_value()}
142 | {'raw', non_neg_integer(), non_neg_integer(), binary()}.
143 -spec connect(Addr :: atom() | string() | inet:ip_address(),
144 Port :: inet:port_number(),
145 Opts :: [option()]) -> {'error', atom()} | {'ok', socket_state()}.
146 connect(Addr, Port, Opts) ->
147
:-(
connect(Addr, Port, Opts, infinity).
148
149
150 -spec connect(Addr :: atom() | string() | inet:ip_address(),
151 Port :: inet:port_number(),
152 Opts :: [option()],
153 Timeout :: non_neg_integer() | infinity
154 ) -> {'error', atom()} | {'ok', socket_state()}.
155 connect(Addr, Port, Opts, Timeout) ->
156 19 case gen_tcp:connect(Addr, Port, Opts, Timeout) of
157 {ok, Socket} ->
158 19 Receiver = ejabberd_receiver:start(Socket, gen_tcp, none, Opts),
159 19 {SrcAddr, SrcPort} = case inet:sockname(Socket) of
160 19 {ok, {A, P}} -> {A, P};
161
:-(
{error, _} -> {unknown, unknown}
162 end,
163 19 SocketData = #socket_state{sockmod = gen_tcp,
164 socket = Socket,
165 receiver = Receiver,
166 connection_details = #{proxy => false,
167 src_address => SrcAddr,
168 src_port => SrcPort,
169 dest_address => Addr,
170 dest_port => Port}},
171 19 Pid = self(),
172 19 case gen_tcp:controlling_process(Socket, Receiver) of
173 ok ->
174 19 ejabberd_receiver:become_controller(Receiver, Pid),
175 19 {ok, SocketData};
176 {error, _Reason} = Error ->
177
:-(
gen_tcp:close(Socket),
178
:-(
Error
179 end;
180 {error, _Reason} = Error ->
181
:-(
Error
182 end.
183
184
185 -spec tcp_to_tls(socket_state(), list()) -> ejabberd_tls:tls_socket().
186 tcp_to_tls(#socket_state{receiver = Receiver}, TLSOpts) ->
187 339 SanitizedTLSOpts = case lists:keyfind(protocol_options, 1, TLSOpts) of
188 339 false -> [{protocol_options, "no_sslv2|no_sslv3|no_tlsv1|no_tlsv1_1"} | TLSOpts];
189 {_, ProtoOpts} ->
190
:-(
NewProtoOpts = {protocol_options, string:join(ProtoOpts, "|")},
191
:-(
lists:keyreplace(protocol_options, 1, TLSOpts, NewProtoOpts)
192 end,
193 339 ejabberd_receiver:starttls(Receiver, SanitizedTLSOpts).
194
195 get_tls_socket(#socket_state{receiver = Receiver}) ->
196 339 case ejabberd_receiver:get_socket(Receiver) of
197 181 {ok, TLSSocket} -> TLSSocket;
198 158 _ -> invalid_socket
199 end.
200
201
202 -spec starttls(socket_state(), list()) -> socket_state().
203 starttls(SocketData, TLSOpts) ->
204 324 tcp_to_tls(SocketData, TLSOpts),
205 324 case get_tls_socket(SocketData) of
206 invalid_socket ->
207 158 exit(invalid_socket_after_upgrade_to_tls);
208 NewSocket ->
209 166 SocketData#socket_state{socket = NewSocket, sockmod = ejabberd_tls}
210 end.
211
212
213 -spec starttls(socket_state(), _, _) -> socket_state().
214 starttls(SocketData, TLSOpts, Data) ->
215 15 tcp_to_tls(SocketData, TLSOpts),
216 15 send(SocketData, Data), %% send last negotiation chunk via tcp
217 15 NewSocket = get_tls_socket(SocketData),
218 15 SocketData#socket_state{socket = NewSocket, sockmod = ejabberd_tls}.
219
220 -spec compress(socket_state(), integer(), _) -> socket_state().
221 compress(SocketData, InflateSizeLimit, Data) ->
222
:-(
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
223 SocketData#socket_state.sockmod,
224 SocketData#socket_state.socket,
225 InflateSizeLimit),
226
:-(
ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket),
227
:-(
send(SocketData, Data),
228
:-(
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
229
230 %% @doc sockmod=gen_tcp|fast_tls|ejabberd_zlib (ejabberd:sockmod())
231 send(SocketData, Data) ->
232 2428 case catch (SocketData#socket_state.sockmod):send(
233 SocketData#socket_state.socket, Data) of
234 2425 ok -> ok;
235 {error, timeout} ->
236
:-(
?LOG_INFO(#{what => socket_error, reason => timeout,
237
:-(
socket => SocketData#socket_state.sockmod}),
238
:-(
exit(normal);
239 Error ->
240 3 ?LOG_INFO(#{what => socket_error, reason => Error,
241 3 socket => SocketData#socket_state.sockmod}),
242 3 exit(normal)
243 end.
244
245
246 %% @doc Can only be called when in c2s StateData#state.xml_socket is true
247 %% This function is used for HTTP bind
248 %% sockmod=mod_bosh_socket|mod_websockets or any custom module
249 -spec send_xml(socket_state(), mongoose_transport:send_xml_input()) -> ok.
250 send_xml(SocketData, Data) ->
251
:-(
catch (SocketData#socket_state.sockmod):send_xml(
252 SocketData#socket_state.socket, Data).
253
254
255 -spec change_shaper(#socket_state{receiver::atom() | pid() | tuple()}, _) -> any().
256 change_shaper(SocketData, Shaper)
257 when is_pid(SocketData#socket_state.receiver) ->
258 561 ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper);
259 change_shaper(SocketData, Shaper)
260 when is_atom(SocketData#socket_state.receiver) ->
261
:-(
(SocketData#socket_state.receiver):change_shaper(
262 SocketData#socket_state.socket, Shaper).
263
264
265 -spec monitor(socket_state()) -> reference().
266 monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
267 401 erlang:monitor(process, SocketData#socket_state.receiver);
268 monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
269
:-(
(SocketData#socket_state.receiver):monitor(
270 SocketData#socket_state.socket).
271
272
273 -spec get_sockmod(socket_state()) -> ejabberd:sockmod().
274 get_sockmod(SocketData) ->
275 1527 SocketData#socket_state.sockmod.
276
277
278 -spec get_peer_certificate(socket_state()) -> mongoose_transport:peercert_return().
279 get_peer_certificate(#socket_state{sockmod = ejabberd_tls, socket = Socket}) ->
280 19 ejabberd_tls:get_peer_certificate(Socket);
281 get_peer_certificate(_SocketData) ->
282 238 no_peer_cert.
283
284
285 -spec close(socket_state()) -> ok.
286 close(SocketData) ->
287 449 ejabberd_receiver:close(SocketData#socket_state.receiver).
288
289
290 -spec sockname(socket_state()) -> mongoose_transport:peername_return().
291 sockname(#socket_state{connection_details = #{dest_address := DestAddr,
292 dest_port := DestPort}}) ->
293
:-(
{ok, {DestAddr, DestPort}};
294 sockname(#socket_state{sockmod = gen_tcp, socket = Socket}) ->
295
:-(
inet:sockname(Socket);
296 sockname(#socket_state{sockmod = SockMod, socket = Socket}) ->
297
:-(
SockMod:sockname(Socket).
298
299
300 -spec peername(socket_state()) -> mongoose_transport:peername_return().
301 peername(#socket_state{connection_details = #{src_address := SrcAddr,
302 src_port := SrcPort}}) ->
303 557 {ok, {SrcAddr, SrcPort}};
304 peername(#socket_state{sockmod = gen_tcp, socket = Socket}) ->
305
:-(
inet:peername(Socket);
306 peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
307
:-(
SockMod:peername(Socket).
308
309 -spec get_socket(socket_state()) -> term().
310 get_socket(#socket_state{socket = Socket}) ->
311
:-(
Socket.
312
313
314 format_socket(#socket_state{sockmod = Mod, socket = Socket,
315 receiver = Receiver, connection_details = Info}) ->
316
:-(
Info2 = format_details(Info),
317
:-(
Info2#{socket_module => Mod,
318 socket => format_term(Socket),
319 receiver => format_term(Receiver)};
320 format_socket(_) ->
321
:-(
#{}.
322
323
:-(
format_term(X) -> iolist_to_binary(io_lib:format("~0p", [X])).
324
325 format_details(Info = #{dest_address := DestAddr, src_address := SrcAddr}) ->
326
:-(
Info#{dest_address => inet:ntoa(DestAddr),
327 src_address => inet:ntoa(SrcAddr)}.
Line Hits Source