Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c942e31e0 | |||
| 9912e1b548 | |||
| 09a5fafc8a | |||
| 64036bc121 | |||
| 33973677f2 | |||
| c4c0b45ced | |||
| 4e9c81b66a | |||
| 7a53b66e46 | |||
| d5ebef1362 | |||
| 26b814e367 | |||
| f675ae1e8c | |||
| b03828d2f7 | |||
| df2e050780 | |||
| 089331caf0 | |||
| db834ca421 | |||
| 5594e1470f | |||
| f7076f49b6 | |||
| 5c40d38b69 | |||
| e2763b7d11 | |||
| 80188d3a52 | |||
| 6575f899b6 | |||
| 3cd9cd0e36 | |||
| 576dda5c87 | |||
| 35a71fc493 | |||
| 1a3abf15c0 | |||
| c7c59766b2 | |||
| e681f4d4cf | |||
| b2a6eccded | |||
| a6e5fb269c | |||
| 96c99b6427 | |||
| 3fd5513549 | |||
| 804549415d | |||
| fa08db7091 | |||
| df0754ade9 | |||
| d3c4eab46f | |||
| 1c62edc53c | |||
| 17099eaaca | |||
| f3f80ea0ea | |||
| 8b45ee5208 | |||
| 1dc1795f2a | |||
| a435c4b123 | |||
| 151994c8d5 | |||
| 1aa36cb05c | |||
| e6e146c0f7 | |||
| adc37a8cc6 | |||
| b017702a15 | |||
| d6d58d13f7 | |||
| 8bcde56409 | |||
| e1e75b2852 | |||
| d6d3190b47 | |||
| fed00a0c17 | |||
| 4fcff0b6b9 | |||
| d1f1958dd4 | |||
| adc4a7f67c | |||
| ad6218cdf2 | |||
| edda5e1747 | |||
| 2f5dc0d35d | |||
| dbe71153dc | |||
| 8ddbf1c1c5 | |||
| 7d3633e91c | |||
| fef8c3a1f3 | |||
| 7355e810a8 | |||
| 6eb5b0d298 | |||
| e895b12aea | |||
| afad6c75a0 | |||
| 4a4a7afe22 | |||
| 24f5c964cd | |||
| 1b7cc33a7f | |||
| aa8c9bf0a5 | |||
| 92887e96bd | |||
| 4f101c318f | |||
| 4fcbd5894e | |||
| cd7beb9492 | |||
| 91fc4a6fbb | |||
| 158aa06760 | |||
| 9c2f0681f0 | |||
| cd2d207877 | |||
| 5ab3561700 | |||
| 19ab0174e4 | |||
| 82551036c2 | |||
| d09acc8231 | |||
| 98a78624cc | |||
| d923c70082 | |||
| 06d4889586 | |||
| ff8bd0c2ec | |||
| 12d6f3364b | |||
| 86859e5234 | |||
| b0755c7cfb | |||
| c5cf9b4e6d | |||
| 8760eef677 | |||
| 0b1f3ca148 | |||
| 0d7e69a65e | |||
| 466481d126 | |||
| b8d74ab2a9 | |||
| 1d8a69d94f | |||
| 72e3e0e6ad | |||
| 3ccbf1115c | |||
| c1e6c7d821 | |||
| 01c160251a | |||
| 8d4945aa7f | |||
| 06e3503976 | |||
| 1d244c7d9b | |||
| d3d17e8041 | |||
| bf5938ee1d | |||
| 8ad81a3db8 | |||
| ef67ec4286 | |||
| b2f06226b5 | |||
| 84b0e6333d | |||
| 9c9f278a96 |
@@ -12,8 +12,6 @@ To compile ejabberd you need:
|
||||
Avoid R14A and R14B.
|
||||
- exmpp 0.9.6 or higher
|
||||
- OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support
|
||||
(XEP-0138). Optional.
|
||||
- Erlang mysql library. Optional. MySQL authentication/storage.
|
||||
- Erlang pgsql library. Optional. PostgreSQL authentication/storage.
|
||||
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
|
||||
|
||||
+168
-80
@@ -112,10 +112,11 @@
|
||||
\newcommand{\iqdiscitem}[1]{\titem{\{iqdisc, Discipline\}} \ind{options!iqdisc}This specifies
|
||||
the processing discipline for #1 IQ queries (see section~\ref{modiqdiscoption}).}
|
||||
\newcommand{\hostitem}[1]{
|
||||
\titem{\{host, HostName\}} \ind{options!host} This option defines the Jabber ID of the
|
||||
service. If the \texttt{host} option is not specified, the Jabber ID will be the
|
||||
hostname of the virtual host with the prefix `\jid{#1.}'. The keyword "@HOST@"
|
||||
is replaced at start time with the real virtual host name.
|
||||
\titem{\{host, HostName\}} \ind{options!host}
|
||||
This option defines the Jabber ID of the service.
|
||||
If the option is not specified, the Jabber ID of the service will be the
|
||||
hostname of the virtual host preceded with `\jid{#1.}'.
|
||||
For more details see \ref{modhostoption}.
|
||||
}
|
||||
\newcommand{\backend}[1]{\titem{\{backend, mnesia|odbc\}} \ind{options!backend}
|
||||
Specify the backend used to store the tables: #1.
|
||||
@@ -250,8 +251,9 @@ and for example it doesn't read the file ejabberdctl.cfg.
|
||||
On a *nix system, if you want ejabberd to be started as daemon at boot time,
|
||||
copy \term{ejabberd.init} from the 'bin' directory to something like \term{/etc/init.d/ejabberd}
|
||||
(depending on your distribution).
|
||||
Create a system user called \term{ejabberd};
|
||||
it will be used by the script to start the server.
|
||||
Create a system user called \term{ejabberd},
|
||||
give it write access to the directories \term{database/} and \term{logs/}, and set that as home;
|
||||
the script will start the server with that user.
|
||||
Then you can call \term{/etc/inid.d/ejabberd start} as root to start the server.
|
||||
|
||||
If \term{ejabberd} doesn't start correctly in Windows,
|
||||
@@ -317,7 +319,6 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need:
|
||||
Don't use R14A or R14B because \footahref{http://www.erlang.org/cgi-bin/ezmlm-cgi/4/54598}{they have a bug}.
|
||||
\item exmpp 0.9.6 or higher
|
||||
\item OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
\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}.
|
||||
\item Erlang pgsql library. Optional. For PostgreSQL authentication or storage. See section \ref{compilepgsql}.
|
||||
\item PAM library. Optional. For Pluggable Authentication Modules (PAM). See section \ref{pam}.
|
||||
@@ -393,6 +394,9 @@ Some options that you may be interested in modifying:
|
||||
|
||||
\titem{--disable-transient-supervisors}
|
||||
Disable the use of Erlang/OTP supervision for transient processes.
|
||||
|
||||
\titem{--enable-flash-hack}
|
||||
Enable support for non-standard XML socket clients of Adobe Flash 8 and lower.
|
||||
\end{description}
|
||||
|
||||
\makesubsection{install}{Install}
|
||||
@@ -520,7 +524,6 @@ To compile \ejabberd{} on a Microsoft Windows system, you need:
|
||||
\item \footahref{http://support.process-one.net/doc/display/EXMPP}{exmpp 0.9.5 or higher}
|
||||
\item \footahref{http://www.slproweb.com/products/Win32OpenSSL.html}{Shining Light OpenSSL 0.9.8d or higher}
|
||||
(to enable SSL connections)
|
||||
\item \footahref{http://www.zlib.net/}{Zlib 1.2.3 or higher}
|
||||
\end{itemize}
|
||||
|
||||
|
||||
@@ -531,8 +534,6 @@ We assume that we will try to put as much library as possible into \verb|C:\sdk\
|
||||
\begin{enumerate}
|
||||
\item Install Erlang emulator (for example, into \verb|C:\sdk\erl5.6.5|).
|
||||
\item Install OpenSSL in \verb|C:\sdk\OpenSSL| and add \verb|C:\sdk\OpenSSL\lib\VC| to your path or copy the binaries to your system directory.
|
||||
\item Install ZLib in \verb|C:\sdk\gnuWin32|. Copy
|
||||
\verb|C:\sdk\GnuWin32\bin\zlib1.dll| to your system directory.
|
||||
\item Make sure the you can access Erlang binaries from your path. For example: \verb|set PATH=%PATH%;"C:\sdk\erl5.6.5\bin"|
|
||||
\item Depending on how you end up actually installing the library you might need to check and tweak the paths in the file configure.erl.
|
||||
\item While in the directory \verb|ejabberd\src| run:
|
||||
@@ -565,8 +566,8 @@ ejabberdctl register admin1 example.org FgT5bk3
|
||||
\end{enumerate}
|
||||
\item Edit the \ejabberd{} configuration file to give administration rights to the XMPP account you created:
|
||||
\begin{verbatim}
|
||||
{acl, admins, {user, "admin1", "example.org"}}.
|
||||
{access, configure, [{allow, admins}]}.
|
||||
{acl, admin, {user, "admin1", "example.org"}}.
|
||||
{access, configure, [{allow, admin}]}.
|
||||
\end{verbatim}
|
||||
You can grant administrative privileges to many XMPP accounts,
|
||||
and also to accounts in other XMPP servers.
|
||||
@@ -1221,12 +1222,41 @@ When the storage is configured for ODBC, the ODBC server is
|
||||
configured with the \term{odbc\_server} option, see
|
||||
\ref{mysql} for MySQL, \ref{pgsql} for PostgreSQL, \ref{mssql} for MSSQL, and \ref{odbc} for generic ODBC.
|
||||
|
||||
The option \term{\{auth\_password\_format, plain|scram\}}
|
||||
defines in what format the users passwords are stored:
|
||||
\begin{description}
|
||||
\titem{plain}
|
||||
The password is stored as plain text in the database.
|
||||
This is risky because the passwords can be read if your database gets compromised.
|
||||
This is the default value.
|
||||
This format allows clients to authenticate using:
|
||||
the old Jabber Non-SASL (\xepref{0078}), \term{SASL PLAIN},
|
||||
\term{SASL DIGEST-MD5}, and \term{SASL SCRAM-SHA-1}.
|
||||
|
||||
\titem{scram}
|
||||
The password is not stored, only some information that allows to verify the hash provided by the client.
|
||||
It is impossible to obtain the original plain password from the stored information;
|
||||
for this reason, when this value is configured it cannot be changed to \term{plain} anymore.
|
||||
This format allows clients to authenticate using: \term{SASL PLAIN} and \term{SASL SCRAM-SHA-1}.
|
||||
\end{description}
|
||||
|
||||
The option \option{resource\_conflict} defines the action when a client attempts to
|
||||
login to an account with a resource that is already connected.
|
||||
The option syntax is:
|
||||
\esyntax{\{resource\_conflict, setresource|closenew|closeold\}.}
|
||||
The possible values match exactly the three possibilities described in
|
||||
\footahref{http://tools.ietf.org/html/rfc6120\#section-7.7.2.2}{XMPP Core: section 7.7.2.2}.
|
||||
The default value is \term{closeold}.
|
||||
If the client uses old Jabber Non-SASL authentication (\xepref{0078}),
|
||||
then this option is not respected, and the action performed is \term{closeold}.
|
||||
|
||||
Examples:
|
||||
\begin{itemize}
|
||||
\item To use internal Mnesia storage on all virtual hosts:
|
||||
\item To use internal Mnesia storage with hashed passwords on all virtual hosts:
|
||||
\begin{verbatim}
|
||||
{auth_method, storage}.
|
||||
{auth_storage, mnesia}.
|
||||
{auth_password_format, scram}.
|
||||
\end{verbatim}
|
||||
\item To use ODBC storage on all virtual hosts:
|
||||
\begin{verbatim}
|
||||
@@ -1533,10 +1563,10 @@ declarations of ACLs in the configuration file have the following syntax:
|
||||
{acl, tests, {user_regexp, "^test[0-9]*$"}}.
|
||||
\end{verbatim}
|
||||
%$
|
||||
\titem{\{user\_regexp, UserRegexp, Server\}} Matches any user with a name
|
||||
\titem{\{user\_regexp, Regexp, Server\}} Matches any user with a name
|
||||
that matches \term{Regexp} at server \term{Server}. Example:
|
||||
\begin{verbatim}
|
||||
{acl, tests, {user_Userregexp, "^test", "example.org"}}.
|
||||
{acl, tests, {user_regexp, "^test", "example.org"}}.
|
||||
\end{verbatim}
|
||||
\titem{\{server\_regexp, Regexp\}} Matches any JID from the server that
|
||||
matches \term{Regexp}. Example:
|
||||
@@ -2291,8 +2321,11 @@ one for authentication and other for regular calls.
|
||||
|
||||
Parameters:
|
||||
\begin{description}
|
||||
\titem{\{ldap\_servers, [Servers, ...]\}} \ind{options!ldap\_server}List of IP addresses or DNS names of your
|
||||
LDAP servers. This option is required.
|
||||
\titem{\{ldap\_servers, [Servers, ...]|\{host, VHost\}\}} \ind{options!ldap\_server}
|
||||
This option can contain a list of IP addresses or DNS names of your LDAP servers.
|
||||
If you are configuring multiple vhosts, you can configure in this option that some of
|
||||
them must use the same LDAP connections as another vhost.
|
||||
This option is required.
|
||||
\titem{\{ldap\_encrypt, none|tls\}} \ind{options!ldap\_encrypt}Type of connection encryption to the LDAP server.
|
||||
Allowed values are: \term{none}, \term{tls}.
|
||||
The value \term{tls} enables encryption by using LDAP over SSL.
|
||||
@@ -2303,6 +2336,16 @@ This option specifies whether to verify LDAP server certificate or not when TLS
|
||||
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\_tls\_cacertfile, Path\}} \ind{options!ldap\_tls\_cacertfile}
|
||||
Path to file containing PEM encoded CA certificates. This option is needed
|
||||
(and required) when TLS verification is enabled.
|
||||
\titem{\{ldap\_tls\_depth, Number\}} \ind{options!ldap\_tls\_depth}
|
||||
Specifies the maximum verification depth when TLS verification is enabled,
|
||||
i.e. how far in a chain of certificates the verification process can proceed
|
||||
before the verification is considered to fail.
|
||||
Peer certificate = 0, CA certificate = 1, higher level CA certificate = 2, etc.
|
||||
The value 2 thus means that a chain can at most contain peer cert,
|
||||
CA cert, next CA cert, and an additional CA cert. The default value is 1.
|
||||
\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.
|
||||
@@ -2312,6 +2355,7 @@ the value previously stored in the database will be used instead of the default
|
||||
is~\term{""} which means `anonymous connection'.
|
||||
\titem{\{ldap\_password, Password\}} \ind{options!ldap\_password}Bind password. The default
|
||||
value is \term{""}.
|
||||
\titem{\{ldap\_deref\_aliases, never|always|finding|searching\}} \ind{options!ldap\_deref\_aliases} Whether or not to dereference aliases. The default is \term{never}.
|
||||
\end{description}
|
||||
|
||||
Example:
|
||||
@@ -2321,6 +2365,12 @@ Example:
|
||||
{ldap_port, 389}.
|
||||
{ldap_rootdn, "cn=Manager,dc=domain,dc=org"}.
|
||||
{ldap_password, "secret"}.
|
||||
{host_config, "example3.org", [
|
||||
{ldap_servers, ["ldap3.example.org"]}.
|
||||
]}.
|
||||
{host_config, "example4.org", [
|
||||
{ldap_servers, {host, "example3.org"}}
|
||||
]}.
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsubsection{ldapauth}{Authentication}
|
||||
@@ -2540,37 +2590,52 @@ configuration is shown below:
|
||||
\makesection{modules}{Modules Configuration}
|
||||
\ind{modules}
|
||||
|
||||
The option \term{modules} defines the list of modules that will be loaded after
|
||||
\ejabberd{}'s startup. Each entry in the list is a tuple in which the first
|
||||
element is the name of a module and the second is a list of options for that
|
||||
module.
|
||||
|
||||
The option \term{modules} defines the modules that will be started
|
||||
for each virtual host, and the module options.
|
||||
The syntax is:
|
||||
\esyntax{\{modules, [ \{ModuleName, ModuleOptions\}, ...]\}.}
|
||||
|
||||
Each module is started for each virtual host with the same options.
|
||||
If you want a module to be started in a specific host with a different configuration,
|
||||
use \term{host\_config} with \term{add} as explained in section~\ref{virtualhost}.
|
||||
|
||||
If you configure ejabberd to serve millions of virtual hosts,
|
||||
you may want that each module is started only once, and let it serve all the hosts.
|
||||
Those modules are started statically when ejabberd starts,
|
||||
and cannot be reconfigured until the next server start.
|
||||
The option \term{static\_modules} defines the modules that will be started statically.
|
||||
The syntax is:
|
||||
\esyntax{\{static\_modules, [ \{ModuleName, ModuleOptions\}, ...]\}.}
|
||||
|
||||
Examples:
|
||||
\begin{itemize}
|
||||
\item In this example only the module \modecho{} is loaded and no module
|
||||
options are specified between the square brackets:
|
||||
\item Start three modules for each vhost.
|
||||
They can be reconfigured or stopped dynamically, for example with WebAdmin.
|
||||
Remark that, besides the last entry, all entries end with a comma:
|
||||
\begin{verbatim}
|
||||
{modules,
|
||||
[
|
||||
{mod_echo, []}
|
||||
{mod_echo, [{host, "echodynamic.@HOST@"}]},
|
||||
{mod_time, []},
|
||||
{mod_version, []}
|
||||
]}.
|
||||
\end{verbatim}
|
||||
\item In the second example the modules \modecho{}, \modtime{}, and
|
||||
\modversion{} are loaded without options. Remark that, besides the last entry,
|
||||
all entries end with a comma:
|
||||
\item Start MUC for each vhost, and the other three modules for all the vhosts:
|
||||
\begin{verbatim}
|
||||
{modules,
|
||||
[
|
||||
{mod_echo, []},
|
||||
{mod_muc, []}
|
||||
]}.
|
||||
{static_modules,
|
||||
[
|
||||
{mod_echo, [{host, "echostatic.@HOST@"}]}
|
||||
{mod_time, []},
|
||||
{mod_version, []}
|
||||
]}.
|
||||
\end{verbatim}
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\makesubsection{modoverview}{Modules Overview}
|
||||
\ind{modules!overview}\ind{XMPP compliancy}
|
||||
|
||||
@@ -2578,48 +2643,49 @@ The following table lists all modules included in \ejabberd{}.
|
||||
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\begin{tabular}{|l|l|l|}
|
||||
\hline {\bf Module} & {\bf Feature} & {\bf Dependencies} \\
|
||||
\begin{tabular}{|l|l|l|l|}
|
||||
\hline {\bf Module} & {\bf Feature} & {\bf St} & {\bf Dependencies} \\
|
||||
\hline
|
||||
\hline \modadhoc{} & Ad-Hoc Commands (\xepref{0050}) & \\
|
||||
\hline \ahrefloc{modannounce}{\modannounce{}} & Manage announcements & recommends \modadhoc{} \\
|
||||
\hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\
|
||||
\hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\
|
||||
\hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\
|
||||
\hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\
|
||||
\hline \ahrefloc{modecho}{\modecho{}} & Echoes XMPP stanzas & \\
|
||||
\hline \ahrefloc{modhttpbind}{\modhttpbind{}} & XMPP over Bosh service (HTTP Binding) & \\
|
||||
\hline \ahrefloc{modhttpfileserver}{\modhttpfileserver{}} & Small HTTP file server & \\
|
||||
\hline \ahrefloc{modlast}{\modlast{}} & Last Activity (\xepref{0012}) & \\
|
||||
\hline \ahrefloc{modmuc}{\modmuc{}} & Multi-User Chat (\xepref{0045}) & \\
|
||||
\hline \ahrefloc{modmuclog}{\modmuclog{}} & Multi-User Chat room logging & \modmuc{} \\
|
||||
\hline \ahrefloc{modmulticast}{\modmulticast{}} & Multicast Service (\xepref{0033}) & \\
|
||||
\hline \ahrefloc{modoffline}{\modoffline{}} & Offline message storage (\xepref{0160}) & \\
|
||||
\hline \ahrefloc{modping}{\modping{}} & XMPP Ping and periodic keepalives (\xepref{0199}) & \\
|
||||
\hline \ahrefloc{modprescounter}{\modprivacy{}} & Detect presence subscription flood & \\
|
||||
\hline \ahrefloc{modprivacy}{\modprivacy{}} & Blocking Communication (XMPP IM) & \\
|
||||
\hline \ahrefloc{modprivate}{\modprivate{}} & Private XML Storage (\xepref{0049}) & \\
|
||||
\hline \ahrefloc{modproxy}{\modproxy{}} & SOCKS5 Bytestreams (\xepref{0065}) & \\
|
||||
\hline \ahrefloc{modpubsub}{\modpubsub{}} & Pub-Sub (\xepref{0060}), PEP (\xepref{0163}) & \modcaps{} \\
|
||||
\hline \ahrefloc{modpubsub}{\modpubsubodbc{}} & Pub-Sub (\xepref{0060}), PEP (\xepref{0163}) & supported DB (*) and \modcaps{} \\
|
||||
\hline \ahrefloc{modregister}{\modregister{}} & In-Band Registration (\xepref{0077}) & \\
|
||||
\hline \ahrefloc{modregisterweb}{\modregisterweb{}} & Web for Account Registrations & \\
|
||||
\hline \ahrefloc{modroster}{\modroster{}} & Roster management (XMPP IM) & \\
|
||||
\hline \ahrefloc{modservicelog}{\modservicelog{}} & Copy user messages to logger service & \\
|
||||
\hline \ahrefloc{modsharedroster}{\modsharedroster{}} & Shared roster management & \modroster{} \\
|
||||
\hline \ahrefloc{modsharedrosterldap}{\modsharedrosterldap{}} & LDAP Shared roster management & \modroster{} \\
|
||||
\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{modvcardxupdate}{\modvcardxupdate{}} & vCard-Based Avatars (\xepref{0153}) & \modvcard{} \\
|
||||
\hline \ahrefloc{modversion}{\modversion{}} & Software Version (\xepref{0092}) & \\
|
||||
\hline \modadhoc{} & Ad-Hoc Commands (\xepref{0050}) & & \\
|
||||
\hline \ahrefloc{modannounce}{\modannounce{}} & Manage announcements& & recommends \modadhoc{} \\
|
||||
\hline \modblocking{} & Simple Communic. Blocking (\xepref{0191}) & & \modprivacy{} \\
|
||||
\hline \modcaps{} & Entity Capabilities (\xepref{0115}) & & \\
|
||||
\hline \modconfigure{} & Server configuration using Ad-Hoc & & \modadhoc{} \\
|
||||
\hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & & \\
|
||||
\hline \ahrefloc{modecho}{\modecho{}} & Echoes XMPP stanzas & & \\
|
||||
\hline \ahrefloc{modhttpbind}{\modhttpbind{}} & XMPP over Bosh service (HTTP Binding) & & \\
|
||||
\hline \ahrefloc{modhttpfileserver}{\modhttpfileserver{}} & Small HTTP file server & & \\
|
||||
\hline \ahrefloc{modlast}{\modlast{}} & Last Activity (\xepref{0012}) & & \\
|
||||
\hline \ahrefloc{modmuc}{\modmuc{}} & Multi-User Chat (\xepref{0045}) & & \\
|
||||
\hline \ahrefloc{modmuclog}{\modmuclog{}} & Multi-User Chat room logging & & \modmuc{} \\
|
||||
\hline \ahrefloc{modmulticast}{\modmulticast{}} & Multicast Service (\xepref{0033}) & - & \\
|
||||
\hline \ahrefloc{modoffline}{\modoffline{}} & Offline message storage (\xepref{0160}) & & \\
|
||||
\hline \ahrefloc{modping}{\modping{}} & XMPP Ping and keepalives (\xepref{0199}) & & \\
|
||||
\hline \ahrefloc{modprescounter}{\modprescounter{}} & Detect presence subscription flood & & \\
|
||||
\hline \ahrefloc{modprivacy}{\modprivacy{}} & Blocking Communication (XMPP IM) & & \\
|
||||
\hline \ahrefloc{modprivate}{\modprivate{}} & Private XML Storage (\xepref{0049}) & & \\
|
||||
\hline \ahrefloc{modproxy}{\modproxy{}} & SOCKS5 Bytestreams (\xepref{0065}) & - & \\
|
||||
\hline \ahrefloc{modpubsub}{\modpubsub{}} & Pub-Sub (\xepref{0060}), PEP (\xepref{0163}) & - & \modcaps{} \\
|
||||
\hline \ahrefloc{modpubsub}{\modpubsubodbc{}} & Pub-Sub (\xepref{0060}), PEP (\xepref{0163}) & - & DB (*) and \modcaps{} \\
|
||||
\hline \ahrefloc{modregister}{\modregister{}} & In-Band Registration (\xepref{0077}) & & \\
|
||||
\hline \ahrefloc{modregisterweb}{\modregisterweb{}} & Web for Account Registrations & & \\
|
||||
\hline \ahrefloc{modroster}{\modroster{}} & Roster management (XMPP IM) & & \\
|
||||
\hline \ahrefloc{modservicelog}{\modservicelog{}} & Copy user messages to logger service & - & \\
|
||||
\hline \ahrefloc{modsharedroster}{\modsharedroster{}} & Shared roster management & & \modroster{} \\
|
||||
\hline \ahrefloc{modsharedrosterldap}{\modsharedrosterldap{}} & LDAP Shared roster management & - & \modroster{} \\
|
||||
\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{modvcardxupdate}{\modvcardxupdate{}} & vCard-Based Avatars (\xepref{0153}) & & \modvcard{} \\
|
||||
\hline \ahrefloc{modversion}{\modversion{}} & Software Version (\xepref{0092}) & & \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{table}
|
||||
|
||||
\begin{itemize}
|
||||
\item St -: This means that the module cannot be configured to start statically, only dynamically.
|
||||
\item (*) This module requires a supported database. For a list of supported databases, see section~\ref{database}.
|
||||
\end{itemize}
|
||||
|
||||
@@ -2709,6 +2775,7 @@ The syntax is:
|
||||
|
||||
If you include the keyword "@HOST@" in the HostName,
|
||||
it is replaced at start time with the real virtual host string.
|
||||
If you configure the module to be started statically, then the keyword @HOST@ is mandatory.
|
||||
|
||||
This example configures
|
||||
the \ind{modules!\modecho{}}echo module to provide its echoing service
|
||||
@@ -2733,6 +2800,20 @@ the "@HOST@" keyword must be used:
|
||||
]}.
|
||||
\end{verbatim}
|
||||
|
||||
This example configures
|
||||
the \ind{modules!\modecho{}}echo module to provide its echoing service
|
||||
in Jabber IDs like
|
||||
\jid{mirror.example.org}, \jid{mirror.host2.com}, \jid{mirror.shakespeare.lit}:
|
||||
\begin{verbatim}
|
||||
{hosts, ["example.org", "host2.com", "shakespeare.lit"]}.
|
||||
{static_modules,
|
||||
[
|
||||
...
|
||||
{mod_echo, [{host, "mirror.@HOST@"}]},
|
||||
...
|
||||
]}.
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{modannounce}{\modannounce{}}
|
||||
\ind{modules!\modannounce{}}\ind{MOTD}\ind{message of the day}\ind{announcements}
|
||||
|
||||
@@ -2781,7 +2862,7 @@ Examples:
|
||||
\begin{itemize}
|
||||
\item Only administrators can send announcements:
|
||||
\begin{verbatim}
|
||||
{access, announce, [{allow, admins}]}.
|
||||
{access, announce, [{allow, admin}]}.
|
||||
|
||||
{modules,
|
||||
[
|
||||
@@ -2795,9 +2876,9 @@ Examples:
|
||||
\begin{verbatim}
|
||||
{acl, direction, {user, "big_boss", "example.org"}}.
|
||||
{acl, direction, {user, "assistant", "example.org"}}.
|
||||
{acl, admins, {user, "admin", "example.org"}}.
|
||||
{acl, admin, {user, "admin", "example.org"}}.
|
||||
|
||||
{access, announce, [{allow, admins},
|
||||
{access, announce, [{allow, admin},
|
||||
{allow, direction}]}.
|
||||
|
||||
{modules,
|
||||
@@ -3201,6 +3282,7 @@ Module options:
|
||||
\begin{description}
|
||||
\titem{\{allow\_change\_subj, true|false\}} Allow occupants to change the subject.
|
||||
\titem{\{allow\_private\_messages, true|false\}} Occupants can send private messages to other occupants.
|
||||
\titem{\{allow\_private\_messages\_from\_visitors, anyone|moderators|nobody\}} Visitors can send private messages to other occupants.
|
||||
\titem{\{allow\_query\_users, true|false\}} Occupants can send IQ queries to other occupants.
|
||||
\titem{\{allow\_user\_invites, false|true\}} Allow occupants to send invitations.
|
||||
\titem{\{allow\_visitor\_nickchange, true|false\}} Allow visitors to
|
||||
@@ -3445,10 +3527,10 @@ Examples:
|
||||
be stored in /var/www/muclogs, and the local time will be used. Finally, the
|
||||
top link will be the default \verb|<a href="/">Home</a>|.
|
||||
\begin{verbatim}
|
||||
{acl, admins, {user, "admin1", "example.org"}}.
|
||||
{acl, admins, {user, "admin2", "example.net"}}.
|
||||
{acl, admin, {user, "admin1", "example.org"}}.
|
||||
{acl, admin, {user, "admin2", "example.net"}}.
|
||||
|
||||
{access, muc_log, [{allow, admins},
|
||||
{access, muc_log, [{allow, admin},
|
||||
{deny, all}]}.
|
||||
|
||||
{modules,
|
||||
@@ -3969,6 +4051,8 @@ Options:
|
||||
|
||||
This example configuration shows how to enable the module and the web handler:
|
||||
\begin{verbatim}
|
||||
{hosts, ["localhost", "example.org", "example.com"]}.
|
||||
|
||||
{listen, [
|
||||
...
|
||||
{5281, ejabberd_http, [
|
||||
@@ -3987,7 +4071,8 @@ This example configuration shows how to enable the module and the web handler:
|
||||
]}.
|
||||
\end{verbatim}
|
||||
|
||||
The users can visit this page: https://localhost:5281/register/
|
||||
For example, the users of the host \term{example.org} can visit the page:
|
||||
\ns{https://example.org:5281/register/}
|
||||
It is important to include the last / character in the URL,
|
||||
otherwise the subpages URL will be incorrect.
|
||||
|
||||
@@ -4116,8 +4201,9 @@ has a unique identification and the following parameters:
|
||||
which is only recommended for a small server with just a few hundred users.
|
||||
The special member directive \term{@online@}
|
||||
represents the online users in the virtual host.
|
||||
\item[Displayed groups] A list of groups that will be in the rosters of this
|
||||
group's members.
|
||||
\item[Displayed groups]
|
||||
A list of groups that will be in the rosters of this group's members.
|
||||
A group of other vhost can be identified with \term{groupid@vhost}
|
||||
\end{description}
|
||||
|
||||
Examples:
|
||||
@@ -4688,8 +4774,9 @@ The \modvcardldap{} module has
|
||||
its own optional parameters. The first group of parameters has the same
|
||||
meaning as the top-level LDAP parameters to set the authentication method:
|
||||
\option{ldap\_servers}, \option{ldap\_port}, \option{ldap\_rootdn},
|
||||
\option{ldap\_password}, \option{ldap\_base}, \option{ldap\_uids}, and
|
||||
\option{ldap\_filter}. See section~\ref{ldapauth} for detailed information
|
||||
\option{ldap\_password}, \option{ldap\_base}, \option{ldap\_uids},
|
||||
\option{ldap\_deref\_aliases} and \option{ldap\_filter}.
|
||||
See section~\ref{ldapauth} for detailed information
|
||||
about these options. If one of these options is not set, \ejabberd{} will look
|
||||
for the top-level option with the same name.
|
||||
|
||||
@@ -5262,11 +5349,11 @@ Example configurations:
|
||||
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"}}]}.
|
||||
{acl, admin, {user, "admin", "example.net"}}.
|
||||
{host_config, "example.com", [{acl, admin, {user, "admin", "example.com"}}]}.
|
||||
{host_config, "example.com", [{acl, viewers, {user, "reviewer", "example.com"}}]}.
|
||||
|
||||
{access, configure, [{allow, admins}]}.
|
||||
{access, configure, [{allow, admin}]}.
|
||||
{access, webadmin_view, [{allow, viewers}]}.
|
||||
|
||||
{hosts, ["example.org"]}.
|
||||
@@ -5682,7 +5769,8 @@ domain.
|
||||
\makesection{servicelb}{Service Load-Balancing}
|
||||
\ind{component load-balancing}
|
||||
|
||||
\makesubsection{componentlb}{Components Load-Balancing}
|
||||
% This section never had content, should it?
|
||||
% \makesubsection{componentlb}{Components Load-Balancing}
|
||||
|
||||
\makesubsection{domainlb}{Domain Load-Balancing Algorithm}
|
||||
\ind{options!domain\_balancing}
|
||||
|
||||
@@ -43,6 +43,7 @@ ejabberd 3.0.0 includes three major changes:
|
||||
|
||||
* Massive Hosting (abbreviated MH) is preliminary implemented in ejabberd,
|
||||
but it is still incomplete, untested and undocumented.
|
||||
New option static_modules.
|
||||
|
||||
|
||||
This is a more detailed list of changes since ejabberd 2.1.x releases:
|
||||
@@ -71,6 +72,7 @@ This is a more detailed list of changes since ejabberd 2.1.x releases:
|
||||
|
||||
* XEP support
|
||||
- Removed support for the deprecated XEP-0018 Invisible Presence (EJAB-810)
|
||||
- Update http_bind to XEP-0124 1.10 and XEP-0206 1.3
|
||||
- mod_multicast: service for XEP-0033: Extended Stanza Addressing (EJAB-265)
|
||||
- New ejabberd router for multicast packets (XEP-0033) (EJAB-329)
|
||||
- ejabberd_c2s uses XEP-0033 if mod_multicast is enabled (EJAB-267)
|
||||
@@ -93,7 +95,9 @@ This is a more detailed list of changes since ejabberd 2.1.x releases:
|
||||
- Bugfix: external contacts were offline after unavailable presence (EJAB-943)
|
||||
- Don't offer SASL auth before doing TLS if TLS is required
|
||||
- Experimental support for GSSAPI auth, requires esasl library (EJAB-831)
|
||||
- Replaced the full ejabberd_zlib into a simple exmpp_compress interface (EJAB-991)
|
||||
- Use binaries instead of lists where possible (EJAB-17)
|
||||
- Webadmin: provide list of registered pids/ports
|
||||
- Windows ejabberdctl.cmd only handles up to 4 arguments (EJAB-1216)
|
||||
- ejabberdctl: when starting, check it isn't already running
|
||||
|
||||
|
||||
+7
-2
@@ -64,10 +64,15 @@ ifeq (@pam@, pam)
|
||||
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
|
||||
endif
|
||||
|
||||
ifeq (@flash_hack@, true)
|
||||
ERLC_FLAGS+=-DENABLE_FLASH_HACK
|
||||
CPPFLAGS+=-DENABLE_FLASH_HACK
|
||||
endif
|
||||
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
|
||||
SUBDIRS = stun @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ @tls@ @odbc@ @ejabberd_zlib@
|
||||
SUBDIRS = stun @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ @tls@ @odbc@
|
||||
ERLSHLIBS =
|
||||
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
|
||||
SOURCES_ALL = $(wildcard *.erl)
|
||||
@@ -158,7 +163,7 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
@ERLC@ -W $(EFLAGS) $*.erl
|
||||
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(ERLANG_LIBS) \
|
||||
$(ERLANG_CFLAGS) \
|
||||
|
||||
@@ -69,9 +69,6 @@ release : build release_clean
|
||||
mkdir $(SRC_DIR)\tls
|
||||
copy tls\*.erl $(SRC_DIR)\tls
|
||||
copy tls\*.c $(SRC_DIR)\tls
|
||||
mkdir $(SRC_DIR)\ejabberd_zlib
|
||||
copy ejabberd_zlib\*.erl $(SRC_DIR)\ejabberd_zlib
|
||||
copy ejabberd_zlib\*.c $(SRC_DIR)\ejabberd_zlib
|
||||
mkdir $(SRC_DIR)\web
|
||||
copy web\*.erl $(SRC_DIR)\web
|
||||
mkdir $(SRC_DIR)\odbc
|
||||
@@ -103,8 +100,6 @@ all-recursive :
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\ejabberd_zlib
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\web
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\odbc
|
||||
@@ -142,8 +137,6 @@ clean-recursive :
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\ejabberd_zlib
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\web
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\odbc
|
||||
|
||||
@@ -1,39 +1,3 @@
|
||||
AC_DEFUN([AM_WITH_ZLIB],
|
||||
[ AC_ARG_WITH(zlib,
|
||||
[AC_HELP_STRING([--with-zlib=PREFIX], [prefix where zlib is installed])])
|
||||
|
||||
if test x"$ejabberd_zlib" != x; then
|
||||
ZLIB_CFLAGS=
|
||||
ZLIB_LIBS=
|
||||
if test x"$with_zlib" != x; then
|
||||
ZLIB_CFLAGS="-I$with_zlib/include"
|
||||
ZLIB_LIBS="-L$with_zlib/lib"
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB(z, gzgets,
|
||||
[ ZLIB_LIBS="$ZLIB_LIBS -lz"
|
||||
zlib_found=yes ],
|
||||
[ zlib_found=no ],
|
||||
"$ZLIB_LIBS")
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find development files of zlib library. Install them or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
|
||||
fi
|
||||
zlib_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
||||
zlib_save_CPPFLAGS="$CFLAGS"
|
||||
CPPFLAGS="$CPPFLAGS $ZLIB_CFLAGS"
|
||||
AC_CHECK_HEADERS(zlib.h, , zlib_found=no)
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find zlib.h. Install it or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
|
||||
fi
|
||||
CFLAGS="$zlib_save_CFLAGS"
|
||||
CPPFLAGS="$zlib_save_CPPFLAGS"
|
||||
|
||||
AC_SUBST(ZLIB_CFLAGS)
|
||||
AC_SUBST(ZLIB_LIBS)
|
||||
fi
|
||||
])
|
||||
|
||||
AC_DEFUN([AM_WITH_PAM],
|
||||
[ AC_ARG_WITH(pam,
|
||||
[AC_HELP_STRING([--with-pam=PREFIX], [prefix where PAM is installed])])
|
||||
|
||||
+10
-6
@@ -36,10 +36,6 @@ AC_MOD_ENABLE(odbc, no)
|
||||
AC_MOD_ENABLE(tls, yes)
|
||||
AC_MOD_ENABLE(web, yes)
|
||||
|
||||
AC_MOD_ENABLE(ejabberd_zlib, yes)
|
||||
#locating zlib
|
||||
AM_WITH_ZLIB
|
||||
|
||||
AC_MOD_ENABLE(pam, no)
|
||||
#locating PAM
|
||||
AM_WITH_PAM
|
||||
@@ -62,6 +58,15 @@ AC_ARG_ENABLE(roster_gateway_workaround,
|
||||
esac],[roster_gateway_workaround=false])
|
||||
AC_SUBST(roster_gateway_workaround)
|
||||
|
||||
AC_ARG_ENABLE(flash_hack,
|
||||
[AC_HELP_STRING([--enable-flash-hack], [support Adobe Flash client XML (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) flash_hack=true ;;
|
||||
no) flash_hack=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-flash-hack) ;;
|
||||
esac],[flash_hack=false])
|
||||
AC_SUBST(flash_hack)
|
||||
|
||||
AC_ARG_ENABLE(mssql,
|
||||
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
|
||||
[case "${enableval}" in
|
||||
@@ -98,8 +103,7 @@ AC_CONFIG_FILES([Makefile
|
||||
$make_web
|
||||
stun/Makefile
|
||||
$make_tls
|
||||
$make_odbc
|
||||
$make_ejabberd_zlib])
|
||||
$make_odbc])
|
||||
#openssl
|
||||
AM_WITH_OPENSSL
|
||||
# If ssl is kerberized it need krb5.h
|
||||
|
||||
+23
-12
@@ -43,7 +43,7 @@
|
||||
%% Require_Plain = bool().
|
||||
%% Registry entry of a supported SASL mechanism.
|
||||
|
||||
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
|
||||
-record(sasl_mechanism, {mechanism, module, password_type}).
|
||||
|
||||
%% @type saslstate() = {sasl_state, Service, Myname, Realm, GetPassword, CheckPassword, CheckPasswordDigest, Mech_Mod, Mech_State}
|
||||
%% Service = string()
|
||||
@@ -76,6 +76,7 @@ start() ->
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_scram:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
maybe_try_start_gssapi(),
|
||||
ok.
|
||||
@@ -101,11 +102,11 @@ try_start_gssapi() ->
|
||||
%% Module = atom()
|
||||
%% Require_Plain = bool()
|
||||
|
||||
register_mechanism(Mechanism, Module, RequirePlainPassword) ->
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module,
|
||||
require_plain_password = RequirePlainPassword}).
|
||||
password_type = PasswordType}).
|
||||
|
||||
% TODO use callbacks
|
||||
%-include("ejabberd.hrl").
|
||||
@@ -153,17 +154,20 @@ check_credentials(_State, Props) ->
|
||||
%% Mechanism = string()
|
||||
|
||||
listmech(Host) ->
|
||||
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
|
||||
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
require_plain_password = '$2',
|
||||
password_type = '$2',
|
||||
_ = '_'},
|
||||
if
|
||||
RequirePlainPassword ->
|
||||
[{'==', '$2', false}];
|
||||
true ->
|
||||
[]
|
||||
case catch ejabberd_auth:store_type(Host) of
|
||||
external ->
|
||||
[{'==', '$2', plain}];
|
||||
scram ->
|
||||
[{'/=', '$2', digest}];
|
||||
{'EXIT',{undef,[{Module,store_type,[]} | _]}} ->
|
||||
?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]),
|
||||
[];
|
||||
_Else ->
|
||||
[]
|
||||
end,
|
||||
['$1']}]),
|
||||
filter_anonymous(Host, Mechs).
|
||||
@@ -228,7 +232,7 @@ server_start(State, Mech, ClientIn) ->
|
||||
%% @spec (State, ClientIn) -> Ok | Continue | Error
|
||||
%% State = saslstate()
|
||||
%% ClientIn = string()
|
||||
%% Ok = {ok, Props}
|
||||
%% Ok = {ok, Props} | {ok, Props, ServerOut}
|
||||
%% Props = [Prop]
|
||||
%% Prop = [{Key, Value}]
|
||||
%% Key = atom()
|
||||
@@ -252,6 +256,13 @@ server_step(State, ClientIn) ->
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
{ok, Props, ServerOut} ->
|
||||
case check_credentials(State, Props) of
|
||||
ok ->
|
||||
{ok, Props, ServerOut};
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
end;
|
||||
{continue, ServerOut, NewMechState} ->
|
||||
{continue, ServerOut,
|
||||
State#sasl_state{mech_state = NewMechState}};
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false),
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, digest).
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
@@ -91,7 +91,7 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
bad ->
|
||||
{error, 'bad-protocol'};
|
||||
{error, 'malformed-request'};
|
||||
KeyVals ->
|
||||
DigestURI = proplists:get_value("digest-uri", KeyVals, ""),
|
||||
UserName = proplists:get_value("username", KeyVals, ""),
|
||||
@@ -136,7 +136,7 @@ mech_step(#state{step = 5,
|
||||
{auth_module, AuthModule}]};
|
||||
mech_step(A, B) ->
|
||||
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
{error, 'bad-protocol'}.
|
||||
{error, 'malformed-request'}.
|
||||
|
||||
%% @spec (S) -> [{Key, Value}] | bad
|
||||
%% S = string()
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
%% Opts = term()
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, false),
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, plain),
|
||||
ok.
|
||||
|
||||
%% @spec () -> ok
|
||||
@@ -77,7 +77,7 @@ mech_step(State, ClientIn) ->
|
||||
{error, 'not-authorized', "", User}
|
||||
end;
|
||||
_ ->
|
||||
{error, 'bad-protocol'}
|
||||
{error, 'malformed-request'}
|
||||
end.
|
||||
|
||||
prepare(ClientIn) ->
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SASL SCRAM authentication
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(cyrsasl_scram).
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/1,
|
||||
mech_step/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("cyrsasl.hrl").
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {step, stored_key, server_key, username, get_password, check_password,
|
||||
auth_message, client_nonce, server_nonce}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
-define(NONCE_LENGTH, 16).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("SCRAM-SHA-1", ?MODULE, scram).
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(#sasl_params{get_password=GetPassword}) ->
|
||||
{ok, #state{step = 2, get_password = GetPassword}}.
|
||||
|
||||
mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
[CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
|
||||
case parse_attribute(UserNameAttribute) of
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
{_, EscapedUserName} ->
|
||||
case unescape_username(EscapedUserName) of
|
||||
error ->
|
||||
{error, 'malformed-request', "Error in username encoding", EscapedUserName};
|
||||
UserName ->
|
||||
case parse_attribute(ClientNonceAttribute) of
|
||||
{$r, ClientNonce} ->
|
||||
case (State#state.get_password)(UserName) of
|
||||
{false, _} ->
|
||||
{error, 'not-authorized', "", UserName};
|
||||
{Ret, _AuthModule} ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} = if
|
||||
is_tuple(Ret) ->
|
||||
Ret;
|
||||
true ->
|
||||
TempSalt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT),
|
||||
{scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
scram:server_key(SaltedPassword), TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT}
|
||||
end,
|
||||
ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
|
||||
ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage = "r=" ++ ClientNonce ++ ServerNonce ++ "," ++
|
||||
"s=" ++ base64:encode_to_string(Salt) ++ "," ++
|
||||
"i=" ++ integer_to_list(IterationCount),
|
||||
{continue,
|
||||
ServerFirstMessage,
|
||||
State#state{step = 4, stored_key = StoredKey, server_key = ServerKey,
|
||||
auth_message = ClientFirstMessageBare ++ "," ++ ServerFirstMessage,
|
||||
client_nonce = ClientNonce, server_nonce = ServerNonce, username = UserName}}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request'}
|
||||
end
|
||||
end
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request'}
|
||||
end;
|
||||
mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
case string:tokens(ClientIn, ",") of
|
||||
[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
|
||||
case parse_attribute(GS2ChannelBindingAttribute) of
|
||||
{$c, CVal} when (CVal == "biws") or (CVal == "eSws") ->
|
||||
%% biws is base64 for n,, => channelbinding not supported
|
||||
%% eSws is base64 for y,, => channelbinding supported by client only
|
||||
Nonce = State#state.client_nonce ++ State#state.server_nonce,
|
||||
case parse_attribute(NonceAttribute) of
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = base64:decode(ClientProofB64),
|
||||
AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
|
||||
ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
|
||||
ClientKey = scram:client_key(ClientProof, ClientSignature),
|
||||
CompareStoredKey = scram:stored_key(ClientKey),
|
||||
if CompareStoredKey == State#state.stored_key ->
|
||||
ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
|
||||
{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
|
||||
true ->
|
||||
{error, 'not-authorized', "", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
{$r, _} ->
|
||||
{error, 'malformed-request', "Bad nonce", State#state.username};
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "Bad protocol", State#state.username}
|
||||
end.
|
||||
|
||||
parse_attribute(Attribute) ->
|
||||
AttributeLen = string:len(Attribute),
|
||||
if
|
||||
AttributeLen >= 3 ->
|
||||
SecondChar = lists:nth(2, Attribute),
|
||||
case is_alpha(lists:nth(1, Attribute)) of
|
||||
true ->
|
||||
if
|
||||
SecondChar == $= ->
|
||||
String = string:substr(Attribute, 3),
|
||||
{lists:nth(1, Attribute), String};
|
||||
true ->
|
||||
{error, 'malformed-request', "Second char not equal sign", ""}
|
||||
end;
|
||||
_Else ->
|
||||
{error, 'malformed-request', "First char not a letter", ""}
|
||||
end;
|
||||
true ->
|
||||
{error, 'malformed-request', "Attribute too short", ""}
|
||||
end.
|
||||
|
||||
unescape_username("") ->
|
||||
"";
|
||||
unescape_username(EscapedUsername) ->
|
||||
Pos = string:str(EscapedUsername, "="),
|
||||
if
|
||||
Pos == 0 ->
|
||||
EscapedUsername;
|
||||
true ->
|
||||
Start = string:substr(EscapedUsername, 1, Pos-1),
|
||||
End = string:substr(EscapedUsername, Pos),
|
||||
EndLen = string:len(End),
|
||||
if
|
||||
EndLen < 3 ->
|
||||
error;
|
||||
true ->
|
||||
case string:substr(End, 1, 3) of
|
||||
"=2C" ->
|
||||
Start ++ "," ++ unescape_username(string:substr(End, 4));
|
||||
"=3D" ->
|
||||
Start ++ "=" ++ unescape_username(string:substr(End, 4));
|
||||
_Else ->
|
||||
error
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
is_alpha(Char) when Char >= $a, Char =< $z ->
|
||||
true;
|
||||
is_alpha(Char) when Char >= $A, Char =< $Z ->
|
||||
true;
|
||||
is_alpha(_) ->
|
||||
false.
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "3.0.0-alpha-x"},
|
||||
{vsn, "3.0.0-alpha-5"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
@@ -8,6 +8,7 @@
|
||||
cyrsasl,
|
||||
cyrsasl_digest,
|
||||
cyrsasl_plain,
|
||||
cyrsasl_scram,
|
||||
ejabberd_admin,
|
||||
ejabberd_app,
|
||||
ejabberd_auth_anonymous,
|
||||
@@ -47,7 +48,6 @@
|
||||
ejabberd_update,
|
||||
ejabberd_web_admin,
|
||||
ejabberd_web,
|
||||
ejabberd_zlib,
|
||||
ejd2odbc,
|
||||
eldap,
|
||||
eldap_filter,
|
||||
|
||||
@@ -295,6 +295,15 @@
|
||||
%%
|
||||
%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
|
||||
|
||||
%%
|
||||
%% auth_password_format: Format of storing users passwords
|
||||
%% The default format is plain text.
|
||||
%% If you change to hashed scram, you can never go back to plain.
|
||||
%% This option is only supported by the 'storage' auth_method.
|
||||
%%
|
||||
{auth_password_format, plain}.
|
||||
%%{auth_password_format, scram}.
|
||||
|
||||
|
||||
%%%. ==============
|
||||
%%%' DATABASE SETUP
|
||||
@@ -611,6 +620,12 @@
|
||||
%%
|
||||
{static_modules,
|
||||
[
|
||||
%% {mod_adhoc, []},
|
||||
%% {mod_announce, []},
|
||||
%% {mod_disco, []},
|
||||
%% {mod_echo, [{prefix, "echo"}]},
|
||||
%% {mod_last, []},
|
||||
%% {mod_offline, []}
|
||||
]}.
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
-define(CONFIG_PATH, "ejabberd.cfg").
|
||||
-define(LOG_PATH, "ejabberd.log").
|
||||
|
||||
-ifdef(ENABLE_FLASH_HACK).
|
||||
-define(FLASH_HACK, true).
|
||||
-else.
|
||||
-define(FLASH_HACK, false).
|
||||
-endif.
|
||||
|
||||
|
||||
-define(EJABBERD_URI, "http://www.process-one.net/en/ejabberd/").
|
||||
|
||||
-define(S2STIMEOUT, 600000).
|
||||
@@ -40,6 +47,9 @@
|
||||
|
||||
%%-define(DBGFSM, true).
|
||||
|
||||
-record(scram, {storedkey, serverkey, salt, iterationcount}).
|
||||
-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Logging mechanism
|
||||
|
||||
|
||||
@@ -312,14 +312,15 @@ update_list() ->
|
||||
[atom_to_list(Beam) || Beam <- UpdatedBeams].
|
||||
|
||||
update("all") ->
|
||||
[update_module(ModStr) || ModStr <- update_list()];
|
||||
[update_module(ModStr) || ModStr <- update_list()],
|
||||
{ok, []};
|
||||
update(ModStr) ->
|
||||
update_module(ModStr).
|
||||
|
||||
update_module(ModuleNameString) ->
|
||||
ModuleName = list_to_atom(ModuleNameString),
|
||||
case ejabberd_update:update([ModuleName]) of
|
||||
{ok, Res} -> {ok, io_lib:format("Updated: ~p", [Res])};
|
||||
{ok, _Res} -> {ok, []};
|
||||
{error, Reason} -> {error, Reason}
|
||||
end.
|
||||
|
||||
@@ -453,7 +454,7 @@ restore(Path) ->
|
||||
%% Obsolete tables or tables created by module who are no longer used are not
|
||||
%% restored and are ignored.
|
||||
keep_tables() ->
|
||||
lists:flatten([acl, passwd, config, local_config, disco_publish,
|
||||
lists:flatten([acl, passwd, config, local_config,
|
||||
keep_modules_tables()]).
|
||||
|
||||
%% Returns the list of modules tables in use, according to the list of actually
|
||||
|
||||
+47
-21
@@ -49,17 +49,16 @@
|
||||
is_user_exists_in_other_modules/3,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
remove_host/1,
|
||||
plain_password_required/1,
|
||||
store_type/1,
|
||||
entropy/1
|
||||
]).
|
||||
|
||||
-export([start/1
|
||||
,start_module/2
|
||||
,stop_module/2
|
||||
,stop/1
|
||||
,start_modules/2
|
||||
,start_method/2
|
||||
,stop_method/2
|
||||
,start_methods/2
|
||||
,stop_modules/2
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
@@ -81,32 +80,58 @@ start() ->
|
||||
lists:foreach(fun start/1, ?MYHOSTS).
|
||||
|
||||
start(Host) ->
|
||||
ejabberd_hooks:add(remove_host, list_to_binary(Host), ?MODULE, remove_host, 150),
|
||||
start_modules(Host, auth_modules(Host)).
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(remove_host, list_to_binary(Host), ?MODULE, remove_host, 150),
|
||||
stop_modules(Host, auth_modules(Host)).
|
||||
|
||||
remove_host(HostB) when is_binary(HostB) ->
|
||||
lists:foreach(
|
||||
fun({Username, Host}) ->
|
||||
ejabberd_auth:remove_user(Username, Host)
|
||||
end,
|
||||
ejabberd_auth:get_vh_registered_users(binary_to_list(HostB))).
|
||||
|
||||
start_modules(Host, Modules) when is_list(Modules) ->
|
||||
lists:foreach(fun (M) -> start_module(Host, M) end, Modules).
|
||||
start_module(Host, Module) when is_atom(Module) ->
|
||||
lists:foreach(fun (M) -> start_modules(Host, M) end, Modules);
|
||||
start_modules(Host, Module) when is_atom(Module) ->
|
||||
Module:start(Host).
|
||||
stop_module(Host, Module) when is_atom(Module) ->
|
||||
|
||||
stop_modules(Host, Modules) when is_list(Modules) ->
|
||||
lists:foreach(fun (M) -> stop_modules(Host, M) end, Modules);
|
||||
stop_modules(Host, Module) when is_atom(Module) ->
|
||||
Module:stop(Host).
|
||||
|
||||
start_methods(Host, Methods) when is_list(Methods) ->
|
||||
lists:foreach(fun (M) -> start_method(Host, M) end, Methods).
|
||||
start_method(Host, Method) when is_atom(Method) ->
|
||||
start_module(Host, module_name(Method)).
|
||||
stop_method(Host, Method) when is_atom(Method) ->
|
||||
stop_module(Host, module_name(Method)).
|
||||
|
||||
|
||||
%% @spec (Server) -> bool()
|
||||
%% Server = string()
|
||||
|
||||
%% This is only executed by ejabberd_c2s for non-SASL auth client
|
||||
plain_password_required(Server) when is_list(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:plain_password_required()
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% @spec (Server) -> bool()
|
||||
%% Server = string()
|
||||
|
||||
store_type(Server) ->
|
||||
lists:foldl(
|
||||
fun(_, external) ->
|
||||
external;
|
||||
(M, scram) ->
|
||||
case M:store_type() of
|
||||
external ->
|
||||
external;
|
||||
_Else ->
|
||||
scram
|
||||
end;
|
||||
(M, plain) ->
|
||||
M:store_type()
|
||||
end, plain, auth_modules(Server)).
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
@@ -338,8 +363,10 @@ get_password_s(User, Server) when is_list(User), is_list(Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
"";
|
||||
Password ->
|
||||
Password
|
||||
Password when is_list(Password) ->
|
||||
Password;
|
||||
_ ->
|
||||
""
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> {Password, AuthModule} | {false, none}
|
||||
@@ -491,9 +518,8 @@ auth_modules() ->
|
||||
%% Server = string()
|
||||
%% @doc Return the list of authenticated modules for a given host.
|
||||
|
||||
auth_modules(Server) when is_list(Server) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, ejabberd:normalize_host(LServer)}),
|
||||
auth_modules(Server) ->
|
||||
Method = ejabberd_config:get_local_option({auth_method, Server}),
|
||||
Methods = if
|
||||
Method == undefined -> [];
|
||||
is_list(Method) -> Method;
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
@@ -360,6 +361,9 @@ remove_user(_User, _Server, _Password) ->
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(anonymous, local_content) of
|
||||
false ->
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -94,6 +95,9 @@ check_cache_last_options(Server) ->
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
@@ -146,19 +150,39 @@ dirty_get_registered_users() ->
|
||||
ejabberd_auth_storage:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server);
|
||||
no_cache ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server, Data).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users(Server, Data);
|
||||
no_cache ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> nil()
|
||||
%% Server = string()
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server);
|
||||
no_cache ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server, Data).
|
||||
case check_cache_last_options(Server) of
|
||||
cache ->
|
||||
ejabberd_auth_storage:get_vh_registered_users_number(Server, Data);
|
||||
no_cache ->
|
||||
0
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> bool()
|
||||
%% User = string()
|
||||
|
||||
+54
-23
@@ -54,6 +54,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -73,6 +74,7 @@
|
||||
uids,
|
||||
ufilter,
|
||||
lfilter, %% Local filter (performed by ejabberd, not LDAP)
|
||||
deref_aliases,
|
||||
dn_filter,
|
||||
dn_filter_attrs
|
||||
}).
|
||||
@@ -99,25 +101,31 @@ handle_info(_Info, State) ->
|
||||
|
||||
start(Host) ->
|
||||
?DEBUG("Starting ~p for ~p.", [?MODULE, Host]),
|
||||
case ejabberd_config:get_host_option(Host, ldap_servers) of
|
||||
undefined -> check_bad_config(Host);
|
||||
{host, _Host} -> ok;
|
||||
_ ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
Option = case ejabberd_config:get_host_option(Host, ldap_servers) of
|
||||
undefined -> check_local_config(Host);
|
||||
{host, _Host} -> nothing;
|
||||
_ ->
|
||||
{start, Host}
|
||||
end,
|
||||
case Option of
|
||||
nothing -> ok;
|
||||
{start, Host2} ->
|
||||
Proc = gen_mod:get_module_proc(Host2, ?MODULE),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host]},
|
||||
transient, 1000, worker, [?MODULE]
|
||||
},
|
||||
Proc, {?MODULE, start_link, [Host2]},
|
||||
transient, 1000, worker, [?MODULE]
|
||||
},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec)
|
||||
end.
|
||||
|
||||
check_bad_config(Host) ->
|
||||
check_local_config(Host) ->
|
||||
case ejabberd_config:get_local_option({ldap_servers, Host}) of
|
||||
undefined ->
|
||||
?ERROR_MSG("Can't start ~p for host ~p: missing ldap_servers configuration",
|
||||
[?MODULE, Host]),
|
||||
?ERROR_MSG("Can't start ~p for host ~p: missing ldap_servers "
|
||||
"configuration", [?MODULE, Host]),
|
||||
{error, bad_config};
|
||||
_ -> ok
|
||||
X when is_list(X)->
|
||||
{start, Host}
|
||||
end.
|
||||
|
||||
%% @spec (Host) -> term()
|
||||
@@ -173,6 +181,9 @@ init(Host) ->
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
%% Server = string()
|
||||
@@ -342,10 +353,12 @@ get_vh_registered_users_ldap(Server) ->
|
||||
ResAttrs = result_attrs(State),
|
||||
case eldap_filter:parse(SearchFilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID, [{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{attributes, ResAttrs}]) of
|
||||
case eldap_pool:search(Eldap_ID,
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
@@ -401,9 +414,11 @@ find_user_dn(User, Server, State) ->
|
||||
UserFilter = build_ufilter(State, Server),
|
||||
case eldap_filter:parse(UserFilter, [{"%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},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ResAttrs}]) of
|
||||
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||
object_name = DN} | _]} ->
|
||||
dn_filter(DN, Server, Attrs, State);
|
||||
@@ -443,6 +458,7 @@ is_valid_dn(DN, Server, Attrs, State) ->
|
||||
case eldap_pool:search(State#state.eldap_id,
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ["dn"]}]) of
|
||||
#eldap_search_result{entries = [_|_]} ->
|
||||
DN;
|
||||
@@ -512,6 +528,8 @@ parse_options(Host) ->
|
||||
end,
|
||||
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
|
||||
LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
|
||||
LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}),
|
||||
LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> case LDAPEncrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
@@ -530,11 +548,15 @@ parse_options(Host) ->
|
||||
end,
|
||||
UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
|
||||
undefined -> [{"uid", "%u"}];
|
||||
UI -> UI
|
||||
UI -> eldap_utils:uids_domain_subst(Host, UI)
|
||||
end,
|
||||
SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
|
||||
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> "";
|
||||
F -> F
|
||||
"" -> "";
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
@@ -546,7 +568,13 @@ parse_options(Host) ->
|
||||
{DNF, DNFA} ->
|
||||
{DNF, DNFA}
|
||||
end,
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
eldap_utils:check_filter(DNFilter),
|
||||
LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
|
||||
DerefAliases = case ejabberd_config:get_local_option(
|
||||
{ldap_deref_aliases, Host}) of
|
||||
undefined -> never;
|
||||
Val -> Val
|
||||
end,
|
||||
#state{host = Host,
|
||||
eldap_id = Eldap_ID,
|
||||
bind_eldap_id = Bind_Eldap_ID,
|
||||
@@ -554,13 +582,16 @@ parse_options(Host) ->
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
{tls_verify, LDAPTLSVerify},
|
||||
{tls_cacertfile, LDAPTLSCAFile},
|
||||
{tls_depth, LDAPTLSDepth}],
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
uids = UIDs,
|
||||
ufilter = UserFilter,
|
||||
lfilter = LocalFilter,
|
||||
deref_aliases = DerefAliases,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
}.
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
@@ -171,6 +172,9 @@ remove_user(_User, _Server, _Password) ->
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
store_type() ->
|
||||
external.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
|
||||
+187
-19
@@ -44,10 +44,27 @@
|
||||
%%% user_host = {Username::string(), Host::string()}
|
||||
%%% password = string()
|
||||
%%%
|
||||
%%% 3.0.0-beta / mnesia / passwd
|
||||
%%% user_host = {Username::string(), Host::string()}
|
||||
%%% password = string()
|
||||
%%% storedkey = base64 binary()
|
||||
%%% serverkey = base64 binary()
|
||||
%%% iterationcount = integer()
|
||||
%%% salt = base64 binary()
|
||||
%%%
|
||||
%%% 3.0.0-alpha / odbc / passwd
|
||||
%%% user = varchar150
|
||||
%%% host = varchar150
|
||||
%%% password = text
|
||||
%%%
|
||||
%%% 3.0.0-beta / odbc / passwd
|
||||
%%% user = varchar150
|
||||
%%% host = varchar150
|
||||
%%% password = base64 text
|
||||
%%% storedkey = base64 text
|
||||
%%% serverkey = base64 text
|
||||
%%% iterationcount = integer
|
||||
%%% salt = base64 text
|
||||
|
||||
-module(ejabberd_auth_storage).
|
||||
-author('alexey@process-one.net').
|
||||
@@ -69,14 +86,17 @@
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
store_type/0,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {user_host, password}).
|
||||
-record(passwd, {user_host, password, storedkey, serverkey, salt, iterationcount}).
|
||||
-record(reg_users_counter, {vhost, count}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -95,9 +115,14 @@ start(Host) ->
|
||||
[{odbc_host, Host},
|
||||
{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)},
|
||||
{types, [{user_host, {text, text}}]}
|
||||
{types, [{user_host, {text, text}},
|
||||
{storedkey, binary},
|
||||
{serverkey, binary},
|
||||
{salt, binary},
|
||||
{iterationcount, int}]}
|
||||
]),
|
||||
update_table(Host, Backend),
|
||||
maybe_scram_passwords(HostB),
|
||||
mnesia:create_table(reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]),
|
||||
@@ -120,7 +145,16 @@ update_reg_users_counter_table(Server) ->
|
||||
%% @spec () -> bool()
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
case is_option_scram(?MYNAME) of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
|
||||
store_type() ->
|
||||
case is_option_scram(?MYNAME) of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> bool()
|
||||
%% User = string()
|
||||
@@ -132,6 +166,8 @@ check_password(User, Server, Password) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
is_password_scram_valid(Password, Passwd);
|
||||
[#passwd{password = Password}] ->
|
||||
Password /= "";
|
||||
_ ->
|
||||
@@ -150,6 +186,19 @@ check_password(User, Server, Password, Digest, DigestGen) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
Storedkey = base64:decode(Passwd#passwd.storedkey),
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == DigestGen(Storedkey);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Storedkey == Password) and (Password /= "")
|
||||
end;
|
||||
[#passwd{password = Passwd}] ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
@@ -182,9 +231,11 @@ set_password(User, Server, Password) ->
|
||||
US ->
|
||||
%% TODO: why is this a transaction?
|
||||
F = fun() ->
|
||||
gen_storage:write(LServer,
|
||||
#passwd{user_host = US,
|
||||
password = Password})
|
||||
Passwd = case is_option_scram(LServer) and (Password /= "") of
|
||||
true -> password_to_scram(Password, #passwd{user_host=US});
|
||||
false -> #passwd{user_host = US, password = Password}
|
||||
end,
|
||||
gen_storage:write(LServer, Passwd)
|
||||
end,
|
||||
{atomic, ok} = gen_storage:transaction(LServer, passwd, F),
|
||||
ok
|
||||
@@ -207,9 +258,11 @@ try_register(User, Server, Password) ->
|
||||
F = fun() ->
|
||||
case gen_storage:read(LServer, {passwd, US}) of
|
||||
[] ->
|
||||
gen_storage:write(LServer,
|
||||
#passwd{user_host = US,
|
||||
password = Password}),
|
||||
Passwd = case is_option_scram(LServer) and (Password /= "") of
|
||||
true -> password_to_scram(Password, #passwd{user_host=US});
|
||||
false -> #passwd{user_host = US, password = Password}
|
||||
end,
|
||||
gen_storage:write(LServer, Passwd),
|
||||
mnesia:dirty_update_counter(
|
||||
reg_users_counter,
|
||||
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), 1),
|
||||
@@ -228,8 +281,12 @@ try_register(User, Server, Password) ->
|
||||
%% @doc Get all registered users in Mnesia.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
%% TODO:
|
||||
exit(not_implemented).
|
||||
lists:foldl(
|
||||
fun(HostB, Res) ->
|
||||
get_vh_registered_users(binary_to_list(HostB)) ++ Res
|
||||
end,
|
||||
[],
|
||||
gen_storage:all_table_hosts(passwd)).
|
||||
|
||||
%% @spec (Server) -> [{LUser, LServer}]
|
||||
%% Server = string()
|
||||
@@ -348,9 +405,14 @@ get_password(User, Server) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
{base64:decode(Passwd#passwd.storedkey),
|
||||
base64:decode(Passwd#passwd.serverkey),
|
||||
base64:decode(Passwd#passwd.salt),
|
||||
Passwd#passwd.iterationcount};
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
catch
|
||||
@@ -369,8 +431,8 @@ get_password_s(User, Server) ->
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch gen_storage:dirty_read(LServer, passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
@@ -437,13 +499,21 @@ remove_user(User, Server, Password) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
case gen_storage:read(LServer, {passwd, US}) of
|
||||
[#passwd{password = ""} = Passwd] ->
|
||||
case is_password_scram_valid(Password, Passwd) of
|
||||
true ->
|
||||
gen_storage:delete(LServer, {passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
false ->
|
||||
not_allowed
|
||||
end;
|
||||
[#passwd{password = Password}] ->
|
||||
gen_storage:delete(LServer, {passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), -1),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
_ ->
|
||||
not_exists
|
||||
end
|
||||
@@ -459,13 +529,111 @@ remove_user(User, Server, Password) ->
|
||||
bad_request
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
%% The passwords are stored scrammed in the table either if the option says so,
|
||||
%% or if at least the first password is empty.
|
||||
|
||||
action_password_format(Host) ->
|
||||
OptionScram = is_option_scram(Host),
|
||||
case {OptionScram, get_format_first_element(Host)} of
|
||||
{true, scram} -> scram;
|
||||
{true, any} -> scram;
|
||||
{true, plain} -> must_scram;
|
||||
{false, plain} -> plain;
|
||||
{false, any} -> plain;
|
||||
{false, scram} ->
|
||||
set_option_password_format(scram),
|
||||
forced_scram
|
||||
end.
|
||||
|
||||
get_format_first_element(HostB) ->
|
||||
case gen_storage:dirty_select(HostB, passwd, []) of
|
||||
[] -> any;
|
||||
[#passwd{password = ""} | _] -> scram;
|
||||
[#passwd{} | _] -> plain
|
||||
end.
|
||||
|
||||
is_option_scram(Host) when is_list(Host) ->
|
||||
scram == ejabberd_config:get_local_option({auth_password_format, Host}).
|
||||
|
||||
set_option_password_format(Value) ->
|
||||
?ERROR_MSG("Some passwords are stored in the database as SCRAM bits, "
|
||||
"but the option 'auth_password_format' is not configured 'scram'. "
|
||||
"The option will now be considered to be 'scram'.", []),
|
||||
ejabberd_config:add_local_option({auth_password_format, ?MYNAME}, Value).
|
||||
|
||||
maybe_scram_passwords(HostB) ->
|
||||
case action_password_format(binary_to_list(HostB)) of
|
||||
must_scram -> scram_passwords(HostB);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
scram_passwords(HostB) ->
|
||||
Backend =
|
||||
case ejabberd_config:get_local_option({auth_storage, binary_to_list(HostB)}) of
|
||||
undefined -> mnesia;
|
||||
B -> B
|
||||
end,
|
||||
scram_passwords(HostB, Backend).
|
||||
scram_passwords(HostB, mnesia) ->
|
||||
?INFO_MSG("Converting the passwords stored in mnesia for host ~p into SCRAM bits", [HostB]),
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
HostB, passwd,
|
||||
[{passwd, [user_host, password, storedkey, serverkey, salt, iterationcount],
|
||||
fun(#passwd{password = Password} = Passwd) ->
|
||||
password_to_scram(Password, Passwd)
|
||||
end}]);
|
||||
scram_passwords(HostB, odbc) ->
|
||||
?INFO_MSG("Converting the passwords stored in odbc for host ~p into SCRAM bits", [HostB]),
|
||||
gen_storage_migration:migrate_odbc(
|
||||
HostB, [passwd],
|
||||
[{"passwd", ["user", "host", "password", "storedkey", "serverkey", "salt", "iterationcount"],
|
||||
fun(_, User, Host2, Password, _Storedkey, _Serverkey, _Iterationcount, _Salt) ->
|
||||
password_to_scram(Password, #passwd{user_host = {User, Host2}})
|
||||
end}]).
|
||||
|
||||
password_to_scram(Password, Passwd) ->
|
||||
password_to_scram(Password, Passwd, ?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, Passwd, IterationCount) ->
|
||||
Salt = crypto:rand_bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
Passwd#passwd{password = "",
|
||||
storedkey = base64:encode(StoredKey),
|
||||
salt = base64:encode(Salt),
|
||||
iterationcount = IterationCount,
|
||||
serverkey = base64:encode(ServerKey)}.
|
||||
|
||||
is_password_scram_valid(Password, Passwd) ->
|
||||
IterationCount = Passwd#passwd.iterationcount,
|
||||
Salt = base64:decode(Passwd#passwd.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
|
||||
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
(base64:decode(Passwd#passwd.storedkey) == StoredKey).
|
||||
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, passwd,
|
||||
[{passwd, [us, password],
|
||||
fun({passwd, {User, _Host}, Password}) ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = Password}
|
||||
case is_list(Password) of
|
||||
true ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = Password};
|
||||
false ->
|
||||
#passwd{user_host = {User, Host},
|
||||
password = "",
|
||||
storedkey = Password#scram.storedkey,
|
||||
serverkey = Password#scram.serverkey,
|
||||
salt = Password#scram.salt,
|
||||
iterationcount = Password#scram.iterationcount}
|
||||
end
|
||||
end}]);
|
||||
update_table(Host, odbc) ->
|
||||
gen_storage_migration:migrate_odbc(
|
||||
|
||||
+129
-18
@@ -114,7 +114,8 @@
|
||||
ip,
|
||||
aux_fields = [],
|
||||
fsm_limit_opts,
|
||||
lang}).
|
||||
lang,
|
||||
flash_connection = false}).
|
||||
|
||||
%-define(DBGFSM, true).
|
||||
|
||||
@@ -151,6 +152,14 @@
|
||||
"id='~s' from='~s'~s~s>"
|
||||
).
|
||||
|
||||
-define(FLASH_STREAM_HEADER,
|
||||
"<?xml version='1.0'?>"
|
||||
"<flash:stream xmlns='jabber:client' "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"id='~s' from='~s'~s~s>"
|
||||
).
|
||||
-define(NS_FLASH_STREAM, "http://www.jabber.com/streams/flash").
|
||||
|
||||
-define(INVALID_NS_ERR, exmpp_stream:error('invalid-namespace')).
|
||||
-define(INVALID_XML_ERR, exmpp_stream:error('xml-not-well-formed')).
|
||||
-define(HOST_UNKNOWN_ERR, exmpp_stream:error('host-unknown')).
|
||||
@@ -344,7 +353,8 @@ get_subscribed(FsmRef) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
|
||||
wait_for_stream({xmlstreamstart, #xmlel{ns = NS, name = Name} = Opening},
|
||||
StateData) ->
|
||||
DefaultLang = case ?MYLANG of
|
||||
undefined ->
|
||||
"en";
|
||||
@@ -353,11 +363,19 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
|
||||
end,
|
||||
Header = exmpp_stream:opening_reply(Opening,
|
||||
StateData#state.streamid, DefaultLang),
|
||||
case NS of
|
||||
?NS_XMPP ->
|
||||
case {NS, exmpp_xml:is_ns_declared_here(Opening, ?NS_FLASH_STREAM),
|
||||
?FLASH_HACK,
|
||||
StateData#state.flash_connection} of
|
||||
{_, true, true, false} ->
|
||||
%% Flash client connecting - attention!
|
||||
%% Some of them don't provide an xmlns:stream attribute -
|
||||
%% compensate for that.
|
||||
wait_for_stream({xmlstreamstart, Opening#xmlel{ns = ?NS_XMPP}},
|
||||
StateData#state{flash_connection = true});
|
||||
{?NS_XMPP, _, _, _} ->
|
||||
ServerB = exmpp_stringprep:nameprep(
|
||||
exmpp_stream:get_receiving_entity(Opening)),
|
||||
Server = binary_to_list(ServerB),
|
||||
Server = binary_to_list(ServerB),
|
||||
case ?IS_MY_HOST(Server) of
|
||||
true ->
|
||||
Lang = case exmpp_stream:get_lang(Opening) of
|
||||
@@ -518,11 +536,17 @@ wait_for_stream({xmlstreamstart, #xmlel{ns = NS} = Opening}, StateData) ->
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, "", DefaultLang),
|
||||
send_element(StateData, ?INVALID_NS_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
_ ->
|
||||
case Name of
|
||||
"policy-file-request" ->
|
||||
send_text(StateData, flash_policy_string()),
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_header(StateData, ?MYNAME, "", DefaultLang),
|
||||
send_element(StateData, ?INVALID_NS_ERR),
|
||||
send_trailer(StateData),
|
||||
{stop, normal, StateData}
|
||||
end
|
||||
end;
|
||||
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
@@ -764,9 +788,11 @@ wait_for_feature_request({xmlstreamelement, #xmlel{ns = NS, name = Name} = El},
|
||||
fsm_next_state(wait_for_feature_request, StateData);
|
||||
<<"zlib">> ->
|
||||
Socket = StateData#state.socket,
|
||||
Compressed = exmpp_xml:node_to_list(
|
||||
exmpp_server_compression:compressed(), [?DEFAULT_NS], ?PREFIXED_NS),
|
||||
ZlibSocket = (StateData#state.sockmod):compress(
|
||||
Socket,
|
||||
exmpp_server_compression:compressed()),
|
||||
Compressed),
|
||||
fsm_next_state(wait_for_stream,
|
||||
StateData#state{socket = ZlibSocket,
|
||||
streamid = new_id()
|
||||
@@ -820,6 +846,21 @@ wait_for_sasl_response({xmlstreamelement, #xmlel{ns = NS, name = Name} = El},
|
||||
StateData#state.socket),
|
||||
send_element(StateData, exmpp_server_sasl:success()),
|
||||
U = proplists:get_value(username, Props),
|
||||
AuthModule = proplists:get_value(auth_module, Props),
|
||||
?INFO_MSG("(~w) Accepted authentication for ~s by ~s",
|
||||
[StateData#state.socket, U, AuthModule]),
|
||||
fsm_next_state(wait_for_stream,
|
||||
StateData#state{
|
||||
streamid = new_id(),
|
||||
authenticated = true,
|
||||
auth_module = AuthModule,
|
||||
user = list_to_binary(U)});
|
||||
{ok, Props, ServerOut} ->
|
||||
catch (StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
send_element(StateData, exmpp_server_sasl:success(ServerOut)),
|
||||
U = proplists:get_value(username, Props),
|
||||
|
||||
AuthModule = proplists:get_value(auth_module, Props),
|
||||
?INFO_MSG("(~w) Accepted authentication for ~s by ~s",
|
||||
[StateData#state.socket, U, AuthModule]),
|
||||
@@ -868,6 +909,29 @@ wait_for_sasl_response(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
resource_conflict_action(U, S, R) ->
|
||||
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
|
||||
true ->
|
||||
ejabberd_config:get_local_option({resource_conflict,binary_to_list(S)});
|
||||
false ->
|
||||
acceptnew
|
||||
end,
|
||||
Option = case OptionRaw of
|
||||
setresource -> setresource;
|
||||
closeold -> acceptnew; %% ejabberd_sm will close old session
|
||||
closenew -> closenew;
|
||||
acceptnew -> acceptnew;
|
||||
_ -> acceptnew %% default ejabberd behavior
|
||||
end,
|
||||
case Option of
|
||||
acceptnew ->
|
||||
{accept_resource, R};
|
||||
closenew ->
|
||||
closenew;
|
||||
setresource ->
|
||||
Rnew = lists:concat([randoms:get_string() | tuple_to_list(now())]),
|
||||
{accept_resource, Rnew}
|
||||
end.
|
||||
|
||||
wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
try
|
||||
@@ -884,11 +948,17 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
|
||||
%%send_element(StateData,
|
||||
%% exmpp_stream:features([exmpp_server_session:feature()
|
||||
%% | RosterVersioningFeature])),
|
||||
JID = exmpp_jid:make(StateData#state.user, StateData#state.server, R),
|
||||
Res = exmpp_server_binding:bind(El, JID),
|
||||
send_element(StateData, Res),
|
||||
fsm_next_state(wait_for_session,
|
||||
case resource_conflict_action(StateData#state.user, StateData#state.server, list_to_binary(R)) of
|
||||
closenew ->
|
||||
send_element(StateData, ?SERRT_CONFLICT), %% (Lang, "Replaced by new connection")),
|
||||
fsm_next_state(wait_for_bind, StateData);
|
||||
{accept_resource, R2} ->
|
||||
JID = exmpp_jid:make(StateData#state.user, StateData#state.server, R2),
|
||||
Res = exmpp_server_binding:bind(El, JID),
|
||||
send_element(StateData, Res),
|
||||
fsm_next_state(wait_for_session,
|
||||
StateData#state{resource = exmpp_jid:resource(JID), jid = JID})
|
||||
end
|
||||
catch
|
||||
throw:{stringprep, resourceprep, _, _} ->
|
||||
Err = exmpp_server_binding:error(El, 'bad-request'),
|
||||
@@ -1565,8 +1635,15 @@ change_shaper(StateData, JID) ->
|
||||
|
||||
send_text(StateData, Text) when StateData#state.xml_socket ->
|
||||
?DEBUG("Send Text on stream = ~p", [lists:flatten(Text)]),
|
||||
Text1 =
|
||||
if ?FLASH_HACK and StateData#state.flash_connection ->
|
||||
%% send a null byte after each stanza to Flash clients
|
||||
[Text, 0];
|
||||
true ->
|
||||
Text
|
||||
end,
|
||||
(StateData#state.sockmod):send_xml(StateData#state.socket,
|
||||
{xmlstreamraw, Text});
|
||||
{xmlstreamraw, Text1});
|
||||
send_text(StateData, Text) ->
|
||||
?DEBUG("Send XML on stream = ~s", [Text]),
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text).
|
||||
@@ -1579,6 +1656,15 @@ send_element(StateData, El) when StateData#state.xml_socket ->
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, exmpp_stanza:to_iolist(El)).
|
||||
|
||||
send_header(StateData,Server, Version, Lang)
|
||||
when StateData#state.flash_connection ->
|
||||
Header = io_lib:format(?FLASH_STREAM_HEADER,
|
||||
[StateData#state.streamid,
|
||||
Server,
|
||||
Version,
|
||||
Lang]),
|
||||
send_text(StateData, Header);
|
||||
|
||||
send_header(StateData, Server, Version, Lang)
|
||||
when StateData#state.xml_socket ->
|
||||
VersionAttr =
|
||||
@@ -1781,9 +1867,9 @@ presence_update(From, Packet, StateData) ->
|
||||
update_priority(NewPriority, Packet, StateData),
|
||||
FromUnavail = (StateData#state.pres_last == undefined),
|
||||
?DEBUG("from unavail = ~p~n", [FromUnavail]),
|
||||
NewStateData = StateData#state{pres_last = Packet,
|
||||
pres_timestamp = Timestamp},
|
||||
NewState =
|
||||
NewStateData = StateData#state{pres_last = Packet,
|
||||
pres_timestamp = Timestamp},
|
||||
if
|
||||
FromUnavail ->
|
||||
ejabberd_hooks:run(user_available_hook,
|
||||
@@ -2361,3 +2447,28 @@ pack_string(String, Pack) ->
|
||||
none ->
|
||||
{String, gb_trees:insert(String, String, Pack)}
|
||||
end.
|
||||
|
||||
|
||||
%% @spec () -> string()
|
||||
%% @doc Build the content of a Flash policy file.
|
||||
%% It specifies as domain "*".
|
||||
%% It specifies as to-ports the ports that serve ejabberd_c2s.
|
||||
flash_policy_string() ->
|
||||
Listen = ejabberd_config:get_local_option(listen),
|
||||
ClientPortsDeep = ["," ++ integer_to_list(Port)
|
||||
|| {{Port,_,_}, ejabberd_c2s, _Opts} <- Listen],
|
||||
%% NOTE: The function string:join/2 was introduced in Erlang/OTP R12B-0
|
||||
%% so it can't be used yet in ejabberd.
|
||||
ToPortsString = case lists:flatten(ClientPortsDeep) of
|
||||
[$, | Tail] -> Tail;
|
||||
_ -> []
|
||||
end,
|
||||
|
||||
"<?xml version=\"1.0\"?>\n"
|
||||
"<!DOCTYPE cross-domain-policy SYSTEM "
|
||||
"\"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
|
||||
"<cross-domain-policy>\n"
|
||||
" <allow-access-from domain=\"*\" to-ports=\""
|
||||
++ ToPortsString ++
|
||||
"\"/>\n"
|
||||
"</cross-domain-policy>\n\0".
|
||||
|
||||
+40
-42
@@ -119,27 +119,14 @@ delete_dist(Hook, Host, Node, Module, Function, Seq) ->
|
||||
%% @doc Run the calls of this hook in order, don't care about function results.
|
||||
%% If a call returns stop, no more calls are performed.
|
||||
run(Hook, Args) ->
|
||||
runx(Hook, global, Args).
|
||||
run(Hook, global, Args).
|
||||
|
||||
run(Hook, Host, Args) when is_binary(Host) ->
|
||||
case runx(Hook, Host, Args) of
|
||||
stop -> stop;
|
||||
_ -> runx(Hook, global, Args)
|
||||
end;
|
||||
run(Hook, Host, Args) when Host == global ->
|
||||
runx(Hook, Host, Args).
|
||||
|
||||
runx(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) ->
|
||||
case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Ls}] ->
|
||||
run1(Ls, Hook, Args);
|
||||
[] ->
|
||||
case ets:lookup(hooks, {Hook, global}) of
|
||||
[{_, Ls}] ->
|
||||
run1(Ls, Hook, Args);
|
||||
[] ->
|
||||
ok
|
||||
end
|
||||
run(Hook, Host, Args) ->
|
||||
case get_registered_hooks_for(Hook, Host) of
|
||||
[] ->
|
||||
ok;
|
||||
Ls ->
|
||||
run1(Ls, Hook, Args)
|
||||
end.
|
||||
|
||||
%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal
|
||||
@@ -149,29 +136,16 @@ runx(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) ->
|
||||
%% If a call returns 'stop', no more calls are performed and 'stopped' is returned.
|
||||
%% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned.
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_foldx(Hook, global, Val, Args).
|
||||
run_fold(Hook, global, Val, Args).
|
||||
|
||||
%% @spec (Hook::atom(), Host, Val, Args) -> Val | stopped | NewVal
|
||||
%% Host = global | binary()
|
||||
run_fold(Hook, Host, Val, Args) when is_binary(Host) ->
|
||||
case run_foldx(Hook, Host, Val, Args) of
|
||||
stopped -> stopped;
|
||||
Val2 -> run_foldx(Hook, global, Val2, Args)
|
||||
end;
|
||||
run_fold(Hook, Host, Val, Args) when Host == global ->
|
||||
run_foldx(Hook, Host, Val, Args).
|
||||
|
||||
run_foldx(Hook, Host, Val, Args) ->
|
||||
case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Ls}] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
[] ->
|
||||
case ets:lookup(hooks, {Hook, global}) of
|
||||
[{_, Ls}] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
[] ->
|
||||
Val
|
||||
end
|
||||
run_fold(Hook, Host, Val, Args) ->
|
||||
case get_registered_hooks_for(Hook, Host) of
|
||||
[] ->
|
||||
Val;
|
||||
Ls ->
|
||||
run_fold1(Ls, Hook, Val, Args)
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -240,7 +214,10 @@ handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, NHost}) of
|
||||
[{_, Ls}] ->
|
||||
NewLs = lists:delete({Seq, Module, Function}, Ls),
|
||||
ets:insert(hooks, {{Hook, NHost}, NewLs}),
|
||||
case NewLs of
|
||||
[] -> ets:delete(hooks, {Hook, NHost});
|
||||
_ -> ets:insert(hooks, {{Hook, NHost}, NewLs})
|
||||
end,
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
@@ -250,7 +227,10 @@ handle_call({delete, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
NewLs = lists:delete({Seq, Node, Module, Function}, Ls),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
case NewLs of
|
||||
[] -> ets:delete(hooks, {Hook, Host});
|
||||
_ -> ets:insert(hooks, {{Hook, Host}, NewLs})
|
||||
end,
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
@@ -374,3 +354,21 @@ run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
NewVal ->
|
||||
run_fold1(Ls, Hook, NewVal, Args)
|
||||
end.
|
||||
|
||||
get_registered_hooks_for(Hook, global) ->
|
||||
case ets:lookup(hooks, {Hook, global}) of
|
||||
[{_, Ls}] ->
|
||||
Ls;
|
||||
[] ->
|
||||
[]
|
||||
end;
|
||||
|
||||
get_registered_hooks_for(Hook, Host) ->
|
||||
GlobalHooks = get_registered_hooks_for(Hook, global),
|
||||
HostHooks = case ets:lookup(hooks, {Hook, ejabberd:normalize_host(Host)}) of
|
||||
[{_, Ls}] ->
|
||||
Ls;
|
||||
[] ->
|
||||
[]
|
||||
end,
|
||||
lists:umerge(GlobalHooks, HostHooks).
|
||||
|
||||
+11
-25
@@ -7,6 +7,8 @@
|
||||
|
||||
%%% Database schema (version / storage / table)
|
||||
%%%
|
||||
%%% The table 'hosts' keeps the dynamic hosts (not defined in ejabberd.cfg)
|
||||
%%%
|
||||
%%% 3.0.0-alpha-x / mnesia / hosts
|
||||
%%% host = string()
|
||||
%%% clusterid = integer()
|
||||
@@ -99,13 +101,18 @@ update_host_conf(Host, Config) when is_list(Host), is_list(Config) ->
|
||||
case registered(Host) of
|
||||
false -> {error, host_process_not_registered};
|
||||
true ->
|
||||
remove(Host),
|
||||
remove_host_info(Host),
|
||||
?MODULE:register(Host, Config)
|
||||
end.
|
||||
|
||||
|
||||
%% Removes a vhost from the system,
|
||||
%% XXX deleting all ODBC data.
|
||||
remove(Host) when is_list(Host) ->
|
||||
HostB = list_to_binary(Host),
|
||||
ejabberd_hooks:run(remove_host, HostB, [HostB]),
|
||||
remove_host_info(Host).
|
||||
|
||||
remove_host_info(Host) ->
|
||||
true = exmpp_stringprep:is_node(Host),
|
||||
ID = get_clusterid(),
|
||||
gen_storage:dirty_delete_where(
|
||||
@@ -213,11 +220,6 @@ handle_info(timeout, State = #state{state=wait_odbc,backend=Backend,odbc_wait_ti
|
||||
{clusterid, int},
|
||||
{config, text}]}]),
|
||||
|
||||
%% Now let's add the default vhost: "localhost"
|
||||
gen_storage:dirty_write(HostB, #hosts{host = Host,
|
||||
clusterid = 1,
|
||||
config = ""}),
|
||||
|
||||
self() ! reload,
|
||||
timer:send_interval(?RELOAD_INTERVAL, reload),
|
||||
{noreply, State#state{state=running,odbc_wait_time=0}};
|
||||
@@ -349,10 +351,7 @@ start_host(Host) when is_list(Host) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
end,
|
||||
case auth_method(Host) of
|
||||
{host_method, HostMethod} ->
|
||||
ejabberd_auth:start_method(Host, HostMethod)
|
||||
end,
|
||||
ejabberd_auth:start(Host),
|
||||
ok.
|
||||
|
||||
|
||||
@@ -369,10 +368,7 @@ stop_host(Host) when is_list(Host) ->
|
||||
lists:foreach(fun(Module) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
end, gen_mod:loaded_modules(Host)),
|
||||
case auth_method(Host) of
|
||||
{host_method, Method} ->
|
||||
ejabberd_auth:stop_method(Host, Method)
|
||||
end.
|
||||
ejabberd_auth:stop(Host).
|
||||
|
||||
%% Get the current vhost list from a variety of sources (ODBC, internal)
|
||||
get_hosts(ejabberd) -> ?MYHOSTS;
|
||||
@@ -420,16 +416,6 @@ diff_hosts(NewHosts, OldHosts) ->
|
||||
AddHosts = NewHosts -- OldHosts,
|
||||
{AddHosts,RemoveHosts}.
|
||||
|
||||
%% XXX - this should be part of auth, not hosts.
|
||||
auth_method(Host) ->
|
||||
case ejabberd_config:get_host_option(Host, auth_method) of
|
||||
undefined ->
|
||||
[Default] = ejabberd_config:get_host_option(global, auth_method),
|
||||
{host_method, Default};
|
||||
Other ->
|
||||
{host_method, Other}
|
||||
end.
|
||||
|
||||
configure_static_hosts() ->
|
||||
?DEBUG("Node startup - configuring hosts: ~p", [?MYHOSTS]),
|
||||
%% Add a null configuration for all MYHOSTS - this ensures
|
||||
|
||||
@@ -159,21 +159,23 @@ process_element(El,State) ->
|
||||
|
||||
add_user(El, Domain) ->
|
||||
User = exmpp_xml:get_attribute(El,<<"name">>,none),
|
||||
PasswordFormat = exmpp_xml:get_attribute(El,<<"password-format">>,<<"plaintext">>),
|
||||
Password = exmpp_xml:get_attribute(El,<<"password">>,none),
|
||||
add_user(El, Domain, User, Password).
|
||||
add_user(El, Domain, User, PasswordFormat, Password).
|
||||
|
||||
%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
|
||||
%% @spec (El::xmlel(), Domain::string(), User::binary(), PasswordFormat::binary(), Password::binary() | none)
|
||||
%% -> ok | {error, ErrorText::string()}
|
||||
%% @doc Add a new user to the database.
|
||||
%% If user already exists, it will be only updated.
|
||||
add_user(El, Domain, User, none) ->
|
||||
add_user(El, Domain, User, <<"plaintext">>, none) ->
|
||||
io:format("Account ~s@~s will not be created, updating it...~n",
|
||||
[User, Domain]),
|
||||
io:format(""),
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
add_user(El, Domain, User, Password) ->
|
||||
case create_user(User,Password,Domain) of
|
||||
add_user(El, Domain, User, <<"scram">> = PasswordFormat, Password) ->
|
||||
Password2 = prepare_password(PasswordFormat, Password, El),
|
||||
case create_user(User,Password2,Domain) of
|
||||
ok ->
|
||||
populate_user_with_elements(El, Domain, User),
|
||||
ok;
|
||||
@@ -188,6 +190,21 @@ add_user(El, Domain, User, Password) ->
|
||||
{error, Other}
|
||||
end.
|
||||
|
||||
prepare_password(<<"plaintext">>, PasswordBinary, _El) ->
|
||||
?BTL(PasswordBinary);
|
||||
prepare_password(<<"scram">>, none, El) ->
|
||||
ScramEl = exmpp_xml:get_element(El, 'scram-hash'),
|
||||
#scram{storedkey = base64:decode(exmpp_xml:get_attribute(
|
||||
ScramEl, <<"stored-key">>, none)),
|
||||
serverkey = base64:decode(exmpp_xml:get_attribute(
|
||||
ScramEl, <<"server-key">>, none)),
|
||||
salt = base64:decode(exmpp_xml:get_attribute(
|
||||
ScramEl, <<"salt">>, none)),
|
||||
iterationcount = list_to_integer(exmpp_xml:get_attribute_as_list(
|
||||
ScramEl, <<"iteration-count">>,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT))
|
||||
}.
|
||||
|
||||
populate_user_with_elements(El, Domain, User) ->
|
||||
exmpp_xml:foreach(
|
||||
fun (_,Child) ->
|
||||
@@ -199,7 +216,7 @@ populate_user_with_elements(El, Domain, User) ->
|
||||
%% -> ok | {atomic, exists} | {error, not_allowed}
|
||||
%% @doc Create a new user
|
||||
create_user(User,Password,Domain) ->
|
||||
case ejabberd_auth:try_register(?BTL(User),?BTL(Domain),?BTL(Password)) of
|
||||
case ejabberd_auth:try_register(?BTL(User),?BTL(Domain),Password) of
|
||||
{atomic,ok} -> ok;
|
||||
{atomic, exists} -> {atomic, exists};
|
||||
{error, not_allowed} -> {error, not_allowed};
|
||||
@@ -482,10 +499,23 @@ export_user(Fd, Username, Host) ->
|
||||
|
||||
%% @spec (Username::string(), Host::string()) -> string()
|
||||
extract_user(Username, Host) ->
|
||||
Password = ejabberd_auth:get_password_s(Username, Host),
|
||||
Password = ejabberd_auth:get_password(Username, Host),
|
||||
PasswordStr = build_password_string(Password),
|
||||
UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]],
|
||||
UserInfoString = lists:flatten(UserInfo),
|
||||
io_lib:format("<user name='~s' password='~s'>~s</user>", [Username, Password, UserInfoString]).
|
||||
io_lib:format("<user name='~s' ~s ~s</user>",
|
||||
[Username, PasswordStr, UserInfoString]).
|
||||
|
||||
build_password_string({StoredKey, ServerKey, Salt, IterationCount}) ->
|
||||
io_lib:format("password-format='scram'>"
|
||||
"<scram-hash stored-key='~s' server-key='~s' "
|
||||
"salt='~s' iteration-count='~w'/> ",
|
||||
[base64:encode_to_string(StoredKey),
|
||||
base64:encode_to_string(ServerKey),
|
||||
base64:encode_to_string(Salt),
|
||||
IterationCount]);
|
||||
build_password_string(Password) when is_list(Password) ->
|
||||
io_lib:format("password-format='plaintext' password='~s'>", [Password]).
|
||||
|
||||
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
|
||||
extract_user_info(roster, Username, Host) ->
|
||||
|
||||
+38
-1
@@ -42,7 +42,11 @@
|
||||
allow_host/2,
|
||||
incoming_s2s_number/0,
|
||||
outgoing_s2s_number/0,
|
||||
migrate/1
|
||||
migrate/1,
|
||||
clean_temporarily_blocked_table/0,
|
||||
list_temporarily_blocked_hosts/0,
|
||||
external_host_overloaded/1,
|
||||
is_temporarly_blocked/1
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -65,9 +69,14 @@
|
||||
-define(PREFIXED_NS,
|
||||
[{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}]).
|
||||
|
||||
-define(S2S_OVERLOAD_BLOCK_PERIOD, 60).
|
||||
%% once a server is temporarly blocked, it stay blocked for 60 seconds
|
||||
|
||||
-record(s2s, {fromto, pid, key}).
|
||||
-record(state, {}).
|
||||
|
||||
-record(temporarily_blocked, {host, timestamp}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@@ -98,6 +107,31 @@ route(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
clean_temporarily_blocked_table() ->
|
||||
mnesia:clear_table(temporarily_blocked).
|
||||
list_temporarily_blocked_hosts() ->
|
||||
ets:tab2list(temporarily_blocked).
|
||||
|
||||
external_host_overloaded(Host) ->
|
||||
?INFO_MSG("Disabling connections from ~s for ~s seconds", [Host, ?S2S_OVERLOAD_BLOCK_PERIOD]),
|
||||
mnesia:transaction( fun() ->
|
||||
mnesia:write(#temporarily_blocked{host = Host, timestamp = now()})
|
||||
end).
|
||||
|
||||
is_temporarly_blocked(Host) ->
|
||||
case mnesia:dirty_read(temporarily_blocked, Host) of
|
||||
[] -> false;
|
||||
[#temporarily_blocked{timestamp = T}=Entry] ->
|
||||
case timer:now_diff(now(), T) of
|
||||
N when N > ?S2S_OVERLOAD_BLOCK_PERIOD * 1000 * 1000 ->
|
||||
mnesia:dirty_delete_object(Entry),
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
remove_connection(FromTo, Pid, Key) ->
|
||||
case catch mnesia:dirty_match_object(s2s, #s2s{fromto = FromTo,
|
||||
pid = Pid,
|
||||
@@ -248,6 +282,7 @@ init([]) ->
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
|
||||
ejabberd_commands:register_commands(commands()),
|
||||
mnesia:create_table(temporarily_blocked, [{ram_copies, [node()]}, {attributes, record_info(fields, temporarily_blocked)}]),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -576,6 +611,8 @@ update_tables() ->
|
||||
|
||||
%% Check if host is in blacklist or white list
|
||||
allow_host(MyServer, S2SHost) ->
|
||||
allow_host2(MyServer, S2SHost) andalso (not is_temporarly_blocked(S2SHost)).
|
||||
allow_host2(MyServer, S2SHost) ->
|
||||
Hosts = ?MYHOSTS,
|
||||
case lists:dropwhile(
|
||||
fun(ParentDomain) ->
|
||||
|
||||
+37
-5
@@ -27,7 +27,7 @@
|
||||
-module(ejabberd_s2s_in).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
@@ -44,6 +44,7 @@
|
||||
handle_sync_event/4,
|
||||
code_change/4,
|
||||
handle_info/3,
|
||||
print_state/1,
|
||||
terminate/3]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
@@ -80,10 +81,12 @@
|
||||
-define(FSMOPTS, []).
|
||||
-endif.
|
||||
|
||||
-define(FSMLIMITS, [{max_queue, 2000}]). %% if queue grows more than this, we shutdown this connection.
|
||||
|
||||
%% Module start with or without supervisor:
|
||||
-ifdef(NO_TRANSIENT_SUPERVISORS).
|
||||
-define(SUPERVISOR_START, gen_fsm:start(ejabberd_s2s_in, [SockData, Opts],
|
||||
?FSMOPTS)).
|
||||
-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_in, [SockData, Opts],
|
||||
?FSMOPTS ++ ?FSMLIMITS)).
|
||||
-else.
|
||||
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_in_sup,
|
||||
[SockData, Opts])).
|
||||
@@ -102,7 +105,7 @@ start(SockData, Opts) ->
|
||||
?SUPERVISOR_START.
|
||||
|
||||
start_link(SockData, Opts) ->
|
||||
gen_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS).
|
||||
p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS ++ ?FSMLIMITS).
|
||||
|
||||
socket_type() ->
|
||||
xml_stream.
|
||||
@@ -322,14 +325,20 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
_ ->
|
||||
false
|
||||
end,
|
||||
AllowRemoteHost = ejabberd_s2s:allow_host("", AuthDomain),
|
||||
if
|
||||
AuthRes ->
|
||||
AuthRes andalso AllowRemoteHost ->
|
||||
(StateData#state.sockmod):reset_stream(
|
||||
StateData#state.socket),
|
||||
send_element(StateData,
|
||||
exmpp_server_sasl:success()),
|
||||
?DEBUG("(~w) Accepted s2s authentication for ~s",
|
||||
[StateData#state.socket, AuthDomain]),
|
||||
%% acess rules are first checked against the globally defined ones, that have precedence over
|
||||
%% domain-specific ones.. http://www.process-one.net/docs/ejabberd/guide_en.html#AccessRights
|
||||
%% since there is allways a shaper defined globally for s2s, it doesn't matter the actual
|
||||
%% local host, since the globall one will be used, even if this domain has a special rule
|
||||
change_shaper(StateData, "", exmpp_jid:make(AuthDomain)),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
authenticated = true,
|
||||
@@ -617,9 +626,32 @@ handle_info(_, StateName, StateData) ->
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, _StateName, StateData) ->
|
||||
?DEBUG("terminated: ~p", [Reason]),
|
||||
case Reason of
|
||||
{process_limit, _} ->
|
||||
[ejabberd_s2s:external_host_overloaded(Host) || Host <- get_external_hosts(StateData)];
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
get_external_hosts(StateData) ->
|
||||
case StateData#state.authenticated of
|
||||
true ->
|
||||
[StateData#state.auth_domain];
|
||||
false ->
|
||||
Connections = StateData#state.connections,
|
||||
[D || {{D, _}, established} <- dict:to_list(Connections)]
|
||||
end.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: print_state/1
|
||||
%% Purpose: Prepare the state to be printed on error log
|
||||
%% Returns: State to print
|
||||
%%----------------------------------------------------------------------
|
||||
print_state(State) ->
|
||||
State.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
+14
-6
@@ -59,6 +59,7 @@
|
||||
get_session_pid/1,
|
||||
get_user_info/3,
|
||||
get_user_ip/1,
|
||||
is_existing_resource/3,
|
||||
migrate/1
|
||||
]).
|
||||
|
||||
@@ -727,16 +728,12 @@ check_for_sessions_to_replace(JID) ->
|
||||
check_max_sessions(JID).
|
||||
|
||||
check_existing_resources(JID) ->
|
||||
USR = {exmpp_jid:prep_node(JID),
|
||||
exmpp_jid:prep_domain(JID),
|
||||
exmpp_jid:prep_resource(JID)},
|
||||
%% A connection exist with the same resource. We replace it:
|
||||
SIDs = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]),
|
||||
SIDs = get_resource_sessions(JID),
|
||||
if
|
||||
SIDs == [] -> ok;
|
||||
true ->
|
||||
%% A connection exist with the same resource. We replace it:
|
||||
MaxSID = lists:max(SIDs),
|
||||
lists:foreach(
|
||||
fun({_, Pid} = S) when S /= MaxSID ->
|
||||
@@ -745,6 +742,17 @@ check_existing_resources(JID) ->
|
||||
end, SIDs)
|
||||
end.
|
||||
|
||||
is_existing_resource(U, S, R) ->
|
||||
[] /= get_resource_sessions(exmpp_jid:make(U, S, R)).
|
||||
|
||||
get_resource_sessions(JID) ->
|
||||
USR = {exmpp_jid:prep_node(JID),
|
||||
exmpp_jid:prep_domain(JID),
|
||||
exmpp_jid:prep_resource(JID)},
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]).
|
||||
|
||||
check_max_sessions(JID) ->
|
||||
%% If the max number of sessions for a given is reached, we replace the
|
||||
%% first one
|
||||
|
||||
+22
-6
@@ -41,10 +41,10 @@ update() ->
|
||||
case update_info() of
|
||||
{ok, Dir, _UpdatedBeams, _Script, LowLevelScript, _Check} ->
|
||||
Eval =
|
||||
release_handler_1:eval_script(
|
||||
eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?INFO_MSG("eval: ~p~n", [Eval]),
|
||||
?DEBUG("eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
@@ -58,15 +58,25 @@ update(ModulesToUpdate) ->
|
||||
[A || A <- UpdatedBeamsAll, B <- ModulesToUpdate, A == B],
|
||||
{_, LowLevelScript, _} = build_script(Dir, UpdatedBeamsNow),
|
||||
Eval =
|
||||
release_handler_1:eval_script(
|
||||
eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
?INFO_MSG("eval: ~p~n", [Eval]),
|
||||
?DEBUG("eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
%% OTP R14B03 and older provided release_handler_1:eval_script/3
|
||||
%% But OTP R14B04 and newer provide release_handler_1:eval_script/5
|
||||
eval_script(Script, Apps, LibDirs) ->
|
||||
case lists:member({eval_script, 5}, release_handler_1:module_info(exports)) of
|
||||
true ->
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs, [], []);
|
||||
false ->
|
||||
release_handler_1:eval_script(Script, Apps, LibDirs)
|
||||
end.
|
||||
|
||||
%% Get information about the modified modules
|
||||
update_info() ->
|
||||
Dir = filename:dirname(code:which(ejabberd)),
|
||||
@@ -118,8 +128,10 @@ get_new_version(Module) ->
|
||||
|
||||
get_current_version(Module) ->
|
||||
Attrs = Module:module_info(attributes),
|
||||
{value, {vsn, CurVsn}} = lists:keysearch(vsn, 1, Attrs),
|
||||
CurVsn.
|
||||
case lists:keysearch(vsn, 1, Attrs) of
|
||||
{value, {vsn, CurVsn}} -> CurVsn;
|
||||
_ -> unknown_version
|
||||
end.
|
||||
|
||||
%% @spec(Dir::string(), UpdatedBeams::[atom()]) -> {Script,LowLevelScript,Check}
|
||||
build_script(Dir, UpdatedBeams) ->
|
||||
@@ -134,6 +146,10 @@ build_script(Dir, UpdatedBeams) ->
|
||||
?DEBUG("script: ~p~n", [Script]),
|
||||
?DEBUG("low level script: ~p~n", [LowLevelScript]),
|
||||
?DEBUG("check: ~p~n", [Check]);
|
||||
{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]),
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_zlib.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Interface to exmpp_compress
|
||||
%%% Created : 19 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_zlib).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, start_link/0,
|
||||
enable_zlib/2, disable_zlib/1,
|
||||
send/2,
|
||||
recv/2, recv/3, recv_data/2,
|
||||
setopts/2,
|
||||
get_sockmod/1,
|
||||
sockname/1, peername/1,
|
||||
controlling_process/2,
|
||||
close/1]).
|
||||
|
||||
-define(DEFLATE, 1).
|
||||
-define(INFLATE, 2).
|
||||
|
||||
%% Copied from exmpp/src/core/exmpp_compress.erl
|
||||
-record(compress_socket, {socket,
|
||||
packet_mode = binary,
|
||||
port
|
||||
}).
|
||||
|
||||
start() ->
|
||||
exmpp_compress:start().
|
||||
|
||||
start_link() ->
|
||||
exmpp_compress:start_link().
|
||||
|
||||
|
||||
enable_zlib(SockMod, Socket) ->
|
||||
try
|
||||
ZlibSock = exmpp_compress:enable_compression({SockMod, Socket},
|
||||
[{compress_method, zlib}]),
|
||||
{ok, ZlibSock}
|
||||
catch
|
||||
Exception ->
|
||||
{error, Exception}
|
||||
end.
|
||||
|
||||
disable_zlib(ZlibSock) ->
|
||||
exmpp_compress:disable_compression(ZlibSock).
|
||||
|
||||
recv(Socket, Length) ->
|
||||
recv(Socket, Length, infinity).
|
||||
recv(ZlibSock, _Length, Timeout) ->
|
||||
exmpp_compress:recv(ZlibSock, Timeout).
|
||||
|
||||
recv_data(#compress_socket{socket = {SockMod, Socket}} = ZlibSock, Packet) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
recv_data2(ZlibSock, Packet);
|
||||
_ ->
|
||||
case SockMod:recv_data(Socket, Packet) of
|
||||
{ok, Packet2} ->
|
||||
recv_data2(ZlibSock, Packet2);
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end.
|
||||
|
||||
recv_data2(ZlibSock, Packet) ->
|
||||
exmpp_compress:recv_data(ZlibSock, Packet).
|
||||
|
||||
send(ZlibSock, Packet) ->
|
||||
exmpp_compress:send(ZlibSock, Packet).
|
||||
|
||||
|
||||
setopts(ZlibSock, Opts) ->
|
||||
exmpp_compress:setopts(ZlibSock, Opts).
|
||||
|
||||
get_sockmod(#compress_socket{socket = {SockMod, _Port}}) ->
|
||||
SockMod.
|
||||
|
||||
sockname(ZlibSock) ->
|
||||
exmpp_compress:sockname(ZlibSock).
|
||||
|
||||
peername(ZlibSock) ->
|
||||
exmpp_compress:peername(ZlibSock).
|
||||
|
||||
controlling_process(ZlibSock, Pid) ->
|
||||
exmpp_compress:controlling_process(ZlibSock, Pid).
|
||||
|
||||
close(ZlibSock) ->
|
||||
exmpp_compress:close(ZlibSock).
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@
|
||||
|
||||
ZLIB_CFLAGS = @ZLIB_CFLAGS@
|
||||
ZLIB_LIBS = @ZLIB_LIBS@
|
||||
|
||||
ERLANG_CFLAGS = @ERLANG_CFLAGS@
|
||||
ERLANG_LIBS = @ERLANG_LIBS@
|
||||
|
||||
# Assume Linux-style dynamic library flags
|
||||
DYNAMIC_LIB_CFLAGS = -fpic -shared
|
||||
ifeq ($(shell uname),Darwin)
|
||||
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
|
||||
endif
|
||||
ifeq ($(shell uname),SunOs)
|
||||
DYNAMIC_LIB_CFLAGS = -KPIC -G -z text
|
||||
endif
|
||||
|
||||
EFLAGS += -I ..
|
||||
EFLAGS += -pz ..
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
EFLAGS+=+debug_info +export_all
|
||||
endif
|
||||
|
||||
ERLSHLIBS = ../ejabberd_zlib_drv.so
|
||||
OUTDIR = ..
|
||||
SOURCES = $(wildcard *.erl)
|
||||
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
|
||||
|
||||
all: $(BEAMS) $(ERLSHLIBS)
|
||||
|
||||
$(OUTDIR)/%.beam: %.erl
|
||||
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
|
||||
|
||||
#all: $(ERLSHLIBS)
|
||||
# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
|
||||
|
||||
$(ERLSHLIBS): ../%.so: %.c
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(LIBS) \
|
||||
$(ZLIB_LIBS) \
|
||||
$(ZLIB_CFLAGS) \
|
||||
$(ERLANG_LIBS) \
|
||||
$(ERLANG_CFLAGS) \
|
||||
-o $@ \
|
||||
$(DYNAMIC_LIB_CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(BEAMS) $(ERLSHLIBS)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile
|
||||
|
||||
TAGS:
|
||||
etags *.erl
|
||||
|
||||
|
||||
DEVDOCDIR=../../doc/devdoc
|
||||
DDTDIR=..
|
||||
HTMLS = $(SOURCES:%.erl=../../doc/devdoc/%.html)
|
||||
ERLHTMLS = $(SOURCES:%.erl=../../doc/devdoc/%.erl.html)
|
||||
SVGS = $(SOURCES:%.erl=../../doc/devdoc/%.svg)
|
||||
|
||||
devdoc: $(HTMLS) $(ERLHTMLS) $(SVGS)
|
||||
|
||||
$(DEVDOCDIR)/%.erl.html: %.erl
|
||||
@ERL@ -noshell -pa $(DEVDOCDIR) -run escobar_run file $< $(DDTDIR) -s init stop
|
||||
|
||||
$(DEVDOCDIR)/%.html: %.erl
|
||||
@ERL@ -noshell -run edoc_run file $< \
|
||||
'[{dir,"$(DDTDIR)"},{packages,false},{todo,true},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"overview.edoc"}]' -s init stop
|
||||
|
||||
$(DEVDOCDIR)/%.svg: %.erl
|
||||
@ERL@ -noshell -pa $(DEVDOCDIR) -run funrelg dir $< $(DDTDIR) -s init stop
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
include ..\Makefile.inc
|
||||
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
OUTDIR = ..
|
||||
BEAMS = ..\ejabberd_zlib.beam
|
||||
|
||||
SOURCE = ejabberd_zlib_drv.c
|
||||
OBJECT = ejabberd_zlib_drv.o
|
||||
DLL = $(OUTDIR)\ejabberd_zlib_drv.dll
|
||||
|
||||
ALL : $(DLL) $(BEAMS)
|
||||
|
||||
CLEAN :
|
||||
-@erase $(DLL)
|
||||
-@erase $(OUTDIR)\ejabberd_zlib_drv.exp
|
||||
-@erase $(OUTDIR)\ejabberd_zlib_drv.lib
|
||||
-@erase $(OBJECT)
|
||||
-@erase $(BEAMS)
|
||||
|
||||
$(OUTDIR)\ejabberd_zlib.beam : ejabberd_zlib.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_zlib.erl
|
||||
|
||||
CC=cl.exe
|
||||
CC_FLAGS=-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT -MD -Ox -I"$(ERLANG_DIR)\usr\include" -I"$(EI_DIR)\include" -I"$(ZLIB_DIR)\include"
|
||||
|
||||
LD=link.exe
|
||||
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" "$(ZLIB_LIB)" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
|
||||
|
||||
$(DLL) : $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
|
||||
|
||||
$(OBJECT) : $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_zlib.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Interface to zlib
|
||||
%%% Created : 19 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License
|
||||
%%% along with this program; if not, write to the Free Software
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_zlib).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start/0, start_link/0,
|
||||
enable_zlib/2, disable_zlib/1,
|
||||
send/2,
|
||||
recv/2, recv/3, recv_data/2,
|
||||
setopts/2,
|
||||
sockname/1, peername/1,
|
||||
get_sockmod/1,
|
||||
controlling_process/2,
|
||||
close/1]).
|
||||
|
||||
%% Internal exports, call-back functions.
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2]).
|
||||
|
||||
-define(DEFLATE, 1).
|
||||
-define(INFLATE, 2).
|
||||
|
||||
-record(zlibsock, {sockmod, socket, zlibport}).
|
||||
|
||||
start() ->
|
||||
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
init([]) ->
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
{ok, Port}.
|
||||
|
||||
|
||||
%%% --------------------------------------------------------
|
||||
%%% The call-back functions.
|
||||
%%% --------------------------------------------------------
|
||||
|
||||
handle_call(_, _, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_cast(_, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({'EXIT', Port, Reason}, Port) ->
|
||||
{stop, {port_died, Reason}, Port};
|
||||
|
||||
handle_info({'EXIT', _Pid, _Reason}, Port) ->
|
||||
{noreply, Port};
|
||||
|
||||
handle_info(_, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, Port) ->
|
||||
Port ! {self, close},
|
||||
ok.
|
||||
|
||||
|
||||
enable_zlib(SockMod, Socket) ->
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
{ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}.
|
||||
|
||||
disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
port_close(Port),
|
||||
{SockMod, Socket}.
|
||||
|
||||
recv(Socket, Length) ->
|
||||
recv(Socket, Length, infinity).
|
||||
recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock,
|
||||
Length, Timeout) ->
|
||||
case SockMod:recv(Socket, Length, Timeout) of
|
||||
{ok, Packet} ->
|
||||
recv_data(ZlibSock, Packet);
|
||||
{error, _Reason} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
recv_data(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock, Packet) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
recv_data2(ZlibSock, Packet);
|
||||
_ ->
|
||||
case SockMod:recv_data(Socket, Packet) of
|
||||
{ok, Packet2} ->
|
||||
recv_data2(ZlibSock, Packet2);
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
end.
|
||||
|
||||
recv_data2(ZlibSock, Packet) ->
|
||||
case catch recv_data1(ZlibSock, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
{error, Reason};
|
||||
Res ->
|
||||
Res
|
||||
end.
|
||||
|
||||
recv_data1(#zlibsock{zlibport = Port} = _ZlibSock, Packet) ->
|
||||
case port_control(Port, ?INFLATE, Packet) of
|
||||
<<0, In/binary>> ->
|
||||
{ok, In};
|
||||
<<1, Error/binary>> ->
|
||||
{error, binary_to_list(Error)}
|
||||
end.
|
||||
|
||||
send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port},
|
||||
Packet) ->
|
||||
case port_control(Port, ?DEFLATE, Packet) of
|
||||
<<0, Out/binary>> ->
|
||||
SockMod:send(Socket, Out);
|
||||
<<1, Error/binary>> ->
|
||||
{error, binary_to_list(Error)}
|
||||
end.
|
||||
|
||||
|
||||
setopts(#zlibsock{sockmod = SockMod, socket = Socket}, Opts) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, Opts);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, Opts)
|
||||
end.
|
||||
|
||||
sockname(#zlibsock{sockmod = SockMod, socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:sockname(Socket);
|
||||
_ ->
|
||||
SockMod:sockname(Socket)
|
||||
end.
|
||||
|
||||
get_sockmod(#zlibsock{sockmod = SockMod}) ->
|
||||
SockMod.
|
||||
|
||||
peername(#zlibsock{sockmod = SockMod, socket = Socket}) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
end.
|
||||
|
||||
controlling_process(#zlibsock{sockmod = SockMod, socket = Socket}, Pid) ->
|
||||
SockMod:controlling_process(Socket, Pid).
|
||||
|
||||
close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
SockMod:close(Socket),
|
||||
port_close(Port).
|
||||
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* ejabberd, Copyright (C) 2002-2011 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <erl_driver.h>
|
||||
#include <zlib.h>
|
||||
|
||||
|
||||
#define BUF_SIZE 1024
|
||||
|
||||
typedef struct {
|
||||
ErlDrvPort port;
|
||||
z_stream *d_stream;
|
||||
z_stream *i_stream;
|
||||
} ejabberd_zlib_data;
|
||||
|
||||
|
||||
static ErlDrvData ejabberd_zlib_drv_start(ErlDrvPort port, char *buff)
|
||||
{
|
||||
ejabberd_zlib_data *d =
|
||||
(ejabberd_zlib_data *)driver_alloc(sizeof(ejabberd_zlib_data));
|
||||
d->port = port;
|
||||
|
||||
d->d_stream = (z_stream *)malloc(sizeof(z_stream));
|
||||
|
||||
d->d_stream->zalloc = (alloc_func)0;
|
||||
d->d_stream->zfree = (free_func)0;
|
||||
d->d_stream->opaque = (voidpf)0;
|
||||
|
||||
deflateInit(d->d_stream, Z_DEFAULT_COMPRESSION);
|
||||
|
||||
d->i_stream = (z_stream *)malloc(sizeof(z_stream));
|
||||
|
||||
d->i_stream->zalloc = (alloc_func)0;
|
||||
d->i_stream->zfree = (free_func)0;
|
||||
d->i_stream->opaque = (voidpf)0;
|
||||
|
||||
inflateInit(d->i_stream);
|
||||
|
||||
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
||||
|
||||
return (ErlDrvData)d;
|
||||
}
|
||||
|
||||
static void ejabberd_zlib_drv_stop(ErlDrvData handle)
|
||||
{
|
||||
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
|
||||
|
||||
deflateEnd(d->d_stream);
|
||||
free(d->d_stream);
|
||||
|
||||
inflateEnd(d->i_stream);
|
||||
free(d->i_stream);
|
||||
|
||||
driver_free((char *)handle);
|
||||
}
|
||||
|
||||
|
||||
#define DEFLATE 1
|
||||
#define INFLATE 2
|
||||
|
||||
#define die_unless(cond, errstr) \
|
||||
if (!(cond)) \
|
||||
{ \
|
||||
rlen = strlen(errstr) + 1; \
|
||||
b = driver_realloc_binary(b, rlen); \
|
||||
b->orig_bytes[0] = 1; \
|
||||
strncpy(b->orig_bytes + 1, errstr, rlen - 1); \
|
||||
*rbuf = (char *)b; \
|
||||
return rlen; \
|
||||
}
|
||||
|
||||
|
||||
static int ejabberd_zlib_drv_control(ErlDrvData handle,
|
||||
unsigned int command,
|
||||
char *buf, int len,
|
||||
char **rbuf, int rlen)
|
||||
{
|
||||
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
|
||||
int err;
|
||||
int size;
|
||||
ErlDrvBinary *b;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case DEFLATE:
|
||||
size = BUF_SIZE + 1;
|
||||
rlen = 1;
|
||||
b = driver_alloc_binary(size);
|
||||
b->orig_bytes[0] = 0;
|
||||
|
||||
d->d_stream->next_in = (unsigned char *)buf;
|
||||
d->d_stream->avail_in = len;
|
||||
d->d_stream->avail_out = 0;
|
||||
err = Z_OK;
|
||||
|
||||
while (err == Z_OK && d->d_stream->avail_out == 0)
|
||||
{
|
||||
d->d_stream->next_out = (unsigned char *)b->orig_bytes + rlen;
|
||||
d->d_stream->avail_out = BUF_SIZE;
|
||||
|
||||
err = deflate(d->d_stream, Z_SYNC_FLUSH);
|
||||
die_unless((err == Z_OK) || (err == Z_STREAM_END),
|
||||
"Deflate error");
|
||||
|
||||
rlen += (BUF_SIZE - d->d_stream->avail_out);
|
||||
size += (BUF_SIZE - d->d_stream->avail_out);
|
||||
b = driver_realloc_binary(b, size);
|
||||
}
|
||||
b = driver_realloc_binary(b, rlen);
|
||||
*rbuf = (char *)b;
|
||||
return rlen;
|
||||
case INFLATE:
|
||||
size = BUF_SIZE + 1;
|
||||
rlen = 1;
|
||||
b = driver_alloc_binary(size);
|
||||
b->orig_bytes[0] = 0;
|
||||
|
||||
if (len > 0) {
|
||||
d->i_stream->next_in = (unsigned char *)buf;
|
||||
d->i_stream->avail_in = len;
|
||||
d->i_stream->avail_out = 0;
|
||||
err = Z_OK;
|
||||
|
||||
while (err == Z_OK && d->i_stream->avail_out == 0)
|
||||
{
|
||||
d->i_stream->next_out = (unsigned char *)b->orig_bytes + rlen;
|
||||
d->i_stream->avail_out = BUF_SIZE;
|
||||
|
||||
err = inflate(d->i_stream, Z_SYNC_FLUSH);
|
||||
die_unless((err == Z_OK) || (err == Z_STREAM_END),
|
||||
"Inflate error");
|
||||
|
||||
rlen += (BUF_SIZE - d->i_stream->avail_out);
|
||||
size += (BUF_SIZE - d->i_stream->avail_out);
|
||||
b = driver_realloc_binary(b, size);
|
||||
}
|
||||
}
|
||||
b = driver_realloc_binary(b, rlen);
|
||||
*rbuf = (char *)b;
|
||||
return rlen;
|
||||
}
|
||||
|
||||
b = driver_alloc_binary(1);
|
||||
b->orig_bytes[0] = 0;
|
||||
*rbuf = (char *)b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
ErlDrvEntry ejabberd_zlib_driver_entry = {
|
||||
NULL, /* F_PTR init, N/A */
|
||||
ejabberd_zlib_drv_start, /* L_PTR start, called when port is opened */
|
||||
ejabberd_zlib_drv_stop, /* F_PTR stop, called when port is closed */
|
||||
NULL, /* F_PTR output, called when erlang has sent */
|
||||
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
||||
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
||||
"ejabberd_zlib_drv", /* char *driver_name, the argument to open_port */
|
||||
NULL, /* F_PTR finish, called when unloaded */
|
||||
NULL, /* handle */
|
||||
ejabberd_zlib_drv_control, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
};
|
||||
|
||||
DRIVER_INIT(ejabberd_zlib_drv) /* must match name in driver_entry */
|
||||
{
|
||||
return &ejabberd_zlib_driver_entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +133,9 @@ INET_DIST_INTERFACE={127,0,0,1}
|
||||
# The same as previous, but erlang will use long hostname
|
||||
# (see erl (1) manual for details)
|
||||
#
|
||||
# Default: ejabberd
|
||||
# Default: ejabberd@localhost
|
||||
#
|
||||
#ERLANG_NODE=ejabberd
|
||||
#ERLANG_NODE=ejabberd@localhost
|
||||
|
||||
#.
|
||||
#' EJABBERD_PID_PATH: ejabberd PID file
|
||||
|
||||
@@ -328,21 +328,13 @@ ctlexec ()
|
||||
{
|
||||
CONN_NAME=$1; shift
|
||||
COMMAND=$@
|
||||
|
||||
CTLEXEC="$ERL \
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME ${CONN_NAME} \
|
||||
-noinput \
|
||||
-hidden \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
$KERNEL_OPTS \
|
||||
-s ejabberd_ctl -extra $ERLANG_NODE"
|
||||
|
||||
# quote input from the command line
|
||||
for i in $COMMAND; do
|
||||
CTLEXEC="$CTLEXEC '$i'";
|
||||
done
|
||||
|
||||
$EXEC_CMD "$CTLEXEC"
|
||||
-s ejabberd_ctl -extra $ERLANG_NODE $COMMAND"
|
||||
}
|
||||
|
||||
# display ctl usage
|
||||
|
||||
@@ -4,7 +4,7 @@ CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@
|
||||
|
||||
ASN_FLAGS = -bber_bin +optimize +driver
|
||||
ASN_FLAGS = -bber_bin +optimize
|
||||
|
||||
ERLANG_CFLAGS = @ERLANG_CFLAGS@
|
||||
ERLANG_LIBS = @ERLANG_LIBS@
|
||||
|
||||
@@ -6,7 +6,7 @@ EFLAGS = -I .. -pz ..
|
||||
OUTDIR = ..
|
||||
BEAMS = ..\eldap.beam ..\eldap_filter.beam ..\eldap_pool.beam ..\eldap_utils.beam ..\eldap_filter_yecc.beam
|
||||
|
||||
ASN_FLAGS = -bber_bin +optimize +driver
|
||||
ASN_FLAGS = -bber_bin +optimize
|
||||
|
||||
ALL : $(BEAMS)
|
||||
|
||||
|
||||
+47
-18
@@ -322,6 +322,14 @@ parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) ->
|
||||
parse_search_args(T,A#eldap_search{timeout = Timeout});
|
||||
parse_search_args([{limit, Limit}|T],A) when is_integer(Limit) ->
|
||||
parse_search_args(T,A#eldap_search{limit = Limit});
|
||||
parse_search_args([{deref_aliases, never}|T],A) ->
|
||||
parse_search_args(T,A#eldap_search{deref_aliases = neverDerefAliases});
|
||||
parse_search_args([{deref_aliases, searching}|T],A) ->
|
||||
parse_search_args(T,A#eldap_search{deref_aliases = derefInSearching});
|
||||
parse_search_args([{deref_aliases, finding}|T],A) ->
|
||||
parse_search_args(T,A#eldap_search{deref_aliases = derefFindingBaseObj});
|
||||
parse_search_args([{deref_aliases, always}|T],A) ->
|
||||
parse_search_args(T,A#eldap_search{deref_aliases = derefAlways});
|
||||
parse_search_args([H|_],_) ->
|
||||
throw({error,{unknown_arg, H}});
|
||||
parse_search_args([],A) ->
|
||||
@@ -430,7 +438,7 @@ init([]) ->
|
||||
end;
|
||||
init({Hosts, Port, Rootdn, Passwd, Opts}) ->
|
||||
catch ssl:start(),
|
||||
ssl:seed(randoms:get_string()),
|
||||
catch ssl:seed(randoms:get_string()),
|
||||
Encrypt = case proplists:get_value(encrypt, Opts) of
|
||||
tls -> tls;
|
||||
_ -> none
|
||||
@@ -445,14 +453,29 @@ 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,
|
||||
CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of
|
||||
[_|_] = Path -> [{cacertfile, Path}];
|
||||
_ -> []
|
||||
end,
|
||||
DepthOpts = case proplists:get_value(tls_depth, Opts) of
|
||||
Depth when is_integer(Depth), Depth >= 0 ->
|
||||
[{depth, Depth}];
|
||||
_ -> []
|
||||
end,
|
||||
Verify = proplists:get_value(tls_verify, Opts),
|
||||
TLSOpts = if (Verify == hard orelse Verify == soft)
|
||||
andalso CacertOpts == [] ->
|
||||
?WARNING_MSG("TLS verification is enabled "
|
||||
"but no CA certfiles configured, so "
|
||||
"verification is disabled.", []),
|
||||
[];
|
||||
Verify == soft ->
|
||||
[{verify, 1}] ++ CacertOpts ++ DepthOpts;
|
||||
Verify == hard ->
|
||||
[{verify, 2}] ++ CacertOpts ++ DepthOpts;
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
{ok, connecting, #eldap{hosts = Hosts,
|
||||
port = PortTemp,
|
||||
rootdn = Rootdn,
|
||||
@@ -684,7 +707,7 @@ gen_req({search, A}) ->
|
||||
{searchRequest,
|
||||
#'SearchRequest'{baseObject = A#eldap_search.base,
|
||||
scope = v_scope(A#eldap_search.scope),
|
||||
derefAliases = neverDerefAliases,
|
||||
derefAliases = A#eldap_search.deref_aliases,
|
||||
sizeLimit = A#eldap_search.limit,
|
||||
timeLimit = v_timeout(A#eldap_search.timeout),
|
||||
typesOnly = v_bool(A#eldap_search.types_only),
|
||||
@@ -957,18 +980,21 @@ polish([], Res, Ref) ->
|
||||
connect_bind(S) ->
|
||||
Host = next_host(S#eldap.host, S#eldap.hosts),
|
||||
?INFO_MSG("LDAP connection on ~s:~p", [Host, S#eldap.port]),
|
||||
Opts = if S#eldap.tls == tls ->
|
||||
[{packet, asn1}, {active, true}, {keepalive, true},
|
||||
binary | S#eldap.tls_options];
|
||||
true ->
|
||||
[{packet, asn1}, {active, true}, {keepalive, true},
|
||||
{send_timeout, ?SEND_TIMEOUT}, binary]
|
||||
end,
|
||||
SocketData = case S#eldap.tls of
|
||||
tls ->
|
||||
SockMod = ssl,
|
||||
SslOpts = [{packet, asn1}, {active, true}, {keepalive, true},
|
||||
binary | S#eldap.tls_options],
|
||||
ssl:connect(Host, S#eldap.port, SslOpts);
|
||||
ssl:connect(Host, S#eldap.port, Opts);
|
||||
%% starttls -> %% TODO: Implement STARTTLS;
|
||||
_ ->
|
||||
SockMod = gen_tcp,
|
||||
TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true},
|
||||
{send_timeout, ?SEND_TIMEOUT}, binary],
|
||||
gen_tcp:connect(Host, S#eldap.port, TcpOpts)
|
||||
gen_tcp:connect(Host, S#eldap.port, Opts)
|
||||
end,
|
||||
case SocketData of
|
||||
{ok, Socket} ->
|
||||
@@ -986,8 +1012,11 @@ connect_bind(S) ->
|
||||
{ok, connecting, NewS#eldap{host = Host}}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("LDAP connection failed on ~s:~p~nReason: ~p",
|
||||
[Host, S#eldap.port, Reason]),
|
||||
?ERROR_MSG("LDAP connection failed:~n"
|
||||
"** Server: ~s:~p~n"
|
||||
"** Reason: ~p~n"
|
||||
"** Socket options: ~p",
|
||||
[Host, S#eldap.port, Reason, Opts]),
|
||||
NewS = close_and_retry(S),
|
||||
{ok, connecting, NewS#eldap{host = Host}}
|
||||
end.
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
limit = 0,
|
||||
attributes = [],
|
||||
types_only = false,
|
||||
deref_aliases = neverDerefAliases,
|
||||
timeout = 0}).
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ parse(L) when is_list(L) ->
|
||||
%%%-------------------------------------------------------------------
|
||||
parse(L, SList) when is_list(L), is_list(SList) ->
|
||||
case catch eldap_filter_yecc:parse(scan(L, SList)) of
|
||||
{'EXIT', _} = Err ->
|
||||
{error, Err};
|
||||
{error, {_, _, Msg}} ->
|
||||
{error, Msg};
|
||||
{ok, Result} ->
|
||||
|
||||
@@ -35,8 +35,11 @@
|
||||
make_filter/2,
|
||||
get_state/2,
|
||||
case_insensitive_match/2,
|
||||
check_filter/1,
|
||||
uids_domain_subst/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%% Generate an 'or' LDAP query on one or several attributes
|
||||
%% If there is only one attribute
|
||||
generate_subfilter([UID]) ->
|
||||
@@ -91,9 +94,13 @@ get_user_part(String, Pattern) ->
|
||||
{'EXIT', _} ->
|
||||
{error, badmatch};
|
||||
Result ->
|
||||
case re:replace(Pattern, "%u", Result, [{return, list}]) of
|
||||
String -> {ok, Result};
|
||||
_ -> {error, badmatch}
|
||||
case string:to_lower(
|
||||
re:replace(Pattern, "%u", Result, [{return, list}])) ==
|
||||
string:to_lower(String) of
|
||||
true ->
|
||||
{ok, Result};
|
||||
false ->
|
||||
{error, badmatch}
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -144,3 +151,16 @@ uids_domain_subst(Host, UIDs) ->
|
||||
(A) -> A
|
||||
end,
|
||||
UIDs).
|
||||
|
||||
check_filter(undefined) ->
|
||||
ok;
|
||||
check_filter(Filter) ->
|
||||
case eldap_filter:parse(Filter) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to parse LDAP filter:~n"
|
||||
"** Filter: ~p~n"
|
||||
"** Reason: ~p",
|
||||
[Filter, Err])
|
||||
end.
|
||||
|
||||
+31
-16
@@ -40,7 +40,7 @@
|
||||
loaded_modules_with_opts/1,
|
||||
get_hosts/2,
|
||||
get_module_proc/2,
|
||||
expand_host_name/3,
|
||||
get_module_proc_existing/2,
|
||||
is_loaded/2]).
|
||||
|
||||
-export([behaviour_info/1]).
|
||||
@@ -85,8 +85,8 @@ start_module2(Host, Module, Opts) ->
|
||||
catch Class:Reason ->
|
||||
del_module_mnesia(Host, Module),
|
||||
ets:delete(ejabberd_modules, {Module, Host}),
|
||||
ErrorText = io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p",
|
||||
[Module, Host, Opts, Class, Reason]),
|
||||
ErrorText = io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p~n stacktarce: ~p",
|
||||
[Module, Host, Opts, Class, Reason, erlang:get_stacktrace()]),
|
||||
?CRITICAL_MSG(ErrorText, []),
|
||||
case is_app_running(ejabberd) of
|
||||
true ->
|
||||
@@ -194,7 +194,13 @@ get_module_opt(Host, Module, Opt, Default) ->
|
||||
OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
|
||||
case OptsList of
|
||||
[] ->
|
||||
Default;
|
||||
OptsList2 = ets:lookup(ejabberd_modules, {Module, global}),
|
||||
case OptsList2 of
|
||||
[] ->
|
||||
Default;
|
||||
[#ejabberd_module{opts = Opts} | _] ->
|
||||
get_opt(Opt, Opts, Default)
|
||||
end;
|
||||
[#ejabberd_module{opts = Opts} | _] ->
|
||||
get_opt(Opt, Opts, Default)
|
||||
end.
|
||||
@@ -204,8 +210,14 @@ get_module_opt_host(Host, Module, Default) ->
|
||||
re:replace(Val, "@HOST@", Host, [global,{return,list}]).
|
||||
|
||||
get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
re:replace(Val, "@HOST@", Host, [global,{return,list}]).
|
||||
case Host of
|
||||
global ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
{global, re:replace(Val, ".@HOST@", "", [global,{return,list}])};
|
||||
Host ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
re:replace(Val, "@HOST@", Host, [global,{return,list}])
|
||||
end.
|
||||
|
||||
loaded_modules(Host) ->
|
||||
ets:select(ejabberd_modules,
|
||||
@@ -260,6 +272,15 @@ get_hosts(Opts, Prefix) ->
|
||||
Hosts
|
||||
end.
|
||||
|
||||
get_module_proc_existing(Host, Base) ->
|
||||
Proc = get_module_proc(Host, Base),
|
||||
%% If the process doesn't exist for Host, it may exist for global
|
||||
case {whereis(Proc), Host == global} of
|
||||
{undefined, false} -> get_module_proc(global, Base);
|
||||
{undefined, true} -> not_existing;
|
||||
{_, _} -> Proc
|
||||
end.
|
||||
|
||||
get_module_proc(Host, Base) when is_binary(Host) ->
|
||||
get_module_proc(binary_to_list(Host), Base);
|
||||
get_module_proc(global, Base) ->
|
||||
@@ -269,14 +290,8 @@ get_module_proc(Host, {frontend, Base}) ->
|
||||
get_module_proc(Host, Base) ->
|
||||
list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
|
||||
|
||||
%% @spec(Host::string() | global, Module::atom()) -> true | false
|
||||
%% @doc Check if the module is loaded in this host (or global), or not.
|
||||
is_loaded(Host, Module) ->
|
||||
ets:member(ejabberd_modules, {Module, Host}).
|
||||
|
||||
expand_host_name(Host, Opts, DefaultPrefix) ->
|
||||
case Host of
|
||||
global ->
|
||||
{global, gen_mod:get_opt(prefix, Opts, DefaultPrefix)};
|
||||
_ ->
|
||||
gen_mod:get_opt_host(Host, Opts, DefaultPrefix ++ ".@HOST@")
|
||||
end.
|
||||
|
||||
ets:member(ejabberd_modules, {Module, Host})
|
||||
orelse ets:member(ejabberd_modules, {Module, global}).
|
||||
|
||||
+8
-3
@@ -597,10 +597,15 @@ async_dirty(Host, Tab, Fun) ->
|
||||
get_table(Host, Tab) when is_list(Host) ->
|
||||
get_table(list_to_binary(Host), Tab);
|
||||
get_table(Host, Tab) ->
|
||||
case mnesia:dirty_read(table, {Host, Tab}) of
|
||||
[T] ->
|
||||
case {mnesia:dirty_read(table, {Host, Tab}), Host} of
|
||||
{[T], _} ->
|
||||
T;
|
||||
_ ->
|
||||
{_, Host} when is_binary(Host) ->
|
||||
Prefix = lists:nth(1, string:tokens(binary_to_list(Host), ".")),
|
||||
get_table({global, Prefix}, Tab);
|
||||
{_, {global, _Prefix}} ->
|
||||
get_table(global, Tab);
|
||||
{_, global} ->
|
||||
catch throw(error123),
|
||||
Stacktrace = erlang:get_stacktrace(),
|
||||
error_logger:error_msg("gen_storage: Table ~p not found on ~p~nStacktrace: ~p", [Tab, Host, Stacktrace]),
|
||||
|
||||
@@ -32,8 +32,9 @@ migrate_mnesia(Host, Table, Migrations) ->
|
||||
end
|
||||
end, DifferentTableName).
|
||||
|
||||
migrate_mnesia1(Host, Table, {OldTable, OldAttributes, MigrateFun}) ->
|
||||
HostB = list_to_binary(Host),
|
||||
migrate_mnesia1(Host, Table, {OldTable, OldAttributes, MigrateFun}) when is_list(Host) ->
|
||||
migrate_mnesia1(list_to_binary(Host), Table, {OldTable, OldAttributes, MigrateFun});
|
||||
migrate_mnesia1(HostB, Table, {OldTable, OldAttributes, MigrateFun}) ->
|
||||
case (catch mnesia:table_info(OldTable, attributes)) of
|
||||
OldAttributes ->
|
||||
if
|
||||
@@ -110,9 +111,12 @@ migrate_mnesia1(Host, Table, {OldTable, OldAttributes, MigrateFun}) ->
|
||||
end.
|
||||
|
||||
|
||||
migrate_odbc(HostStr, Tables, Migrations) when is_list(HostStr) ->
|
||||
migrate_odbc(list_to_binary(HostStr), Tables, Migrations);
|
||||
migrate_odbc(Host, Tables, Migrations) ->
|
||||
HostStr = binary_to_list(Host),
|
||||
try ejabberd_odbc:sql_transaction(
|
||||
Host,
|
||||
HostStr,
|
||||
fun() ->
|
||||
lists:foreach(
|
||||
fun(Migration) ->
|
||||
@@ -145,10 +149,9 @@ migrate_odbc1(Host, Tables, {OldTablesColumns, MigrateFun}) ->
|
||||
ignored
|
||||
end.
|
||||
|
||||
migrate_odbc2(Host, Tables, OldTable, OldTables, OldColumns, OldColumnsAll, OldTablesA, ColumnsT, MigrateFun)
|
||||
migrate_odbc2(HostB, Tables, OldTable, OldTables, OldColumns, OldColumnsAll, OldTablesA, ColumnsT, MigrateFun)
|
||||
when ColumnsT == OldColumnsAll ->
|
||||
?INFO_MSG("Migrating ODBC table ~p to gen_storage tables ~p", [OldTable, Tables]),
|
||||
HostB = list_to_binary(Host),
|
||||
|
||||
%% rename old tables to *_old
|
||||
lists:foreach(fun(OldTable1) ->
|
||||
|
||||
@@ -162,12 +162,7 @@ create_table(#tabdef{name = Tab,
|
||||
end, {"", []}, Attributes),
|
||||
TabS = atom_to_list(Tab),
|
||||
PKey = case TableType of
|
||||
%% This 105 limits the size of fields in the primary key.
|
||||
%% That prevents MySQL from complaining when setting the
|
||||
%% last_activity key (text, text) with this error:
|
||||
%% #42000Specified key was too long; max key length is 1000bytes"
|
||||
%% Similarly for rosteritem and other tables, maybe also PgSQL.
|
||||
set -> [", PRIMARY KEY (", string:join(K, "(105), "), "(105))"];
|
||||
set -> primary_key_string(Host, K);
|
||||
bag -> []
|
||||
end,
|
||||
case odbc_command(Host,
|
||||
@@ -192,6 +187,22 @@ create_table(#tabdef{name = Tab,
|
||||
{aborted, Reason}
|
||||
end.
|
||||
|
||||
%% This 105 limits the size of fields in the primary key.
|
||||
%% That prevents MySQL from complaining when setting the
|
||||
%% last_activity key (text, text) with this error:
|
||||
%% #42000Specified key was too long; max key length is 1000bytes"
|
||||
%% Similarly for rosteritem and other tables.
|
||||
primary_key_string(Host, K) ->
|
||||
Dbtype = ejabberd_odbc:db_type(Host),
|
||||
case Dbtype of
|
||||
mysql ->
|
||||
[", PRIMARY KEY (", string:join(K, "(105), "), "(105))"];
|
||||
odbc ->
|
||||
[", PRIMARY KEY (", string:join(K, ", "), ")"];
|
||||
pgsql ->
|
||||
[", PRIMARY KEY (", string:join(K, ", "), ")"]
|
||||
end.
|
||||
|
||||
type_to_sql_type(Type, false = _NoTextKeys) ->
|
||||
type_to_sql_type(Type);
|
||||
type_to_sql_type(Type, true = _NoTextKeys) ->
|
||||
|
||||
+3
-2
@@ -47,8 +47,9 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_ADHOC,
|
||||
|
||||
@@ -57,8 +57,9 @@
|
||||
-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
|
||||
tokenize(Node) -> string:tokens(Node, "/#").
|
||||
|
||||
start(Host, _Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, _Opts) ->
|
||||
mnesia:create_table(motd, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, motd)}]),
|
||||
mnesia:create_table(motd_users, [{disc_copies, [node()]},
|
||||
@@ -73,7 +74,7 @@ start(Host, _Opts) ->
|
||||
ejabberd_hooks:add(adhoc_local_commands, HostB, ?MODULE, announce_commands, 50),
|
||||
ejabberd_hooks:add(user_available_hook, HostB,
|
||||
?MODULE, send_motd, 50),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
register(gen_mod:get_module_proc(HostB, ?PROCNAME),
|
||||
proc_lib:spawn(?MODULE, init, [])).
|
||||
|
||||
init() ->
|
||||
@@ -135,7 +136,7 @@ announce(From, To, Packet) ->
|
||||
case {exmpp_jid:prep_node(To), exmpp_jid:prep_resource(To)} of
|
||||
{undefined, Res} ->
|
||||
Name = Packet#xmlel.name,
|
||||
Proc = gen_mod:get_module_proc(exmpp_jid:prep_domain_as_list(To), ?PROCNAME),
|
||||
Proc = gen_mod:get_module_proc_existing(exmpp_jid:prep_domain_as_list(To), ?PROCNAME),
|
||||
case {Res, Name} of
|
||||
{<<"announce/all">>, 'message'} ->
|
||||
Proc ! {announce_all, From, To, Packet},
|
||||
@@ -541,7 +542,7 @@ handle_adhoc_form(From, To,
|
||||
node = Node,
|
||||
sessionid = SessionID},
|
||||
Fields) ->
|
||||
LServer = exmpp_jid:prep_domain_as_list(To),
|
||||
LServerB = exmpp_jid:prep_domain(To),
|
||||
Confirm = case lists:keysearch("confirm", 1, Fields) of
|
||||
{value, {"confirm", ["true"]}} ->
|
||||
true;
|
||||
@@ -582,7 +583,7 @@ handle_adhoc_form(From, To,
|
||||
[]
|
||||
end},
|
||||
|
||||
Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
|
||||
Proc = gen_mod:get_module_proc_existing(LServerB, ?PROCNAME),
|
||||
case {Node, Body} of
|
||||
{?NS_ADMIN_s ++ "#delete-motd", _} ->
|
||||
if Confirm ->
|
||||
|
||||
+22
-14
@@ -38,8 +38,9 @@
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
ejabberd_hooks:add(privacy_iq_get, HostB,
|
||||
?MODULE, process_iq_get, 40),
|
||||
@@ -66,9 +67,9 @@ process_iq_get(_, From, _To, #iq{ns = ?NS_BLOCKING, payload = SubEl}, _) ->
|
||||
true ->
|
||||
LUser = exmpp_jid:prep_node(From),
|
||||
LServer = exmpp_jid:prep_domain(From),
|
||||
process_blocklist_get(LUser, LServer);
|
||||
{stop, process_blocklist_get(LUser, LServer)};
|
||||
false ->
|
||||
{error, 'bad-request'}
|
||||
{stop, {error, 'bad-request'}}
|
||||
end;
|
||||
|
||||
process_iq_get(Acc, _, _, _, _) ->
|
||||
@@ -79,7 +80,7 @@ process_iq_set(_, From, _To, #iq{ns = ?NS_BLOCKING,
|
||||
children = SubEls}}) ->
|
||||
LUser = exmpp_jid:prep_node(From),
|
||||
LServer = exmpp_jid:prep_domain(From),
|
||||
case {SubElName, exmpp_xml:remove_cdata_from_list(SubEls)} of
|
||||
Res = case {SubElName, exmpp_xml:remove_cdata_from_list(SubEls)} of
|
||||
{block, []} ->
|
||||
{error, 'bad-request'};
|
||||
{block, Els} ->
|
||||
@@ -92,7 +93,8 @@ process_iq_set(_, From, _To, #iq{ns = ?NS_BLOCKING,
|
||||
process_blocklist_unblock(LUser, LServer, JIDs);
|
||||
_ ->
|
||||
{error, 'bad-request'}
|
||||
end;
|
||||
end,
|
||||
{stop, Res};
|
||||
|
||||
process_iq_set(Acc, _, _, _) ->
|
||||
Acc.
|
||||
@@ -198,9 +200,10 @@ process_blocklist_block(LUser, LServer, JIDs) ->
|
||||
%% [{'=', user_host, {LUser, LServer}},
|
||||
%% {'=', name, Default}]),
|
||||
List = list_data_to_items(Data),
|
||||
broadcast_list_update(LUser, LServer, Default, List),
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {block, JIDs}),
|
||||
{result, []};
|
||||
{result, [], UserList};
|
||||
Error ->
|
||||
?DEBUG("Error ~n~p", [Error]),
|
||||
{error, 'internal-server-error'}
|
||||
@@ -259,9 +262,10 @@ process_blocklist_unblock_all(LUser, LServer) ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, Data}} ->
|
||||
List = list_data_to_items(Data),
|
||||
broadcast_list_update(LUser, LServer, Default, List),
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, unblock_all),
|
||||
{result, []};
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, 'internal-server-error'}
|
||||
end.
|
||||
@@ -304,17 +308,21 @@ process_blocklist_unblock(LUser, LServer, JIDs) ->
|
||||
{result, []};
|
||||
{atomic, {ok, Default, Data}} ->
|
||||
List = list_data_to_items(Data),
|
||||
broadcast_list_update(LUser, LServer, Default, List),
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, Default, UserList),
|
||||
broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}),
|
||||
{result, []};
|
||||
{result, [], UserList};
|
||||
_ ->
|
||||
{error, 'internal-server-error'}
|
||||
end.
|
||||
|
||||
broadcast_list_update(LUser, LServer, Name, List) ->
|
||||
make_userlist(Name, List) ->
|
||||
NeedDb = is_list_needdb(List),
|
||||
#userlist{name = Name, list = List, needdb = NeedDb}.
|
||||
|
||||
broadcast_list_update(LUser, LServer, Name, UserList) ->
|
||||
JID = exmpp_jid:make(LUser, LServer),
|
||||
ListString = lists:flatten(io_lib:format("~p.", [#userlist{name = Name, list = List, needdb = NeedDb}])),
|
||||
ListString = lists:flatten(io_lib:format("~p.", [UserList])),
|
||||
ejabberd_router:route(
|
||||
JID,
|
||||
JID,
|
||||
|
||||
+9
-8
@@ -92,11 +92,13 @@ start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(HostB, ?PROCNAME),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{?MODULE, start_link, [Host, Opts]},
|
||||
{?MODULE, start_link, [HostB, Opts]},
|
||||
transient,
|
||||
1000,
|
||||
worker,
|
||||
@@ -225,7 +227,7 @@ c2s_presence_in(C2SState, {From, To, #xmlel{attrs = Attrs, children = Els}}) ->
|
||||
Subscription = ejabberd_c2s:get_subscription(From, C2SState),
|
||||
Insert = ((Type == <<>>) or (Type == <<"available">>))
|
||||
and ((Subscription == both) or (Subscription == to)),
|
||||
Delete = (Type == <<"unavailable">>) or (Type == <<"error">>) or (Type == <<"invisible">>),
|
||||
Delete = (Type == <<"unavailable">>) or (Type == <<"error">>),
|
||||
if Insert or Delete ->
|
||||
LFrom = exmpp_jid:to_lower(From),
|
||||
Rs = case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
|
||||
@@ -284,7 +286,7 @@ c2s_broadcast_recipients(Acc, _, _, _, _) ->
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, Opts]) ->
|
||||
init([HostB, Opts]) ->
|
||||
case catch mnesia:table_info(caps_features, storage_type) of
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
@@ -301,7 +303,6 @@ init([Host, Opts]) ->
|
||||
MaxSize = gen_mod:get_opt(cache_size, Opts, 1000),
|
||||
LifeTime = gen_mod:get_opt(cache_life_time, Opts, timer:hours(24) div 1000),
|
||||
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
|
||||
HostB = list_to_binary(Host),
|
||||
ejabberd_hooks:add(c2s_presence_in, HostB,
|
||||
?MODULE, c2s_presence_in, 75),
|
||||
ejabberd_hooks:add(c2s_broadcast_recipients, HostB,
|
||||
@@ -320,7 +321,7 @@ init([Host, Opts]) ->
|
||||
?MODULE, disco_identity, 75),
|
||||
ejabberd_hooks:add(disco_info, HostB,
|
||||
?MODULE, disco_info, 75),
|
||||
{ok, #state{host = Host}}.
|
||||
{ok, #state{host = HostB}}.
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
@@ -334,7 +335,7 @@ handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
HostB = list_to_binary(State#state.host),
|
||||
HostB = State#state.host,
|
||||
ejabberd_hooks:delete(c2s_presence_in, HostB,
|
||||
?MODULE, c2s_presence_in, 75),
|
||||
ejabberd_hooks:delete(c2s_broadcast_recipients, HostB,
|
||||
|
||||
@@ -56,8 +56,9 @@
|
||||
%% Copied from ejabberd_sm.erl
|
||||
-record(session, {sid, usr, us, priority, info}).
|
||||
|
||||
start(Host, _Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, _Opts) ->
|
||||
ejabberd_hooks:add(disco_local_items, HostB, ?MODULE, get_local_items, 50),
|
||||
ejabberd_hooks:add(disco_local_features, HostB, ?MODULE, get_local_features, 50),
|
||||
ejabberd_hooks:add(disco_local_identity, HostB, ?MODULE, get_local_identity, 50),
|
||||
|
||||
+11
-10
@@ -51,10 +51,12 @@
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
|
||||
ejabberd_local:refresh_iq_handlers(),
|
||||
|
||||
@@ -71,11 +73,10 @@ start(Host, Opts) ->
|
||||
catch ets:new(disco_features, [named_table, ordered_set, public]),
|
||||
register_feature(HostB, "iq"),
|
||||
register_feature(HostB, "presence"),
|
||||
register_feature(HostB, "presence-invisible"),
|
||||
|
||||
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
|
||||
ExtraDomains = gen_mod:get_opt(extra_domains, Opts, []),
|
||||
lists:foreach(fun(Domain) -> register_extra_domain(list_to_binary(Host), Domain) end,
|
||||
lists:foreach(fun(Domain) -> register_extra_domain(HostB, Domain) end,
|
||||
ExtraDomains),
|
||||
catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
|
||||
catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
|
||||
@@ -106,7 +107,7 @@ stop(Host) ->
|
||||
ok.
|
||||
|
||||
|
||||
register_feature(HostB, Feature) when is_binary(HostB) ->
|
||||
register_feature(HostB, Feature) ->
|
||||
catch ets:new(disco_features, [named_table, ordered_set, public]),
|
||||
ets:insert(disco_features, {{Feature, HostB}}).
|
||||
|
||||
@@ -114,11 +115,11 @@ unregister_feature(HostB, Feature) ->
|
||||
catch ets:new(disco_features, [named_table, ordered_set, public]),
|
||||
ets:delete(disco_features, {Feature, HostB}).
|
||||
|
||||
register_extra_domain(HostB, Domain) when is_binary(HostB) ->
|
||||
register_extra_domain(HostB, Domain) ->
|
||||
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
|
||||
ets:insert(disco_extra_domains, {{Domain, HostB}}).
|
||||
|
||||
unregister_extra_domain(HostB, Domain) when is_binary(HostB) ->
|
||||
unregister_extra_domain(HostB, Domain) ->
|
||||
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
|
||||
ets:delete(disco_extra_domains, {Domain, HostB}).
|
||||
|
||||
@@ -242,14 +243,14 @@ get_local_services(Acc, _From, To, <<>>, _Lang) ->
|
||||
empty -> []
|
||||
end,
|
||||
Host = exmpp_jid:prep_domain_as_list(To),
|
||||
NHost = ejabberd:normalize_host(Host),
|
||||
NHostB = list_to_binary(ejabberd:normalize_host(Host)),
|
||||
{result,
|
||||
lists:usort(
|
||||
lists:map(fun domain_to_xml/1,
|
||||
get_vh_services(Host) ++
|
||||
ets:select(disco_extra_domains,
|
||||
ets:fun2ms(fun ({{Service, H}})
|
||||
when H =:= NHost;
|
||||
when H =:= NHostB;
|
||||
H =:= global ->
|
||||
Service
|
||||
end)))
|
||||
@@ -315,7 +316,7 @@ is_presence_subscribed(From, To) ->
|
||||
Server = exmpp_jid:prep_domain(From),
|
||||
LUser = exmpp_jid:prep_node(To),
|
||||
LServer = exmpp_jid:prep_domain(To),
|
||||
lists:any(fun({roster, _, _, {TUser, TServer, _}, _, S, _, _, _, _}) ->
|
||||
lists:any(fun(#roster{jid = {TUser, TServer, _}, subscription = S}) ->
|
||||
if
|
||||
LUser == TUser, LServer == TServer, S/=none ->
|
||||
true;
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ stop(Host) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
MyHost = gen_mod:expand_host_name(Host, Opts, "echo"),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "echo.@HOST@"),
|
||||
ClientVersion = gen_mod:get_opt(client_version, Opts, false),
|
||||
ejabberd_router:register_route(MyHost),
|
||||
{ok, #state{host = MyHost, client_version = ClientVersion}}.
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
%% API:
|
||||
-export([start/2,
|
||||
preinit/2,
|
||||
init/1,
|
||||
stop/1]).
|
||||
-export([update_bl_c2s/0]).
|
||||
@@ -49,14 +50,20 @@
|
||||
-record(bl_c2s, {ip}).
|
||||
|
||||
%% Start once for all vhost
|
||||
start(Host, Opts) ->
|
||||
case whereis(?PROCNAME) of
|
||||
undefined ->
|
||||
?DEBUG("Starting mod_ip_blacklist ~p ~p~n", [Host, Opts]),
|
||||
register(?PROCNAME,
|
||||
spawn(?MODULE, init, [#state{}]));
|
||||
_ ->
|
||||
ok
|
||||
start(_Host, _Opts) ->
|
||||
Pid = spawn(?MODULE, preinit, [self(), #state{}]),
|
||||
receive {ok, Pid, PreinitResult} ->
|
||||
PreinitResult
|
||||
end.
|
||||
|
||||
preinit(Parent, State) ->
|
||||
Pid = self(),
|
||||
try register(?PROCNAME, Pid) of
|
||||
true ->
|
||||
Parent ! {ok, Pid, true},
|
||||
init(State)
|
||||
catch error:_ ->
|
||||
Parent ! {ok, Pid, true}
|
||||
end.
|
||||
|
||||
%% TODO:
|
||||
@@ -85,7 +92,7 @@ loop(_State) ->
|
||||
%% TODO: Support comment lines starting by %
|
||||
update_bl_c2s() ->
|
||||
?INFO_MSG("Updating C2S Blacklist", []),
|
||||
case http:request(?BLC2S) of
|
||||
case httpc:request(?BLC2S) of
|
||||
{ok, {{_Version, 200, _Reason}, _Headers, Body}} ->
|
||||
IPs = string:tokens(Body,"\n"),
|
||||
ets:delete_all_objects(bl_c2s),
|
||||
|
||||
+16
-11
@@ -77,17 +77,18 @@
|
||||
-record(last_activity, {user_host, timestamp, status}).
|
||||
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, HostB, last_activity,
|
||||
[{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{attributes, record_info(fields, last_activity)},
|
||||
{types, [{user_host, {text, text}},
|
||||
{timestamp, bigint}]}]),
|
||||
update_table(Host, Backend),
|
||||
update_table(HostB, Backend),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY,
|
||||
?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY,
|
||||
@@ -97,8 +98,9 @@ start(Host, Opts) ->
|
||||
ejabberd_hooks:add(unset_presence_hook, HostB,
|
||||
?MODULE, on_presence_update, 50).
|
||||
|
||||
stop(Host) ->
|
||||
HostB = list_to_binary(Host),
|
||||
stop(Host) when is_list(Host) ->
|
||||
stop(list_to_binary(Host));
|
||||
stop(HostB) ->
|
||||
ejabberd_hooks:delete(remove_user, HostB,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(unset_presence_hook, HostB,
|
||||
@@ -252,9 +254,12 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
update_table(global, Storage) ->
|
||||
[update_table(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_table(HostB, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, last_activity,
|
||||
HostB, last_activity,
|
||||
[{last_activity, [us, timestamp, status],
|
||||
fun({last_activity, {U, S}, Timestamp, Status}) ->
|
||||
U1 = case U of
|
||||
@@ -266,14 +271,14 @@ update_table(Host, mnesia) ->
|
||||
timestamp = Timestamp,
|
||||
status = list_to_binary(Status)}
|
||||
end}]);
|
||||
update_table(Host, odbc) ->
|
||||
update_table(HostB, odbc) ->
|
||||
gen_storage_migration:migrate_odbc(
|
||||
Host, [last_activity],
|
||||
HostB, [last_activity],
|
||||
[{"last", ["username", "seconds", "state"],
|
||||
fun(_, Username, STimeStamp, Status) ->
|
||||
case catch list_to_integer(STimeStamp) of
|
||||
TimeStamp when is_integer(TimeStamp) ->
|
||||
[#last_activity{user_host = {Username, Host},
|
||||
[#last_activity{user_host = {Username, HostB},
|
||||
timestamp = TimeStamp,
|
||||
status = Status}];
|
||||
_ ->
|
||||
|
||||
+57
-24
@@ -91,6 +91,7 @@
|
||||
register_room/3,
|
||||
migrate/1,
|
||||
get_vh_rooms/1,
|
||||
remove_host/1,
|
||||
can_use_nick/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
@@ -128,12 +129,14 @@ start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
start(Host, Opts) ->
|
||||
start_supervisor(Host),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
start_supervisor(HostB),
|
||||
Proc = gen_mod:get_module_proc(HostB, ?PROCNAME),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{?MODULE, start_link, [Host, Opts]},
|
||||
{?MODULE, start_link, [HostB, Opts]},
|
||||
temporary,
|
||||
1000,
|
||||
worker,
|
||||
@@ -154,7 +157,7 @@ stop(Host) ->
|
||||
%% So the message sending must be catched
|
||||
room_destroyed(Host, Room, Pid, ServerHost) when is_binary(Host),
|
||||
is_binary(Room) ->
|
||||
catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
|
||||
catch gen_mod:get_module_proc_existing(ServerHost, ?PROCNAME) !
|
||||
{room_destroyed, {Room, Host}, Pid},
|
||||
ok.
|
||||
|
||||
@@ -162,7 +165,7 @@ room_destroyed(Host, Room, Pid, ServerHost) when is_binary(Host),
|
||||
%% If Opts = default, the default room options are used.
|
||||
%% Else use the passed options as defined in mod_muc_room.
|
||||
create_room(Host, Name, From, Nick, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Proc = gen_mod:get_module_proc_existing(Host, ?PROCNAME),
|
||||
RoomHost = gen_mod:get_module_opt_host(Host, ?MODULE, "conference.@HOST@"),
|
||||
Node = ejabberd_cluster:get_node({Name, RoomHost}),
|
||||
gen_server:call({Proc, Node}, {create, Name, From, Nick, Opts}).
|
||||
@@ -255,7 +258,7 @@ forget_room(Host, Name) when is_binary(Host), is_binary(Name) ->
|
||||
end,
|
||||
gen_storage:transaction(Host, muc_room_opt, F).
|
||||
|
||||
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) when is_binary(Host) ->
|
||||
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
|
||||
Rsm = jlib:rsm_decode(IQ),
|
||||
Res = exmpp_iq:result(IQ, #xmlel{ns = ?NS_DISCO_ITEMS,
|
||||
name = 'query',
|
||||
@@ -296,6 +299,16 @@ migrate(After) ->
|
||||
end
|
||||
end, Rs).
|
||||
|
||||
remove_host(MyHostB) when is_binary(MyHostB) ->
|
||||
Host = gen_mod:get_module_opt_host(binary_to_list(MyHostB), ?MODULE, "conference.@HOST@"),
|
||||
?INFO_MSG("Removing rooms of MUC service ~p", [Host]),
|
||||
lists:foreach(
|
||||
fun(#muc_online_room{name_host = {NameB, HostB}, pid = Pid}) ->
|
||||
gen_fsm:send_all_state_event(Pid, destroy),
|
||||
forget_room(HostB, NameB)
|
||||
end,
|
||||
get_vh_rooms(list_to_binary(Host))).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
@@ -308,8 +321,8 @@ migrate(After) ->
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
MyHost_L = gen_mod:expand_host_name(Host, Opts, "conference"),
|
||||
MyHost = list_to_binary(MyHost_L),
|
||||
MyHostStr = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
|
||||
MyHost = l2b(MyHostStr),
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, MyHost, muc_room_opt,
|
||||
[{disc_copies, [node()]},
|
||||
@@ -339,11 +352,11 @@ init([Host, Opts]) ->
|
||||
{pid, pid}]}]),
|
||||
%% If ejabberd stops abruptly, ODBC table keeps obsolete data. Let's clean:
|
||||
gen_storage:dirty_delete_where(MyHost, muc_online_room,
|
||||
[{'=', name_host, {'_', MyHost}}]),
|
||||
[{'=', name_host, {'_', MyHost}}]),
|
||||
gen_storage:add_table_copy(MyHost, muc_online_room, node(), ram_copies),
|
||||
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
||||
gen_storage:add_table_index(MyHost, muc_registered, nick),
|
||||
update_tables(MyHost_L, Backend),
|
||||
update_tables(MyHost, Backend),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
|
||||
@@ -351,8 +364,9 @@ init([Host, Opts]) ->
|
||||
HistorySize = gen_mod:get_opt(history_size, Opts, 20),
|
||||
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
|
||||
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
|
||||
ejabberd_router:register_route(MyHost_L),
|
||||
ejabberd_router:register_route(MyHostStr),
|
||||
ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100),
|
||||
ejabberd_hooks:add(remove_host, Host, ?MODULE, remove_host, 50),
|
||||
load_permanent_rooms(MyHost, Host,
|
||||
{Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||
HistorySize,
|
||||
@@ -413,12 +427,12 @@ handle_cast(_Msg, State) ->
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, From, To, Packet},
|
||||
#state{host = Host,
|
||||
server_host = ServerHost,
|
||||
#state{server_host = ServerHost,
|
||||
access = Access,
|
||||
default_room_opts = DefRoomOpts,
|
||||
history_size = HistorySize,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
Host = exmpp_jid:domain(To),
|
||||
US = {exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To)},
|
||||
case ejabberd_cluster:get_node(US) of
|
||||
Node when Node == node() ->
|
||||
@@ -430,7 +444,7 @@ handle_info({route, From, To, Packet},
|
||||
ok
|
||||
end;
|
||||
Node ->
|
||||
Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME),
|
||||
Proc = gen_mod:get_module_proc_existing(ServerHost, ?PROCNAME),
|
||||
{Proc, Node} ! {route, From, To, Packet}
|
||||
end,
|
||||
{noreply, State};
|
||||
@@ -454,6 +468,7 @@ handle_info(_Info, State) ->
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100),
|
||||
ejabberd_hooks:delete(remove_host, State#state.server_host, ?MODULE, remove_host, 50),
|
||||
ejabberd_router:unregister_route(binary_to_list(State#state.host)),
|
||||
ok.
|
||||
|
||||
@@ -500,6 +515,9 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
ejabberd_router:route_error(To, From, Err, Packet)
|
||||
end.
|
||||
|
||||
l2b(global) -> global;
|
||||
l2b(String) when is_list(String) -> list_to_binary(String);
|
||||
l2b(Other) -> Other.
|
||||
|
||||
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts) ->
|
||||
@@ -516,7 +534,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
case exmpp_iq:xmlel_to_iq(Packet) of
|
||||
#iq{type = get, ns = ?NS_DISCO_INFO = XMLNS,
|
||||
payload = _SubEl, lang = Lang} = IQ ->
|
||||
ServerHostB = list_to_binary(ServerHost),
|
||||
ServerHostB = l2b(ServerHost),
|
||||
Info = ejabberd_hooks:run_fold(
|
||||
disco_info, ServerHostB, [],
|
||||
[ServerHost, ?MODULE, <<>>, ""]),
|
||||
@@ -661,10 +679,20 @@ check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) ->
|
||||
end.
|
||||
|
||||
|
||||
load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
||||
load_permanent_rooms({global, Prefix}, global, Access, HistorySize, RoomShaper) ->
|
||||
timer:sleep(2000), %% Wait for ejabberd_hosts to start
|
||||
lists:foreach(
|
||||
fun(ServerHost) ->
|
||||
Host = list_to_binary(Prefix++"."++ServerHost),
|
||||
load_permanent_rooms(Host, global, Access, HistorySize, RoomShaper)
|
||||
end,
|
||||
?MYHOSTS);
|
||||
|
||||
load_permanent_rooms(Host, ServerHost1, Access, HistorySize, RoomShaper) ->
|
||||
F = fun() ->
|
||||
Rs = gen_storage:select(Host, muc_room_opt,
|
||||
[{'=', name_host, {'_', Host}}]),
|
||||
[{'=', opt, persistent},
|
||||
{'=', name_host, {'_', Host}}]),
|
||||
Names = lists:foldl(
|
||||
fun(#muc_room_opt{name_host = {Room, _}}, Names) ->
|
||||
sets:add_element(Room, Names)
|
||||
@@ -678,7 +706,7 @@ load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
||||
Opts = restore_room_internal(Host, Room),
|
||||
{ok, Pid} = mod_muc_room:start(
|
||||
Host,
|
||||
ServerHost,
|
||||
ServerHost1,
|
||||
Access,
|
||||
Room,
|
||||
HistorySize,
|
||||
@@ -1029,10 +1057,12 @@ get_vh_rooms(Host) when is_binary(Host) ->
|
||||
%%%%% Database migration from ejabberd 2.1.x
|
||||
%%%%%
|
||||
|
||||
update_tables(Host, mnesia) ->
|
||||
HostB = list_to_binary(Host),
|
||||
update_tables(global, Storage) ->
|
||||
[update_tables(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_tables(HostB, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, muc_registered,
|
||||
HostB, muc_registered,
|
||||
[{muc_registered, [us_host, nick],
|
||||
fun({muc_registered, {{UserS, ServerS}, HostS}, NickS}) ->
|
||||
#muc_registered{user_host = {exmpp_jid:make(UserS, ServerS),
|
||||
@@ -1040,7 +1070,7 @@ update_tables(Host, mnesia) ->
|
||||
nick = list_to_binary(NickS)}
|
||||
end}]),
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, muc_room_opt,
|
||||
HostB, muc_room_opt,
|
||||
[{muc_room, [name_host, opts],
|
||||
fun({muc_room, {NameString, HostString}, Options}) ->
|
||||
NameHost = {list_to_binary(NameString),
|
||||
@@ -1066,4 +1096,7 @@ update_tables(Host, mnesia) ->
|
||||
opt = Opt,
|
||||
val = Val})
|
||||
end, Options)
|
||||
end}]).
|
||||
end}]);
|
||||
|
||||
update_tables(_HostB, odbc) ->
|
||||
ok.
|
||||
|
||||
@@ -877,6 +877,7 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
|
||||
max_users -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(integer_to_list(T), FileFormat) ++ "\"</div>";
|
||||
title -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(T, FileFormat) ++ "\"</div>";
|
||||
description -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(T, FileFormat) ++ "\"</div>";
|
||||
allow_private_messages_from_visitors -> "<div class=\"rcot\">" ++ OptText ++ ": \"" ++ htmlize(?T(atom_to_list(T)), FileFormat) ++ "\"</div>";
|
||||
_ -> "\"" ++ T ++ "\""
|
||||
end
|
||||
end,
|
||||
@@ -898,6 +899,7 @@ get_roomconfig_text(moderated) -> "Make room moderated";
|
||||
get_roomconfig_text(members_by_default) -> "Default users as participants";
|
||||
get_roomconfig_text(allow_change_subj) -> "Allow users to change the subject";
|
||||
get_roomconfig_text(allow_private_messages) -> "Allow users to send private messages";
|
||||
get_roomconfig_text(allow_private_messages_from_visitors) -> "Allow visitors to send private messages to";
|
||||
get_roomconfig_text(allow_query_users) -> "Allow users to query other users";
|
||||
get_roomconfig_text(allow_user_invites) -> "Allow users to send invites";
|
||||
get_roomconfig_text(logging) -> "Enable logging";
|
||||
@@ -957,7 +959,7 @@ get_room_state(RoomName, MucService) ->
|
||||
RoomPid = R#muc_online_room.pid,
|
||||
get_room_state(RoomPid);
|
||||
[] ->
|
||||
room_not_found
|
||||
#state{}
|
||||
end.
|
||||
|
||||
get_room_state(RoomPid) ->
|
||||
|
||||
+272
-89
@@ -451,26 +451,42 @@ normal_state({route, From, ToNick,
|
||||
ToNick),
|
||||
From, Err);
|
||||
_ ->
|
||||
case find_jid_by_nick(ToNick, StateData) of
|
||||
ToJIDs = find_jids_by_nick(ToNick, StateData),
|
||||
case ToJIDs of
|
||||
false ->
|
||||
ErrText = "Recipient is not in the room",
|
||||
ErrText = "Recipient is not in the conference room",
|
||||
Err = exmpp_stanza:reply_with_error(Packet,
|
||||
exmpp_stanza:error(Packet#xmlel.ns, 'item-not-found',
|
||||
{Lang, translate:translate(Lang, ErrText)})),
|
||||
exmpp_stanza:error(Packet#xmlel.ns, 'item-not-found',
|
||||
{Lang, translate:translate(Lang, ErrText)})),
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(
|
||||
StateData#state.jid,
|
||||
ToNick),
|
||||
From, Err);
|
||||
ToJID ->
|
||||
{ok, #user{nick = FromNick}} =
|
||||
?DICT:find(jlib:short_prepd_jid(From),
|
||||
StateData#state.users),
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(
|
||||
StateData#state.jid,
|
||||
FromNick),
|
||||
ToJID, Packet)
|
||||
_ ->
|
||||
SrcIsVisitor = is_visitor(From, StateData),
|
||||
DstIsModerator = is_moderator(hd(ToJIDs), StateData),
|
||||
PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors,
|
||||
if SrcIsVisitor == false;
|
||||
PmFromVisitors == anyone;
|
||||
(PmFromVisitors == moderators) and (DstIsModerator) ->
|
||||
{ok, #user{nick = FromNick}} =
|
||||
?DICT:find(jlib:jid_tolower(From),
|
||||
StateData#state.users),
|
||||
FromNickJID = jid_replace_resource(StateData#state.jid, FromNick),
|
||||
[ejabberd_router:route(FromNickJID, ToJID, Packet) || ToJID <- ToJIDs];
|
||||
true ->
|
||||
ErrText = "It is not allowed to send private messages",
|
||||
Err = exmpp_stanza:reply_with_error(Packet,
|
||||
exmpp_stanza:error(Packet#xmlel.ns, 'forbidden',
|
||||
{Lang, translate:translate(Lang, ErrText)})),
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(
|
||||
StateData#state.jid,
|
||||
ToNick),
|
||||
From, Err)
|
||||
|
||||
end
|
||||
end
|
||||
end;
|
||||
{true, false} ->
|
||||
@@ -934,7 +950,10 @@ process_presence(From, Nick, #xmlel{name = 'presence'} = Packet,
|
||||
true ->
|
||||
NewState =
|
||||
add_user_presence_un(From, Packet, StateData),
|
||||
send_new_presence(From, NewState),
|
||||
case ?DICT:find(Nick, StateData#state.nicks) of
|
||||
{ok, [_, _ | _]} -> ok;
|
||||
_ -> send_new_presence(From, NewState)
|
||||
end,
|
||||
Reason = case exmpp_xml:get_element(Packet, 'status') of
|
||||
undefined -> <<>>;
|
||||
Status_el -> exmpp_xml:get_cdata(Status_el)
|
||||
@@ -958,7 +977,7 @@ process_presence(From, Nick, #xmlel{name = 'presence'} = Packet,
|
||||
true ->
|
||||
case is_nick_change(From, Nick, StateData) of
|
||||
true ->
|
||||
case {is_nick_exists(Nick, StateData),
|
||||
case {nick_collision(From, Nick, StateData),
|
||||
mod_muc:can_use_nick(
|
||||
StateData#state.host, From, Nick),
|
||||
{(StateData#state.config)#config.allow_visitor_nickchange,
|
||||
@@ -1277,21 +1296,31 @@ set_role(JID, Role, StateData) ->
|
||||
[]
|
||||
end
|
||||
end,
|
||||
Users = case Role of
|
||||
none ->
|
||||
lists:foldl(fun(J, Us) ->
|
||||
?DICT:erase(J,
|
||||
Us)
|
||||
end, StateData#state.users, LJIDs);
|
||||
_ ->
|
||||
lists:foldl(fun(J, Us) ->
|
||||
{ok, User} = ?DICT:find(J, Us),
|
||||
?DICT:store(J,
|
||||
User#user{role = Role},
|
||||
Us)
|
||||
end, StateData#state.users, LJIDs)
|
||||
end,
|
||||
StateData#state{users = Users}.
|
||||
{Users, Nicks}
|
||||
= case Role of
|
||||
none ->
|
||||
lists:foldl(fun(J, {Us, Ns}) ->
|
||||
NewNs =
|
||||
case ?DICT:find(J, Us) of
|
||||
{ok, #user{nick = Nick}} ->
|
||||
?DICT:erase(Nick, Ns);
|
||||
_ ->
|
||||
Ns
|
||||
end,
|
||||
{?DICT:erase(J, Us), NewNs}
|
||||
end,
|
||||
{StateData#state.users, StateData#state.nicks},
|
||||
LJIDs);
|
||||
_ ->
|
||||
{lists:foldl(fun(J, Us) ->
|
||||
{ok, User} = ?DICT:find(J, Us),
|
||||
?DICT:store(J,
|
||||
User#user{role = Role},
|
||||
Us)
|
||||
end, StateData#state.users, LJIDs),
|
||||
StateData#state.nicks}
|
||||
end,
|
||||
StateData#state{users = Users, nicks = Nicks}.
|
||||
|
||||
get_role(JID, StateData) ->
|
||||
LJID = jlib:short_prepd_jid(JID),
|
||||
@@ -1325,6 +1354,9 @@ get_default_role(Affiliation, StateData) ->
|
||||
is_visitor(Jid, StateData) ->
|
||||
get_role(Jid, StateData) =:= visitor.
|
||||
|
||||
is_moderator(Jid, StateData) ->
|
||||
get_role(Jid, StateData) =:= moderator.
|
||||
|
||||
get_max_users(StateData) ->
|
||||
MaxUsers = (StateData#state.config)#config.max_users,
|
||||
ServiceMaxUsers = get_service_max_users(StateData),
|
||||
@@ -1471,8 +1503,19 @@ add_online_user(JID, Nick, Role, StateData) ->
|
||||
role = Role},
|
||||
StateData#state.users),
|
||||
add_to_log(join, Nick, StateData),
|
||||
Nicks = ?DICT:update(Nick,
|
||||
fun(Entry) ->
|
||||
case lists:member(LJID, Entry) of
|
||||
true ->
|
||||
Entry;
|
||||
false ->
|
||||
[LJID|Entry]
|
||||
end
|
||||
end,
|
||||
[LJID],
|
||||
StateData#state.nicks),
|
||||
tab_add_online_user(JID, StateData),
|
||||
StateData#state{users = Users}.
|
||||
StateData#state{users = Users, nicks = Nicks}.
|
||||
|
||||
remove_online_user(JID, StateData) ->
|
||||
remove_online_user(JID, StateData, <<>>).
|
||||
@@ -1484,7 +1527,15 @@ remove_online_user(JID, StateData, Reason) ->
|
||||
add_to_log(leave, {Nick, Reason}, StateData),
|
||||
tab_remove_online_user(JID, StateData),
|
||||
Users = ?DICT:erase(LJID, StateData#state.users),
|
||||
StateData#state{users = Users}.
|
||||
Nicks = case ?DICT:find(Nick, StateData#state.nicks) of
|
||||
{ok, [LJID]} ->
|
||||
?DICT:erase(Nick, StateData#state.nicks);
|
||||
{ok, U} ->
|
||||
?DICT:store(Nick, U -- [LJID], StateData#state.nicks);
|
||||
false ->
|
||||
StateData#state.nicks
|
||||
end,
|
||||
StateData#state{users = Users, nicks = Nicks}.
|
||||
|
||||
|
||||
filter_presence(#xmlel{name = 'presence'} = Packet) ->
|
||||
@@ -1534,19 +1585,48 @@ add_user_presence_un(JID, Presence, StateData) ->
|
||||
StateData#state{users = Users}.
|
||||
|
||||
|
||||
is_nick_exists(Nick, StateData) ->
|
||||
?DICT:fold(fun(_, #user{nick = N}, B) ->
|
||||
B orelse (N == Nick)
|
||||
end, false, StateData#state.users).
|
||||
%% Find and return a list of the full JIDs of the users of Nick.
|
||||
%% Return jid record.
|
||||
find_jids_by_nick(Nick, StateData) ->
|
||||
case ?DICT:find(Nick, StateData#state.nicks) of
|
||||
{ok, [User]} ->
|
||||
[exmpp_jid:make(User)];
|
||||
{ok, Users} ->
|
||||
[exmpp_jid:make(LJID) || LJID <- Users];
|
||||
error ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% @spec(Nick::binary(), StateData) -> JID::jid() | false
|
||||
%% Find and return the full JID of the user of Nick with
|
||||
%% highest-priority presence. Return jid record.
|
||||
find_jid_by_nick(Nick, StateData) ->
|
||||
?DICT:fold(fun(_, #user{jid = JID, nick = N}, R) ->
|
||||
case Nick of
|
||||
N -> JID;
|
||||
_ -> R
|
||||
end
|
||||
end, false, StateData#state.users).
|
||||
case ?DICT:find(Nick, StateData#state.nicks) of
|
||||
{ok, [User]} ->
|
||||
exmpp_jid:make(User);
|
||||
{ok, [FirstUser|Users]} ->
|
||||
#user{last_presence = FirstPresence} =
|
||||
?DICT:fetch(FirstUser, StateData#state.users),
|
||||
{LJID, _} =
|
||||
lists:foldl(fun(Compare, {HighestUser, HighestPresence}) ->
|
||||
#user{last_presence = P1} =
|
||||
?DICT:fetch(Compare, StateData#state.users),
|
||||
case higher_presence(P1, HighestPresence) of
|
||||
true ->
|
||||
{Compare, P1};
|
||||
false ->
|
||||
{HighestUser, HighestPresence}
|
||||
end
|
||||
end, {FirstUser, FirstPresence}, Users),
|
||||
exmpp_jid:make(LJID);
|
||||
error ->
|
||||
false
|
||||
end.
|
||||
|
||||
higher_presence(Pres1, Pres2) ->
|
||||
Pri1 = exmpp_presence:get_priority(Pres1),
|
||||
Pri2 = exmpp_presence:get_priority(Pres2),
|
||||
Pri1 > Pri2.
|
||||
|
||||
is_nick_change(JID, Nick, StateData) ->
|
||||
LJID = jlib:short_prepd_jid(JID),
|
||||
@@ -1559,6 +1639,13 @@ is_nick_change(JID, Nick, StateData) ->
|
||||
Nick /= OldNick
|
||||
end.
|
||||
|
||||
nick_collision(User, Nick, StateData) ->
|
||||
UserOfNick = find_jid_by_nick(Nick, StateData),
|
||||
%% if nick is not used, or is used by another resource of the same
|
||||
%% user, it's ok.
|
||||
UserOfNick /= false andalso
|
||||
not exmpp_jid:bare_compare(UserOfNick, User).
|
||||
|
||||
add_new_user(From, Nick, Packet, StateData) ->
|
||||
Lang = exmpp_stanza:get_lang(Packet),
|
||||
MaxUsers = get_max_users(StateData),
|
||||
@@ -1571,13 +1658,14 @@ add_new_user(From, Nick, Packet, StateData) ->
|
||||
MaxConferences = gen_mod:get_module_opt(
|
||||
StateData#state.server_host,
|
||||
mod_muc, max_user_conferences, 10),
|
||||
Collision = nick_collision(From, Nick, StateData),
|
||||
case {(ServiceAffiliation == owner orelse
|
||||
MaxUsers < 0 orelse
|
||||
((Affiliation == admin orelse Affiliation == owner) andalso
|
||||
NUsers < MaxAdminUsers) orelse
|
||||
NUsers < MaxUsers) andalso
|
||||
NConferences < MaxConferences,
|
||||
is_nick_exists(Nick, StateData),
|
||||
Collision,
|
||||
mod_muc:can_use_nick(StateData#state.host, From, Nick),
|
||||
get_default_role(Affiliation, StateData)} of
|
||||
{false, _, _, _} ->
|
||||
@@ -1915,13 +2003,19 @@ send_new_presence(NJID, StateData) ->
|
||||
%% @spec(NJID::jid(), Reason::binary(), StateData) ->
|
||||
send_new_presence({U, S, R}, Reason, StateData) ->
|
||||
send_new_presence(exmpp_jid:make(U, S, R), Reason, StateData);
|
||||
send_new_presence(NJID, Reason, StateData) ->
|
||||
send_new_presence(NJID1, Reason, StateData) ->
|
||||
NJID = {exmpp_jid:node(NJID1), exmpp_jid:domain(NJID1), exmpp_jid:resource(NJID1)},
|
||||
%% First, find the nick associated with this JID.
|
||||
#user{nick = Nick} = ?DICT:fetch(NJID, StateData#state.users),
|
||||
%% Then find the JID using this nick with highest priority.
|
||||
LJID1 = find_jid_by_nick(Nick, StateData),
|
||||
LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
|
||||
%% Then we get the presence data we're supposed to send.
|
||||
{ok, #user{jid = RealJID,
|
||||
nick = Nick,
|
||||
role = Role,
|
||||
last_presence = Presence}} =
|
||||
?DICT:find(jlib:short_prepd_jid(NJID), StateData#state.users),
|
||||
Affiliation = get_affiliation(NJID, StateData),
|
||||
?DICT:find(LJID, StateData#state.users),
|
||||
Affiliation = get_affiliation(LJID1, StateData),
|
||||
SAffiliation = affiliation_to_binary(Affiliation),
|
||||
SRole = role_to_binary(Role),
|
||||
lists:foreach(
|
||||
@@ -1960,11 +2054,19 @@ send_new_presence(NJID, Reason, StateData) ->
|
||||
false ->
|
||||
Status
|
||||
end,
|
||||
Status3 = case NJID == Info#user.jid of
|
||||
true ->
|
||||
[#xmlel{name = 'status',
|
||||
attrs = [?XMLATTR(<<"code">>, <<"110">>)]}
|
||||
| Status2];
|
||||
false ->
|
||||
Status2
|
||||
end,
|
||||
Packet = exmpp_xml:append_child(Presence,
|
||||
#xmlel{ns = ?NS_MUC_USER, name = 'x',
|
||||
children = [#xmlel{ns = ?NS_MUC_USER, name ='item',
|
||||
attrs = ItemAttrs,
|
||||
children = ItemEls} | Status2]}),
|
||||
children = ItemEls} | Status3]}),
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(StateData#state.jid, Nick),
|
||||
Info#user.jid,
|
||||
@@ -1973,21 +2075,23 @@ send_new_presence(NJID, Reason, StateData) ->
|
||||
|
||||
|
||||
send_existing_presences(ToJID, StateData) ->
|
||||
LToJID = jlib:short_prepd_jid(ToJID),
|
||||
LToJID = {exmpp_jid:node(ToJID), exmpp_jid:domain(ToJID), exmpp_jid:resource(ToJID)},
|
||||
{ok, #user{jid = RealToJID,
|
||||
role = Role}} =
|
||||
?DICT:find(LToJID, StateData#state.users),
|
||||
lists:foreach(
|
||||
fun({LJID, #user{jid = FromJID,
|
||||
nick = FromNick,
|
||||
role = FromRole,
|
||||
last_presence = Presence
|
||||
}}) ->
|
||||
fun({FromNick, _Users}) ->
|
||||
LJID1 = find_jid_by_nick(FromNick, StateData),
|
||||
LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
|
||||
#user{jid = FromJID,
|
||||
role = FromRole,
|
||||
last_presence = Presence
|
||||
} = ?DICT:fetch(LJID, StateData#state.users),
|
||||
case RealToJID of
|
||||
FromJID ->
|
||||
ok;
|
||||
_ ->
|
||||
{N,D,R} = LJID,
|
||||
{N,D,R} = LJID,
|
||||
FromAffiliation = get_affiliation(exmpp_jid:make(N,D,R),
|
||||
StateData),
|
||||
ItemAttrs =
|
||||
@@ -2014,7 +2118,7 @@ send_existing_presences(ToJID, StateData) ->
|
||||
RealToJID,
|
||||
Packet)
|
||||
end
|
||||
end, ?DICT:to_list(StateData#state.users)).
|
||||
end, ?DICT:to_list(StateData#state.nicks)).
|
||||
|
||||
|
||||
|
||||
@@ -2032,12 +2136,37 @@ change_nick(JID, Nick, StateData) ->
|
||||
fun(#user{} = User) ->
|
||||
User#user{nick = Nick}
|
||||
end, StateData#state.users),
|
||||
NewStateData = StateData#state{users = Users},
|
||||
send_nick_changing(JID, OldNick, NewStateData),
|
||||
OldNickUsers = ?DICT:fetch(OldNick, StateData#state.nicks),
|
||||
NewNickUsers = case ?DICT:find(Nick, StateData#state.nicks) of
|
||||
{ok, U} -> U;
|
||||
error -> []
|
||||
end,
|
||||
%% Send unavailable presence from the old nick if it's no longer
|
||||
%% used.
|
||||
SendOldUnavailable = length(OldNickUsers) == 1,
|
||||
%% If we send unavailable presence from the old nick, we should
|
||||
%% probably send presence from the new nick, in order not to
|
||||
%% confuse clients. Otherwise, do it only if the new nick was
|
||||
%% unused.
|
||||
SendNewAvailable = SendOldUnavailable orelse
|
||||
NewNickUsers == [],
|
||||
Nicks =
|
||||
case OldNickUsers of
|
||||
[LJID] ->
|
||||
?DICT:store(Nick, [LJID|NewNickUsers],
|
||||
?DICT:erase(OldNick, StateData#state.nicks));
|
||||
[_|_] ->
|
||||
?DICT:store(Nick, [LJID|NewNickUsers],
|
||||
?DICT:store(OldNick, OldNickUsers -- [LJID],
|
||||
StateData#state.nicks))
|
||||
end,
|
||||
NewStateData = StateData#state{users = Users, nicks = Nicks},
|
||||
send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable),
|
||||
add_to_log(nickchange, {OldNick, Nick}, StateData),
|
||||
NewStateData.
|
||||
|
||||
send_nick_changing(JID, OldNick, StateData) ->
|
||||
send_nick_changing(JID, OldNick, StateData,
|
||||
SendOldUnavailable, SendNewAvailable) ->
|
||||
{ok, #user{jid = RealJID,
|
||||
nick = Nick,
|
||||
role = Role,
|
||||
@@ -2090,15 +2219,22 @@ send_nick_changing(JID, OldNick, StateData) ->
|
||||
children =[#xmlel{ns = ?NS_MUC_USER,
|
||||
name = 'item',
|
||||
attrs = ItemAttrs2}]}),
|
||||
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(StateData#state.jid, OldNick),
|
||||
Info#user.jid,
|
||||
Packet1),
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(StateData#state.jid, Nick),
|
||||
Info#user.jid,
|
||||
Packet2)
|
||||
if SendOldUnavailable ->
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(StateData#state.jid, OldNick),
|
||||
Info#user.jid,
|
||||
Packet1);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if SendNewAvailable ->
|
||||
ejabberd_router:route(
|
||||
jid_replace_resource(StateData#state.jid, Nick),
|
||||
Info#user.jid,
|
||||
Packet2);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, ?DICT:to_list(StateData#state.users)).
|
||||
|
||||
|
||||
@@ -2264,16 +2400,19 @@ items_with_affiliation(SAffiliation, StateData) ->
|
||||
fun({JID, {Affiliation, Reason}}) ->
|
||||
{N, D, R} = JID,
|
||||
#xmlel{name = 'item',
|
||||
ns = ?NS_MUC_ADMIN,
|
||||
attrs = [?XMLATTR(<<"affiliation">>,
|
||||
affiliation_to_binary(Affiliation)),
|
||||
?XMLATTR(<<"jid">>,
|
||||
exmpp_jid:to_binary(N, D, R))],
|
||||
children = [ #xmlel{name = 'reason',
|
||||
ns = ?NS_MUC_ADMIN,
|
||||
children = [#xmlcdata{cdata = Reason}]}]};
|
||||
|
||||
({JID, Affiliation}) ->
|
||||
{N, D, R} = JID,
|
||||
#xmlel{name = 'item',
|
||||
ns = ?NS_MUC_ADMIN,
|
||||
attrs = [?XMLATTR(<<"affiliation">>,
|
||||
affiliation_to_binary(Affiliation)),
|
||||
?XMLATTR(<<"jid">>,
|
||||
@@ -2286,6 +2425,7 @@ user_to_item(#user{role = Role,
|
||||
}, StateData) ->
|
||||
Affiliation = get_affiliation(JID, StateData),
|
||||
#xmlel{name = 'item',
|
||||
ns =?NS_MUC_ADMIN,
|
||||
attrs = [
|
||||
?XMLATTR(<<"role">>, role_to_binary(Role)),
|
||||
?XMLATTR(<<"affiliation">>, affiliation_to_binary(Affiliation)),
|
||||
@@ -2322,7 +2462,7 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
|
||||
lists:foldl(
|
||||
fun(E, SD) ->
|
||||
process_admin_items_set(E, SD)
|
||||
end, StateData, Res),
|
||||
end, StateData, lists:flatten(Res)),
|
||||
case (NSD#state.config)#config.persistent of
|
||||
true ->
|
||||
mod_muc:store_room(NSD#state.host, NSD#state.room,
|
||||
@@ -2419,7 +2559,7 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
S when S =/= false ->
|
||||
try exmpp_jid:parse(S) of
|
||||
J ->
|
||||
{value, J}
|
||||
{value, [J]}
|
||||
catch
|
||||
_:_ ->
|
||||
ErrText = io_lib:format(
|
||||
@@ -2431,7 +2571,7 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
_ ->
|
||||
case exmpp_xml:get_attribute(Item, <<"nick">>, false) of
|
||||
N when N =/= false ->
|
||||
case find_jid_by_nick(N, StateData) of
|
||||
case find_jids_by_nick(N, StateData) of
|
||||
false ->
|
||||
ErrText =
|
||||
io_lib:format(
|
||||
@@ -2448,7 +2588,7 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
end
|
||||
end,
|
||||
case TJID of
|
||||
{value, JID} ->
|
||||
{value, [JID|_]=JIDs} ->
|
||||
TAffiliation = get_affiliation(JID, StateData),
|
||||
TRole = get_role(JID, StateData),
|
||||
case exmpp_xml:get_attribute_as_binary(Item, <<"role">>,false) of
|
||||
@@ -2498,16 +2638,13 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
Items, Lang, StateData,
|
||||
Res);
|
||||
true ->
|
||||
Reason = exmpp_xml:get_path(Item, [{element, 'reason'}, cdata]),
|
||||
MoreRes = [{exmpp_jid:bare(Jidx), affiliation, SAffiliation, Reason} || Jidx <- JIDs],
|
||||
find_changed_items(
|
||||
UJID,
|
||||
UAffiliation, URole,
|
||||
Items, Lang, StateData,
|
||||
[{exmpp_jid:bare(JID),
|
||||
affiliation,
|
||||
SAffiliation,
|
||||
exmpp_xml:get_path(
|
||||
Item, [{element, 'reason'},
|
||||
cdata])} | Res]);
|
||||
[MoreRes | Res]);
|
||||
false ->
|
||||
{error, 'not-allowed'}
|
||||
end
|
||||
@@ -2555,14 +2692,13 @@ find_changed_items(UJID, UAffiliation, URole,
|
||||
Items, Lang, StateData,
|
||||
Res);
|
||||
true ->
|
||||
Reason = exmpp_xml:get_path(Item, [{element, 'reason'}, cdata]),
|
||||
MoreRes = [{Jidx, role, SRole, Reason} || Jidx <- JIDs],
|
||||
find_changed_items(
|
||||
UJID,
|
||||
UAffiliation, URole,
|
||||
Items, Lang, StateData,
|
||||
[{JID, role, SRole,
|
||||
exmpp_xml:get_path(
|
||||
Item, [{element, 'reason'},
|
||||
cdata])} | Res]);
|
||||
[MoreRes | Res]);
|
||||
_ ->
|
||||
{error, 'not-allowed'}
|
||||
end
|
||||
@@ -3093,6 +3229,39 @@ get_config(Lang, StateData, From) ->
|
||||
?BOOLXFIELD("Allow users to send private messages",
|
||||
"allow_private_messages",
|
||||
Config#config.allow_private_messages),
|
||||
#xmlel{name = 'field', attrs = [
|
||||
?XMLATTR(<<"type">>, <<"list-single">>),
|
||||
?XMLATTR(<<"label">>,
|
||||
translate:translate(Lang, "Allow visitors to send private messages to")),
|
||||
?XMLATTR(<<"var">>, <<"allow_private_messages_from_visitors">>)],
|
||||
children = [#xmlel{name = 'value',
|
||||
children = [#xmlcdata{cdata =
|
||||
case Config#config.allow_private_messages_from_visitors of
|
||||
anyone ->
|
||||
<<"anyone">>;
|
||||
moderators ->
|
||||
<<"moderators">>;
|
||||
nobody ->
|
||||
<<"nobody">>
|
||||
end}]},
|
||||
#xmlel{name = 'option', attrs = [
|
||||
?XMLATTR(<<"label">>,
|
||||
translate:translate(Lang, "nobody"))],
|
||||
children = [#xmlel{name = 'value',
|
||||
children = [#xmlcdata{cdata =
|
||||
<<"nobody">>}]}]},
|
||||
#xmlel{name = 'option', attrs = [
|
||||
?XMLATTR(<<"label">>,
|
||||
translate:translate(Lang, "moderators only"))],
|
||||
children = [#xmlel{name = 'value',
|
||||
children = [#xmlcdata{cdata =
|
||||
<<"moderators">>}]}]},
|
||||
#xmlel{name = 'option', attrs = [
|
||||
?XMLATTR(<<"label">>,
|
||||
translate:translate(Lang, "anyone"))],
|
||||
children = [#xmlel{name = 'value',
|
||||
children = [#xmlcdata{cdata =
|
||||
<<"anyone">>}]}]}]},
|
||||
?BOOLXFIELD("Allow users to query other users",
|
||||
"allow_query_users",
|
||||
Config#config.allow_query_users),
|
||||
@@ -3197,6 +3366,17 @@ set_xoption([{"allow_query_users", [Val]} | Opts], Config) ->
|
||||
?SET_BOOL_XOPT(allow_query_users, Val);
|
||||
set_xoption([{"allow_private_messages", [Val]} | Opts], Config) ->
|
||||
?SET_BOOL_XOPT(allow_private_messages, Val);
|
||||
set_xoption([{"allow_private_messages_from_visitors", [Val]} | Opts], Config) ->
|
||||
case Val of
|
||||
"anyone" ->
|
||||
?SET_STRING_XOPT(allow_private_messages_from_visitors, anyone);
|
||||
"moderators" ->
|
||||
?SET_STRING_XOPT(allow_private_messages_from_visitors, moderators);
|
||||
"nobody" ->
|
||||
?SET_STRING_XOPT(allow_private_messages_from_visitors, nobody);
|
||||
_ ->
|
||||
{error, 'bad-request'}
|
||||
end;
|
||||
set_xoption([{"muc#roomconfig_allowvisitorstatus", [Val]} | Opts], Config) ->
|
||||
?SET_BOOL_XOPT(allow_visitor_status, Val);
|
||||
set_xoption([{"muc#roomconfig_allowvisitornickchange", [Val]} | Opts], Config) ->
|
||||
@@ -3299,6 +3479,7 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
||||
allow_change_subj -> StateData#state{config = (StateData#state.config)#config{allow_change_subj = Val}};
|
||||
allow_query_users -> StateData#state{config = (StateData#state.config)#config{allow_query_users = Val}};
|
||||
allow_private_messages -> StateData#state{config = (StateData#state.config)#config{allow_private_messages = Val}};
|
||||
allow_private_messages_from_visitors -> StateData#state{config = (StateData#state.config)#config{allow_private_messages_from_visitors = Val}};
|
||||
allow_visitor_nickchange -> StateData#state{config = (StateData#state.config)#config{allow_visitor_nickchange = Val}};
|
||||
allow_visitor_status -> StateData#state{config = (StateData#state.config)#config{allow_visitor_status = Val}};
|
||||
public -> StateData#state{config = (StateData#state.config)#config{public = Val}};
|
||||
@@ -3342,6 +3523,7 @@ make_opts(StateData) ->
|
||||
?MAKE_CONFIG_OPT(allow_change_subj),
|
||||
?MAKE_CONFIG_OPT(allow_query_users),
|
||||
?MAKE_CONFIG_OPT(allow_private_messages),
|
||||
?MAKE_CONFIG_OPT(allow_private_messages_from_visitors),
|
||||
?MAKE_CONFIG_OPT(allow_visitor_status),
|
||||
?MAKE_CONFIG_OPT(allow_visitor_nickchange),
|
||||
?MAKE_CONFIG_OPT(public),
|
||||
@@ -3450,7 +3632,7 @@ process_iq_disco_info(_From, get, Lang, StateData) ->
|
||||
#xmlcdata{cdata = list_to_binary(Val)}]}]}).
|
||||
|
||||
iq_disco_info_extras(Lang, StateData) ->
|
||||
Len = length(?DICT:to_list(StateData#state.users)),
|
||||
Len = ?DICT:size(StateData#state.users),
|
||||
RoomDescription = (StateData#state.config)#config.description,
|
||||
[#xmlel{ns = ?NS_DATA_FORMS, name = 'x',
|
||||
attrs = [?XMLATTR(<<"type">>, <<"result">>)],
|
||||
@@ -3667,8 +3849,7 @@ check_decline_invitation(Packet) ->
|
||||
%% The original stanza must be slightly modified.
|
||||
send_decline_invitation({Packet, XEl, DEl = #xmlel{name='decline'}, ToJID},
|
||||
RoomJID, FromJID) ->
|
||||
FromString = exmpp_jid:to_binary(FromJID),
|
||||
|
||||
FromString = exmpp_jid:bare_to_binary(FromJID),
|
||||
DEl1 = exmpp_xml:remove_attribute(DEl, <<"to">>),
|
||||
DEl2 = exmpp_xml:set_attribute(DEl1, <<"from">>,FromString),
|
||||
XEl2 = replace_subelement(XEl,DEl2),
|
||||
@@ -3715,27 +3896,29 @@ add_to_log(Type, Data, StateData) ->
|
||||
tab_add_online_user(JID, StateData) ->
|
||||
LUser = exmpp_jid:prep_node(JID),
|
||||
LServer = exmpp_jid:prep_domain(JID),
|
||||
LResource = exmpp_jid:prep_resource(JID),
|
||||
US = {LUser, LServer},
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
catch ets:insert(
|
||||
muc_online_users,
|
||||
#muc_online_users{us = US, room = Room, host = Host}).
|
||||
#muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
|
||||
|
||||
|
||||
|
||||
tab_remove_online_user(JID, StateData) when ?IS_JID(JID) ->
|
||||
LUser = exmpp_jid:prep_node(JID),
|
||||
LServer = exmpp_jid:prep_domain(JID),
|
||||
tab_remove_online_user({LUser, LServer, none},StateData);
|
||||
LResource = exmpp_jid:prep_resource(JID),
|
||||
tab_remove_online_user({LUser, LServer, LResource},StateData);
|
||||
|
||||
tab_remove_online_user({LUser, LServer,_}, StateData) ->
|
||||
tab_remove_online_user({LUser, LServer, LResource}, StateData) ->
|
||||
US = {LUser, LServer},
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
catch ets:delete_object(
|
||||
muc_online_users,
|
||||
#muc_online_users{us = US, room = Room, host = Host}).
|
||||
#muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
|
||||
|
||||
tab_count_user(JID) ->
|
||||
LUser = exmpp_jid:prep_node(JID),
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
allow_change_subj = true,
|
||||
allow_query_users = true,
|
||||
allow_private_messages = true,
|
||||
allow_private_messages_from_visitors = anyone,
|
||||
allow_visitor_status = true,
|
||||
allow_visitor_nickchange = true,
|
||||
public = true,
|
||||
@@ -68,6 +69,7 @@
|
||||
config = #config{},
|
||||
users = ?DICT:new(),
|
||||
robots = ?DICT:new(),
|
||||
nicks = ?DICT:new(),
|
||||
affiliations = ?DICT:new(),
|
||||
history,
|
||||
subject = "",
|
||||
@@ -78,6 +80,7 @@
|
||||
room_queue = queue:new()}).
|
||||
|
||||
-record(muc_online_users, {us,
|
||||
resource,
|
||||
room,
|
||||
host}).
|
||||
|
||||
|
||||
+13
-8
@@ -112,12 +112,14 @@
|
||||
%% default value for the maximum number of user messages
|
||||
-define(MAX_USER_MESSAGES, infinity).
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
% Host = binary_to_list(HostB),
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, HostB, offline_msg,
|
||||
[{disc_only_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, offline_msg)},
|
||||
{types, [{user_host, {text, text}},
|
||||
@@ -125,7 +127,7 @@ start(Host, Opts) ->
|
||||
{expire, bigint},
|
||||
{from, jid},
|
||||
{to, jid}]}]),
|
||||
update_table(Host, Backend),
|
||||
update_table(HostB, Backend),
|
||||
ejabberd_hooks:add(offline_message_hook, HostB,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:add(resend_offline_messages_hook, HostB,
|
||||
@@ -145,7 +147,7 @@ start(Host, Opts) ->
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, HostB,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
register(gen_mod:get_module_proc(HostB, ?PROCNAME),
|
||||
spawn(?MODULE, loop, [AccessMaxOfflineMsgs])).
|
||||
|
||||
stanza_to_store(Stanza) ->
|
||||
@@ -258,7 +260,7 @@ store_packet(From, To, Packet) ->
|
||||
LServer = exmpp_jid:prep_domain(To),
|
||||
TimeStamp = make_timestamp(),
|
||||
Expire = find_x_expire(TimeStamp, Packet#xmlel.children),
|
||||
gen_mod:get_module_proc(LServer, ?PROCNAME) !
|
||||
gen_mod:get_module_proc_existing(LServer, ?PROCNAME) !
|
||||
#offline_msg{user_host = {LUser, LServer},
|
||||
timestamp = TimeStamp,
|
||||
expire = Expire,
|
||||
@@ -464,6 +466,9 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
update_table(global, Storage) ->
|
||||
[update_table(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, offline_msg,
|
||||
@@ -720,8 +725,8 @@ get_messages_subset2(Max, Length, MsgsAll) ->
|
||||
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
|
||||
|
||||
webadmin_user(Acc, User, Server, Lang) ->
|
||||
LUser = exmpp_stringprep:nodeprep(User),
|
||||
LServer = exmpp_stringprep:nameprep(Server),
|
||||
LUser = list_to_binary(exmpp_stringprep:nodeprep(User)),
|
||||
LServer = list_to_binary(exmpp_stringprep:nameprep(Server)),
|
||||
QueueLen = get_queue_length(LUser, LServer),
|
||||
FQueueLen = [?AC("queue/", integer_to_list(QueueLen))],
|
||||
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
|
||||
|
||||
+13
-10
@@ -52,7 +52,7 @@
|
||||
%% Hook callbacks
|
||||
-export([iq_ping/3, user_online/3, user_offline/3, user_send/3]).
|
||||
|
||||
-record(state, {host = "",
|
||||
-record(state, {host, % binary() | global
|
||||
send_pings = ?DEFAULT_SEND_PINGS,
|
||||
ping_interval = ?DEFAULT_PING_INTERVAL,
|
||||
timeout_action = none,
|
||||
@@ -66,16 +66,18 @@ start_link(Host, Opts) ->
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
start_ping(Host, JID) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
Proc = gen_mod:get_module_proc_existing(Host, ?MODULE),
|
||||
gen_server:cast(Proc, {start_ping, JID}).
|
||||
|
||||
stop_ping(Host, JID) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
Proc = gen_mod:get_module_proc_existing(Host, ?MODULE),
|
||||
gen_server:cast(Proc, {stop_ping, JID}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
PingSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
@@ -90,8 +92,7 @@ stop(Host) ->
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, Opts]) ->
|
||||
HostB = list_to_binary(Host),
|
||||
init([HostB, Opts]) ->
|
||||
SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS),
|
||||
PingInterval = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL),
|
||||
TimeoutAction = gen_mod:get_opt(timeout_action, Opts, none),
|
||||
@@ -103,6 +104,8 @@ init([Host, Opts]) ->
|
||||
?MODULE, iq_ping, IQDisc),
|
||||
case SendPings of
|
||||
true ->
|
||||
%% Ping requests are sent to all entities, whether they
|
||||
%% announce 'urn:xmpp:ping' in their caps or not
|
||||
ejabberd_hooks:add(sm_register_connection_hook, HostB,
|
||||
?MODULE, user_online, 100),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, HostB,
|
||||
@@ -112,14 +115,13 @@ init([Host, Opts]) ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{ok, #state{host = Host,
|
||||
{ok, #state{host = HostB,
|
||||
send_pings = SendPings,
|
||||
ping_interval = PingInterval,
|
||||
timeout_action = TimeoutAction,
|
||||
timers = ?DICT:new()}}.
|
||||
|
||||
terminate(_Reason, #state{host = Host}) ->
|
||||
HostB = list_to_binary(Host),
|
||||
terminate(_Reason, #state{host = HostB}) ->
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, HostB,
|
||||
?MODULE, user_offline, 100),
|
||||
ejabberd_hooks:delete(sm_register_connection_hook, HostB,
|
||||
@@ -143,7 +145,8 @@ handle_cast({stop_ping, JID}, State) ->
|
||||
{noreply, State#state{timers = Timers}};
|
||||
handle_cast({iq_pong, JID, timeout}, State) ->
|
||||
Timers = del_timer(JID, State#state.timers),
|
||||
ejabberd_hooks:run(user_ping_timeout, list_to_binary(State#state.host), [JID]),
|
||||
Host = exmpp_jid:domain(JID),
|
||||
ejabberd_hooks:run(user_ping_timeout, Host, [JID]),
|
||||
case State#state.timeout_action of
|
||||
kill ->
|
||||
case ejabberd_sm:get_session_pid(JID) of
|
||||
@@ -171,7 +174,7 @@ handle_info({timeout, _TRef, {ping, JID}}, State) ->
|
||||
F = fun(Response) ->
|
||||
gen_server:cast(Pid, {iq_pong, JID, Response})
|
||||
end,
|
||||
From = exmpp_jid:make(State#state.host),
|
||||
From = exmpp_jid:make(exmpp_jid:domain(JID)),
|
||||
ejabberd_local:route_iq(From, JID, IQ, F),
|
||||
Timers = add_timer(JID, State#state.ping_interval, State#state.timers),
|
||||
{noreply, State#state{timers = Timers}};
|
||||
|
||||
@@ -37,8 +37,9 @@
|
||||
|
||||
-record(pres_counter, {dir, start, count, logged = false}).
|
||||
|
||||
start(Host, _Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, _Opts) ->
|
||||
ejabberd_hooks:add(privacy_check_packet, HostB,
|
||||
?MODULE, check_packet, 25),
|
||||
ok.
|
||||
|
||||
+34
-9
@@ -114,28 +114,30 @@
|
||||
updated_list/3]).
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
-include_lib("exmpp/include/exmpp_jid.hrl").
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, HostB, privacy_list,
|
||||
[{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, privacy_list)},
|
||||
{types, [{user_host, {text, text}}]}]),
|
||||
gen_storage:create_table(Backend, HostB, privacy_default_list,
|
||||
[{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{attributes, record_info(fields, privacy_default_list)},
|
||||
{types, [{user_host, {text, text}}]}]),
|
||||
gen_storage:create_table(Backend, HostB, privacy_list_data,
|
||||
[{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, privacy_list_data)},
|
||||
{types, [{user_host, {text, text}},
|
||||
@@ -149,7 +151,7 @@ start(Host, Opts) ->
|
||||
{match_presence_in, atom},
|
||||
{match_presence_out, atom}
|
||||
]}]),
|
||||
update_tables(Host, Backend),
|
||||
update_tables(HostB, Backend),
|
||||
gen_storage:add_table_index(HostB, privacy_list, name),
|
||||
gen_storage:add_table_index(HostB, privacy_list_data, name),
|
||||
ejabberd_hooks:add(privacy_iq_get, HostB,
|
||||
@@ -673,6 +675,27 @@ get_user_list(_, LUser, LServer)
|
||||
%% From is the sender, To is the destination.
|
||||
%% If Dir = out, User@Server is the sender account (From).
|
||||
%% If Dir = in, User@Server is the destination account (To).
|
||||
check_packet(_, _User, _Server,
|
||||
_UserList,
|
||||
{#jid{node = "", domain = Server} = _From,
|
||||
#jid{domain = Server} = _To,
|
||||
_},
|
||||
in) ->
|
||||
allow;
|
||||
check_packet(_, _User, _Server,
|
||||
_UserList,
|
||||
{#jid{domain = Server} = _From,
|
||||
#jid{node = "", domain = Server} = _To,
|
||||
_},
|
||||
out) ->
|
||||
allow;
|
||||
check_packet(_, _User, _Server,
|
||||
_UserList,
|
||||
{#jid{node = User, domain = Server} = _From,
|
||||
#jid{node = User, domain = Server} = _To,
|
||||
_},
|
||||
_Dir) ->
|
||||
allow;
|
||||
check_packet(_, User, Server,
|
||||
#userlist{list = List, needdb = NeedDb},
|
||||
{From, To, #xmlel{name = PName} = El},
|
||||
@@ -807,10 +830,12 @@ updated_list(_,
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
update_tables(Host, mnesia) ->
|
||||
HostB = list_to_binary(Host),
|
||||
update_tables(global, Storage) ->
|
||||
[update_tables(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_tables(HostB, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, privacy_default_list,
|
||||
HostB, privacy_default_list,
|
||||
[{privacy, [us, default, lists],
|
||||
fun({privacy, {U, S}, Default, Lists}) ->
|
||||
US = {list_to_binary(U), list_to_binary(S)},
|
||||
|
||||
+8
-4
@@ -69,16 +69,17 @@
|
||||
%% TODO: usns instead of user_host_ns requires no migration
|
||||
-record(private_storage, {user_host_ns, xml}).
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, HostB, private_storage,
|
||||
[{disc_only_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{attributes, record_info(fields, private_storage)},
|
||||
{types, [{user_host_ns, {binary, binary, atom}}, {xml, xmlel}]}]),
|
||||
update_table(Host, Backend),
|
||||
update_table(HostB, Backend),
|
||||
ejabberd_hooks:add(remove_user, HostB,
|
||||
?MODULE, remove_user, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVATE,
|
||||
@@ -218,6 +219,9 @@ remove_user(User, Server)
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
update_table(global, Storage) ->
|
||||
[update_table(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, private_storage,
|
||||
|
||||
@@ -222,7 +222,7 @@ iq_vcard(Lang) ->
|
||||
"\nCopyright (c) 2003-2011 ProcessOne")}]}].
|
||||
|
||||
parse_options(ServerHost, Opts) ->
|
||||
MyHost = gen_mod:expand_host_name(ServerHost, Opts, "proxy"),
|
||||
MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"),
|
||||
Port = gen_mod:get_opt(port, Opts, 7777),
|
||||
ACL = gen_mod:get_opt(access, Opts, all),
|
||||
Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
|
||||
|
||||
@@ -203,7 +203,7 @@ stop(Host) ->
|
||||
|
||||
init([ServerHost, Opts]) ->
|
||||
?DEBUG("pubsub init ~p ~p",[ServerHost,Opts]),
|
||||
Host = gen_mod:expand_host_name(ServerHost, Opts, "pubsub"),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, "pubsub.@HOST@"),
|
||||
Access = gen_mod:get_opt('access_createnode', Opts, 'all'),
|
||||
PepOffline = gen_mod:get_opt('ignore_pep_from_offline', Opts, true),
|
||||
IQDisc = gen_mod:get_opt('iqdisc', Opts, 'one_queue'),
|
||||
@@ -250,6 +250,7 @@ init([ServerHost, Opts]) ->
|
||||
ejabberd_router:register_route(Host),
|
||||
update_node_database(Host, ServerHost),
|
||||
update_state_database(Host, ServerHost),
|
||||
put(server_host, ServerHost),
|
||||
init_nodes(Host, ServerHost, NodeTree, Plugins),
|
||||
State = #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
@@ -2170,13 +2171,16 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
case transaction(CreateNode, transaction) of
|
||||
{result, {NodeId, {Result, broadcast}}} ->
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions),
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_NodeId, default}} ->
|
||||
{result, {NodeId, default}} ->
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
{result, Reply};
|
||||
{result, {_NodeId, Result}} ->
|
||||
{result, {NodeId, Result}} ->
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
{result, Result};
|
||||
Error ->
|
||||
%% in case we change transaction to sync_dirty...
|
||||
@@ -2219,6 +2223,7 @@ delete_node(Host, Node, Owner) ->
|
||||
end
|
||||
end,
|
||||
Reply = [],
|
||||
ServerHost = get(server_host),
|
||||
case transaction(Host, Node, Action, transaction) of
|
||||
{result, {_, {Result, broadcast, Removed}}} ->
|
||||
lists:foreach(fun({RNode, _RSubscriptions}) ->
|
||||
@@ -2227,20 +2232,31 @@ delete_node(Host, Node, Owner) ->
|
||||
Type = RNode#pubsub_node.type,
|
||||
Options = RNode#pubsub_node.options,
|
||||
broadcast_removed_node(RH, RN, Nidx, Type, Options),
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, Nidx]),
|
||||
unset_cached_item(RH, Nidx)
|
||||
end, Removed),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_, {Result, _Removed}}} ->
|
||||
{result, {_, {Result, Removed}}} ->
|
||||
lists:foreach(fun({RNode, _RSubscriptions}) ->
|
||||
{RH, RN} = RNode#pubsub_node.id,
|
||||
Nidx = RNode#pubsub_node.idx,
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, Nidx]),
|
||||
unset_cached_item(RH, Nidx)
|
||||
end, Removed),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_, default}} ->
|
||||
{result, {TNode, default}} ->
|
||||
Nidx = TNode#pubsub_node.idx,
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, Nidx]),
|
||||
{result, Reply};
|
||||
{result, {_, Result}} ->
|
||||
{result, {TNode, Result}} ->
|
||||
Nidx = TNode#pubsub_node.idx,
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, Nidx]),
|
||||
{result, Result};
|
||||
Error ->
|
||||
Error
|
||||
@@ -2420,8 +2436,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
DeliverPayloads = get_option(Options, deliver_payloads),
|
||||
PersistItems = get_option(Options, persist_items),
|
||||
MaxItems = case PersistItems of
|
||||
0 -> 0;
|
||||
1 -> max_items(Host, Options)
|
||||
false -> 0;
|
||||
true -> max_items(Host, Options)
|
||||
end,
|
||||
{PayloadCount, PayloadNS} = payload_els_ns(Payload),
|
||||
PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2
|
||||
@@ -2467,11 +2483,17 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
Nidx = TNode#pubsub_node.idx,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
BrPayload = case Broadcast of
|
||||
broadcast -> Payload;
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId, jlib:short_prepd_jid(Publisher), BrPayload, Removed),
|
||||
case get_option(Options, deliver_notifications) of
|
||||
true ->
|
||||
BrPayload = case Broadcast of
|
||||
broadcast -> Payload;
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId,
|
||||
jlib:short_prepd_jid(Publisher), BrPayload, Removed);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
set_cached_item(Host, Nidx, ItemId, Publisher, Payload),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
@@ -2503,8 +2525,11 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
case lists:member("auto-create", features(Type)) of
|
||||
true ->
|
||||
case create_node(Host, ServerHost, Node, Publisher, Type) of
|
||||
{result, _} ->
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload);
|
||||
{result, Reply2} ->
|
||||
NewNode = exmpp_xml:get_path(Reply2, [{element, 'create'},
|
||||
{attribute, <<"node">>}]),
|
||||
publish_item(Host, ServerHost, NewNode, Publisher, ItemId,
|
||||
Payload);
|
||||
_ ->
|
||||
{error, 'item-not-found'}
|
||||
end;
|
||||
|
||||
@@ -203,7 +203,7 @@ stop(Host) ->
|
||||
|
||||
init([ServerHost, Opts]) ->
|
||||
?DEBUG("pubsub init ~p ~p",[ServerHost,Opts]),
|
||||
Host = gen_mod:expand_host_name(ServerHost, Opts, "pubsub"),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, "pubsub.@HOST@"),
|
||||
Access = gen_mod:get_opt('access_createnode', Opts, 'all'),
|
||||
PepOffline = gen_mod:get_opt('ignore_pep_from_offline', Opts, true),
|
||||
IQDisc = gen_mod:get_opt('iqdisc', Opts, 'one_queue'),
|
||||
@@ -248,6 +248,7 @@ init([ServerHost, Opts]) ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_router:register_route(Host),
|
||||
put(server_host, ServerHost),
|
||||
init_nodes(Host, ServerHost, NodeTree, Plugins),
|
||||
State = #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
@@ -1964,13 +1965,16 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
case transaction(Host, CreateNode, transaction) of
|
||||
{result, {NodeId, {Result, broadcast}}} ->
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions),
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_NodeId, default}} ->
|
||||
{result, {NodeId, default}} ->
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
{result, Reply};
|
||||
{result, {_NodeId, Result}} ->
|
||||
{result, {NodeId, Result}} ->
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
{result, Result};
|
||||
Error ->
|
||||
%% in case we change transaction to sync_dirty...
|
||||
@@ -2013,6 +2017,7 @@ delete_node(Host, Node, Owner) ->
|
||||
end
|
||||
end,
|
||||
Reply = [],
|
||||
ServerHost = get(server_host),
|
||||
case transaction(Host, Node, Action, transaction) of
|
||||
{result, {_, {Result, broadcast, Removed}}} ->
|
||||
lists:foreach(fun({RNode, _RSubscriptions}) ->
|
||||
@@ -2021,20 +2026,31 @@ delete_node(Host, Node, Owner) ->
|
||||
Type = RNode#pubsub_node.type,
|
||||
Options = RNode#pubsub_node.options,
|
||||
broadcast_removed_node(RH, RN, Nidx, Type, Options),
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, Nidx]),
|
||||
unset_cached_item(RH, Nidx)
|
||||
end, Removed),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_, {Result, _Removed}}} ->
|
||||
{result, {_, {Result, Removed}}} ->
|
||||
lists:foreach(fun({RNode, _RSubscriptions}) ->
|
||||
{RH, RN} = RNode#pubsub_node.id,
|
||||
Nidx = RNode#pubsub_node.idx,
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, RH, RN, Nidx]),
|
||||
unset_cached_item(RH, Nidx)
|
||||
end, Removed),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
end;
|
||||
{result, {_, default}} ->
|
||||
{result, {TNode, default}} ->
|
||||
Nidx = TNode#pubsub_node.idx,
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, Nidx]),
|
||||
{result, Reply};
|
||||
{result, {_, Result}} ->
|
||||
{result, {TNode, Result}} ->
|
||||
Nidx = TNode#pubsub_node.idx,
|
||||
ejabberd_hooks:run(pubsub_delete_node, ServerHost, [ServerHost, Host, Node, Nidx]),
|
||||
{result, Result};
|
||||
Error ->
|
||||
Error
|
||||
@@ -2259,11 +2275,17 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
Nidx = TNode#pubsub_node.idx,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
BrPayload = case Broadcast of
|
||||
broadcast -> Payload;
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId, jlib:short_prepd_jid(Publisher), BrPayload, Removed),
|
||||
case get_option(Options, deliver_notifications) of
|
||||
true ->
|
||||
BrPayload = case Broadcast of
|
||||
broadcast -> Payload;
|
||||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId,
|
||||
jlib:short_prepd_jid(Publisher), BrPayload, Removed);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
set_cached_item(Host, Nidx, ItemId, Publisher, Payload),
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
@@ -2295,8 +2317,11 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
||||
case lists:member("auto-create", features(Type)) of
|
||||
true ->
|
||||
case create_node(Host, ServerHost, Node, Publisher, Type) of
|
||||
{result, _} ->
|
||||
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload);
|
||||
{result, Reply2} ->
|
||||
NewNode = exmpp_xml:get_path(Reply2, [{element, 'create'},
|
||||
{attribute, <<"node">>}]),
|
||||
publish_item(Host, ServerHost, NewNode, Publisher, ItemId,
|
||||
Payload);
|
||||
_ ->
|
||||
{error, 'item-not-found'}
|
||||
end;
|
||||
|
||||
@@ -178,8 +178,7 @@ create_node_permission(Host, ServerHost, _NodeId, _ParentNodeId, #jid{node = U,
|
||||
).
|
||||
|
||||
create_node(NodeIdx, JID) ->
|
||||
{result, _} = node_flat:create_node(NodeIdx, JID),
|
||||
{result, []}.
|
||||
node_flat:create_node(NodeIdx, JID).
|
||||
% case node_flat:create_node(NodeIdx, JID) of
|
||||
% {result, _} -> {result, []};
|
||||
% Error -> Error
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--- mod_pubsub.erl 2011-03-23 09:31:16.000000000 +0100
|
||||
+++ mod_pubsub_odbc.erl 2011-03-23 09:51:43.000000000 +0100
|
||||
--- mod_pubsub.erl 2011-11-29 15:46:28.000000000 +0100
|
||||
+++ mod_pubsub_odbc.erl 2011-11-29 15:47:30.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.
|
||||
@@ -46,10 +46,10 @@
|
||||
ejabberd_router:register_route(Host),
|
||||
- update_node_database(Host, ServerHost),
|
||||
- update_state_database(Host, ServerHost),
|
||||
put(server_host, ServerHost),
|
||||
init_nodes(Host, ServerHost, NodeTree, Plugins),
|
||||
State = #state{host = Host,
|
||||
server_host = ServerHost,
|
||||
@@ -351,229 +349,15 @@
|
||||
@@ -352,229 +350,15 @@
|
||||
|
||||
init_nodes(Host, ServerHost, _NodeTree, Plugins) ->
|
||||
%% TODO, this call should be done plugin side
|
||||
@@ -282,7 +282,7 @@
|
||||
-spec(send_loop/1 ::
|
||||
(
|
||||
State::#state{})
|
||||
@@ -590,7 +374,10 @@
|
||||
@@ -591,14 +375,17 @@
|
||||
%% 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) ->
|
||||
@@ -294,37 +294,70 @@
|
||||
lists:foreach(
|
||||
fun({Node, subscribed, _, SubJID}) ->
|
||||
if (SubJID == LJID) or (SubJID == BJID) ->
|
||||
@@ -763,7 +550,8 @@
|
||||
- #pubsub_node{id = {H, NodeId}, type = Type, idx = Nidx, options = Options} = Node,
|
||||
+ #pubsub_node{id = {H, NodeId}, type = Type, idx = NodeIdx, options = Options} = Node,
|
||||
case get_option(Options, 'send_last_published_item') of
|
||||
'on_sub_and_presence' ->
|
||||
- send_items(H, NodeId, Nidx, Type, LJID, 'last');
|
||||
+ send_items(H, NodeId, NodeIdx, Type, LJID, 'last');
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
@@ -647,7 +434,7 @@
|
||||
spawn(fun() ->
|
||||
Host = State#state.host,
|
||||
Owner = {U,S,undefined},
|
||||
- lists:foreach(fun(#pubsub_node{id = {_, NodeId}, type = Type, idx = Nidx, options = Options}) ->
|
||||
+ lists:foreach(fun(#pubsub_node{id = {_, NodeId}, type = Type, idx = NodeIdx, options = Options}) ->
|
||||
case get_option(Options, 'send_last_published_item') of
|
||||
'on_sub_and_presence' ->
|
||||
lists:foreach(fun(Resource) ->
|
||||
@@ -661,7 +448,7 @@
|
||||
RosterGroups = get_option(Options, 'roster_groups_allowed', []),
|
||||
element(2, get_roster_info(U, S, LJID, RosterGroups))
|
||||
end,
|
||||
- if Subscribed -> send_items(Owner, NodeId, Nidx, Type, LJID, 'last');
|
||||
+ if Subscribed -> send_items(Owner, NodeId, NodeIdx, Type, LJID, 'last');
|
||||
true -> ok
|
||||
end
|
||||
end, Resources);
|
||||
@@ -764,8 +551,9 @@
|
||||
[#xmlel{name = 'identity', ns = ?NS_DISCO_INFO,
|
||||
attrs = [?XMLATTR(<<"category">>, <<"pubsub">>), ?XMLATTR(<<"type">>, <<"pep">>)]}];
|
||||
disco_identity(#jid{node = U, domain = S, resource = R} = Host, NodeId, From) ->
|
||||
- Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options, owners = Owners}) ->
|
||||
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
|
||||
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
|
||||
+ Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options}) ->
|
||||
+ Owners = node_owners_call(Type, NodeIdx),
|
||||
case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
+ case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
{result, _} ->
|
||||
{result,
|
||||
@@ -813,7 +601,8 @@
|
||||
[#xmlel{name = 'identity', ns = ?NS_DISCO_INFO,
|
||||
@@ -814,8 +602,9 @@
|
||||
[?NS_PUBSUB_s
|
||||
| [?NS_PUBSUB_s++"#"++Feature || Feature <- features("pep")]];
|
||||
disco_features(#jid{node = U, domain = S, resource = R} = Host, NodeId, From) ->
|
||||
- Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options, owners = Owners}) ->
|
||||
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
|
||||
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
|
||||
+ Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options}) ->
|
||||
+ Owners = node_owners_call(Type, NodeIdx),
|
||||
case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
+ case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
{result, _} ->
|
||||
{result, [?NS_PUBSUB_s
|
||||
@@ -853,7 +642,8 @@
|
||||
| [?NS_PUBSUB_s ++ "#" ++ Feature || Feature <- features("pep")]]};
|
||||
@@ -854,8 +643,9 @@
|
||||
).
|
||||
|
||||
disco_items(#jid{raw = JID, node = U, domain = S, resource = R} = Host, <<>>, From) ->
|
||||
- Action = fun(#pubsub_node{id ={_, NodeId}, options = Options, type = Type, idx = NodeIdx, owners = Owners}, Acc) ->
|
||||
- Action = fun(#pubsub_node{id ={_, NodeId}, options = Options, type = Type, idx = Nidx, owners = Owners}, Acc) ->
|
||||
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
|
||||
+ Action = fun(#pubsub_node{id ={_, NodeId}, options = Options, type = Type, idx = NodeIdx}, Acc) ->
|
||||
+ Owners = node_owners_call(Type, NodeIdx),
|
||||
case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
+ case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
{result, _} ->
|
||||
[#xmlel{name = 'item', ns = ?NS_DISCO_INFO,
|
||||
@@ -867,13 +657,14 @@
|
||||
attrs = [?XMLATTR(<<"jid">>, JID),
|
||||
@@ -868,14 +658,15 @@
|
||||
_ -> Acc
|
||||
end
|
||||
end,
|
||||
@@ -335,13 +368,15 @@
|
||||
end;
|
||||
|
||||
disco_items(#jid{raw = JID, node = U, domain = S, resource = R} = Host, NodeId, From) ->
|
||||
- Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options, owners = Owners}) ->
|
||||
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
|
||||
- case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
|
||||
+ Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options}) ->
|
||||
+ Owners = node_owners_call(Type, NodeIdx),
|
||||
case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
+ case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
{result, Items} ->
|
||||
{result,
|
||||
@@ -982,10 +773,10 @@
|
||||
[#xmlel{name = 'item', ns = ?NS_DISCO_INFO,
|
||||
@@ -983,10 +774,10 @@
|
||||
lists:foreach(fun(PType) ->
|
||||
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]),
|
||||
lists:foreach(fun
|
||||
@@ -354,7 +389,7 @@
|
||||
true ->
|
||||
node_action(Host, PType, unsubscribe_node, [Nidx, Entity, JID, all]);
|
||||
false ->
|
||||
@@ -1183,9 +974,10 @@
|
||||
@@ -1184,9 +975,10 @@
|
||||
end,
|
||||
ejabberd_router:route(To, From, Res);
|
||||
%% Service discovery : disco#items
|
||||
@@ -367,16 +402,21 @@
|
||||
{result, IQRes} ->
|
||||
Result = #xmlel{ns = ?NS_DISCO_ITEMS,
|
||||
name = 'query',
|
||||
@@ -1335,7 +1127,7 @@
|
||||
@@ -1332,11 +1124,11 @@
|
||||
).
|
||||
|
||||
node_disco_info(Host, NodeId, From) ->
|
||||
- Action = fun(#pubsub_node{type = Plugin, idx = Nidx}) ->
|
||||
+ Action = fun(#pubsub_node{type = Plugin, idx = NodeIdx}) ->
|
||||
Types = case tree_call(Host, get_subnodes, [Host, NodeId, From]) of
|
||||
[] -> ["leaf"];
|
||||
_ ->
|
||||
- case node_call(Plugin, get_items, [NodeIdx, From]) of
|
||||
- case node_call(Plugin, get_items, [Nidx, From]) of
|
||||
+ case node_call(Plugin, get_items, [NodeIdx, From, none]) of
|
||||
{result, []} -> ["collection"];
|
||||
{result, _} -> ["leaf", "collection"];
|
||||
_ -> []
|
||||
@@ -1353,10 +1145,15 @@
|
||||
@@ -1354,10 +1146,15 @@
|
||||
[#xmlel{ns = ?NS_DISCO_INFO,
|
||||
name = 'feature',
|
||||
attrs = [?XMLATTR(<<"var">>, ?NS_PUBSUB_b)]} |
|
||||
@@ -396,7 +436,7 @@
|
||||
end,
|
||||
case transaction(Host, NodeId, Action, sync_dirty) of
|
||||
{result, {_, Result}} -> {result, Result};
|
||||
@@ -1400,10 +1197,10 @@
|
||||
@@ -1401,10 +1198,10 @@
|
||||
name = 'feature',
|
||||
attrs = [?XMLATTR(<<"var">>, ?NS_VCARD_b)]}]
|
||||
++
|
||||
@@ -411,7 +451,7 @@
|
||||
iq_disco_info(Host, NodeId, From, _Lang)
|
||||
when NodeId == ?NS_ADHOC_b orelse NodeId == ?NS_PUBSUB_GET_PENDING_b ->
|
||||
command_disco_info(Host, NodeId, From);
|
||||
@@ -1411,16 +1208,17 @@
|
||||
@@ -1412,16 +1209,17 @@
|
||||
node_disco_info(Host, NodeId, From).
|
||||
|
||||
|
||||
@@ -432,7 +472,7 @@
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
@@ -1437,7 +1235,7 @@
|
||||
@@ -1438,7 +1236,7 @@
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
@@ -441,7 +481,7 @@
|
||||
%% TODO: support localization of this string
|
||||
{result,
|
||||
[#xmlel{ns = ?NS_DISCO_ITEMS,
|
||||
@@ -1445,14 +1243,15 @@
|
||||
@@ -1446,14 +1244,15 @@
|
||||
attrs = [?XMLATTR(<<"jid">>, Host),
|
||||
?XMLATTR(<<"node">>, ?NS_PUBSUB_GET_PENDING_b),
|
||||
?XMLATTR(<<"name">>, "Get Pending")]}]};
|
||||
@@ -450,8 +490,8 @@
|
||||
%% TODO
|
||||
{result, []};
|
||||
-iq_disco_items(Host, NodeId, From) ->
|
||||
- Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options, owners = Owners}) ->
|
||||
- NodeItems = case get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) of
|
||||
- Action = fun(#pubsub_node{idx = Nidx, type = Type, options = Options, owners = Owners}) ->
|
||||
- NodeItems = case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of
|
||||
+iq_disco_items(Host, NodeId, From, Rsm) ->
|
||||
+ Action = fun(#pubsub_node{idx = NodeIdx, type = Type, options = Options}) ->
|
||||
+ Owners = node_owners_call(Type, NodeIdx),
|
||||
@@ -462,7 +502,7 @@
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{id = {_, SubNodeId}, options = SubOptions}) ->
|
||||
@@ -1475,7 +1274,7 @@
|
||||
@@ -1476,7 +1275,7 @@
|
||||
attrs = [?XMLATTR(<<"jid">>, Host),
|
||||
?XMLATTR(<<"name">>, Name)]}
|
||||
end, NodeItems),
|
||||
@@ -471,7 +511,7 @@
|
||||
end,
|
||||
case transaction(Host, NodeId, Action, sync_dirty) of
|
||||
{result, {_, Result}} -> {result, Result};
|
||||
@@ -1484,12 +1283,6 @@
|
||||
@@ -1485,12 +1284,6 @@
|
||||
|
||||
|
||||
|
||||
@@ -484,7 +524,7 @@
|
||||
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) ->
|
||||
if (AccessModel == presence) or (AccessModel == roster) ->
|
||||
case Host of
|
||||
@@ -1645,7 +1438,7 @@
|
||||
@@ -1646,7 +1439,7 @@
|
||||
end;
|
||||
(_, Acc) -> Acc
|
||||
end, [], exmpp_xml:remove_cdata_from_list(SubEls)),
|
||||
@@ -493,7 +533,7 @@
|
||||
{'get', 'subscriptions'} ->
|
||||
get_subscriptions(Host, NodeId, From, Plugins);
|
||||
{'get', 'affiliations'} ->
|
||||
@@ -1836,7 +1629,8 @@
|
||||
@@ -1837,7 +1630,8 @@
|
||||
_ -> []
|
||||
end
|
||||
end,
|
||||
@@ -503,7 +543,7 @@
|
||||
sync_dirty) of
|
||||
{result, Res} -> Res;
|
||||
Err -> Err
|
||||
@@ -1880,7 +1674,7 @@
|
||||
@@ -1881,7 +1675,7 @@
|
||||
|
||||
%%% authorization handling
|
||||
|
||||
@@ -512,7 +552,7 @@
|
||||
Lang = <<"en">>, %% TODO fix
|
||||
{U, S, R} = Subscriber,
|
||||
Stanza = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', children =
|
||||
@@ -1910,7 +1704,7 @@
|
||||
@@ -1911,7 +1705,7 @@
|
||||
lists:foreach(fun(Owner) ->
|
||||
{U, S, R} = Owner,
|
||||
ejabberd_router:route(service_jid(Host), exmpp_jid:make(U, S, R), Stanza)
|
||||
@@ -521,7 +561,7 @@
|
||||
|
||||
find_authorization_response(Packet) ->
|
||||
Els = Packet#xmlel.children,
|
||||
@@ -1969,8 +1763,8 @@
|
||||
@@ -1970,8 +1764,8 @@
|
||||
"true" -> true;
|
||||
_ -> false
|
||||
end,
|
||||
@@ -532,7 +572,7 @@
|
||||
{result, Subscriptions} = node_call(Type, get_subscriptions, [Nidx, Subscriber]),
|
||||
if
|
||||
not IsApprover ->
|
||||
@@ -2167,7 +1961,7 @@
|
||||
@@ -2168,7 +1962,7 @@
|
||||
end,
|
||||
Reply = #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children =
|
||||
[#xmlel{ns = ?NS_PUBSUB, name = 'create', attrs = nodeAttr(Node)}]},
|
||||
@@ -540,8 +580,8 @@
|
||||
+ case transaction(Host, CreateNode, transaction) of
|
||||
{result, {NodeId, {Result, broadcast}}} ->
|
||||
broadcast_created_node(Host, Node, NodeId, Type, NodeOptions),
|
||||
case Result of
|
||||
@@ -2269,7 +2063,7 @@
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
@@ -2285,7 +2079,7 @@
|
||||
%%<li>The node does not exist.</li>
|
||||
%%</ul>
|
||||
subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
@@ -550,7 +590,7 @@
|
||||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -2279,7 +2073,7 @@
|
||||
@@ -2295,7 +2089,7 @@
|
||||
_:_ ->
|
||||
{undefined, undefined, undefined}
|
||||
end,
|
||||
@@ -559,7 +599,7 @@
|
||||
Features = features(Type),
|
||||
SubscribeFeature = lists:member("subscribe", Features),
|
||||
OptionsFeature = lists:member("subscription-options", Features),
|
||||
@@ -2288,6 +2082,7 @@
|
||||
@@ -2304,6 +2098,7 @@
|
||||
AccessModel = get_option(Options, access_model),
|
||||
SendLast = get_option(Options, send_last_published_item),
|
||||
AllowedGroups = get_option(Options, roster_groups_allowed, []),
|
||||
@@ -567,7 +607,21 @@
|
||||
{PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups),
|
||||
if
|
||||
not SubscribeFeature ->
|
||||
@@ -2633,7 +2428,7 @@
|
||||
@@ -2433,12 +2228,9 @@
|
||||
Features = features(Type),
|
||||
PublishFeature = lists:member("publish", Features),
|
||||
PublishModel = get_option(Options, publish_model),
|
||||
+ MaxItems = max_items(Host, Options),
|
||||
DeliverPayloads = get_option(Options, deliver_payloads),
|
||||
PersistItems = get_option(Options, persist_items),
|
||||
- MaxItems = case PersistItems of
|
||||
- false -> 0;
|
||||
- true -> max_items(Host, Options)
|
||||
- end,
|
||||
{PayloadCount, PayloadNS} = payload_els_ns(Payload),
|
||||
PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2
|
||||
PayloadMaxSize = get_option(Options, max_payload_size),
|
||||
@@ -2661,7 +2453,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.
|
||||
@@ -576,7 +630,7 @@
|
||||
MaxItems =
|
||||
if
|
||||
SMaxItems == "" -> get_max_items_node(Host);
|
||||
@@ -2647,12 +2442,13 @@
|
||||
@@ -2675,12 +2467,13 @@
|
||||
{error, Error} ->
|
||||
{error, Error};
|
||||
_ ->
|
||||
@@ -591,7 +645,7 @@
|
||||
{PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
|
||||
if
|
||||
not RetreiveFeature ->
|
||||
@@ -2665,11 +2461,11 @@
|
||||
@@ -2693,11 +2486,11 @@
|
||||
node_call(Type, get_items,
|
||||
[Nidx, From,
|
||||
AccessModel, PresenceSubscription, RosterGroup,
|
||||
@@ -605,7 +659,7 @@
|
||||
SendItems = case ItemIds of
|
||||
[] ->
|
||||
Items;
|
||||
@@ -2682,7 +2478,7 @@
|
||||
@@ -2710,7 +2503,7 @@
|
||||
%% number of items sent to MaxItems:
|
||||
{result, #xmlel{ns = ?NS_PUBSUB, name = 'pubsub', children =
|
||||
[#xmlel{ns = ?NS_PUBSUB, name = 'items', attrs = nodeAttr(Node), children =
|
||||
@@ -614,7 +668,27 @@
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
@@ -2723,6 +2519,17 @@
|
||||
@@ -2726,8 +2519,8 @@
|
||||
).
|
||||
|
||||
get_items(Host, NodeId) ->
|
||||
- Action = fun(#pubsub_node{type = Type, idx = Nidx}) ->
|
||||
- node_call(Type, get_items, [Nidx, service_jid(Host)])
|
||||
+ Action = fun(#pubsub_node{type = Type, idx = NodeIdx}) ->
|
||||
+ node_call(Type, get_items, [NodeIdx, service_jid(Host)])
|
||||
end,
|
||||
case transaction(Host, NodeId, Action, sync_dirty) of
|
||||
{result, {_, Items}} -> Items
|
||||
@@ -2744,13 +2537,24 @@
|
||||
).
|
||||
|
||||
get_item(Host, NodeId, ItemId) ->
|
||||
- Action = fun(#pubsub_node{type = Type, idx = Nidx}) ->
|
||||
- node_call(Type, get_item, [Nidx, ItemId])
|
||||
+ Action = fun(#pubsub_node{type = Type, idx = NodeIdx}) ->
|
||||
+ node_call(Type, get_item, [NodeIdx, ItemId])
|
||||
end,
|
||||
case transaction(Host, NodeId, Action, sync_dirty) of
|
||||
{result, {_, Items}} -> Items;
|
||||
Error -> Error
|
||||
end.
|
||||
@@ -632,14 +706,16 @@
|
||||
|
||||
%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any()
|
||||
%% Host = pubsubHost()
|
||||
@@ -2734,16 +2541,29 @@
|
||||
@@ -2761,32 +2565,32 @@
|
||||
%% Number = last | integer()
|
||||
%% @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') ->
|
||||
- case get_cached_item(Host, NodeId) of
|
||||
-send_items(Host, Node, Nidx, Type, {U,S,R} = LJID, 'last') ->
|
||||
- case get_cached_item(Host, Nidx) of
|
||||
+send_items(Host, Node, NodeId, Type, LJID, 'last') ->
|
||||
+ Stanza = case get_cached_item(Host, NodeId) of
|
||||
undefined ->
|
||||
- send_items(Host, Node, NodeId, Type, LJID, 1);
|
||||
- send_items(Host, Node, Nidx, Type, LJID, 1);
|
||||
+ % 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]} ->
|
||||
@@ -659,16 +735,55 @@
|
||||
+ event_stanza_with_delay(
|
||||
[#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = nodeAttr(Node),
|
||||
- children = itemsEls([LastItem])}], ModifNow, ModifUSR),
|
||||
- ejabberd_router:route(service_jid(Host), exmpp_jid:make(LJID), Stanza)
|
||||
- case is_tuple(Host) of
|
||||
- false ->
|
||||
- ejabberd_router:route(service_jid(Host), exmpp_jid:make(U, S, R), Stanza);
|
||||
- true ->
|
||||
- case ejabberd_sm:get_session_pid(U,S,R) of
|
||||
- C2SPid when is_pid(C2SPid) ->
|
||||
- ejabberd_c2s:broadcast(C2SPid,
|
||||
- {pep_message, << Node/binary, <<"+notify">>/binary >>},
|
||||
- _Sender = service_jid(Host),
|
||||
- Stanza);
|
||||
- _ ->
|
||||
- ok
|
||||
- end
|
||||
- end
|
||||
- end;
|
||||
-send_items(Host, Node, Nidx, Type, {U,S,R} = LJID, Number) ->
|
||||
- ToSend = case node_action(Host, Type, get_items, [Nidx, LJID]) of
|
||||
+ children = itemsEls([LastItem])}], ModifNow, ModifUSR)
|
||||
+ end,
|
||||
+ {U, S, R} = LJID,
|
||||
+ ejabberd_router:route(service_jid(Host), exmpp_jid:make(U, S, R), Stanza);
|
||||
send_items(Host, Node, NodeId, Type, {LU, LS, LR} = LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
+send_items(Host, Node, NodeId, Type, {LU, LS, LR} = LJID, Number) ->
|
||||
+ ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
{result, []} ->
|
||||
@@ -2870,7 +2690,8 @@
|
||||
[];
|
||||
{result, Items} ->
|
||||
@@ -2808,20 +2612,7 @@
|
||||
[#xmlel{ns = ?NS_PUBSUB_EVENT, name = 'items', attrs = nodeAttr(Node), children =
|
||||
itemsEls(ToSend)}])
|
||||
end,
|
||||
- case is_tuple(Host) of
|
||||
- false ->
|
||||
- ejabberd_router:route(service_jid(Host), exmpp_jid:make(U, S, R), Stanza);
|
||||
- true ->
|
||||
- case ejabberd_sm:get_session_pid(U,S,R) of
|
||||
- C2SPid when is_pid(C2SPid) ->
|
||||
- ejabberd_c2s:broadcast(C2SPid,
|
||||
- {pep_message, << Node/binary, <<"+notify">>/binary >>},
|
||||
- _Sender = service_jid(Host),
|
||||
- Stanza);
|
||||
- _ ->
|
||||
- ok
|
||||
- end
|
||||
- end.
|
||||
+ ejabberd_router:route(service_jid(Host), exmpp_jid:make(LU, LS, LR), Stanza).
|
||||
|
||||
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
|
||||
%% Host = host()
|
||||
@@ -2924,7 +2715,8 @@
|
||||
error ->
|
||||
{error, 'bad-request'};
|
||||
_ ->
|
||||
@@ -678,7 +793,7 @@
|
||||
case lists:member(Owner, Owners) of
|
||||
true ->
|
||||
OwnerJID = exmpp_jid:make(Owner),
|
||||
@@ -2880,24 +2701,8 @@
|
||||
@@ -2934,24 +2726,8 @@
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({JID, Affiliation}) ->
|
||||
@@ -705,11 +820,40 @@
|
||||
end, FilteredEntities),
|
||||
{result, []};
|
||||
_ ->
|
||||
@@ -2952,9 +2757,9 @@
|
||||
@@ -2980,7 +2756,7 @@
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
read_sub(Subscriber, Node, NodeId, SubId, Lang) ->
|
||||
- case pubsub_subscription:get_subscription(Subscriber, NodeId, SubId) of
|
||||
-get_options_helper(JID, Lang, Node, Nidx, SubId, Type) ->
|
||||
+get_options_helper(JID, Lang, Node, NodeId, SubId, Type) ->
|
||||
Subscriber = try exmpp_jid:parse(JID) of
|
||||
J -> jlib:short_jid(J)
|
||||
catch
|
||||
@@ -2988,7 +2764,7 @@
|
||||
exmpp_jid:make("", "", "") %% TODO, check if use <<>> instead of ""
|
||||
end,
|
||||
{result, Subs} = node_call(Type, get_subscriptions,
|
||||
- [Nidx, Subscriber]),
|
||||
+ [NodeId, Subscriber]),
|
||||
SubIds = lists:foldl(fun({subscribed, SID}, Acc) ->
|
||||
[SID | Acc];
|
||||
(_, Acc) ->
|
||||
@@ -2998,17 +2774,17 @@
|
||||
{_, []} ->
|
||||
{error, extended_error('not-acceptable', "not-subscribed")};
|
||||
{[], [SID]} ->
|
||||
- read_sub(Subscriber, Node, Nidx, SID, Lang);
|
||||
+ read_sub(Subscriber, Node, NodeId, SID, Lang);
|
||||
{[], _} ->
|
||||
{error, extended_error('not-acceptable', "subid-required")};
|
||||
{_, _} ->
|
||||
- read_sub(Subscriber, Node, Nidx, SubId, Lang)
|
||||
+ read_sub(Subscriber, Node, NodeId, SubId, Lang)
|
||||
end.
|
||||
|
||||
-read_sub(Subscriber, Node, Nidx, SubId, Lang) ->
|
||||
- case pubsub_subscription:get_subscription(Subscriber, Nidx, SubId) of
|
||||
+read_sub(Subscriber, Node, NodeId, SubId, Lang) ->
|
||||
+ case pubsub_subscription_odbc:get_subscription(Subscriber, NodeId, SubId) of
|
||||
{result, #pubsub_subscription{options = Options}} ->
|
||||
- {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options),
|
||||
@@ -717,25 +861,53 @@
|
||||
OptionsEl = #xmlel{ns = ?NS_PUBSUB, name = 'options',
|
||||
attrs = [ ?XMLATTR(<<"jid">>, exmpp_jid:to_binary(Subscriber)),
|
||||
?XMLATTR(<<"subid">>, SubId) | nodeAttr(Node)],
|
||||
@@ -2987,7 +2792,7 @@
|
||||
@@ -3040,8 +2816,8 @@
|
||||
Error -> Error
|
||||
end.
|
||||
|
||||
set_options_helper(Configuration, JID, NodeId, SubId, Type) ->
|
||||
-set_options_helper(Configuration, JID, Nidx, SubId, Type) ->
|
||||
- SubOpts = case pubsub_subscription:parse_options_xform(Configuration) of
|
||||
+set_options_helper(Configuration, JID, NodeId, SubId, Type) ->
|
||||
+ SubOpts = case pubsub_subscription_odbc:parse_options_xform(Configuration) of
|
||||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -3019,7 +2824,7 @@
|
||||
write_sub(_Subscriber, _NodeID, _SubID, []) ->
|
||||
@@ -3051,7 +2827,7 @@
|
||||
_ -> exmpp_jid:make("", "", "") %% TODO, check if use <<>> instead of ""
|
||||
end,
|
||||
{result, Subs} = node_call(Type, get_subscriptions,
|
||||
- [Nidx, Subscriber]),
|
||||
+ [NodeId, Subscriber]),
|
||||
SubIds = lists:foldl(fun({subscribed, SID}, Acc) ->
|
||||
[SID | Acc];
|
||||
(_, Acc) ->
|
||||
@@ -3061,19 +2837,19 @@
|
||||
{_, []} ->
|
||||
{error, extended_error('not-acceptable', "not-subscribed")};
|
||||
{[], [SID]} ->
|
||||
- write_sub(Subscriber, Nidx, SID, SubOpts);
|
||||
+ write_sub(Subscriber, NodeId, SID, SubOpts);
|
||||
{[], _} ->
|
||||
{error, extended_error('not-acceptable', "subid-required")};
|
||||
{_, _} ->
|
||||
- write_sub(Subscriber, Nidx, SubId, SubOpts)
|
||||
+ write_sub(Subscriber, NodeId, SubId, SubOpts)
|
||||
end.
|
||||
|
||||
-write_sub(_Subscriber, _Nidx, _SubId, invalid) ->
|
||||
+write_sub(_Subscriber, _NodeId, _SubId, invalid) ->
|
||||
{error, extended_error('bad-request', "invalid-options")};
|
||||
-write_sub(_Subscriber, _Nidx, _SubId, []) ->
|
||||
+write_sub(_Subscriber, _NodeID, _SubID, []) ->
|
||||
{result, []};
|
||||
write_sub(Subscriber, NodeId, SubId, Options) ->
|
||||
- case pubsub_subscription:set_subscription(Subscriber, NodeId, SubId, Options) of
|
||||
-write_sub(Subscriber, Nidx, SubId, Options) ->
|
||||
- case pubsub_subscription:set_subscription(Subscriber, Nidx, SubId, Options) of
|
||||
+write_sub(Subscriber, NodeId, SubId, Options) ->
|
||||
+ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeId, SubId, Options) of
|
||||
{result, _} ->
|
||||
{result, []};
|
||||
{error, _} ->
|
||||
@@ -3193,8 +2998,8 @@
|
||||
@@ -3247,8 +3023,8 @@
|
||||
?XMLATTR(<<"subsription">>, subscription_to_string(Sub)) | nodeAttr(Node)]}]}]},
|
||||
ejabberd_router:route(service_jid(Host), JID, Stanza)
|
||||
end,
|
||||
@@ -746,7 +918,7 @@
|
||||
true ->
|
||||
Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
|
||||
|
||||
@@ -3548,7 +3353,7 @@
|
||||
@@ -3602,7 +3378,7 @@
|
||||
Collection = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]),
|
||||
{result, [{Depth, [{N, sub_with_options(N)} || N <- Nodes]} || {Depth, Nodes} <- Collection]}
|
||||
end,
|
||||
@@ -755,18 +927,33 @@
|
||||
{result, CollSubs} -> subscribed_nodes_by_jid(NotifyType, CollSubs);
|
||||
_ -> []
|
||||
end.
|
||||
@@ -3617,8 +3422,8 @@
|
||||
@@ -3660,19 +3436,19 @@
|
||||
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
|
||||
JIDSubs.
|
||||
|
||||
-sub_with_options(#pubsub_node{type = Type, idx = Nidx}) ->
|
||||
- case node_call(Type, get_node_subscriptions, [Nidx]) of
|
||||
+sub_with_options(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
+ case node_call(Type, get_node_subscriptions, [NodeId]) of
|
||||
{result, Subs} ->
|
||||
lists:foldl(
|
||||
- fun({JID, subscribed, SubId}, Acc) -> [sub_with_options(JID, Nidx, SubId) | Acc];
|
||||
+ fun({JID, subscribed, SubId}, Acc) -> [sub_with_options(JID, NodeId, SubId) | Acc];
|
||||
(_, Acc) -> Acc
|
||||
end, [], Subs);
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
sub_with_options(JID, NodeId, SubId) ->
|
||||
- case pubsub_subscription:read_subscription(JID, NodeId, SubId) of
|
||||
-sub_with_options(JID, Nidx, SubId) ->
|
||||
- case pubsub_subscription:read_subscription(JID, Nidx, SubId) of
|
||||
- #pubsub_subscription{options = Options} -> {JID, SubId, Options};
|
||||
+sub_with_options(JID, NodeId, SubId) ->
|
||||
+ case pubsub_subscription_odbc:read_subscription(JID, NodeId, SubId) of
|
||||
+ {result, #pubsub_subscription{options = Options}} -> {JID, SubId, Options};
|
||||
_ -> {JID, SubId, []}
|
||||
end.
|
||||
|
||||
@@ -3730,6 +3535,30 @@
|
||||
@@ -3784,6 +3560,30 @@
|
||||
Result
|
||||
end.
|
||||
|
||||
@@ -797,7 +984,7 @@
|
||||
%% @spec (Host, Options) -> MaxItems
|
||||
%% Host = host()
|
||||
%% Options = [Option]
|
||||
@@ -4253,9 +4082,14 @@
|
||||
@@ -4307,9 +4107,14 @@
|
||||
|
||||
tree_action(Host, Function, Args) ->
|
||||
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
|
||||
@@ -815,7 +1002,7 @@
|
||||
|
||||
%% @doc <p>node plugin call.</p>
|
||||
-spec(node_call/3 ::
|
||||
@@ -4293,7 +4127,7 @@
|
||||
@@ -4347,7 +4152,7 @@
|
||||
|
||||
node_action(Host, Type, Function, Args) ->
|
||||
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
|
||||
@@ -824,7 +1011,7 @@
|
||||
node_call(Type, Function, Args)
|
||||
end, sync_dirty).
|
||||
|
||||
@@ -4308,7 +4142,7 @@
|
||||
@@ -4362,7 +4167,7 @@
|
||||
).
|
||||
|
||||
transaction(Host, NodeId, Action, Trans) ->
|
||||
@@ -833,7 +1020,7 @@
|
||||
case tree_call(Host, get_node, [Host, NodeId]) of
|
||||
#pubsub_node{} = Node ->
|
||||
case Action(Node) of
|
||||
@@ -4322,7 +4156,7 @@
|
||||
@@ -4376,7 +4181,7 @@
|
||||
end, Trans).
|
||||
|
||||
|
||||
@@ -842,7 +1029,7 @@
|
||||
(
|
||||
Host :: string() | host(),
|
||||
Action :: fun(),
|
||||
@@ -4330,21 +4164,28 @@
|
||||
@@ -4384,21 +4189,28 @@
|
||||
-> {'result', Nodes :: [] | [Node::pubsubNode()]}
|
||||
).
|
||||
|
||||
@@ -876,7 +1063,7 @@
|
||||
{result, Result} -> {result, Result};
|
||||
{error, Error} -> {error, Error};
|
||||
{atomic, {result, Result}} -> {result, Result};
|
||||
@@ -4352,6 +4193,15 @@
|
||||
@@ -4406,6 +4218,15 @@
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
|
||||
{error, 'internal-server-error'};
|
||||
@@ -892,7 +1079,7 @@
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
|
||||
{error, 'internal-server-error'};
|
||||
@@ -4360,6 +4210,16 @@
|
||||
@@ -4414,6 +4235,16 @@
|
||||
{error, 'internal-server-error'}
|
||||
end.
|
||||
|
||||
|
||||
@@ -40,8 +40,9 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_INBAND_REGISTER,
|
||||
?MODULE, process_iq, IQDisc),
|
||||
|
||||
+14
-11
@@ -134,26 +134,27 @@
|
||||
%% Opts = list()
|
||||
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, HostB,
|
||||
rosteritem, [{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{attributes, record_info(fields, rosteritem)},
|
||||
{types, [{user_host_jid, {text, text, ljid}},
|
||||
{subscription, atom},
|
||||
{ask, atom}]}]),
|
||||
gen_storage:create_table(Backend, HostB,
|
||||
rostergroup, [{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, rostergroup)},
|
||||
{types, [{user_host_jid, {text, text, ljid}}]}]),
|
||||
mnesia:create_table(roster_version, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, roster_version)}]),
|
||||
update_table(Host, Backend),
|
||||
update_table(HostB, Backend),
|
||||
mnesia:add_table_index(roster, us),
|
||||
mnesia:add_table_index(roster_version, us),
|
||||
ejabberd_hooks:add(roster_get, HostB,
|
||||
@@ -383,7 +384,7 @@ storageroster_to_roster(#rosteritem{user_host_jid = {U, S, JID} = USJ,
|
||||
fun(#rostergroup{user_host_jid = USJ1, grp = G}, R)
|
||||
when USJ =:= USJ1 ->
|
||||
%% G is a string when using odbc beckend, and a binary when using mnesia
|
||||
GString = convert_to_string(G),
|
||||
GString = convert_to_binary(G),
|
||||
[GString | R];
|
||||
(_, R) ->
|
||||
R
|
||||
@@ -391,14 +392,14 @@ storageroster_to_roster(#rosteritem{user_host_jid = {U, S, JID} = USJ,
|
||||
#roster{usj = {U, S, JID},
|
||||
us = US,
|
||||
jid = JID,
|
||||
name = convert_to_string(Name),
|
||||
name = convert_to_binary(Name),
|
||||
subscription = Subscription,
|
||||
ask = Ask,
|
||||
askmessage = AskMessage,
|
||||
groups = Groups}.
|
||||
|
||||
convert_to_string(A) when is_binary(A) -> binary_to_list(A);
|
||||
convert_to_string(A) when is_list(A) -> A.
|
||||
convert_to_binary(A) when is_list(A) -> list_to_binary(A);
|
||||
convert_to_binary(A) when is_binary(A) -> A.
|
||||
|
||||
%% @spec (Item) -> XML
|
||||
%% Item = rosteritem()
|
||||
@@ -1228,10 +1229,12 @@ get_jid_info(_, User, Server, JID)
|
||||
|
||||
%% Only supports migration from ejabberd 1.1.2 or higher.
|
||||
|
||||
update_table(Host, mnesia) ->
|
||||
HostB = list_to_binary(Host),
|
||||
update_table(global, Storage) ->
|
||||
[update_table(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_table(HostB, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, rosteritem,
|
||||
HostB, rosteritem,
|
||||
[{roster, [usj, us, jid, name, subscription, ask, groups, askmessage, xs],
|
||||
fun({roster, USJ, _, _, Name, Subscription, Ask, Groups, AskMessage, _Xs}) ->
|
||||
%% Convert "" to undefined in JIDs and string() to binary().
|
||||
|
||||
+35
-18
@@ -63,8 +63,9 @@
|
||||
-record(sr_group, {group_host, opts}).
|
||||
-record(sr_user, {us, group_host}).
|
||||
|
||||
start(Host, _Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, _Opts) ->
|
||||
mnesia:create_table(sr_group,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, sr_group)}]),
|
||||
@@ -145,9 +146,11 @@ get_user_roster(Items, US) ->
|
||||
fun(Group, Acc1) ->
|
||||
GroupName = get_group_name(S, Group),
|
||||
lists:foldl(
|
||||
fun(UserServer, Acc2) ->
|
||||
fun({User, Server} = UserServer, Acc2) ->
|
||||
if UserServer == US -> Acc2;
|
||||
true -> dict:append(UserServer,
|
||||
true ->
|
||||
UserServerB = {list_to_binary(User), list_to_binary(Server)},
|
||||
dict:append(UserServerB,
|
||||
GroupName,
|
||||
Acc2)
|
||||
end
|
||||
@@ -172,8 +175,8 @@ get_user_roster(Items, US) ->
|
||||
|
||||
%% Export items in roster format:
|
||||
ModVcard = get_vcard_module(S),
|
||||
SRItems = [#roster{usj = {U, S, {U1, S1, undefined}},
|
||||
us = US,
|
||||
SRItems = [ #roster{usj = {U, S, {U1, S1, undefined}},
|
||||
us = {U, S},
|
||||
jid = {U1, S1, undefined},
|
||||
name = get_rosteritem_name(ModVcard, U1, S1),
|
||||
subscription = both,
|
||||
@@ -332,7 +335,7 @@ get_subscription_lists({F, T}, User, Server)
|
||||
fun(Group) ->
|
||||
get_group_users(LServer, Group)
|
||||
end, DisplayedGroups)),
|
||||
SRJIDs = [{U1, S1, undefined} || {U1, S1} <- SRUsers],
|
||||
SRJIDs = [{list_to_binary(U1), list_to_binary(S1), undefined} || {U1, S1} <- SRUsers],
|
||||
{lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}
|
||||
catch
|
||||
_ ->
|
||||
@@ -493,7 +496,8 @@ get_user_groups(US) ->
|
||||
[]
|
||||
end ++ get_special_users_groups(Host).
|
||||
|
||||
is_group_enabled(Host, Group) ->
|
||||
is_group_enabled(Host1, Group1) ->
|
||||
{Host, Group} = split_grouphost(Host1, Group1),
|
||||
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
|
||||
[#sr_group{opts = Opts}] ->
|
||||
not lists:member(disabled, Opts);
|
||||
@@ -524,7 +528,8 @@ get_online_users(Host) ->
|
||||
get_group_users(HostB, Group) when is_binary(HostB) ->
|
||||
get_group_users(binary_to_list(HostB), Group);
|
||||
|
||||
get_group_users(Host, Group) ->
|
||||
get_group_users(Host1, Group1) ->
|
||||
{Host, Group} = split_grouphost(Host1, Group1),
|
||||
case get_group_opt(Host, Group, all_users, false) of
|
||||
true ->
|
||||
ejabberd_auth:get_vh_registered_users(Host);
|
||||
@@ -570,7 +575,8 @@ get_group_explicit_users(Host, Group) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_group_name(Host, Group) ->
|
||||
get_group_name(Host1, Group1) ->
|
||||
{Host, Group} = split_grouphost(Host1, Group1),
|
||||
get_group_opt(Host, Group, name, Group).
|
||||
|
||||
%% @spec(Host::string)
|
||||
@@ -646,10 +652,10 @@ get_user_displayed_groups(US) ->
|
||||
end, get_user_groups(US))),
|
||||
[Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].
|
||||
|
||||
is_user_in_group({_U, S} = US, Group, Host) ->
|
||||
is_user_in_group(US, Group, Host) ->
|
||||
case catch mnesia:dirty_match_object(
|
||||
#sr_user{us=US, group_host={Group, Host}}) of
|
||||
[] -> lists:member(US, get_group_users(S, Group));
|
||||
[] -> lists:member(US, get_group_users(Host, Group));
|
||||
_ -> true
|
||||
end.
|
||||
|
||||
@@ -661,14 +667,15 @@ add_user_to_group(Host, {LUser, LServer} = US, Group) ->
|
||||
case re:run(LUser, "^@.+@$", [{capture, none}]) of
|
||||
match ->
|
||||
GroupOpts = mod_shared_roster:get_group_opts(Host, Group),
|
||||
AllUsersOpt =
|
||||
case LUser == "@all@" of
|
||||
true -> [{all_users, true}];
|
||||
false -> []
|
||||
MoreGroupOpts =
|
||||
case LUser of
|
||||
"@all@" -> [{all_users, true}];
|
||||
"@online@" -> [{online_users, true}];
|
||||
_ -> []
|
||||
end,
|
||||
mod_shared_roster:set_group_opts(
|
||||
Host, Group,
|
||||
GroupOpts ++ AllUsersOpt);
|
||||
GroupOpts ++ MoreGroupOpts);
|
||||
nomatch ->
|
||||
%% Push this new user to members of groups where this group is displayed
|
||||
push_user_to_displayed(LUser, LServer, Group, both),
|
||||
@@ -697,7 +704,9 @@ remove_user_from_group(Host, {LUser, LServer} = US, Group) ->
|
||||
NewGroupOpts =
|
||||
case LUser of
|
||||
"@all@" ->
|
||||
lists:filter(fun(X) -> X/={all_users,true} end, GroupOpts)
|
||||
lists:filter(fun(X) -> X/={all_users,true} end, GroupOpts);
|
||||
"@online@" ->
|
||||
lists:filter(fun(X) -> X/={online_users,true} end, GroupOpts)
|
||||
end,
|
||||
mod_shared_roster:set_group_opts(Host, Group, NewGroupOpts);
|
||||
nomatch ->
|
||||
@@ -1129,3 +1138,11 @@ get_opt(Opts, Opt, Default) ->
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
exmpp_jid:bare_to_list(User, Server).
|
||||
|
||||
split_grouphost(Host, Group) ->
|
||||
case string:tokens(Group, "@") of
|
||||
[GroupName, HostName] ->
|
||||
{HostName, GroupName};
|
||||
[_] ->
|
||||
{Host, Group}
|
||||
end.
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
base,
|
||||
password,
|
||||
uid,
|
||||
deref_aliases,
|
||||
group_attr,
|
||||
group_desc,
|
||||
user_desc,
|
||||
@@ -330,6 +331,7 @@ eldap_search(State, FilterParseArgs, AttributesList) ->
|
||||
[{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, AttributesList}]) of
|
||||
#eldap_search_result{entries = Es} ->
|
||||
%% A result with entries. Return their list.
|
||||
@@ -495,6 +497,17 @@ parse_options(Host, Opts) ->
|
||||
ejabberd_config:get_local_option({ldap_tls_verify, Host});
|
||||
Verify -> Verify
|
||||
end,
|
||||
LDAPTLSCAFile = case gen_mod:get_opt(ldap_tls_cacertfile, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_tls_cacertfile, Host});
|
||||
CAFile -> CAFile
|
||||
end,
|
||||
LDAPTLSDepth = case gen_mod:get_opt(ldap_tls_depth, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_tls_depth, Host});
|
||||
Depth ->
|
||||
Depth
|
||||
end,
|
||||
LDAPPort = case gen_mod:get_opt(ldap_port, Opts, undefined) of
|
||||
undefined ->
|
||||
case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
@@ -631,7 +644,9 @@ parse_options(Host, Opts) ->
|
||||
RF ->
|
||||
RF
|
||||
end,
|
||||
|
||||
lists:foreach(fun eldap_utils:check_filter/1,
|
||||
[ConfigFilter, ConfigUserFilter,
|
||||
ConfigGroupFilter, RosterFilter]),
|
||||
SubFilter = "(&("++UIDAttr++"="++UIDAttrFormat++")("++GroupAttr++"=%g))",
|
||||
UserSubFilter = case ConfigUserFilter of
|
||||
undefined -> eldap_filter:do_sub(SubFilter, [{"%g", "*"}]);
|
||||
@@ -658,17 +673,29 @@ parse_options(Host, Opts) ->
|
||||
"" -> GroupSubFilter;
|
||||
_ -> "(&" ++ GroupSubFilter ++ ConfigFilter ++ ")"
|
||||
end,
|
||||
DerefAliases = case gen_mod:get_opt(deref_aliases, Opts, undefined) of
|
||||
undefined ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{deref_aliases, Host}) of
|
||||
undefined -> never;
|
||||
D -> D
|
||||
end;
|
||||
D -> D
|
||||
end,
|
||||
#state{host = Host,
|
||||
eldap_id = Eldap_ID,
|
||||
servers = LDAPServers,
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
{tls_verify, LDAPTLSVerify},
|
||||
{tls_cacertfile, LDAPTLSCAFile},
|
||||
{tls_depth, LDAPTLSDepth}],
|
||||
dn = RootDN,
|
||||
base = LDAPBase,
|
||||
password = Password,
|
||||
uid = UIDAttr,
|
||||
deref_aliases = DerefAliases,
|
||||
group_attr = GroupAttr,
|
||||
group_desc = GroupDesc,
|
||||
user_desc = UserDesc,
|
||||
|
||||
+3
-2
@@ -38,8 +38,9 @@
|
||||
-include_lib("exmpp/include/exmpp_jid.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
mod_disco:register_feature(HostB, ?NS_SIC_0_s),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_SIC_0_s,
|
||||
|
||||
+10
-8
@@ -35,9 +35,11 @@
|
||||
|
||||
-include_lib("exmpp/include/exmpp.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, list_to_binary(Host), ?NS_STATS_s,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_STATS_s,
|
||||
?MODULE, process_local_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
@@ -155,12 +157,12 @@ get_local_stat(_Server, [], Name) when Name == <<"users/all-hosts/online">> ->
|
||||
end;
|
||||
|
||||
get_local_stat(_Server, [], Name) when Name == <<"users/all-hosts/total">> ->
|
||||
case catch mnesia:table_info(passwd, size) of
|
||||
{'EXIT', _Reason} ->
|
||||
?STATERR(<<"500">>, <<"Internal Server Error">>);
|
||||
Users ->
|
||||
?STATVAL(list_to_binary(integer_to_list(Users)), <<"users">>)
|
||||
end;
|
||||
NumUsers = lists:foldl(
|
||||
fun(Host, Total) ->
|
||||
ejabberd_auth:get_vh_registered_users_number(Host)
|
||||
+ Total
|
||||
end, 0, ejabberd_config:get_global_option(hosts)),
|
||||
?STATVAL(list_to_binary(integer_to_list(NumUsers)), <<"users">>);
|
||||
|
||||
get_local_stat(_Server, _, Name) ->
|
||||
?STATERR(<<"404">>, <<"Not Found">>).
|
||||
|
||||
+5
-3
@@ -39,12 +39,14 @@
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
start(Host, Opts) ->
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
%% TODO: Remove the next two lines once XEP-0090 is Obsolete
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, list_to_binary(Host), ?NS_TIME_OLD,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_TIME_OLD,
|
||||
?MODULE, process_local_iq90, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, list_to_binary(Host), ?NS_TIME,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_TIME,
|
||||
?MODULE, process_local_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
|
||||
+14
-11
@@ -104,20 +104,21 @@
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_vcard).
|
||||
|
||||
start(Host, Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
Backend = gen_mod:get_opt(backend, Opts, mnesia),
|
||||
gen_storage:create_table(Backend, HostB, vcard,
|
||||
[{disc_only_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{attributes, record_info(fields, vcard)},
|
||||
{types, [{user_host, {text, text}}]}]),
|
||||
gen_storage:create_table(Backend, HostB, vcard_search,
|
||||
[{disc_copies, [node()]},
|
||||
{odbc_host, Host},
|
||||
{odbc_host, HostB},
|
||||
{attributes, record_info(fields, vcard_search)},
|
||||
{types, [{user_host, {text, text}}]}]),
|
||||
update_tables(Host, Backend),
|
||||
update_tables(HostB, Backend),
|
||||
gen_storage:add_table_index(HostB, vcard_search, lusername),
|
||||
gen_storage:add_table_index(HostB, vcard_search, lfn),
|
||||
gen_storage:add_table_index(HostB, vcard_search, lfamily),
|
||||
@@ -147,10 +148,10 @@ start(Host, Opts) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_VCARD,
|
||||
?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(disco_sm_features, HostB, ?MODULE, get_sm_features, 50),
|
||||
MyHost = gen_mod:expand_host_name(Host, Opts, "vjud"),
|
||||
MyHost = gen_mod:get_opt_host(HostB, Opts, "vjud.@HOST@"),
|
||||
Search = gen_mod:get_opt(search, Opts, true),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost, Host, Search])).
|
||||
register(gen_mod:get_module_proc(HostB, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost, HostB, Search])).
|
||||
|
||||
|
||||
init(Host, ServerHost, Search) ->
|
||||
@@ -343,7 +344,7 @@ set_vcard(User, Server, VCARD) ->
|
||||
})
|
||||
end,
|
||||
gen_storage:transaction(Server, vcard, F),
|
||||
ejabberd_hooks:run(vcard_set, Server, [LUser, Server, VCARD])
|
||||
ejabberd_hooks:run(vcard_set, Server, [User, Server, VCARD])
|
||||
catch
|
||||
_ ->
|
||||
{error, badarg}
|
||||
@@ -443,9 +444,8 @@ do_route(ServerHost, From, To, Packet) ->
|
||||
Err = exmpp_iq:error(Packet, 'not-allowed'),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
{get, ?NS_DISCO_INFO} ->
|
||||
ServerHostB = list_to_binary(ServerHost),
|
||||
Info = ejabberd_hooks:run_fold(
|
||||
disco_info, ServerHostB, [],
|
||||
disco_info, ServerHost, [],
|
||||
[ServerHost, ?MODULE, <<>>, ""]),
|
||||
Result = #xmlel{ns = ?NS_DISCO_INFO, name = 'query',
|
||||
children = Info ++ [
|
||||
@@ -755,6 +755,9 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
|
||||
%%% Update tables
|
||||
%%%
|
||||
|
||||
update_tables(global, Storage) ->
|
||||
[update_tables(HostB, Storage) || HostB <- ejabberd_hosts:get_hosts(ejabberd)];
|
||||
|
||||
update_tables(Host, mnesia) ->
|
||||
gen_storage_migration:migrate_mnesia(
|
||||
Host, vcard,
|
||||
|
||||
+43
-11
@@ -75,6 +75,7 @@
|
||||
search_fields,
|
||||
search_reported,
|
||||
search_reported_attrs,
|
||||
deref_aliases,
|
||||
matches
|
||||
}).
|
||||
|
||||
@@ -285,9 +286,11 @@ find_ldap_user(User, State) ->
|
||||
VCardAttrs = State#state.vcard_map_attrs,
|
||||
case eldap_filter:parse(RFC2254_Filter, [{"%u", User}]) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID, [{base, Base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, VCardAttrs}]) of
|
||||
case eldap_pool:search(Eldap_ID,
|
||||
[{base, Base},
|
||||
{filter, EldapFilter},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, VCardAttrs}]) of
|
||||
#eldap_search_result{entries = [E | _]} ->
|
||||
E;
|
||||
_ ->
|
||||
@@ -557,10 +560,12 @@ search(State, Data) ->
|
||||
Limit = State#state.matches,
|
||||
ReportedAttrs = State#state.search_reported_attrs,
|
||||
Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs)]),
|
||||
case eldap_pool:search(Eldap_ID, [{base, Base},
|
||||
{filter, Filter},
|
||||
{limit, Limit},
|
||||
{attributes, ReportedAttrs}]) of
|
||||
case eldap_pool:search(Eldap_ID,
|
||||
[{base, Base},
|
||||
{filter, Filter},
|
||||
{limit, Limit},
|
||||
{deref_aliases, State#state.deref_aliases},
|
||||
{attributes, ReportedAttrs}]) of
|
||||
#eldap_search_result{entries = E} ->
|
||||
search_items(E, State);
|
||||
_ ->
|
||||
@@ -645,7 +650,7 @@ find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
parse_options(Host, Opts) ->
|
||||
MyHost = gen_mod:expand_host_name(Host, Opts, "vjud"),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, "vjud.@HOST@"),
|
||||
Search = gen_mod:get_opt(search, Opts, true),
|
||||
Matches = case gen_mod:get_opt(matches, Opts, 30) of
|
||||
infinity -> 0;
|
||||
@@ -672,6 +677,17 @@ parse_options(Host, Opts) ->
|
||||
ejabberd_config:get_local_option({ldap_tls_verify, Host});
|
||||
Verify -> Verify
|
||||
end,
|
||||
LDAPTLSCAFile = case gen_mod:get_opt(ldap_tls_cacertfile, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_tls_cacertfile, Host});
|
||||
CAFile -> CAFile
|
||||
end,
|
||||
LDAPTLSDepth = case gen_mod:get_opt(ldap_tls_depth, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_tls_depth, Host});
|
||||
Depth ->
|
||||
Depth
|
||||
end,
|
||||
LDAPPortTemp = case gen_mod:get_opt(ldap_port, Opts, undefined) of
|
||||
undefined ->
|
||||
ejabberd_config:get_local_option({ldap_port, Host});
|
||||
@@ -721,10 +737,14 @@ parse_options(Host, Opts) ->
|
||||
case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> SubFilter;
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
end;
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
F ->
|
||||
eldap_utils:check_filter(F),
|
||||
"(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
{ok, SearchFilter} = eldap_filter:parse(
|
||||
eldap_filter:do_sub(UserFilter, [{"%u","*"}])),
|
||||
@@ -745,6 +765,15 @@ parse_options(Host, Opts) ->
|
||||
_ -> []
|
||||
end
|
||||
end, SearchReported) ++ UIDAttrs),
|
||||
DerefAliases = case gen_mod:get_opt(deref_aliases, Opts, undefined) of
|
||||
undefined ->
|
||||
case ejabberd_config:get_local_option(
|
||||
{deref_aliases, Host}) of
|
||||
undefined -> never;
|
||||
D -> D
|
||||
end;
|
||||
D -> D
|
||||
end,
|
||||
#state{serverhost = Host,
|
||||
myhost = MyHost,
|
||||
eldap_id = Eldap_ID,
|
||||
@@ -753,7 +782,9 @@ parse_options(Host, Opts) ->
|
||||
backups = LDAPBackups,
|
||||
port = LDAPPort,
|
||||
tls_options = [{encrypt, LDAPEncrypt},
|
||||
{tls_verify, LDAPTLSVerify}],
|
||||
{tls_verify, LDAPTLSVerify},
|
||||
{tls_cacertfile, LDAPTLSCAFile},
|
||||
{tls_depth, LDAPTLSDepth}],
|
||||
dn = RootDN,
|
||||
base = LDAPBase,
|
||||
password = Password,
|
||||
@@ -765,5 +796,6 @@ parse_options(Host, Opts) ->
|
||||
search_fields = SearchFields,
|
||||
search_reported = SearchReported,
|
||||
search_reported_attrs = SearchReportedAttrs,
|
||||
deref_aliases = DerefAliases,
|
||||
matches = Matches
|
||||
}.
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
|
||||
start(Host, _Opts) ->
|
||||
HostB = list_to_binary(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, _Opts) ->
|
||||
mnesia:create_table(vcard_xupdate,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, vcard_xupdate)}]),
|
||||
@@ -65,7 +66,7 @@ vcard_set(User, Server, VCARD) ->
|
||||
[] ->
|
||||
remove_xupdate(User, Server);
|
||||
BinVal ->
|
||||
add_xupdate(User, Server, sha:sha(jlib:decode_base64(BinVal)))
|
||||
add_xupdate(User, Server, list_to_binary(sha:sha(jlib:decode_base64(BinVal))))
|
||||
end,
|
||||
ejabberd_sm:force_update_presence(US).
|
||||
|
||||
@@ -105,7 +106,7 @@ presence_with_xupdate(Stanza, User, Host) ->
|
||||
build_xphotoel(User, Host) ->
|
||||
Hash = get_xupdate(User, Host),
|
||||
PhotoSubEls = case Hash of
|
||||
Hash when is_list(Hash) ->
|
||||
Hash when is_binary(Hash) ->
|
||||
[exmpp_xml:cdata(Hash)];
|
||||
_ ->
|
||||
[]
|
||||
|
||||
+4
-3
@@ -38,10 +38,11 @@
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
|
||||
|
||||
start(Host, Opts) ->
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, list_to_binary(Host), ?NS_SOFT_VERSION,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_SOFT_VERSION,
|
||||
?MODULE, process_local_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
|
||||
@@ -100,6 +100,7 @@ init([Host]) ->
|
||||
[?MODULE]}
|
||||
end, lists:seq(1, PoolSize))}}.
|
||||
|
||||
%% @spec (Host::string()) -> [pid()]
|
||||
get_pids(Host) ->
|
||||
case ejabberd_config:get_local_option({odbc_server, Host}) of
|
||||
{host, Host1} ->
|
||||
@@ -109,10 +110,18 @@ get_pids(Host) ->
|
||||
[R#sql_pool.pid || R <- Rs]
|
||||
end.
|
||||
|
||||
get_random_pid(HostB) when is_binary(HostB) ->
|
||||
get_random_pid(binary_to_list(HostB));
|
||||
get_random_pid(global) ->
|
||||
get_random_pid("localhost");
|
||||
get_random_pid(Host) ->
|
||||
Pids = get_pids(ejabberd:normalize_host(Host)),
|
||||
lists:nth(erlang:phash(now(), length(Pids)), Pids).
|
||||
|
||||
get_dbtype(HostB) when is_binary(HostB) ->
|
||||
get_dbtype(binary_to_list(HostB));
|
||||
get_dbtype(global) ->
|
||||
get_dbtype("localhost");
|
||||
get_dbtype(Host) ->
|
||||
case ejabberd_config:get_local_option({odbc_server, Host}) of
|
||||
{host, Host1} ->
|
||||
|
||||
+178
-146
@@ -25,6 +25,10 @@
|
||||
-- Needs MySQL (at least 4.0.x) with innodb back-end
|
||||
SET table_type=InnoDB;
|
||||
|
||||
--
|
||||
-- Tables schemas keep from previous ejabberd versions
|
||||
--
|
||||
|
||||
CREATE TABLE hosts (
|
||||
clusterid integer NOT NULL,
|
||||
host varchar(250) NOT NULL PRIMARY KEY,
|
||||
@@ -34,158 +38,12 @@ CREATE TABLE hosts (
|
||||
INSERT INTO hosts (clusterid, host, config)
|
||||
VALUES (1, 'localhost', '');
|
||||
|
||||
CREATE TABLE users (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
password text NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (host, username)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
|
||||
CREATE TABLE last (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
seconds text NOT NULL,
|
||||
state text NOT NULL,
|
||||
PRIMARY KEY (host, username)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
|
||||
CREATE TABLE rosteritem (
|
||||
host varchar(250) NOT NULL,
|
||||
user varchar(250) NOT NULL,
|
||||
jid varchar(250) NOT NULL,
|
||||
name text,
|
||||
subscription text NOT NULL,
|
||||
ask text NOT NULL,
|
||||
askmessage text NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (host(75), username(75), jid(75))
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE INDEX i_rosteru_username ON rosteritem(username);
|
||||
CREATE INDEX i_rosteru_jid ON rosteritem(jid);
|
||||
|
||||
CREATE TABLE rostergroup (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
jid varchar(250) NOT NULL,
|
||||
grp text NOT NULL,
|
||||
PRIMARY KEY (host(75), username(75), jid(75))
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE TABLE spool (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
xml text NOT NULL,
|
||||
seq BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (host, username, seq)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
|
||||
CREATE TABLE vcard (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
vcard mediumtext NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (host, username)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
lusername varchar(250) NOT NULL,
|
||||
fn text NOT NULL,
|
||||
lfn varchar(250) NOT NULL,
|
||||
family text NOT NULL,
|
||||
lfamily varchar(250) NOT NULL,
|
||||
given text NOT NULL,
|
||||
lgiven varchar(250) NOT NULL,
|
||||
middle text NOT NULL,
|
||||
lmiddle varchar(250) NOT NULL,
|
||||
nickname text NOT NULL,
|
||||
lnickname varchar(250) NOT NULL,
|
||||
bday text NOT NULL,
|
||||
lbday varchar(250) NOT NULL,
|
||||
ctry text NOT NULL,
|
||||
lctry varchar(250) NOT NULL,
|
||||
locality text NOT NULL,
|
||||
llocality varchar(250) NOT NULL,
|
||||
email text NOT NULL,
|
||||
lemail varchar(250) NOT NULL,
|
||||
orgname text NOT NULL,
|
||||
lorgname varchar(250) NOT NULL,
|
||||
orgunit text NOT NULL,
|
||||
lorgunit varchar(250) NOT NULL,
|
||||
PRIMARY KEY (host, lusername)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE INDEX i_vcard_search_lfn ON vcard_search(lfn);
|
||||
CREATE INDEX i_vcard_search_lfamily ON vcard_search(lfamily);
|
||||
CREATE INDEX i_vcard_search_lgiven ON vcard_search(lgiven);
|
||||
CREATE INDEX i_vcard_search_lmiddle ON vcard_search(lmiddle);
|
||||
CREATE INDEX i_vcard_search_lnickname ON vcard_search(lnickname);
|
||||
CREATE INDEX i_vcard_search_lbday ON vcard_search(lbday);
|
||||
CREATE INDEX i_vcard_search_lctry ON vcard_search(lctry);
|
||||
CREATE INDEX i_vcard_search_llocality ON vcard_search(llocality);
|
||||
CREATE INDEX i_vcard_search_lemail ON vcard_search(lemail);
|
||||
CREATE INDEX i_vcard_search_lorgname ON vcard_search(lorgname);
|
||||
CREATE INDEX i_vcard_search_lorgunit ON vcard_search(lorgunit);
|
||||
|
||||
CREATE TABLE privacy_default_list (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250),
|
||||
name varchar(250) NOT NULL,
|
||||
PRIMARY KEY (host, username)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE TABLE privacy_list (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
name varchar(250) NOT NULL,
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (host, username, name)
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE TABLE privacy_list_data (
|
||||
id bigint,
|
||||
t character(1) NOT NULL,
|
||||
value text NOT NULL,
|
||||
action character(1) NOT NULL,
|
||||
ord NUMERIC NOT NULL,
|
||||
match_all boolean NOT NULL,
|
||||
match_iq boolean NOT NULL,
|
||||
match_message boolean NOT NULL,
|
||||
match_presence_in boolean NOT NULL,
|
||||
match_presence_out boolean NOT NULL
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE TABLE private_storage (
|
||||
host varchar(250) NOT NULL,
|
||||
username varchar(250) NOT NULL,
|
||||
namespace varchar(250) NOT NULL,
|
||||
data text NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (host(75), username(75), namespace(75))
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
CREATE INDEX i_private_storage_username USING BTREE ON private_storage(username);
|
||||
|
||||
-- Not tested in mysql
|
||||
CREATE TABLE roster_version (
|
||||
username varchar(250) PRIMARY KEY,
|
||||
version text NOT NULL
|
||||
) CHARACTER SET utf8;
|
||||
|
||||
-- To update from 1.x:
|
||||
-- ALTER TABLE rosterusers ADD COLUMN askmessage text AFTER ask;
|
||||
-- UPDATE rosterusers SET askmessage = '';
|
||||
-- ALTER TABLE rosterusers ALTER COLUMN askmessage SET NOT NULL;
|
||||
|
||||
CREATE TABLE pubsub_node (
|
||||
host text,
|
||||
node text,
|
||||
@@ -240,3 +98,177 @@ CREATE TABLE pubsub_subscription_opt (
|
||||
opt_value text
|
||||
);
|
||||
CREATE UNIQUE INDEX i_pubsub_subscription_opt ON pubsub_subscription_opt(subid(32), opt_name(32));
|
||||
|
||||
--
|
||||
-- Tables schemas dumped from gen_storage
|
||||
--
|
||||
|
||||
CREATE TABLE `last_activity` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`timestamp` bigint(20) DEFAULT NULL,
|
||||
`status` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `muc_online_room` (
|
||||
`name` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`pid` text,
|
||||
PRIMARY KEY (`name`(105),`host`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `muc_registered` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`nick` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105)),
|
||||
KEY `muc_registered_nick` (`nick`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `muc_room_affiliation` (
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`host` varchar(255) DEFAULT NULL,
|
||||
`jid` varchar(255) DEFAULT NULL,
|
||||
`affiliation` varchar(255) DEFAULT NULL,
|
||||
`reason` varchar(255) DEFAULT NULL,
|
||||
KEY `muc_room_affiliation_bag` (`name`(75),`host`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `muc_room_opt` (
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`host` varchar(255) DEFAULT NULL,
|
||||
`opt` varchar(255) DEFAULT NULL,
|
||||
`val` varchar(255) DEFAULT NULL,
|
||||
KEY `muc_room_opt_bag` (`name`(75),`host`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `offline_msg` (
|
||||
`user` varchar(255) DEFAULT NULL,
|
||||
`host` varchar(255) DEFAULT NULL,
|
||||
`timestamp` bigint(20) DEFAULT NULL,
|
||||
`expire` bigint(20) DEFAULT NULL,
|
||||
`from` varchar(255) DEFAULT NULL,
|
||||
`to` varchar(255) DEFAULT NULL,
|
||||
`packet` text,
|
||||
KEY `offline_msg_bag` (`user`(75),`host`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `passwd` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`password` text,
|
||||
`storedkey` text,
|
||||
`serverkey` text,
|
||||
`salt` text,
|
||||
`iterationcount` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`user`(105),`host`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `privacy_default_list` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`name` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `privacy_list` (
|
||||
`user` varchar(255) DEFAULT NULL,
|
||||
`host` varchar(255) DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
KEY `privacy_list_bag` (`user`(75),`host`(75)),
|
||||
KEY `privacy_list_name` (`name`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `privacy_list_data` (
|
||||
`user` varchar(255) DEFAULT NULL,
|
||||
`host` varchar(255) DEFAULT NULL,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
`type` varchar(255) DEFAULT NULL,
|
||||
`value` varchar(255) DEFAULT NULL,
|
||||
`action` varchar(255) DEFAULT NULL,
|
||||
`order` int(11) DEFAULT NULL,
|
||||
`match_all` varchar(255) DEFAULT NULL,
|
||||
`match_iq` varchar(255) DEFAULT NULL,
|
||||
`match_message` varchar(255) DEFAULT NULL,
|
||||
`match_presence_in` varchar(255) DEFAULT NULL,
|
||||
`match_presence_out` varchar(255) DEFAULT NULL,
|
||||
KEY `privacy_list_data_bag` (`user`(75),`host`(75)),
|
||||
KEY `privacy_list_data_name` (`name`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `private_storage` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`ns` varchar(255) NOT NULL DEFAULT '',
|
||||
`xml` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105),`ns`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `rostergroup` (
|
||||
`user` varchar(255) DEFAULT NULL,
|
||||
`host` varchar(255) DEFAULT NULL,
|
||||
`jid` varchar(255) DEFAULT NULL,
|
||||
`grp` varchar(255) DEFAULT NULL,
|
||||
KEY `rostergroup_bag` (`user`(75),`host`(75),`jid`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `rosteritem` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`jid` varchar(255) NOT NULL DEFAULT '',
|
||||
`name` text,
|
||||
`subscription` text,
|
||||
`ask` text,
|
||||
`askmessage` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105),`jid`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `vcard` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`vcard` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `vcard_search` (
|
||||
`user` varchar(255) NOT NULL DEFAULT '',
|
||||
`host` varchar(255) NOT NULL DEFAULT '',
|
||||
`username` text,
|
||||
`lusername` text,
|
||||
`fn` text,
|
||||
`lfn` text,
|
||||
`family` text,
|
||||
`lfamily` text,
|
||||
`given` text,
|
||||
`lgiven` text,
|
||||
`middle` text,
|
||||
`lmiddle` text,
|
||||
`nickname` text,
|
||||
`lnickname` text,
|
||||
`bday` text,
|
||||
`lbday` text,
|
||||
`ctry` text,
|
||||
`lctry` text,
|
||||
`locality` text,
|
||||
`llocality` text,
|
||||
`email` text,
|
||||
`lemail` text,
|
||||
`orgname` text,
|
||||
`lorgname` text,
|
||||
`orgunit` text,
|
||||
`lorgunit` text,
|
||||
PRIMARY KEY (`user`(105),`host`(105)),
|
||||
KEY `vcard_search_lusername` (`lusername`(75)),
|
||||
KEY `vcard_search_lfn` (`lfn`(75)),
|
||||
KEY `vcard_search_lfamily` (`lfamily`(75)),
|
||||
KEY `vcard_search_lgiven` (`lgiven`(75)),
|
||||
KEY `vcard_search_lmiddle` (`lmiddle`(75)),
|
||||
KEY `vcard_search_lnickname` (`lnickname`(75)),
|
||||
KEY `vcard_search_lbday` (`lbday`(75)),
|
||||
KEY `vcard_search_lctry` (`lctry`(75)),
|
||||
KEY `vcard_search_llocality` (`llocality`(75)),
|
||||
KEY `vcard_search_lemail` (`lemail`(75)),
|
||||
KEY `vcard_search_lorgname` (`lorgname`(75)),
|
||||
KEY `vcard_search_lorgunit` (`lorgunit`(75))
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
@@ -225,10 +225,6 @@ list_users(LServer, [{prefix, Prefix},
|
||||
users_number(LServer) ->
|
||||
Host = escape(LServer),
|
||||
case element(1, ejabberd_config:get_local_option({odbc_server, LServer})) of
|
||||
mysql ->
|
||||
ejabberd_odbc:sql_query(
|
||||
LServer,
|
||||
"select table_rows from information_schema.tables where table_name='users'");
|
||||
pgsql ->
|
||||
case ejabberd_config:get_local_option({pgsql_users_number_estimate, LServer}) of
|
||||
true ->
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : scram.erl
|
||||
%%% Author : Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%% Purpose : SCRAM (RFC 5802)
|
||||
%%% Created : 7 Aug 2011 by Stephen Röttger <stephen.roettger@googlemail.com>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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(scram).
|
||||
-author('stephen.roettger@googlemail.com').
|
||||
|
||||
%% External exports
|
||||
-export([salted_password/3,
|
||||
stored_key/1,
|
||||
server_key/1,
|
||||
server_signature/2,
|
||||
client_signature/2,
|
||||
client_key/1,
|
||||
client_key/2
|
||||
]).
|
||||
|
||||
%% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
|
||||
salted_password(Password, Salt, IterationCount) ->
|
||||
hi(exmpp_stringprep:resourceprep(Password), Salt, IterationCount).
|
||||
|
||||
client_key(SaltedPassword) ->
|
||||
crypto:sha_mac(SaltedPassword, "Client Key").
|
||||
|
||||
stored_key(ClientKey) ->
|
||||
crypto:sha(ClientKey).
|
||||
|
||||
server_key(SaltedPassword) ->
|
||||
crypto:sha_mac(SaltedPassword, "Server Key").
|
||||
|
||||
client_signature(StoredKey, AuthMessage) ->
|
||||
crypto:sha_mac(StoredKey, AuthMessage).
|
||||
|
||||
client_key(ClientProof, ClientSignature) ->
|
||||
list_to_binary(lists:zipwith(fun(X, Y) ->
|
||||
X bxor Y
|
||||
end,
|
||||
binary_to_list(ClientProof),
|
||||
binary_to_list(ClientSignature))).
|
||||
|
||||
server_signature(ServerKey, AuthMessage) ->
|
||||
crypto:sha_mac(ServerKey, AuthMessage).
|
||||
|
||||
hi(Password, Salt, IterationCount) ->
|
||||
U1 = crypto:sha_mac(Password, string:concat(binary_to_list(Salt), [0,0,0,1])),
|
||||
list_to_binary(lists:zipwith(fun(X, Y) ->
|
||||
X bxor Y
|
||||
end,
|
||||
binary_to_list(U1),
|
||||
binary_to_list(hi_round(Password, U1, IterationCount-1)))).
|
||||
|
||||
hi_round(Password, UPrev, 1) ->
|
||||
crypto:sha_mac(Password, UPrev);
|
||||
hi_round(Password, UPrev, IterationCount) ->
|
||||
U = crypto:sha_mac(Password, UPrev),
|
||||
list_to_binary(lists:zipwith(fun(X, Y) ->
|
||||
X bxor Y
|
||||
end,
|
||||
binary_to_list(U),
|
||||
binary_to_list(hi_round(Password, U, IterationCount-1)))).
|
||||
+26
-4
@@ -24,16 +24,26 @@
|
||||
#include <openssl/md2.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* R15B changed several driver callbacks to use ErlDrvSizeT and
|
||||
* ErlDrvSSizeT typedefs instead of int.
|
||||
* This provides missing typedefs on older OTP versions.
|
||||
*/
|
||||
#if ERL_DRV_EXTENDED_MAJOR_VERSION < 2
|
||||
typedef int ErlDrvSizeT;
|
||||
typedef int ErlDrvSSizeT;
|
||||
#endif
|
||||
|
||||
static ErlDrvData sha_drv_start(ErlDrvPort port, char *buf)
|
||||
{
|
||||
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int sha_drv_control(ErlDrvData handle,
|
||||
static ErlDrvSSizeT sha_drv_control(ErlDrvData handle,
|
||||
unsigned int command,
|
||||
char *buf, int len,
|
||||
char **rbuf, int rlen)
|
||||
char *buf, ErlDrvSizeT len,
|
||||
char **rbuf, ErlDrvSizeT rlen)
|
||||
{
|
||||
ErlDrvBinary *b = NULL;
|
||||
|
||||
@@ -89,7 +99,19 @@ ErlDrvEntry sha_driver_entry = {
|
||||
NULL, /* handle */
|
||||
sha_drv_control, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
NULL, /* F_PTR outputv, reserved */
|
||||
/* Added in Erlang/OTP R15B: */
|
||||
NULL, /* ready_async */
|
||||
NULL, /* flush */
|
||||
NULL, /* call */
|
||||
NULL, /* event */
|
||||
ERL_DRV_EXTENDED_MARKER, /* extended_marker */
|
||||
ERL_DRV_EXTENDED_MAJOR_VERSION, /* major_version */
|
||||
ERL_DRV_EXTENDED_MINOR_VERSION, /* minor_version */
|
||||
0, /* driver_flags */
|
||||
NULL, /* handle2 */
|
||||
NULL, /* process_exit */
|
||||
NULL /* stop_select */
|
||||
};
|
||||
|
||||
DRIVER_INIT(sha_drv) /* must match name in driver_entry */
|
||||
|
||||
+29
-17
@@ -44,6 +44,16 @@ typedef unsigned __int32 uint32_t;
|
||||
#define SSL_OP_NO_TICKET 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* R15B changed several driver callbacks to use ErlDrvSizeT and
|
||||
* ErlDrvSSizeT typedefs instead of int.
|
||||
* This provides missing typedefs on older OTP versions.
|
||||
*/
|
||||
#if ERL_DRV_EXTENDED_MAJOR_VERSION < 2
|
||||
typedef int ErlDrvSizeT;
|
||||
typedef int ErlDrvSSizeT;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* str_hash is based on the public domain code from
|
||||
* http://www.burtleburtle.net/bob/hash/doobs.html
|
||||
@@ -305,10 +315,10 @@ static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
|
||||
}
|
||||
|
||||
|
||||
static int tls_drv_control(ErlDrvData handle,
|
||||
static ErlDrvSSizeT tls_drv_control(ErlDrvData handle,
|
||||
unsigned int command,
|
||||
char *buf, int len,
|
||||
char **rbuf, int rlen)
|
||||
char *buf, ErlDrvSizeT len,
|
||||
char **rbuf, ErlDrvSizeT rlen)
|
||||
{
|
||||
tls_data *d = (tls_data *)handle;
|
||||
int res;
|
||||
@@ -407,22 +417,12 @@ static int tls_drv_control(ErlDrvData handle,
|
||||
break;
|
||||
case GET_ENCRYPTED_OUTPUT:
|
||||
die_unless(d->ssl, "SSL not initialized");
|
||||
size = BUF_SIZE + 1;
|
||||
rlen = 1;
|
||||
size = BIO_ctrl_pending(d->bio_write) + 1;
|
||||
b = driver_alloc_binary(size);
|
||||
b->orig_bytes[0] = 0;
|
||||
while ((res = BIO_read(d->bio_write,
|
||||
b->orig_bytes + rlen, BUF_SIZE)) > 0)
|
||||
{
|
||||
//printf("%d bytes of encrypted data read from state machine\r\n", res);
|
||||
|
||||
rlen += res;
|
||||
size += BUF_SIZE;
|
||||
b = driver_realloc_binary(b, size);
|
||||
}
|
||||
b = driver_realloc_binary(b, rlen);
|
||||
BIO_read(d->bio_write, b->orig_bytes + 1, size - 1);
|
||||
*rbuf = (char *)b;
|
||||
return rlen;
|
||||
return size;
|
||||
case GET_DECRYPTED_INPUT:
|
||||
if (!SSL_is_init_finished(d->ssl))
|
||||
{
|
||||
@@ -513,7 +513,19 @@ ErlDrvEntry tls_driver_entry = {
|
||||
NULL, /* handle */
|
||||
tls_drv_control, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
NULL, /* F_PTR outputv, reserved */
|
||||
/* Added in Erlang/OTP R15B: */
|
||||
NULL, /* ready_async */
|
||||
NULL, /* flush */
|
||||
NULL, /* call */
|
||||
NULL, /* event */
|
||||
ERL_DRV_EXTENDED_MARKER, /* extended_marker */
|
||||
ERL_DRV_EXTENDED_MAJOR_VERSION, /* major_version */
|
||||
ERL_DRV_EXTENDED_MINOR_VERSION, /* minor_version */
|
||||
0, /* driver_flags */
|
||||
NULL, /* handle2 */
|
||||
NULL, /* process_exit */
|
||||
NULL /* stop_select */
|
||||
};
|
||||
|
||||
DRIVER_INIT(tls_drv) /* must match name in driver_entry */
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
-define(FSMOPTS, []).
|
||||
-endif.
|
||||
|
||||
-define(BOSH_VERSION_b, <<"1.8">>).
|
||||
-define(BOSH_VERSION_b, <<"1.10">>).
|
||||
|
||||
-define(MAX_REQUESTS, 2). % number of simultaneous requests
|
||||
-define(MIN_POLLING, 2000000). % don't poll faster than that or we will
|
||||
@@ -121,7 +121,7 @@
|
||||
%% supervisor
|
||||
start(XMPPDomain, Sid, Key, IP) ->
|
||||
?DEBUG("Starting session", []),
|
||||
SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME_MHB),
|
||||
SupervisorProc = gen_mod:get_module_proc_existing(XMPPDomain, ?PROCNAME_MHB),
|
||||
case catch supervisor:start_child(SupervisorProc, [Sid, Key, IP]) of
|
||||
{ok, Pid} ->
|
||||
{ok, Pid};
|
||||
@@ -232,7 +232,7 @@ process_request(Data, IP) ->
|
||||
%% Existing session
|
||||
{ok, {Sid, Rid, Attrs, Payload1}} ->
|
||||
StreamStart =
|
||||
case exmpp_xml:get_attribute_from_list_as_list(Attrs, ?NS_BOSH, <<"restart">>, "") of
|
||||
case exmpp_xml:get_attribute_from_list_as_list(Attrs, ?NS_XBOSH, <<"restart">>, "") of
|
||||
"true" ->
|
||||
true;
|
||||
_ ->
|
||||
@@ -303,7 +303,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs,
|
||||
{'EXIT', _} -> 0.0;
|
||||
V -> V
|
||||
end,
|
||||
XmppVersion = exmpp_xml:get_attribute_from_list_as_list(Attrs, ?NS_BOSH, <<"version">>, ""),
|
||||
XmppVersion = exmpp_xml:get_attribute_from_list_as_list(Attrs, ?NS_XBOSH, <<"version">>, ""),
|
||||
?DEBUG("Create session: ~p", [Sid]),
|
||||
mnesia:async_dirty(
|
||||
fun() ->
|
||||
@@ -1049,7 +1049,8 @@ prepare_outpacket_response(#http_bind{id=Sid, wait=Wait,
|
||||
BOSH_attribs =
|
||||
[#xmlattr{name = <<"authid">>, value = list_to_binary(AuthID)},
|
||||
#xmlattr{name = <<"xmlns:stream">>, value = ?NS_XMPP_b}] ++
|
||||
[#xmlattr{name = <<"version">>, ns = ?NS_BOSH_s, value = list_to_binary(Version)}],
|
||||
[#xmlattr{name = <<"version">>, ns = ?NS_XBOSH_s, value = list_to_binary(Version)},
|
||||
#xmlattr{name = <<"restartlogic">>, ns = ?NS_XBOSH_s, value = <<"true">>}],
|
||||
MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY),
|
||||
MaxPause = get_max_pause(To),
|
||||
{200, ?HEADER,
|
||||
|
||||
@@ -1177,11 +1177,12 @@ process_admin(Host,
|
||||
process_admin(Host, #request{lang = Lang,
|
||||
auth = {_, _Auth, AJID}
|
||||
} = Request) ->
|
||||
{Hook, Opts} = case Host of
|
||||
global -> {webadmin_page_main, [Request]};
|
||||
Host -> {webadmin_page_host, [Host, Request]}
|
||||
end,
|
||||
case ejabberd_hooks:run_fold(Hook, list_to_binary(Host), [], Opts) of
|
||||
{Hook, Opts, HostB} =
|
||||
case Host of
|
||||
global -> {webadmin_page_main, [Request], global};
|
||||
Host -> {webadmin_page_host, [Host, Request], list_to_binary(Host)}
|
||||
end,
|
||||
case ejabberd_hooks:run_fold(Hook, HostB, [], Opts) of
|
||||
[] ->
|
||||
setelement(1, make_xhtml([?XC('h1', "Not Found")], Host, Lang, AJID), 404);
|
||||
[{xmlel, _, _, _, _, _} | _] = Res ->
|
||||
@@ -1658,8 +1659,7 @@ list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
|
||||
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
|
||||
[list_given_users(Host, Sub, "../../", Lang, URLFunc)].
|
||||
|
||||
list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
ModOffline = get_offlinemsg_module(Host),
|
||||
list_given_users(_Host, Users, Prefix, Lang, URLFunc) ->
|
||||
?XE('table',
|
||||
[?XE('thead',
|
||||
[?XE('tr',
|
||||
@@ -1672,7 +1672,7 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
ServerB = list_to_binary(Server),
|
||||
UserB = list_to_binary(User),
|
||||
US = {UserB, ServerB},
|
||||
QueueLenStr = get_offlinemsg_length(ModOffline, User, Server),
|
||||
QueueLenStr = get_offlinemsg_length(User, Server),
|
||||
FQueueLen = [?AC(URLFunc({users_queue, Prefix,
|
||||
User, Server}),
|
||||
QueueLenStr)],
|
||||
@@ -1707,16 +1707,10 @@ list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
end, Users)
|
||||
)]).
|
||||
|
||||
get_offlinemsg_length(ModOffline, User, Server) ->
|
||||
case ModOffline of
|
||||
none -> "disabled";
|
||||
_ -> pretty_string_int(ModOffline:get_queue_length(list_to_binary(User), list_to_binary(Server)))
|
||||
end.
|
||||
|
||||
get_offlinemsg_module(Server) ->
|
||||
case [mod_offline] -- gen_mod:loaded_modules(Server) of
|
||||
[mod_offline] -> none;
|
||||
[] -> mod_offline
|
||||
get_offlinemsg_length(User, Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_offline) of
|
||||
false -> "disabled";
|
||||
true -> pretty_string_int(mod_offline:get_queue_length(list_to_binary(User), list_to_binary(Server)))
|
||||
end.
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
@@ -1730,7 +1724,10 @@ su_to_list({Server, User}) ->
|
||||
|
||||
get_stats(global, Lang) ->
|
||||
OnlineUsers = mnesia:table_info(session, size),
|
||||
RegisteredUsers = mnesia:table_info(passwd, size),
|
||||
RegisteredUsers = lists:foldl(
|
||||
fun(Host, Total) ->
|
||||
ejabberd_auth:get_vh_registered_users_number(Host) + Total
|
||||
end, 0, ejabberd_config:get_global_option(hosts)),
|
||||
S2SConns = ejabberd_s2s:dirty_get_connections(),
|
||||
S2SConnections = length(S2SConns),
|
||||
S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])),
|
||||
@@ -1768,7 +1765,7 @@ host_info(Server, Query, Lang) ->
|
||||
error -> [?XREST("Bad format")];
|
||||
nothing -> []
|
||||
end ++
|
||||
[?XAE('p', [], [?CT("Is registered: "), ?CT(IsRegistered)])
|
||||
[?XAE('p', [], [?CT("Is registered:"), ?C(" "), ?CT(IsRegistered)])
|
||||
] ++ host_info_permanent(Server, Lang)
|
||||
++ host_info_running(Server, Lang).
|
||||
|
||||
@@ -1779,14 +1776,14 @@ host_info_permanent(Server, Lang) ->
|
||||
[?XAE('form', [?XMLATTR(<<"action">>, <<>>), ?XMLATTR(<<"method">>, <<"post">>)],
|
||||
[?P, ?INPUTT("submit", "removehost", "Remove Host")])]}
|
||||
end,
|
||||
[?XAE('p', [], [?CT("Is permanent: "), PermanentX])] ++ FormList.
|
||||
[?XAE('p', [], [?CT("Is permanent:"), ?C(" "), PermanentX])] ++ FormList.
|
||||
|
||||
host_info_running(Server, Lang) ->
|
||||
{RunningX, InputName,InputString} = case ejabberd_hosts:running(Server) of
|
||||
true -> {?CT("true"), "stophost", "Stop Host"};
|
||||
false -> {?CT("false"), "starthost", "Start Host"}
|
||||
end,
|
||||
[?XAE('p', [], [?CT("Is running: "), RunningX]),
|
||||
[?XAE('p', [], [?CT("Is running:"), ?C(" "), RunningX]),
|
||||
?XAE('form', [?XMLATTR(<<"action">>, <<>>), ?XMLATTR(<<"method">>, <<"post">>)],
|
||||
[?P, ?INPUTT("submit", InputName, InputString)])].
|
||||
|
||||
@@ -2443,12 +2440,21 @@ get_node(global, Node, ["backup"], Query, Lang) ->
|
||||
|
||||
get_node(global, Node, ["pid"], _Query, Lang) ->
|
||||
NodeS = atom_to_list(Node),
|
||||
Registered = lists:reverse(lists:sort(rpc:call(Node, erlang, registered, []))),
|
||||
Processes = rpc:call(Node, erlang, processes, []),
|
||||
ProcessesNumber = length(Processes),
|
||||
RegisteredList = lists:foldl(
|
||||
fun(P, RRL) ->
|
||||
PS = atom_to_list(P),
|
||||
NodePidS = NodeS ++ "/pid/" ++ PS,
|
||||
[?AC("/admin/node/" ++ NodePidS ++ "/", PS), ?BR | RRL]
|
||||
end,
|
||||
[],
|
||||
Registered),
|
||||
ProcessesList = lists:map(
|
||||
fun(P) ->
|
||||
PS = pid_to_list(P),
|
||||
NodePidS = NodeS ++ "/pid/" ++ PS,
|
||||
NodePidS = NodeS ++ "/pid/process/" ++ PS,
|
||||
?AC("/admin/node/" ++ NodePidS ++ "/", PS)
|
||||
end,
|
||||
Processes),
|
||||
@@ -2461,10 +2467,23 @@ get_node(global, Node, ["pid"], _Query, Lang) ->
|
||||
])
|
||||
]),
|
||||
?XAE('p', [],
|
||||
[?CT("Processes: ")] ++ ProcessesList
|
||||
[?CT("Registered processes and ports:"), ?C(" ")] ++ RegisteredList
|
||||
),
|
||||
?XAE('p', [],
|
||||
[?CT("Total processes:"), ?C(" ")] ++ ProcessesList
|
||||
)];
|
||||
|
||||
get_node(global, Node, ["pid", PidS], _Query, Lang) ->
|
||||
get_node(global, Node, ["pid", [Char1|_] = RegS], _Query, Lang) when Char1 /= $< ->
|
||||
Ref = whereis(list_to_atom(RegS)),
|
||||
{Type, PidS} = try
|
||||
{"process", pid_to_list(Ref)}
|
||||
catch
|
||||
error:badarg ->
|
||||
{"port", RegS}
|
||||
end,
|
||||
get_node(global, Node, ["pid", Type, PidS], _Query, Lang);
|
||||
|
||||
get_node(global, Node, ["pid", "process" = Type, PidS], _Query, Lang) ->
|
||||
NodeS = atom_to_list(Node),
|
||||
ProcessInfo = rpc:call(Node, erlang, process_info, [list_to_pid(PidS)]),
|
||||
ProcessInfoS = io_lib:format("~p", [ProcessInfo]),
|
||||
@@ -2473,7 +2492,7 @@ get_node(global, Node, ["pid", PidS], _Query, Lang) ->
|
||||
{match, PidsRareList} ->
|
||||
lists:map(
|
||||
fun([PS]) ->
|
||||
NodePidS = NodeS ++ "/pid/" ++ PS,
|
||||
NodePidS = NodeS ++ "/pid/" ++ Type ++ "/" ++ PS,
|
||||
?AC("/admin/node/" ++ NodePidS ++ "/", PS)
|
||||
end,
|
||||
PidsRareList);
|
||||
@@ -2495,7 +2514,7 @@ get_node(global, Node, ["pid", PidS], _Query, Lang) ->
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
[?XC('h1', io_lib:format(?T("Erlang Process ~s at node ~p"), [PidS, Node])),
|
||||
[?XC('h1', io_lib:format(?T("Erlang process ~s at node ~p"), [PidS, Node])),
|
||||
?XC('h3', ?T("Process Information:")),
|
||||
?XAE('pre', [], [?C(ProcessInfoS)]),
|
||||
?XC('h3', ?T("Related Processes:")),
|
||||
@@ -2506,6 +2525,14 @@ get_node(global, Node, ["pid", PidS], _Query, Lang) ->
|
||||
|| {PortName, PortDescr} <- PortLinkList])
|
||||
];
|
||||
|
||||
get_node(global, Node, ["pid", "port", PidS], _Query, Lang) ->
|
||||
ProcessInfo = rpc:call(Node, erlang, port_info, [list_to_atom(PidS)]),
|
||||
ProcessInfoS = io_lib:format("~p", [ProcessInfo]),
|
||||
[?XC('h1', io_lib:format(?T("Erlang port ~s at node ~p"), [PidS, Node])),
|
||||
?XC('h3', ?T("Port Information:")),
|
||||
?XAE('pre', [], [?C(ProcessInfoS)])
|
||||
];
|
||||
|
||||
get_node(global, Node, ["ports"], Query, Lang) ->
|
||||
Ports = rpc:call(Node, ejabberd_config, get_local_option, [listen]),
|
||||
Res = case catch node_ports_parse_query(Node, Ports, Query) of
|
||||
@@ -2660,11 +2687,12 @@ get_node(global, Node, ["update"], Query, Lang) ->
|
||||
];
|
||||
|
||||
get_node(Host, Node, NPath, Query, Lang) ->
|
||||
{Hook, Opts} = case Host of
|
||||
global -> {webadmin_page_node, [Node, NPath, Query, Lang]};
|
||||
Host -> {webadmin_page_hostnode, [Host, Node, NPath, Query, Lang]}
|
||||
end,
|
||||
case ejabberd_hooks:run_fold(Hook, list_to_binary(Host), [], Opts) of
|
||||
{Hook, Opts, HostB} =
|
||||
case Host of
|
||||
global -> {webadmin_page_node, [Node, NPath, Query, Lang], global};
|
||||
Host -> {webadmin_page_hostnode, [Host, Node, NPath, Query, Lang], list_to_binary(Host)}
|
||||
end,
|
||||
case ejabberd_hooks:run_fold(Hook, HostB, [], Opts) of
|
||||
[] -> [?XC('h1', "Not Found")];
|
||||
Res -> Res
|
||||
end.
|
||||
@@ -3134,7 +3162,6 @@ make_host_menu(Host, HostNodeMenu, Lang, JID) ->
|
||||
{"online-users", "Online Users"},
|
||||
{"last-activity", "Last Activity"},
|
||||
{"nodes", "Nodes", HostNodeMenu},
|
||||
{"misc", "Miscelanea Options"},
|
||||
{"stats", "Statistics"}]
|
||||
++ get_menu_items_hook({host, Host}, Lang),
|
||||
HostBasePath = url_to_path(HostBase),
|
||||
@@ -3162,7 +3189,7 @@ make_server_menu(HostMenu, NodeMenu, Lang, JID) ->
|
||||
{"access", "Access Rules"},
|
||||
{"vhosts", "Virtual Hosts", HostMenu},
|
||||
{"nodes", "Nodes", NodeMenu},
|
||||
{"misc", "Miscelanea Options"},
|
||||
{"misc", "Miscellanea Options"},
|
||||
{"stats", "Statistics"}]
|
||||
++ get_menu_items_hook(server, Lang),
|
||||
BasePath = url_to_path(Base),
|
||||
|
||||
@@ -103,9 +103,11 @@ get_human_html_xmlel() ->
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% BEHAVIOUR CALLBACKS
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host, _Opts) ->
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, _Opts) ->
|
||||
setup_database(),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB),
|
||||
Proc = gen_mod:get_module_proc(HostB, ?PROCNAME_MHB),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
-export([process/2]).
|
||||
|
||||
%% ejabberd_hooks callbacks
|
||||
-export([reopen_log/1]).
|
||||
-export([reopen_log/0, reopen_log/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -81,9 +81,10 @@
|
||||
{".html", "text/html"},
|
||||
{".jar", "application/java-archive"},
|
||||
{".jpeg", "image/jpeg"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".js", "text/javascript"},
|
||||
{".png", "image/png"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".txt", "text/plain"},
|
||||
{".xml", "application/xml"},
|
||||
{".xpi", "application/x-xpinstall"},
|
||||
@@ -95,11 +96,13 @@
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
|
||||
start(Host, Opts) ->
|
||||
Proc = get_proc_name(Host),
|
||||
start(Host, Opts) when is_list(Host) ->
|
||||
start(list_to_binary(Host), Opts);
|
||||
start(HostB, Opts) ->
|
||||
Proc = get_proc_name(HostB),
|
||||
ChildSpec =
|
||||
{Proc,
|
||||
{?MODULE, start_link, [Host, Opts]},
|
||||
{?MODULE, start_link, [HostB, Opts]},
|
||||
transient, % if process crashes abruptly, it gets restarted
|
||||
1000,
|
||||
worker,
|
||||
@@ -205,7 +208,7 @@ check_docroot_is_readable(DRInfo, DocRoot) ->
|
||||
|
||||
try_open_log(undefined, _Host) ->
|
||||
undefined;
|
||||
try_open_log(FN, Host) ->
|
||||
try_open_log(FN, HostB) ->
|
||||
FD = try open_log(FN) of
|
||||
FD1 -> FD1
|
||||
catch
|
||||
@@ -213,7 +216,6 @@ try_open_log(FN, Host) ->
|
||||
?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", [FN, Reason]),
|
||||
undefined
|
||||
end,
|
||||
HostB = list_to_binary(Host),
|
||||
ejabberd_hooks:add(reopen_log_hook, HostB, ?MODULE, reopen_log, 50),
|
||||
FD.
|
||||
|
||||
@@ -287,7 +289,7 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%% Returns the page to be sent back to the client and/or HTTP status code.
|
||||
process(LocalPath, Request) ->
|
||||
?DEBUG("Requested ~p", [LocalPath]),
|
||||
try gen_server:call(get_proc_name(Request#request.host), {serve, LocalPath}) of
|
||||
try gen_server:call(get_proc_name_existing(Request#request.host), {serve, LocalPath}) of
|
||||
{FileSize, Code, Headers, Contents} ->
|
||||
add_to_log(FileSize, Code, Request),
|
||||
{Code, Headers, Contents}
|
||||
@@ -359,11 +361,14 @@ reopen_log(FN, FD) ->
|
||||
close_log(FD),
|
||||
open_log(FN).
|
||||
|
||||
reopen_log() ->
|
||||
%% This function is called when the hook was registered for host 'global'
|
||||
gen_server:cast(get_proc_name_existing(global), reopen_log).
|
||||
reopen_log(Host) ->
|
||||
gen_server:cast(get_proc_name(Host), reopen_log).
|
||||
gen_server:cast(get_proc_name_existing(Host), reopen_log).
|
||||
|
||||
add_to_log(FileSize, Code, Request) ->
|
||||
gen_server:cast(get_proc_name(Request#request.host),
|
||||
gen_server:cast(get_proc_name_existing(Request#request.host),
|
||||
{add_to_log, FileSize, Code, Request}).
|
||||
|
||||
add_to_log(undefined, _FileSize, _Code, _Request) ->
|
||||
@@ -404,6 +409,7 @@ find_header(Header, Headers, Default) ->
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME).
|
||||
get_proc_name_existing(Host) -> gen_mod:get_module_proc_existing(Host, ?PROCNAME).
|
||||
|
||||
join([], _) ->
|
||||
"";
|
||||
|
||||
@@ -516,6 +516,12 @@ form_del_get(Host, Lang) ->
|
||||
%% {error, not_allowed} |
|
||||
%% {error, invalid_jid}
|
||||
register_account(Username, Host, Password) ->
|
||||
try exmpp_jid:make(Username, Host) of
|
||||
_ -> register_account2(Username, Host, Password)
|
||||
catch
|
||||
_ -> {error, invalid_jid}
|
||||
end.
|
||||
register_account2(Username, Host, Password) ->
|
||||
case ejabberd_auth:try_register(Username, Host, Password) of
|
||||
{atomic, Res} ->
|
||||
{success, Res, {Username, Host, Password}};
|
||||
@@ -578,6 +584,8 @@ unregister_account(Username, Host, Password) ->
|
||||
|
||||
get_error_text({error, captcha_non_valid}) ->
|
||||
"The captcha you entered is wrong";
|
||||
get_error_text({success, exists, _}) ->
|
||||
get_error_text({atomic, exists});
|
||||
get_error_text({atomic, exists}) ->
|
||||
"The account already exists";
|
||||
get_error_text({error, password_incorrect}) ->
|
||||
|
||||
@@ -91,7 +91,7 @@ extract_lang_all ()
|
||||
cd $MSGS_DIR
|
||||
for i in $( ls *.msg ) ; do
|
||||
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
|
||||
LANGUAGE=`grep "Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LANGUAGE=`grep "X-Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
|
||||
echo -e "$i\t$MISSING\t$LANGUAGE\t$LASTAUTH"
|
||||
done
|
||||
@@ -258,7 +258,7 @@ extract_lang_updateall ()
|
||||
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
|
||||
echo -n " $MISSING"
|
||||
|
||||
LANGUAGE=`grep "Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
|
||||
LANGUAGE=`grep "X-Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
|
||||
echo -n " $LANGUAGE"
|
||||
|
||||
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
|
||||
|
||||
Reference in New Issue
Block a user