25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-26 17:38:45 +01:00

Merge branch '2.2.x' of git+ssh://git@gitorious.process-one.net/+ejabberd-developers/ejabberd/maincustomers.git into 2.2.x-applepush

This commit is contained in:
Alexey Shchepin 2010-10-19 13:08:46 +03:00
commit 807af3c08a
83 changed files with 8427 additions and 1087 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
Makefile
archives
build/
*.dSYM/
.gitignore
*.so
contrib/extract_translations/extract_translations.beam
doc/contributed_modules.tex
doc/features.aux
doc/features.log
doc/features.out
doc/features.pdf
doc/guide.aux
doc/guide.idx
doc/guide.ilg
doc/guide.ind
doc/guide.log
doc/guide.out
doc/guide.pdf
doc/guide.toc
doc/version.aux
src/*.beam
src/*.so
src/eldap/eldap_filter_yecc.erl
src/XmppAddr.asn1db
src/XmppAddr.erl
src/XmppAddr.hrl
src/config.log
src/config.status
src/eldap/ELDAPv3.asn1db
src/eldap/ELDAPv3.beam
src/eldap/ELDAPv3.erl
src/eldap/ELDAPv3.hrl
src/epam
src/ejabberdctl.example
src/ejabberd.init

View File

@ -1,5 +0,0 @@
% List of ejabberd-modules to add for ejabberd packaging (source archive and installer)
%
% HTTP-binding:
%https://svn.process-one.net/ejabberd-modules/http_bind/trunk
%https://svn.process-one.net/ejabberd-modules/mod_http_fileserver/trunk

View File

@ -1944,8 +1944,8 @@ Mnesia as backend.
(see&#XA0;<A HREF="#database">3.2</A>) as backend.
</LI><LI CLASS="li-itemize">&#X2018;_ldap&#X2019;, this means that the module needs an LDAP server as backend.
</LI></UL><P>If you want to,
it is possible to use a relational database to store pieces of
information. You can do this by changing the module name to a name with an
it is possible to use a relational database to store the tables created by some ejabberd modules.
You can do this by changing the module name to a name with an
<TT>_odbc</TT> suffix in <TT>ejabberd</TT> config file. You can use a relational
database for the following data:</P><UL CLASS="itemize"><LI CLASS="li-itemize">
Last connection date and time: Use <TT>mod_last_odbc</TT> instead of
@ -1956,6 +1956,7 @@ Last connection date and time: Use <TT>mod_last_odbc</TT> instead of
</LI><LI CLASS="li-itemize">Users&#X2019; VCARD: Use <TT>mod_vcard_odbc</TT> instead of <TT>mod_vcard</TT>.
</LI><LI CLASS="li-itemize">Private XML storage: Use <TT>mod_private_odbc</TT> instead of <TT>mod_private</TT>.
</LI><LI CLASS="li-itemize">User rules for blocking communications: Use <TT>mod_privacy_odbc</TT> instead of <TT>mod_privacy</TT>.
</LI><LI CLASS="li-itemize">Pub-Sub nodes, items and subscriptions: Use <TT>mod_pubsub_odbc</TT> instead of <TT>mod_pubsub</TT>.
</LI></UL><P>You can find more
<A HREF="http://www.ejabberd.im/contributions">contributed modules</A> on the
<TT>ejabberd</TT> website. Please remember that these contributions might not work or

View File

@ -386,6 +386,9 @@ Some options that you may be interested in modifying:
\titem{--enable-nif}
Replaces some critical Erlang functions with equivalents written in C to improve performance.
This feature requires Erlang/OTP R13B04 or higher.
\titem{--enable-flash-hack}
Enable support for non-standard XML socket clients of Adobe Flash 8 and lower.
\end{description}
\makesubsection{install}{Install}
@ -2000,9 +2003,19 @@ enabled. This can be done, by using next commands:
\makesubsubsection{configuremssql}{Database Connection}
\ind{Microsoft SQL Server!Database Connection}
The configuration of Database Connection for a Microsoft SQL Server
is the same as the configuration for
ODBC compatible servers (see section~\ref{configureodbc}).
By default \ejabberd{} opens 10 connections to the database for each virtual host.
Use this option to modify the value:
\begin{verbatim}
{odbc_pool_size, 10}.
\end{verbatim}
You can configure an interval to make a dummy SQL request
to keep alive the connections to the database.
The default value is 'undefined', so no keepalive requests are made.
Specify in seconds: for example 28800 means 8 hours.
\begin{verbatim}
{odbc_keepalive_interval, undefined}.
\end{verbatim}
\makesubsubsection{mssqlauth}{Authentication}
@ -2010,8 +2023,7 @@ ODBC compatible servers (see section~\ref{configureodbc}).
%TODO: not sure if this section is right!!!!!!
The configuration of Authentication for a Microsoft SQL Server
is the same as the configuration for
The configuration of Microsoft SQL Server is the same as the configuration of
ODBC compatible servers (see section~\ref{odbcauth}).
\makesubsubsection{mssqlstorage}{Storage}
@ -2218,9 +2230,6 @@ and LDAP server supports
\makesubsubsection{ldapconnection}{Connection}
Two connections are established to the LDAP server per vhost,
one for authentication and other for regular calls.
Parameters:
\begin{description}
\titem{\{ldap\_servers, [Servers, ...]\}} \ind{options!ldap\_server}List of IP addresses or DNS names of your
@ -2560,8 +2569,8 @@ You can see which database backend each module needs by looking at the suffix:
\end{itemize}
If you want to,
it is possible to use a relational database to store pieces of
information. You can do this by changing the module name to a name with an
it is possible to use a relational database to store the tables created by some ejabberd modules.
You can do this by changing the module name to a name with an
\term{\_odbc} suffix in \ejabberd{} config file. You can use a relational
database for the following data:
@ -2574,6 +2583,7 @@ database for the following data:
\item Users' VCARD: Use \term{mod\_vcard\_odbc} instead of \term{mod\_vcard}.
\item Private XML storage: Use \term{mod\_private\_odbc} instead of \term{mod\_private}.
\item User rules for blocking communications: Use \term{mod\_privacy\_odbc} instead of \term{mod\_privacy}.
\item Pub-Sub nodes, items and subscriptions: Use \term{mod\_pubsub\_odbc} instead of \term{mod\_pubsub}.
\end{itemize}
You can find more

146
doc/http_post.md Normal file
View File

@ -0,0 +1,146 @@
# Managing pubsub nodes through HTTP Atompub #
## Configuration ##
These options will be used by the service to know how to build URLs. Using the previous configuration items the service should be accessed through `http://notify.push.bbc.co.uk:5280/pshb/<host>/<node>/`.
Also, in the ejabberd_http handler configuration, add the identified line.
{5280, ejabberd_http, [
http_poll,
web_admin,
{request_handlers, [{["pshb"], pshb_http}]} % this should be added
]}
It will automatically detect the version of mod_pubsub (odbc or mnesia) and call the appropriate module.
## Important notice ##
In the current version of the code, some security checks are not done :
* node creation uses the default `all` access_createnode acl, not checking for the actual configuration.
* most read operations are successfully executed without authentication. HOWEVER listing items can only be done when the node access_model is "open". In all other cases, the service returns 403. A finer grained authentication will be implemented.
## Usage example with cURL ##
### Errors ###
HTTP status codes are used as intended. Additionally, the XMPP error stanza can also be set in the body :
$ curl -i -X POST -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost
HTTP/1.1 409 Conflict
Content-Type: text/html; charset=utf-8
Content-Length: 95
Content-type: application/xml
<error code='409' type='cancel'><conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
or
$ curl -i -X DELETE -u cstar@localhost:encore http://localhost:5280/pshb/localhost/princely_musings
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Content-Length: 101
Content-type: application/xml
<error code='404' type='cancel'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
### Getting the service document ###
No authentication necessary. All nodes are listed.
$ curl -i http://host:port/pshb/domain/
### Getting items from a node ###
No authentication done, and all nodes are accessible.
$ curl -i http://host:port/pshb/domain/node/
### Posting a new item ###
$ curl -u jid:password -i -X POST -d @entry.atom http://post:port/pshb/domain/node
User ability to post is based on node configuration.
### Editing a new item ###
$ curl -u jid:password -i -X POST -d @entry.atom http://post:port/pshb/domain/node/itemid
User ability to post is based on node configuration.
### Deleting an item ###
$ curl -u jid:password -i -X DELETE http://post:port/pshb/domain/node/itemid
User ability to post is based on node configuration.
### Creating a new node ###
An instant node can be created if server configuration allows:
$ curl -X POST -u cstar@localhost:encore -d "" http://localhost:5280/pshb/localhost
or
$ curl -X POST -u cstar@localhost:encore -d "<pubsub><create node='princely_musings'/></pubsub>" http://localhost:5280/pshb/localhost
configure element (as per XEP-60) can be passed in the pubsub body.
$ cat createnode.xml
<pubsub><create node='princely_musings' type='flat'/>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/protocol/pubsub#node_config</value>
</field>
<field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
<field var='pubsub#max_payload_size'><value>1028</value></field>
<field var='pubsub#type'><value>Atom</value></field>
</x>
</pubsub>
$ curl -i -X POST -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost
HTTP/1.1 200 OK
Content-Length: 130
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?><pubsub xmlns='http://jabber.org/protocol/pubsub'><create node='princely_musings'/></pubsub>
### Editing a node configuration ###
$ cat editnode.xml
<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
<configure node='princely_musings'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE' type='hidden'>
<value>http://jabber.org/protocol/pubsub#node_config</value>
</field>
<field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
<field var='pubsub#deliver_notifications'><value>1</value></field>
<field var='pubsub#deliver_payloads'><value>1</value></field>
<field var='pubsub#persist_items'><value>1</value></field>
<field var='pubsub#max_items'><value>10</value></field>
<field var='pubsub#item_expire'><value>604800</value></field>
<field var='pubsub#access_model'><value>roster</value></field>
</x>
</configure>
</pubsub>
$ curl -i -X PUT -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost/princely_musings
### Deleting a node ###
A node is deleted by:
$ curl -X DELETE -u cstar@localhost:encore http://localhost:5280/pshb/localhost/princely_musings

View File

@ -74,6 +74,11 @@ ifeq (@pam@, pam)
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
endif
ifeq (@flash_hack@, true)
ERLC_FLAGS+=-DENABLE_FLASH_HACK
CPPFLAGS+=-DENABLE_FLASH_HACK
endif
prefix = @prefix@
exec_prefix = @exec_prefix@
@ -168,7 +173,7 @@ mostlyclean-recursive maintainer-clean-recursive:
@ERLC@ -W $(EFLAGS) $*.erl
$(ERLSHLIBS): %.so: %.c
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LIBS) \
$(subst ../,,$(subst .so,.c,$@)) \
$(EXPAT_LIBS) \
$(EXPAT_CFLAGS) \

563
src/cache_tab.erl Normal file
View File

@ -0,0 +1,563 @@
%%%-------------------------------------------------------------------
%%% File : cache_tab.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Description : Caching key-value table
%%%
%%% Created : 29 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(cache_tab).
-define(GEN_SERVER, gen_server).
-behaviour(?GEN_SERVER).
%% API
-export([start_link/4, new/2, delete/1, delete/3, lookup/3,
insert/4, info/2, tab2list/1, setopts/2,
dirty_lookup/3, dirty_insert/4, dirty_delete/3,
all/0, test/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-record(state, {tab = treap:empty(),
name,
size = 0,
owner,
max_size,
life_time,
warn,
hits = 0,
miss = 0,
procs_num,
cache_missed,
shrink_size}).
-define(PROCNAME, ?MODULE).
-define(CALL_TIMEOUT, 60000).
%% Defaults
-define(MAX_SIZE, 1000).
-define(WARN, true).
-define(CACHE_MISSED, true).
-define(LIFETIME, 600). %% 10 minutes
%%====================================================================
%% API
%%====================================================================
start_link(Proc, Tab, Opts, Owner) ->
?GEN_SERVER:start_link(
{local, Proc}, ?MODULE, [Tab, Opts, get_proc_num(), Owner], []).
new(Tab, Opts) ->
Res = lists:flatmap(
fun(Proc) ->
Spec = {{Tab, Proc},
{?MODULE, start_link,
[Proc, Tab, Opts, self()]},
permanent,
brutal_kill,
worker,
[?MODULE]},
case supervisor:start_child(cache_tab_sup, Spec) of
{ok, _Pid} ->
[ok];
R ->
[R]
end
end, get_all_procs(Tab)),
case lists:filter(fun(ok) -> false; (_) -> true end, Res) of
[] ->
ok;
Err ->
{error, Err}
end.
delete(Tab) ->
lists:foreach(
fun(Proc) ->
supervisor:terminate_child(cache_tab_sup, {Tab, Proc}),
supervisor:delete_child(cache_tab_sup, {Tab, Proc})
end, get_all_procs(Tab)).
delete(Tab, Key, F) ->
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {delete, Key, F}, ?CALL_TIMEOUT).
dirty_delete(Tab, Key, F) ->
F(),
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {dirty_delete, Key}, ?CALL_TIMEOUT).
lookup(Tab, Key, F) ->
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {lookup, Key, F}, ?CALL_TIMEOUT).
dirty_lookup(Tab, Key, F) ->
Proc = get_proc_by_hash(Tab, Key),
case ?GEN_SERVER:call(Proc, {dirty_lookup, Key}, ?CALL_TIMEOUT) of
{ok, '$cached_mismatch'} ->
error;
{ok, Val} ->
{ok, Val};
_ ->
case F() of
{ok, Val} ->
?GEN_SERVER:call(
Proc, {dirty_insert, Key, Val}, ?CALL_TIMEOUT),
{ok, Val};
_ ->
error
end
end.
insert(Tab, Key, Val, F) ->
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {insert, Key, Val, F}, ?CALL_TIMEOUT).
dirty_insert(Tab, Key, Val, F) ->
F(),
?GEN_SERVER:call(
get_proc_by_hash(Tab, Key), {dirty_insert, Key, Val}, ?CALL_TIMEOUT).
info(Tab, Info) ->
case lists:map(
fun(Proc) ->
?GEN_SERVER:call(Proc, {info, Info}, ?CALL_TIMEOUT)
end, get_all_procs(Tab)) of
Res when Info == size ->
{ok, lists:sum(Res)};
Res when Info == all ->
{ok, Res};
Res when Info == ratio ->
{H, M} = lists:foldl(
fun({Hits, Miss}, {HitsAcc, MissAcc}) ->
{HitsAcc + Hits, MissAcc + Miss}
end, {0, 0}, Res),
{ok, [{hits, H}, {miss, M}]};
_ ->
{error, badarg}
end.
setopts(Tab, Opts) ->
lists:foreach(
fun(Proc) ->
?GEN_SERVER:call(Proc, {setopts, Opts}, ?CALL_TIMEOUT)
end, get_all_procs(Tab)).
tab2list(Tab) ->
lists:flatmap(
fun(Proc) ->
?GEN_SERVER:call(Proc, tab2list, ?CALL_TIMEOUT)
end, get_all_procs(Tab)).
all() ->
lists:usort(
[Tab || {{Tab, _}, _, _, _} <- supervisor:which_children(cache_tab_sup)]).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Tab, Opts, N, Pid]) ->
State = #state{procs_num = N,
owner = Pid,
name = Tab},
{ok, do_setopts(State, Opts)}.
handle_call({lookup, Key, F}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
Hits = State#state.hits,
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
case Val of
'$cached_mismatch' ->
{reply, error, NewState};
_ ->
{reply, {ok, Val}, NewState}
end;
_ ->
case catch F() of
{ok, Val} ->
Miss = State#state.miss,
NewState = treap_insert(Key, Val, State),
{reply, {ok, Val}, NewState#state{miss = Miss + 1}};
{'EXIT', Reason} ->
print_error(lookup, [Key], Reason, State),
{reply, error, State};
_ ->
Miss = State#state.miss,
NewState = State#state{miss = Miss + 1},
if State#state.cache_missed ->
{reply, error,
treap_insert(Key, '$cached_mismatch', NewState)};
true ->
{reply, error, NewState}
end
end
end;
handle_call({dirty_lookup, Key}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
Hits = State#state.hits,
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
{reply, {ok, Val}, NewState};
_ ->
Miss = State#state.miss,
NewState = State#state{miss = Miss + 1},
if State#state.cache_missed ->
{reply, error,
treap_insert(Key, '$cached_mismatch', NewState)};
true ->
{reply, error, NewState}
end
end;
handle_call({insert, Key, Val, F}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
{reply, ok, State};
Res ->
case catch F() of
{'EXIT', Reason} ->
print_error(insert, [Key, Val], Reason, State),
{reply, ok, State};
_ ->
NewState = case Res of
{ok, _, _} ->
treap_update(Key, Val, State);
_ ->
treap_insert(Key, Val, State)
end,
{reply, ok, NewState}
end
end;
handle_call({dirty_insert, Key, Val}, _From, #state{tab = T} = State) ->
case treap:lookup(Key, T) of
{ok, _Prio, Val} ->
{reply, ok, State};
{ok, _, _} ->
{reply, ok, treap_update(Key, Val, State)};
_ ->
{reply, ok, treap_insert(Key, Val, State)}
end;
handle_call({delete, Key, F}, _From, State) ->
NewState = treap_delete(Key, State),
case catch F() of
{'EXIT', Reason} ->
print_error(delete, [Key], Reason, State);
_ ->
ok
end,
{reply, ok, NewState};
handle_call({dirty_delete, Key}, _From, State) ->
NewState = treap_delete(Key, State),
{reply, ok, NewState};
handle_call({info, Info}, _From, State) ->
Res = case Info of
size ->
State#state.size;
ratio ->
{State#state.hits, State#state.miss};
all ->
[{max_size, State#state.max_size},
{life_time, State#state.life_time},
{shrink_size, State#state.shrink_size},
{size, State#state.size},
{owner, State#state.owner},
{hits, State#state.hits},
{miss, State#state.miss},
{cache_missed, State#state.cache_missed},
{warn, State#state.warn}];
_ ->
badarg
end,
{reply, Res, State};
handle_call(tab2list, _From, #state{tab = T} = State) ->
Res = treap:fold(
fun({Key, _, Val}, Acc) ->
[{Key, Val}|Acc]
end, [], T),
{reply, Res, State};
handle_call({setopts, Opts}, _From, State) ->
{reply, ok, do_setopts(State, Opts)};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_setopts(#state{procs_num = N} = State, Opts) ->
MaxSize = case {proplists:get_value(max_size, Opts),
State#state.max_size} of
{MS, _} when is_integer(MS), MS > 0 ->
round(MS/N);
{unlimited, _} ->
unlimited;
{_, undefined} ->
round(?MAX_SIZE/N);
{_, MS} ->
MS
end,
LifeTime = case {proplists:get_value(life_time, Opts),
State#state.life_time} of
{LT, _} when is_integer(LT), LT > 0 ->
LT*1000*1000;
{unlimited, _} ->
unlimited;
{_, undefined} ->
?LIFETIME*1000*1000;
{_, LT} ->
LT
end,
ShrinkSize = case {proplists:get_value(shrink_size, Opts),
State#state.shrink_size} of
{SS, _} when is_integer(SS), SS > 0 ->
round(SS/N);
_ when is_integer(MaxSize) ->
round(MaxSize/2);
_ ->
unlimited
end,
Warn = case {proplists:get_value(warn, Opts),
State#state.warn} of
{true, _} ->
true;
{false, _} ->
false;
{_, undefined} ->
?WARN;
{_, W} ->
W
end,
CacheMissed = case proplists:get_value(
cache_missed, Opts, State#state.cache_missed) of
false ->
false;
true ->
true;
_ ->
?CACHE_MISSED
end,
State#state{max_size = MaxSize,
warn = Warn,
life_time = LifeTime,
cache_missed = CacheMissed,
shrink_size = ShrinkSize}.
get_proc_num() ->
erlang:system_info(logical_processors).
get_proc_by_hash(Tab, Term) ->
N = erlang:phash2(Term, get_proc_num()) + 1,
get_proc(Tab, N).
get_proc(Tab, N) ->
list_to_atom(atom_to_list(?PROCNAME) ++ "_" ++
atom_to_list(Tab) ++ "_" ++ integer_to_list(N)).
get_all_procs(Tab) ->
[get_proc(Tab, N) || N <- lists:seq(1, get_proc_num())].
now_priority() ->
{MSec, Sec, USec} = now(),
-((MSec*1000000 + Sec)*1000000 + USec).
treap_update(Key, Val, #state{tab = T} = State) ->
Priority = now_priority(),
NewT = treap:insert(Key, Priority, Val, T),
State#state{tab = NewT}.
treap_insert(Key, Val, State) ->
State1 = clean_treap(State),
#state{size = Size} = State2 = shrink_treap(State1),
treap_update(Key, Val, State2#state{size = Size+1}).
treap_delete(Key, #state{tab = T, size = Size} = State) ->
case treap:lookup(Key, T) of
{ok, _, _} ->
NewT = treap:delete(Key, T),
clean_treap(State#state{tab = NewT, size = Size-1});
_ ->
State
end.
clean_treap(#state{tab = T, size = Size, life_time = LifeTime} = State) ->
if is_integer(LifeTime) ->
Priority = now_priority(),
{Cleaned, NewT} = clean_treap(T, Priority + LifeTime, 0),
State#state{size = Size - Cleaned, tab = NewT};
true ->
State
end.
clean_treap(Treap, CleanPriority, N) ->
case treap:is_empty(Treap) of
true ->
{N, Treap};
false ->
{_Key, Priority, _Value} = treap:get_root(Treap),
if Priority > CleanPriority ->
clean_treap(treap:delete_root(Treap), CleanPriority, N+1);
true ->
{N, Treap}
end
end.
shrink_treap(#state{tab = T,
max_size = MaxSize,
shrink_size = ShrinkSize,
warn = Warn,
size = Size} = State) when Size >= MaxSize ->
if Warn ->
?WARNING_MSG("shrinking table:~n"
"** Table: ~p~n"
"** Processes Number: ~p~n"
"** Max Size: ~p items~n"
"** Shrink Size: ~p items~n"
"** Life Time: ~p microseconds~n"
"** Hits/Miss: ~p/~p~n"
"** Owner: ~p~n"
"** Cache Missed: ~p~n"
"** Instruction: you have to tune cacheing options"
" if this message repeats too frequently",
[State#state.name, State#state.procs_num,
MaxSize, ShrinkSize, State#state.life_time,
State#state.hits, State#state.miss,
State#state.owner, State#state.cache_missed]);
true ->
ok
end,
{Shrinked, NewT} = shrink_treap(T, ShrinkSize, 0),
State#state{tab = NewT, size = Size - Shrinked};
shrink_treap(State) ->
State.
shrink_treap(T, ShrinkSize, ShrinkSize) ->
{ShrinkSize, T};
shrink_treap(T, ShrinkSize, N) ->
case treap:is_empty(T) of
true ->
{N, T};
false ->
shrink_treap(treap:delete_root(T), ShrinkSize, N+1)
end.
print_error(Operation, Args, Reason, State) ->
?ERROR_MSG("callback failed:~n"
"** Tab: ~p~n"
"** Owner: ~p~n"
"** Operation: ~p~n"
"** Args: ~p~n"
"** Reason: ~p",
[State#state.name, State#state.owner,
Operation, Args, Reason]).
%%--------------------------------------------------------------------
%%% Tests
%%--------------------------------------------------------------------
-define(lookup, dirty_lookup).
-define(delete, dirty_delete).
-define(insert, dirty_insert).
%% -define(lookup, lookup).
%% -define(delete, delete).
%% -define(insert, insert).
test() ->
LifeTime = 2,
ok = new(test_tbl, [{life_time, LifeTime}, {max_size, unlimited}]),
check([]),
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
check([{"key", "value"}]),
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
check([{"key", "value"}]),
io:format("** waiting for ~p seconds to check if cleaner works fine...~n",
[LifeTime+1]),
timer:sleep(timer:seconds(LifeTime+1)),
ok = ?insert(test_tbl, "key1", "value1", fun() -> ok end),
check([{"key1", "value1"}]),
ok = ?delete(test_tbl, "key1", fun() -> ok end),
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> {ok, "value"} end),
check([{"key", "value"}]),
ok = ?delete(test_tbl, "key", fun() -> ok end),
check([]),
%% io:format("** testing buggy callbacks...~n"),
%% delete(test_tbl, "key", fun() -> erlang:error(badarg) end),
%% insert(test_tbl, "key", "val", fun() -> erlang:error(badarg) end),
%% lookup(test_tbl, "key", fun() -> erlang:error(badarg) end),
check([]),
delete(test_tbl),
test1().
test1() ->
MaxSize = 10,
ok = new(test_tbl, [{max_size, MaxSize}, {shrink_size, 1}, {warn, false}]),
lists:foreach(
fun(N) ->
ok = ?insert(test_tbl, N, N, fun() -> ok end)
end, lists:seq(1, MaxSize*get_proc_num())),
{ok, MaxSize} = info(test_tbl, size),
delete(test_tbl),
io:format("** testing speed, this may take a while...~n"),
test2(1000),
test2(10000),
test2(100000),
test2(1000000).
test2(Iter) ->
ok = new(test_tbl, [{max_size, unlimited}, {life_time, unlimited}]),
L = lists:seq(1, Iter),
T1 = now(),
lists:foreach(
fun(N) ->
ok = ?insert(test_tbl, N, N, fun() -> ok end)
end, L),
io:format("** average insert (size = ~p): ~p usec~n",
[Iter, round(timer:now_diff(now(), T1)/Iter)]),
T2 = now(),
lists:foreach(
fun(N) ->
{ok, N} = ?lookup(test_tbl, N, fun() -> ok end)
end, L),
io:format("** average lookup (size = ~p): ~p usec~n",
[Iter, round(timer:now_diff(now(), T2)/Iter)]),
{ok, Iter} = info(test_tbl, size),
delete(test_tbl).
check(List) ->
Size = length(List),
{ok, Size} = info(test_tbl, size),
List = tab2list(test_tbl).

53
src/cache_tab_sup.erl Normal file
View File

@ -0,0 +1,53 @@
%%%-------------------------------------------------------------------
%%% File : cache_tab_sup.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Description : Cache tables supervisor
%%%
%%% Created : 30 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(cache_tab_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API functions
%%====================================================================
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
init([]) ->
{ok, {{one_for_one,10,1}, []}}.
%%====================================================================
%% Internal functions
%%====================================================================

359
src/configure vendored
View File

@ -1,13 +1,13 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.65 for ejabberd 2.1.x.
# Generated by GNU Autoconf 2.67 for ejabberd 2.1.x.
#
# Report bugs to <ejabberd@process-one.net>.
#
#
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
# Inc.
# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
# Foundation, Inc.
#
#
# This configure script is free software; the Free Software Foundation
@ -319,7 +319,7 @@ $as_echo X"$as_dir" |
test -d "$as_dir" && break
done
test -z "$as_dirs" || eval "mkdir $as_dirs"
} || test -d "$as_dir" || as_fn_error "cannot create directory $as_dir"
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
} # as_fn_mkdir_p
@ -359,19 +359,19 @@ else
fi # as_fn_arith
# as_fn_error ERROR [LINENO LOG_FD]
# ---------------------------------
# as_fn_error STATUS ERROR [LINENO LOG_FD]
# ----------------------------------------
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
# script with status $?, using 1 if that was 0.
# script with STATUS, using 1 if that was 0.
as_fn_error ()
{
as_status=$?; test $as_status -eq 0 && as_status=1
if test "$3"; then
as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3
as_status=$1; test $as_status -eq 0 && as_status=1
if test "$4"; then
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
fi
$as_echo "$as_me: error: $1" >&2
$as_echo "$as_me: error: $2" >&2
as_fn_exit $as_status
} # as_fn_error
@ -533,7 +533,7 @@ test -n "$DJDIR" || exec 7<&0 </dev/null
exec 6>&1
# Name of the host.
# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
# so uname gets run too.
ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
@ -616,6 +616,7 @@ nif
full_xml
transient_supervisors
db_type
flash_hack
roster_gateway_workaround
hipe
PAM_LIBS
@ -720,6 +721,7 @@ enable_pam
with_pam
enable_hipe
enable_roster_gateway_workaround
enable_flash_hack
enable_mssql
enable_transient_supervisors
enable_full_xml
@ -800,8 +802,9 @@ do
fi
case $ac_option in
*=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
*) ac_optarg=yes ;;
*=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
*=) ac_optarg= ;;
*) ac_optarg=yes ;;
esac
# Accept the important Cygnus configure options, so we can diagnose typos.
@ -846,7 +849,7 @@ do
ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error "invalid feature name: $ac_useropt"
as_fn_error $? "invalid feature name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
@ -872,7 +875,7 @@ do
ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error "invalid feature name: $ac_useropt"
as_fn_error $? "invalid feature name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
@ -1076,7 +1079,7 @@ do
ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error "invalid package name: $ac_useropt"
as_fn_error $? "invalid package name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
@ -1092,7 +1095,7 @@ do
ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error "invalid package name: $ac_useropt"
as_fn_error $? "invalid package name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
@ -1122,8 +1125,8 @@ do
| --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
x_libraries=$ac_optarg ;;
-*) as_fn_error "unrecognized option: \`$ac_option'
Try \`$0 --help' for more information."
-*) as_fn_error $? "unrecognized option: \`$ac_option'
Try \`$0 --help' for more information"
;;
*=*)
@ -1131,7 +1134,7 @@ Try \`$0 --help' for more information."
# Reject names that are not valid shell variable names.
case $ac_envvar in #(
'' | [0-9]* | *[!_$as_cr_alnum]* )
as_fn_error "invalid variable name: \`$ac_envvar'" ;;
as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
esac
eval $ac_envvar=\$ac_optarg
export $ac_envvar ;;
@ -1149,13 +1152,13 @@ done
if test -n "$ac_prev"; then
ac_option=--`echo $ac_prev | sed 's/_/-/g'`
as_fn_error "missing argument to $ac_option"
as_fn_error $? "missing argument to $ac_option"
fi
if test -n "$ac_unrecognized_opts"; then
case $enable_option_checking in
no) ;;
fatal) as_fn_error "unrecognized options: $ac_unrecognized_opts" ;;
fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
*) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
esac
fi
@ -1178,7 +1181,7 @@ do
[\\/$]* | ?:[\\/]* ) continue;;
NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
esac
as_fn_error "expected an absolute directory name for --$ac_var: $ac_val"
as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
done
# There might be people who depend on the old broken behavior: `$host'
@ -1192,8 +1195,8 @@ target=$target_alias
if test "x$host_alias" != x; then
if test "x$build_alias" = x; then
cross_compiling=maybe
$as_echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
If a cross compiler is detected then cross compile mode will be used." >&2
$as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host.
If a cross compiler is detected then cross compile mode will be used" >&2
elif test "x$build_alias" != "x$host_alias"; then
cross_compiling=yes
fi
@ -1208,9 +1211,9 @@ test "$silent" = yes && exec 6>/dev/null
ac_pwd=`pwd` && test -n "$ac_pwd" &&
ac_ls_di=`ls -di .` &&
ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
as_fn_error "working directory cannot be determined"
as_fn_error $? "working directory cannot be determined"
test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
as_fn_error "pwd does not report name of working directory"
as_fn_error $? "pwd does not report name of working directory"
# Find the source files, if location was not specified.
@ -1249,11 +1252,11 @@ else
fi
if test ! -r "$srcdir/$ac_unique_file"; then
test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
as_fn_error "cannot find sources ($ac_unique_file) in $srcdir"
as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
fi
ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
ac_abs_confdir=`(
cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error "$ac_msg"
cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
pwd)`
# When building in place, set srcdir=.
if test "$ac_abs_confdir" = "$ac_pwd"; then
@ -1293,7 +1296,7 @@ Configuration:
--help=short display options specific to this package
--help=recursive display the short help of all the included packages
-V, --version display version information and exit
-q, --quiet, --silent do not print \`checking...' messages
-q, --quiet, --silent do not print \`checking ...' messages
--cache-file=FILE cache test results in FILE [disabled]
-C, --config-cache alias for \`--cache-file=config.cache'
-n, --no-create do not create output files
@ -1368,6 +1371,7 @@ Optional Features:
--enable-roster-gateway-workaround
turn on workaround for processing gateway
subscriptions (default: no)
--enable-flash-hack support Adobe Flash client XML (default: no)
--enable-mssql use Microsoft SQL Server database (default: no,
requires --enable-odbc)
--enable-transient_supervisors
@ -1471,9 +1475,9 @@ test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
ejabberd configure 2.1.x
generated by GNU Autoconf 2.65
generated by GNU Autoconf 2.67
Copyright (C) 2009 Free Software Foundation, Inc.
Copyright (C) 2010 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
_ACEOF
@ -1589,7 +1593,7 @@ $as_echo "$ac_try_echo"; } >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } >/dev/null && {
test $ac_status = 0; } > conftest.i && {
test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
test ! -s conftest.err
}; then :
@ -1613,10 +1617,10 @@ fi
ac_fn_c_check_header_mongrel ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
if eval "test \"\${$3+set}\"" = set; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
if eval "test \"\${$3+set}\"" = set; then :
$as_echo_n "(cached) " >&6
fi
eval ac_res=\$$3
@ -1652,7 +1656,7 @@ if ac_fn_c_try_cpp "$LINENO"; then :
else
ac_header_preproc=no
fi
rm -f conftest.err conftest.$ac_ext
rm -f conftest.err conftest.i conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
$as_echo "$ac_header_preproc" >&6; }
@ -1675,17 +1679,15 @@ $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
( cat <<\_ASBOX
## --------------------------------------- ##
( $as_echo "## --------------------------------------- ##
## Report this to ejabberd@process-one.net ##
## --------------------------------------- ##
_ASBOX
## --------------------------------------- ##"
) | sed "s/^/$as_me: WARNING: /" >&2
;;
esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
if eval "test \"\${$3+set}\"" = set; then :
$as_echo_n "(cached) " >&6
else
eval "$3=\$ac_header_compiler"
@ -1749,7 +1751,7 @@ ac_fn_c_check_header_compile ()
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
if eval "test \"\${$3+set}\"" = set; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@ -1817,7 +1819,7 @@ This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by ejabberd $as_me 2.1.x, which was
generated by GNU Autoconf 2.65. Invocation command line was
generated by GNU Autoconf 2.67. Invocation command line was
$ $0 $@
@ -1927,11 +1929,9 @@ trap 'exit_status=$?
{
echo
cat <<\_ASBOX
## ---------------- ##
$as_echo "## ---------------- ##
## Cache variables. ##
## ---------------- ##
_ASBOX
## ---------------- ##"
echo
# The following way of writing the cache mishandles newlines in values,
(
@ -1965,11 +1965,9 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
)
echo
cat <<\_ASBOX
## ----------------- ##
$as_echo "## ----------------- ##
## Output variables. ##
## ----------------- ##
_ASBOX
## ----------------- ##"
echo
for ac_var in $ac_subst_vars
do
@ -1982,11 +1980,9 @@ _ASBOX
echo
if test -n "$ac_subst_files"; then
cat <<\_ASBOX
## ------------------- ##
$as_echo "## ------------------- ##
## File substitutions. ##
## ------------------- ##
_ASBOX
## ------------------- ##"
echo
for ac_var in $ac_subst_files
do
@ -2000,11 +1996,9 @@ _ASBOX
fi
if test -s confdefs.h; then
cat <<\_ASBOX
## ----------- ##
$as_echo "## ----------- ##
## confdefs.h. ##
## ----------- ##
_ASBOX
## ----------- ##"
echo
cat confdefs.h
echo
@ -2059,7 +2053,12 @@ _ACEOF
ac_site_file1=NONE
ac_site_file2=NONE
if test -n "$CONFIG_SITE"; then
ac_site_file1=$CONFIG_SITE
# We do not want a PATH search for config.site.
case $CONFIG_SITE in #((
-*) ac_site_file1=./$CONFIG_SITE;;
*/*) ac_site_file1=$CONFIG_SITE;;
*) ac_site_file1=./$CONFIG_SITE;;
esac
elif test "x$prefix" != xNONE; then
ac_site_file1=$prefix/share/config.site
ac_site_file2=$prefix/etc/config.site
@ -2074,7 +2073,11 @@ do
{ $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
$as_echo "$as_me: loading site script $ac_site_file" >&6;}
sed 's/^/| /' "$ac_site_file" >&5
. "$ac_site_file"
. "$ac_site_file" \
|| { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "failed to load site script $ac_site_file
See \`config.log' for more details" "$LINENO" 5 ; }
fi
done
@ -2150,7 +2153,7 @@ if $ac_cache_corrupted; then
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
as_fn_error "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
fi
## -------------------- ##
## Main body of script. ##
@ -2465,8 +2468,8 @@ fi
test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error "no acceptable C compiler found in \$PATH
See \`config.log' for more details." "$LINENO" 5; }
as_fn_error $? "no acceptable C compiler found in \$PATH
See \`config.log' for more details" "$LINENO" 5 ; }
# Provide some information about the compiler.
$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
@ -2580,9 +2583,8 @@ sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
{ as_fn_set_status 77
as_fn_error "C compiler cannot create executables
See \`config.log' for more details." "$LINENO" 5; }; }
as_fn_error 77 "C compiler cannot create executables
See \`config.log' for more details" "$LINENO" 5 ; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
@ -2624,8 +2626,8 @@ done
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error "cannot compute suffix of executables: cannot compile and link
See \`config.log' for more details." "$LINENO" 5; }
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
See \`config.log' for more details" "$LINENO" 5 ; }
fi
rm -f conftest conftest$ac_cv_exeext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
@ -2682,9 +2684,9 @@ $as_echo "$ac_try_echo"; } >&5
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error "cannot run C compiled programs.
as_fn_error $? "cannot run C compiled programs.
If you meant to cross compile, use \`--host'.
See \`config.log' for more details." "$LINENO" 5; }
See \`config.log' for more details" "$LINENO" 5 ; }
fi
fi
fi
@ -2735,8 +2737,8 @@ sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error "cannot compute suffix of object files: cannot compile
See \`config.log' for more details." "$LINENO" 5; }
as_fn_error $? "cannot compute suffix of object files: cannot compile
See \`config.log' for more details" "$LINENO" 5 ; }
fi
rm -f conftest.$ac_cv_objext conftest.$ac_ext
fi
@ -2960,7 +2962,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
set x ${MAKE-make}
ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
if { as_var=ac_cv_prog_make_${ac_make}_set; eval "test \"\${$as_var+set}\" = set"; }; then :
if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then :
$as_echo_n "(cached) " >&6
else
cat >conftest.make <<\_ACEOF
@ -2968,7 +2970,7 @@ SHELL = /bin/sh
all:
@echo '@@@%%%=$(MAKE)=@@@%%%'
_ACEOF
# GNU make sometimes prints "make[1]: Entering...", which would confuse us.
# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
case `${MAKE-make} -f conftest.make 2>/dev/null` in
*@@@%%%=?*=@@@%%%*)
eval ac_cv_prog_make_${ac_make}_set=yes;;
@ -3202,7 +3204,7 @@ fi
if test "z$ERLC" = "z" || test "z$ERL" = "z"; then
as_fn_error "erlang not found" "$LINENO" 5
as_fn_error $? "erlang not found" "$LINENO" 5
fi
@ -3260,15 +3262,15 @@ libpath(App) ->
_EOF
if ! $ERLC conftest.erl; then
as_fn_error "could not compile sample program" "$LINENO" 5
as_fn_error $? "could not compile sample program" "$LINENO" 5
fi
if ! $ERL -s conftest -noshell; then
as_fn_error "could not run sample program" "$LINENO" 5
as_fn_error $? "could not run sample program" "$LINENO" 5
fi
if ! test -f conftest.out; then
as_fn_error "erlang program was not properly executed, (conftest.out was not produced)" "$LINENO" 5
as_fn_error $? "erlang program was not properly executed, (conftest.out was not produced)" "$LINENO" 5
fi
# First line
@ -3497,7 +3499,7 @@ else
# Broken: fails on valid input.
continue
fi
rm -f conftest.err conftest.$ac_ext
rm -f conftest.err conftest.i conftest.$ac_ext
# OK, works on sane cases. Now check whether nonexistent headers
# can be detected and how.
@ -3513,11 +3515,11 @@ else
ac_preproc_ok=:
break
fi
rm -f conftest.err conftest.$ac_ext
rm -f conftest.err conftest.i conftest.$ac_ext
done
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
rm -f conftest.err conftest.$ac_ext
rm -f conftest.i conftest.err conftest.$ac_ext
if $ac_preproc_ok; then :
break
fi
@ -3556,7 +3558,7 @@ else
# Broken: fails on valid input.
continue
fi
rm -f conftest.err conftest.$ac_ext
rm -f conftest.err conftest.i conftest.$ac_ext
# OK, works on sane cases. Now check whether nonexistent headers
# can be detected and how.
@ -3572,18 +3574,18 @@ else
ac_preproc_ok=:
break
fi
rm -f conftest.err conftest.$ac_ext
rm -f conftest.err conftest.i conftest.$ac_ext
done
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
rm -f conftest.err conftest.$ac_ext
rm -f conftest.i conftest.err conftest.$ac_ext
if $ac_preproc_ok; then :
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error "C preprocessor \"$CPP\" fails sanity check
See \`config.log' for more details." "$LINENO" 5; }
as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
See \`config.log' for more details" "$LINENO" 5 ; }
fi
ac_ext=c
@ -3644,7 +3646,7 @@ esac
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_GREP"; then
as_fn_error "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_GREP=$GREP
@ -3710,7 +3712,7 @@ esac
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_EGREP"; then
as_fn_error "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_EGREP=$EGREP
@ -3842,8 +3844,7 @@ do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
"
eval as_val=\$$as_ac_Header
if test "x$as_val" = x""yes; then :
if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
_ACEOF
@ -3911,7 +3912,7 @@ else
fi
if test $expat_found = no; then
as_fn_error "Could not find development files of Expat library" "$LINENO" 5
as_fn_error $? "Could not find development files of Expat library" "$LINENO" 5
fi
expat_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
@ -3932,7 +3933,7 @@ fi
done
if test $expat_found = no; then
as_fn_error "Could not find expat.h" "$LINENO" 5
as_fn_error $? "Could not find expat.h" "$LINENO" 5
fi
CFLAGS="$expat_save_CFLAGS"
CPPFLAGS="$expat_save_CPPFLAGS"
@ -4469,7 +4470,7 @@ else
fi
if test $zlib_found = no; then
as_fn_error "Could not find development files of zlib library. Install them or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
as_fn_error $? "Could not find development files of zlib library. Install them or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
fi
zlib_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
@ -4490,7 +4491,7 @@ fi
done
if test $zlib_found = no; then
as_fn_error "Could not find zlib.h. Install it or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
as_fn_error $? "Could not find zlib.h. Install it or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
fi
CFLAGS="$zlib_save_CFLAGS"
CPPFLAGS="$zlib_save_CPPFLAGS"
@ -4581,7 +4582,7 @@ else
fi
if test $pam_found = no; then
as_fn_error "Could not find development files of PAM library. Install them or disable \`pam' with: --disable-pam" "$LINENO" 5
as_fn_error $? "Could not find development files of PAM library. Install them or disable \`pam' with: --disable-pam" "$LINENO" 5
fi
pam_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $PAM_CFLAGS"
@ -4602,7 +4603,7 @@ fi
done
if test $pam_found = no; then
as_fn_error "Could not find security/pam_appl.h. Install it or disable \`pam' with: --disable-pam" "$LINENO" 5
as_fn_error $? "Could not find security/pam_appl.h. Install it or disable \`pam' with: --disable-pam" "$LINENO" 5
fi
CFLAGS="$pam_save_CFLAGS"
CPPFLAGS="$pam_save_CPPFLAGS"
@ -4617,7 +4618,7 @@ if test "${enable_hipe+set}" = set; then :
enableval=$enable_hipe; case "${enableval}" in
yes) hipe=true ;;
no) hipe=false ;;
*) as_fn_error "bad value ${enableval} for --enable-hipe" "$LINENO" 5 ;;
*) as_fn_error $? "bad value ${enableval} for --enable-hipe" "$LINENO" 5 ;;
esac
else
hipe=false
@ -4630,7 +4631,7 @@ if test "${enable_roster_gateway_workaround+set}" = set; then :
enableval=$enable_roster_gateway_workaround; case "${enableval}" in
yes) roster_gateway_workaround=true ;;
no) roster_gateway_workaround=false ;;
*) as_fn_error "bad value ${enableval} for --enable-roster-gateway-workaround" "$LINENO" 5 ;;
*) as_fn_error $? "bad value ${enableval} for --enable-roster-gateway-workaround" "$LINENO" 5 ;;
esac
else
roster_gateway_workaround=false
@ -4638,12 +4639,25 @@ fi
# Check whether --enable-flash_hack was given.
if test "${enable_flash_hack+set}" = set; then :
enableval=$enable_flash_hack; case "${enableval}" in
yes) flash_hack=true ;;
no) flash_hack=false ;;
*) as_fn_error $? "bad value ${enableval} for --enable-flash-hack" "$LINENO" 5 ;;
esac
else
flash_hack=false
fi
# Check whether --enable-mssql was given.
if test "${enable_mssql+set}" = set; then :
enableval=$enable_mssql; case "${enableval}" in
yes) db_type=mssql ;;
no) db_type=generic ;;
*) as_fn_error "bad value ${enableval} for --enable-mssql" "$LINENO" 5 ;;
*) as_fn_error $? "bad value ${enableval} for --enable-mssql" "$LINENO" 5 ;;
esac
else
db_type=generic
@ -4656,7 +4670,7 @@ if test "${enable_transient_supervisors+set}" = set; then :
enableval=$enable_transient_supervisors; case "${enableval}" in
yes) transient_supervisors=true ;;
no) transient_supervisors=false ;;
*) as_fn_error "bad value ${enableval} for --enable-transient_supervisors" "$LINENO" 5 ;;
*) as_fn_error $? "bad value ${enableval} for --enable-transient_supervisors" "$LINENO" 5 ;;
esac
else
transient_supervisors=true
@ -4669,7 +4683,7 @@ if test "${enable_full_xml+set}" = set; then :
enableval=$enable_full_xml; case "${enableval}" in
yes) full_xml=true ;;
no) full_xml=false ;;
*) as_fn_error "bad value ${enableval} for --enable-full-xml" "$LINENO" 5 ;;
*) as_fn_error $? "bad value ${enableval} for --enable-full-xml" "$LINENO" 5 ;;
esac
else
full_xml=false
@ -4682,7 +4696,7 @@ if test "${enable_nif+set}" = set; then :
enableval=$enable_nif; case "${enableval}" in
yes) nif=true ;;
no) nif=false ;;
*) as_fn_error "bad value ${enableval} for --enable-nif" "$LINENO" 5 ;;
*) as_fn_error $? "bad value ${enableval} for --enable-nif" "$LINENO" 5 ;;
esac
else
nif=false
@ -4780,7 +4794,7 @@ done
fi
done
if test x${have_openssl} != xyes; then
as_fn_error "Could not find development files of OpenSSL library. Install them or disable \`tls' with: --disable-tls" "$LINENO" 5
as_fn_error $? "Could not find development files of OpenSSL library. Install them or disable \`tls' with: --disable-tls" "$LINENO" 5
fi
@ -4835,16 +4849,22 @@ fi
ac_aux_dir=
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
for ac_t in install-sh install.sh shtool; do
if test -f "$ac_dir/$ac_t"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/$ac_t -c"
break 2
fi
done
if test -f "$ac_dir/install-sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install-sh -c"
break
elif test -f "$ac_dir/install.sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install.sh -c"
break
elif test -f "$ac_dir/shtool"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/shtool install -c"
break
fi
done
if test -z "$ac_aux_dir"; then
as_fn_error "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
fi
# These three variables are undocumented and unsupported,
@ -4858,7 +4878,7 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
# Make sure we can run config.sub.
$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
as_fn_error "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
$as_echo_n "checking build system type... " >&6; }
@ -4869,16 +4889,16 @@ else
test "x$ac_build_alias" = x &&
ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
test "x$ac_build_alias" = x &&
as_fn_error "cannot guess build type; you must specify one" "$LINENO" 5
as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
as_fn_error "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
$as_echo "$ac_cv_build" >&6; }
case $ac_cv_build in
*-*-*) ;;
*) as_fn_error "invalid value of canonical build" "$LINENO" 5;;
*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5 ;;
esac
build=$ac_cv_build
ac_save_IFS=$IFS; IFS='-'
@ -4903,7 +4923,7 @@ else
ac_cv_host=$ac_cv_build
else
ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
as_fn_error "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
fi
fi
@ -4911,7 +4931,7 @@ fi
$as_echo "$ac_cv_host" >&6; }
case $ac_cv_host in
*-*-*) ;;
*) as_fn_error "invalid value of canonical host" "$LINENO" 5;;
*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5 ;;
esac
host=$ac_cv_host
ac_save_IFS=$IFS; IFS='-'
@ -4936,7 +4956,7 @@ else
ac_cv_target=$ac_cv_host
else
ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` ||
as_fn_error "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5
fi
fi
@ -4944,7 +4964,7 @@ fi
$as_echo "$ac_cv_target" >&6; }
case $ac_cv_target in
*-*-*) ;;
*) as_fn_error "invalid value of canonical target" "$LINENO" 5;;
*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5 ;;
esac
target=$ac_cv_target
ac_save_IFS=$IFS; IFS='-'
@ -5088,8 +5108,8 @@ fi
if test "$cross_compiling" = yes; then :
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error "cannot run test program while cross compiling
See \`config.log' for more details." "$LINENO" 5; }
as_fn_error $? "cannot run test program while cross compiling
See \`config.log' for more details" "$LINENO" 5 ; }
else
cat > conftest.$ac_ext <<_ACEOF
-module(conftest).
@ -5243,6 +5263,7 @@ DEFS=`sed -n "$ac_script" confdefs.h`
ac_libobjs=
ac_ltlibobjs=
U=
for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
# 1. Remove the extension, and $U if already installed.
ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
@ -5404,19 +5425,19 @@ export LANGUAGE
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
# as_fn_error ERROR [LINENO LOG_FD]
# ---------------------------------
# as_fn_error STATUS ERROR [LINENO LOG_FD]
# ----------------------------------------
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
# script with status $?, using 1 if that was 0.
# script with STATUS, using 1 if that was 0.
as_fn_error ()
{
as_status=$?; test $as_status -eq 0 && as_status=1
if test "$3"; then
as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3
as_status=$1; test $as_status -eq 0 && as_status=1
if test "$4"; then
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
fi
$as_echo "$as_me: error: $1" >&2
$as_echo "$as_me: error: $2" >&2
as_fn_exit $as_status
} # as_fn_error
@ -5612,7 +5633,7 @@ $as_echo X"$as_dir" |
test -d "$as_dir" && break
done
test -z "$as_dirs" || eval "mkdir $as_dirs"
} || test -d "$as_dir" || as_fn_error "cannot create directory $as_dir"
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
} # as_fn_mkdir_p
@ -5666,7 +5687,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# values after options handling.
ac_log="
This file was extended by ejabberd $as_me 2.1.x, which was
generated by GNU Autoconf 2.65. Invocation command line was
generated by GNU Autoconf 2.67. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS
@ -5719,10 +5740,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
ejabberd config.status 2.1.x
configured by $0, generated by GNU Autoconf 2.65,
configured by $0, generated by GNU Autoconf 2.67,
with options \\"\$ac_cs_config\\"
Copyright (C) 2009 Free Software Foundation, Inc.
Copyright (C) 2010 Free Software Foundation, Inc.
This config.status script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it."
@ -5737,11 +5758,16 @@ ac_need_defaults=:
while test $# != 0
do
case $1 in
--*=*)
--*=?*)
ac_option=`expr "X$1" : 'X\([^=]*\)='`
ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
ac_shift=:
;;
--*=)
ac_option=`expr "X$1" : 'X\([^=]*\)='`
ac_optarg=
ac_shift=:
;;
*)
ac_option=$1
ac_optarg=$2
@ -5763,6 +5789,7 @@ do
$ac_shift
case $ac_optarg in
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
'') as_fn_error $? "missing file argument" ;;
esac
as_fn_append CONFIG_FILES " '$ac_optarg'"
ac_need_defaults=false;;
@ -5773,7 +5800,7 @@ do
ac_cs_silent=: ;;
# This is an error.
-*) as_fn_error "unrecognized option: \`$1'
-*) as_fn_error $? "unrecognized option: \`$1'
Try \`$0 --help' for more information." ;;
*) as_fn_append ac_config_targets " $1"
@ -5836,7 +5863,7 @@ do
"$make_odbc") CONFIG_FILES="$CONFIG_FILES $make_odbc" ;;
"$make_ejabberd_zlib") CONFIG_FILES="$CONFIG_FILES $make_ejabberd_zlib" ;;
*) as_fn_error "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;;
esac
done
@ -5872,7 +5899,7 @@ $debug ||
{
tmp=./conf$$-$RANDOM
(umask 077 && mkdir "$tmp")
} || as_fn_error "cannot create a temporary directory in ." "$LINENO" 5
} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
# Set up the scripts for CONFIG_FILES section.
# No need to generate them if there are no CONFIG_FILES.
@ -5889,7 +5916,7 @@ if test "x$ac_cr" = x; then
fi
ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
ac_cs_awk_cr='\r'
ac_cs_awk_cr='\\r'
else
ac_cs_awk_cr=$ac_cr
fi
@ -5903,18 +5930,18 @@ _ACEOF
echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
echo "_ACEOF"
} >conf$$subs.sh ||
as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_num=`echo "$ac_subst_vars" | grep -c '$'`
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
ac_delim='%!_!# '
for ac_last_try in false false false false false :; do
. ./conf$$subs.sh ||
as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
if test $ac_delim_n = $ac_delim_num; then
break
elif $ac_last_try; then
as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
else
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
fi
@ -6003,20 +6030,28 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
else
cat
fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
|| as_fn_error "could not setup config files machinery" "$LINENO" 5
|| as_fn_error $? "could not setup config files machinery" "$LINENO" 5
_ACEOF
# VPATH may cause trouble with some makes, so we remove $(srcdir),
# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
# trailing colons and then remove the whole line if VPATH becomes empty
# (actually we leave an empty line to preserve line numbers).
if test "x$srcdir" = x.; then
ac_vpsub='/^[ ]*VPATH[ ]*=/{
s/:*\$(srcdir):*/:/
s/:*\${srcdir}:*/:/
s/:*@srcdir@:*/:/
s/^\([^=]*=[ ]*\):*/\1/
ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
h
s///
s/^/:/
s/[ ]*$/:/
s/:\$(srcdir):/:/g
s/:\${srcdir}:/:/g
s/:@srcdir@:/:/g
s/^:*//
s/:*$//
x
s/\(=[ ]*\).*/\1/
G
s/\n//
s/^[^=]*=[ ]*$//
}'
fi
@ -6034,7 +6069,7 @@ do
esac
case $ac_mode$ac_tag in
:[FHL]*:*);;
:L* | :C*:*) as_fn_error "invalid tag \`$ac_tag'" "$LINENO" 5;;
:L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;;
:[FH]-) ac_tag=-:-;;
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
esac
@ -6062,7 +6097,7 @@ do
[\\/$]*) false;;
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
esac ||
as_fn_error "cannot find input file: \`$ac_f'" "$LINENO" 5;;
as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;;
esac
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
as_fn_append ac_file_inputs " '$ac_f'"
@ -6089,7 +6124,7 @@ $as_echo "$as_me: creating $ac_file" >&6;}
case $ac_tag in
*:-:* | *:-) cat >"$tmp/stdin" \
|| as_fn_error "could not create $ac_file" "$LINENO" 5 ;;
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
esac
;;
esac
@ -6215,22 +6250,22 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
$ac_datarootdir_hack
"
eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
|| as_fn_error "could not create $ac_file" "$LINENO" 5
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
{ ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
{ ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined." >&5
which seems to be undefined. Please make sure it is defined" >&5
$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined." >&2;}
which seems to be undefined. Please make sure it is defined" >&2;}
rm -f "$tmp/stdin"
case $ac_file in
-) cat "$tmp/out" && rm -f "$tmp/out";;
*) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
esac \
|| as_fn_error "could not create $ac_file" "$LINENO" 5
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
;;
@ -6245,7 +6280,7 @@ _ACEOF
ac_clean_files=$ac_clean_files_save
test $ac_write_fail = 0 ||
as_fn_error "write failure creating $CONFIG_STATUS" "$LINENO" 5
as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
# configure is writing to config.log, and then calls config.status.
@ -6266,7 +6301,7 @@ if test "$no_create" != yes; then
exec 5>>config.log
# Use ||, not &&, to avoid exiting from the if with $? = 1, which
# would make configure fail if this is the last instruction.
$ac_cs_success || as_fn_exit $?
$ac_cs_success || as_fn_exit 1
fi
if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5

View File

@ -67,6 +67,15 @@ AC_ARG_ENABLE(roster_gateway_workaround,
esac],[roster_gateway_workaround=false])
AC_SUBST(roster_gateway_workaround)
AC_ARG_ENABLE(flash_hack,
[AC_HELP_STRING([--enable-flash-hack], [support Adobe Flash client XML (default: no)])],
[case "${enableval}" in
yes) flash_hack=true ;;
no) flash_hack=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-flash-hack) ;;
esac],[flash_hack=false])
AC_SUBST(flash_hack)
AC_ARG_ENABLE(mssql,
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
[case "${enableval}" in

View File

@ -2,7 +2,7 @@
{application, ejabberd,
[{description, "ejabberd"},
{vsn, "2.1.x"},
{vsn, "2.2.x"},
{modules, [acl,
adhoc,
configure,

View File

@ -31,6 +31,13 @@
-define(CONFIG_PATH, "ejabberd.cfg").
-define(LOG_PATH, "ejabberd.log").
-ifdef(ENABLE_FLASH_HACK).
-define(FLASH_HACK, true).
-else.
-define(FLASH_HACK, false).
-endif.
-define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/").
-define(S2STIMEOUT, 600000).

View File

@ -68,6 +68,8 @@ start(normal, _Args) ->
%ejabberd_debug:fprof_start(),
maybe_add_nameservers(),
start_modules(),
ejabberd_cluster:announce(),
ejabberd_node_groups:start(),
ejabberd_listener:start_listeners(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;

View File

@ -34,6 +34,7 @@
anonymous_user_exist/2,
allow_multiple_connections/1,
register_connection/3,
unregister_migrated_connection/3,
unregister_connection/3
]).
@ -61,12 +62,16 @@
%% Register to login / logout events
start(Host) ->
%% TODO: Check cluster mode
update_tables(),
mnesia:create_table(anonymous, [{ram_copies, [node()]},
{type, bag},
{type, bag}, {local_content, true},
{attributes, record_info(fields, anonymous)}]),
mnesia:add_table_copy(anonymous, node(), ram_copies),
%% The hooks are needed to add / remove users from the anonymous tables
ejabberd_hooks:add(sm_register_connection_hook, Host,
?MODULE, register_connection, 100),
ejabberd_hooks:add(sm_remove_migrated_connection_hook, Host,
?MODULE, unregister_migrated_connection, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
?MODULE, unregister_connection, 100),
ok.
@ -123,11 +128,18 @@ anonymous_user_exist(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({anonymous, US}) of
[] ->
false;
Ss = case ejabberd_cluster:get_node(US) of
Node when Node == node() ->
catch mnesia:dirty_read({anonymous, US});
Node ->
catch rpc:call(Node, mnesia, dirty_read,
[{anonymous, US}], 5000)
end,
case Ss of
[_H|_T] ->
true
true;
_ ->
false
end.
%% Remove connection from Mnesia tables
@ -136,7 +148,7 @@ remove_connection(SID, LUser, LServer) ->
F = fun() ->
mnesia:delete_object({anonymous, US, SID})
end,
mnesia:transaction(F).
mnesia:async_dirty(F).
%% Register connection
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
@ -144,7 +156,7 @@ register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
case AuthModule == ?MODULE of
true ->
US = {LUser, LServer},
mnesia:sync_dirty(
mnesia:async_dirty(
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
end);
false ->
@ -157,6 +169,10 @@ unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
LUser, LServer),
remove_connection(SID, LUser, LServer).
%% Remove an anonymous user from the anonymous users table
unregister_migrated_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
remove_connection(SID, LUser, LServer).
%% Launch the hook to purge user data only for anonymous users
purge_hook(false, _LUser, _LServer) ->
ok;
@ -246,3 +262,11 @@ remove_user(_User, _Server, _Password) ->
plain_password_required() ->
false.
update_tables() ->
case catch mnesia:table_info(anonymous, local_content) of
false ->
mnesia:delete_table(anonymous);
_ ->
ok
end.

View File

@ -35,13 +35,16 @@
%% External exports
-export([start/2,
stop/1,
start_link/2,
start_link/3,
send_text/2,
send_element/2,
socket_type/0,
get_presence/1,
get_subscribed/1]).
%% API:
-export([add_rosteritem/3, del_rosteritem/2]).
%% gen_fsm callbacks
-export([init/1,
wait_for_stream/2,
@ -56,69 +59,14 @@
code_change/4,
handle_info/3,
terminate/3,
print_state/1
]).
print_state/1,
migrate/3
]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_privacy.hrl").
-define(SETS, gb_sets).
-define(DICT, dict).
%% pres_a contains all the presence available send (either through roster mechanism or directed).
%% Directed presence unavailable remove user from pres_a.
-record(state, {socket,
sockmod,
socket_monitor,
xml_socket,
streamid,
sasl_state,
access,
shaper,
zlib = false,
tls = false,
tls_required = false,
tls_enabled = false,
tls_options = [],
authenticated = false,
jid,
user = "", server = ?MYNAME, resource = "",
sid,
pres_t = ?SETS:new(),
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_last, pres_pri,
pres_timestamp,
pres_invis = false,
privacy_list = #userlist{},
conn = unknown,
auth_module = unknown,
ip,
lang,
reception = true,
standby = false,
queue = queue:new(),
queue_len = 0,
pres_queue = gb_trees:empty(),
keepalive_timer,
keepalive_timeout,
oor_timeout,
oor_status = "",
oor_show = "",
oor_notification,
oor_send_body = all,
oor_send_groupchat = false,
oor_send_from = jid,
oor_appid = "",
oor_unread = 0,
oor_unread_users = ?SETS:new(),
oor_offline = false,
ack_enabled = false,
ack_counter = 0,
ack_queue = queue:new(),
ack_timer}).
-include("ejabberd_c2s.hrl").
%-define(DBGFSM, true).
@ -130,11 +78,12 @@
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, [SockData, Opts],
fsm_limit_opts(Opts) ++ ?FSMOPTS)).
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s,
[SockData, Opts, FSMLimitOpts],
FSMLimitOpts ++ ?FSMOPTS)).
-else.
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
[SockData, Opts])).
[SockData, Opts, FSMLimitOpts])).
-endif.
%% This is the timeout to apply between event when starting a new
@ -149,6 +98,13 @@
"id='~s' from='~s'~s~s>"
).
-define(FLASH_STREAM_HEADER,
"<?xml version='1.0'?>"
"<flash:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"id='~s' from='~s'~s~s>"
).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE).
@ -171,12 +127,17 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(StateName, #state{fsm_limit_opts = Opts} = State) ->
start(StateName, State, Opts);
start(SockData, Opts) ->
start(SockData, Opts, fsm_limit_opts(Opts)).
start(SockData, Opts, FSMLimitOpts) ->
?SUPERVISOR_START.
start_link(SockData, Opts) ->
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts],
fsm_limit_opts(Opts) ++ ?FSMOPTS).
start_link(SockData, Opts, FSMLimitOpts) ->
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts, FSMLimitOpts],
FSMLimitOpts ++ ?FSMOPTS).
socket_type() ->
xml_stream.
@ -185,9 +146,18 @@ socket_type() ->
get_presence(FsmRef) ->
?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000).
add_rosteritem(FsmRef, IJID, ISubscription) ->
?GEN_FSM:send_all_state_event(FsmRef, {add_rosteritem, IJID, ISubscription}).
del_rosteritem(FsmRef, IJID) ->
?GEN_FSM:send_all_state_event(FsmRef, {del_rosteritem, IJID}).
stop(FsmRef) ->
?GEN_FSM:send_event(FsmRef, closed).
migrate(FsmRef, Node, After) ->
?GEN_FSM:send_all_state_event(FsmRef, {migrate, Node, After}).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@ -199,7 +169,7 @@ stop(FsmRef) ->
%% ignore |
%% {stop, StopReason}
%%----------------------------------------------------------------------
init([{SockMod, Socket}, Opts]) ->
init([{SockMod, Socket}, Opts, FSMLimitOpts]) ->
Access = case lists:keysearch(access, 1, Opts) of
{value, {_, A}} -> A;
_ -> all
@ -223,7 +193,12 @@ init([{SockMod, Socket}, Opts]) ->
(_) -> false
end, Opts),
TLSOpts = [verify_none | TLSOpts1],
IP = peerip(SockMod, Socket),
IP = case lists:keysearch(frontend_ip, 1, Opts) of
{value, {_, IP1}} ->
IP1;
_ ->
peerip(SockMod, Socket)
end,
%% Check if IP is blacklisted:
case is_ip_blacklisted(IP) of
true ->
@ -239,20 +214,47 @@ init([{SockMod, Socket}, Opts]) ->
Socket
end,
SocketMonitor = SockMod:monitor(Socket1),
{ok, wait_for_stream, #state{socket = Socket1,
sockmod = SockMod,
socket_monitor = SocketMonitor,
xml_socket = XMLSocket,
zlib = Zlib,
tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled,
tls_options = TLSOpts,
streamid = new_id(),
access = Access,
shaper = Shaper,
ip = IP},
?C2S_OPEN_TIMEOUT}
StateData = #state{socket = Socket1,
sockmod = SockMod,
socket_monitor = SocketMonitor,
xml_socket = XMLSocket,
zlib = Zlib,
tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled,
tls_options = TLSOpts,
streamid = new_id(),
access = Access,
shaper = Shaper,
ip = IP,
fsm_limit_opts = FSMLimitOpts},
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}
end;
init([StateName, StateData, _FSMLimitOpts]) ->
MRef = (StateData#state.sockmod):monitor(StateData#state.socket),
if StateName == session_established ->
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, StateData#state.auth_module}],
{Time, _} = StateData#state.sid,
SID = {Time, self()},
Priority = case StateData#state.pres_last of
undefined ->
undefined;
El ->
get_priority_from_presence(El)
end,
ejabberd_sm:open_session(
SID,
StateData#state.user,
StateData#state.server,
StateData#state.resource,
Priority,
Info),
NewStateData = StateData#state{sid = SID, socket_monitor = MRef},
{ok, StateName, NewStateData};
true ->
{ok, StateName, StateData#state{socket_monitor = MRef}}
end.
%% Return list of all available resources of contacts,
@ -266,15 +268,25 @@ get_subscribed(FsmRef) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
DefaultLang = case ?MYLANG of
undefined ->
"en";
DL ->
DL
end,
case xml:get_attr_s("xmlns:stream", Attrs) of
?NS_STREAM ->
case {xml:get_attr_s("xmlns:stream", Attrs),
xml:get_attr_s("xmlns:flash", Attrs),
?FLASH_HACK,
StateData#state.flash_connection} of
{_, ?NS_FLASH_STREAM, true, false} ->
%% Flash client connecting - attention!
%% Some of them don't provide an xmlns:stream attribute -
%% compensate for that.
wait_for_stream({xmlstreamstart, Name, [{"xmlns:stream", ?NS_STREAM}|Attrs]},
StateData#state{flash_connection = true});
{?NS_STREAM, _, _, _} ->
Server = jlib:nameprep(xml:get_attr_s("to", Attrs)),
case lists:member(Server, ?MYHOSTS) of
true ->
@ -432,11 +444,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
send_trailer(StateData),
{stop, normal, StateData}
end;
_ ->
send_header(StateData, ?MYNAME, "", DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
send_trailer(StateData),
{stop, normal, StateData}
_ ->
case Name of
"policy-file-request" ->
send_text(StateData, flash_policy_string()),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, "", DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end
end;
wait_for_stream(timeout, StateData) ->
@ -515,13 +533,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
jlib:jid_to_string(JID), AuthModule]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, AuthModule}],
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
%% {auth_module, AuthModule}],
Res1 = jlib:make_result_iq_reply(El),
Res = setelement(4, Res1, []),
send_element(StateData, Res),
ejabberd_sm:open_session(
SID, U, StateData#state.server, R, Info),
%% ejabberd_sm:open_session(
%% SID, U, StateData#state.server, R, Info),
change_shaper(StateData, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
@ -537,8 +555,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
privacy_get_user_list, StateData#state.server,
#userlist{},
[U, StateData#state.server]),
NewStateData =
StateData#state{
NewStateData = StateData#state{
user = U,
resource = R,
jid = JID,
@ -548,8 +565,11 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList},
fsm_next_state_pack(session_established,
NewStateData);
DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook,
NewStateData#state.server,
false,
[self(), NewStateData]),
maybe_migrate(session_established, NewStateData#state{debug=DebugFlag});
_ ->
?INFO_MSG(
"(~w) Failed legacy authentication for ~s",
@ -641,8 +661,8 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
Mech,
ClientIn) of
{ok, Props} ->
(StateData#state.sockmod):reset_stream(
StateData#state.socket),
catch (StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData,
{xmlelement, "success",
[{"xmlns", ?NS_SASL}], []}),
@ -794,8 +814,8 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
case cyrsasl:server_step(StateData#state.sasl_state,
ClientIn) of
{ok, Props} ->
(StateData#state.sockmod):reset_stream(
StateData#state.socket),
catch (StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData,
{xmlelement, "success",
[{"xmlns", ?NS_SASL}], []}),
@ -919,7 +939,7 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
case jlib:iq_query_info(El) of
#iq{type = set, xmlns = ?NS_SESSION} ->
U = StateData#state.user,
R = StateData#state.resource,
%%R = StateData#state.resource,
JID = StateData#state.jid,
case acl:match_rule(StateData#state.server,
StateData#state.access, JID) of
@ -945,10 +965,10 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
[U, StateData#state.server]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:open_session(
SID, U, StateData#state.server, R, Info),
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
%% {auth_module, StateData#state.auth_module}],
%% ejabberd_sm:open_session(
%% SID, U, StateData#state.server, R, Info),
NewStateData =
StateData#state{
sid = SID,
@ -956,8 +976,11 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList},
fsm_next_state_pack(session_established,
NewStateData);
DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook,
NewStateData#state.server,
false,
[self(), NewStateData]),
maybe_migrate(session_established, NewStateData#state{debug=DebugFlag});
_ ->
ejabberd_hooks:run(forbidden_session_hook,
StateData#state.server, [JID]),
@ -1084,7 +1107,7 @@ session_established2(El, StateData) ->
ejabberd_hooks:run(
user_send_packet,
Server,
[FromJID, ToJID, PresenceEl]),
[StateData#state.debug, FromJID, ToJID, PresenceEl]),
case ToJID of
#jid{user = User,
server = Server,
@ -1100,6 +1123,10 @@ session_established2(El, StateData) ->
"iq" ->
case jlib:iq_query_info(NewEl) of
#iq{xmlns = ?NS_PRIVACY} = IQ ->
ejabberd_hooks:run(
user_send_packet,
Server,
[StateData#state.debug, FromJID, ToJID, NewEl]),
process_privacy_iq(
FromJID, ToJID, IQ, StateData);
#iq{xmlns = ?NS_P1_PUSH} = IQ ->
@ -1108,7 +1135,7 @@ session_established2(El, StateData) ->
ejabberd_hooks:run(
user_send_packet,
Server,
[FromJID, ToJID, NewEl]),
[StateData#state.debug, FromJID, ToJID, NewEl]),
ejabberd_router:route(
FromJID, ToJID, NewEl),
StateData
@ -1116,7 +1143,7 @@ session_established2(El, StateData) ->
"message" ->
ejabberd_hooks:run(user_send_packet,
Server,
[FromJID, ToJID, NewEl]),
[StateData#state.debug, FromJID, ToJID, NewEl]),
check_privacy_route(FromJID, StateData, FromJID,
ToJID, NewEl),
StateData;
@ -1154,6 +1181,17 @@ session_established2(El, StateData) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event({migrate, Node, After}, StateName, StateData) when Node /= node() ->
fsm_migrate(StateName, StateData, Node, After * 2);
handle_event({add_rosteritem, IJID, ISubscription}, StateName, StateData) ->
NewStateData = roster_change(IJID, ISubscription, StateData),
fsm_next_state(StateName, NewStateData);
handle_event({del_rosteritem, IJID}, StateName, StateData) ->
NewStateData = roster_change(IJID, none, StateData),
fsm_next_state(StateName, NewStateData);
handle_event({xmlstreamcdata, _}, StateName, StateData) ->
?DEBUG("cdata ping", []),
NSD1 = change_reception(StateData, true),
@ -1458,7 +1496,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
end,
ejabberd_hooks:run(user_receive_packet,
StateData#state.server,
[StateData#state.jid, From, To, FixedPacket]),
[StateData#state.debug, StateData#state.jid, From, To, FixedPacket]),
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
fsm_next_state(StateName, NewState2);
true ->
@ -1560,12 +1598,31 @@ print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
pres_a = {pres_a, ?SETS:size(A)},
pres_i = {pres_i, ?SETS:size(I)}
}.
%%----------------------------------------------------------------------
%% Func: terminate/3
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate({migrated, ClonePid}, StateName, StateData) ->
ejabberd_hooks:run(c2s_debug_stop_hook,
StateData#state.server,
[self(), StateData]),
if StateName == session_established ->
?INFO_MSG("(~w) Migrating ~s to ~p on node ~p",
[StateData#state.socket,
jlib:jid_to_string(StateData#state.jid),
ClonePid, node(ClonePid)]),
ejabberd_sm:close_migrated_session(StateData#state.sid,
StateData#state.user,
StateData#state.server,
StateData#state.resource);
true ->
ok
end,
(StateData#state.sockmod):change_controller(
StateData#state.socket, ClonePid),
ok;
terminate(_Reason, StateName, StateData) ->
case StateName of
session_established ->
@ -1675,7 +1732,14 @@ change_shaper(StateData, JID) ->
send_text(StateData, Text) ->
?DEBUG("Send XML on stream = ~p", [Text]),
(StateData#state.sockmod):send(StateData#state.socket, Text).
Text1 =
if ?FLASH_HACK and StateData#state.flash_connection ->
%% send a null byte after each stanza to Flash clients
[Text, 0];
true ->
Text
end,
(StateData#state.sockmod):send(StateData#state.socket, Text1).
send_element(StateData, El) when StateData#state.xml_socket ->
ejabberd_hooks:run(feature_inspect_packet,
@ -1693,6 +1757,15 @@ send_element(StateData, El) ->
StateData#state.pres_last, El]),
send_text(StateData, xml:element_to_binary(El)).
send_header(StateData,Server, Version, Lang)
when StateData#state.flash_connection ->
Header = io_lib:format(?FLASH_STREAM_HEADER,
[StateData#state.streamid,
Server,
Version,
Lang]),
send_text(StateData, Header);
send_header(StateData, Server, Version, Lang)
when StateData#state.xml_socket ->
VersionAttr =
@ -1781,16 +1854,22 @@ get_auth_tags([], U, P, D, R) ->
get_conn_type(StateData) ->
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
gen_tcp -> c2s;
tls -> c2s_tls;
ejabberd_zlib ->
case ejabberd_zlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
tls -> c2s_compressed_tls
end;
ejabberd_http_poll -> http_poll;
ejabberd_http_bind -> http_bind;
_ -> unknown
gen_tcp -> c2s;
tls -> c2s_tls;
ejabberd_zlib ->
if is_pid(StateData#state.socket) ->
unknown;
true ->
case ejabberd_zlib:get_sockmod(
(StateData#state.socket)#socket_state.socket) of
gen_tcp -> c2s_compressed;
tls -> c2s_compressed_tls
end
end;
ejabberd_http_poll -> http_poll;
ejabberd_http_ws -> http_ws;
ejabberd_http_bind -> http_bind;
_ -> unknown
end.
process_presence_probe(From, To, StateData) ->
@ -2197,6 +2276,7 @@ roster_change(IJID, ISubscription, StateData) ->
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
From = StateData#state.jid,
To = jlib:make_jid(IJID),
% To = IJID,
Cond1 = (not StateData#state.pres_invis) and IsFrom
and (not OldIsFrom),
Cond2 = (not IsFrom) and OldIsFrom
@ -2353,7 +2433,7 @@ resend_offline_messages(#state{user = User,
send_element(StateData, FixedPacket),
ejabberd_hooks:run(user_receive_packet,
StateData#state.server,
[StateData#state.jid,
[StateData#state.debug, StateData#state.jid,
From, To, FixedPacket]);
true ->
ok
@ -2426,16 +2506,28 @@ peerip(SockMod, Socket) ->
_ -> undefined
end.
%% fsm_next_state_pack: Pack the StateData structure to improve
%% sharing.
fsm_next_state_pack(StateName, StateData) ->
fsm_next_state_gc(StateName, pack(StateData)).
%% fsm_next_state_gc: Garbage collect the process heap to make use of
%% the newly packed StateData structure.
fsm_next_state_gc(StateName, PackedStateData) ->
erlang:garbage_collect(),
fsm_next_state(StateName, PackedStateData).
maybe_migrate(StateName, StateData) ->
PackedStateData = pack(StateData),
case ejabberd_cluster:get_node({StateData#state.user,
StateData#state.server}) of
Node when Node == node() ->
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, StateData#state.auth_module}],
#state{user = U, server = S, resource = R, sid = SID} = StateData,
ejabberd_sm:open_session(SID, U, S, R, Info),
erlang:garbage_collect(),
case ejabberd_cluster:get_node_new({U, S}) of
Node ->
ok;
NewNode ->
After = ejabberd_cluster:rehash_timeout(),
migrate(self(), NewNode, After)
end,
fsm_next_state(StateName, PackedStateData);
Node ->
fsm_migrate(StateName, PackedStateData, Node, 0)
end.
%% fsm_next_state: Generate the next_state FSM tuple with different
%% timeout, depending on the future state
@ -2444,6 +2536,10 @@ fsm_next_state(session_established, StateData) ->
fsm_next_state(StateName, StateData) ->
{next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
fsm_migrate(StateName, StateData, Node, Timeout) ->
{migrate, StateData,
{Node, ?MODULE, start, [StateName, StateData]}, Timeout}.
%% fsm_reply: Generate the reply FSM tuple with different timeout,
%% depending on the future state
fsm_reply(Reply, session_established, StateData) ->
@ -3180,3 +3276,28 @@ pack_string(String, Pack) ->
none ->
{String, gb_trees:insert(String, String, Pack)}
end.
%% @spec () -> string()
%% @doc Build the content of a Flash policy file.
%% It specifies as domain "*".
%% It specifies as to-ports the ports that serve ejabberd_c2s.
flash_policy_string() ->
Listen = ejabberd_config:get_local_option(listen),
ClientPortsDeep = ["," ++ integer_to_list(Port)
|| {{Port,_,_}, ejabberd_c2s, _Opts} <- Listen],
%% NOTE: The function string:join/2 was introduced in Erlang/OTP R12B-0
%% so it can't be used yet in ejabberd.
ToPortsString = case lists:flatten(ClientPortsDeep) of
[$, | Tail] -> Tail;
_ -> []
end,
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE cross-domain-policy SYSTEM "
"\"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
"<cross-domain-policy>\n"
" <allow-access-from domain=\"*\" to-ports=\""
++ ToPortsString ++
"\"/>\n"
"</cross-domain-policy>\n\0".

85
src/ejabberd_c2s.hrl Normal file
View File

@ -0,0 +1,85 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-ifndef(mod_privacy_hrl).
-include("mod_privacy.hrl").
-endif.
-define(SETS, gb_sets).
-define(DICT, dict).
%% pres_a contains all the presence available send (either through roster mechanism or directed).
%% Directed presence unavailable remove user from pres_a.
-record(state, {socket,
sockmod,
socket_monitor,
xml_socket,
streamid,
sasl_state,
access,
shaper,
zlib = false,
tls = false,
tls_required = false,
tls_enabled = false,
tls_options = [],
authenticated = false,
jid,
user = "", server = ?MYNAME, resource = "",
sid,
pres_t = ?SETS:new(),
pres_f = ?SETS:new(),
pres_a = ?SETS:new(),
pres_i = ?SETS:new(),
pres_last, pres_pri,
pres_timestamp,
pres_invis = false,
privacy_list = #userlist{},
conn = unknown,
auth_module = unknown,
ip,
fsm_limit_opts,
lang,
debug=false,
flash_connection = false,
reception = true,
standby = false,
queue = queue:new(),
queue_len = 0,
pres_queue = gb_trees:empty(),
keepalive_timer,
keepalive_timeout,
oor_timeout,
oor_status = "",
oor_show = "",
oor_notification,
oor_send_body = all,
oor_send_groupchat = false,
oor_send_from = jid,
oor_appid = "",
oor_unread = 0,
oor_unread_users = ?SETS:new(),
oor_offline = false,
ack_enabled = false,
ack_counter = 0,
ack_queue = queue:new(),
ack_timer}).

View File

@ -35,7 +35,7 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
-export([create_captcha/5, build_captcha_html/2, check_captcha/2,
process_reply/1, process/2, is_feature_available/0]).
-include("jlib.hrl").
@ -48,19 +48,11 @@
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
-define(CAPTCHA_LIFETIME, 120000). % two minutes
-define(RPC_TIMEOUT, 5000).
-record(state, {}).
-record(captcha, {id, pid, key, tref, args}).
-define(T(S),
case catch mnesia:transaction(fun() -> S end) of
{atomic, Res} ->
Res;
{_, Reason} ->
?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
{error, Reason}
end).
%%====================================================================
%% API
%%====================================================================
@ -71,11 +63,12 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
create_captcha(Id, SID, From, To, Lang, Args)
when is_list(Id), is_list(Lang), is_list(SID),
create_captcha(SID, From, To, Lang, Args)
when is_list(Lang), is_list(SID),
is_record(From, jid), is_record(To, jid) ->
case create_image() of
{ok, Type, Key, Image} ->
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
B64Image = jlib:encode_base64(binary_to_list(Image)),
JID = jlib:jid_to_string(From),
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
@ -101,13 +94,9 @@ create_captcha(Id, SID, From, To, Lang, Args)
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
tref=Tref, args=Args})) of
ok ->
{ok, [Body, OOB, Captcha, Data]};
_Err ->
error
end;
ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
tref=Tref, args=Args}),
{ok, Id, [Body, OOB, Captcha, Data]};
_Err ->
error
end.
@ -119,8 +108,8 @@ create_captcha(Id, SID, From, To, Lang, Args)
%% IdEl = xmlelement()
%% KeyEl = xmlelement()
build_captcha_html(Id, Lang) ->
case mnesia:dirty_read(captcha, Id) of
[#captcha{}] ->
case lookup_captcha(Id) of
{ok, _} ->
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
IdEl = {xmlelement, "input", [{"type", "hidden"},
@ -150,21 +139,25 @@ build_captcha_html(Id, Lang) ->
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
check_captcha(Id, ProvidedKey) ->
?T(case mnesia:read(captcha, Id, write) of
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
mnesia:delete({captcha, Id}),
erlang:cancel_timer(Tref),
if StoredKey == ProvidedKey ->
Pid ! {captcha_succeed, Args},
captcha_valid;
true ->
Pid ! {captcha_failed, Args},
captcha_non_valid
end;
_ ->
captcha_not_found
end).
case string:tokens(Id, "-") of
[_, NodeID] ->
case ejabberd_cluster:get_node_by_id(NodeID) of
Node when Node == node() ->
do_check_captcha(Id, ProvidedKey);
Node ->
case catch rpc:call(Node, ?MODULE, check_captcha,
[Id, ProvidedKey], ?RPC_TIMEOUT) of
{'EXIT', _} ->
captcha_not_found;
{badrpc, _} ->
captcha_not_found;
Res ->
Res
end
end;
_ ->
captcha_not_found
end.
process_reply({xmlelement, "captcha", _, _} = El) ->
case xml:get_subtag(El, "x") of
@ -175,20 +168,14 @@ process_reply({xmlelement, "captcha", _, _} = El) ->
case {proplists:get_value("challenge", Fields),
proplists:get_value("ocr", Fields)} of
{[Id|_], [OCR|_]} ->
?T(case mnesia:read(captcha, Id, write) of
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
mnesia:delete({captcha, Id}),
erlang:cancel_timer(Tref),
if OCR == Key ->
Pid ! {captcha_succeed, Args},
ok;
true ->
Pid ! {captcha_failed, Args},
{error, bad_match}
end;
_ ->
{error, not_found}
end);
case check_captcha(Id, OCR) of
captcha_valid ->
ok;
captcha_non_valid ->
{error, bad_match};
captcha_not_found ->
{error, not_found}
end;
_ ->
{error, malformed}
end
@ -209,8 +196,8 @@ process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
end;
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
case mnesia:dirty_read(captcha, Id) of
[#captcha{key=Key}] ->
case lookup_captcha(Id) of
{ok, #captcha{key=Key}} ->
case create_image(Key) of
{ok, Type, _, Img} ->
{200,
@ -249,10 +236,8 @@ process(_Handlers, _Request) ->
%% gen_server callbacks
%%====================================================================
init([]) ->
mnesia:create_table(captcha,
[{ram_copies, [node()]},
{attributes, record_info(fields, captcha)}]),
mnesia:add_table_copy(captcha, node(), ram_copies),
mnesia:delete_table(captcha),
ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
check_captcha_setup(),
{ok, #state{}}.
@ -264,13 +249,13 @@ handle_cast(_Msg, State) ->
handle_info({remove_id, Id}, State) ->
?DEBUG("captcha ~p timed out", [Id]),
_ = ?T(case mnesia:read(captcha, Id, write) of
[#captcha{args=Args, pid=Pid}] ->
Pid ! {captcha_failed, Args},
mnesia:delete({captcha, Id});
_ ->
ok
end),
case ets:lookup(captcha, Id) of
[#captcha{args=Args, pid=Pid}] ->
Pid ! {captcha_failed, Args},
ets:delete(captcha, Id);
_ ->
ok
end,
{noreply, State};
handle_info(_Info, State) ->
@ -414,3 +399,43 @@ check_captcha_setup() ->
false ->
ok
end.
lookup_captcha(Id) ->
case string:tokens(Id, "-") of
[_, NodeID] ->
case ejabberd_cluster:get_node_by_id(NodeID) of
Node when Node == node() ->
case ets:lookup(captcha, Id) of
[C] ->
{ok, C};
_ ->
{error, enoent}
end;
Node ->
case catch rpc:call(Node, ets, lookup,
[captcha, Id], ?RPC_TIMEOUT) of
[C] ->
{ok, C};
_ ->
{error, enoent}
end
end;
_ ->
{error, enoent}
end.
do_check_captcha(Id, ProvidedKey) ->
case ets:lookup(captcha, Id) of
[#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
ets:delete(captcha, Id),
erlang:cancel_timer(Tref),
if ValidKey == ProvidedKey ->
Pid ! {captcha_succeed, Args},
captcha_valid;
true ->
Pid ! {captcha_failed, Args},
captcha_non_valid
end;
_ ->
captcha_not_found
end.

177
src/ejabberd_cluster.erl Normal file
View File

@ -0,0 +1,177 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_cluster.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Description :
%%%
%%% Created : 2 Apr 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%-------------------------------------------------------------------
-module(ejabberd_cluster).
-behaviour(gen_server).
%% API
-export([start_link/0, get_node/1, get_node_new/1, announce/0,
node_id/0, get_node_by_id/1, get_nodes/0, rehash_timeout/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-define(HASHTBL, nodes_hash).
-define(HASHTBL_NEW, nodes_hash_new).
-define(POINTS, 64).
-define(REHASH_TIMEOUT, 30000).
-record(state, {}).
%%====================================================================
%% API
%%====================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
get_node(Key) ->
Hash = erlang:phash2(Key),
get_node_by_hash(?HASHTBL, Hash).
get_node_new(Key) ->
Hash = erlang:phash2(Key),
get_node_by_hash(?HASHTBL_NEW, Hash).
get_nodes() ->
%% TODO
mnesia:system_info(running_db_nodes).
announce() ->
gen_server:call(?MODULE, announce, infinity).
node_id() ->
integer_to_list(erlang:phash2(node())).
rehash_timeout() ->
?REHASH_TIMEOUT.
get_node_by_id(NodeID) when is_list(NodeID) ->
case catch list_to_existing_atom(NodeID) of
{'EXIT', _} ->
node();
Res ->
get_node_by_id(Res)
end;
get_node_by_id(NodeID) ->
case global:whereis_name(NodeID) of
Pid when is_pid(Pid) ->
node(Pid);
_ ->
node()
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
net_kernel:monitor_nodes(true, [{node_type, visible}]),
ets:new(?HASHTBL, [named_table, public, ordered_set]),
ets:new(?HASHTBL_NEW, [named_table, public, ordered_set]),
register_node(),
AllNodes = mnesia:system_info(running_db_nodes),
OtherNodes = case AllNodes of
[_] ->
AllNodes;
_ ->
AllNodes -- [node()]
end,
append_nodes(?HASHTBL, OtherNodes),
append_nodes(?HASHTBL_NEW, AllNodes),
{ok, #state{}}.
handle_call(announce, _From, State) ->
case mnesia:system_info(running_db_nodes) of
[_MyNode] ->
ok;
Nodes ->
OtherNodes = Nodes -- [node()],
lists:foreach(
fun(Node) ->
{?MODULE, Node} ! {node_ready, node()}
end, OtherNodes),
?INFO_MSG("waiting for migration from nodes: ~w",
[OtherNodes]),
timer:sleep(?REHASH_TIMEOUT),
append_node(?HASHTBL, node())
end,
{reply, ok, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({node_ready, Node}, State) ->
?INFO_MSG("node ~p is ready, starting migration", [Node]),
append_node(?HASHTBL_NEW, Node),
ejabberd_hooks:run(node_hash_update, [?REHASH_TIMEOUT]),
timer:sleep(?REHASH_TIMEOUT),
?INFO_MSG("adding node ~p to hash", [Node]),
append_node(?HASHTBL, Node),
{noreply, State};
handle_info({nodedown, Node, _}, State) ->
?INFO_MSG("node ~p goes down", [Node]),
delete_node(?HASHTBL, Node),
delete_node(?HASHTBL_NEW, Node),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
append_nodes(Tab, Nodes) ->
lists:foreach(
fun(Node) ->
append_node(Tab, Node)
end, Nodes).
append_node(Tab, Node) ->
lists:foreach(
fun(I) ->
Hash = erlang:phash2({I, Node}),
ets:insert(Tab, {Hash, Node})
end, lists:seq(1, ?POINTS)).
delete_node(Tab, Node) ->
lists:foreach(
fun(I) ->
Hash = erlang:phash2({I, Node}),
ets:delete(Tab, Hash)
end, lists:seq(1, ?POINTS)).
get_node_by_hash(Tab, Hash) ->
NodeHash = case ets:next(Tab, Hash) of
'$end_of_table' ->
ets:first(Tab);
NH ->
NH
end,
if NodeHash == '$end_of_table' ->
erlang:error(no_running_nodes);
true ->
case ets:lookup(Tab, NodeHash) of
[] ->
get_node_by_hash(Tab, Hash);
[{_, Node}] ->
Node
end
end.
register_node() ->
global:register_name(list_to_atom(node_id()), self()).

View File

@ -45,6 +45,8 @@
get_peer_certificate/1,
get_verify_result/1,
close/1,
setopts/2,
change_controller/2,
sockname/1, peername/1]).
%% gen_server callbacks
@ -94,18 +96,15 @@ start(Module, SockMod, Socket, Opts) ->
todo
end.
starttls(FsmRef, _TLSOpts) ->
%% TODO: Frontend improvements planned by Aleksey
%%gen_server:call(FsmRef, {starttls, TLSOpts}),
FsmRef.
starttls(FsmRef, TLSOpts) ->
starttls(FsmRef, TLSOpts, undefined).
starttls(FsmRef, TLSOpts, Data) ->
gen_server:call(FsmRef, {starttls, TLSOpts, Data}),
FsmRef.
compress(FsmRef) ->
gen_server:call(FsmRef, compress),
FsmRef.
compress(FsmRef, undefined).
compress(FsmRef, Data) ->
gen_server:call(FsmRef, {compress, Data}),
@ -138,11 +137,14 @@ close(FsmRef) ->
sockname(FsmRef) ->
gen_server:call(FsmRef, sockname).
peername(_FsmRef) ->
%% TODO: Frontend improvements planned by Aleksey
%%gen_server:call(FsmRef, peername).
{ok, {{0, 0, 0, 0}, 0}}.
setopts(FsmRef, Opts) ->
gen_server:call(FsmRef, {setopts, Opts}).
change_controller(FsmRef, C2SPid) ->
gen_server:call(FsmRef, {change_controller, C2SPid}).
peername(FsmRef) ->
gen_server:call(FsmRef, peername).
%%====================================================================
%% gen_server callbacks
@ -158,9 +160,16 @@ peername(_FsmRef) ->
init([Module, SockMod, Socket, Opts, Receiver]) ->
%% TODO: monitor the receiver
Node = ejabberd_node_groups:get_closest_node(backend),
IP = case peername(SockMod, Socket) of
{ok, IP1} ->
IP1;
_ ->
undefined
end,
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
{ok, Pid} =
rpc:call(Node, Module, start, [{?MODULE, self()}, Opts]),
rpc:call(Node, Module, start,
[{?MODULE, self()}, [{frontend_ip, IP} | Opts]]),
ejabberd_receiver:become_controller(Receiver, Pid),
{ok, #state{sockmod = SockMod2,
socket = Socket2,
@ -175,38 +184,16 @@ init([Module, SockMod, Socket, Opts, Receiver]) ->
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({starttls, TLSOpts}, _From, State) ->
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
Reply = ok,
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
?HIBERNATE_TIMEOUT};
handle_call({starttls, TLSOpts, Data}, _From, State) ->
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
catch (State#state.sockmod):send(
State#state.socket, Data),
{ok, TLSSocket} = ejabberd_receiver:starttls(
State#state.receiver, TLSOpts, Data),
Reply = ok,
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
?HIBERNATE_TIMEOUT};
handle_call(compress, _From, State) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
State#state.sockmod,
State#state.socket),
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
Reply = ok,
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
?HIBERNATE_TIMEOUT};
handle_call({compress, Data}, _From, State) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
State#state.sockmod,
State#state.socket),
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
catch (State#state.sockmod):send(
State#state.socket, Data),
{ok, ZlibSocket} = ejabberd_receiver:compress(
State#state.receiver, Data),
Reply = ok,
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
?HIBERNATE_TIMEOUT};
@ -246,13 +233,7 @@ handle_call(close, _From, State) ->
handle_call(sockname, _From, State) ->
#state{sockmod = SockMod, socket = Socket} = State,
Reply =
case SockMod of
gen_tcp ->
inet:sockname(Socket);
_ ->
SockMod:sockname(Socket)
end,
Reply = peername(SockMod, Socket),
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(peername, _From, State) ->
@ -266,6 +247,14 @@ handle_call(peername, _From, State) ->
end,
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call({setopts, Opts}, _From, State) ->
ejabberd_receiver:setopts(State#state.receiver, Opts),
{reply, ok, State, ?HIBERNATE_TIMEOUT};
handle_call({change_controller, Pid}, _From, State) ->
ejabberd_receiver:change_controller(State#state.receiver, Pid),
{reply, ok, State, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
@ -318,10 +307,16 @@ check_starttls(SockMod, Socket, Receiver, Opts) ->
end, Opts),
if
TLSEnabled ->
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(Receiver, TLSSocket),
{ok, TLSSocket} = ejabberd_receiver:starttls(Receiver, TLSOpts),
{tls, TLSSocket};
true ->
{SockMod, Socket}
end.
peername(SockMod, Socket) ->
case SockMod of
gen_tcp ->
inet:peername(Socket);
_ ->
SockMod:peername(Socket)
end.

View File

@ -35,7 +35,8 @@
stop_listener/2,
parse_listener_portip/2,
add_listener/3,
delete_listener/2
delete_listener/2,
rate_limit/2
]).
-include("ejabberd.hrl").
@ -225,6 +226,9 @@ get_ip_tuple(IPOpt, _IPVOpt) ->
IPOpt.
accept(ListenSocket, Module, Opts) ->
accept(ListenSocket, Module, Opts, 0).
accept(ListenSocket, Module, Opts, Interval) ->
NewInterval = check_rate_limit(Interval),
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
case {inet:sockname(Socket), inet:peername(Socket)} of
@ -239,11 +243,11 @@ accept(ListenSocket, Module, Opts) ->
false -> ejabberd_socket
end,
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
accept(ListenSocket, Module, Opts);
accept(ListenSocket, Module, Opts, NewInterval);
{error, Reason} ->
?INFO_MSG("(~w) Failed TCP accept: ~w",
[ListenSocket, Reason]),
accept(ListenSocket, Module, Opts)
accept(ListenSocket, Module, Opts, NewInterval)
end.
udp_recv(Socket, Module, Opts) ->
@ -471,3 +475,39 @@ format_error(Reason) ->
ReasonStr ->
ReasonStr
end.
%% Set interval between two accepts on given port
rate_limit([], _Interval) ->
ok;
rate_limit([Port|Ports], Interval) ->
rate_limit(Port, Interval),
rate_limit(Ports, Interval);
rate_limit(Port, Interval) ->
case get_listener_pid_by_port(Port) of
undefined -> no_listener;
Pid -> Pid ! {rate_limit, Interval}, ok
end.
get_listener_pid_by_port(Port) ->
ListenerPids = [Pid || {{P,_,_},Pid,_,_} <-
supervisor:which_children(erlang:whereis(ejabberd_listeners)),
P == Port],
ListenerPid = case ListenerPids of
[] -> undefined;
[LPid|_] -> LPid
end,
ListenerPid.
check_rate_limit(Interval) ->
NewInterval = receive
{rate_limit, AcceptInterval} ->
AcceptInterval
after 0 ->
Interval
end,
case NewInterval of
0 -> ok;
Ms ->
timer:sleep(Ms)
end,
NewInterval.

View File

@ -125,7 +125,7 @@ route(From, To, Packet) ->
route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
ID = ejabberd_router:make_id(),
Host = From#jid.lserver,
register_iq_response_handler(Host, ID, undefined, F),
jlib:iq_to_xml(IQ#iq{id = ID});
@ -136,10 +136,10 @@ route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
register_iq_response_handler(_Host, ID, Module, Function) ->
TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID),
mnesia:dirty_write(#iq_response{id = ID,
module = Module,
function = Function,
timer = TRef}).
ets:insert(iq_response, #iq_response{id = ID,
module = Module,
function = Function,
timer = TRef}).
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
@ -181,11 +181,9 @@ init([]) ->
?MODULE, bounce_resource_packet, 100)
end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
update_table(),
mnesia:create_table(iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
mnesia:add_table_copy(iq_response, node(), ram_copies),
mnesia:delete_table(iq_response),
catch ets:new(iq_response, [named_table, public,
{keypos, #iq_response.id}]),
{ok, #state{}}.
%%--------------------------------------------------------------------
@ -257,7 +255,7 @@ handle_info(refresh_iq_handlers, State) ->
end, ets:tab2list(?IQTABLE)),
{noreply, State};
handle_info({timeout, _TRef, ID}, State) ->
process_iq_timeout(ID),
spawn(fun() -> process_iq_timeout(ID) end),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
@ -312,40 +310,22 @@ do_route(From, To, Packet) ->
end
end.
update_table() ->
case catch mnesia:table_info(iq_response, attributes) of
[id, module, function] ->
mnesia:delete_table(iq_response);
[id, module, function, timer] ->
ok;
{'EXIT', _} ->
ok
end.
get_iq_callback(ID) ->
case mnesia:dirty_read(iq_response, ID) of
case ets:lookup(iq_response, ID) of
[#iq_response{module = Module, timer = TRef,
function = Function}] ->
cancel_timer(TRef),
mnesia:dirty_delete(iq_response, ID),
ets:delete(iq_response, ID),
{ok, Module, Function};
_ ->
error
end.
process_iq_timeout(ID) ->
spawn(fun process_iq_timeout/0) ! ID.
process_iq_timeout() ->
receive
ID ->
case get_iq_callback(ID) of
{ok, undefined, Function} ->
Function(timeout);
_ ->
ok
end
after 5000 ->
case get_iq_callback(ID) of
{ok, undefined, Function} ->
Function(timeout);
_ ->
ok
end.

View File

@ -31,6 +31,7 @@
%% API
-export([start_link/0,
start/0,
join/1,
leave/1,
get_members/1,
@ -40,7 +41,7 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {}).
-record(state, {groups = []}).
%%====================================================================
%% API
@ -49,6 +50,15 @@
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start() ->
ChildSpec = {?MODULE,
{?MODULE, start_link, []},
permanent,
brutal_kill,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@ -81,30 +91,19 @@ get_closest_node(Name) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
{FE, BE} =
Groups =
case ejabberd_config:get_local_option(node_type) of
frontend ->
{true, false};
[frontend];
backend ->
{false, true};
[backend];
generic ->
{true, true};
[frontend, backend];
undefined ->
{true, true}
[frontend, backend]
end,
if
FE ->
join(frontend);
true ->
ok
end,
if
BE ->
join(backend);
true ->
ok
end,
{ok, #state{}}.
lists:foreach(fun join/1, Groups),
{ok, #state{groups = Groups}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@ -144,7 +143,8 @@ handle_info(_Info, State) ->
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
terminate(_Reason, #state{groups = Groups}) ->
lists:foreach(fun leave/1, Groups),
ok.
%%--------------------------------------------------------------------

View File

@ -36,8 +36,12 @@
change_shaper/2,
reset_stream/1,
starttls/2,
starttls/3,
compress/2,
send/2,
become_controller/2,
change_controller/2,
setopts/2,
close/1]).
%% gen_server callbacks
@ -52,6 +56,7 @@
c2s_pid,
max_stanza_size,
xml_stream_state,
tref,
timeout}).
-define(HIBERNATE_TIMEOUT, 90000).
@ -86,15 +91,42 @@ change_shaper(Pid, Shaper) ->
reset_stream(Pid) ->
gen_server:call(Pid, reset_stream).
starttls(Pid, TLSSocket) ->
gen_server:call(Pid, {starttls, TLSSocket}).
starttls(Pid, TLSOpts) ->
starttls(Pid, TLSOpts, undefined).
compress(Pid, ZlibSocket) ->
gen_server:call(Pid, {compress, ZlibSocket}).
starttls(Pid, TLSOpts, Data) ->
gen_server:call(Pid, {starttls, TLSOpts, Data}).
compress(Pid, Data) ->
gen_server:call(Pid, {compress, Data}).
become_controller(Pid, C2SPid) ->
gen_server:call(Pid, {become_controller, C2SPid}).
change_controller(Pid, C2SPid) ->
case catch gen_server:call(Pid, {change_controller, C2SPid}) of
{'EXIT', _} ->
{error, einval};
Res ->
Res
end.
setopts(Pid, Opts) ->
case lists:member({active, false}, Opts) of
true ->
case catch gen_server:call(Pid, deactivate_socket) of
{'EXIT', _} ->
{error, einval};
Res ->
Res
end;
false ->
ok
end.
send(Pid, Data) ->
gen_server:call(Pid, {send, Data}).
close(Pid) ->
gen_server:cast(Pid, close).
@ -132,10 +164,17 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({starttls, TLSSocket}, _From,
handle_call({starttls, TLSOpts, Data}, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
socket = Socket,
max_stanza_size = MaxStanzaSize} = State) ->
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
if Data /= undefined ->
do_send(State, Data);
true ->
ok
end,
close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
NewState = State#state{socket = TLSSocket,
@ -143,14 +182,23 @@ handle_call({starttls, TLSSocket}, _From,
xml_stream_state = NewXMLStreamState},
case tls:recv_data(TLSSocket, "") of
{ok, TLSData} ->
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{reply, {ok, TLSSocket},
process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
handle_call({compress, ZlibSocket}, _From,
handle_call({compress, Data}, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
socket = Socket,
sock_mod = SockMod,
max_stanza_size = MaxStanzaSize} = State) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(SockMod, Socket),
if Data /= undefined ->
do_send(State, Data);
true ->
ok
end,
close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
NewState = State#state{socket = ZlibSocket,
@ -158,7 +206,8 @@ handle_call({compress, ZlibSocket}, _From,
xml_stream_state = NewXMLStreamState},
case ejabberd_zlib:recv_data(ZlibSocket, "") of
{ok, ZlibData} ->
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
{reply, {ok, ZlibSocket},
process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
@ -172,12 +221,31 @@ handle_call(reset_stream, _From,
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
erlang:monitor(process, C2SPid),
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call({change_controller, C2SPid}, _From, State) ->
erlang:monitor(process, C2SPid),
NewXMLStreamState = xml_stream:change_callback_pid(
State#state.xml_stream_state, C2SPid),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = NewXMLStreamState},
activate_socket(NewState),
{reply, ok, NewState, ?HIBERNATE_TIMEOUT};
handle_call({send, Data}, _From, State) ->
case do_send(State, Data) of
ok ->
{reply, ok, State, ?HIBERNATE_TIMEOUT};
{error, _Reason} = Err ->
{stop, normal, Err, State}
end;
handle_call(deactivate_socket, _From, State) ->
deactivate_socket(State),
{reply, ok, State, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
@ -237,6 +305,9 @@ handle_info({Tag, _TCPSocket, Reason}, State)
_ ->
{stop, normal, State}
end;
handle_info({'DOWN', _MRef, process, C2SPid, _},
#state{c2s_pid = C2SPid} = State) ->
{stop, normal, State};
handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State),
{noreply, State, ?HIBERNATE_TIMEOUT};
@ -294,6 +365,17 @@ activate_socket(#state{socket = Socket,
ok
end.
deactivate_socket(#state{socket = Socket,
tref = TRef,
sock_mod = SockMod}) ->
cancel_timer(TRef),
case SockMod of
gen_tcp ->
inet:setopts(Socket, [{active, false}]);
_ ->
SockMod:setopts(Socket, [{active, false}])
end.
%% Data processing for connectors directly generating xmlelement in
%% Erlang data structure.
%% WARNING: Shaper does not work with Erlang data structure.
@ -315,20 +397,23 @@ process_data([Element|Els], #state{c2s_pid = C2SPid} = State)
%% Data processing for connectors receivind data as string.
process_data(Data,
#state{xml_stream_state = XMLStreamState,
tref = TRef,
shaper_state = ShaperState,
c2s_pid = C2SPid} = State) ->
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
if
C2SPid == undefined ->
ok;
Pause > 0 ->
erlang:start_timer(Pause, self(), activate);
true ->
activate_socket(State)
end,
NewTRef = if
C2SPid == undefined ->
TRef;
Pause > 0 ->
erlang:start_timer(Pause, self(), activate);
true ->
activate_socket(State),
TRef
end,
State#state{xml_stream_state = XMLStreamState1,
tref = NewTRef,
shaper_state = NewShaperState}.
%% Element coming from XML parser are wrapped inside xmlstreamelement
@ -345,3 +430,21 @@ close_stream(undefined) ->
ok;
close_stream(XMLStreamState) ->
xml_stream:close(XMLStreamState).
do_send(State, Data) ->
(State#state.sock_mod):send(State#state.socket, Data).
cancel_timer(TRef) when is_reference(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
receive
{timeout, TRef, _} ->
ok
after 0 ->
ok
end;
_ ->
ok
end;
cancel_timer(_) ->
ok.

View File

@ -38,7 +38,8 @@
unregister_route/1,
unregister_routes/1,
dirty_get_all_routes/0,
dirty_get_all_domains/0
dirty_get_all_domains/0,
make_id/0
]).
-export([start_link/0]).
@ -53,6 +54,9 @@
-record(route, {domain, pid, local_hint}).
-record(state, {}).
%% "rr" stands for Record-Route.
-define(ROUTE_PREFIX, "rr-").
%%====================================================================
%% API
%%====================================================================
@ -65,7 +69,7 @@ start_link() ->
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
case catch route_check_id(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
@ -192,6 +196,8 @@ dirty_get_all_routes() ->
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
make_id() ->
?ROUTE_PREFIX ++ randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id().
%%====================================================================
%% gen_server callbacks
@ -309,6 +315,32 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
route_check_id(From, To, {xmlelement, "iq", Attrs, _} = Packet) ->
case xml:get_attr_s("id", Attrs) of
?ROUTE_PREFIX ++ Rest ->
Type = xml:get_attr_s("type", Attrs),
if Type == "error"; Type == "result" ->
case string:tokens(Rest, "-") of
[_, NodeID] ->
case ejabberd_cluster:get_node_by_id(NodeID) of
Node when Node == node() ->
do_route(From, To, Packet);
Node ->
{ejabberd_router, Node} !
{route, From, To, Packet}
end;
_ ->
do_route(From, To, Packet)
end;
true ->
do_route(From, To, Packet)
end;
_ ->
do_route(From, To, Packet)
end;
route_check_id(From, To, Packet) ->
do_route(From, To, Packet).
do_route(OrigFrom, OrigTo, OrigPacket) ->
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
[OrigFrom, OrigTo, OrigPacket]),
@ -413,4 +445,3 @@ update_tables() ->
false ->
ok
end.

View File

@ -40,7 +40,8 @@
dirty_get_connections/0,
allow_host/2,
incoming_s2s_number/0,
outgoing_s2s_number/0
outgoing_s2s_number/0,
migrate/1
]).
%% gen_server callbacks
@ -94,30 +95,63 @@ remove_connection(FromTo, Pid, Key) ->
end.
have_connection(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
[_] ->
true;
_ ->
false
case ejabberd_cluster:get_node(FromTo) of
Node when Node == node() ->
case mnesia:dirty_read(s2s, FromTo) of
[_] ->
true;
_ ->
false
end;
Node ->
case catch rpc:call(Node, mnesia, dirty_read,
[s2s, FromTo], 5000) of
[_] ->
true;
_ ->
false
end
end.
has_key(FromTo, Key) ->
case mnesia:dirty_select(s2s,
[{#s2s{fromto = FromTo, key = Key, _ = '_'},
[],
['$_']}]) of
[] ->
false;
_ ->
true
Query = [{#s2s{fromto = FromTo, key = Key, _ = '_'},
[],
['$_']}],
case ejabberd_cluster:get_node(FromTo) of
Node when Node == node() ->
case mnesia:dirty_select(s2s, Query) of
[] ->
false;
_ ->
true
end;
Node ->
case catch rpc:call(Node, mnesia, dirty_select,
[s2s, Query], 5000) of
[_|_] ->
true;
_ ->
false
end
end.
get_connections_pids(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
L when is_list(L) ->
[Connection#s2s.pid || Connection <- L];
_ ->
[]
case ejabberd_cluster:get_node(FromTo) of
Node when Node == node() ->
case catch mnesia:dirty_read(s2s, FromTo) of
L when is_list(L) ->
[Connection#s2s.pid || Connection <- L];
_ ->
[]
end;
Node ->
case catch rpc:call(Node, mnesia, dirty_read,
[s2s, FromTo], 5000) of
L when is_list(L) ->
[Connection#s2s.pid || Connection <- L];
_ ->
[]
end
end.
try_register(FromTo) ->
@ -148,7 +182,33 @@ try_register(FromTo) ->
end.
dirty_get_connections() ->
mnesia:dirty_all_keys(s2s).
lists:flatmap(
fun(Node) when Node == node() ->
mnesia:dirty_all_keys(s2s);
(Node) ->
case catch rpc:call(Node, mnesia, dirty_all_keys, [s2s], 5000) of
L when is_list(L) ->
L;
_ ->
[]
end
end, ejabberd_cluster:get_nodes()).
migrate(After) ->
Ss = mnesia:dirty_select(
s2s,
[{#s2s{fromto = '$1', pid = '$2', _ = '_'},
[],
['$$']}]),
lists:foreach(
fun([FromTo, Pid]) ->
case ejabberd_cluster:get_node_new(FromTo) of
Node when Node /= node() ->
ejabberd_s2s_out:stop_connection(Pid, After * 2);
_ ->
ok
end
end, Ss).
%%====================================================================
%% gen_server callbacks
@ -163,10 +223,11 @@ dirty_get_connections() ->
%%--------------------------------------------------------------------
init([]) ->
update_tables(),
mnesia:create_table(s2s, [{ram_copies, [node()]}, {type, bag},
mnesia:create_table(s2s, [{ram_copies, [node()]},
{type, bag}, {local_content, true},
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
ejabberd_commands:register_commands(commands()),
{ok, #state{}}.
@ -198,9 +259,6 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -221,6 +279,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
ejabberd_commands:unregister_commands(commands()),
ok.
@ -234,22 +293,18 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
s2s,
[{#s2s{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
do_route(From, To, Packet) ->
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
FromTo = {From#jid.lserver, To#jid.lserver},
case ejabberd_cluster:get_node(FromTo) of
Node when Node == node() ->
do_route1(From, To, Packet);
Node ->
{?MODULE, Node} ! {route, From, To, Packet}
end.
do_route1(From, To, Packet) ->
case find_connection(From, To) of
{atomic, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]),
@ -482,6 +537,12 @@ update_tables() ->
mnesia:delete_table(local_s2s);
false ->
ok
end,
case catch mnesia:table_info(s2s, local_content) of
false ->
mnesia:delete_table(s2s);
_ ->
ok
end.
%% Check if host is in blacklist or white list

View File

@ -34,7 +34,8 @@
start_link/3,
start_connection/1,
terminate_if_waiting_delay/2,
stop_connection/1]).
stop_connection/1,
stop_connection/2]).
%% p1_fsm callbacks (same as gen_fsm)
-export([init/1,
@ -44,6 +45,7 @@
wait_for_features/2,
wait_for_auth_result/2,
wait_for_starttls_proceed/2,
relay_to_bridge/2,
reopen_socket/2,
wait_before_retry/2,
stream_established/2,
@ -51,9 +53,9 @@
handle_sync_event/4,
handle_info/3,
terminate/3,
print_state/1,
code_change/4,
test_get_addr_port/1,
print_state/1,
get_addr_port/1]).
-include("ejabberd.hrl").
@ -72,6 +74,7 @@
myname, server, queue,
delay_to_retry = undefined_delay,
new = false, verify = false,
bridge,
timer}).
%%-define(DBGFSM, true).
@ -84,10 +87,11 @@
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_out, [From, Host, Type],
fsm_limit_opts() ++ ?FSMOPTS)).
-define(SUPERVISOR_START, rpc:call(Node, p1_fsm, start,
[ejabberd_s2s_out, [From, Host, Type],
fsm_limit_opts() ++ ?FSMOPTS])).
-else.
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_out_sup,
-define(SUPERVISOR_START, supervisor:start_child({ejabberd_s2s_out_sup, Node},
[From, Host, Type])).
-endif.
@ -126,6 +130,7 @@
%%% API
%%%----------------------------------------------------------------------
start(From, Host, Type) ->
Node = ejabberd_cluster:get_node({From, Host}),
?SUPERVISOR_START.
start_link(From, Host, Type) ->
@ -136,7 +141,10 @@ start_connection(Pid) ->
p1_fsm:send_event(Pid, init).
stop_connection(Pid) ->
p1_fsm:send_event(Pid, stop).
p1_fsm:send_event(Pid, closed).
stop_connection(Pid, Timeout) ->
p1_fsm:send_all_state_event(Pid, {closed, Timeout}).
%%%----------------------------------------------------------------------
%%% Callback functions from p1_fsm
@ -228,8 +236,19 @@ open_socket(init, StateData) ->
{error, _Reason} ->
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
[StateData#state.myname, StateData#state.server]),
wait_before_reconnect(StateData)
%%{stop, normal, StateData}
case ejabberd_hooks:run_fold(find_s2s_bridge,
undefined,
[StateData#state.myname,
StateData#state.server]) of
{Mod, Fun, Type} ->
?INFO_MSG("found a bridge to ~s for: ~s -> ~s",
[Type, StateData#state.myname,
StateData#state.server]),
NewStateData = StateData#state{bridge={Mod, Fun}},
{next_state, relay_to_bridge, NewStateData};
_ ->
wait_before_reconnect(StateData)
end
end;
open_socket(stop, StateData) ->
?INFO_MSG("s2s connection: ~s -> ~s (stopped in open socket)",
@ -677,6 +696,15 @@ reopen_socket(closed, StateData) ->
wait_before_retry(_Event, StateData) ->
{next_state, wait_before_retry, StateData, ?FSMTIMEOUT}.
relay_to_bridge(stop, StateData) ->
wait_before_reconnect(StateData);
relay_to_bridge(closed, StateData) ->
?INFO_MSG("relay to bridge: ~s -> ~s (closed)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
relay_to_bridge(_Event, StateData) ->
{next_state, relay_to_bridge, StateData}.
stream_established({xmlstreamelement, El}, StateData) ->
?DEBUG("s2S stream established", []),
case is_verify_res(El) of
@ -747,6 +775,9 @@ stream_established(closed, StateData) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event({closed, Timeout}, StateName, StateData) ->
p1_fsm:send_event_after(Timeout, closed),
{next_state, StateName, StateData};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
@ -827,6 +858,19 @@ handle_info({send_element, El}, StateName, StateData) ->
wait_before_retry ->
bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
{next_state, StateName, StateData};
relay_to_bridge ->
%% In this state we relay all outbound messages
%% to a foreign protocol bridge such as SMTP, SIP, etc.
{Mod, Fun} = StateData#state.bridge,
?DEBUG("relaying stanza via ~p:~p/1", [Mod, Fun]),
case catch Mod:Fun(El) of
{'EXIT', Reason} ->
?ERROR_MSG("Error while relaying to bridge: ~p", [Reason]),
bounce_element(El, ?ERR_INTERNAL_SERVER_ERROR),
wait_before_reconnect(StateData);
_ ->
{next_state, StateName, StateData}
end;
_ ->
Q = queue:in(El, StateData#state.queue),
{next_state, StateName, StateData#state{queue = Q},

View File

@ -353,6 +353,9 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
ejabberd_router:route_error(To, From, Err, Packet)
end,
{next_state, StateName, StateData};
handle_info(Info, StateName, StateData) ->
?ERROR_MSG("Unexpected info: ~p", [Info]),
{next_state, StateName, StateData}.

View File

@ -32,7 +32,11 @@
%% API
-export([start_link/0,
route/3,
open_session/5, close_session/4,
set_session/6,
open_session/5,
open_session/6,
close_session/4,
close_migrated_session/4,
check_in_subscription/6,
bounce_offline_message/3,
disconnect_removed_user/2,
@ -43,6 +47,7 @@
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
get_vh_my_session_list/1,
get_vh_session_number/1,
register_iq_handler/4,
register_iq_handler/5,
@ -53,7 +58,8 @@
user_resources/2,
get_session_pid/3,
get_user_info/3,
get_user_ip/3
get_user_ip/3,
migrate/1
]).
%% gen_server callbacks
@ -66,7 +72,6 @@
-include("mod_privacy.hrl").
-record(session, {sid, usr, us, priority, info}).
-record(session_counter, {vhost, count}).
-record(state, {}).
%% default value for the maximum number of user connections
@ -92,28 +97,37 @@ route(From, To, Packet) ->
end.
open_session(SID, User, Server, Resource, Info) ->
set_session(SID, User, Server, Resource, undefined, Info),
mnesia:dirty_update_counter(session_counter,
jlib:nameprep(Server), 1),
open_session(SID, User, Server, Resource, undefined, Info).
open_session(SID, User, Server, Resource, Priority, Info) ->
set_session(SID, User, Server, Resource, Priority, Info),
check_for_sessions_to_replace(User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
[SID, JID, Info]).
close_session(SID, User, Server, Resource) ->
Info = do_close_session(SID, User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
[SID, JID, Info]).
close_migrated_session(SID, User, Server, Resource) ->
Info = do_close_session(SID, User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_migrated_connection_hook, JID#jid.lserver,
[SID, JID, Info]).
do_close_session(SID, User, Server, Resource) ->
Info = case mnesia:dirty_read({session, SID}) of
[] -> [];
[#session{info=I}] -> I
end,
F = fun() ->
mnesia:delete({session, SID}),
mnesia:dirty_update_counter(session_counter,
jlib:nameprep(Server), -1)
mnesia:delete({session, SID})
end,
mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
[SID, JID, Info]).
Info.
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
@ -138,11 +152,17 @@ get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_index_read(session, US, #session.us) of
{'EXIT', _Reason} ->
[];
Ss ->
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
Ss = case ejabberd_cluster:get_node({LUser, LServer}) of
Node when Node == node() ->
catch mnesia:dirty_index_read(session, US, #session.us);
Node ->
catch rpc:call(Node, mnesia, dirty_index_read,
[session, US, #session.us], 5000)
end,
if is_list(Ss) ->
[element(3, S#session.usr) || S <- clean_session_list(Ss)];
true ->
[]
end.
get_user_ip(User, Server, Resource) ->
@ -150,12 +170,18 @@ get_user_ip(User, Server, Resource) ->
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
undefined;
Ss ->
Ss = case ejabberd_cluster:get_node({LUser, LServer}) of
Node when Node == node() ->
mnesia:dirty_index_read(session, USR, #session.usr);
Node ->
catch rpc:call(Node, mnesia, dirty_index_read,
[session, USR, #session.usr], 5000)
end,
if is_list(Ss), Ss /= [] ->
Session = lists:max(Ss),
proplists:get_value(ip, Session#session.info)
proplists:get_value(ip, Session#session.info);
true ->
undefined
end.
get_user_info(User, Server, Resource) ->
@ -163,15 +189,21 @@ get_user_info(User, Server, Resource) ->
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
offline;
Ss ->
Ss = case ejabberd_cluster:get_node({LUser, LServer}) of
Node when Node == node() ->
mnesia:dirty_index_read(session, USR, #session.usr);
Node ->
catch rpc:call(Node, mnesia, dirty_index_read,
[session, USR, #session.usr], 5000)
end,
if is_list(Ss), Ss /= [] ->
Session = lists:max(Ss),
Node = node(element(2, Session#session.sid)),
N = node(element(2, Session#session.sid)),
Conn = proplists:get_value(conn, Session#session.info),
IP = proplists:get_value(ip, Session#session.info),
[{node, Node}, {conn, Conn}, {ip, IP}]
[{node, N}, {conn, Conn}, {ip, IP}];
true ->
offline
end.
set_presence(SID, User, Server, Resource, Priority, Presence, Info) ->
@ -194,26 +226,37 @@ get_session_pid(User, Server, Resource) ->
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
case catch mnesia:dirty_index_read(session, USR, #session.usr) of
Res = case ejabberd_cluster:get_node({LUser, LServer}) of
Node when Node == node() ->
mnesia:dirty_index_read(session, USR, #session.usr);
Node ->
catch rpc:call(Node, mnesia, dirty_index_read,
[session, USR, #session.usr], 5000)
end,
case Res of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
end.
dirty_get_sessions_list() ->
mnesia:dirty_select(
session,
[{#session{usr = '$1', _ = '_'},
[],
['$1']}]).
Match = [{#session{usr = '$1', _ = '_'}, [], ['$1']}],
lists:flatmap(
fun(Node) when Node == node() ->
mnesia:dirty_select(session, Match);
(Node) ->
case catch rpc:call(Node, mnesia, dirty_select,
[session, Match], 5000) of
Ss when is_list(Ss) ->
Ss;
_ ->
[]
end
end, ejabberd_cluster:get_nodes()).
dirty_get_my_sessions_list() ->
mnesia:dirty_select(
session,
[{#session{sid = {'_', '$1'}, _ = '_'},
[{'==', {node, '$1'}, node()}],
['$_']}]).
mnesia:dirty_match_object(#session{_ = '_'}).
get_vh_session_list(Server) ->
get_vh_my_session_list(Server) ->
LServer = jlib:nameprep(Server),
mnesia:dirty_select(
session,
@ -221,19 +264,24 @@ get_vh_session_list(Server) ->
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
get_vh_session_list(Server) ->
lists:flatmap(
fun(Node) when Node == node() ->
get_vh_my_session_list(Server);
(Node) ->
case catch rpc:call(Node, ?MODULE, get_vh_my_session_list,
[Server], 5000) of
Ss when is_list(Ss) ->
Ss;
_ ->
[]
end
end, ejabberd_cluster:get_nodes()).
get_vh_session_number(Server) ->
LServer = jlib:nameprep(Server),
Query = mnesia:dirty_select(
session_counter,
[{#session_counter{vhost = LServer, count = '$1'},
[],
['$1']}]),
case Query of
[Count] ->
Count;
_ -> 0
end.
%% TODO
length(get_vh_session_list(Server)).
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
@ -243,6 +291,21 @@ register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
unregister_iq_handler(Host, XMLNS) ->
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
migrate(After) ->
Ss = mnesia:dirty_select(
session,
[{#session{us = '$1', sid = {'_', '$2'}, _ = '_'},
[],
['$$']}]),
lists:foreach(
fun([US, Pid]) ->
case ejabberd_cluster:get_node_new(US) of
Node when Node /= node() ->
ejabberd_c2s:migrate(Pid, Node, After);
_ ->
ok
end
end, Ss).
%%====================================================================
%% gen_server callbacks
@ -259,16 +322,13 @@ init([]) ->
update_tables(),
mnesia:create_table(session,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, session)}]),
mnesia:create_table(session_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, session_counter)}]),
mnesia:add_table_index(session, usr),
mnesia:add_table_index(session, us),
mnesia:add_table_copy(session, node(), ram_copies),
mnesia:add_table_copy(session_counter, node(), ram_copies),
mnesia:subscribe(system),
ets:new(sm_iqtable, [named_table]),
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(roster_in_subscription, Host,
@ -319,9 +379,6 @@ handle_info({route, From, To, Packet}, State) ->
ok
end,
{noreply, State};
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
recount_session_table(Node),
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
{noreply, State};
@ -348,6 +405,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
ejabberd_commands:unregister_commands(commands()),
ok.
@ -377,38 +435,20 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
end,
mnesia:sync_dirty(F).
%% Recalculates alive sessions when Node goes down
%% and updates session and session_counter tables
recount_session_table(Node) ->
F = fun() ->
Es = mnesia:select(
session,
[{#session{sid = {'_', '$1'}, _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete({session, E#session.sid})
end, Es),
%% reset session_counter table with active sessions
mnesia:clear_table(session_counter),
lists:foreach(fun(Server) ->
LServer = jlib:nameprep(Server),
Hs = mnesia:select(session,
[{#session{usr = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]),
mnesia:write(
#session_counter{vhost = LServer,
count = length(Hs)})
end, ?MYHOSTS)
end,
mnesia:async_dirty(F).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_route(From, To, Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
{U, S, _} = jlib:jid_tolower(To),
case ejabberd_cluster:get_node({U, S}) of
Node when Node /= node() ->
{?MODULE, Node} ! {route, From, To, Packet};
_ ->
do_route1(From, To, Packet)
end.
do_route1(From, To, Packet) ->
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
{xmlelement, Name, Attrs, _Els} = Packet,
@ -795,4 +835,11 @@ update_tables() ->
mnesia:delete_table(local_session);
false ->
ok
end,
mnesia:delete_table(session_counter),
case catch mnesia:table_info(session, local_content) of
false ->
mnesia:delete_table(session);
_ ->
ok
end.

View File

@ -44,6 +44,7 @@
get_peer_certificate/1,
get_verify_result/1,
close/1,
change_controller/2,
sockname/1, peername/1]).
-include("ejabberd.hrl").
@ -129,29 +130,19 @@ connect(Addr, Port, Opts, Timeout) ->
end.
starttls(SocketData, TLSOpts) ->
{ok, TLSSocket} = tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
SocketData#socket_state{socket = TLSSocket, sockmod = tls}.
starttls(SocketData, TLSOpts, undefined).
starttls(SocketData, TLSOpts, Data) ->
{ok, TLSSocket} = tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
send(SocketData, Data),
{ok, TLSSocket} = ejabberd_receiver:starttls(
SocketData#socket_state.receiver, TLSOpts, Data),
SocketData#socket_state{socket = TLSSocket, sockmod = tls}.
compress(SocketData) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
SocketData#socket_state.sockmod,
SocketData#socket_state.socket),
ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket),
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
compress(SocketData, undefined).
compress(SocketData, Data) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
SocketData#socket_state.sockmod,
SocketData#socket_state.socket),
ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket),
send(SocketData, Data),
{ok, ZlibSocket} = ejabberd_receiver:compress(
SocketData#socket_state.receiver, Data),
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
reset_stream(SocketData) when is_pid(SocketData#socket_state.receiver) ->
@ -160,10 +151,25 @@ reset_stream(SocketData) when is_atom(SocketData#socket_state.receiver) ->
(SocketData#socket_state.receiver):reset_stream(
SocketData#socket_state.socket).
change_controller(#socket_state{receiver = Recv}, Pid) when is_pid(Recv) ->
ejabberd_receiver:setopts(Recv, [{active, false}]),
sync_events(Pid),
ejabberd_receiver:change_controller(Recv, Pid);
change_controller(#socket_state{socket = Socket, receiver = Mod}, Pid) ->
Mod:setopts(Socket, [{active, false}]),
sync_events(Pid),
Mod:change_controller(Socket, Pid).
%% sockmod=gen_tcp|tls|ejabberd_zlib
send(SocketData, Data) ->
case catch (SocketData#socket_state.sockmod):send(
SocketData#socket_state.socket, Data) of
Res = if node(SocketData#socket_state.receiver) == node() ->
catch (SocketData#socket_state.sockmod):send(
SocketData#socket_state.socket, Data);
true ->
catch ejabberd_receiver:send(
SocketData#socket_state.receiver, Data)
end,
case Res of
ok -> ok;
{error, timeout} ->
?INFO_MSG("Timeout on ~p:send",[SocketData#socket_state.sockmod]),
@ -225,3 +231,21 @@ peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
%%====================================================================
%% Internal functions
%%====================================================================
%% dirty hack to relay queued messages from
%% old owner to new owner. The idea is based
%% on code of gen_tcp:controlling_process/2.
sync_events(C2SPid) ->
receive
{'$gen_event', El} = Event when element(1, El) == xmlelement;
element(1, El) == xmlstreamstart;
element(1, El) == xmlstreamelement;
element(1, El) == xmlstreamend;
element(1, El) == xmlstreamerror ->
C2SPid ! Event,
sync_events(C2SPid);
closed ->
C2SPid ! closed,
sync_events(C2SPid)
after 0 ->
ok
end.

View File

@ -42,13 +42,6 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_hooks]},
NodeGroups =
{ejabberd_node_groups,
{ejabberd_node_groups, start_link, []},
permanent,
brutal_kill,
worker,
[ejabberd_node_groups]},
SystemMonitor =
{ejabberd_system_monitor,
{ejabberd_system_monitor, start_link, []},
@ -153,6 +146,14 @@ init([]) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
WSLoopSupervisor =
{ejabberd_wsloop_sup,
{ejabberd_tmp_sup, start_link,
[ejabberd_wsloop_sup, ejabberd_wsloop]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
FrontendSocketSupervisor =
{ejabberd_frontend_socket_sup,
{ejabberd_tmp_sup, start_link,
@ -177,9 +178,23 @@ init([]) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
Cluster =
{ejabberd_cluster,
{ejabberd_cluster, start_link, []},
permanent,
brutal_kill,
worker,
[ejabberd_cluster]},
CacheTabSupervisor =
{cache_tab_sup,
{cache_tab_sup, start_link, []},
permanent,
infinity,
supervisor,
[cache_tab_sup]},
{ok, {{one_for_one, 10, 1},
[Hooks,
NodeGroups,
Cluster,
SystemMonitor,
Router,
SM,
@ -196,6 +211,7 @@ init([]) ->
IQSupervisor,
STUNSupervisor,
FrontendSocketSupervisor,
CacheTabSupervisor,
Listener]}}.

View File

@ -136,6 +136,7 @@ export EXEC_CMD
# start server
start ()
{
check_start
$EXEC_CMD "$ERL \
$NAME $ERLANG_NODE \
-noinput -detached \
@ -182,6 +183,7 @@ debug ()
# start interactive server
live ()
{
check_start
echo "--------------------------------------------------------------------"
echo ""
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
@ -210,6 +212,13 @@ live ()
$ERLANG_OPTS $ARGS \"$@\""
}
etop()
{
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
}
help ()
{
echo ""
@ -330,6 +339,26 @@ stop_epmd()
epmd -names | grep -q name || epmd -kill
}
# make sure node not already running and node name unregistered
check_start()
{
epmd -names | grep -q $NODE && {
ps ux | grep -q $ERLANG_NODE && {
echo "ejabberd is already running."
exit 4
} || {
ps ux | grep beam | grep -v "grep beam" && {
echo "ejabberd node is registered, but no ejabberd process has been found."
echo "can not kill epmd as other erlang nodes are running."
echo "please stop all erlang nodes, and call 'epmd -kill'."
exit 5
} || {
epmd -kill
}
}
}
}
# allow sync calls
wait_for_status()
{
@ -359,6 +388,7 @@ case $ARGS in
' start') start;;
' debug') debug;;
' live') live;;
' etop') etop;;
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
' stopped') wait_for_status 3 15 2; stop_epmd;; # wait 15x2s before timeout
*) ctl $ARGS;;

29
src/etop_defs.hrl Normal file
View File

@ -0,0 +1,29 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-define(SYSFORM,
" ~-72w~10s~n"
" Load: cpu ~8w Memory: total ~8w binary ~8w~n"
" procs~8w processes~8w code ~8w~n"
" runq ~8w atom ~8w ets ~8w~n").
-record(opts, {node=node(), port = 8415, accum = false, intv = 5000, lines = 10,
width = 700, height = 340, sort = runtime, tracing = on,
%% Other state information
out_mod=etop_gui, out_proc, server, host, tracer, store,
accum_tab, remote}).

130
src/etop_tr.erl Normal file
View File

@ -0,0 +1,130 @@
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(etop_tr).
-author('siri@erix.ericsson.se').
%%-compile(export_all).
-export([setup_tracer/1,stop_tracer/1,reader/1]).
-import(etop,[getopt/2]).
-include("etop_defs.hrl").
setup_tracer(Config) ->
TraceNode = getopt(node,Config),
RHost = rpc:call(TraceNode, net_adm, localhost, []),
Store = ets:new(?MODULE, [set, public]),
%% We can only trace one process anyway kill the old one.
case erlang:whereis(dbg) of
undefined ->
case rpc:call(TraceNode, erlang, whereis, [dbg]) of
undefined -> fine;
Pid ->
exit(Pid, kill)
end;
Pid ->
exit(Pid,kill)
end,
dbg:tracer(TraceNode,port,dbg:trace_port(ip,{getopt(port,Config),5000})),
dbg:p(all,[running,timestamp]),
T = dbg:get_tracer(TraceNode),
Config#opts{tracer=T,host=RHost,store=Store}.
stop_tracer(_Config) ->
dbg:p(all,clear),
dbg:stop(),
ok.
reader(Config) ->
Host = getopt(host, Config),
Port = getopt(port, Config),
{ok, Sock} = gen_tcp:connect(Host, Port, [{active, false}]),
spawn_link(fun() -> reader_init(Sock,getopt(store,Config),nopid) end).
%%%%%%%%%%%%%% Socket reader %%%%%%%%%%%%%%%%%%%%%%%%%%%
reader_init(Sock, Store, Last) ->
process_flag(priority, high),
reader(Sock, Store, Last).
reader(Sock, Store, Last) ->
Data = get_data(Sock),
New = handle_data(Last, Data, Store),
reader(Sock, Store, New).
handle_data(_, {_, Pid, in, _, Time}, _) ->
{Pid,Time};
handle_data({Pid,Time1}, {_, Pid, out, _, Time2}, Store) ->
Elapsed = elapsed(Time1, Time2),
case ets:member(Store,Pid) of
true -> ets:update_counter(Store, Pid, Elapsed);
false -> ets:insert(Store,{Pid,Elapsed})
end,
nopid;
handle_data(_W, {drop, D}, _) -> %% Error case we are missing data here!
io:format("Erlang top dropped data ~p~n", [D]),
nopid;
handle_data(nopid, {_, _, out, _, _}, _Store) ->
%% ignore - there was probably just a 'drop'
nopid;
handle_data(_, G, _) ->
%% io:format("Erlang top got garbage ~p~n", [G]),
nopid.
elapsed({Me1, S1, Mi1}, {Me2, S2, Mi2}) ->
Me = (Me2 - Me1) * 1000000,
S = (S2 - S1 + Me) * 1000000,
Mi2 - Mi1 + S.
%%%%%% Socket helpers %%%%
get_data(Sock) ->
[Op | BESiz] = my_ip_read(Sock, 5),
Siz = get_be(BESiz),
case Op of
0 ->
B = list_to_binary(my_ip_read(Sock, Siz)),
binary_to_term(B);
1 ->
{drop, Siz};
Else ->
exit({'bad trace tag', Else})
end.
get_be([A,B,C,D]) ->
A * 16777216 + B * 65536 + C * 256 + D.
my_ip_read(Sock,N) ->
case gen_tcp:recv(Sock, N) of
{ok, Data} ->
case length(Data) of
N ->
Data;
X ->
Data ++ my_ip_read(Sock, N - X)
end;
_Else ->
exit(eof)
end.

View File

@ -138,6 +138,35 @@ static int expat_erl_control(ErlDrvData drv_data,
case PARSE_COMMAND:
case PARSE_FINAL_COMMAND:
ei_x_new_with_version(&event_buf);
#ifdef ENABLE_FLASH_HACK
/* Flash hack - Flash clients send a null byte after the stanza. Remove that... */
{
int i;
int found_null = 0;
/* Maybe the Flash client sent many stanzas in one packet.
If so, there is a null byte between every stanza. */
for (i = 0; i < len; i++) {
if (buf[i] == '\0') {
buf[i] = ' ';
found_null = 1;
}
}
/* And also remove the closing slash if this is a
flash:stream element. Assume that flash:stream is the
last element in the packet, and entirely contained in
it. This requires that a null byte has been found. */
if (found_null && strstr(buf, "<flash:stream"))
/* buf[len - 1] is an erased null byte.
buf[len - 2] is >
buf[len - 3] is / (maybe)
*/
if (buf[len - 3] == '/')
buf[len - 3] = ' ';
}
#endif /* ENABLE_FLASH_HACK */
res = XML_Parse(d->parser, buf, len, command == PARSE_FINAL_COMMAND);
if(!res)

205
src/floodcheck.erl Normal file
View File

@ -0,0 +1,205 @@
%%%-------------------------------------------------------------------
%%% File : floodcheck.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Description :
%%%
%%% Created : 11 Sep 2008 by Christophe Romain <christophe.romain@process-one.net>
%%%-------------------------------------------------------------------
-module(floodcheck).
-behaviour(gen_server).
%% API
-export([start_link/0, stop/0]).
-export([monitor/5, demonitor/1, interval/1, check/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(DEFAULT_INTERVAL, 300). %% check every 5mn
-define(SERVER, ?MODULE).
-record(state, {timer, interval, monitors}).
-record(monitor, {id, pid, ref, info, rule, value, handler}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
case whereis(?SERVER) of
undefined -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []);
Pid -> {ok, Pid}
end.
stop() ->
gen_server:call(?SERVER, stop).
monitor(Id, Pid, Info, Spec, {Mod, Fun}) ->
gen_server:cast(?SERVER, {monitor, Id, Pid, Info, Spec, {Mod, Fun}}).
demonitor(Id) ->
gen_server:cast(?SERVER, {demonitor, Id}).
interval(Value) ->
gen_server:cast(?SERVER, {interval, Value}).
check() ->
gen_server:call(?SERVER, check).
%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
Timer = erlang:send_after(?DEFAULT_INTERVAL*1000, ?SERVER, monitor),
{ok, #state{timer=Timer, interval=?DEFAULT_INTERVAL, monitors=[]}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} |
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(check, _From, State) ->
Reply = lists:map(fun(#monitor{id=Id}=M) ->
{Id, check(M)}
end, State#state.monitors),
{reply, Reply, State};
handle_call(stop, _From, State) ->
erlang:cancel_timer(State#state.timer),
{stop, normal, ok, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({monitor, Id, Pid, Info, Spec, Handler}, State) ->
Monitors = State#state.monitors,
Ref = erlang:monitor(process, Pid),
{Rule, Value} = case Spec of
{Op, V} -> {Op, V};
V -> {'>', V}
end,
Monitor = #monitor{id=Id, pid=Pid, ref=Ref, info=Info, rule=Rule, value=Value, handler=Handler},
New = case lists:keysearch(Id, #monitor.id, Monitors) of
{value, #monitor{ref=OldRef}} ->
erlang:demonitor(OldRef),
lists:keyreplace(Id, #monitor.id, Monitors, Monitor);
_ ->
[Monitor|Monitors]
end,
{noreply, State#state{monitors=New}};
handle_cast({demonitor, Id}, State) ->
Monitors = State#state.monitors,
New = case lists:keysearch(Id, #monitor.id, Monitors) of
{value, #monitor{ref=Ref}} ->
erlang:demonitor(Ref),
lists:keydelete(Id, #monitor.id, Monitors);
_ ->
Monitors
end,
{noreply, State#state{monitors=New}};
handle_cast({interval, Value}, State) ->
erlang:cancel_timer(State#state.timer),
Timer = erlang:send_after(Value*1000, ?SERVER, monitor),
{noreply, State#state{timer=Timer, interval=Value}};
handle_cast(_Msg, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({'DOWN', Ref, _Type, _Pid, _Info}, State) ->
Monitors = State#state.monitors,
New = lists:keydelete(Ref, #monitor.ref, Monitors),
{noreply, State#state{monitors=New}};
handle_info(monitor, State) ->
lists:foreach(fun(#monitor{id=Id, pid=Pid, info=Info, handler={Mod, Fun}}=M) ->
case check(M) of
ok -> ok;
Value -> spawn(Mod, Fun, [Id, Pid, Info, Value])
end
end, State#state.monitors),
Timer = erlang:send_after(State#state.interval*1000, ?SERVER, monitor),
{noreply, State#state{timer=Timer}};
handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
check(#monitor{pid=Pid, info=Info, rule=Rule, value=Value}) ->
case catch process_info(Pid, Info) of
{Info, Actual} ->
Check = case Info of
messages -> byte_size(term_to_binary(Actual));
dictionary -> byte_size(term_to_binary(Actual));
_ -> Actual
end,
case Rule of
'>' ->
if Check > Value -> Value;
true -> ok
end;
'<' ->
if Check < Value -> Value;
true -> ok
end;
'=' ->
if Check == Value -> Value;
true -> ok
end;
_ ->
ok
end;
_ ->
ok
end.
%%% Documentation
%%% authorized Info
%%% message_queue_len: number of messages
%%% messages: messages queue size in bytes
%%% dictionary: dictionary size in bytes
%%% total_heap_size: total size in words of all heap fragments
%%% heap_size: size in words of youngest heap generation
%%% stack_size: stack size in words
%%% reductions: number of reductions executed by the process
%%% memory: process size in bytes

337
src/http_p1.erl Normal file
View File

@ -0,0 +1,337 @@
%%%----------------------------------------------------------------------
%%% File : http_p1.erl
%%% Author : Emilio Bustos <ebustos@process-one.net>
%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
%%% Created : 29 Jul 2010 by Emilio Bustos <ebustos@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(http_p1).
-author('ebustos@process-one.net').
-export([
start/0,
stop/0,
get/1,
get/2,
post/2,
post/3,
request/3,
request/4,
request/5
]).
% -define(USE_INETS, 1).
% -define(USE_LHTTPC, 1).
% -define(USE_IBROWSE, 1).
% inets used as default if none specified
-ifdef(USE_IBROWSE).
-define(start(), start_ibrowse()).
-define(request(M, U, H, B, O), request_ibrowse(M, U, H, B, O)).
-define(stop(), stop_ibrowse()).
-else.
-ifdef(USE_LHTTPC).
-define(start(), start_lhttpc()).
-define(request(M, U, H, B, O), request_lhttpc(M, U, H, B, O)).
-define(stop(), stop_lhttpc()).
-else.
-define(start(), start_inets()).
-define(request(M, U, H, B, O), request_inets(M, U, H, B, O)).
-define(stop(), stop_inets()).
-endif.
-endif.
-type header() :: {string() | atom(), string()}.
-type headers() :: [header()].
-type option() ::
{connect_timeout, timeout()} |
{timeout, timeout()} |
{send_retry, non_neg_integer()} |
{partial_upload, non_neg_integer() | infinity} |
{partial_download, pid(), non_neg_integer() | infinity}.
-type options() :: [option()].
-type result() :: {ok, {{pos_integer(), string()}, headers(), string()}} |
{error, atom()}.
%% @spec () -> ok | {error, Reason}
%% Reason = term()
%% @doc
%% Start the application.
%% This is a helper function that will start the corresponding backend.
%% It allows the library to be started using the `-s' flag.
%% For instance:
%% `$ erl -s http_p1'
%%
%% @end
-spec start() -> ok | {error, any()}.
start() ->
?start().
start_inets()->
inets:start(),
ssl:start().
start_lhttpc()->
application:start(crypto),
application:start(ssl),
lhttpc:start().
start_ibrowse()->
ibrowse:start(),
ssl:start().
%% @spec () -> ok | {error, Reason}
%% Reason = term()
%% @doc
%% Stops the application.
%% This is a helper function that will stop the corresponding backend.
%%
%% @end
-spec stop() -> ok | {error, any()}.
stop() ->
?stop().
stop_inets()->
inets:stop(),
ssl:stop().
stop_lhttpc()->
lhttpc:stop(),
application:stop(ssl).
stop_ibrowse()->
ibrowse:stop().
%% @spec (URL) -> Result
%% URL = string()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a GET request.
%% Would be the same as calling `request(get, URL, [])',
%% that is {@link request/3} with an empty header list.
%% @end
%% @see request/3
-spec get(string()) -> result().
get(URL) ->
request(get, URL, []).
%% @spec (URL, Hdrs) -> Result
%% URL = string()
%% Hdrs = [{Header, Value}]
%% Header = string()
%% Value = string()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a GET request.
%% Would be the same as calling `request(get, URL, Hdrs)'.
%% @end
%% @see request/3
-spec get(string(), headers()) -> result().
get(URL, Hdrs) ->
request(get, URL, Hdrs).
%% @spec (URL, RequestBody) -> Result
%% URL = string()
%% RequestBody = string()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a POST request with form data.
%% Would be the same as calling
%% `request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body)'.
%% @end
%% @see request/4
-spec post(string(), string()) -> result().
post(URL, Body) ->
request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body).
%% @spec (URL, Hdrs, RequestBody) -> Result
%% URL = string()
%% Hdrs = [{Header, Value}]
%% Header = string()
%% Value = string()
%% RequestBody = string()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a POST request.
%% Would be the same as calling
%% `request(post, URL, Hdrs, Body)'.
%% @end
%% @see request/4
-spec post(string(), headers(), string()) -> result().
post(URL, Hdrs, Body) ->
NewHdrs = case [X || {X,_}<-Hdrs, string:to_lower(X) == "content-type"] of
[] ->
[{"content-type", "x-www-form-urlencoded"} | Hdrs];
_ ->
Hdrs
end,
request(post, URL, NewHdrs, Body).
%% @spec (Method, URL, Hdrs) -> Result
%% Method = atom()
%% URL = string()
%% Hdrs = [{Header, Value}]
%% Header = string()
%% Value = string()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a request without a body.
%% Would be the same as calling `request(Method, URL, Hdrs, [], [])',
%% that is {@link request/5} with an empty body.
%% @end
%% @see request/5
-spec request(atom(), string(), headers()) -> result().
request(Method, URL, Hdrs) ->
request(Method, URL, Hdrs, [], []).
%% @spec (Method, URL, Hdrs, RequestBody) -> Result
%% Method = atom()
%% URL = string()
%% Hdrs = [{Header, Value}]
%% Header = string()
%% Value = string()
%% RequestBody = string()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a request with a body.
%% Would be the same as calling
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
%% with no options.
%% @end
%% @see request/5
-spec request(atom(), string(), headers(), string()) -> result().
request(Method, URL, Hdrs, Body) ->
request(Method, URL, Hdrs, Body, []).
%% @spec (Method, URL, Hdrs, RequestBody, Options) -> Result
%% Method = atom()
%% URL = string()
%% Hdrs = [{Header, Value}]
%% Header = string()
%% Value = string()
%% RequestBody = string()
%% Options = [Option]
%% Option = {timeout, Milliseconds | infinity} |
%% {connect_timeout, Milliseconds | infinity} |
%% {socket_options, [term()]} |
%% Milliseconds = integer()
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
%% | {error, Reason}
%% StatusCode = integer()
%% ResponseBody = string()
%% Reason = connection_closed | connect_timeout | timeout
%% @doc Sends a request with a body.
%% Would be the same as calling
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
%% with no options.
%% @end
%% @see request/5
-spec request(atom(), string(), headers(), string(), options()) -> result().
request(Method, URL, Hdrs, Body, Opts) ->
% ?DEBUG("Making request with headers: ~p~n~n", [Hdrs]),
% Headers = lists:map(fun({H, V}) ->
% H2 = if
% is_atom(H) ->
% string:to_lower(atom_to_list(H));
% is_list(H) ->
% string:to_lower(H);
% true ->
% H
% end,
% {H2, V}
% end, Hdrs),
?request(Method, URL, Hdrs, Body, Opts).
request_inets(Method, URL, Hdrs, Body, Opts) ->
Request = case Method of
get ->
{URL, Hdrs};
head ->
{URL, Hdrs};
_ -> % post, etc.
{URL, Hdrs, proplists:get_value("content-type", Hdrs, []), Body}
end,
Options = case proplists:get_value(timeout, Opts, infinity) of
infinity ->
proplists:delete(timeout, Opts);
_ ->
Opts
end,
case http:request(Method, Request, Options, []) of
{ok, {{_, Status, _}, Headers, Response}} ->
{ok, Status, Headers, Response};
{error, Reason} ->
{error, Reason}
end.
request_lhttpc(Method, URL, Hdrs, Body, Opts) ->
TimeOut = proplists:get_value(timeout, Opts, infinity),
SockOpt = proplists:get_value(socket_options, Opts, []),
Options = [{connect_options, SockOpt} | proplists:delete(timeout, Opts)],
case lhttpc:request(URL, Method, Hdrs, Body, TimeOut, Options) of
{ok, {{Status, _Reason}, Headers, Response}} ->
{ok, Status, Headers, binary_to_list(Response)};
{error, Reason} ->
{error, Reason}
end.
request_ibrowse(Method, URL, Hdrs, Body, Opts) ->
TimeOut = proplists:get_value(timeout, Opts, infinity),
Options = [{inactivity_timeout, TimeOut} | proplists:delete(timeout, Opts)],
case ibrowse:send_req(URL, Hdrs, Method, Body, Options) of
{ok, Status, Headers, Response} ->
{ok, list_to_integer(Status), Headers, Response};
{error, Reason} ->
{error, Reason}
end.
% ibrowse {response_format, response_format()} |
% Options - [option()]
% Option - {sync, boolean()} | {stream, StreamTo} | {body_format, body_format()} | {full_result,
% boolean()} | {headers_as_is, boolean()}
%body_format() = string() | binary()
% The body_format option is only valid for the synchronous request and the default is string.
% When making an asynchronous request the body will always be received as a binary.
% lhttpc: always binary

View File

@ -67,6 +67,7 @@
-define(NS_EJABBERD_CONFIG, "ejabberd:config").
-define(NS_STREAM, "http://etherx.jabber.org/streams").
-define(NS_FLASH_STREAM, "http://www.jabber.com/streams/flash").
-define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas").
-define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").

1
src/licence.hrl Normal file
View File

@ -0,0 +1 @@
-define(IS_VALID, true).

1166
src/mod_admin_p1.erl Normal file

File diff suppressed because it is too large Load Diff

186
src/mod_antiflood.erl Normal file
View File

@ -0,0 +1,186 @@
%%%-------------------------------------------------------------------
%%% File : mod_antiflood.erl
%%% Author : Christophe Romain <cromain@process-one.net>
%%% Description :
%%% Created : 12 Sep 2008 by Christophe Romain <cromain@process-one.net>
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(mod_antiflood).
-author('cromain@process-one.net').
-behaviour(gen_mod).
%% API
-export([start/2, stop/1]).
-export([handler/4]).
-include("ejabberd.hrl").
%%====================================================================
%% API
%%====================================================================
start(Host, Opts) ->
floodcheck:start_link(),
lists:foreach(fun
({listeners, Info, Op, Val}) ->
lists:foreach(fun({Name, Pid}) ->
Id = {Name, Info},
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler})
end, supervised_processes(listeners));
({Module, Info, Op, Val}) ->
case module_pid(Host, Module) of
Pid when is_pid(Pid) ->
Id = {Host, Module, Info},
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
Error ->
?INFO_MSG("can not monitor ~s (~p)", [Module, Error])
end;
(Arg) ->
?INFO_MSG("invalid argument: ~p", [Arg])
end, Opts).
stop(Host) ->
MList = gen_mod:loaded_modules_with_opts(Host),
case lists:keysearch(?MODULE, 1, MList) of
{value, {?MODULE, Opts}} ->
lists:foreach(fun
({Type, Info, _, _}) ->
case supervised_processes(Type) of
[] ->
Id = {Host, Type, Info},
floodcheck:demonitor(Id);
Childs ->
lists:foreach(fun({Name, _}) ->
Id = {Name, Info},
floodcheck:demonitor(Id)
end, Childs)
end;
(_) ->
ok
end, Opts);
false ->
ok
end,
case floodcheck:check() of
[] -> floodcheck:stop(), ok;
_ -> ok
end.
handler({Host, Module, Info}, Pid, Info, Value) ->
?WARNING_MSG("Flood alert on Process ~p (~s on ~s): ~s=~p", [Pid, Module, Host, Info, Value]),
restart_module(Host, Module),
remonitor({Host, Module, Info});
handler({Name, Info}, Pid, Info, Value) ->
?WARNING_MSG("Flood alert on Process ~p (~s): ~s=~p", [Pid, Name, Info, Value]),
kill_process(Name, Pid),
remonitor({Name, Info});
handler(Id, Pid, Info, Value) ->
?WARNING_MSG("Flood alert on Process ~p (~s): ~s=~p~nUnknown id, alert ignored", [Pid, Id, Info, Value]).
%%====================================================================
%% Internal functions
%%====================================================================
process_pid(Name) -> whereis(Name).
server_pid(Host, Name) -> process_pid(gen_mod:get_module_proc(Host, Name)).
module_pid(Host, mod_caps) -> server_pid(Host, ejabberd_mod_caps);
module_pid(Host, mod_ip_blacklist) -> server_pid(Host, mod_ip_blacklist);
module_pid(Host, mod_offline) -> server_pid(Host, ejabberd_offline);
module_pid(Host, mod_offline_odbc) -> server_pid(Host, ejabberd_offline);
module_pid(Host, mod_vcard) -> server_pid(Host, ejabberd_mod_vcard);
module_pid(Host, mod_vcard_odbc) -> server_pid(Host, ejabberd_mod_vcard);
module_pid(Host, mod_vcard_ldap) -> server_pid(Host, ejabberd_mod_vcard_ldap);
module_pid(Host, mod_irc) -> server_pid(Host, ejabberd_mod_irc);
module_pid(Host, mod_muc) -> server_pid(Host, ejabberd_mod_muc);
module_pid(Host, mod_muc_log) -> server_pid(Host, ejabberd_mod_muc_log);
module_pid(Host, mod_proxy65) -> server_pid(Host, ejabberd_mod_proxy65);
module_pid(Host, mod_proxy65_service) -> server_pid(Host, ejabberd_mod_proxy65_service);
module_pid(Host, mod_proxy65_sm) -> server_pid(Host, ejabberd_mod_proxy65_sm);
module_pid(Host, mod_pubsub) -> server_pid(Host, ejabberd_mod_pubsub);
module_pid(_, _) -> unsupported.
supervised_processes(listeners) ->
{links, Links} = process_info(whereis(ejabberd_listeners), links),
lists:map(fun(Pid) ->
{dictionary, Dict} = process_info(Pid, dictionary),
{_, _, [Port|_]} = proplists:get_value('$initial_call', Dict),
Name = list_to_atom("listener_"++integer_to_list(Port)),
{Name, Pid}
end, Links);
supervised_processes(_) -> [].
remonitor({Host, Module, Info}) ->
MList = gen_mod:loaded_modules_with_opts(Host),
case lists:keysearch(?MODULE, 1, MList) of
{value, {?MODULE, Opts}} ->
lists:foreach(fun
({M, I, Op, Val}) when M =:= Module, I =:= Info ->
case module_pid(Host, Module) of
Pid when is_pid(Pid) ->
Id = {Host, Module, Info},
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
Error ->
?INFO_MSG("can not monitor ~s (~p)", [Module, Error])
end;
(_) ->
ok
end, Opts);
_ ->
ok
end;
remonitor({Name, Info}) ->
[Host|_] = ejabberd_config:get_global_option(hosts),
MList = gen_mod:loaded_modules_with_opts(Host),
case lists:keysearch(?MODULE, 1, MList) of
{value, {?MODULE, Opts}} ->
lists:foreach(fun
({Type, I, Op, Val}) when I =:= Info ->
lists:foreach(fun
({N, Pid}) when N =:= Name ->
Id = {Name, Info},
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
(_) ->
ok
end, supervised_processes(Type));
(_) ->
ok
end, Opts);
_ ->
ok
end;
remonitor(Id) ->
?INFO_MSG("can not monitor ~s", [Id]).
restart_module(Host, Module) ->
MList = gen_mod:loaded_modules_with_opts(Host),
case lists:keysearch(Module, 1, MList) of
{value, {Module, Opts}} ->
?WARNING_MSG("restarting module ~s on ~s", [Module, Host]),
gen_mod:stop_module(Host, Module),
gen_mod:start_module(Host, Module, Opts);
_ ->
not_running
end.
kill_process(Name, Pid) ->
?WARNING_MSG("killing process ~s(~p)", [Name, Pid]),
exit(Pid, kill).

127
src/mod_autofilter.erl Normal file
View File

@ -0,0 +1,127 @@
%%% ====================================================================
%%% This software is copyright 2006-2010, ProcessOne.
%%%
%%% mod_autofilter
%%%
%%% @copyright 2006-2010 ProcessOne
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(mod_autofilter).
-author('christophe.romain@process-one.net').
-behaviour(gen_mod).
% module functions
-export([start/2,stop/1,is_loaded/0]).
-export([offline_message/3,filter_packet/1,close_session/2,close_session/3]).
-export([deny/2,allow/2,denied/0,listed/0,purge/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("licence.hrl").
-record(autofilter, {key, timestamp=0, count=1, drop=false, reason}).
start(Host, Opts) ->
case ?IS_VALID of
true ->
mnesia:create_table(autofilter, [
{disc_copies, [node()]}, {type, set},
{attributes, record_info(fields, autofilter)} ]),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, offline_message, 10),
ejabberd_hooks:add(filter_packet, ?MODULE, filter_packet, 10),
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, close_session, 10),
case gen_mod:get_opt(purge_freq, Opts, 0) of %% purge_freq in minutes
0 ->
no_purge;
Freq ->
Keep = gen_mod:get_opt(keep, Opts, 10), %% keep in minutes
timer:apply_interval(Freq*60000, ?MODULE, purge, [Keep*60])
end,
start;
false ->
not_started
end.
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, offline_message, 10),
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, close_session, 10),
stop.
is_loaded() ->
ok.
purge(Keep) ->
?INFO_MSG("autofilter purge",[]),
{T1, T2, _} = now(),
Older = T1*1000000+T2-Keep,
lists:foreach(fun(Key) ->
mnesia:dirty_delete({autofilter, Key})
end, mnesia:dirty_select(autofilter, [{#autofilter{key = '$1', drop = false, timestamp = '$2', _ = '_'}, [{'<', '$2', Older}], ['$1']}])),
ok.
deny(User, Server) ->
Key = {User, Server},
Record = case mnesia:dirty_read({autofilter, Key}) of
[R] -> R;
_ -> #autofilter{key=Key}
end,
?INFO_MSG("autofilter: messages from ~s@~s will be droped~n", [User, Server]),
mnesia:dirty_write(Record#autofilter{drop=true}).
allow(User, Server) ->
Key = {User, Server},
?INFO_MSG("autofilter: messages from ~s@~s are accepted~n", [User, Server]),
mnesia:dirty_delete({autofilter, Key}).
denied() ->
mnesia:dirty_select(autofilter, [{#autofilter{key = '$1', drop = true, reason = '$2', _ = '_'}, [], [['$1','$2']]}]).
listed() ->
mnesia:dirty_select(autofilter, [{#autofilter{key = '$1', drop = false, reason = '$2', _ = '_'}, [], [['$1','$2']]}]).
offline_message({jid, [], _, [], [], _, []}, _To, _Packet) ->
ok;
offline_message(From, To, Packet) ->
{User, Server, _} = jlib:jid_tolower(From),
Key = {User, Server},
{T1, T2, _} = now(),
T = T1*1000000+T2,
Record = case mnesia:dirty_read({autofilter, Key}) of
[#autofilter{timestamp=O, count=C}=R] ->
D = T-O,
if
D > 3600 -> % this is usefull only of purge_freq is not set
R#autofilter{timestamp=T, count=1};
((C/D) > 1/10) and (C > 90) ->
?INFO_MSG("autofilter: messages from ~s@~s will be droped~n", [User, Server]),
R#autofilter{drop=true, reason=offline_flood};
true ->
R#autofilter{count=C+1}
end;
_ ->
#autofilter{key=Key, timestamp=T, count=1, drop=false, reason=offline_flood}
end,
mnesia:dirty_write(Record),
ok.
filter_packet({From, To, Packet}) ->
{User, Server, _} = jlib:jid_tolower(From),
case mnesia:dirty_read({autofilter, {User, Server}}) of
[#autofilter{drop=true}] -> drop;
_ -> {From, To, Packet}
end.
close_session(SID, JID) ->
close_session(SID, JID, []).
close_session(_SID, {jid, _, _, _, User, Server, _}, _Info) ->
% this allows user, except for blocked ones
lists:foreach(fun(#autofilter{key=Key}) ->
mnesia:dirty_delete({autofilter, Key})
end, mnesia:dirty_match_object(#autofilter{key={User, Server}, drop = false, _ = '_'})),
ok.

175
src/mod_c2s_debug.erl Normal file
View File

@ -0,0 +1,175 @@
%% Usage:
%% In config file:
%% {mod_c2s_debug, [{logdir, "/tmp/xmpplogs"}]},
%% From Erlang shell:
%% mod_c2s_debug:start("localhost", []).
%% mod_c2s_debug:stop("localhost").
%%
%% Warning: Only one module for the debug handler can be defined.
-module(mod_c2s_debug).
-author('mremond@process-one.net').
-behaviour(gen_mod).
-behavior(gen_server).
-export([start/2, start_link/2, stop/1,
debug_start/3, debug_stop/2, log_packet/4, log_packet/5]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_c2s.hrl").
-record(modstate, {host, logdir, pid, iodevice}).
-record(clientinfo, {pid, jid, auth_module, ip}).
-define(SUPERVISOR, ejabberd_sup).
-define(PROCNAME, c2s_debug).
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Spec = {Proc, {?MODULE, start_link, [Host, Opts]},
transient, 2000, worker, [?MODULE]},
supervisor:start_child(?SUPERVISOR, Spec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(?SUPERVISOR, Proc).
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
%%====================================================================
%% Hooks
%%====================================================================
%% Debug handled by another module... Do nothing:
debug_start(_Status, Pid, C2SState) ->
Host = C2SState#state.server,
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
JID = jlib:jid_to_string(C2SState#state.jid),
AuthModule = C2SState#state.auth_module,
IP = C2SState#state.ip,
ClientInfo = #clientinfo{pid = Pid, jid = JID, auth_module = AuthModule, ip = IP},
gen_server:call(Proc, {debug_start, ClientInfo}).
debug_stop(Pid, C2SState) ->
Host = C2SState#state.server,
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {debug_stop, Pid}).
log_packet(false, _FromJID, _ToJID, _Packet) ->
ok;
log_packet(true, FromJID, ToJID, Packet) ->
Host = FromJID#jid.lserver,
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {addlog, {"Send", FromJID, ToJID, Packet}}).
log_packet(false, _JID, _FromJID, _ToJID, _Packet) ->
ok;
log_packet(true, JID, FromJID, ToJID, Packet) ->
Host = JID#jid.lserver,
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {addlog, {"Receive", FromJID, ToJID, Packet}}).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
?INFO_MSG("Starting c2s debug module for: ~p", [Host]),
MyHost = gen_mod:get_opt_host(Host, Opts, "c2s_debug.@HOST@"),
ejabberd_hooks:add(c2s_debug_start_hook, Host,
?MODULE, debug_start, 50),
ejabberd_hooks:add(c2s_debug_stop_hook, Host,
?MODULE, debug_stop, 50),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, log_packet, 50),
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, log_packet, 50),
Logdir = gen_mod:get_opt(logdir, Opts, "/tmp/xmpplogs/"),
make_dir_rec(Logdir),
{ok, #modstate{host = MyHost, logdir = Logdir}}.
terminate(_Reason, #modstate{host = Host}) ->
?INFO_MSG("Stopping c2s debug module for: ~s", [Host]),
ejabberd_hooks:delete(c2s_debug_start_hook, Host,
?MODULE, debug_start, 50),
ejabberd_hooks:delete(c2s_debug_stop_hook, Host,
?MODULE, debug_stop, 50),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, log_packet, 50).
handle_call({debug_start, ClientInfo}, _From, #modstate{pid=undefined} = State) ->
Pid = ClientInfo#clientinfo.pid,
?INFO_MSG("Debug started for PID:~p", [Pid]),
JID = ClientInfo#clientinfo.jid,
AuthModule = ClientInfo#clientinfo.auth_module,
IP = ClientInfo#clientinfo.ip,
{ok, IOD} = file:open(filename(State#modstate.logdir), [append]),
Line = io_lib:format("~s - Session open~nJID: ~s~nAuthModule: ~p~nIP: ~p~n",
[timestamp(), JID, AuthModule, IP]),
file:write(IOD, Line),
{reply, true, State#modstate{pid = Pid, iodevice = IOD}};
handle_call({debug_start, _ClientInfo}, _From, State) ->
{reply, false, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}.
handle_cast({addlog, _}, #modstate{iodevice=undefined} = State) ->
{noreply, State};
handle_cast({addlog, {Direction, FromJID, ToJID, Packet}}, #modstate{iodevice=IOD} = State) ->
LogEntry = io_lib:format("=====~n~s - ~s~nFrom: ~s~nTo: ~s~n~s~n", [timestamp(), Direction,
jlib:jid_to_string(FromJID),
jlib:jid_to_string(ToJID),
xml:element_to_string(Packet)]),
file:write(IOD, LogEntry),
{noreply, State};
handle_cast({debug_stop, Pid}, #modstate{pid=Pid, iodevice=IOD} = State) ->
Line = io_lib:format("=====~n~s - Session closed~n",
[timestamp()]),
file:write(IOD, Line),
file:close(IOD),
{noreply, State#modstate{pid = undefined, iodevice=undefined}};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Generate filename
filename(LogDir) ->
Filename = lists:flatten(timestamp()) ++ "-c2s.log",
filename:join([LogDir, Filename]).
%% Generate timestamp
timestamp() ->
{Y,Mo,D} = erlang:date(),
{H,Mi,S} = erlang:time(),
io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,Mo,D,H,Mi,S]).
%% Create dir recusively
make_dir_rec(Dir) ->
case file:read_file_info(Dir) of
{ok, _} ->
ok;
{error, enoent} ->
DirS = filename:split(Dir),
DirR = lists:sublist(DirS, length(DirS)-1),
make_dir_rec(filename:join(DirR)),
file:make_dir(Dir)
end.

View File

@ -53,7 +53,7 @@
]).
%% hook handlers
-export([user_send_packet/3]).
-export([user_send_packet/4]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -98,11 +98,12 @@ get_features(#caps{node = Node, version = Version, exts = Exts}) ->
SubNodes = [Version | Exts],
lists:foldl(
fun(SubNode, Acc) ->
case mnesia:dirty_read({caps_features,
node_to_binary(Node, SubNode)}) of
[] ->
BinaryNode = node_to_binary(Node, SubNode),
case cache_tab:lookup(caps_features, BinaryNode,
caps_read_fun(BinaryNode)) of
error ->
Acc;
[#caps_features{features = Features}] ->
{ok, Features} ->
binary_to_features(Features) ++ Acc
end
end, [], SubNodes).
@ -141,7 +142,8 @@ read_caps([], Result) ->
%%====================================================================
%% Hooks
%%====================================================================
user_send_packet(#jid{luser = User, lserver = Server} = From,
user_send_packet(_DebugFlag,
#jid{luser = User, lserver = Server} = From,
#jid{luser = User, lserver = Server, lresource = ""},
{xmlelement, "presence", Attrs, Els}) ->
Type = xml:get_attr_s("type", Attrs),
@ -155,7 +157,7 @@ user_send_packet(#jid{luser = User, lserver = Server} = From,
true ->
ok
end;
user_send_packet(_From, _To, _Packet) ->
user_send_packet(_DebugFlag, _From, _To, _Packet) ->
ok.
caps_stream_features(Acc, MyHost) ->
@ -196,12 +198,23 @@ disco_info(Acc, _Host, _Module, _Node, _Lang) ->
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, _Opts]) ->
init([Host, Opts]) ->
case catch mnesia:table_info(caps_features, storage_type) of
{'EXIT', _} ->
ok;
disc_only_copies ->
ok;
_ ->
mnesia:delete_table(caps_features)
end,
mnesia:create_table(caps_features,
[{disc_copies, [node()]},
[{disc_only_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, caps_features)}]),
mnesia:add_table_copy(caps_features, node(), disc_copies),
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000),
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
ejabberd_hooks:add(user_send_packet, Host,
?MODULE, user_send_packet, 75),
ejabberd_hooks:add(c2s_stream_features, Host,
@ -252,8 +265,9 @@ code_change(_OldVsn, State, _Extra) ->
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
Node = Caps#caps.node,
BinaryNode = node_to_binary(Node, SubNode),
case mnesia:dirty_read({caps_features, BinaryNode}) of
[] ->
case cache_tab:lookup(caps_features, BinaryNode,
caps_read_fun(BinaryNode)) of
error ->
IQ = #iq{type = get,
xmlns = ?NS_DISCO_INFO,
sub_el = [{xmlelement, "query",
@ -284,11 +298,13 @@ feature_response(#iq{type = result,
(_) ->
[]
end, Els),
mnesia:dirty_write(
#caps_features{node_pair = BinaryNode,
features = features_to_binary(Features)});
BinaryFeatures = features_to_binary(Features),
cache_tab:insert(
caps_features, BinaryNode, BinaryFeatures,
caps_write_fun(BinaryNode, BinaryFeatures));
false ->
mnesia:dirty_write(#caps_features{node_pair = BinaryNode})
cache_tab:insert(caps_features, BinaryNode, [],
caps_write_fun(BinaryNode, []))
end,
feature_request(Host, From, Caps, SubNodes);
feature_response(timeout, _Host, _From, _Caps, _SubNodes) ->
@ -297,7 +313,8 @@ feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) ->
%% We got type=error or invalid type=result stanza, so
%% we cache empty feature not to probe the client permanently
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
cache_tab:insert(caps_features, BinaryNode, [],
caps_write_fun(BinaryNode, [])),
feature_request(Host, From, Caps, SubNodes).
node_to_binary(Node, SubNode) ->
@ -306,6 +323,23 @@ node_to_binary(Node, SubNode) ->
features_to_binary(L) -> [list_to_binary(I) || I <- L].
binary_to_features(L) -> [binary_to_list(I) || I <- L].
caps_read_fun(Node) ->
fun() ->
case mnesia:dirty_read({caps_features, Node}) of
[#caps_features{features = Features}] ->
{ok, Features};
_ ->
error
end
end.
caps_write_fun(Node, Features) ->
fun() ->
mnesia:dirty_write(
#caps_features{node_pair = Node,
features = Features})
end.
make_my_disco_hash(Host) ->
JID = jlib:make_jid("", Host, ""),
case {ejabberd_hooks:run_fold(disco_local_features,

370
src/mod_filter.erl Normal file
View File

@ -0,0 +1,370 @@
%%% ====================================================================
%%% This software is copyright 2006-2010, ProcessOne.
%%%
%%% mod_filter
%%% allow message filtering using regexp on message body
%%% THIS MODULE NEEDS A PATCHED ERLANG VM AGAINST
%%% THE PCRE PATCH AND NEEDS LIBPCRE INSTALLED
%%% ejabberd MUST USE THAT PATCHED ERLANG VM
%%% BUT, if patch is not available, mod_filter uses re.beam module
%%% instead, with speed degradation.
%%%
%%% @copyright 2006-2010 ProcessOne
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(mod_filter).
-author('christophe.romain@process-one.net').
-vsn('$Id: mod_filter.erl 972 2010-10-14 21:53:56Z jpcarlino $').
-behaviour(gen_mod).
% module functions
-export([start/2,stop/1,init/2,update/2,is_loaded/0,loop/5]).
-export([add_regexp/4,add_regexp/3,del_regexp/3,del_regexp/2]).
-export([purge_logs/0,purge_regexps/1,reload/1]).
-export([logged/0,logged/1,rules/0]).
-export([process_local_iq/3]).
% handled ejabberd hooks
-export([filter_packet/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("licence.hrl").
-record(filter_rule, {id, type="filter", regexp, binre}).
-record(filter_log, {date, from, to, message}).
-define(TIMEOUT, 5000). % deliver message anyway if filter does not respond after 5s
-define(PROCNAME(VH), list_to_atom(VH++"_message_filter")).
-define(NS_FILTER, "p1:iq:filter").
-define(ALLHOSTS, "all hosts"). %% must be sync with filter.erl
start(Host, Opts) ->
case ?IS_VALID of
true ->
mnesia:create_table(filter_rule, [
{disc_copies, [node()]}, {type, set},
{attributes, record_info(fields, filter_rule)} ]),
mnesia:create_table(filter_log, [
{disc_only_copies, [node()]}, {type, bag},
{attributes, record_info(fields, filter_log)} ]),
%% this force the last code to be used
case whereis(?PROCNAME(Host)) of
undefined ->
ok;
_ ->
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_FILTER),
?PROCNAME(Host) ! quit
end,
ejabberd_hooks:add(filter_packet, ?MODULE, filter_packet, 10),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_FILTER,
?MODULE, process_local_iq, one_queue),
case whereis(?PROCNAME(Host)) of
undefined -> register(?PROCNAME(Host), spawn(?MODULE, init, [Host, Opts]));
_ -> ok
end,
%% start the all_alias handler
case whereis(?PROCNAME(?ALLHOSTS)) of
undefined -> init_all_hosts_handler();
_ -> ok
end,
start;
false ->
not_started
end.
stop(Host) ->
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_FILTER),
exit(whereis(?PROCNAME(Host)), kill),
{wait, ?PROCNAME(Host)}.
% this is used by team_leader to check code presence
is_loaded() ->
ok.
% this loads rules and return {Types, BinRegExps}
% RegExps are text regexp list
% Types are regexp type list
% both list share the same ordered
load_rules(Host) ->
Rules = mnesia:dirty_match_object(#filter_rule{id={'_', Host}, _ = '_'})
++ mnesia:dirty_match_object(#filter_rule{id={'_', ?ALLHOSTS}, _ = '_'}),
lists:map(fun({filter_rule, _, Type, _, BinRegExp}) -> {Type, BinRegExp} end, Rules).
%lists:unzip(Config).
% this call init or reset local rules reading database
init(Host, Opts) ->
Rules = load_rules(Host),
Scope = gen_mod:get_opt(scope, Opts, message),
Pattern = gen_mod:get_opt(pattern, Opts, ""),
?MODULE:loop(Host, Opts, Rules, Scope, Pattern).
init_all_hosts_handler() ->
register(?PROCNAME(?ALLHOSTS), spawn(?MODULE, loop, [?ALLHOSTS, [], [], none, []])).
% this call reset local rules reading database
% and tell other nodes to reset rules as well
update(?ALLHOSTS, _Opts) ->
lists:foreach(fun(Host) ->
lists:foreach(fun(Node) ->
catch rpc:call(Node,mod_filter,reload,[Host])
end, mnesia:system_info(running_db_nodes))
end, ejabberd_config:get_global_option(hosts)),
?MODULE:loop(?ALLHOSTS, [], [], none, []);
update(Host, Opts) ->
% tell other nodes to update filter
lists:foreach(fun(Node) ->
catch rpc:call(Node,mod_filter,reload,[Host])
end, mnesia:system_info(running_db_nodes)--[node()]),
% update rules
init(Host, Opts).
loop(Host, Opts, Rules, Scope, Pattern) ->
receive
{add, Id, RegExp} ->
try
[BinRegExp] = tlre:compile([RegExp]),
?INFO_MSG("Adding new filter rule with regexp=~p", [RegExp]),
mnesia:dirty_write(#filter_rule{id={Id, Host}, regexp=RegExp, binre=BinRegExp}),
?MODULE:update(Host, Opts)
catch
Class:Reason ->
?INFO_MSG("~p can't add filter rule with regexp=~p for id=~p. Reason: ~p", [Class, RegExp, Id, Reason]),
loop(Host, Opts, Rules, Scope, Pattern)
end;
{add, Id, RegExp, Type} ->
try
[BinRegExp] = tlre:compile([RegExp]),
?INFO_MSG("Adding new filter rule with regexp=~p", [RegExp]),
mnesia:dirty_write(#filter_rule{id={Id, Host}, regexp=RegExp, binre=BinRegExp, type=Type}),
?MODULE:update(Host, Opts)
catch
Class:Reason ->
?INFO_MSG("~p can't add filter rule with regexp=~p for id=~p with type=~p. Reason: ~p", [Class, RegExp, Id, Type, Reason]),
loop(Host, Opts, Rules, Scope, Pattern)
end;
{del, Id} ->
RulesToRemove = mnesia:dirty_match_object(#filter_rule{id={Id, Host}, _='_'}),
lists:foreach(fun(Rule) ->
mnesia:dirty_delete_object(Rule)
end, RulesToRemove),
?MODULE:update(Host, Opts);
{del, Id, RegExp} ->
RulesToRemove = mnesia:dirty_match_object(#filter_rule{id={Id, Host}, regexp=RegExp, _='_'}),
lists:foreach(fun(Rule) ->
mnesia:dirty_delete_object(Rule)
end, RulesToRemove),
?MODULE:update(Host, Opts);
{match, From, String} ->
From ! {match, string_filter(String, Rules, Scope, Pattern)},
?MODULE:loop(Host, Opts, Rules, Scope, Pattern);
reload ->
?MODULE:init(Host, Opts);
quit ->
unregister(?PROCNAME(Host)),
ok
end.
string_filter(String, Rules, Scope, Pattern) ->
lists:foldl(fun
(_, {Pass, []}) -> {Pass, []};
({Type, RegExp}, {Pass, NewString}) -> string_filter(NewString, Pass, RegExp, Type, Scope, Pattern)
end, {"pass", String}, Rules).
string_filter(String, Pass, RegExp, Type, Scope, Pattern) ->
case tlre:grep(String, [RegExp]) of
[no_match] ->
{Pass, String};
[{S1, S2, _}] ->
case Scope of
word ->
Start = string:sub_string(String, 1, S1),
StringTail = string:sub_string(String, S2+1, length(String)),
NewPass = pass_rule(Pass, Type),
{LastPass, End} = string_filter(StringTail, NewPass, RegExp, Type, Scope, Pattern),
NewString = case Type of
"log" -> lists:append([string:sub_string(String, 1, S2), End]);
_ -> lists:append([Start, Pattern, End])
end,
{LastPass, NewString};
_ ->
NewString = case Type of
"log" -> String;
_ -> []
end,
{pass_rule(Pass, Type), NewString}
end
end.
pass_rule("pass", New) -> New;
pass_rule("log", "log") -> "log";
pass_rule("log", "log and filter") -> "log and filter";
pass_rule("log", "filter") -> "log and filter";
pass_rule("filter", "log") -> "log and filter";
pass_rule("filter", "log and filter") -> "log and filter";
pass_rule("filter", "filter") -> "filter";
pass_rule("log and filter", _) -> "log and filter".
add_regexp(VH, Id, RegExp) ->
?PROCNAME(VH) ! {add, Id, RegExp},
ok.
add_regexp(VH, Id, RegExp, Type) ->
?PROCNAME(VH) ! {add, Id, RegExp, Type},
ok.
del_regexp(VH, Id) ->
?PROCNAME(VH) ! {del, Id},
ok.
del_regexp(VH, Id, RegExp) ->
?PROCNAME(VH) ! {del, Id, RegExp},
ok.
reload(VH) ->
?PROCNAME(VH) ! reload,
ok.
purge_logs() ->
mnesia:dirty_delete_object(#filter_log{_='_'}).
%purge_regexps() ->
% mnesia:dirty_delete_object(#filter_rule{_='_'}),
% reload().
purge_regexps(VH) ->
mnesia:dirty_delete_object(#filter_rule{id={'_', VH}, _='_'}),
reload(VH).
rules() ->
lists:map(fun(#filter_rule{id={Label, VH}, type=Type, regexp=Regexp}) ->
{VH, Label, Type, Regexp}
end, mnesia:dirty_match_object(#filter_rule{_='_'})).
logged() ->
lists:reverse(lists:map(fun(#filter_log{date=Date, from=From, to=To, message=Msg}) ->
{Date, jlib:jid_to_string(From), jlib:jid_to_string(To), Msg}
end, mnesia:dirty_match_object(#filter_log{_='_'}))).
logged(Limit) ->
List = mnesia:dirty_match_object(#filter_log{_='_'}),
Len = length(List),
FinalList = if
Len < Limit -> List;
true -> lists:nthtail(Len-Limit, List)
end,
lists:reverse(lists:map(fun(#filter_log{date=Date, from=From, to=To, message=Msg}) ->
{Date, jlib:jid_to_string(From), jlib:jid_to_string(To), Msg}
end, FinalList)).
%% filter_packet can receive drop if a previous filter already dropped
%% the packet
filter_packet(drop) -> drop;
filter_packet({From, To, Packet}) ->
case Packet of
{xmlelement, "message", MsgAttrs, Els} ->
case lists:keysearch("body", 2, Els) of
{value, {xmlelement, "body", BodyAttrs, Data}} ->
NewData = lists:foldl(fun
({xmlcdata, CData}, DataAcc) when is_binary(CData) ->
#jid{lserver = Host} = To,
case lists:member(Host, ejabberd_config:get_global_option(hosts)) of
true ->
Msg = binary_to_list(CData),
?PROCNAME(Host) ! {match, self(), Msg},
receive
{match, {"pass", _}} ->
[{xmlcdata, CData}|DataAcc];
{match, {"log", _}} ->
mnesia:dirty_write(#filter_log{
date=erlang:localtime(),
from=From, to=To, message=Msg}),
[{xmlcdata, CData}|DataAcc];
{match, {"log and filter", FinalString}} ->
mnesia:dirty_write(#filter_log{
date=erlang:localtime(),
from=From, to=To, message=Msg}),
case FinalString of
[] -> % entire message is dropped
DataAcc;
S -> % message must be regenerated
[{xmlcdata, list_to_binary(S)}|DataAcc]
end;
{match, {"filter", FinalString}} ->
case FinalString of
[] -> % entire message is dropped
DataAcc;
S -> % message must be regenerated
[{xmlcdata, list_to_binary(S)}|DataAcc]
end
after ?TIMEOUT ->
[{xmlcdata, CData}|DataAcc]
end;
false ->
[{xmlcdata, CData}|DataAcc]
end;
(Item, DataAcc) -> %% to not filter internal messages
[Item|DataAcc]
end, [], Data),
case NewData of
[] ->
drop;
D ->
NewEls = lists:keyreplace("body", 2, Els, {xmlelement, "body", BodyAttrs, lists:reverse(D)}),
{From, To, {xmlelement, "message", MsgAttrs, NewEls}}
end;
_ ->
{From, To, Packet}
end;
_ ->
{From, To, Packet}
end.
process_local_iq(From, #jid{lserver=VH} = _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
get ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
set ->
#jid{luser = User, lserver = Server, lresource = Resource} = From,
case acl:match_rule(global, configure, {User, Server, Resource}) of
allow ->
case xml:get_subtag(SubEl, "add") of
{xmlelement, "add", AddAttrs, _} ->
AID = xml:get_attr_s("id", AddAttrs),
ARE = xml:get_attr_s("re", AddAttrs),
case xml:get_attr_s("type", AddAttrs) of
"" -> add_regexp(VH, AID, ARE);
ATP -> add_regexp(VH, AID, ARE, ATP)
end;
_ -> ok
end,
case xml:get_subtag(SubEl, "del") of
{xmlelement, "del", DelAttrs, _} ->
DID = xml:get_attr_s("id", DelAttrs),
case xml:get_attr_s("re", DelAttrs) of
"" -> del_regexp(VH, DID);
DRE -> del_regexp(VH, DID, DRE)
end;
_ -> ok
end,
case xml:get_subtag(SubEl, "dellogs") of
{xmlelement, "dellogs", _, _} -> purge_logs();
_ -> ok
end,
case xml:get_subtag(SubEl, "delrules") of
{xmlelement, "delrules", _, _} -> purge_regexps(VH);
_ -> ok
end,
IQ#iq{type = result, sub_el = []};
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end
end.

View File

@ -85,7 +85,7 @@ loop(_State) ->
%% TODO: Support comment lines starting by %
update_bl_c2s() ->
?INFO_MSG("Updating C2S Blacklist", []),
case http:request(?BLC2S) of
case http_p1:request(?BLC2S) of
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
IPs = string:tokens(Body,"\n"),
ets:delete_all_objects(bl_c2s),

View File

@ -105,8 +105,16 @@ init([From, Host, Server, Username, Encoding, Port, Password]) ->
open_socket(init, StateData) ->
Addr = StateData#state.server,
Port = StateData#state.port,
?DEBUG("connecting to ~s:~p~n", [Addr, Port]),
case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of
?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]),
Connect6 = gen_tcp:connect(Addr, Port, [inet6, binary, {packet, 0}]),
Connect = case Connect6 of
{error, _} ->
?DEBUG("Connection with IPv6 to ~s:~p failed. Now using IPv4.", [Addr, Port]),
gen_tcp:connect(Addr, Port, [inet, binary, {packet, 0}]);
_ ->
Connect6
end,
case Connect of
{ok, Socket} ->
NewStateData = StateData#state{socket = Socket},
if

147
src/mod_mnesia_mngt.erl Normal file
View File

@ -0,0 +1,147 @@
%% Usage:
%% In config file:
%% {mod_mnesia_mgmt, [{logdir, "/tmp/xmpplogs/"}]},
%% From Erlang shell:
%% mod_mnesia_mgmt:start("localhost", []).
%% mod_mnesia_mgmt:stop("localhost").
-module(mod_mnesia_mngt).
-author('mremond@process-one.net').
-behaviour(gen_mod).
-behavior(gen_server).
-export([start/2, start_link/2, stop/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(modstate, {host, logdir, iodevice, timer}).
-define(SUPERVISOR, ejabberd_sup).
-define(PROCNAME, mod_mnesia_mgmt).
-define(STANDARD_ACCEPT_INTERVAL, 20). %% accept maximum one new connection every 20ms
-define(ACCEPT_INTERVAL, 200). %% This is used when Mnesia is overloaded
-define(RATE_LIMIT_DURATION, 120000). %% Time during which the rate limitation need to be maintained
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Spec = {Proc, {?MODULE, start_link, [Host, Opts]},
transient, 2000, worker, [?MODULE]},
supervisor:start_child(?SUPERVISOR, Spec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:delete_child(?SUPERVISOR, Proc).
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
?INFO_MSG("Starting mod_mnesia_mgmt for: ~p", [Host]),
ejabberd_listener:rate_limit([5222, 5223], ?STANDARD_ACCEPT_INTERVAL),
MyHost = gen_mod:get_opt_host(Host, Opts, "mnesia_mngt.@HOST@"),
Logdir = gen_mod:get_opt(logdir, Opts, "/tmp/xmpplogs/"),
make_dir_rec(Logdir),
{ok, IOD} = file:open(filename(Logdir), [append]),
mnesia:subscribe(system),
{ok, #modstate{host = MyHost, logdir = Logdir, iodevice = IOD}}.
terminate(_Reason, #modstate{host = Host, iodevice = IOD}) ->
?INFO_MSG("Stopping mod_mnesia_mgmt for: ~s", [Host]),
mnesia:unsubscribe(system),
file:close(IOD).
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Req, _From, State) ->
{reply, {error, badarg}, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({mnesia_system_event,{mnesia_overload, {mnesia_tm, message_queue_len, Values}}},
#modstate{iodevice = IOD, timer = Timer} = State) ->
Line = io_lib:format("~s - Mnesia overload due to message queue length (~p)",
[timestamp(), Values]),
file:write(IOD, Line),
reset_timer(Timer),
{messages, Messages} = process_info(whereis(mnesia_tm), messages),
log_messages(IOD, Messages, 20),
{noreply, State#modstate{timer = undefined}};
handle_info({mnesia_system_event,{mnesia_overload, Details}},
#modstate{iodevice = IOD, timer = Timer} = State) ->
Line = io_lib:format("~s - Mnesia overload: ~p",
[timestamp(), Details]),
file:write(IOD, Line),
reset_timer(Timer),
{noreply, State};
handle_info({mnesia_system_event, _Event}, State) ->
%% TODO: More event to handle
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Generate filename
filename(LogDir) ->
Filename = lists:flatten(timestamp()) ++ "-mnesia.log",
filename:join([LogDir, Filename]).
%% Generate timestamp
timestamp() ->
{Y,Mo,D} = erlang:date(),
{H,Mi,S} = erlang:time(),
io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,Mo,D,H,Mi,S]).
%% Create dir recusively
make_dir_rec(Dir) ->
case file:read_file_info(Dir) of
{ok, _} ->
ok;
{error, enoent} ->
DirS = filename:split(Dir),
DirR = lists:sublist(DirS, length(DirS)-1),
make_dir_rec(filename:join(DirR)),
file:make_dir(Dir)
end.
%% Write first messages to log file
log_messages(_IOD, _Messages, 0) ->
ok;
log_messages(_IOD, [], _N) ->
ok;
log_messages(IOD, [Message|Messages], N) ->
Line = io_lib:format("** ~w",
[Message]),
file:write(IOD, Line),
log_messages(IOD, Messages, N-1).
reset_timer(Timer) ->
cancel_timer(Timer),
ejabberd_listener:rate_limit([5222, 5223], ?ACCEPT_INTERVAL),
timer:apply_after(?RATE_LIMIT_DURATION, ejabberd_listener, rate_limit, [[5222, 5223], ?STANDARD_ACCEPT_INTERVAL]).
cancel_timer(undefined) ->
ok;
cancel_timer(Timer) ->
timer:cancel(Timer).

View File

@ -41,6 +41,9 @@
create_room/5,
process_iq_disco_items/4,
broadcast_service_message/2,
register_room/3,
migrate/1,
get_vh_rooms/1,
can_use_nick/3]).
%% gen_server callbacks
@ -109,7 +112,9 @@ room_destroyed(Host, Room, Pid, ServerHost) ->
%% Else use the passed options as defined in mod_muc_room.
create_room(Host, Name, From, Nick, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
RoomHost = gen_mod:get_module_opt_host(Host, ?MODULE, "conference.@HOST@"),
Node = ejabberd_cluster:get_node({Name, RoomHost}),
gen_server:call({Proc, Node}, {create, Name, From, Nick, Opts}).
store_room(Host, Name, Opts) ->
F = fun() ->
@ -162,6 +167,22 @@ can_use_nick(Host, JID, Nick) ->
U == LUS
end.
migrate(After) ->
Rs = mnesia:dirty_select(
muc_online_room,
[{#muc_online_room{name_host = '$1', pid = '$2', _ = '_'},
[],
['$$']}]),
lists:foreach(
fun([NameHost, Pid]) ->
case ejabberd_cluster:get_node_new(NameHost) of
Node when Node /= node() ->
mod_muc_room:migrate(Pid, Node, After);
_ ->
ok
end
end, Rs).
%%====================================================================
%% gen_server callbacks
%%====================================================================
@ -174,6 +195,7 @@ can_use_nick(Host, JID, Nick) ->
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
update_muc_online_table(),
mnesia:create_table(muc_room,
[{disc_copies, [node()]},
{attributes, record_info(fields, muc_room)}]),
@ -182,14 +204,13 @@ init([Host, Opts]) ->
{attributes, record_info(fields, muc_registered)}]),
mnesia:create_table(muc_online_room,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, muc_online_room)}]),
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
update_tables(MyHost),
clean_table_from_bad_node(node(), MyHost),
mnesia:add_table_index(muc_registered, nick),
mnesia:subscribe(system),
Access = gen_mod:get_opt(access, Opts, all),
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
@ -198,6 +219,7 @@ init([Host, Opts]) ->
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
ejabberd_router:register_route(MyHost),
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
HistorySize,
@ -264,12 +286,19 @@ handle_info({route, From, To, Packet},
default_room_opts = DefRoomOpts,
history_size = HistorySize,
room_shaper = RoomShaper} = State) ->
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
{U, S, _} = jlib:jid_tolower(To),
case ejabberd_cluster:get_node({U, S}) of
Node when Node == node() ->
case catch do_route(Host, ServerHost, Access, HistorySize,
RoomShaper, From, To, Packet, DefRoomOpts) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
_ ->
ok
end;
Node ->
Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME),
{Proc, Node} ! {route, From, To, Packet}
end,
{noreply, State};
handle_info({room_destroyed, RoomHost, Pid}, State) ->
@ -277,10 +306,7 @@ handle_info({room_destroyed, RoomHost, Pid}, State) ->
mnesia:delete_object(#muc_online_room{name_host = RoomHost,
pid = Pid})
end,
mnesia:transaction(F),
{noreply, State};
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
mnesia:sync_dirty(F),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
@ -293,6 +319,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
ejabberd_router:unregister_route(State#state.host),
ok.
@ -532,17 +559,22 @@ load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
lists:foreach(
fun(R) ->
{Room, Host} = R#muc_room.name_host,
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
{ok, Pid} = mod_muc_room:start(
Host,
ServerHost,
Access,
Room,
HistorySize,
RoomShaper,
R#muc_room.opts),
register_room(Host, Room, Pid);
case ejabberd_cluster:get_node({Room, Host}) of
Node when Node == node() ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
{ok, Pid} = mod_muc_room:start(
Host,
ServerHost,
Access,
Room,
HistorySize,
RoomShaper,
R#muc_room.opts),
register_room(Host, Room, Pid);
_ ->
ok
end;
_ ->
ok
end
@ -568,11 +600,10 @@ start_new_room(Host, ServerHost, Access, Room,
register_room(Host, Room, Pid) ->
F = fun() ->
mnesia:write(#muc_online_room{name_host = {Room, Host},
pid = Pid})
end,
mnesia:transaction(F).
mnesia:write(#muc_online_room{name_host = {Room, Host},
pid = Pid})
end,
mnesia:sync_dirty(F).
iq_disco_info(Lang) ->
[{xmlelement, "identity",
@ -601,7 +632,7 @@ iq_disco_items(Host, From, Lang, none) ->
_ ->
false
end
end, get_vh_rooms(Host));
end, get_vh_rooms_all_nodes(Host));
iq_disco_items(Host, From, Lang, Rsm) ->
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
@ -621,19 +652,9 @@ iq_disco_items(Host, From, Lang, Rsm) ->
end, Rooms) ++ RsmOut.
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
AllRooms = lists:sort(get_vh_rooms(Host)),
AllRooms = get_vh_rooms_all_nodes(Host),
Count = erlang:length(AllRooms),
Guard = case Direction of
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
_ -> [{'==', {element, 2, '$1'}, Host}]
end,
L = lists:sort(
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
Guard,
['$_']}])),
L = get_vh_rooms_direction(Direction, I, Index, AllRooms),
L2 = if
Index == undefined andalso Direction == before ->
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
@ -656,6 +677,27 @@ get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
{L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
end.
get_vh_rooms_direction(_Direction, _I, Index, AllRooms) when Index =/= undefined ->
AllRooms;
get_vh_rooms_direction(aft, I, _Index, AllRooms) ->
{_Before, After} =
lists:splitwith(
fun(#muc_online_room{name_host = {Na, _}}) ->
Na < I end, AllRooms),
case After of
[] -> [];
[#muc_online_room{name_host = {I, _Host}} | AfterTail] -> AfterTail;
_ -> After
end;
get_vh_rooms_direction(before, I, _Index, AllRooms) when I =/= []->
{Before, _} =
lists:splitwith(
fun(#muc_online_room{name_host = {Na, _}}) ->
Na < I end, AllRooms),
Before;
get_vh_rooms_direction(_Direction, _I, _Index, AllRooms) ->
AllRooms.
%% @doc Return the position of desired room in the list of rooms.
%% The room must exist in the list. The count starts in 0.
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
@ -813,7 +855,22 @@ broadcast_service_message(Host, Msg) ->
fun(#muc_online_room{pid = Pid}) ->
gen_fsm:send_all_state_event(
Pid, {service_message, Msg})
end, get_vh_rooms(Host)).
end, get_vh_rooms_all_nodes(Host)).
get_vh_rooms_all_nodes(Host) ->
Rooms = lists:foldl(
fun(Node, Acc) when Node == node() ->
get_vh_rooms(Host) ++ Acc;
(Node, Acc) ->
case catch rpc:call(Node, ?MODULE, get_vh_rooms,
[Host], 5000) of
Res when is_list(Res) ->
Res ++ Acc;
_ ->
Acc
end
end, [], ejabberd_cluster:get_nodes()),
lists:ukeysort(#muc_online_room.name_host, Rooms).
get_vh_rooms(Host) ->
mnesia:dirty_select(muc_online_room,
@ -821,39 +878,18 @@ get_vh_rooms(Host) ->
[{'==', {element, 2, '$1'}, Host}],
['$_']}]).
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
muc_online_room,
[{#muc_online_room{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
clean_table_from_bad_node(Node, Host) ->
F = fun() ->
Es = mnesia:select(
muc_online_room,
[{#muc_online_room{pid = '$1',
name_host = {'_', Host},
_ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
update_tables(Host) ->
update_muc_room_table(Host),
update_muc_registered_table(Host).
update_muc_online_table() ->
case catch mnesia:table_info(muc_online_room, local_content) of
false ->
mnesia:delete_table(muc_online_room);
_ ->
ok
end.
update_muc_room_table(Host) ->
Fields = record_info(fields, muc_room),
case mnesia:table_info(muc_room, attributes) of

View File

@ -73,11 +73,11 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
Proc = get_proc_name(Host),
gen_server:start_link(Proc, ?MODULE, [Host, Opts], []).
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc = get_proc_name(Host),
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
@ -88,7 +88,7 @@ start(Host, Opts) ->
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
Proc = get_proc_name(Host),
gen_server:call(Proc, stop),
supervisor:delete_child(ejabberd_sup, Proc).
@ -950,7 +950,8 @@ get_room_state(RoomPid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, get_state),
R.
get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME).
get_proc_name(Host) ->
{global, gen_mod:get_module_proc(Host, ?PROCNAME)}.
calc_hour_offset(TimeHere) ->
TimeZero = calendar:now_to_universal_time(now()),

View File

@ -27,14 +27,19 @@
-module(mod_muc_room).
-author('alexey@process-one.net').
-behaviour(gen_fsm).
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
%% External exports
-export([start_link/9,
start_link/7,
start_link/2,
start/9,
start/7,
start/2,
migrate/3,
route/4]).
%% gen_fsm callbacks
@ -44,6 +49,7 @@
handle_sync_event/4,
handle_info/3,
terminate/3,
print_state/1,
code_change/4]).
-include("ejabberd.hrl").
@ -63,16 +69,12 @@
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
-define(SUPERVISOR_START,
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts],
?FSMOPTS)).
-define(SUPERVISOR_START(Args),
?GEN_FSM:start(?MODULE, Args, ?FSMOPTS)).
-else.
-define(SUPERVISOR_START,
-define(SUPERVISOR_START(Args),
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
supervisor:start_child(
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts])).
supervisor:start_child(Supervisor, Args)).
-endif.
%%%----------------------------------------------------------------------
@ -80,7 +82,8 @@
%%%----------------------------------------------------------------------
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts) ->
?SUPERVISOR_START.
?SUPERVISOR_START([Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts]).
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
@ -88,16 +91,26 @@ start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Opts]).
start(StateName, StateData) ->
ServerHost = StateData#state.server_host,
?SUPERVISOR_START([StateName, StateData]).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts) ->
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts],
?FSMOPTS).
?GEN_FSM:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts],
?FSMOPTS).
?GEN_FSM:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts],
?FSMOPTS).
start_link(StateName, StateData) ->
?GEN_FSM:start_link(?MODULE, [StateName, StateData], ?FSMOPTS).
migrate(FsmRef, Node, After) ->
?GEN_FSM:send_all_state_event(FsmRef, {migrate, Node, After}).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
@ -139,7 +152,11 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
jid = jlib:make_jid(Room, Host, ""),
room_shaper = Shaper}),
add_to_log(room_existence, started, State),
{ok, normal_state, State}.
{ok, normal_state, State};
init([StateName, #state{room = Room, host = Host} = StateData]) ->
process_flag(trap_exit, true),
mod_muc:register_room(Host, Room, self()),
{ok, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@ -580,6 +597,9 @@ handle_event(destroy, StateName, StateData) ->
handle_event({set_affiliations, Affiliations}, StateName, StateData) ->
{next_state, StateName, StateData#state{affiliations = Affiliations}};
handle_event({migrate, Node, After}, StateName, StateData) when Node /= node() ->
{migrate, StateData,
{Node, ?MODULE, start, [StateName, StateData]}, After * 2};
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
@ -612,6 +632,9 @@ handle_sync_event(_Event, _From, StateName, StateData) ->
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
print_state(StateData) ->
StateData.
%%----------------------------------------------------------------------
%% Func: handle_info/3
%% Returns: {next_state, NextStateName, NextStateData} |
@ -701,6 +724,13 @@ handle_info(_Info, StateName, StateData) ->
%% Purpose: Shutdown the fsm
%% Returns: any
%%----------------------------------------------------------------------
terminate({migrated, Clone}, _StateName, StateData) ->
?INFO_MSG("Migrating room ~s@~s to ~p on node ~p",
[StateData#state.room, StateData#state.host,
Clone, node(Clone)]),
mod_muc:room_destroyed(StateData#state.host, StateData#state.room,
self(), StateData#state.server_host),
ok;
terminate(Reason, _StateName, StateData) ->
?INFO_MSG("Stopping MUC room ~s@~s",
[StateData#state.room, StateData#state.host]),
@ -739,7 +769,7 @@ terminate(Reason, _StateName, StateData) ->
%%%----------------------------------------------------------------------
route(Pid, From, ToNick, Packet) ->
gen_fsm:send_event(Pid, {route, From, ToNick, Packet}).
?GEN_FSM:send_event(Pid, {route, From, ToNick, Packet}).
process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
StateData) ->
@ -1625,13 +1655,12 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
From, Err),
StateData;
captcha_required ->
ID = randoms:get_string(),
SID = xml:get_attr_s("id", Attrs),
RoomJID = StateData#state.jid,
To = jlib:jid_replace_resource(RoomJID, Nick),
case ejabberd_captcha:create_captcha(
ID, SID, RoomJID, To, Lang, From) of
{ok, CaptchaEls} ->
SID, RoomJID, To, Lang, From) of
{ok, ID, CaptchaEls} ->
MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls},
Robots = ?DICT:store(From,
{Nick, Packet}, StateData#state.robots),

View File

@ -30,16 +30,14 @@
-behaviour(gen_mod).
-export([start/2,
loop/1,
init/1,
stop/1,
store_packet/3,
resend_offline_messages/2,
pop_offline_messages/3,
get_sm_features/5,
remove_expired_messages/0,
remove_old_messages/1,
remove_user/2,
get_queue_length/2,
webadmin_page/3,
webadmin_user/4,
webadmin_user_parse_query/5,
@ -55,9 +53,6 @@
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
start(Host, Opts) ->
mnesia:create_table(offline_msg,
[{disc_only_copies, [node()]},
@ -72,10 +67,6 @@ start(Host, Opts) ->
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
@ -83,19 +74,23 @@ start(Host, Opts) ->
ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50),
ejabberd_hooks:add(count_offline_messages, Host,
?MODULE, count_offline_messages, 50),
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
?MODULE, count_offline_messages, 50),
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, loop, [AccessMaxOfflineMsgs])).
spawn(?MODULE, init, [MaxOfflineMsgs])).
loop(AccessMaxOfflineMsgs) ->
%% MaxOfflineMsgs is either infinity of integer > 0
init(infinity) ->
loop(infinity);
init(MaxOfflineMsgs)
when is_integer(MaxOfflineMsgs), MaxOfflineMsgs > 0 ->
loop(MaxOfflineMsgs).
loop(MaxOfflineMsgs) ->
receive
#offline_msg{us=US} = Msg ->
Msgs = receive_all(US, [Msg]),
Len = length(Msgs),
{User, Host} = US,
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
User, Host),
F = fun() ->
%% Only count messages if needed:
Count = if MaxOfflineMsgs =/= infinity ->
@ -121,18 +116,9 @@ loop(AccessMaxOfflineMsgs) ->
end
end,
mnesia:transaction(F),
loop(AccessMaxOfflineMsgs);
loop(MaxOfflineMsgs);
_ ->
loop(AccessMaxOfflineMsgs)
end.
%% Function copied from ejabberd_sm.erl:
get_max_user_messages(AccessRule, LUser, Host) ->
case acl:match_rule(
Host, AccessRule, jlib:make_jid(LUser, Host, "")) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_MESSAGES
loop(MaxOfflineMsgs)
end.
receive_all(US, Msgs) ->
@ -153,8 +139,6 @@ stop(Host) ->
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
@ -165,27 +149,12 @@ stop(Host) ->
exit(whereis(Proc), stop),
{wait, Proc}.
get_sm_features(Acc, _From, _To, "", _Lang) ->
Feats = case Acc of
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
store_packet(From, To, Packet) ->
Type = xml:get_tag_attr_s("type", Packet),
if
(Type /= "error") and (Type /= "groupchat") and
(Type /= "headline") ->
case check_event_chatstates(From, To, Packet) of
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser, lserver = LServer} = To,
TimeStamp = now(),
@ -206,22 +175,12 @@ store_packet(From, To, Packet) ->
ok
end.
%% Check if the packet has any content about XEP-0022 or XEP-0085
check_event_chatstates(From, To, Packet) ->
check_event(From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = Packet,
case find_x_event_chatstates(Els, {false, false, false}) of
%% There wasn't any x:event or chatstates subelements
{false, false, _} ->
case find_x_event(Els) of
false ->
true;
%% There a chatstates subelement and other stuff, but no x:event
{false, CEl, true} when CEl /= false ->
true;
%% There was only a subelement: a chatstates
{false, CEl, false} when CEl /= false ->
%% Don't allow offline storage
false;
%% There was an x:event element, and maybe also other stuff
{El, _, _} when El /= false ->
El ->
case xml:get_subtag(El, "id") of
false ->
case xml:get_subtag(El, "offline") of
@ -249,19 +208,16 @@ check_event_chatstates(From, To, Packet) ->
end
end.
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
find_x_event_chatstates([], Res) ->
Res;
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
find_x_event_chatstates(Els, Res);
find_x_event_chatstates([El | Els], {A, B, C}) ->
find_x_event([]) ->
false;
find_x_event([{xmlcdata, _} | Els]) ->
find_x_event(Els);
find_x_event([El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EVENT ->
find_x_event_chatstates(Els, {El, B, C});
?NS_CHATSTATES ->
find_x_event_chatstates(Els, {A, El, C});
El;
_ ->
find_x_event_chatstates(Els, {A, B, true})
find_x_event(Els)
end.
find_x_expire(_, []) ->
@ -310,13 +266,6 @@ resend_offline_messages(User, Server) ->
{xmlelement, Name, Attrs,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}}
end,
@ -346,14 +295,7 @@ pop_offline_messages(Ls, User, Server) ->
{xmlelement, Name, Attrs,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}}
end,
lists:filter(
@ -370,7 +312,6 @@ pop_offline_messages(Ls, User, Server) ->
Ls
end.
remove_expired_messages() ->
TimeStamp = now(),
F = fun() ->
@ -533,9 +474,8 @@ webadmin_page(Acc, _, _) -> Acc.
user_queue(User, Server, Query, Lang) ->
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
Res = user_queue_parse_query(US, Query),
MsgsAll = lists:keysort(#offline_msg.timestamp,
mnesia:dirty_read({offline_msg, US})),
Msgs = get_messages_subset(User, Server, MsgsAll),
Msgs = lists:keysort(#offline_msg.timestamp,
mnesia:dirty_read({offline_msg, US})),
FMsgs =
lists:map(
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
@ -617,32 +557,9 @@ user_queue_parse_query(US, Query) ->
us_to_list({User, Server}) ->
jlib:jid_to_string({User, Server, ""}).
get_queue_length(User, Server) ->
length(mnesia:dirty_read({offline_msg, {User, Server}})).
get_messages_subset(User, Host, MsgsAll) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
max_user_offline_messages),
MaxOfflineMsgs = case get_max_user_messages(Access, User, Host) of
Number when is_integer(Number) -> Number;
_ -> 100
end,
Length = length(MsgsAll),
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
get_messages_subset2(Max, Length, MsgsAll) when Length =< Max*2 ->
MsgsAll;
get_messages_subset2(Max, Length, MsgsAll) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
NoJID = jlib:make_jid("...", "...", ""),
IntermediateMsg = #offline_msg{timestamp = now(), from = NoJID, to = NoJID,
packet = {xmlelement, "...", [], []}},
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
QueueLen = get_queue_length(jlib:nodeprep(User), jlib:nameprep(Server)),
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
QueueLen = length(mnesia:dirty_read({offline_msg, US})),
FQueueLen = [?AC("queue/",
integer_to_list(QueueLen))],
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].

View File

@ -32,13 +32,11 @@
-export([count_offline_messages/2]).
-export([start/2,
loop/2,
init/2,
stop/1,
store_packet/3,
pop_offline_messages/3,
get_sm_features/5,
remove_user/2,
get_queue_length/2,
webadmin_page/3,
webadmin_user/4,
webadmin_user_parse_query/5,
@ -54,9 +52,6 @@
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
start(Host, Opts) ->
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, store_packet, 50),
@ -66,10 +61,6 @@ start(Host, Opts) ->
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
@ -78,17 +69,22 @@ start(Host, Opts) ->
?MODULE, webadmin_user_parse_query, 50),
ejabberd_hooks:add(count_offline_messages, Host,
?MODULE, count_offline_messages, 50),
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
MaxOfflineMsgs = gen_mod:get_opt(user_max_messages, Opts, infinity),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
spawn(?MODULE, loop, [Host, AccessMaxOfflineMsgs])).
spawn(?MODULE, init, [Host, MaxOfflineMsgs])).
loop(Host, AccessMaxOfflineMsgs) ->
%% MaxOfflineMsgs is either infinity of integer > 0
init(Host, infinity) ->
loop(Host, infinity);
init(Host, MaxOfflineMsgs)
when is_integer(MaxOfflineMsgs), MaxOfflineMsgs > 0 ->
loop(Host, MaxOfflineMsgs).
loop(Host, MaxOfflineMsgs) ->
receive
#offline_msg{user = User} = Msg ->
Msgs = receive_all(User, [Msg]),
Len = length(Msgs),
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
User, Host),
%% Only count existing messages if needed:
Count = if MaxOfflineMsgs =/= infinity ->
@ -116,17 +112,11 @@ loop(Host, AccessMaxOfflineMsgs) ->
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp),
utc,
jlib:make_jid("", Host, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp))]},
XML =
ejabberd_odbc:escape(
xml:element_to_binary(Packet)),
lists:flatten(
xml:element_to_string(Packet))),
odbc_queries:add_spool_sql(Username, XML)
end, Msgs),
case catch odbc_queries:add_spool(Host, Query) of
@ -138,18 +128,9 @@ loop(Host, AccessMaxOfflineMsgs) ->
ok
end
end,
loop(Host, AccessMaxOfflineMsgs);
loop(Host, MaxOfflineMsgs);
_ ->
loop(Host, AccessMaxOfflineMsgs)
end.
%% Function copied from ejabberd_sm.erl:
get_max_user_messages(AccessRule, LUser, Host) ->
case acl:match_rule(
Host, AccessRule, jlib:make_jid(LUser, Host, "")) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_MESSAGES
loop(Host, MaxOfflineMsgs)
end.
receive_all(Username, Msgs) ->
@ -170,8 +151,6 @@ stop(Host) ->
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
@ -182,27 +161,12 @@ stop(Host) ->
exit(whereis(Proc), stop),
ok.
get_sm_features(Acc, _From, _To, "", _Lang) ->
Feats = case Acc of
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
store_packet(From, To, Packet) ->
Type = xml:get_tag_attr_s("type", Packet),
if
(Type /= "error") and (Type /= "groupchat") and
(Type /= "headline") ->
case check_event_chatstates(From, To, Packet) of
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser} = To,
TimeStamp = now(),
@ -223,22 +187,12 @@ store_packet(From, To, Packet) ->
ok
end.
%% Check if the packet has any content about XEP-0022 or XEP-0085
check_event_chatstates(From, To, Packet) ->
check_event(From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = Packet,
case find_x_event_chatstates(Els, {false, false, false}) of
%% There wasn't any x:event or chatstates subelements
{false, false, _} ->
case find_x_event(Els) of
false ->
true;
%% There a chatstates subelement and other stuff, but no x:event
{false, CEl, true} when CEl /= false ->
true;
%% There was only a subelement: a chatstates
{false, CEl, false} when CEl /= false ->
%% Don't allow offline storage
false;
%% There was an x:event element, and maybe also other stuff
{El, _, _} when El /= false ->
El ->
case xml:get_subtag(El, "id") of
false ->
case xml:get_subtag(El, "offline") of
@ -260,25 +214,22 @@ check_event_chatstates(From, To, Packet) ->
{xmlelement, "offline", [], []}]}]
}),
true
end;
end;
_ ->
false
end
end.
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
find_x_event_chatstates([], Res) ->
Res;
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
find_x_event_chatstates(Els, Res);
find_x_event_chatstates([El | Els], {A, B, C}) ->
find_x_event([]) ->
false;
find_x_event([{xmlcdata, _} | Els]) ->
find_x_event(Els);
find_x_event([El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EVENT ->
find_x_event_chatstates(Els, {El, B, C});
?NS_CHATSTATES ->
find_x_event_chatstates(Els, {A, El, C});
El;
_ ->
find_x_event_chatstates(Els, {A, B, true})
find_x_event(Els)
end.
find_x_expire(_, []) ->
@ -378,7 +329,7 @@ user_queue(User, Server, Query, Lang) ->
Username = ejabberd_odbc:escape(LUser),
US = {LUser, LServer},
Res = user_queue_parse_query(Username, LServer, Query),
MsgsAll = case catch ejabberd_odbc:sql_query(
Msgs = case catch ejabberd_odbc:sql_query(
LServer,
["select username, xml from spool"
" where username='", Username, "'"
@ -396,7 +347,6 @@ user_queue(User, Server, Query, Lang) ->
_ ->
[]
end,
Msgs = get_messages_subset(User, Server, MsgsAll),
FMsgs =
lists:map(
fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
@ -483,8 +433,11 @@ user_queue_parse_query(Username, LServer, Query) ->
us_to_list({User, Server}) ->
jlib:jid_to_string({User, Server, ""}).
get_queue_length(Username, LServer) ->
case catch ejabberd_odbc:sql_query(
webadmin_user(Acc, User, Server, Lang) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
QueueLen = case catch ejabberd_odbc:sql_query(
LServer,
["select count(*) from spool"
" where username='", Username, "';"]) of
@ -492,32 +445,7 @@ get_queue_length(Username, LServer) ->
SCount;
_ ->
0
end.
get_messages_subset(User, Host, MsgsAll) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
max_user_offline_messages),
MaxOfflineMsgs = case get_max_user_messages(Access, User, Host) of
Number when is_integer(Number) -> Number;
_ -> 100
end,
Length = length(MsgsAll),
get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll).
get_messages_subset2(Max, Length, MsgsAll) when Length =< Max*2 ->
MsgsAll;
get_messages_subset2(Max, Length, MsgsAll) ->
FirstN = Max,
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2),
IntermediateMsg = {xmlelement, "...", [], []},
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
QueueLen = get_queue_length(Username, LServer),
end,
FQueueLen = [?AC("queue/", QueueLen)],
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].

View File

@ -51,7 +51,7 @@
handle_info/2, code_change/3]).
%% Hook callbacks
-export([iq_ping/3, user_online/3, user_offline/3, user_send/3]).
-export([iq_ping/3, user_online/3, user_offline/3, user_send/4]).
-record(state, {host = "",
send_pings = ?DEFAULT_SEND_PINGS,
@ -107,6 +107,8 @@ init([Host, Opts]) ->
?MODULE, user_online, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
?MODULE, user_offline, 100),
ejabberd_hooks:add(sm_remove_migrated_connection_hook, Host,
?MODULE, user_offline, 100),
ejabberd_hooks:add(user_send_packet, Host,
?MODULE, user_send, 100);
_ ->
@ -121,6 +123,8 @@ init([Host, Opts]) ->
terminate(_Reason, #state{host = Host}) ->
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
?MODULE, user_offline, 100),
ejabberd_hooks:delete(sm_remove_migrated_connection_hook, Host,
?MODULE, user_offline, 100),
ejabberd_hooks:delete(sm_register_connection_hook, Host,
?MODULE, user_online, 100),
ejabberd_hooks:delete(user_send_packet, Host,
@ -193,7 +197,7 @@ user_online(_SID, JID, _Info) ->
user_offline(_SID, JID, _Info) ->
stop_ping(JID#jid.lserver, JID).
user_send(JID, _From, _Packet) ->
user_send(_DebugFlag, JID, _From, _Packet) ->
start_ping(JID#jid.lserver, JID).
%%====================================================================

View File

@ -392,12 +392,13 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
NeedDb = is_list_needdb(List),
ejabberd_router:route(
jlib:make_jid(LUser, LServer, ""),
jlib:make_jid(LUser, LServer, ""),
{xmlelement, "broadcast", [],
[{privacy_list,
#userlist{name = Name, list = List},
#userlist{name = Name, list = List, needdb = NeedDb},
Name}]}),
Res;
_ ->

View File

@ -19,6 +19,8 @@
%%%
%%%----------------------------------------------------------------------
-define(mod_privacy_hrl, true).
-record(privacy, {us,
default = none,
lists = []}).

View File

@ -392,12 +392,13 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
NeedDb = is_list_needdb(List),
ejabberd_router:route(
jlib:make_jid(LUser, LServer, ""),
jlib:make_jid(LUser, LServer, ""),
{xmlelement, "broadcast", [],
[{privacy_list,
#userlist{name = Name, list = List},
#userlist{name = Name, list = List, needdb = NeedDb},
Name}]}),
Res;
_ ->

View File

@ -71,7 +71,9 @@ start_link(Host, Opts) ->
gen_server:start_link({local, Proc}, ?MODULE, [Opts], []).
init([Opts]) ->
update_tables(),
mnesia:create_table(bytestream, [{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, bytestream)}]),
mnesia:add_table_copy(bytestream, node(), ram_copies),
MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
@ -179,3 +181,11 @@ activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) ->
_ ->
error
end.
update_tables() ->
case catch mnesia:table_info(bytestream, local_content) of
false ->
mnesia:delete_table(bytestream);
_ ->
ok
end.

View File

@ -78,6 +78,7 @@
%% exports for console debug manual use
-export([create_node/5,
create_node/7,
delete_node/3,
subscribe_node/5,
unsubscribe_node/5,
@ -766,13 +767,15 @@ out_subscription(User, Server, JID, subscribed) ->
[] -> user_resources(PUser, PServer);
_ -> [PResource]
end,
presence(Server, {presence, PUser, PServer, PResources, Owner});
presence(Server, {presence, PUser, PServer, PResources, Owner}),
true;
out_subscription(_,_,_,_) ->
ok.
true.
in_subscription(_, User, Server, Owner, unsubscribed, _) ->
unsubscribe_user(jlib:make_jid(User, Server, ""), Owner);
unsubscribe_user(jlib:make_jid(User, Server, ""), Owner),
true;
in_subscription(_, _, _, _, _, _) ->
ok.
true.
unsubscribe_user(Entity, Owner) ->
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),

View File

@ -76,8 +76,7 @@ terminate(Host, ServerHost) ->
node_hometree_odbc:terminate(Host, ServerHost).
options() ->
[{node_type, flat},
{deliver_payloads, true},
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},

View File

@ -86,7 +86,6 @@ terminate(Host, ServerHost) ->
options() ->
[{odbc, true},
{node_type, pep},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},

View File

@ -0,0 +1,38 @@
-module(pubsub_clean).
-define(TIMEOUT, 1000*600). % 1 minute
-export([start/0, loop/0, purge/0, offline/1]).
start() ->
Pid = spawn(?MODULE, loop, []),
register(pubsub_clean, Pid),
Pid.
loop() ->
receive
purge -> purge()
after ?TIMEOUT -> purge()
end,
loop().
purge() ->
Sessions = lists:sum([mnesia:table_info(session,size)|[rpc:call(N,mnesia,table_info,[session,size]) || N <- nodes()]]),
Subscriptions = mnesia:table_info(pubsub_state,size),
if Subscriptions > Sessions + 500 ->
lists:foreach(fun(K) ->
[N]=mnesia:dirty_read({pubsub_node, K}),
I=element(3,N),
lists:foreach(fun(JID) ->
mnesia:dirty_delete({pubsub_state, {JID, I}})
end, offline(pubsub_debug:subscribed(I)))
end, mnesia:dirty_all_keys(pubsub_node));
true ->
ok
end.
offline(Jids) ->
lists:filter(fun({U,S,""}) -> ejabberd_sm:get_user_resources(U,S) == [];
({U,S,R}) -> not lists:member(R,ejabberd_sm:get_user_resources(U,S))
end, Jids).
%%ejabberd_cluster:get_node({LUser, LServer})

View File

@ -0,0 +1,113 @@
-module(pubsub_debug).
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-compile(export_all).
nodeid(Host, Node) ->
case mnesia:dirty_read({pubsub_node, {Host, Node}}) of
[N] -> nodeid(N);
_ -> 0
end.
nodeid(N) -> N#pubsub_node.id.
nodeids() -> [nodeid(Host, Node) || {Host, Node} <- mnesia:dirty_all_keys(pubsub_node)].
nodeids_by_type(Type) -> [nodeid(N) || N <- mnesia:dirty_match_object(#pubsub_node{type=Type, _='_'})].
nodeids_by_option(Key, Value) -> [nodeid(N) || N <- mnesia:dirty_match_object(#pubsub_node{_='_'}), lists:member({Key, Value}, N#pubsub_node.options)].
nodeids_by_owner(JID) -> [nodeid(N) || N <- mnesia:dirty_match_object(#pubsub_node{_='_'}), lists:member(JID, N#pubsub_node.owners)].
nodes_by_id(I) -> mnesia:dirty_match_object(#pubsub_node{id=I, _='_'}).
nodes() -> [element(2, element(2, N)) || N <- mnesia:dirty_match_object(#pubsub_node{_='_'})].
state(JID, NodeId) ->
case mnesia:dirty_read({pubsub_state, {JID, NodeId}}) of
[S] -> S;
_ -> undefined
end.
states(NodeId) -> mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, _='_'}).
stateid(S) -> element(1, S#pubsub_state.stateid).
stateids(NodeId) -> [stateid(S) || S <- states(NodeId)].
states_by_jid(JID) -> mnesia:dirty_match_object(#pubsub_state{stateid={JID, '_'}, _='_'}).
item(ItemId, NodeId) ->
case mnesia:dirty_read({pubsub_item, {ItemId, NodeId}}) of
[I] -> I;
_ -> undefined
end.
items(NodeId) -> mnesia:dirty_match_object(#pubsub_item{itemid={'_', NodeId}, _='_'}).
itemid(I) -> element(1, I#pubsub_item.itemid).
itemids(NodeId) -> [itemid(I) || I <- items(NodeId)].
items_by_id(ItemId) -> mnesia:dirty_match_object(#pubsub_item{itemid={ItemId, '_'}, _='_'}).
affiliated(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.affiliation=/=none].
subscribed(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.subscriptions=/=[]].
%subscribed(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.subscription=/=none]. %% old record
owners(NodeId) -> [stateid(S) || S <- mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, affiliation=owner, _='_'})].
orphan_items(NodeId) ->
itemids(NodeId) -- lists:foldl(fun(S, A) -> A++S#pubsub_state.items end, [], mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, _='_'})).
newer_items(NodeId, Seconds) ->
Now = calendar:universal_time(),
Oldest = calendar:seconds_to_daystime(Seconds),
[itemid(I) || I <- items(NodeId), calendar:time_difference(calendar:now_to_universal_time(element(1, I#pubsub_item.modification)), Now) < Oldest].
older_items(NodeId, Seconds) ->
Now = calendar:universal_time(),
Oldest = calendar:seconds_to_daystime(Seconds),
[itemid(I) || I <- items(NodeId), calendar:time_difference(calendar:now_to_universal_time(element(1, I#pubsub_item.modification)), Now) > Oldest].
orphan_nodes() -> [I || I <- nodeids(), owners(I)==[]].
duplicated_nodes() -> L = nodeids(), lists:usort(L -- lists:seq(1, lists:max(L))).
node_options(NodeId) ->
[N] = mnesia:dirty_match_object(#pubsub_node{id=NodeId, _='_'}),
N#pubsub_node.options.
update_node_options(Key, Value, NodeId) ->
[N] = mnesia:dirty_match_object(#pubsub_node{id=NodeId, _='_'}),
NewOptions = lists:keyreplace(Key, 1, N#pubsub_node.options, {Key, Value}),
mnesia:dirty_write(N#pubsub_node{options = NewOptions}).
check() ->
mnesia:transaction(fun() ->
case mnesia:read({pubsub_index, node}) of
[Idx] ->
Free = Idx#pubsub_index.free,
Last = Idx#pubsub_index.last,
Allocated = lists:seq(1, Last) -- Free,
NodeIds = mnesia:foldl(fun(N,A) -> [nodeid(N)|A] end, [], pubsub_node),
StateIds = lists:usort(mnesia:foldl(fun(S,A) -> [element(2, S#pubsub_state.stateid)|A] end, [], pubsub_state)),
ItemIds = lists:usort(mnesia:foldl(fun(I,A) -> [element(2, I#pubsub_item.itemid)|A] end, [], pubsub_item)),
BadNodeIds = NodeIds -- Allocated,
BadStateIds = StateIds -- NodeIds,
BadItemIds = ItemIds -- NodeIds,
Lost = Allocated -- NodeIds,
[{bad_nodes, [N#pubsub_node.nodeid || N <- lists:flatten([mnesia:match_object(#pubsub_node{id=I, _='_'}) || I <- BadNodeIds])]},
{bad_states, lists:foldl(fun(N,A) -> A++[{I,N} || I <- stateids(N)] end, [], BadStateIds)},
{bad_items, lists:foldl(fun(N,A) -> A++[{I,N} || I <- itemids(N)] end, [], BadItemIds)},
{lost_idx, Lost},
{orphaned, [I || I <- NodeIds, owners(I)==[]]},
{duplicated, lists:usort(NodeIds -- lists:seq(1, lists:max(NodeIds)))}];
_ ->
no_index
end
end).
rebuild_index() ->
mnesia:transaction(fun() ->
NodeIds = mnesia:foldl(fun(N,A) -> [nodeid(N)|A] end, [], pubsub_node),
Last = lists:max(NodeIds),
Free = lists:seq(1, Last) -- NodeIds,
mnesia:write(#pubsub_index{index = node, last = Last, free = Free})
end).
pep_subscriptions(LUser, LServer, LResource) ->
case ejabberd_sm:get_session_pid({LUser, LServer, LResource}) of
C2SPid when is_pid(C2SPid) ->
case catch ejabberd_c2s:get_subscribed(C2SPid) of
Contacts when is_list(Contacts) ->
lists:map(fun({U, S, _}) ->
io_lib:format("~s@~s", [U, S])
end, Contacts);
_ ->
[]
end;
_ ->
[]
end.

View File

@ -31,8 +31,8 @@
-export([start/2,
stop/1,
log_user_send/3,
log_user_receive/4]).
log_user_send/4,
log_user_receive/5]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -51,10 +51,10 @@ stop(Host) ->
?MODULE, log_user_receive, 50),
ok.
log_user_send(From, To, Packet) ->
log_user_send(_DebugFlag, From, To, Packet) ->
log_packet(From, To, Packet, From#jid.lserver).
log_user_receive(_JID, From, To, Packet) ->
log_user_receive(_DebugFlag, _JID, From, To, Packet) ->
log_packet(From, To, Packet, To#jid.lserver).
@ -74,4 +74,3 @@ log_packet(From, To, {xmlelement, Name, Attrs, Els}, Host) ->
luser = "", lserver = Logger, lresource = ""},
{xmlelement, "route", [], [FixedPacket]})
end, Loggers).

260
src/mod_support.erl Normal file
View File

@ -0,0 +1,260 @@
%%% ====================================================================
%%% This software is copyright 2006-2010, ProcessOne.
%%%
%%% mod_support
%%% allow automatic build of support archive to be sent to Process-One
%%%
%%% @copyright 2006-2010 ProcessOne
%%% @author Christophe Romain <christophe.romain@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
-module(mod_support).
-author('christophe.romain@process-one.net').
-behaviour(gen_mod).
%-behaviour(gen_server).
% module functions
-export([start/2,stop/1,is_loaded/0,loop/1,dump/0]).
-compile(export_all).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("licence.hrl").
-include_lib("kernel/include/file.hrl").
-define(LOG_FETCH_SIZE, 1000000).
-define(RPC_TIMEOUT, 10000). % 10
-define(MAX_FILE_SIZE, 2147483648). %%2Gb
start(Host, Opts) ->
case ?IS_VALID of
true ->
case gen_mod:get_opt(dump_freq, Opts, 0) of
0 -> no_dump;
Freq -> spawn(?MODULE, loop, [Freq*60000])
end,
ok;
false ->
not_started
end.
stop(Host) ->
ok.
is_loaded() ->
ok.
loop(Timeout) ->
receive
quit -> ok
after Timeout ->
Dump = dump(),
BaseName = get_base_name(),
%%{Data,EjabberdLog,SaslLog,ECrash} = Dump,
write_logs(tuple_to_list(Dump),BaseName,["_memory.bin",
"_ejabberd.log.gz",
"_sasl.log.gz",
"_erlang_crash_dump.log.gz"]),
loop(Timeout)
end.
get_base_name() ->
{{Y,M,D},{Hr,Mn,_Sc}} = calendar:local_time(),
case os:getenv("EJABBERD_LOG_PATH") of
false ->
filename:join(filename:dirname(filename:absname("")),
lists:flatten(io_lib:format("~b~b~b~b~b",[Y,M,D,Hr,Mn])));
Path ->
filename:join(filename:dirname(Path),
lists:flatten(io_lib:format("~b~b~b~b~b",[Y,M,D,Hr,Mn])))
end.
write_logs([BinaryData|T],BaseName,[Filename|Filenames]) ->
Log = BaseName++Filename,
file:write_file(Log, BinaryData),
write_logs(T,BaseName,Filenames);
write_logs([],BaseName,_)-> ok.
dump() ->
Dump = lists:map(fun(LogFile) ->
Content = case file:open(LogFile,[read,raw]) of
{ok, IO} ->
Size = case file:read_file_info(LogFile) of
{ok, FileInfo} -> FileInfo#file_info.size;
_ -> ?LOG_FETCH_SIZE
end,
case Size>?MAX_FILE_SIZE of
true -> io_lib:format("File ~s is too big: ~p bytes.",[LogFile, Size]);
false ->
if Size>?LOG_FETCH_SIZE ->
file:position(IO, Size-?LOG_FETCH_SIZE),
case file:read(IO, ?LOG_FETCH_SIZE) of
{ok, Data1} -> Data1;
Error1 -> io_lib:format("can not read log file (~s): ~p",[LogFile, Error1])
end;
true ->
case file:read(IO, Size) of
{ok, Data2} -> Data2;
Error2 -> io_lib:format("can not read log file (~s): ~p",[LogFile, Error2])
end
end
end;
{error, Reason} ->
io_lib:format("can not open log file (~s): ~p",[LogFile, Reason])
end,
zlib:gzip(list_to_binary(Content))
end, [ejabberd_logs(), sasl_logs(), erl_crash()]),
NodeState = get_node_state(),
list_to_tuple([NodeState|Dump]).
ejabberd_logs() ->
LogPath = case application:get_env(log_path) of
{ok, Path} ->
Path;
undefined ->
case os:getenv("EJABBERD_LOG_PATH") of
false -> ?LOG_PATH;
Path -> Path
end
end.
sasl_logs() ->
case os:getenv("SASL_LOG_PATH") of
false -> filename:join([filename:dirname(ejabberd_logs()),"sasl.log"]);
Path -> Path
end.
erl_crash() ->
LogsDir = filename:dirname(ejabberd_logs()),
CrashDumpWildcard = filename:join([LogsDir,"erl_crash*dump"]),
FileName = case filelib:wildcard(CrashDumpWildcard) of
[Files] -> [LastFile|T] = lists:reverse([Files]),
LastFile;
_ -> case os:getenv("ERL_CRASH_DUMP") of
false -> "erl_crash.dump";
Path -> Path
end
end.
proc_info(Pid) ->
Info = process_info(Pid),
lists:map(fun(Elem) ->
List = proplists:get_value(Elem, Info),
{Elem, size(term_to_binary(List))}
end, [messages, dictionary])
++ [X || X <- Info,
lists:member(element(1,X),
[heap_size,stack_size,reductions,links,status,initial_call,current_function])].
environment() ->
{ok, KE} = application:get_key(kernel,env),
{ok, EE} = application:get_key(ejabberd,env),
Env = [{inetrc, os:getenv("ERL_INETRC")},
{sopath, os:getenv("EJABBERD_SO_PATH")},
{maxports, os:getenv("ERL_MAX_PORTS")},
{maxtables, os:getenv("ERL_MAX_ETS_TABLES")},
{crashdump, os:getenv("ERL_CRASH_DUMP")},
{archdir, os:getenv("ARCHDIR")},
{mnesia, mnesia:system_info(all)}],
Args = [{args, init:get_arguments()}, {plain, init:get_plain_arguments()}],
KE++EE++Env++Args.
memtop(N) ->
E = lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Tab) -> {Tab, ets:info(Tab,memory)} end, ets:all()))),N),
M = lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Tab) -> {Tab, mnesia:table_info(Tab,memory)} end, mnesia:system_info(tables)))),N),
E++M.
maxmsgqueue() ->
lists:max(lists:map(fun(Pid) -> proplists:get_value(message_queue_len,process_info(Pid)) end, erlang:processes())).
msgqueue(N) ->
lists:filter(fun(L) -> proplists:get_value(message_queue_len, L) > N
end, lists:map(fun(Pid) -> process_info(Pid) end, erlang:processes())).
%lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Pid) -> {E,L} = process_info(Pid, dictionary), {E,length(L)} end, erlang:processes()))), 10)
%%Entry point to invoke mod_support via command line.
%%Example: erl -sname debug@localhost -s mod_support report ejabberd@localhost
%%See issue #TECH-286.
report(Node) ->
[NodeId|T]=Node,
UploadResult = force_load_code_into_node(NodeId, ?MODULE),
case UploadResult of
ok -> NodeState = rpc:call(NodeId,mod_support,get_node_state,[],?RPC_TIMEOUT),
Dump = rpc:call(NodeId,mod_support,dump,[],?RPC_TIMEOUT),
BaseName = get_base_name(),
%%{Data,EjabberdLog,SaslLog,ECrash} = Dump,
write_logs(tuple_to_list(Dump),BaseName,["_memory.bin",
"_ejabberd.log.gz",
"_sasl.log.gz",
"_erlang_crash_dump.log.gz"]),
error_logger:info_msg("State in node ~p was written to log~n",[NodeId]),
error_logger:info_msg("Unloading module ~s from node ~p. ",[?MODULE,NodeId]),
force_unload_code_from_node(NodeId, ?MODULE);
_ -> error_logger:info_msg("Error uploading module ~s from node ~p~n",[?MODULE,NodeId])
end.
%%Load Module into the ejabberd Node specified.
force_load_code_into_node(Node, Module) ->
CodeFile = code:where_is_file(atom_to_list(Module)++".beam"),
case file:read_file(CodeFile) of
{ok, Code} ->
rpc:call(Node, code, purge, [Module], ?RPC_TIMEOUT),
rpc:call(Node, code, delete, [Module], ?RPC_TIMEOUT),
case rpc:call(Node, code, load_binary, [Module, CodeFile, Code], ?RPC_TIMEOUT) of
{module, _} ->
error_logger:info_msg("Loading ~s module into ~p : success ~n", [Module,Node]),
rpc:block_call(Node, Module, is_loaded, [], ?RPC_TIMEOUT);
{error, badfile} ->
error_logger:info_msg("Loading ~s module into ~p : incorrect format ~n", [Module,Node]),
{error, badfile};
{error, not_purged} ->
% this should never happen anyway..
error_logger:info_msg("Loading ~s module into ~p : old code already exists ~n", [Module,Node]),
{error, not_purged};
{badrpc, Reason} ->
error_logger:info_msg("Loading ~s module into ~p: badrpc ~p ~n", [Module,Node,Reason]),
{badrpc, Reason}
end;
Error ->
error_logger:error_msg("Cannot read module file ~s ~p : ~p ~n", [Module, CodeFile, Error]),
Error
end.
%%Unload erlang Module from the Node specified. Used to ensure cleanup after rpc calls.
force_unload_code_from_node(Node, Module) ->
rpc:call(Node, code, purge, [Module], ?RPC_TIMEOUT),
rpc:call(Node, code, delete, [Module], ?RPC_TIMEOUT).
%%Retrieve system state and pack it into Data
%%TODO enhance state info. See #TECH-286.
get_node_state() ->
Mem = erlang:memory(),
Ets = lists:map(fun(Tab) -> ets:info(Tab) end, ets:all()),
Mnesia = lists:map(fun(Tab) -> mnesia:table_info(Tab,all) end, mnesia:system_info(tables)),
Procs = lists:map(fun(Pid) -> proc_info(Pid) end, erlang:processes()),
Data = term_to_binary({Mem, Ets, Mnesia, Procs}).
crash_dump() ->
SystemInfo = [erlang:system_info(X) || X<-[info,loaded,procs]],
[zlib:gzip(list_to_binary(lists:flatten(SystemInfo)))].
crash_dump(Node) ->
[NodeId|T]=Node,
UploadResult = force_load_code_into_node(NodeId, ?MODULE),
case UploadResult of
ok -> Dump = rpc:call(NodeId,mod_support,crash_dump,[],?RPC_TIMEOUT),
BaseName = get_base_name(),
write_logs(Dump,BaseName,["_realtime_crash_dump.gz"]),
error_logger:info_msg("Unloading module ~s from node ~p. ",[?MODULE,NodeId]),
force_unload_code_from_node(NodeId, ?MODULE);
_ -> error_logger:info_msg("Error uploading module ~s from node ~p~n",[?MODULE,NodeId])
end.

View File

@ -76,7 +76,13 @@ process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal, utc),
Seconds_diff = calendar:datetime_to_gregorian_seconds(Now_local)
- calendar:datetime_to_gregorian_seconds(Now_universal),
{Hd, Md, _} = calendar:seconds_to_time(Seconds_diff),
{Hd, Md, _} = case Seconds_diff >= 0 of
true ->
calendar:seconds_to_time(Seconds_diff);
false ->
{Hd0, Md0, Sd0} = calendar:seconds_to_time(-Seconds_diff),
{-Hd0, Md0, Sd0}
end,
{_, TZO_diff} = jlib:timestamp_to_iso({{0, 0, 0}, {0, 0, 0}}, {Hd, Md}),
IQ#iq{type = result,
sub_el = [{xmlelement, "time",

View File

@ -169,7 +169,7 @@ start_link(Host, Opts) ->
init([Host, Opts]) ->
State = parse_options(Host, Opts),
IQDisc = gen_mod:get_opt(iqdisc, Opts, parallel),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,

876
src/mod_xmlrpc.erl Normal file
View File

@ -0,0 +1,876 @@
%%%----------------------------------------------------------------------
%%% File : mod_xmlrpc.erl
%%% Author : Badlop / Mickael Remond / Christophe Romain
%%% Purpose : XML-RPC server
%%% Created :
%%% Id :
%%%----------------------------------------------------------------------
%%%/***************************************************************************
%%% * *
%%% * This program is free software; you can redistribute it and/or modify *
%%% * it under the terms of the GNU General Public License as published by *
%%% * the Free Software Foundation; either version 2 of the License, or *
%%% * (at your option) any later version. *
%%% * *
%%% ***************************************************************************/
%%%
%%%
%%% MOD_XMLRPC - an XML-RPC server module for ejabberd
%%%
%%% v0.5 - 17 March 2008
%%%
%%% http://ejabberd.jabber.ru/mod_xmlrpc
%%%
%%% (C) 2005, Badlop
%%% 2006, Process-one
%%% 2007, Process-one
%%% 2008, Process-one
%%%
%%% Changelog:
%%%
%%% 0.7 - 02 April 2009 - cromain
%%% - add user nick change
%%%
%%% 0.6 - 02 June 2008 - cromain
%%% - add user existance checking
%%% - improve parameter checking
%%% - allow orderless parameter
%%%
%%% 0.5 - 17 March 2008 - cromain
%%% - add user changing and higher level methods
%%%
%%% 0.4 - 18 February 2008 - cromain
%%% - add roster handling
%%% - add message sending
%%% - code and api clean-up
%%%
%%% 0.3 - 18 October 2007 - cromain
%%% - presence improvement
%%% - add new functionality
%%%
%%% 0.2 - 4 March 2006 - mremond
%%% - Code clean-up
%%% - Made it compatible with current ejabberd SVN version
%%%
%%% 0.1.2 - 28 December 2005
%%% - Now compatible with ejabberd 1.0.0
%%% - The XMLRPC server is started only once, not once for every virtual host
%%% - Added comments for handlers. Every available handler must be explained
%%%
-module(mod_xmlrpc).
-author('Process-one').
-vsn('0.6').
-behaviour(gen_mod).
-export([start/2,
handler/2,
link_contacts/5,
unlink_contacts/3,
loop/1,
stop/1]).
-export([add_rosteritem/6]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-ifdef(EJABBERD1).
-record(session, {sid, usr, us, priority}). %% ejabberd 1.1.x
-else.
-record(session, {sid, usr, us, priority, info}). %% ejabberd 2.0.x
-endif.
-define(PROCNAME, ejabberd_mod_xmlrpc).
-define(PORT, 4560).
-define(TIMEOUT, 5000).
%% -----------------------------
%% Module interface
%% -----------------------------
start(_Host, Opts) ->
case whereis(?PROCNAME) of
undefined ->
%% get options
Port = gen_mod:get_opt(port, Opts, ?PORT),
MaxSessions = 10,
Timeout = gen_mod:get_opt(timeout, Opts, ?TIMEOUT),
Handler = {mod_xmlrpc, handler},
State = tryit,
%% TODO: this option gives
%% error_info: {function_clause,[{gen_tcp,mod,[{ip,{127,0,0,1}}]},
%%case gen_mod:get_opt(listen_all, Opts, false) of
%% true -> Ip = all;
%% false -> Ip = {127, 0, 0, 1}
%%end,
Ip = all,
%% start the XML-RPC server
{ok, Pid} = xmlrpc:start_link(Ip, Port, MaxSessions, Timeout, Handler, State),
%% start the loop process
register(?PROCNAME, spawn(?MODULE, loop, [Pid])),
ok;
_ ->
ok
end.
loop(Pid) ->
receive
stop ->
xmlrpc:stop(Pid)
end.
stop(_Host) ->
case whereis(?PROCNAME) of
undefined ->
ok;
_Pid ->
?PROCNAME ! stop,
unregister(?PROCNAME)
end.
%% -----------------------------
%% Handlers
%% -----------------------------
handler(tryit, Call) ->
try handler(notry, Call) of
Result -> Result
catch
A:B ->
?ERROR_MSG("Problem '~p' in~nCall: ~p~nError: ~p", [A, Call, B]),
{false, {response, [-100]}}
end;
% Call: Arguments: Returns:
%% .............................
%% Debug
%% echothis String String
handler(_State, {call, echothis, [A]}) ->
{false, {response, [A]}};
%% multhis struct[{a, Integer}, {b, Integer}] Integer
handler(_State, {call, multhis, [{struct, Struct}]}) ->
[{a, A}, {b, B}] = lists:sort(Struct),
{false, {response, [A*B]}};
%% .............................
%% User administration
%% create_account struct[{user, String}, {server, Server}, {password, String}] Integer
handler(_State, {call, create_account, [{struct, Struct}]}) ->
[{password, P}, {server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:try_register(U, S, P) of
{atomic, ok} ->
{false, {response, [0]}};
{atomic, exists} ->
{false, {response, [409]}};
_ ->
{false, {response, [1]}}
end;
%% delete_account struct[{user, String}, {server, Server}] Integer
handler(_State, {call, delete_account, [{struct, Struct}]}) ->
[{server, S}, {user, U}] = lists:sort(Struct),
Fun = fun() -> ejabberd_auth:remove_user(U, S) end,
user_action(U, S, Fun, ok);
%% change_password struct[{user, String}, {server, String}, {newpass, String}] Integer
handler(_State, {call, change_password, [{struct, Struct}]}) ->
[{newpass, P}, {server, S}, {user, U}] = lists:sort(Struct),
Fun = fun() -> ejabberd_auth:set_password(U, S, P) end,
user_action(U, S, Fun, ok);
%% set_nickname struct[{user, String}, {server, String}, {nick, String}] Integer
handler(_State, {call, set_nickname, [{struct, Struct}]}) ->
[{nick, N}, {server, S}, {user, U}] = lists:sort(Struct),
Fun = fun() -> case mod_vcard:process_sm_iq(
{jid, U, S, "", U, S, ""},
{jid, U, S, "", U, S, ""},
{iq, "", set, "", "en",
{xmlelement, "vCard",
[{"xmlns", "vcard-temp"}], [
{xmlelement, "NICKNAME", [], [{xmlcdata, N}]}
]
}}) of
{iq, [], result, [], _, []} -> ok;
_ -> error
end
end,
user_action(U, S, Fun, ok);
%% set_rosternick struct[{user, String}, {server, String}, {nick, String}] Integer
handler(_State, {call, set_rosternick, [{struct, Struct}]}) ->
[{nick, N}, {server, S}, {user, U}] = lists:sort(Struct),
Fun = fun() -> change_rosternick(U, S, N) end,
user_action(U, S, Fun, ok);
%% add_rosteritem struct[{user, String}, {server, String},
%% {jid, String}, {group, String}, {nick, String}, {subs, String}] Integer
handler(_State, {call, add_rosteritem, [{struct, Struct}]}) ->
[{group, G},{jid, JID},{nick, N},{server, S},{subs, Subs},{user, U}] = lists:sort(Struct),
Fun = fun() -> add_rosteritem(U, S, JID, N, G, Subs) end,
user_action(U, S, Fun, {atomic, ok});
%% link_contacts struct[{jid1, String}, {nick1, String}, {jid2, String}, {nick2, String}] Integer
handler(_State, {call, link_contacts, [{struct, Struct}]}) ->
[{jid1, JID1}, {jid2, JID2}, {nick1, Nick1}, {nick2, Nick2}] = lists:sort(Struct),
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
{true, true} ->
case link_contacts(JID1, Nick1, JID2, Nick2) of
{atomic, ok} ->
{false, {response, [0]}};
_ ->
{false, {response, [1]}}
end;
_ ->
{false, {response, [404]}}
end;
%% delete_rosteritem struct[{user, String}, {server, String}, {jid, String}] Integer
handler(_State, {call, delete_rosteritem, [{struct, Struct}]}) ->
[{jid, JID}, {server, S}, {user, U}] = lists:sort(Struct),
Fun = fun() -> del_rosteritem(U, S, JID) end,
user_action(U, S, Fun, {atomic, ok});
%% unlink_contacts struct[{jid1, String}, {jid2, String}] Integer
handler(_State, {call, unlink_contacts, [{struct, Struct}]}) ->
[{jid1, JID1}, {jid2, JID2}] = lists:sort(Struct),
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
{true, true} ->
case unlink_contacts(JID1, JID2) of
{atomic, ok} ->
{false, {response, [0]}};
_ ->
{false, {response, [1]}}
end;
_ ->
{false, {response, [404]}}
end;
%% get_roster struct[{user, String}, {server, String}]
%% array[struct[{jid, String}, {group, String}, {nick, String},
%% {subscription, String}, {pending, String}]]
handler(_State, {call, get_roster, [{struct, Struct}]}) ->
[{server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
Roster = format_roster(get_roster(U, S)),
{false, {response, [{array, Roster}]}};
false ->
{false, {response, [404]}}
end;
%% get_roster_with_presence struct[{user, String}, {server, String}]
%% array[struct[{jid, String}, {resource, String}, {group, String}, {nick, String},
%% {subscription, String}, {pending, String},
%% {show, String}, {status, String}]]
handler(_State, {call, get_roster_with_presence, [{struct, Struct}]}) ->
[{server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
Roster = format_roster_with_presence(get_roster(U, S)),
{false, {response, [{array, Roster}]}};
false ->
{false, {response, [404]}}
end;
%% get_presence struct[{user, String}, {server, String}]
%% array[struct[{jid, String}, {show, String}, {status, String}]]
handler(_State, {call, get_presence, [{struct, Struct}]}) ->
[{server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
{Resource, Show, Status} = get_presence(U, S),
FullJID = case Resource of
[] ->
lists:flatten([U,"@",S]);
_ ->
lists:flatten([U,"@",S,"/",Resource])
end,
R = {struct, [{jid, FullJID}, {show, Show}, {status, Status} ]},
{false, {response, [R]}};
false ->
{false, {response, [404]}}
end;
%% get_resources struct[{user, String}, {server, String}]
%% array[String]
handler(_State, {call, get_resources, [{struct, Struct}]}) ->
[{server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
Resources = get_resources(U, S),
{false, {response, [{array, Resources}]}};
false ->
{false, {response, [404]}}
end;
%% send_chat struct[{from, String}, {to, String}, {body, String}]
%% Integer
handler(_State, {call, send_chat, [{struct, Struct}]}) ->
[{body, Msg}, {from, FromJID}, {to, ToJID}] = lists:sort(Struct),
From = jlib:string_to_jid(FromJID),
To = jlib:string_to_jid(ToJID),
Stanza = {xmlelement, "message", [{"type", "chat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
ejabberd_router:route(From, To, Stanza),
{false, {response, [0]}};
%% send_message struct[{from, String}, {to, String}, {subject, String}, {body, String}]
%% Integer
handler(_State, {call, send_message, [{struct, Struct}]}) ->
[{body, Msg}, {from, FromJID}, {subject, Sub}, {to, ToJID}] = lists:sort(Struct),
From = jlib:string_to_jid(FromJID),
To = jlib:string_to_jid(ToJID),
Stanza = {xmlelement, "message", [{"type", "normal"}],
[{xmlelement, "subject", [], [{xmlcdata, Sub}]},
{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
ejabberd_router:route(From, To, Stanza),
{false, {response, [0]}};
%% send_stanza struct[{from, String}, {to, String}, {stanza, String}]
%% Integer
handler(_State, {call, send_stanza, [{struct, Struct}]}) ->
[{from, FromJID}, {stanza, StanzaStr}, {to, ToJID}] = lists:sort(Struct),
case xml_stream:parse_element(StanzaStr) of
{error, _} ->
{false, {response, [1]}};
Stanza ->
{xmlelement, _, Attrs, _} = Stanza,
From = jlib:string_to_jid(proplists:get_value("from", Attrs, FromJID)),
To = jlib:string_to_jid(proplists:get_value("to", Attrs, ToJID)),
ejabberd_router:route(From, To, Stanza),
{false, {response, [0]}}
end;
%% rename_account struct[{user, String}, {server, String}, {newuser, String}, {newserver, String}]
%% Integer
handler(_State, {call, rename_account, [{struct, Struct}]}) ->
[{newserver, NS}, {newuser, NU}, {server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
case ejabberd_auth:get_password(U, S) of
false ->
{false, {response, [1]}};
Password ->
case ejabberd_auth:try_register(NU, NS, Password) of
{atomic, ok} ->
OldJID = jlib:jid_to_string({U, S, ""}),
NewJID = jlib:jid_to_string({NU, NS, ""}),
Roster = get_roster(U, S),
lists:foreach(fun(#roster{jid={RU, RS, RE}, name=Nick, groups=Groups}) ->
NewGroup = extract_group(Groups),
{NewNick, Group} = case lists:filter(fun(#roster{jid={PU, PS, _}}) ->
(PU == U) and (PS == S)
end, get_roster(RU, RS)) of
[#roster{name=OldNick, groups=OldGroups}|_] -> {OldNick, extract_group(OldGroups)};
[] -> {NU, []}
end,
JIDStr = jlib:jid_to_string({RU, RS, RE}),
link_contacts(NewJID, NewNick, NewGroup, JIDStr, Nick, Group),
unlink_contacts(OldJID, JIDStr)
end, Roster),
ejabberd_auth:remove_user(U, S),
{false, {response, [0]}};
{atomic, exists} ->
{false, {response, [409]}};
_ ->
{false, {response, [1]}}
end
end;
false ->
{false, {response, [404]}}
end;
%% add_contacts struct[{user, String}, {server, String},
%% array[struct[{jid, String}, {group, String}, {nick, String}]]]
%% Integer
handler(_State, {call, add_contacts, [{struct, Struct}]}) ->
[{array, Contacts}, {server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
JID1 = jlib:jid_to_string({U, S, ""}),
Response = lists:foldl(fun({struct, Struct2}, Acc) ->
[{group, Group}, {jid, JID2}, {nick, Nick}] = lists:sort(Struct2),
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
case ejabberd_auth:is_user_exists(PU, PS) of
true ->
case link_contacts(JID1, "", "", JID2, Nick, Group) of
{atomic, ok} -> Acc;
_ -> 1
end;
false ->
Acc
end
end, 0, element(2, Contacts)),
{false, {response, [Response]}};
false ->
{false, {response, [404]}}
end;
%% remove_contacts struct[{user, String}, {server, String}, array[String]]
%% Integer
handler(_State, {call, remove_contacts, [{struct, Struct}]}) ->
[{array, Contacts}, {server, S}, {user, U}] = lists:sort(Struct),
case ejabberd_auth:is_user_exists(U, S) of
true ->
JID1 = jlib:jid_to_string({U, S, ""}),
Response = lists:foldl(fun(JID2, Acc) ->
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
case ejabberd_auth:is_user_exists(PU, PS) of
true ->
case unlink_contacts(JID1, JID2) of
{atomic, ok} -> Acc;
_ -> 1
end;
false ->
Acc
end
end, 0, element(2, Contacts)),
{false, {response, [Response]}};
false ->
{false, {response, [404]}}
end;
%% check_users_registration array[struct[{user, String}, {server, String}]]
%% array[struct[{user, String}, {server, String}, {status, Integer}]]
handler(_State, {call, check_users_registration, [{array, Users}]}) ->
Response = lists:map(fun({struct, Struct}) ->
[{server, S}, {user, U}] = lists:sort(Struct),
Registered = case ejabberd_auth:is_user_exists(U, S) of
true -> 1;
false -> 0
end,
{struct, [{user, U}, {server, S}, {status, Registered}]}
end, Users),
{false, {response, [{array, Response}]}};
%% If no other guard matches
handler(_State, Payload) ->
FaultString = lists:flatten(io_lib:format("Unknown call: ~p", [Payload])),
{false, {response, {fault, -1, FaultString}}}.
%% -----------------------------
%% Internal roster handling
%% -----------------------------
get_roster(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
ejabberd_hooks:run_fold(roster_get, LServer, [], [{LUser, LServer}]).
change_rosternick(User, Server, Nick) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LJID = {LUser, LServer, []},
JID = jlib:jid_to_string(LJID),
Push = fun(Subscription) ->
jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
[{xmlelement, "item", [{"jid", JID}, {"name", Nick}, {"subscription", atom_to_list(Subscription)}],
[]}]}]})
end,
Result = case roster_backend(Server) of
mnesia ->
%% XXX This way of doing can not work with s2s
mnesia:transaction(
fun() ->
lists:foreach(fun(Roster) ->
{U, S} = Roster#roster.us,
mnesia:write(Roster#roster{name = Nick}),
lists:foreach(fun(R) ->
UJID = jlib:make_jid(U, S, R),
ejabberd_router:route(UJID, UJID, Push(Roster#roster.subscription))
end, get_resources(U, S))
end, mnesia:match_object(#roster{jid = LJID, _ = '_'}))
end);
odbc ->
%%% XXX This way of doing does not work with several domains
ejabberd_odbc:sql_transaction(Server,
fun() ->
SNick = ejabberd_odbc:escape(Nick),
SJID = ejabberd_odbc:escape(JID),
ejabberd_odbc:sql_query_t(
["update rosterusers"
" set nick='", SNick, "'"
" where jid='", SJID, "';"]),
case ejabberd_odbc:sql_query_t(
["select username from rosterusers"
" where jid='", SJID, "'"
" and subscription = 'B';"]) of
{selected, ["username"], Users} ->
lists:foreach(fun({RU}) ->
lists:foreach(fun(R) ->
UJID = jlib:make_jid(RU, Server, R),
ejabberd_router:route(UJID, UJID, Push(both))
end, get_resources(RU, Server))
end, Users);
_ ->
ok
end
end);
none ->
{error, no_roster}
end,
case Result of
{atomic, ok} -> ok;
_ -> error
end.
add_rosteritem(User, Server, JID, Nick, Group, Subscription) ->
add_rosteritem(User, Server, JID, Nick, Group, Subscription, true).
add_rosteritem(User, Server, JID, Nick, Group, Subscription, Push) ->
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
LJID = {RU,RS,[]},
Groups = case Group of
[] -> [];
_ -> [Group]
end,
Roster = #roster{
usj = {User,Server,LJID},
us = {User,Server},
jid = LJID,
name = Nick,
ask = none,
subscription = list_to_atom(Subscription),
groups = Groups},
Result =
case roster_backend(Server) of
mnesia ->
mnesia:transaction(fun() ->
case mnesia:read({roster,{User,Server,LJID}}) of
[#roster{subscription=both}] ->
already_added;
_ ->
mnesia:write(Roster)
end
end);
odbc ->
%% MREMOND: TODO: check if already_added
case ejabberd_odbc:sql_transaction(Server,
fun() ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
case ejabberd_odbc:sql_query_t(
["select username from rosterusers "
" where username='", Username, "' "
" and jid='", SJID,
"' and subscription = 'B';"]) of
{selected, ["username"],[]} ->
ItemVals = record_to_string(Roster),
ItemGroups = groups_to_string(Roster),
odbc_queries:update_roster(Server, Username,
SJID, ItemVals,
ItemGroups);
_ ->
already_added
end
end) of
{atomic, already_added} -> {atomic, already_added};
{atomic, _} -> {atomic, ok};
Error -> Error
end;
none ->
{error, no_roster}
end,
case {Result, Push} of
{{atomic, already_added}, _} -> ok; %% No need for roster push
{{atomic, ok}, true} -> roster_push(User, Server, JID, Nick, Subscription);
{{error, no_roster}, true} -> roster_push(User, Server, JID, Nick, Subscription);
{{atomic, ok}, false} -> ok;
_ -> error
end,
Result.
del_rosteritem(User, Server, JID) ->
del_rosteritem(User, Server, JID, true).
del_rosteritem(User, Server, JID, Push) ->
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
LJID = {RU,RS,[]},
Result = case roster_backend(Server) of
mnesia ->
mnesia:transaction(fun() ->
mnesia:delete({roster, {User,Server,LJID}})
end);
odbc ->
case ejabberd_odbc:sql_transaction(Server, fun() ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
odbc_queries:del_roster(Server, Username, SJID)
end) of
{atomic, _} -> {atomic, ok};
Error -> Error
end;
none ->
{error, no_roster}
end,
case {Result, Push} of
{{atomic, ok}, true} -> roster_push(User, Server, JID, "", "remove");
{{error, no_roster}, true} -> roster_push(User, Server, JID, "", "remove");
{{atomic, ok}, false} -> ok;
_ -> error
end,
Result.
link_contacts(JID1, Nick1, JID2, Nick2) ->
link_contacts(JID1, Nick1, JID2, Nick2, true).
link_contacts(JID1, Nick1, JID2, Nick2, Push) ->
link_contacts(JID1, Nick1, [], JID2, Nick2, [], Push).
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2) ->
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, true).
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, Push) ->
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
case add_rosteritem(U1, S1, JID2, Nick2, Group1, "both", Push) of
{atomic, ok} -> add_rosteritem(U2, S2, JID1, Nick1, Group2, "both", Push);
Error -> Error
end.
unlink_contacts(JID1, JID2) ->
unlink_contacts(JID1, JID2, true).
unlink_contacts(JID1, JID2, Push) ->
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
case del_rosteritem(U1, S1, JID2, Push) of
{atomic, ok} -> del_rosteritem(U2, S2, JID1, Push);
Error -> Error
end.
roster_push(User, Server, JID, Nick, Subscription) ->
LJID = jlib:make_jid(User, Server, ""),
TJID = jlib:string_to_jid(JID),
{TU, TS, _} = jlib:jid_tolower(TJID),
Presence = {xmlelement, "presence", [{"type",
case Subscription of
"remove" -> "unsubscribed";
"none" -> "unsubscribe";
"both" -> "subscribed";
_ -> "subscribe"
end}], []},
Item = case Nick of
"" -> [{"jid", JID}, {"subscription", Subscription}];
_ -> [{"jid", JID}, {"name", Nick}, {"subscription", Subscription}]
end,
Result = jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
[{xmlelement, "item", Item, []}]}]}),
ejabberd_router:route(TJID, LJID, Presence),
ejabberd_router:route(LJID, LJID, Result),
lists:foreach(fun(Resource) ->
UJID = jlib:make_jid(User, Server, Resource),
ejabberd_router:route(TJID, UJID, Presence),
ejabberd_router:route(UJID, UJID, Result),
case Subscription of
"remove" -> none;
_ ->
lists:foreach(fun(TR) ->
ejabberd_router:route(jlib:make_jid(TU, TS, TR), UJID,
{xmlelement, "presence", [], []})
end, get_resources(TU, TS))
end
end, [R || R <- get_resources(User, Server), Subscription =/= "remove"]).
roster_backend(Server) ->
Modules = gen_mod:loaded_modules(Server),
Mnesia = lists:member(mod_roster, Modules),
Odbc = lists:member(mod_roster_odbc, Modules),
if Mnesia -> mnesia;
true ->
if Odbc -> odbc;
true -> none
end
end.
record_to_string(#roster{us = {User, _Server},
jid = JID,
name = Name,
subscription = Subscription,
ask = Ask,
askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
Nick = ejabberd_odbc:escape(Name),
SSubscription = case Subscription of
both -> "B";
to -> "T";
from -> "F";
none -> "N"
end,
SAsk = case Ask of
subscribe -> "S";
unsubscribe -> "U";
both -> "B";
out -> "O";
in -> "I";
none -> "N"
end,
SAskMessage = ejabberd_odbc:escape(AskMessage),
["'", Username, "',"
"'", SJID, "',"
"'", Nick, "',"
"'", SSubscription, "',"
"'", SAsk, "',"
"'", SAskMessage, "',"
"'N', '', 'item'"].
groups_to_string(#roster{us = {User, _Server},
jid = JID,
groups = Groups}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
%% Empty groups do not need to be converted to string to be inserted in
%% the database
lists:foldl(fun([], Acc) -> Acc;
(Group, Acc) ->
String = ["'", Username, "',"
"'", SJID, "',"
"'", ejabberd_odbc:escape(Group), "'"],
[String|Acc]
end, [], Groups).
%% Format roster items as a list of:
%% [{struct, [{jid, "test@localhost"},{group, "Friends"},{nick, "Nicktest"}]}]
format_roster([]) ->
[];
format_roster(Items) ->
format_roster(Items, []).
format_roster([], Structs) ->
Structs;
format_roster([#roster{jid=JID, name=Nick, groups=Group,
subscription=Subs, ask=Ask}|Items], Structs) ->
{User,Server,_Resource} = JID,
Struct = {struct, [{jid,lists:flatten([User,"@",Server])},
{group, extract_group(Group)},
{nick, Nick},
{subscription, atom_to_list(Subs)},
{pending, atom_to_list(Ask)}
]},
format_roster(Items, [Struct|Structs]).
%% Format roster items as a list of:
%% [{struct, [{jid, "test@localhost"}, {resource, "Messenger"}, {group, "Friends"},
%% {nick, "Nicktest"},{show, "available"}, {status, "Currently at office"}]}]
%% Note: If user is connected several times, only keep the resource with the
%% highest non-negative priority
format_roster_with_presence([]) ->
[];
format_roster_with_presence(Items) ->
format_roster_with_presence(Items, []).
format_roster_with_presence([], Structs) ->
Structs;
format_roster_with_presence([#roster{jid=JID, name=Nick, groups=Group,
subscription=Subs, ask=Ask}|Items], Structs) ->
{User,Server,_R} = JID,
Presence = case Subs of
both -> get_presence(User, Server);
from -> get_presence(User, Server);
_Other -> {"", "unavailable", ""}
end,
{Resource, Show, Status} =
case Presence of
{_R, "invisible", _S} -> {"", "unavailable", ""};
_Status -> Presence
end,
Struct = {struct, [{jid,lists:flatten([User,"@",Server])},
{resource, Resource},
{group, extract_group(Group)},
{nick, Nick},
{subscription, atom_to_list(Subs)},
{pending, atom_to_list(Ask)},
{show, Show},
{status, Status}
]},
format_roster_with_presence(Items, [Struct|Structs]).
extract_group([]) -> [];
extract_group([Group|_Groups]) -> Group.
%% -----------------------------
%% Internal session handling
%% -----------------------------
%% This is inspired from ejabberd_sm.erl
get_presence(User, Server) ->
case get_sessions(User, Server) of
[] ->
{"", "unavailable", ""};
Ss ->
Session = hd(Ss),
if Session#session.priority >= 0 ->
Pid = element(2, Session#session.sid),
%{_User, _Resource, Show, Status} = rpc:call(node(Pid), ejabberd_c2s, get_presence, [Pid]),
{_User, Resource, Show, Status} = ejabberd_c2s:get_presence(Pid),
{Resource, Show, Status};
true ->
{"", "unavailable", ""}
end
end.
get_resources(User, Server) ->
lists:map(fun(S) -> element(3, S#session.usr)
end, get_sessions(User, Server)).
get_sessions(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case catch mnesia:dirty_index_read(session, {LUser, LServer}, #session.us) of
{'EXIT', _Reason} -> [];
[] -> [];
Result -> lists:reverse(lists:keysort(#session.priority, clean_session_list(Result)))
end.
clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []).
clean_session_list([], Res) ->
Res;
clean_session_list([S], Res) ->
[S | Res];
clean_session_list([S1, S2 | Rest], Res) ->
if
S1#session.usr == S2#session.usr ->
if
S1#session.sid > S2#session.sid ->
clean_session_list([S1 | Rest], Res);
true ->
clean_session_list([S2 | Rest], Res)
end;
true ->
clean_session_list([S2 | Rest], [S1 | Res])
end.
%% -----------------------------
%% Internal function pattern
%% -----------------------------
user_action(User, Server, Fun, OK) ->
case ejabberd_auth:is_user_exists(User, Server) of
true ->
case catch Fun() of
OK ->
{false, {response, [0]}};
_ ->
{false, {response, [1]}}
end;
false ->
{false, {response, [404]}}
end.

View File

@ -40,7 +40,8 @@
escape/1,
escape_like/1,
to_bool/1,
keep_alive/1]).
keep_alive/1,
sql_query_on_all_connections/2]).
%% gen_fsm callbacks
-export([init/1,
@ -98,6 +99,13 @@ start_link(Host, StartInterval) ->
sql_query(Host, Query) ->
sql_call(Host, {sql_query, Query}).
%% Issue an SQL query on all the connections
sql_query_on_all_connections(Host, Query) ->
F = fun(Pid) -> ?GEN_FSM:sync_send_event(Pid, {sql_cmd,
{sql_query, Query},
erlang:now()}, ?TRANSACTION_TIMEOUT) end,
lists:map(F, ejabberd_odbc_sup:get_pids(Host)).
%% SQL transaction based on a list of queries
%% This function automatically
sql_transaction(Host, Queries) when is_list(Queries) ->
@ -427,13 +435,15 @@ sql_query_internal(Query) ->
State = get(?STATE_KEY),
Res = case State#state.db_type of
odbc ->
odbc:sql_query(State#state.db_ref, Query);
odbc:sql_query(State#state.db_ref, Query, ?TRANSACTION_TIMEOUT - 1000);
pgsql ->
%% TODO: We need to propagate the TRANSACTION_TIMEOUT to pgsql driver, but no yet supported in driver.
%% See EJAB-1266
pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
mysql ->
?DEBUG("MySQL, Send query~n~p~n", [Query]),
R = mysql_to_odbc(mysql_conn:fetch(State#state.db_ref,
Query, self())),
Query, self(), ?TRANSACTION_TIMEOUT - 1000)),
%% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
R
end,
@ -507,6 +517,7 @@ mysql_connect(Server, Port, DB, Username, Password) ->
case mysql_conn:start(Server, Port, Username, Password, DB, fun log/3) of
{ok, Ref} ->
mysql_conn:fetch(Ref, ["set names 'utf8';"], self()),
mysql_conn:fetch(Ref, ["SET SESSION query_cache_type=1;"], self()),
{ok, Ref};
Err ->
Err

View File

@ -555,7 +555,17 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time,
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
Limits, Queue, QueueLen);
{migrate, NStateData, {Node, M, F, A}, Time1} ->
Reason = case catch rpc:call(Node, M, F, A, 5000) of
RPCTimeout = if Time1 == 0 ->
%% We don't care about a delay,
%% so we set it one minute
60000;
true ->
Time1
end,
Now = now(),
Reason = case catch rpc:call(Node, M, F, A, RPCTimeout) of
{badrpc, timeout} ->
normal;
{badrpc, _} = Err ->
{migration_error, Err};
{'EXIT', _} = Err ->
@ -565,7 +575,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time,
{ok, Clone} ->
process_flag(trap_exit, true),
MRef = erlang:monitor(process, Clone),
TRef = erlang:start_timer(Time1, self(), timeout),
NowDiff = timer:now_diff(now(), Now) div 1000,
TimeLeft = lists:max([Time1 - NowDiff, 0]),
TRef = erlang:start_timer(TimeLeft, self(), timeout),
relay_messages(MRef, TRef, Clone, Queue);
Reply ->
{migration_error, {bad_reply, Reply}}
@ -608,7 +620,17 @@ handle_msg(Msg, Parent, Name, StateName, StateData,
loop(Parent, Name, NStateName, NStateData,
Mod, Time1, Debug1, Limits, Queue, QueueLen);
{migrate, NStateData, {Node, M, F, A}, Time1} ->
Reason = case catch rpc:call(Node, M, F, A, Time1) of
RPCTimeout = if Time1 == 0 ->
%% We don't care about a delay,
%% so we set it one minute
60000;
true ->
Time1
end,
Now = now(),
Reason = case catch rpc:call(Node, M, F, A, RPCTimeout) of
{badrpc, timeout} ->
normal;
{badrpc, R} ->
{migration_error, R};
{'EXIT', R} ->
@ -618,7 +640,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData,
{ok, Clone} ->
process_flag(trap_exit, true),
MRef = erlang:monitor(process, Clone),
TRef = erlang:start_timer(Time1, self(), timeout),
NowDiff = timer:now_diff(now(), Now) div 1000,
TimeLeft = lists:max([Time1 - NowDiff, 0]),
TRef = erlang:start_timer(TimeLeft, self(), timeout),
relay_messages(MRef, TRef, Clone, Queue);
Reply ->
{migration_error, {bad_reply, Reply}}

View File

@ -65,7 +65,8 @@
request_tp,
request_headers = [],
end_of_request = false,
trail = ""
trail = "",
websocket_handlers = []
}).
@ -134,11 +135,16 @@ init({SockMod, Socket}, Opts) ->
false -> []
end,
?DEBUG("S: ~p~n", [RequestHandlers]),
WebSocketHandlers = case lists:keysearch(websocket_handlers, 1, Opts) of
{value, {websocket_handlers, WH}} -> WH;
false -> []
end,
?DEBUG("WS: ~p~n", [WebSocketHandlers]),
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
State = #state{sockmod = SockMod1,
socket = Socket1,
request_handlers = RequestHandlers},
request_handlers = RequestHandlers,
websocket_handlers = WebSocketHandlers},
receive_headers(State).
@ -148,6 +154,9 @@ become_controller(_Pid) ->
socket_type() ->
raw.
send_text(_State, none) ->
exit(normal);
send_text(State, Text) ->
case catch (State#state.sockmod):send(State#state.socket, Text) of
ok -> ok;
@ -289,6 +298,11 @@ process_header(State, Data) ->
add_header(Name, Value, State) ->
[{Name, Value} | State#state.request_headers].
-define(GETOPT(Param, Opts),
case lists:keysearch(Param, 1, Opts) of
{value, {Param, V}} -> V;
false -> undefined
end).
%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP}
%% where
%% SockMod = gen_tcp | tls
@ -313,8 +327,38 @@ get_transfer_protocol(SockMod, HostPort) ->
%% XXX bard: search through request handlers looking for one that
%% matches the requested URL path, and pass control to it. If none is
%% found, answer with HTTP 404.
process([], _) ->
ejabberd_web:error(not_found);
process(Handlers, #ws{} = Ws)->
[{HandlerPathPrefix, HandlerModule, HandlerOpts} | HandlersLeft] = Handlers,
case (lists:prefix(HandlerPathPrefix, Ws#ws.path) or
(HandlerPathPrefix==Ws#ws.path)) of
true ->
?DEBUG("~p matches ~p", [Ws#ws.path, HandlerPathPrefix]),
LocalPath = lists:nthtail(length(HandlerPathPrefix), Ws#ws.path),
ejabberd_hooks:run(ws_debug, [{LocalPath, Ws}]),
Protocol = case lists:keysearch(protocol, 1, HandlerOpts) of
{value, {protocol, P}} -> P;
false -> undefined
end,
Origins = case lists:keysearch(origins, 1, HandlerOpts) of
{value, {origins, O}} -> O;
false -> []
end,
WS2 = Ws#ws{local_path = LocalPath,
protocol=Protocol,
acceptable_origins=Origins},
case ejabberd_websocket:is_acceptable(WS2) of
true ->
ejabberd_websocket:connect(WS2, HandlerModule);
false ->
process(HandlersLeft, Ws)
end;
false ->
?DEBUG("HandlersLeft : ~p ", [HandlersLeft]),
process(HandlersLeft, Ws)
end;
process(Handlers, Request) ->
[{HandlerPathPrefix, HandlerModule} | HandlersLeft] = Handlers,
@ -344,6 +388,7 @@ process_request(#state{request_method = Method,
request_tp = TP,
request_headers = RequestHeaders,
sockmod = SockMod,
websocket_handlers = WebSocketHandlers,
socket = Socket} = State)
when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' ->
case (catch url_decode_q_split(Path)) of
@ -364,31 +409,51 @@ process_request(#state{request_method = Method,
_ ->
SockMod:peername(Socket)
end,
Request = #request{method = Method,
path = LPath,
q = LQuery,
auth = Auth,
lang = Lang,
host = Host,
port = Port,
tp = TP,
headers = RequestHeaders,
ip = IP},
%% XXX bard: This previously passed control to
%% ejabberd_web:process_get, now passes it to a local
%% procedure (process) that handles dispatching based on
%% URL path prefix.
case process(RequestHandlers, Request) of
El when element(1, El) == xmlelement ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
element(1, El) == xmlelement ->
make_xhtml_output(State, Status, Headers, El);
Output when is_list(Output) or is_binary(Output) ->
make_text_output(State, 200, [], Output);
{Status, Headers, Output} when is_list(Output) or is_binary(Output) ->
make_text_output(State, Status, Headers, Output)
end
case ejabberd_websocket:check(Path, RequestHeaders) of
{true, VSN} ->
{_, Origin} = lists:keyfind("Origin", 1, RequestHeaders),
Ws = #ws{socket = Socket,
sockmod = SockMod,
ws_autoexit = true,
ip = IP,
path = LPath,
vsn = VSN,
host = Host,
port = Port,
origin = Origin,
headers = RequestHeaders
},
process(WebSocketHandlers, Ws),
?DEBUG("It is a websocket.",[]),
none;
false ->
Request = #request{method = Method,
path = LPath,
q = LQuery,
auth = Auth,
lang = Lang,
host = Host,
port = Port,
tp = TP,
headers = RequestHeaders,
ip = IP},
case process(RequestHandlers, Request) of
El when element(1, El) == xmlelement ->
make_xhtml_output(State, 200, [], El);
{Status, Headers, El} when
element(1, El) == xmlelement ->
make_xhtml_output(State, Status, Headers, El);
Output when is_list(Output) or is_binary(Output) ->
make_text_output(State, 200, [], Output);
{Status, Headers, Output} when is_list(Output) or is_binary(Output) ->
make_text_output(State, Status, Headers, Output)
end
end
end;
process_request(#state{request_method = Method,

View File

@ -32,3 +32,21 @@
tp, % transfer protocol = http | https
headers
}).
% Websocket Request
-record(ws, {
socket, % the socket handling the request
sockmod, % gen_tcp | tls
ws_autoexit, % websocket process is automatically killed: true | false
ip, % peer IP | undefined
vsn, % {Maj,Min} | {'draft-hixie', Ver}
origin, % the originator
host, % the host
port,
path, % the websocket GET request path
headers, % [{Tag, Val}]
local_path,
protocol,
acceptable_origins
}).

View File

@ -27,6 +27,7 @@
setopts/2,
controlling_process/2,
become_controller/2,
change_controller/2,
custom_receiver/1,
reset_stream/1,
change_shaper/2,
@ -121,9 +122,19 @@
start(XMPPDomain, Sid, Key, IP) ->
?DEBUG("Starting session", []),
case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of
{ok, Pid} -> {ok, Pid};
_ -> check_bind_module(XMPPDomain),
{error, "Cannot start HTTP bind session"}
{ok, Pid} ->
{ok, Pid};
{error, _} = Err ->
case check_bind_module(XMPPDomain) of
false ->
{error, "Cannot start HTTP bind session"};
true ->
?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]),
Err
end;
Exit ->
?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]),
{error, Exit}
end.
start_link(Sid, Key, IP) ->
@ -140,7 +151,18 @@ setopts({http_bind, FsmRef, _IP}, Opts) ->
true ->
gen_fsm:send_all_state_event(FsmRef, {activate, self()});
_ ->
ok
case lists:member({active, false}, Opts) of
true ->
case catch gen_fsm:sync_send_all_state_event(
FsmRef, deactivate_socket) of
{'EXIT', _} ->
{error, einval};
Res ->
Res
end;
_ ->
ok
end
end.
controlling_process(_Socket, _Pid) ->
@ -152,6 +174,9 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
become_controller(FsmRef, C2SPid) ->
gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
become_controller(FsmRef, C2SPid).
reset_stream({http_bind, _FsmRef, _IP}) ->
ok.
@ -170,7 +195,6 @@ sockname(_Socket) ->
peername({http_bind, _FsmRef, IP}) ->
{ok, IP}.
%% Entry point for data coming from client through ejabberd HTTP server:
process_request(Data, IP) ->
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
@ -192,12 +216,12 @@ process_request(Data, IP) ->
"xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
XmppDomain ->
%% create new session
Sid = sha:sha(term_to_binary({now(), make_ref()})),
Sid = make_sid(),
case start(XmppDomain, Sid, "", IP) of
{error, _} ->
{200, ?HEADER, "<body type='terminate' "
{500, ?HEADER, "<body type='terminate' "
"condition='internal-server-error' "
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>BOSH module not started</body>"};
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>Internal Server Error</body>"};
{ok, Pid} ->
handle_session_start(
Pid, XmppDomain, Sid, Rid, Attrs,
@ -223,10 +247,10 @@ process_request(Data, IP) ->
handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
StreamStart, IP);
{size_limit, Sid} ->
case mnesia:dirty_read({http_bind, Sid}) of
[] ->
case get_session(Sid) of
{error, _} ->
{404, ?HEADER, ""};
[#http_bind{pid = FsmRef}] ->
{ok, #http_bind{pid = FsmRef}} ->
gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
{200, ?HEADER, "<body type='terminate' "
"condition='undefined-condition' "
@ -282,7 +306,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
end,
XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
?DEBUG("Create session: ~p", [Sid]),
mnesia:transaction(
mnesia:async_dirty(
fun() ->
mnesia:write(
#http_bind{id = Sid,
@ -340,6 +364,7 @@ init([Sid, Key, IP]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event({become_controller, C2SPid}, StateName, StateData) ->
erlang:monitor(process, C2SPid),
case StateData#state.input of
cancel ->
{next_state, StateName, StateData#state{
@ -404,6 +429,14 @@ handle_sync_event({stop,close}, _From, _StateName, StateData) ->
handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) ->
Reply = ok,
{stop, normal, Reply, StateData};
handle_sync_event(deactivate_socket, _From, StateName, StateData) ->
%% Input = case StateData#state.input of
%% cancel ->
%% queue:new();
%% Q ->
%% Q
%% end,
{reply, ok, StateName, StateData#state{waiting_input = false}};
handle_sync_event({stop,Reason}, _From, _StateName, StateData) ->
?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
Reply = ok,
@ -537,6 +570,9 @@ handle_info({timeout, ShaperTimer, _}, StateName,
#state{shaper_timer = ShaperTimer} = StateData) ->
{next_state, StateName, StateData#state{shaper_timer = undefined}};
handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName,
#state{waiting_input = C2SPid} = StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
@ -804,10 +840,10 @@ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
?DEBUG("Looking for session: ~p", [Sid]),
case mnesia:dirty_read({http_bind, Sid}) of
[] ->
case get_session(Sid) of
{error, _} ->
{error, not_exists};
[#http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess] ->
{ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}->
NewStream =
case StreamStart of
true ->
@ -1225,7 +1261,36 @@ check_default_xmlns({xmlelement, Name, Attrs, Els} = El) ->
%% Print a warning in log file if this is not the case.
check_bind_module(XmppDomain) ->
case gen_mod:is_loaded(XmppDomain, mod_http_bind) of
true -> ok;
true -> true;
false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), but the module mod_http_bind is not started.~n"
"Check your 'modules' section in your ejabberd configuration file.",[])
"Check your 'modules' section in your ejabberd configuration file.",[]),
false
end.
make_sid() ->
sha:sha(term_to_binary({now(), make_ref()}))
++ "-" ++ ejabberd_cluster:node_id().
get_session(SID) ->
case string:tokens(SID, "-") of
[_, NodeID] ->
case ejabberd_cluster:get_node_by_id(NodeID) of
Node when Node == node() ->
case mnesia:dirty_read({http_bind, SID}) of
[] ->
{error, enoent};
[Session] ->
{ok, Session}
end;
Node ->
case catch rpc:call(Node, mnesia, dirty_read,
[{http_bind, SID}], 5000) of
[Session] ->
{ok, Session};
_ ->
{error, enoent}
end
end;
_ ->
{error, enoent}
end.

View File

@ -77,9 +77,12 @@
%%% API
%%%----------------------------------------------------------------------
start(ID, Key, IP) ->
update_tables(),
mnesia:create_table(http_poll,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, http_poll)}]),
mnesia:add_table_copy(http_poll, node(), ram_copies),
supervisor:start_child(ejabberd_http_poll_sup, [ID, Key, IP]).
start_link(ID, Key, IP) ->
@ -115,9 +118,9 @@ process([], #request{data = Data,
{ok, ID1, Key, NewKey, Packet} ->
ID = if
(ID1 == "0") or (ID1 == "mobile") ->
NewID = sha:sha(term_to_binary({now(), make_ref()})),
NewID = make_sid(),
{ok, Pid} = start(NewID, "", IP),
mnesia:transaction(
mnesia:async_dirty(
fun() ->
mnesia:write(#http_poll{id = NewID,
pid = Pid})
@ -272,7 +275,13 @@ handle_event(_Event, StateName, StateData) ->
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
handle_sync_event({send, Packet}, _From, StateName, StateData) ->
Output = StateData#state.output ++ [lists:flatten(Packet)],
Packet2 = if
is_binary(Packet) ->
binary_to_list(Packet);
true ->
Packet
end,
Output = StateData#state.output ++ [lists:flatten(Packet2)],
Reply = ok,
{reply, Reply, StateName, StateData#state{output = Output}};
@ -350,7 +359,7 @@ handle_info(_, StateName, StateData) ->
%% Returns: any
%%----------------------------------------------------------------------
terminate(_Reason, _StateName, StateData) ->
mnesia:transaction(
mnesia:async_dirty(
fun() ->
mnesia:delete({http_poll, StateData#state.id})
end),
@ -375,19 +384,19 @@ terminate(_Reason, _StateName, StateData) ->
%%%----------------------------------------------------------------------
http_put(ID, Key, NewKey, Packet) ->
case mnesia:dirty_read({http_poll, ID}) of
[] ->
case get_session(ID) of
{error, _} ->
{error, not_exists};
[#http_poll{pid = FsmRef}] ->
{ok, #http_poll{pid = FsmRef}} ->
gen_fsm:sync_send_all_state_event(
FsmRef, {http_put, Key, NewKey, Packet})
end.
http_get(ID) ->
case mnesia:dirty_read({http_poll, ID}) of
[] ->
case get_session(ID) of
{error, _} ->
{error, not_exists};
[#http_poll{pid = FsmRef}] ->
{ok, #http_poll{pid = FsmRef}} ->
gen_fsm:sync_send_all_state_event(FsmRef, http_get)
end.
@ -446,3 +455,39 @@ get_jid(Type, ParsedPacket) ->
false ->
jlib:make_jid("","","")
end.
update_tables() ->
case catch mnesia:table_info(http_poll, local_content) of
false ->
mnesia:delete_table(http_poll);
_ ->
ok
end.
make_sid() ->
sha:sha(term_to_binary({now(), make_ref()}))
++ "-" ++ ejabberd_cluster:node_id().
get_session(SID) ->
case string:tokens(SID, "-") of
[_, NodeID] ->
case ejabberd_cluster:get_node_by_id(NodeID) of
Node when Node == node() ->
case mnesia:dirty_read({http_poll, SID}) of
[] ->
{error, enoent};
[Session] ->
{ok, Session}
end;
Node ->
case catch rpc:call(Node, mnesia, dirty_read,
[{http_poll, SID}], 5000) of
[Session] ->
{ok, Session};
_ ->
{error, enoent}
end
end;
_ ->
{error, enoent}
end.

View File

@ -0,0 +1,202 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_websocket.erl
%%% Author : Eric Cestari <ecestari@process-one.net>
%%% Purpose : XMPP Websocket support
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module (ejabberd_http_ws).
-author('ecestari@process-one.net').
-behaviour(gen_fsm).
% External exports
-export([
start/1,
start_link/1,
init/1,
handle_event/3,
handle_sync_event/4,
code_change/4,
handle_info/3,
terminate/3,
send/2,
setopts/2,
sockname/1, peername/1,
controlling_process/2,
become_controller/2,
close/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
-record(state, {
socket,
timeout,
timer,
input = "",
waiting_input = false, %% {ReceiverPid, Tag}
last_receiver,
ws}).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
-else.
-define(FSMOPTS, []).
-endif.
-define(WEBSOCKET_TIMEOUT, 300000).
%
%
%%%%----------------------------------------------------------------------
%%%% API
%%%%----------------------------------------------------------------------
start(WS) ->
supervisor:start_child(ejabberd_wsloop_sup, [WS]).
start_link(WS) ->
gen_fsm:start_link(?MODULE, [WS],?FSMOPTS).
send({http_ws, FsmRef, _IP}, Packet) ->
gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
true ->
gen_fsm:send_all_state_event(FsmRef, {activate, self()});
_ ->
ok
end.
sockname(_Socket) ->
{ok, {{0, 0, 0, 0}, 0}}.
peername({http_ws, _FsmRef, IP}) ->
{ok, IP}.
controlling_process(_Socket, _Pid) ->
ok.
become_controller(FsmRef, C2SPid) ->
gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
close({http_ws, FsmRef, _IP}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
%%% Internal
init([WS]) ->
%% Read c2s options from the first ejabberd_c2s configuration in
%% the config file listen section
%% TODO: We should have different access and shaper values for
%% each connector. The default behaviour should be however to use
%% the default c2s restrictions if not defined for the current
%% connector.
Opts = ejabberd_c2s_config:get_c2s_limits(),
WSTimeout = case ejabberd_config:get_local_option({websocket_timeout,
?MYNAME}) of
%% convert seconds of option into milliseconds
Int when is_integer(Int) -> Int*1000;
undefined -> ?WEBSOCKET_TIMEOUT
end,
Socket = {http_ws, self(), WS:get(ip)},
?DEBUG("Client connected through websocket ~p", [Socket]),
ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
Timer = erlang:start_timer(WSTimeout, self(), []),
{ok, loop, #state{
socket = Socket,
timeout = WSTimeout,
timer = Timer,
ws = WS}}.
handle_event({activate, From}, StateName, StateData) ->
case StateData#state.input of
"" ->
{next_state, StateName,
StateData#state{waiting_input = {From, ok}}};
Input ->
Receiver = From,
Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)},
{next_state, StateName, StateData#state{input = "",
waiting_input = false,
last_receiver = Receiver
}}
end.
handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) ->
Packet2 = if
is_binary(Packet) ->
Packet;
true ->
list_to_binary(Packet)
end,
?DEBUG("sending on websocket : ~p ", [Packet2]),
WS:send(Packet2),
{reply, ok, StateName, StateData};
handle_sync_event(close, _From, _StateName, StateData) ->
Reply = ok,
{stop, normal, Reply, StateData}.
handle_info({browser, Packet}, StateName, StateData)->
NewState = case StateData#state.waiting_input of
false ->
Input = [StateData#state.input|Packet],
StateData#state{input = Input};
{Receiver, _Tag} ->
Receiver ! {tcp, StateData#state.socket,Packet},
cancel_timer(StateData#state.timer),
Timer = erlang:start_timer(StateData#state.timeout, self(), []),
StateData#state{waiting_input = false,
last_receiver = Receiver,
timer = Timer}
end,
{next_state, StateName, NewState};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
{stop, normal, StateData};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData}.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
terminate(_Reason, _StateName, _StateData) -> ok.
cancel_timer(Timer) ->
erlang:cancel_timer(Timer),
receive
{timeout, Timer, _} ->
ok
after 0 ->
ok
end.

View File

@ -0,0 +1,275 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_websocket.erl
%%% Author : Eric Cestari <ecestari@process-one.net>
%%% Purpose : XMPP Websocket support
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
%%%
%%% Some code lifted from MISULTIN - WebSocket misultin_websocket.erl - >-|-|-(°>
%%% (http://github.com/ostinelli/misultin/blob/master/src/misultin_websocket.erl)
%%% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Joe Armstrong.
%%% All rights reserved.
%%%
%%% Code portions from Joe Armstrong have been originally taken under MIT license at the address:
%%% <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html>
%%%
%%% BSD License
%%%
%%% Redistribution and use in source and binary forms, with or without modification, are permitted provided
%%% that the following conditions are met:
%%%
%%% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
%%% following disclaimer.
%%% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
%%% the following disclaimer in the documentation and/or other materials provided with the distribution.
%%% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
%%% products derived from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
%%% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
%%% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
%%% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
%%% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
%%% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
%%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%%% ==========================================================================================================
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%----------------------------------------------------------------------
-module (ejabberd_websocket).
-author('ecestari@process-one.net').
-export([connect/2, check/2, is_acceptable/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
check(_Path, Headers)->
% set supported websocket protocols, order does matter
VsnSupported = [{'draft-hixie', 0}, {'draft-hixie', 68}],
% checks
check_websockets(VsnSupported, Headers).
% Checks if websocket can be access by client
% If origins are set in configuration, check if it belongs
% If origins not set, access is open.
is_acceptable(#ws{origin=Origin, protocol=Protocol,
headers = Headers, acceptable_origins = Origins})->
ClientProtocol = lists:keyfind("Sec-WebSocket-Protocol",1, Headers),
case {(Origin == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of
{false, _, _} ->
?DEBUG("client does not come from authorized origin", []),
false;
{_, false, _} ->
?DEBUG("Client did not ask for protocol", []),
true;
{_, {_, P}, P} ->
?DEBUG("Protocoles are matching", []),
true;
_ -> false
end.
% Connect and handshake with Websocket.
connect(#ws{vsn = Vsn, socket = Socket, origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) ->
% build handshake
HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Origin, Host, Port}),
% send handshake back
%?DEBUG("building handshake response : ~p", [HandshakeServer]),
SockMod:send(Socket, HandshakeServer),
Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, host = Host}, self()),
%?DEBUG("Ws0 : ~p",[Ws0]),
% add data to ws record and spawn controlling process
{ok, WsHandleLoopPid} = WsLoop:start_link(Ws0),
erlang:monitor(process, WsHandleLoopPid),
% set opts
case SockMod of
gen_tcp ->
inet:setopts(Socket, [{packet, 0}, {active, true}]);
_ ->
SockMod:setopts(Socket, [{packet, 0}, {active, true}])
end,
% start listening for incoming data
ws_loop(Socket, none, WsHandleLoopPid, SockMod, WsAutoExit).
check_websockets([], _Headers) -> false;
check_websockets([Vsn|T], Headers) ->
case check_websocket(Vsn, Headers) of
false -> check_websockets(T, Headers);
{true, Vsn} -> {true, Vsn}
end.
% Function: {true, Vsn} | false
% Description: Check if the incoming request is a websocket request.
check_websocket({'draft-hixie', 0} = Vsn, Headers) ->
%?DEBUG("testing for websocket protocol ~p", [Vsn]),
% set required headers
RequiredHeaders = [
{'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore},
{"Sec-Websocket-Key1", ignore}, {"Sec-Websocket-Key2", ignore}
],
% check for headers existance
case check_headers(Headers, RequiredHeaders) of
true ->
% return
{true, Vsn};
_RemainingHeaders ->
%?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
false
end;
check_websocket({'draft-hixie', 68} = Vsn, Headers) ->
%?DEBUG("testing for websocket protocol ~p", [Vsn]),
% set required headers
RequiredHeaders = [
{'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore}
],
% check for headers existance
case check_headers(Headers, RequiredHeaders) of
true -> {true, Vsn};
_RemainingHeaders ->
%?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
false
end;
check_websocket(_Vsn, _Headers) -> false. % not implemented
% Function: true | [{RequiredTag, RequiredVal}, ..]
% Description: Check if headers correspond to headers requirements.
check_headers(Headers, RequiredHeaders) ->
F = fun({Tag, Val}) ->
% see if the required Tag is in the Headers
case lists:keyfind(Tag, 1, Headers) of
false -> true; % header not found, keep in list
{Tag, HVal} ->
%?DEBUG("check: ~p", [{Tag, HVal,Val }]),
case Val of
ignore -> false; % ignore value -> ok, remove from list
HVal -> false; % expected val -> ok, remove from list
_ -> true % val is different, keep in list
end
end
end,
case lists:filter(F, RequiredHeaders) of
[] -> true;
MissingHeaders -> MissingHeaders
end.
% Function: List
% Description: Builds the server handshake response.
handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Origin, Host, Port}) ->
% build data
{_, Key1} = lists:keyfind("Sec-Websocket-Key1",1, Headers),
{_, Key2} = lists:keyfind("Sec-Websocket-Key2",1, Headers),
% handshake needs body of the request, still need to read it [TODO: default recv timeout hard set, will be exported when WS protocol is final]
case SocketMod of
gen_tcp ->
inet:setopts(Sock, [{packet, raw}, {active, false}]);
_ ->
SocketMod:setopts(Sock, [{packet, raw}, {active, false}])
end,
Body = case SocketMod:recv(Sock, 8, 30*1000) of
{ok, Bin} -> Bin;
{error, timeout} ->
?WARNING_MSG("timeout in reading websocket body", []),
<<>>;
_Other ->
?ERROR_MSG("tcp error treating data: ~p", [_Other]),
<<>>
end,
%?DEBUG("got content in body of websocket request: ~p, ~p", [Body,string:join([Host, Path],"/")]),
% prepare handhsake response
["HTTP/1.1 101 WebSocket Protocol Handshake\r\n",
"Upgrade: WebSocket\r\n",
"Connection: Upgrade\r\n",
"Sec-WebSocket-Origin: ", Origin, "\r\n",
"Sec-WebSocket-Location: ws://",
string:join([Host, integer_to_list(Port)],":"),
"/",string:join(Path,"/") , "\r\n\r\n",
build_challenge({'draft-hixie', 0}, {Key1, Key2, Body})
];
handshake({'draft-hixie', 68}, _Sock,_SocketMod, _Headers, {Path, Origin, Host, Port}) ->
% prepare handhsake response
["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
"Upgrade: WebSocket\r\n",
"Connection: Upgrade\r\n",
"WebSocket-Origin: ", Origin , "\r\n",
"WebSocket-Location: ws://",
lists:concat([Host, integer_to_list(Port)]),
"/",string:join(Path,"/"), "\r\n\r\n"
].
% Function: List
% Description: Builds the challenge for a handshake response.
% Code portions from Sergio Veiga <http://sergioveiga.com/index.php/2010/06/17/websocket-handshake-76-in-erlang/>
build_challenge({'draft-hixie', 0}, {Key1, Key2, Key3}) ->
Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
Blank1 = length([D || D <- Key1, D =:= 32]),
Blank2 = length([D || D <- Key2, D =:= 32]),
Part1 = list_to_integer(Ikey1) div Blank1,
Part2 = list_to_integer(Ikey2) div Blank2,
Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
erlang:md5(Ckey).
ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit) ->
receive
{tcp, Socket, Data} ->
handle_data(Buffer, Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
{tcp_closed, Socket} ->
?DEBUG("tcp connection was closed, exit", []),
% close websocket and custom controlling loop
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
{'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
case Reason of
normal ->
%?DEBUG("linked websocket controlling loop stopped.", []);
ok;
_ ->
?ERROR_MSG("linked websocket controlling loop crashed with reason: ~p", [Reason])
end,
% demonitor
erlang:demonitor(Ref),
% close websocket and custom controlling loop
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
{send, Data} ->
?DEBUG("sending data to websocket: ~p", [Data]),
SocketMode:send(Socket, iolist_to_binary([0,Data,255])),
ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit);
shutdown ->
?DEBUG("shutdown request received, closing websocket with pid ~p", [self()]),
% close websocket and custom controlling loop
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
_Ignored ->
?WARNING_MSG("received unexpected message, ignoring: ~p", [_Ignored]),
ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit)
end.
% Buffering and data handling
handle_data(none,<<0,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
handle_data(<<>>, T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
handle_data(none, <<>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
ws_loop(Socket, none, WsHandleLoopPid, SocketMode, WsAutoExit);
handle_data(L, <<255,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
WsHandleLoopPid ! {browser, iolist_to_binary(L)},
handle_data(none, T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
handle_data(L, <<H/utf8,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
handle_data(iolist_to_binary([L, H]), T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
handle_data(<<>>, L, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
ws_loop(Socket, L, WsHandleLoopPid, SocketMode, WsAutoExit).
% Close socket and custom handling loop dependency
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
case WsAutoExit of
true ->
% kill custom handling loop process
exit(WsHandleLoopPid, kill);
false ->
% the killing of the custom handling loop process is handled in the loop itself -> send event
WsHandleLoopPid ! closed
end,
% close main socket
SocketMode:close(Socket).

80
src/web/ejabberd_ws.erl Normal file
View File

@ -0,0 +1,80 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_websocket.erl
%%% Author : Eric Cestari <ecestari@process-one.net>
%%% Purpose : Websocket support
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
%%% Slightly adapted from :
% ==========================================================================================================
% MISULTIN - Websocket Request
%
% >-|-|-(°>
%
% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>.
% All rights reserved.
%
% BSD License
%
% Redistribution and use in source and binary forms, with or without modification, are permitted provided
% that the following conditions are met:
%
% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
% following disclaimer.
% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
% the following disclaimer in the documentation and/or other materials provided with the distribution.
% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
% products derived from this software without specific prior written permission.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
% ==========================================================================================================
-module(ejabberd_ws, [Ws, SocketPid]).
-vsn("0.6.1").
% API
-export([raw/0, get/1, send/1]).
% includes
-include("ejabberd_http.hrl").
% ============================ \/ API ======================================================================
% Description: Returns raw websocket content.
raw() ->
Ws.
% Description: Get websocket info.
get(socket) ->
Ws#ws.socket;
get(socket_mode) ->
Ws#ws.sockmod;
get(ip) ->
Ws#ws.ip;
get(vsn) ->
Ws#ws.vsn;
get(origin) ->
Ws#ws.origin;
get(host) ->
Ws#ws.host;
get(path) ->
Ws#ws.path;
get(headers) ->
Ws#ws.headers.
% send data
send(Data) ->
SocketPid ! {send, Data}.
% ============================ /\ API ======================================================================
% ============================ \/ INTERNAL FUNCTIONS =======================================================
% ============================ /\ INTERNAL FUNCTIONS =======================================================

View File

@ -117,7 +117,7 @@ start(_Host, _Opts) ->
% mod_http_bind is already started so it will not be started again
ok;
{error, Error} ->
{'EXIT', {start_child_error, Error}}
exit({start_child_error, Error})
end.
stop(_Host) ->
@ -125,14 +125,22 @@ stop(_Host) ->
ok ->
ok;
{error, Error} ->
{'EXIT', {terminate_child_error, Error}}
exit({terminate_child_error, Error})
end,
case supervisor:delete_child(ejabberd_sup, ejabberd_http_bind_sup) of
ok ->
ok;
{error, Error2} ->
exit({delete_child_error, Error2})
end.
setup_database() ->
migrate_database(),
mnesia:create_table(http_bind,
[{ram_copies, [node()]},
{attributes, record_info(fields, http_bind)}]).
{local_content, true},
{attributes, record_info(fields, http_bind)}]),
mnesia:add_table_copy(http_bind, node(), ram_copies).
migrate_database() ->
case catch mnesia:table_info(http_bind, attributes) of
@ -142,4 +150,10 @@ migrate_database() ->
%% Since the stored information is not important, instead
%% of actually migrating data, let's just destroy the table
mnesia:delete_table(http_bind)
end,
case catch mnesia:table_info(http_bind, local_content) of
false ->
mnesia:delete_table(http_bind);
_ ->
ok
end.

414
src/web/pshb_http.erl Normal file
View File

@ -0,0 +1,414 @@
%%%----------------------------------------------------------------------
%%% File : pshb_http.erl
%%% Author : Eric Cestari <ecestari@process-one.net>
%%% Purpose :
%%% Created :01-09-2010
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
%%%
%%% {5280, ejabberd_http, [
%%% http_poll,
%%% web_admin,
%%% {request_handlers, [{["pshb"], pshb_http}]} % this should be added
%%% ]}
%%%
%%% To post to a node the content of the file "sam.atom" on the "foo", on the localhost virtual host, using cstar@localhost
%%% curl -u cstar@localhost:encore -i -X POST http://localhost:5280/pshb/localhost/foo -d @sam.atom
%%%
-module (pshb_http).
-author('ecestari@process-one.net').
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
-include("mod_pubsub/pubsub.hrl").
-export([process/2]).
process([Domain | _Rest] = LocalPath, #request{auth = Auth} = Request)->
UD = get_auth(Auth),
Module = backend(Domain),
case catch out(Module, Request, Request#request.method, LocalPath,UD) of
{'EXIT', Error} ->
?ERROR_MSG("Error while processing ~p : ~n~p", [LocalPath, Error]),
error(500);
Result ->
Result
end.
get_auth(Auth) ->
case Auth of
{SJID, P} ->
case jlib:string_to_jid(SJID) of
error ->
undefined;
#jid{user = U, server = S} ->
case ejabberd_auth:check_password(U, S, P) of
true ->
{U, S};
false ->
undefined
end
end;
_ ->
undefined
end.
out(Module, Args, 'GET', [Domain,Node]=Uri, _User) ->
case Module:tree_action(get_host(Uri), get_node, [get_host(Uri),get_collection(Uri)]) of
{error, Error} ->
error(Error);
#pubsub_node{options = Options}->
AccessModel = lists:keyfind(access_model, 1, Options),
case AccessModel of
{access_model, open} ->
Items = lists:sort(fun(X,Y)->
{DateX, _} = X#pubsub_item.modification,
{DateY, _} = Y#pubsub_item.modification,
DateX > DateY
end, Module:get_items(
get_host(Uri),
get_collection(Uri))),
case Items of
[] -> ?DEBUG("Items : ~p ~n", [collection(get_collection(Uri),
collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])]),
{200, [{"Content-Type", "application/atom+xml"}],
collection(get_collection(Uri),
collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])};
_ ->
#pubsub_item{modification = {LastDate, _JID}} = LastItem = hd(Items),
Etag =generate_etag(LastItem),
IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
if IfNoneMatch==Etag
->
success(304);
true ->
XMLEntries= [item_to_entry(Args,Domain, Node,Entry)||Entry <- Items],
{200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}],
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
++ xml:element_to_string(
collection(get_collection(Uri), collection_uri(Args,Domain,Node),
calendar:now_to_universal_time(LastDate), "", XMLEntries))}
end
end;
{access_model, Access} ->
?INFO_MSG("Uri ~p requested. access_model is ~p. HTTP access denied unless access_model =:= open",
[Uri, Access]),
error(?ERR_FORBIDDEN)
end
end;
out(Module, Args, 'POST', [_D, _Node]=Uri, {_User, _Domain} = UD) ->
publish_item(Module, Args, Uri, uniqid(false), UD);
out(Module, Args, 'PUT', [_D, _Node, Slug]=Uri, {_User, _Domain} = UD) ->
publish_item(Module, Args, Uri, Slug, UD);
out(Module, _Args, 'DELETE', [_D, Node, Id]= Uri, {User, UDomain}) ->
Jid = jlib:make_jid({User, UDomain, ""}),
case Module:delete_item(get_host(Uri), list_to_binary(Node), Jid, Id) of
{error, Error} -> error(Error);
{result, _Res} -> success(200)
end;
out(Module, Args, 'PUT', [_Domain, Node]= Uri, {User, UDomain}) ->
Host = get_host(Uri),
Jid = jlib:make_jid({User, UDomain, ""}),
Payload = xml_stream:parse_element(Args#request.data),
ConfigureElement = case xml:get_subtag(Payload, "configure") of
false ->[];
{xmlelement, _, _, SubEls}->SubEls
end,
case Module:set_configure(Host, list_to_binary(Node), Jid, ConfigureElement, Args#request.lang) of
{result, []} -> success(200);
{error, Error} -> error(Error)
end;
out(Module, Args, 'GET', [Domain]=Uri, From)->
Host = get_host(Uri),
?DEBUG("Host = ~p", [Host]),
case Module:tree_action(Host, get_subnodes, [Host, <<>>, From ]) of
[] ->
?DEBUG("Error getting URI ~p : ~p",[Uri, From]),
error(?ERR_ITEM_NOT_FOUND);
Collections ->
{200, [{"Content-Type", "application/atomsvc+xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
++ xml:element_to_string(service(Args,Domain, Collections))}
end;
out(Module, Args, 'POST', [Domain]=Uri, {User, UDomain})->
Host = get_host(Uri),
Payload = xml_stream:parse_element(Args#request.data),
{Node, Type} = case xml:get_subtag(Payload, "create") of
false -> {<<>>,"flat"};
E ->
{list_to_binary(get_tag_attr_or_default("node", E,"")),
get_tag_attr_or_default("type", E,"flat")}
end,
ConfigureElement = case xml:get_subtag(Payload, "configure") of
false ->[];
{xmlelement, _, _, SubEls}->SubEls
end,
Jid = jlib:make_jid({User, UDomain, ""}),
case Module:create_node(Host, Domain, Node, Jid, Type, all, ConfigureElement) of
{error, Error} ->
?ERROR_MSG("Error create node via HTTP : ~p",[Error]),
error(Error); % will probably detail more
{result, [Result]} ->
{200, [{"Content-Type", "application/xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
++ xml:element_to_string(Result)}
end;
out(Module,_Args, 'DELETE', [_Domain, Node] = Uri, {User, UDomain})->
Host = get_host(Uri),
Jid = jlib:make_jid({User, UDomain, ""}),
BinNode = list_to_binary(Node),
case Module:delete_node(Host, BinNode, Jid) of
{error, Error} -> error(Error);
{result, []} ->
{200, [],[]}
end;
out(Module, Args, 'GET', [Domain, Node, _Item]=URI, _) ->
Failure = fun(Error)->
?DEBUG("Error getting URI ~p : ~p",[URI, Error]),
error(Error)
end,
Success = fun(Item)->
Etag =generate_etag(Item),
IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
if IfNoneMatch==Etag
->
success(304);
true ->
{200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
++ xml:element_to_string(item_to_entry(Args, Domain,Node, Item))}
end
end,
get_item(Module, URI, Failure, Success);
out(_Module,_,Method,Uri,undefined) ->
?DEBUG("Error, ~p not authorized for ~p : ~p",[ Method,Uri]),
error(?ERR_FORBIDDEN).
get_item(Module, Uri, Failure, Success)->
?DEBUG(" Module:get_item(~p, ~p,~p)", [get_host(Uri), get_collection(Uri), get_member(Uri)]),
case Module:get_item(get_host(Uri), get_collection(Uri), get_member(Uri)) of
{error, Reason} ->
Failure(Reason);
#pubsub_item{}=Item ->
Success(Item)
end.
publish_item(Module, Args, [Domain, Node | _R] = Uri, Slug, {User, Domain})->
Payload = xml_stream:parse_element(Args#request.data),
[FilteredPayload]=xml:remove_cdata([Payload]),
%FilteredPayload2 = case xml:get_subtag(FilteredPayload, "app:edited") ->
% {xmlelement, Name, Attrs, [{cdata, }]}
case Module:publish_item(get_host(Uri),
Domain,
get_collection(Uri),
jlib:make_jid(User,Domain, ""),
Slug,
[FilteredPayload]) of
{result, [_]} ->
?DEBUG("Publishing to ~p~n",[entry_uri(Args, Domain, Node,Slug)]),
{201, [{"location", entry_uri(Args, Domain,Node,Slug)}], Payload};
{error, Error} ->
error(Error)
end.
generate_etag(#pubsub_item{modification={{_, D2, D3}, _JID}})->integer_to_list(D3+D2).
get_host([Domain|_Rest])-> "pubsub."++Domain.
get_collection([_Domain, Node|_Rest])->list_to_binary(Node).
get_member([_Domain, _Node, Member])->
Member.
collection_uri(R, Domain, Node) ->
base_uri(R, Domain)++ "/" ++ b2l(Node).
entry_uri(R,Domain, Node, Id)->
collection_uri(R,Domain, Node)++"/"++b2l(Id).
base_uri(#request{host=Host, port=Port}, Domain)->
"http://"++Host++":"++i2l(Port)++"/pshb/" ++ Domain.
item_to_entry(Args,Domain, Node,#pubsub_item{itemid={Id,_}, payload=Entry}=Item)->
[R]=xml:remove_cdata(Entry),
item_to_entry(Args, Domain, Node, Id, R, Item).
item_to_entry(Args,Domain, Node, Id,{xmlelement, "entry", Attrs, SubEl},
#pubsub_item{modification={ Secs, JID} }) ->
Date = calendar:now_to_local_time(Secs),
{_User, Domain, _}=jlib:jid_tolower(JID),
SubEl2=[{xmlelement, "app:edited", [], [{xmlcdata, w3cdtf(Date)}]},
{xmlelement, "updated", [],[{xmlcdata, w3cdtf(Date)}]},
{xmlelement, "author", [],[{xmlelement, "name", [], [{xmlcdata, list_to_binary(jlib:jid_to_string(JID))}]}]},
{xmlelement, "link",[{"rel", "edit"},
{"href", entry_uri(Args,Domain,Node, Id)}],[] },
{xmlelement, "id", [],[{xmlcdata, entry_uri(Args, Domain, Node, Id)}]}
| SubEl],
{xmlelement, "entry", [{"xmlns:app","http://www.w3.org/2007/app"}|Attrs], SubEl2};
% Don't do anything except adding xmlns
item_to_entry(_Args,_Domain, Node, _Id, {xmlelement, Name, Attrs, Subels}=Element, _Item)->
case proplists:is_defined("xmlns",Attrs) of
true -> Element;
false -> {xmlelement, Name, [{"xmlns", Node}|Attrs], Subels}
end.
collection(Title, Link, Updated, _Id, Entries)->
{xmlelement, "feed", [{"xmlns", "http://www.w3.org/2005/Atom"},
{"xmlns:app", "http://www.w3.org/2007/app"}], [
{xmlelement, "title", [],[{xmlcdata, Title}]},
{xmlelement, "generator", [],[{xmlcdata, <<"ejabberd">>}]},
{xmlelement, "updated", [],[{xmlcdata, w3cdtf(Updated)}]},
{xmlelement, "link", [{"href", Link}, {"rel", "self"}], []},
{xmlelement, "id", [], [{xmlcdata, list_to_binary(Link)}]},
{xmlelement, "title", [],[{xmlcdata, Title}]} |
Entries
]}.
service(Args, Domain,Collections)->
{xmlelement, "service", [{"xmlns", "http://www.w3.org/2007/app"},
{"xmlns:atom", "http://www.w3.org/2005/Atom"},
{"xmlns:app", "http://www.w3.org/2007/app"}],[
{xmlelement, "workspace", [],[
{xmlelement, "atom:title", [],[{xmlcdata,"Pubsub node Feed for " ++Domain}]} |
lists:map(fun(#pubsub_node{nodeid={_Server, Id}, type=_Type})->
{xmlelement, "collection", [{"href", collection_uri(Args,Domain, Id)}], [
{xmlelement, "atom:title", [], [{xmlcdata, Id}]}
]}
end, Collections)
]}
]}.
%% simple output functions
error({xmlelement, "error", Attrs, _}=Error) ->
Value = list_to_integer(xml:get_attr_s("code", Attrs)),
{Value, [{"Content-type", "application/xml"}], xml:element_to_string(Error)};
error(404)->
{404, [], "Not Found"};
error(403)->
{403, [], "Forbidden"};
error(500)->
{500, [], "Internal server error"};
error(401)->
{401, [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],"Unauthorized"};
error(Code)->
{Code, [], ""}.
success(200)->
{200, [], ""};
success(Code)->
{Code, [], ""}.
backend(Domain)->
Modules = gen_mod:loaded_modules(Domain),
case lists:member(mod_pubsub_odbc, Modules) of
true -> mod_pubsub_odbc;
_ -> mod_pubsub
end.
% Code below is taken (with some modifications) from the yaws webserver, which
% is distributed under the folowing license:
%
% This software (the yaws webserver) is free software.
% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
% Any use or misuse of the source code is hereby freely allowed.
%
% 1. Redistributions of source code must retain the above copyright
% notice as well as this list of conditions.
%
% 2. Redistributions in binary form must reproduce the above copyright
% notice as well as this list of conditions.
%%% Create W3CDTF (http://www.w3.org/TR/NOTE-datetime) formatted date
%%% w3cdtf(GregSecs) -> "YYYY-MM-DDThh:mm:ssTZD"
%%%
uniqid(false)->
{T1, T2, T3} = now(),
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]));
uniqid(Slug) ->
Slut = string:to_lower(Slug),
S = string:substr(Slut, 1, 9),
{_T1, T2, T3} = now(),
lists:flatten(io_lib:fwrite("~s-~.16B~.16B", [S, T2, T3])).
w3cdtf(Date) -> %1 Date = calendar:gregorian_seconds_to_datetime(GregSecs),
{{Y, Mo, D},{H, Mi, S}} = Date,
[UDate|_] = calendar:local_time_to_universal_time_dst(Date),
{DiffD,{DiffH,DiffMi,_}}=calendar:time_difference(UDate,Date),
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi).
%%% w3cdtf's helper function
w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) when DiffH < 12, DiffH /= 0 ->
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++ add_zero(DiffMi);
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD == 0 ->
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++
add_zero(DiffMi);
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi /= 0 ->
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
add_zero(S) ++ "-" ++ add_zero(23-DiffH) ++
":" ++ add_zero(60-DiffMi);
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi == 0 ->
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
add_zero(S) ++ "-" ++ add_zero(24-DiffH) ++
":" ++ add_zero(DiffMi);
w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) when DiffH == 0 ->
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
add_zero(S) ++ "Z".
add_zero(I) when is_integer(I) -> add_zero(i2l(I));
add_zero([A]) -> [$0,A];
add_zero(L) when is_list(L) -> L.
i2l(I) when is_integer(I) -> integer_to_list(I);
i2l(L) when is_list(L) -> L.
b2l(B) when is_binary(B) -> binary_to_list(B);
b2l(L) when is_list(L) -> L.
get_tag_attr_or_default(AttrName, Element, Default)->
case xml:get_tag_attr_s(AttrName, Element) of
"" -> Default;
Val -> Val
end.

View File

@ -0,0 +1,18 @@
-module (websocket_test).
-export([start/1, loop/1]).
% callback on received websockets data
start(Ws) ->
spawn(?MODULE, loop, [Ws]).
loop(Ws) ->
receive
{browser, Data} ->
Ws:send(["received '", Data, "'"]),
loop(Ws);
_Ignore ->
loop(Ws)
after 5000 ->
Ws:send("pushing!"),
loop(Ws)
end.

View File

@ -31,6 +31,7 @@
new/2,
parse/2,
close/1,
change_callback_pid/2,
parse_element/1]).
-define(XML_START, 0).
@ -110,6 +111,8 @@ new(CallbackPid, MaxSize) ->
size = 0,
maxsize = MaxSize}.
change_callback_pid(State, CallbackPid) ->
State#xml_stream_state{callback_pid = CallbackPid}.
parse(#xml_stream_state{callback_pid = CallbackPid,
port = Port,

140
tools/joincluster Executable file
View File

@ -0,0 +1,140 @@
#!/bin/bash
# Add the current ejabberd node in a cluster
# copyright (c) 2010 ProcessOne
#
# This script is proprietary software and cannot be published or redistribute.
# Return Code:
# 0 : groovy baby
# 11 : erl not found
# 12 : erlc not found
# 20 : database dir doesn't exist
# 21 : database dir not writable
# 21 : database dir variable not set
# 30 : network issue
# 31 : node names incompatibility
function error
{
echo "Error: $1" >&2
exit $2
}
echo "--------------------------------------------------------------------"
echo ""
echo "ejabberd cluster configuration"
echo ""
echo "This ejabberd node will be configured for use in an ejabberd cluster."
echo "IMPORTANT: all local data from the database will be lost, and"
echo "cluster database will be initialized. All data from the master"
echo "node will be replicated to this one."
echo ""
echo "--------------------------------------------------------------------"
echo "Press any key to continue, or Ctrl+C to stop now"
read foo
echo ""
echo "Make sure you have a running remote master ejabberd node"
echo "Before continuing, you must copy the ~/.erlang.cookie file from"
echo "remote master node and check ejabberd.cfg compatibility."
echo "e.g. hosts definition must match on all nodes"
echo ""
echo "The remote master node name is defined as ERLANG_NODE into"
echo "ejabberdctl.cfg on that remote node."
echo ""
echo -n "Remote master node name: "
read REMOTE
echo ""
cont=Y
ping -q -c 1 ${REMOTE#*@} 2>/dev/null >/dev/null
[ $? -eq 0 ] || {
echo "Cannot ping ${REMOTE#*@}. Are you sure network setup is correct ?"
echo -n "Should we continue anyway ? (Y/n) "
read cont
}
cont=`echo $cont | tr a-z A-Z`
[ "$cont" == "Y" ] || error "Check your network configuration (dns, firewall, etc...)" 30
HERE=`which "$0"`
BASE=`dirname $HERE`/..
ROOTDIR=`cd $BASE; pwd`
. $ROOTDIR/bin/ejabberdctl stop 2>/dev/null >/dev/null
NAME=-name
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname
PA=/tmp/clustersetup_$$
CLUSTERSETUP=clustersetup
CLUSTERSETUP_ERL=$PA/$CLUSTERSETUP.erl
REMOTENAME=-name
[ "$REMOTE" = "${REMOTE%.*}" ] && REMOTENAME=-sname
[ "$REMOTENAME" = "$NAME" ] || {
echo "IMPORTANT!: node names are incompatible"
echo "Remote node name is $REMOTE"
echo "Local node name is $ERLANG_NODE"
echo ""
echo "Both node names must be short or fqdn names."
echo "Using short and fqdn names is impossible."
echo ""
error "incompatible node names" 31
}
set -o errexit
set -o nounset
echo "Using commands:"
which erl || error "can't find erl" 11
which erlc || error "can't find erlc" 12
echo ""
[ -d $EJABBERD_DB ] && rm -Rf $EJABBERD_DB
mkdir $EJABBERD_DB || error "$EJABBERD_DB cannot be created" 20
[ -w $EJABBERD_DB ] || error "$EJABBERD_DB directory is not writable" 21
cd $ROOTDIR
mkdir -p $PA
cat <<EOF > $CLUSTERSETUP_ERL
-module($CLUSTERSETUP).
-export([start/0]).
set_table_copy(Table, _Node, {badrpc, Reason}) ->
io:format("Error: cannot get storage type for table ~p on node $REMOTE:~n ~p~n",[Table, Reason]);
set_table_copy(Table, Node, Type) ->
io:format("setting table ~p to mode ~p~n",[Table, Type]),
case mnesia:add_table_copy(Table, Node, Type) of
{aborted, _} ->
mnesia:change_table_copy_type(Table, Node, Type);
_ ->
ok
end.
set_tables({badrpc, Reason}) ->
io:format("ERROR: cannot get tables list on $REMOTE : ~p~n",[Reason]);
set_tables([]) ->
ok;
set_tables([schema | Tables]) ->
set_tables(Tables);
set_tables([s2s | Tables]) ->
set_tables(Tables);
set_tables([session | Tables]) ->
set_tables(Tables);
set_tables([Table | Tables]) ->
set_table_copy(Table, node(),
rpc:call('$REMOTE', mnesia, table_info, [Table, storage_type])),
set_tables(Tables).
start() ->
io:format("~n",[]),
set_table_copy(schema, node(), disc_copies),
set_tables(rpc:call('$REMOTE', mnesia, system_info, [tables])),
halt(0).
EOF
erlc -o $PA $CLUSTERSETUP_ERL
erl $NAME $ERLANG_NODE -pa $PA $KERNEL_OPTS -mnesia extra_db_nodes "['$REMOTE']" dir "\"$EJABBERD_DB\"" -s mnesia -s $CLUSTERSETUP start
rm -Rf $PA
echo "End."
echo "Check that there is no error in the above messages."

93
tools/leavecluster Executable file
View File

@ -0,0 +1,93 @@
#!/bin/bash
# Remove the current ejabberd node in a cluster
# Return Code:
# 0 : groovy baby
# 11 : erl not found
# 12 : erlc not found
# 20 : database dir doesn't exist
# 21 : database dir not writable
# 21 : database dir variable not set
function error
{
echo "Error: $1" >&2
exit $2
}
echo "--------------------------------------------------------------------"
echo ""
echo "ejabberd cluster configuration"
echo ""
echo "This ejabberd node will be removed from the cluster."
echo "IMPORTANT: this node will be stopped. At least one other clustered"
echo "node must be running."
echo ""
echo "--------------------------------------------------------------------"
echo "Press any key to continue, or Ctrl+C to stop now"
read foo
echo ""
HERE=`which "$0"`
BASE=`dirname $HERE`/..
ROOTDIR=`cd $BASE; pwd`
. $ROOTDIR/bin/ejabberdctl stop 2>/dev/null >/dev/null
$ROOTDIR/bin/ejabberdctl stopped
PA=/tmp/clustersetup_$$
CLUSTERSETUP=clustersetup
CLUSTERSETUP_ERL=$PA/$CLUSTERSETUP.erl
set -o errexit
set -o nounset
echo "Using commands:"
which erl || error "can't find erl" 11
which erlc || error "can't find erlc" 12
echo ""
cd $ROOTDIR
mkdir -p $PA
cat <<EOF > $CLUSTERSETUP_ERL
-module($CLUSTERSETUP).
-export([start/0]).
del_table_copy(Table, Node) ->
case mnesia:del_table_copy(Table, Node) of
{aborted, Reason} -> io:format("Error: can not remove ~p table: ~p~n", [Table, Reason]);
_ -> io:format("table ~p removed from cluster~n", [Table])
end.
del_tables([],_) ->
ok;
del_tables([schema | Tables], Node) ->
del_tables(Tables, Node);
del_tables([Table | Tables], Node) ->
del_table_copy(Table, Node),
del_tables(Tables, Node).
start() ->
io:format("~n",[]),
Removed = node(),
case mnesia:system_info(running_db_nodes)--[Removed] of
[] -> io:format("Error: no other node running in the cluster~n");
Nodes ->
del_tables(mnesia:system_info(local_tables), Removed),
mnesia:stop(),
case rpc:call(hd(Nodes), mnesia, del_table_copy, [schema, Removed]) of
{badrpc,Reason} -> io:format("Error: can not unregister node ~p from cluster: ~p~n", [Removed, Reason]);
{aborted,Reason} -> io:format("Error: can not unregister node ~p from cluster: ~p~n", [Removed, Reason]);
{atomic, ok} ->
mnesia:delete_schema([Removed]),
io:format("node ~p removed from cluster~n", [Removed])
end
end,
halt(0).
EOF
erlc -o $PA $CLUSTERSETUP_ERL
erl $NAME $ERLANG_NODE -pa $PA $KERNEL_OPTS -mnesia dir "\"$EJABBERD_DB\"" -s mnesia -s $CLUSTERSETUP start
rm -Rf $PA
echo "End."
echo "Check that there is no error in the above messages."

38
tools/tcp-tuning.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/sh
ulimit -n 562524
# /etc/security/limits.conf
#@ejabberd soft nofile 562000
#@ejabberd hard nofile 562000
# 4KB send buffer, 20,480 connections max at worst case
#echo 83886080 > /proc/sys/net/core/wmem_max
#echo 83886080 > /proc/sys/net/core/wmem_default
echo 327680000 > /proc/sys/net/core/wmem_max
echo 327680000 > /proc/sys/net/core/wmem_default
# 16KB receive buffer, 20,480 connections max at worst case
#echo 335544320 > /proc/sys/net/core/rmem_max
#echo 335544320 > /proc/sys/net/core/rmem_default
echo 1310720000 > /proc/sys/net/core/rmem_max
echo 1310720000 > /proc/sys/net/core/rmem_default
# Max open files
echo 562524 > /proc/sys/fs/file-max
# Fast port recycling (TIME_WAIT)
echo 1 >/proc/sys/net/ipv4/tcp_tw_recycle
echo 1 >/proc/sys/net/ipv4/tcp_tw_reuse
# TIME_WAIT buckets increased
echo 360000 > /proc/sys/net/ipv4/tcp_max_tw_buckets
# FIN timeout decreased
echo 15 > /proc/sys/net/ipv4/tcp_fin_timeout
# SYN backlog increased
echo 65536 > /proc/sys/net/ipv4/tcp_max_syn_backlog
# SYN cookies enabled
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
# Local port range maximized
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
# Netdev backlog increased
echo 100000 > /proc/sys/net/core/netdev_max_backlog
# Do no save route metrics
echo 1 > /proc/sys/net/ipv4/route/flush
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
# Interface transmit queuelen increased
ifconfig eth0 txqueuelen 10000