26
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-28 17:38:54 +01:00

The road-to-exmpp branch now becomes trunk.

SVN Revision: 2693
This commit is contained in:
Badlop 2009-10-21 12:58:55 +00:00
commit c889491e2f
202 changed files with 26275 additions and 41102 deletions

8
README
View File

@ -8,9 +8,8 @@ Quickstart guide
To compile ejabberd you need:
- GNU Make
- GCC
- Libexpat 1.95 or higher
- Erlang/OTP R10B-9 or higher. The recommended version is R12B-5.
Support for R13 is experimental.
- Erlang/OTP R12B-4 or higher, R13B or higher.
- exmpp 0.9.1 or higher
- OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL
encryption. Optional, highly recommended.
- Zlib 1.2.3 or higher, for Stream Compression support
@ -18,10 +17,7 @@ To compile ejabberd you need:
- Erlang mysql library. Optional. MySQL authentication/storage.
- Erlang pgsql library. Optional. PostgreSQL authentication/storage.
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
- GNU Iconv 1.8 or higher, for the IRC Transport
(mod_irc). Optional. Not needed on systems with GNU Libc.
- ImageMagicks Convert program. Optional. For CAPTCHA challenges.
- exmpp 0.9.1 or higher. Optional. For import/export XEP-0227 files.
1. Compile and install on *nix systems

View File

@ -281,14 +281,14 @@ build_additional_translators(List) ->
List).
print_translation(File, Line, Str, StrT) ->
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
StrQ = re:replace(Str, "\"", "\\\"", [global, {return, list}]),
StrTQ = re:replace(StrT, "\"", "\\\"", [global, {return, list}]),
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
print_translation_obsolete(Str, StrT) ->
File = "unknown.erl",
Line = 1,
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
StrQ = re:replace(Str, "\"", "\\\"", [global, {return, list}]),
StrTQ = re:replace(StrT, "\"", "\\\"", [global, {return, list}]),
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).

View File

@ -15,4 +15,4 @@ clean:
docs:
erl -noshell -run edoc_run application \
"'$(APPNAME)'" '"$(SRCDIR)"' '[{dir,"$(DOCDIR)"},{packages, false},{todo,true},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"$(DOCDIR)/overview.edoc"}]' -s init stop
"'$(APPNAME)'" '"$(SRCDIR)"' '[{dir,"$(DOCDIR)"},{packages, false},{todo,false},{private,true},{def,{vsn,"$(VSN)"}},{stylesheet,"process-one.css"},{overview,"$(DOCDIR)/overview.edoc"}]' -s init stop

View File

@ -2,7 +2,7 @@
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 2.1.0 Developers Guide
<TITLE>Ejabberd 3.0.0-alpha 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.0 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 3.0.0-alpha 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">
@ -98,7 +98,7 @@ Comprehensive documentation.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
Translated to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
@ -141,7 +141,6 @@ Support for virtual hosting.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--TOC section How it Works-->

View File

@ -2,7 +2,7 @@
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 2.1.0 Feature Sheet
<TITLE>Ejabberd 3.0.0-alpha 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.0 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 3.0.0-alpha 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">
@ -76,7 +76,7 @@ Comprehensive documentation.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
Translated to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
@ -120,7 +120,6 @@ Support for virtual hosting.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--CUT END -->

View File

@ -6,7 +6,7 @@
ejabberd 2.1.0
ejabberd 3.0.0-alpha
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.0 </B></FONT></TD></TR>
<TABLE CELLSPACING=6 CELLPADDING=0><TR><TD ALIGN=right NOWRAP> <FONT SIZE=6><B>ejabberd 3.0.0-alpha </B></FONT></TD></TR>
<TR><TD ALIGN=right NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=right NOWRAP> <FONT SIZE=6>Installation and Operation Guide</FONT></TD></TR>
</TABLE><BR>
@ -146,78 +146,77 @@ BLOCKQUOTE.figure DIV.center DIV.center HR{display:none;}
</LI><LI CLASS="li-toc"><A HREF="#htoc44">3.3.5&#XA0;&#XA0;<TT>mod_echo</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc45">3.3.6&#XA0;&#XA0;<TT>mod_http_bind</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc46">3.3.7&#XA0;&#XA0;<TT>mod_http_fileserver</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc47">3.3.8&#XA0;&#XA0;<TT>mod_irc</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc48">3.3.9&#XA0;&#XA0;<TT>mod_last</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc49">3.3.10&#XA0;&#XA0;<TT>mod_muc</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc50">3.3.11&#XA0;&#XA0;<TT>mod_muc_log</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc51">3.3.12&#XA0;&#XA0;<TT>mod_offline</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc52">3.3.13&#XA0;&#XA0;<TT>mod_ping</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc53">3.3.14&#XA0;&#XA0;<TT>mod_privacy</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc54">3.3.15&#XA0;&#XA0;<TT>mod_private</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc55">3.3.16&#XA0;&#XA0;<TT>mod_proxy65</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc56">3.3.17&#XA0;&#XA0;<TT>mod_pubsub</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc57">3.3.18&#XA0;&#XA0;<TT>mod_register</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc58">3.3.19&#XA0;&#XA0;<TT>mod_roster</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc59">3.3.20&#XA0;&#XA0;<TT>mod_service_log</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc60">3.3.21&#XA0;&#XA0;<TT>mod_shared_roster</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc61">3.3.22&#XA0;&#XA0;<TT>mod_stats</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc62">3.3.23&#XA0;&#XA0;<TT>mod_time</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc63">3.3.24&#XA0;&#XA0;<TT>mod_vcard</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc64">3.3.25&#XA0;&#XA0;<TT>mod_vcard_ldap</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc65">3.3.26&#XA0;&#XA0;<TT>mod_version</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc47">3.3.8&#XA0;&#XA0;<TT>mod_last</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc48">3.3.9&#XA0;&#XA0;<TT>mod_muc</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc49">3.3.10&#XA0;&#XA0;<TT>mod_muc_log</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc50">3.3.11&#XA0;&#XA0;<TT>mod_offline</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc51">3.3.12&#XA0;&#XA0;<TT>mod_ping</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc52">3.3.13&#XA0;&#XA0;<TT>mod_privacy</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc53">3.3.14&#XA0;&#XA0;<TT>mod_private</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc54">3.3.15&#XA0;&#XA0;<TT>mod_proxy65</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc55">3.3.16&#XA0;&#XA0;<TT>mod_pubsub</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc56">3.3.17&#XA0;&#XA0;<TT>mod_register</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc57">3.3.18&#XA0;&#XA0;<TT>mod_roster</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc58">3.3.19&#XA0;&#XA0;<TT>mod_service_log</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc59">3.3.20&#XA0;&#XA0;<TT>mod_shared_roster</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc60">3.3.21&#XA0;&#XA0;<TT>mod_stats</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc61">3.3.22&#XA0;&#XA0;<TT>mod_time</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc62">3.3.23&#XA0;&#XA0;<TT>mod_vcard</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc63">3.3.24&#XA0;&#XA0;<TT>mod_vcard_ldap</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc64">3.3.25&#XA0;&#XA0;<TT>mod_version</TT></A>
</LI></UL>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc66">Chapter&#XA0;4&#XA0;&#XA0;Managing an <TT>ejabberd</TT> Server</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc65">Chapter&#XA0;4&#XA0;&#XA0;Managing an <TT>ejabberd</TT> Server</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc67">4.1&#XA0;&#XA0;<TT>ejabberdctl</TT></A>
<A HREF="#htoc66">4.1&#XA0;&#XA0;<TT>ejabberdctl</TT></A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc68">4.1.1&#XA0;&#XA0;ejabberdctl Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc69">4.1.2&#XA0;&#XA0;Erlang Runtime System</A>
<A HREF="#htoc67">4.1.1&#XA0;&#XA0;ejabberdctl Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc68">4.1.2&#XA0;&#XA0;Erlang Runtime System</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc70">4.2&#XA0;&#XA0;<TT>ejabberd</TT> Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc69">4.2&#XA0;&#XA0;<TT>ejabberd</TT> Commands</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc71">4.2.1&#XA0;&#XA0;List of ejabberd Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc72">4.2.2&#XA0;&#XA0;Restrict Execution with AccessCommands</A>
<A HREF="#htoc70">4.2.1&#XA0;&#XA0;List of ejabberd Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc71">4.2.2&#XA0;&#XA0;Restrict Execution with AccessCommands</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc73">4.3&#XA0;&#XA0;Web Admin</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc74">4.4&#XA0;&#XA0;Ad-hoc Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc75">4.5&#XA0;&#XA0;Change Computer Hostname</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc72">4.3&#XA0;&#XA0;Web Admin</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc73">4.4&#XA0;&#XA0;Ad-hoc Commands</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc74">4.5&#XA0;&#XA0;Change Computer Hostname</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc76">Chapter&#XA0;5&#XA0;&#XA0;Securing <TT>ejabberd</TT></A>
</LI><LI CLASS="li-toc"><A HREF="#htoc75">Chapter&#XA0;5&#XA0;&#XA0;Securing <TT>ejabberd</TT></A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc77">5.1&#XA0;&#XA0;Firewall Settings</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc78">5.2&#XA0;&#XA0;epmd</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc79">5.3&#XA0;&#XA0;Erlang Cookie</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc80">5.4&#XA0;&#XA0;Erlang Node Name</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc81">5.5&#XA0;&#XA0;Securing Sensitive Files</A>
<A HREF="#htoc76">5.1&#XA0;&#XA0;Firewall Settings</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc77">5.2&#XA0;&#XA0;epmd</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc78">5.3&#XA0;&#XA0;Erlang Cookie</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc79">5.4&#XA0;&#XA0;Erlang Node Name</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc80">5.5&#XA0;&#XA0;Securing Sensitive Files</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc82">Chapter&#XA0;6&#XA0;&#XA0;Clustering</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc81">Chapter&#XA0;6&#XA0;&#XA0;Clustering</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc83">6.1&#XA0;&#XA0;How it Works</A>
<A HREF="#htoc82">6.1&#XA0;&#XA0;How it Works</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc84">6.1.1&#XA0;&#XA0;Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc85">6.1.2&#XA0;&#XA0;Local Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc86">6.1.3&#XA0;&#XA0;Session Manager</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc87">6.1.4&#XA0;&#XA0;s2s Manager</A>
<A HREF="#htoc83">6.1.1&#XA0;&#XA0;Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc84">6.1.2&#XA0;&#XA0;Local Router</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc85">6.1.3&#XA0;&#XA0;Session Manager</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc86">6.1.4&#XA0;&#XA0;s2s Manager</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc88">6.2&#XA0;&#XA0;Clustering Setup</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc89">6.3&#XA0;&#XA0;Service Load-Balancing</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc87">6.2&#XA0;&#XA0;Clustering Setup</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc88">6.3&#XA0;&#XA0;Service Load-Balancing</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc90">6.3.1&#XA0;&#XA0;Components Load-Balancing</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc91">6.3.2&#XA0;&#XA0;Domain Load-Balancing Algorithm</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc92">6.3.3&#XA0;&#XA0;Load-Balancing Buckets</A>
<A HREF="#htoc89">6.3.1&#XA0;&#XA0;Components Load-Balancing</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc90">6.3.2&#XA0;&#XA0;Domain Load-Balancing Algorithm</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc91">6.3.3&#XA0;&#XA0;Load-Balancing Buckets</A>
</LI></UL>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc93">Chapter&#XA0;7&#XA0;&#XA0;Debugging</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc92">Chapter&#XA0;7&#XA0;&#XA0;Debugging</A>
<UL CLASS="toc"><LI CLASS="li-toc">
<A HREF="#htoc94">7.1&#XA0;&#XA0;Log Files</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc95">7.2&#XA0;&#XA0;Debug Console</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc96">7.3&#XA0;&#XA0;Watchdog Alerts</A>
<A HREF="#htoc93">7.1&#XA0;&#XA0;Log Files</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc94">7.2&#XA0;&#XA0;Debug Console</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc95">7.3&#XA0;&#XA0;Watchdog Alerts</A>
</LI></UL>
</LI><LI CLASS="li-toc"><A HREF="#htoc97">Appendix&#XA0;A&#XA0;&#XA0;Internationalization and Localization</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc98">Appendix&#XA0;B&#XA0;&#XA0;Release Notes</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc99">Appendix&#XA0;C&#XA0;&#XA0;Acknowledgements</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc100">Appendix&#XA0;D&#XA0;&#XA0;Copyright Information</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc96">Appendix&#XA0;A&#XA0;&#XA0;Internationalization and Localization</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc97">Appendix&#XA0;B&#XA0;&#XA0;Release Notes</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc98">Appendix&#XA0;C&#XA0;&#XA0;Acknowledgements</A>
</LI><LI CLASS="li-toc"><A HREF="#htoc99">Appendix&#XA0;D&#XA0;&#XA0;Copyright Information</A>
</LI></UL><!--TOC chapter Introduction-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc1">Chapter&#XA0;1</A>&#XA0;&#XA0;Introduction</H1><!--SEC END --><P>
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
@ -234,7 +233,7 @@ Comprehensive documentation.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
<UL CLASS="itemize"><LI CLASS="li-itemize">
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
Translated to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
<UL CLASS="itemize"><LI CLASS="li-itemize">
Fully XMPP compliant.
@ -277,7 +276,6 @@ Support for virtual hosting.
</LI><LI CLASS="li-itemize"><A HREF="http://xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><P> <A NAME="installing"></A> </P><!--TOC chapter Installing <TT>ejabberd</TT>-->
@ -343,16 +341,14 @@ as long as your system have all the dependencies.</P><P> <A NAME="installreq"></
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
GNU Make
</LI><LI CLASS="li-itemize">GCC
</LI><LI CLASS="li-itemize">Libexpat 1.95 or higher
</LI><LI CLASS="li-itemize">Erlang/OTP R10B-9 or higher. The recommended version is R12B-5. Support for R13 is experimental.
</LI><LI CLASS="li-itemize">Erlang/OTP R12B-4 or higher, R13B or higher.
</LI><LI CLASS="li-itemize">exmpp 0.9.1 or higher
</LI><LI CLASS="li-itemize">OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL encryption. Optional, highly recommended.
</LI><LI CLASS="li-itemize">Zlib 1.2.3 or higher, for Stream Compression support (<A HREF="http://xmpp.org/extensions/xep-0138.html">XEP-0138</A>). Optional.
</LI><LI CLASS="li-itemize">Erlang mysql library. Optional. For MySQL authentication or storage. See section <A HREF="#compilemysql">3.2.1</A>.
</LI><LI CLASS="li-itemize">Erlang pgsql library. Optional. For PostgreSQL authentication or storage. See section <A HREF="#compilepgsql">3.2.3</A>.
</LI><LI CLASS="li-itemize">PAM library. Optional. For Pluggable Authentication Modules (PAM). See section <A HREF="#pam">3.1.4</A>.
</LI><LI CLASS="li-itemize">GNU Iconv 1.8 or higher, for the IRC Transport (mod_irc). Optional. Not needed on systems with GNU Libc. See section <A HREF="#modirc">3.3.8</A>.
</LI><LI CLASS="li-itemize">ImageMagick&#X2019;s Convert program. Optional. For CAPTCHA challenges. See section <A HREF="#captcha">3.1.8</A>.
</LI><LI CLASS="li-itemize">exmpp 0.9.1 or higher. Optional. For import/export user data with <A HREF="http://xmpp.org/extensions/xep-0227.html">XEP-0227</A> XML files.
</LI></UL><P> <A NAME="download"></A> </P><!--TOC subsection Download Source Code-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc10">2.4.2</A>&#XA0;&#XA0;<A HREF="#download">Download Source Code</A></H3><!--SEC END --><P> <A NAME="download"></A>
</P><P>Released versions of <TT>ejabberd</TT> are available in the ProcessOne <TT>ejabberd</TT> downloads page:
@ -479,31 +475,25 @@ for example:
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#windowsreq">Requirements</A></H4><!--SEC END --><P> <A NAME="windowsreq"></A> </P><P>To compile <TT>ejabberd</TT> on a Microsoft Windows system, you need:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
MS Visual C++ 6.0 Compiler
</LI><LI CLASS="li-itemize"><A HREF="http://www.erlang.org/download.html">Erlang/OTP R11B-5</A>
</LI><LI CLASS="li-itemize"><A HREF="http://www.erlang.org/download.html">Erlang/OTP R12B-5</A>. Support for R13 or higher is experimental.
</LI><LI CLASS="li-itemize"><A HREF="http://support.process-one.net/doc/display/EXMPP">exmpp 0.9.1 or higher</A>
</LI><LI CLASS="li-itemize"><A HREF="http://sourceforge.net/project/showfiles.php?group_id=10127&package_id=11277">Expat 2.0.0 or higher</A>
</LI><LI CLASS="li-itemize"><A HREF="http://www.gnu.org/software/libiconv/">GNU Iconv 1.9.2</A>
(optional)
</LI><LI CLASS="li-itemize"><A HREF="http://www.slproweb.com/products/Win32OpenSSL.html">Shining Light OpenSSL 0.9.8d or higher</A>
(to enable SSL connections)
</LI><LI CLASS="li-itemize"><A HREF="http://www.zlib.net/">Zlib 1.2.3 or higher</A>
</LI></UL><P> <A NAME="windowscom"></A> </P><!--TOC subsubsection Compilation-->
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#windowscom">Compilation</A></H4><!--SEC END --><P> <A NAME="windowscom"></A> </P><P>We assume that we will try to put as much library as possible into <CODE>C:\sdk\</CODE> to make it easier to track what is install for <TT>ejabberd</TT>.</P><OL CLASS="enumerate" type=1><LI CLASS="li-enumerate">
Install Erlang emulator (for example, into <CODE>C:\sdk\erl5.5.5</CODE>).
Install Erlang emulator (for example, into <CODE>C:\sdk\erl5.6.5</CODE>).
</LI><LI CLASS="li-enumerate">Install Expat library into <CODE>C:\sdk\Expat-2.0.0</CODE>
directory.<P>Copy file <CODE>C:\sdk\Expat-2.0.0\Libs\libexpat.dll</CODE>
to your Windows system directory (for example, <CODE>C:\WINNT</CODE> or
<CODE>C:\WINNT\System32</CODE>)
</P></LI><LI CLASS="li-enumerate">Build and install the Iconv library into the directory
<CODE>C:\sdk\GnuWin32</CODE>.<P>Copy file <CODE>C:\sdk\GnuWin32\bin\lib*.dll</CODE> to your
Windows system directory (more installation instructions can be found in the
file README.woe32 in the iconv distribution).</P><P>Note: instead of copying libexpat.dll and iconv.dll to the Windows
directory, you can add the directories
<CODE>C:\sdk\Expat-2.0.0\Libs</CODE> and
<CODE>C:\sdk\GnuWin32\bin</CODE> to the <CODE>PATH</CODE> environment
variable.
Note: instead of copying libexpat.dll to the Windows
directory, you can add the directory <CODE>C:\sdk\Expat-2.0.0\Libs</CODE>
to the <CODE>PATH</CODE> environment variable.
</P></LI><LI CLASS="li-enumerate">Install OpenSSL in <CODE>C:\sdk\OpenSSL</CODE> and add <CODE>C:\sdk\OpenSSL\lib\VC</CODE> to your path or copy the binaries to your system directory.
</LI><LI CLASS="li-enumerate">Install ZLib in <CODE>C:\sdk\gnuWin32</CODE>. Copy
<CODE>C:\sdk\GnuWin32\bin\zlib1.dll</CODE> to your system directory. If you change your path it should already be set after libiconv install.
<CODE>C:\sdk\GnuWin32\bin\zlib1.dll</CODE> to your system directory.
</LI><LI CLASS="li-enumerate">Make sure the you can access Erlang binaries from your path. For example: <CODE>set PATH=%PATH%;"C:\sdk\erl5.6.5\bin"</CODE>
</LI><LI CLASS="li-enumerate">Depending on how you end up actually installing the library you might need to check and tweak the paths in the file configure.erl.
</LI><LI CLASS="li-enumerate">While in the directory <CODE>ejabberd\src</CODE> run:
@ -520,7 +510,7 @@ There are two ways to register a XMPP account:
<OL CLASS="enumerate" type=a><LI CLASS="li-enumerate">
Using <TT>ejabberdctl</TT> (see section&#XA0;<A HREF="#ejabberdctl">4.1</A>):
<PRE CLASS="verbatim">ejabberdctl register admin1 example.org FgT5bk3
</PRE></LI><LI CLASS="li-enumerate">Using a XMPP client and In-Band Registration (see section&#XA0;<A HREF="#modregister">3.3.18</A>).
</PRE></LI><LI CLASS="li-enumerate">Using a XMPP client and In-Band Registration (see section&#XA0;<A HREF="#modregister">3.3.17</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"}}.
@ -1825,8 +1815,7 @@ all entries end with a comma:
<TR><TD ALIGN=left NOWRAP><TT>mod_caps</TT></TD><TD ALIGN=left NOWRAP>Entity Capabilities (<A HREF="http://xmpp.org/extensions/xep-0115.html">XEP-0115</A>)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><TT>mod_configure</TT></TD><TD ALIGN=left NOWRAP>Server configuration using Ad-Hoc</TD><TD ALIGN=left NOWRAP><TT>mod_adhoc</TT></TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#moddisco"><TT>mod_disco</TT></A></TD><TD ALIGN=left NOWRAP>Service Discovery (<A HREF="http://xmpp.org/extensions/xep-0030.html">XEP-0030</A>)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modecho"><TT>mod_echo</TT></A></TD><TD ALIGN=left NOWRAP>Echoes XMPP stanzas</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modirc"><TT>mod_irc</TT></A></TD><TD ALIGN=left NOWRAP>IRC transport</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modecho"><TT>mod_echo</TT></A></TD><TD ALIGN=left NOWRAP>Echoes Jabber packets</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modlast"><TT>mod_last</TT></A></TD><TD ALIGN=left NOWRAP>Last Activity (<A HREF="http://xmpp.org/extensions/xep-0012.html">XEP-0012</A>)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modlast"><TT>mod_last_odbc</TT></A></TD><TD ALIGN=left NOWRAP>Last Activity (<A HREF="http://xmpp.org/extensions/xep-0012.html">XEP-0012</A>)</TD><TD ALIGN=left NOWRAP>supported DB (*)</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modmuc"><TT>mod_muc</TT></A></TD><TD ALIGN=left NOWRAP>Multi-User Chat (<A HREF="http://xmpp.org/extensions/xep-0045.html">XEP-0045</A>)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
@ -1840,7 +1829,6 @@ all entries end with a comma:
<TR><TD ALIGN=left NOWRAP><A HREF="#modprivate"><TT>mod_private_odbc</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>supported DB (*)</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modproxy"><TT>mod_proxy65</TT></A></TD><TD ALIGN=left NOWRAP>SOCKS5 Bytestreams (<A HREF="http://xmpp.org/extensions/xep-0065.html">XEP-0065</A>)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modpubsub"><TT>mod_pubsub</TT></A></TD><TD ALIGN=left NOWRAP>Pub-Sub (<A HREF="http://xmpp.org/extensions/xep-0060.html">XEP-0060</A>), PEP (<A HREF="http://xmpp.org/extensions/xep-0163.html">XEP-0163</A>)</TD><TD ALIGN=left NOWRAP><TT>mod_caps</TT></TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modpubsub"><TT>mod_pubsub_odbc</TT></A></TD><TD ALIGN=left NOWRAP>Pub-Sub (<A HREF="http://xmpp.org/extensions/xep-0060.html">XEP-0060</A>), PEP (<A HREF="http://xmpp.org/extensions/xep-0163.html">XEP-0163</A>)</TD><TD ALIGN=left NOWRAP>supported DB (*) and <TT>mod_caps</TT></TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modregister"><TT>mod_register</TT></A></TD><TD ALIGN=left NOWRAP>In-Band Registration (<A HREF="http://xmpp.org/extensions/xep-0077.html">XEP-0077</A>)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modroster"><TT>mod_roster</TT></A></TD><TD ALIGN=left NOWRAP>Roster management (XMPP IM)</TD><TD ALIGN=left NOWRAP>&nbsp;</TD></TR>
<TR><TD ALIGN=left NOWRAP><A HREF="#modroster"><TT>mod_roster_odbc</TT></A></TD><TD ALIGN=left NOWRAP>Roster management (XMPP IM)</TD><TD ALIGN=left NOWRAP>supported DB (*)</TD></TR>
@ -1951,7 +1939,7 @@ message is sent to all registered users. If the user is online and connected
to several resources, only the resource with the highest priority will receive
the message. If the registered user is not connected, the message will be
stored offline in assumption that offline storage
(see section&#XA0;<A HREF="#modoffline">3.3.12</A>) is enabled.
(see section&#XA0;<A HREF="#modoffline">3.3.11</A>) is enabled.
</DD><DT CLASS="dt-description"><B><TT>example.org/announce/online (example.org/announce/all-hosts/online)</TT></B></DT><DD CLASS="dd-description">The
message is sent to all connected users. If the user is online and connected
to several resources, all resources will receive the message.
@ -2205,69 +2193,8 @@ To use this module you must enable it:
},
...
]}.
</PRE><P> <A NAME="modirc"></A> </P><!--TOC subsection <TT>mod_irc</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc47">3.3.8</A>&#XA0;&#XA0;<A HREF="#modirc"><TT>mod_irc</TT></A></H3><!--SEC END --><P> <A NAME="modirc"></A>
</P><P>This module is an IRC transport that can be used to join channels on IRC
servers.</P><P>End user information:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
A XMPP client with &#X2018;groupchat 1.0&#X2019; support or Multi-User
Chat support (<A HREF="http://xmpp.org/extensions/xep-0045.html">XEP-0045</A>) is necessary to join IRC channels.
</LI><LI CLASS="li-itemize">An IRC channel can be joined in nearly the same way as joining a
XMPP Multi-User Chat room. The difference is that the room name will
be &#X2018;channel%<TT>irc.example.org</TT>&#X2019; in case <TT>irc.example.org</TT> is
the IRC server hosting &#X2018;channel&#X2019;. And of course the host should point
to the IRC transport instead of the Multi-User Chat service.
</LI><LI CLASS="li-itemize">You can register your nickame by sending &#X2018;IDENTIFY password&#X2019; to<BR>
<TT>nickserver!irc.example.org@irc.jabberserver.org</TT>.
</LI><LI CLASS="li-itemize">Entering your password is possible by sending &#X2018;LOGIN nick password&#X2019;<BR>
to <TT>nickserver!irc.example.org@irc.jabberserver.org</TT>.
</LI><LI CLASS="li-itemize">The IRC transport provides Ad-Hoc Commands (<A HREF="http://xmpp.org/extensions/xep-0050.html">XEP-0050</A>)
to join a channel, and to set custom IRC username and encoding.
</LI><LI CLASS="li-itemize">When using a popular XMPP server, it can occur that no
connection can be achieved with some IRC servers because they limit the
number of conections from one IP.
</LI></UL><P>Options:
</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>{host, HostName}</TT></B></DT><DD CLASS="dd-description"> This option defines the Jabber ID of the
service. If the <TT>host</TT> option is not specified, the Jabber ID will be the
hostname of the virtual host with the prefix &#X2018;<TT>irc.</TT>&#X2019;. The keyword "@HOST@"
is replaced at start time with the real virtual host name.
</DD><DT CLASS="dt-description"><B><TT>{access, AccessName}</TT></B></DT><DD CLASS="dd-description"> This option can be used to specify who
may use the IRC transport (default value: <TT>all</TT>).
</DD><DT CLASS="dt-description"><B><TT>{default_encoding, Encoding}</TT></B></DT><DD CLASS="dd-description"> Set the default IRC encoding.
Default value: <TT>"koi8-r"</TT>
</DD></DL><P>Examples:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
In the first example, the IRC transport is available on (all) your
virtual host(s) with the prefix &#X2018;<TT>irc.</TT>&#X2019;. Furthermore, anyone is
able to use the transport. The default encoding is set to "iso8859-15".
<PRE CLASS="verbatim">{modules,
[
...
{mod_irc, [{access, all}, {default_encoding, "iso8859-15"}]},
...
]}.
</PRE></LI><LI CLASS="li-itemize">In next example the IRC transport is available with JIDs with prefix <TT>irc-t.net</TT>.
Moreover, the transport is only accessible to two users
of <TT>example.org</TT>, and any user of <TT>example.com</TT>:
<PRE CLASS="verbatim">{acl, paying_customers, {user, "customer1", "example.org"}}.
{acl, paying_customers, {user, "customer2", "example.org"}}.
{acl, paying_customers, {server, "example.com"}}.
{access, irc_users, [{allow, paying_customers}, {deny, all}]}.
{modules,
[
...
{mod_irc, [{access, irc_users},
{host, "irc.example.net"}]},
...
]}.
</PRE></LI></UL><P> <A NAME="modlast"></A> </P><!--TOC subsection <TT>mod_last</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc48">3.3.9</A>&#XA0;&#XA0;<A HREF="#modlast"><TT>mod_last</TT></A></H3><!--SEC END --><P> <A NAME="modlast"></A>
</PRE><P> <A NAME="modlast"></A> </P><!--TOC subsection <TT>mod_last</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc47">3.3.8</A>&#XA0;&#XA0;<A HREF="#modlast"><TT>mod_last</TT></A></H3><!--SEC END --><P> <A NAME="modlast"></A>
</P><P>This module adds support for Last Activity (<A HREF="http://xmpp.org/extensions/xep-0012.html">XEP-0012</A>). It can be used to
discover when a disconnected user last accessed the server, to know when a
connected user was last active on the server, or to query the uptime of the
@ -2276,7 +2203,7 @@ connected user was last active on the server, or to query the uptime of the
<B><TT>{iqdisc, Discipline}</TT></B></DT><DD CLASS="dd-description"> This specifies
the processing discipline for Last activity (<TT>jabber:iq:last</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
</DD></DL><P> <A NAME="modmuc"></A> </P><!--TOC subsection <TT>mod_muc</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc49">3.3.10</A>&#XA0;&#XA0;<A HREF="#modmuc"><TT>mod_muc</TT></A></H3><!--SEC END --><P> <A NAME="modmuc"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc48">3.3.9</A>&#XA0;&#XA0;<A HREF="#modmuc"><TT>mod_muc</TT></A></H3><!--SEC END --><P> <A NAME="modmuc"></A>
</P><P>This module provides a Multi-User Chat (<A HREF="http://xmpp.org/extensions/xep-0045.html">XEP-0045</A>) service.
Users can discover existing rooms, join or create them.
Occupants of a room can chat in public or have private chats.</P><P>Some of the features of Multi-User Chat:
@ -2499,7 +2426,7 @@ the newly created rooms have by default those options.
...
]}.
</PRE></LI></UL><P> <A NAME="modmuclog"></A> </P><!--TOC subsection <TT>mod_muc_log</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc50">3.3.11</A>&#XA0;&#XA0;<A HREF="#modmuclog"><TT>mod_muc_log</TT></A></H3><!--SEC END --><P> <A NAME="modmuclog"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc49">3.3.10</A>&#XA0;&#XA0;<A HREF="#modmuclog"><TT>mod_muc_log</TT></A></H3><!--SEC END --><P> <A NAME="modmuclog"></A>
</P><P>This module enables optional logging of Multi-User Chat (MUC) public conversations to
HTML. Once you enable this module, users can join a room using a MUC capable
XMPP client, and if they have enough privileges, they can request the
@ -2618,7 +2545,7 @@ top link will be the default <CODE>&lt;a href="/"&gt;Home&lt;/a&gt;</CODE>.
...
]}.
</PRE></LI></UL><P> <A NAME="modoffline"></A> </P><!--TOC subsection <TT>mod_offline</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc51">3.3.12</A>&#XA0;&#XA0;<A HREF="#modoffline"><TT>mod_offline</TT></A></H3><!--SEC END --><P> <A NAME="modoffline"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc50">3.3.11</A>&#XA0;&#XA0;<A HREF="#modoffline"><TT>mod_offline</TT></A></H3><!--SEC END --><P> <A NAME="modoffline"></A>
</P><P>This module implements offline message storage (<A HREF="http://xmpp.org/extensions/xep-0160.html">XEP-0160</A>).
This means that all messages
sent to an offline user will be stored on the server until that user comes
@ -2650,7 +2577,7 @@ and all the other users up to 100.
...
]}.
</PRE><P> <A NAME="modping"></A> </P><!--TOC subsection <TT>mod_ping</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc52">3.3.13</A>&#XA0;&#XA0;<A HREF="#modping"><TT>mod_ping</TT></A></H3><!--SEC END --><P> <A NAME="modping"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc51">3.3.12</A>&#XA0;&#XA0;<A HREF="#modping"><TT>mod_ping</TT></A></H3><!--SEC END --><P> <A NAME="modping"></A>
</P><P>This module implements support for XMPP Ping (<A HREF="http://xmpp.org/extensions/xep-0199.html">XEP-0199</A>) and periodic keepalives.
When this module is enabled ejabberd responds correctly to
ping requests, as defined in the protocol.</P><P>Configuration options:
@ -2678,7 +2605,7 @@ and if a client does not answer to the ping in less than 32 seconds, its connect
...
]}.
</PRE><P> <A NAME="modprivacy"></A> </P><!--TOC subsection <TT>mod_privacy</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc53">3.3.14</A>&#XA0;&#XA0;<A HREF="#modprivacy"><TT>mod_privacy</TT></A></H3><!--SEC END --><P> <A NAME="modprivacy"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc52">3.3.13</A>&#XA0;&#XA0;<A HREF="#modprivacy"><TT>mod_privacy</TT></A></H3><!--SEC END --><P> <A NAME="modprivacy"></A>
</P><P>This module implements Blocking Communication (also known as Privacy Rules)
as defined in section 10 from XMPP IM. If end users have support for it in
their XMPP client, they will be able to:
@ -2706,7 +2633,7 @@ subscription type (or globally).
<B><TT>{iqdisc, Discipline}</TT></B></DT><DD CLASS="dd-description"> This specifies
the processing discipline for Blocking Communication (<TT>jabber:iq:privacy</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
</DD></DL><P> <A NAME="modprivate"></A> </P><!--TOC subsection <TT>mod_private</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc54">3.3.15</A>&#XA0;&#XA0;<A HREF="#modprivate"><TT>mod_private</TT></A></H3><!--SEC END --><P> <A NAME="modprivate"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc53">3.3.14</A>&#XA0;&#XA0;<A HREF="#modprivate"><TT>mod_private</TT></A></H3><!--SEC END --><P> <A NAME="modprivate"></A>
</P><P>This module adds support for Private XML Storage (<A HREF="http://xmpp.org/extensions/xep-0049.html">XEP-0049</A>):
</P><BLOCKQUOTE CLASS="quote">
Using this method, XMPP entities can store private data on the server and
@ -2718,7 +2645,7 @@ of client-specific preferences; another is Bookmark Storage (<A HREF="http://xmp
<B><TT>{iqdisc, Discipline}</TT></B></DT><DD CLASS="dd-description"> This specifies
the processing discipline for Private XML Storage (<TT>jabber:iq:private</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
</DD></DL><P> <A NAME="modproxy"></A> </P><!--TOC subsection <TT>mod_proxy65</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc55">3.3.16</A>&#XA0;&#XA0;<A HREF="#modproxy"><TT>mod_proxy65</TT></A></H3><!--SEC END --><P> <A NAME="modproxy"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc54">3.3.15</A>&#XA0;&#XA0;<A HREF="#modproxy"><TT>mod_proxy65</TT></A></H3><!--SEC END --><P> <A NAME="modproxy"></A>
</P><P>This module implements SOCKS5 Bytestreams (<A HREF="http://xmpp.org/extensions/xep-0065.html">XEP-0065</A>).
It allows <TT>ejabberd</TT> to act as a file transfer proxy between two
XMPP clients.</P><P>Options:
@ -2776,7 +2703,7 @@ The simpliest configuration of the module:
...
]}.
</PRE></LI></UL><P> <A NAME="modpubsub"></A> </P><!--TOC subsection <TT>mod_pubsub</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc56">3.3.17</A>&#XA0;&#XA0;<A HREF="#modpubsub"><TT>mod_pubsub</TT></A></H3><!--SEC END --><P> <A NAME="modpubsub"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc55">3.3.16</A>&#XA0;&#XA0;<A HREF="#modpubsub"><TT>mod_pubsub</TT></A></H3><!--SEC END --><P> <A NAME="modpubsub"></A>
</P><P>This module offers a Publish-Subscribe Service (<A HREF="http://xmpp.org/extensions/xep-0060.html">XEP-0060</A>).
The functionality in <TT>mod_pubsub</TT> can be extended using plugins.
The plugin that implements PEP (Personal Eventing via Pubsub) (<A HREF="http://xmpp.org/extensions/xep-0163.html">XEP-0163</A>)
@ -2797,27 +2724,13 @@ By default any account in the local ejabberd server is allowed to create pubsub
Define the maximum number of items that can be stored in a node.
Default value is 10.
</DD><DT CLASS="dt-description"><B><TT>{plugins, [ Plugin, ...]}</TT></B></DT><DD CLASS="dd-description">
To specify which pubsub node plugins to use.
The first one in the list is used by default.
If this option is not defined, the default plugins list is: <TT>["flat"]</TT>.
PubSub clients can define which plugin to use when creating a node:
add <TT>type=&#X2019;plugin-name&#X2019;</TT> attribute to the <TT>create</TT> stanza element.
</DD><DT CLASS="dt-description"><B><TT>{nodetree, Nodetree}</TT></B></DT><DD CLASS="dd-description">
To specify which nodetree to use.
If not defined, the default pubsub nodetree is used: "tree".
Only one nodetree can be used per host, and is shared by all node plugins.<P>The "virtual" nodetree does not store nodes on database.
This saves resources on systems with tons of nodes.
If using the "virtual" nodetree,
you can only enable those node plugins:
["flat","pep"] or ["flat"];
any other plugins configuration will not work.
Also, all nodes will have the defaut configuration,
and this can not be changed.
Using "virtual" nodetree requires to start from a clean database,
it will not work if you used the default "tree" nodetree before.</P><P>The "dag" nodetree provides experimental support for PubSub Collection Nodes (<A HREF="http://xmpp.org/extensions/xep-0248.html">XEP-0248</A>).
In that case you should also add "dag" node plugin as default, for example:
<TT>{plugins, ["dag","flat","hometree","pep"]}</TT>
</P></DD><DT CLASS="dt-description"><B><TT>{ignore_pep_from_offline, false|true}</TT></B></DT><DD CLASS="dd-description">
To specify which pubsub node plugins to use. If not defined, the default
pubsub plugin is always used.
</DD><DT CLASS="dt-description"><B><TT>{nodetree, Name}</TT></B></DT><DD CLASS="dd-description">
To specify which nodetree to use. If not defined, the default pubsub
nodetree is used. Only one nodetree can be used per host,
and is shared by all node plugins.
</DD><DT CLASS="dt-description"><B><TT>{ignore_pep_from_offline, false|true}</TT></B></DT><DD CLASS="dd-description">
To specify whether or not we should get last published PEP items
from users in our roster which are offline when we connect. Value is true or false.
If not defined, pubsub assumes true so we only get last items of online contacts.
@ -2830,29 +2743,18 @@ usage, as every item is stored in memory.
This allow to define a Key-Value list to choose defined node plugins on given PEP namespace.
The following example will use node_tune instead of node_pep for every PEP node with tune namespace:
<PRE CLASS="verbatim"> {mod_pubsub, [{pep_mapping, [{"http://jabber.org/protocol/tune", "tune"}]}]}
</PRE></DD></DL><P>Example of configuration that uses flat nodes as default, and allows use of flat, nodetree and pep nodes:
</PRE></DD></DL><P>Example:
</P><PRE CLASS="verbatim">{modules,
[
...
{mod_pubsub, [
{access_createnode, pubsub_createnode},
{plugins, ["flat", "hometree", "pep"]}
]},
...
]}.
</PRE><P>Using ODBC database requires use of dedicated plugins. The following example shows previous configuration
with ODBC usage:
</P><PRE CLASS="verbatim">{modules,
[
...
{mod_pubsub_odbc, [
{access_createnode, pubsub_createnode},
{plugins, ["flat_odbc", "hometree_odbc", "pep_odbc"]}
]},
{plugins, ["default", "pep"]}
]}
...
]}.
</PRE><P> <A NAME="modregister"></A> </P><!--TOC subsection <TT>mod_register</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc57">3.3.18</A>&#XA0;&#XA0;<A HREF="#modregister"><TT>mod_register</TT></A></H3><!--SEC END --><P> <A NAME="modregister"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc56">3.3.17</A>&#XA0;&#XA0;<A HREF="#modregister"><TT>mod_register</TT></A></H3><!--SEC END --><P> <A NAME="modregister"></A>
</P><P>This module adds support for In-Band Registration (<A HREF="http://xmpp.org/extensions/xep-0077.html">XEP-0077</A>). This protocol
enables end users to use a XMPP client to:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
@ -2931,7 +2833,7 @@ Also define a registration timeout of one hour:
...
]}.
</PRE></LI></UL><P> <A NAME="modroster"></A> </P><!--TOC subsection <TT>mod_roster</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc58">3.3.19</A>&#XA0;&#XA0;<A HREF="#modroster"><TT>mod_roster</TT></A></H3><!--SEC END --><P> <A NAME="modroster"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc57">3.3.18</A>&#XA0;&#XA0;<A HREF="#modroster"><TT>mod_roster</TT></A></H3><!--SEC END --><P> <A NAME="modroster"></A>
</P><P>This module implements roster management as defined in
<A HREF="http://xmpp.org/specs/rfc3921.html#roster">RFC 3921: XMPP IM</A>.
It also supports Roster Versioning (<A HREF="http://xmpp.org/extensions/xep-0237.html">XEP-0237</A>).</P><P>Options:
@ -2957,7 +2859,7 @@ Important: if you use <TT>mod_shared_roster</TT>, you must disable this option.
...
]}.
</PRE><P> <A NAME="modservicelog"></A> </P><!--TOC subsection <TT>mod_service_log</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc59">3.3.20</A>&#XA0;&#XA0;<A HREF="#modservicelog"><TT>mod_service_log</TT></A></H3><!--SEC END --><P> <A NAME="modservicelog"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc58">3.3.19</A>&#XA0;&#XA0;<A HREF="#modservicelog"><TT>mod_service_log</TT></A></H3><!--SEC END --><P> <A NAME="modservicelog"></A>
</P><P>This module adds support for logging end user packets via a XMPP message
auditing service such as
<A HREF="http://www.funkypenguin.info/project/bandersnatch/">Bandersnatch</A>. All user
@ -2987,7 +2889,7 @@ To log all end user packets to the Bandersnatch service running on
...
]}.
</PRE></LI></UL><P> <A NAME="modsharedroster"></A> </P><!--TOC subsection <TT>mod_shared_roster</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc60">3.3.21</A>&#XA0;&#XA0;<A HREF="#modsharedroster"><TT>mod_shared_roster</TT></A></H3><!--SEC END --><P> <A NAME="modsharedroster"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc59">3.3.20</A>&#XA0;&#XA0;<A HREF="#modsharedroster"><TT>mod_shared_roster</TT></A></H3><!--SEC END --><P> <A NAME="modsharedroster"></A>
</P><P>This module enables you to create shared roster groups. This means that you can
create groups of people that can see members from (other) groups in their
rosters. The big advantages of this feature are that end users do not need to
@ -3062,7 +2964,7 @@ roster groups as shown in the following table:
</TABLE>
<DIV CLASS="center"><HR WIDTH="80%" SIZE=2></DIV></DIV></BLOCKQUOTE>
</LI></UL><P> <A NAME="modstats"></A> </P><!--TOC subsection <TT>mod_stats</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc61">3.3.22</A>&#XA0;&#XA0;<A HREF="#modstats"><TT>mod_stats</TT></A></H3><!--SEC END --><P> <A NAME="modstats"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc60">3.3.21</A>&#XA0;&#XA0;<A HREF="#modstats"><TT>mod_stats</TT></A></H3><!--SEC END --><P> <A NAME="modstats"></A>
</P><P>This module adds support for Statistics Gathering (<A HREF="http://xmpp.org/extensions/xep-0039.html">XEP-0039</A>). This protocol
allows you to retrieve next statistics from your <TT>ejabberd</TT> deployment:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
@ -3094,14 +2996,14 @@ by sending:
&lt;/query&gt;
&lt;/iq&gt;
</PRE></LI></UL><P> <A NAME="modtime"></A> </P><!--TOC subsection <TT>mod_time</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc62">3.3.23</A>&#XA0;&#XA0;<A HREF="#modtime"><TT>mod_time</TT></A></H3><!--SEC END --><P> <A NAME="modtime"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc61">3.3.22</A>&#XA0;&#XA0;<A HREF="#modtime"><TT>mod_time</TT></A></H3><!--SEC END --><P> <A NAME="modtime"></A>
</P><P>This module features support for Entity Time (<A HREF="http://xmpp.org/extensions/xep-0202.html">XEP-0202</A>). By using this XEP,
you are able to discover the time at another entity&#X2019;s location.</P><P>Options:
</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>{iqdisc, Discipline}</TT></B></DT><DD CLASS="dd-description"> This specifies
the processing discipline for Entity Time (<TT>jabber:iq:time</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
</DD></DL><P> <A NAME="modvcard"></A> </P><!--TOC subsection <TT>mod_vcard</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc63">3.3.24</A>&#XA0;&#XA0;<A HREF="#modvcard"><TT>mod_vcard</TT></A></H3><!--SEC END --><P> <A NAME="modvcard"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc62">3.3.23</A>&#XA0;&#XA0;<A HREF="#modvcard"><TT>mod_vcard</TT></A></H3><!--SEC END --><P> <A NAME="modvcard"></A>
</P><P>This module allows end users to store and retrieve their vCard, and to retrieve
other users vCards, as defined in vcard-temp (<A HREF="http://xmpp.org/extensions/xep-0054.html">XEP-0054</A>). The module also
implements an uncomplicated Jabber User Directory based on the vCards of
@ -3156,7 +3058,7 @@ and that all virtual hosts will be searched instead of only the current one:
...
]}.
</PRE></LI></UL><P> <A NAME="modvcardldap"></A> </P><!--TOC subsection <TT>mod_vcard_ldap</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc64">3.3.25</A>&#XA0;&#XA0;<A HREF="#modvcardldap"><TT>mod_vcard_ldap</TT></A></H3><!--SEC END --><P> <A NAME="modvcardldap"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc63">3.3.24</A>&#XA0;&#XA0;<A HREF="#modvcardldap"><TT>mod_vcard_ldap</TT></A></H3><!--SEC END --><P> <A NAME="modvcardldap"></A>
</P><P><TT>ejabberd</TT> can map LDAP attributes to vCard fields. This behaviour is
implemented in the <TT>mod_vcard_ldap</TT> module. This module does not depend on the
authentication method (see&#XA0;<A HREF="#ldapauth">3.2.5</A>).</P><P>Note that <TT>ejabberd</TT> treats LDAP as a read-only storage:
@ -3335,7 +3237,7 @@ searching his info in LDAP.</P></LI><LI CLASS="li-itemize"><TT>ldap_vcard_map</T
{"Nickname", "NICKNAME"}
]},
</PRE></LI></UL><P> <A NAME="modversion"></A> </P><!--TOC subsection <TT>mod_version</TT>-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc65">3.3.26</A>&#XA0;&#XA0;<A HREF="#modversion"><TT>mod_version</TT></A></H3><!--SEC END --><P> <A NAME="modversion"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc64">3.3.25</A>&#XA0;&#XA0;<A HREF="#modversion"><TT>mod_version</TT></A></H3><!--SEC END --><P> <A NAME="modversion"></A>
</P><P>This module implements Software Version (<A HREF="http://xmpp.org/extensions/xep-0092.html">XEP-0092</A>). Consequently, it
answers <TT>ejabberd</TT>&#X2019;s version when queried.</P><P>Options:
</P><DL CLASS="description"><DT CLASS="dt-description">
@ -3344,8 +3246,8 @@ The default value is <TT>true</TT>.
</DD><DT CLASS="dt-description"><B><TT>{iqdisc, Discipline}</TT></B></DT><DD CLASS="dd-description"> This specifies
the processing discipline for Software Version (<TT>jabber:iq:version</TT>) IQ queries (see section&#XA0;<A HREF="#modiqdiscoption">3.3.2</A>).
</DD></DL><P> <A NAME="manage"></A> </P><!--TOC chapter Managing an <TT>ejabberd</TT> Server-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc66">Chapter&#XA0;4</A>&#XA0;&#XA0;<A HREF="#manage">Managing an <TT>ejabberd</TT> Server</A></H1><!--SEC END --><P> <A NAME="manage"></A> </P><P> <A NAME="ejabberdctl"></A> </P><!--TOC section <TT>ejabberdctl</TT>-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc67">4.1</A>&#XA0;&#XA0;<A HREF="#ejabberdctl"><TT>ejabberdctl</TT></A></H2><!--SEC END --><P> <A NAME="ejabberdctl"></A> </P><P>With the <TT>ejabberdctl</TT> command line administration script
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc65">Chapter&#XA0;4</A>&#XA0;&#XA0;<A HREF="#manage">Managing an <TT>ejabberd</TT> Server</A></H1><!--SEC END --><P> <A NAME="manage"></A> </P><P> <A NAME="ejabberdctl"></A> </P><!--TOC section <TT>ejabberdctl</TT>-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc66">4.1</A>&#XA0;&#XA0;<A HREF="#ejabberdctl"><TT>ejabberdctl</TT></A></H2><!--SEC END --><P> <A NAME="ejabberdctl"></A> </P><P>With the <TT>ejabberdctl</TT> command line administration script
you can execute <TT>ejabberdctl commands</TT> (described in the next section, <A HREF="#ectl-commands">4.1.1</A>)
and also many general <TT>ejabberd commands</TT> (described in section <A HREF="#eja-commands">4.2</A>).
This means you can start, stop and perform many other administrative tasks
@ -3357,7 +3259,7 @@ and other codes may be used for specific results.
This can be used by other scripts to determine automatically
if a command succeeded or failed,
for example using: <TT>echo $?</TT></P><P> <A NAME="ectl-commands"></A> </P><!--TOC subsection ejabberdctl Commands-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc68">4.1.1</A>&#XA0;&#XA0;<A HREF="#ectl-commands">ejabberdctl Commands</A></H3><!--SEC END --><P> <A NAME="ectl-commands"></A> </P><P>When <TT>ejabberdctl</TT> is executed without any parameter,
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc67">4.1.1</A>&#XA0;&#XA0;<A HREF="#ectl-commands">ejabberdctl Commands</A></H3><!--SEC END --><P> <A NAME="ectl-commands"></A> </P><P>When <TT>ejabberdctl</TT> is executed without any parameter,
it displays the available options. If there isn&#X2019;t an <TT>ejabberd</TT> server running,
the available parameters are:
</P><DL CLASS="description"><DT CLASS="dt-description">
@ -3393,7 +3295,7 @@ robot1
testuser1
testuser2
</PRE><P> <A NAME="erlangconfiguration"></A> </P><!--TOC subsection Erlang Runtime System-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc69">4.1.2</A>&#XA0;&#XA0;<A HREF="#erlangconfiguration">Erlang Runtime System</A></H3><!--SEC END --><P> <A NAME="erlangconfiguration"></A> </P><P><TT>ejabberd</TT> is an Erlang/OTP application that runs inside an Erlang runtime system.
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc68">4.1.2</A>&#XA0;&#XA0;<A HREF="#erlangconfiguration">Erlang Runtime System</A></H3><!--SEC END --><P> <A NAME="erlangconfiguration"></A> </P><P><TT>ejabberd</TT> is an Erlang/OTP application that runs inside an Erlang runtime system.
This system is configured using environment variables and command line parameters.
The <TT>ejabberdctl</TT> administration script uses many of those possibilities.
You can configure some of them with the file <TT>ejabberdctl.cfg</TT>,
@ -3470,7 +3372,7 @@ not &#X201C;Simple Authentication and Security Layer&#X201D;.
</DD></DL><P>
Note that some characters need to be escaped when used in shell scripts, for instance <CODE>"</CODE> and <CODE>{}</CODE>.
You can find other options in the Erlang manual page (<TT>erl -man erl</TT>).</P><P> <A NAME="eja-commands"></A> </P><!--TOC section <TT>ejabberd</TT> Commands-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc70">4.2</A>&#XA0;&#XA0;<A HREF="#eja-commands"><TT>ejabberd</TT> Commands</A></H2><!--SEC END --><P> <A NAME="eja-commands"></A> </P><P>An <TT>ejabberd command</TT> is an abstract function identified by a name,
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc69">4.2</A>&#XA0;&#XA0;<A HREF="#eja-commands"><TT>ejabberd</TT> Commands</A></H2><!--SEC END --><P> <A NAME="eja-commands"></A> </P><P>An <TT>ejabberd command</TT> is an abstract function identified by a name,
with a defined number and type of calling arguments and type of result
that is registered in the <TT>ejabberd_commands</TT> service.
Those commands can be defined in any Erlang module and executed using any valid frontend.</P><P><TT>ejabberd</TT> includes a frontend to execute <TT>ejabberd commands</TT>: the script <TT>ejabberdctl</TT>.
@ -3478,7 +3380,7 @@ Other known frontends that can be installed to execute ejabberd commands in diff
<TT>ejabberd_xmlrpc</TT> (XML-RPC service),
<TT>mod_rest</TT> (HTTP POST service),
<TT>mod_shcommands</TT> (ejabberd WebAdmin page).</P><P> <A NAME="list-eja-commands"></A> </P><!--TOC subsection List of ejabberd Commands-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc71">4.2.1</A>&#XA0;&#XA0;<A HREF="#list-eja-commands">List of ejabberd Commands</A></H3><!--SEC END --><P> <A NAME="list-eja-commands"></A> </P><P><TT>ejabberd</TT> includes a few ejabberd Commands by default.
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc70">4.2.1</A>&#XA0;&#XA0;<A HREF="#list-eja-commands">List of ejabberd Commands</A></H3><!--SEC END --><P> <A NAME="list-eja-commands"></A> </P><P><TT>ejabberd</TT> includes a few ejabberd Commands by default.
When more modules are installed, new commands may be available in the frontends.</P><P>The easiest way to get a list of the available commands, and get help for them is to use
the ejabberdctl script:
</P><PRE CLASS="verbatim">$ ejabberdctl help
@ -3529,7 +3431,7 @@ is very high.
</DD><DT CLASS="dt-description"><B><TT>register user host password</TT></B></DT><DD CLASS="dd-description"> Register an account in that domain with the given password.
</DD><DT CLASS="dt-description"><B><TT>unregister user host</TT></B></DT><DD CLASS="dd-description"> Unregister the given account.
</DD></DL><P> <A NAME="accesscommands"></A> </P><!--TOC subsection Restrict Execution with AccessCommands-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc72">4.2.2</A>&#XA0;&#XA0;<A HREF="#accesscommands">Restrict Execution with AccessCommands</A></H3><!--SEC END --><P> <A NAME="accesscommands"></A> </P><P>The frontends can be configured to restrict access to certain commands.
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc71">4.2.2</A>&#XA0;&#XA0;<A HREF="#accesscommands">Restrict Execution with AccessCommands</A></H3><!--SEC END --><P> <A NAME="accesscommands"></A> </P><P>The frontends can be configured to restrict access to certain commands.
In that case, authentication information must be provided.
In each frontend the <TT>AccessCommands</TT> option is defined
in a different place. But in all cases the option syntax is the same:
@ -3575,7 +3477,7 @@ and the provided arguments do not contradict Arguments.</P><P>As an example to u
{_bot_reg_test, [register, unregister], [{host, "test.org"}]}
]
</PRE><P> <A NAME="webadmin"></A> </P><!--TOC section Web Admin-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc73">4.3</A>&#XA0;&#XA0;<A HREF="#webadmin">Web Admin</A></H2><!--SEC END --><P> <A NAME="webadmin"></A>
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc72">4.3</A>&#XA0;&#XA0;<A HREF="#webadmin">Web Admin</A></H2><!--SEC END --><P> <A NAME="webadmin"></A>
</P><P>The <TT>ejabberd</TT> Web Admin allows to administer most of <TT>ejabberd</TT> using a web browser.</P><P>This feature is enabled by default:
a <TT>ejabberd_http</TT> listener with the option <TT>web_admin</TT> (see
section&#XA0;<A HREF="#listened">3.1.3</A>) is included in the listening ports. Then you can open
@ -3647,13 +3549,13 @@ The file is searched by default in
The directory of the documentation can be specified in
the environment variable <TT>EJABBERD_DOC_PATH</TT>.
See section <A HREF="#erlangconfiguration">4.1.2</A>.</P><P> <A NAME="adhoccommands"></A> </P><!--TOC section Ad-hoc Commands-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc74">4.4</A>&#XA0;&#XA0;<A HREF="#adhoccommands">Ad-hoc Commands</A></H2><!--SEC END --><P> <A NAME="adhoccommands"></A> </P><P>If you enable <TT>mod_configure</TT> and <TT>mod_adhoc</TT>,
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc73">4.4</A>&#XA0;&#XA0;<A HREF="#adhoccommands">Ad-hoc Commands</A></H2><!--SEC END --><P> <A NAME="adhoccommands"></A> </P><P>If you enable <TT>mod_configure</TT> and <TT>mod_adhoc</TT>,
you can perform several administrative tasks in <TT>ejabberd</TT>
with a XMPP client.
The client must support Ad-Hoc Commands (<A HREF="http://xmpp.org/extensions/xep-0050.html">XEP-0050</A>),
and you must login in the XMPP server with
an account with proper privileges.</P><P> <A NAME="changeerlangnodename"></A> </P><!--TOC section Change Computer Hostname-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc75">4.5</A>&#XA0;&#XA0;<A HREF="#changeerlangnodename">Change Computer Hostname</A></H2><!--SEC END --><P> <A NAME="changeerlangnodename"></A> </P><P><TT>ejabberd</TT> uses the distributed Mnesia database.
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc74">4.5</A>&#XA0;&#XA0;<A HREF="#changeerlangnodename">Change Computer Hostname</A></H2><!--SEC END --><P> <A NAME="changeerlangnodename"></A> </P><P><TT>ejabberd</TT> uses the distributed Mnesia database.
Being distributed, Mnesia enforces consistency of its file,
so it stores the name of the Erlang node in it (see section <A HREF="#nodename">5.4</A>).
The name of an Erlang node includes the hostname of the computer.
@ -3690,8 +3592,8 @@ mv /var/lib/ejabberd/*.* /var/lib/ejabberd/oldfiles/
</PRE></LI><LI CLASS="li-enumerate">Check that the information of the old database is available: accounts, rosters...
After you finish, remember to delete the temporary backup files from public directories.
</LI></OL><P> <A NAME="secure"></A> </P><!--TOC chapter Securing <TT>ejabberd</TT>-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc76">Chapter&#XA0;5</A>&#XA0;&#XA0;<A HREF="#secure">Securing <TT>ejabberd</TT></A></H1><!--SEC END --><P> <A NAME="secure"></A> </P><P> <A NAME="firewall"></A> </P><!--TOC section Firewall Settings-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc77">5.1</A>&#XA0;&#XA0;<A HREF="#firewall">Firewall Settings</A></H2><!--SEC END --><P> <A NAME="firewall"></A>
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc75">Chapter&#XA0;5</A>&#XA0;&#XA0;<A HREF="#secure">Securing <TT>ejabberd</TT></A></H1><!--SEC END --><P> <A NAME="secure"></A> </P><P> <A NAME="firewall"></A> </P><!--TOC section Firewall Settings-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc76">5.1</A>&#XA0;&#XA0;<A HREF="#firewall">Firewall Settings</A></H2><!--SEC END --><P> <A NAME="firewall"></A>
</P><P>You need to take the following TCP ports in mind when configuring your firewall:
</P><BLOCKQUOTE CLASS="table"><DIV CLASS="center"><DIV CLASS="center"><HR WIDTH="80%" SIZE=2></DIV>
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=1><TR><TD ALIGN=left NOWRAP><B>Port</B></TD><TD ALIGN=left NOWRAP><B>Description</B></TD></TR>
@ -3702,7 +3604,7 @@ After you finish, remember to delete the temporary backup files from public dire
<TR><TD ALIGN=left NOWRAP>port range</TD><TD ALIGN=left NOWRAP>Used for connections between Erlang nodes. This range is configurable (see section <A HREF="#epmd">5.2</A>).</TD></TR>
</TABLE>
<DIV CLASS="center"><HR WIDTH="80%" SIZE=2></DIV></DIV></BLOCKQUOTE><P> <A NAME="epmd"></A> </P><!--TOC section epmd-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc78">5.2</A>&#XA0;&#XA0;<A HREF="#epmd">epmd</A></H2><!--SEC END --><P> <A NAME="epmd"></A> </P><P><A HREF="http://www.erlang.org/doc/man/epmd.html">epmd (Erlang Port Mapper Daemon)</A>
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc77">5.2</A>&#XA0;&#XA0;<A HREF="#epmd">epmd</A></H2><!--SEC END --><P> <A NAME="epmd"></A> </P><P><A HREF="http://www.erlang.org/doc/man/epmd.html">epmd (Erlang Port Mapper Daemon)</A>
is a small name server included in Erlang/OTP
and used by Erlang programs when establishing distributed Erlang communications.
<TT>ejabberd</TT> needs <TT>epmd</TT> to use <TT>ejabberdctl</TT> and also when clustering <TT>ejabberd</TT> nodes.
@ -3727,7 +3629,7 @@ but can be configured in the file <TT>ejabberdctl.cfg</TT>.
The Erlang command-line parameter used internally is, for example:
</P><PRE CLASS="verbatim">erl ... -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375
</PRE><P> <A NAME="cookie"></A> </P><!--TOC section Erlang Cookie-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc79">5.3</A>&#XA0;&#XA0;<A HREF="#cookie">Erlang Cookie</A></H2><!--SEC END --><P> <A NAME="cookie"></A> </P><P>The Erlang cookie is a string with numbers and letters.
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc78">5.3</A>&#XA0;&#XA0;<A HREF="#cookie">Erlang Cookie</A></H2><!--SEC END --><P> <A NAME="cookie"></A> </P><P>The Erlang cookie is a string with numbers and letters.
An Erlang node reads the cookie at startup from the command-line parameter <TT>-setcookie</TT>.
If not indicated, the cookie is read from the cookie file <TT>$HOME/.erlang.cookie</TT>.
If this file does not exist, it is created immediately with a random cookie.
@ -3741,7 +3643,7 @@ to prevent unauthorized access or intrusion to an Erlang node.
The communication between Erlang nodes are not encrypted,
so the cookie could be read sniffing the traffic on the network.
The recommended way to secure the Erlang node is to block the port 4369.</P><P> <A NAME="nodename"></A> </P><!--TOC section Erlang Node Name-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc80">5.4</A>&#XA0;&#XA0;<A HREF="#nodename">Erlang Node Name</A></H2><!--SEC END --><P> <A NAME="nodename"></A> </P><P>An Erlang node may have a node name.
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc79">5.4</A>&#XA0;&#XA0;<A HREF="#nodename">Erlang Node Name</A></H2><!--SEC END --><P> <A NAME="nodename"></A> </P><P>An Erlang node may have a node name.
The name can be short (if indicated with the command-line parameter <TT>-sname</TT>)
or long (if indicated with the parameter <TT>-name</TT>).
Starting an Erlang node with -sname limits the communication between Erlang nodes to the LAN.</P><P>Using the option <TT>-sname</TT> instead of <TT>-name</TT> is a simple method
@ -3750,7 +3652,7 @@ However, it is not ultimately effective to prevent access to the Erlang node,
because it may be possible to fake the fact that you are on another network
using a modified version of Erlang <TT>epmd</TT>.
The recommended way to secure the Erlang node is to block the port 4369.</P><P> <A NAME="secure-files"></A> </P><!--TOC section Securing Sensitive Files-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc81">5.5</A>&#XA0;&#XA0;<A HREF="#secure-files">Securing Sensitive Files</A></H2><!--SEC END --><P> <A NAME="secure-files"></A> </P><P><TT>ejabberd</TT> stores sensitive data in the file system either in plain text or binary files.
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc80">5.5</A>&#XA0;&#XA0;<A HREF="#secure-files">Securing Sensitive Files</A></H2><!--SEC END --><P> <A NAME="secure-files"></A> </P><P><TT>ejabberd</TT> stores sensitive data in the file system either in plain text or binary files.
The file system permissions should be set to only allow the proper user to read,
write and execute those files and directories.</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>ejabberd configuration file: /etc/ejabberd/ejabberd.cfg</TT></B></DT><DD CLASS="dd-description">
@ -3770,9 +3672,9 @@ so it is preferable to secure the whole <TT>/var/lib/ejabberd/</TT> directory.
</DD><DT CLASS="dt-description"><B><TT>Erlang cookie file: /var/lib/ejabberd/.erlang.cookie</TT></B></DT><DD CLASS="dd-description">
See section <A HREF="#cookie">5.3</A>.
</DD></DL><P> <A NAME="clustering"></A> </P><!--TOC chapter Clustering-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc82">Chapter&#XA0;6</A>&#XA0;&#XA0;<A HREF="#clustering">Clustering</A></H1><!--SEC END --><P> <A NAME="clustering"></A>
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc81">Chapter&#XA0;6</A>&#XA0;&#XA0;<A HREF="#clustering">Clustering</A></H1><!--SEC END --><P> <A NAME="clustering"></A>
</P><P> <A NAME="howitworks"></A> </P><!--TOC section How it Works-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc83">6.1</A>&#XA0;&#XA0;<A HREF="#howitworks">How it Works</A></H2><!--SEC END --><P> <A NAME="howitworks"></A>
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc82">6.1</A>&#XA0;&#XA0;<A HREF="#howitworks">How it Works</A></H2><!--SEC END --><P> <A NAME="howitworks"></A>
</P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
be run on different machines that are connected via a network. They all
must have the ability to connect to port 4369 of all another nodes, and must
@ -3786,29 +3688,29 @@ router,
</LI><LI CLASS="li-itemize">session manager,
</LI><LI CLASS="li-itemize">s2s manager.
</LI></UL><P> <A NAME="router"></A> </P><!--TOC subsection Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc84">6.1.1</A>&#XA0;&#XA0;<A HREF="#router">Router</A></H3><!--SEC END --><P> <A NAME="router"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc83">6.1.1</A>&#XA0;&#XA0;<A HREF="#router">Router</A></H3><!--SEC END --><P> <A NAME="router"></A>
</P><P>This module is the main router of XMPP packets on each node. It
routes them based on their destination&#X2019;s domains. It uses a global
routing table. The domain of the packet&#X2019;s destination is searched in the
routing table, and if it is found, the packet is routed to the
appropriate process. If not, it is sent to the s2s manager.</P><P> <A NAME="localrouter"></A> </P><!--TOC subsection Local Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc85">6.1.2</A>&#XA0;&#XA0;<A HREF="#localrouter">Local Router</A></H3><!--SEC END --><P> <A NAME="localrouter"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc84">6.1.2</A>&#XA0;&#XA0;<A HREF="#localrouter">Local Router</A></H3><!--SEC END --><P> <A NAME="localrouter"></A>
</P><P>This module routes packets which have a destination domain equal to
one of this server&#X2019;s host names. If the destination JID has a non-empty user
part, it is routed to the session manager, otherwise it is processed depending
on its content.</P><P> <A NAME="sessionmanager"></A> </P><!--TOC subsection Session Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc86">6.1.3</A>&#XA0;&#XA0;<A HREF="#sessionmanager">Session Manager</A></H3><!--SEC END --><P> <A NAME="sessionmanager"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc85">6.1.3</A>&#XA0;&#XA0;<A HREF="#sessionmanager">Session Manager</A></H3><!--SEC END --><P> <A NAME="sessionmanager"></A>
</P><P>This module routes packets to local users. It looks up to which user
resource a packet must be sent via a presence table. Then the packet is
either routed to the appropriate c2s process, or stored in offline
storage, or bounced back.</P><P> <A NAME="s2smanager"></A> </P><!--TOC subsection s2s Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc87">6.1.4</A>&#XA0;&#XA0;<A HREF="#s2smanager">s2s Manager</A></H3><!--SEC END --><P> <A NAME="s2smanager"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc86">6.1.4</A>&#XA0;&#XA0;<A HREF="#s2smanager">s2s Manager</A></H3><!--SEC END --><P> <A NAME="s2smanager"></A>
</P><P>This module routes packets to other XMPP servers. First, it
checks if an opened s2s connection from the domain of the packet&#X2019;s
source to the domain of the packet&#X2019;s destination exists. If that is the case,
the s2s manager routes the packet to the process
serving this connection, otherwise a new connection is opened.</P><P> <A NAME="cluster"></A> </P><!--TOC section Clustering Setup-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc88">6.2</A>&#XA0;&#XA0;<A HREF="#cluster">Clustering Setup</A></H2><!--SEC END --><P> <A NAME="cluster"></A>
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc87">6.2</A>&#XA0;&#XA0;<A HREF="#cluster">Clustering Setup</A></H2><!--SEC END --><P> <A NAME="cluster"></A>
</P><P>Suppose you already configured <TT>ejabberd</TT> on one machine named (<TT>first</TT>),
and you need to setup another one to make an <TT>ejabberd</TT> cluster. Then do
following steps:</P><OL CLASS="enumerate" type=1><LI CLASS="li-enumerate">
@ -3842,14 +3744,14 @@ the Erlang shell. This probably can take some time if Mnesia has not yet
transfered and processed all data it needed from <TT>first</TT>.</LI><LI CLASS="li-enumerate">Now run <TT>ejabberd</TT> on <TT>second</TT> with a configuration similar as
on <TT>first</TT>: you probably do not need to duplicate &#X2018;<CODE>acl</CODE>&#X2019;
and &#X2018;<CODE>access</CODE>&#X2019; options because they will be taken from
<TT>first</TT>; and <CODE>mod_irc</CODE> should be
<TT>first</TT>. If you installed <CODE>mod_irc</CODE>, notice that it should be
enabled only on one machine in the cluster.
</LI></OL><P>You can repeat these steps for other machines supposed to serve this
domain.</P><P> <A NAME="servicelb"></A> </P><!--TOC section Service Load-Balancing-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc89">6.3</A>&#XA0;&#XA0;<A HREF="#servicelb">Service Load-Balancing</A></H2><!--SEC END --><P> <A NAME="servicelb"></A>
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc88">6.3</A>&#XA0;&#XA0;<A HREF="#servicelb">Service Load-Balancing</A></H2><!--SEC END --><P> <A NAME="servicelb"></A>
</P><P> <A NAME="componentlb"></A> </P><!--TOC subsection Components Load-Balancing-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc90">6.3.1</A>&#XA0;&#XA0;<A HREF="#componentlb">Components Load-Balancing</A></H3><!--SEC END --><P> <A NAME="componentlb"></A> </P><P> <A NAME="domainlb"></A> </P><!--TOC subsection Domain Load-Balancing Algorithm-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc91">6.3.2</A>&#XA0;&#XA0;<A HREF="#domainlb">Domain Load-Balancing Algorithm</A></H3><!--SEC END --><P> <A NAME="domainlb"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc89">6.3.1</A>&#XA0;&#XA0;<A HREF="#componentlb">Components Load-Balancing</A></H3><!--SEC END --><P> <A NAME="componentlb"></A> </P><P> <A NAME="domainlb"></A> </P><!--TOC subsection Domain Load-Balancing Algorithm-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc90">6.3.2</A>&#XA0;&#XA0;<A HREF="#domainlb">Domain Load-Balancing Algorithm</A></H3><!--SEC END --><P> <A NAME="domainlb"></A>
</P><P><TT>ejabberd</TT> includes an algorithm to load balance the components that are plugged on an <TT>ejabberd</TT> cluster. It means that you can plug one or several instances of the same component on each <TT>ejabberd</TT> cluster and that the traffic will be automatically distributed.</P><P>The default distribution algorithm try to deliver to a local instance of a component. If several local instances are available, one instance is chosen randomly. If no instance is available locally, one instance is chosen randomly among the remote component instances.</P><P>If you need a different behaviour, you can change the load balancing behaviour with the option <TT>domain_balancing</TT>. The syntax of the option is the following:
</P><DL CLASS="description"><DT CLASS="dt-description"><B><TT>{domain_balancing, "component.example.com", BalancingCriteria}.</TT></B></DT></DL><P>Several balancing criteria are available:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
@ -3858,12 +3760,12 @@ domain.</P><P> <A NAME="servicelb"></A> </P><!--TOC section Service Load-Balanci
</LI><LI CLASS="li-itemize"><TT>bare_destination</TT>: the bare JID (without resource) of the packet <TT>to</TT> attribute is used.
</LI><LI CLASS="li-itemize"><TT>bare_source</TT>: the bare JID (without resource) of the packet <TT>from</TT> attribute is used.
</LI></UL><P>If the value corresponding to the criteria is the same, the same component instance in the cluster will be used.</P><P> <A NAME="lbbuckets"></A> </P><!--TOC subsection Load-Balancing Buckets-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc92">6.3.3</A>&#XA0;&#XA0;<A HREF="#lbbuckets">Load-Balancing Buckets</A></H3><!--SEC END --><P> <A NAME="lbbuckets"></A>
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc91">6.3.3</A>&#XA0;&#XA0;<A HREF="#lbbuckets">Load-Balancing Buckets</A></H3><!--SEC END --><P> <A NAME="lbbuckets"></A>
</P><P>When there is a risk of failure for a given component, domain balancing can cause service trouble. If one component is failing the service will not work correctly unless the sessions are rebalanced.</P><P>In this case, it is best to limit the problem to the sessions handled by the failing component. This is what the <TT>domain_balancing_component_number</TT> option does, making the load balancing algorithm not dynamic, but sticky on a fix number of component instances.</P><P>The syntax is:
</P><DL CLASS="description"><DT CLASS="dt-description"><B><TT>{domain_balancing_component_number, "component.example.com", Number}.</TT></B></DT></DL><P> <A NAME="debugging"></A> </P><!--TOC chapter Debugging-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc93">Chapter&#XA0;7</A>&#XA0;&#XA0;<A HREF="#debugging">Debugging</A></H1><!--SEC END --><P> <A NAME="debugging"></A>
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc92">Chapter&#XA0;7</A>&#XA0;&#XA0;<A HREF="#debugging">Debugging</A></H1><!--SEC END --><P> <A NAME="debugging"></A>
</P><P> <A NAME="logfiles"></A> </P><!--TOC section Log Files-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc94">7.1</A>&#XA0;&#XA0;<A HREF="#logfiles">Log Files</A></H2><!--SEC END --><P> <A NAME="logfiles"></A> </P><P>An <TT>ejabberd</TT> node writes two log files:
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc93">7.1</A>&#XA0;&#XA0;<A HREF="#logfiles">Log Files</A></H2><!--SEC END --><P> <A NAME="logfiles"></A> </P><P>An <TT>ejabberd</TT> node writes two log files:
</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>ejabberd.log</TT></B></DT><DD CLASS="dd-description"> is the ejabberd service log, with the messages reported by <TT>ejabberd</TT> code
</DD><DT CLASS="dt-description"><B><TT>erlang.log</TT></B></DT><DD CLASS="dd-description"> is the Erlang/OTP system log, with the messages reported by Erlang/OTP using SASL (System Architecture Support Libraries)
@ -3885,12 +3787,12 @@ The ejabberdctl command <TT>reopen-log</TT>
(please refer to section <A HREF="#ectl-commands">4.1.1</A>)
reopens the log files,
and also renames the old ones if you didn&#X2019;t rename them.</P><P> <A NAME="debugconsole"></A> </P><!--TOC section Debug Console-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc95">7.2</A>&#XA0;&#XA0;<A HREF="#debugconsole">Debug Console</A></H2><!--SEC END --><P> <A NAME="debugconsole"></A> </P><P>The Debug Console is an Erlang shell attached to an already running <TT>ejabberd</TT> server.
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc94">7.2</A>&#XA0;&#XA0;<A HREF="#debugconsole">Debug Console</A></H2><!--SEC END --><P> <A NAME="debugconsole"></A> </P><P>The Debug Console is an Erlang shell attached to an already running <TT>ejabberd</TT> server.
With this Erlang shell, an experienced administrator can perform complex tasks.</P><P>This shell gives complete control over the <TT>ejabberd</TT> server,
so it is important to use it with extremely care.
There are some simple and safe examples in the article
<A HREF="http://www.ejabberd.im/interconnect-erl-nodes">Interconnecting Erlang Nodes</A></P><P>To exit the shell, close the window or press the keys: control+c control+c.</P><P> <A NAME="watchdog"></A> </P><!--TOC section Watchdog Alerts-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc96">7.3</A>&#XA0;&#XA0;<A HREF="#watchdog">Watchdog Alerts</A></H2><!--SEC END --><P> <A NAME="watchdog"></A>
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc95">7.3</A>&#XA0;&#XA0;<A HREF="#watchdog">Watchdog Alerts</A></H2><!--SEC END --><P> <A NAME="watchdog"></A>
</P><P><TT>ejabberd</TT> includes a watchdog mechanism that may be useful to developers
when troubleshooting a problem related to memory usage.
If a process in the <TT>ejabberd</TT> server consumes more memory than the configured threshold,
@ -3910,7 +3812,7 @@ or in a conversation with the watchdog alert bot.</P><P>The syntax is:
To remove all watchdog admins, set the option with an empty list:
</P><PRE CLASS="verbatim">{watchdog_admins, []}.
</PRE><P> <A NAME="i18ni10n"></A> </P><!--TOC chapter Internationalization and Localization-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc97">Appendix&#XA0;A</A>&#XA0;&#XA0;<A HREF="#i18ni10n">Internationalization and Localization</A></H1><!--SEC END --><P> <A NAME="i18ni10n"></A>
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc96">Appendix&#XA0;A</A>&#XA0;&#XA0;<A HREF="#i18ni10n">Internationalization and Localization</A></H1><!--SEC END --><P> <A NAME="i18ni10n"></A>
</P><P>The source code of <TT>ejabberd</TT> supports localization.
The translators can edit the
<A HREF="http://www.gnu.org/software/gettext/">gettext</A> .po files
@ -3945,9 +3847,9 @@ HTTP header &#X2018;Accept-Language: ru&#X2019;</TD></TR>
</TABLE></DIV>
<A NAME="fig:webadmmainru"></A>
<DIV CLASS="center"><HR WIDTH="80%" SIZE=2></DIV></DIV></BLOCKQUOTE><P> <A NAME="releasenotes"></A> </P><!--TOC chapter Release Notes-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc98">Appendix&#XA0;B</A>&#XA0;&#XA0;<A HREF="#releasenotes">Release Notes</A></H1><!--SEC END --><P> <A NAME="releasenotes"></A>
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc97">Appendix&#XA0;B</A>&#XA0;&#XA0;<A HREF="#releasenotes">Release Notes</A></H1><!--SEC END --><P> <A NAME="releasenotes"></A>
</P><P>Release notes are available from <A HREF="http://www.process-one.net/en/ejabberd/release_notes/">ejabberd Home Page</A></P><P> <A NAME="acknowledgements"></A> </P><!--TOC chapter Acknowledgements-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc99">Appendix&#XA0;C</A>&#XA0;&#XA0;<A HREF="#acknowledgements">Acknowledgements</A></H1><!--SEC END --><P> <A NAME="acknowledgements"></A> </P><P>Thanks to all people who contributed to this guide:
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc98">Appendix&#XA0;C</A>&#XA0;&#XA0;<A HREF="#acknowledgements">Acknowledgements</A></H1><!--SEC END --><P> <A NAME="acknowledgements"></A> </P><P>Thanks to all people who contributed to this guide:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
Alexey Shchepin (<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A>)
</LI><LI CLASS="li-itemize">Badlop (<A HREF="xmpp:badlop@jabberes.org"><TT>xmpp:badlop@jabberes.org</TT></A>)
@ -3959,7 +3861,7 @@ Alexey Shchepin (<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT
</LI><LI CLASS="li-itemize">Sergei Golovan (<A HREF="xmpp:sgolovan@nes.ru"><TT>xmpp:sgolovan@nes.ru</TT></A>)
</LI><LI CLASS="li-itemize">Vsevolod Pelipas (<A HREF="xmpp:vsevoload@jabber.ru"><TT>xmpp:vsevoload@jabber.ru</TT></A>)
</LI></UL><P> <A NAME="copyright"></A> </P><!--TOC chapter Copyright Information-->
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc100">Appendix&#XA0;D</A>&#XA0;&#XA0;<A HREF="#copyright">Copyright Information</A></H1><!--SEC END --><P> <A NAME="copyright"></A> </P><P>Ejabberd Installation and Operation Guide.<BR>
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc99">Appendix&#XA0;D</A>&#XA0;&#XA0;<A HREF="#copyright">Copyright Information</A></H1><!--SEC END --><P> <A NAME="copyright"></A> </P><P>Ejabberd Installation and Operation Guide.<BR>
Copyright &#XA9; 2003 &#X2014; 2009 ProcessOne</P><P>This document is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2

View File

@ -72,7 +72,6 @@
\newcommand{\modecho}{\module{mod\_echo}}
\newcommand{\modhttpbind}{\module{mod\_http\_bind}}
\newcommand{\modhttpfileserver}{\module{mod\_http\_fileserver}}
\newcommand{\modirc}{\module{mod\_irc}}
\newcommand{\modlast}{\module{mod\_last}}
\newcommand{\modlastodbc}{\module{mod\_last\_odbc}}
\newcommand{\modmuc}{\module{mod\_muc}}
@ -86,7 +85,6 @@
\newcommand{\modprivateodbc}{\module{mod\_private\_odbc}}
\newcommand{\modproxy}{\module{mod\_proxy65}}
\newcommand{\modpubsub}{\module{mod\_pubsub}}
\newcommand{\modpubsubodbc}{\module{mod\_pubsub\_odbc}}
\newcommand{\modregister}{\module{mod\_register}}
\newcommand{\modroster}{\module{mod\_roster}}
\newcommand{\modrosterodbc}{\module{mod\_roster\_odbc}}
@ -307,16 +305,14 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need:
\begin{itemize}
\item GNU Make
\item GCC
\item Libexpat 1.95 or higher
\item Erlang/OTP R10B-9 or higher. The recommended version is R12B-5. Support for R13 is experimental.
\item Erlang/OTP R12B-4 or higher, R13B or higher.
\item exmpp 0.9.1 or higher
\item OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL encryption. Optional, highly recommended.
\item Zlib 1.2.3 or higher, for Stream Compression support (\xepref{0138}). Optional.
\item Erlang mysql library. Optional. For MySQL authentication or storage. See section \ref{compilemysql}.
\item Erlang pgsql library. Optional. For PostgreSQL authentication or storage. See section \ref{compilepgsql}.
\item PAM library. Optional. For Pluggable Authentication Modules (PAM). See section \ref{pam}.
\item GNU Iconv 1.8 or higher, for the IRC Transport (mod\_irc). Optional. Not needed on systems with GNU Libc. See section \ref{modirc}.
\item ImageMagick's Convert program. Optional. For CAPTCHA challenges. See section \ref{captcha}.
\item exmpp 0.9.1 or higher. Optional. For import/export user data with \xepref{0227} XML files.
\end{itemize}
\makesubsection{download}{Download Source Code}
@ -516,11 +512,9 @@ gmake -f Makefile.gi ginstall
To compile \ejabberd{} on a Microsoft Windows system, you need:
\begin{itemize}
\item MS Visual C++ 6.0 Compiler
\item \footahref{http://www.erlang.org/download.html}{Erlang/OTP R11B-5}
\item \footahref{http://www.erlang.org/download.html}{Erlang/OTP R12B-5}. Support for R13 or higher is experimental.
\item \footahref{http://support.process-one.net/doc/display/EXMPP}{exmpp 0.9.1 or higher}
\item \footahref{http://sourceforge.net/project/showfiles.php?group\_id=10127\&package\_id=11277}{Expat 2.0.0 or higher}
\item
\footahref{http://www.gnu.org/software/libiconv/}{GNU Iconv 1.9.2}
(optional)
\item \footahref{http://www.slproweb.com/products/Win32OpenSSL.html}{Shining Light OpenSSL 0.9.8d or higher}
(to enable SSL connections)
\item \footahref{http://www.zlib.net/}{Zlib 1.2.3 or higher}
@ -532,28 +526,19 @@ To compile \ejabberd{} on a Microsoft Windows system, you need:
We assume that we will try to put as much library as possible into \verb|C:\sdk\| to make it easier to track what is install for \ejabberd{}.
\begin{enumerate}
\item Install Erlang emulator (for example, into \verb|C:\sdk\erl5.5.5|).
\item Install Erlang emulator (for example, into \verb|C:\sdk\erl5.6.5|).
\item Install Expat library into \verb|C:\sdk\Expat-2.0.0|
directory.
Copy file \verb|C:\sdk\Expat-2.0.0\Libs\libexpat.dll|
to your Windows system directory (for example, \verb|C:\WINNT| or
\verb|C:\WINNT\System32|)
\item Build and install the Iconv library into the directory
\verb|C:\sdk\GnuWin32|.
Copy file \verb|C:\sdk\GnuWin32\bin\lib*.dll| to your
Windows system directory (more installation instructions can be found in the
file README.woe32 in the iconv distribution).
Note: instead of copying libexpat.dll and iconv.dll to the Windows
directory, you can add the directories
\verb|C:\sdk\Expat-2.0.0\Libs| and
\verb|C:\sdk\GnuWin32\bin| to the \verb|PATH| environment
variable.
Note: instead of copying libexpat.dll to the Windows
directory, you can add the directory \verb|C:\sdk\Expat-2.0.0\Libs|
to the \verb|PATH| environment variable.
\item Install OpenSSL in \verb|C:\sdk\OpenSSL| and add \verb|C:\sdk\OpenSSL\lib\VC| to your path or copy the binaries to your system directory.
\item Install ZLib in \verb|C:\sdk\gnuWin32|. Copy
\verb|C:\sdk\GnuWin32\bin\zlib1.dll| to your system directory. If you change your path it should already be set after libiconv install.
\verb|C:\sdk\GnuWin32\bin\zlib1.dll| to your system directory.
\item Make sure the you can access Erlang binaries from your path. For example: \verb|set PATH=%PATH%;"C:\sdk\erl5.6.5\bin"|
\item Depending on how you end up actually installing the library you might need to check and tweak the paths in the file configure.erl.
\item While in the directory \verb|ejabberd\src| run:
@ -2425,8 +2410,7 @@ The following table lists all modules included in \ejabberd{}.
\hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\
\hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\
\hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\
\hline \ahrefloc{modecho}{\modecho{}} & Echoes XMPP stanzas & \\
\hline \ahrefloc{modirc}{\modirc{}} & IRC transport & \\
\hline \ahrefloc{modecho}{\modecho{}} & Echoes Jabber packets & \\
\hline \ahrefloc{modlast}{\modlast{}} & Last Activity (\xepref{0012}) & \\
\hline \ahrefloc{modlast}{\modlastodbc{}} & Last Activity (\xepref{0012}) & supported DB (*) \\
\hline \ahrefloc{modmuc}{\modmuc{}} & Multi-User Chat (\xepref{0045}) & \\
@ -2440,7 +2424,6 @@ The following table lists all modules included in \ejabberd{}.
\hline \ahrefloc{modprivate}{\modprivateodbc{}} & Private XML Storage (\xepref{0049}) & supported DB (*) \\
\hline \ahrefloc{modproxy}{\modproxy{}} & SOCKS5 Bytestreams (\xepref{0065}) & \\
\hline \ahrefloc{modpubsub}{\modpubsub{}} & Pub-Sub (\xepref{0060}), PEP (\xepref{0163}) & \modcaps{} \\
\hline \ahrefloc{modpubsub}{\modpubsubodbc{}} & Pub-Sub (\xepref{0060}), PEP (\xepref{0163}) & supported DB (*) and \modcaps{} \\
\hline \ahrefloc{modregister}{\modregister{}} & In-Band Registration (\xepref{0077}) & \\
\hline \ahrefloc{modroster}{\modroster{}} & Roster management (XMPP IM) & \\
\hline \ahrefloc{modroster}{\modrosterodbc{}} & Roster management (XMPP IM) & supported DB (*) \\
@ -2911,75 +2894,6 @@ And define it as a handler in the HTTP service:
]}.
\end{verbatim}
\makesubsection{modirc}{\modirc{}}
\ind{modules!\modirc{}}\ind{IRC}
This module is an IRC transport that can be used to join channels on IRC
servers.
End user information:
\ind{protocols!groupchat 1.0}\ind{protocols!XEP-0045: Multi-User Chat}
\begin{itemize}
\item A \XMPP{} client with `groupchat 1.0' support or Multi-User
Chat support (\xepref{0045}) is necessary to join IRC channels.
\item An IRC channel can be joined in nearly the same way as joining a
\XMPP{} Multi-User Chat room. The difference is that the room name will
be `channel\%\jid{irc.example.org}' in case \jid{irc.example.org} is
the IRC server hosting `channel'. And of course the host should point
to the IRC transport instead of the Multi-User Chat service.
\item You can register your nickame by sending `IDENTIFY password' to \\
\jid{nickserver!irc.example.org@irc.jabberserver.org}.
\item Entering your password is possible by sending `LOGIN nick password' \\
to \jid{nickserver!irc.example.org@irc.jabberserver.org}.
\item The IRC transport provides Ad-Hoc Commands (\xepref{0050})
to join a channel, and to set custom IRC username and encoding.
\item When using a popular \XMPP{} server, it can occur that no
connection can be achieved with some IRC servers because they limit the
number of conections from one IP.
\end{itemize}
Options:
\begin{description}
\hostitem{irc}
\titem{\{access, AccessName\}} \ind{options!access}This option can be used to specify who
may use the IRC transport (default value: \term{all}).
\titem{\{default\_encoding, Encoding\}} \ind{options!defaultencoding}Set the default IRC encoding.
Default value: \term{"koi8-r"}
\end{description}
Examples:
\begin{itemize}
\item In the first example, the IRC transport is available on (all) your
virtual host(s) with the prefix `\jid{irc.}'. Furthermore, anyone is
able to use the transport. The default encoding is set to "iso8859-15".
\begin{verbatim}
{modules,
[
...
{mod_irc, [{access, all}, {default_encoding, "iso8859-15"}]},
...
]}.
\end{verbatim}
\item In next example the IRC transport is available with JIDs with prefix \jid{irc-t.net}.
Moreover, the transport is only accessible to two users
of \term{example.org}, and any user of \term{example.com}:
\begin{verbatim}
{acl, paying_customers, {user, "customer1", "example.org"}}.
{acl, paying_customers, {user, "customer2", "example.org"}}.
{acl, paying_customers, {server, "example.com"}}.
{access, irc_users, [{allow, paying_customers}, {deny, all}]}.
{modules,
[
...
{mod_irc, [{access, irc_users},
{host, "irc.example.net"}]},
...
]}.
\end{verbatim}
\end{itemize}
\makesubsection{modlast}{\modlast{}}
\ind{modules!\modlast{}}\ind{protocols!XEP-0012: Last Activity}
@ -3578,30 +3492,12 @@ Options:
Define the maximum number of items that can be stored in a node.
Default value is 10.
\titem{\{plugins, [ Plugin, ...]\}} \ind{options!plugins}
To specify which pubsub node plugins to use.
The first one in the list is used by default.
If this option is not defined, the default plugins list is: \term{["flat"]}.
PubSub clients can define which plugin to use when creating a node:
add \term{type='plugin-name'} attribute to the \term{create} stanza element.
\titem{\{nodetree, Nodetree\}} \ind{options!nodetree}
To specify which nodetree to use.
If not defined, the default pubsub nodetree is used: "tree".
Only one nodetree can be used per host, and is shared by all node plugins.
The "virtual" nodetree does not store nodes on database.
This saves resources on systems with tons of nodes.
If using the "virtual" nodetree,
you can only enable those node plugins:
["flat","pep"] or ["flat"];
any other plugins configuration will not work.
Also, all nodes will have the defaut configuration,
and this can not be changed.
Using "virtual" nodetree requires to start from a clean database,
it will not work if you used the default "tree" nodetree before.
The "dag" nodetree provides experimental support for PubSub Collection Nodes (\xepref{0248}).
In that case you should also add "dag" node plugin as default, for example:
\term{\{plugins, ["dag","flat","hometree","pep"]\}}
To specify which pubsub node plugins to use. If not defined, the default
pubsub plugin is always used.
\titem{\{nodetree, Name\}} \ind{options!nodetree}
To specify which nodetree to use. If not defined, the default pubsub
nodetree is used. Only one nodetree can be used per host,
and is shared by all node plugins.
\titem{\{ignore\_pep\_from\_offline, false|true\}} \ind{options!ignore\_pep\_from\_offline}
To specify whether or not we should get last published PEP items
from users in our roster which are offline when we connect. Value is true or false.
@ -3621,32 +3517,19 @@ Options:
% This option allows to create additional pubsub virtual hosts in a single module instance.
\end{description}
Example of configuration that uses flat nodes as default, and allows use of flat, nodetree and pep nodes:
Example:
\begin{verbatim}
{modules,
[
...
{mod_pubsub, [
{access_createnode, pubsub_createnode},
{plugins, ["flat", "hometree", "pep"]}
]},
...
]}.
\end{verbatim}
Using ODBC database requires use of dedicated plugins. The following example shows previous configuration
with ODBC usage:
\begin{verbatim}
{modules,
[
...
{mod_pubsub_odbc, [
{access_createnode, pubsub_createnode},
{plugins, ["flat_odbc", "hometree_odbc", "pep_odbc"]}
]},
{plugins, ["default", "pep"]}
]}
...
]}.
\end{verbatim}
% {served_hosts, ["example.com", "example.org"]}
\makesubsection{modregister}{\modregister{}}
\ind{modules!\modregister{}}\ind{protocols!XEP-0077: In-Band Registration}\ind{public registration}
@ -4983,7 +4866,7 @@ mnesia:change_table_copy_type(schema, node(), disc_copies).
\item Now run \ejabberd{} on \term{second} with a configuration similar as
on \term{first}: you probably do not need to duplicate `\verb|acl|'
and `\verb|access|' options because they will be taken from
\term{first}; and \verb|mod_irc| should be
\term{first}. If you installed \verb|mod_irc|, notice that it should be
enabled only on one machine in the cluster.
\end{enumerate}

View File

@ -68,7 +68,7 @@ Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
\item \marking{Internationalized:} \ejabberd{} leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
\begin{itemize}
\item Translated to 25 languages. %%\improved{}
\item Translated to 24 languages. %%\improved{}
\item Support for \footahref{http://www.ietf.org/rfc/rfc3490.txt}{IDNA}.
\end{itemize}
@ -127,7 +127,6 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
\item Users Directory based on users vCards.
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{0163}{Personal Eventing via Pubsub}.
\item Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
\item IRC transport.
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
\end{itemize}
\end{itemize}

View File

@ -1,271 +0,0 @@
Release Notes
ejabberd 2.1.0
ejabberd 2.1.0 is a major new version for ejabberd adding many
new features, performance and scalability improvements.
ejabberd 2.1.0 includes many new features, improvements and bug fixes.
A complete list of changes can be retrieved from:
http://redir.process-one.net/ejabberd-2.1.0
The new code can be downloaded from ejabberd download page:
http://www.process-one.net/en/ejabberd/
New features and improvements:
* Anti-abuse
- Captcha support (XEP-0158). The example script uses ImageMagick.
- New option: registration_timeout to limit registrations by time
- Use send timeout to avoid locking on gen_tcp:send
- mod_ip_blacklist: client blacklist support by IP
* API
- ejabberd_http provides Host, Port, Headers and Protocol in HTTP requests
- Export function to create MUC room
- New events: s2s_send_packet and s2s_receive_packet
- New event: webadmin_user_parse_query when POST in web admin user page
- Support distributed hooks over the cluster
* Authentification
- Extauth responses: log strange responses and add timeout
* Binary Installer
- Includes exmpp library to support import/export XML files
* Caps
- Remove useless caps tables entries
- mod_caps must handle correctly external contacts with several resources
- Complain if mod_caps disabled and mod_pubsub has PEP plugin enabled
* Clustering and Architecture
* Configuration
- Added option access_max_user_messages for mod_offline
- Added option backlog for ejabberd_listener to increase TCP backlog
- Added option define_macro and use_macro
- Added option include_config_file to include additional configuration files
- Added option max_fsm_queue
- Added option outgoing_s2s_options to define IP address families and timeout
- Added option registration_timeout to ejabberd.cfg.example
- Added option s2s_dns_options to define DNS timeout and retries
- Added option ERL_OPTIONS to ejabberdctl.cfg
- Added option FIREWALL_WINDOW to ejabberdctl.cfg
- Added option EJABBERD_PID_PATH to ejabberdctl.cfg
- Deleted option user_max_messages of mod_offline
- Check certfiles are readable on server start and listener start
- Config file management mix file reading and sanity check
- Include example PAM configuration file: ejabberd.pam
- New ejabberd listener: ejabberd_stun
- Support to bind the same port to multiple interfaces
- New syntax to specify the IP address and IPv6 in listeners
configuration. The old options {ip,{1,2,3,4}} and inet6 are
supported even if they aren't documented.
- New syntax to specify the network protocol: tcp or udp
- Report error at startup if a listener module isn't available
- Only listen in a port when actually ready to serve requests
- In default config, only local accounts can create rooms and PubSub nodes
* Core architecture
- More verbose error reporting for xml:element_to_string
- Deliver messages when first presence is Invisible
- Better log message when config file is not found
- Include original timestamp on delayed presences
* Crypto
- Do not ask certificate for client (c2s)
- SSL code remove from ejabberd in favor of TLS
- Support Zlib compression after STARTTLS encryption
- tls v1 client hello
* Documentation
- Document possible default MUC room options
- Document service_check_from in the Guide
- Document s2s_default_policy and s2s_host in the Guide
- new command and guide instructions to change node name in a Mnesia database
* ejabberd commands
- ejabberd commands: separate command definition and calling interface
- access_commands restricts who can execute what commands and arguments
- ejabberdctl script now displays help and categorization of commands
* HTTP Binding and HTTP Polling
- HTTP-Bind: module optimization and clean-up
- HTTP-Bind: allow configuration of max_inactivity timeout
- HTTP-Poll: turn session timeout into a config file parameter
* Jingle
- STUN server that facilitates the client-to-client negotiation process
* LDAP
- Faster reconnection to LDAP servers
- LDAP filter optimisation: Add ability to filter user in ejabberd and not LDAP
- LDAP differentiates failed auth and unavailable auth service
- Improve LDAP logging
- LDAPS support using TLS.
* Localization
- Use Gettext PO for translators, export to ejabberd MSG
- Support translation files for additional projects
- Most translations are updated to latest code
- New translation to Greek language
* Multi-User Chat (MUC)
- Allow admins to send messages to rooms
- Allow to store room description
- Captcha support in MUC: the admin of a room can configure it to
require participants to fill a captcha to join the room.
- Limit number of characters in Room ID, Name and Description
- Prevent unvoiced occupants from changing nick
- Support Result Set Management (XEP-0059) for listing rooms
- Support for decline of invitation to MUC room
- mod_muc_log options: plaintext format; filename with only room name
* Performance
- Run roster_get_jid_info only if privacy list has subscription or group item
- Significant PubSub performance improvements
* Publish-Subscribe
- Add nodetree filtering/authorization
- Add subscription option support for collection nodes
- Allow Multiple Subscriptions
- Check option of the nodetree instead of checking configuration
- Implement whitelist authorize and roster access model
- Implicit item deletion is not notified when deleting node
- Make PubSub x-data configuration form handles list value
- Make default node name convention XEP-compatible, document usage of hierarchy
- Node names are used verbatim, without separating by slash, unless a
node plugin uses its own separator
- Send authorization update event (XEP-0060, 8.6)
- Support of collection node subscription options
- Support ODBC storage. Experimental, needs more testing.
* Relational databases:
- Added MSSQL 2000 and 2005
- Privacy rules storage in MySQL
- Implement reliable ODBC transaction nesting
* Source Package
- Default installation directories changed. Please see the upgrade notes below.
- Allow more environment variable overrides in ejabberdctl
- ChangeLog is not edited manually anymore; it's generated automatically.
- Install the ejabberd Guide
- Install the ejabberd include files
- New option for the 'configure' script: --enable-user which installs
ejabberd granting permission to manage it to a regular system user;
no need to use root account to.
- Only try to install epam if pam was enabled in configure script
- Spool, config and log dirs: owner writes, group reads, others do nothing.
- Provides an example ejabberd.init file
* S2S
- Option to define s2s outgoing behaviour: IPv4, IPv6 and timeout
- DNS timeout and retries, configurable with s2s_dns_options.
* Shared rosters
- When a member is added/removed to group, send roster upgrade to group members
* Users management
- When account is deleted, cancel presence subscription for all roster items
* XEP Support
- Added XEP-0059 Result Set Management (for listing rooms)
- Added XEP-0082 Date Time
- Added XEP-0085 Chat State Notifications
- Added XEP-0157 Contact Addresses for XMPP Services
- Added XEP-0158 CAPTCHA Forms (in MUC rooms)
- Added STUN server, for XEP-0176: Jingle ICE-UDP Transport Method
- Added XEP-0199 XMPP Ping
- Added XEP-0202 Entity Time
- Added XEP-0203 Delayed Delivery
- Added XEP-0227 Portable Import/Export Format for XMPP-IM Servers
- Added XEP-0237 Roster Versioning
* Web Admin
- Display the connection method of user sessions
- Cross link of ejabberd users in the list of users and rosters
- Improved the browsing menu: don't disappear when browsing a host or node
- Include Last-Modified HTTP header in responses to allow caching
- Make some Input areas multiline: options of listening ports and modules
- Support PUT and DELETE methods in ejabberd_http
- WebAdmin serves Guide and links to related sections
* Web plugins
- mod_http_fileserver: new option directory_indices, and improve logging
Important Notes:
- ejabberd 2.1.0 requires Erlang R10B-9 or higher.
R12B-5 is the recommended version. Support for R13B is experimental.
Upgrading From ejabberd 1.x.x:
- Check the Release Notes of the intermediate versions for additional
information about database or configuration changes.
Upgrading From ejabberd 2.0.x:
- The database schemas didn't change since ejabberd 1.1.4.
Anyway, it is recommended to backup the Mnesia spool directory and
your SQL database (if used) before upgrading ejabberd.
- The plugin of mod_pubsub "default" is renamed to "flat". You need
to edit the ejabberd configuration file and replace those names.
- The listener options 'ip' and inet6' are not documented anymore
but they are supported and you can continue using them.
There is a new syntax to define IP address and IP version.
As usual, check the ejabberd Guide for more information.
- The log file sasl.log is now called erlang.log
- ejabberdctl commands now have _ characters instead of -.
For backwards compatibility, it is still supported -.
- mod_offline has a new option: access_max_user_messages.
The old option user_max_messages is no longer supported.
- If you upgrade from ejabberd trunk SVN, you must execute this:
$ ejabberdctl rename_default_nodeplugin
- Default installation directories changed a bit:
* The Mnesia spool files that were previously stored in
/var/lib/ejabberd/db/NODENAME/*
are now stored in
/var/lib/ejabberd/*
* The directories
/var/lib/ejabberd/ebin
/var/lib/ejabberd/priv
and their content is now installed as
/lib/ejabberd/ebin
/lib/ejabberd/priv
* There is a new directory with Erlang header files:
/lib/ejabberd/include
* There is a new directory for ejabberd documentation,
which includes the Admin Guide and the release notes::
/share/doc/ejabberd
- How to upgrade from previous version to ejabberd 2.1.0:
1. Stop the old instance of ejabberd.
2. Run 'make install' of new ejabberd 2.1.0 to create the new directories.
3. Copy the content of your old directory:
/var/lib/ejabberd/db/NODENAME/
to the new location:
/var/lib/ejabberd/
so you will have the files like this:
/var/lib/ejabberd/acl.DCD ...
4. You can backup the content of those directories and delete them:
/var/lib/ejabberd/ebin
/var/lib/ejabberd/priv
/var/lib/ejabberd/db
5. Now try to start your new ejabberd 2.1.0.
Bug reports
You can officially report bugs on ProcessOne support site:
http://support.process-one.net/

View File

@ -1,2 +1,2 @@
% ejabberd version (automatically generated).
\newcommand{\version}{2.1.0}
\newcommand{\version}{3.0.0-alpha}

View File

@ -70,7 +70,7 @@ done
echo '7. compile ejabberd'
gmake
for A in mod_irc mod_muc mod_pubsub; do
for A in mod_muc mod_pubsub; do
(cd $A; gmake)
done

View File

@ -30,7 +30,7 @@ else
INIT_USER=$(INSTALLUSER)
endif
EFLAGS += @ERLANG_SSL39@ -pa .
EFLAGS += -pa .
# make debug=true to compile Erlang module with debug informations.
ifdef debug
@ -65,8 +65,8 @@ endif
prefix = @prefix@
exec_prefix = @exec_prefix@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@
ERLSHLIBS = expat_erl.so
SUBDIRS = stun @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ @tls@ @odbc@ @ejabberd_zlib@
ERLSHLIBS =
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
SOURCES_ALL = $(wildcard *.erl)
SOURCES = $(filter-out $(ERLBEHAVS),$(SOURCES_ALL))
@ -277,7 +277,6 @@ distclean: distclean-recursive clean-local
rm -f config.status
rm -f config.log
rm -f Makefile
[ ! -f ../ChangeLog ] || rm -f ../ChangeLog
TAGS:
etags *.erl

View File

@ -52,9 +52,6 @@ release : build release_clean
mkdir $(SRC_DIR)\eldap
copy eldap\eldap.* $(SRC_DIR)\eldap
copy eldap\ELDAPv3.asn $(SRC_DIR)\eldap
mkdir $(SRC_DIR)\mod_irc
copy mod_irc\*.erl $(SRC_DIR)\mod_irc
copy mod_irc\*.c $(SRC_DIR)\mod_irc
mkdir $(SRC_DIR)\mod_muc
copy mod_muc\*.erl $(SRC_DIR)\mod_muc
mkdir $(SRC_DIR)\mod_pubsub
@ -94,8 +91,6 @@ build : $(DLL) compile-beam all-recursive
all-recursive :
cd eldap
nmake -nologo -f Makefile.win32
cd ..\mod_irc
nmake -nologo -f Makefile.win32
cd ..\mod_muc
nmake -nologo -f Makefile.win32
cd ..\mod_pubsub
@ -137,8 +132,6 @@ clean-local :
clean-recursive :
cd eldap
nmake -nologo -f Makefile.win32 clean
cd ..\mod_irc
nmake -nologo -f Makefile.win32 clean
cd ..\mod_muc
nmake -nologo -f Makefile.win32 clean
cd ..\mod_pubsub

View File

@ -37,8 +37,38 @@
-include("ejabberd.hrl").
%% @type aclspec() = all | JID_Exact | JID_Regexp | JID_Glob | Shared_Group
%% JID_Exact = {user, U} | {user, U, S} | {server, S} | {resource, R}
%% U = string()
%% S = string()
%% R = string()
%% JID_Regexp = {user_regexp, UR} | {user_regexp, UR, S} | {server_regexp, SR} | {resource_regexp, RR} | {node_regexp, UR, SR}
%% UR = string()
%% SR = string()
%% RR = string()
%% JID_Glob = {user_glob, UG} | {user_glob, UG, S} | {server_glob, SG} | {resource_glob, RG} | {node_glob, UG, SG}
%% UG = string()
%% SG = string()
%% RG = string()
%% Shared_Group = {shared_group, G} | {shared_group, G, H}
%% G = string()
%% H = string().
%% @type acl() = {acl, ACLName, ACLSpec}
%% ACLName = atom()
%% ACLSpec = aclspec().
%% Record in its Ejabberd-configuration-file variant.
%% @type storedacl() = {acl, {ACLName, Host}, ACLSpec}
%% ACLName = atom()
%% Host = global | string()
%% ACLSpec = aclspec().
%% Record in its Mnesia-table-record variant.
-record(acl, {aclname, aclspec}).
%% @spec () -> ok
start() ->
mnesia:create_table(acl,
[{disc_copies, [node()]},
@ -47,9 +77,20 @@ start() ->
mnesia:add_table_copy(acl, node(), ram_copies),
ok.
%% @spec (Host, ACLName, ACLSpec) -> storedacl()
%% Host = global | string()
%% ACLName = atom()
%% ACLSpec = aclspec()
to_record(Host, ACLName, ACLSpec) ->
#acl{aclname = {ACLName, Host}, aclspec = normalize_spec(ACLSpec)}.
%% @spec (Host, ACLName, ACLSpec) -> {atomic, ok} | {aborted, Reason}
%% Host = global | string()
%% ACLName = atom()
%% ACLSpec = all | none | aclspec()
%% Reason = term()
add(Host, ACLName, ACLSpec) ->
F = fun() ->
mnesia:write(#acl{aclname = {ACLName, Host},
@ -57,6 +98,11 @@ add(Host, ACLName, ACLSpec) ->
end,
mnesia:transaction(F).
%% @spec (Host, ACLs, Clear) -> ok | false
%% Host = global | string()
%% ACLs = [acl()]
%% Clear = bool()
add_list(Host, ACLs, Clear) ->
F = fun() ->
if
@ -86,8 +132,17 @@ add_list(Host, ACLs, Clear) ->
false
end.
normalize(A) ->
jlib:nodeprep(A).
%% @spec (String) -> Prepd_String
%% String = string()
%% Prepd_String = string()
normalize(String) ->
exmpp_stringprep:nodeprep(String).
%% @spec (ACLSpec) -> Normalized_ACLSpec
%% ACLSpec = all | none | aclspec()
%% Normalized_ACLSpec = aclspec()
normalize_spec({A, B}) ->
{A, normalize(B)};
normalize_spec({A, B, C}) ->
@ -99,6 +154,12 @@ normalize_spec(none) ->
%% @spec (Host, Rule, JID) -> Access
%% Host = global | string()
%% Rule = all | none | atom()
%% JID = exmpp_jid:jid()
%% Access = allow | deny | atom()
match_rule(global, Rule, JID) ->
case Rule of
all -> allow;
@ -143,22 +204,36 @@ match_rule(Host, Rule, JID) ->
end
end.
%% @spec (ACLs, JID, Host) -> Access
%% ACLs = [{Access, ACLName}]
%% Access = deny | atom()
%% ACLName = atom()
%% JID = exmpp_jid:jid()
%% Host = string()
match_acls([], _, _Host) ->
deny;
match_acls([{Access, ACL} | ACLs], JID, Host) ->
case match_acl(ACL, JID, Host) of
match_acls([{Access, ACLName} | ACLs], JID, Host) ->
case match_acl(ACLName, JID, Host) of
true ->
Access;
_ ->
match_acls(ACLs, JID, Host)
end.
match_acl(ACL, JID, Host) ->
case ACL of
%% @spec (ACLName, JID, Host) -> bool()
%% ACLName = all | none | atom()
%% JID = exmpp_jid:jid()
%% Host = string()
match_acl(ACLName, JID, Host) ->
case ACLName of
all -> true;
none -> false;
_ ->
{User, Server, Resource} = jlib:jid_tolower(JID),
User = exmpp_jid:prep_node_as_list(JID),
Server = exmpp_jid:prep_domain_as_list(JID),
Resource = exmpp_jid:prep_resource_as_list(JID),
lists:any(fun(#acl{aclspec = Spec}) ->
case Spec of
all ->
@ -218,24 +293,35 @@ match_acl(ACL, JID, Host) ->
false
end
end,
ets:lookup(acl, {ACL, global}) ++
ets:lookup(acl, {ACL, Host}))
ets:lookup(acl, {ACLName, global}) ++
ets:lookup(acl, {ACLName, Host}))
end.
%% @spec (String, RegExp) -> bool()
%% String = string() | undefined
%% RegExp = string()
is_regexp_match(undefined, _RegExp) ->
false;
is_regexp_match(String, RegExp) ->
case regexp:first_match(String, RegExp) of
try re:run(String, RegExp, [{capture, none}]) of
nomatch ->
false;
{match, _, _} ->
true;
{error, ErrDesc} ->
match ->
true
catch
_:ErrDesc ->
?ERROR_MSG(
"Wrong regexp ~p in ACL: ~p",
[RegExp, lists:flatten(regexp:format_error(ErrDesc))]),
"Wrong regexp ~p in ACL:~n~p",
[RegExp, ErrDesc]),
false
end.
%% @spec (String, Glob) -> bool()
%% String = string() | undefined
%% Glob = string()
is_glob_match(String, Glob) ->
is_regexp_match(String, regexp:sh_to_awk(Glob)).
is_regexp_match(String, xmerl_regexp:sh_to_awk(Glob)).

109
src/aclocal.m4 vendored
View File

@ -121,21 +121,20 @@ AC_DEFUN(AM_WITH_ERLANG,
-author('alexey@sevcom.net').
-export([[start/0]]).
-include_lib("ssl/include/ssl_pkix.hrl").
start() ->
EIDirS = code:lib_dir("erl_interface") ++ "\n",
EILibS = libpath("erl_interface") ++ "\n",
EXMPPDir = code:lib_dir("exmpp"),
case EXMPPDir of
{error, bad_name} -> exit("exmpp not found");
_ -> ok
end,
EXMPPDirS = EXMPPDir ++ "\n",
RootDirS = code:root_dir() ++ "\n",
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ ssldef() ++ RootDirS)),
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ EXMPPDirS ++ RootDirS)),
halt().
-[ifdef]('id-pkix').
ssldef() -> "-DSSL39\n".
-else.
ssldef() -> "\n".
-endif.
%% return physical architecture based on OS/Processor
archname() ->
ArchStr = erlang:system_info(system_architecture),
@ -184,7 +183,7 @@ _EOF
# Second line
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
# Third line
ERLANG_SSL39=`cat conftest.out | head -n 3 | tail -n 1`
ERLANG_EXMPP=`cat conftest.out | head -n 3 | tail -n 1`
# End line
ERLANG_DIR=`cat conftest.out | tail -n 1`
@ -193,7 +192,7 @@ _EOF
AC_SUBST(ERLANG_CFLAGS)
AC_SUBST(ERLANG_LIBS)
AC_SUBST(ERLANG_SSL39)
AC_SUBST(ERLANG_EXMPP)
AC_SUBST(ERLC)
AC_SUBST(ERL)
])
@ -217,96 +216,6 @@ AC_SUBST(make_$1)
])
dnl From Bruno Haible.
AC_DEFUN([AM_ICONV],
[
dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
dnl those with the standalone portable GNU libiconv installed).
AC_ARG_WITH([libiconv-prefix],
[AC_HELP_STRING([--with-libiconv-prefix=PREFIX], [prefix where libiconv is installed])], [
for dir in `echo "$withval" | tr : ' '`; do
if test -d $dir/include; then CPPFLAGS="$CPPFLAGS -I$dir/include"; fi
if test -d $dir/include; then CFLAGS="$CFLAGS -I$dir/include"; fi
if test -d $dir/lib; then LDFLAGS="$LDFLAGS -L$dir/lib"; fi
done
])
AC_CACHE_CHECK(for iconv, am_cv_func_iconv, [
am_cv_func_iconv="no, consider installing GNU libiconv"
am_cv_lib_iconv=no
AC_TRY_LINK([#include <stdlib.h>
#include <iconv.h>],
[iconv_t cd = iconv_open("","");
iconv(cd,NULL,NULL,NULL,NULL);
iconv_close(cd);],
am_cv_func_iconv=yes)
if test "$am_cv_func_iconv" != yes; then
am_save_LIBS="$LIBS"
LIBS="$LIBS -liconv"
AC_TRY_LINK([#include <stdlib.h>
#include <iconv.h>],
[iconv_t cd = iconv_open("","");
iconv(cd,NULL,NULL,NULL,NULL);
iconv_close(cd);],
am_cv_lib_iconv=yes
am_cv_func_iconv=yes)
LIBS="$am_save_LIBS"
fi
dnl trying /usr/local
if test "$am_cv_func_iconv" != yes; then
am_save_LIBS="$LIBS"
am_save_CFLAGS="$CFLAGS"
am_save_LDFLAGS="$LDFLAGS"
LIBS="$LIBS -liconv"
LDFLAGS="$LDFLAGS -L/usr/local/lib"
CFLAGS="$CFLAGS -I/usr/local/include"
AC_TRY_LINK([#include <stdlib.h>
#include <iconv.h>],
[iconv_t cd = iconv_open("","");
iconv(cd,NULL,NULL,NULL,NULL);
iconv_close(cd);],
am_cv_lib_iconv=yes
am_cv_func_iconv=yes
CPPFLAGS="$CPPFLAGS -I/usr/local/include",
LDFLAGS="$am_save_LDFLAGS"
CFLAGS="$am_save_CFLAGS")
LIBS="$am_save_LIBS"
fi
])
if test "$am_cv_func_iconv" = yes; then
AC_DEFINE(HAVE_ICONV, 1, [Define if you have the iconv() function.])
AC_MSG_CHECKING([for iconv declaration])
AC_CACHE_VAL(am_cv_proto_iconv, [
AC_TRY_COMPILE([
#include <stdlib.h>
#include <iconv.h>
extern
#ifdef __cplusplus
"C"
#endif
#if defined(__STDC__) || defined(__cplusplus)
size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
#else
size_t iconv();
#endif
], [], am_cv_proto_iconv_arg1="", am_cv_proto_iconv_arg1="const")
am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"])
am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'`
AC_MSG_RESULT([$]{ac_t:-
}[$]am_cv_proto_iconv)
AC_DEFINE_UNQUOTED(ICONV_CONST, $am_cv_proto_iconv_arg1,
[Define as const if the declaration of iconv() needs const.])
fi
LIBICONV=
if test "$am_cv_lib_iconv" = yes; then
LIBICONV="-liconv"
fi
AC_SUBST(LIBICONV)
])
dnl <openssl>
AC_DEFUN(AM_WITH_OPENSSL,
[ AC_ARG_WITH(openssl,

View File

@ -31,47 +31,51 @@
produce_response/2,
produce_response/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
%% Parse an ad-hoc request. Return either an adhoc_request record or
%% an {error, ErrorType} tuple.
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
?DEBUG("entering parse_request...", []),
Node = xml:get_tag_attr_s("node", SubEl),
SessionID = xml:get_tag_attr_s("sessionid", SubEl),
Action = xml:get_tag_attr_s("action", SubEl),
XData = find_xdata_el(SubEl),
{xmlelement, _, _, AllEls} = SubEl,
if XData ->
Others = lists:delete(XData, AllEls);
true ->
Others = AllEls
end,
parse_request(#iq{type = Type, ns = NS, payload = SubEl, lang = Lang}) ->
try
case {Type, NS} of
{set, ?NS_ADHOC} ->
?DEBUG("entering parse_request...", []),
Node = exmpp_xml:get_attribute_as_list(SubEl, 'node', ""),
SessionID = exmpp_xml:get_attribute_as_list(SubEl, 'sessionid', ""),
Action = exmpp_xml:get_attribute_as_list(SubEl, 'action', ""),
XData = find_xdata_el(SubEl),
AllEls = exmpp_xml:get_child_elements(SubEl),
if XData ->
Others = lists:delete(XData, AllEls);
true ->
Others = AllEls
end,
#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID,
action = Action,
xdata = XData,
others = Others};
parse_request(_) ->
{error, ?ERR_BAD_REQUEST}.
#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID,
action = Action,
xdata = XData,
others = Others};
_ ->
{error, 'bad-request'}
end
catch
_ ->
{error, 'bad-request'}
end.
%% Borrowed from mod_vcard.erl
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
find_xdata_el(#xmlel{children = SubEls}) ->
find_xdata_el1(SubEls).
find_xdata_el1([]) ->
false;
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_XDATA ->
{xmlelement, Name, Attrs, SubEls};
_ ->
find_xdata_el1(Els)
end;
find_xdata_el1([#xmlel{ns = ?NS_DATA_FORMS} = El | _Els]) ->
El;
find_xdata_el1([_ | Els]) ->
find_xdata_el1(Els).
@ -109,20 +113,19 @@ produce_response(#adhoc_response{lang = _Lang,
"" ->
ActionsElAttrs = [];
_ ->
ActionsElAttrs = [{"execute", DefaultAction}]
ActionsElAttrs = [?XMLATTR('execute', DefaultAction)]
end,
ActionsEls = [{xmlelement, "actions",
ActionsElAttrs,
[{xmlelement, Action, [], []} || Action <- Actions]}]
ActionsEls = [#xmlel{ns = ?NS_ADHOC, name = 'actions', attrs =
ActionsElAttrs, children =
[#xmlel{ns = ?NS_ADHOC, name = Action} || Action <- Actions]}]
end,
NotesEls = lists:map(fun({Type, Text}) ->
{xmlelement, "note",
[{"type", Type}],
[{xmlcdata, Text}]}
#xmlel{ns = ?NS_ADHOC, name = 'note', attrs =
[?XMLATTR('type', Type)],
children = [#xmlcdata{cdata = list_to_binary(Text)}]}
end, Notes),
{xmlelement, "command",
[{"xmlns", ?NS_COMMANDS},
{"sessionid", SessionID},
{"node", Node},
{"status", atom_to_list(Status)}],
#xmlel{ns = ?NS_ADHOC, name = 'command', attrs =
[?XMLATTR('sessionid', SessionID),
?XMLATTR('node', Node),
?XMLATTR('status', Status)], children =
ActionsEls ++ NotesEls ++ Elements}.

619
src/configure vendored
View File

@ -554,6 +554,7 @@ PACKAGE_STRING='ejabberd.erl version'
PACKAGE_BUGREPORT='ejabberd@process-one.net'
PACKAGE_URL=''
ac_default_prefix=/
# Factoring default headers for most tests.
ac_includes_default="\
#include <stdio.h>
@ -590,7 +591,6 @@ ac_includes_default="\
# include <unistd.h>
#endif"
ac_default_prefix=/
ac_subst_vars='LTLIBOBJS
ERLCFLAGS
target_os
@ -635,16 +635,11 @@ make_mod_proxy65
mod_proxy65
make_mod_muc
mod_muc
make_mod_irc
mod_irc
LIBOBJS
EXPAT_LIBS
EXPAT_CFLAGS
EGREP
GREP
CPP
LIBICONV
ERLANG_SSL39
ERLANG_EXMPP
ERLANG_LIBS
ERLANG_CFLAGS
ERL
@ -699,9 +694,6 @@ ac_subst_files=''
ac_user_opts='
enable_option_checking
with_erlang
with_libiconv_prefix
with_expat
enable_mod_irc
enable_mod_muc
enable_mod_proxy65
enable_mod_pubsub
@ -1347,7 +1339,6 @@ Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-mod_irc enable mod_irc (default: yes)
--enable-mod_muc enable mod_muc (default: yes)
--enable-mod_proxy65 enable mod_proxy65 (default: yes)
--enable-mod_pubsub enable mod_pubsub (default: yes)
@ -1377,9 +1368,6 @@ Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-erlang=PREFIX path to erlc and erl
--with-libiconv-prefix=PREFIX
prefix where libiconv is installed
--with-expat=PREFIX prefix where EXPAT is installed
--with-zlib=PREFIX prefix where zlib is installed
--with-pam=PREFIX prefix where PAM is installed
--with-openssl=PREFIX prefix where OPENSSL is installed
@ -1514,52 +1502,6 @@ fi
} # ac_fn_c_try_compile
# ac_fn_c_try_link LINENO
# -----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_link ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
rm -f conftest.$ac_objext conftest$ac_exeext
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext && {
test "$cross_compiling" = yes ||
$as_test_x conftest$ac_exeext
}; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
# Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
# created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
# interfere with the next link command; also delete a directory that is
# left behind by Apple's compiler. We do this before executing the actions.
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
return $ac_retval
} # ac_fn_c_try_link
# ac_fn_c_try_cpp LINENO
# ----------------------
# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
@ -1597,6 +1539,48 @@ fi
} # ac_fn_c_try_cpp
# ac_fn_c_try_run LINENO
# ----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
# that executables *can* be run.
ac_fn_c_try_run ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
{ { case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_try") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; }; then :
ac_retval=0
else
$as_echo "$as_me: program exited with status $ac_status" >&5
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=$ac_status
fi
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
return $ac_retval
} # ac_fn_c_try_run
# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists, giving a warning if it cannot be compiled using
@ -1690,48 +1674,6 @@ fi
} # ac_fn_c_check_header_mongrel
# ac_fn_c_try_run LINENO
# ----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
# that executables *can* be run.
ac_fn_c_try_run ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
{ { case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_try") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; }; then :
ac_retval=0
else
$as_echo "$as_me: program exited with status $ac_status" >&5
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=$ac_status
fi
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
return $ac_retval
} # ac_fn_c_try_run
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists and can be compiled using the include files in
@ -1763,6 +1705,52 @@ $as_echo "$ac_res" >&6; }
} # ac_fn_c_check_header_compile
# ac_fn_c_try_link LINENO
# -----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_link ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
rm -f conftest.$ac_objext conftest$ac_exeext
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext && {
test "$cross_compiling" = yes ||
$as_test_x conftest$ac_exeext
}; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
# Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
# created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
# interfere with the next link command; also delete a directory that is
# left behind by Apple's compiler. We do this before executing the actions.
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;}
return $ac_retval
} # ac_fn_c_try_link
# ac_fn_erl_try_run LINENO
# ------------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
@ -3180,21 +3168,20 @@ fi
-author('alexey@sevcom.net').
-export([start/0]).
-include_lib("ssl/include/ssl_pkix.hrl").
start() ->
EIDirS = code:lib_dir("erl_interface") ++ "\n",
EILibS = libpath("erl_interface") ++ "\n",
EXMPPDir = code:lib_dir("exmpp"),
case EXMPPDir of
{error, bad_name} -> exit("exmpp not found");
_ -> ok
end,
EXMPPDirS = EXMPPDir ++ "\n",
RootDirS = code:root_dir() ++ "\n",
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ ssldef() ++ RootDirS)),
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ EXMPPDirS ++ RootDirS)),
halt().
-ifdef('id-pkix').
ssldef() -> "-DSSL39\n".
-else.
ssldef() -> "\n".
-endif.
%% return physical architecture based on OS/Processor
archname() ->
ArchStr = erlang:system_info(system_architecture),
@ -3243,7 +3230,7 @@ _EOF
# Second line
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
# Third line
ERLANG_SSL39=`cat conftest.out | head -n 3 | tail -n 1`
ERLANG_EXMPP=`cat conftest.out | head -n 3 | tail -n 1`
# End line
ERLANG_DIR=`cat conftest.out | tail -n 1`
@ -3256,172 +3243,97 @@ _EOF
#locating iconv
# Checks for typedefs, structures, and compiler characteristics.
# Check whether --with-libiconv-prefix was given.
if test "${with_libiconv_prefix+set}" = set; then :
withval=$with_libiconv_prefix;
for dir in `echo "$withval" | tr : ' '`; do
if test -d $dir/include; then CPPFLAGS="$CPPFLAGS -I$dir/include"; fi
if test -d $dir/include; then CFLAGS="$CFLAGS -I$dir/include"; fi
if test -d $dir/lib; then LDFLAGS="$LDFLAGS -L$dir/lib"; fi
done
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for iconv" >&5
$as_echo_n "checking for iconv... " >&6; }
if test "${am_cv_func_iconv+set}" = set; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5
$as_echo_n "checking for an ANSI C-conforming const... " >&6; }
if test "${ac_cv_c_const+set}" = set; then :
$as_echo_n "(cached) " >&6
else
am_cv_func_iconv="no, consider installing GNU libiconv"
am_cv_lib_iconv=no
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <stdlib.h>
#include <iconv.h>
int
main ()
{
iconv_t cd = iconv_open("","");
iconv(cd,NULL,NULL,NULL,NULL);
iconv_close(cd);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
am_cv_func_iconv=yes
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
if test "$am_cv_func_iconv" != yes; then
am_save_LIBS="$LIBS"
LIBS="$LIBS -liconv"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <stdlib.h>
#include <iconv.h>
int
main ()
{
iconv_t cd = iconv_open("","");
iconv(cd,NULL,NULL,NULL,NULL);
iconv_close(cd);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
am_cv_lib_iconv=yes
am_cv_func_iconv=yes
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS="$am_save_LIBS"
fi
if test "$am_cv_func_iconv" != yes; then
am_save_LIBS="$LIBS"
am_save_CFLAGS="$CFLAGS"
am_save_LDFLAGS="$LDFLAGS"
LIBS="$LIBS -liconv"
LDFLAGS="$LDFLAGS -L/usr/local/lib"
CFLAGS="$CFLAGS -I/usr/local/include"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <stdlib.h>
#include <iconv.h>
int
main ()
{
iconv_t cd = iconv_open("","");
iconv(cd,NULL,NULL,NULL,NULL);
iconv_close(cd);
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
am_cv_lib_iconv=yes
am_cv_func_iconv=yes
CPPFLAGS="$CPPFLAGS -I/usr/local/include"
else
LDFLAGS="$am_save_LDFLAGS"
CFLAGS="$am_save_CFLAGS"
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS="$am_save_LIBS"
fi
/* FIXME: Include the comments suggested by Paul. */
#ifndef __cplusplus
/* Ultrix mips cc rejects this. */
typedef int charset[2];
const charset cs;
/* SunOS 4.1.1 cc rejects this. */
char const *const *pcpcc;
char **ppc;
/* NEC SVR4.0.2 mips cc rejects this. */
struct point {int x, y;};
static struct point const zero = {0,0};
/* AIX XL C 1.02.0.0 rejects this.
It does not let you subtract one const X* pointer from another in
an arm of an if-expression whose if-part is not a constant
expression */
const char *g = "string";
pcpcc = &g + (g ? g-g : 0);
/* HPUX 7.0 cc rejects these. */
++pcpcc;
ppc = (char**) pcpcc;
pcpcc = (char const *const *) ppc;
{ /* SCO 3.2v4 cc rejects this. */
char *t;
char const *s = 0 ? (char *) 0 : (char const *) 0;
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_func_iconv" >&5
$as_echo "$am_cv_func_iconv" >&6; }
if test "$am_cv_func_iconv" = yes; then
$as_echo "#define HAVE_ICONV 1" >>confdefs.h
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for iconv declaration" >&5
$as_echo_n "checking for iconv declaration... " >&6; }
if test "${am_cv_proto_iconv+set}" = set; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <stdlib.h>
#include <iconv.h>
extern
#ifdef __cplusplus
"C"
*t++ = 0;
if (s) return 0;
}
{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */
int x[] = {25, 17};
const int *foo = &x[0];
++foo;
}
{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */
typedef const int *iptr;
iptr p = 0;
++p;
}
{ /* AIX XL C 1.02.0.0 rejects this saying
"k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */
struct s { int j; const int *ap[3]; };
struct s *b; b->j = 5;
}
{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */
const int foo = 10;
if (!foo) return 0;
}
return !cs[0] && !zero.x;
#endif
#if defined(__STDC__) || defined(__cplusplus)
size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);
#else
size_t iconv();
#endif
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
am_cv_proto_iconv_arg1=""
ac_cv_c_const=yes
else
am_cv_proto_iconv_arg1="const"
ac_cv_c_const=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5
$as_echo "$ac_cv_c_const" >&6; }
if test $ac_cv_c_const = no; then
$as_echo "#define const /**/" >>confdefs.h
fi
am_cv_proto_iconv=`echo "$am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'`
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_t:-
}$am_cv_proto_iconv" >&5
$as_echo "${ac_t:-
}$am_cv_proto_iconv" >&6; }
cat >>confdefs.h <<_ACEOF
#define ICONV_CONST $am_cv_proto_iconv_arg1
_ACEOF
# Check Erlang headers are installed
#AC_CHECK_HEADER(erl_driver.h,,[AC_MSG_ERROR([cannot find Erlang header files])])
fi
LIBICONV=
if test "$am_cv_lib_iconv" = yes; then
LIBICONV="-liconv"
fi
# Change default prefix
#locating libexpat
# Checks for library functions.
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@ -3820,183 +3732,6 @@ fi
done
# Check whether --with-expat was given.
if test "${with_expat+set}" = set; then :
withval=$with_expat;
fi
EXPAT_CFLAGS=
EXPAT_LIBS=
if test x"$with_expat" != x; then
EXPAT_CFLAGS="-I$with_expat/include"
EXPAT_LIBS="-L$with_expat/lib"
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for XML_ParserCreate in -lexpat" >&5
$as_echo_n "checking for XML_ParserCreate in -lexpat... " >&6; }
if test "${ac_cv_lib_expat_XML_ParserCreate+set}" = set; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lexpat "$EXPAT_LIBS" $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char XML_ParserCreate ();
int
main ()
{
return XML_ParserCreate ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_expat_XML_ParserCreate=yes
else
ac_cv_lib_expat_XML_ParserCreate=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_expat_XML_ParserCreate" >&5
$as_echo "$ac_cv_lib_expat_XML_ParserCreate" >&6; }
if test "x$ac_cv_lib_expat_XML_ParserCreate" = x""yes; then :
EXPAT_LIBS="$EXPAT_LIBS -lexpat"
expat_found=yes
else
expat_found=no
fi
if test $expat_found = no; then
as_fn_error "Could not find development files of Expat library" "$LINENO" 5
fi
expat_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
expat_save_CPPFLAGS="$CPPFLAGS"
CPPFLAGS="$CPPFLAGS $EXPAT_CFLAGS"
for ac_header in expat.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "expat.h" "ac_cv_header_expat_h" "$ac_includes_default"
if test "x$ac_cv_header_expat_h" = x""yes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_EXPAT_H 1
_ACEOF
else
expat_found=no
fi
done
if test $expat_found = no; then
as_fn_error "Could not find expat.h" "$LINENO" 5
fi
CFLAGS="$expat_save_CFLAGS"
CPPFLAGS="$expat_save_CPPFLAGS"
# Checks for typedefs, structures, and compiler characteristics.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5
$as_echo_n "checking for an ANSI C-conforming const... " >&6; }
if test "${ac_cv_c_const+set}" = set; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
/* FIXME: Include the comments suggested by Paul. */
#ifndef __cplusplus
/* Ultrix mips cc rejects this. */
typedef int charset[2];
const charset cs;
/* SunOS 4.1.1 cc rejects this. */
char const *const *pcpcc;
char **ppc;
/* NEC SVR4.0.2 mips cc rejects this. */
struct point {int x, y;};
static struct point const zero = {0,0};
/* AIX XL C 1.02.0.0 rejects this.
It does not let you subtract one const X* pointer from another in
an arm of an if-expression whose if-part is not a constant
expression */
const char *g = "string";
pcpcc = &g + (g ? g-g : 0);
/* HPUX 7.0 cc rejects these. */
++pcpcc;
ppc = (char**) pcpcc;
pcpcc = (char const *const *) ppc;
{ /* SCO 3.2v4 cc rejects this. */
char *t;
char const *s = 0 ? (char *) 0 : (char const *) 0;
*t++ = 0;
if (s) return 0;
}
{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */
int x[] = {25, 17};
const int *foo = &x[0];
++foo;
}
{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */
typedef const int *iptr;
iptr p = 0;
++p;
}
{ /* AIX XL C 1.02.0.0 rejects this saying
"k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */
struct s { int j; const int *ap[3]; };
struct s *b; b->j = 5;
}
{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */
const int foo = 10;
if (!foo) return 0;
}
return !cs[0] && !zero.x;
#endif
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_c_const=yes
else
ac_cv_c_const=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5
$as_echo "$ac_cv_c_const" >&6; }
if test $ac_cv_c_const = no; then
$as_echo "#define const /**/" >>confdefs.h
fi
# Check Erlang headers are installed
#AC_CHECK_HEADER(erl_driver.h,,[AC_MSG_ERROR([cannot find Erlang header files])])
# Change default prefix
# Checks for library functions.
for ac_header in stdlib.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default"
@ -4178,28 +3913,6 @@ fi
mod_irc=
make_mod_irc=
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build mod_irc" >&5
$as_echo_n "checking whether build mod_irc... " >&6; }
# Check whether --enable-mod_irc was given.
if test "${enable_mod_irc+set}" = set; then :
enableval=$enable_mod_irc; mr_enable_mod_irc="$enableval"
else
mr_enable_mod_irc=yes
fi
if test "$mr_enable_mod_irc" = "yes"; then
mod_irc=mod_irc
make_mod_irc=mod_irc/Makefile
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $mr_enable_mod_irc" >&5
$as_echo "$mr_enable_mod_irc" >&6; }
mod_muc=
make_mod_muc=
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build mod_muc" >&5
@ -4644,7 +4357,7 @@ fi
ac_config_files="$ac_config_files Makefile $make_mod_irc $make_mod_muc $make_mod_pubsub $make_mod_proxy65 $make_eldap $make_pam $make_web stringprep/Makefile stun/Makefile $make_tls $make_odbc $make_ejabberd_zlib"
ac_config_files="$ac_config_files Makefile $make_mod_muc $make_mod_pubsub $make_mod_proxy65 $make_eldap $make_pam $make_web stun/Makefile $make_tls $make_odbc $make_ejabberd_zlib"
#openssl
@ -5763,14 +5476,12 @@ for ac_config_target in $ac_config_targets
do
case $ac_config_target in
"Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
"$make_mod_irc") CONFIG_FILES="$CONFIG_FILES $make_mod_irc" ;;
"$make_mod_muc") CONFIG_FILES="$CONFIG_FILES $make_mod_muc" ;;
"$make_mod_pubsub") CONFIG_FILES="$CONFIG_FILES $make_mod_pubsub" ;;
"$make_mod_proxy65") CONFIG_FILES="$CONFIG_FILES $make_mod_proxy65" ;;
"$make_eldap") CONFIG_FILES="$CONFIG_FILES $make_eldap" ;;
"$make_pam") CONFIG_FILES="$CONFIG_FILES $make_pam" ;;
"$make_web") CONFIG_FILES="$CONFIG_FILES $make_web" ;;
"stringprep/Makefile") CONFIG_FILES="$CONFIG_FILES stringprep/Makefile" ;;
"stun/Makefile") CONFIG_FILES="$CONFIG_FILES stun/Makefile" ;;
"$make_tls") CONFIG_FILES="$CONFIG_FILES $make_tls" ;;
"$make_odbc") CONFIG_FILES="$CONFIG_FILES $make_odbc" ;;

View File

@ -14,10 +14,6 @@ fi
#locating erlang
AM_WITH_ERLANG
#locating iconv
AM_ICONV
#locating libexpat
AM_WITH_EXPAT
# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
@ -32,7 +28,6 @@ AC_PREFIX_DEFAULT(/)
AC_FUNC_MALLOC
AC_HEADER_STDC
AC_MOD_ENABLE(mod_irc, yes)
AC_MOD_ENABLE(mod_muc, yes)
AC_MOD_ENABLE(mod_proxy65, yes)
AC_MOD_ENABLE(mod_pubsub, yes)
@ -95,14 +90,12 @@ esac],[full_xml=false])
AC_SUBST(full_xml)
AC_CONFIG_FILES([Makefile
$make_mod_irc
$make_mod_muc
$make_mod_pubsub
$make_mod_proxy65
$make_eldap
$make_pam
$make_web
stringprep/Makefile
stun/Makefile
$make_tls
$make_odbc

View File

@ -44,15 +44,11 @@ start() ->
true ->
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\StaticLibs\\libexpatMT.lib\n",
ExpatFlag = "EXPAT_FLAG = -DXML_STATIC\n",
IconvDir = "ICONV_DIR = c:\\sdk\\GnuWin32\n",
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\libiconv.lib\n",
ZlibDir = "ZLIB_DIR = c:\\sdk\\GnuWin32\n",
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib.lib\n";
false ->
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\Libs\\libexpat.lib\n",
ExpatFlag = "",
IconvDir = "ICONV_DIR = c:\\sdk\\GnuWin32\n",
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\libiconv.lib\n",
ZlibDir = "ZLIB_DIR = c:\\sdk\\GnuWin32\n",
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib.lib\n"
end,
@ -82,8 +78,6 @@ start() ->
ExpatDir ++
ExpatLib ++
ExpatFlag ++
IconvDir ++
IconvLib ++
ZlibDir ++
ZlibLib)),
halt().

View File

@ -34,18 +34,40 @@
server_start/3,
server_step/2]).
%% @type saslmechanism() = {sasl_mechanism, Mechanism, Module, Require_Plain}
%% Mechanism = string()
%% Module = atom()
%% Require_Plain = bool().
%% Registry entry of a supported SASL mechanism.
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
%% @type saslstate() = {sasl_state, Service, Myname, Realm, GetPassword, CheckPassword, CheckPasswordDigest, Mech_Mod, Mech_State}
%% Service = string()
%% Myname = string()
%% Realm = string()
%% GetPassword = function()
%% CheckPassword = function()
%% CheckPasswordDigest = any()
%% Mech_Mod = atom()
%% Mech_State = term().
%% State of this process.
-record(sasl_state, {service, myname, realm,
get_password, check_password, check_password_digest,
mech_mod, mech_state}).
-export([behaviour_info/1]).
%% @hidden
behaviour_info(callbacks) ->
[{mech_new, 4}, {mech_step, 2}];
behaviour_info(_Other) ->
undefined.
%% @spec () -> ok
start() ->
ets:new(sasl_mechanism, [named_table,
public,
@ -55,47 +77,62 @@ start() ->
cyrsasl_anonymous:start([]),
ok.
%% @spec (Mechanism, Module, Require_Plain) -> true
%% Mechanism = string()
%% Module = atom()
%% Require_Plain = bool()
register_mechanism(Mechanism, Module, RequirePlainPassword) ->
ets:insert(sasl_mechanism,
#sasl_mechanism{mechanism = Mechanism,
module = Module,
require_plain_password = RequirePlainPassword}).
%%% TODO: use callbacks
%%-include("ejabberd.hrl").
%%-include("jlib.hrl").
%%check_authzid(_State, Props) ->
%% AuthzId = xml:get_attr_s(authzid, Props),
%% case jlib:string_to_jid(AuthzId) of
%% error ->
%% {error, "invalid-authzid"};
%% JID ->
%% LUser = jlib:nodeprep(xml:get_attr_s(username, Props)),
%% {U, S, R} = jlib:jid_tolower(JID),
%% case R of
%% "" ->
%% {error, "invalid-authzid"};
%% _ ->
%% case {LUser, ?MYNAME} of
%% {U, S} ->
%% ok;
%% _ ->
%% {error, "invalid-authzid"}
%% end
%% end
%% end.
% TODO use callbacks
%-include("ejabberd.hrl").
%-include("jlib.hrl").
%check_authzid(_State, Props) ->
% AuthzId = xml:get_attr_s(authzid, Props),
% case jlib:string_to_jid(AuthzId) of
% error ->
% {error, "invalid-authzid"};
% JID ->
% LUser = jlib:nodeprep(xml:get_attr_s(username, Props)),
% {U, S, R} = jlib:jid_tolower(JID),
% case R of
% "" ->
% {error, "invalid-authzid"};
% _ ->
% case {LUser, ?MYNAME} of
% {U, S} ->
% ok;
% _ ->
% {error, "invalid-authzid"}
% end
% end
% end.
%% @spec (State, Props) -> ok | {error, 'not-authorized'}
%% State = saslstate()
%% Props = [{Key, Value}]
%% Key = atom()
%% Value = string()
check_credentials(_State, Props) ->
User = xml:get_attr_s(username, Props),
case jlib:nodeprep(User) of
error ->
{error, "not-authorized"};
"" ->
{error, "not-authorized"};
_LUser ->
ok
case proplists:get_value(username, Props) of
undefined ->
{error, 'not-authorized'};
User ->
case exmpp_stringprep:is_node(User) of
false -> {error, 'not-authorized'};
true -> ok
end
end.
%% @spec (Host) -> [Mechanism]
%% Host = string()
%% Mechanism = string()
listmech(Host) ->
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
@ -112,6 +149,14 @@ listmech(Host) ->
['$1']}]),
filter_anonymous(Host, Mechs).
%% @spec (Service, ServerFQDN, UserRealm, SecFlags, GetPassword, CheckPassword, CheckPasswordDigest) -> saslstate()
%% Service = string()
%% ServerFQDN = string()
%% UserRealm = string()
%% SecFlags = [term()]
%% GetPassword = function()
%% CheckPassword = function()
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
GetPassword, CheckPassword, CheckPasswordDigest) ->
#sasl_state{service = Service,
@ -121,6 +166,22 @@ server_new(Service, ServerFQDN, UserRealm, _SecFlags,
check_password = CheckPassword,
check_password_digest= CheckPasswordDigest}.
%% @spec (State, Mech, ClientIn) -> Ok | Continue | Error
%% State = saslstate()
%% Mech = string()
%% ClientIn = string()
%% Ok = {ok, Props}
%% Props = [Prop]
%% Prop = [{Key, Value}]
%% Key = atom()
%% Value = string()
%% Continue = {continue, ServerOut, New_State}
%% ServerOut = string()
%% New_State = saslstate()
%% Error = {error, Reason} | {error, Username, Reason}
%% Reason = term()
%% Username = string()
server_start(State, Mech, ClientIn) ->
case lists:member(Mech, listmech(State#sasl_state.myname)) of
true ->
@ -135,12 +196,27 @@ server_start(State, Mech, ClientIn) ->
mech_state = MechState},
ClientIn);
_ ->
{error, "no-mechanism"}
{error, 'invalid-mechanism'}
end;
false ->
{error, "no-mechanism"}
{error, 'invalid-mechanism'}
end.
%% @spec (State, ClientIn) -> Ok | Continue | Error
%% State = saslstate()
%% ClientIn = string()
%% Ok = {ok, Props}
%% Props = [Prop]
%% Prop = [{Key, Value}]
%% Key = atom()
%% Value = string()
%% Continue = {continue, ServerOut, New_State}
%% ServerOut = string()
%% New_State = saslstate()
%% Error = {error, Reason} | {error, Username, Reason}
%% Reason = term()
%% Username = string()
server_step(State, ClientIn) ->
Module = State#sasl_state.mech_mod,
MechState = State#sasl_state.mech_state,
@ -161,8 +237,15 @@ server_step(State, ClientIn) ->
{error, Error}
end.
%% Remove the anonymous mechanism from the list if not enabled for the given
%% host
%% @spec (Host, Mechs) -> [Filtered_Mechs]
%% Host = string()
%% Mechs = [Mech]
%% Mech = string()
%% Filtered_Mechs = [Mech]
%%
%% @doc Remove the anonymous mechanism from the list if not enabled for
%% the given host.
filter_anonymous(Host, Mechs) ->
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
true -> Mechs;

View File

@ -31,18 +31,42 @@
-behaviour(cyrsasl).
%% @type mechstate() = {state, Server}
%% Server = string().
-record(state, {server}).
%% @spec (Opts) -> true
%% Opts = term()
start(_Opts) ->
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false),
ok.
%% @spec () -> ok
stop() ->
ok.
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
%% Host = string()
%% GetPassword = function()
%% CheckPassword = function()
%% State = mechstate()
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
{ok, #state{server = Host}}.
%% @spec (State, ClientIn) -> Ok | Error
%% State = mechstate()
%% ClientIn = string()
%% Ok = {ok, Props}
%% Props = [Prop]
%% Prop = {username, Username} | {auth_module, AuthModule}
%% Username = string()
%% AuthModule = ejabberd_auth_anonymous
%% Error = {error, 'not-authorized'}
mech_step(State, _ClientIn) ->
%% We generate a random username:
User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
@ -50,7 +74,7 @@ mech_step(State, _ClientIn) ->
%% Checks that the username is available
case ejabberd_auth:is_user_exists(User, Server) of
true -> {error, "not-authorized"};
true -> {error, 'not-authorized'};
false -> {ok, [{username, User},
{auth_module, ejabberd_auth_anonymous}]}
end.

View File

@ -36,15 +36,35 @@
-behaviour(cyrsasl).
%% @type mechstate() = {state, Step, Nonce, Username, AuthzId, GetPassword, CheckPassword, AuthModule, Host}
%% Step = 1 | 3 | 5
%% Nonce = string()
%% Username = string()
%% AuthzId = string()
%% GetPassword = function()
%% AuthModule = atom()
%% Host = string().
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
host}).
%% @spec (Opts) -> true
%% Opts = term()
start(_Opts) ->
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
%% @spec () -> ok
stop() ->
ok.
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
%% Host = string()
%% GetPassword = function()
%% CheckPassword = function()
%% State = mechstate()
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
{ok, #state{step = 1,
nonce = randoms:get_string(),
@ -52,6 +72,21 @@ mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
get_password = GetPassword,
check_password = CheckPasswordDigest}}.
%% @spec (State, ClientIn) -> Ok | Continue | Error
%% State = mechstate()
%% ClientIn = string()
%% Ok = {ok, Props}
%% Props = [Prop]
%% Prop = {username, Username} | {authzid, AuthzId} | {auth_module, AuthModule}
%% Username = string()
%% AuthzId = string()
%% AuthModule = atom()
%% Continue = {continue, ServerOut, New_State}
%% ServerOut = string()
%% New_State = mechstate()
%% Error = {error, Reason} | {error, Reason, Username}
%% Reason = term()
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue,
"nonce=\"" ++ Nonce ++
@ -60,23 +95,23 @@ mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
case parse(ClientIn) of
bad ->
{error, "bad-protocol"};
{error, 'bad-protocol'};
KeyVals ->
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
UserName = xml:get_attr_s("username", KeyVals),
DigestURI = proplists:get_value("digest-uri", KeyVals, ""),
UserName = proplists:get_value("username", KeyVals, ""),
case is_digesturi_valid(DigestURI, State#state.host) of
false ->
?DEBUG("User login not authorized because digest-uri "
"seems invalid: ~p", [DigestURI]),
{error, "not-authorized", UserName};
{error, 'not-authorized', UserName};
true ->
AuthzId = xml:get_attr_s("authzid", KeyVals),
AuthzId = proplists:get_value("authzid", KeyVals, ""),
case (State#state.get_password)(UserName) of
{false, _} ->
{error, "not-authorized", UserName};
{error, 'not-authorized', UserName};
{Passwd, AuthModule} ->
case (State#state.check_password)(UserName, "",
xml:get_attr_s("response", KeyVals),
proplists:get_value("response", KeyVals, ""),
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
"AUTHENTICATE") end) of
{true, _} ->
@ -90,9 +125,9 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
username = UserName,
authzid = AuthzId}};
false ->
{error, "not-authorized", UserName};
{error, 'not-authorized', UserName};
{false, _} ->
{error, "not-authorized", UserName}
{error, 'not-authorized', UserName}
end
end
end
@ -105,11 +140,18 @@ mech_step(#state{step = 5,
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
{error, "bad-protocol"}.
{error, 'bad-protocol'}.
%% @spec (S) -> [{Key, Value}] | bad
%% S = string()
%% Key = string()
%% Value = string()
parse(S) ->
parse1(S, "", []).
%% @hidden
parse1([$= | Cs], S, Ts) ->
parse2(Cs, lists:reverse(S), "", Ts);
parse1([$, | Cs], [], Ts) ->
@ -123,6 +165,8 @@ parse1([], [], T) ->
parse1([], _S, _T) ->
bad.
%% @hidden
parse2([$\" | Cs], Key, Val, Ts) ->
parse3(Cs, Key, Val, Ts);
parse2([C | Cs], Key, Val, Ts) ->
@ -130,6 +174,8 @@ parse2([C | Cs], Key, Val, Ts) ->
parse2([], _, _, _) ->
bad.
%% @hidden
parse3([$\" | Cs], Key, Val, Ts) ->
parse4(Cs, Key, Val, Ts);
parse3([$\\, C | Cs], Key, Val, Ts) ->
@ -139,6 +185,8 @@ parse3([C | Cs], Key, Val, Ts) ->
parse3([], _, _, _) ->
bad.
%% @hidden
parse4([$, | Cs], Key, Val, Ts) ->
parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
parse4([$\s | Cs], Key, Val, Ts) ->
@ -149,6 +197,10 @@ parse4([], Key, Val, Ts) ->
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
%% @spec (DigestURICase, JabberHost) -> bool()
%% DigestURICase = string()
%% JabberHost = string()
%%
%% @doc Check if the digest-uri is valid.
%% RFC-2831 allows to provide the IP address in Host,
%% however ejabberd doesn't allow that.
@ -156,8 +208,9 @@ parse4([], Key, Val, Ts) ->
%% is provided by several hosts (being one of them server3.example.org),
%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
%% In that case, ejabberd only checks the service name, not the host.
is_digesturi_valid(DigestURICase, JabberHost) ->
DigestURI = stringprep:tolower(DigestURICase),
DigestURI = exmpp_stringprep:to_lower(DigestURICase),
case catch string:tokens(DigestURI, "/") of
["xmpp", Host] when Host == JabberHost ->
true;
@ -170,14 +223,20 @@ is_digesturi_valid(DigestURICase, JabberHost) ->
%% @hidden
digit_to_xchar(D) when (D >= 0) and (D < 10) ->
D + 48;
digit_to_xchar(D) ->
D + 87.
%% @hidden
hex(S) ->
hex(S, []).
%% @hidden
hex([], Res) ->
lists:reverse(Res);
hex([N | Ns], Res) ->
@ -185,12 +244,22 @@ hex([N | Ns], Res) ->
digit_to_xchar(N div 16) | Res]).
%% @spec (KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) -> string()
%% KeyVals = [{Key, Value}]
%% Key = string()
%% Value = string()
%% User = string()
%% Passwd = string()
%% Nonce = string()
%% AuthzId = nil() | string()
%% A2Prefix = string()
response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
Realm = xml:get_attr_s("realm", KeyVals),
CNonce = xml:get_attr_s("cnonce", KeyVals),
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
NC = xml:get_attr_s("nc", KeyVals),
QOP = xml:get_attr_s("qop", KeyVals),
Realm = proplists:get_value("realm", KeyVals, ""),
CNonce = proplists:get_value("cnonce", KeyVals, ""),
DigestURI = proplists:get_value("digest-uri", KeyVals, ""),
NC = proplists:get_value("nc", KeyVals, ""),
QOP = proplists:get_value("qop", KeyVals, ""),
A1 = case AuthzId of
"" ->
binary_to_list(

View File

@ -31,18 +31,44 @@
-behaviour(cyrsasl).
%% @type mechstate() = {state, CheckPassword}
%% CheckPassword = function().
-record(state, {check_password}).
%% @spec (Opts) -> true
%% Opts = term()
start(_Opts) ->
cyrsasl:register_mechanism("PLAIN", ?MODULE, false),
ok.
%% @spec () -> ok
stop() ->
ok.
%% @spec (Host, GetPassword, CheckPassword, CheckPasswordDigest) -> {ok, State}
%% Host = string()
%% GetPassword = function()
%% CheckPassword = function()
%% State = mechstate()
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
{ok, #state{check_password = CheckPassword}}.
%% @spec (State, ClientIn) -> Ok | Error
%% State = mechstate()
%% ClientIn = string()
%% Ok = {ok, Props}
%% Props = [Prop]
%% Prop = {username, Username} | {authzid, AuthzId} | {auth_module, AuthModule}
%% Username = string()
%% AuthzId = string()
%% AuthModule = atom()
%% Error = {error, Reason} | {error, Reason, Username}
%% Reason = term()
mech_step(State, ClientIn) ->
case parse(ClientIn) of
[AuthzId, User, Password] ->
@ -51,16 +77,20 @@ mech_step(State, ClientIn) ->
{ok, [{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]};
_ ->
{error, "not-authorized", User}
{error, 'not-authorized', User}
end;
_ ->
{error, "bad-protocol"}
{error, 'bad-protocol'}
end.
%% @hidden
parse(S) ->
parse1(S, "", []).
%% @hidden
parse1([0 | Cs], S, T) ->
parse1(Cs, "", [lists:reverse(S) | T]);
parse1([C | Cs], S, T) ->

View File

@ -2,7 +2,7 @@
{application, ejabberd,
[{description, "ejabberd"},
{vsn, "2.1.0"},
{vsn, "3.0.0-alpha"},
{modules, [acl,
adhoc,
configure,
@ -61,7 +61,6 @@
gen_mod,
gen_pubsub_node,
gen_pubsub_nodetree,
iconv,
idna,
jd2ejd,
jlib,
@ -74,8 +73,6 @@
mod_echo,
mod_http_bind,
mod_http_fileserver,
mod_irc,
mod_irc_connection,
mod_last,
mod_last_odbc,
mod_muc,
@ -142,7 +139,6 @@
ejabberd_mod_roster,
ejabberd_mod_echo,
ejabberd_mod_pubsub,
ejabberd_mod_irc,
ejabberd_mod_muc,
ejabberd_offline,
random_generator

View File

@ -390,7 +390,7 @@
{access, max_user_sessions, [{10, all}]}.
%% Maximum number of offline messages that users can have:
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
%% This rule allows access only for local users:
{access, local, [{allow, local}]}.
@ -443,6 +443,20 @@
%%}.
%%%. =======
%%%' CAPTCHA
%%
%% Full path to a script that generates the image.
%%
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
%%
%% Host part of the URL sent to the user.
%%
%%{captcha_host, "example.org:5280"}.
%%%. ================
%%%' DEFAULT LANGUAGE
@ -459,20 +473,6 @@
%%}.
%%%. =======
%%%' CAPTCHA
%%
%% Full path to a script that generates the image.
%%
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
%%
%% Host part of the URL sent to the user.
%%
%%{captcha_host, "example.org:5280"}.
%%%. =======
%%%' MODULES
@ -487,7 +487,6 @@
{mod_configure,[]}, % requires mod_adhoc
{mod_disco, []},
%%{mod_echo, [{host, "echo.localhost"}]},
{mod_irc, []},
{mod_http_bind, []},
%%{mod_http_fileserver, [
%% {docroot, "/var/www"},
@ -511,7 +510,7 @@
{access_createnode, pubsub_createnode},
{ignore_pep_from_offline, true},
{last_item_cache, false},
{plugins, ["flat", "hometree", "pep"]} % pep requires mod_caps
{plugins, ["flat", "pep"]} % pep requires mod_caps
]},
{mod_register, [
%%

View File

@ -135,11 +135,6 @@ commands() ->
desc = "Delete offline messages older than DAYS",
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
#ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
desc = "Update PubSub table from ejabberd trunk SVN to 2.1.0",
module = mod_pubsub, function = rename_default_nodeplugin,
args = [], result = {res, rescode}},
#ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
desc = "Change the erlang node name in a backup file",

View File

@ -42,10 +42,10 @@ start(normal, _Args) ->
ejabberd_loglevel:set(4),
write_pid_file(),
application:start(sasl),
application:start(exmpp),
randoms:start(),
db_init(),
sha:start(),
stringprep_sup:start_link(),
start(),
translate:start(),
acl:start(),
@ -101,19 +101,7 @@ init() ->
LogPath = get_log_path(),
error_logger:add_report_handler(ejabberd_logger_h, LogPath),
erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv),
case erl_ddll:load_driver(ejabberd:get_so_path(), expat_erl) of
ok -> ok;
{error, already_loaded} -> ok
end,
Port = open_port({spawn, expat_erl}, [binary]),
loop(Port).
loop(Port) ->
receive
_ ->
loop(Port)
end.
ok.
db_init() ->
case mnesia:system_info(extra_db_nodes) of

View File

@ -56,9 +56,16 @@
-include("ejabberd.hrl").
%% @type authmodule() = ejabberd_auth_anonymous | ejabberd_auth_external |
%% ejabberd_auth_internal | ejabberd_auth_ldap |
%% ejabberd_auth_odbc | ejabberd_auth_pam | atom().
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% @spec () -> term()
start() ->
lists:foreach(
fun(Host) ->
@ -68,46 +75,74 @@ start() ->
end, auth_modules(Host))
end, ?MYHOSTS).
plain_password_required(Server) ->
%% @spec (Server) -> bool()
%% Server = string()
plain_password_required(Server) when is_list(Server) ->
lists:any(
fun(M) ->
M:plain_password_required()
end, auth_modules(Server)).
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% true | false
check_password(User, Server, Password) ->
check_password(User, Server, Password)
when is_list(User), is_list(Server), is_list(Password) ->
case check_password_with_authmodule(User, Server, Password) of
{true, _AuthModule} -> true;
false -> false
end.
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
check_password(User, Server, Password, Digest, DigestGen) ->
check_password(User, Server, Password, Digest, DigestGen)
when is_list(User), is_list(Server), is_list(Password),
is_list(Digest), is_function(DigestGen) ->
case check_password_with_authmodule(User, Server, Password,
Digest, DigestGen) of
{true, _AuthModule} -> true;
false -> false
end.
%% @spec (User, Server, Password) -> {true, AuthModule} | false
%% User = string()
%% Server = string()
%% Password = string()
%% AuthModule = authmodule()
%% @doc Check if the user and password can login in server.
%% The user can login if at least an authentication method accepts the user
%% and the password.
%% The first authentication method that accepts the credentials is returned.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
%% | ejabberd_auth_odbc | ejabberd_auth_pam
check_password_with_authmodule(User, Server, Password) ->
check_password_with_authmodule(User, Server, Password)
when is_list(User), is_list(Server), is_list(Password) ->
check_password_loop(auth_modules(Server), [User, Server, Password]).
check_password_with_authmodule(User, Server, Password, Digest, DigestGen) ->
%% @spec (User, Server, Password, Digest, DigestGen) -> {true, AuthModule} | false
%% User = string()
%% Server = string()
%% Password = string() | undefined
%% Digest = string() | undefined
%% DigestGen = function()
%% AuthModule = authmodule()
%% @doc Check the password is valid and also return the authentication module that accepts it.
%% The password is 'undefined' if the client
%% authenticates using the digest method as defined in
%% XEP-0078: Non-SASL Authentication
check_password_with_authmodule(User, Server, Password, Digest, DigestGen)
when is_list(User), is_list(Server), (is_list(Password) orelse Password == 'undefined'),
is_function(DigestGen), (is_list(Digest) orelse Digest == 'undefined')->
check_password_loop(auth_modules(Server), [User, Server, Password,
Digest, DigestGen]).
@ -121,14 +156,17 @@ check_password_loop([AuthModule | AuthModules], Args) ->
check_password_loop(AuthModules, Args)
end.
%% @spec (User, Server, Password) -> ok | {error, ErrorType}
%% User = string()
%% Server = string()
%% Password = string()
%% ErrorType = empty_password | not_allowed | invalid_jid
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, ErrorType}
%% where ErrorType = empty_password | not_allowed | invalid_jid
set_password(_User, _Server, "") ->
%% We do not allow empty password
{error, empty_password};
set_password(User, Server, Password) ->
set_password(User, Server, Password)
when is_list(User), is_list(Server), is_list(Password) ->
lists:foldl(
fun(M, {error, _}) ->
M:set_password(User, Server, Password);
@ -137,15 +175,20 @@ set_password(User, Server, Password) ->
end, {error, not_allowed}, auth_modules(Server)).
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string() | nil()
try_register(_User, _Server, "") ->
%% We do not allow empty password
{error, not_allowed};
try_register(User, Server, Password) ->
try_register(User, Server, Password)
when is_list(User), is_list(Server), is_list(Password) ->
case is_user_exists(User,Server) of
true ->
{atomic, exists};
false ->
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
case lists:member(exmpp_stringprep:nameprep(Server), ?MYHOSTS) of
true ->
Res = lists:foldl(
fun(_M, {atomic, ok} = Res) ->
@ -155,7 +198,7 @@ try_register(User, Server, Password) ->
end, {error, not_allowed}, auth_modules(Server)),
case Res of
{atomic, ok} ->
ejabberd_hooks:run(register_user, Server,
ejabberd_hooks:run(register_user, list_to_binary(Server),
[User, Server]),
{atomic, ok};
_ -> Res
@ -165,33 +208,54 @@ try_register(User, Server, Password) ->
end
end.
%% Registered users list do not include anonymous users logged
%% @spec () -> [{LUser, LServer}]
%% LUser = string()
%% LServer = string()
%% @doc Registered users list do not include anonymous users logged.
dirty_get_registered_users() ->
lists:flatmap(
fun(M) ->
M:dirty_get_registered_users()
end, auth_modules()).
%% Registered users list do not include anonymous users logged
get_vh_registered_users(Server) ->
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
%% @doc Registered users list do not include anonymous users logged.
get_vh_registered_users(Server) when is_list(Server) ->
lists:flatmap(
fun(M) ->
M:get_vh_registered_users(Server)
end, auth_modules(Server)).
get_vh_registered_users(Server, Opts) ->
%% @spec (Server, Opts) -> [{LUser, LServer}]
%% Server = string()
%% Opts = [{Opt, Val}]
%% Opt = atom()
%% Val = term()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(Server, Opts) when is_list(Server) ->
lists:flatmap(
fun(M) ->
case erlang:function_exported(
M, get_vh_registered_users, 2) of
M, get_vh_registered_users_number, 2) of
true ->
M:get_vh_registered_users(Server, Opts);
M:get_vh_registered_users_number(Server, Opts);
false ->
M:get_vh_registered_users(Server)
M:get_vh_registered_users_number(Server)
end
end, auth_modules(Server)).
get_vh_registered_users_number(Server) ->
%% @spec (Server) -> Users_Number
%% Server = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server) when is_list(Server) ->
lists:sum(
lists:map(
fun(M) ->
@ -204,7 +268,14 @@ get_vh_registered_users_number(Server) ->
end
end, auth_modules(Server))).
get_vh_registered_users_number(Server, Opts) ->
%% @spec (Server, Opts) -> Users_Number
%% Server = string()
%% Opts = [{Opt, Val}]
%% Opt = atom()
%% Val = term()
%% Users_Number = integer()
get_vh_registered_users_number(Server, Opts) when is_list(Server) ->
lists:sum(
lists:map(
fun(M) ->
@ -217,9 +288,13 @@ get_vh_registered_users_number(Server, Opts) ->
end
end, auth_modules(Server))).
%% @spec (User, Server) -> Password | false
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Get the password of the user.
%% @spec (User::string(), Server::string()) -> Password::string()
get_password(User, Server) ->
get_password(User, Server) when is_list(User), is_list(Server) ->
lists:foldl(
fun(M, false) ->
M:get_password(User, Server);
@ -227,7 +302,13 @@ get_password(User, Server) ->
Password
end, false, auth_modules(Server)).
get_password_s(User, Server) ->
%% @spec (User, Server) -> Password | nil()
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Get the password of the user.
get_password_s(User, Server) when is_list(User), is_list(Server) ->
case get_password(User, Server) of
false ->
"";
@ -235,10 +316,15 @@ get_password_s(User, Server) ->
Password
end.
%% @spec (User, Server) -> {Password, AuthModule} | {false, none}
%% User = string()
%% Server = string()
%% Password = string()
%% AuthModule = authmodule()
%% @doc Get the password of the user and the auth module.
%% @spec (User::string(), Server::string()) ->
%% {Password::string(), AuthModule::atom()} | {false, none}
get_password_with_authmodule(User, Server) ->
get_password_with_authmodule(User, Server)
when is_list(User), is_list(Server) ->
lists:foldl(
fun(M, {false, _}) ->
{M:get_password(User, Server), M};
@ -246,18 +332,27 @@ get_password_with_authmodule(User, Server) ->
{Password, AuthModule}
end, {false, none}, auth_modules(Server)).
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
is_user_exists(User, Server) ->
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
%% @doc Returns true if the user exists in the DB or if an anonymous
%% user is logged under the given name.
is_user_exists(User, Server) when is_list(User), is_list(Server) ->
lists:any(
fun(M) ->
M:is_user_exists(User, Server)
end, auth_modules(Server)).
%% Check if the user exists in all authentications module except the module
%% passed as parameter
%% @spec (Module::atom(), User, Server) -> true | false | maybe
is_user_exists_in_other_modules(Module, User, Server) ->
%% @spec (Module, User, Server) -> true | false | maybe
%% Module = authmodule()
%% User = string()
%% Server = string()
%% @doc Check if the user exists in all authentications module except
%% the module passed as parameter.
is_user_exists_in_other_modules(Module, User, Server)
when is_list(User), is_list(Server) ->
is_user_exists_in_other_modules_loop(
auth_modules(Server)--[Module],
User, Server).
@ -278,25 +373,34 @@ is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
%% @spec (User, Server) -> ok | error | {error, not_allowed}
%% User = string()
%% Server = string()
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
remove_user(User, Server) when is_list(User), is_list(Server) ->
R = 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]);
ok -> ejabberd_hooks:run(remove_user, list_to_binary(exmpp_stringprep:nameprep(Server)),
[list_to_binary(User), list_to_binary(Server)]);
_ -> none
end,
R.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Try to remove user if the provided password is correct.
%% The removal is attempted in each auth method provided:
%% when one returns 'ok' the loop stops;
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
remove_user(User, Server, Password) ->
remove_user(User, Server, Password)
when is_list(User), is_list(Server), is_list(Password) ->
R = lists:foldl(
fun(_M, ok = Res) ->
Res;
@ -304,7 +408,8 @@ remove_user(User, Server, Password) ->
M:remove_user(User, Server, Password)
end, error, auth_modules(Server)),
case R of
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
ok -> ejabberd_hooks:run(remove_user, list_to_binary(exmpp_stringprep:nameprep(Server)),
[list_to_binary(User), list_to_binary(Server)]);
_ -> none
end,
R.
@ -313,8 +418,11 @@ remove_user(User, Server, Password) ->
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
%% Return the lists of all the auth modules actually used in the
%% configuration
%% @spec () -> [authmodule()]
%% @doc Return the lists of all the auth modules actually used in the
%% configuration.
auth_modules() ->
lists:usort(
lists:flatmap(
@ -322,9 +430,12 @@ auth_modules() ->
auth_modules(Server)
end, ?MYHOSTS)).
%% Return the list of authenticated modules for a given host
auth_modules(Server) ->
LServer = jlib:nameprep(Server),
%% @spec (Server) -> [authmodule()]
%% Server = string()
%% @doc Return the list of authenticated modules for a given host.
auth_modules(Server) when is_list(Server) ->
LServer = exmpp_stringprep:nameprep(Server),
Method = ejabberd_config:get_local_option({auth_method, LServer}),
Methods = if
Method == undefined -> [];

View File

@ -53,31 +53,44 @@
remove_user/3,
plain_password_required/0]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(anonymous, {us, sid}).
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
start(Host) ->
%% @spec (Host) -> ok
%% Host = string()
%% @doc Create the anonymous table if at least one virtual host has
%% anonymous features enabled.
%% Register to login / logout events.
start(Host) when is_list(Host) ->
HostB = list_to_binary(Host),
%% TODO: Check cluster mode
mnesia:create_table(anonymous, [{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, anonymous)}]),
%% The hooks are needed to add / remove users from the anonymous tables
ejabberd_hooks:add(sm_register_connection_hook, Host,
ejabberd_hooks:add(sm_register_connection_hook, HostB,
?MODULE, register_connection, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
ejabberd_hooks:add(sm_remove_connection_hook, HostB,
?MODULE, unregister_connection, 100),
ok.
%% Return true if anonymous is allowed for host or false otherwise
allow_anonymous(Host) ->
%% @spec (Host) -> bool()
%% Host = string()
%% @doc Return true if anonymous is allowed for host or false otherwise.
allow_anonymous(Host) when is_list(Host) ->
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
%% Return true if anonymous mode is enabled and if anonymous protocol is SASL
%% anonymous protocol can be: sasl_anon|login_anon|both
is_sasl_anonymous_enabled(Host) ->
%% @spec (Host) -> bool()
%% Host = string()
%% @doc Return true if anonymous mode is enabled and if anonymous
%% protocol is SASL anonymous.
%% protocol can be: sasl_anon|login_anon|both
is_sasl_anonymous_enabled(Host) when is_list(Host) ->
case allow_anonymous(Host) of
false -> false;
true ->
@ -88,10 +101,13 @@ is_sasl_anonymous_enabled(Host) ->
end
end.
%% Return true if anonymous login is enabled on the server
%% @spec (Host) -> bool()
%% Host = string()
%% @doc Return true if anonymous login is enabled on the server.
%% anonymous login can be use using standard authentication method (i.e. with
%% clients that do not support anonymous login)
is_login_anonymous_enabled(Host) ->
is_login_anonymous_enabled(Host) when is_list(Host) ->
case allow_anonymous(Host) of
false -> false;
true ->
@ -102,9 +118,12 @@ is_login_anonymous_enabled(Host) ->
end
end.
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% @spec (Host) -> sasl_anon | login_anon | both
%% Host = string()
%% @doc Return the anonymous protocol to use: sasl_anon|login_anon|both.
%% defaults to login_anon
anonymous_protocol(Host) ->
anonymous_protocol(Host) when is_list(Host) ->
case ejabberd_config:get_local_option({anonymous_protocol, Host}) of
sasl_anon -> sasl_anon;
login_anon -> login_anon;
@ -112,18 +131,26 @@ anonymous_protocol(Host) ->
_Other -> sasl_anon
end.
%% Return true if multiple connections have been allowed in the config file
%% @spec (Host) -> bool()
%% Host = string()
%% @doc Return true if multiple connections have been allowed in the
%% config file.
%% defaults to false
allow_multiple_connections(Host) ->
allow_multiple_connections(Host) when is_list(Host) ->
case ejabberd_config:get_local_option({allow_multiple_connections, Host}) of
true -> true;
_Other -> false
end.
%% Check if user exist in the anonymus database
anonymous_user_exist(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
%% @doc Check if user exist in the anonymus database.
anonymous_user_exist(User, Server) when is_list(User), is_list(Server) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({anonymous, US}) of
[] ->
@ -132,45 +159,74 @@ anonymous_user_exist(User, Server) ->
true
end.
%% Remove connection from Mnesia tables
remove_connection(SID, LUser, LServer) ->
%% @spec (SID, LUser, LServer) -> term()
%% SID = term()
%% LUser = string()
%% LServer = string()
%% @doc Remove connection from Mnesia tables.
remove_connection(SID, LUser, LServer) when is_list(LUser), is_list(LServer) ->
US = {LUser, LServer},
F = fun() ->
mnesia:delete_object({anonymous, US, SID})
end,
mnesia:transaction(F).
%% Register connection
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
AuthModule = xml:get_attr_s(auth_module, Info),
case AuthModule == ?MODULE of
true ->
%% @spec (SID, JID, Info) -> term()
%% SID = term()
%% JID = exmpp_jid:jid()
%% Info = [term()]
%% @doc Register connection.
register_connection(SID, JID, Info) when ?IS_JID(JID) ->
LUser = exmpp_jid:prep_node(JID),
LServer = exmpp_jid:prep_domain(JID),
case proplists:get_value(auth_module, Info) of
undefined ->
ok;
?MODULE ->
US = {LUser, LServer},
mnesia:sync_dirty(
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
end);
false ->
ok
_ ->
ok
end.
%% Remove an anonymous user from the anonymous users table
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
%% @spec (SID, JID, Ignored) -> term()
%% SID = term()
%% JID = exmpp_jid:jid()
%% Ignored = term()
%% @doc Remove an anonymous user from the anonymous users table.
unregister_connection(SID, JID, _) when ?IS_JID(JID) ->
LUser = exmpp_jid:prep_node(JID),
LServer = exmpp_jid:prep_domain(JID),
purge_hook(anonymous_user_exist(LUser, LServer),
LUser, LServer),
remove_connection(SID, LUser, LServer).
%% Launch the hook to purge user data only for anonymous users
%% @spec (bool(), LUser, LServer) -> term()
%% LUser = string()
%% LServer = string()
%% @doc Launch the hook to purge user data only for anonymous users.
purge_hook(false, _LUser, _LServer) ->
ok;
purge_hook(true, LUser, LServer) ->
purge_hook(true, LUser, LServer) when is_list(LUser), is_list(LServer) ->
ejabberd_hooks:run(anonymous_purge_hook, LServer, [LUser, LServer]).
%% ---------------------------------
%% Specific anonymous auth functions
%% ---------------------------------
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% @doc When anonymous login is enabled, check the password for
%% permenant users before allowing access.
check_password(User, Server, Password) ->
check_password(User, Server, Password, undefined, undefined).
check_password(User, Server, _Password, _Digest, _DigestGen) ->
@ -185,6 +241,10 @@ check_password(User, Server, _Password, _Digest, _DigestGen) ->
false -> login(User, Server)
end.
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
login(User, Server) ->
case is_login_anonymous_enabled(Server) of
false -> false;
@ -199,8 +259,13 @@ login(User, Server) ->
end
end.
%% When anonymous login is enabled, check that the user is permanent before
%% changing its password
%% @spec (User, Server, Password) -> ok | {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
%% @doc When anonymous login is enabled, check that the user is
%% permanent before changing its password.
set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of
true ->
@ -209,22 +274,41 @@ set_password(User, Server, _Password) ->
{error, not_allowed}
end.
%% When anonymous login is enabled, check if permanent users are allowed on
%% the server:
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
%% @doc When anonymous login is enabled, check if permanent users are
%% allowed on the server:
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
%% @spec () -> nil()
dirty_get_registered_users() ->
[].
%% @spec (Server) -> nil()
%% Server = string()
get_vh_registered_users(_Server) ->
[].
%% @spec (User, Server) -> Password | false
%% User = string()
%% Server = string()
%% Password = nil()
%% @doc Return password of permanent user or false for anonymous users.
%% Return password of permanent user or false for anonymous users
get_password(User, Server) ->
get_password(User, Server, "").
%% @spec (User, Server, DefaultValue) -> DefaultValue | false
%% User = string()
%% Server = string()
%% DefaultValue = string()
get_password(User, Server, DefaultValue) ->
case anonymous_user_exist(User, Server) or login(User, Server) of
%% We return the default value if the user is anonymous
@ -235,17 +319,32 @@ get_password(User, Server, DefaultValue) ->
false
end.
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
%% @doc Returns true if the user exists in the DB or if an anonymous
%% user is logged under the given name.
is_user_exists(User, Server) ->
anonymous_user_exist(User, Server).
%% @spec (User, Server) -> {error, not_allowed}
%% User = string()
%% Server = string()
remove_user(_User, _Server) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
remove_user(_User, _Server, _Password) ->
not_allowed.
%% @spec () -> bool()
plain_password_required() ->
false.

View File

@ -46,44 +46,88 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% @spec (Host) -> ok
%% Host = string()
start(Host) ->
extauth:start(
Host, ejabberd_config:get_local_option({extauth_program, Host})),
ok.
%% @spec () -> bool()
plain_password_required() ->
true.
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password(User, Server, Password) ->
extauth:check_password(User, Server, Password) andalso Password /= "".
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
check_password(User, Server, Password, _Digest, _DigestGen) ->
check_password(User, Server, Password).
%% @spec (User, Server, Password) -> ok | {error, unknown_problem}
%% User = string()
%% Server = string()
%% Password = string()
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
true -> ok;
_ -> {error, unknown_problem}
end.
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
%% TODO
%% Return the list of all users handled by external
%% @spec () -> nil()
%% @todo Write it.
%% @doc Return the list of all users handled by external.
dirty_get_registered_users() ->
[].
%% @spec (Server) -> nil()
%% Server = string()
get_vh_registered_users(_Server) ->
[].
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
get_password(_User, _Server) ->
false.
%% @spec (User, Server) -> nil()
%% User = string()
%% Server = string()
get_password_s(_User, _Server) ->
"".
%% @spec (User, Server) -> true | false | {error, Error}
%% User = string()
%% Server = string()
is_user_exists(User, Server) ->
try extauth:is_user_exists(User, Server) of
Res -> Res
@ -91,9 +135,18 @@ is_user_exists(User, Server) ->
_:Error -> {error, Error}
end.
%% @spec (User, Server) -> {error, not_allowed}
%% User = string()
%% Server = string()
remove_user(_User, _Server) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> not_allowed
%% User = string()
%% Server = string()
%% Password = string()
remove_user(_User, _Server, _Password) ->
not_allowed.

View File

@ -54,6 +54,10 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% @spec (Host) -> ok
%% Host = string()
start(Host) ->
mnesia:create_table(passwd, [{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
@ -67,15 +71,22 @@ start(Host) ->
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
LServer = jlib:nameprep(Server),
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
set_vh_registered_users_counter(LServer, Size).
%% @spec () -> bool()
plain_password_required() ->
false.
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}] ->
@ -84,9 +95,16 @@ check_password(User, Server, Password) ->
false
end.
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
check_password(User, Server, Password, Digest, DigestGen) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] ->
@ -105,11 +123,14 @@ check_password(User, Server, Password, Digest, DigestGen) ->
false
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
%% User = string()
%% Server = string()
%% Password = string()
set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
if
(LUser == error) or (LServer == error) ->
@ -124,9 +145,13 @@ set_password(User, Server, Password) ->
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
%% User = string()
%% Server = string()
%% Password = string()
try_register(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
if
(LUser == error) or (LServer == error) ->
@ -146,18 +171,45 @@ try_register(User, Server, Password) ->
mnesia:transaction(F)
end.
%% Get all registered users in Mnesia
%% @spec () -> [{LUser, LServer}]
%% LUser = string()
%% LServer = string()
%% @doc Get all registered users in Mnesia.
dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
mnesia:dirty_select(
passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
%% @spec (Server, Opts) -> [{LUser, LServer}]
%% Server = string()
%% Opts = [{Opt, Val}]
%% Opt = atom()
%% Val = term()
%% LUser = string()
%% LServer = string()
%% @doc Return the registered users for the specified host.
%%
%% `Opts' can be one of the following:
%% <ul>
%% <li>`[{from, integer()}, {to, integer()}]'</li>
%% <li>`[{limit, integer()}, {offset, integer()}]'</li>
%% <li>`[{prefix, string()}]'</li>
%% <li>`[{prefix, string()}, {from, integer()}, {to, integer()}]'</li>
%% <li>`[{prefix, string()}, {limit, integer()}, {offset, integer()}]'</li>
%% </ul>
get_vh_registered_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server, [{limit, End-Start+1}, {offset, Start}]);
@ -204,8 +256,12 @@ get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offs
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
%% @spec (Server) -> Users_Number
%% Server = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
Query = mnesia:dirty_select(
reg_users_counter,
[{#reg_users_counter{vhost = LServer, count = '$1'},
@ -217,6 +273,11 @@ get_vh_registered_users_number(Server) ->
_ -> 0
end.
%% @spec (Server, [{prefix, Prefix}]) -> Users_Number
%% Server = string()
%% Prefix = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
length(Set);
@ -258,88 +319,137 @@ set_vh_registered_users_counter(LServer, Count) ->
end,
mnesia:sync_dirty(F).
%% @spec (User, Server) -> Password | false
%% User = string()
%% Server = string()
%% Password = string()
get_password(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] ->
Password;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] ->
Password;
_ ->
false
end
catch
_ ->
false
end.
%% @spec (User, Server) -> Password | nil()
%% User = string()
%% Server = string()
%% Password = string()
get_password_s(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] ->
Password;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}] ->
Password;
_ ->
[]
end
catch
_ ->
[]
end.
%% @spec (User, Server) -> true | false | {error, Error}
%% User = string()
%% Server = string()
is_user_exists(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] ->
false;
[_] ->
true;
Other ->
{error, Other}
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] ->
false;
[_] ->
true;
Other ->
{error, Other}
end
catch
_ ->
false
end.
%% @spec (User, Server) -> ok
%% User = string()
%% Server = string()
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({passwd, US}),
dec_vh_registered_users_counter(LServer)
end,
mnesia:transaction(F),
ok.
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({passwd, US}),
dec_vh_registered_users_counter(LServer)
end,
mnesia:transaction(F),
ok
catch
_ ->
ok
end.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
case mnesia:read({passwd, US}) of
[#passwd{password = Password}] ->
mnesia:delete({passwd, US}),
dec_vh_registered_users_counter(LServer),
ok;
[_] ->
not_allowed;
_ ->
not_exists
end
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{atomic, Res} ->
Res;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
case mnesia:read({passwd, US}) of
[#passwd{password = Password}] ->
mnesia:delete({passwd, US}),
dec_vh_registered_users_counter(LServer),
ok;
[_] ->
not_allowed;
_ ->
not_exists
end
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{atomic, Res} ->
Res;
_ ->
bad_request
end
catch
_ ->
bad_request
end.
%% @spec () -> term()
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
Fields ->
% No conversion is needed when the table comes from an exmpp-less
% Ejabberd because ejabberd_auth* modules use string() and not
% binary().
ok;
[user, password] ->
?INFO_MSG("Converting passwd table from "

View File

@ -95,6 +95,9 @@ handle_info(_Info, State) ->
%%% API
%%%----------------------------------------------------------------------
%% @spec (Host) -> term()
%% Host = string()
start(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
ChildSpec = {
@ -103,19 +106,31 @@ start(Host) ->
},
supervisor:start_child(ejabberd_sup, ChildSpec).
%% @spec (Host) -> term()
%% Host = string()
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:call(Proc, stop),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
%% @spec (Host) -> term()
%% Host = string()
start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
%% @hidden
terminate(_Reason, _State) ->
ok.
%% @spec (Host) -> {ok, State}
%% Host = string()
%% State = term()
init(Host) ->
State = parse_options(Host),
eldap_pool:start_link(State#state.eldap_id,
@ -134,9 +149,16 @@ init(Host) ->
State#state.encrypt),
{ok, State}.
%% @spec () -> true
plain_password_required() ->
true.
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password(User, Server, Password) ->
%% In LDAP spec: empty password means anonymous authentication.
%% As ejabberd is providing other anonymous authentication mechanisms
@ -150,16 +172,36 @@ check_password(User, Server, Password) ->
end
end.
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
check_password(User, Server, Password, _Digest, _DigestGen) ->
check_password(User, Server, Password).
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
%% @spec () -> [{LUser, LServer}]
%% LUser = string()
%% LServer = string()
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
lists:flatmap(
@ -167,22 +209,42 @@ dirty_get_registered_users() ->
get_vh_registered_users(Server)
end, Servers).
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(Server) ->
case catch get_vh_registered_users_ldap(Server) of
{'EXIT', _} -> [];
Result -> Result
end.
%% @spec (Server) -> Users_Number
%% Server = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
get_password(_User, _Server) ->
false.
%% @spec (User, Server) -> nil()
%% User = string()
%% Server = string()
get_password_s(_User, _Server) ->
"".
%% @spec (User, Server) -> true | false | {error, Error}
%% User = string()
%% Server = string()
is_user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of
{'EXIT', Error} ->
@ -191,15 +253,30 @@ is_user_exists(User, Server) ->
Result
end.
%% @spec (User, Server) -> {error, not_allowed}
%% User = string()
%% Server = string()
remove_user(_User, _Server) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> not_allowed
%% User = string()
%% Server = string()
%% Password = string()
remove_user(_User, _Server, _Password) ->
not_allowed.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password_ldap(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
@ -212,6 +289,11 @@ check_password_ldap(User, Server, Password) ->
end
end.
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
get_vh_registered_users_ldap(Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
UIDs = State#state.uids,
@ -236,9 +318,11 @@ get_vh_registered_users_ldap(Server) ->
{User, UIDFormat} ->
case eldap_utils:get_user_part(User, UIDFormat) of
{ok, U} ->
case jlib:nodeprep(U) of
error -> [];
LU -> [{LU, jlib:nameprep(Server)}]
try
[{exmpp_stringprep:nodeprep(U), exmpp_stringprep:nameprep(Server)}]
catch
_ ->
[]
end;
_ -> []
end
@ -252,6 +336,10 @@ get_vh_registered_users_ldap(Server) ->
[]
end.
%% @spec (User, Server) -> bool()
%% User = string()
%% Server = string()
is_user_exists_ldap(User, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of

View File

@ -51,22 +51,30 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% @spec (Host) -> ok
%% Host = string()
start(_Host) ->
ok.
%% @spec () -> bool()
plain_password_required() ->
false.
%% @spec (User, Server, Password) -> true | false | {error, Error}
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password /= ""; %% Password is correct, and not empty
{selected, ["password"], [{_Password2}]} ->
false; %% Password is not correct
@ -77,31 +85,38 @@ check_password(User, Server, Password) ->
catch
_:_ ->
false %% Typical error is database not accessible
end
end
catch
_ ->
false
end.
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
check_password(User, Server, Password, Digest, DigestGen) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
%% Account exists, check if password is valid
{selected, ["password"], [{Passwd}]} ->
DigRes = if
Digest /= "" ->
{selected, ["password"], [{Passwd}]} ->
DigRes = if
Digest /= "" ->
Digest == DigestGen(Passwd);
true ->
false
end,
if DigRes ->
true;
true ->
(Passwd == Password) and (Password /= "")
end;
true ->
false
end,
if DigRes ->
true;
true ->
(Passwd == Password) and (Password /= "")
end;
{selected, ["password"], []} ->
false; %% Account does not exist
{error, _Error} ->
@ -109,43 +124,59 @@ check_password(User, Server, Password, Digest, DigestGen) ->
catch
_:_ ->
false %% Typical error is database not accessible
end
end
catch
_ ->
false
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
%% User = string()
%% Server = string()
%% Password = string()
set_password(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
{error, invalid_jid};
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server),
case catch odbc_queries:set_password_t(LServer, Username, Pass) of
{atomic, ok} -> ok;
Other -> {error, Other}
end
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:set_password_t(LServer, Username, Pass) of
{atomic, ok} -> ok;
Other -> {error, Other}
end
catch
_ ->
{error, invalid_jid}
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
%% User = string()
%% Server = string()
%% Password = string()
try_register(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
{error, invalid_jid};
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server),
case catch odbc_queries:add_user(LServer, Username, Pass) of
{updated, 1} ->
{atomic, ok};
_ ->
{atomic, exists}
end
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:add_user(LServer, Username, Pass) of
{updated, 1} ->
{atomic, ok};
_ ->
{atomic, exists}
end
catch
_ ->
{error, invalid_jid}
end.
%% @spec () -> [{LUser, LServer}]
%% LUser = string()
%% LServer = string()
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(odbc),
lists:flatmap(
@ -153,8 +184,13 @@ dirty_get_registered_users() ->
get_vh_registered_users(Server)
end, Servers).
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:list_users(LServer) of
{selected, ["username"], Res} ->
[{U, LServer} || {U} <- Res];
@ -162,8 +198,16 @@ get_vh_registered_users(Server) ->
[]
end.
%% @spec (Server, Opts) -> [{LUser, LServer}]
%% Server = string()
%% Opts = [{Opt, Val}]
%% Opt = atom()
%% Val = term()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(Server, Opts) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:list_users(LServer, Opts) of
{selected, ["username"], Res} ->
[{U, LServer} || {U} <- Res];
@ -171,8 +215,12 @@ get_vh_registered_users(Server, Opts) ->
[]
end.
%% @spec (Server) -> Users_Number
%% Server = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:users_number(LServer) of
{selected, [_], [{Res}]} ->
list_to_integer(Res);
@ -180,8 +228,15 @@ get_vh_registered_users_number(Server) ->
0
end.
%% @spec (Server, Opts) -> Users_Number
%% Server = string()
%% Opts = [{Opt, Val}]
%% Opt = atom()
%% Val = term()
%% Users_Number = integer()
get_vh_registered_users_number(Server, Opts) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:users_number(LServer, Opts) of
{selected, [_], [{Res}]} ->
list_to_integer(Res);
@ -189,46 +244,59 @@ get_vh_registered_users_number(Server, Opts) ->
0
end.
%% @spec (User, Server) -> Password | false
%% User = string()
%% Server = string()
%% Password = string()
get_password(User, Server) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
false
end
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
false
end
catch
_ ->
false
end.
%% @spec (User, Server) -> Password | nil()
%% User = string()
%% Server = string()
%% Password = string()
get_password_s(User, Server) ->
case jlib:nodeprep(User) of
error ->
"";
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
""
end
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
""
end
catch
_ ->
""
end.
%% @spec (User, Server) -> true | false | {error, Error}
%% User = string()
%% Server = string()
is_user_exists(User, Server) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{_Password}]} ->
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{_Password}]} ->
true; %% Account exists
{selected, ["password"], []} ->
false; %% Account does not exist
@ -237,45 +305,57 @@ is_user_exists(User, Server) ->
catch
_:B ->
{error, B} %% Typical error is database not accessible
end
end
catch
_ ->
false
end.
%% @spec (User, Server) -> ok | error
%% User = string()
%% Server = string()
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
case jlib:nodeprep(User) of
error ->
error;
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
catch odbc_queries:del_user(LServer, Username),
ok
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
LServer = exmpp_stringprep:nameprep(Server),
catch odbc_queries:del_user(LServer, Username),
ok
catch
_ ->
error
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
error;
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server),
F = fun() ->
Result = odbc_queries:del_user_return_password(
LServer, Username, Pass),
case Result of
{selected, ["password"], [{Password}]} ->
ok;
{selected, ["password"], []} ->
not_exists;
_ ->
not_allowed
end
end,
{atomic, Result} = odbc_queries:sql_transaction(LServer, F),
Result
try
LUser = exmpp_stringprep:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = exmpp_stringprep:nameprep(Server),
F = fun() ->
Result = odbc_queries:del_user_return_password(
LServer, Username, Pass),
case Result of
{selected, ["password"], [{Password}]} ->
ok;
{selected, ["password"], []} ->
not_exists;
_ ->
not_allowed
end
end,
{atomic, Result} = odbc_queries:sql_transaction(LServer, F),
Result
catch
_ ->
error
end.

View File

@ -45,6 +45,10 @@
%%====================================================================
%% API
%%====================================================================
%% @spec (Host) -> ok | term()
%% Host = string()
start(_Host) ->
case epam:start() of
{ok, _} -> ok;
@ -52,57 +56,117 @@ start(_Host) ->
Err -> Err
end.
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
check_password(User, Server, Password, _Digest, _DigestGen) ->
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
check_password(User, Server, Password, _StreamID, _Digest) ->
check_password(User, Server, Password).
check_password(User, Host, Password) ->
Service = get_pam_service(Host),
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password(User, Server, Password) ->
Service = get_pam_service(Server),
case catch epam:authenticate(Service, User, Password) of
true -> true;
_ -> false
end.
%% @spec (User, Server, Password) -> {error, not_allowed}
%% User = string()
%% Server = string()
%% Password = string()
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
%% @spec () -> [{LUser, LServer}]
%% LUser = string()
%% LServer = string()
dirty_get_registered_users() ->
[].
get_vh_registered_users(_Host) ->
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(_Server) ->
[].
%% @spec (User, Server) -> Password | false
%% User = string()
%% Server = string()
%% Password = string()
get_password(_User, _Server) ->
false.
%% @spec (User, Server) -> Password | nil()
%% User = string()
%% Server = string()
%% Password = string()
get_password_s(_User, _Server) ->
"".
%% @spec (User, Server) -> true | false | {error, Error}
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
is_user_exists(User, Host) ->
Service = get_pam_service(Host),
%% User = string()
%% Server = string()
%% TODO: Improve this function to return an error instead of 'false' when
%% connection to PAM failed
is_user_exists(User, Server) ->
Service = get_pam_service(Server),
case catch epam:acct_mgmt(Service, User) of
true -> true;
_ -> false
end.
%% @spec (User, Server) -> {error, not_allowed}
%% User = string()
%% Server = string()
remove_user(_User, _Server) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> not_allowed
%% User = string()
%% Server = string()
%% Password = string()
remove_user(_User, _Server, _Password) ->
not_allowed.
%% @spec () -> bool()
plain_password_required() ->
true.
%%====================================================================
%% Internal functions
%%====================================================================
get_pam_service(Host) ->
case ejabberd_config:get_local_option({pam_service, Host}) of
%% @spec (Server) -> string()
%% Server = string()
get_pam_service(Server) ->
case ejabberd_config:get_local_option({pam_service, Server}) of
undefined -> "ejabberd";
Service -> Service
end.

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,8 @@
-export([create_captcha/6, build_captcha_html/2, check_captcha/2,
process_reply/1, process/2, is_feature_available/0]).
-include_lib("exmpp/include/exmpp.hrl").
-include("jlib.hrl").
-include("ejabberd.hrl").
-include("web/ejabberd_http.hrl").
@ -72,12 +74,11 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
create_captcha(Id, SID, From, To, Lang, Args)
when is_list(Id), is_list(Lang), is_list(SID),
is_record(From, jid), is_record(To, jid) ->
when is_list(Id), is_list(SID) ->
case create_image() of
{ok, Type, Key, Image} ->
B64Image = jlib:encode_base64(binary_to_list(Image)),
JID = jlib:jid_to_string(From),
JID = exmpp_jid:to_list(From),
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
Data = {xmlelement, "data",
[{"xmlns", ?NS_BOB}, {"cid", CID},
@ -85,9 +86,10 @@ create_captcha(Id, SID, From, To, Lang, Args)
[{xmlcdata, B64Image}]},
Captcha =
{xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
%% ?NS_DATA_FORMS is 'jabber:x:data'
[{xmlelement, "x", [{"xmlns", "jabber:x:data"}, {"type", "form"}],
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
?VFIELD("hidden", "from", {xmlcdata, exmpp_jid:to_list(To)}),
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
?VFIELD("hidden", "sid", {xmlcdata, SID}),
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
@ -165,12 +167,14 @@ check_captcha(Id, ProvidedKey) ->
captcha_not_found
end).
process_reply({xmlelement, "captcha", _, _} = El) ->
case xml:get_subtag(El, "x") of
false ->
process_reply(El) ->
case {exmpp_xml:element_matches(El, captcha),
exmpp_xml:get_element(El, x)} of
{false, _} ->
{error, malformed};
Xdata ->
{_, undefined} ->
{error, malformed};
{true, Xdata} ->
Fields = jlib:parse_xdata_submit(Xdata),
case {proplists:get_value("challenge", Fields),
proplists:get_value("ocr", Fields)} of
@ -192,9 +196,7 @@ process_reply({xmlelement, "captcha", _, _} = El) ->
_ ->
{error, malformed}
end
end;
process_reply(_) ->
{error, malformed}.
end.
process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
@ -244,7 +246,6 @@ process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
process(_Handlers, _Request) ->
ejabberd_web:error(not_found).
%%====================================================================
%% gen_server callbacks
%%====================================================================

View File

@ -396,7 +396,7 @@ get_md5(AccountPass) ->
check_access(Access, User, Server) ->
%% Check this user has access permission
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of
case acl:match_rule(Server, Access, exmpp_jid:make(User, Server, "")) of
allow -> true;
deny -> false
end.

View File

@ -160,13 +160,14 @@ normalize_hosts(Hosts) ->
normalize_hosts([], PrepHosts) ->
lists:reverse(PrepHosts);
normalize_hosts([Host|Hosts], PrepHosts) ->
case jlib:nodeprep(Host) of
error ->
try
PrepHost = exmpp_stringprep:nodeprep(Host),
normalize_hosts(Hosts, [PrepHost|PrepHosts])
catch
_ ->
?ERROR_MSG("Can't load config file: "
"invalid host name [~p]", [Host]),
exit("invalid hostname");
PrepHost ->
normalize_hosts(Hosts, [PrepHost|PrepHosts])
exit("invalid hostname")
end.
@ -377,12 +378,12 @@ process_term(Term, State) ->
add_option(watchdog_large_heap, LH, State);
{registration_timeout, Timeout} ->
add_option(registration_timeout, Timeout, State);
{ejabberdctl_access_commands, ACs} ->
add_option(ejabberdctl_access_commands, ACs, State);
{captcha_cmd, Cmd} ->
add_option(captcha_cmd, Cmd, State);
{captcha_host, Host} ->
add_option(captcha_host, Host, State);
{ejabberdctl_access_commands, ACs} ->
add_option(ejabberdctl_access_commands, ACs, State);
{loglevel, Loglevel} ->
ejabberd_loglevel:set(Loglevel),
State;

View File

@ -277,7 +277,7 @@ try_call_command(Args, Auth, AccessCommands) ->
%% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType}
call_command([CmdString | Args], Auth, AccessCommands) ->
{ok, CmdStringU, _} = regexp:gsub(CmdString, "-", "_"),
CmdStringU = re:replace(CmdString, "-", "_", [global,{return,list}]),
Command = list_to_atom(CmdStringU),
case ejabberd_commands:get_command_format(Command) of
{error, command_unknown} ->
@ -673,13 +673,13 @@ filter_commands(All, SubString) ->
end.
filter_commands_regexp(All, Glob) ->
RegExp = regexp:sh_to_awk(Glob),
RegExp = xmerl_regexp:sh_to_awk(Glob),
lists:filter(
fun(Command) ->
case regexp:first_match(Command, RegExp) of
{match, _, _} ->
case re:run(Command, RegExp, [{capture, none}]) of
match ->
true;
_ ->
nomatch ->
false
end
end,

View File

@ -95,8 +95,7 @@ start(Module, SockMod, Socket, Opts) ->
end.
starttls(FsmRef, _TLSOpts) ->
%% TODO: Frontend improvements planned by Aleksey
%%gen_server:call(FsmRef, {starttls, TLSOpts}),
%gen_server:call(FsmRef, {starttls, TLSOpts}),
FsmRef.
starttls(FsmRef, TLSOpts, Data) ->
@ -139,8 +138,7 @@ sockname(FsmRef) ->
gen_server:call(FsmRef, sockname).
peername(_FsmRef) ->
%% TODO: Frontend improvements planned by Aleksey
%%gen_server:call(FsmRef, peername).
%gen_server:call(FsmRef, peername).
{ok, {{0, 0, 0, 0}, 0}}.

View File

@ -81,7 +81,8 @@ add(Hook, Host, Function, Seq) when is_function(Function) ->
add(Hook, Module, Function, Seq) ->
add(Hook, global, Module, Function, Seq).
add(Hook, Host, Module, Function, Seq) ->
add(Hook, Host, Module, Function, Seq)
when is_binary(Host) orelse is_atom(Host) ->
gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).
add_dist(Hook, Node, Module, Function, Seq) ->
@ -104,7 +105,8 @@ delete(Hook, Host, Function, Seq) when is_function(Function) ->
delete(Hook, Module, Function, Seq) ->
delete(Hook, global, Module, Function, Seq).
delete(Hook, Host, Module, Function, Seq) ->
delete(Hook, Host, Module, Function, Seq)
when is_binary(Host) orelse is_atom(Host) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}).
delete_dist(Hook, Node, Module, Function, Seq) ->
@ -119,7 +121,7 @@ delete_dist(Hook, Host, Node, Module, Function, Seq) ->
run(Hook, Args) ->
run(Hook, global, Args).
run(Hook, Host, Args) ->
run(Hook, Host, Args) when is_binary(Host) orelse is_atom(Host) ->
case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
run1(Ls, Hook, Args);
@ -136,7 +138,7 @@ run(Hook, Host, Args) ->
run_fold(Hook, Val, Args) ->
run_fold(Hook, global, Val, Args).
run_fold(Hook, Host, Val, Args) ->
run_fold(Hook, Host, Val, Args) when is_binary(Host) orelse is_atom(Host) ->
case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
run_fold1(Ls, Hook, Val, Args);

View File

@ -48,8 +48,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {}).
@ -60,6 +61,13 @@
%% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
%%====================================================================
%% API
%%====================================================================
@ -71,33 +79,31 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS} ->
Host = To#jid.lserver,
case exmpp_iq:xmlel_to_iq(Packet) of
#iq{kind = request, ns = XMLNS} = IQ_Rec ->
Host = exmpp_jid:prep_domain(To),
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
[{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ),
ResIQ = Module:Function(From, To, IQ_Rec),
if
ResIQ /= ignore ->
ejabberd_router:route(
To, From, jlib:iq_to_xml(ResIQ));
Reply = exmpp_iq:iq_to_xmlel(ResIQ, To, From),
ejabberd_router:route(To, From, Reply);
true ->
ok
end;
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
From, To, IQ_Rec);
[] ->
Err = jlib:make_error_reply(
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
Err = exmpp_iq:error(Packet, 'feature-not-implemented'),
ejabberd_router:route(To, From, Err)
end;
reply ->
IQReply = jlib:iq_query_or_response_info(Packet),
process_iq_reply(From, To, IQReply);
#iq{kind = response} = IQReply ->
%%IQReply = jlib:iq_query_or_response_info(IQ_Rec),
process_iq_reply(From, To, IQReply);
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
Err = exmpp_iq:error(Packet, 'bad-request'),
ejabberd_router:route(To, From, Err),
ok
end.
@ -113,7 +119,17 @@ process_iq_reply(From, To, #iq{id = ID} = IQ) ->
_ ->
nothing
end.
route(FromOld, ToOld, #xmlelement{} = PacketOld) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nLOCAL: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
route(From, To, Packet);
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -125,12 +141,12 @@ route(From, To, Packet) ->
route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
Host = From#jid.lserver,
ID = list_to_binary(randoms:get_string()),
Host = exmpp_jid:prep_domain(From),
register_iq_response_handler(Host, ID, undefined, F),
jlib:iq_to_xml(IQ#iq{id = ID});
exmpp_iq:iq_to_xmlel(IQ#iq{id = ID});
true ->
jlib:iq_to_xml(IQ)
exmpp_iq:iq_to_xmlel(IQ)
end,
ejabberd_router:route(From, To, Packet).
@ -158,7 +174,7 @@ refresh_iq_handlers() ->
ejabberd_local ! refresh_iq_handlers.
bounce_resource_packet(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
Err = exmpp_stanza:reply_with_error(Packet, 'item-not-found'),
ejabberd_router:route(To, From, Err),
stop.
@ -177,7 +193,7 @@ init([]) ->
lists:foreach(
fun(Host) ->
ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
ejabberd_hooks:add(local_send_to_resource_hook, list_to_binary(Host),
?MODULE, bounce_resource_packet, 100)
end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
@ -216,6 +232,16 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, FromOld, ToOld, #xmlelement{} = PacketOld}, State) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nLOCAL: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
handle_info({route, From, To, Packet}, State);
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -285,29 +311,30 @@ code_change(_OldVsn, State, _Extra) ->
do_route(From, To, Packet) ->
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
LNode = exmpp_jid:prep_node(To),
LResource = exmpp_jid:prep_resource(To),
if
To#jid.luser /= "" ->
LNode /= undefined ->
ejabberd_sm:route(From, To, Packet);
To#jid.lresource == "" ->
{xmlelement, Name, _Attrs, _Els} = Packet,
case Name of
"iq" ->
LResource == undefined ->
case Packet of
_ when ?IS_IQ(Packet) ->
process_iq(From, To, Packet);
"message" ->
_ when ?IS_MESSAGE(Packet) ->
ok;
"presence" ->
_ when ?IS_PRESENCE(Packet) ->
ok;
_ ->
ok
end;
true ->
{xmlelement, _Name, Attrs, _Els} = Packet,
case xml:get_attr_s("type", Attrs) of
"error" -> ok;
"result" -> ok;
case exmpp_stanza:get_type(Packet) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
ejabberd_hooks:run(local_send_to_resource_hook,
To#jid.lserver,
exmpp_jid:prep_domain(To),
[From, To, Packet])
end
end.

View File

@ -25,7 +25,6 @@
%%%----------------------------------------------------------------------
%%% Not implemented:
%%% - write mod_piefxis with ejabberdctl commands
%%% - Export from mod_offline_odbc.erl
%%% - Export from mod_private_odbc.erl
%%% - XEP-227: 6. Security Considerations
@ -41,42 +40,8 @@
-record(parsing_state, {parser, host, dir}).
-include("ejabberd.hrl").
%%-include_lib("exmpp/include/exmpp.hrl").
%%-include_lib("exmpp/include/exmpp_client.hrl").
%% Copied from exmpp header files:
-define(NS_ROSTER, "jabber:iq:roster").
-define(NS_VCARD, "vcard-temp").
-record(xmlcdata, {
cdata = <<>>
}).
-record(xmlattr, {
ns = undefined,
name,
value
}).
-record(xmlel, {
ns = undefined,
declared_ns = [],
name,
attrs = [],
children = []
}).
-record(iq, {
kind,
type,
id,
ns,
payload,
error,
lang,
iq_ns
}).
-record(xmlendtag, {
ns = undefined,
name
}).
-include_lib("exmpp/include/exmpp.hrl").
-include_lib("exmpp/include/exmpp_client.hrl").
%% Copied from mod_private.erl
-record(private_storage, {usns, xml}).
@ -96,7 +61,6 @@
%%%% Import file
import_file(FileName) ->
_ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused"
import_file(FileName, 2).
import_file(FileName, RootDepth) ->
@ -159,7 +123,7 @@ process_element(El=#xmlel{name=user, ns=_XMLNS},
State;
process_element(H=#xmlel{name=host},State) ->
State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H,"jid",none))};
State#parsing_state{host=exmpp_xml:get_attribute(H,"jid",none)};
process_element(#xmlel{name='server-data'},State) ->
State;
@ -196,7 +160,7 @@ process_element(El,State) ->
add_user(El, Domain) ->
User = exmpp_xml:get_attribute(El,name,none),
Password = exmpp_xml:get_attribute(El,password,none),
add_user(El, Domain, ?BTL(User), ?BTL(Password)).
add_user(El, Domain, User, Password).
%% @spec (El::xmlel(), Domain::string(), User::string(), Password::string())
%% -> ok | {atomic, exists} | {error, not_allowed}
@ -228,7 +192,7 @@ add_user(El, Domain, User, Password) ->
%% -> ok | {atomic, exists} | {error, not_allowed}
%% @doc Create a new user
create_user(User,Password,Domain) ->
case ejabberd_auth:try_register(User,Domain,Password) of
case ejabberd_auth:try_register(?BTL(User),?BTL(Domain),?BTL(Password)) of
{atomic,ok} -> ok;
{atomic, exists} -> {atomic, exists};
{error, not_allowed} -> {error, not_allowed};
@ -265,7 +229,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
io:format("Trying to add/update roster list...",[]),
case loaded_module(Domain,[mod_roster_odbc,mod_roster]) of
{ok, M} ->
case M:set_items(User, Domain, exmpp_xml:xmlel_to_xmlelement(El)) of
case M:set_items(User, Domain, El) of
{atomic, ok} ->
io:format(" DONE.~n",[]),
ok;
@ -305,8 +269,8 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
io:format("Trying to add/update vCards...",[]),
case loaded_module(Domain,[mod_vcard,mod_vcard_odbc]) of
{ok, M} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
IQ = iq_to_old_iq(#iq{type = set, payload = El}),
{ok, M} -> FullUser = exmpp_jid:make(User, Domain),
IQ = #iq{type = set, payload = El},
case M:process_sm_iq(FullUser, FullUser , IQ) of
{error,_Err} ->
io:format(" ERROR.~n",[]),
@ -337,11 +301,9 @@ populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
ok;
(_Element, Child) ->
From = exmpp_xml:get_attribute(Child,from,none),
FullFrom = jid_to_old_jid(exmpp_jid:parse(From)),
FullUser = jid_to_old_jid(exmpp_jid:make(User,
Domain)),
OldChild = exmpp_xml:xmlel_to_xmlelement(Child),
_R = M:store_packet(FullFrom, FullUser, OldChild)
FullFrom = exmpp_jid:parse(From),
FullUser = exmpp_jid:make(User, Domain),
_R = M:store_packet(FullFrom, FullUser, Child)
end, El), io:format(" DONE.~n",[]);
_ ->
io:format(" ERROR.~n",[]),
@ -360,12 +322,12 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
io:format("Trying to add/update private storage...",[]),
case loaded_module(Domain,[mod_private_odbc,mod_private]) of
{ok, M} ->
FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
IQ = iq_to_old_iq(#iq{type = set,
ns = 'jabber:iq:private',
kind = request,
iq_ns = 'jabberd:client',
payload = El}),
FullUser = exmpp_jid:make(User, Domain),
IQ = #iq{type = set,
ns = 'jabber:iq:private',
kind = request,
iq_ns = 'jabberd:client',
payload = El},
case M:process_sm_iq(FullUser, FullUser, IQ ) of
{error, _Err} ->
io:format(" ERROR.~n",[]),
@ -375,7 +337,7 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
end;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s ~n",
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
@ -389,6 +351,8 @@ populate_user(_User, _Domain, _El) ->
%%%==================================
%%%% Utilities
loaded_module(Domain,Options) when is_binary(Domain) ->
loaded_module(?BTL(Domain),Options);
loaded_module(Domain,Options) ->
LoadedModules = gen_mod:loaded_modules(Domain),
case lists:filter(fun(Module) ->
@ -398,30 +362,12 @@ loaded_module(Domain,Options) ->
[] -> {error,not_found}
end.
jid_to_old_jid(Jid) ->
{jid, to_list(exmpp_jid:node_as_list(Jid)),
to_list(exmpp_jid:domain_as_list(Jid)),
to_list(exmpp_jid:resource_as_list(Jid)),
to_list(exmpp_jid:prep_node_as_list(Jid)),
to_list(exmpp_jid:prep_domain_as_list(Jid)),
to_list(exmpp_jid:prep_resource_as_list(Jid))}.
iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) ->
{iq, to_list(ID), Type, to_list(NS), to_list(Lang),
exmpp_xml:xmlel_to_xmlelement(El)}.
to_list(L) when is_list(L) -> L;
to_list(B) when is_binary(B) -> binary_to_list(B);
to_list(undefined) -> "";
to_list(B) when is_atom(B) -> atom_to_list(B).
%%%==================================
%%%% Export server
%% @spec (Dir::string()) -> ok
export_server(Dir) ->
try_start_exmpp(),
FnT = make_filename_template(),
DFn = make_main_basefilename(Dir, FnT),
@ -447,7 +393,6 @@ export_server(Dir) ->
%% @spec (Dir::string(), Host::string()) -> ok
export_host(Dir, Host) ->
try_start_exmpp(),
FnT = make_filename_template(),
FnH = make_host_filename(FnT, Host),
export_host(Dir, FnH, Host).
@ -525,16 +470,13 @@ extract_user(Username, Host) ->
extract_user_info(roster, Username, Host) ->
case loaded_module(Host,[mod_roster_odbc,mod_roster]) of
{ok, M} ->
From = To = jlib:make_jid(Username, Host, ""),
SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []},
%%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version
IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet},
From = To = exmpp_jid:make(Username, Host, ""),
SubelGet = exmpp_xml:element(?NS_ROSTER, 'query', [], []),
IQGet = #iq{type=get, ns=?NS_ROSTER, payload=[SubelGet]},
Res = M:process_local_iq(From, To, IQGet),
%%[El] = Res#iq.payload, % this is for 3.0.0 version
{iq, _, result, _, _, Els} = Res,
case Els of
[El] -> exmpp_xml:document_to_list(El);
[] -> ""
case Res#iq.payload of
undefined -> "";
El -> exmpp_xml:document_to_list(El)
end;
_E ->
""
@ -569,16 +511,13 @@ extract_user_info(private, Username, Host) ->
extract_user_info(vcard, Username, Host) ->
case loaded_module(Host,[mod_vcard, mod_vcard_odbc, mod_vcard_odbc]) of
{ok, M} ->
From = To = jlib:make_jid(Username, Host, ""),
SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []},
%%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version
IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet},
From = To = exmpp_jid:make(Username, Host, ""),
SubelGet = exmpp_xml:element(?NS_VCARD, 'vCard', [], []),
IQGet = #iq{type=get, ns=?NS_VCARD, payload=[SubelGet]},
Res = M:process_sm_iq(From, To, IQGet),
%%[El] = Res#iq.payload, % this is for 3.0.0 version
{iq, _, result, _, _, Els} = Res,
case Els of
[El] -> exmpp_xml:document_to_list(El);
[] -> ""
case Res#iq.payload of
undefined -> "";
El -> exmpp_xml:document_to_list(El)
end;
_E ->
""
@ -590,49 +529,63 @@ extract_user_info(vcard, Username, Host) ->
%% Copied from mod_offline.erl and customized
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
mnesia_pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
%%mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
TS = now(),
Ls ++ lists:map(
fun(R) ->
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
FromString = jlib:jid_to_string(R#offline_msg.from),
Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}),
Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}),
{xmlelement, Name, Attrs3,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
true;
TimeStamp ->
TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)));
try
LUser = User,
LServer = Server,
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
TS = now(),
Ls ++ lists:map(
fun(R) ->
Packet = R#offline_msg.packet,
FromString = exmpp_jid:prep_to_list(R#offline_msg.from),
Packet2 = exmpp_xml:set_attribute(Packet, "from", FromString),
Packet3 = Packet2#xmlel{ns = ?NS_JABBER_CLIENT},
exmpp_xml:append_children(
Packet3,
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
exmpp_jid:make("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]
)
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
true;
TimeStamp ->
TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)));
_ ->
Ls
end
catch
_ ->
Ls
end.
%%%==================================
%%%% Interface with ejabberd private storage
get_user_private_mnesia(Username, Host) ->
ListNsEl = mnesia:dirty_select(private_storage,
[{#private_storage{usns={Username, Host, '$1'}, xml = '$2'},
[{#private_storage{usns={?LTB(Username), ?LTB(Host), '$1'}, xml = '$2'},
[], ['$$']}]),
Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl],
case lists:flatten(Els) of

View File

@ -70,9 +70,14 @@ start_odbc(Host) ->
%% Returns true if we have configured odbc_server for the given host
needs_odbc(Host) ->
LHost = jlib:nameprep(Host),
case ejabberd_config:get_local_option({odbc_server, LHost}) of
undefined ->
false;
_ -> true
try
LHost = exmpp_stringprep:nameprep(Host),
case ejabberd_config:get_local_option({odbc_server, LHost}) of
undefined ->
false;
_ -> true
end
catch
_ ->
false
end.

View File

@ -132,47 +132,40 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) ->
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({starttls, TLSSocket}, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
handle_call({starttls, TLSSocket}, _From, State) ->
NewXMLStreamState = do_reset_stream(State),
NewState = State#state{socket = TLSSocket,
sock_mod = tls,
xml_stream_state = NewXMLStreamState},
case tls:recv_data(TLSSocket, "") of
{ok, TLSData} ->
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{NextState, Hib} = process_data(TLSData, NewState),
{reply, ok, NextState, Hib};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
handle_call({compress, ZlibSocket}, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
#state{xml_stream_state = XMLStreamState} = State) ->
NewXMLStreamState = exmpp_xmlstream:reset(XMLStreamState),
NewState = State#state{socket = ZlibSocket,
sock_mod = ejabberd_zlib,
xml_stream_state = NewXMLStreamState},
case ejabberd_zlib:recv_data(ZlibSocket, "") of
{ok, ZlibData} ->
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
{NextState, Hib} = process_data(ZlibData, NewState),
{reply, ok, NextState, Hib};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
handle_call(reset_stream, _From,
#state{xml_stream_state = XMLStreamState,
c2s_pid = C2SPid,
max_stanza_size = MaxStanzaSize} = State) ->
close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
#state{xml_stream_state = XMLStreamState} = State) ->
NewXMLStreamState = exmpp_xmlstream:reset(XMLStreamState),
Reply = ok,
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
close_stream(State#state.xml_stream_state),
XMLStreamState = new_xmlstream(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
@ -210,21 +203,22 @@ handle_info({Tag, _TCPSocket, Data},
tls ->
case tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
{NextState, Hib} = process_data(TLSData, State),
{noreply, NextState, Hib};
{error, _Reason} ->
{stop, normal, State}
end;
ejabberd_zlib ->
case ejabberd_zlib:recv_data(Socket, Data) of
{ok, ZlibData} ->
{noreply, process_data(ZlibData, State),
?HIBERNATE_TIMEOUT};
{NextState, Hib} = process_data(ZlibData, State),
{noreply, NextState, Hib};
{error, _Reason} ->
{stop, normal, State}
end;
_ ->
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
{NextState, Hib} = process_data(Data, State),
{noreply, NextState, Hib}
end;
handle_info({Tag, _TCPSocket}, State)
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
@ -241,8 +235,8 @@ handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State),
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
{noreply, State, hibernate};
handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
@ -317,19 +311,23 @@ process_data(Data,
#state{xml_stream_state = XMLStreamState,
shaper_state = ShaperState,
c2s_pid = C2SPid} = State) ->
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
?DEBUG("Received XML on stream = ~p", [Data]),
{ok, XMLStreamState1} = exmpp_xmlstream:parse(XMLStreamState, Data),
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
if
C2SPid == undefined ->
ok;
Pause > 0 ->
erlang:start_timer(Pause, self(), activate);
true ->
activate_socket(State)
end,
State#state{xml_stream_state = XMLStreamState1,
shaper_state = NewShaperState}.
HibTimeout =
if
C2SPid == undefined ->
infinity;
Pause > 0 ->
erlang:start_timer(Pause, self(), activate),
hibernate;
true ->
activate_socket(State),
?HIBERNATE_TIMEOUT
end,
{State#state{xml_stream_state = XMLStreamState1,
shaper_state = NewShaperState}, HibTimeout}.
%% Element coming from XML parser are wrapped inside xmlstreamelement
%% When we receive directly xmlelement tuple (from a socket module
@ -344,4 +342,26 @@ element_wrapper(Element) ->
close_stream(undefined) ->
ok;
close_stream(XMLStreamState) ->
xml_stream:close(XMLStreamState).
exmpp_xml:stop_parser(exmpp_xmlstream:get_parser(XMLStreamState)),
exmpp_xmlstream:stop(XMLStreamState).
do_reset_stream(#state{xml_stream_state = undefined, c2s_pid = C2SPid, max_stanza_size = MaxStanzaSize}) ->
new_xmlstream(C2SPid, MaxStanzaSize);
do_reset_stream(#state{xml_stream_state = XMLStreamState}) ->
exmpp_xmlstream:reset(XMLStreamState).
new_xmlstream(C2SPid, MaxStanzaSize) ->
Parser = exmpp_xml:start_parser([
{names_as_atom, true},
{check_nss, xmpp},
{check_elems, xmpp},
{check_attrs, xmpp},
{max_size, MaxStanzaSize}
]),
exmpp_xmlstream:start(
{gen_fsm, C2SPid}, Parser,
[{xmlstreamstart, new}]
).

View File

@ -46,8 +46,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(route, {domain, pid, local_hint}).
-record(state, {}).
@ -63,6 +64,16 @@ start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
route(FromOld, ToOld, #xmlelement{} = PacketOld) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nROUTER: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
route(From, To, Packet);
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -76,51 +87,53 @@ register_route(Domain) ->
register_route(Domain, undefined).
register_route(Domain, LocalHint) ->
case jlib:nameprep(Domain) of
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun() ->
mnesia:write(#route{domain = LDomain,
pid = Pid,
local_hint = LocalHint})
end,
mnesia:transaction(F);
N ->
F = fun() ->
case mnesia:read({route, LDomain}) of
[] ->
mnesia:write(
#route{domain = LDomain,
pid = Pid,
local_hint = 1}),
lists:foreach(
fun(I) ->
mnesia:write(
#route{domain = LDomain,
pid = undefined,
local_hint = I})
end, lists:seq(2, N));
Rs ->
lists:any(
fun(#route{pid = undefined,
local_hint = I} = R) ->
mnesia:write(
#route{domain = LDomain,
pid = Pid,
local_hint = I}),
mnesia:delete_object(R),
true;
(_) ->
false
end, Rs)
end
end,
mnesia:transaction(F)
end
try
LDomain = exmpp_stringprep:nameprep(Domain),
LDomainB = list_to_binary(LDomain),
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun() ->
mnesia:write(#route{domain = LDomainB,
pid = Pid,
local_hint = LocalHint})
end,
mnesia:transaction(F);
N ->
F = fun() ->
case mnesia:read({route, LDomainB}) of
[] ->
mnesia:write(
#route{domain = LDomainB,
pid = Pid,
local_hint = 1}),
lists:foreach(
fun(I) ->
mnesia:write(
#route{domain = LDomainB,
pid = undefined,
local_hint = I})
end, lists:seq(2, N));
Rs ->
lists:any(
fun(#route{pid = undefined,
local_hint = I} = R) ->
mnesia:write(
#route{domain = LDomainB,
pid = Pid,
local_hint = I}),
mnesia:delete_object(R),
true;
(_) ->
false
end, Rs)
end
end,
mnesia:transaction(F)
end
catch
_ ->
erlang:error({invalid_domain, Domain})
end.
register_routes(Domains) ->
@ -129,43 +142,45 @@ register_routes(Domains) ->
end, Domains).
unregister_route(Domain) ->
case jlib:nameprep(Domain) of
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun() ->
case mnesia:match_object(
#route{domain = LDomain,
pid = Pid,
_ = '_'}) of
[R] ->
mnesia:delete_object(R);
_ ->
ok
end
end,
mnesia:transaction(F);
_ ->
F = fun() ->
case mnesia:match_object(#route{domain=LDomain,
pid = Pid,
_ = '_'}) of
[R] ->
I = R#route.local_hint,
mnesia:write(
#route{domain = LDomain,
pid = undefined,
local_hint = I}),
mnesia:delete_object(R);
_ ->
ok
end
end,
mnesia:transaction(F)
end
try
LDomain = exmpp_stringprep:nameprep(Domain),
LDomainB = list_to_binary(LDomain),
Pid = self(),
case get_component_number(LDomain) of
undefined ->
F = fun() ->
case mnesia:match_object(
#route{domain = LDomainB,
pid = Pid,
_ = '_'}) of
[R] ->
mnesia:delete_object(R);
_ ->
ok
end
end,
mnesia:transaction(F);
_ ->
F = fun() ->
case mnesia:match_object(#route{domain=LDomainB,
pid = Pid,
_ = '_'}) of
[R] ->
I = R#route.local_hint,
mnesia:write(
#route{domain = LDomainB,
pid = undefined,
local_hint = I}),
mnesia:delete_object(R);
_ ->
ok
end
end,
mnesia:transaction(F)
end
catch
_ ->
erlang:error({invalid_domain, Domain})
end.
unregister_routes(Domains) ->
@ -175,10 +190,14 @@ unregister_routes(Domains) ->
dirty_get_all_routes() ->
lists:usort(mnesia:dirty_all_keys(route)) -- ?MYHOSTS.
lists:usort(
lists:map(fun erlang:binary_to_list/1,
mnesia:dirty_all_keys(route))) -- ?MYHOSTS.
dirty_get_all_domains() ->
lists:usort(mnesia:dirty_all_keys(route)).
lists:usort(
lists:map(fun erlang:binary_to_list/1,
mnesia:dirty_all_keys(route))).
%%====================================================================
@ -236,6 +255,16 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, FromOld, ToOld, #xmlelement{} = PacketOld}, State) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nROUTER: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
handle_info({route, From, To, Packet}, State);
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -303,8 +332,8 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
case ejabberd_hooks:run_fold(filter_packet,
{OrigFrom, OrigTo, OrigPacket}, []) of
{From, To, Packet} ->
LDstDomain = To#jid.lserver,
case mnesia:dirty_read(route, LDstDomain) of
LDomain = exmpp_jid:prep_domain(To),
case mnesia:dirty_read(route, LDomain) of
[] ->
ejabberd_s2s:route(From, To, Packet);
[R] ->
@ -323,18 +352,17 @@ do_route(OrigFrom, OrigTo, OrigPacket) ->
drop
end;
Rs ->
LDstDomain = exmpp_jid:prep_domain_as_list(To),
Value = case ejabberd_config:get_local_option(
{domain_balancing, LDstDomain}) of
undefined -> now();
random -> now();
source -> jlib:jid_tolower(From);
destination -> jlib:jid_tolower(To);
source -> jlib:short_prepd_jid(From);
destination -> jlib:short_prepd_jid(To);
bare_source ->
jlib:jid_remove_resource(
jlib:jid_tolower(From));
jlib:short_prepd_bare_jid(From);
bare_destination ->
jlib:jid_remove_resource(
jlib:jid_tolower(To))
jlib:short_prepd_bare_jid(To)
end,
case get_component_number(LDstDomain) of
undefined ->
@ -401,4 +429,3 @@ update_tables() ->
false ->
ok
end.

View File

@ -49,13 +49,21 @@
%% ejabberd API
-export([get_info_s2s_connections/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_commands.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-record(s2s, {fromto, pid, key}).
-record(state, {}).
@ -69,6 +77,16 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
route(FromOld, ToOld, #xmlelement{} = PacketOld) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nS2S: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
route(From, To, Packet);
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -201,6 +219,16 @@ handle_cast(_Msg, State) ->
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
handle_info({route, FromOld, ToOld, #xmlelement{} = PacketOld}, State) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nS2S: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
handle_info({route, From, To, Packet}, State);
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -253,32 +281,30 @@ do_route(From, To, Packet) ->
case find_connection(From, To) of
{atomic, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]),
{xmlelement, Name, Attrs, Els} = Packet,
NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
#jid{lserver = MyServer} = From,
NewPacket1 = exmpp_stanza:set_sender(Packet, From),
NewPacket = exmpp_stanza:set_recipient(NewPacket1, To),
MyServer = exmpp_jid:prep_domain(From),
ejabberd_hooks:run(
s2s_send_packet,
MyServer,
[From, To, Packet]),
send_element(Pid, {xmlelement, Name, NewAttrs, Els}),
[From, To, NewPacket]),
send_element(Pid, NewPacket),
ok;
{aborted, _Reason} ->
case xml:get_tag_attr_s("type", Packet) of
"error" -> ok;
"result" -> ok;
case exmpp_stanza:get_type(Packet) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(
Packet, ?ERR_SERVICE_UNAVAILABLE),
Err = exmpp_stanza:reply_with_error(Packet,
'service-unavailable'),
ejabberd_router:route(To, From, Err)
end,
false
end.
find_connection(From, To) ->
#jid{lserver = MyServer} = From,
#jid{lserver = Server} = To,
MyServer = exmpp_jid:prep_domain_as_list(From),
Server = exmpp_jid:prep_domain_as_list(To),
FromTo = {MyServer, Server},
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
MaxS2SConnectionsNumberPerNode =
@ -331,7 +357,7 @@ choose_pid(From, Pids) ->
% Use sticky connections based on the JID of the sender (whithout
% the resource to ensure that a muc room always uses the same
% connection)
Pid = lists:nth(erlang:phash(jlib:jid_remove_resource(From), length(Pids1)),
Pid = lists:nth(erlang:phash(exmpp_jid:bare(From), length(Pids1)),
Pids1),
?DEBUG("Using ejabberd_s2s_out ~p~n", [Pid]),
Pid.
@ -382,14 +408,14 @@ new_connection(MyServer, Server, From, FromTo,
max_s2s_connections_number({From, To}) ->
case acl:match_rule(
From, max_s2s_connections, jlib:make_jid("", To, "")) of
From, max_s2s_connections, exmpp_jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER
end.
max_s2s_connections_number_per_node({From, To}) ->
case acl:match_rule(
From, max_s2s_connections_per_node, jlib:make_jid("", To, "")) of
From, max_s2s_connections_per_node, exmpp_jid:make(To)) of
Max when is_integer(Max) -> Max;
_ -> ?DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE
end.
@ -406,12 +432,12 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
%% service.
%% --------------------------------------------------------------------
is_service(From, To) ->
LFromDomain = From#jid.lserver,
LFromDomain = exmpp_jid:prep_domain_as_list(From),
case ejabberd_config:get_local_option({route_subdomains, LFromDomain}) of
s2s -> % bypass RFC 3920 10.3
false;
_ ->
LDstDomain = To#jid.lserver,
LDstDomain = exmpp_jid:prep_domain_as_list(To),
P = fun(Domain) -> is_subdomain(LDstDomain, Domain) end,
lists:any(P, ?MYHOSTS)
end.

View File

@ -46,19 +46,13 @@
handle_info/3,
terminate/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-ifdef(SSL39).
-include("XmppAddr.hrl").
-include_lib("ssl/include/ssl_pkix.hrl").
-define(PKIXEXPLICIT, 'OTP-PKIX').
-define(PKIXIMPLICIT, 'OTP-PKIX').
-else.
-include_lib("ssl/include/PKIX1Explicit88.hrl").
-include_lib("ssl/include/PKIX1Implicit88.hrl").
-define(PKIXEXPLICIT, 'PKIX1Explicit88').
-define(PKIXIMPLICIT, 'PKIX1Implicit88').
-endif.
-include("XmppAddr.hrl").
-define(DICT, dict).
@ -92,28 +86,12 @@
[SockData, Opts])).
-endif.
-define(STREAM_HEADER(Version),
("<?xml version='1.0'?>"
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:server' "
"xmlns:db='jabber:server:dialback' "
"id='" ++ StateData#state.streamid ++ "'" ++ Version ++ ">")
).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_NAMESPACE_ERR,
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
xml:element_to_string(?SERR_HOST_UNKNOWN)).
-define(INVALID_FROM_ERR,
xml:element_to_string(?SERR_INVALID_FROM)).
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_SERVER).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
%%%----------------------------------------------------------------------
%%% API
@ -174,13 +152,16 @@ init([{SockMod, Socket}, Opts]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case {xml:get_attr_s("xmlns", Attrs),
xml:get_attr_s("xmlns:db", Attrs),
xml:get_attr_s("version", Attrs) == "1.0"} of
{"jabber:server", _, true} when
wait_for_stream({xmlstreamstart, Opening}, StateData) ->
case {exmpp_stream:get_default_ns(Opening),
exmpp_xml:is_ns_declared_here(Opening, ?NS_DIALBACK),
exmpp_stream:get_version(Opening) == {1, 0}} of
{?NS_JABBER_SERVER, _, true} when
StateData#state.tls and (not StateData#state.authenticated) ->
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
Opening_Reply = exmpp_stream:opening_reply(Opening,
StateData#state.streamid),
send_element(StateData,
exmpp_stream:set_dialback_support(Opening_Reply)),
SASL =
if
StateData#state.tls_enabled ->
@ -190,10 +171,8 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case (StateData#state.sockmod):get_verify_result(
StateData#state.socket) of
0 ->
[{xmlelement, "mechanisms",
[{"xmlns", ?NS_SASL}],
[{xmlelement, "mechanism", [],
[{xmlcdata, "EXTERNAL"}]}]}];
[exmpp_server_sasl:feature(
["EXTERNAL"])];
_ ->
[]
end;
@ -207,30 +186,35 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
StateData#state.tls_enabled ->
[];
true ->
[{xmlelement, "starttls",
[{"xmlns", ?NS_TLS}], []}]
[exmpp_server_tls:feature()]
end,
send_element(StateData,
{xmlelement, "stream:features", [],
SASL ++ StartTLS}),
send_element(StateData, exmpp_stream:features(SASL ++ StartTLS)),
{next_state, wait_for_feature_request, StateData};
{"jabber:server", _, true} when
{?NS_JABBER_SERVER, _, true} when
StateData#state.authenticated ->
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
Opening_Reply = exmpp_stream:opening_reply(Opening,
StateData#state.streamid),
send_element(StateData,
{xmlelement, "stream:features", [], []}),
exmpp_stream:set_dialback_support(Opening_Reply)),
send_element(StateData, exmpp_stream:features([])),
{next_state, stream_established, StateData};
{"jabber:server", "jabber:server:dialback", _} ->
send_text(StateData, ?STREAM_HEADER("")),
{?NS_JABBER_SERVER, true, _} ->
Opening_Reply = exmpp_stream:opening_reply(Opening,
StateData#state.streamid),
send_element(StateData,
exmpp_stream:set_dialback_support(Opening_Reply)),
{next_state, stream_established, StateData};
_ ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
send_element(StateData, exmpp_stream:error('invalid-namespace')),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?STREAM_HEADER("") ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
Opening_Reply = exmpp_stream:opening_reply(undefined, ?NS_JABBER_SERVER,
"", StateData#state.streamid),
send_element(StateData, Opening_Reply),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_stream(timeout, StateData) ->
@ -241,67 +225,64 @@ wait_for_stream(closed, StateData) ->
wait_for_feature_request({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, Attrs, Els} = El,
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
SockMod = (StateData#state.sockmod):get_sockmod(StateData#state.socket),
case {xml:get_attr_s("xmlns", Attrs), Name} of
{?NS_TLS, "starttls"} when TLS == true,
case El of
#xmlel{ns = ?NS_TLS, name = 'starttls'} when TLS == true,
TLSEnabled == false,
SockMod == gen_tcp ->
?DEBUG("starttls", []),
Socket = StateData#state.socket,
Proceed = exmpp_xml:node_to_list(
exmpp_server_tls:proceed(), [?DEFAULT_NS], ?PREFIXED_NS),
TLSOpts = StateData#state.tls_options,
TLSSocket = (StateData#state.sockmod):starttls(
Socket, TLSOpts,
xml:element_to_string(
{xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []})),
Proceed),
{next_state, wait_for_stream,
StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
}};
{?NS_SASL, "auth"} when TLSEnabled ->
Mech = xml:get_attr_s("mechanism", Attrs),
case Mech of
"EXTERNAL" ->
Auth = jlib:decode_base64(xml:get_cdata(Els)),
AuthDomain = jlib:nameprep(Auth),
AuthRes =
case (StateData#state.sockmod):get_peer_certificate(
#xmlel{ns = ?NS_SASL, name = 'auth'} when TLSEnabled ->
case exmpp_server_sasl:next_step(El) of
{auth, "EXTERNAL", Auth} ->
{AuthDomain, AuthRes} = try
AuthDomain0 = exmpp_stringprep:nameprep(Auth),
AuthRes0 = case (StateData#state.sockmod):get_peer_certificate(
StateData#state.socket) of
{ok, Cert} ->
case (StateData#state.sockmod):get_verify_result(
StateData#state.socket) of
0 ->
case AuthDomain of
error ->
case idna:domain_utf8_to_ascii(AuthDomain0) of
false ->
false;
_ ->
case idna:domain_utf8_to_ascii(AuthDomain) of
false ->
false;
PCAuthDomain ->
lists:any(
fun(D) ->
match_domain(
PCAuthDomain, D)
end, get_cert_domains(Cert))
end
PCAuthDomain ->
lists:any(
fun(D) ->
match_domain(
PCAuthDomain, D)
end, get_cert_domains(Cert))
end;
_ ->
false
end;
error ->
false
{undefined, false}
end,
{AuthDomain0, AuthRes0}
catch
_ ->
false
end,
if
AuthRes ->
(StateData#state.sockmod):reset_stream(
StateData#state.socket),
send_element(StateData,
{xmlelement, "success",
[{"xmlns", ?NS_SASL}], []}),
exmpp_server_sasl:success()),
?DEBUG("(~w) Accepted s2s authentication for ~s",
[StateData#state.socket, AuthDomain]),
{next_state, wait_for_stream,
@ -311,16 +292,14 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
}};
true ->
send_element(StateData,
{xmlelement, "failure",
[{"xmlns", ?NS_SASL}], []}),
send_text(StateData, ?STREAM_TRAILER),
exmpp_server_sasl:failure()),
send_element(StateData,
exmpp_stream:closing()),
{stop, normal, StateData}
end;
_ ->
send_element(StateData,
{xmlelement, "failure",
[{"xmlns", ?NS_SASL}],
[{xmlelement, "invalid-mechanism", [], []}]}),
exmpp_server_sasl:failure('invalid-mechanism')),
{stop, normal, StateData}
end;
_ ->
@ -328,11 +307,12 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) ->
end;
wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
send_text(StateData, ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _}, StateData) ->
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
@ -345,8 +325,8 @@ stream_established({xmlstreamelement, El}, StateData) ->
case is_key_packet(El) of
{key, To, From, Id, Key} ->
?DEBUG("GET KEY: ~p", [{To, From, Id, Key}]),
LTo = jlib:nameprep(To),
LFrom = jlib:nameprep(From),
LTo = exmpp_stringprep:nameprep(To),
LFrom = exmpp_stringprep:nameprep(From),
%% Checks if the from domain is allowed and if the to
%% domain is handled by this server:
case {ejabberd_s2s:allow_host(To, From),
@ -358,49 +338,53 @@ stream_established({xmlstreamelement, El}, StateData) ->
Key, StateData#state.streamid}),
Conns = ?DICT:store({LFrom, LTo}, wait_for_verification,
StateData#state.connections),
change_shaper(StateData, LTo, jlib:make_jid("", LFrom, "")),
change_shaper(StateData, LTo,
exmpp_jid:make(LFrom)),
{next_state,
stream_established,
StateData#state{connections = Conns,
timer = Timer}};
{_, false} ->
send_text(StateData, ?HOST_UNKNOWN_ERR),
send_element(StateData, exmpp_stream:error('host-unknown')),
{stop, normal, StateData};
{false, _} ->
send_text(StateData, ?INVALID_FROM_ERR),
send_element(StateData, exmpp_stream:error('invalid-from')),
{stop, normal, StateData}
end;
{verify, To, From, Id, Key} ->
?DEBUG("VERIFY KEY: ~p", [{To, From, Id, Key}]),
LTo = jlib:nameprep(To),
LFrom = jlib:nameprep(From),
Type = case ejabberd_s2s:has_key({LTo, LFrom}, Key) of
true -> "valid";
_ -> "invalid"
end,
%Type = if Key == Key1 -> "valid";
% true -> "invalid"
% end,
send_element(StateData,
{xmlelement,
"db:verify",
[{"from", To},
{"to", From},
{"id", Id},
{"type", Type}],
[]}),
LTo = exmpp_stringprep:nameprep(To),
LFrom = exmpp_stringprep:nameprep(From),
send_element(StateData, exmpp_dialback:verify_response(
El, ejabberd_s2s:has_key({LTo, LFrom}, Key))),
{next_state, stream_established, StateData#state{timer = Timer}};
_ ->
NewEl = jlib:remove_attr("xmlns", El),
{xmlelement, Name, Attrs, _Els} = NewEl,
From_s = xml:get_attr_s("from", Attrs),
From = jlib:string_to_jid(From_s),
To_s = xml:get_attr_s("to", Attrs),
To = jlib:string_to_jid(To_s),
From = case exmpp_stanza:get_sender(El) of
undefined ->
error;
F ->
try
exmpp_jid:parse(F)
catch
_Exception1 -> error
end
end,
To = case exmpp_stanza:get_recipient(El) of
undefined ->
error;
T ->
try
exmpp_jid:parse(T)
catch
_Exception2 -> error
end
end,
% No namespace conversion (:server <-> :client) is done.
% This is handled by C2S and S2S send_element functions.
if
(To /= error) and (From /= error) ->
LFrom = From#jid.lserver,
LTo = To#jid.lserver,
LFrom = exmpp_jid:prep_domain_as_list(From),
LTo = exmpp_jid:prep_domain_as_list(To),
if
StateData#state.authenticated ->
case (LFrom == StateData#state.auth_domain)
@ -409,15 +393,16 @@ stream_established({xmlstreamelement, El}, StateData) ->
LTo,
ejabberd_router:dirty_get_all_domains()) of
true ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
Name = El#xmlel.name,
if ((Name == 'iq') or
(Name == 'message') or
(Name == 'presence')) ->
ejabberd_hooks:run(
s2s_receive_packet,
LTo,
[From, To, NewEl]),
exmpp_jid:prep_domain(To),
[From, To, El]),
ejabberd_router:route(
From, To, NewEl);
From, To, El);
true ->
error
end;
@ -428,15 +413,16 @@ stream_established({xmlstreamelement, El}, StateData) ->
case ?DICT:find({LFrom, LTo},
StateData#state.connections) of
{ok, established} ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
Name = El#xmlel.name,
if ((Name == 'iq') or
(Name == 'message') or
(Name == 'presence')) ->
ejabberd_hooks:run(
s2s_receive_packet,
LTo,
[From, To, NewEl]),
exmpp_jid:prep_domain(To),
[From, To, El]),
ejabberd_router:route(
From, To, NewEl);
From, To, El);
true ->
error
end;
@ -452,30 +438,19 @@ stream_established({xmlstreamelement, El}, StateData) ->
end;
stream_established({valid, From, To}, StateData) ->
send_element(StateData,
{xmlelement,
"db:result",
[{"from", To},
{"to", From},
{"type", "valid"}],
[]}),
LFrom = jlib:nameprep(From),
LTo = jlib:nameprep(To),
send_element(StateData, exmpp_dialback:validate(From, To)),
LFrom = exmpp_stringprep:nameprep(From),
LTo = exmpp_stringprep:nameprep(To),
NSD = StateData#state{
connections = ?DICT:store({LFrom, LTo}, established,
StateData#state.connections)},
{next_state, stream_established, NSD};
stream_established({invalid, From, To}, StateData) ->
send_element(StateData,
{xmlelement,
"db:result",
[{"from", To},
{"to", From},
{"type", "invalid"}],
[]}),
LFrom = jlib:nameprep(From),
LTo = jlib:nameprep(To),
Valid = exmpp_dialback:validate(From, To),
send_element(StateData, exmpp_stanza:set_type(Valid, "invalid")),
LFrom = exmpp_stringprep:nameprep(From),
LTo = exmpp_stringprep:nameprep(To),
NSD = StateData#state{
connections = ?DICT:erase({LFrom, LTo},
StateData#state.connections)},
@ -485,8 +460,8 @@ stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
stream_established(timeout, StateData) ->
@ -608,8 +583,11 @@ terminate(Reason, _StateName, StateData) ->
send_text(StateData, Text) ->
(StateData#state.sockmod):send(StateData#state.socket, Text).
send_element(StateData, #xmlel{ns = ?NS_XMPP, name = 'stream'} = El) ->
send_text(StateData, exmpp_stream:to_iolist(El));
send_element(StateData, El) ->
send_text(StateData, xml:element_to_string(El)).
send_text(StateData, exmpp_stanza:to_iolist(El)).
change_shaper(StateData, Host, JID) ->
@ -630,18 +608,20 @@ cancel_timer(Timer) ->
end.
is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" ->
is_key_packet(#xmlel{ns = ?NS_DIALBACK, name = 'result',
attrs = Attrs} = El) ->
{key,
xml:get_attr_s("to", Attrs),
xml:get_attr_s("from", Attrs),
xml:get_attr_s("id", Attrs),
xml:get_cdata(Els)};
is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:verify" ->
binary_to_list(exmpp_stanza:get_recipient_from_attrs(Attrs)),
binary_to_list(exmpp_stanza:get_sender_from_attrs(Attrs)),
exmpp_stanza:get_id_from_attrs(Attrs),
exmpp_xml:get_cdata_as_list(El)};
is_key_packet(#xmlel{ns = ?NS_DIALBACK, name = 'verify',
attrs = Attrs} = El) ->
{verify,
xml:get_attr_s("to", Attrs),
xml:get_attr_s("from", Attrs),
xml:get_attr_s("id", Attrs),
xml:get_cdata(Els)};
binary_to_list(exmpp_stanza:get_recipient_from_attrs(Attrs)),
binary_to_list(exmpp_stanza:get_sender_from_attrs(Attrs)),
exmpp_stanza:get_id_from_attrs(Attrs),
exmpp_xml:get_cdata_as_list(El)};
is_key_packet(_) ->
false.
@ -663,10 +643,11 @@ get_cert_domains(Cert) ->
end,
if
D /= error ->
case jlib:string_to_jid(D) of
#jid{luser = "",
lserver = LD,
lresource = ""} ->
JID = exmpp_jid:parse(D),
case {exmpp_jid:prep_node_as_list(JID),
exmpp_jid:prep_domain_as_list(JID),
exmpp_jid:prep_resource_as_list(JID)} of
{undefined, LD, undefined} ->
[LD];
_ ->
[]
@ -698,11 +679,11 @@ get_cert_domains(Cert) ->
case 'XmppAddr':decode(
'XmppAddr', XmppAddr) of
{ok, D} when is_binary(D) ->
case jlib:string_to_jid(
binary_to_list(D)) of
#jid{luser = "",
lserver = LD,
lresource = ""} ->
JID2 = exmpp_jid:parse(binary_to_list(D)),
case {exmpp_jid:prep_node_as_list(JID2),
exmpp_jid:prep_domain_as_list(JID2),
exmpp_jid:prep_resource_as_list(JID2)} of
{ undefined, LD, undefined} ->
case idna:domain_utf8_to_ascii(LD) of
false ->
[];
@ -716,10 +697,11 @@ get_cert_domains(Cert) ->
[]
end;
({dNSName, D}) when is_list(D) ->
case jlib:string_to_jid(D) of
#jid{luser = "",
lserver = LD,
lresource = ""} ->
JID3 = exmpp_jid:parse(D),
case {exmpp_jid:prep_node_as_list(JID3),
exmpp_jid:prep_domain_as_list(JID3),
exmpp_jid:prep_resource_as_list(JID3)} of
{undefined, LD, undefined} ->
[LD];
_ ->
[]
@ -753,11 +735,11 @@ match_labels([DL | DLabels], [PL | PLabels]) ->
orelse (C == $-) orelse (C == $*)
end, PL) of
true ->
Regexp = regexp:sh_to_awk(PL),
case regexp:match(DL, Regexp) of
{match, _, _} ->
Regexp = xmerl_regexp:sh_to_awk(PL),
case re:run(DL, Regexp, [{capture, none}]) of
match ->
match_labels(DLabels, PLabels);
_ ->
nomatch ->
false
end;
false ->

View File

@ -55,8 +55,9 @@
test_get_addr_port/1,
get_addr_port/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {socket,
streamid,
@ -73,7 +74,7 @@
new = false, verify = false,
timer}).
%%-define(DBGFSM, true).
%-define(DBGFSM, true).
-ifdef(DBGFSM).
-define(FSMOPTS, [{debug, [trace]}]).
@ -99,25 +100,13 @@
%% Specified in miliseconds. Default value is 5 minutes.
-define(MAX_RETRY_DELAY, 300000).
-define(STREAM_HEADER,
"<?xml version='1.0'?>"
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:server' "
"xmlns:db='jabber:server:dialback' "
"to='~s'~s>"
).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_SERVER).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_NAMESPACE_ERR,
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
-define(HOST_UNKNOWN_ERR,
xml:element_to_string(?SERR_HOST_UNKNOWN)).
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
@ -212,16 +201,19 @@ open_socket(init, StateData) ->
{ok, Socket} ->
Version = if
StateData#state.use_v10 ->
" version='1.0'";
"1.0";
true ->
""
end,
NewStateData = StateData#state{socket = Socket,
tls_enabled = false,
streamid = new_id()},
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
[StateData#state.server,
Version])),
Opening = exmpp_stream:opening(
StateData#state.server,
?NS_JABBER_SERVER,
Version),
send_element(NewStateData,
exmpp_stream:set_dialback_support(Opening)),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
{error, _Reason} ->
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
@ -287,28 +279,29 @@ open_socket2(Type, Addr, Port) ->
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case {xml:get_attr_s("xmlns", Attrs),
xml:get_attr_s("xmlns:db", Attrs),
xml:get_attr_s("version", Attrs) == "1.0"} of
{"jabber:server", "jabber:server:dialback", false} ->
wait_for_stream({xmlstreamstart, Opening}, StateData) ->
case {exmpp_stream:get_default_ns(Opening),
exmpp_xml:is_ns_declared_here(Opening, ?NS_DIALBACK),
exmpp_stream:get_version(Opening) == {1, 0}} of
{?NS_JABBER_SERVER, true, false} ->
send_db_request(StateData);
{"jabber:server", "jabber:server:dialback", true} when
{?NS_JABBER_SERVER, true, true} when
StateData#state.use_v10 ->
{next_state, wait_for_features, StateData, ?FSMTIMEOUT};
{"jabber:server", "", true} when StateData#state.use_v10 ->
{?NS_JABBER_SERVER, false, true} when StateData#state.use_v10 ->
{next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT};
{NSProvided, _, _} ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace).~n"
"Namespace provided: ~p~nNamespace expected: \"jabber:server\"",
[StateData#state.myname, StateData#state.server, NSProvided]),
send_element(StateData, exmpp_stream:error('invalid-namespace')),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace)~n"
"Namespace provided: ~p~nNamespace expected: ~p",
[StateData#state.myname, StateData#state.server,
NSProvided, ?NS_JABBER_SERVER]),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid xml)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
@ -392,8 +385,8 @@ wait_for_validation({xmlstreamend, _Name}, StateData) ->
wait_for_validation({xmlstreamerror, _}, StateData) ->
?INFO_MSG("wait for validation: ~s -> ~s (xmlstreamerror)",
[StateData#state.myname, StateData#state.server]),
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData)
@ -417,41 +410,37 @@ wait_for_validation(closed, StateData) ->
wait_for_features({xmlstreamelement, El}, StateData) ->
case El of
{xmlelement, "stream:features", _Attrs, Els} ->
#xmlel{ns = ?NS_XMPP, name = 'features'} = Features ->
{SASLEXT, StartTLS, StartTLSRequired} =
lists:foldl(
fun({xmlelement, "mechanisms", Attrs1, Els1} = _El1,
fun(#xmlel{ns = ?NS_SASL, name = 'mechanisms'},
{_SEXT, STLS, STLSReq} = Acc) ->
case xml:get_attr_s("xmlns", Attrs1) of
?NS_SASL ->
NewSEXT =
lists:any(
fun({xmlelement, "mechanism", _, Els2}) ->
case xml:get_cdata(Els2) of
"EXTERNAL" -> true;
_ -> false
end;
(_) -> false
end, Els1),
{NewSEXT, STLS, STLSReq};
_ ->
try
Mechs = exmpp_client_sasl:announced_mechanisms(
El),
NewSEXT = lists:member("EXTERNAL", Mechs),
{NewSEXT, STLS, STLSReq}
catch
_Exception ->
Acc
end;
({xmlelement, "starttls", Attrs1, _Els1} = El1,
(#xmlel{ns = ?NS_TLS, name ='starttls'},
{SEXT, _STLS, _STLSReq} = Acc) ->
case xml:get_attr_s("xmlns", Attrs1) of
?NS_TLS ->
Req = case xml:get_subtag(El1, "required") of
{xmlelement, _, _, _} -> true;
false -> false
end,
{SEXT, true, Req};
_ ->
try
Support = exmpp_client_tls:announced_support(
El),
case Support of
none -> Acc;
optional -> {SEXT, true, false};
required -> {SEXT, true, true}
end
catch
_Exception ->
Acc
end;
(_, Acc) ->
Acc
end, {false, false, false}, Els),
end, {false, false, false}, Features#xmlel.children),
if
(not SASLEXT) and (not StartTLS) and
StateData#state.authenticated ->
@ -466,19 +455,14 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
SASLEXT and StateData#state.try_auth and
(StateData#state.new /= false) ->
send_element(StateData,
{xmlelement, "auth",
[{"xmlns", ?NS_SASL},
{"mechanism", "EXTERNAL"}],
[{xmlcdata,
jlib:encode_base64(
StateData#state.myname)}]}),
exmpp_client_sasl:selected_mechanism("EXTERNAL",
StateData#state.myname)),
{next_state, wait_for_auth_result,
StateData#state{try_auth = false}, ?FSMTIMEOUT};
StartTLS and StateData#state.tls and
(not StateData#state.tls_enabled) ->
send_element(StateData,
{xmlelement, "starttls",
[{"xmlns", ?NS_TLS}], []}),
exmpp_client_tls:starttls()),
{next_state, wait_for_starttls_proceed, StateData,
?FSMTIMEOUT};
StartTLSRequired and (not StateData#state.tls) ->
@ -499,9 +483,8 @@ wait_for_features({xmlstreamelement, El}, StateData) ->
use_v10 = false}, ?FSMTIMEOUT}
end;
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('bad-format')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
@ -512,8 +495,8 @@ wait_for_features({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_features({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("wait for features: xmlstreamerror", []),
{stop, normal, StateData};
@ -528,48 +511,29 @@ wait_for_features(closed, StateData) ->
wait_for_auth_result({xmlstreamelement, El}, StateData) ->
case El of
{xmlelement, "success", Attrs, _Els} ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_SASL ->
?DEBUG("auth: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:reset_stream(StateData#state.socket),
send_text(StateData,
io_lib:format(?STREAM_HEADER,
[StateData#state.server,
" version='1.0'"])),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true
}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
{xmlelement, "failure", Attrs, _Els} ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_SASL ->
?DEBUG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:close(StateData#state.socket),
{next_state, reopen_socket,
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
#xmlel{ns = ?NS_SASL, name = 'success'} ->
?DEBUG("auth: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:reset_stream(StateData#state.socket),
Opening = exmpp_stream:opening(
StateData#state.server,
?NS_JABBER_SERVER,
"1.0"),
send_element(StateData,
exmpp_stream:set_dialback_support(Opening)),
{next_state, wait_for_stream,
StateData#state{streamid = new_id(),
authenticated = true
}, ?FSMTIMEOUT};
#xmlel{ns = ?NS_SASL, name = 'failure'} ->
?DEBUG("restarted: ~p", [{StateData#state.myname,
StateData#state.server}]),
ejabberd_socket:close(StateData#state.socket),
{next_state, reopen_socket,
StateData#state{socket = undefined}, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('bad-format')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
@ -580,8 +544,8 @@ wait_for_auth_result({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_auth_result({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("wait for auth result: xmlstreamerror", []),
{stop, normal, StateData};
@ -596,42 +560,36 @@ wait_for_auth_result(closed, StateData) ->
wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
case El of
{xmlelement, "proceed", Attrs, _Els} ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_TLS ->
?DEBUG("starttls: ~p", [{StateData#state.myname,
StateData#state.server}]),
Socket = StateData#state.socket,
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile,
StateData#state.server}) of
undefined ->
StateData#state.tls_options;
CertFile ->
[{certfile, CertFile} |
lists:keydelete(
certfile, 1,
StateData#state.tls_options)]
end,
TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
NewStateData = StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
},
send_text(NewStateData,
io_lib:format(?STREAM_HEADER,
[StateData#state.server,
" version='1.0'"])),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
_ ->
send_text(StateData,
xml:element_to_string(?SERR_BAD_FORMAT) ++
?STREAM_TRAILER),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
end;
#xmlel{ns = ?NS_TLS, name = 'proceed'} ->
?DEBUG("starttls: ~p", [{StateData#state.myname,
StateData#state.server}]),
Socket = StateData#state.socket,
TLSOpts = case ejabberd_config:get_local_option(
{domain_certfile,
StateData#state.server}) of
undefined ->
StateData#state.tls_options;
CertFile ->
[{certfile, CertFile} |
lists:keydelete(
certfile, 1,
StateData#state.tls_options)]
end,
TLSSocket = ejabberd_socket:starttls(Socket, TLSOpts),
NewStateData = StateData#state{socket = TLSSocket,
streamid = new_id(),
tls_enabled = true
},
Opening = exmpp_stream:opening(
StateData#state.server,
?NS_JABBER_SERVER,
"1.0"),
send_element(NewStateData,
exmpp_stream:set_dialback_support(Opening)),
{next_state, wait_for_stream, NewStateData, ?FSMTIMEOUT};
_ ->
send_element(StateData, exmpp_stream:error('bad-format')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("Closing s2s connection: ~s -> ~s (bad format)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData}
@ -642,8 +600,8 @@ wait_for_starttls_proceed({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_starttls_proceed({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("wait for starttls proceed: xmlstreamerror", []),
{stop, normal, StateData};
@ -706,8 +664,8 @@ stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData,
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
?INFO_MSG("stream established: ~s -> ~s (xmlstreamerror)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
@ -821,7 +779,7 @@ handle_info({send_element, El}, StateName, StateData) ->
%% In this state we bounce all message: We are waiting before
%% trying to reconnect
wait_before_retry ->
bounce_element(El, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_element(El, 'remote-server-not-found'),
{next_state, StateName, StateData};
_ ->
Q = queue:in(El, StateData#state.queue),
@ -863,8 +821,8 @@ terminate(Reason, StateName, StateData) ->
{StateData#state.myname, StateData#state.server}, self(), Key)
end,
%% bounce queue manage by process and Erlang message queue
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_queue(StateData#state.queue, 'remote-server-not-found'),
bounce_messages('remote-server-not-found'),
case StateData#state.socket of
undefined ->
ok;
@ -880,8 +838,10 @@ terminate(Reason, StateName, StateData) ->
send_text(StateData, Text) ->
ejabberd_socket:send(StateData#state.socket, Text).
send_element(StateData, #xmlel{ns = ?NS_XMPP, name = 'stream'} = El) ->
send_text(StateData, exmpp_stream:to_iolist(El));
send_element(StateData, El) ->
send_text(StateData, xml:element_to_string(El)).
send_text(StateData, exmpp_stanza:to_iolist(El)).
send_queue(StateData, Q) ->
case queue:out(Q) of
@ -892,24 +852,25 @@ send_queue(StateData, Q) ->
ok
end.
%% Bounce a single message (xmlelement)
bounce_element(El, Error) ->
{xmlelement, _Name, Attrs, _SubTags} = El,
case xml:get_attr_s("type", Attrs) of
"error" -> ok;
"result" -> ok;
%% Bounce a single message (xmlel)
bounce_element(El, Condition) ->
case exmpp_stanza:get_type(El) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
Err = jlib:make_error_reply(El, Error),
From = jlib:string_to_jid(xml:get_tag_attr_s("from", El)),
To = jlib:string_to_jid(xml:get_tag_attr_s("to", El)),
Err = exmpp_stanza:reply_with_error(El, Condition),
From = exmpp_jid:parse(exmpp_stanza:get_sender(El)),
To = exmpp_jid:parse(exmpp_stanza:get_recipient(El)),
% No namespace conversion (:server <-> :client) is done.
% This is handled by C2S and S2S send_element functions.
ejabberd_router:route(To, From, Err)
end.
bounce_queue(Q, Error) ->
bounce_queue(Q, Condition) ->
case queue:out(Q) of
{{value, El}, Q1} ->
bounce_element(El, Error),
bounce_queue(Q1, Error);
bounce_element(El, Condition),
bounce_queue(Q1, Condition);
{empty, _} ->
ok
end.
@ -926,11 +887,11 @@ cancel_timer(Timer) ->
ok
end.
bounce_messages(Error) ->
bounce_messages(Condition) ->
receive
{send_element, El} ->
bounce_element(El, Error),
bounce_messages(Error)
bounce_element(El, Condition),
bounce_messages(Condition)
after 0 ->
ok
end.
@ -954,40 +915,33 @@ send_db_request(StateData) ->
false ->
ok;
Key1 ->
send_element(StateData,
{xmlelement,
"db:result",
[{"from", StateData#state.myname},
{"to", Server}],
[{xmlcdata, Key1}]})
send_element(StateData, exmpp_dialback:key(
StateData#state.myname, Server, Key1))
end,
case StateData#state.verify of
false ->
ok;
{_Pid, Key2, SID} ->
send_element(StateData,
{xmlelement,
"db:verify",
[{"from", StateData#state.myname},
{"to", StateData#state.server},
{"id", SID}],
[{xmlcdata, Key2}]})
send_element(StateData, exmpp_dialback:verify_request(
StateData#state.myname, StateData#state.server, SID, Key2))
end,
{next_state, wait_for_validation, StateData#state{new = New}, ?FSMTIMEOUT*6}.
is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:result" ->
is_verify_res(#xmlel{ns = ?NS_DIALBACK, name = 'result',
attrs = Attrs}) ->
{result,
xml:get_attr_s("to", Attrs),
xml:get_attr_s("from", Attrs),
xml:get_attr_s("id", Attrs),
xml:get_attr_s("type", Attrs)};
is_verify_res({xmlelement, Name, Attrs, _Els}) when Name == "db:verify" ->
exmpp_stanza:get_recipient_from_attrs(Attrs),
exmpp_stanza:get_sender_from_attrs(Attrs),
exmpp_stanza:get_id_from_attrs(Attrs),
binary_to_list(exmpp_stanza:get_type_from_attrs(Attrs))};
is_verify_res(#xmlel{ns = ?NS_DIALBACK, name = 'verify',
attrs = Attrs}) ->
{verify,
xml:get_attr_s("to", Attrs),
xml:get_attr_s("from", Attrs),
xml:get_attr_s("id", Attrs),
xml:get_attr_s("type", Attrs)};
exmpp_stanza:get_recipient_from_attrs(Attrs),
exmpp_stanza:get_sender_from_attrs(Attrs),
exmpp_stanza:get_id_from_attrs(Attrs),
binary_to_list(exmpp_stanza:get_type_from_attrs(Attrs))};
is_verify_res(_) ->
false.
@ -1157,8 +1111,8 @@ get_timeout_interval(StateName) ->
%% function that want to wait for a reconnect delay before stopping.
wait_before_reconnect(StateData) ->
%% bounce queue manage by process and Erlang message queue
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_queue(StateData#state.queue, 'remote-server-not-found'),
bounce_messages('remote-server-not-found'),
cancel_timer(StateData#state.timer),
Delay = case StateData#state.delay_to_retry of
undefined_delay ->

View File

@ -49,8 +49,9 @@
handle_info/3,
terminate/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {socket, sockmod, streamid,
hosts, password, access,
@ -64,36 +65,10 @@
-define(FSMOPTS, []).
-endif.
-define(STREAM_HEADER,
"<?xml version='1.0'?>"
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='jabber:component:accept' "
"id='~s' from='~s'>"
).
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_HEADER_ERR,
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams'>"
"<stream:error>Invalid Stream Header</stream:error>"
"</stream:stream>"
).
-define(INVALID_HANDSHAKE_ERR,
"<stream:error>"
"<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
"<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>"
"Invalid Handshake</text>"
"</stream:error>"
"</stream:stream>"
).
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
-define(INVALID_NS_ERR,
xml:element_to_string(?SERR_INVALID_NAMESPACE)).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_COMPONENT_ACCEPT).
-define(PREFIXED_NS, [{?NS_XMPP, ?NS_XMPP_pfx}]).
%%%----------------------------------------------------------------------
%%% API
@ -162,7 +137,7 @@ init([{SockMod, Socket}, Opts]) ->
{ok, wait_for_stream, #state{socket = Socket,
sockmod = SockMod,
streamid = new_id(),
hosts = Hosts,
hosts = [list_to_binary(H) || H <- Hosts],
password = Password,
access = Access,
check_from = CheckFrom
@ -175,28 +150,36 @@ init([{SockMod, Socket}, Opts]) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
case xml:get_attr_s("xmlns", Attrs) of
"jabber:component:accept" ->
wait_for_stream({xmlstreamstart, El = #xmlel{ns = _NS, attrs = Attrs}}, StateData) ->
case exmpp_xml:is_ns_declared_here(El, ?NS_COMPONENT_ACCEPT) of
true ->
%% Note: XEP-0114 requires to check that destination is a Jabber
%% component served by this Jabber server.
%% However several transports don't respect that,
%% so ejabberd doesn't check 'to' attribute (EJAB-717)
To = xml:get_attr_s("to", Attrs),
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid, xml:crypt(To)]),
send_text(StateData, Header),
To = exmpp_stanza:get_recipient_from_attrs(Attrs),
Opening_Reply = exmpp_stream:opening_reply(To,
?NS_COMPONENT_ACCEPT,
{0, 0}, StateData#state.streamid),
send_element(StateData, Opening_Reply),
{next_state, wait_for_handshake, StateData};
_ ->
send_text(StateData, ?INVALID_HEADER_ERR),
false ->
Error = #xmlel{ns = ?NS_XMPP, name = 'stream', children = [
#xmlel{ns = ?NS_XMPP, name = 'error', children = [
#xmlcdata{cdata = <<"Invalid Stream Header">>}
]}
]},
send_element(StateData, Error),
{stop, normal, StateData}
end;
wait_for_stream({xmlstreamerror, _}, StateData) ->
Header = io_lib:format(?STREAM_HEADER,
["none", ?MYNAME]),
send_text(StateData,
Header ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
Opening_Reply = exmpp_stream:opening_reply(?MYNAME,
?NS_COMPONENT_ACCEPT,
{0, 0}, "none"),
send_element(StateData, Opening_Reply),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
@ -204,21 +187,38 @@ wait_for_stream(closed, StateData) ->
wait_for_handshake({xmlstreamelement, El}, StateData) ->
{xmlelement, Name, _Attrs, Els} = El,
case {Name, xml:get_cdata(Els)} of
{"handshake", Digest} ->
case {El#xmlel.name, exmpp_xml:get_cdata_as_list(El)} of
{'handshake', Digest} ->
case sha:sha(StateData#state.streamid ++
StateData#state.password) of
Digest ->
send_text(StateData, "<handshake/>"),
send_element(StateData,
#xmlel{ns = ?NS_COMPONENT_ACCEPT, name = 'handshake'}),
lists:foreach(
fun(H) ->
ejabberd_router:register_route(H),
ejabberd_router:register_route(binary_to_list(H)),
?INFO_MSG("Route registered for service ~p~n", [H])
end, StateData#state.hosts),
{next_state, stream_established, StateData};
_ ->
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
_ ->
TextEl =
#xmlel{ns = ?NS_STANZA_ERRORS,
name = 'text',
children =
[#xmlcdata{cdata = <<"Invalid Handshake">>}]
},
NotAuthorizedEl =
#xmlel{ns = ?NS_STANZA_ERRORS,
name = 'not-authorized',
children = [TextEl]
},
InvalidHandshakeEl =
#xmlel{ns = ?NS_XMPP,
name = 'error',
children = [NotAuthorizedEl]
},
send_element(StateData, InvalidHandshakeEl),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData}
end;
_ ->
@ -229,7 +229,8 @@ wait_for_handshake({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
wait_for_handshake({xmlstreamerror, _}, StateData) ->
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
wait_for_handshake(closed, StateData) ->
@ -237,39 +238,34 @@ wait_for_handshake(closed, StateData) ->
stream_established({xmlstreamelement, El}, StateData) ->
NewEl = jlib:remove_attr("xmlns", El),
{xmlelement, Name, Attrs, _Els} = NewEl,
From = xml:get_attr_s("from", Attrs),
From = exmpp_stanza:get_sender(El),
FromJID = case StateData#state.check_from of
%% If the admin does not want to check the from field
%% when accept packets from any address.
%% In this case, the component can send packet of
%% behalf of the server users.
false -> jlib:string_to_jid(From);
false -> exmpp_jid:parse(From);
%% The default is the standard behaviour in XEP-0114
_ ->
FromJID1 = jlib:string_to_jid(From),
case FromJID1 of
#jid{lserver = Server} ->
case lists:member(Server, StateData#state.hosts) of
FromJID1 = exmpp_jid:parse(From),
Server = exmpp_jid:prep_domain(FromJID1),
case lists:member(Server, StateData#state.hosts) of
true -> FromJID1;
false -> error
end;
_ -> error
end
end
end,
To = xml:get_attr_s("to", Attrs),
To = exmpp_stanza:get_recipient(El),
ToJID = case To of
"" -> error;
_ -> jlib:string_to_jid(To)
undefined -> error;
_ -> exmpp_jid:parse(To)
end,
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) and
if ((El#xmlel.name == 'iq') or
(El#xmlel.name == 'message') or
(El#xmlel.name == 'presence')) and
(ToJID /= error) and (FromJID /= error) ->
ejabberd_router:route(FromJID, ToJID, NewEl);
ejabberd_router:route(FromJID, ToJID, El);
true ->
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
Err = exmpp_stanza:reply_with_error(El, 'bad-request'),
send_element(StateData, Err),
error
end,
@ -280,7 +276,8 @@ stream_established({xmlstreamend, _Name}, StateData) ->
{stop, normal, StateData};
stream_established({xmlstreamerror, _}, StateData) ->
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
send_element(StateData, exmpp_stream:error('xml-not-well-formed')),
send_element(StateData, exmpp_stream:closing()),
{stop, normal, StateData};
stream_established(closed, StateData) ->
@ -334,22 +331,21 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({send_text, Text}, StateName, StateData) ->
% XXX OLD FORMAT: This clause should be removed.
send_text(StateData, Text),
{next_state, StateName, StateData};
handle_info({send_element, El}, StateName, StateData) ->
io:format("ejabberd_service send_element ~p~n",[ El]),
send_element(StateData, El),
{next_state, StateName, StateData};
handle_info({route, From, To, Packet}, StateName, StateData) ->
case acl:match_rule(global, StateData#state.access, From) of
allow ->
{xmlelement, Name, Attrs, Els} = Packet,
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
Text = xml:element_to_string({xmlelement, Name, Attrs2, Els}),
send_text(StateData, Text);
El1 = exmpp_stanza:set_sender(Packet, From),
El2 = exmpp_stanza:set_recipient(El1, To),
send_element(StateData, El2);
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
Err = exmpp_stanza:reply_with_error(Packet, 'not-allowed'),
ejabberd_router:route(To, From, Err)
end,
{next_state, StateName, StateData}.
@ -366,7 +362,7 @@ terminate(Reason, StateName, StateData) ->
stream_established ->
lists:foreach(
fun(H) ->
ejabberd_router:unregister_route(H)
ejabberd_router:unregister_route(binary_to_list(H))
end, StateData#state.hosts);
_ ->
ok
@ -379,10 +375,13 @@ terminate(Reason, StateName, StateData) ->
%%%----------------------------------------------------------------------
send_text(StateData, Text) ->
io:format(">>~n ~s ~n", [Text]),
(StateData#state.sockmod):send(StateData#state.socket, Text).
send_element(StateData, #xmlel{ns = ?NS_XMPP, name = 'stream'} = El) ->
send_text(StateData, exmpp_stream:to_iolist(El));
send_element(StateData, El) ->
send_text(StateData, xml:element_to_string(El)).
send_text(StateData, exmpp_stanza:to_iolist(El)).
new_id() ->
randoms:get_string().

View File

@ -32,14 +32,14 @@
%% API
-export([start_link/0,
route/3,
open_session/5, close_session/4,
open_session/3, close_session/2,
check_in_subscription/6,
bounce_offline_message/3,
disconnect_removed_user/2,
get_user_resources/2,
set_presence/7,
unset_presence/6,
close_session_unset_presence/5,
set_presence/5,
unset_presence/4,
close_session_unset_presence/3,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
@ -50,17 +50,18 @@
connected_users/0,
connected_users_number/0,
user_resources/2,
get_session_pid/3,
get_session_pid/1,
get_user_info/3,
get_user_ip/3
get_user_ip/1
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_commands.hrl").
-include("mod_privacy.hrl").
@ -71,6 +72,16 @@
%% default value for the maximum number of user connections
-define(MAX_USER_SESSIONS, infinity).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [
{?NS_XMPP, ?NS_XMPP_pfx}, {?NS_DIALBACK, ?NS_DIALBACK_pfx}
]).
-define(IS_BINARY_OR_UNDEF(X),
(is_binary(X) orelse X == 'undefined')).
%%====================================================================
%% API
%%====================================================================
@ -81,6 +92,16 @@
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
route(FromOld, ToOld, #xmlelement{} = PacketOld) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nSM: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
route(From, To, Packet);
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -90,30 +111,29 @@ route(From, To, Packet) ->
ok
end.
open_session(SID, User, Server, Resource, Info) ->
set_session(SID, User, Server, Resource, undefined, Info),
inc_session_counter(jlib:nameprep(Server)),
check_for_sessions_to_replace(User, Server, Resource),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
open_session(SID, JID, Info) when ?IS_JID(JID) ->
set_session(SID, JID, undefined, Info),
inc_session_counter(exmpp_jid:domain(JID)),
check_for_sessions_to_replace(JID),
ejabberd_hooks:run(sm_register_connection_hook, exmpp_jid:prep_domain(JID),
[SID, JID, Info]).
close_session(SID, User, Server, Resource) ->
close_session(SID, JID ) when ?IS_JID(JID)->
Info = case mnesia:dirty_read({session, SID}) of
[] -> [];
[#session{info=I}] -> I
end,
F = fun() ->
mnesia:delete({session, SID}),
dec_session_counter(jlib:nameprep(Server))
dec_session_counter(exmpp_jid:domain(JID))
end,
mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
ejabberd_hooks:run(sm_remove_connection_hook, exmpp_jid:prep_domain(JID),
[SID, JID, Info]).
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason)
when is_binary(User), is_binary(Server)->
case ejabberd_auth:is_user_exists(binary_to_list(User), binary_to_list(Server)) of
true ->
Acc;
false ->
@ -121,20 +141,20 @@ check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
end.
bounce_offline_message(From, To, Packet) ->
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
Err = exmpp_stanza:reply_with_error(Packet, 'service-unavailable'),
ejabberd_router:route(To, From, Err),
stop.
disconnect_removed_user(User, Server) ->
ejabberd_sm:route(jlib:make_jid("", "", ""),
jlib:make_jid(User, Server, ""),
{xmlelement, "broadcast", [],
[{exit, "User removed"}]}).
ejabberd_sm:route(exmpp_jid:make(),
exmpp_jid:make(User,
Server),
#xmlel{name = 'broadcast',
children = [{exit, "User removed"}]}).
get_user_resources(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
get_user_resources(User, Server)
when is_binary(User), is_binary(Server) ->
US = {User, Server},
case catch mnesia:dirty_index_read(session, US, #session.us) of
{'EXIT', _Reason} ->
[];
@ -142,11 +162,10 @@ get_user_resources(User, Server) ->
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
end.
get_user_ip(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
get_user_ip(JID) when ?IS_JID(JID) ->
USR = {exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID)},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
undefined;
@ -155,11 +174,16 @@ get_user_ip(User, Server, Resource) ->
proplists:get_value(ip, Session#session.info)
end.
get_user_info(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
get_user_info(User, Server, Resource)
when is_binary(User),
is_binary(Server),
is_binary(Resource) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
LResource = exmpp_stringprep:resourceprep(Resource),
USR = {LUser,
LServer,
LResource},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
offline;
@ -171,26 +195,37 @@ get_user_info(User, Server, Resource) ->
[{node, Node}, {conn, Conn}, {ip, IP}]
end.
set_presence(SID, User, Server, Resource, Priority, Presence, Info) ->
set_session(SID, User, Server, Resource, Priority, Info),
ejabberd_hooks:run(set_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Presence]).
set_presence(SID, JID, Priority, Presence, Info) when ?IS_JID(JID) ->
set_session(SID, JID, Priority, Info),
ejabberd_hooks:run(set_presence_hook,
exmpp_jid:prep_domain(JID),
[exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID),
Presence]).
unset_presence(SID, User, Server, Resource, Status, Info) ->
set_session(SID, User, Server, Resource, undefined, Info),
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Status]).
unset_presence(SID, JID, Status, Info) when ?IS_JID(JID)->
set_session(SID, JID, undefined, Info),
ejabberd_hooks:run(unset_presence_hook,
exmpp_jid:prep_domain(JID),
[exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID),
Status]).
close_session_unset_presence(SID, User, Server, Resource, Status) ->
close_session(SID, User, Server, Resource),
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
[User, Server, Resource, Status]).
close_session_unset_presence(SID, JID, Status) when ?IS_JID(JID) ->
close_session(SID, JID),
ejabberd_hooks:run(unset_presence_hook,
exmpp_jid:prep_domain(JID),
[exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID),
Status]).
get_session_pid(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
USR = {LUser, LServer, LResource},
get_session_pid(JID) when ?IS_JID(JID) ->
USR = {exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID)},
case catch mnesia:dirty_index_read(session, USR, #session.usr) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
@ -210,8 +245,8 @@ dirty_get_my_sessions_list() ->
[{'==', {node, '$1'}, node()}],
['$_']}]).
get_vh_session_list(Server) ->
LServer = jlib:nameprep(Server),
get_vh_session_list(Server) when is_binary(Server) ->
LServer = exmpp_stringprep:nameprep(Server),
mnesia:dirty_select(
session,
[{#session{usr = '$1', _ = '_'},
@ -219,7 +254,7 @@ get_vh_session_list(Server) ->
['$1']}]).
get_vh_session_number(Server) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
Query = mnesia:dirty_select(
session_counter,
[{#session_counter{vhost = LServer, count = '$1'},
@ -267,11 +302,12 @@ init([]) ->
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(roster_in_subscription, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:add(roster_in_subscription, HostB,
ejabberd_sm, check_in_subscription, 20),
ejabberd_hooks:add(offline_message_hook, Host,
ejabberd_hooks:add(offline_message_hook, HostB,
ejabberd_sm, bounce_offline_message, 100),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
ejabberd_sm, disconnect_removed_user, 100)
end, ?MYHOSTS),
ejabberd_commands:register_commands(commands()),
@ -306,6 +342,16 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, FromOld, ToOld, #xmlelement{} = PacketOld}, State) ->
catch throw(for_stacktrace), % To have a stacktrace.
io:format("~nSM: old #xmlelement:~n~p~n~p~n~n",
[PacketOld, erlang:get_stacktrace()]),
% XXX OLD FORMAT: From, To, Packet.
From = jlib:from_old_jid(FromOld),
To = jlib:from_old_jid(ToOld),
Packet = exmpp_xml:xmlelement_to_xmlel(PacketOld, [?NS_JABBER_CLIENT],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
handle_info({route, From, To, Packet}, State);
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
@ -358,12 +404,11 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions
%%--------------------------------------------------------------------
set_session(SID, User, Server, Resource, Priority, Info) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
set_session(SID, JID, Priority, Info) ->
US = {exmpp_jid:prep_node(JID), exmpp_jid:prep_domain(JID)},
USR = {exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID)},
F = fun() ->
mnesia:write(#session{sid = SID,
usr = USR,
@ -393,95 +438,93 @@ clean_table_from_bad_node(Node) ->
do_route(From, To, Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
{xmlelement, Name, Attrs, _Els} = Packet,
case LResource of
"" ->
case Name of
"presence" ->
case exmpp_jid:prep_resource(To) of
undefined ->
case Packet of
_ when ?IS_PRESENCE(Packet) ->
{Pass, _Subsc} =
case xml:get_attr_s("type", Attrs) of
"subscribe" ->
Reason = xml:get_path_s(
Packet,
[{elem, "status"}, cdata]),
case exmpp_presence:get_type(Packet) of
'subscribe' ->
Reason = exmpp_presence:get_status(Packet),
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
exmpp_jid:prep_domain(To),
false,
[User, Server, From, subscribe, Reason]),
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), From, subscribe, Reason]),
true};
"subscribed" ->
'subscribed' ->
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
exmpp_jid:prep_domain(To),
false,
[User, Server, From, subscribed, ""]),
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), From, subscribed, <<>>]),
true};
"unsubscribe" ->
'unsubscribe' ->
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
exmpp_jid:prep_domain(To),
false,
[User, Server, From, unsubscribe, ""]),
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), From, unsubscribe, <<>>]),
true};
"unsubscribed" ->
'unsubscribed' ->
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
exmpp_jid:prep_domain(To),
false,
[User, Server, From, unsubscribed, ""]),
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), From, unsubscribed, <<>>]),
true};
_ ->
{true, false}
end,
if Pass ->
PResources = get_user_present_resources(
LUser, LServer),
exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To)),
lists:foreach(
fun({_, R}) ->
do_route(
From,
jlib:jid_replace_resource(To, R),
exmpp_jid:full(To, R),
Packet)
end, PResources);
true ->
ok
end;
"message" ->
_ when ?IS_MESSAGE(Packet) ->
route_message(From, To, Packet);
"iq" ->
_ when ?IS_IQ(Packet) ->
process_iq(From, To, Packet);
"broadcast" ->
#xmlel{name = 'broadcast'} ->
lists:foreach(
fun(R) ->
do_route(From,
jlib:jid_replace_resource(To, R),
exmpp_jid:full(To, R),
Packet)
end, get_user_resources(User, Server));
end, get_user_resources(exmpp_jid:prep_node(To),
exmpp_jid:prep_domain(To)));
_ ->
ok
end;
_ ->
USR = {LUser, LServer, LResource},
USR = {exmpp_jid:prep_node(To),
exmpp_jid:prep_domain(To),
exmpp_jid:prep_resource(To)},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
case Name of
"message" ->
case Packet of
_ when ?IS_MESSAGE(Packet) ->
route_message(From, To, Packet);
"iq" ->
case xml:get_attr_s("type", Attrs) of
"error" -> ok;
"result" -> ok;
_ when ?IS_IQ(Packet) ->
case exmpp_iq:get_type(Packet) of
'error' -> ok;
'result' -> ok;
_ ->
Err =
jlib:make_error_reply(
Packet, ?ERR_RECIPIENT_UNAVAILABLE),
exmpp_iq:error(Packet,
'service-unavailable'),
ejabberd_router:route(To, From, Err)
end;
_ ->
@ -500,8 +543,8 @@ do_route(From, To, Packet) ->
%% for the target session/resource to which a stanza is addressed,
%% or if there are no current sessions for the user.
is_privacy_allow(From, To, Packet) ->
User = To#jid.user,
Server = To#jid.server,
User = exmpp_jid:prep_node(To),
Server = exmpp_jid:prep_domain(To),
PrivacyList = ejabberd_hooks:run_fold(privacy_get_user_list, Server,
#userlist{}, [User, Server]),
is_privacy_allow(From, To, Packet, PrivacyList).
@ -509,8 +552,8 @@ is_privacy_allow(From, To, Packet) ->
%% Check if privacy rules allow this delivery
%% Function copied from ejabberd_c2s.erl
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user,
Server = To#jid.server,
User = exmpp_jid:prep_node(To),
Server = exmpp_jid:prep_domain(To),
allow == ejabberd_hooks:run_fold(
privacy_check_packet, Server,
allow,
@ -521,8 +564,8 @@ is_privacy_allow(From, To, Packet, PrivacyList) ->
in]).
route_message(From, To, Packet) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
LUser = exmpp_jid:prep_node(To),
LServer = exmpp_jid:prep_domain(To),
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
{Priority, _R} when is_integer(Priority), Priority >= 0 ->
@ -530,8 +573,7 @@ route_message(From, To, Packet) ->
%% Route messages to all priority that equals the max, if
%% positive
fun({P, R}) when P == Priority ->
LResource = jlib:resourceprep(R),
USR = {LUser, LServer, LResource},
USR = {LUser, LServer, R},
case mnesia:dirty_index_read(session, USR, #session.usr) of
[] ->
ok; % Race condition
@ -547,22 +589,23 @@ route_message(From, To, Packet) ->
end,
PrioRes);
_ ->
case xml:get_tag_attr_s("type", Packet) of
"error" ->
case exmpp_message:get_type(Packet) of
'error' ->
ok;
"groupchat" ->
'groupchat' ->
bounce_offline_message(From, To, Packet);
"headline" ->
'headline' ->
bounce_offline_message(From, To, Packet);
_ ->
case ejabberd_auth:is_user_exists(LUser, LServer) of
case ejabberd_auth:is_user_exists(exmpp_jid:prep_node_as_list(To),
exmpp_jid:prep_domain_as_list(To)) of
true ->
ejabberd_hooks:run(offline_message_hook,
LServer,
exmpp_jid:prep_domain(To),
[From, To, Packet]);
_ ->
Err = jlib:make_error_reply(
Packet, ?ERR_SERVICE_UNAVAILABLE),
Err = exmpp_stanza:reply_with_error(
Packet, 'service-unaivailable'),
ejabberd_router:route(To, From, Err)
end
end
@ -607,18 +650,16 @@ get_user_present_resources(LUser, LServer) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% On new session, check if some existing connections need to be replace
check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LResource = jlib:resourceprep(Resource),
check_for_sessions_to_replace(JID) ->
%% TODO: Depending on how this is executed, there could be an unneeded
%% replacement for max_sessions. We need to check this at some point.
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
check_existing_resources(JID),
check_max_sessions(JID).
check_existing_resources(LUser, LServer, LResource) ->
USR = {LUser, LServer, LResource},
check_existing_resources(JID) ->
USR = {exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID)},
%% A connection exist with the same resource. We replace it:
SIDs = mnesia:dirty_select(
session,
@ -634,14 +675,16 @@ check_existing_resources(LUser, LServer, LResource) ->
end, SIDs)
end.
check_max_sessions(LUser, LServer) ->
check_max_sessions(JID) ->
%% If the max number of sessions for a given is reached, we replace the
%% first one
SIDs = mnesia:dirty_select(
session,
[{#session{sid = '$1', us = {LUser, LServer}, _ = '_'}, [],
[{#session{sid = '$1',
us = {exmpp_jid:prep_node(JID), exmpp_jid:prep_domain(JID)},
_ = '_'}, [],
['$1']}]),
MaxSessions = get_max_user_sessions(LUser, LServer),
MaxSessions = get_max_user_sessions(JID),
if
length(SIDs) =< MaxSessions ->
ok;
@ -655,9 +698,9 @@ check_max_sessions(LUser, LServer) ->
%% This option defines the max number of time a given users are allowed to
%% log in
%% Defaults to infinity
get_max_user_sessions(LUser, Host) ->
get_max_user_sessions(JID) ->
case acl:match_rule(
Host, max_user_sessions, jlib:make_jid(LUser, Host, "")) of
exmpp_jid:prep_domain_as_list(JID), max_user_sessions, exmpp_jid:bare(JID)) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_SESSIONS
@ -694,32 +737,30 @@ dec_session_counter(LServer) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS} ->
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
case exmpp_iq:xmlel_to_iq(Packet) of
#iq{kind = request, ns = XMLNS} = IQ_Rec ->
LServer = exmpp_jid:prep_domain(To),
case ets:lookup(sm_iqtable, {XMLNS, LServer}) of
[{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ),
ResIQ = Module:Function(From, To, IQ_Rec),
if
ResIQ /= ignore ->
ejabberd_router:route(To, From,
jlib:iq_to_xml(ResIQ));
Reply = exmpp_iq:iq_to_xmlel(ResIQ, To, From),
ejabberd_router:route(To, From, Reply);
true ->
ok
end;
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
gen_iq_handler:handle(LServer,
Module, Function, Opts, From, To, IQ_Rec);
[] ->
Err = jlib:make_error_reply(
Packet, ?ERR_SERVICE_UNAVAILABLE),
Err = exmpp_iq:error(Packet, 'service-unavailable'),
ejabberd_router:route(To, From, Err)
end;
reply ->
#iq{kind = response} ->
ok;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
Err = exmpp_iq:error(Packet, 'bad-request'),
ejabberd_router:route(To, From, Err),
ok
end.
@ -759,7 +800,8 @@ connected_users_number() ->
length(dirty_get_sessions_list()).
user_resources(User, Server) ->
Resources = get_user_resources(User, Server),
Resources = get_user_resources(list_to_binary(User),
list_to_binary(Server)),
lists:sort(Resources).

View File

@ -38,8 +38,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {}).
@ -59,16 +60,16 @@ end,
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
process_command(From, To, Packet) ->
case To of
#jid{luser = "", lresource = "watchdog"} ->
{xmlelement, Name, _Attrs, _Els} = Packet,
case Name of
"message" ->
LFrom = jlib:jid_tolower(jlib:jid_remove_resource(From)),
case lists:member(LFrom, get_admin_jids()) of
case {exmpp_jid:prep_node(To), exmpp_jid:prep_resource(To) } of
{undefined, <<"watchdog">>} ->
case Packet#xmlel.name of
'message' ->
case lists:any(fun(J) ->
exmpp_jid:compare(J,From)
end, get_admin_jids()) of
true ->
Body = xml:get_path_s(
Packet, [{elem, "body"}, cdata]),
Body = exmpp_xml:get_path(
Packet, [{element, 'body'}, cdata_as_list]),
spawn(fun() ->
process_flag(priority, high),
process_command1(From, To, Body)
@ -101,7 +102,8 @@ init(Opts) ->
erlang:system_monitor(self(), [{large_heap, LH}]),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(local_send_to_resource_hook, Host,
ejabberd_hooks:add(local_send_to_resource_hook,
list_to_binary(Host),
?MODULE, process_command, 50)
end, ?MYHOSTS),
{ok, #state{}}.
@ -188,13 +190,15 @@ process_large_heap(Pid, Info) ->
"(~w) The process ~w is consuming too much memory:~n~p~n"
"~s",
[node(), Pid, Info, DetailedInfo]),
From = jlib:make_jid("", Host, "watchdog"),
From = exmpp_jid:make(undefined, Host, <<"watchdog">>),
lists:foreach(
fun(S) ->
case jlib:string_to_jid(S) of
error -> ok;
JID ->
send_message(From, JID, Body)
try
JID = exmpp_jid:parse(S),
send_message(From, JID, Body)
catch
_ ->
ok
end
end, JIDs);
_ ->
@ -204,18 +208,21 @@ process_large_heap(Pid, Info) ->
send_message(From, To, Body) ->
ejabberd_router:route(
From, To,
{xmlelement, "message", [{"type", "chat"}],
[{xmlelement, "body", [],
[{xmlcdata, lists:flatten(Body)}]}]}).
exmpp_message:chat(lists:flatten(Body))).
get_admin_jids() ->
case ejabberd_config:get_local_option(watchdog_admins) of
JIDs when is_list(JIDs) ->
lists:flatmap(
fun(S) ->
case jlib:string_to_jid(S) of
error -> [];
JID -> [jlib:jid_tolower(JID)]
try
JID = exmpp_jid:parse(S),
[{exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID)}]
catch
_ ->
[]
end
end, JIDs);
_ ->

View File

@ -36,8 +36,9 @@
export_vcard_search/2,
export_private_storage/2]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
@ -88,10 +89,10 @@ export_passwd(Server, Output) ->
export_roster(Server, Output) ->
export_common(
Server, roster, Output,
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
fun(Host, #roster{usj = {LUser, LServer, {N, D, Res} = _LJID}} = R)
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
SJID = ejabberd_odbc:escape(exmpp_jid:to_list(N, D, Res)),
ItemVals = record_to_string(R),
ItemGroups = groups_to_string(R),
["delete from rosterusers "
@ -123,26 +124,22 @@ export_offline(Server, Output) ->
packet = Packet})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
{xmlelement, Name, Attrs, Els} = Packet,
Attrs2 = jlib:replace_from_to_attrs(
jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
NewPacket = {xmlelement, Name, Attrs2,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(TimeStamp),
Packet0 = exmpp_stanza:set_jids(Packet,
exmpp_jid:to_list(From),
exmpp_jid:to_list(To)),
Packet0b = exmpp_xml:append_child(Packet0,
jlib:timestamp_to_xml(
calendar:now_to_universal_time(TimeStamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
TimeStamp))]},
exmpp_jid:make("", Server, ""),
"Offline Storage")),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
Packet1 = exmpp_xml:append_child(Packet0b,
jlib:timestamp_to_xml(
calendar:now_to_universal_time(TimeStamp))),
XML =
ejabberd_odbc:escape(
lists:flatten(
xml:element_to_string(NewPacket))),
exmpp_xml:document_to_list(Packet1)),
["insert into spool(username, xml) "
"values ('", Username, "', '",
XML,
@ -176,7 +173,7 @@ export_vcard(Server, Output) ->
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SVCARD = ejabberd_odbc:escape(
lists:flatten(xml:element_to_string(VCARD))),
exmpp_xml:document_to_list(VCARD)),
["delete from vcard where username='", Username, "';"
"insert into vcard(username, vcard) "
"values ('", Username, "', '", SVCARD, "');"];
@ -260,7 +257,7 @@ export_private_storage(Server, Output) ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData = ejabberd_odbc:escape(
lists:flatten(xml:element_to_string(Data))),
exmpp_xml:document_to_list(Data)),
odbc_queries:set_private_data_sql(Username, LXMLNS, SData);
(_Host, _R) ->
[]
@ -281,7 +278,7 @@ export_common(Server, Table, Output, ConvertFun) ->
mnesia:transaction(
fun() ->
mnesia:read_lock_table(Table),
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
{_N, SQLs} =
mnesia:foldl(
fun(R, {N, SQLs} = Acc) ->
@ -317,13 +314,13 @@ output(LServer, IO, SQL) ->
file:write(IO, [SQL, $;, $\n])
end.
record_to_string(#roster{usj = {User, _Server, JID},
record_to_string(#roster{usj = {User, _Server, {N, D, R} = _JID},
name = Name,
subscription = Subscription,
ask = Ask,
askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
SJID = ejabberd_odbc:escape(exmpp_jid:to_list(N, D, R)),
Nick = ejabberd_odbc:escape(Name),
SSubscription = case Subscription of
both -> "B";
@ -356,10 +353,10 @@ record_to_string(#roster{usj = {User, _Server, JID},
"'", SAskMessage, "',"
"'N', '', 'item')"].
groups_to_string(#roster{usj = {User, _Server, JID},
groups_to_string(#roster{usj = {User, _Server, {N, D, R} = _JID},
groups = Groups}) ->
Username = ejabberd_odbc:escape(User),
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
SJID = ejabberd_odbc:escape(exmpp_jid:to_list(N, D, R)),
[["("
"'", Username, "',"
"'", SJID, "',"

View File

@ -182,7 +182,7 @@ parse_attr(less, {Name, Value}, ListOfSubValues) ->
eldap:lessOrEqual(Name, NewValue);
parse_attr(equal, {Name, Value}, ListOfSubValues) ->
{ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
RegSList = re:split(remove_extra_asterisks(Value), "[*]", [{return, list}]),
Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
[Head | Tail] when Tail /= [] ->
{Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
@ -228,14 +228,15 @@ do_sub(S, [{RegExp, New, Times} | T]) ->
do_sub(Result, T).
do_sub(S, {RegExp, New}, Iter) ->
case regexp:sub(S, RegExp, New) of
{ok, NewS, 0} ->
try re:replace(S, RegExp, New, [{return, list}]) of
NewS when NewS == S ->
NewS;
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
NewS when Iter =< ?MAX_RECURSION ->
do_sub(NewS, {RegExp, New}, Iter+1);
{ok, _, _} when Iter > ?MAX_RECURSION ->
throw({regexp, max_substitute_recursion});
_ ->
_ when Iter > ?MAX_RECURSION ->
throw({regexp, max_substitute_recursion})
catch
_:_ ->
throw({regexp, bad_regexp})
end;
@ -243,14 +244,15 @@ do_sub(S, {_, _, N}, _) when N<1 ->
S;
do_sub(S, {RegExp, New, Times}, Iter) ->
case regexp:sub(S, RegExp, New) of
{ok, NewS, 0} ->
try re:replace(S, RegExp, New, [{return, list}]) of
NewS when NewS == S ->
NewS;
{ok, NewS, _} when Iter < Times ->
NewS when Iter < Times ->
do_sub(NewS, {RegExp, New, Times}, Iter+1);
{ok, NewS, _} ->
NewS;
_ ->
NewS ->
NewS
catch
_:_ ->
throw({regexp, bad_regexp})
end.

View File

@ -89,8 +89,8 @@ get_user_part(String, Pattern) ->
{'EXIT', _} ->
{error, badmatch};
Result ->
case regexp:sub(Pattern, "%u", Result) of
{ok, String, _} -> {ok, Result};
case re:replace(Pattern, "%u", Result, [{return, list}]) of
String -> {ok, Result};
_ -> {error, badmatch}
end
end.
@ -121,8 +121,8 @@ make_filter(Data, UIDs) ->
end.
case_insensitive_match(X, Y) ->
X1 = stringprep:tolower(X),
Y1 = stringprep:tolower(Y),
X1 = exmpp_stringprep:to_lower(X),
Y1 = exmpp_stringprep:to_lower(Y),
if
X1 == Y1 -> true;
true -> false

View File

@ -1,190 +0,0 @@
/*
* ejabberd, Copyright (C) 2002-2009 ProcessOne
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/
#include <stdio.h>
#include <string.h>
#include <erl_driver.h>
#include <ei.h>
#include <expat.h>
#define XML_START 0
#define XML_END 1
#define XML_CDATA 2
#define XML_ERROR 3
#define PARSE_COMMAND 0
#define PARSE_FINAL_COMMAND 1
ei_x_buff event_buf;
typedef struct {
ErlDrvPort port;
XML_Parser parser;
} expat_data;
void *erlXML_StartElementHandler(expat_data *d,
const XML_Char *name,
const XML_Char **atts)
{
int i;
ei_x_encode_list_header(&event_buf, 1);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_START);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_string(&event_buf, name);
for (i = 0; atts[i]; i += 2) {}
if (i > 0)
{
ei_x_encode_list_header(&event_buf, i/2);
for (i = 0; atts[i]; i += 2)
{
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_string(&event_buf, atts[i]);
ei_x_encode_string(&event_buf, atts[i+1]);
}
}
ei_x_encode_empty_list(&event_buf);
return NULL;
}
void *erlXML_EndElementHandler(expat_data *d,
const XML_Char *name)
{
ei_x_encode_list_header(&event_buf, 1);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_END);
ei_x_encode_string(&event_buf, name);
return NULL;
}
void *erlXML_CharacterDataHandler(expat_data *d,
const XML_Char *s,
int len)
{
ei_x_encode_list_header(&event_buf, 1);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_CDATA);
ei_x_encode_binary(&event_buf, s, len);
return NULL;
}
static ErlDrvData expat_erl_start(ErlDrvPort port, char *buff)
{
expat_data* d = (expat_data*)driver_alloc(sizeof(expat_data));
d->port = port;
d->parser = XML_ParserCreate("UTF-8");
XML_SetUserData(d->parser, d);
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
XML_SetStartElementHandler(
d->parser, (XML_StartElementHandler)erlXML_StartElementHandler);
XML_SetEndElementHandler(
d->parser, (XML_EndElementHandler)erlXML_EndElementHandler);
XML_SetCharacterDataHandler(
d->parser, (XML_CharacterDataHandler)erlXML_CharacterDataHandler);
return (ErlDrvData)d;
}
static void expat_erl_stop(ErlDrvData handle)
{
XML_ParserFree(((expat_data *)handle)->parser);
driver_free((char*)handle);
}
static int expat_erl_control(ErlDrvData drv_data,
unsigned int command,
char *buf, int len,
char **rbuf, int rlen)
{
expat_data* d = (expat_data*)drv_data;
int res, errcode;
char *errstring;
ErlDrvBinary *b;
size_t size;
switch (command)
{
case PARSE_COMMAND:
case PARSE_FINAL_COMMAND:
ei_x_new_with_version(&event_buf);
res = XML_Parse(d->parser, buf, len, command == PARSE_FINAL_COMMAND);
if(!res)
{
errcode = XML_GetErrorCode(d->parser);
errstring = (char *)XML_ErrorString(errcode);
ei_x_encode_list_header(&event_buf, 1);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, XML_ERROR);
ei_x_encode_tuple_header(&event_buf, 2);
ei_x_encode_long(&event_buf, errcode);
ei_x_encode_string(&event_buf, errstring);
}
ei_x_encode_empty_list(&event_buf);
size = event_buf.index;
b = driver_alloc_binary(size);
memcpy(b->orig_bytes, event_buf.buff, size);
ei_x_free(&event_buf);
*rbuf = (char *)b;
return size;
default:
return 0;
}
}
ErlDrvEntry expat_driver_entry = {
NULL, /* F_PTR init, N/A */
expat_erl_start, /* L_PTR start, called when port is opened */
expat_erl_stop, /* F_PTR stop, called when port is closed */
NULL, /* F_PTR output, called when erlang has sent */
NULL, /* F_PTR ready_input, called when input descriptor ready */
NULL, /* F_PTR ready_output, called when output descriptor ready */
"expat_erl", /* char *driver_name, the argument to open_port */
NULL, /* F_PTR finish, called when unloaded */
NULL, /* handle */
expat_erl_control, /* F_PTR control, port_command callback */
NULL, /* F_PTR timeout, reserved */
NULL /* F_PTR outputv, reserved */
};
DRIVER_INIT(expat_erl) /* must match name in driver_entry */
{
return &expat_driver_entry;
}

View File

@ -57,7 +57,7 @@ set_password(User, Server, Password) ->
call_port(Server, ["setpass", User, Server, Password]).
call_port(Server, Msg) ->
LServer = jlib:nameprep(Server),
LServer = exmpp_stringprep:nameprep(Server),
gen_mod:get_module_proc(LServer, eauth) ! {call, self(), Msg},
receive
{eauth,Result} ->

View File

@ -41,6 +41,8 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-record(state, {host,
@ -96,34 +98,34 @@ stop_iq_handler(_Module, _Function, Opts) ->
ok
end.
handle(Host, Module, Function, Opts, From, To, IQ) ->
handle(Host, Module, Function, Opts, From, To, IQ_Rec) ->
case Opts of
no_queue ->
process_iq(Host, Module, Function, From, To, IQ);
process_iq(Host, Module, Function, From, To, IQ_Rec);
{one_queue, Pid} ->
Pid ! {process_iq, From, To, IQ};
Pid ! {process_iq, From, To, IQ_Rec};
{queues, Pids} ->
Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids),
Pid ! {process_iq, From, To, IQ};
Pid ! {process_iq, From, To, IQ_Rec};
parallel ->
spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]);
spawn(?MODULE, process_iq,
[Host, Module, Function, From, To, IQ_Rec]);
_ ->
todo
end.
process_iq(_Host, Module, Function, From, To, IQ) ->
case catch Module:Function(From, To, IQ) of
{'EXIT', Reason} ->
?ERROR_MSG("~p", [Reason]);
process_iq(_Host, Module, Function, From, To, IQ_Rec) ->
try Module:Function(From, To, IQ_Rec) of
ignore ->
ok;
ResIQ ->
if
ResIQ /= ignore ->
ejabberd_router:route(To, From,
jlib:iq_to_xml(ResIQ));
true ->
ok
end
Reply = exmpp_iq:iq_to_xmlel(ResIQ, To, From),
ejabberd_router:route(To, From, Reply)
catch
_Class:Reason ->
?ERROR_MSG("~s:~s/3 crashed: ~p~n~p~n",
[Module, Function, Reason, erlang:get_stacktrace()])
end.
%%====================================================================
@ -170,11 +172,11 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({process_iq, From, To, IQ},
handle_info({process_iq, From, To, IQ_Rec},
#state{host = Host,
module = Module,
function = Function} = State) ->
process_iq(Host, Module, Function, From, To, IQ),
process_iq(Host, Module, Function, From, To, IQ_Rec),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.

View File

@ -173,11 +173,11 @@ get_module_opt(Host, Module, Opt, Default) ->
get_module_opt_host(Host, Module, Default) ->
Val = get_module_opt(Host, Module, host, Default),
element(2, regexp:gsub(Val, "@HOST@", Host)).
re:replace(Val, "@HOST@", Host, [global,{return,list}]).
get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, Default),
element(2, regexp:gsub(Val, "@HOST@", Host)).
re:replace(Val, "@HOST@", Host, [global,{return,list}]).
loaded_modules(Host) ->
ets:select(ejabberd_modules,

View File

@ -31,8 +31,9 @@
-export([import_file/1,
import_dir/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -43,26 +44,28 @@
import_file(File) ->
User = filename:rootname(filename:basename(File)),
Server = filename:basename(filename:dirname(File)),
case (jlib:nodeprep(User) /= error) andalso
(jlib:nameprep(Server) /= error) of
case exmpp_stringprep:is_node(User) andalso
exmpp_stringprep:is_name(Server) of
true ->
case file:read_file(File) of
{ok, Text} ->
case xml_stream:parse_element(Text) of
El when element(1, El) == xmlelement ->
case catch process_xdb(User, Server, El) of
{'EXIT', Reason} ->
?ERROR_MSG(
"Error while processing file \"~s\": ~p~n",
[File, Reason]),
{error, Reason};
_ ->
ok
end;
{error, Reason} ->
try
[El] = exmpp_xml:parse_document(Text,
[names_as_atom]),
case catch process_xdb(User, Server, El) of
{'EXIT', Reason} ->
?ERROR_MSG(
"Error while processing file \"~s\": ~p~n",
[File, Reason]),
{error, Reason};
_ ->
ok
end
catch
_:Reason1 ->
?ERROR_MSG("Can't parse file \"~s\": ~p~n",
[File, Reason]),
{error, Reason}
[File, Reason1]),
{error, Reason1}
end;
{error, Reason} ->
?ERROR_MSG("Can't read file \"~s\": ~p~n", [File, Reason]),
@ -100,26 +103,23 @@ import_dir(Dir) ->
%%% Internal functions
%%%----------------------------------------------------------------------
process_xdb(User, Server, {xmlelement, Name, _Attrs, Els}) ->
case Name of
"xdb" ->
lists:foreach(
fun(El) ->
xdb_data(User, Server, El)
end, Els);
_ ->
ok
end.
process_xdb(User, Server, #xmlel{name = "xdb", children = Els}) ->
lists:foreach(
fun(El) ->
xdb_data(User, Server, El)
end, Els);
process_xdb(_User, _Server, _El) ->
ok.
xdb_data(_User, _Server, {xmlcdata, _CData}) ->
xdb_data(_User, _Server, #xmlcdata{}) ->
ok;
xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
From = jlib:make_jid(User, Server, ""),
LServer = jlib:nameprep(Server),
case xml:get_attr_s("xmlns", Attrs) of
?NS_AUTH ->
Password = xml:get_tag_cdata(El),
xdb_data(User, Server, #xmlel{ns = NS} = El) ->
From = exmpp_jid:make(User, Server),
LServer = exmpp_stringprep:nameprep(Server),
case NS of
?NS_LEGACY_AUTH ->
Password = exmpp_xml:get_cdata(El),
ejabberd_auth:set_password(User, Server, Password),
ok;
?NS_ROSTER ->
@ -131,9 +131,9 @@ xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
catch mod_roster:set_items(User, Server, El)
end,
ok;
?NS_LAST ->
TimeStamp = xml:get_attr_s("last", Attrs),
Status = xml:get_tag_cdata(El),
?NS_LAST_ACTIVITY ->
TimeStamp = exmpp_xml:get_attribute_as_list(El, 'last', ""),
Status = exmpp_xml:get_cdata(El),
case lists:member(mod_last_odbc,
gen_mod:loaded_modules(LServer)) of
true ->
@ -156,29 +156,29 @@ xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
true ->
catch mod_vcard_odbc:process_sm_iq(
From,
jlib:make_jid("", Server, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El});
exmpp_jid:make(Server),
#iq{kind = request, type = set, ns = ?NS_VCARD, payload = El, iq_ns = ?NS_JABBER_CLIENT});
false ->
catch mod_vcard:process_sm_iq(
From,
jlib:make_jid("", Server, ""),
#iq{type = set, xmlns = ?NS_VCARD, sub_el = El})
exmpp_jid:make(Server),
#iq{kind = request, type = set, ns = ?NS_VCARD, payload = El, iq_ns = ?NS_JABBER_CLIENT})
end,
ok;
"jabber:x:offline" ->
process_offline(Server, From, El),
ok;
XMLNS ->
case xml:get_attr_s("j_private_flag", Attrs) of
case exmpp_xml:get_attribute_as_list(El, "j_private_flag", "") of
"1" ->
catch mod_private:process_sm_iq(
From,
jlib:make_jid("", Server, ""),
#iq{type = set, xmlns = ?NS_PRIVATE,
sub_el = {xmlelement, "query", [],
[jlib:remove_attr(
"j_private_flag",
jlib:remove_attr("xdbns", El))]}});
exmpp_jid:make(Server),
#iq{kind = request, type = set, ns = ?NS_PRIVATE,
iq_ns = ?NS_JABBER_CLIENT,
payload = #xmlel{name = 'query', children =
[exmpp_xml:remove_attribute(
exmpp_xml:remove_attribute(El, "xdbns"), "j_private_flag")]}});
_ ->
?DEBUG("jd2ejd: Unknown namespace \"~s\"~n", [XMLNS])
end,
@ -186,15 +186,20 @@ xdb_data(User, Server, {xmlelement, _Name, Attrs, _Els} = El) ->
end.
process_offline(Server, To, {xmlelement, _, _, Els}) ->
LServer = jlib:nameprep(Server),
lists:foreach(fun({xmlelement, _, Attrs, _} = El) ->
FromS = xml:get_attr_s("from", Attrs),
process_offline(Server, To, #xmlel{children = Els}) ->
LServer = exmpp_stringprep:nameprep(Server),
lists:foreach(fun(#xmlel{} = El) ->
FromS = exmpp_stanza:get_sender(El),
From = case FromS of
"" ->
jlib:make_jid("", Server, "");
undefined ->
exmpp_jid:make(Server);
_ ->
jlib:string_to_jid(FromS)
try
exmpp_jid:parse(FromS)
catch
_ ->
error
end
end,
case From of
error ->

View File

@ -27,34 +27,7 @@
-module(jlib).
-author('alexey@process-one.net').
-export([make_result_iq_reply/1,
make_error_reply/3,
make_error_reply/2,
make_error_element/2,
make_correct_from_to_attrs/3,
replace_from_to_attrs/3,
replace_from_to/3,
replace_from_attrs/2,
replace_from/2,
remove_attr/2,
make_jid/3,
make_jid/1,
string_to_jid/1,
jid_to_string/1,
is_nodename/1,
tolower/1,
nodeprep/1,
nameprep/1,
resourceprep/1,
jid_tolower/1,
jid_remove_resource/1,
jid_replace_resource/2,
get_iq_namespace/1,
iq_query_info/1,
iq_query_or_response_info/1,
is_iq_request_type/1,
iq_to_xml/1,
parse_xdata_submit/1,
-export([parse_xdata_submit/1,
timestamp_to_iso/1, % TODO: Remove once XEP-0091 is Obsolete
timestamp_to_iso/2,
timestamp_to_xml/4,
@ -67,390 +40,24 @@
ip_to_list/1,
rsm_encode/1,
rsm_encode/2,
rsm_decode/1]).
rsm_decode/1,
from_old_jid/1,
short_jid/1,
short_bare_jid/1,
short_prepd_jid/1,
short_prepd_bare_jid/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("jlib.hrl").
%send_iq(From, To, ID, SubTags) ->
% ok.
%% @type shortjid() = {U, S, R}
%% U = binary()
%% S = binary()
%% R = binary().
make_result_iq_reply({xmlelement, Name, Attrs, SubTags}) ->
NewAttrs = make_result_iq_reply_attrs(Attrs),
{xmlelement, Name, NewAttrs, SubTags}.
make_result_iq_reply_attrs(Attrs) ->
To = xml:get_attr("to", Attrs),
From = xml:get_attr("from", Attrs),
Attrs1 = lists:keydelete("to", 1, Attrs),
Attrs2 = lists:keydelete("from", 1, Attrs1),
Attrs3 = case To of
{value, ToVal} ->
[{"from", ToVal} | Attrs2];
_ ->
Attrs2
end,
Attrs4 = case From of
{value, FromVal} ->
[{"to", FromVal} | Attrs3];
_ ->
Attrs3
end,
Attrs5 = lists:keydelete("type", 1, Attrs4),
Attrs6 = [{"type", "result"} | Attrs5],
Attrs6.
make_error_reply({xmlelement, Name, Attrs, SubTags}, Code, Desc) ->
NewAttrs = make_error_reply_attrs(Attrs),
{xmlelement, Name, NewAttrs, SubTags ++ [{xmlelement, "error",
[{"code", Code}],
[{xmlcdata, Desc}]}]}.
make_error_reply({xmlelement, Name, Attrs, SubTags}, Error) ->
NewAttrs = make_error_reply_attrs(Attrs),
{xmlelement, Name, NewAttrs, SubTags ++ [Error]}.
make_error_reply_attrs(Attrs) ->
To = xml:get_attr("to", Attrs),
From = xml:get_attr("from", Attrs),
Attrs1 = lists:keydelete("to", 1, Attrs),
Attrs2 = lists:keydelete("from", 1, Attrs1),
Attrs3 = case To of
{value, ToVal} ->
[{"from", ToVal} | Attrs2];
_ ->
Attrs2
end,
Attrs4 = case From of
{value, FromVal} ->
[{"to", FromVal} | Attrs3];
_ ->
Attrs3
end,
Attrs5 = lists:keydelete("type", 1, Attrs4),
Attrs6 = [{"type", "error"} | Attrs5],
Attrs6.
make_error_element(Code, Desc) ->
{xmlelement, "error",
[{"code", Code}],
[{xmlcdata, Desc}]}.
make_correct_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete("from", 1, Attrs),
Attrs2 = case xml:get_attr("to", Attrs) of
{value, _} ->
Attrs1;
_ ->
[{"to", To} | Attrs1]
end,
Attrs3 = [{"from", From} | Attrs2],
Attrs3.
replace_from_to_attrs(From, To, Attrs) ->
Attrs1 = lists:keydelete("to", 1, Attrs),
Attrs2 = lists:keydelete("from", 1, Attrs1),
Attrs3 = [{"to", To} | Attrs2],
Attrs4 = [{"from", From} | Attrs3],
Attrs4.
replace_from_to(From, To, {xmlelement, Name, Attrs, Els}) ->
NewAttrs = replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
{xmlelement, Name, NewAttrs, Els}.
replace_from_attrs(From, Attrs) ->
Attrs1 = lists:keydelete("from", 1, Attrs),
[{"from", From} | Attrs1].
replace_from(From, {xmlelement, Name, Attrs, Els}) ->
NewAttrs = replace_from_attrs(jlib:jid_to_string(From), Attrs),
{xmlelement, Name, NewAttrs, Els}.
remove_attr(Attr, {xmlelement, Name, Attrs, Els}) ->
NewAttrs = lists:keydelete(Attr, 1, Attrs),
{xmlelement, Name, NewAttrs, Els}.
make_jid(User, Server, Resource) ->
case nodeprep(User) of
error -> error;
LUser ->
case nameprep(Server) of
error -> error;
LServer ->
case resourceprep(Resource) of
error -> error;
LResource ->
#jid{user = User,
server = Server,
resource = Resource,
luser = LUser,
lserver = LServer,
lresource = LResource}
end
end
end.
make_jid({User, Server, Resource}) ->
make_jid(User, Server, Resource).
string_to_jid(J) ->
string_to_jid1(J, "").
string_to_jid1([$@ | _J], "") ->
error;
string_to_jid1([$@ | J], N) ->
string_to_jid2(J, lists:reverse(N), "");
string_to_jid1([$/ | _J], "") ->
error;
string_to_jid1([$/ | J], N) ->
string_to_jid3(J, "", lists:reverse(N), "");
string_to_jid1([C | J], N) ->
string_to_jid1(J, [C | N]);
string_to_jid1([], "") ->
error;
string_to_jid1([], N) ->
make_jid("", lists:reverse(N), "").
%% Only one "@" is admitted per JID
string_to_jid2([$@ | _J], _N, _S) ->
error;
string_to_jid2([$/ | _J], _N, "") ->
error;
string_to_jid2([$/ | J], N, S) ->
string_to_jid3(J, N, lists:reverse(S), "");
string_to_jid2([C | J], N, S) ->
string_to_jid2(J, N, [C | S]);
string_to_jid2([], _N, "") ->
error;
string_to_jid2([], N, S) ->
make_jid(N, lists:reverse(S), "").
string_to_jid3([C | J], N, S, R) ->
string_to_jid3(J, N, S, [C | R]);
string_to_jid3([], N, S, R) ->
make_jid(N, S, lists:reverse(R)).
jid_to_string(#jid{user = User, server = Server, resource = Resource}) ->
jid_to_string({User, Server, Resource});
jid_to_string({Node, Server, Resource}) ->
S1 = case Node of
"" ->
"";
_ ->
Node ++ "@"
end,
S2 = S1 ++ Server,
S3 = case Resource of
"" ->
S2;
_ ->
S2 ++ "/" ++ Resource
end,
S3.
is_nodename([]) ->
false;
is_nodename(J) ->
nodeprep(J) /= error.
%tolower_c(C) when C >= $A, C =< $Z ->
% C + 32;
%tolower_c(C) ->
% C.
-define(LOWER(Char),
if
Char >= $A, Char =< $Z ->
Char + 32;
true ->
Char
end).
%tolower(S) ->
% lists:map(fun tolower_c/1, S).
%tolower(S) ->
% [?LOWER(Char) || Char <- S].
% Not tail-recursive but it seems works faster than variants above
tolower([C | Cs]) ->
if
C >= $A, C =< $Z ->
[C + 32 | tolower(Cs)];
true ->
[C | tolower(Cs)]
end;
tolower([]) ->
[].
%tolower([C | Cs]) when C >= $A, C =< $Z ->
% [C + 32 | tolower(Cs)];
%tolower([C | Cs]) ->
% [C | tolower(Cs)];
%tolower([]) ->
% [].
nodeprep(S) when length(S) < 1024 ->
R = stringprep:nodeprep(S),
if
length(R) < 1024 -> R;
true -> error
end;
nodeprep(_) ->
error.
nameprep(S) when length(S) < 1024 ->
R = stringprep:nameprep(S),
if
length(R) < 1024 -> R;
true -> error
end;
nameprep(_) ->
error.
resourceprep(S) when length(S) < 1024 ->
R = stringprep:resourceprep(S),
if
length(R) < 1024 -> R;
true -> error
end;
resourceprep(_) ->
error.
jid_tolower(#jid{luser = U, lserver = S, lresource = R}) ->
{U, S, R};
jid_tolower({U, S, R}) ->
case nodeprep(U) of
error -> error;
LUser ->
case nameprep(S) of
error -> error;
LServer ->
case resourceprep(R) of
error -> error;
LResource ->
{LUser, LServer, LResource}
end
end
end.
jid_remove_resource(#jid{} = JID) ->
JID#jid{resource = "", lresource = ""};
jid_remove_resource({U, S, _R}) ->
{U, S, ""}.
jid_replace_resource(JID, Resource) ->
case resourceprep(Resource) of
error -> error;
LResource ->
JID#jid{resource = Resource, lresource = LResource}
end.
get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
case xml:remove_cdata(Els) of
[{xmlelement, _Name2, Attrs2, _Els2}] ->
xml:get_attr_s("xmlns", Attrs2);
_ ->
""
end;
get_iq_namespace(_) ->
"".
iq_query_info(El) ->
iq_info_internal(El, request).
iq_query_or_response_info(El) ->
iq_info_internal(El, any).
iq_info_internal({xmlelement, Name, Attrs, Els}, Filter) when Name == "iq" ->
%% Filter is either request or any. If it is request, any replies
%% are converted to the atom reply.
ID = xml:get_attr_s("id", Attrs),
Type = xml:get_attr_s("type", Attrs),
Lang = xml:get_attr_s("xml:lang", Attrs),
{Type1, Class} = case Type of
"set" -> {set, request};
"get" -> {get, request};
"result" -> {result, reply};
"error" -> {error, reply};
_ -> {invalid, invalid}
end,
if
Type1 == invalid ->
invalid;
Class == request; Filter == any ->
%% The iq record is a bit strange. The sub_el field is an
%% XML tuple for requests, but a list of XML tuples for
%% responses.
FilteredEls = xml:remove_cdata(Els),
{XMLNS, SubEl} =
case {Class, FilteredEls} of
{request, [{xmlelement, _Name2, Attrs2, _Els2}]} ->
{xml:get_attr_s("xmlns", Attrs2),
hd(FilteredEls)};
{reply, _} ->
%% Find the namespace of the first non-error
%% element, if there is one.
NonErrorEls = [El ||
{xmlelement, SubName, _, _} = El
<- FilteredEls,
SubName /= "error"],
{case NonErrorEls of
[NonErrorEl] -> xml:get_tag_attr_s("xmlns", NonErrorEl);
_ -> invalid
end,
FilteredEls};
_ ->
{invalid, invalid}
end,
if XMLNS == "", Class == request ->
invalid;
true ->
#iq{id = ID,
type = Type1,
xmlns = XMLNS,
lang = Lang,
sub_el = SubEl}
end;
Class == reply, Filter /= any ->
reply
end;
iq_info_internal(_, _) ->
not_iq.
is_iq_request_type(set) -> true;
is_iq_request_type(get) -> true;
is_iq_request_type(_) -> false.
iq_type_to_string(set) -> "set";
iq_type_to_string(get) -> "get";
iq_type_to_string(result) -> "result";
iq_type_to_string(error) -> "error";
iq_type_to_string(_) -> invalid.
iq_to_xml(#iq{id = ID, type = Type, sub_el = SubEl}) ->
if
ID /= "" ->
{xmlelement, "iq",
[{"id", ID}, {"type", iq_type_to_string(Type)}], SubEl};
true ->
{xmlelement, "iq",
[{"type", iq_type_to_string(Type)}], SubEl}
end.
parse_xdata_submit(El) ->
{xmlelement, _Name, Attrs, Els} = El,
case xml:get_attr_s("type", Attrs) of
parse_xdata_submit(#xmlel{attrs = Attrs, children = Els}) ->
case exmpp_xml:get_attribute_from_list_as_list(Attrs, 'type', "") of
"submit" ->
lists:reverse(parse_xdata_fields(Els, []));
_ ->
@ -459,61 +66,51 @@ parse_xdata_submit(El) ->
parse_xdata_fields([], Res) ->
Res;
parse_xdata_fields([{xmlelement, Name, Attrs, SubEls} | Els], Res) ->
case Name of
"field" ->
case xml:get_attr_s("var", Attrs) of
"" ->
parse_xdata_fields(Els, Res);
Var ->
Field =
{Var, lists:reverse(parse_xdata_values(SubEls, []))},
parse_xdata_fields(Els, [Field | Res])
end;
_ ->
parse_xdata_fields(Els, Res)
parse_xdata_fields([#xmlel{name = 'field', attrs = Attrs, children = SubEls} |
Els], Res) ->
case exmpp_xml:get_attribute_from_list_as_list(Attrs, 'var', "") of
"" ->
parse_xdata_fields(Els, Res);
Var ->
Field = {Var, lists:reverse(parse_xdata_values(SubEls, []))},
parse_xdata_fields(Els, [Field | Res])
end;
parse_xdata_fields([_ | Els], Res) ->
parse_xdata_fields(Els, Res).
parse_xdata_values([], Res) ->
Res;
parse_xdata_values([{xmlelement, Name, _Attrs, SubEls} | Els], Res) ->
case Name of
"value" ->
Val = xml:get_cdata(SubEls),
parse_xdata_values(Els, [Val | Res]);
_ ->
parse_xdata_values(Els, Res)
end;
parse_xdata_values([#xmlel{name = 'value', children = SubEls} | Els], Res) ->
Val = exmpp_xml:get_cdata_from_list_as_list(SubEls),
parse_xdata_values(Els, [Val | Res]);
parse_xdata_values([_ | Els], Res) ->
parse_xdata_values(Els, Res).
rsm_decode(#iq{sub_el=SubEl})->
rsm_decode(#iq{payload=SubEl})->
rsm_decode(SubEl);
rsm_decode({xmlelement, _,_,_}=SubEl)->
case xml:get_subtag(SubEl,"set") of
false ->
rsm_decode(#xmlel{}=SubEl)->
case exmpp_xml:get_element(SubEl, 'set') of
undefined ->
none;
{xmlelement, "set", _Attrs, SubEls}->
#xmlel{name = 'set', children = SubEls}->
lists:foldl(fun rsm_parse_element/2, #rsm_in{}, SubEls)
end.
rsm_parse_element({xmlelement, "max",[], _}=Elem, RsmIn)->
CountStr = xml:get_tag_cdata(Elem),
rsm_parse_element(#xmlel{name = 'max'}=Elem, RsmIn)->
CountStr = exmpp_xml:get_cdata_as_list(Elem),
{Count, _} = string:to_integer(CountStr),
RsmIn#rsm_in{max=Count};
rsm_parse_element({xmlelement, "before", [], _}=Elem, RsmIn)->
UID = xml:get_tag_cdata(Elem),
rsm_parse_element(#xmlel{name = 'before'}=Elem, RsmIn)->
UID = exmpp_xml:get_cdata_as_list(Elem),
RsmIn#rsm_in{direction=before, id=UID};
rsm_parse_element({xmlelement, "after", [], _}=Elem, RsmIn)->
UID = xml:get_tag_cdata(Elem),
rsm_parse_element(#xmlel{name = 'after'}=Elem, RsmIn)->
UID = exmpp_xml:get_cdata_as_list(Elem),
RsmIn#rsm_in{direction=aft, id=UID};
rsm_parse_element({xmlelement, "index",[], _}=Elem, RsmIn)->
IndexStr = xml:get_tag_cdata(Elem),
rsm_parse_element(#xmlel{name = 'index'}=Elem, RsmIn)->
IndexStr = exmpp_xml:get_cdata_as_list(Elem),
{Index, _} = string:to_integer(IndexStr),
RsmIn#rsm_in{index=Index};
@ -521,17 +118,16 @@ rsm_parse_element({xmlelement, "index",[], _}=Elem, RsmIn)->
rsm_parse_element(_, RsmIn)->
RsmIn.
rsm_encode(#iq{sub_el=SubEl}=IQ,RsmOut)->
Set = {xmlelement, "set", [{"xmlns", ?NS_RSM}],
rsm_encode(#iq{payload=SubEl}=IQ_Rec,RsmOut)->
Set = #xmlel{ns = ?NS_RSM, name = 'set', children =
lists:reverse(rsm_encode_out(RsmOut))},
{xmlelement, Name, Attrs, SubEls} = SubEl,
New = {xmlelement, Name, Attrs, [Set | SubEls]},
IQ#iq{sub_el=New}.
New = exmpp_xml:prepend_child(SubEl, Set),
IQ_Rec#iq{payload=New}.
rsm_encode(none)->
[];
rsm_encode(RsmOut)->
[{xmlelement, "set", [{"xmlns", ?NS_RSM}], lists:reverse(rsm_encode_out(RsmOut))}].
[#xmlel{ns = ?NS_RSM, name = 'set', children = lists:reverse(rsm_encode_out(RsmOut))}].
rsm_encode_out(#rsm_out{count=Count, index=Index, first=First, last=Last})->
El = rsm_encode_first(First, Index, []),
El2 = rsm_encode_last(Last,El),
@ -540,39 +136,37 @@ rsm_encode_out(#rsm_out{count=Count, index=Index, first=First, last=Last})->
rsm_encode_first(undefined, undefined, Arr) ->
Arr;
rsm_encode_first(First, undefined, Arr) ->
[{xmlelement, "first",[], [{xmlcdata, First}]}|Arr];
[#xmlel{ns = ?NS_RSM, name = 'first', children = [#xmlcdata{cdata = list_to_binary(First)}]}|Arr];
rsm_encode_first(First, Index, Arr) ->
[{xmlelement, "first",[{"index", i2l(Index)}], [{xmlcdata, First}]}|Arr].
[#xmlel{ns = ?NS_RSM, name = 'first', attrs = [?XMLATTR('index', Index)], children = [#xmlcdata{cdata = list_to_binary(First)}]}|Arr].
rsm_encode_last(undefined, Arr) -> Arr;
rsm_encode_last(Last, Arr) ->
[{xmlelement, "last",[], [{xmlcdata, Last}]}|Arr].
[#xmlel{ns = ?NS_RSM, name = 'last', children = [#xmlcdata{cdata = list_to_binary(Last)}]}|Arr].
rsm_encode_count(undefined, Arr)-> Arr;
rsm_encode_count(Count, Arr)->
[{xmlelement, "count",[], [{xmlcdata, i2l(Count)}]} | Arr].
[#xmlel{ns = ?NS_RSM, name = 'count', children = [#xmlcdata{cdata = i2b(Count)}]} | Arr].
i2l(I) when is_integer(I) -> integer_to_list(I);
i2l(L) when is_list(L) -> L.
i2b(I) when is_integer(I) -> list_to_binary(integer_to_list(I));
i2b(L) when is_list(L) -> list_to_binary(L).
%% Timezone = utc | {Hours, Minutes}
%% Hours = integer()
%% Minutes = integer()
timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}, Timezone) ->
Timestamp_string =
lists:flatten(
io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second])),
Timezone_string =
case Timezone of
utc -> "Z";
{TZh, TZm} ->
Timestamp_string = lists:flatten(
io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second])),
Timezone_string = case Timezone of
utc -> "Z";
{TZh, TZm} ->
Sign = case TZh >= 0 of
true -> "+";
false -> "-"
end,
true -> "+";
false -> "-"
end,
io_lib:format("~s~2..0w:~2..0w", [Sign, abs(TZh),TZm])
end,
end,
{Timestamp_string, Timezone_string}.
timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}) ->
@ -582,22 +176,19 @@ timestamp_to_iso({{Year, Month, Day}, {Hour, Minute, Second}}) ->
timestamp_to_xml(DateTime, Timezone, FromJID, Desc) ->
{T_string, Tz_string} = timestamp_to_iso(DateTime, Timezone),
Text = [{xmlcdata, Desc}],
From = jlib:jid_to_string(FromJID),
{xmlelement, "delay",
[{"xmlns", ?NS_DELAY},
{"from", From},
{"stamp", T_string ++ Tz_string}],
Text}.
From = exmpp_jid:to_list(FromJID),
P1 = exmpp_xml:set_attributes(#xmlel{ns = ?NS_DELAY, name = 'delay'},
[{'from', From},
{'stamp', T_string ++ Tz_string}]),
exmpp_xml:set_cdata(P1, Desc).
%% TODO: Remove this function once XEP-0091 is Obsolete
timestamp_to_xml({{Year, Month, Day}, {Hour, Minute, Second}}) ->
{xmlelement, "x",
[{"xmlns", ?NS_DELAY91},
{"stamp", lists:flatten(
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second]))}],
[]}.
Timestamp = lists:flatten(
io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
[Year, Month, Day, Hour, Minute, Second])),
exmpp_xml:set_attribute(#xmlel{ns = ?NS_DELAY_OLD, name = 'x'},
'stamp', Timestamp).
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
{{Year, Month, Day}, {Hour, Minute, Second}} =
@ -793,3 +384,38 @@ ip_to_list({IP, _Port}) ->
ip_to_list(IP);
ip_to_list({A,B,C,D}) ->
lists:flatten(io_lib:format("~w.~w.~w.~w",[A,B,C,D])).
% --------------------------------------------------------------------
% Compat layer.
% --------------------------------------------------------------------
%% @spec (JID) -> New_JID
%% JID = jid()
%% New_JID = jid()
%% @doc Convert a JID from its ejabberd form to its exmpp form.
%%
%% Empty fields are set to `undefined', not the empty string.
%%TODO: this doesn't make sence!, it is still used?.
from_old_jid(JID) ->
Node = exmpp_jid:node(JID),
Resource = exmpp_jid:resource(JID),
Domain = exmpp_jid:domain(JID),
exmpp_jid:make(Node,Domain,Resource).
short_jid(JID) ->
{exmpp_jid:node(JID), exmpp_jid:domain(JID), exmpp_jid:resource(JID)}.
short_bare_jid(JID) ->
short_jid(exmpp_jid:bare(JID)).
short_prepd_jid(JID) ->
{exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID),
exmpp_jid:prep_resource(JID)}.
short_prepd_bare_jid(JID) ->
short_prepd_jid(exmpp_jid:bare(JID)).

View File

@ -19,308 +19,11 @@
%%%
%%%----------------------------------------------------------------------
-define(NS_DISCO_ITEMS, "http://jabber.org/protocol/disco#items").
-define(NS_DISCO_INFO, "http://jabber.org/protocol/disco#info").
-define(NS_VCARD, "vcard-temp").
-define(NS_AUTH, "jabber:iq:auth").
-define(NS_AUTH_ERROR, "jabber:iq:auth:error").
-define(NS_REGISTER, "jabber:iq:register").
-define(NS_SEARCH, "jabber:iq:search").
-define(NS_ROSTER, "jabber:iq:roster").
-define(NS_ROSTER_VER, "urn:xmpp:features:rosterver").
-define(NS_PRIVACY, "jabber:iq:privacy").
-define(NS_PRIVATE, "jabber:iq:private").
-define(NS_VERSION, "jabber:iq:version").
-define(NS_TIME90, "jabber:iq:time"). % TODO: Remove once XEP-0090 is Obsolete
-define(NS_TIME, "urn:xmpp:time").
-define(NS_LAST, "jabber:iq:last").
-define(NS_XDATA, "jabber:x:data").
-define(NS_IQDATA, "jabber:iq:data").
-define(NS_DELAY91, "jabber:x:delay"). % TODO: Remove once XEP-0091 is Obsolete
-define(NS_DELAY, "urn:xmpp:delay").
-define(NS_EXPIRE, "jabber:x:expire").
-define(NS_EVENT, "jabber:x:event").
-define(NS_CHATSTATES, "http://jabber.org/protocol/chatstates").
-define(NS_XCONFERENCE, "jabber:x:conference").
-define(NS_STATS, "http://jabber.org/protocol/stats").
-define(NS_MUC, "http://jabber.org/protocol/muc").
-define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").
-define(NS_MUC_ADMIN, "http://jabber.org/protocol/muc#admin").
-define(NS_MUC_OWNER, "http://jabber.org/protocol/muc#owner").
-define(NS_PUBSUB, "http://jabber.org/protocol/pubsub").
-define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
-define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
-define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
-define(NS_PUBSUB_NODE_CONFIG, "http://jabber.org/protocol/pubsub#node_config").
-define(NS_PUBSUB_SUB_OPTIONS, "http://jabber.org/protocol/pubsub#subscribe_options").
-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
-define(NS_PUBSUB_GET_PENDING, "http://jabber.org/protocol/pubsub#get-pending").
-define(NS_COMMANDS, "http://jabber.org/protocol/commands").
-define(NS_BYTESTREAMS, "http://jabber.org/protocol/bytestreams").
-define(NS_ADMIN, "http://jabber.org/protocol/admin").
-define(NS_SERVERINFO, "http://jabber.org/network/serverinfo").
-define(NS_RSM, "http://jabber.org/protocol/rsm").
-define(NS_EJABBERD_CONFIG, "ejabberd:config").
-define(NS_STREAM, "http://etherx.jabber.org/streams").
-define(NS_STANZAS, "urn:ietf:params:xml:ns:xmpp-stanzas").
-define(NS_STREAMS, "urn:ietf:params:xml:ns:xmpp-streams").
-define(NS_TLS, "urn:ietf:params:xml:ns:xmpp-tls").
-define(NS_SASL, "urn:ietf:params:xml:ns:xmpp-sasl").
-define(NS_SESSION, "urn:ietf:params:xml:ns:xmpp-session").
-define(NS_BIND, "urn:ietf:params:xml:ns:xmpp-bind").
-define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth").
-define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register").
-define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress").
-define(NS_FEATURE_MSGOFFLINE, "msgoffline").
-define(NS_COMPRESS, "http://jabber.org/protocol/compress").
-define(NS_CAPS, "http://jabber.org/protocol/caps").
-define(NS_SHIM, "http://jabber.org/protocol/shim").
%% CAPTCHA related NSes.
-define(NS_OOB, "jabber:x:oob").
-define(NS_CAPTCHA, "urn:xmpp:captcha").
-define(NS_MEDIA, "urn:xmpp:media-element").
-define(NS_BOB, "urn:xmpp:bob").
% TODO: remove "code" attribute (currently it used for backward-compatibility)
-define(STANZA_ERROR(Code, Type, Condition),
{xmlelement, "error",
[{"code", Code}, {"type", Type}],
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []}]}).
-define(ERR_BAD_REQUEST,
?STANZA_ERROR("400", "modify", "bad-request")).
-define(ERR_CONFLICT,
?STANZA_ERROR("409", "cancel", "conflict")).
-define(ERR_FEATURE_NOT_IMPLEMENTED,
?STANZA_ERROR("501", "cancel", "feature-not-implemented")).
-define(ERR_FORBIDDEN,
?STANZA_ERROR("403", "auth", "forbidden")).
-define(ERR_GONE,
?STANZA_ERROR("302", "modify", "gone")).
-define(ERR_INTERNAL_SERVER_ERROR,
?STANZA_ERROR("500", "wait", "internal-server-error")).
-define(ERR_ITEM_NOT_FOUND,
?STANZA_ERROR("404", "cancel", "item-not-found")).
-define(ERR_JID_MALFORMED,
?STANZA_ERROR("400", "modify", "jid-malformed")).
-define(ERR_NOT_ACCEPTABLE,
?STANZA_ERROR("406", "modify", "not-acceptable")).
-define(ERR_NOT_ALLOWED,
?STANZA_ERROR("405", "cancel", "not-allowed")).
-define(ERR_NOT_AUTHORIZED,
?STANZA_ERROR("401", "auth", "not-authorized")).
-define(ERR_PAYMENT_REQUIRED,
?STANZA_ERROR("402", "auth", "payment-required")).
-define(ERR_RECIPIENT_UNAVAILABLE,
?STANZA_ERROR("404", "wait", "recipient-unavailable")).
-define(ERR_REDIRECT,
?STANZA_ERROR("302", "modify", "redirect")).
-define(ERR_REGISTRATION_REQUIRED,
?STANZA_ERROR("407", "auth", "registration-required")).
-define(ERR_REMOTE_SERVER_NOT_FOUND,
?STANZA_ERROR("404", "cancel", "remote-server-not-found")).
-define(ERR_REMOTE_SERVER_TIMEOUT,
?STANZA_ERROR("504", "wait", "remote-server-timeout")).
-define(ERR_RESOURCE_CONSTRAINT,
?STANZA_ERROR("500", "wait", "resource-constraint")).
-define(ERR_SERVICE_UNAVAILABLE,
?STANZA_ERROR("503", "cancel", "service-unavailable")).
-define(ERR_SUBSCRIPTION_REQUIRED,
?STANZA_ERROR("407", "auth", "subscription-required")).
-define(ERR_UNEXPECTED_REQUEST,
?STANZA_ERROR("400", "wait", "unexpected-request")).
%-define(ERR_,
% ?STANZA_ERROR("", "", "")).
-define(STANZA_ERRORT(Code, Type, Condition, Lang, Text),
{xmlelement, "error",
[{"code", Code}, {"type", Type}],
[{xmlelement, Condition, [{"xmlns", ?NS_STANZAS}], []},
{xmlelement, "text", [{"xmlns", ?NS_STANZAS}],
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
-define(ERRT_BAD_REQUEST(Lang, Text),
?STANZA_ERRORT("400", "modify", "bad-request", Lang, Text)).
-define(ERRT_CONFLICT(Lang, Text),
?STANZA_ERRORT("409", "cancel", "conflict", Lang, Text)).
-define(ERRT_FEATURE_NOT_IMPLEMENTED(Lang, Text),
?STANZA_ERRORT("501", "cancel", "feature-not-implemented", Lang, Text)).
-define(ERRT_FORBIDDEN(Lang, Text),
?STANZA_ERRORT("403", "auth", "forbidden", Lang, Text)).
-define(ERRT_GONE(Lang, Text),
?STANZA_ERRORT("302", "modify", "gone", Lang, Text)).
-define(ERRT_INTERNAL_SERVER_ERROR(Lang, Text),
?STANZA_ERRORT("500", "wait", "internal-server-error", Lang, Text)).
-define(ERRT_ITEM_NOT_FOUND(Lang, Text),
?STANZA_ERRORT("404", "cancel", "item-not-found", Lang, Text)).
-define(ERRT_JID_MALFORMED(Lang, Text),
?STANZA_ERRORT("400", "modify", "jid-malformed", Lang, Text)).
-define(ERRT_NOT_ACCEPTABLE(Lang, Text),
?STANZA_ERRORT("406", "modify", "not-acceptable", Lang, Text)).
-define(ERRT_NOT_ALLOWED(Lang, Text),
?STANZA_ERRORT("405", "cancel", "not-allowed", Lang, Text)).
-define(ERRT_NOT_AUTHORIZED(Lang, Text),
?STANZA_ERRORT("401", "auth", "not-authorized", Lang, Text)).
-define(ERRT_PAYMENT_REQUIRED(Lang, Text),
?STANZA_ERRORT("402", "auth", "payment-required", Lang, Text)).
-define(ERRT_RECIPIENT_UNAVAILABLE(Lang, Text),
?STANZA_ERRORT("404", "wait", "recipient-unavailable", Lang, Text)).
-define(ERRT_REDIRECT(Lang, Text),
?STANZA_ERRORT("302", "modify", "redirect", Lang, Text)).
-define(ERRT_REGISTRATION_REQUIRED(Lang, Text),
?STANZA_ERRORT("407", "auth", "registration-required", Lang, Text)).
-define(ERRT_REMOTE_SERVER_NOT_FOUND(Lang, Text),
?STANZA_ERRORT("404", "cancel", "remote-server-not-found", Lang, Text)).
-define(ERRT_REMOTE_SERVER_TIMEOUT(Lang, Text),
?STANZA_ERRORT("504", "wait", "remote-server-timeout", Lang, Text)).
-define(ERRT_RESOURCE_CONSTRAINT(Lang, Text),
?STANZA_ERRORT("500", "wait", "resource-constraint", Lang, Text)).
-define(ERRT_SERVICE_UNAVAILABLE(Lang, Text),
?STANZA_ERRORT("503", "cancel", "service-unavailable", Lang, Text)).
-define(ERRT_SUBSCRIPTION_REQUIRED(Lang, Text),
?STANZA_ERRORT("407", "auth", "subscription-required", Lang, Text)).
-define(ERRT_UNEXPECTED_REQUEST(Lang, Text),
?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)).
% Auth stanza errors
-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang),
?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")).
-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang),
?ERRT_NOT_ACCEPTABLE(Lang, "Illegal resource format")).
-define(ERR_AUTH_RESOURCE_CONFLICT(Lang),
?ERRT_CONFLICT(Lang, "Resource conflict")).
-define(STREAM_ERROR(Condition),
{xmlelement, "stream:error",
[],
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}).
-define(SERR_BAD_FORMAT,
?STREAM_ERROR("bad-format")).
-define(SERR_BAD_NAMESPACE_PREFIX,
?STREAM_ERROR("bad-namespace-prefix")).
-define(SERR_CONFLICT,
?STREAM_ERROR("conflict")).
-define(SERR_CONNECTION_TIMEOUT,
?STREAM_ERROR("connection-timeout")).
-define(SERR_HOST_GONE,
?STREAM_ERROR("host-gone")).
-define(SERR_HOST_UNKNOWN,
?STREAM_ERROR("host-unknown")).
-define(SERR_IMPROPER_ADDRESSING,
?STREAM_ERROR("improper-addressing")).
-define(SERR_INTERNAL_SERVER_ERROR,
?STREAM_ERROR("internal-server-error")).
-define(SERR_INVALID_FROM,
?STREAM_ERROR("invalid-from")).
-define(SERR_INVALID_ID,
?STREAM_ERROR("invalid-id")).
-define(SERR_INVALID_NAMESPACE,
?STREAM_ERROR("invalid-namespace")).
-define(SERR_INVALID_XML,
?STREAM_ERROR("invalid-xml")).
-define(SERR_NOT_AUTHORIZED,
?STREAM_ERROR("not-authorized")).
-define(SERR_POLICY_VIOLATION,
?STREAM_ERROR("policy-violation")).
-define(SERR_REMOTE_CONNECTION_FAILED,
?STREAM_ERROR("remote-connection-failed")).
-define(SERR_RESOURSE_CONSTRAINT,
?STREAM_ERROR("resource-constraint")).
-define(SERR_RESTRICTED_XML,
?STREAM_ERROR("restricted-xml")).
% TODO: include hostname or IP
-define(SERR_SEE_OTHER_HOST,
?STREAM_ERROR("see-other-host")).
-define(SERR_SYSTEM_SHUTDOWN,
?STREAM_ERROR("system-shutdown")).
-define(SERR_UNSUPPORTED_ENCODING,
?STREAM_ERROR("unsupported-encoding")).
-define(SERR_UNSUPPORTED_STANZA_TYPE,
?STREAM_ERROR("unsupported-stanza-type")).
-define(SERR_UNSUPPORTED_VERSION,
?STREAM_ERROR("unsupported-version")).
-define(SERR_XML_NOT_WELL_FORMED,
?STREAM_ERROR("xml-not-well-formed")).
%-define(SERR_,
% ?STREAM_ERROR("")).
-define(STREAM_ERRORT(Condition, Lang, Text),
{xmlelement, "stream:error",
[],
[{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []},
{xmlelement, "text", [{"xml:lang", Lang}, {"xmlns", ?NS_STREAMS}],
[{xmlcdata, translate:translate(Lang, Text)}]}]}).
-define(SERRT_BAD_FORMAT(Lang, Text),
?STREAM_ERRORT("bad-format", Lang, Text)).
-define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text),
?STREAM_ERRORT("bad-namespace-prefix", Lang, Text)).
-define(SERRT_CONFLICT(Lang, Text),
?STREAM_ERRORT("conflict", Lang, Text)).
-define(SERRT_CONNECTION_TIMEOUT(Lang, Text),
?STREAM_ERRORT("connection-timeout", Lang, Text)).
-define(SERRT_HOST_GONE(Lang, Text),
?STREAM_ERRORT("host-gone", Lang, Text)).
-define(SERRT_HOST_UNKNOWN(Lang, Text),
?STREAM_ERRORT("host-unknown", Lang, Text)).
-define(SERRT_IMPROPER_ADDRESSING(Lang, Text),
?STREAM_ERRORT("improper-addressing", Lang, Text)).
-define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text),
?STREAM_ERRORT("internal-server-error", Lang, Text)).
-define(SERRT_INVALID_FROM(Lang, Text),
?STREAM_ERRORT("invalid-from", Lang, Text)).
-define(SERRT_INVALID_ID(Lang, Text),
?STREAM_ERRORT("invalid-id", Lang, Text)).
-define(SERRT_INVALID_NAMESPACE(Lang, Text),
?STREAM_ERRORT("invalid-namespace", Lang, Text)).
-define(SERRT_INVALID_XML(Lang, Text),
?STREAM_ERRORT("invalid-xml", Lang, Text)).
-define(SERRT_NOT_AUTHORIZED(Lang, Text),
?STREAM_ERRORT("not-authorized", Lang, Text)).
-define(SERRT_POLICY_VIOLATION(Lang, Text),
?STREAM_ERRORT("policy-violation", Lang, Text)).
-define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text),
?STREAM_ERRORT("remote-connection-failed", Lang, Text)).
-define(SERRT_RESOURSE_CONSTRAINT(Lang, Text),
?STREAM_ERRORT("resource-constraint", Lang, Text)).
-define(SERRT_RESTRICTED_XML(Lang, Text),
?STREAM_ERRORT("restricted-xml", Lang, Text)).
% TODO: include hostname or IP
-define(SERRT_SEE_OTHER_HOST(Lang, Text),
?STREAM_ERRORT("see-other-host", Lang, Text)).
-define(SERRT_SYSTEM_SHUTDOWN(Lang, Text),
?STREAM_ERRORT("system-shutdown", Lang, Text)).
-define(SERRT_UNSUPPORTED_ENCODING(Lang, Text),
?STREAM_ERRORT("unsupported-encoding", Lang, Text)).
-define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text),
?STREAM_ERRORT("unsupported-stanza-type", Lang, Text)).
-define(SERRT_UNSUPPORTED_VERSION(Lang, Text),
?STREAM_ERRORT("unsupported-version", Lang, Text)).
-define(SERRT_XML_NOT_WELL_FORMED(Lang, Text),
?STREAM_ERRORT("xml-not-well-formed", Lang, Text)).
%-define(SERRT_(Lang, Text),
% ?STREAM_ERRORT("", Lang, Text)).
-record(jid, {user, server, resource,
luser, lserver, lresource}).
-record(iq, {id = "",
type,
xmlns = "",
lang = "",
sub_el}).
-record(rsm_in, {max, direction, id, index}).
-record(rsm_out, {count, index, first, last}).

View File

@ -42,43 +42,48 @@
ping_item/4,
ping_command/4]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_ADHOC,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_ADHOC,
?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 99),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 99),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_commands, 99),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, ping_item, 100),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, ping_command, 100).
ejabberd_hooks:add(disco_local_identity, HostB, ?MODULE, get_local_identity, 99),
ejabberd_hooks:add(disco_local_features, HostB, ?MODULE, get_local_features, 99),
ejabberd_hooks:add(disco_local_items, HostB, ?MODULE, get_local_commands, 99),
ejabberd_hooks:add(disco_sm_identity, HostB, ?MODULE, get_sm_identity, 99),
ejabberd_hooks:add(disco_sm_features, HostB, ?MODULE, get_sm_features, 99),
ejabberd_hooks:add(disco_sm_items, HostB, ?MODULE, get_sm_commands, 99),
ejabberd_hooks:add(adhoc_local_items, HostB, ?MODULE, ping_item, 100),
ejabberd_hooks:add(adhoc_local_commands, HostB, ?MODULE, ping_command, 100).
stop(Host) ->
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, ping_command, 100),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, ping_item, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_commands, 99),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 99),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_commands, 99),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 99),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 99),
HostB = list_to_binary(Host),
ejabberd_hooks:delete(adhoc_local_commands, HostB, ?MODULE, ping_command, 100),
ejabberd_hooks:delete(adhoc_local_items, HostB, ?MODULE, ping_item, 100),
ejabberd_hooks:delete(disco_sm_items, HostB, ?MODULE, get_sm_commands, 99),
ejabberd_hooks:delete(disco_sm_features, HostB, ?MODULE, get_sm_features, 99),
ejabberd_hooks:delete(disco_sm_identity, HostB, ?MODULE, get_sm_identity, 99),
ejabberd_hooks:delete(disco_local_items, HostB, ?MODULE, get_local_commands, 99),
ejabberd_hooks:delete(disco_local_features, HostB, ?MODULE, get_local_features, 99),
ejabberd_hooks:delete(disco_local_identity, HostB, ?MODULE, get_local_identity, 99),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS).
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_ADHOC),
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_ADHOC).
%-------------------------------------------------------------------------
get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, "", Lang) ->
get_local_commands(Acc, _From, To, <<>>, Lang) ->
Server = exmpp_jid:domain(To),
LServer = exmpp_jid:prep_domain_as_list(To),
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
case Display of
false ->
@ -88,19 +93,19 @@ get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, "
{result, I} -> I;
_ -> []
end,
Nodes = [{xmlelement,
"item",
[{"jid", Server},
{"node", ?NS_COMMANDS},
{"name", translate:translate(Lang, "Commands")}],
[]}],
Nodes = [#xmlel{ns = ?NS_DISCO_ITEMS,
name = 'item', attrs =
[?XMLATTR('jid', Server),
?XMLATTR('node', ?NS_ADHOC_s),
?XMLATTR('name', translate:translate(Lang, "Commands"))]
}],
{result, Items ++ Nodes}
end;
get_local_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
ejabberd_hooks:run_fold(adhoc_local_items, LServer, {result, []}, [From, To, Lang]);
get_local_commands(_Acc, From, To, ?NS_ADHOC_b, Lang) ->
ejabberd_hooks:run_fold(adhoc_local_items, exmpp_jid:prep_domain(To), {result, []}, [From, To, Lang]);
get_local_commands(_Acc, _From, _To, "ping", _Lang) ->
get_local_commands(_Acc, _From, _To, <<"ping">>, _Lang) ->
{result, []};
get_local_commands(Acc, _From, _To, _Node, _Lang) ->
@ -108,7 +113,8 @@ get_local_commands(Acc, _From, _To, _Node, _Lang) ->
%-------------------------------------------------------------------------
get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) ->
get_sm_commands(Acc, _From, To, <<>>, Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false),
case Display of
false ->
@ -118,17 +124,17 @@ get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) ->
{result, I} -> I;
_ -> []
end,
Nodes = [{xmlelement,
"item",
[{"jid", jlib:jid_to_string(To)},
{"node", ?NS_COMMANDS},
{"name", translate:translate(Lang, "Commands")}],
[]}],
Nodes = [#xmlel{ns = ?NS_DISCO_ITEMS,
name = 'item', attrs =
[?XMLATTR('jid', exmpp_jid:to_binary(To)),
?XMLATTR('node', ?NS_ADHOC_s),
?XMLATTR('name', translate:translate(Lang, "Commands"))]
}],
{result, Items ++ Nodes}
end;
get_sm_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) ->
ejabberd_hooks:run_fold(adhoc_sm_items, LServer, {result, []}, [From, To, Lang]);
get_sm_commands(_Acc, From, To, ?NS_ADHOC_b, Lang) ->
ejabberd_hooks:run_fold(adhoc_sm_items, exmpp_jid:prep_domain(To), {result, []}, [From, To, Lang]);
get_sm_commands(Acc, _From, _To, _Node, _Lang) ->
Acc.
@ -136,17 +142,17 @@ get_sm_commands(Acc, _From, _To, _Node, _Lang) ->
%-------------------------------------------------------------------------
%% On disco info request to the ad-hoc node, return automation/command-list.
get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-list"},
{"name", translate:translate(Lang, "Commands")}], []} | Acc];
get_local_identity(Acc, _From, _To, ?NS_ADHOC_b, Lang) ->
[#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs =
[?XMLATTR('category', <<"automation">>),
?XMLATTR('type', <<"command-list">>),
?XMLATTR('name', translate:translate(Lang, "Commands"))]} | Acc];
get_local_identity(Acc, _From, _To, "ping", Lang) ->
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-node"},
{"name", translate:translate(Lang, "Ping")}], []} | Acc];
get_local_identity(Acc, _From, _To, <<"ping">>, Lang) ->
[#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs =
[?XMLATTR('category', <<"automation">>),
?XMLATTR('type', <<"command-node">>),
?XMLATTR('name', translate:translate(Lang, "Ping"))]} | Acc];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
@ -154,31 +160,31 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) ->
%-------------------------------------------------------------------------
%% On disco info request to the ad-hoc node, return automation/command-list.
get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) ->
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-list"},
{"name", translate:translate(Lang, "Commands")}], []} | Acc];
get_sm_identity(Acc, _From, _To, ?NS_ADHOC_s, Lang) ->
[#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs =
[?XMLATTR('category', <<"automation">>),
?XMLATTR('type', <<"command-list">>),
?XMLATTR('name', translate:translate(Lang, "Commands"))]} | Acc];
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
%-------------------------------------------------------------------------
get_local_features(Acc, _From, _To, "", _Lang) ->
get_local_features(Acc, _From, _To, <<>>, _Lang) ->
Feats = case Acc of
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_COMMANDS]};
{result, Feats ++ [?NS_ADHOC_s]};
get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
get_local_features(_Acc, _From, _To, ?NS_ADHOC_b, _Lang) ->
%% override all lesser features...
{result, []};
get_local_features(_Acc, _From, _To, "ping", _Lang) ->
get_local_features(_Acc, _From, _To, <<"ping">>, _Lang) ->
%% override all lesser features...
{result, [?NS_COMMANDS]};
{result, [?NS_ADHOC_s]};
get_local_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
@ -190,9 +196,9 @@ get_sm_features(Acc, _From, _To, "", _Lang) ->
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_COMMANDS]};
{result, Feats ++ [?NS_ADHOC_s]};
get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) ->
get_sm_features(_Acc, _From, _To, ?NS_ADHOC_s, _Lang) ->
%% override all lesser features...
{result, []};
@ -201,47 +207,46 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) ->
%-------------------------------------------------------------------------
process_local_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_local_commands).
process_local_iq(From, To, IQ_Rec) ->
process_adhoc_request(From, To, IQ_Rec, adhoc_local_commands).
process_sm_iq(From, To, IQ) ->
process_adhoc_request(From, To, IQ, adhoc_sm_commands).
process_sm_iq(From, To, IQ_Rec) ->
process_adhoc_request(From, To, IQ_Rec, adhoc_sm_commands).
process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) ->
?DEBUG("About to parse ~p...", [IQ]),
case adhoc:parse_request(IQ) of
process_adhoc_request(From, To, IQ_Rec, Hook) ->
?DEBUG("About to parse ~p...", [IQ_Rec]),
case adhoc:parse_request(IQ_Rec) of
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]};
exmpp_iq:error(IQ_Rec, Error);
#adhoc_request{} = AdhocRequest ->
Host = To#jid.lserver,
case ejabberd_hooks:run_fold(Hook, Host, empty,
case ejabberd_hooks:run_fold(Hook, exmpp_jid:prep_domain(To), empty,
[From, To, AdhocRequest]) of
ignore ->
ignore;
empty ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
exmpp_iq:error(IQ_Rec, 'item-not-found');
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]};
exmpp_iq:error(IQ_Rec, Error);
Command ->
IQ#iq{type = result, sub_el = [Command]}
exmpp_iq:result(IQ_Rec, Command)
end
end.
ping_item(Acc, _From, #jid{server = Server} = _To, Lang) ->
ping_item(Acc, _From, To, Lang) ->
Server = exmpp_jid:domain(To),
Items = case Acc of
{result, I} ->
I;
_ ->
[]
end,
Nodes = [{xmlelement, "item",
[{"jid", Server},
{"node", "ping"},
{"name", translate:translate(Lang, "Ping")}],
[]}],
Nodes = [#xmlel{ns = ?NS_DISCO_INFO, name = 'item', attrs =
[?XMLATTR('jid', Server),
?XMLATTR('node', <<"ping">>),
?XMLATTR('name', translate:translate(Lang, "Ping"))]}],
{result, Items ++ Nodes}.
@ -259,7 +264,7 @@ ping_command(_Acc, _From, _To,
Lang,
"Pong")}]});
true ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
ping_command(Acc, _From, _To, _Request) ->

View File

@ -43,8 +43,9 @@
announce_commands/4,
announce_items/4]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
-record(motd, {server, packet}).
@ -52,23 +53,26 @@
-define(PROCNAME, ejabberd_announce).
-define(NS_ADMIN_s, "http://jabber.org/protocol/admin").
-define(NS_ADMINL(Sub), ["http:","jabber.org","protocol","admin", Sub]).
tokenize(Node) -> string:tokens(Node, "/#").
start(Host, _Opts) ->
HostB = list_to_binary(Host),
mnesia:create_table(motd, [{disc_copies, [node()]},
{attributes, record_info(fields, motd)}]),
mnesia:create_table(motd_users, [{disc_copies, [node()]},
{attributes, record_info(fields, motd_users)}]),
update_tables(),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
ejabberd_hooks:add(local_send_to_resource_hook, HostB,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 50),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, announce_items, 50),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
ejabberd_hooks:add(user_available_hook, Host,
ejabberd_hooks:add(disco_local_identity, HostB, ?MODULE, disco_identity, 50),
ejabberd_hooks:add(disco_local_features, HostB, ?MODULE, disco_features, 50),
ejabberd_hooks:add(disco_local_items, HostB, ?MODULE, disco_items, 50),
ejabberd_hooks:add(adhoc_local_items, HostB, ?MODULE, announce_items, 50),
ejabberd_hooks:add(adhoc_local_commands, HostB, ?MODULE, announce_commands, 50),
ejabberd_hooks:add(user_available_hook, HostB,
?MODULE, send_motd, 50),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
proc_lib:spawn(?MODULE, init, [])).
@ -113,14 +117,15 @@ loop() ->
end.
stop(Host) ->
ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, announce_commands, 50),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, announce_items, 50),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50),
ejabberd_hooks:delete(local_send_to_resource_hook, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(adhoc_local_commands, HostB, ?MODULE, announce_commands, 50),
ejabberd_hooks:delete(adhoc_local_items, HostB, ?MODULE, announce_items, 50),
ejabberd_hooks:delete(disco_local_identity, HostB, ?MODULE, disco_identity, 50),
ejabberd_hooks:delete(disco_local_features, HostB, ?MODULE, disco_features, 50),
ejabberd_hooks:delete(disco_local_items, HostB, ?MODULE, disco_items, 50),
ejabberd_hooks:delete(local_send_to_resource_hook, HostB,
?MODULE, announce, 50),
ejabberd_hooks:delete(user_available_hook, Host,
ejabberd_hooks:delete(user_available_hook, HostB,
?MODULE, send_motd, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
@ -128,39 +133,39 @@ stop(Host) ->
%% Announcing via messages to a custom resource
announce(From, To, Packet) ->
case To of
#jid{luser = "", lresource = Res} ->
{xmlelement, Name, _Attrs, _Els} = Packet,
Proc = gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME),
case {exmpp_jid:prep_node(To), exmpp_jid:prep_resource(To)} of
{undefined, Res} ->
Name = Packet#xmlel.name,
Proc = gen_mod:get_module_proc(exmpp_jid:prep_domain_as_list(To), ?PROCNAME),
case {Res, Name} of
{"announce/all", "message"} ->
{<<"announce/all">>, 'message'} ->
Proc ! {announce_all, From, To, Packet},
stop;
{"announce/all-hosts/all", "message"} ->
{<<"announce/all-hosts/all">>, 'message'} ->
Proc ! {announce_all_hosts_all, From, To, Packet},
stop;
{"announce/online", "message"} ->
{<<"announce/online">>, 'message'} ->
Proc ! {announce_online, From, To, Packet},
stop;
{"announce/all-hosts/online", "message"} ->
{<<"announce/all-hosts/online">>, 'message'} ->
Proc ! {announce_all_hosts_online, From, To, Packet},
stop;
{"announce/motd", "message"} ->
{<<"announce/motd">>, 'message'} ->
Proc ! {announce_motd, From, To, Packet},
stop;
{"announce/all-hosts/motd", "message"} ->
{<<"announce/all-hosts/motd">>, 'message'} ->
Proc ! {announce_all_hosts_motd, From, To, Packet},
stop;
{"announce/motd/update", "message"} ->
{<<"announce/motd/update">>, 'message'} ->
Proc ! {announce_motd_update, From, To, Packet},
stop;
{"announce/all-hosts/motd/update", "message"} ->
{<<"announce/all-hosts/motd/update">>, 'message'} ->
Proc ! {announce_all_hosts_motd_update, From, To, Packet},
stop;
{"announce/motd/delete", "message"} ->
{<<"announce/motd/delete">>, 'message'} ->
Proc ! {announce_motd_delete, From, To, Packet},
stop;
{"announce/all-hosts/motd/delete", "message"} ->
{<<"announce/all-hosts/motd/delete">>, 'message'} ->
Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
stop;
_ ->
@ -173,13 +178,13 @@ announce(From, To, Packet) ->
%%-------------------------------------------------------------------------
%% Announcing via ad-hoc commands
-define(INFO_COMMAND(Lang, Node),
[{xmlelement, "identity",
[{"category", "automation"},
{"type", "command-node"},
{"name", get_title(Lang, Node)}], []}]).
[#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs =
[?XMLATTR('category', <<"automation">>),
?XMLATTR('type', <<"command-node">>),
?XMLATTR('name', get_title(Lang, Node))]}]).
disco_identity(Acc, _From, _To, Node, Lang) ->
LNode = tokenize(Node),
LNode = tokenize(binary_to_list(Node)),
case LNode of
?NS_ADMINL("announce") ->
?INFO_COMMAND(Lang, Node);
@ -210,13 +215,13 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
allow ->
{result, Feats}
end).
disco_features(Acc, From, #jid{lserver = LServer} = _To,
"announce", _Lang) ->
disco_features(Acc, From, To, <<"announce">>, _Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@ -226,14 +231,14 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
_ ->
{result, []}
end
end;
disco_features(Acc, From, #jid{lserver = LServer} = _To,
Node, _Lang) ->
disco_features(Acc, From, To, Node, _Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@ -243,26 +248,26 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none),
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN ++ "#announce" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?NS_ADMIN ++ "#announce-all" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?NS_ADMIN ++ "#set-motd" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?NS_ADMIN ++ "#edit-motd" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?NS_ADMIN ++ "#delete-motd" ->
?INFO_RESULT(Allow, [?NS_COMMANDS]);
?NS_ADMIN ++ "#announce-allhosts" ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?NS_ADMIN ++ "#announce-all-allhosts" ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?NS_ADMIN ++ "#set-motd-allhosts" ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?NS_ADMIN ++ "#edit-motd-allhosts" ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
?NS_ADMIN ++ "#delete-motd-allhosts" ->
?INFO_RESULT(AllowGlobal, [?NS_COMMANDS]);
<<?NS_ADMIN_s, "#announce">> ->
?INFO_RESULT(Allow, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#announce-all">> ->
?INFO_RESULT(Allow, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#set-motd">> ->
?INFO_RESULT(Allow, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#edit-motd">> ->
?INFO_RESULT(Allow, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#delete-motd">> ->
?INFO_RESULT(Allow, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#announce-allhosts">> ->
?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#announce-all-allhosts">> ->
?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#set-motd-allhosts">> ->
?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#edit-motd-allhosts">> ->
?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]);
<<?NS_ADMIN_s, "#delete-motd-allhosts">> ->
?INFO_RESULT(AllowGlobal, [?NS_ADHOC_s]);
_ ->
Acc
end
@ -271,22 +276,23 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To,
%%-------------------------------------------------------------------------
-define(NODE_TO_ITEM(Lang, Server, Node),
{xmlelement, "item",
[{"jid", Server},
{"node", Node},
{"name", get_title(Lang, Node)}],
[]}).
#xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', attrs =
[?XMLATTR('jid', Server),
?XMLATTR('node', Node),
?XMLATTR('name', get_title(Lang, Node))]}).
-define(ITEMS_RESULT(Allow, Items),
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
allow ->
{result, Items}
end).
disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
"", Lang) ->
disco_items(Acc, From, To, <<>>, Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
Server = exmpp_jid:domain(To),
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@ -302,12 +308,13 @@ disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To,
{result, I} -> I;
_ -> []
end,
Nodes = [?NODE_TO_ITEM(Lang, Server, "announce")],
Nodes = [?NODE_TO_ITEM(Lang, Server, <<"announce">>)],
{result, Items ++ Nodes}
end
end;
disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) ->
disco_items(Acc, From, To, <<"announce">>, Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@ -315,7 +322,8 @@ disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) ->
announce_items(Acc, From, To, Lang)
end;
disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
disco_items(Acc, From, To, Node, _Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
case gen_mod:is_loaded(LServer, mod_adhoc) of
false ->
Acc;
@ -325,25 +333,25 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
AccessGlobal = gen_mod:get_module_opt(global, ?MODULE, access, none),
AllowGlobal = acl:match_rule(global, AccessGlobal, From),
case Node of
?NS_ADMIN ++ "#announce" ->
<<?NS_ADMIN_s, "#announce">> ->
?ITEMS_RESULT(Allow, []);
?NS_ADMIN ++ "#announce-all" ->
<<?NS_ADMIN_s, "#announce-all">> ->
?ITEMS_RESULT(Allow, []);
?NS_ADMIN ++ "#set-motd" ->
<<?NS_ADMIN_s, "#set-motd">> ->
?ITEMS_RESULT(Allow, []);
?NS_ADMIN ++ "#edit-motd" ->
<<?NS_ADMIN_s, "#edit-motd">> ->
?ITEMS_RESULT(Allow, []);
?NS_ADMIN ++ "#delete-motd" ->
<<?NS_ADMIN_s, "#delete-motd">> ->
?ITEMS_RESULT(Allow, []);
?NS_ADMIN ++ "#announce-allhosts" ->
<<?NS_ADMIN_s, "#announce-allhosts">> ->
?ITEMS_RESULT(AllowGlobal, []);
?NS_ADMIN ++ "#announce-all-allhosts" ->
<<?NS_ADMIN_s, "#announce-all-allhosts">> ->
?ITEMS_RESULT(AllowGlobal, []);
?NS_ADMIN ++ "#set-motd-allhosts" ->
<<?NS_ADMIN_s, "#set-motd-allhosts">> ->
?ITEMS_RESULT(AllowGlobal, []);
?NS_ADMIN ++ "#edit-motd-allhosts" ->
<<?NS_ADMIN_s, "#edit-motd-allhosts">> ->
?ITEMS_RESULT(AllowGlobal, []);
?NS_ADMIN ++ "#delete-motd-allhosts" ->
<<?NS_ADMIN_s, "#delete-motd-allhosts">> ->
?ITEMS_RESULT(AllowGlobal, []);
_ ->
Acc
@ -352,26 +360,28 @@ disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) ->
%%-------------------------------------------------------------------------
announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) ->
announce_items(Acc, From, To, Lang) ->
LServer = exmpp_jid:prep_domain_as_list(To),
Server = exmpp_jid:domain(To),
Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none),
Nodes1 = case acl:match_rule(LServer, Access1, From) of
allow ->
[?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd")];
[?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#announce">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#announce-all">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#set-motd">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#edit-motd">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#delete-motd">>)];
deny ->
[]
end,
Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none),
Nodes2 = case acl:match_rule(global, Access2, From) of
allow ->
[?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-allhosts"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#announce-all-allhosts"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#set-motd-allhosts"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#edit-motd-allhosts"),
?NODE_TO_ITEM(Lang, Server, ?NS_ADMIN ++ "#delete-motd-allhosts")];
[?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#announce-allhosts">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#announce-all-allhosts">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#set-motd-allhosts">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#edit-motd-allhosts">>),
?NODE_TO_ITEM(Lang, Server, <<?NS_ADMIN_s, "#delete-motd-allhosts">>)];
deny ->
[]
end,
@ -391,14 +401,14 @@ announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang)
commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
allow ->
announce_commands(From, To, Request)
end.
announce_commands(Acc, From, #jid{lserver = LServer} = To,
#adhoc_request{ node = Node} = Request) ->
announce_commands(Acc, From, To, #adhoc_request{ node = Node} = Request) ->
LServer = exmpp_jid:prep_domain_as_list(To),
LNode = tokenize(Node),
F = fun() ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
@ -453,7 +463,7 @@ announce_commands(From, To,
#adhoc_response{status = canceled});
XData == false, ActionIsExecute ->
%% User requests form
Elements = generate_adhoc_form(Lang, Node, To#jid.lserver),
Elements = generate_adhoc_form(Lang, Node, exmpp_jid:prep_domain_as_list(To)),
adhoc:produce_response(
Request,
#adhoc_response{status = executing,
@ -462,26 +472,26 @@ announce_commands(From, To,
%% User returns form.
case jlib:parse_xdata_submit(XData) of
invalid ->
{error, ?ERR_BAD_REQUEST};
{error, 'bad-request'};
Fields ->
handle_adhoc_form(From, To, Request, Fields)
end;
true ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end.
-define(VVALUE(Val),
{xmlelement, "value", [], [{xmlcdata, Val}]}).
#xmlel{ns = ?NS_DATA_FORMS, name = 'value', children = [#xmlcdata{cdata = Val}]}).
-define(VVALUEL(Val),
case Val of
"" -> [];
<<>> -> [];
_ -> [?VVALUE(Val)]
end).
-define(TVFIELD(Type, Var, Val),
{xmlelement, "field", [{"type", Type},
{"var", Var}],
#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs = [?XMLATTR('type', Type),
?XMLATTR('var', Var)], children =
?VVALUEL(Val)}).
-define(HFIELD(), ?TVFIELD("hidden", "FORM_TYPE", ?NS_ADMIN)).
-define(HFIELD(), ?TVFIELD(<<"hidden">>, <<"FORM_TYPE">>, list_to_binary(?NS_ADMIN_s))).
generate_adhoc_form(Lang, Node, ServerHost) ->
LNode = tokenize(Node),
@ -491,32 +501,30 @@ generate_adhoc_form(Lang, Node, ServerHost) ->
true ->
{[], []}
end,
{xmlelement, "x",
[{"xmlns", ?NS_XDATA},
{"type", "form"}],
#xmlel{ns = ?NS_DATA_FORMS, name = 'x', attrs =
[?XMLATTR('type', <<"form">>)], children =
[?HFIELD(),
{xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}]
#xmlel{ns = ?NS_DATA_FORMS, name = 'title', children = [#xmlcdata{cdata = list_to_binary(get_title(Lang, Node))}]}]
++
if (LNode == ?NS_ADMINL("delete-motd"))
or (LNode == ?NS_ADMINL("delete-motd-allhosts")) ->
[{xmlelement, "field",
[{"var", "confirm"},
{"type", "boolean"},
{"label", translate:translate(Lang, "Really delete message of the day?")}],
[{xmlelement, "value",
[],
[{xmlcdata, "true"}]}]}];
[#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
[?XMLATTR('var', <<"confirm">>),
?XMLATTR('type', <<"boolean">>),
?XMLATTR('label', translate:translate(Lang, "Really delete message of the day?"))], children =
[#xmlel{ns = ?NS_DATA_FORMS, name = 'value', children =
[#xmlcdata{cdata = <<"true">>}]}]}];
true ->
[{xmlelement, "field",
[{"var", "subject"},
{"type", "text-single"},
{"label", translate:translate(Lang, "Subject")}],
?VVALUEL(OldSubject)},
{xmlelement, "field",
[{"var", "body"},
{"type", "text-multi"},
{"label", translate:translate(Lang, "Message body")}],
?VVALUEL(OldBody)}]
[#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
[?XMLATTR('var', <<"subject">>),
?XMLATTR('type', <<"text-single">>),
?XMLATTR('label', translate:translate(Lang, "Subject"))], children =
?VVALUEL(list_to_binary(OldSubject))},
#xmlel{ns = ?NS_DATA_FORMS, name = 'field', attrs =
[?XMLATTR('var', <<"body">>),
?XMLATTR('type', <<"text-multi">>),
?XMLATTR('label', translate:translate(Lang, "Message body"))], children =
?VVALUEL(list_to_binary(OldBody))}]
end}.
join_lines([]) ->
@ -529,11 +537,12 @@ join_lines([], Acc) ->
%% Remove last newline
lists:flatten(lists:reverse(tl(Acc))).
handle_adhoc_form(From, #jid{lserver = LServer} = To,
handle_adhoc_form(From, To,
#adhoc_request{lang = Lang,
node = Node,
sessionid = SessionID},
Fields) ->
LServer = exmpp_jid:prep_domain_as_list(To),
Confirm = case lists:keysearch("confirm", 1, Fields) of
{value, {"confirm", ["true"]}} ->
true;
@ -560,30 +569,30 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
node = Node,
sessionid = SessionID,
status = completed},
Packet = {xmlelement, "message", [{"type", "normal"}],
Packet = #xmlel{ns = ?NS_JABBER_CLIENT, name = 'message', attrs = [?XMLATTR('type', <<"normal">>)], children =
if Subject /= [] ->
[{xmlelement, "subject", [],
[{xmlcdata, Subject}]}];
[#xmlel{ns = ?NS_JABBER_CLIENT, name = 'subject', children =
[#xmlcdata{cdata = list_to_binary(Subject)}]}];
true ->
[]
end ++
if Body /= [] ->
[{xmlelement, "body", [],
[{xmlcdata, Body}]}];
[#xmlel{ns = ?NS_JABBER_CLIENT, name = 'body', children =
[#xmlcdata{cdata = list_to_binary(Body)}]}];
true ->
[]
end},
Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
case {Node, Body} of
{?NS_ADMIN ++ "#delete-motd", _} ->
{?NS_ADMIN_s ++ "#delete-motd", _} ->
if Confirm ->
Proc ! {announce_motd_delete, From, To, Packet},
adhoc:produce_response(Response);
true ->
adhoc:produce_response(Response)
end;
{?NS_ADMIN ++ "#delete-motd-allhosts", _} ->
{?NS_ADMIN_s ++ "#delete-motd-allhosts", _} ->
if Confirm ->
Proc ! {announce_all_hosts_motd_delete, From, To, Packet},
adhoc:produce_response(Response);
@ -593,79 +602,79 @@ handle_adhoc_form(From, #jid{lserver = LServer} = To,
{_, []} ->
%% An announce message with no body is definitely an operator error.
%% Throw an error and give him/her a chance to send message again.
{error, ?ERRT_NOT_ACCEPTABLE(
Lang,
"No body provided for announce message")};
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acceptable',
{"en", "No body provided for announce message"})};
%% Now send the packet to ?PROCNAME.
%% We don't use direct announce_* functions because it
%% leads to large delay in response and <iq/> queries processing
{?NS_ADMIN ++ "#announce", _} ->
{?NS_ADMIN_s ++ "#announce", _} ->
Proc ! {announce_online, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#announce-allhosts", _} ->
{?NS_ADMIN_s ++ "#announce-allhosts", _} ->
Proc ! {announce_all_hosts_online, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#announce-all", _} ->
{?NS_ADMIN_s ++ "#announce-all", _} ->
Proc ! {announce_all, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#announce-all-allhosts", _} ->
{?NS_ADMIN_s ++ "#announce-all-allhosts", _} ->
Proc ! {announce_all_hosts_all, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#set-motd", _} ->
{?NS_ADMIN_s ++ "#set-motd", _} ->
Proc ! {announce_motd, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#set-motd-allhosts", _} ->
{?NS_ADMIN_s ++ "#set-motd-allhosts", _} ->
Proc ! {announce_all_hosts_motd, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#edit-motd", _} ->
{?NS_ADMIN_s ++ "#edit-motd", _} ->
Proc ! {announce_motd_update, From, To, Packet},
adhoc:produce_response(Response);
{?NS_ADMIN ++ "#edit-motd-allhosts", _} ->
{?NS_ADMIN_s ++ "#edit-motd-allhosts", _} ->
Proc ! {announce_all_hosts_motd_update, From, To, Packet},
adhoc:produce_response(Response);
_ ->
%% This can't happen, as we haven't registered any other
%% command nodes.
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end.
get_title(Lang, "announce") ->
get_title(Lang, Node) when is_list(Node) ->
get_title(Lang, list_to_binary(Node));
get_title(Lang, <<"announce">>) ->
translate:translate(Lang, "Announcements");
get_title(Lang, ?NS_ADMIN ++ "#announce-all") ->
get_title(Lang, <<?NS_ADMIN_s, "#announce-all">>) ->
translate:translate(Lang, "Send announcement to all users");
get_title(Lang, ?NS_ADMIN ++ "#announce-all-allhosts") ->
get_title(Lang, <<?NS_ADMIN_s , "#announce-all-allhosts">>) ->
translate:translate(Lang, "Send announcement to all users on all hosts");
get_title(Lang, ?NS_ADMIN ++ "#announce") ->
get_title(Lang, <<?NS_ADMIN_s , "#announce">>) ->
translate:translate(Lang, "Send announcement to all online users");
get_title(Lang, ?NS_ADMIN ++ "#announce-allhosts") ->
get_title(Lang, <<?NS_ADMIN_s , "#announce-allhosts">>) ->
translate:translate(Lang, "Send announcement to all online users on all hosts");
get_title(Lang, ?NS_ADMIN ++ "#set-motd") ->
get_title(Lang, <<?NS_ADMIN_s , "#set-motd">>) ->
translate:translate(Lang, "Set message of the day and send to online users");
get_title(Lang, ?NS_ADMIN ++ "#set-motd-allhosts") ->
get_title(Lang, <<?NS_ADMIN_s , "#set-motd-allhosts">>) ->
translate:translate(Lang, "Set message of the day on all hosts and send to online users");
get_title(Lang, ?NS_ADMIN ++ "#edit-motd") ->
get_title(Lang, <<?NS_ADMIN_s , "#edit-motd">>) ->
translate:translate(Lang, "Update message of the day (don't send)");
get_title(Lang, ?NS_ADMIN ++ "#edit-motd-allhosts") ->
get_title(Lang, <<?NS_ADMIN_s , "#edit-motd-allhosts">>) ->
translate:translate(Lang, "Update message of the day on all hosts (don't send)");
get_title(Lang, ?NS_ADMIN ++ "#delete-motd") ->
get_title(Lang, <<?NS_ADMIN_s , "#delete-motd">>) ->
translate:translate(Lang, "Delete message of the day");
get_title(Lang, ?NS_ADMIN ++ "#delete-motd-allhosts") ->
get_title(Lang, <<?NS_ADMIN_s , "#delete-motd-allhosts">>) ->
translate:translate(Lang, "Delete message of the day on all hosts").
%%-------------------------------------------------------------------------
announce_all(From, To, Packet) ->
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
Local = jlib:make_jid("", To#jid.server, ""),
Local = exmpp_jid:make(exmpp_jid:domain(To)),
lists:foreach(
fun({User, Server}) ->
Dest = jlib:make_jid(User, Server, ""),
Dest = exmpp_jid:make(User, Server),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:get_vh_registered_users(Host))
end.
@ -674,27 +683,27 @@ announce_all_hosts_all(From, To, Packet) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
Local = jlib:make_jid("", To#jid.server, ""),
Local = exmpp_jid:make(exmpp_jid:domain(To)),
lists:foreach(
fun({User, Server}) ->
Dest = jlib:make_jid(User, Server, ""),
Dest = exmpp_jid:make(User, Server),
ejabberd_router:route(Local, Dest, Packet)
end, ejabberd_auth:dirty_get_registered_users())
end.
announce_online(From, To, Packet) ->
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:get_vh_session_list(Host),
To#jid.server,
announce_online1(ejabberd_sm:get_vh_session_list(exmpp_jid:prep_domain(To)),
exmpp_jid:domain_as_list(To),
Packet)
end.
@ -702,28 +711,28 @@ announce_all_hosts_online(From, To, Packet) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
announce_online1(ejabberd_sm:dirty_get_sessions_list(),
To#jid.server,
exmpp_jid:domain_as_list(To),
Packet)
end.
announce_online1(Sessions, Server, Packet) ->
Local = jlib:make_jid("", Server, ""),
Local = exmpp_jid:make(Server),
lists:foreach(
fun({U, S, R}) ->
Dest = jlib:make_jid(U, S, R),
Dest = exmpp_jid:make(U, S, R),
ejabberd_router:route(Local, Dest, Packet)
end, Sessions).
announce_motd(From, To, Packet) ->
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd(Host, Packet)
@ -733,7 +742,7 @@ announce_all_hosts_motd(From, To, Packet) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@ -742,7 +751,7 @@ announce_all_hosts_motd(From, To, Packet) ->
announce_motd(Host, Packet) ->
announce_motd_update(Host, Packet),
Sessions = ejabberd_sm:get_vh_session_list(Host),
Sessions = ejabberd_sm:get_vh_session_list(list_to_binary(Host)),
announce_online1(Sessions, Host, Packet),
F = fun() ->
lists:foreach(
@ -753,11 +762,11 @@ announce_motd(Host, Packet) ->
mnesia:transaction(F).
announce_motd_update(From, To, Packet) ->
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_update(Host, Packet)
@ -767,7 +776,7 @@ announce_all_hosts_motd_update(From, To, Packet) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@ -782,11 +791,11 @@ announce_motd_update(LServer, Packet) ->
mnesia:transaction(F).
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
Access = gen_mod:get_module_opt(Host, ?MODULE, access, none),
case acl:match_rule(Host, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
announce_motd_delete(Host)
@ -796,7 +805,7 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
Access = gen_mod:get_module_opt(global, ?MODULE, access, none),
case acl:match_rule(global, Access, From) of
deny ->
Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN),
Err = exmpp_stanza:reply_with_error(Packet, 'forbidden'),
ejabberd_router:route(To, From, Err);
allow ->
Hosts = ?MYHOSTS,
@ -818,7 +827,9 @@ announce_motd_delete(LServer) ->
end,
mnesia:transaction(F).
send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
send_motd(JID) ->
LServer = exmpp_jid:prep_domain_as_list(JID),
LUser = exmpp_jid:prep_node_as_list(JID),
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
US = {LUser, LServer},
@ -826,7 +837,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
[#motd_users{}] ->
ok;
_ ->
Local = jlib:make_jid("", LServer, ""),
Local = exmpp_jid:make(exmpp_jid:prep_domain(JID)),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
mnesia:write(#motd_users{us = US})
@ -840,8 +851,8 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) ->
get_stored_motd(LServer) ->
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
{xml:get_subtag_cdata(Packet, "subject"),
xml:get_subtag_cdata(Packet, "body")};
{exmpp_xml:get_cdata_as_list(exmpp_xml:get_element(Packet, 'subject')),
exmpp_xml:get_cdata_as_list(exmpp_xml:get_element(Packet, 'body'))};
_ ->
{"", ""}
end.

View File

@ -54,6 +54,8 @@
code_change/3
]).
-include_lib("exmpp/include/exmpp.hrl").
%% hook handlers
-export([receive_packet/3,
receive_packet/4,
@ -61,7 +63,6 @@
remove_connection/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_caps).
-define(DICT, dict).
@ -81,23 +82,13 @@
%% capabilities are advertised.
read_caps(Els) ->
read_caps(Els, nothing).
read_caps([{xmlelement, "c", Attrs, _Els} | Tail], Result) ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_CAPS ->
Node = xml:get_attr_s("node", Attrs),
Version = xml:get_attr_s("ver", Attrs),
Exts = string:tokens(xml:get_attr_s("ext", Attrs), " "),
read_caps(Tail, #caps{node = Node, version = Version, exts = Exts});
_ ->
read_caps(Tail, Result)
end;
read_caps([{xmlelement, "x", Attrs, _Els} | Tail], Result) ->
case xml:get_attr_s("xmlns", Attrs) of
?NS_MUC_USER ->
nothing;
_ ->
read_caps(Tail, Result)
end;
read_caps([#xmlel{ns = ?NS_CAPS, name = 'c'} = El | Tail], _Result) ->
Node = exmpp_xml:get_attribute_as_list(El, 'node', ""),
Version = exmpp_xml:get_attribute_as_list(El, 'ver', ""),
Exts = string:tokens(exmpp_xml:get_attribute_as_list(El, 'ext', ""), " "),
read_caps(Tail, #caps{node = Node, version = Version, exts = Exts});
read_caps([#xmlel{ns = ?NS_MUC_USER, name = 'x'} | _Tail], _Result) ->
nothing;
read_caps([_ | Tail], Result) ->
read_caps(Tail, Result);
read_caps([], Result) ->
@ -114,11 +105,12 @@ get_caps(LJID) ->
get_caps(LJID, 5).
get_caps(_, 0) ->
nothing;
get_caps(LJID, Retry) ->
case catch mnesia:dirty_read({user_caps, jid_to_binary(LJID)}) of
get_caps({U, S, R}, Retry) ->
BJID = exmpp_jid:to_binary(U, S, R),
case catch mnesia:dirty_read({user_caps, BJID}) of
[#user_caps{caps=waiting}] ->
timer:sleep(2000),
get_caps(LJID, Retry-1);
get_caps({U, S, R}, Retry-1);
[#user_caps{caps=Caps}] ->
Caps;
_ ->
@ -127,16 +119,17 @@ get_caps(LJID, Retry) ->
%% clear_caps removes user caps from database
clear_caps(JID) ->
{U, S, R} = jlib:jid_tolower(JID),
BJID = jid_to_binary(JID),
BUID = jid_to_binary({U, S, []}),
R = exmpp_jid:prep_resource(JID),
BJID = exmpp_jid:to_binary(JID),
BUID = exmpp_jid:bare_to_binary(JID),
catch mnesia:dirty_delete({user_caps, BJID}),
catch mnesia:dirty_delete_object(#user_caps_resources{uid = BUID, resource = list_to_binary(R)}),
ok.
%% give default user resource
get_user_resources(LUser, LServer) ->
case catch mnesia:dirty_read({user_caps_resources, jid_to_binary({LUser, LServer, []})}) of
get_user_resources(U, S) ->
BUID = exmpp_jid:bare_to_binary(U, S),
case catch mnesia:dirty_read({user_caps_resources, BUID}) of
{'EXIT', _} ->
[];
Resources ->
@ -188,25 +181,25 @@ stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop).
receive_packet(From, To, {xmlelement, "presence", Attrs, Els}) ->
case xml:get_attr_s("type", Attrs) of
"probe" ->
receive_packet(From, To, Packet) when ?IS_PRESENCE(Packet) ->
case exmpp_presence:get_type(Packet) of
'probe' ->
ok;
"error" ->
'error' ->
ok;
"invisible" ->
'invisible' ->
ok;
"subscribe" ->
'subscribe' ->
ok;
"subscribed" ->
'subscribed' ->
ok;
"unsubscribe" ->
'unsubscribe' ->
ok;
"unsubscribed" ->
'unsubscribed' ->
ok;
"unavailable" ->
{_, S1, _} = jlib:jid_tolower(From),
case jlib:jid_tolower(To) of
'unavailable' ->
{_, S1, _} = jlib:short_prepd_jid(From),
case jlib:short_prepd_jid(To) of
{_, S1, _} -> ok;
_ -> clear_caps(From)
end;
@ -220,7 +213,9 @@ receive_packet(From, To, {xmlelement, "presence", Attrs, Els}) ->
%% anymore until he login again.
%% This is tracked in EJAB-943
_ ->
note_caps(To#jid.lserver, From, read_caps(Els))
ServerString = exmpp_jid:prep_domain_as_list(To),
Els = Packet#xmlel.children,
note_caps(ServerString, From, read_caps(Els))
end;
receive_packet(_, _, _) ->
ok.
@ -229,15 +224,12 @@ receive_packet(_JID, From, To, Packet) ->
receive_packet(From, To, Packet).
presence_probe(From, To, _) ->
wait_caps(To#jid.lserver, From).
ServerString = exmpp_jid:prep_domain_as_list(To),
wait_caps(ServerString, From).
remove_connection(_SID, JID, _Info) ->
clear_caps(JID).
jid_to_binary(JID) ->
{U, S, R} = jlib:jid_tolower(JID),
list_to_binary(jlib:jid_to_string({U, S, R})).
caps_to_binary(#caps{node = Node, version = Version, exts = Exts}) ->
BExts = [list_to_binary(Ext) || Ext <- Exts],
#caps{node = list_to_binary(Node), version = list_to_binary(Version), exts = BExts}.
@ -312,7 +304,7 @@ handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
handle_cast({note_caps, From, nothing}, State) ->
BJID = jid_to_binary(From),
BJID = exmpp_jid:to_binary(From),
catch mnesia:dirty_delete({user_caps, BJID}),
{noreply, State};
handle_cast({note_caps, From,
@ -320,29 +312,32 @@ handle_cast({note_caps, From,
#state{host = Host, disco_requests = Requests} = State) ->
%% XXX: this leads to race conditions where ejabberd will send
%% lots of caps disco requests.
{U, S, R} = jlib:jid_tolower(From),
BJID = jid_to_binary(From),
%#jid{node = U, domain = S, resource = R} = From,
U = exmpp_jid:prep_node(From),
S = exmpp_jid:prep_domain(From),
R = exmpp_jid:resource(From),
BJID = exmpp_jid:to_binary(From),
mnesia:transaction(fun() ->
mnesia:write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
mnesia:dirty_write(#user_caps{jid = BJID, caps = caps_to_binary(Caps)}),
case ejabberd_sm:get_user_resources(U, S) of
[] ->
% only store resources of caps aware external contacts
BUID = jid_to_binary({U, S, []}),
mnesia:write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
_ ->
% only store resource of caps aware external contacts
BUID = exmpp_jid:bare_to_binary(From),
mnesia:dirty_write(#user_caps_resources{uid = BUID, resource = list_to_binary(R)});
_ ->
ok
end
end),
%% Now, find which of these are not already in the database.
SubNodes = [Version | Exts],
case lists:foldl(fun(SubNode, Acc) ->
case mnesia:dirty_read({caps_features, node_to_binary(Node, SubNode)}) of
[] ->
[SubNode | Acc];
_ ->
Acc
end
end, [], SubNodes) of
case mnesia:dirty_read({caps_features, {Node, SubNode}}) of
[] ->
[SubNode | Acc];
_ ->
Acc
end
end, [], SubNodes) of
[] ->
{noreply, State};
Missing ->
@ -350,37 +345,31 @@ handle_cast({note_caps, From,
NewRequests = lists:foldl(
fun(SubNode, Dict) ->
ID = randoms:get_string(),
Stanza =
{xmlelement, "iq",
[{"type", "get"},
{"id", ID}],
[{xmlelement, "query",
[{"xmlns", ?NS_DISCO_INFO},
{"node", lists:concat([Node, "#", SubNode])}],
[]}]},
Query = exmpp_xml:set_attribute(
#xmlel{ns = ?NS_DISCO_INFO, name = 'query'},
'node', lists:concat([Node, "#", SubNode])),
Stanza = exmpp_iq:get(?NS_JABBER_CLIENT, Query, ID),
ejabberd_local:register_iq_response_handler
(Host, ID, ?MODULE, handle_disco_response),
ejabberd_router:route(jlib:make_jid("", Host, ""), From, Stanza),
(list_to_binary(Host), ID, ?MODULE, handle_disco_response),
ejabberd_router:route(exmpp_jid:make(Host), From, Stanza),
timer:send_after(?CAPS_QUERY_TIMEOUT, self(), {disco_timeout, ID, BJID}),
?DICT:store(ID, node_to_binary(Node, SubNode), Dict)
end, Requests, Missing),
{noreply, State#state{disco_requests = NewRequests}}
end;
handle_cast({wait_caps, From}, State) ->
BJID = jid_to_binary(From),
BJID = exmpp_jid:to_binary(From),
mnesia:dirty_write(#user_caps{jid = BJID, caps = waiting}),
{noreply, State};
handle_cast({disco_response, From, _To,
#iq{type = Type, id = ID,
sub_el = SubEls}},
handle_cast({disco_response, From, _To, #iq{id = ID, type = Type, payload = Payload}},
#state{disco_requests = Requests} = State) ->
case {Type, SubEls} of
{result, [{xmlelement, "query", _Attrs, Els}]} ->
case {Type, Payload} of
{result, #xmlel{name = 'query', children = Els}} ->
case ?DICT:find(ID, Requests) of
{ok, BinaryNode} ->
Features =
lists:flatmap(fun({xmlelement, "feature", FAttrs, _}) ->
[xml:get_attr_s("var", FAttrs)];
lists:flatmap(fun(#xmlel{name = 'feature'} = F) ->
[exmpp_xml:get_attribute_as_list(F, 'var', "")];
(_) ->
[]
end, Els),
@ -399,9 +388,9 @@ handle_cast({disco_response, From, _To,
?DEBUG("ID '~s' matches no query", [ID])
end;
%gen_server:cast(self(), visit_feature_queries),
%?DEBUG("Error IQ reponse from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
{result, _} ->
?DEBUG("Invalid IQ contents from ~s:~n~p", [jlib:jid_to_string(From), SubEls]);
%?DEBUG("Error IQ reponse from ~s:~n~p", [exmpp_jid:to_list(From), SubEls]);
{result, Payload} ->
?DEBUG("Invalid IQ contents from ~s:~n~p", [exmpp_jid:to_binary(From), Payload]);
_ ->
%% Can't do anything about errors
ok
@ -413,7 +402,7 @@ handle_cast({disco_timeout, ID, BJID}, #state{host = Host, disco_requests = Requ
NewRequests = case ?DICT:is_key(ID, Requests) of
true ->
catch mnesia:dirty_delete({user_caps, BJID}),
ejabberd_local:unregister_iq_response_handler(Host, ID),
ejabberd_local:unregister_iq_response_handler(list_to_binary(Host), ID),
?DICT:erase(ID, Requests);
false ->
Requests
@ -433,10 +422,10 @@ handle_cast(visit_feature_queries, #state{feature_queries = FeatureQueries} = St
end, [], FeatureQueries),
{noreply, State#state{feature_queries = NewFeatureQueries}}.
handle_disco_response(From, To, IQ) ->
#jid{lserver = Host} = To,
handle_disco_response(From, To, IQ_Rec) ->
Host = exmpp_jid:prep_domain_as_list(To),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:cast(Proc, {disco_response, From, To, IQ}).
gen_server:cast(Proc, {disco_response, From, To, IQ_Rec}).
handle_info(_Info, State) ->
{noreply, State}.

File diff suppressed because it is too large Load Diff

View File

@ -33,30 +33,51 @@
stop/1,
process_local_iq/3]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-define(NS_ECONFIGURE, "http://ejabberd.jabberstudio.org/protocol/configure").
-include("ejabberd.hrl").
-define(NS_ECONFIGURE, 'http://ejabberd.jabberstudio.org/protocol/configure').
-define(NS_ECONFIGURE_s, "http://ejabberd.jabberstudio.org/protocol/configure").
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE,
gen_iq_handler:add_iq_handler(ejabberd_local, list_to_binary(Host), ?NS_ECONFIGURE,
?MODULE, process_local_iq, IQDisc),
% Add nss/names/attrs used by this module to the known lists of Exmpp.
exmpp_xml:add_known_nss(xmpp, [?NS_ECONFIGURE]),
exmpp_xml:add_known_elems(xmpp, [
'access',
'acls',
'body',
'info',
'jid',
'last',
'registration-watchers',
'subject',
'welcome-message'
]),
exmpp_xml:add_known_attrs(xmpp, [
'online-users',
'outgoing-s2s-servers',
'registered-users',
'running-nodes',
'stopped-nodes'
]),
ok.
stop(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ECONFIGURE).
gen_iq_handler:remove_iq_handler(ejabberd_local, list_to_binary(Host), ?NS_ECONFIGURE).
process_local_iq(From, To, #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ) ->
case acl:match_rule(To#jid.lserver, configure, From) of
process_local_iq(From, To, #iq{type = Type, payload = Request} = IQ_Rec) ->
case acl:match_rule(exmpp_jid:prep_domain_as_list(To), configure, From) of
deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
exmpp_iq:error(IQ_Rec, 'not-allowed');
allow ->
case Type of
set ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]};
exmpp_iq:error(IQ_Rec, 'feature-not-implemented');
%%case xml:get_tag_attr_s("type", SubEl) of
%% "cancel" ->
%% IQ#iq{type = result,
@ -90,56 +111,54 @@ process_local_iq(From, To, #iq{type = Type, lang = _Lang, sub_el = SubEl} = IQ)
%% sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
%%end;
get ->
case process_get(SubEl) of
case process_get(Request) of
{result, Res} ->
IQ#iq{type = result, sub_el = [Res]};
exmpp_iq:result(IQ_Rec, Res);
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
exmpp_iq:error(IQ_Rec, Error)
end
end
end.
process_get({xmlelement, "info", _Attrs, _SubEls}) ->
process_get(#xmlel{ns = ?NS_ECONFIGURE, name = 'info'}) ->
S2SConns = ejabberd_s2s:dirty_get_connections(),
TConns = lists:usort([element(2, C) || C <- S2SConns]),
Attrs = [{"registered-users",
integer_to_list(mnesia:table_info(passwd, size))},
{"online-users",
integer_to_list(mnesia:table_info(presence, size))},
{"running-nodes",
integer_to_list(length(mnesia:system_info(running_db_nodes)))},
{"stopped-nodes",
integer_to_list(
length(lists:usort(mnesia:system_info(db_nodes) ++
mnesia:system_info(extra_db_nodes)) --
mnesia:system_info(running_db_nodes)))},
{"outgoing-s2s-servers", integer_to_list(length(TConns))}],
{result, {xmlelement, "info",
[{"xmlns", ?NS_ECONFIGURE} | Attrs], []}};
process_get({xmlelement, "welcome-message", Attrs, _SubEls}) ->
Attrs = [?XMLATTR('registered-users', mnesia:table_info(passwd, size)),
?XMLATTR('online-users', mnesia:table_info(presence, size)),
?XMLATTR('running-nodes',
length(mnesia:system_info(running_db_nodes))),
?XMLATTR('stopped-nodes',
length(lists:usort(mnesia:system_info(db_nodes) ++
mnesia:system_info(extra_db_nodes)) --
mnesia:system_info(running_db_nodes))),
?XMLATTR('outgoing-s2s-servers',
length(TConns))],
{result, #xmlel{ns = ?NS_ECONFIGURE, name = 'info', attrs = Attrs}};
process_get(#xmlel{ns = ?NS_ECONFIGURE, name = 'welcome-message', attrs = Attrs}) ->
{Subj, Body} = case ejabberd_config:get_local_option(welcome_message) of
{_Subj, _Body} = SB -> SB;
_ -> {"", ""}
end,
{result, {xmlelement, "welcome-message", Attrs,
[{xmlelement, "subject", [], [{xmlcdata, Subj}]},
{xmlelement, "body", [], [{xmlcdata, Body}]}]}};
process_get({xmlelement, "registration-watchers", Attrs, _SubEls}) ->
{result, #xmlel{ns = ?NS_ECONFIGURE, name = 'welcome-message',
attrs = Attrs, children =
[#xmlel{ns = ?NS_ECONFIGURE, name = 'subject', children = [#xmlcdata{cdata = list_to_binary(Subj)}]},
#xmlel{ns = ?NS_ECONFIGURE, name = 'body', children = [#xmlcdata{cdata = list_to_binary(Body)}]}]}};
process_get(#xmlel{ns = ?NS_ECONFIGURE, name = 'registration-watchers', attrs = Attrs}) ->
SubEls =
case ejabberd_config:get_local_option(registration_watchers) of
JIDs when is_list(JIDs) ->
lists:map(fun(JID) ->
{xmlelement, "jid", [], [{xmlcdata, JID}]}
#xmlel{ns = ?NS_ECONFIGURE, name = 'jid', children = [#xmlcdata{cdata = list_to_binary(JID)}]}
end, JIDs);
_ ->
[]
end,
{result, {xmlelement, "registration_watchers", Attrs, SubEls}};
process_get({xmlelement, "acls", Attrs, _SubEls}) ->
{result, #xmlel{ns = ?NS_ECONFIGURE, name = 'registration_watchers', attrs = Attrs, children = SubEls}};
process_get(#xmlel{ns = ?NS_ECONFIGURE, name = 'acls', attrs = Attrs}) ->
Str = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])),
{result, {xmlelement, "acls", Attrs, [{xmlcdata, Str}]}};
process_get({xmlelement, "access", Attrs, _SubEls}) ->
{result, #xmlel{ns = ?NS_ECONFIGURE, name = 'acls', attrs = Attrs, children = [#xmlcdata{cdata = list_to_binary(Str)}]}};
process_get(#xmlel{ns = ?NS_ECONFIGURE, name = 'access', attrs = Attrs}) ->
Str =
lists:flatten(
io_lib:format(
@ -149,22 +168,22 @@ process_get({xmlelement, "access", Attrs, _SubEls}) ->
[],
[{{access, '$1', '$2'}}]}])
])),
{result, {xmlelement, "access", Attrs, [{xmlcdata, Str}]}};
process_get({xmlelement, "last", Attrs, _SubEls}) ->
{result, #xmlel{ns = ?NS_ECONFIGURE, name = 'access', attrs = Attrs, children = [#xmlcdata{cdata = list_to_binary(Str)}]}};
process_get(#xmlel{ns = ?NS_ECONFIGURE, name = 'last', attrs = Attrs}) ->
case catch mnesia:dirty_select(
last_activity, [{{last_activity, '_', '$1', '_'}, [], ['$1']}]) of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, 'internal-server-error'};
Vals ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
Str = lists:flatten(
lists:append(
[[integer_to_list(TimeStamp - V), " "] || V <- Vals])),
{result, {xmlelement, "last", Attrs, [{xmlcdata, Str}]}}
{result, #xmlel{ns = ?NS_ECONFIGURE, name = 'last', attrs = Attrs, children = [#xmlcdata{cdata = list_to_binary(Str)}]}}
end;
%%process_get({xmlelement, Name, Attrs, SubEls}) ->
%% {result, };
process_get(_) ->
{error, ?ERR_BAD_REQUEST}.
{error, 'bad-request'}.

View File

@ -1,7 +1,7 @@
%%%----------------------------------------------------------------------
%%% File : mod_disco.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Service Discovery (XEP-0030) support
%%% Purpose : Service Discovery (JEP-0030) support
%%% Created : 1 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
@ -47,20 +47,23 @@
register_extra_domain/2,
unregister_extra_domain/2]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
start(Host, Opts) ->
HostB = list_to_binary(Host),
ejabberd_local:refresh_iq_handlers(),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_DISCO_ITEMS,
?MODULE, process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_DISCO_INFO,
?MODULE, process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_DISCO_ITEMS,
?MODULE, process_sm_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_DISCO_INFO,
?MODULE, process_sm_iq_info, IQDisc),
catch ets:new(disco_features, [named_table, ordered_set, public]),
@ -74,27 +77,28 @@ start(Host, Opts) ->
ExtraDomains),
catch ets:new(disco_sm_features, [named_table, ordered_set, public]),
catch ets:new(disco_sm_nodes, [named_table, ordered_set, public]),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_services, 100),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 100),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, get_info, 100),
ejabberd_hooks:add(disco_local_items, HostB, ?MODULE, get_local_services, 100),
ejabberd_hooks:add(disco_local_features, HostB, ?MODULE, get_local_features, 100),
ejabberd_hooks:add(disco_local_identity, HostB, ?MODULE, get_local_identity, 100),
ejabberd_hooks:add(disco_sm_items, HostB, ?MODULE, get_sm_items, 100),
ejabberd_hooks:add(disco_sm_features, HostB, ?MODULE, get_sm_features, 100),
ejabberd_hooks:add(disco_sm_identity, HostB, ?MODULE, get_sm_identity, 100),
ejabberd_hooks:add(disco_info, HostB, ?MODULE, get_info, 100),
ok.
stop(Host) ->
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 100),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 100),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 100),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_services, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, get_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
HostB = list_to_binary(Host),
ejabberd_hooks:delete(disco_sm_identity, HostB, ?MODULE, get_sm_identity, 100),
ejabberd_hooks:delete(disco_sm_features, HostB, ?MODULE, get_sm_features, 100),
ejabberd_hooks:delete(disco_sm_items, HostB, ?MODULE, get_sm_items, 100),
ejabberd_hooks:delete(disco_local_identity, HostB, ?MODULE, get_local_identity, 100),
ejabberd_hooks:delete(disco_local_features, HostB, ?MODULE, get_local_features, 100),
ejabberd_hooks:delete(disco_local_items, HostB, ?MODULE, get_local_services, 100),
ejabberd_hooks:delete(disco_info, HostB, ?MODULE, get_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_DISCO_INFO),
catch ets:match_delete(disco_features, {{'_', Host}}),
catch ets:match_delete(disco_extra_domains, {{'_', Host}}),
ok.
@ -116,74 +120,66 @@ unregister_extra_domain(Host, Domain) ->
catch ets:new(disco_extra_domains, [named_table, ordered_set, public]),
ets:delete(disco_extra_domains, {Domain, Host}).
process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Node = xml:get_tag_attr_s("node", SubEl),
Host = To#jid.lserver,
process_local_iq_items(From, To, #iq{type = get, payload = SubEl,
lang = Lang} = IQ_Rec) ->
Node = exmpp_xml:get_attribute_as_binary(SubEl, 'node', <<>>),
case ejabberd_hooks:run_fold(disco_local_items,
Host,
empty,
[From, To, Node, Lang]) of
{result, Items} ->
ANode = case Node of
"" -> [];
_ -> [{"node", Node}]
case ejabberd_hooks:run_fold(disco_local_items,
exmpp_jid:prep_domain(To),
empty,
[From, To, Node, Lang]) of
{result, Items} ->
ANode = case Node of
<<>> -> [];
_ -> [?XMLATTR('node', Node)]
end,
Result = #xmlel{ns = ?NS_DISCO_ITEMS, name = 'query',
attrs = ANode, children = Items},
exmpp_iq:result(IQ_Rec, Result);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end;
process_local_iq_items(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
process_local_iq_info(From, To, #iq{type = get, payload = SubEl,
lang = Lang} = IQ_Rec) ->
Node = exmpp_xml:get_attribute_as_binary(SubEl, 'node', <<>>),
HostB = exmpp_jid:prep_domain(To),
Identity = ejabberd_hooks:run_fold(disco_local_identity,
HostB,
[],
[From, To, Node, Lang]),
Host = exmpp_jid:prep_domain_as_list(To),
Info = ejabberd_hooks:run_fold(disco_info, HostB, [],
[Host, ?MODULE, Node, Lang]),
case ejabberd_hooks:run_fold(disco_local_features,
exmpp_jid:prep_domain(To),
empty,
[From, To, Node, Lang]) of
{result, Features} ->
ANode = case Node of
<<>> -> [];
_ -> [?XMLATTR('node', Node)]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_ITEMS} | ANode],
Items
}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end
end.
Result = #xmlel{ns = ?NS_DISCO_INFO, name = 'query',
attrs = ANode,
children = Identity ++ Info ++ lists:map(fun feature_to_xml/1,
Features)},
exmpp_iq:result(IQ_Rec, Result);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end;
process_local_iq_info(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
process_local_iq_info(From, To, #iq{type = Type, lang = Lang,
sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Host = To#jid.lserver,
Node = xml:get_tag_attr_s("node", SubEl),
Identity = ejabberd_hooks:run_fold(disco_local_identity,
Host,
[],
[From, To, Node, Lang]),
Info = ejabberd_hooks:run_fold(disco_info, Host, [],
[Host, ?MODULE, Node, Lang]),
case ejabberd_hooks:run_fold(disco_local_features,
Host,
empty,
[From, To, Node, Lang]) of
{result, Features} ->
ANode = case Node of
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_INFO} | ANode],
Identity ++
Info ++
lists:map(fun feature_to_xml/1, Features)
}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end
end.
get_local_identity(Acc, _From, _To, [], _Lang) ->
Acc ++ [{xmlelement, "identity",
[{"category", "server"},
{"type", "im"},
{"name", "ejabberd"}], []}];
get_local_identity(Acc, _From, _To, <<>>, _Lang) ->
Acc ++ [#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs = [
?XMLATTR('category', <<"server">>),
?XMLATTR('type', <<"im">>),
?XMLATTR('name', <<"ejabberd">>)
]}];
get_local_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
@ -191,12 +187,12 @@ get_local_identity(Acc, _From, _To, _Node, _Lang) ->
get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
get_local_features(Acc, _From, To, [], _Lang) ->
get_local_features(Acc, _From, To, <<>>, _Lang) ->
Feats = case Acc of
{result, Features} -> Features;
empty -> []
end,
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
{result,
ets:select(disco_features, [{{{'_', Host}}, [], ['$_']}]) ++ Feats};
@ -205,29 +201,41 @@ get_local_features(Acc, _From, _To, _Node, _Lang) ->
{result, _Features} ->
Acc;
empty ->
{error, ?ERR_ITEM_NOT_FOUND}
{error, 'item-not-found'}
end.
feature_to_xml({{Feature, _Host}}) ->
feature_to_xml(Feature);
feature_to_xml(Feature) when is_binary(Feature) ->
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs = [
?XMLATTR('var', Feature)
]};
feature_to_xml(Feature) when is_list(Feature) ->
{xmlelement, "feature", [{"var", Feature}], []}.
feature_to_xml(list_to_binary(Feature));
feature_to_xml(Feature) when is_atom(Feature) ->
feature_to_xml(atom_to_list(Feature)).
domain_to_xml({Domain}) ->
{xmlelement, "item", [{"jid", Domain}], []};
domain_to_xml(Domain) ->
{xmlelement, "item", [{"jid", Domain}], []}.
domain_to_xml(Domain);
domain_to_xml(Domain) when is_binary(Domain)->
#xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', attrs = [
?XMLATTR('jid', Domain)
]};
domain_to_xml(Domain) when is_list(Domain) ->
domain_to_xml(list_to_binary(Domain)).
get_local_services({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
get_local_services(Acc, _From, To, [], _Lang) ->
get_local_services(Acc, _From, To, <<>>, _Lang) ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
Host = To#jid.lserver,
Host = exmpp_jid:prep_domain_as_list(To),
{result,
lists:usort(
lists:map(fun domain_to_xml/1,
@ -240,7 +248,7 @@ get_local_services({result, _} = Acc, _From, _To, _Node, _Lang) ->
Acc;
get_local_services(empty, _From, _To, _Node, _Lang) ->
{error, ?ERR_ITEM_NOT_FOUND}.
{error, 'item-not-found'}.
get_vh_services(Host) ->
Hosts = lists:sort(fun(H1, H2) -> length(H1) >= length(H2) end, ?MYHOSTS),
@ -258,45 +266,42 @@ get_vh_services(Host) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Host = To#jid.lserver,
Node = xml:get_tag_attr_s("node", SubEl),
case ejabberd_hooks:run_fold(disco_sm_items,
Host,
empty,
[From, To, Node, Lang]) of
{result, Items} ->
ANode = case Node of
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_ITEMS} | ANode],
Items
}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end
end.
process_sm_iq_items(From, To, #iq{type = get, payload = SubEl,
lang = Lang} = IQ_Rec) ->
Node = exmpp_xml:get_attribute_as_binary(SubEl, 'node', <<>>),
case ejabberd_hooks:run_fold(disco_sm_items,
exmpp_jid:prep_domain(To),
empty,
[From, To, Node, Lang]) of
{result, Items} ->
ANode = case Node of
<<>> -> [];
_ -> [?XMLATTR('node', Node)]
end,
Result = #xmlel{ns = ?NS_DISCO_ITEMS, name = 'query',
attrs = ANode, children = Items},
exmpp_iq:result(IQ_Rec, Result);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end;
process_sm_iq_items(_From, _To, #iq{type = set, payload = _SubEl} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
Acc;
get_sm_items(Acc,
#jid{luser = LFrom, lserver = LSFrom},
#jid{user = User, server = Server, luser = LTo, lserver = LSTo} = _To,
[], _Lang) ->
get_sm_items(Acc, From, To, <<>>, _Lang) ->
LFrom = exmpp_jid:prep_node_as_list(From),
LSFrom = exmpp_jid:prep_domain_as_list(From),
LTo = exmpp_jid:prep_node_as_list(To),
LSTo = exmpp_jid:prep_domain_as_list(To),
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
Items1 = case {LFrom, LSFrom} of
{LTo, LSTo} -> get_user_resources(User, Server);
{LTo, LSTo} -> [binary_to_list(R) || R <- get_user_resources(To)];
_ -> []
end,
{result, Items ++ Items1};
@ -305,87 +310,92 @@ get_sm_items({result, _} = Acc, _From, _To, _Node, _Lang) ->
Acc;
get_sm_items(empty, From, To, _Node, _Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
LFrom = exmpp_jid:prep_node_as_list(From),
LSFrom = exmpp_jid:prep_domain_as_list(From),
LTo = exmpp_jid:prep_node_as_list(To),
LSTo = exmpp_jid:prep_domain_as_list(To),
case {LFrom, LSFrom} of
{LTo, LSTo} ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
_ ->
{error, ?ERR_NOT_ALLOWED}
{error, 'not-allowed'}
end.
process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Host = To#jid.lserver,
Node = xml:get_tag_attr_s("node", SubEl),
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
Host,
[],
[From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features,
Host,
empty,
[From, To, Node, Lang]) of
{result, Features} ->
ANode = case Node of
"" -> [];
_ -> [{"node", Node}]
end,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_INFO} | ANode],
Identity ++
lists:map(fun feature_to_xml/1, Features)
}]};
{error, Error} ->
IQ#iq{type = error, sub_el = [SubEl, Error]}
end
end.
process_sm_iq_info(From, To, #iq{type = get, payload = SubEl,
lang = Lang} = IQ_Rec) ->
Node = exmpp_xml:get_attribute_as_binary(SubEl, 'node', <<>>),
Identity = ejabberd_hooks:run_fold(disco_sm_identity,
exmpp_jid:prep_domain(To),
[],
[From, To, Node, Lang]),
case ejabberd_hooks:run_fold(disco_sm_features,
exmpp_jid:prep_domain(To),
empty,
[From, To, Node, Lang]) of
{result, Features} ->
ANode = case Node of
<<>> -> [];
_ -> [?XMLATTR('node', Node)]
end,
Result = #xmlel{ns = ?NS_DISCO_INFO, name = 'query',
attrs = ANode,
children = Identity ++ lists:map(fun feature_to_xml/1,
Features)},
exmpp_iq:result(IQ_Rec, Result);
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end;
process_sm_iq_info(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
get_sm_identity(Acc, _From, _To, _Node, _Lang) ->
Acc.
get_sm_features(empty, From, To, _Node, _Lang) ->
#jid{luser = LFrom, lserver = LSFrom} = From,
#jid{luser = LTo, lserver = LSTo} = To,
LFrom = exmpp_jid:prep_node_as_list(From),
LSFrom = exmpp_jid:prep_domain_as_list(From),
LTo = exmpp_jid:prep_node_as_list(To),
LSTo = exmpp_jid:prep_domain_as_list(To),
case {LFrom, LSFrom} of
{LTo, LSTo} ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
_ ->
{error, ?ERR_NOT_ALLOWED}
{error, 'not-allowed'}
end;
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server),
get_user_resources(JID) ->
Rs = ejabberd_sm:get_user_resources(exmpp_jid:prep_node(JID),
exmpp_jid:prep_domain(JID)),
lists:map(fun(R) ->
{xmlelement, "item",
[{"jid", User ++ "@" ++ Server ++ "/" ++ R},
{"name", User}], []}
#xmlel{ns = ?NS_DISCO_ITEMS, name = 'item', attrs = [
?XMLATTR('jid',
exmpp_jid:to_binary(exmpp_jid:full(JID, R))),
?XMLATTR('name', exmpp_jid:prep_node(JID))
]}
end, lists:sort(Rs)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support for: XEP-0157 Contact Addresses for XMPP Services
get_info(_A, Host, Module, Node, _Lang) when Node == [] ->
get_info(Acc, Host, Module, Node, _Lang) when Node == <<>> ->
Serverinfo_fields = get_fields_xml(Host, Module),
[{xmlelement, "x",
[{"xmlns", ?NS_XDATA}, {"type", "result"}],
[{xmlelement, "field",
[{"var", "FORM_TYPE"}, {"type", "hidden"}],
[{xmlelement, "value",
[],
[{xmlcdata, ?NS_SERVERINFO}]
}]
}]
++ Serverinfo_fields
}];
CData1 = #xmlcdata{cdata = list_to_binary(?NS_SERVERINFO_s)},
Value1 = #xmlel{name = 'value', children = [CData1]},
Field1 = #xmlel{name = 'field',
attrs = [?XMLATTR('type', <<"hidden">>),
?XMLATTR('var', <<"FORM_TYPE">>)],
children = [Value1]
},
X = #xmlel{name = 'x',
ns = ?NS_DATA_FORMS,
attrs = [?XMLATTR('type', <<"result">>)],
children = [Field1 | Serverinfo_fields]
},
[X | Acc];
get_info(_, _, _, _Node, _) ->
[].
@ -410,18 +420,16 @@ fields_to_xml(Fields) ->
field_to_xml({_, Var, Values}) ->
Values_xml = values_to_xml(Values),
{xmlelement, "field",
[{"var", Var}],
Values_xml
}.
#xmlel{name = 'field',
attrs = [?XMLATTR('var', list_to_binary(Var))],
children = Values_xml
}.
values_to_xml(Values) ->
lists:map(
fun(Value) ->
{xmlelement, "value",
[],
[{xmlcdata, Value}]
}
CData= #xmlcdata{cdata = list_to_binary(Value)},
#xmlel{name = 'value', children = [CData]}
end,
Values
).

View File

@ -37,8 +37,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(state, {host}).
@ -117,8 +118,8 @@ handle_cast(_Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, From, To, Packet}, State) ->
Packet2 = case From#jid.user of
"" -> jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST);
Packet2 = case exmpp_jid:node(From) of
undefined -> exmpp_stanza:reply_with_error(Packet, 'bad-request');
_ -> Packet
end,
do_client_version(disabled, To, From), % Put 'enabled' to enable it
@ -171,16 +172,15 @@ code_change(_OldVsn, State, _Extra) ->
do_client_version(disabled, _From, _To) ->
ok;
do_client_version(enabled, From, To) ->
ToS = jlib:jid_to_string(To),
%% It is important to identify this process and packet
Random_resource = integer_to_list(random:uniform(100000)),
From2 = From#jid{resource = Random_resource,
lresource = Random_resource},
From2 = exmpp_jid:full(From,Random_resource),
%% Build an iq:query request
Packet = {xmlelement, "iq",
[{"to", ToS}, {"type", "get"}],
[{xmlelement, "query", [{"xmlns", ?NS_VERSION}], []}]},
Request = #xmlel{ns = ?NS_SOFT_VERSION, name = 'query'},
Packet = exmpp_stanza:set_recipient(
exmpp_iq:get(?NS_JABBER_CLIENT, Request),
To),
%% Send the request
ejabberd_router:route(From2, To, Packet),
@ -189,15 +189,15 @@ do_client_version(enabled, From, To) ->
%% It is very important to only accept a packet which is the
%% response to the request that he sent
Els = receive {route, To, From2, IQ} ->
{xmlelement, "query", _, List} = xml:get_subtag(IQ, "query"),
#xmlel{ns = ?NS_SOFT_VERSION, name = 'query', children = List} = exmpp_iq:get_payload(IQ),
List
after 5000 -> % Timeout in miliseconds: 5 seconds
[]
end,
Values = [{Name, Value} || {xmlelement,Name,[],[{xmlcdata,Value}]} <- Els],
Values = [{Name, exmpp_xml:get_cdata_as_list(El)} || #xmlel{name = Name} = El <- Els],
%% Print in log
Values_string1 = [io_lib:format("~n~s: ~p", [N, V]) || {N, V} <- Values],
Values_string2 = lists:concat(Values_string1),
?INFO_MSG("Information of the client: ~s~s", [ToS, Values_string2]).
?INFO_MSG("Information of the client: ~s~s", [exmpp_jid:to_binary(To), Values_string2]).

View File

@ -1,60 +0,0 @@
# $Id$
CC = @CC@
CFLAGS = @CFLAGS@
CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ @LIBICONV@
ERLANG_CFLAGS = @ERLANG_CFLAGS@
ERLANG_LIBS = @ERLANG_LIBS@
# Assume Linux-style dynamic library flags
DYNAMIC_LIB_CFLAGS = -fpic -shared
ifeq ($(shell uname),Darwin)
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
endif
ifeq ($(shell uname),SunOs)
DYNAMIC_LIB_CFLAGS = -KPIC -G -z text
endif
EFLAGS += -I ..
EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
EFLAGS+=+debug_info
endif
ERLSHLIBS = ../iconv_erl.so
OUTDIR = ..
SOURCES = $(wildcard *.erl)
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
all: $(BEAMS) $(ERLSHLIBS)
$(OUTDIR)/%.beam: %.erl
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
#all: $(ERLSHLIBS)
# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
$(ERLSHLIBS): ../%.so: %.c
$(CC) $(INCLUDES) $(CFLAGS) $(LDFLAGS) \
$(subst ../,,$(subst .so,.c,$@)) \
$(LIBS) \
$(ERLANG_CFLAGS) \
$(ERLANG_LIBS) \
-o $@ \
$(DYNAMIC_LIB_CFLAGS)
clean:
rm -f $(BEAMS) $(ERLSHLIBS)
distclean: clean
rm -f Makefile
TAGS:
etags *.erl

View File

@ -1,42 +0,0 @@
include ..\Makefile.inc
EFLAGS = -I .. -pz ..
OUTDIR = ..
BEAMS = ..\iconv.beam ..\mod_irc.beam ..\mod_irc_connection.beam
SOURCE = iconv_erl.c
OBJECT = iconv_erl.o
DLL = $(OUTDIR)\iconv_erl.dll
ALL : $(DLL) $(BEAMS)
CLEAN :
-@erase $(DLL)
-@erase $(OUTDIR)\iconv_erl.exp
-@erase $(OUTDIR)\iconv_erl.lib
-@erase $(OBJECT)
-@erase $(BEAMS)
$(OUTDIR)\iconv.beam : iconv.erl
erlc -W $(EFLAGS) -o $(OUTDIR) iconv.erl
$(OUTDIR)\mod_irc.beam : mod_irc.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_irc.erl
$(OUTDIR)\mod_irc_connection.beam : mod_irc_connection.erl
erlc -W $(EFLAGS) -o $(OUTDIR) mod_irc_connection.erl
CC=cl.exe
CC_FLAGS=-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT -MD -Ox -I"$(ERLANG_DIR)\usr\include" -I"$(EI_DIR)\include" -I"$(ICONV_DIR)\include"
LD=link.exe
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" "$(ICONV_LIB)" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
$(DLL) : $(OBJECT)
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
$(OBJECT) : $(SOURCE)
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)

View File

@ -1,94 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : iconv.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Interface to libiconv
%%% Created : 16 Feb 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(iconv).
-author('alexey@process-one.net').
-behaviour(gen_server).
-export([start/0, start_link/0, convert/3]).
%% Internal exports, call-back functions.
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) ->
case erl_ddll:load_driver(ejabberd:get_so_path(), iconv_erl) of
ok -> ok;
{error, already_loaded} -> ok
end,
Port = open_port({spawn, iconv_erl}, []),
ets:new(iconv_table, [set, public, named_table]),
ets:insert(iconv_table, {port, Port}),
{ok, Port}.
%%% --------------------------------------------------------
%%% The call-back functions.
%%% --------------------------------------------------------
handle_call(_, _, State) ->
{noreply, State}.
handle_cast(_, State) ->
{noreply, State}.
handle_info({'EXIT', Port, Reason}, Port) ->
{stop, {port_died, Reason}, Port};
handle_info({'EXIT', _Pid, _Reason}, Port) ->
{noreply, Port};
handle_info(_, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, Port) ->
Port ! {self, close},
ok.
convert(From, To, String) ->
[{port, Port} | _] = ets:lookup(iconv_table, port),
Bin = term_to_binary({From, To, String}),
BRes = port_control(Port, 1, Bin),
binary_to_list(BRes).

View File

@ -1,155 +0,0 @@
/*
* ejabberd, Copyright (C) 2002-2009 ProcessOne
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/
#include <stdio.h>
#include <string.h>
#include <erl_driver.h>
#include <ei.h>
#include <iconv.h>
typedef struct {
ErlDrvPort port;
iconv_t cd;
} iconv_data;
static ErlDrvData iconv_erl_start(ErlDrvPort port, char *buff)
{
iconv_data* d = (iconv_data*)driver_alloc(sizeof(iconv_data));
d->port = port;
d->cd = NULL;
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
return (ErlDrvData)d;
}
static void iconv_erl_stop(ErlDrvData handle)
{
driver_free((char*)handle);
}
static int iconv_erl_control(ErlDrvData drv_data,
unsigned int command,
char *buf, int len,
char **rbuf, int rlen)
{
int i;
int size;
int index = 0;
int avail;
size_t inleft, outleft;
ErlDrvBinary *b;
char *from, *to, *string, *stmp, *rstring, *rtmp;
iconv_t cd;
int invalid_utf8_as_latin1 = 0;
ei_decode_version(buf, &index, &i);
ei_decode_tuple_header(buf, &index, &i);
ei_get_type(buf, &index, &i, &size);
from = malloc(size + 1);
ei_decode_string(buf, &index, from);
ei_get_type(buf, &index, &i, &size);
to = malloc(size + 1);
ei_decode_string(buf, &index, to);
ei_get_type(buf, &index, &i, &size);
stmp = string = malloc(size + 1);
ei_decode_string(buf, &index, string);
/* Special mode: parse as UTF-8 if possible; otherwise assume it's
Latin-1. Makes no difference when encoding. */
if (strcmp(from, "utf-8+latin-1") == 0) {
from[5] = '\0';
invalid_utf8_as_latin1 = 1;
}
if (strcmp(to, "utf-8+latin-1") == 0) {
to[5] = '\0';
}
cd = iconv_open(to, from);
if (cd == (iconv_t) -1) {
cd = iconv_open("ascii", "ascii");
if (cd == (iconv_t) -1) {
*rbuf = (char*)(b = driver_alloc_binary(size));
memcpy(b->orig_bytes, string, size);
free(from);
free(to);
free(string);
return size;
}
}
outleft = avail = 4*size;
inleft = size;
rtmp = rstring = malloc(avail);
while (inleft > 0) {
if (iconv(cd, &stmp, &inleft, &rtmp, &outleft) == (size_t) -1) {
if (invalid_utf8_as_latin1 && (*stmp & 0x80) && outleft >= 2) {
/* Encode one byte of (assumed) Latin-1 into two bytes of UTF-8 */
*rtmp++ = 0xc0 | ((*stmp & 0xc0) >> 6);
*rtmp++ = 0x80 | (*stmp & 0x3f);
outleft -= 2;
}
stmp++;
inleft--;
}
}
size = rtmp - rstring;
*rbuf = (char*)(b = driver_alloc_binary(size));
memcpy(b->orig_bytes, rstring, size);
free(from);
free(to);
free(string);
free(rstring);
iconv_close(cd);
return size;
}
ErlDrvEntry iconv_driver_entry = {
NULL, /* F_PTR init, N/A */
iconv_erl_start, /* L_PTR start, called when port is opened */
iconv_erl_stop, /* F_PTR stop, called when port is closed */
NULL, /* F_PTR output, called when erlang has sent */
NULL, /* F_PTR ready_input, called when input descriptor ready */
NULL, /* F_PTR ready_output, called when output descriptor ready */
"iconv_erl", /* char *driver_name, the argument to open_port */
NULL, /* F_PTR finish, called when unloaded */
NULL, /* handle */
iconv_erl_control, /* F_PTR control, port_command callback */
NULL, /* F_PTR timeout, reserved */
NULL /* F_PTR outputv, reserved */
};
DRIVER_INIT(iconv_erl) /* must match name in driver_entry */
{
return &iconv_driver_entry;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -38,52 +38,50 @@
get_last_info/2,
remove_user/2]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_privacy.hrl").
-record(last_activity, {us, timestamp, status}).
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mnesia:create_table(last_activity,
[{disc_copies, [node()]},
{attributes, record_info(fields, last_activity)}]),
update_table(),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY,
?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:add(unset_presence_hook, Host,
ejabberd_hooks:add(unset_presence_hook, HostB,
?MODULE, on_presence_update, 50).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(unset_presence_hook, Host,
ejabberd_hooks:delete(unset_presence_hook, HostB,
?MODULE, on_presence_update, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_LAST),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_LAST).
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY).
%%%
%%% Uptime of ejabberd node
%%%
process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Sec = get_node_uptime(),
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_LAST},
{"seconds", integer_to_list(Sec)}],
[]}]}
end.
process_local_iq(_From, _To, #iq{type = get} = IQ_Rec) ->
Sec = get_node_uptime(),
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query', attrs =
[?XMLATTR('seconds', Sec)]},
exmpp_iq:result(IQ_Rec, Response);
process_local_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
%% @spec () -> integer()
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
@ -104,57 +102,49 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
%%% Serve queries about user last online
%%%
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
User = To#jid.luser,
Server = To#jid.lserver,
{Subscription, _Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, Server,
{none, []}, [User, Server, From]),
if
(Subscription == both) or (Subscription == from) ->
UserListRecord = ejabberd_hooks:run_fold(
privacy_get_user_list, Server,
#userlist{},
[User, Server]),
case ejabberd_hooks:run_fold(
privacy_check_packet, Server,
allow,
[User, Server, UserListRecord,
{From, To,
{xmlelement, "presence", [], []}},
out]) of
allow ->
get_last(IQ, SubEl, User, Server);
deny ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end;
true ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end
end.
process_sm_iq(From, To, #iq{type = get} = IQ_Rec) ->
{Subscription, _Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, exmpp_jid:prep_domain(To),
{none, []}, [exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), From]),
if
(Subscription == both) or (Subscription == from) ->
UserListRecord = ejabberd_hooks:run_fold(
privacy_get_user_list, exmpp_jid:prep_domain(To),
#userlist{},
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To)]),
case ejabberd_hooks:run_fold(
privacy_check_packet, exmpp_jid:prep_domain(To),
allow,
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), UserListRecord,
{From, To,
exmpp_presence:available()},
out]) of
allow ->
get_last(IQ_Rec, exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To));
deny ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end;
true ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end;
process_sm_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
%% TODO: This function could use get_last_info/2
get_last(IQ, SubEl, LUser, LServer) ->
get_last(IQ_Rec, LUser, LServer) ->
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
{'EXIT', _Reason} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
exmpp_iq:error(IQ_Rec, 'internal-server-error');
[] ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
exmpp_iq:error(IQ_Rec, 'service-unavailable');
[#last_activity{timestamp = TimeStamp, status = Status}] ->
TimeStamp2 = now_to_seconds(now()),
Sec = TimeStamp2 - TimeStamp,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_LAST},
{"seconds", integer_to_list(Sec)}],
[{xmlcdata, Status}]}]}
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query',
attrs = [?XMLATTR('seconds', Sec)],
children = [#xmlcdata{cdata = Status}]},
exmpp_iq:result(IQ_Rec, Response)
end.
@ -163,20 +153,24 @@ on_presence_update(User, Server, _Resource, Status) ->
TimeStamp = now_to_seconds(now()),
store_last_info(User, Server, TimeStamp, Status).
store_last_info(User, Server, TimeStamp, Status) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:write(#last_activity{us = US,
timestamp = TimeStamp,
status = Status})
end,
mnesia:transaction(F).
store_last_info(User, Server, TimeStamp, Status)
when is_binary(User), is_binary(Server) ->
try
US = {User, Server},
F = fun() ->
mnesia:write(#last_activity{us = US,
timestamp = TimeStamp,
status = Status})
end,
mnesia:transaction(F)
catch
_ ->
ok
end.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, Timestamp::integer(), Status::string()} | not_found
get_last_info(LUser, LServer) ->
get_last_info(LUser, LServer) when is_binary(LUser), is_binary(LServer) ->
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
{'EXIT', _Reason} ->
not_found;
@ -186,21 +180,26 @@ get_last_info(LUser, LServer) ->
{ok, TimeStamp, Status}
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({last_activity, US})
end,
mnesia:transaction(F).
remove_user(User, Server) when is_binary(User), is_binary(Server) ->
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({last_activity, US})
end,
mnesia:transaction(F)
catch
_ ->
ok
end.
update_table() ->
Fields = record_info(fields, last_activity),
case mnesia:table_info(last_activity, attributes) of
Fields ->
ok;
convert_to_exmpp();
[user, timestamp, status] ->
?INFO_MSG("Converting last_activity table from {user, timestamp, status} format", []),
Host = ?MYNAME,
@ -209,11 +208,12 @@ update_table() ->
mnesia:write_lock_table(last_activity),
mnesia:foldl(
fun({_, U, T, S} = R, _) ->
U1 = convert_jid_to_exmpp(U),
mnesia:delete_object(R),
mnesia:write(
#last_activity{us = {U, Host},
#last_activity{us = {U1, Host},
timestamp = T,
status = S})
status = list_to_binary(S)})
end, ok, last_activity)
end,
mnesia:transaction(F);
@ -225,7 +225,7 @@ update_table() ->
fun({_, U, T}) ->
#last_activity{us = U,
timestamp = T,
status = ""}
status = <<>>}
end, Fields),
F = fun() ->
mnesia:write_lock_table(last_activity),
@ -244,3 +244,37 @@ update_table() ->
mnesia:transform_table(last_activity, ignore, Fields)
end.
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(last_activity) of
'$end_of_table' ->
none;
Key ->
case mnesia:read({last_activity, Key}) of
[#last_activity{status = Status}] when is_binary(Status) ->
none;
[#last_activity{}] ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, last_activity, write)
end
end
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#last_activity{us = {U, S} = Key, status = Status} = LA,
Acc) ->
% Remove old entry.
mnesia:delete({last_activity, Key}),
% Convert "" to undefined in JIDs.
U1 = convert_jid_to_exmpp(U),
% Convert status.
Status1 = list_to_binary(Status),
% Prepare the new record.
New_LA = LA#last_activity{us = {list_to_binary(U1), list_to_binary(S)},
status = Status1},
% Write the new record.
mnesia:write(New_LA),
Acc.
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) -> V.

View File

@ -38,45 +38,43 @@
get_last_info/2,
remove_user/2]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_privacy.hrl").
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_LAST,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY,
?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:add(unset_presence_hook, Host,
ejabberd_hooks:add(unset_presence_hook, HostB,
?MODULE, on_presence_update, 50).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(unset_presence_hook, Host,
ejabberd_hooks:delete(unset_presence_hook, HostB,
?MODULE, on_presence_update, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_LAST),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_LAST).
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY).
%%%
%%% Uptime of ejabberd node
%%%
process_local_iq(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
Sec = get_node_uptime(),
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_LAST},
{"seconds", integer_to_list(Sec)}],
[]}]}
end.
process_local_iq(_From, _To, #iq{type = get} = IQ_Rec) ->
Sec = get_node_uptime(),
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query', attrs =
[?XMLATTR('seconds', Sec)]},
exmpp_iq:result(IQ_Rec, Response);
process_local_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
%% @spec () -> integer()
%% @doc Get the uptime of the ejabberd node, expressed in seconds.
@ -96,77 +94,79 @@ now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
%%%
%%% Serve queries about user last online
%%%
process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
case Type of
set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
get ->
User = To#jid.luser,
Server = To#jid.lserver,
{Subscription, _Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, Server,
{none, []}, [User, Server, From]),
if
(Subscription == both) or (Subscription == from) ->
UserListRecord = ejabberd_hooks:run_fold(
privacy_get_user_list, Server,
#userlist{},
[User, Server]),
case ejabberd_hooks:run_fold(
privacy_check_packet, Server,
allow,
[User, Server, UserListRecord,
{From, To,
{xmlelement, "presence", [], []}},
out]) of
allow ->
get_last(IQ, SubEl, User, Server);
deny ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end;
true ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end
end.
process_sm_iq(From, To, #iq{type = get} = IQ_Rec) ->
User = exmpp_jid:prep_node_as_list(To),
Server = exmpp_jid:prep_domain_as_list(To),
{Subscription, _Groups} =
ejabberd_hooks:run_fold(
roster_get_jid_info, exmpp_jid:prep_domain(To),
{none, []}, [exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), From]),
if
(Subscription == both) or (Subscription == from) ->
UserListRecord = ejabberd_hooks:run_fold(
privacy_get_user_list, exmpp_jid:prep_domain(To),
#userlist{},
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To)]),
case ejabberd_hooks:run_fold(
privacy_check_packet, exmpp_jid:prep_domain(To),
allow,
[exmpp_jid:prep_node(To), exmpp_jid:prep_domain(To), UserListRecord,
{From, To,
exmpp_presence:available()},
out]) of
allow ->
get_last(IQ_Rec, User, Server);
deny ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end;
true ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end;
process_sm_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
%% TODO: This function could use get_last_info/2
get_last(IQ, SubEl, LUser, LServer) ->
get_last(IQ_Rec, LUser, LServer) ->
Username = ejabberd_odbc:escape(LUser),
case catch odbc_queries:get_last(LServer, Username) of
{selected, ["seconds","state"], []} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
exmpp_iq:error(IQ_Rec, 'service-unavailable');
{selected, ["seconds","state"], [{STimeStamp, Status}]} ->
case catch list_to_integer(STimeStamp) of
TimeStamp when is_integer(TimeStamp) ->
TimeStamp2 = now_to_seconds(now()),
Sec = TimeStamp2 - TimeStamp,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_LAST},
{"seconds", integer_to_list(Sec)}],
[{xmlcdata, Status}]}]};
Response = #xmlel{ns = ?NS_LAST_ACTIVITY, name = 'query',
attrs = [?XMLATTR('seconds', Sec)],
children = [#xmlcdata{cdata = list_to_binary(Status)}]},
exmpp_iq:result(IQ_Rec, Response);
_ ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
exmpp_iq:error(IQ_Rec, 'internal-server-error')
end;
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
exmpp_iq:error(IQ_Rec, 'internal-server-error')
end.
on_presence_update(User, Server, _Resource, Status) ->
TimeStamp = now_to_seconds(now()),
store_last_info(User, Server, TimeStamp, Status).
store_last_info(User, Server, TimeStamp, Status) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
State = ejabberd_odbc:escape(Status),
odbc_queries:set_last_t(LServer, Username, Seconds, State).
store_last_info(User, Server, TimeStamp, Status)
when is_binary(User), is_binary(Server) ->
try
%LUser = exmpp_stringprep:nodeprep(User),
%LServer = exmpp_stringprep:nameprep(Server),
LUser = binary_to_list(User),
LServer = binary_to_list(Server),
Username = ejabberd_odbc:escape(LUser),
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
State = ejabberd_odbc:escape(Status),
odbc_queries:set_last_t(LServer, Username, Seconds, State)
catch
_ ->
ok
end.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, Timestamp::integer(), Status::string()} | not_found
@ -187,7 +187,12 @@ get_last_info(LUser, LServer) ->
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_last(LServer, Username).
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_last(LServer, Username)
catch
_ ->
ok
end.

View File

@ -46,6 +46,8 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -98,7 +100,8 @@ stop(Host) ->
%% C) mod_muc:stop was called, and each room is being terminated
%% In this case, the mod_muc process died before the room processes
%% So the message sending must be catched
room_destroyed(Host, Room, Pid, ServerHost) ->
room_destroyed(Host, Room, Pid, ServerHost) when is_binary(Host),
is_binary(Room) ->
catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
{room_destroyed, {Room, Host}, Pid},
ok.
@ -110,14 +113,14 @@ create_room(Host, Name, From, Nick, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
store_room(Host, Name, Opts) ->
store_room(Host, Name, Opts) when is_binary(Host), is_binary(Name) ->
F = fun() ->
mnesia:write(#muc_room{name_host = {Name, Host},
opts = Opts})
end,
mnesia:transaction(F).
restore_room(Host, Name) ->
restore_room(Host, Name) when is_binary(Host), is_binary(Name) ->
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
[#muc_room{opts = Opts}] ->
Opts;
@ -125,27 +128,25 @@ restore_room(Host, Name) ->
error
end.
forget_room(Host, Name) ->
forget_room(Host, Name) when is_binary(Host), is_binary(Name) ->
F = fun() ->
mnesia:delete({muc_room, {Name, Host}})
end,
mnesia:transaction(F).
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) ->
process_iq_disco_items(Host, From, To, #iq{lang = Lang} = IQ) when is_binary(Host) ->
Rsm = jlib:rsm_decode(IQ),
Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_DISCO_ITEMS}],
iq_disco_items(Host, From, Lang, Rsm)}]},
Res = exmpp_iq:result(IQ, #xmlel{ns = ?NS_DISCO_ITEMS,
name = 'query',
children = iq_disco_items(Host, From, Lang, Rsm)}),
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res)).
exmpp_iq:iq_to_xmlel(Res)).
can_use_nick(_Host, _JID, "") ->
can_use_nick(_Host, _JID, <<>>) ->
false;
can_use_nick(Host, JID, Nick) ->
{LUser, LServer, _} = jlib:jid_tolower(JID),
LUS = {LUser, LServer},
can_use_nick(Host, JID, Nick) when is_binary(Host), is_binary(Nick) ->
LUS = {exmpp_jid:prep_node(JID), exmpp_jid:prep_domain(JID)},
case catch mnesia:dirty_select(
muc_registered,
[{#muc_registered{us_host = '$1',
@ -184,7 +185,8 @@ init([Host, Opts]) ->
{attributes, record_info(fields, muc_online_room)}]),
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
MyHost = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
MyHost_L = gen_mod:get_opt_host(Host, Opts, "conference.@HOST@"),
MyHost = list_to_binary(MyHost_L),
update_tables(MyHost),
clean_table_from_bad_node(node(), MyHost),
mnesia:add_table_index(muc_registered, nick),
@ -196,7 +198,7 @@ init([Host, Opts]) ->
HistorySize = gen_mod:get_opt(history_size, Opts, 20),
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []),
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
ejabberd_router:register_route(MyHost),
ejabberd_router:register_route(MyHost_L),
load_permanent_rooms(MyHost, Host,
{Access, AccessCreate, AccessAdmin, AccessPersistent},
HistorySize,
@ -292,7 +294,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host),
ejabberd_router:unregister_route(binary_to_list(State#state.host)),
ok.
%%--------------------------------------------------------------------
@ -330,11 +332,11 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts);
_ ->
{xmlelement, _Name, Attrs, _Els} = Packet,
Lang = xml:get_attr_s("xml:lang", Attrs),
ErrText = "Access denied by service policy",
Err = jlib:make_error_reply(Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
Lang = exmpp_stanza:get_lang(Packet),
ErrText = "Access denied by service policy",
Err = exmpp_iq:error(Packet,exmpp_stanza:error(Packet#xmlel.ns,
'forbidden',
{Lang,ErrText})),
ejabberd_router:route(To, From, Err)
end.
@ -342,128 +344,112 @@ do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) ->
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jlib:jid_tolower(To),
{xmlelement, Name, Attrs, _Els} = Packet,
Room = exmpp_jid:prep_node(To),
Nick = exmpp_jid:prep_resource(To),
#xmlel{name = Name} = Packet,
case Room of
"" ->
'undefined' ->
case Nick of
"" ->
'undefined' ->
case Name of
"iq" ->
case jlib:iq_query_info(Packet) of
#iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
sub_el = _SubEl, lang = Lang} = IQ ->
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [],
[ServerHost, ?MODULE, "", ""]),
Res = IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", XMLNS}],
iq_disco_info(Lang)
++Info}]},
'iq' ->
case exmpp_iq:xmlel_to_iq(Packet) of
#iq{type = get, ns = ?NS_DISCO_INFO = XMLNS,
payload = _SubEl, lang = Lang} = IQ ->
ServerHostB = list_to_binary(ServerHost),
Info = ejabberd_hooks:run_fold(
disco_info, ServerHostB, [],
[ServerHost, ?MODULE, <<>>, ""]),
ResPayload = #xmlel{ns = XMLNS, name = 'query',
children = iq_disco_info(Lang)++Info},
Res = exmpp_iq:result(IQ, ResPayload),
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res));
exmpp_iq:iq_to_xmlel(Res));
#iq{type = get,
xmlns = ?NS_DISCO_ITEMS} = IQ ->
ns = ?NS_DISCO_ITEMS} = IQ ->
spawn(?MODULE,
process_iq_disco_items,
[Host, From, To, IQ]);
#iq{type = get,
xmlns = ?NS_REGISTER = XMLNS,
lang = Lang,
sub_el = _SubEl} = IQ ->
Res = IQ#iq{type = result,
sub_el =
[{xmlelement, "query",
[{"xmlns", XMLNS}],
iq_get_register_info(
Host, From, Lang)}]},
ns = ?NS_INBAND_REGISTER = XMLNS,
lang = Lang} = IQ ->
ResPayload = #xmlel{ns = XMLNS, name = 'query',
children = iq_get_register_info(Host,
From,
Lang)},
Res = exmpp_iq:result(IQ,ResPayload),
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res));
exmpp_iq:iq_to_xmlel(Res));
#iq{type = set,
xmlns = ?NS_REGISTER = XMLNS,
ns = ?NS_INBAND_REGISTER ,
lang = Lang,
sub_el = SubEl} = IQ ->
payload = SubEl} = IQ ->
case process_iq_register_set(Host, From, SubEl, Lang) of
{result, IQRes} ->
Res = IQ#iq{type = result,
sub_el =
[{xmlelement, "query",
[{"xmlns", XMLNS}],
IQRes}]},
ok ->
Res = exmpp_iq:result(IQ),
ejabberd_router:route(
To, From, jlib:iq_to_xml(Res));
To, From, exmpp_iq:iq_to_xmlel(Res));
{error, Error} ->
Err = jlib:make_error_reply(
Packet, Error),
Err = exmpp_iq:error(IQ,Error),
ejabberd_router:route(
To, From, Err)
To, From, exmpp_iq:iq_to_xmlel(Err))
end;
#iq{type = get,
xmlns = ?NS_VCARD = XMLNS,
lang = Lang,
sub_el = _SubEl} = IQ ->
Res = IQ#iq{type = result,
sub_el =
[{xmlelement, "vCard",
[{"xmlns", XMLNS}],
iq_get_vcard(Lang)}]},
ns = ?NS_VCARD,
lang = Lang} = IQ ->
Res = exmpp_iq:result(IQ,iq_get_vcard(Lang)),
ejabberd_router:route(To,
From,
jlib:iq_to_xml(Res));
#iq{} ->
Err = jlib:make_error_reply(
Packet,
?ERR_FEATURE_NOT_IMPLEMENTED),
exmpp_iq:iq_to_xmlel(Res));
#iq{} = IQ ->
Err = exmpp_iq:error(IQ,'feature-not-implemented'),
ejabberd_router:route(To, From, Err);
_ ->
ok
end;
"message" ->
case xml:get_attr_s("type", Attrs) of
'message' ->
case exmpp_xml:get_attribute_as_list(Packet,type, "chat") of
"error" ->
ok;
_ ->
case acl:match_rule(ServerHost, AccessAdmin, From) of
allow ->
Msg = xml:get_path_s(
Packet,
[{elem, "body"}, cdata]),
Msg = exmpp_xml:get_path(Packet,
[{element,'body'},cdata]),
broadcast_service_message(Host, Msg);
_ ->
Lang = xml:get_attr_s("xml:lang", Attrs),
Lang = exmpp_stanza:get_lang(Packet),
ErrText = "Only service administrators "
"are allowed to send service messages",
Err = jlib:make_error_reply(
Packet,
?ERRT_FORBIDDEN(Lang, ErrText)),
Err = exmpp_iq:error(Packet,exmpp_stanza:error(Packet#xmlel.ns,
'forbidden',
{Lang,ErrText})),
ejabberd_router:route(
To, From, Err)
end
end;
"presence" ->
'presence' ->
ok
end;
_ ->
case xml:get_attr_s("type", Attrs) of
"error" ->
case exmpp_stanza:get_type(Packet) of
<<"error">> ->
ok;
"result" ->
<<"result">> ->
ok;
_ ->
Err = jlib:make_error_reply(
Packet, ?ERR_ITEM_NOT_FOUND),
Err = exmpp_iq:error(Packet,'item-not-found'),
ejabberd_router:route(To, From, Err)
end
end;
_ ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
Type = xml:get_attr_s("type", Attrs),
Type = exmpp_stanza:get_type(Packet),
case {Name, Type} of
{"presence", ""} ->
{'presence', 'undefined'} ->
case check_user_can_create_room(ServerHost,
AccessCreate, From,
Room) of
@ -477,17 +463,20 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
Lang = xml:get_attr_s("xml:lang", Attrs),
Lang = exmpp_stanza:get_lang(Packet),
ErrText = "Room creation is denied by service policy",
Err = jlib:make_error_reply(
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
Err = exmpp_stanza:reply_with_error(Packet,exmpp_stanza:error(Packet#xmlel.ns,
'forbidden',
{Lang,ErrText})),
ejabberd_router:route(To, From, Err)
end;
_ ->
Lang = xml:get_attr_s("xml:lang", Attrs),
Lang = exmpp_stanza:get_lang(Packet),
ErrText = "Conference room does not exist",
Err = jlib:make_error_reply(
Packet, ?ERRT_ITEM_NOT_FOUND(Lang, ErrText)),
Err = exmpp_stanza:reply_with_error(Packet,
exmpp_stanza:error(Packet#xmlel.ns,
'item-not-found',
{Lang,ErrText})),
ejabberd_router:route(To, From, Err)
end;
[R] ->
@ -501,7 +490,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
check_user_can_create_room(ServerHost, AccessCreate, From, RoomID) ->
case acl:match_rule(ServerHost, AccessCreate, From) of
allow ->
(length(RoomID) =< gen_mod:get_module_opt(ServerHost, mod_muc,
(size(RoomID) =< gen_mod:get_module_opt(ServerHost, mod_muc,
max_room_id, infinite));
_ ->
false
@ -554,7 +543,7 @@ start_new_room(Host, ServerHost, Access, Room,
RoomShaper, Opts)
end.
register_room(Host, Room, Pid) ->
register_room(Host, Room, Pid) when is_binary(Host), is_binary(Room) ->
F = fun() ->
mnesia:write(#muc_online_room{name_host = {Room, Host},
pid = Pid})
@ -563,28 +552,46 @@ register_room(Host, Room, Pid) ->
iq_disco_info(Lang) ->
[{xmlelement, "identity",
[{"category", "conference"},
{"type", "text"},
{"name", translate:translate(Lang, "Chatrooms")}], []},
{xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
{xmlelement, "feature", [{"var", ?NS_MUC}], []},
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
{xmlelement, "feature", [{"var", ?NS_RSM}], []},
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
[#xmlel{ns = ?NS_DISCO_INFO, name = 'identity',
attrs = [?XMLATTR('category',
<<"conference">>),
?XMLATTR('type',
<<"text">>),
?XMLATTR('name',
translate:translate(Lang, "Chatrooms"))]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[?XMLATTR('var',
?NS_DISCO_INFO_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[?XMLATTR('var',
?NS_DISCO_ITEMS_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[?XMLATTR('var',
?NS_MUC_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[?XMLATTR('var',
?NS_INBAND_REGISTER_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[?XMLATTR('var',
?NS_RSM_s)]},
#xmlel{ns = ?NS_DISCO_INFO, name = 'feature', attrs =
[?XMLATTR('var',
?NS_VCARD_s)]}].
iq_disco_items(Host, From, Lang, none) ->
iq_disco_items(Host, From, Lang, none) when is_binary(Host) ->
lists:zf(fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
case catch gen_fsm:sync_send_all_state_event(
Pid, {get_disco_item, From, Lang}, 100) of
{item, Desc} ->
flush(),
{true,
{xmlelement, "item",
[{"jid", jlib:jid_to_string({Name, Host, ""})},
{"name", Desc}], []}};
#xmlel{name = 'item',
attrs = [?XMLATTR('jid',
exmpp_jid:to_binary(Name,
Host)),
?XMLATTR('name',
Desc)]}};
_ ->
false
end
@ -664,13 +671,17 @@ flush() ->
end.
-define(XFIELD(Type, Label, Var, Val),
{xmlelement, "field", [{"type", Type},
{"label", translate:translate(Lang, Label)},
{"var", Var}],
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
#xmlel{name = "field",
attrs = [?XMLATTR('type', Type),
?XMLATTR('label',
translate:translate(Lang, Label)),
?XMLATTR('var', Var)],
children = [#xmlel{name = 'value',
children = [#xmlcdata{cdata = Val}]}]}).
iq_get_register_info(Host, From, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
iq_get_register_info(Host, From, Lang) ->
LUser = exmpp_jid:prep_node(From),
LServer = exmpp_jid:prep_domain(From),
LUS = {LUser, LServer},
{Nick, Registered} =
case catch mnesia:dirty_read(muc_registered, {LUS, Host}) of
@ -679,31 +690,32 @@ iq_get_register_info(Host, From, Lang) ->
[] ->
{"", []};
[#muc_registered{nick = N}] ->
{N, [{xmlelement, "registered", [], []}]}
{N, [#xmlel{name = 'registered'}]}
end,
Registered ++
[{xmlelement, "instructions", [],
[{xmlcdata,
translate:translate(
Lang, "You need an x:data capable client to register nickname")}]},
{xmlelement, "x",
[{"xmlns", ?NS_XDATA}],
[{xmlelement, "title", [],
[{xmlcdata,
translate:translate(
Lang, "Nickname Registration at ") ++ Host}]},
{xmlelement, "instructions", [],
[{xmlcdata,
translate:translate(
Lang, "Enter nickname you want to register")}]},
?XFIELD("text-single", "Nickname", "nick", Nick)]}].
[#xmlel{name = 'instructions' ,
children = [#xmlcdata{cdata =
translate:translate(Lang,
"You need an x:data capable client to register nickname")}]},
#xmlel{ns = ?NS_DATA_FORMS, name = 'x',
children = [
#xmlel{ns = ?NS_DATA_FORMS, name = 'title',
children = [#xmlcdata{cdata =
[translate:translate(Lang, "Nickname Registration at "), Host]}]},
#xmlel{ns = ?NS_DATA_FORMS, name = 'instructions',
children = [#xmlcdata{cdata =
translate:translate(Lang, "Enter nickname you want to register")}]},
?XFIELD(<<"text-single">>, "Nickname", <<"nick">>, Nick)]}].
iq_set_register_info(Host, From, Nick, Lang) ->
{LUser, LServer, _} = jlib:jid_tolower(From),
iq_set_register_info(Host, From, Nick, Lang) when is_binary(Host), is_binary(Nick) ->
LUser = exmpp_jid:prep_node(From),
LServer = exmpp_jid:prep_domain(From),
LUS = {LUser, LServer},
F = fun() ->
case Nick of
"" ->
<<>> ->
mnesia:delete({muc_registered, {LUS, Host}}),
ok;
_ ->
@ -733,56 +745,64 @@ iq_set_register_info(Host, From, Nick, Lang) ->
end,
case mnesia:transaction(F) of
{atomic, ok} ->
{result, []};
ok;
{atomic, false} ->
ErrText = "That nickname is registered by another person",
{error, ?ERRT_CONFLICT(Lang, ErrText)};
%%TODO: Always in the jabber:client namespace?
{error,exmpp_stanza:error(?NS_JABBER_CLIENT,
'conflict',
{Lang, ErrText})};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end.
process_iq_register_set(Host, From, SubEl, Lang) ->
{xmlelement, _Name, _Attrs, Els} = SubEl,
case xml:get_subtag(SubEl, "remove") of
false ->
case xml:remove_cdata(Els) of
[{xmlelement, "x", _Attrs1, _Els1} = XEl] ->
case {xml:get_tag_attr_s("xmlns", XEl),
xml:get_tag_attr_s("type", XEl)} of
{?NS_XDATA, "cancel"} ->
{result, []};
{?NS_XDATA, "submit"} ->
% {xmlelement, _Name, _Attrs, Els} = SubEl,
case exmpp_xml:get_element(SubEl,'remove') of
undefined ->
case exmpp_xml:get_child_elements(SubEl) of
[#xmlel{ns= NS, name = 'x'} = XEl] ->
case {NS, exmpp_stanza:get_type(XEl)} of
{?NS_DATA_FORMS, <<"cancel">>} ->
ok;
{?NS_DATA_FORMS, <<"submit">>} ->
XData = jlib:parse_xdata_submit(XEl),
case XData of
invalid ->
{error, ?ERR_BAD_REQUEST};
{error, 'bad-request'};
_ ->
case lists:keysearch("nick", 1, XData) of
{value, {_, [Nick]}} when Nick /= "" ->
iq_set_register_info(Host, From, Nick, Lang);
{value, {_, [Nick]}} ->
iq_set_register_info(Host, From, list_to_binary(Nick), Lang);
_ ->
ErrText = "You must fill in field \"Nickname\" in the form",
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
Err = exmpp_stanza:error(SubEl#xmlel.ns,
'not-acceptable',
{Lang, translate:translate(Lang,ErrText)}),
{error, Err}
end
end;
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
_ ->
iq_set_register_info(Host, From, "", Lang)
iq_set_register_info(Host, From, <<>>, Lang)
end.
iq_get_vcard(Lang) ->
[{xmlelement, "FN", [],
[{xmlcdata, "ejabberd/mod_muc"}]},
{xmlelement, "URL", [],
[{xmlcdata, ?EJABBERD_URI}]},
{xmlelement, "DESC", [],
[{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++
"\nCopyright (c) 2003-2009 Alexey Shchepin"}]}].
#xmlel{ns = ?NS_VCARD, name = 'vCard',
children =
[#xmlel{ns = ?NS_VCARD, name = 'FN',
children = [#xmlcdata{cdata = <<"ejabberd/mod_muc">>}]},
#xmlel{ns = ?NS_VCARD, name = 'URL',
children = [#xmlcdata{cdata = ?EJABBERD_URI}]},
#xmlel{ns = ?NS_VCARD, name = 'DESC',
children = [#xmlcdata{cdata =
translate:translate(Lang, "ejabberd MUC module") ++
"\nCopyright (c) 2003-2009 Alexey Shchepin"}]}]}.
broadcast_service_message(Host, Msg) ->
@ -792,7 +812,7 @@ broadcast_service_message(Host, Msg) ->
Pid, {service_message, Msg})
end, get_vh_rooms(Host)).
get_vh_rooms(Host) ->
get_vh_rooms(Host) when is_binary(Host) ->
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Host}],

View File

@ -41,8 +41,9 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_muc_room.hrl").
%% Copied from mod_muc/mod_muc.erl
@ -141,7 +142,7 @@ init([Host, Opts]) ->
file_format = FileFormat,
css_file = CSSFile,
access = AccessLog,
lang = Lang,
lang = list_to_binary(Lang),
timezone = Timezone,
spam_prevention = NoFollow,
top_link = Top_link}}.
@ -208,15 +209,16 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions
%%--------------------------------------------------------------------
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
case {xml:get_subtag(Packet, "subject"), xml:get_subtag(Packet, "body")} of
{false, false} ->
case {exmpp_xml:get_element(Packet, 'subject'),
exmpp_xml:get_element(Packet, 'body')} of
{'undefined', 'undefined'} ->
ok;
{false, SubEl} ->
Message = {body, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State);
{'undefined', SubEl} ->
Message = {body, exmpp_xml:get_cdata_as_list(SubEl)},
add_message_to_log(binary_to_list(Nick), Message, Room, Opts, State);
{SubEl, _} ->
Message = {subject, xml:get_tag_cdata(SubEl)},
add_message_to_log(Nick, Message, Room, Opts, State)
Message = {subject, exmpp_xml:get_cdata_as_list(SubEl)},
add_message_to_log(binary_to_list(Nick), Message, Room, Opts, State)
end;
add_to_log2(roomconfig_change, _Occupants, Room, Opts, State) ->
@ -226,19 +228,19 @@ add_to_log2(roomconfig_change_enabledlogging, Occupants, Room, Opts, State) ->
add_message_to_log("", {roomconfig_change, Occupants}, Room, Opts, State);
add_to_log2(nickchange, {OldNick, NewNick}, Room, Opts, State) ->
add_message_to_log(NewNick, {nickchange, OldNick}, Room, Opts, State);
add_message_to_log(binary_to_list(NewNick), {nickchange, binary_to_list(OldNick)}, Room, Opts, State);
add_to_log2(join, Nick, Room, Opts, State) ->
add_message_to_log(Nick, join, Room, Opts, State);
add_message_to_log(binary_to_list(Nick), join, Room, Opts, State);
add_to_log2(leave, {Nick, Reason}, Room, Opts, State) ->
case Reason of
"" -> add_message_to_log(Nick, leave, Room, Opts, State);
_ -> add_message_to_log(Nick, {leave, Reason}, Room, Opts, State)
case binary_to_list(Reason) of
"" -> add_message_to_log(binary_to_list(Nick), leave, Room, Opts, State);
R -> add_message_to_log(binary_to_list(Nick), {leave, R}, Room, Opts, State)
end;
add_to_log2(kickban, {Nick, Reason, Code}, Room, Opts, State) ->
add_message_to_log(Nick, {kickban, Code, Reason}, Room, Opts, State).
add_message_to_log(binary_to_list(Nick), {kickban, Code, binary_to_list(Reason)}, Room, Opts, State).
%%----------------------------------------------------------------------
@ -276,8 +278,8 @@ build_filename_string(TimeStamp, OutDir, RoomJID, DirType, DirName, FileFormat)
{Fd, Fn, Fnrel}.
get_room_name(RoomJID) ->
JID = jlib:string_to_jid(RoomJID),
JID#jid.user.
JID = exmpp_jid:parse(RoomJID),
exmpp_jid:node_as_list(JID).
%% calculate day before
get_timestamp_daydiff(TimeStamp, Daydiff) ->
@ -413,11 +415,11 @@ add_message_to_log(Nick1, Message, RoomJID, Opts, State) ->
io_lib:format("<font class=\"msc\">~s~s~s</font><br/>",
[Nick, ?T(" has set the subject to: "), htmlize(T,NoFollow,FileFormat)]);
{body, T} ->
case {regexp:first_match(T, "^/me\s"), Nick} of
case {re:run(T, "^/me\s", [{capture, none}]), Nick} of
{_, ""} ->
io_lib:format("<font class=\"msm\">~s</font><br/>",
[htmlize(T,NoFollow,FileFormat)]);
{{match, _, _}, _} ->
{match, _} ->
io_lib:format("<font class=\"mne\">~s ~s</font><br/>",
[Nick, string:substr(htmlize(T,FileFormat), 5)]);
{nomatch, _} ->
@ -475,16 +477,27 @@ get_dateweek(Date, Lang) ->
end.
make_dir_rec(Dir) ->
Path = filename:split(Dir),
inc_foreach(Path,fun make_dir_if_not_exists/1).
make_dir_if_not_exists(DirPath) ->
Dir = filename:join(DirPath),
case file:read_file_info(Dir) of
{ok, _} ->
ok;
{error, enoent} ->
DirS = filename:split(Dir),
DirR = lists:sublist(DirS, length(DirS)-1),
make_dir_rec(filename:join(DirR)),
file:make_dir(Dir)
end.
inc_foreach(Lists, F) ->
lists:foldl(fun(Item, Accum) ->
New = Accum ++ [Item],
F(New),
New
end,[],Lists).
%% {ok, F1}=file:open("valid-xhtml10.png", [read]).
%% {ok, F1b}=file:read(F1, 1000000).
@ -651,8 +664,7 @@ fw(F, S, O, FileFormat) ->
html ->
S1;
plaintext ->
{ok, Res, _} = regexp:gsub(S1, "<[^>]*>", ""),
Res
re:replace(S1, "<[^>]*>", "", [global,{return,list}])
end,
io:format(F, S2, []).
@ -753,6 +765,8 @@ put_room_occupants(F, RoomOccupants, Lang, _FileFormat) ->
%% htmlize
%% The default behaviour is to ignore the nofollow spam prevention on links
%% (NoFollow=false)
htmlize(S1) ->
htmlize(S1, html).
@ -779,15 +793,20 @@ htmlize(S1, NoFollow, _FileFormat) ->
S2_list).
htmlize2(S1, NoFollow) ->
S2 = element(2, regexp:gsub(S1, "\\&", "\\&amp;")),
S3 = element(2, regexp:gsub(S2, "<", "\\&lt;")),
S4 = element(2, regexp:gsub(S3, ">", "\\&gt;")),
S5 = element(2, regexp:gsub(S4, "((http|https|ftp)://|(mailto|xmpp):)[^] )\'\"}]+",
link_regexp(NoFollow))),
%% Remove 'right-to-left override' unicode character 0x202e
S6 = element(2, regexp:gsub(S5, " ", "\\&nbsp;\\&nbsp;")),
S7 = element(2, regexp:gsub(S6, "\\t", "\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;")),
element(2, regexp:gsub(S7, [226,128,174], "[RLO]")).
ReplacementRules =
[{"\\&", "\\&amp;"},
{"<", "\\&lt;"},
{">", "\\&gt;"},
{"((http|https|ftp)://|(mailto|xmpp):)[^] )\'\"}]+", link_regexp(NoFollow)},
{" ", "\\&nbsp;\\&nbsp;"},
{"\\t", "\\&nbsp;\\&nbsp;\\&nbsp;\\&nbsp;"},
{[226,128,174], "[RLO]"}], %% Remove 'right-to-left override' unicode character 0x202e
lists:foldl(
fun({RegExp, Replace}, Acc) ->
re:replace(Acc, RegExp, Replace)
end,
S1,
ReplacementRules).
%% Regexp link
%% Add the nofollow rel attribute when required
@ -812,7 +831,7 @@ get_room_info(RoomJID, Opts) ->
{value, {_, SA}} -> SA;
false -> ""
end,
#room{jid = jlib:jid_to_string(RoomJID),
#room{jid = exmpp_jid:to_list(RoomJID),
title = Title,
subject = Subject,
subject_author = SubjectAuthor,
@ -910,9 +929,9 @@ role_users_to_string(RoleS, Users) ->
[RoleS, ": ", UsersString].
get_room_occupants(RoomJIDString) ->
RoomJID = jlib:string_to_jid(RoomJIDString),
RoomName = RoomJID#jid.luser,
MucService = RoomJID#jid.lserver,
RoomJID = exmpp_jid:parse(RoomJIDString),
RoomName = exmpp_jid:node(RoomJID),
MucService = exmpp_jid:domain(RoomJID),
StateData = get_room_state(RoomName, MucService),
[{U#user.jid, U#user.nick, U#user.role}
|| {_, U} <- ?DICT:to_list(StateData#state.users)].

File diff suppressed because it is too large Load Diff

View File

@ -43,8 +43,9 @@
webadmin_user/4,
webadmin_user_parse_query/5]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("web/ejabberd_http.hrl").
-include("web/ejabberd_web_admin.hrl").
@ -53,32 +54,38 @@
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [{?NS_XMPP, ?NS_XMPP_pfx}]).
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
start(Host, Opts) ->
HostB = list_to_binary(Host),
mnesia:create_table(offline_msg,
[{disc_only_copies, [node()]},
{type, bag},
{attributes, record_info(fields, offline_msg)}]),
update_table(),
ejabberd_hooks:add(offline_message_hook, Host,
ejabberd_hooks:add(offline_message_hook, HostB,
?MODULE, store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
ejabberd_hooks:add(resend_offline_messages_hook, HostB,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
ejabberd_hooks:add(anonymous_purge_hook, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host,
ejabberd_hooks:add(disco_sm_features, HostB,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
ejabberd_hooks:add(disco_local_features, HostB,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
ejabberd_hooks:add(webadmin_page_host, HostB,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
ejabberd_hooks:add(webadmin_user, HostB,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
ejabberd_hooks:add(webadmin_user_parse_query, HostB,
?MODULE, webadmin_user_parse_query, 50),
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
@ -125,7 +132,7 @@ loop(AccessMaxOfflineMsgs) ->
%% Function copied from ejabberd_sm.erl:
get_max_user_messages(AccessRule, LUser, Host) ->
case acl:match_rule(
Host, AccessRule, jlib:make_jid(LUser, Host, "")) of
Host, AccessRule, exmpp_jid:make(LUser, Host, "")) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_MESSAGES
@ -141,21 +148,22 @@ receive_all(US, Msgs) ->
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(offline_message_hook, HostB,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(resend_offline_messages_hook, Host,
ejabberd_hooks:delete(resend_offline_messages_hook, HostB,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:delete(remove_user, Host,
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
ejabberd_hooks:delete(anonymous_purge_hook, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
ejabberd_hooks:delete(disco_sm_features, HostB, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, HostB, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(webadmin_page_host, HostB,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
ejabberd_hooks:delete(webadmin_user, HostB,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
ejabberd_hooks:delete(webadmin_user_parse_query, HostB,
?MODULE, webadmin_user_parse_query, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
@ -166,9 +174,9 @@ get_sm_features(Acc, _From, _To, "", _Lang) ->
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
{result, Feats ++ [?NS_MSGOFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
get_sm_features(_Acc, _From, _To, ?NS_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
@ -177,17 +185,17 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) ->
store_packet(From, To, Packet) ->
Type = xml:get_tag_attr_s("type", Packet),
Type = exmpp_stanza:get_type(Packet),
if
(Type /= "error") and (Type /= "groupchat") and
(Type /= "headline") ->
(Type /= <<"error">>) and (Type /= <<"groupchat">>) and
(Type /= <<"headline">>) ->
case check_event_chatstates(From, To, Packet) of
true ->
#jid{luser = LUser, lserver = LServer} = To,
LUser = exmpp_jid:prep_node_as_list(To),
LServer = exmpp_jid:prep_domain_as_list(To),
TimeStamp = now(),
{xmlelement, _Name, _Attrs, Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
Expire = find_x_expire(TimeStamp, Packet#xmlel.children),
gen_mod:get_module_proc(LServer, ?PROCNAME) !
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp,
expire = Expire,
@ -204,40 +212,37 @@ store_packet(From, To, Packet) ->
%% Check if the packet has any content about XEP-0022 or XEP-0085
check_event_chatstates(From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = Packet,
case find_x_event_chatstates(Els, {false, false, false}) of
case find_x_event_chatstates(Packet#xmlel.children, {false, false, false}) of
%% There wasn't any x:event or chatstates subelements
{false, false, _} ->
true;
%% There a chatstates subelement and other stuff, but no x:event
{false, CEl, true} when CEl /= false ->
true;
true;
%% There was only a subelement: a chatstates
{false, CEl, false} when CEl /= false ->
%% Don't allow offline storage
false;
%% There was an x:event element, and maybe also other stuff
{El, _, _} when El /= false ->
case xml:get_subtag(El, "id") of
false ->
case xml:get_subtag(El, "offline") of
false ->
{El, _, _} when El /= false->
case exmpp_xml:get_element(El, 'id') of
undefined ->
case exmpp_xml:get_element(El, 'offline') of
undefined ->
true;
_ ->
ID = case xml:get_tag_attr_s("id", Packet) of
"" ->
{xmlelement, "id", [], []};
ID = case exmpp_stanza:get_id(Packet) of
undefined ->
#xmlel{ns = ?NS_MESSAGE_EVENT, name = 'id'};
S ->
{xmlelement, "id", [],
[{xmlcdata, S}]}
#xmlel{ns = ?NS_MESSAGE_EVENT, name = 'id',
children = [#xmlcdata{cdata =
S}]}
end,
X = #xmlel{ns = ?NS_MESSAGE_EVENT, name = 'x', children =
[ID, #xmlel{ns = ?NS_MESSAGE_EVENT, name = 'offline'}]},
ejabberd_router:route(
To, From, {xmlelement, Name, Attrs,
[{xmlelement, "x",
[{"xmlns", ?NS_EVENT}],
[ID,
{xmlelement, "offline", [], []}]}]
}),
To, From, exmpp_xml:set_children(Packet, [X])),
true
end;
_ ->
@ -248,120 +253,125 @@ check_event_chatstates(From, To, Packet) ->
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
find_x_event_chatstates([], Res) ->
Res;
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
find_x_event_chatstates(Els, Res);
find_x_event_chatstates([El | Els], {A, B, C}) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EVENT ->
find_x_event_chatstates(Els, {El, B, C});
?NS_CHATSTATES ->
find_x_event_chatstates(Els, {A, El, C});
_ ->
find_x_event_chatstates(Els, {A, B, true})
end.
find_x_event_chatstates([#xmlel{ns = ?NS_MESSAGE_EVENT} = El | Els], {_, B, C}) ->
find_x_event_chatstates(Els, {El, B, C});
find_x_event_chatstates([#xmlel{ns = ?NS_CHATSTATES} = El | Els], {A, _, C}) ->
find_x_event_chatstates(Els, {A, El, C});
find_x_event_chatstates([#xmlcdata{} = _ | Els], {A, B, C}) ->
find_x_event_chatstates(Els, {A, B, C});
find_x_event_chatstates([_ | Els], {A, B, _}) ->
find_x_event_chatstates(Els, {A, B, true}).
find_x_expire(_, []) ->
never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EXPIRE ->
Val = xml:get_tag_attr_s("seconds", El),
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
never
end;
find_x_expire(TimeStamp, [#xmlel{ns = ?NS_MESSAGE_EXPIRE} = El | _Els]) ->
Val = exmpp_xml:get_attribute_as_list(El, 'seconds', ""),
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
find_x_expire(TimeStamp, Els)
end.
never
end;
find_x_expire(TimeStamp, [_ | Els]) ->
find_x_expire(TimeStamp, Els).
resend_offline_messages(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
lists:foreach(
fun(R) ->
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
ejabberd_sm !
{route,
R#offline_msg.from,
R#offline_msg.to,
{xmlelement, Name, Attrs,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}}
end,
lists:keysort(#offline_msg.timestamp, Rs));
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
lists:foreach(
fun(R) ->
Packet = R#offline_msg.packet,
ejabberd_sm !
{route,
R#offline_msg.from,
R#offline_msg.to,
exmpp_xml:append_children(
Packet,
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(R#offline_msg.timestamp),
utc,
exmpp_jid:make("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))
]
)}
end,
lists:keysort(#offline_msg.timestamp, Rs));
_ ->
ok
end
catch
_ ->
ok
end.
pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
TS = now(),
Ls ++ lists:map(
fun(R) ->
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
{route,
R#offline_msg.from,
R#offline_msg.to,
{xmlelement, Name, Attrs,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}}
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
true;
TimeStamp ->
TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)));
pop_offline_messages(Ls, User, Server)
when is_binary(User), is_binary(Server) ->
try
LUser = binary_to_list(User),
LServer = binary_to_list(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
TS = now(),
Ls ++ lists:map(
fun(R) ->
Packet = R#offline_msg.packet,
{route,
R#offline_msg.from,
R#offline_msg.to,
exmpp_xml:append_children(
Packet,
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp),
utc,
exmpp_jid:make("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]
)}
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
true;
TimeStamp ->
TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)));
_ ->
Ls
end
catch
_ ->
Ls
end.
@ -405,20 +415,25 @@ remove_old_messages(Days) ->
end,
mnesia:transaction(F).
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({offline_msg, US})
end,
mnesia:transaction(F).
remove_user(User, Server) when is_binary(User), is_binary(Server) ->
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({offline_msg, US})
end,
mnesia:transaction(F)
catch
_ ->
ok
end.
update_table() ->
Fields = record_info(fields, offline_msg),
case mnesia:table_info(offline_msg, attributes) of
Fields ->
ok;
convert_to_exmpp();
[user, timestamp, expire, from, to, packet] ->
?INFO_MSG("Converting offline_msg table from "
"{user, timestamp, expire, from, to, packet} format", []),
@ -434,10 +449,23 @@ update_table() ->
F1 = fun() ->
mnesia:write_lock_table(mod_offline_tmp_table),
mnesia:foldl(
fun(#offline_msg{us = U} = R, _) ->
fun(#offline_msg{us = U, from = F, to = T, packet = P} = R, _) ->
U1 = convert_jid_to_exmpp(U),
F1 = jlib:from_old_jid(F),
T1 = jlib:from_old_jid(T),
P1 = exmpp_xml:xmlelement_to_xmlel(
P,
[?DEFAULT_NS],
?PREFIXED_NS),
New_R = R#offline_msg{
us = {U1, Host},
from = F1,
to = T1,
packet = P1
},
mnesia:dirty_write(
mod_offline_tmp_table,
R#offline_msg{us = {U, Host}})
New_R)
end, ok, offline_msg)
end,
mnesia:transaction(F1),
@ -465,14 +493,20 @@ update_table() ->
mnesia:transform_table(
offline_msg,
fun({_, U, TS, F, T, P}) ->
{xmlelement, _Name, _Attrs, Els} = P,
Expire = find_x_expire(TS, Els),
#offline_msg{us = U,
Expire = find_x_expire(TS, P#xmlelement.children),
U1 = convert_jid_to_exmpp(U),
F1 = jlib:from_old_jid(F),
T1 = jlib:from_old_jid(T),
P1 = exmpp_xml:xmlelement_to_xmlel(
P,
[?DEFAULT_NS],
?PREFIXED_NS),
#offline_msg{us = U1,
timestamp = TS,
expire = Expire,
from = F,
to = T,
packet = P}
from = F1,
to = T1,
packet = P1}
end, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_offline_tmp_table),
@ -499,6 +533,50 @@ update_table() ->
mnesia:transform_table(offline_msg, ignore, Fields)
end.
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(offline_msg) of
'$end_of_table' ->
none;
Key ->
case mnesia:read({offline_msg, Key}) of
[#offline_msg{packet = #xmlel{}} | _] ->
none;
[#offline_msg{packet = #xmlelement{}} | _] ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, offline_msg, write)
end
end
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#offline_msg{
us = {US_U, US_S},
from = From,
to = To,
packet = Packet} = R, Acc) ->
% Remove old entry.
mnesia:delete_object(R),
% Convert "" to undefined in JIDs.
US_U1 = convert_jid_to_exmpp(US_U),
US_S1 = convert_jid_to_exmpp(US_S),
From1 = jlib:from_old_jid(From),
To1 = jlib:from_old_jid(To),
% Convert stanza.
Packet1 = exmpp_xml:xmlelement_to_xmlel(Packet,
[?DEFAULT_NS], ?PREFIXED_NS),
% Prepare the new record.
New_R = R#offline_msg{
us = {US_U1, US_S1},
from = From1,
to = To1,
packet = Packet1},
% Write the new record.
mnesia:write(New_R),
Acc.
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) -> V.
%% Helper functions:
@ -507,9 +585,9 @@ discard_warn_sender(Msgs) ->
lists:foreach(
fun(#offline_msg{from=From, to=To, packet=Packet}) ->
ErrText = "Your contact offline message queue is full. The message has been discarded.",
Lang = xml:get_tag_attr_s("xml:lang", Packet),
Err = jlib:make_error_reply(
Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
Error = exmpp_stanza:error('resource-constraint',
{"en", ErrText}),
Err = exmpp_stanza:reply_with_error(Packet, Error),
ejabberd_router:route(
To,
From, Err)
@ -527,14 +605,25 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
user_queue(User, Server, Query, Lang) ->
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
Res = user_queue_parse_query(US, Query),
Msgs = lists:keysort(#offline_msg.timestamp,
mnesia:dirty_read({offline_msg, US})),
{US, Msgs, Res} = try
US0 = {
exmpp_stringprep:nodeprep(User),
exmpp_stringprep:nameprep(Server)
},
{
US0,
lists:keysort(#offline_msg.timestamp,
mnesia:dirty_read({offline_msg, US0})),
user_queue_parse_query(US0, Query)
}
catch
_ ->
{{"invalid", "invalid"}, [], nothing}
end,
FMsgs =
lists:map(
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
packet = {xmlelement, Name, Attrs, Els}} = Msg) ->
packet = Packet} = Msg) ->
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
@ -542,17 +631,18 @@ user_queue(User, Server, Query, Lang) ->
io_lib:format(
"~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour, Minute, Second])),
SFrom = jlib:jid_to_string(From),
STo = jlib:jid_to_string(To),
Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs),
Packet = {xmlelement, Name, Attrs2, Els},
FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
SFrom = exmpp_jid:to_list(From),
STo = exmpp_jid:to_list(To),
Packet1 = exmpp_stanza:set_jids(Packet, SFrom, STo),
FPacket = exmpp_xml:node_to_list(
exmpp_xml:indent_document(Packet1, <<" ">>),
[?DEFAULT_NS], ?PREFIXED_NS),
?XE("tr",
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
?XAC("td", [{"class", "valign"}], Time),
?XAC("td", [{"class", "valign"}], SFrom),
?XAC("td", [{"class", "valign"}], STo),
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
[?XAE("td", [?XMLATTR('class', <<"valign">>)], [?INPUT("checkbox", "selected", ID)]),
?XAC("td", [?XMLATTR('class', <<"valign">>)], Time),
?XAC("td", [?XMLATTR('class', <<"valign">>)], SFrom),
?XAC("td", [?XMLATTR('class', <<"valign">>)], STo),
?XAE("td", [?XMLATTR('class', <<"valign">>)], [?XC("pre", FPacket)])]
)
end, Msgs),
[?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
@ -561,7 +651,7 @@ user_queue(User, Server, Query, Lang) ->
ok -> [?XREST("Submitted")];
nothing -> []
end ++
[?XAE("form", [{"action", ""}, {"method", "post"}],
[?XAE("form", [?XMLATTR('action', <<"">>), ?XMLATTR('method', <<"post">>)],
[?XE("table",
[?XE("thead",
[?XE("tr",
@ -575,7 +665,7 @@ user_queue(User, Server, Query, Lang) ->
if
FMsgs == [] ->
[?XE("tr",
[?XAC("td", [{"colspan", "4"}], " ")]
[?XAC("td", [?XMLATTR('colspan', <<"4">>)], " ")]
)];
true ->
FMsgs
@ -610,13 +700,21 @@ user_queue_parse_query(US, Query) ->
end.
us_to_list({User, Server}) ->
jlib:jid_to_string({User, Server, ""}).
exmpp_jid:to_list(User, Server).
webadmin_user(Acc, User, Server, Lang) ->
US = {jlib:nodeprep(User), jlib:nameprep(Server)},
QueueLen = length(mnesia:dirty_read({offline_msg, US})),
FQueueLen = [?AC("queue/",
integer_to_list(QueueLen))],
FQueueLen = try
US = {
exmpp_stringprep:nodeprep(User),
exmpp_stringprep:nameprep(Server)
},
QueueLen = length(mnesia:dirty_read({offline_msg, US})),
[?AC("queue/",
integer_to_list(QueueLen))]
catch
_ ->
[?C("?")]
end,
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->

View File

@ -42,8 +42,9 @@
webadmin_user/4,
webadmin_user_parse_query/5]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("web/ejabberd_http.hrl").
-include("web/ejabberd_web_admin.hrl").
@ -52,27 +53,33 @@
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
% These are the namespace already declared by the stream opening. This is
% used at serialization time.
-define(DEFAULT_NS, ?NS_JABBER_CLIENT).
-define(PREFIXED_NS, [{?NS_XMPP, ?NS_XMPP_pfx}]).
%% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity).
start(Host, Opts) ->
ejabberd_hooks:add(offline_message_hook, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:add(offline_message_hook, HostB,
?MODULE, store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, Host,
ejabberd_hooks:add(resend_offline_messages_hook, HostB,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
ejabberd_hooks:add(anonymous_purge_hook, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host,
ejabberd_hooks:add(disco_sm_features, HostB,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
ejabberd_hooks:add(disco_local_features, HostB,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(webadmin_page_host, Host,
ejabberd_hooks:add(webadmin_page_host, HostB,
?MODULE, webadmin_page, 50),
ejabberd_hooks:add(webadmin_user, Host,
ejabberd_hooks:add(webadmin_user, HostB,
?MODULE, webadmin_user, 50),
ejabberd_hooks:add(webadmin_user_parse_query, Host,
ejabberd_hooks:add(webadmin_user_parse_query, HostB,
?MODULE, webadmin_user_parse_query, 50),
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages),
register(gen_mod:get_module_proc(Host, ?PROCNAME),
@ -99,31 +106,28 @@ loop(Host, AccessMaxOfflineMsgs) ->
fun(M) ->
Username =
ejabberd_odbc:escape(
(M#offline_msg.to)#jid.luser),
exmpp_jid:prep_node_as_list(M#offline_msg.to)),
From = M#offline_msg.from,
To = M#offline_msg.to,
{xmlelement, Name, Attrs, Els} =
M#offline_msg.packet,
Attrs2 = jlib:replace_from_to_attrs(
jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
Packet = {xmlelement, Name, Attrs2,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp),
utc,
jlib:make_jid("", Host, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp))]},
Packet0 = exmpp_stanza:set_jids(
M#offline_msg.packet,
From,
To),
Packet1 = exmpp_xml:append_children(
Packet0,
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp),
utc,
jlib:make_jid("", Host, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp))]),
XML =
ejabberd_odbc:escape(
lists:flatten(
xml:element_to_string(Packet))),
exmpp_xml:document_to_list(Packet1)),
odbc_queries:add_spool_sql(Username, XML)
end, Msgs),
case catch odbc_queries:add_spool(Host, Query) of
@ -143,7 +147,7 @@ loop(Host, AccessMaxOfflineMsgs) ->
%% Function copied from ejabberd_sm.erl:
get_max_user_messages(AccessRule, LUser, Host) ->
case acl:match_rule(
Host, AccessRule, jlib:make_jid(LUser, Host, "")) of
Host, AccessRule, exmpp_jid:make(LUser, Host, "")) of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_MESSAGES
@ -159,21 +163,22 @@ receive_all(Username, Msgs) ->
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(offline_message_hook, HostB,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(resend_offline_messages_hook, Host,
ejabberd_hooks:delete(resend_offline_messages_hook, HostB,
?MODULE, pop_offline_messages, 50),
ejabberd_hooks:delete(remove_user, Host,
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
ejabberd_hooks:delete(anonymous_purge_hook, HostB,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(webadmin_page_host, Host,
ejabberd_hooks:delete(disco_sm_features, HostB, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, HostB, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(webadmin_page_host, HostB,
?MODULE, webadmin_page, 50),
ejabberd_hooks:delete(webadmin_user, Host,
ejabberd_hooks:delete(webadmin_user, HostB,
?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
ejabberd_hooks:delete(webadmin_user_parse_query, HostB,
?MODULE, webadmin_user_parse_query, 50),
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
exit(whereis(Proc), stop),
@ -184,9 +189,9 @@ get_sm_features(Acc, _From, _To, "", _Lang) ->
{result, I} -> I;
_ -> []
end,
{result, Feats ++ [?NS_FEATURE_MSGOFFLINE]};
{result, Feats ++ [?NS_MSGOFFLINE]};
get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
get_sm_features(_Acc, _From, _To, ?NS_MSGOFFLINE, _Lang) ->
%% override all lesser features...
{result, []};
@ -195,17 +200,16 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) ->
store_packet(From, To, Packet) ->
Type = xml:get_tag_attr_s("type", Packet),
Type = exmpp_stanza:get_type(Packet),
if
(Type /= "error") and (Type /= "groupchat") and
(Type /= "headline") ->
(Type /= <<"error">>) and (Type /= <<"groupchat">>) and
(Type /= <<"headline">>) ->
case check_event_chatstates(From, To, Packet) of
true ->
#jid{luser = LUser} = To,
LUser = exmpp_jid:prep_node_as_list(To),
TimeStamp = now(),
{xmlelement, _Name, _Attrs, Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
Expire = find_x_expire(TimeStamp, Packet#xmlel.children),
gen_mod:get_module_proc(exmpp_jid:prep_domain_as_list(To), ?PROCNAME) !
#offline_msg{user = LUser,
timestamp = TimeStamp,
expire = Expire,
@ -222,40 +226,37 @@ store_packet(From, To, Packet) ->
%% Check if the packet has any content about XEP-0022 or XEP-0085
check_event_chatstates(From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = Packet,
case find_x_event_chatstates(Els, {false, false, false}) of
case find_x_event_chatstates(Packet#xmlel.children, {false, false, false}) of
%% There wasn't any x:event or chatstates subelements
{false, false, _} ->
true;
%% There a chatstates subelement and other stuff, but no x:event
{false, CEl, true} when CEl /= false ->
true;
true;
%% There was only a subelement: a chatstates
{false, CEl, false} when CEl /= false ->
%% Don't allow offline storage
false;
%% There was an x:event element, and maybe also other stuff
{El, _, _} when El /= false ->
case xml:get_subtag(El, "id") of
false ->
case xml:get_subtag(El, "offline") of
false ->
{El, _, _} when El /= false->
case exmpp_xml:get_element(El, 'id') of
undefined ->
case exmpp_xml:get_element(El, 'offline') of
undefined ->
true;
_ ->
ID = case xml:get_tag_attr_s("id", Packet) of
"" ->
{xmlelement, "id", [], []};
ID = case exmpp_stanza:get_id(Packet) of
undefined ->
#xmlel{ns = ?NS_MESSAGE_EVENT, name = 'id'};
S ->
{xmlelement, "id", [],
[{xmlcdata, S}]}
#xmlel{ns = ?NS_MESSAGE_EVENT, name = 'id',
children = [#xmlcdata{cdata =
S}]}
end,
X = #xmlel{ns = ?NS_MESSAGE_EVENT, name = 'x', children =
[ID, #xmlel{ns = ?NS_MESSAGE_EVENT, name = 'offline'}]},
ejabberd_router:route(
To, From, {xmlelement, Name, Attrs,
[{xmlelement, "x",
[{"xmlns", ?NS_EVENT}],
[ID,
{xmlelement, "offline", [], []}]}]
}),
To, From, exmpp_xml:set_children(Packet, [X])),
true
end;
_ ->
@ -266,80 +267,81 @@ check_event_chatstates(From, To, Packet) ->
%% Check if the packet has subelements about XEP-0022, XEP-0085 or other
find_x_event_chatstates([], Res) ->
Res;
find_x_event_chatstates([{xmlcdata, _} | Els], Res) ->
find_x_event_chatstates(Els, Res);
find_x_event_chatstates([El | Els], {A, B, C}) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EVENT ->
find_x_event_chatstates(Els, {El, B, C});
?NS_CHATSTATES ->
find_x_event_chatstates(Els, {A, El, C});
_ ->
find_x_event_chatstates(Els, {A, B, true})
end.
find_x_event_chatstates([#xmlel{ns = ?NS_MESSAGE_EVENT} = El | Els], {_, B, C}) ->
find_x_event_chatstates(Els, {El, B, C});
find_x_event_chatstates([#xmlel{ns = ?NS_CHATSTATES} = El | Els], {A, _, C}) ->
find_x_event_chatstates(Els, {A, El, C});
find_x_event_chatstates([#xmlcdata{} = _ | Els], {A, B, C}) ->
find_x_event_chatstates(Els, {A, B, C});
find_x_event_chatstates([_ | Els], {A, B, _}) ->
find_x_event_chatstates(Els, {A, B, true}).
find_x_expire(_, []) ->
never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EXPIRE ->
Val = xml:get_tag_attr_s("seconds", El),
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
never
end;
find_x_expire(TimeStamp, [#xmlel{ns = ?NS_MESSAGE_EXPIRE} = El | _Els]) ->
Val = exmpp_xml:get_attribute_as_list(El, 'seconds', ""),
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
find_x_expire(TimeStamp, Els)
never
end;
find_x_expire(TimeStamp, [_ | Els]) ->
find_x_expire(TimeStamp, Els).
pop_offline_messages(Ls, User, Server)
when is_binary(User), is_binary(Server) ->
try
LUser = binary_to_list(User),
LServer = binary_to_list(Server),
EUser = ejabberd_odbc:escape(LUser),
case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of
{atomic, {selected, ["username","xml"], Rs}} ->
Ls ++ lists:flatmap(
fun({_, XML}) ->
try
[El] = exmpp_xml:parse_document(XML,
[names_as_atom, {check_elems, xmpp},
{check_nss,xmpp}, {check_attrs,xmpp}]),
To = exmpp_jid:parse(
exmpp_stanza:get_recipient(El)),
From = exmpp_jid:parse(
exmpp_stanza:get_sender(El)),
[{route, From, To, El}]
catch
_ ->
[]
end
end, Rs);
_ ->
Ls
end
catch
_ ->
[]
end.
pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
EUser = ejabberd_odbc:escape(LUser),
case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of
{atomic, {selected, ["username","xml"], Rs}} ->
Ls ++ lists:flatmap(
fun({_, XML}) ->
case xml_stream:parse_element(XML) of
{error, _Reason} ->
[];
El ->
To = jlib:string_to_jid(
xml:get_tag_attr_s("to", El)),
From = jlib:string_to_jid(
xml:get_tag_attr_s("from", El)),
if
(To /= error) and
(From /= error) ->
[{route, From, To, El}];
true ->
[]
end
end
end, Rs);
remove_user(User, Server)
when is_binary(User), is_binary(Server) ->
try
LUser = binary_to_list(exmpp_stringprep:nodeprep(User)),
LServer = binary_to_list(exmpp_stringprep:nameprep(Server)),
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username)
catch
_ ->
Ls
ok
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_spool_msg(LServer, Username).
%% Helper functions:
%% TODO: Warning - This function is a duplicate from mod_offline.erl
@ -350,9 +352,9 @@ discard_warn_sender(Msgs) ->
lists:foreach(
fun(#offline_msg{from=From, to=To, packet=Packet}) ->
ErrText = "Your contact offline message queue is full. The message has been discarded.",
Lang = xml:get_tag_attr_s("xml:lang", Packet),
Err = jlib:make_error_reply(
Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)),
Error = exmpp_stanza:error('resource-constraint',
{"en", ErrText}),
Err = exmpp_stanza:reply_with_error(Packet, Error),
ejabberd_router:route(
To,
From, Err)
@ -370,38 +372,49 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
user_queue(User, Server, Query, Lang) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
US = {LUser, LServer},
Res = user_queue_parse_query(Username, LServer, Query),
Msgs = case catch ejabberd_odbc:sql_query(
LServer,
["select username, xml from spool"
" where username='", Username, "'"
" order by seq;"]) of
{selected, ["username", "xml"], Rs} ->
lists:flatmap(
fun({_, XML}) ->
case xml_stream:parse_element(XML) of
{error, _Reason} ->
[];
El ->
[El]
end
end, Rs);
_ ->
[]
end,
{US, Msgs, Res} = try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
US0 = {LUser, LServer},
R = user_queue_parse_query(Username, LServer, Query),
M = case catch ejabberd_odbc:sql_query(
LServer,
["select username, xml from spool"
" where username='", Username, "'"
" order by seq;"]) of
{selected, ["username", "xml"], Rs} ->
lists:flatmap(
fun({_, XML}) ->
try exmpp_xml:parse_document(XML,
[names_as_atom, {check_elems, xmpp},
{check_nss,xmpp}, {check_attrs,xmpp}]) of
[El] ->
[El]
catch
_ ->
[]
end
end, Rs);
_ ->
[]
end,
{US0, M, R}
catch
_ ->
{{"invalid", "invalid"}, [], nothing}
end,
FMsgs =
lists:map(
fun({xmlelement, _Name, _Attrs, _Els} = Msg) ->
fun(#xmlel{} = Msg) ->
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
Packet = Msg,
FPacket = ejabberd_web_admin:pretty_print_xml(Packet),
FPacket = exmpp_xml:node_to_list(
exmpp_xml:indent_document(Packet, <<" ">>),
[?DEFAULT_NS], ?PREFIXED_NS),
?XE("tr",
[?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]),
?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])]
[?XAE("td", [?XMLATTR('class', <<"valign">>)], [?INPUT("checkbox", "selected", ID)]),
?XAE("td", [?XMLATTR('class', <<"valign">>)], [?XC("pre", FPacket)])]
)
end, Msgs),
[?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
@ -410,7 +423,7 @@ user_queue(User, Server, Query, Lang) ->
ok -> [?XREST("Submitted")];
nothing -> []
end ++
[?XAE("form", [{"action", ""}, {"method", "post"}],
[?XAE("form", [?XMLATTR('action', <<"">>), ?XMLATTR('method', <<"post">>)],
[?XE("table",
[?XE("thead",
[?XE("tr",
@ -421,7 +434,7 @@ user_queue(User, Server, Query, Lang) ->
if
FMsgs == [] ->
[?XE("tr",
[?XAC("td", [{"colspan", "4"}], " ")]
[?XAC("td", [?XMLATTR('colspan', <<"4">>)], " ")]
)];
true ->
FMsgs
@ -442,11 +455,14 @@ user_queue_parse_query(Username, LServer, Query) ->
{selected, ["xml", "seq"], Rs} ->
lists:flatmap(
fun({XML, Seq}) ->
case xml_stream:parse_element(XML) of
{error, _Reason} ->
[];
El ->
try exmpp_xml:parse_document(XML,
[names_as_atom, {check_elems, xmpp},
{check_nss,xmpp}, {check_attrs,xmpp}]) of
[El] ->
[{El, Seq}]
catch
_ ->
[]
end
end, Rs);
_ ->
@ -477,22 +493,27 @@ user_queue_parse_query(Username, LServer, Query) ->
end.
us_to_list({User, Server}) ->
jlib:jid_to_string({User, Server, ""}).
exmpp_jid:to_list(User, Server).
webadmin_user(Acc, User, Server, Lang) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
QueueLen = case catch ejabberd_odbc:sql_query(
LServer,
["select count(*) from spool"
" where username='", Username, "';"]) of
{selected, [_], [{SCount}]} ->
SCount;
_ ->
0
end,
FQueueLen = [?AC("queue/", QueueLen)],
FQueueLen = try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
Username = ejabberd_odbc:escape(LUser),
QueueLen = case catch ejabberd_odbc:sql_query(
LServer,
["select count(*) from spool"
" where username='", Username, "';"]) of
{selected, [_], [{SCount}]} ->
SCount;
_ ->
0
end,
[?AC("queue/", QueueLen)]
catch
_ ->
[?C("?")]
end,
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->

View File

@ -31,10 +31,9 @@
-behavior(gen_server).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-define(SUPERVISOR, ejabberd_sup).
-define(NS_PING, "urn:xmpp:ping").
-define(DEFAULT_SEND_PINGS, false). % bool()
-define(DEFAULT_PING_INTERVAL, 60). % seconds
@ -92,22 +91,23 @@ stop(Host) ->
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
HostB = list_to_binary(Host),
SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS),
PingInterval = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL),
TimeoutAction = gen_mod:get_opt(timeout_action, Opts, none),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mod_disco:register_feature(Host, ?NS_PING),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PING,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PING,
?MODULE, iq_ping, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PING,
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_PING,
?MODULE, iq_ping, IQDisc),
case SendPings of
true ->
ejabberd_hooks:add(sm_register_connection_hook, Host,
ejabberd_hooks:add(sm_register_connection_hook, HostB,
?MODULE, user_online, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
ejabberd_hooks:add(sm_remove_connection_hook, HostB,
?MODULE, user_offline, 100),
ejabberd_hooks:add(user_send_packet, Host,
ejabberd_hooks:add(user_send_packet, HostB,
?MODULE, user_send, 100);
_ ->
ok
@ -119,14 +119,15 @@ init([Host, Opts]) ->
timers = ?DICT:new()}}.
terminate(_Reason, #state{host = Host}) ->
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(sm_remove_connection_hook, HostB,
?MODULE, user_offline, 100),
ejabberd_hooks:delete(sm_register_connection_hook, Host,
ejabberd_hooks:delete(sm_register_connection_hook, HostB,
?MODULE, user_online, 100),
ejabberd_hooks:delete(user_send_packet, Host,
ejabberd_hooks:delete(user_send_packet, HostB,
?MODULE, user_send, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PING),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PING),
gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_PING),
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PING),
mod_disco:unregister_feature(Host, ?NS_PING).
handle_call(stop, _From, State) ->
@ -142,11 +143,10 @@ handle_cast({stop_ping, JID}, State) ->
{noreply, State#state{timers = Timers}};
handle_cast({iq_pong, JID, timeout}, State) ->
Timers = del_timer(JID, State#state.timers),
ejabberd_hooks:run(user_ping_timeout, State#state.host, [JID]),
ejabberd_hooks:run(user_ping_timeout, list_to_binary(State#state.host), [JID]),
case State#state.timeout_action of
kill ->
#jid{user = User, server = Server, resource = Resource} = JID,
case ejabberd_sm:get_session_pid(User, Server, Resource) of
case ejabberd_sm:get_session_pid(JID) of
Pid when is_pid(Pid) ->
ejabberd_c2s:stop(Pid);
_ ->
@ -160,13 +160,18 @@ handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({timeout, _TRef, {ping, JID}}, State) ->
IQ = #iq{type = get,
sub_el = [{xmlelement, "ping", [{"xmlns", ?NS_PING}], []}]},
%%IQ = #iq{type = get,
%% sub_el = [{xmlelement, "ping", [{"xmlns", ?NS_PING}], []}]},
%% Build an iq:query request
%%IQ = exmpp_iq:get(?NS_PING, #xmlel{ns = ?NS_PING, name = 'ping'}),
IQ = #iq{type = get, payload = #xmlel{name = 'ping', ns = ?NS_PING}},
Pid = self(),
F = fun(Response) ->
gen_server:cast(Pid, {iq_pong, JID, Response})
end,
From = jlib:make_jid("", State#state.host, ""),
From = exmpp_jid:make(State#state.host),
ejabberd_local:route_iq(From, JID, IQ, F),
Timers = add_timer(JID, State#state.ping_interval, State#state.timers),
{noreply, State#state{timers = Timers}};
@ -179,28 +184,31 @@ code_change(_OldVsn, State, _Extra) ->
%%====================================================================
%% Hook callbacks
%%====================================================================
iq_ping(_From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
iq_ping(_From, _To, #iq{type = Type, payload = SubEl} = IQ) ->
case {Type, SubEl} of
{get, {xmlelement, "ping", _, _}} ->
IQ#iq{type = result, sub_el = []};
{get, #xmlel{name = ping, ns = ?NS_PING}} ->
exmpp_iq:result(IQ);
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
exmpp_iq:error(IQ, 'feature-not-implemented')
end.
user_online(_SID, JID, _Info) ->
start_ping(JID#jid.lserver, JID).
Host = exmpp_jid:prep_domain_as_list(JID),
start_ping(Host, JID).
user_offline(_SID, JID, _Info) ->
stop_ping(JID#jid.lserver, JID).
Host = exmpp_jid:prep_domain_as_list(JID),
start_ping(Host, JID).
user_send(JID, _From, _Packet) ->
start_ping(JID#jid.lserver, JID).
Host = exmpp_jid:prep_domain_as_list(JID),
start_ping(Host, JID).
%%====================================================================
%% Internal functions
%%====================================================================
add_timer(JID, Interval, Timers) ->
LJID = jlib:jid_tolower(JID),
LJID = exmpp_jid:prep_to_binary(JID),
NewTimers = case ?DICT:find(LJID, Timers) of
{ok, OldTRef} ->
cancel_timer(OldTRef),
@ -212,7 +220,7 @@ add_timer(JID, Interval, Timers) ->
?DICT:store(LJID, TRef, NewTimers).
del_timer(JID, Timers) ->
LJID = jlib:jid_tolower(JID),
LJID = exmpp_jid:prep_to_binary(JID),
case ?DICT:find(LJID, Timers) of
{ok, TRef} ->
cancel_timer(TRef),

View File

@ -38,142 +38,137 @@
remove_user/2,
updated_list/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_privacy.hrl").
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mnesia:create_table(privacy, [{disc_copies, [node()]},
{attributes, record_info(fields, privacy)}]),
update_table(),
ejabberd_hooks:add(privacy_iq_get, Host,
ejabberd_hooks:add(privacy_iq_get, HostB,
?MODULE, process_iq_get, 50),
ejabberd_hooks:add(privacy_iq_set, Host,
ejabberd_hooks:add(privacy_iq_set, HostB,
?MODULE, process_iq_set, 50),
ejabberd_hooks:add(privacy_get_user_list, Host,
ejabberd_hooks:add(privacy_get_user_list, HostB,
?MODULE, get_user_list, 50),
ejabberd_hooks:add(privacy_check_packet, Host,
ejabberd_hooks:add(privacy_check_packet, HostB,
?MODULE, check_packet, 50),
ejabberd_hooks:add(privacy_updated_list, Host,
ejabberd_hooks:add(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY,
?MODULE, process_iq, IQDisc).
stop(Host) ->
ejabberd_hooks:delete(privacy_iq_get, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(privacy_iq_get, HostB,
?MODULE, process_iq_get, 50),
ejabberd_hooks:delete(privacy_iq_set, Host,
ejabberd_hooks:delete(privacy_iq_set, HostB,
?MODULE, process_iq_set, 50),
ejabberd_hooks:delete(privacy_get_user_list, Host,
ejabberd_hooks:delete(privacy_get_user_list, HostB,
?MODULE, get_user_list, 50),
ejabberd_hooks:delete(privacy_check_packet, Host,
ejabberd_hooks:delete(privacy_check_packet, HostB,
?MODULE, check_packet, 50),
ejabberd_hooks:delete(privacy_updated_list, Host,
ejabberd_hooks:delete(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:delete(remove_user, Host,
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY).
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY).
process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
process_iq(_From, _To, IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
process_iq_get(_, From, _To, #iq{sub_el = SubEl},
process_iq_get(_, From, _To, #iq{payload = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
{xmlelement, _, _, Els} = SubEl,
case xml:remove_cdata(Els) of
LUser = exmpp_jid:prep_node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
case exmpp_xml:get_child_elements(SubEl) of
[] ->
process_lists_get(LUser, LServer, Active);
[{xmlelement, Name, Attrs, _SubEls}] ->
[#xmlel{name = Name} = Child] ->
case Name of
"list" ->
ListName = xml:get_attr("name", Attrs),
list ->
ListName = exmpp_xml:get_attribute_as_list(Child, name, false),
process_list_get(LUser, LServer, ListName);
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end.
process_lists_get(LUser, LServer, Active) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, 'internal-server-error'};
[] ->
{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query'}};
[#privacy{default = Default, lists = Lists}] ->
case Lists of
[] ->
{result, [{xmlelement, "query",
[{"xmlns", ?NS_PRIVACY}], []}]};
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query'}};
_ ->
LItems = lists:map(
fun({N, _}) ->
{xmlelement, "list",
[{"name", N}], []}
exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = list}, name, N)
end, Lists),
DItems =
case Default of
none ->
LItems;
_ ->
[{xmlelement, "default",
[{"name", Default}], []} | LItems]
[exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = default}, name, Default) | LItems]
end,
ADItems =
case Active of
none ->
DItems;
_ ->
[{xmlelement, "active",
[{"name", Active}], []} | DItems]
[exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = active}, name, Active) | DItems]
end,
{result,
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
ADItems}]}
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query', children = ADItems}}
end
end.
process_list_get(LUser, LServer, {value, Name}) ->
process_list_get(_LUser, _LServer, false) ->
{error, 'bad-request'};
process_list_get(LUser, LServer, Name) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, 'internal-server-error'};
[] ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
%{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
[#privacy{lists = Lists}] ->
case lists:keysearch(Name, 1, Lists) of
{value, {_, List}} ->
LItems = lists:map(fun item_to_xml/1, List),
{result,
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
[{xmlelement, "list",
[{"name", Name}], LItems}]}]};
ListEl = exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = list, children = LItems}, name, Name),
{result,#xmlel{ns = ?NS_PRIVACY, name = 'query', children = [ListEl]}};
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
{error, 'item-not-found'}
end
end;
end.
process_list_get(_LUser, _LServer, false) ->
{error, ?ERR_BAD_REQUEST}.
item_to_xml(Item) ->
Attrs1 = [{"action", action_to_list(Item#listitem.action)},
{"order", order_to_list(Item#listitem.order)}],
Attrs1 = [?XMLATTR('action', action_to_binary(Item#listitem.action)),
?XMLATTR('order', order_to_binary(Item#listitem.order))],
Attrs2 = case Item#listitem.type of
none ->
Attrs1;
Type ->
[{"type", type_to_list(Item#listitem.type)},
{"value", value_to_list(Type, Item#listitem.value)} |
[?XMLATTR('type', type_to_binary(Item#listitem.type)),
?XMLATTR('value', value_to_binary(Type, Item#listitem.value)) |
Attrs1]
end,
SubEls = case Item#listitem.match_all of
@ -182,59 +177,61 @@ item_to_xml(Item) ->
false ->
SE1 = case Item#listitem.match_iq of
true ->
[{xmlelement, "iq", [], []}];
[#xmlel{ns = ?NS_PRIVACY, name = iq}];
false ->
[]
end,
SE2 = case Item#listitem.match_message of
true ->
[{xmlelement, "message", [], []} | SE1];
[#xmlel{ns = ?NS_PRIVACY, name = message} | SE1];
false ->
SE1
end,
SE3 = case Item#listitem.match_presence_in of
true ->
[{xmlelement, "presence-in", [], []} | SE2];
[#xmlel{ns = ?NS_PRIVACY, name = 'presence-in'} | SE2];
false ->
SE2
end,
SE4 = case Item#listitem.match_presence_out of
true ->
[{xmlelement, "presence-out", [], []} | SE3];
[#xmlel{ns = ?NS_PRIVACY, name = 'presence-out'} | SE3];
false ->
SE3
end,
SE4
end,
{xmlelement, "item", Attrs2, SubEls}.
exmpp_xml:set_attributes(#xmlel{ns = ?NS_PRIVACY, name = item, children = SubEls}, Attrs2).
action_to_list(Action) ->
action_to_binary(Action) ->
case Action of
allow -> "allow";
deny -> "deny"
allow -> <<"allow">>;
deny -> <<"deny">>
end.
order_to_list(Order) ->
integer_to_list(Order).
order_to_binary(Order) ->
list_to_binary(integer_to_list(Order)).
type_to_list(Type) ->
type_to_binary(Type) ->
case Type of
jid -> "jid";
group -> "group";
subscription -> "subscription"
jid -> <<"jid">>;
group -> <<"group">>;
subscription -> <<"subscription">>
end.
value_to_list(Type, Val) ->
value_to_binary(Type, Val) ->
case Type of
jid -> jlib:jid_to_string(Val);
jid ->
{N, D, R} = Val,
exmpp_jid:to_binary(N, D, R);
group -> Val;
subscription ->
case Val of
both -> "both";
to -> "to";
from -> "from";
none -> "none"
both -> <<"both">>;
to -> <<"to">>;
from -> <<"from">>;
none -> <<"none">>
end
end.
@ -248,53 +245,28 @@ list_to_action(S) ->
process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
{xmlelement, _, _, Els} = SubEl,
case xml:remove_cdata(Els) of
[{xmlelement, Name, Attrs, SubEls}] ->
ListName = xml:get_attr("name", Attrs),
process_iq_set(_, From, _To, #iq{payload = SubEl}) ->
LUser = exmpp_jid:prep_node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
case exmpp_xml:get_child_elements(SubEl) of
[#xmlel{name = Name} = Child] ->
ListName = exmpp_xml:get_attribute_as_list(Child, 'name', false),
case Name of
"list" ->
list ->
process_list_set(LUser, LServer, ListName,
xml:remove_cdata(SubEls));
"active" ->
exmpp_xml:get_child_elements(Child));
active ->
process_active_set(LUser, LServer, ListName);
"default" ->
default ->
process_default_set(LUser, LServer, ListName);
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end.
process_default_set(LUser, LServer, {value, Name}) ->
F = fun() ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
{error, ?ERR_ITEM_NOT_FOUND};
[#privacy{lists = Lists} = P] ->
case lists:keymember(Name, 1, Lists) of
true ->
mnesia:write(P#privacy{default = Name,
lists = Lists}),
{result, []};
false ->
{error, ?ERR_ITEM_NOT_FOUND}
end
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
process_default_set(LUser, LServer, false) ->
F = fun() ->
case mnesia:read({privacy, {LUser, LServer}}) of
@ -311,32 +283,61 @@ process_default_set(LUser, LServer, false) ->
{atomic, {result, _} = Res} ->
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end;
process_default_set(LUser, LServer, Name) ->
F = fun() ->
case mnesia:read({privacy, {LUser, LServer}}) of
[] ->
{error, 'item-not-found'};
[#privacy{lists = Lists} = P] ->
case lists:keymember(Name, 1, Lists) of
true ->
mnesia:write(P#privacy{default = Name,
lists = Lists}),
{result, []};
false ->
{error, 'item-not-found'}
end
end
end,
case mnesia:transaction(F) of
{atomic, {error, _} = Error} ->
Error;
{atomic, {result, _} = Res} ->
Res;
_ ->
{error, 'internal-server-error'}
end.
process_active_set(LUser, LServer, {value, Name}) ->
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}};
process_active_set(LUser, LServer, Name) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
[] ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
[#privacy{lists = Lists}] ->
case lists:keysearch(Name, 1, Lists) of
{value, {_, List}} ->
NeedDb = is_list_needdb(List),
{result, [], #userlist{name = Name, list = List, needdb = NeedDb}};
false ->
{error, ?ERR_ITEM_NOT_FOUND}
{error, 'item-not-found'}
end
end;
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
end.
process_list_set(LUser, LServer, {value, Name}, Els) ->
process_list_set(_LUser, _LServer, false, _Els) ->
{error, 'bad-request'};
process_list_set(LUser, LServer, Name, Els) ->
case parse_items(Els) of
false ->
{error, ?ERR_BAD_REQUEST};
{error, 'bad-request'};
remove ->
F =
fun() ->
@ -347,7 +348,7 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
% TODO: check active
if
Name == Default ->
{error, ?ERR_CONFLICT};
{error, 'conflict'};
true ->
NewLists =
lists:keydelete(Name, 1, Lists),
@ -362,15 +363,15 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
Error;
{atomic, {result, _} = Res} ->
ejabberd_router:route(
jlib:make_jid(LUser, LServer, ""),
jlib:make_jid(LUser, LServer, ""),
{xmlelement, "broadcast", [],
[{privacy_list,
#userlist{name = Name, list = []},
Name}]}),
exmpp_jid:make(LUser, LServer),
exmpp_jid:make(LUser, LServer),
#xmlel{name = 'broadcast',
children=[{privacy_list,
#userlist{name = Name, list = []},
Name}]}),
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end;
List ->
F =
@ -393,20 +394,18 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
Error;
{atomic, {result, _} = Res} ->
ejabberd_router:route(
jlib:make_jid(LUser, LServer, ""),
jlib:make_jid(LUser, LServer, ""),
{xmlelement, "broadcast", [],
[{privacy_list,
#userlist{name = Name, list = List},
Name}]}),
exmpp_jid:make(LUser, LServer),
exmpp_jid:make(LUser, LServer),
#xmlel{name = 'broadcast',
children=[{privacy_list,
#userlist{name = Name, list = List},
Name}]}),
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal_server_error'}
end
end;
end.
process_list_set(_LUser, _LServer, false, _Els) ->
{error, ?ERR_BAD_REQUEST}.
parse_items([]) ->
@ -417,16 +416,16 @@ parse_items(Els) ->
parse_items([], Res) ->
%% Sort the items by their 'order' attribute
lists:keysort(#listitem.order, Res);
parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
Type = xml:get_attr("type", Attrs),
Value = xml:get_attr("value", Attrs),
SAction = xml:get_attr("action", Attrs),
SOrder = xml:get_attr("order", Attrs),
Action = case catch list_to_action(element(2, SAction)) of
parse_items([El = #xmlel{name = item} | Els], Res) ->
Type = exmpp_xml:get_attribute_as_list(El, type, false),
Value = exmpp_xml:get_attribute_as_list(El, value, false),
SAction =exmpp_xml:get_attribute_as_list(El, action, false),
SOrder = exmpp_xml:get_attribute_as_list(El, order, false),
Action = case catch list_to_action(SAction) of
{'EXIT', _} -> false;
Val -> Val
end,
Order = case catch list_to_integer(element(2, SOrder)) of
Order = case catch list_to_integer(SOrder) of
{'EXIT', _} ->
false;
IntVal ->
@ -441,16 +440,17 @@ parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
(Action /= false) and (Order /= false) ->
I1 = #listitem{action = Action, order = Order},
I2 = case {Type, Value} of
{{value, T}, {value, V}} ->
{T, V} when is_list(T), is_list(V) ->
case T of
"jid" ->
case jlib:string_to_jid(V) of
error ->
false;
JID ->
I1#listitem{
type = jid,
value = jlib:jid_tolower(JID)}
try
JID = exmpp_jid:parse(V),
I1#listitem{
type = jid,
value = jlib:short_prepd_jid(JID)}
catch
_ ->
false
end;
"group" ->
I1#listitem{type = group,
@ -473,7 +473,7 @@ parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
false
end
end;
{{value, _}, false} ->
{T, false} when is_list(T) ->
false;
_ ->
I1
@ -482,7 +482,7 @@ parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
false ->
false;
_ ->
case parse_matches(I2, xml:remove_cdata(SubEls)) of
case parse_matches(I2, exmpp_xml:get_child_elements(El)) of
false ->
false;
I3 ->
@ -504,15 +504,15 @@ parse_matches(Item, Els) ->
parse_matches1(Item, []) ->
Item;
parse_matches1(Item, [{xmlelement, "message", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = message} | Els]) ->
parse_matches1(Item#listitem{match_message = true}, Els);
parse_matches1(Item, [{xmlelement, "iq", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = iq} | Els]) ->
parse_matches1(Item#listitem{match_iq = true}, Els);
parse_matches1(Item, [{xmlelement, "presence-in", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = 'presence-in'} | Els]) ->
parse_matches1(Item#listitem{match_presence_in = true}, Els);
parse_matches1(Item, [{xmlelement, "presence-out", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = 'presence-out'} | Els]) ->
parse_matches1(Item#listitem{match_presence_out = true}, Els);
parse_matches1(_Item, [{xmlelement, _, _, _} | _Els]) ->
parse_matches1(_Item, [#xmlel{} | _Els]) ->
false.
@ -531,25 +531,31 @@ is_list_needdb(Items) ->
end
end, Items).
get_user_list(_, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
[] ->
#userlist{};
[#privacy{default = Default, lists = Lists}] ->
case Default of
none ->
#userlist{};
_ ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
NeedDb = is_list_needdb(List),
#userlist{name = Default, list = List, needdb = NeedDb};
_ ->
#userlist{}
end
end;
get_user_list(_, User, Server)
when is_binary(User), is_binary(Server) ->
try
LUser = binary_to_list(User),
LServer = binary_to_list(Server),
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
[] ->
#userlist{};
[#privacy{default = Default, lists = Lists}] ->
case Default of
none ->
#userlist{};
_ ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} ->
SortedList = lists:keysort(#listitem.order, List),
#userlist{name = Default, list = SortedList};
_ ->
#userlist{}
end
end;
_ ->
#userlist{}
end
catch
_ ->
#userlist{}
end.
@ -560,20 +566,23 @@ get_user_list(_, User, Server) ->
%% If Dir = in, User@Server is the destination account (To).
check_packet(_, User, Server,
#userlist{list = List, needdb = NeedDb},
{From, To, {xmlelement, PName, Attrs, _}},
Dir) ->
{From, To, #xmlel{name = PName} = El},
Dir) when
PName =:= message ;
PName =:= iq ;
PName =:= presence ->
case List of
[] ->
allow;
_ ->
PType = case PName of
"message" -> message;
"iq" -> iq;
"presence" ->
case xml:get_attr_s("type", Attrs) of
'message' -> message;
'iq' -> iq;
'presence' ->
case exmpp_xml:get_attribute(El, type, '') of
%% notification
"" -> presence;
"unavailable" -> presence;
'' -> presence;
'unavailable' -> presence;
%% subscribe, subscribed, unsubscribe,
%% unsubscribed, error, probe, or other
_ -> other
@ -587,13 +596,13 @@ check_packet(_, User, Server,
{_, _} -> other
end,
LJID = case Dir of
in -> jlib:jid_tolower(From);
out -> jlib:jid_tolower(To)
in -> From;
out -> To
end,
{Subscription, Groups} =
case NeedDb of
true -> ejabberd_hooks:run_fold(roster_get_jid_info,
jlib:nameprep(Server),
exmpp_stringprep:nameprep(Server),
{none, []},
[User, Server, LJID]);
false -> {[], []}
@ -646,27 +655,14 @@ is_ptype_match(Item, PType) ->
end.
%% TODO: Investigate this: sometimes Value has binaries, other times has strings
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->
case Value of
{"", Server, ""} ->
case JID of
{_, Server, _} ->
true;
_ ->
false
end;
{User, Server, ""} ->
case JID of
{User, Server, _} ->
true;
_ ->
false
end;
_ ->
Value == JID
end;
{User, Server, Resource} = Value,
((User == undefined) orelse (User == []) orelse (User == exmpp_jid:prep_node(JID)))
andalso ((Server == undefined) orelse (Server == []) orelse (Server == exmpp_jid:prep_domain(JID)))
andalso ((Resource == undefined) orelse (Resource == []) orelse (Resource == exmpp_jid:prep_resource(JID)));
subscription ->
Value == Subscription;
group ->
@ -675,8 +671,8 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
F = fun() ->
mnesia:delete({privacy,
{LUser, LServer}})
@ -699,7 +695,7 @@ update_table() ->
Fields = record_info(fields, privacy),
case mnesia:table_info(privacy, attributes) of
Fields ->
ok;
convert_to_exmpp();
[user, default, lists] ->
?INFO_MSG("Converting privacy table from "
"{user, default, lists} format", []),
@ -715,10 +711,12 @@ update_table() ->
F1 = fun() ->
mnesia:write_lock_table(mod_privacy_tmp_table),
mnesia:foldl(
fun(#privacy{us = U} = R, _) ->
fun(#privacy{us = U, lists = L} = R, _) ->
U1 = convert_jid_to_exmpp(U),
L1 = convert_lists_to_exmpp(L),
mnesia:dirty_write(
mod_privacy_tmp_table,
R#privacy{us = {U, Host}})
R#privacy{us = {U1, Host}, lists = L1})
end, ok, privacy)
end,
mnesia:transaction(F1),
@ -738,3 +736,42 @@ update_table() ->
end.
convert_to_exmpp() ->
Fun = fun() ->
mnesia:foldl(fun convert_to_exmpp2/2, done, privacy, write)
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#privacy{us = {U, S} = Key, lists = L} = P, Acc) ->
U1 = convert_jid_to_exmpp(U),
L1 = convert_lists_to_exmpp(L),
New_P = P#privacy{
us = {U1, S},
lists = L1
},
if
New_P /= P -> mnesia:delete({privacy, Key}), mnesia:write(New_P);
true -> ok
end,
Acc.
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) -> V.
convert_lists_to_exmpp(L) ->
convert_lists_to_exmpp2(L, []).
convert_lists_to_exmpp2([{Name, List} | Rest], Result) ->
convert_lists_to_exmpp2(Rest,
[{Name, convert_list_to_exmpp(List, [])} | Result]);
convert_lists_to_exmpp2([], Result) ->
lists:reverse(Result).
convert_list_to_exmpp([#listitem{type = jid, value = {U, S, R}} = I | Rest],
Result) ->
U1 = convert_jid_to_exmpp(U),
R1 = convert_jid_to_exmpp(R),
New_I = I#listitem{value = {U1, S, R1}},
convert_list_to_exmpp(Rest, [New_I | Result]);
convert_list_to_exmpp([], Result) ->
lists:reverse(Result).

View File

@ -38,64 +38,66 @@
remove_user/2,
updated_list/3]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_privacy.hrl").
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
ejabberd_hooks:add(privacy_iq_get, Host,
ejabberd_hooks:add(privacy_iq_get, HostB,
?MODULE, process_iq_get, 50),
ejabberd_hooks:add(privacy_iq_set, Host,
ejabberd_hooks:add(privacy_iq_set, HostB,
?MODULE, process_iq_set, 50),
ejabberd_hooks:add(privacy_get_user_list, Host,
ejabberd_hooks:add(privacy_get_user_list, HostB,
?MODULE, get_user_list, 50),
ejabberd_hooks:add(privacy_check_packet, Host,
ejabberd_hooks:add(privacy_check_packet, HostB,
?MODULE, check_packet, 50),
ejabberd_hooks:add(privacy_updated_list, Host,
ejabberd_hooks:add(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY,
?MODULE, process_iq, IQDisc).
stop(Host) ->
ejabberd_hooks:delete(privacy_iq_get, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(privacy_iq_get, HostB,
?MODULE, process_iq_get, 50),
ejabberd_hooks:delete(privacy_iq_set, Host,
ejabberd_hooks:delete(privacy_iq_set, HostB,
?MODULE, process_iq_set, 50),
ejabberd_hooks:delete(privacy_get_user_list, Host,
ejabberd_hooks:delete(privacy_get_user_list, HostB,
?MODULE, get_user_list, 50),
ejabberd_hooks:delete(privacy_check_packet, Host,
ejabberd_hooks:delete(privacy_check_packet, HostB,
?MODULE, check_packet, 50),
ejabberd_hooks:delete(privacy_updated_list, Host,
ejabberd_hooks:delete(privacy_updated_list, HostB,
?MODULE, updated_list, 50),
ejabberd_hooks:delete(remove_user, Host,
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY).
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PRIVACY).
process_iq(_From, _To, IQ) ->
SubEl = IQ#iq.sub_el,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}.
process_iq(_From, _To, IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed').
process_iq_get(_, From, _To, #iq{sub_el = SubEl},
process_iq_get(_, From, _To, #iq{payload = SubEl},
#userlist{name = Active}) ->
#jid{luser = LUser, lserver = LServer} = From,
{xmlelement, _, _, Els} = SubEl,
case xml:remove_cdata(Els) of
LUser = exmpp_jid:prep_node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
case exmpp_xml:get_child_elements(SubEl) of
[] ->
process_lists_get(LUser, LServer, Active);
[{xmlelement, Name, Attrs, _SubEls}] ->
[#xmlel{name = Name} = Child] ->
case Name of
"list" ->
ListName = xml:get_attr("name", Attrs),
list ->
ListName = exmpp_xml:get_attribute_as_list(Child, name, false),
process_list_get(LUser, LServer, ListName);
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end.
@ -110,40 +112,38 @@ process_lists_get(LUser, LServer, Active) ->
end,
case catch sql_get_privacy_list_names(LUser, LServer) of
{selected, ["name"], []} ->
{result, [{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}], []}]};
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query'}};
{selected, ["name"], Names} ->
LItems = lists:map(
fun({N}) ->
{xmlelement, "list",
[{"name", N}], []}
exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = list}, name, N)
end, Names),
DItems =
case Default of
none ->
LItems;
_ ->
[{xmlelement, "default",
[{"name", Default}], []} | LItems]
[exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = default}, name, Default) | LItems]
end,
ADItems =
case Active of
none ->
DItems;
_ ->
[{xmlelement, "active",
[{"name", Active}], []} | DItems]
[exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = active}, name, Active) | DItems]
end,
{result,
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
ADItems}]};
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query', children = ADItems}};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end.
process_list_get(LUser, LServer, {value, Name}) ->
process_list_get(_LUser, _LServer, false) ->
{error, 'bad-request'};
process_list_get(LUser, LServer, Name) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
{selected, ["id"], []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
{selected, ["id"], [{ID}]} ->
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
{selected, ["t", "value", "action", "ord", "match_all",
@ -152,29 +152,29 @@ process_list_get(LUser, LServer, {value, Name}) ->
RItems} ->
Items = lists:map(fun raw_to_item/1, RItems),
LItems = lists:map(fun item_to_xml/1, Items),
{result,
[{xmlelement, "query", [{"xmlns", ?NS_PRIVACY}],
[{xmlelement, "list",
[{"name", Name}], LItems}]}]};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
ListEl = exmpp_xml:set_attribute(#xmlel{name = list,
ns = ?NS_PRIVACY,
children = LItems},
name,
Name),
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query', children = [ListEl]}};
_ ->
{error, 'internal-server-error'}
end;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
{error, 'internal-server-error'}
end.
process_list_get(_LUser, _LServer, false) ->
{error, ?ERR_BAD_REQUEST}.
item_to_xml(Item) ->
Attrs1 = [{"action", action_to_list(Item#listitem.action)},
{"order", order_to_list(Item#listitem.order)}],
Attrs1 = [?XMLATTR('action', action_to_binary(Item#listitem.action)),
?XMLATTR('order', order_to_binary(Item#listitem.order))],
Attrs2 = case Item#listitem.type of
none ->
Attrs1;
Type ->
[{"type", type_to_list(Item#listitem.type)},
{"value", value_to_list(Type, Item#listitem.value)} |
[?XMLATTR('type', type_to_binary(Item#listitem.type)),
?XMLATTR('value', value_to_binary(Type, Item#listitem.value)) |
Attrs1]
end,
SubEls = case Item#listitem.match_all of
@ -183,59 +183,61 @@ item_to_xml(Item) ->
false ->
SE1 = case Item#listitem.match_iq of
true ->
[{xmlelement, "iq", [], []}];
[#xmlel{ns = ?NS_PRIVACY, name = iq}];
false ->
[]
end,
SE2 = case Item#listitem.match_message of
true ->
[{xmlelement, "message", [], []} | SE1];
[#xmlel{ns = ?NS_PRIVACY, name = message} | SE1];
false ->
SE1
end,
SE3 = case Item#listitem.match_presence_in of
true ->
[{xmlelement, "presence-in", [], []} | SE2];
[#xmlel{ns = ?NS_PRIVACY, name = 'presence-in'} | SE2];
false ->
SE2
end,
SE4 = case Item#listitem.match_presence_out of
true ->
[{xmlelement, "presence-out", [], []} | SE3];
[#xmlel{ns = ?NS_PRIVACY, name = 'presence-out'} | SE3];
false ->
SE3
end,
SE4
end,
{xmlelement, "item", Attrs2, SubEls}.
exmpp_xml:set_attributes(#xmlel{ns = ?NS_PRIVACY, name = item, children = SubEls}, Attrs2).
action_to_list(Action) ->
action_to_binary(Action) ->
case Action of
allow -> "allow";
deny -> "deny"
allow -> <<"allow">>;
deny -> <<"deny">>
end.
order_to_list(Order) ->
integer_to_list(Order).
order_to_binary(Order) ->
list_to_binary(integer_to_list(Order)).
type_to_list(Type) ->
type_to_binary(Type) ->
case Type of
jid -> "jid";
group -> "group";
subscription -> "subscription"
jid -> <<"jid">>;
group -> <<"group">>;
subscription -> <<"subscription">>
end.
value_to_list(Type, Val) ->
value_to_binary(Type, Val) ->
case Type of
jid -> jlib:jid_to_string(Val);
jid ->
{N, D, R} = Val,
exmpp_jid:to_binary(N, D, R);
group -> Val;
subscription ->
case Val of
both -> "both";
to -> "to";
from -> "from";
none -> "none"
both -> <<"both">>;
to -> <<"to">>;
from -> <<"from">>;
none -> <<"none">>
end
end.
@ -249,40 +251,50 @@ list_to_action(S) ->
process_iq_set(_, From, _To, #iq{sub_el = SubEl}) ->
#jid{luser = LUser, lserver = LServer} = From,
{xmlelement, _, _, Els} = SubEl,
case xml:remove_cdata(Els) of
[{xmlelement, Name, Attrs, SubEls}] ->
ListName = xml:get_attr("name", Attrs),
process_iq_set(_, From, _To, #iq{payload = SubEl}) ->
LUser = exmpp_jid:prep_node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
case exmpp_xml:get_child_elements(SubEl) of
[#xmlel{name = Name} = Child] ->
ListName = exmpp_xml:get_attribute_as_list(Child, 'name', false),
case Name of
"list" ->
list ->
process_list_set(LUser, LServer, ListName,
xml:remove_cdata(SubEls));
"active" ->
exmpp_xml:get_child_elements(Child));
active ->
process_active_set(LUser, LServer, ListName);
"default" ->
default ->
process_default_set(LUser, LServer, ListName);
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end;
_ ->
{error, ?ERR_BAD_REQUEST}
{error, 'bad-request'}
end.
process_default_set(LUser, LServer, {value, Name}) ->
process_default_set(LUser, LServer, false) ->
case catch sql_unset_default_privacy_list(LUser, LServer) of
{'EXIT', _Reason} ->
{error, 'internal_server_error'};
{error, _Reason} ->
{error, 'internal_server_error'};
_ ->
{result, []}
end;
process_default_set(LUser, LServer, Name) ->
F = fun() ->
case sql_get_privacy_list_names_t(LUser) of
{selected, ["name"], []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
{selected, ["name"], Names} ->
case lists:member({Name}, Names) of
case lists:member({Name}, Names) of
true ->
sql_set_default_privacy_list(LUser, Name),
{result, []};
false ->
{error, ?ERR_ITEM_NOT_FOUND}
{error, 'item-not-found'}
end
end
end,
@ -292,24 +304,17 @@ process_default_set(LUser, LServer, {value, Name}) ->
{atomic, {result, _} = Res} ->
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
process_default_set(LUser, LServer, false) ->
case catch sql_unset_default_privacy_list(LUser, LServer) of
{'EXIT', _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
{error, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR};
_ ->
{result, []}
{error, 'internal-server-error'}
end.
process_active_set(LUser, LServer, {value, Name}) ->
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}};
process_active_set(LUser, LServer, Name) ->
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
{selected, ["id"], []} ->
{error, ?ERR_ITEM_NOT_FOUND};
{error, 'item-not-found'};
{selected, ["id"], [{ID}]} ->
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
{selected, ["t", "value", "action", "ord", "match_all",
@ -320,24 +325,21 @@ process_active_set(LUser, LServer, {value, Name}) ->
NeedDb = is_list_needdb(Items),
{result, [], #userlist{name = Name, list = Items, needdb = NeedDb}};
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end;
process_active_set(_LUser, _LServer, false) ->
{result, [], #userlist{}}.
{error, 'internal_server_error'}
end.
process_list_set(_LUser, _LServer, false, _Els) ->
{error, 'bad-request'};
process_list_set(LUser, LServer, {value, Name}, Els) ->
process_list_set(LUser, LServer, Name, Els) ->
case parse_items(Els) of
false ->
{error, ?ERR_BAD_REQUEST};
{error, 'bad-request'};
remove ->
F =
fun() ->
@ -346,10 +348,10 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
sql_remove_privacy_list(LUser, Name),
{result, []};
{selected, ["name"], [{Default}]} ->
%% TODO: check active
% TODO: check active
if
Name == Default ->
{error, ?ERR_CONFLICT};
{error, 'conflict'};
true ->
sql_remove_privacy_list(LUser, Name),
{result, []}
@ -361,15 +363,15 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
Error;
{atomic, {result, _} = Res} ->
ejabberd_router:route(
jlib:make_jid(LUser, LServer, ""),
jlib:make_jid(LUser, LServer, ""),
{xmlelement, "broadcast", [],
[{privacy_list,
#userlist{name = Name, list = []},
Name}]}),
exmpp_jid:make(LUser, LServer),
exmpp_jid:make(LUser, LServer),
#xmlel{name = 'broadcast',
children=[{privacy_list,
#userlist{name = Name, list = []},
Name}]}),
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end;
List ->
RItems = lists:map(fun item_to_raw/1, List),
@ -393,20 +395,18 @@ process_list_set(LUser, LServer, {value, Name}, Els) ->
Error;
{atomic, {result, _} = Res} ->
ejabberd_router:route(
jlib:make_jid(LUser, LServer, ""),
jlib:make_jid(LUser, LServer, ""),
{xmlelement, "broadcast", [],
[{privacy_list,
#userlist{name = Name, list = List},
Name}]}),
exmpp_jid:make(LUser, LServer),
exmpp_jid:make(LUser, LServer),
#xmlel{name = 'broadcast',
children=[{privacy_list,
#userlist{name = Name, list = List},
Name}]}),
Res;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal_server_error'}
end
end;
end.
process_list_set(_LUser, _LServer, false, _Els) ->
{error, ?ERR_BAD_REQUEST}.
parse_items([]) ->
@ -417,16 +417,16 @@ parse_items(Els) ->
parse_items([], Res) ->
%% Sort the items by their 'order' attribute
lists:keysort(#listitem.order, Res);
parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
Type = xml:get_attr("type", Attrs),
Value = xml:get_attr("value", Attrs),
SAction = xml:get_attr("action", Attrs),
SOrder = xml:get_attr("order", Attrs),
Action = case catch list_to_action(element(2, SAction)) of
parse_items([El = #xmlel{name = item} | Els], Res) ->
Type = exmpp_xml:get_attribute_as_list(El, type, false),
Value = exmpp_xml:get_attribute_as_list(El, value, false),
SAction =exmpp_xml:get_attribute_as_list(El, action, false),
SOrder = exmpp_xml:get_attribute_as_list(El, order, false),
Action = case catch list_to_action(SAction) of
{'EXIT', _} -> false;
Val -> Val
end,
Order = case catch list_to_integer(element(2, SOrder)) of
Order = case catch list_to_integer(SOrder) of
{'EXIT', _} ->
false;
IntVal ->
@ -441,16 +441,17 @@ parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
(Action /= false) and (Order /= false) ->
I1 = #listitem{action = Action, order = Order},
I2 = case {Type, Value} of
{{value, T}, {value, V}} ->
{T, V} when is_list(T), is_list(V) ->
case T of
"jid" ->
case jlib:string_to_jid(V) of
error ->
false;
JID ->
I1#listitem{
type = jid,
value = jlib:jid_tolower(JID)}
try
JID = exmpp_jid:parse(V),
I1#listitem{
type = jid,
value = jlib:short_prepd_jid(JID)}
catch
_ ->
false
end;
"group" ->
I1#listitem{type = group,
@ -473,7 +474,7 @@ parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
false
end
end;
{{value, _}, false} ->
{T, false} when is_list(T) ->
false;
_ ->
I1
@ -482,7 +483,7 @@ parse_items([{xmlelement, "item", Attrs, SubEls} | Els], Res) ->
false ->
false;
_ ->
case parse_matches(I2, xml:remove_cdata(SubEls)) of
case parse_matches(I2, exmpp_xml:get_child_elements(El)) of
false ->
false;
I3 ->
@ -504,15 +505,15 @@ parse_matches(Item, Els) ->
parse_matches1(Item, []) ->
Item;
parse_matches1(Item, [{xmlelement, "message", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = message} | Els]) ->
parse_matches1(Item#listitem{match_message = true}, Els);
parse_matches1(Item, [{xmlelement, "iq", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = iq} | Els]) ->
parse_matches1(Item#listitem{match_iq = true}, Els);
parse_matches1(Item, [{xmlelement, "presence-in", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = 'presence-in'} | Els]) ->
parse_matches1(Item#listitem{match_presence_in = true}, Els);
parse_matches1(Item, [{xmlelement, "presence-out", _, _} | Els]) ->
parse_matches1(Item, [#xmlel{name = 'presence-out'} | Els]) ->
parse_matches1(Item#listitem{match_presence_out = true}, Els);
parse_matches1(_Item, [{xmlelement, _, _, _} | _Els]) ->
parse_matches1(_Item, [#xmlel{} | _Els]) ->
false.
@ -529,25 +530,30 @@ is_list_needdb(Items) ->
end
end, Items).
get_user_list(_, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
case catch sql_get_default_privacy_list(LUser, LServer) of
{selected, ["name"], []} ->
#userlist{};
{selected, ["name"], [{Default}]} ->
case catch sql_get_privacy_list_data(LUser, LServer, Default) of
{selected, ["t", "value", "action", "ord", "match_all",
"match_iq", "match_message",
"match_presence_in", "match_presence_out"],
RItems} ->
Items = lists:map(fun raw_to_item/1, RItems),
NeedDb = is_list_needdb(Items),
#userlist{name = Default, list = Items, needdb = NeedDb};
_ ->
#userlist{}
end;
get_user_list(_, User, Server)
when is_binary(User), is_binary(Server) ->
try
LUser = binary_to_list(User),
LServer = binary_to_list(Server),
case catch sql_get_default_privacy_list(LUser, LServer) of
{selected, ["name"], []} ->
#userlist{};
{selected, ["name"], [{Default}]} ->
case catch sql_get_privacy_list_data(LUser, LServer, Default) of
{selected, ["t", "value", "action", "ord", "match_all",
"match_iq", "match_message",
"match_presence_in", "match_presence_out"],
RItems} ->
Items = lists:map(fun raw_to_item/1, RItems),
NeedDb = is_list_needdb(Items),
#userlist{name = Default, list = Items, needdb = NeedDb};
_ ->
#userlist{}
end;
_ ->
#userlist{}
end
catch
_ ->
#userlist{}
end.
@ -555,47 +561,45 @@ get_user_list(_, User, Server) ->
check_packet(_, User, Server,
#userlist{list = List, needdb = NeedDb},
{From, To, {xmlelement, PName, _, _}},
Dir) ->
{From, To, #xmlel{name = PName}},
Dir) when
PName =:= message ;
PName =:= iq ;
PName =:= presence ->
case List of
[] ->
allow;
_ ->
PType = case PName of
"message" -> message;
"iq" -> iq;
"presence" -> presence
end,
case {PType, Dir} of
case {PName, Dir} of
{message, in} ->
LJID = jlib:jid_tolower(From),
LJID = jlib:short_prepd_jid(From),
{Subscription, Groups} =
case NeedDb of
true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]);
true -> ejabberd_hooks:run_fold(roster_get_jid_info, Server, {none, []}, [User, Server, LJID]);
false -> {[], []}
end,
check_packet_aux(List, message,
LJID, Subscription, Groups);
{iq, in} ->
LJID = jlib:jid_tolower(From),
LJID = jlib:short_prepd_jid(From),
{Subscription, Groups} =
case NeedDb of
true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]);
true -> ejabberd_hooks:run_fold(roster_get_jid_info, Server, {none, []}, [User, Server, LJID]);
false -> {[], []}
end,
check_packet_aux(List, iq,
LJID, Subscription, Groups);
{presence, in} ->
LJID = jlib:jid_tolower(From),
LJID = jlib:short_prepd_jid(From),
{Subscription, Groups} =
case NeedDb of
true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]);
true -> ejabberd_hooks:run_fold(roster_get_jid_info, Server, {none, []}, [User, Server, LJID]);
false -> {[], []}
end,
check_packet_aux(List, presence_in,
LJID, Subscription, Groups);
{presence, out} ->
LJID = jlib:jid_tolower(To),
LJID = jlib:short_prepd_jid(To),
{Subscription, Groups} =
case NeedDb of
true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]);
@ -650,27 +654,14 @@ is_ptype_match(Item, PType) ->
end.
%% TODO: Investigate this: sometimes Value has binaries, other times has strings
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->
case Value of
{"", Server, ""} ->
case JID of
{_, Server, _} ->
true;
_ ->
false
end;
{User, Server, ""} ->
case JID of
{User, Server, _} ->
true;
_ ->
false
end;
_ ->
Value == JID
end;
{User, Server, Resource} = Value,
((User == undefined) orelse (User == []) orelse (User == exmpp_jid:prep_node(JID)))
andalso ((Server == undefined) orelse (Server == []) orelse (Server == exmpp_jid:prep_domain(JID)))
andalso ((Resource == undefined) orelse (Resource == []) orelse (Resource == exmpp_jid:prep_resource(JID)));
subscription ->
Value == Subscription;
group ->
@ -679,9 +670,9 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
sql_del_privacy_lists(LUser, LServer).
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
sql_del_privacy_lists(binary_to_list(LUser), binary_to_list(LServer)).
updated_list(_,
@ -702,10 +693,8 @@ raw_to_item({SType, SValue, SAction, SOrder, SMatchAll, SMatchIQ,
"n" ->
{none, none};
"j" ->
case jlib:string_to_jid(SValue) of
#jid{} = JID ->
{jid, jlib:jid_tolower(JID)}
end;
JID = exmpp_jid:parse(SValue),
{jid, jlib:short_prepd_jid(JID)};
"g" ->
{group, SValue};
"s" ->
@ -757,7 +746,8 @@ item_to_raw(#listitem{type = Type,
none ->
{"n", ""};
jid ->
{"j", jlib:jid_to_string(Value)};
{N0, D0, R0} = Value,
{"j", exmpp_jid:to_list(N0, D0, R0)};
group ->
{"g", Value};
subscription ->

View File

@ -34,74 +34,118 @@
process_sm_iq/3,
remove_user/2]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(private_storage, {usns, xml}).
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
mnesia:create_table(private_storage,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, private_storage)}]),
update_table(),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVATE,
?MODULE, process_sm_iq, IQDisc).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE).
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PRIVATE).
process_sm_iq(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
#jid{luser = LUser, lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true ->
{xmlelement, Name, Attrs, Els} = SubEl,
process_sm_iq(From, To, #iq{type = Type} = IQ_Rec) ->
case check_packet(From, To, IQ_Rec) of
ok ->
case Type of
set ->
F = fun() ->
lists:foreach(
fun(El) ->
set_data(LUser, LServer, El)
end, Els)
end,
mnesia:transaction(F),
IQ#iq{type = result,
sub_el = [{xmlelement, Name, Attrs, []}]};
process_iq_set(From, To, IQ_Rec);
get ->
case catch get_data(LUser, LServer, Els) of
{'EXIT', _Reason} ->
IQ#iq{type = error,
sub_el = [SubEl,
?ERR_INTERNAL_SERVER_ERROR]};
Res ->
IQ#iq{type = result,
sub_el = [{xmlelement, Name, Attrs, Res}]}
end
process_iq_get(From, To, IQ_Rec)
end;
false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end.
process_iq_get(From, _To, #iq{payload = SubEl} = IQ_Rec) ->
LUser = exmpp_jid:prep_node(From),
LServer = exmpp_jid:prep_domain(From),
case catch get_data(LUser,
LServer,
exmpp_xml:get_child_elements(SubEl)) of
{'EXIT', _Reason} ->
{error, 'internal-server-error'};
Res ->
exmpp_iq:result(IQ_Rec, #xmlel{ns = ?NS_PRIVATE,
name = 'query',
children = Res})
end.
process_iq_set(From, _To, #iq{payload = SubEl} = IQ_Rec) ->
LUser = exmpp_jid:prep_node(From),
LServer = exmpp_jid:prep_domain(From),
F = fun() ->
lists:foreach(
fun(El) ->
set_data(LUser, LServer, El)
end, exmpp_xml:get_child_elements(SubEl))
end,
mnesia:transaction(F),
exmpp_iq:result(IQ_Rec).
check_packet(From, To, IQ_Rec) ->
check_packet(From, To, IQ_Rec, [ fun check_domain/3,
fun check_user/3,
fun check_ns/3]).
check_packet(_From, _To, _IQ_Rec, []) ->
ok;
check_packet(From, To, IQ_Rec, [F | R]) ->
case F(From, To, IQ_Rec) of
{error, _} = Error -> Error;
ok -> check_packet(From, To, IQ_Rec, R)
end.
check_domain(From, _To, _IQ_Rec) ->
LServer = exmpp_jid:prep_domain_as_list(From),
case lists:member(LServer, ?MYHOSTS) of
true -> ok;
false -> {error, 'not-allowed'}
end.
% the iq can't be directed to another jid
check_user(From, To, _IQ_Rec) ->
case exmpp_jid:bare_compare(From, To) of
true -> ok;
false -> {error, 'forbidden'}
end.
%there must be at least one child, and every child should have
%a namespace specified (reject if the namespace is jabber:iq:private,
%the same than the parent element).
check_ns(_From, _To, #iq{payload = SubEl}) ->
case exmpp_xml:get_child_elements(SubEl) of
[] ->
{error, 'not-acceptable'};
Children ->
case lists:any(fun(Child) ->
exmpp_xml:get_ns_as_atom(Child) =:= ?NS_PRIVATE
end, Children) of
true -> {error, 'not-acceptable'};
false -> ok
end
end.
set_data(LUser, LServer, El) ->
case El of
{xmlelement, _Name, Attrs, _Els} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
case XMLNS of
"" ->
ignore;
_ ->
mnesia:write(
#private_storage{usns = {LUser, LServer, XMLNS},
xml = El})
end;
_ ->
ignore
end.
XMLNS = exmpp_xml:get_ns_as_atom(El),
mnesia:write(#private_storage{usns = {LUser, LServer, XMLNS},
xml = El}).
get_data(LUser, LServer, Els) ->
get_data(LUser, LServer, Els, []).
@ -109,45 +153,46 @@ get_data(LUser, LServer, Els) ->
get_data(_LUser, _LServer, [], Res) ->
lists:reverse(Res);
get_data(LUser, LServer, [El | Els], Res) ->
case El of
{xmlelement, _Name, Attrs, _} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
case mnesia:dirty_read(private_storage, {LUser, LServer, XMLNS}) of
[R] ->
get_data(LUser, LServer, Els,
[R#private_storage.xml | Res]);
[] ->
get_data(LUser, LServer, Els,
[El | Res])
end;
_ ->
get_data(LUser, LServer, Els, Res)
XMLNS = exmpp_xml:get_ns_as_atom(El),
case mnesia:dirty_read(private_storage, {LUser, LServer, XMLNS}) of
[R] ->
get_data(LUser, LServer, Els,
[R#private_storage.xml | Res]);
[] ->
get_data(LUser, LServer, Els,
[El | Res])
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
F = fun() ->
Namespaces = mnesia:select(
private_storage,
[{#private_storage{usns={LUser, LServer, '$1'},
_ = '_'},
[],
['$$']}]),
lists:foreach(
fun([Namespace]) ->
mnesia:delete({private_storage,
{LUser, LServer, Namespace}})
end, Namespaces)
end,
mnesia:transaction(F).
remove_user(User, Server)
when is_binary(User), is_binary(Server) ->
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
F = fun() ->
Namespaces = mnesia:select(
private_storage,
[{#private_storage{usns = {LUser, LServer, '$1'},
_ = '_'},
[],
['$$']}]),
lists:foreach(
fun([Namespace]) ->
mnesia:delete({private_storage,
{LUser, LServer, Namespace}})
end, Namespaces)
end,
mnesia:transaction(F)
catch
_ ->
ok
end.
update_table() ->
Fields = record_info(fields, private_storage),
case mnesia:table_info(private_storage, attributes) of
Fields ->
ok;
convert_to_exmpp();
[userns, xml] ->
?INFO_MSG("Converting private_storage table from "
"{user, default, lists} format", []),
@ -163,10 +208,14 @@ update_table() ->
F1 = fun() ->
mnesia:write_lock_table(mod_private_tmp_table),
mnesia:foldl(
fun(#private_storage{usns = {U, NS}} = R, _) ->
fun(#private_storage{usns = {U, NS}, xml = El} = R, _) ->
NS1 = list_to_atom(NS),
El0 = exmpp_xml:xmlelement_to_xmlel(El,
[?NS_PRIVATE], [{?NS_XMPP, ?NS_XMPP_pfx}]),
El1 = exmpp_xml:remove_whitespaces_deeply(El0),
mnesia:dirty_write(
mod_private_tmp_table,
R#private_storage{usns = {U, Host, NS}})
R#private_storage{usns = {U, Host, NS1}, xml = El1})
end, ok, private_storage)
end,
mnesia:transaction(F1),
@ -186,3 +235,30 @@ update_table() ->
end.
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(private_storage) of
'$end_of_table' ->
none;
{U, _S, _NS} when is_binary(U) ->
none;
{U, _S, _NS} when is_list(U) ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, private_storage, write)
end
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#private_storage{usns = {U, S, NS} = Key, xml = El} = R,
Acc) ->
mnesia:delete({private_storage, Key}),
U1 = list_to_binary(U),
S1 = list_to_binary(S),
NS1 = list_to_atom(NS),
El1 = exmpp_xml:xmlelement_to_xmlel(El,
[?NS_PRIVATE], [{?NS_XMPP, ?NS_XMPP_pfx}]),
New_R = R#private_storage{
usns = {U1, S1, NS1},
xml = El1},
mnesia:write(New_R),
Acc.

View File

@ -34,103 +34,148 @@
process_sm_iq/3,
remove_user/2]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
start(Host, Opts) ->
HostB = list_to_binary(Host),
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
ejabberd_hooks:add(remove_user, Host,
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE,
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVATE,
?MODULE, process_sm_iq, IQDisc).
stop(Host) ->
ejabberd_hooks:delete(remove_user, Host,
HostB = list_to_binary(Host),
ejabberd_hooks:delete(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PRIVATE).
gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_PRIVATE).
process_sm_iq(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
#jid{luser = LUser, lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true ->
{xmlelement, Name, Attrs, Els} = SubEl,
process_sm_iq(From, To, #iq{type = Type} = IQ_Rec) ->
case check_packet(From, To, IQ_Rec) of
ok ->
case Type of
set ->
F = fun() ->
lists:foreach(
fun(El) ->
set_data(LUser, LServer, El)
end, Els)
end,
odbc_queries:sql_transaction(LServer, F),
IQ#iq{type = result,
sub_el = [{xmlelement, Name, Attrs, []}]};
process_iq_set(From, To, IQ_Rec);
get ->
case catch get_data(LUser, LServer, Els) of
{'EXIT', _Reason} ->
IQ#iq{type = error,
sub_el = [SubEl,
?ERR_INTERNAL_SERVER_ERROR]};
Res ->
IQ#iq{type = result,
sub_el = [{xmlelement, Name, Attrs, Res}]}
end
process_iq_get(From, To, IQ_Rec)
end;
false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
{error, Error} ->
exmpp_iq:error(IQ_Rec, Error)
end.
set_data(LUser, LServer, El) ->
case El of
{xmlelement, _Name, Attrs, _Els} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
case XMLNS of
"" ->
ignore;
_ ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData = ejabberd_odbc:escape(
lists:flatten(xml:element_to_string(El))),
odbc_queries:set_private_data(LServer, Username, LXMLNS, SData)
end;
_ ->
ignore
process_iq_get(From, _To, #iq{payload = SubEl} = IQ_Rec) ->
LUser = exmpp_jid:prep_node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
case catch get_data(LUser,
LServer,
exmpp_xml:get_child_elements(SubEl)) of
{'EXIT', _Reason} ->
{error, 'internal-server-error'};
Res ->
exmpp_iq:result(IQ_Rec, #xmlel{ns = ?NS_PRIVATE,
name = 'query',
children = Res})
end.
process_iq_set(From, _To, #iq{payload = SubEl} = IQ_Rec) ->
LUser = exmpp_jid:prep_node_as_list(From),
LServer = exmpp_jid:prep_domain_as_list(From),
F = fun() ->
lists:foreach(
fun(El) ->
set_data(LUser, LServer, El)
end, exmpp_xml:get_child_elements(SubEl))
end,
odbc_queries:sql_transaction(LServer, F),
exmpp_iq:result(IQ_Rec).
check_packet(From, To, IQ_Rec) ->
check_packet(From, To, IQ_Rec, [ fun check_domain/3,
fun check_user/3,
fun check_ns/3]).
check_packet(_From, _To, _IQ_Rec, []) ->
ok;
check_packet(From, To, IQ_Rec, [F | R]) ->
case F(From, To, IQ_Rec) of
{error, _} = Error -> Error;
ok -> check_packet(From, To, IQ_Rec, R)
end.
check_domain(From, _To, _IQ_Rec) ->
LServer = exmpp_jid:prep_domain_as_list(From),
case lists:member(LServer, ?MYHOSTS) of
true -> ok;
false -> {error, 'not-allowed'}
end.
% the iq can't be directed to another jid
check_user(From, To, _IQ_Rec) ->
case exmpp_jid:bare_compare(From, To) of
true -> ok;
false -> {error, 'forbidden'}
end.
%there must be at least one child, and every child should have
%a namespace specified (reject if the namespace is jabber:iq:private,
%the same than the parent element).
check_ns(_From, _To, #iq{payload = SubEl}) ->
case exmpp_xml:get_child_elements(SubEl) of
[] ->
{error, 'not-acceptable'};
Children ->
case lists:any(fun(Child) ->
exmpp_xml:get_ns_as_atom(Child) =:= ?NS_PRIVATE
end, Children) of
true -> {error, 'not-acceptable'};
false -> ok
end
end.
set_data(LUser, LServer, El) ->
XMLNS = exmpp_xml:get_ns_as_list(El),
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData = ejabberd_odbc:escape(exmpp_xml:document_to_list(El)),
odbc_queries:set_private_data(LServer, Username, LXMLNS, SData).
get_data(LUser, LServer, Els) ->
get_data(LUser, LServer, Els, []).
get_data(_LUser, _LServer, [], Res) ->
lists:reverse(Res);
get_data(LUser, LServer, [El | Els], Res) ->
case El of
{xmlelement, _Name, Attrs, _} ->
XMLNS = xml:get_attr_s("xmlns", Attrs),
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of
{selected, ["data"], [{SData}]} ->
case xml_stream:parse_element(SData) of
Data when element(1, Data) == xmlelement ->
get_data(LUser, LServer, Els,
[Data | Res])
end;
%% MREMOND: I wonder when the query could return a vcard ?
{selected, ["vcard"], []} ->
get_data(LUser, LServer, Els,
[El | Res]);
_ ->
get_data(LUser, LServer, Els,[El | Res])
end;
_ ->
get_data(LUser, LServer, Els, Res)
end.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
XMLNS = exmpp_xml:get_ns_as_list(El),
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_user_private_storage(LServer, Username).
LXMLNS = ejabberd_odbc:escape(XMLNS),
case catch odbc_queries:get_private_data(LServer, Username, LXMLNS) of
{selected, ["data"], [{SData}]} ->
[Data] = exmpp_xml:parse_document(SData,
[names_as_atom, {check_elems, xmpp},
{check_nss,xmpp}, {check_attrs,xmpp}]),
get_data(LUser, LServer, Els, [Data | Res]);
%% MREMOND: I wonder when the query could return a vcard ?
{selected, ["vcard"], []} ->
get_data(LUser, LServer, Els,
[El | Res]);
_ ->
get_data(LUser, LServer, Els, [El | Res])
end.
remove_user(User, Server) when is_binary(User), is_binary(Server) ->
try
LUser = binary_to_list(exmpp_stringprep:nodeprep(User)),
LServer = binary_to_list(exmpp_stringprep:nameprep(Server)),
Username = ejabberd_odbc:escape(LUser),
odbc_queries:del_user_private_storage(LServer, Username)
catch
_ ->
ok
end.

View File

@ -41,8 +41,9 @@
%% API.
-export([start_link/2, add_listener/2, delete_listener/1]).
-include_lib("exmpp/include/exmpp.hrl").
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_proxy65_service).
@ -74,14 +75,15 @@ terminate(_Reason, #state{myhost=MyHost}) ->
ejabberd_router:unregister_route(MyHost),
ok.
handle_info({route, From, To, {xmlelement, "iq", _, _} = Packet}, State) ->
IQ = jlib:iq_query_info(Packet),
case catch process_iq(From, IQ, State) of
Result when is_record(Result, iq) ->
ejabberd_router:route(To, From, jlib:iq_to_xml(Result));
handle_info({route, From, To, Packet}, State) when ?IS_IQ(Packet) ->
IQ_Rec = exmpp_iq:xmlel_to_iq(Packet),
case catch process_iq(From, IQ_Rec, State) of
Result when ?IS_IQ_RECORD(Result) ->
ejabberd_router:route(To, From,
exmpp_iq:iq_to_xmlel(Result, To, From));
{'EXIT', Reason} ->
?ERROR_MSG("Error when processing IQ stanza: ~p", [Reason]),
Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
Err = exmpp_iq:error(Packet, 'internal-server-error'),
ejabberd_router:route(To, From, Err);
_ ->
ok
@ -120,71 +122,76 @@ delete_listener(Host) ->
%%%------------------------
%% disco#info request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ,
#state{name=Name, serverhost=ServerHost}) ->
process_iq(_, #iq{type = get, ns = ?NS_DISCO_INFO, lang = Lang} = IQ_Rec,
#state{name=Name, serverhost = ServerHost}) ->
ServerHostB = list_to_binary(ServerHost),
Info = ejabberd_hooks:run_fold(
disco_info, ServerHost, [], [ServerHost, ?MODULE, "", ""]),
IQ#iq{type = result, sub_el =
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}],
iq_disco_info(Lang, Name) ++ Info}]};
disco_info, ServerHostB, [], [ServerHost, ?MODULE, <<>>, ""]),
Result = #xmlel{ns = ?NS_DISCO_INFO, name = 'query',
children = iq_disco_info(Lang, Name)++Info},
exmpp_iq:result(IQ_Rec, Result);
%% disco#items request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
IQ#iq{type = result, sub_el =
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
process_iq(_, #iq{type = get, ns = ?NS_DISCO_ITEMS} = IQ_Rec, _) ->
Result = #xmlel{ns = ?NS_DISCO_ITEMS, name = 'query',
children = []},
exmpp_iq:result(IQ_Rec, Result);
%% vCard request
process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) ->
IQ#iq{type = result, sub_el =
[{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}], iq_vcard(Lang)}]};
process_iq(_, #iq{type = get, ns = ?NS_VCARD, lang = Lang} = IQ_Rec, _) ->
Result = #xmlel{ns = ?NS_VCARD, name = 'vCard',
children = iq_vcard(Lang)},
exmpp_iq:result(IQ_Rec, Result);
%% bytestreams info request
process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
process_iq(JID, #iq{type = get, ns = ?NS_BYTESTREAMS} = IQ_Rec,
#state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, JID) of
allow ->
StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
IQ#iq{type = result, sub_el =
[{xmlelement, "query", [{"xmlns", ?NS_BYTESTREAMS}], StreamHostEl}]};
StreamHostEl = [#xmlel{ns = ?NS_BYTESTREAMS, name = 'streamhost',
attrs = StreamAddr}],
Result = #xmlel{ns = ?NS_BYTESTREAMS, name = 'query',
children = StreamHostEl},
exmpp_iq:result(IQ_Rec, Result);
deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
exmpp_iq:error(IQ_Rec, 'forbidden')
end;
%% bytestream activation request
process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
process_iq(InitiatorJID, #iq{type = set, payload = SubEl, ns = ?NS_BYTESTREAMS} = IQ_Rec,
#state{acl = ACL, serverhost = ServerHost}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of
allow ->
ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
SID = xml:get_tag_attr_s("sid", SubEl),
case catch jlib:string_to_jid(xml:get_tag_cdata(ActivateEl)) of
TargetJID when is_record(TargetJID, jid), SID /= "",
ActivateEl = exmpp_xml:get_path(SubEl, [{element, 'activate'}]),
SID = exmpp_xml:get_attribute_as_list(SubEl, 'sid', ""),
case catch exmpp_jid:parse(exmpp_xml:get_cdata_as_string(ActivateEl)) of
TargetJID when ?IS_JID(TargetJID), SID /= "",
length(SID) =< 128, TargetJID /= InitiatorJID ->
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
Target = exmpp_jid:prep_to_list(TargetJID),
Initiator = exmpp_jid:prep_to_list(InitiatorJID),
SHA1 = sha:sha(SID ++ Initiator ++ Target),
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of
ok ->
IQ#iq{type = result, sub_el = []};
exmpp_iq:result(IQ_Rec);
false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
exmpp_iq:error(IQ_Rec, 'item-not-found');
limit ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_RESOURCE_CONSTRAINT]};
exmpp_iq:error(IQ_Rec, 'resource-constraint');
conflict ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_CONFLICT]};
exmpp_iq:error(IQ_Rec, 'conflict');
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
exmpp_iq:error(IQ_Rec, 'internal-server-error')
end;
_ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
exmpp_iq:error(IQ_Rec, 'bad-request')
end;
deny ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
exmpp_iq:error(IQ_Rec, 'forbidden')
end;
%% Unknown "set" or "get" request
process_iq(_, #iq{type=Type, sub_el=SubEl} = IQ, _) when Type==get; Type==set ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
process_iq(_, #iq{kind=request} = IQ_Rec, _) ->
exmpp_iq:error(IQ_Rec, 'service-unavailable');
%% IQ "result" or "error".
process_iq(_, _, _) ->
@ -193,25 +200,26 @@ process_iq(_, _, _) ->
%%%-------------------------
%%% Auxiliary functions.
%%%-------------------------
-define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
-define(FEATURE(Feat), #xmlel{ns = ?NS_DISCO_INFO, name = 'feature',
attrs = [?XMLATTR('var', Feat)]}).
iq_disco_info(Lang, Name) ->
[{xmlelement, "identity",
[{"category", "proxy"},
{"type", "bytestreams"},
{"name", translate:translate(Lang, Name)}], []},
?FEATURE(?NS_DISCO_INFO),
?FEATURE(?NS_VCARD),
?FEATURE(?NS_BYTESTREAMS)].
[#xmlel{ns = ?NS_DISCO_INFO, name = 'identity', attrs =
[?XMLATTR('category', <<"proxy">>),
?XMLATTR('type', <<"bytestreams">>),
?XMLATTR('name', translate:translate(Lang, Name))]},
?FEATURE(?NS_DISCO_INFO_s),
?FEATURE(?NS_VCARD_s),
?FEATURE(?NS_BYTESTREAMS_s)].
iq_vcard(Lang) ->
[{xmlelement, "FN", [],
[{xmlcdata, "ejabberd/mod_proxy65"}]},
{xmlelement, "URL", [],
[{xmlcdata, ?EJABBERD_URI}]},
{xmlelement, "DESC", [],
[{xmlcdata, translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++
"\nCopyright (c) 2003-2009 Alexey Shchepin"}]}].
[#xmlel{ns = ?NS_VCARD, name = 'FN', children =
[#xmlcdata{cdata = <<"ejabberd/mod_proxy65">>}]},
#xmlel{ns = ?NS_VCARD, name = 'URL', children =
[#xmlcdata{cdata = list_to_binary(?EJABBERD_URI)}]},
#xmlel{ns = ?NS_VCARD, name = 'DESC', children =
[#xmlcdata{cdata = list_to_binary(translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++
"\nCopyright (c) 2003-2009 Alexey Shchepin")}]}].
parse_options(ServerHost, Opts) ->
MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"),
@ -223,7 +231,7 @@ parse_options(ServerHost, Opts) ->
Addr -> Addr
end,
StrIP = inet_parse:ntoa(IP),
StreamAddr = [{"jid", MyHost}, {"host", StrIP}, {"port", integer_to_list(Port)}],
StreamAddr = [?XMLATTR('jid', MyHost), ?XMLATTR('host', StrIP), ?XMLATTR('port', Port)],
#state{myhost = MyHost,
serverhost = ServerHost,
name = Name,

View File

@ -123,8 +123,8 @@ activate({P1, J1}, {P2, J2}) ->
{S1, S2} when is_port(S1), is_port(S2) ->
P1 ! {activate, P2, S2, J1, J2},
P2 ! {activate, P1, S1, J1, J2},
JID1 = jlib:jid_to_string(J1),
JID2 = jlib:jid_to_string(J2),
JID1 = exmpp_jid:to_list(J1),
JID2 = exmpp_jid:to_list(J2),
?INFO_MSG("(~w:~w) Activated bytestream for ~s -> ~s", [P1, P2, JID1, JID2]),
ok;
_ ->

View File

@ -47,8 +47,6 @@ behaviour_info(callbacks) ->
{get_node, 1},
{get_nodes, 2},
{get_nodes, 1},
{get_parentnodes, 3},
{get_parentnodes_tree, 3},
{get_subnodes, 3},
{get_subnodes_tree, 3},
{create_node, 6},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@
-author(__TO_BE_DEFINED__).
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@ -45,7 +44,7 @@
create_node/2,
delete_node/1,
purge_node/2,
subscribe_node/8,
subscribe_node/7,
unsubscribe_node/4,
publish_item/6,
delete_item/4,
@ -56,9 +55,8 @@
set_affiliation/3,
get_entity_subscriptions/2,
get_node_subscriptions/1,
get_subscriptions/2,
set_subscriptions/4,
get_pending_nodes/2,
get_subscription/2,
set_subscription/3,
get_states/1,
get_state/2,
set_state/1,
@ -66,8 +64,7 @@
get_items/2,
get_item/7,
get_item/2,
set_item/1,
get_item_name/3
set_item/1
]).
@ -78,7 +75,8 @@ terminate(Host, ServerHost) ->
node_hometree:terminate(Host, ServerHost).
options() ->
[{deliver_payloads, true},
[{node_type, __TO_BE_DEFINED__},
{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@ -97,7 +95,7 @@ options() ->
features() ->
["create-nodes",
"delete-nodes",
"delete-items",
"delete-any",
"instant-nodes",
"outcast-affiliation",
"persistent-items",
@ -120,8 +118,8 @@ create_node(NodeId, Owner) ->
delete_node(Removed) ->
node_hometree:delete_node(Removed).
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options) ->
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup, Options).
subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup) ->
node_hometree:subscribe_node(NodeId, Sender, Subscriber, AccessModel, SendLast, PresenceSubscription, RosterGroup).
unsubscribe_node(NodeId, Sender, Subscriber, SubID) ->
node_hometree:unsubscribe_node(NodeId, Sender, Subscriber, SubID).
@ -156,14 +154,11 @@ get_entity_subscriptions(Host, Owner) ->
get_node_subscriptions(NodeId) ->
node_hometree:get_node_subscriptions(NodeId).
get_subscriptions(NodeId, Owner) ->
node_hometree:get_subscriptions(NodeId, Owner).
get_subscription(NodeId, Owner) ->
node_hometree:get_subscription(NodeId, Owner).
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
node_hometree:set_subscriptions(NodeId, Owner, Subscription, SubId).
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
set_subscription(NodeId, Owner, Subscription) ->
node_hometree:set_subscription(NodeId, Owner, Subscription).
get_states(NodeId) ->
node_hometree:get_states(NodeId).
@ -177,7 +172,7 @@ set_state(State) ->
get_items(NodeId, From) ->
node_hometree:get_items(NodeId, From).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId)
node_hometree:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
@ -188,6 +183,3 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, Su
set_item(Item) ->
node_hometree:set_item(Item).
get_item_name(Host, Node, Id) ->
node_hometree:get_item_name(Host, Node, Id).

View File

@ -26,8 +26,9 @@
-module(node_buddy).
-author('christophe.romain@process-one.net').
-include_lib("exmpp/include/exmpp.hrl").
-include("pubsub.hrl").
-include("jlib.hrl").
-behaviour(gen_pubsub_node).
@ -69,8 +70,8 @@
get_item/2,
set_item/1,
get_item_name/3,
node_to_path/1,
path_to_node/1
node_to_path/1,
path_to_node/1
]).
@ -169,6 +170,7 @@ set_subscriptions(NodeId, Owner, Subscription, SubId) ->
get_pending_nodes(Host, Owner) ->
node_hometree:get_pending_nodes(Host, Owner).
get_states(NodeId) ->
node_hometree:get_states(NodeId).
@ -201,4 +203,3 @@ node_to_path(Node) ->
path_to_node(Path) ->
node_flat:path_to_node(Path).

View File

@ -27,7 +27,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -69,8 +69,8 @@
get_item/2,
set_item/1,
get_item_name/3,
node_to_path/1,
path_to_node/1
node_to_path/1,
path_to_node/1
]).
@ -200,4 +200,3 @@ node_to_path(Node) ->
path_to_node(Path) ->
node_flat:path_to_node(Path).

View File

@ -53,8 +53,8 @@
get_item/2,
set_item/1,
get_item_name/3,
node_to_path/1,
path_to_node/1]).
node_to_path/1,
path_to_node/1]).
init(Host, ServerHost, Opts) ->
@ -96,7 +96,7 @@ publish_item(NodeID, Publisher, Model, MaxItems, ItemID, Payload) ->
#pubsub_node{options = Options} ->
case find_opt(node_type, Options) of
collection ->
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "publish")};
{error, mod_pubsub:extended_error('not-allowed', "publish")};
_ ->
node_hometree:publish_item(NodeID, Publisher, Model,
MaxItems, ItemID, Payload)
@ -181,4 +181,3 @@ node_to_path(Node) ->
path_to_node(Path) ->
node_hometree:path_to_node(Path).

View File

@ -27,7 +27,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -67,8 +67,8 @@
get_item/2,
set_item/1,
get_item_name/3,
node_to_path/1,
path_to_node/1
node_to_path/1,
path_to_node/1
]).
@ -122,10 +122,10 @@ delete_node(Removed) ->
subscribe_node(_NodeId, _Sender, _Subscriber, _AccessModel,
_SendLast, _PresenceSubscription, _RosterGroup, _Options) ->
{error, ?ERR_FORBIDDEN}.
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
unsubscribe_node(_NodeId, _Sender, _Subscriber, _SubID) ->
{error, ?ERR_FORBIDDEN}.
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
publish_item(NodeId, Publisher, Model, MaxItems, ItemId, Payload) ->
lists:foreach(fun(SubNode) ->
@ -138,10 +138,10 @@ remove_extra_items(_NodeId, _MaxItems, ItemIds) ->
{result, {ItemIds, []}}.
delete_item(_NodeId, _Publisher, _PublishModel, _ItemId) ->
{error, ?ERR_ITEM_NOT_FOUND}.
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'item-not-found')}.
purge_node(_NodeId, _Owner) ->
{error, ?ERR_FORBIDDEN}.
{error, exmpp_stanza:error(?NS_JABBER_CLIENT, 'forbidden')}.
get_entity_affiliations(_Host, _Owner) ->
{result, []}.
@ -204,4 +204,3 @@ node_to_path(Node) ->
path_to_node(Path) ->
node_flat:path_to_node(Path).

View File

@ -26,7 +26,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -60,8 +60,8 @@
get_item/2,
set_item/1,
get_item_name/3,
node_to_path/1,
path_to_node/1
node_to_path/1,
path_to_node/1
]).
@ -95,12 +95,13 @@ features() ->
%% the home/localhost/user/... hierarchy
%% any node is allowed
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
LOwner = jlib:short_prepd_jid(Owner),
Allowed = case LOwner of
{"", Host, ""} ->
{undefined, Host, undefined} ->
true; % pubsub service always allowed
_ ->
acl:match_rule(ServerHost, Access, LOwner) =:= allow
{LU, LS, LR} = LOwner,
acl:match_rule(ServerHost, Access, exmpp_jid:make(LU, LS, LR)) =:= allow
end,
{result, Allowed}.

View File

@ -26,7 +26,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -47,7 +47,6 @@
get_affiliation/2,
set_affiliation/3,
get_entity_subscriptions/2,
get_entity_subscriptions_for_send_last/2,
get_node_subscriptions/1,
get_subscriptions/2,
set_subscriptions/4,
@ -55,17 +54,15 @@
get_states/1,
get_state/2,
set_state/1,
get_items/7,
get_items/6,
get_items/3,
get_items/2,
get_item/7,
get_item/2,
set_item/1,
get_item_name/3,
get_last_items/3,
node_to_path/1,
path_to_node/1
node_to_path/1,
path_to_node/1
]).
@ -76,8 +73,7 @@ terminate(Host, ServerHost) ->
node_hometree_odbc:terminate(Host, ServerHost).
options() ->
[{node_type, flat},
{deliver_payloads, true},
[{deliver_payloads, true},
{notify_config, false},
{notify_delete, false},
{notify_retract, true},
@ -102,12 +98,13 @@ features() ->
%% the home/localhost/user/... hierarchy
%% any node is allowed
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
LOwner = jlib:short_prepd_jid(Owner),
Allowed = case LOwner of
{"", Host, ""} ->
{undefined, Host, undefined} ->
true; % pubsub service always allowed
_ ->
acl:match_rule(ServerHost, Access, LOwner) =:= allow
{LU, LS, LR} = LOwner,
acl:match_rule(ServerHost, Access, exmpp_jid:make(LU, LS, LR)) =:= allow
end,
{result, Allowed}.
@ -150,9 +147,6 @@ set_affiliation(NodeId, Owner, Affiliation) ->
get_entity_subscriptions(Host, Owner) ->
node_hometree_odbc:get_entity_subscriptions(Host, Owner).
get_entity_subscriptions_for_send_last(Host, Owner) ->
node_hometree_odbc:get_entity_subscriptions_for_send_last(Host, Owner).
get_node_subscriptions(NodeId) ->
node_hometree_odbc:get_node_subscriptions(NodeId).
@ -176,12 +170,9 @@ set_state(State) ->
get_items(NodeId, From) ->
node_hometree_odbc:get_items(NodeId, From).
get_items(NodeId, From, RSM) ->
node_hometree_odbc:get_items(NodeId, From, RSM).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId) ->
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, none).
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM) ->
node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId, RSM).
node_hometree_odbc:get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, SubId).
get_item(NodeId, ItemId) ->
node_hometree_odbc:get_item(NodeId, ItemId).
@ -209,5 +200,4 @@ path_to_node(Path) ->
[Node|_] when is_list(Node) -> list_to_binary(string:join([""|Path], "/"));
% default case (used by PEP for example)
_ -> list_to_binary(Path)
end.
end.

View File

@ -42,7 +42,7 @@
-author('christophe.romain@process-one.net').
-include("pubsub.hrl").
-include("jlib.hrl").
-include_lib("exmpp/include/exmpp.hrl").
-behaviour(gen_pubsub_node).
@ -76,8 +76,8 @@
get_item/2,
set_item/1,
get_item_name/3,
node_to_path/1,
path_to_node/1
node_to_path/1,
path_to_node/1
]).
%% ================
@ -197,13 +197,14 @@ features() ->
%% ```check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access) ->
%% node_default:check_create_user_permission(Host, ServerHost, Node, ParentNode, Owner, Access).'''</p>
create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
LOwner = jlib:jid_tolower(Owner),
LOwner = jlib:short_prepd_jid(Owner),
{User, Server, _Resource} = LOwner,
Allowed = case LOwner of
{"", Host, ""} ->
{undefined, Host, undefined} ->
true; % pubsub service always allowed
_ ->
case acl:match_rule(ServerHost, Access, LOwner) of
{LU, LS, LR} = LOwner,
case acl:match_rule(ServerHost, Access, exmpp_jid:make(LU, LS, LR)) of
allow ->
case node_to_path(Node) of
["home", Server, User | _] -> true;
@ -221,7 +222,7 @@ create_node_permission(Host, ServerHost, Node, _ParentNode, Owner, Access) ->
%% Owner = mod_pubsub:jid()
%% @doc <p></p>
create_node(NodeId, Owner) ->
OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
OwnerKey = jlib:short_prepd_bare_jid(Owner),
set_state(#pubsub_state{stateid = {OwnerKey, NodeId}, affiliation = owner}),
{result, {default, broadcast}}.
@ -281,9 +282,9 @@ delete_node(Removed) ->
%% <p>In the default plugin module, the record is unchanged.</p>
subscribe_node(NodeId, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
SubKey = jlib:short_prepd_jid(Subscriber),
GenKey = jlib:short_prepd_bare_jid(SubKey),
Authorized = (jlib:short_prepd_bare_jid(Sender) == GenKey),
GenState = get_state(NodeId, GenKey),
SubState = case SubKey of
GenKey -> GenState;
@ -298,31 +299,31 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
if
not Authorized ->
%% JIDs do not match
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "invalid-jid")};
{error, ?ERR_EXTENDED('bad-request', "invalid-jid")};
Affiliation == outcast ->
%% Requesting entity is blocked
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
PendingSubscription ->
%% Requesting entity has pending subscription
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "pending-subscription")};
{error, ?ERR_EXTENDED('not-authorized', "pending-subscription")};
(AccessModel == presence) and (not PresenceSubscription) ->
%% Entity is not authorized to create a subscription (presence subscription required)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
{error, ?ERR_EXTENDED('not-authorized', "presence-subscription-required")};
(AccessModel == roster) and (not RosterGroup) ->
%% Entity is not authorized to create a subscription (not in roster group)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
{error, ?ERR_EXTENDED('not-authorized', "not-in-roster-group")};
(AccessModel == whitelist) and (not Whitelisted) ->
%% Node has whitelist access model and entity lacks required affiliation
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
{error, ?ERR_EXTENDED('not-allowed', "closed-node")};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
%%ForbiddenAnonymous ->
%% % Requesting entity is anonymous
%% {error, ?ERR_FORBIDDEN};
%% {error, 'forbidden'};
true ->
case pubsub_subscription:add_subscription(Subscriber, NodeId, Options) of
SubId when is_list(SubId) ->
case pubsub_subscription:subscribe_node(Subscriber, NodeId, Options) of
{result, SubId} ->
NewSub = case AccessModel of
authorize -> pending;
_ -> subscribed
@ -337,7 +338,7 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
{result, {default, pending, SubId}}
end;
_ ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
{error, 'internal-server-error'}
end
end.
@ -350,9 +351,9 @@ subscribe_node(NodeId, Sender, Subscriber, AccessModel,
%% Reason = mod_pubsub:stanzaError()
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
SubKey = jlib:jid_tolower(Subscriber),
GenKey = jlib:jid_remove_resource(SubKey),
Authorized = (jlib:jid_tolower(jlib:jid_remove_resource(Sender)) == GenKey),
SubKey = jlib:short_prepd_jid(Subscriber),
GenKey = jlib:short_prepd_bare_jid(SubKey),
Authorized = (jlib:short_prepd_bare_jid(Sender) == GenKey),
GenState = get_state(NodeId, GenKey),
SubState = case SubKey of
GenKey -> GenState;
@ -369,16 +370,16 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
if
%% Requesting entity is prohibited from unsubscribing entity
not Authorized ->
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
%% Entity did not specify SubId
%%SubId == "", ?? ->
%% {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%% {error, ?ERR_EXTENDED('bad-request', "subid-required")};
%% Invalid subscription identifier
%%InvalidSubId ->
%% {error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
%% {error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")};
%% Requesting entity is not a subscriber
Subscriptions == [] ->
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST, "not-subscribed")};
{error, ?ERR_EXTENDED('unexpected-request', "not-subscribed")};
%% Subid supplied, so use that.
SubIdExists ->
Sub = first_in_list(fun(S) ->
@ -392,7 +393,7 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
delete_subscription(SubKey, NodeId, S, SubState),
{result, default};
false ->
{error, ?ERR_EXTENDED(?ERR_UNEXPECTED_REQUEST,
{error, ?ERR_EXTENDED('unexpected-request',
"not-subscribed")}
end;
%% No subid supplied, but there's only one matching
@ -400,16 +401,15 @@ unsubscribe_node(NodeId, Sender, Subscriber, SubId) ->
length(Subscriptions) == 1 ->
delete_subscription(SubKey, NodeId, hd(Subscriptions), SubState),
{result, default};
%% No subid and more than one possible subscription match.
true ->
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")}
{error, ?ERR_EXTENDED('bad-request', "subid-required")}
end.
delete_subscription(SubKey, NodeID, {Subscription, SubId}, SubState) ->
Affiliation = SubState#pubsub_state.affiliation,
AllSubs = SubState#pubsub_state.subscriptions,
NewSubs = AllSubs -- [{Subscription, SubId}],
pubsub_subscription:delete_subscription(SubKey, NodeID, SubId),
pubsub_subscription:unsubscribe_node(SubKey, NodeID, SubId),
case {Affiliation, NewSubs} of
{none, []} ->
% Just a regular subscriber, and this is final item, so
@ -459,8 +459,8 @@ delete_subscription(SubKey, NodeID, {Subscription, SubId}, SubState) ->
%% </p>
%% <p>In the default plugin module, the record is unchanged.</p>
publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
SubKey = jlib:short_prepd_jid(Publisher),
GenKey = jlib:short_prepd_bare_jid(Publisher),
GenState = get_state(NodeId, GenKey),
SubState = case SubKey of
GenKey -> GenState;
@ -477,7 +477,7 @@ publish_item(NodeId, Publisher, PublishModel, MaxItems, ItemId, Payload) ->
and ((Affiliation == owner) or (Affiliation == publisher)))
or (Subscribed == true)) ->
%% Entity does not have sufficient privileges to publish to node
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
true ->
%% TODO: check creation, presence, roster
if MaxItems > 0 ->
@ -539,8 +539,7 @@ remove_extra_items(NodeId, MaxItems, ItemIds) ->
%% <p>Default plugin: The user performing the deletion must be the node owner
%% or a publisher, or PublishModel being open.</p>
delete_item(NodeId, Publisher, PublishModel, ItemId) ->
SubKey = jlib:jid_tolower(Publisher),
GenKey = jlib:jid_remove_resource(SubKey),
GenKey = jlib:short_prepd_bare_jid(Publisher),
GenState = get_state(NodeId, GenKey),
#pubsub_state{affiliation = Affiliation, items = Items} = GenState,
Allowed = (Affiliation == publisher) orelse (Affiliation == owner)
@ -552,7 +551,7 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
if
not Allowed ->
%% Requesting entity does not have sufficient privileges
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
true ->
case lists:member(ItemId, Items) of
true ->
@ -576,10 +575,10 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
end;
(_, Res) ->
Res
end, {error, ?ERR_ITEM_NOT_FOUND}, States);
end, {error, 'item-not-found'}, States);
_ ->
%% Non-existent node or item
{error, ?ERR_ITEM_NOT_FOUND}
{error, 'item-not-found'}
end
end
end.
@ -590,8 +589,7 @@ delete_item(NodeId, Publisher, PublishModel, ItemId) ->
%% NodeId = mod_pubsub:pubsubNodeId()
%% Owner = mod_pubsub:jid()
purge_node(NodeId, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenKey = jlib:short_prepd_bare_jid(Owner),
GenState = get_state(NodeId, GenKey),
case GenState of
#pubsub_state{affiliation = owner} ->
@ -606,7 +604,7 @@ purge_node(NodeId, Owner) ->
{result, {default, broadcast}};
_ ->
%% Entity is not owner
{error, ?ERR_FORBIDDEN}
{error, 'forbidden'}
end.
%% @spec (Host, JID) -> [{Node,Affiliation}]
@ -620,8 +618,7 @@ purge_node(NodeId, Owner) ->
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_affiliations(Host, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenKey = jlib:short_prepd_bare_jid(Owner),
States = mnesia:match_object(#pubsub_state{stateid = {GenKey, '_'}, _ = '_'}),
NodeTree = case catch ets:lookup(gen_mod:get_module_proc(Host, config), nodetree) of
[{nodetree, N}] -> N;
@ -643,14 +640,12 @@ get_node_affiliations(NodeId) ->
{result, lists:map(Tr, States)}.
get_affiliation(NodeId, Owner) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
GenKey = jlib:short_prepd_bare_jid(Owner),
GenState = get_state(NodeId, GenKey),
{result, GenState#pubsub_state.affiliation}.
set_affiliation(NodeId, Owner, Affiliation) ->
SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
set_affiliation(NodeId, Owner, Affiliation) when ?IS_JID(Owner)->
GenKey = jlib:short_prepd_bare_jid(Owner),
GenState = get_state(NodeId, GenKey),
case {Affiliation, GenState#pubsub_state.subscriptions} of
{none, none} ->
@ -670,8 +665,8 @@ set_affiliation(NodeId, Owner, Affiliation) ->
%% that will be added to the affiliation stored in the main
%% <tt>pubsub_state</tt> table.</p>
get_entity_subscriptions(Host, Owner) ->
{U, D, _} = SubKey = jlib:jid_tolower(Owner),
GenKey = jlib:jid_remove_resource(SubKey),
{U, D, _} = SubKey = jlib:short_prepd_jid(Owner),
GenKey = jlib:short_prepd_bare_jid(Owner),
States = case SubKey of
GenKey -> mnesia:match_object(
#pubsub_state{stateid = {{U, D, '_'}, '_'}, _ = '_'});
@ -717,17 +712,17 @@ get_node_subscriptions(NodeId) ->
{result, lists:flatmap(Tr, States)}.
get_subscriptions(NodeId, Owner) ->
SubKey = jlib:jid_tolower(Owner),
SubKey = jlib:short_prepd_jid(Owner),
SubState = get_state(NodeId, SubKey),
{result, SubState#pubsub_state.subscriptions}.
set_subscriptions(NodeId, Owner, Subscription, SubId) ->
SubKey = jlib:jid_tolower(Owner),
SubKey = jlib:short_prepd_jid(Owner),
SubState = get_state(NodeId, SubKey),
case {SubId, SubState#pubsub_state.subscriptions} of
{_, []} ->
case Subscription of
none -> {error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "not-subscribed")};
none -> {error, ?ERR_EXTENDED('bad_request', "not-subscribed")};
_ -> new_subscription(NodeId, Owner, Subscription, SubState)
end;
{"", [{_, SID}]} ->
@ -736,7 +731,7 @@ set_subscriptions(NodeId, Owner, Subscription, SubId) ->
_ -> replace_subscription({Subscription, SID}, SubState)
end;
{"", [_|_]} ->
{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
{error, ?ERR_EXTENDED('bad_request', "subid-required")};
_ ->
case Subscription of
none -> unsub_with_subid(NodeId, SubId, SubState);
@ -771,7 +766,6 @@ unsub_with_subid(NodeId, SubId, SubState) ->
_ ->
set_state(SubState#pubsub_state{subscriptions = NewSubs})
end.
%% @spec (Host, Owner) -> {result, [Node]} | {error, Reason}
%% Host = host()
%% Owner = jid()
@ -857,11 +851,10 @@ get_state(NodeId, JID) ->
set_state(State) when is_record(State, pubsub_state) ->
mnesia:write(State);
set_state(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.
{error, 'internal-server-error'}.
%% @spec (NodeId, JID) -> ok | {error, Reason::stanzaError()}
%% NodeId = mod_pubsub:pubsubNodeId()
%% JID = mod_pubsub:jid()
%% @doc <p>Delete a state from database.</p>
del_state(NodeId, JID) ->
mnesia:delete({pubsub_state, {JID, NodeId}}).
@ -883,8 +876,8 @@ get_items(NodeId, _From) ->
Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeId}, _ = '_'}),
{result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}.
get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
SubKey = jlib:short_prepd_jid(JID),
GenKey = jlib:short_prepd_bare_jid(JID),
GenState = get_state(NodeId, GenKey),
SubState = get_state(NodeId, SubKey),
Affiliation = GenState#pubsub_state.affiliation,
@ -893,25 +886,25 @@ get_items(NodeId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -
if
%%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%{error, ?ERR_EXTENDED('bad-request', "subid-required")};
%%InvalidSubId ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
GenState#pubsub_state.affiliation == outcast ->
%{error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")};
Affiliation == outcast ->
%% Requesting entity is blocked
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
(AccessModel == presence) and (not PresenceSubscription) ->
%% Entity is not authorized to create a subscription (presence subscription required)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
{error, ?ERR_EXTENDED('not-authorized', "presence-subscription-required")};
(AccessModel == roster) and (not RosterGroup) ->
%% Entity is not authorized to create a subscription (not in roster group)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
{error, ?ERR_EXTENDED('not-authorized', "not-in-roster-group")};
(AccessModel == whitelist) and (not Whitelisted) ->
%% Node has whitelist access model and entity lacks required affiliation
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
{error, ?ERR_EXTENDED('not-allowed', "closed-node")};
(AccessModel == authorize) and (not Whitelisted) ->
%% Node has authorize access model
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@ -929,11 +922,10 @@ get_item(NodeId, ItemId) ->
[Item] when is_record(Item, pubsub_item) ->
{result, Item};
_ ->
{error, ?ERR_ITEM_NOT_FOUND}
{error, 'item-not-found'}
end.
get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
SubKey = jlib:jid_tolower(JID),
GenKey = jlib:jid_remove_resource(SubKey),
GenKey = jlib:short_prepd_bare_jid(JID),
GenState = get_state(NodeId, GenKey),
Affiliation = GenState#pubsub_state.affiliation,
Subscriptions = GenState#pubsub_state.subscriptions,
@ -941,25 +933,25 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
if
%%SubId == "", ?? ->
%% Entity has multiple subscriptions to the node but does not specify a subscription ID
%{error, ?ERR_EXTENDED(?ERR_BAD_REQUEST, "subid-required")};
%%InvalidSubId ->
%{error, ?ERR_EXTENDED('bad-request', "subid-required")};
%%InvalidSubID ->
%% Entity is subscribed but specifies an invalid subscription ID
%{error, ?ERR_EXTENDED(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
GenState#pubsub_state.affiliation == outcast ->
%{error, ?ERR_EXTENDED('not-acceptable', "invalid-subid")};
Affiliation == outcast ->
%% Requesting entity is blocked
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
(AccessModel == presence) and (not PresenceSubscription) ->
%% Entity is not authorized to create a subscription (presence subscription required)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "presence-subscription-required")};
{error, ?ERR_EXTENDED('not-authorized', "presence-subscription-required")};
(AccessModel == roster) and (not RosterGroup) ->
%% Entity is not authorized to create a subscription (not in roster group)
{error, ?ERR_EXTENDED(?ERR_NOT_AUTHORIZED, "not-in-roster-group")};
{error, ?ERR_EXTENDED('not-authorized', "not-in-roster-group")};
(AccessModel == whitelist) and (not Whitelisted) ->
%% Node has whitelist access model and entity lacks required affiliation
{error, ?ERR_EXTENDED(?ERR_NOT_ALLOWED, "closed-node")};
{error, ?ERR_EXTENDED('not-allowed', "closed-node")};
(AccessModel == authorize) and (not Whitelisted) ->
%% Node has authorize access model
{error, ?ERR_FORBIDDEN};
{error, 'forbidden'};
%%MustPay ->
%% % Payment is required for a subscription
%% {error, ?ERR_PAYMENT_REQUIRED};
@ -973,7 +965,7 @@ get_item(NodeId, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _S
set_item(Item) when is_record(Item, pubsub_item) ->
mnesia:write(Item);
set_item(_) ->
{error, ?ERR_INTERNAL_SERVER_ERROR}.
{error, 'internal-server-error'}.
%% @spec (NodeId, ItemId) -> ok | {error, Reason::stanzaError()}
%% NodeId = mod_pubsub:pubsubNodeId()
@ -993,7 +985,7 @@ get_item_name(_Host, _Node, Id) ->
node_to_path(Node) ->
string:tokens(binary_to_list(Node), "/").
path_to_node([]) ->
<<>>;
path_to_node(Path) ->

Some files were not shown because too many files have changed in this diff Show More