mirror of
https://github.com/processone/ejabberd.git
synced 2024-08-14 01:38:09 +02: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.
|
(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><LI CLASS="li-itemize">‘_ldap’, this means that the module needs an LDAP server as backend.
|
||||||
</LI></UL><P>If you want to,
|
</LI></UL><P>If you want to,
|
||||||
it is possible to use a relational database to store pieces of
|
it is possible to use a relational database to store the tables created by some ejabberd modules.
|
||||||
information. You can do this by changing the module name to a name with an
|
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
|
<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">
|
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
|
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">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">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">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
|
</LI></UL><P>You can find more
|
||||||
<A HREF="http://www.ejabberd.im/contributions">contributed modules</A> on the
|
<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
|
<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}
|
\titem{--enable-nif}
|
||||||
Replaces some critical Erlang functions with equivalents written in C to improve performance.
|
Replaces some critical Erlang functions with equivalents written in C to improve performance.
|
||||||
This feature requires Erlang/OTP R13B04 or higher.
|
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}
|
\end{description}
|
||||||
|
|
||||||
\makesubsection{install}{Install}
|
\makesubsection{install}{Install}
|
||||||
@ -2000,9 +2003,19 @@ enabled. This can be done, by using next commands:
|
|||||||
\makesubsubsection{configuremssql}{Database Connection}
|
\makesubsubsection{configuremssql}{Database Connection}
|
||||||
\ind{Microsoft SQL Server!Database Connection}
|
\ind{Microsoft SQL Server!Database Connection}
|
||||||
|
|
||||||
The configuration of Database Connection for a Microsoft SQL Server
|
By default \ejabberd{} opens 10 connections to the database for each virtual host.
|
||||||
is the same as the configuration for
|
Use this option to modify the value:
|
||||||
ODBC compatible servers (see section~\ref{configureodbc}).
|
\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}
|
\makesubsubsection{mssqlauth}{Authentication}
|
||||||
@ -2010,8 +2023,7 @@ ODBC compatible servers (see section~\ref{configureodbc}).
|
|||||||
|
|
||||||
%TODO: not sure if this section is right!!!!!!
|
%TODO: not sure if this section is right!!!!!!
|
||||||
|
|
||||||
The configuration of Authentication for a Microsoft SQL Server
|
The configuration of Microsoft SQL Server is the same as the configuration of
|
||||||
is the same as the configuration for
|
|
||||||
ODBC compatible servers (see section~\ref{odbcauth}).
|
ODBC compatible servers (see section~\ref{odbcauth}).
|
||||||
|
|
||||||
\makesubsubsection{mssqlstorage}{Storage}
|
\makesubsubsection{mssqlstorage}{Storage}
|
||||||
@ -2218,9 +2230,6 @@ and LDAP server supports
|
|||||||
|
|
||||||
\makesubsubsection{ldapconnection}{Connection}
|
\makesubsubsection{ldapconnection}{Connection}
|
||||||
|
|
||||||
Two connections are established to the LDAP server per vhost,
|
|
||||||
one for authentication and other for regular calls.
|
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
\begin{description}
|
\begin{description}
|
||||||
\titem{\{ldap\_servers, [Servers, ...]\}} \ind{options!ldap\_server}List of IP addresses or DNS names of your
|
\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}
|
\end{itemize}
|
||||||
|
|
||||||
If you want to,
|
If you want to,
|
||||||
it is possible to use a relational database to store pieces of
|
it is possible to use a relational database to store the tables created by some ejabberd modules.
|
||||||
information. You can do this by changing the module name to a name with an
|
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
|
\term{\_odbc} suffix in \ejabberd{} config file. You can use a relational
|
||||||
database for the following data:
|
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 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 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 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}
|
\end{itemize}
|
||||||
|
|
||||||
You can find more
|
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)
|
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq (@flash_hack@, true)
|
||||||
|
ERLC_FLAGS+=-DENABLE_FLASH_HACK
|
||||||
|
CPPFLAGS+=-DENABLE_FLASH_HACK
|
||||||
|
endif
|
||||||
|
|
||||||
prefix = @prefix@
|
prefix = @prefix@
|
||||||
exec_prefix = @exec_prefix@
|
exec_prefix = @exec_prefix@
|
||||||
|
|
||||||
@ -168,7 +173,7 @@ mostlyclean-recursive maintainer-clean-recursive:
|
|||||||
@ERLC@ -W $(EFLAGS) $*.erl
|
@ERLC@ -W $(EFLAGS) $*.erl
|
||||||
|
|
||||||
$(ERLSHLIBS): %.so: %.c
|
$(ERLSHLIBS): %.so: %.c
|
||||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LIBS) \
|
||||||
$(subst ../,,$(subst .so,.c,$@)) \
|
$(subst ../,,$(subst .so,.c,$@)) \
|
||||||
$(EXPAT_LIBS) \
|
$(EXPAT_LIBS) \
|
||||||
$(EXPAT_CFLAGS) \
|
$(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
|
||||||
|
%%====================================================================
|
349
src/configure
vendored
349
src/configure
vendored
@ -1,13 +1,13 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
# Guess values for system-dependent variables and create Makefiles.
|
# 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>.
|
# Report bugs to <ejabberd@process-one.net>.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
|
||||||
# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation,
|
# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software
|
||||||
# Inc.
|
# Foundation, Inc.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# This configure script is free software; the Free Software Foundation
|
# This configure script is free software; the Free Software Foundation
|
||||||
@ -319,7 +319,7 @@ $as_echo X"$as_dir" |
|
|||||||
test -d "$as_dir" && break
|
test -d "$as_dir" && break
|
||||||
done
|
done
|
||||||
test -z "$as_dirs" || eval "mkdir $as_dirs"
|
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
|
} # as_fn_mkdir_p
|
||||||
@ -359,19 +359,19 @@ else
|
|||||||
fi # as_fn_arith
|
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
|
# 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
|
# 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_fn_error ()
|
||||||
{
|
{
|
||||||
as_status=$?; test $as_status -eq 0 && as_status=1
|
as_status=$1; test $as_status -eq 0 && as_status=1
|
||||||
if test "$3"; then
|
if test "$4"; then
|
||||||
as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||||
$as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3
|
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
|
||||||
fi
|
fi
|
||||||
$as_echo "$as_me: error: $1" >&2
|
$as_echo "$as_me: error: $2" >&2
|
||||||
as_fn_exit $as_status
|
as_fn_exit $as_status
|
||||||
} # as_fn_error
|
} # as_fn_error
|
||||||
|
|
||||||
@ -533,7 +533,7 @@ test -n "$DJDIR" || exec 7<&0 </dev/null
|
|||||||
exec 6>&1
|
exec 6>&1
|
||||||
|
|
||||||
# Name of the host.
|
# 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.
|
# so uname gets run too.
|
||||||
ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
|
ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
|
||||||
|
|
||||||
@ -616,6 +616,7 @@ nif
|
|||||||
full_xml
|
full_xml
|
||||||
transient_supervisors
|
transient_supervisors
|
||||||
db_type
|
db_type
|
||||||
|
flash_hack
|
||||||
roster_gateway_workaround
|
roster_gateway_workaround
|
||||||
hipe
|
hipe
|
||||||
PAM_LIBS
|
PAM_LIBS
|
||||||
@ -720,6 +721,7 @@ enable_pam
|
|||||||
with_pam
|
with_pam
|
||||||
enable_hipe
|
enable_hipe
|
||||||
enable_roster_gateway_workaround
|
enable_roster_gateway_workaround
|
||||||
|
enable_flash_hack
|
||||||
enable_mssql
|
enable_mssql
|
||||||
enable_transient_supervisors
|
enable_transient_supervisors
|
||||||
enable_full_xml
|
enable_full_xml
|
||||||
@ -800,7 +802,8 @@ do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
case $ac_option in
|
case $ac_option in
|
||||||
*=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
|
*=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
|
||||||
|
*=) ac_optarg= ;;
|
||||||
*) ac_optarg=yes ;;
|
*) ac_optarg=yes ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@ -846,7 +849,7 @@ do
|
|||||||
ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
|
ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
|
||||||
# Reject names that are not valid shell variable names.
|
# Reject names that are not valid shell variable names.
|
||||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
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_orig=$ac_useropt
|
||||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||||
case $ac_user_opts in
|
case $ac_user_opts in
|
||||||
@ -872,7 +875,7 @@ do
|
|||||||
ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
|
ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
|
||||||
# Reject names that are not valid shell variable names.
|
# Reject names that are not valid shell variable names.
|
||||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
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_orig=$ac_useropt
|
||||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||||
case $ac_user_opts in
|
case $ac_user_opts in
|
||||||
@ -1076,7 +1079,7 @@ do
|
|||||||
ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
|
ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
|
||||||
# Reject names that are not valid shell variable names.
|
# Reject names that are not valid shell variable names.
|
||||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
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_orig=$ac_useropt
|
||||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||||
case $ac_user_opts in
|
case $ac_user_opts in
|
||||||
@ -1092,7 +1095,7 @@ do
|
|||||||
ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
|
ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
|
||||||
# Reject names that are not valid shell variable names.
|
# Reject names that are not valid shell variable names.
|
||||||
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
|
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_orig=$ac_useropt
|
||||||
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
|
||||||
case $ac_user_opts in
|
case $ac_user_opts in
|
||||||
@ -1122,8 +1125,8 @@ do
|
|||||||
| --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
|
| --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
|
||||||
x_libraries=$ac_optarg ;;
|
x_libraries=$ac_optarg ;;
|
||||||
|
|
||||||
-*) as_fn_error "unrecognized option: \`$ac_option'
|
-*) as_fn_error $? "unrecognized option: \`$ac_option'
|
||||||
Try \`$0 --help' for more information."
|
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.
|
# Reject names that are not valid shell variable names.
|
||||||
case $ac_envvar in #(
|
case $ac_envvar in #(
|
||||||
'' | [0-9]* | *[!_$as_cr_alnum]* )
|
'' | [0-9]* | *[!_$as_cr_alnum]* )
|
||||||
as_fn_error "invalid variable name: \`$ac_envvar'" ;;
|
as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
|
||||||
esac
|
esac
|
||||||
eval $ac_envvar=\$ac_optarg
|
eval $ac_envvar=\$ac_optarg
|
||||||
export $ac_envvar ;;
|
export $ac_envvar ;;
|
||||||
@ -1149,13 +1152,13 @@ done
|
|||||||
|
|
||||||
if test -n "$ac_prev"; then
|
if test -n "$ac_prev"; then
|
||||||
ac_option=--`echo $ac_prev | sed 's/_/-/g'`
|
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
|
fi
|
||||||
|
|
||||||
if test -n "$ac_unrecognized_opts"; then
|
if test -n "$ac_unrecognized_opts"; then
|
||||||
case $enable_option_checking in
|
case $enable_option_checking in
|
||||||
no) ;;
|
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 ;;
|
*) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
@ -1178,7 +1181,7 @@ do
|
|||||||
[\\/$]* | ?:[\\/]* ) continue;;
|
[\\/$]* | ?:[\\/]* ) continue;;
|
||||||
NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
|
NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
|
||||||
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
|
done
|
||||||
|
|
||||||
# There might be people who depend on the old broken behavior: `$host'
|
# 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$host_alias" != x; then
|
||||||
if test "x$build_alias" = x; then
|
if test "x$build_alias" = x; then
|
||||||
cross_compiling=maybe
|
cross_compiling=maybe
|
||||||
$as_echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
|
$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
|
If a cross compiler is detected then cross compile mode will be used" >&2
|
||||||
elif test "x$build_alias" != "x$host_alias"; then
|
elif test "x$build_alias" != "x$host_alias"; then
|
||||||
cross_compiling=yes
|
cross_compiling=yes
|
||||||
fi
|
fi
|
||||||
@ -1208,9 +1211,9 @@ test "$silent" = yes && exec 6>/dev/null
|
|||||||
ac_pwd=`pwd` && test -n "$ac_pwd" &&
|
ac_pwd=`pwd` && test -n "$ac_pwd" &&
|
||||||
ac_ls_di=`ls -di .` &&
|
ac_ls_di=`ls -di .` &&
|
||||||
ac_pwd_ls_di=`cd "$ac_pwd" && 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" ||
|
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.
|
# Find the source files, if location was not specified.
|
||||||
@ -1249,11 +1252,11 @@ else
|
|||||||
fi
|
fi
|
||||||
if test ! -r "$srcdir/$ac_unique_file"; then
|
if test ! -r "$srcdir/$ac_unique_file"; then
|
||||||
test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
|
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
|
fi
|
||||||
ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
|
ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
|
||||||
ac_abs_confdir=`(
|
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)`
|
pwd)`
|
||||||
# When building in place, set srcdir=.
|
# When building in place, set srcdir=.
|
||||||
if test "$ac_abs_confdir" = "$ac_pwd"; then
|
if test "$ac_abs_confdir" = "$ac_pwd"; then
|
||||||
@ -1368,6 +1371,7 @@ Optional Features:
|
|||||||
--enable-roster-gateway-workaround
|
--enable-roster-gateway-workaround
|
||||||
turn on workaround for processing gateway
|
turn on workaround for processing gateway
|
||||||
subscriptions (default: no)
|
subscriptions (default: no)
|
||||||
|
--enable-flash-hack support Adobe Flash client XML (default: no)
|
||||||
--enable-mssql use Microsoft SQL Server database (default: no,
|
--enable-mssql use Microsoft SQL Server database (default: no,
|
||||||
requires --enable-odbc)
|
requires --enable-odbc)
|
||||||
--enable-transient_supervisors
|
--enable-transient_supervisors
|
||||||
@ -1471,9 +1475,9 @@ test -n "$ac_init_help" && exit $ac_status
|
|||||||
if $ac_init_version; then
|
if $ac_init_version; then
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
ejabberd configure 2.1.x
|
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
|
This configure script is free software; the Free Software Foundation
|
||||||
gives unlimited permission to copy, distribute and modify it.
|
gives unlimited permission to copy, distribute and modify it.
|
||||||
_ACEOF
|
_ACEOF
|
||||||
@ -1589,7 +1593,7 @@ $as_echo "$ac_try_echo"; } >&5
|
|||||||
mv -f conftest.er1 conftest.err
|
mv -f conftest.er1 conftest.err
|
||||||
fi
|
fi
|
||||||
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
$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 -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
|
||||||
test ! -s conftest.err
|
test ! -s conftest.err
|
||||||
}; then :
|
}; then :
|
||||||
@ -1613,10 +1617,10 @@ fi
|
|||||||
ac_fn_c_check_header_mongrel ()
|
ac_fn_c_check_header_mongrel ()
|
||||||
{
|
{
|
||||||
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
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 "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
||||||
$as_echo_n "checking for $2... " >&6; }
|
$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
|
$as_echo_n "(cached) " >&6
|
||||||
fi
|
fi
|
||||||
eval ac_res=\$$3
|
eval ac_res=\$$3
|
||||||
@ -1652,7 +1656,7 @@ if ac_fn_c_try_cpp "$LINENO"; then :
|
|||||||
else
|
else
|
||||||
ac_header_preproc=no
|
ac_header_preproc=no
|
||||||
fi
|
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 "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
|
||||||
$as_echo "$ac_header_preproc" >&6; }
|
$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: 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:${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;}
|
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
|
||||||
( cat <<\_ASBOX
|
( $as_echo "## --------------------------------------- ##
|
||||||
## --------------------------------------- ##
|
|
||||||
## Report this to ejabberd@process-one.net ##
|
## Report this to ejabberd@process-one.net ##
|
||||||
## --------------------------------------- ##
|
## --------------------------------------- ##"
|
||||||
_ASBOX
|
|
||||||
) | sed "s/^/$as_me: WARNING: /" >&2
|
) | sed "s/^/$as_me: WARNING: /" >&2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
||||||
$as_echo_n "checking for $2... " >&6; }
|
$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
|
$as_echo_n "(cached) " >&6
|
||||||
else
|
else
|
||||||
eval "$3=\$ac_header_compiler"
|
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_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 "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
|
||||||
$as_echo_n "checking for $2... " >&6; }
|
$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
|
$as_echo_n "(cached) " >&6
|
||||||
else
|
else
|
||||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
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.
|
running configure, to aid debugging if configure makes a mistake.
|
||||||
|
|
||||||
It was created by ejabberd $as_me 2.1.x, which was
|
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 $@
|
$ $0 $@
|
||||||
|
|
||||||
@ -1927,11 +1929,9 @@ trap 'exit_status=$?
|
|||||||
{
|
{
|
||||||
echo
|
echo
|
||||||
|
|
||||||
cat <<\_ASBOX
|
$as_echo "## ---------------- ##
|
||||||
## ---------------- ##
|
|
||||||
## Cache variables. ##
|
## Cache variables. ##
|
||||||
## ---------------- ##
|
## ---------------- ##"
|
||||||
_ASBOX
|
|
||||||
echo
|
echo
|
||||||
# The following way of writing the cache mishandles newlines in values,
|
# 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
|
echo
|
||||||
|
|
||||||
cat <<\_ASBOX
|
$as_echo "## ----------------- ##
|
||||||
## ----------------- ##
|
|
||||||
## Output variables. ##
|
## Output variables. ##
|
||||||
## ----------------- ##
|
## ----------------- ##"
|
||||||
_ASBOX
|
|
||||||
echo
|
echo
|
||||||
for ac_var in $ac_subst_vars
|
for ac_var in $ac_subst_vars
|
||||||
do
|
do
|
||||||
@ -1982,11 +1980,9 @@ _ASBOX
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
if test -n "$ac_subst_files"; then
|
if test -n "$ac_subst_files"; then
|
||||||
cat <<\_ASBOX
|
$as_echo "## ------------------- ##
|
||||||
## ------------------- ##
|
|
||||||
## File substitutions. ##
|
## File substitutions. ##
|
||||||
## ------------------- ##
|
## ------------------- ##"
|
||||||
_ASBOX
|
|
||||||
echo
|
echo
|
||||||
for ac_var in $ac_subst_files
|
for ac_var in $ac_subst_files
|
||||||
do
|
do
|
||||||
@ -2000,11 +1996,9 @@ _ASBOX
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if test -s confdefs.h; then
|
if test -s confdefs.h; then
|
||||||
cat <<\_ASBOX
|
$as_echo "## ----------- ##
|
||||||
## ----------- ##
|
|
||||||
## confdefs.h. ##
|
## confdefs.h. ##
|
||||||
## ----------- ##
|
## ----------- ##"
|
||||||
_ASBOX
|
|
||||||
echo
|
echo
|
||||||
cat confdefs.h
|
cat confdefs.h
|
||||||
echo
|
echo
|
||||||
@ -2059,7 +2053,12 @@ _ACEOF
|
|||||||
ac_site_file1=NONE
|
ac_site_file1=NONE
|
||||||
ac_site_file2=NONE
|
ac_site_file2=NONE
|
||||||
if test -n "$CONFIG_SITE"; then
|
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
|
elif test "x$prefix" != xNONE; then
|
||||||
ac_site_file1=$prefix/share/config.site
|
ac_site_file1=$prefix/share/config.site
|
||||||
ac_site_file2=$prefix/etc/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:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
|
||||||
$as_echo "$as_me: loading site script $ac_site_file" >&6;}
|
$as_echo "$as_me: loading site script $ac_site_file" >&6;}
|
||||||
sed 's/^/| /' "$ac_site_file" >&5
|
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
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -2150,7 +2153,7 @@ if $ac_cache_corrupted; then
|
|||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$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:${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_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
|
fi
|
||||||
## -------------------- ##
|
## -------------------- ##
|
||||||
## Main body of script. ##
|
## Main body of script. ##
|
||||||
@ -2465,8 +2468,8 @@ fi
|
|||||||
|
|
||||||
test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
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_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||||
as_fn_error "no acceptable C compiler found in \$PATH
|
as_fn_error $? "no acceptable C compiler found in \$PATH
|
||||||
See \`config.log' for more details." "$LINENO" 5; }
|
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||||
|
|
||||||
# Provide some information about the compiler.
|
# Provide some information about the compiler.
|
||||||
$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
|
$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:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||||
{ as_fn_set_status 77
|
as_fn_error 77 "C compiler cannot create executables
|
||||||
as_fn_error "C compiler cannot create executables
|
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||||
See \`config.log' for more details." "$LINENO" 5; }; }
|
|
||||||
else
|
else
|
||||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||||
$as_echo "yes" >&6; }
|
$as_echo "yes" >&6; }
|
||||||
@ -2624,8 +2626,8 @@ done
|
|||||||
else
|
else
|
||||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||||
as_fn_error "cannot compute suffix of executables: cannot compile and link
|
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
|
||||||
See \`config.log' for more details." "$LINENO" 5; }
|
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||||
fi
|
fi
|
||||||
rm -f conftest conftest$ac_cv_exeext
|
rm -f conftest conftest$ac_cv_exeext
|
||||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
|
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
|
||||||
@ -2682,9 +2684,9 @@ $as_echo "$ac_try_echo"; } >&5
|
|||||||
else
|
else
|
||||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$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'.
|
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
|
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:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||||
as_fn_error "cannot compute suffix of object files: cannot compile
|
as_fn_error $? "cannot compute suffix of object files: cannot compile
|
||||||
See \`config.log' for more details." "$LINENO" 5; }
|
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||||
fi
|
fi
|
||||||
rm -f conftest.$ac_cv_objext conftest.$ac_ext
|
rm -f conftest.$ac_cv_objext conftest.$ac_ext
|
||||||
fi
|
fi
|
||||||
@ -2960,7 +2962,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
|
|||||||
$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
|
$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
|
||||||
set x ${MAKE-make}
|
set x ${MAKE-make}
|
||||||
ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
|
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
|
$as_echo_n "(cached) " >&6
|
||||||
else
|
else
|
||||||
cat >conftest.make <<\_ACEOF
|
cat >conftest.make <<\_ACEOF
|
||||||
@ -3202,7 +3204,7 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
if test "z$ERLC" = "z" || test "z$ERL" = "z"; then
|
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
|
fi
|
||||||
|
|
||||||
|
|
||||||
@ -3260,15 +3262,15 @@ libpath(App) ->
|
|||||||
_EOF
|
_EOF
|
||||||
|
|
||||||
if ! $ERLC conftest.erl; then
|
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
|
fi
|
||||||
|
|
||||||
if ! $ERL -s conftest -noshell; then
|
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
|
fi
|
||||||
|
|
||||||
if ! test -f conftest.out; then
|
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
|
fi
|
||||||
|
|
||||||
# First line
|
# First line
|
||||||
@ -3497,7 +3499,7 @@ else
|
|||||||
# Broken: fails on valid input.
|
# Broken: fails on valid input.
|
||||||
continue
|
continue
|
||||||
fi
|
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
|
# OK, works on sane cases. Now check whether nonexistent headers
|
||||||
# can be detected and how.
|
# can be detected and how.
|
||||||
@ -3513,11 +3515,11 @@ else
|
|||||||
ac_preproc_ok=:
|
ac_preproc_ok=:
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
rm -f conftest.err conftest.$ac_ext
|
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||||
|
|
||||||
done
|
done
|
||||||
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
|
# 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 :
|
if $ac_preproc_ok; then :
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
@ -3556,7 +3558,7 @@ else
|
|||||||
# Broken: fails on valid input.
|
# Broken: fails on valid input.
|
||||||
continue
|
continue
|
||||||
fi
|
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
|
# OK, works on sane cases. Now check whether nonexistent headers
|
||||||
# can be detected and how.
|
# can be detected and how.
|
||||||
@ -3572,18 +3574,18 @@ else
|
|||||||
ac_preproc_ok=:
|
ac_preproc_ok=:
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
rm -f conftest.err conftest.$ac_ext
|
rm -f conftest.err conftest.i conftest.$ac_ext
|
||||||
|
|
||||||
done
|
done
|
||||||
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
|
# 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 :
|
if $ac_preproc_ok; then :
|
||||||
|
|
||||||
else
|
else
|
||||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||||
as_fn_error "C preprocessor \"$CPP\" fails sanity check
|
as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
|
||||||
See \`config.log' for more details." "$LINENO" 5; }
|
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ac_ext=c
|
ac_ext=c
|
||||||
@ -3644,7 +3646,7 @@ esac
|
|||||||
done
|
done
|
||||||
IFS=$as_save_IFS
|
IFS=$as_save_IFS
|
||||||
if test -z "$ac_cv_path_GREP"; then
|
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
|
fi
|
||||||
else
|
else
|
||||||
ac_cv_path_GREP=$GREP
|
ac_cv_path_GREP=$GREP
|
||||||
@ -3710,7 +3712,7 @@ esac
|
|||||||
done
|
done
|
||||||
IFS=$as_save_IFS
|
IFS=$as_save_IFS
|
||||||
if test -z "$ac_cv_path_EGREP"; then
|
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
|
fi
|
||||||
else
|
else
|
||||||
ac_cv_path_EGREP=$EGREP
|
ac_cv_path_EGREP=$EGREP
|
||||||
@ -3842,8 +3844,7 @@ do :
|
|||||||
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
|
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
|
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
|
||||||
"
|
"
|
||||||
eval as_val=\$$as_ac_Header
|
if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
|
||||||
if test "x$as_val" = x""yes; then :
|
|
||||||
cat >>confdefs.h <<_ACEOF
|
cat >>confdefs.h <<_ACEOF
|
||||||
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
|
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
|
||||||
_ACEOF
|
_ACEOF
|
||||||
@ -3911,7 +3912,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if test $expat_found = no; then
|
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
|
fi
|
||||||
expat_save_CFLAGS="$CFLAGS"
|
expat_save_CFLAGS="$CFLAGS"
|
||||||
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
|
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
|
||||||
@ -3932,7 +3933,7 @@ fi
|
|||||||
done
|
done
|
||||||
|
|
||||||
if test $expat_found = no; then
|
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
|
fi
|
||||||
CFLAGS="$expat_save_CFLAGS"
|
CFLAGS="$expat_save_CFLAGS"
|
||||||
CPPFLAGS="$expat_save_CPPFLAGS"
|
CPPFLAGS="$expat_save_CPPFLAGS"
|
||||||
@ -4469,7 +4470,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if test $zlib_found = no; then
|
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
|
fi
|
||||||
zlib_save_CFLAGS="$CFLAGS"
|
zlib_save_CFLAGS="$CFLAGS"
|
||||||
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
||||||
@ -4490,7 +4491,7 @@ fi
|
|||||||
done
|
done
|
||||||
|
|
||||||
if test $zlib_found = no; then
|
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
|
fi
|
||||||
CFLAGS="$zlib_save_CFLAGS"
|
CFLAGS="$zlib_save_CFLAGS"
|
||||||
CPPFLAGS="$zlib_save_CPPFLAGS"
|
CPPFLAGS="$zlib_save_CPPFLAGS"
|
||||||
@ -4581,7 +4582,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if test $pam_found = no; then
|
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
|
fi
|
||||||
pam_save_CFLAGS="$CFLAGS"
|
pam_save_CFLAGS="$CFLAGS"
|
||||||
CFLAGS="$CFLAGS $PAM_CFLAGS"
|
CFLAGS="$CFLAGS $PAM_CFLAGS"
|
||||||
@ -4602,7 +4603,7 @@ fi
|
|||||||
done
|
done
|
||||||
|
|
||||||
if test $pam_found = no; then
|
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
|
fi
|
||||||
CFLAGS="$pam_save_CFLAGS"
|
CFLAGS="$pam_save_CFLAGS"
|
||||||
CPPFLAGS="$pam_save_CPPFLAGS"
|
CPPFLAGS="$pam_save_CPPFLAGS"
|
||||||
@ -4617,7 +4618,7 @@ if test "${enable_hipe+set}" = set; then :
|
|||||||
enableval=$enable_hipe; case "${enableval}" in
|
enableval=$enable_hipe; case "${enableval}" in
|
||||||
yes) hipe=true ;;
|
yes) hipe=true ;;
|
||||||
no) hipe=false ;;
|
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
|
esac
|
||||||
else
|
else
|
||||||
hipe=false
|
hipe=false
|
||||||
@ -4630,7 +4631,7 @@ if test "${enable_roster_gateway_workaround+set}" = set; then :
|
|||||||
enableval=$enable_roster_gateway_workaround; case "${enableval}" in
|
enableval=$enable_roster_gateway_workaround; case "${enableval}" in
|
||||||
yes) roster_gateway_workaround=true ;;
|
yes) roster_gateway_workaround=true ;;
|
||||||
no) roster_gateway_workaround=false ;;
|
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
|
esac
|
||||||
else
|
else
|
||||||
roster_gateway_workaround=false
|
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.
|
# Check whether --enable-mssql was given.
|
||||||
if test "${enable_mssql+set}" = set; then :
|
if test "${enable_mssql+set}" = set; then :
|
||||||
enableval=$enable_mssql; case "${enableval}" in
|
enableval=$enable_mssql; case "${enableval}" in
|
||||||
yes) db_type=mssql ;;
|
yes) db_type=mssql ;;
|
||||||
no) db_type=generic ;;
|
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
|
esac
|
||||||
else
|
else
|
||||||
db_type=generic
|
db_type=generic
|
||||||
@ -4656,7 +4670,7 @@ if test "${enable_transient_supervisors+set}" = set; then :
|
|||||||
enableval=$enable_transient_supervisors; case "${enableval}" in
|
enableval=$enable_transient_supervisors; case "${enableval}" in
|
||||||
yes) transient_supervisors=true ;;
|
yes) transient_supervisors=true ;;
|
||||||
no) transient_supervisors=false ;;
|
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
|
esac
|
||||||
else
|
else
|
||||||
transient_supervisors=true
|
transient_supervisors=true
|
||||||
@ -4669,7 +4683,7 @@ if test "${enable_full_xml+set}" = set; then :
|
|||||||
enableval=$enable_full_xml; case "${enableval}" in
|
enableval=$enable_full_xml; case "${enableval}" in
|
||||||
yes) full_xml=true ;;
|
yes) full_xml=true ;;
|
||||||
no) full_xml=false ;;
|
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
|
esac
|
||||||
else
|
else
|
||||||
full_xml=false
|
full_xml=false
|
||||||
@ -4682,7 +4696,7 @@ if test "${enable_nif+set}" = set; then :
|
|||||||
enableval=$enable_nif; case "${enableval}" in
|
enableval=$enable_nif; case "${enableval}" in
|
||||||
yes) nif=true ;;
|
yes) nif=true ;;
|
||||||
no) nif=false ;;
|
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
|
esac
|
||||||
else
|
else
|
||||||
nif=false
|
nif=false
|
||||||
@ -4780,7 +4794,7 @@ done
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if test x${have_openssl} != xyes; then
|
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
|
fi
|
||||||
|
|
||||||
|
|
||||||
@ -4835,16 +4849,22 @@ fi
|
|||||||
|
|
||||||
ac_aux_dir=
|
ac_aux_dir=
|
||||||
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
|
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
|
||||||
for ac_t in install-sh install.sh shtool; do
|
if test -f "$ac_dir/install-sh"; then
|
||||||
if test -f "$ac_dir/$ac_t"; then
|
|
||||||
ac_aux_dir=$ac_dir
|
ac_aux_dir=$ac_dir
|
||||||
ac_install_sh="$ac_aux_dir/$ac_t -c"
|
ac_install_sh="$ac_aux_dir/install-sh -c"
|
||||||
break 2
|
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
|
fi
|
||||||
done
|
done
|
||||||
done
|
|
||||||
if test -z "$ac_aux_dir"; then
|
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
|
fi
|
||||||
|
|
||||||
# These three variables are undocumented and unsupported,
|
# 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.
|
# Make sure we can run config.sub.
|
||||||
$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
|
$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 "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
|
||||||
$as_echo_n "checking build system type... " >&6; }
|
$as_echo_n "checking build system type... " >&6; }
|
||||||
@ -4869,16 +4889,16 @@ else
|
|||||||
test "x$ac_build_alias" = x &&
|
test "x$ac_build_alias" = x &&
|
||||||
ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
|
ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
|
||||||
test "x$ac_build_alias" = x &&
|
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` ||
|
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
|
fi
|
||||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
|
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
|
||||||
$as_echo "$ac_cv_build" >&6; }
|
$as_echo "$ac_cv_build" >&6; }
|
||||||
case $ac_cv_build in
|
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
|
esac
|
||||||
build=$ac_cv_build
|
build=$ac_cv_build
|
||||||
ac_save_IFS=$IFS; IFS='-'
|
ac_save_IFS=$IFS; IFS='-'
|
||||||
@ -4903,7 +4923,7 @@ else
|
|||||||
ac_cv_host=$ac_cv_build
|
ac_cv_host=$ac_cv_build
|
||||||
else
|
else
|
||||||
ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
|
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
|
||||||
|
|
||||||
fi
|
fi
|
||||||
@ -4911,7 +4931,7 @@ fi
|
|||||||
$as_echo "$ac_cv_host" >&6; }
|
$as_echo "$ac_cv_host" >&6; }
|
||||||
case $ac_cv_host in
|
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
|
esac
|
||||||
host=$ac_cv_host
|
host=$ac_cv_host
|
||||||
ac_save_IFS=$IFS; IFS='-'
|
ac_save_IFS=$IFS; IFS='-'
|
||||||
@ -4936,7 +4956,7 @@ else
|
|||||||
ac_cv_target=$ac_cv_host
|
ac_cv_target=$ac_cv_host
|
||||||
else
|
else
|
||||||
ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` ||
|
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
|
||||||
|
|
||||||
fi
|
fi
|
||||||
@ -4944,7 +4964,7 @@ fi
|
|||||||
$as_echo "$ac_cv_target" >&6; }
|
$as_echo "$ac_cv_target" >&6; }
|
||||||
case $ac_cv_target in
|
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
|
esac
|
||||||
target=$ac_cv_target
|
target=$ac_cv_target
|
||||||
ac_save_IFS=$IFS; IFS='-'
|
ac_save_IFS=$IFS; IFS='-'
|
||||||
@ -5088,8 +5108,8 @@ fi
|
|||||||
if test "$cross_compiling" = yes; then :
|
if test "$cross_compiling" = yes; then :
|
||||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||||
as_fn_error "cannot run test program while cross compiling
|
as_fn_error $? "cannot run test program while cross compiling
|
||||||
See \`config.log' for more details." "$LINENO" 5; }
|
See \`config.log' for more details" "$LINENO" 5 ; }
|
||||||
else
|
else
|
||||||
cat > conftest.$ac_ext <<_ACEOF
|
cat > conftest.$ac_ext <<_ACEOF
|
||||||
-module(conftest).
|
-module(conftest).
|
||||||
@ -5243,6 +5263,7 @@ DEFS=`sed -n "$ac_script" confdefs.h`
|
|||||||
|
|
||||||
ac_libobjs=
|
ac_libobjs=
|
||||||
ac_ltlibobjs=
|
ac_ltlibobjs=
|
||||||
|
U=
|
||||||
for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
|
for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
|
||||||
# 1. Remove the extension, and $U if already installed.
|
# 1. Remove the extension, and $U if already installed.
|
||||||
ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
|
ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
|
||||||
@ -5404,19 +5425,19 @@ export LANGUAGE
|
|||||||
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
|
(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
|
# 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
|
# 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_fn_error ()
|
||||||
{
|
{
|
||||||
as_status=$?; test $as_status -eq 0 && as_status=1
|
as_status=$1; test $as_status -eq 0 && as_status=1
|
||||||
if test "$3"; then
|
if test "$4"; then
|
||||||
as_lineno=${as_lineno-"$2"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
|
||||||
$as_echo "$as_me:${as_lineno-$LINENO}: error: $1" >&$3
|
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
|
||||||
fi
|
fi
|
||||||
$as_echo "$as_me: error: $1" >&2
|
$as_echo "$as_me: error: $2" >&2
|
||||||
as_fn_exit $as_status
|
as_fn_exit $as_status
|
||||||
} # as_fn_error
|
} # as_fn_error
|
||||||
|
|
||||||
@ -5612,7 +5633,7 @@ $as_echo X"$as_dir" |
|
|||||||
test -d "$as_dir" && break
|
test -d "$as_dir" && break
|
||||||
done
|
done
|
||||||
test -z "$as_dirs" || eval "mkdir $as_dirs"
|
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
|
} # as_fn_mkdir_p
|
||||||
@ -5666,7 +5687,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
|||||||
# values after options handling.
|
# values after options handling.
|
||||||
ac_log="
|
ac_log="
|
||||||
This file was extended by ejabberd $as_me 2.1.x, which was
|
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_FILES = $CONFIG_FILES
|
||||||
CONFIG_HEADERS = $CONFIG_HEADERS
|
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_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||||
ac_cs_version="\\
|
ac_cs_version="\\
|
||||||
ejabberd config.status 2.1.x
|
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\\"
|
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
|
This config.status script is free software; the Free Software Foundation
|
||||||
gives unlimited permission to copy, distribute and modify it."
|
gives unlimited permission to copy, distribute and modify it."
|
||||||
|
|
||||||
@ -5737,11 +5758,16 @@ ac_need_defaults=:
|
|||||||
while test $# != 0
|
while test $# != 0
|
||||||
do
|
do
|
||||||
case $1 in
|
case $1 in
|
||||||
--*=*)
|
--*=?*)
|
||||||
ac_option=`expr "X$1" : 'X\([^=]*\)='`
|
ac_option=`expr "X$1" : 'X\([^=]*\)='`
|
||||||
ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
|
ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
|
||||||
ac_shift=:
|
ac_shift=:
|
||||||
;;
|
;;
|
||||||
|
--*=)
|
||||||
|
ac_option=`expr "X$1" : 'X\([^=]*\)='`
|
||||||
|
ac_optarg=
|
||||||
|
ac_shift=:
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
ac_option=$1
|
ac_option=$1
|
||||||
ac_optarg=$2
|
ac_optarg=$2
|
||||||
@ -5763,6 +5789,7 @@ do
|
|||||||
$ac_shift
|
$ac_shift
|
||||||
case $ac_optarg in
|
case $ac_optarg in
|
||||||
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
|
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
|
||||||
|
'') as_fn_error $? "missing file argument" ;;
|
||||||
esac
|
esac
|
||||||
as_fn_append CONFIG_FILES " '$ac_optarg'"
|
as_fn_append CONFIG_FILES " '$ac_optarg'"
|
||||||
ac_need_defaults=false;;
|
ac_need_defaults=false;;
|
||||||
@ -5773,7 +5800,7 @@ do
|
|||||||
ac_cs_silent=: ;;
|
ac_cs_silent=: ;;
|
||||||
|
|
||||||
# This is an error.
|
# This is an error.
|
||||||
-*) as_fn_error "unrecognized option: \`$1'
|
-*) as_fn_error $? "unrecognized option: \`$1'
|
||||||
Try \`$0 --help' for more information." ;;
|
Try \`$0 --help' for more information." ;;
|
||||||
|
|
||||||
*) as_fn_append ac_config_targets " $1"
|
*) as_fn_append ac_config_targets " $1"
|
||||||
@ -5836,7 +5863,7 @@ do
|
|||||||
"$make_odbc") CONFIG_FILES="$CONFIG_FILES $make_odbc" ;;
|
"$make_odbc") CONFIG_FILES="$CONFIG_FILES $make_odbc" ;;
|
||||||
"$make_ejabberd_zlib") CONFIG_FILES="$CONFIG_FILES $make_ejabberd_zlib" ;;
|
"$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
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -5872,7 +5899,7 @@ $debug ||
|
|||||||
{
|
{
|
||||||
tmp=./conf$$-$RANDOM
|
tmp=./conf$$-$RANDOM
|
||||||
(umask 077 && mkdir "$tmp")
|
(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.
|
# Set up the scripts for CONFIG_FILES section.
|
||||||
# No need to generate them if there are no CONFIG_FILES.
|
# No need to generate them if there are no CONFIG_FILES.
|
||||||
@ -5889,7 +5916,7 @@ if test "x$ac_cr" = x; then
|
|||||||
fi
|
fi
|
||||||
ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
|
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
|
if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
|
||||||
ac_cs_awk_cr='\r'
|
ac_cs_awk_cr='\\r'
|
||||||
else
|
else
|
||||||
ac_cs_awk_cr=$ac_cr
|
ac_cs_awk_cr=$ac_cr
|
||||||
fi
|
fi
|
||||||
@ -5903,18 +5930,18 @@ _ACEOF
|
|||||||
echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
|
echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
|
||||||
echo "_ACEOF"
|
echo "_ACEOF"
|
||||||
} >conf$$subs.sh ||
|
} >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_num=`echo "$ac_subst_vars" | grep -c '$'`
|
ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
|
||||||
ac_delim='%!_!# '
|
ac_delim='%!_!# '
|
||||||
for ac_last_try in false false false false false :; do
|
for ac_last_try in false false false false false :; do
|
||||||
. ./conf$$subs.sh ||
|
. ./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`
|
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
|
if test $ac_delim_n = $ac_delim_num; then
|
||||||
break
|
break
|
||||||
elif $ac_last_try; then
|
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
|
else
|
||||||
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
|
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
|
||||||
fi
|
fi
|
||||||
@ -6003,20 +6030,28 @@ if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
|
|||||||
else
|
else
|
||||||
cat
|
cat
|
||||||
fi < "$tmp/subs1.awk" > "$tmp/subs.awk" \
|
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
|
_ACEOF
|
||||||
|
|
||||||
# VPATH may cause trouble with some makes, so we remove $(srcdir),
|
# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
|
||||||
# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
|
# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
|
||||||
# trailing colons and then remove the whole line if VPATH becomes empty
|
# trailing colons and then remove the whole line if VPATH becomes empty
|
||||||
# (actually we leave an empty line to preserve line numbers).
|
# (actually we leave an empty line to preserve line numbers).
|
||||||
if test "x$srcdir" = x.; then
|
if test "x$srcdir" = x.; then
|
||||||
ac_vpsub='/^[ ]*VPATH[ ]*=/{
|
ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
|
||||||
s/:*\$(srcdir):*/:/
|
h
|
||||||
s/:*\${srcdir}:*/:/
|
s///
|
||||||
s/:*@srcdir@:*/:/
|
s/^/:/
|
||||||
s/^\([^=]*=[ ]*\):*/\1/
|
s/[ ]*$/:/
|
||||||
|
s/:\$(srcdir):/:/g
|
||||||
|
s/:\${srcdir}:/:/g
|
||||||
|
s/:@srcdir@:/:/g
|
||||||
|
s/^:*//
|
||||||
s/:*$//
|
s/:*$//
|
||||||
|
x
|
||||||
|
s/\(=[ ]*\).*/\1/
|
||||||
|
G
|
||||||
|
s/\n//
|
||||||
s/^[^=]*=[ ]*$//
|
s/^[^=]*=[ ]*$//
|
||||||
}'
|
}'
|
||||||
fi
|
fi
|
||||||
@ -6034,7 +6069,7 @@ do
|
|||||||
esac
|
esac
|
||||||
case $ac_mode$ac_tag in
|
case $ac_mode$ac_tag in
|
||||||
:[FHL]*:*);;
|
:[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=-:-;;
|
||||||
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
|
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
|
||||||
esac
|
esac
|
||||||
@ -6062,7 +6097,7 @@ do
|
|||||||
[\\/$]*) false;;
|
[\\/$]*) false;;
|
||||||
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
|
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
|
||||||
esac ||
|
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
|
esac
|
||||||
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
|
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
|
||||||
as_fn_append ac_file_inputs " '$ac_f'"
|
as_fn_append ac_file_inputs " '$ac_f'"
|
||||||
@ -6089,7 +6124,7 @@ $as_echo "$as_me: creating $ac_file" >&6;}
|
|||||||
|
|
||||||
case $ac_tag in
|
case $ac_tag in
|
||||||
*:-:* | *:-) cat >"$tmp/stdin" \
|
*:-:* | *:-) 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
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -6215,22 +6250,22 @@ s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
|
|||||||
$ac_datarootdir_hack
|
$ac_datarootdir_hack
|
||||||
"
|
"
|
||||||
eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$tmp/subs.awk" >$tmp/out \
|
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" &&
|
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 -n "$ac_out"; } &&
|
||||||
{ ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$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'
|
{ $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'
|
$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"
|
rm -f "$tmp/stdin"
|
||||||
case $ac_file in
|
case $ac_file in
|
||||||
-) cat "$tmp/out" && rm -f "$tmp/out";;
|
-) cat "$tmp/out" && rm -f "$tmp/out";;
|
||||||
*) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
|
*) rm -f "$ac_file" && mv "$tmp/out" "$ac_file";;
|
||||||
esac \
|
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
|
ac_clean_files=$ac_clean_files_save
|
||||||
|
|
||||||
test $ac_write_fail = 0 ||
|
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.
|
# 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
|
exec 5>>config.log
|
||||||
# Use ||, not &&, to avoid exiting from the if with $? = 1, which
|
# Use ||, not &&, to avoid exiting from the if with $? = 1, which
|
||||||
# would make configure fail if this is the last instruction.
|
# would make configure fail if this is the last instruction.
|
||||||
$ac_cs_success || as_fn_exit $?
|
$ac_cs_success || as_fn_exit 1
|
||||||
fi
|
fi
|
||||||
if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
|
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
|
{ $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])
|
esac],[roster_gateway_workaround=false])
|
||||||
AC_SUBST(roster_gateway_workaround)
|
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_ARG_ENABLE(mssql,
|
||||||
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
|
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
|
||||||
[case "${enableval}" in
|
[case "${enableval}" in
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{application, ejabberd,
|
{application, ejabberd,
|
||||||
[{description, "ejabberd"},
|
[{description, "ejabberd"},
|
||||||
{vsn, "2.1.x"},
|
{vsn, "2.2.x"},
|
||||||
{modules, [acl,
|
{modules, [acl,
|
||||||
adhoc,
|
adhoc,
|
||||||
configure,
|
configure,
|
||||||
|
@ -31,6 +31,13 @@
|
|||||||
-define(CONFIG_PATH, "ejabberd.cfg").
|
-define(CONFIG_PATH, "ejabberd.cfg").
|
||||||
-define(LOG_PATH, "ejabberd.log").
|
-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(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/").
|
||||||
|
|
||||||
-define(S2STIMEOUT, 600000).
|
-define(S2STIMEOUT, 600000).
|
||||||
|
@ -68,6 +68,8 @@ start(normal, _Args) ->
|
|||||||
%ejabberd_debug:fprof_start(),
|
%ejabberd_debug:fprof_start(),
|
||||||
maybe_add_nameservers(),
|
maybe_add_nameservers(),
|
||||||
start_modules(),
|
start_modules(),
|
||||||
|
ejabberd_cluster:announce(),
|
||||||
|
ejabberd_node_groups:start(),
|
||||||
ejabberd_listener:start_listeners(),
|
ejabberd_listener:start_listeners(),
|
||||||
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
|
||||||
Sup;
|
Sup;
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
anonymous_user_exist/2,
|
anonymous_user_exist/2,
|
||||||
allow_multiple_connections/1,
|
allow_multiple_connections/1,
|
||||||
register_connection/3,
|
register_connection/3,
|
||||||
|
unregister_migrated_connection/3,
|
||||||
unregister_connection/3
|
unregister_connection/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
@ -61,12 +62,16 @@
|
|||||||
%% Register to login / logout events
|
%% Register to login / logout events
|
||||||
start(Host) ->
|
start(Host) ->
|
||||||
%% TODO: Check cluster mode
|
%% TODO: Check cluster mode
|
||||||
|
update_tables(),
|
||||||
mnesia:create_table(anonymous, [{ram_copies, [node()]},
|
mnesia:create_table(anonymous, [{ram_copies, [node()]},
|
||||||
{type, bag},
|
{type, bag}, {local_content, true},
|
||||||
{attributes, record_info(fields, anonymous)}]),
|
{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
|
%% The hooks are needed to add / remove users from the anonymous tables
|
||||||
ejabberd_hooks:add(sm_register_connection_hook, Host,
|
ejabberd_hooks:add(sm_register_connection_hook, Host,
|
||||||
?MODULE, register_connection, 100),
|
?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,
|
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
||||||
?MODULE, unregister_connection, 100),
|
?MODULE, unregister_connection, 100),
|
||||||
ok.
|
ok.
|
||||||
@ -123,11 +128,18 @@ anonymous_user_exist(User, Server) ->
|
|||||||
LUser = jlib:nodeprep(User),
|
LUser = jlib:nodeprep(User),
|
||||||
LServer = jlib:nameprep(Server),
|
LServer = jlib:nameprep(Server),
|
||||||
US = {LUser, LServer},
|
US = {LUser, LServer},
|
||||||
case catch mnesia:dirty_read({anonymous, US}) of
|
Ss = case ejabberd_cluster:get_node(US) of
|
||||||
[] ->
|
Node when Node == node() ->
|
||||||
false;
|
catch mnesia:dirty_read({anonymous, US});
|
||||||
|
Node ->
|
||||||
|
catch rpc:call(Node, mnesia, dirty_read,
|
||||||
|
[{anonymous, US}], 5000)
|
||||||
|
end,
|
||||||
|
case Ss of
|
||||||
[_H|_T] ->
|
[_H|_T] ->
|
||||||
true
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Remove connection from Mnesia tables
|
%% Remove connection from Mnesia tables
|
||||||
@ -136,7 +148,7 @@ remove_connection(SID, LUser, LServer) ->
|
|||||||
F = fun() ->
|
F = fun() ->
|
||||||
mnesia:delete_object({anonymous, US, SID})
|
mnesia:delete_object({anonymous, US, SID})
|
||||||
end,
|
end,
|
||||||
mnesia:transaction(F).
|
mnesia:async_dirty(F).
|
||||||
|
|
||||||
%% Register connection
|
%% Register connection
|
||||||
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
|
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
|
case AuthModule == ?MODULE of
|
||||||
true ->
|
true ->
|
||||||
US = {LUser, LServer},
|
US = {LUser, LServer},
|
||||||
mnesia:sync_dirty(
|
mnesia:async_dirty(
|
||||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||||
end);
|
end);
|
||||||
false ->
|
false ->
|
||||||
@ -157,6 +169,10 @@ unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
|
|||||||
LUser, LServer),
|
LUser, LServer),
|
||||||
remove_connection(SID, 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
|
%% Launch the hook to purge user data only for anonymous users
|
||||||
purge_hook(false, _LUser, _LServer) ->
|
purge_hook(false, _LUser, _LServer) ->
|
||||||
ok;
|
ok;
|
||||||
@ -246,3 +262,11 @@ remove_user(_User, _Server, _Password) ->
|
|||||||
|
|
||||||
plain_password_required() ->
|
plain_password_required() ->
|
||||||
false.
|
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
|
%% External exports
|
||||||
-export([start/2,
|
-export([start/2,
|
||||||
stop/1,
|
stop/1,
|
||||||
start_link/2,
|
start_link/3,
|
||||||
send_text/2,
|
send_text/2,
|
||||||
send_element/2,
|
send_element/2,
|
||||||
socket_type/0,
|
socket_type/0,
|
||||||
get_presence/1,
|
get_presence/1,
|
||||||
get_subscribed/1]).
|
get_subscribed/1]).
|
||||||
|
|
||||||
|
%% API:
|
||||||
|
-export([add_rosteritem/3, del_rosteritem/2]).
|
||||||
|
|
||||||
%% gen_fsm callbacks
|
%% gen_fsm callbacks
|
||||||
-export([init/1,
|
-export([init/1,
|
||||||
wait_for_stream/2,
|
wait_for_stream/2,
|
||||||
@ -56,69 +59,14 @@
|
|||||||
code_change/4,
|
code_change/4,
|
||||||
handle_info/3,
|
handle_info/3,
|
||||||
terminate/3,
|
terminate/3,
|
||||||
print_state/1
|
print_state/1,
|
||||||
|
migrate/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
-include("mod_privacy.hrl").
|
-include("mod_privacy.hrl").
|
||||||
|
-include("ejabberd_c2s.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}).
|
|
||||||
|
|
||||||
%-define(DBGFSM, true).
|
%-define(DBGFSM, true).
|
||||||
|
|
||||||
@ -130,11 +78,12 @@
|
|||||||
|
|
||||||
%% Module start with or without supervisor:
|
%% Module start with or without supervisor:
|
||||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||||
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, [SockData, Opts],
|
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s,
|
||||||
fsm_limit_opts(Opts) ++ ?FSMOPTS)).
|
[SockData, Opts, FSMLimitOpts],
|
||||||
|
FSMLimitOpts ++ ?FSMOPTS)).
|
||||||
-else.
|
-else.
|
||||||
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
|
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
|
||||||
[SockData, Opts])).
|
[SockData, Opts, FSMLimitOpts])).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
%% This is the timeout to apply between event when starting a new
|
%% This is the timeout to apply between event when starting a new
|
||||||
@ -149,6 +98,13 @@
|
|||||||
"id='~s' from='~s'~s~s>"
|
"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(STREAM_TRAILER, "</stream:stream>").
|
||||||
|
|
||||||
-define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE).
|
-define(INVALID_NS_ERR, ?SERR_INVALID_NAMESPACE).
|
||||||
@ -171,12 +127,17 @@
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
start(StateName, #state{fsm_limit_opts = Opts} = State) ->
|
||||||
|
start(StateName, State, Opts);
|
||||||
start(SockData, Opts) ->
|
start(SockData, Opts) ->
|
||||||
|
start(SockData, Opts, fsm_limit_opts(Opts)).
|
||||||
|
|
||||||
|
start(SockData, Opts, FSMLimitOpts) ->
|
||||||
?SUPERVISOR_START.
|
?SUPERVISOR_START.
|
||||||
|
|
||||||
start_link(SockData, Opts) ->
|
start_link(SockData, Opts, FSMLimitOpts) ->
|
||||||
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts],
|
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts, FSMLimitOpts],
|
||||||
fsm_limit_opts(Opts) ++ ?FSMOPTS).
|
FSMLimitOpts ++ ?FSMOPTS).
|
||||||
|
|
||||||
socket_type() ->
|
socket_type() ->
|
||||||
xml_stream.
|
xml_stream.
|
||||||
@ -185,9 +146,18 @@ socket_type() ->
|
|||||||
get_presence(FsmRef) ->
|
get_presence(FsmRef) ->
|
||||||
?GEN_FSM:sync_send_all_state_event(FsmRef, {get_presence}, 1000).
|
?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) ->
|
stop(FsmRef) ->
|
||||||
?GEN_FSM:send_event(FsmRef, closed).
|
?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
|
%%% Callback functions from gen_fsm
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
@ -199,7 +169,7 @@ stop(FsmRef) ->
|
|||||||
%% ignore |
|
%% ignore |
|
||||||
%% {stop, StopReason}
|
%% {stop, StopReason}
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
init([{SockMod, Socket}, Opts]) ->
|
init([{SockMod, Socket}, Opts, FSMLimitOpts]) ->
|
||||||
Access = case lists:keysearch(access, 1, Opts) of
|
Access = case lists:keysearch(access, 1, Opts) of
|
||||||
{value, {_, A}} -> A;
|
{value, {_, A}} -> A;
|
||||||
_ -> all
|
_ -> all
|
||||||
@ -223,7 +193,12 @@ init([{SockMod, Socket}, Opts]) ->
|
|||||||
(_) -> false
|
(_) -> false
|
||||||
end, Opts),
|
end, Opts),
|
||||||
TLSOpts = [verify_none | TLSOpts1],
|
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:
|
%% Check if IP is blacklisted:
|
||||||
case is_ip_blacklisted(IP) of
|
case is_ip_blacklisted(IP) of
|
||||||
true ->
|
true ->
|
||||||
@ -239,7 +214,7 @@ init([{SockMod, Socket}, Opts]) ->
|
|||||||
Socket
|
Socket
|
||||||
end,
|
end,
|
||||||
SocketMonitor = SockMod:monitor(Socket1),
|
SocketMonitor = SockMod:monitor(Socket1),
|
||||||
{ok, wait_for_stream, #state{socket = Socket1,
|
StateData = #state{socket = Socket1,
|
||||||
sockmod = SockMod,
|
sockmod = SockMod,
|
||||||
socket_monitor = SocketMonitor,
|
socket_monitor = SocketMonitor,
|
||||||
xml_socket = XMLSocket,
|
xml_socket = XMLSocket,
|
||||||
@ -251,8 +226,35 @@ init([{SockMod, Socket}, Opts]) ->
|
|||||||
streamid = new_id(),
|
streamid = new_id(),
|
||||||
access = Access,
|
access = Access,
|
||||||
shaper = Shaper,
|
shaper = Shaper,
|
||||||
ip = IP},
|
ip = IP,
|
||||||
?C2S_OPEN_TIMEOUT}
|
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.
|
end.
|
||||||
|
|
||||||
%% Return list of all available resources of contacts,
|
%% Return list of all available resources of contacts,
|
||||||
@ -266,15 +268,25 @@ get_subscribed(FsmRef) ->
|
|||||||
%% {stop, Reason, NewStateData}
|
%% {stop, Reason, NewStateData}
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
|
|
||||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
|
||||||
DefaultLang = case ?MYLANG of
|
DefaultLang = case ?MYLANG of
|
||||||
undefined ->
|
undefined ->
|
||||||
"en";
|
"en";
|
||||||
DL ->
|
DL ->
|
||||||
DL
|
DL
|
||||||
end,
|
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)),
|
Server = jlib:nameprep(xml:get_attr_s("to", Attrs)),
|
||||||
case lists:member(Server, ?MYHOSTS) of
|
case lists:member(Server, ?MYHOSTS) of
|
||||||
true ->
|
true ->
|
||||||
@ -432,11 +444,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
|||||||
send_trailer(StateData),
|
send_trailer(StateData),
|
||||||
{stop, normal, StateData}
|
{stop, normal, StateData}
|
||||||
end;
|
end;
|
||||||
|
_ ->
|
||||||
|
case Name of
|
||||||
|
"policy-file-request" ->
|
||||||
|
send_text(StateData, flash_policy_string()),
|
||||||
|
{stop, normal, StateData};
|
||||||
_ ->
|
_ ->
|
||||||
send_header(StateData, ?MYNAME, "", DefaultLang),
|
send_header(StateData, ?MYNAME, "", DefaultLang),
|
||||||
send_element(StateData, ?INVALID_NS_ERR),
|
send_element(StateData, ?INVALID_NS_ERR),
|
||||||
send_trailer(StateData),
|
send_trailer(StateData),
|
||||||
{stop, normal, StateData}
|
{stop, normal, StateData}
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
|
|
||||||
wait_for_stream(timeout, StateData) ->
|
wait_for_stream(timeout, StateData) ->
|
||||||
@ -515,13 +533,13 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
|||||||
jlib:jid_to_string(JID), AuthModule]),
|
jlib:jid_to_string(JID), AuthModule]),
|
||||||
SID = {now(), self()},
|
SID = {now(), self()},
|
||||||
Conn = get_conn_type(StateData),
|
Conn = get_conn_type(StateData),
|
||||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||||
{auth_module, AuthModule}],
|
%% {auth_module, AuthModule}],
|
||||||
Res1 = jlib:make_result_iq_reply(El),
|
Res1 = jlib:make_result_iq_reply(El),
|
||||||
Res = setelement(4, Res1, []),
|
Res = setelement(4, Res1, []),
|
||||||
send_element(StateData, Res),
|
send_element(StateData, Res),
|
||||||
ejabberd_sm:open_session(
|
%% ejabberd_sm:open_session(
|
||||||
SID, U, StateData#state.server, R, Info),
|
%% SID, U, StateData#state.server, R, Info),
|
||||||
change_shaper(StateData, JID),
|
change_shaper(StateData, JID),
|
||||||
{Fs, Ts} = ejabberd_hooks:run_fold(
|
{Fs, Ts} = ejabberd_hooks:run_fold(
|
||||||
roster_get_subscription_lists,
|
roster_get_subscription_lists,
|
||||||
@ -537,8 +555,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
|||||||
privacy_get_user_list, StateData#state.server,
|
privacy_get_user_list, StateData#state.server,
|
||||||
#userlist{},
|
#userlist{},
|
||||||
[U, StateData#state.server]),
|
[U, StateData#state.server]),
|
||||||
NewStateData =
|
NewStateData = StateData#state{
|
||||||
StateData#state{
|
|
||||||
user = U,
|
user = U,
|
||||||
resource = R,
|
resource = R,
|
||||||
jid = JID,
|
jid = JID,
|
||||||
@ -548,8 +565,11 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
|||||||
pres_f = ?SETS:from_list(Fs1),
|
pres_f = ?SETS:from_list(Fs1),
|
||||||
pres_t = ?SETS:from_list(Ts1),
|
pres_t = ?SETS:from_list(Ts1),
|
||||||
privacy_list = PrivList},
|
privacy_list = PrivList},
|
||||||
fsm_next_state_pack(session_established,
|
DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook,
|
||||||
NewStateData);
|
NewStateData#state.server,
|
||||||
|
false,
|
||||||
|
[self(), NewStateData]),
|
||||||
|
maybe_migrate(session_established, NewStateData#state{debug=DebugFlag});
|
||||||
_ ->
|
_ ->
|
||||||
?INFO_MSG(
|
?INFO_MSG(
|
||||||
"(~w) Failed legacy authentication for ~s",
|
"(~w) Failed legacy authentication for ~s",
|
||||||
@ -641,7 +661,7 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
|||||||
Mech,
|
Mech,
|
||||||
ClientIn) of
|
ClientIn) of
|
||||||
{ok, Props} ->
|
{ok, Props} ->
|
||||||
(StateData#state.sockmod):reset_stream(
|
catch (StateData#state.sockmod):reset_stream(
|
||||||
StateData#state.socket),
|
StateData#state.socket),
|
||||||
send_element(StateData,
|
send_element(StateData,
|
||||||
{xmlelement, "success",
|
{xmlelement, "success",
|
||||||
@ -794,7 +814,7 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
|
|||||||
case cyrsasl:server_step(StateData#state.sasl_state,
|
case cyrsasl:server_step(StateData#state.sasl_state,
|
||||||
ClientIn) of
|
ClientIn) of
|
||||||
{ok, Props} ->
|
{ok, Props} ->
|
||||||
(StateData#state.sockmod):reset_stream(
|
catch (StateData#state.sockmod):reset_stream(
|
||||||
StateData#state.socket),
|
StateData#state.socket),
|
||||||
send_element(StateData,
|
send_element(StateData,
|
||||||
{xmlelement, "success",
|
{xmlelement, "success",
|
||||||
@ -919,7 +939,7 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
|
|||||||
case jlib:iq_query_info(El) of
|
case jlib:iq_query_info(El) of
|
||||||
#iq{type = set, xmlns = ?NS_SESSION} ->
|
#iq{type = set, xmlns = ?NS_SESSION} ->
|
||||||
U = StateData#state.user,
|
U = StateData#state.user,
|
||||||
R = StateData#state.resource,
|
%%R = StateData#state.resource,
|
||||||
JID = StateData#state.jid,
|
JID = StateData#state.jid,
|
||||||
case acl:match_rule(StateData#state.server,
|
case acl:match_rule(StateData#state.server,
|
||||||
StateData#state.access, JID) of
|
StateData#state.access, JID) of
|
||||||
@ -945,10 +965,10 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
|
|||||||
[U, StateData#state.server]),
|
[U, StateData#state.server]),
|
||||||
SID = {now(), self()},
|
SID = {now(), self()},
|
||||||
Conn = get_conn_type(StateData),
|
Conn = get_conn_type(StateData),
|
||||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
%% Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||||
{auth_module, StateData#state.auth_module}],
|
%% {auth_module, StateData#state.auth_module}],
|
||||||
ejabberd_sm:open_session(
|
%% ejabberd_sm:open_session(
|
||||||
SID, U, StateData#state.server, R, Info),
|
%% SID, U, StateData#state.server, R, Info),
|
||||||
NewStateData =
|
NewStateData =
|
||||||
StateData#state{
|
StateData#state{
|
||||||
sid = SID,
|
sid = SID,
|
||||||
@ -956,8 +976,11 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
|
|||||||
pres_f = ?SETS:from_list(Fs1),
|
pres_f = ?SETS:from_list(Fs1),
|
||||||
pres_t = ?SETS:from_list(Ts1),
|
pres_t = ?SETS:from_list(Ts1),
|
||||||
privacy_list = PrivList},
|
privacy_list = PrivList},
|
||||||
fsm_next_state_pack(session_established,
|
DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook,
|
||||||
NewStateData);
|
NewStateData#state.server,
|
||||||
|
false,
|
||||||
|
[self(), NewStateData]),
|
||||||
|
maybe_migrate(session_established, NewStateData#state{debug=DebugFlag});
|
||||||
_ ->
|
_ ->
|
||||||
ejabberd_hooks:run(forbidden_session_hook,
|
ejabberd_hooks:run(forbidden_session_hook,
|
||||||
StateData#state.server, [JID]),
|
StateData#state.server, [JID]),
|
||||||
@ -1084,7 +1107,7 @@ session_established2(El, StateData) ->
|
|||||||
ejabberd_hooks:run(
|
ejabberd_hooks:run(
|
||||||
user_send_packet,
|
user_send_packet,
|
||||||
Server,
|
Server,
|
||||||
[FromJID, ToJID, PresenceEl]),
|
[StateData#state.debug, FromJID, ToJID, PresenceEl]),
|
||||||
case ToJID of
|
case ToJID of
|
||||||
#jid{user = User,
|
#jid{user = User,
|
||||||
server = Server,
|
server = Server,
|
||||||
@ -1100,6 +1123,10 @@ session_established2(El, StateData) ->
|
|||||||
"iq" ->
|
"iq" ->
|
||||||
case jlib:iq_query_info(NewEl) of
|
case jlib:iq_query_info(NewEl) of
|
||||||
#iq{xmlns = ?NS_PRIVACY} = IQ ->
|
#iq{xmlns = ?NS_PRIVACY} = IQ ->
|
||||||
|
ejabberd_hooks:run(
|
||||||
|
user_send_packet,
|
||||||
|
Server,
|
||||||
|
[StateData#state.debug, FromJID, ToJID, NewEl]),
|
||||||
process_privacy_iq(
|
process_privacy_iq(
|
||||||
FromJID, ToJID, IQ, StateData);
|
FromJID, ToJID, IQ, StateData);
|
||||||
#iq{xmlns = ?NS_P1_PUSH} = IQ ->
|
#iq{xmlns = ?NS_P1_PUSH} = IQ ->
|
||||||
@ -1108,7 +1135,7 @@ session_established2(El, StateData) ->
|
|||||||
ejabberd_hooks:run(
|
ejabberd_hooks:run(
|
||||||
user_send_packet,
|
user_send_packet,
|
||||||
Server,
|
Server,
|
||||||
[FromJID, ToJID, NewEl]),
|
[StateData#state.debug, FromJID, ToJID, NewEl]),
|
||||||
ejabberd_router:route(
|
ejabberd_router:route(
|
||||||
FromJID, ToJID, NewEl),
|
FromJID, ToJID, NewEl),
|
||||||
StateData
|
StateData
|
||||||
@ -1116,7 +1143,7 @@ session_established2(El, StateData) ->
|
|||||||
"message" ->
|
"message" ->
|
||||||
ejabberd_hooks:run(user_send_packet,
|
ejabberd_hooks:run(user_send_packet,
|
||||||
Server,
|
Server,
|
||||||
[FromJID, ToJID, NewEl]),
|
[StateData#state.debug, FromJID, ToJID, NewEl]),
|
||||||
check_privacy_route(FromJID, StateData, FromJID,
|
check_privacy_route(FromJID, StateData, FromJID,
|
||||||
ToJID, NewEl),
|
ToJID, NewEl),
|
||||||
StateData;
|
StateData;
|
||||||
@ -1154,6 +1181,17 @@ session_established2(El, StateData) ->
|
|||||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
%% {stop, Reason, NewStateData}
|
%% {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) ->
|
handle_event({xmlstreamcdata, _}, StateName, StateData) ->
|
||||||
?DEBUG("cdata ping", []),
|
?DEBUG("cdata ping", []),
|
||||||
NSD1 = change_reception(StateData, true),
|
NSD1 = change_reception(StateData, true),
|
||||||
@ -1458,7 +1496,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
|||||||
end,
|
end,
|
||||||
ejabberd_hooks:run(user_receive_packet,
|
ejabberd_hooks:run(user_receive_packet,
|
||||||
StateData#state.server,
|
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}]),
|
ejabberd_hooks:run(c2s_loop_debug, [{route, From, To, Packet}]),
|
||||||
fsm_next_state(StateName, NewState2);
|
fsm_next_state(StateName, NewState2);
|
||||||
true ->
|
true ->
|
||||||
@ -1566,6 +1604,25 @@ print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
|
|||||||
%% Purpose: Shutdown the fsm
|
%% Purpose: Shutdown the fsm
|
||||||
%% Returns: any
|
%% 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) ->
|
terminate(_Reason, StateName, StateData) ->
|
||||||
case StateName of
|
case StateName of
|
||||||
session_established ->
|
session_established ->
|
||||||
@ -1675,7 +1732,14 @@ change_shaper(StateData, JID) ->
|
|||||||
|
|
||||||
send_text(StateData, Text) ->
|
send_text(StateData, Text) ->
|
||||||
?DEBUG("Send XML on stream = ~p", [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 ->
|
send_element(StateData, El) when StateData#state.xml_socket ->
|
||||||
ejabberd_hooks:run(feature_inspect_packet,
|
ejabberd_hooks:run(feature_inspect_packet,
|
||||||
@ -1693,6 +1757,15 @@ send_element(StateData, El) ->
|
|||||||
StateData#state.pres_last, El]),
|
StateData#state.pres_last, El]),
|
||||||
send_text(StateData, xml:element_to_binary(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)
|
send_header(StateData, Server, Version, Lang)
|
||||||
when StateData#state.xml_socket ->
|
when StateData#state.xml_socket ->
|
||||||
VersionAttr =
|
VersionAttr =
|
||||||
@ -1784,11 +1857,17 @@ get_conn_type(StateData) ->
|
|||||||
gen_tcp -> c2s;
|
gen_tcp -> c2s;
|
||||||
tls -> c2s_tls;
|
tls -> c2s_tls;
|
||||||
ejabberd_zlib ->
|
ejabberd_zlib ->
|
||||||
case ejabberd_zlib:get_sockmod((StateData#state.socket)#socket_state.socket) of
|
if is_pid(StateData#state.socket) ->
|
||||||
|
unknown;
|
||||||
|
true ->
|
||||||
|
case ejabberd_zlib:get_sockmod(
|
||||||
|
(StateData#state.socket)#socket_state.socket) of
|
||||||
gen_tcp -> c2s_compressed;
|
gen_tcp -> c2s_compressed;
|
||||||
tls -> c2s_compressed_tls
|
tls -> c2s_compressed_tls
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
ejabberd_http_poll -> http_poll;
|
ejabberd_http_poll -> http_poll;
|
||||||
|
ejabberd_http_ws -> http_ws;
|
||||||
ejabberd_http_bind -> http_bind;
|
ejabberd_http_bind -> http_bind;
|
||||||
_ -> unknown
|
_ -> unknown
|
||||||
end.
|
end.
|
||||||
@ -2197,6 +2276,7 @@ roster_change(IJID, ISubscription, StateData) ->
|
|||||||
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
|
?DEBUG("roster changed for ~p~n", [StateData#state.user]),
|
||||||
From = StateData#state.jid,
|
From = StateData#state.jid,
|
||||||
To = jlib:make_jid(IJID),
|
To = jlib:make_jid(IJID),
|
||||||
|
% To = IJID,
|
||||||
Cond1 = (not StateData#state.pres_invis) and IsFrom
|
Cond1 = (not StateData#state.pres_invis) and IsFrom
|
||||||
and (not OldIsFrom),
|
and (not OldIsFrom),
|
||||||
Cond2 = (not IsFrom) and OldIsFrom
|
Cond2 = (not IsFrom) and OldIsFrom
|
||||||
@ -2353,7 +2433,7 @@ resend_offline_messages(#state{user = User,
|
|||||||
send_element(StateData, FixedPacket),
|
send_element(StateData, FixedPacket),
|
||||||
ejabberd_hooks:run(user_receive_packet,
|
ejabberd_hooks:run(user_receive_packet,
|
||||||
StateData#state.server,
|
StateData#state.server,
|
||||||
[StateData#state.jid,
|
[StateData#state.debug, StateData#state.jid,
|
||||||
From, To, FixedPacket]);
|
From, To, FixedPacket]);
|
||||||
true ->
|
true ->
|
||||||
ok
|
ok
|
||||||
@ -2426,16 +2506,28 @@ peerip(SockMod, Socket) ->
|
|||||||
_ -> undefined
|
_ -> undefined
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% fsm_next_state_pack: Pack the StateData structure to improve
|
maybe_migrate(StateName, StateData) ->
|
||||||
%% sharing.
|
PackedStateData = pack(StateData),
|
||||||
fsm_next_state_pack(StateName, StateData) ->
|
case ejabberd_cluster:get_node({StateData#state.user,
|
||||||
fsm_next_state_gc(StateName, pack(StateData)).
|
StateData#state.server}) of
|
||||||
|
Node when Node == node() ->
|
||||||
%% fsm_next_state_gc: Garbage collect the process heap to make use of
|
Conn = get_conn_type(StateData),
|
||||||
%% the newly packed StateData structure.
|
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||||
fsm_next_state_gc(StateName, PackedStateData) ->
|
{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(),
|
erlang:garbage_collect(),
|
||||||
fsm_next_state(StateName, PackedStateData).
|
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
|
%% fsm_next_state: Generate the next_state FSM tuple with different
|
||||||
%% timeout, depending on the future state
|
%% timeout, depending on the future state
|
||||||
@ -2444,6 +2536,10 @@ fsm_next_state(session_established, StateData) ->
|
|||||||
fsm_next_state(StateName, StateData) ->
|
fsm_next_state(StateName, StateData) ->
|
||||||
{next_state, StateName, StateData, ?C2S_OPEN_TIMEOUT}.
|
{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,
|
%% fsm_reply: Generate the reply FSM tuple with different timeout,
|
||||||
%% depending on the future state
|
%% depending on the future state
|
||||||
fsm_reply(Reply, session_established, StateData) ->
|
fsm_reply(Reply, session_established, StateData) ->
|
||||||
@ -3180,3 +3276,28 @@ pack_string(String, Pack) ->
|
|||||||
none ->
|
none ->
|
||||||
{String, gb_trees:insert(String, String, Pack)}
|
{String, gb_trees:insert(String, String, Pack)}
|
||||||
end.
|
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,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
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]).
|
process_reply/1, process/2, is_feature_available/0]).
|
||||||
|
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
@ -48,19 +48,11 @@
|
|||||||
|
|
||||||
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
|
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
|
||||||
-define(CAPTCHA_LIFETIME, 120000). % two minutes
|
-define(CAPTCHA_LIFETIME, 120000). % two minutes
|
||||||
|
-define(RPC_TIMEOUT, 5000).
|
||||||
|
|
||||||
-record(state, {}).
|
-record(state, {}).
|
||||||
-record(captcha, {id, pid, key, tref, args}).
|
-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
|
%% API
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
@ -71,11 +63,12 @@
|
|||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
create_captcha(Id, SID, From, To, Lang, Args)
|
create_captcha(SID, From, To, Lang, Args)
|
||||||
when is_list(Id), is_list(Lang), is_list(SID),
|
when is_list(Lang), is_list(SID),
|
||||||
is_record(From, jid), is_record(To, jid) ->
|
is_record(From, jid), is_record(To, jid) ->
|
||||||
case create_image() of
|
case create_image() of
|
||||||
{ok, Type, Key, Image} ->
|
{ok, Type, Key, Image} ->
|
||||||
|
Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(),
|
||||||
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
B64Image = jlib:encode_base64(binary_to_list(Image)),
|
||||||
JID = jlib:jid_to_string(From),
|
JID = jlib:jid_to_string(From),
|
||||||
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
|
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}],
|
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
|
||||||
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
|
||||||
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
|
||||||
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
|
ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key,
|
||||||
tref=Tref, args=Args})) of
|
tref=Tref, args=Args}),
|
||||||
ok ->
|
{ok, Id, [Body, OOB, Captcha, Data]};
|
||||||
{ok, [Body, OOB, Captcha, Data]};
|
|
||||||
_Err ->
|
|
||||||
error
|
|
||||||
end;
|
|
||||||
_Err ->
|
_Err ->
|
||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
@ -119,8 +108,8 @@ create_captcha(Id, SID, From, To, Lang, Args)
|
|||||||
%% IdEl = xmlelement()
|
%% IdEl = xmlelement()
|
||||||
%% KeyEl = xmlelement()
|
%% KeyEl = xmlelement()
|
||||||
build_captcha_html(Id, Lang) ->
|
build_captcha_html(Id, Lang) ->
|
||||||
case mnesia:dirty_read(captcha, Id) of
|
case lookup_captcha(Id) of
|
||||||
[#captcha{}] ->
|
{ok, _} ->
|
||||||
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
|
||||||
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
|
||||||
IdEl = {xmlelement, "input", [{"type", "hidden"},
|
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
|
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
|
||||||
check_captcha(Id, ProvidedKey) ->
|
check_captcha(Id, ProvidedKey) ->
|
||||||
?T(case mnesia:read(captcha, Id, write) of
|
case string:tokens(Id, "-") of
|
||||||
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
|
[_, NodeID] ->
|
||||||
mnesia:delete({captcha, Id}),
|
case ejabberd_cluster:get_node_by_id(NodeID) of
|
||||||
erlang:cancel_timer(Tref),
|
Node when Node == node() ->
|
||||||
if StoredKey == ProvidedKey ->
|
do_check_captcha(Id, ProvidedKey);
|
||||||
Pid ! {captcha_succeed, Args},
|
Node ->
|
||||||
captcha_valid;
|
case catch rpc:call(Node, ?MODULE, check_captcha,
|
||||||
true ->
|
[Id, ProvidedKey], ?RPC_TIMEOUT) of
|
||||||
Pid ! {captcha_failed, Args},
|
{'EXIT', _} ->
|
||||||
captcha_non_valid
|
captcha_not_found;
|
||||||
|
{badrpc, _} ->
|
||||||
|
captcha_not_found;
|
||||||
|
Res ->
|
||||||
|
Res
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
captcha_not_found
|
captcha_not_found
|
||||||
end).
|
end.
|
||||||
|
|
||||||
|
|
||||||
process_reply({xmlelement, "captcha", _, _} = El) ->
|
process_reply({xmlelement, "captcha", _, _} = El) ->
|
||||||
case xml:get_subtag(El, "x") of
|
case xml:get_subtag(El, "x") of
|
||||||
@ -175,20 +168,14 @@ process_reply({xmlelement, "captcha", _, _} = El) ->
|
|||||||
case {proplists:get_value("challenge", Fields),
|
case {proplists:get_value("challenge", Fields),
|
||||||
proplists:get_value("ocr", Fields)} of
|
proplists:get_value("ocr", Fields)} of
|
||||||
{[Id|_], [OCR|_]} ->
|
{[Id|_], [OCR|_]} ->
|
||||||
?T(case mnesia:read(captcha, Id, write) of
|
case check_captcha(Id, OCR) of
|
||||||
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
|
captcha_valid ->
|
||||||
mnesia:delete({captcha, Id}),
|
|
||||||
erlang:cancel_timer(Tref),
|
|
||||||
if OCR == Key ->
|
|
||||||
Pid ! {captcha_succeed, Args},
|
|
||||||
ok;
|
ok;
|
||||||
true ->
|
captcha_non_valid ->
|
||||||
Pid ! {captcha_failed, Args},
|
{error, bad_match};
|
||||||
{error, bad_match}
|
captcha_not_found ->
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
{error, not_found}
|
{error, not_found}
|
||||||
end);
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
{error, malformed}
|
{error, malformed}
|
||||||
end
|
end
|
||||||
@ -209,8 +196,8 @@ process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
|
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
|
||||||
case mnesia:dirty_read(captcha, Id) of
|
case lookup_captcha(Id) of
|
||||||
[#captcha{key=Key}] ->
|
{ok, #captcha{key=Key}} ->
|
||||||
case create_image(Key) of
|
case create_image(Key) of
|
||||||
{ok, Type, _, Img} ->
|
{ok, Type, _, Img} ->
|
||||||
{200,
|
{200,
|
||||||
@ -249,10 +236,8 @@ process(_Handlers, _Request) ->
|
|||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
init([]) ->
|
init([]) ->
|
||||||
mnesia:create_table(captcha,
|
mnesia:delete_table(captcha),
|
||||||
[{ram_copies, [node()]},
|
ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
|
||||||
{attributes, record_info(fields, captcha)}]),
|
|
||||||
mnesia:add_table_copy(captcha, node(), ram_copies),
|
|
||||||
check_captcha_setup(),
|
check_captcha_setup(),
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
@ -264,13 +249,13 @@ handle_cast(_Msg, State) ->
|
|||||||
|
|
||||||
handle_info({remove_id, Id}, State) ->
|
handle_info({remove_id, Id}, State) ->
|
||||||
?DEBUG("captcha ~p timed out", [Id]),
|
?DEBUG("captcha ~p timed out", [Id]),
|
||||||
_ = ?T(case mnesia:read(captcha, Id, write) of
|
case ets:lookup(captcha, Id) of
|
||||||
[#captcha{args=Args, pid=Pid}] ->
|
[#captcha{args=Args, pid=Pid}] ->
|
||||||
Pid ! {captcha_failed, Args},
|
Pid ! {captcha_failed, Args},
|
||||||
mnesia:delete({captcha, Id});
|
ets:delete(captcha, Id);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
end),
|
end,
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
@ -414,3 +399,43 @@ check_captcha_setup() ->
|
|||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
end.
|
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_peer_certificate/1,
|
||||||
get_verify_result/1,
|
get_verify_result/1,
|
||||||
close/1,
|
close/1,
|
||||||
|
setopts/2,
|
||||||
|
change_controller/2,
|
||||||
sockname/1, peername/1]).
|
sockname/1, peername/1]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -94,18 +96,15 @@ start(Module, SockMod, Socket, Opts) ->
|
|||||||
todo
|
todo
|
||||||
end.
|
end.
|
||||||
|
|
||||||
starttls(FsmRef, _TLSOpts) ->
|
starttls(FsmRef, TLSOpts) ->
|
||||||
%% TODO: Frontend improvements planned by Aleksey
|
starttls(FsmRef, TLSOpts, undefined).
|
||||||
%%gen_server:call(FsmRef, {starttls, TLSOpts}),
|
|
||||||
FsmRef.
|
|
||||||
|
|
||||||
starttls(FsmRef, TLSOpts, Data) ->
|
starttls(FsmRef, TLSOpts, Data) ->
|
||||||
gen_server:call(FsmRef, {starttls, TLSOpts, Data}),
|
gen_server:call(FsmRef, {starttls, TLSOpts, Data}),
|
||||||
FsmRef.
|
FsmRef.
|
||||||
|
|
||||||
compress(FsmRef) ->
|
compress(FsmRef) ->
|
||||||
gen_server:call(FsmRef, compress),
|
compress(FsmRef, undefined).
|
||||||
FsmRef.
|
|
||||||
|
|
||||||
compress(FsmRef, Data) ->
|
compress(FsmRef, Data) ->
|
||||||
gen_server:call(FsmRef, {compress, Data}),
|
gen_server:call(FsmRef, {compress, Data}),
|
||||||
@ -138,11 +137,14 @@ close(FsmRef) ->
|
|||||||
sockname(FsmRef) ->
|
sockname(FsmRef) ->
|
||||||
gen_server:call(FsmRef, sockname).
|
gen_server:call(FsmRef, sockname).
|
||||||
|
|
||||||
peername(_FsmRef) ->
|
setopts(FsmRef, Opts) ->
|
||||||
%% TODO: Frontend improvements planned by Aleksey
|
gen_server:call(FsmRef, {setopts, Opts}).
|
||||||
%%gen_server:call(FsmRef, peername).
|
|
||||||
{ok, {{0, 0, 0, 0}, 0}}.
|
|
||||||
|
|
||||||
|
change_controller(FsmRef, C2SPid) ->
|
||||||
|
gen_server:call(FsmRef, {change_controller, C2SPid}).
|
||||||
|
|
||||||
|
peername(FsmRef) ->
|
||||||
|
gen_server:call(FsmRef, peername).
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -158,9 +160,16 @@ peername(_FsmRef) ->
|
|||||||
init([Module, SockMod, Socket, Opts, Receiver]) ->
|
init([Module, SockMod, Socket, Opts, Receiver]) ->
|
||||||
%% TODO: monitor the receiver
|
%% TODO: monitor the receiver
|
||||||
Node = ejabberd_node_groups:get_closest_node(backend),
|
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),
|
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
|
||||||
{ok, Pid} =
|
{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),
|
ejabberd_receiver:become_controller(Receiver, Pid),
|
||||||
{ok, #state{sockmod = SockMod2,
|
{ok, #state{sockmod = SockMod2,
|
||||||
socket = Socket2,
|
socket = Socket2,
|
||||||
@ -175,38 +184,16 @@ init([Module, SockMod, Socket, Opts, Receiver]) ->
|
|||||||
%% {stop, Reason, State}
|
%% {stop, Reason, State}
|
||||||
%% Description: Handling call messages
|
%% 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) ->
|
handle_call({starttls, TLSOpts, Data}, _From, State) ->
|
||||||
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
|
{ok, TLSSocket} = ejabberd_receiver:starttls(
|
||||||
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
|
State#state.receiver, TLSOpts, Data),
|
||||||
catch (State#state.sockmod):send(
|
|
||||||
State#state.socket, Data),
|
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
|
||||||
?HIBERNATE_TIMEOUT};
|
?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) ->
|
handle_call({compress, Data}, _From, State) ->
|
||||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
{ok, ZlibSocket} = ejabberd_receiver:compress(
|
||||||
State#state.sockmod,
|
State#state.receiver, Data),
|
||||||
State#state.socket),
|
|
||||||
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
|
|
||||||
catch (State#state.sockmod):send(
|
|
||||||
State#state.socket, Data),
|
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
|
||||||
?HIBERNATE_TIMEOUT};
|
?HIBERNATE_TIMEOUT};
|
||||||
@ -246,13 +233,7 @@ handle_call(close, _From, State) ->
|
|||||||
|
|
||||||
handle_call(sockname, _From, State) ->
|
handle_call(sockname, _From, State) ->
|
||||||
#state{sockmod = SockMod, socket = Socket} = State,
|
#state{sockmod = SockMod, socket = Socket} = State,
|
||||||
Reply =
|
Reply = peername(SockMod, Socket),
|
||||||
case SockMod of
|
|
||||||
gen_tcp ->
|
|
||||||
inet:sockname(Socket);
|
|
||||||
_ ->
|
|
||||||
SockMod:sockname(Socket)
|
|
||||||
end,
|
|
||||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
||||||
|
|
||||||
handle_call(peername, _From, State) ->
|
handle_call(peername, _From, State) ->
|
||||||
@ -266,6 +247,14 @@ handle_call(peername, _From, State) ->
|
|||||||
end,
|
end,
|
||||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
|
{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) ->
|
handle_call(_Request, _From, State) ->
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||||
@ -318,10 +307,16 @@ check_starttls(SockMod, Socket, Receiver, Opts) ->
|
|||||||
end, Opts),
|
end, Opts),
|
||||||
if
|
if
|
||||||
TLSEnabled ->
|
TLSEnabled ->
|
||||||
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
|
{ok, TLSSocket} = ejabberd_receiver:starttls(Receiver, TLSOpts),
|
||||||
ejabberd_receiver:starttls(Receiver, TLSSocket),
|
|
||||||
{tls, TLSSocket};
|
{tls, TLSSocket};
|
||||||
true ->
|
true ->
|
||||||
{SockMod, Socket}
|
{SockMod, Socket}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
peername(SockMod, Socket) ->
|
||||||
|
case SockMod of
|
||||||
|
gen_tcp ->
|
||||||
|
inet:peername(Socket);
|
||||||
|
_ ->
|
||||||
|
SockMod:peername(Socket)
|
||||||
|
end.
|
||||||
|
@ -35,7 +35,8 @@
|
|||||||
stop_listener/2,
|
stop_listener/2,
|
||||||
parse_listener_portip/2,
|
parse_listener_portip/2,
|
||||||
add_listener/3,
|
add_listener/3,
|
||||||
delete_listener/2
|
delete_listener/2,
|
||||||
|
rate_limit/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
@ -225,6 +226,9 @@ get_ip_tuple(IPOpt, _IPVOpt) ->
|
|||||||
IPOpt.
|
IPOpt.
|
||||||
|
|
||||||
accept(ListenSocket, Module, Opts) ->
|
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
|
case gen_tcp:accept(ListenSocket) of
|
||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
case {inet:sockname(Socket), inet:peername(Socket)} of
|
case {inet:sockname(Socket), inet:peername(Socket)} of
|
||||||
@ -239,11 +243,11 @@ accept(ListenSocket, Module, Opts) ->
|
|||||||
false -> ejabberd_socket
|
false -> ejabberd_socket
|
||||||
end,
|
end,
|
||||||
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
|
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
|
||||||
accept(ListenSocket, Module, Opts);
|
accept(ListenSocket, Module, Opts, NewInterval);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
||||||
[ListenSocket, Reason]),
|
[ListenSocket, Reason]),
|
||||||
accept(ListenSocket, Module, Opts)
|
accept(ListenSocket, Module, Opts, NewInterval)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
udp_recv(Socket, Module, Opts) ->
|
udp_recv(Socket, Module, Opts) ->
|
||||||
@ -471,3 +475,39 @@ format_error(Reason) ->
|
|||||||
ReasonStr ->
|
ReasonStr ->
|
||||||
ReasonStr
|
ReasonStr
|
||||||
end.
|
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) ->
|
route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
|
||||||
Packet = if Type == set; Type == get ->
|
Packet = if Type == set; Type == get ->
|
||||||
ID = randoms:get_string(),
|
ID = ejabberd_router:make_id(),
|
||||||
Host = From#jid.lserver,
|
Host = From#jid.lserver,
|
||||||
register_iq_response_handler(Host, ID, undefined, F),
|
register_iq_response_handler(Host, ID, undefined, F),
|
||||||
jlib:iq_to_xml(IQ#iq{id = ID});
|
jlib:iq_to_xml(IQ#iq{id = ID});
|
||||||
@ -136,7 +136,7 @@ route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
|
|||||||
|
|
||||||
register_iq_response_handler(_Host, ID, Module, Function) ->
|
register_iq_response_handler(_Host, ID, Module, Function) ->
|
||||||
TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID),
|
TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID),
|
||||||
mnesia:dirty_write(#iq_response{id = ID,
|
ets:insert(iq_response, #iq_response{id = ID,
|
||||||
module = Module,
|
module = Module,
|
||||||
function = Function,
|
function = Function,
|
||||||
timer = TRef}).
|
timer = TRef}).
|
||||||
@ -181,11 +181,9 @@ init([]) ->
|
|||||||
?MODULE, bounce_resource_packet, 100)
|
?MODULE, bounce_resource_packet, 100)
|
||||||
end, ?MYHOSTS),
|
end, ?MYHOSTS),
|
||||||
catch ets:new(?IQTABLE, [named_table, public]),
|
catch ets:new(?IQTABLE, [named_table, public]),
|
||||||
update_table(),
|
mnesia:delete_table(iq_response),
|
||||||
mnesia:create_table(iq_response,
|
catch ets:new(iq_response, [named_table, public,
|
||||||
[{ram_copies, [node()]},
|
{keypos, #iq_response.id}]),
|
||||||
{attributes, record_info(fields, iq_response)}]),
|
|
||||||
mnesia:add_table_copy(iq_response, node(), ram_copies),
|
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
@ -257,7 +255,7 @@ handle_info(refresh_iq_handlers, State) ->
|
|||||||
end, ets:tab2list(?IQTABLE)),
|
end, ets:tab2list(?IQTABLE)),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_info({timeout, _TRef, ID}, State) ->
|
handle_info({timeout, _TRef, ID}, State) ->
|
||||||
process_iq_timeout(ID),
|
spawn(fun() -> process_iq_timeout(ID) end),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
@ -312,41 +310,23 @@ do_route(From, To, Packet) ->
|
|||||||
end
|
end
|
||||||
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) ->
|
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,
|
[#iq_response{module = Module, timer = TRef,
|
||||||
function = Function}] ->
|
function = Function}] ->
|
||||||
cancel_timer(TRef),
|
cancel_timer(TRef),
|
||||||
mnesia:dirty_delete(iq_response, ID),
|
ets:delete(iq_response, ID),
|
||||||
{ok, Module, Function};
|
{ok, Module, Function};
|
||||||
_ ->
|
_ ->
|
||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
process_iq_timeout(ID) ->
|
process_iq_timeout(ID) ->
|
||||||
spawn(fun process_iq_timeout/0) ! ID.
|
|
||||||
|
|
||||||
process_iq_timeout() ->
|
|
||||||
receive
|
|
||||||
ID ->
|
|
||||||
case get_iq_callback(ID) of
|
case get_iq_callback(ID) of
|
||||||
{ok, undefined, Function} ->
|
{ok, undefined, Function} ->
|
||||||
Function(timeout);
|
Function(timeout);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
end
|
|
||||||
after 5000 ->
|
|
||||||
ok
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
cancel_timer(TRef) ->
|
cancel_timer(TRef) ->
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start_link/0,
|
-export([start_link/0,
|
||||||
|
start/0,
|
||||||
join/1,
|
join/1,
|
||||||
leave/1,
|
leave/1,
|
||||||
get_members/1,
|
get_members/1,
|
||||||
@ -40,7 +41,7 @@
|
|||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-record(state, {}).
|
-record(state, {groups = []}).
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% API
|
%% API
|
||||||
@ -49,6 +50,15 @@
|
|||||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||||
%% Description: Starts the server
|
%% Description: Starts the server
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
start() ->
|
||||||
|
ChildSpec = {?MODULE,
|
||||||
|
{?MODULE, start_link, []},
|
||||||
|
permanent,
|
||||||
|
brutal_kill,
|
||||||
|
worker,
|
||||||
|
[?MODULE]},
|
||||||
|
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
@ -81,30 +91,19 @@ get_closest_node(Name) ->
|
|||||||
%% Description: Initiates the server
|
%% Description: Initiates the server
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{FE, BE} =
|
Groups =
|
||||||
case ejabberd_config:get_local_option(node_type) of
|
case ejabberd_config:get_local_option(node_type) of
|
||||||
frontend ->
|
frontend ->
|
||||||
{true, false};
|
[frontend];
|
||||||
backend ->
|
backend ->
|
||||||
{false, true};
|
[backend];
|
||||||
generic ->
|
generic ->
|
||||||
{true, true};
|
[frontend, backend];
|
||||||
undefined ->
|
undefined ->
|
||||||
{true, true}
|
[frontend, backend]
|
||||||
end,
|
end,
|
||||||
if
|
lists:foreach(fun join/1, Groups),
|
||||||
FE ->
|
{ok, #state{groups = Groups}}.
|
||||||
join(frontend);
|
|
||||||
true ->
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
if
|
|
||||||
BE ->
|
|
||||||
join(backend);
|
|
||||||
true ->
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
{ok, #state{}}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
%% 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.
|
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||||
%% The return value is ignored.
|
%% The return value is ignored.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, #state{groups = Groups}) ->
|
||||||
|
lists:foreach(fun leave/1, Groups),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -36,8 +36,12 @@
|
|||||||
change_shaper/2,
|
change_shaper/2,
|
||||||
reset_stream/1,
|
reset_stream/1,
|
||||||
starttls/2,
|
starttls/2,
|
||||||
|
starttls/3,
|
||||||
compress/2,
|
compress/2,
|
||||||
|
send/2,
|
||||||
become_controller/2,
|
become_controller/2,
|
||||||
|
change_controller/2,
|
||||||
|
setopts/2,
|
||||||
close/1]).
|
close/1]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -52,6 +56,7 @@
|
|||||||
c2s_pid,
|
c2s_pid,
|
||||||
max_stanza_size,
|
max_stanza_size,
|
||||||
xml_stream_state,
|
xml_stream_state,
|
||||||
|
tref,
|
||||||
timeout}).
|
timeout}).
|
||||||
|
|
||||||
-define(HIBERNATE_TIMEOUT, 90000).
|
-define(HIBERNATE_TIMEOUT, 90000).
|
||||||
@ -86,15 +91,42 @@ change_shaper(Pid, Shaper) ->
|
|||||||
reset_stream(Pid) ->
|
reset_stream(Pid) ->
|
||||||
gen_server:call(Pid, reset_stream).
|
gen_server:call(Pid, reset_stream).
|
||||||
|
|
||||||
starttls(Pid, TLSSocket) ->
|
starttls(Pid, TLSOpts) ->
|
||||||
gen_server:call(Pid, {starttls, TLSSocket}).
|
starttls(Pid, TLSOpts, undefined).
|
||||||
|
|
||||||
compress(Pid, ZlibSocket) ->
|
starttls(Pid, TLSOpts, Data) ->
|
||||||
gen_server:call(Pid, {compress, ZlibSocket}).
|
gen_server:call(Pid, {starttls, TLSOpts, Data}).
|
||||||
|
|
||||||
|
compress(Pid, Data) ->
|
||||||
|
gen_server:call(Pid, {compress, Data}).
|
||||||
|
|
||||||
become_controller(Pid, C2SPid) ->
|
become_controller(Pid, C2SPid) ->
|
||||||
gen_server:call(Pid, {become_controller, 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) ->
|
close(Pid) ->
|
||||||
gen_server:cast(Pid, close).
|
gen_server:cast(Pid, close).
|
||||||
|
|
||||||
@ -132,10 +164,17 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
|
|||||||
%% {stop, Reason, State}
|
%% {stop, Reason, State}
|
||||||
%% Description: Handling call messages
|
%% Description: Handling call messages
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
handle_call({starttls, TLSSocket}, _From,
|
handle_call({starttls, TLSOpts, Data}, _From,
|
||||||
#state{xml_stream_state = XMLStreamState,
|
#state{xml_stream_state = XMLStreamState,
|
||||||
c2s_pid = C2SPid,
|
c2s_pid = C2SPid,
|
||||||
|
socket = Socket,
|
||||||
max_stanza_size = MaxStanzaSize} = State) ->
|
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),
|
close_stream(XMLStreamState),
|
||||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||||
NewState = State#state{socket = TLSSocket,
|
NewState = State#state{socket = TLSSocket,
|
||||||
@ -143,14 +182,23 @@ handle_call({starttls, TLSSocket}, _From,
|
|||||||
xml_stream_state = NewXMLStreamState},
|
xml_stream_state = NewXMLStreamState},
|
||||||
case tls:recv_data(TLSSocket, "") of
|
case tls:recv_data(TLSSocket, "") of
|
||||||
{ok, TLSData} ->
|
{ok, TLSData} ->
|
||||||
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
|
{reply, {ok, TLSSocket},
|
||||||
|
process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
|
||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
{stop, normal, ok, NewState}
|
{stop, normal, ok, NewState}
|
||||||
end;
|
end;
|
||||||
handle_call({compress, ZlibSocket}, _From,
|
handle_call({compress, Data}, _From,
|
||||||
#state{xml_stream_state = XMLStreamState,
|
#state{xml_stream_state = XMLStreamState,
|
||||||
c2s_pid = C2SPid,
|
c2s_pid = C2SPid,
|
||||||
|
socket = Socket,
|
||||||
|
sock_mod = SockMod,
|
||||||
max_stanza_size = MaxStanzaSize} = State) ->
|
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),
|
close_stream(XMLStreamState),
|
||||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||||
NewState = State#state{socket = ZlibSocket,
|
NewState = State#state{socket = ZlibSocket,
|
||||||
@ -158,7 +206,8 @@ handle_call({compress, ZlibSocket}, _From,
|
|||||||
xml_stream_state = NewXMLStreamState},
|
xml_stream_state = NewXMLStreamState},
|
||||||
case ejabberd_zlib:recv_data(ZlibSocket, "") of
|
case ejabberd_zlib:recv_data(ZlibSocket, "") of
|
||||||
{ok, ZlibData} ->
|
{ok, ZlibData} ->
|
||||||
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
|
{reply, {ok, ZlibSocket},
|
||||||
|
process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
|
||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
{stop, normal, ok, NewState}
|
{stop, normal, ok, NewState}
|
||||||
end;
|
end;
|
||||||
@ -172,12 +221,31 @@ handle_call(reset_stream, _From,
|
|||||||
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
|
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
|
||||||
?HIBERNATE_TIMEOUT};
|
?HIBERNATE_TIMEOUT};
|
||||||
handle_call({become_controller, C2SPid}, _From, State) ->
|
handle_call({become_controller, C2SPid}, _From, State) ->
|
||||||
|
erlang:monitor(process, C2SPid),
|
||||||
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
|
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
|
||||||
NewState = State#state{c2s_pid = C2SPid,
|
NewState = State#state{c2s_pid = C2SPid,
|
||||||
xml_stream_state = XMLStreamState},
|
xml_stream_state = XMLStreamState},
|
||||||
activate_socket(NewState),
|
activate_socket(NewState),
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
|
{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) ->
|
handle_call(_Request, _From, State) ->
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||||
@ -237,6 +305,9 @@ handle_info({Tag, _TCPSocket, Reason}, State)
|
|||||||
_ ->
|
_ ->
|
||||||
{stop, normal, State}
|
{stop, normal, State}
|
||||||
end;
|
end;
|
||||||
|
handle_info({'DOWN', _MRef, process, C2SPid, _},
|
||||||
|
#state{c2s_pid = C2SPid} = State) ->
|
||||||
|
{stop, normal, State};
|
||||||
handle_info({timeout, _Ref, activate}, State) ->
|
handle_info({timeout, _Ref, activate}, State) ->
|
||||||
activate_socket(State),
|
activate_socket(State),
|
||||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||||
@ -294,6 +365,17 @@ activate_socket(#state{socket = Socket,
|
|||||||
ok
|
ok
|
||||||
end.
|
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
|
%% Data processing for connectors directly generating xmlelement in
|
||||||
%% Erlang data structure.
|
%% Erlang data structure.
|
||||||
%% WARNING: Shaper does not work with 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.
|
%% Data processing for connectors receivind data as string.
|
||||||
process_data(Data,
|
process_data(Data,
|
||||||
#state{xml_stream_state = XMLStreamState,
|
#state{xml_stream_state = XMLStreamState,
|
||||||
|
tref = TRef,
|
||||||
shaper_state = ShaperState,
|
shaper_state = ShaperState,
|
||||||
c2s_pid = C2SPid} = State) ->
|
c2s_pid = C2SPid} = State) ->
|
||||||
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
|
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
|
||||||
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
|
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
|
||||||
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
|
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
|
||||||
if
|
NewTRef = if
|
||||||
C2SPid == undefined ->
|
C2SPid == undefined ->
|
||||||
ok;
|
TRef;
|
||||||
Pause > 0 ->
|
Pause > 0 ->
|
||||||
erlang:start_timer(Pause, self(), activate);
|
erlang:start_timer(Pause, self(), activate);
|
||||||
true ->
|
true ->
|
||||||
activate_socket(State)
|
activate_socket(State),
|
||||||
|
TRef
|
||||||
end,
|
end,
|
||||||
State#state{xml_stream_state = XMLStreamState1,
|
State#state{xml_stream_state = XMLStreamState1,
|
||||||
|
tref = NewTRef,
|
||||||
shaper_state = NewShaperState}.
|
shaper_state = NewShaperState}.
|
||||||
|
|
||||||
%% Element coming from XML parser are wrapped inside xmlstreamelement
|
%% Element coming from XML parser are wrapped inside xmlstreamelement
|
||||||
@ -345,3 +430,21 @@ close_stream(undefined) ->
|
|||||||
ok;
|
ok;
|
||||||
close_stream(XMLStreamState) ->
|
close_stream(XMLStreamState) ->
|
||||||
xml_stream:close(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_route/1,
|
||||||
unregister_routes/1,
|
unregister_routes/1,
|
||||||
dirty_get_all_routes/0,
|
dirty_get_all_routes/0,
|
||||||
dirty_get_all_domains/0
|
dirty_get_all_domains/0,
|
||||||
|
make_id/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
@ -53,6 +54,9 @@
|
|||||||
-record(route, {domain, pid, local_hint}).
|
-record(route, {domain, pid, local_hint}).
|
||||||
-record(state, {}).
|
-record(state, {}).
|
||||||
|
|
||||||
|
%% "rr" stands for Record-Route.
|
||||||
|
-define(ROUTE_PREFIX, "rr-").
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% API
|
%% API
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
@ -65,7 +69,7 @@ start_link() ->
|
|||||||
|
|
||||||
|
|
||||||
route(From, To, Packet) ->
|
route(From, To, Packet) ->
|
||||||
case catch do_route(From, To, Packet) of
|
case catch route_check_id(From, To, Packet) of
|
||||||
{'EXIT', Reason} ->
|
{'EXIT', Reason} ->
|
||||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||||
[Reason, {From, To, Packet}]);
|
[Reason, {From, To, Packet}]);
|
||||||
@ -192,6 +196,8 @@ dirty_get_all_routes() ->
|
|||||||
dirty_get_all_domains() ->
|
dirty_get_all_domains() ->
|
||||||
lists:usort(mnesia:dirty_all_keys(route)).
|
lists:usort(mnesia:dirty_all_keys(route)).
|
||||||
|
|
||||||
|
make_id() ->
|
||||||
|
?ROUTE_PREFIX ++ randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id().
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -309,6 +315,32 @@ code_change(_OldVsn, State, _Extra) ->
|
|||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%%% Internal functions
|
%%% 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) ->
|
do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||||
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
|
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
|
||||||
[OrigFrom, OrigTo, OrigPacket]),
|
[OrigFrom, OrigTo, OrigPacket]),
|
||||||
@ -413,4 +445,3 @@ update_tables() ->
|
|||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -40,7 +40,8 @@
|
|||||||
dirty_get_connections/0,
|
dirty_get_connections/0,
|
||||||
allow_host/2,
|
allow_host/2,
|
||||||
incoming_s2s_number/0,
|
incoming_s2s_number/0,
|
||||||
outgoing_s2s_number/0
|
outgoing_s2s_number/0,
|
||||||
|
migrate/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -94,30 +95,63 @@ remove_connection(FromTo, Pid, Key) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
have_connection(FromTo) ->
|
have_connection(FromTo) ->
|
||||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
case ejabberd_cluster:get_node(FromTo) of
|
||||||
|
Node when Node == node() ->
|
||||||
|
case mnesia:dirty_read(s2s, FromTo) of
|
||||||
[_] ->
|
[_] ->
|
||||||
true;
|
true;
|
||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
|
end;
|
||||||
|
Node ->
|
||||||
|
case catch rpc:call(Node, mnesia, dirty_read,
|
||||||
|
[s2s, FromTo], 5000) of
|
||||||
|
[_] ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
has_key(FromTo, Key) ->
|
has_key(FromTo, Key) ->
|
||||||
case mnesia:dirty_select(s2s,
|
Query = [{#s2s{fromto = FromTo, key = Key, _ = '_'},
|
||||||
[{#s2s{fromto = FromTo, key = Key, _ = '_'},
|
|
||||||
[],
|
[],
|
||||||
['$_']}]) of
|
['$_']}],
|
||||||
|
case ejabberd_cluster:get_node(FromTo) of
|
||||||
|
Node when Node == node() ->
|
||||||
|
case mnesia:dirty_select(s2s, Query) of
|
||||||
[] ->
|
[] ->
|
||||||
false;
|
false;
|
||||||
_ ->
|
_ ->
|
||||||
true
|
true
|
||||||
|
end;
|
||||||
|
Node ->
|
||||||
|
case catch rpc:call(Node, mnesia, dirty_select,
|
||||||
|
[s2s, Query], 5000) of
|
||||||
|
[_|_] ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_connections_pids(FromTo) ->
|
get_connections_pids(FromTo) ->
|
||||||
|
case ejabberd_cluster:get_node(FromTo) of
|
||||||
|
Node when Node == node() ->
|
||||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||||
L when is_list(L) ->
|
L when is_list(L) ->
|
||||||
[Connection#s2s.pid || Connection <- 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.
|
end.
|
||||||
|
|
||||||
try_register(FromTo) ->
|
try_register(FromTo) ->
|
||||||
@ -148,7 +182,33 @@ try_register(FromTo) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
dirty_get_connections() ->
|
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
|
%% gen_server callbacks
|
||||||
@ -163,10 +223,11 @@ dirty_get_connections() ->
|
|||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
init([]) ->
|
init([]) ->
|
||||||
update_tables(),
|
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)}]),
|
{attributes, record_info(fields, s2s)}]),
|
||||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
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()),
|
ejabberd_commands:register_commands(commands()),
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
@ -198,9 +259,6 @@ handle_cast(_Msg, State) ->
|
|||||||
%% {stop, Reason, State}
|
%% {stop, Reason, State}
|
||||||
%% Description: Handling all non call/cast messages
|
%% 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) ->
|
handle_info({route, From, To, Packet}, State) ->
|
||||||
case catch do_route(From, To, Packet) of
|
case catch do_route(From, To, Packet) of
|
||||||
{'EXIT', Reason} ->
|
{'EXIT', Reason} ->
|
||||||
@ -221,6 +279,7 @@ handle_info(_Info, State) ->
|
|||||||
%% The return value is ignored.
|
%% The return value is ignored.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
|
||||||
ejabberd_commands:unregister_commands(commands()),
|
ejabberd_commands:unregister_commands(commands()),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -234,22 +293,18 @@ code_change(_OldVsn, State, _Extra) ->
|
|||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%%% Internal functions
|
%%% 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) ->
|
do_route(From, To, Packet) ->
|
||||||
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||||
[From, To, Packet, 8]),
|
[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
|
case find_connection(From, To) of
|
||||||
{atomic, Pid} when is_pid(Pid) ->
|
{atomic, Pid} when is_pid(Pid) ->
|
||||||
?DEBUG("sending to process ~p~n", [Pid]),
|
?DEBUG("sending to process ~p~n", [Pid]),
|
||||||
@ -482,6 +537,12 @@ update_tables() ->
|
|||||||
mnesia:delete_table(local_s2s);
|
mnesia:delete_table(local_s2s);
|
||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
|
end,
|
||||||
|
case catch mnesia:table_info(s2s, local_content) of
|
||||||
|
false ->
|
||||||
|
mnesia:delete_table(s2s);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check if host is in blacklist or white list
|
%% Check if host is in blacklist or white list
|
||||||
|
@ -34,7 +34,8 @@
|
|||||||
start_link/3,
|
start_link/3,
|
||||||
start_connection/1,
|
start_connection/1,
|
||||||
terminate_if_waiting_delay/2,
|
terminate_if_waiting_delay/2,
|
||||||
stop_connection/1]).
|
stop_connection/1,
|
||||||
|
stop_connection/2]).
|
||||||
|
|
||||||
%% p1_fsm callbacks (same as gen_fsm)
|
%% p1_fsm callbacks (same as gen_fsm)
|
||||||
-export([init/1,
|
-export([init/1,
|
||||||
@ -44,6 +45,7 @@
|
|||||||
wait_for_features/2,
|
wait_for_features/2,
|
||||||
wait_for_auth_result/2,
|
wait_for_auth_result/2,
|
||||||
wait_for_starttls_proceed/2,
|
wait_for_starttls_proceed/2,
|
||||||
|
relay_to_bridge/2,
|
||||||
reopen_socket/2,
|
reopen_socket/2,
|
||||||
wait_before_retry/2,
|
wait_before_retry/2,
|
||||||
stream_established/2,
|
stream_established/2,
|
||||||
@ -51,9 +53,9 @@
|
|||||||
handle_sync_event/4,
|
handle_sync_event/4,
|
||||||
handle_info/3,
|
handle_info/3,
|
||||||
terminate/3,
|
terminate/3,
|
||||||
print_state/1,
|
|
||||||
code_change/4,
|
code_change/4,
|
||||||
test_get_addr_port/1,
|
test_get_addr_port/1,
|
||||||
|
print_state/1,
|
||||||
get_addr_port/1]).
|
get_addr_port/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
@ -72,6 +74,7 @@
|
|||||||
myname, server, queue,
|
myname, server, queue,
|
||||||
delay_to_retry = undefined_delay,
|
delay_to_retry = undefined_delay,
|
||||||
new = false, verify = false,
|
new = false, verify = false,
|
||||||
|
bridge,
|
||||||
timer}).
|
timer}).
|
||||||
|
|
||||||
%%-define(DBGFSM, true).
|
%%-define(DBGFSM, true).
|
||||||
@ -84,10 +87,11 @@
|
|||||||
|
|
||||||
%% Module start with or without supervisor:
|
%% Module start with or without supervisor:
|
||||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||||
-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_out, [From, Host, Type],
|
-define(SUPERVISOR_START, rpc:call(Node, p1_fsm, start,
|
||||||
fsm_limit_opts() ++ ?FSMOPTS)).
|
[ejabberd_s2s_out, [From, Host, Type],
|
||||||
|
fsm_limit_opts() ++ ?FSMOPTS])).
|
||||||
-else.
|
-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])).
|
[From, Host, Type])).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
@ -126,6 +130,7 @@
|
|||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
start(From, Host, Type) ->
|
start(From, Host, Type) ->
|
||||||
|
Node = ejabberd_cluster:get_node({From, Host}),
|
||||||
?SUPERVISOR_START.
|
?SUPERVISOR_START.
|
||||||
|
|
||||||
start_link(From, Host, Type) ->
|
start_link(From, Host, Type) ->
|
||||||
@ -136,7 +141,10 @@ start_connection(Pid) ->
|
|||||||
p1_fsm:send_event(Pid, init).
|
p1_fsm:send_event(Pid, init).
|
||||||
|
|
||||||
stop_connection(Pid) ->
|
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
|
%%% Callback functions from p1_fsm
|
||||||
@ -228,8 +236,19 @@ open_socket(init, StateData) ->
|
|||||||
{error, _Reason} ->
|
{error, _Reason} ->
|
||||||
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
|
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
|
||||||
[StateData#state.myname, StateData#state.server]),
|
[StateData#state.myname, StateData#state.server]),
|
||||||
|
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)
|
wait_before_reconnect(StateData)
|
||||||
%%{stop, normal, StateData}
|
end
|
||||||
end;
|
end;
|
||||||
open_socket(stop, StateData) ->
|
open_socket(stop, StateData) ->
|
||||||
?INFO_MSG("s2s connection: ~s -> ~s (stopped in open socket)",
|
?INFO_MSG("s2s connection: ~s -> ~s (stopped in open socket)",
|
||||||
@ -677,6 +696,15 @@ reopen_socket(closed, StateData) ->
|
|||||||
wait_before_retry(_Event, StateData) ->
|
wait_before_retry(_Event, StateData) ->
|
||||||
{next_state, wait_before_retry, StateData, ?FSMTIMEOUT}.
|
{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) ->
|
stream_established({xmlstreamelement, El}, StateData) ->
|
||||||
?DEBUG("s2S stream established", []),
|
?DEBUG("s2S stream established", []),
|
||||||
case is_verify_res(El) of
|
case is_verify_res(El) of
|
||||||
@ -747,6 +775,9 @@ stream_established(closed, StateData) ->
|
|||||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
%% {stop, Reason, NewStateData}
|
%% {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) ->
|
handle_event(_Event, StateName, StateData) ->
|
||||||
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
|
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
|
||||||
|
|
||||||
@ -827,6 +858,19 @@ handle_info({send_element, El}, StateName, StateData) ->
|
|||||||
wait_before_retry ->
|
wait_before_retry ->
|
||||||
bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
|
bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
|
||||||
{next_state, StateName, StateData};
|
{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),
|
Q = queue:in(El, StateData#state.queue),
|
||||||
{next_state, StateName, StateData#state{queue = Q},
|
{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),
|
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
|
||||||
ejabberd_router:route_error(To, From, Err, Packet)
|
ejabberd_router:route_error(To, From, Err, Packet)
|
||||||
end,
|
end,
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
handle_info(Info, StateName, StateData) ->
|
||||||
|
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||||
{next_state, StateName, StateData}.
|
{next_state, StateName, StateData}.
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,7 +32,11 @@
|
|||||||
%% API
|
%% API
|
||||||
-export([start_link/0,
|
-export([start_link/0,
|
||||||
route/3,
|
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,
|
check_in_subscription/6,
|
||||||
bounce_offline_message/3,
|
bounce_offline_message/3,
|
||||||
disconnect_removed_user/2,
|
disconnect_removed_user/2,
|
||||||
@ -43,6 +47,7 @@
|
|||||||
dirty_get_sessions_list/0,
|
dirty_get_sessions_list/0,
|
||||||
dirty_get_my_sessions_list/0,
|
dirty_get_my_sessions_list/0,
|
||||||
get_vh_session_list/1,
|
get_vh_session_list/1,
|
||||||
|
get_vh_my_session_list/1,
|
||||||
get_vh_session_number/1,
|
get_vh_session_number/1,
|
||||||
register_iq_handler/4,
|
register_iq_handler/4,
|
||||||
register_iq_handler/5,
|
register_iq_handler/5,
|
||||||
@ -53,7 +58,8 @@
|
|||||||
user_resources/2,
|
user_resources/2,
|
||||||
get_session_pid/3,
|
get_session_pid/3,
|
||||||
get_user_info/3,
|
get_user_info/3,
|
||||||
get_user_ip/3
|
get_user_ip/3,
|
||||||
|
migrate/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -66,7 +72,6 @@
|
|||||||
-include("mod_privacy.hrl").
|
-include("mod_privacy.hrl").
|
||||||
|
|
||||||
-record(session, {sid, usr, us, priority, info}).
|
-record(session, {sid, usr, us, priority, info}).
|
||||||
-record(session_counter, {vhost, count}).
|
|
||||||
-record(state, {}).
|
-record(state, {}).
|
||||||
|
|
||||||
%% default value for the maximum number of user connections
|
%% default value for the maximum number of user connections
|
||||||
@ -92,28 +97,37 @@ route(From, To, Packet) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
open_session(SID, User, Server, Resource, Info) ->
|
open_session(SID, User, Server, Resource, Info) ->
|
||||||
set_session(SID, User, Server, Resource, undefined, Info),
|
open_session(SID, User, Server, Resource, undefined, Info).
|
||||||
mnesia:dirty_update_counter(session_counter,
|
|
||||||
jlib:nameprep(Server), 1),
|
open_session(SID, User, Server, Resource, Priority, Info) ->
|
||||||
|
set_session(SID, User, Server, Resource, Priority, Info),
|
||||||
check_for_sessions_to_replace(User, Server, Resource),
|
check_for_sessions_to_replace(User, Server, Resource),
|
||||||
JID = jlib:make_jid(User, Server, Resource),
|
JID = jlib:make_jid(User, Server, Resource),
|
||||||
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
|
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
|
||||||
[SID, JID, Info]).
|
[SID, JID, Info]).
|
||||||
|
|
||||||
close_session(SID, User, Server, Resource) ->
|
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
|
Info = case mnesia:dirty_read({session, SID}) of
|
||||||
[] -> [];
|
[] -> [];
|
||||||
[#session{info=I}] -> I
|
[#session{info=I}] -> I
|
||||||
end,
|
end,
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
mnesia:delete({session, SID}),
|
mnesia:delete({session, SID})
|
||||||
mnesia:dirty_update_counter(session_counter,
|
|
||||||
jlib:nameprep(Server), -1)
|
|
||||||
end,
|
end,
|
||||||
mnesia:sync_dirty(F),
|
mnesia:sync_dirty(F),
|
||||||
JID = jlib:make_jid(User, Server, Resource),
|
Info.
|
||||||
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
|
|
||||||
[SID, JID, Info]).
|
|
||||||
|
|
||||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||||
case ejabberd_auth:is_user_exists(User, Server) of
|
case ejabberd_auth:is_user_exists(User, Server) of
|
||||||
@ -138,11 +152,17 @@ get_user_resources(User, Server) ->
|
|||||||
LUser = jlib:nodeprep(User),
|
LUser = jlib:nodeprep(User),
|
||||||
LServer = jlib:nameprep(Server),
|
LServer = jlib:nameprep(Server),
|
||||||
US = {LUser, LServer},
|
US = {LUser, LServer},
|
||||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
Ss = case ejabberd_cluster:get_node({LUser, LServer}) of
|
||||||
{'EXIT', _Reason} ->
|
Node when Node == node() ->
|
||||||
[];
|
catch mnesia:dirty_index_read(session, US, #session.us);
|
||||||
Ss ->
|
Node ->
|
||||||
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
|
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.
|
end.
|
||||||
|
|
||||||
get_user_ip(User, Server, Resource) ->
|
get_user_ip(User, Server, Resource) ->
|
||||||
@ -150,12 +170,18 @@ get_user_ip(User, Server, Resource) ->
|
|||||||
LServer = jlib:nameprep(Server),
|
LServer = jlib:nameprep(Server),
|
||||||
LResource = jlib:resourceprep(Resource),
|
LResource = jlib:resourceprep(Resource),
|
||||||
USR = {LUser, LServer, LResource},
|
USR = {LUser, LServer, LResource},
|
||||||
case mnesia:dirty_index_read(session, USR, #session.usr) of
|
Ss = case ejabberd_cluster:get_node({LUser, LServer}) of
|
||||||
[] ->
|
Node when Node == node() ->
|
||||||
undefined;
|
mnesia:dirty_index_read(session, USR, #session.usr);
|
||||||
Ss ->
|
Node ->
|
||||||
|
catch rpc:call(Node, mnesia, dirty_index_read,
|
||||||
|
[session, USR, #session.usr], 5000)
|
||||||
|
end,
|
||||||
|
if is_list(Ss), Ss /= [] ->
|
||||||
Session = lists:max(Ss),
|
Session = lists:max(Ss),
|
||||||
proplists:get_value(ip, Session#session.info)
|
proplists:get_value(ip, Session#session.info);
|
||||||
|
true ->
|
||||||
|
undefined
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_user_info(User, Server, Resource) ->
|
get_user_info(User, Server, Resource) ->
|
||||||
@ -163,15 +189,21 @@ get_user_info(User, Server, Resource) ->
|
|||||||
LServer = jlib:nameprep(Server),
|
LServer = jlib:nameprep(Server),
|
||||||
LResource = jlib:resourceprep(Resource),
|
LResource = jlib:resourceprep(Resource),
|
||||||
USR = {LUser, LServer, LResource},
|
USR = {LUser, LServer, LResource},
|
||||||
case mnesia:dirty_index_read(session, USR, #session.usr) of
|
Ss = case ejabberd_cluster:get_node({LUser, LServer}) of
|
||||||
[] ->
|
Node when Node == node() ->
|
||||||
offline;
|
mnesia:dirty_index_read(session, USR, #session.usr);
|
||||||
Ss ->
|
Node ->
|
||||||
|
catch rpc:call(Node, mnesia, dirty_index_read,
|
||||||
|
[session, USR, #session.usr], 5000)
|
||||||
|
end,
|
||||||
|
if is_list(Ss), Ss /= [] ->
|
||||||
Session = lists:max(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),
|
Conn = proplists:get_value(conn, Session#session.info),
|
||||||
IP = proplists:get_value(ip, 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.
|
end.
|
||||||
|
|
||||||
set_presence(SID, User, Server, Resource, Priority, Presence, Info) ->
|
set_presence(SID, User, Server, Resource, Priority, Presence, Info) ->
|
||||||
@ -194,26 +226,37 @@ get_session_pid(User, Server, Resource) ->
|
|||||||
LServer = jlib:nameprep(Server),
|
LServer = jlib:nameprep(Server),
|
||||||
LResource = jlib:resourceprep(Resource),
|
LResource = jlib:resourceprep(Resource),
|
||||||
USR = {LUser, LServer, LResource},
|
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;
|
[#session{sid = {_, Pid}}] -> Pid;
|
||||||
_ -> none
|
_ -> none
|
||||||
end.
|
end.
|
||||||
|
|
||||||
dirty_get_sessions_list() ->
|
dirty_get_sessions_list() ->
|
||||||
mnesia:dirty_select(
|
Match = [{#session{usr = '$1', _ = '_'}, [], ['$1']}],
|
||||||
session,
|
lists:flatmap(
|
||||||
[{#session{usr = '$1', _ = '_'},
|
fun(Node) when Node == node() ->
|
||||||
[],
|
mnesia:dirty_select(session, Match);
|
||||||
['$1']}]).
|
(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() ->
|
dirty_get_my_sessions_list() ->
|
||||||
mnesia:dirty_select(
|
mnesia:dirty_match_object(#session{_ = '_'}).
|
||||||
session,
|
|
||||||
[{#session{sid = {'_', '$1'}, _ = '_'},
|
|
||||||
[{'==', {node, '$1'}, node()}],
|
|
||||||
['$_']}]).
|
|
||||||
|
|
||||||
get_vh_session_list(Server) ->
|
get_vh_my_session_list(Server) ->
|
||||||
LServer = jlib:nameprep(Server),
|
LServer = jlib:nameprep(Server),
|
||||||
mnesia:dirty_select(
|
mnesia:dirty_select(
|
||||||
session,
|
session,
|
||||||
@ -221,18 +264,23 @@ get_vh_session_list(Server) ->
|
|||||||
[{'==', {element, 2, '$1'}, LServer}],
|
[{'==', {element, 2, '$1'}, LServer}],
|
||||||
['$1']}]).
|
['$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) ->
|
get_vh_session_number(Server) ->
|
||||||
LServer = jlib:nameprep(Server),
|
%% TODO
|
||||||
Query = mnesia:dirty_select(
|
length(get_vh_session_list(Server)).
|
||||||
session_counter,
|
|
||||||
[{#session_counter{vhost = LServer, count = '$1'},
|
|
||||||
[],
|
|
||||||
['$1']}]),
|
|
||||||
case Query of
|
|
||||||
[Count] ->
|
|
||||||
Count;
|
|
||||||
_ -> 0
|
|
||||||
end.
|
|
||||||
|
|
||||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||||
ejabberd_sm ! {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) ->
|
unregister_iq_handler(Host, XMLNS) ->
|
||||||
ejabberd_sm ! {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
|
%% gen_server callbacks
|
||||||
@ -259,16 +322,13 @@ init([]) ->
|
|||||||
update_tables(),
|
update_tables(),
|
||||||
mnesia:create_table(session,
|
mnesia:create_table(session,
|
||||||
[{ram_copies, [node()]},
|
[{ram_copies, [node()]},
|
||||||
|
{local_content, true},
|
||||||
{attributes, record_info(fields, session)}]),
|
{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, usr),
|
||||||
mnesia:add_table_index(session, us),
|
mnesia:add_table_index(session, us),
|
||||||
mnesia:add_table_copy(session, node(), ram_copies),
|
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]),
|
ets:new(sm_iqtable, [named_table]),
|
||||||
|
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Host) ->
|
fun(Host) ->
|
||||||
ejabberd_hooks:add(roster_in_subscription, Host,
|
ejabberd_hooks:add(roster_in_subscription, Host,
|
||||||
@ -319,9 +379,6 @@ handle_info({route, From, To, Packet}, State) ->
|
|||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{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) ->
|
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
|
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
@ -348,6 +405,7 @@ handle_info(_Info, State) ->
|
|||||||
%% The return value is ignored.
|
%% The return value is ignored.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
|
||||||
ejabberd_commands:unregister_commands(commands()),
|
ejabberd_commands:unregister_commands(commands()),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -377,38 +435,20 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
|
|||||||
end,
|
end,
|
||||||
mnesia:sync_dirty(F).
|
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) ->
|
do_route(From, To, Packet) ->
|
||||||
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||||
[From, To, Packet, 8]),
|
[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,
|
#jid{user = User, server = Server,
|
||||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||||
{xmlelement, Name, Attrs, _Els} = Packet,
|
{xmlelement, Name, Attrs, _Els} = Packet,
|
||||||
@ -795,4 +835,11 @@ update_tables() ->
|
|||||||
mnesia:delete_table(local_session);
|
mnesia:delete_table(local_session);
|
||||||
false ->
|
false ->
|
||||||
ok
|
ok
|
||||||
|
end,
|
||||||
|
mnesia:delete_table(session_counter),
|
||||||
|
case catch mnesia:table_info(session, local_content) of
|
||||||
|
false ->
|
||||||
|
mnesia:delete_table(session);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
end.
|
end.
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
get_peer_certificate/1,
|
get_peer_certificate/1,
|
||||||
get_verify_result/1,
|
get_verify_result/1,
|
||||||
close/1,
|
close/1,
|
||||||
|
change_controller/2,
|
||||||
sockname/1, peername/1]).
|
sockname/1, peername/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
@ -129,29 +130,19 @@ connect(Addr, Port, Opts, Timeout) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
starttls(SocketData, TLSOpts) ->
|
starttls(SocketData, TLSOpts) ->
|
||||||
{ok, TLSSocket} = tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
|
starttls(SocketData, TLSOpts, undefined).
|
||||||
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
|
|
||||||
SocketData#socket_state{socket = TLSSocket, sockmod = tls}.
|
|
||||||
|
|
||||||
starttls(SocketData, TLSOpts, Data) ->
|
starttls(SocketData, TLSOpts, Data) ->
|
||||||
{ok, TLSSocket} = tls:tcp_to_tls(SocketData#socket_state.socket, TLSOpts),
|
{ok, TLSSocket} = ejabberd_receiver:starttls(
|
||||||
ejabberd_receiver:starttls(SocketData#socket_state.receiver, TLSSocket),
|
SocketData#socket_state.receiver, TLSOpts, Data),
|
||||||
send(SocketData, Data),
|
|
||||||
SocketData#socket_state{socket = TLSSocket, sockmod = tls}.
|
SocketData#socket_state{socket = TLSSocket, sockmod = tls}.
|
||||||
|
|
||||||
compress(SocketData) ->
|
compress(SocketData) ->
|
||||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
compress(SocketData, undefined).
|
||||||
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, Data) ->
|
compress(SocketData, Data) ->
|
||||||
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
|
{ok, ZlibSocket} = ejabberd_receiver:compress(
|
||||||
SocketData#socket_state.sockmod,
|
SocketData#socket_state.receiver, Data),
|
||||||
SocketData#socket_state.socket),
|
|
||||||
ejabberd_receiver:compress(SocketData#socket_state.receiver, ZlibSocket),
|
|
||||||
send(SocketData, Data),
|
|
||||||
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
|
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
|
||||||
|
|
||||||
reset_stream(SocketData) when is_pid(SocketData#socket_state.receiver) ->
|
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.receiver):reset_stream(
|
||||||
SocketData#socket_state.socket).
|
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
|
%% sockmod=gen_tcp|tls|ejabberd_zlib
|
||||||
send(SocketData, Data) ->
|
send(SocketData, Data) ->
|
||||||
case catch (SocketData#socket_state.sockmod):send(
|
Res = if node(SocketData#socket_state.receiver) == node() ->
|
||||||
SocketData#socket_state.socket, Data) of
|
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;
|
ok -> ok;
|
||||||
{error, timeout} ->
|
{error, timeout} ->
|
||||||
?INFO_MSG("Timeout on ~p:send",[SocketData#socket_state.sockmod]),
|
?INFO_MSG("Timeout on ~p:send",[SocketData#socket_state.sockmod]),
|
||||||
@ -225,3 +231,21 @@ peername(#socket_state{sockmod = SockMod, socket = Socket}) ->
|
|||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% Internal functions
|
%% 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,
|
brutal_kill,
|
||||||
worker,
|
worker,
|
||||||
[ejabberd_hooks]},
|
[ejabberd_hooks]},
|
||||||
NodeGroups =
|
|
||||||
{ejabberd_node_groups,
|
|
||||||
{ejabberd_node_groups, start_link, []},
|
|
||||||
permanent,
|
|
||||||
brutal_kill,
|
|
||||||
worker,
|
|
||||||
[ejabberd_node_groups]},
|
|
||||||
SystemMonitor =
|
SystemMonitor =
|
||||||
{ejabberd_system_monitor,
|
{ejabberd_system_monitor,
|
||||||
{ejabberd_system_monitor, start_link, []},
|
{ejabberd_system_monitor, start_link, []},
|
||||||
@ -153,6 +146,14 @@ init([]) ->
|
|||||||
infinity,
|
infinity,
|
||||||
supervisor,
|
supervisor,
|
||||||
[ejabberd_tmp_sup]},
|
[ejabberd_tmp_sup]},
|
||||||
|
WSLoopSupervisor =
|
||||||
|
{ejabberd_wsloop_sup,
|
||||||
|
{ejabberd_tmp_sup, start_link,
|
||||||
|
[ejabberd_wsloop_sup, ejabberd_wsloop]},
|
||||||
|
permanent,
|
||||||
|
infinity,
|
||||||
|
supervisor,
|
||||||
|
[ejabberd_tmp_sup]},
|
||||||
FrontendSocketSupervisor =
|
FrontendSocketSupervisor =
|
||||||
{ejabberd_frontend_socket_sup,
|
{ejabberd_frontend_socket_sup,
|
||||||
{ejabberd_tmp_sup, start_link,
|
{ejabberd_tmp_sup, start_link,
|
||||||
@ -177,9 +178,23 @@ init([]) ->
|
|||||||
infinity,
|
infinity,
|
||||||
supervisor,
|
supervisor,
|
||||||
[ejabberd_tmp_sup]},
|
[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},
|
{ok, {{one_for_one, 10, 1},
|
||||||
[Hooks,
|
[Hooks,
|
||||||
NodeGroups,
|
Cluster,
|
||||||
SystemMonitor,
|
SystemMonitor,
|
||||||
Router,
|
Router,
|
||||||
SM,
|
SM,
|
||||||
@ -196,6 +211,7 @@ init([]) ->
|
|||||||
IQSupervisor,
|
IQSupervisor,
|
||||||
STUNSupervisor,
|
STUNSupervisor,
|
||||||
FrontendSocketSupervisor,
|
FrontendSocketSupervisor,
|
||||||
|
CacheTabSupervisor,
|
||||||
Listener]}}.
|
Listener]}}.
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,6 +136,7 @@ export EXEC_CMD
|
|||||||
# start server
|
# start server
|
||||||
start ()
|
start ()
|
||||||
{
|
{
|
||||||
|
check_start
|
||||||
$EXEC_CMD "$ERL \
|
$EXEC_CMD "$ERL \
|
||||||
$NAME $ERLANG_NODE \
|
$NAME $ERLANG_NODE \
|
||||||
-noinput -detached \
|
-noinput -detached \
|
||||||
@ -182,6 +183,7 @@ debug ()
|
|||||||
# start interactive server
|
# start interactive server
|
||||||
live ()
|
live ()
|
||||||
{
|
{
|
||||||
|
check_start
|
||||||
echo "--------------------------------------------------------------------"
|
echo "--------------------------------------------------------------------"
|
||||||
echo ""
|
echo ""
|
||||||
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
|
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
|
||||||
@ -210,6 +212,13 @@ live ()
|
|||||||
$ERLANG_OPTS $ARGS \"$@\""
|
$ERLANG_OPTS $ARGS \"$@\""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
etop()
|
||||||
|
{
|
||||||
|
$EXEC_CMD "$ERL \
|
||||||
|
$NAME debug-${TTY}-${ERLANG_NODE} \
|
||||||
|
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
|
||||||
|
}
|
||||||
|
|
||||||
help ()
|
help ()
|
||||||
{
|
{
|
||||||
echo ""
|
echo ""
|
||||||
@ -330,6 +339,26 @@ stop_epmd()
|
|||||||
epmd -names | grep -q name || epmd -kill
|
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
|
# allow sync calls
|
||||||
wait_for_status()
|
wait_for_status()
|
||||||
{
|
{
|
||||||
@ -359,6 +388,7 @@ case $ARGS in
|
|||||||
' start') start;;
|
' start') start;;
|
||||||
' debug') debug;;
|
' debug') debug;;
|
||||||
' live') live;;
|
' live') live;;
|
||||||
|
' etop') etop;;
|
||||||
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||||
' stopped') wait_for_status 3 15 2; stop_epmd;; # wait 15x2s before timeout
|
' stopped') wait_for_status 3 15 2; stop_epmd;; # wait 15x2s before timeout
|
||||||
*) ctl $ARGS;;
|
*) 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_COMMAND:
|
||||||
case PARSE_FINAL_COMMAND:
|
case PARSE_FINAL_COMMAND:
|
||||||
ei_x_new_with_version(&event_buf);
|
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);
|
res = XML_Parse(d->parser, buf, len, command == PARSE_FINAL_COMMAND);
|
||||||
|
|
||||||
if(!res)
|
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_EJABBERD_CONFIG, "ejabberd:config").
|
||||||
|
|
||||||
-define(NS_STREAM, "http://etherx.jabber.org/streams").
|
-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_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas").
|
||||||
-define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
|
-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
|
%% hook handlers
|
||||||
-export([user_send_packet/3]).
|
-export([user_send_packet/4]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
@ -98,11 +98,12 @@ get_features(#caps{node = Node, version = Version, exts = Exts}) ->
|
|||||||
SubNodes = [Version | Exts],
|
SubNodes = [Version | Exts],
|
||||||
lists:foldl(
|
lists:foldl(
|
||||||
fun(SubNode, Acc) ->
|
fun(SubNode, Acc) ->
|
||||||
case mnesia:dirty_read({caps_features,
|
BinaryNode = node_to_binary(Node, SubNode),
|
||||||
node_to_binary(Node, SubNode)}) of
|
case cache_tab:lookup(caps_features, BinaryNode,
|
||||||
[] ->
|
caps_read_fun(BinaryNode)) of
|
||||||
|
error ->
|
||||||
Acc;
|
Acc;
|
||||||
[#caps_features{features = Features}] ->
|
{ok, Features} ->
|
||||||
binary_to_features(Features) ++ Acc
|
binary_to_features(Features) ++ Acc
|
||||||
end
|
end
|
||||||
end, [], SubNodes).
|
end, [], SubNodes).
|
||||||
@ -141,7 +142,8 @@ read_caps([], Result) ->
|
|||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% Hooks
|
%% 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 = ""},
|
#jid{luser = User, lserver = Server, lresource = ""},
|
||||||
{xmlelement, "presence", Attrs, Els}) ->
|
{xmlelement, "presence", Attrs, Els}) ->
|
||||||
Type = xml:get_attr_s("type", Attrs),
|
Type = xml:get_attr_s("type", Attrs),
|
||||||
@ -155,7 +157,7 @@ user_send_packet(#jid{luser = User, lserver = Server} = From,
|
|||||||
true ->
|
true ->
|
||||||
ok
|
ok
|
||||||
end;
|
end;
|
||||||
user_send_packet(_From, _To, _Packet) ->
|
user_send_packet(_DebugFlag, _From, _To, _Packet) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
caps_stream_features(Acc, MyHost) ->
|
caps_stream_features(Acc, MyHost) ->
|
||||||
@ -196,12 +198,23 @@ disco_info(Acc, _Host, _Module, _Node, _Lang) ->
|
|||||||
%%====================================================================
|
%%====================================================================
|
||||||
%% gen_server callbacks
|
%% 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,
|
mnesia:create_table(caps_features,
|
||||||
[{disc_copies, [node()]},
|
[{disc_only_copies, [node()]},
|
||||||
{local_content, true},
|
{local_content, true},
|
||||||
{attributes, record_info(fields, caps_features)}]),
|
{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,
|
ejabberd_hooks:add(user_send_packet, Host,
|
||||||
?MODULE, user_send_packet, 75),
|
?MODULE, user_send_packet, 75),
|
||||||
ejabberd_hooks:add(c2s_stream_features, Host,
|
ejabberd_hooks:add(c2s_stream_features, Host,
|
||||||
@ -252,8 +265,9 @@ code_change(_OldVsn, State, _Extra) ->
|
|||||||
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
|
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
|
||||||
Node = Caps#caps.node,
|
Node = Caps#caps.node,
|
||||||
BinaryNode = node_to_binary(Node, SubNode),
|
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,
|
IQ = #iq{type = get,
|
||||||
xmlns = ?NS_DISCO_INFO,
|
xmlns = ?NS_DISCO_INFO,
|
||||||
sub_el = [{xmlelement, "query",
|
sub_el = [{xmlelement, "query",
|
||||||
@ -284,11 +298,13 @@ feature_response(#iq{type = result,
|
|||||||
(_) ->
|
(_) ->
|
||||||
[]
|
[]
|
||||||
end, Els),
|
end, Els),
|
||||||
mnesia:dirty_write(
|
BinaryFeatures = features_to_binary(Features),
|
||||||
#caps_features{node_pair = BinaryNode,
|
cache_tab:insert(
|
||||||
features = features_to_binary(Features)});
|
caps_features, BinaryNode, BinaryFeatures,
|
||||||
|
caps_write_fun(BinaryNode, BinaryFeatures));
|
||||||
false ->
|
false ->
|
||||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode})
|
cache_tab:insert(caps_features, BinaryNode, [],
|
||||||
|
caps_write_fun(BinaryNode, []))
|
||||||
end,
|
end,
|
||||||
feature_request(Host, From, Caps, SubNodes);
|
feature_request(Host, From, Caps, SubNodes);
|
||||||
feature_response(timeout, _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 got type=error or invalid type=result stanza, so
|
||||||
%% we cache empty feature not to probe the client permanently
|
%% we cache empty feature not to probe the client permanently
|
||||||
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
|
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).
|
feature_request(Host, From, Caps, SubNodes).
|
||||||
|
|
||||||
node_to_binary(Node, SubNode) ->
|
node_to_binary(Node, SubNode) ->
|
||||||
@ -306,6 +323,23 @@ node_to_binary(Node, SubNode) ->
|
|||||||
features_to_binary(L) -> [list_to_binary(I) || I <- L].
|
features_to_binary(L) -> [list_to_binary(I) || I <- L].
|
||||||
binary_to_features(L) -> [binary_to_list(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) ->
|
make_my_disco_hash(Host) ->
|
||||||
JID = jlib:make_jid("", Host, ""),
|
JID = jlib:make_jid("", Host, ""),
|
||||||
case {ejabberd_hooks:run_fold(disco_local_features,
|
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 %
|
%% TODO: Support comment lines starting by %
|
||||||
update_bl_c2s() ->
|
update_bl_c2s() ->
|
||||||
?INFO_MSG("Updating C2S Blacklist", []),
|
?INFO_MSG("Updating C2S Blacklist", []),
|
||||||
case http:request(?BLC2S) of
|
case http_p1:request(?BLC2S) of
|
||||||
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
|
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
|
||||||
IPs = string:tokens(Body,"\n"),
|
IPs = string:tokens(Body,"\n"),
|
||||||
ets:delete_all_objects(bl_c2s),
|
ets:delete_all_objects(bl_c2s),
|
||||||
|
@ -105,8 +105,16 @@ init([From, Host, Server, Username, Encoding, Port, Password]) ->
|
|||||||
open_socket(init, StateData) ->
|
open_socket(init, StateData) ->
|
||||||
Addr = StateData#state.server,
|
Addr = StateData#state.server,
|
||||||
Port = StateData#state.port,
|
Port = StateData#state.port,
|
||||||
?DEBUG("connecting to ~s:~p~n", [Addr, Port]),
|
?DEBUG("Connecting with IPv6 to ~s:~p", [Addr, Port]),
|
||||||
case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of
|
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} ->
|
{ok, Socket} ->
|
||||||
NewStateData = StateData#state{socket = Socket},
|
NewStateData = StateData#state{socket = Socket},
|
||||||
if
|
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,
|
create_room/5,
|
||||||
process_iq_disco_items/4,
|
process_iq_disco_items/4,
|
||||||
broadcast_service_message/2,
|
broadcast_service_message/2,
|
||||||
|
register_room/3,
|
||||||
|
migrate/1,
|
||||||
|
get_vh_rooms/1,
|
||||||
can_use_nick/3]).
|
can_use_nick/3]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
@ -109,7 +112,9 @@ room_destroyed(Host, Room, Pid, ServerHost) ->
|
|||||||
%% Else use the passed options as defined in mod_muc_room.
|
%% Else use the passed options as defined in mod_muc_room.
|
||||||
create_room(Host, Name, From, Nick, Opts) ->
|
create_room(Host, Name, From, Nick, Opts) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
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) ->
|
store_room(Host, Name, Opts) ->
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
@ -162,6 +167,22 @@ can_use_nick(Host, JID, Nick) ->
|
|||||||
U == LUS
|
U == LUS
|
||||||
end.
|
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
|
%% gen_server callbacks
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
@ -174,6 +195,7 @@ can_use_nick(Host, JID, Nick) ->
|
|||||||
%% Description: Initiates the server
|
%% Description: Initiates the server
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
init([Host, Opts]) ->
|
init([Host, Opts]) ->
|
||||||
|
update_muc_online_table(),
|
||||||
mnesia:create_table(muc_room,
|
mnesia:create_table(muc_room,
|
||||||
[{disc_copies, [node()]},
|
[{disc_copies, [node()]},
|
||||||
{attributes, record_info(fields, muc_room)}]),
|
{attributes, record_info(fields, muc_room)}]),
|
||||||
@ -182,14 +204,13 @@ init([Host, Opts]) ->
|
|||||||
{attributes, record_info(fields, muc_registered)}]),
|
{attributes, record_info(fields, muc_registered)}]),
|
||||||
mnesia:create_table(muc_online_room,
|
mnesia:create_table(muc_online_room,
|
||||||
[{ram_copies, [node()]},
|
[{ram_copies, [node()]},
|
||||||
|
{local_content, true},
|
||||||
{attributes, record_info(fields, muc_online_room)}]),
|
{attributes, record_info(fields, muc_online_room)}]),
|
||||||
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
|
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
|
||||||
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
||||||
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
|
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
|
||||||
update_tables(MyHost),
|
update_tables(MyHost),
|
||||||
clean_table_from_bad_node(node(), MyHost),
|
|
||||||
mnesia:add_table_index(muc_registered, nick),
|
mnesia:add_table_index(muc_registered, nick),
|
||||||
mnesia:subscribe(system),
|
|
||||||
Access = gen_mod:get_opt(access, Opts, all),
|
Access = gen_mod:get_opt(access, Opts, all),
|
||||||
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
||||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
|
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, []),
|
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
|
||||||
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
|
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
|
||||||
ejabberd_router:register_route(MyHost),
|
ejabberd_router:register_route(MyHost),
|
||||||
|
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
|
||||||
load_permanent_rooms(MyHost, Host,
|
load_permanent_rooms(MyHost, Host,
|
||||||
{Access, AccessCreate, AccessAdmin, AccessPersistent},
|
{Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||||
HistorySize,
|
HistorySize,
|
||||||
@ -264,12 +286,19 @@ handle_info({route, From, To, Packet},
|
|||||||
default_room_opts = DefRoomOpts,
|
default_room_opts = DefRoomOpts,
|
||||||
history_size = HistorySize,
|
history_size = HistorySize,
|
||||||
room_shaper = RoomShaper} = State) ->
|
room_shaper = RoomShaper} = State) ->
|
||||||
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
{U, S, _} = jlib:jid_tolower(To),
|
||||||
From, To, Packet, DefRoomOpts) of
|
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} ->
|
{'EXIT', Reason} ->
|
||||||
?ERROR_MSG("~p", [Reason]);
|
?ERROR_MSG("~p", [Reason]);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
|
end;
|
||||||
|
Node ->
|
||||||
|
Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME),
|
||||||
|
{Proc, Node} ! {route, From, To, Packet}
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_info({room_destroyed, RoomHost, Pid}, 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,
|
mnesia:delete_object(#muc_online_room{name_host = RoomHost,
|
||||||
pid = Pid})
|
pid = Pid})
|
||||||
end,
|
end,
|
||||||
mnesia:transaction(F),
|
mnesia:sync_dirty(F),
|
||||||
{noreply, State};
|
|
||||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
|
||||||
clean_table_from_bad_node(Node),
|
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
@ -293,6 +319,7 @@ handle_info(_Info, State) ->
|
|||||||
%% The return value is ignored.
|
%% The return value is ignored.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
terminate(_Reason, State) ->
|
terminate(_Reason, State) ->
|
||||||
|
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
|
||||||
ejabberd_router:unregister_route(State#state.host),
|
ejabberd_router:unregister_route(State#state.host),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -532,6 +559,8 @@ load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
|||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(R) ->
|
fun(R) ->
|
||||||
{Room, Host} = R#muc_room.name_host,
|
{Room, Host} = R#muc_room.name_host,
|
||||||
|
case ejabberd_cluster:get_node({Room, Host}) of
|
||||||
|
Node when Node == node() ->
|
||||||
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
||||||
[] ->
|
[] ->
|
||||||
{ok, Pid} = mod_muc_room:start(
|
{ok, Pid} = mod_muc_room:start(
|
||||||
@ -545,6 +574,9 @@ load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
|||||||
register_room(Host, Room, Pid);
|
register_room(Host, Room, Pid);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
end
|
end
|
||||||
end, Rs)
|
end, Rs)
|
||||||
end.
|
end.
|
||||||
@ -571,8 +603,7 @@ register_room(Host, Room, Pid) ->
|
|||||||
mnesia:write(#muc_online_room{name_host = {Room, Host},
|
mnesia:write(#muc_online_room{name_host = {Room, Host},
|
||||||
pid = Pid})
|
pid = Pid})
|
||||||
end,
|
end,
|
||||||
mnesia:transaction(F).
|
mnesia:sync_dirty(F).
|
||||||
|
|
||||||
|
|
||||||
iq_disco_info(Lang) ->
|
iq_disco_info(Lang) ->
|
||||||
[{xmlelement, "identity",
|
[{xmlelement, "identity",
|
||||||
@ -601,7 +632,7 @@ iq_disco_items(Host, From, Lang, none) ->
|
|||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end, get_vh_rooms(Host));
|
end, get_vh_rooms_all_nodes(Host));
|
||||||
|
|
||||||
iq_disco_items(Host, From, Lang, Rsm) ->
|
iq_disco_items(Host, From, Lang, Rsm) ->
|
||||||
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
|
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
|
||||||
@ -621,19 +652,9 @@ iq_disco_items(Host, From, Lang, Rsm) ->
|
|||||||
end, Rooms) ++ RsmOut.
|
end, Rooms) ++ RsmOut.
|
||||||
|
|
||||||
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
|
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),
|
Count = erlang:length(AllRooms),
|
||||||
Guard = case Direction of
|
L = get_vh_rooms_direction(Direction, I, Index, AllRooms),
|
||||||
_ 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,
|
|
||||||
['$_']}])),
|
|
||||||
L2 = if
|
L2 = if
|
||||||
Index == undefined andalso Direction == before ->
|
Index == undefined andalso Direction == before ->
|
||||||
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
|
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}}
|
{L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
|
||||||
end.
|
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.
|
%% @doc Return the position of desired room in the list of rooms.
|
||||||
%% The room must exist in the list. The count starts in 0.
|
%% The room must exist in the list. The count starts in 0.
|
||||||
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
|
%% @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}) ->
|
fun(#muc_online_room{pid = Pid}) ->
|
||||||
gen_fsm:send_all_state_event(
|
gen_fsm:send_all_state_event(
|
||||||
Pid, {service_message, Msg})
|
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) ->
|
get_vh_rooms(Host) ->
|
||||||
mnesia:dirty_select(muc_online_room,
|
mnesia:dirty_select(muc_online_room,
|
||||||
@ -821,39 +878,18 @@ get_vh_rooms(Host) ->
|
|||||||
[{'==', {element, 2, '$1'}, 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_tables(Host) ->
|
||||||
update_muc_room_table(Host),
|
update_muc_room_table(Host),
|
||||||
update_muc_registered_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) ->
|
update_muc_room_table(Host) ->
|
||||||
Fields = record_info(fields, muc_room),
|
Fields = record_info(fields, muc_room),
|
||||||
case mnesia:table_info(muc_room, attributes) of
|
case mnesia:table_info(muc_room, attributes) of
|
||||||
|
@ -73,11 +73,11 @@
|
|||||||
%% Description: Starts the server
|
%% Description: Starts the server
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
start_link(Host, Opts) ->
|
start_link(Host, Opts) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = get_proc_name(Host),
|
||||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
gen_server:start_link(Proc, ?MODULE, [Host, Opts], []).
|
||||||
|
|
||||||
start(Host, Opts) ->
|
start(Host, Opts) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = get_proc_name(Host),
|
||||||
ChildSpec =
|
ChildSpec =
|
||||||
{Proc,
|
{Proc,
|
||||||
{?MODULE, start_link, [Host, Opts]},
|
{?MODULE, start_link, [Host, Opts]},
|
||||||
@ -88,7 +88,7 @@ start(Host, Opts) ->
|
|||||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||||
|
|
||||||
stop(Host) ->
|
stop(Host) ->
|
||||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
Proc = get_proc_name(Host),
|
||||||
gen_server:call(Proc, stop),
|
gen_server:call(Proc, stop),
|
||||||
supervisor:delete_child(ejabberd_sup, Proc).
|
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),
|
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid, get_state),
|
||||||
R.
|
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) ->
|
calc_hour_offset(TimeHere) ->
|
||||||
TimeZero = calendar:now_to_universal_time(now()),
|
TimeZero = calendar:now_to_universal_time(now()),
|
||||||
|
@ -27,14 +27,19 @@
|
|||||||
-module(mod_muc_room).
|
-module(mod_muc_room).
|
||||||
-author('alexey@process-one.net').
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
-behaviour(gen_fsm).
|
-define(GEN_FSM, p1_fsm).
|
||||||
|
|
||||||
|
-behaviour(?GEN_FSM).
|
||||||
|
|
||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
-export([start_link/9,
|
-export([start_link/9,
|
||||||
start_link/7,
|
start_link/7,
|
||||||
|
start_link/2,
|
||||||
start/9,
|
start/9,
|
||||||
start/7,
|
start/7,
|
||||||
|
start/2,
|
||||||
|
migrate/3,
|
||||||
route/4]).
|
route/4]).
|
||||||
|
|
||||||
%% gen_fsm callbacks
|
%% gen_fsm callbacks
|
||||||
@ -44,6 +49,7 @@
|
|||||||
handle_sync_event/4,
|
handle_sync_event/4,
|
||||||
handle_info/3,
|
handle_info/3,
|
||||||
terminate/3,
|
terminate/3,
|
||||||
|
print_state/1,
|
||||||
code_change/4]).
|
code_change/4]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
@ -63,16 +69,12 @@
|
|||||||
|
|
||||||
%% Module start with or without supervisor:
|
%% Module start with or without supervisor:
|
||||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||||
-define(SUPERVISOR_START,
|
-define(SUPERVISOR_START(Args),
|
||||||
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
?GEN_FSM:start(?MODULE, Args, ?FSMOPTS)).
|
||||||
RoomShaper, Creator, Nick, DefRoomOpts],
|
|
||||||
?FSMOPTS)).
|
|
||||||
-else.
|
-else.
|
||||||
-define(SUPERVISOR_START,
|
-define(SUPERVISOR_START(Args),
|
||||||
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
|
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
|
||||||
supervisor:start_child(
|
supervisor:start_child(Supervisor, Args)).
|
||||||
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
|
||||||
Creator, Nick, DefRoomOpts])).
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
@ -80,7 +82,8 @@
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||||
Creator, Nick, DefRoomOpts) ->
|
Creator, Nick, DefRoomOpts) ->
|
||||||
?SUPERVISOR_START.
|
?SUPERVISOR_START([Host, ServerHost, Access, Room, HistorySize,
|
||||||
|
RoomShaper, Creator, Nick, DefRoomOpts]).
|
||||||
|
|
||||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
||||||
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
|
Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup),
|
||||||
@ -88,17 +91,27 @@ start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
|||||||
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||||
Opts]).
|
Opts]).
|
||||||
|
|
||||||
|
start(StateName, StateData) ->
|
||||||
|
ServerHost = StateData#state.server_host,
|
||||||
|
?SUPERVISOR_START([StateName, StateData]).
|
||||||
|
|
||||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||||
Creator, Nick, DefRoomOpts) ->
|
Creator, Nick, DefRoomOpts) ->
|
||||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
?GEN_FSM:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||||
RoomShaper, Creator, Nick, DefRoomOpts],
|
RoomShaper, Creator, Nick, DefRoomOpts],
|
||||||
?FSMOPTS).
|
?FSMOPTS).
|
||||||
|
|
||||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) ->
|
||||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
?GEN_FSM:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||||
RoomShaper, Opts],
|
RoomShaper, Opts],
|
||||||
?FSMOPTS).
|
?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
|
%%% Callback functions from gen_fsm
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
@ -139,7 +152,11 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts]) ->
|
|||||||
jid = jlib:make_jid(Room, Host, ""),
|
jid = jlib:make_jid(Room, Host, ""),
|
||||||
room_shaper = Shaper}),
|
room_shaper = Shaper}),
|
||||||
add_to_log(room_existence, started, State),
|
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
|
%% Func: StateName/2
|
||||||
@ -580,6 +597,9 @@ handle_event(destroy, StateName, StateData) ->
|
|||||||
handle_event({set_affiliations, Affiliations}, StateName, StateData) ->
|
handle_event({set_affiliations, Affiliations}, StateName, StateData) ->
|
||||||
{next_state, StateName, StateData#state{affiliations = Affiliations}};
|
{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) ->
|
handle_event(_Event, StateName, StateData) ->
|
||||||
{next_state, StateName, StateData}.
|
{next_state, StateName, StateData}.
|
||||||
|
|
||||||
@ -612,6 +632,9 @@ handle_sync_event(_Event, _From, StateName, StateData) ->
|
|||||||
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||||
{ok, StateName, StateData}.
|
{ok, StateName, StateData}.
|
||||||
|
|
||||||
|
print_state(StateData) ->
|
||||||
|
StateData.
|
||||||
|
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
%% Func: handle_info/3
|
%% Func: handle_info/3
|
||||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||||
@ -701,6 +724,13 @@ handle_info(_Info, StateName, StateData) ->
|
|||||||
%% Purpose: Shutdown the fsm
|
%% Purpose: Shutdown the fsm
|
||||||
%% Returns: any
|
%% 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) ->
|
terminate(Reason, _StateName, StateData) ->
|
||||||
?INFO_MSG("Stopping MUC room ~s@~s",
|
?INFO_MSG("Stopping MUC room ~s@~s",
|
||||||
[StateData#state.room, StateData#state.host]),
|
[StateData#state.room, StateData#state.host]),
|
||||||
@ -739,7 +769,7 @@ terminate(Reason, _StateName, StateData) ->
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
route(Pid, From, ToNick, Packet) ->
|
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,
|
process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
|
||||||
StateData) ->
|
StateData) ->
|
||||||
@ -1625,13 +1655,12 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
|
|||||||
From, Err),
|
From, Err),
|
||||||
StateData;
|
StateData;
|
||||||
captcha_required ->
|
captcha_required ->
|
||||||
ID = randoms:get_string(),
|
|
||||||
SID = xml:get_attr_s("id", Attrs),
|
SID = xml:get_attr_s("id", Attrs),
|
||||||
RoomJID = StateData#state.jid,
|
RoomJID = StateData#state.jid,
|
||||||
To = jlib:jid_replace_resource(RoomJID, Nick),
|
To = jlib:jid_replace_resource(RoomJID, Nick),
|
||||||
case ejabberd_captcha:create_captcha(
|
case ejabberd_captcha:create_captcha(
|
||||||
ID, SID, RoomJID, To, Lang, From) of
|
SID, RoomJID, To, Lang, From) of
|
||||||
{ok, CaptchaEls} ->
|
{ok, ID, CaptchaEls} ->
|
||||||
MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls},
|
MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls},
|
||||||
Robots = ?DICT:store(From,
|
Robots = ?DICT:store(From,
|
||||||
{Nick, Packet}, StateData#state.robots),
|
{Nick, Packet}, StateData#state.robots),
|
||||||
|
@ -30,16 +30,14 @@
|
|||||||
-behaviour(gen_mod).
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
-export([start/2,
|
-export([start/2,
|
||||||
loop/1,
|
init/1,
|
||||||
stop/1,
|
stop/1,
|
||||||
store_packet/3,
|
store_packet/3,
|
||||||
resend_offline_messages/2,
|
resend_offline_messages/2,
|
||||||
pop_offline_messages/3,
|
pop_offline_messages/3,
|
||||||
get_sm_features/5,
|
|
||||||
remove_expired_messages/0,
|
remove_expired_messages/0,
|
||||||
remove_old_messages/1,
|
remove_old_messages/1,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
get_queue_length/2,
|
|
||||||
webadmin_page/3,
|
webadmin_page/3,
|
||||||
webadmin_user/4,
|
webadmin_user/4,
|
||||||
webadmin_user_parse_query/5,
|
webadmin_user_parse_query/5,
|
||||||
@ -55,9 +53,6 @@
|
|||||||
-define(PROCNAME, ejabberd_offline).
|
-define(PROCNAME, ejabberd_offline).
|
||||||
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
||||||
|
|
||||||
%% default value for the maximum number of user messages
|
|
||||||
-define(MAX_USER_MESSAGES, infinity).
|
|
||||||
|
|
||||||
start(Host, Opts) ->
|
start(Host, Opts) ->
|
||||||
mnesia:create_table(offline_msg,
|
mnesia:create_table(offline_msg,
|
||||||
[{disc_only_copies, [node()]},
|
[{disc_only_copies, [node()]},
|
||||||
@ -72,10 +67,6 @@ start(Host, Opts) ->
|
|||||||
?MODULE, remove_user, 50),
|
?MODULE, remove_user, 50),
|
||||||
ejabberd_hooks:add(anonymous_purge_hook, Host,
|
ejabberd_hooks:add(anonymous_purge_hook, Host,
|
||||||
?MODULE, remove_user, 50),
|
?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,
|
ejabberd_hooks:add(webadmin_page_host, Host,
|
||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:add(webadmin_user, Host,
|
ejabberd_hooks:add(webadmin_user, Host,
|
||||||
@ -84,18 +75,22 @@ start(Host, Opts) ->
|
|||||||
?MODULE, webadmin_user_parse_query, 50),
|
?MODULE, webadmin_user_parse_query, 50),
|
||||||
ejabberd_hooks:add(count_offline_messages, Host,
|
ejabberd_hooks:add(count_offline_messages, Host,
|
||||||
?MODULE, count_offline_messages, 50),
|
?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),
|
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
|
receive
|
||||||
#offline_msg{us=US} = Msg ->
|
#offline_msg{us=US} = Msg ->
|
||||||
Msgs = receive_all(US, [Msg]),
|
Msgs = receive_all(US, [Msg]),
|
||||||
Len = length(Msgs),
|
Len = length(Msgs),
|
||||||
{User, Host} = US,
|
|
||||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
|
||||||
User, Host),
|
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
%% Only count messages if needed:
|
%% Only count messages if needed:
|
||||||
Count = if MaxOfflineMsgs =/= infinity ->
|
Count = if MaxOfflineMsgs =/= infinity ->
|
||||||
@ -121,18 +116,9 @@ loop(AccessMaxOfflineMsgs) ->
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
mnesia:transaction(F),
|
mnesia:transaction(F),
|
||||||
loop(AccessMaxOfflineMsgs);
|
loop(MaxOfflineMsgs);
|
||||||
_ ->
|
_ ->
|
||||||
loop(AccessMaxOfflineMsgs)
|
loop(MaxOfflineMsgs)
|
||||||
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
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
receive_all(US, Msgs) ->
|
receive_all(US, Msgs) ->
|
||||||
@ -153,8 +139,6 @@ stop(Host) ->
|
|||||||
?MODULE, remove_user, 50),
|
?MODULE, remove_user, 50),
|
||||||
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
||||||
?MODULE, remove_user, 50),
|
?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,
|
ejabberd_hooks:delete(webadmin_page_host, Host,
|
||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:delete(webadmin_user, Host,
|
ejabberd_hooks:delete(webadmin_user, Host,
|
||||||
@ -165,27 +149,12 @@ stop(Host) ->
|
|||||||
exit(whereis(Proc), stop),
|
exit(whereis(Proc), stop),
|
||||||
{wait, Proc}.
|
{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) ->
|
store_packet(From, To, Packet) ->
|
||||||
Type = xml:get_tag_attr_s("type", Packet),
|
Type = xml:get_tag_attr_s("type", Packet),
|
||||||
if
|
if
|
||||||
(Type /= "error") and (Type /= "groupchat") and
|
(Type /= "error") and (Type /= "groupchat") and
|
||||||
(Type /= "headline") ->
|
(Type /= "headline") ->
|
||||||
case check_event_chatstates(From, To, Packet) of
|
case check_event(From, To, Packet) of
|
||||||
true ->
|
true ->
|
||||||
#jid{luser = LUser, lserver = LServer} = To,
|
#jid{luser = LUser, lserver = LServer} = To,
|
||||||
TimeStamp = now(),
|
TimeStamp = now(),
|
||||||
@ -206,22 +175,12 @@ store_packet(From, To, Packet) ->
|
|||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check if the packet has any content about XEP-0022 or XEP-0085
|
check_event(From, To, Packet) ->
|
||||||
check_event_chatstates(From, To, Packet) ->
|
|
||||||
{xmlelement, Name, Attrs, Els} = Packet,
|
{xmlelement, Name, Attrs, Els} = Packet,
|
||||||
case find_x_event_chatstates(Els, {false, false, false}) of
|
case find_x_event(Els) of
|
||||||
%% There wasn't any x:event or chatstates subelements
|
false ->
|
||||||
{false, false, _} ->
|
|
||||||
true;
|
true;
|
||||||
%% There a chatstates subelement and other stuff, but no x:event
|
El ->
|
||||||
{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 ->
|
|
||||||
case xml:get_subtag(El, "id") of
|
case xml:get_subtag(El, "id") of
|
||||||
false ->
|
false ->
|
||||||
case xml:get_subtag(El, "offline") of
|
case xml:get_subtag(El, "offline") of
|
||||||
@ -249,19 +208,16 @@ check_event_chatstates(From, To, Packet) ->
|
|||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
|
find_x_event([]) ->
|
||||||
find_x_event_chatstates([], Res) ->
|
false;
|
||||||
Res;
|
find_x_event([{xmlcdata, _} | Els]) ->
|
||||||
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
|
find_x_event(Els);
|
||||||
find_x_event_chatstates(Els, Res);
|
find_x_event([El | Els]) ->
|
||||||
find_x_event_chatstates([El | Els], {A, B, C}) ->
|
|
||||||
case xml:get_tag_attr_s("xmlns", El) of
|
case xml:get_tag_attr_s("xmlns", El) of
|
||||||
?NS_EVENT ->
|
?NS_EVENT ->
|
||||||
find_x_event_chatstates(Els, {El, B, C});
|
El;
|
||||||
?NS_CHATSTATES ->
|
|
||||||
find_x_event_chatstates(Els, {A, El, C});
|
|
||||||
_ ->
|
_ ->
|
||||||
find_x_event_chatstates(Els, {A, B, true})
|
find_x_event(Els)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
find_x_expire(_, []) ->
|
find_x_expire(_, []) ->
|
||||||
@ -310,13 +266,6 @@ resend_offline_messages(User, Server) ->
|
|||||||
{xmlelement, Name, Attrs,
|
{xmlelement, Name, Attrs,
|
||||||
Els ++
|
Els ++
|
||||||
[jlib:timestamp_to_xml(
|
[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))]}}
|
R#offline_msg.timestamp))]}}
|
||||||
end,
|
end,
|
||||||
@ -346,13 +295,6 @@ pop_offline_messages(Ls, User, Server) ->
|
|||||||
{xmlelement, Name, Attrs,
|
{xmlelement, Name, Attrs,
|
||||||
Els ++
|
Els ++
|
||||||
[jlib:timestamp_to_xml(
|
[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))]}}
|
R#offline_msg.timestamp))]}}
|
||||||
end,
|
end,
|
||||||
@ -370,7 +312,6 @@ pop_offline_messages(Ls, User, Server) ->
|
|||||||
Ls
|
Ls
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
remove_expired_messages() ->
|
remove_expired_messages() ->
|
||||||
TimeStamp = now(),
|
TimeStamp = now(),
|
||||||
F = fun() ->
|
F = fun() ->
|
||||||
@ -533,9 +474,8 @@ webadmin_page(Acc, _, _) -> Acc.
|
|||||||
user_queue(User, Server, Query, Lang) ->
|
user_queue(User, Server, Query, Lang) ->
|
||||||
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
|
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
|
||||||
Res = user_queue_parse_query(US, Query),
|
Res = user_queue_parse_query(US, Query),
|
||||||
MsgsAll = lists:keysort(#offline_msg.timestamp,
|
Msgs = lists:keysort(#offline_msg.timestamp,
|
||||||
mnesia:dirty_read({offline_msg, US})),
|
mnesia:dirty_read({offline_msg, US})),
|
||||||
Msgs = get_messages_subset(User, Server, MsgsAll),
|
|
||||||
FMsgs =
|
FMsgs =
|
||||||
lists:map(
|
lists:map(
|
||||||
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
|
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
|
||||||
@ -617,32 +557,9 @@ user_queue_parse_query(US, Query) ->
|
|||||||
us_to_list({User, Server}) ->
|
us_to_list({User, Server}) ->
|
||||||
jlib:jid_to_string({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) ->
|
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/",
|
FQueueLen = [?AC("queue/",
|
||||||
integer_to_list(QueueLen))],
|
integer_to_list(QueueLen))],
|
||||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||||
|
@ -32,13 +32,11 @@
|
|||||||
-export([count_offline_messages/2]).
|
-export([count_offline_messages/2]).
|
||||||
|
|
||||||
-export([start/2,
|
-export([start/2,
|
||||||
loop/2,
|
init/2,
|
||||||
stop/1,
|
stop/1,
|
||||||
store_packet/3,
|
store_packet/3,
|
||||||
pop_offline_messages/3,
|
pop_offline_messages/3,
|
||||||
get_sm_features/5,
|
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
get_queue_length/2,
|
|
||||||
webadmin_page/3,
|
webadmin_page/3,
|
||||||
webadmin_user/4,
|
webadmin_user/4,
|
||||||
webadmin_user_parse_query/5,
|
webadmin_user_parse_query/5,
|
||||||
@ -54,9 +52,6 @@
|
|||||||
-define(PROCNAME, ejabberd_offline).
|
-define(PROCNAME, ejabberd_offline).
|
||||||
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
||||||
|
|
||||||
%% default value for the maximum number of user messages
|
|
||||||
-define(MAX_USER_MESSAGES, infinity).
|
|
||||||
|
|
||||||
start(Host, Opts) ->
|
start(Host, Opts) ->
|
||||||
ejabberd_hooks:add(offline_message_hook, Host,
|
ejabberd_hooks:add(offline_message_hook, Host,
|
||||||
?MODULE, store_packet, 50),
|
?MODULE, store_packet, 50),
|
||||||
@ -66,10 +61,6 @@ start(Host, Opts) ->
|
|||||||
?MODULE, remove_user, 50),
|
?MODULE, remove_user, 50),
|
||||||
ejabberd_hooks:add(anonymous_purge_hook, Host,
|
ejabberd_hooks:add(anonymous_purge_hook, Host,
|
||||||
?MODULE, remove_user, 50),
|
?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,
|
ejabberd_hooks:add(webadmin_page_host, Host,
|
||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:add(webadmin_user, Host,
|
ejabberd_hooks:add(webadmin_user, Host,
|
||||||
@ -78,17 +69,22 @@ start(Host, Opts) ->
|
|||||||
?MODULE, webadmin_user_parse_query, 50),
|
?MODULE, webadmin_user_parse_query, 50),
|
||||||
ejabberd_hooks:add(count_offline_messages, Host,
|
ejabberd_hooks:add(count_offline_messages, Host,
|
||||||
?MODULE, count_offline_messages, 50),
|
?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),
|
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
|
receive
|
||||||
#offline_msg{user = User} = Msg ->
|
#offline_msg{user = User} = Msg ->
|
||||||
Msgs = receive_all(User, [Msg]),
|
Msgs = receive_all(User, [Msg]),
|
||||||
Len = length(Msgs),
|
Len = length(Msgs),
|
||||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
|
||||||
User, Host),
|
|
||||||
|
|
||||||
%% Only count existing messages if needed:
|
%% Only count existing messages if needed:
|
||||||
Count = if MaxOfflineMsgs =/= infinity ->
|
Count = if MaxOfflineMsgs =/= infinity ->
|
||||||
@ -115,18 +111,12 @@ loop(Host, AccessMaxOfflineMsgs) ->
|
|||||||
Packet = {xmlelement, Name, Attrs2,
|
Packet = {xmlelement, Name, Attrs2,
|
||||||
Els ++
|
Els ++
|
||||||
[jlib:timestamp_to_xml(
|
[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(
|
calendar:now_to_universal_time(
|
||||||
M#offline_msg.timestamp))]},
|
M#offline_msg.timestamp))]},
|
||||||
XML =
|
XML =
|
||||||
ejabberd_odbc:escape(
|
ejabberd_odbc:escape(
|
||||||
xml:element_to_binary(Packet)),
|
lists:flatten(
|
||||||
|
xml:element_to_string(Packet))),
|
||||||
odbc_queries:add_spool_sql(Username, XML)
|
odbc_queries:add_spool_sql(Username, XML)
|
||||||
end, Msgs),
|
end, Msgs),
|
||||||
case catch odbc_queries:add_spool(Host, Query) of
|
case catch odbc_queries:add_spool(Host, Query) of
|
||||||
@ -138,18 +128,9 @@ loop(Host, AccessMaxOfflineMsgs) ->
|
|||||||
ok
|
ok
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
loop(Host, AccessMaxOfflineMsgs);
|
loop(Host, MaxOfflineMsgs);
|
||||||
_ ->
|
_ ->
|
||||||
loop(Host, AccessMaxOfflineMsgs)
|
loop(Host, MaxOfflineMsgs)
|
||||||
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
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
receive_all(Username, Msgs) ->
|
receive_all(Username, Msgs) ->
|
||||||
@ -170,8 +151,6 @@ stop(Host) ->
|
|||||||
?MODULE, remove_user, 50),
|
?MODULE, remove_user, 50),
|
||||||
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
||||||
?MODULE, remove_user, 50),
|
?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,
|
ejabberd_hooks:delete(webadmin_page_host, Host,
|
||||||
?MODULE, webadmin_page, 50),
|
?MODULE, webadmin_page, 50),
|
||||||
ejabberd_hooks:delete(webadmin_user, Host,
|
ejabberd_hooks:delete(webadmin_user, Host,
|
||||||
@ -182,27 +161,12 @@ stop(Host) ->
|
|||||||
exit(whereis(Proc), stop),
|
exit(whereis(Proc), stop),
|
||||||
ok.
|
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) ->
|
store_packet(From, To, Packet) ->
|
||||||
Type = xml:get_tag_attr_s("type", Packet),
|
Type = xml:get_tag_attr_s("type", Packet),
|
||||||
if
|
if
|
||||||
(Type /= "error") and (Type /= "groupchat") and
|
(Type /= "error") and (Type /= "groupchat") and
|
||||||
(Type /= "headline") ->
|
(Type /= "headline") ->
|
||||||
case check_event_chatstates(From, To, Packet) of
|
case check_event(From, To, Packet) of
|
||||||
true ->
|
true ->
|
||||||
#jid{luser = LUser} = To,
|
#jid{luser = LUser} = To,
|
||||||
TimeStamp = now(),
|
TimeStamp = now(),
|
||||||
@ -223,22 +187,12 @@ store_packet(From, To, Packet) ->
|
|||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check if the packet has any content about XEP-0022 or XEP-0085
|
check_event(From, To, Packet) ->
|
||||||
check_event_chatstates(From, To, Packet) ->
|
|
||||||
{xmlelement, Name, Attrs, Els} = Packet,
|
{xmlelement, Name, Attrs, Els} = Packet,
|
||||||
case find_x_event_chatstates(Els, {false, false, false}) of
|
case find_x_event(Els) of
|
||||||
%% There wasn't any x:event or chatstates subelements
|
false ->
|
||||||
{false, false, _} ->
|
|
||||||
true;
|
true;
|
||||||
%% There a chatstates subelement and other stuff, but no x:event
|
El ->
|
||||||
{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 ->
|
|
||||||
case xml:get_subtag(El, "id") of
|
case xml:get_subtag(El, "id") of
|
||||||
false ->
|
false ->
|
||||||
case xml:get_subtag(El, "offline") of
|
case xml:get_subtag(El, "offline") of
|
||||||
@ -266,19 +220,16 @@ check_event_chatstates(From, To, Packet) ->
|
|||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
|
find_x_event([]) ->
|
||||||
find_x_event_chatstates([], Res) ->
|
false;
|
||||||
Res;
|
find_x_event([{xmlcdata, _} | Els]) ->
|
||||||
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
|
find_x_event(Els);
|
||||||
find_x_event_chatstates(Els, Res);
|
find_x_event([El | Els]) ->
|
||||||
find_x_event_chatstates([El | Els], {A, B, C}) ->
|
|
||||||
case xml:get_tag_attr_s("xmlns", El) of
|
case xml:get_tag_attr_s("xmlns", El) of
|
||||||
?NS_EVENT ->
|
?NS_EVENT ->
|
||||||
find_x_event_chatstates(Els, {El, B, C});
|
El;
|
||||||
?NS_CHATSTATES ->
|
|
||||||
find_x_event_chatstates(Els, {A, El, C});
|
|
||||||
_ ->
|
_ ->
|
||||||
find_x_event_chatstates(Els, {A, B, true})
|
find_x_event(Els)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
find_x_expire(_, []) ->
|
find_x_expire(_, []) ->
|
||||||
@ -378,7 +329,7 @@ user_queue(User, Server, Query, Lang) ->
|
|||||||
Username = ejabberd_odbc:escape(LUser),
|
Username = ejabberd_odbc:escape(LUser),
|
||||||
US = {LUser, LServer},
|
US = {LUser, LServer},
|
||||||
Res = user_queue_parse_query(Username, LServer, Query),
|
Res = user_queue_parse_query(Username, LServer, Query),
|
||||||
MsgsAll = case catch ejabberd_odbc:sql_query(
|
Msgs = case catch ejabberd_odbc:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
["select username, xml from spool"
|
["select username, xml from spool"
|
||||||
" where username='", Username, "'"
|
" where username='", Username, "'"
|
||||||
@ -396,7 +347,6 @@ user_queue(User, Server, Query, Lang) ->
|
|||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
end,
|
end,
|
||||||
Msgs = get_messages_subset(User, Server, MsgsAll),
|
|
||||||
FMsgs =
|
FMsgs =
|
||||||
lists:map(
|
lists:map(
|
||||||
fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
|
fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
|
||||||
@ -483,8 +433,11 @@ user_queue_parse_query(Username, LServer, Query) ->
|
|||||||
us_to_list({User, Server}) ->
|
us_to_list({User, Server}) ->
|
||||||
jlib:jid_to_string({User, Server, ""}).
|
jlib:jid_to_string({User, Server, ""}).
|
||||||
|
|
||||||
get_queue_length(Username, LServer) ->
|
webadmin_user(Acc, User, Server, Lang) ->
|
||||||
case catch ejabberd_odbc:sql_query(
|
LUser = jlib:nodeprep(User),
|
||||||
|
LServer = jlib:nameprep(Server),
|
||||||
|
Username = ejabberd_odbc:escape(LUser),
|
||||||
|
QueueLen = case catch ejabberd_odbc:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
["select count(*) from spool"
|
["select count(*) from spool"
|
||||||
" where username='", Username, "';"]) of
|
" where username='", Username, "';"]) of
|
||||||
@ -492,32 +445,7 @@ get_queue_length(Username, LServer) ->
|
|||||||
SCount;
|
SCount;
|
||||||
_ ->
|
_ ->
|
||||||
0
|
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,
|
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),
|
|
||||||
FQueueLen = [?AC("queue/", QueueLen)],
|
FQueueLen = [?AC("queue/", QueueLen)],
|
||||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
handle_info/2, code_change/3]).
|
handle_info/2, code_change/3]).
|
||||||
|
|
||||||
%% Hook callbacks
|
%% 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 = "",
|
-record(state, {host = "",
|
||||||
send_pings = ?DEFAULT_SEND_PINGS,
|
send_pings = ?DEFAULT_SEND_PINGS,
|
||||||
@ -107,6 +107,8 @@ init([Host, Opts]) ->
|
|||||||
?MODULE, user_online, 100),
|
?MODULE, user_online, 100),
|
||||||
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
||||||
?MODULE, user_offline, 100),
|
?MODULE, user_offline, 100),
|
||||||
|
ejabberd_hooks:add(sm_remove_migrated_connection_hook, Host,
|
||||||
|
?MODULE, user_offline, 100),
|
||||||
ejabberd_hooks:add(user_send_packet, Host,
|
ejabberd_hooks:add(user_send_packet, Host,
|
||||||
?MODULE, user_send, 100);
|
?MODULE, user_send, 100);
|
||||||
_ ->
|
_ ->
|
||||||
@ -121,6 +123,8 @@ init([Host, Opts]) ->
|
|||||||
terminate(_Reason, #state{host = Host}) ->
|
terminate(_Reason, #state{host = Host}) ->
|
||||||
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
|
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
|
||||||
?MODULE, user_offline, 100),
|
?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,
|
ejabberd_hooks:delete(sm_register_connection_hook, Host,
|
||||||
?MODULE, user_online, 100),
|
?MODULE, user_online, 100),
|
||||||
ejabberd_hooks:delete(user_send_packet, Host,
|
ejabberd_hooks:delete(user_send_packet, Host,
|
||||||
@ -193,7 +197,7 @@ user_online(_SID, JID, _Info) ->
|
|||||||
user_offline(_SID, JID, _Info) ->
|
user_offline(_SID, JID, _Info) ->
|
||||||
stop_ping(JID#jid.lserver, JID).
|
stop_ping(JID#jid.lserver, JID).
|
||||||
|
|
||||||
user_send(JID, _From, _Packet) ->
|
user_send(_DebugFlag, JID, _From, _Packet) ->
|
||||||
start_ping(JID#jid.lserver, JID).
|
start_ping(JID#jid.lserver, JID).
|
||||||
|
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
|
@ -392,12 +392,13 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
|
|||||||
{atomic, {error, _} = Error} ->
|
{atomic, {error, _} = Error} ->
|
||||||
Error;
|
Error;
|
||||||
{atomic, {result, _} = Res} ->
|
{atomic, {result, _} = Res} ->
|
||||||
|
NeedDb = is_list_needdb(List),
|
||||||
ejabberd_router:route(
|
ejabberd_router:route(
|
||||||
jlib:make_jid(LUser, LServer, ""),
|
jlib:make_jid(LUser, LServer, ""),
|
||||||
jlib:make_jid(LUser, LServer, ""),
|
jlib:make_jid(LUser, LServer, ""),
|
||||||
{xmlelement, "broadcast", [],
|
{xmlelement, "broadcast", [],
|
||||||
[{privacy_list,
|
[{privacy_list,
|
||||||
#userlist{name = Name, list = List},
|
#userlist{name = Name, list = List, needdb = NeedDb},
|
||||||
Name}]}),
|
Name}]}),
|
||||||
Res;
|
Res;
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(mod_privacy_hrl, true).
|
||||||
|
|
||||||
-record(privacy, {us,
|
-record(privacy, {us,
|
||||||
default = none,
|
default = none,
|
||||||
lists = []}).
|
lists = []}).
|
||||||
|
@ -392,12 +392,13 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
|
|||||||
{atomic, {error, _} = Error} ->
|
{atomic, {error, _} = Error} ->
|
||||||
Error;
|
Error;
|
||||||
{atomic, {result, _} = Res} ->
|
{atomic, {result, _} = Res} ->
|
||||||
|
NeedDb = is_list_needdb(List),
|
||||||
ejabberd_router:route(
|
ejabberd_router:route(
|
||||||
jlib:make_jid(LUser, LServer, ""),
|
jlib:make_jid(LUser, LServer, ""),
|
||||||
jlib:make_jid(LUser, LServer, ""),
|
jlib:make_jid(LUser, LServer, ""),
|
||||||
{xmlelement, "broadcast", [],
|
{xmlelement, "broadcast", [],
|
||||||
[{privacy_list,
|
[{privacy_list,
|
||||||
#userlist{name = Name, list = List},
|
#userlist{name = Name, list = List, needdb = NeedDb},
|
||||||
Name}]}),
|
Name}]}),
|
||||||
Res;
|
Res;
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -71,7 +71,9 @@ start_link(Host, Opts) ->
|
|||||||
gen_server:start_link({local, Proc}, ?MODULE, [Opts], []).
|
gen_server:start_link({local, Proc}, ?MODULE, [Opts], []).
|
||||||
|
|
||||||
init([Opts]) ->
|
init([Opts]) ->
|
||||||
|
update_tables(),
|
||||||
mnesia:create_table(bytestream, [{ram_copies, [node()]},
|
mnesia:create_table(bytestream, [{ram_copies, [node()]},
|
||||||
|
{local_content, true},
|
||||||
{attributes, record_info(fields, bytestream)}]),
|
{attributes, record_info(fields, bytestream)}]),
|
||||||
mnesia:add_table_copy(bytestream, node(), ram_copies),
|
mnesia:add_table_copy(bytestream, node(), ram_copies),
|
||||||
MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
|
MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
|
||||||
@ -179,3 +181,11 @@ activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) ->
|
|||||||
_ ->
|
_ ->
|
||||||
error
|
error
|
||||||
end.
|
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
|
%% exports for console debug manual use
|
||||||
-export([create_node/5,
|
-export([create_node/5,
|
||||||
|
create_node/7,
|
||||||
delete_node/3,
|
delete_node/3,
|
||||||
subscribe_node/5,
|
subscribe_node/5,
|
||||||
unsubscribe_node/5,
|
unsubscribe_node/5,
|
||||||
@ -766,13 +767,15 @@ out_subscription(User, Server, JID, subscribed) ->
|
|||||||
[] -> user_resources(PUser, PServer);
|
[] -> user_resources(PUser, PServer);
|
||||||
_ -> [PResource]
|
_ -> [PResource]
|
||||||
end,
|
end,
|
||||||
presence(Server, {presence, PUser, PServer, PResources, Owner});
|
presence(Server, {presence, PUser, PServer, PResources, Owner}),
|
||||||
|
true;
|
||||||
out_subscription(_,_,_,_) ->
|
out_subscription(_,_,_,_) ->
|
||||||
ok.
|
true.
|
||||||
in_subscription(_, User, Server, Owner, unsubscribed, _) ->
|
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(_, _, _, _, _, _) ->
|
in_subscription(_, _, _, _, _, _) ->
|
||||||
ok.
|
true.
|
||||||
|
|
||||||
unsubscribe_user(Entity, Owner) ->
|
unsubscribe_user(Entity, Owner) ->
|
||||||
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
BJID = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||||
|
@ -76,8 +76,7 @@ terminate(Host, ServerHost) ->
|
|||||||
node_hometree_odbc:terminate(Host, ServerHost).
|
node_hometree_odbc:terminate(Host, ServerHost).
|
||||||
|
|
||||||
options() ->
|
options() ->
|
||||||
[{node_type, flat},
|
[{deliver_payloads, true},
|
||||||
{deliver_payloads, true},
|
|
||||||
{notify_config, false},
|
{notify_config, false},
|
||||||
{notify_delete, false},
|
{notify_delete, false},
|
||||||
{notify_retract, true},
|
{notify_retract, true},
|
||||||
|
@ -86,7 +86,6 @@ terminate(Host, ServerHost) ->
|
|||||||
|
|
||||||
options() ->
|
options() ->
|
||||||
[{odbc, true},
|
[{odbc, true},
|
||||||
{node_type, pep},
|
|
||||||
{deliver_payloads, true},
|
{deliver_payloads, true},
|
||||||
{notify_config, false},
|
{notify_config, false},
|
||||||
{notify_delete, 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,
|
-export([start/2,
|
||||||
stop/1,
|
stop/1,
|
||||||
log_user_send/3,
|
log_user_send/4,
|
||||||
log_user_receive/4]).
|
log_user_receive/5]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("jlib.hrl").
|
-include("jlib.hrl").
|
||||||
@ -51,10 +51,10 @@ stop(Host) ->
|
|||||||
?MODULE, log_user_receive, 50),
|
?MODULE, log_user_receive, 50),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
log_user_send(From, To, Packet) ->
|
log_user_send(_DebugFlag, From, To, Packet) ->
|
||||||
log_packet(From, To, Packet, From#jid.lserver).
|
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).
|
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 = ""},
|
luser = "", lserver = Logger, lresource = ""},
|
||||||
{xmlelement, "route", [], [FixedPacket]})
|
{xmlelement, "route", [], [FixedPacket]})
|
||||||
end, Loggers).
|
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),
|
{UTC, UTC_diff} = jlib:timestamp_to_iso(Now_universal, utc),
|
||||||
Seconds_diff = calendar:datetime_to_gregorian_seconds(Now_local)
|
Seconds_diff = calendar:datetime_to_gregorian_seconds(Now_local)
|
||||||
- calendar:datetime_to_gregorian_seconds(Now_universal),
|
- 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}),
|
{_, TZO_diff} = jlib:timestamp_to_iso({{0, 0, 0}, {0, 0, 0}}, {Hd, Md}),
|
||||||
IQ#iq{type = result,
|
IQ#iq{type = result,
|
||||||
sub_el = [{xmlelement, "time",
|
sub_el = [{xmlelement, "time",
|
||||||
|
@ -169,7 +169,7 @@ start_link(Host, Opts) ->
|
|||||||
|
|
||||||
init([Host, Opts]) ->
|
init([Host, Opts]) ->
|
||||||
State = parse_options(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,
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||||
?MODULE, process_local_iq, IQDisc),
|
?MODULE, process_local_iq, IQDisc),
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
|
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/1,
|
||||||
escape_like/1,
|
escape_like/1,
|
||||||
to_bool/1,
|
to_bool/1,
|
||||||
keep_alive/1]).
|
keep_alive/1,
|
||||||
|
sql_query_on_all_connections/2]).
|
||||||
|
|
||||||
%% gen_fsm callbacks
|
%% gen_fsm callbacks
|
||||||
-export([init/1,
|
-export([init/1,
|
||||||
@ -98,6 +99,13 @@ start_link(Host, StartInterval) ->
|
|||||||
sql_query(Host, Query) ->
|
sql_query(Host, Query) ->
|
||||||
sql_call(Host, {sql_query, 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
|
%% SQL transaction based on a list of queries
|
||||||
%% This function automatically
|
%% This function automatically
|
||||||
sql_transaction(Host, Queries) when is_list(Queries) ->
|
sql_transaction(Host, Queries) when is_list(Queries) ->
|
||||||
@ -427,13 +435,15 @@ sql_query_internal(Query) ->
|
|||||||
State = get(?STATE_KEY),
|
State = get(?STATE_KEY),
|
||||||
Res = case State#state.db_type of
|
Res = case State#state.db_type of
|
||||||
odbc ->
|
odbc ->
|
||||||
odbc:sql_query(State#state.db_ref, Query);
|
odbc:sql_query(State#state.db_ref, Query, ?TRANSACTION_TIMEOUT - 1000);
|
||||||
pgsql ->
|
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));
|
pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query));
|
||||||
mysql ->
|
mysql ->
|
||||||
?DEBUG("MySQL, Send query~n~p~n", [Query]),
|
?DEBUG("MySQL, Send query~n~p~n", [Query]),
|
||||||
R = mysql_to_odbc(mysql_conn:fetch(State#state.db_ref,
|
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]),
|
%% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
|
||||||
R
|
R
|
||||||
end,
|
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
|
case mysql_conn:start(Server, Port, Username, Password, DB, fun log/3) of
|
||||||
{ok, Ref} ->
|
{ok, Ref} ->
|
||||||
mysql_conn:fetch(Ref, ["set names 'utf8';"], self()),
|
mysql_conn:fetch(Ref, ["set names 'utf8';"], self()),
|
||||||
|
mysql_conn:fetch(Ref, ["SET SESSION query_cache_type=1;"], self()),
|
||||||
{ok, Ref};
|
{ok, Ref};
|
||||||
Err ->
|
Err ->
|
||||||
Err
|
Err
|
||||||
|
@ -555,7 +555,17 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time,
|
|||||||
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
|
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
|
||||||
Limits, Queue, QueueLen);
|
Limits, Queue, QueueLen);
|
||||||
{migrate, NStateData, {Node, M, F, A}, Time1} ->
|
{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 ->
|
{badrpc, _} = Err ->
|
||||||
{migration_error, Err};
|
{migration_error, Err};
|
||||||
{'EXIT', _} = Err ->
|
{'EXIT', _} = Err ->
|
||||||
@ -565,7 +575,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time,
|
|||||||
{ok, Clone} ->
|
{ok, Clone} ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
MRef = erlang:monitor(process, Clone),
|
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);
|
relay_messages(MRef, TRef, Clone, Queue);
|
||||||
Reply ->
|
Reply ->
|
||||||
{migration_error, {bad_reply, Reply}}
|
{migration_error, {bad_reply, Reply}}
|
||||||
@ -608,7 +620,17 @@ handle_msg(Msg, Parent, Name, StateName, StateData,
|
|||||||
loop(Parent, Name, NStateName, NStateData,
|
loop(Parent, Name, NStateName, NStateData,
|
||||||
Mod, Time1, Debug1, Limits, Queue, QueueLen);
|
Mod, Time1, Debug1, Limits, Queue, QueueLen);
|
||||||
{migrate, NStateData, {Node, M, F, A}, Time1} ->
|
{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} ->
|
{badrpc, R} ->
|
||||||
{migration_error, R};
|
{migration_error, R};
|
||||||
{'EXIT', R} ->
|
{'EXIT', R} ->
|
||||||
@ -618,7 +640,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData,
|
|||||||
{ok, Clone} ->
|
{ok, Clone} ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
MRef = erlang:monitor(process, Clone),
|
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);
|
relay_messages(MRef, TRef, Clone, Queue);
|
||||||
Reply ->
|
Reply ->
|
||||||
{migration_error, {bad_reply, Reply}}
|
{migration_error, {bad_reply, Reply}}
|
||||||
|
@ -65,7 +65,8 @@
|
|||||||
request_tp,
|
request_tp,
|
||||||
request_headers = [],
|
request_headers = [],
|
||||||
end_of_request = false,
|
end_of_request = false,
|
||||||
trail = ""
|
trail = "",
|
||||||
|
websocket_handlers = []
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
|
||||||
@ -134,11 +135,16 @@ init({SockMod, Socket}, Opts) ->
|
|||||||
false -> []
|
false -> []
|
||||||
end,
|
end,
|
||||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
?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}]),
|
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
|
||||||
State = #state{sockmod = SockMod1,
|
State = #state{sockmod = SockMod1,
|
||||||
socket = Socket1,
|
socket = Socket1,
|
||||||
request_handlers = RequestHandlers},
|
request_handlers = RequestHandlers,
|
||||||
|
websocket_handlers = WebSocketHandlers},
|
||||||
receive_headers(State).
|
receive_headers(State).
|
||||||
|
|
||||||
|
|
||||||
@ -148,6 +154,9 @@ become_controller(_Pid) ->
|
|||||||
socket_type() ->
|
socket_type() ->
|
||||||
raw.
|
raw.
|
||||||
|
|
||||||
|
|
||||||
|
send_text(_State, none) ->
|
||||||
|
exit(normal);
|
||||||
send_text(State, Text) ->
|
send_text(State, Text) ->
|
||||||
case catch (State#state.sockmod):send(State#state.socket, Text) of
|
case catch (State#state.sockmod):send(State#state.socket, Text) of
|
||||||
ok -> ok;
|
ok -> ok;
|
||||||
@ -289,6 +298,11 @@ process_header(State, Data) ->
|
|||||||
add_header(Name, Value, State) ->
|
add_header(Name, Value, State) ->
|
||||||
[{Name, Value} | State#state.request_headers].
|
[{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}
|
%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP}
|
||||||
%% where
|
%% where
|
||||||
%% SockMod = gen_tcp | tls
|
%% SockMod = gen_tcp | tls
|
||||||
@ -313,8 +327,38 @@ get_transfer_protocol(SockMod, HostPort) ->
|
|||||||
%% XXX bard: search through request handlers looking for one that
|
%% XXX bard: search through request handlers looking for one that
|
||||||
%% matches the requested URL path, and pass control to it. If none is
|
%% matches the requested URL path, and pass control to it. If none is
|
||||||
%% found, answer with HTTP 404.
|
%% found, answer with HTTP 404.
|
||||||
|
|
||||||
process([], _) ->
|
process([], _) ->
|
||||||
ejabberd_web:error(not_found);
|
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) ->
|
process(Handlers, Request) ->
|
||||||
[{HandlerPathPrefix, HandlerModule} | HandlersLeft] = Handlers,
|
[{HandlerPathPrefix, HandlerModule} | HandlersLeft] = Handlers,
|
||||||
|
|
||||||
@ -344,6 +388,7 @@ process_request(#state{request_method = Method,
|
|||||||
request_tp = TP,
|
request_tp = TP,
|
||||||
request_headers = RequestHeaders,
|
request_headers = RequestHeaders,
|
||||||
sockmod = SockMod,
|
sockmod = SockMod,
|
||||||
|
websocket_handlers = WebSocketHandlers,
|
||||||
socket = Socket} = State)
|
socket = Socket} = State)
|
||||||
when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' ->
|
when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' ->
|
||||||
case (catch url_decode_q_split(Path)) of
|
case (catch url_decode_q_split(Path)) of
|
||||||
@ -364,6 +409,29 @@ process_request(#state{request_method = Method,
|
|||||||
_ ->
|
_ ->
|
||||||
SockMod:peername(Socket)
|
SockMod:peername(Socket)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
%% 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 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,
|
Request = #request{method = Method,
|
||||||
path = LPath,
|
path = LPath,
|
||||||
q = LQuery,
|
q = LQuery,
|
||||||
@ -374,10 +442,6 @@ process_request(#state{request_method = Method,
|
|||||||
tp = TP,
|
tp = TP,
|
||||||
headers = RequestHeaders,
|
headers = RequestHeaders,
|
||||||
ip = IP},
|
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
|
case process(RequestHandlers, Request) of
|
||||||
El when element(1, El) == xmlelement ->
|
El when element(1, El) == xmlelement ->
|
||||||
make_xhtml_output(State, 200, [], El);
|
make_xhtml_output(State, 200, [], El);
|
||||||
@ -389,6 +453,7 @@ process_request(#state{request_method = Method,
|
|||||||
{Status, Headers, Output} when is_list(Output) or is_binary(Output) ->
|
{Status, Headers, Output} when is_list(Output) or is_binary(Output) ->
|
||||||
make_text_output(State, Status, Headers, Output)
|
make_text_output(State, Status, Headers, Output)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
|
|
||||||
process_request(#state{request_method = Method,
|
process_request(#state{request_method = Method,
|
||||||
|
@ -32,3 +32,21 @@
|
|||||||
tp, % transfer protocol = http | https
|
tp, % transfer protocol = http | https
|
||||||
headers
|
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,
|
setopts/2,
|
||||||
controlling_process/2,
|
controlling_process/2,
|
||||||
become_controller/2,
|
become_controller/2,
|
||||||
|
change_controller/2,
|
||||||
custom_receiver/1,
|
custom_receiver/1,
|
||||||
reset_stream/1,
|
reset_stream/1,
|
||||||
change_shaper/2,
|
change_shaper/2,
|
||||||
@ -121,9 +122,19 @@
|
|||||||
start(XMPPDomain, Sid, Key, IP) ->
|
start(XMPPDomain, Sid, Key, IP) ->
|
||||||
?DEBUG("Starting session", []),
|
?DEBUG("Starting session", []),
|
||||||
case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of
|
case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of
|
||||||
{ok, Pid} -> {ok, Pid};
|
{ok, Pid} ->
|
||||||
_ -> check_bind_module(XMPPDomain),
|
{ok, Pid};
|
||||||
{error, "Cannot start HTTP bind session"}
|
{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.
|
end.
|
||||||
|
|
||||||
start_link(Sid, Key, IP) ->
|
start_link(Sid, Key, IP) ->
|
||||||
@ -139,8 +150,19 @@ setopts({http_bind, FsmRef, _IP}, Opts) ->
|
|||||||
case lists:member({active, once}, Opts) of
|
case lists:member({active, once}, Opts) of
|
||||||
true ->
|
true ->
|
||||||
gen_fsm:send_all_state_event(FsmRef, {activate, self()});
|
gen_fsm:send_all_state_event(FsmRef, {activate, self()});
|
||||||
|
_ ->
|
||||||
|
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
|
ok
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
controlling_process(_Socket, _Pid) ->
|
controlling_process(_Socket, _Pid) ->
|
||||||
@ -152,6 +174,9 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
|
|||||||
become_controller(FsmRef, C2SPid) ->
|
become_controller(FsmRef, C2SPid) ->
|
||||||
gen_fsm:send_all_state_event(FsmRef, {become_controller, 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}) ->
|
reset_stream({http_bind, _FsmRef, _IP}) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -170,7 +195,6 @@ sockname(_Socket) ->
|
|||||||
peername({http_bind, _FsmRef, IP}) ->
|
peername({http_bind, _FsmRef, IP}) ->
|
||||||
{ok, IP}.
|
{ok, IP}.
|
||||||
|
|
||||||
|
|
||||||
%% Entry point for data coming from client through ejabberd HTTP server:
|
%% Entry point for data coming from client through ejabberd HTTP server:
|
||||||
process_request(Data, IP) ->
|
process_request(Data, IP) ->
|
||||||
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
|
Opts1 = ejabberd_c2s_config:get_c2s_limits(),
|
||||||
@ -192,12 +216,12 @@ process_request(Data, IP) ->
|
|||||||
"xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
|
"xmlns='" ++ ?NS_HTTP_BIND ++ "'/>"};
|
||||||
XmppDomain ->
|
XmppDomain ->
|
||||||
%% create new session
|
%% create new session
|
||||||
Sid = sha:sha(term_to_binary({now(), make_ref()})),
|
Sid = make_sid(),
|
||||||
case start(XmppDomain, Sid, "", IP) of
|
case start(XmppDomain, Sid, "", IP) of
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
{200, ?HEADER, "<body type='terminate' "
|
{500, ?HEADER, "<body type='terminate' "
|
||||||
"condition='internal-server-error' "
|
"condition='internal-server-error' "
|
||||||
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>BOSH module not started</body>"};
|
"xmlns='" ++ ?NS_HTTP_BIND ++ "'>Internal Server Error</body>"};
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
handle_session_start(
|
handle_session_start(
|
||||||
Pid, XmppDomain, Sid, Rid, Attrs,
|
Pid, XmppDomain, Sid, Rid, Attrs,
|
||||||
@ -223,10 +247,10 @@ process_request(Data, IP) ->
|
|||||||
handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
|
handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize,
|
||||||
StreamStart, IP);
|
StreamStart, IP);
|
||||||
{size_limit, Sid} ->
|
{size_limit, Sid} ->
|
||||||
case mnesia:dirty_read({http_bind, Sid}) of
|
case get_session(Sid) of
|
||||||
[] ->
|
{error, _} ->
|
||||||
{404, ?HEADER, ""};
|
{404, ?HEADER, ""};
|
||||||
[#http_bind{pid = FsmRef}] ->
|
{ok, #http_bind{pid = FsmRef}} ->
|
||||||
gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
|
gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}),
|
||||||
{200, ?HEADER, "<body type='terminate' "
|
{200, ?HEADER, "<body type='terminate' "
|
||||||
"condition='undefined-condition' "
|
"condition='undefined-condition' "
|
||||||
@ -282,7 +306,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
|
|||||||
end,
|
end,
|
||||||
XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
|
XmppVersion = xml:get_attr_s("xmpp:version", Attrs),
|
||||||
?DEBUG("Create session: ~p", [Sid]),
|
?DEBUG("Create session: ~p", [Sid]),
|
||||||
mnesia:transaction(
|
mnesia:async_dirty(
|
||||||
fun() ->
|
fun() ->
|
||||||
mnesia:write(
|
mnesia:write(
|
||||||
#http_bind{id = Sid,
|
#http_bind{id = Sid,
|
||||||
@ -340,6 +364,7 @@ init([Sid, Key, IP]) ->
|
|||||||
%% {stop, Reason, NewStateData}
|
%% {stop, Reason, NewStateData}
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
handle_event({become_controller, C2SPid}, StateName, StateData) ->
|
handle_event({become_controller, C2SPid}, StateName, StateData) ->
|
||||||
|
erlang:monitor(process, C2SPid),
|
||||||
case StateData#state.input of
|
case StateData#state.input of
|
||||||
cancel ->
|
cancel ->
|
||||||
{next_state, StateName, StateData#state{
|
{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) ->
|
handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) ->
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
{stop, normal, Reply, StateData};
|
{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) ->
|
handle_sync_event({stop,Reason}, _From, _StateName, StateData) ->
|
||||||
?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
|
?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]),
|
||||||
Reply = ok,
|
Reply = ok,
|
||||||
@ -537,6 +570,9 @@ handle_info({timeout, ShaperTimer, _}, StateName,
|
|||||||
#state{shaper_timer = ShaperTimer} = StateData) ->
|
#state{shaper_timer = ShaperTimer} = StateData) ->
|
||||||
{next_state, StateName, StateData#state{shaper_timer = undefined}};
|
{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) ->
|
handle_info(_, StateName, StateData) ->
|
||||||
{next_state, 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) ->
|
http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) ->
|
||||||
?DEBUG("Looking for session: ~p", [Sid]),
|
?DEBUG("Looking for session: ~p", [Sid]),
|
||||||
case mnesia:dirty_read({http_bind, Sid}) of
|
case get_session(Sid) of
|
||||||
[] ->
|
{error, _} ->
|
||||||
{error, not_exists};
|
{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 =
|
NewStream =
|
||||||
case StreamStart of
|
case StreamStart of
|
||||||
true ->
|
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.
|
%% Print a warning in log file if this is not the case.
|
||||||
check_bind_module(XmppDomain) ->
|
check_bind_module(XmppDomain) ->
|
||||||
case gen_mod:is_loaded(XmppDomain, mod_http_bind) of
|
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"
|
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.
|
end.
|
||||||
|
@ -77,9 +77,12 @@
|
|||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
start(ID, Key, IP) ->
|
start(ID, Key, IP) ->
|
||||||
|
update_tables(),
|
||||||
mnesia:create_table(http_poll,
|
mnesia:create_table(http_poll,
|
||||||
[{ram_copies, [node()]},
|
[{ram_copies, [node()]},
|
||||||
|
{local_content, true},
|
||||||
{attributes, record_info(fields, http_poll)}]),
|
{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]).
|
supervisor:start_child(ejabberd_http_poll_sup, [ID, Key, IP]).
|
||||||
|
|
||||||
start_link(ID, Key, IP) ->
|
start_link(ID, Key, IP) ->
|
||||||
@ -115,9 +118,9 @@ process([], #request{data = Data,
|
|||||||
{ok, ID1, Key, NewKey, Packet} ->
|
{ok, ID1, Key, NewKey, Packet} ->
|
||||||
ID = if
|
ID = if
|
||||||
(ID1 == "0") or (ID1 == "mobile") ->
|
(ID1 == "0") or (ID1 == "mobile") ->
|
||||||
NewID = sha:sha(term_to_binary({now(), make_ref()})),
|
NewID = make_sid(),
|
||||||
{ok, Pid} = start(NewID, "", IP),
|
{ok, Pid} = start(NewID, "", IP),
|
||||||
mnesia:transaction(
|
mnesia:async_dirty(
|
||||||
fun() ->
|
fun() ->
|
||||||
mnesia:write(#http_poll{id = NewID,
|
mnesia:write(#http_poll{id = NewID,
|
||||||
pid = Pid})
|
pid = Pid})
|
||||||
@ -272,7 +275,13 @@ handle_event(_Event, StateName, StateData) ->
|
|||||||
%% {stop, Reason, Reply, NewStateData}
|
%% {stop, Reason, Reply, NewStateData}
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
handle_sync_event({send, Packet}, _From, StateName, StateData) ->
|
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 = ok,
|
||||||
{reply, Reply, StateName, StateData#state{output = Output}};
|
{reply, Reply, StateName, StateData#state{output = Output}};
|
||||||
|
|
||||||
@ -350,7 +359,7 @@ handle_info(_, StateName, StateData) ->
|
|||||||
%% Returns: any
|
%% Returns: any
|
||||||
%%----------------------------------------------------------------------
|
%%----------------------------------------------------------------------
|
||||||
terminate(_Reason, _StateName, StateData) ->
|
terminate(_Reason, _StateName, StateData) ->
|
||||||
mnesia:transaction(
|
mnesia:async_dirty(
|
||||||
fun() ->
|
fun() ->
|
||||||
mnesia:delete({http_poll, StateData#state.id})
|
mnesia:delete({http_poll, StateData#state.id})
|
||||||
end),
|
end),
|
||||||
@ -375,19 +384,19 @@ terminate(_Reason, _StateName, StateData) ->
|
|||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
http_put(ID, Key, NewKey, Packet) ->
|
http_put(ID, Key, NewKey, Packet) ->
|
||||||
case mnesia:dirty_read({http_poll, ID}) of
|
case get_session(ID) of
|
||||||
[] ->
|
{error, _} ->
|
||||||
{error, not_exists};
|
{error, not_exists};
|
||||||
[#http_poll{pid = FsmRef}] ->
|
{ok, #http_poll{pid = FsmRef}} ->
|
||||||
gen_fsm:sync_send_all_state_event(
|
gen_fsm:sync_send_all_state_event(
|
||||||
FsmRef, {http_put, Key, NewKey, Packet})
|
FsmRef, {http_put, Key, NewKey, Packet})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
http_get(ID) ->
|
http_get(ID) ->
|
||||||
case mnesia:dirty_read({http_poll, ID}) of
|
case get_session(ID) of
|
||||||
[] ->
|
{error, _} ->
|
||||||
{error, not_exists};
|
{error, not_exists};
|
||||||
[#http_poll{pid = FsmRef}] ->
|
{ok, #http_poll{pid = FsmRef}} ->
|
||||||
gen_fsm:sync_send_all_state_event(FsmRef, http_get)
|
gen_fsm:sync_send_all_state_event(FsmRef, http_get)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -446,3 +455,39 @@ get_jid(Type, ParsedPacket) ->
|
|||||||
false ->
|
false ->
|
||||||
jlib:make_jid("","","")
|
jlib:make_jid("","","")
|
||||||
end.
|
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
|
% mod_http_bind is already started so it will not be started again
|
||||||
ok;
|
ok;
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
{'EXIT', {start_child_error, Error}}
|
exit({start_child_error, Error})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
stop(_Host) ->
|
stop(_Host) ->
|
||||||
@ -125,14 +125,22 @@ stop(_Host) ->
|
|||||||
ok ->
|
ok ->
|
||||||
ok;
|
ok;
|
||||||
{error, Error} ->
|
{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.
|
end.
|
||||||
|
|
||||||
setup_database() ->
|
setup_database() ->
|
||||||
migrate_database(),
|
migrate_database(),
|
||||||
mnesia:create_table(http_bind,
|
mnesia:create_table(http_bind,
|
||||||
[{ram_copies, [node()]},
|
[{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() ->
|
migrate_database() ->
|
||||||
case catch mnesia:table_info(http_bind, attributes) of
|
case catch mnesia:table_info(http_bind, attributes) of
|
||||||
@ -142,4 +150,10 @@ migrate_database() ->
|
|||||||
%% Since the stored information is not important, instead
|
%% Since the stored information is not important, instead
|
||||||
%% of actually migrating data, let's just destroy the table
|
%% of actually migrating data, let's just destroy the table
|
||||||
mnesia:delete_table(http_bind)
|
mnesia:delete_table(http_bind)
|
||||||
|
end,
|
||||||
|
case catch mnesia:table_info(http_bind, local_content) of
|
||||||
|
false ->
|
||||||
|
mnesia:delete_table(http_bind);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
end.
|
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,
|
new/2,
|
||||||
parse/2,
|
parse/2,
|
||||||
close/1,
|
close/1,
|
||||||
|
change_callback_pid/2,
|
||||||
parse_element/1]).
|
parse_element/1]).
|
||||||
|
|
||||||
-define(XML_START, 0).
|
-define(XML_START, 0).
|
||||||
@ -110,6 +111,8 @@ new(CallbackPid, MaxSize) ->
|
|||||||
size = 0,
|
size = 0,
|
||||||
maxsize = MaxSize}.
|
maxsize = MaxSize}.
|
||||||
|
|
||||||
|
change_callback_pid(State, CallbackPid) ->
|
||||||
|
State#xml_stream_state{callback_pid = CallbackPid}.
|
||||||
|
|
||||||
parse(#xml_stream_state{callback_pid = CallbackPid,
|
parse(#xml_stream_state{callback_pid = CallbackPid,
|
||||||
port = Port,
|
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