Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05bc5b8b4d |
@@ -9,7 +9,8 @@ To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- Libexpat 1.95 or higher
|
||||
- Erlang/OTP R10B-9 or higher. Recommended versions: R12B-5 and R13B04
|
||||
- Erlang/OTP R10B-9 or higher. The recommended version is R12B-5.
|
||||
Support for R13 is experimental.
|
||||
- OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL
|
||||
encryption. Optional, highly recommended.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support
|
||||
@@ -19,7 +20,7 @@ To compile ejabberd you need:
|
||||
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
|
||||
- GNU Iconv 1.8 or higher, for the IRC Transport
|
||||
(mod_irc). Optional. Not needed on systems with GNU Libc.
|
||||
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
|
||||
- ImageMagick’s Convert program. Optional. For CAPTCHA challenges.
|
||||
- exmpp 0.9.2 or higher. Optional. For import/export XEP-0227 files.
|
||||
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ extract_lang_all ()
|
||||
done
|
||||
|
||||
cd $MSGS_DIR
|
||||
REVISION=`git describe --always`
|
||||
REVISION=`svn info | grep "^Rev" | head -1 | awk '{print $2}'`
|
||||
zip $HOME/ejabberd-langs-$REVISION.zip *.translate;
|
||||
|
||||
rm *.translate
|
||||
|
||||
+2
-5
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.4 Developers Guide
|
||||
<TITLE>Ejabberd 2.1.2 Developers Guide
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
@@ -49,7 +49,7 @@ TD P{margin:0px;}
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.4 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.2 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
@@ -194,9 +194,6 @@ operation are as follows:
|
||||
auth:User:Server:Password (check if a username/password pair is correct)
|
||||
</LI><LI CLASS="li-itemize">isuser:User:Server (check if it’s a valid user)
|
||||
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user’s password)
|
||||
</LI><LI CLASS="li-itemize">tryregister:User:Server:Password (try to register an account)
|
||||
</LI><LI CLASS="li-itemize">removeuser:User:Server (remove this account)
|
||||
</LI><LI CLASS="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
</LI></UL>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">write to stdout: AABB
|
||||
|
||||
@@ -176,9 +176,6 @@ That script is supposed to do theses actions, in an infinite loop:
|
||||
\item auth:User:Server:Password (check if a username/password pair is correct)
|
||||
\item isuser:User:Server (check if it's a valid user)
|
||||
\item setpass:User:Server:Password (set user's password)
|
||||
\item tryregister:User:Server:Password (try to register an account)
|
||||
\item removeuser:User:Server (remove this account)
|
||||
\item removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\item write to stdout: AABB
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.4 Feature Sheet
|
||||
<TITLE>Ejabberd 2.1.2 Feature Sheet
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
@@ -50,7 +50,7 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.4 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.2 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
|
||||
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
|
||||
+237
-337
File diff suppressed because it is too large
Load Diff
+67
-181
@@ -92,13 +92,11 @@
|
||||
\newcommand{\modrosterodbc}{\module{mod\_roster\_odbc}}
|
||||
\newcommand{\modservicelog}{\module{mod\_service\_log}}
|
||||
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
|
||||
\newcommand{\modsic}{\module{mod\_sic}}
|
||||
\newcommand{\modstats}{\module{mod\_stats}}
|
||||
\newcommand{\modtime}{\module{mod\_time}}
|
||||
\newcommand{\modvcard}{\module{mod\_vcard}}
|
||||
\newcommand{\modvcardldap}{\module{mod\_vcard\_ldap}}
|
||||
\newcommand{\modvcardodbc}{\module{mod\_vcard\_odbc}}
|
||||
\newcommand{\modvcardxupdate}{\module{mod\_vcard\_xupdate}}
|
||||
\newcommand{\modversion}{\module{mod\_version}}
|
||||
|
||||
%% Contributed modules
|
||||
@@ -310,7 +308,7 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need:
|
||||
\item GNU Make
|
||||
\item GCC
|
||||
\item Libexpat 1.95 or higher
|
||||
\item Erlang/OTP R10B-9 or higher. The recommended versions are R12B-5 and R13B04.
|
||||
\item Erlang/OTP R10B-9 or higher. The recommended version is R12B-5. Support for R13 is experimental.
|
||||
\item OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL encryption. Optional, highly recommended.
|
||||
\item Zlib 1.2.3 or higher, for Stream Compression support (\xepref{0138}). Optional.
|
||||
\item Erlang mysql library. Optional. For MySQL authentication or storage. See section \ref{compilemysql}.
|
||||
@@ -327,12 +325,10 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need:
|
||||
Released versions of \ejabberd{} are available in the ProcessOne \ejabberd{} downloads page:
|
||||
\ahrefurl{http://www.process-one.net/en/ejabberd/downloads}
|
||||
|
||||
\ind{Git repository}
|
||||
Alternatively, the latest development source code can be retrieved from the Git repository using the commands:
|
||||
\ind{Subversion repository}
|
||||
Alternatively, the latest development version can be retrieved from the Subversion repository using this command:
|
||||
\begin{verbatim}
|
||||
git clone git://git.process-one.net/ejabberd/mainline.git ejabberd
|
||||
cd ejabberd
|
||||
git checkout -b 2.1.x origin/2.1.x
|
||||
svn co http://svn.process-one.net/ejabberd/trunk ejabberd
|
||||
\end{verbatim}
|
||||
|
||||
|
||||
@@ -384,6 +380,21 @@ Some options that you may be interested in modifying:
|
||||
Disable the use of Erlang/OTP supervision for transient processes.
|
||||
\end{description}
|
||||
|
||||
\makesubsection{snowleopard}{Compiling ejabberd under Snow Leopard with Erlang R13B}
|
||||
\ind{install!snowleopard}
|
||||
|
||||
Erl Interface, the library to link Erlang with C code, is compiled as
|
||||
32-bits code in Erlang R13B-2. Mac OS X Snow Leopard is a 64-bits
|
||||
system and will try compiling ejabberd C code in 64-bits as a default.
|
||||
|
||||
To compile ejabberd on Mac OS X Snow Leopard with Erlang R13B-2, you
|
||||
need to force C code to be compiled with 32-bits. This is done with
|
||||
the following configure command:
|
||||
|
||||
\begin{verbatim}
|
||||
CC='gcc -m32' CFLAGS=-m32 LDFLAGS=-m32 ./configure
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{install}{Install}
|
||||
\ind{install!install}
|
||||
|
||||
@@ -798,18 +809,18 @@ The available modules, their purpose and the options allowed by each one are:
|
||||
\begin{description}
|
||||
\titem{\texttt{ejabberd\_c2s}}
|
||||
Handles c2s connections.\\
|
||||
Options: \texttt{access}, \texttt{certfile}, \texttt{max\_fsm\_queue},
|
||||
Options: \texttt{access}, \texttt{certfile},
|
||||
\texttt{max\_stanza\_size}, \texttt{shaper},
|
||||
\texttt{starttls}, \texttt{starttls\_required}, \texttt{tls},
|
||||
\texttt{zlib}
|
||||
\titem{\texttt{ejabberd\_s2s\_in}}
|
||||
Handles incoming s2s connections.\\
|
||||
Options: \texttt{max\_stanza\_size}, \texttt{shaper}
|
||||
Options: \texttt{max\_stanza\_size}
|
||||
\titem{\texttt{ejabberd\_service}}
|
||||
Interacts with an \footahref{http://www.ejabberd.im/tutorials-transports}{external component}
|
||||
(as defined in the Jabber Component Protocol (\xepref{0114}).\\
|
||||
Options: \texttt{access}, \texttt{hosts}, \texttt{max\_fsm\_queue},
|
||||
\texttt{service\_check\_from}, \texttt{shaper}
|
||||
\texttt{shaper}, \texttt{service\_check\_from}
|
||||
\titem{\texttt{ejabberd\_stun}}
|
||||
Handles STUN Binding requests as defined in
|
||||
\footahref{http://tools.ietf.org/html/rfc5389}{RFC 5389}.\\
|
||||
@@ -833,10 +844,13 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
lots of new incoming connections as they may be dropped if there is
|
||||
no space in the queue (and ejabberd was not able to accept them
|
||||
immediately). Default value is 5.
|
||||
\titem{captcha} \ind{options!http-captcha}
|
||||
Simple web page that allows a user to fill a CAPTCHA challenge (see section \ref{captcha}).
|
||||
\titem{\{certfile, Path\}} Full path to a file containing the default SSL certificate.
|
||||
To define a certificate file specific for a given domain, use the global option \term{domain\_certfile}.
|
||||
\titem{\{service\_check\_from, true|false\}} \ind{options!service\_check\_from}
|
||||
This option can be used with \term{ejabberd\_service} only. It is
|
||||
used to disable control on the from field on packets send by an
|
||||
external components. The option can be either \term{true} or
|
||||
\term{false}. The default value is \term{true} which conforms to \xepref{0114}.
|
||||
\titem{\{hosts, [Hostname, ...], [HostOption, ...]\}} \ind{options!hosts}
|
||||
The external Jabber component that connects to this \term{ejabberd\_service}
|
||||
can serve one or more hostnames.
|
||||
@@ -846,6 +860,8 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
Note that you cannot define in a single \term{ejabberd\_service} components of
|
||||
different services: add an \term{ejabberd\_service} for each service,
|
||||
as seen in an example below.
|
||||
\titem{captcha} \ind{options!http-captcha}
|
||||
Simple web page that allows a user to fill a CAPTCHA challenge (see section \ref{captcha}).
|
||||
\titem{http\_bind} \ind{options!http\_bind}\ind{protocols!XEP-0206: HTTP Binding}\ind{JWChat}\ind{web-based XMPP client}
|
||||
This option enables HTTP Binding (\xepref{0124} and \xepref{0206}) support. HTTP Bind
|
||||
enables access via HTTP requests to \ejabberd{} from behind firewalls which
|
||||
@@ -878,27 +894,17 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
The option can be defined in \term{ejabberd.cfg}, expressing the time
|
||||
in seconds: \verb|{http_poll_timeout, 300}.|
|
||||
\titem{\{max\_fsm\_queue, Size\}}
|
||||
This option specifies the maximum number of elements in the queue of the FSM
|
||||
(Finite State Machine).
|
||||
Roughly speaking, each message in such queues represents one XML
|
||||
stanza queued to be sent into its relevant outgoing stream. If queue size
|
||||
reaches the limit (because, for example, the receiver of stanzas is too slow),
|
||||
the FSM and the corresponding connection (if any) will be terminated
|
||||
and error message will be logged.
|
||||
The reasonable value for this option depends on your hardware configuration.
|
||||
However, there is no much sense to set the size above 1000 elements.
|
||||
This option can be specified for \term{ejabberd\_service} and
|
||||
\term{ejabberd\_c2s} listeners,
|
||||
This option specifies the maximum number of elements in the queue of the FSM.
|
||||
This option can be specified for an \term{ejabberd\_service} listener,
|
||||
or also globally for \term{ejabberd\_s2s\_out}.
|
||||
If the option is not specified for \term{ejabberd\_service} or
|
||||
\term{ejabberd\_c2s} listeners,
|
||||
If the option is not specified for an \term{ejabberd\_service} listener,
|
||||
the globally configured value is used.
|
||||
The allowed values are integers and 'undefined'.
|
||||
Default value: 'undefined'.
|
||||
\titem{\{max\_stanza\_size, Size\}}
|
||||
\ind{options!max\_stanza\_size}This option specifies an
|
||||
approximate maximum size in bytes of XML stanzas. Approximate,
|
||||
because it is calculated with the precision of one block of read
|
||||
because it is calculated with the precision of one block of readed
|
||||
data. For example \verb|{max_stanza_size, 65536}|. The default
|
||||
value is \term{infinity}. Recommended values are 65536 for c2s
|
||||
connections and 131072 for s2s connections. s2s max stanza size
|
||||
@@ -910,13 +916,10 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
and you also want \term{mod\_http\_bind} to serve the URIs \term{/http-bind/},
|
||||
use this option: \term{\{request\_handlers, [\{["a", "b"], mod\_foo\}, \{["http-bind"], mod\_http\_bind\}]\}}
|
||||
\titem{\{service\_check\_from, true|false\}}
|
||||
\ind{options!service\_check\_from}
|
||||
This option can be used with \term{ejabberd\_service} only.
|
||||
\xepref{0114} requires that the domain must match the hostname of the component.
|
||||
If this option is set to \term{false}, \ejabberd{} will allow the component
|
||||
to send stanzas with any arbitrary domain in the 'from' attribute.
|
||||
Only use this option if you are completely sure about it.
|
||||
The default value is \term{true}, to be compliant with \xepref{0114}.
|
||||
By enabling this option, \ejabberd{} allows the component to send packets with any arbitrary domain in the 'from' attribute.
|
||||
Note that \xepref{0114} requires that the domain must match the hostname of the component.
|
||||
Only enable this option if you are completely sure you need to enable it.
|
||||
Default value: false.
|
||||
\titem{\{shaper, none|ShaperName\}} \ind{options!shaper}This option defines a
|
||||
shaper for the port (see section~\ref{shapers}). The default value
|
||||
is \term{none}.
|
||||
@@ -977,20 +980,10 @@ There are some additional global options that can be specified in the ejabberd c
|
||||
The maximum allowed delay for retry to connect after a failed connection attempt.
|
||||
Specified in seconds. The default value is 300 seconds (5 minutes).
|
||||
\titem{\{max\_fsm\_queue, Size\}}
|
||||
This option specifies the maximum number of elements in the queue of the FSM
|
||||
(Finite State Machine).
|
||||
Roughly speaking, each message in such queues represents one XML
|
||||
stanza queued to be sent into its relevant outgoing stream. If queue size
|
||||
reaches the limit (because, for example, the receiver of stanzas is too slow),
|
||||
the FSM and the corresponding connection (if any) will be terminated
|
||||
and error message will be logged.
|
||||
The reasonable value for this option depends on your hardware configuration.
|
||||
However, there is no much sense to set the size above 1000 elements.
|
||||
This option can be specified for \term{ejabberd\_service} and
|
||||
\term{ejabberd\_c2s} listeners,
|
||||
This option specifies the maximum number of elements in the queue of the FSM.
|
||||
This option can be specified for an \term{ejabberd\_service} listener,
|
||||
or also globally for \term{ejabberd\_s2s\_out}.
|
||||
If the option is not specified for \term{ejabberd\_service} or
|
||||
\term{ejabberd\_c2s} listeners,
|
||||
If the option is not specified for an \term{ejabberd\_service} listener,
|
||||
the globally configured value is used.
|
||||
The allowed values are integers and 'undefined'.
|
||||
Default value: 'undefined'.
|
||||
@@ -1187,7 +1180,8 @@ for user authentication. The syntax is:
|
||||
The following authentication methods are supported by \ejabberd{}:
|
||||
\begin{itemize}
|
||||
\item internal (default) --- See section~\ref{internalauth}.
|
||||
\item external --- See section~\ref{extauth}.
|
||||
\item external --- There are \footahref{http://www.ejabberd.im/extauth}{some
|
||||
example authentication scripts}.
|
||||
\item ldap --- See section~\ref{ldap}.
|
||||
\item odbc --- See section~\ref{mysql}, \ref{pgsql},
|
||||
\ref{mssql} and \ref{odbc}.
|
||||
@@ -1195,7 +1189,7 @@ The following authentication methods are supported by \ejabberd{}:
|
||||
\item pam --- See section~\ref{pam}.
|
||||
\end{itemize}
|
||||
|
||||
Account creation is only supported by internal, external and odbc methods.
|
||||
Account creation is only supported by internal and odbc methods.
|
||||
|
||||
\makesubsubsection{internalauth}{Internal}
|
||||
\ind{internal authentication}\ind{Mnesia}
|
||||
@@ -1217,42 +1211,6 @@ Examples:
|
||||
\end{verbatim}
|
||||
\end{itemize}
|
||||
|
||||
\makesubsubsection{extauth}{External Script}
|
||||
\ind{external authentication}
|
||||
|
||||
In this authentication method, when \ejabberd{} starts,
|
||||
it start a script, and calls it to perform authentication tasks.
|
||||
|
||||
The server administrator can write the external authentication script
|
||||
in any language.
|
||||
The details on the interface between ejabberd and the script are described
|
||||
in the \term{ejabberd Developers Guide}.
|
||||
There are also \footahref{http://www.ejabberd.im/extauth}{several example authentication scripts}.
|
||||
|
||||
These are the specific options:
|
||||
\begin{description}
|
||||
\titem{\{extauth\_program, PathToScript\}}
|
||||
Indicate in this option the full path to the external authentication script.
|
||||
The script must be executable by ejabberd.
|
||||
|
||||
\titem{\{extauth\_cache, false|CacheTimeInteger\}}
|
||||
The value \term{false} disables the caching feature, this is the default.
|
||||
The integer \term{0} (zero) enables caching for statistics, but doesn't use that cached information to authenticate users.
|
||||
If another integer value is set, caching is enabled both for statistics and for authentication:
|
||||
the CacheTimeInteger indicates the number of seconds that ejabberd can reuse
|
||||
the authentication information since the user last disconnected,
|
||||
to verify again the user authentication without querying again the extauth script.
|
||||
Note: caching should not be enabled in a host if internal auth is also enabled.
|
||||
If caching is enabled, \term{mod\_last} or \term{mod\_last\_odbc} must be enabled also in that vhost.
|
||||
\end{description}
|
||||
|
||||
This example sets external authentication, the extauth script, and enables caching for 10 minutes:
|
||||
\begin{verbatim}
|
||||
{auth_method, [external]}.
|
||||
{extauth_program, "/etc/ejabberd/JabberAuth.class.php"}.
|
||||
{extauth_cache, 600}.
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsubsection{saslanonymous}{SASL Anonymous and Anonymous Login}
|
||||
\ind{sasl anonymous}\ind{anonymous login}
|
||||
|
||||
@@ -1333,10 +1291,6 @@ Options:
|
||||
\titem{\{pam\_service, Name\}}\ind{options!pam\_service}This option defines the PAM service name.
|
||||
Default is \term{"ejabberd"}. Refer to the PAM documentation of your operation system
|
||||
for more information.
|
||||
\titem{\{pam\_userinfotype, username|jid\}}\ind{options!pam\_userinfotype}
|
||||
This option defines what type of information about the user ejabberd
|
||||
provides to the PAM service: only the username, or the user JID.
|
||||
Default is \term{username}.
|
||||
\end{description}
|
||||
|
||||
Example:
|
||||
@@ -1531,7 +1485,7 @@ This example limits the number of sessions per user to 5 for all users, and to 1
|
||||
\ind{options!max\_s2s\_connections}
|
||||
|
||||
The special access \term{max\_s2s\_connections} specifies how many
|
||||
simultaneous S2S connections can be established to a specific remote XMPP server.
|
||||
simultaneus S2S connections can be established to a specific remote XMPP server.
|
||||
The default value is \term{1}.
|
||||
There's also available the access \term{max\_s2s\_connections\_per\_node}.
|
||||
|
||||
@@ -1990,9 +1944,19 @@ enabled. This can be done, by using next commands:
|
||||
\makesubsubsection{configuremssql}{Database Connection}
|
||||
\ind{Microsoft SQL Server!Database Connection}
|
||||
|
||||
The configuration of Database Connection for a Microsoft SQL Server
|
||||
is the same as the configuration for
|
||||
ODBC compatible servers (see section~\ref{configureodbc}).
|
||||
By default \ejabberd{} opens 10 connections to the database for each virtual host.
|
||||
Use this option to modify the value:
|
||||
\begin{verbatim}
|
||||
{odbc_pool_size, 10}.
|
||||
\end{verbatim}
|
||||
|
||||
You can configure an interval to make a dummy SQL request
|
||||
to keep alive the connections to the database.
|
||||
The default value is 'undefined', so no keepalive requests are made.
|
||||
Specify in seconds: for example 28800 means 8 hours.
|
||||
\begin{verbatim}
|
||||
{odbc_keepalive_interval, undefined}.
|
||||
\end{verbatim}
|
||||
|
||||
|
||||
\makesubsubsection{mssqlauth}{Authentication}
|
||||
@@ -2000,8 +1964,7 @@ ODBC compatible servers (see section~\ref{configureodbc}).
|
||||
|
||||
%TODO: not sure if this section is right!!!!!!
|
||||
|
||||
The configuration of Authentication for a Microsoft SQL Server
|
||||
is the same as the configuration for
|
||||
The configuration of Microsoft SQL Server is the same as the configuration of
|
||||
ODBC compatible servers (see section~\ref{odbcauth}).
|
||||
|
||||
\makesubsubsection{mssqlstorage}{Storage}
|
||||
@@ -2198,12 +2161,9 @@ module loaded!
|
||||
server and use LDAP directory as vCard storage. Shared rosters are not supported
|
||||
yet.
|
||||
|
||||
Usually \ejabberd{} treats LDAP as a read-only storage:
|
||||
Note that \ejabberd{} treats LDAP as a read-only storage:
|
||||
it is possible to consult data, but not possible to
|
||||
create accounts or edit vCard that is stored in LDAP.
|
||||
However, it is possible to change passwords if \module{mod\_register} module is enabled
|
||||
and LDAP server supports
|
||||
\footahref{http://tools.ietf.org/html/rfc3062}{RFC 3062}.
|
||||
create accounts, change password or edit vCard that is stored in LDAP.
|
||||
|
||||
|
||||
\makesubsubsection{ldapconnection}{Connection}
|
||||
@@ -2217,11 +2177,6 @@ Allowed values are: \term{none}, \term{tls}.
|
||||
The value \term{tls} enables encryption by using LDAP over SSL.
|
||||
Note that STARTTLS encryption is not supported.
|
||||
The default value is: \term{none}.
|
||||
\titem{\{ldap\_tls\_verify, false|soft|hard\}} \ind{options!ldap\_tls\_verify}
|
||||
This option specifies whether to verify LDAP server certificate or not when TLS is enabled.
|
||||
When \term{hard} is enabled \ejabberd{} doesn't proceed if a certificate is invalid.
|
||||
When \term{soft} is enabled \ejabberd{} proceeds even if check fails.
|
||||
The default is \term{false} which means no checks are performed.
|
||||
\titem{\{ldap\_port, Number\}} \ind{options!ldap\_port}Port to connect to your LDAP server.
|
||||
The default port is~389 if encryption is disabled; and 636 if encryption is enabled.
|
||||
If you configure a value, it is stored in \ejabberd{}'s database.
|
||||
@@ -2276,22 +2231,7 @@ You can authenticate users against an LDAP directory. Available options are:
|
||||
not forget to close brackets and do not use superfluous whitespaces. Also you
|
||||
\emph{must not} use \option{ldap\_uidattr} attribute in filter because this
|
||||
attribute will be substituted in LDAP filter automatically.
|
||||
\titem{\{ldap\_dn\_filter, \{ Filter, FilterAttrs \}\}}\ind{options!ldap\_dn\_filter}
|
||||
This filter is applied on the results returned by the main filter. This filter
|
||||
performs additional LDAP lookup to make the complete result. This is useful
|
||||
when you are unable to define all filter rules in \term{ldap\_filter}. You
|
||||
can define \term{"\%u"}, \term{"\%d"}, \term{"\%s"} and \term{"\%D"} pattern
|
||||
variables in Filter: \term{"\%u"} is replaced by a user's part of a JID,
|
||||
\term{"\%d"} is replaced by the corresponding domain (virtual host),
|
||||
all \term{"\%s"} variables are consecutively replaced by values of FilterAttrs
|
||||
attributes and \term{"\%D"} is replaced by Distinguished Name. By default
|
||||
\term{ldap\_dn\_filter} is undefined.
|
||||
Example:
|
||||
\begin{verbatim}
|
||||
{ldap_dn_filter, {"(&(name=%s)(owner=%D)(user=%u@%d))", ["sn"]}}.
|
||||
\end{verbatim}
|
||||
Since this filter makes additional LDAP lookups, use it only in the
|
||||
last resort: try to define all filter rules in \term{ldap\_filter} if possible.
|
||||
|
||||
\titem{\{ldap\_local\_filter, Filter\}}\ind{options!ldap\_local\_filter}
|
||||
If you can't use \term{ldap\_filter} due to performance reasons
|
||||
(the LDAP server has many users registered),
|
||||
@@ -2518,13 +2458,11 @@ The following table lists all modules included in \ejabberd{}.
|
||||
\hline \ahrefloc{modservicelog}{\modservicelog{}} & Copy user messages to logger service & \\
|
||||
\hline \ahrefloc{modsharedroster}{\modsharedroster{}} & Shared roster management & \modroster{} or \\
|
||||
& & \modrosterodbc\\
|
||||
\hline \ahrefloc{modsic}{\modsic{}} & Server IP Check (\xepref{0279}) & \\
|
||||
\hline \ahrefloc{modstats}{\modstats{}} & Statistics Gathering (\xepref{0039}) & \\
|
||||
\hline \ahrefloc{modtime}{\modtime{}} & Entity Time (\xepref{0202}) & \\
|
||||
\hline \ahrefloc{modvcard}{\modvcard{}} & vcard-temp (\xepref{0054}) & \\
|
||||
\hline \ahrefloc{modvcardldap}{\modvcardldap{}} & vcard-temp (\xepref{0054}) & LDAP server \\
|
||||
\hline \ahrefloc{modvcard}{\modvcardodbc{}} & vcard-temp (\xepref{0054}) & supported DB (*) \\
|
||||
\hline \ahrefloc{modvcardxupdate}{\modvcardxupdate{}} & vCard-Based Avatars (\xepref{0153}) & \modvcard{} or \modvcardodbc{} \\
|
||||
\hline \ahrefloc{modversion}{\modversion{}} & Software Version (\xepref{0092}) & \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
@@ -3586,12 +3524,6 @@ to listen for. Default is an IP address of the service's DNS name, or,
|
||||
if fails, \verb|{127,0,0,1}|.
|
||||
\titem{\{port, Number\}}\ind{options!port}This option defines port to listen for
|
||||
incoming connections. Default is~7777.
|
||||
\titem{\{hostname, HostName\}}\ind{options!hostname}Defines a hostname advertised
|
||||
by the service when establishing a session with clients. This is useful when
|
||||
you run the service behind a NAT. The default is the value of \term{ip} option.
|
||||
Examples: \term{"proxy.mydomain.org"}, \term{"200.150.100.50"}. Note that
|
||||
not all clients understand domain names in stream negotiation,
|
||||
so you should think twice before setting domain name in this option.
|
||||
\titem{\{auth\_type, anonymous|plain\}}\ind{options!auth\_type}SOCKS5 authentication type.
|
||||
Possible values are \term{anonymous} and \term{plain}. Default is
|
||||
\term{anonymous}.
|
||||
@@ -3749,10 +3681,6 @@ Options:
|
||||
rules to restrict registration. If a rule returns `deny' on the requested
|
||||
user name, registration for that user name is denied. (there are no
|
||||
restrictions by default).
|
||||
\titem{\{access\_from, AccessName\}} \ind{options!access\_from}By default, \ejabberd{}
|
||||
doesn't allow to register new accounts from s2s or existing c2s sessions. You can
|
||||
change it by defining access rule in this option. Use with care: allowing registration
|
||||
from s2s leads to uncontrolled massive accounts creation by rogue users.
|
||||
\titem{\{welcome\_message, Message\}} \ind{options!welcomem}Set a welcome message that
|
||||
is sent to each newly registered account. The first string is the subject, and
|
||||
the second string is the message body.
|
||||
@@ -4014,17 +3942,6 @@ Examples:
|
||||
\end{table}
|
||||
\end{itemize}
|
||||
|
||||
\makesubsection{modsic}{\modsic{}}
|
||||
\ind{modules!\modstats{}}\ind{protocols!XEP-0279: Server IP Check}
|
||||
|
||||
This module adds support for Server IP Check (\xepref{0279}). This protocol
|
||||
enables a client to discover its external IP address.
|
||||
|
||||
Options:
|
||||
\begin{description}
|
||||
\iqdiscitem{\ns{urn:xmpp:sic:0}}
|
||||
\end{description}
|
||||
|
||||
\makesubsection{modstats}{\modstats{}}
|
||||
\ind{modules!\modstats{}}\ind{protocols!XEP-0039: Statistics Gathering}\ind{statistics}
|
||||
|
||||
@@ -4147,12 +4064,9 @@ Examples:
|
||||
implemented in the \modvcardldap{} module. This module does not depend on the
|
||||
authentication method (see~\ref{ldapauth}).
|
||||
|
||||
Usually \ejabberd{} treats LDAP as a read-only storage:
|
||||
Note that \ejabberd{} treats LDAP as a read-only storage:
|
||||
it is possible to consult data, but not possible to
|
||||
create accounts or edit vCard that is stored in LDAP.
|
||||
However, it is possible to change passwords if \module{mod\_register} module is enabled
|
||||
and LDAP server supports
|
||||
\footahref{http://tools.ietf.org/html/rfc3062}{RFC 3062}.
|
||||
create accounts, change password or edit vCard that is stored in LDAP.
|
||||
|
||||
The \modvcardldap{} module has
|
||||
its own optional parameters. The first group of parameters has the same
|
||||
@@ -4356,27 +4270,6 @@ searching his info in LDAP.
|
||||
\end{verbatim}
|
||||
\end{itemize}
|
||||
|
||||
\makesubsection{modvcardxupdate}{\modvcardxupdate{}}
|
||||
\ind{modules!\modvcardxupdate{}}\ind{protocols!XEP-0153: vCard-Based Avatars}
|
||||
|
||||
The user's client can store an avatar in the user vCard.
|
||||
The vCard-Based Avatars protocol (\xepref{0153})
|
||||
provides a method for clients to inform the contacts what is the avatar hash value.
|
||||
However, simple or small clients may not implement that protocol.
|
||||
|
||||
If this module is enabled, all the outgoing client presence stanzas get automatically
|
||||
the avatar hash on behalf of the client.
|
||||
So, the contacts receive the presence stanzas with the Update Data described
|
||||
in \xepref{0153} as if the client would had inserted it itself.
|
||||
If the client had already included such element in the presence stanza,
|
||||
it is replaced with the element generated by ejabberd.
|
||||
|
||||
By enabling this module, each vCard modification produces a hash recalculation,
|
||||
and each presence sent by a client produces hash retrieval and a
|
||||
presence stanza rewrite.
|
||||
For this reason, enabling this module will introduce a computational overhead
|
||||
in servers with clients that change frequently their presence.
|
||||
|
||||
\makesubsection{modversion}{\modversion{}}
|
||||
\ind{modules!\modversion{}}\ind{protocols!XEP-0092: Software Version}
|
||||
|
||||
@@ -4511,7 +4404,7 @@ The command line parameters:
|
||||
\titem{-name ejabberd}
|
||||
The Erlang node will be fully identified.
|
||||
This is only useful if you plan to setup an \ejabberd{} cluster with nodes in different networks.
|
||||
\titem{-kernel inetrc '"/etc/ejabberd/inetrc"'}
|
||||
\titem{-kernel inetrc "/etc/ejabberd/inetrc"}
|
||||
Indicates which IP name resolution to use.
|
||||
If using \term{-sname}, specify either this option or \term{ERL\_INETRC}.
|
||||
\titem{-kernel inet\_dist\_listen\_min 4200 inet\_dist\_listen\_min 4210}
|
||||
@@ -4526,7 +4419,7 @@ The command line parameters:
|
||||
Specify the directory where Erlang binary files (*.beam) are located.
|
||||
\titem{-s ejabberd}
|
||||
Tell Erlang runtime system to start the \ejabberd{} application.
|
||||
\titem{-mnesia dir '"/var/lib/ejabberd/"'}
|
||||
\titem{-mnesia dir "/var/lib/ejabberd/"}
|
||||
Specify the Mnesia database directory.
|
||||
\titem{-sasl sasl\_error\_logger \{file, "/var/log/ejabberd/erlang.log"\}}
|
||||
Path to the Erlang/OTP system log file.
|
||||
@@ -4708,10 +4601,7 @@ Here you can edit access restrictions, manage users, create backups,
|
||||
manage the database, enable/disable ports listened for, view server
|
||||
statistics,\ldots
|
||||
|
||||
The access rule \term{configure} determines what accounts can access the Web Admin and modify it.
|
||||
The access rule \term{webadmin\_view} is to grant only view access: those accounts can browse the Web Admin with read-only access.
|
||||
|
||||
Example configurations:
|
||||
Examples:
|
||||
\begin{itemize}
|
||||
\item You can serve the Web Admin on the same port as the
|
||||
\ind{protocols!XEP-0025: HTTP Polling}HTTP Polling interface. In this example
|
||||
@@ -4725,14 +4615,10 @@ Example configurations:
|
||||
URL). If you log in with `\jid{admin@example.com}' on \\
|
||||
\verb|http://example.org:5280/admin/server/example.com/| you can only
|
||||
administer the virtual host \jid{example.com}.
|
||||
The account `\jid{reviewer@example.com}' can browse that vhost in read-only mode.
|
||||
\begin{verbatim}
|
||||
{acl, admins, {user, "admin", "example.net"}}.
|
||||
{host_config, "example.com", [{acl, admins, {user, "admin", "example.com"}}]}.
|
||||
{host_config, "example.com", [{acl, viewers, {user, "reviewer", "example.com"}}]}.
|
||||
|
||||
{access, configure, [{allow, admins}]}.
|
||||
{access, webadmin_view, [{allow, viewers}]}.
|
||||
|
||||
{hosts, ["example.org"]}.
|
||||
|
||||
@@ -5068,7 +4954,7 @@ following steps:
|
||||
|
||||
\begin{verbatim}
|
||||
erl -sname ejabberd \
|
||||
-mnesia dir '"/var/lib/ejabberd/"' \
|
||||
-mnesia dir "/var/lib/ejabberd/" \
|
||||
-mnesia extra_db_nodes "['ejabberd@first']" \
|
||||
-s mnesia
|
||||
\end{verbatim}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.3
|
||||
|
||||
ejabberd 2.1.3 is the third release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.3 includes many bugfixes, and some improvements.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.3
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Client connections
|
||||
- Avoid 'invalid' value in iq record
|
||||
- Avoid resending stream:error stanzas on terminate (EJAB-1180)
|
||||
- Close also legacy sessions that were half connected (EJAB-1165)
|
||||
- iq_query_info/1 now returns 'invalid' if XMLNS is invalid
|
||||
- New ejabberd_c2s option support: max_fsm_queue
|
||||
- Rewrite mnesia counter functions to use dirty_update_counter (EJAB-1177)
|
||||
- Run user_receive_packet also when sending offline messages (EJAB-1193)
|
||||
- Use p1_fsm behaviour in c2s FSM (EJAB-1173)
|
||||
|
||||
* Clustering
|
||||
- Fix cluster race condition in route read
|
||||
- New command to set master Mnesia node
|
||||
- Use mnesia:async_dirty when cleaning table from failed node
|
||||
|
||||
* Documentation
|
||||
- Add quotes in documentation of some erl arguments (EJAB-1191)
|
||||
- Add option access_from (EJAB-1187)
|
||||
- Add option max_fsm_queue (EJAB-1185)
|
||||
- Fix documentation installation, no need for executable permission (EJAB-1170)
|
||||
- Fix typo in EJABBERD_BIN_PATH (EJAB-891)
|
||||
- Fix typos in example config comments (EJAB-1192)
|
||||
|
||||
* ejabberdctl
|
||||
- Support concurrent connections with bound connection names
|
||||
- Add support for Jot in ctl and TTY in debug
|
||||
- Support help command names with old - characters
|
||||
- Fix to really use the variable ERL_PROCESSES
|
||||
|
||||
* Erlang compatibility
|
||||
- Don't call queue:filter/2 to keep compatibility with older Erlang versions
|
||||
- Use alternative of file:read_line/1 to not require R13B02
|
||||
|
||||
* HTTP
|
||||
- Add new debugging hook to the http receiving process
|
||||
- Allow a request_handler to serve a file in root of HTTP
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Cross-domain HTTP-Bind support (EJAB-1168)
|
||||
- Hibernate http-bind process after handling a request
|
||||
- Reduce verbosity of HTTP Binding log messages
|
||||
|
||||
* LDAP
|
||||
- Document ldap_dn_filter, fetch only needed attributes in search (EJAB-1204)
|
||||
- Use "%u" pattern as default for ldap_uids (EJAB-1203)
|
||||
|
||||
* Localization
|
||||
- Fix German translation (EJAB-1195)
|
||||
- Fix Russian translation
|
||||
|
||||
* ODBC
|
||||
- Fix MSSQL support, which was broken (EJAB-1201)
|
||||
- Improved SQL reconnect behaviour
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Add extended stanza addressing 'replyto' on PEP (EJAB-1198)
|
||||
- Add pubsub#purge_offline (EJAB-1186)
|
||||
- Fix pubsub#title option (EJAB-1190)
|
||||
- Fix remove_user for node subscriptions (EJAB-1172)
|
||||
- Optimizations in mod_caps
|
||||
|
||||
* Other
|
||||
- mod_register: Add new acl access_from, default is to deny
|
||||
- mod_sic: new module for the experimental XEP-0279 Server IP Check (EJAB-1205)
|
||||
- PIEFXIS: Catch errors when exporting to PIEFXIS file (EJAB-1178)
|
||||
- Proxy65: new option "hostname" (EJAB-838)
|
||||
- Roster: Fix resending authorization problem
|
||||
- Shared Roster Groups: get contacts nickname from vcard (EJAB-114)
|
||||
- S2S: Improved s2s connections clean up (EJAB-1202)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
@@ -1,80 +0,0 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.4
|
||||
|
||||
ejabberd 2.1.4 is the fourth release in ejabberd 2.1.x branch,
|
||||
and includes many small bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.4
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
This is the full list of changes:
|
||||
|
||||
* Authentication
|
||||
- Extauth: Optionally cache extauth users in mnesia (EJAB-641)
|
||||
- LDAP: Allow inband password change (EJAB-199)
|
||||
- LDAP: Extensible match support (EJAB-722)
|
||||
- LDAP: New option ldap_tls_verify is added (EJAB-1229)
|
||||
- PAM: New option pam_userinfotype to provide username or JID (EJAB-652)
|
||||
|
||||
* HTTP
|
||||
- Add xml default content type
|
||||
- Don't show HTTP request in logs, because reveals password (EJAB-1231)
|
||||
- Move HTTP session timeout log from warning level to info
|
||||
- New Access rule webadmin_view for read-only
|
||||
|
||||
* HTTP-Bind (BOSH)
|
||||
- Change max inactivity from 30 to 120 seconds
|
||||
- Export functions to facilitate prebinding methods
|
||||
- Use dirty_delete when removing the session
|
||||
- Remove an unneeded delay of 100 milliseconds
|
||||
|
||||
* Pubsub, PEP and Caps
|
||||
- Enforce pubsub#presence_based_delivery (EJAB-1221)
|
||||
- Enforce pubsub#show_values subscription option (EJAB-1096)
|
||||
- Fix error code when unsubscribing from a non-existent node
|
||||
- Fix to send node notifications (EJAB-1225)
|
||||
- Full support for XEP-0115 v1.5 (EJAB-1223)(EJAB-1189)
|
||||
- Make last_item_cache feature to be cluster aware
|
||||
- Prevent orphaned pubsub node (EJAB-1233)
|
||||
- Send created node notifications
|
||||
|
||||
* Other
|
||||
- Bounce messages when closing c2s session
|
||||
- Bugfixes when handling Service Discovery to contacts (EJAB-1207)
|
||||
- Compilation of ejabberd_debug.erl is now optional
|
||||
- Don't send error stanza as reply to error stanza (EJAB-930)
|
||||
- Don't store blocked messages in offline queue
|
||||
- Reduce verbosity of log when captcha_cmd is checked but not configured
|
||||
- Use a standard method to get a random seed (EJAB-1229)
|
||||
- Commands: new update_list and update to update modified modules (EJAB-1237)
|
||||
- Localization: Updated most translations
|
||||
- MUC: Refactor code to reduce calls to get_affiliation and get_role
|
||||
- ODBC: Add created_at column also to PostgreSQL schema
|
||||
- Vcard: Automatic vcard avatar addition in presence
|
||||
|
||||
|
||||
Upgrading From previous ejabberd releases:
|
||||
|
||||
- If you use PostgreSQL, maybe you want to add the column created_at
|
||||
to several tables. This is only a suggestion; ejabberd doesn't use
|
||||
that column. Add it to your existing database executing those SQL
|
||||
statements:
|
||||
|
||||
ALTER TABLE users ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE rosterusers ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE spool ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE vcard ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE privacy_list ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
ALTER TABLE privacy_storage ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT now();
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.1.4}
|
||||
\newcommand{\version}{2.1.2}
|
||||
|
||||
Executable → Regular
+13
-16
@@ -37,10 +37,8 @@ ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
endif
|
||||
|
||||
DEBUGTOOLS = ejabberd_debug.erl
|
||||
ifdef ejabberd_debug
|
||||
EFLAGS+=-Dejabberd_debug
|
||||
SOURCES+=$(DEBUGTOOLS)
|
||||
endif
|
||||
|
||||
ifeq (@hipe@, true)
|
||||
@@ -71,8 +69,7 @@ SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ str
|
||||
ERLSHLIBS = expat_erl.so
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
|
||||
SOURCES_ALL = $(wildcard *.erl)
|
||||
SOURCES_MISC = $(ERLBEHAVS) $(DEBUGTOOLS)
|
||||
SOURCES += $(filter-out $(SOURCES_MISC),$(SOURCES_ALL))
|
||||
SOURCES = $(filter-out $(ERLBEHAVS),$(SOURCES_ALL))
|
||||
ERLBEHAVBEAMS = $(ERLBEHAVS:.erl=.beam)
|
||||
BEAMS = $(SOURCES:.erl=.beam)
|
||||
|
||||
@@ -113,9 +110,6 @@ MSGSDIR = $(PRIVDIR)/msgs
|
||||
# /var/lib/ejabberd/
|
||||
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
|
||||
|
||||
# /var/lock/ejabberdctl
|
||||
CTLLOCKDIR = $(DESTDIR)@localstatedir@/lock/ejabberdctl
|
||||
|
||||
# /var/lib/ejabberd/.erlang.cookie
|
||||
COOKIEFILE = $(SPOOLDIR)/.erlang.cookie
|
||||
|
||||
@@ -238,11 +232,6 @@ install: all
|
||||
chmod -R 750 $(SPOOLDIR)
|
||||
[ ! -f $(COOKIEFILE) ] || { $(CHOWN_COMMAND) @INSTALLUSER@ $(COOKIEFILE) >$(CHOWN_OUTPUT) ; chmod 400 $(COOKIEFILE) ; }
|
||||
#
|
||||
# ejabberdctl lock directory
|
||||
install -d -m 750 $(O_USER) $(CTLLOCKDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(CTLLOCKDIR) >$(CHOWN_OUTPUT)
|
||||
chmod -R 750 $(CTLLOCKDIR)
|
||||
#
|
||||
# Log directory
|
||||
install -d -m 750 $(O_USER) $(LOGDIR)
|
||||
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(LOGDIR) >$(CHOWN_OUTPUT)
|
||||
@@ -250,9 +239,9 @@ install: all
|
||||
#
|
||||
# Documentation
|
||||
install -d $(DOCDIR)
|
||||
install -m 644 ../doc/guide.html $(DOCDIR)
|
||||
install -m 644 ../doc/*.png $(DOCDIR)
|
||||
install -m 644 ../doc/*.txt $(DOCDIR)
|
||||
install ../doc/guide.html $(DOCDIR)
|
||||
install ../doc/*.png $(DOCDIR)
|
||||
install ../doc/*.txt $(DOCDIR)
|
||||
|
||||
uninstall: uninstall-binary
|
||||
|
||||
@@ -276,7 +265,6 @@ uninstall-all: uninstall-binary
|
||||
rm -rf $(ETCDIR)
|
||||
rm -rf $(EJABBERDDIR)
|
||||
rm -rf $(SPOOLDIR)
|
||||
rm -rf $(CTLLOCKDIR)
|
||||
rm -rf $(LOGDIR)
|
||||
|
||||
clean: clean-recursive clean-local
|
||||
@@ -298,3 +286,12 @@ Makefile: Makefile.in
|
||||
|
||||
dialyzer: $(BEAMS)
|
||||
@dialyzer -c .
|
||||
|
||||
LASTSVNREVCHANGELOG = 2075
|
||||
changelog:
|
||||
svn up -r $(LASTSVNREVCHANGELOG) ../ChangeLog
|
||||
mv ../ChangeLog ../ChangeLog.old
|
||||
svn2cl -r BASE:$(LASTSVNREVCHANGELOG) -o ../ChangeLog --group-by-day \
|
||||
--separate-daylogs --break-before-msg --reparagraph ..
|
||||
cat ../ChangeLog.old >> ../ChangeLog
|
||||
rm ../ChangeLog.old
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : adhoc.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (XEP-0050)
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (JEP-0050)
|
||||
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.1.4"},
|
||||
{vsn, "2.1.2"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
|
||||
+34
-40
@@ -5,7 +5,7 @@
|
||||
|
||||
%%% The parameters used in this configuration file are explained in more detail
|
||||
%%% in the ejabberd Installation and Operation Guide.
|
||||
%%% Please consult the Guide in case of doubts, it is included with
|
||||
%%% Please consult the Guide in case of doubts, it is included in
|
||||
%%% your copy of ejabberd, and is also available online at
|
||||
%%% http://www.process-one.net/en/ejabberd/docs/
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
%%% [http_poll, web_admin, tls]
|
||||
%%%
|
||||
%%% - A keyword of ejabberd is a word in lowercase.
|
||||
%%% Strings are enclosed in "" and can contain spaces, dots, ...
|
||||
%%% The strings are enclosed in "" and can have spaces, dots...
|
||||
%%% {language, "en"}.
|
||||
%%% {ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%%
|
||||
%%% - This term includes a tuple, a keyword, a list, and two strings:
|
||||
%%% - This term includes a tuple, a keyword, a list and two strings:
|
||||
%%% {hosts, ["jabber.example.net", "im.example.com"]}.
|
||||
%%%
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
%%
|
||||
%% watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% consumes a lot of memory, send live notifications to these XMPP
|
||||
%% accounts.
|
||||
%%
|
||||
%%{watchdog_admins, ["bob@example.com"]}.
|
||||
@@ -91,9 +91,9 @@
|
||||
{hosts, ["localhost"]}.
|
||||
|
||||
%%
|
||||
%% route_subdomains: Delegate subdomains to other XMPP servers.
|
||||
%% route_subdomains: Delegate subdomains to other XMPP server.
|
||||
%% For example, if this ejabberd serves example.org and you want
|
||||
%% to allow communication with an XMPP server called im.example.org.
|
||||
%% to allow communication with a XMPP server called im.example.org.
|
||||
%%
|
||||
%%{route_subdomains, s2s}.
|
||||
|
||||
@@ -102,8 +102,8 @@
|
||||
%%%' LISTENING PORTS
|
||||
|
||||
%%
|
||||
%% listen: The ports ejabberd will listen on, which service each is handled
|
||||
%% by and what options to start it with.
|
||||
%% listen: Which ports will ejabberd listen, which service handles it
|
||||
%% and what options to start it with.
|
||||
%%
|
||||
{listen,
|
||||
[
|
||||
@@ -111,8 +111,8 @@
|
||||
{5222, ejabberd_c2s, [
|
||||
|
||||
%%
|
||||
%% If TLS is compiled in and you installed a SSL
|
||||
%% certificate, specify the full path to the
|
||||
%% If TLS is compiled and you installed a SSL
|
||||
%% certificate, put the correct path to the
|
||||
%% file and uncomment this line:
|
||||
%%
|
||||
%%{certfile, "/path/to/ssl.pem"}, starttls,
|
||||
@@ -123,7 +123,7 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% To enable the old SSL connection method on port 5223:
|
||||
%% To enable the old SSL connection method in port 5223:
|
||||
%%
|
||||
%%{5223, ejabberd_c2s, [
|
||||
%% {access, c2s},
|
||||
@@ -138,7 +138,7 @@
|
||||
]},
|
||||
|
||||
%%
|
||||
%% ejabberd_service: Interact with external components (transports, ...)
|
||||
%% ejabberd_service: Interact with external components (transports...)
|
||||
%%
|
||||
%%{8888, ejabberd_service, [
|
||||
%% {access, all},
|
||||
@@ -249,14 +249,14 @@
|
||||
%%{ldap_encrypt, none}.
|
||||
%%{ldap_encrypt, tls}.
|
||||
%%
|
||||
%% Port to connect to on LDAP servers:
|
||||
%% Port connect to LDAP servers:
|
||||
%%{ldap_port, 389}.
|
||||
%%{ldap_port, 636}.
|
||||
%%
|
||||
%% LDAP manager:
|
||||
%%{ldap_rootdn, "dc=example,dc=com"}.
|
||||
%%
|
||||
%% Password of LDAP manager:
|
||||
%% Password to LDAP manager:
|
||||
%%{ldap_password, "******"}.
|
||||
%%
|
||||
%% Search base of LDAP directory:
|
||||
@@ -286,11 +286,11 @@
|
||||
%%%. ==============
|
||||
%%%' DATABASE SETUP
|
||||
|
||||
%% ejabberd by default uses the internal Mnesia database,
|
||||
%% so you do not necessarily need this section.
|
||||
%% ejabberd uses by default the internal Mnesia database,
|
||||
%% so you can avoid this section.
|
||||
%% This section provides configuration examples in case
|
||||
%% you want to use other database backends.
|
||||
%% Please consult the ejabberd Guide for details on database creation.
|
||||
%% Please consult the ejabberd Guide for details about database creation.
|
||||
|
||||
%%
|
||||
%% MySQL server:
|
||||
@@ -324,8 +324,8 @@
|
||||
%%{odbc_pool_size, 10}.
|
||||
|
||||
%%
|
||||
%% Interval to make a dummy SQL request to keep the connections to the
|
||||
%% database alive. Specify in seconds: for example 28800 means 8 hours
|
||||
%% Interval to make a dummy SQL request to keep alive the connections
|
||||
%% to the database. Specify in seconds: for example 28800 means 8 hours
|
||||
%%
|
||||
%%{odbc_keepalive_interval, undefined}.
|
||||
|
||||
@@ -334,28 +334,22 @@
|
||||
%%%' TRAFFIC SHAPERS
|
||||
|
||||
%%
|
||||
%% The "normal" shaper limits traffic speed to 1000 B/s
|
||||
%% The "normal" shaper limits traffic speed to 1.000 B/s
|
||||
%%
|
||||
{shaper, normal, {maxrate, 1000}}.
|
||||
|
||||
%%
|
||||
%% The "fast" shaper limits traffic speed to 50000 B/s
|
||||
%% The "fast" shaper limits traffic speed to 50.000 B/s
|
||||
%%
|
||||
{shaper, fast, {maxrate, 50000}}.
|
||||
|
||||
%%
|
||||
%% This option specifies the maximum number of elements in the queue
|
||||
%% of the FSM. Refer to the documentation for details.
|
||||
%%
|
||||
{max_fsm_queue, 1000}.
|
||||
|
||||
|
||||
%%%. ====================
|
||||
%%%' ACCESS CONTROL LISTS
|
||||
|
||||
%%
|
||||
%% The 'admin' ACL grants administrative privileges to XMPP accounts.
|
||||
%% You can put here as many accounts as you want.
|
||||
%% You can put as many accounts as you want.
|
||||
%%
|
||||
%%{acl, admin, {user, "aleksey", "localhost"}}.
|
||||
%%{acl, admin, {user, "ermine", "example.org"}}.
|
||||
@@ -396,7 +390,7 @@
|
||||
{access, max_user_sessions, [{10, all}]}.
|
||||
|
||||
%% Maximum number of offline messages that users can have:
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
|
||||
|
||||
%% This rule allows access only for local users:
|
||||
{access, local, [{allow, local}]}.
|
||||
@@ -405,41 +399,41 @@
|
||||
{access, c2s, [{deny, blocked},
|
||||
{allow, all}]}.
|
||||
|
||||
%% For C2S connections, all users except admins use the "normal" shaper
|
||||
%% For C2S connections, all users except admins use "normal" shaper
|
||||
{access, c2s_shaper, [{none, admin},
|
||||
{normal, all}]}.
|
||||
|
||||
%% All S2S connections use the "fast" shaper
|
||||
%% All S2S connections use "fast" shaper
|
||||
{access, s2s_shaper, [{fast, all}]}.
|
||||
|
||||
%% Only admins can send announcement messages:
|
||||
{access, announce, [{allow, admin}]}.
|
||||
|
||||
%% Only admins can use the configuration interface:
|
||||
%% Only admins can use configuration interface:
|
||||
{access, configure, [{allow, admin}]}.
|
||||
|
||||
%% Admins of this server are also admins of the MUC service:
|
||||
%% Admins of this server are also admins of MUC service:
|
||||
{access, muc_admin, [{allow, admin}]}.
|
||||
|
||||
%% Only accounts of the local ejabberd server can create rooms:
|
||||
{access, muc_create, [{allow, local}]}.
|
||||
|
||||
%% All users are allowed to use the MUC service:
|
||||
%% All users are allowed to use MUC service:
|
||||
{access, muc, [{allow, all}]}.
|
||||
|
||||
%% Only accounts on the local ejabberd server can create Pubsub nodes:
|
||||
%% Only accounts in the local ejabberd server can create Pubsub nodes:
|
||||
{access, pubsub_createnode, [{allow, local}]}.
|
||||
|
||||
%% In-band registration allows registration of any possible username.
|
||||
%% To disable in-band registration, replace 'allow' with 'deny'.
|
||||
{access, register, [{allow, all}]}.
|
||||
|
||||
%% By default the frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable, specify: infinity
|
||||
%% By default frequency of account registrations from the same IP
|
||||
%% is limited to 1 account every 10 minutes. To disable put: infinity
|
||||
%%{registration_timeout, 600}.
|
||||
|
||||
%%
|
||||
%% Define specific Access Rules in a virtual host.
|
||||
%% Define specific Access rules in a virtual host.
|
||||
%%
|
||||
%%{host_config, "localhost",
|
||||
%% [
|
||||
@@ -496,7 +490,7 @@
|
||||
{mod_irc, []},
|
||||
{mod_http_bind, []},
|
||||
%%{mod_http_fileserver, [
|
||||
%% {docroot, "/var/www"},
|
||||
%% {docroot, "/var/www"},
|
||||
%% {accesslog, "/var/log/ejabberd/access.log"}
|
||||
%% ]},
|
||||
{mod_last, []},
|
||||
@@ -515,7 +509,7 @@
|
||||
%%{mod_proxy65,[]},
|
||||
{mod_pubsub, [
|
||||
{access_createnode, pubsub_createnode},
|
||||
{ignore_pep_from_offline, true}, % reduces resource comsumption, but XEP incompliant
|
||||
{ignore_pep_from_offline, true}, % reduce resource comsumption, but XEP incompliant
|
||||
%%{ignore_pep_from_offline, false}, % XEP compliant, but increases resource comsumption
|
||||
{last_item_cache, false},
|
||||
{plugins, ["flat", "hometree", "pep"]} % pep requires mod_caps
|
||||
|
||||
+2
-54
@@ -31,8 +31,6 @@
|
||||
%% Server
|
||||
status/0, reopen_log/0,
|
||||
stop_kindly/2, send_service_message_all_mucs/2,
|
||||
%% Erlang
|
||||
update_list/0, update/1,
|
||||
%% Accounts
|
||||
register/3, unregister/2,
|
||||
registered_users/1,
|
||||
@@ -41,7 +39,6 @@
|
||||
%% Purge DB
|
||||
delete_expired_messages/0, delete_old_messages/1,
|
||||
%% Mnesia
|
||||
set_master/1,
|
||||
backup_mnesia/1, restore_mnesia/1,
|
||||
dump_mnesia/1, dump_table/2, load_mnesia/1,
|
||||
install_fallback_mnesia/1,
|
||||
@@ -97,17 +94,6 @@ commands() ->
|
||||
{leveldesc, string}
|
||||
]}}},
|
||||
|
||||
#ejabberd_commands{name = update_list, tags = [server],
|
||||
desc = "List modified modules that can be updated",
|
||||
module = ?MODULE, function = update_list_modified,
|
||||
args = [],
|
||||
result = {modules, {list, {module, string}}}},
|
||||
#ejabberd_commands{name = update, tags = [server],
|
||||
desc = "Update the given module, or use the keyword: all",
|
||||
module = ?MODULE, function = update,
|
||||
args = [{module, string}],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = register, tags = [accounts],
|
||||
desc = "Register a user",
|
||||
module = ?MODULE, function = register,
|
||||
@@ -157,16 +143,10 @@ commands() ->
|
||||
args = [{days, integer}], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
|
||||
desc = "Update PubSub table from old ejabberd trunk SVN to 2.1.0",
|
||||
desc = "Update PubSub table from ejabberd trunk SVN to 2.1.0",
|
||||
module = mod_pubsub, function = rename_default_nodeplugin,
|
||||
args = [], result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = set_master, tags = [mnesia],
|
||||
desc = "Set master node of the clustered Mnesia tables",
|
||||
longdesc = "If you provide as nodename \"self\", this "
|
||||
"node will be set as its own master.",
|
||||
module = ?MODULE, function = set_master,
|
||||
args = [{nodename, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
|
||||
desc = "Change the erlang node name in a backup file",
|
||||
module = ?MODULE, function = mnesia_change_nodename,
|
||||
@@ -289,24 +269,6 @@ send_service_message_all_mucs(Subject, AnnouncementText) ->
|
||||
end,
|
||||
?MYHOSTS).
|
||||
|
||||
%%%
|
||||
%%% ejabberd_update
|
||||
%%%
|
||||
|
||||
update_list() ->
|
||||
{ok, _Dir, UpdatedBeams, _Script, _LowLevelScript, _Check} =
|
||||
ejabberd_update:update_info(),
|
||||
[atom_to_list(Beam) || Beam <- UpdatedBeams].
|
||||
|
||||
update("all") ->
|
||||
[update_module(ModStr) || ModStr <- update_list()];
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
ejabberd_update:update([ModuleName]).
|
||||
|
||||
%%%
|
||||
%%% Account management
|
||||
%%%
|
||||
@@ -314,7 +276,7 @@ update_module(ModuleNameString) ->
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
|
||||
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
String = io_lib:format("User ~s@~s already registered at node ~p",
|
||||
[User, Host, node()]),
|
||||
@@ -377,20 +339,6 @@ delete_old_messages(Days) ->
|
||||
%%% Mnesia management
|
||||
%%%
|
||||
|
||||
set_master("self") ->
|
||||
set_master(node());
|
||||
set_master(NodeString) when is_list(NodeString) ->
|
||||
set_master(list_to_atom(NodeString));
|
||||
set_master(Node) when is_atom(Node) ->
|
||||
case mnesia:set_master_nodes([Node]) of
|
||||
ok ->
|
||||
{ok, ""};
|
||||
{error, Reason} ->
|
||||
String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
|
||||
[Node, node(), Reason]),
|
||||
{error, String}
|
||||
end.
|
||||
|
||||
backup_mnesia(Path) ->
|
||||
case mnesia:backup(Path) of
|
||||
ok ->
|
||||
|
||||
@@ -85,7 +85,7 @@ prep_stop(State) ->
|
||||
stop(_State) ->
|
||||
?INFO_MSG("ejabberd ~s is stopped in the node ~p", [?VERSION, node()]),
|
||||
delete_pid_file(),
|
||||
%%ejabberd_debug:stop(),
|
||||
ejabberd_debug:stop(),
|
||||
ok.
|
||||
|
||||
|
||||
|
||||
+1
-10
@@ -251,16 +251,7 @@ get_password_with_authmodule(User, Server) ->
|
||||
is_user_exists(User, Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
case M:is_user_exists(User, Server) of
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("The authentication module ~p returned an "
|
||||
"error~nwhen checking user ~p in server ~p~n"
|
||||
"Error message: ~p",
|
||||
[M, User, Server, Error]),
|
||||
false;
|
||||
Else ->
|
||||
Else
|
||||
end
|
||||
M:is_user_exists(User, Server)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% Check if the user exists in all authentications module except the module
|
||||
|
||||
+18
-223
@@ -35,9 +35,6 @@
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
@@ -46,87 +43,45 @@
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
extauth:start(
|
||||
Host, ejabberd_config:get_local_option({extauth_program, Host})),
|
||||
case check_cache_last_options(Host) of
|
||||
cache ->
|
||||
ok = ejabberd_auth_internal:start(Host);
|
||||
no_cache ->
|
||||
ok
|
||||
end.
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
%% if extauth_cache is enabled, then a mod_last module must also be enabled
|
||||
case get_cache_option(Server) of
|
||||
false -> no_cache;
|
||||
{true, _CacheTime} ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
no_mod_last ->
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but "
|
||||
"mod_last is not enabled.", [Server]),
|
||||
no_cache;
|
||||
_ -> cache
|
||||
end
|
||||
end.
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> check_password_extauth(User, Server, Password);
|
||||
{true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime)
|
||||
end.
|
||||
extauth:check_password(User, Server, Password) andalso Password /= "".
|
||||
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true -> set_password_internal(User, Server, Password),
|
||||
ok;
|
||||
true -> ok;
|
||||
_ -> {error, unknown_problem}
|
||||
end.
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> try_register_extauth(User, Server, Password);
|
||||
{true, _CacheTime} -> try_register_external_cache(User, Server, Password)
|
||||
end.
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
%% TODO
|
||||
%% Return the list of all users handled by external
|
||||
dirty_get_registered_users() ->
|
||||
ejabberd_auth_internal:dirty_get_registered_users().
|
||||
[].
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server).
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users(Server, Data).
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_internal:get_vh_registered_users_number(Server, Data).
|
||||
|
||||
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
|
||||
get_password(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, CacheTime} -> get_password_cache(User, Server, CacheTime)
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> [];
|
||||
Other -> Other
|
||||
end.
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
@@ -136,169 +91,9 @@ is_user_exists(User, Server) ->
|
||||
_:Error -> {error, Error}
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
case extauth:remove_user(User, Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server)
|
||||
end
|
||||
end.
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
case extauth:remove_user(User, Server, Password) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_internal:remove_user(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
%%%
|
||||
%%% Extauth cache management
|
||||
%%%
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_local_option({extauth_cache, Host}) of
|
||||
CacheTime when is_integer(CacheTime) -> {true, CacheTime};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_extauth(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso Password /= "".
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
try_register_extauth(User, Server, Password) ->
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
check_password_cache(User, Server, Password, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_internal(User, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, Server, Password);
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
|
||||
check_password_external_cache(User, Server, Password);
|
||||
TimeStamp ->
|
||||
%% If last access exists, compare last access with cache refresh time
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_internal(User, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true ->
|
||||
true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
end;
|
||||
%% Else (need to refresh), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_internal(User, Server) ->
|
||||
ejabberd_auth_internal:get_password(User, Server).
|
||||
|
||||
%% @spec (User, Server, CacheTime) -> false | Password::string()
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
get_password_internal(User, Server);
|
||||
never ->
|
||||
false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true ->
|
||||
get_password_internal(User, Server);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%% Check the password using extauth; if success then cache it
|
||||
check_password_external_cache(User, Server, Password) ->
|
||||
case check_password_extauth(User, Server, Password) of
|
||||
true ->
|
||||
set_password_internal(User, Server, Password), true;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Try to register using extauth; if success then cache it
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_internal(User, Server, Password),
|
||||
R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
check_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:check_password(User, Server, Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_internal(User, Server, Password) ->
|
||||
ejabberd_auth_internal:set_password(User, Server, Password).
|
||||
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
is_fresh_enough(online, _CacheTime) ->
|
||||
true;
|
||||
is_fresh_enough(never, _CacheTime) ->
|
||||
false;
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
Now = MegaSecs * 1000000 + Secs,
|
||||
(TimeStampLast + CacheTime > Now).
|
||||
|
||||
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
get_last_access(User, Server) ->
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
_US = {User, Server},
|
||||
case get_last_info(User, Server) of
|
||||
mod_last_required ->
|
||||
mod_last_required;
|
||||
not_found ->
|
||||
never;
|
||||
{ok, Timestamp, _Status} ->
|
||||
Timestamp
|
||||
end;
|
||||
_ ->
|
||||
online
|
||||
end.
|
||||
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
mod_last_odbc -> mod_last_odbc:get_last_info(User, Server);
|
||||
mod_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | mod_last_odbc | no_mod_last
|
||||
get_mod_last_enabled(Server) ->
|
||||
ML = lists:member(mod_last, gen_mod:loaded_modules(Server)),
|
||||
MLO = lists:member(mod_last_odbc, gen_mod:loaded_modules(Server)),
|
||||
case {ML, MLO} of
|
||||
{true, _} -> mod_last;
|
||||
{false, true} -> mod_last_odbc;
|
||||
{false, false} -> no_mod_last
|
||||
end.
|
||||
|
||||
@@ -68,11 +68,7 @@ update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Size = length(Set),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Size})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
set_vh_registered_users_counter(LServer, Size).
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
@@ -141,9 +137,7 @@ try_register(User, Server, Password) ->
|
||||
[] ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password}),
|
||||
mnesia:dirty_update_counter(
|
||||
reg_users_counter,
|
||||
LServer, 1),
|
||||
inc_vh_registered_users_counter(LServer),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
@@ -230,6 +224,40 @@ get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix)
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
inc_vh_registered_users_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({reg_users_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#reg_users_counter.count + 1,
|
||||
C2 = C#reg_users_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = 1})
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
dec_vh_registered_users_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({reg_users_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#reg_users_counter.count - 1,
|
||||
C2 = C#reg_users_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
set_vh_registered_users_counter(LServer, Count) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#reg_users_counter{vhost = LServer,
|
||||
count = Count})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -275,8 +303,7 @@ remove_user(User, Server) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1)
|
||||
dec_vh_registered_users_counter(LServer)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
@@ -291,8 +318,7 @@ remove_user(User, Server, Password) ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
dec_vh_registered_users_counter(LServer),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
|
||||
+25
-46
@@ -66,7 +66,7 @@
|
||||
servers,
|
||||
backups,
|
||||
port,
|
||||
tls_options,
|
||||
encrypt,
|
||||
dn,
|
||||
password,
|
||||
base,
|
||||
@@ -119,19 +119,19 @@ terminate(_Reason, _State) ->
|
||||
init(Host) ->
|
||||
State = parse_options(Host),
|
||||
eldap_pool:start_link(State#state.eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.encrypt),
|
||||
eldap_pool:start_link(State#state.bind_eldap_id,
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
State#state.servers,
|
||||
State#state.backups,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.encrypt),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() ->
|
||||
@@ -153,14 +153,8 @@ check_password(User, Server, Password) ->
|
||||
check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false ->
|
||||
{error, user_not_found};
|
||||
DN ->
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN, Password)
|
||||
end.
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
try_register(_User, _Server, _Password) ->
|
||||
@@ -223,13 +217,13 @@ get_vh_registered_users_ldap(Server) ->
|
||||
UIDs = State#state.uids,
|
||||
Eldap_ID = State#state.eldap_id,
|
||||
Server = State#state.host,
|
||||
ResAttrs = result_attrs(State),
|
||||
SortedDNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
|
||||
case eldap_filter:parse(State#state.sfilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID, [{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{attributes, ResAttrs}]) of
|
||||
{attributes, SortedDNAttrs}]) of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
@@ -275,16 +269,15 @@ handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
find_user_dn(User, State) ->
|
||||
ResAttrs = result_attrs(State),
|
||||
DNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
|
||||
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
|
||||
{ok, Filter} ->
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, Filter},
|
||||
{attributes, ResAttrs}]) of
|
||||
case eldap_pool:search(State#state.eldap_id, [{base, State#state.base},
|
||||
{filter, Filter},
|
||||
{attributes, DNAttrs}]) of
|
||||
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||
object_name = DN} | _]} ->
|
||||
dn_filter(DN, Attrs, State);
|
||||
dn_filter(DN, Attrs, State);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
@@ -353,14 +346,6 @@ local_filter(equal, Attrs, FilterMatch) ->
|
||||
local_filter(notequal, Attrs, FilterMatch) ->
|
||||
not local_filter(equal, Attrs, FilterMatch).
|
||||
|
||||
result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
|
||||
lists:foldl(
|
||||
fun({UID}, Acc) ->
|
||||
[UID | Acc];
|
||||
({UID, _}, Acc) ->
|
||||
[UID | Acc]
|
||||
end, DNFilterAttrs, UIDs).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Auxiliary functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -373,7 +358,6 @@ parse_options(Host) ->
|
||||
Backups -> Backups
|
||||
end,
|
||||
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
|
||||
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> case LDAPEncrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
@@ -404,12 +388,8 @@ parse_options(Host) ->
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||
undefined ->
|
||||
{undefined, []};
|
||||
{DNF, undefined} ->
|
||||
{DNF, []};
|
||||
{DNF, DNFA} ->
|
||||
{DNF, DNFA}
|
||||
undefined -> {undefined, undefined};
|
||||
{DNF, DNFA} -> {DNF, DNFA}
|
||||
end,
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
#state{host = Host,
|
||||
@@ -418,8 +398,7 @@ parse_options(Host) ->
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
encrypt = LDAPEncrypt,
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
|
||||
@@ -60,11 +60,7 @@ check_password(User, Server, Password, _Digest, _DigestGen) ->
|
||||
|
||||
check_password(User, Host, Password) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Host
|
||||
end,
|
||||
case catch epam:authenticate(Service, UserInfo, Password) of
|
||||
case catch epam:authenticate(Service, User, Password) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
@@ -88,11 +84,7 @@ get_password_s(_User, _Server) ->
|
||||
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
|
||||
is_user_exists(User, Host) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
jid -> User++"@"++Host
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
case catch epam:acct_mgmt(Service, User) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
end.
|
||||
@@ -114,8 +106,3 @@ get_pam_service(Host) ->
|
||||
undefined -> "ejabberd";
|
||||
Service -> Service
|
||||
end.
|
||||
get_pam_userinfotype(Host) ->
|
||||
case ejabberd_config:get_local_option({pam_userinfotype, Host}) of
|
||||
undefined -> username;
|
||||
Type -> Type
|
||||
end.
|
||||
|
||||
+24
-109
@@ -28,9 +28,7 @@
|
||||
-author('alexey@process-one.net').
|
||||
-update_info({update, 0}).
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(gen_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
@@ -55,9 +53,7 @@
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1
|
||||
]).
|
||||
terminate/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -108,8 +104,8 @@
|
||||
|
||||
%% Module start with or without supervisor:
|
||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||
-define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, [SockData, Opts],
|
||||
fsm_limit_opts(Opts) ++ ?FSMOPTS)).
|
||||
-define(SUPERVISOR_START, gen_fsm:start(ejabberd_c2s, [SockData, Opts],
|
||||
?FSMOPTS)).
|
||||
-else.
|
||||
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup,
|
||||
[SockData, Opts])).
|
||||
@@ -144,18 +140,17 @@ start(SockData, Opts) ->
|
||||
?SUPERVISOR_START.
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
?GEN_FSM:start_link(ejabberd_c2s, [SockData, Opts],
|
||||
fsm_limit_opts(Opts) ++ ?FSMOPTS).
|
||||
gen_fsm:start_link(ejabberd_c2s, [SockData, Opts], ?FSMOPTS).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
|
||||
%% Return Username, Resource and presence information
|
||||
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).
|
||||
|
||||
stop(FsmRef) ->
|
||||
?GEN_FSM:send_event(FsmRef, closed).
|
||||
gen_fsm:send_event(FsmRef, closed).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
@@ -226,7 +221,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
|
||||
%% Return list of all available resources of contacts,
|
||||
get_subscribed(FsmRef) ->
|
||||
?GEN_FSM:sync_send_all_state_event(FsmRef, get_subscribed, 1000).
|
||||
gen_fsm:sync_send_all_state_event(FsmRef, get_subscribed, 1000).
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: StateName/2
|
||||
@@ -317,10 +312,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
[{xmlelement, "mechanisms",
|
||||
[{"xmlns", ?NS_SASL}],
|
||||
Mechs}] ++
|
||||
ejabberd_hooks:run_fold(
|
||||
c2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
ejabberd_hooks:run_fold(
|
||||
c2s_stream_features,
|
||||
Server,
|
||||
[], [])}),
|
||||
fsm_next_state(wait_for_feature_request,
|
||||
StateData#state{
|
||||
server = Server,
|
||||
@@ -329,20 +324,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
_ ->
|
||||
case StateData#state.resource of
|
||||
"" ->
|
||||
RosterVersioningFeature =
|
||||
ejabberd_hooks:run_fold(
|
||||
roster_get_versioning_feature,
|
||||
Server, [], [Server]),
|
||||
StreamFeatures =
|
||||
[{xmlelement, "bind",
|
||||
[{"xmlns", ?NS_BIND}], []},
|
||||
{xmlelement, "session",
|
||||
[{"xmlns", ?NS_SESSION}], []}]
|
||||
++ RosterVersioningFeature
|
||||
++ ejabberd_hooks:run_fold(
|
||||
c2s_stream_features,
|
||||
Server,
|
||||
[], [Server]),
|
||||
RosterVersioningFeature = ejabberd_hooks:run_fold(roster_get_versioning_feature, Server, [], [Server]),
|
||||
StreamFeatures = [{xmlelement, "bind",
|
||||
[{"xmlns", ?NS_BIND}], []},
|
||||
{xmlelement, "session",
|
||||
[{"xmlns", ?NS_SESSION}], []} | RosterVersioningFeature],
|
||||
send_element(
|
||||
StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
@@ -471,11 +457,11 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
|
||||
Conn = get_conn_type(StateData),
|
||||
Info = [{ip, StateData#state.ip}, {conn, Conn},
|
||||
{auth_module, AuthModule}],
|
||||
ejabberd_sm:open_session(
|
||||
SID, U, StateData#state.server, R, Info),
|
||||
Res1 = jlib:make_result_iq_reply(El),
|
||||
Res = setelement(4, Res1, []),
|
||||
send_element(StateData, Res),
|
||||
ejabberd_sm:open_session(
|
||||
SID, U, StateData#state.server, R, Info),
|
||||
change_shaper(StateData, JID),
|
||||
{Fs, Ts} = ejabberd_hooks:run_fold(
|
||||
roster_get_subscription_lists,
|
||||
@@ -917,7 +903,7 @@ session_established({xmlstreamelement, El}, StateData) ->
|
||||
session_established(timeout, StateData) ->
|
||||
%% TODO: Options must be stored in state:
|
||||
Options = [],
|
||||
proc_lib:hibernate(?GEN_FSM, enter_loop,
|
||||
proc_lib:hibernate(gen_fsm, enter_loop,
|
||||
[?MODULE, Options, session_established, StateData]),
|
||||
fsm_next_state(session_established, StateData);
|
||||
|
||||
@@ -1284,19 +1270,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
{From, To, Packet},
|
||||
in]) of
|
||||
allow ->
|
||||
case ejabberd_hooks:run_fold(
|
||||
feature_check_packet, StateData#state.server,
|
||||
allow,
|
||||
[StateData#state.jid,
|
||||
StateData#state.server,
|
||||
StateData#state.pres_last,
|
||||
{From, To, Packet},
|
||||
in]) of
|
||||
allow ->
|
||||
{true, Attrs, StateData};
|
||||
deny ->
|
||||
{false, Attrs, StateData}
|
||||
end;
|
||||
{true, Attrs, StateData};
|
||||
deny ->
|
||||
{false, Attrs, StateData}
|
||||
end;
|
||||
@@ -1341,42 +1315,10 @@ handle_info(system_shutdown, StateName, StateData) ->
|
||||
ok
|
||||
end,
|
||||
{stop, normal, StateData};
|
||||
handle_info({force_update_presence, LUser}, StateName,
|
||||
#state{user = LUser, server = LServer} = StateData) ->
|
||||
NewStateData =
|
||||
case StateData#state.pres_last of
|
||||
{xmlelement, "presence", _Attrs, _Els} ->
|
||||
PresenceEl = ejabberd_hooks:run_fold(
|
||||
c2s_update_presence,
|
||||
LServer,
|
||||
StateData#state.pres_last,
|
||||
[LUser, LServer]),
|
||||
StateData2 = StateData#state{pres_last = PresenceEl},
|
||||
presence_update(StateData2#state.jid,
|
||||
PresenceEl,
|
||||
StateData2),
|
||||
StateData2;
|
||||
_ ->
|
||||
StateData
|
||||
end,
|
||||
{next_state, StateName, NewStateData};
|
||||
handle_info(Info, StateName, StateData) ->
|
||||
?ERROR_MSG("Unexpected info: ~p", [Info]),
|
||||
fsm_next_state(StateName, StateData).
|
||||
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State = #state{pres_t = T, pres_f = F, pres_a = A, pres_i = I}) ->
|
||||
State#state{pres_t = {pres_t, ?SETS:size(T)},
|
||||
pres_f = {pres_f, ?SETS:size(F)},
|
||||
pres_a = {pres_a, ?SETS:size(A)},
|
||||
pres_i = {pres_i, ?SETS:size(I)}
|
||||
}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: terminate/3
|
||||
%% Purpose: Shutdown the fsm
|
||||
@@ -1435,8 +1377,7 @@ terminate(_Reason, StateName, StateData) ->
|
||||
presence_broadcast(
|
||||
StateData, From, StateData#state.pres_i, Packet)
|
||||
end
|
||||
end,
|
||||
bounce_messages();
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
@@ -2081,12 +2022,8 @@ resend_offline_messages(#state{user = User,
|
||||
jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
FixedPacket = {xmlelement, Name, Attrs2, Els},
|
||||
send_element(StateData, FixedPacket),
|
||||
ejabberd_hooks:run(user_receive_packet,
|
||||
StateData#state.server,
|
||||
[StateData#state.jid,
|
||||
From, To, FixedPacket]);
|
||||
send_element(StateData,
|
||||
{xmlelement, Name, Attrs2, Els});
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
@@ -2216,28 +2153,6 @@ check_from(El, FromJID) ->
|
||||
end
|
||||
end.
|
||||
|
||||
fsm_limit_opts(Opts) ->
|
||||
case lists:keysearch(max_fsm_queue, 1, Opts) of
|
||||
{value, {_, N}} when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option(max_fsm_queue) of
|
||||
N when is_integer(N) ->
|
||||
[{max_queue, N}];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end.
|
||||
|
||||
bounce_messages() ->
|
||||
receive
|
||||
{route, From, To, El} ->
|
||||
ejabberd_router:route(From, To, El),
|
||||
bounce_messages()
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% JID Set memory footprint reduction code
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
@@ -328,7 +328,7 @@ get_prog_name() ->
|
||||
FileName when is_list(FileName) ->
|
||||
FileName;
|
||||
_ ->
|
||||
?DEBUG("The option captcha_cmd is not configured, but some "
|
||||
?CRITICAL_MSG("The option captcha_cmd is not configured, but some "
|
||||
"module wants to use the CAPTCHA feature.", []),
|
||||
throw({error, option_not_configured_captcha_cmd})
|
||||
end.
|
||||
|
||||
@@ -191,7 +191,7 @@ describe_config_problem(Filename, Reason, LineNumber) ->
|
||||
get_config_lines(Filename, TargetNumber, PreContext, PostContext) ->
|
||||
{ok, Fd} = file:open(Filename, [read]),
|
||||
LNumbers = lists:seq(TargetNumber-PreContext, TargetNumber+PostContext),
|
||||
NextL = io:get_line(Fd, no_prompt),
|
||||
NextL = file:read_line(Fd),
|
||||
R = get_config_lines2(Fd, NextL, 1, LNumbers, []),
|
||||
file:close(Fd),
|
||||
R.
|
||||
@@ -200,8 +200,8 @@ get_config_lines2(_Fd, eof, _CurrLine, _LNumbers, R) ->
|
||||
lists:reverse(R);
|
||||
get_config_lines2(_Fd, _NewLine, _CurrLine, [], R) ->
|
||||
lists:reverse(R);
|
||||
get_config_lines2(Fd, Data, CurrLine, [NextWanted | LNumbers], R) when is_list(Data) ->
|
||||
NextL = io:get_line(Fd, no_prompt),
|
||||
get_config_lines2(Fd, {ok, Data}, CurrLine, [NextWanted | LNumbers], R) ->
|
||||
NextL = file:read_line(Fd),
|
||||
if
|
||||
CurrLine >= NextWanted ->
|
||||
Line2 = [integer_to_list(CurrLine), ": " | Data],
|
||||
|
||||
@@ -189,9 +189,8 @@ process(["help" | Mode]) ->
|
||||
["help"] ->
|
||||
print_usage_help(MaxC, ShCode),
|
||||
?STATUS_SUCCESS;
|
||||
[CmdString | _] ->
|
||||
{ok, CmdStringU, _} = regexp:gsub(CmdString, "-", "_"),
|
||||
print_usage_commands(CmdStringU, MaxC, ShCode),
|
||||
[CommandString | _] ->
|
||||
print_usage_commands(CommandString, MaxC, ShCode),
|
||||
?STATUS_SUCCESS
|
||||
end;
|
||||
|
||||
|
||||
@@ -468,7 +468,6 @@ export_host(Dir, FnH, Host) ->
|
||||
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
[export_user(Fd, Username, Host) || {Username, _Host} <- Users],
|
||||
timer:sleep(500), % Delay to ensure ERROR_MSG are displayed in the shell
|
||||
|
||||
print(Fd, make_piefxis_host_tail()),
|
||||
print(Fd, make_piefxis_xml_tail()),
|
||||
@@ -517,14 +516,8 @@ make_xinclude(Fn) ->
|
||||
%% @spec (Fd, Username::string(), Host::string()) -> ok
|
||||
%% @doc Extract user information and print it.
|
||||
export_user(Fd, Username, Host) ->
|
||||
try extract_user(Username, Host) of
|
||||
UserString ->
|
||||
print(Fd, UserString)
|
||||
catch
|
||||
E1:E2 ->
|
||||
?ERROR_MSG("The account ~s@~s is not exported because a problem "
|
||||
"was found in it:~n~p: ~p", [Username, Host, E1, E2])
|
||||
end.
|
||||
UserString = extract_user(Username, Host),
|
||||
print(Fd, UserString).
|
||||
|
||||
%% @spec (Username::string(), Host::string()) -> string()
|
||||
extract_user(Username, Host) ->
|
||||
|
||||
+1
-13
@@ -31,7 +31,6 @@
|
||||
|
||||
%% API
|
||||
-export([route/3,
|
||||
route_error/4,
|
||||
register_route/1,
|
||||
register_route/2,
|
||||
register_routes/1,
|
||||
@@ -73,17 +72,6 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% Route the error packet only if the originating packet is not an error itself.
|
||||
%% RFC3920 9.3.1
|
||||
route_error(From, To, ErrPacket, OrigPacket) ->
|
||||
{xmlelement, _Name, Attrs, _Els} = OrigPacket,
|
||||
case "error" == xml:get_attr_s("type", Attrs) of
|
||||
false ->
|
||||
route(From, To, ErrPacket);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
register_route(Domain) ->
|
||||
register_route(Domain, undefined).
|
||||
|
||||
@@ -103,7 +91,7 @@ register_route(Domain, LocalHint) ->
|
||||
mnesia:transaction(F);
|
||||
N ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({route, LDomain}) of
|
||||
case mnesia:read({route, LDomain}) of
|
||||
[] ->
|
||||
mnesia:write(
|
||||
#route{domain = LDomain,
|
||||
|
||||
@@ -245,7 +245,7 @@ clean_table_from_bad_node(Node) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
mnesia:transaction(F).
|
||||
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
|
||||
+5
-14
@@ -177,9 +177,8 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
case {xml:get_attr_s("xmlns", Attrs),
|
||||
xml:get_attr_s("xmlns:db", Attrs),
|
||||
xml:get_attr_s("to", Attrs),
|
||||
xml:get_attr_s("version", Attrs) == "1.0"} of
|
||||
{"jabber:server", _, Server, true} when
|
||||
{"jabber:server", _, true} when
|
||||
StateData#state.tls and (not StateData#state.authenticated) ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
SASL =
|
||||
@@ -213,23 +212,15 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
end,
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
SASL ++ StartTLS ++
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
SASL ++ StartTLS}),
|
||||
{next_state, wait_for_feature_request, StateData};
|
||||
{"jabber:server", _, Server, true} when
|
||||
{"jabber:server", _, true} when
|
||||
StateData#state.authenticated ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
{xmlelement, "stream:features", [], []}),
|
||||
{next_state, stream_established, StateData};
|
||||
{"jabber:server", "jabber:server:dialback", _Server, _} ->
|
||||
{"jabber:server", "jabber:server:dialback", _} ->
|
||||
send_text(StateData, ?STREAM_HEADER("")),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
|
||||
+24
-39
@@ -51,7 +51,6 @@
|
||||
handle_sync_event/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1,
|
||||
code_change/4,
|
||||
test_get_addr_port/1,
|
||||
get_addr_port/1]).
|
||||
@@ -876,14 +875,6 @@ terminate(Reason, StateName, StateData) ->
|
||||
end,
|
||||
ok.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -961,36 +952,30 @@ send_db_request(StateData) ->
|
||||
Key ->
|
||||
Key
|
||||
end,
|
||||
NewStateData = StateData#state{new = New},
|
||||
try
|
||||
case New of
|
||||
false ->
|
||||
ok;
|
||||
Key1 ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", Server}],
|
||||
[{xmlcdata, Key1}]})
|
||||
end,
|
||||
case StateData#state.verify of
|
||||
false ->
|
||||
ok;
|
||||
{_Pid, Key2, SID} ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", StateData#state.server},
|
||||
{"id", SID}],
|
||||
[{xmlcdata, Key2}]})
|
||||
end,
|
||||
{next_state, wait_for_validation, NewStateData, ?FSMTIMEOUT*6}
|
||||
catch
|
||||
_:_ ->
|
||||
{stop, normal, NewStateData}
|
||||
end.
|
||||
case New of
|
||||
false ->
|
||||
ok;
|
||||
Key1 ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", Server}],
|
||||
[{xmlcdata, Key1}]})
|
||||
end,
|
||||
case StateData#state.verify of
|
||||
false ->
|
||||
ok;
|
||||
{_Pid, Key2, SID} ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", StateData#state.server},
|
||||
{"id", SID}],
|
||||
[{xmlcdata, Key2}]})
|
||||
end,
|
||||
{next_state, wait_for_validation, StateData#state{new = New}, ?FSMTIMEOUT*6}.
|
||||
|
||||
|
||||
is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:result" ->
|
||||
|
||||
@@ -47,8 +47,7 @@
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
print_state/1]).
|
||||
terminate/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -351,7 +350,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
send_text(StateData, Text);
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end,
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
@@ -375,14 +374,6 @@ terminate(Reason, StateName, StateData) ->
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
+39
-38
@@ -47,7 +47,6 @@
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
force_update_presence/1,
|
||||
connected_users/0,
|
||||
connected_users_number/0,
|
||||
user_resources/2,
|
||||
@@ -93,8 +92,7 @@ route(From, To, Packet) ->
|
||||
|
||||
open_session(SID, User, Server, Resource, Info) ->
|
||||
set_session(SID, User, Server, Resource, undefined, Info),
|
||||
mnesia:dirty_update_counter(session_counter,
|
||||
jlib:nameprep(Server), 1),
|
||||
inc_session_counter(jlib:nameprep(Server)),
|
||||
check_for_sessions_to_replace(User, Server, Resource),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
|
||||
@@ -107,8 +105,7 @@ close_session(SID, User, Server, Resource) ->
|
||||
end,
|
||||
F = fun() ->
|
||||
mnesia:delete({session, SID}),
|
||||
mnesia:dirty_update_counter(session_counter,
|
||||
jlib:nameprep(Server), -1)
|
||||
dec_session_counter(jlib:nameprep(Server))
|
||||
end,
|
||||
mnesia:sync_dirty(F),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
@@ -266,7 +263,6 @@ init([]) ->
|
||||
mnesia:add_table_index(session, usr),
|
||||
mnesia:add_table_index(session, us),
|
||||
mnesia:add_table_copy(session, node(), ram_copies),
|
||||
mnesia:add_table_copy(session_counter, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ets:new(sm_iqtable, [named_table]),
|
||||
lists:foreach(
|
||||
@@ -320,7 +316,7 @@ handle_info({route, From, To, Packet}, State) ->
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
recount_session_table(Node),
|
||||
clean_table_from_bad_node(Node),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
|
||||
@@ -377,9 +373,7 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
%% Recalculates alive sessions when Node goes down
|
||||
%% and updates session and session_counter tables
|
||||
recount_session_table(Node) ->
|
||||
clean_table_from_bad_node(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
session,
|
||||
@@ -387,22 +381,12 @@ recount_session_table(Node) ->
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
{_, LServer} = E#session.us,
|
||||
dec_session_counter(LServer),
|
||||
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, Es)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
@@ -573,10 +557,9 @@ route_message(From, To, Packet) ->
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
true ->
|
||||
is_privacy_allow(From, To, Packet) andalso
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
[From, To, Packet]);
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
[From, To, Packet]);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
@@ -680,6 +663,34 @@ get_max_user_sessions(LUser, Host) ->
|
||||
_ -> ?MAX_USER_SESSIONS
|
||||
end.
|
||||
|
||||
inc_session_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({session_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#session_counter.count + 1,
|
||||
C2 = C#session_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
mnesia:write(#session_counter{vhost = LServer,
|
||||
count = 1})
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
dec_session_counter(LServer) ->
|
||||
F = fun() ->
|
||||
case mnesia:wread({session_counter, LServer}) of
|
||||
[C] ->
|
||||
Count = C#session_counter.count - 1,
|
||||
C2 = C#session_counter{count = Count},
|
||||
mnesia:write(C2);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
@@ -713,16 +724,6 @@ process_iq(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
force_update_presence({LUser, _LServer} = US) ->
|
||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
||||
{'EXIT', _Reason} ->
|
||||
ok;
|
||||
Ss ->
|
||||
lists:foreach(fun(#session{sid = {_, Pid}}) ->
|
||||
Pid ! {force_update_presence, LUser}
|
||||
end, Ss)
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% ejabberd commands
|
||||
|
||||
+4
-11
@@ -84,7 +84,7 @@ update_info() ->
|
||||
update_info(Dir, Files) ->
|
||||
Beams = lists:sort(get_beams(Files)),
|
||||
UpdatedBeams = get_updated_beams(Beams),
|
||||
?DEBUG("beam files: ~p~n", [UpdatedBeams]),
|
||||
?INFO_MSG("beam files: ~p~n", [UpdatedBeams]),
|
||||
{Script, LowLevelScript, Check} = build_script(Dir, UpdatedBeams),
|
||||
{ok, Dir, UpdatedBeams, Script, LowLevelScript, Check}.
|
||||
|
||||
@@ -124,21 +124,14 @@ get_current_version(Module) ->
|
||||
%% @spec(Dir::string(), UpdatedBeams::[atom()]) -> {Script,LowLevelScript,Check}
|
||||
build_script(Dir, UpdatedBeams) ->
|
||||
Script = make_script(UpdatedBeams),
|
||||
?INFO_MSG("script: ~p~n", [Script]),
|
||||
LowLevelScript = make_low_level_script(UpdatedBeams, Script),
|
||||
?INFO_MSG("low level script: ~p~n", [LowLevelScript]),
|
||||
Check =
|
||||
release_handler_1:check_script(
|
||||
LowLevelScript,
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
case Check of
|
||||
ok ->
|
||||
?DEBUG("script: ~p~n", [Script]),
|
||||
?DEBUG("low level script: ~p~n", [LowLevelScript]),
|
||||
?DEBUG("check: ~p~n", [Check]);
|
||||
_ ->
|
||||
?ERROR_MSG("script: ~p~n", [Script]),
|
||||
?ERROR_MSG("low level script: ~p~n", [LowLevelScript]),
|
||||
?ERROR_MSG("check: ~p~n", [Check])
|
||||
end,
|
||||
?INFO_MSG("check: ~p~n", [Check]),
|
||||
{Script, LowLevelScript, Check}.
|
||||
|
||||
%% Copied from Erlang/OTP file: lib/sasl/src/systools.hrl
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
#FIREWALL_WINDOW=
|
||||
|
||||
#.
|
||||
#' ERL_PROCESSES: Maximum number of Erlang processes
|
||||
#' PROCESSES: Maximum number of Erlang processes
|
||||
#
|
||||
# Erlang consumes a lot of lightweight processes. If there is a lot of activity
|
||||
# on ejabberd so that the maximum number of processes is reached, people will
|
||||
@@ -62,7 +62,7 @@
|
||||
# Default: 250000
|
||||
# Maximum: 268435456
|
||||
#
|
||||
#ERL_PROCESSES=250000
|
||||
#PROCESSES=250000
|
||||
|
||||
#.
|
||||
#' ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
|
||||
|
||||
+14
-83
@@ -70,7 +70,7 @@ if [ "$ID" -eq "$EJID" ] ; then
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 4
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NAME=-name
|
||||
@@ -94,7 +94,7 @@ fi
|
||||
if [ "$EJABBERD_PRIV_PATH" = "" ]; then
|
||||
EJABBERD_PRIV_PATH=$EJABBERDDIR/priv
|
||||
fi
|
||||
if [ "$EJABBERD_BIN_PATH" = "" ]; then
|
||||
if [ "$EJABBRD_BIN_PATH" = "" ]; then
|
||||
EJABBERD_BIN_PATH=$EJABBERD_PRIV_PATH/bin
|
||||
fi
|
||||
if [ "$EJABBERD_SO_PATH" = "" ]; then
|
||||
@@ -153,7 +153,7 @@ debug ()
|
||||
echo ""
|
||||
echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
|
||||
echo "to an already running ejabberd node."
|
||||
echo "If an ERROR is printed, it means the connection was not successful."
|
||||
echo "If an ERROR is printed, it means the connection was not succesfull."
|
||||
echo "You can interact with the ejabberd node if you know how to use it."
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
@@ -169,9 +169,8 @@ debug ()
|
||||
read foo
|
||||
fi
|
||||
echo ""
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME debug-${TTY}-${ERLANG_NODE} \
|
||||
$NAME debug-${ERLANG_NODE} \
|
||||
-remsh $ERLANG_NODE \
|
||||
-hidden \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
@@ -229,88 +228,20 @@ help ()
|
||||
ctl ()
|
||||
{
|
||||
COMMAND=$@
|
||||
|
||||
# Control number of connections identifiers
|
||||
# using flock if available. Expects a linux-style
|
||||
# flock that can lock a file descriptor.
|
||||
MAXCONNID=100
|
||||
CONNLOCKDIR=@LOCALSTATEDIR@/lock/ejabberdctl
|
||||
FLOCK='/usr/bin/flock'
|
||||
if [ ! -x "$FLOCK" ] ; then
|
||||
JOT='/usr/bin/jot'
|
||||
if [ ! -x "$JOT" ] ; then
|
||||
# no flock or jot, simply invoke ctlexec()
|
||||
CTL_CONN="ctl-${ERLANG_NODE}"
|
||||
ctlexec $CTL_CONN $COMMAND
|
||||
result=$?
|
||||
else
|
||||
# no flock, but at least there is jot
|
||||
RAND=`jot -r 1 0 $MAXCONNID`
|
||||
CTL_CONN="ctl-${RAND}-${ERLANG_NODE}"
|
||||
ctlexec $CTL_CONN $COMMAND
|
||||
result=$?
|
||||
fi
|
||||
else
|
||||
# we have flock so we get a lock
|
||||
# on one of a limited number of
|
||||
# conn names -- this allows
|
||||
# concurrent invocations using a bound
|
||||
# number of atoms
|
||||
for N in $(seq 1 $MAXCONNID); do
|
||||
CTL_CONN="ejabberdctl-$N"
|
||||
CTL_LOCKFILE="$CONNLOCKDIR/$CTL_CONN"
|
||||
(
|
||||
exec 8>"$CTL_LOCKFILE"
|
||||
if flock --nb 8; then
|
||||
ctlexec $CTL_CONN $COMMAND
|
||||
ssresult=$?
|
||||
# segregate from possible flock exit(1)
|
||||
ssresult=$(expr $ssresult \* 10)
|
||||
exit $ssresult
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
result=$?
|
||||
if [ $result -eq 1 ]; then
|
||||
# means we errored out in flock
|
||||
# rather than in the exec - stay in the loop
|
||||
# trying other conn names...
|
||||
badlock=1
|
||||
else
|
||||
badlock=""
|
||||
break;
|
||||
fi
|
||||
done
|
||||
result=$(expr $result / 10)
|
||||
fi
|
||||
|
||||
if [ "$badlock" ];then
|
||||
echo "Ran out of connections to try. Your ejabberd processes" >&2
|
||||
echo "may be stuck or this is a very busy server. For very" >&2
|
||||
echo "busy servers, consider raising MAXCONNID in ejabberdctl">&2
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
case $result in
|
||||
0) :;;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
ctlexec ()
|
||||
{
|
||||
CONN_NAME=$1; shift
|
||||
COMMAND=$@
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME ${CONN_NAME} \
|
||||
$NAME ctl-${ERLANG_NODE} \
|
||||
-noinput \
|
||||
-hidden \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
-s ejabberd_ctl -extra $ERLANG_NODE $COMMAND"
|
||||
result=$?
|
||||
case $result in
|
||||
0) :;;
|
||||
1) :;;
|
||||
2) help;;
|
||||
3) help;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
# display ctl usage
|
||||
@@ -335,7 +266,7 @@ wait_for_status()
|
||||
status=4
|
||||
while [ $status -ne $1 ]; do
|
||||
sleep $3
|
||||
timeout=$(($timeout - 1))
|
||||
let timeout=timeout-1
|
||||
[ $timeout -eq 0 ] && {
|
||||
status=$1
|
||||
} || {
|
||||
|
||||
@@ -286,16 +286,6 @@ ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
|
||||
responseName [10] LDAPOID OPTIONAL,
|
||||
response [11] OCTET STRING OPTIONAL }
|
||||
|
||||
passwdModifyOID LDAPOID ::= "1.3.6.1.4.1.4203.1.11.1"
|
||||
|
||||
PasswdModifyRequestValue ::= SEQUENCE {
|
||||
userIdentity [0] OCTET STRING OPTIONAL,
|
||||
oldPasswd [1] OCTET STRING OPTIONAL,
|
||||
newPasswd [2] OCTET STRING OPTIONAL }
|
||||
|
||||
PasswdModifyResponseValue ::= SEQUENCE {
|
||||
genPasswd [0] OCTET STRING OPTIONAL }
|
||||
|
||||
END
|
||||
|
||||
|
||||
|
||||
+4
-11
@@ -1,4 +1,4 @@
|
||||
# $Id: Makefile.in 2842 2009-12-29 19:10:52Z badlop $
|
||||
# $Id$
|
||||
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
@@ -20,23 +20,18 @@ ifdef debug
|
||||
endif
|
||||
|
||||
OUTDIR = ..
|
||||
SOURCES = $(wildcard *.erl) ELDAPv3.erl eldap_filter_yecc.erl
|
||||
SOURCES = $(wildcard *.erl) ELDAPv3.erl
|
||||
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
|
||||
|
||||
|
||||
all: $(BEAMS) ELDAPv3.beam eldap_filter_yecc.beam
|
||||
all: $(BEAMS) ELDAPv3.beam
|
||||
|
||||
ELDAPv3.beam: ELDAPv3.erl
|
||||
|
||||
ELDAPv3.erl: ELDAPv3.asn
|
||||
@ERLC@ $(ASN_FLAGS) -W $(EFLAGS) $<
|
||||
|
||||
eldap_filter_yecc.beam: eldap_filter_yecc.erl
|
||||
|
||||
eldap_filter_yecc.erl: eldap_filter_yecc.yrl
|
||||
@ERLC@ -W $<
|
||||
|
||||
$(OUTDIR)/%.beam: %.erl ELDAPv3.erl eldap_filter_yecc.erl
|
||||
$(OUTDIR)/%.beam: %.erl ELDAPv3.erl
|
||||
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
|
||||
|
||||
clean:
|
||||
@@ -44,8 +39,6 @@ clean:
|
||||
rm -f ELDAPv3.erl
|
||||
rm -f ELDAPv3.hrl
|
||||
rm -f ELDAPv3.beam
|
||||
rm -f eldap_filter_yecc.erl
|
||||
rm -f eldap_filter_yecc.beam
|
||||
rm -f $(BEAMS)
|
||||
|
||||
distclean: clean
|
||||
|
||||
@@ -4,7 +4,7 @@ include ..\Makefile.inc
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
OUTDIR = ..
|
||||
BEAMS = ..\eldap.beam ..\eldap_filter.beam ..\eldap_pool.beam ..\eldap_utils.beam ..\eldap_filter_yecc.beam
|
||||
BEAMS = ..\eldap.beam ..\eldap_filter.beam ..\eldap_pool.beam ..\eldap_utils.beam
|
||||
|
||||
ASN_FLAGS = -bber_bin +optimize +driver
|
||||
|
||||
@@ -15,16 +15,11 @@ Clean :
|
||||
-@erase ELDAPv3.erl
|
||||
-@erase ELDAPv3.hrl
|
||||
-@erase ELDAPv3.beam
|
||||
-@erase eldap_filter_yecc.erl
|
||||
-@erase eldap_filter_yecc.beam
|
||||
-@erase $(BEAMS)
|
||||
|
||||
ELDAPv3.erl : ELDAPv3.asn
|
||||
erlc $(ASN_FLAGS) -W $(EFLAGS) ELDAPv3.asn
|
||||
|
||||
eldap_filter_yecc.erl: eldap_filter_yecc.yrl
|
||||
erlc -W eldap_filter_yecc.yrl
|
||||
|
||||
$(OUTDIR)\eldap.beam : eldap.erl ELDAPv3.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) eldap.erl
|
||||
|
||||
@@ -39,6 +34,3 @@ $(OUTDIR)\eldap_utils.beam : eldap_utils.erl
|
||||
|
||||
$(OUTDIR)\eldap_pool.beam : eldap_pool.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) eldap_pool.erl
|
||||
|
||||
$(OUTDIR)\eldap_filter_yecc.beam : eldap_filter_yecc.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) eldap_filter_yecc.erl
|
||||
|
||||
+24
-91
@@ -33,11 +33,8 @@
|
||||
|
||||
%%% Modified by Alexey Shchepin <alexey@sevcom.net>
|
||||
|
||||
%%% Modified by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Modified by Evgeniy Khramtsov <xram@jabber.ru>
|
||||
%%% Implemented queue for bind() requests to prevent pending binds.
|
||||
%%% Implemented extensibleMatch/2 function.
|
||||
%%% Implemented LDAP Extended Operations (currently only Password Modify
|
||||
%%% is supported - RFC 3062).
|
||||
|
||||
%%% Modified by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Improve error case handling
|
||||
@@ -74,9 +71,9 @@
|
||||
|
||||
-export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
|
||||
equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
|
||||
approxMatch/2,search/2,substrings/2,present/1,extensibleMatch/2,
|
||||
approxMatch/2,search/2,substrings/2,present/1,
|
||||
'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2,
|
||||
mod_replace/2, add/3, delete/2, modify_dn/5, modify_passwd/3, bind/3]).
|
||||
mod_replace/2, add/3, delete/2, modify_dn/5, bind/3]).
|
||||
-export([get_status/1]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
@@ -130,10 +127,9 @@ start_link(Name) ->
|
||||
Reg_name = list_to_atom("eldap_" ++ Name),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
|
||||
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
start_link(Name, Hosts, Port, Rootdn, Passwd, Encrypt) ->
|
||||
Reg_name = list_to_atom("eldap_" ++ Name),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
{Hosts, Port, Rootdn, Passwd, Opts}, []).
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Encrypt}, []).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Get status of connection.
|
||||
@@ -243,10 +239,6 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
|
||||
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
modify_passwd(Handle, DN, Passwd) when is_list(DN), is_list(Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(
|
||||
Handle1, {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Bind.
|
||||
@@ -382,29 +374,6 @@ substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
|
||||
{substrings,#'SubstringFilter'{type = Type,
|
||||
substrings = Ss}}.
|
||||
|
||||
%%%
|
||||
%%% extensibleMatch filter.
|
||||
%%% FIXME: Describe the purpose of this filter.
|
||||
%%%
|
||||
%%% Value ::= string( <attribute> )
|
||||
%%% Opts ::= listof( {matchingRule, Str} | {type, Str} | {dnAttributes, true} )
|
||||
%%%
|
||||
%%% Example: extensibleMatch("Fred", [{matchingRule, "1.2.3.4.5"}, {type, "cn"}]).
|
||||
%%%
|
||||
extensibleMatch(Value, Opts) when is_list(Value), is_list(Opts) ->
|
||||
MRA = #'MatchingRuleAssertion'{matchValue=Value},
|
||||
{extensibleMatch, extensibleMatch_opts(Opts, MRA)}.
|
||||
|
||||
extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) when is_list(Rule) ->
|
||||
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{matchingRule=Rule});
|
||||
extensibleMatch_opts([{type, Desc} | Opts], MRA) when is_list(Desc) ->
|
||||
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{type=Desc});
|
||||
extensibleMatch_opts([{dnAttributes, true} | Opts], MRA) ->
|
||||
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{dnAttributes=true});
|
||||
extensibleMatch_opts([_ | Opts], MRA) ->
|
||||
extensibleMatch_opts(Opts, MRA);
|
||||
extensibleMatch_opts([], MRA) ->
|
||||
MRA.
|
||||
|
||||
get_handle(Pid) when is_pid(Pid) -> Pid;
|
||||
get_handle(Atom) when is_atom(Atom) -> Atom;
|
||||
@@ -424,18 +393,15 @@ get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
|
||||
%%----------------------------------------------------------------------
|
||||
init([]) ->
|
||||
case get_config() of
|
||||
{ok, Hosts, Rootdn, Passwd, Opts} ->
|
||||
init({Hosts, Rootdn, Passwd, Opts});
|
||||
{ok, Hosts, Rootdn, Passwd, Encrypt} ->
|
||||
init({Hosts, Rootdn, Passwd, Encrypt});
|
||||
{error, Reason} ->
|
||||
{stop, Reason}
|
||||
end;
|
||||
init({Hosts, Port, Rootdn, Passwd, Opts}) ->
|
||||
init({Hosts, Port, Rootdn, Passwd, Encrypt}) ->
|
||||
catch ssl:start(),
|
||||
ssl:seed(randoms:get_string()),
|
||||
Encrypt = case proplists:get_value(encrypt, Opts) of
|
||||
tls -> tls;
|
||||
_ -> none
|
||||
end,
|
||||
{X1,X2,X3} = erlang:now(),
|
||||
ssl:seed(integer_to_list(X1) ++ integer_to_list(X2) ++ integer_to_list(X3)),
|
||||
PortTemp = case Port of
|
||||
undefined ->
|
||||
case Encrypt of
|
||||
@@ -448,14 +414,7 @@ init({Hosts, Port, Rootdn, Passwd, Opts}) ->
|
||||
end;
|
||||
PT -> PT
|
||||
end,
|
||||
TLSOpts = case proplists:get_value(tls_verify, Opts) of
|
||||
soft ->
|
||||
[{verify, 1}];
|
||||
hard ->
|
||||
[{verify, 2}];
|
||||
_ ->
|
||||
[{verify, 0}]
|
||||
end,
|
||||
TLSOpts = [verify_none],
|
||||
{ok, connecting, #eldap{hosts = Hosts,
|
||||
port = PortTemp,
|
||||
rootdn = Rootdn,
|
||||
@@ -712,16 +671,6 @@ gen_req({modify_dn, Entry, NewRDN, DelOldRDN, NewSup}) ->
|
||||
deleteoldrdn = DelOldRDN,
|
||||
newSuperior = NewSup}};
|
||||
|
||||
gen_req({modify_passwd, DN, Passwd}) ->
|
||||
{ok, ReqVal} = asn1rt:encode(
|
||||
'ELDAPv3', 'PasswdModifyRequestValue',
|
||||
#'PasswdModifyRequestValue'{
|
||||
userIdentity = DN,
|
||||
newPasswd = Passwd}),
|
||||
{extendedReq,
|
||||
#'ExtendedRequest'{requestName = ?passwdModifyOID,
|
||||
requestValue = list_to_binary(ReqVal)}};
|
||||
|
||||
gen_req({bind, RootDN, Passwd}) ->
|
||||
{bindRequest,
|
||||
#'BindRequest'{version = ?LDAP_VERSION,
|
||||
@@ -796,11 +745,6 @@ recvd_packet(Pkt, S) ->
|
||||
cancel_timer(Timer),
|
||||
Reply = check_bind_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{extendedReq, {extendedResp, Result}} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
Reply = check_extended_reply(Result, From),
|
||||
{reply, Reply, From, S#eldap{dict = New_dict}};
|
||||
{OtherName, OtherResult} ->
|
||||
New_dict = dict:erase(Id, Dict),
|
||||
cancel_timer(Timer),
|
||||
@@ -825,15 +769,6 @@ check_bind_reply(#'BindResponse'{resultCode = Reason}, _From) ->
|
||||
check_bind_reply(Other, _From) ->
|
||||
{error, Other}.
|
||||
|
||||
%% TODO: process reply depending on requestName:
|
||||
%% this requires BER-decoding of #'ExtendedResponse'.response
|
||||
check_extended_reply(#'ExtendedResponse'{resultCode = success}, _From) ->
|
||||
ok;
|
||||
check_extended_reply(#'ExtendedResponse'{resultCode = Reason}, _From) ->
|
||||
{error, Reason};
|
||||
check_extended_reply(Other, _From) ->
|
||||
{error, Other}.
|
||||
|
||||
get_op_rec(Id, Dict) ->
|
||||
case dict:find(Id, Dict) of
|
||||
{ok, [{Timer, _Command, From, Name}|Res]} ->
|
||||
@@ -969,7 +904,7 @@ connect_bind(S) ->
|
||||
tls ->
|
||||
SockMod = ssl,
|
||||
SslOpts = [{packet, asn1}, {active, true}, {keepalive, true},
|
||||
binary | S#eldap.tls_options],
|
||||
binary],
|
||||
ssl:connect(Host, S#eldap.port, SslOpts);
|
||||
%% starttls -> %% TODO: Implement STARTTLS;
|
||||
_ ->
|
||||
@@ -1038,8 +973,6 @@ v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV};
|
||||
v_filter({approxMatch,AV}) -> {approxMatch,AV};
|
||||
v_filter({present,A}) -> {present,A};
|
||||
v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
|
||||
v_filter({extensibleMatch, S}) when is_record(S, 'MatchingRuleAssertion') ->
|
||||
{extensibleMatch, S};
|
||||
v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
|
||||
|
||||
v_modifications(Mods) ->
|
||||
@@ -1085,8 +1018,8 @@ get_config() ->
|
||||
case file:consult(File) of
|
||||
{ok, Entries} ->
|
||||
case catch parse(Entries) of
|
||||
{ok, Hosts, Port, Rootdn, Passwd, Opts} ->
|
||||
{ok, Hosts, Port, Rootdn, Passwd, Opts};
|
||||
{ok, Hosts, Port, Rootdn, Passwd, Encrypt} ->
|
||||
{ok, Hosts, Port, Rootdn, Passwd, Encrypt};
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{'EXIT', Reason} ->
|
||||
@@ -1102,7 +1035,7 @@ parse(Entries) ->
|
||||
get_integer(port, Entries),
|
||||
get_list(rootdn, Entries),
|
||||
get_list(passwd, Entries),
|
||||
get_list(options, Entries)}.
|
||||
get_atom(encrypt, Entries)}.
|
||||
|
||||
get_integer(Key, List) ->
|
||||
case lists:keysearch(Key, 1, List) of
|
||||
@@ -1124,15 +1057,15 @@ get_list(Key, List) ->
|
||||
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
|
||||
end.
|
||||
|
||||
%% get_atom(Key, List) ->
|
||||
%% case lists:keysearch(Key, 1, List) of
|
||||
%% {value, {Key, Value}} when is_atom(Value) ->
|
||||
%% Value;
|
||||
%% {value, {Key, _Value}} ->
|
||||
%% throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
|
||||
%% false ->
|
||||
%% throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
|
||||
%% end.
|
||||
get_atom(Key, List) ->
|
||||
case lists:keysearch(Key, 1, List) of
|
||||
{value, {Key, Value}} when is_atom(Value) ->
|
||||
Value;
|
||||
{value, {Key, _Value}} ->
|
||||
throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
|
||||
false ->
|
||||
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
|
||||
end.
|
||||
|
||||
get_hosts(Key, List) ->
|
||||
lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A),
|
||||
|
||||
+189
-64
@@ -3,7 +3,7 @@
|
||||
%%% Purpose: Converts String Representation of
|
||||
%%% LDAP Search Filter (RFC 2254)
|
||||
%%% to eldap's representation of filter
|
||||
%%% Author: Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
|
||||
@@ -24,17 +24,19 @@
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(eldap_filter).
|
||||
|
||||
%% TODO: remove this when new regexp module will be used
|
||||
-compile({nowarn_deprecated_function, {regexp, sub, 3}}).
|
||||
%%%======================
|
||||
%%% Export functions
|
||||
%%%======================
|
||||
|
||||
-export([parse/1, parse/2, do_sub/2]).
|
||||
-export([parse/1,
|
||||
parse/2,
|
||||
do_sub/2
|
||||
]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%%-------------------------------------------------------------------
|
||||
%%%-------------------------------------------------------------------------
|
||||
%%% Arity: parse/1
|
||||
%%% Function: parse(RFC2254_Filter) -> {ok, EldapFilter} |
|
||||
%%% {error, bad_filter}
|
||||
@@ -45,15 +47,15 @@
|
||||
%%% to eldap's representation of filter.
|
||||
%%%
|
||||
%%% Example:
|
||||
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
|
||||
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
|
||||
%%%
|
||||
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
|
||||
%%% {present,"mail"}]}}
|
||||
%%%-------------------------------------------------------------------
|
||||
parse(L) when is_list(L) ->
|
||||
parse(L, []).
|
||||
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
|
||||
%%% {present,"mail"}]}}
|
||||
%%%-------------------------------------------------------------------------
|
||||
parse(RFC2254_Filter) ->
|
||||
parse(RFC2254_Filter, []).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%%-------------------------------------------------------------------------
|
||||
%%% Arity: parse/2
|
||||
%%% Function: parse(RFC2254_Filter, [SubstValue |...]) ->
|
||||
%%% {ok, EldapFilter} |
|
||||
@@ -79,53 +81,135 @@ parse(L) when is_list(L) ->
|
||||
%%% {equalityMatch,{'AttributeValueAssertion',
|
||||
%%% "jid",
|
||||
%%% "xramtsov@gmail.com"}}]}}
|
||||
%%%-------------------------------------------------------------------
|
||||
parse(L, SList) when is_list(L), is_list(SList) ->
|
||||
case catch eldap_filter_yecc:parse(scan(L, SList)) of
|
||||
{error, {_, _, Msg}} ->
|
||||
{error, Msg};
|
||||
{ok, Result} ->
|
||||
{ok, Result};
|
||||
{regexp, Err} ->
|
||||
{error, Err}
|
||||
%%%--------------------------------------------------------------------------
|
||||
parse(RFC2254_Filter, ListOfSubValues) ->
|
||||
case catch convert_filter(parse_filter(RFC2254_Filter), ListOfSubValues) of
|
||||
[EldapFilter] when is_tuple(EldapFilter) ->
|
||||
{ok, EldapFilter};
|
||||
{regexp, Error} ->
|
||||
{error, Error};
|
||||
_ ->
|
||||
{error, bad_filter}
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
-define(do_scan(L), scan(Rest, [], [{L, 1} | check(Buf, S) ++ Result], L, S)).
|
||||
%%%==========================
|
||||
%%% Internal functions
|
||||
%%%==========================
|
||||
|
||||
scan(L, SList) ->
|
||||
scan(L, "", [], undefined, SList).
|
||||
%%%----------------------
|
||||
%%% split/1,4
|
||||
%%%----------------------
|
||||
split(Filter) ->
|
||||
split(Filter, 0, [], []).
|
||||
|
||||
scan("=*)" ++ Rest, Buf, Result, '(', S) ->
|
||||
scan(Rest, [], [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S);
|
||||
scan(":dn" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':dn');
|
||||
scan(":=" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':=');
|
||||
scan(":=" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':=');
|
||||
scan(":=" ++ Rest, Buf, Result, ':', S) -> ?do_scan(':=');
|
||||
scan("~=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('~=');
|
||||
scan(">=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('>=');
|
||||
scan("<=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('<=');
|
||||
scan("=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('=');
|
||||
scan(":" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':');
|
||||
scan(":" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':');
|
||||
scan("&" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('&');
|
||||
scan("|" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('|');
|
||||
scan("!" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('!');
|
||||
scan("*" ++ Rest, Buf, Result, '*', S) -> ?do_scan('*');
|
||||
scan("*" ++ Rest, Buf, Result, '=', S) -> ?do_scan('*');
|
||||
scan("(" ++ Rest, Buf, Result, _, S) -> ?do_scan('(');
|
||||
scan(")" ++ Rest, Buf, Result, _, S) -> ?do_scan(')');
|
||||
scan([Letter | Rest], Buf, Result, PreviosAtom, S) ->
|
||||
scan(Rest, [Letter|Buf], Result, PreviosAtom, S);
|
||||
scan([], Buf, Result, _, S) ->
|
||||
lists:reverse(check(Buf, S) ++ Result).
|
||||
split([], _, _, Result) ->
|
||||
Result;
|
||||
|
||||
check([], _) ->
|
||||
[];
|
||||
check(Buf, S) ->
|
||||
[{str, 1, do_sub(lists:reverse(Buf), S)}].
|
||||
split([H|T], Num, Rest, Result) ->
|
||||
NewNum = case H of
|
||||
$( -> Num + 1;
|
||||
$) -> Num - 1;
|
||||
_ -> Num
|
||||
end,
|
||||
if
|
||||
NewNum == 0 ->
|
||||
X = Rest++[H],
|
||||
LenX = length(X),
|
||||
if
|
||||
LenX > 2 ->
|
||||
split(T, 0, [], Result ++ [lists:sublist(X, 2, LenX-2)]);
|
||||
true ->
|
||||
split(T, 0, Rest, Result)
|
||||
end;
|
||||
true ->
|
||||
split(T, NewNum, Rest++[H], Result)
|
||||
end.
|
||||
|
||||
%%%-----------------------
|
||||
%%% parse_filter/1
|
||||
%%%-----------------------
|
||||
parse_filter(Filter) ->
|
||||
case Filter of
|
||||
[$! | T] ->
|
||||
{'not', parse_filter(T)};
|
||||
[$| | T] ->
|
||||
{'or', parse_filter(T)};
|
||||
[$& | T] ->
|
||||
{'and', parse_filter(T)};
|
||||
[$( | _] ->
|
||||
parse_filter(split(Filter));
|
||||
[List | _] when is_list(List) ->
|
||||
[parse_filter(X) || X <- Filter];
|
||||
_ ->
|
||||
Filter
|
||||
end.
|
||||
|
||||
%%%--------------------
|
||||
%%% convert_filter/2
|
||||
%%%--------------------
|
||||
convert_filter({'not', [Val | _]}, Replace) ->
|
||||
eldap:'not'(convert_filter(Val, Replace));
|
||||
|
||||
convert_filter({'or', Vals}, Replace) ->
|
||||
eldap:'or'([convert_filter(X, Replace) || X <- Vals]);
|
||||
|
||||
convert_filter({'and', Vals}, Replace) ->
|
||||
eldap:'and'([convert_filter(X, Replace) || X <- Vals]);
|
||||
|
||||
convert_filter([H|_] = Filter, Replace) when is_integer(H) ->
|
||||
parse_attr(Filter, Replace);
|
||||
|
||||
convert_filter(Filter, Replace) when is_list(Filter) ->
|
||||
[convert_filter(X, Replace) || X <- Filter].
|
||||
|
||||
%%%-----------------
|
||||
%%% parse_attr/2,3
|
||||
%%%-----------------
|
||||
parse_attr(Attr, ListOfSubValues) ->
|
||||
{Action, [_|_] = Name, [_|_] = Value} = split_attribute(Attr),
|
||||
parse_attr(Action, {Name, Value}, ListOfSubValues).
|
||||
|
||||
parse_attr(approx, {Name, Value}, ListOfSubValues) ->
|
||||
NewValue = do_sub(Value, ListOfSubValues),
|
||||
eldap:approxMatch(Name, NewValue);
|
||||
|
||||
parse_attr(greater, {Name, Value}, ListOfSubValues) ->
|
||||
NewValue = do_sub(Value, ListOfSubValues),
|
||||
eldap:greaterOrEqual(Name, NewValue);
|
||||
|
||||
parse_attr(less, {Name, Value}, ListOfSubValues) ->
|
||||
NewValue = do_sub(Value, ListOfSubValues),
|
||||
eldap:lessOrEqual(Name, NewValue);
|
||||
|
||||
parse_attr(equal, {Name, Value}, ListOfSubValues) ->
|
||||
{ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
|
||||
Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
|
||||
[Head | Tail] when Tail /= [] ->
|
||||
{Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
|
||||
R ->
|
||||
R
|
||||
end,
|
||||
case Pattern of
|
||||
[V] ->
|
||||
eldap:equalityMatch(Name, V);
|
||||
{[], [], []} ->
|
||||
eldap:present(Name);
|
||||
{"", Any, ""} ->
|
||||
eldap:substrings(Name, [{any, X} || X<-Any]);
|
||||
{H, Any, ""} ->
|
||||
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]);
|
||||
{"", Any, T} ->
|
||||
eldap:substrings(Name, [{any, X} || X<-Any]++[{final, T}]);
|
||||
{H, Any, T} ->
|
||||
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]++[{final, T}])
|
||||
end;
|
||||
|
||||
parse_attr(_, _, _) ->
|
||||
false.
|
||||
|
||||
%%%--------------------
|
||||
%%% do_sub/2,3
|
||||
%%%--------------------
|
||||
|
||||
-define(MAX_RECURSION, 100).
|
||||
|
||||
@@ -150,9 +234,9 @@ do_sub(S, {RegExp, New}, Iter) ->
|
||||
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
|
||||
do_sub(NewS, {RegExp, New}, Iter+1);
|
||||
{ok, _, _} when Iter > ?MAX_RECURSION ->
|
||||
erlang:error(max_substitute_recursion);
|
||||
throw({regexp, max_substitute_recursion});
|
||||
_ ->
|
||||
erlang:error(bad_regexp)
|
||||
throw({regexp, bad_regexp})
|
||||
end;
|
||||
|
||||
do_sub(S, {_, _, N}, _) when N<1 ->
|
||||
@@ -167,11 +251,52 @@ do_sub(S, {RegExp, New, Times}, Iter) ->
|
||||
{ok, NewS, _} ->
|
||||
NewS;
|
||||
_ ->
|
||||
erlang:error(bad_regexp)
|
||||
throw({regexp, bad_regexp})
|
||||
end.
|
||||
|
||||
remove_extra_asterisks(String) ->
|
||||
{Res, _} = lists:foldl(
|
||||
fun(X, {Acc, Last}) ->
|
||||
case X of
|
||||
$* when Last==$* ->
|
||||
{Acc, X};
|
||||
_ ->
|
||||
{Acc ++ [X], X}
|
||||
end
|
||||
end,
|
||||
{"", ""}, String),
|
||||
Res.
|
||||
|
||||
replace_amps(String) ->
|
||||
lists:map(
|
||||
fun($&) -> "\\&";
|
||||
(Chr) -> Chr
|
||||
end, String).
|
||||
lists:foldl(
|
||||
fun(X, Acc) ->
|
||||
if
|
||||
X == $& ->
|
||||
Acc ++ "\\&";
|
||||
true ->
|
||||
Acc ++ [X]
|
||||
end
|
||||
end,
|
||||
"", String).
|
||||
|
||||
split_attribute(String) ->
|
||||
split_attribute(String, "", $0).
|
||||
|
||||
split_attribute([], _, _) ->
|
||||
{error, "", ""};
|
||||
|
||||
split_attribute([H|Tail], Acc, Last) ->
|
||||
case H of
|
||||
$= when Last==$> ->
|
||||
{greater, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= when Last==$< ->
|
||||
{less, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= when Last==$~ ->
|
||||
{approx, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= when Last==$: ->
|
||||
{equal, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= ->
|
||||
{equal, Acc, Tail};
|
||||
_ ->
|
||||
split_attribute(Tail, Acc++[H], H)
|
||||
end.
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
Nonterminals
|
||||
filter filtercomp filterlist item
|
||||
simple present substring extensible
|
||||
initial any final matchingrule xattr
|
||||
attr value.
|
||||
|
||||
Terminals str
|
||||
'(' ')' '&' '|' '!' '=' '~=' '>=' '<=' '=*' '*' ':dn' ':' ':='.
|
||||
|
||||
Rootsymbol filter.
|
||||
|
||||
filter -> '(' filtercomp ')': '$2'.
|
||||
filtercomp -> '&' filterlist: 'and'('$2').
|
||||
filtercomp -> '|' filterlist: 'or'('$2').
|
||||
filtercomp -> '!' filter: 'not'('$2').
|
||||
filtercomp -> item: '$1'.
|
||||
filterlist -> filter: '$1'.
|
||||
filterlist -> filter filterlist: flatten(['$1', '$2']).
|
||||
|
||||
item -> simple: '$1'.
|
||||
item -> present: '$1'.
|
||||
item -> substring: '$1'.
|
||||
item -> extensible: '$1'.
|
||||
|
||||
simple -> attr '=' value: equal('$1', '$3').
|
||||
simple -> attr '~=' value: approx('$1', '$3').
|
||||
simple -> attr '>=' value: greater('$1', '$3').
|
||||
simple -> attr '<=' value: less('$1', '$3').
|
||||
|
||||
present -> attr '=*': present('$1').
|
||||
|
||||
substring -> attr '=' initial '*' any: substrings('$1', ['$3', '$5']).
|
||||
substring -> attr '=' '*' any final: substrings('$1', ['$4', '$5']).
|
||||
substring -> attr '=' initial '*' any final: substrings('$1', ['$3', '$5', '$6']).
|
||||
substring -> attr '=' '*' any: substrings('$1', ['$4']).
|
||||
any -> any value '*': 'any'('$1', '$2').
|
||||
any -> '$empty': [].
|
||||
initial -> value: initial('$1').
|
||||
final -> value: final('$1').
|
||||
|
||||
extensible -> xattr ':dn' ':' matchingrule ':=' value: extensible('$6', ['$1', '$4']).
|
||||
extensible -> xattr ':' matchingrule ':=' value: extensible('$5', ['$1', '$3']).
|
||||
extensible -> xattr ':dn' ':=' value: extensible('$4', ['$1']).
|
||||
extensible -> xattr ':=' value: extensible('$3', ['$1']).
|
||||
extensible -> ':dn' ':' matchingrule ':=' value: extensible('$5', ['$3']).
|
||||
extensible -> ':' matchingrule ':=' value: extensible('$4', ['$2']).
|
||||
xattr -> value: xattr('$1').
|
||||
matchingrule -> value: matchingrule('$1').
|
||||
|
||||
attr -> str: value_of('$1').
|
||||
value -> str: value_of('$1').
|
||||
|
||||
Erlang code.
|
||||
|
||||
'and'(Value) -> eldap:'and'(Value).
|
||||
'or'(Value) -> eldap:'or'(Value).
|
||||
'not'(Value) -> eldap:'not'(Value).
|
||||
equal(Desc, Value) -> eldap:equalityMatch(Desc, Value).
|
||||
approx(Desc, Value) -> eldap:approxMatch(Desc, Value).
|
||||
greater(Desc, Value) -> eldap:greaterOrEqual(Desc, Value).
|
||||
less(Desc, Value) -> eldap:lessOrEqual(Desc, Value).
|
||||
present(Value) -> eldap:present(Value).
|
||||
extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts).
|
||||
substrings(Desc, ValueList) -> eldap:substrings(Desc, flatten(ValueList)).
|
||||
initial(Value) -> {initial, Value}.
|
||||
final(Value) -> {final, Value}.
|
||||
'any'(Token, Value) -> [Token, {any, Value}].
|
||||
xattr(Value) -> {type, Value}.
|
||||
matchingrule(Value) -> {matchingRule, Value}.
|
||||
value_of(Token) -> element(3, Token).
|
||||
flatten(List) -> lists:flatten(List).
|
||||
+11
-17
@@ -31,8 +31,7 @@
|
||||
-export([
|
||||
start_link/7,
|
||||
bind/3,
|
||||
search/2,
|
||||
modify_passwd/3
|
||||
search/2
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -46,23 +45,18 @@ bind(PoolName, DN, Passwd) ->
|
||||
search(PoolName, Opts) ->
|
||||
do_request(PoolName, {search, [Opts]}).
|
||||
|
||||
modify_passwd(PoolName, DN, Passwd) ->
|
||||
do_request(PoolName, {modify_passwd, [DN, Passwd]}).
|
||||
|
||||
start_link(Name, Hosts, Backups, Port, Rootdn, Passwd, Opts) ->
|
||||
start_link(Name, Hosts, Backups, Port, Rootdn, Passwd, Encrypt) ->
|
||||
PoolName = make_id(Name),
|
||||
pg2:create(PoolName),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ID = erlang:ref_to_list(make_ref()),
|
||||
case catch eldap:start_link(ID, [Host|Backups], Port,
|
||||
Rootdn, Passwd, Opts) of
|
||||
{ok, Pid} ->
|
||||
pg2:join(PoolName, Pid);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end, Hosts).
|
||||
lists:foreach(fun(Host) ->
|
||||
ID = erlang:ref_to_list(make_ref()),
|
||||
case catch eldap:start_link(ID, [Host|Backups], Port, Rootdn, Passwd, Encrypt) of
|
||||
{ok, Pid} ->
|
||||
pg2:join(PoolName, Pid);
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end, Hosts).
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
|
||||
@@ -53,17 +53,15 @@ subfilter({UIDAttr}) ->
|
||||
|
||||
%% Not tail-recursive, but it is not very terribly.
|
||||
%% It stops finding on the first not empty value.
|
||||
find_ldap_attrs([{Attr} | Rest], Attributes) ->
|
||||
find_ldap_attrs([{Attr, "%u"} | Rest], Attributes);
|
||||
find_ldap_attrs([{Attr, Format} | Rest], Attributes) ->
|
||||
case get_ldap_attr(Attr, Attributes) of
|
||||
case get_ldap_attr(Attr, Attributes) of
|
||||
Value when is_list(Value), Value /= "" ->
|
||||
{Value, Format};
|
||||
{Value, Format};
|
||||
_ ->
|
||||
find_ldap_attrs(Rest, Attributes)
|
||||
end;
|
||||
find_ldap_attrs(Rest, Attributes)
|
||||
end;
|
||||
find_ldap_attrs([], _) ->
|
||||
"".
|
||||
"".
|
||||
|
||||
get_ldap_attr(LDAPAttr, Attributes) ->
|
||||
Res = lists:filter(
|
||||
|
||||
+2
-21
@@ -27,15 +27,8 @@
|
||||
-module(extauth).
|
||||
-author('leifj@it.su.se').
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
init/2,
|
||||
check_password/3,
|
||||
set_password/3,
|
||||
try_register/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
is_user_exists/2]).
|
||||
-export([start/2, stop/1, init/2,
|
||||
check_password/3, set_password/3, is_user_exists/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
@@ -63,18 +56,6 @@ is_user_exists(User, Server) ->
|
||||
set_password(User, Server, Password) ->
|
||||
call_port(Server, ["setpass", User, Server, Password]).
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case call_port(Server, ["tryregister", User, Server, Password]) of
|
||||
true -> {atomic, ok};
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
call_port(Server, ["removeuser", User, Server]).
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
call_port(Server, ["removeuser3", User, Server, Password]).
|
||||
|
||||
call_port(Server, Msg) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
gen_mod:get_module_proc(LServer, eauth) ! {call, self(), Msg},
|
||||
|
||||
+9
-11
@@ -382,7 +382,7 @@ iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
|
||||
"result" -> {result, reply};
|
||||
"error" -> {error, reply};
|
||||
_ -> {invalid, invalid}
|
||||
end,
|
||||
end,
|
||||
if
|
||||
Type1 == invalid ->
|
||||
invalid;
|
||||
@@ -404,24 +404,22 @@ iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
|
||||
<- FilteredEls,
|
||||
SubName /= "error"],
|
||||
{case NonErrorEls of
|
||||
[NonErrorEl] ->
|
||||
xml:get_tag_attr_s("xmlns", NonErrorEl);
|
||||
_ ->
|
||||
""
|
||||
[NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
|
||||
_ -> invalid
|
||||
end,
|
||||
FilteredEls};
|
||||
_ ->
|
||||
{"", []}
|
||||
{invalid, invalid}
|
||||
end,
|
||||
if XMLNS == "", Class == request ->
|
||||
invalid;
|
||||
true ->
|
||||
#iq{id = ID,
|
||||
type = Type1,
|
||||
xmlns = XMLNS,
|
||||
lang = Lang,
|
||||
#iq{id = ID,
|
||||
type = Type1,
|
||||
xmlns = XMLNS,
|
||||
lang = Lang,
|
||||
sub_el = SubEl}
|
||||
end;
|
||||
end;
|
||||
Class == reply, Filter /= any ->
|
||||
reply
|
||||
end;
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
-define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items").
|
||||
-define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info").
|
||||
-define(NS_VCARD, "vcard-temp").
|
||||
-define(NS_VCARD_UPDATE, "vcard-temp:x:update").
|
||||
-define(NS_AUTH, "jabber:iq:auth").
|
||||
-define(NS_AUTH_ERROR, "jabber:iq:auth:error").
|
||||
-define(NS_REGISTER, "jabber:iq:register").
|
||||
@@ -85,7 +84,6 @@
|
||||
|
||||
-define(NS_CAPS, "http://jabber.org/protocol/caps").
|
||||
-define(NS_SHIM, "http://jabber.org/protocol/shim").
|
||||
-define(NS_ADDRESS, "http://jabber.org/protocol/address").
|
||||
|
||||
%% CAPTCHA related NSes.
|
||||
-define(NS_OOB, "jabber:x:oob").
|
||||
@@ -141,8 +139,6 @@
|
||||
?STANZA_ERROR("407", "auth", "subscription-required")).
|
||||
-define(ERR_UNEXPECTED_REQUEST,
|
||||
?STANZA_ERROR("400", "wait", "unexpected-request")).
|
||||
-define(ERR_UNEXPECTED_REQUEST_CANCEL,
|
||||
?STANZA_ERROR("401", "cancel", "unexpected-request")).
|
||||
%-define(ERR_,
|
||||
% ?STANZA_ERROR("", "", "")).
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_adhoc.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : Handle incoming ad-doc requests (XEP-0050)
|
||||
%%% Purpose : Handle incoming ad-doc requests (JEP-0050)
|
||||
%%% Created : 15 Nov 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%%
|
||||
%%%
|
||||
|
||||
+356
-316
@@ -33,11 +33,13 @@
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([read_caps/1,
|
||||
caps_stream_features/2,
|
||||
disco_features/5,
|
||||
disco_identity/5,
|
||||
disco_info/5,
|
||||
get_features/1]).
|
||||
get_caps/1,
|
||||
note_caps/3,
|
||||
wait_caps/2,
|
||||
clear_caps/1,
|
||||
get_features/2,
|
||||
get_user_resources/2,
|
||||
handle_disco_response/3]).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, start_link/2,
|
||||
@@ -53,21 +55,120 @@
|
||||
]).
|
||||
|
||||
%% hook handlers
|
||||
-export([user_send_packet/3]).
|
||||
-export([receive_packet/3,
|
||||
receive_packet/4,
|
||||
presence_probe/3,
|
||||
remove_connection/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_caps).
|
||||
-define(DICT, dict).
|
||||
-define(CAPS_QUERY_TIMEOUT, 60000). % 1mn without answer, consider client never answer
|
||||
|
||||
-record(caps, {node, version, hash, exts}).
|
||||
-record(caps, {node, version, exts}).
|
||||
-record(caps_features, {node_pair, features = []}).
|
||||
-record(user_caps, {jid, caps}).
|
||||
-record(user_caps_resources, {uid, resource}).
|
||||
-record(state, {host,
|
||||
disco_requests = ?DICT:new(),
|
||||
feature_queries = []}).
|
||||
|
||||
-record(state, {host}).
|
||||
%% read_caps takes a list of XML elements (the child elements of a
|
||||
%% <presence/> stanza) and returns an opaque value representing the
|
||||
%% Entity Capabilities contained therein, or the atom nothing if no
|
||||
%% capabilities are advertised.
|
||||
read_caps(Els) ->
|
||||
read_caps(Els, nothing).
|
||||
read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_CAPS ->
|
||||
Node = xml:get_attr_s("node", Attrs),
|
||||
Version = xml:get_attr_s("ver", Attrs),
|
||||
Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
|
||||
read_caps(Tail, #caps{node = Node, version = Version, exts = Exts});
|
||||
_ ->
|
||||
read_caps(Tail, Result)
|
||||
end;
|
||||
read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_MUC_USER ->
|
||||
nothing;
|
||||
_ ->
|
||||
read_caps(Tail, Result)
|
||||
end;
|
||||
read_caps([_ | Tail], Result) ->
|
||||
read_caps(Tail, Result);
|
||||
read_caps([], Result) ->
|
||||
Result.
|
||||
|
||||
%% get_caps reads user caps from database
|
||||
%% here we handle a simple retry loop, to avoid race condition
|
||||
%% when asking caps while we still did not called note_caps
|
||||
%% timeout is set to 10s
|
||||
%% this is to be improved, but without altering performances.
|
||||
%% if we did not get user presence 10s after getting presence_probe
|
||||
%% we assume it has no caps
|
||||
get_caps(LJID) ->
|
||||
get_caps(LJID, 5).
|
||||
get_caps(_, 0) ->
|
||||
nothing;
|
||||
get_caps(LJID, Retry) ->
|
||||
case catch mnesia:dirty_read({user_caps, jid_to_binary(LJID)}) of
|
||||
[#user_caps{caps=waiting}] ->
|
||||
timer:sleep(2000),
|
||||
get_caps(LJID, Retry-1);
|
||||
[#user_caps{caps=Caps}] ->
|
||||
Caps;
|
||||
_ ->
|
||||
nothing
|
||||
end.
|
||||
|
||||
%% clear_caps removes user caps from database
|
||||
clear_caps(JID) ->
|
||||
{U, S, R} = jlib:jid_tolower(JID),
|
||||
BJID = jid_to_binary(JID),
|
||||
BUID = jid_to_binary({U, S, []}),
|
||||
catch mnesia:dirty_delete({user_caps, BJID}),
|
||||
catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}),
|
||||
ok.
|
||||
|
||||
%% give default user resource
|
||||
get_user_resources(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of
|
||||
{'EXIT', _} ->
|
||||
[];
|
||||
Resources ->
|
||||
lists:map(fun(#user_caps_resources{resource=R}) -> binary_to_list(R) end, Resources)
|
||||
end.
|
||||
|
||||
%% note_caps should be called to make the module request disco
|
||||
%% information. Host is the host that asks, From is the full JID that
|
||||
%% sent the caps packet, and Caps is what read_caps returned.
|
||||
note_caps(Host, From, Caps) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:cast(Proc, {note_caps, From, Caps}).
|
||||
|
||||
%% wait_caps should be called just before note_caps
|
||||
%% it allows to lock get_caps usage for code using presence_probe
|
||||
%% that may run before we get any chance to note_caps.
|
||||
wait_caps(Host, From) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:cast(Proc, {wait_caps, From}).
|
||||
|
||||
%% get_features returns a list of features implied by the given caps
|
||||
%% record (as extracted by read_caps). It may block, and may signal a
|
||||
%% timeout error.
|
||||
get_features(Host, Caps) ->
|
||||
case Caps of
|
||||
nothing ->
|
||||
[];
|
||||
#caps{} ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, {get_features, Caps})
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
@@ -85,227 +186,61 @@ start(Host, Opts) ->
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
gen_server:call(Proc, stop).
|
||||
|
||||
%% get_features returns a list of features implied by the given caps
|
||||
%% record (as extracted by read_caps) or 'unknown' if features are
|
||||
%% not completely collected at the moment.
|
||||
get_features(nothing) ->
|
||||
[];
|
||||
get_features(#caps{node = Node, version = Version, exts = Exts}) ->
|
||||
SubNodes = [Version | Exts],
|
||||
lists:foldl(
|
||||
fun(SubNode, Acc) ->
|
||||
case mnesia:dirty_read({caps_features,
|
||||
node_to_binary(Node, SubNode)}) of
|
||||
[] ->
|
||||
Acc;
|
||||
[#caps_features{features = Features}] ->
|
||||
binary_to_features(Features) ++ Acc
|
||||
end
|
||||
end, [], SubNodes).
|
||||
|
||||
%% read_caps takes a list of XML elements (the child elements of a
|
||||
%% <presence/> stanza) and returns an opaque value representing the
|
||||
%% Entity Capabilities contained therein, or the atom nothing if no
|
||||
%% capabilities are advertised.
|
||||
read_caps(Els) ->
|
||||
read_caps(Els, nothing).
|
||||
|
||||
read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_CAPS ->
|
||||
Node = xml:get_attr_s("node", Attrs),
|
||||
Version = xml:get_attr_s("ver", Attrs),
|
||||
Hash = xml:get_attr_s("hash", Attrs),
|
||||
Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
|
||||
read_caps(Tail, #caps{node = Node, hash = Hash,
|
||||
version = Version, exts = Exts});
|
||||
_ ->
|
||||
read_caps(Tail, Result)
|
||||
receive_packet(From, To, {xmlelement, "presence", Attrs, Els}) ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"probe" ->
|
||||
ok;
|
||||
"error" ->
|
||||
ok;
|
||||
"invisible" ->
|
||||
ok;
|
||||
"subscribe" ->
|
||||
ok;
|
||||
"subscribed" ->
|
||||
ok;
|
||||
"unsubscribe" ->
|
||||
ok;
|
||||
"unsubscribed" ->
|
||||
ok;
|
||||
"unavailable" ->
|
||||
{_, S1, _} = jlib:jid_tolower(From),
|
||||
case jlib:jid_tolower(To) of
|
||||
{_, S1, _} -> ok;
|
||||
_ -> clear_caps(From)
|
||||
end;
|
||||
%% TODO: probe client, and clean only if no answers
|
||||
%% as far as protocol does not allow inter-server communication to
|
||||
%% let remote server handle it's own caps to decide which user is to be
|
||||
%% notified, we must keep a cache of online status of external contacts
|
||||
%% this is absolutely not scallable, but we have no choice for now
|
||||
%% we can only use unavailable presence, but if remote user just remove a local user
|
||||
%% from its roster, then it's considered as offline, so he does not receive local PEP
|
||||
%% anymore until he login again.
|
||||
%% This is tracked in EJAB-943
|
||||
_ ->
|
||||
note_caps(To#jid.lserver, From, read_caps(Els))
|
||||
end;
|
||||
read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_MUC_USER ->
|
||||
nothing;
|
||||
_ ->
|
||||
read_caps(Tail, Result)
|
||||
end;
|
||||
read_caps([_ | Tail], Result) ->
|
||||
read_caps(Tail, Result);
|
||||
read_caps([], Result) ->
|
||||
Result.
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
user_send_packet(#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server, lresource = ""},
|
||||
{xmlelement, "presence", Attrs, Els}) ->
|
||||
Type = xml:get_attr_s("type", Attrs),
|
||||
if Type == ""; Type == "available" ->
|
||||
case read_caps(Els) of
|
||||
nothing ->
|
||||
ok;
|
||||
#caps{version = Version, exts = Exts} = Caps ->
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
user_send_packet(_From, _To, _Packet) ->
|
||||
receive_packet(_, _, _) ->
|
||||
ok.
|
||||
|
||||
caps_stream_features(Acc, MyHost) ->
|
||||
case make_my_disco_hash(MyHost) of
|
||||
"" ->
|
||||
Acc;
|
||||
Hash ->
|
||||
[{xmlelement, "c", [{"xmlns", ?NS_CAPS},
|
||||
{"hash", "sha-1"},
|
||||
{"node", ?EJABBERD_URI},
|
||||
{"ver", Hash}], []} | Acc]
|
||||
end.
|
||||
receive_packet(_JID, From, To, Packet) ->
|
||||
receive_packet(From, To, Packet).
|
||||
|
||||
disco_features(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
|
||||
ejabberd_hooks:run_fold(disco_local_features,
|
||||
To#jid.lserver,
|
||||
empty,
|
||||
[From, To, "", Lang]);
|
||||
disco_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
presence_probe(From, To, _) ->
|
||||
wait_caps(To#jid.lserver, From).
|
||||
|
||||
disco_identity(_Acc, From, To, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
|
||||
ejabberd_hooks:run_fold(disco_local_identity,
|
||||
To#jid.lserver,
|
||||
[],
|
||||
[From, To, "", Lang]);
|
||||
disco_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
remove_connection(_SID, JID, _Info) ->
|
||||
clear_caps(JID).
|
||||
|
||||
disco_info(_Acc, Host, Module, ?EJABBERD_URI ++ "#" ++ [_|_], Lang) ->
|
||||
ejabberd_hooks:run_fold(disco_info,
|
||||
Host,
|
||||
[],
|
||||
[Host, Module, "", Lang]);
|
||||
disco_info(Acc, _Host, _Module, _Node, _Lang) ->
|
||||
Acc.
|
||||
jid_to_binary(JID) ->
|
||||
{U, S, R} = jlib:jid_tolower(JID),
|
||||
list_to_binary(jlib:jid_to_string({U, S, R})).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, _Opts]) ->
|
||||
mnesia:create_table(caps_features,
|
||||
[{disc_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, caps_features)}]),
|
||||
mnesia:add_table_copy(caps_features, node(), disc_copies),
|
||||
ejabberd_hooks:add(user_send_packet, Host,
|
||||
?MODULE, user_send_packet, 75),
|
||||
ejabberd_hooks:add(c2s_stream_features, Host,
|
||||
?MODULE, caps_stream_features, 75),
|
||||
ejabberd_hooks:add(s2s_stream_features, Host,
|
||||
?MODULE, caps_stream_features, 75),
|
||||
ejabberd_hooks:add(disco_local_features, Host,
|
||||
?MODULE, disco_features, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, Host,
|
||||
?MODULE, disco_identity, 75),
|
||||
ejabberd_hooks:add(disco_info, Host,
|
||||
?MODULE, disco_info, 75),
|
||||
{ok, #state{host = Host}}.
|
||||
|
||||
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(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
Host = State#state.host,
|
||||
ejabberd_hooks:delete(user_send_packet, Host,
|
||||
?MODULE, user_send_packet, 75),
|
||||
ejabberd_hooks:delete(c2s_stream_features, Host,
|
||||
?MODULE, caps_stream_features, 75),
|
||||
ejabberd_hooks:delete(s2s_stream_features, Host,
|
||||
?MODULE, caps_stream_features, 75),
|
||||
ejabberd_hooks:delete(disco_local_features, Host,
|
||||
?MODULE, disco_features, 75),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host,
|
||||
?MODULE, disco_identity, 75),
|
||||
ejabberd_hooks:delete(disco_info, Host,
|
||||
?MODULE, disco_info, 75),
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%====================================================================
|
||||
%% Aux functions
|
||||
%%====================================================================
|
||||
feature_request(Host, From, Caps, [SubNode | Tail] = SubNodes) ->
|
||||
Node = Caps#caps.node,
|
||||
BinaryNode = node_to_binary(Node, SubNode),
|
||||
case mnesia:dirty_read({caps_features, BinaryNode}) of
|
||||
[] ->
|
||||
IQ = #iq{type = get,
|
||||
xmlns = ?NS_DISCO_INFO,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO},
|
||||
{"node", Node ++ "#" ++ SubNode}],
|
||||
[]}]},
|
||||
F = fun(IQReply) ->
|
||||
feature_response(
|
||||
IQReply, Host, From, Caps, SubNodes)
|
||||
end,
|
||||
ejabberd_local:route_iq(
|
||||
jlib:make_jid("", Host, ""), From, IQ, F);
|
||||
_ ->
|
||||
feature_request(Host, From, Caps, Tail)
|
||||
end;
|
||||
feature_request(_Host, _From, _Caps, []) ->
|
||||
ok.
|
||||
|
||||
feature_response(#iq{type = result,
|
||||
sub_el = [{xmlelement, _, _, Els}]},
|
||||
Host, From, Caps, [SubNode | SubNodes]) ->
|
||||
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
|
||||
IsValid = case Caps#caps.hash of
|
||||
"sha-1" ->
|
||||
Caps#caps.version == make_disco_hash(Els, sha1);
|
||||
"md5" ->
|
||||
Caps#caps.version == make_disco_hash(Els, md5);
|
||||
_ ->
|
||||
true
|
||||
end,
|
||||
if IsValid ->
|
||||
Features = lists:flatmap(
|
||||
fun({xmlelement, "feature", FAttrs, _}) ->
|
||||
[xml:get_attr_s("var", FAttrs)];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els),
|
||||
mnesia:dirty_write(
|
||||
#caps_features{node_pair = BinaryNode,
|
||||
features = features_to_binary(Features)});
|
||||
true ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode})
|
||||
end,
|
||||
feature_request(Host, From, Caps, SubNodes);
|
||||
feature_response(timeout, _Host, _From, _Caps, _SubNodes) ->
|
||||
ok;
|
||||
feature_response(_IQResult, Host, From, Caps, [SubNode | SubNodes]) ->
|
||||
%% We got type=error or invalid type=result stanza, so
|
||||
%% we cache empty feature not to probe the client permanently
|
||||
BinaryNode = node_to_binary(Caps#caps.node, SubNode),
|
||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
|
||||
feature_request(Host, From, Caps, SubNodes).
|
||||
caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) ->
|
||||
BExts = [list_to_binary(Ext) || Ext <- Exts],
|
||||
#caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}.
|
||||
|
||||
node_to_binary(Node, SubNode) ->
|
||||
{list_to_binary(Node), list_to_binary(SubNode)}.
|
||||
@@ -313,101 +248,206 @@ node_to_binary(Node, SubNode) ->
|
||||
features_to_binary(L) -> [list_to_binary(I) || I <- L].
|
||||
binary_to_features(L) -> [binary_to_list(I) || I <- L].
|
||||
|
||||
make_my_disco_hash(Host) ->
|
||||
JID = jlib:make_jid("", Host, ""),
|
||||
case {ejabberd_hooks:run_fold(disco_local_features,
|
||||
Host,
|
||||
empty,
|
||||
[JID, JID, "", ""]),
|
||||
ejabberd_hooks:run_fold(disco_local_identity,
|
||||
Host,
|
||||
[],
|
||||
[JID, JID, "", ""]),
|
||||
ejabberd_hooks:run_fold(disco_info,
|
||||
Host,
|
||||
[],
|
||||
[Host, undefined, "", ""])} of
|
||||
{{result, Features}, Identities, Info} ->
|
||||
Feats = lists:map(
|
||||
fun({{Feat, _Host}}) ->
|
||||
{xmlelement, "feature", [{"var", Feat}], []};
|
||||
(Feat) ->
|
||||
{xmlelement, "feature", [{"var", Feat}], []}
|
||||
end, Features),
|
||||
make_disco_hash(Identities ++ Info ++ Feats, sha1);
|
||||
_Err ->
|
||||
""
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
init([Host, _Opts]) ->
|
||||
mnesia:create_table(caps_features,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, caps_features)}]),
|
||||
mnesia:create_table(user_caps,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, user_caps)}]),
|
||||
mnesia:create_table(user_caps_resources,
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, user_caps_resources)}]),
|
||||
mnesia:delete_table(user_caps_default),
|
||||
mnesia:clear_table(user_caps), % clean in case of explicitely set to disc_copies
|
||||
mnesia:clear_table(user_caps_resources), % clean in case of explicitely set to disc_copies
|
||||
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 30),
|
||||
ejabberd_hooks:add(s2s_receive_packet, Host, ?MODULE, receive_packet, 30),
|
||||
ejabberd_hooks:add(presence_probe_hook, Host, ?MODULE, presence_probe, 20),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20),
|
||||
{ok, #state{host = Host}}.
|
||||
|
||||
maybe_get_features(#caps{node = Node, version = Version, exts = Exts}) ->
|
||||
SubNodes = [Version | Exts],
|
||||
%% Make sure that we have all nodes we need to know.
|
||||
%% If a single one is missing, we wait for more disco
|
||||
%% responses.
|
||||
case lists:foldl(fun(SubNode, Acc) ->
|
||||
case Acc of
|
||||
fail -> fail;
|
||||
_ ->
|
||||
case mnesia:dirty_read({caps_features, {Node, SubNode}}) of
|
||||
[] -> fail;
|
||||
[#caps_features{features = Features}] -> Features ++ Acc %% TODO binary
|
||||
end
|
||||
end
|
||||
end, [], SubNodes) of
|
||||
fail -> wait;
|
||||
Features -> {ok, Features}
|
||||
end.
|
||||
|
||||
make_disco_hash(DiscoEls, Algo) when Algo == sha1; Algo == md5 ->
|
||||
Concat = [concat_identities(DiscoEls),
|
||||
concat_features(DiscoEls),
|
||||
concat_info(DiscoEls)],
|
||||
base64:encode_to_string(
|
||||
if Algo == sha1 ->
|
||||
crypto:sha(Concat);
|
||||
Algo == md5 ->
|
||||
crypto:md5(Concat)
|
||||
end).
|
||||
timestamp() ->
|
||||
{MegaSecs, Secs, _MicroSecs} = now(),
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
concat_features(Els) ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "feature", Attrs, _}) ->
|
||||
[[xml:get_attr_s("var", Attrs), $<]];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els)).
|
||||
handle_call({get_features, Caps}, From, State) ->
|
||||
case maybe_get_features(Caps) of
|
||||
{ok, Features} ->
|
||||
{reply, binary_to_features(Features), State};
|
||||
wait ->
|
||||
gen_server:cast(self(), visit_feature_queries),
|
||||
Timeout = timestamp() + 10,
|
||||
FeatureQueries = State#state.feature_queries,
|
||||
NewFeatureQueries = [{From, Caps, Timeout} | FeatureQueries],
|
||||
NewState = State#state{feature_queries = NewFeatureQueries},
|
||||
{noreply, NewState}
|
||||
end;
|
||||
|
||||
concat_identities(Els) ->
|
||||
lists:sort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "identity", Attrs, _}) ->
|
||||
[[xml:get_attr_s("category", Attrs), $/,
|
||||
xml:get_attr_s("type", Attrs), $/,
|
||||
xml:get_attr_s("xml:lang", Attrs), $/,
|
||||
xml:get_attr_s("name", Attrs), $<]];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els)).
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State}.
|
||||
|
||||
concat_info(Els) ->
|
||||
lists:sort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "x", Attrs, Fields}) ->
|
||||
case {xml:get_attr_s("xmlns", Attrs),
|
||||
xml:get_attr_s("type", Attrs)} of
|
||||
{?NS_XDATA, "result"} ->
|
||||
[concat_xdata_fields(Fields)];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Els)).
|
||||
handle_cast({note_caps, From, nothing}, State) ->
|
||||
BJID = jid_to_binary(From),
|
||||
catch mnesia:dirty_delete({user_caps, BJID}),
|
||||
{noreply, State};
|
||||
handle_cast({note_caps, From,
|
||||
#caps{node = Node, version = Version, exts = Exts} = Caps},
|
||||
#state{host = Host, disco_requests = Requests} = State) ->
|
||||
%% XXX: this leads to race conditions where ejabberd will send
|
||||
%% lots of caps disco requests.
|
||||
{U, S, R} = jlib:jid_tolower(From),
|
||||
BJID = jid_to_binary(From),
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
|
||||
case ejabberd_sm:get_user_resources(U, S) of
|
||||
[] ->
|
||||
% only store resources of caps aware external contacts
|
||||
BUID = jid_to_binary({U, S, []}),
|
||||
mnesia:write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
%% Now, find which of these are not already in the database.
|
||||
SubNodes = [Version | Exts],
|
||||
case lists:foldl(fun(SubNode, Acc) ->
|
||||
case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of
|
||||
[] ->
|
||||
[SubNode | Acc];
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end, [], SubNodes) of
|
||||
[] ->
|
||||
{noreply, State};
|
||||
Missing ->
|
||||
%% For each unknown caps "subnode", we send a disco request.
|
||||
NewRequests = lists:foldl(
|
||||
fun(SubNode, Dict) ->
|
||||
ID = randoms:get_string(),
|
||||
Stanza =
|
||||
{xmlelement, "iq",
|
||||
[{"type", "get"},
|
||||
{"id", ID}],
|
||||
[{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO},
|
||||
{"node", lists:concat([Node, "#", SubNode])}],
|
||||
[]}]},
|
||||
ejabberd_local:register_iq_response_handler
|
||||
(Host, ID, ?MODULE, handle_disco_response),
|
||||
ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
|
||||
timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID, BJID}),
|
||||
?DICT:store(ID, node_to_binary(Node, SubNode), Dict)
|
||||
end, Requests, Missing),
|
||||
{noreply, State#state{disco_requests = NewRequests}}
|
||||
end;
|
||||
handle_cast({wait_caps, From}, State) ->
|
||||
BJID = jid_to_binary(From),
|
||||
mnesia:dirty_write(#user_caps{jid = BJID, caps = waiting}),
|
||||
{noreply, State};
|
||||
handle_cast({disco_response, From, _To,
|
||||
#iq{type = Type, id = ID,
|
||||
sub_el = SubEls}},
|
||||
#state{disco_requests = Requests} = State) ->
|
||||
case {Type, SubEls} of
|
||||
{result, [{xmlelement, "query", _Attrs, Els}]} ->
|
||||
case ?DICT:find(ID, Requests) of
|
||||
{ok, BinaryNode} ->
|
||||
Features =
|
||||
lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
|
||||
[xml:get_attr_s("var", FAttrs)];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els),
|
||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode, features = features_to_binary(Features)}),
|
||||
gen_server:cast(self(), visit_feature_queries);
|
||||
error ->
|
||||
?DEBUG("ID '~s' matches no query", [ID])
|
||||
end;
|
||||
{error, _} ->
|
||||
%% XXX: if we get error, we cache empty feature not to probe the client continuously
|
||||
case ?DICT:find(ID, Requests) of
|
||||
{ok, BinaryNode} ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = BinaryNode}),
|
||||
gen_server:cast(self(), visit_feature_queries);
|
||||
error ->
|
||||
?DEBUG("ID '~s' matches no query", [ID])
|
||||
end;
|
||||
%gen_server:cast(self(), visit_feature_queries),
|
||||
%?DEBUG("Error IQ reponse from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
|
||||
{result, _} ->
|
||||
?DEBUG("Invalid IQ contents from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
|
||||
_ ->
|
||||
%% Can't do anything about errors
|
||||
ok
|
||||
end,
|
||||
NewRequests = ?DICT:erase(ID, Requests),
|
||||
{noreply, State#state{disco_requests = NewRequests}};
|
||||
handle_cast({disco_timeout, ID, BJID}, #state{host = Host, disco_requests = Requests} = State) ->
|
||||
%% do not wait a response anymore for this IQ, client certainly will never answer
|
||||
NewRequests = case ?DICT:is_key(ID, Requests) of
|
||||
true ->
|
||||
catch mnesia:dirty_delete({user_caps, BJID}),
|
||||
ejabberd_local:unregister_iq_response_handler(Host, ID),
|
||||
?DICT:erase(ID, Requests);
|
||||
false ->
|
||||
Requests
|
||||
end,
|
||||
{noreply, State#state{disco_requests = NewRequests}};
|
||||
handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = State) ->
|
||||
Timestamp = timestamp(),
|
||||
NewFeatureQueries =
|
||||
lists:foldl(fun({From, Caps, Timeout}, Acc) ->
|
||||
case maybe_get_features(Caps) of
|
||||
wait when Timeout > Timestamp -> [{From, Caps, Timeout} | Acc];
|
||||
wait -> Acc;
|
||||
{ok, Features} ->
|
||||
gen_server:reply(From, Features),
|
||||
Acc
|
||||
end
|
||||
end, [], FeatureQueries),
|
||||
{noreply, State#state{feature_queries = NewFeatureQueries}}.
|
||||
|
||||
concat_xdata_fields(Fields) ->
|
||||
[Form, Res] =
|
||||
lists:foldl(
|
||||
fun({xmlelement, "field", Attrs, Els} = El,
|
||||
[FormType, VarFields] = Acc) ->
|
||||
case xml:get_attr_s("var", Attrs) of
|
||||
"" ->
|
||||
Acc;
|
||||
"FORM_TYPE" ->
|
||||
[xml:get_subtag_cdata(El, "value"), VarFields];
|
||||
Var ->
|
||||
[FormType,
|
||||
[[[Var, $<],
|
||||
lists:sort(
|
||||
lists:flatmap(
|
||||
fun({xmlelement, "value", _, VEls}) ->
|
||||
[[xml:get_cdata(VEls), $<]];
|
||||
(_) ->
|
||||
[]
|
||||
end, Els))] | VarFields]]
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, ["", []], Fields),
|
||||
[Form, $<, lists:sort(Res)].
|
||||
handle_disco_response(From, To, IQ) ->
|
||||
#jid{lserver = Host} = To,
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:cast(Proc, {disco_response, From, To, IQ}).
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
Host = State#state.host,
|
||||
ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 30),
|
||||
ejabberd_hooks:delete(s2s_receive_packet, Host, ?MODULE, receive_packet, 30),
|
||||
ejabberd_hooks:delete(presence_probe_hook, Host, ?MODULE, presence_probe, 20),
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, remove_connection, 20),
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
+2
-34
@@ -237,23 +237,7 @@ get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
["config", _] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("add-user") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("delete-user") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("end-user-session") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("get-user-password") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("change-user-password") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("get-user-lastlogin") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("user-stats") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("get-registered-users-num") ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
?NS_ADMINL("get-online-users-num") ->
|
||||
["http:" | _] ->
|
||||
?INFO_RESULT(Allow, [?NS_COMMANDS]);
|
||||
_ ->
|
||||
Acc
|
||||
@@ -465,23 +449,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
["config", _] ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("add-user") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("delete-user") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("end-user-session") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("get-user-password") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("change-user-password") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("get-user-lastlogin") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("user-stats") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("get-registered-users-num") ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
?NS_ADMINL("get-online-users-num") ->
|
||||
?NS_ADMINL(_) ->
|
||||
?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN});
|
||||
_ ->
|
||||
Acc
|
||||
|
||||
+60
-102
@@ -172,7 +172,7 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
|
||||
[{"xmlns", ?NS_DISCO_INFO} | ANode],
|
||||
Identity ++
|
||||
Info ++
|
||||
features_to_xml(Features)
|
||||
lists:map(fun feature_to_xml/1, Features)
|
||||
}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
@@ -209,16 +209,10 @@ get_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
end.
|
||||
|
||||
|
||||
features_to_xml(FeatureList) ->
|
||||
%% Avoid duplicating features
|
||||
[{xmlelement, "feature", [{"var", Feat}], []} ||
|
||||
Feat <- lists:usort(
|
||||
lists:map(
|
||||
fun({{Feature, _Host}}) ->
|
||||
Feature;
|
||||
(Feature) when is_list(Feature) ->
|
||||
Feature
|
||||
end, FeatureList))].
|
||||
feature_to_xml({{Feature, _Host}}) ->
|
||||
feature_to_xml(Feature);
|
||||
feature_to_xml(Feature) when is_list(Feature) ->
|
||||
{xmlelement, "feature", [{"var", Feature}], []}.
|
||||
|
||||
domain_to_xml({Domain}) ->
|
||||
{xmlelement, "item", [{"jid", Domain}], []};
|
||||
@@ -269,48 +263,42 @@ process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
case ejabberd_hooks:run_fold(disco_sm_items,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
{result, Items} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_ITEMS} | ANode],
|
||||
Items
|
||||
}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
end
|
||||
Host = To#jid.lserver,
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
case ejabberd_hooks:run_fold(disco_sm_items,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
{result, Items} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_ITEMS} | ANode],
|
||||
Items
|
||||
}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
end.
|
||||
|
||||
get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc;
|
||||
|
||||
get_sm_items(Acc, From,
|
||||
#jid{user = User, server = Server} = To,
|
||||
get_sm_items(Acc,
|
||||
#jid{luser = LFrom, lserver = LSFrom},
|
||||
#jid{user = User, server = Server, luser = LTo, lserver = LSTo} = _To,
|
||||
[], _Lang) ->
|
||||
Items = case Acc of
|
||||
{result, Its} -> Its;
|
||||
empty -> []
|
||||
end,
|
||||
Items1 = case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
get_user_resources(User, Server);
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
Items1 = case {LFrom, LSFrom} of
|
||||
{LTo, LSTo} -> get_user_resources(User, Server);
|
||||
_ -> []
|
||||
end,
|
||||
{result, Items ++ Items1};
|
||||
|
||||
get_sm_items({result, _} = Acc, _From, _To, _Node, _Lang) ->
|
||||
@@ -326,63 +314,39 @@ get_sm_items(empty, From, To, _Node, _Lang) ->
|
||||
{error, ?ERR_NOT_ALLOWED}
|
||||
end.
|
||||
|
||||
is_presence_subscribed(#jid{luser=User, lserver=Server}, #jid{luser=LUser, lserver=LServer}) ->
|
||||
lists:any(fun({roster, _, _, {TUser, TServer, _}, _, S, _, _, _, _}) ->
|
||||
if
|
||||
LUser == TUser, LServer == TServer, S/=none ->
|
||||
true;
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]))
|
||||
orelse User == LUser andalso Server == LServer.
|
||||
|
||||
process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
|
||||
case Type of
|
||||
set ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||
get ->
|
||||
case is_presence_subscribed(From, To) of
|
||||
true ->
|
||||
Host = To#jid.lserver,
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
|
||||
Host,
|
||||
[],
|
||||
[From, To, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_sm_features,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
{result, Features} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO} | ANode],
|
||||
Identity ++
|
||||
features_to_xml(Features)
|
||||
}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end;
|
||||
false ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]}
|
||||
end
|
||||
end.
|
||||
|
||||
get_sm_identity(Acc, _From, #jid{luser = LUser, lserver=LServer}, _Node, _Lang) ->
|
||||
Acc ++ case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
true ->
|
||||
[{xmlelement, "identity", [{"category", "account"},
|
||||
{"type", "registered"}], []}];
|
||||
_ ->
|
||||
[]
|
||||
Host = To#jid.lserver,
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
|
||||
Host,
|
||||
[],
|
||||
[From, To, Node, Lang]),
|
||||
case ejabberd_hooks:run_fold(disco_sm_features,
|
||||
Host,
|
||||
empty,
|
||||
[From, To, Node, Lang]) of
|
||||
{result, Features} ->
|
||||
ANode = case Node of
|
||||
"" -> [];
|
||||
_ -> [{"node", Node}]
|
||||
end,
|
||||
IQ#iq{type = result,
|
||||
sub_el = [{xmlelement, "query",
|
||||
[{"xmlns", ?NS_DISCO_INFO} | ANode],
|
||||
Identity ++
|
||||
lists:map(fun feature_to_xml/1, Features)
|
||||
}]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
||||
end
|
||||
end.
|
||||
|
||||
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
get_sm_features(empty, From, To, _Node, _Lang) ->
|
||||
#jid{luser = LFrom, lserver = LSFrom} = From,
|
||||
@@ -409,13 +373,7 @@ get_user_resources(User, Server) ->
|
||||
|
||||
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
|
||||
|
||||
get_info(_A, Host, Mod, Node, _Lang) when Node == [] ->
|
||||
Module = case Mod of
|
||||
undefined ->
|
||||
?MODULE;
|
||||
_ ->
|
||||
Mod
|
||||
end,
|
||||
get_info(_A, Host, Module, Node, _Lang) when Node == [] ->
|
||||
Serverinfo_fields = get_fields_xml(Host, Module),
|
||||
[{xmlelement, "x",
|
||||
[{"xmlns", ?NS_XDATA}, {"type", "result"}],
|
||||
@@ -429,8 +387,8 @@ get_info(_A, Host, Mod, Node, _Lang) when Node == [] ->
|
||||
++ Serverinfo_fields
|
||||
}];
|
||||
|
||||
get_info(Acc, _, _, _Node, _) ->
|
||||
Acc.
|
||||
get_info(_, _, _, _Node, _) ->
|
||||
[].
|
||||
|
||||
get_fields_xml(Host, Module) ->
|
||||
Fields = gen_mod:get_module_opt(Host, ?MODULE, server_info, []),
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_last.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : jabber:iq:last support (XEP-0012)
|
||||
%%% Purpose : jabber:iq:last support (JEP-0012)
|
||||
%%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_last_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : jabber:iq:last support (XEP-0012)
|
||||
%%% Purpose : jabber:iq:last support (JEP-0012)
|
||||
%%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_muc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : MUC support (XEP-0045)
|
||||
%%% Purpose : MUC support (JEP-0045)
|
||||
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
@@ -336,7 +336,7 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
ErrText = "Access denied by service policy",
|
||||
Err = jlib:make_error_reply(Packet,
|
||||
?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end.
|
||||
|
||||
|
||||
@@ -833,7 +833,7 @@ clean_table_from_bad_node(Node) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
mnesia:transaction(F).
|
||||
|
||||
clean_table_from_bad_node(Node, Host) ->
|
||||
F = fun() ->
|
||||
@@ -848,7 +848,7 @@ clean_table_from_bad_node(Node, Host) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:async_dirty(F).
|
||||
mnesia:transaction(F).
|
||||
|
||||
update_tables(Host) ->
|
||||
update_muc_room_table(Host),
|
||||
|
||||
@@ -571,7 +571,7 @@ handle_event({destroy, Reason}, _StateName, StateData) ->
|
||||
?INFO_MSG("Destroyed MUC room ~s with reason: ~p",
|
||||
[jlib:jid_to_string(StateData#state.jid), Reason]),
|
||||
add_to_log(room_existence, destroyed, StateData),
|
||||
{stop, shutdown, StateData};
|
||||
{stop, Reason, StateData};
|
||||
handle_event(destroy, StateName, StateData) ->
|
||||
?INFO_MSG("Destroyed MUC room ~s",
|
||||
[jlib:jid_to_string(StateData#state.jid)]),
|
||||
@@ -593,21 +593,36 @@ handle_event(_Event, StateName, StateData) ->
|
||||
%% {stop, Reason, Reply, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_sync_event({get_disco_item, JID, Lang}, _From, StateName, StateData) ->
|
||||
case (StateData#state.config)#config.public_list of
|
||||
true ->
|
||||
Reply = get_roomdesc_reply(StateData,
|
||||
get_roomdesc_tail(StateData, Lang)),
|
||||
{reply, Reply, StateName, StateData};
|
||||
_ ->
|
||||
case is_occupant_or_admin(JID, StateData) of
|
||||
FAffiliation = get_affiliation(JID, StateData),
|
||||
FRole = get_role(JID, StateData),
|
||||
Tail =
|
||||
case ((StateData#state.config)#config.public_list == true) orelse
|
||||
(FRole /= none) orelse
|
||||
(FAffiliation == admin) orelse
|
||||
(FAffiliation == owner) of
|
||||
true ->
|
||||
Desc = case (StateData#state.config)#config.public of
|
||||
true ->
|
||||
"";
|
||||
_ ->
|
||||
translate:translate(Lang, "private, ")
|
||||
end,
|
||||
Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0,
|
||||
StateData#state.users),
|
||||
" (" ++ Desc ++ integer_to_list(Len) ++ ")";
|
||||
_ ->
|
||||
" (n/a)"
|
||||
end,
|
||||
Reply = case ((StateData#state.config)#config.public == true) orelse
|
||||
(FRole /= none) orelse
|
||||
(FAffiliation == admin) orelse
|
||||
(FAffiliation == owner) of
|
||||
true ->
|
||||
Reply = get_roomdesc_reply(StateData, get_roomdesc_tail(
|
||||
StateData, Lang)),
|
||||
{reply, Reply, StateName, StateData};
|
||||
{item, get_title(StateData) ++ Tail};
|
||||
_ ->
|
||||
{reply, false, StateName, StateData}
|
||||
end
|
||||
end;
|
||||
false
|
||||
end,
|
||||
{reply, Reply, StateName, StateData};
|
||||
handle_sync_event(get_config, _From, StateName, StateData) ->
|
||||
{reply, {ok, StateData#state.config}, StateName, StateData};
|
||||
handle_sync_event(get_state, _From, StateName, StateData) ->
|
||||
@@ -719,7 +734,7 @@ terminate(Reason, _StateName, StateData) ->
|
||||
ReasonT = case Reason of
|
||||
shutdown -> "You are being removed from the room because"
|
||||
" of a system shutdown";
|
||||
_ -> "Room terminates"
|
||||
_ -> atom_to_list(Reason)
|
||||
end,
|
||||
ItemAttrs = [{"affiliation", "none"}, {"role", "none"}],
|
||||
ReasonEl = {xmlelement, "reason", [], [{xmlcdata, ReasonT}]},
|
||||
@@ -985,18 +1000,6 @@ is_user_online(JID, StateData) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
?DICT:is_key(LJID, StateData#state.users).
|
||||
|
||||
%% Check if the user is occupant of the room, or at least is an admin or owner.
|
||||
is_occupant_or_admin(JID, StateData) ->
|
||||
FAffiliation = get_affiliation(JID, StateData),
|
||||
FRole = get_role(JID, StateData),
|
||||
case (FRole /= none) orelse
|
||||
(FAffiliation == admin) orelse
|
||||
(FAffiliation == owner) of
|
||||
true ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Handle IQ queries of vCard
|
||||
@@ -3326,16 +3329,28 @@ process_iq_disco_items(_From, set, _Lang, _StateData) ->
|
||||
{error, ?ERR_NOT_ALLOWED};
|
||||
|
||||
process_iq_disco_items(From, get, _Lang, StateData) ->
|
||||
case (StateData#state.config)#config.public_list of
|
||||
FAffiliation = get_affiliation(From, StateData),
|
||||
FRole = get_role(From, StateData),
|
||||
case ((StateData#state.config)#config.public_list == true) orelse
|
||||
(FRole /= none) orelse
|
||||
(FAffiliation == admin) orelse
|
||||
(FAffiliation == owner) of
|
||||
true ->
|
||||
{result, get_mucroom_disco_items(StateData), StateData};
|
||||
UList =
|
||||
lists:map(
|
||||
fun({_LJID, Info}) ->
|
||||
Nick = Info#user.nick,
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string(
|
||||
{StateData#state.room,
|
||||
StateData#state.host,
|
||||
Nick})},
|
||||
{"name", Nick}], []}
|
||||
end,
|
||||
?DICT:to_list(StateData#state.users)),
|
||||
{result, UList, StateData};
|
||||
_ ->
|
||||
case is_occupant_or_admin(From, StateData) of
|
||||
true ->
|
||||
{result, get_mucroom_disco_items(StateData), StateData};
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end.
|
||||
|
||||
process_iq_captcha(_From, get, _Lang, _SubEl, _StateData) ->
|
||||
@@ -3357,34 +3372,6 @@ get_title(StateData) ->
|
||||
Name
|
||||
end.
|
||||
|
||||
get_roomdesc_reply(StateData, Tail) ->
|
||||
case ((StateData#state.config)#config.public == true) of
|
||||
true ->
|
||||
{item, get_title(StateData) ++ Tail};
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_roomdesc_tail(StateData, Lang) ->
|
||||
Desc = case (StateData#state.config)#config.public of
|
||||
true ->
|
||||
"";
|
||||
_ ->
|
||||
translate:translate(Lang, "private, ")
|
||||
end,
|
||||
Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0, StateData#state.users),
|
||||
" (" ++ Desc ++ integer_to_list(Len) ++ ")".
|
||||
|
||||
get_mucroom_disco_items(StateData) ->
|
||||
lists:map(
|
||||
fun({_LJID, Info}) ->
|
||||
Nick = Info#user.nick,
|
||||
{xmlelement, "item",
|
||||
[{"jid", jlib:jid_to_string({StateData#state.room,
|
||||
StateData#state.host, Nick})},
|
||||
{"name", Nick}], []}
|
||||
end,
|
||||
?DICT:to_list(StateData#state.users)).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Invitation support
|
||||
|
||||
@@ -222,16 +222,8 @@ parse_options(ServerHost, Opts) ->
|
||||
none -> get_my_ip();
|
||||
Addr -> Addr
|
||||
end,
|
||||
HostName = case gen_mod:get_opt(hostname, Opts, none) of
|
||||
none ->
|
||||
inet_parse:ntoa(IP);
|
||||
HostAddr when is_tuple(HostAddr) ->
|
||||
inet_parse:ntoa(HostAddr);
|
||||
HostNameStr ->
|
||||
HostNameStr
|
||||
end,
|
||||
StreamAddr = [{"jid", MyHost}, {"host", HostName},
|
||||
{"port", integer_to_list(Port)}],
|
||||
StrIP = inet_parse:ntoa(IP),
|
||||
StreamAddr = [{"jid", MyHost}, {"host", StrIP}, {"port", integer_to_list(Port)}],
|
||||
#state{myhost = MyHost,
|
||||
serverhost = ServerHost,
|
||||
name = Name,
|
||||
|
||||
+210
-385
@@ -62,9 +62,7 @@
|
||||
-export([presence_probe/3,
|
||||
in_subscription/6,
|
||||
out_subscription/4,
|
||||
on_user_offline/3,
|
||||
remove_user/2,
|
||||
feature_check_packet/6,
|
||||
disco_local_identity/5,
|
||||
disco_local_features/5,
|
||||
disco_local_items/5,
|
||||
@@ -183,8 +181,9 @@ init([ServerHost, Opts]) ->
|
||||
pubsub_index:init(Host, ServerHost, Opts),
|
||||
ets:new(gen_mod:get_module_proc(Host, config), [set, named_table]),
|
||||
ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
|
||||
ets:new(gen_mod:get_module_proc(Host, last_items), [set, named_table]),
|
||||
ets:new(gen_mod:get_module_proc(ServerHost, last_items), [set, named_table]),
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
mnesia:create_table(pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]),
|
||||
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
|
||||
ets:insert(gen_mod:get_module_proc(Host, config), {nodetree, NodeTree}),
|
||||
ets:insert(gen_mod:get_module_proc(Host, config), {plugins, Plugins}),
|
||||
@@ -197,7 +196,6 @@ init([ServerHost, Opts]) ->
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
|
||||
ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
|
||||
@@ -210,7 +208,6 @@ init([ServerHost, Opts]) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, IQDisc),
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
true ->
|
||||
ejabberd_hooks:add(feature_check_packet, ServerHost, ?MODULE, feature_check_packet, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
|
||||
ejabberd_hooks:add(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
|
||||
ejabberd_hooks:add(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
|
||||
@@ -517,13 +514,18 @@ send_loop(State) ->
|
||||
%% this makes that hack only work for local domain by now
|
||||
if not State#state.ignore_pep_from_offline ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
case mod_caps:get_caps({User, Server, Resource}) of
|
||||
nothing ->
|
||||
%% we don't have caps, no need to handle PEP items
|
||||
ok;
|
||||
_ ->
|
||||
case catch ejabberd_c2s:get_subscribed(Pid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:foreach(
|
||||
fun({U, S, R}) ->
|
||||
case S of
|
||||
ServerHost -> %% local contacts
|
||||
case user_resources(U, S) of
|
||||
case ejabberd_sm:get_user_resources(U, S) of
|
||||
[] -> %% offline
|
||||
PeerJID = jlib:make_jid(U, S, R),
|
||||
self() ! {presence, User, Server, [Resource], PeerJID};
|
||||
@@ -538,7 +540,8 @@ send_loop(State) ->
|
||||
end, Contacts);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
@@ -546,35 +549,55 @@ send_loop(State) ->
|
||||
{presence, User, Server, Resources, JID} ->
|
||||
%% get resources caps and check if processing is needed
|
||||
spawn(fun() ->
|
||||
Host = State#state.host,
|
||||
Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
|
||||
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) ->
|
||||
{HasCaps, ResourcesCaps} = lists:foldl(fun(Resource, {R, L}) ->
|
||||
case mod_caps:get_caps({User, Server, Resource}) of
|
||||
nothing -> {R, L};
|
||||
Caps -> {true, [{Resource, Caps} | L]}
|
||||
end
|
||||
end, {false, []}, Resources),
|
||||
case HasCaps of
|
||||
true ->
|
||||
Host = State#state.host,
|
||||
ServerHost = State#state.server_host,
|
||||
Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
|
||||
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) ->
|
||||
case get_option(Options, send_last_published_item) of
|
||||
on_sub_and_presence ->
|
||||
lists:foreach(
|
||||
fun(Resource) ->
|
||||
LJID = {User, Server, Resource},
|
||||
Subscribed = case get_option(Options, access_model) of
|
||||
open -> true;
|
||||
presence -> true;
|
||||
whitelist -> false; % subscribers are added manually
|
||||
authorize -> false; % likewise
|
||||
roster ->
|
||||
Grps = get_option(Options, roster_groups_allowed, []),
|
||||
{OU, OS, _} = Owner,
|
||||
element(2, get_roster_info(OU, OS, LJID, Grps))
|
||||
end,
|
||||
if Subscribed ->
|
||||
send_items(Owner, Node, NodeId, Type, LJID, last);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, Resources);
|
||||
lists:foreach(fun({Resource, Caps}) ->
|
||||
CapsNotify = case catch mod_caps:get_features(ServerHost, Caps) of
|
||||
Features when is_list(Features) -> lists:member(node_to_string(Node) ++ "+notify", Features);
|
||||
_ -> false
|
||||
end,
|
||||
case CapsNotify of
|
||||
true ->
|
||||
LJID = {User, Server, Resource},
|
||||
Subscribed = case get_option(Options, access_model) of
|
||||
open -> true;
|
||||
presence -> true;
|
||||
whitelist -> false; % subscribers are added manually
|
||||
authorize -> false; % likewise
|
||||
roster ->
|
||||
Grps = get_option(Options, roster_groups_allowed, []),
|
||||
{OU, OS, _} = Owner,
|
||||
element(2, get_roster_info(OU, OS, LJID, Grps))
|
||||
end,
|
||||
if Subscribed ->
|
||||
send_items(Owner, Node, NodeId, Type, LJID, last);
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, ResourcesCaps);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, tree_action(Host, get_nodes, [Owner, JID]))
|
||||
end),
|
||||
end, tree_action(Host, get_nodes, [Owner, JID]));
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
send_loop(State);
|
||||
stop ->
|
||||
ok
|
||||
@@ -774,19 +797,17 @@ remove_user(User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Entity = jlib:make_jid(LUser, LServer, ""),
|
||||
Host = host(LServer),
|
||||
HomeTreeBase = string_to_node("/home/"++LServer++"/"++LUser),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(PType) ->
|
||||
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
|
||||
lists:foreach(fun
|
||||
({#pubsub_node{id = NodeId}, _, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all])
|
||||
({#pubsub_node{id = NodeId}, subscribed, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
{result, Affiliations} = node_action(Host, PType, get_entity_affiliations, [Host, Entity]),
|
||||
lists:foreach(fun
|
||||
({#pubsub_node{nodeid = {H, N}, parents = []}, owner}) -> delete_node(H, N, Entity);
|
||||
({#pubsub_node{nodeid = {H, N}, type = "hometree"}, owner}) when N == HomeTreeBase -> delete_node(H, N, Entity);
|
||||
({#pubsub_node{id = NodeId}, publisher}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none]);
|
||||
(_) -> ok
|
||||
({#pubsub_node{nodeid = {H, N}}, owner}) -> delete_node(H, N, Entity);
|
||||
({#pubsub_node{id = NodeId}, _}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none])
|
||||
end, Affiliations)
|
||||
end, plugins(Host))
|
||||
end).
|
||||
@@ -857,7 +878,6 @@ terminate(_Reason, #state{host = Host,
|
||||
ejabberd_router:unregister_route(Host),
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
true ->
|
||||
ejabberd_hooks:delete(feature_check_packet, ServerHost, ?MODULE, feature_check_packet, 75),
|
||||
ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
|
||||
ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
|
||||
ejabberd_hooks:delete(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
|
||||
@@ -866,7 +886,6 @@ terminate(_Reason, #state{host = Host,
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
|
||||
ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
|
||||
@@ -1008,10 +1027,14 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
|
||||
end.
|
||||
|
||||
command_disco_info(_Host, <<?NS_COMMANDS>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-list"}], []},
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-list"}],
|
||||
[]},
|
||||
{result, [IdentityEl]};
|
||||
command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-node"}], []},
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-node"}],
|
||||
[]},
|
||||
FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{result, [IdentityEl, FeaturesEl]}.
|
||||
|
||||
@@ -1092,20 +1115,15 @@ iq_disco_info(Host, SNode, From, Lang) ->
|
||||
|
||||
iq_disco_items(Host, [], From) ->
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
|
||||
Attrs =
|
||||
case get_option(Options, title) of
|
||||
false ->
|
||||
[{"jid", Host} |nodeAttr(SubNode)];
|
||||
Title ->
|
||||
[{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
|
||||
end,
|
||||
{xmlelement, "item", Attrs, []}
|
||||
end, Nodes)};
|
||||
Other ->
|
||||
Other
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, Nodes)};
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
iq_disco_items(Host, ?NS_COMMANDS, _From) ->
|
||||
%% TODO: support localization of this string
|
||||
@@ -1128,21 +1146,16 @@ iq_disco_items(Host, Item, From) ->
|
||||
_ -> []
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
|
||||
Attrs =
|
||||
case get_option(Options, title) of
|
||||
false ->
|
||||
[{"jid", Host} |nodeAttr(SubNode)];
|
||||
Title ->
|
||||
[{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
|
||||
end,
|
||||
{xmlelement, "item", Attrs, []}
|
||||
end, tree_call(Host, get_subnodes, [Host, Node, From])),
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_call(Host, get_subnodes, [Host, Node, From])),
|
||||
Items = lists:map(
|
||||
fun(#pubsub_item{itemid = {RN, _}}) ->
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
fun(#pubsub_item{itemid = {RN, _}}) ->
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
{result, Nodes ++ Items}
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
@@ -1443,7 +1456,7 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
true ->
|
||||
case node_call(Type, get_affiliation, [NodeID, Owner]) of
|
||||
{result, owner} ->
|
||||
node_call(Type, get_node_subscriptions, [NodeID]);
|
||||
node_call(Type, get_node_subscriptions, [NodeID]);
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end;
|
||||
@@ -1453,10 +1466,10 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, {N, Subscriptions}} ->
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
#adhoc_response{};
|
||||
Err ->
|
||||
Err
|
||||
@@ -1736,17 +1749,9 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
{result, true} ->
|
||||
case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of
|
||||
{ok, NodeId} ->
|
||||
ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, Owner]),
|
||||
SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree],
|
||||
case node_call(Type, create_node, [NodeId, Owner]) of
|
||||
{result, Result} -> {result, {NodeId, SubsByDepth, Result}};
|
||||
Error -> Error
|
||||
end;
|
||||
node_call(Type, create_node, [NodeId, Owner]);
|
||||
{error, {virtual, NodeId}} ->
|
||||
case node_call(Type, create_node, [NodeId, Owner]) of
|
||||
{result, Result} -> {result, {NodeId, [], Result}};
|
||||
Error -> Error
|
||||
end;
|
||||
node_call(Type, create_node, [NodeId, Owner]);
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
@@ -1758,15 +1763,20 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
[{xmlelement, "create", nodeAttr(Node),
|
||||
[]}]}],
|
||||
case transaction(CreateNode, transaction) of
|
||||
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
|
||||
{result, {Result, broadcast}} ->
|
||||
%%Lang = "en", %% TODO: fix
|
||||
%%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
%%broadcast_publish_item(Host, Node, uniqid(), Owner,
|
||||
%% [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
|
||||
%% [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NMI),
|
||||
%% ?XFIELD("jid-single", "Node Creator", "creator", jlib:jid_to_string(OwnerKey))]}]),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_NodeId, _SubsByDepth, default}} ->
|
||||
{result, default} ->
|
||||
{result, Reply};
|
||||
{result, {_NodeId, _SubsByDepth, Result}} ->
|
||||
{result, Result} ->
|
||||
{result, Result};
|
||||
Error ->
|
||||
%% in case we change transaction to sync_dirty...
|
||||
@@ -2056,7 +2066,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, Options, Removed, ItemId, jlib:jid_tolower(Publisher), BroadcastPayload),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Payload),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
@@ -2066,14 +2076,14 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Payload),
|
||||
{result, Reply};
|
||||
{result, {TNode, {Result, Removed}}} ->
|
||||
NodeId = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Payload),
|
||||
{result, Result};
|
||||
{result, {_, default}} ->
|
||||
{result, Reply};
|
||||
@@ -2312,10 +2322,10 @@ send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
undefined ->
|
||||
send_items(Host, Node, NodeId, Type, LJID, 1);
|
||||
LastItem ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
{ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
Stanza = event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls([LastItem])}], ModifNow, ModifUSR),
|
||||
itemsEls([LastItem])}], ModifNow, ModifLjid),
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza)
|
||||
end;
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
@@ -2332,10 +2342,10 @@ send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
end,
|
||||
Stanza = case ToSend of
|
||||
[LastItem] ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
{ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls(ToSend)}], ModifNow, ModifUSR);
|
||||
itemsEls(ToSend)}], ModifNow, ModifLjid);
|
||||
_ ->
|
||||
event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
@@ -2446,11 +2456,6 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
|
||||
Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
|
||||
case lists:member(Owner, Owners) of
|
||||
true ->
|
||||
OwnerJID = jlib:make_jid(Owner),
|
||||
FilteredEntities = case Owners of
|
||||
[Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID];
|
||||
_ -> Entities
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({JID, Affiliation}) ->
|
||||
node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
|
||||
@@ -2471,7 +2476,7 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, FilteredEntities),
|
||||
end, Entities),
|
||||
{result, []};
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
@@ -2875,66 +2880,15 @@ node_to_deliver(LJID, NodeOptions) ->
|
||||
presence_can_deliver(LJID, PresenceDelivery).
|
||||
|
||||
presence_can_deliver(_, false) -> true;
|
||||
presence_can_deliver({User, Server, Resource}, true) ->
|
||||
presence_can_deliver({User, Server, _}, true) ->
|
||||
case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
|
||||
[] -> false;
|
||||
Ss ->
|
||||
lists:foldl(fun(_, true) -> true;
|
||||
({session, _, _ , _, undefined, _}, _Acc) -> false;
|
||||
({session, _, {_, _, R}, _, _Priority, _}, _Acc) ->
|
||||
case Resource of
|
||||
[] -> true;
|
||||
R -> true;
|
||||
_ -> false
|
||||
end
|
||||
lists:foldl(fun({session, _, _, _, undefined, _}, Acc) -> Acc;
|
||||
({session, _, _, _, _Priority, _}, _Acc) -> true
|
||||
end, false, Ss)
|
||||
end.
|
||||
|
||||
state_can_deliver({U, S, R}, []) -> [{U, S, R}];
|
||||
state_can_deliver({U, S, R}, SubOptions) ->
|
||||
%% Check SubOptions for 'show_values'
|
||||
case lists:keysearch('show_values', 1, SubOptions) of
|
||||
%% If not in suboptions, item can be delivered, case doesn't apply
|
||||
false -> [{U, S, R}];
|
||||
%% If in a suboptions ...
|
||||
{_, {_, ShowValues}} ->
|
||||
%% Get subscriber resources
|
||||
Resources = case R of
|
||||
%% If the subscriber JID is a bare one, get all its resources
|
||||
[] -> user_resources(U, S);
|
||||
%% If the subscriber JID is a full one, use its resource
|
||||
R -> [R]
|
||||
end,
|
||||
%% For each resource, test if the item is allowed to be delivered
|
||||
%% based on resource state
|
||||
lists:foldl(
|
||||
fun(Resource, Acc) ->
|
||||
get_resource_state({U, S, Resource}, ShowValues, Acc)
|
||||
end, [], Resources)
|
||||
end.
|
||||
|
||||
get_resource_state({U, S, R}, ShowValues, JIDs) ->
|
||||
%% Get user session PID
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
%% If no PID, item can be delivered
|
||||
none -> lists:append([{U, S, R}], JIDs);
|
||||
%% If PID ...
|
||||
Pid ->
|
||||
%% Get user resource state
|
||||
%% TODO : add a catch clause
|
||||
Show = case ejabberd_c2s:get_presence(Pid) of
|
||||
{_, _, "available", _} -> "online";
|
||||
{_, _, State, _} -> State
|
||||
end,
|
||||
%% Is current resource state listed in 'show-values' suboption ?
|
||||
case lists:member(Show, ShowValues) of %andalso Show =/= "online" of
|
||||
%% If yes, item can be delivered
|
||||
true -> lists:append([{U, S, R}], JIDs);
|
||||
%% If no, item can't be delivered
|
||||
false -> JIDs
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (Payload) -> int()
|
||||
%% Payload = term()
|
||||
%% @doc <p>Count occurence of XML elements in payload.</p>
|
||||
@@ -2949,9 +2903,9 @@ payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count).
|
||||
event_stanza(Els) ->
|
||||
event_stanza_withmoreels(Els, []).
|
||||
|
||||
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
|
||||
event_stanza_with_delay(Els, ModifNow, ModifLjid) ->
|
||||
DateTime = calendar:now_to_datetime(ModifNow),
|
||||
MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifUSR, "")],
|
||||
MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifLjid, "")],
|
||||
event_stanza_withmoreels(Els, MoreEls).
|
||||
|
||||
event_stanza_withmoreels(Els, MoreEls) ->
|
||||
@@ -2960,7 +2914,8 @@ event_stanza_withmoreels(Els, MoreEls) ->
|
||||
|
||||
%%%%%% broadcast functions
|
||||
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, From, Payload) ->
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _From, Payload) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, none, true, "items", ItemEls)
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
Content = case get_option(NodeOptions, deliver_payloads) of
|
||||
@@ -2970,7 +2925,7 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F
|
||||
Stanza = event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
[{xmlelement, "item", itemAttr(ItemId), Content}]}]),
|
||||
broadcast_stanza(Host, From, Node, NodeId, Type,
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubsByDepth, items, Stanza, true),
|
||||
case Removed of
|
||||
[] ->
|
||||
@@ -2998,6 +2953,7 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) ->
|
||||
broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) ->
|
||||
{result, false};
|
||||
broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, ForceNotify, "retract", RetractEls)
|
||||
case (get_option(NodeOptions, notify_retract) or ForceNotify) of
|
||||
true ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
@@ -3016,6 +2972,7 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot
|
||||
end.
|
||||
|
||||
broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, false, "purge", [])
|
||||
case get_option(NodeOptions, notify_retract) of
|
||||
true ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
@@ -3034,10 +2991,11 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
|
||||
end.
|
||||
|
||||
broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_delete, false, "delete", [])
|
||||
case get_option(NodeOptions, notify_delete) of
|
||||
true ->
|
||||
case SubsByDepth of
|
||||
[] ->
|
||||
[] ->
|
||||
{result, false};
|
||||
_ ->
|
||||
Stanza = event_stanza(
|
||||
@@ -3051,14 +3009,8 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
{result, false}
|
||||
end.
|
||||
|
||||
broadcast_created_node(_, _, _, _, _, []) ->
|
||||
{result, false};
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true),
|
||||
{result, true}.
|
||||
|
||||
broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_config, false, "items", ConfigEls)
|
||||
case get_option(NodeOptions, notify_config) of
|
||||
true ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
@@ -3075,7 +3027,7 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubsByDepth, nodes, Stanza, false),
|
||||
{result, true};
|
||||
_ ->
|
||||
_ ->
|
||||
{result, false}
|
||||
end;
|
||||
_ ->
|
||||
@@ -3111,7 +3063,7 @@ get_options_for_subs(NodeID, Subs) ->
|
||||
Acc
|
||||
end, [], Subs).
|
||||
|
||||
broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
NotificationType = get_option(NodeOptions, notification_type, headline),
|
||||
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
|
||||
From = service_jid(Host),
|
||||
@@ -3130,66 +3082,63 @@ broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTy
|
||||
[LJID]
|
||||
end,
|
||||
%% Determine if the stanza should have SHIM ('SubID' and 'name') headers
|
||||
StanzaToSend = case {SHIM, SubIDs} of
|
||||
{false, _} ->
|
||||
Stanza;
|
||||
%% If there's only one SubID, don't add it
|
||||
{true, [_]} ->
|
||||
add_shim_headers(Stanza, collection_shim(NodeName));
|
||||
{true, SubIDs} ->
|
||||
add_shim_headers(Stanza, lists:append(collection_shim(NodeName), subid_shim(SubIDs)))
|
||||
end,
|
||||
StanzaToSend = case SHIM of
|
||||
true ->
|
||||
Headers = lists:append(collection_shim(NodeName), subid_shim(SubIDs)),
|
||||
add_headers(Stanza, Headers);
|
||||
false ->
|
||||
Stanza
|
||||
end,
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend)
|
||||
end, LJIDs)
|
||||
end, SubIDsByJID).
|
||||
|
||||
broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza({LUser, LServer, LResource}, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
|
||||
ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend)
|
||||
end, LJIDs)
|
||||
end, SubIDsByJID),
|
||||
%% Handles implicit presence subscriptions
|
||||
SenderResource = case LResource of
|
||||
[] ->
|
||||
case user_resources(LUser, LServer) of
|
||||
[Resource|_] -> Resource;
|
||||
_ -> ""
|
||||
end;
|
||||
_ ->
|
||||
LResource
|
||||
end,
|
||||
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
Stanza = case get_option(NodeOptions, notification_type, headline) of
|
||||
normal -> BaseStanza;
|
||||
MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType))
|
||||
end,
|
||||
%% set the from address on the notification to the bare JID of the account owner
|
||||
%% Also, add "replyto" if entity has presence subscription to the account owner
|
||||
%% See XEP-0163 1.1 section 4.3.1
|
||||
Sender = jlib:make_jid(LUser, LServer, ""),
|
||||
ReplyTo = jlib:jid_to_string(Publisher),
|
||||
StanzaToSend = add_extended_headers(Stanza, extended_headers([ReplyTo])),
|
||||
case catch ejabberd_c2s:get_subscribed(C2SPid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:foreach(fun({U, S, _}) ->
|
||||
spawn(fun() ->
|
||||
case lists:member(S, ?MYHOSTS) of
|
||||
true ->
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(Sender, jlib:make_jid(To), StanzaToSend)
|
||||
end, [{U, S, R} || R <- user_resources(U, S)]);
|
||||
false ->
|
||||
ejabberd_router:route(Sender, jlib:make_jid(U, S, ""), StanzaToSend)
|
||||
end
|
||||
end)
|
||||
end, Contacts);
|
||||
case Host of
|
||||
{LUser, LServer, LResource} ->
|
||||
SenderResource = case LResource of
|
||||
[] ->
|
||||
case user_resources(LUser, LServer) of
|
||||
[Resource|_] -> Resource;
|
||||
_ -> ""
|
||||
end;
|
||||
_ ->
|
||||
LResource
|
||||
end,
|
||||
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
%% set the from address on the notification to the bare JID of the account owner
|
||||
%% Also, add "replyto" if entity has presence subscription to the account owner
|
||||
%% See XEP-0163 1.1 section 4.3.1
|
||||
Sender = jlib:make_jid(LUser, LServer, ""),
|
||||
%%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used
|
||||
case catch ejabberd_c2s:get_subscribed(C2SPid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:foreach(fun({U, S, _}) ->
|
||||
spawn(fun() ->
|
||||
LJIDs = lists:foldl(fun(R, Acc) ->
|
||||
LJID = {U, S, R},
|
||||
case is_caps_notify(LServer, Node, LJID) of
|
||||
true -> [LJID | Acc];
|
||||
false -> Acc
|
||||
end
|
||||
end, [], user_resources(U, S)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(Sender, jlib:make_jid(To), Stanza)
|
||||
end, LJIDs)
|
||||
end)
|
||||
end, Contacts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
_ ->
|
||||
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza])
|
||||
end;
|
||||
broadcast_stanza(Host, _Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
|
||||
ok
|
||||
end.
|
||||
|
||||
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
NodesToDeliver = fun(Depth, Node, Subs, Acc) ->
|
||||
@@ -3200,25 +3149,20 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
NodeOptions = Node#pubsub_node.options,
|
||||
lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) ->
|
||||
case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
|
||||
true ->
|
||||
%% If is to deliver :
|
||||
case state_can_deliver(LJID, SubOptions) of
|
||||
[] -> {JIDs, Recipients};
|
||||
JIDsToDeliver ->
|
||||
lists:foldl(
|
||||
fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
|
||||
case lists:member(JIDToDeliver, JIDs) of
|
||||
true ->
|
||||
%% If is to deliver :
|
||||
case lists:member(LJID, JIDs) of
|
||||
%% check if the JIDs co-accumulator contains the Subscription Jid,
|
||||
false ->
|
||||
false ->
|
||||
%% - if not,
|
||||
%% - add the Jid to JIDs list co-accumulator ;
|
||||
%% - create a tuple of the Jid, NodeId, and SubID (as list),
|
||||
%% and add the tuple to the Recipients list co-accumulator
|
||||
{[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]};
|
||||
true ->
|
||||
{[LJID | JIDs], [{LJID, NodeName, [SubID]} | Recipients]};
|
||||
true ->
|
||||
%% - if the JIDs co-accumulator contains the Jid
|
||||
%% get the tuple containing the Jid from the Recipient list co-accumulator
|
||||
{_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
|
||||
{_, {LJID, NodeName1, SubIDs}} = lists:keysearch(LJID, 1, Recipients),
|
||||
%% delete the tuple from the Recipients list
|
||||
% v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
|
||||
% v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, NodeId1, [SubID | SubIDs]}),
|
||||
@@ -3227,25 +3171,39 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
% v1.1 : {JIDs, lists:append(Recipients1, [{LJID, NodeId1, lists:append(SubIDs, [SubID])}])}
|
||||
% v1.2 : {JIDs, [{LJID, NodeId1, [SubID | SubIDs]} | Recipients1]}
|
||||
% v2: {JIDs, Recipients1}
|
||||
{JIDsAcc, lists:keyreplace(JIDToDeliver, 1, RecipientsAcc, {JIDToDeliver, NodeName1, [SubID | SubIDs]})}
|
||||
end
|
||||
end, {JIDs, Recipients}, JIDsToDeliver)
|
||||
end;
|
||||
{JIDs, lists:keyreplace(LJID, 1, Recipients, {LJID, NodeName1, [SubID | SubIDs]})}
|
||||
end;
|
||||
false ->
|
||||
{JIDs, Recipients}
|
||||
end
|
||||
end, Acc, Subs)
|
||||
end,
|
||||
DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) ->
|
||||
DepthsToDeliver = fun({Depth, SubsByNode}, Acc) ->
|
||||
lists:foldl(fun({Node, Subs}, Acc2) ->
|
||||
NodesToDeliver(Depth, Node, Subs, Acc2)
|
||||
end, Acc1, SubsByNode)
|
||||
end, Acc, SubsByNode)
|
||||
end,
|
||||
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
|
||||
JIDSubs.
|
||||
|
||||
%% If we don't know the resource, just pick first if any
|
||||
%% If no resource available, check if caps anyway (remote online)
|
||||
user_resources(User, Server) ->
|
||||
ejabberd_sm:get_user_resources(User, Server).
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] -> mod_caps:get_user_resources(User, Server);
|
||||
Rs -> Rs
|
||||
end.
|
||||
|
||||
is_caps_notify(Host, Node, LJID) ->
|
||||
case mod_caps:get_caps(LJID) of
|
||||
nothing ->
|
||||
false;
|
||||
Caps ->
|
||||
case catch mod_caps:get_features(Host, Caps) of
|
||||
Features when is_list(Features) -> lists:member(node_to_string(Node) ++ "+notify", Features);
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%%%%%%% Configuration handling
|
||||
|
||||
@@ -3386,7 +3344,6 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
|
||||
?LISTM_CONFIG_FIELD("Roster groups allowed to subscribe", roster_groups_allowed, Groups),
|
||||
?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
|
||||
[publishers, subscribers, open]),
|
||||
?BOOL_CONFIG_FIELD("Purge all items when the relevant publisher goes offline", purge_offline),
|
||||
?ALIST_CONFIG_FIELD("Specify the event message type", notification_type,
|
||||
[headline, normal]),
|
||||
?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
|
||||
@@ -3530,8 +3487,6 @@ set_xoption(Host, [{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts)
|
||||
?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
|
||||
set_xoption(Host, [{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
|
||||
?SET_BOOL_XOPT(presence_based_delivery, Val);
|
||||
set_xoption(Host, [{"pubsub#purge_offline", [Val]} | Opts], NewOpts) ->
|
||||
?SET_BOOL_XOPT(purge_offline, Val);
|
||||
set_xoption(Host, [{"pubsub#title", Value} | Opts], NewOpts) ->
|
||||
?SET_STRING_XOPT(title, Value);
|
||||
set_xoption(Host, [{"pubsub#type", Value} | Opts], NewOpts) ->
|
||||
@@ -3566,18 +3521,18 @@ is_last_item_cache_enabled(Host) ->
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
set_cached_item({_, ServerHost, _}, NodeId, ItemId, Publisher, Payload) ->
|
||||
set_cached_item(ServerHost, NodeId, ItemId, Publisher, Payload);
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload) ->
|
||||
set_cached_item({_, ServerHost, _}, NodeId, ItemId, Payload) ->
|
||||
set_cached_item(ServerHost, NodeId, ItemId, Payload);
|
||||
set_cached_item(Host, NodeId, ItemId, Payload) ->
|
||||
case is_last_item_cache_enabled(Host) of
|
||||
true -> mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, {now(), jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, Payload});
|
||||
true -> ets:insert(gen_mod:get_module_proc(Host, last_items), {NodeId, {ItemId, Payload}});
|
||||
_ -> ok
|
||||
end.
|
||||
unset_cached_item({_, ServerHost, _}, NodeId) ->
|
||||
unset_cached_item(ServerHost, NodeId);
|
||||
unset_cached_item(Host, NodeId) ->
|
||||
case is_last_item_cache_enabled(Host) of
|
||||
true -> mnesia:dirty_delete({pubsub_last_item, NodeId});
|
||||
true -> ets:delete(gen_mod:get_module_proc(Host, last_items), NodeId);
|
||||
_ -> ok
|
||||
end.
|
||||
get_cached_item({_, ServerHost, _}, NodeId) ->
|
||||
@@ -3585,10 +3540,9 @@ get_cached_item({_, ServerHost, _}, NodeId) ->
|
||||
get_cached_item(Host, NodeId) ->
|
||||
case is_last_item_cache_enabled(Host) of
|
||||
true ->
|
||||
case mnesia:dirty_read({pubsub_last_item, NodeId}) of
|
||||
[{pubsub_last_item, NodeId, ItemId, Creation, Payload}] ->
|
||||
#pubsub_item{itemid = {ItemId, NodeId}, payload = Payload,
|
||||
creation = Creation, modification = Creation};
|
||||
case catch ets:lookup(gen_mod:get_module_proc(Host, last_items), NodeId) of
|
||||
[{NodeId, {ItemId, Payload}}] ->
|
||||
#pubsub_item{itemid = {ItemId, NodeId}, payload = Payload};
|
||||
_ ->
|
||||
undefined
|
||||
end;
|
||||
@@ -3806,14 +3760,8 @@ add_message_type(XmlEl, _Type) ->
|
||||
%% "[SHIM Headers] SHOULD be included after the event notification information
|
||||
%% (i.e., as the last child of the <message/> stanza)".
|
||||
|
||||
add_shim_headers(Stanza, HeaderEls) ->
|
||||
add_headers(Stanza, "headers", ?NS_SHIM, HeaderEls).
|
||||
|
||||
add_extended_headers(Stanza, HeaderEls) ->
|
||||
add_headers(Stanza, "addresses", ?NS_ADDRESS, HeaderEls).
|
||||
|
||||
add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) ->
|
||||
HeaderEl = {xmlelement, HeaderName, [{"xmlns", HeaderNS}], HeaderEls},
|
||||
add_headers({xmlelement, Name, Attrs, Els}, HeaderEls) ->
|
||||
HeaderEl = {xmlelement, "headers", [{"xmlns", ?NS_SHIM}], HeaderEls},
|
||||
{xmlelement, Name, Attrs, lists:append(Els, [HeaderEl])}.
|
||||
|
||||
%% Removed multiple <header name=Collection>Foo</header/> elements
|
||||
@@ -3837,126 +3785,3 @@ subid_shim(SubIDs) ->
|
||||
[{xmlelement, "header", [{"name", "SubID"}],
|
||||
[{xmlcdata, SubID}]} || SubID <- SubIDs].
|
||||
|
||||
%% The argument is a list of Jids because this function could be used
|
||||
%% with the 'pubsub#replyto' (type=jid-multi) node configuration.
|
||||
|
||||
extended_headers(Jids) ->
|
||||
[{xmlelement, "address", [{"type", "replyto"}, {"jid", Jid}], []} || Jid <- Jids].
|
||||
|
||||
|
||||
feature_check_packet(allow, _User, Server, Pres,
|
||||
{#jid{lserver = LServer}, _To,
|
||||
{xmlelement, "message", MsgAttrs, _} = El}, in) ->
|
||||
Host = host(Server),
|
||||
case LServer of
|
||||
%% If the sender Server equals Host, the message comes from the Pubsub server
|
||||
Host ->
|
||||
allow;
|
||||
%% Else, the message comes from PEP
|
||||
_ ->
|
||||
case xml:get_subtag(El, "event") of
|
||||
{xmlelement, _, Attrs, _} = EventEl ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_PUBSUB_EVENT ->
|
||||
case xml:get_attr_s("type", MsgAttrs) of
|
||||
"error" ->
|
||||
%% Filter error-repsonse of PEP message
|
||||
%% to avoid routing it to client
|
||||
deny;
|
||||
_ when Pres /= undefined ->
|
||||
%% Yes, sometimes Pres = undefined,
|
||||
%% very rare though.
|
||||
%% Seems like this is a bug: should
|
||||
%% be fixed in ejabberd_s2s.erl
|
||||
Feature = xml:get_path_s(
|
||||
EventEl, [{elem, "items"},
|
||||
{attr, "node"}]),
|
||||
case is_feature_supported(Pres, Feature) of
|
||||
true ->
|
||||
allow;
|
||||
false ->
|
||||
deny
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
end
|
||||
end;
|
||||
feature_check_packet(Acc, _User, _Server, _Pres, _Packet, _Direction) ->
|
||||
Acc.
|
||||
|
||||
is_feature_supported({xmlelement, "presence", _, Els}, Feature) ->
|
||||
case mod_caps:read_caps(Els) of
|
||||
nothing -> false;
|
||||
Caps -> lists:member(Feature ++ "+notify", mod_caps:get_features(Caps))
|
||||
end.
|
||||
|
||||
on_user_offline(_, JID, _) ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] -> purge_offline({User, Server, Resource});
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
purge_offline({User, Server, _} = LJID) ->
|
||||
Host = host(element(2, LJID)),
|
||||
Plugins = plugins(Host),
|
||||
Result = lists:foldl(
|
||||
fun(Type, {Status, Acc}) ->
|
||||
case lists:member("retrieve-affiliations", features(Type)) of
|
||||
false ->
|
||||
{{error, extended_error('feature-not-implemented', unsupported, "retrieve-affiliations")}, Acc};
|
||||
true ->
|
||||
{result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, LJID]),
|
||||
{Status, [Affiliations|Acc]}
|
||||
end
|
||||
end, {ok, []}, Plugins),
|
||||
case Result of
|
||||
{ok, Affiliations} ->
|
||||
lists:foreach(
|
||||
fun({#pubsub_node{nodeid = {_, NodeId}, options = Options, type = Type}, Affiliation})
|
||||
when Affiliation == 'owner' orelse Affiliation == 'publisher' ->
|
||||
Action = fun(#pubsub_node{type = NType, id = NodeIdx}) ->
|
||||
node_call(NType, get_items, [NodeIdx, service_jid(Host)])
|
||||
end,
|
||||
case transaction(Host, NodeId, Action, sync_dirty) of
|
||||
{result, {_, []}} ->
|
||||
true;
|
||||
{result, {_, Items}} ->
|
||||
Features = features(Type),
|
||||
case
|
||||
{lists:member("retract-items", Features),
|
||||
lists:member("persistent-items", Features),
|
||||
get_option(Options, persist_items),
|
||||
get_option(Options, purge_offline)}
|
||||
of
|
||||
{true, true, true, true} ->
|
||||
ForceNotify = get_option(Options, notify_retract),
|
||||
lists:foreach(
|
||||
fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, Modification}}) ->
|
||||
case Modification of
|
||||
{User, Server, _} ->
|
||||
delete_item(Host, NodeId, LJID, ItemId, ForceNotify);
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
(_) ->
|
||||
true
|
||||
end, Items);
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
(_) ->
|
||||
true
|
||||
end, lists:usort(lists:flatten(Affiliations)));
|
||||
{Error, _} ->
|
||||
?DEBUG("on_user_offline ~p", [Error])
|
||||
end.
|
||||
|
||||
+213
-389
@@ -62,9 +62,7 @@
|
||||
-export([presence_probe/3,
|
||||
in_subscription/6,
|
||||
out_subscription/4,
|
||||
on_user_offline/3,
|
||||
remove_user/2,
|
||||
feature_check_packet/6,
|
||||
disco_local_identity/5,
|
||||
disco_local_features/5,
|
||||
disco_local_items/5,
|
||||
@@ -183,8 +181,9 @@ init([ServerHost, Opts]) ->
|
||||
pubsub_index:init(Host, ServerHost, Opts),
|
||||
ets:new(gen_mod:get_module_proc(Host, config), [set, named_table]),
|
||||
ets:new(gen_mod:get_module_proc(ServerHost, config), [set, named_table]),
|
||||
ets:new(gen_mod:get_module_proc(Host, last_items), [set, named_table]),
|
||||
ets:new(gen_mod:get_module_proc(ServerHost, last_items), [set, named_table]),
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
mnesia:create_table(pubsub_last_item, [{ram_copies, [node()]}, {attributes, record_info(fields, pubsub_last_item)}]),
|
||||
mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
|
||||
ets:insert(gen_mod:get_module_proc(Host, config), {nodetree, NodeTree}),
|
||||
ets:insert(gen_mod:get_module_proc(Host, config), {plugins, Plugins}),
|
||||
@@ -197,7 +196,6 @@ init([ServerHost, Opts]) ->
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {ignore_pep_from_offline, PepOffline}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {host, Host}),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
|
||||
ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
|
||||
@@ -210,7 +208,6 @@ init([ServerHost, Opts]) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, ?MODULE, iq_sm, IQDisc),
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
true ->
|
||||
ejabberd_hooks:add(feature_check_packet, ServerHost, ?MODULE, feature_check_packet, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
|
||||
ejabberd_hooks:add(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
|
||||
ejabberd_hooks:add(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
|
||||
@@ -320,13 +317,18 @@ send_loop(State) ->
|
||||
%% this makes that hack only work for local domain by now
|
||||
if not State#state.ignore_pep_from_offline ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
case mod_caps:get_caps({User, Server, Resource}) of
|
||||
nothing ->
|
||||
%% we don't have caps, no need to handle PEP items
|
||||
ok;
|
||||
_ ->
|
||||
case catch ejabberd_c2s:get_subscribed(Pid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:foreach(
|
||||
fun({U, S, R}) ->
|
||||
case S of
|
||||
ServerHost -> %% local contacts
|
||||
case user_resources(U, S) of
|
||||
case ejabberd_sm:get_user_resources(U, S) of
|
||||
[] -> %% offline
|
||||
PeerJID = jlib:make_jid(U, S, R),
|
||||
self() ! {presence, User, Server, [Resource], PeerJID};
|
||||
@@ -341,7 +343,8 @@ send_loop(State) ->
|
||||
end, Contacts);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
@@ -349,35 +352,55 @@ send_loop(State) ->
|
||||
{presence, User, Server, Resources, JID} ->
|
||||
%% get resources caps and check if processing is needed
|
||||
spawn(fun() ->
|
||||
Host = State#state.host,
|
||||
Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
|
||||
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) ->
|
||||
{HasCaps, ResourcesCaps} = lists:foldl(fun(Resource, {R, L}) ->
|
||||
case mod_caps:get_caps({User, Server, Resource}) of
|
||||
nothing -> {R, L};
|
||||
Caps -> {true, [{Resource, Caps} | L]}
|
||||
end
|
||||
end, {false, []}, Resources),
|
||||
case HasCaps of
|
||||
true ->
|
||||
Host = State#state.host,
|
||||
ServerHost = State#state.server_host,
|
||||
Owner = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
|
||||
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = NodeId, options = Options}) ->
|
||||
case get_option(Options, send_last_published_item) of
|
||||
on_sub_and_presence ->
|
||||
lists:foreach(
|
||||
fun(Resource) ->
|
||||
LJID = {User, Server, Resource},
|
||||
Subscribed = case get_option(Options, access_model) of
|
||||
open -> true;
|
||||
presence -> true;
|
||||
whitelist -> false; % subscribers are added manually
|
||||
authorize -> false; % likewise
|
||||
roster ->
|
||||
Grps = get_option(Options, roster_groups_allowed, []),
|
||||
{OU, OS, _} = Owner,
|
||||
element(2, get_roster_info(OU, OS, LJID, Grps))
|
||||
end,
|
||||
if Subscribed ->
|
||||
send_items(Owner, Node, NodeId, Type, LJID, last);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, Resources);
|
||||
lists:foreach(fun({Resource, Caps}) ->
|
||||
CapsNotify = case catch mod_caps:get_features(ServerHost, Caps) of
|
||||
Features when is_list(Features) -> lists:member(node_to_string(Node) ++ "+notify", Features);
|
||||
_ -> false
|
||||
end,
|
||||
case CapsNotify of
|
||||
true ->
|
||||
LJID = {User, Server, Resource},
|
||||
Subscribed = case get_option(Options, access_model) of
|
||||
open -> true;
|
||||
presence -> true;
|
||||
whitelist -> false; % subscribers are added manually
|
||||
authorize -> false; % likewise
|
||||
roster ->
|
||||
Grps = get_option(Options, roster_groups_allowed, []),
|
||||
{OU, OS, _} = Owner,
|
||||
element(2, get_roster_info(OU, OS, LJID, Grps))
|
||||
end,
|
||||
if Subscribed ->
|
||||
send_items(Owner, Node, NodeId, Type, LJID, last);
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, ResourcesCaps);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, tree_action(Host, get_nodes, [Owner, JID]))
|
||||
end),
|
||||
end, tree_action(Host, get_nodes, [Owner, JID]));
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end),
|
||||
send_loop(State);
|
||||
stop ->
|
||||
ok
|
||||
@@ -577,19 +600,17 @@ remove_user(User, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Entity = jlib:make_jid(LUser, LServer, ""),
|
||||
Host = host(LServer),
|
||||
HomeTreeBase = string_to_node("/home/"++LServer++"/"++LUser),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(PType) ->
|
||||
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
|
||||
lists:foreach(fun
|
||||
({#pubsub_node{id = NodeId}, _, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all])
|
||||
({#pubsub_node{id = NodeId}, subscribed, _, JID}) -> node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
{result, Affiliations} = node_action(Host, PType, get_entity_affiliations, [Host, Entity]),
|
||||
lists:foreach(fun
|
||||
({#pubsub_node{nodeid = {H, N}, parents = []}, owner}) -> delete_node(H, N, Entity);
|
||||
({#pubsub_node{nodeid = {H, N}, type = "hometree"}, owner}) when N == HomeTreeBase -> delete_node(H, N, Entity);
|
||||
({#pubsub_node{id = NodeId}, publisher}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none]);
|
||||
(_) -> ok
|
||||
({#pubsub_node{nodeid = {H, N}}, owner}) -> delete_node(H, N, Entity);
|
||||
({#pubsub_node{id = NodeId}, _}) -> node_action(Host, PType, set_affiliation, [NodeId, Entity, none])
|
||||
end, Affiliations)
|
||||
end, plugins(Host))
|
||||
end).
|
||||
@@ -660,7 +681,6 @@ terminate(_Reason, #state{host = Host,
|
||||
ejabberd_router:unregister_route(Host),
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
true ->
|
||||
ejabberd_hooks:delete(feature_check_packet, ServerHost, ?MODULE, feature_check_packet, 75),
|
||||
ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
|
||||
ejabberd_hooks:delete(disco_local_features, ServerHost, ?MODULE, disco_local_features, 75),
|
||||
ejabberd_hooks:delete(disco_local_items, ServerHost, ?MODULE, disco_local_items, 75),
|
||||
@@ -669,7 +689,6 @@ terminate(_Reason, #state{host = Host,
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost, ?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:delete(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
|
||||
ejabberd_hooks:delete(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, disco_sm_items, 75),
|
||||
@@ -812,10 +831,14 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
|
||||
end.
|
||||
|
||||
command_disco_info(_Host, <<?NS_COMMANDS>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-list"}], []},
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-list"}],
|
||||
[]},
|
||||
{result, [IdentityEl]};
|
||||
command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"}, {"type", "command-node"}], []},
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-node"}],
|
||||
[]},
|
||||
FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{result, [IdentityEl, FeaturesEl]}.
|
||||
|
||||
@@ -898,20 +921,15 @@ iq_disco_info(Host, SNode, From, Lang) ->
|
||||
|
||||
iq_disco_items(Host, [], From, _RSM) ->
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
|
||||
Attrs =
|
||||
case get_option(Options, title) of
|
||||
false ->
|
||||
[{"jid", Host} |nodeAttr(SubNode)];
|
||||
Title ->
|
||||
[{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
|
||||
end,
|
||||
{xmlelement, "item", Attrs, []}
|
||||
end, Nodes)};
|
||||
Other ->
|
||||
Other
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, Nodes)};
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
|
||||
%% TODO: support localization of this string
|
||||
@@ -934,21 +952,16 @@ iq_disco_items(Host, Item, From, RSM) ->
|
||||
_ -> {[], none}
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
|
||||
Attrs =
|
||||
case get_option(Options, title) of
|
||||
false ->
|
||||
[{"jid", Host} |nodeAttr(SubNode)];
|
||||
Title ->
|
||||
[{"jid", Host}, {"name", Title}|nodeAttr(SubNode)]
|
||||
end,
|
||||
{xmlelement, "item", Attrs, []}
|
||||
end, tree_call(Host, get_subnodes, [Host, Node, From])),
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_call(Host, get_subnodes, [Host, Node, From])),
|
||||
Items = lists:map(
|
||||
fun(#pubsub_item{itemid = {RN, _}}) ->
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
fun(#pubsub_item{itemid = {RN, _}}) ->
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
{result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
@@ -1253,7 +1266,7 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
true ->
|
||||
case node_call(Type, get_affiliation, [NodeID, Owner]) of
|
||||
{result, owner} ->
|
||||
node_call(Type, get_node_subscriptions, [NodeID]);
|
||||
node_call(Type, get_node_subscriptions, [NodeID]);
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end;
|
||||
@@ -1263,10 +1276,10 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, {N, Subscriptions}} ->
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
#adhoc_response{};
|
||||
Err ->
|
||||
Err
|
||||
@@ -1546,17 +1559,9 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
{result, true} ->
|
||||
case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of
|
||||
{ok, NodeId} ->
|
||||
ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, Owner]),
|
||||
SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree],
|
||||
case node_call(Type, create_node, [NodeId, Owner]) of
|
||||
{result, Result} -> {result, {NodeId, SubsByDepth, Result}};
|
||||
Error -> Error
|
||||
end;
|
||||
node_call(Type, create_node, [NodeId, Owner]);
|
||||
{error, {virtual, NodeId}} ->
|
||||
case node_call(Type, create_node, [NodeId, Owner]) of
|
||||
{result, Result} -> {result, {NodeId, [], Result}};
|
||||
Error -> Error
|
||||
end;
|
||||
node_call(Type, create_node, [NodeId, Owner]);
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
@@ -1568,15 +1573,20 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
[{xmlelement, "create", nodeAttr(Node),
|
||||
[]}]}],
|
||||
case transaction(Host, CreateNode, transaction) of
|
||||
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
|
||||
{result, {Result, broadcast}} ->
|
||||
%%Lang = "en", %% TODO: fix
|
||||
%%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
%%broadcast_publish_item(Host, Node, uniqid(), Owner,
|
||||
%% [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}],
|
||||
%% [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NMI),
|
||||
%% ?XFIELD("jid-single", "Node Creator", "creator", jlib:jid_to_string(OwnerKey))]}]),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_NodeId, _SubsByDepth, default}} ->
|
||||
{result, default} ->
|
||||
{result, Reply};
|
||||
{result, {_NodeId, _SubsByDepth, Result}} ->
|
||||
{result, Result} ->
|
||||
{result, Result};
|
||||
Error ->
|
||||
%% in case we change transaction to sync_dirty...
|
||||
@@ -1870,7 +1880,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, Options, Removed, ItemId, jlib:jid_tolower(Publisher), BroadcastPayload),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Payload),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
@@ -1880,14 +1890,14 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Payload),
|
||||
{result, Reply};
|
||||
{result, {TNode, {Result, Removed}}} ->
|
||||
NodeId = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
broadcast_retract_items(Host, Node, NodeId, Type, Options, Removed),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Payload),
|
||||
{result, Result};
|
||||
{result, {_, default}} ->
|
||||
{result, Reply};
|
||||
@@ -2128,20 +2138,20 @@ send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
% special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc
|
||||
case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of
|
||||
{result, [LastItem]} ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
{ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls([LastItem])}], ModifNow, ModifUSR);
|
||||
itemsEls([LastItem])}], ModifNow, ModifLjid);
|
||||
_ ->
|
||||
event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls([])}])
|
||||
end;
|
||||
LastItem ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
{ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls([LastItem])}], ModifNow, ModifUSR)
|
||||
itemsEls([LastItem])}], ModifNow, ModifLjid)
|
||||
end,
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
@@ -2158,10 +2168,10 @@ send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
end,
|
||||
Stanza = case ToSend of
|
||||
[LastItem] ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
{ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls(ToSend)}], ModifNow, ModifUSR);
|
||||
itemsEls(ToSend)}], ModifNow, ModifLjid);
|
||||
_ ->
|
||||
event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
@@ -2270,18 +2280,12 @@ set_affiliations(Host, Node, From, EntitiesEls) ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
Owners = node_owners_call(Type, NodeId),
|
||||
case lists:member(Owner, Owners) of
|
||||
case lists:member(Owner, node_owners_call(Type, NodeId)) of
|
||||
true ->
|
||||
OwnerJID = jlib:make_jid(Owner),
|
||||
FilteredEntities = case Owners of
|
||||
[Owner] -> [E || E <- Entities, element(1, E) =/= OwnerJID];
|
||||
_ -> Entities
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({JID, Affiliation}) ->
|
||||
node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
|
||||
end, FilteredEntities),
|
||||
end, Entities),
|
||||
{result, []};
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
@@ -2685,66 +2689,15 @@ node_to_deliver(LJID, NodeOptions) ->
|
||||
presence_can_deliver(LJID, PresenceDelivery).
|
||||
|
||||
presence_can_deliver(_, false) -> true;
|
||||
presence_can_deliver({User, Server, Resource}, true) ->
|
||||
presence_can_deliver({User, Server, _}, true) ->
|
||||
case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of
|
||||
[] -> false;
|
||||
Ss ->
|
||||
lists:foldl(fun(_, true) -> true;
|
||||
({session, _, _ , _, undefined, _}, _Acc) -> false;
|
||||
({session, _, {_, _, R}, _, _Priority, _}, _Acc) ->
|
||||
case Resource of
|
||||
[] -> true;
|
||||
R -> true;
|
||||
_ -> false
|
||||
end
|
||||
lists:foldl(fun({session, _, _, _, undefined, _}, Acc) -> Acc;
|
||||
({session, _, _, _, _Priority, _}, _Acc) -> true
|
||||
end, false, Ss)
|
||||
end.
|
||||
|
||||
state_can_deliver({U, S, R}, []) -> [{U, S, R}];
|
||||
state_can_deliver({U, S, R}, SubOptions) ->
|
||||
%% Check SubOptions for 'show_values'
|
||||
case lists:keysearch('show_values', 1, SubOptions) of
|
||||
%% If not in suboptions, item can be delivered, case doesn't apply
|
||||
false -> [{U, S, R}];
|
||||
%% If in a suboptions ...
|
||||
{_, {_, ShowValues}} ->
|
||||
%% Get subscriber resources
|
||||
Resources = case R of
|
||||
%% If the subscriber JID is a bare one, get all its resources
|
||||
[] -> user_resources(U, S);
|
||||
%% If the subscriber JID is a full one, use its resource
|
||||
R -> [R]
|
||||
end,
|
||||
%% For each resource, test if the item is allowed to be delivered
|
||||
%% based on resource state
|
||||
lists:foldl(
|
||||
fun(Resource, Acc) ->
|
||||
get_resource_state({U, S, Resource}, ShowValues, Acc)
|
||||
end, [], Resources)
|
||||
end.
|
||||
|
||||
get_resource_state({U, S, R}, ShowValues, JIDs) ->
|
||||
%% Get user session PID
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
%% If no PID, item can be delivered
|
||||
none -> lists:append([{U, S, R}], JIDs);
|
||||
%% If PID ...
|
||||
Pid ->
|
||||
%% Get user resource state
|
||||
%% TODO : add a catch clause
|
||||
Show = case ejabberd_c2s:get_presence(Pid) of
|
||||
{_, _, "available", _} -> "online";
|
||||
{_, _, State, _} -> State
|
||||
end,
|
||||
%% Is current resource state listed in 'show-values' suboption ?
|
||||
case lists:member(Show, ShowValues) of %andalso Show =/= "online" of
|
||||
%% If yes, item can be delivered
|
||||
true -> lists:append([{U, S, R}], JIDs);
|
||||
%% If no, item can't be delivered
|
||||
false -> JIDs
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (Payload) -> int()
|
||||
%% Payload = term()
|
||||
%% @doc <p>Count occurence of XML elements in payload.</p>
|
||||
@@ -2759,9 +2712,9 @@ payload_xmlelements([_|Tail], Count) -> payload_xmlelements(Tail, Count).
|
||||
event_stanza(Els) ->
|
||||
event_stanza_withmoreels(Els, []).
|
||||
|
||||
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
|
||||
event_stanza_with_delay(Els, ModifNow, ModifLjid) ->
|
||||
DateTime = calendar:now_to_datetime(ModifNow),
|
||||
MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifUSR, "")],
|
||||
MoreEls = [jlib:timestamp_to_xml(DateTime, utc, ModifLjid, "")],
|
||||
event_stanza_withmoreels(Els, MoreEls).
|
||||
|
||||
event_stanza_withmoreels(Els, MoreEls) ->
|
||||
@@ -2770,7 +2723,8 @@ event_stanza_withmoreels(Els, MoreEls) ->
|
||||
|
||||
%%%%%% broadcast functions
|
||||
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, From, Payload) ->
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, _From, Payload) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, none, true, "items", ItemEls)
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
SubsByDepth when is_list(SubsByDepth) ->
|
||||
Content = case get_option(NodeOptions, deliver_payloads) of
|
||||
@@ -2780,7 +2734,7 @@ broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, F
|
||||
Stanza = event_stanza(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
[{xmlelement, "item", itemAttr(ItemId), Content}]}]),
|
||||
broadcast_stanza(Host, From, Node, NodeId, Type,
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubsByDepth, items, Stanza, true),
|
||||
case Removed of
|
||||
[] ->
|
||||
@@ -2808,6 +2762,7 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) ->
|
||||
broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) ->
|
||||
{result, false};
|
||||
broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, ForceNotify, "retract", RetractEls)
|
||||
case (get_option(NodeOptions, notify_retract) or ForceNotify) of
|
||||
true ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
@@ -2826,6 +2781,7 @@ broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNot
|
||||
end.
|
||||
|
||||
broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_retract, false, "purge", [])
|
||||
case get_option(NodeOptions, notify_retract) of
|
||||
true ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
@@ -2844,10 +2800,11 @@ broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) ->
|
||||
end.
|
||||
|
||||
broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_delete, false, "delete", [])
|
||||
case get_option(NodeOptions, notify_delete) of
|
||||
true ->
|
||||
case SubsByDepth of
|
||||
[] ->
|
||||
[] ->
|
||||
{result, false};
|
||||
_ ->
|
||||
Stanza = event_stanza(
|
||||
@@ -2861,14 +2818,8 @@ broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
{result, false}
|
||||
end.
|
||||
|
||||
broadcast_created_node(_, _, _, _, _, []) ->
|
||||
{result, false};
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) ->
|
||||
Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]),
|
||||
broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true),
|
||||
{result, true}.
|
||||
|
||||
broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
%broadcast(Host, Node, NodeId, NodeOptions, notify_config, false, "items", ConfigEls)
|
||||
case get_option(NodeOptions, notify_config) of
|
||||
true ->
|
||||
case get_collection_subscriptions(Host, Node) of
|
||||
@@ -2885,7 +2836,7 @@ broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) ->
|
||||
broadcast_stanza(Host, Node, NodeId, Type,
|
||||
NodeOptions, SubsByDepth, nodes, Stanza, false),
|
||||
{result, true};
|
||||
_ ->
|
||||
_ ->
|
||||
{result, false}
|
||||
end;
|
||||
_ ->
|
||||
@@ -2921,7 +2872,7 @@ get_options_for_subs(NodeID, Subs) ->
|
||||
Acc
|
||||
end, [], Subs).
|
||||
|
||||
broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
NotificationType = get_option(NodeOptions, notification_type, headline),
|
||||
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
|
||||
From = service_jid(Host),
|
||||
@@ -2940,66 +2891,63 @@ broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTy
|
||||
[LJID]
|
||||
end,
|
||||
%% Determine if the stanza should have SHIM ('SubID' and 'name') headers
|
||||
StanzaToSend = case {SHIM, SubIDs} of
|
||||
{false, _} ->
|
||||
Stanza;
|
||||
%% If there's only one SubID, don't add it
|
||||
{true, [_]} ->
|
||||
add_shim_headers(Stanza, collection_shim(NodeName));
|
||||
{true, SubIDs} ->
|
||||
add_shim_headers(Stanza, lists:append(collection_shim(NodeName), subid_shim(SubIDs)))
|
||||
end,
|
||||
StanzaToSend = case SHIM of
|
||||
true ->
|
||||
Headers = lists:append(collection_shim(NodeName), subid_shim(SubIDs)),
|
||||
add_headers(Stanza, Headers);
|
||||
false ->
|
||||
Stanza
|
||||
end,
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend)
|
||||
end, LJIDs)
|
||||
end, SubIDsByJID).
|
||||
|
||||
broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza({LUser, LServer, LResource}, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM),
|
||||
ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend)
|
||||
end, LJIDs)
|
||||
end, SubIDsByJID),
|
||||
%% Handles implicit presence subscriptions
|
||||
SenderResource = case LResource of
|
||||
[] ->
|
||||
case user_resources(LUser, LServer) of
|
||||
[Resource|_] -> Resource;
|
||||
_ -> ""
|
||||
end;
|
||||
_ ->
|
||||
LResource
|
||||
end,
|
||||
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
Stanza = case get_option(NodeOptions, notification_type, headline) of
|
||||
normal -> BaseStanza;
|
||||
MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType))
|
||||
end,
|
||||
%% set the from address on the notification to the bare JID of the account owner
|
||||
%% Also, add "replyto" if entity has presence subscription to the account owner
|
||||
%% See XEP-0163 1.1 section 4.3.1
|
||||
Sender = jlib:make_jid(LUser, LServer, ""),
|
||||
ReplyTo = jlib:jid_to_string(Publisher),
|
||||
StanzaToSend = add_extended_headers(Stanza, extended_headers([ReplyTo])),
|
||||
case catch ejabberd_c2s:get_subscribed(C2SPid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:foreach(fun({U, S, _}) ->
|
||||
spawn(fun() ->
|
||||
case lists:member(S, ?MYHOSTS) of
|
||||
true ->
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(Sender, jlib:make_jid(To), StanzaToSend)
|
||||
end, [{U, S, R} || R <- user_resources(U, S)]);
|
||||
false ->
|
||||
ejabberd_router:route(Sender, jlib:make_jid(U, S, ""), StanzaToSend)
|
||||
end
|
||||
end)
|
||||
end, Contacts);
|
||||
case Host of
|
||||
{LUser, LServer, LResource} ->
|
||||
SenderResource = case LResource of
|
||||
[] ->
|
||||
case user_resources(LUser, LServer) of
|
||||
[Resource|_] -> Resource;
|
||||
_ -> ""
|
||||
end;
|
||||
_ ->
|
||||
LResource
|
||||
end,
|
||||
case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
%% set the from address on the notification to the bare JID of the account owner
|
||||
%% Also, add "replyto" if entity has presence subscription to the account owner
|
||||
%% See XEP-0163 1.1 section 4.3.1
|
||||
Sender = jlib:make_jid(LUser, LServer, ""),
|
||||
%%ReplyTo = jlib:make_jid(LUser, LServer, SenderResource), % This has to be used
|
||||
case catch ejabberd_c2s:get_subscribed(C2SPid) of
|
||||
Contacts when is_list(Contacts) ->
|
||||
lists:foreach(fun({U, S, _}) ->
|
||||
spawn(fun() ->
|
||||
LJIDs = lists:foldl(fun(R, Acc) ->
|
||||
LJID = {U, S, R},
|
||||
case is_caps_notify(LServer, Node, LJID) of
|
||||
true -> [LJID | Acc];
|
||||
false -> Acc
|
||||
end
|
||||
end, [], user_resources(U, S)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router:route(Sender, jlib:make_jid(To), Stanza)
|
||||
end, LJIDs)
|
||||
end)
|
||||
end, Contacts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ok;
|
||||
_ ->
|
||||
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, Stanza]),
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza])
|
||||
end;
|
||||
broadcast_stanza(Host, _Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
|
||||
broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
|
||||
ok
|
||||
end.
|
||||
|
||||
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
NodesToDeliver = fun(Depth, Node, Subs, Acc) ->
|
||||
@@ -3010,25 +2958,20 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
NodeOptions = Node#pubsub_node.options,
|
||||
lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) ->
|
||||
case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
|
||||
true ->
|
||||
%% If is to deliver :
|
||||
case state_can_deliver(LJID, SubOptions) of
|
||||
[] -> {JIDs, Recipients};
|
||||
JIDsToDeliver ->
|
||||
lists:foldl(
|
||||
fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
|
||||
case lists:member(JIDToDeliver, JIDs) of
|
||||
true ->
|
||||
%% If is to deliver :
|
||||
case lists:member(LJID, JIDs) of
|
||||
%% check if the JIDs co-accumulator contains the Subscription Jid,
|
||||
false ->
|
||||
false ->
|
||||
%% - if not,
|
||||
%% - add the Jid to JIDs list co-accumulator ;
|
||||
%% - create a tuple of the Jid, NodeId, and SubID (as list),
|
||||
%% and add the tuple to the Recipients list co-accumulator
|
||||
{[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]};
|
||||
true ->
|
||||
{[LJID | JIDs], [{LJID, NodeName, [SubID]} | Recipients]};
|
||||
true ->
|
||||
%% - if the JIDs co-accumulator contains the Jid
|
||||
%% get the tuple containing the Jid from the Recipient list co-accumulator
|
||||
{_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
|
||||
{_, {LJID, NodeName1, SubIDs}} = lists:keysearch(LJID, 1, Recipients),
|
||||
%% delete the tuple from the Recipients list
|
||||
% v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
|
||||
% v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, NodeId1, [SubID | SubIDs]}),
|
||||
@@ -3037,25 +2980,39 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||
% v1.1 : {JIDs, lists:append(Recipients1, [{LJID, NodeId1, lists:append(SubIDs, [SubID])}])}
|
||||
% v1.2 : {JIDs, [{LJID, NodeId1, [SubID | SubIDs]} | Recipients1]}
|
||||
% v2: {JIDs, Recipients1}
|
||||
{JIDsAcc, lists:keyreplace(JIDToDeliver, 1, RecipientsAcc, {JIDToDeliver, NodeName1, [SubID | SubIDs]})}
|
||||
end
|
||||
end, {JIDs, Recipients}, JIDsToDeliver)
|
||||
end;
|
||||
{JIDs, lists:keyreplace(LJID, 1, Recipients, {LJID, NodeName1, [SubID | SubIDs]})}
|
||||
end;
|
||||
false ->
|
||||
{JIDs, Recipients}
|
||||
end
|
||||
end, Acc, Subs)
|
||||
end,
|
||||
DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) ->
|
||||
DepthsToDeliver = fun({Depth, SubsByNode}, Acc) ->
|
||||
lists:foldl(fun({Node, Subs}, Acc2) ->
|
||||
NodesToDeliver(Depth, Node, Subs, Acc2)
|
||||
end, Acc1, SubsByNode)
|
||||
end, Acc, SubsByNode)
|
||||
end,
|
||||
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
|
||||
JIDSubs.
|
||||
|
||||
%% If we don't know the resource, just pick first if any
|
||||
%% If no resource available, check if caps anyway (remote online)
|
||||
user_resources(User, Server) ->
|
||||
ejabberd_sm:get_user_resources(User, Server).
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] -> mod_caps:get_user_resources(User, Server);
|
||||
Rs -> Rs
|
||||
end.
|
||||
|
||||
is_caps_notify(Host, Node, LJID) ->
|
||||
case mod_caps:get_caps(LJID) of
|
||||
nothing ->
|
||||
false;
|
||||
Caps ->
|
||||
case catch mod_caps:get_features(Host, Caps) of
|
||||
Features when is_list(Features) -> lists:member(node_to_string(Node) ++ "+notify", Features);
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%%%%%%% Configuration handling
|
||||
|
||||
@@ -3220,7 +3177,6 @@ get_configure_xfields(_Type, Options, Lang, Groups) ->
|
||||
?LISTM_CONFIG_FIELD("Roster groups allowed to subscribe", roster_groups_allowed, Groups),
|
||||
?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
|
||||
[publishers, subscribers, open]),
|
||||
?BOOL_CONFIG_FIELD("Purge all items when the relevant publisher goes offline", purge_offline),
|
||||
?ALIST_CONFIG_FIELD("Specify the event message type", notification_type,
|
||||
[headline, normal]),
|
||||
?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
|
||||
@@ -3364,8 +3320,6 @@ set_xoption(Host, [{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts)
|
||||
?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
|
||||
set_xoption(Host, [{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
|
||||
?SET_BOOL_XOPT(presence_based_delivery, Val);
|
||||
set_xoption(Host, [{"pubsub#purge_offline", [Val]} | Opts], NewOpts) ->
|
||||
?SET_BOOL_XOPT(purge_offline, Val);
|
||||
set_xoption(Host, [{"pubsub#title", Value} | Opts], NewOpts) ->
|
||||
?SET_STRING_XOPT(title, Value);
|
||||
set_xoption(Host, [{"pubsub#type", Value} | Opts], NewOpts) ->
|
||||
@@ -3400,18 +3354,18 @@ is_last_item_cache_enabled(Host) ->
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
set_cached_item({_, ServerHost, _}, NodeId, ItemId, Publisher, Payload) ->
|
||||
set_cached_item(ServerHost, NodeId, ItemId, Publisher, Payload);
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload) ->
|
||||
set_cached_item({_, ServerHost, _}, NodeId, ItemId, Payload) ->
|
||||
set_cached_item(ServerHost, NodeId, ItemId, Payload);
|
||||
set_cached_item(Host, NodeId, ItemId, Payload) ->
|
||||
case is_last_item_cache_enabled(Host) of
|
||||
true -> mnesia:dirty_write({pubsub_last_item, NodeId, ItemId, {now(), jlib:jid_tolower(jlib:jid_remove_resource(Publisher))}, Payload});
|
||||
true -> ets:insert(gen_mod:get_module_proc(Host, last_items), {NodeId, {ItemId, Payload}});
|
||||
_ -> ok
|
||||
end.
|
||||
unset_cached_item({_, ServerHost, _}, NodeId) ->
|
||||
unset_cached_item(ServerHost, NodeId);
|
||||
unset_cached_item(Host, NodeId) ->
|
||||
case is_last_item_cache_enabled(Host) of
|
||||
true -> mnesia:dirty_delete({pubsub_last_item, NodeId});
|
||||
true -> ets:delete(gen_mod:get_module_proc(Host, last_items), NodeId);
|
||||
_ -> ok
|
||||
end.
|
||||
get_cached_item({_, ServerHost, _}, NodeId) ->
|
||||
@@ -3419,10 +3373,9 @@ get_cached_item({_, ServerHost, _}, NodeId) ->
|
||||
get_cached_item(Host, NodeId) ->
|
||||
case is_last_item_cache_enabled(Host) of
|
||||
true ->
|
||||
case mnesia:dirty_read({pubsub_last_item, NodeId}) of
|
||||
[{pubsub_last_item, NodeId, ItemId, Creation, Payload}] ->
|
||||
#pubsub_item{itemid = {ItemId, NodeId}, payload = Payload,
|
||||
creation = Creation, modification = Creation};
|
||||
case catch ets:lookup(gen_mod:get_module_proc(Host, last_items), NodeId) of
|
||||
[{NodeId, {ItemId, Payload}}] ->
|
||||
#pubsub_item{itemid = {ItemId, NodeId}, payload = Payload};
|
||||
_ ->
|
||||
undefined
|
||||
end;
|
||||
@@ -3672,14 +3625,8 @@ add_message_type(XmlEl, _Type) ->
|
||||
%% "[SHIM Headers] SHOULD be included after the event notification information
|
||||
%% (i.e., as the last child of the <message/> stanza)".
|
||||
|
||||
add_shim_headers(Stanza, HeaderEls) ->
|
||||
add_headers(Stanza, "headers", ?NS_SHIM, HeaderEls).
|
||||
|
||||
add_extended_headers(Stanza, HeaderEls) ->
|
||||
add_headers(Stanza, "addresses", ?NS_ADDRESS, HeaderEls).
|
||||
|
||||
add_headers({xmlelement, Name, Attrs, Els}, HeaderName, HeaderNS, HeaderEls) ->
|
||||
HeaderEl = {xmlelement, HeaderName, [{"xmlns", HeaderNS}], HeaderEls},
|
||||
add_headers({xmlelement, Name, Attrs, Els}, HeaderEls) ->
|
||||
HeaderEl = {xmlelement, "headers", [{"xmlns", ?NS_SHIM}], HeaderEls},
|
||||
{xmlelement, Name, Attrs, lists:append(Els, [HeaderEl])}.
|
||||
|
||||
%% Removed multiple <header name=Collection>Foo</header/> elements
|
||||
@@ -3703,126 +3650,3 @@ subid_shim(SubIDs) ->
|
||||
[{xmlelement, "header", [{"name", "SubID"}],
|
||||
[{xmlcdata, SubID}]} || SubID <- SubIDs].
|
||||
|
||||
%% The argument is a list of Jids because this function could be used
|
||||
%% with the 'pubsub#replyto' (type=jid-multi) node configuration.
|
||||
|
||||
extended_headers(Jids) ->
|
||||
[{xmlelement, "address", [{"type", "replyto"}, {"jid", Jid}], []} || Jid <- Jids].
|
||||
|
||||
|
||||
feature_check_packet(allow, _User, Server, Pres,
|
||||
{#jid{lserver = LServer}, _To,
|
||||
{xmlelement, "message", MsgAttrs, _} = El}, in) ->
|
||||
Host = host(Server),
|
||||
case LServer of
|
||||
%% If the sender Server equals Host, the message comes from the Pubsub server
|
||||
Host ->
|
||||
allow;
|
||||
%% Else, the message comes from PEP
|
||||
_ ->
|
||||
case xml:get_subtag(El, "event") of
|
||||
{xmlelement, _, Attrs, _} = EventEl ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_PUBSUB_EVENT ->
|
||||
case xml:get_attr_s("type", MsgAttrs) of
|
||||
"error" ->
|
||||
%% Filter error-repsonse of PEP message
|
||||
%% to avoid routing it to client
|
||||
deny;
|
||||
_ when Pres /= undefined ->
|
||||
%% Yes, sometimes Pres = undefined,
|
||||
%% very rare though.
|
||||
%% Seems like this is a bug: should
|
||||
%% be fixed in ejabberd_s2s.erl
|
||||
Feature = xml:get_path_s(
|
||||
EventEl, [{elem, "items"},
|
||||
{attr, "node"}]),
|
||||
case is_feature_supported(Pres, Feature) of
|
||||
true ->
|
||||
allow;
|
||||
false ->
|
||||
deny
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
end;
|
||||
_ ->
|
||||
allow
|
||||
end
|
||||
end;
|
||||
feature_check_packet(Acc, _User, _Server, _Pres, _Packet, _Direction) ->
|
||||
Acc.
|
||||
|
||||
is_feature_supported({xmlelement, "presence", _, Els}, Feature) ->
|
||||
case mod_caps:read_caps(Els) of
|
||||
nothing -> false;
|
||||
Caps -> lists:member(Feature ++ "+notify", mod_caps:get_features(Caps))
|
||||
end.
|
||||
|
||||
on_user_offline(_, JID, _) ->
|
||||
{User, Server, Resource} = jlib:jid_tolower(JID),
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] -> purge_offline({User, Server, Resource});
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
purge_offline({User, Server, _} = LJID) ->
|
||||
Host = host(element(2, LJID)),
|
||||
Plugins = plugins(Host),
|
||||
Result = lists:foldl(
|
||||
fun(Type, {Status, Acc}) ->
|
||||
case lists:member("retrieve-affiliations", features(Type)) of
|
||||
false ->
|
||||
{{error, extended_error('feature-not-implemented', unsupported, "retrieve-affiliations")}, Acc};
|
||||
true ->
|
||||
{result, Affiliations} = node_action(Host, Type, get_entity_affiliations, [Host, LJID]),
|
||||
{Status, [Affiliations|Acc]}
|
||||
end
|
||||
end, {ok, []}, Plugins),
|
||||
case Result of
|
||||
{ok, Affiliations} ->
|
||||
lists:foreach(
|
||||
fun({#pubsub_node{nodeid = {_, NodeId}, options = Options, type = Type}, Affiliation})
|
||||
when Affiliation == 'owner' orelse Affiliation == 'publisher' ->
|
||||
Action = fun(#pubsub_node{type = NType, id = NodeIdx}) ->
|
||||
node_call(NType, get_items, [NodeIdx, service_jid(Host)])
|
||||
end,
|
||||
case transaction(Host, NodeId, Action, sync_dirty) of
|
||||
{result, {_, []}} ->
|
||||
true;
|
||||
{result, {_, Items}} ->
|
||||
Features = features(Type),
|
||||
case
|
||||
{lists:member("retract-items", Features),
|
||||
lists:member("persistent-items", Features),
|
||||
get_option(Options, persist_items),
|
||||
get_option(Options, purge_offline)}
|
||||
of
|
||||
{true, true, true, true} ->
|
||||
ForceNotify = get_option(Options, notify_retract),
|
||||
lists:foreach(
|
||||
fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, Modification}}) ->
|
||||
case Modification of
|
||||
{User, Server, _} ->
|
||||
delete_item(Host, NodeId, LJID, ItemId, ForceNotify);
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
(_) ->
|
||||
true
|
||||
end, Items);
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
(_) ->
|
||||
true
|
||||
end, lists:usort(lists:flatten(Affiliations)));
|
||||
{Error, _} ->
|
||||
?DEBUG("on_user_offline ~p", [Error])
|
||||
end.
|
||||
|
||||
@@ -82,7 +82,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -85,7 +85,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -85,7 +85,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -83,7 +83,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -76,7 +76,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -81,7 +81,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -139,7 +139,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
@@ -379,7 +378,7 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
%% Requesting entity is not a subscriber
|
||||
Subscriptions == [] ->
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")};
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
|
||||
%% Subid supplied, so use that.
|
||||
SubIdExists ->
|
||||
Sub = first_in_list(fun(S) ->
|
||||
@@ -393,7 +392,7 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
delete_subscriptions(SubKey, NodeId, [S], SubState),
|
||||
{result, default};
|
||||
false ->
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")}
|
||||
end;
|
||||
%% Asking to remove all subscriptions to the given node
|
||||
SubId == all ->
|
||||
|
||||
@@ -143,7 +143,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
@@ -379,7 +378,7 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
%% Requesting entity is not a subscriber
|
||||
Subscriptions == [] ->
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")};
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
|
||||
%% Subid supplied, so use that.
|
||||
SubIdExists ->
|
||||
Sub = first_in_list(fun(S) ->
|
||||
@@ -393,7 +392,7 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
|
||||
delete_subscription(SubKey, NodeId, S, Affiliation, Subscriptions),
|
||||
{result, default};
|
||||
false ->
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST_CANCEL, "not-subscribed")}
|
||||
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")}
|
||||
end;
|
||||
%% Asking to remove all subscriptions to the given node
|
||||
SubId == all ->
|
||||
|
||||
@@ -88,7 +88,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -83,7 +83,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
{purge_offline, false},
|
||||
{persist_items, false},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -91,7 +91,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, false},
|
||||
{purge_offline, false},
|
||||
{persist_items, false},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -85,7 +85,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -85,7 +85,6 @@ options() ->
|
||||
{notify_config, false},
|
||||
{notify_delete, false},
|
||||
{notify_retract, true},
|
||||
{purge_offline, false},
|
||||
{persist_items, true},
|
||||
{max_items, ?MAXITEMS},
|
||||
{subscribe, true},
|
||||
|
||||
@@ -135,12 +135,3 @@
|
||||
%% <p>This is the format of the <tt>subscriptions</tt> table. The type of the
|
||||
%% table is: <tt>set</tt>,<tt>ram/disc</tt>.</p>
|
||||
-record(pubsub_subscription, {subid, options}).
|
||||
|
||||
%% @type pubsubLastItem() = #pubsub_last_item{
|
||||
%% nodeid = nodeidx(),
|
||||
%% itemid = string(),
|
||||
%% payload = XMLContent::string()}.
|
||||
%% <p>This is the format of the <tt>last items</tt> table. it stores last item payload
|
||||
%% for every node</p>
|
||||
-record(pubsub_last_item, {nodeid, itemid, creation, payload}).
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--- mod_pubsub.erl 2010-06-02 15:03:48.000000000 +0200
|
||||
+++ mod_pubsub_odbc.erl 2010-06-02 16:45:38.000000000 +0200
|
||||
--- mod_pubsub.erl 2010-01-13 10:58:17.000000000 +0100
|
||||
+++ mod_pubsub_odbc.erl 2010-01-13 10:59:59.000000000 +0100
|
||||
@@ -42,7 +42,7 @@
|
||||
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
|
||||
%%% XEP-0060 section 12.18.
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
%% exports for hooks
|
||||
-export([presence_probe/3,
|
||||
@@ -104,7 +104,7 @@
|
||||
@@ -102,7 +102,7 @@
|
||||
string_to_affiliation/1,
|
||||
extended_error/2,
|
||||
extended_error/3,
|
||||
@@ -31,7 +31,7 @@
|
||||
]).
|
||||
|
||||
%% API and gen_server callbacks
|
||||
@@ -123,7 +123,7 @@
|
||||
@@ -121,7 +121,7 @@
|
||||
-export([send_loop/1
|
||||
]).
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
|
||||
-define(PLUGIN_PREFIX, "node_").
|
||||
-define(TREE_PREFIX, "nodetree_").
|
||||
@@ -220,8 +220,6 @@
|
||||
@@ -217,8 +217,6 @@
|
||||
ok
|
||||
end,
|
||||
ejabberd_router:register_route(Host),
|
||||
@@ -49,7 +49,7 @@
|
||||
init_nodes(Host, ServerHost, NodeTree, Plugins),
|
||||
State = #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
@@ -280,207 +278,14 @@
|
||||
@@ -277,207 +275,14 @@
|
||||
|
||||
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
|
||||
%% TODO, this call should be done plugin side
|
||||
@@ -260,7 +260,7 @@
|
||||
send_loop(State) ->
|
||||
receive
|
||||
{presence, JID, Pid} ->
|
||||
@@ -491,17 +296,15 @@
|
||||
@@ -488,17 +293,15 @@
|
||||
%% for each node From is subscribed to
|
||||
%% and if the node is so configured, send the last published item to From
|
||||
lists:foreach(fun(PType) ->
|
||||
@@ -284,7 +284,7 @@
|
||||
true ->
|
||||
% resource not concerned about that subscription
|
||||
ok
|
||||
@@ -747,10 +550,10 @@
|
||||
@@ -770,10 +573,10 @@
|
||||
lists:foreach(fun(PType) ->
|
||||
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
|
||||
lists:foreach(fun
|
||||
@@ -297,7 +297,7 @@
|
||||
true ->
|
||||
node_action(Host, PType, unsubscribe_node, [NodeId, Entity, JID, all]);
|
||||
false ->
|
||||
@@ -920,7 +723,8 @@
|
||||
@@ -939,7 +742,8 @@
|
||||
sub_el = SubEl} = IQ ->
|
||||
{xmlelement, _, QAttrs, _} = SubEl,
|
||||
Node = xml:get_attr_s("node", QAttrs),
|
||||
@@ -307,7 +307,7 @@
|
||||
{result, IQRes} ->
|
||||
jlib:iq_to_xml(
|
||||
IQ#iq{type = result,
|
||||
@@ -1033,7 +837,7 @@
|
||||
@@ -1056,7 +860,7 @@
|
||||
[] ->
|
||||
["leaf"]; %% No sub-nodes: it's a leaf node
|
||||
_ ->
|
||||
@@ -316,7 +316,7 @@
|
||||
{result, []} -> ["collection"];
|
||||
{result, _} -> ["leaf", "collection"];
|
||||
_ -> []
|
||||
@@ -1049,8 +853,9 @@
|
||||
@@ -1072,8 +876,9 @@
|
||||
[];
|
||||
true ->
|
||||
[{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
|
||||
@@ -328,7 +328,7 @@
|
||||
end, features(Type))]
|
||||
end,
|
||||
%% TODO: add meta-data info (spec section 5.4)
|
||||
@@ -1079,8 +884,9 @@
|
||||
@@ -1102,8 +907,9 @@
|
||||
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
|
||||
@@ -340,18 +340,18 @@
|
||||
end, features(Host, Node))};
|
||||
<<?NS_COMMANDS>> ->
|
||||
command_disco_info(Host, Node, From);
|
||||
@@ -1090,7 +896,7 @@
|
||||
@@ -1113,7 +919,7 @@
|
||||
node_disco_info(Host, Node, From)
|
||||
end.
|
||||
|
||||
-iq_disco_items(Host, [], From) ->
|
||||
+iq_disco_items(Host, [], From, _RSM) ->
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
@@ -1107,14 +913,14 @@
|
||||
Other ->
|
||||
Other
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
@@ -1125,14 +931,14 @@
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
-iq_disco_items(Host, ?NS_COMMANDS, _From) ->
|
||||
+iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
|
||||
@@ -367,7 +367,7 @@
|
||||
case string:tokens(Item, "!") of
|
||||
[_SNode, _ItemID] ->
|
||||
{result, []};
|
||||
@@ -1122,10 +928,10 @@
|
||||
@@ -1140,10 +946,10 @@
|
||||
Node = string_to_node(SNode),
|
||||
Action =
|
||||
fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
@@ -380,17 +380,17 @@
|
||||
+ _ -> {[], none}
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, options = Options}) ->
|
||||
@@ -1143,7 +949,7 @@
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
@@ -1156,7 +962,7 @@
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
- {result, Nodes ++ Items}
|
||||
+ {result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, {_, Result}} -> {result, Result};
|
||||
@@ -1272,7 +1078,8 @@
|
||||
@@ -1285,7 +1091,8 @@
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], xml:remove_cdata(Els)),
|
||||
@@ -400,7 +400,7 @@
|
||||
{get, "subscriptions"} ->
|
||||
get_subscriptions(Host, Node, From, Plugins);
|
||||
{get, "affiliations"} ->
|
||||
@@ -1295,7 +1102,9 @@
|
||||
@@ -1308,7 +1115,9 @@
|
||||
|
||||
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
|
||||
{xmlelement, _, _, SubEls} = SubEl,
|
||||
@@ -411,7 +411,7 @@
|
||||
case Action of
|
||||
[{xmlelement, Name, Attrs, Els}] ->
|
||||
Node = string_to_node(xml:get_attr_s("node", Attrs)),
|
||||
@@ -1425,7 +1234,8 @@
|
||||
@@ -1438,7 +1247,8 @@
|
||||
_ -> []
|
||||
end
|
||||
end,
|
||||
@@ -421,7 +421,7 @@
|
||||
sync_dirty) of
|
||||
{result, Res} -> Res;
|
||||
Err -> Err
|
||||
@@ -1464,7 +1274,7 @@
|
||||
@@ -1477,7 +1287,7 @@
|
||||
|
||||
%%% authorization handling
|
||||
|
||||
@@ -430,7 +430,7 @@
|
||||
Lang = "en", %% TODO fix
|
||||
Stanza = {xmlelement, "message",
|
||||
[],
|
||||
@@ -1493,7 +1303,7 @@
|
||||
@@ -1506,7 +1316,7 @@
|
||||
[{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
|
||||
lists:foreach(fun(Owner) ->
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
|
||||
@@ -439,7 +439,7 @@
|
||||
|
||||
find_authorization_response(Packet) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = Packet,
|
||||
@@ -1557,8 +1367,8 @@
|
||||
@@ -1570,8 +1380,8 @@
|
||||
"true" -> true;
|
||||
_ -> false
|
||||
end,
|
||||
@@ -450,16 +450,16 @@
|
||||
{result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
|
||||
if
|
||||
not IsApprover ->
|
||||
@@ -1757,7 +1567,7 @@
|
||||
@@ -1762,7 +1572,7 @@
|
||||
Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "create", nodeAttr(Node),
|
||||
[]}]}],
|
||||
- case transaction(CreateNode, transaction) of
|
||||
+ case transaction(Host, CreateNode, transaction) of
|
||||
{result, {NodeId, SubsByDepth, {Result, broadcast}}} ->
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth),
|
||||
case Result of
|
||||
@@ -1860,7 +1670,7 @@
|
||||
{result, {Result, broadcast}} ->
|
||||
%%Lang = "en", %% TODO: fix
|
||||
%%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
@@ -1870,7 +1680,7 @@
|
||||
%%<li>The node does not exist.</li>
|
||||
%%</ul>
|
||||
subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
@@ -468,7 +468,7 @@
|
||||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -1868,7 +1678,7 @@
|
||||
@@ -1878,7 +1688,7 @@
|
||||
error -> {"", "", ""};
|
||||
J -> jlib:jid_tolower(J)
|
||||
end,
|
||||
@@ -477,7 +477,7 @@
|
||||
Features = features(Type),
|
||||
SubscribeFeature = lists:member("subscribe", Features),
|
||||
OptionsFeature = lists:member("subscription-options", Features),
|
||||
@@ -1887,9 +1697,13 @@
|
||||
@@ -1897,9 +1707,13 @@
|
||||
{"", "", ""} ->
|
||||
{false, false};
|
||||
_ ->
|
||||
@@ -494,7 +494,7 @@
|
||||
end
|
||||
end,
|
||||
if
|
||||
@@ -2220,7 +2034,7 @@
|
||||
@@ -2230,7 +2044,7 @@
|
||||
%% <p>The permission are not checked in this function.</p>
|
||||
%% @todo We probably need to check that the user doing the query has the right
|
||||
%% to read the items.
|
||||
@@ -503,7 +503,7 @@
|
||||
MaxItems =
|
||||
if
|
||||
SMaxItems == "" -> get_max_items_node(Host);
|
||||
@@ -2259,11 +2073,11 @@
|
||||
@@ -2269,11 +2083,11 @@
|
||||
node_call(Type, get_items,
|
||||
[NodeId, From,
|
||||
AccessModel, PresenceSubscription, RosterGroup,
|
||||
@@ -517,7 +517,7 @@
|
||||
SendItems = case ItemIDs of
|
||||
[] ->
|
||||
Items;
|
||||
@@ -2276,7 +2090,8 @@
|
||||
@@ -2286,7 +2100,8 @@
|
||||
%% number of items sent to MaxItems:
|
||||
{result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
@@ -527,7 +527,7 @@
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
@@ -2308,16 +2123,27 @@
|
||||
@@ -2318,16 +2133,27 @@
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
%% @todo use cache-last-item feature
|
||||
send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
@@ -538,41 +538,38 @@
|
||||
+ % special ODBC optimization, works only with node_hometree_odbc, node_flat_odbc and node_pep_odbc
|
||||
+ case node_action(Host, Type, get_last_items, [NodeId, LJID, 1]) of
|
||||
+ {result, [LastItem]} ->
|
||||
+ {ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
+ {ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
+ event_stanza_with_delay(
|
||||
+ [{xmlelement, "items", nodeAttr(Node),
|
||||
+ itemsEls([LastItem])}], ModifNow, ModifUSR);
|
||||
+ itemsEls([LastItem])}], ModifNow, ModifLjid);
|
||||
+ _ ->
|
||||
+ event_stanza(
|
||||
+ [{xmlelement, "items", nodeAttr(Node),
|
||||
+ itemsEls([])}])
|
||||
+ end;
|
||||
LastItem ->
|
||||
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification,
|
||||
{ModifNow, ModifLjid} = LastItem#pubsub_item.modification,
|
||||
- Stanza = event_stanza_with_delay(
|
||||
+ event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
- itemsEls([LastItem])}], ModifNow, ModifUSR),
|
||||
- itemsEls([LastItem])}], ModifNow, ModifLjid),
|
||||
- ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza)
|
||||
- end;
|
||||
+ itemsEls([LastItem])}], ModifNow, ModifUSR)
|
||||
+ itemsEls([LastItem])}], ModifNow, ModifLjid)
|
||||
+ end,
|
||||
+ ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
{result, []} ->
|
||||
@@ -2443,7 +2269,8 @@
|
||||
@@ -2453,29 +2279,12 @@
|
||||
error ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}=N) ->
|
||||
- case lists:member(Owner, Owners) of
|
||||
+ Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
+ Owners = node_owners_call(Type, NodeId),
|
||||
case lists:member(Owner, Owners) of
|
||||
+ case lists:member(Owner, node_owners_call(Type, NodeId)) of
|
||||
true ->
|
||||
OwnerJID = jlib:make_jid(Owner),
|
||||
@@ -2453,24 +2280,7 @@
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({JID, Affiliation}) ->
|
||||
- node_call(Type, set_affiliation, [NodeId, JID, Affiliation]),
|
||||
@@ -594,10 +591,10 @@
|
||||
- ok
|
||||
- end
|
||||
+ node_call(Type, set_affiliation, [NodeId, JID, Affiliation])
|
||||
end, FilteredEntities),
|
||||
end, Entities),
|
||||
{result, []};
|
||||
_ ->
|
||||
@@ -2523,11 +2333,11 @@
|
||||
@@ -2528,11 +2337,11 @@
|
||||
end.
|
||||
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
|
||||
@@ -611,7 +608,7 @@
|
||||
OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
|
||||
{"subid", SubID}|nodeAttr(Node)],
|
||||
[XdataEl]},
|
||||
@@ -2553,7 +2363,7 @@
|
||||
@@ -2558,7 +2367,7 @@
|
||||
end.
|
||||
|
||||
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
|
||||
@@ -620,7 +617,7 @@
|
||||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -2582,7 +2392,7 @@
|
||||
@@ -2587,7 +2396,7 @@
|
||||
write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
|
||||
{error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
|
||||
write_sub(Subscriber, NodeID, SubID, Options) ->
|
||||
@@ -629,7 +626,7 @@
|
||||
{error, notfound} ->
|
||||
{error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
{result, _} ->
|
||||
@@ -2750,8 +2560,8 @@
|
||||
@@ -2755,8 +2564,8 @@
|
||||
{"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
|
||||
end,
|
||||
@@ -640,7 +637,7 @@
|
||||
true ->
|
||||
Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
|
||||
|
||||
@@ -3088,7 +2898,7 @@
|
||||
@@ -3040,7 +2849,7 @@
|
||||
{Depth, [{N, get_node_subs(N)} || N <- Nodes]}
|
||||
end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))}
|
||||
end,
|
||||
@@ -649,7 +646,7 @@
|
||||
{result, CollSubs} -> CollSubs;
|
||||
_ -> []
|
||||
end.
|
||||
@@ -3102,9 +2912,9 @@
|
||||
@@ -3054,9 +2863,9 @@
|
||||
|
||||
get_options_for_subs(NodeID, Subs) ->
|
||||
lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
|
||||
@@ -661,7 +658,7 @@
|
||||
_ -> Acc
|
||||
end;
|
||||
(_, Acc) ->
|
||||
@@ -3308,6 +3118,30 @@
|
||||
@@ -3266,6 +3075,30 @@
|
||||
Result
|
||||
end.
|
||||
|
||||
@@ -692,7 +689,7 @@
|
||||
%% @spec (Host, Options) -> MaxItems
|
||||
%% Host = host()
|
||||
%% Options = [Option]
|
||||
@@ -3704,7 +3538,13 @@
|
||||
@@ -3658,7 +3491,13 @@
|
||||
tree_action(Host, Function, Args) ->
|
||||
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
|
||||
Fun = fun() -> tree_call(Host, Function, Args) end,
|
||||
@@ -707,7 +704,7 @@
|
||||
|
||||
%% @doc <p>node plugin call.</p>
|
||||
node_call(Type, Function, Args) ->
|
||||
@@ -3724,13 +3564,13 @@
|
||||
@@ -3678,13 +3517,13 @@
|
||||
|
||||
node_action(Host, Type, Function, Args) ->
|
||||
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
|
||||
@@ -723,7 +720,7 @@
|
||||
case tree_call(Host, get_node, [Host, Node]) of
|
||||
N when is_record(N, pubsub_node) ->
|
||||
case Action(N) of
|
||||
@@ -3743,8 +3583,14 @@
|
||||
@@ -3697,8 +3536,14 @@
|
||||
end
|
||||
end, Trans).
|
||||
|
||||
@@ -740,7 +737,7 @@
|
||||
{result, Result} -> {result, Result};
|
||||
{error, Error} -> {error, Error};
|
||||
{atomic, {result, Result}} -> {result, Result};
|
||||
@@ -3752,6 +3598,15 @@
|
||||
@@ -3706,6 +3551,15 @@
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
@@ -756,7 +753,7 @@
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
@@ -3760,6 +3615,17 @@
|
||||
@@ -3714,6 +3568,17 @@
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
|
||||
+8
-20
@@ -31,7 +31,7 @@
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
stream_feature_register/2,
|
||||
stream_feature_register/1,
|
||||
unauthenticated_iq_register/4,
|
||||
try_register/5,
|
||||
process_iq/3]).
|
||||
@@ -65,7 +65,7 @@ stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_REGISTER).
|
||||
|
||||
|
||||
stream_feature_register(Acc, _Host) ->
|
||||
stream_feature_register(Acc) ->
|
||||
[{xmlelement, "register",
|
||||
[{"xmlns", ?NS_FEATURE_IQREGISTER}], []} | Acc].
|
||||
|
||||
@@ -166,20 +166,13 @@ process_iq(From, To,
|
||||
#jid{user = User, lserver = Server} ->
|
||||
try_set_password(User, Server, Password, IQ, SubEl);
|
||||
_ ->
|
||||
case check_from(From, Server) of
|
||||
allow ->
|
||||
case try_register(User, Server, Password,
|
||||
Source, Lang) of
|
||||
ok ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [SubEl]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, Error]}
|
||||
end;
|
||||
deny ->
|
||||
case try_register(User, Server, Password,
|
||||
Source, Lang) of
|
||||
ok ->
|
||||
IQ#iq{type = result, sub_el = [SubEl]};
|
||||
{error, Error} ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_FORBIDDEN]}
|
||||
sub_el = [SubEl, Error]}
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
@@ -300,11 +293,6 @@ send_registration_notifications(UJID, Source) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
check_from(#jid{user = "", server = ""}, _Server) ->
|
||||
allow;
|
||||
check_from(JID, Server) ->
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access_from, none),
|
||||
acl:match_rule(Server, Access, JID).
|
||||
|
||||
check_timeout(undefined) ->
|
||||
true;
|
||||
|
||||
+2
-2
@@ -604,7 +604,7 @@ in_state_change(to, in, subscribed) -> none;
|
||||
in_state_change(to, in, unsubscribe) -> {to, none};
|
||||
in_state_change(to, in, unsubscribed) -> {none, in};
|
||||
in_state_change(from, none, subscribe) -> none;
|
||||
in_state_change(from, none, subscribed) -> {both, none};
|
||||
in_state_change(from, none, subscribed) -> none;
|
||||
in_state_change(from, none, unsubscribe) -> {none, none};
|
||||
in_state_change(from, none, unsubscribed) -> none;
|
||||
in_state_change(from, out, subscribe) -> none;
|
||||
@@ -633,7 +633,7 @@ out_state_change(none, both, subscribed) -> {from, out};
|
||||
out_state_change(none, both, unsubscribe) -> {none, in};
|
||||
out_state_change(none, both, unsubscribed) -> {none, out};
|
||||
out_state_change(to, none, subscribe) -> none;
|
||||
out_state_change(to, none, subscribed) -> {both, none};
|
||||
out_state_change(to, none, subscribed) -> none;
|
||||
out_state_change(to, none, unsubscribe) -> {none, none};
|
||||
out_state_change(to, none, unsubscribed) -> none;
|
||||
out_state_change(to, in, subscribe) -> none;
|
||||
|
||||
@@ -351,7 +351,8 @@ process_item_set(From, To, {xmlelement, _Name, Attrs, Els}) ->
|
||||
Item2 = process_item_els(Item1, Els),
|
||||
case Item2#roster.subscription of
|
||||
remove ->
|
||||
odbc_queries:del_roster(LServer, Username, SJID);
|
||||
io:format("del_roster: ~p ~p ~p \n", [LServer, Username, SJID]),
|
||||
odbc_queries:del_roster(LServer, Username, SJID);
|
||||
_ ->
|
||||
ItemVals = record_to_string(Item2),
|
||||
ItemGroups = groups_to_string(Item2),
|
||||
@@ -665,7 +666,7 @@ in_state_change(to, in, subscribed) -> none;
|
||||
in_state_change(to, in, unsubscribe) -> {to, none};
|
||||
in_state_change(to, in, unsubscribed) -> {none, in};
|
||||
in_state_change(from, none, subscribe) -> none;
|
||||
in_state_change(from, none, subscribed) -> {both, none};
|
||||
in_state_change(from, none, subscribed) -> none;
|
||||
in_state_change(from, none, unsubscribe) -> {none, none};
|
||||
in_state_change(from, none, unsubscribed) -> none;
|
||||
in_state_change(from, out, subscribe) -> none;
|
||||
@@ -694,7 +695,7 @@ out_state_change(none, both, subscribed) -> {from, out};
|
||||
out_state_change(none, both, unsubscribe) -> {none, in};
|
||||
out_state_change(none, both, unsubscribed) -> {none, out};
|
||||
out_state_change(to, none, subscribe) -> none;
|
||||
out_state_change(to, none, subscribed) -> {both, none};
|
||||
out_state_change(to, none, subscribed) -> none;
|
||||
out_state_change(to, none, unsubscribe) -> {none, none};
|
||||
out_state_change(to, none, unsubscribed) -> none;
|
||||
out_state_change(to, in, subscribe) -> none;
|
||||
|
||||
@@ -152,45 +152,16 @@ get_user_roster(Items, US) ->
|
||||
end, SRUsers, Items),
|
||||
|
||||
%% Export items in roster format:
|
||||
ModVcard = get_vcard_module(S),
|
||||
SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
|
||||
us = US,
|
||||
jid = {U1, S1, ""},
|
||||
name = get_rosteritem_name(ModVcard, U1, S1),
|
||||
name = "",
|
||||
subscription = both,
|
||||
ask = none,
|
||||
groups = GroupNames} ||
|
||||
{{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
|
||||
SRItems ++ NewItems1.
|
||||
|
||||
get_vcard_module(Server) ->
|
||||
Modules = gen_mod:loaded_modules(Server),
|
||||
[M || M <- Modules,
|
||||
(M == mod_vcard) or (M == mod_vcard_odbc) or (M == mod_vcard_ldap)].
|
||||
|
||||
get_rosteritem_name([], _, _) ->
|
||||
"";
|
||||
get_rosteritem_name([ModVcard], U, S) ->
|
||||
From = jlib:make_jid("", S, mod_shared_roster),
|
||||
To = jlib:make_jid(U, S, ""),
|
||||
IQ = {iq,"",get,"vcard-temp","",
|
||||
{xmlelement,"vCard",[{"xmlns","vcard-temp"}],[]}},
|
||||
IQ_Vcard = ModVcard:process_sm_iq(From, To, IQ),
|
||||
try get_rosteritem_name_vcard(IQ_Vcard#iq.sub_el)
|
||||
catch E1:E2 ->
|
||||
?ERROR_MSG("Error ~p found when trying to get the vCard of ~s@~s "
|
||||
"in ~p:~n ~p", [E1, U, S, ModVcard, E2]),
|
||||
""
|
||||
end.
|
||||
|
||||
get_rosteritem_name_vcard([]) ->
|
||||
"";
|
||||
get_rosteritem_name_vcard([Vcard]) ->
|
||||
case xml:get_path_s(Vcard, [{elem, "NICKNAME"}, cdata]) of
|
||||
"" -> xml:get_path_s(Vcard, [{elem, "FN"}, cdata]);
|
||||
Nickname -> Nickname
|
||||
end.
|
||||
|
||||
%% This function rewrites the roster entries when moving or renaming
|
||||
%% them in the user contact list.
|
||||
process_item(RosterItem, Host) ->
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_sic.erl
|
||||
%%% Author : Karim Gemayel <karim.gemayel@process-one.net>
|
||||
%%% Purpose : XEP-0279 Server IP Check
|
||||
%%% Created : 6 Mar 2010 by Karim Gemayel <karim.gemayel@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_sic).
|
||||
-author('karim.gemayel@process-one.net').
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
process_local_iq/3,
|
||||
process_sm_iq/3
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-define(NS_SIC, "urn:xmpp:sic:0").
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_SIC, ?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_SIC, ?MODULE, process_sm_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_SIC),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SIC).
|
||||
|
||||
|
||||
process_local_iq(#jid{user = User, server = Server, resource = Resource}, _To,
|
||||
#iq{type = 'get', sub_el = _SubEl} = IQ) ->
|
||||
get_ip({User, Server, Resource}, IQ);
|
||||
|
||||
process_local_iq(_From, _To, #iq{type = 'set', sub_el = SubEl} = IQ) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
|
||||
process_sm_iq(#jid{user = User, server = Server, resource = Resource},
|
||||
#jid{user = User, server = Server},
|
||||
#iq{type = 'get', sub_el = _SubEl} = IQ) ->
|
||||
get_ip({User, Server, Resource}, IQ);
|
||||
|
||||
process_sm_iq(_From, _To, #iq{type = 'get', sub_el = SubEl} = IQ) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]};
|
||||
|
||||
process_sm_iq(_From, _To, #iq{type = 'set', sub_el = SubEl} = IQ) ->
|
||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
|
||||
|
||||
get_ip({User, Server, Resource},
|
||||
#iq{sub_el = {xmlelement, Name, Attrs, _} = SubEl} = IQ) ->
|
||||
case ejabberd_sm:get_user_ip(User, Server, Resource) of
|
||||
{IP, _} when is_tuple(IP) ->
|
||||
IQ#iq{
|
||||
type = 'result',
|
||||
sub_el = [
|
||||
{xmlelement, Name, Attrs,
|
||||
[{xmlcdata, list_to_binary(inet_parse:ntoa(IP))}]}
|
||||
]
|
||||
};
|
||||
_ ->
|
||||
IQ#iq{
|
||||
type = 'error',
|
||||
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]
|
||||
}
|
||||
end.
|
||||
+1
-2
@@ -267,8 +267,7 @@ set_vcard(User, LServer, VCARD) ->
|
||||
orgunit = OrgUnit, lorgunit = LOrgUnit
|
||||
})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD])
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
-define(TLFIELD(Type, Label, Var),
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
servers,
|
||||
backups,
|
||||
port,
|
||||
tls_options,
|
||||
encrypt,
|
||||
dn,
|
||||
base,
|
||||
password,
|
||||
@@ -181,7 +181,7 @@ init([Host, Opts]) ->
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password,
|
||||
State#state.tls_options),
|
||||
State#state.encrypt),
|
||||
case State#state.search of
|
||||
true ->
|
||||
ejabberd_router:register_route(State#state.myhost);
|
||||
@@ -686,11 +686,6 @@ parse_options(Host, Opts) ->
|
||||
ejabberd_config:get_local_option({ldap_encrypt, Host});
|
||||
E -> E
|
||||
end,
|
||||
LDAPTLSVerify = case gen_mod:get_opt(ldap_tls_verify, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_tls_verify, Host});
|
||||
Verify -> Verify
|
||||
end,
|
||||
LDAPPortTemp = case gen_mod:get_opt(ldap_port, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_port, Host});
|
||||
@@ -771,8 +766,7 @@ parse_options(Host, Opts) ->
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
encrypt = LDAPEncrypt,
|
||||
dn = RootDN,
|
||||
base = LDAPBase,
|
||||
password = Password,
|
||||
|
||||
@@ -247,9 +247,7 @@ set_vcard(User, LServer, VCARD) ->
|
||||
SLLocality, SLMiddle, SLNickname,
|
||||
SLOrgName, SLOrgUnit, SLocality,
|
||||
SMiddle, SNickname, SOrgName,
|
||||
SOrgUnit, SVCARD, Username),
|
||||
|
||||
ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD])
|
||||
SOrgUnit, SVCARD, Username)
|
||||
end.
|
||||
|
||||
-define(TLFIELD(Type, Label, Var),
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_vcard_xupdate.erl
|
||||
%%% Author : Igor Goryachev <igor@goryachev.org>
|
||||
%%% Purpose : Add avatar hash in presence on behalf of client (XEP-0153)
|
||||
%%% Created : 9 Mar 2007 by Igor Goryachev <igor@goryachev.org>
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_vcard_xupdate).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2,
|
||||
stop/1]).
|
||||
|
||||
%% hooks
|
||||
-export([update_presence/3,
|
||||
vcard_set/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(vcard_xupdate, {us, hash}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
|
||||
start(Host, _Opts) ->
|
||||
mnesia:create_table(vcard_xupdate,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, vcard_xupdate)}]),
|
||||
ejabberd_hooks:add(c2s_update_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:add(vcard_set, Host,
|
||||
?MODULE, vcard_set, 100),
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(c2s_update_presence, Host,
|
||||
?MODULE, update_presence, 100),
|
||||
ejabberd_hooks:delete(vcard_set, Host,
|
||||
?MODULE, vcard_set, 100),
|
||||
ok.
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
|
||||
update_presence({xmlelement, "presence", Attrs, _Els} = Packet, User, Host) ->
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
[] ->
|
||||
presence_with_xupdate(Packet, User, Host);
|
||||
_ ->
|
||||
Packet
|
||||
end;
|
||||
update_presence(Packet, _User, _Host) ->
|
||||
Packet.
|
||||
|
||||
vcard_set(LUser, LServer, VCARD) ->
|
||||
US = {LUser, LServer},
|
||||
case xml:get_path_s(VCARD, [{elem, "PHOTO"}, {elem, "BINVAL"}, cdata]) of
|
||||
[] ->
|
||||
remove_xupdate(LUser, LServer);
|
||||
BinVal ->
|
||||
add_xupdate(LUser, LServer, sha:sha(jlib:decode_base64(BinVal)))
|
||||
end,
|
||||
ejabberd_sm:force_update_presence(US).
|
||||
|
||||
%%====================================================================
|
||||
%% Mnesia storage
|
||||
%%====================================================================
|
||||
|
||||
add_xupdate(LUser, LServer, Hash) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#vcard_xupdate{us = {LUser, LServer}, hash = Hash})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
get_xupdate(LUser, LServer) ->
|
||||
case mnesia:dirty_read(vcard_xupdate, {LUser, LServer}) of
|
||||
[#vcard_xupdate{hash = Hash}] ->
|
||||
Hash;
|
||||
_ ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
remove_xupdate(LUser, LServer) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({vcard_xupdate, {LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Presence stanza rebuilding
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
presence_with_xupdate({xmlelement, "presence", Attrs, Els}, User, Host) ->
|
||||
XPhotoEl = build_xphotoel(User, Host),
|
||||
Els2 = presence_with_xupdate2(Els, [], XPhotoEl),
|
||||
{xmlelement, "presence", Attrs, Els2}.
|
||||
|
||||
presence_with_xupdate2([], Els2, XPhotoEl) ->
|
||||
lists:reverse([XPhotoEl | Els2]);
|
||||
%% This clause assumes that the x element contains only the XMLNS attribute:
|
||||
presence_with_xupdate2([{xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], _}
|
||||
| Els], Els2, XPhotoEl) ->
|
||||
presence_with_xupdate2(Els, Els2, XPhotoEl);
|
||||
presence_with_xupdate2([El | Els], Els2, XPhotoEl) ->
|
||||
presence_with_xupdate2(Els, [El | Els2], XPhotoEl).
|
||||
|
||||
build_xphotoel(User, Host) ->
|
||||
Hash = get_xupdate(User, Host),
|
||||
PhotoSubEls = case Hash of
|
||||
Hash when is_list(Hash) ->
|
||||
[{xmlcdata, Hash}];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
PhotoEl = [{xmlelement, "photo", [], PhotoSubEls}],
|
||||
{xmlelement, "x", [{"xmlns", ?NS_VCARD_UPDATE}], PhotoEl}.
|
||||
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Canviar password"}.
|
||||
{"Change User Password","Canviar Password d'Usuari"}.
|
||||
{"Chatroom configuration modified","Configuració de la sala de xat modificada"}.
|
||||
{"Chatroom is created","La sala s'ha creat"}.
|
||||
{"Chatroom is destroyed","La sala s'ha destruït"}.
|
||||
{"Chatroom is started","La sala s'ha iniciat"}.
|
||||
{"Chatroom is stopped","La sala s'ha aturat"}.
|
||||
{"Chatrooms","Sales de xat"}.
|
||||
{"Choose a username and password to register with this server","Tria nom d'usuari i password per a registrar-te en aquest servidor"}.
|
||||
{"Choose modules to stop","Selecciona mòduls a detindre"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Protocol"}.
|
||||
{"Publish-Subscribe","Publicar-subscriure't"}.
|
||||
{"PubSub subscriber request","Petició de subscriptor PubSub"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Eliminar tots els elements quan el publicant relevant es desconnecti"}.
|
||||
{"Queries to the conference members are not allowed in this room"," En aquesta sala no es permeten solicituts als membres de la sala"}.
|
||||
{"RAM and disc copy","Còpia en RAM i disc"}.
|
||||
{"RAM copy","Còpia en RAM"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Apager el Servei"}.
|
||||
{"~s invites you to the room ~s","~s et convida a la sala ~s"}.
|
||||
{"Specify the access model","Especificar el model d'accés"}.
|
||||
{"Specify the event message type","Especifica el tipus de missatge d'event"}.
|
||||
{"Specify the publisher model","Especificar el model del publicant"}.
|
||||
{"~s's Offline Messages Queue","~s's cua de missatges offline"}.
|
||||
{"Start","Iniciar"}.
|
||||
|
||||
+404
-425
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Změnit heslo"}.
|
||||
{"Change User Password","Změnit heslo uživatele"}.
|
||||
{"Chatroom configuration modified","Nastavení diskuzní místnosti bylo změněno"}.
|
||||
{"Chatroom is created","Konference vytvořena"}.
|
||||
{"Chatroom is destroyed","Konference zrušena"}.
|
||||
{"Chatroom is started","Konference spuštěna"}.
|
||||
{"Chatroom is stopped","Konference zastavena"}.
|
||||
{"Chatrooms","Konference"}.
|
||||
{"Choose a username and password to register with this server","Zadejte jméno uživatele a heslo pro registraci na tomto serveru"}.
|
||||
{"Choose modules to stop","Vyberte moduly, které mají být zastaveny"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Protokol"}.
|
||||
{"Publish-Subscribe","Publish-Subscribe"}.
|
||||
{"PubSub subscriber request","Žádost odběratele PubSub"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Smazat všechny položky, pokud se příslušný poskytovatel odpojí"}.
|
||||
{"Queries to the conference members are not allowed in this room","Požadavky (queries) na členy konference nejsou v této místnosti povolené"}.
|
||||
{"RAM and disc copy","Kopie RAM a disku"}.
|
||||
{"RAM copy","Kopie RAM"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Vypnout službu"}.
|
||||
{"~s invites you to the room ~s","~s vás zve do místnosti ~s"}.
|
||||
{"Specify the access model","Uveďte přístupový model"}.
|
||||
{"Specify the event message type","Zvolte typ zpráv pro události"}.
|
||||
{"Specify the publisher model","Specifikovat model pro publikování"}.
|
||||
{"~s's Offline Messages Queue","Fronta offline zpráv uživatele ~s"}.
|
||||
{"Start Modules at ","Spustit moduly na "}.
|
||||
|
||||
+404
-425
File diff suppressed because it is too large
Load Diff
+83
-89
@@ -1,20 +1,20 @@
|
||||
{"Access Configuration","Zugangskonfiguration"}.
|
||||
{"Access Control List Configuration","Konfiguration der Zugangskontrolllisten"}.
|
||||
{"Access Control List Configuration","Zugangskontroll-Liste (ACL) Konfiguration"}.
|
||||
{"Access control lists","Zugangskontroll-Listen (ACL)"}.
|
||||
{"Access Control Lists","Zugangskontroll-Listen (ACL)"}.
|
||||
{"Access denied by service policy","Zugang aufgrund der Dienstrichtlinien verweigert"}.
|
||||
{"Access rules","Zugangsregeln"}.
|
||||
{"Access Rules","Zugangsregeln"}.
|
||||
{"Action on user","Aktion auf Benutzer"}.
|
||||
{"Add Jabber ID","Jabber-ID hinzufügen"}.
|
||||
{"Add Jabber ID","Jabber ID hinzufügen"}.
|
||||
{"Add New","Neuen hinzufügen"}.
|
||||
{"Add User","Benutzer hinzufügen"}.
|
||||
{"Administration of ","Administration von "}.
|
||||
{"Administration","Verwaltung"}.
|
||||
{"Administrator privileges required","Administratorenrechte benötigt"}.
|
||||
{"A friendly name for the node","Ein merkbarer Name für den Knoten"}.
|
||||
{"A friendly name for the node","Ein passender Name für den Knoten"}.
|
||||
{"All activity","Alle Aktivitäten"}.
|
||||
{"Allow this Jabber ID to subscribe to this pubsub node?","Dieser Jabber-ID das Abonnement dieses pubsub-Knotens erlauben?"}.
|
||||
{"Allow this Jabber ID to subscribe to this pubsub node?","Erlauben sie dieser Jabber ID das Abonnement dieses pubsub Knotens?"}.
|
||||
{"Allow users to change the subject","Erlaube Benutzern das Thema zu ändern"}.
|
||||
{"Allow users to query other users","Erlaube Benutzern andere Benutzer abzufragen"}.
|
||||
{"Allow users to send invites","Erlaube Benutzern Einladungen zu senden"}.
|
||||
@@ -28,89 +28,85 @@
|
||||
{"April","April"}.
|
||||
{"August","August"}.
|
||||
{"Backup","Datensicherung"}.
|
||||
{"Backup Management","Datensicherungsverwaltung"}.
|
||||
{"Backup Management","Datensicherungsmanagement"}.
|
||||
{"Backup of ","Sicherung von "}.
|
||||
{"Backup to File at ","Datensicherung in die Datei "}.
|
||||
{"Bad format","Ungültiges Format"}.
|
||||
{"Birthday","Geburtsdatum"}.
|
||||
{"Change Password","Passwort ändern"}.
|
||||
{"Change User Password","Benutzer-Passwort ändern"}.
|
||||
{"Chatroom configuration modified","Chatraum-Konfiguration geändert"}.
|
||||
{"Chatroom is created","Chatraum wurde erstellt"}.
|
||||
{"Chatroom is destroyed","Chatraum wurde entfernt"}.
|
||||
{"Chatroom is started","Chatraum wurde gestartet"}.
|
||||
{"Chatroom is stopped","Chatraum wurde beendet"}.
|
||||
{"Change User Password","Benutzer Passwort ändern"}.
|
||||
{"Chatroom configuration modified","Chatraum Konfiguration geändert"}.
|
||||
{"Chatrooms","Chaträume"}.
|
||||
{"Choose a username and password to register with this server","Wählen sie zum Registrieren einen Benutzernamen und ein Passwort"}.
|
||||
{"Choose modules to stop","Wähle zu stoppende Module"}.
|
||||
{"Choose storage type of tables","Wähle Speichertyp der Tabellen"}.
|
||||
{"Choose whether to approve this entity's subscription.","Wähle Sie, ob dieses Abonnement akzeptiert werden soll."}.
|
||||
{"Choose whether to approve this entity's subscription.","Wähle ob dieses Abonnement bestätigt wird."}.
|
||||
{"City","Stadt"}.
|
||||
{"Commands","Befehle"}.
|
||||
{"Conference room does not exist","Konferenzraum existiert nicht"}.
|
||||
{"Configuration","Konfiguration"}.
|
||||
{"Configuration of room ~s","Konfiguration für Raum ~s"}.
|
||||
{"Connected Resources:","Verbundene Ressourcen"}.
|
||||
{"Connected Resources:","Verbundene Resourcen"}.
|
||||
{"Connections parameters","Verbindungsparameter"}.
|
||||
{"Country","Land"}.
|
||||
{"CPU Time:","CPU-Zeit:"}.
|
||||
{"CPU Time:","CPU Zeit:"}.
|
||||
{"Database","Datenbank"}.
|
||||
{"Database Tables at ","Datenbanktabellen auf "}.
|
||||
{"Database Tables Configuration at ","Datenbanktabellen-Konfiguration auf "}.
|
||||
{"Database Tables at ","Datenbank Tabellen bei "}.
|
||||
{"Database Tables Configuration at ","Datenbank Tabellen Konfiguration bei "}.
|
||||
{"December","Dezember"}.
|
||||
{"Default users as participants","Standardbenutzer als Teilnehmer"}.
|
||||
{"Delete","Löschen"}.
|
||||
{"Delete message of the day","Lösche Nachricht des Tages"}.
|
||||
{"Delete message of the day on all hosts","Lösche Nachricht des Tages auf allen Hosts"}.
|
||||
{"Delete Selected","Markierte löschen"}.
|
||||
{"Delete Selected","Markiertes löschen"}.
|
||||
{"Delete User","Benutzer löschen"}.
|
||||
{"Deliver event notifications","Ereignisbenachrichtigung zustellen"}.
|
||||
{"Deliver payloads with event notifications","Nachrichten mit mit Ereignis-Benachrichtigungen zustellen"}.
|
||||
{"Deliver event notifications","Versende Ereignisbenachrichtigungen"}.
|
||||
{"Deliver payloads with event notifications","Versende Nutzlast mit Ereignisbenachrichtigungen"}.
|
||||
{"Description:","Beschreibung:"}.
|
||||
{"Disc only copy","Nur auf Festplatte"}.
|
||||
{"Disc only copy","Festplatten Kopie"}.
|
||||
{"Displayed Groups:","Angezeigte Gruppen:"}.
|
||||
{"Dump Backup to Text File at ","Ausgabe der Sicherung in diese Textdatei "}.
|
||||
{"Dump to Text File","Ausgabe in Textdatei"}.
|
||||
{"Edit Properties","Einstellungen ändern"}.
|
||||
{"ejabberd IRC module","ejabberd IRC-Modul"}.
|
||||
{"ejabberd MUC module","ejabberd MUC-Modul"}.
|
||||
{"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe-Modul"}.
|
||||
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5-Bytestreams-Modul"}.
|
||||
{"ejabberd vCard module","ejabberd vCard-Modul"}.
|
||||
{"ejabberd IRC module","ejabberd IRC Modul"}.
|
||||
{"ejabberd MUC module","ejabberd MUC Modul"}.
|
||||
{"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe Modul"}.
|
||||
{"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams Modul"}.
|
||||
{"ejabberd vCard module","ejabberd vCard Modul"}.
|
||||
{"ejabberd virtual hosts","ejabberd virtuelle Hosts"}.
|
||||
{"ejabberd Web Admin","ejabberd Web-Admin"}.
|
||||
{"ejabberd Web Admin","ejabberd Web Admin"}.
|
||||
{"Elements","Elemente"}.
|
||||
{"Email","E-Mail"}.
|
||||
{"Enable logging","Protokollierung aktivieren"}.
|
||||
{"Enable logging","Log-Funktion aktivieren"}.
|
||||
{"Encoding for server ~b","Kodierung für Server ~b"}.
|
||||
{"End User Session","Benutzer-Sitzung beenden"}.
|
||||
{"End User Session","Benutzer Sitzung beenden"}.
|
||||
{"Enter list of {Module, [Options]}","Geben sie eine Liste bestehend aus {Modul, [Optionen]} ein"}.
|
||||
{"Enter nickname you want to register","Geben sie den zu registrierenden Spitznamen ein"}.
|
||||
{"Enter path to backup file","Geben sie den Pfad zur Datensicherung ein"}.
|
||||
{"Enter path to jabberd14 spool dir","Geben Sie den Pfad zum jabberd14-Spool-Verzeichnis ein"}.
|
||||
{"Enter path to jabberd14 spool file","Geben Sie den Pfad zur jabberd14-Spool-Datei ein"}.
|
||||
{"Enter path to jabberd14 spool dir","Geben sie den Pfad zum jabberd14 spool Verzeichnis ein"}.
|
||||
{"Enter path to jabberd14 spool file","Geben sie den Pfad zur jabberd14 spool Datei ein"}.
|
||||
{"Enter path to text file","Geben sie den Pfad zur Textdatei ein"}.
|
||||
{"Enter the text you see","Geben sie den Text den sie sehen ein"}.
|
||||
{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","Geben sie Benutzernamen und Kodierung für Verbindungen zu IRC Servern an. Drücken sie 'Mehr' um leere Felder hinzuzufügen. Drücken sie 'Beenden' um die Einstellungen zu speichern."}.
|
||||
{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","Geben Sie Benutzernamen und Zeichenkodierung für die Verbindung zum IRC-Server an"}.
|
||||
{"Erlang Jabber Server","Erlang Jabber-Server"}.
|
||||
{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","Geben sie Benutzernamen, Kodierungen, Ports und Passwörter für die Verbindung zu IRC Servern an"}.
|
||||
{"Erlang Jabber Server","Erlang Jabber Server"}.
|
||||
{"Error","Fehler"}.
|
||||
{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","Beispiel: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}.
|
||||
{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Alle Benutzerdaten des Servers in PIEFXIS Dateien (XEP-0227) exportieren:"}.
|
||||
{"Export data of users in a host to PIEFXIS files (XEP-0227):","Alle Benutzerdaten des Hosts in PIEFXIS Dateien (XEP-0227) exportieren:"}.
|
||||
{"Family Name","Nachname"}.
|
||||
{"February","Februar"}.
|
||||
{"Fill in fields to search for any matching Jabber User","Füllen Sie die Felder aus, um nach passenden Jabber-Benutzern zu suchen"}.
|
||||
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","Füllen Sie die Felder aus, um nach passenden Jabber-Benutzern zu suchen (beenden Sie ein Feld mit *, um auch nach Teilzeichenketten zu suchen)"}.
|
||||
{"Fill in fields to search for any matching Jabber User","Felder ausfüllen, um nach passenden Jabber Benutzern zu suchen"}.
|
||||
{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","Füllen sie die Felder aus, um nach passenden Jabber Benutzern zu suchen (beenden sie ein Feld mit *, um auch nach Teilzeichenketten zu suchen)"}.
|
||||
{"Friday","Freitag"}.
|
||||
{"From ~s","Von ~s"}.
|
||||
{"From","Von"}.
|
||||
{"Full Name","Vollständiger Name"}.
|
||||
{"Full Name","Gesamter Name"}.
|
||||
{"Get Number of Online Users","Anzahl der angemeldeten Benutzer abrufen"}.
|
||||
{"Get Number of Registered Users","Anzahl der registrierten Benutzer abrufen"}.
|
||||
{"Get User Last Login Time","letzte Anmeldezeit abrufen"}.
|
||||
{"Get User Password","Benutzer-Passwort abrufen"}.
|
||||
{"Get User Statistics","Benutzer-Statistiken abrufen"}.
|
||||
{"Get User Password","Benutzer Passwort abrufen"}.
|
||||
{"Get User Statistics","Benutzer Statistik abrufen"}.
|
||||
{"Group ","Gruppe "}.
|
||||
{"Groups","Gruppen"}.
|
||||
{"has been banned","wurde gebannt"}.
|
||||
@@ -127,8 +123,8 @@
|
||||
{"Import User from File at ","Benutzer aus dieser Datei importieren "}.
|
||||
{"Import users data from a PIEFXIS file (XEP-0227):","Benutzerdaten von einer PIEFXIS Datei (XEP-0227) importieren:"}.
|
||||
{"Import users data from jabberd14 spool directory:","Importiere Benutzer von jabberd14 Spool Verzeichnis:"}.
|
||||
{"Import Users from Dir at ","Benutzer importieren aus dem Verzeichnis "}.
|
||||
{"Import Users From jabberd14 Spool Files","Importiere Benutzer aus jabberd14-Spool-Dateien"}.
|
||||
{"Import Users from Dir at ","Benutzer vom Verzeichnis importieren "}.
|
||||
{"Import Users From jabberd14 Spool Files","Importiere Benutzer von jabberd14 Spool Dateien"}.
|
||||
{"Improper message type","Unzulässiger Nachrichtentyp"}.
|
||||
{"Incorrect password","Falsches Passwort"}.
|
||||
{"Invalid affiliation: ~s","Ungültige Mitgliedschaft: ~s"}.
|
||||
@@ -140,13 +136,13 @@
|
||||
{"IRC settings","IRC Einstellungen"}.
|
||||
{"IRC Transport","IRC Transport"}.
|
||||
{"IRC username","IRC Benutzername"}.
|
||||
{"IRC Username","IRC-Benutzername"}.
|
||||
{"IRC Username","IRC Benutzername"}.
|
||||
{"is now known as","ist nun bekannt als"}.
|
||||
{"It is not allowed to send private messages","Es ist nicht erlaubt private Nachrichten zu senden"}.
|
||||
{"It is not allowed to send private messages of type \"groupchat\"","Es ist nicht erlaubt private Nachrichten des Typs \"Gruppenchat\" zu senden"}.
|
||||
{"It is not allowed to send private messages to the conference","Es ist nicht erlaubt private Nachrichten an den Raum zu schicken"}.
|
||||
{"Jabber ID","Jabber ID"}.
|
||||
{"Jabber ID ~s is invalid","Die Jabber-ID ~s ist ungültig"}.
|
||||
{"Jabber ID ~s is invalid","Jabber ID ~s ist ungültig"}.
|
||||
{"January","Januar"}.
|
||||
{"Join IRC channel","IRC Channel beitreten"}.
|
||||
{"joins the room","kommt in den Raum"}.
|
||||
@@ -154,7 +150,7 @@
|
||||
{"Join the IRC channel in this Jabber ID: ~s","Den IRC Channel mit dieser Jabber ID beitreten: ~s"}.
|
||||
{"July","Juli"}.
|
||||
{"June","Juni"}.
|
||||
{"Last Activity","Letzte Aktivität"}.
|
||||
{"Last Activity","Letzte Aktion"}.
|
||||
{"Last login","Letzte Anmeldung"}.
|
||||
{"Last month","Letzter Monat"}.
|
||||
{"Last year","Letztes Jahr"}.
|
||||
@@ -166,8 +162,8 @@
|
||||
{"Make participants list public","Teilnehmerliste öffentlich machen"}.
|
||||
{"Make room captcha protected","Raum mit Verifizierung (Captcha) versehen"}.
|
||||
{"Make room members-only","Raum nur für Mitglieder zugänglich machen"}.
|
||||
{"Make room moderated","Raum moderiert machen"}.
|
||||
{"Make room password protected","Raum mit Passwort schützen"}.
|
||||
{"Make room moderated","Raum modieriert machen"}.
|
||||
{"Make room password protected","Raum passwortgeschützt machen"}.
|
||||
{"Make room persistent","Raum persistent machen"}.
|
||||
{"Make room public searchable","Raum öffentlich suchbar machen"}.
|
||||
{"March","März"}.
|
||||
@@ -191,11 +187,11 @@
|
||||
{"Name","Vorname"}.
|
||||
{"Never","Nie"}.
|
||||
{"Nickname Registration at ","Registrieren des Spitznamens bei"}.
|
||||
{"Nickname ~s does not exist in the room","Der Spitzname ~s existiert im Raum nicht"}.
|
||||
{"Nickname ~s does not exist in the room","Spitzname ~s existiert im Raum nicht"}.
|
||||
{"Nickname","Spitzname"}.
|
||||
{"No body provided for announce message","Kein Text für die Ankündigung angegeben"}.
|
||||
{"No Data","Keine Daten"}.
|
||||
{"Node ID","Knoten-ID"}.
|
||||
{"Node ID","Knoten ID"}.
|
||||
{"Node ","Knoten "}.
|
||||
{"Node not found","Knoten nicht gefunden"}.
|
||||
{"Nodes","Knoten"}.
|
||||
@@ -204,15 +200,15 @@
|
||||
{"No resource provided","Keine Ressource angegeben"}.
|
||||
{"Not Found","nicht gefunden"}.
|
||||
{"Notify subscribers when items are removed from the node","Abonnenten benachrichtigen, wenn Einträge vom Knoten entfernt werden"}.
|
||||
{"Notify subscribers when the node configuration changes","Abonnenten benachrichtigen, wenn sich die Knotenkonfiguration ändert"}.
|
||||
{"Notify subscribers when the node configuration changes","Abonnenten benachrichtigen, wenn die Knotenkonfiguration sich ändert"}.
|
||||
{"Notify subscribers when the node is deleted","Abonnenten benachrichtigen, wenn der Knoten gelöscht wird"}.
|
||||
{"November","November"}.
|
||||
{"Number of occupants","Anzahl der Teilnehmer"}.
|
||||
{"Number of online users","Anzahl der angemeldeten Benutzer"}.
|
||||
{"Number of registered users","Anzahl der registrierten Benutzer"}.
|
||||
{"October","Oktober"}.
|
||||
{"Offline Messages:","Offline-Nachrichten:"}.
|
||||
{"Offline Messages","Offline-Nachrichten"}.
|
||||
{"Offline Messages:","Offline Nachrichten:"}.
|
||||
{"Offline Messages","Offline Nachrichten"}.
|
||||
{"OK","OK"}.
|
||||
{"Online","Angemeldet"}.
|
||||
{"Online Users:","Angemeldete Benutzer:"}.
|
||||
@@ -222,13 +218,13 @@
|
||||
{"Only moderators are allowed to change the subject in this room","Nur Moderatoren dürfen das Thema in diesem Raum ändern"}.
|
||||
{"Only occupants are allowed to send messages to the conference","Nur Teilnehmer dürfen Nachrichten an den Raum schicken"}.
|
||||
{"Only occupants are allowed to send queries to the conference","Nur Teilnehmer sind berechtig Anfragen an die Konferenz zu senden"}.
|
||||
{"Only service administrators are allowed to send service messages","Nur Service-Administratoren sind berechtigt, Servicenachrichten zu versenden"}.
|
||||
{"Only service administrators are allowed to send service messages","Nur Service Administratoren sind berechtigt, Servicenachrichten zu senden"}.
|
||||
{"Options","Optionen"}.
|
||||
{"Organization Name","Organisation"}.
|
||||
{"Organization Name","Firmenname"}.
|
||||
{"Organization Unit","Abteilung"}.
|
||||
{"Outgoing s2s Connections:","Ausgehende s2s-Verbindungen:"}.
|
||||
{"Outgoing s2s Connections","Ausgehende s2s-Verbindungen"}.
|
||||
{"Outgoing s2s Servers:","Ausgehende s2s-Server:"}.
|
||||
{"Outgoing s2s Connections:","Ausgehende s2s Verbindungen:"}.
|
||||
{"Outgoing s2s Connections","Ausgehende s2s Verbindungen"}.
|
||||
{"Outgoing s2s Servers:","Ausgehende s2s Server:"}.
|
||||
{"Owner privileges required","Besitzerrechte benötigt"}.
|
||||
{"Packet","Paket"}.
|
||||
{"Password ~b","Passwort ~b"}.
|
||||
@@ -237,25 +233,24 @@
|
||||
{"Password Verification","Passwort bestätigen"}.
|
||||
{"Path to Dir","Pfad zum Verzeichnis"}.
|
||||
{"Path to File","Pfad zur Datei"}.
|
||||
{"Pending","anhängig"}.
|
||||
{"Pending","schwebend"}.
|
||||
{"Period: ","Zeitraum: "}.
|
||||
{"Persist items to storage","Einträge dauerhaft speichern"}.
|
||||
{"Ping","Ping"}.
|
||||
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Beachten sie, das diese Optionen nur die eingebaute Mnesia-Datenbank sichern. Wenn sie das ODBC-Modul verwenden, müssen sie die SQL-Datenbank manuell sichern."}.
|
||||
{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Beachten sie, das diese Optionen nur die eingebaute Mnesia Datenbank sichert. Wenn sie das ODBC Modul verwenden müssen sie die SQL Datenbank zusätzlich manuell sichern."}.
|
||||
{"Pong","Pong"}.
|
||||
{"Port ~b","Port ~b"}.
|
||||
{"Port","Port"}.
|
||||
{"Present real Jabber IDs to","Echte Jabber-IDs anzeigen für"}.
|
||||
{"Present real Jabber IDs to","Echte Jabber IDs anzeigen für"}.
|
||||
{"private, ","privat, "}.
|
||||
{"Protocol","Protokoll"}.
|
||||
{"Publish-Subscribe","Publish-Subscribe"}.
|
||||
{"PubSub subscriber request","PubSub-Abonnenten-Anfrage"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Alle Einträge entfernen, wenn der relevante Veröffentlicher offline geht"}.
|
||||
{"PubSub subscriber request","PubSub Abonnenten Anfrage"}.
|
||||
{"Queries to the conference members are not allowed in this room","Anfragen an die Teilnehmer sind in diesem Raum nicht erlaubt"}.
|
||||
{"RAM and disc copy","RAM und Festplatte"}.
|
||||
{"RAM copy","Nur RAM"}.
|
||||
{"RAM and disc copy","RAM und Festplatten Kopie"}.
|
||||
{"RAM copy","RAM Kopie"}.
|
||||
{"Raw","Unformatiert"}.
|
||||
{"Really delete message of the day?","Die Nachricht des Tages wirklich löschen?"}.
|
||||
{"Really delete message of the day?","Wirklich die Nachricht des Tages löschen?"}.
|
||||
{"Recipient is not in the conference room","Der Empfänger ist nicht im Raum"}.
|
||||
{"Registered Users:","Registrierte Benutzer:"}.
|
||||
{"Registered Users","Registrierte Benutzer"}.
|
||||
@@ -269,11 +264,11 @@
|
||||
{"Restart","Neustart"}.
|
||||
{"Restart Service","Dienst neustarten"}.
|
||||
{"Restore Backup from File at ","Datenwiederherstellung aus der Datei "}.
|
||||
{"Restore binary backup after next ejabberd restart (requires less memory):","Stelle binäre Sicherung beim nächsten ejabberd-Neustart wieder her (benötigt weniger Speicher):"}.
|
||||
{"Restore binary backup after next ejabberd restart (requires less memory):","Stelle binäre Sicherung beim nächsten ejabberd Neustart wieder her (benötigt weniger Speicher):"}.
|
||||
{"Restore binary backup immediately:","Stelle binäre Sicherung sofort wieder her:"}.
|
||||
{"Restore plain text backup immediately:","Stelle Klartext-Sicherung sofort wieder her:"}.
|
||||
{"Restore","Wiederherstellung"}.
|
||||
{"Room Configuration","Raum-Konfiguration"}.
|
||||
{"Room Configuration","Raum Konfiguration"}.
|
||||
{"Room creation is denied by service policy","Anlegen des Raumes aufgrund der Dienstrichtlinien verweigert"}.
|
||||
{"Room description","Raum Beschreibung"}.
|
||||
{"Room Occupants","Teilnehmer in diesem Raum"}.
|
||||
@@ -282,11 +277,11 @@
|
||||
{"Roster","Kontaktliste"}.
|
||||
{"Roster of ","Kontaktliste von "}.
|
||||
{"Roster size","Kontaktlistengröße"}.
|
||||
{"RPC Call Error","Fehler bei RPC-Aufruf"}.
|
||||
{"RPC Call Error","RPC Abruf-Fehler"}.
|
||||
{"Running Nodes","Aktive Knoten"}.
|
||||
{"~s access rule configuration","~s Zugangsregel-Konfiguration"}.
|
||||
{"~s access rule configuration","~s Zugangsregel Konfiguration"}.
|
||||
{"Saturday","Samstag"}.
|
||||
{"Script check","Script-Überprüfung"}.
|
||||
{"Script check","Script Überprüfung"}.
|
||||
{"Search Results for ","Suchergebnisse für "}.
|
||||
{"Search users in ","Benutzer suchen in "}.
|
||||
{"Send announcement to all online users on all hosts","Sende Ankündigung an alle angemeldeten Benutzer auf allen Hosts"}.
|
||||
@@ -301,50 +296,49 @@
|
||||
{"Show Integral Table","Vollständige Tabelle anzeigen"}.
|
||||
{"Show Ordinary Table","Normale Tabelle anzeigen"}.
|
||||
{"Shut Down Service","Dienst herunterfahren"}.
|
||||
{"~s invites you to the room ~s","~s lädt Sie in den Raum ~s ein"}.
|
||||
{"~s invites you to the room ~s","~s lädt sie in den Raum ~s ein"}.
|
||||
{"Specify the access model","Geben sie das Zugangsmodell an"}.
|
||||
{"Specify the event message type","Geben sie den Ereignis-Nachrichtentyp an"}.
|
||||
{"Specify the publisher model","Geben sie das Publikationsmodell an"}.
|
||||
{"~s's Offline Messages Queue","~s's Offline-Nachrichten-Warteschlange"}.
|
||||
{"Start Modules at ","Starte Module auf "}.
|
||||
{"~s's Offline Messages Queue","~s's Offline Nachrichten Warteschlange"}.
|
||||
{"Start","Anfang"}.
|
||||
{"Start Modules at ","Starte Module bei "}.
|
||||
{"Start Modules","Module starten"}.
|
||||
{"Start","Starten"}.
|
||||
{"Statistics of ~p","Statistiken von ~p"}.
|
||||
{"Statistics","Statistik"}.
|
||||
{"Stop Modules at ","Stoppe Module auf "}.
|
||||
{"Stop Modules at ","Stoppe Module bei "}.
|
||||
{"Stop Modules","Module stoppen"}.
|
||||
{"Stopped Nodes","Inaktive Knoten"}.
|
||||
{"Stop","Stoppen"}.
|
||||
{"Stop","Stop"}.
|
||||
{"Storage Type","Speichertyp"}.
|
||||
{"Store binary backup:","Speichere binäre Sicherung:"}.
|
||||
{"Store plain text backup:","Speichere Klartext-Sicherung:"}.
|
||||
{"Subject","Betreff"}.
|
||||
{"Subject","Thema"}.
|
||||
{"Submit","Senden"}.
|
||||
{"Submitted","Gesendet"}.
|
||||
{"Subscriber Address","Abonnenten-Adresse"}.
|
||||
{"Subscriber Address","Abonnenten Adresse"}.
|
||||
{"Subscription","Abonnement"}.
|
||||
{"Sunday","Sonntag"}.
|
||||
{"That nickname is already in use by another occupant","Dieser Spitzname wird bereits von einem Teilnehmer genutzt"}.
|
||||
{"That nickname is registered by another person","Dieser Spitzname wurde bereits von jemand anderem registriert"}.
|
||||
{"The captcha is valid.","Die Verifizierung ist gültig."}.
|
||||
{"The collections with which a node is affiliated","Sammlungen, mit denen ein Knoten verknüpft ist"}.
|
||||
{"the password is","das Passwort lautet"}.
|
||||
{"This participant is kicked from the room because he sent an error message","Dieser Teilnehmer wurde aus dem Raum geworfen, da er eine Fehlernachricht gesendet hat"}.
|
||||
{"This participant is kicked from the room because he sent an error message to another participant","Dieser Teilnehmer wurde aus dem Raum geworfen, da er eine Fehlernachricht an einen anderen Teilnehmer gesendet hat"}.
|
||||
{"the password is","das Passwort ist"}.
|
||||
{"This participant is kicked from the room because he sent an error message","Dieser Teilnehmer wurde aus dem Raum gekickt, da er eine Fehlernachricht gesendet hat"}.
|
||||
{"This participant is kicked from the room because he sent an error message to another participant","Dieser Teilnehmer wurde aus dem Raum gekickt, da er eine Fehlernachricht an einen anderen Teilnehmer gesendet hat"}.
|
||||
{"This participant is kicked from the room because he sent an error presence","Dieser Teilnehmer wurde aus dem Raum gekickt, da er einen fehlerhaften Status gesendet hat"}.
|
||||
{"This room is not anonymous","Dieser Raum ist nicht anonym"}.
|
||||
{"Thursday","Donnerstag"}.
|
||||
{"Time delay","Zeitverzögerung"}.
|
||||
{"Time","Zeit"}.
|
||||
{"To","An"}.
|
||||
{"To ~s","An ~s"}.
|
||||
{"To","Zu"}.
|
||||
{"Traffic rate limit is exceeded","Datenratenlimit wurde überschritten"}.
|
||||
{"Transactions Aborted:","Abgebrochene Transaktionen:"}.
|
||||
{"Transactions Committed:","Durchgeführte Transaktionen:"}.
|
||||
{"Transactions Logged:","Protokollierte Transaktionen:"}.
|
||||
{"Transactions Restarted:","Neugestartete Transaktionen:"}.
|
||||
{"Transactions Aborted:","Vorgänge abgebrochen:"}.
|
||||
{"Transactions Committed:","Vorgänge durchgeführt:"}.
|
||||
{"Transactions Logged:","Vorgänge protokolliert:"}.
|
||||
{"Transactions Restarted:","Vorgänge neu gestartet:"}.
|
||||
{"Tuesday","Dienstag"}.
|
||||
{"Unable to generate a captcha","Konnte Verifizierung (Captcha) nicht erstellen"}.
|
||||
{"Unable to generate a captcha","Konnte Verifizierung nicht erstellen"}.
|
||||
{"Unauthorized","Nicht berechtigt"}.
|
||||
{"Update","Aktualisieren"}.
|
||||
{"Update ","Aktualisierung "}.
|
||||
@@ -356,12 +350,12 @@
|
||||
{"Use of STARTTLS required","Verwendung von STARTTLS erforderlich"}.
|
||||
{"User ","Benutzer "}.
|
||||
{"User","Benutzer"}.
|
||||
{"User Management","Benutzerverwaltung"}.
|
||||
{"User Management","Benutzer Verwaltung"}.
|
||||
{"Users are not allowed to register accounts so quickly","Benutzer dürfen Konten nicht so schnell registrieren"}.
|
||||
{"Users","Benutzer"}.
|
||||
{"Users Last Activity","Letzte Benutzeraktivität"}.
|
||||
{"Validate","Validieren"}.
|
||||
{"vCard User Search","vCard-Benutzer-Suche"}.
|
||||
{"vCard User Search","vCard Benutzer Suche"}.
|
||||
{"Visitors are not allowed to change their nicknames in this room","Besucher dürfen in diesem Raum ihren Spitznamen nicht ändern"}.
|
||||
{"Visitors are not allowed to send messages to all occupants","Besucher dürfen nicht an alle Teilnehmer Nachrichten verschicken"}.
|
||||
{"Wednesday","Mittwoch"}.
|
||||
@@ -369,9 +363,9 @@
|
||||
{"Whether to allow subscriptions","Ob Abonnements erlaubt sind"}.
|
||||
{"You have been banned from this room","Sie wurden aus diesem Raum verbannt"}.
|
||||
{"You must fill in field \"Nickname\" in the form","Sie müssen das Feld \"Spitzname\" ausfüllen"}.
|
||||
{"You need an x:data capable client to configure mod_irc settings","Sie benötigen einen Client, der x:data unterstützt, um die mod_irc-Einstellungen zu konfigurieren"}.
|
||||
{"You need an x:data capable client to configure mod_irc settings","Sie benötigen einen Client, der x:data unterstützt, um die mod_irc Einstellungen zu konfigurieren"}.
|
||||
{"You need an x:data capable client to configure room","Sie benötigen einen Client, der x:data unterstützt, um den Raum zu konfigurieren"}.
|
||||
{"You need an x:data capable client to register nickname","Sie benötigen einen Client, der x:data unterstützt, um Ihren Spitznamen zu registrieren"}.
|
||||
{"You need an x:data capable client to search","Sie benötigen einen Client, der x:data unterstützt, um suchen zu können"}.
|
||||
{"Your contact offline message queue is full. The message has been discarded.","Ihre Offline-Nachrichten-Warteschlange ist voll. Die Nachricht wurde verworfen."}.
|
||||
{"Your contact offline message queue is full. The message has been discarded.","Ihre offline Kontakt Warteschlange ist voll. Die Nachricht wurde verworfen."}.
|
||||
{"Your messages to ~s are being blocked. To unblock them, visit ~s","Ihre Nachrichten an ~s werden blockiert. Um dies zu ändern, besuchen sie ~s"}.
|
||||
|
||||
+507
-517
File diff suppressed because it is too large
Load Diff
+403
-425
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Αλλαγή κωδικού"}.
|
||||
{"Change User Password","Αλλαγή Κωδικού Πρόσβασης Χρήστη"}.
|
||||
{"Chatroom configuration modified","Διαμόρφωση Αίθουσaς σύνεδριασης τροποποιηθηκε"}.
|
||||
{"Chatroom is created","Η αίθουσα σύνεδριασης δημιουργήθηκε"}.
|
||||
{"Chatroom is destroyed","Η αίθουσα σύνεδριασης διαγράφηκε"}.
|
||||
{"Chatroom is started","Η αίθουσα σύνεδριασης έχει ξεκινήσει"}.
|
||||
{"Chatroom is stopped","Η αίθουσα σύνεδριασης έχει σταματήσει"}.
|
||||
{"Chatrooms","Αίθουσες σύνεδριασης"}.
|
||||
{"Choose a username and password to register with this server","Επιλέξτε ένα όνομα χρήστη και κωδικό πρόσβασης για να εγγραφείτε σε αυτό τον διακομιστή"}.
|
||||
{"Choose modules to stop","Επιλέξτε modules για να σταματήσουν"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Πρωτόκολλο"}.
|
||||
{"Publish-Subscribe","Δημοσίευση-Εγγραφή"}.
|
||||
{"PubSub subscriber request","Αίτηση συνδρομητή Δημοσίευσης-Εγγραφής"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Διαγραφή όλων των στοιχείων όταν ο σχετικός εκδότης αποσυνδέεται"}.
|
||||
{"Queries to the conference members are not allowed in this room","Ερωτήματα πρώς τα μέλη της διασκέψεως δεν επιτρέπονται σε αυτήν την αίθουσα"}.
|
||||
{"RAM and disc copy","Αντίγραφο μόνο σε RAM καί δίσκο"}.
|
||||
{"RAM copy","Αντίγραφο σε RAM"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Κλείσιμο Υπηρεσίας"}.
|
||||
{"~s invites you to the room ~s","~s σας προσκαλεί στην αίθουσα ~s"}.
|
||||
{"Specify the access model","Καθορίστε το μοντέλο πρόσβασης"}.
|
||||
{"Specify the event message type","Καθορίστε τον τύπο μηνύματος συμβάντος"}.
|
||||
{"Specify the publisher model","Καθορίστε το μοντέλο εκδότη"}.
|
||||
{"~s's Offline Messages Queue","Η Σειρά Χωρίς Σύνδεση Μηνύματων τού ~s"}.
|
||||
{"Start Modules at ","Εκκίνηση Modules στο "}.
|
||||
|
||||
+405
-428
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Ŝanĝu pasvorton"}.
|
||||
{"Change User Password","Ŝanĝu pasvorton de uzanto"}.
|
||||
{"Chatroom configuration modified","Agordo de babilejo ŝanĝita"}.
|
||||
{"Chatroom is created","Babilejo kreita"}.
|
||||
{"Chatroom is destroyed","Babilejo neniigita"}.
|
||||
{"Chatroom is started","Babilejo lanĉita"}.
|
||||
{"Chatroom is stopped","Babilejo haltita"}.
|
||||
{"Chatrooms","Babilejoj"}.
|
||||
{"Choose a username and password to register with this server","Elektu uzantnomon kaj pasvorton por registri je ĉi tiu servilo"}.
|
||||
{"Choose modules to stop","Elektu modulojn por fini"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Protokolo"}.
|
||||
{"Publish-Subscribe","Public-Abonado"}.
|
||||
{"PubSub subscriber request","PubAbo abonpeto"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Forigu ĉiujn erojn kiam la rilata publikanto malkonektiĝas"}.
|
||||
{"Queries to the conference members are not allowed in this room","Malpermesas informmendoj al partoprenantoj en ĉi tiu babilejo"}.
|
||||
{"RAM and disc copy","RAM- kaj disk-kopio"}.
|
||||
{"RAM copy","RAM-kopio"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Haltigu Servon"}.
|
||||
{"~s invites you to the room ~s","~s invitas vin al la babilejo ~s"}.
|
||||
{"Specify the access model","Specifu atingo-modelon"}.
|
||||
{"Specify the event message type","Specifu tipo de event-mesaĝo"}.
|
||||
{"Specify the publisher model","Enmetu publikadan modelon"}.
|
||||
{"~s's Offline Messages Queue","Mesaĝo-atendovico de ~s"}.
|
||||
{"Start Modules at ","Startu modulojn je "}.
|
||||
|
||||
+404
-425
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Cambiar contraseña"}.
|
||||
{"Change User Password","Cambiar contraseña de usuario"}.
|
||||
{"Chatroom configuration modified","Configuración de la sala modificada"}.
|
||||
{"Chatroom is created","Se ha creado la sala"}.
|
||||
{"Chatroom is destroyed","Se ha destruido la sala"}.
|
||||
{"Chatroom is started","Se ha iniciado la sala"}.
|
||||
{"Chatroom is stopped","Se ha detenido la sala"}.
|
||||
{"Chatrooms","Salas de charla"}.
|
||||
{"Choose a username and password to register with this server","Escoge un nombre de usuario y contraseña para registrarte en este servidor"}.
|
||||
{"Choose modules to stop","Selecciona módulos a detener"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Protocolo"}.
|
||||
{"Publish-Subscribe","Servicio de Publicar-Subscribir"}.
|
||||
{"PubSub subscriber request","Petición de subscriptor de PubSub"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Borra todos los elementos cuando el publicador relevante se desconecta"}.
|
||||
{"Queries to the conference members are not allowed in this room","En esta sala no se permiten solicitudes a los miembros de la sala"}.
|
||||
{"RAM and disc copy","Copia en RAM y disco"}.
|
||||
{"RAM copy","Copia en RAM"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Detener el servicio"}.
|
||||
{"~s invites you to the room ~s","~s te invita a la sala ~s"}.
|
||||
{"Specify the access model","Especifica el modelo de acceso"}.
|
||||
{"Specify the event message type","Especifica el tipo del mensaje de evento"}.
|
||||
{"Specify the publisher model","Especificar el modelo del publicante"}.
|
||||
{"~s's Offline Messages Queue","Cola de mensajes diferidos de ~s"}.
|
||||
{"Start","Iniciar"}.
|
||||
|
||||
+405
-426
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Modifier le mot de passe"}.
|
||||
{"Change User Password","Changer le mot de passe de l'utilisateur"}.
|
||||
{"Chatroom configuration modified","Configuration du salon modifiée"}.
|
||||
{"Chatroom is created","Le salon de discussion est créé"}.
|
||||
{"Chatroom is destroyed","Le salon de discussion est détruit"}.
|
||||
{"Chatroom is started","Le salon de discussion a démarré"}.
|
||||
{"Chatroom is stopped","Le salon de discussion est stoppé"}.
|
||||
{"Chatrooms","Salons de discussion"}.
|
||||
{"Choose a username and password to register with this server","Choisissez un nom d'utilisateur et un mot de passe pour s'enregistrer sur ce serveur"}.
|
||||
{"Choose modules to stop","Sélectionnez les modules à arrêter"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Protocole"}.
|
||||
{"Publish-Subscribe","Publication-Abonnement"}.
|
||||
{"PubSub subscriber request","Demande d'abonnement PubSub"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Purger tous les items lorsque publieur est hors-ligne"}.
|
||||
{"Queries to the conference members are not allowed in this room","Les requêtes sur les membres de la conférence ne sont pas autorisé dans ce salon"}.
|
||||
{"RAM and disc copy","Copie en mémoire vive (RAM) et sur disque"}.
|
||||
{"RAM copy","Copie en mémoire vive (RAM)"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Arrêter le service"}.
|
||||
{"~s invites you to the room ~s","~s vous a invité dans la salle de discussion ~s"}.
|
||||
{"Specify the access model","Définir le modèle d'accès"}.
|
||||
{"Specify the event message type","Définir le type de message d'événement"}.
|
||||
{"Specify the publisher model","Définir le modèle de publication"}.
|
||||
{"~s's Offline Messages Queue","~s messages en file d'attente"}.
|
||||
{"Start","Démarrer"}.
|
||||
|
||||
+405
-426
File diff suppressed because it is too large
Load Diff
+402
-428
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,6 @@
|
||||
{"Change Password","Modificare la password"}.
|
||||
{"Change User Password","Cambiare la password dell'utente"}.
|
||||
{"Chatroom configuration modified","Configurazione della stanza modificata"}.
|
||||
{"Chatroom is created","La stanza è creata"}.
|
||||
{"Chatroom is destroyed","La stanza è eliminata"}.
|
||||
{"Chatroom is started","La stanza è avviata"}.
|
||||
{"Chatroom is stopped","La stanza è arrestata"}.
|
||||
{"Chatrooms","Stanze"}.
|
||||
{"Choose a username and password to register with this server","Scegliere un nome utente e una password per la registrazione con questo server"}.
|
||||
{"Choose modules to stop","Selezionare i moduli da arrestare"}.
|
||||
@@ -250,7 +246,6 @@
|
||||
{"Protocol","Protocollo"}.
|
||||
{"Publish-Subscribe","Pubblicazione-Iscrizione"}.
|
||||
{"PubSub subscriber request","Richiesta di iscrizione per PubSub"}.
|
||||
{"Purge all items when the relevant publisher goes offline","Cancella tutti gli elementi quando chi li ha pubblicati non è più online"}.
|
||||
{"Queries to the conference members are not allowed in this room","In questa stanza non sono consentite query ai membri della conferenza"}.
|
||||
{"RAM and disc copy","Copia in memoria (RAM) e su disco"}.
|
||||
{"RAM copy","Copia in memoria (RAM)"}.
|
||||
@@ -303,7 +298,6 @@
|
||||
{"Shut Down Service","Terminare il servizio"}.
|
||||
{"~s invites you to the room ~s","~s ti invita nella stanza ~s"}.
|
||||
{"Specify the access model","Specificare il modello di accesso"}.
|
||||
{"Specify the event message type","Specificare il tipo di messaggio di evento"}.
|
||||
{"Specify the publisher model","Definire il modello di pubblicazione"}.
|
||||
{"~s's Offline Messages Queue","Coda di ~s messaggi offline"}.
|
||||
{"Start","Avviare"}.
|
||||
|
||||
+404
-426
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user