mirror of
https://github.com/processone/ejabberd.git
synced 2024-07-06 23:22:36 +02:00
fix merge conflicts from 2.1.9
This commit is contained in:
commit
adf56dedf3
|
@ -2,7 +2,7 @@
|
|||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.7 Developers Guide
|
||||
<TITLE>Ejabberd 2.1.9 Developers Guide
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
|
@ -49,7 +49,7 @@ TD P{margin:0px;}
|
|||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.7 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.9 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.7 Feature Sheet
|
||||
<TITLE>Ejabberd 2.1.9 Feature Sheet
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
|
@ -50,7 +50,7 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
|||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.7 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.9 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
|
||||
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
|
|
100
doc/guide.html
100
doc/guide.html
|
@ -6,7 +6,7 @@
|
|||
|
||||
|
||||
|
||||
ejabberd 2.1.7
|
||||
ejabberd 2.1.9
|
||||
|
||||
Installation and Operation Guide
|
||||
|
||||
|
@ -76,7 +76,7 @@ BLOCKQUOTE.figure DIV.center DIV.center HR{display:none;}
|
|||
<HR SIZE=2><BR>
|
||||
<BR>
|
||||
|
||||
<TABLE CELLSPACING=6 CELLPADDING=0><TR><TD ALIGN=right NOWRAP> <FONT SIZE=6><B>ejabberd 2.1.7 </B></FONT></TD></TR>
|
||||
<TABLE CELLSPACING=6 CELLPADDING=0><TR><TD ALIGN=right NOWRAP> <FONT SIZE=6><B>ejabberd 2.1.9 </B></FONT></TD></TR>
|
||||
<TR><TD ALIGN=right NOWRAP> </TD></TR>
|
||||
<TR><TD ALIGN=right NOWRAP> <FONT SIZE=6>Installation and Operation Guide</FONT></TD></TR>
|
||||
</TABLE><BR>
|
||||
|
@ -306,8 +306,9 @@ Note that the Windows service is a feature still in development,
|
|||
and for example it doesn’t read the file ejabberdctl.cfg.</P><P>On a *nix system, if you want ejabberd to be started as daemon at boot time,
|
||||
copy <TT>ejabberd.init</TT> from the ’bin’ directory to something like <TT>/etc/init.d/ejabberd</TT>
|
||||
(depending on your distribution).
|
||||
Create a system user called <TT>ejabberd</TT>;
|
||||
it will be used by the script to start the server.
|
||||
Create a system user called <TT>ejabberd</TT>,
|
||||
give it write access to the directories <TT>database/</TT> and <TT>logs/</TT>, and set that as home;
|
||||
the script will start the server with that user.
|
||||
Then you can call <TT>/etc/inid.d/ejabberd start</TT> as root to start the server.</P><P>If <TT>ejabberd</TT> doesn’t start correctly in Windows,
|
||||
try to start it using the shortcut in desktop or start menu.
|
||||
If the window shows error 14001, the solution is to install:
|
||||
|
@ -525,8 +526,8 @@ Using <TT>ejabberdctl</TT> (see section <A HREF="#ejabberdctl">4.1</A>):
|
|||
</PRE></LI><LI CLASS="li-enumerate">Using a XMPP client and In-Band Registration (see section <A HREF="#modregister">3.3.19</A>).
|
||||
</LI></OL>
|
||||
</LI><LI CLASS="li-enumerate">Edit the <TT>ejabberd</TT> configuration file to give administration rights to the XMPP account you created:
|
||||
<PRE CLASS="verbatim">{acl, admins, {user, "admin1", "example.org"}}.
|
||||
{access, configure, [{allow, admins}]}.
|
||||
<PRE CLASS="verbatim">{acl, admin, {user, "admin1", "example.org"}}.
|
||||
{access, configure, [{allow, admin}]}.
|
||||
</PRE>You can grant administrative privileges to many XMPP accounts,
|
||||
and also to accounts in other XMPP servers.
|
||||
</LI><LI CLASS="li-enumerate">Restart <TT>ejabberd</TT> to load the new configuration.
|
||||
|
@ -1051,17 +1052,40 @@ internal (default) — See section <A HREF="#internalauth">3.1.4</A>.
|
|||
<A HREF="#mssql">3.2.2</A> and <A HREF="#odbc">3.2.4</A>.
|
||||
</LI><LI CLASS="li-itemize">anonymous — See section <A HREF="#saslanonymous">3.1.4</A>.
|
||||
</LI><LI CLASS="li-itemize">pam — See section <A HREF="#pam">3.1.4</A>.
|
||||
</LI></UL><P>Account creation is only supported by internal, external and odbc methods.</P><P> <A NAME="internalauth"></A> </P><!--TOC subsubsection Internal-->
|
||||
</LI></UL><P>Account creation is only supported by internal, external and odbc methods.</P><P>The option <TT>resource_conflict</TT> defines the action when a client attempts to
|
||||
login to an account with a resource that is already connected.
|
||||
The option syntax is:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description"><B><TT>{resource_conflict, setresource|closenew|closeold}.</TT></B></DT></DL><P>
|
||||
The possible values match exactly the three possibilities described in
|
||||
<A HREF="http://tools.ietf.org/html/rfc6120#section-7.7.2.2">XMPP Core: section 7.7.2.2</A>.
|
||||
The default value is <TT>closeold</TT>.
|
||||
If the client uses old Jabber Non-SASL authentication (<A HREF="http://xmpp.org/extensions/xep-0078.html">XEP-0078</A>),
|
||||
then this option is not respected, and the action performed is <TT>closeold</TT>.</P><P> <A NAME="internalauth"></A> </P><!--TOC subsubsection Internal-->
|
||||
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#internalauth">Internal</A></H4><!--SEC END --><P> <A NAME="internalauth"></A>
|
||||
</P><P><TT>ejabberd</TT> uses its internal Mnesia database as the default authentication method.
|
||||
The value <TT>internal</TT> will enable the internal authentication method.</P><P>Examples:
|
||||
The value <TT>internal</TT> will enable the internal authentication method.</P><P>The option <TT>{auth_password_format, plain|scram}</TT>
|
||||
defines in what format the users passwords are stored:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
<B><TT>plain</TT></B></DT><DD CLASS="dd-description">
|
||||
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 (<A HREF="http://xmpp.org/extensions/xep-0078.html">XEP-0078</A>), <TT>SASL PLAIN</TT>,
|
||||
<TT>SASL DIGEST-MD5</TT>, and <TT>SASL SCRAM-SHA-1</TT>.</DD><DT CLASS="dt-description"><B><TT>scram</TT></B></DT><DD CLASS="dd-description">
|
||||
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 <TT>plain</TT> anymore.
|
||||
This format allows clients to authenticate using: <TT>SASL PLAIN</TT> and <TT>SASL SCRAM-SHA-1</TT>.
|
||||
</DD></DL><P>Examples:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
To use internal authentication on <TT>example.org</TT> and LDAP
|
||||
authentication on <TT>example.net</TT>:
|
||||
<PRE CLASS="verbatim">{host_config, "example.org", [{auth_method, [internal]}]}.
|
||||
{host_config, "example.net", [{auth_method, [ldap]}]}.
|
||||
</PRE></LI><LI CLASS="li-itemize">To use internal authentication on all virtual hosts:
|
||||
</PRE></LI><LI CLASS="li-itemize">To use internal authentication with hashed passwords on all virtual hosts:
|
||||
<PRE CLASS="verbatim">{auth_method, internal}.
|
||||
{auth_password_format, scram}.
|
||||
</PRE></LI></UL><P> <A NAME="extauth"></A> </P><!--TOC subsubsection External Script-->
|
||||
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#extauth">External Script</A></H4><!--SEC END --><P> <A NAME="extauth"></A>
|
||||
</P><P>In this authentication method, when <TT>ejabberd</TT> starts,
|
||||
|
@ -1208,9 +1232,9 @@ declarations of ACLs in the configuration file have the following syntax:
|
|||
</PRE></DD><DT CLASS="dt-description"><B><TT>{user_regexp, Regexp}</TT></B></DT><DD CLASS="dd-description"> Matches any local user with a name that
|
||||
matches <TT>Regexp</TT> on local virtual hosts. Example:
|
||||
<PRE CLASS="verbatim">{acl, tests, {user_regexp, "^test[0-9]*$"}}.
|
||||
</PRE></DD><DT CLASS="dt-description"><B><TT>{user_regexp, UserRegexp, Server}</TT></B></DT><DD CLASS="dd-description"> Matches any user with a name
|
||||
</PRE></DD><DT CLASS="dt-description"><B><TT>{user_regexp, Regexp, Server}</TT></B></DT><DD CLASS="dd-description"> Matches any user with a name
|
||||
that matches <TT>Regexp</TT> at server <TT>Server</TT>. Example:
|
||||
<PRE CLASS="verbatim">{acl, tests, {user_Userregexp, "^test", "example.org"}}.
|
||||
<PRE CLASS="verbatim">{acl, tests, {user_regexp, "^test", "example.org"}}.
|
||||
</PRE></DD><DT CLASS="dt-description"><B><TT>{server_regexp, Regexp}</TT></B></DT><DD CLASS="dd-description"> Matches any JID from the server that
|
||||
matches <TT>Regexp</TT>. Example:
|
||||
<PRE CLASS="verbatim">{acl, icq, {server_regexp, "^icq\\."}}.
|
||||
|
@ -1701,6 +1725,16 @@ This option specifies whether to verify LDAP server certificate or not when TLS
|
|||
When <TT>hard</TT> is enabled <TT>ejabberd</TT> doesn’t proceed if a certificate is invalid.
|
||||
When <TT>soft</TT> is enabled <TT>ejabberd</TT> proceeds even if check fails.
|
||||
The default is <TT>false</TT> which means no checks are performed.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_tls_cacertfile, Path}</TT></B></DT><DD CLASS="dd-description">
|
||||
Path to file containing PEM encoded CA certificates. This option is needed
|
||||
(and required) when TLS verification is enabled.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_tls_depth, Number}</TT></B></DT><DD CLASS="dd-description">
|
||||
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.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_port, Number}</TT></B></DT><DD CLASS="dd-description"> 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 <TT>ejabberd</TT>’s database.
|
||||
|
@ -1710,6 +1744,7 @@ the value previously stored in the database will be used instead of the default
|
|||
is <TT>""</TT> which means ‘anonymous connection’.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_password, Password}</TT></B></DT><DD CLASS="dd-description"> Bind password. The default
|
||||
value is <TT>""</TT>.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_deref_aliases, never|always|finding|searching}</TT></B></DT><DD CLASS="dd-description"> Whether or not to dereference aliases. The default is <TT>never</TT>.
|
||||
</DD></DL><P>Example:
|
||||
</P><PRE CLASS="verbatim">{auth_method, ldap}.
|
||||
{ldap_servers, ["ldap.example.org"]}.
|
||||
|
@ -1931,7 +1966,7 @@ all entries end with a comma:
|
|||
<TR><TD ALIGN=left NOWRAP><A HREF="#modoffline"><TT>mod_offline</TT></A></TD><TD ALIGN=left NOWRAP>Offline message storage (<A HREF="http://xmpp.org/extensions/xep-0160.html">XEP-0160</A>)</TD><TD ALIGN=left NOWRAP> </TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modoffline"><TT>mod_offline_odbc</TT></A></TD><TD ALIGN=left NOWRAP>Offline message storage (<A HREF="http://xmpp.org/extensions/xep-0160.html">XEP-0160</A>)</TD><TD ALIGN=left NOWRAP>supported DB (*)</TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modping"><TT>mod_ping</TT></A></TD><TD ALIGN=left NOWRAP>XMPP Ping and periodic keepalives (<A HREF="http://xmpp.org/extensions/xep-0199.html">XEP-0199</A>)</TD><TD ALIGN=left NOWRAP> </TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modprescounter"><TT>mod_privacy</TT></A></TD><TD ALIGN=left NOWRAP>Detect presence subscription flood</TD><TD ALIGN=left NOWRAP> </TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modprescounter"><TT>mod_pres_counter</TT></A></TD><TD ALIGN=left NOWRAP>Detect presence subscription flood</TD><TD ALIGN=left NOWRAP> </TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modprivacy"><TT>mod_privacy</TT></A></TD><TD ALIGN=left NOWRAP>Blocking Communication (<A HREF="http://xmpp.org/extensions/xep-0016.html">XEP-0016</A>)</TD><TD ALIGN=left NOWRAP> </TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modprivacy"><TT>mod_privacy_odbc</TT></A></TD><TD ALIGN=left NOWRAP>Blocking Communication (<A HREF="http://xmpp.org/extensions/xep-0016.html">XEP-0016</A>)</TD><TD ALIGN=left NOWRAP>supported DB (*)</TD></TR>
|
||||
<TR><TD ALIGN=left NOWRAP><A HREF="#modprivate"><TT>mod_private</TT></A></TD><TD ALIGN=left NOWRAP>Private XML Storage (<A HREF="http://xmpp.org/extensions/xep-0049.html">XEP-0049</A>)</TD><TD ALIGN=left NOWRAP> </TD></TR>
|
||||
|
@ -2077,7 +2112,7 @@ able to send such messages).
|
|||
</DD></DL><P>Examples:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Only administrators can send announcements:
|
||||
<PRE CLASS="verbatim">{access, announce, [{allow, admins}]}.
|
||||
<PRE CLASS="verbatim">{access, announce, [{allow, admin}]}.
|
||||
|
||||
{modules,
|
||||
[
|
||||
|
@ -2089,9 +2124,9 @@ Only administrators can send announcements:
|
|||
</PRE></LI><LI CLASS="li-itemize">Administrators as well as the direction can send announcements:
|
||||
<PRE CLASS="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,
|
||||
|
@ -2497,6 +2532,7 @@ The available room options and the default values are:
|
|||
<DL CLASS="description"><DT CLASS="dt-description">
|
||||
<B><TT>{allow_change_subj, true|false}</TT></B></DT><DD CLASS="dd-description"> Allow occupants to change the subject.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{allow_private_messages, true|false}</TT></B></DT><DD CLASS="dd-description"> Occupants can send private messages to other occupants.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{allow_private_messages_from_visitors, anyone|moderators|nobody}</TT></B></DT><DD CLASS="dd-description"> Visitors can send private messages to other occupants.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{allow_query_users, true|false}</TT></B></DT><DD CLASS="dd-description"> Occupants can send IQ queries to other occupants.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{allow_user_invites, false|true}</TT></B></DT><DD CLASS="dd-description"> Allow occupants to send invitations.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{allow_visitor_nickchange, true|false}</TT></B></DT><DD CLASS="dd-description"> Allow visitors to
|
||||
|
@ -2715,10 +2751,10 @@ used. The names of the log files will only contain the day (number),
|
|||
and there will be subdirectories for each year and month. The log files will
|
||||
be stored in /var/www/muclogs, and the local time will be used. Finally, the
|
||||
top link will be the default <CODE><a href="/">Home</a></CODE>.
|
||||
<PRE CLASS="verbatim">{acl, admins, {user, "admin1", "example.org"}}.
|
||||
{acl, admins, {user, "admin2", "example.net"}}.
|
||||
<PRE CLASS="verbatim">{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,
|
||||
|
@ -3115,7 +3151,9 @@ To enable this feature, configure the options captcha_cmd and captcha_host.</P><
|
|||
<B><TT>{registration_watchers, [ JID, ...]}</TT></B></DT><DD CLASS="dd-description"> This option defines a
|
||||
list of JIDs which will be notified each time a new account is registered.
|
||||
</DD></DL><P>This example configuration shows how to enable the module and the web handler:
|
||||
</P><PRE CLASS="verbatim">{listen, [
|
||||
</P><PRE CLASS="verbatim">{hosts, ["localhost", "example.org", "example.com"]}.
|
||||
|
||||
{listen, [
|
||||
...
|
||||
{5281, ejabberd_http, [
|
||||
tls,
|
||||
|
@ -3131,7 +3169,8 @@ list of JIDs which will be notified each time a new account is registered.
|
|||
{mod_register_web, []},
|
||||
...
|
||||
]}.
|
||||
</PRE><P>The users can visit this page: https://localhost:5281/register/
|
||||
</PRE><P>For example, the users of the host <TT>example.org</TT> can visit the page:
|
||||
<TT>https://example.org:5281/register/</TT>
|
||||
It is important to include the last / character in the URL,
|
||||
otherwise the subpages URL will be incorrect.</P><P> <A NAME="modroster"></A> </P><!--TOC subsection <TT>mod_roster</TT>-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc59">3.3.21</A>  <A HREF="#modroster"><TT>mod_roster</TT></A></H3><!--SEC END --><P> <A NAME="modroster"></A>
|
||||
|
@ -3598,8 +3637,9 @@ and LDAP server supports
|
|||
its own optional parameters. The first group of parameters has the same
|
||||
meaning as the top-level LDAP parameters to set the authentication method:
|
||||
<TT>ldap_servers</TT>, <TT>ldap_port</TT>, <TT>ldap_rootdn</TT>,
|
||||
<TT>ldap_password</TT>, <TT>ldap_base</TT>, <TT>ldap_uids</TT>, and
|
||||
<TT>ldap_filter</TT>. See section <A HREF="#ldapauth">3.2.5</A> for detailed information
|
||||
<TT>ldap_password</TT>, <TT>ldap_base</TT>, <TT>ldap_uids</TT>,
|
||||
<TT>ldap_deref_aliases</TT> and <TT>ldap_filter</TT>.
|
||||
See section <A HREF="#ldapauth">3.2.5</A> for detailed information
|
||||
about these options. If one of these options is not set, <TT>ejabberd</TT> will look
|
||||
for the top-level option with the same name.</P><P>The second group of parameters
|
||||
consists of the following <TT>mod_vcard_ldap</TT>-specific options:</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
|
@ -3865,6 +3905,8 @@ all the environment variables and command line parameters.</P><P>The environment
|
|||
This path is used to read the file <TT>.erlang.cookie</TT>.
|
||||
</DD><DT CLASS="dt-description"><B><TT>ERL_CRASH_DUMP</TT></B></DT><DD CLASS="dd-description">
|
||||
Path to the file where crash reports will be dumped.
|
||||
</DD><DT CLASS="dt-description"><B><TT>ERL_EPMD_ADDRESS</TT></B></DT><DD CLASS="dd-description">
|
||||
IP address where epmd listens for connections (see section <A HREF="#epmd">5.2</A>).
|
||||
</DD><DT CLASS="dt-description"><B><TT>ERL_INETRC</TT></B></DT><DD CLASS="dd-description">
|
||||
Indicates which IP name resolution to use.
|
||||
If using <TT>-sname</TT>, specify either this option or <TT>-kernel inetrc filepath</TT>.
|
||||
|
@ -3891,10 +3933,10 @@ This is only useful if you plan to setup an <TT>ejabberd</TT> cluster with nodes
|
|||
connections (see section <A HREF="#epmd">5.2</A>).
|
||||
</DD><DT CLASS="dt-description"><B><TT>-detached</TT></B></DT><DD CLASS="dd-description">
|
||||
Starts the Erlang system detached from the system console.
|
||||
Useful for running daemons and backgrounds processes.
|
||||
Useful for running daemons and background processes.
|
||||
</DD><DT CLASS="dt-description"><B><TT>-noinput</TT></B></DT><DD CLASS="dd-description">
|
||||
Ensures that the Erlang system never tries to read any input.
|
||||
Useful for running daemons and backgrounds processes.
|
||||
Useful for running daemons and background processes.
|
||||
</DD><DT CLASS="dt-description"><B><TT>-pa /var/lib/ejabberd/ebin</TT></B></DT><DD CLASS="dd-description">
|
||||
Specify the directory where Erlang binary files (*.beam) are located.
|
||||
</DD><DT CLASS="dt-description"><B><TT>-s ejabberd</TT></B></DT><DD CLASS="dd-description">
|
||||
|
@ -4059,11 +4101,11 @@ URL). If you log in with ‘<TT>admin@example.com</TT>’ on<BR>
|
|||
<CODE>http://example.org:5280/admin/server/example.com/</CODE> you can only
|
||||
administer the virtual host <TT>example.com</TT>.
|
||||
The account ‘<TT>reviewer@example.com</TT>’ can browse that vhost in read-only mode.
|
||||
<PRE CLASS="verbatim">{acl, admins, {user, "admin", "example.net"}}.
|
||||
{host_config, "example.com", [{acl, admins, {user, "admin", "example.com"}}]}.
|
||||
<PRE CLASS="verbatim">{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"]}.
|
||||
|
@ -4178,7 +4220,9 @@ and connects to the Erlang node that holds <TT>ejabberd</TT>.
|
|||
In order for this communication to work,
|
||||
<TT>epmd</TT> must be running and listening for name requests in the port 4369.
|
||||
You should block the port 4369 in the firewall in such a way that
|
||||
only the programs in your machine can access it.</P><P>If you build a cluster of several <TT>ejabberd</TT> instances,
|
||||
only the programs in your machine can access it.
|
||||
or configure the option <TT>ERL_EPMD_ADDRESS</TT> in the file <TT>ejabberdctl.cfg</TT>
|
||||
(this option works only in Erlang/OTP R14B03 or higher).</P><P>If you build a cluster of several <TT>ejabberd</TT> instances,
|
||||
each <TT>ejabberd</TT> instance is called an <TT>ejabberd</TT> node.
|
||||
Those <TT>ejabberd</TT> nodes use a special Erlang communication method to
|
||||
build the cluster, and EPMD is again needed listening in the port 4369.
|
||||
|
|
|
@ -250,8 +250,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,
|
||||
|
@ -1489,10 +1490,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:
|
||||
|
@ -2311,6 +2312,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:
|
||||
|
@ -4697,8 +4699,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.
|
||||
|
||||
|
|
56
doc/release_notes_2.1.9.txt
Normal file
56
doc/release_notes_2.1.9.txt
Normal file
|
@ -0,0 +1,56 @@
|
|||
|
||||
Release Notes
|
||||
ejabberd 2.1.9
|
||||
|
||||
ejabberd 2.1.9 is the eighth release in ejabberd 2.1.x branch,
|
||||
and includes a lot of bugfixes and improvements.
|
||||
|
||||
Read more details about the changes in:
|
||||
http://redir.process-one.net/ejabberd-2.1.9
|
||||
|
||||
Download the source code and installers from:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core ejabberd
|
||||
- Decrease CPU usage caused by tls:send with large data
|
||||
- Escape iolist correctly when NIFs are disabled (EJAB-1462)
|
||||
- Fix code to satisfy Dialyzer warnings
|
||||
- Fix compilation in Windows
|
||||
- Replace calls of OTP's Binary, since they would require R14
|
||||
|
||||
* LDAP
|
||||
- Document ldap_tls_cacertfile and ldap_tls_depth options (EJAB-1299)
|
||||
- Log an error when an LDAP filter is incorrect (EJAB-1395)
|
||||
- New options: ldap_tls_cacertfile and ldap_tls_depth (EJAB-1299)
|
||||
- New option: ldap_deref_aliases (EJAB-639)
|
||||
- Match ldap_uidattr_format case-insensitively (EJAB-1449)
|
||||
|
||||
* MUC
|
||||
- Support for multiple entry with same nick to MUC rooms (EJAB-305)
|
||||
- Support voice request and approvement
|
||||
- New room option: allow_private_messages_from_visitors
|
||||
- New room options: allow_voice_requests and voice_request_min_interval
|
||||
- Include status 110 in presence to new occupant (EJAB-740)
|
||||
- Fix mod_muc_log crash when first log entry is room destroy (EJAB-1499)
|
||||
- Many fixes and improvements in mod_muc
|
||||
|
||||
* Pubsub
|
||||
- Enable pubsub#deliver_notification checking (EJAB-1453)
|
||||
- Fix Denial of Service when user sends malformed publish stanza (EJAB-1498)
|
||||
|
||||
* ODBC
|
||||
- Fix ODBC account counting (EJAB-1491)
|
||||
- Optimized mod_roster_odbc:get_roster
|
||||
|
||||
* Miscellanea:
|
||||
- New SASL SCRAM-SHA-1 authentication mechanism (EJAB-1196)
|
||||
- New option: resource_conflict (EJAB-650)
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
|
@ -1,2 +1,2 @@
|
|||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.1.7}
|
||||
\newcommand{\version}{2.1.9}
|
||||
|
|
|
@ -55,6 +55,8 @@ 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 ->
|
||||
|
@ -89,11 +91,7 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
|||
_Else ->
|
||||
{error, "not-supported"}
|
||||
end
|
||||
end;
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
end
|
||||
end;
|
||||
_Else ->
|
||||
{error, "bad-protocol"}
|
||||
|
@ -139,18 +137,14 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
|||
parse_attribute(Attribute) ->
|
||||
AttributeLen = string:len(Attribute),
|
||||
if
|
||||
AttributeLen > 3 ->
|
||||
AttributeLen >= 3 ->
|
||||
SecondChar = lists:nth(2, Attribute),
|
||||
case is_alpha(lists:nth(1, Attribute)) of
|
||||
true ->
|
||||
if
|
||||
SecondChar == $= ->
|
||||
case string:substr(Attribute, 3) of
|
||||
String when is_list(String) ->
|
||||
{lists:nth(1, Attribute), String};
|
||||
_Else ->
|
||||
{error, "bad-format failed"}
|
||||
end;
|
||||
String = string:substr(Attribute, 3),
|
||||
{lists:nth(1, Attribute), String};
|
||||
true ->
|
||||
{error, "bad-format second char not equal sign"}
|
||||
end;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.2.x"},
|
||||
{vsn, "2.2.9"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
|
|
|
@ -478,7 +478,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
|
||||
|
|
|
@ -114,7 +114,7 @@ init() ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, expat_erl}, [binary]),
|
||||
Port = open_port({spawn, "expat_erl"}, [binary]),
|
||||
loop(Port).
|
||||
|
||||
|
||||
|
|
|
@ -306,19 +306,16 @@ is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
|
|||
end.
|
||||
|
||||
|
||||
%% @spec (User, Server) -> ok | error | {error, not_allowed}
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
R = lists:foreach(
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server)
|
||||
end, auth_modules(Server)),
|
||||
case R of
|
||||
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
|
||||
_ -> none
|
||||
end,
|
||||
R.
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]),
|
||||
ok.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
|
||||
%% @doc Try to remove user if the provided password is correct.
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
ufilter,
|
||||
sfilter,
|
||||
lfilter, %% Local filter (performed by ejabberd, not LDAP)
|
||||
deref_aliases,
|
||||
dn_filter,
|
||||
dn_filter_attrs
|
||||
}).
|
||||
|
@ -230,10 +231,12 @@ get_vh_registered_users_ldap(Server) ->
|
|||
ResAttrs = result_attrs(State),
|
||||
case eldap_filter:parse(State#state.sfilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(Eldap_ID, [{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{timeout, ?LDAP_SEARCH_TIMEOUT},
|
||||
{attributes, ResAttrs}]) of
|
||||
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,
|
||||
|
@ -285,6 +288,7 @@ find_user_dn(User, State) ->
|
|||
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} | _]} ->
|
||||
|
@ -322,10 +326,11 @@ is_valid_dn(DN, Attrs, State) ->
|
|||
end ++ [{"%d", State#state.host}, {"%D", DN}],
|
||||
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap_pool:search(State#state.eldap_id, [
|
||||
{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, ["dn"]}]) of
|
||||
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;
|
||||
_ ->
|
||||
|
@ -421,6 +426,11 @@ parse_options(Host) ->
|
|||
end,
|
||||
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,
|
||||
|
@ -438,6 +448,7 @@ parse_options(Host) ->
|
|||
ufilter = UserFilter,
|
||||
sfilter = SearchFilter,
|
||||
lfilter = LocalFilter,
|
||||
deref_aliases = DerefAliases,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
}.
|
||||
|
|
|
@ -269,8 +269,8 @@ try_call_command(Args, Auth, AccessCommands) ->
|
|||
try call_command(Args, Auth, AccessCommands) of
|
||||
{error, command_unknown} ->
|
||||
{io_lib:format("Error: command ~p not known.", [hd(Args)]), ?STATUS_ERROR};
|
||||
{error, wrong_number_parameters} ->
|
||||
{"Error: wrong number of parameters", ?STATUS_ERROR};
|
||||
{error, wrong_command_arguments} ->
|
||||
{"Error: wrong arguments", ?STATUS_ERROR};
|
||||
Res ->
|
||||
Res
|
||||
catch
|
||||
|
@ -637,7 +637,7 @@ print_usage_help(MaxC, ShCode) ->
|
|||
ArgsDef = [],
|
||||
C = #ejabberd_commands{
|
||||
desc = "Show help of ejabberd commands",
|
||||
longdesc = LongDesc,
|
||||
longdesc = lists:flatten(LongDesc),
|
||||
args = ArgsDef,
|
||||
result = {help, string}},
|
||||
print_usage_command("help", C, MaxC, ShCode).
|
||||
|
|
|
@ -195,7 +195,7 @@ process_element(El,State) ->
|
|||
|
||||
add_user(El, Domain) ->
|
||||
User = exmpp_xml:get_attribute(El, <<"name">>, none),
|
||||
PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, none),
|
||||
PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, <<"plaintext">>),
|
||||
Password = exmpp_xml:get_attribute(El, <<"password">>, none),
|
||||
add_user(El, Domain, User, PasswordFormat, Password).
|
||||
|
||||
|
|
|
@ -1082,33 +1082,29 @@ get_addr_port(Server) ->
|
|||
{ok, HEnt} ->
|
||||
?DEBUG("srv lookup of '~s': ~p~n",
|
||||
[Server, HEnt#hostent.h_addr_list]),
|
||||
case HEnt#hostent.h_addr_list of
|
||||
[] ->
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
AddrList ->
|
||||
%% Probabilities are not exactly proportional to weights
|
||||
%% for simplicity (higher weigths are overvalued)
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
case (catch lists:map(
|
||||
fun({Priority, Weight, Port, Host}) ->
|
||||
N = case Weight of
|
||||
0 -> 0;
|
||||
_ -> (Weight + 1) * random:uniform()
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
{'EXIT', _Reason} ->
|
||||
[{Server, outgoing_s2s_port()}];
|
||||
SortedList ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
{Host, Port}
|
||||
end, lists:keysort(1, SortedList)),
|
||||
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
|
||||
List
|
||||
end
|
||||
end
|
||||
AddrList = HEnt#hostent.h_addr_list,
|
||||
%% Probabilities are not exactly proportional to weights
|
||||
%% for simplicity (higher weigths are overvalued)
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
case (catch lists:map(
|
||||
fun({Priority, Weight, Port, Host}) ->
|
||||
N = case Weight of
|
||||
0 -> 0;
|
||||
_ -> (Weight + 1) * random:uniform()
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
SortedList = [_|_] ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
{Host, Port}
|
||||
end, lists:keysort(1, SortedList)),
|
||||
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
|
||||
List;
|
||||
_ ->
|
||||
[{Server, outgoing_s2s_port()}]
|
||||
end
|
||||
end.
|
||||
|
||||
srv_lookup(Server) ->
|
||||
|
|
|
@ -63,7 +63,7 @@ init([]) ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
|
||||
Port = open_port({spawn, "ejabberd_zlib_drv"}, [binary]),
|
||||
{ok, Port}.
|
||||
|
||||
|
||||
|
@ -99,7 +99,7 @@ enable_zlib(SockMod, Socket) ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
|
||||
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}) ->
|
||||
|
|
|
@ -324,21 +324,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
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
host = null, % Connected Host LDAP server
|
||||
port = 389, % The LDAP server port
|
||||
sockmod, % SockMod (gen_tcp|tls)
|
||||
tls = none, % LDAP/LDAPS (none|starttls|tls)
|
||||
tls = none, % LDAP/LDAPS (none|tls)
|
||||
tls_options = [],
|
||||
fd = null, % Socket filedescriptor.
|
||||
rootdn = "", % Name of the entry to bind as
|
||||
|
@ -323,6 +323,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) ->
|
||||
|
@ -424,8 +432,8 @@ get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
|
|||
%%----------------------------------------------------------------------
|
||||
init([]) ->
|
||||
case get_config() of
|
||||
{ok, Hosts, Rootdn, Passwd, Opts} ->
|
||||
init({Hosts, Rootdn, Passwd, Opts});
|
||||
{ok, Hosts, Port, Rootdn, Passwd, Opts} ->
|
||||
init({Hosts, Port, Rootdn, Passwd, Opts});
|
||||
{error, Reason} ->
|
||||
{stop, Reason}
|
||||
end;
|
||||
|
@ -441,8 +449,6 @@ init({Hosts, Port, Rootdn, Passwd, Opts}) ->
|
|||
case Encrypt of
|
||||
tls ->
|
||||
?LDAPS_PORT;
|
||||
starttls ->
|
||||
?LDAP_PORT;
|
||||
_ ->
|
||||
?LDAP_PORT
|
||||
end;
|
||||
|
@ -702,7 +708,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),
|
||||
|
@ -902,14 +908,9 @@ cancel_timer(Timer) ->
|
|||
|
||||
%%% Sanity check of received packet
|
||||
check_tag(Data) ->
|
||||
case asn1rt_ber_bin:decode_tag(Data) of
|
||||
{_Tag, Data1, _Rb} ->
|
||||
case asn1rt_ber_bin:decode_length(Data1) of
|
||||
{{_Len,_Data2}, _Rb2} -> ok;
|
||||
_ -> throw({error,decoded_tag_length})
|
||||
end;
|
||||
_ -> throw({error,decoded_tag})
|
||||
end.
|
||||
{_Tag, Data1, _Rb} = asn1rt_ber_bin:decode_tag(Data),
|
||||
{{_Len,_Data2}, _Rb2} = asn1rt_ber_bin:decode_length(Data1),
|
||||
ok.
|
||||
|
||||
close_and_retry(S, Timeout) ->
|
||||
catch (S#eldap.sockmod):close(S#eldap.fd),
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
limit = 0,
|
||||
attributes = [],
|
||||
types_only = false,
|
||||
deref_aliases = neverDerefAliases,
|
||||
timeout = 0}).
|
||||
|
||||
|
||||
|
|
|
@ -473,17 +473,18 @@ announce_commands(From, To,
|
|||
|
||||
-define(VVALUE(Val),
|
||||
{xmlelement, "value", [], [{xmlcdata, Val}]}).
|
||||
-define(VVALUEL(Val),
|
||||
case Val of
|
||||
"" -> [];
|
||||
_ -> [?VVALUE(Val)]
|
||||
end).
|
||||
-define(TVFIELD(Type, Var, Val),
|
||||
{xmlelement, "field", [{"type", Type},
|
||||
{"var", Var}],
|
||||
?VVALUEL(Val)}).
|
||||
vvaluel(Val)}).
|
||||
-define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
|
||||
|
||||
vvaluel(Val) ->
|
||||
case Val of
|
||||
"" -> [];
|
||||
_ -> [?VVALUE(Val)]
|
||||
end.
|
||||
|
||||
generate_adhoc_form(Lang, Node, ServerHost) ->
|
||||
LNode = tokenize(Node),
|
||||
{OldSubject, OldBody} = if (LNode == ?NS_ADMINL("edit-motd"))
|
||||
|
@ -512,12 +513,12 @@ generate_adhoc_form(Lang, Node, ServerHost) ->
|
|||
[{"var", "subject"},
|
||||
{"type", "text-single"},
|
||||
{"label", translate:translate(Lang, "Subject")}],
|
||||
?VVALUEL(OldSubject)},
|
||||
vvaluel(OldSubject)},
|
||||
{xmlelement, "field",
|
||||
[{"var", "body"},
|
||||
{"type", "text-multi"},
|
||||
{"label", translate:translate(Lang, "Message body")}],
|
||||
?VVALUEL(OldBody)}]
|
||||
vvaluel(OldBody)}]
|
||||
end}.
|
||||
|
||||
join_lines([]) ->
|
||||
|
|
|
@ -1403,9 +1403,7 @@ set_form(_From, Host, ["running nodes", ENode, "modules", "start"], _Lang, XData
|
|||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
-behaviour(gen_mod).
|
||||
|
||||
%% API
|
||||
-export([start_link/2, start/2, stop/1]).
|
||||
-export([start_link/2, start/2, stop/1, do_client_version/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
|
|
|
@ -52,7 +52,7 @@ init([]) ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, iconv_erl}, []),
|
||||
Port = open_port({spawn, "iconv_erl"}, []),
|
||||
ets:new(iconv_table, [set, public, named_table]),
|
||||
ets:insert(iconv_table, {port, Port}),
|
||||
{ok, Port}.
|
||||
|
|
|
@ -813,13 +813,9 @@ process_channel_topic_who(StateData, Chan, String) ->
|
|||
Words = string:tokens(String, " "),
|
||||
Msg1 = case Words of
|
||||
[_, "333", _, _Chan, Whoset , Timeset] ->
|
||||
case string:to_integer(Timeset) of
|
||||
{Unixtimeset, _Rest} ->
|
||||
"Topic for #" ++ Chan ++ " set by " ++ Whoset ++
|
||||
" at " ++ unixtime2string(Unixtimeset);
|
||||
_->
|
||||
"Topic for #" ++ Chan ++ " set by " ++ Whoset
|
||||
end;
|
||||
{Unixtimeset, _Rest} = string:to_integer(Timeset),
|
||||
"Topic for #" ++ Chan ++ " set by " ++ Whoset ++
|
||||
" at " ++ unixtime2string(Unixtimeset);
|
||||
[_, "333", _, _Chan, Whoset | _] ->
|
||||
"Topic for #" ++ Chan ++ " set by " ++ Whoset;
|
||||
_ ->
|
||||
|
@ -1327,15 +1323,12 @@ filter_mirc_colors(Msg) ->
|
|||
unixtime2string(Unixtime) ->
|
||||
Secs = Unixtime + calendar:datetime_to_gregorian_seconds(
|
||||
{{1970, 1, 1}, {0,0,0}}),
|
||||
case calendar:universal_time_to_local_time(
|
||||
calendar:gregorian_seconds_to_datetime(Secs)) of
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} ->
|
||||
lists:flatten(
|
||||
io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second]));
|
||||
_->
|
||||
"0000-00-00 00:00:00"
|
||||
end.
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
calendar:universal_time_to_local_time(
|
||||
calendar:gregorian_seconds_to_datetime(Secs)),
|
||||
lists:flatten(
|
||||
io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
|
||||
[Year, Month, Day, Hour, Minute, Second])).
|
||||
|
||||
toupper([C | Cs]) ->
|
||||
if
|
||||
|
|
|
@ -863,6 +863,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,
|
||||
|
@ -884,7 +885,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";
|
||||
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";
|
||||
|
@ -944,7 +945,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) ->
|
||||
|
|
|
@ -304,16 +304,107 @@ normal_state({route, From, "",
|
|||
NSD#state.room,
|
||||
make_opts(NSD));
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{next_state, normal_state, NSD};
|
||||
_ ->
|
||||
{next_state, normal_state,
|
||||
StateData}
|
||||
{next_state, normal_state, StateData}
|
||||
end;
|
||||
false ->
|
||||
{next_state, normal_state, StateData}
|
||||
end
|
||||
end;
|
||||
IsVoiceRequest ->
|
||||
NewStateData =
|
||||
case (StateData#state.config)#config.allow_voice_requests of
|
||||
true ->
|
||||
MinInterval = (StateData#state.config)
|
||||
#config.voice_request_min_interval,
|
||||
BareFrom = jlib:jid_remove_resource( jlib:jid_tolower(From)),
|
||||
NowPriority = -now_to_usec(now()),
|
||||
CleanPriority =
|
||||
NowPriority + MinInterval*1000000,
|
||||
Times = clean_treap(
|
||||
StateData#state.last_voice_request_time,
|
||||
CleanPriority),
|
||||
case treap:lookup(BareFrom, Times) of
|
||||
error ->
|
||||
Times1 = treap:insert(
|
||||
BareFrom,
|
||||
NowPriority,
|
||||
true, Times),
|
||||
NSD = StateData#state{last_voice_request_time = Times1},
|
||||
send_voice_request(From, NSD),
|
||||
NSD;
|
||||
{ok, _, _} ->
|
||||
ErrText = "Please, wait for "
|
||||
"a while before sending "
|
||||
"new voice request",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_NOT_ACCEPTABLE(
|
||||
Lang, ErrText)),
|
||||
ejabberd_router:route(
|
||||
StateData#state.jid,
|
||||
From, Err),
|
||||
StateData#state{
|
||||
last_voice_request_time =
|
||||
Times}
|
||||
end;
|
||||
false ->
|
||||
{next_state, normal_state, StateData}
|
||||
end
|
||||
ErrText = "Voice requests are "
|
||||
"disabled in this conference",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_FORBIDDEN(
|
||||
Lang, ErrText)),
|
||||
ejabberd_router:route(StateData#state.jid, From, Err),
|
||||
StateData
|
||||
end,
|
||||
{next_state, normal_state, NewStateData};
|
||||
IsVoiceApprovement ->
|
||||
NewStateData =
|
||||
case is_moderator(From, StateData) of
|
||||
true ->
|
||||
case extract_jid_from_voice_approvement(Els) of
|
||||
error ->
|
||||
ErrText = "Failed to extract "
|
||||
"JID from your voice "
|
||||
"request approvement",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_BAD_REQUEST(
|
||||
Lang, ErrText)),
|
||||
ejabberd_router:route(
|
||||
StateData#state.jid,
|
||||
From, Err),
|
||||
StateData;
|
||||
{ok, TargetJid} ->
|
||||
case is_visitor(
|
||||
TargetJid, StateData) of
|
||||
true ->
|
||||
Reason = [],
|
||||
NSD = set_role(
|
||||
TargetJid,
|
||||
participant,
|
||||
StateData),
|
||||
catch send_new_presence(
|
||||
TargetJid,
|
||||
Reason, NSD),
|
||||
NSD;
|
||||
_ ->
|
||||
StateData
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
ErrText = "Only moderators can "
|
||||
"approve voice requests",
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
?ERRT_NOT_ALLOWED(
|
||||
Lang, ErrText)),
|
||||
ejabberd_router:route(StateData#state.jid, From, Err),
|
||||
StateData
|
||||
end,
|
||||
{next_state, normal_state, NewStateData};
|
||||
true ->
|
||||
{next_state, normal_state, StateData}
|
||||
end;
|
||||
_ ->
|
||||
ErrText = "Improper message type",
|
||||
|
@ -481,7 +572,29 @@ normal_state({route, From, ToNick,
|
|||
jlib:jid_replace_resource(
|
||||
StateData#state.jid,
|
||||
ToNick),
|
||||
From, Err)
|
||||
From, Err);
|
||||
ToJIDs ->
|
||||
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 = jlib: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 = jlib:make_error_reply(
|
||||
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
|
||||
ejabberd_router:route(
|
||||
jlib:jid_replace_resource(
|
||||
StateData#state.jid,
|
||||
ToNick),
|
||||
From, Err)
|
||||
end
|
||||
end
|
||||
end;
|
||||
{true, false} ->
|
||||
|
@ -1250,19 +1363,9 @@ expulse_participant(Packet, From, StateData, Reason1) ->
|
|||
|
||||
|
||||
set_affiliation(JID, Affiliation, StateData) ->
|
||||
LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
|
||||
Affiliations = case Affiliation of
|
||||
none ->
|
||||
?DICT:erase(LJID,
|
||||
StateData#state.affiliations);
|
||||
_ ->
|
||||
?DICT:store(LJID,
|
||||
Affiliation,
|
||||
StateData#state.affiliations)
|
||||
end,
|
||||
StateData#state{affiliations = Affiliations}.
|
||||
set_affiliation(JID, Affiliation, StateData, "").
|
||||
|
||||
set_affiliation_and_reason(JID, Affiliation, Reason, StateData) ->
|
||||
set_affiliation(JID, Affiliation, StateData, Reason) ->
|
||||
LJID = jlib:jid_remove_resource(jlib:jid_tolower(JID)),
|
||||
Affiliations = case Affiliation of
|
||||
none ->
|
||||
|
@ -1582,7 +1685,7 @@ remove_online_user(JID, StateData, Reason) ->
|
|||
?DICT:erase(Nick, StateData#state.nicks);
|
||||
{ok, U} ->
|
||||
?DICT:store(Nick, U -- [LJID], StateData#state.nicks);
|
||||
false ->
|
||||
error ->
|
||||
StateData#state.nicks
|
||||
end,
|
||||
StateData#state{users = Users, nicks = Nicks}.
|
||||
|
@ -1693,6 +1796,12 @@ get_priority_from_presence(PresencePacket) ->
|
|||
end
|
||||
end.
|
||||
|
||||
find_nick_by_jid(Jid, StateData) ->
|
||||
[{_, #user{nick = Nick}}] = lists:filter(
|
||||
fun({_, #user{jid = FJid}}) -> FJid == Jid end,
|
||||
?DICT:to_list(StateData#state.users)),
|
||||
Nick.
|
||||
|
||||
is_nick_change(JID, Nick, StateData) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
case Nick of
|
||||
|
@ -1726,7 +1835,6 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) ->
|
|||
mod_muc, max_user_conferences, 10),
|
||||
Collision = nick_collision(From, Nick, StateData),
|
||||
case {(ServiceAffiliation == owner orelse
|
||||
MaxUsers == none orelse
|
||||
((Affiliation == admin orelse Affiliation == owner) andalso
|
||||
NUsers < MaxAdminUsers) orelse
|
||||
NUsers < MaxUsers) andalso
|
||||
|
@ -2222,7 +2330,7 @@ change_nick(JID, Nick, StateData) ->
|
|||
?DICT:store(OldNick, OldNickUsers -- [LJID],
|
||||
StateData#state.nicks))
|
||||
end,
|
||||
NewStateData = StateData#state{users = Users, nicks = Nicks},
|
||||
NewStateData = StateData#state{users = Users, nicks = Nicks},
|
||||
send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable),
|
||||
add_to_log(nickchange, {OldNick, Nick}, StateData),
|
||||
NewStateData.
|
||||
|
@ -2540,18 +2648,18 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
|
|||
{JID, affiliation, outcast, Reason} ->
|
||||
catch send_kickban_presence(
|
||||
JID, Reason, "301", outcast, SD),
|
||||
set_affiliation_and_reason(
|
||||
JID, outcast, Reason,
|
||||
set_role(JID, none, SD));
|
||||
set_affiliation(
|
||||
JID, outcast,
|
||||
set_role(JID, none, SD), Reason);
|
||||
{JID, affiliation, A, Reason} when
|
||||
(A == admin) or (A == owner) ->
|
||||
SD1 = set_affiliation_and_reason(JID, A, Reason, SD),
|
||||
SD1 = set_affiliation(JID, A, SD, Reason),
|
||||
SD2 = set_role(JID, moderator, SD1),
|
||||
send_update_presence(JID, Reason, SD2),
|
||||
SD2;
|
||||
{JID, affiliation, member, Reason} ->
|
||||
SD1 = set_affiliation_and_reason(
|
||||
JID, member, Reason, SD),
|
||||
SD1 = set_affiliation(
|
||||
JID, member, SD, Reason),
|
||||
SD2 = set_role(JID, participant, SD1),
|
||||
send_update_presence(JID, Reason, SD2),
|
||||
SD2;
|
||||
|
@ -2572,7 +2680,7 @@ process_admin_items_set(UJID, Items, Lang, StateData) ->
|
|||
NSD ->
|
||||
NSD
|
||||
end
|
||||
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,
|
||||
|
@ -2604,12 +2712,12 @@ find_changed_items(UJID, UAffiliation, URole,
|
|||
"Jabber ID ~s is invalid"), [S]),
|
||||
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)};
|
||||
J ->
|
||||
{value, J}
|
||||
{value, [J]}
|
||||
end;
|
||||
_ ->
|
||||
case xml:get_attr("nick", Attrs) of
|
||||
{value, N} ->
|
||||
case find_jid_by_nick(N, StateData) of
|
||||
case find_jids_by_nick(N, StateData) of
|
||||
false ->
|
||||
ErrText =
|
||||
io_lib:format(
|
||||
|
@ -2626,7 +2734,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 xml:get_attr("role", Attrs) of
|
||||
|
@ -2676,16 +2784,13 @@ find_changed_items(UJID, UAffiliation, URole,
|
|||
Items, Lang, StateData,
|
||||
Res);
|
||||
true ->
|
||||
Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]),
|
||||
MoreRes = [{jlib:jid_remove_resource(Jidx), affiliation, SAffiliation, Reason} || Jidx <- JIDs],
|
||||
find_changed_items(
|
||||
UJID,
|
||||
UAffiliation, URole,
|
||||
Items, Lang, StateData,
|
||||
[{jlib:jid_remove_resource(JID),
|
||||
affiliation,
|
||||
SAffiliation,
|
||||
xml:get_path_s(
|
||||
Item, [{elem, "reason"},
|
||||
cdata])} | Res]);
|
||||
[MoreRes | Res]);
|
||||
false ->
|
||||
{error, ?ERR_NOT_ALLOWED}
|
||||
end
|
||||
|
@ -2733,14 +2838,13 @@ find_changed_items(UJID, UAffiliation, URole,
|
|||
Items, Lang, StateData,
|
||||
Res);
|
||||
true ->
|
||||
Reason = xml:get_path_s(Item, [{elem, "reason"}, cdata]),
|
||||
MoreRes = [{Jidx, role, SRole, Reason} || Jidx <- JIDs],
|
||||
find_changed_items(
|
||||
UJID,
|
||||
UAffiliation, URole,
|
||||
Items, Lang, StateData,
|
||||
[{JID, role, SRole,
|
||||
xml:get_path_s(
|
||||
Item, [{elem, "reason"},
|
||||
cdata])} | Res]);
|
||||
[MoreRes | Res]);
|
||||
_ ->
|
||||
{error, ?ERR_NOT_ALLOWED}
|
||||
end
|
||||
|
@ -3291,7 +3395,13 @@ get_config(Lang, StateData, From) ->
|
|||
Config#config.allow_visitor_status),
|
||||
?BOOLXFIELD("Allow visitors to change nickname",
|
||||
"muc#roomconfig_allowvisitornickchange",
|
||||
Config#config.allow_visitor_nickchange)
|
||||
Config#config.allow_visitor_nickchange),
|
||||
?BOOLXFIELD("Allow visitors to send voice requests",
|
||||
"muc#roomconfig_allowvoicerequests",
|
||||
Config#config.allow_voice_requests),
|
||||
?STRINGXFIELD("Minimum interval between voice requests (in seconds)",
|
||||
"muc#roomconfig_voicerequestmininterval",
|
||||
erlang:integer_to_list(Config#config.voice_request_min_interval))
|
||||
] ++
|
||||
case ejabberd_captcha:is_feature_available() of
|
||||
true ->
|
||||
|
@ -3434,12 +3544,16 @@ set_xoption([{"muc#roomconfig_roomsecret", [Val]} | Opts], Config) ->
|
|||
?SET_STRING_XOPT(password, Val);
|
||||
set_xoption([{"anonymous", [Val]} | Opts], Config) ->
|
||||
?SET_BOOL_XOPT(anonymous, Val);
|
||||
set_xoption([{"muc#roomconfig_allowvoicerequests", [Val]} | Opts], Config) ->
|
||||
?SET_BOOL_XOPT(allow_voice_requests, Val);
|
||||
set_xoption([{"muc#roomconfig_voicerequestmininterval", [Val]} | Opts], Config) ->
|
||||
?SET_NAT_XOPT(voice_request_min_interval, Val);
|
||||
set_xoption([{"muc#roomconfig_whois", [Val]} | Opts], Config) ->
|
||||
case Val of
|
||||
"moderators" ->
|
||||
?SET_BOOL_XOPT(anonymous, "1");
|
||||
?SET_BOOL_XOPT(anonymous, integer_to_list(1));
|
||||
"anyone" ->
|
||||
?SET_BOOL_XOPT(anonymous, "0");
|
||||
?SET_BOOL_XOPT(anonymous, integer_to_list(0));
|
||||
_ ->
|
||||
{error, ?ERR_BAD_REQUEST}
|
||||
end;
|
||||
|
@ -3526,6 +3640,8 @@ set_opts([{Opt, Val} | Opts], StateData) ->
|
|||
anonymous -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}};
|
||||
logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}};
|
||||
captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist = ?SETS:from_list(Val)}};
|
||||
allow_voice_requests -> StateData#state{config = (StateData#state.config)#config{allow_voice_requests = Val}};
|
||||
voice_request_min_interval -> StateData#state{config = (StateData#state.config)#config{voice_request_min_interval = Val}};
|
||||
max_users ->
|
||||
ServiceMaxUsers = get_service_max_users(StateData),
|
||||
MaxUsers = if
|
||||
|
@ -3571,6 +3687,8 @@ make_opts(StateData) ->
|
|||
?MAKE_CONFIG_OPT(anonymous),
|
||||
?MAKE_CONFIG_OPT(logging),
|
||||
?MAKE_CONFIG_OPT(max_users),
|
||||
?MAKE_CONFIG_OPT(allow_voice_requests),
|
||||
?MAKE_CONFIG_OPT(voice_request_min_interval),
|
||||
{captcha_whitelist,
|
||||
?SETS:to_list((StateData#state.config)#config.captcha_whitelist)},
|
||||
{affiliations, ?DICT:to_list(StateData#state.affiliations)},
|
||||
|
@ -3732,9 +3850,136 @@ get_mucroom_disco_items(StateData) ->
|
|||
end,
|
||||
?DICT:to_list(StateData#state.users)).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Voice request support
|
||||
|
||||
is_voice_request(Els) ->
|
||||
lists:foldl(
|
||||
fun({xmlelement, "x", Attrs, _} = El, false) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_XDATA ->
|
||||
case jlib:parse_xdata_submit(El) of
|
||||
[_|_] = Fields ->
|
||||
case {lists:keysearch("FORM_TYPE", 1, Fields),
|
||||
lists:keysearch("muc#role", 1, Fields)} of
|
||||
{{value,
|
||||
{_, ["http://jabber.org/protocol/muc#request"]}},
|
||||
{value, {_, ["participant"]}}} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, false, Els).
|
||||
|
||||
prepare_request_form(Requester, Nick, Lang) ->
|
||||
{xmlelement, "message", [{"type", "normal"}],
|
||||
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
|
||||
[{xmlelement, "title", [],
|
||||
[{xmlcdata, translate:translate(Lang, "Voice request")}]},
|
||||
{xmlelement, "instructions", [],
|
||||
[{xmlcdata,
|
||||
translate:translate(
|
||||
Lang, "Either approve or decline the voice request.")}]},
|
||||
{xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
|
||||
[{xmlelement, "value", [],
|
||||
[{xmlcdata, "http://jabber.org/protocol/muc#request"}]}]},
|
||||
{xmlelement, "field", [{"var", "muc#role"}, {"type", "hidden"}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, "participant"}]}]},
|
||||
?STRINGXFIELD("User JID", "muc#jid", jlib:jid_to_string(Requester)),
|
||||
?STRINGXFIELD("Nickname", "muc#roomnick", Nick),
|
||||
?BOOLXFIELD("Grant voice to this person?", "muc#request_allow",
|
||||
list_to_atom("false"))
|
||||
]}]}.
|
||||
|
||||
send_voice_request(From, StateData) ->
|
||||
Moderators = search_role(moderator, StateData),
|
||||
FromNick = find_nick_by_jid(From, StateData),
|
||||
lists:foreach(
|
||||
fun({_, User}) ->
|
||||
ejabberd_router:route(
|
||||
StateData#state.jid,
|
||||
User#user.jid,
|
||||
prepare_request_form(From, FromNick, ""))
|
||||
end, Moderators).
|
||||
|
||||
is_voice_approvement(Els) ->
|
||||
lists:foldl(
|
||||
fun({xmlelement, "x", Attrs, _} = El, false) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_XDATA ->
|
||||
case jlib:parse_xdata_submit(El) of
|
||||
[_|_] = Fs ->
|
||||
case {lists:keysearch("FORM_TYPE", 1, Fs),
|
||||
lists:keysearch("muc#role", 1, Fs),
|
||||
lists:keysearch("muc#request_allow", 1, Fs)} of
|
||||
{{value,
|
||||
{_, ["http://jabber.org/protocol/muc#request"]}},
|
||||
{value, {_, ["participant"]}},
|
||||
{value, {_, [Flag]}}}
|
||||
when Flag == "true"; Flag == "1" ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, false, Els).
|
||||
|
||||
extract_jid_from_voice_approvement(Els) ->
|
||||
lists:foldl(
|
||||
fun({xmlelement, "x", _, _} = El, error) ->
|
||||
Fields = case jlib:parse_xdata_submit(El) of
|
||||
invalid -> [];
|
||||
Res -> Res
|
||||
end,
|
||||
lists:foldl(
|
||||
fun({"muc#jid", [JIDStr]}, error) ->
|
||||
case jlib:string_to_jid(JIDStr) of
|
||||
error -> error;
|
||||
J -> {ok, J}
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, error, Fields);
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, error, Els).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Invitation support
|
||||
|
||||
is_invitation(Els) ->
|
||||
lists:foldl(
|
||||
fun({xmlelement, "x", Attrs, _} = El, false) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_MUC_USER ->
|
||||
case xml:get_subtag(El, "invite") of
|
||||
false ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, false, Els).
|
||||
|
||||
check_invitation(From, Els, Lang, StateData) ->
|
||||
FAffiliation = get_affiliation(From, StateData),
|
||||
CanInvite = (StateData#state.config)#config.allow_user_invites
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
password_protected = false,
|
||||
password = "",
|
||||
anonymous = true,
|
||||
allow_voice_requests = true,
|
||||
voice_request_min_interval = 1800,
|
||||
max_users = ?MAX_USERS_DEFAULT,
|
||||
logging = false,
|
||||
captcha_whitelist = ?SETS:empty()
|
||||
|
@ -69,6 +71,7 @@
|
|||
jid,
|
||||
config = #config{},
|
||||
users = ?DICT:new(),
|
||||
last_voice_request_time = treap:empty(),
|
||||
robots = ?DICT:new(),
|
||||
nicks = ?DICT:new(),
|
||||
affiliations = ?DICT:new(),
|
||||
|
|
|
@ -2113,8 +2113,11 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
|||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, Options, ItemId, jlib:jid_tolower(Publisher), BrPayload, Removed),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, BrPayload),
|
||||
case get_option(Options, deliver_notifications) of
|
||||
true -> broadcast_publish_item(Host, Node, NodeId, Type, Options, ItemId, jlib:jid_tolower(Publisher), BrPayload, Removed);
|
||||
false -> ok
|
||||
end,
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
|
@ -2145,8 +2148,10 @@ 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, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "create", [{"node", NewNode}], []}]}]} ->
|
||||
publish_item(Host, ServerHost, list_to_binary(NewNode),
|
||||
Publisher, ItemId, Payload);
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end;
|
||||
|
|
|
@ -1880,8 +1880,11 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) ->
|
|||
PluginPayload -> PluginPayload
|
||||
end,
|
||||
ejabberd_hooks:run(pubsub_publish_item, ServerHost, [ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
|
||||
broadcast_publish_item(Host, Node, NodeId, Type, Options, ItemId, jlib:jid_tolower(Publisher), BrPayload, Removed),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, Payload),
|
||||
set_cached_item(Host, NodeId, ItemId, Publisher, BrPayload),
|
||||
case get_option(Options, deliver_notifications) of
|
||||
true -> broadcast_publish_item(Host, Node, NodeId, Type, Options, ItemId, jlib:jid_tolower(Publisher), BrPayload, Removed);
|
||||
false -> ok
|
||||
end,
|
||||
case Result of
|
||||
default -> {result, Reply};
|
||||
_ -> {result, Result}
|
||||
|
@ -1912,8 +1915,10 @@ 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, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "create", [{"node", NewNode}], []}]}]} ->
|
||||
publish_item(Host, ServerHost, list_to_binary(NewNode),
|
||||
Publisher, ItemId, Payload);
|
||||
_ ->
|
||||
{error, ?ERR_ITEM_NOT_FOUND}
|
||||
end;
|
||||
|
|
|
@ -136,10 +136,7 @@ create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
|
|||
{result, Allowed}.
|
||||
|
||||
create_node(NodeId, Owner) ->
|
||||
case node_hometree:create_node(NodeId, Owner) of
|
||||
{result, _} -> {result, []};
|
||||
Error -> Error
|
||||
end.
|
||||
node_hometree:create_node(NodeId, Owner).
|
||||
|
||||
delete_node(Removed) ->
|
||||
case node_hometree:delete_node(Removed) of
|
||||
|
|
|
@ -215,7 +215,7 @@ get_entity_subscriptions(_Host, Owner) ->
|
|||
{selected, ["host", "node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
|
||||
lists:map(fun({H, N, T, I, J, S}) ->
|
||||
O = node_hometree_odbc:decode_jid(H),
|
||||
Node = nodetree_odbc:raw_to_node(O, {N, "", T, I}),
|
||||
Node = nodetree_tree_odbc:raw_to_node(O, {N, "", T, I}),
|
||||
{Node, node_hometree_odbc:decode_subscriptions(S), node_hometree_odbc:decode_jid(J)}
|
||||
end, RItems);
|
||||
_ ->
|
||||
|
@ -249,7 +249,7 @@ get_entity_subscriptions_for_send_last(_Host, Owner) ->
|
|||
{selected, ["host", "node", "type", "nodeid", "jid", "subscriptions"], RItems} ->
|
||||
lists:map(fun({H, N, T, I, J, S}) ->
|
||||
O = node_hometree_odbc:decode_jid(H),
|
||||
Node = nodetree_odbc:raw_to_node(O, {N, "", T, I}),
|
||||
Node = nodetree_tree_odbc:raw_to_node(O, {N, "", T, I}),
|
||||
{Node, node_hometree_odbc:decode_subscriptions(S), node_hometree_odbc:decode_jid(J)}
|
||||
end, RItems);
|
||||
_ ->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
--- mod_pubsub.erl 2011-08-29 09:35:41.000000000 +0200
|
||||
+++ mod_pubsub_odbc.erl 2011-09-26 16:35:05.000000000 +0200
|
||||
--- mod_pubsub.erl 2011-09-30 13:59:23.000000000 +0200
|
||||
+++ mod_pubsub_odbc.erl 2011-09-30 14:04:34.000000000 +0200
|
||||
@@ -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.
|
||||
|
@ -580,7 +580,7 @@
|
|||
{PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups),
|
||||
if
|
||||
not SubscribeFeature ->
|
||||
@@ -2278,7 +2045,7 @@
|
||||
@@ -2283,7 +2050,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.
|
||||
|
@ -589,7 +589,7 @@
|
|||
MaxItems =
|
||||
if
|
||||
SMaxItems == "" -> get_max_items_node(Host);
|
||||
@@ -2292,12 +2059,13 @@
|
||||
@@ -2297,12 +2064,13 @@
|
||||
{error, Error} ->
|
||||
{error, Error};
|
||||
_ ->
|
||||
|
@ -604,7 +604,7 @@
|
|||
{PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
|
||||
if
|
||||
not RetreiveFeature ->
|
||||
@@ -2310,11 +2078,11 @@
|
||||
@@ -2315,11 +2083,11 @@
|
||||
node_call(Type, get_items,
|
||||
[NodeId, From,
|
||||
AccessModel, PresenceSubscription, RosterGroup,
|
||||
|
@ -618,7 +618,7 @@
|
|||
SendItems = case ItemIDs of
|
||||
[] ->
|
||||
Items;
|
||||
@@ -2327,7 +2095,8 @@
|
||||
@@ -2332,7 +2100,8 @@
|
||||
%% number of items sent to MaxItems:
|
||||
{result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
|
@ -628,7 +628,7 @@
|
|||
Error ->
|
||||
Error
|
||||
end
|
||||
@@ -2349,10 +2118,15 @@
|
||||
@@ -2354,10 +2123,15 @@
|
||||
Error -> Error
|
||||
end.
|
||||
get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
|
||||
|
@ -645,7 +645,7 @@
|
|||
|
||||
|
||||
%% @spec (Host, Node, NodeId, Type, LJID, Number) -> any()
|
||||
@@ -2365,27 +2139,27 @@
|
||||
@@ -2370,27 +2144,27 @@
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
%% @todo use cache-last-item feature
|
||||
send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, last) ->
|
||||
|
@ -689,7 +689,7 @@
|
|||
end
|
||||
end;
|
||||
send_items(Host, Node, NodeId, Type, {U,S,R} = LJID, Number) ->
|
||||
@@ -2526,7 +2300,8 @@
|
||||
@@ -2531,7 +2305,8 @@
|
||||
error ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
|
@ -699,7 +699,7 @@
|
|||
case lists:member(Owner, Owners) of
|
||||
true ->
|
||||
OwnerJID = jlib:make_jid(Owner),
|
||||
@@ -2536,24 +2311,7 @@
|
||||
@@ -2541,24 +2316,7 @@
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({JID, Affiliation}) ->
|
||||
|
@ -725,7 +725,7 @@
|
|||
end, FilteredEntities),
|
||||
{result, []};
|
||||
_ ->
|
||||
@@ -2606,9 +2364,9 @@
|
||||
@@ -2611,9 +2369,9 @@
|
||||
end.
|
||||
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
|
||||
|
@ -737,7 +737,7 @@
|
|||
OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
|
||||
{"subid", SubID}|nodeAttr(Node)],
|
||||
[XdataEl]},
|
||||
@@ -2640,7 +2398,7 @@
|
||||
@@ -2645,7 +2403,7 @@
|
||||
end.
|
||||
|
||||
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
|
||||
|
@ -746,7 +746,7 @@
|
|||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -2839,8 +2597,8 @@
|
||||
@@ -2844,8 +2602,8 @@
|
||||
{"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
|
||||
end,
|
||||
|
@ -757,7 +757,7 @@
|
|||
true ->
|
||||
Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
|
||||
|
||||
@@ -3203,7 +2961,7 @@
|
||||
@@ -3208,7 +2966,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,
|
||||
|
@ -766,7 +766,7 @@
|
|||
{result, CollSubs} -> subscribed_nodes_by_jid(NotifyType, CollSubs);
|
||||
_ -> []
|
||||
end.
|
||||
@@ -3272,8 +3030,8 @@
|
||||
@@ -3277,8 +3035,8 @@
|
||||
[]
|
||||
end.
|
||||
sub_with_options(JID, NodeId, SubId) ->
|
||||
|
@ -777,7 +777,7 @@
|
|||
_ -> {JID, SubId, []}
|
||||
end.
|
||||
|
||||
@@ -3349,6 +3107,30 @@
|
||||
@@ -3354,6 +3112,30 @@
|
||||
Result
|
||||
end.
|
||||
|
||||
|
@ -808,7 +808,7 @@
|
|||
%% @spec (Host, Options) -> MaxItems
|
||||
%% Host = host()
|
||||
%% Options = [Option]
|
||||
@@ -3479,7 +3261,7 @@
|
||||
@@ -3484,7 +3266,7 @@
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, transaction) of
|
||||
|
@ -817,7 +817,7 @@
|
|||
NodeId = TNode#pubsub_node.id,
|
||||
Type = TNode#pubsub_node.type,
|
||||
Options = TNode#pubsub_node.options,
|
||||
@@ -3745,7 +3527,13 @@
|
||||
@@ -3750,7 +3532,13 @@
|
||||
tree_action(Host, Function, Args) ->
|
||||
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
|
||||
Fun = fun() -> tree_call(Host, Function, Args) end,
|
||||
|
@ -832,7 +832,7 @@
|
|||
|
||||
%% @doc <p>node plugin call.</p>
|
||||
node_call(Type, Function, Args) ->
|
||||
@@ -3765,13 +3553,13 @@
|
||||
@@ -3770,13 +3558,13 @@
|
||||
|
||||
node_action(Host, Type, Function, Args) ->
|
||||
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
|
||||
|
@ -848,7 +848,7 @@
|
|||
case tree_call(Host, get_node, [Host, Node]) of
|
||||
N when is_record(N, pubsub_node) ->
|
||||
case Action(N) of
|
||||
@@ -3783,13 +3571,19 @@
|
||||
@@ -3788,13 +3576,19 @@
|
||||
Error
|
||||
end
|
||||
end, Trans).
|
||||
|
@ -872,7 +872,7 @@
|
|||
{result, Result} -> {result, Result};
|
||||
{error, Error} -> {error, Error};
|
||||
{atomic, {result, Result}} -> {result, Result};
|
||||
@@ -3797,6 +3591,15 @@
|
||||
@@ -3802,6 +3596,15 @@
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
|
@ -888,7 +888,7 @@
|
|||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
@@ -3805,6 +3608,17 @@
|
||||
@@ -3810,6 +3613,17 @@
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1,
|
||||
item_to_xml/1,
|
||||
webadmin_menu/3, webadmin_page/3,
|
||||
get_user_roster/2,
|
||||
get_subscription_lists/3,
|
||||
|
@ -616,14 +617,15 @@ add_user_to_group(Host, US, Group) ->
|
|||
case regexp:match(LUser, "^@.+@$") 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),
|
||||
|
@ -651,7 +653,9 @@ remove_user_from_group(Host, 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 ->
|
||||
|
@ -726,8 +730,6 @@ displayed_to_groups(GroupName, LServer) ->
|
|||
lists:member(GroupName, proplists:get_value(displayed_groups, Opts, []))
|
||||
end, GroupsOpts).
|
||||
|
||||
push_item(_User, _Server, _From, none) ->
|
||||
ok;
|
||||
push_item(User, Server, From, Item) ->
|
||||
%% It was
|
||||
%% ejabberd_sm:route(jlib:make_jid("", "", ""),
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
base,
|
||||
password,
|
||||
uid,
|
||||
deref_aliases,
|
||||
group_attr,
|
||||
group_desc,
|
||||
user_desc,
|
||||
|
@ -314,6 +315,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.
|
||||
|
@ -659,6 +661,15 @@ 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,
|
||||
|
@ -672,6 +683,7 @@ parse_options(Host, Opts) ->
|
|||
base = LDAPBase,
|
||||
password = Password,
|
||||
uid = UIDAttr,
|
||||
deref_aliases = DerefAliases,
|
||||
group_attr = GroupAttr,
|
||||
group_desc = GroupDesc,
|
||||
user_desc = UserDesc,
|
||||
|
|
|
@ -167,12 +167,7 @@ get_local_stat(_Server, [], Name) when Name == "users/all-hosts/total" ->
|
|||
ejabberd_auth:get_vh_registered_users_number(Host)
|
||||
+ Total
|
||||
end, 0, ejabberd_config:get_global_option(hosts)),
|
||||
case NumUsers of
|
||||
{'EXIT', _Reason} ->
|
||||
?STATERR("500", "Internal Server Error");
|
||||
Users ->
|
||||
?STATVAL(integer_to_list(Users), "users")
|
||||
end;
|
||||
?STATVAL(integer_to_list(NumUsers), "users");
|
||||
|
||||
get_local_stat(_Server, _, Name) ->
|
||||
?STATERR("404", "Not Found").
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
search_fields,
|
||||
search_reported,
|
||||
search_reported_attrs,
|
||||
deref_aliases,
|
||||
matches
|
||||
}).
|
||||
|
||||
|
@ -287,9 +288,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;
|
||||
_ ->
|
||||
|
@ -572,10 +575,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);
|
||||
_ ->
|
||||
|
@ -779,6 +784,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,
|
||||
|
@ -801,5 +815,6 @@ parse_options(Host, Opts) ->
|
|||
search_fields = SearchFields,
|
||||
search_reported = SearchReported,
|
||||
search_reported_attrs = SearchReportedAttrs,
|
||||
deref_aliases = DerefAliases,
|
||||
matches = Matches
|
||||
}.
|
||||
|
|
|
@ -458,7 +458,7 @@ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug,
|
|||
handle_msg(Msg, Parent, Name, StateName, StateData,
|
||||
Mod, Time, Limits, Queue, QueueLen);
|
||||
_Msg ->
|
||||
Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
|
||||
Debug1 = sys:handle_debug(Debug, fun print_event/3,
|
||||
{Name, StateName}, {in, Msg}),
|
||||
handle_msg(Msg, Parent, Name, StateName, StateData,
|
||||
Mod, Time, Debug1, Limits, Queue, QueueLen)
|
||||
|
@ -472,6 +472,8 @@ system_continue(Parent, Debug, [Name, StateName, StateData,
|
|||
loop(Parent, Name, StateName, StateData, Mod, Time, Debug,
|
||||
Limits, Queue, QueueLen).
|
||||
|
||||
-spec system_terminate(term(), _, _, [term(),...]) -> no_return().
|
||||
|
||||
system_terminate(Reason, _Parent, Debug,
|
||||
[Name, StateName, StateData, Mod, _Time,
|
||||
_Limits, Queue, _QueueLen]) ->
|
||||
|
@ -603,12 +605,12 @@ handle_msg(Msg, Parent, Name, StateName, StateData,
|
|||
From = from(Msg),
|
||||
case catch dispatch(Msg, Mod, StateName, StateData) of
|
||||
{next_state, NStateName, NStateData} ->
|
||||
Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
|
||||
Debug1 = sys:handle_debug(Debug, fun print_event/3,
|
||||
{Name, NStateName}, return),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, infinity, Debug1, Limits, Queue, QueueLen);
|
||||
{next_state, NStateName, NStateData, Time1} ->
|
||||
Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
|
||||
Debug1 = sys:handle_debug(Debug, fun print_event/3,
|
||||
{Name, NStateName}, return),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, Time1, Debug1, Limits, Queue, QueueLen);
|
||||
|
@ -689,7 +691,7 @@ reply({To, Tag}, Reply) ->
|
|||
|
||||
reply(Name, {To, Tag}, Reply, Debug, StateName) ->
|
||||
reply({To, Tag}, Reply),
|
||||
sys:handle_debug(Debug, {?MODULE, print_event}, Name,
|
||||
sys:handle_debug(Debug, fun print_event/3, Name,
|
||||
{out, Reply, To, StateName}).
|
||||
|
||||
%%% ---------------------------------------------------
|
||||
|
|
|
@ -37,8 +37,9 @@
|
|||
client_key/2
|
||||
]).
|
||||
|
||||
%% ejabberd doesn't implement SASLPREP, so we use the similar RESOURCEPREP instead
|
||||
salted_password(Password, Salt, IterationCount) ->
|
||||
hi(jlib:nameprep(Password), Salt, IterationCount).
|
||||
hi(jlib:resourceprep(Password), Salt, IterationCount).
|
||||
|
||||
client_key(SaltedPassword) ->
|
||||
crypto:sha_mac(SaltedPassword, "Client Key").
|
||||
|
@ -53,29 +54,29 @@ client_signature(StoredKey, AuthMessage) ->
|
|||
crypto:sha_mac(StoredKey, AuthMessage).
|
||||
|
||||
client_key(ClientProof, ClientSignature) ->
|
||||
binary:list_to_bin(lists:zipwith(fun(X, Y) ->
|
||||
list_to_binary(lists:zipwith(fun(X, Y) ->
|
||||
X bxor Y
|
||||
end,
|
||||
binary:bin_to_list(ClientProof),
|
||||
binary:bin_to_list(ClientSignature))).
|
||||
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:bin_to_list(Salt), [0,0,0,1])),
|
||||
binary:list_to_bin(lists:zipwith(fun(X, Y) ->
|
||||
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:bin_to_list(U1),
|
||||
binary:bin_to_list(hi_round(Password, U1, IterationCount-1)))).
|
||||
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),
|
||||
binary:list_to_bin(lists:zipwith(fun(X, Y) ->
|
||||
list_to_binary(lists:zipwith(fun(X, Y) ->
|
||||
X bxor Y
|
||||
end,
|
||||
binary:bin_to_list(U),
|
||||
binary:bin_to_list(hi_round(Password, U, IterationCount-1)))).
|
||||
binary_to_list(U),
|
||||
binary_to_list(hi_round(Password, U, IterationCount-1)))).
|
||||
|
|
|
@ -47,7 +47,7 @@ start() ->
|
|||
end,
|
||||
case Res of
|
||||
ok ->
|
||||
Port = open_port({spawn, ?DRIVER}, [binary]),
|
||||
Port = open_port({spawn, atom_to_list(?DRIVER)}, [binary]),
|
||||
register(?DRIVER, Port);
|
||||
{error, Reason} ->
|
||||
?CRITICAL_MSG("unable to load driver '~s': ~s",
|
||||
|
|
|
@ -60,7 +60,7 @@ init([]) ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, stringprep_drv}, []),
|
||||
Port = open_port({spawn, "stringprep_drv"}, []),
|
||||
register(?STRINGPREP_PORT, Port),
|
||||
{ok, Port}.
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ init([]) ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, tls_drv}, [binary]),
|
||||
Port = open_port({spawn, "tls_drv"}, [binary]),
|
||||
Res = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT, "./ssl.pem" ++ [0]),
|
||||
case Res of
|
||||
<<0>> ->
|
||||
|
@ -130,7 +130,7 @@ tcp_to_tls(TCPSocket, Options) ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, tls_drv}, [binary]),
|
||||
Port = open_port({spawn, "tls_drv"}, [binary]),
|
||||
Flags =
|
||||
case lists:member(verify_none, Options) of
|
||||
true ->
|
||||
|
@ -267,7 +267,7 @@ test() ->
|
|||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, tls_drv}, [binary]),
|
||||
Port = open_port({spawn, "tls_drv"}, [binary]),
|
||||
?PRINT("open_port: ~p~n", [Port]),
|
||||
PCRes = port_control(Port, ?SET_CERTIFICATE_FILE_ACCEPT,
|
||||
"./ssl.pem" ++ [0]),
|
||||
|
|
|
@ -409,20 +409,12 @@ static int tls_drv_control(ErlDrvData handle,
|
|||
break;
|
||||
case GET_ENCRYPTED_OUTPUT:
|
||||
die_unless(d->ssl, "SSL not initialized");
|
||||
rlen = 1;
|
||||
b = driver_alloc_binary(rlen + BUF_SIZE);
|
||||
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;
|
||||
b = driver_realloc_binary(b, rlen + BUF_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))
|
||||
{
|
||||
|
|
|
@ -63,8 +63,6 @@ insert1({HashKey1, Priority1, Value1, Left, Right} = Tree,
|
|||
insert1(delete_root(Tree), HashKey, Priority, Value)
|
||||
end.
|
||||
|
||||
heapify(nil) ->
|
||||
nil;
|
||||
heapify({_HashKey, _Priority, _Value, nil, nil} = Tree) ->
|
||||
Tree;
|
||||
heapify({HashKey, Priority, Value,
|
||||
|
|
|
@ -409,7 +409,7 @@ process_request(#state{request_method = Method,
|
|||
when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' ->
|
||||
case (catch url_decode_q_split(Path)) of
|
||||
{'EXIT', _} ->
|
||||
process_request(false);
|
||||
make_bad_request(State);
|
||||
{NPath, Query} ->
|
||||
LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")],
|
||||
LQuery = case (catch parse_urlencoded(Query)) of
|
||||
|
@ -507,7 +507,7 @@ process_request(#state{request_method = Method,
|
|||
?DEBUG("client data: ~p~n", [Data]),
|
||||
case (catch url_decode_q_split(Path)) of
|
||||
{'EXIT', _} ->
|
||||
process_request(false);
|
||||
make_bad_request(State);
|
||||
{NPath, _Query} ->
|
||||
LPath = [path_decode(NPE) || NPE <- string:tokens(NPath, "/")],
|
||||
LQuery = case (catch parse_urlencoded(Data)) of
|
||||
|
@ -543,6 +543,9 @@ process_request(#state{request_method = Method,
|
|||
end;
|
||||
|
||||
process_request(State) ->
|
||||
make_bad_request(State).
|
||||
|
||||
make_bad_request(State) ->
|
||||
make_xhtml_output(State,
|
||||
400,
|
||||
[],
|
||||
|
@ -589,7 +592,7 @@ recv_data(State, Len, Acc) ->
|
|||
end,
|
||||
case (State#state.sockmod):recv(State#state.socket, Len2, 300000) of
|
||||
{ok, Data} ->
|
||||
recv_data(State, Len - size(Data), [Acc | Data]);
|
||||
recv_data(State, Len - size(Data), [Acc | [Data]]);
|
||||
_ ->
|
||||
""
|
||||
end;
|
||||
|
@ -1179,8 +1182,8 @@ is_space(_) ->
|
|||
strip_spaces(String) ->
|
||||
strip_spaces(String, both).
|
||||
|
||||
strip_spaces(String, left) ->
|
||||
drop_spaces(String);
|
||||
%% strip_spaces(String, left) ->
|
||||
%% drop_spaces(String);
|
||||
strip_spaces(String, right) ->
|
||||
lists:reverse(drop_spaces(lists:reverse(String)));
|
||||
strip_spaces(String, both) ->
|
||||
|
|
|
@ -106,16 +106,6 @@ get_menu_items(global, cluster, Lang, JID) ->
|
|||
);
|
||||
get_menu_items(Host, cluster, Lang, JID) ->
|
||||
{Base, _, Items} = make_host_menu(Host, [], Lang, JID),
|
||||
lists:map(
|
||||
fun({URI, Name}) ->
|
||||
{Base++URI++"/", Name};
|
||||
({URI, Name, _SubMenu}) ->
|
||||
{Base++URI++"/", Name}
|
||||
end,
|
||||
Items
|
||||
);
|
||||
get_menu_items(Host, Node, Lang, JID) ->
|
||||
{Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID),
|
||||
lists:map(
|
||||
fun({URI, Name}) ->
|
||||
{Base++URI++"/", Name};
|
||||
|
@ -124,6 +114,16 @@ get_menu_items(Host, Node, Lang, JID) ->
|
|||
end,
|
||||
Items
|
||||
).
|
||||
%% get_menu_items(Host, Node, Lang, JID) ->
|
||||
%% {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID),
|
||||
%% lists:map(
|
||||
%% fun({URI, Name}) ->
|
||||
%% {Base++URI++"/", Name};
|
||||
%% ({URI, Name, _SubMenu}) ->
|
||||
%% {Base++URI++"/", Name}
|
||||
%% end,
|
||||
%% Items
|
||||
%% ).
|
||||
|
||||
is_allowed_path(BasePath, {Path, _}, JID) ->
|
||||
is_allowed_path(BasePath ++ [Path], JID);
|
||||
|
@ -2023,7 +2023,6 @@ get_node(global, Node, ["db"], Query, Lang) ->
|
|||
get_node(global, Node, ["backup"], Query, Lang) ->
|
||||
HomeDirRaw = case {os:getenv("HOME"), os:type()} of
|
||||
{EnvHome, _} when is_list(EnvHome) -> EnvHome;
|
||||
{false, win32} -> "C:/";
|
||||
{false, {win32, _Osname}} -> "C:/";
|
||||
{false, _} -> "/tmp/"
|
||||
end,
|
||||
|
|
|
@ -61,9 +61,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"},
|
||||
|
|
|
@ -104,7 +104,7 @@ new(CallbackPid) ->
|
|||
new(CallbackPid, infinity).
|
||||
|
||||
new(CallbackPid, MaxSize) ->
|
||||
Port = open_port({spawn, expat_erl}, [binary]),
|
||||
Port = open_port({spawn, "expat_erl"}, [binary]),
|
||||
#xml_stream_state{callback_pid = CallbackPid,
|
||||
port = Port,
|
||||
stack = [],
|
||||
|
@ -147,7 +147,7 @@ close(#xml_stream_state{port = Port}) ->
|
|||
|
||||
|
||||
parse_element(Str) ->
|
||||
Port = open_port({spawn, expat_erl}, [binary]),
|
||||
Port = open_port({spawn, "expat_erl"}, [binary]),
|
||||
Res = port_control(Port, ?PARSE_FINAL_COMMAND, Str),
|
||||
port_close(Port),
|
||||
process_element_events(binary_to_term(Res)).
|
||||
|
|
Loading…
Reference in New Issue
Block a user