./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 595 case mongoose_listener:socket_type(Module) of
74 xml_stream ->
75 595 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 595 {ReceiverMod, Receiver, RecRef} =
103 case catch SockMod:custom_receiver(Socket) of
104 {receiver, RecMod, RecPid} ->
105
:-(
{RecMod, RecPid, RecMod};
106 _ ->
107 595 RecPid = ejabberd_receiver:start(Socket, SockMod, none, Opts),
108 595 {ejabberd_receiver, RecPid, RecPid}
109 end,
110 595 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 595 case SockMod:controlling_process(Socket, Receiver) of
118 ok ->
119 595 case Module:start({?MODULE, SocketData}, Opts) of
120 {ok, Pid} ->
121 437 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 19 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 19 ReceiverOpts = #{max_stanza_size => infinity, hibernate_after => 0},
159 19 Receiver = ejabberd_receiver:start(Socket, gen_tcp, none, ReceiverOpts),
160 19 {SrcAddr, SrcPort} = case inet:sockname(Socket) of
161 19 {ok, {A, P}} -> {A, P};
162
:-(
{error, _} -> {unknown, unknown}
163 end,
164 19 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 19 Pid = self(),
173 19 case gen_tcp:controlling_process(Socket, Receiver) of
174 ok ->
175 19 ejabberd_receiver:become_controller(Receiver, Pid),
176 19 {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(), mongoose_tls:options()) -> mongoose_tls:tls_socket().
187 tcp_to_tls(#socket_state{receiver = Receiver}, TLSOpts) ->
188 340 ejabberd_receiver:starttls(Receiver, TLSOpts).
189
190 get_tls_socket(#socket_state{receiver = Receiver}) ->
191 340 case ejabberd_receiver:get_socket(Receiver) of
192 182 {ok, TLSSocket} -> TLSSocket;
193 158 _ -> invalid_socket
194 end.
195
196
197 -spec starttls(socket_state(), mongoose_tls:options()) -> socket_state().
198 starttls(SocketData, TLSOpts) ->
199 324 tcp_to_tls(SocketData, TLSOpts),
200 324 case get_tls_socket(SocketData) of
201 invalid_socket ->
202 158 exit(invalid_socket_after_upgrade_to_tls);
203 NewSocket ->
204 166 SocketData#socket_state{socket = NewSocket, sockmod = mongoose_tls}
205 end.
206
207
208 -spec starttls(socket_state(), mongoose_tls:options(), _) -> socket_state().
209 starttls(SocketData, TLSOpts, Data) ->
210 16 tcp_to_tls(SocketData, TLSOpts),
211 16 send(SocketData, Data), %% send last negotiation chunk via tcp
212 16 NewSocket = get_tls_socket(SocketData),
213 16 SocketData#socket_state{socket = NewSocket, sockmod = mongoose_tls}.
214
215 -spec compress(socket_state(), integer(), _) -> socket_state().
216 compress(SocketData, InflateSizeLimit, Data) ->
217
:-(
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
218 SocketData#socket_state.sockmod,
219 SocketData#socket_state.socket,
220 InflateSizeLimit),
221
:-(
ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket),
222
:-(
send(SocketData, Data),
223
:-(
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
224
225 %% @doc sockmod=gen_tcp|fast_tls|ejabberd_zlib (ejabberd:sockmod())
226 send(SocketData, Data) ->
227 2460 case catch (SocketData#socket_state.sockmod):send(
228 SocketData#socket_state.socket, Data) of
229 2456 ok -> ok;
230 {error, timeout} ->
231
:-(
?LOG_INFO(#{what => socket_error, reason => timeout,
232
:-(
socket => SocketData#socket_state.sockmod}),
233
:-(
exit(normal);
234 Error ->
235 4 ?LOG_INFO(#{what => socket_error, reason => Error,
236 4 socket => SocketData#socket_state.sockmod}),
237 4 exit(normal)
238 end.
239
240
241 %% @doc Can only be called when in c2s StateData#state.xml_socket is true
242 %% This function is used for HTTP bind
243 %% sockmod=mod_bosh_socket|mod_websockets or any custom module
244 -spec send_xml(socket_state(), mongoose_transport:send_xml_input()) -> ok.
245 send_xml(SocketData, Data) ->
246
:-(
catch (SocketData#socket_state.sockmod):send_xml(
247 SocketData#socket_state.socket, Data).
248
249
250 -spec change_shaper(#socket_state{receiver::atom() | pid() | tuple()}, _) -> any().
251 change_shaper(SocketData, Shaper)
252 when is_pid(SocketData#socket_state.receiver) ->
253 564 ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper);
254 change_shaper(SocketData, Shaper)
255 when is_atom(SocketData#socket_state.receiver) ->
256
:-(
(SocketData#socket_state.receiver):change_shaper(
257 SocketData#socket_state.socket, Shaper).
258
259
260 -spec monitor(socket_state()) -> reference().
261 monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
262 402 erlang:monitor(process, SocketData#socket_state.receiver);
263 monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
264
:-(
(SocketData#socket_state.receiver):monitor(
265 SocketData#socket_state.socket).
266
267
268 -spec get_sockmod(socket_state()) -> ejabberd:sockmod().
269 get_sockmod(SocketData) ->
270 2327 SocketData#socket_state.sockmod.
271
272
273 -spec get_peer_certificate(socket_state()) -> mongoose_transport:peercert_return().
274 get_peer_certificate(#socket_state{sockmod = mongoose_tls, socket = Socket}) ->
275 19 mongoose_tls:get_peer_certificate(Socket);
276 get_peer_certificate(_SocketData) ->
277 239 no_peer_cert.
278
279
280 -spec close(socket_state()) -> ok.
281 close(SocketData) ->
282 456 ejabberd_receiver:close(SocketData#socket_state.receiver).
283
284
285 -spec sockname(socket_state()) -> mongoose_transport:peername_return().
286 sockname(#socket_state{connection_details = #{dest_address := DestAddr,
287 dest_port := DestPort}}) ->
288
:-(
{ok, {DestAddr, DestPort}};
289 sockname(#socket_state{sockmod = gen_tcp, socket = Socket}) ->
290
:-(
inet:sockname(Socket);
291 sockname(#socket_state{sockmod = SockMod, socket = Socket}) ->
292
:-(
SockMod:sockname(Socket).
293
294
295 -spec peername(socket_state()) -> mongoose_transport:peername_return().
296 peername(#socket_state{connection_details = #{src_address := SrcAddr,
297 src_port := SrcPort}}) ->
298 557 {ok, {SrcAddr, SrcPort}};
299 peername(#socket_state{sockmod = gen_tcp, socket = Socket}) ->
300
:-(
inet:peername(Socket);
301 peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
302
:-(
SockMod:peername(Socket).
303
304 -spec get_socket(socket_state()) -> term().
305 get_socket(#socket_state{socket = Socket}) ->
306
:-(
Socket.
307
308
309 format_socket(#socket_state{sockmod = Mod, socket = Socket,
310 receiver = Receiver, connection_details = Info}) ->
311
:-(
Info2 = format_details(Info),
312
:-(
Info2#{socket_module => Mod,
313 socket => format_term(Socket),
314 receiver => format_term(Receiver)};
315 format_socket(_) ->
316
:-(
#{}.
317
318
:-(
format_term(X) -> iolist_to_binary(io_lib:format("~0p", [X])).
319
320 format_details(Info = #{dest_address := DestAddr, src_address := SrcAddr}) ->
321
:-(
Info#{dest_address => inet:ntoa(DestAddr),
322 src_address => inet:ntoa(SrcAddr)}.
Line Hits Source