Compare commits
53 Commits
v3.0.0-alpha-1
...
v2.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9ac026ed | |||
| 402a8c0d35 | |||
| d24449488c | |||
| b9e57a341d | |||
| 79be929d96 | |||
| 65aed47873 | |||
| 8ac16a0ec1 | |||
| 3250c24ff1 | |||
| 88b6849e69 | |||
| e3b4c99b00 | |||
| 1f5bf8fbc0 | |||
| f044d6ed28 | |||
| 3aa242e2c7 | |||
| 8983cba129 | |||
| bd97c40f92 | |||
| 5a006b977d | |||
| 3658da3ae8 | |||
| 2265f52c34 | |||
| 7b244652f1 | |||
| 325e67f4cb | |||
| 6ce660b9c2 | |||
| 575e07a986 | |||
| 20d1011194 | |||
| 3df5277728 | |||
| ea4f379bd5 | |||
| 51594b6011 | |||
| 58425ba45d | |||
| 7fd3d67ff3 | |||
| 1716b090f9 | |||
| e5e777694d | |||
| 6cf3ae8050 | |||
| 26c572b7e7 | |||
| 311bf4dbb1 | |||
| 1511a5dc82 | |||
| 041d947e83 | |||
| 2007b3d496 | |||
| 09e847b97d | |||
| b9f911554c | |||
| 24323e8b3b | |||
| 1a5eb4f73d | |||
| 3930715712 | |||
| 64bc97d471 | |||
| 4aec8dda48 | |||
| 9192c75e1a | |||
| 512d701ef8 | |||
| ef3dd090c7 | |||
| 3d5fe6a337 | |||
| 0906a68c27 | |||
| 798853abb6 | |||
| 5e3fdbeeed | |||
| 04545f2668 | |||
| f383511e0d | |||
| 2f65188edb |
+1
-1
@@ -16,7 +16,7 @@ release:
|
||||
@echo "* Do not forget to update the version number in src/ejabberd.app!"
|
||||
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@echo "Press any key to continue"
|
||||
@read foo
|
||||
##@read foo
|
||||
@echo "% ejabberd version (automatically generated)." > version.tex
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.0 Developers Guide
|
||||
<TITLE>Ejabberd 2.1.1 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 2.1.1 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.0 Feature Sheet
|
||||
<TITLE>Ejabberd 2.1.1 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 2.1.1 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">
|
||||
|
||||
+16
-6
@@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
|
||||
ejabberd 2.1.0
|
||||
ejabberd 2.1.1
|
||||
|
||||
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 2.1.1 </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>
|
||||
@@ -1620,7 +1620,8 @@ create accounts, change password or edit vCard that is stored in LDAP.</P><P> <A
|
||||
LDAP servers. This option is required.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_encrypt, none|tls}</TT></B></DT><DD CLASS="dd-description"> Type of connection encryption to the LDAP server.
|
||||
Allowed values are: <TT>none</TT>, <TT>tls</TT>.
|
||||
Note that STARTTLS is not supported.
|
||||
The value <TT>tls</TT> enables encryption by using LDAP over SSL.
|
||||
Note that STARTTLS encryption is not supported.
|
||||
The default value is: <TT>none</TT>.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_port, Number}</TT></B></DT><DD CLASS="dd-description"> Port to connect to your LDAP server.
|
||||
The default port is 389 if encryption is disabled; and 636 if encryption is enabled.
|
||||
@@ -1661,7 +1662,7 @@ value is <TT>"%u"</TT>.
|
||||
</DD></DL>
|
||||
</DD><DT CLASS="dt-description"><B><TT>{ldap_filter, Filter}</TT></B></DT><DD CLASS="dd-description">
|
||||
<A HREF="http://tools.ietf.org/html/rfc4515">RFC 4515</A> LDAP filter. The
|
||||
default is <TT>none</TT>. Example:
|
||||
default Filter value is: <TT>undefined</TT>. Example:
|
||||
<TT>"(&(objectClass=shadowAccount)(memberOf=Jabber Users))"</TT>. Please, do
|
||||
not forget to close brackets and do not use superfluous whitespaces. Also you
|
||||
<EM>must not</EM> use <TT>ldap_uidattr</TT> attribute in filter because this
|
||||
@@ -2159,7 +2160,11 @@ Indicate one or more directory index files, similarly to Apache’s
|
||||
DirectoryIndex variable. When a web request hits a directory
|
||||
instead of a regular file, those directory indices are looked in
|
||||
order, and the first one found is returned.
|
||||
Specify a mapping of extensions to content types.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{custom_headers, [ {Name, Value}, ...]}</TT></B></DT><DD CLASS="dd-description">
|
||||
Indicate custom HTTP headers to be included in all responses.
|
||||
Default value is: <TT>[]</TT>
|
||||
</DD><DT CLASS="dt-description"><B><TT>{content_types, [ {Name, Type}, ...]}</TT></B></DT><DD CLASS="dd-description">
|
||||
Specify mappings of extension to content type.
|
||||
There are several content types already defined,
|
||||
with this option you can add new definitions, modify or delete existing ones.
|
||||
To delete an existing definition, simply define it with a value: ‘undefined’.
|
||||
@@ -2179,6 +2184,9 @@ To use this module you must enable it:
|
||||
{docroot, "/var/www"},
|
||||
{accesslog, "/var/log/ejabberd/access.log"},
|
||||
{directory_indices, ["index.html", "main.htm"]},
|
||||
{custom_headers, [{"X-Powered-By", "Erlang/OTP"},
|
||||
{"X-Fry", "It's a widely-believed fact!"}
|
||||
]},
|
||||
{content_types, [{".ogg", "audio/ogg"},
|
||||
{".png", "image/png"},
|
||||
{".jpg", undefined}
|
||||
@@ -2789,6 +2797,8 @@ 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>pubsub.</TT>’. The keyword "@HOST@"
|
||||
is replaced at start time with the real virtual host name.
|
||||
|
||||
If you use <TT>mod_pubsub_odbc</TT>, please ensure the prefix contains only one dot,
|
||||
for example ‘<TT>pubsub.</TT>’, or ‘<TT>publish.</TT>’,.
|
||||
</DD><DT CLASS="dt-description"><B><TT>{access_createnode, AccessName}</TT></B></DT><DD CLASS="dd-description">
|
||||
This option restricts which users are allowed to create pubsub nodes using
|
||||
ACL and ACCESS.
|
||||
@@ -3813,7 +3823,7 @@ serving this connection, otherwise a new connection is opened.</P><P> <A NAME="c
|
||||
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">
|
||||
Copy <CODE>~ejabberd/.erlang.cookie</CODE> file from <TT>first</TT> to
|
||||
<TT>second</TT>.<P>(alt) You can also add ‘<CODE>-cookie content_of_.erlang.cookie</CODE>’
|
||||
<TT>second</TT>.<P>(alt) You can also add ‘<CODE>-setcookie content_of_.erlang.cookie</CODE>’
|
||||
option to all ‘<TT>erl</TT>’ commands below.</P></LI><LI CLASS="li-enumerate">On <TT>second</TT> run the following command as the <TT>ejabberd</TT> daemon user,
|
||||
in the working directory of <TT>ejabberd</TT>:<PRE CLASS="verbatim">erl -sname ejabberd \
|
||||
-mnesia dir "/var/lib/ejabberd/" \
|
||||
|
||||
+14
-9
@@ -2163,7 +2163,8 @@ Parameters:
|
||||
LDAP servers. This option is required.
|
||||
\titem{\{ldap\_encrypt, none|tls\}} \ind{options!ldap\_encrypt}Type of connection encryption to the LDAP server.
|
||||
Allowed values are: \term{none}, \term{tls}.
|
||||
Note that STARTTLS is not supported.
|
||||
The value \term{tls} enables encryption by using LDAP over SSL.
|
||||
Note that STARTTLS encryption is not supported.
|
||||
The default value is: \term{none}.
|
||||
\titem{\{ldap\_port, Number\}} \ind{options!ldap\_port}Port to connect to your LDAP server.
|
||||
The default port is~389 if encryption is disabled; and 636 if encryption is enabled.
|
||||
@@ -2215,7 +2216,7 @@ You can authenticate users against an LDAP directory. Available options are:
|
||||
\titem{\{ldap\_filter, Filter\}}\ind{options!ldap\_filter}\ind{protocols!RFC 4515:
|
||||
LDAP String Representation of Search Filters}
|
||||
\footahref{http://tools.ietf.org/html/rfc4515}{RFC 4515} LDAP filter. The
|
||||
default is \term{none}. Example:
|
||||
default Filter value is: \term{undefined}. Example:
|
||||
\term{"(\&(objectClass=shadowAccount)(memberOf=Jabber Users))"}. Please, do
|
||||
not forget to close brackets and do not use superfluous whitespaces. Also you
|
||||
\emph{must not} use \option{ldap\_uidattr} attribute in filter because this
|
||||
@@ -2853,12 +2854,11 @@ Options:
|
||||
DirectoryIndex variable. When a web request hits a directory
|
||||
instead of a regular file, those directory indices are looked in
|
||||
order, and the first one found is returned.
|
||||
%B \titem{content\_types} \ind{options!contenttypes}
|
||||
%M \titem{\{content\_types, \{Extension, Type\} \}} \ind{options!contenttypes}
|
||||
%B \titem{\{content\_types, [ Extension, Type, ... ]\}} \ind{options!contenttypes}
|
||||
%B \titem{\{content\_types, [ {Extension, Type}, ... ]\}} \ind{options!contenttypes}
|
||||
%M \titem{\{content\_types, [ \{Extension, Type\}, ... ]\}} \ind{options!contenttypes}
|
||||
Specify a mapping of extensions to content types.
|
||||
\titem{\{custom\_headers, [ \{Name, Value\}, ...]\}} \ind{options!customheaders}
|
||||
Indicate custom HTTP headers to be included in all responses.
|
||||
Default value is: \term{[]}
|
||||
\titem{\{content\_types, [ \{Name, Type\}, ...]\}} \ind{options!contenttypes}
|
||||
Specify mappings of extension to content type.
|
||||
There are several content types already defined,
|
||||
with this option you can add new definitions, modify or delete existing ones.
|
||||
To delete an existing definition, simply define it with a value: `undefined'.
|
||||
@@ -2881,6 +2881,9 @@ To use this module you must enable it:
|
||||
{docroot, "/var/www"},
|
||||
{accesslog, "/var/log/ejabberd/access.log"},
|
||||
{directory_indices, ["index.html", "main.htm"]},
|
||||
{custom_headers, [{"X-Powered-By", "Erlang/OTP"},
|
||||
{"X-Fry", "It's a widely-believed fact!"}
|
||||
]},
|
||||
{content_types, [{".ogg", "audio/ogg"},
|
||||
{".png", "image/png"},
|
||||
{".jpg", undefined}
|
||||
@@ -3570,6 +3573,8 @@ and it requires \modcaps{}.
|
||||
Options:
|
||||
\begin{description}
|
||||
\hostitem{pubsub}
|
||||
If you use \modpubsubodbc, please ensure the prefix contains only one dot,
|
||||
for example `\jid{pubsub.}', or `\jid{publish.}',.
|
||||
\titem{\{access\_createnode, AccessName\}} \ind{options!access\_createnode}
|
||||
This option restricts which users are allowed to create pubsub nodes using
|
||||
ACL and ACCESS.
|
||||
@@ -4914,7 +4919,7 @@ following steps:
|
||||
\item Copy \verb|~ejabberd/.erlang.cookie| file from \term{first} to
|
||||
\term{second}.
|
||||
|
||||
(alt) You can also add `\verb|-cookie content_of_.erlang.cookie|'
|
||||
(alt) You can also add `\verb|-setcookie content_of_.erlang.cookie|'
|
||||
option to all `\shell{erl}' commands below.
|
||||
|
||||
\item On \term{second} run the following command as the \ejabberd{} daemon user,
|
||||
|
||||
@@ -135,6 +135,8 @@
|
||||
- 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.
|
||||
@@ -207,14 +209,24 @@
|
||||
|
||||
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
|
||||
- The database schemas have three changes since ejabberd 2.0.x.
|
||||
Check the database creation SQL files and update your database.
|
||||
1) New table roster_version to support roster versioning.
|
||||
2) Six new tables for optional pubsub ODBC storage.
|
||||
3) Some tables in the MySQL database have a new created_at column.
|
||||
|
||||
- As usual, 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.
|
||||
- Between ejabberd 2.0.0 and 2.0.5, mod_pubsub used "default" as the
|
||||
default node plugin. But in 2.1.0 this is renamed to "hometree".
|
||||
You have to edit your ejabberd config file and replace those names.
|
||||
If you used ejabberd 2.0.5 or older, the database will be updated
|
||||
automatically. But if you were using ejabberd from SVN, you must
|
||||
manually run ejabberdctl with the command: rename_default_nodeplugin.
|
||||
Running this command on already updated database does nothing.
|
||||
|
||||
- The listener options 'ip' and inet6' are not documented anymore
|
||||
- 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.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
Release Notes
|
||||
ejabberd 2.1.1
|
||||
|
||||
ejabberd 2.1.1 is the first bugfix release in ejabberd 2.1.x branch.
|
||||
|
||||
ejabberd 2.1.1 includes several important bugfixes.
|
||||
More details of those fixes can be retrieved from:
|
||||
http://redir.process-one.net/ejabberd-2.1.1
|
||||
|
||||
The new code can be downloaded from ejabberd download page:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
|
||||
The changes are:
|
||||
|
||||
* Core
|
||||
- Call ejabberd_router:route/3 instead of sending a message
|
||||
- Can't connect if starttls_required and zlib are set
|
||||
- Routes vCard request to the occupant full JID, but should to bare JID
|
||||
- S2S: fix allow_host/2 on subdomains. added hook s2s_allow_host
|
||||
|
||||
* MUC
|
||||
- Support converting one-to-one chat to MUC
|
||||
- Add support for serving a Unique Room Name
|
||||
|
||||
* Publish Subscribe
|
||||
- Receive same last published PEP items at reconnect if several resources online
|
||||
- Typo in mod_pubsub_odbc breaks Service Discovery and more
|
||||
|
||||
* Web
|
||||
- Fix memory and port leak when TLS is enabled in HTTP
|
||||
- WebAdmin doesn't report correct last activity with postgresql backend
|
||||
- Option to define custom HTTP headers in mod_http_fileserver
|
||||
- Show informative webpage when browsing the HTTP-Poll page
|
||||
|
||||
* Other
|
||||
- Change captcha.sh to not depend on bash
|
||||
- Generate main XML file also when exporting only a vhost
|
||||
- Fix last newline in ejabberdctl result
|
||||
- Guide: fix -setcookie, mod_pubsub_odbc host, content_types
|
||||
|
||||
|
||||
Bug reports
|
||||
|
||||
You can officially report bugs on ProcessOne support site:
|
||||
http://support.process-one.net/
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.1.0}
|
||||
\newcommand{\version}{2.1.1}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "2.1.0"},
|
||||
{vsn, "2.1.1"},
|
||||
{modules, [acl,
|
||||
adhoc,
|
||||
configure,
|
||||
|
||||
@@ -187,7 +187,6 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
TLSOpts = [verify_none | TLSOpts1],
|
||||
Zlib = lists:member(zlib, Opts) andalso (not StartTLSRequired),
|
||||
IP = peerip(SockMod, Socket),
|
||||
%% Check if IP is blacklisted:
|
||||
case is_ip_blacklisted(IP) of
|
||||
@@ -1220,7 +1219,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
"iq" ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = ?NS_VCARD} ->
|
||||
#iq{xmlns = ?NS_VCARD} when (To#jid.luser == "") or (To#jid.lresource == "") ->
|
||||
Host = StateData#state.server,
|
||||
case ets:lookup(sm_iqtable, {?NS_VCARD, Host}) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
|
||||
@@ -197,8 +197,12 @@ process(["help" | Mode]) ->
|
||||
process(Args) ->
|
||||
AccessCommands = get_accesscommands(),
|
||||
{String, Code} = process2(Args, AccessCommands),
|
||||
io:format(String),
|
||||
io:format("\n"),
|
||||
case String of
|
||||
[] -> ok;
|
||||
_ ->
|
||||
io:format(String),
|
||||
io:format("\n")
|
||||
end,
|
||||
Code.
|
||||
|
||||
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
|
||||
|
||||
@@ -129,7 +129,7 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
SockOpts2 = case erlang:system_info(otp_release) >= "R13B" of
|
||||
true -> [{send_timeout_close, true} | SockOpts];
|
||||
false -> []
|
||||
false -> SockOpts
|
||||
end,
|
||||
Res = gen_tcp:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
|
||||
@@ -212,7 +212,7 @@ add_user(El, Domain, User, Password) ->
|
||||
El),
|
||||
ok;
|
||||
{atomic, exists} ->
|
||||
?INFO_MSG("User ~p@~p already exists, using stored profile...~n",
|
||||
io:format("Account ~s@~s already exists, updating it...~n",
|
||||
[User, Domain]),
|
||||
io:format(""),
|
||||
ok = exmpp_xml:foreach(
|
||||
@@ -417,10 +417,10 @@ to_list(B) when is_atom(B) -> atom_to_list(B).
|
||||
|
||||
%%%==================================
|
||||
|
||||
%%%% Export server
|
||||
%%%% Export hosts
|
||||
|
||||
%% @spec (Dir::string()) -> ok
|
||||
export_server(Dir) ->
|
||||
%% @spec (Dir::string(), Hosts::[string()]) -> ok
|
||||
export_hosts(Dir, Hosts) ->
|
||||
try_start_exmpp(),
|
||||
|
||||
FnT = make_filename_template(),
|
||||
@@ -430,7 +430,6 @@ export_server(Dir) ->
|
||||
print(Fd, make_piefxis_xml_head()),
|
||||
print(Fd, make_piefxis_server_head()),
|
||||
|
||||
Hosts = ?MYHOSTS,
|
||||
FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
|
||||
[print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
|
||||
|
||||
@@ -442,15 +441,21 @@ export_server(Dir) ->
|
||||
|
||||
ok.
|
||||
|
||||
%%%==================================
|
||||
%%%% Export server
|
||||
|
||||
%% @spec (Dir::string()) -> ok
|
||||
export_server(Dir) ->
|
||||
Hosts = ?MYHOSTS,
|
||||
export_hosts(Dir, Hosts).
|
||||
|
||||
%%%==================================
|
||||
%%%% Export host
|
||||
|
||||
%% @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).
|
||||
Hosts = [Host],
|
||||
export_hosts(Dir, Hosts).
|
||||
|
||||
%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
|
||||
export_host(Dir, FnH, Host) ->
|
||||
|
||||
+25
-9
@@ -486,15 +486,31 @@ update_tables() ->
|
||||
|
||||
%% Check if host is in blacklist or white list
|
||||
allow_host(MyServer, S2SHost) ->
|
||||
case ejabberd_config:get_local_option({{s2s_host, S2SHost},MyServer}) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option({s2s_default_policy, MyServer}) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true %% The default s2s policy is allow
|
||||
end
|
||||
case lists:filter(
|
||||
fun(Host) ->
|
||||
is_subdomain(MyServer, Host)
|
||||
end, ?MYHOSTS) of
|
||||
[MyHost|_] ->
|
||||
allow_host1(MyHost, S2SHost);
|
||||
[] ->
|
||||
allow_host1(MyServer, S2SHost)
|
||||
end.
|
||||
|
||||
allow_host1(MyHost, S2SHost) ->
|
||||
case ejabberd_config:get_local_option({{s2s_host, S2SHost}, MyHost}) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ ->
|
||||
case ejabberd_config:get_local_option({s2s_default_policy, MyHost}) of
|
||||
deny -> false;
|
||||
_ ->
|
||||
case ejabberd_hooks:run_fold(s2s_allow_host, MyHost,
|
||||
allow, [MyHost, S2SHost]) of
|
||||
deny -> false;
|
||||
allow -> true;
|
||||
_ -> true
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% Get information about S2S connections of the specified type.
|
||||
|
||||
@@ -272,7 +272,6 @@ open_socket2(Type, Addr, Port) ->
|
||||
case (catch ejabberd_socket:connect(Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{send_timeout, ?TCP_SEND_TIMEOUT},
|
||||
{send_timeout_close, true},
|
||||
{active, false}, Type | SockOpts],
|
||||
Timeout)) of
|
||||
{ok, _Socket} = R -> R;
|
||||
|
||||
+1
-1
@@ -481,7 +481,7 @@ do_route(From, To, Packet) ->
|
||||
_ ->
|
||||
Err =
|
||||
jlib:make_error_reply(
|
||||
Packet, ?ERR_RECIPIENT_UNAVAILABLE),
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
_ ->
|
||||
|
||||
@@ -159,8 +159,12 @@ debug ()
|
||||
echo " control+c, control+c"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
read foo
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
read foo
|
||||
fi
|
||||
echo ""
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME debug-${ERLANG_NODE} \
|
||||
@@ -184,8 +188,12 @@ live ()
|
||||
echo " q(). and press the Enter key"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
read foo
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
read foo
|
||||
fi
|
||||
echo ""
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
-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_MUC_UNIQUE, "http://jabber.org/protocol/muc#unique").
|
||||
-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").
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%% 2009, improvements from Process-One to support correct PEP handling
|
||||
%%% 2009, improvements from ProcessOne to support correct PEP handling
|
||||
%%% through s2s, use less memory, and speedup global caps handling
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ store_last_info(User, Server, TimeStamp, Status) ->
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, Timestamp::integer(), Status::string()} | not_found
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found
|
||||
get_last_info(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read(last_activity, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} ->
|
||||
|
||||
@@ -413,6 +413,17 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{type = get,
|
||||
xmlns = ?NS_MUC_UNIQUE
|
||||
} = IQ ->
|
||||
Res = IQ#iq{type = result,
|
||||
sub_el =
|
||||
[{xmlelement, "unique",
|
||||
[{"xmlns", ?NS_MUC_UNIQUE}],
|
||||
[iq_get_unique(From)]}]},
|
||||
ejabberd_router:route(To,
|
||||
From,
|
||||
jlib:iq_to_xml(Res));
|
||||
#iq{} ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet,
|
||||
@@ -570,6 +581,7 @@ iq_disco_info(Lang) ->
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_MUC}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_MUC_UNIQUE}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_REGISTER}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_RSM}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}].
|
||||
@@ -669,6 +681,16 @@ flush() ->
|
||||
{"var", Var}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, Val}]}]}).
|
||||
|
||||
%% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
|
||||
%% the requester JID, the local time and a random salt.
|
||||
%%
|
||||
%% "pseudo" because we don't verify that there is not a room
|
||||
%% with the returned Name already created, nor mark the generated Name
|
||||
%% as "already used". But in practice, it is unique enough. See
|
||||
%% http://xmpp.org/extensions/xep-0045.html#createroom-unique
|
||||
iq_get_unique(From) ->
|
||||
{xmlcdata, sha:sha(term_to_binary([From, now(), randoms:get_string()]))}.
|
||||
|
||||
iq_get_register_info(Host, From, Lang) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
LUS = {LUser, LServer},
|
||||
|
||||
@@ -470,9 +470,10 @@ normal_state({route, From, ToNick,
|
||||
{xmlelement, "iq", Attrs, _Els} = Packet},
|
||||
StateData) ->
|
||||
Lang = xml:get_attr_s("xml:lang", Attrs),
|
||||
StanzaId = xml:get_attr_s("id", Attrs),
|
||||
case {(StateData#state.config)#config.allow_query_users,
|
||||
is_user_online(From, StateData)} of
|
||||
{true, true} ->
|
||||
is_user_online_iq(StanzaId, From, StateData)} of
|
||||
{true, {true, NewId, FromFull}} ->
|
||||
case find_jid_by_nick(ToNick, StateData) of
|
||||
false ->
|
||||
case jlib:iq_query_info(Packet) of
|
||||
@@ -489,13 +490,15 @@ normal_state({route, From, ToNick,
|
||||
end;
|
||||
ToJID ->
|
||||
{ok, #user{nick = FromNick}} =
|
||||
?DICT:find(jlib:jid_tolower(From),
|
||||
?DICT:find(jlib:jid_tolower(FromFull),
|
||||
StateData#state.users),
|
||||
{ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
|
||||
StanzaId, NewId,Packet),
|
||||
ejabberd_router:route(
|
||||
jlib:jid_replace_resource(StateData#state.jid, FromNick),
|
||||
ToJID, Packet)
|
||||
ToJID2, Packet2)
|
||||
end;
|
||||
{_, false} ->
|
||||
{_, {false, _, _}} ->
|
||||
case jlib:iq_query_info(Packet) of
|
||||
reply ->
|
||||
ok;
|
||||
@@ -829,7 +832,6 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet,
|
||||
{next_state, normal_state, StateData}
|
||||
end.
|
||||
|
||||
|
||||
%% @doc Check if this non participant can send message to room.
|
||||
%%
|
||||
%% XEP-0045 v1.23:
|
||||
@@ -969,6 +971,50 @@ is_user_online(JID, StateData) ->
|
||||
LJID = jlib:jid_tolower(JID),
|
||||
?DICT:is_key(LJID, StateData#state.users).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Handle IQ queries of vCard
|
||||
%%%
|
||||
is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= "" ->
|
||||
{is_user_online(JID, StateData), StanzaId, JID};
|
||||
is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == "" ->
|
||||
try stanzaid_unpack(StanzaId) of
|
||||
{OriginalId, Resource} ->
|
||||
JIDWithResource = jlib:jid_replace_resource(JID, Resource),
|
||||
{is_user_online(JIDWithResource, StateData),
|
||||
OriginalId, JIDWithResource}
|
||||
catch
|
||||
_:_ ->
|
||||
{is_user_online(JID, StateData), StanzaId, JID}
|
||||
end.
|
||||
|
||||
handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) ->
|
||||
ToBareJID = jlib:jid_remove_resource(ToJID),
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, NewId, IQ, Packet).
|
||||
handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, _NewId,
|
||||
#iq{type = get, xmlns = ?NS_VCARD}, Packet)
|
||||
when ToBareJID /= ToJID ->
|
||||
{ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
|
||||
handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, _StanzaId, NewId, _IQ, Packet) ->
|
||||
{ToJID, change_stanzaid(NewId, Packet)}.
|
||||
|
||||
stanzaid_pack(OriginalId, Resource) ->
|
||||
"berd"++base64:encode_to_string("ejab\0" ++ OriginalId ++ "\0" ++ Resource).
|
||||
stanzaid_unpack("berd"++StanzaIdBase64) ->
|
||||
StanzaId = base64:decode_to_string(StanzaIdBase64),
|
||||
["ejab", OriginalId, Resource] = string:tokens(StanzaId, "\0"),
|
||||
{OriginalId, Resource}.
|
||||
|
||||
change_stanzaid(NewId, Packet) ->
|
||||
{xmlelement, Name, Attrs, Els} = jlib:remove_attr("id", Packet),
|
||||
{xmlelement, Name, [{"id", NewId} | Attrs], Els}.
|
||||
change_stanzaid(PreviousId, ToJID, Packet) ->
|
||||
NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
|
||||
change_stanzaid(NewId, Packet).
|
||||
%%%
|
||||
%%%
|
||||
|
||||
role_to_list(Role) ->
|
||||
case Role of
|
||||
moderator -> "moderator";
|
||||
|
||||
@@ -191,8 +191,8 @@ init([ServerHost, Opts]) ->
|
||||
ets:insert(gen_mod:get_module_proc(Host, config), {max_items_node, MaxItemsNode}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, Plugins}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, LastItemCache}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
|
||||
ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
|
||||
ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
|
||||
@@ -631,7 +631,7 @@ disco_local_features(Acc, _From, To, [], _Lang) ->
|
||||
end,
|
||||
{result, Feats ++ lists:map(fun(Feature) ->
|
||||
?NS_PUBSUB++"#"++Feature
|
||||
end, features(Host, []))};
|
||||
end, features(Host, <<>>))};
|
||||
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
@@ -718,10 +718,16 @@ disco_sm_items(Acc, From, To, SNode, _Lang) ->
|
||||
|
||||
presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, JID, Pid) ->
|
||||
Proc = gen_mod:get_module_proc(Server, ?PROCNAME),
|
||||
%%?DEBUG("presence probe self ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
|
||||
gen_server:cast(Proc, {presence, JID, Pid}),
|
||||
gen_server:cast(Proc, {presence, User, Server, [Resource], JID});
|
||||
presence_probe(#jid{luser = User, lserver = Server}, #jid{luser = User, lserver = Server}, _Pid) ->
|
||||
%% ignore presence_probe from other ressources for the current user
|
||||
%% this way, we do not send duplicated last items if user already connected with other clients
|
||||
ok;
|
||||
presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, #jid{lserver = Host} = JID, _Pid) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
%%?DEBUG("presence probe peer ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
|
||||
gen_server:cast(Proc, {presence, User, Server, [Resource], JID}).
|
||||
|
||||
%% -------
|
||||
@@ -1030,6 +1036,18 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
|
||||
end
|
||||
end.
|
||||
|
||||
command_disco_info(_Host, <<?NS_COMMANDS>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-list"}],
|
||||
[]},
|
||||
{result, [IdentityEl]};
|
||||
command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-node"}],
|
||||
[]},
|
||||
FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{result, [IdentityEl, FeaturesEl]}.
|
||||
|
||||
node_disco_info(Host, Node, From) ->
|
||||
node_disco_info(Host, Node, From, true, true).
|
||||
node_disco_identity(Host, Node, From) ->
|
||||
@@ -1092,21 +1110,38 @@ iq_disco_info(Host, SNode, From, Lang) ->
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
|
||||
lists:map(fun(Feature) ->
|
||||
{xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
|
||||
end, features(Host, Node))};
|
||||
<<?NS_COMMANDS>> ->
|
||||
command_disco_info(Host, Node, From);
|
||||
<<?NS_PUBSUB_GET_PENDING>> ->
|
||||
command_disco_info(Host, Node, From);
|
||||
_ ->
|
||||
node_disco_info(Host, Node, From)
|
||||
end.
|
||||
|
||||
iq_disco_items(Host, [], From) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_action(Host, get_subnodes, [Host, <<>>, From]))};
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, Nodes)};
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
iq_disco_items(Host, ?NS_COMMANDS, _From) ->
|
||||
%% TODO: support localization of this string
|
||||
CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}],
|
||||
{result, CommandItems};
|
||||
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
|
||||
CommandItems = [],
|
||||
{result, CommandItems};
|
||||
iq_disco_items(Host, Item, From) ->
|
||||
case string:tokens(Item, "!") of
|
||||
[_SNode, _ItemID] ->
|
||||
@@ -1122,7 +1157,9 @@ iq_disco_items(Host, Item, From) ->
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
{xmlelement, "item", [{"jid", Host}|nodeAttr(SubNode)], []}
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_call(Host, get_subnodes, [Host, Node, From])),
|
||||
Items = lists:map(
|
||||
fun(#pubsub_item{itemid = {RN, _}}) ->
|
||||
@@ -1174,7 +1211,7 @@ iq_get_vcard(Lang) ->
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
"ejabberd Publish-Subscribe module") ++
|
||||
"\nCopyright (c) 2004-2009 Process-One"}]}].
|
||||
"\nCopyright (c) 2004-2009 ProcessOne"}]}].
|
||||
|
||||
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
|
||||
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
|
||||
@@ -1366,6 +1403,13 @@ adhoc_request(Host, _ServerHost, Owner,
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
adhoc_request(_Host, _ServerHost, _Owner, #adhoc_request{action = "cancel"},
|
||||
_Access, _Plugins) ->
|
||||
#adhoc_response{status = canceled};
|
||||
adhoc_request(Host, ServerHost, Owner, #adhoc_request{action = []} = R,
|
||||
Access, Plugins) ->
|
||||
adhoc_request(Host, ServerHost, Owner, R#adhoc_request{action = "execute"},
|
||||
Access, Plugins);
|
||||
adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
|
||||
?DEBUG("Couldn't process ad hoc command:~n~p", [Other]),
|
||||
{error, ?ERR_ITEM_NOT_FOUND}.
|
||||
@@ -1417,17 +1461,12 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
?DEBUG("Sending pending auth events for ~s on ~s:~s",
|
||||
[jlib:jid_to_string(Owner), Host, node_to_string(Node)]),
|
||||
Action =
|
||||
fun (#pubsub_node{id = NodeID, type = Type} = N) ->
|
||||
fun (#pubsub_node{id = NodeID, type = Type}) ->
|
||||
case lists:member("get-pending", features(Type)) of
|
||||
true ->
|
||||
case node_call(Type, get_affiliation, [NodeID, Owner]) of
|
||||
{result, owner} ->
|
||||
{result, Subscriptions} = node_call(Type, get_node_subscriptions, [NodeID]),
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
{result, ok};
|
||||
node_call(Type, get_node_subscriptions, [NodeID]);
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end;
|
||||
@@ -1436,7 +1475,11 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, _} ->
|
||||
{result, {N, Subscriptions}} ->
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
#adhoc_response{};
|
||||
Err ->
|
||||
Err
|
||||
@@ -1472,7 +1515,7 @@ send_authorization_request(#pubsub_node{owners = Owners, nodeid = {Host, Node}},
|
||||
{"label", translate:translate(Lang, "Allow this Jabber ID to subscribe to this pubsub node?")}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
|
||||
lists:foreach(fun(Owner) ->
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(Owner), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
|
||||
end, Owners).
|
||||
|
||||
find_authorization_response(Packet) ->
|
||||
@@ -1522,7 +1565,7 @@ send_authorization_approval(Host, JID, SNode, Subscription) ->
|
||||
[{xmlelement, "subscription",
|
||||
[{"jid", jlib:jid_to_string(JID)}|nodeAttr(SNode)] ++ SubAttrs,
|
||||
[]}]),
|
||||
ejabberd_router ! {route, service_jid(Host), JID, Stanza}.
|
||||
ejabberd_router:route(service_jid(Host), JID, Stanza).
|
||||
|
||||
handle_authorization_response(Host, From, To, Packet, XFields) ->
|
||||
case {lists:keysearch("pubsub#node", 1, XFields),
|
||||
@@ -1768,7 +1811,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
%%<li>The node is the root collection node, which cannot be deleted.</li>
|
||||
%%<li>The specified node does not exist.</li>
|
||||
%%</ul>
|
||||
delete_node(_Host, [], _Owner) ->
|
||||
delete_node(_Host, <<>>, _Owner) ->
|
||||
%% Node is the root
|
||||
{error, ?ERR_NOT_ALLOWED};
|
||||
delete_node(Host, Node, Owner) ->
|
||||
@@ -2293,7 +2336,7 @@ send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
Stanza = event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls([LastItem])}], ModifNow, ModifLjid),
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza)
|
||||
end;
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
@@ -2318,7 +2361,7 @@ send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls(ToSend)}])
|
||||
end,
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}.
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza).
|
||||
|
||||
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
|
||||
%% Host = host()
|
||||
@@ -2720,7 +2763,7 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
|
||||
[{"jid", jlib:jid_to_string(JID)},
|
||||
%{"subid", SubId},
|
||||
{"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
|
||||
end,
|
||||
Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
|
||||
case lists:member(Owner, Owners) of
|
||||
@@ -3068,7 +3111,7 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp
|
||||
end,
|
||||
SHIMStanza = add_headers(Stanza, collection_shim(Node, Nodes)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router ! {route, From, jlib:make_jid(To), SHIMStanza}
|
||||
ejabberd_router:route(From, jlib:make_jid(To), SHIMStanza)
|
||||
end, LJIDs)
|
||||
end, NodesByJID),
|
||||
%% Handles implicit presence subscriptions
|
||||
@@ -3102,7 +3145,7 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp
|
||||
end
|
||||
end, [], user_resources(U, S)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router ! {route, Sender, jlib:make_jid(To), Stanza}
|
||||
ejabberd_router:route(Sender, jlib:make_jid(To), Stanza)
|
||||
end, LJIDs)
|
||||
end)
|
||||
end, Contacts);
|
||||
@@ -3583,7 +3626,7 @@ features(Type) ->
|
||||
{'EXIT', {undef, _}} -> [];
|
||||
Result -> Result
|
||||
end.
|
||||
features(Host, []) ->
|
||||
features(Host, <<>>) ->
|
||||
lists:usort(lists:foldl(fun(Plugin, Acc) ->
|
||||
Acc ++ features(Plugin)
|
||||
end, [], plugins(Host)));
|
||||
|
||||
@@ -191,8 +191,8 @@ init([ServerHost, Opts]) ->
|
||||
ets:insert(gen_mod:get_module_proc(Host, config), {max_items_node, MaxItemsNode}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {nodetree, NodeTree}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {plugins, Plugins}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, Plugins}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, LastItemCache}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {last_item_cache, LastItemCache}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {max_items_node, MaxItemsNode}),
|
||||
ets:insert(gen_mod:get_module_proc(ServerHost, config), {pep_mapping, PepMapping}),
|
||||
ejabberd_hooks:add(disco_sm_identity, ServerHost, ?MODULE, disco_sm_identity, 75),
|
||||
ejabberd_hooks:add(disco_sm_features, ServerHost, ?MODULE, disco_sm_features, 75),
|
||||
@@ -434,7 +434,7 @@ disco_local_features(Acc, _From, To, [], _Lang) ->
|
||||
end,
|
||||
{result, Feats ++ lists:map(fun(Feature) ->
|
||||
?NS_PUBSUB++"#"++Feature
|
||||
end, features(Host, []))};
|
||||
end, features(Host, <<>>))};
|
||||
disco_local_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
@@ -521,10 +521,16 @@ disco_sm_items(Acc, From, To, SNode, _Lang) ->
|
||||
|
||||
presence_probe(#jid{luser = User, lserver = Server, lresource = Resource} = JID, JID, Pid) ->
|
||||
Proc = gen_mod:get_module_proc(Server, ?PROCNAME),
|
||||
%%?DEBUG("presence probe self ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
|
||||
gen_server:cast(Proc, {presence, JID, Pid}),
|
||||
gen_server:cast(Proc, {presence, User, Server, [Resource], JID});
|
||||
presence_probe(#jid{luser = User, lserver = Server}, #jid{luser = User, lserver = Server}, _Pid) ->
|
||||
%% ignore presence_probe from other ressources for the current user
|
||||
%% this way, we do not send duplicated last items if user already connected with other clients
|
||||
ok;
|
||||
presence_probe(#jid{luser = User, lserver = Server, lresource = Resource}, #jid{lserver = Host} = JID, _Pid) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
%%?DEBUG("presence probe peer ~s@~s/~s ~s@~s/~s",[User,Server,Resource,element(2,JID),element(3,JID),element(4,JID)]),
|
||||
gen_server:cast(Proc, {presence, User, Server, [Resource], JID}).
|
||||
|
||||
%% -------
|
||||
@@ -834,6 +840,18 @@ do_route(ServerHost, Access, Plugins, Host, From, To, Packet) ->
|
||||
end
|
||||
end.
|
||||
|
||||
command_disco_info(_Host, <<?NS_COMMANDS>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-list"}],
|
||||
[]},
|
||||
{result, [IdentityEl]};
|
||||
command_disco_info(_Host, <<?NS_PUBSUB_GET_PENDING>>, _From) ->
|
||||
IdentityEl = {xmlelement, "identity", [{"category", "automation"},
|
||||
{"type", "command-node"}],
|
||||
[]},
|
||||
FeaturesEl = {xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{result, [IdentityEl, FeaturesEl]}.
|
||||
|
||||
node_disco_info(Host, Node, From) ->
|
||||
node_disco_info(Host, Node, From, true, true).
|
||||
node_disco_identity(Host, Node, From) ->
|
||||
@@ -897,22 +915,39 @@ iq_disco_info(Host, SNode, From, Lang) ->
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_INFO}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
|
||||
lists:map(fun
|
||||
("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
|
||||
(T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
|
||||
end, features(Host, Node))};
|
||||
<<?NS_COMMANDS>> ->
|
||||
command_disco_info(Host, Node, From);
|
||||
<<?NS_PUBSUB_GET_PENDING>> ->
|
||||
command_disco_info(Host, Node, From);
|
||||
_ ->
|
||||
node_disco_info(Host, Node, From)
|
||||
end.
|
||||
|
||||
iq_disco_items(Host, [], From, _RSM) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_action(Host, get_subnodes, [Host, <<>>, From]))};
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, Nodes)};
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
|
||||
%% TODO: support localization of this string
|
||||
CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}],
|
||||
{result, CommandItems};
|
||||
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
|
||||
CommandItems = [],
|
||||
{result, CommandItems};
|
||||
iq_disco_items(Host, Item, From, RSM) ->
|
||||
case string:tokens(Item, "!") of
|
||||
[_SNode, _ItemID] ->
|
||||
@@ -928,7 +963,9 @@ iq_disco_items(Host, Item, From, RSM) ->
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
{xmlelement, "item", [{"jid", Host}|nodeAttr(SubNode)], []}
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_call(Host, get_subnodes, [Host, Node, From])),
|
||||
Items = lists:map(
|
||||
fun(#pubsub_item{itemid = {RN, _}}) ->
|
||||
@@ -980,7 +1017,7 @@ iq_get_vcard(Lang) ->
|
||||
[{xmlcdata,
|
||||
translate:translate(Lang,
|
||||
"ejabberd Publish-Subscribe module") ++
|
||||
"\nCopyright (c) 2004-2009 Process-One"}]}].
|
||||
"\nCopyright (c) 2004-2009 ProcessOne"}]}].
|
||||
|
||||
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) ->
|
||||
iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)).
|
||||
@@ -1175,6 +1212,13 @@ adhoc_request(Host, _ServerHost, Owner,
|
||||
Error ->
|
||||
Error
|
||||
end;
|
||||
adhoc_request(_Host, _ServerHost, _Owner, #adhoc_request{action = "cancel"},
|
||||
_Access, _Plugins) ->
|
||||
#adhoc_response{status = canceled};
|
||||
adhoc_request(Host, ServerHost, Owner, #adhoc_request{action = []} = R,
|
||||
Access, Plugins) ->
|
||||
adhoc_request(Host, ServerHost, Owner, R#adhoc_request{action = "execute"},
|
||||
Access, Plugins);
|
||||
adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) ->
|
||||
?DEBUG("Couldn't process ad hoc command:~n~p", [Other]),
|
||||
{error, ?ERR_ITEM_NOT_FOUND}.
|
||||
@@ -1227,17 +1271,12 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
?DEBUG("Sending pending auth events for ~s on ~s:~s",
|
||||
[jlib:jid_to_string(Owner), Host, node_to_string(Node)]),
|
||||
Action =
|
||||
fun (#pubsub_node{id = NodeID, type = Type} = N) ->
|
||||
fun (#pubsub_node{id = NodeID, type = Type}) ->
|
||||
case lists:member("get-pending", features(Type)) of
|
||||
true ->
|
||||
case node_call(Type, get_affiliation, [NodeID, Owner]) of
|
||||
{result, owner} ->
|
||||
{result, Subscriptions} = node_call(Type, get_node_subscriptions, [NodeID]),
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
{result, ok};
|
||||
node_call(Type, get_node_subscriptions, [NodeID]);
|
||||
_ ->
|
||||
{error, ?ERR_FORBIDDEN}
|
||||
end;
|
||||
@@ -1246,7 +1285,11 @@ send_pending_auth_events(Host, Node, Owner) ->
|
||||
end
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, _} ->
|
||||
{result, {N, Subscriptions}} ->
|
||||
lists:foreach(fun({J, pending, _SubID}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
({J, pending}) -> send_authorization_request(N, jlib:make_jid(J));
|
||||
(_) -> ok
|
||||
end, Subscriptions),
|
||||
#adhoc_response{};
|
||||
Err ->
|
||||
Err
|
||||
@@ -1282,7 +1325,7 @@ send_authorization_request(#pubsub_node{nodeid = {Host, Node}, type = Type, id =
|
||||
{"label", translate:translate(Lang, "Allow this Jabber ID to subscribe to this pubsub node?")}],
|
||||
[{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
|
||||
lists:foreach(fun(Owner) ->
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(Owner), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
|
||||
end, node_owners(Host, Type, NodeId)).
|
||||
|
||||
find_authorization_response(Packet) ->
|
||||
@@ -1332,7 +1375,7 @@ send_authorization_approval(Host, JID, SNode, Subscription) ->
|
||||
[{xmlelement, "subscription",
|
||||
[{"jid", jlib:jid_to_string(JID)}|nodeAttr(SNode)] ++ SubAttrs,
|
||||
[]}]),
|
||||
ejabberd_router ! {route, service_jid(Host), JID, Stanza}.
|
||||
ejabberd_router:route(service_jid(Host), JID, Stanza).
|
||||
|
||||
handle_authorization_response(Host, From, To, Packet, XFields) ->
|
||||
case {lists:keysearch("pubsub#node", 1, XFields),
|
||||
@@ -1578,7 +1621,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
%%<li>The node is the root collection node, which cannot be deleted.</li>
|
||||
%%<li>The specified node does not exist.</li>
|
||||
%%</ul>
|
||||
delete_node(_Host, [], _Owner) ->
|
||||
delete_node(_Host, <<>>, _Owner) ->
|
||||
%% Node is the root
|
||||
{error, ?ERR_NOT_ALLOWED};
|
||||
delete_node(Host, Node, Owner) ->
|
||||
@@ -2120,7 +2163,7 @@ send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls([LastItem])}], ModifNow, ModifLjid)
|
||||
end,
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza};
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
{result, []} ->
|
||||
@@ -2144,7 +2187,7 @@ send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
itemsEls(ToSend)}])
|
||||
end,
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}.
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza).
|
||||
|
||||
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
|
||||
%% Host = host()
|
||||
@@ -2529,7 +2572,7 @@ set_subscriptions(Host, Node, From, EntitiesEls) ->
|
||||
[{"jid", jlib:jid_to_string(JID)},
|
||||
%{"subid", SubId},
|
||||
{"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
|
||||
end,
|
||||
Action = fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
case lists:member(Owner, node_owners_call(Type, NodeId)) of
|
||||
@@ -2877,7 +2920,7 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp
|
||||
end,
|
||||
SHIMStanza = add_headers(Stanza, collection_shim(Node, Nodes)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router ! {route, From, jlib:make_jid(To), SHIMStanza}
|
||||
ejabberd_router:route(From, jlib:make_jid(To), SHIMStanza)
|
||||
end, LJIDs)
|
||||
end, NodesByJID),
|
||||
%% Handles implicit presence subscriptions
|
||||
@@ -2911,7 +2954,7 @@ broadcast_stanza(Host, Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyTyp
|
||||
end
|
||||
end, [], user_resources(U, S)),
|
||||
lists:foreach(fun(To) ->
|
||||
ejabberd_router ! {route, Sender, jlib:make_jid(To), Stanza}
|
||||
ejabberd_router:route(Sender, jlib:make_jid(To), Stanza)
|
||||
end, LJIDs)
|
||||
end)
|
||||
end, Contacts);
|
||||
@@ -3416,7 +3459,7 @@ features(Type) ->
|
||||
{'EXIT', {undef, _}} -> [];
|
||||
Result -> Result
|
||||
end.
|
||||
features(Host, []) ->
|
||||
features(Host, <<>>) ->
|
||||
lists:usort(lists:foldl(fun(Plugin, Acc) ->
|
||||
Acc ++ features(Plugin)
|
||||
end, [], plugins(Host)));
|
||||
@@ -3520,7 +3563,7 @@ transaction_retry(Host, Fun, Trans, Count) ->
|
||||
odbc_conn({_U, Host, _R})->
|
||||
Host;
|
||||
odbc_conn(Host) ->
|
||||
Host--"pubsub.". %% TODO, improve that for custom host
|
||||
lists:dropwhile(fun(A) -> A/=$. end, Host) -- ".".
|
||||
|
||||
%% escape value for database storage
|
||||
escape({_U, _H, _R}=JID)->
|
||||
|
||||
@@ -154,17 +154,21 @@ get_parentnodes_tree(Host, NodeID, _From) ->
|
||||
get_subnodes(Host, NodeID, _From) ->
|
||||
get_subnodes(Host, NodeID).
|
||||
|
||||
get_subnodes(Host, <<>>) ->
|
||||
get_subnodes_helper(Host, <<>>);
|
||||
get_subnodes(Host, NodeID) ->
|
||||
case find_node(Host, NodeID) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
_ ->
|
||||
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
|
||||
parents = Parents} = Node <- mnesia:table(pubsub_node),
|
||||
Host == NHost,
|
||||
lists:member(NodeID, Parents)]),
|
||||
qlc:e(Q)
|
||||
_ -> get_subnodes_helper(Host, NodeID)
|
||||
end.
|
||||
|
||||
get_subnodes_helper(Host, NodeID) ->
|
||||
Q = qlc:q([Node || #pubsub_node{nodeid = {NHost, _},
|
||||
parents = Parents} = Node <- mnesia:table(pubsub_node),
|
||||
Host == NHost,
|
||||
lists:member(NodeID, Parents)]),
|
||||
qlc:e(Q).
|
||||
|
||||
get_subnodes_tree(Host, NodeID, From) ->
|
||||
Pred = fun (NID, #pubsub_node{parents = Parents}) ->
|
||||
lists:member(NID, Parents)
|
||||
@@ -224,6 +228,8 @@ validate_parentage(_Key, _Owners, []) ->
|
||||
true;
|
||||
validate_parentage(Key, Owners, [[] | T]) ->
|
||||
validate_parentage(Key, Owners, T);
|
||||
validate_parentage(Key, Owners, [<<>> | T]) ->
|
||||
validate_parentage(Key, Owners, T);
|
||||
validate_parentage(Key, Owners, [ParentID | T]) ->
|
||||
case find_node(Key, ParentID) of
|
||||
false -> {error, ?ERR_ITEM_NOT_FOUND};
|
||||
|
||||
@@ -197,7 +197,7 @@ get_subnodes_tree(Host, Node) ->
|
||||
end, [], pubsub_node)
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node, Type, Owner, Options) -> ok | {error, Reason}
|
||||
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% NodeType = mod_pubsub:nodeType()
|
||||
|
||||
@@ -203,7 +203,7 @@ get_subnodes_tree(Host, Node) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
%% @spec (Host, Node, Type, Owner, Options) -> ok | {error, Reason}
|
||||
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok | {error, Reason}
|
||||
%% Host = mod_pubsub:host() | mod_pubsub:jid()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% NodeType = mod_pubsub:nodeType()
|
||||
|
||||
@@ -148,7 +148,7 @@ get_subnodes_tree(Host, Node, _From) ->
|
||||
get_subnodes_tree(_Host, _Node) ->
|
||||
[].
|
||||
|
||||
%% @spec (Host, Node, Type, Owner, Options) -> ok
|
||||
%% @spec (Host, Node, Type, Owner, Options, Parents) -> ok
|
||||
%% Host = mod_pubsub:host()
|
||||
%% Node = mod_pubsub:pubsubNode()
|
||||
%% Type = mod_pubsub:nodeType()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--- mod_pubsub.erl 2009-10-20 16:33:47.000000000 +0200
|
||||
+++ mod_pubsub_odbc.erl 2009-10-20 16:33:26.000000000 +0200
|
||||
--- mod_pubsub.erl 2009-12-07 00:17:20.000000000 +0100
|
||||
+++ mod_pubsub_odbc.erl 2009-12-07 00:17:59.000000000 +0100
|
||||
@@ -42,7 +42,7 @@
|
||||
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see
|
||||
%%% XEP-0060 section 12.18.
|
||||
@@ -284,7 +284,7 @@
|
||||
true ->
|
||||
% resource not concerned about that subscription
|
||||
ok
|
||||
@@ -825,10 +628,10 @@
|
||||
@@ -831,10 +634,10 @@
|
||||
{result, Subscriptions} = node_action(Host, PType, get_entity_subscriptions, [Host, Subscriber]),
|
||||
lists:foreach(fun
|
||||
({Node, subscribed, _, JID}) ->
|
||||
@@ -297,7 +297,7 @@
|
||||
true ->
|
||||
node_action(Host, Type, unsubscribe_node, [NodeId, Subscriber, JID, all]);
|
||||
false ->
|
||||
@@ -943,7 +746,8 @@
|
||||
@@ -949,7 +752,8 @@
|
||||
sub_el = SubEl} = IQ ->
|
||||
{xmlelement, _, QAttrs, _} = SubEl,
|
||||
Node = xml:get_attr_s("node", QAttrs),
|
||||
@@ -307,7 +307,7 @@
|
||||
{result, IQRes} ->
|
||||
jlib:iq_to_xml(
|
||||
IQ#iq{type = result,
|
||||
@@ -1048,7 +852,7 @@
|
||||
@@ -1066,7 +870,7 @@
|
||||
[] ->
|
||||
["leaf"]; %% No sub-nodes: it's a leaf node
|
||||
_ ->
|
||||
@@ -316,7 +316,7 @@
|
||||
{result, []} -> ["collection"];
|
||||
{result, _} -> ["leaf", "collection"];
|
||||
_ -> []
|
||||
@@ -1064,8 +868,9 @@
|
||||
@@ -1082,8 +886,9 @@
|
||||
[];
|
||||
true ->
|
||||
[{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []} |
|
||||
@@ -328,9 +328,9 @@
|
||||
end, features(Type))]
|
||||
end,
|
||||
%% TODO: add meta-data info (spec section 5.4)
|
||||
@@ -1093,21 +898,22 @@
|
||||
{xmlelement, "feature", [{"var", ?NS_DISCO_ITEMS}], []},
|
||||
@@ -1112,8 +917,9 @@
|
||||
{xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_COMMANDS}], []},
|
||||
{xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
|
||||
- lists:map(fun(Feature) ->
|
||||
- {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++Feature}], []}
|
||||
@@ -338,24 +338,36 @@
|
||||
+ ("rsm")-> {xmlelement, "feature", [{"var", ?NS_RSM}], []};
|
||||
+ (T) -> {xmlelement, "feature", [{"var", ?NS_PUBSUB++"#"++T}], []}
|
||||
end, features(Host, Node))};
|
||||
_ ->
|
||||
<<?NS_COMMANDS>> ->
|
||||
command_disco_info(Host, Node, From);
|
||||
@@ -1123,7 +929,7 @@
|
||||
node_disco_info(Host, Node, From)
|
||||
end.
|
||||
|
||||
-iq_disco_items(Host, [], From) ->
|
||||
+iq_disco_items(Host, [], From, _RSM) ->
|
||||
{result, lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}, type = Type}) ->
|
||||
{result, Path} = node_call(Type, node_to_path, [SubNode]),
|
||||
[Name|_] = lists:reverse(Path),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}|nodeAttr(SubNode)], []}
|
||||
end, tree_action(Host, get_subnodes, [Host, <<>>, From]))};
|
||||
case tree_action(Host, get_subnodes, [Host, <<>>, From]) of
|
||||
Nodes when is_list(Nodes) ->
|
||||
{result, lists:map(
|
||||
@@ -1135,14 +941,14 @@
|
||||
Other ->
|
||||
Other
|
||||
end;
|
||||
-iq_disco_items(Host, ?NS_COMMANDS, _From) ->
|
||||
+iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) ->
|
||||
%% TODO: support localization of this string
|
||||
CommandItems = [{xmlelement, "item", [{"jid", Host}, {"node", ?NS_PUBSUB_GET_PENDING}, {"name", "Get Pending"}], []}],
|
||||
{result, CommandItems};
|
||||
-iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From) ->
|
||||
+iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) ->
|
||||
CommandItems = [],
|
||||
{result, CommandItems};
|
||||
-iq_disco_items(Host, Item, From) ->
|
||||
+iq_disco_items(Host, Item, From, RSM) ->
|
||||
case string:tokens(Item, "!") of
|
||||
[_SNode, _ItemID] ->
|
||||
{result, []};
|
||||
@@ -1115,10 +921,10 @@
|
||||
@@ -1150,10 +956,10 @@
|
||||
Node = string_to_node(SNode),
|
||||
Action =
|
||||
fun(#pubsub_node{type = Type, id = NodeId}) ->
|
||||
@@ -369,7 +381,7 @@
|
||||
end,
|
||||
Nodes = lists:map(
|
||||
fun(#pubsub_node{nodeid = {_, SubNode}}) ->
|
||||
@@ -1129,7 +935,7 @@
|
||||
@@ -1166,7 +972,7 @@
|
||||
{result, Name} = node_call(Type, get_item_name, [Host, Node, RN]),
|
||||
{xmlelement, "item", [{"jid", Host}, {"name", Name}], []}
|
||||
end, NodeItems),
|
||||
@@ -378,7 +390,7 @@
|
||||
end,
|
||||
case transaction(Host, Node, Action, sync_dirty) of
|
||||
{result, {_, Result}} -> {result, Result};
|
||||
@@ -1258,7 +1064,8 @@
|
||||
@@ -1295,7 +1101,8 @@
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], xml:remove_cdata(Els)),
|
||||
@@ -388,7 +400,7 @@
|
||||
{get, "subscriptions"} ->
|
||||
get_subscriptions(Host, Node, From, Plugins);
|
||||
{get, "affiliations"} ->
|
||||
@@ -1281,7 +1088,9 @@
|
||||
@@ -1318,7 +1125,9 @@
|
||||
|
||||
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) ->
|
||||
{xmlelement, _, _, SubEls} = SubEl,
|
||||
@@ -399,7 +411,7 @@
|
||||
case Action of
|
||||
[{xmlelement, Name, Attrs, Els}] ->
|
||||
Node = string_to_node(xml:get_attr_s("node", Attrs)),
|
||||
@@ -1404,7 +1213,8 @@
|
||||
@@ -1448,7 +1257,8 @@
|
||||
_ -> []
|
||||
end
|
||||
end,
|
||||
@@ -409,7 +421,7 @@
|
||||
sync_dirty) of
|
||||
{result, Res} -> Res;
|
||||
Err -> Err
|
||||
@@ -1444,7 +1254,7 @@
|
||||
@@ -1487,7 +1297,7 @@
|
||||
|
||||
%%% authorization handling
|
||||
|
||||
@@ -418,16 +430,16 @@
|
||||
Lang = "en", %% TODO fix
|
||||
Stanza = {xmlelement, "message",
|
||||
[],
|
||||
@@ -1473,7 +1283,7 @@
|
||||
@@ -1516,7 +1326,7 @@
|
||||
[{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
|
||||
lists:foreach(fun(Owner) ->
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(Owner), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(Owner), Stanza)
|
||||
- end, Owners).
|
||||
+ end, node_owners(Host, Type, NodeId)).
|
||||
|
||||
find_authorization_response(Packet) ->
|
||||
{xmlelement, _Name, _Attrs, Els} = Packet,
|
||||
@@ -1537,8 +1347,8 @@
|
||||
@@ -1580,8 +1390,8 @@
|
||||
"true" -> true;
|
||||
_ -> false
|
||||
end,
|
||||
@@ -438,7 +450,7 @@
|
||||
{result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]),
|
||||
if
|
||||
not IsApprover ->
|
||||
@@ -1729,7 +1539,7 @@
|
||||
@@ -1772,7 +1582,7 @@
|
||||
Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "create", nodeAttr(Node),
|
||||
[]}]}],
|
||||
@@ -447,7 +459,7 @@
|
||||
{result, {Result, broadcast}} ->
|
||||
%%Lang = "en", %% TODO: fix
|
||||
%%OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
|
||||
@@ -1837,7 +1647,7 @@
|
||||
@@ -1880,7 +1690,7 @@
|
||||
%%<li>The node does not exist.</li>
|
||||
%%</ul>
|
||||
subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
@@ -456,7 +468,7 @@
|
||||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -1845,7 +1655,7 @@
|
||||
@@ -1888,7 +1698,7 @@
|
||||
error -> {"", "", ""};
|
||||
J -> jlib:jid_tolower(J)
|
||||
end,
|
||||
@@ -465,7 +477,7 @@
|
||||
Features = features(Type),
|
||||
SubscribeFeature = lists:member("subscribe", Features),
|
||||
OptionsFeature = lists:member("subscription-options", Features),
|
||||
@@ -1864,9 +1674,13 @@
|
||||
@@ -1907,9 +1717,13 @@
|
||||
{"", "", ""} ->
|
||||
{false, false};
|
||||
_ ->
|
||||
@@ -482,7 +494,7 @@
|
||||
end
|
||||
end,
|
||||
if
|
||||
@@ -2197,7 +2011,7 @@
|
||||
@@ -2240,7 +2054,7 @@
|
||||
%% <p>The permission are not checked in this function.</p>
|
||||
%% @todo We probably need to check that the user doing the query has the right
|
||||
%% to read the items.
|
||||
@@ -491,7 +503,7 @@
|
||||
MaxItems =
|
||||
if
|
||||
SMaxItems == "" -> get_max_items_node(Host);
|
||||
@@ -2236,11 +2050,11 @@
|
||||
@@ -2279,11 +2093,11 @@
|
||||
node_call(Type, get_items,
|
||||
[NodeId, From,
|
||||
AccessModel, PresenceSubscription, RosterGroup,
|
||||
@@ -505,7 +517,7 @@
|
||||
SendItems = case ItemIDs of
|
||||
[] ->
|
||||
Items;
|
||||
@@ -2253,7 +2067,8 @@
|
||||
@@ -2296,7 +2110,8 @@
|
||||
%% number of items sent to MaxItems:
|
||||
{result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}],
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
@@ -515,7 +527,7 @@
|
||||
Error ->
|
||||
Error
|
||||
end
|
||||
@@ -2285,16 +2100,27 @@
|
||||
@@ -2328,16 +2143,27 @@
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
%% @todo use cache-last-item feature
|
||||
send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
@@ -541,15 +553,15 @@
|
||||
+ event_stanza_with_delay(
|
||||
[{xmlelement, "items", nodeAttr(Node),
|
||||
- itemsEls([LastItem])}], ModifNow, ModifLjid),
|
||||
- ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza}
|
||||
- ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza)
|
||||
- end;
|
||||
+ itemsEls([LastItem])}], ModifNow, ModifLjid)
|
||||
+ end,
|
||||
+ ejabberd_router ! {route, service_jid(Host), jlib:make_jid(LJID), Stanza};
|
||||
+ ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of
|
||||
{result, []} ->
|
||||
@@ -2420,29 +2246,12 @@
|
||||
@@ -2463,29 +2289,12 @@
|
||||
error ->
|
||||
{error, ?ERR_BAD_REQUEST};
|
||||
_ ->
|
||||
@@ -582,7 +594,7 @@
|
||||
end, Entities),
|
||||
{result, []};
|
||||
_ ->
|
||||
@@ -2495,11 +2304,11 @@
|
||||
@@ -2538,11 +2347,11 @@
|
||||
end.
|
||||
|
||||
read_sub(Subscriber, Node, NodeID, SubID, Lang) ->
|
||||
@@ -596,7 +608,7 @@
|
||||
OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)},
|
||||
{"subid", SubID}|nodeAttr(Node)],
|
||||
[XdataEl]},
|
||||
@@ -2525,7 +2334,7 @@
|
||||
@@ -2568,7 +2377,7 @@
|
||||
end.
|
||||
|
||||
set_options_helper(Configuration, JID, NodeID, SubID, Type) ->
|
||||
@@ -605,7 +617,7 @@
|
||||
{result, GoodSubOpts} -> GoodSubOpts;
|
||||
_ -> invalid
|
||||
end,
|
||||
@@ -2554,7 +2363,7 @@
|
||||
@@ -2597,7 +2406,7 @@
|
||||
write_sub(_Subscriber, _NodeID, _SubID, invalid) ->
|
||||
{error, extended_error(?ERR_BAD_REQUEST, "invalid-options")};
|
||||
write_sub(Subscriber, NodeID, SubID, Options) ->
|
||||
@@ -614,9 +626,9 @@
|
||||
{error, notfound} ->
|
||||
{error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")};
|
||||
{result, _} ->
|
||||
@@ -2722,8 +2531,8 @@
|
||||
@@ -2765,8 +2574,8 @@
|
||||
{"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]},
|
||||
ejabberd_router ! {route, service_jid(Host), jlib:make_jid(JID), Stanza}
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza)
|
||||
end,
|
||||
- Action = fun(#pubsub_node{owners = Owners, type = Type, id = NodeId}) ->
|
||||
- case lists:member(Owner, Owners) of
|
||||
@@ -625,7 +637,7 @@
|
||||
true ->
|
||||
Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) ->
|
||||
|
||||
@@ -3007,7 +2816,7 @@
|
||||
@@ -3050,7 +2859,7 @@
|
||||
{Depth, [{N, get_node_subs(N)} || N <- Nodes]}
|
||||
end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))}
|
||||
end,
|
||||
@@ -634,7 +646,7 @@
|
||||
{result, CollSubs} -> CollSubs;
|
||||
_ -> []
|
||||
end.
|
||||
@@ -3021,9 +2830,9 @@
|
||||
@@ -3064,9 +2873,9 @@
|
||||
|
||||
get_options_for_subs(NodeID, Subs) ->
|
||||
lists:foldl(fun({JID, subscribed, SubID}, Acc) ->
|
||||
@@ -646,7 +658,7 @@
|
||||
_ -> Acc
|
||||
end;
|
||||
(_, Acc) ->
|
||||
@@ -3221,6 +3030,30 @@
|
||||
@@ -3264,6 +3073,30 @@
|
||||
Result
|
||||
end.
|
||||
|
||||
@@ -677,7 +689,7 @@
|
||||
%% @spec (Host, Options) -> MaxItems
|
||||
%% Host = host()
|
||||
%% Options = [Option]
|
||||
@@ -3607,7 +3440,13 @@
|
||||
@@ -3650,7 +3483,13 @@
|
||||
tree_action(Host, Function, Args) ->
|
||||
?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]),
|
||||
Fun = fun() -> tree_call(Host, Function, Args) end,
|
||||
@@ -692,7 +704,7 @@
|
||||
|
||||
%% @doc <p>node plugin call.</p>
|
||||
node_call(Type, Function, Args) ->
|
||||
@@ -3627,13 +3466,13 @@
|
||||
@@ -3670,13 +3509,13 @@
|
||||
|
||||
node_action(Host, Type, Function, Args) ->
|
||||
?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]),
|
||||
@@ -708,7 +720,7 @@
|
||||
case tree_call(Host, get_node, [Host, Node]) of
|
||||
N when is_record(N, pubsub_node) ->
|
||||
case Action(N) of
|
||||
@@ -3646,8 +3485,14 @@
|
||||
@@ -3689,8 +3528,14 @@
|
||||
end
|
||||
end, Trans).
|
||||
|
||||
@@ -725,7 +737,7 @@
|
||||
{result, Result} -> {result, Result};
|
||||
{error, Error} -> {error, Error};
|
||||
{atomic, {result, Result}} -> {result, Result};
|
||||
@@ -3655,6 +3500,15 @@
|
||||
@@ -3698,6 +3543,15 @@
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
@@ -741,14 +753,14 @@
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]),
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR};
|
||||
@@ -3663,6 +3517,17 @@
|
||||
@@ -3706,6 +3560,17 @@
|
||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||
end.
|
||||
|
||||
+odbc_conn({_U, Host, _R})->
|
||||
+ Host;
|
||||
+odbc_conn(Host) ->
|
||||
+ Host--"pubsub.". %% TODO, improve that for custom host
|
||||
+ lists:dropwhile(fun(A) -> A/=$. end, Host) -- ".".
|
||||
+
|
||||
+%% escape value for database storage
|
||||
+escape({_U, _H, _R}=JID)->
|
||||
|
||||
+16
-4
@@ -33,6 +33,7 @@
|
||||
stop/1,
|
||||
stream_feature_register/1,
|
||||
unauthenticated_iq_register/4,
|
||||
try_register/5,
|
||||
process_iq/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -224,7 +225,7 @@ try_register(User, Server, Password, Source, Lang) ->
|
||||
case ejabberd_auth:try_register(User, Server, Password) of
|
||||
{atomic, ok} ->
|
||||
send_welcome_message(JID),
|
||||
send_registration_notifications(JID),
|
||||
send_registration_notifications(JID, Source),
|
||||
ok;
|
||||
Error ->
|
||||
remove_timeout(Source),
|
||||
@@ -264,15 +265,17 @@ send_welcome_message(JID) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
send_registration_notifications(UJID) ->
|
||||
send_registration_notifications(UJID, Source) ->
|
||||
Host = UJID#jid.lserver,
|
||||
case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of
|
||||
[] -> ok;
|
||||
JIDs when is_list(JIDs) ->
|
||||
Body = lists:flatten(
|
||||
io_lib:format(
|
||||
"The user '~s' was just created on node ~w.",
|
||||
[jlib:jid_to_string(UJID), node()])),
|
||||
"[~s] The account ~s was registered from IP address ~s "
|
||||
"on node ~w using ~p.",
|
||||
[get_time_string(), jlib:jid_to_string(UJID),
|
||||
ip_to_string(Source), node(), ?MODULE])),
|
||||
lists:foreach(
|
||||
fun(S) ->
|
||||
case jlib:string_to_jid(S) of
|
||||
@@ -380,3 +383,12 @@ remove_timeout(Source) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source);
|
||||
ip_to_string(undefined) -> "undefined";
|
||||
ip_to_string(_) -> "unknown".
|
||||
|
||||
get_time_string() -> write_time(erlang:localtime()).
|
||||
%% Function copied from ejabberd_logger_h.erl and customized
|
||||
write_time({{Y,Mo,D},{H,Mi,S}}) ->
|
||||
io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
|
||||
[Y, Mo, D, H, Mi, S]).
|
||||
|
||||
@@ -124,11 +124,12 @@ get_user_roster(Items, US) ->
|
||||
SRUsers =
|
||||
lists:foldl(
|
||||
fun(Group, Acc1) ->
|
||||
GroupName = get_group_name(S, Group),
|
||||
lists:foldl(
|
||||
fun(User, Acc2) ->
|
||||
if User == US -> Acc2;
|
||||
true -> dict:append(User,
|
||||
get_group_name(S, Group),
|
||||
GroupName,
|
||||
Acc2)
|
||||
end
|
||||
end, Acc1, get_group_users(S, Group))
|
||||
|
||||
+1
-1
@@ -310,7 +310,7 @@ do_route(ServerHost, From, To, Packet) ->
|
||||
if
|
||||
(User /= "") or (Resource /= "") ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router ! {route, To, From, Err};
|
||||
ejabberd_router:route(To, From, Err);
|
||||
true ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* ejabberd, Copyright (C) 2002-2009 Process-one
|
||||
* 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
|
||||
|
||||
@@ -369,7 +369,7 @@ static int stringprep_erl_control(ErlDrvData drv_data,
|
||||
}
|
||||
last_ral = info & D1Mask;
|
||||
have_ral = have_ral || last_ral;
|
||||
have_l = info & D2Mask;
|
||||
have_l |= info & D2Mask;
|
||||
ADD_UCHAR(ruc);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
receive_headers/1,
|
||||
url_encode/1]).
|
||||
|
||||
%% Callbacks
|
||||
-export([init/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
@@ -79,7 +82,10 @@
|
||||
start(SockData, Opts) ->
|
||||
supervisor:start_child(ejabberd_http_sup, [SockData, Opts]).
|
||||
|
||||
start_link({SockMod, Socket}, Opts) ->
|
||||
start_link(SockData, Opts) ->
|
||||
{ok, proc_lib:spawn_link(ejabberd_http, init, [SockData, Opts])}.
|
||||
|
||||
init({SockMod, Socket}, Opts) ->
|
||||
TLSEnabled = lists:member(tls, Opts),
|
||||
TLSOpts = lists:filter(fun({certfile, _}) -> true;
|
||||
(_) -> false
|
||||
@@ -129,11 +135,10 @@ start_link({SockMod, Socket}, Opts) ->
|
||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||
|
||||
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
|
||||
{ok, proc_lib:spawn_link(ejabberd_http,
|
||||
receive_headers,
|
||||
[#state{sockmod = SockMod1,
|
||||
socket = Socket1,
|
||||
request_handlers = RequestHandlers}])}.
|
||||
State = #state{sockmod = SockMod1,
|
||||
socket = Socket1,
|
||||
request_handlers = RequestHandlers},
|
||||
receive_headers(State).
|
||||
|
||||
|
||||
become_controller(_Pid) ->
|
||||
|
||||
@@ -154,12 +154,32 @@ process([], #request{data = Data,
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
|
||||
HumanHTMLxmlel = get_human_html_xmlel(),
|
||||
{200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], HumanHTMLxmlel}
|
||||
end;
|
||||
process(_, _Request) ->
|
||||
{400, [], {xmlelement, "h1", [],
|
||||
[{xmlcdata, "400 Bad Request"}]}}.
|
||||
|
||||
%% Code copied from mod_http_bind.erl and customized
|
||||
get_human_html_xmlel() ->
|
||||
Heading = "ejabberd " ++ atom_to_list(?MODULE),
|
||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
|
||||
[{xmlelement, "head", [],
|
||||
[{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
|
||||
{xmlelement, "body", [],
|
||||
[{xmlelement, "h1", [], [{xmlcdata, Heading}]},
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata, "An implementation of "},
|
||||
{xmlelement, "a",
|
||||
[{"href", "http://xmpp.org/extensions/xep-0025.html"}],
|
||||
[{xmlcdata, "Jabber HTTP Polling (XEP-0025)"}]}]},
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata, "This web page is only informative. "
|
||||
"To use HTTP-Poll you need a Jabber/XMPP client that supports it."}
|
||||
]}
|
||||
]}]}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
@@ -1467,7 +1467,7 @@ list_users(Host, Query, Lang, URLFunc) ->
|
||||
FUsers =
|
||||
case length(SUsers) of
|
||||
N when N =< 100 ->
|
||||
[list_given_users(SUsers, "../", Lang, URLFunc)];
|
||||
[list_given_users(Host, SUsers, "../", Lang, URLFunc)];
|
||||
N ->
|
||||
NParts = trunc(math:sqrt(N * 0.618)) + 1,
|
||||
M = trunc(N / NParts) + 1,
|
||||
@@ -1542,9 +1542,10 @@ list_users_in_diapason(Host, Diap, Lang, URLFunc) ->
|
||||
N1 = list_to_integer(S1),
|
||||
N2 = list_to_integer(S2),
|
||||
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
|
||||
[list_given_users(Sub, "../../", Lang, URLFunc)].
|
||||
[list_given_users(Host, Sub, "../../", Lang, URLFunc)].
|
||||
|
||||
list_given_users(Users, Prefix, Lang, URLFunc) ->
|
||||
list_given_users(Host, Users, Prefix, Lang, URLFunc) ->
|
||||
ModLast = get_lastactivity_module(Host),
|
||||
?XE("table",
|
||||
[?XE("thead",
|
||||
[?XE("tr",
|
||||
@@ -1562,11 +1563,10 @@ list_given_users(Users, Prefix, Lang, URLFunc) ->
|
||||
FLast =
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
case mnesia:dirty_read({last_activity, US}) of
|
||||
[] ->
|
||||
case ModLast:get_last_info(User, Server) of
|
||||
not_found ->
|
||||
?T("Never");
|
||||
[E] ->
|
||||
Shift = element(3, E),
|
||||
{ok, Shift, _Status} ->
|
||||
TimeStamp = {Shift div 1000000,
|
||||
Shift rem 1000000,
|
||||
0},
|
||||
@@ -1591,6 +1591,18 @@ list_given_users(Users, Prefix, Lang, URLFunc) ->
|
||||
end, Users)
|
||||
)]).
|
||||
|
||||
get_lastactivity_module(Server) ->
|
||||
case lists:member(mod_last, gen_mod:loaded_modules(Server)) of
|
||||
true -> mod_last;
|
||||
_ -> mod_last_odbc
|
||||
end.
|
||||
|
||||
get_lastactivity_menuitem_list(Server) ->
|
||||
case get_lastactivity_module(Server) of
|
||||
mod_last -> [{"last-activity", "Last Activity"}];
|
||||
mod_last_odbc -> []
|
||||
end.
|
||||
|
||||
us_to_list({User, Server}) ->
|
||||
jlib:jid_to_string({User, Server, ""}).
|
||||
|
||||
@@ -1947,7 +1959,13 @@ get_node(global, Node, ["db"], Query, Lang) ->
|
||||
end;
|
||||
|
||||
get_node(global, Node, ["backup"], Query, Lang) ->
|
||||
HomeDir = re:replace(filename:nativename(os:cmd("echo $HOME")), "\n", "", [{return, list}]),
|
||||
HomeDirRaw = case {os:getenv("HOME"), os:type()} of
|
||||
{EnvHome, _} when is_list(EnvHome) -> EnvHome;
|
||||
{false, win32} -> "C:/";
|
||||
{false, {win32, _Osname}} -> "C:/";
|
||||
{false, _} -> "/tmp/"
|
||||
end,
|
||||
HomeDir = filename:nativename(HomeDirRaw),
|
||||
ResS = case node_backup_parse_query(Node, Query) of
|
||||
nothing -> [];
|
||||
ok -> [?XREST("Submitted")];
|
||||
@@ -2694,9 +2712,9 @@ make_host_menu(Host, HostNodeMenu, Lang, JID) ->
|
||||
HostFixed = [{"acls", "Access Control Lists"},
|
||||
{"access", "Access Rules"},
|
||||
{"users", "Users"},
|
||||
{"online-users", "Online Users"},
|
||||
{"last-activity", "Last Activity"},
|
||||
{"nodes", "Nodes", HostNodeMenu},
|
||||
{"online-users", "Online Users"}]
|
||||
++ get_lastactivity_menuitem_list(Host) ++
|
||||
[{"nodes", "Nodes", HostNodeMenu},
|
||||
{"stats", "Statistics"}]
|
||||
++ get_menu_items_hook({host, Host}, Lang),
|
||||
HostBasePath = url_to_path(HostBase),
|
||||
|
||||
@@ -69,7 +69,14 @@ process([], #request{method = 'POST',
|
||||
ejabberd_http_bind:process_request(Data, IP);
|
||||
process([], #request{method = 'GET',
|
||||
data = []}) ->
|
||||
Heading = "Ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ ?MOD_HTTP_BIND_VERSION,
|
||||
get_human_html_xmlel();
|
||||
process(_Path, _Request) ->
|
||||
?DEBUG("Bad Request: ~p", [_Request]),
|
||||
{400, [], {xmlelement, "h1", [],
|
||||
[{xmlcdata, "400 Bad Request"}]}}.
|
||||
|
||||
get_human_html_xmlel() ->
|
||||
Heading = "ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ ?MOD_HTTP_BIND_VERSION,
|
||||
{xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
|
||||
[{xmlelement, "head", [],
|
||||
[{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
|
||||
@@ -77,14 +84,14 @@ process([], #request{method = 'GET',
|
||||
[{xmlelement, "h1", [], [{xmlcdata, Heading}]},
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata, "An implementation of "},
|
||||
{xmlelement, "a", [{"href", "http://www.xmpp.org/extensions/xep-0206.html"}],
|
||||
[{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]}
|
||||
]}]};
|
||||
process(_Path, _Request) ->
|
||||
?DEBUG("Bad Request: ~p", [_Request]),
|
||||
{400, [], {xmlelement, "h1", [],
|
||||
[{xmlcdata, "400 Bad Request"}]}}.
|
||||
|
||||
{xmlelement, "a",
|
||||
[{"href", "http://xmpp.org/extensions/xep-0206.html"}],
|
||||
[{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]},
|
||||
{xmlelement, "p", [],
|
||||
[{xmlcdata, "This web page is only informative. "
|
||||
"To use HTTP-Bind you need a Jabber/XMPP client that supports it."}
|
||||
]}
|
||||
]}]}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% BEHAVIOUR CALLBACKS
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
-endif.
|
||||
|
||||
-record(state, {host, docroot, accesslog, accesslogfd, directory_indices,
|
||||
default_content_type, content_types = []}).
|
||||
custom_headers, default_content_type, content_types = []}).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_http_fileserver).
|
||||
|
||||
@@ -141,12 +141,13 @@ start_link(Host, Opts) ->
|
||||
init([Host, Opts]) ->
|
||||
try initialize(Host, Opts) of
|
||||
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
|
||||
DefaultContentType, ContentTypes} ->
|
||||
CustomHeaders, DefaultContentType, ContentTypes} ->
|
||||
{ok, #state{host = Host,
|
||||
accesslog = AccessLog,
|
||||
accesslogfd = AccessLogFD,
|
||||
docroot = DocRoot,
|
||||
directory_indices = DirectoryIndices,
|
||||
custom_headers = CustomHeaders,
|
||||
default_content_type = DefaultContentType,
|
||||
content_types = ContentTypes}}
|
||||
catch
|
||||
@@ -163,12 +164,13 @@ initialize(Host, Opts) ->
|
||||
AccessLog = gen_mod:get_opt(accesslog, Opts, undefined),
|
||||
AccessLogFD = try_open_log(AccessLog, Host),
|
||||
DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []),
|
||||
CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
|
||||
DefaultContentType = gen_mod:get_opt(default_content_type, Opts,
|
||||
?DEFAULT_CONTENT_TYPE),
|
||||
ContentTypes = build_list_content_types(gen_mod:get_opt(content_types, Opts, []), ?DEFAULT_CONTENT_TYPES),
|
||||
?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++
|
||||
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
|
||||
DefaultContentType, ContentTypes}.
|
||||
CustomHeaders, DefaultContentType, ContentTypes}.
|
||||
|
||||
%% @spec (AdminCTs::[CT], Default::[CT]) -> [CT]
|
||||
%% where CT = {Extension::string(), Value}
|
||||
@@ -231,6 +233,7 @@ try_open_log(FN, Host) ->
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call({serve, LocalPath}, _From, State) ->
|
||||
Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices,
|
||||
State#state.custom_headers,
|
||||
State#state.default_content_type, State#state.content_types),
|
||||
{reply, Reply, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
@@ -298,42 +301,44 @@ process(LocalPath, Request) ->
|
||||
ejabberd_web:error(not_found)
|
||||
end.
|
||||
|
||||
serve(LocalPath, DocRoot, DirectoryIndices, DefaultContentType, ContentTypes) ->
|
||||
serve(LocalPath, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType, ContentTypes) ->
|
||||
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
|
||||
case file:read_file_info(FileName) of
|
||||
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, eacces} -> ?HTTP_ERR_FORBIDDEN;
|
||||
{ok, #file_info{type = directory}} -> serve_index(FileName,
|
||||
DirectoryIndices,
|
||||
CustomHeaders,
|
||||
DefaultContentType,
|
||||
ContentTypes);
|
||||
{ok, FileInfo} -> serve_file(FileInfo, FileName,
|
||||
CustomHeaders,
|
||||
DefaultContentType,
|
||||
ContentTypes)
|
||||
end.
|
||||
|
||||
%% Troll through the directory indices attempting to find one which
|
||||
%% works, if none can be found, return a 404.
|
||||
serve_index(_FileName, [], _DefaultContentType, _ContentTypes) ->
|
||||
serve_index(_FileName, [], _CH, _DefaultContentType, _ContentTypes) ->
|
||||
?HTTP_ERR_FILE_NOT_FOUND;
|
||||
serve_index(FileName, [Index | T], DefaultContentType, ContentTypes) ->
|
||||
serve_index(FileName, [Index | T], CH, DefaultContentType, ContentTypes) ->
|
||||
IndexFileName = filename:join([FileName] ++ [Index]),
|
||||
case file:read_file_info(IndexFileName) of
|
||||
{error, _Error} -> serve_index(FileName, T, DefaultContentType, ContentTypes);
|
||||
{ok, #file_info{type = directory}} -> serve_index(FileName, T, DefaultContentType, ContentTypes);
|
||||
{ok, FileInfo} -> serve_file(FileInfo, IndexFileName, DefaultContentType, ContentTypes)
|
||||
{error, _Error} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes);
|
||||
{ok, #file_info{type = directory}} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes);
|
||||
{ok, FileInfo} -> serve_file(FileInfo, IndexFileName, CH, DefaultContentType, ContentTypes)
|
||||
end.
|
||||
|
||||
%% Assume the file exists if we got this far and attempt to read it in
|
||||
%% and serve it up.
|
||||
serve_file(FileInfo, FileName, DefaultContentType, ContentTypes) ->
|
||||
serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes) ->
|
||||
?DEBUG("Delivering: ~s", [FileName]),
|
||||
{ok, FileContents} = file:read_file(FileName),
|
||||
ContentType = content_type(FileName, DefaultContentType, ContentTypes),
|
||||
{FileInfo#file_info.size,
|
||||
200, [{"Server", "ejabberd"},
|
||||
{"Last-Modified", last_modified(FileInfo)},
|
||||
{"Content-Type", ContentType}],
|
||||
{"Content-Type", ContentType} | CustomHeaders],
|
||||
FileContents}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
+60
-25
@@ -2,34 +2,69 @@
|
||||
|
||||
INPUT=$1
|
||||
|
||||
WAVE1_AMPLITUDE=$((2 + $RANDOM % 5))
|
||||
WAVE1_LENGTH=$((50 + $RANDOM % 25))
|
||||
WAVE2_AMPLITUDE=$((2 + $RANDOM % 5))
|
||||
WAVE2_LENGTH=$((50 + $RANDOM % 25))
|
||||
WAVE3_AMPLITUDE=$((2 + $RANDOM % 5))
|
||||
WAVE3_LENGTH=$((50 + $RANDOM % 25))
|
||||
W1_LINE_START_Y=$((10 + $RANDOM % 40))
|
||||
W1_LINE_STOP_Y=$((10 + $RANDOM % 40))
|
||||
W2_LINE_START_Y=$((10 + $RANDOM % 40))
|
||||
W2_LINE_STOP_Y=$((10 + $RANDOM % 40))
|
||||
W3_LINE_START_Y=$((10 + $RANDOM % 40))
|
||||
W3_LINE_STOP_Y=$((10 + $RANDOM % 40))
|
||||
if test -n ${BASH_VERSION:-''} ; then
|
||||
get_random ()
|
||||
{
|
||||
R=$RANDOM
|
||||
}
|
||||
else
|
||||
for n in `od -A n -t u2 -N 48 /dev/urandom`; do RL="$RL$n "; done
|
||||
get_random ()
|
||||
{
|
||||
R=${RL%% *}
|
||||
RL=${RL#* }
|
||||
}
|
||||
fi
|
||||
|
||||
B1_LINE_START_Y=$(($RANDOM % 40))
|
||||
B1_LINE_STOP_Y=$(($RANDOM % 40))
|
||||
B2_LINE_START_Y=$(($RANDOM % 40))
|
||||
B2_LINE_STOP_Y=$(($RANDOM % 40))
|
||||
#B3_LINE_START_Y=$(($RANDOM % 40))
|
||||
#B3_LINE_STOP_Y=$(($RANDOM % 40))
|
||||
get_random
|
||||
WAVE1_AMPLITUDE=$((2 + $R % 5))
|
||||
get_random
|
||||
WAVE1_LENGTH=$((50 + $R % 25))
|
||||
get_random
|
||||
WAVE2_AMPLITUDE=$((2 + $R % 5))
|
||||
get_random
|
||||
WAVE2_LENGTH=$((50 + $R % 25))
|
||||
get_random
|
||||
WAVE3_AMPLITUDE=$((2 + $R % 5))
|
||||
get_random
|
||||
WAVE3_LENGTH=$((50 + $R % 25))
|
||||
get_random
|
||||
W1_LINE_START_Y=$((10 + $R % 40))
|
||||
get_random
|
||||
W1_LINE_STOP_Y=$((10 + $R % 40))
|
||||
get_random
|
||||
W2_LINE_START_Y=$((10 + $R % 40))
|
||||
get_random
|
||||
W2_LINE_STOP_Y=$((10 + $R % 40))
|
||||
get_random
|
||||
W3_LINE_START_Y=$((10 + $R % 40))
|
||||
get_random
|
||||
W3_LINE_STOP_Y=$((10 + $R % 40))
|
||||
|
||||
B1_LINE_START_X=$(($RANDOM % 20))
|
||||
B1_LINE_STOP_X=$((100 + $RANDOM % 40))
|
||||
B2_LINE_START_X=$(($RANDOM % 20))
|
||||
B2_LINE_STOP_X=$((100 + $RANDOM % 40))
|
||||
#B3_LINE_START_X=$(($RANDOM % 20))
|
||||
#B3_LINE_STOP_X=$((100 + $RANDOM % 40))
|
||||
get_random
|
||||
B1_LINE_START_Y=$(($R % 40))
|
||||
get_random
|
||||
B1_LINE_STOP_Y=$(($R % 40))
|
||||
get_random
|
||||
B2_LINE_START_Y=$(($R % 40))
|
||||
get_random
|
||||
B2_LINE_STOP_Y=$(($R % 40))
|
||||
#B3_LINE_START_Y=$(($R % 40))
|
||||
#B3_LINE_STOP_Y=$(($R % 40))
|
||||
|
||||
ROLL_X=$(($RANDOM % 40))
|
||||
get_random
|
||||
B1_LINE_START_X=$(($R % 20))
|
||||
get_random
|
||||
B1_LINE_STOP_X=$((100 + $R % 40))
|
||||
get_random
|
||||
B2_LINE_START_X=$(($R % 20))
|
||||
get_random
|
||||
B2_LINE_STOP_X=$((100 + $R % 40))
|
||||
#B3_LINE_START_X=$(($R % 20))
|
||||
#B3_LINE_STOP_X=$((100 + $R % 40))
|
||||
|
||||
get_random
|
||||
ROLL_X=$(($R % 40))
|
||||
|
||||
convert -size 180x60 xc:none -pointsize 40 \
|
||||
\( -clone 0 -fill white \
|
||||
|
||||
Reference in New Issue
Block a user