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:
commit
c889491e2f
8
README
8
README
@ -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.
|
||||
- ImageMagick’s 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
|
||||
|
@ -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]).
|
||||
|
||||
|
@ -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
|
||||
|
@ -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-->
|
||||
|
@ -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 -->
|
||||
|
364
doc/guide.html
364
doc/guide.html
@ -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> </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  <TT>mod_echo</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc45">3.3.6  <TT>mod_http_bind</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc46">3.3.7  <TT>mod_http_fileserver</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc47">3.3.8  <TT>mod_irc</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc48">3.3.9  <TT>mod_last</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc49">3.3.10  <TT>mod_muc</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc50">3.3.11  <TT>mod_muc_log</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc51">3.3.12  <TT>mod_offline</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc52">3.3.13  <TT>mod_ping</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc53">3.3.14  <TT>mod_privacy</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc54">3.3.15  <TT>mod_private</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc55">3.3.16  <TT>mod_proxy65</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc56">3.3.17  <TT>mod_pubsub</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc57">3.3.18  <TT>mod_register</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc58">3.3.19  <TT>mod_roster</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc59">3.3.20  <TT>mod_service_log</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc60">3.3.21  <TT>mod_shared_roster</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc61">3.3.22  <TT>mod_stats</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc62">3.3.23  <TT>mod_time</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc63">3.3.24  <TT>mod_vcard</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc64">3.3.25  <TT>mod_vcard_ldap</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc65">3.3.26  <TT>mod_version</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc47">3.3.8  <TT>mod_last</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc48">3.3.9  <TT>mod_muc</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc49">3.3.10  <TT>mod_muc_log</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc50">3.3.11  <TT>mod_offline</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc51">3.3.12  <TT>mod_ping</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc52">3.3.13  <TT>mod_privacy</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc53">3.3.14  <TT>mod_private</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc54">3.3.15  <TT>mod_proxy65</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc55">3.3.16  <TT>mod_pubsub</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc56">3.3.17  <TT>mod_register</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc57">3.3.18  <TT>mod_roster</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc58">3.3.19  <TT>mod_service_log</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc59">3.3.20  <TT>mod_shared_roster</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc60">3.3.21  <TT>mod_stats</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc61">3.3.22  <TT>mod_time</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc62">3.3.23  <TT>mod_vcard</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc63">3.3.24  <TT>mod_vcard_ldap</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc64">3.3.25  <TT>mod_version</TT></A>
|
||||
</LI></UL>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc66">Chapter 4  Managing an <TT>ejabberd</TT> Server</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc65">Chapter 4  Managing an <TT>ejabberd</TT> Server</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc67">4.1  <TT>ejabberdctl</TT></A>
|
||||
<A HREF="#htoc66">4.1  <TT>ejabberdctl</TT></A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc68">4.1.1  ejabberdctl Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc69">4.1.2  Erlang Runtime System</A>
|
||||
<A HREF="#htoc67">4.1.1  ejabberdctl Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc68">4.1.2  Erlang Runtime System</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc70">4.2  <TT>ejabberd</TT> Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc69">4.2  <TT>ejabberd</TT> Commands</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc71">4.2.1  List of ejabberd Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc72">4.2.2  Restrict Execution with AccessCommands</A>
|
||||
<A HREF="#htoc70">4.2.1  List of ejabberd Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc71">4.2.2  Restrict Execution with AccessCommands</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc73">4.3  Web Admin</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc74">4.4  Ad-hoc Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc75">4.5  Change Computer Hostname</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc72">4.3  Web Admin</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc73">4.4  Ad-hoc Commands</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc74">4.5  Change Computer Hostname</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc76">Chapter 5  Securing <TT>ejabberd</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc75">Chapter 5  Securing <TT>ejabberd</TT></A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc77">5.1  Firewall Settings</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc78">5.2  epmd</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc79">5.3  Erlang Cookie</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc80">5.4  Erlang Node Name</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc81">5.5  Securing Sensitive Files</A>
|
||||
<A HREF="#htoc76">5.1  Firewall Settings</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc77">5.2  epmd</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc78">5.3  Erlang Cookie</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc79">5.4  Erlang Node Name</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc80">5.5  Securing Sensitive Files</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc82">Chapter 6  Clustering</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc81">Chapter 6  Clustering</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc83">6.1  How it Works</A>
|
||||
<A HREF="#htoc82">6.1  How it Works</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc84">6.1.1  Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc85">6.1.2  Local Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc86">6.1.3  Session Manager</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc87">6.1.4  s2s Manager</A>
|
||||
<A HREF="#htoc83">6.1.1  Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc84">6.1.2  Local Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc85">6.1.3  Session Manager</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc86">6.1.4  s2s Manager</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc88">6.2  Clustering Setup</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc89">6.3  Service Load-Balancing</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc87">6.2  Clustering Setup</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc88">6.3  Service Load-Balancing</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc90">6.3.1  Components Load-Balancing</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc91">6.3.2  Domain Load-Balancing Algorithm</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc92">6.3.3  Load-Balancing Buckets</A>
|
||||
<A HREF="#htoc89">6.3.1  Components Load-Balancing</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc90">6.3.2  Domain Load-Balancing Algorithm</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc91">6.3.3  Load-Balancing Buckets</A>
|
||||
</LI></UL>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc93">Chapter 7  Debugging</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc92">Chapter 7  Debugging</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc94">7.1  Log Files</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc95">7.2  Debug Console</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc96">7.3  Watchdog Alerts</A>
|
||||
<A HREF="#htoc93">7.1  Log Files</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc94">7.2  Debug Console</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc95">7.3  Watchdog Alerts</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc97">Appendix A  Internationalization and Localization</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc98">Appendix B  Release Notes</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc99">Appendix C  Acknowledgements</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc100">Appendix D  Copyright Information</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc96">Appendix A  Internationalization and Localization</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc97">Appendix B  Release Notes</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc98">Appendix C  Acknowledgements</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc99">Appendix D  Copyright Information</A>
|
||||
</LI></UL><!--TOC chapter Introduction-->
|
||||
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc1">Chapter 1</A>  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’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>  <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 <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 <A HREF="#modregister">3.3.18</A>).
|
||||
</PRE></LI><LI CLASS="li-enumerate">Using a XMPP client and In-Band Registration (see section <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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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 <A HREF="#modoffline">3.3.12</A>) is enabled.
|
||||
(see section <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>  <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 ‘groupchat 1.0’ 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 ‘channel%<TT>irc.example.org</TT>’ in case <TT>irc.example.org</TT> is
|
||||
the IRC server hosting ‘channel’. 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 ‘IDENTIFY password’ to<BR>
|
||||
<TT>nickserver!irc.example.org@irc.jabberserver.org</TT>.
|
||||
</LI><LI CLASS="li-itemize">Entering your password is possible by sending ‘LOGIN nick password’<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 ‘<TT>irc.</TT>’. 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 ‘<TT>irc.</TT>’. 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>  <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>  <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 <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>  <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>  <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>  <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>  <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><a href="/">Home</a></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>  <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>  <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>  <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>  <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>  <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>  <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 <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>  <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>  <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 <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>  <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>  <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>  <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>  <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=’plugin-name’</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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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:
|
||||
</query>
|
||||
</iq>
|
||||
</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>  <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>  <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’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 <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>  <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>  <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>  <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>  <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 <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>  <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>  <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>’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 <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 4</A>  <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>  <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 4</A>  <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>  <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>  <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>  <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’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>  <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>  <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 “Simple Authentication and Security Layer”.
|
||||
</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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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 <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>  <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>  <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>  <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>  <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 5</A>  <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>  <A HREF="#firewall">Firewall Settings</A></H2><!--SEC END --><P> <A NAME="firewall"></A>
|
||||
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc75">Chapter 5</A>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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 6</A>  <A HREF="#clustering">Clustering</A></H1><!--SEC END --><P> <A NAME="clustering"></A>
|
||||
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc81">Chapter 6</A>  <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>  <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>  <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>  <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>  <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’s domains. It uses a global
|
||||
routing table. The domain of the packet’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>  <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>  <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’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>  <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>  <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>  <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>  <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’s
|
||||
source to the domain of the packet’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>  <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>  <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 ‘<CODE>acl</CODE>’
|
||||
and ‘<CODE>access</CODE>’ 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>  <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>  <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>  <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>  <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>  <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>  <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>  <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>  <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 7</A>  <A HREF="#debugging">Debugging</A></H1><!--SEC END --><P> <A NAME="debugging"></A>
|
||||
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc92">Chapter 7</A>  <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>  <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>  <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’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>  <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>  <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>  <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>  <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 A</A>  <A HREF="#i18ni10n">Internationalization and Localization</A></H1><!--SEC END --><P> <A NAME="i18ni10n"></A>
|
||||
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc96">Appendix A</A>  <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 ‘Accept-Language: ru’</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 B</A>  <A HREF="#releasenotes">Release Notes</A></H1><!--SEC END --><P> <A NAME="releasenotes"></A>
|
||||
<H1 CLASS="chapter"><!--SEC ANCHOR --><A NAME="htoc97">Appendix B</A>  <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 C</A>  <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 C</A>  <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 D</A>  <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 D</A>  <A HREF="#copyright">Copyright Information</A></H1><!--SEC END --><P> <A NAME="copyright"></A> </P><P>Ejabberd Installation and Operation Guide.<BR>
|
||||
Copyright © 2003 — 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
|
||||
|
159
doc/guide.tex
159
doc/guide.tex
@ -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}
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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/
|
@ -1,2 +1,2 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.1.0}
|
||||
\newcommand{\version}{3.0.0-alpha}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
118
src/acl.erl
118
src/acl.erl
@ -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
109
src/aclocal.m4
vendored
@ -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,
|
||||
|
@ -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
619
src/configure
vendored
@ -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" ;;
|
||||
|
@ -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
|
||||
|
@ -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().
|
||||
|
153
src/cyrsasl.erl
153
src/cyrsasl.erl
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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(
|
||||
|
@ -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) ->
|
||||
|
@ -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
|
||||
|
@ -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, [
|
||||
%%
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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 -> [];
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 "
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
1392
src/ejabberd_c2s.erl
1392
src/ejabberd_c2s.erl
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
%%====================================================================
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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}}.
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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}]
|
||||
).
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 ->
|
||||
|
@ -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 ->
|
||||
|
@ -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().
|
||||
|
@ -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).
|
||||
|
||||
|
||||
|
@ -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);
|
||||
_ ->
|
||||
|
@ -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, "',"
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
190
src/expat_erl.c
190
src/expat_erl.c
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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} ->
|
||||
|
@ -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}.
|
||||
|
@ -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,
|
||||
|
115
src/jd2ejd.erl
115
src/jd2ejd.erl
@ -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 ->
|
||||
|
584
src/jlib.erl
584
src/jlib.erl
@ -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)).
|
||||
|
||||
|
||||
|
297
src/jlib.hrl
297
src/jlib.hrl
@ -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}).
|
||||
|
@ -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) ->
|
||||
|
@ -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.
|
||||
|
159
src/mod_caps.erl
159
src/mod_caps.erl
@ -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
@ -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'}.
|
||||
|
||||
|
@ -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
|
||||
).
|
||||
|
@ -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]).
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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).
|
||||
|
||||
|
||||
|
@ -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
208
src/mod_last.erl
208
src/mod_last.erl
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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}],
|
||||
|
@ -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, "\\&", "\\&")),
|
||||
S3 = element(2, regexp:gsub(S2, "<", "\\<")),
|
||||
S4 = element(2, regexp:gsub(S3, ">", "\\>")),
|
||||
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, " ", "\\ \\ ")),
|
||||
S7 = element(2, regexp:gsub(S6, "\\t", "\\ \\ \\ \\ ")),
|
||||
element(2, regexp:gsub(S7, [226,128,174], "[RLO]")).
|
||||
ReplacementRules =
|
||||
[{"\\&", "\\&"},
|
||||
{"<", "\\<"},
|
||||
{">", "\\>"},
|
||||
{"((http|https|ftp)://|(mailto|xmpp):)[^] )\'\"}]+", link_regexp(NoFollow)},
|
||||
{" ", "\\ \\ "},
|
||||
{"\\t", "\\ \\ \\ \\ "},
|
||||
{[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
@ -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) ->
|
||||
|
@ -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) ->
|
||||
|
@ -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),
|
||||
|
@ -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).
|
||||
|
@ -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 ->
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
_ ->
|
||||
|
@ -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
@ -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).
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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}.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user