diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..11269d0ee
--- /dev/null
+++ b/.gitignore
@@ -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
diff --git a/contrib/ejabberd-modules.repo b/contrib/ejabberd-modules.repo
deleted file mode 100644
index dfd4a1d17..000000000
--- a/contrib/ejabberd-modules.repo
+++ /dev/null
@@ -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
diff --git a/doc/guide.html b/doc/guide.html
index 57873a873..50cdca74a 100644
--- a/doc/guide.html
+++ b/doc/guide.html
@@ -1944,8 +1944,8 @@ Mnesia as backend.
(see 3.2) as backend.
‘_ldap’, this means that the module needs an LDAP server as backend.
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
_odbc suffix in ejabberd config file. You can use a relational
database for the following data:
-
Last connection date and time: Use mod_last_odbc instead of
@@ -1956,6 +1956,7 @@ Last connection date and time: Use mod_last_odbc instead of
- Users’ VCARD: Use mod_vcard_odbc instead of mod_vcard.
- Private XML storage: Use mod_private_odbc instead of mod_private.
- User rules for blocking communications: Use mod_privacy_odbc instead of mod_privacy.
+
- Pub-Sub nodes, items and subscriptions: Use mod_pubsub_odbc instead of mod_pubsub.
You can find more
contributed modules on the
ejabberd website. Please remember that these contributions might not work or
diff --git a/doc/guide.tex b/doc/guide.tex
index 044fab829..8bcd9b25f 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -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
diff --git a/doc/http_post.md b/doc/http_post.md
new file mode 100644
index 000000000..359fb2adf
--- /dev/null
+++ b/doc/http_post.md
@@ -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///`.
+
+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
+
+
+
+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
+
+
+
+### 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 "" http://localhost:5280/pshb/localhost
+
+configure element (as per XEP-60) can be passed in the pubsub body.
+
+ $ cat createnode.xml
+
+
+
+ http://jabber.org/protocol/pubsub#node_config
+
+ Princely Musings (Atom)
+ 1028
+ Atom
+
+
+
+ $ 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
+
+
+
+### Editing a node configuration ###
+
+ $ cat editnode.xml
+
+
+
+
+ http://jabber.org/protocol/pubsub#node_config
+
+ Princely Musings (Atom)
+ 1
+ 1
+ 1
+ 10
+ 604800
+ roster
+
+
+
+
+
+ $ 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
+
+
+
diff --git a/src/Makefile.in b/src/Makefile.in
index 4e561813d..312983395 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -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) \
diff --git a/src/cache_tab.erl b/src/cache_tab.erl
new file mode 100644
index 000000000..b05e78b0e
--- /dev/null
+++ b/src/cache_tab.erl
@@ -0,0 +1,563 @@
+%%%-------------------------------------------------------------------
+%%% File : cache_tab.erl
+%%% Author : Evgeniy Khramtsov
+%%% Description : Caching key-value table
+%%%
+%%% Created : 29 Aug 2010 by Evgeniy Khramtsov
+%%%
+%%%
+%%% 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).
diff --git a/src/cache_tab_sup.erl b/src/cache_tab_sup.erl
new file mode 100644
index 000000000..a49593f5e
--- /dev/null
+++ b/src/cache_tab_sup.erl
@@ -0,0 +1,53 @@
+%%%-------------------------------------------------------------------
+%%% File : cache_tab_sup.erl
+%%% Author : Evgeniy Khramtsov
+%%% Description : Cache tables supervisor
+%%%
+%%% Created : 30 Aug 2010 by Evgeniy Khramtsov
+%%%
+%%%
+%%% 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
+%%====================================================================
diff --git a/src/configure b/src/configure
index d515abc22..ff51ca020 100755
--- a/src/configure
+++ b/src/configure
@@ -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 .
#
#
# 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 &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`
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
diff --git a/src/configure.ac b/src/configure.ac
index 1d25dd871..5492f429a 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -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
diff --git a/src/ejabberd.app b/src/ejabberd.app
index 67653d241..ea85a9f26 100644
--- a/src/ejabberd.app
+++ b/src/ejabberd.app
@@ -2,7 +2,7 @@
{application, ejabberd,
[{description, "ejabberd"},
- {vsn, "2.1.x"},
+ {vsn, "2.2.x"},
{modules, [acl,
adhoc,
configure,
diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl
index e1f0cfd37..fcc2aeac7 100644
--- a/src/ejabberd.hrl
+++ b/src/ejabberd.hrl
@@ -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).
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index 22279046f..8726805dd 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -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;
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index 1f86e2cc5..94f46eb79 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -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.
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index e8c37b740..570f4f636 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -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,
+ ""
+ ""
+ ).
+
-define(STREAM_TRAILER, "").
-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,
+
+ "\n"
+ "\n"
+ "\n"
+ " \n"
+ "\n\0".
diff --git a/src/ejabberd_c2s.hrl b/src/ejabberd_c2s.hrl
new file mode 100644
index 000000000..381e042ba
--- /dev/null
+++ b/src/ejabberd_c2s.hrl
@@ -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}).
\ No newline at end of file
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index ab3271b80..e575d7c40 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -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.
diff --git a/src/ejabberd_cluster.erl b/src/ejabberd_cluster.erl
new file mode 100644
index 000000000..190e35202
--- /dev/null
+++ b/src/ejabberd_cluster.erl
@@ -0,0 +1,177 @@
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_cluster.erl
+%%% Author : Evgeniy Khramtsov
+%%% Description :
+%%%
+%%% Created : 2 Apr 2010 by Evgeniy Khramtsov
+%%%-------------------------------------------------------------------
+-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()).
diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl
index 5ff4d08b4..88d77aed9 100644
--- a/src/ejabberd_frontend_socket.erl
+++ b/src/ejabberd_frontend_socket.erl
@@ -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.
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index a179dfda4..0465dedba 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -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.
diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl
index da1dadc0e..3c9f5d3da 100644
--- a/src/ejabberd_local.erl
+++ b/src/ejabberd_local.erl
@@ -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.
diff --git a/src/ejabberd_node_groups.erl b/src/ejabberd_node_groups.erl
index fc1b4ded5..055b96aef 100644
--- a/src/ejabberd_node_groups.erl
+++ b/src/ejabberd_node_groups.erl
@@ -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.
%%--------------------------------------------------------------------
diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl
index a3d0a8af8..0482a27dc 100644
--- a/src/ejabberd_receiver.erl
+++ b/src/ejabberd_receiver.erl
@@ -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.
diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl
index 87ba99094..ba4072cfa 100644
--- a/src/ejabberd_router.erl
+++ b/src/ejabberd_router.erl
@@ -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.
-
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index 8ab520f56..3e5807eee 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -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
diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl
index a1c641280..658786962 100644
--- a/src/ejabberd_s2s_out.erl
+++ b/src/ejabberd_s2s_out.erl
@@ -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},
diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl
index bd07bfea8..942f0fa4d 100644
--- a/src/ejabberd_service.erl
+++ b/src/ejabberd_service.erl
@@ -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}.
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 4fc8123e7..e98ccf338 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -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.
diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl
index 25ff64c27..a6cf0d0fe 100644
--- a/src/ejabberd_socket.erl
+++ b/src/ejabberd_socket.erl
@@ -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.
diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl
index f0119326a..d48deeaef 100644
--- a/src/ejabberd_sup.erl
+++ b/src/ejabberd_sup.erl
@@ -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]}}.
diff --git a/src/ejabberdctl.template b/src/ejabberdctl.template
index fecdecc86..a1e7902c7 100644
--- a/src/ejabberdctl.template
+++ b/src/ejabberdctl.template
@@ -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;;
diff --git a/src/etop_defs.hrl b/src/etop_defs.hrl
new file mode 100644
index 000000000..664de6197
--- /dev/null
+++ b/src/etop_defs.hrl
@@ -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}).
diff --git a/src/etop_tr.erl b/src/etop_tr.erl
new file mode 100644
index 000000000..c5ae79cc0
--- /dev/null
+++ b/src/etop_tr.erl
@@ -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.
+
diff --git a/src/expat_erl.c b/src/expat_erl.c
index f6b552c59..60cd72c88 100644
--- a/src/expat_erl.c
+++ b/src/expat_erl.c
@@ -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, "
+ 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)
diff --git a/src/floodcheck.erl b/src/floodcheck.erl
new file mode 100644
index 000000000..f2a20e46a
--- /dev/null
+++ b/src/floodcheck.erl
@@ -0,0 +1,205 @@
+%%%-------------------------------------------------------------------
+%%% File : floodcheck.erl
+%%% Author : Christophe Romain
+%%% Description :
+%%%
+%%% Created : 11 Sep 2008 by Christophe Romain
+%%%-------------------------------------------------------------------
+-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
diff --git a/src/http_p1.erl b/src/http_p1.erl
new file mode 100644
index 000000000..1a8a1e630
--- /dev/null
+++ b/src/http_p1.erl
@@ -0,0 +1,337 @@
+%%%----------------------------------------------------------------------
+%%% File : http_p1.erl
+%%% Author : Emilio Bustos
+%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
+%%% Created : 29 Jul 2010 by Emilio Bustos
+%%%
+%%%
+%%% 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
diff --git a/src/jlib.hrl b/src/jlib.hrl
index fe844be02..cb89f91a5 100644
--- a/src/jlib.hrl
+++ b/src/jlib.hrl
@@ -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").
diff --git a/src/licence.hrl b/src/licence.hrl
new file mode 100644
index 000000000..a82dab15e
--- /dev/null
+++ b/src/licence.hrl
@@ -0,0 +1 @@
+-define(IS_VALID, true).
diff --git a/src/mod_admin_p1.erl b/src/mod_admin_p1.erl
new file mode 100644
index 000000000..18e391027
--- /dev/null
+++ b/src/mod_admin_p1.erl
@@ -0,0 +1,1166 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_admin_p1.erl
+%%% Author : Badlop / Mickael Remond / Christophe Romain
+%%% Purpose : Administrative functions and commands for ProcessOne customers
+%%% Created : 21 May 2008 by Badlop
+%%%
+%%%
+%%% 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
+%%%
+%%%-------------------------------------------------------------------
+
+%%% @doc Administrative functions and commands for ProcessOne customers
+%%%
+%%% This ejabberd module defines and registers many ejabberd commands
+%%% that can be used for performing administrative tasks in ejabberd.
+%%%
+%%% The documentation of all those commands can be read using ejabberdctl
+%%% in the shell.
+%%%
+%%% The commands can be executed using any frontend to ejabberd commands.
+%%% Currently ejabberd_xmlrpc and ejabberdctl. Using ejabberd_xmlrpc it is possible
+%%% to call any ejabberd command. However using ejabberdctl not all commands
+%%% can be called.
+
+%%% Changelog:
+%%%
+%%% 0.8 - 26 September 2008 - badlop
+%%% - added patch for parameter 'Push'
+%%%
+%%% 0.7 - 20 August 2008 - badlop
+%%% - module converted to ejabberd commands
+%%%
+%%% 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_admin_p1).
+-author('ProcessOne').
+
+-export([start/2, stop/1,
+ %% Erlang
+ restart_module/2,
+ %% Accounts
+ create_account/3,
+ delete_account/2,
+ change_password/3,
+ rename_account/4,
+ check_users_registration/1,
+ %% Sessions
+ get_presence/2,
+ get_resources/2,
+ %% Vcard
+ set_nickname/3,
+ %% Roster
+ add_rosteritem/6,
+ delete_rosteritem/3,
+ link_contacts/4,
+ unlink_contacts/2,
+ link_contacts/5, unlink_contacts/3, % Versions with Push parameter
+ get_roster/2,
+ get_roster_with_presence/2,
+ add_contacts/3,
+ remove_contacts/3,
+ %% PubSub
+ update_status/4,
+ delete_status/3,
+ %% Transports
+ transport_register/5,
+ %% Stanza
+ send_chat/3,
+ send_message/4,
+ send_notification/6, %% ON
+ send_stanza/3
+ ]).
+
+-include("ejabberd.hrl").
+-include("ejabberd_commands.hrl").
+-include("mod_roster.hrl").
+-include("jlib.hrl").
+
+-ifdef(EJABBERD1).
+-record(session, {sid, usr, us, priority}). %% ejabberd 1.1.x
+-else.
+-record(session, {sid, usr, us, priority, info}). %% ejabberd 2.x.x
+-endif.
+
+start(_Host, _Opts) ->
+ ejabberd_commands:register_commands(commands()).
+
+stop(_Host) ->
+ ejabberd_commands:unregister_commands(commands()).
+
+%%%
+%%% Register commands
+%%%
+
+commands() ->
+ [
+ #ejabberd_commands{name = restart_module, tags = [erlang],
+ desc = "Stop an ejabberd module, reload code and start",
+ module = ?MODULE, function = restart_module,
+ args = [{module, string}, {host, string}],
+ result = {res, rescode}},
+
+ %% Similar to ejabberd_admin register
+ #ejabberd_commands{name = create_account, tags = [accounts],
+ desc = "Create an ejabberd user account",
+ longdesc = "This command is similar to 'register'.",
+ module = ?MODULE, function = create_account,
+ args = [{user, string}, {server, string},
+ {password, string}],
+ result = {res, integer}},
+
+ %% Similar to ejabberd_admin unregister
+ #ejabberd_commands{name = delete_account, tags = [accounts],
+ desc = "Remove an account from the server",
+ longdesc = "This command is similar to 'unregister'.",
+ module = ?MODULE, function = delete_account,
+ args = [{user, string}, {server, string}],
+ result = {res, integer}},
+
+ #ejabberd_commands{name = rename_account, tags = [accounts],
+ desc = "Change an acount name",
+ longdesc = "Creates a new account "
+ "and copies the roster from the old one. "
+ "Offline messages and private storage are lost.",
+ module = ?MODULE, function = rename_account,
+ args = [{user, string}, {server, string},
+ {newuser, string}, {newserver, string}],
+ result = {res, integer}},
+
+ %% This command is also implemented in mod_admin_contrib
+ #ejabberd_commands{name = change_password, tags = [accounts],
+ desc = "Change the password on behalf of the given user",
+ module = ?MODULE, function = change_password,
+ args = [{user, string}, {server, string},
+ {newpass, string}],
+ result = {res, integer}},
+
+ %% This command is also implemented in mod_admin_contrib
+ #ejabberd_commands{name = set_nickname, tags = [vcard],
+ desc = "Define user nickname",
+ longdesc = "Set/updated nickname in the user Vcard. "
+ "Other informations are unchanged.",
+ module = ?MODULE, function = set_nickname,
+ args = [{user, string}, {server, string}, {nick,string}],
+ result = {res, integer}},
+
+ %% This command is also implemented in mod_admin_contrib
+ #ejabberd_commands{name = add_rosteritem, tags = [roster],
+ desc = "Add an entry in a user's roster",
+ longdesc = "Some arguments are:\n"
+ " - jid: the JabberID of the user you would "
+ "like to add in user roster on the server.\n"
+ " - subs: the state of the roster item subscription.\n\n"
+ "The allowed values of the 'subs' argument are: both, to, from or none.\n"
+ " - none: presence packets are not sent between parties.\n"
+ " - both: presence packets are sent in both direction.\n"
+ " - to: the user sees the presence of the given JID.\n"
+ " - from: the JID specified sees the user presence.\n\n"
+ "Don't forget that roster items should keep symmetric: "
+ "when adding a roster item for a user, "
+ "you have to do the symmetric roster item addition.\n\n",
+ module = ?MODULE, function = add_rosteritem,
+ args = [{user, string}, {server, string}, {jid, string},
+ {group, string}, {nick, string}, {subs, string}],
+ result = {res, integer}},
+
+ %% This command is also implemented in mod_admin_contrib
+ #ejabberd_commands{name = delete_rosteritem, tags = [roster],
+ desc = "Remove an entry for a user roster",
+ longdesc = "Roster items should be kept symmetric: "
+ "when removing a roster item for a user you have to do "
+ "the symmetric roster item removal. \n\n"
+ "This mechanism bypass the standard roster approval "
+ "addition mechanism and should only be used for server "
+ "administration or server integration purpose.",
+ module = ?MODULE, function = delete_rosteritem,
+ args = [{user, string}, {server, string}, {jid, string}],
+ result = {res, integer}},
+
+ #ejabberd_commands{name = link_contacts, tags = [roster],
+ desc = "Add a symmetrical entry in two users roster",
+ longdesc = "jid1 is the JabberID of the user1 you would "
+ "like to add in user2 roster on the server.\n"
+ "nick1 is the nick of user1.\n"
+ "jid2 is the JabberID of the user2 you would like to "
+ "add in user1 roster on the server.\n"
+ "nick2 is the nick of user2.\n\n"
+ "This mechanism bypass the standard roster approval "
+ "addition mechanism "
+ "and should only be userd for server administration or "
+ "server integration purpose.",
+ module = ?MODULE, function = link_contacts,
+ args = [{jid1, string}, {nick1, string}, {jid2, string}, {nick2, string}],
+ result = {res, integer}},
+
+ #ejabberd_commands{name = unlink_contacts, tags = [roster],
+ desc = "Remove a symmetrical entry in two users roster",
+ longdesc = "jid1 is the JabberID of the user1.\n"
+ "jid2 is the JabberID of the user2.\n\n"
+ "This mechanism bypass the standard roster approval "
+ "addition mechanism "
+ "and should only be userd for server administration or "
+ "server integration purpose.",
+ module = ?MODULE, function = unlink_contacts,
+ args = [{jid1, string}, {jid2, string}],
+ result = {res, integer}},
+
+ %% TODO: test
+ %% This command is not supported by ejabberdctl
+ #ejabberd_commands{name = add_contacts, tags = [roster],
+ desc = "Call add_rosteritem with subscription \"both\" "
+ "for a given list of contacts",
+ module = ?MODULE, function = add_contacts,
+ args = [{user, string},
+ {server, string},
+ {contacts, {list,
+ {contact, {tuple, [
+ {jid, string},
+ {group, string},
+ {nick, string}
+ ]}}
+ }}
+ ],
+ result = {res, integer}},
+ %% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, add_contacts, [{struct,
+ %% [{user, "badlop"},
+ %% {server, "localhost"},
+ %% {contacts, {array, [{struct, [
+ %% {contact, {array, [{struct, [
+ %% {group, "Friends"},
+ %% {jid, "tom@localhost"},
+ %% {nick, "Tom"}
+ %% ]}]}}
+ %% ]}]}}
+ %% ]
+ %% }]}).
+
+ %% TODO: test
+ %% This command is not supported by ejabberdctl
+ #ejabberd_commands{name = remove_contacts, tags = [roster],
+ desc = "Call del_rosteritem for a list of contacts",
+ module = ?MODULE, function = remove_contacts,
+ args = [{user, string},
+ {server, string},
+ {contacts, {list,
+ {jid, string}
+ }}
+ ],
+ result = {res, integer}},
+ %% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, remove_contacts, [{struct,
+ %% [{user, "badlop"},
+ %% {server, "localhost"},
+ %% {contacts, {array, [{struct, [
+ %% {jid, "tom@localhost"}
+ %% ]}]}}
+ %% ]
+ %% }]}).
+
+ %% TODO: test
+ %% This command is not supported by ejabberdctl
+ #ejabberd_commands{name = check_users_registration, tags = [roster],
+ desc = "List registration status for a list of users",
+ module = ?MODULE, function = check_users_registration,
+ args = [{users, {list,
+ {auser, {tuple, [
+ {user, string},
+ {server, string}
+ ]}}
+ }}
+ ],
+ result = {users, {list,
+ {auser, {tuple, [
+ {user, string},
+ {server, string},
+ {status, integer}
+ ]}}
+ }}},
+ %% xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, check_users_registration, [{struct,
+ %% [{users, {array, [{struct, [
+ %% {auser, {array, [{struct, [
+ %% {user, "badlop"},
+ %% {server, "localhost"}
+ %% ]}]}}
+ %% ]}]}}]
+ %% }]}).
+
+ %% This command is also implemented in mod_admin_contrib
+ #ejabberd_commands{name = get_roster, tags = [roster],
+ desc = "Retrieve the roster for a given user",
+ longdesc = "Returns a list of the contacts in a user "
+ "roster.\n\n"
+ "Also returns the state of the contact subscription. "
+ "Subscription can be either "
+ " \"none\", \"from\", \"to\", \"both\". "
+ "Pending can be \"in\", \"out\" or \"none\".",
+ module = ?MODULE, function = get_roster,
+ args = [{user, string}, {server, string}],
+ result = {contacts, {list, {contact, {tuple, [{jid, string}, {group, string},
+ {nick, string}, {subscription, string}, {pending, string}]}}}}},
+
+ #ejabberd_commands{name = get_roster_with_presence, tags = [roster],
+ desc = "Retrieve the roster for a given user including "
+ "presence information",
+ longdesc = "The 'show' value contains the user presence. "
+ "It can take limited values:\n"
+ " - available\n"
+ " - chat (Free for chat)\n"
+ " - away\n"
+ " - dnd (Do not disturb)\n"
+ " - xa (Not available, extended away)\n"
+ " - unavailable (Not connected)\n\n"
+ "'status' is a free text defined by the user client.\n\n"
+ "Also returns the state of the contact subscription. "
+ "Subscription can be either "
+ "\"none\", \"from\", \"to\", \"both\". "
+ "Pending can be \"in\", \"out\" or \"none\".\n\n"
+ "Note: If user is connected several times, only keep the"
+ " resource with the highest non-negative priority.",
+ module = ?MODULE, function = get_roster_with_presence,
+ args = [{user, string}, {server, string}],
+ result = {contacts, {list, {contact, {tuple, [{jid, string}, {resource, string}, {group, string}, {nick, string}, {subscription, string}, {pending, string}, {show, string}, {status, string}]}}}}},
+
+ #ejabberd_commands{name = get_presence, tags = [session],
+ desc = "Retrieve the resource with highest priority, "
+ "and its presence (show and status message) for a given "
+ "user.",
+ longdesc = "The 'jid' value contains the user jid with "
+ "resource.\n"
+ "The 'show' value contains the user presence flag. "
+ "It can take limited values:\n"
+ " - available\n"
+ " - chat (Free for chat)\n"
+ " - away\n"
+ " - dnd (Do not disturb)\n"
+ " - xa (Not available, extended away)\n"
+ " - unavailable (Not connected)\n\n"
+ "'status' is a free text defined by the user client.",
+ module = ?MODULE, function = get_presence,
+ args = [{user, string}, {server, string}],
+ result = {presence, {tuple, [{jid, string},
+ {show, string},
+ {status, string}]}}},
+
+ #ejabberd_commands{name = get_resources, tags = [session],
+ desc = "Get all available resources for a given user",
+ module = ?MODULE, function = get_resources,
+ args = [{user, string}, {server, string}],
+ result = {resources, {list, {resource, string}}}},
+
+ %% PubSub
+ #ejabberd_commands{name = update_status, tags = [pubsub],
+ desc = "Update the status on behalf of a user",
+ longdesc =
+ "jid: the JabberID of the user. Example: user@domain.\n\n"
+ "node: the reference of the node to publish on.\n"
+ "Example: http://process-one.net/protocol/availability\n\n"
+ "itemid: the reference of the item (in our case profile ID).\n\n"
+ "payload: the payload of the publish operation in XML.\n"
+ "The string has to be properly escaped to comply with XML formalism of XML RPC.",
+ module = ?MODULE, function = update_status,
+ args = [{jid, string}, {node, string}, {itemid, string}, {payload, string}],
+ result = {res, string}},
+
+ #ejabberd_commands{name = delete_status, tags = [pubsub],
+ desc = "Delete the status on behalf of a user",
+ longdesc =
+ "jid: the JabberID of the user. Example: user@domain.\n\n"
+ "node: the reference of the node to publish on.\n"
+ "Example: http://process-one.net/protocol/availability\n\n"
+ "itemid: the reference of the item (in our case profile ID).",
+ module = ?MODULE, function = delete_status,
+ args = [{jid, string}, {node, string}, {itemid, string}],
+ result = {res, string}},
+
+ #ejabberd_commands{name = transport_register, tags = [transports],
+ desc = "Register a user in a transport",
+ module = ?MODULE, function = transport_register,
+ args = [{host, string}, {transport, string},
+ {jidstring, string}, {username, string}, {password, string}],
+ result = {res, string}},
+
+ %% Similar to mod_admin_contrib send_message which sends a headline
+ #ejabberd_commands{name = send_chat, tags = [stanza],
+ desc = "Send chat message to a given user",
+ module = ?MODULE, function = send_chat,
+ args = [{from, string}, {to, string}, {body, string}],
+ result = {res, integer}},
+
+ #ejabberd_commands{name = send_message, tags = [stanza],
+ desc = "Send normal message to a given user",
+ module = ?MODULE, function = send_message,
+ args = [{from, string}, {to, string},
+ {subject, string}, {body, string}],
+ result = {res, integer}},
+
+ #ejabberd_commands{name = send_notification, tags = [stanza],
+ desc = "Send ON notification to XMPP client sessions",
+ module = ?MODULE, function = send_notification,
+ args = [{send_from, string}, {send_to, string}, {host, string},
+ {unread_items, string}, {message, string},
+ {type, string}],
+ result = {res, integer}},
+
+ #ejabberd_commands{name = send_stanza, tags = [stanza],
+ desc = "Send stanza to a given user",
+ longdesc = "If Stanza contains a \"from\" field, "
+ "then it overrides the passed from argument."
+ "If Stanza contains a \"to\" field, then it overrides "
+ "the passed to argument.",
+ module = ?MODULE, function = send_stanza,
+ args = [{user, string}, {server, string},
+ {stanza, string}],
+ result = {res, integer}}
+ ].
+
+
+%%%
+%%% Erlang
+%%%
+
+restart_module(ModuleString, Host) ->
+ Module = list_to_atom(ModuleString),
+ List = gen_mod:loaded_modules_with_opts(Host),
+ Opts = case lists:keysearch(Module,1, List) of
+ {value, {_, O}} -> O;
+ _ -> []
+ end,
+ gen_mod:stop_module(Host, Module),
+ code:delete(Module),
+ code:purge(Module),
+ gen_mod:start_module(Host, Module, Opts),
+ ok.
+
+
+%%%
+%%% Accounts
+%%%
+
+create_account(U, S, P) ->
+ case ejabberd_auth:try_register(U, S, P) of
+ {atomic, ok} ->
+ 0;
+ {atomic, exists} ->
+ 409;
+ _ ->
+ 1
+ end.
+
+delete_account(U, S) ->
+ Fun = fun() -> ejabberd_auth:remove_user(U, S) end,
+ user_action(U, S, Fun, ok).
+
+change_password(U, S, P) ->
+ Fun = fun() -> ejabberd_auth:set_password(U, S, P) end,
+ user_action(U, S, Fun, ok).
+
+rename_account(U, S, NU, NS) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ case ejabberd_auth:get_password(U, S) of
+ false ->
+ 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_roster2(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_roster2(RU, RS)) of
+ [#roster{name=OldNick, groups=OldGroups}|_] -> {OldNick, extract_group(OldGroups)};
+ [] -> {NU, []}
+ end,
+ JIDStr = jlib:jid_to_string({RU, RS, RE}),
+ link_contacts2(NewJID, NewNick, NewGroup, JIDStr, Nick, Group, true),
+ unlink_contacts2(OldJID, JIDStr, true)
+ end, Roster),
+ ejabberd_auth:remove_user(U, S),
+ 0;
+ {atomic, exists} ->
+ 409;
+ _ ->
+ 1
+ end
+ end;
+ false ->
+ 404
+ end.
+
+
+%%%
+%%% Sessions
+%%%
+
+get_presence(U, S) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ {Resource, Show, Status} = get_presence2(U, S),
+ FullJID = case Resource of
+ [] ->
+ lists:flatten([U,"@",S]);
+ _ ->
+ lists:flatten([U,"@",S,"/",Resource])
+ end,
+ {FullJID, Show, Status};
+ false ->
+ 404
+ end.
+
+get_resources(U, S) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ get_resources2(U, S);
+ false ->
+ 404
+ end.
+
+
+%%%
+%%% Vcard
+%%%
+
+set_nickname(U, S, N) ->
+ 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).
+
+
+%%%
+%%% Roster
+%%%
+
+add_rosteritem(U, S, JID, G, N, Subs) ->
+ add_rosteritem(U, S, JID, G, N, Subs, true).
+
+add_rosteritem(U, S, JID, G, N, Subs, Push) ->
+ Fun = fun() -> add_rosteritem2(U, S, JID, N, G, Subs, Push) end,
+ user_action(U, S, Fun, {atomic, ok}).
+
+link_contacts(JID1, Nick1, JID2, Nick2) ->
+ link_contacts(JID1, Nick1, JID2, Nick2, true).
+
+link_contacts(JID1, Nick1, JID2, Nick2, Push) ->
+ {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_contacts2(JID1, Nick1, JID2, Nick2, Push) of
+ {atomic, ok} ->
+ 0;
+ _ ->
+ 1
+ end;
+ _ ->
+ 404
+ end.
+
+delete_rosteritem(U, S, JID) ->
+ Fun = fun() -> del_rosteritem(U, S, JID) end,
+ user_action(U, S, Fun, {atomic, ok}).
+
+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 {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
+ {true, true} ->
+ case unlink_contacts2(JID1, JID2, Push) of
+ {atomic, ok} ->
+ 0;
+ _ ->
+ 1
+ end;
+ _ ->
+ 404
+ end.
+
+get_roster(U, S) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ format_roster(get_roster2(U, S));
+ false ->
+ 404
+ end.
+
+get_roster_with_presence(U, S) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ format_roster_with_presence(get_roster2(U, S));
+ false ->
+ 404
+ end.
+
+add_contacts(U, S, Contacts) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ JID1 = jlib:jid_to_string({U, S, ""}),
+ lists:foldl(fun({JID2, Group, Nick}, Acc) ->
+ {PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
+ case ejabberd_auth:is_user_exists(PU, PS) of
+ true ->
+ case link_contacts2(JID1, "", Group, JID2, Nick, Group, true) of
+ {atomic, ok} -> Acc;
+ _ -> 1
+ end;
+ false ->
+ Acc
+ end
+ end, 0, Contacts);
+ false ->
+ 404
+ end.
+
+remove_contacts(U, S, Contacts) ->
+ case ejabberd_auth:is_user_exists(U, S) of
+ true ->
+ JID1 = jlib:jid_to_string({U, S, ""}),
+ 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_contacts2(JID1, JID2, true) of
+ {atomic, ok} -> Acc;
+ _ -> 1
+ end;
+ false ->
+ Acc
+ end
+ end, 0, Contacts);
+ false ->
+ 404
+ end.
+
+check_users_registration(Users) ->
+ lists:map(fun({U, S}) ->
+ Registered = case ejabberd_auth:is_user_exists(U, S) of
+ true -> 1;
+ false -> 0
+ end,
+ {U, S, Registered}
+ end, Users).
+
+
+%%%
+%%% PubSub
+%%%
+
+update_status(JidString, NodeString, Itemid, PayloadString) ->
+ Publisher = jlib:string_to_jid(JidString),
+ Host = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
+ ServerHost = Publisher#jid.lserver,
+ Node = mod_pubsub_on:string_to_node(NodeString),
+ Payload = [xml_stream:parse_element(PayloadString)],
+ ?DEBUG("PayloadString: ~n~p~nPayload elements: ~n~p", [PayloadString, Payload]),
+ case mod_pubsub_on:publish_item_nothook(Host, ServerHost, Node, Publisher, Itemid, Payload) of
+ {result, _} ->
+ "OK";
+ {error, {xmlelement, _, _, _} = XmlEl} ->
+ "ERROR: " ++ xml:element_to_string(XmlEl);
+ {error, ErrorAtom} when is_atom(ErrorAtom) ->
+ "ERROR: " ++ atom_to_list(ErrorAtom);
+ {error, ErrorString} when is_list(ErrorString) ->
+ "ERROR: " ++ ErrorString
+ end.
+
+delete_status(JidString, NodeString, Itemid) ->
+ Publisher = jlib:string_to_jid(JidString),
+ Host = jlib:jid_tolower(jlib:jid_remove_resource(Publisher)),
+ Node = mod_pubsub_on:string_to_node(NodeString),
+ case mod_pubsub_on:delete_item_nothook(Host, Node, Publisher, Itemid, true) of
+ {result, _} ->
+ "OK";
+ {error, {xmlelement, _, _, _} = XmlEl} ->
+ "ERROR: " ++ xml:element_to_string(XmlEl);
+ {error, ErrorAtom} when is_atom(ErrorAtom) ->
+ "ERROR: " ++ atom_to_list(ErrorAtom);
+ {error, ErrorString} when is_list(ErrorString) ->
+ "ERROR: " ++ ErrorString
+ end.
+
+transport_register(Host, TransportString, JIDString, Username, Password) ->
+ TransportAtom = list_to_atom(TransportString),
+ case {lists:member(Host, ?MYHOSTS), jlib:string_to_jid(JIDString)} of
+ {true, JID} when is_record(JID, jid) ->
+ case catch gen_transport:register(Host, TransportAtom, JIDString,
+ Username, Password) of
+ ok ->
+ "OK";
+ {error, Reason} ->
+ "ERROR: " ++ atom_to_list(Reason);
+ {'EXIT', {timeout,_}} ->
+ "ERROR: timed_out";
+ {'EXIT', _} ->
+ "ERROR: unexpected_error"
+ end;
+ {false, _} ->
+ "ERROR: unknown_host";
+ _ ->
+ "ERROR: bad_jid"
+ end.
+
+%%%
+%%% Stanza
+%%%
+
+send_chat(FromJID, ToJID, Msg) ->
+ 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),
+ 0.
+
+send_message(FromJID, ToJID, Sub, Msg) ->
+ 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),
+ 0.
+
+send_notification(SendFromUsername, SendToUsername, Host, UnreadItemsInteger, MessageBody, Type) ->
+ case get_resources(SendToUsername, Host) of
+ 404 ->
+ -1;
+ [] ->
+ -2;
+ [A|_] when is_list(A) ->
+ send_notification_really(SendFromUsername, SendToUsername, Host, UnreadItemsInteger, MessageBody, Type),
+ 0
+ end.
+
+send_notification_really(SendFromUsername, SendToUsername, Host, UnreadItemsInteger, MessageBody, Type) ->
+ FromString = Host ++ "/voicemail-notifier",
+ ToString = SendToUsername ++ "@" ++ Host,
+
+ XAttrs = [{"type", Type},
+ {"send_from", SendFromUsername},
+ {"unread_items", UnreadItemsInteger}],
+ XChildren = [{xmlelement, "text", [], [{xmlcdata, MessageBody}]}],
+ XEl = {xmlelement, "x", XAttrs, XChildren},
+
+ Attrs = [{"from", FromString}, {"to", ToString}, {"type", "chat"}],
+ Children = [XEl],
+ Stanza = {xmlelement, "message", Attrs, Children},
+
+ From = jlib:string_to_jid(FromString),
+ To = jlib:string_to_jid(ToString),
+ ejabberd_router:route(From, To, Stanza).
+
+send_stanza(FromJID, ToJID, StanzaStr) ->
+ case xml_stream:parse_element(StanzaStr) of
+ {error, _} ->
+ 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),
+ 0
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Internal functions
+
+%% -----------------------------
+%% Internal roster handling
+%% -----------------------------
+
+get_roster2(User, Server) ->
+ LUser = jlib:nodeprep(User),
+ LServer = jlib:nameprep(Server),
+ case roster_backend(LServer) of
+ mnesia -> mod_roster:get_user_roster([], {LUser, LServer});
+ odbc -> mod_roster_odbc:get_user_roster([], {LUser, LServer})
+ end.
+
+add_rosteritem2(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
+ end,
+ case {Result, Push} of
+ {{atomic, already_added}, _} -> ok; %% No need for roster push
+ {{atomic, ok}, true} -> roster_push(User, Server, JID, Nick, Subscription, Groups);
+ {{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
+ end,
+ case {Result, Push} of
+ {{atomic, ok}, true} -> roster_push(User, Server, JID, "", "remove", []);
+ {{atomic, ok}, false} -> ok;
+ _ -> error
+ end,
+ Result.
+
+link_contacts2(JID1, Nick1, JID2, Nick2, Push) ->
+ link_contacts2(JID1, Nick1, [], JID2, Nick2, [], Push).
+
+link_contacts2(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_rosteritem2(U1, S1, JID2, Nick2, Group1, "both", Push) of
+ {atomic, ok} -> add_rosteritem2(U2, S2, JID1, Nick1, Group2, "both", Push);
+ Error -> Error
+ end.
+
+unlink_contacts2(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, Groups) ->
+ LJID = jlib:make_jid(User, Server, ""),
+ TJID = jlib:string_to_jid(JID),
+ {TU, TS, _} = jlib:jid_tolower(TJID),
+
+ %% TODO: Problem: We assume that both user are local. More test
+ %% are needed to check if the JID is remote or not:
+
+ %% TODO: We need to probe user2 especially, if it is not local.
+ %% As a quick fix, I do not go for the probe solution however, because all users
+ %% are local
+ case Subscription of
+ "to" -> %% Probe second user to route his presence to modified user
+ %% TODO: For now we assume both user are local so we do not, but we need to move to probe.
+ set_roster(User, Server, TJID, Nick, Subscription, Groups);
+ "from" ->
+ %% Send roster updates
+ set_roster(User, Server, TJID, Nick, Subscription, Groups);
+ "both" ->
+ %% Update both presence
+ set_roster(User, Server, TJID, Nick, Subscription, Groups),
+ UJID = jlib:make_jid(User, Server, ""),
+ set_roster(TU, TS, UJID, Nick, Subscription, Groups);
+ _ ->
+ %% Remove subscription
+ set_roster(User, Server, TJID, Nick, "none", Groups)
+ end.
+
+
+set_roster(User, Server, TJID, Nick, Subscription, Groups) ->
+ GroupsXML = [{xmlelement, "group", [], [{xmlcdata, GroupString}]} || GroupString <- Groups],
+ Item = case Nick of
+ "" -> [{"jid", jlib:jid_to_string(TJID)}, {"subscription", Subscription}];
+ _ -> [{"jid", jlib:jid_to_string(TJID)}, {"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, GroupsXML}]}]}),
+ lists:foreach(fun(Session) ->
+ JID = jlib:make_jid(Session#session.usr),
+ ejabberd_router:route(JID, JID, Result),
+ PID = element(2, Session#session.sid),
+ ejabberd_c2s:add_rosteritem(PID, TJID, list_to_atom(Subscription)) %% TODO: Better error management
+ end, get_sessions(User, Server)).
+
+
+roster_backend(Server) ->
+ case lists:member(mod_roster, gen_mod:loaded_modules(Server)) of
+ true -> mnesia;
+ _ -> odbc % we assume that
+ 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 = {lists:flatten([User,"@",Server]),
+ extract_group(Group),
+ Nick,
+ atom_to_list(Subs),
+ atom_to_list(Ask)
+ },
+ format_roster(Items, [Struct|Structs]).
+
+%% 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_presence2(User, Server);
+ from -> get_presence2(User, Server);
+ _Other -> {"", "unavailable", ""}
+ end,
+ {Resource, Show, Status} =
+ case Presence of
+ {_R, "invisible", _S} -> {"", "unavailable", ""};
+ _Status -> Presence
+ end,
+ Struct = {lists:flatten([User,"@",Server]),
+ Resource,
+ extract_group(Group),
+ Nick,
+ atom_to_list(Subs),
+ atom_to_list(Ask),
+ Show,
+ 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_presence2(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_resources2(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 ->
+ 0;
+ _ ->
+ 1
+ end;
+ false ->
+ 404
+ end.
diff --git a/src/mod_antiflood.erl b/src/mod_antiflood.erl
new file mode 100644
index 000000000..aca0737eb
--- /dev/null
+++ b/src/mod_antiflood.erl
@@ -0,0 +1,186 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_antiflood.erl
+%%% Author : Christophe Romain
+%%% Description :
+%%% Created : 12 Sep 2008 by Christophe Romain
+%%%
+%%% 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).
diff --git a/src/mod_autofilter.erl b/src/mod_autofilter.erl
new file mode 100644
index 000000000..97b374b6b
--- /dev/null
+++ b/src/mod_autofilter.erl
@@ -0,0 +1,127 @@
+%%% ====================================================================
+%%% This software is copyright 2006-2010, ProcessOne.
+%%%
+%%% mod_autofilter
+%%%
+%%% @copyright 2006-2010 ProcessOne
+%%% @author Christophe Romain
+%%% [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.
diff --git a/src/mod_c2s_debug.erl b/src/mod_c2s_debug.erl
new file mode 100644
index 000000000..6bd2e6e76
--- /dev/null
+++ b/src/mod_c2s_debug.erl
@@ -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.
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index d9f4f30c8..5e6333e14 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -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,
diff --git a/src/mod_filter.erl b/src/mod_filter.erl
new file mode 100644
index 000000000..ecc3446d6
--- /dev/null
+++ b/src/mod_filter.erl
@@ -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
+%%% [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.
+
diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl
index c9102f2c0..16b70deaa 100644
--- a/src/mod_ip_blacklist.erl
+++ b/src/mod_ip_blacklist.erl
@@ -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),
diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl
index 107e6602e..39a06a294 100644
--- a/src/mod_irc/mod_irc_connection.erl
+++ b/src/mod_irc/mod_irc_connection.erl
@@ -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
diff --git a/src/mod_mnesia_mngt.erl b/src/mod_mnesia_mngt.erl
new file mode 100644
index 000000000..0c5ad1d36
--- /dev/null
+++ b/src/mod_mnesia_mngt.erl
@@ -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).
diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl
index f79f4cd24..c2af1ff83 100644
--- a/src/mod_muc/mod_muc.erl
+++ b/src/mod_muc/mod_muc.erl
@@ -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
diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl
index d010e86b8..fbc2d5cbe 100644
--- a/src/mod_muc/mod_muc_log.erl
+++ b/src/mod_muc/mod_muc_log.erl
@@ -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()),
diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl
index 0eeea91e8..e5b528a04 100644
--- a/src/mod_muc/mod_muc_room.erl
+++ b/src/mod_muc/mod_muc_room.erl
@@ -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),
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 88d0572de..2448a17c0 100644
--- a/src/mod_offline.erl
+++ b/src/mod_offline.erl
@@ -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")].
diff --git a/src/mod_offline_odbc.erl b/src/mod_offline_odbc.erl
index ab63b00b3..420ff581f 100644
--- a/src/mod_offline_odbc.erl
+++ b/src/mod_offline_odbc.erl
@@ -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")].
diff --git a/src/mod_ping.erl b/src/mod_ping.erl
index 80b6dac72..a650f983e 100644
--- a/src/mod_ping.erl
+++ b/src/mod_ping.erl
@@ -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).
%%====================================================================
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 0c9d089e0..d25bc9b7b 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -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;
_ ->
diff --git a/src/mod_privacy.hrl b/src/mod_privacy.hrl
index 0e0b02b21..3f4489b10 100644
--- a/src/mod_privacy.hrl
+++ b/src/mod_privacy.hrl
@@ -19,6 +19,8 @@
%%%
%%%----------------------------------------------------------------------
+-define(mod_privacy_hrl, true).
+
-record(privacy, {us,
default = none,
lists = []}).
diff --git a/src/mod_privacy_odbc.erl b/src/mod_privacy_odbc.erl
index 5ad2fb958..593e99140 100644
--- a/src/mod_privacy_odbc.erl
+++ b/src/mod_privacy_odbc.erl
@@ -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;
_ ->
diff --git a/src/mod_proxy65/mod_proxy65_sm.erl b/src/mod_proxy65/mod_proxy65_sm.erl
index 569458f6a..bdd1b5bb8 100644
--- a/src/mod_proxy65/mod_proxy65_sm.erl
+++ b/src/mod_proxy65/mod_proxy65_sm.erl
@@ -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.
diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl
index a9293212d..78a3b3f6d 100644
--- a/src/mod_pubsub/mod_pubsub.erl
+++ b/src/mod_pubsub/mod_pubsub.erl
@@ -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)),
diff --git a/src/mod_pubsub/node_flat_odbc.erl b/src/mod_pubsub/node_flat_odbc.erl
index a255e9b0d..95998a554 100644
--- a/src/mod_pubsub/node_flat_odbc.erl
+++ b/src/mod_pubsub/node_flat_odbc.erl
@@ -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},
diff --git a/src/mod_pubsub/node_pep_odbc.erl b/src/mod_pubsub/node_pep_odbc.erl
index 9f0071bbc..5d2fb0e1e 100644
--- a/src/mod_pubsub/node_pep_odbc.erl
+++ b/src/mod_pubsub/node_pep_odbc.erl
@@ -86,7 +86,6 @@ terminate(Host, ServerHost) ->
options() ->
[{odbc, true},
- {node_type, pep},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
diff --git a/src/mod_pubsub/pubsub_clean.erl b/src/mod_pubsub/pubsub_clean.erl
new file mode 100644
index 000000000..814cee0fb
--- /dev/null
+++ b/src/mod_pubsub/pubsub_clean.erl
@@ -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})
diff --git a/src/mod_pubsub/pubsub_debug.erl b/src/mod_pubsub/pubsub_debug.erl
new file mode 100644
index 000000000..97ca8275f
--- /dev/null
+++ b/src/mod_pubsub/pubsub_debug.erl
@@ -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.
diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl
index 8d33ac4d8..44fcfd25c 100644
--- a/src/mod_service_log.erl
+++ b/src/mod_service_log.erl
@@ -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).
-
diff --git a/src/mod_support.erl b/src/mod_support.erl
new file mode 100644
index 000000000..bd3b8a01b
--- /dev/null
+++ b/src/mod_support.erl
@@ -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
+%%% [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.
diff --git a/src/mod_time.erl b/src/mod_time.erl
index a8a43c0ab..e96c0ea72 100644
--- a/src/mod_time.erl
+++ b/src/mod_time.erl
@@ -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",
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index ed52a0593..53f8b58dc 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -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,
diff --git a/src/mod_xmlrpc.erl b/src/mod_xmlrpc.erl
new file mode 100644
index 000000000..d01838a0d
--- /dev/null
+++ b/src/mod_xmlrpc.erl
@@ -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.
diff --git a/src/odbc/ejabberd_odbc.erl b/src/odbc/ejabberd_odbc.erl
index 4a6481525..dc23dd928 100644
--- a/src/odbc/ejabberd_odbc.erl
+++ b/src/odbc/ejabberd_odbc.erl
@@ -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
diff --git a/src/p1_fsm.erl b/src/p1_fsm.erl
index 9ca924112..bc47e9c67 100644
--- a/src/p1_fsm.erl
+++ b/src/p1_fsm.erl
@@ -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}}
diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl
index 2f2315017..f4ee13e20 100644
--- a/src/web/ejabberd_http.erl
+++ b/src/web/ejabberd_http.erl
@@ -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,
diff --git a/src/web/ejabberd_http.hrl b/src/web/ejabberd_http.hrl
index 8bb96220a..89c2a55c4 100644
--- a/src/web/ejabberd_http.hrl
+++ b/src/web/ejabberd_http.hrl
@@ -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
+ }).
\ No newline at end of file
diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl
index 27ea3c089..98f8ea520 100644
--- a/src/web/ejabberd_http_bind.erl
+++ b/src/web/ejabberd_http_bind.erl
@@ -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, "BOSH module not started"};
+ "xmlns='" ++ ?NS_HTTP_BIND ++ "'>Internal Server Error
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.
diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl
index 0bdbc6287..f7550b099 100644
--- a/src/web/ejabberd_http_poll.erl
+++ b/src/web/ejabberd_http_poll.erl
@@ -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.
diff --git a/src/web/ejabberd_http_ws.erl b/src/web/ejabberd_http_ws.erl
new file mode 100644
index 000000000..f8ffb3aef
--- /dev/null
+++ b/src/web/ejabberd_http_ws.erl
@@ -0,0 +1,202 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_websocket.erl
+%%% Author : Eric Cestari
+%%% Purpose : XMPP Websocket support
+%%% Created : 09-10-2010 by Eric Cestari
+%%%
+%%%
+%%% 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.
diff --git a/src/web/ejabberd_websocket.erl b/src/web/ejabberd_websocket.erl
new file mode 100644
index 000000000..2b4d41292
--- /dev/null
+++ b/src/web/ejabberd_websocket.erl
@@ -0,0 +1,275 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_websocket.erl
+%%% Author : Eric Cestari
+%%% Purpose : XMPP Websocket support
+%%% Created : 09-10-2010 by Eric Cestari
+%%%
+%%% 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 , Joe Armstrong.
+%%% All rights reserved.
+%%%
+%%% Code portions from Joe Armstrong have been originally taken under MIT license at the address:
+%%%
+%%%
+%%% 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
+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 = <>,
+ 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, <>, 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).
diff --git a/src/web/ejabberd_ws.erl b/src/web/ejabberd_ws.erl
new file mode 100644
index 000000000..5ccaadf34
--- /dev/null
+++ b/src/web/ejabberd_ws.erl
@@ -0,0 +1,80 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_websocket.erl
+%%% Author : Eric Cestari
+%%% Purpose : Websocket support
+%%% Created : 09-10-2010 by Eric Cestari
+%%% Slightly adapted from :
+% ==========================================================================================================
+% MISULTIN - Websocket Request
+%
+% >-|-|-(°>
+%
+% Copyright (C) 2010, Roberto Ostinelli .
+% 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 =======================================================
diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl
index 25e964f93..26c082645 100644
--- a/src/web/mod_http_bind.erl
+++ b/src/web/mod_http_bind.erl
@@ -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.
diff --git a/src/web/pshb_http.erl b/src/web/pshb_http.erl
new file mode 100644
index 000000000..47efa0fc6
--- /dev/null
+++ b/src/web/pshb_http.erl
@@ -0,0 +1,414 @@
+%%%----------------------------------------------------------------------
+%%% File : pshb_http.erl
+%%% Author : Eric Cestari
+%%% 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}],
+ "\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: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: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: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
+% 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.
\ No newline at end of file
diff --git a/src/web/websocket_test.erl b/src/web/websocket_test.erl
new file mode 100644
index 000000000..5928e7d83
--- /dev/null
+++ b/src/web/websocket_test.erl
@@ -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.
diff --git a/src/xml_stream.erl b/src/xml_stream.erl
index a918811cb..947992ad0 100644
--- a/src/xml_stream.erl
+++ b/src/xml_stream.erl
@@ -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,
diff --git a/tools/joincluster b/tools/joincluster
new file mode 100755
index 000000000..b6c610231
--- /dev/null
+++ b/tools/joincluster
@@ -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 < $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."
diff --git a/tools/leavecluster b/tools/leavecluster
new file mode 100755
index 000000000..f6b7b7ac8
--- /dev/null
+++ b/tools/leavecluster
@@ -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 < $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."
diff --git a/tools/tcp-tuning.sh b/tools/tcp-tuning.sh
new file mode 100755
index 000000000..dcc2ef5ba
--- /dev/null
+++ b/tools/tcp-tuning.sh
@@ -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
+