~ubuntu-branches/ubuntu/trusty/ejabberd/trusty-proposed

« back to all changes in this revision

Viewing changes to src/stun/ejabberd_stun.erl

  • Committer: Bazaar Package Importer
  • Author(s): Gerfried Fuchs, Konstantin Khomoutov, Gerfried Fuchs
  • Date: 2009-12-04 18:22:49 UTC
  • mfrom: (1.1.11 upstream)
  • Revision ID: james.westby@ubuntu.com-20091204182249-6jfmdz8878h7oaos
Tags: 2.1.0-1
[ Konstantin Khomoutov ]
* New upstream release (Closes: #519858).
  This also adds support for LDAPS upstream (Closes: #526145).
* Do not depend on cdbs anymore, port debian/rules to dh+quilt,
  remove build dependency on patchutils, use erlang-depends.
* Bump debhelper version to 7, standards base to 3.8.3
* Depend on erlang R13B.
* Recommend imagemagick (for captcha support).
* Remove deprecated patches (ssl.patch patch, dynamic_compile_loglevel.patch,
  ldaps.patch, update.patch, proxy.patch, caps.patch, convert.patch,
  s2s.patch).
* Replace mod_ctlextra with mod_admin_extra.
* Use upstream inetrc file.
* Bring debian/ejabberd.cfg and ejabberdctl in sync with upstream.
* Update ejabberdctl manual page.
* Provide NEWS file.
* Rework README.Debian:
  * Group all information into sections.
  * Describe issues with epam binary (Closes: #502791).
  * Discuss how to use DBMS backends (Closes: #540915, #507144).
  * Discuss upgrading from 2.0.x series.
* Implement PID file management (Closes: #519858).
* Make logrotate process all files matching "*.log".
* Improve init script:
  * Make init script LSB-compliant.
  * Implement "live" target which allows to run ejabberd in foreground.
* Make captcha.sh use bash explicitly.
* Rework node-generation for ejabberdctl to fix ejabberd's atom table
  overflows while preserving the possibility to run several versions
  of ejabberdctl concurrently as before.
* Add webadmin patch restoring compatibility with Erlang/OTP <= R12B-4.
* Integrate upstream patch for EJAB-1106.
* Add upstream patch for EJAB-1098.
* Add upstream patch for EJAB-1045.
* Add Konstantin Khomoutov to uploaders.
* Add Japanese debconf translation (thanks to Hideki Yamane)
  (Closes: #558071).

[ Gerfried Fuchs ]
* Build-Depend on po-debconf so po2debconf can be called.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%%%-------------------------------------------------------------------
 
2
%%% File    : ejabberd_stun.erl
 
3
%%% Author  : Evgeniy Khramtsov <ekhramtsov@process-one.net>
 
4
%%% Description : RFC5389 implementation.
 
5
%%%               Currently only Binding usage is supported.
 
6
%%%
 
7
%%% Created :  8 Aug 2009 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
 
8
%%%
 
9
%%%
 
10
%%% ejabberd, Copyright (C) 2002-2009   ProcessOne
 
11
%%%
 
12
%%% This program is free software; you can redistribute it and/or
 
13
%%% modify it under the terms of the GNU General Public License as
 
14
%%% published by the Free Software Foundation; either version 2 of the
 
15
%%% License, or (at your option) any later version.
 
16
%%%
 
17
%%% This program is distributed in the hope that it will be useful,
 
18
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
 
19
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
20
%%% General Public License for more details.
 
21
%%%
 
22
%%% You should have received a copy of the GNU General Public License
 
23
%%% along with this program; if not, write to the Free Software
 
24
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 
25
%%% 02111-1307 USA
 
26
%%%
 
27
%%%-------------------------------------------------------------------
 
28
-module(ejabberd_stun).
 
29
 
 
30
-behaviour(gen_fsm).
 
31
 
 
32
%% API
 
33
-export([start_link/2,
 
34
         start/2,
 
35
         socket_type/0,
 
36
         udp_recv/5]).
 
37
 
 
38
%% gen_fsm callbacks
 
39
-export([init/1,
 
40
         handle_event/3,
 
41
         handle_sync_event/4,
 
42
         handle_info/3,
 
43
         terminate/3,
 
44
         code_change/4]).
 
45
 
 
46
%% gen_fsm states
 
47
-export([wait_for_tls/2,
 
48
         session_established/2]).
 
49
 
 
50
-include("ejabberd.hrl").
 
51
-include("stun.hrl").
 
52
 
 
53
-define(MAX_BUF_SIZE, 64*1024). %% 64kb
 
54
-define(TIMEOUT, 10000). %% 10 sec
 
55
 
 
56
-record(state, {sock,
 
57
                sock_mod = gen_tcp,
 
58
                certfile,
 
59
                peer,
 
60
                tref,
 
61
                buf = <<>>}).
 
62
 
 
63
%%====================================================================
 
64
%% API
 
65
%%====================================================================
 
66
start({gen_tcp, Sock}, Opts) ->
 
67
    supervisor:start_child(ejabberd_stun_sup, [Sock, Opts]).
 
68
 
 
69
start_link(Sock, Opts) ->
 
70
    gen_fsm:start_link(?MODULE, [Sock, Opts], []).
 
71
 
 
72
socket_type() ->
 
73
    raw.
 
74
 
 
75
udp_recv(Sock, Addr, Port, Data, _Opts) ->
 
76
    case stun_codec:decode(Data) of
 
77
        {ok, Msg, <<>>} ->
 
78
            ?DEBUG("got:~n~p", [Msg]),
 
79
            case process(Addr, Port, Msg) of
 
80
                RespMsg when is_record(RespMsg, stun) ->
 
81
                    ?DEBUG("sent:~n~p", [RespMsg]),
 
82
                    Data1 = stun_codec:encode(RespMsg),
 
83
                    gen_udp:send(Sock, Addr, Port, Data1);
 
84
                _ ->
 
85
                    ok
 
86
            end;
 
87
        _ ->
 
88
            ok
 
89
    end.
 
90
 
 
91
%%====================================================================
 
92
%% gen_fsm callbacks
 
93
%%====================================================================
 
94
init([Sock, Opts]) ->
 
95
    case inet:peername(Sock) of
 
96
        {ok, Addr} ->
 
97
            inet:setopts(Sock, [{active, once}]),
 
98
            TRef = erlang:start_timer(?TIMEOUT, self(), stop),
 
99
            State = #state{sock = Sock, peer = Addr, tref = TRef},
 
100
            case proplists:get_value(certfile, Opts) of
 
101
                undefined ->
 
102
                    {ok, session_established, State};
 
103
                CertFile ->
 
104
                    {ok, wait_for_tls, State#state{certfile = CertFile}}
 
105
            end;
 
106
        Err ->
 
107
            Err
 
108
    end.
 
109
 
 
110
wait_for_tls(Event, State) ->
 
111
    ?INFO_MSG("unexpected event in wait_for_tls: ~p", [Event]),
 
112
    {next_state, wait_for_tls, State}.
 
113
 
 
114
session_established(Msg, State) when is_record(Msg, stun) ->
 
115
    ?DEBUG("got:~n~p", [Msg]),
 
116
    {Addr, Port} = State#state.peer,
 
117
    case process(Addr, Port, Msg) of
 
118
        Resp when is_record(Resp, stun) ->
 
119
            ?DEBUG("sent:~n~p", [Resp]),
 
120
            Data = stun_codec:encode(Resp),
 
121
            (State#state.sock_mod):send(State#state.sock, Data);
 
122
        _ ->
 
123
            ok
 
124
    end,
 
125
    {next_state, session_established, State};
 
126
session_established(Event, State) ->
 
127
    ?INFO_MSG("unexpected event in session_established: ~p", [Event]),
 
128
    {next_state, session_established, State}.
 
129
 
 
130
handle_event(_Event, StateName, State) ->
 
131
    {next_state, StateName, State}.
 
132
 
 
133
handle_sync_event(_Event, _From, StateName, State) ->
 
134
    {reply, {error, badarg}, StateName, State}.
 
135
 
 
136
handle_info({tcp, Sock, TLSData}, wait_for_tls, State) ->
 
137
    Buf = <<(State#state.buf)/binary, TLSData/binary>>,
 
138
    %% Check if the initial message is a TLS handshake
 
139
    case Buf of
 
140
        _ when size(Buf) < 3 ->
 
141
            {next_state, wait_for_tls,
 
142
             update_state(State#state{buf = Buf})};
 
143
        <<_:16, 1, _/binary>> ->
 
144
            TLSOpts = [{certfile, State#state.certfile}],
 
145
            {ok, TLSSock} = tls:tcp_to_tls(Sock, TLSOpts),
 
146
            NewState = State#state{sock = TLSSock,
 
147
                                   buf = <<>>,
 
148
                                   sock_mod = tls},
 
149
            case tls:recv_data(TLSSock, Buf) of
 
150
                {ok, Data} ->
 
151
                    process_data(session_established, NewState, Data);
 
152
                _Err ->
 
153
                    {stop, normal, NewState}
 
154
            end;
 
155
        _ ->
 
156
            process_data(session_established, State, TLSData)
 
157
    end;
 
158
handle_info({tcp, _Sock, TLSData}, StateName,
 
159
            #state{sock_mod = tls} = State) ->
 
160
    case tls:recv_data(State#state.sock, TLSData) of
 
161
        {ok, Data} ->
 
162
            process_data(StateName, State, Data);
 
163
        _Err ->
 
164
            {stop, normal, State}
 
165
    end;
 
166
handle_info({tcp, _Sock, Data}, StateName, State) ->
 
167
    process_data(StateName, State, Data);
 
168
handle_info({tcp_closed, _Sock}, _StateName, State) ->
 
169
    ?DEBUG("connection reset by peer", []),
 
170
    {stop, normal, State};
 
171
handle_info({tcp_error, _Sock, Reason}, _StateName, State) ->
 
172
    ?DEBUG("connection error: ~p", [Reason]),
 
173
    {stop, normal, State};
 
174
handle_info({timeout, TRef, stop}, _StateName,
 
175
            #state{tref = TRef} = State) ->
 
176
    {stop, normal, State};
 
177
handle_info(Info, StateName, State) ->
 
178
    ?INFO_MSG("unexpected info: ~p", [Info]),
 
179
    {next_state, StateName, State}.
 
180
 
 
181
terminate(_Reason, _StateName, State) ->
 
182
    catch (State#state.sock_mod):close(State#state.sock),
 
183
    ok.
 
184
 
 
185
code_change(_OldVsn, StateName, State, _Extra) ->
 
186
    {ok, StateName, State}.
 
187
 
 
188
%%--------------------------------------------------------------------
 
189
%%% Internal functions
 
190
%%--------------------------------------------------------------------
 
191
process(Addr, Port, #stun{class = request, unsupported = []} = Msg) ->
 
192
    Resp = prepare_response(Msg),
 
193
    if Msg#stun.method == ?STUN_METHOD_BINDING ->
 
194
            case stun_codec:version(Msg) of
 
195
                old ->
 
196
                    Resp#stun{class = response,
 
197
                              'MAPPED-ADDRESS' = {Addr, Port}};
 
198
                new ->
 
199
                    Resp#stun{class = response,
 
200
                              'XOR-MAPPED-ADDRESS' = {Addr, Port}}
 
201
            end;
 
202
       true ->
 
203
            Resp#stun{class = error,
 
204
                      'ERROR-CODE' = {405, <<"Method Not Allowed">>}}
 
205
    end;
 
206
process(_Addr, _Port, #stun{class = request} = Msg) ->
 
207
    Resp = prepare_response(Msg),
 
208
    Resp#stun{class = error,
 
209
              'UNKNOWN-ATTRIBUTES' = Msg#stun.unsupported,
 
210
              'ERROR-CODE' = {420, stun_codec:reason(420)}};
 
211
process(_Addr, _Port, _Msg) ->
 
212
    pass.
 
213
 
 
214
prepare_response(Msg) ->
 
215
    Version = list_to_binary("ejabberd " ++ ?VERSION),
 
216
    #stun{method = Msg#stun.method,
 
217
          magic = Msg#stun.magic,
 
218
          trid = Msg#stun.trid,
 
219
          'SOFTWARE' = Version}.
 
220
 
 
221
process_data(NextStateName, #state{buf = Buf} = State, Data) ->
 
222
    NewBuf = <<Buf/binary, Data/binary>>,
 
223
    case stun_codec:decode(NewBuf) of
 
224
        {ok, Msg, Tail} ->
 
225
            gen_fsm:send_event(self(), Msg),
 
226
            process_data(NextStateName, State#state{buf = <<>>}, Tail);
 
227
        empty ->
 
228
            NewState = State#state{buf = <<>>},
 
229
            {next_state, NextStateName, update_state(NewState)};
 
230
        more when size(NewBuf) < ?MAX_BUF_SIZE ->
 
231
            NewState = State#state{buf = NewBuf},
 
232
            {next_state, NextStateName, update_state(NewState)};
 
233
        _ ->
 
234
            {stop, normal, State}
 
235
    end.
 
236
 
 
237
update_state(#state{sock = Sock} = State) ->
 
238
    case State#state.sock_mod of
 
239
        gen_tcp ->
 
240
            inet:setopts(Sock, [{active, once}]);
 
241
        SockMod ->
 
242
            SockMod:setopts(Sock, [{active, once}])
 
243
    end,
 
244
    cancel_timer(State#state.tref),
 
245
    TRef = erlang:start_timer(?TIMEOUT, self(), stop),
 
246
    State#state{tref = TRef}.
 
247
 
 
248
cancel_timer(TRef) ->
 
249
    case erlang:cancel_timer(TRef) of
 
250
        false ->
 
251
            receive
 
252
                {timeout, TRef, _} ->
 
253
                    ok
 
254
            after 0 ->
 
255
                    ok
 
256
            end;
 
257
        _ ->
 
258
            ok
 
259
    end.