mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-26 17:38:45 +01:00
Merge branch '2.2.x' of git+ssh://git@gitorious.process-one.net/+ejabberd-developers/ejabberd/maincustomers.git into 2.2.x-applepush
This commit is contained in:
commit
807af3c08a
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
Makefile
|
||||
archives
|
||||
build/
|
||||
*.dSYM/
|
||||
.gitignore
|
||||
*.so
|
||||
contrib/extract_translations/extract_translations.beam
|
||||
doc/contributed_modules.tex
|
||||
doc/features.aux
|
||||
doc/features.log
|
||||
doc/features.out
|
||||
doc/features.pdf
|
||||
doc/guide.aux
|
||||
doc/guide.idx
|
||||
doc/guide.ilg
|
||||
doc/guide.ind
|
||||
doc/guide.log
|
||||
doc/guide.out
|
||||
doc/guide.pdf
|
||||
doc/guide.toc
|
||||
doc/version.aux
|
||||
src/*.beam
|
||||
src/*.so
|
||||
src/eldap/eldap_filter_yecc.erl
|
||||
src/XmppAddr.asn1db
|
||||
src/XmppAddr.erl
|
||||
src/XmppAddr.hrl
|
||||
src/config.log
|
||||
src/config.status
|
||||
src/eldap/ELDAPv3.asn1db
|
||||
src/eldap/ELDAPv3.beam
|
||||
src/eldap/ELDAPv3.erl
|
||||
src/eldap/ELDAPv3.hrl
|
||||
src/epam
|
||||
src/ejabberdctl.example
|
||||
src/ejabberd.init
|
@ -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
|
@ -1944,8 +1944,8 @@ Mnesia as backend.
|
||||
(see <A HREF="#database">3.2</A>) as backend.
|
||||
</LI><LI CLASS="li-itemize">‘_ldap’, this means that the module needs an LDAP server as backend.
|
||||
</LI></UL><P>If you want to,
|
||||
it is possible to use a relational database to store pieces of
|
||||
information. You can do this by changing the module name to a name with an
|
||||
it is possible to use a relational database to store the tables created by some ejabberd modules.
|
||||
You can do this by changing the module name to a name with an
|
||||
<TT>_odbc</TT> suffix in <TT>ejabberd</TT> config file. You can use a relational
|
||||
database for the following data:</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Last connection date and time: Use <TT>mod_last_odbc</TT> instead of
|
||||
@ -1956,6 +1956,7 @@ Last connection date and time: Use <TT>mod_last_odbc</TT> instead of
|
||||
</LI><LI CLASS="li-itemize">Users’ VCARD: Use <TT>mod_vcard_odbc</TT> instead of <TT>mod_vcard</TT>.
|
||||
</LI><LI CLASS="li-itemize">Private XML storage: Use <TT>mod_private_odbc</TT> instead of <TT>mod_private</TT>.
|
||||
</LI><LI CLASS="li-itemize">User rules for blocking communications: Use <TT>mod_privacy_odbc</TT> instead of <TT>mod_privacy</TT>.
|
||||
</LI><LI CLASS="li-itemize">Pub-Sub nodes, items and subscriptions: Use <TT>mod_pubsub_odbc</TT> instead of <TT>mod_pubsub</TT>.
|
||||
</LI></UL><P>You can find more
|
||||
<A HREF="http://www.ejabberd.im/contributions">contributed modules</A> on the
|
||||
<TT>ejabberd</TT> website. Please remember that these contributions might not work or
|
||||
|
@ -386,6 +386,9 @@ Some options that you may be interested in modifying:
|
||||
\titem{--enable-nif}
|
||||
Replaces some critical Erlang functions with equivalents written in C to improve performance.
|
||||
This feature requires Erlang/OTP R13B04 or higher.
|
||||
|
||||
\titem{--enable-flash-hack}
|
||||
Enable support for non-standard XML socket clients of Adobe Flash 8 and lower.
|
||||
\end{description}
|
||||
|
||||
\makesubsection{install}{Install}
|
||||
@ -2000,9 +2003,19 @@ enabled. This can be done, by using next commands:
|
||||
\makesubsubsection{configuremssql}{Database Connection}
|
||||
\ind{Microsoft SQL Server!Database Connection}
|
||||
|
||||
The configuration of Database Connection for a Microsoft SQL Server
|
||||
is the same as the configuration for
|
||||
ODBC compatible servers (see section~\ref{configureodbc}).
|
||||
By default \ejabberd{} opens 10 connections to the database for each virtual host.
|
||||
Use this option to modify the value:
|
||||
\begin{verbatim}
|
||||
{odbc_pool_size, 10}.
|
||||
\end{verbatim}
|
||||
|
||||
You can configure an interval to make a dummy SQL request
|
||||
to keep alive the connections to the database.
|
||||
The default value is 'undefined', so no keepalive requests are made.
|
||||
Specify in seconds: for example 28800 means 8 hours.
|
||||
\begin{verbatim}
|
||||
{odbc_keepalive_interval, undefined}.
|
||||
\end{verbatim}
|
||||
|
||||
|
||||
\makesubsubsection{mssqlauth}{Authentication}
|
||||
@ -2010,8 +2023,7 @@ ODBC compatible servers (see section~\ref{configureodbc}).
|
||||
|
||||
%TODO: not sure if this section is right!!!!!!
|
||||
|
||||
The configuration of Authentication for a Microsoft SQL Server
|
||||
is the same as the configuration for
|
||||
The configuration of Microsoft SQL Server is the same as the configuration of
|
||||
ODBC compatible servers (see section~\ref{odbcauth}).
|
||||
|
||||
\makesubsubsection{mssqlstorage}{Storage}
|
||||
@ -2218,9 +2230,6 @@ and LDAP server supports
|
||||
|
||||
\makesubsubsection{ldapconnection}{Connection}
|
||||
|
||||
Two connections are established to the LDAP server per vhost,
|
||||
one for authentication and other for regular calls.
|
||||
|
||||
Parameters:
|
||||
\begin{description}
|
||||
\titem{\{ldap\_servers, [Servers, ...]\}} \ind{options!ldap\_server}List of IP addresses or DNS names of your
|
||||
@ -2560,8 +2569,8 @@ You can see which database backend each module needs by looking at the suffix:
|
||||
\end{itemize}
|
||||
|
||||
If you want to,
|
||||
it is possible to use a relational database to store pieces of
|
||||
information. You can do this by changing the module name to a name with an
|
||||
it is possible to use a relational database to store the tables created by some ejabberd modules.
|
||||
You can do this by changing the module name to a name with an
|
||||
\term{\_odbc} suffix in \ejabberd{} config file. You can use a relational
|
||||
database for the following data:
|
||||
|
||||
@ -2574,6 +2583,7 @@ database for the following data:
|
||||
\item Users' VCARD: Use \term{mod\_vcard\_odbc} instead of \term{mod\_vcard}.
|
||||
\item Private XML storage: Use \term{mod\_private\_odbc} instead of \term{mod\_private}.
|
||||
\item User rules for blocking communications: Use \term{mod\_privacy\_odbc} instead of \term{mod\_privacy}.
|
||||
\item Pub-Sub nodes, items and subscriptions: Use \term{mod\_pubsub\_odbc} instead of \term{mod\_pubsub}.
|
||||
\end{itemize}
|
||||
|
||||
You can find more
|
||||
|
146
doc/http_post.md
Normal file
146
doc/http_post.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Managing pubsub nodes through HTTP Atompub #
|
||||
|
||||
|
||||
## Configuration ##
|
||||
|
||||
These options will be used by the service to know how to build URLs. Using the previous configuration items the service should be accessed through `http://notify.push.bbc.co.uk:5280/pshb/<host>/<node>/`.
|
||||
|
||||
Also, in the ejabberd_http handler configuration, add the identified line.
|
||||
|
||||
{5280, ejabberd_http, [
|
||||
http_poll,
|
||||
web_admin,
|
||||
{request_handlers, [{["pshb"], pshb_http}]} % this should be added
|
||||
]}
|
||||
|
||||
It will automatically detect the version of mod_pubsub (odbc or mnesia) and call the appropriate module.
|
||||
|
||||
## Important notice ##
|
||||
|
||||
In the current version of the code, some security checks are not done :
|
||||
|
||||
* node creation uses the default `all` access_createnode acl, not checking for the actual configuration.
|
||||
|
||||
* most read operations are successfully executed without authentication. HOWEVER listing items can only be done when the node access_model is "open". In all other cases, the service returns 403. A finer grained authentication will be implemented.
|
||||
|
||||
|
||||
## Usage example with cURL ##
|
||||
|
||||
### Errors ###
|
||||
|
||||
HTTP status codes are used as intended. Additionally, the XMPP error stanza can also be set in the body :
|
||||
|
||||
$ curl -i -X POST -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost
|
||||
HTTP/1.1 409 Conflict
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: 95
|
||||
Content-type: application/xml
|
||||
|
||||
<error code='409' type='cancel'><conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
|
||||
|
||||
or
|
||||
|
||||
$ curl -i -X DELETE -u cstar@localhost:encore http://localhost:5280/pshb/localhost/princely_musings
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Length: 101
|
||||
Content-type: application/xml
|
||||
|
||||
<error code='404' type='cancel'><item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>
|
||||
|
||||
### Getting the service document ###
|
||||
|
||||
No authentication necessary. All nodes are listed.
|
||||
|
||||
$ curl -i http://host:port/pshb/domain/
|
||||
|
||||
### Getting items from a node ###
|
||||
|
||||
No authentication done, and all nodes are accessible.
|
||||
|
||||
$ curl -i http://host:port/pshb/domain/node/
|
||||
|
||||
|
||||
### Posting a new item ###
|
||||
|
||||
$ curl -u jid:password -i -X POST -d @entry.atom http://post:port/pshb/domain/node
|
||||
|
||||
User ability to post is based on node configuration.
|
||||
|
||||
### Editing a new item ###
|
||||
|
||||
$ curl -u jid:password -i -X POST -d @entry.atom http://post:port/pshb/domain/node/itemid
|
||||
|
||||
User ability to post is based on node configuration.
|
||||
|
||||
### Deleting an item ###
|
||||
|
||||
$ curl -u jid:password -i -X DELETE http://post:port/pshb/domain/node/itemid
|
||||
|
||||
User ability to post is based on node configuration.
|
||||
|
||||
|
||||
### Creating a new node ###
|
||||
|
||||
An instant node can be created if server configuration allows:
|
||||
|
||||
$ curl -X POST -u cstar@localhost:encore -d "" http://localhost:5280/pshb/localhost
|
||||
|
||||
or
|
||||
|
||||
$ curl -X POST -u cstar@localhost:encore -d "<pubsub><create node='princely_musings'/></pubsub>" http://localhost:5280/pshb/localhost
|
||||
|
||||
configure element (as per XEP-60) can be passed in the pubsub body.
|
||||
|
||||
$ cat createnode.xml
|
||||
<pubsub><create node='princely_musings' type='flat'/>
|
||||
<x xmlns='jabber:x:data' type='submit'>
|
||||
<field var='FORM_TYPE' type='hidden'>
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
|
||||
<field var='pubsub#max_payload_size'><value>1028</value></field>
|
||||
<field var='pubsub#type'><value>Atom</value></field>
|
||||
</x>
|
||||
</pubsub>
|
||||
|
||||
$ curl -i -X POST -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 130
|
||||
Content-Type: application/xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?><pubsub xmlns='http://jabber.org/protocol/pubsub'><create node='princely_musings'/></pubsub>
|
||||
|
||||
### Editing a node configuration ###
|
||||
|
||||
$ cat editnode.xml
|
||||
<pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
|
||||
<configure node='princely_musings'>
|
||||
<x xmlns='jabber:x:data' type='submit'>
|
||||
<field var='FORM_TYPE' type='hidden'>
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field var='pubsub#title'><value>Princely Musings (Atom)</value></field>
|
||||
<field var='pubsub#deliver_notifications'><value>1</value></field>
|
||||
<field var='pubsub#deliver_payloads'><value>1</value></field>
|
||||
<field var='pubsub#persist_items'><value>1</value></field>
|
||||
<field var='pubsub#max_items'><value>10</value></field>
|
||||
<field var='pubsub#item_expire'><value>604800</value></field>
|
||||
<field var='pubsub#access_model'><value>roster</value></field>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
|
||||
|
||||
$ curl -i -X PUT -u cstar@localhost:encore -d @createnode.xml http://localhost:5280/pshb/localhost/princely_musings
|
||||
|
||||
|
||||
|
||||
### Deleting a node ###
|
||||
|
||||
A node is deleted by:
|
||||
|
||||
$ curl -X DELETE -u cstar@localhost:encore http://localhost:5280/pshb/localhost/princely_musings
|
||||
|
||||
|
||||
|
@ -74,6 +74,11 @@ ifeq (@pam@, pam)
|
||||
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
|
||||
endif
|
||||
|
||||
ifeq (@flash_hack@, true)
|
||||
ERLC_FLAGS+=-DENABLE_FLASH_HACK
|
||||
CPPFLAGS+=-DENABLE_FLASH_HACK
|
||||
endif
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
|
||||
@ -168,7 +173,7 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
@ERLC@ -W $(EFLAGS) $*.erl
|
||||
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(EXPAT_LIBS) \
|
||||
$(EXPAT_CFLAGS) \
|
||||
|
563
src/cache_tab.erl
Normal file
563
src/cache_tab.erl
Normal file
@ -0,0 +1,563 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : cache_tab.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Caching key-value table
|
||||
%%%
|
||||
%%% Created : 29 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(cache_tab).
|
||||
|
||||
-define(GEN_SERVER, gen_server).
|
||||
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
%% API
|
||||
-export([start_link/4, new/2, delete/1, delete/3, lookup/3,
|
||||
insert/4, info/2, tab2list/1, setopts/2,
|
||||
dirty_lookup/3, dirty_insert/4, dirty_delete/3,
|
||||
all/0, test/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {tab = treap:empty(),
|
||||
name,
|
||||
size = 0,
|
||||
owner,
|
||||
max_size,
|
||||
life_time,
|
||||
warn,
|
||||
hits = 0,
|
||||
miss = 0,
|
||||
procs_num,
|
||||
cache_missed,
|
||||
shrink_size}).
|
||||
|
||||
-define(PROCNAME, ?MODULE).
|
||||
-define(CALL_TIMEOUT, 60000).
|
||||
|
||||
%% Defaults
|
||||
-define(MAX_SIZE, 1000).
|
||||
-define(WARN, true).
|
||||
-define(CACHE_MISSED, true).
|
||||
-define(LIFETIME, 600). %% 10 minutes
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Proc, Tab, Opts, Owner) ->
|
||||
?GEN_SERVER:start_link(
|
||||
{local, Proc}, ?MODULE, [Tab, Opts, get_proc_num(), Owner], []).
|
||||
|
||||
new(Tab, Opts) ->
|
||||
Res = lists:flatmap(
|
||||
fun(Proc) ->
|
||||
Spec = {{Tab, Proc},
|
||||
{?MODULE, start_link,
|
||||
[Proc, Tab, Opts, self()]},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]},
|
||||
case supervisor:start_child(cache_tab_sup, Spec) of
|
||||
{ok, _Pid} ->
|
||||
[ok];
|
||||
R ->
|
||||
[R]
|
||||
end
|
||||
end, get_all_procs(Tab)),
|
||||
case lists:filter(fun(ok) -> false; (_) -> true end, Res) of
|
||||
[] ->
|
||||
ok;
|
||||
Err ->
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
delete(Tab) ->
|
||||
lists:foreach(
|
||||
fun(Proc) ->
|
||||
supervisor:terminate_child(cache_tab_sup, {Tab, Proc}),
|
||||
supervisor:delete_child(cache_tab_sup, {Tab, Proc})
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
delete(Tab, Key, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {delete, Key, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_delete(Tab, Key, F) ->
|
||||
F(),
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {dirty_delete, Key}, ?CALL_TIMEOUT).
|
||||
|
||||
lookup(Tab, Key, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {lookup, Key, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_lookup(Tab, Key, F) ->
|
||||
Proc = get_proc_by_hash(Tab, Key),
|
||||
case ?GEN_SERVER:call(Proc, {dirty_lookup, Key}, ?CALL_TIMEOUT) of
|
||||
{ok, '$cached_mismatch'} ->
|
||||
error;
|
||||
{ok, Val} ->
|
||||
{ok, Val};
|
||||
_ ->
|
||||
case F() of
|
||||
{ok, Val} ->
|
||||
?GEN_SERVER:call(
|
||||
Proc, {dirty_insert, Key, Val}, ?CALL_TIMEOUT),
|
||||
{ok, Val};
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end.
|
||||
|
||||
insert(Tab, Key, Val, F) ->
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {insert, Key, Val, F}, ?CALL_TIMEOUT).
|
||||
|
||||
dirty_insert(Tab, Key, Val, F) ->
|
||||
F(),
|
||||
?GEN_SERVER:call(
|
||||
get_proc_by_hash(Tab, Key), {dirty_insert, Key, Val}, ?CALL_TIMEOUT).
|
||||
|
||||
info(Tab, Info) ->
|
||||
case lists:map(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, {info, Info}, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)) of
|
||||
Res when Info == size ->
|
||||
{ok, lists:sum(Res)};
|
||||
Res when Info == all ->
|
||||
{ok, Res};
|
||||
Res when Info == ratio ->
|
||||
{H, M} = lists:foldl(
|
||||
fun({Hits, Miss}, {HitsAcc, MissAcc}) ->
|
||||
{HitsAcc + Hits, MissAcc + Miss}
|
||||
end, {0, 0}, Res),
|
||||
{ok, [{hits, H}, {miss, M}]};
|
||||
_ ->
|
||||
{error, badarg}
|
||||
end.
|
||||
|
||||
setopts(Tab, Opts) ->
|
||||
lists:foreach(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, {setopts, Opts}, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
tab2list(Tab) ->
|
||||
lists:flatmap(
|
||||
fun(Proc) ->
|
||||
?GEN_SERVER:call(Proc, tab2list, ?CALL_TIMEOUT)
|
||||
end, get_all_procs(Tab)).
|
||||
|
||||
all() ->
|
||||
lists:usort(
|
||||
[Tab || {{Tab, _}, _, _, _} <- supervisor:which_children(cache_tab_sup)]).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Tab, Opts, N, Pid]) ->
|
||||
State = #state{procs_num = N,
|
||||
owner = Pid,
|
||||
name = Tab},
|
||||
{ok, do_setopts(State, Opts)}.
|
||||
|
||||
handle_call({lookup, Key, F}, _From, #state{tab = T} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _Prio, Val} ->
|
||||
Hits = State#state.hits,
|
||||
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
|
||||
case Val of
|
||||
'$cached_mismatch' ->
|
||||
{reply, error, NewState};
|
||||
_ ->
|
||||
{reply, {ok, Val}, NewState}
|
||||
end;
|
||||
_ ->
|
||||
case catch F() of
|
||||
{ok, Val} ->
|
||||
Miss = State#state.miss,
|
||||
NewState = treap_insert(Key, Val, State),
|
||||
{reply, {ok, Val}, NewState#state{miss = Miss + 1}};
|
||||
{'EXIT', Reason} ->
|
||||
print_error(lookup, [Key], Reason, State),
|
||||
{reply, error, State};
|
||||
_ ->
|
||||
Miss = State#state.miss,
|
||||
NewState = State#state{miss = Miss + 1},
|
||||
if State#state.cache_missed ->
|
||||
{reply, error,
|
||||
treap_insert(Key, '$cached_mismatch', NewState)};
|
||||
true ->
|
||||
{reply, error, NewState}
|
||||
end
|
||||
end
|
||||
end;
|
||||
handle_call({dirty_lookup, Key}, _From, #state{tab = T} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _Prio, Val} ->
|
||||
Hits = State#state.hits,
|
||||
NewState = treap_update(Key, Val, State#state{hits = Hits + 1}),
|
||||
{reply, {ok, Val}, NewState};
|
||||
_ ->
|
||||
Miss = State#state.miss,
|
||||
NewState = State#state{miss = Miss + 1},
|
||||
if State#state.cache_missed ->
|
||||
{reply, error,
|
||||
treap_insert(Key, '$cached_mismatch', NewState)};
|
||||
true ->
|
||||
{reply, error, NewState}
|
||||
end
|
||||
end;
|
||||
handle_call({insert, Key, Val, F}, _From, #state{tab = T} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _Prio, Val} ->
|
||||
{reply, ok, State};
|
||||
Res ->
|
||||
case catch F() of
|
||||
{'EXIT', Reason} ->
|
||||
print_error(insert, [Key, Val], Reason, State),
|
||||
{reply, ok, State};
|
||||
_ ->
|
||||
NewState = case Res of
|
||||
{ok, _, _} ->
|
||||
treap_update(Key, Val, State);
|
||||
_ ->
|
||||
treap_insert(Key, Val, State)
|
||||
end,
|
||||
{reply, ok, NewState}
|
||||
end
|
||||
end;
|
||||
handle_call({dirty_insert, Key, Val}, _From, #state{tab = T} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _Prio, Val} ->
|
||||
{reply, ok, State};
|
||||
{ok, _, _} ->
|
||||
{reply, ok, treap_update(Key, Val, State)};
|
||||
_ ->
|
||||
{reply, ok, treap_insert(Key, Val, State)}
|
||||
end;
|
||||
handle_call({delete, Key, F}, _From, State) ->
|
||||
NewState = treap_delete(Key, State),
|
||||
case catch F() of
|
||||
{'EXIT', Reason} ->
|
||||
print_error(delete, [Key], Reason, State);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{reply, ok, NewState};
|
||||
handle_call({dirty_delete, Key}, _From, State) ->
|
||||
NewState = treap_delete(Key, State),
|
||||
{reply, ok, NewState};
|
||||
handle_call({info, Info}, _From, State) ->
|
||||
Res = case Info of
|
||||
size ->
|
||||
State#state.size;
|
||||
ratio ->
|
||||
{State#state.hits, State#state.miss};
|
||||
all ->
|
||||
[{max_size, State#state.max_size},
|
||||
{life_time, State#state.life_time},
|
||||
{shrink_size, State#state.shrink_size},
|
||||
{size, State#state.size},
|
||||
{owner, State#state.owner},
|
||||
{hits, State#state.hits},
|
||||
{miss, State#state.miss},
|
||||
{cache_missed, State#state.cache_missed},
|
||||
{warn, State#state.warn}];
|
||||
_ ->
|
||||
badarg
|
||||
end,
|
||||
{reply, Res, State};
|
||||
handle_call(tab2list, _From, #state{tab = T} = State) ->
|
||||
Res = treap:fold(
|
||||
fun({Key, _, Val}, Acc) ->
|
||||
[{Key, Val}|Acc]
|
||||
end, [], T),
|
||||
{reply, Res, State};
|
||||
handle_call({setopts, Opts}, _From, State) ->
|
||||
{reply, ok, do_setopts(State, Opts)};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
do_setopts(#state{procs_num = N} = State, Opts) ->
|
||||
MaxSize = case {proplists:get_value(max_size, Opts),
|
||||
State#state.max_size} of
|
||||
{MS, _} when is_integer(MS), MS > 0 ->
|
||||
round(MS/N);
|
||||
{unlimited, _} ->
|
||||
unlimited;
|
||||
{_, undefined} ->
|
||||
round(?MAX_SIZE/N);
|
||||
{_, MS} ->
|
||||
MS
|
||||
end,
|
||||
LifeTime = case {proplists:get_value(life_time, Opts),
|
||||
State#state.life_time} of
|
||||
{LT, _} when is_integer(LT), LT > 0 ->
|
||||
LT*1000*1000;
|
||||
{unlimited, _} ->
|
||||
unlimited;
|
||||
{_, undefined} ->
|
||||
?LIFETIME*1000*1000;
|
||||
{_, LT} ->
|
||||
LT
|
||||
end,
|
||||
ShrinkSize = case {proplists:get_value(shrink_size, Opts),
|
||||
State#state.shrink_size} of
|
||||
{SS, _} when is_integer(SS), SS > 0 ->
|
||||
round(SS/N);
|
||||
_ when is_integer(MaxSize) ->
|
||||
round(MaxSize/2);
|
||||
_ ->
|
||||
unlimited
|
||||
end,
|
||||
Warn = case {proplists:get_value(warn, Opts),
|
||||
State#state.warn} of
|
||||
{true, _} ->
|
||||
true;
|
||||
{false, _} ->
|
||||
false;
|
||||
{_, undefined} ->
|
||||
?WARN;
|
||||
{_, W} ->
|
||||
W
|
||||
end,
|
||||
CacheMissed = case proplists:get_value(
|
||||
cache_missed, Opts, State#state.cache_missed) of
|
||||
false ->
|
||||
false;
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
?CACHE_MISSED
|
||||
end,
|
||||
State#state{max_size = MaxSize,
|
||||
warn = Warn,
|
||||
life_time = LifeTime,
|
||||
cache_missed = CacheMissed,
|
||||
shrink_size = ShrinkSize}.
|
||||
|
||||
get_proc_num() ->
|
||||
erlang:system_info(logical_processors).
|
||||
|
||||
get_proc_by_hash(Tab, Term) ->
|
||||
N = erlang:phash2(Term, get_proc_num()) + 1,
|
||||
get_proc(Tab, N).
|
||||
|
||||
get_proc(Tab, N) ->
|
||||
list_to_atom(atom_to_list(?PROCNAME) ++ "_" ++
|
||||
atom_to_list(Tab) ++ "_" ++ integer_to_list(N)).
|
||||
|
||||
get_all_procs(Tab) ->
|
||||
[get_proc(Tab, N) || N <- lists:seq(1, get_proc_num())].
|
||||
|
||||
now_priority() ->
|
||||
{MSec, Sec, USec} = now(),
|
||||
-((MSec*1000000 + Sec)*1000000 + USec).
|
||||
|
||||
treap_update(Key, Val, #state{tab = T} = State) ->
|
||||
Priority = now_priority(),
|
||||
NewT = treap:insert(Key, Priority, Val, T),
|
||||
State#state{tab = NewT}.
|
||||
|
||||
treap_insert(Key, Val, State) ->
|
||||
State1 = clean_treap(State),
|
||||
#state{size = Size} = State2 = shrink_treap(State1),
|
||||
treap_update(Key, Val, State2#state{size = Size+1}).
|
||||
|
||||
treap_delete(Key, #state{tab = T, size = Size} = State) ->
|
||||
case treap:lookup(Key, T) of
|
||||
{ok, _, _} ->
|
||||
NewT = treap:delete(Key, T),
|
||||
clean_treap(State#state{tab = NewT, size = Size-1});
|
||||
_ ->
|
||||
State
|
||||
end.
|
||||
|
||||
clean_treap(#state{tab = T, size = Size, life_time = LifeTime} = State) ->
|
||||
if is_integer(LifeTime) ->
|
||||
Priority = now_priority(),
|
||||
{Cleaned, NewT} = clean_treap(T, Priority + LifeTime, 0),
|
||||
State#state{size = Size - Cleaned, tab = NewT};
|
||||
true ->
|
||||
State
|
||||
end.
|
||||
|
||||
clean_treap(Treap, CleanPriority, N) ->
|
||||
case treap:is_empty(Treap) of
|
||||
true ->
|
||||
{N, Treap};
|
||||
false ->
|
||||
{_Key, Priority, _Value} = treap:get_root(Treap),
|
||||
if Priority > CleanPriority ->
|
||||
clean_treap(treap:delete_root(Treap), CleanPriority, N+1);
|
||||
true ->
|
||||
{N, Treap}
|
||||
end
|
||||
end.
|
||||
|
||||
shrink_treap(#state{tab = T,
|
||||
max_size = MaxSize,
|
||||
shrink_size = ShrinkSize,
|
||||
warn = Warn,
|
||||
size = Size} = State) when Size >= MaxSize ->
|
||||
if Warn ->
|
||||
?WARNING_MSG("shrinking table:~n"
|
||||
"** Table: ~p~n"
|
||||
"** Processes Number: ~p~n"
|
||||
"** Max Size: ~p items~n"
|
||||
"** Shrink Size: ~p items~n"
|
||||
"** Life Time: ~p microseconds~n"
|
||||
"** Hits/Miss: ~p/~p~n"
|
||||
"** Owner: ~p~n"
|
||||
"** Cache Missed: ~p~n"
|
||||
"** Instruction: you have to tune cacheing options"
|
||||
" if this message repeats too frequently",
|
||||
[State#state.name, State#state.procs_num,
|
||||
MaxSize, ShrinkSize, State#state.life_time,
|
||||
State#state.hits, State#state.miss,
|
||||
State#state.owner, State#state.cache_missed]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{Shrinked, NewT} = shrink_treap(T, ShrinkSize, 0),
|
||||
State#state{tab = NewT, size = Size - Shrinked};
|
||||
shrink_treap(State) ->
|
||||
State.
|
||||
|
||||
shrink_treap(T, ShrinkSize, ShrinkSize) ->
|
||||
{ShrinkSize, T};
|
||||
shrink_treap(T, ShrinkSize, N) ->
|
||||
case treap:is_empty(T) of
|
||||
true ->
|
||||
{N, T};
|
||||
false ->
|
||||
shrink_treap(treap:delete_root(T), ShrinkSize, N+1)
|
||||
end.
|
||||
|
||||
print_error(Operation, Args, Reason, State) ->
|
||||
?ERROR_MSG("callback failed:~n"
|
||||
"** Tab: ~p~n"
|
||||
"** Owner: ~p~n"
|
||||
"** Operation: ~p~n"
|
||||
"** Args: ~p~n"
|
||||
"** Reason: ~p",
|
||||
[State#state.name, State#state.owner,
|
||||
Operation, Args, Reason]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Tests
|
||||
%%--------------------------------------------------------------------
|
||||
-define(lookup, dirty_lookup).
|
||||
-define(delete, dirty_delete).
|
||||
-define(insert, dirty_insert).
|
||||
%% -define(lookup, lookup).
|
||||
%% -define(delete, delete).
|
||||
%% -define(insert, insert).
|
||||
|
||||
test() ->
|
||||
LifeTime = 2,
|
||||
ok = new(test_tbl, [{life_time, LifeTime}, {max_size, unlimited}]),
|
||||
check([]),
|
||||
ok = ?insert(test_tbl, "key", "value", fun() -> ok end),
|
||||
check([{"key", "value"}]),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> error end),
|
||||
check([{"key", "value"}]),
|
||||
io:format("** waiting for ~p seconds to check if cleaner works fine...~n",
|
||||
[LifeTime+1]),
|
||||
timer:sleep(timer:seconds(LifeTime+1)),
|
||||
ok = ?insert(test_tbl, "key1", "value1", fun() -> ok end),
|
||||
check([{"key1", "value1"}]),
|
||||
ok = ?delete(test_tbl, "key1", fun() -> ok end),
|
||||
{ok, "value"} = ?lookup(test_tbl, "key", fun() -> {ok, "value"} end),
|
||||
check([{"key", "value"}]),
|
||||
ok = ?delete(test_tbl, "key", fun() -> ok end),
|
||||
check([]),
|
||||
%% io:format("** testing buggy callbacks...~n"),
|
||||
%% delete(test_tbl, "key", fun() -> erlang:error(badarg) end),
|
||||
%% insert(test_tbl, "key", "val", fun() -> erlang:error(badarg) end),
|
||||
%% lookup(test_tbl, "key", fun() -> erlang:error(badarg) end),
|
||||
check([]),
|
||||
delete(test_tbl),
|
||||
test1().
|
||||
|
||||
test1() ->
|
||||
MaxSize = 10,
|
||||
ok = new(test_tbl, [{max_size, MaxSize}, {shrink_size, 1}, {warn, false}]),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
ok = ?insert(test_tbl, N, N, fun() -> ok end)
|
||||
end, lists:seq(1, MaxSize*get_proc_num())),
|
||||
{ok, MaxSize} = info(test_tbl, size),
|
||||
delete(test_tbl),
|
||||
io:format("** testing speed, this may take a while...~n"),
|
||||
test2(1000),
|
||||
test2(10000),
|
||||
test2(100000),
|
||||
test2(1000000).
|
||||
|
||||
test2(Iter) ->
|
||||
ok = new(test_tbl, [{max_size, unlimited}, {life_time, unlimited}]),
|
||||
L = lists:seq(1, Iter),
|
||||
T1 = now(),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
ok = ?insert(test_tbl, N, N, fun() -> ok end)
|
||||
end, L),
|
||||
io:format("** average insert (size = ~p): ~p usec~n",
|
||||
[Iter, round(timer:now_diff(now(), T1)/Iter)]),
|
||||
T2 = now(),
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
{ok, N} = ?lookup(test_tbl, N, fun() -> ok end)
|
||||
end, L),
|
||||
io:format("** average lookup (size = ~p): ~p usec~n",
|
||||
[Iter, round(timer:now_diff(now(), T2)/Iter)]),
|
||||
{ok, Iter} = info(test_tbl, size),
|
||||
delete(test_tbl).
|
||||
|
||||
check(List) ->
|
||||
Size = length(List),
|
||||
{ok, Size} = info(test_tbl, size),
|
||||
List = tab2list(test_tbl).
|
53
src/cache_tab_sup.erl
Normal file
53
src/cache_tab_sup.erl
Normal file
@ -0,0 +1,53 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : cache_tab_sup.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Cache tables supervisor
|
||||
%%%
|
||||
%%% Created : 30 Aug 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(cache_tab_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
%%====================================================================
|
||||
%% API functions
|
||||
%%====================================================================
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
%%====================================================================
|
||||
%% Supervisor callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
{ok, {{one_for_one,10,1}, []}}.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
359
src/configure
vendored
359
src/configure
vendored
@ -1,13 +1,13 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.65 for ejabberd 2.1.x.
|
||||
# Generated by GNU Autoconf 2.67 for ejabberd 2.1.x.
|
||||
#
|
||||
# Report bugs to <ejabberd@process-one.net>.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||
# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
|
||||
# Inc.
|
||||
# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
|
||||
# Foundation, Inc.
|
||||
#
|
||||
#
|
||||
# This configure script is free software; the Free Software Foundation
|
||||
@ -319,7 +319,7 @@ $as_echo X"$as_dir" |
|
||||
test -d "$as_dir" && break
|
||||
done
|
||||
test -z "$as_dirs" || eval "mkdir $as_dirs"
|
||||
} || test -d "$as_dir" || as_fn_error "cannot create directory $as_dir"
|
||||
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
|
||||
|
||||
|
||||
} # as_fn_mkdir_p
|
||||
@ -359,19 +359,19 @@ else
|
||||
fi # as_fn_arith
|
||||
|
||||
|
||||
# as_fn_error ERROR [LINENO LOG_FD]
|
||||
# ---------------------------------
|
||||
# as_fn_error STATUS ERROR [LINENO LOG_FD]
|
||||
# ----------------------------------------
|
||||
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
|
||||
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
|
||||
# script with status $?, using 1 if that was 0.
|
||||
# script with STATUS, using 1 if that was 0.
|
||||
as_fn_error ()
|
||||
{
|
||||
as_status=$?; test $as_status -eq 0 && as_status=1
|
||||
if test "$3"; then
|
||||
as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3
|
||||
as_status=$1; test $as_status -eq 0 && as_status=1
|
||||
if test "$4"; then
|
||||
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
|
||||
fi
|
||||
$as_echo "$as_me: error: $1" >&2
|
||||
$as_echo "$as_me: error: $2" >&2
|
||||
as_fn_exit $as_status
|
||||
} # as_fn_error
|
||||
|
||||
@ -533,7 +533,7 @@ test -n "$DJDIR" || exec 7<&0 </dev/null
|
||||
exec 6>&1
|
||||
|
||||
# Name of the host.
|
||||
# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
|
||||
# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
|
||||
# so uname gets run too.
|
||||
ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
|
||||
|
||||
@ -616,6 +616,7 @@ nif
|
||||
full_xml
|
||||
transient_supervisors
|
||||
db_type
|
||||
flash_hack
|
||||
roster_gateway_workaround
|
||||
hipe
|
||||
PAM_LIBS
|
||||
@ -720,6 +721,7 @@ enable_pam
|
||||
with_pam
|
||||
enable_hipe
|
||||
enable_roster_gateway_workaround
|
||||
enable_flash_hack
|
||||
enable_mssql
|
||||
enable_transient_supervisors
|
||||
enable_full_xml
|
||||
@ -800,8 +802,9 @@ do
|
||||
fi
|
||||
|
||||
case $ac_option in
|
||||
*=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
|
||||
*) ac_optarg=yes ;;
|
||||
*=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
|
||||
*=) ac_optarg= ;;
|
||||
*) ac_optarg=yes ;;
|
||||
esac
|
||||
|
||||
# Accept the important Cygnus configure options, so we can diagnose typos.
|
||||
@ -846,7 +849,7 @@ do
|
||||
ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
|
||||
# Reject names that are not valid shell variable names.
|
||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
||||
as_fn_error "invalid feature name: $ac_useropt"
|
||||
as_fn_error $? "invalid feature name: $ac_useropt"
|
||||
ac_useropt_orig=$ac_useropt
|
||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||
case $ac_user_opts in
|
||||
@ -872,7 +875,7 @@ do
|
||||
ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
|
||||
# Reject names that are not valid shell variable names.
|
||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
||||
as_fn_error "invalid feature name: $ac_useropt"
|
||||
as_fn_error $? "invalid feature name: $ac_useropt"
|
||||
ac_useropt_orig=$ac_useropt
|
||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||
case $ac_user_opts in
|
||||
@ -1076,7 +1079,7 @@ do
|
||||
ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
|
||||
# Reject names that are not valid shell variable names.
|
||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
||||
as_fn_error "invalid package name: $ac_useropt"
|
||||
as_fn_error $? "invalid package name: $ac_useropt"
|
||||
ac_useropt_orig=$ac_useropt
|
||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||
case $ac_user_opts in
|
||||
@ -1092,7 +1095,7 @@ do
|
||||
ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
|
||||
# Reject names that are not valid shell variable names.
|
||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
||||
as_fn_error "invalid package name: $ac_useropt"
|
||||
as_fn_error $? "invalid package name: $ac_useropt"
|
||||
ac_useropt_orig=$ac_useropt
|
||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||
case $ac_user_opts in
|
||||
@ -1122,8 +1125,8 @@ do
|
||||
| --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
|
||||
x_libraries=$ac_optarg ;;
|
||||
|
||||
-*) as_fn_error "unrecognized option: \`$ac_option'
|
||||
Try \`$0 --help' for more information."
|
||||
-*) as_fn_error $? "unrecognized option: \`$ac_option'
|
||||
Try \`$0 --help' for more information"
|
||||
;;
|
||||
|
||||
*=*)
|
||||
@ -1131,7 +1134,7 @@ Try \`$0 --help' for more information."
|
||||
# Reject names that are not valid shell variable names.
|
||||
case $ac_envvar in #(
|
||||
'' | [0-9]* | *[!_$as_cr_alnum]* )
|
||||
as_fn_error "invalid variable name: \`$ac_envvar'" ;;
|
||||
as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
|
||||
esac
|
||||
eval $ac_envvar=\$ac_optarg
|
||||
export $ac_envvar ;;
|
||||
@ -1149,13 +1152,13 @@ done
|
||||
|
||||
if test -n "$ac_prev"; then
|
||||
ac_option=--`echo $ac_prev | sed 's/_/-/g'`
|
||||
as_fn_error "missing argument to $ac_option"
|
||||
as_fn_error $? "missing argument to $ac_option"
|
||||
fi
|
||||
|
||||
if test -n "$ac_unrecognized_opts"; then
|
||||
case $enable_option_checking in
|
||||
no) ;;
|
||||
fatal) as_fn_error "unrecognized options: $ac_unrecognized_opts" ;;
|
||||
fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
|
||||
*) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
|
||||
esac
|
||||
fi
|
||||
@ -1178,7 +1181,7 @@ do
|
||||
[\\/$]* | ?:[\\/]* ) continue;;
|
||||
NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
|
||||
esac
|
||||
as_fn_error "expected an absolute directory name for --$ac_var: $ac_val"
|
||||
as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
|
||||
done
|
||||
|
||||
# There might be people who depend on the old broken behavior: `$host'
|
||||
@ -1192,8 +1195,8 @@ target=$target_alias
|
||||
if test "x$host_alias" != x; then
|
||||
if test "x$build_alias" = x; then
|
||||
cross_compiling=maybe
|
||||
$as_echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
|
||||
If a cross compiler is detected then cross compile mode will be used." >&2
|
||||
$as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host.
|
||||
If a cross compiler is detected then cross compile mode will be used" >&2
|
||||
elif test "x$build_alias" != "x$host_alias"; then
|
||||
cross_compiling=yes
|
||||
fi
|
||||
@ -1208,9 +1211,9 @@ test "$silent" = yes && exec 6>/dev/null
|
||||
ac_pwd=`pwd` && test -n "$ac_pwd" &&
|
||||
ac_ls_di=`ls -di .` &&
|
||||
ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
|
||||
as_fn_error "working directory cannot be determined"
|
||||
as_fn_error $? "working directory cannot be determined"
|
||||
test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
|
||||
as_fn_error "pwd does not report name of working directory"
|
||||
as_fn_error $? "pwd does not report name of working directory"
|
||||
|
||||
|
||||
# Find the source files, if location was not specified.
|
||||
@ -1249,11 +1252,11 @@ else
|
||||
fi
|
||||
if test ! -r "$srcdir/$ac_unique_file"; then
|
||||
test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
|
||||
as_fn_error "cannot find sources ($ac_unique_file) in $srcdir"
|
||||
as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
|
||||
fi
|
||||
ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
|
||||
ac_abs_confdir=`(
|
||||
cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error "$ac_msg"
|
||||
cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
|
||||
pwd)`
|
||||
# When building in place, set srcdir=.
|
||||
if test "$ac_abs_confdir" = "$ac_pwd"; then
|
||||
@ -1293,7 +1296,7 @@ Configuration:
|
||||
--help=short display options specific to this package
|
||||
--help=recursive display the short help of all the included packages
|
||||
-V, --version display version information and exit
|
||||
-q, --quiet, --silent do not print \`checking...' messages
|
||||
-q, --quiet, --silent do not print \`checking ...' messages
|
||||
--cache-file=FILE cache test results in FILE [disabled]
|
||||
-C, --config-cache alias for \`--cache-file=config.cache'
|
||||
-n, --no-create do not create output files
|
||||
@ -1368,6 +1371,7 @@ Optional Features:
|
||||
--enable-roster-gateway-workaround
|
||||
turn on workaround for processing gateway
|
||||
subscriptions (default: no)
|
||||
--enable-flash-hack support Adobe Flash client XML (default: no)
|
||||
--enable-mssql use Microsoft SQL Server database (default: no,
|
||||
requires --enable-odbc)
|
||||
--enable-transient_supervisors
|
||||
@ -1471,9 +1475,9 @@ test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
ejabberd configure 2.1.x
|
||||
generated by GNU Autoconf 2.65
|
||||
generated by GNU Autoconf 2.67
|
||||
|
||||
Copyright (C) 2009 Free Software Foundation, Inc.
|
||||
Copyright (C) 2010 Free Software Foundation, Inc.
|
||||
This configure script is free software; the Free Software Foundation
|
||||
gives unlimited permission to copy, distribute and modify it.
|
||||
_ACEOF
|
||||
@ -1589,7 +1593,7 @@ $as_echo "$ac_try_echo"; } >&5
|
||||
mv -f conftest.er1 conftest.err
|
||||
fi
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; } >/dev/null && {
|
||||
test $ac_status = 0; } > conftest.i && {
|
||||
test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
|
||||
test ! -s conftest.err
|
||||
}; then :
|
||||
@ -1613,10 +1617,10 @@ fi
|
||||
ac_fn_c_check_header_mongrel ()
|
||||
{
|
||||
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
|
||||
if eval "test \"\${$3+set}\"" = set; then :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
||||
$as_echo_n "checking for $2... " >&6; }
|
||||
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
|
||||
if eval "test \"\${$3+set}\"" = set; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
fi
|
||||
eval ac_res=\$$3
|
||||
@ -1652,7 +1656,7 @@ if ac_fn_c_try_cpp "$LINENO"; then :
|
||||
else
|
||||
ac_header_preproc=no
|
||||
fi
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
|
||||
$as_echo "$ac_header_preproc" >&6; }
|
||||
|
||||
@ -1675,17 +1679,15 @@ $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
|
||||
$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
|
||||
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
|
||||
( cat <<\_ASBOX
|
||||
## --------------------------------------- ##
|
||||
( $as_echo "## --------------------------------------- ##
|
||||
## Report this to ejabberd@process-one.net ##
|
||||
## --------------------------------------- ##
|
||||
_ASBOX
|
||||
## --------------------------------------- ##"
|
||||
) | sed "s/^/$as_me: WARNING: /" >&2
|
||||
;;
|
||||
esac
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
||||
$as_echo_n "checking for $2... " >&6; }
|
||||
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
|
||||
if eval "test \"\${$3+set}\"" = set; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
eval "$3=\$ac_header_compiler"
|
||||
@ -1749,7 +1751,7 @@ ac_fn_c_check_header_compile ()
|
||||
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
||||
$as_echo_n "checking for $2... " >&6; }
|
||||
if { as_var=$3; eval "test \"\${$as_var+set}\" = set"; }; then :
|
||||
if eval "test \"\${$3+set}\"" = set; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
@ -1817,7 +1819,7 @@ This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by ejabberd $as_me 2.1.x, which was
|
||||
generated by GNU Autoconf 2.65. Invocation command line was
|
||||
generated by GNU Autoconf 2.67. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
|
||||
@ -1927,11 +1929,9 @@ trap 'exit_status=$?
|
||||
{
|
||||
echo
|
||||
|
||||
cat <<\_ASBOX
|
||||
## ---------------- ##
|
||||
$as_echo "## ---------------- ##
|
||||
## Cache variables. ##
|
||||
## ---------------- ##
|
||||
_ASBOX
|
||||
## ---------------- ##"
|
||||
echo
|
||||
# The following way of writing the cache mishandles newlines in values,
|
||||
(
|
||||
@ -1965,11 +1965,9 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
|
||||
)
|
||||
echo
|
||||
|
||||
cat <<\_ASBOX
|
||||
## ----------------- ##
|
||||
$as_echo "## ----------------- ##
|
||||
## Output variables. ##
|
||||
## ----------------- ##
|
||||
_ASBOX
|
||||
## ----------------- ##"
|
||||
echo
|
||||
for ac_var in $ac_subst_vars
|
||||
do
|
||||
@ -1982,11 +1980,9 @@ _ASBOX
|
||||
echo
|
||||
|
||||
if test -n "$ac_subst_files"; then
|
||||
cat <<\_ASBOX
|
||||
## ------------------- ##
|
||||
$as_echo "## ------------------- ##
|
||||
## File substitutions. ##
|
||||
## ------------------- ##
|
||||
_ASBOX
|
||||
## ------------------- ##"
|
||||
echo
|
||||
for ac_var in $ac_subst_files
|
||||
do
|
||||
@ -2000,11 +1996,9 @@ _ASBOX
|
||||
fi
|
||||
|
||||
if test -s confdefs.h; then
|
||||
cat <<\_ASBOX
|
||||
## ----------- ##
|
||||
$as_echo "## ----------- ##
|
||||
## confdefs.h. ##
|
||||
## ----------- ##
|
||||
_ASBOX
|
||||
## ----------- ##"
|
||||
echo
|
||||
cat confdefs.h
|
||||
echo
|
||||
@ -2059,7 +2053,12 @@ _ACEOF
|
||||
ac_site_file1=NONE
|
||||
ac_site_file2=NONE
|
||||
if test -n "$CONFIG_SITE"; then
|
||||
ac_site_file1=$CONFIG_SITE
|
||||
# We do not want a PATH search for config.site.
|
||||
case $CONFIG_SITE in #((
|
||||
-*) ac_site_file1=./$CONFIG_SITE;;
|
||||
*/*) ac_site_file1=$CONFIG_SITE;;
|
||||
*) ac_site_file1=./$CONFIG_SITE;;
|
||||
esac
|
||||
elif test "x$prefix" != xNONE; then
|
||||
ac_site_file1=$prefix/share/config.site
|
||||
ac_site_file2=$prefix/etc/config.site
|
||||
@ -2074,7 +2073,11 @@ do
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
|
||||
$as_echo "$as_me: loading site script $ac_site_file" >&6;}
|
||||
sed 's/^/| /' "$ac_site_file" >&5
|
||||
. "$ac_site_file"
|
||||
. "$ac_site_file" \
|
||||
|| { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error $? "failed to load site script $ac_site_file
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
fi
|
||||
done
|
||||
|
||||
@ -2150,7 +2153,7 @@ if $ac_cache_corrupted; then
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
|
||||
$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
|
||||
as_fn_error "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
|
||||
as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
|
||||
fi
|
||||
## -------------------- ##
|
||||
## Main body of script. ##
|
||||
@ -2465,8 +2468,8 @@ fi
|
||||
|
||||
test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error "no acceptable C compiler found in \$PATH
|
||||
See \`config.log' for more details." "$LINENO" 5; }
|
||||
as_fn_error $? "no acceptable C compiler found in \$PATH
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
|
||||
# Provide some information about the compiler.
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
|
||||
@ -2580,9 +2583,8 @@ sed 's/^/| /' conftest.$ac_ext >&5
|
||||
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
{ as_fn_set_status 77
|
||||
as_fn_error "C compiler cannot create executables
|
||||
See \`config.log' for more details." "$LINENO" 5; }; }
|
||||
as_fn_error 77 "C compiler cannot create executables
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
@ -2624,8 +2626,8 @@ done
|
||||
else
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error "cannot compute suffix of executables: cannot compile and link
|
||||
See \`config.log' for more details." "$LINENO" 5; }
|
||||
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
fi
|
||||
rm -f conftest conftest$ac_cv_exeext
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
|
||||
@ -2682,9 +2684,9 @@ $as_echo "$ac_try_echo"; } >&5
|
||||
else
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error "cannot run C compiled programs.
|
||||
as_fn_error $? "cannot run C compiled programs.
|
||||
If you meant to cross compile, use \`--host'.
|
||||
See \`config.log' for more details." "$LINENO" 5; }
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@ -2735,8 +2737,8 @@ sed 's/^/| /' conftest.$ac_ext >&5
|
||||
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error "cannot compute suffix of object files: cannot compile
|
||||
See \`config.log' for more details." "$LINENO" 5; }
|
||||
as_fn_error $? "cannot compute suffix of object files: cannot compile
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
fi
|
||||
rm -f conftest.$ac_cv_objext conftest.$ac_ext
|
||||
fi
|
||||
@ -2960,7 +2962,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
||||
$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
|
||||
set x ${MAKE-make}
|
||||
ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
|
||||
if { as_var=ac_cv_prog_make_${ac_make}_set; eval "test \"\${$as_var+set}\" = set"; }; then :
|
||||
if eval "test \"\${ac_cv_prog_make_${ac_make}_set+set}\"" = set; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
cat >conftest.make <<\_ACEOF
|
||||
@ -2968,7 +2970,7 @@ SHELL = /bin/sh
|
||||
all:
|
||||
@echo '@@@%%%=$(MAKE)=@@@%%%'
|
||||
_ACEOF
|
||||
# GNU make sometimes prints "make[1]: Entering...", which would confuse us.
|
||||
# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
|
||||
case `${MAKE-make} -f conftest.make 2>/dev/null` in
|
||||
*@@@%%%=?*=@@@%%%*)
|
||||
eval ac_cv_prog_make_${ac_make}_set=yes;;
|
||||
@ -3202,7 +3204,7 @@ fi
|
||||
|
||||
|
||||
if test "z$ERLC" = "z" || test "z$ERL" = "z"; then
|
||||
as_fn_error "erlang not found" "$LINENO" 5
|
||||
as_fn_error $? "erlang not found" "$LINENO" 5
|
||||
fi
|
||||
|
||||
|
||||
@ -3260,15 +3262,15 @@ libpath(App) ->
|
||||
_EOF
|
||||
|
||||
if ! $ERLC conftest.erl; then
|
||||
as_fn_error "could not compile sample program" "$LINENO" 5
|
||||
as_fn_error $? "could not compile sample program" "$LINENO" 5
|
||||
fi
|
||||
|
||||
if ! $ERL -s conftest -noshell; then
|
||||
as_fn_error "could not run sample program" "$LINENO" 5
|
||||
as_fn_error $? "could not run sample program" "$LINENO" 5
|
||||
fi
|
||||
|
||||
if ! test -f conftest.out; then
|
||||
as_fn_error "erlang program was not properly executed, (conftest.out was not produced)" "$LINENO" 5
|
||||
as_fn_error $? "erlang program was not properly executed, (conftest.out was not produced)" "$LINENO" 5
|
||||
fi
|
||||
|
||||
# First line
|
||||
@ -3497,7 +3499,7 @@ else
|
||||
# Broken: fails on valid input.
|
||||
continue
|
||||
fi
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||
|
||||
# OK, works on sane cases. Now check whether nonexistent headers
|
||||
# can be detected and how.
|
||||
@ -3513,11 +3515,11 @@ else
|
||||
ac_preproc_ok=:
|
||||
break
|
||||
fi
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||
|
||||
done
|
||||
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.i conftest.err conftest.$ac_ext
|
||||
if $ac_preproc_ok; then :
|
||||
break
|
||||
fi
|
||||
@ -3556,7 +3558,7 @@ else
|
||||
# Broken: fails on valid input.
|
||||
continue
|
||||
fi
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||
|
||||
# OK, works on sane cases. Now check whether nonexistent headers
|
||||
# can be detected and how.
|
||||
@ -3572,18 +3574,18 @@ else
|
||||
ac_preproc_ok=:
|
||||
break
|
||||
fi
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||
|
||||
done
|
||||
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
|
||||
rm -f conftest.err conftest.$ac_ext
|
||||
rm -f conftest.i conftest.err conftest.$ac_ext
|
||||
if $ac_preproc_ok; then :
|
||||
|
||||
else
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error "C preprocessor \"$CPP\" fails sanity check
|
||||
See \`config.log' for more details." "$LINENO" 5; }
|
||||
as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
fi
|
||||
|
||||
ac_ext=c
|
||||
@ -3644,7 +3646,7 @@ esac
|
||||
done
|
||||
IFS=$as_save_IFS
|
||||
if test -z "$ac_cv_path_GREP"; then
|
||||
as_fn_error "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
|
||||
as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
|
||||
fi
|
||||
else
|
||||
ac_cv_path_GREP=$GREP
|
||||
@ -3710,7 +3712,7 @@ esac
|
||||
done
|
||||
IFS=$as_save_IFS
|
||||
if test -z "$ac_cv_path_EGREP"; then
|
||||
as_fn_error "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
|
||||
as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
|
||||
fi
|
||||
else
|
||||
ac_cv_path_EGREP=$EGREP
|
||||
@ -3842,8 +3844,7 @@ do :
|
||||
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
|
||||
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
|
||||
"
|
||||
eval as_val=\$$as_ac_Header
|
||||
if test "x$as_val" = x""yes; then :
|
||||
if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
|
||||
_ACEOF
|
||||
@ -3911,7 +3912,7 @@ else
|
||||
fi
|
||||
|
||||
if test $expat_found = no; then
|
||||
as_fn_error "Could not find development files of Expat library" "$LINENO" 5
|
||||
as_fn_error $? "Could not find development files of Expat library" "$LINENO" 5
|
||||
fi
|
||||
expat_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
|
||||
@ -3932,7 +3933,7 @@ fi
|
||||
done
|
||||
|
||||
if test $expat_found = no; then
|
||||
as_fn_error "Could not find expat.h" "$LINENO" 5
|
||||
as_fn_error $? "Could not find expat.h" "$LINENO" 5
|
||||
fi
|
||||
CFLAGS="$expat_save_CFLAGS"
|
||||
CPPFLAGS="$expat_save_CPPFLAGS"
|
||||
@ -4469,7 +4470,7 @@ else
|
||||
fi
|
||||
|
||||
if test $zlib_found = no; then
|
||||
as_fn_error "Could not find development files of zlib library. Install them or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
|
||||
as_fn_error $? "Could not find development files of zlib library. Install them or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
|
||||
fi
|
||||
zlib_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
||||
@ -4490,7 +4491,7 @@ fi
|
||||
done
|
||||
|
||||
if test $zlib_found = no; then
|
||||
as_fn_error "Could not find zlib.h. Install it or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
|
||||
as_fn_error $? "Could not find zlib.h. Install it or disable \`ejabberd_zlib' with: --disable-ejabberd_zlib" "$LINENO" 5
|
||||
fi
|
||||
CFLAGS="$zlib_save_CFLAGS"
|
||||
CPPFLAGS="$zlib_save_CPPFLAGS"
|
||||
@ -4581,7 +4582,7 @@ else
|
||||
fi
|
||||
|
||||
if test $pam_found = no; then
|
||||
as_fn_error "Could not find development files of PAM library. Install them or disable \`pam' with: --disable-pam" "$LINENO" 5
|
||||
as_fn_error $? "Could not find development files of PAM library. Install them or disable \`pam' with: --disable-pam" "$LINENO" 5
|
||||
fi
|
||||
pam_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $PAM_CFLAGS"
|
||||
@ -4602,7 +4603,7 @@ fi
|
||||
done
|
||||
|
||||
if test $pam_found = no; then
|
||||
as_fn_error "Could not find security/pam_appl.h. Install it or disable \`pam' with: --disable-pam" "$LINENO" 5
|
||||
as_fn_error $? "Could not find security/pam_appl.h. Install it or disable \`pam' with: --disable-pam" "$LINENO" 5
|
||||
fi
|
||||
CFLAGS="$pam_save_CFLAGS"
|
||||
CPPFLAGS="$pam_save_CPPFLAGS"
|
||||
@ -4617,7 +4618,7 @@ if test "${enable_hipe+set}" = set; then :
|
||||
enableval=$enable_hipe; case "${enableval}" in
|
||||
yes) hipe=true ;;
|
||||
no) hipe=false ;;
|
||||
*) as_fn_error "bad value ${enableval} for --enable-hipe" "$LINENO" 5 ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-hipe" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
hipe=false
|
||||
@ -4630,7 +4631,7 @@ if test "${enable_roster_gateway_workaround+set}" = set; then :
|
||||
enableval=$enable_roster_gateway_workaround; case "${enableval}" in
|
||||
yes) roster_gateway_workaround=true ;;
|
||||
no) roster_gateway_workaround=false ;;
|
||||
*) as_fn_error "bad value ${enableval} for --enable-roster-gateway-workaround" "$LINENO" 5 ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-roster-gateway-workaround" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
roster_gateway_workaround=false
|
||||
@ -4638,12 +4639,25 @@ fi
|
||||
|
||||
|
||||
|
||||
# Check whether --enable-flash_hack was given.
|
||||
if test "${enable_flash_hack+set}" = set; then :
|
||||
enableval=$enable_flash_hack; case "${enableval}" in
|
||||
yes) flash_hack=true ;;
|
||||
no) flash_hack=false ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-flash-hack" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
flash_hack=false
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# Check whether --enable-mssql was given.
|
||||
if test "${enable_mssql+set}" = set; then :
|
||||
enableval=$enable_mssql; case "${enableval}" in
|
||||
yes) db_type=mssql ;;
|
||||
no) db_type=generic ;;
|
||||
*) as_fn_error "bad value ${enableval} for --enable-mssql" "$LINENO" 5 ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-mssql" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
db_type=generic
|
||||
@ -4656,7 +4670,7 @@ if test "${enable_transient_supervisors+set}" = set; then :
|
||||
enableval=$enable_transient_supervisors; case "${enableval}" in
|
||||
yes) transient_supervisors=true ;;
|
||||
no) transient_supervisors=false ;;
|
||||
*) as_fn_error "bad value ${enableval} for --enable-transient_supervisors" "$LINENO" 5 ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-transient_supervisors" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
transient_supervisors=true
|
||||
@ -4669,7 +4683,7 @@ if test "${enable_full_xml+set}" = set; then :
|
||||
enableval=$enable_full_xml; case "${enableval}" in
|
||||
yes) full_xml=true ;;
|
||||
no) full_xml=false ;;
|
||||
*) as_fn_error "bad value ${enableval} for --enable-full-xml" "$LINENO" 5 ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-full-xml" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
full_xml=false
|
||||
@ -4682,7 +4696,7 @@ if test "${enable_nif+set}" = set; then :
|
||||
enableval=$enable_nif; case "${enableval}" in
|
||||
yes) nif=true ;;
|
||||
no) nif=false ;;
|
||||
*) as_fn_error "bad value ${enableval} for --enable-nif" "$LINENO" 5 ;;
|
||||
*) as_fn_error $? "bad value ${enableval} for --enable-nif" "$LINENO" 5 ;;
|
||||
esac
|
||||
else
|
||||
nif=false
|
||||
@ -4780,7 +4794,7 @@ done
|
||||
fi
|
||||
done
|
||||
if test x${have_openssl} != xyes; then
|
||||
as_fn_error "Could not find development files of OpenSSL library. Install them or disable \`tls' with: --disable-tls" "$LINENO" 5
|
||||
as_fn_error $? "Could not find development files of OpenSSL library. Install them or disable \`tls' with: --disable-tls" "$LINENO" 5
|
||||
fi
|
||||
|
||||
|
||||
@ -4835,16 +4849,22 @@ fi
|
||||
|
||||
ac_aux_dir=
|
||||
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
|
||||
for ac_t in install-sh install.sh shtool; do
|
||||
if test -f "$ac_dir/$ac_t"; then
|
||||
ac_aux_dir=$ac_dir
|
||||
ac_install_sh="$ac_aux_dir/$ac_t -c"
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
if test -f "$ac_dir/install-sh"; then
|
||||
ac_aux_dir=$ac_dir
|
||||
ac_install_sh="$ac_aux_dir/install-sh -c"
|
||||
break
|
||||
elif test -f "$ac_dir/install.sh"; then
|
||||
ac_aux_dir=$ac_dir
|
||||
ac_install_sh="$ac_aux_dir/install.sh -c"
|
||||
break
|
||||
elif test -f "$ac_dir/shtool"; then
|
||||
ac_aux_dir=$ac_dir
|
||||
ac_install_sh="$ac_aux_dir/shtool install -c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if test -z "$ac_aux_dir"; then
|
||||
as_fn_error "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
|
||||
as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
|
||||
fi
|
||||
|
||||
# These three variables are undocumented and unsupported,
|
||||
@ -4858,7 +4878,7 @@ ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
|
||||
|
||||
# Make sure we can run config.sub.
|
||||
$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
|
||||
as_fn_error "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
|
||||
as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
|
||||
$as_echo_n "checking build system type... " >&6; }
|
||||
@ -4869,16 +4889,16 @@ else
|
||||
test "x$ac_build_alias" = x &&
|
||||
ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
|
||||
test "x$ac_build_alias" = x &&
|
||||
as_fn_error "cannot guess build type; you must specify one" "$LINENO" 5
|
||||
as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
|
||||
ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
|
||||
as_fn_error "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
|
||||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
|
||||
|
||||
fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
|
||||
$as_echo "$ac_cv_build" >&6; }
|
||||
case $ac_cv_build in
|
||||
*-*-*) ;;
|
||||
*) as_fn_error "invalid value of canonical build" "$LINENO" 5;;
|
||||
*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5 ;;
|
||||
esac
|
||||
build=$ac_cv_build
|
||||
ac_save_IFS=$IFS; IFS='-'
|
||||
@ -4903,7 +4923,7 @@ else
|
||||
ac_cv_host=$ac_cv_build
|
||||
else
|
||||
ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
|
||||
as_fn_error "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
|
||||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
|
||||
fi
|
||||
|
||||
fi
|
||||
@ -4911,7 +4931,7 @@ fi
|
||||
$as_echo "$ac_cv_host" >&6; }
|
||||
case $ac_cv_host in
|
||||
*-*-*) ;;
|
||||
*) as_fn_error "invalid value of canonical host" "$LINENO" 5;;
|
||||
*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5 ;;
|
||||
esac
|
||||
host=$ac_cv_host
|
||||
ac_save_IFS=$IFS; IFS='-'
|
||||
@ -4936,7 +4956,7 @@ else
|
||||
ac_cv_target=$ac_cv_host
|
||||
else
|
||||
ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` ||
|
||||
as_fn_error "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5
|
||||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $target_alias failed" "$LINENO" 5
|
||||
fi
|
||||
|
||||
fi
|
||||
@ -4944,7 +4964,7 @@ fi
|
||||
$as_echo "$ac_cv_target" >&6; }
|
||||
case $ac_cv_target in
|
||||
*-*-*) ;;
|
||||
*) as_fn_error "invalid value of canonical target" "$LINENO" 5;;
|
||||
*) as_fn_error $? "invalid value of canonical target" "$LINENO" 5 ;;
|
||||
esac
|
||||
target=$ac_cv_target
|
||||
ac_save_IFS=$IFS; IFS='-'
|
||||
@ -5088,8 +5108,8 @@ fi
|
||||
if test "$cross_compiling" = yes; then :
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error "cannot run test program while cross compiling
|
||||
See \`config.log' for more details." "$LINENO" 5; }
|
||||
as_fn_error $? "cannot run test program while cross compiling
|
||||
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||
else
|
||||
cat > conftest.$ac_ext <<_ACEOF
|
||||
-module(conftest).
|
||||
@ -5243,6 +5263,7 @@ DEFS=`sed -n "$ac_script" confdefs.h`
|
||||
|
||||
ac_libobjs=
|
||||
ac_ltlibobjs=
|
||||
U=
|
||||
for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
|
||||
# 1. Remove the extension, and $U if already installed.
|
||||
ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
|
||||
@ -5404,19 +5425,19 @@ export LANGUAGE
|
||||
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
|
||||
|
||||
|
||||
# as_fn_error ERROR [LINENO LOG_FD]
|
||||
# ---------------------------------
|
||||
# as_fn_error STATUS ERROR [LINENO LOG_FD]
|
||||
# ----------------------------------------
|
||||
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
|
||||
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
|
||||
# script with status $?, using 1 if that was 0.
|
||||
# script with STATUS, using 1 if that was 0.
|
||||
as_fn_error ()
|
||||
{
|
||||
as_status=$?; test $as_status -eq 0 && as_status=1
|
||||
if test "$3"; then
|
||||
as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3
|
||||
as_status=$1; test $as_status -eq 0 && as_status=1
|
||||
if test "$4"; then
|
||||
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
|
||||
fi
|
||||
$as_echo "$as_me: error: $1" >&2
|
||||
$as_echo "$as_me: error: $2" >&2
|
||||
as_fn_exit $as_status
|
||||
} # as_fn_error
|
||||
|
||||
@ -5612,7 +5633,7 @@ $as_echo X"$as_dir" |
|
||||
test -d "$as_dir" && break
|
||||
done
|
||||
test -z "$as_dirs" || eval "mkdir $as_dirs"
|
||||
} || test -d "$as_dir" || as_fn_error "cannot create directory $as_dir"
|
||||
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
|
||||
|
||||
|
||||
} # as_fn_mkdir_p
|
||||
@ -5666,7 +5687,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by ejabberd $as_me 2.1.x, which was
|
||||
generated by GNU Autoconf 2.65. Invocation command line was
|
||||
generated by GNU Autoconf 2.67. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
CONFIG_HEADERS = $CONFIG_HEADERS
|
||||
@ -5719,10 +5740,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
ejabberd config.status 2.1.x
|
||||
configured by $0, generated by GNU Autoconf 2.65,
|
||||
configured by $0, generated by GNU Autoconf 2.67,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
Copyright (C) 2009 Free Software Foundation, Inc.
|
||||
Copyright (C) 2010 Free Software Foundation, Inc.
|
||||
This config.status script is free software; the Free Software Foundation
|
||||
gives unlimited permission to copy, distribute and modify it."
|
||||
|
||||
@ -5737,11 +5758,16 @@ ac_need_defaults=:
|
||||
while test $# != 0
|
||||
do
|
||||
case $1 in
|
||||
--*=*)
|
||||
--*=?*)
|
||||
ac_option=`expr "X$1" : 'X\([^=]*\)='`
|
||||
ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
|
||||
ac_shift=:
|
||||
;;
|
||||
--*=)
|
||||
ac_option=`expr "X$1" : 'X\([^=]*\)='`
|
||||
ac_optarg=
|
||||
ac_shift=:
|
||||
;;
|
||||
*)
|
||||
ac_option=$1
|
||||
ac_optarg=$2
|
||||
@ -5763,6 +5789,7 @@ do
|
||||
$ac_shift
|
||||
case $ac_optarg in
|
||||
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
|
||||
'') as_fn_error $? "missing file argument" ;;
|
||||
esac
|
||||
as_fn_append CONFIG_FILES " '$ac_optarg'"
|
||||
ac_need_defaults=false;;
|
||||
@ -5773,7 +5800,7 @@ do
|
||||
ac_cs_silent=: ;;
|
||||
|
||||
# This is an error.
|
||||
-*) as_fn_error "unrecognized option: \`$1'
|
||||
-*) as_fn_error $? "unrecognized option: \`$1'
|
||||
Try \`$0 --help' for more information." ;;
|
||||
|
||||
*) as_fn_append ac_config_targets " $1"
|
||||
@ -5836,7 +5863,7 @@ do
|
||||
"$make_odbc") CONFIG_FILES="$CONFIG_FILES $make_odbc" ;;
|
||||
"$make_ejabberd_zlib") CONFIG_FILES="$CONFIG_FILES $make_ejabberd_zlib" ;;
|
||||
|
||||
*) as_fn_error "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
|
||||
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@ -5872,7 +5899,7 @@ $debug ||
|
||||
{
|
||||
tmp=./conf$$-$RANDOM
|
||||
(umask 077 && mkdir "$tmp")
|
||||
} || as_fn_error "cannot create a temporary directory in ." "$LINENO" 5
|
||||
} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
|
||||
|
||||
# Set up the scripts for CONFIG_FILES section.
|
||||
# No need to generate them if there are no CONFIG_FILES.
|
||||
@ -5889,7 +5916,7 @@ if test "x$ac_cr" = x; then
|
||||
fi
|
||||
ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
|
||||
if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
|
||||
ac_cs_awk_cr='\r'
|
||||
ac_cs_awk_cr='\\r'
|
||||
else
|
||||
ac_cs_awk_cr=$ac_cr
|
||||
fi
|
||||
@ -5903,18 +5930,18 @@ _ACEOF
|
||||
echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
|
||||
echo "_ACEOF"
|
||||
} >conf$$subs.sh ||
|
||||
as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5
|
||||
ac_delim_num=`echo "$ac_subst_vars" | grep -c '$'`
|
||||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
|
||||
ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
|
||||
ac_delim='%!_!# '
|
||||
for ac_last_try in false false false false false :; do
|
||||
. ./conf$$subs.sh ||
|
||||
as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5
|
||||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
|
||||
|
||||
ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
|
||||
if test $ac_delim_n = $ac_delim_num; then
|
||||
break
|
||||
elif $ac_last_try; then
|
||||
as_fn_error "could not make $CONFIG_STATUS" "$LINENO" 5
|
||||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
|
||||
else
|
||||
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
|
||||
fi
|
||||
@ -6003,20 +6030,28 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
|
||||
else
|
||||
cat
|
||||
fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
|
||||
|| as_fn_error "could not setup config files machinery" "$LINENO" 5
|
||||
|| as_fn_error $? "could not setup config files machinery" "$LINENO" 5
|
||||
_ACEOF
|
||||
|
||||
# VPATH may cause trouble with some makes, so we remove $(srcdir),
|
||||
# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
|
||||
# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
|
||||
# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
|
||||
# trailing colons and then remove the whole line if VPATH becomes empty
|
||||
# (actually we leave an empty line to preserve line numbers).
|
||||
if test "x$srcdir" = x.; then
|
||||
ac_vpsub='/^[ ]*VPATH[ ]*=/{
|
||||
s/:*\$(srcdir):*/:/
|
||||
s/:*\${srcdir}:*/:/
|
||||
s/:*@srcdir@:*/:/
|
||||
s/^\([^=]*=[ ]*\):*/\1/
|
||||
ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
|
||||
h
|
||||
s///
|
||||
s/^/:/
|
||||
s/[ ]*$/:/
|
||||
s/:\$(srcdir):/:/g
|
||||
s/:\${srcdir}:/:/g
|
||||
s/:@srcdir@:/:/g
|
||||
s/^:*//
|
||||
s/:*$//
|
||||
x
|
||||
s/\(=[ ]*\).*/\1/
|
||||
G
|
||||
s/\n//
|
||||
s/^[^=]*=[ ]*$//
|
||||
}'
|
||||
fi
|
||||
@ -6034,7 +6069,7 @@ do
|
||||
esac
|
||||
case $ac_mode$ac_tag in
|
||||
:[FHL]*:*);;
|
||||
:L* | :C*:*) as_fn_error "invalid tag \`$ac_tag'" "$LINENO" 5;;
|
||||
:L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5 ;;
|
||||
:[FH]-) ac_tag=-:-;;
|
||||
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
|
||||
esac
|
||||
@ -6062,7 +6097,7 @@ do
|
||||
[\\/$]*) false;;
|
||||
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
|
||||
esac ||
|
||||
as_fn_error "cannot find input file: \`$ac_f'" "$LINENO" 5;;
|
||||
as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5 ;;
|
||||
esac
|
||||
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
|
||||
as_fn_append ac_file_inputs " '$ac_f'"
|
||||
@ -6089,7 +6124,7 @@ $as_echo "$as_me: creating $ac_file" >&6;}
|
||||
|
||||
case $ac_tag in
|
||||
*:-:* | *:-) cat >"$tmp/stdin" \
|
||||
|| as_fn_error "could not create $ac_file" "$LINENO" 5 ;;
|
||||
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@ -6215,22 +6250,22 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
|
||||
$ac_datarootdir_hack
|
||||
"
|
||||
eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
|
||||
|| as_fn_error "could not create $ac_file" "$LINENO" 5
|
||||
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
|
||||
|
||||
test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
|
||||
{ ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } &&
|
||||
{ ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } &&
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
|
||||
which seems to be undefined. Please make sure it is defined." >&5
|
||||
which seems to be undefined. Please make sure it is defined" >&5
|
||||
$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
|
||||
which seems to be undefined. Please make sure it is defined." >&2;}
|
||||
which seems to be undefined. Please make sure it is defined" >&2;}
|
||||
|
||||
rm -f "$tmp/stdin"
|
||||
case $ac_file in
|
||||
-) cat "$tmp/out" && rm -f "$tmp/out";;
|
||||
*) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
|
||||
esac \
|
||||
|| as_fn_error "could not create $ac_file" "$LINENO" 5
|
||||
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
|
||||
;;
|
||||
|
||||
|
||||
@ -6245,7 +6280,7 @@ _ACEOF
|
||||
ac_clean_files=$ac_clean_files_save
|
||||
|
||||
test $ac_write_fail = 0 ||
|
||||
as_fn_error "write failure creating $CONFIG_STATUS" "$LINENO" 5
|
||||
as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
|
||||
|
||||
|
||||
# configure is writing to config.log, and then calls config.status.
|
||||
@ -6266,7 +6301,7 @@ if test "$no_create" != yes; then
|
||||
exec 5>>config.log
|
||||
# Use ||, not &&, to avoid exiting from the if with $? = 1, which
|
||||
# would make configure fail if this is the last instruction.
|
||||
$ac_cs_success || as_fn_exit $?
|
||||
$ac_cs_success || as_fn_exit 1
|
||||
fi
|
||||
if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
|
||||
|
@ -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
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.1.x"},
|
||||
{vsn, "2.2.x"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
|
@ -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).
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -35,13 +35,16 @@
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
start_link/2,
|
||||
start_link/3,
|
||||
send_text/2,
|
||||
send_element/2,
|
||||
socket_type/0,
|
||||
get_presence/1,
|
||||
get_subscribed/1]).
|
||||
|
||||
%% API:
|
||||
-export([add_rosteritem/3, del_rosteritem/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
wait_for_stream/2,
|
||||
@ -56,69 +59,14 @@
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1
|
||||
]).
|
||||
print_state/1,
|
||||
migrate/3
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
-define(DICT, dict).
|
||||
|
||||
%% pres_a contains all the presence available send (either through roster mechanism or directed).
|
||||
%% Directed presence unavailable remove user from pres_a.
|
||||
-record(state, {socket,
|
||||
sockmod,
|
||||
socket_monitor,
|
||||
xml_socket,
|
||||
streamid,
|
||||
sasl_state,
|
||||
access,
|
||||
shaper,
|
||||
zlib = false,
|
||||
tls = false,
|
||||
tls_required = false,
|
||||
tls_enabled = false,
|
||||
tls_options = [],
|
||||
authenticated = false,
|
||||
jid,
|
||||
user = "", server = ?MYNAME, resource = "",
|
||||
sid,
|
||||
pres_t = ?SETS:new(),
|
||||
pres_f = ?SETS:new(),
|
||||
pres_a = ?SETS:new(),
|
||||
pres_i = ?SETS:new(),
|
||||
pres_last, pres_pri,
|
||||
pres_timestamp,
|
||||
pres_invis = false,
|
||||
privacy_list = #userlist{},
|
||||
conn = unknown,
|
||||
auth_module = unknown,
|
||||
ip,
|
||||
lang,
|
||||
reception = true,
|
||||
standby = false,
|
||||
queue = queue:new(),
|
||||
queue_len = 0,
|
||||
pres_queue = gb_trees:empty(),
|
||||
keepalive_timer,
|
||||
keepalive_timeout,
|
||||
oor_timeout,
|
||||
oor_status = "",
|
||||
oor_show = "",
|
||||
oor_notification,
|
||||
oor_send_body = all,
|
||||
oor_send_groupchat = false,
|
||||
oor_send_from = jid,
|
||||
oor_appid = "",
|
||||
oor_unread = 0,
|
||||
oor_unread_users = ?SETS:new(),
|
||||
oor_offline = false,
|
||||
ack_enabled = false,
|
||||
ack_counter = 0,
|
||||
ack_queue = queue:new(),
|
||||
ack_timer}).
|
||||
-include("ejabberd_c2s.hrl").
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
@ -130,11 +78,12 @@
|
||||
|
||||
%% Module start with or without supervisor:
|
||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, [SockData, Opts],
|
||||
fsm_limit_opts(Opts) ++ ?FSMOPTS)).
|
||||
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s,
|
||||
[SockData, Opts, FSMLimitOpts],
|
||||
FSMLimitOpts ++ ?FSMOPTS)).
|
||||
-else.
|
||||
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
|
||||
[SockData, Opts])).
|
||||
[SockData, Opts, FSMLimitOpts])).
|
||||
-endif.
|
||||
|
||||
%% This is the timeout to apply between event when starting a new
|
||||
@ -149,6 +98,13 @@
|
||||
"id='~s' from='~s'~s~s>"
|
||||
).
|
||||
|
||||
-define(FLASH_STREAM_HEADER,
|
||||
"<?xml version='1.0'?>"
|
||||
"<flash:stream xmlns='jabber:client' "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"id='~s' from='~s'~s~s>"
|
||||
).
|
||||
|
||||
-define(STREAM_TRAILER, "</stream:stream>").
|
||||
|
||||
-define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE).
|
||||
@ -171,12 +127,17 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(StateName, #state{fsm_limit_opts = Opts} = State) ->
|
||||
start(StateName, State, Opts);
|
||||
start(SockData, Opts) ->
|
||||
start(SockData, Opts, fsm_limit_opts(Opts)).
|
||||
|
||||
start(SockData, Opts, FSMLimitOpts) ->
|
||||
?SUPERVISOR_START.
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts],
|
||||
fsm_limit_opts(Opts) ++ ?FSMOPTS).
|
||||
start_link(SockData, Opts, FSMLimitOpts) ->
|
||||
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts, FSMLimitOpts],
|
||||
FSMLimitOpts ++ ?FSMOPTS).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
@ -185,9 +146,18 @@ socket_type() ->
|
||||
get_presence(FsmRef) ->
|
||||
?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000).
|
||||
|
||||
add_rosteritem(FsmRef, IJID, ISubscription) ->
|
||||
?GEN_FSM:send_all_state_event(FsmRef, {add_rosteritem, IJID, ISubscription}).
|
||||
|
||||
del_rosteritem(FsmRef, IJID) ->
|
||||
?GEN_FSM:send_all_state_event(FsmRef, {del_rosteritem, IJID}).
|
||||
|
||||
stop(FsmRef) ->
|
||||
?GEN_FSM:send_event(FsmRef, closed).
|
||||
|
||||
migrate(FsmRef, Node, After) ->
|
||||
?GEN_FSM:send_all_state_event(FsmRef, {migrate, Node, After}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -199,7 +169,7 @@ stop(FsmRef) ->
|
||||
%% ignore |
|
||||
%% {stop, StopReason}
|
||||
%%----------------------------------------------------------------------
|
||||
init([{SockMod, Socket}, Opts]) ->
|
||||
init([{SockMod, Socket}, Opts, FSMLimitOpts]) ->
|
||||
Access = case lists:keysearch(access, 1, Opts) of
|
||||
{value, {_, A}} -> A;
|
||||
_ -> all
|
||||
@ -223,7 +193,12 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
TLSOpts = [verify_none | TLSOpts1],
|
||||
IP = peerip(SockMod, Socket),
|
||||
IP = case lists:keysearch(frontend_ip, 1, Opts) of
|
||||
{value, {_, IP1}} ->
|
||||
IP1;
|
||||
_ ->
|
||||
peerip(SockMod, Socket)
|
||||
end,
|
||||
%% Check if IP is blacklisted:
|
||||
case is_ip_blacklisted(IP) of
|
||||
true ->
|
||||
@ -239,20 +214,47 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
Socket
|
||||
end,
|
||||
SocketMonitor = SockMod:monitor(Socket1),
|
||||
{ok, wait_for_stream, #state{socket = Socket1,
|
||||
sockmod = SockMod,
|
||||
socket_monitor = SocketMonitor,
|
||||
xml_socket = XMLSocket,
|
||||
zlib = Zlib,
|
||||
tls = TLS,
|
||||
tls_required = StartTLSRequired,
|
||||
tls_enabled = TLSEnabled,
|
||||
tls_options = TLSOpts,
|
||||
streamid = new_id(),
|
||||
access = Access,
|
||||
shaper = Shaper,
|
||||
ip = IP},
|
||||
?C2S_OPEN_TIMEOUT}
|
||||
StateData = #state{socket = Socket1,
|
||||
sockmod = SockMod,
|
||||
socket_monitor = SocketMonitor,
|
||||
xml_socket = XMLSocket,
|
||||
zlib = Zlib,
|
||||
tls = TLS,
|
||||
tls_required = StartTLSRequired,
|
||||
tls_enabled = TLSEnabled,
|
||||
tls_options = TLSOpts,
|
||||
streamid = new_id(),
|
||||
access = Access,
|
||||
shaper = Shaper,
|
||||
ip = IP,
|
||||
fsm_limit_opts = FSMLimitOpts},
|
||||
{ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT}
|
||||
end;
|
||||
init([StateName, StateData, _FSMLimitOpts]) ->
|
||||
MRef = (StateData#state.sockmod):monitor(StateData#state.socket),
|
||||
if StateName == session_established ->
|
||||
Conn = get_conn_type(StateData),
|
||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
{auth_module, StateData#state.auth_module}],
|
||||
{Time, _} = StateData#state.sid,
|
||||
SID = {Time, self()},
|
||||
Priority = case StateData#state.pres_last of
|
||||
undefined ->
|
||||
undefined;
|
||||
El ->
|
||||
get_priority_from_presence(El)
|
||||
end,
|
||||
ejabberd_sm:open_session(
|
||||
SID,
|
||||
StateData#state.user,
|
||||
StateData#state.server,
|
||||
StateData#state.resource,
|
||||
Priority,
|
||||
Info),
|
||||
NewStateData = StateData#state{sid = SID, socket_monitor = MRef},
|
||||
{ok, StateName, NewStateData};
|
||||
true ->
|
||||
{ok, StateName, StateData#state{socket_monitor = MRef}}
|
||||
end.
|
||||
|
||||
%% Return list of all available resources of contacts,
|
||||
@ -266,15 +268,25 @@ get_subscribed(FsmRef) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
|
||||
DefaultLang = case ?MYLANG of
|
||||
undefined ->
|
||||
"en";
|
||||
DL ->
|
||||
DL
|
||||
end,
|
||||
case xml:get_attr_s("xmlns:stream", Attrs) of
|
||||
?NS_STREAM ->
|
||||
|
||||
case {xml:get_attr_s("xmlns:stream", Attrs),
|
||||
xml:get_attr_s("xmlns:flash", Attrs),
|
||||
?FLASH_HACK,
|
||||
StateData#state.flash_connection} of
|
||||
{_, ?NS_FLASH_STREAM, true, false} ->
|
||||
%% Flash client connecting - attention!
|
||||
%% Some of them don't provide an xmlns:stream attribute -
|
||||
%% compensate for that.
|
||||
wait_for_stream({xmlstreamstart, Name, [{"xmlns:stream", ?NS_STREAM}|Attrs]},
|
||||
StateData#state{flash_connection = true});
|
||||
{?NS_STREAM, _, _, _} ->
|
||||
Server = jlib:nameprep(xml:get_attr_s("to", Attrs)),
|
||||
case lists:member(Server, ?MYHOSTS) of
|
||||
true ->
|
||||
@ -432,11 +444,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, "", DefaultLang),
|
||||
send_element(StateData, ?INVALID_NS_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
_ ->
|
||||
case Name of
|
||||
"policy-file-request" ->
|
||||
send_text(StateData, flash_policy_string()),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, "", DefaultLang),
|
||||
send_element(StateData, ?INVALID_NS_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
end
|
||||
end;
|
||||
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
@ -515,13 +533,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
jlib:jid_to_string(JID), AuthModule]),
|
||||
SID = {now(), self()},
|
||||
Conn = get_conn_type(StateData),
|
||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
{auth_module, AuthModule}],
|
||||
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
%% {auth_module, AuthModule}],
|
||||
Res1 = jlib:make_result_iq_reply(El),
|
||||
Res = setelement(4, Res1, []),
|
||||
send_element(StateData, Res),
|
||||
ejabberd_sm:open_session(
|
||||
SID, U, StateData#state.server, R, Info),
|
||||
%% ejabberd_sm:open_session(
|
||||
%% SID, U, StateData#state.server, R, Info),
|
||||
change_shaper(StateData, JID),
|
||||
{Fs, Ts} = ejabberd_hooks:run_fold(
|
||||
roster_get_subscription_lists,
|
||||
@ -537,8 +555,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
privacy_get_user_list, StateData#state.server,
|
||||
#userlist{},
|
||||
[U, StateData#state.server]),
|
||||
NewStateData =
|
||||
StateData#state{
|
||||
NewStateData = StateData#state{
|
||||
user = U,
|
||||
resource = R,
|
||||
jid = JID,
|
||||
@ -548,8 +565,11 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
pres_f = ?SETS:from_list(Fs1),
|
||||
pres_t = ?SETS:from_list(Ts1),
|
||||
privacy_list = PrivList},
|
||||
fsm_next_state_pack(session_established,
|
||||
NewStateData);
|
||||
DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook,
|
||||
NewStateData#state.server,
|
||||
false,
|
||||
[self(), NewStateData]),
|
||||
maybe_migrate(session_established, NewStateData#state{debug=DebugFlag});
|
||||
_ ->
|
||||
?INFO_MSG(
|
||||
"(~w) Failed legacy authentication for ~s",
|
||||
@ -641,8 +661,8 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
Mech,
|
||||
ClientIn) of
|
||||
{ok, Props} ->
|
||||
(StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
catch (StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
send_element(StateData,
|
||||
{xmlelement, "success",
|
||||
[{"xmlns", ?NS_SASL}], []}),
|
||||
@ -794,8 +814,8 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
|
||||
case cyrsasl:server_step(StateData#state.sasl_state,
|
||||
ClientIn) of
|
||||
{ok, Props} ->
|
||||
(StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
catch (StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
send_element(StateData,
|
||||
{xmlelement, "success",
|
||||
[{"xmlns", ?NS_SASL}], []}),
|
||||
@ -919,7 +939,7 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
|
||||
case jlib:iq_query_info(El) of
|
||||
#iq{type = set, xmlns = ?NS_SESSION} ->
|
||||
U = StateData#state.user,
|
||||
R = StateData#state.resource,
|
||||
%%R = StateData#state.resource,
|
||||
JID = StateData#state.jid,
|
||||
case acl:match_rule(StateData#state.server,
|
||||
StateData#state.access, JID) of
|
||||
@ -945,10 +965,10 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
|
||||
[U, StateData#state.server]),
|
||||
SID = {now(), self()},
|
||||
Conn = get_conn_type(StateData),
|
||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
{auth_module, StateData#state.auth_module}],
|
||||
ejabberd_sm:open_session(
|
||||
SID, U, StateData#state.server, R, Info),
|
||||
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
%% {auth_module, StateData#state.auth_module}],
|
||||
%% ejabberd_sm:open_session(
|
||||
%% SID, U, StateData#state.server, R, Info),
|
||||
NewStateData =
|
||||
StateData#state{
|
||||
sid = SID,
|
||||
@ -956,8 +976,11 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
|
||||
pres_f = ?SETS:from_list(Fs1),
|
||||
pres_t = ?SETS:from_list(Ts1),
|
||||
privacy_list = PrivList},
|
||||
fsm_next_state_pack(session_established,
|
||||
NewStateData);
|
||||
DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook,
|
||||
NewStateData#state.server,
|
||||
false,
|
||||
[self(), NewStateData]),
|
||||
maybe_migrate(session_established, NewStateData#state{debug=DebugFlag});
|
||||
_ ->
|
||||
ejabberd_hooks:run(forbidden_session_hook,
|
||||
StateData#state.server, [JID]),
|
||||
@ -1084,7 +1107,7 @@ session_established2(El, StateData) ->
|
||||
ejabberd_hooks:run(
|
||||
user_send_packet,
|
||||
Server,
|
||||
[FromJID, ToJID, PresenceEl]),
|
||||
[StateData#state.debug, FromJID, ToJID, PresenceEl]),
|
||||
case ToJID of
|
||||
#jid{user = User,
|
||||
server = Server,
|
||||
@ -1100,6 +1123,10 @@ session_established2(El, StateData) ->
|
||||
"iq" ->
|
||||
case jlib:iq_query_info(NewEl) of
|
||||
#iq{xmlns = ?NS_PRIVACY} = IQ ->
|
||||
ejabberd_hooks:run(
|
||||
user_send_packet,
|
||||
Server,
|
||||
[StateData#state.debug, FromJID, ToJID, NewEl]),
|
||||
process_privacy_iq(
|
||||
FromJID, ToJID, IQ, StateData);
|
||||
#iq{xmlns = ?NS_P1_PUSH} = IQ ->
|
||||
@ -1108,7 +1135,7 @@ session_established2(El, StateData) ->
|
||||
ejabberd_hooks:run(
|
||||
user_send_packet,
|
||||
Server,
|
||||
[FromJID, ToJID, NewEl]),
|
||||
[StateData#state.debug, FromJID, ToJID, NewEl]),
|
||||
ejabberd_router:route(
|
||||
FromJID, ToJID, NewEl),
|
||||
StateData
|
||||
@ -1116,7 +1143,7 @@ session_established2(El, StateData) ->
|
||||
"message" ->
|
||||
ejabberd_hooks:run(user_send_packet,
|
||||
Server,
|
||||
[FromJID, ToJID, NewEl]),
|
||||
[StateData#state.debug, FromJID, ToJID, NewEl]),
|
||||
check_privacy_route(FromJID, StateData, FromJID,
|
||||
ToJID, NewEl),
|
||||
StateData;
|
||||
@ -1154,6 +1181,17 @@ session_established2(El, StateData) ->
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_event({migrate, Node, After}, StateName, StateData) when Node /= node() ->
|
||||
fsm_migrate(StateName, StateData, Node, After * 2);
|
||||
|
||||
handle_event({add_rosteritem, IJID, ISubscription}, StateName, StateData) ->
|
||||
NewStateData = roster_change(IJID, ISubscription, StateData),
|
||||
fsm_next_state(StateName, NewStateData);
|
||||
|
||||
handle_event({del_rosteritem, IJID}, StateName, StateData) ->
|
||||
NewStateData = roster_change(IJID, none, StateData),
|
||||
fsm_next_state(StateName, NewStateData);
|
||||
|
||||
handle_event({xmlstreamcdata, _}, StateName, StateData) ->
|
||||
?DEBUG("cdata ping", []),
|
||||
NSD1 = change_reception(StateData, true),
|
||||
@ -1458,7 +1496,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
end,
|
||||
ejabberd_hooks:run(user_receive_packet,
|
||||
StateData#state.server,
|
||||
[StateData#state.jid, From, To, FixedPacket]),
|
||||
[StateData#state.debug, StateData#state.jid, From, To, FixedPacket]),
|
||||
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
|
||||
fsm_next_state(StateName, NewState2);
|
||||
true ->
|
||||
@ -1560,12 +1598,31 @@ print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
|
||||
pres_a = {pres_a, ?SETS:size(A)},
|
||||
pres_i = {pres_i, ?SETS:size(I)}
|
||||
}.
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: terminate/3
|
||||
%% Purpose: Shutdown the fsm
|
||||
%% Returns: any
|
||||
%%----------------------------------------------------------------------
|
||||
terminate({migrated, ClonePid}, StateName, StateData) ->
|
||||
ejabberd_hooks:run(c2s_debug_stop_hook,
|
||||
StateData#state.server,
|
||||
[self(), StateData]),
|
||||
if StateName == session_established ->
|
||||
?INFO_MSG("(~w) Migrating ~s to ~p on node ~p",
|
||||
[StateData#state.socket,
|
||||
jlib:jid_to_string(StateData#state.jid),
|
||||
ClonePid, node(ClonePid)]),
|
||||
ejabberd_sm:close_migrated_session(StateData#state.sid,
|
||||
StateData#state.user,
|
||||
StateData#state.server,
|
||||
StateData#state.resource);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
(StateData#state.sockmod):change_controller(
|
||||
StateData#state.socket, ClonePid),
|
||||
ok;
|
||||
terminate(_Reason, StateName, StateData) ->
|
||||
case StateName of
|
||||
session_established ->
|
||||
@ -1675,7 +1732,14 @@ change_shaper(StateData, JID) ->
|
||||
|
||||
send_text(StateData, Text) ->
|
||||
?DEBUG("Send XML on stream = ~p", [Text]),
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text).
|
||||
Text1 =
|
||||
if ?FLASH_HACK and StateData#state.flash_connection ->
|
||||
%% send a null byte after each stanza to Flash clients
|
||||
[Text, 0];
|
||||
true ->
|
||||
Text
|
||||
end,
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text1).
|
||||
|
||||
send_element(StateData, El) when StateData#state.xml_socket ->
|
||||
ejabberd_hooks:run(feature_inspect_packet,
|
||||
@ -1693,6 +1757,15 @@ send_element(StateData, El) ->
|
||||
StateData#state.pres_last, El]),
|
||||
send_text(StateData, xml:element_to_binary(El)).
|
||||
|
||||
send_header(StateData,Server, Version, Lang)
|
||||
when StateData#state.flash_connection ->
|
||||
Header = io_lib:format(?FLASH_STREAM_HEADER,
|
||||
[StateData#state.streamid,
|
||||
Server,
|
||||
Version,
|
||||
Lang]),
|
||||
send_text(StateData, Header);
|
||||
|
||||
send_header(StateData, Server, Version, Lang)
|
||||
when StateData#state.xml_socket ->
|
||||
VersionAttr =
|
||||
@ -1781,16 +1854,22 @@ get_auth_tags([], U, P, D, R) ->
|
||||
|
||||
get_conn_type(StateData) ->
|
||||
case (StateData#state.sockmod):get_sockmod(StateData#state.socket) of
|
||||
gen_tcp -> c2s;
|
||||
tls -> c2s_tls;
|
||||
ejabberd_zlib ->
|
||||
case ejabberd_zlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
|
||||
gen_tcp -> c2s_compressed;
|
||||
tls -> c2s_compressed_tls
|
||||
end;
|
||||
ejabberd_http_poll -> http_poll;
|
||||
ejabberd_http_bind -> http_bind;
|
||||
_ -> unknown
|
||||
gen_tcp -> c2s;
|
||||
tls -> c2s_tls;
|
||||
ejabberd_zlib ->
|
||||
if is_pid(StateData#state.socket) ->
|
||||
unknown;
|
||||
true ->
|
||||
case ejabberd_zlib:get_sockmod(
|
||||
(StateData#state.socket)#socket_state.socket) of
|
||||
gen_tcp -> c2s_compressed;
|
||||
tls -> c2s_compressed_tls
|
||||
end
|
||||
end;
|
||||
ejabberd_http_poll -> http_poll;
|
||||
ejabberd_http_ws -> http_ws;
|
||||
ejabberd_http_bind -> http_bind;
|
||||
_ -> unknown
|
||||
end.
|
||||
|
||||
process_presence_probe(From, To, StateData) ->
|
||||
@ -2197,6 +2276,7 @@ roster_change(IJID, ISubscription, StateData) ->
|
||||
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
|
||||
From = StateData#state.jid,
|
||||
To = jlib:make_jid(IJID),
|
||||
% To = IJID,
|
||||
Cond1 = (not StateData#state.pres_invis) and IsFrom
|
||||
and (not OldIsFrom),
|
||||
Cond2 = (not IsFrom) and OldIsFrom
|
||||
@ -2353,7 +2433,7 @@ resend_offline_messages(#state{user = User,
|
||||
send_element(StateData, FixedPacket),
|
||||
ejabberd_hooks:run(user_receive_packet,
|
||||
StateData#state.server,
|
||||
[StateData#state.jid,
|
||||
[StateData#state.debug, StateData#state.jid,
|
||||
From, To, FixedPacket]);
|
||||
true ->
|
||||
ok
|
||||
@ -2426,16 +2506,28 @@ peerip(SockMod, Socket) ->
|
||||
_ -> undefined
|
||||
end.
|
||||
|
||||
%% fsm_next_state_pack: Pack the StateData structure to improve
|
||||
%% sharing.
|
||||
fsm_next_state_pack(StateName, StateData) ->
|
||||
fsm_next_state_gc(StateName, pack(StateData)).
|
||||
|
||||
%% fsm_next_state_gc: Garbage collect the process heap to make use of
|
||||
%% the newly packed StateData structure.
|
||||
fsm_next_state_gc(StateName, PackedStateData) ->
|
||||
erlang:garbage_collect(),
|
||||
fsm_next_state(StateName, PackedStateData).
|
||||
maybe_migrate(StateName, StateData) ->
|
||||
PackedStateData = pack(StateData),
|
||||
case ejabberd_cluster:get_node({StateData#state.user,
|
||||
StateData#state.server}) of
|
||||
Node when Node == node() ->
|
||||
Conn = get_conn_type(StateData),
|
||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
{auth_module, StateData#state.auth_module}],
|
||||
#state{user = U, server = S, resource = R, sid = SID} = StateData,
|
||||
ejabberd_sm:open_session(SID, U, S, R, Info),
|
||||
erlang:garbage_collect(),
|
||||
case ejabberd_cluster:get_node_new({U, S}) of
|
||||
Node ->
|
||||
ok;
|
||||
NewNode ->
|
||||
After = ejabberd_cluster:rehash_timeout(),
|
||||
migrate(self(), NewNode, After)
|
||||
end,
|
||||
fsm_next_state(StateName, PackedStateData);
|
||||
Node ->
|
||||
fsm_migrate(StateName, PackedStateData, Node, 0)
|
||||
end.
|
||||
|
||||
%% fsm_next_state: Generate the next_state FSM tuple with different
|
||||
%% timeout, depending on the future state
|
||||
@ -2444,6 +2536,10 @@ fsm_next_state(session_established, StateData) ->
|
||||
fsm_next_state(StateName, StateData) ->
|
||||
{next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
|
||||
|
||||
fsm_migrate(StateName, StateData, Node, Timeout) ->
|
||||
{migrate, StateData,
|
||||
{Node, ?MODULE, start, [StateName, StateData]}, Timeout}.
|
||||
|
||||
%% fsm_reply: Generate the reply FSM tuple with different timeout,
|
||||
%% depending on the future state
|
||||
fsm_reply(Reply, session_established, StateData) ->
|
||||
@ -3180,3 +3276,28 @@ pack_string(String, Pack) ->
|
||||
none ->
|
||||
{String, gb_trees:insert(String, String, Pack)}
|
||||
end.
|
||||
|
||||
|
||||
%% @spec () -> string()
|
||||
%% @doc Build the content of a Flash policy file.
|
||||
%% It specifies as domain "*".
|
||||
%% It specifies as to-ports the ports that serve ejabberd_c2s.
|
||||
flash_policy_string() ->
|
||||
Listen = ejabberd_config:get_local_option(listen),
|
||||
ClientPortsDeep = ["," ++ integer_to_list(Port)
|
||||
|| {{Port,_,_}, ejabberd_c2s, _Opts} <- Listen],
|
||||
%% NOTE: The function string:join/2 was introduced in Erlang/OTP R12B-0
|
||||
%% so it can't be used yet in ejabberd.
|
||||
ToPortsString = case lists:flatten(ClientPortsDeep) of
|
||||
[$, | Tail] -> Tail;
|
||||
_ -> []
|
||||
end,
|
||||
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<!DOCTYPE cross-domain-policy SYSTEM "
|
||||
"\"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
|
||||
"<cross-domain-policy>\n"
|
||||
" <allow-access-from domain=\"*\" to-ports=\""
|
||||
++ ToPortsString ++
|
||||
"\"/>\n"
|
||||
"</cross-domain-policy>\n\0".
|
||||
|
85
src/ejabberd_c2s.hrl
Normal file
85
src/ejabberd_c2s.hrl
Normal file
@ -0,0 +1,85 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
|
||||
-ifndef(mod_privacy_hrl).
|
||||
-include("mod_privacy.hrl").
|
||||
-endif.
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
-define(DICT, dict).
|
||||
|
||||
%% pres_a contains all the presence available send (either through roster mechanism or directed).
|
||||
%% Directed presence unavailable remove user from pres_a.
|
||||
-record(state, {socket,
|
||||
sockmod,
|
||||
socket_monitor,
|
||||
xml_socket,
|
||||
streamid,
|
||||
sasl_state,
|
||||
access,
|
||||
shaper,
|
||||
zlib = false,
|
||||
tls = false,
|
||||
tls_required = false,
|
||||
tls_enabled = false,
|
||||
tls_options = [],
|
||||
authenticated = false,
|
||||
jid,
|
||||
user = "", server = ?MYNAME, resource = "",
|
||||
sid,
|
||||
pres_t = ?SETS:new(),
|
||||
pres_f = ?SETS:new(),
|
||||
pres_a = ?SETS:new(),
|
||||
pres_i = ?SETS:new(),
|
||||
pres_last, pres_pri,
|
||||
pres_timestamp,
|
||||
pres_invis = false,
|
||||
privacy_list = #userlist{},
|
||||
conn = unknown,
|
||||
auth_module = unknown,
|
||||
ip,
|
||||
fsm_limit_opts,
|
||||
lang,
|
||||
debug=false,
|
||||
flash_connection = false,
|
||||
reception = true,
|
||||
standby = false,
|
||||
queue = queue:new(),
|
||||
queue_len = 0,
|
||||
pres_queue = gb_trees:empty(),
|
||||
keepalive_timer,
|
||||
keepalive_timeout,
|
||||
oor_timeout,
|
||||
oor_status = "",
|
||||
oor_show = "",
|
||||
oor_notification,
|
||||
oor_send_body = all,
|
||||
oor_send_groupchat = false,
|
||||
oor_send_from = jid,
|
||||
oor_appid = "",
|
||||
oor_unread = 0,
|
||||
oor_unread_users = ?SETS:new(),
|
||||
oor_offline = false,
|
||||
ack_enabled = false,
|
||||
ack_counter = 0,
|
||||
ack_queue = queue:new(),
|
||||
ack_timer}).
|
@ -35,7 +35,7 @@
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
|
||||
-export([create_captcha/5, build_captcha_html/2, check_captcha/2,
|
||||
process_reply/1, process/2, is_feature_available/0]).
|
||||
|
||||
-include("jlib.hrl").
|
||||
@ -48,19 +48,11 @@
|
||||
|
||||
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
|
||||
-define(CAPTCHA_LIFETIME, 120000). % two minutes
|
||||
-define(RPC_TIMEOUT, 5000).
|
||||
|
||||
-record(state, {}).
|
||||
-record(captcha, {id, pid, key, tref, args}).
|
||||
|
||||
-define(T(S),
|
||||
case catch mnesia:transaction(fun() -> S end) of
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{_, Reason} ->
|
||||
?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
end).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@ -71,11 +63,12 @@
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
create_captcha(Id, SID, From, To, Lang, Args)
|
||||
when is_list(Id), is_list(Lang), is_list(SID),
|
||||
create_captcha(SID, From, To, Lang, Args)
|
||||
when is_list(Lang), is_list(SID),
|
||||
is_record(From, jid), is_record(To, jid) ->
|
||||
case create_image() of
|
||||
{ok, Type, Key, Image} ->
|
||||
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
|
||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||
JID = jlib:jid_to_string(From),
|
||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
||||
@ -101,13 +94,9 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
||||
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
|
||||
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args})) of
|
||||
ok ->
|
||||
{ok, [Body, OOB, Captcha, Data]};
|
||||
_Err ->
|
||||
error
|
||||
end;
|
||||
ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
|
||||
tref=Tref, args=Args}),
|
||||
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||
_Err ->
|
||||
error
|
||||
end.
|
||||
@ -119,8 +108,8 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
||||
%% IdEl = xmlelement()
|
||||
%% KeyEl = xmlelement()
|
||||
build_captcha_html(Id, Lang) ->
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{}] ->
|
||||
case lookup_captcha(Id) of
|
||||
{ok, _} ->
|
||||
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
||||
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||
IdEl = {xmlelement, "input", [{"type", "hidden"},
|
||||
@ -150,21 +139,25 @@ build_captcha_html(Id, Lang) ->
|
||||
|
||||
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
|
||||
check_captcha(Id, ProvidedKey) ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if StoredKey == ProvidedKey ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
captcha_valid;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end).
|
||||
|
||||
case string:tokens(Id, "-") of
|
||||
[_, NodeID] ->
|
||||
case ejabberd_cluster:get_node_by_id(NodeID) of
|
||||
Node when Node == node() ->
|
||||
do_check_captcha(Id, ProvidedKey);
|
||||
Node ->
|
||||
case catch rpc:call(Node, ?MODULE, check_captcha,
|
||||
[Id, ProvidedKey], ?RPC_TIMEOUT) of
|
||||
{'EXIT', _} ->
|
||||
captcha_not_found;
|
||||
{badrpc, _} ->
|
||||
captcha_not_found;
|
||||
Res ->
|
||||
Res
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end.
|
||||
|
||||
process_reply({xmlelement, "captcha", _, _} = El) ->
|
||||
case xml:get_subtag(El, "x") of
|
||||
@ -175,20 +168,14 @@ process_reply({xmlelement, "captcha", _, _} = El) ->
|
||||
case {proplists:get_value("challenge", Fields),
|
||||
proplists:get_value("ocr", Fields)} of
|
||||
{[Id|_], [OCR|_]} ->
|
||||
?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
||||
mnesia:delete({captcha, Id}),
|
||||
erlang:cancel_timer(Tref),
|
||||
if OCR == Key ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
ok;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
{error, bad_match}
|
||||
end;
|
||||
_ ->
|
||||
{error, not_found}
|
||||
end);
|
||||
case check_captcha(Id, OCR) of
|
||||
captcha_valid ->
|
||||
ok;
|
||||
captcha_non_valid ->
|
||||
{error, bad_match};
|
||||
captcha_not_found ->
|
||||
{error, not_found}
|
||||
end;
|
||||
_ ->
|
||||
{error, malformed}
|
||||
end
|
||||
@ -209,8 +196,8 @@ process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
|
||||
end;
|
||||
|
||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
|
||||
case mnesia:dirty_read(captcha, Id) of
|
||||
[#captcha{key=Key}] ->
|
||||
case lookup_captcha(Id) of
|
||||
{ok, #captcha{key=Key}} ->
|
||||
case create_image(Key) of
|
||||
{ok, Type, _, Img} ->
|
||||
{200,
|
||||
@ -249,10 +236,8 @@ process(_Handlers, _Request) ->
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
mnesia:create_table(captcha,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, captcha)}]),
|
||||
mnesia:add_table_copy(captcha, node(), ram_copies),
|
||||
mnesia:delete_table(captcha),
|
||||
ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
|
||||
check_captcha_setup(),
|
||||
{ok, #state{}}.
|
||||
|
||||
@ -264,13 +249,13 @@ handle_cast(_Msg, State) ->
|
||||
|
||||
handle_info({remove_id, Id}, State) ->
|
||||
?DEBUG("captcha ~p timed out", [Id]),
|
||||
_ = ?T(case mnesia:read(captcha, Id, write) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
mnesia:delete({captcha, Id});
|
||||
_ ->
|
||||
ok
|
||||
end),
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{args=Args, pid=Pid}] ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
ets:delete(captcha, Id);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
@ -414,3 +399,43 @@ check_captcha_setup() ->
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
lookup_captcha(Id) ->
|
||||
case string:tokens(Id, "-") of
|
||||
[_, NodeID] ->
|
||||
case ejabberd_cluster:get_node_by_id(NodeID) of
|
||||
Node when Node == node() ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[C] ->
|
||||
{ok, C};
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end;
|
||||
Node ->
|
||||
case catch rpc:call(Node, ets, lookup,
|
||||
[captcha, Id], ?RPC_TIMEOUT) of
|
||||
[C] ->
|
||||
{ok, C};
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end.
|
||||
|
||||
do_check_captcha(Id, ProvidedKey) ->
|
||||
case ets:lookup(captcha, Id) of
|
||||
[#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
|
||||
ets:delete(captcha, Id),
|
||||
erlang:cancel_timer(Tref),
|
||||
if ValidKey == ProvidedKey ->
|
||||
Pid ! {captcha_succeed, Args},
|
||||
captcha_valid;
|
||||
true ->
|
||||
Pid ! {captcha_failed, Args},
|
||||
captcha_non_valid
|
||||
end;
|
||||
_ ->
|
||||
captcha_not_found
|
||||
end.
|
||||
|
177
src/ejabberd_cluster.erl
Normal file
177
src/ejabberd_cluster.erl
Normal file
@ -0,0 +1,177 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description :
|
||||
%%%
|
||||
%%% Created : 2 Apr 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_cluster).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, get_node/1, get_node_new/1, announce/0,
|
||||
node_id/0, get_node_by_id/1, get_nodes/0, rehash_timeout/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-define(HASHTBL, nodes_hash).
|
||||
-define(HASHTBL_NEW, nodes_hash_new).
|
||||
-define(POINTS, 64).
|
||||
-define(REHASH_TIMEOUT, 30000).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
get_node(Key) ->
|
||||
Hash = erlang:phash2(Key),
|
||||
get_node_by_hash(?HASHTBL, Hash).
|
||||
|
||||
get_node_new(Key) ->
|
||||
Hash = erlang:phash2(Key),
|
||||
get_node_by_hash(?HASHTBL_NEW, Hash).
|
||||
|
||||
get_nodes() ->
|
||||
%% TODO
|
||||
mnesia:system_info(running_db_nodes).
|
||||
|
||||
announce() ->
|
||||
gen_server:call(?MODULE, announce, infinity).
|
||||
|
||||
node_id() ->
|
||||
integer_to_list(erlang:phash2(node())).
|
||||
|
||||
rehash_timeout() ->
|
||||
?REHASH_TIMEOUT.
|
||||
|
||||
get_node_by_id(NodeID) when is_list(NodeID) ->
|
||||
case catch list_to_existing_atom(NodeID) of
|
||||
{'EXIT', _} ->
|
||||
node();
|
||||
Res ->
|
||||
get_node_by_id(Res)
|
||||
end;
|
||||
get_node_by_id(NodeID) ->
|
||||
case global:whereis_name(NodeID) of
|
||||
Pid when is_pid(Pid) ->
|
||||
node(Pid);
|
||||
_ ->
|
||||
node()
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
net_kernel:monitor_nodes(true, [{node_type, visible}]),
|
||||
ets:new(?HASHTBL, [named_table, public, ordered_set]),
|
||||
ets:new(?HASHTBL_NEW, [named_table, public, ordered_set]),
|
||||
register_node(),
|
||||
AllNodes = mnesia:system_info(running_db_nodes),
|
||||
OtherNodes = case AllNodes of
|
||||
[_] ->
|
||||
AllNodes;
|
||||
_ ->
|
||||
AllNodes -- [node()]
|
||||
end,
|
||||
append_nodes(?HASHTBL, OtherNodes),
|
||||
append_nodes(?HASHTBL_NEW, AllNodes),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(announce, _From, State) ->
|
||||
case mnesia:system_info(running_db_nodes) of
|
||||
[_MyNode] ->
|
||||
ok;
|
||||
Nodes ->
|
||||
OtherNodes = Nodes -- [node()],
|
||||
lists:foreach(
|
||||
fun(Node) ->
|
||||
{?MODULE, Node} ! {node_ready, node()}
|
||||
end, OtherNodes),
|
||||
?INFO_MSG("waiting for migration from nodes: ~w",
|
||||
[OtherNodes]),
|
||||
timer:sleep(?REHASH_TIMEOUT),
|
||||
append_node(?HASHTBL, node())
|
||||
end,
|
||||
{reply, ok, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({node_ready, Node}, State) ->
|
||||
?INFO_MSG("node ~p is ready, starting migration", [Node]),
|
||||
append_node(?HASHTBL_NEW, Node),
|
||||
ejabberd_hooks:run(node_hash_update, [?REHASH_TIMEOUT]),
|
||||
timer:sleep(?REHASH_TIMEOUT),
|
||||
?INFO_MSG("adding node ~p to hash", [Node]),
|
||||
append_node(?HASHTBL, Node),
|
||||
{noreply, State};
|
||||
handle_info({nodedown, Node, _}, State) ->
|
||||
?INFO_MSG("node ~p goes down", [Node]),
|
||||
delete_node(?HASHTBL, Node),
|
||||
delete_node(?HASHTBL_NEW, Node),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
append_nodes(Tab, Nodes) ->
|
||||
lists:foreach(
|
||||
fun(Node) ->
|
||||
append_node(Tab, Node)
|
||||
end, Nodes).
|
||||
|
||||
append_node(Tab, Node) ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
Hash = erlang:phash2({I, Node}),
|
||||
ets:insert(Tab, {Hash, Node})
|
||||
end, lists:seq(1, ?POINTS)).
|
||||
|
||||
delete_node(Tab, Node) ->
|
||||
lists:foreach(
|
||||
fun(I) ->
|
||||
Hash = erlang:phash2({I, Node}),
|
||||
ets:delete(Tab, Hash)
|
||||
end, lists:seq(1, ?POINTS)).
|
||||
|
||||
get_node_by_hash(Tab, Hash) ->
|
||||
NodeHash = case ets:next(Tab, Hash) of
|
||||
'$end_of_table' ->
|
||||
ets:first(Tab);
|
||||
NH ->
|
||||
NH
|
||||
end,
|
||||
if NodeHash == '$end_of_table' ->
|
||||
erlang:error(no_running_nodes);
|
||||
true ->
|
||||
case ets:lookup(Tab, NodeHash) of
|
||||
[] ->
|
||||
get_node_by_hash(Tab, Hash);
|
||||
[{_, Node}] ->
|
||||
Node
|
||||
end
|
||||
end.
|
||||
|
||||
register_node() ->
|
||||
global:register_name(list_to_atom(node_id()), self()).
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
|
@ -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}.
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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]}}.
|
||||
|
||||
|
||||
|
@ -136,6 +136,7 @@ export EXEC_CMD
|
||||
# start server
|
||||
start ()
|
||||
{
|
||||
check_start
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
-noinput -detached \
|
||||
@ -182,6 +183,7 @@ debug ()
|
||||
# start interactive server
|
||||
live ()
|
||||
{
|
||||
check_start
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
|
||||
@ -210,6 +212,13 @@ live ()
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
|
||||
etop()
|
||||
{
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME debug-${TTY}-${ERLANG_NODE} \
|
||||
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
|
||||
}
|
||||
|
||||
help ()
|
||||
{
|
||||
echo ""
|
||||
@ -330,6 +339,26 @@ stop_epmd()
|
||||
epmd -names | grep -q name || epmd -kill
|
||||
}
|
||||
|
||||
# make sure node not already running and node name unregistered
|
||||
check_start()
|
||||
{
|
||||
epmd -names | grep -q $NODE && {
|
||||
ps ux | grep -q $ERLANG_NODE && {
|
||||
echo "ejabberd is already running."
|
||||
exit 4
|
||||
} || {
|
||||
ps ux | grep beam | grep -v "grep beam" && {
|
||||
echo "ejabberd node is registered, but no ejabberd process has been found."
|
||||
echo "can not kill epmd as other erlang nodes are running."
|
||||
echo "please stop all erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
} || {
|
||||
epmd -kill
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# allow sync calls
|
||||
wait_for_status()
|
||||
{
|
||||
@ -359,6 +388,7 @@ case $ARGS in
|
||||
' start') start;;
|
||||
' debug') debug;;
|
||||
' live') live;;
|
||||
' etop') etop;;
|
||||
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||
' stopped') wait_for_status 3 15 2; stop_epmd;; # wait 15x2s before timeout
|
||||
*) ctl $ARGS;;
|
||||
|
29
src/etop_defs.hrl
Normal file
29
src/etop_defs.hrl
Normal file
@ -0,0 +1,29 @@
|
||||
%%
|
||||
%% %CopyrightBegin%
|
||||
%%
|
||||
%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
|
||||
%%
|
||||
%% The contents of this file are subject to the Erlang Public License,
|
||||
%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You should have received a copy of the
|
||||
%% Erlang Public License along with this software. If not, it can be
|
||||
%% retrieved online at http://www.erlang.org/.
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and limitations
|
||||
%% under the License.
|
||||
%%
|
||||
%% %CopyrightEnd%
|
||||
%%
|
||||
-define(SYSFORM,
|
||||
" ~-72w~10s~n"
|
||||
" Load: cpu ~8w Memory: total ~8w binary ~8w~n"
|
||||
" procs~8w processes~8w code ~8w~n"
|
||||
" runq ~8w atom ~8w ets ~8w~n").
|
||||
|
||||
-record(opts, {node=node(), port = 8415, accum = false, intv = 5000, lines = 10,
|
||||
width = 700, height = 340, sort = runtime, tracing = on,
|
||||
%% Other state information
|
||||
out_mod=etop_gui, out_proc, server, host, tracer, store,
|
||||
accum_tab, remote}).
|
130
src/etop_tr.erl
Normal file
130
src/etop_tr.erl
Normal file
@ -0,0 +1,130 @@
|
||||
%%
|
||||
%% %CopyrightBegin%
|
||||
%%
|
||||
%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
|
||||
%%
|
||||
%% The contents of this file are subject to the Erlang Public License,
|
||||
%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You should have received a copy of the
|
||||
%% Erlang Public License along with this software. If not, it can be
|
||||
%% retrieved online at http://www.erlang.org/.
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and limitations
|
||||
%% under the License.
|
||||
%%
|
||||
%% %CopyrightEnd%
|
||||
%%
|
||||
-module(etop_tr).
|
||||
-author('siri@erix.ericsson.se').
|
||||
|
||||
%%-compile(export_all).
|
||||
-export([setup_tracer/1,stop_tracer/1,reader/1]).
|
||||
-import(etop,[getopt/2]).
|
||||
|
||||
-include("etop_defs.hrl").
|
||||
|
||||
setup_tracer(Config) ->
|
||||
TraceNode = getopt(node,Config),
|
||||
RHost = rpc:call(TraceNode, net_adm, localhost, []),
|
||||
Store = ets:new(?MODULE, [set, public]),
|
||||
|
||||
%% We can only trace one process anyway kill the old one.
|
||||
case erlang:whereis(dbg) of
|
||||
undefined ->
|
||||
case rpc:call(TraceNode, erlang, whereis, [dbg]) of
|
||||
undefined -> fine;
|
||||
Pid ->
|
||||
exit(Pid, kill)
|
||||
end;
|
||||
Pid ->
|
||||
exit(Pid,kill)
|
||||
end,
|
||||
|
||||
dbg:tracer(TraceNode,port,dbg:trace_port(ip,{getopt(port,Config),5000})),
|
||||
dbg:p(all,[running,timestamp]),
|
||||
T = dbg:get_tracer(TraceNode),
|
||||
Config#opts{tracer=T,host=RHost,store=Store}.
|
||||
|
||||
stop_tracer(_Config) ->
|
||||
dbg:p(all,clear),
|
||||
dbg:stop(),
|
||||
ok.
|
||||
|
||||
|
||||
|
||||
reader(Config) ->
|
||||
Host = getopt(host, Config),
|
||||
Port = getopt(port, Config),
|
||||
|
||||
{ok, Sock} = gen_tcp:connect(Host, Port, [{active, false}]),
|
||||
spawn_link(fun() -> reader_init(Sock,getopt(store,Config),nopid) end).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%% Socket reader %%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
reader_init(Sock, Store, Last) ->
|
||||
process_flag(priority, high),
|
||||
reader(Sock, Store, Last).
|
||||
|
||||
reader(Sock, Store, Last) ->
|
||||
Data = get_data(Sock),
|
||||
New = handle_data(Last, Data, Store),
|
||||
reader(Sock, Store, New).
|
||||
|
||||
handle_data(_, {_, Pid, in, _, Time}, _) ->
|
||||
{Pid,Time};
|
||||
handle_data({Pid,Time1}, {_, Pid, out, _, Time2}, Store) ->
|
||||
Elapsed = elapsed(Time1, Time2),
|
||||
case ets:member(Store,Pid) of
|
||||
true -> ets:update_counter(Store, Pid, Elapsed);
|
||||
false -> ets:insert(Store,{Pid,Elapsed})
|
||||
end,
|
||||
nopid;
|
||||
handle_data(_W, {drop, D}, _) -> %% Error case we are missing data here!
|
||||
io:format("Erlang top dropped data ~p~n", [D]),
|
||||
nopid;
|
||||
handle_data(nopid, {_, _, out, _, _}, _Store) ->
|
||||
%% ignore - there was probably just a 'drop'
|
||||
nopid;
|
||||
handle_data(_, G, _) ->
|
||||
%% io:format("Erlang top got garbage ~p~n", [G]),
|
||||
nopid.
|
||||
|
||||
elapsed({Me1, S1, Mi1}, {Me2, S2, Mi2}) ->
|
||||
Me = (Me2 - Me1) * 1000000,
|
||||
S = (S2 - S1 + Me) * 1000000,
|
||||
Mi2 - Mi1 + S.
|
||||
|
||||
|
||||
%%%%%% Socket helpers %%%%
|
||||
get_data(Sock) ->
|
||||
[Op | BESiz] = my_ip_read(Sock, 5),
|
||||
Siz = get_be(BESiz),
|
||||
case Op of
|
||||
0 ->
|
||||
B = list_to_binary(my_ip_read(Sock, Siz)),
|
||||
binary_to_term(B);
|
||||
1 ->
|
||||
{drop, Siz};
|
||||
Else ->
|
||||
exit({'bad trace tag', Else})
|
||||
end.
|
||||
|
||||
get_be([A,B,C,D]) ->
|
||||
A * 16777216 + B * 65536 + C * 256 + D.
|
||||
|
||||
my_ip_read(Sock,N) ->
|
||||
case gen_tcp:recv(Sock, N) of
|
||||
{ok, Data} ->
|
||||
case length(Data) of
|
||||
N ->
|
||||
Data;
|
||||
X ->
|
||||
Data ++ my_ip_read(Sock, N - X)
|
||||
end;
|
||||
_Else ->
|
||||
exit(eof)
|
||||
end.
|
||||
|
@ -138,6 +138,35 @@ static int expat_erl_control(ErlDrvData drv_data,
|
||||
case PARSE_COMMAND:
|
||||
case PARSE_FINAL_COMMAND:
|
||||
ei_x_new_with_version(&event_buf);
|
||||
#ifdef ENABLE_FLASH_HACK
|
||||
/* Flash hack - Flash clients send a null byte after the stanza. Remove that... */
|
||||
{
|
||||
int i;
|
||||
int found_null = 0;
|
||||
|
||||
/* Maybe the Flash client sent many stanzas in one packet.
|
||||
If so, there is a null byte between every stanza. */
|
||||
for (i = 0; i < len; i++) {
|
||||
if (buf[i] == '\0') {
|
||||
buf[i] = ' ';
|
||||
found_null = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* And also remove the closing slash if this is a
|
||||
flash:stream element. Assume that flash:stream is the
|
||||
last element in the packet, and entirely contained in
|
||||
it. This requires that a null byte has been found. */
|
||||
if (found_null && strstr(buf, "<flash:stream"))
|
||||
/* buf[len - 1] is an erased null byte.
|
||||
buf[len - 2] is >
|
||||
buf[len - 3] is / (maybe)
|
||||
*/
|
||||
if (buf[len - 3] == '/')
|
||||
buf[len - 3] = ' ';
|
||||
}
|
||||
#endif /* ENABLE_FLASH_HACK */
|
||||
|
||||
res = XML_Parse(d->parser, buf, len, command == PARSE_FINAL_COMMAND);
|
||||
|
||||
if(!res)
|
||||
|
205
src/floodcheck.erl
Normal file
205
src/floodcheck.erl
Normal file
@ -0,0 +1,205 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : floodcheck.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Description :
|
||||
%%%
|
||||
%%% Created : 11 Sep 2008 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(floodcheck).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, stop/0]).
|
||||
-export([monitor/5, demonitor/1, interval/1, check/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-define(DEFAULT_INTERVAL, 300). %% check every 5mn
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-record(state, {timer, interval, monitors}).
|
||||
-record(monitor, {id, pid, ref, info, rule, value, handler}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
case whereis(?SERVER) of
|
||||
undefined -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []);
|
||||
Pid -> {ok, Pid}
|
||||
end.
|
||||
|
||||
stop() ->
|
||||
gen_server:call(?SERVER, stop).
|
||||
|
||||
monitor(Id, Pid, Info, Spec, {Mod, Fun}) ->
|
||||
gen_server:cast(?SERVER, {monitor, Id, Pid, Info, Spec, {Mod, Fun}}).
|
||||
|
||||
demonitor(Id) ->
|
||||
gen_server:cast(?SERVER, {demonitor, Id}).
|
||||
|
||||
interval(Value) ->
|
||||
gen_server:cast(?SERVER, {interval, Value}).
|
||||
|
||||
check() ->
|
||||
gen_server:call(?SERVER, check).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
Timer = erlang:send_after(?DEFAULT_INTERVAL*1000, ?SERVER, monitor),
|
||||
{ok, #state{timer=Timer, interval=?DEFAULT_INTERVAL, monitors=[]}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(check, _From, State) ->
|
||||
Reply = lists:map(fun(#monitor{id=Id}=M) ->
|
||||
{Id, check(M)}
|
||||
end, State#state.monitors),
|
||||
{reply, Reply, State};
|
||||
handle_call(stop, _From, State) ->
|
||||
erlang:cancel_timer(State#state.timer),
|
||||
{stop, normal, ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({monitor, Id, Pid, Info, Spec, Handler}, State) ->
|
||||
Monitors = State#state.monitors,
|
||||
Ref = erlang:monitor(process, Pid),
|
||||
{Rule, Value} = case Spec of
|
||||
{Op, V} -> {Op, V};
|
||||
V -> {'>', V}
|
||||
end,
|
||||
Monitor = #monitor{id=Id, pid=Pid, ref=Ref, info=Info, rule=Rule, value=Value, handler=Handler},
|
||||
New = case lists:keysearch(Id, #monitor.id, Monitors) of
|
||||
{value, #monitor{ref=OldRef}} ->
|
||||
erlang:demonitor(OldRef),
|
||||
lists:keyreplace(Id, #monitor.id, Monitors, Monitor);
|
||||
_ ->
|
||||
[Monitor|Monitors]
|
||||
end,
|
||||
{noreply, State#state{monitors=New}};
|
||||
handle_cast({demonitor, Id}, State) ->
|
||||
Monitors = State#state.monitors,
|
||||
New = case lists:keysearch(Id, #monitor.id, Monitors) of
|
||||
{value, #monitor{ref=Ref}} ->
|
||||
erlang:demonitor(Ref),
|
||||
lists:keydelete(Id, #monitor.id, Monitors);
|
||||
_ ->
|
||||
Monitors
|
||||
end,
|
||||
{noreply, State#state{monitors=New}};
|
||||
handle_cast({interval, Value}, State) ->
|
||||
erlang:cancel_timer(State#state.timer),
|
||||
Timer = erlang:send_after(Value*1000, ?SERVER, monitor),
|
||||
{noreply, State#state{timer=Timer, interval=Value}};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({'DOWN', Ref, _Type, _Pid, _Info}, State) ->
|
||||
Monitors = State#state.monitors,
|
||||
New = lists:keydelete(Ref, #monitor.ref, Monitors),
|
||||
{noreply, State#state{monitors=New}};
|
||||
handle_info(monitor, State) ->
|
||||
lists:foreach(fun(#monitor{id=Id, pid=Pid, info=Info, handler={Mod, Fun}}=M) ->
|
||||
case check(M) of
|
||||
ok -> ok;
|
||||
Value -> spawn(Mod, Fun, [Id, Pid, Info, Value])
|
||||
end
|
||||
end, State#state.monitors),
|
||||
Timer = erlang:send_after(State#state.interval*1000, ?SERVER, monitor),
|
||||
{noreply, State#state{timer=Timer}};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
check(#monitor{pid=Pid, info=Info, rule=Rule, value=Value}) ->
|
||||
case catch process_info(Pid, Info) of
|
||||
{Info, Actual} ->
|
||||
Check = case Info of
|
||||
messages -> byte_size(term_to_binary(Actual));
|
||||
dictionary -> byte_size(term_to_binary(Actual));
|
||||
_ -> Actual
|
||||
end,
|
||||
case Rule of
|
||||
'>' ->
|
||||
if Check > Value -> Value;
|
||||
true -> ok
|
||||
end;
|
||||
'<' ->
|
||||
if Check < Value -> Value;
|
||||
true -> ok
|
||||
end;
|
||||
'=' ->
|
||||
if Check == Value -> Value;
|
||||
true -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%%% Documentation
|
||||
%%% authorized Info
|
||||
%%% message_queue_len: number of messages
|
||||
%%% messages: messages queue size in bytes
|
||||
%%% dictionary: dictionary size in bytes
|
||||
%%% total_heap_size: total size in words of all heap fragments
|
||||
%%% heap_size: size in words of youngest heap generation
|
||||
%%% stack_size: stack size in words
|
||||
%%% reductions: number of reductions executed by the process
|
||||
%%% memory: process size in bytes
|
337
src/http_p1.erl
Normal file
337
src/http_p1.erl
Normal file
@ -0,0 +1,337 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : http_p1.erl
|
||||
%%% Author : Emilio Bustos <ebustos@process-one.net>
|
||||
%%% Purpose : Provide a common API for inets / lhttpc / ibrowse
|
||||
%%% Created : 29 Jul 2010 by Emilio Bustos <ebustos@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(http_p1).
|
||||
-author('ebustos@process-one.net').
|
||||
|
||||
-export([
|
||||
start/0,
|
||||
stop/0,
|
||||
get/1,
|
||||
get/2,
|
||||
post/2,
|
||||
post/3,
|
||||
request/3,
|
||||
request/4,
|
||||
request/5
|
||||
]).
|
||||
|
||||
% -define(USE_INETS, 1).
|
||||
% -define(USE_LHTTPC, 1).
|
||||
% -define(USE_IBROWSE, 1).
|
||||
% inets used as default if none specified
|
||||
|
||||
-ifdef(USE_IBROWSE).
|
||||
-define(start(), start_ibrowse()).
|
||||
-define(request(M, U, H, B, O), request_ibrowse(M, U, H, B, O)).
|
||||
-define(stop(), stop_ibrowse()).
|
||||
-else.
|
||||
-ifdef(USE_LHTTPC).
|
||||
-define(start(), start_lhttpc()).
|
||||
-define(request(M, U, H, B, O), request_lhttpc(M, U, H, B, O)).
|
||||
-define(stop(), stop_lhttpc()).
|
||||
-else.
|
||||
-define(start(), start_inets()).
|
||||
-define(request(M, U, H, B, O), request_inets(M, U, H, B, O)).
|
||||
-define(stop(), stop_inets()).
|
||||
-endif.
|
||||
-endif.
|
||||
|
||||
-type header() :: {string() | atom(), string()}.
|
||||
-type headers() :: [header()].
|
||||
|
||||
-type option() ::
|
||||
{connect_timeout, timeout()} |
|
||||
{timeout, timeout()} |
|
||||
|
||||
{send_retry, non_neg_integer()} |
|
||||
{partial_upload, non_neg_integer() | infinity} |
|
||||
{partial_download, pid(), non_neg_integer() | infinity}.
|
||||
|
||||
-type options() :: [option()].
|
||||
|
||||
-type result() :: {ok, {{pos_integer(), string()}, headers(), string()}} |
|
||||
{error, atom()}.
|
||||
|
||||
%% @spec () -> ok | {error, Reason}
|
||||
%% Reason = term()
|
||||
%% @doc
|
||||
%% Start the application.
|
||||
%% This is a helper function that will start the corresponding backend.
|
||||
%% It allows the library to be started using the `-s' flag.
|
||||
%% For instance:
|
||||
%% `$ erl -s http_p1'
|
||||
%%
|
||||
%% @end
|
||||
-spec start() -> ok | {error, any()}.
|
||||
start() ->
|
||||
?start().
|
||||
|
||||
start_inets()->
|
||||
inets:start(),
|
||||
ssl:start().
|
||||
|
||||
start_lhttpc()->
|
||||
application:start(crypto),
|
||||
application:start(ssl),
|
||||
lhttpc:start().
|
||||
|
||||
start_ibrowse()->
|
||||
ibrowse:start(),
|
||||
ssl:start().
|
||||
|
||||
%% @spec () -> ok | {error, Reason}
|
||||
%% Reason = term()
|
||||
%% @doc
|
||||
%% Stops the application.
|
||||
%% This is a helper function that will stop the corresponding backend.
|
||||
%%
|
||||
%% @end
|
||||
-spec stop() -> ok | {error, any()}.
|
||||
stop() ->
|
||||
?stop().
|
||||
|
||||
stop_inets()->
|
||||
inets:stop(),
|
||||
ssl:stop().
|
||||
|
||||
stop_lhttpc()->
|
||||
lhttpc:stop(),
|
||||
application:stop(ssl).
|
||||
|
||||
stop_ibrowse()->
|
||||
ibrowse:stop().
|
||||
|
||||
%% @spec (URL) -> Result
|
||||
%% URL = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a GET request.
|
||||
%% Would be the same as calling `request(get, URL, [])',
|
||||
%% that is {@link request/3} with an empty header list.
|
||||
%% @end
|
||||
%% @see request/3
|
||||
-spec get(string()) -> result().
|
||||
get(URL) ->
|
||||
request(get, URL, []).
|
||||
|
||||
%% @spec (URL, Hdrs) -> Result
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a GET request.
|
||||
%% Would be the same as calling `request(get, URL, Hdrs)'.
|
||||
%% @end
|
||||
%% @see request/3
|
||||
-spec get(string(), headers()) -> result().
|
||||
get(URL, Hdrs) ->
|
||||
request(get, URL, Hdrs).
|
||||
|
||||
%% @spec (URL, RequestBody) -> Result
|
||||
%% URL = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a POST request with form data.
|
||||
%% Would be the same as calling
|
||||
%% `request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body)'.
|
||||
%% @end
|
||||
%% @see request/4
|
||||
-spec post(string(), string()) -> result().
|
||||
post(URL, Body) ->
|
||||
request(post, URL, [{"content-type", "x-www-form-urlencoded"}], Body).
|
||||
|
||||
%% @spec (URL, Hdrs, RequestBody) -> Result
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a POST request.
|
||||
%% Would be the same as calling
|
||||
%% `request(post, URL, Hdrs, Body)'.
|
||||
%% @end
|
||||
%% @see request/4
|
||||
-spec post(string(), headers(), string()) -> result().
|
||||
post(URL, Hdrs, Body) ->
|
||||
NewHdrs = case [X || {X,_}<-Hdrs, string:to_lower(X) == "content-type"] of
|
||||
[] ->
|
||||
[{"content-type", "x-www-form-urlencoded"} | Hdrs];
|
||||
_ ->
|
||||
Hdrs
|
||||
end,
|
||||
request(post, URL, NewHdrs, Body).
|
||||
|
||||
%% @spec (Method, URL, Hdrs) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request without a body.
|
||||
%% Would be the same as calling `request(Method, URL, Hdrs, [], [])',
|
||||
%% that is {@link request/5} with an empty body.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers()) -> result().
|
||||
request(Method, URL, Hdrs) ->
|
||||
request(Method, URL, Hdrs, [], []).
|
||||
|
||||
%% @spec (Method, URL, Hdrs, RequestBody) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request with a body.
|
||||
%% Would be the same as calling
|
||||
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||
%% with no options.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers(), string()) -> result().
|
||||
request(Method, URL, Hdrs, Body) ->
|
||||
request(Method, URL, Hdrs, Body, []).
|
||||
|
||||
%% @spec (Method, URL, Hdrs, RequestBody, Options) -> Result
|
||||
%% Method = atom()
|
||||
%% URL = string()
|
||||
%% Hdrs = [{Header, Value}]
|
||||
%% Header = string()
|
||||
%% Value = string()
|
||||
%% RequestBody = string()
|
||||
%% Options = [Option]
|
||||
%% Option = {timeout, Milliseconds | infinity} |
|
||||
%% {connect_timeout, Milliseconds | infinity} |
|
||||
%% {socket_options, [term()]} |
|
||||
|
||||
%% Milliseconds = integer()
|
||||
%% Result = {ok, StatusCode, Hdrs, ResponseBody}
|
||||
%% | {error, Reason}
|
||||
%% StatusCode = integer()
|
||||
%% ResponseBody = string()
|
||||
%% Reason = connection_closed | connect_timeout | timeout
|
||||
%% @doc Sends a request with a body.
|
||||
%% Would be the same as calling
|
||||
%% `request(Method, URL, Hdrs, Body, [])', that is {@link request/5}
|
||||
%% with no options.
|
||||
%% @end
|
||||
%% @see request/5
|
||||
-spec request(atom(), string(), headers(), string(), options()) -> result().
|
||||
request(Method, URL, Hdrs, Body, Opts) ->
|
||||
% ?DEBUG("Making request with headers: ~p~n~n", [Hdrs]),
|
||||
% Headers = lists:map(fun({H, V}) ->
|
||||
% H2 = if
|
||||
% is_atom(H) ->
|
||||
% string:to_lower(atom_to_list(H));
|
||||
% is_list(H) ->
|
||||
% string:to_lower(H);
|
||||
% true ->
|
||||
% H
|
||||
% end,
|
||||
% {H2, V}
|
||||
% end, Hdrs),
|
||||
?request(Method, URL, Hdrs, Body, Opts).
|
||||
|
||||
request_inets(Method, URL, Hdrs, Body, Opts) ->
|
||||
Request = case Method of
|
||||
get ->
|
||||
{URL, Hdrs};
|
||||
head ->
|
||||
{URL, Hdrs};
|
||||
_ -> % post, etc.
|
||||
{URL, Hdrs, proplists:get_value("content-type", Hdrs, []), Body}
|
||||
end,
|
||||
Options = case proplists:get_value(timeout, Opts, infinity) of
|
||||
infinity ->
|
||||
proplists:delete(timeout, Opts);
|
||||
_ ->
|
||||
Opts
|
||||
end,
|
||||
case http:request(Method, Request, Options, []) of
|
||||
{ok, {{_, Status, _}, Headers, Response}} ->
|
||||
{ok, Status, Headers, Response};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
request_lhttpc(Method, URL, Hdrs, Body, Opts) ->
|
||||
TimeOut = proplists:get_value(timeout, Opts, infinity),
|
||||
SockOpt = proplists:get_value(socket_options, Opts, []),
|
||||
Options = [{connect_options, SockOpt} | proplists:delete(timeout, Opts)],
|
||||
case lhttpc:request(URL, Method, Hdrs, Body, TimeOut, Options) of
|
||||
{ok, {{Status, _Reason}, Headers, Response}} ->
|
||||
{ok, Status, Headers, binary_to_list(Response)};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
request_ibrowse(Method, URL, Hdrs, Body, Opts) ->
|
||||
TimeOut = proplists:get_value(timeout, Opts, infinity),
|
||||
Options = [{inactivity_timeout, TimeOut} | proplists:delete(timeout, Opts)],
|
||||
case ibrowse:send_req(URL, Hdrs, Method, Body, Options) of
|
||||
{ok, Status, Headers, Response} ->
|
||||
{ok, list_to_integer(Status), Headers, Response};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
% ibrowse {response_format, response_format()} |
|
||||
% Options - [option()]
|
||||
% Option - {sync, boolean()} | {stream, StreamTo} | {body_format, body_format()} | {full_result,
|
||||
% boolean()} | {headers_as_is, boolean()}
|
||||
%body_format() = string() | binary()
|
||||
% The body_format option is only valid for the synchronous request and the default is string.
|
||||
% When making an asynchronous request the body will always be received as a binary.
|
||||
% lhttpc: always binary
|
@ -67,6 +67,7 @@
|
||||
-define(NS_EJABBERD_CONFIG, "ejabberd:config").
|
||||
|
||||
-define(NS_STREAM, "http://etherx.jabber.org/streams").
|
||||
-define(NS_FLASH_STREAM, "http://www.jabber.com/streams/flash").
|
||||
|
||||
-define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas").
|
||||
-define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
|
||||
|
1
src/licence.hrl
Normal file
1
src/licence.hrl
Normal file
@ -0,0 +1 @@
|
||||
-define(IS_VALID, true).
|
1166
src/mod_admin_p1.erl
Normal file
1166
src/mod_admin_p1.erl
Normal file
File diff suppressed because it is too large
Load Diff
186
src/mod_antiflood.erl
Normal file
186
src/mod_antiflood.erl
Normal file
@ -0,0 +1,186 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_antiflood.erl
|
||||
%%% Author : Christophe Romain <cromain@process-one.net>
|
||||
%%% Description :
|
||||
%%% Created : 12 Sep 2008 by Christophe Romain <cromain@process-one.net>
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_antiflood).
|
||||
-author('cromain@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start/2, stop/1]).
|
||||
-export([handler/4]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start(Host, Opts) ->
|
||||
floodcheck:start_link(),
|
||||
lists:foreach(fun
|
||||
({listeners, Info, Op, Val}) ->
|
||||
lists:foreach(fun({Name, Pid}) ->
|
||||
Id = {Name, Info},
|
||||
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler})
|
||||
end, supervised_processes(listeners));
|
||||
({Module, Info, Op, Val}) ->
|
||||
case module_pid(Host, Module) of
|
||||
Pid when is_pid(Pid) ->
|
||||
Id = {Host, Module, Info},
|
||||
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
|
||||
Error ->
|
||||
?INFO_MSG("can not monitor ~s (~p)", [Module, Error])
|
||||
end;
|
||||
(Arg) ->
|
||||
?INFO_MSG("invalid argument: ~p", [Arg])
|
||||
end, Opts).
|
||||
|
||||
stop(Host) ->
|
||||
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||
case lists:keysearch(?MODULE, 1, MList) of
|
||||
{value, {?MODULE, Opts}} ->
|
||||
lists:foreach(fun
|
||||
({Type, Info, _, _}) ->
|
||||
case supervised_processes(Type) of
|
||||
[] ->
|
||||
Id = {Host, Type, Info},
|
||||
floodcheck:demonitor(Id);
|
||||
Childs ->
|
||||
lists:foreach(fun({Name, _}) ->
|
||||
Id = {Name, Info},
|
||||
floodcheck:demonitor(Id)
|
||||
end, Childs)
|
||||
end;
|
||||
(_) ->
|
||||
ok
|
||||
end, Opts);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
case floodcheck:check() of
|
||||
[] -> floodcheck:stop(), ok;
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
handler({Host, Module, Info}, Pid, Info, Value) ->
|
||||
?WARNING_MSG("Flood alert on Process ~p (~s on ~s): ~s=~p", [Pid, Module, Host, Info, Value]),
|
||||
restart_module(Host, Module),
|
||||
remonitor({Host, Module, Info});
|
||||
handler({Name, Info}, Pid, Info, Value) ->
|
||||
?WARNING_MSG("Flood alert on Process ~p (~s): ~s=~p", [Pid, Name, Info, Value]),
|
||||
kill_process(Name, Pid),
|
||||
remonitor({Name, Info});
|
||||
handler(Id, Pid, Info, Value) ->
|
||||
?WARNING_MSG("Flood alert on Process ~p (~s): ~s=~p~nUnknown id, alert ignored", [Pid, Id, Info, Value]).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
|
||||
process_pid(Name) -> whereis(Name).
|
||||
server_pid(Host, Name) -> process_pid(gen_mod:get_module_proc(Host, Name)).
|
||||
|
||||
module_pid(Host, mod_caps) -> server_pid(Host, ejabberd_mod_caps);
|
||||
module_pid(Host, mod_ip_blacklist) -> server_pid(Host, mod_ip_blacklist);
|
||||
module_pid(Host, mod_offline) -> server_pid(Host, ejabberd_offline);
|
||||
module_pid(Host, mod_offline_odbc) -> server_pid(Host, ejabberd_offline);
|
||||
module_pid(Host, mod_vcard) -> server_pid(Host, ejabberd_mod_vcard);
|
||||
module_pid(Host, mod_vcard_odbc) -> server_pid(Host, ejabberd_mod_vcard);
|
||||
module_pid(Host, mod_vcard_ldap) -> server_pid(Host, ejabberd_mod_vcard_ldap);
|
||||
module_pid(Host, mod_irc) -> server_pid(Host, ejabberd_mod_irc);
|
||||
module_pid(Host, mod_muc) -> server_pid(Host, ejabberd_mod_muc);
|
||||
module_pid(Host, mod_muc_log) -> server_pid(Host, ejabberd_mod_muc_log);
|
||||
module_pid(Host, mod_proxy65) -> server_pid(Host, ejabberd_mod_proxy65);
|
||||
module_pid(Host, mod_proxy65_service) -> server_pid(Host, ejabberd_mod_proxy65_service);
|
||||
module_pid(Host, mod_proxy65_sm) -> server_pid(Host, ejabberd_mod_proxy65_sm);
|
||||
module_pid(Host, mod_pubsub) -> server_pid(Host, ejabberd_mod_pubsub);
|
||||
module_pid(_, _) -> unsupported.
|
||||
|
||||
supervised_processes(listeners) ->
|
||||
{links, Links} = process_info(whereis(ejabberd_listeners), links),
|
||||
lists:map(fun(Pid) ->
|
||||
{dictionary, Dict} = process_info(Pid, dictionary),
|
||||
{_, _, [Port|_]} = proplists:get_value('$initial_call', Dict),
|
||||
Name = list_to_atom("listener_"++integer_to_list(Port)),
|
||||
{Name, Pid}
|
||||
end, Links);
|
||||
supervised_processes(_) -> [].
|
||||
|
||||
remonitor({Host, Module, Info}) ->
|
||||
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||
case lists:keysearch(?MODULE, 1, MList) of
|
||||
{value, {?MODULE, Opts}} ->
|
||||
lists:foreach(fun
|
||||
({M, I, Op, Val}) when M =:= Module, I =:= Info ->
|
||||
case module_pid(Host, Module) of
|
||||
Pid when is_pid(Pid) ->
|
||||
Id = {Host, Module, Info},
|
||||
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
|
||||
Error ->
|
||||
?INFO_MSG("can not monitor ~s (~p)", [Module, Error])
|
||||
end;
|
||||
(_) ->
|
||||
ok
|
||||
end, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
remonitor({Name, Info}) ->
|
||||
[Host|_] = ejabberd_config:get_global_option(hosts),
|
||||
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||
case lists:keysearch(?MODULE, 1, MList) of
|
||||
{value, {?MODULE, Opts}} ->
|
||||
lists:foreach(fun
|
||||
({Type, I, Op, Val}) when I =:= Info ->
|
||||
lists:foreach(fun
|
||||
({N, Pid}) when N =:= Name ->
|
||||
Id = {Name, Info},
|
||||
floodcheck:monitor(Id, Pid, Info, {Op, Val}, {?MODULE, handler});
|
||||
(_) ->
|
||||
ok
|
||||
end, supervised_processes(Type));
|
||||
(_) ->
|
||||
ok
|
||||
end, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
remonitor(Id) ->
|
||||
?INFO_MSG("can not monitor ~s", [Id]).
|
||||
|
||||
restart_module(Host, Module) ->
|
||||
MList = gen_mod:loaded_modules_with_opts(Host),
|
||||
case lists:keysearch(Module, 1, MList) of
|
||||
{value, {Module, Opts}} ->
|
||||
?WARNING_MSG("restarting module ~s on ~s", [Module, Host]),
|
||||
gen_mod:stop_module(Host, Module),
|
||||
gen_mod:start_module(Host, Module, Opts);
|
||||
_ ->
|
||||
not_running
|
||||
end.
|
||||
|
||||
kill_process(Name, Pid) ->
|
||||
?WARNING_MSG("killing process ~s(~p)", [Name, Pid]),
|
||||
exit(Pid, kill).
|
127
src/mod_autofilter.erl
Normal file
127
src/mod_autofilter.erl
Normal file
@ -0,0 +1,127 @@
|
||||
%%% ====================================================================
|
||||
%%% This software is copyright 2006-2010, ProcessOne.
|
||||
%%%
|
||||
%%% mod_autofilter
|
||||
%%%
|
||||
%%% @copyright 2006-2010 ProcessOne
|
||||
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% [http://www.process-one.net/]
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
|
||||
-module(mod_autofilter).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
% module functions
|
||||
-export([start/2,stop/1,is_loaded/0]).
|
||||
-export([offline_message/3,filter_packet/1,close_session/2,close_session/3]).
|
||||
-export([deny/2,allow/2,denied/0,listed/0,purge/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("licence.hrl").
|
||||
|
||||
-record(autofilter, {key, timestamp=0, count=1, drop=false, reason}).
|
||||
|
||||
start(Host, Opts) ->
|
||||
case ?IS_VALID of
|
||||
true ->
|
||||
mnesia:create_table(autofilter, [
|
||||
{disc_copies, [node()]}, {type, set},
|
||||
{attributes, record_info(fields, autofilter)} ]),
|
||||
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, offline_message, 10),
|
||||
ejabberd_hooks:add(filter_packet, ?MODULE, filter_packet, 10),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, close_session, 10),
|
||||
case gen_mod:get_opt(purge_freq, Opts, 0) of %% purge_freq in minutes
|
||||
0 ->
|
||||
no_purge;
|
||||
Freq ->
|
||||
Keep = gen_mod:get_opt(keep, Opts, 10), %% keep in minutes
|
||||
timer:apply_interval(Freq*60000, ?MODULE, purge, [Keep*60])
|
||||
end,
|
||||
start;
|
||||
false ->
|
||||
not_started
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, offline_message, 10),
|
||||
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, close_session, 10),
|
||||
stop.
|
||||
|
||||
is_loaded() ->
|
||||
ok.
|
||||
|
||||
purge(Keep) ->
|
||||
?INFO_MSG("autofilter purge",[]),
|
||||
{T1, T2, _} = now(),
|
||||
Older = T1*1000000+T2-Keep,
|
||||
lists:foreach(fun(Key) ->
|
||||
mnesia:dirty_delete({autofilter, Key})
|
||||
end, mnesia:dirty_select(autofilter, [{#autofilter{key = '$1', drop = false, timestamp = '$2', _ = '_'}, [{'<', '$2', Older}], ['$1']}])),
|
||||
ok.
|
||||
|
||||
deny(User, Server) ->
|
||||
Key = {User, Server},
|
||||
Record = case mnesia:dirty_read({autofilter, Key}) of
|
||||
[R] -> R;
|
||||
_ -> #autofilter{key=Key}
|
||||
end,
|
||||
?INFO_MSG("autofilter: messages from ~s@~s will be droped~n", [User, Server]),
|
||||
mnesia:dirty_write(Record#autofilter{drop=true}).
|
||||
|
||||
allow(User, Server) ->
|
||||
Key = {User, Server},
|
||||
?INFO_MSG("autofilter: messages from ~s@~s are accepted~n", [User, Server]),
|
||||
mnesia:dirty_delete({autofilter, Key}).
|
||||
|
||||
denied() ->
|
||||
mnesia:dirty_select(autofilter, [{#autofilter{key = '$1', drop = true, reason = '$2', _ = '_'}, [], [['$1','$2']]}]).
|
||||
listed() ->
|
||||
mnesia:dirty_select(autofilter, [{#autofilter{key = '$1', drop = false, reason = '$2', _ = '_'}, [], [['$1','$2']]}]).
|
||||
|
||||
offline_message({jid, [], _, [], [], _, []}, _To, _Packet) ->
|
||||
ok;
|
||||
offline_message(From, To, Packet) ->
|
||||
{User, Server, _} = jlib:jid_tolower(From),
|
||||
Key = {User, Server},
|
||||
{T1, T2, _} = now(),
|
||||
T = T1*1000000+T2,
|
||||
Record = case mnesia:dirty_read({autofilter, Key}) of
|
||||
[#autofilter{timestamp=O, count=C}=R] ->
|
||||
D = T-O,
|
||||
if
|
||||
D > 3600 -> % this is usefull only of purge_freq is not set
|
||||
R#autofilter{timestamp=T, count=1};
|
||||
((C/D) > 1/10) and (C > 90) ->
|
||||
?INFO_MSG("autofilter: messages from ~s@~s will be droped~n", [User, Server]),
|
||||
R#autofilter{drop=true, reason=offline_flood};
|
||||
true ->
|
||||
R#autofilter{count=C+1}
|
||||
end;
|
||||
_ ->
|
||||
#autofilter{key=Key, timestamp=T, count=1, drop=false, reason=offline_flood}
|
||||
end,
|
||||
mnesia:dirty_write(Record),
|
||||
ok.
|
||||
|
||||
filter_packet({From, To, Packet}) ->
|
||||
{User, Server, _} = jlib:jid_tolower(From),
|
||||
case mnesia:dirty_read({autofilter, {User, Server}}) of
|
||||
[#autofilter{drop=true}] -> drop;
|
||||
_ -> {From, To, Packet}
|
||||
end.
|
||||
|
||||
close_session(SID, JID) ->
|
||||
close_session(SID, JID, []).
|
||||
close_session(_SID, {jid, _, _, _, User, Server, _}, _Info) ->
|
||||
% this allows user, except for blocked ones
|
||||
lists:foreach(fun(#autofilter{key=Key}) ->
|
||||
mnesia:dirty_delete({autofilter, Key})
|
||||
end, mnesia:dirty_match_object(#autofilter{key={User, Server}, drop = false, _ = '_'})),
|
||||
ok.
|
175
src/mod_c2s_debug.erl
Normal file
175
src/mod_c2s_debug.erl
Normal file
@ -0,0 +1,175 @@
|
||||
%% Usage:
|
||||
%% In config file:
|
||||
%% {mod_c2s_debug, [{logdir, "/tmp/xmpplogs"}]},
|
||||
%% From Erlang shell:
|
||||
%% mod_c2s_debug:start("localhost", []).
|
||||
%% mod_c2s_debug:stop("localhost").
|
||||
%%
|
||||
%% Warning: Only one module for the debug handler can be defined.
|
||||
-module(mod_c2s_debug).
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
-behavior(gen_server).
|
||||
|
||||
-export([start/2, start_link/2, stop/1,
|
||||
debug_start/3, debug_stop/2, log_packet/4, log_packet/5]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_c2s.hrl").
|
||||
|
||||
-record(modstate, {host, logdir, pid, iodevice}).
|
||||
-record(clientinfo, {pid, jid, auth_module, ip}).
|
||||
|
||||
-define(SUPERVISOR, ejabberd_sup).
|
||||
-define(PROCNAME, c2s_debug).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Spec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, 2000, worker, [?MODULE]},
|
||||
supervisor:start_child(?SUPERVISOR, Spec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:delete_child(?SUPERVISOR, Proc).
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
|
||||
%% Debug handled by another module... Do nothing:
|
||||
debug_start(_Status, Pid, C2SState) ->
|
||||
Host = C2SState#state.server,
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
|
||||
JID = jlib:jid_to_string(C2SState#state.jid),
|
||||
AuthModule = C2SState#state.auth_module,
|
||||
IP = C2SState#state.ip,
|
||||
ClientInfo = #clientinfo{pid = Pid, jid = JID, auth_module = AuthModule, ip = IP},
|
||||
|
||||
gen_server:call(Proc, {debug_start, ClientInfo}).
|
||||
|
||||
debug_stop(Pid, C2SState) ->
|
||||
Host = C2SState#state.server,
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:cast(Proc, {debug_stop, Pid}).
|
||||
|
||||
log_packet(false, _FromJID, _ToJID, _Packet) ->
|
||||
ok;
|
||||
log_packet(true, FromJID, ToJID, Packet) ->
|
||||
Host = FromJID#jid.lserver,
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:cast(Proc, {addlog, {"Send", FromJID, ToJID, Packet}}).
|
||||
log_packet(false, _JID, _FromJID, _ToJID, _Packet) ->
|
||||
ok;
|
||||
log_packet(true, JID, FromJID, ToJID, Packet) ->
|
||||
Host = JID#jid.lserver,
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:cast(Proc, {addlog, {"Receive", FromJID, ToJID, Packet}}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, Opts]) ->
|
||||
?INFO_MSG("Starting c2s debug module for: ~p", [Host]),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "c2s_debug.@HOST@"),
|
||||
ejabberd_hooks:add(c2s_debug_start_hook, Host,
|
||||
?MODULE, debug_start, 50),
|
||||
ejabberd_hooks:add(c2s_debug_stop_hook, Host,
|
||||
?MODULE, debug_stop, 50),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, log_packet, 50),
|
||||
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, log_packet, 50),
|
||||
|
||||
Logdir = gen_mod:get_opt(logdir, Opts, "/tmp/xmpplogs/"),
|
||||
make_dir_rec(Logdir),
|
||||
{ok, #modstate{host = MyHost, logdir = Logdir}}.
|
||||
|
||||
terminate(_Reason, #modstate{host = Host}) ->
|
||||
?INFO_MSG("Stopping c2s debug module for: ~s", [Host]),
|
||||
ejabberd_hooks:delete(c2s_debug_start_hook, Host,
|
||||
?MODULE, debug_start, 50),
|
||||
ejabberd_hooks:delete(c2s_debug_stop_hook, Host,
|
||||
?MODULE, debug_stop, 50),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, log_packet, 50).
|
||||
|
||||
handle_call({debug_start, ClientInfo}, _From, #modstate{pid=undefined} = State) ->
|
||||
Pid = ClientInfo#clientinfo.pid,
|
||||
?INFO_MSG("Debug started for PID:~p", [Pid]),
|
||||
|
||||
JID = ClientInfo#clientinfo.jid,
|
||||
AuthModule = ClientInfo#clientinfo.auth_module,
|
||||
IP = ClientInfo#clientinfo.ip,
|
||||
|
||||
{ok, IOD} = file:open(filename(State#modstate.logdir), [append]),
|
||||
Line = io_lib:format("~s - Session open~nJID: ~s~nAuthModule: ~p~nIP: ~p~n",
|
||||
[timestamp(), JID, AuthModule, IP]),
|
||||
file:write(IOD, Line),
|
||||
|
||||
{reply, true, State#modstate{pid = Pid, iodevice = IOD}};
|
||||
handle_call({debug_start, _ClientInfo}, _From, State) ->
|
||||
{reply, false, State};
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
handle_call(_Req, _From, State) ->
|
||||
{reply, {error, badarg}, State}.
|
||||
|
||||
handle_cast({addlog, _}, #modstate{iodevice=undefined} = State) ->
|
||||
{noreply, State};
|
||||
handle_cast({addlog, {Direction, FromJID, ToJID, Packet}}, #modstate{iodevice=IOD} = State) ->
|
||||
LogEntry = io_lib:format("=====~n~s - ~s~nFrom: ~s~nTo: ~s~n~s~n", [timestamp(), Direction,
|
||||
jlib:jid_to_string(FromJID),
|
||||
jlib:jid_to_string(ToJID),
|
||||
xml:element_to_string(Packet)]),
|
||||
file:write(IOD, LogEntry),
|
||||
{noreply, State};
|
||||
handle_cast({debug_stop, Pid}, #modstate{pid=Pid, iodevice=IOD} = State) ->
|
||||
Line = io_lib:format("=====~n~s - Session closed~n",
|
||||
[timestamp()]),
|
||||
file:write(IOD, Line),
|
||||
|
||||
file:close(IOD),
|
||||
{noreply, State#modstate{pid = undefined, iodevice=undefined}};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% Generate filename
|
||||
filename(LogDir) ->
|
||||
Filename = lists:flatten(timestamp()) ++ "-c2s.log",
|
||||
filename:join([LogDir, Filename]).
|
||||
|
||||
%% Generate timestamp
|
||||
timestamp() ->
|
||||
{Y,Mo,D} = erlang:date(),
|
||||
{H,Mi,S} = erlang:time(),
|
||||
io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,Mo,D,H,Mi,S]).
|
||||
|
||||
%% Create dir recusively
|
||||
make_dir_rec(Dir) ->
|
||||
case file:read_file_info(Dir) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
{error, enoent} ->
|
||||
DirS = filename:split(Dir),
|
||||
DirR = lists:sublist(DirS, length(DirS)-1),
|
||||
make_dir_rec(filename:join(DirR)),
|
||||
file:make_dir(Dir)
|
||||
end.
|
@ -53,7 +53,7 @@
|
||||
]).
|
||||
|
||||
%% hook handlers
|
||||
-export([user_send_packet/3]).
|
||||
-export([user_send_packet/4]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@ -98,11 +98,12 @@ get_features(#caps{node = Node, version = Version, exts = Exts}) ->
|
||||
SubNodes = [Version | Exts],
|
||||
lists:foldl(
|
||||
fun(SubNode, Acc) ->
|
||||
case mnesia:dirty_read({caps_features,
|
||||
node_to_binary(Node, SubNode)}) of
|
||||
[] ->
|
||||
BinaryNode = node_to_binary(Node, SubNode),
|
||||
case cache_tab:lookup(caps_features, BinaryNode,
|
||||
caps_read_fun(BinaryNode)) of
|
||||
error ->
|
||||
Acc;
|
||||
[#caps_features{features = Features}] ->
|
||||
{ok, Features} ->
|
||||
binary_to_features(Features) ++ Acc
|
||||
end
|
||||
end, [], SubNodes).
|
||||
@ -141,7 +142,8 @@ read_caps([], Result) ->
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
user_send_packet(#jid{luser = User, lserver = Server} = From,
|
||||
user_send_packet(_DebugFlag,
|
||||
#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server, lresource = ""},
|
||||
{xmlelement, "presence", Attrs, Els}) ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
@ -155,7 +157,7 @@ user_send_packet(#jid{luser = User, lserver = Server} = From,
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
user_send_packet(_From, _To, _Packet) ->
|
||||
user_send_packet(_DebugFlag, _From, _To, _Packet) ->
|
||||
ok.
|
||||
|
||||
caps_stream_features(Acc, MyHost) ->
|
||||
@ -196,12 +198,23 @@ disco_info(Acc, _Host, _Module, _Node, _Lang) ->
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, _Opts]) ->
|
||||
init([Host, Opts]) ->
|
||||
case catch mnesia:table_info(caps_features, storage_type) of
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
disc_only_copies ->
|
||||
ok;
|
||||
_ ->
|
||||
mnesia:delete_table(caps_features)
|
||||
end,
|
||||
mnesia:create_table(caps_features,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, caps_features)}]),
|
||||
mnesia:add_table_copy(caps_features, node(), disc_copies),
|
||||
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
|
||||
MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
|
||||
LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000),
|
||||
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
|
||||
ejabberd_hooks:add(user_send_packet, Host,
|
||||
?MODULE, user_send_packet, 75),
|
||||
ejabberd_hooks:add(c2s_stream_features, Host,
|
||||
@ -252,8 +265,9 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
|
||||
Node = Caps#caps.node,
|
||||
BinaryNode = node_to_binary(Node, SubNode),
|
||||
case mnesia:dirty_read({caps_features, BinaryNode}) of
|
||||
[] ->
|
||||
case cache_tab:lookup(caps_features, BinaryNode,
|
||||
caps_read_fun(BinaryNode)) of
|
||||
error ->
|
||||
IQ = #iq{type = get,
|
||||
xmlns = ?NS_DISCO_INFO,
|
||||
sub_el = [{xmlelement, "query",
|
||||
@ -284,11 +298,13 @@ feature_response(#iq{type = result,
|
||||
(_) ->
|
||||
[]
|
||||
end, Els),
|
||||
mnesia:dirty_write(
|
||||
#caps_features{node_pair = BinaryNode,
|
||||
features = features_to_binary(Features)});
|
||||
BinaryFeatures = features_to_binary(Features),
|
||||
cache_tab:insert(
|
||||
caps_features, BinaryNode, BinaryFeatures,
|
||||
caps_write_fun(BinaryNode, BinaryFeatures));
|
||||
false ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode})
|
||||
cache_tab:insert(caps_features, BinaryNode, [],
|
||||
caps_write_fun(BinaryNode, []))
|
||||
end,
|
||||
feature_request(Host, From, Caps, SubNodes);
|
||||
feature_response(timeout, _Host, _From, _Caps, _SubNodes) ->
|
||||
@ -297,7 +313,8 @@ feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) ->
|
||||
%% We got type=error or invalid type=result stanza, so
|
||||
%% we cache empty feature not to probe the client permanently
|
||||
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
|
||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
|
||||
cache_tab:insert(caps_features, BinaryNode, [],
|
||||
caps_write_fun(BinaryNode, [])),
|
||||
feature_request(Host, From, Caps, SubNodes).
|
||||
|
||||
node_to_binary(Node, SubNode) ->
|
||||
@ -306,6 +323,23 @@ node_to_binary(Node, SubNode) ->
|
||||
features_to_binary(L) -> [list_to_binary(I) || I <- L].
|
||||
binary_to_features(L) -> [binary_to_list(I) || I <- L].
|
||||
|
||||
caps_read_fun(Node) ->
|
||||
fun() ->
|
||||
case mnesia:dirty_read({caps_features, Node}) of
|
||||
[#caps_features{features = Features}] ->
|
||||
{ok, Features};
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end.
|
||||
|
||||
caps_write_fun(Node, Features) ->
|
||||
fun() ->
|
||||
mnesia:dirty_write(
|
||||
#caps_features{node_pair = Node,
|
||||
features = Features})
|
||||
end.
|
||||
|
||||
make_my_disco_hash(Host) ->
|
||||
JID = jlib:make_jid("", Host, ""),
|
||||
case {ejabberd_hooks:run_fold(disco_local_features,
|
||||
|
370
src/mod_filter.erl
Normal file
370
src/mod_filter.erl
Normal file
@ -0,0 +1,370 @@
|
||||
%%% ====================================================================
|
||||
%%% This software is copyright 2006-2010, ProcessOne.
|
||||
%%%
|
||||
%%% mod_filter
|
||||
%%% allow message filtering using regexp on message body
|
||||
%%% THIS MODULE NEEDS A PATCHED ERLANG VM AGAINST
|
||||
%%% THE PCRE PATCH AND NEEDS LIBPCRE INSTALLED
|
||||
%%% ejabberd MUST USE THAT PATCHED ERLANG VM
|
||||
%%% BUT, if patch is not available, mod_filter uses re.beam module
|
||||
%%% instead, with speed degradation.
|
||||
%%%
|
||||
%%% @copyright 2006-2010 ProcessOne
|
||||
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% [http://www.process-one.net/]
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
-module(mod_filter).
|
||||
-author('christophe.romain@process-one.net').
|
||||
-vsn('$Id: mod_filter.erl 972 2010-10-14 21:53:56Z jpcarlino $').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
% module functions
|
||||
-export([start/2,stop/1,init/2,update/2,is_loaded/0,loop/5]).
|
||||
-export([add_regexp/4,add_regexp/3,del_regexp/3,del_regexp/2]).
|
||||
-export([purge_logs/0,purge_regexps/1,reload/1]).
|
||||
-export([logged/0,logged/1,rules/0]).
|
||||
-export([process_local_iq/3]).
|
||||
|
||||
% handled ejabberd hooks
|
||||
-export([filter_packet/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("licence.hrl").
|
||||
|
||||
-record(filter_rule, {id, type="filter", regexp, binre}).
|
||||
-record(filter_log, {date, from, to, message}).
|
||||
|
||||
-define(TIMEOUT, 5000). % deliver message anyway if filter does not respond after 5s
|
||||
-define(PROCNAME(VH), list_to_atom(VH++"_message_filter")).
|
||||
-define(NS_FILTER, "p1:iq:filter").
|
||||
|
||||
-define(ALLHOSTS, "all hosts"). %% must be sync with filter.erl
|
||||
|
||||
start(Host, Opts) ->
|
||||
case ?IS_VALID of
|
||||
true ->
|
||||
mnesia:create_table(filter_rule, [
|
||||
{disc_copies, [node()]}, {type, set},
|
||||
{attributes, record_info(fields, filter_rule)} ]),
|
||||
mnesia:create_table(filter_log, [
|
||||
{disc_only_copies, [node()]}, {type, bag},
|
||||
{attributes, record_info(fields, filter_log)} ]),
|
||||
%% this force the last code to be used
|
||||
case whereis(?PROCNAME(Host)) of
|
||||
undefined ->
|
||||
ok;
|
||||
_ ->
|
||||
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_FILTER),
|
||||
?PROCNAME(Host) ! quit
|
||||
end,
|
||||
ejabberd_hooks:add(filter_packet, ?MODULE, filter_packet, 10),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_FILTER,
|
||||
?MODULE, process_local_iq, one_queue),
|
||||
case whereis(?PROCNAME(Host)) of
|
||||
undefined -> register(?PROCNAME(Host), spawn(?MODULE, init, [Host, Opts]));
|
||||
_ -> ok
|
||||
end,
|
||||
%% start the all_alias handler
|
||||
case whereis(?PROCNAME(?ALLHOSTS)) of
|
||||
undefined -> init_all_hosts_handler();
|
||||
_ -> ok
|
||||
end,
|
||||
start;
|
||||
false ->
|
||||
not_started
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(filter_packet, ?MODULE, filter_packet, 10),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_FILTER),
|
||||
exit(whereis(?PROCNAME(Host)), kill),
|
||||
{wait, ?PROCNAME(Host)}.
|
||||
|
||||
% this is used by team_leader to check code presence
|
||||
is_loaded() ->
|
||||
ok.
|
||||
|
||||
% this loads rules and return {Types, BinRegExps}
|
||||
% RegExps are text regexp list
|
||||
% Types are regexp type list
|
||||
% both list share the same ordered
|
||||
load_rules(Host) ->
|
||||
Rules = mnesia:dirty_match_object(#filter_rule{id={'_', Host}, _ = '_'})
|
||||
++ mnesia:dirty_match_object(#filter_rule{id={'_', ?ALLHOSTS}, _ = '_'}),
|
||||
lists:map(fun({filter_rule, _, Type, _, BinRegExp}) -> {Type, BinRegExp} end, Rules).
|
||||
%lists:unzip(Config).
|
||||
|
||||
% this call init or reset local rules reading database
|
||||
init(Host, Opts) ->
|
||||
Rules = load_rules(Host),
|
||||
Scope = gen_mod:get_opt(scope, Opts, message),
|
||||
Pattern = gen_mod:get_opt(pattern, Opts, ""),
|
||||
?MODULE:loop(Host, Opts, Rules, Scope, Pattern).
|
||||
|
||||
init_all_hosts_handler() ->
|
||||
register(?PROCNAME(?ALLHOSTS), spawn(?MODULE, loop, [?ALLHOSTS, [], [], none, []])).
|
||||
|
||||
% this call reset local rules reading database
|
||||
% and tell other nodes to reset rules as well
|
||||
update(?ALLHOSTS, _Opts) ->
|
||||
lists:foreach(fun(Host) ->
|
||||
lists:foreach(fun(Node) ->
|
||||
catch rpc:call(Node,mod_filter,reload,[Host])
|
||||
end, mnesia:system_info(running_db_nodes))
|
||||
end, ejabberd_config:get_global_option(hosts)),
|
||||
?MODULE:loop(?ALLHOSTS, [], [], none, []);
|
||||
update(Host, Opts) ->
|
||||
% tell other nodes to update filter
|
||||
lists:foreach(fun(Node) ->
|
||||
catch rpc:call(Node,mod_filter,reload,[Host])
|
||||
end, mnesia:system_info(running_db_nodes)--[node()]),
|
||||
% update rules
|
||||
init(Host, Opts).
|
||||
|
||||
loop(Host, Opts, Rules, Scope, Pattern) ->
|
||||
receive
|
||||
{add, Id, RegExp} ->
|
||||
try
|
||||
[BinRegExp] = tlre:compile([RegExp]),
|
||||
?INFO_MSG("Adding new filter rule with regexp=~p", [RegExp]),
|
||||
mnesia:dirty_write(#filter_rule{id={Id, Host}, regexp=RegExp, binre=BinRegExp}),
|
||||
?MODULE:update(Host, Opts)
|
||||
catch
|
||||
Class:Reason ->
|
||||
?INFO_MSG("~p can't add filter rule with regexp=~p for id=~p. Reason: ~p", [Class, RegExp, Id, Reason]),
|
||||
loop(Host, Opts, Rules, Scope, Pattern)
|
||||
end;
|
||||
{add, Id, RegExp, Type} ->
|
||||
try
|
||||
[BinRegExp] = tlre:compile([RegExp]),
|
||||
?INFO_MSG("Adding new filter rule with regexp=~p", [RegExp]),
|
||||
mnesia:dirty_write(#filter_rule{id={Id, Host}, regexp=RegExp, binre=BinRegExp, type=Type}),
|
||||
?MODULE:update(Host, Opts)
|
||||
catch
|
||||
Class:Reason ->
|
||||
?INFO_MSG("~p can't add filter rule with regexp=~p for id=~p with type=~p. Reason: ~p", [Class, RegExp, Id, Type, Reason]),
|
||||
loop(Host, Opts, Rules, Scope, Pattern)
|
||||
end;
|
||||
{del, Id} ->
|
||||
RulesToRemove = mnesia:dirty_match_object(#filter_rule{id={Id, Host}, _='_'}),
|
||||
lists:foreach(fun(Rule) ->
|
||||
mnesia:dirty_delete_object(Rule)
|
||||
end, RulesToRemove),
|
||||
?MODULE:update(Host, Opts);
|
||||
{del, Id, RegExp} ->
|
||||
RulesToRemove = mnesia:dirty_match_object(#filter_rule{id={Id, Host}, regexp=RegExp, _='_'}),
|
||||
lists:foreach(fun(Rule) ->
|
||||
mnesia:dirty_delete_object(Rule)
|
||||
end, RulesToRemove),
|
||||
?MODULE:update(Host, Opts);
|
||||
{match, From, String} ->
|
||||
From ! {match, string_filter(String, Rules, Scope, Pattern)},
|
||||
?MODULE:loop(Host, Opts, Rules, Scope, Pattern);
|
||||
reload ->
|
||||
?MODULE:init(Host, Opts);
|
||||
quit ->
|
||||
unregister(?PROCNAME(Host)),
|
||||
ok
|
||||
end.
|
||||
|
||||
string_filter(String, Rules, Scope, Pattern) ->
|
||||
lists:foldl(fun
|
||||
(_, {Pass, []}) -> {Pass, []};
|
||||
({Type, RegExp}, {Pass, NewString}) -> string_filter(NewString, Pass, RegExp, Type, Scope, Pattern)
|
||||
end, {"pass", String}, Rules).
|
||||
string_filter(String, Pass, RegExp, Type, Scope, Pattern) ->
|
||||
case tlre:grep(String, [RegExp]) of
|
||||
[no_match] ->
|
||||
{Pass, String};
|
||||
[{S1, S2, _}] ->
|
||||
case Scope of
|
||||
word ->
|
||||
Start = string:sub_string(String, 1, S1),
|
||||
StringTail = string:sub_string(String, S2+1, length(String)),
|
||||
NewPass = pass_rule(Pass, Type),
|
||||
{LastPass, End} = string_filter(StringTail, NewPass, RegExp, Type, Scope, Pattern),
|
||||
NewString = case Type of
|
||||
"log" -> lists:append([string:sub_string(String, 1, S2), End]);
|
||||
_ -> lists:append([Start, Pattern, End])
|
||||
end,
|
||||
{LastPass, NewString};
|
||||
_ ->
|
||||
NewString = case Type of
|
||||
"log" -> String;
|
||||
_ -> []
|
||||
end,
|
||||
{pass_rule(Pass, Type), NewString}
|
||||
end
|
||||
end.
|
||||
|
||||
pass_rule("pass", New) -> New;
|
||||
pass_rule("log", "log") -> "log";
|
||||
pass_rule("log", "log and filter") -> "log and filter";
|
||||
pass_rule("log", "filter") -> "log and filter";
|
||||
pass_rule("filter", "log") -> "log and filter";
|
||||
pass_rule("filter", "log and filter") -> "log and filter";
|
||||
pass_rule("filter", "filter") -> "filter";
|
||||
pass_rule("log and filter", _) -> "log and filter".
|
||||
|
||||
add_regexp(VH, Id, RegExp) ->
|
||||
?PROCNAME(VH) ! {add, Id, RegExp},
|
||||
ok.
|
||||
|
||||
add_regexp(VH, Id, RegExp, Type) ->
|
||||
?PROCNAME(VH) ! {add, Id, RegExp, Type},
|
||||
ok.
|
||||
|
||||
del_regexp(VH, Id) ->
|
||||
?PROCNAME(VH) ! {del, Id},
|
||||
ok.
|
||||
|
||||
del_regexp(VH, Id, RegExp) ->
|
||||
?PROCNAME(VH) ! {del, Id, RegExp},
|
||||
ok.
|
||||
|
||||
reload(VH) ->
|
||||
?PROCNAME(VH) ! reload,
|
||||
ok.
|
||||
|
||||
purge_logs() ->
|
||||
mnesia:dirty_delete_object(#filter_log{_='_'}).
|
||||
|
||||
%purge_regexps() ->
|
||||
% mnesia:dirty_delete_object(#filter_rule{_='_'}),
|
||||
% reload().
|
||||
|
||||
purge_regexps(VH) ->
|
||||
mnesia:dirty_delete_object(#filter_rule{id={'_', VH}, _='_'}),
|
||||
reload(VH).
|
||||
|
||||
rules() ->
|
||||
lists:map(fun(#filter_rule{id={Label, VH}, type=Type, regexp=Regexp}) ->
|
||||
{VH, Label, Type, Regexp}
|
||||
end, mnesia:dirty_match_object(#filter_rule{_='_'})).
|
||||
|
||||
logged() ->
|
||||
lists:reverse(lists:map(fun(#filter_log{date=Date, from=From, to=To, message=Msg}) ->
|
||||
{Date, jlib:jid_to_string(From), jlib:jid_to_string(To), Msg}
|
||||
end, mnesia:dirty_match_object(#filter_log{_='_'}))).
|
||||
|
||||
logged(Limit) ->
|
||||
List = mnesia:dirty_match_object(#filter_log{_='_'}),
|
||||
Len = length(List),
|
||||
FinalList = if
|
||||
Len < Limit -> List;
|
||||
true -> lists:nthtail(Len-Limit, List)
|
||||
end,
|
||||
lists:reverse(lists:map(fun(#filter_log{date=Date, from=From, to=To, message=Msg}) ->
|
||||
{Date, jlib:jid_to_string(From), jlib:jid_to_string(To), Msg}
|
||||
end, FinalList)).
|
||||
|
||||
%% filter_packet can receive drop if a previous filter already dropped
|
||||
%% the packet
|
||||
filter_packet(drop) -> drop;
|
||||
filter_packet({From, To, Packet}) ->
|
||||
case Packet of
|
||||
{xmlelement, "message", MsgAttrs, Els} ->
|
||||
case lists:keysearch("body", 2, Els) of
|
||||
{value, {xmlelement, "body", BodyAttrs, Data}} ->
|
||||
NewData = lists:foldl(fun
|
||||
({xmlcdata, CData}, DataAcc) when is_binary(CData) ->
|
||||
#jid{lserver = Host} = To,
|
||||
case lists:member(Host, ejabberd_config:get_global_option(hosts)) of
|
||||
true ->
|
||||
Msg = binary_to_list(CData),
|
||||
?PROCNAME(Host) ! {match, self(), Msg},
|
||||
receive
|
||||
{match, {"pass", _}} ->
|
||||
[{xmlcdata, CData}|DataAcc];
|
||||
{match, {"log", _}} ->
|
||||
mnesia:dirty_write(#filter_log{
|
||||
date=erlang:localtime(),
|
||||
from=From, to=To, message=Msg}),
|
||||
[{xmlcdata, CData}|DataAcc];
|
||||
{match, {"log and filter", FinalString}} ->
|
||||
mnesia:dirty_write(#filter_log{
|
||||
date=erlang:localtime(),
|
||||
from=From, to=To, message=Msg}),
|
||||
case FinalString of
|
||||
[] -> % entire message is dropped
|
||||
DataAcc;
|
||||
S -> % message must be regenerated
|
||||
[{xmlcdata, list_to_binary(S)}|DataAcc]
|
||||
end;
|
||||
{match, {"filter", FinalString}} ->
|
||||
case FinalString of
|
||||
[] -> % entire message is dropped
|
||||
DataAcc;
|
||||
S -> % message must be regenerated
|
||||
[{xmlcdata, list_to_binary(S)}|DataAcc]
|
||||
end
|
||||
after ?TIMEOUT ->
|
||||
[{xmlcdata, CData}|DataAcc]
|
||||
end;
|
||||
false ->
|
||||
[{xmlcdata, CData}|DataAcc]
|
||||
end;
|
||||
(Item, DataAcc) -> %% to not filter internal messages
|
||||
[Item|DataAcc]
|
||||
end, [], Data),
|
||||
case NewData of
|
||||
[] ->
|
||||
drop;
|
||||
D ->
|
||||
NewEls = lists:keyreplace("body", 2, Els, {xmlelement, "body", BodyAttrs, lists:reverse(D)}),
|
||||
{From, To, {xmlelement, "message", MsgAttrs, NewEls}}
|
||||
end;
|
||||
_ ->
|
||||
{From, To, Packet}
|
||||
end;
|
||||
_ ->
|
||||
{From, To, Packet}
|
||||
end.
|
||||
|
||||
process_local_iq(From, #jid{lserver=VH} = _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
get ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
set ->
|
||||
#jid{luser = User, lserver = Server, lresource = Resource} = From,
|
||||
case acl:match_rule(global, configure, {User, Server, Resource}) of
|
||||
allow ->
|
||||
case xml:get_subtag(SubEl, "add") of
|
||||
{xmlelement, "add", AddAttrs, _} ->
|
||||
AID = xml:get_attr_s("id", AddAttrs),
|
||||
ARE = xml:get_attr_s("re", AddAttrs),
|
||||
case xml:get_attr_s("type", AddAttrs) of
|
||||
"" -> add_regexp(VH, AID, ARE);
|
||||
ATP -> add_regexp(VH, AID, ARE, ATP)
|
||||
end;
|
||||
_ -> ok
|
||||
end,
|
||||
case xml:get_subtag(SubEl, "del") of
|
||||
{xmlelement, "del", DelAttrs, _} ->
|
||||
DID = xml:get_attr_s("id", DelAttrs),
|
||||
case xml:get_attr_s("re", DelAttrs) of
|
||||
"" -> del_regexp(VH, DID);
|
||||
DRE -> del_regexp(VH, DID, DRE)
|
||||
end;
|
||||
_ -> ok
|
||||
end,
|
||||
case xml:get_subtag(SubEl, "dellogs") of
|
||||
{xmlelement, "dellogs", _, _} -> purge_logs();
|
||||
_ -> ok
|
||||
end,
|
||||
case xml:get_subtag(SubEl, "delrules") of
|
||||
{xmlelement, "delrules", _, _} -> purge_regexps(VH);
|
||||
_ -> ok
|
||||
end,
|
||||
IQ#iq{type = result, sub_el = []};
|
||||
_ ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
||||
end
|
||||
end.
|
||||
|
@ -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),
|
||||
|
@ -105,8 +105,16 @@ init([From, Host, Server, Username, Encoding, Port, Password]) ->
|
||||
open_socket(init, StateData) ->
|
||||
Addr = StateData#state.server,
|
||||
Port = StateData#state.port,
|
||||
?DEBUG("connecting to ~s:~p~n", [Addr, Port]),
|
||||
case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of
|
||||
?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]),
|
||||
Connect6 = gen_tcp:connect(Addr, Port, [inet6, binary, {packet, 0}]),
|
||||
Connect = case Connect6 of
|
||||
{error, _} ->
|
||||
?DEBUG("Connection with IPv6 to ~s:~p failed. Now using IPv4.", [Addr, Port]),
|
||||
gen_tcp:connect(Addr, Port, [inet, binary, {packet, 0}]);
|
||||
_ ->
|
||||
Connect6
|
||||
end,
|
||||
case Connect of
|
||||
{ok, Socket} ->
|
||||
NewStateData = StateData#state{socket = Socket},
|
||||
if
|
||||
|
147
src/mod_mnesia_mngt.erl
Normal file
147
src/mod_mnesia_mngt.erl
Normal file
@ -0,0 +1,147 @@
|
||||
%% Usage:
|
||||
%% In config file:
|
||||
%% {mod_mnesia_mgmt, [{logdir, "/tmp/xmpplogs/"}]},
|
||||
%% From Erlang shell:
|
||||
%% mod_mnesia_mgmt:start("localhost", []).
|
||||
%% mod_mnesia_mgmt:stop("localhost").
|
||||
|
||||
-module(mod_mnesia_mngt).
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
-behavior(gen_server).
|
||||
|
||||
-export([start/2, start_link/2, stop/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(modstate, {host, logdir, iodevice, timer}).
|
||||
|
||||
-define(SUPERVISOR, ejabberd_sup).
|
||||
-define(PROCNAME, mod_mnesia_mgmt).
|
||||
|
||||
-define(STANDARD_ACCEPT_INTERVAL, 20). %% accept maximum one new connection every 20ms
|
||||
-define(ACCEPT_INTERVAL, 200). %% This is used when Mnesia is overloaded
|
||||
-define(RATE_LIMIT_DURATION, 120000). %% Time during which the rate limitation need to be maintained
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Spec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, 2000, worker, [?MODULE]},
|
||||
supervisor:start_child(?SUPERVISOR, Spec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:delete_child(?SUPERVISOR, Proc).
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, Opts]) ->
|
||||
?INFO_MSG("Starting mod_mnesia_mgmt for: ~p", [Host]),
|
||||
ejabberd_listener:rate_limit([5222, 5223], ?STANDARD_ACCEPT_INTERVAL),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "mnesia_mngt.@HOST@"),
|
||||
|
||||
Logdir = gen_mod:get_opt(logdir, Opts, "/tmp/xmpplogs/"),
|
||||
make_dir_rec(Logdir),
|
||||
{ok, IOD} = file:open(filename(Logdir), [append]),
|
||||
|
||||
mnesia:subscribe(system),
|
||||
|
||||
{ok, #modstate{host = MyHost, logdir = Logdir, iodevice = IOD}}.
|
||||
|
||||
terminate(_Reason, #modstate{host = Host, iodevice = IOD}) ->
|
||||
?INFO_MSG("Stopping mod_mnesia_mgmt for: ~s", [Host]),
|
||||
mnesia:unsubscribe(system),
|
||||
file:close(IOD).
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
handle_call(_Req, _From, State) ->
|
||||
{reply, {error, badarg}, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({mnesia_system_event,{mnesia_overload, {mnesia_tm, message_queue_len, Values}}},
|
||||
#modstate{iodevice = IOD, timer = Timer} = State) ->
|
||||
Line = io_lib:format("~s - Mnesia overload due to message queue length (~p)",
|
||||
[timestamp(), Values]),
|
||||
file:write(IOD, Line),
|
||||
reset_timer(Timer),
|
||||
|
||||
{messages, Messages} = process_info(whereis(mnesia_tm), messages),
|
||||
log_messages(IOD, Messages, 20),
|
||||
{noreply, State#modstate{timer = undefined}};
|
||||
handle_info({mnesia_system_event,{mnesia_overload, Details}},
|
||||
#modstate{iodevice = IOD, timer = Timer} = State) ->
|
||||
Line = io_lib:format("~s - Mnesia overload: ~p",
|
||||
[timestamp(), Details]),
|
||||
file:write(IOD, Line),
|
||||
reset_timer(Timer),
|
||||
{noreply, State};
|
||||
handle_info({mnesia_system_event, _Event}, State) ->
|
||||
%% TODO: More event to handle
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% Generate filename
|
||||
filename(LogDir) ->
|
||||
Filename = lists:flatten(timestamp()) ++ "-mnesia.log",
|
||||
filename:join([LogDir, Filename]).
|
||||
|
||||
%% Generate timestamp
|
||||
timestamp() ->
|
||||
{Y,Mo,D} = erlang:date(),
|
||||
{H,Mi,S} = erlang:time(),
|
||||
io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w", [Y,Mo,D,H,Mi,S]).
|
||||
|
||||
%% Create dir recusively
|
||||
make_dir_rec(Dir) ->
|
||||
case file:read_file_info(Dir) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
{error, enoent} ->
|
||||
DirS = filename:split(Dir),
|
||||
DirR = lists:sublist(DirS, length(DirS)-1),
|
||||
make_dir_rec(filename:join(DirR)),
|
||||
file:make_dir(Dir)
|
||||
end.
|
||||
|
||||
%% Write first messages to log file
|
||||
log_messages(_IOD, _Messages, 0) ->
|
||||
ok;
|
||||
log_messages(_IOD, [], _N) ->
|
||||
ok;
|
||||
log_messages(IOD, [Message|Messages], N) ->
|
||||
Line = io_lib:format("** ~w",
|
||||
[Message]),
|
||||
file:write(IOD, Line),
|
||||
log_messages(IOD, Messages, N-1).
|
||||
|
||||
reset_timer(Timer) ->
|
||||
cancel_timer(Timer),
|
||||
ejabberd_listener:rate_limit([5222, 5223], ?ACCEPT_INTERVAL),
|
||||
timer:apply_after(?RATE_LIMIT_DURATION, ejabberd_listener, rate_limit, [[5222, 5223], ?STANDARD_ACCEPT_INTERVAL]).
|
||||
|
||||
cancel_timer(undefined) ->
|
||||
ok;
|
||||
cancel_timer(Timer) ->
|
||||
timer:cancel(Timer).
|
@ -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
|
||||
|
@ -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()),
|
||||
|
@ -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),
|
||||
|
@ -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")].
|
||||
|
@ -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")].
|
||||
|
||||
|
@ -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).
|
||||
|
||||
%%====================================================================
|
||||
|
@ -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;
|
||||
_ ->
|
||||
|
@ -19,6 +19,8 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(mod_privacy_hrl, true).
|
||||
|
||||
-record(privacy, {us,
|
||||
default = none,
|
||||
lists = []}).
|
||||
|
@ -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;
|
||||
_ ->
|
||||
|
@ -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.
|
||||
|
@ -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)),
|
||||
|
@ -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},
|
||||
|
@ -86,7 +86,6 @@ terminate(Host, ServerHost) ->
|
||||
|
||||
options() ->
|
||||
[{odbc, true},
|
||||
{node_type, pep},
|
||||
{deliver_payloads, true},
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
|
38
src/mod_pubsub/pubsub_clean.erl
Normal file
38
src/mod_pubsub/pubsub_clean.erl
Normal file
@ -0,0 +1,38 @@
|
||||
-module(pubsub_clean).
|
||||
|
||||
-define(TIMEOUT, 1000*600). % 1 minute
|
||||
|
||||
-export([start/0, loop/0, purge/0, offline/1]).
|
||||
|
||||
start() ->
|
||||
Pid = spawn(?MODULE, loop, []),
|
||||
register(pubsub_clean, Pid),
|
||||
Pid.
|
||||
|
||||
loop() ->
|
||||
receive
|
||||
purge -> purge()
|
||||
after ?TIMEOUT -> purge()
|
||||
end,
|
||||
loop().
|
||||
|
||||
purge() ->
|
||||
Sessions = lists:sum([mnesia:table_info(session,size)|[rpc:call(N,mnesia,table_info,[session,size]) || N <- nodes()]]),
|
||||
Subscriptions = mnesia:table_info(pubsub_state,size),
|
||||
if Subscriptions > Sessions + 500 ->
|
||||
lists:foreach(fun(K) ->
|
||||
[N]=mnesia:dirty_read({pubsub_node, K}),
|
||||
I=element(3,N),
|
||||
lists:foreach(fun(JID) ->
|
||||
mnesia:dirty_delete({pubsub_state, {JID, I}})
|
||||
end, offline(pubsub_debug:subscribed(I)))
|
||||
end, mnesia:dirty_all_keys(pubsub_node));
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
offline(Jids) ->
|
||||
lists:filter(fun({U,S,""}) -> ejabberd_sm:get_user_resources(U,S) == [];
|
||||
({U,S,R}) -> not lists:member(R,ejabberd_sm:get_user_resources(U,S))
|
||||
end, Jids).
|
||||
%%ejabberd_cluster:get_node({LUser, LServer})
|
113
src/mod_pubsub/pubsub_debug.erl
Normal file
113
src/mod_pubsub/pubsub_debug.erl
Normal file
@ -0,0 +1,113 @@
|
||||
-module(pubsub_debug).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-include("pubsub.hrl").
|
||||
|
||||
-compile(export_all).
|
||||
|
||||
nodeid(Host, Node) ->
|
||||
case mnesia:dirty_read({pubsub_node, {Host, Node}}) of
|
||||
[N] -> nodeid(N);
|
||||
_ -> 0
|
||||
end.
|
||||
nodeid(N) -> N#pubsub_node.id.
|
||||
nodeids() -> [nodeid(Host, Node) || {Host, Node} <- mnesia:dirty_all_keys(pubsub_node)].
|
||||
nodeids_by_type(Type) -> [nodeid(N) || N <- mnesia:dirty_match_object(#pubsub_node{type=Type, _='_'})].
|
||||
nodeids_by_option(Key, Value) -> [nodeid(N) || N <- mnesia:dirty_match_object(#pubsub_node{_='_'}), lists:member({Key, Value}, N#pubsub_node.options)].
|
||||
nodeids_by_owner(JID) -> [nodeid(N) || N <- mnesia:dirty_match_object(#pubsub_node{_='_'}), lists:member(JID, N#pubsub_node.owners)].
|
||||
nodes_by_id(I) -> mnesia:dirty_match_object(#pubsub_node{id=I, _='_'}).
|
||||
nodes() -> [element(2, element(2, N)) || N <- mnesia:dirty_match_object(#pubsub_node{_='_'})].
|
||||
|
||||
state(JID, NodeId) ->
|
||||
case mnesia:dirty_read({pubsub_state, {JID, NodeId}}) of
|
||||
[S] -> S;
|
||||
_ -> undefined
|
||||
end.
|
||||
states(NodeId) -> mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, _='_'}).
|
||||
stateid(S) -> element(1, S#pubsub_state.stateid).
|
||||
stateids(NodeId) -> [stateid(S) || S <- states(NodeId)].
|
||||
states_by_jid(JID) -> mnesia:dirty_match_object(#pubsub_state{stateid={JID, '_'}, _='_'}).
|
||||
|
||||
item(ItemId, NodeId) ->
|
||||
case mnesia:dirty_read({pubsub_item, {ItemId, NodeId}}) of
|
||||
[I] -> I;
|
||||
_ -> undefined
|
||||
end.
|
||||
items(NodeId) -> mnesia:dirty_match_object(#pubsub_item{itemid={'_', NodeId}, _='_'}).
|
||||
itemid(I) -> element(1, I#pubsub_item.itemid).
|
||||
itemids(NodeId) -> [itemid(I) || I <- items(NodeId)].
|
||||
items_by_id(ItemId) -> mnesia:dirty_match_object(#pubsub_item{itemid={ItemId, '_'}, _='_'}).
|
||||
|
||||
affiliated(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.affiliation=/=none].
|
||||
subscribed(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.subscriptions=/=[]].
|
||||
%subscribed(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.subscription=/=none]. %% old record
|
||||
owners(NodeId) -> [stateid(S) || S <- mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, affiliation=owner, _='_'})].
|
||||
|
||||
orphan_items(NodeId) ->
|
||||
itemids(NodeId) -- lists:foldl(fun(S, A) -> A++S#pubsub_state.items end, [], mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, _='_'})).
|
||||
newer_items(NodeId, Seconds) ->
|
||||
Now = calendar:universal_time(),
|
||||
Oldest = calendar:seconds_to_daystime(Seconds),
|
||||
[itemid(I) || I <- items(NodeId), calendar:time_difference(calendar:now_to_universal_time(element(1, I#pubsub_item.modification)), Now) < Oldest].
|
||||
older_items(NodeId, Seconds) ->
|
||||
Now = calendar:universal_time(),
|
||||
Oldest = calendar:seconds_to_daystime(Seconds),
|
||||
[itemid(I) || I <- items(NodeId), calendar:time_difference(calendar:now_to_universal_time(element(1, I#pubsub_item.modification)), Now) > Oldest].
|
||||
|
||||
orphan_nodes() -> [I || I <- nodeids(), owners(I)==[]].
|
||||
duplicated_nodes() -> L = nodeids(), lists:usort(L -- lists:seq(1, lists:max(L))).
|
||||
node_options(NodeId) ->
|
||||
[N] = mnesia:dirty_match_object(#pubsub_node{id=NodeId, _='_'}),
|
||||
N#pubsub_node.options.
|
||||
update_node_options(Key, Value, NodeId) ->
|
||||
[N] = mnesia:dirty_match_object(#pubsub_node{id=NodeId, _='_'}),
|
||||
NewOptions = lists:keyreplace(Key, 1, N#pubsub_node.options, {Key, Value}),
|
||||
mnesia:dirty_write(N#pubsub_node{options = NewOptions}).
|
||||
|
||||
check() ->
|
||||
mnesia:transaction(fun() ->
|
||||
case mnesia:read({pubsub_index, node}) of
|
||||
[Idx] ->
|
||||
Free = Idx#pubsub_index.free,
|
||||
Last = Idx#pubsub_index.last,
|
||||
Allocated = lists:seq(1, Last) -- Free,
|
||||
NodeIds = mnesia:foldl(fun(N,A) -> [nodeid(N)|A] end, [], pubsub_node),
|
||||
StateIds = lists:usort(mnesia:foldl(fun(S,A) -> [element(2, S#pubsub_state.stateid)|A] end, [], pubsub_state)),
|
||||
ItemIds = lists:usort(mnesia:foldl(fun(I,A) -> [element(2, I#pubsub_item.itemid)|A] end, [], pubsub_item)),
|
||||
BadNodeIds = NodeIds -- Allocated,
|
||||
BadStateIds = StateIds -- NodeIds,
|
||||
BadItemIds = ItemIds -- NodeIds,
|
||||
Lost = Allocated -- NodeIds,
|
||||
[{bad_nodes, [N#pubsub_node.nodeid || N <- lists:flatten([mnesia:match_object(#pubsub_node{id=I, _='_'}) || I <- BadNodeIds])]},
|
||||
{bad_states, lists:foldl(fun(N,A) -> A++[{I,N} || I <- stateids(N)] end, [], BadStateIds)},
|
||||
{bad_items, lists:foldl(fun(N,A) -> A++[{I,N} || I <- itemids(N)] end, [], BadItemIds)},
|
||||
{lost_idx, Lost},
|
||||
{orphaned, [I || I <- NodeIds, owners(I)==[]]},
|
||||
{duplicated, lists:usort(NodeIds -- lists:seq(1, lists:max(NodeIds)))}];
|
||||
_ ->
|
||||
no_index
|
||||
end
|
||||
end).
|
||||
|
||||
rebuild_index() ->
|
||||
mnesia:transaction(fun() ->
|
||||
NodeIds = mnesia:foldl(fun(N,A) -> [nodeid(N)|A] end, [], pubsub_node),
|
||||
Last = lists:max(NodeIds),
|
||||
Free = lists:seq(1, Last) -- NodeIds,
|
||||
mnesia:write(#pubsub_index{index = node, last = Last, free = Free})
|
||||
end).
|
||||
|
||||
pep_subscriptions(LUser, LServer, LResource) ->
|
||||
case ejabberd_sm:get_session_pid({LUser, LServer, LResource}) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
case catch ejabberd_c2s:get_subscribed(C2SPid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:map(fun({U, S, _}) ->
|
||||
io_lib:format("~s@~s", [U, S])
|
||||
end, Contacts);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
@ -31,8 +31,8 @@
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
log_user_send/3,
|
||||
log_user_receive/4]).
|
||||
log_user_send/4,
|
||||
log_user_receive/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@ -51,10 +51,10 @@ stop(Host) ->
|
||||
?MODULE, log_user_receive, 50),
|
||||
ok.
|
||||
|
||||
log_user_send(From, To, Packet) ->
|
||||
log_user_send(_DebugFlag, From, To, Packet) ->
|
||||
log_packet(From, To, Packet, From#jid.lserver).
|
||||
|
||||
log_user_receive(_JID, From, To, Packet) ->
|
||||
log_user_receive(_DebugFlag, _JID, From, To, Packet) ->
|
||||
log_packet(From, To, Packet, To#jid.lserver).
|
||||
|
||||
|
||||
@ -74,4 +74,3 @@ log_packet(From, To, {xmlelement, Name, Attrs, Els}, Host) ->
|
||||
luser = "", lserver = Logger, lresource = ""},
|
||||
{xmlelement, "route", [], [FixedPacket]})
|
||||
end, Loggers).
|
||||
|
||||
|
260
src/mod_support.erl
Normal file
260
src/mod_support.erl
Normal file
@ -0,0 +1,260 @@
|
||||
%%% ====================================================================
|
||||
%%% This software is copyright 2006-2010, ProcessOne.
|
||||
%%%
|
||||
%%% mod_support
|
||||
%%% allow automatic build of support archive to be sent to Process-One
|
||||
%%%
|
||||
%%% @copyright 2006-2010 ProcessOne
|
||||
%%% @author Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% [http://www.process-one.net/]
|
||||
%%% @version {@vsn}, {@date} {@time}
|
||||
%%% @end
|
||||
%%% ====================================================================
|
||||
|
||||
|
||||
-module(mod_support).
|
||||
-author('christophe.romain@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
%-behaviour(gen_server).
|
||||
|
||||
% module functions
|
||||
-export([start/2,stop/1,is_loaded/0,loop/1,dump/0]).
|
||||
-compile(export_all).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("licence.hrl").
|
||||
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
-define(LOG_FETCH_SIZE, 1000000).
|
||||
-define(RPC_TIMEOUT, 10000). % 10
|
||||
-define(MAX_FILE_SIZE, 2147483648). %%2Gb
|
||||
|
||||
start(Host, Opts) ->
|
||||
case ?IS_VALID of
|
||||
true ->
|
||||
case gen_mod:get_opt(dump_freq, Opts, 0) of
|
||||
0 -> no_dump;
|
||||
Freq -> spawn(?MODULE, loop, [Freq*60000])
|
||||
end,
|
||||
ok;
|
||||
false ->
|
||||
not_started
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ok.
|
||||
|
||||
is_loaded() ->
|
||||
ok.
|
||||
|
||||
loop(Timeout) ->
|
||||
receive
|
||||
quit -> ok
|
||||
after Timeout ->
|
||||
Dump = dump(),
|
||||
BaseName = get_base_name(),
|
||||
%%{Data,EjabberdLog,SaslLog,ECrash} = Dump,
|
||||
write_logs(tuple_to_list(Dump),BaseName,["_memory.bin",
|
||||
"_ejabberd.log.gz",
|
||||
"_sasl.log.gz",
|
||||
"_erlang_crash_dump.log.gz"]),
|
||||
loop(Timeout)
|
||||
end.
|
||||
|
||||
get_base_name() ->
|
||||
{{Y,M,D},{Hr,Mn,_Sc}} = calendar:local_time(),
|
||||
case os:getenv("EJABBERD_LOG_PATH") of
|
||||
false ->
|
||||
filename:join(filename:dirname(filename:absname("")),
|
||||
lists:flatten(io_lib:format("~b~b~b~b~b",[Y,M,D,Hr,Mn])));
|
||||
Path ->
|
||||
filename:join(filename:dirname(Path),
|
||||
lists:flatten(io_lib:format("~b~b~b~b~b",[Y,M,D,Hr,Mn])))
|
||||
end.
|
||||
|
||||
write_logs([BinaryData|T],BaseName,[Filename|Filenames]) ->
|
||||
Log = BaseName++Filename,
|
||||
file:write_file(Log, BinaryData),
|
||||
write_logs(T,BaseName,Filenames);
|
||||
|
||||
write_logs([],BaseName,_)-> ok.
|
||||
|
||||
dump() ->
|
||||
Dump = lists:map(fun(LogFile) ->
|
||||
Content = case file:open(LogFile,[read,raw]) of
|
||||
{ok, IO} ->
|
||||
Size = case file:read_file_info(LogFile) of
|
||||
{ok, FileInfo} -> FileInfo#file_info.size;
|
||||
_ -> ?LOG_FETCH_SIZE
|
||||
end,
|
||||
case Size>?MAX_FILE_SIZE of
|
||||
true -> io_lib:format("File ~s is too big: ~p bytes.",[LogFile, Size]);
|
||||
false ->
|
||||
if Size>?LOG_FETCH_SIZE ->
|
||||
file:position(IO, Size-?LOG_FETCH_SIZE),
|
||||
case file:read(IO, ?LOG_FETCH_SIZE) of
|
||||
{ok, Data1} -> Data1;
|
||||
Error1 -> io_lib:format("can not read log file (~s): ~p",[LogFile, Error1])
|
||||
end;
|
||||
true ->
|
||||
case file:read(IO, Size) of
|
||||
{ok, Data2} -> Data2;
|
||||
Error2 -> io_lib:format("can not read log file (~s): ~p",[LogFile, Error2])
|
||||
end
|
||||
end
|
||||
end;
|
||||
{error, Reason} ->
|
||||
io_lib:format("can not open log file (~s): ~p",[LogFile, Reason])
|
||||
end,
|
||||
zlib:gzip(list_to_binary(Content))
|
||||
end, [ejabberd_logs(), sasl_logs(), erl_crash()]),
|
||||
NodeState = get_node_state(),
|
||||
list_to_tuple([NodeState|Dump]).
|
||||
|
||||
ejabberd_logs() ->
|
||||
LogPath = case application:get_env(log_path) of
|
||||
{ok, Path} ->
|
||||
Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_LOG_PATH") of
|
||||
false -> ?LOG_PATH;
|
||||
Path -> Path
|
||||
end
|
||||
end.
|
||||
|
||||
sasl_logs() ->
|
||||
case os:getenv("SASL_LOG_PATH") of
|
||||
false -> filename:join([filename:dirname(ejabberd_logs()),"sasl.log"]);
|
||||
Path -> Path
|
||||
end.
|
||||
|
||||
erl_crash() ->
|
||||
LogsDir = filename:dirname(ejabberd_logs()),
|
||||
CrashDumpWildcard = filename:join([LogsDir,"erl_crash*dump"]),
|
||||
FileName = case filelib:wildcard(CrashDumpWildcard) of
|
||||
[Files] -> [LastFile|T] = lists:reverse([Files]),
|
||||
LastFile;
|
||||
_ -> case os:getenv("ERL_CRASH_DUMP") of
|
||||
false -> "erl_crash.dump";
|
||||
Path -> Path
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
proc_info(Pid) ->
|
||||
Info = process_info(Pid),
|
||||
lists:map(fun(Elem) ->
|
||||
List = proplists:get_value(Elem, Info),
|
||||
{Elem, size(term_to_binary(List))}
|
||||
end, [messages, dictionary])
|
||||
++ [X || X <- Info,
|
||||
lists:member(element(1,X),
|
||||
[heap_size,stack_size,reductions,links,status,initial_call,current_function])].
|
||||
|
||||
environment() ->
|
||||
{ok, KE} = application:get_key(kernel,env),
|
||||
{ok, EE} = application:get_key(ejabberd,env),
|
||||
Env = [{inetrc, os:getenv("ERL_INETRC")},
|
||||
{sopath, os:getenv("EJABBERD_SO_PATH")},
|
||||
{maxports, os:getenv("ERL_MAX_PORTS")},
|
||||
{maxtables, os:getenv("ERL_MAX_ETS_TABLES")},
|
||||
{crashdump, os:getenv("ERL_CRASH_DUMP")},
|
||||
{archdir, os:getenv("ARCHDIR")},
|
||||
{mnesia, mnesia:system_info(all)}],
|
||||
Args = [{args, init:get_arguments()}, {plain, init:get_plain_arguments()}],
|
||||
KE++EE++Env++Args.
|
||||
|
||||
memtop(N) ->
|
||||
E = lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Tab) -> {Tab, ets:info(Tab,memory)} end, ets:all()))),N),
|
||||
M = lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Tab) -> {Tab, mnesia:table_info(Tab,memory)} end, mnesia:system_info(tables)))),N),
|
||||
E++M.
|
||||
|
||||
maxmsgqueue() ->
|
||||
lists:max(lists:map(fun(Pid) -> proplists:get_value(message_queue_len,process_info(Pid)) end, erlang:processes())).
|
||||
|
||||
msgqueue(N) ->
|
||||
lists:filter(fun(L) -> proplists:get_value(message_queue_len, L) > N
|
||||
end, lists:map(fun(Pid) -> process_info(Pid) end, erlang:processes())).
|
||||
|
||||
%lists:sublist(lists:reverse(lists:keysort(2,lists:map(fun(Pid) -> {E,L} = process_info(Pid, dictionary), {E,length(L)} end, erlang:processes()))), 10)
|
||||
|
||||
%%Entry point to invoke mod_support via command line.
|
||||
%%Example: erl -sname debug@localhost -s mod_support report ejabberd@localhost
|
||||
%%See issue #TECH-286.
|
||||
report(Node) ->
|
||||
[NodeId|T]=Node,
|
||||
UploadResult = force_load_code_into_node(NodeId, ?MODULE),
|
||||
case UploadResult of
|
||||
ok -> NodeState = rpc:call(NodeId,mod_support,get_node_state,[],?RPC_TIMEOUT),
|
||||
Dump = rpc:call(NodeId,mod_support,dump,[],?RPC_TIMEOUT),
|
||||
BaseName = get_base_name(),
|
||||
%%{Data,EjabberdLog,SaslLog,ECrash} = Dump,
|
||||
write_logs(tuple_to_list(Dump),BaseName,["_memory.bin",
|
||||
"_ejabberd.log.gz",
|
||||
"_sasl.log.gz",
|
||||
"_erlang_crash_dump.log.gz"]),
|
||||
error_logger:info_msg("State in node ~p was written to log~n",[NodeId]),
|
||||
error_logger:info_msg("Unloading module ~s from node ~p. ",[?MODULE,NodeId]),
|
||||
force_unload_code_from_node(NodeId, ?MODULE);
|
||||
_ -> error_logger:info_msg("Error uploading module ~s from node ~p~n",[?MODULE,NodeId])
|
||||
end.
|
||||
|
||||
%%Load Module into the ejabberd Node specified.
|
||||
force_load_code_into_node(Node, Module) ->
|
||||
CodeFile = code:where_is_file(atom_to_list(Module)++".beam"),
|
||||
case file:read_file(CodeFile) of
|
||||
{ok, Code} ->
|
||||
rpc:call(Node, code, purge, [Module], ?RPC_TIMEOUT),
|
||||
rpc:call(Node, code, delete, [Module], ?RPC_TIMEOUT),
|
||||
case rpc:call(Node, code, load_binary, [Module, CodeFile, Code], ?RPC_TIMEOUT) of
|
||||
{module, _} ->
|
||||
error_logger:info_msg("Loading ~s module into ~p : success ~n", [Module,Node]),
|
||||
rpc:block_call(Node, Module, is_loaded, [], ?RPC_TIMEOUT);
|
||||
{error, badfile} ->
|
||||
error_logger:info_msg("Loading ~s module into ~p : incorrect format ~n", [Module,Node]),
|
||||
{error, badfile};
|
||||
{error, not_purged} ->
|
||||
% this should never happen anyway..
|
||||
error_logger:info_msg("Loading ~s module into ~p : old code already exists ~n", [Module,Node]),
|
||||
{error, not_purged};
|
||||
{badrpc, Reason} ->
|
||||
error_logger:info_msg("Loading ~s module into ~p: badrpc ~p ~n", [Module,Node,Reason]),
|
||||
{badrpc, Reason}
|
||||
end;
|
||||
Error ->
|
||||
error_logger:error_msg("Cannot read module file ~s ~p : ~p ~n", [Module, CodeFile, Error]),
|
||||
Error
|
||||
end.
|
||||
|
||||
%%Unload erlang Module from the Node specified. Used to ensure cleanup after rpc calls.
|
||||
force_unload_code_from_node(Node, Module) ->
|
||||
rpc:call(Node, code, purge, [Module], ?RPC_TIMEOUT),
|
||||
rpc:call(Node, code, delete, [Module], ?RPC_TIMEOUT).
|
||||
|
||||
%%Retrieve system state and pack it into Data
|
||||
%%TODO enhance state info. See #TECH-286.
|
||||
get_node_state() ->
|
||||
Mem = erlang:memory(),
|
||||
Ets = lists:map(fun(Tab) -> ets:info(Tab) end, ets:all()),
|
||||
Mnesia = lists:map(fun(Tab) -> mnesia:table_info(Tab,all) end, mnesia:system_info(tables)),
|
||||
Procs = lists:map(fun(Pid) -> proc_info(Pid) end, erlang:processes()),
|
||||
Data = term_to_binary({Mem, Ets, Mnesia, Procs}).
|
||||
|
||||
crash_dump() ->
|
||||
SystemInfo = [erlang:system_info(X) || X<-[info,loaded,procs]],
|
||||
[zlib:gzip(list_to_binary(lists:flatten(SystemInfo)))].
|
||||
|
||||
crash_dump(Node) ->
|
||||
[NodeId|T]=Node,
|
||||
UploadResult = force_load_code_into_node(NodeId, ?MODULE),
|
||||
case UploadResult of
|
||||
ok -> Dump = rpc:call(NodeId,mod_support,crash_dump,[],?RPC_TIMEOUT),
|
||||
BaseName = get_base_name(),
|
||||
write_logs(Dump,BaseName,["_realtime_crash_dump.gz"]),
|
||||
error_logger:info_msg("Unloading module ~s from node ~p. ",[?MODULE,NodeId]),
|
||||
force_unload_code_from_node(NodeId, ?MODULE);
|
||||
_ -> error_logger:info_msg("Error uploading module ~s from node ~p~n",[?MODULE,NodeId])
|
||||
end.
|
@ -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",
|
||||
|
@ -169,7 +169,7 @@ start_link(Host, Opts) ->
|
||||
|
||||
init([Host, Opts]) ->
|
||||
State = parse_options(Host, Opts),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, parallel),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
|
||||
|
876
src/mod_xmlrpc.erl
Normal file
876
src/mod_xmlrpc.erl
Normal file
@ -0,0 +1,876 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_xmlrpc.erl
|
||||
%%% Author : Badlop / Mickael Remond / Christophe Romain
|
||||
%%% Purpose : XML-RPC server
|
||||
%%% Created :
|
||||
%%% Id :
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%%/***************************************************************************
|
||||
%%% * *
|
||||
%%% * This program is free software; you can redistribute it and/or modify *
|
||||
%%% * it under the terms of the GNU General Public License as published by *
|
||||
%%% * the Free Software Foundation; either version 2 of the License, or *
|
||||
%%% * (at your option) any later version. *
|
||||
%%% * *
|
||||
%%% ***************************************************************************/
|
||||
%%%
|
||||
%%%
|
||||
%%% MOD_XMLRPC - an XML-RPC server module for ejabberd
|
||||
%%%
|
||||
%%% v0.5 - 17 March 2008
|
||||
%%%
|
||||
%%% http://ejabberd.jabber.ru/mod_xmlrpc
|
||||
%%%
|
||||
%%% (C) 2005, Badlop
|
||||
%%% 2006, Process-one
|
||||
%%% 2007, Process-one
|
||||
%%% 2008, Process-one
|
||||
%%%
|
||||
%%% Changelog:
|
||||
%%%
|
||||
%%% 0.7 - 02 April 2009 - cromain
|
||||
%%% - add user nick change
|
||||
%%%
|
||||
%%% 0.6 - 02 June 2008 - cromain
|
||||
%%% - add user existance checking
|
||||
%%% - improve parameter checking
|
||||
%%% - allow orderless parameter
|
||||
%%%
|
||||
%%% 0.5 - 17 March 2008 - cromain
|
||||
%%% - add user changing and higher level methods
|
||||
%%%
|
||||
%%% 0.4 - 18 February 2008 - cromain
|
||||
%%% - add roster handling
|
||||
%%% - add message sending
|
||||
%%% - code and api clean-up
|
||||
%%%
|
||||
%%% 0.3 - 18 October 2007 - cromain
|
||||
%%% - presence improvement
|
||||
%%% - add new functionality
|
||||
%%%
|
||||
%%% 0.2 - 4 March 2006 - mremond
|
||||
%%% - Code clean-up
|
||||
%%% - Made it compatible with current ejabberd SVN version
|
||||
%%%
|
||||
%%% 0.1.2 - 28 December 2005
|
||||
%%% - Now compatible with ejabberd 1.0.0
|
||||
%%% - The XMLRPC server is started only once, not once for every virtual host
|
||||
%%% - Added comments for handlers. Every available handler must be explained
|
||||
%%%
|
||||
|
||||
-module(mod_xmlrpc).
|
||||
-author('Process-one').
|
||||
-vsn('0.6').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
handler/2,
|
||||
link_contacts/5,
|
||||
unlink_contacts/3,
|
||||
loop/1,
|
||||
stop/1]).
|
||||
|
||||
-export([add_rosteritem/6]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-ifdef(EJABBERD1).
|
||||
-record(session, {sid, usr, us, priority}). %% ejabberd 1.1.x
|
||||
-else.
|
||||
-record(session, {sid, usr, us, priority, info}). %% ejabberd 2.0.x
|
||||
-endif.
|
||||
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_xmlrpc).
|
||||
-define(PORT, 4560).
|
||||
-define(TIMEOUT, 5000).
|
||||
|
||||
%% -----------------------------
|
||||
%% Module interface
|
||||
%% -----------------------------
|
||||
|
||||
start(_Host, Opts) ->
|
||||
case whereis(?PROCNAME) of
|
||||
undefined ->
|
||||
%% get options
|
||||
Port = gen_mod:get_opt(port, Opts, ?PORT),
|
||||
MaxSessions = 10,
|
||||
Timeout = gen_mod:get_opt(timeout, Opts, ?TIMEOUT),
|
||||
Handler = {mod_xmlrpc, handler},
|
||||
State = tryit,
|
||||
|
||||
%% TODO: this option gives
|
||||
%% error_info: {function_clause,[{gen_tcp,mod,[{ip,{127,0,0,1}}]},
|
||||
%%case gen_mod:get_opt(listen_all, Opts, false) of
|
||||
%% true -> Ip = all;
|
||||
%% false -> Ip = {127, 0, 0, 1}
|
||||
%%end,
|
||||
Ip = all,
|
||||
|
||||
%% start the XML-RPC server
|
||||
{ok, Pid} = xmlrpc:start_link(Ip, Port, MaxSessions, Timeout, Handler, State),
|
||||
|
||||
%% start the loop process
|
||||
register(?PROCNAME, spawn(?MODULE, loop, [Pid])),
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
loop(Pid) ->
|
||||
receive
|
||||
stop ->
|
||||
xmlrpc:stop(Pid)
|
||||
end.
|
||||
|
||||
stop(_Host) ->
|
||||
case whereis(?PROCNAME) of
|
||||
undefined ->
|
||||
ok;
|
||||
_Pid ->
|
||||
?PROCNAME ! stop,
|
||||
unregister(?PROCNAME)
|
||||
end.
|
||||
|
||||
|
||||
%% -----------------------------
|
||||
%% Handlers
|
||||
%% -----------------------------
|
||||
|
||||
handler(tryit, Call) ->
|
||||
try handler(notry, Call) of
|
||||
Result -> Result
|
||||
catch
|
||||
A:B ->
|
||||
?ERROR_MSG("Problem '~p' in~nCall: ~p~nError: ~p", [A, Call, B]),
|
||||
{false, {response, [-100]}}
|
||||
end;
|
||||
|
||||
% Call: Arguments: Returns:
|
||||
|
||||
%% .............................
|
||||
%% Debug
|
||||
|
||||
%% echothis String String
|
||||
handler(_State, {call, echothis, [A]}) ->
|
||||
{false, {response, [A]}};
|
||||
|
||||
%% multhis struct[{a, Integer}, {b, Integer}] Integer
|
||||
handler(_State, {call, multhis, [{struct, Struct}]}) ->
|
||||
[{a, A}, {b, B}] = lists:sort(Struct),
|
||||
{false, {response, [A*B]}};
|
||||
|
||||
%% .............................
|
||||
%% User administration
|
||||
|
||||
%% create_account struct[{user, String}, {server, Server}, {password, String}] Integer
|
||||
handler(_State, {call, create_account, [{struct, Struct}]}) ->
|
||||
[{password, P}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:try_register(U, S, P) of
|
||||
{atomic, ok} ->
|
||||
{false, {response, [0]}};
|
||||
{atomic, exists} ->
|
||||
{false, {response, [409]}};
|
||||
_ ->
|
||||
{false, {response, [1]}}
|
||||
end;
|
||||
|
||||
%% delete_account struct[{user, String}, {server, Server}] Integer
|
||||
handler(_State, {call, delete_account, [{struct, Struct}]}) ->
|
||||
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||
Fun = fun() -> ejabberd_auth:remove_user(U, S) end,
|
||||
user_action(U, S, Fun, ok);
|
||||
|
||||
%% change_password struct[{user, String}, {server, String}, {newpass, String}] Integer
|
||||
handler(_State, {call, change_password, [{struct, Struct}]}) ->
|
||||
[{newpass, P}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
Fun = fun() -> ejabberd_auth:set_password(U, S, P) end,
|
||||
user_action(U, S, Fun, ok);
|
||||
|
||||
%% set_nickname struct[{user, String}, {server, String}, {nick, String}] Integer
|
||||
handler(_State, {call, set_nickname, [{struct, Struct}]}) ->
|
||||
[{nick, N}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
Fun = fun() -> case mod_vcard:process_sm_iq(
|
||||
{jid, U, S, "", U, S, ""},
|
||||
{jid, U, S, "", U, S, ""},
|
||||
{iq, "", set, "", "en",
|
||||
{xmlelement, "vCard",
|
||||
[{"xmlns", "vcard-temp"}], [
|
||||
{xmlelement, "NICKNAME", [], [{xmlcdata, N}]}
|
||||
]
|
||||
}}) of
|
||||
{iq, [], result, [], _, []} -> ok;
|
||||
_ -> error
|
||||
end
|
||||
end,
|
||||
user_action(U, S, Fun, ok);
|
||||
|
||||
%% set_rosternick struct[{user, String}, {server, String}, {nick, String}] Integer
|
||||
handler(_State, {call, set_rosternick, [{struct, Struct}]}) ->
|
||||
[{nick, N}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
Fun = fun() -> change_rosternick(U, S, N) end,
|
||||
user_action(U, S, Fun, ok);
|
||||
|
||||
%% add_rosteritem struct[{user, String}, {server, String},
|
||||
%% {jid, String}, {group, String}, {nick, String}, {subs, String}] Integer
|
||||
handler(_State, {call, add_rosteritem, [{struct, Struct}]}) ->
|
||||
[{group, G},{jid, JID},{nick, N},{server, S},{subs, Subs},{user, U}] = lists:sort(Struct),
|
||||
Fun = fun() -> add_rosteritem(U, S, JID, N, G, Subs) end,
|
||||
user_action(U, S, Fun, {atomic, ok});
|
||||
|
||||
%% link_contacts struct[{jid1, String}, {nick1, String}, {jid2, String}, {nick2, String}] Integer
|
||||
handler(_State, {call, link_contacts, [{struct, Struct}]}) ->
|
||||
[{jid1, JID1}, {jid2, JID2}, {nick1, Nick1}, {nick2, Nick2}] = lists:sort(Struct),
|
||||
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
|
||||
{true, true} ->
|
||||
case link_contacts(JID1, Nick1, JID2, Nick2) of
|
||||
{atomic, ok} ->
|
||||
{false, {response, [0]}};
|
||||
_ ->
|
||||
{false, {response, [1]}}
|
||||
end;
|
||||
_ ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% delete_rosteritem struct[{user, String}, {server, String}, {jid, String}] Integer
|
||||
handler(_State, {call, delete_rosteritem, [{struct, Struct}]}) ->
|
||||
[{jid, JID}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
Fun = fun() -> del_rosteritem(U, S, JID) end,
|
||||
user_action(U, S, Fun, {atomic, ok});
|
||||
|
||||
%% unlink_contacts struct[{jid1, String}, {jid2, String}] Integer
|
||||
handler(_State, {call, unlink_contacts, [{struct, Struct}]}) ->
|
||||
[{jid1, JID1}, {jid2, JID2}] = lists:sort(Struct),
|
||||
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||
case {ejabberd_auth:is_user_exists(U1, S1), ejabberd_auth:is_user_exists(U2, S2)} of
|
||||
{true, true} ->
|
||||
case unlink_contacts(JID1, JID2) of
|
||||
{atomic, ok} ->
|
||||
{false, {response, [0]}};
|
||||
_ ->
|
||||
{false, {response, [1]}}
|
||||
end;
|
||||
_ ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% get_roster struct[{user, String}, {server, String}]
|
||||
%% array[struct[{jid, String}, {group, String}, {nick, String},
|
||||
%% {subscription, String}, {pending, String}]]
|
||||
handler(_State, {call, get_roster, [{struct, Struct}]}) ->
|
||||
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
Roster = format_roster(get_roster(U, S)),
|
||||
{false, {response, [{array, Roster}]}};
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% get_roster_with_presence struct[{user, String}, {server, String}]
|
||||
%% array[struct[{jid, String}, {resource, String}, {group, String}, {nick, String},
|
||||
%% {subscription, String}, {pending, String},
|
||||
%% {show, String}, {status, String}]]
|
||||
handler(_State, {call, get_roster_with_presence, [{struct, Struct}]}) ->
|
||||
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
Roster = format_roster_with_presence(get_roster(U, S)),
|
||||
{false, {response, [{array, Roster}]}};
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% get_presence struct[{user, String}, {server, String}]
|
||||
%% array[struct[{jid, String}, {show, String}, {status, String}]]
|
||||
handler(_State, {call, get_presence, [{struct, Struct}]}) ->
|
||||
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
{Resource, Show, Status} = get_presence(U, S),
|
||||
FullJID = case Resource of
|
||||
[] ->
|
||||
lists:flatten([U,"@",S]);
|
||||
_ ->
|
||||
lists:flatten([U,"@",S,"/",Resource])
|
||||
end,
|
||||
R = {struct, [{jid, FullJID}, {show, Show}, {status, Status} ]},
|
||||
{false, {response, [R]}};
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% get_resources struct[{user, String}, {server, String}]
|
||||
%% array[String]
|
||||
handler(_State, {call, get_resources, [{struct, Struct}]}) ->
|
||||
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
Resources = get_resources(U, S),
|
||||
{false, {response, [{array, Resources}]}};
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% send_chat struct[{from, String}, {to, String}, {body, String}]
|
||||
%% Integer
|
||||
handler(_State, {call, send_chat, [{struct, Struct}]}) ->
|
||||
[{body, Msg}, {from, FromJID}, {to, ToJID}] = lists:sort(Struct),
|
||||
From = jlib:string_to_jid(FromJID),
|
||||
To = jlib:string_to_jid(ToJID),
|
||||
Stanza = {xmlelement, "message", [{"type", "chat"}],
|
||||
[{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
|
||||
ejabberd_router:route(From, To, Stanza),
|
||||
{false, {response, [0]}};
|
||||
|
||||
%% send_message struct[{from, String}, {to, String}, {subject, String}, {body, String}]
|
||||
%% Integer
|
||||
handler(_State, {call, send_message, [{struct, Struct}]}) ->
|
||||
[{body, Msg}, {from, FromJID}, {subject, Sub}, {to, ToJID}] = lists:sort(Struct),
|
||||
From = jlib:string_to_jid(FromJID),
|
||||
To = jlib:string_to_jid(ToJID),
|
||||
Stanza = {xmlelement, "message", [{"type", "normal"}],
|
||||
[{xmlelement, "subject", [], [{xmlcdata, Sub}]},
|
||||
{xmlelement, "body", [], [{xmlcdata, Msg}]}]},
|
||||
ejabberd_router:route(From, To, Stanza),
|
||||
{false, {response, [0]}};
|
||||
|
||||
%% send_stanza struct[{from, String}, {to, String}, {stanza, String}]
|
||||
%% Integer
|
||||
handler(_State, {call, send_stanza, [{struct, Struct}]}) ->
|
||||
[{from, FromJID}, {stanza, StanzaStr}, {to, ToJID}] = lists:sort(Struct),
|
||||
case xml_stream:parse_element(StanzaStr) of
|
||||
{error, _} ->
|
||||
{false, {response, [1]}};
|
||||
Stanza ->
|
||||
{xmlelement, _, Attrs, _} = Stanza,
|
||||
From = jlib:string_to_jid(proplists:get_value("from", Attrs, FromJID)),
|
||||
To = jlib:string_to_jid(proplists:get_value("to", Attrs, ToJID)),
|
||||
ejabberd_router:route(From, To, Stanza),
|
||||
{false, {response, [0]}}
|
||||
end;
|
||||
|
||||
%% rename_account struct[{user, String}, {server, String}, {newuser, String}, {newserver, String}]
|
||||
%% Integer
|
||||
handler(_State, {call, rename_account, [{struct, Struct}]}) ->
|
||||
[{newserver, NS}, {newuser, NU}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
case ejabberd_auth:get_password(U, S) of
|
||||
false ->
|
||||
{false, {response, [1]}};
|
||||
Password ->
|
||||
case ejabberd_auth:try_register(NU, NS, Password) of
|
||||
{atomic, ok} ->
|
||||
OldJID = jlib:jid_to_string({U, S, ""}),
|
||||
NewJID = jlib:jid_to_string({NU, NS, ""}),
|
||||
Roster = get_roster(U, S),
|
||||
lists:foreach(fun(#roster{jid={RU, RS, RE}, name=Nick, groups=Groups}) ->
|
||||
NewGroup = extract_group(Groups),
|
||||
{NewNick, Group} = case lists:filter(fun(#roster{jid={PU, PS, _}}) ->
|
||||
(PU == U) and (PS == S)
|
||||
end, get_roster(RU, RS)) of
|
||||
[#roster{name=OldNick, groups=OldGroups}|_] -> {OldNick, extract_group(OldGroups)};
|
||||
[] -> {NU, []}
|
||||
end,
|
||||
JIDStr = jlib:jid_to_string({RU, RS, RE}),
|
||||
link_contacts(NewJID, NewNick, NewGroup, JIDStr, Nick, Group),
|
||||
unlink_contacts(OldJID, JIDStr)
|
||||
end, Roster),
|
||||
ejabberd_auth:remove_user(U, S),
|
||||
{false, {response, [0]}};
|
||||
{atomic, exists} ->
|
||||
{false, {response, [409]}};
|
||||
_ ->
|
||||
{false, {response, [1]}}
|
||||
end
|
||||
end;
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% add_contacts struct[{user, String}, {server, String},
|
||||
%% array[struct[{jid, String}, {group, String}, {nick, String}]]]
|
||||
%% Integer
|
||||
handler(_State, {call, add_contacts, [{struct, Struct}]}) ->
|
||||
[{array, Contacts}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
JID1 = jlib:jid_to_string({U, S, ""}),
|
||||
Response = lists:foldl(fun({struct, Struct2}, Acc) ->
|
||||
[{group, Group}, {jid, JID2}, {nick, Nick}] = lists:sort(Struct2),
|
||||
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||
case ejabberd_auth:is_user_exists(PU, PS) of
|
||||
true ->
|
||||
case link_contacts(JID1, "", "", JID2, Nick, Group) of
|
||||
{atomic, ok} -> Acc;
|
||||
_ -> 1
|
||||
end;
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end, 0, element(2, Contacts)),
|
||||
{false, {response, [Response]}};
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% remove_contacts struct[{user, String}, {server, String}, array[String]]
|
||||
%% Integer
|
||||
handler(_State, {call, remove_contacts, [{struct, Struct}]}) ->
|
||||
[{array, Contacts}, {server, S}, {user, U}] = lists:sort(Struct),
|
||||
case ejabberd_auth:is_user_exists(U, S) of
|
||||
true ->
|
||||
JID1 = jlib:jid_to_string({U, S, ""}),
|
||||
Response = lists:foldl(fun(JID2, Acc) ->
|
||||
{PU, PS, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||
case ejabberd_auth:is_user_exists(PU, PS) of
|
||||
true ->
|
||||
case unlink_contacts(JID1, JID2) of
|
||||
{atomic, ok} -> Acc;
|
||||
_ -> 1
|
||||
end;
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end, 0, element(2, Contacts)),
|
||||
{false, {response, [Response]}};
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end;
|
||||
|
||||
%% check_users_registration array[struct[{user, String}, {server, String}]]
|
||||
%% array[struct[{user, String}, {server, String}, {status, Integer}]]
|
||||
handler(_State, {call, check_users_registration, [{array, Users}]}) ->
|
||||
Response = lists:map(fun({struct, Struct}) ->
|
||||
[{server, S}, {user, U}] = lists:sort(Struct),
|
||||
Registered = case ejabberd_auth:is_user_exists(U, S) of
|
||||
true -> 1;
|
||||
false -> 0
|
||||
end,
|
||||
{struct, [{user, U}, {server, S}, {status, Registered}]}
|
||||
end, Users),
|
||||
{false, {response, [{array, Response}]}};
|
||||
|
||||
|
||||
%% If no other guard matches
|
||||
handler(_State, Payload) ->
|
||||
FaultString = lists:flatten(io_lib:format("Unknown call: ~p", [Payload])),
|
||||
{false, {response, {fault, -1, FaultString}}}.
|
||||
|
||||
|
||||
%% -----------------------------
|
||||
%% Internal roster handling
|
||||
%% -----------------------------
|
||||
|
||||
get_roster(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
ejabberd_hooks:run_fold(roster_get, LServer, [], [{LUser, LServer}]).
|
||||
|
||||
change_rosternick(User, Server, Nick) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
LJID = {LUser, LServer, []},
|
||||
JID = jlib:jid_to_string(LJID),
|
||||
Push = fun(Subscription) ->
|
||||
jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
|
||||
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
|
||||
[{xmlelement, "item", [{"jid", JID}, {"name", Nick}, {"subscription", atom_to_list(Subscription)}],
|
||||
[]}]}]})
|
||||
end,
|
||||
Result = case roster_backend(Server) of
|
||||
mnesia ->
|
||||
%% XXX This way of doing can not work with s2s
|
||||
mnesia:transaction(
|
||||
fun() ->
|
||||
lists:foreach(fun(Roster) ->
|
||||
{U, S} = Roster#roster.us,
|
||||
mnesia:write(Roster#roster{name = Nick}),
|
||||
lists:foreach(fun(R) ->
|
||||
UJID = jlib:make_jid(U, S, R),
|
||||
ejabberd_router:route(UJID, UJID, Push(Roster#roster.subscription))
|
||||
end, get_resources(U, S))
|
||||
end, mnesia:match_object(#roster{jid = LJID, _ = '_'}))
|
||||
end);
|
||||
odbc ->
|
||||
%%% XXX This way of doing does not work with several domains
|
||||
ejabberd_odbc:sql_transaction(Server,
|
||||
fun() ->
|
||||
SNick = ejabberd_odbc:escape(Nick),
|
||||
SJID = ejabberd_odbc:escape(JID),
|
||||
ejabberd_odbc:sql_query_t(
|
||||
["update rosterusers"
|
||||
" set nick='", SNick, "'"
|
||||
" where jid='", SJID, "';"]),
|
||||
case ejabberd_odbc:sql_query_t(
|
||||
["select username from rosterusers"
|
||||
" where jid='", SJID, "'"
|
||||
" and subscription = 'B';"]) of
|
||||
{selected, ["username"], Users} ->
|
||||
lists:foreach(fun({RU}) ->
|
||||
lists:foreach(fun(R) ->
|
||||
UJID = jlib:make_jid(RU, Server, R),
|
||||
ejabberd_router:route(UJID, UJID, Push(both))
|
||||
end, get_resources(RU, Server))
|
||||
end, Users);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end);
|
||||
none ->
|
||||
{error, no_roster}
|
||||
end,
|
||||
case Result of
|
||||
{atomic, ok} -> ok;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
add_rosteritem(User, Server, JID, Nick, Group, Subscription) ->
|
||||
add_rosteritem(User, Server, JID, Nick, Group, Subscription, true).
|
||||
add_rosteritem(User, Server, JID, Nick, Group, Subscription, Push) ->
|
||||
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
||||
LJID = {RU,RS,[]},
|
||||
Groups = case Group of
|
||||
[] -> [];
|
||||
_ -> [Group]
|
||||
end,
|
||||
Roster = #roster{
|
||||
usj = {User,Server,LJID},
|
||||
us = {User,Server},
|
||||
jid = LJID,
|
||||
name = Nick,
|
||||
ask = none,
|
||||
subscription = list_to_atom(Subscription),
|
||||
groups = Groups},
|
||||
Result =
|
||||
case roster_backend(Server) of
|
||||
mnesia ->
|
||||
mnesia:transaction(fun() ->
|
||||
case mnesia:read({roster,{User,Server,LJID}}) of
|
||||
[#roster{subscription=both}] ->
|
||||
already_added;
|
||||
_ ->
|
||||
mnesia:write(Roster)
|
||||
end
|
||||
end);
|
||||
odbc ->
|
||||
%% MREMOND: TODO: check if already_added
|
||||
case ejabberd_odbc:sql_transaction(Server,
|
||||
fun() ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
case ejabberd_odbc:sql_query_t(
|
||||
["select username from rosterusers "
|
||||
" where username='", Username, "' "
|
||||
" and jid='", SJID,
|
||||
"' and subscription = 'B';"]) of
|
||||
{selected, ["username"],[]} ->
|
||||
ItemVals = record_to_string(Roster),
|
||||
ItemGroups = groups_to_string(Roster),
|
||||
odbc_queries:update_roster(Server, Username,
|
||||
SJID, ItemVals,
|
||||
ItemGroups);
|
||||
_ ->
|
||||
already_added
|
||||
end
|
||||
end) of
|
||||
{atomic, already_added} -> {atomic, already_added};
|
||||
{atomic, _} -> {atomic, ok};
|
||||
Error -> Error
|
||||
end;
|
||||
none ->
|
||||
{error, no_roster}
|
||||
end,
|
||||
case {Result, Push} of
|
||||
{{atomic, already_added}, _} -> ok; %% No need for roster push
|
||||
{{atomic, ok}, true} -> roster_push(User, Server, JID, Nick, Subscription);
|
||||
{{error, no_roster}, true} -> roster_push(User, Server, JID, Nick, Subscription);
|
||||
{{atomic, ok}, false} -> ok;
|
||||
_ -> error
|
||||
end,
|
||||
Result.
|
||||
|
||||
del_rosteritem(User, Server, JID) ->
|
||||
del_rosteritem(User, Server, JID, true).
|
||||
del_rosteritem(User, Server, JID, Push) ->
|
||||
{RU, RS, _} = jlib:jid_tolower(jlib:string_to_jid(JID)),
|
||||
LJID = {RU,RS,[]},
|
||||
Result = case roster_backend(Server) of
|
||||
mnesia ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:delete({roster, {User,Server,LJID}})
|
||||
end);
|
||||
odbc ->
|
||||
case ejabberd_odbc:sql_transaction(Server, fun() ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
odbc_queries:del_roster(Server, Username, SJID)
|
||||
end) of
|
||||
{atomic, _} -> {atomic, ok};
|
||||
Error -> Error
|
||||
end;
|
||||
none ->
|
||||
{error, no_roster}
|
||||
end,
|
||||
case {Result, Push} of
|
||||
{{atomic, ok}, true} -> roster_push(User, Server, JID, "", "remove");
|
||||
{{error, no_roster}, true} -> roster_push(User, Server, JID, "", "remove");
|
||||
{{atomic, ok}, false} -> ok;
|
||||
_ -> error
|
||||
end,
|
||||
Result.
|
||||
|
||||
link_contacts(JID1, Nick1, JID2, Nick2) ->
|
||||
link_contacts(JID1, Nick1, JID2, Nick2, true).
|
||||
link_contacts(JID1, Nick1, JID2, Nick2, Push) ->
|
||||
link_contacts(JID1, Nick1, [], JID2, Nick2, [], Push).
|
||||
|
||||
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2) ->
|
||||
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, true).
|
||||
link_contacts(JID1, Nick1, Group1, JID2, Nick2, Group2, Push) ->
|
||||
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||
case add_rosteritem(U1, S1, JID2, Nick2, Group1, "both", Push) of
|
||||
{atomic, ok} -> add_rosteritem(U2, S2, JID1, Nick1, Group2, "both", Push);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
unlink_contacts(JID1, JID2) ->
|
||||
unlink_contacts(JID1, JID2, true).
|
||||
unlink_contacts(JID1, JID2, Push) ->
|
||||
{U1, S1, _} = jlib:jid_tolower(jlib:string_to_jid(JID1)),
|
||||
{U2, S2, _} = jlib:jid_tolower(jlib:string_to_jid(JID2)),
|
||||
case del_rosteritem(U1, S1, JID2, Push) of
|
||||
{atomic, ok} -> del_rosteritem(U2, S2, JID1, Push);
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
roster_push(User, Server, JID, Nick, Subscription) ->
|
||||
LJID = jlib:make_jid(User, Server, ""),
|
||||
TJID = jlib:string_to_jid(JID),
|
||||
{TU, TS, _} = jlib:jid_tolower(TJID),
|
||||
Presence = {xmlelement, "presence", [{"type",
|
||||
case Subscription of
|
||||
"remove" -> "unsubscribed";
|
||||
"none" -> "unsubscribe";
|
||||
"both" -> "subscribed";
|
||||
_ -> "subscribe"
|
||||
end}], []},
|
||||
Item = case Nick of
|
||||
"" -> [{"jid", JID}, {"subscription", Subscription}];
|
||||
_ -> [{"jid", JID}, {"name", Nick}, {"subscription", Subscription}]
|
||||
end,
|
||||
Result = jlib:iq_to_xml(#iq{type = set, xmlns = ?NS_ROSTER, id = "push",
|
||||
sub_el = [{xmlelement, "query", [{"xmlns", ?NS_ROSTER}],
|
||||
[{xmlelement, "item", Item, []}]}]}),
|
||||
ejabberd_router:route(TJID, LJID, Presence),
|
||||
ejabberd_router:route(LJID, LJID, Result),
|
||||
lists:foreach(fun(Resource) ->
|
||||
UJID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_router:route(TJID, UJID, Presence),
|
||||
ejabberd_router:route(UJID, UJID, Result),
|
||||
case Subscription of
|
||||
"remove" -> none;
|
||||
_ ->
|
||||
lists:foreach(fun(TR) ->
|
||||
ejabberd_router:route(jlib:make_jid(TU, TS, TR), UJID,
|
||||
{xmlelement, "presence", [], []})
|
||||
end, get_resources(TU, TS))
|
||||
end
|
||||
end, [R || R <- get_resources(User, Server), Subscription =/= "remove"]).
|
||||
|
||||
roster_backend(Server) ->
|
||||
Modules = gen_mod:loaded_modules(Server),
|
||||
Mnesia = lists:member(mod_roster, Modules),
|
||||
Odbc = lists:member(mod_roster_odbc, Modules),
|
||||
if Mnesia -> mnesia;
|
||||
true ->
|
||||
if Odbc -> odbc;
|
||||
true -> none
|
||||
end
|
||||
end.
|
||||
|
||||
record_to_string(#roster{us = {User, _Server},
|
||||
jid = JID,
|
||||
name = Name,
|
||||
subscription = Subscription,
|
||||
ask = Ask,
|
||||
askmessage = AskMessage}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
||||
Nick = ejabberd_odbc:escape(Name),
|
||||
SSubscription = case Subscription of
|
||||
both -> "B";
|
||||
to -> "T";
|
||||
from -> "F";
|
||||
none -> "N"
|
||||
end,
|
||||
SAsk = case Ask of
|
||||
subscribe -> "S";
|
||||
unsubscribe -> "U";
|
||||
both -> "B";
|
||||
out -> "O";
|
||||
in -> "I";
|
||||
none -> "N"
|
||||
end,
|
||||
SAskMessage = ejabberd_odbc:escape(AskMessage),
|
||||
["'", Username, "',"
|
||||
"'", SJID, "',"
|
||||
"'", Nick, "',"
|
||||
"'", SSubscription, "',"
|
||||
"'", SAsk, "',"
|
||||
"'", SAskMessage, "',"
|
||||
"'N', '', 'item'"].
|
||||
|
||||
groups_to_string(#roster{us = {User, _Server},
|
||||
jid = JID,
|
||||
groups = Groups}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(JID))),
|
||||
%% Empty groups do not need to be converted to string to be inserted in
|
||||
%% the database
|
||||
lists:foldl(fun([], Acc) -> Acc;
|
||||
(Group, Acc) ->
|
||||
String = ["'", Username, "',"
|
||||
"'", SJID, "',"
|
||||
"'", ejabberd_odbc:escape(Group), "'"],
|
||||
[String|Acc]
|
||||
end, [], Groups).
|
||||
|
||||
%% Format roster items as a list of:
|
||||
%% [{struct, [{jid, "test@localhost"},{group, "Friends"},{nick, "Nicktest"}]}]
|
||||
format_roster([]) ->
|
||||
[];
|
||||
format_roster(Items) ->
|
||||
format_roster(Items, []).
|
||||
format_roster([], Structs) ->
|
||||
Structs;
|
||||
format_roster([#roster{jid=JID, name=Nick, groups=Group,
|
||||
subscription=Subs, ask=Ask}|Items], Structs) ->
|
||||
{User,Server,_Resource} = JID,
|
||||
Struct = {struct, [{jid,lists:flatten([User,"@",Server])},
|
||||
{group, extract_group(Group)},
|
||||
{nick, Nick},
|
||||
{subscription, atom_to_list(Subs)},
|
||||
{pending, atom_to_list(Ask)}
|
||||
]},
|
||||
format_roster(Items, [Struct|Structs]).
|
||||
|
||||
%% Format roster items as a list of:
|
||||
%% [{struct, [{jid, "test@localhost"}, {resource, "Messenger"}, {group, "Friends"},
|
||||
%% {nick, "Nicktest"},{show, "available"}, {status, "Currently at office"}]}]
|
||||
%% Note: If user is connected several times, only keep the resource with the
|
||||
%% highest non-negative priority
|
||||
format_roster_with_presence([]) ->
|
||||
[];
|
||||
format_roster_with_presence(Items) ->
|
||||
format_roster_with_presence(Items, []).
|
||||
format_roster_with_presence([], Structs) ->
|
||||
Structs;
|
||||
format_roster_with_presence([#roster{jid=JID, name=Nick, groups=Group,
|
||||
subscription=Subs, ask=Ask}|Items], Structs) ->
|
||||
{User,Server,_R} = JID,
|
||||
Presence = case Subs of
|
||||
both -> get_presence(User, Server);
|
||||
from -> get_presence(User, Server);
|
||||
_Other -> {"", "unavailable", ""}
|
||||
end,
|
||||
{Resource, Show, Status} =
|
||||
case Presence of
|
||||
{_R, "invisible", _S} -> {"", "unavailable", ""};
|
||||
_Status -> Presence
|
||||
end,
|
||||
Struct = {struct, [{jid,lists:flatten([User,"@",Server])},
|
||||
{resource, Resource},
|
||||
{group, extract_group(Group)},
|
||||
{nick, Nick},
|
||||
{subscription, atom_to_list(Subs)},
|
||||
{pending, atom_to_list(Ask)},
|
||||
{show, Show},
|
||||
{status, Status}
|
||||
]},
|
||||
format_roster_with_presence(Items, [Struct|Structs]).
|
||||
|
||||
extract_group([]) -> [];
|
||||
extract_group([Group|_Groups]) -> Group.
|
||||
|
||||
%% -----------------------------
|
||||
%% Internal session handling
|
||||
%% -----------------------------
|
||||
|
||||
%% This is inspired from ejabberd_sm.erl
|
||||
get_presence(User, Server) ->
|
||||
case get_sessions(User, Server) of
|
||||
[] ->
|
||||
{"", "unavailable", ""};
|
||||
Ss ->
|
||||
Session = hd(Ss),
|
||||
if Session#session.priority >= 0 ->
|
||||
Pid = element(2, Session#session.sid),
|
||||
%{_User, _Resource, Show, Status} = rpc:call(node(Pid), ejabberd_c2s, get_presence, [Pid]),
|
||||
{_User, Resource, Show, Status} = ejabberd_c2s:get_presence(Pid),
|
||||
{Resource, Show, Status};
|
||||
true ->
|
||||
{"", "unavailable", ""}
|
||||
end
|
||||
end.
|
||||
|
||||
get_resources(User, Server) ->
|
||||
lists:map(fun(S) -> element(3, S#session.usr)
|
||||
end, get_sessions(User, Server)).
|
||||
|
||||
get_sessions(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch mnesia:dirty_index_read(session, {LUser, LServer}, #session.us) of
|
||||
{'EXIT', _Reason} -> [];
|
||||
[] -> [];
|
||||
Result -> lists:reverse(lists:keysort(#session.priority, clean_session_list(Result)))
|
||||
end.
|
||||
|
||||
clean_session_list(Ss) ->
|
||||
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
||||
|
||||
clean_session_list([], Res) ->
|
||||
Res;
|
||||
clean_session_list([S], Res) ->
|
||||
[S | Res];
|
||||
clean_session_list([S1, S2 | Rest], Res) ->
|
||||
if
|
||||
S1#session.usr == S2#session.usr ->
|
||||
if
|
||||
S1#session.sid > S2#session.sid ->
|
||||
clean_session_list([S1 | Rest], Res);
|
||||
true ->
|
||||
clean_session_list([S2 | Rest], Res)
|
||||
end;
|
||||
true ->
|
||||
clean_session_list([S2 | Rest], [S1 | Res])
|
||||
end.
|
||||
|
||||
|
||||
%% -----------------------------
|
||||
%% Internal function pattern
|
||||
%% -----------------------------
|
||||
|
||||
user_action(User, Server, Fun, OK) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true ->
|
||||
case catch Fun() of
|
||||
OK ->
|
||||
{false, {response, [0]}};
|
||||
_ ->
|
||||
{false, {response, [1]}}
|
||||
end;
|
||||
false ->
|
||||
{false, {response, [404]}}
|
||||
end.
|
@ -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
|
||||
|
@ -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}}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}).
|
@ -27,6 +27,7 @@
|
||||
setopts/2,
|
||||
controlling_process/2,
|
||||
become_controller/2,
|
||||
change_controller/2,
|
||||
custom_receiver/1,
|
||||
reset_stream/1,
|
||||
change_shaper/2,
|
||||
@ -121,9 +122,19 @@
|
||||
start(XMPPDomain, Sid, Key, IP) ->
|
||||
?DEBUG("Starting session", []),
|
||||
case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of
|
||||
{ok, Pid} -> {ok, Pid};
|
||||
_ -> check_bind_module(XMPPDomain),
|
||||
{error, "Cannot start HTTP bind session"}
|
||||
{ok, Pid} ->
|
||||
{ok, Pid};
|
||||
{error, _} = Err ->
|
||||
case check_bind_module(XMPPDomain) of
|
||||
false ->
|
||||
{error, "Cannot start HTTP bind session"};
|
||||
true ->
|
||||
?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]),
|
||||
Err
|
||||
end;
|
||||
Exit ->
|
||||
?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]),
|
||||
{error, Exit}
|
||||
end.
|
||||
|
||||
start_link(Sid, Key, IP) ->
|
||||
@ -140,7 +151,18 @@ setopts({http_bind, FsmRef, _IP}, Opts) ->
|
||||
true ->
|
||||
gen_fsm:send_all_state_event(FsmRef, {activate, self()});
|
||||
_ ->
|
||||
ok
|
||||
case lists:member({active, false}, Opts) of
|
||||
true ->
|
||||
case catch gen_fsm:sync_send_all_state_event(
|
||||
FsmRef, deactivate_socket) of
|
||||
{'EXIT', _} ->
|
||||
{error, einval};
|
||||
Res ->
|
||||
Res
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
controlling_process(_Socket, _Pid) ->
|
||||
@ -152,6 +174,9 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
|
||||
|
||||
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
|
||||
become_controller(FsmRef, C2SPid).
|
||||
|
||||
reset_stream({http_bind, _FsmRef, _IP}) ->
|
||||
ok.
|
||||
|
||||
@ -170,7 +195,6 @@ sockname(_Socket) ->
|
||||
peername({http_bind, _FsmRef, IP}) ->
|
||||
{ok, IP}.
|
||||
|
||||
|
||||
%% Entry point for data coming from client through ejabberd HTTP server:
|
||||
process_request(Data, IP) ->
|
||||
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
|
||||
@ -192,12 +216,12 @@ process_request(Data, IP) ->
|
||||
"xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
|
||||
XmppDomain ->
|
||||
%% create new session
|
||||
Sid = sha:sha(term_to_binary({now(), make_ref()})),
|
||||
Sid = make_sid(),
|
||||
case start(XmppDomain, Sid, "", IP) of
|
||||
{error, _} ->
|
||||
{200, ?HEADER, "<body type='terminate' "
|
||||
{500, ?HEADER, "<body type='terminate' "
|
||||
"condition='internal-server-error' "
|
||||
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>BOSH module not started</body>"};
|
||||
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>Internal Server Error</body>"};
|
||||
{ok, Pid} ->
|
||||
handle_session_start(
|
||||
Pid, XmppDomain, Sid, Rid, Attrs,
|
||||
@ -223,10 +247,10 @@ process_request(Data, IP) ->
|
||||
handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
|
||||
StreamStart, IP);
|
||||
{size_limit, Sid} ->
|
||||
case mnesia:dirty_read({http_bind, Sid}) of
|
||||
[] ->
|
||||
case get_session(Sid) of
|
||||
{error, _} ->
|
||||
{404, ?HEADER, ""};
|
||||
[#http_bind{pid = FsmRef}] ->
|
||||
{ok, #http_bind{pid = FsmRef}} ->
|
||||
gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
|
||||
{200, ?HEADER, "<body type='terminate' "
|
||||
"condition='undefined-condition' "
|
||||
@ -282,7 +306,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
|
||||
end,
|
||||
XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
|
||||
?DEBUG("Create session: ~p", [Sid]),
|
||||
mnesia:transaction(
|
||||
mnesia:async_dirty(
|
||||
fun() ->
|
||||
mnesia:write(
|
||||
#http_bind{id = Sid,
|
||||
@ -340,6 +364,7 @@ init([Sid, Key, IP]) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_event({become_controller, C2SPid}, StateName, StateData) ->
|
||||
erlang:monitor(process, C2SPid),
|
||||
case StateData#state.input of
|
||||
cancel ->
|
||||
{next_state, StateName, StateData#state{
|
||||
@ -404,6 +429,14 @@ handle_sync_event({stop,close}, _From, _StateName, StateData) ->
|
||||
handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) ->
|
||||
Reply = ok,
|
||||
{stop, normal, Reply, StateData};
|
||||
handle_sync_event(deactivate_socket, _From, StateName, StateData) ->
|
||||
%% Input = case StateData#state.input of
|
||||
%% cancel ->
|
||||
%% queue:new();
|
||||
%% Q ->
|
||||
%% Q
|
||||
%% end,
|
||||
{reply, ok, StateName, StateData#state{waiting_input = false}};
|
||||
handle_sync_event({stop,Reason}, _From, _StateName, StateData) ->
|
||||
?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
|
||||
Reply = ok,
|
||||
@ -537,6 +570,9 @@ handle_info({timeout, ShaperTimer, _}, StateName,
|
||||
#state{shaper_timer = ShaperTimer} = StateData) ->
|
||||
{next_state, StateName, StateData#state{shaper_timer = undefined}};
|
||||
|
||||
handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName,
|
||||
#state{waiting_input = C2SPid} = StateData) ->
|
||||
{stop, normal, StateData};
|
||||
handle_info(_, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
@ -804,10 +840,10 @@ handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
|
||||
|
||||
http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
|
||||
?DEBUG("Looking for session: ~p", [Sid]),
|
||||
case mnesia:dirty_read({http_bind, Sid}) of
|
||||
[] ->
|
||||
case get_session(Sid) of
|
||||
{error, _} ->
|
||||
{error, not_exists};
|
||||
[#http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess] ->
|
||||
{ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}->
|
||||
NewStream =
|
||||
case StreamStart of
|
||||
true ->
|
||||
@ -1225,7 +1261,36 @@ check_default_xmlns({xmlelement, Name, Attrs, Els} = El) ->
|
||||
%% Print a warning in log file if this is not the case.
|
||||
check_bind_module(XmppDomain) ->
|
||||
case gen_mod:is_loaded(XmppDomain, mod_http_bind) of
|
||||
true -> ok;
|
||||
true -> true;
|
||||
false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), but the module mod_http_bind is not started.~n"
|
||||
"Check your 'modules' section in your ejabberd configuration file.",[])
|
||||
"Check your 'modules' section in your ejabberd configuration file.",[]),
|
||||
false
|
||||
end.
|
||||
|
||||
make_sid() ->
|
||||
sha:sha(term_to_binary({now(), make_ref()}))
|
||||
++ "-" ++ ejabberd_cluster:node_id().
|
||||
|
||||
get_session(SID) ->
|
||||
case string:tokens(SID, "-") of
|
||||
[_, NodeID] ->
|
||||
case ejabberd_cluster:get_node_by_id(NodeID) of
|
||||
Node when Node == node() ->
|
||||
case mnesia:dirty_read({http_bind, SID}) of
|
||||
[] ->
|
||||
{error, enoent};
|
||||
[Session] ->
|
||||
{ok, Session}
|
||||
end;
|
||||
Node ->
|
||||
case catch rpc:call(Node, mnesia, dirty_read,
|
||||
[{http_bind, SID}], 5000) of
|
||||
[Session] ->
|
||||
{ok, Session};
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
{error, enoent}
|
||||
end.
|
||||
|
@ -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.
|
||||
|
202
src/web/ejabberd_http_ws.erl
Normal file
202
src/web/ejabberd_http_ws.erl
Normal file
@ -0,0 +1,202 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_websocket.erl
|
||||
%%% Author : Eric Cestari <ecestari@process-one.net>
|
||||
%%% Purpose : XMPP Websocket support
|
||||
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
-module (ejabberd_http_ws).
|
||||
|
||||
-author('ecestari@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
|
||||
% External exports
|
||||
-export([
|
||||
start/1,
|
||||
start_link/1,
|
||||
init/1,
|
||||
handle_event/3,
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
send/2,
|
||||
setopts/2,
|
||||
sockname/1, peername/1,
|
||||
controlling_process/2,
|
||||
become_controller/2,
|
||||
close/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
-record(state, {
|
||||
socket,
|
||||
timeout,
|
||||
timer,
|
||||
input = "",
|
||||
waiting_input = false, %% {ReceiverPid, Tag}
|
||||
last_receiver,
|
||||
ws}).
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
-ifdef(DBGFSM).
|
||||
-define(FSMOPTS, [{debug, [trace]}]).
|
||||
-else.
|
||||
-define(FSMOPTS, []).
|
||||
-endif.
|
||||
|
||||
-define(WEBSOCKET_TIMEOUT, 300000).
|
||||
%
|
||||
%
|
||||
%%%%----------------------------------------------------------------------
|
||||
%%%% API
|
||||
%%%%----------------------------------------------------------------------
|
||||
start(WS) ->
|
||||
supervisor:start_child(ejabberd_wsloop_sup, [WS]).
|
||||
|
||||
start_link(WS) ->
|
||||
gen_fsm:start_link(?MODULE, [WS],?FSMOPTS).
|
||||
|
||||
send({http_ws, FsmRef, _IP}, Packet) ->
|
||||
gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
|
||||
|
||||
setopts({http_ws, FsmRef, _IP}, Opts) ->
|
||||
case lists:member({active, once}, Opts) of
|
||||
true ->
|
||||
gen_fsm:send_all_state_event(FsmRef, {activate, self()});
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
sockname(_Socket) ->
|
||||
{ok, {{0, 0, 0, 0}, 0}}.
|
||||
|
||||
peername({http_ws, _FsmRef, IP}) ->
|
||||
{ok, IP}.
|
||||
|
||||
controlling_process(_Socket, _Pid) ->
|
||||
ok.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}).
|
||||
|
||||
close({http_ws, FsmRef, _IP}) ->
|
||||
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
|
||||
|
||||
%%% Internal
|
||||
|
||||
|
||||
init([WS]) ->
|
||||
%% Read c2s options from the first ejabberd_c2s configuration in
|
||||
%% the config file listen section
|
||||
%% TODO: We should have different access and shaper values for
|
||||
%% each connector. The default behaviour should be however to use
|
||||
%% the default c2s restrictions if not defined for the current
|
||||
%% connector.
|
||||
Opts = ejabberd_c2s_config:get_c2s_limits(),
|
||||
|
||||
WSTimeout = case ejabberd_config:get_local_option({websocket_timeout,
|
||||
?MYNAME}) of
|
||||
%% convert seconds of option into milliseconds
|
||||
Int when is_integer(Int) -> Int*1000;
|
||||
undefined -> ?WEBSOCKET_TIMEOUT
|
||||
end,
|
||||
|
||||
Socket = {http_ws, self(), WS:get(ip)},
|
||||
?DEBUG("Client connected through websocket ~p", [Socket]),
|
||||
ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts),
|
||||
Timer = erlang:start_timer(WSTimeout, self(), []),
|
||||
{ok, loop, #state{
|
||||
socket = Socket,
|
||||
timeout = WSTimeout,
|
||||
timer = Timer,
|
||||
ws = WS}}.
|
||||
|
||||
handle_event({activate, From}, StateName, StateData) ->
|
||||
case StateData#state.input of
|
||||
"" ->
|
||||
{next_state, StateName,
|
||||
StateData#state{waiting_input = {From, ok}}};
|
||||
Input ->
|
||||
Receiver = From,
|
||||
Receiver ! {tcp, StateData#state.socket, list_to_binary(Input)},
|
||||
{next_state, StateName, StateData#state{input = "",
|
||||
waiting_input = false,
|
||||
last_receiver = Receiver
|
||||
}}
|
||||
end.
|
||||
|
||||
handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) ->
|
||||
Packet2 = if
|
||||
is_binary(Packet) ->
|
||||
Packet;
|
||||
true ->
|
||||
list_to_binary(Packet)
|
||||
end,
|
||||
?DEBUG("sending on websocket : ~p ", [Packet2]),
|
||||
WS:send(Packet2),
|
||||
{reply, ok, StateName, StateData};
|
||||
|
||||
handle_sync_event(close, _From, _StateName, StateData) ->
|
||||
Reply = ok,
|
||||
{stop, normal, Reply, StateData}.
|
||||
|
||||
handle_info({browser, Packet}, StateName, StateData)->
|
||||
NewState = case StateData#state.waiting_input of
|
||||
false ->
|
||||
Input = [StateData#state.input|Packet],
|
||||
StateData#state{input = Input};
|
||||
{Receiver, _Tag} ->
|
||||
Receiver ! {tcp, StateData#state.socket,Packet},
|
||||
cancel_timer(StateData#state.timer),
|
||||
Timer = erlang:start_timer(StateData#state.timeout, self(), []),
|
||||
StateData#state{waiting_input = false,
|
||||
last_receiver = Receiver,
|
||||
timer = Timer}
|
||||
end,
|
||||
{next_state, StateName, NewState};
|
||||
|
||||
|
||||
handle_info({timeout, Timer, _}, _StateName,
|
||||
#state{timer = Timer} = StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
handle_info(_, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
|
||||
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||
{ok, StateName, StateData}.
|
||||
|
||||
terminate(_Reason, _StateName, _StateData) -> ok.
|
||||
|
||||
cancel_timer(Timer) ->
|
||||
erlang:cancel_timer(Timer),
|
||||
receive
|
||||
{timeout, Timer, _} ->
|
||||
ok
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
275
src/web/ejabberd_websocket.erl
Normal file
275
src/web/ejabberd_websocket.erl
Normal file
@ -0,0 +1,275 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_websocket.erl
|
||||
%%% Author : Eric Cestari <ecestari@process-one.net>
|
||||
%%% Purpose : XMPP Websocket support
|
||||
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
|
||||
%%%
|
||||
%%% Some code lifted from MISULTIN - WebSocket misultin_websocket.erl - >-|-|-(°>
|
||||
%%% (http://github.com/ostinelli/misultin/blob/master/src/misultin_websocket.erl)
|
||||
%%% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>, Joe Armstrong.
|
||||
%%% All rights reserved.
|
||||
%%%
|
||||
%%% Code portions from Joe Armstrong have been originally taken under MIT license at the address:
|
||||
%%% <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html>
|
||||
%%%
|
||||
%%% BSD License
|
||||
%%%
|
||||
%%% Redistribution and use in source and binary forms, with or without modification, are permitted provided
|
||||
%%% that the following conditions are met:
|
||||
%%%
|
||||
%%% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
||||
%%% following disclaimer.
|
||||
%%% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
|
||||
%%% the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
%%% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
|
||||
%%% products derived from this software without specific prior written permission.
|
||||
%%%
|
||||
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
%%% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
%%% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
%%% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
%%% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
%%% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
%%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
%%% POSSIBILITY OF SUCH DAMAGE.
|
||||
%%% ==========================================================================================================
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module (ejabberd_websocket).
|
||||
-author('ecestari@process-one.net').
|
||||
-export([connect/2, check/2, is_acceptable/1]).
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
check(_Path, Headers)->
|
||||
% set supported websocket protocols, order does matter
|
||||
VsnSupported = [{'draft-hixie', 0}, {'draft-hixie', 68}],
|
||||
% checks
|
||||
check_websockets(VsnSupported, Headers).
|
||||
|
||||
% Checks if websocket can be access by client
|
||||
% If origins are set in configuration, check if it belongs
|
||||
% If origins not set, access is open.
|
||||
is_acceptable(#ws{origin=Origin, protocol=Protocol,
|
||||
headers = Headers, acceptable_origins = Origins})->
|
||||
ClientProtocol = lists:keyfind("Sec-WebSocket-Protocol",1, Headers),
|
||||
case {(Origin == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of
|
||||
{false, _, _} ->
|
||||
?DEBUG("client does not come from authorized origin", []),
|
||||
false;
|
||||
{_, false, _} ->
|
||||
?DEBUG("Client did not ask for protocol", []),
|
||||
true;
|
||||
{_, {_, P}, P} ->
|
||||
?DEBUG("Protocoles are matching", []),
|
||||
true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
|
||||
% Connect and handshake with Websocket.
|
||||
connect(#ws{vsn = Vsn, socket = Socket, origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) ->
|
||||
% build handshake
|
||||
HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Origin, Host, Port}),
|
||||
% send handshake back
|
||||
%?DEBUG("building handshake response : ~p", [HandshakeServer]),
|
||||
SockMod:send(Socket, HandshakeServer),
|
||||
Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, host = Host}, self()),
|
||||
%?DEBUG("Ws0 : ~p",[Ws0]),
|
||||
% add data to ws record and spawn controlling process
|
||||
{ok, WsHandleLoopPid} = WsLoop:start_link(Ws0),
|
||||
erlang:monitor(process, WsHandleLoopPid),
|
||||
% set opts
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{packet, 0}, {active, true}]);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, [{packet, 0}, {active, true}])
|
||||
end,
|
||||
% start listening for incoming data
|
||||
ws_loop(Socket, none, WsHandleLoopPid, SockMod, WsAutoExit).
|
||||
|
||||
|
||||
check_websockets([], _Headers) -> false;
|
||||
check_websockets([Vsn|T], Headers) ->
|
||||
case check_websocket(Vsn, Headers) of
|
||||
false -> check_websockets(T, Headers);
|
||||
{true, Vsn} -> {true, Vsn}
|
||||
end.
|
||||
|
||||
% Function: {true, Vsn} | false
|
||||
% Description: Check if the incoming request is a websocket request.
|
||||
check_websocket({'draft-hixie', 0} = Vsn, Headers) ->
|
||||
%?DEBUG("testing for websocket protocol ~p", [Vsn]),
|
||||
% set required headers
|
||||
RequiredHeaders = [
|
||||
{'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore},
|
||||
{"Sec-Websocket-Key1", ignore}, {"Sec-Websocket-Key2", ignore}
|
||||
],
|
||||
% check for headers existance
|
||||
case check_headers(Headers, RequiredHeaders) of
|
||||
true ->
|
||||
% return
|
||||
{true, Vsn};
|
||||
_RemainingHeaders ->
|
||||
%?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
|
||||
false
|
||||
end;
|
||||
check_websocket({'draft-hixie', 68} = Vsn, Headers) ->
|
||||
%?DEBUG("testing for websocket protocol ~p", [Vsn]),
|
||||
% set required headers
|
||||
RequiredHeaders = [
|
||||
{'Upgrade', "WebSocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {"Origin", ignore}
|
||||
],
|
||||
% check for headers existance
|
||||
case check_headers(Headers, RequiredHeaders) of
|
||||
true -> {true, Vsn};
|
||||
_RemainingHeaders ->
|
||||
%?DEBUG("not protocol ~p, remaining headers: ~p", [Vsn, _RemainingHeaders]),
|
||||
false
|
||||
end;
|
||||
check_websocket(_Vsn, _Headers) -> false. % not implemented
|
||||
|
||||
% Function: true | [{RequiredTag, RequiredVal}, ..]
|
||||
% Description: Check if headers correspond to headers requirements.
|
||||
check_headers(Headers, RequiredHeaders) ->
|
||||
F = fun({Tag, Val}) ->
|
||||
% see if the required Tag is in the Headers
|
||||
case lists:keyfind(Tag, 1, Headers) of
|
||||
false -> true; % header not found, keep in list
|
||||
{Tag, HVal} ->
|
||||
%?DEBUG("check: ~p", [{Tag, HVal,Val }]),
|
||||
case Val of
|
||||
ignore -> false; % ignore value -> ok, remove from list
|
||||
HVal -> false; % expected val -> ok, remove from list
|
||||
_ -> true % val is different, keep in list
|
||||
end
|
||||
end
|
||||
end,
|
||||
case lists:filter(F, RequiredHeaders) of
|
||||
[] -> true;
|
||||
MissingHeaders -> MissingHeaders
|
||||
end.
|
||||
|
||||
% Function: List
|
||||
% Description: Builds the server handshake response.
|
||||
handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Origin, Host, Port}) ->
|
||||
% build data
|
||||
{_, Key1} = lists:keyfind("Sec-Websocket-Key1",1, Headers),
|
||||
{_, Key2} = lists:keyfind("Sec-Websocket-Key2",1, Headers),
|
||||
% handshake needs body of the request, still need to read it [TODO: default recv timeout hard set, will be exported when WS protocol is final]
|
||||
case SocketMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Sock, [{packet, raw}, {active, false}]);
|
||||
_ ->
|
||||
SocketMod:setopts(Sock, [{packet, raw}, {active, false}])
|
||||
end,
|
||||
Body = case SocketMod:recv(Sock, 8, 30*1000) of
|
||||
{ok, Bin} -> Bin;
|
||||
{error, timeout} ->
|
||||
?WARNING_MSG("timeout in reading websocket body", []),
|
||||
<<>>;
|
||||
_Other ->
|
||||
?ERROR_MSG("tcp error treating data: ~p", [_Other]),
|
||||
<<>>
|
||||
end,
|
||||
%?DEBUG("got content in body of websocket request: ~p, ~p", [Body,string:join([Host, Path],"/")]),
|
||||
% prepare handhsake response
|
||||
["HTTP/1.1 101 WebSocket Protocol Handshake\r\n",
|
||||
"Upgrade: WebSocket\r\n",
|
||||
"Connection: Upgrade\r\n",
|
||||
"Sec-WebSocket-Origin: ", Origin, "\r\n",
|
||||
"Sec-WebSocket-Location: ws://",
|
||||
string:join([Host, integer_to_list(Port)],":"),
|
||||
"/",string:join(Path,"/") , "\r\n\r\n",
|
||||
build_challenge({'draft-hixie', 0}, {Key1, Key2, Body})
|
||||
];
|
||||
handshake({'draft-hixie', 68}, _Sock,_SocketMod, _Headers, {Path, Origin, Host, Port}) ->
|
||||
% prepare handhsake response
|
||||
["HTTP/1.1 101 Web Socket Protocol Handshake\r\n",
|
||||
"Upgrade: WebSocket\r\n",
|
||||
"Connection: Upgrade\r\n",
|
||||
"WebSocket-Origin: ", Origin , "\r\n",
|
||||
"WebSocket-Location: ws://",
|
||||
lists:concat([Host, integer_to_list(Port)]),
|
||||
"/",string:join(Path,"/"), "\r\n\r\n"
|
||||
].
|
||||
|
||||
% Function: List
|
||||
% Description: Builds the challenge for a handshake response.
|
||||
% Code portions from Sergio Veiga <http://sergioveiga.com/index.php/2010/06/17/websocket-handshake-76-in-erlang/>
|
||||
build_challenge({'draft-hixie', 0}, {Key1, Key2, Key3}) ->
|
||||
Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
|
||||
Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
|
||||
Blank1 = length([D || D <- Key1, D =:= 32]),
|
||||
Blank2 = length([D || D <- Key2, D =:= 32]),
|
||||
Part1 = list_to_integer(Ikey1) div Blank1,
|
||||
Part2 = list_to_integer(Ikey2) div Blank2,
|
||||
Ckey = <<Part1:4/big-unsigned-integer-unit:8, Part2:4/big-unsigned-integer-unit:8, Key3/binary>>,
|
||||
erlang:md5(Ckey).
|
||||
|
||||
|
||||
ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
receive
|
||||
{tcp, Socket, Data} ->
|
||||
handle_data(Buffer, Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
{tcp_closed, Socket} ->
|
||||
?DEBUG("tcp connection was closed, exit", []),
|
||||
% close websocket and custom controlling loop
|
||||
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
{'DOWN', Ref, process, WsHandleLoopPid, Reason} ->
|
||||
case Reason of
|
||||
normal ->
|
||||
%?DEBUG("linked websocket controlling loop stopped.", []);
|
||||
ok;
|
||||
_ ->
|
||||
?ERROR_MSG("linked websocket controlling loop crashed with reason: ~p", [Reason])
|
||||
end,
|
||||
% demonitor
|
||||
erlang:demonitor(Ref),
|
||||
% close websocket and custom controlling loop
|
||||
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
{send, Data} ->
|
||||
?DEBUG("sending data to websocket: ~p", [Data]),
|
||||
SocketMode:send(Socket, iolist_to_binary([0,Data,255])),
|
||||
ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
shutdown ->
|
||||
?DEBUG("shutdown request received, closing websocket with pid ~p", [self()]),
|
||||
% close websocket and custom controlling loop
|
||||
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
_Ignored ->
|
||||
?WARNING_MSG("received unexpected message, ignoring: ~p", [_Ignored]),
|
||||
ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit)
|
||||
end.
|
||||
|
||||
% Buffering and data handling
|
||||
handle_data(none,<<0,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
handle_data(<<>>, T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
|
||||
handle_data(none, <<>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
ws_loop(Socket, none, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
|
||||
handle_data(L, <<255,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
WsHandleLoopPid ! {browser, iolist_to_binary(L)},
|
||||
handle_data(none, T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
|
||||
handle_data(L, <<H/utf8,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
handle_data(iolist_to_binary([L, H]), T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
|
||||
|
||||
handle_data(<<>>, L, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
ws_loop(Socket, L, WsHandleLoopPid, SocketMode, WsAutoExit).
|
||||
|
||||
% Close socket and custom handling loop dependency
|
||||
websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit) ->
|
||||
case WsAutoExit of
|
||||
true ->
|
||||
% kill custom handling loop process
|
||||
exit(WsHandleLoopPid, kill);
|
||||
false ->
|
||||
% the killing of the custom handling loop process is handled in the loop itself -> send event
|
||||
WsHandleLoopPid ! closed
|
||||
end,
|
||||
% close main socket
|
||||
SocketMode:close(Socket).
|
80
src/web/ejabberd_ws.erl
Normal file
80
src/web/ejabberd_ws.erl
Normal file
@ -0,0 +1,80 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_websocket.erl
|
||||
%%% Author : Eric Cestari <ecestari@process-one.net>
|
||||
%%% Purpose : Websocket support
|
||||
%%% Created : 09-10-2010 by Eric Cestari <ecestari@process-one.net>
|
||||
%%% Slightly adapted from :
|
||||
% ==========================================================================================================
|
||||
% MISULTIN - Websocket Request
|
||||
%
|
||||
% >-|-|-(°>
|
||||
%
|
||||
% Copyright (C) 2010, Roberto Ostinelli <roberto@ostinelli.net>.
|
||||
% All rights reserved.
|
||||
%
|
||||
% BSD License
|
||||
%
|
||||
% Redistribution and use in source and binary forms, with or without modification, are permitted provided
|
||||
% that the following conditions are met:
|
||||
%
|
||||
% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
||||
% following disclaimer.
|
||||
% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
|
||||
% the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
|
||||
% products derived from this software without specific prior written permission.
|
||||
%
|
||||
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
% POSSIBILITY OF SUCH DAMAGE.
|
||||
% ==========================================================================================================
|
||||
-module(ejabberd_ws, [Ws, SocketPid]).
|
||||
-vsn("0.6.1").
|
||||
|
||||
% API
|
||||
-export([raw/0, get/1, send/1]).
|
||||
|
||||
% includes
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
|
||||
% ============================ \/ API ======================================================================
|
||||
|
||||
% Description: Returns raw websocket content.
|
||||
raw() ->
|
||||
Ws.
|
||||
|
||||
% Description: Get websocket info.
|
||||
get(socket) ->
|
||||
Ws#ws.socket;
|
||||
get(socket_mode) ->
|
||||
Ws#ws.sockmod;
|
||||
get(ip) ->
|
||||
Ws#ws.ip;
|
||||
get(vsn) ->
|
||||
Ws#ws.vsn;
|
||||
get(origin) ->
|
||||
Ws#ws.origin;
|
||||
get(host) ->
|
||||
Ws#ws.host;
|
||||
get(path) ->
|
||||
Ws#ws.path;
|
||||
get(headers) ->
|
||||
Ws#ws.headers.
|
||||
|
||||
% send data
|
||||
send(Data) ->
|
||||
SocketPid ! {send, Data}.
|
||||
|
||||
% ============================ /\ API ======================================================================
|
||||
|
||||
|
||||
|
||||
% ============================ \/ INTERNAL FUNCTIONS =======================================================
|
||||
|
||||
% ============================ /\ INTERNAL FUNCTIONS =======================================================
|
@ -117,7 +117,7 @@ start(_Host, _Opts) ->
|
||||
% mod_http_bind is already started so it will not be started again
|
||||
ok;
|
||||
{error, Error} ->
|
||||
{'EXIT', {start_child_error, Error}}
|
||||
exit({start_child_error, Error})
|
||||
end.
|
||||
|
||||
stop(_Host) ->
|
||||
@ -125,14 +125,22 @@ stop(_Host) ->
|
||||
ok ->
|
||||
ok;
|
||||
{error, Error} ->
|
||||
{'EXIT', {terminate_child_error, Error}}
|
||||
exit({terminate_child_error, Error})
|
||||
end,
|
||||
case supervisor:delete_child(ejabberd_sup, ejabberd_http_bind_sup) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, Error2} ->
|
||||
exit({delete_child_error, Error2})
|
||||
end.
|
||||
|
||||
setup_database() ->
|
||||
migrate_database(),
|
||||
mnesia:create_table(http_bind,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, http_bind)}]).
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, http_bind)}]),
|
||||
mnesia:add_table_copy(http_bind, node(), ram_copies).
|
||||
|
||||
migrate_database() ->
|
||||
case catch mnesia:table_info(http_bind, attributes) of
|
||||
@ -142,4 +150,10 @@ migrate_database() ->
|
||||
%% Since the stored information is not important, instead
|
||||
%% of actually migrating data, let's just destroy the table
|
||||
mnesia:delete_table(http_bind)
|
||||
end,
|
||||
case catch mnesia:table_info(http_bind, local_content) of
|
||||
false ->
|
||||
mnesia:delete_table(http_bind);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
414
src/web/pshb_http.erl
Normal file
414
src/web/pshb_http.erl
Normal file
@ -0,0 +1,414 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : pshb_http.erl
|
||||
%%% Author : Eric Cestari <ecestari@process-one.net>
|
||||
%%% Purpose :
|
||||
%%% Created :01-09-2010
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
%%%
|
||||
%%% {5280, ejabberd_http, [
|
||||
%%% http_poll,
|
||||
%%% web_admin,
|
||||
%%% {request_handlers, [{["pshb"], pshb_http}]} % this should be added
|
||||
%%% ]}
|
||||
%%%
|
||||
%%% To post to a node the content of the file "sam.atom" on the "foo", on the localhost virtual host, using cstar@localhost
|
||||
%%% curl -u cstar@localhost:encore -i -X POST http://localhost:5280/pshb/localhost/foo -d @sam.atom
|
||||
%%%
|
||||
|
||||
|
||||
-module (pshb_http).
|
||||
-author('ecestari@process-one.net').
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
-include("mod_pubsub/pubsub.hrl").
|
||||
|
||||
-export([process/2]).
|
||||
|
||||
process([Domain | _Rest] = LocalPath, #request{auth = Auth} = Request)->
|
||||
UD = get_auth(Auth),
|
||||
Module = backend(Domain),
|
||||
case catch out(Module, Request, Request#request.method, LocalPath,UD) of
|
||||
{'EXIT', Error} ->
|
||||
?ERROR_MSG("Error while processing ~p : ~n~p", [LocalPath, Error]),
|
||||
error(500);
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
get_auth(Auth) ->
|
||||
case Auth of
|
||||
{SJID, P} ->
|
||||
case jlib:string_to_jid(SJID) of
|
||||
error ->
|
||||
undefined;
|
||||
#jid{user = U, server = S} ->
|
||||
case ejabberd_auth:check_password(U, S, P) of
|
||||
true ->
|
||||
{U, S};
|
||||
false ->
|
||||
undefined
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
out(Module, Args, 'GET', [Domain,Node]=Uri, _User) ->
|
||||
case Module:tree_action(get_host(Uri), get_node, [get_host(Uri),get_collection(Uri)]) of
|
||||
{error, Error} ->
|
||||
error(Error);
|
||||
#pubsub_node{options = Options}->
|
||||
AccessModel = lists:keyfind(access_model, 1, Options),
|
||||
case AccessModel of
|
||||
{access_model, open} ->
|
||||
Items = lists:sort(fun(X,Y)->
|
||||
{DateX, _} = X#pubsub_item.modification,
|
||||
{DateY, _} = Y#pubsub_item.modification,
|
||||
DateX > DateY
|
||||
end, Module:get_items(
|
||||
get_host(Uri),
|
||||
get_collection(Uri))),
|
||||
case Items of
|
||||
[] -> ?DEBUG("Items : ~p ~n", [collection(get_collection(Uri),
|
||||
collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])]),
|
||||
{200, [{"Content-Type", "application/atom+xml"}],
|
||||
collection(get_collection(Uri),
|
||||
collection_uri(Args,Domain,Node), calendar:now_to_universal_time(erlang:now()), "", [])};
|
||||
_ ->
|
||||
#pubsub_item{modification = {LastDate, _JID}} = LastItem = hd(Items),
|
||||
Etag =generate_etag(LastItem),
|
||||
IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
|
||||
if IfNoneMatch==Etag
|
||||
->
|
||||
success(304);
|
||||
true ->
|
||||
XMLEntries= [item_to_entry(Args,Domain, Node,Entry)||Entry <- Items],
|
||||
{200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}],
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
++ xml:element_to_string(
|
||||
collection(get_collection(Uri), collection_uri(Args,Domain,Node),
|
||||
calendar:now_to_universal_time(LastDate), "", XMLEntries))}
|
||||
end
|
||||
end;
|
||||
{access_model, Access} ->
|
||||
?INFO_MSG("Uri ~p requested. access_model is ~p. HTTP access denied unless access_model =:= open",
|
||||
[Uri, Access]),
|
||||
error(?ERR_FORBIDDEN)
|
||||
end
|
||||
end;
|
||||
|
||||
out(Module, Args, 'POST', [_D, _Node]=Uri, {_User, _Domain} = UD) ->
|
||||
publish_item(Module, Args, Uri, uniqid(false), UD);
|
||||
|
||||
out(Module, Args, 'PUT', [_D, _Node, Slug]=Uri, {_User, _Domain} = UD) ->
|
||||
publish_item(Module, Args, Uri, Slug, UD);
|
||||
|
||||
out(Module, _Args, 'DELETE', [_D, Node, Id]= Uri, {User, UDomain}) ->
|
||||
Jid = jlib:make_jid({User, UDomain, ""}),
|
||||
case Module:delete_item(get_host(Uri), list_to_binary(Node), Jid, Id) of
|
||||
{error, Error} -> error(Error);
|
||||
{result, _Res} -> success(200)
|
||||
end;
|
||||
|
||||
|
||||
out(Module, Args, 'PUT', [_Domain, Node]= Uri, {User, UDomain}) ->
|
||||
Host = get_host(Uri),
|
||||
Jid = jlib:make_jid({User, UDomain, ""}),
|
||||
Payload = xml_stream:parse_element(Args#request.data),
|
||||
ConfigureElement = case xml:get_subtag(Payload, "configure") of
|
||||
false ->[];
|
||||
{xmlelement, _, _, SubEls}->SubEls
|
||||
end,
|
||||
case Module:set_configure(Host, list_to_binary(Node), Jid, ConfigureElement, Args#request.lang) of
|
||||
{result, []} -> success(200);
|
||||
{error, Error} -> error(Error)
|
||||
end;
|
||||
|
||||
out(Module, Args, 'GET', [Domain]=Uri, From)->
|
||||
Host = get_host(Uri),
|
||||
?DEBUG("Host = ~p", [Host]),
|
||||
case Module:tree_action(Host, get_subnodes, [Host, <<>>, From ]) of
|
||||
[] ->
|
||||
?DEBUG("Error getting URI ~p : ~p",[Uri, From]),
|
||||
error(?ERR_ITEM_NOT_FOUND);
|
||||
Collections ->
|
||||
{200, [{"Content-Type", "application/atomsvc+xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
++ xml:element_to_string(service(Args,Domain, Collections))}
|
||||
end;
|
||||
|
||||
out(Module, Args, 'POST', [Domain]=Uri, {User, UDomain})->
|
||||
Host = get_host(Uri),
|
||||
Payload = xml_stream:parse_element(Args#request.data),
|
||||
{Node, Type} = case xml:get_subtag(Payload, "create") of
|
||||
false -> {<<>>,"flat"};
|
||||
E ->
|
||||
{list_to_binary(get_tag_attr_or_default("node", E,"")),
|
||||
get_tag_attr_or_default("type", E,"flat")}
|
||||
end,
|
||||
ConfigureElement = case xml:get_subtag(Payload, "configure") of
|
||||
false ->[];
|
||||
{xmlelement, _, _, SubEls}->SubEls
|
||||
end,
|
||||
Jid = jlib:make_jid({User, UDomain, ""}),
|
||||
case Module:create_node(Host, Domain, Node, Jid, Type, all, ConfigureElement) of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Error create node via HTTP : ~p",[Error]),
|
||||
error(Error); % will probably detail more
|
||||
{result, [Result]} ->
|
||||
{200, [{"Content-Type", "application/xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
++ xml:element_to_string(Result)}
|
||||
end;
|
||||
|
||||
out(Module,_Args, 'DELETE', [_Domain, Node] = Uri, {User, UDomain})->
|
||||
Host = get_host(Uri),
|
||||
Jid = jlib:make_jid({User, UDomain, ""}),
|
||||
BinNode = list_to_binary(Node),
|
||||
case Module:delete_node(Host, BinNode, Jid) of
|
||||
{error, Error} -> error(Error);
|
||||
{result, []} ->
|
||||
{200, [],[]}
|
||||
end;
|
||||
|
||||
|
||||
|
||||
out(Module, Args, 'GET', [Domain, Node, _Item]=URI, _) ->
|
||||
Failure = fun(Error)->
|
||||
?DEBUG("Error getting URI ~p : ~p",[URI, Error]),
|
||||
error(Error)
|
||||
end,
|
||||
Success = fun(Item)->
|
||||
Etag =generate_etag(Item),
|
||||
IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
|
||||
if IfNoneMatch==Etag
|
||||
->
|
||||
success(304);
|
||||
true ->
|
||||
{200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
++ xml:element_to_string(item_to_entry(Args, Domain,Node, Item))}
|
||||
end
|
||||
end,
|
||||
get_item(Module, URI, Failure, Success);
|
||||
|
||||
out(_Module,_,Method,Uri,undefined) ->
|
||||
?DEBUG("Error, ~p not authorized for ~p : ~p",[ Method,Uri]),
|
||||
error(?ERR_FORBIDDEN).
|
||||
|
||||
get_item(Module, Uri, Failure, Success)->
|
||||
?DEBUG(" Module:get_item(~p, ~p,~p)", [get_host(Uri), get_collection(Uri), get_member(Uri)]),
|
||||
case Module:get_item(get_host(Uri), get_collection(Uri), get_member(Uri)) of
|
||||
{error, Reason} ->
|
||||
Failure(Reason);
|
||||
#pubsub_item{}=Item ->
|
||||
Success(Item)
|
||||
end.
|
||||
|
||||
publish_item(Module, Args, [Domain, Node | _R] = Uri, Slug, {User, Domain})->
|
||||
|
||||
Payload = xml_stream:parse_element(Args#request.data),
|
||||
[FilteredPayload]=xml:remove_cdata([Payload]),
|
||||
|
||||
%FilteredPayload2 = case xml:get_subtag(FilteredPayload, "app:edited") ->
|
||||
% {xmlelement, Name, Attrs, [{cdata, }]}
|
||||
case Module:publish_item(get_host(Uri),
|
||||
Domain,
|
||||
get_collection(Uri),
|
||||
jlib:make_jid(User,Domain, ""),
|
||||
Slug,
|
||||
[FilteredPayload]) of
|
||||
{result, [_]} ->
|
||||
?DEBUG("Publishing to ~p~n",[entry_uri(Args, Domain, Node,Slug)]),
|
||||
{201, [{"location", entry_uri(Args, Domain,Node,Slug)}], Payload};
|
||||
{error, Error} ->
|
||||
error(Error)
|
||||
end.
|
||||
|
||||
generate_etag(#pubsub_item{modification={{_, D2, D3}, _JID}})->integer_to_list(D3+D2).
|
||||
get_host([Domain|_Rest])-> "pubsub."++Domain.
|
||||
get_collection([_Domain, Node|_Rest])->list_to_binary(Node).
|
||||
get_member([_Domain, _Node, Member])->
|
||||
Member.
|
||||
|
||||
collection_uri(R, Domain, Node) ->
|
||||
base_uri(R, Domain)++ "/" ++ b2l(Node).
|
||||
|
||||
entry_uri(R,Domain, Node, Id)->
|
||||
collection_uri(R,Domain, Node)++"/"++b2l(Id).
|
||||
|
||||
base_uri(#request{host=Host, port=Port}, Domain)->
|
||||
"http://"++Host++":"++i2l(Port)++"/pshb/" ++ Domain.
|
||||
|
||||
item_to_entry(Args,Domain, Node,#pubsub_item{itemid={Id,_}, payload=Entry}=Item)->
|
||||
[R]=xml:remove_cdata(Entry),
|
||||
item_to_entry(Args, Domain, Node, Id, R, Item).
|
||||
|
||||
item_to_entry(Args,Domain, Node, Id,{xmlelement, "entry", Attrs, SubEl},
|
||||
#pubsub_item{modification={ Secs, JID} }) ->
|
||||
Date = calendar:now_to_local_time(Secs),
|
||||
{_User, Domain, _}=jlib:jid_tolower(JID),
|
||||
SubEl2=[{xmlelement, "app:edited", [], [{xmlcdata, w3cdtf(Date)}]},
|
||||
{xmlelement, "updated", [],[{xmlcdata, w3cdtf(Date)}]},
|
||||
{xmlelement, "author", [],[{xmlelement, "name", [], [{xmlcdata, list_to_binary(jlib:jid_to_string(JID))}]}]},
|
||||
{xmlelement, "link",[{"rel", "edit"},
|
||||
{"href", entry_uri(Args,Domain,Node, Id)}],[] },
|
||||
{xmlelement, "id", [],[{xmlcdata, entry_uri(Args, Domain, Node, Id)}]}
|
||||
| SubEl],
|
||||
{xmlelement, "entry", [{"xmlns:app","http://www.w3.org/2007/app"}|Attrs], SubEl2};
|
||||
|
||||
% Don't do anything except adding xmlns
|
||||
item_to_entry(_Args,_Domain, Node, _Id, {xmlelement, Name, Attrs, Subels}=Element, _Item)->
|
||||
case proplists:is_defined("xmlns",Attrs) of
|
||||
true -> Element;
|
||||
false -> {xmlelement, Name, [{"xmlns", Node}|Attrs], Subels}
|
||||
end.
|
||||
|
||||
collection(Title, Link, Updated, _Id, Entries)->
|
||||
{xmlelement, "feed", [{"xmlns", "http://www.w3.org/2005/Atom"},
|
||||
{"xmlns:app", "http://www.w3.org/2007/app"}], [
|
||||
{xmlelement, "title", [],[{xmlcdata, Title}]},
|
||||
{xmlelement, "generator", [],[{xmlcdata, <<"ejabberd">>}]},
|
||||
{xmlelement, "updated", [],[{xmlcdata, w3cdtf(Updated)}]},
|
||||
{xmlelement, "link", [{"href", Link}, {"rel", "self"}], []},
|
||||
{xmlelement, "id", [], [{xmlcdata, list_to_binary(Link)}]},
|
||||
{xmlelement, "title", [],[{xmlcdata, Title}]} |
|
||||
Entries
|
||||
]}.
|
||||
|
||||
service(Args, Domain,Collections)->
|
||||
{xmlelement, "service", [{"xmlns", "http://www.w3.org/2007/app"},
|
||||
{"xmlns:atom", "http://www.w3.org/2005/Atom"},
|
||||
{"xmlns:app", "http://www.w3.org/2007/app"}],[
|
||||
{xmlelement, "workspace", [],[
|
||||
{xmlelement, "atom:title", [],[{xmlcdata,"Pubsub node Feed for " ++Domain}]} |
|
||||
lists:map(fun(#pubsub_node{nodeid={_Server, Id}, type=_Type})->
|
||||
{xmlelement, "collection", [{"href", collection_uri(Args,Domain, Id)}], [
|
||||
{xmlelement, "atom:title", [], [{xmlcdata, Id}]}
|
||||
]}
|
||||
end, Collections)
|
||||
]}
|
||||
]}.
|
||||
|
||||
%% simple output functions
|
||||
error({xmlelement, "error", Attrs, _}=Error) ->
|
||||
Value = list_to_integer(xml:get_attr_s("code", Attrs)),
|
||||
{Value, [{"Content-type", "application/xml"}], xml:element_to_string(Error)};
|
||||
error(404)->
|
||||
{404, [], "Not Found"};
|
||||
error(403)->
|
||||
{403, [], "Forbidden"};
|
||||
error(500)->
|
||||
{500, [], "Internal server error"};
|
||||
error(401)->
|
||||
{401, [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],"Unauthorized"};
|
||||
error(Code)->
|
||||
{Code, [], ""}.
|
||||
success(200)->
|
||||
{200, [], ""};
|
||||
success(Code)->
|
||||
{Code, [], ""}.
|
||||
|
||||
backend(Domain)->
|
||||
Modules = gen_mod:loaded_modules(Domain),
|
||||
case lists:member(mod_pubsub_odbc, Modules) of
|
||||
true -> mod_pubsub_odbc;
|
||||
_ -> mod_pubsub
|
||||
end.
|
||||
|
||||
|
||||
% Code below is taken (with some modifications) from the yaws webserver, which
|
||||
% is distributed under the folowing license:
|
||||
%
|
||||
% This software (the yaws webserver) is free software.
|
||||
% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
|
||||
% Any use or misuse of the source code is hereby freely allowed.
|
||||
%
|
||||
% 1. Redistributions of source code must retain the above copyright
|
||||
% notice as well as this list of conditions.
|
||||
%
|
||||
% 2. Redistributions in binary form must reproduce the above copyright
|
||||
% notice as well as this list of conditions.
|
||||
%%% Create W3CDTF (http://www.w3.org/TR/NOTE-datetime) formatted date
|
||||
%%% w3cdtf(GregSecs) -> "YYYY-MM-DDThh:mm:ssTZD"
|
||||
%%%
|
||||
uniqid(false)->
|
||||
{T1, T2, T3} = now(),
|
||||
lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]));
|
||||
uniqid(Slug) ->
|
||||
Slut = string:to_lower(Slug),
|
||||
S = string:substr(Slut, 1, 9),
|
||||
{_T1, T2, T3} = now(),
|
||||
lists:flatten(io_lib:fwrite("~s-~.16B~.16B", [S, T2, T3])).
|
||||
|
||||
w3cdtf(Date) -> %1 Date = calendar:gregorian_seconds_to_datetime(GregSecs),
|
||||
{{Y, Mo, D},{H, Mi, S}} = Date,
|
||||
[UDate|_] = calendar:local_time_to_universal_time_dst(Date),
|
||||
{DiffD,{DiffH,DiffMi,_}}=calendar:time_difference(UDate,Date),
|
||||
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi).
|
||||
|
||||
%%% w3cdtf's helper function
|
||||
w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) when DiffH < 12, DiffH /= 0 ->
|
||||
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
|
||||
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
|
||||
add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++ add_zero(DiffMi);
|
||||
|
||||
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD == 0 ->
|
||||
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
|
||||
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
|
||||
add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":" ++
|
||||
add_zero(DiffMi);
|
||||
|
||||
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi /= 0 ->
|
||||
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
|
||||
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
|
||||
add_zero(S) ++ "-" ++ add_zero(23-DiffH) ++
|
||||
":" ++ add_zero(60-DiffMi);
|
||||
|
||||
w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12, DiffD /= 0, DiffMi == 0 ->
|
||||
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
|
||||
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
|
||||
add_zero(S) ++ "-" ++ add_zero(24-DiffH) ++
|
||||
":" ++ add_zero(DiffMi);
|
||||
|
||||
w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) when DiffH == 0 ->
|
||||
i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
|
||||
add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":" ++
|
||||
add_zero(S) ++ "Z".
|
||||
|
||||
add_zero(I) when is_integer(I) -> add_zero(i2l(I));
|
||||
add_zero([A]) -> [$0,A];
|
||||
add_zero(L) when is_list(L) -> L.
|
||||
|
||||
|
||||
|
||||
i2l(I) when is_integer(I) -> integer_to_list(I);
|
||||
i2l(L) when is_list(L) -> L.
|
||||
|
||||
b2l(B) when is_binary(B) -> binary_to_list(B);
|
||||
b2l(L) when is_list(L) -> L.
|
||||
|
||||
get_tag_attr_or_default(AttrName, Element, Default)->
|
||||
case xml:get_tag_attr_s(AttrName, Element) of
|
||||
"" -> Default;
|
||||
Val -> Val
|
||||
end.
|
18
src/web/websocket_test.erl
Normal file
18
src/web/websocket_test.erl
Normal file
@ -0,0 +1,18 @@
|
||||
-module (websocket_test).
|
||||
-export([start/1, loop/1]).
|
||||
|
||||
% callback on received websockets data
|
||||
start(Ws) ->
|
||||
spawn(?MODULE, loop, [Ws]).
|
||||
|
||||
loop(Ws) ->
|
||||
receive
|
||||
{browser, Data} ->
|
||||
Ws:send(["received '", Data, "'"]),
|
||||
loop(Ws);
|
||||
_Ignore ->
|
||||
loop(Ws)
|
||||
after 5000 ->
|
||||
Ws:send("pushing!"),
|
||||
loop(Ws)
|
||||
end.
|
@ -31,6 +31,7 @@
|
||||
new/2,
|
||||
parse/2,
|
||||
close/1,
|
||||
change_callback_pid/2,
|
||||
parse_element/1]).
|
||||
|
||||
-define(XML_START, 0).
|
||||
@ -110,6 +111,8 @@ new(CallbackPid, MaxSize) ->
|
||||
size = 0,
|
||||
maxsize = MaxSize}.
|
||||
|
||||
change_callback_pid(State, CallbackPid) ->
|
||||
State#xml_stream_state{callback_pid = CallbackPid}.
|
||||
|
||||
parse(#xml_stream_state{callback_pid = CallbackPid,
|
||||
port = Port,
|
||||
|
140
tools/joincluster
Executable file
140
tools/joincluster
Executable file
@ -0,0 +1,140 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Add the current ejabberd node in a cluster
|
||||
|
||||
# copyright (c) 2010 ProcessOne
|
||||
#
|
||||
# This script is proprietary software and cannot be published or redistribute.
|
||||
|
||||
# Return Code:
|
||||
# 0 : groovy baby
|
||||
# 11 : erl not found
|
||||
# 12 : erlc not found
|
||||
# 20 : database dir doesn't exist
|
||||
# 21 : database dir not writable
|
||||
# 21 : database dir variable not set
|
||||
# 30 : network issue
|
||||
# 31 : node names incompatibility
|
||||
|
||||
function error
|
||||
{
|
||||
echo "Error: $1" >&2
|
||||
exit $2
|
||||
}
|
||||
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "ejabberd cluster configuration"
|
||||
echo ""
|
||||
echo "This ejabberd node will be configured for use in an ejabberd cluster."
|
||||
echo "IMPORTANT: all local data from the database will be lost, and"
|
||||
echo "cluster database will be initialized. All data from the master"
|
||||
echo "node will be replicated to this one."
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "Press any key to continue, or Ctrl+C to stop now"
|
||||
read foo
|
||||
echo ""
|
||||
|
||||
echo "Make sure you have a running remote master ejabberd node"
|
||||
echo "Before continuing, you must copy the ~/.erlang.cookie file from"
|
||||
echo "remote master node and check ejabberd.cfg compatibility."
|
||||
echo "e.g. hosts definition must match on all nodes"
|
||||
echo ""
|
||||
echo "The remote master node name is defined as ERLANG_NODE into"
|
||||
echo "ejabberdctl.cfg on that remote node."
|
||||
echo ""
|
||||
echo -n "Remote master node name: "
|
||||
read REMOTE
|
||||
echo ""
|
||||
|
||||
cont=Y
|
||||
ping -q -c 1 ${REMOTE#*@} 2>/dev/null >/dev/null
|
||||
[ $? -eq 0 ] || {
|
||||
echo "Cannot ping ${REMOTE#*@}. Are you sure network setup is correct ?"
|
||||
echo -n "Should we continue anyway ? (Y/n) "
|
||||
read cont
|
||||
}
|
||||
cont=`echo $cont | tr a-z A-Z`
|
||||
[ "$cont" == "Y" ] || error "Check your network configuration (dns, firewall, etc...)" 30
|
||||
|
||||
HERE=`which "$0"`
|
||||
BASE=`dirname $HERE`/..
|
||||
ROOTDIR=`cd $BASE; pwd`
|
||||
. $ROOTDIR/bin/ejabberdctl stop 2>/dev/null >/dev/null
|
||||
NAME=-name
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname
|
||||
PA=/tmp/clustersetup_$$
|
||||
CLUSTERSETUP=clustersetup
|
||||
CLUSTERSETUP_ERL=$PA/$CLUSTERSETUP.erl
|
||||
|
||||
REMOTENAME=-name
|
||||
[ "$REMOTE" = "${REMOTE%.*}" ] && REMOTENAME=-sname
|
||||
[ "$REMOTENAME" = "$NAME" ] || {
|
||||
echo "IMPORTANT!: node names are incompatible"
|
||||
echo "Remote node name is $REMOTE"
|
||||
echo "Local node name is $ERLANG_NODE"
|
||||
echo ""
|
||||
echo "Both node names must be short or fqdn names."
|
||||
echo "Using short and fqdn names is impossible."
|
||||
echo ""
|
||||
error "incompatible node names" 31
|
||||
}
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
echo "Using commands:"
|
||||
which erl || error "can't find erl" 11
|
||||
which erlc || error "can't find erlc" 12
|
||||
echo ""
|
||||
|
||||
[ -d $EJABBERD_DB ] && rm -Rf $EJABBERD_DB
|
||||
mkdir $EJABBERD_DB || error "$EJABBERD_DB cannot be created" 20
|
||||
[ -w $EJABBERD_DB ] || error "$EJABBERD_DB directory is not writable" 21
|
||||
|
||||
cd $ROOTDIR
|
||||
mkdir -p $PA
|
||||
cat <<EOF > $CLUSTERSETUP_ERL
|
||||
-module($CLUSTERSETUP).
|
||||
|
||||
-export([start/0]).
|
||||
|
||||
set_table_copy(Table, _Node, {badrpc, Reason}) ->
|
||||
io:format("Error: cannot get storage type for table ~p on node $REMOTE:~n ~p~n",[Table, Reason]);
|
||||
set_table_copy(Table, Node, Type) ->
|
||||
io:format("setting table ~p to mode ~p~n",[Table, Type]),
|
||||
case mnesia:add_table_copy(Table, Node, Type) of
|
||||
{aborted, _} ->
|
||||
mnesia:change_table_copy_type(Table, Node, Type);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
set_tables({badrpc, Reason}) ->
|
||||
io:format("ERROR: cannot get tables list on $REMOTE : ~p~n",[Reason]);
|
||||
set_tables([]) ->
|
||||
ok;
|
||||
set_tables([schema | Tables]) ->
|
||||
set_tables(Tables);
|
||||
set_tables([s2s | Tables]) ->
|
||||
set_tables(Tables);
|
||||
set_tables([session | Tables]) ->
|
||||
set_tables(Tables);
|
||||
set_tables([Table | Tables]) ->
|
||||
set_table_copy(Table, node(),
|
||||
rpc:call('$REMOTE', mnesia, table_info, [Table, storage_type])),
|
||||
set_tables(Tables).
|
||||
|
||||
start() ->
|
||||
io:format("~n",[]),
|
||||
set_table_copy(schema, node(), disc_copies),
|
||||
set_tables(rpc:call('$REMOTE', mnesia, system_info, [tables])),
|
||||
halt(0).
|
||||
EOF
|
||||
erlc -o $PA $CLUSTERSETUP_ERL
|
||||
erl $NAME $ERLANG_NODE -pa $PA $KERNEL_OPTS -mnesia extra_db_nodes "['$REMOTE']" dir "\"$EJABBERD_DB\"" -s mnesia -s $CLUSTERSETUP start
|
||||
rm -Rf $PA
|
||||
|
||||
echo "End."
|
||||
echo "Check that there is no error in the above messages."
|
93
tools/leavecluster
Executable file
93
tools/leavecluster
Executable file
@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Remove the current ejabberd node in a cluster
|
||||
|
||||
# Return Code:
|
||||
# 0 : groovy baby
|
||||
# 11 : erl not found
|
||||
# 12 : erlc not found
|
||||
# 20 : database dir doesn't exist
|
||||
# 21 : database dir not writable
|
||||
# 21 : database dir variable not set
|
||||
|
||||
function error
|
||||
{
|
||||
echo "Error: $1" >&2
|
||||
exit $2
|
||||
}
|
||||
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "ejabberd cluster configuration"
|
||||
echo ""
|
||||
echo "This ejabberd node will be removed from the cluster."
|
||||
echo "IMPORTANT: this node will be stopped. At least one other clustered"
|
||||
echo "node must be running."
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "Press any key to continue, or Ctrl+C to stop now"
|
||||
read foo
|
||||
echo ""
|
||||
|
||||
HERE=`which "$0"`
|
||||
BASE=`dirname $HERE`/..
|
||||
ROOTDIR=`cd $BASE; pwd`
|
||||
. $ROOTDIR/bin/ejabberdctl stop 2>/dev/null >/dev/null
|
||||
$ROOTDIR/bin/ejabberdctl stopped
|
||||
PA=/tmp/clustersetup_$$
|
||||
CLUSTERSETUP=clustersetup
|
||||
CLUSTERSETUP_ERL=$PA/$CLUSTERSETUP.erl
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
echo "Using commands:"
|
||||
which erl || error "can't find erl" 11
|
||||
which erlc || error "can't find erlc" 12
|
||||
echo ""
|
||||
|
||||
cd $ROOTDIR
|
||||
mkdir -p $PA
|
||||
cat <<EOF > $CLUSTERSETUP_ERL
|
||||
-module($CLUSTERSETUP).
|
||||
|
||||
-export([start/0]).
|
||||
|
||||
del_table_copy(Table, Node) ->
|
||||
case mnesia:del_table_copy(Table, Node) of
|
||||
{aborted, Reason} -> io:format("Error: can not remove ~p table: ~p~n", [Table, Reason]);
|
||||
_ -> io:format("table ~p removed from cluster~n", [Table])
|
||||
end.
|
||||
|
||||
del_tables([],_) ->
|
||||
ok;
|
||||
del_tables([schema | Tables], Node) ->
|
||||
del_tables(Tables, Node);
|
||||
del_tables([Table | Tables], Node) ->
|
||||
del_table_copy(Table, Node),
|
||||
del_tables(Tables, Node).
|
||||
|
||||
start() ->
|
||||
io:format("~n",[]),
|
||||
Removed = node(),
|
||||
case mnesia:system_info(running_db_nodes)--[Removed] of
|
||||
[] -> io:format("Error: no other node running in the cluster~n");
|
||||
Nodes ->
|
||||
del_tables(mnesia:system_info(local_tables), Removed),
|
||||
mnesia:stop(),
|
||||
case rpc:call(hd(Nodes), mnesia, del_table_copy, [schema, Removed]) of
|
||||
{badrpc,Reason} -> io:format("Error: can not unregister node ~p from cluster: ~p~n", [Removed, Reason]);
|
||||
{aborted,Reason} -> io:format("Error: can not unregister node ~p from cluster: ~p~n", [Removed, Reason]);
|
||||
{atomic, ok} ->
|
||||
mnesia:delete_schema([Removed]),
|
||||
io:format("node ~p removed from cluster~n", [Removed])
|
||||
end
|
||||
end,
|
||||
halt(0).
|
||||
EOF
|
||||
erlc -o $PA $CLUSTERSETUP_ERL
|
||||
erl $NAME $ERLANG_NODE -pa $PA $KERNEL_OPTS -mnesia dir "\"$EJABBERD_DB\"" -s mnesia -s $CLUSTERSETUP start
|
||||
rm -Rf $PA
|
||||
|
||||
echo "End."
|
||||
echo "Check that there is no error in the above messages."
|
38
tools/tcp-tuning.sh
Executable file
38
tools/tcp-tuning.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
ulimit -n 562524
|
||||
# /etc/security/limits.conf
|
||||
#@ejabberd soft nofile 562000
|
||||
#@ejabberd hard nofile 562000
|
||||
# 4KB send buffer, 20,480 connections max at worst case
|
||||
#echo 83886080 > /proc/sys/net/core/wmem_max
|
||||
#echo 83886080 > /proc/sys/net/core/wmem_default
|
||||
echo 327680000 > /proc/sys/net/core/wmem_max
|
||||
echo 327680000 > /proc/sys/net/core/wmem_default
|
||||
# 16KB receive buffer, 20,480 connections max at worst case
|
||||
#echo 335544320 > /proc/sys/net/core/rmem_max
|
||||
#echo 335544320 > /proc/sys/net/core/rmem_default
|
||||
echo 1310720000 > /proc/sys/net/core/rmem_max
|
||||
echo 1310720000 > /proc/sys/net/core/rmem_default
|
||||
# Max open files
|
||||
echo 562524 > /proc/sys/fs/file-max
|
||||
# Fast port recycling (TIME_WAIT)
|
||||
echo 1 >/proc/sys/net/ipv4/tcp_tw_recycle
|
||||
echo 1 >/proc/sys/net/ipv4/tcp_tw_reuse
|
||||
# TIME_WAIT buckets increased
|
||||
echo 360000 > /proc/sys/net/ipv4/tcp_max_tw_buckets
|
||||
# FIN timeout decreased
|
||||
echo 15 > /proc/sys/net/ipv4/tcp_fin_timeout
|
||||
# SYN backlog increased
|
||||
echo 65536 > /proc/sys/net/ipv4/tcp_max_syn_backlog
|
||||
# SYN cookies enabled
|
||||
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
|
||||
# Local port range maximized
|
||||
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
|
||||
# Netdev backlog increased
|
||||
echo 100000 > /proc/sys/net/core/netdev_max_backlog
|
||||
# Do no save route metrics
|
||||
echo 1 > /proc/sys/net/ipv4/route/flush
|
||||
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
|
||||
# Interface transmit queuelen increased
|
||||
ifconfig eth0 txqueuelen 10000
|
||||
|
Loading…
Reference in New Issue
Block a user