diff --git a/README b/README index fa80b3c77..950d342ee 100644 --- a/README +++ b/README @@ -20,7 +20,7 @@ To compile ejabberd you need: - 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.2 or higher. Optional. For import/export XEP-0227 files. + - exmpp 0.9.6 or higher. Optional. For import/export XEP-0227 files. 1. Compile and install on *nix systems diff --git a/doc/guide.html b/doc/guide.html index 8835f90ba..a38e9b293 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -356,7 +356,7 @@ Don’t use R14A or R14B because PAM library. Optional. For Pluggable Authentication Modules (PAM). See section 3.1.4.
  • GNU Iconv 1.8 or higher, for the IRC Transport (mod_irc). Optional. Not needed on systems with GNU Libc. See section 3.3.8.
  • ImageMagick’s Convert program. Optional. For CAPTCHA challenges. See section 3.1.8. -
  • exmpp 0.9.2 or higher. Optional. For import/export user data with XEP-0227 XML files. +
  • exmpp 0.9.6 or higher. Optional. For import/export user data with XEP-0227 XML files.
  • 2.4.2  Download Source Code

    Released versions of ejabberd are available in the ProcessOne ejabberd downloads page: @@ -2994,7 +2994,7 @@ The default option value is an empty list: []. This option sets the minimum informational entropy for passwords. The value Entropy is a number of bits of entropy. The recommended minimum is 32 bits. The default is 0, i.e. no checks are performed. -

    {welcome_message, Message}
    Set a welcome message that +
    {welcome_message, {Subject, Body}}
    Set a welcome message that is sent to each newly registered account. The first string is the subject, and the second string is the message body. In the body you can set a newline with the characters: \n diff --git a/doc/guide.tex b/doc/guide.tex index 1e2d02412..4bd7606b1 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -66,6 +66,7 @@ \newcommand{\module}[1]{\texttt{#1}} \newcommand{\modadhoc}{\module{mod\_adhoc}} \newcommand{\modannounce}{\module{mod\_announce}} +\newcommand{\modblocking}{\module{mod\_blocking}} \newcommand{\modcaps}{\module{mod\_caps}} \newcommand{\modconfigure}{\module{mod\_configure}} \newcommand{\moddisco}{\module{mod\_disco}} @@ -80,6 +81,7 @@ \newcommand{\modoffline}{\module{mod\_offline}} \newcommand{\modofflineodbc}{\module{mod\_offline\_odbc}} \newcommand{\modping}{\module{mod\_ping}} +\newcommand{\modprescounter}{\module{mod\_pres\_counter}} \newcommand{\modprivacy}{\module{mod\_privacy}} \newcommand{\modprivacyodbc}{\module{mod\_privacy\_odbc}} \newcommand{\modprivate}{\module{mod\_private}} @@ -321,7 +323,7 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need: \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.2 or higher. Optional. For import/export user data with \xepref{0227} XML files. +\item exmpp 0.9.6 or higher. Optional. For import/export user data with \xepref{0227} XML files. \end{itemize} \makesubsection{download}{Download Source Code} @@ -1030,7 +1032,7 @@ However, the c2s and s2s connections to the domain \term{example.com} use the fi \item Port 5269 listens for s2s connections with STARTTLS. The socket is set for IPv6 instead of IPv4. \item Port 3478 listens for STUN requests over UDP. \item Port 5280 listens for HTTP requests, and serves the HTTP Poll service. -\item Port 5281 listens for HTTP requests, and serves the Web Admin using HTTPS as explained in +\item Port 5281 listens for HTTP requests, using HTTPS to serve HTTP-Bind (BOSH) and the Web Admin as explained in section~\ref{webadmin}. The socket only listens connections to the IP address 127.0.0.1. \end{itemize} \begin{verbatim} @@ -1059,6 +1061,7 @@ However, the c2s and s2s connections to the domain \term{example.com} use the fi ]}, {{5281, "127.0.0.1"}, ejabberd_http, [ web_admin, + http_bind, tls, {certfile, "/etc/ejabberd/server.pem"}, ]} ] @@ -1635,11 +1638,14 @@ The configurable options are: \titem{\{captcha\_cmd, Path\}} Full path to a script that generates the image. The default value is an empty string: \term{""} - \titem{\{captcha\_host, Host\}} - Host part of the URL sent to the user. - You can include the port number. - The URL sent to the user is formed by: \term{http://Host/captcha/} - The default value is the first hostname configured. + \titem{\{captcha\_host, ProtocolHostPort\}} + ProtocolHostPort is a string with the host, and optionally the Protocol and Port number. + It must identify where ejabberd listens for CAPTCHA requests. + The URL sent to the user is formed by: \term{Protocol://Host:Port/captcha/} + The default value is: protocol \term{http}, the first hostname configured, and port \term{80}. + If you specify a port number that does not match exactly an ejabberd listener + (because you are using a reverse proxy or other port-forwarding tool), + then you must specify the transfer protocol, as seen in the example below. \end{description} Additionally, an \term{ejabberd\_http} listener must be enabled with the \term{captcha} option. @@ -1651,6 +1657,8 @@ Example configuration: {captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}. {captcha_host, "example.org:5280"}. +%% {captcha_host, "https://example.org:443"}. +%% {captcha_host, "http://example.com"}. {listen, [ @@ -1863,6 +1871,7 @@ The following LDAP servers are tested with \ejabberd{}: \item \footahref{http://www.microsoft.com/activedirectory/}{Active Directory} (see section~\ref{ad}) \item \footahref{http://www.openldap.org/}{OpenLDAP} +\item \footahref{http://www.communigate.com/}{CommuniGate Pro} \item Normally any LDAP compatible server should work; inform us about your success with a not-listed server so that we can list it here. \end{itemize} @@ -2526,6 +2535,7 @@ The following table lists all modules included in \ejabberd{}. \hline \hline \modadhoc{} & Ad-Hoc Commands (\xepref{0050}) & \\ \hline \ahrefloc{modannounce}{\modannounce{}} & Manage announcements & recommends \modadhoc{} \\ + \hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\ \hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\ \hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\ \hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\ @@ -2540,8 +2550,9 @@ The following table lists all modules included in \ejabberd{}. \hline \ahrefloc{modoffline}{\modoffline{}} & Offline message storage (\xepref{0160}) & \\ \hline \ahrefloc{modoffline}{\modofflineodbc{}} & Offline message storage (\xepref{0160}) & supported DB (*) \\ \hline \ahrefloc{modping}{\modping{}} & XMPP Ping and periodic keepalives (\xepref{0199}) & \\ - \hline \ahrefloc{modprivacy}{\modprivacy{}} & Blocking Communication (XMPP IM) & \\ - \hline \ahrefloc{modprivacy}{\modprivacyodbc{}} & Blocking Communication (XMPP IM) & supported DB (*) \\ + \hline \ahrefloc{modprescounter}{\modprivacy{}} & Detect presence subscription flood & \\ + \hline \ahrefloc{modprivacy}{\modprivacy{}} & Blocking Communication (\xepref{0016}) & \\ + \hline \ahrefloc{modprivacy}{\modprivacyodbc{}} & Blocking Communication (\xepref{0016}) & supported DB (*) \\ \hline \ahrefloc{modprivate}{\modprivate{}} & Private XML Storage (\xepref{0049}) & \\ \hline \ahrefloc{modprivate}{\modprivateodbc{}} & Private XML Storage (\xepref{0049}) & supported DB (*) \\ \hline \ahrefloc{modproxy}{\modproxy{}} & SOCKS5 Bytestreams (\xepref{0065}) & \\ @@ -2625,15 +2636,16 @@ The syntax is: Possible \term{Value} are: \begin{description} \titem{no\_queue} All queries of a namespace with this processing discipline are - processed immediately. This also means that no other packets can be processed + processed directly. This means that the XMPP connection that sends this IQ query gets blocked: + no other packets can be processed until this one has been completely processed. Hence this discipline is not recommended if the processing of a query can take a relatively long time. \titem{one\_queue} In this case a separate queue is created for the processing of IQ queries of a namespace with this discipline. In addition, the processing of this queue is done in parallel with that of other packets. This discipline is most recommended. - \titem{\{queues, N\}} N separate queues are created to process the - queries. The queries are thus process in parallel, but in a +\titem{\{queues, N\}} N separate queues are created to process the + queries. The queries are thus processed in parallel, but in a controlled way. \titem{parallel} For every packet with this discipline a separate Erlang process is spawned. Consequently, all these packets are processed in parallel. @@ -3564,6 +3576,39 @@ and if a client does not answer to the ping in less than 32 seconds, its connect ]}. \end{verbatim} +\makesubsection{modprescounter}{\modprescounter{}} +\ind{modules!\modprescounter{}} + +This module detects flood/spam in presence subscription stanza traffic. +If a user sends or receives more of those stanzas in a time interval, +the exceeding stanzas are silently dropped, and warning is logged. + +Configuration options: +\begin{description} + \titem{\{count, StanzaNumber\}}\ind{options!count} + The number of subscription presence stanzas + (subscribe, unsubscribe, subscribed, unsubscribed) + allowed for any direction (input or output) + per time interval. + Please note that two users subscribing to each other usually generate + 4 stanzas, so the recommended value is 4 or more. + The default value is: 5. + \titem{\{interval, Seconds\}}\ind{options!interval} + The time interval defined in seconds. + The default value is 60. +\end{description} + +This example enables the module, and allows up to 5 presence subscription stanzas +to be sent or received by the users in 60 seconds: +\begin{verbatim} +{modules, + [ + ... + {mod_pres_counter, [{count, 5}, {interval, 60}]}, + ... + ]}. +\end{verbatim} + \makesubsection{modprivacy}{\modprivacy{}} \ind{modules!\modprivacy{}}\ind{Blocking Communication}\ind{Privacy Rules}\ind{protocols!RFC 3921: XMPP IM} @@ -3788,10 +3833,11 @@ enables end users to use a \XMPP{} client to: Options: \begin{description} -\titem{\{access, AccessName\}} \ind{options!access}This option can be configured to specify - rules to restrict registration. If a rule returns `deny' on the requested - user name, registration for that user name is denied. (there are no - restrictions by default). +\titem{\{access, AccessName\}} \ind{options!access} + Specify rules to restrict what usernames can be registered and unregistered. + If a rule returns `deny' on the requested username, + registration and unregistration of that user name is denied. + There are no restrictions by default. \titem{\{access\_from, AccessName\}} \ind{options!access\_from}By default, \ejabberd{} doesn't allow to register new accounts from s2s or existing c2s sessions. You can change it by defining access rule in this option. Use with care: allowing registration @@ -3808,7 +3854,7 @@ Protect registrations with CAPTCHA (see section \ref{captcha}). The default is \ This option sets the minimum informational entropy for passwords. The value \term{Entropy} is a number of bits of entropy. The recommended minimum is 32 bits. The default is 0, i.e. no checks are performed. -\titem{\{welcome\_message, Message\}} \ind{options!welcomem}Set a welcome message that +\titem{\{welcome\_message, \{Subject, Body\}\}} \ind{options!welcomem}Set a welcome message that is sent to each newly registered account. The first string is the subject, and the second string is the message body. In the body you can set a newline with the characters: \verb|\n| @@ -4029,11 +4075,13 @@ has a unique identification and the following parameters: \item[Name] The name of the group, which will be displayed in the roster. \item[Description] The description of the group. This parameter does not affect anything. -\item[Members] A list of full JIDs of group members, entered one per line in +\item[Members] A list of JIDs of group members, entered one per line in the Web Admin. - To put as members all the registered users in the virtual hosts, - you can use the special directive: @all@. - Note that this directive is designed for a small server with just a few hundred users. + The special member directive \term{@all@} + represents all the registered users in the virtual host; + which is only recommended for a small server with just a few hundred users. + The special member directive \term{@online@} + represents the online users in the virtual host. \item[Displayed groups] A list of groups that will be in the rosters of this group's members. \end{description} @@ -4964,6 +5012,9 @@ The command line parameters: If using \term{-sname}, specify either this option or \term{ERL\_INETRC}. \titem{-kernel inet\_dist\_listen\_min 4200 inet\_dist\_listen\_min 4210} Define the first and last ports that \term{epmd} (section \ref{epmd}) can listen to. + \titem{-kernel inet\_dist\_use\_interface "\{ 127,0,0,1 \}"} + Define the IP address where this Erlang node listens for other nodes + connections (see section \ref{epmd}). \titem{-detached} Starts the Erlang system detached from the system console. Useful for running daemons and backgrounds processes. @@ -5376,6 +5427,12 @@ The Erlang command-line parameter used internally is, for example: \begin{verbatim} erl ... -kernel inet_dist_listen_min 4370 inet_dist_listen_max 4375 \end{verbatim} +It is also possible to configure in \term{ejabberdctl.cfg} +the network interface where the Erlang node will listen and accept connections. +The Erlang command-line parameter used internally is, for example: +\begin{verbatim} +erl ... -kernel inet_dist_use_interface "{127,0,0,1}" +\end{verbatim} \makesection{cookie}{Erlang Cookie} diff --git a/src/Makefile.in b/src/Makefile.in index 312983395..12da612a2 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -213,6 +213,7 @@ install: all sed -e "s*@ctlscriptpath@*$(SBINDIR)*" \ -e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \ > ejabberd.init + chmod 755 ejabberd.init # # Binary Erlang files install -d $(BEAMDIR) diff --git a/src/acl.erl b/src/acl.erl index e4eb57f8a..59173f004 100644 --- a/src/acl.erl +++ b/src/acl.erl @@ -5,7 +5,7 @@ %%% Created : 18 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/adhoc.erl b/src/adhoc.erl index 96669d237..cdddd5922 100644 --- a/src/adhoc.erl +++ b/src/adhoc.erl @@ -5,7 +5,7 @@ %%% Created : 31 Oct 2005 by Magnus Henoch %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/adhoc.hrl b/src/adhoc.hrl index 392955622..6ace9d54e 100644 --- a/src/adhoc.hrl +++ b/src/adhoc.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/cache_tab.erl b/src/cache_tab.erl index 7aef6cb4b..0e44f5d01 100644 --- a/src/cache_tab.erl +++ b/src/cache_tab.erl @@ -6,7 +6,7 @@ %%% Created : 29 Aug 2010 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/cache_tab_sup.erl b/src/cache_tab_sup.erl index a49593f5e..766b2892c 100644 --- a/src/cache_tab_sup.erl +++ b/src/cache_tab_sup.erl @@ -6,7 +6,7 @@ %%% Created : 30 Aug 2010 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/configure.erl b/src/configure.erl index a70302ffb..2b9753e50 100644 --- a/src/configure.erl +++ b/src/configure.erl @@ -5,7 +5,7 @@ %%% Created : 27 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index 121ef6df8..fd54ce44f 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -5,7 +5,7 @@ %%% Created : 8 Mar 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl index 2b3ba361b..3af9991d1 100644 --- a/src/cyrsasl_anonymous.erl +++ b/src/cyrsasl_anonymous.erl @@ -6,7 +6,7 @@ %%% Created : 23 Aug 2005 by Magnus Henoch %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 2a7ce2a69..40e7edfd8 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -5,7 +5,7 @@ %%% Created : 11 Mar 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index 129fb8b74..1b7108e31 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -5,7 +5,7 @@ %%% Created : 8 Mar 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example index a95689b35..92e78e32d 100644 --- a/src/ejabberd.cfg.example +++ b/src/ejabberd.cfg.example @@ -475,10 +475,14 @@ %%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}. %% -%% Host part of the URL sent to the user. +%% Host for the URL and port where ejabberd listens for CAPTCHA requests. %% %%{captcha_host, "example.org:5280"}. +%% +%% Limit CAPTCHA calls per minute for JID/IP to avoid DoS. +%% +%%{captcha_limit, 5}. %%%. ======= %%%' MODULES @@ -490,6 +494,7 @@ [ {mod_adhoc, []}, {mod_announce, [{access, announce}]}, % recommends mod_adhoc + {mod_blocking,[]}, % requires mod_privacy {mod_caps, []}, {mod_configure,[]}, % requires mod_adhoc {mod_disco, []}, @@ -511,6 +516,7 @@ %%{mod_muc_log,[]}, {mod_offline, [{access_max_user_messages, max_user_offline_messages}]}, {mod_ping, []}, + %%{mod_pres_counter,[{count, 5}, {interval, 60}]}, {mod_privacy, []}, {mod_private, []}, %%{mod_proxy65,[]}, @@ -545,9 +551,17 @@ %% %%{registration_watchers, ["admin1@example.org"]}, + %% + %% Only clients in the server machine can register accounts + %% {ip_access, [{allow, "127.0.0.0/8"}, {deny, "0.0.0.0/0"}]}, + %% + %% Local c2s or remote s2s users cannot register accounts + %% + %%{access_from, deny}, + {access, register} ]}, %%{mod_register_web, [ diff --git a/src/ejabberd.erl b/src/ejabberd.erl index c92509e7a..54efe4246 100644 --- a/src/ejabberd.erl +++ b/src/ejabberd.erl @@ -5,7 +5,7 @@ %%% Created : 16 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl index fcc2aeac7..faa503da4 100644 --- a/src/ejabberd.hrl +++ b/src/ejabberd.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd.init.template b/src/ejabberd.init.template index 6659557b6..ef691e4b3 100644 --- a/src/ejabberd.init.template +++ b/src/ejabberd.init.template @@ -1,6 +1,20 @@ #! /bin/sh + +### BEGIN INIT INFO +# Provides: ejabberd +# Required-Start: $remote_fs $network $named $time +# Required-Stop: $remote_fs $network $named $time +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Starts ejabberd jabber server +# Description: Starts ejabberd jabber server, an XMPP +# compliant server written in Erlang. +### END INIT INFO + +# chkconfig: 2345 90 10 +# description: ejabberd XMPP server + set -o errexit -set -o nounset DIR=@ctlscriptpath@ CTL="$DIR"/ejabberdctl @@ -32,14 +46,17 @@ case "$1" in su - $USER -c "$CTL stopped" echo "done." ;; - + status) + test -x "$CTL" || exit 0 + echo "Getting ejabberd status..." + su - $USER -c "$CTL status" + ;; force-reload|restart) "$0" stop "$0" start ;; - *) - echo "Usage: $0 {start|stop|restart|force-reload}" + echo "Usage: $0 {start|stop|restart|force-reload|status}" exit 1 esac diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index f3db0ca38..148bdccb9 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -5,7 +5,7 @@ %%% Created : 7 May 2006 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index ba7b2d319..2caef6e1f 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -5,7 +5,7 @@ %%% Created : 31 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -67,8 +67,9 @@ start(normal, _Args) -> %ejabberd_debug:eprof_start(), %ejabberd_debug:fprof_start(), maybe_add_nameservers(), + {ok, Pid} = ejabberd_cluster:start(), start_modules(), - ejabberd_cluster:announce(), + ejabberd_cluster:announce(Pid), ejabberd_node_groups:start(), ejabberd_listener:start_listeners(), ?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]), diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 613ec1e3b..23f9c4ba2 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -5,7 +5,7 @@ %%% Created : 23 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index 94f46eb79..bf378d370 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -5,7 +5,7 @@ %%% Created : 17 Feb 2006 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -155,6 +155,7 @@ register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) -> AuthModule = xml:get_attr_s(auth_module, Info), case AuthModule == ?MODULE of true -> + ejabberd_hooks:run(register_user, LServer, [LUser, LServer]), US = {LUser, LServer}, mnesia:async_dirty( fun() -> mnesia:write(#anonymous{us = US, sid=SID}) @@ -231,8 +232,8 @@ try_register(_User, _Server, _Password) -> dirty_get_registered_users() -> []. -get_vh_registered_users(_Server) -> - []. +get_vh_registered_users(Server) -> + [{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)]. %% Return password of permanent user or false for anonymous users diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 5415d003c..a426fe958 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -5,7 +5,7 @@ %%% Created : 12 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index 9cd89b724..39554bebc 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -5,7 +5,7 @@ %%% Created : 12 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index b96571366..c21811242 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -5,7 +5,7 @@ %%% Created : 12 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index a032a83ce..0c4eb65da 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 12 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index b99c7cbd3..974985f33 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -5,7 +5,7 @@ %%% Created : 5 Jul 2007 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index abae813f8..1f9c6397b 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -5,7 +5,7 @@ %%% Created : 16 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -194,7 +194,7 @@ stop(FsmRef) -> ?GEN_FSM:send_event(FsmRef, closed). migrate(FsmRef, Node, After) -> - ?GEN_FSM:send_all_state_event(FsmRef, {migrate, Node, After}). + erlang:send_after(After, FsmRef, {migrate, Node}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm @@ -231,6 +231,12 @@ init([{SockMod, Socket}, Opts, FSMLimitOpts]) -> (_) -> false end, Opts), TLSOpts = [verify_none | TLSOpts1], + Redirect = case lists:keysearch(redirect, 1, Opts) of + {value, {_, true}} -> + true; + _ -> + false + end, IP = case lists:keysearch(frontend_ip, 1, Opts) of {value, {_, IP1}} -> IP1; @@ -246,7 +252,7 @@ init([{SockMod, Socket}, Opts, FSMLimitOpts]) -> false -> Socket1 = if - TLSEnabled -> + TLSEnabled andalso SockMod /= ejabberd_frontend_socket -> SockMod:starttls(Socket, TLSOpts); true -> Socket @@ -265,8 +271,20 @@ init([{SockMod, Socket}, Opts, FSMLimitOpts]) -> access = Access, shaper = Shaper, ip = IP, + redirect = Redirect, fsm_limit_opts = FSMLimitOpts}, - {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT} + case get_jid_from_opts(Opts) of + {ok, #jid{user = U, server = Server, resource = R} = JID} -> + ?GEN_FSM:send_event(self(), open_session), + {ok, wait_for_session, StateData#state{ + user = U, + server = Server, + resource = R, + jid = JID, + lang = ""}}; + _ -> + {ok, wait_for_stream, StateData, ?C2S_OPEN_TIMEOUT} + end end; init([StateName, StateData, _FSMLimitOpts]) -> MRef = (StateData#state.sockmod):monitor(StateData#state.socket), @@ -282,6 +300,7 @@ init([StateName, StateData, _FSMLimitOpts]) -> El -> get_priority_from_presence(El) end, + ejabberd_sm:drop_session(StateData#state.sid), ejabberd_sm:open_session( SID, StateData#state.user, @@ -289,8 +308,11 @@ init([StateName, StateData, _FSMLimitOpts]) -> StateData#state.resource, Priority, Info), + %%ejabberd_sm:drop_session(StateData#state.sid), NewStateData = StateData#state{sid = SID, socket_monitor = MRef}, - {ok, StateName, NewStateData}; + StateData2 = change_reception(NewStateData, true), + StateData3 = start_keepalive_timer(StateData2), + {ok, StateName, StateData3}; true -> {ok, StateName, StateData#state{socket_monitor = MRef}} end. @@ -581,45 +603,55 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> "(~w) Accepted legacy authentication for ~s by ~p", [StateData#state.socket, jlib:jid_to_string(JID), AuthModule]), - SID = {now(), self()}, - Conn = get_conn_type(StateData), - %% Info = [{ip, StateData#state.ip}, {conn, Conn}, - %% {auth_module, AuthModule}], - Res1 = jlib:make_result_iq_reply(El), - Res = setelement(4, Res1, []), - send_element(StateData, Res), - %% ejabberd_sm:open_session( - %% SID, U, StateData#state.server, R, Info), - change_shaper(StateData, JID), - {Fs, Ts} = ejabberd_hooks:run_fold( - roster_get_subscription_lists, - StateData#state.server, - {[], []}, - [U, StateData#state.server]), - LJID = jlib:jid_tolower( - jlib:jid_remove_resource(JID)), - Fs1 = [LJID | Fs], - Ts1 = [LJID | Ts], - PrivList = - ejabberd_hooks:run_fold( - privacy_get_user_list, StateData#state.server, - #userlist{}, - [U, StateData#state.server]), - NewStateData = StateData#state{ - user = U, - resource = R, - jid = JID, - sid = SID, - conn = Conn, - auth_module = AuthModule, - pres_f = ?SETS:from_list(Fs1), - pres_t = ?SETS:from_list(Ts1), - privacy_list = PrivList}, - DebugFlag = ejabberd_hooks:run_fold(c2s_debug_start_hook, - NewStateData#state.server, - false, - [self(), NewStateData]), - maybe_migrate(session_established, NewStateData#state{debug=DebugFlag}); + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, + jlib:jid_to_string(JID), Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + SID = {now(), self()}, + Conn = get_conn_type(StateData), + Res1 = jlib:make_result_iq_reply(El), + Res = setelement(4, Res1, []), + send_element(StateData, Res), + change_shaper(StateData, JID), + {Fs, Ts} = ejabberd_hooks:run_fold( + roster_get_subscription_lists, + StateData#state.server, + {[], []}, + [U, StateData#state.server]), + LJID = jlib:jid_tolower( + jlib:jid_remove_resource(JID)), + Fs1 = [LJID | Fs], + Ts1 = [LJID | Ts], + PrivList = + ejabberd_hooks:run_fold( + privacy_get_user_list, + StateData#state.server, + #userlist{}, + [U, StateData#state.server]), + NewStateData = + StateData#state{ + user = U, + resource = R, + jid = JID, + sid = SID, + conn = Conn, + auth_module = AuthModule, + pres_f = ?SETS:from_list(Fs1), + pres_t = ?SETS:from_list(Ts1), + privacy_list = PrivList}, + DebugFlag = ejabberd_hooks:run_fold( + c2s_debug_start_hook, + NewStateData#state.server, + false, + [self(), NewStateData]), + maybe_migrate(session_established, + NewStateData#state{debug=DebugFlag}) + end; _ -> ?INFO_MSG( "(~w) Failed legacy authentication for ~s", @@ -711,21 +743,30 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> Mech, ClientIn) of {ok, Props} -> - catch (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), - U = xml:get_attr_s(username, Props), - AuthModule = xml:get_attr_s(auth_module, Props), - ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", - [StateData#state.socket, U, AuthModule]), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U }); + catch (StateData#state.sockmod):reset_stream( + StateData#state.socket), + U = xml:get_attr_s(username, Props), + AuthModule = xml:get_attr_s(auth_module, Props), + ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", + [StateData#state.socket, U, AuthModule]), + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, U, Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + send_element(StateData, + {xmlelement, "success", + [{"xmlns", ?NS_SASL}], []}), + fsm_next_state(wait_for_stream, + StateData#state{ + streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U }) + end; {continue, ServerOut, NewSASLState} -> send_element(StateData, {xmlelement, "challenge", @@ -865,20 +906,29 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) -> ClientIn) of {ok, Props} -> catch (StateData#state.sockmod):reset_stream( - StateData#state.socket), - send_element(StateData, - {xmlelement, "success", - [{"xmlns", ?NS_SASL}], []}), + StateData#state.socket), U = xml:get_attr_s(username, Props), AuthModule = xml:get_attr_s(auth_module, Props), ?INFO_MSG("(~w) Accepted authentication for ~s by ~p", [StateData#state.socket, U, AuthModule]), - fsm_next_state(wait_for_stream, - StateData#state{ - streamid = new_id(), - authenticated = true, - auth_module = AuthModule, - user = U}); + case need_redirect(StateData#state{user = U}) of + {true, Host} -> + ?INFO_MSG("(~w) Redirecting ~s to ~s", + [StateData#state.socket, U, Host]), + send_element(StateData, ?SERR_SEE_OTHER_HOST(Host)), + send_trailer(StateData), + {stop, normal, StateData}; + false -> + send_element(StateData, + {xmlelement, "success", + [{"xmlns", ?NS_SASL}], []}), + fsm_next_state(wait_for_stream, + StateData#state{ + streamid = new_id(), + authenticated = true, + auth_module = AuthModule, + user = U}) + end; {continue, ServerOut, NewSASLState} -> send_element(StateData, {xmlelement, "challenge", @@ -1045,6 +1095,11 @@ wait_for_session({xmlstreamelement, El}, StateData) -> fsm_next_state(wait_for_session, StateData) end; +wait_for_session(open_session, StateData) -> + El = {xmlelement, "iq", [{"type", "set"}, {"id", "session"}], + [{xmlelement, "session", [{"xmlns", ?NS_SESSION}], []}]}, + wait_for_session({xmlstreamelement, El}, StateData); + wait_for_session(timeout, StateData) -> {stop, normal, StateData}; @@ -1172,7 +1227,9 @@ session_established2(El, StateData) -> end; "iq" -> case jlib:iq_query_info(NewEl) of - #iq{xmlns = ?NS_PRIVACY} = IQ -> + #iq{xmlns = Xmlns} = IQ + when Xmlns == ?NS_PRIVACY; + Xmlns == ?NS_BLOCKING -> ejabberd_hooks:run( user_send_packet, Server, @@ -1230,9 +1287,6 @@ session_established2(El, StateData) -> %% {next_state, NextStateName, NextStateData, Timeout} | %% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- -handle_event({migrate, Node, After}, StateName, StateData) when Node /= node() -> - fsm_migrate(StateName, StateData, Node, After * 2); - handle_event({add_rosteritem, IJID, ISubscription}, StateName, StateData) -> NewStateData = roster_change(IJID, ISubscription, StateData), fsm_next_state(StateName, NewStateData); @@ -1433,6 +1487,9 @@ handle_info({route, From, To, Packet}, StateName, StateData) -> send_element(StateData, PrivPushEl), {false, Attrs, StateData#state{privacy_list = NewPL}} end; + [{blocking, What}] -> + route_blocking(What, StateData), + {false, Attrs, StateData}; _ -> {false, Attrs, StateData} end; @@ -1622,6 +1679,12 @@ handle_info({force_update_presence, LUser}, StateName, StateData end, {next_state, StateName, NewStateData}; +handle_info({migrate, Node}, StateName, StateData) -> + if Node /= node() -> + fsm_migrate(StateName, StateData, Node, 0); + true -> + fsm_next_state(StateName, StateData) + end; handle_info({broadcast, Type, From, Packet}, StateName, StateData) -> Recipients = ejabberd_hooks:run_fold( c2s_broadcast_recipients, StateData#state.server, @@ -1698,7 +1761,7 @@ terminate(_Reason, StateName, StateData) -> presence_broadcast( StateData, From, StateData#state.pres_i, Packet); rebinded -> - ejabberd_sm:close_session( + ejabberd_sm:close_migrated_session( StateData#state.sid, StateData#state.user, StateData#state.server, @@ -1969,24 +2032,12 @@ process_presence_probe(From, To, StateData) -> (E) -> [E] end, PresEls), - {xmlelement, "presence", PresAttrs, - [{xmlelement, "show", [], - [{xmlcdata, - StateData#state.oor_show}]}, - {xmlelement, "status", [], - [{xmlcdata, - StateData#state.oor_status}]}] - ++ PresEls1} + make_oor_presence( + StateData, PresAttrs, PresEls1) end end, Timestamp = StateData#state.pres_timestamp, - Packet1 = xml:append_subtags( - xml:remove_subtags( - Packet, "x", {"xmlns", ?NS_DELAY91}), - %% To is the one sending the presence (the target of the probe) - [jlib:timestamp_to_xml(Timestamp, utc, To, ""), - %% TODO: Delete the next line once XEP-0091 is Obsolete - jlib:timestamp_to_xml(Timestamp)]), + Packet1 = maybe_add_delay(Packet, utc, To, "", Timestamp), case ejabberd_hooks:run_fold( privacy_check_packet, StateData#state.server, allow, @@ -2422,7 +2473,7 @@ resend_offline_messages(StateData) -> Rs when is_list(Rs) -> lists:foreach( fun({route, - From, To, {xmlelement, Name, Attrs, Els} = Packet}) -> + From, To, {xmlelement, _Name, _Attrs, _Els} = Packet}) -> Pass = case privacy_check_packet(StateData, From, To, Packet, in) of allow -> true; @@ -2431,11 +2482,11 @@ resend_offline_messages(StateData) -> end, if Pass -> - Attrs2 = jlib:replace_from_to_attrs( - jlib:jid_to_string(From), - jlib:jid_to_string(To), - Attrs), - FixedPacket = {xmlelement, Name, Attrs2, Els}, + %% Attrs2 = jlib:replace_from_to_attrs( + %% jlib:jid_to_string(From), + %% jlib:jid_to_string(To), + %% Attrs), + %% FixedPacket = {xmlelement, Name, Attrs2, Els}, %% Use route instead of send_element to go through standard workflow ejabberd_router:route(From, To, Packet); %% send_element(StateData, FixedPacket), @@ -2526,23 +2577,25 @@ peerip(SockMod, Socket) -> maybe_migrate(StateName, StateData) -> PackedStateData = pack(StateData), - case ejabberd_cluster:get_node({StateData#state.user, - StateData#state.server}) of + #state{user = U, server = S, resource = R, sid = SID} = StateData, + case ejabberd_cluster:get_node({jlib:nodeprep(U), jlib:nameprep(S)}) of Node when Node == node() -> Conn = get_conn_type(StateData), Info = [{ip, StateData#state.ip}, {conn, Conn}, {auth_module, StateData#state.auth_module}], - #state{user = U, server = S, resource = R, sid = SID} = StateData, - ejabberd_sm:open_session(SID, U, S, R, Info), + Presence = StateData#state.pres_last, + Priority = + case Presence of + undefined -> + undefined; + _ -> + get_priority_from_presence(Presence) + end, + ejabberd_sm:open_session(SID, U, S, R, Priority, Info), + StateData2 = change_reception(PackedStateData, true), + StateData3 = start_keepalive_timer(StateData2), erlang:garbage_collect(), - case ejabberd_cluster:get_node_new({U, S}) of - Node -> - ok; - NewNode -> - After = ejabberd_cluster:rehash_timeout(), - migrate(self(), NewNode, After) - end, - fsm_next_state(StateName, PackedStateData); + fsm_next_state(StateName, StateData3); Node -> fsm_migrate(StateName, PackedStateData, Node, 0) end. @@ -2627,12 +2680,7 @@ change_reception(#state{reception = true} = StateData, false) -> "" -> ok; _ -> - Packet = - {xmlelement, "presence", [], - [{xmlelement, "show", [], - [{xmlcdata, StateData#state.oor_show}]}, - {xmlelement, "status", [], - [{xmlcdata, StateData#state.oor_status}]}]}, + Packet = make_oor_presence(StateData), update_priority(0, Packet, StateData#state{reception = false}), presence_broadcast_to_trusted( StateData, @@ -2775,7 +2823,14 @@ send_out_of_reception_message(StateData, From, To, CBody = utf8_cut(Body, 100), case StateData#state.oor_send_from of jid -> SFrom ++ ": " ++ CBody; - username -> BFrom#jid.user ++ ": " ++ CBody; + username -> + UnescapedFrom = + unescape(BFrom#jid.user), + UnescapedFrom ++ ": " ++ CBody; + name -> + Name = get_roster_name( + StateData, BFrom), + Name ++ ": " ++ CBody; _ -> CBody end; true -> @@ -2810,6 +2865,23 @@ send_out_of_reception_message(StateData, From, To, send_out_of_reception_message(StateData, _From, _To, _Packet) -> StateData. +make_oor_presence(StateData) -> + make_oor_presence(StateData, [], []). + +make_oor_presence(StateData, PresenceAttrs, PresenceEls) -> + ShowEl = + case StateData#state.oor_show of + "available" -> []; + _ -> + [{xmlelement, "show", [], + [{xmlcdata, StateData#state.oor_show}]}] + end, + {xmlelement, "presence", PresenceAttrs, + ShowEl ++ + [{xmlelement, "status", [], + [{xmlcdata, StateData#state.oor_status}]}] + ++ PresenceEls}. + utf8_cut(S, Bytes) -> utf8_cut(S, [], [], Bytes + 1). @@ -2825,6 +2897,47 @@ utf8_cut([C | S], Cur, Prev, Bytes) -> utf8_cut(S, [C | Cur], Cur, Bytes - 1) end. +-include("mod_roster.hrl"). + +get_roster_name(StateData, JID) -> + User = StateData#state.user, + Server = StateData#state.server, + RosterItems = ejabberd_hooks:run_fold( + roster_get, Server, [], [{User, Server}]), + JUser = JID#jid.luser, + JServer = JID#jid.lserver, + Item = + lists:foldl( + fun(_, Res = #roster{}) -> + Res; + (I, false) -> + case I#roster.jid of + {JUser, JServer, _} -> + I; + _ -> + false + end + end, false, RosterItems), + case Item of + false -> + unescape(JID#jid.user); + #roster{} -> + Item#roster.name + end. + +unescape("") -> ""; +unescape("\\20" ++ S) -> [$\s | unescape(S)]; +unescape("\\22" ++ S) -> [$" | unescape(S)]; +unescape("\\26" ++ S) -> [$& | unescape(S)]; +unescape("\\27" ++ S) -> [$' | unescape(S)]; +unescape("\\2f" ++ S) -> [$/ | unescape(S)]; +unescape("\\3a" ++ S) -> [$: | unescape(S)]; +unescape("\\3c" ++ S) -> [$< | unescape(S)]; +unescape("\\3e" ++ S) -> [$> | unescape(S)]; +unescape("\\40" ++ S) -> [$@ | unescape(S)]; +unescape("\\5c" ++ S) -> [$\\ | unescape(S)]; +unescape([C | S]) -> [C | unescape(S)]. + cancel_timer(Timer) -> erlang:cancel_timer(Timer), @@ -2873,17 +2986,13 @@ enqueue(StateData, From, To, Packet) -> StateData#state{pres_queue = NewQueue} end; true -> - CleanPacket = xml:remove_subtags( - xml:remove_subtags(Packet, "x", {"xmlns", ?NS_P1_PUSHED}), - "x", {"xmlns", ?NS_DELAY91}), + CleanPacket = xml:remove_subtags(Packet, "x", {"xmlns", ?NS_P1_PUSHED}), Packet2 = case CleanPacket of - {xmlelement, "message" = Name, Attrs, Els} -> - {xmlelement, Name, Attrs, - Els ++ - [jlib:timestamp_to_xml( - calendar:now_to_universal_time(now())), - {xmlelement, "x", [{"xmlns", ?NS_P1_PUSHED}], []}]}; + {xmlelement, "message", _, _} -> + xml:append_subtags( + maybe_add_delay(CleanPacket, utc, To, ""), + [{xmlelement, "x", [{"xmlns", ?NS_P1_PUSHED}], []}]); _ -> Packet end, @@ -3025,15 +3134,6 @@ rebind(StateData, JID, StreamID) -> [StateData#state.socket, jlib:jid_to_string(JID)]), SID = {now(), self()}, - Conn = get_conn_type(NewStateData), - Info = [{ip, StateData#state.ip}, {conn, Conn}, - {auth_module, NewStateData#state.auth_module}], - ejabberd_sm:open_session( - SID, - NewStateData#state.user, - NewStateData#state.server, - NewStateData#state.resource, - Info), StateData2 = NewStateData#state{ socket = StateData#state.socket, @@ -3044,22 +3144,11 @@ rebind(StateData, JID, StreamID) -> keepalive_timer = StateData#state.keepalive_timer, ack_timer = undefined }, - Presence = StateData2#state.pres_last, - case Presence of - undefined -> - ok; - _ -> - NewPriority = get_priority_from_presence(Presence), - update_priority(NewPriority, Presence, StateData2) - end, send_element(StateData2, {xmlelement, "rebind", [{"xmlns", ?NS_P1_REBIND}], []}), - StateData3 = change_reception(StateData2, true), - StateData4 = start_keepalive_timer(StateData3), - fsm_next_state(session_established, - StateData4) + maybe_migrate(session_established, StateData2) after 1000 -> send_element(StateData, {xmlelement, "failure", @@ -3221,6 +3310,33 @@ check_x_attachment1([El | Els]) -> check_x_attachment1(Els) end. +%% TODO: Delete XEP-0091 stuff once it is Obsolete +maybe_add_delay(El, TZ, From, Desc) -> + maybe_add_delay(El, TZ, From, Desc, calendar:now_to_universal_time(now())). +maybe_add_delay({xmlelement, _, _, Els} = El, TZ, From, Desc, TimeStamp) -> + HasOldTS = lists:any( + fun({xmlelement, "x", Attrs, _}) -> + xml:get_attr_s("xmlns", Attrs) == ?NS_DELAY91; + (_) -> + false + end, Els), + HasNewTS = lists:any( + fun({xmlelement, "delay", Attrs, _}) -> + xml:get_attr_s("xmlns", Attrs) == ?NS_DELAY; + (_) -> + false + end, Els), + El1 = if not HasOldTS -> + xml:append_subtags(El, [jlib:timestamp_to_xml(TimeStamp)]); + true -> + El + end, + if not HasNewTS -> + xml:append_subtags( + El1, [jlib:timestamp_to_xml(TimeStamp, TZ, From, Desc)]); + true -> + El1 + end. send_from(El) -> %% First test previous version attribute: @@ -3233,6 +3349,7 @@ send_from(El) -> case xml:get_path_s(El, [{elem, "body"}, {attr, "from"}]) of "jid" -> jid; "username" -> username; + "name" -> name; "none" -> none; _ -> jid end @@ -3260,6 +3377,51 @@ bounce_messages() -> ok end. +%%%---------------------------------------------------------------------- +%%% XEP-0191 +%%%---------------------------------------------------------------------- + +route_blocking(What, StateData) -> + SubEl = + case What of + {block, JIDs} -> + {xmlelement, "block", + [{"xmlns", ?NS_BLOCKING}], + lists:map( + fun(JID) -> + {xmlelement, "item", + [{"jid", jlib:jid_to_string(JID)}], + []} + end, JIDs)}; + {unblock, JIDs} -> + {xmlelement, "unblock", + [{"xmlns", ?NS_BLOCKING}], + lists:map( + fun(JID) -> + {xmlelement, "item", + [{"jid", jlib:jid_to_string(JID)}], + []} + end, JIDs)}; + unblock_all -> + {xmlelement, "unblock", + [{"xmlns", ?NS_BLOCKING}], []} + end, + PrivPushIQ = + #iq{type = set, xmlns = ?NS_BLOCKING, + id = "push", + sub_el = [SubEl]}, + PrivPushEl = + jlib:replace_from_to( + jlib:jid_remove_resource( + StateData#state.jid), + StateData#state.jid, + jlib:iq_to_xml(PrivPushIQ)), + send_element(StateData, PrivPushEl), + %% No need to replace active privacy list here, + %% blocking pushes are always accompanied by + %% Privacy List pushes + ok. + %%%---------------------------------------------------------------------- %%% JID Set memory footprint reduction code %%%---------------------------------------------------------------------- @@ -3333,3 +3495,39 @@ flash_policy_string() -> ++ ToPortsString ++ "\"/>\n" "\n\0". + +need_redirect(#state{redirect = true, user = User, server = Server}) -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + case ejabberd_cluster:get_node({LUser, LServer}) of + Node when node() == Node -> + false; + Node -> + case rpc:call(Node, ejabberd_config, + get_local_option, [hostname], 5000) of + Host when is_list(Host) -> + {true, Host}; + _ -> + false + end + end; +need_redirect(_) -> + false. + +get_jid_from_opts(Opts) -> + case lists:keysearch(jid, 1, Opts) of + {value, {_, JIDValue}} -> + JID = case JIDValue of + {_U, _S, _R} -> + jlib:make_jid(JIDValue); + _ when is_binary(JIDValue) -> + jlib:string_to_jid(binary_to_list(JIDValue)); + _ when is_list(JIDValue) -> + jlib:string_to_jid(JIDValue); + _ -> + JIDValue + end, + {ok, JID}; + _ -> + error + end. diff --git a/src/ejabberd_c2s.hrl b/src/ejabberd_c2s.hrl index 9af6b02d5..7889c0de5 100644 --- a/src/ejabberd_c2s.hrl +++ b/src/ejabberd_c2s.hrl @@ -57,6 +57,7 @@ conn = unknown, auth_module = unknown, ip, + redirect = false, aux_fields = [], fsm_limit_opts, lang, diff --git a/src/ejabberd_c2s_config.erl b/src/ejabberd_c2s_config.erl index 1777163c6..929be254f 100644 --- a/src/ejabberd_c2s_config.erl +++ b/src/ejabberd_c2s_config.erl @@ -6,7 +6,7 @@ %%% Created : 2 Nov 2007 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index 61fed71c3..da9785d15 100644 --- a/src/ejabberd_captcha.erl +++ b/src/ejabberd_captcha.erl @@ -5,7 +5,7 @@ %%% Created : 26 Apr 2008 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -35,9 +35,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([create_captcha/5, build_captcha_html/2, check_captcha/2, +-export([create_captcha/6, build_captcha_html/2, check_captcha/2, process_reply/1, process/2, is_feature_available/0, - create_captcha_x/4, create_captcha_x/5]). + create_captcha_x/5, create_captcha_x/6]). -include("jlib.hrl"). -include("ejabberd.hrl"). @@ -50,8 +50,9 @@ -define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")). -define(CAPTCHA_LIFETIME, 120000). % two minutes -define(RPC_TIMEOUT, 5000). +-define(LIMIT_PERIOD, 60*1000*1000). % one minute --record(state, {}). +-record(state, {limits = treap:empty()}). -record(captcha, {id, pid, key, tref, args}). %%==================================================================== @@ -64,10 +65,10 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -create_captcha(SID, From, To, Lang, Args) +create_captcha(SID, From, To, Lang, Limiter, Args) when is_list(Lang), is_list(SID), is_record(From, jid), is_record(To, jid) -> - case create_image() of + case create_image(Limiter) of {ok, Type, Key, Image} -> Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(), B64Image = jlib:encode_base64(binary_to_list(Image)), @@ -96,18 +97,18 @@ create_captcha(SID, From, To, Lang, Args) OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}], [{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]}, Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}), - ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key, + ets:insert(captcha, #captcha{id=Id, pid=self(), key=Key, tref=Tref, args=Args}), {ok, Id, [Body, OOB, Captcha, Data]}; - _Err -> - error + Err -> + Err end. -create_captcha_x(SID, To, Lang, HeadEls) -> - create_captcha_x(SID, To, Lang, HeadEls, []). +create_captcha_x(SID, To, Lang, Limiter, HeadEls) -> + create_captcha_x(SID, To, Lang, Limiter, HeadEls, []). -create_captcha_x(SID, To, Lang, HeadEls, TailEls) -> - case create_image() of +create_captcha_x(SID, To, Lang, Limiter, HeadEls, TailEls) -> + case create_image(Limiter) of {ok, Type, Key, Image} -> Id = randoms:get_string() ++ "-" ++ ejabberd_cluster:node_id(), B64Image = jlib:encode_base64(binary_to_list(Image)), @@ -144,8 +145,8 @@ create_captcha_x(SID, To, Lang, HeadEls, TailEls) -> Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}), ets:insert(captcha, #captcha{id=Id, key=Key, tref=Tref}), {ok, [Captcha, Data]}; - _ -> - error + Err -> + Err end. %% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found @@ -242,16 +243,19 @@ process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) -> ejabberd_web:error(not_found) end; -process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) -> +process(_Handlers, #request{method='GET', path=[_, Id, "image"], ip = IP}) -> + {Addr, _Port} = IP, case lookup_captcha(Id) of {ok, #captcha{key=Key}} -> - case create_image(Key) of + case create_image(Addr, Key) of {ok, Type, _, Img} -> {200, [{"Content-Type", Type}, {"Cache-Control", "no-cache"}, {"Last-Modified", httpd_util:rfc1123_date()}], Img}; + {error, limit} -> + ejabberd_web:error(not_allowed); _ -> ejabberd_web:error(not_found) end; @@ -288,6 +292,20 @@ init([]) -> check_captcha_setup(), {ok, #state{}}. +handle_call({is_limited, Limiter, RateLimit}, _From, State) -> + NowPriority = now_priority(), + CleanPriority = NowPriority + ?LIMIT_PERIOD, + Limits = clean_treap(State#state.limits, CleanPriority), + case treap:lookup(Limiter, Limits) of + {ok, _, Rate} when Rate >= RateLimit -> + {reply, true, State#state{limits = Limits}}; + {ok, Priority, Rate} -> + NewLimits = treap:insert(Limiter, Priority, Rate+1, Limits), + {reply, false, State#state{limits = NewLimits}}; + _ -> + NewLimits = treap:insert(Limiter, NowPriority, 1, Limits), + {reply, false, State#state{limits = NewLimits}} + end; handle_call(_Request, _From, State) -> {reply, bad_request, State}. @@ -329,11 +347,22 @@ code_change(_OldVsn, State, _Extra) -> %% Reason = atom() %%-------------------------------------------------------------------- create_image() -> + create_image(undefined). + +create_image(Limiter) -> %% Six numbers from 1 to 9. Key = string:substr(randoms:get_string(), 1, 6), - create_image(Key). + create_image(Limiter, Key). -create_image(Key) -> +create_image(Limiter, Key) -> + case is_limited(Limiter) of + true -> + {error, limit}; + false -> + do_create_image(Key) + end. + +do_create_image(Key) -> FileName = get_prog_name(), Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])), case cmd(Cmd) of @@ -363,20 +392,82 @@ get_prog_name() -> case ejabberd_config:get_local_option(captcha_cmd) of FileName when is_list(FileName) -> FileName; - _ -> + Value when (Value == undefined) or (Value == "") -> ?DEBUG("The option captcha_cmd is not configured, but some " "module wants to use the CAPTCHA feature.", []), - throw({error, option_not_configured_captcha_cmd}) + false end. get_url(Str) -> - case ejabberd_config:get_local_option(captcha_host) of - Host when is_list(Host) -> + CaptchaHost = ejabberd_config:get_local_option(captcha_host), + case string:tokens(CaptchaHost, ":") of + [Host] -> "http://" ++ Host ++ "/captcha/" ++ Str; + ["http"++_ = TransferProt, Host] -> + TransferProt ++ ":" ++ Host ++ "/captcha/" ++ Str; + [Host, PortString] -> + TransferProt = atom_to_list(get_transfer_protocol(PortString)), + TransferProt ++ "://" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str; + [TransferProt, Host, PortString] -> + TransferProt ++ ":" ++ Host ++ ":" ++ PortString ++ "/captcha/" ++ Str; _ -> "http://" ++ ?MYNAME ++ "/captcha/" ++ Str end. +get_transfer_protocol(PortString) -> + PortNumber = list_to_integer(PortString), + PortListeners = get_port_listeners(PortNumber), + get_captcha_transfer_protocol(PortListeners). + +get_port_listeners(PortNumber) -> + AllListeners = ejabberd_config:get_local_option(listen), + lists:filter( + fun({{Port, _Ip, _Netp}, _Module1, _Opts1}) when Port == PortNumber -> + true; + (_) -> + false + end, + AllListeners). + +get_captcha_transfer_protocol([]) -> + throw("The port number mentioned in captcha_host is not " + "a ejabberd_http listener with 'captcha' option. " + "Change the port number or specify http:// in that option."); +get_captcha_transfer_protocol([{{_Port, _Ip, tcp}, ejabberd_http, Opts} + | Listeners]) -> + case lists:member(captcha, Opts) of + true -> + case lists:member(tls, Opts) of + true -> + https; + false -> + http + end; + false -> + get_captcha_transfer_protocol(Listeners) + end; +get_captcha_transfer_protocol([_ | Listeners]) -> + get_captcha_transfer_protocol(Listeners). + +is_limited(undefined) -> + false; +is_limited(Limiter) -> + case ejabberd_config:get_local_option(captcha_limit) of + Int when is_integer(Int), Int > 0 -> + case catch gen_server:call(?MODULE, {is_limited, Limiter, Int}, + 5000) of + true -> + true; + false -> + false; + Err -> + ?ERROR_MSG("Call failed: ~p", [Err]), + false + end; + _ -> + false + end. + %%-------------------------------------------------------------------- %% Function: cmd(Cmd) -> Data | {error, Reason} %% Cmd = string() @@ -425,28 +516,23 @@ return(Port, TRef, Result) -> catch port_close(Port), Result. -is_feature_enabled() -> - try get_prog_name() of - Prog when is_list(Prog) -> true - catch - _:_ -> false - end. - is_feature_available() -> - case is_feature_enabled() of - false -> false; - true -> - case create_image() of - {ok, _, _, _} -> true; - _Error -> false - end + case get_prog_name() of + Prog when is_list(Prog) -> true; + false -> false end. check_captcha_setup() -> - case is_feature_enabled() andalso not is_feature_available() of + case is_feature_available() of true -> - ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, " - "but it can't generate images.", []); + case create_image() of + {ok, _, _, _} -> + ok; + _Err -> + ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, " + "but it can't generate images.", []), + throw({error, captcha_cmd_enabled_but_fails}) + end; false -> ok end. @@ -498,3 +584,21 @@ do_check_captcha(Id, ProvidedKey) -> _ -> captcha_not_found end. + +clean_treap(Treap, CleanPriority) -> + case treap:is_empty(Treap) of + true -> + Treap; + false -> + {_Key, Priority, _Value} = treap:get_root(Treap), + if + Priority > CleanPriority -> + clean_treap(treap:delete_root(Treap), CleanPriority); + true -> + Treap + end + end. + +now_priority() -> + {MSec, Sec, USec} = now(), + -((MSec*1000000 + Sec)*1000000 + USec). diff --git a/src/ejabberd_check.erl b/src/ejabberd_check.erl index 403cdd2a9..1964af73a 100644 --- a/src/ejabberd_check.erl +++ b/src/ejabberd_check.erl @@ -5,7 +5,7 @@ %%% Created : 27 Feb 2008 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_cluster.erl b/src/ejabberd_cluster.erl index c189eb450..bc2f85da6 100644 --- a/src/ejabberd_cluster.erl +++ b/src/ejabberd_cluster.erl @@ -10,8 +10,8 @@ -behaviour(gen_server). %% API --export([start_link/0, get_node/1, get_node_new/1, announce/0, shutdown/0, - node_id/0, get_node_by_id/1, get_nodes/0, rehash_timeout/0]). +-export([start_link/0, get_node/1, get_node_new/1, announce/1, shutdown/0, + node_id/0, get_node_by_id/1, get_nodes/0, rehash_timeout/0, start/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -22,15 +22,28 @@ -define(HASHTBL, nodes_hash). -define(HASHTBL_NEW, nodes_hash_new). -define(POINTS, 64). --define(REHASH_TIMEOUT, 30000). +-define(REHASH_TIMEOUT, timer:seconds(30)). +-define(MIGRATE_TIMEOUT, timer:minutes(2)). +%%-define(REHASH_TIMEOUT, timer:seconds(10)). +%%-define(MIGRATE_TIMEOUT, timer:seconds(5)). +-define(LOCK, {migrate, node()}). -record(state, {}). %%==================================================================== %% API %%==================================================================== +start() -> + ChildSpec = {?MODULE, + {?MODULE, start_link, []}, + permanent, + brutal_kill, + worker, + [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec). + start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + gen_server:start_link(?MODULE, [], []). get_node(Key) -> Hash = erlang:phash2(Key), @@ -44,8 +57,8 @@ get_nodes() -> %% TODO mnesia:system_info(running_db_nodes). -announce() -> - gen_server:call(?MODULE, announce, infinity). +announce(Pid) -> + gen_server:call(Pid, announce, infinity). node_id() -> integer_to_list(erlang:phash2(node())). @@ -80,13 +93,15 @@ shutdown() -> %% gen_server callbacks %%==================================================================== init([]) -> + {A, B, C} = now(), + random:seed(A, B, C), net_kernel:monitor_nodes(true, [{node_type, visible}]), ets:new(?HASHTBL, [named_table, public, ordered_set]), ets:new(?HASHTBL_NEW, [named_table, public, ordered_set]), register_node(), - AllNodes = mnesia:system_info(running_db_nodes), + AllNodes = get_nodes(), OtherNodes = case AllNodes of - [_] -> + [_MyNode] -> AllNodes; _ -> AllNodes -- [node()] @@ -96,35 +111,59 @@ init([]) -> {ok, #state{}}. handle_call(announce, _From, State) -> - case mnesia:system_info(running_db_nodes) of + case global:set_lock(?LOCK, get_nodes(), 0) of + false -> + ?INFO_MSG("Another node is recently attached to " + "the cluster and is being rebalanced. " + "Waiting for the rebalancing to be completed " + "before starting this node. " + "This may take serveral minutes. " + "Please, be patient.", []), + global:set_lock(?LOCK, get_nodes(), infinity); + true -> + ok + end, + case get_nodes() of [_MyNode] -> - ok; + register(?MODULE, self()), + global:del_lock(?LOCK); Nodes -> OtherNodes = Nodes -- [node()], - lists:foreach( - fun(Node) -> - {?MODULE, Node} ! {node_ready, node()} - end, OtherNodes), ?INFO_MSG("waiting for migration from nodes: ~w", [OtherNodes]), - timer:sleep(?REHASH_TIMEOUT), - append_node(?HASHTBL, node()) + {_Res, BadNodes} = gen_server:multi_call( + OtherNodes, ?MODULE, + {node_ready, node()}, ?REHASH_TIMEOUT), + append_node(?HASHTBL, node()), + register(?MODULE, self()), + case OtherNodes -- BadNodes of + [] -> + global:del_lock(?LOCK); + WorkingNodes -> + gen_server:abcast(WorkingNodes, ?MODULE, {node_ready, node()}), + erlang:send_after(?MIGRATE_TIMEOUT, self(), del_lock) + end end, {reply, ok, State}; +handle_call({node_ready, Node}, _From, State) -> + ?INFO_MSG("node ~p is ready, preparing migration", [Node]), + append_node(?HASHTBL_NEW, Node), + ejabberd_hooks:run(node_up, [Node]), + {reply, ok, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. +handle_cast({node_ready, Node}, State) -> + ?INFO_MSG("adding node ~p to hash and starting migration", [Node]), + append_node(?HASHTBL, Node), + ejabberd_hooks:run(node_hash_update, [?MIGRATE_TIMEOUT]), + {noreply, State}; handle_cast(_Msg, State) -> {noreply, State}. -handle_info({node_ready, Node}, State) -> - ?INFO_MSG("node ~p is ready, starting migration", [Node]), - append_node(?HASHTBL_NEW, Node), - ejabberd_hooks:run(node_hash_update, [?REHASH_TIMEOUT]), - timer:sleep(?REHASH_TIMEOUT), - ?INFO_MSG("adding node ~p to hash", [Node]), - append_node(?HASHTBL, Node), +handle_info(del_lock, State) -> + global:del_lock(?LOCK), {noreply, State}; handle_info({node_down, Node}, State) -> delete_node(?HASHTBL, Node), diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index a18e4d3ab..4b6994e26 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -5,7 +5,7 @@ %%% Created : 20 May 2008 by Badlop %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_commands.hrl b/src/ejabberd_commands.hrl index 96d8e4086..81f5f6428 100644 --- a/src/ejabberd_commands.hrl +++ b/src/ejabberd_commands.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 383bb574b..848bcf7ca 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -5,7 +5,7 @@ %%% Created : 14 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -431,6 +431,8 @@ process_term(Term, State) -> add_option(captcha_cmd, Cmd, State); {captcha_host, Host} -> add_option(captcha_host, Host, State); + {captcha_limit, Limit} -> + add_option(captcha_limit, Limit, State); {ejabberdctl_access_commands, ACs} -> add_option(ejabberdctl_access_commands, ACs, State); {loglevel, Loglevel} -> @@ -438,6 +440,8 @@ process_term(Term, State) -> State; {max_fsm_queue, N} -> add_option(max_fsm_queue, N, State); + {hostname, Host} -> + add_option(hostname, Host, State); {_Opt, _Val} -> lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end, State, State#state.hosts) diff --git a/src/ejabberd_config.hrl b/src/ejabberd_config.hrl index f847f35b7..e0130979f 100644 --- a/src/ejabberd_config.hrl +++ b/src/ejabberd_config.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index 7433d256b..6c1d25beb 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -5,7 +5,7 @@ %%% Created : 11 Jan 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_ctl.hrl b/src/ejabberd_ctl.hrl index 69b16d14e..3521ad04a 100644 --- a/src/ejabberd_ctl.hrl +++ b/src/ejabberd_ctl.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_frontend_socket.erl b/src/ejabberd_frontend_socket.erl index 88d77aed9..88d8c91d8 100644 --- a/src/ejabberd_frontend_socket.erl +++ b/src/ejabberd_frontend_socket.erl @@ -5,7 +5,7 @@ %%% Created : 23 Aug 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index c44d37e65..59eb67777 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -5,7 +5,7 @@ %%% Created : 8 Aug 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 2553c8da3..75c0e9a96 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -5,7 +5,7 @@ %%% Created : 16 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index 3c9f5d3da..4d5f2ecb4 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -5,7 +5,7 @@ %%% Created : 30 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -34,10 +34,12 @@ -export([route/3, route_iq/4, + route_iq/5, process_iq_reply/3, register_iq_handler/4, register_iq_handler/5, register_iq_response_handler/4, + register_iq_response_handler/5, unregister_iq_handler/2, unregister_iq_response_handler/2, refresh_iq_handlers/0, @@ -123,23 +125,35 @@ route(From, To, Packet) -> ok end. -route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) -> +route_iq(From, To, IQ, F) -> + route_iq(From, To, IQ, F, undefined). + +route_iq(From, To, #iq{type = Type} = IQ, F, Timeout) when is_function(F) -> Packet = if Type == set; Type == get -> ID = ejabberd_router:make_id(), Host = From#jid.lserver, - register_iq_response_handler(Host, ID, undefined, F), + register_iq_response_handler(Host, ID, undefined, F, Timeout), jlib:iq_to_xml(IQ#iq{id = ID}); true -> jlib:iq_to_xml(IQ) end, ejabberd_router:route(From, To, Packet). -register_iq_response_handler(_Host, ID, Module, Function) -> - TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID), +register_iq_response_handler(Host, ID, Module, Function) -> + register_iq_response_handler(Host, ID, Module, Function, undefined). + +register_iq_response_handler(_Host, ID, Module, Function, Timeout0) -> + Timeout = case Timeout0 of + undefined -> + ?IQ_TIMEOUT; + N when is_integer(N), N > 0 -> + N + end, + TRef = erlang:start_timer(Timeout, ejabberd_local, ID), ets:insert(iq_response, #iq_response{id = ID, - module = Module, - function = Function, - timer = TRef}). + module = Module, + function = Function, + timer = TRef}). register_iq_handler(Host, XMLNS, Module, Fun) -> ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}. diff --git a/src/ejabberd_logger_h.erl b/src/ejabberd_logger_h.erl index 56a29bcff..e155042ab 100644 --- a/src/ejabberd_logger_h.erl +++ b/src/ejabberd_logger_h.erl @@ -5,7 +5,7 @@ %%% Created : 23 Oct 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_loglevel.erl b/src/ejabberd_loglevel.erl index b2cb9c346..897994a28 100644 --- a/src/ejabberd_loglevel.erl +++ b/src/ejabberd_loglevel.erl @@ -9,7 +9,7 @@ %%% Created : 29 Nov 2006 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_node_groups.erl b/src/ejabberd_node_groups.erl index d8cf43414..8779c0036 100644 --- a/src/ejabberd_node_groups.erl +++ b/src/ejabberd_node_groups.erl @@ -5,7 +5,7 @@ %%% Created : 1 Nov 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index fb744b89a..02697163b 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -5,7 +5,7 @@ %%% Created : 17 Jul 2008 by Pablo Polvorin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index 289cc127a..0602ba05a 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -5,7 +5,7 @@ %%% Created : 31 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 0482a27dc..e1fa4768d 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -5,7 +5,7 @@ %%% Created : 10 Nov 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index e6aa7ea51..672bdbe0f 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -5,7 +5,7 @@ %%% Created : 27 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index adb7e48f0..99403f75c 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -5,7 +5,7 @@ %%% Created : 7 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -203,9 +203,9 @@ migrate(After) -> ['$$']}]), lists:foreach( fun([FromTo, Pid]) -> - case ejabberd_cluster:get_node_new(FromTo) of + case ejabberd_cluster:get_node(FromTo) of Node when Node /= node() -> - ejabberd_s2s_out:stop_connection(Pid, After * 2); + ejabberd_s2s_out:stop_connection(Pid, After); _ -> ok end diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index eb8c05a6e..848e58c95 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -5,7 +5,7 @@ %%% Created : 6 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index 356d3390d..088f4560d 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -5,7 +5,7 @@ %%% Created : 6 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -322,6 +322,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> {"jabber:server", "jabber:server:dialback", true} when StateData#state.use_v10 -> {next_state, wait_for_features, StateData, ?FSMTIMEOUT}; + %% Clause added to handle Tigase's workaround for an old ejabberd bug: + {"jabber:server", "jabber:server:dialback", true} when + not StateData#state.use_v10 -> + send_db_request(StateData); {"jabber:server", "", true} when StateData#state.use_v10 -> {next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT}; {NSProvided, DB, _} -> diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 942f0fa4d..91827bf3a 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -5,7 +5,7 @@ %%% Created : 6 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index e98ccf338..a96ae8a3a 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -5,7 +5,7 @@ %%% Created : 24 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -37,9 +37,11 @@ open_session/6, close_session/4, close_migrated_session/4, + drop_session/1, check_in_subscription/6, bounce_offline_message/3, disconnect_removed_user/2, + get_user_sessions/2, get_user_resources/2, set_presence/7, unset_presence/6, @@ -59,6 +61,7 @@ get_session_pid/3, get_user_info/3, get_user_ip/3, + node_up/1, migrate/1 ]). @@ -107,27 +110,37 @@ open_session(SID, User, Server, Resource, Priority, Info) -> [SID, JID, Info]). close_session(SID, User, Server, Resource) -> - Info = do_close_session(SID, User, Server, Resource), + Info = do_close_session(SID), + US = {jlib:nodeprep(User), jlib:nameprep(Server)}, + case ejabberd_cluster:get_node_new(US) of + Node when Node /= node() -> + rpc:cast(Node, ?MODULE, drop_session, [SID]); + _ -> + ok + end, JID = jlib:make_jid(User, Server, Resource), ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver, [SID, JID, Info]). close_migrated_session(SID, User, Server, Resource) -> - Info = do_close_session(SID, User, Server, Resource), + Info = do_close_session(SID), JID = jlib:make_jid(User, Server, Resource), ejabberd_hooks:run(sm_remove_migrated_connection_hook, JID#jid.lserver, [SID, JID, Info]). -do_close_session(SID, User, Server, Resource) -> +do_close_session(SID) -> Info = case mnesia:dirty_read({session, SID}) of - [] -> []; - [#session{info=I}] -> I - end, + [] -> []; + [#session{info=I}] -> I + end, + drop_session(SID), + Info. + +drop_session(SID) -> F = fun() -> mnesia:delete({session, SID}) end, - mnesia:sync_dirty(F), - Info. + mnesia:sync_dirty(F). check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) -> case ejabberd_auth:is_user_exists(User, Server) of @@ -148,6 +161,18 @@ disconnect_removed_user(User, Server) -> {xmlelement, "broadcast", [], [{exit, "User removed"}]}). +get_user_sessions(User, Server) -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + US = {LUser, LServer}, + case ejabberd_cluster:get_node({LUser, LServer}) of + Node when Node == node() -> + catch mnesia:dirty_index_read(session, US, #session.us); + Node -> + catch rpc:call(Node, mnesia, dirty_index_read, + [session, US, #session.us], 5000) + end. + get_user_resources(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), @@ -299,14 +324,33 @@ migrate(After) -> ['$$']}]), lists:foreach( fun([US, Pid]) -> - case ejabberd_cluster:get_node_new(US) of + case ejabberd_cluster:get_node(US) of Node when Node /= node() -> - ejabberd_c2s:migrate(Pid, Node, After); + ejabberd_c2s:migrate(Pid, Node, random:uniform(After)); _ -> ok end end, Ss). +node_up(_Node) -> + copy_sessions(mnesia:dirty_first(session)). + +copy_sessions('$end_of_table') -> + ok; +copy_sessions(Key) -> + case mnesia:dirty_read(session, Key) of + [#session{us = US} = Session] -> + case ejabberd_cluster:get_node_new(US) of + Node when node() /= Node -> + rpc:cast(Node, mnesia, dirty_write, [Session]); + _ -> + ok + end; + _ -> + ok + end, + copy_sessions(mnesia:dirty_next(session, Key)). + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -328,6 +372,7 @@ init([]) -> mnesia:add_table_index(session, us), mnesia:add_table_copy(session, node(), ram_copies), ets:new(sm_iqtable, [named_table]), + ejabberd_hooks:add(node_up, ?MODULE, node_up, 100), ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100), lists:foreach( fun(Host) -> @@ -339,7 +384,7 @@ init([]) -> ejabberd_sm, disconnect_removed_user, 100) end, ?MYHOSTS), ejabberd_commands:register_commands(commands()), - + start_dispatchers(), {ok, #state{}}. %%-------------------------------------------------------------------- @@ -370,13 +415,19 @@ handle_cast(_Msg, State) -> %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- -handle_info({route, From, To, Packet}, State) -> - case catch do_route(From, To, Packet) of - {'EXIT', Reason} -> - ?ERROR_MSG("~p~nwhen processing: ~p", - [Reason, {From, To, Packet}]); - _ -> - ok +handle_info({route, From, To, Packet} = Msg, State) -> + case get_proc_num() of + N when N > 1 -> + #jid{luser = U, lserver = S} = To, + get_proc_by_hash({U, S}) ! Msg; + _ -> + case catch do_route(From, To, Packet) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> + ok + end end, {noreply, State}; handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) -> @@ -405,8 +456,10 @@ handle_info(_Info, State) -> %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _State) -> + ejabberd_hooks:delete(node_up, ?MODULE, node_up, 100), ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100), ejabberd_commands:unregister_commands(commands()), + stop_dispatchers(), ok. %%-------------------------------------------------------------------- @@ -420,7 +473,7 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- -set_session(SID, User, Server, Resource, Priority, Info) -> +set_session({_, Pid} = SID, User, Server, Resource, Priority, Info) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), LResource = jlib:resourceprep(Resource), @@ -433,7 +486,31 @@ set_session(SID, User, Server, Resource, Priority, Info) -> priority = Priority, info = Info}) end, - mnesia:sync_dirty(F). + mnesia:sync_dirty(F), + case ejabberd_cluster:get_node_new(US) of + Node when node() /= Node -> + %% New node has just been added. But we may miss session records + %% copy procedure, so we copy the session record manually just + %% to make sure + rpc:cast(Node, mnesia, dirty_write, + [#session{sid = SID, + usr = USR, + us = US, + priority = Priority, + info = Info}]), + case ejabberd_cluster:get_node(US) of + Node when node() /= Node -> + %% Migration to new node has completed, and seems like + %% we missed it, so we migrate the session pid manually. + %% It is not a problem if we have already got migration + %% notification: dups are just ignored by the c2s pid. + ejabberd_c2s:migrate(Pid, Node, 0); + _ -> + ok + end; + _ -> + ok + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -805,6 +882,55 @@ user_resources(User, Server) -> Resources = get_user_resources(User, Server), lists:sort(Resources). +get_proc_num() -> + erlang:system_info(logical_processors). + +get_proc_by_hash(Term) -> + N = erlang:phash2(Term, get_proc_num()) + 1, + get_proc(N). + +get_proc(N) -> + list_to_atom(atom_to_list(?MODULE) ++ "_" ++ integer_to_list(N)). + +start_dispatchers() -> + case get_proc_num() of + N when N > 1 -> + lists:foreach( + fun(I) -> + Pid = spawn(fun dispatch/0), + erlang:register(get_proc(I), Pid) + end, lists:seq(1, N)); + _ -> + ok + end. + +stop_dispatchers() -> + case get_proc_num() of + N when N > 1 -> + lists:foreach( + fun(I) -> + get_proc(I) ! stop + end, lists:seq(1, N)); + _ -> + ok + end. + +dispatch() -> + receive + {route, From, To, Packet} -> + case catch do_route(From, To, Packet) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p~nwhen processing: ~p", + [Reason, {From, To, Packet}]); + _ -> + ok + end, + dispatch(); + stop -> + stopped; + _ -> + dispatch() + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Update Mnesia tables diff --git a/src/ejabberd_socket.erl b/src/ejabberd_socket.erl index 32dce80bc..359e1d9ca 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -5,7 +5,7 @@ %%% Created : 23 Aug 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index d48deeaef..f7c303333 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -5,7 +5,7 @@ %%% Created : 31 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -178,13 +178,6 @@ init([]) -> infinity, supervisor, [ejabberd_tmp_sup]}, - Cluster = - {ejabberd_cluster, - {ejabberd_cluster, start_link, []}, - permanent, - brutal_kill, - worker, - [ejabberd_cluster]}, CacheTabSupervisor = {cache_tab_sup, {cache_tab_sup, start_link, []}, @@ -194,7 +187,6 @@ init([]) -> [cache_tab_sup]}, {ok, {{one_for_one, 10, 1}, [Hooks, - Cluster, SystemMonitor, Router, SM, diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl index ccd7b59e7..7d819e32d 100644 --- a/src/ejabberd_system_monitor.erl +++ b/src/ejabberd_system_monitor.erl @@ -5,7 +5,7 @@ %%% Created : 21 Mar 2007 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_tmp_sup.erl b/src/ejabberd_tmp_sup.erl index 3948a8d7e..e8f100bdc 100644 --- a/src/ejabberd_tmp_sup.erl +++ b/src/ejabberd_tmp_sup.erl @@ -5,7 +5,7 @@ %%% Created : 18 Jul 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_update.erl b/src/ejabberd_update.erl index 8d626ad13..fc3c3306a 100644 --- a/src/ejabberd_update.erl +++ b/src/ejabberd_update.erl @@ -5,7 +5,7 @@ %%% Created : 27 Jan 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_zlib/ejabberd_zlib.erl b/src/ejabberd_zlib/ejabberd_zlib.erl index 663640c42..9d90a0dc5 100644 --- a/src/ejabberd_zlib/ejabberd_zlib.erl +++ b/src/ejabberd_zlib/ejabberd_zlib.erl @@ -5,7 +5,7 @@ %%% Created : 19 Jan 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/ejabberd_zlib/ejabberd_zlib_drv.c b/src/ejabberd_zlib/ejabberd_zlib_drv.c index 5b420e4eb..ca3e7938a 100644 --- a/src/ejabberd_zlib/ejabberd_zlib_drv.c +++ b/src/ejabberd_zlib/ejabberd_zlib_drv.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff --git a/src/ejabberdctl.cfg.example b/src/ejabberdctl.cfg.example index 4a3db1c8b..2106e3d22 100644 --- a/src/ejabberdctl.cfg.example +++ b/src/ejabberdctl.cfg.example @@ -50,12 +50,23 @@ # #FIREWALL_WINDOW= +#. +#' INET_DIST_INTERFACE: IP address where this Erlang node listens other nodes +# +# This communication is used by ejabberdctl command line tool, +# and in a cluster of several ejabberd nodes. +# Notice that the IP address must be specified in the Erlang syntax. +# +# Default: {127,0,0,1} +# +INET_DIST_INTERFACE={127,0,0,1} + #. #' ERL_PROCESSES: Maximum number of Erlang processes # # Erlang consumes a lot of lightweight processes. If there is a lot of activity # on ejabberd so that the maximum number of processes is reached, people will -# experiment greater latency times. As these processes are implemented in +# experience greater latency times. As these processes are implemented in # Erlang, and therefore not related to the operating system processes, you do # not have to worry about allowing a huge number of them. # diff --git a/src/ejabberdctl.template b/src/ejabberdctl.template index 800d07fe7..172b68dc7 100644 --- a/src/ejabberdctl.template +++ b/src/ejabberdctl.template @@ -76,10 +76,12 @@ fi NAME=-name [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname -if [ "$FIREWALL_WINDOW" = "" ] ; then - KERNEL_OPTS="" -else - KERNEL_OPTS="-kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}" +KERNEL_OPTS="" +if [ "$FIREWALL_WINDOW" != "" ] ; then + KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}" +fi +if [ "$INET_DIST_INTERFACE" != "" ] ; then + KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${INET_DIST_INTERFACE}\"" fi ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" @@ -321,13 +323,21 @@ ctlexec () { CONN_NAME=$1; shift COMMAND=$@ - $EXEC_CMD "$ERL \ + + CTLEXEC="$ERL \ $NAME ${CONN_NAME} \ -noinput \ -hidden \ -pa $EJABBERD_EBIN_PATH \ $KERNEL_OPTS \ - -s ejabberd_ctl -extra $ERLANG_NODE $COMMAND" + -s ejabberd_ctl -extra $ERLANG_NODE" + + # quote input from the command line + for i in $COMMAND; do + CTLEXEC="$CTLEXEC '$i'"; + done + + $EXEC_CMD "$CTLEXEC" } # display ctl usage diff --git a/src/ejd2odbc.erl b/src/ejd2odbc.erl index 6c7caf9b2..5aa9d72f7 100644 --- a/src/ejd2odbc.erl +++ b/src/ejd2odbc.erl @@ -5,7 +5,7 @@ %%% Created : 22 Aug 2005 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/eldap/eldap.hrl b/src/eldap/eldap.hrl index ff0786104..5436fa794 100644 --- a/src/eldap/eldap.hrl +++ b/src/eldap/eldap.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/eldap/eldap_filter.erl b/src/eldap/eldap_filter.erl index 341fba13a..e1b5be303 100644 --- a/src/eldap/eldap_filter.erl +++ b/src/eldap/eldap_filter.erl @@ -6,7 +6,7 @@ %%% Author: Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/eldap/eldap_pool.erl b/src/eldap/eldap_pool.erl index 1b2961243..fc7317bda 100644 --- a/src/eldap/eldap_pool.erl +++ b/src/eldap/eldap_pool.erl @@ -5,7 +5,7 @@ %%% Created : 12 Nov 2006 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/eldap/eldap_utils.erl b/src/eldap/eldap_utils.erl index 993219eb3..b8ddee36b 100644 --- a/src/eldap/eldap_utils.erl +++ b/src/eldap/eldap_utils.erl @@ -5,7 +5,7 @@ %%% Created : 12 Oct 2006 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/expat_erl.c b/src/expat_erl.c index 60cd72c88..f06899eba 100644 --- a/src/expat_erl.c +++ b/src/expat_erl.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,6 +35,7 @@ #define PARSE_FINAL_COMMAND 1 ei_x_buff event_buf; +ei_x_buff xmlns_buf; typedef struct { ErlDrvPort port; @@ -43,6 +44,32 @@ typedef struct { static XML_Memory_Handling_Suite ms = {driver_alloc, driver_realloc, driver_free}; +void encode_name(const XML_Char *name) +{ + char *name_start; + char *prefix_start; + char *buf; + int name_len, prefix_len, buf_len; + + if ((name_start = strchr(name, '\n'))) { + if ((prefix_start = strchr(name_start+1, '\n'))) { + name_len = prefix_start - name_start; + prefix_len = strlen(prefix_start+1); + buf_len = prefix_len + name_len; + buf = driver_alloc(buf_len); + memcpy(buf, prefix_start+1, prefix_len); + memcpy(buf+prefix_len, name_start, name_len); + buf[prefix_len] = ':'; + ei_x_encode_string_len(&event_buf, buf, buf_len); + driver_free(buf); + } else { + ei_x_encode_string(&event_buf, name_start+1); + }; + } else { + ei_x_encode_string(&event_buf, name); + } +} + void *erlXML_StartElementHandler(expat_data *d, const XML_Char *name, const XML_Char **atts) @@ -53,7 +80,10 @@ void *erlXML_StartElementHandler(expat_data *d, 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); + encode_name(name); + ei_x_append(&event_buf, &xmlns_buf); + ei_x_free(&xmlns_buf); + ei_x_new(&xmlns_buf); for (i = 0; atts[i]; i += 2) {} @@ -64,7 +94,7 @@ void *erlXML_StartElementHandler(expat_data *d, for (i = 0; atts[i]; i += 2) { ei_x_encode_tuple_header(&event_buf, 2); - ei_x_encode_string(&event_buf, atts[i]); + encode_name(atts[i]); ei_x_encode_string(&event_buf, atts[i+1]); } } @@ -80,7 +110,7 @@ void *erlXML_EndElementHandler(expat_data *d, 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); + encode_name(name); return NULL; } @@ -95,12 +125,45 @@ void *erlXML_CharacterDataHandler(expat_data *d, return NULL; } +void *erlXML_StartNamespaceDeclHandler(expat_data *d, + const XML_Char *prefix, + const XML_Char *uri) +{ + int prefix_len; + char *buf; + + /* From the expat documentation: + "For a default namespace declaration (xmlns='...'), + the prefix will be null ... + ... The URI will be null for the case where + the default namespace is being unset." + + FIXME: I'm not quite sure what all that means */ + if (uri == NULL) + return NULL; + + ei_x_encode_list_header(&xmlns_buf, 1); + ei_x_encode_tuple_header(&xmlns_buf, 2); + if (prefix) { + prefix_len = strlen(prefix); + buf = driver_alloc(7 + prefix_len); + strcpy(buf, "xmlns:"); + strcpy(buf+6, prefix); + ei_x_encode_string(&xmlns_buf, buf); + driver_free(buf); + } else { + ei_x_encode_string(&xmlns_buf, "xmlns"); + }; + ei_x_encode_string(&xmlns_buf, uri); + + 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_MM("UTF-8", &ms, NULL); + d->parser = XML_ParserCreate_MM("UTF-8", &ms, "\n"); XML_SetUserData(d->parser, d); set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); @@ -112,6 +175,11 @@ static ErlDrvData expat_erl_start(ErlDrvPort port, char *buff) XML_SetCharacterDataHandler( d->parser, (XML_CharacterDataHandler)erlXML_CharacterDataHandler); + XML_SetStartNamespaceDeclHandler( + d->parser, (XML_StartNamespaceDeclHandler) erlXML_StartNamespaceDeclHandler); + XML_SetReturnNSTriplet(d->parser, 1); + + XML_SetDefaultHandler(d->parser, NULL); return (ErlDrvData)d; } @@ -138,6 +206,7 @@ static int expat_erl_control(ErlDrvData drv_data, case PARSE_COMMAND: case PARSE_FINAL_COMMAND: ei_x_new_with_version(&event_buf); + ei_x_new(&xmlns_buf); #ifdef ENABLE_FLASH_HACK /* Flash hack - Flash clients send a null byte after the stanza. Remove that... */ { @@ -190,6 +259,7 @@ static int expat_erl_control(ErlDrvData drv_data, memcpy(b->orig_bytes, event_buf.buff, size); ei_x_free(&event_buf); + ei_x_free(&xmlns_buf); *rbuf = (char *)b; return size; diff --git a/src/extauth.erl b/src/extauth.erl index 3f96c9ab2..f49392983 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -5,7 +5,7 @@ %%% Created : 30 Jul 2004 by Leif Johansson %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -45,16 +45,23 @@ start(Host, ExtPrg) -> lists:foreach( fun(This) -> - spawn(?MODULE, init, [get_process_name(Host, This), ExtPrg]) + start_instance(get_process_name(Host, This), ExtPrg) end, lists:seq(0, get_instances(Host)-1) ). +start_instance(ProcessName, ExtPrg) -> + spawn(?MODULE, init, [ProcessName, ExtPrg]). + +restart_instance(ProcessName, ExtPrg) -> + unregister(ProcessName), + start_instance(ProcessName, ExtPrg). + init(ProcessName, ExtPrg) -> register(ProcessName, self()), process_flag(trap_exit,true), Port = open_port({spawn, ExtPrg}, [{packet,2}]), - loop(Port, ?INIT_TIMEOUT). + loop(Port, ?INIT_TIMEOUT, ProcessName, ExtPrg). stop(Host) -> lists:foreach( @@ -108,23 +115,27 @@ get_instances(Server) -> _ -> 1 end. -loop(Port, Timeout) -> +loop(Port, Timeout, ProcessName, ExtPrg) -> receive {call, Caller, Msg} -> - Port ! {self(), {command, encode(Msg)}}, + port_command(Port, encode(Msg)), receive {Port, {data, Data}} -> ?DEBUG("extauth call '~p' received data response:~n~p", [Msg, Data]), - Caller ! {eauth, decode(Data)}; + Caller ! {eauth, decode(Data)}, + loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg); {Port, Other} -> ?ERROR_MSG("extauth call '~p' received strange response:~n~p", [Msg, Other]), - Caller ! {eauth, false} + Caller ! {eauth, false}, + loop(Port, ?CALL_TIMEOUT, ProcessName, ExtPrg) after Timeout -> ?ERROR_MSG("extauth call '~p' didn't receive response", [Msg]), - Caller ! {eauth, false} - end, - loop(Port, ?CALL_TIMEOUT); + Caller ! {eauth, false}, + Pid = restart_instance(ProcessName, ExtPrg), + flush_buffer_and_forward_messages(Pid), + exit(port_terminated) + end; stop -> Port ! {self(), close}, receive @@ -132,10 +143,21 @@ loop(Port, Timeout) -> exit(normal) end; {'EXIT', Port, Reason} -> - ?CRITICAL_MSG("~p ~n", [Reason]), + ?CRITICAL_MSG("extauth script has exitted abruptly with reason '~p'", [Reason]), + Pid = restart_instance(ProcessName, ExtPrg), + flush_buffer_and_forward_messages(Pid), exit(port_terminated) end. +flush_buffer_and_forward_messages(Pid) -> + receive + Message -> + Pid ! Message, + flush_buffer_and_forward_messages(Pid) + after 0 -> + true + end. + join(List, Sep) -> lists:foldl(fun(A, "") -> A; (A, Acc) -> Acc ++ Sep ++ A diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index c7239e066..0caddf4c9 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -5,7 +5,7 @@ %%% Created : 22 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 92a5628cb..397cbc35a 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -5,7 +5,7 @@ %%% Created : 24 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -70,11 +70,24 @@ start_module(Host, Module, Opts) -> catch Class:Reason -> del_module_mnesia(Host, Module), ets:delete(ejabberd_modules, {Module, Host}), - ?ERROR_MSG("Problem starting the module ~p for host ~p with options:~n ~p~n ~p: ~p", + ErrorText = io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p", [Module, Host, Opts, Class, Reason]), - erlang:raise(Class, Reason, erlang:get_stacktrace()) + ?CRITICAL_MSG(ErrorText, []), + case is_app_running(ejabberd) of + true -> + erlang:raise(Class, Reason, erlang:get_stacktrace()); + false -> + ?CRITICAL_MSG("ejabberd initialization was aborted because a module start failed.", []), + timer:sleep(3000), + erlang:halt(lists:flatten(ErrorText)) + end end. +is_app_running(AppName) -> + %% Use a high timeout to prevent a false positive in a high load system + Timeout = 15000, + lists:keymember(AppName, 1, application:which_applications(Timeout)). + %% @doc Stop the module in a host, and forget its configuration. stop_module(Host, Module) -> case stop_module_keep_config(Host, Module) of diff --git a/src/idna.erl b/src/idna.erl index 73506c558..65ccf68ff 100644 --- a/src/idna.erl +++ b/src/idna.erl @@ -5,7 +5,7 @@ %%% Created : 10 Apr 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/jd2ejd.erl b/src/jd2ejd.erl index 7a3d08633..7b6bf0b38 100644 --- a/src/jd2ejd.erl +++ b/src/jd2ejd.erl @@ -5,7 +5,7 @@ %%% Created : 2 Feb 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/jlib.erl b/src/jlib.erl index 1d5648a2b..cc31fbdd4 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -5,7 +5,7 @@ %%% Created : 23 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/jlib.hrl b/src/jlib.hrl index cb89f91a5..e18e8e29c 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -30,6 +30,7 @@ -define(NS_ROSTER, "jabber:iq:roster"). -define(NS_ROSTER_VER, "urn:xmpp:features:rosterver"). -define(NS_PRIVACY, "jabber:iq:privacy"). +-define(NS_BLOCKING, "urn:xmpp:blocking"). -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 @@ -206,117 +207,117 @@ ?ERRT_CONFLICT(Lang, "Resource conflict")). --define(STREAM_ERROR(Condition), +-define(STREAM_ERROR(Condition, Cdata), {xmlelement, "stream:error", [], - [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}]}). + [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], + [{xmlcdata, Cdata}]}]}). -define(SERR_BAD_FORMAT, - ?STREAM_ERROR("bad-format")). + ?STREAM_ERROR("bad-format", "")). -define(SERR_BAD_NAMESPACE_PREFIX, - ?STREAM_ERROR("bad-namespace-prefix")). + ?STREAM_ERROR("bad-namespace-prefix", "")). -define(SERR_CONFLICT, - ?STREAM_ERROR("conflict")). + ?STREAM_ERROR("conflict", "")). -define(SERR_CONNECTION_TIMEOUT, - ?STREAM_ERROR("connection-timeout")). + ?STREAM_ERROR("connection-timeout", "")). -define(SERR_HOST_GONE, - ?STREAM_ERROR("host-gone")). + ?STREAM_ERROR("host-gone", "")). -define(SERR_HOST_UNKNOWN, - ?STREAM_ERROR("host-unknown")). + ?STREAM_ERROR("host-unknown", "")). -define(SERR_IMPROPER_ADDRESSING, - ?STREAM_ERROR("improper-addressing")). + ?STREAM_ERROR("improper-addressing", "")). -define(SERR_INTERNAL_SERVER_ERROR, - ?STREAM_ERROR("internal-server-error")). + ?STREAM_ERROR("internal-server-error", "")). -define(SERR_INVALID_FROM, - ?STREAM_ERROR("invalid-from")). + ?STREAM_ERROR("invalid-from", "")). -define(SERR_INVALID_ID, - ?STREAM_ERROR("invalid-id")). + ?STREAM_ERROR("invalid-id", "")). -define(SERR_INVALID_NAMESPACE, - ?STREAM_ERROR("invalid-namespace")). + ?STREAM_ERROR("invalid-namespace", "")). -define(SERR_INVALID_XML, - ?STREAM_ERROR("invalid-xml")). + ?STREAM_ERROR("invalid-xml", "")). -define(SERR_NOT_AUTHORIZED, - ?STREAM_ERROR("not-authorized")). + ?STREAM_ERROR("not-authorized", "")). -define(SERR_POLICY_VIOLATION, - ?STREAM_ERROR("policy-violation")). + ?STREAM_ERROR("policy-violation", "")). -define(SERR_REMOTE_CONNECTION_FAILED, - ?STREAM_ERROR("remote-connection-failed")). + ?STREAM_ERROR("remote-connection-failed", "")). -define(SERR_RESOURSE_CONSTRAINT, - ?STREAM_ERROR("resource-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")). + ?STREAM_ERROR("restricted-xml", "")). +-define(SERR_SEE_OTHER_HOST(Host), + ?STREAM_ERROR("see-other-host", Host)). -define(SERR_SYSTEM_SHUTDOWN, - ?STREAM_ERROR("system-shutdown")). + ?STREAM_ERROR("system-shutdown", "")). -define(SERR_UNSUPPORTED_ENCODING, - ?STREAM_ERROR("unsupported-encoding")). + ?STREAM_ERROR("unsupported-encoding", "")). -define(SERR_UNSUPPORTED_STANZA_TYPE, - ?STREAM_ERROR("unsupported-stanza-type")). + ?STREAM_ERROR("unsupported-stanza-type", "")). -define(SERR_UNSUPPORTED_VERSION, - ?STREAM_ERROR("unsupported-version")). + ?STREAM_ERROR("unsupported-version", "")). -define(SERR_XML_NOT_WELL_FORMED, - ?STREAM_ERROR("xml-not-well-formed")). + ?STREAM_ERROR("xml-not-well-formed", "")). %-define(SERR_, -% ?STREAM_ERROR("")). +% ?STREAM_ERROR("", "")). --define(STREAM_ERRORT(Condition, Lang, Text), +-define(STREAM_ERRORT(Condition, Cdata, Lang, Text), {xmlelement, "stream:error", [], - [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], []}, + [{xmlelement, Condition, [{"xmlns", ?NS_STREAMS}], + [{xmlcdata, Cdata}]}, {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)). + ?STREAM_ERRORT("bad-format", "", Lang, Text)). -define(SERRT_BAD_NAMESPACE_PREFIX(Lang, Text), - ?STREAM_ERRORT("bad-namespace-prefix", Lang, Text)). + ?STREAM_ERRORT("bad-namespace-prefix", "", Lang, Text)). -define(SERRT_CONFLICT(Lang, Text), - ?STREAM_ERRORT("conflict", Lang, Text)). + ?STREAM_ERRORT("conflict", "", Lang, Text)). -define(SERRT_CONNECTION_TIMEOUT(Lang, Text), - ?STREAM_ERRORT("connection-timeout", Lang, Text)). + ?STREAM_ERRORT("connection-timeout", "", Lang, Text)). -define(SERRT_HOST_GONE(Lang, Text), - ?STREAM_ERRORT("host-gone", Lang, Text)). + ?STREAM_ERRORT("host-gone", "", Lang, Text)). -define(SERRT_HOST_UNKNOWN(Lang, Text), - ?STREAM_ERRORT("host-unknown", Lang, Text)). + ?STREAM_ERRORT("host-unknown", "", Lang, Text)). -define(SERRT_IMPROPER_ADDRESSING(Lang, Text), - ?STREAM_ERRORT("improper-addressing", Lang, Text)). + ?STREAM_ERRORT("improper-addressing", "", Lang, Text)). -define(SERRT_INTERNAL_SERVER_ERROR(Lang, Text), - ?STREAM_ERRORT("internal-server-error", Lang, Text)). + ?STREAM_ERRORT("internal-server-error", "", Lang, Text)). -define(SERRT_INVALID_FROM(Lang, Text), - ?STREAM_ERRORT("invalid-from", Lang, Text)). + ?STREAM_ERRORT("invalid-from", "", Lang, Text)). -define(SERRT_INVALID_ID(Lang, Text), - ?STREAM_ERRORT("invalid-id", Lang, Text)). + ?STREAM_ERRORT("invalid-id", "", Lang, Text)). -define(SERRT_INVALID_NAMESPACE(Lang, Text), - ?STREAM_ERRORT("invalid-namespace", Lang, Text)). + ?STREAM_ERRORT("invalid-namespace", "", Lang, Text)). -define(SERRT_INVALID_XML(Lang, Text), - ?STREAM_ERRORT("invalid-xml", Lang, Text)). + ?STREAM_ERRORT("invalid-xml", "", Lang, Text)). -define(SERRT_NOT_AUTHORIZED(Lang, Text), - ?STREAM_ERRORT("not-authorized", Lang, Text)). + ?STREAM_ERRORT("not-authorized", "", Lang, Text)). -define(SERRT_POLICY_VIOLATION(Lang, Text), - ?STREAM_ERRORT("policy-violation", Lang, Text)). + ?STREAM_ERRORT("policy-violation", "", Lang, Text)). -define(SERRT_REMOTE_CONNECTION_FAILED(Lang, Text), - ?STREAM_ERRORT("remote-connection-failed", Lang, Text)). + ?STREAM_ERRORT("remote-connection-failed", "", Lang, Text)). -define(SERRT_RESOURSE_CONSTRAINT(Lang, Text), - ?STREAM_ERRORT("resource-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)). + ?STREAM_ERRORT("restricted-xml", "", Lang, Text)). +-define(SERRT_SEE_OTHER_HOST(Host, Lang, Text), + ?STREAM_ERRORT("see-other-host", Host, Lang, Text)). -define(SERRT_SYSTEM_SHUTDOWN(Lang, Text), - ?STREAM_ERRORT("system-shutdown", Lang, Text)). + ?STREAM_ERRORT("system-shutdown", "", Lang, Text)). -define(SERRT_UNSUPPORTED_ENCODING(Lang, Text), - ?STREAM_ERRORT("unsupported-encoding", Lang, Text)). + ?STREAM_ERRORT("unsupported-encoding", "", Lang, Text)). -define(SERRT_UNSUPPORTED_STANZA_TYPE(Lang, Text), - ?STREAM_ERRORT("unsupported-stanza-type", Lang, Text)). + ?STREAM_ERRORT("unsupported-stanza-type", "", Lang, Text)). -define(SERRT_UNSUPPORTED_VERSION(Lang, Text), - ?STREAM_ERRORT("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)). + ?STREAM_ERRORT("xml-not-well-formed", "", Lang, Text)). %-define(SERRT_(Lang, Text), -% ?STREAM_ERRORT("", Lang, Text)). +% ?STREAM_ERRORT("", "", Lang, Text)). -record(jid, {user, server, resource, diff --git a/src/mod_ack.erl b/src/mod_ack.erl new file mode 100644 index 000000000..7268b549e --- /dev/null +++ b/src/mod_ack.erl @@ -0,0 +1,420 @@ +%%%------------------------------------------------------------------- +%%% File : mod_ack.erl +%%% Author : Mickael Remond +%%% Description : Implements reliable message delivery +%%% Note: this module depends on mod_caps +%%% Created : 12 Mar 2010 by Mickael Remond +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2010 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(mod_ack). + +-behaviour(gen_server). +-behaviour(gen_mod). + +%% API +-export([start/2, stop/1, start_link/2]). + +-export([user_send_packet/3, + offline_message/3, + delayed_message/3, + remove_connection/3, + feature_inspect_packet/4]). + +%% gen_server callbacks +-export([init/1, + handle_info/2, + handle_call/3, + handle_cast/2, + terminate/2, + code_change/3]). + +-include("jlib.hrl"). +-include("ejabberd.hrl"). + +-define(PROCNAME, ejabberd_mod_ack). +-define(ACK_TIMEOUT, 60). %% seconds +-define(DICT, dict). + +-ifndef(NS_RECEIPTS). +-define(NS_RECEIPTS, "urn:xmpp:receipts"). +-endif. +-ifndef(NS_PING). +-define(NS_PING, "urn:xmpp:ping"). +-endif. +-ifndef(NS_P1_PUSHED). +-define(NS_P1_PUSHED, "p1:pushed"). +-endif. + +-record(state, {host, timers = ?DICT:new(), timeout}). + +%%==================================================================== +%% API +%%==================================================================== +start_link(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). + +start(Host, Opts) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + ChildSpec = + {Proc, + {?MODULE, start_link, [Host, Opts]}, + transient, + 1000, + worker, + [?MODULE]}, + supervisor:start_child(ejabberd_sup, ChildSpec). + +stop(Host) -> + Proc = gen_mod:get_module_proc(Host, ?PROCNAME), + gen_server:call(Proc, stop), + supervisor:terminate_child(ejabberd_sup, Proc), + supervisor:delete_child(ejabberd_sup, Proc). + +%% TODO: Make ack on server receive optional ? +user_send_packet(From, To, {xmlelement, "message", _Attrs, _Els} = Packet) -> + case has_receipt_request(Packet) of + {true, _} -> + process_ack_request("on-sender-server", From, To, Packet); + false -> + case has_receipt_response(Packet) of + {true, ID} -> + Server = From#jid.lserver, + del_timer(Server, {message, ID}, From); + false -> + do_nothing + end + end; +user_send_packet(From, _To, {xmlelement, "iq", Attrs, _Els}) -> + case xml:get_attr_s("id", Attrs) of + "" -> + ok; + ID -> + Server = From#jid.lserver, + del_timer(Server, {iq, ID}, From) + end; +user_send_packet(_From, _To, _Packet) -> + do_nothing. + +offline_message(From, To, Packet) -> + process_ack_request("offline", From, To, Packet), + ok. + +delayed_message(From, To, Packet) -> + process_ack_request("delayed", From, To, Packet), + ok. + +feature_inspect_packet(JID, Server, + {xmlelement, "presence", _, _} = Pres, + {xmlelement, "message", Attrs, _} = El) -> + HasReceipts = has_receipt_request(El), + ReceiptsSupported = are_receipts_supported(Pres), + ?DEBUG("feature_inspect_packet:~n" + "** JID: ~p~n" + "** Has receipts: ~p~n" + "** Receipts supported: ~p~n" + "** Pres: ~p~n" + "** El: ~p", + [JID, HasReceipts, ReceiptsSupported, Pres, El]), + Type = xml:get_attr_s("type", Attrs), + case HasReceipts of + _ when Type == "error" -> + ok; + {true, ID} -> + case {jlib:string_to_jid(xml:get_attr_s("from", Attrs)), + jlib:string_to_jid(xml:get_attr_s("to", Attrs))} of + {#jid{} = From, #jid{} = To} -> + Pkt = {From, To, El}, + case ReceiptsSupported of + true -> + add_timer(Server, {message, ID}, JID, Pkt); + false -> + ping(From, To, Server, JID, El); + unknown -> + process_ack_request("unreliable", From, To, El) + end; + _ -> + ?WARNING_MSG("message doesn't have 'from' or 'to'" + " attribute:~n** El: ~p", [El]) + end; + _ -> + ok + end; +feature_inspect_packet(_User, _Server, _Pres, _El) -> + ok. + +remove_connection({_, C2SPid}, #jid{lserver = Host}, _Info) -> + gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME), {del, C2SPid}). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== +init([Host, Opts]) -> + Timeout = timer:seconds(gen_mod:get_opt(timeout, Opts, ?ACK_TIMEOUT)), + ejabberd_hooks:add(user_send_packet, Host, + ?MODULE, user_send_packet, 20), + ejabberd_hooks:add(offline_message_hook, Host, + ?MODULE, offline_message, 20), + ejabberd_hooks:add(delayed_message_hook, Host, + ?MODULE, delayed_message, 20), + ejabberd_hooks:add(feature_inspect_packet, Host, + ?MODULE, feature_inspect_packet, 150), + ejabberd_hooks:add(sm_remove_connection_hook, Host, + ?MODULE, remove_connection, 20), + ejabberd_hooks:add(sm_remove_migrated_connection_hook, Host, + ?MODULE, remove_connection, 20), + {ok, #state{host = Host, timeout = Timeout}}. + +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; +handle_call(_Req, _From, State) -> + {reply, {error, badarg}, State}. + +handle_cast({add, ID, Pid, Packet}, State) -> + TRef = erlang:start_timer(State#state.timeout, self(), {ID, Pid}), + Timers = insert(Pid, ID, {TRef, Packet}, State#state.timers), + {noreply, State#state{timers = Timers}}; +handle_cast({del, ID, Pid}, State) -> + case lookup(Pid, ID, State#state.timers) of + {ok, {TRef, {From, To, {xmlelement, _, Attrs, _}}}} -> + cancel_timer(TRef), + Timers = delete(Pid, ID, State#state.timers), + case ID of + {iq, _} -> + MsgID = xml:get_attr_s("id", Attrs), + Message = {xmlelement, "message", [{"id", MsgID}], + [{xmlelement, "received", + [{"xmlns", ?NS_RECEIPTS}, {"id", MsgID}], []}]}, + ejabberd_router:route(To, From, Message); + _ -> + ok + end, + {noreply, State#state{timers = Timers}}; + error -> + {noreply, State} + end; +handle_cast({del, Pid}, State) -> + lists:foreach( + fun({_, _, {TRef, {From, To, El}}}) -> + cancel_timer(TRef), + El1 = xml:remove_subtags(El, "x", {"xmlns", ?NS_P1_PUSHED}), + El2 = xml:append_subtags( + El1, [{xmlelement, "x", [{"xmlns", ?NS_P1_PUSHED}], []}]), + ?DEBUG("Resending message:~n" + "** From: ~p~n" + "** To: ~p~n" + "** El: ~p", + [From, To, El2]), + ejabberd_router:route(From, To, El2) + end, to_list(Pid, State#state.timers)), + Timers = delete(Pid, State#state.timers), + {noreply, State#state{timers = Timers}}; +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({timeout, _TRef, {ID, Pid}}, State) -> + case lookup(Pid, ID, State#state.timers) of + {ok, _} -> + MRef = erlang:monitor(process, Pid), + catch ejabberd_c2s:stop(Pid), + receive + {'DOWN', MRef, process, Pid, _Reason}-> + ok + after 5 -> + catch exit(Pid, kill) + end, + erlang:demonitor(MRef, [flush]), + handle_cast({del, Pid}, State); + error -> + {noreply, State} + end; +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State) -> + Host = State#state.host, + ejabberd_hooks:delete(user_send_packet, Host, + ?MODULE, user_send_packet, 20), + ejabberd_hooks:delete(offline_message_hook, Host, + ?MODULE, offline_message, 20), + ejabberd_hooks:delete(delayed_message_hook, Host, + ?MODULE, delayed_message, 20), + ejabberd_hooks:delete(feature_inspect_packet, Host, + ?MODULE, feature_inspect_packet, 150), + ejabberd_hooks:delete(sm_remove_connection_hook, Host, + ?MODULE, remove_connection, 20), + ejabberd_hooks:delete(sm_remove_migrated_connection_hook, Host, + ?MODULE, remove_connection, 20), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%==================================================================== +%% Internal functions +%%==================================================================== +process_ack_request(AckTagName, + #jid{lserver=LServer} = From, To, + {xmlelement, "message", _Attrs, _Els} = Packet) -> + case has_receipt_request(Packet) of + {true, ID} -> + BareTo = jlib:jid_remove_resource(To), + Message = {xmlelement, "message", [{"id", ID}], + [{xmlelement, AckTagName, + [{"xmlns", ?NS_RECEIPTS}, + {"server", LServer}, {"id", ID}], []}]}, + ejabberd_router:route(BareTo, From, Message); + false -> + do_nothing + end. + +has_receipt_request(Packet) -> + has_receipt(Packet, "request"). + +has_receipt_response(Packet) -> + has_receipt(Packet, "received"). + +has_receipt({xmlelement, "message", MsgAttrs, _} = Packet, Type) -> + case xml:get_attr_s("id", MsgAttrs) of + "" -> + case Type of + "request" -> false; %% Message must have an ID to ask a request for ack. + "received" -> + case xml:get_subtag(Packet, "received") of + false -> + false; + {xmlelement, _Name, Attrs, _Els} -> + case xml:get_attr_s("xmlns", Attrs) of + ?NS_RECEIPTS -> + case xml:get_attr_s("id", Attrs) of + "" -> false; + SubTagID -> {true, SubTagID} + end; + _ -> false + end + end + end; + ID -> + case xml:get_subtag(Packet, Type) of + false -> + false; + {xmlelement, _Name, Attrs, _Els} -> + case xml:get_attr_s("xmlns", Attrs) of + ?NS_RECEIPTS -> + case xml:get_attr_s("id", Attrs) of + "" -> {true, ID}; + SubTagID -> {true, SubTagID} + end; + _ -> + false + end + end + end. + +are_receipts_supported(undefined) -> + unknown; +are_receipts_supported({xmlelement, "presence", _, Els}) -> + case mod_caps:read_caps(Els) of + nothing -> + unknown; + Caps -> + lists:member(?NS_RECEIPTS, mod_caps:get_features(Caps)) + end. + +ping(From, To, Server, JID, El) -> + ID = randoms:get_string(), + add_timer(Server, {iq, ID}, JID, {From, To, El}), + ejabberd_router:route(jlib:make_jid("", Server, ""), JID, + {xmlelement, "iq", + [{"type", "get"}, {"id", ID}], + [{xmlelement, "query", [{"xmlns", ?NS_PING}], []}]}). + +add_timer(Host, ID, JID, Packet) -> + {U, S, R} = jlib:jid_tolower(JID), + C2SPid = ejabberd_sm:get_session_pid(U, S, R), + gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME), + {add, ID, C2SPid, Packet}). + +del_timer(Host, ID, JID) -> + {U, S, R} = jlib:jid_tolower(JID), + C2SPid = ejabberd_sm:get_session_pid(U, S, R), + gen_server:cast(gen_mod:get_module_proc(Host, ?PROCNAME), + {del, ID, C2SPid}). + +cancel_timer(TRef) -> + case erlang:cancel_timer(TRef) of + false -> + receive + {timeout, TRef, _} -> + ok + after 0 -> + ok + end; + _ -> + ok + end. + +lookup(Pid, Key, Queue) -> + case ?DICT:find(Pid, Queue) of + {ok, Treap} -> + case treap:lookup(Key, Treap) of + {ok, _, Val} -> + {ok, Val}; + error -> + error + end; + error -> + error + end. + +insert(Pid, Key, Val, Queue) -> + Treap = case ?DICT:find(Pid, Queue) of + {ok, Treap1} -> + Treap1; + error -> + nil + end, + ?DICT:store(Pid, treap:insert(Key, now(), Val, Treap), Queue). + +delete(Pid, Key, Queue) -> + case ?DICT:find(Pid, Queue) of + {ok, Treap} -> + NewTreap = treap:delete(Key, Treap), + case treap:is_empty(NewTreap) of + true -> + ?DICT:erase(Pid, Queue); + false -> + ?DICT:store(Pid, NewTreap, Queue) + end; + error -> + Queue + end. + +delete(Pid, Queue) -> + ?DICT:erase(Pid, Queue). + +to_list(Pid, Queue) -> + case ?DICT:find(Pid, Queue) of + {ok, Treap} -> + treap:to_list(Treap); + error -> + [] + end. diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl index cdfd6845a..8acb9b85d 100644 --- a/src/mod_adhoc.erl +++ b/src/mod_adhoc.erl @@ -5,7 +5,7 @@ %%% Created : 15 Nov 2005 by Magnus Henoch %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 15c2ef382..6f61ae301 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -5,7 +5,7 @@ %%% Created : 11 Aug 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl new file mode 100644 index 000000000..ea2c19cfb --- /dev/null +++ b/src/mod_blocking.erl @@ -0,0 +1,333 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_blocking.erl +%%% Author : Stephan Maka +%%% Purpose : XEP-0191: Simple Communications Blocking +%%% Created : 24 Aug 2008 by Stephan Maka +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-module(mod_blocking). + +-behaviour(gen_mod). + +-export([start/2, stop/1, + process_iq/3, + process_iq_set/4, + process_iq_get/5]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +start(Host, Opts) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), + ejabberd_hooks:add(privacy_iq_get, Host, + ?MODULE, process_iq_get, 40), + ejabberd_hooks:add(privacy_iq_set, Host, + ?MODULE, process_iq_set, 40), + mod_disco:register_feature(Host, ?NS_BLOCKING), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING, + ?MODULE, process_iq, IQDisc). + +stop(Host) -> + ejabberd_hooks:delete(privacy_iq_get, Host, + ?MODULE, process_iq_get, 40), + ejabberd_hooks:delete(privacy_iq_set, Host, + ?MODULE, process_iq_set, 40), + mod_disco:unregister_feature(Host, ?NS_BLOCKING), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_BLOCKING). + +process_iq(_From, _To, IQ) -> + SubEl = IQ#iq.sub_el, + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}. + +process_iq_get(_, From, _To, + #iq{xmlns = ?NS_BLOCKING, + sub_el = {xmlelement, "blocklist", _, _}}, + _) -> + #jid{luser = LUser, lserver = LServer} = From, + process_blocklist_get(LUser, LServer); + +process_iq_get(Acc, _, _, _, _) -> + Acc. + +process_iq_set(_, From, _To, #iq{xmlns = ?NS_BLOCKING, + sub_el = {xmlelement, SubElName, _, SubEls}}) -> + #jid{luser = LUser, lserver = LServer} = From, + case {SubElName, xml:remove_cdata(SubEls)} of + {"block", []} -> + {error, ?ERR_BAD_REQUEST}; + {"block", Els} -> + JIDs = parse_blocklist_items(Els, []), + process_blocklist_block(LUser, LServer, JIDs); + {"unblock", []} -> + process_blocklist_unblock_all(LUser, LServer); + {"unblock", Els} -> + JIDs = parse_blocklist_items(Els, []), + process_blocklist_unblock(LUser, LServer, JIDs); + _ -> + {error, ?ERR_BAD_REQUEST} + end; + +process_iq_set(Acc, _, _, _) -> + Acc. + +is_list_needdb(Items) -> + lists:any( + fun(X) -> + case X#listitem.type of + subscription -> true; + group -> true; + _ -> false + end + end, Items). + +list_to_blocklist_jids([], JIDs) -> + JIDs; + +list_to_blocklist_jids([#listitem{type = jid, + action = deny, + value = JID} = Item | Items], JIDs) -> + case Item of + #listitem{match_all = true} -> + Match = true; + #listitem{match_iq = true, + match_message = true, + match_presence_in = true, + match_presence_out = true} -> + Match = true; + _ -> + Match = false + end, + if + Match -> + list_to_blocklist_jids(Items, [JID | JIDs]); + true -> + list_to_blocklist_jids(Items, JIDs) + end; + +% Skip Privacy List items than cannot be mapped to Blocking items +list_to_blocklist_jids([_ | Items], JIDs) -> + list_to_blocklist_jids(Items, JIDs). + +parse_blocklist_items([], JIDs) -> + JIDs; + +parse_blocklist_items([{xmlelement, "item", Attrs, _} | Els], JIDs) -> + case xml:get_attr("jid", Attrs) of + {value, JID1} -> + JID = jlib:jid_tolower(jlib:string_to_jid(JID1)), + parse_blocklist_items(Els, [JID | JIDs]); + false -> + % Tolerate missing jid attribute + parse_blocklist_items(Els, JIDs) + end; + +parse_blocklist_items([_ | Els], JIDs) -> + % Tolerate unknown elements + parse_blocklist_items(Els, JIDs). + +process_blocklist_block(LUser, LServer, JIDs) -> + F = + fun() -> + case mnesia:wread({privacy, {LUser, LServer}}) of + [] -> + % No lists yet + P = #privacy{us = {LUser, LServer}}, + % TODO: i18n here: + NewDefault = "Blocked contacts", + NewLists1 = [], + List = []; + [#privacy{default = Default, + lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + % Default list exists + NewDefault = Default, + NewLists1 = lists:keydelete(Default, 1, Lists); + false -> + % No default list yet, create one + % TODO: i18n here: + NewDefault = "Blocked contacts", + NewLists1 = Lists, + List = [] + end + end, + + AlreadyBlocked = list_to_blocklist_jids(List, []), + NewList = + lists:foldr(fun(JID, List1) -> + case lists:member(JID, AlreadyBlocked) of + true -> + List1; + false -> + [#listitem{type = jid, + value = JID, + action = deny, + order = 0, + match_all = true + } | List1] + end + end, List, JIDs), + NewLists = [{NewDefault, NewList} | NewLists1], + mnesia:write(P#privacy{default = NewDefault, + lists = NewLists}), + {ok, NewDefault, NewList} + end, + case mnesia:transaction(F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, {ok, Default, List}} -> + broadcast_list_update(LUser, LServer, Default, List), + broadcast_blocklist_event(LUser, LServer, {block, JIDs}), + {result, []}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +process_blocklist_unblock_all(LUser, LServer) -> + F = + fun() -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> + % No lists, nothing to unblock + ok; + [#privacy{default = Default, + lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + % Default list, remove all deny items + NewList = + lists:filter( + fun(#listitem{action = A}) -> + A =/= deny + end, List), + + NewLists1 = lists:keydelete(Default, 1, Lists), + NewLists = [{Default, NewList} | NewLists1], + mnesia:write(P#privacy{lists = NewLists}), + + {ok, Default, NewList}; + false -> + % No default list, nothing to unblock + ok + end + end + end, + case mnesia:transaction(F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, ok} -> + {result, []}; + {atomic, {ok, Default, List}} -> + broadcast_list_update(LUser, LServer, Default, List), + broadcast_blocklist_event(LUser, LServer, unblock_all), + {result, []}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +process_blocklist_unblock(LUser, LServer, JIDs) -> + F = + fun() -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> + % No lists, nothing to unblock + ok; + [#privacy{default = Default, + lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + % Default list, remove matching deny items + NewList = + lists:filter( + fun(#listitem{action = deny, + type = jid, + value = JID}) -> + not(lists:member(JID, JIDs)); + (_) -> + true + end, List), + + NewLists1 = lists:keydelete(Default, 1, Lists), + NewLists = [{Default, NewList} | NewLists1], + mnesia:write(P#privacy{lists = NewLists}), + + {ok, Default, NewList}; + false -> + % No default list, nothing to unblock + ok + end + end + end, + case mnesia:transaction(F) of + {atomic, {error, _} = Error} -> + Error; + {atomic, ok} -> + {result, []}; + {atomic, {ok, Default, List}} -> + broadcast_list_update(LUser, LServer, Default, List), + broadcast_blocklist_event(LUser, LServer, {unblock, JIDs}), + {result, []}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +broadcast_list_update(LUser, LServer, Name, List) -> + NeedDb = is_list_needdb(List), + ejabberd_router:route( + jlib:make_jid(LUser, LServer, ""), + jlib:make_jid(LUser, LServer, ""), + {xmlelement, "broadcast", [], + [{privacy_list, + #userlist{name = Name, list = List, needdb = NeedDb}, + Name}]}). + +broadcast_blocklist_event(LUser, LServer, Event) -> + JID = jlib:make_jid(LUser, LServer, ""), + ejabberd_router:route( + JID, JID, + {xmlelement, "broadcast", [], + [{blocking, Event}]}). + +process_blocklist_get(LUser, LServer) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + {'EXIT', _Reason} -> + {error, ?ERR_INTERNAL_SERVER_ERROR}; + [] -> + {result, [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], []}]}; + [#privacy{default = Default, lists = Lists}] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + JIDs = list_to_blocklist_jids(List, []), + Items = lists:map( + fun(JID) -> + ?DEBUG("JID: ~p",[JID]), + {xmlelement, "item", + [{"jid", jlib:jid_to_string(JID)}], []} + end, JIDs), + {result, + [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], + Items}]}; + _ -> + {result, [{xmlelement, "blocklist", [{"xmlns", ?NS_BLOCKING}], []}]} + end + end. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 10dcd3cee..b98db6ac7 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -5,7 +5,7 @@ %%% Created : 7 Oct 2006 by Magnus Henoch %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_configure.erl b/src/mod_configure.erl index cb50a9366..33657e9de 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -5,7 +5,7 @@ %%% Created : 19 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_configure2.erl b/src/mod_configure2.erl index ae6e8fa73..5a83bf048 100644 --- a/src/mod_configure2.erl +++ b/src/mod_configure2.erl @@ -5,7 +5,7 @@ %%% Created : 26 Oct 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 151bf0d9c..143d57091 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -5,7 +5,7 @@ %%% Created : 1 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_echo.erl b/src/mod_echo.erl index cf8b9cc92..f8d9ce622 100644 --- a/src/mod_echo.erl +++ b/src/mod_echo.erl @@ -5,7 +5,7 @@ %%% Created : 15 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_ip_blacklist.erl b/src/mod_ip_blacklist.erl index 16b70deaa..78df6c2a4 100644 --- a/src/mod_ip_blacklist.erl +++ b/src/mod_ip_blacklist.erl @@ -7,7 +7,7 @@ %%% {mod_ip_blacklist, []} %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_irc/iconv.erl b/src/mod_irc/iconv.erl index 47376f029..d80db78ac 100644 --- a/src/mod_irc/iconv.erl +++ b/src/mod_irc/iconv.erl @@ -5,7 +5,7 @@ %%% Created : 16 Feb 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_irc/iconv_erl.c b/src/mod_irc/iconv_erl.c index 3212568d9..9f4850074 100644 --- a/src/mod_irc/iconv_erl.c +++ b/src/mod_irc/iconv_erl.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl index b12f3b8b2..e610bf771 100644 --- a/src/mod_irc/mod_irc.erl +++ b/src/mod_irc/mod_irc.erl @@ -5,7 +5,7 @@ %%% Created : 15 Feb 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -425,7 +425,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, ?EJABBERD_URI}]}, {xmlelement, "DESC", [], [{xmlcdata, translate:translate(Lang, "ejabberd IRC module") ++ - "\nCopyright (c) 2003-2010 Alexey Shchepin"}]}]. + "\nCopyright (c) 2003-2011 ProcessOne"}]}]. command_items(Host, Lang) -> lists:map(fun({Node, Name, _Function}) diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl index 39a06a294..c342f2877 100644 --- a/src/mod_irc/mod_irc_connection.erl +++ b/src/mod_irc/mod_irc_connection.erl @@ -5,7 +5,7 @@ %%% Created : 15 Feb 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -219,6 +219,7 @@ handle_info({route_chan, Channel, Resource, NewStateData = case xml:get_attr_s("type", Attrs) of "unavailable" -> + send_stanza_unavailable(Channel, StateData), S1 = ?SEND(io_lib:format("PART #~s\r\n", [Channel])), S1#state{channels = dict:erase(Channel, S1#state.channels)}; @@ -656,13 +657,9 @@ terminate(_Reason, _StateName, FullStateData) -> bounce_messages("Server Connect Failed"), lists:foreach( fun(Chan) -> - ejabberd_router:route( - jlib:make_jid( - lists:concat([Chan, "%", StateData#state.server]), - StateData#state.host, StateData#state.nick), - StateData#state.user, - {xmlelement, "presence", [{"type", "error"}], - [Error]}) + Stanza = {xmlelement, "presence", [{"type", "error"}], + [Error]}, + send_stanza(Chan, StateData, Stanza) end, dict:fetch_keys(StateData#state.channels)), case StateData#state.socket of undefined -> @@ -672,6 +669,30 @@ terminate(_Reason, _StateName, FullStateData) -> end, ok. +send_stanza(Chan, StateData, Stanza) -> + ejabberd_router:route( + jlib:make_jid( + lists:concat([Chan, "%", StateData#state.server]), + StateData#state.host, StateData#state.nick), + StateData#state.user, + Stanza). + +send_stanza_unavailable(Chan, StateData) -> + Affiliation = "member", % this is a simplification + Role = "none", + Stanza = + {xmlelement, "presence", [{"type", "unavailable"}], + [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], + [{xmlelement, "item", + [{"affiliation", Affiliation}, + {"role", Role}], + []}, + {xmlelement, "status", + [{"code", "110"}], + []} + ]}]}, + send_stanza(Chan, StateData, Stanza). + %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- diff --git a/src/mod_last.erl b/src/mod_last.erl index a4c984dd1..2d930bef3 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -5,7 +5,7 @@ %%% Created : 24 Oct 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -116,7 +116,9 @@ process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) -> roster_get_jid_info, Server, {none, []}, [User, Server, From]), if - (Subscription == both) or (Subscription == from) -> + (Subscription == both) or (Subscription == from) + or ((From#jid.luser == To#jid.luser) + and (From#jid.lserver == To#jid.lserver)) -> UserListRecord = ejabberd_hooks:run_fold( privacy_get_user_list, Server, #userlist{}, diff --git a/src/mod_last_odbc.erl b/src/mod_last_odbc.erl index 2cc140059..791d94365 100644 --- a/src/mod_last_odbc.erl +++ b/src/mod_last_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 24 Oct 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index 5f6436077..078cb36c7 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -5,7 +5,7 @@ %%% Created : 19 Mar 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -42,6 +42,7 @@ process_iq_disco_items/4, broadcast_service_message/2, register_room/3, + node_up/1, migrate/1, get_vh_rooms/1, can_use_nick/3]). @@ -175,14 +176,33 @@ migrate(After) -> ['$$']}]), lists:foreach( fun([NameHost, Pid]) -> - case ejabberd_cluster:get_node_new(NameHost) of + case ejabberd_cluster:get_node(NameHost) of Node when Node /= node() -> - mod_muc_room:migrate(Pid, Node, After); + mod_muc_room:migrate(Pid, Node, random:uniform(After)); _ -> ok end end, Rs). +node_up(_Node) -> + copy_rooms(mnesia:dirty_first(muc_online_room)). + +copy_rooms('$end_of_table') -> + ok; +copy_rooms(Key) -> + case mnesia:dirty_read(muc_online_room, Key) of + [#muc_online_room{name_host = NameHost} = Room] -> + case ejabberd_cluster:get_node_new(NameHost) of + Node when node() /= Node -> + rpc:cast(Node, mnesia, dirty_write, [Room]); + _ -> + ok + end; + _ -> + ok + end, + copy_rooms(mnesia:dirty_next(muc_online_room, Key)). + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -219,6 +239,7 @@ init([Host, Opts]) -> DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []), RoomShaper = gen_mod:get_opt(room_shaper, Opts, none), ejabberd_router:register_route(MyHost), + ejabberd_hooks:add(node_up, ?MODULE, node_up, 100), ejabberd_hooks:add(node_hash_update, ?MODULE, migrate, 100), load_permanent_rooms(MyHost, Host, {Access, AccessCreate, AccessAdmin, AccessPersistent}, @@ -306,7 +327,15 @@ handle_info({room_destroyed, RoomHost, Pid}, State) -> mnesia:delete_object(#muc_online_room{name_host = RoomHost, pid = Pid}) end, - mnesia:sync_dirty(F), + mnesia:async_dirty(F), + case ejabberd_cluster:get_node_new(RoomHost) of + Node when Node /= node() -> + rpc:cast(Node, mnesia, dirty_delete_object, + [#muc_online_room{name_host = RoomHost, + pid = Pid}]); + _ -> + ok + end, {noreply, State}; handle_info(_Info, State) -> {noreply, State}. @@ -319,6 +348,7 @@ handle_info(_Info, State) -> %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, State) -> + ejabberd_hooks:delete(node_up, ?MODULE, node_up, 100), ejabberd_hooks:delete(node_hash_update, ?MODULE, migrate, 100), ejabberd_router:unregister_route(State#state.host), ok. @@ -507,14 +537,20 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper, AccessCreate, From, Room) of true -> - {ok, Pid} = start_new_room( - Host, ServerHost, Access, - Room, HistorySize, - RoomShaper, From, - Nick, DefRoomOpts), - register_room(Host, Room, Pid), - mod_muc_room:route(Pid, From, Nick, Packet), - ok; + case start_new_room( + Host, ServerHost, Access, + Room, HistorySize, + RoomShaper, From, + Nick, DefRoomOpts) of + {ok, Pid} -> + mod_muc_room:route(Pid, From, Nick, Packet), + register_room(Host, Room, Pid), + ok; + _Err -> + Err = jlib:make_error_reply( + Packet, ?ERR_INTERNAL_SERVER_ERROR), + ejabberd_router:route(To, From, Err) + end; false -> Lang = xml:get_attr_s("xml:lang", Attrs), ErrText = "Room creation is denied by service policy", @@ -603,7 +639,28 @@ register_room(Host, Room, Pid) -> mnesia:write(#muc_online_room{name_host = {Room, Host}, pid = Pid}) end, - mnesia:sync_dirty(F). + mnesia:async_dirty(F), + case ejabberd_cluster:get_node_new({Room, Host}) of + Node when Node /= node() -> + %% New node has just been added. But we may miss MUC records + %% copy procedure, so we copy the MUC record manually just + %% to make sure + rpc:cast(Node, mnesia, dirty_write, + [#muc_online_room{name_host = {Room, Host}, + pid = Pid}]), + case ejabberd_cluster:get_node({Room, Host}) of + Node when node() /= Node -> + %% Migration to new node has completed, and seems like + %% we missed it, so we migrate the MUC room pid manually. + %% It is not a problem if we have already got migration + %% notification: dups are just ignored by the MUC room pid. + mod_muc_room:migrate(Pid, Node, 0); + _ -> + ok + end; + _ -> + ok + end. iq_disco_info(Lang) -> [{xmlelement, "identity", @@ -847,7 +904,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, ?EJABBERD_URI}]}, {xmlelement, "DESC", [], [{xmlcdata, translate:translate(Lang, "ejabberd MUC module") ++ - "\nCopyright (c) 2003-2010 Alexey Shchepin"}]}]. + "\nCopyright (c) 2003-2011 ProcessOne"}]}]. broadcast_service_message(Host, Msg) -> diff --git a/src/mod_muc/mod_muc_log.erl b/src/mod_muc/mod_muc_log.erl index fbc2d5cbe..d92ac7963 100644 --- a/src/mod_muc/mod_muc_log.erl +++ b/src/mod_muc/mod_muc_log.erl @@ -5,7 +5,7 @@ %%% Created : 12 Mar 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index f28b04e2b..5bf88a675 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -5,7 +5,7 @@ %%% Created : 19 Mar 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -110,7 +110,7 @@ start_link(StateName, StateData) -> ?GEN_FSM:start_link(?MODULE, [StateName, StateData], ?FSMOPTS). migrate(FsmRef, Node, After) -> - ?GEN_FSM:send_all_state_event(FsmRef, {migrate, Node, After}). + erlang:send_after(After, FsmRef, {migrate, Node}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_fsm @@ -597,9 +597,6 @@ handle_event(destroy, StateName, StateData) -> handle_event({set_affiliations, Affiliations}, StateName, StateData) -> {next_state, StateName, StateData#state{affiliations = Affiliations}}; -handle_event({migrate, Node, After}, StateName, StateData) when Node /= node() -> - {migrate, StateData, - {Node, ?MODULE, start, [StateName, StateData]}, After * 2}; handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. @@ -716,6 +713,13 @@ handle_info({captcha_failed, From}, normal_state, StateData) -> StateData end, {next_state, normal_state, NewState}; +handle_info({migrate, Node}, StateName, StateData) -> + if Node /= node() -> + {migrate, StateData, + {Node, ?MODULE, start, [StateName, StateData]}, 0}; + true -> + {next_state, StateName, StateData} + end; handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. @@ -870,9 +874,6 @@ process_groupchat_message(From, {xmlelement, "message", Attrs, _Els} = Packet, %% an implementation MAY allow users with certain privileges %% (e.g., a room owner, room admin, or service-level admin) %% to send messages to the room even if those users are not occupants. -%% -%% Check the mod_muc option access_message_nonparticipant and wether this JID -%% is allowed or denied is_user_allowed_message_nonparticipant(JID, StateData) -> case get_service_affiliation(JID, StateData) of owner -> @@ -1665,15 +1666,25 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> SID = xml:get_attr_s("id", Attrs), RoomJID = StateData#state.jid, To = jlib:jid_replace_resource(RoomJID, Nick), + Limiter = {From#jid.luser, From#jid.lserver}, case ejabberd_captcha:create_captcha( - SID, RoomJID, To, Lang, From) of + SID, RoomJID, To, Lang, Limiter, From) of {ok, ID, CaptchaEls} -> MsgPkt = {xmlelement, "message", [{"id", ID}], CaptchaEls}, Robots = ?DICT:store(From, {Nick, Packet}, StateData#state.robots), ejabberd_router:route(RoomJID, From, MsgPkt), StateData#state{robots = Robots}; - error -> + {error, limit} -> + ErrText = "Too many CAPTCHA requests", + Err = jlib:make_error_reply( + Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)), + ejabberd_router:route( % TODO: s/Nick/""/ + jlib:jid_replace_resource( + StateData#state.jid, Nick), + From, Err), + StateData; + _ -> ErrText = "Unable to generate a captcha", Err = jlib:make_error_reply( Packet, ?ERRT_INTERNAL_SERVER_ERROR(Lang, ErrText)), @@ -1725,7 +1736,24 @@ check_captcha(Affiliation, From, StateData) -> {ok, passed} -> true; _ -> - captcha_required + WList = (StateData#state.config)#config.captcha_whitelist, + #jid{luser = U, lserver = S, lresource = R} = From, + case ?SETS:is_element({U, S, R}, WList) of + true -> + true; + false -> + case ?SETS:is_element({U, S, ""}, WList) of + true -> + true; + false -> + case ?SETS:is_element({"", S, ""}, WList) of + true -> + true; + false -> + captcha_required + end + end + end end; _ -> true @@ -2525,6 +2553,11 @@ can_change_ra(_FAffiliation, _FRole, %% A room owner tries to add as persistent owner a %% participant that is already owner because he is MUC admin true; +can_change_ra(_FAffiliation, _FRole, + _TAffiliation, _TRole, + _RoleorAffiliation, _Value, owner) -> + %% Nobody can decrease MUC admin's role/affiliation + false; can_change_ra(_FAffiliation, _FRole, TAffiliation, _TRole, affiliation, Value, _ServiceAf) @@ -2699,14 +2732,21 @@ send_kickban_presence(JID, Reason, Code, NewAffiliation, StateData) -> end, LJIDs). send_kickban_presence1(UJID, Reason, Code, Affiliation, StateData) -> - {ok, #user{jid = _RealJID, + {ok, #user{jid = RealJID, nick = Nick}} = ?DICT:find(jlib:jid_tolower(UJID), StateData#state.users), SAffiliation = affiliation_to_list(Affiliation), + BannedJIDString = jlib:jid_to_string(RealJID), lists:foreach( fun({_LJID, Info}) -> + JidAttrList = case (Info#user.role == moderator) orelse + ((StateData#state.config)#config.anonymous + == false) of + true -> [{"jid", BannedJIDString}]; + false -> [] + end, ItemAttrs = [{"affiliation", SAffiliation}, - {"role", "none"}], + {"role", "none"}] ++ JidAttrList, ItemEls = case Reason of "" -> []; @@ -2907,6 +2947,13 @@ is_password_settings_correct(XEl, StateData) -> -define(PRIVATEXFIELD(Label, Var, Val), ?XFIELD("text-private", Label, Var, Val)). +-define(JIDMULTIXFIELD(Label, Var, JIDList), + {xmlelement, "field", [{"type", "jid-multi"}, + {"label", translate:translate(Lang, Label)}, + {"var", Var}], + [{xmlelement, "value", [], [{xmlcdata, jlib:jid_to_string(JID)}]} + || JID <- JIDList]}). + get_default_room_maxusers(RoomState) -> DefRoomOpts = gen_mod:get_module_opt(RoomState#state.server_host, mod_muc, default_room_options, []), RoomState2 = set_opts(DefRoomOpts, RoomState), @@ -3027,6 +3074,9 @@ get_config(Lang, StateData, From) -> Config#config.captcha_protected)]; false -> [] end ++ + [?JIDMULTIXFIELD("Exclude Jabber IDs from CAPTCHA challenge", + "muc#roomconfig_captcha_whitelist", + ?SETS:to_list(Config#config.captcha_whitelist))] ++ case mod_muc_log:check_access_log( StateData#state.server_host, From) of allow -> @@ -3096,6 +3146,18 @@ set_config(XEl, StateData) -> -define(SET_STRING_XOPT(Opt, Val), set_xoption(Opts, Config#config{Opt = Val})). +-define(SET_JIDMULTI_XOPT(Opt, Vals), + begin + Set = lists:foldl( + fun({U, S, R}, Set1) -> + ?SETS:add_element({U, S, R}, Set1); + (#jid{luser = U, lserver = S, lresource = R}, Set1) -> + ?SETS:add_element({U, S, R}, Set1); + (_, Set1) -> + Set1 + end, ?SETS:empty(), Vals), + set_xoption(Opts, Config#config{Opt = Set}) + end). set_xoption([], Config) -> Config; @@ -3153,6 +3215,9 @@ set_xoption([{"muc#roomconfig_maxusers", [Val]} | Opts], Config) -> end; set_xoption([{"muc#roomconfig_enablelogging", [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(logging, Val); +set_xoption([{"muc#roomconfig_captcha_whitelist", Vals} | Opts], Config) -> + JIDs = [jlib:string_to_jid(Val) || Val <- Vals], + ?SET_JIDMULTI_XOPT(captcha_whitelist, JIDs); set_xoption([{"FORM_TYPE", _} | Opts], Config) -> %% Ignore our FORM_TYPE set_xoption(Opts, Config); @@ -3222,6 +3287,7 @@ set_opts([{Opt, Val} | Opts], StateData) -> password -> StateData#state{config = (StateData#state.config)#config{password = Val}}; anonymous -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}}; logging -> StateData#state{config = (StateData#state.config)#config{logging = Val}}; + captcha_whitelist -> StateData#state{config = (StateData#state.config)#config{captcha_whitelist = ?SETS:from_list(Val)}}; max_users -> ServiceMaxUsers = get_service_max_users(StateData), MaxUsers = if @@ -3266,6 +3332,8 @@ make_opts(StateData) -> ?MAKE_CONFIG_OPT(anonymous), ?MAKE_CONFIG_OPT(logging), ?MAKE_CONFIG_OPT(max_users), + {captcha_whitelist, + ?SETS:to_list((StateData#state.config)#config.captcha_whitelist)}, {affiliations, ?DICT:to_list(StateData#state.affiliations)}, {subject, StateData#state.subject}, {subject_author, StateData#state.subject_author} diff --git a/src/mod_muc/mod_muc_room.hrl b/src/mod_muc/mod_muc_room.hrl index f1e9063a8..192ecf845 100644 --- a/src/mod_muc/mod_muc_room.hrl +++ b/src/mod_muc/mod_muc_room.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -45,7 +45,8 @@ password = "", anonymous = true, max_users = ?MAX_USERS_DEFAULT, - logging = false + logging = false, + captcha_whitelist = ?SETS:empty() }). -record(user, {jid, diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 2448a17c0..5acdd4715 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -5,7 +5,7 @@ %%% Created : 5 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_offline_odbc.erl b/src/mod_offline_odbc.erl index 420ff581f..63cf34945 100644 --- a/src/mod_offline_odbc.erl +++ b/src/mod_offline_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 5 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_ping.erl b/src/mod_ping.erl index a650f983e..463bffb5c 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -5,7 +5,7 @@ %%% Created : 11 Jul 2009 by Brian Cully %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -95,7 +95,7 @@ init([Host, Opts]) -> SendPings = gen_mod:get_opt(send_pings, Opts, ?DEFAULT_SEND_PINGS), PingInterval = gen_mod:get_opt(ping_interval, Opts, ?DEFAULT_PING_INTERVAL), TimeoutAction = gen_mod:get_opt(timeout_action, Opts, none), - IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), + IQDisc = gen_mod:get_opt(iqdisc, Opts, no_queue), mod_disco:register_feature(Host, ?NS_PING), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PING, ?MODULE, iq_ping, IQDisc), diff --git a/src/mod_pres_counter.erl b/src/mod_pres_counter.erl new file mode 100644 index 000000000..0e520b57a --- /dev/null +++ b/src/mod_pres_counter.erl @@ -0,0 +1,134 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_pres_counter.erl +%%% Author : Ahmed Omar +%%% Purpose : Presence subscription flood prevention +%%% Created : 23 Sep 2010 by Ahmed Omar +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-module(mod_pres_counter). + +-behavior(gen_mod). + +-export([start/2, + stop/1, + check_packet/6]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). + +-record(pres_counter, {dir, start, count, logged = false}). + +start(Host, _Opts) -> + ejabberd_hooks:add(privacy_check_packet, Host, + ?MODULE, check_packet, 25), + ok. + +stop(Host) -> + ejabberd_hooks:delete(privacy_check_packet, Host, + ?MODULE, check_packet, 25), + ok. + +check_packet(_, _User, Server, + _PrivacyList, + {From, To, {xmlelement, Name, Attrs, _}}, + Dir) -> + case Name of + "presence" -> + IsSubscription = + case xml:get_attr_s("type", Attrs) of + "subscribe" -> true; + "subscribed" -> true; + "unsubscribe" -> true; + "unsubscribed" -> true; + _ -> false + end, + if + IsSubscription -> + JID = case Dir of + in -> To; + out -> From + end, + update(Server, JID, Dir); + true -> + allow + end; + _ -> + allow + end. + +update(Server, JID, Dir) -> + %% get options + StormCount = gen_mod:get_module_opt(Server, ?MODULE, count, 5), + TimeInterval = gen_mod:get_module_opt(Server, ?MODULE, interval, 60), + {MegaSecs, Secs, _MicroSecs} = now(), + TimeStamp = MegaSecs * 1000000 + Secs, + case read(Dir) of + undefined -> + write(Dir, #pres_counter{dir = Dir, + start = TimeStamp, + count = 1}), + allow; + #pres_counter{start = TimeStart, count = Count, logged = Logged} = R -> + %% record for this key exists, check if we're + %% within TimeInterval seconds, and whether the StormCount is + %% high enough. or else just increment the count. + if + TimeStamp - TimeStart > TimeInterval -> + write(Dir, R#pres_counter{ + start = TimeStamp, + count = 1}), + allow; + (Count =:= StormCount) and Logged -> + {stop, deny}; + Count =:= StormCount -> + write(Dir, R#pres_counter{logged = true}), + case Dir of + in -> + ?WARNING_MSG( + "User ~s is being flooded, " + "ignoring received presence subscriptions", + [jlib:jid_to_string(JID)]); + out -> + IP = ejabberd_sm:get_user_ip( + JID#jid.luser, + JID#jid.lserver, + JID#jid.lresource), + ?WARNING_MSG( + "Flooder detected: ~s, on IP: ~s " + "ignoring sent presence subscriptions~n", + [jlib:jid_to_string(JID), + jlib:ip_to_list(IP)]) + end, + {stop, deny}; + true -> + write(Dir, R#pres_counter{ + start = TimeStamp, + count = Count + 1}), + allow + end + end. + +read(K)-> + get({pres_counter, K}). + +write(K, V)-> + put({pres_counter, K}, V). diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index d25bc9b7b..38ecf138b 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -5,7 +5,7 @@ %%% Created : 21 Jul 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_privacy.hrl b/src/mod_privacy.hrl index 3f4489b10..657916e02 100644 --- a/src/mod_privacy.hrl +++ b/src/mod_privacy.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_privacy_odbc.erl b/src/mod_privacy_odbc.erl index 593e99140..2df9ee27a 100644 --- a/src/mod_privacy_odbc.erl +++ b/src/mod_privacy_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 5 Oct 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -554,9 +554,12 @@ get_user_list(_, User, Server) -> end. +%% From is the sender, To is the destination. +%% If Dir = out, User@Server is the sender account (From). +%% If Dir = in, User@Server is the destination account (To). check_packet(_, User, Server, #userlist{list = List, needdb = NeedDb}, - {From, To, {xmlelement, PName, _, _}}, + {From, To, {xmlelement, PName, Attrs, _}}, Dir) -> case List of [] -> @@ -565,48 +568,36 @@ check_packet(_, User, Server, PType = case PName of "message" -> message; "iq" -> iq; - "presence" -> presence + "presence" -> + case xml:get_attr_s("type", Attrs) of + %% notification + "" -> presence; + "unavailable" -> presence; + %% subscribe, subscribed, unsubscribe, + %% unsubscribed, error, probe, or other + _ -> other + end end, - case {PType, Dir} of - {message, in} -> - LJID = jlib:jid_tolower(From), - {Subscription, Groups} = - case NeedDb of - true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]); - false -> {[], []} - end, - check_packet_aux(List, message, - LJID, Subscription, Groups); - {iq, in} -> - LJID = jlib:jid_tolower(From), - {Subscription, Groups} = - case NeedDb of - true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]); - false -> {[], []} - end, - check_packet_aux(List, iq, - LJID, Subscription, Groups); - {presence, in} -> - LJID = jlib:jid_tolower(From), - {Subscription, Groups} = - case NeedDb of - true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]); - false -> {[], []} - end, - check_packet_aux(List, presence_in, - LJID, Subscription, Groups); - {presence, out} -> - LJID = jlib:jid_tolower(To), - {Subscription, Groups} = - case NeedDb of - true -> ejabberd_hooks:run_fold(roster_get_jid_info, jlib:nameprep(Server), {none, []}, [User, Server, LJID]); - false -> {[], []} - end, - check_packet_aux(List, presence_out, - LJID, Subscription, Groups); - _ -> - allow - end + PType2 = case {PType, Dir} of + {message, in} -> message; + {iq, in} -> iq; + {presence, in} -> presence_in; + {presence, out} -> presence_out; + {_, _} -> other + end, + LJID = case Dir of + in -> jlib:jid_tolower(From); + out -> jlib:jid_tolower(To) + end, + {Subscription, Groups} = + case NeedDb of + true -> ejabberd_hooks:run_fold(roster_get_jid_info, + jlib:nameprep(Server), + {none, []}, + [User, Server, LJID]); + false -> {[], []} + end, + check_packet_aux(List, PType2, LJID, Subscription, Groups) end. check_packet_aux([], _PType, _JID, _Subscription, _Groups) -> @@ -646,7 +637,9 @@ is_ptype_match(Item, PType) -> presence_in -> Item#listitem.match_presence_in; presence_out -> - Item#listitem.match_presence_out + Item#listitem.match_presence_out; + other -> + false end end. @@ -758,9 +751,9 @@ item_to_raw(#listitem{type = Type, none -> {"n", ""}; jid -> - {"j", jlib:jid_to_string(Value)}; + {"j", ejabberd_odbc:escape(jlib:jid_to_string(Value))}; group -> - {"g", Value}; + {"g", ejabberd_odbc:escape(Value)}; subscription -> case Value of none -> diff --git a/src/mod_private.erl b/src/mod_private.erl index 05696e08d..f8ba72d47 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -5,7 +5,7 @@ %%% Created : 16 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_private_odbc.erl b/src/mod_private_odbc.erl index 13e6683b4..6ca1ab7ed 100644 --- a/src/mod_private_odbc.erl +++ b/src/mod_private_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 5 Oct 2006 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_proxy65/mod_proxy65.erl b/src/mod_proxy65/mod_proxy65.erl index bd0d54bb7..584b6aa32 100644 --- a/src/mod_proxy65/mod_proxy65.erl +++ b/src/mod_proxy65/mod_proxy65.erl @@ -5,7 +5,7 @@ %%% Created : 12 Oct 2006 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_proxy65/mod_proxy65.hrl b/src/mod_proxy65/mod_proxy65.hrl index 04ecacd3f..d19b28b0a 100644 --- a/src/mod_proxy65/mod_proxy65.hrl +++ b/src/mod_proxy65/mod_proxy65.hrl @@ -2,7 +2,7 @@ %%% RFC 1928 constants. %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_proxy65/mod_proxy65_lib.erl b/src/mod_proxy65/mod_proxy65_lib.erl index 2740f9846..154989fa5 100644 --- a/src/mod_proxy65/mod_proxy65_lib.erl +++ b/src/mod_proxy65/mod_proxy65_lib.erl @@ -5,7 +5,7 @@ %%% Created : 12 Oct 2006 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_proxy65/mod_proxy65_service.erl b/src/mod_proxy65/mod_proxy65_service.erl index b2869e3af..9ba006347 100644 --- a/src/mod_proxy65/mod_proxy65_service.erl +++ b/src/mod_proxy65/mod_proxy65_service.erl @@ -5,7 +5,7 @@ %%% Created : 12 Oct 2006 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -211,7 +211,7 @@ iq_vcard(Lang) -> [{xmlcdata, ?EJABBERD_URI}]}, {xmlelement, "DESC", [], [{xmlcdata, translate:translate(Lang, "ejabberd SOCKS5 Bytestreams module") ++ - "\nCopyright (c) 2003-2010 Alexey Shchepin"}]}]. + "\nCopyright (c) 2003-2011 ProcessOne"}]}]. parse_options(ServerHost, Opts) -> MyHost = gen_mod:get_opt_host(ServerHost, Opts, "proxy.@HOST@"), diff --git a/src/mod_proxy65/mod_proxy65_sm.erl b/src/mod_proxy65/mod_proxy65_sm.erl index bdd1b5bb8..1174830ed 100644 --- a/src/mod_proxy65/mod_proxy65_sm.erl +++ b/src/mod_proxy65/mod_proxy65_sm.erl @@ -5,7 +5,7 @@ %%% Created : 12 Oct 2006 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_proxy65/mod_proxy65_stream.erl b/src/mod_proxy65/mod_proxy65_stream.erl index f1b6ae245..ded48b2eb 100644 --- a/src/mod_proxy65/mod_proxy65_stream.erl +++ b/src/mod_proxy65/mod_proxy65_stream.erl @@ -4,7 +4,7 @@ %%% Purpose : Bytestream process. %%% Created : 12 Oct 2006 by Evgeniy Khramtsov %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_pubsub/gen_pubsub_node.erl b/src/mod_pubsub/gen_pubsub_node.erl index b0209809d..204a217fd 100644 --- a/src/mod_pubsub/gen_pubsub_node.erl +++ b/src/mod_pubsub/gen_pubsub_node.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/gen_pubsub_nodetree.erl b/src/mod_pubsub/gen_pubsub_nodetree.erl index f0767b8dc..b69f643be 100644 --- a/src/mod_pubsub/gen_pubsub_nodetree.erl +++ b/src/mod_pubsub/gen_pubsub_nodetree.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/mod_pubsub.erl b/src/mod_pubsub/mod_pubsub.erl index 8bba3be9b..42c0efadf 100644 --- a/src/mod_pubsub/mod_pubsub.erl +++ b/src/mod_pubsub/mod_pubsub.erl @@ -11,11 +11,11 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} @@ -78,7 +78,7 @@ %% exports for console debug manual use -export([create_node/5, - create_node/7, + create_node/7, delete_node/3, subscribe_node/5, unsubscribe_node/5, @@ -88,7 +88,6 @@ get_items/2, get_item/3, get_cached_item/2, - broadcast_stanza/9, get_configure/5, set_configure/5, tree_action/3, @@ -480,6 +479,55 @@ update_state_database(_Host, _ServerHost) -> ?ERROR_MSG("Problem updating Pubsub state tables:~n~p", [Reason]) end; + [stateid, items, affiliation, subscriptions] -> + ?INFO_MSG("upgrade state pubsub table", []), + F = fun ({pubsub_state, {JID, Nidx}, Items, Aff, Subs}, Acc) -> + NewState = #pubsub_state{stateid = {JID, Nidx}, + nodeidx = Nidx, + items = Items, + affiliation = Aff, + subscriptions = Subs}, + [NewState | Acc] + end, + {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3, + [F, [], pubsub_state]), + {atomic, ok} = mnesia:delete_table(pubsub_state), + {atomic, ok} = mnesia:create_table(pubsub_state, + [{disc_copies, [node()]}, + {attributes, record_info(fields, pubsub_state)}]), + FNew = fun () -> + lists:foreach(fun mnesia:write/1, NewRecs) + end, + case mnesia:transaction(FNew) of + {atomic, Res1} -> + ?INFO_MSG("Pubsub state tables updated correctly: ~p", [Res1]); + {aborted, Rea1} -> + ?ERROR_MSG("Problem updating Pubsub state table:~n~p", [Rea1]) + end, + ?INFO_MSG("upgrade item pubsub table", []), + F = fun ({pubsub_item, {ItemId, Nidx}, C, M, P}, Acc) -> + NewItem = #pubsub_item{itemid = {ItemId, Nidx}, + nodeidx = Nidx, + creation = C, + modification = M, + payload = P}, + [NewItem | Acc] + end, + {atomic, NewRecs} = mnesia:transaction(fun mnesia:foldl/3, + [F, [], pubsub_item]), + {atomic, ok} = mnesia:delete_table(pubsub_item), + {atomic, ok} = mnesia:create_table(pubsub_item, + [{disc_copies, [node()]}, + {attributes, record_info(fields, pubsub_item)}]), + FNew = fun () -> + lists:foreach(fun mnesia:write/1, NewRecs) + end, + case mnesia:transaction(FNew) of + {atomic, Res2} -> + ?INFO_MSG("Pubsub item tables updated correctly: ~p", [Res2]); + {aborted, Rea2} -> + ?ERROR_MSG("Problem updating Pubsub item table:~n~p", [Rea2]) + end; _ -> ok end. @@ -1212,7 +1260,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, translate:translate(Lang, "ejabberd Publish-Subscribe module") ++ - "\nCopyright (c) 2004-2010 ProcessOne"}]}]. + "\nCopyright (c) 2004-2011 ProcessOne"}]}]. iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). @@ -1760,10 +1808,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> {result, true} -> case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of {ok, NodeId} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, Owner]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], case node_call(Type, create_node, [NodeId, Owner]) of - {result, Result} -> {result, {NodeId, SubsByDepth, Result}}; + {result, Result} -> {result, {NodeId, Result}}; Error -> Error end; {error, {virtual, NodeId}} -> @@ -1782,15 +1828,15 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> [{xmlelement, "create", nodeAttr(Node), []}]}], case transaction(CreateNode, transaction) of - {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), + {result, {NodeId, {Result, broadcast}}} -> + broadcast_created_node(Host, Node, NodeId, Type, NodeOptions), case Result of default -> {result, Reply}; _ -> {result, Result} end; - {result, {_NodeId, _SubsByDepth, default}} -> + {result, {_NodeId, default}} -> {result, Reply}; - {result, {_NodeId, _SubsByDepth, Result}} -> + {result, {_NodeId, Result}} -> {result, Result}; Error -> %% in case we change transaction to sync_dirty... @@ -1822,11 +1868,9 @@ delete_node(Host, Node, Owner) -> Action = fun(#pubsub_node{type = Type, id = NodeId}) -> case node_call(Type, get_affiliation, [NodeId, Owner]) of {result, owner} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], Removed = tree_call(Host, delete_node, [Host, Node]), case node_call(Type, delete_node, [Removed]) of - {result, Res} -> {result, {SubsByDepth, Res}}; + {result, Res} -> {result, Res}; Error -> Error end; _ -> @@ -1836,26 +1880,26 @@ delete_node(Host, Node, Owner) -> end, Reply = [], case transaction(Host, Node, Action, transaction) of - {result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} -> + {result, {_, {Result, broadcast, Removed}}} -> lists:foreach(fun({RNode, _RSubscriptions}) -> {RH, RN} = RNode#pubsub_node.nodeid, NodeId = RNode#pubsub_node.id, Type = RNode#pubsub_node.type, Options = RNode#pubsub_node.options, - broadcast_removed_node(RH, RN, NodeId, Type, Options, SubsByDepth) + broadcast_removed_node(RH, RN, NodeId, Type, Options) end, Removed), case Result of default -> {result, Reply}; _ -> {result, Result} end; - {result, {_, {_, {Result, _Removed}}}} -> + {result, {_, {Result, _Removed}}} -> case Result of default -> {result, Reply}; _ -> {result, Result} end; - {result, {_, {_, default}}} -> + {result, {_, default}} -> {result, Reply}; - {result, {_, {_, Result}}} -> + {result, {_, Result}} -> {result, Result}; Error -> Error @@ -2026,7 +2070,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> DeliverPayloads = get_option(Options, deliver_payloads), PersistItems = get_option(Options, persist_items), PayloadCount = payload_xmlelements(Payload), - PayloadSize = size(term_to_binary(Payload)), + PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2 PayloadMaxSize = get_option(Options, max_payload_size), % pubsub#deliver_payloads true % pubsub#persist_items true -> 1 item; false -> 0 item @@ -2062,12 +2106,11 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> NodeId = TNode#pubsub_node.id, Type = TNode#pubsub_node.type, Options = TNode#pubsub_node.options, - BroadcastPayload = case Broadcast of - default -> Payload; + BrPayload = case Broadcast of broadcast -> Payload; PluginPayload -> PluginPayload end, - broadcast_publish_item(Host, Node, NodeId, Type, Options, Removed, ItemId, jlib:jid_tolower(Publisher), BroadcastPayload), + broadcast_publish_item(Host, Node, NodeId, Type, Options, ItemId, jlib:jid_tolower(Publisher), BrPayload, Removed), set_cached_item(Host, NodeId, ItemId, Publisher, Payload), case Result of default -> {result, Reply}; @@ -2535,14 +2578,18 @@ get_options_helper(JID, Lang, Node, NodeID, SubID, Type) -> read_sub(Subscriber, Node, NodeID, SubID, Lang) -> case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; {result, #pubsub_subscription{options = Options}} -> {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options), OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, {"subid", SubID}|nodeAttr(Node)], [XdataEl]}, PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]}, + {result, PubsubEl}; + _ -> + OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, + {"subid", SubID}|nodeAttr(Node)], + []}, + PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]}, {result, PubsubEl} end. @@ -2592,12 +2639,14 @@ set_options_helper(Configuration, JID, NodeID, SubID, Type) -> write_sub(_Subscriber, _NodeID, _SubID, invalid) -> {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")}; +write_sub(_Subscriber, _NodeID, _SubID, []) -> + {result, []}; write_sub(Subscriber, NodeID, SubID, Options) -> case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; {result, _} -> - {result, []} + {result, []}; + {error, _} -> + {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")} end. %% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} @@ -2891,21 +2940,20 @@ sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) -> sub_option_can_deliver(NotifyType, Depth, Option) end, SubOptions). +node_to_deliver(LJID, NodeOptions) -> + presence_can_deliver(LJID, get_option(NodeOptions, presence_based_delivery)). + sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false; sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false; sub_option_can_deliver(_, _, {subscription_depth, all}) -> true; sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D; -sub_option_can_deliver(_, _, {deliver, false}) -> false; -sub_option_can_deliver(_, _, {expire, When}) -> now() < When; -sub_option_can_deliver(_, _, _) -> true. - -node_to_deliver(LJID, NodeOptions) -> - PresenceDelivery = get_option(NodeOptions, presence_based_delivery), - presence_can_deliver(LJID, PresenceDelivery). +sub_option_can_deliver(_, _, {deliver, false}) -> false; +sub_option_can_deliver(_, _, {expire, When}) -> now() < When; +sub_option_can_deliver(_, _, _) -> true. presence_can_deliver(_, false) -> true; presence_can_deliver({User, Server, Resource}, true) -> - case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of + case ejabberd_sm:get_user_sessions(User, Server) of [] -> false; Ss -> lists:foldl(fun(_, true) -> true; @@ -2923,45 +2971,45 @@ state_can_deliver({U, S, R}, []) -> [{U, S, R}]; state_can_deliver({U, S, R}, SubOptions) -> %% Check SubOptions for 'show_values' case lists:keysearch('show_values', 1, SubOptions) of - %% If not in suboptions, item can be delivered, case doesn't apply - false -> [{U, S, R}]; - %% If in a suboptions ... - {_, {_, ShowValues}} -> - %% Get subscriber resources - Resources = case R of - %% If the subscriber JID is a bare one, get all its resources - [] -> user_resources(U, S); - %% If the subscriber JID is a full one, use its resource - R -> [R] - end, - %% For each resource, test if the item is allowed to be delivered - %% based on resource state - lists:foldl( - fun(Resource, Acc) -> - get_resource_state({U, S, Resource}, ShowValues, Acc) - end, [], Resources) + %% If not in suboptions, item can be delivered, case doesn't apply + false -> [{U, S, R}]; + %% If in a suboptions ... + {_, {_, ShowValues}} -> + %% Get subscriber resources + Resources = case R of + %% If the subscriber JID is a bare one, get all its resources + [] -> user_resources(U, S); + %% If the subscriber JID is a full one, use its resource + R -> [R] + end, + %% For each resource, test if the item is allowed to be delivered + %% based on resource state + lists:foldl( + fun(Resource, Acc) -> + get_resource_state({U, S, Resource}, ShowValues, Acc) + end, [], Resources) end. get_resource_state({U, S, R}, ShowValues, JIDs) -> %% Get user session PID case ejabberd_sm:get_session_pid(U, S, R) of - %% If no PID, item can be delivered - none -> lists:append([{U, S, R}], JIDs); - %% If PID ... - Pid -> - %% Get user resource state - %% TODO : add a catch clause - Show = case ejabberd_c2s:get_presence(Pid) of - {_, _, "available", _} -> "online"; - {_, _, State, _} -> State - end, - %% Is current resource state listed in 'show-values' suboption ? - case lists:member(Show, ShowValues) of %andalso Show =/= "online" of - %% If yes, item can be delivered - true -> lists:append([{U, S, R}], JIDs); - %% If no, item can't be delivered - false -> JIDs - end + %% If no PID, item can be delivered + none -> lists:append([{U, S, R}], JIDs); + %% If PID ... + Pid -> + %% Get user resource state + %% TODO : add a catch clause + Show = case ejabberd_c2s:get_presence(Pid) of + {_, _, "available", _} -> "online"; + {_, _, State, _} -> State + end, + %% Is current resource state listed in 'show-values' suboption ? + case lists:member(Show, ShowValues) of %andalso Show =/= "online" of + %% If yes, item can be delivered + true -> lists:append([{U, S, R}], JIDs); + %% If no, item can't be delivered + false -> JIDs + end end. %% @spec (Payload) -> int() @@ -2987,223 +3035,149 @@ event_stanza_withmoreels(Els, MoreEls) -> {xmlelement, "message", [], [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], Els} | MoreEls]}. +event_stanza(Event, EvAttr) -> + event_stanza([{xmlelement, Event, EvAttr, []}]). +event_stanza(Event, EvAttr, Entries) -> + event_stanza([{xmlelement, Event, EvAttr, + [{xmlelement, Entry, EnAttr, []} || {Entry, EnAttr} <- Entries]}]). +event_stanza(Event, EvAttr, Entry, EnAttr, Payload) -> + event_stanza([{xmlelement, Event, EvAttr, [{xmlelement, Entry, EnAttr, Payload}]}]). +event_stanza(Event, EvAttr, Entry, EnAttr, Payload, Publisher) -> + Stanza = event_stanza(Event, EvAttr, Entry, EnAttr, Payload), + add_extended_headers(Stanza, extended_headers([jlib:jid_to_string(Publisher)])). + %%%%%% broadcast functions -broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, From, Payload) -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> Payload; - false -> [] - end, - Stanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "item", itemAttr(ItemId), Content}]}]), - broadcast_stanza(Host, From, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - case Removed of - [] -> - ok; - _ -> - case get_option(NodeOptions, notify_retract) of - true -> - RetractStanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, - items, RetractStanza, true); - _ -> - ok - end - end, - {result, true}; +broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, ItemId, Publisher, Payload, Removed) -> + Publish = case get_option(NodeOptions, deliver_payloads) of + true -> event_stanza("items", nodeAttr(Node), "item", itemAttr(ItemId), Payload, Publisher); + false -> event_stanza("items", nodeAttr(Node), "item", itemAttr(ItemId), [], Publisher) + end, + case Removed of + [] -> + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, items, true, Publish, true)}; _ -> - {result, false} + Retract = event_stanza("items", nodeAttr(Node), [{"retract", itemAttr(Rid)} || Rid <- Removed]), + Stanzas = [{true, Publish, true}, {get_option(NodeOptions, notify_retract), Retract, true}], + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, items, Stanzas)} end. broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) -> - broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false). -broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) -> + broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, notify_retract). +broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _) -> {result, false}; -broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) -> - case (get_option(NodeOptions, notify_retract) or ForceNotify) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. +broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false) -> + broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, notify_retract); +broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, Notify) -> + Stanza = event_stanza("items", nodeAttr(Node), [{"retract", itemAttr(Rid)} || Rid <- ItemIds]), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, items, Notify, Stanza, true)}. broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) -> - case get_option(NodeOptions, notify_retract) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [{xmlelement, "purge", nodeAttr(Node), - []}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. + Stanza = event_stanza("purge", nodeAttr(Node)), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, notify_retract, Stanza, false)}. -broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - case get_option(NodeOptions, notify_delete) of - true -> - case SubsByDepth of - [] -> - {result, false}; - _ -> - Stanza = event_stanza( - [{xmlelement, "delete", nodeAttr(Node), - []}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true} - end; - _ -> - {result, false} - end. +broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions) -> + Stanza = event_stanza("delete", nodeAttr(Node)), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, notify_delete, Stanza, false)}. -broadcast_created_node(_, _, _, _, _, []) -> - {result, false}; -broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]), - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true), - {result, true}. +broadcast_created_node(Host, Node, NodeId, Type, NodeOptions) -> + Stanza = event_stanza("create", nodeAttr(Node)), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, true, Stanza, true)}. broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) -> - case get_option(NodeOptions, notify_config) of + Stanza = case get_option(NodeOptions, deliver_payloads) of true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], - get_configure_xfields(Type, NodeOptions, Lang, [])}]; - false -> - [] - end, - Stanza = event_stanza( - [{xmlelement, "configuration", nodeAttr(Node), Content}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -get_collection_subscriptions(Host, Node) -> - Action = fun() -> - {result, lists:map(fun({Depth, Nodes}) -> - {Depth, [{N, get_node_subs(N)} || N <- Nodes]} - end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} + event_stanza("configuration", nodeAttr(Node), + "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], + get_configure_xfields(Type, NodeOptions, Lang, [])); + false -> + event_stanza("configuration", nodeAttr(Node)) end, - case transaction(Action, sync_dirty) of - {result, CollSubs} -> CollSubs; - _ -> [] - end. + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, notify_config, Stanza, false)}. -get_node_subs(#pubsub_node{type = Type, - id = NodeID}) -> - case node_call(Type, get_node_subscriptions, [NodeID]) of - {result, Subs} -> get_options_for_subs(NodeID, Subs); - Other -> Other - end. +broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, Stanzas) -> + Subs = node_subscriptions(Host, Node, NodeId, Type, NodeOptions, Notify), + Result = [broadcast(Host, Node, NodeId, Type, NodeOptions, Subs, Stanza, SHIM) || + {Cond, Stanza, SHIM} <- Stanzas, Cond =:= true], + lists:member(true, Result). +broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, true, Stanza, SHIM) -> + Subs = node_subscriptions(Host, Node, NodeId, Type, NodeOptions, Notify), + broadcast(Host, Node, NodeId, Type, NodeOptions, Subs, Stanza, SHIM); +broadcast(_Host, _Node, _NodeId, _Type, _NodeOptions, _Notify, false, _Stanza, _SHIM) -> + false; +broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, Condition, Stanza, SHIM) -> + broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, get_option(NodeOptions, Condition), Stanza, SHIM). -get_options_for_subs(NodeID, Subs) -> - lists:foldl(fun({JID, subscribed, SubID}, Acc) -> - case pubsub_subscription:read_subscription(JID, NodeID, SubID) of - {error, notfound} -> [{JID, SubID, []} | Acc]; - #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc]; - _ -> Acc - end; - (_, Acc) -> - Acc - end, [], Subs). - -broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - NotificationType = get_option(NodeOptions, notification_type, headline), - BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - From = service_jid(Host), - Stanza = case NotificationType of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, - %% Handles explicit subscriptions - SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), - lists:foreach(fun ({LJID, NodeName, SubIDs}) -> - LJIDs = case BroadcastAll of - true -> - {U, S, _} = LJID, - [{U, S, R} || R <- user_resources(U, S)]; - false -> - [LJID] - end, - %% Determine if the stanza should have SHIM ('SubID' and 'name') headers - StanzaToSend = case {SHIM, SubIDs} of - {false, _} -> - Stanza; - %% If there's only one SubID, don't add it - {true, [_]} -> - add_shim_headers(Stanza, collection_shim(NodeName)); - {true, SubIDs} -> - add_shim_headers(Stanza, lists:append(collection_shim(NodeName), subid_shim(SubIDs))) - end, - lists:foreach(fun(To) -> - ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend) - end, LJIDs) - end, SubIDsByJID). - -broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza({LUser, LServer, LResource}, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM), - %% Handles implicit presence subscriptions - SenderResource = case LResource of - [] -> - case user_resources(LUser, LServer) of - [Resource|_] -> Resource; - _ -> "" - end; - _ -> - LResource - end, - case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of +broadcast({U, S, R}, Node, NodeId, Type, NodeOptions, Subscriptions, Stanza, SHIM) -> + broadcast(S, Node, NodeId, Type, NodeOptions, Subscriptions, Stanza, SHIM) + or case ejabberd_sm:get_session_pid(U, S, user_resource(U, S, R)) of C2SPid when is_pid(C2SPid) -> - Stanza = case get_option(NodeOptions, notification_type, headline) of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, %% set the from address on the notification to the bare JID of the account owner %% Also, add "replyto" if entity has presence subscription to the account owner %% See XEP-0163 1.1 section 4.3.1 - ejabberd_c2s:broadcast(C2SPid, - {pep_message, binary_to_list(Node)++"+notify"}, - _Sender = jlib:make_jid(LUser, LServer, ""), - _StanzaToSend = add_extended_headers(Stanza, - _ReplyTo = extended_headers([jlib:jid_to_string(Publisher)]))); + Event = {pep_message, binary_to_list(Node)++"+notify"}, + Message = case get_option(NodeOptions, notification_type, headline) of + normal -> Stanza; + MsgType -> add_message_type(Stanza, atom_to_list(MsgType)) + end, + ejabberd_c2s:broadcast(C2SPid, Event, jlib:make_jid(U, S, ""), Message), + true; _ -> - ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza]) + ?DEBUG("~p@~p has no session; can't deliver stanza: ~p", [U, S, Stanza]), + false end; -broadcast_stanza(Host, _Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). +broadcast(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _Stanza, _SHIM) -> + false; +broadcast(Host, _Node, _NodeId, _Type, NodeOptions, Subscriptions, Stanza, SHIM) -> + From = service_jid(Host), + Message = case get_option(NodeOptions, notification_type, headline) of + normal -> Stanza; + MsgType -> add_message_type(Stanza, atom_to_list(MsgType)) + end, + lists:foreach(fun({LJID, NodeName, SubIds}) -> + Send = case {SHIM, SubIds} of + {false, _} -> Message; + {true, [_]} -> add_shim_headers(Message, collection_shim(NodeName)); + {true, _} -> add_shim_headers(Message, lists:append(collection_shim(NodeName), subid_shim(SubIds))) + end, + ejabberd_router:route(From, jlib:make_jid(LJID), Send) + end, Subscriptions), + true. + +node_subscriptions(Host, Node, NodeId, Type, _NodeOptions, Notify) -> + % TODO temporary dirty condition, should be improved using plugin or node options + case Type of + "flat" -> node_subscriptions_bare(Host, Node, NodeId, Type); + "pep" -> node_subscriptions_bare(Host, Node, NodeId, Type); + _ -> node_subscriptions_full(Host, Node, Notify) + end. + +node_subscriptions_bare(Host, Node, NodeId, Type) -> + case node_action(Host, Type, get_node_subscriptions, [NodeId]) of + {result, Subs} -> + SubsByJid = lists:foldl( + fun({JID, subscribed, SubId}, Acc) -> + case dict:is_key(JID, Acc) of + true -> dict:append(JID, SubId, Acc); + false -> dict:store(JID, [SubId], Acc) + end; + (_, Acc) -> + Acc + end, dict:new(), Subs), + [{J, Node, S} || {J, S} <- dict:to_list(SubsByJid)]; + _ -> + [] + end. + +node_subscriptions_full(Host, Node, NotifyType) -> + Action = fun() -> + Collection = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), + {result, [{Depth, [{N, sub_with_options(N)} || N <- Nodes]} || {Depth, Nodes} <- Collection]} + end, + case transaction(Action, sync_dirty) of + {result, CollSubs} -> subscribed_nodes_by_jid(NotifyType, CollSubs); + _ -> [] + end. subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodesToDeliver = fun(Depth, Node, Subs, Acc) -> @@ -3214,7 +3188,7 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodeOptions = Node#pubsub_node.options, lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) -> case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of - true -> + true -> %% If is to deliver : case state_can_deliver(LJID, SubOptions) of [] -> {JIDs, Recipients}; @@ -3223,13 +3197,13 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> case lists:member(JIDToDeliver, JIDs) of %% check if the JIDs co-accumulator contains the Subscription Jid, - false -> + false -> %% - if not, %% - add the Jid to JIDs list co-accumulator ; %% - create a tuple of the Jid, NodeId, and SubID (as list), %% and add the tuple to the Recipients list co-accumulator {[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]}; - true -> + true -> %% - if the JIDs co-accumulator contains the Jid %% get the tuple containing the Jid from the Recipient list co-accumulator {_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc), @@ -3258,9 +3232,33 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), JIDSubs. +sub_with_options(#pubsub_node{type = Type, id = NodeId}) -> + case node_call(Type, get_node_subscriptions, [NodeId]) of + {result, Subs} -> + lists:foldl( + fun({JID, subscribed, SubId}, Acc) -> [sub_with_options(JID, NodeId, SubId) | Acc]; + (_, Acc) -> Acc + end, [], Subs); + _ -> + [] + end. +sub_with_options(JID, NodeId, SubId) -> + case pubsub_subscription:read_subscription(JID, NodeId, SubId) of + #pubsub_subscription{options = Options} -> {JID, SubId, Options}; + _ -> {JID, SubId, []} + end. + user_resources(User, Server) -> ejabberd_sm:get_user_resources(User, Server). +user_resource(User, Server, []) -> + case user_resources(User, Server) of + [R|_] -> R; + _ -> [] + end; +user_resource(_, _, Resource) -> + Resource. + %%%%%%% Configuration handling %%

    There are several reasons why the default node configuration options request might fail:

    @@ -3863,7 +3861,7 @@ extended_headers(Jids) -> on_user_offline(_, JID, _) -> {User, Server, Resource} = jlib:jid_tolower(JID), - case ejabberd_sm:get_user_resources(User, Server) of + case user_resources(User, Server) of [] -> purge_offline({User, Server, Resource}); _ -> true end. diff --git a/src/mod_pubsub/mod_pubsub_odbc.erl b/src/mod_pubsub/mod_pubsub_odbc.erl index 4649aac78..880bcf058 100644 --- a/src/mod_pubsub/mod_pubsub_odbc.erl +++ b/src/mod_pubsub/mod_pubsub_odbc.erl @@ -11,11 +11,11 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} @@ -78,6 +78,7 @@ %% exports for console debug manual use -export([create_node/5, + create_node/7, delete_node/3, subscribe_node/5, unsubscribe_node/5, @@ -87,7 +88,6 @@ get_items/2, get_item/3, get_cached_item/2, - broadcast_stanza/9, get_configure/5, set_configure/5, tree_action/3, @@ -1022,7 +1022,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, translate:translate(Lang, "ejabberd Publish-Subscribe module") ++ - "\nCopyright (c) 2004-2010 ProcessOne"}]}]. + "\nCopyright (c) 2004-2011 ProcessOne"}]}]. iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang) -> iq_pubsub(Host, ServerHost, From, IQType, SubEl, Lang, all, plugins(ServerHost)). @@ -1574,10 +1574,8 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> {result, true} -> case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of {ok, NodeId} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, Owner]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], case node_call(Type, create_node, [NodeId, Owner]) of - {result, Result} -> {result, {NodeId, SubsByDepth, Result}}; + {result, Result} -> {result, {NodeId, Result}}; Error -> Error end; {error, {virtual, NodeId}} -> @@ -1596,15 +1594,15 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> [{xmlelement, "create", nodeAttr(Node), []}]}], case transaction(Host, CreateNode, transaction) of - {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), + {result, {NodeId, {Result, broadcast}}} -> + broadcast_created_node(Host, Node, NodeId, Type, NodeOptions), case Result of default -> {result, Reply}; _ -> {result, Result} end; - {result, {_NodeId, _SubsByDepth, default}} -> + {result, {_NodeId, default}} -> {result, Reply}; - {result, {_NodeId, _SubsByDepth, Result}} -> + {result, {_NodeId, Result}} -> {result, Result}; Error -> %% in case we change transaction to sync_dirty... @@ -1636,11 +1634,9 @@ delete_node(Host, Node, Owner) -> Action = fun(#pubsub_node{type = Type, id = NodeId}) -> case node_call(Type, get_affiliation, [NodeId, Owner]) of {result, owner} -> - ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), - SubsByDepth = [{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree], Removed = tree_call(Host, delete_node, [Host, Node]), case node_call(Type, delete_node, [Removed]) of - {result, Res} -> {result, {SubsByDepth, Res}}; + {result, Res} -> {result, Res}; Error -> Error end; _ -> @@ -1650,26 +1646,26 @@ delete_node(Host, Node, Owner) -> end, Reply = [], case transaction(Host, Node, Action, transaction) of - {result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} -> + {result, {_, {Result, broadcast, Removed}}} -> lists:foreach(fun({RNode, _RSubscriptions}) -> {RH, RN} = RNode#pubsub_node.nodeid, NodeId = RNode#pubsub_node.id, Type = RNode#pubsub_node.type, Options = RNode#pubsub_node.options, - broadcast_removed_node(RH, RN, NodeId, Type, Options, SubsByDepth) + broadcast_removed_node(RH, RN, NodeId, Type, Options) end, Removed), case Result of default -> {result, Reply}; _ -> {result, Result} end; - {result, {_, {_, {Result, _Removed}}}} -> + {result, {_, {Result, _Removed}}} -> case Result of default -> {result, Reply}; _ -> {result, Result} end; - {result, {_, {_, default}}} -> + {result, {_, default}} -> {result, Reply}; - {result, {_, {_, Result}}} -> + {result, {_, Result}} -> {result, Result}; Error -> Error @@ -1841,7 +1837,7 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> DeliverPayloads = get_option(Options, deliver_payloads), PersistItems = get_option(Options, persist_items), PayloadCount = payload_xmlelements(Payload), - PayloadSize = size(term_to_binary(Payload)), + PayloadSize = size(term_to_binary(Payload))-2, % size(term_to_binary([])) == 2 PayloadMaxSize = get_option(Options, max_payload_size), % pubsub#deliver_payloads true % pubsub#persist_items true -> 1 item; false -> 0 item @@ -1877,12 +1873,11 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> NodeId = TNode#pubsub_node.id, Type = TNode#pubsub_node.type, Options = TNode#pubsub_node.options, - BroadcastPayload = case Broadcast of - default -> Payload; + BrPayload = case Broadcast of broadcast -> Payload; PluginPayload -> PluginPayload end, - broadcast_publish_item(Host, Node, NodeId, Type, Options, Removed, ItemId, jlib:jid_tolower(Publisher), BroadcastPayload), + broadcast_publish_item(Host, Node, NodeId, Type, Options, ItemId, jlib:jid_tolower(Publisher), BrPayload, Removed), set_cached_item(Host, NodeId, ItemId, Publisher, Payload), case Result of default -> {result, Reply}; @@ -2352,14 +2347,18 @@ get_options_helper(JID, Lang, Node, NodeID, SubID, Type) -> read_sub(Subscriber, Node, NodeID, SubID, Lang) -> case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; {result, #pubsub_subscription{options = Options}} -> {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options), OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, {"subid", SubID}|nodeAttr(Node)], [XdataEl]}, PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]}, + {result, PubsubEl}; + _ -> + OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, + {"subid", SubID}|nodeAttr(Node)], + []}, + PubsubEl = {xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [OptionsEl]}, {result, PubsubEl} end. @@ -2409,12 +2408,14 @@ set_options_helper(Configuration, JID, NodeID, SubID, Type) -> write_sub(_Subscriber, _NodeID, _SubID, invalid) -> {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")}; +write_sub(_Subscriber, _NodeID, _SubID, []) -> + {result, []}; write_sub(Subscriber, NodeID, SubID, Options) -> - case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; + case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of {result, _} -> - {result, []} + {result, []}; + {error, _} -> + {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")} end. %% @spec (Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} @@ -2708,21 +2709,20 @@ sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) -> sub_option_can_deliver(NotifyType, Depth, Option) end, SubOptions). +node_to_deliver(LJID, NodeOptions) -> + presence_can_deliver(LJID, get_option(NodeOptions, presence_based_delivery)). + sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false; sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false; sub_option_can_deliver(_, _, {subscription_depth, all}) -> true; sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D; -sub_option_can_deliver(_, _, {deliver, false}) -> false; -sub_option_can_deliver(_, _, {expire, When}) -> now() < When; -sub_option_can_deliver(_, _, _) -> true. - -node_to_deliver(LJID, NodeOptions) -> - PresenceDelivery = get_option(NodeOptions, presence_based_delivery), - presence_can_deliver(LJID, PresenceDelivery). +sub_option_can_deliver(_, _, {deliver, false}) -> false; +sub_option_can_deliver(_, _, {expire, When}) -> now() < When; +sub_option_can_deliver(_, _, _) -> true. presence_can_deliver(_, false) -> true; presence_can_deliver({User, Server, Resource}, true) -> - case mnesia:dirty_match_object({session, '_', '_', {User, Server}, '_', '_'}) of + case ejabberd_sm:get_user_sessions(User, Server) of [] -> false; Ss -> lists:foldl(fun(_, true) -> true; @@ -2740,45 +2740,45 @@ state_can_deliver({U, S, R}, []) -> [{U, S, R}]; state_can_deliver({U, S, R}, SubOptions) -> %% Check SubOptions for 'show_values' case lists:keysearch('show_values', 1, SubOptions) of - %% If not in suboptions, item can be delivered, case doesn't apply - false -> [{U, S, R}]; - %% If in a suboptions ... - {_, {_, ShowValues}} -> - %% Get subscriber resources - Resources = case R of - %% If the subscriber JID is a bare one, get all its resources - [] -> user_resources(U, S); - %% If the subscriber JID is a full one, use its resource - R -> [R] - end, - %% For each resource, test if the item is allowed to be delivered - %% based on resource state - lists:foldl( - fun(Resource, Acc) -> - get_resource_state({U, S, Resource}, ShowValues, Acc) - end, [], Resources) + %% If not in suboptions, item can be delivered, case doesn't apply + false -> [{U, S, R}]; + %% If in a suboptions ... + {_, {_, ShowValues}} -> + %% Get subscriber resources + Resources = case R of + %% If the subscriber JID is a bare one, get all its resources + [] -> user_resources(U, S); + %% If the subscriber JID is a full one, use its resource + R -> [R] + end, + %% For each resource, test if the item is allowed to be delivered + %% based on resource state + lists:foldl( + fun(Resource, Acc) -> + get_resource_state({U, S, Resource}, ShowValues, Acc) + end, [], Resources) end. get_resource_state({U, S, R}, ShowValues, JIDs) -> %% Get user session PID case ejabberd_sm:get_session_pid(U, S, R) of - %% If no PID, item can be delivered - none -> lists:append([{U, S, R}], JIDs); - %% If PID ... - Pid -> - %% Get user resource state - %% TODO : add a catch clause - Show = case ejabberd_c2s:get_presence(Pid) of - {_, _, "available", _} -> "online"; - {_, _, State, _} -> State - end, - %% Is current resource state listed in 'show-values' suboption ? - case lists:member(Show, ShowValues) of %andalso Show =/= "online" of - %% If yes, item can be delivered - true -> lists:append([{U, S, R}], JIDs); - %% If no, item can't be delivered - false -> JIDs - end + %% If no PID, item can be delivered + none -> lists:append([{U, S, R}], JIDs); + %% If PID ... + Pid -> + %% Get user resource state + %% TODO : add a catch clause + Show = case ejabberd_c2s:get_presence(Pid) of + {_, _, "available", _} -> "online"; + {_, _, State, _} -> State + end, + %% Is current resource state listed in 'show-values' suboption ? + case lists:member(Show, ShowValues) of %andalso Show =/= "online" of + %% If yes, item can be delivered + true -> lists:append([{U, S, R}], JIDs); + %% If no, item can't be delivered + false -> JIDs + end end. %% @spec (Payload) -> int() @@ -2804,223 +2804,149 @@ event_stanza_withmoreels(Els, MoreEls) -> {xmlelement, "message", [], [{xmlelement, "event", [{"xmlns", ?NS_PUBSUB_EVENT}], Els} | MoreEls]}. +event_stanza(Event, EvAttr) -> + event_stanza([{xmlelement, Event, EvAttr, []}]). +event_stanza(Event, EvAttr, Entries) -> + event_stanza([{xmlelement, Event, EvAttr, + [{xmlelement, Entry, EnAttr, []} || {Entry, EnAttr} <- Entries]}]). +event_stanza(Event, EvAttr, Entry, EnAttr, Payload) -> + event_stanza([{xmlelement, Event, EvAttr, [{xmlelement, Entry, EnAttr, Payload}]}]). +event_stanza(Event, EvAttr, Entry, EnAttr, Payload, Publisher) -> + Stanza = event_stanza(Event, EvAttr, Entry, EnAttr, Payload), + add_extended_headers(Stanza, extended_headers([jlib:jid_to_string(Publisher)])). + %%%%%% broadcast functions -broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, Removed, ItemId, From, Payload) -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> Payload; - false -> [] - end, - Stanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "item", itemAttr(ItemId), Content}]}]), - broadcast_stanza(Host, From, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - case Removed of - [] -> - ok; - _ -> - case get_option(NodeOptions, notify_retract) of - true -> - RetractStanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "retract", itemAttr(RId), []} || RId <- Removed]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, - items, RetractStanza, true); - _ -> - ok - end - end, - {result, true}; +broadcast_publish_item(Host, Node, NodeId, Type, NodeOptions, ItemId, Publisher, Payload, Removed) -> + Publish = case get_option(NodeOptions, deliver_payloads) of + true -> event_stanza("items", nodeAttr(Node), "item", itemAttr(ItemId), Payload, Publisher); + false -> event_stanza("items", nodeAttr(Node), "item", itemAttr(ItemId), [], Publisher) + end, + case Removed of + [] -> + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, items, true, Publish, true)}; _ -> - {result, false} + Retract = event_stanza("items", nodeAttr(Node), [{"retract", itemAttr(Rid)} || Rid <- Removed]), + Stanzas = [{true, Publish, true}, {get_option(NodeOptions, notify_retract), Retract, true}], + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, items, Stanzas)} end. broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds) -> - broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false). -broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _ForceNotify) -> + broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, notify_retract). +broadcast_retract_items(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _) -> {result, false}; -broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, ForceNotify) -> - case (get_option(NodeOptions, notify_retract) or ForceNotify) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [{xmlelement, "items", nodeAttr(Node), - [{xmlelement, "retract", itemAttr(ItemId), []} || ItemId <- ItemIds]}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, items, Stanza, true), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. +broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, false) -> + broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, notify_retract); +broadcast_retract_items(Host, Node, NodeId, Type, NodeOptions, ItemIds, Notify) -> + Stanza = event_stanza("items", nodeAttr(Node), [{"retract", itemAttr(Rid)} || Rid <- ItemIds]), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, items, Notify, Stanza, true)}. broadcast_purge_node(Host, Node, NodeId, Type, NodeOptions) -> - case get_option(NodeOptions, notify_retract) of - true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Stanza = event_stanza( - [{xmlelement, "purge", nodeAttr(Node), - []}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. + Stanza = event_stanza("purge", nodeAttr(Node)), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, notify_retract, Stanza, false)}. -broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - case get_option(NodeOptions, notify_delete) of - true -> - case SubsByDepth of - [] -> - {result, false}; - _ -> - Stanza = event_stanza( - [{xmlelement, "delete", nodeAttr(Node), - []}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true} - end; - _ -> - {result, false} - end. +broadcast_removed_node(Host, Node, NodeId, Type, NodeOptions) -> + Stanza = event_stanza("delete", nodeAttr(Node)), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, notify_delete, Stanza, false)}. -broadcast_created_node(_, _, _, _, _, []) -> - {result, false}; -broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth) -> - Stanza = event_stanza([{xmlelement, "create", nodeAttr(Node), []}]), - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, nodes, Stanza, true), - {result, true}. +broadcast_created_node(Host, Node, NodeId, Type, NodeOptions) -> + Stanza = event_stanza("create", nodeAttr(Node)), + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, true, Stanza, true)}. broadcast_config_notification(Host, Node, NodeId, Type, NodeOptions, Lang) -> - case get_option(NodeOptions, notify_config) of + Stanza = case get_option(NodeOptions, deliver_payloads) of true -> - case get_collection_subscriptions(Host, Node) of - SubsByDepth when is_list(SubsByDepth) -> - Content = case get_option(NodeOptions, deliver_payloads) of - true -> - [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], - get_configure_xfields(Type, NodeOptions, Lang, [])}]; - false -> - [] - end, - Stanza = event_stanza( - [{xmlelement, "configuration", nodeAttr(Node), Content}]), - broadcast_stanza(Host, Node, NodeId, Type, - NodeOptions, SubsByDepth, nodes, Stanza, false), - {result, true}; - _ -> - {result, false} - end; - _ -> - {result, false} - end. - -get_collection_subscriptions(Host, Node) -> - Action = fun() -> - {result, lists:map(fun({Depth, Nodes}) -> - {Depth, [{N, get_node_subs(N)} || N <- Nodes]} - end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} + event_stanza("configuration", nodeAttr(Node), + "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], + get_configure_xfields(Type, NodeOptions, Lang, [])); + false -> + event_stanza("configuration", nodeAttr(Node)) end, - case transaction(Host, Action, sync_dirty) of - {result, CollSubs} -> CollSubs; - _ -> [] - end. + {result, broadcast(Host, Node, NodeId, Type, NodeOptions, nodes, notify_config, Stanza, false)}. -get_node_subs(#pubsub_node{type = Type, - id = NodeID}) -> - case node_call(Type, get_node_subscriptions, [NodeID]) of - {result, Subs} -> get_options_for_subs(NodeID, Subs); - Other -> Other - end. +broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, Stanzas) -> + Subs = node_subscriptions(Host, Node, NodeId, Type, NodeOptions, Notify), + Result = [broadcast(Host, Node, NodeId, Type, NodeOptions, Subs, Stanza, SHIM) || + {Cond, Stanza, SHIM} <- Stanzas, Cond =:= true], + lists:member(true, Result). +broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, true, Stanza, SHIM) -> + Subs = node_subscriptions(Host, Node, NodeId, Type, NodeOptions, Notify), + broadcast(Host, Node, NodeId, Type, NodeOptions, Subs, Stanza, SHIM); +broadcast(_Host, _Node, _NodeId, _Type, _NodeOptions, _Notify, false, _Stanza, _SHIM) -> + false; +broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, Condition, Stanza, SHIM) -> + broadcast(Host, Node, NodeId, Type, NodeOptions, Notify, get_option(NodeOptions, Condition), Stanza, SHIM). -get_options_for_subs(NodeID, Subs) -> - lists:foldl(fun({JID, subscribed, SubID}, Acc) -> - case pubsub_subscription_odbc:get_subscription(JID, NodeID, SubID) of - {error, notfound} -> [{JID, SubID, []} | Acc]; - {result, #pubsub_subscription{options = Options}} -> [{JID, SubID, Options} | Acc]; - _ -> Acc - end; - (_, Acc) -> - Acc - end, [], Subs). - -broadcast_stanza(Host, _Node, _NodeId, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - NotificationType = get_option(NodeOptions, notification_type, headline), - BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull - From = service_jid(Host), - Stanza = case NotificationType of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, - %% Handles explicit subscriptions - SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), - lists:foreach(fun ({LJID, NodeName, SubIDs}) -> - LJIDs = case BroadcastAll of - true -> - {U, S, _} = LJID, - [{U, S, R} || R <- user_resources(U, S)]; - false -> - [LJID] - end, - %% Determine if the stanza should have SHIM ('SubID' and 'name') headers - StanzaToSend = case {SHIM, SubIDs} of - {false, _} -> - Stanza; - %% If there's only one SubID, don't add it - {true, [_]} -> - add_shim_headers(Stanza, collection_shim(NodeName)); - {true, SubIDs} -> - add_shim_headers(Stanza, lists:append(collection_shim(NodeName), subid_shim(SubIDs))) - end, - lists:foreach(fun(To) -> - ejabberd_router:route(From, jlib:make_jid(To), StanzaToSend) - end, LJIDs) - end, SubIDsByJID). - -broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza({LUser, LServer, LResource}, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM), - %% Handles implicit presence subscriptions - SenderResource = case LResource of - [] -> - case user_resources(LUser, LServer) of - [Resource|_] -> Resource; - _ -> "" - end; - _ -> - LResource - end, - case ejabberd_sm:get_session_pid(LUser, LServer, SenderResource) of +broadcast({U, S, R}, Node, NodeId, Type, NodeOptions, Subscriptions, Stanza, SHIM) -> + broadcast(S, Node, NodeId, Type, NodeOptions, Subscriptions, Stanza, SHIM) + or case ejabberd_sm:get_session_pid(U, S, user_resource(U, S, R)) of C2SPid when is_pid(C2SPid) -> - Stanza = case get_option(NodeOptions, notification_type, headline) of - normal -> BaseStanza; - MsgType -> add_message_type(BaseStanza, atom_to_list(MsgType)) - end, %% set the from address on the notification to the bare JID of the account owner %% Also, add "replyto" if entity has presence subscription to the account owner %% See XEP-0163 1.1 section 4.3.1 - ejabberd_c2s:broadcast(C2SPid, - {pep_message, binary_to_list(Node)++"+notify"}, - _Sender = jlib:make_jid(LUser, LServer, ""), - _StanzaToSend = add_extended_headers(Stanza, - _ReplyTo = extended_headers([jlib:jid_to_string(Publisher)]))); + Event = {pep_message, binary_to_list(Node)++"+notify"}, + Message = case get_option(NodeOptions, notification_type, headline) of + normal -> Stanza; + MsgType -> add_message_type(Stanza, atom_to_list(MsgType)) + end, + ejabberd_c2s:broadcast(C2SPid, Event, jlib:make_jid(U, S, ""), Message), + true; _ -> - ?DEBUG("~p@~p has no session; can't deliver ~p to contacts", [LUser, LServer, BaseStanza]) + ?DEBUG("~p@~p has no session; can't deliver stanza: ~p", [U, S, Stanza]), + false end; -broadcast_stanza(Host, _Publisher, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) -> - broadcast_stanza(Host, Node, NodeId, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM). +broadcast(_Host, _Node, _NodeId, _Type, _NodeOptions, [], _Stanza, _SHIM) -> + false; +broadcast(Host, _Node, _NodeId, _Type, NodeOptions, Subscriptions, Stanza, SHIM) -> + From = service_jid(Host), + Message = case get_option(NodeOptions, notification_type, headline) of + normal -> Stanza; + MsgType -> add_message_type(Stanza, atom_to_list(MsgType)) + end, + lists:foreach(fun({LJID, NodeName, SubIds}) -> + Send = case {SHIM, SubIds} of + {false, _} -> Message; + {true, [_]} -> add_shim_headers(Message, collection_shim(NodeName)); + {true, _} -> add_shim_headers(Message, lists:append(collection_shim(NodeName), subid_shim(SubIds))) + end, + ejabberd_router:route(From, jlib:make_jid(LJID), Send) + end, Subscriptions), + true. + +node_subscriptions(Host, Node, NodeId, Type, _NodeOptions, Notify) -> + % TODO temporary dirty condition, should be improved using plugin or node options + case Type of + "flat" -> node_subscriptions_bare(Host, Node, NodeId, Type); + "pep" -> node_subscriptions_bare(Host, Node, NodeId, Type); + _ -> node_subscriptions_full(Host, Node, Notify) + end. + +node_subscriptions_bare(Host, Node, NodeId, Type) -> + case node_action(Host, Type, get_node_subscriptions, [NodeId]) of + {result, Subs} -> + SubsByJid = lists:foldl( + fun({JID, subscribed, SubId}, Acc) -> + case dict:is_key(JID, Acc) of + true -> dict:append(JID, SubId, Acc); + false -> dict:store(JID, [SubId], Acc) + end; + (_, Acc) -> + Acc + end, dict:new(), Subs), + [{J, Node, S} || {J, S} <- dict:to_list(SubsByJid)]; + _ -> + [] + end. + +node_subscriptions_full(Host, Node, NotifyType) -> + Action = fun() -> + Collection = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), + {result, [{Depth, [{N, sub_with_options(N)} || N <- Nodes]} || {Depth, Nodes} <- Collection]} + end, + case transaction(Host, Action, sync_dirty) of + {result, CollSubs} -> subscribed_nodes_by_jid(NotifyType, CollSubs); + _ -> [] + end. subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodesToDeliver = fun(Depth, Node, Subs, Acc) -> @@ -3031,7 +2957,7 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodeOptions = Node#pubsub_node.options, lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) -> case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of - true -> + true -> %% If is to deliver : case state_can_deliver(LJID, SubOptions) of [] -> {JIDs, Recipients}; @@ -3040,13 +2966,13 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> case lists:member(JIDToDeliver, JIDs) of %% check if the JIDs co-accumulator contains the Subscription Jid, - false -> + false -> %% - if not, %% - add the Jid to JIDs list co-accumulator ; %% - create a tuple of the Jid, NodeId, and SubID (as list), %% and add the tuple to the Recipients list co-accumulator {[JIDToDeliver | JIDsAcc], [{JIDToDeliver, NodeName, [SubID]} | RecipientsAcc]}; - true -> + true -> %% - if the JIDs co-accumulator contains the Jid %% get the tuple containing the Jid from the Recipient list co-accumulator {_, {JIDToDeliver, NodeName1, SubIDs}} = lists:keysearch(JIDToDeliver, 1, RecipientsAcc), @@ -3075,9 +3001,33 @@ subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), JIDSubs. +sub_with_options(#pubsub_node{type = Type, id = NodeId}) -> + case node_call(Type, get_node_subscriptions, [NodeId]) of + {result, Subs} -> + lists:foldl( + fun({JID, subscribed, SubId}, Acc) -> [sub_with_options(JID, NodeId, SubId) | Acc]; + (_, Acc) -> Acc + end, [], Subs); + _ -> + [] + end. +sub_with_options(JID, NodeId, SubId) -> + case pubsub_subscription_odbc:get_subscription(JID, NodeId, SubId) of + {result, #pubsub_subscription{options = Options}} -> {JID, SubId, Options}; + _ -> {JID, SubId, []} + end. + user_resources(User, Server) -> ejabberd_sm:get_user_resources(User, Server). +user_resource(User, Server, []) -> + case user_resources(User, Server) of + [R|_] -> R; + _ -> [] + end; +user_resource(_, _, Resource) -> + Resource. + %%%%%%% Configuration handling %%

    There are several reasons why the default node configuration options request might fail:

    @@ -3736,7 +3686,7 @@ extended_headers(Jids) -> on_user_offline(_, JID, _) -> {User, Server, Resource} = jlib:jid_tolower(JID), - case ejabberd_sm:get_user_resources(User, Server) of + case user_resources(User, Server) of [] -> purge_offline({User, Server, Resource}); _ -> true end. diff --git a/src/mod_pubsub/node.template b/src/mod_pubsub/node.template index 0c5555131..f07a62996 100644 --- a/src/mod_pubsub/node.template +++ b/src/mod_pubsub/node.template @@ -11,11 +11,11 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_buddy.erl b/src/mod_pubsub/node_buddy.erl index 315dd01e2..b10f6d3c7 100644 --- a/src/mod_pubsub/node_buddy.erl +++ b/src/mod_pubsub/node_buddy.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_club.erl b/src/mod_pubsub/node_club.erl index 6e71c5b15..5879ec43e 100644 --- a/src/mod_pubsub/node_club.erl +++ b/src/mod_pubsub/node_club.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_dispatch.erl b/src/mod_pubsub/node_dispatch.erl index ed6bf85ae..62abdecc1 100644 --- a/src/mod_pubsub/node_dispatch.erl +++ b/src/mod_pubsub/node_dispatch.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_flat.erl b/src/mod_pubsub/node_flat.erl index 5b0bcde06..1d29ad578 100644 --- a/src/mod_pubsub/node_flat.erl +++ b/src/mod_pubsub/node_flat.erl @@ -11,11 +11,11 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_flat_odbc.erl b/src/mod_pubsub/node_flat_odbc.erl index 95998a554..72659eef4 100644 --- a/src/mod_pubsub/node_flat_odbc.erl +++ b/src/mod_pubsub/node_flat_odbc.erl @@ -11,11 +11,11 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_hometree.erl b/src/mod_pubsub/node_hometree.erl index 41785a149..8aa01c3cc 100644 --- a/src/mod_pubsub/node_hometree.erl +++ b/src/mod_pubsub/node_hometree.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} @@ -97,9 +97,11 @@ init(_Host, _ServerHost, _Options) -> pubsub_subscription:init(), mnesia:create_table(pubsub_state, [{disc_copies, [node()]}, + {index, [nodeidx]}, {attributes, record_info(fields, pubsub_state)}]), mnesia:create_table(pubsub_item, [{disc_only_copies, [node()]}, + {index, [nodeidx]}, {attributes, record_info(fields, pubsub_item)}]), ItemsFields = record_info(fields, pubsub_item), case mnesia:table_info(pubsub_item, attributes) of @@ -224,7 +226,7 @@ create_node_permission(Host, ServerHost, NodeId, _ParentNodeId, Owner, Access) - %% @doc

    create_node(NodeIdx, Owner) -> OwnerKey = jlib:jid_tolower(jlib:jid_remove_resource(Owner)), - set_state(#pubsub_state{stateid = {OwnerKey, NodeIdx}, affiliation = owner}), + set_state(#pubsub_state{stateid = {OwnerKey, NodeIdx}, nodeidx = NodeIdx, affiliation = owner}), {result, {default, broadcast}}. %% @spec (Nodes) -> {result, {default, broadcast, Reply}} @@ -507,6 +509,7 @@ publish_item(NodeIdx, Publisher, PublishModel, MaxItems, ItemId, Payload) -> payload = Payload}; _ -> #pubsub_item{itemid = {ItemId, NodeIdx}, + nodeidx = NodeIdx, creation = {Now, GenKey}, modification = PubId, payload = Payload} @@ -581,10 +584,10 @@ delete_item(NodeIdx, Publisher, PublishModel, ItemId) -> false -> case Affiliation of owner -> - %% Owner can delete other publishers items as well + %% Owner can delete any items from its own node {result, States} = get_states(NodeIdx), lists:foldl( - fun(#pubsub_state{items = PI, affiliation = publisher} = S, Res) -> + fun(#pubsub_state{items = PI} = S, Res) -> case lists:member(ItemId, PI) of true -> del_item(NodeIdx, ItemId), @@ -860,8 +863,7 @@ get_nodes_helper(NodeTree, %% ```get_states(NodeIdx) -> %% node_default:get_states(NodeIdx).'''

    get_states(NodeIdx) -> - States = case catch mnesia:match_object( - #pubsub_state{stateid = {'_', NodeIdx}, _ = '_'}) of + States = case catch mnesia:index_read(pubsub_state, NodeIdx, #pubsub_state.nodeidx) of List when is_list(List) -> List; _ -> [] end, @@ -876,7 +878,7 @@ get_state(NodeIdx, JID) -> StateId = {JID, NodeIdx}, case catch mnesia:read({pubsub_state, StateId}) of [State] when is_record(State, pubsub_state) -> State; - _ -> #pubsub_state{stateid=StateId} + _ -> #pubsub_state{stateid=StateId, nodeidx=NodeIdx} end. %% @spec (State) -> ok | {error, Reason} @@ -911,7 +913,7 @@ del_state(NodeIdx, JID) -> %% ```get_items(NodeIdx, From) -> %% node_default:get_items(NodeIdx, From).'''

    get_items(NodeIdx, _From) -> - Items = mnesia:match_object(#pubsub_item{itemid = {'_', NodeIdx}, _ = '_'}), + Items = mnesia:index_read(pubsub_item, NodeIdx, #pubsub_item.nodeidx), {result, lists:reverse(lists:keysort(#pubsub_item.modification, Items))}. get_items(NodeIdx, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> diff --git a/src/mod_pubsub/node_hometree_odbc.erl b/src/mod_pubsub/node_hometree_odbc.erl index 5adf24c00..1fa18ae2b 100644 --- a/src/mod_pubsub/node_hometree_odbc.erl +++ b/src/mod_pubsub/node_hometree_odbc.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_mb.erl b/src/mod_pubsub/node_mb.erl index 4a8be3f0b..c328217da 100644 --- a/src/mod_pubsub/node_mb.erl +++ b/src/mod_pubsub/node_mb.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Eric Cestari %%% @version {@vsn}, {@date} {@time} %%% @end diff --git a/src/mod_pubsub/node_pep.erl b/src/mod_pubsub/node_pep.erl index 0f406296d..d8a28c728 100644 --- a/src/mod_pubsub/node_pep.erl +++ b/src/mod_pubsub/node_pep.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_pep_odbc.erl b/src/mod_pubsub/node_pep_odbc.erl index 5d2fb0e1e..ba261591e 100644 --- a/src/mod_pubsub/node_pep_odbc.erl +++ b/src/mod_pubsub/node_pep_odbc.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_private.erl b/src/mod_pubsub/node_private.erl index 1ba0bc3d5..af5b5e383 100644 --- a/src/mod_pubsub/node_private.erl +++ b/src/mod_pubsub/node_private.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/node_public.erl b/src/mod_pubsub/node_public.erl index c8895dd15..2c66b62ea 100644 --- a/src/mod_pubsub/node_public.erl +++ b/src/mod_pubsub/node_public.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/nodetree_tree.erl b/src/mod_pubsub/nodetree_tree.erl index 99185628e..ca991a1e3 100644 --- a/src/mod_pubsub/nodetree_tree.erl +++ b/src/mod_pubsub/nodetree_tree.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/nodetree_tree_odbc.erl b/src/mod_pubsub/nodetree_tree_odbc.erl index dbfc8bc72..34f561e3e 100644 --- a/src/mod_pubsub/nodetree_tree_odbc.erl +++ b/src/mod_pubsub/nodetree_tree_odbc.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/nodetree_virtual.erl b/src/mod_pubsub/nodetree_virtual.erl index a988a6c7d..e2c7e49a9 100644 --- a/src/mod_pubsub/nodetree_virtual.erl +++ b/src/mod_pubsub/nodetree_virtual.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/pubsub.hrl b/src/mod_pubsub/pubsub.hrl index 6fd40ecfa..88d2529f0 100644 --- a/src/mod_pubsub/pubsub.hrl +++ b/src/mod_pubsub/pubsub.hrl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% copyright 2006-2010 ProcessOne +%%% copyright 2006-2011 ProcessOne %%% %%% This file contains pubsub types definition. %%% ==================================================================== @@ -146,6 +146,7 @@ -record(pubsub_state, { stateid, + nodeidx, items = [], affiliation = 'none', subscriptions = [] @@ -161,6 +162,7 @@ -record(pubsub_item, { itemid, + nodeidx, creation = {'unknown','unknown'}, modification = {'unknown','unknown'}, payload = [] diff --git a/src/mod_pubsub/pubsub_clean.erl b/src/mod_pubsub/pubsub_clean.erl index 2368b9f34..4b59e0821 100644 --- a/src/mod_pubsub/pubsub_clean.erl +++ b/src/mod_pubsub/pubsub_clean.erl @@ -25,7 +25,7 @@ purge() -> I=element(3,N), lists:foreach(fun(JID) -> case mnesia:dirty_read({pubsub_state, {JID, I}}) of - [{pubsub_state, K, _, _, [{subscribed,S}]}] -> mnesia:dirty_delete({pubsub_subscription, S}); + [{pubsub_state, K, _, _, _, [{subscribed,S}]}] -> mnesia:dirty_delete({pubsub_subscription, S}); _ -> ok end, mnesia:dirty_delete({pubsub_state, {JID, I}}) diff --git a/src/mod_pubsub/pubsub_db_odbc.erl b/src/mod_pubsub/pubsub_db_odbc.erl index 21b285917..9b3dd4467 100644 --- a/src/mod_pubsub/pubsub_db_odbc.erl +++ b/src/mod_pubsub/pubsub_db_odbc.erl @@ -11,9 +11,9 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% @author Pablo Polvorin %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/pubsub_debug.erl b/src/mod_pubsub/pubsub_debug.erl index 97ca8275f..791031e5e 100644 --- a/src/mod_pubsub/pubsub_debug.erl +++ b/src/mod_pubsub/pubsub_debug.erl @@ -23,7 +23,7 @@ state(JID, NodeId) -> [S] -> S; _ -> undefined end. -states(NodeId) -> mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, _='_'}). +states(NodeId) -> mnesia:dirty_index_read(pubsub_state, NodeId, #pubsub_state.nodeidx). stateid(S) -> element(1, S#pubsub_state.stateid). stateids(NodeId) -> [stateid(S) || S <- states(NodeId)]. states_by_jid(JID) -> mnesia:dirty_match_object(#pubsub_state{stateid={JID, '_'}, _='_'}). @@ -33,7 +33,7 @@ item(ItemId, NodeId) -> [I] -> I; _ -> undefined end. -items(NodeId) -> mnesia:dirty_match_object(#pubsub_item{itemid={'_', NodeId}, _='_'}). +items(NodeId) -> mnesia:dirty_index_read(pubsub_item, NodeId, #pubsub_item.nodeidx). itemid(I) -> element(1, I#pubsub_item.itemid). itemids(NodeId) -> [itemid(I) || I <- items(NodeId)]. items_by_id(ItemId) -> mnesia:dirty_match_object(#pubsub_item{itemid={ItemId, '_'}, _='_'}). @@ -41,10 +41,10 @@ items_by_id(ItemId) -> mnesia:dirty_match_object(#pubsub_item{itemid={ItemId, '_ affiliated(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.affiliation=/=none]. subscribed(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.subscriptions=/=[]]. %subscribed(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.subscription=/=none]. %% old record -owners(NodeId) -> [stateid(S) || S <- mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, affiliation=owner, _='_'})]. +owners(NodeId) -> [stateid(S) || S <- states(NodeId), S#pubsub_state.affiliation==owner]. orphan_items(NodeId) -> - itemids(NodeId) -- lists:foldl(fun(S, A) -> A++S#pubsub_state.items end, [], mnesia:dirty_match_object(#pubsub_state{stateid={'_', NodeId}, _='_'})). + itemids(NodeId) -- lists:foldl(fun(S, A) -> A++S#pubsub_state.items end, [], states(NodeId)). newer_items(NodeId, Seconds) -> Now = calendar:universal_time(), Oldest = calendar:seconds_to_daystime(Seconds), diff --git a/src/mod_pubsub/pubsub_index.erl b/src/mod_pubsub/pubsub_index.erl index 6cde91dcc..f10c33612 100644 --- a/src/mod_pubsub/pubsub_index.erl +++ b/src/mod_pubsub/pubsub_index.erl @@ -11,12 +11,12 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% -%%% @copyright 2006-2010 ProcessOne +%%% @copyright 2006-2011 ProcessOne %%% @author Christophe Romain %%% [http://www.process-one.net/] %%% @version {@vsn}, {@date} {@time} diff --git a/src/mod_pubsub/pubsub_odbc.patch b/src/mod_pubsub/pubsub_odbc.patch index 998132f74..948dc7c67 100644 --- a/src/mod_pubsub/pubsub_odbc.patch +++ b/src/mod_pubsub/pubsub_odbc.patch @@ -1,5 +1,5 @@ ---- mod_pubsub.erl 2010-12-07 13:54:26.000000000 +0100 -+++ mod_pubsub_odbc.erl 2010-12-07 13:59:56.000000000 +0100 +--- mod_pubsub.erl 2011-03-18 15:07:54.000000000 +0100 ++++ mod_pubsub_odbc.erl 2011-03-18 15:16:22.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. @@ -496,16 +496,16 @@ {result, Subscriptions} = node_call(Type, get_subscriptions, [NodeId, Subscriber]), if not IsApprover -> -@@ -1780,7 +1595,7 @@ +@@ -1778,7 +1593,7 @@ Reply = [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [{xmlelement, "create", nodeAttr(Node), []}]}], - case transaction(CreateNode, transaction) of + case transaction(Host, CreateNode, transaction) of - {result, {NodeId, SubsByDepth, {Result, broadcast}}} -> - broadcast_created_node(Host, Node, NodeId, Type, NodeOptions, SubsByDepth), + {result, {NodeId, {Result, broadcast}}} -> + broadcast_created_node(Host, Node, NodeId, Type, NodeOptions), case Result of -@@ -1883,7 +1698,7 @@ +@@ -1879,7 +1694,7 @@ %%
  • The node does not exist.
  • %% subscribe_node(Host, Node, From, JID, Configuration) -> @@ -514,7 +514,7 @@ {result, GoodSubOpts} -> GoodSubOpts; _ -> invalid end, -@@ -1891,7 +1706,7 @@ +@@ -1887,7 +1702,7 @@ error -> {"", "", ""}; J -> jlib:jid_tolower(J) end, @@ -523,7 +523,7 @@ Features = features(Type), SubscribeFeature = lists:member("subscribe", Features), OptionsFeature = lists:member("subscription-options", Features), -@@ -1900,6 +1715,7 @@ +@@ -1896,6 +1711,7 @@ AccessModel = get_option(Options, access_model), SendLast = get_option(Options, send_last_published_item), AllowedGroups = get_option(Options, roster_groups_allowed, []), @@ -531,7 +531,7 @@ {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, Subscriber, Owners, AccessModel, AllowedGroups), if not SubscribeFeature -> -@@ -2231,7 +2047,7 @@ +@@ -2226,7 +2042,7 @@ %%

    The permission are not checked in this function.

    %% @todo We probably need to check that the user doing the query has the right %% to read the items. @@ -540,7 +540,7 @@ MaxItems = if SMaxItems == "" -> get_max_items_node(Host); -@@ -2245,12 +2061,13 @@ +@@ -2240,12 +2056,13 @@ {error, Error} -> {error, Error}; _ -> @@ -555,7 +555,7 @@ {PresenceSubscription, RosterGroup} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), if not RetreiveFeature -> -@@ -2263,11 +2080,11 @@ +@@ -2258,11 +2075,11 @@ node_call(Type, get_items, [NodeId, From, AccessModel, PresenceSubscription, RosterGroup, @@ -569,7 +569,7 @@ SendItems = case ItemIDs of [] -> Items; -@@ -2280,7 +2097,8 @@ +@@ -2275,7 +2092,8 @@ %% number of items sent to MaxItems: {result, [{xmlelement, "pubsub", [{"xmlns", ?NS_PUBSUB}], [{xmlelement, "items", nodeAttr(Node), @@ -579,7 +579,7 @@ Error -> Error end -@@ -2302,10 +2120,15 @@ +@@ -2297,10 +2115,15 @@ Error -> Error end. get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) -> @@ -596,7 +596,7 @@ %% @spec (Host, Node, NodeId, Type, LJID, Number) -> any() -@@ -2318,16 +2141,27 @@ +@@ -2313,16 +2136,27 @@ %% @doc

    Resend the items of a node to the user.

    %% @todo use cache-last-item feature send_items(Host, Node, NodeId, Type, LJID, last) -> @@ -630,7 +630,7 @@ send_items(Host, Node, NodeId, Type, LJID, Number) -> ToSend = case node_action(Host, Type, get_items, [NodeId, LJID]) of {result, []} -> -@@ -2453,7 +2287,8 @@ +@@ -2448,7 +2282,8 @@ error -> {error, ?ERR_BAD_REQUEST}; _ -> @@ -640,7 +640,7 @@ case lists:member(Owner, Owners) of true -> OwnerJID = jlib:make_jid(Owner), -@@ -2463,24 +2298,7 @@ +@@ -2458,24 +2293,7 @@ end, lists:foreach( fun({JID, Affiliation}) -> @@ -666,21 +666,19 @@ end, FilteredEntities), {result, []}; _ -> -@@ -2533,11 +2351,11 @@ +@@ -2528,9 +2346,9 @@ end. read_sub(Subscriber, Node, NodeID, SubID, Lang) -> - case pubsub_subscription:get_subscription(Subscriber, NodeID, SubID) of + case pubsub_subscription_odbc:get_subscription(Subscriber, NodeID, SubID) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; {result, #pubsub_subscription{options = Options}} -> - {result, XdataEl} = pubsub_subscription:get_options_xform(Lang, Options), + {result, XdataEl} = pubsub_subscription_odbc:get_options_xform(Lang, Options), OptionsEl = {xmlelement, "options", [{"jid", jlib:jid_to_string(Subscriber)}, {"subid", SubID}|nodeAttr(Node)], [XdataEl]}, -@@ -2563,7 +2381,7 @@ +@@ -2562,7 +2380,7 @@ end. set_options_helper(Configuration, JID, NodeID, SubID, Type) -> @@ -689,16 +687,7 @@ {result, GoodSubOpts} -> GoodSubOpts; _ -> invalid end, -@@ -2592,7 +2410,7 @@ - write_sub(_Subscriber, _NodeID, _SubID, invalid) -> - {error, extended_error(?ERR_BAD_REQUEST, "invalid-options")}; - write_sub(Subscriber, NodeID, SubID, Options) -> -- case pubsub_subscription:set_subscription(Subscriber, NodeID, SubID, Options) of -+ case pubsub_subscription_odbc:set_subscription(Subscriber, NodeID, SubID, Options) of - {error, notfound} -> - {error, extended_error(?ERR_NOT_ACCEPTABLE, "invalid-subid")}; - {result, _} -> -@@ -2760,8 +2578,8 @@ +@@ -2761,8 +2579,8 @@ {"subscription", subscription_to_string(Sub)} | nodeAttr(Node)], []}]}]}, ejabberd_router:route(service_jid(Host), jlib:make_jid(JID), Stanza) end, @@ -709,28 +698,27 @@ true -> Result = lists:foldl(fun({JID, Subscription, SubId}, Acc) -> -@@ -3116,7 +2934,7 @@ - {Depth, [{N, get_node_subs(N)} || N <- Nodes]} - end, tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]))} +@@ -3125,7 +2943,7 @@ + Collection = tree_call(Host, get_parentnodes_tree, [Host, Node, service_jid(Host)]), + {result, [{Depth, [{N, sub_with_options(N)} || N <- Nodes]} || {Depth, Nodes} <- Collection]} end, - case transaction(Action, sync_dirty) of + case transaction(Host, Action, sync_dirty) of - {result, CollSubs} -> CollSubs; + {result, CollSubs} -> subscribed_nodes_by_jid(NotifyType, CollSubs); _ -> [] end. -@@ -3130,9 +2948,9 @@ +@@ -3194,8 +3012,8 @@ + [] + end. + sub_with_options(JID, NodeId, SubId) -> +- case pubsub_subscription:read_subscription(JID, NodeId, SubId) of +- #pubsub_subscription{options = Options} -> {JID, SubId, Options}; ++ case pubsub_subscription_odbc:get_subscription(JID, NodeId, SubId) of ++ {result, #pubsub_subscription{options = Options}} -> {JID, SubId, Options}; + _ -> {JID, SubId, []} + end. - get_options_for_subs(NodeID, Subs) -> - lists:foldl(fun({JID, subscribed, SubID}, Acc) -> -- case pubsub_subscription:read_subscription(JID, NodeID, SubID) of -+ case pubsub_subscription_odbc:get_subscription(JID, NodeID, SubID) of - {error, notfound} -> [{JID, SubID, []} | Acc]; -- #pubsub_subscription{options = Options} -> [{JID, SubID, Options} | Acc]; -+ {result, #pubsub_subscription{options = Options}} -> [{JID, SubID, Options} | Acc]; - _ -> Acc - end; - (_, Acc) -> -@@ -3321,6 +3139,30 @@ +@@ -3271,6 +3089,30 @@ Result end. @@ -761,7 +749,7 @@ %% @spec (Host, Options) -> MaxItems %% Host = host() %% Options = [Option] -@@ -3717,7 +3559,13 @@ +@@ -3667,7 +3509,13 @@ tree_action(Host, Function, Args) -> ?DEBUG("tree_action ~p ~p ~p",[Host,Function,Args]), Fun = fun() -> tree_call(Host, Function, Args) end, @@ -776,7 +764,7 @@ %% @doc

    node plugin call.

    node_call(Type, Function, Args) -> -@@ -3737,13 +3585,13 @@ +@@ -3687,13 +3535,13 @@ node_action(Host, Type, Function, Args) -> ?DEBUG("node_action ~p ~p ~p ~p",[Host,Type,Function,Args]), @@ -792,7 +780,7 @@ case tree_call(Host, get_node, [Host, Node]) of N when is_record(N, pubsub_node) -> case Action(N) of -@@ -3755,13 +3603,19 @@ +@@ -3705,13 +3553,19 @@ Error end end, Trans). @@ -816,7 +804,7 @@ {result, Result} -> {result, Result}; {error, Error} -> {error, Error}; {atomic, {result, Result}} -> {result, Result}; -@@ -3769,6 +3623,15 @@ +@@ -3719,6 +3573,15 @@ {aborted, Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{aborted, Reason}]), {error, ?ERR_INTERNAL_SERVER_ERROR}; @@ -832,7 +820,7 @@ {'EXIT', Reason} -> ?ERROR_MSG("transaction return internal error: ~p~n", [{'EXIT', Reason}]), {error, ?ERR_INTERNAL_SERVER_ERROR}; -@@ -3777,6 +3640,17 @@ +@@ -3727,6 +3590,17 @@ {error, ?ERR_INTERNAL_SERVER_ERROR} end. diff --git a/src/mod_pubsub/pubsub_subscription.erl b/src/mod_pubsub/pubsub_subscription.erl index 4d157db94..ef164ff4b 100644 --- a/src/mod_pubsub/pubsub_subscription.erl +++ b/src/mod_pubsub/pubsub_subscription.erl @@ -11,9 +11,9 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% @author Brian Cully %%% @version {@vsn}, {@date} {@time} @@ -160,6 +160,8 @@ create_table() -> Other -> Other end. +add_subscription(_JID, _NodeID, []) -> + make_subid(); add_subscription(_JID, _NodeID, Options) -> SubID = make_subid(), mnesia:write(#pubsub_subscription{subid = SubID, options = Options}), @@ -174,11 +176,8 @@ read_subscription(_JID, _NodeID, SubID) -> _ -> {error, notfound} end. -write_subscription(JID, NodeID, SubID, Options) -> - case read_subscription(JID, NodeID, SubID) of - {error, notfound} -> {error, notfound}; - Sub -> mnesia:write(Sub#pubsub_subscription{options = Options}) - end. +write_subscription(_JID, _NodeID, SubID, Options) -> + mnesia:write(#pubsub_subscription{subid = SubID, options = Options}). make_subid() -> {T1, T2, T3} = now(), diff --git a/src/mod_pubsub/pubsub_subscription_odbc.erl b/src/mod_pubsub/pubsub_subscription_odbc.erl index ce4bafc84..d3abee604 100644 --- a/src/mod_pubsub/pubsub_subscription_odbc.erl +++ b/src/mod_pubsub/pubsub_subscription_odbc.erl @@ -11,9 +11,9 @@ %%% under the License. %%% %%% The Initial Developer of the Original Code is ProcessOne. -%%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %%% All Rights Reserved.'' -%%% This software is copyright 2006-2010, ProcessOne. +%%% This software is copyright 2006-2011, ProcessOne. %%% %%% @author Pablo Polvorin %%% @author based on pubsub_subscription.erl by Brian Cully diff --git a/src/mod_register.erl b/src/mod_register.erl index 92b4df712..048678373 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -5,7 +5,7 @@ %%% Created : 8 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -106,8 +106,10 @@ process_iq(From, To, PTag = xml:get_subtag(SubEl, "password"), RTag = xml:get_subtag(SubEl, "remove"), Server = To#jid.lserver, + Access = gen_mod:get_module_opt(Server, ?MODULE, access, all), + AllowRemove = (allow == acl:match_rule(Server, Access, From)), if - (UTag /= false) and (RTag /= false) -> + (UTag /= false) and (RTag /= false) and AllowRemove -> User = xml:get_tag_cdata(UTag), case From of #jid{user = User, lserver = Server} -> @@ -148,7 +150,7 @@ process_iq(From, To, sub_el = [SubEl, ?ERR_BAD_REQUEST]} end end; - (UTag == false) and (RTag /= false) -> + (UTag == false) and (RTag /= false) and AllowRemove -> case From of #jid{user = User, lserver = Server, @@ -234,13 +236,18 @@ process_iq(From, To, {"var", "password"}], [{xmlelement, "required", [], []}]}, case ejabberd_captcha:create_captcha_x( - ID, To, Lang, [InstrEl, UField, PField]) of + ID, To, Lang, Source, [InstrEl, UField, PField]) of {ok, CaptchaEls} -> IQ#iq{type = result, sub_el = [{xmlelement, "query", [{"xmlns", "jabber:iq:register"}], [TopInstrEl | CaptchaEls]}]}; - error -> + {error, limit} -> + ErrText = "Too many CAPTCHA requests", + IQ#iq{type = error, + sub_el = [SubEl, ?ERRT_RESOURCE_CONSTRAINT( + Lang, ErrText)]}; + _Err -> ErrText = "Unable to generate a CAPTCHA", IQ#iq{type = error, sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR( diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 90c46017d..63162b4d0 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -5,7 +5,7 @@ %%% Created : 11 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -161,7 +161,7 @@ get_versioning_feature(Acc, Host) -> Feature = {xmlelement, "ver", [{"xmlns", ?NS_ROSTER_VER}], - [{xmlelement, "optional", [], []}]}, + []}, [Feature | Acc]; false -> [] end. @@ -411,10 +411,17 @@ push_item(User, Server, From, Item) -> % TODO: don't push to those who didn't load roster push_item(User, Server, Resource, From, Item) -> + push_item(User, Server, Resource, From, Item, not_found). + +push_item(User, Server, Resource, From, Item, RosterVersion) -> + ExtraAttrs = case RosterVersion of + not_found -> []; + _ -> [{"ver", RosterVersion}] + end, ResIQ = #iq{type = set, xmlns = ?NS_ROSTER, id = "push" ++ randoms:get_string(), sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_ROSTER}], + [{"xmlns", ?NS_ROSTER}|ExtraAttrs], [item_to_xml(Item)]}]}, ejabberd_router:route( From, @@ -425,21 +432,9 @@ push_item(User, Server, Resource, From, Item) -> %% TODO: don't push to those who didn't load roster push_item_version(Server, User, From, Item, RosterVersion) -> lists:foreach(fun(Resource) -> - push_item_version(User, Server, Resource, From, Item, RosterVersion) + push_item(User, Server, Resource, From, Item, RosterVersion) end, ejabberd_sm:get_user_resources(User, Server)). -push_item_version(User, Server, Resource, From, Item, RosterVersion) -> - IQPush = #iq{type = 'set', xmlns = ?NS_ROSTER, - id = "push" ++ randoms:get_string(), - sub_el = [{xmlelement, "query", - [{"xmlns", ?NS_ROSTER}, - {"ver", RosterVersion}], - [item_to_xml(Item)]}]}, - ejabberd_router:route( - From, - jlib:make_jid(User, Server, Resource), - jlib:iq_to_xml(IQPush)). - get_subscription_lists(_, User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), diff --git a/src/mod_roster.hrl b/src/mod_roster.hrl index 36ab44574..4ec3b1ab6 100644 --- a/src/mod_roster.hrl +++ b/src/mod_roster.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_roster_odbc.erl b/src/mod_roster_odbc.erl index 415961485..b713a0aab 100644 --- a/src/mod_roster_odbc.erl +++ b/src/mod_roster_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 15 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -249,6 +249,11 @@ get_roster(LUser, LServer) -> _ -> [] end, + GroupsDict = + lists:foldl( + fun({J, G}, Acc) -> + dict:append(J, G, Acc) + end, dict:new(), JIDGroups), RItems = lists:flatmap( fun(I) -> case raw_to_record(LServer, I) of @@ -257,12 +262,11 @@ get_roster(LUser, LServer) -> []; R -> SJID = jlib:jid_to_string(R#roster.jid), - Groups = lists:flatmap( - fun({S, G}) when S == SJID -> - [G]; - (_) -> - [] - end, JIDGroups), + Groups = + case dict:find(SJID, GroupsDict) of + {ok, Gs} -> Gs; + error -> [] + end, [R#roster{groups = Groups}] end end, Items), diff --git a/src/mod_service_log.erl b/src/mod_service_log.erl index 44fcfd25c..e28aabc3b 100644 --- a/src/mod_service_log.erl +++ b/src/mod_service_log.erl @@ -5,7 +5,7 @@ %%% Created : 24 Aug 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index d5070c81c..61e84b0da 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -5,7 +5,7 @@ %%% Created : 5 Mar 2005 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -37,6 +37,8 @@ process_item/2, in_subscription/6, out_subscription/4, + user_available/1, + unset_presence/4, register_user/2, remove_user/2, list_groups/1, @@ -85,8 +87,14 @@ start(Host, _Opts) -> ?MODULE, get_jid_info, 70), ejabberd_hooks:add(roster_process_item, Host, ?MODULE, process_item, 50), + ejabberd_hooks:add(user_available_hook, Host, + ?MODULE, user_available, 50), + ejabberd_hooks:add(unset_presence_hook, Host, + ?MODULE, unset_presence, 50), ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 50), + ejabberd_hooks:add(anonymous_purge_hook, Host, + ?MODULE, remove_user, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50). %%ejabberd_hooks:add(remove_user, Host, @@ -109,8 +117,14 @@ stop(Host) -> ?MODULE, get_jid_info, 70), ejabberd_hooks:delete(roster_process_item, Host, ?MODULE, process_item, 50), + ejabberd_hooks:delete(user_available_hook, Host, + ?MODULE, user_available, 50), + ejabberd_hooks:delete(unset_presence_hook, Host, + ?MODULE, unset_presence, 50), ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 50), + ejabberd_hooks:delete(anonymous_purge_hook, Host, + ?MODULE, remove_user, 50), ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50). %%ejabberd_hooks:delete(remove_user, Host, @@ -470,21 +484,38 @@ get_group_opt(Host, Group, Opt, Default) -> Default end. +get_online_users(Host) -> + lists:usort([{U, S} || {U, S, _} <- ejabberd_sm:get_vh_session_list(Host)]). + get_group_users(Host, Group) -> case get_group_opt(Host, Group, all_users, false) of true -> ejabberd_auth:get_vh_registered_users(Host); false -> [] - end ++ get_group_explicit_users(Host, Group). + end ++ + case get_group_opt(Host, Group, online_users, false) of + true -> + get_online_users(Host); + false -> + [] + end ++ + get_group_explicit_users(Host, Group). -get_group_users(_User, Host, Group, GroupOpts) -> +get_group_users(Host, Group, GroupOpts) -> case proplists:get_value(all_users, GroupOpts, false) of true -> ejabberd_auth:get_vh_registered_users(Host); false -> [] - end ++ get_group_explicit_users(Host, Group). + end ++ + case proplists:get_value(online_users, GroupOpts, false) of + true -> + get_online_users(Host); + false -> + [] + end ++ + get_group_explicit_users(Host, Group). %% @spec (Host::string(), Group::string()) -> [{User::string(), Server::string()}] get_group_explicit_users(Host, Group) -> @@ -502,11 +533,20 @@ get_group_explicit_users(Host, Group) -> get_group_name(Host, Group) -> get_group_opt(Host, Group, name, Group). -%% Get list of names of groups that have @all@ in the memberlist +%% Get list of names of groups that have @all@/@online@/etc in the memberlist get_special_users_groups(Host) -> lists:filter( fun(Group) -> get_group_opt(Host, Group, all_users, false) + orelse get_group_opt(Host, Group, online_users, false) + end, + list_groups(Host)). + +%% Get list of names of groups that have @online@ in the memberlist +get_special_users_groups_online(Host) -> + lists:filter( + fun(Group) -> + get_group_opt(Host, Group, online_users, false) end, list_groups(Host)). @@ -661,7 +701,7 @@ push_user_to_members(User, Server, Subscription) -> lists:foreach( fun({U, S}) -> push_roster_item(U, S, LUser, LServer, GroupName, Subscription) - end, get_group_users(LUser, LServer, Group, GroupOpts)) + end, get_group_users(LServer, Group, GroupOpts)) end, lists:usort(SpecialGroups++UserGroups)). push_user_to_displayed(LUser, LServer, Group, Subscription) -> @@ -673,7 +713,8 @@ push_user_to_displayed(LUser, LServer, Group, Subscription) -> push_user_to_group(LUser, LServer, Group, GroupName, Subscription) -> lists:foreach( - fun({U, S}) -> + fun({U, S}) when (U == LUser) and (S == LServer) -> ok; + ({U, S}) -> push_roster_item(U, S, LUser, LServer, GroupName, Subscription) end, get_group_users(LServer, Group)). @@ -757,6 +798,51 @@ ask_to_pending(subscribe) -> out; ask_to_pending(unsubscribe) -> none; ask_to_pending(Ask) -> Ask. +user_available(New) -> + LUser = New#jid.luser, + LServer = New#jid.lserver, + Resources = ejabberd_sm:get_user_resources(LUser, LServer), + ?DEBUG("user_available for ~p @ ~p (~p resources)", + [LUser, LServer, length(Resources)]), + case length(Resources) of + %% first session for this user + 1 -> + %% This is a simplification - we ignore he 'display' + %% property - @online@ is always reflective. + OnlineGroups = get_special_users_groups_online(LServer), + lists:foreach( + fun(OG) -> + ?DEBUG("user_available: pushing ~p @ ~p grp ~p", + [LUser, LServer, OG ]), + push_user_to_displayed(LUser, LServer, OG, both) + end, OnlineGroups); + _ -> + ok + end. + +unset_presence(LUser, LServer, Resource, Status) -> + Resources = ejabberd_sm:get_user_resources(LUser, LServer), + ?DEBUG("unset_presence for ~p @ ~p / ~p -> ~p (~p resources)", + [LUser, LServer, Resource, Status, length(Resources)]), + %% if user has no resources left... + case length(Resources) of + 0 -> + %% This is a simplification - we ignore he 'display' + %% property - @online@ is always reflective. + OnlineGroups = get_special_users_groups_online(LServer), + %% for each of these groups... + lists:foreach( + fun(OG) -> + %% Push removal of the old user to members of groups + %% where the group that this uwas members was displayed + push_user_to_displayed(LUser, LServer, OG, remove), + %% Push removal of members of groups that where + %% displayed to the group which thiuser has left + push_displayed_to_user(LUser, LServer, OG, LServer,remove) + end, OnlineGroups); + _ -> + ok + end. %%--------------------- %% Web Admin @@ -860,6 +946,7 @@ shared_roster_group(Host, Group, Query, Lang) -> Name = get_opt(GroupOpts, name, ""), Description = get_opt(GroupOpts, description, ""), AllUsers = get_opt(GroupOpts, all_users, false), + OnlineUsers = get_opt(GroupOpts, online_users, false), %%Disabled = false, DisplayedGroups = get_opt(GroupOpts, displayed_groups, []), Members = mod_shared_roster:get_group_explicit_users(Host, Group), @@ -869,7 +956,14 @@ shared_roster_group(Host, Group, Query, Lang) -> "@all@\n"; true -> [] - end ++ [[us_to_list(Member), $\n] || Member <- Members], + end ++ + if + OnlineUsers -> + "@online@\n"; + true -> + [] + end ++ + [[us_to_list(Member), $\n] || Member <- Members], FDisplayedGroups = [[DG, $\n] || DG <- DisplayedGroups], DescNL = length(element(2, regexp:split(Description, "\n"))), FGroup = @@ -953,6 +1047,8 @@ shared_roster_group_parse_query(Host, Group, Query) -> case SJID of "@all@" -> USs; + "@online@" -> + USs; _ -> case jlib:string_to_jid(SJID) of JID when is_record(JID, jid) -> @@ -967,10 +1063,15 @@ shared_roster_group_parse_query(Host, Group, Query) -> true -> [{all_users, true}]; false -> [] end, + OnlineUsersOpt = + case lists:member("@online@", SJIDs) of + true -> [{online_users, true}]; + false -> [] + end, mod_shared_roster:set_group_opts( Host, Group, - NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt), + NameOpt ++ DispGroupsOpt ++ DescriptionOpt ++ AllUsersOpt ++ OnlineUsersOpt), if NewMembers == error -> error; diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index a7c8ed440..e2ae9bca9 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -7,7 +7,7 @@ %%% Created : 5 Mar 2005 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_sic.erl b/src/mod_sic.erl index 08f356496..ae6944608 100644 --- a/src/mod_sic.erl +++ b/src/mod_sic.erl @@ -5,7 +5,7 @@ %%% Created : 6 Mar 2010 by Karim Gemayel %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_stats.erl b/src/mod_stats.erl index 84238bdb4..97ba031dd 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -5,7 +5,7 @@ %%% Created : 11 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_time.erl b/src/mod_time.erl index 52c52502a..1ffa762ce 100644 --- a/src/mod_time.erl +++ b/src/mod_time.erl @@ -5,7 +5,7 @@ %%% Created : 18 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 3cdd3cffa..77dc30b15 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -5,7 +5,7 @@ %%% Created : 2 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -163,7 +163,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) translate:translate( Lang, "Erlang Jabber Server") ++ - "\nCopyright (c) 2002-2010 ProcessOne"}]}, + "\nCopyright (c) 2002-2011 ProcessOne"}]}, {xmlelement, "BDAY", [], [{xmlcdata, "2002-11-16"}]} ]}]} @@ -437,7 +437,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, translate:translate( Lang, "ejabberd vCard module") ++ - "\nCopyright (c) 2003-2010 ProcessOne"}]}]. + "\nCopyright (c) 2003-2011 ProcessOne"}]}]. find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> find_xdata_el1(SubEls). diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index 53f8b58dc..30c694358 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -5,7 +5,7 @@ %%% Created : 2 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -235,7 +235,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) translate:translate( Lang, "Erlang Jabber Server") ++ - "\nCopyright (c) 2002-2010 ProcessOne"}]}, + "\nCopyright (c) 2002-2011 ProcessOne"}]}, {xmlelement, "BDAY", [], [{xmlcdata, "2002-11-16"}]} ]}]} @@ -535,7 +535,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, translate:translate( Lang, "ejabberd vCard module") ++ - "\nCopyright (c) 2003-2010 ProcessOne"}]}]. + "\nCopyright (c) 2003-2011 ProcessOne"}]}]. -define(LFIELD(Label, Var), {xmlelement, "field", [{"label", translate:translate(Lang, Label)}, diff --git a/src/mod_vcard_odbc.erl b/src/mod_vcard_odbc.erl index 701babb40..c35f27362 100644 --- a/src/mod_vcard_odbc.erl +++ b/src/mod_vcard_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 2 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -127,7 +127,7 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) translate:translate( Lang, "Erlang Jabber Server") ++ - "\nCopyright (c) 2002-2010 ProcessOne"}]}, + "\nCopyright (c) 2002-2011 ProcessOne"}]}, {xmlelement, "BDAY", [], [{xmlcdata, "2002-11-16"}]} ]}]} @@ -413,7 +413,7 @@ iq_get_vcard(Lang) -> [{xmlcdata, translate:translate( Lang, "ejabberd vCard module") ++ - "\nCopyright (c) 2003-2010 ProcessOne"}]}]. + "\nCopyright (c) 2003-2011 ProcessOne"}]}]. find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> find_xdata_el1(SubEls). diff --git a/src/mod_version.erl b/src/mod_version.erl index 3bf291c21..3ef3cfb2e 100644 --- a/src/mod_version.erl +++ b/src/mod_version.erl @@ -5,7 +5,7 @@ %%% Created : 18 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -53,7 +53,7 @@ process_local_iq(_From, To, #iq{id = _ID, type = Type, set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> - Host = To#jid.server, + Host = To#jid.lserver, OS = case gen_mod:get_module_opt(Host, ?MODULE, show_os, true) of true -> [get_os()]; false -> [] diff --git a/src/msgs/ca.msg b/src/msgs/ca.msg index 56a737c18..2742289a8 100644 --- a/src/msgs/ca.msg +++ b/src/msgs/ca.msg @@ -77,7 +77,6 @@ {"ejabberd Publish-Subscribe module","Mòdul ejannerd Publicar-Subscriure"}. {"ejabberd SOCKS5 Bytestreams module","mòdul ejabberd SOCKS5 Bytestreams"}. {"ejabberd vCard module","Mòdul ejabberd vCard"}. -{"ejabberd virtual hosts","Hosts virtuals del ejabberd"}. {"ejabberd Web Admin","Web d'administració del ejabberd"}. {"Elements","Elements"}. {"Email","Email"}. @@ -362,6 +361,7 @@ {"User","Usuari"}. {"Validate","Validar"}. {"vCard User Search","Recerca de vCard d'usuari"}. +{"Virtual Hosts","Hosts virtuals"}. {"Visitors are not allowed to change their nicknames in this room","Els visitants no tenen permés canviar el seus Nicknames en esta sala"}. {"Visitors are not allowed to send messages to all occupants","Els visitants no poden enviar missatges a tots els ocupants"}. {"Wednesday","Dimecres"}. diff --git a/src/msgs/ca.po b/src/msgs/ca.po index c916641c1..4554e91cc 100644 --- a/src/msgs/ca.po +++ b/src/msgs/ca.po @@ -1365,8 +1365,8 @@ msgid "~s access rule configuration" msgstr "Configuració de les Regles d'Accés ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Hosts virtuals del ejabberd" +msgid "Virtual Hosts" +msgstr "Hosts virtuals" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/cs.msg b/src/msgs/cs.msg index 21d04c334..eed1c47d5 100644 --- a/src/msgs/cs.msg +++ b/src/msgs/cs.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe modul"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams modul"}. {"ejabberd vCard module","ejabberd vCard modul"}. -{"ejabberd virtual hosts","Virtuální hostitelé ejabberd"}. {"ejabberd Web Admin","Webová administrace ejabberd"}. {"Elements","Položek"}. {"Email","E-mail"}. @@ -388,6 +387,7 @@ {"User","Uživatel"}. {"Validate","Ověřit"}. {"vCard User Search","Hledání uživatelů podle vizitek"}. +{"Virtual Hosts","Virtuální hostitelé"}. {"Visitors are not allowed to change their nicknames in this room","Návštěvníkům této místnosti je zakázáno měnit přezdívku"}. {"Visitors are not allowed to send messages to all occupants","Návštevníci nemají povoleno zasílat zprávy všem účastníkům konference"}. {"Wednesday","Středa"}. diff --git a/src/msgs/cs.po b/src/msgs/cs.po index fd3bd337f..c3f02cbf5 100644 --- a/src/msgs/cs.po +++ b/src/msgs/cs.po @@ -1345,8 +1345,8 @@ msgid "~s access rule configuration" msgstr "~s konfigurace pravidla přístupu" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Virtuální hostitelé ejabberd" +msgid "Virtual Hosts" +msgstr "Virtuální hostitelé" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/de.msg b/src/msgs/de.msg index 224b5241d..6dde04f8c 100644 --- a/src/msgs/de.msg +++ b/src/msgs/de.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe-Modul"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5-Bytestreams-Modul"}. {"ejabberd vCard module","ejabberd vCard-Modul"}. -{"ejabberd virtual hosts","ejabberd virtuelle Hosts"}. {"ejabberd Web Admin","ejabberd Web-Admin"}. {"Elements","Elemente"}. {"Email","E-Mail"}. @@ -388,6 +387,7 @@ {"Users Last Activity","Letzte Benutzeraktivität"}. {"Validate","Validieren"}. {"vCard User Search","vCard-Benutzer-Suche"}. +{"Virtual Hosts","Virtuelle Hosts"}. {"Visitors are not allowed to change their nicknames in this room","Besucher dürfen in diesem Raum ihren Benutzernamen nicht ändern"}. {"Visitors are not allowed to send messages to all occupants","Besucher dürfen nicht an alle Teilnehmer Nachrichten verschicken"}. {"Wednesday","Mittwoch"}. diff --git a/src/msgs/de.po b/src/msgs/de.po index 90abdd6ea..baec05323 100644 --- a/src/msgs/de.po +++ b/src/msgs/de.po @@ -1378,8 +1378,8 @@ msgid "~s access rule configuration" msgstr "~s Zugangsregel-Konfiguration" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "ejabberd virtuelle Hosts" +msgid "Virtual Hosts" +msgstr "Virtuelle Hosts" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/ejabberd.pot b/src/msgs/ejabberd.pot index 3311d6b71..921a307fe 100644 --- a/src/msgs/ejabberd.pot +++ b/src/msgs/ejabberd.pot @@ -1329,7 +1329,7 @@ msgid "~s access rule configuration" msgstr "" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" +msgid "Virtual Hosts" msgstr "" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 diff --git a/src/msgs/el.msg b/src/msgs/el.msg index d0b68fc7e..4fa37a046 100644 --- a/src/msgs/el.msg +++ b/src/msgs/el.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","ejabberd module Δημοσίευσης-Εγγραφής"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams module"}. {"ejabberd vCard module","ejabberd vCard module"}. -{"ejabberd virtual hosts","εικονικοί κεντρικοί υπολογιστές ejabberd"}. {"ejabberd Web Admin","ejabberd Web Admin"}. {"Elements","Στοιχεία"}. {"Email","Email"}. @@ -388,6 +387,7 @@ {"User","Χρήστης"}. {"Validate","Επαληθεύστε"}. {"vCard User Search","vCard Αναζήτηση χρηστών"}. +{"Virtual Hosts","εικονικοί κεντρικοί υπολογιστές"}. {"Visitors are not allowed to change their nicknames in this room","Οι επισκέπτες δεν επιτρέπεται να αλλάξουν τα ψευδώνυμα τους σε αυτή την αίθουσα"}. {"Visitors are not allowed to send messages to all occupants","Οι επισκέπτες δεν επιτρέπεται να στείλουν μηνύματα σε όλους τους συμμετέχωντες"}. {"Wednesday","Τετάρτη"}. diff --git a/src/msgs/el.po b/src/msgs/el.po index 35a74ac30..68561de5a 100644 --- a/src/msgs/el.po +++ b/src/msgs/el.po @@ -1382,8 +1382,8 @@ msgid "~s access rule configuration" msgstr "~s διαμόρφωση κανόνα πρόσβασης" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "εικονικοί κεντρικοί υπολογιστές ejabberd" +msgid "Virtual Hosts" +msgstr "εικονικοί κεντρικοί υπολογιστές" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/eo.msg b/src/msgs/eo.msg index 5fd21c37c..435f44d4f 100644 --- a/src/msgs/eo.msg +++ b/src/msgs/eo.msg @@ -77,7 +77,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Public-Abonada modulo"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bajtfluo modulo"}. {"ejabberd vCard module","ejabberd vCard-modulo"}. -{"ejabberd virtual hosts","ejabberd virtual-gastigoj"}. {"ejabberd Web Admin","ejabberd Teksaĵa Administro"}. {"Elements","Eroj"}. {"Email","Retpoŝto"}. @@ -362,6 +361,7 @@ {"User","Uzanto"}. {"Validate","Validigu"}. {"vCard User Search","Serĉado de vizitkartoj"}. +{"Virtual Hosts","Virtual-gastigoj"}. {"Visitors are not allowed to change their nicknames in this room","Ne estas permesata al vizitantoj ŝanĝi siajn kaŝnomojn en ĉi tiu ĉambro"}. {"Visitors are not allowed to send messages to all occupants","Vizitantoj ne rajtas sendi mesaĝojn al ĉiuj partoprenantoj"}. {"Wednesday","Merkredo"}. diff --git a/src/msgs/eo.po b/src/msgs/eo.po index 9d84ba3eb..62b93a76f 100644 --- a/src/msgs/eo.po +++ b/src/msgs/eo.po @@ -1359,8 +1359,8 @@ msgid "~s access rule configuration" msgstr "Agordo de atingo-reguloj de ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "ejabberd virtual-gastigoj" +msgid "Virtual Hosts" +msgstr "Virtual-gastigoj" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1789,9 +1789,6 @@ msgstr "" #~ msgid "(Raw)" #~ msgstr "(Kruda)" -#~ msgid "Virtual Hosts" -#~ msgstr "Virtual-gastigoj" - #~ msgid "Specified nickname is already registered" #~ msgstr "Donita kaŝnomo jam estas registrita" diff --git a/src/msgs/es.msg b/src/msgs/es.msg index 3b5a7a761..17598c45a 100644 --- a/src/msgs/es.msg +++ b/src/msgs/es.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","Módulo de Publicar-Subscribir de ejabberd"}. {"ejabberd SOCKS5 Bytestreams module","Módulo SOCKS5 Bytestreams para ejabberd"}. {"ejabberd vCard module","Módulo vCard para ejabberd"}. -{"ejabberd virtual hosts","Dominios de ejabberd"}. {"ejabberd Web Admin","ejabberd Web Admin"}. {"Elements","Elementos"}. {"Email","correo"}. @@ -388,6 +387,7 @@ {"User","Usuario"}. {"Validate","Validar"}. {"vCard User Search","Buscar vCard de usuario"}. +{"Virtual Hosts","Dominios Virtuales"}. {"Visitors are not allowed to change their nicknames in this room","Los visitantes no tienen permitido cambiar sus apodos en esta sala"}. {"Visitors are not allowed to send messages to all occupants","Los visitantes no pueden enviar mensajes a todos los ocupantes"}. {"Wednesday","miércoles"}. diff --git a/src/msgs/es.po b/src/msgs/es.po index fffef21f2..0d791b811 100644 --- a/src/msgs/es.po +++ b/src/msgs/es.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: es\n" "PO-Revision-Date: 2010-11-19 13:40+0100\n" -"Last-Translator: \n" +"Last-Translator: Badlop \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1367,8 +1367,8 @@ msgid "~s access rule configuration" msgstr "Configuración de las Regla de Acceso ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Dominios de ejabberd" +msgid "Virtual Hosts" +msgstr "Dominios Virtuales" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/fr.msg b/src/msgs/fr.msg index 59e6ff628..ee9ec2a38 100644 --- a/src/msgs/fr.msg +++ b/src/msgs/fr.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","Module Publish-Subscribe d'ejabberd"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams module"}. {"ejabberd vCard module","Module vCard ejabberd"}. -{"ejabberd virtual hosts","Serveurs virtuels d'ejabberd"}. {"ejabberd Web Admin","Console Web d'administration de ejabberd"}. {"Elements","Éléments"}. {"Email","Email"}. @@ -351,9 +350,9 @@ {"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","C'est insensible à la casse : macbeth est identique à MacBeth et Macbeth."}. {"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Cette page permet de créer un compte Jabber sur ce serveur Jabber. Votre JID (Jabber IDentifier, identifiant Jabber) sera de la forme : nom@serveur. Prière de lire avec attention les instructions pour remplir correctement ces champs."}. {"This page allows to unregister a Jabber account in this Jabber server.","Cette page permet d'effacer un compte Jabber sur ce serveur Jabber."}. -{"This participant is kicked from the room because he sent an error message","Ce participant est expulsé du salon pour avoir envoyé un message erroné"}. -{"This participant is kicked from the room because he sent an error message to another participant","Ce participant est expulsé du salon pour avoir envoyé un message erroné à un autre participant"}. -{"This participant is kicked from the room because he sent an error presence","Ce participant est expulsé du salon pour avoir envoyé une présence erroné"}. +{"This participant is kicked from the room because he sent an error message","Ce participant est expulsé du salon pour avoir envoyé un message erronée"}. +{"This participant is kicked from the room because he sent an error message to another participant","Ce participant est expulsé du salon pour avoir envoyé un message erronée à un autre participant"}. +{"This participant is kicked from the room because he sent an error presence","Ce participant est expulsé du salon pour avoir envoyé une présence erronée"}. {"This room is not anonymous","Ce salon n'est pas anonyme"}. {"Thursday","Jeudi"}. {"Time delay","Délais"}. @@ -388,6 +387,7 @@ {"User","Utilisateur"}. {"Validate","Valider"}. {"vCard User Search","Recherche dans l'annnuaire"}. +{"Virtual Hosts","Serveurs virtuels"}. {"Visitors are not allowed to change their nicknames in this room","Les visiteurs ne sont pas autorisés à changer de pseudo dans ce salon"}. {"Visitors are not allowed to send messages to all occupants","Les visiteurs ne sont pas autorisés à envoyer des messages à tout les occupants"}. {"Wednesday","Mercredi"}. diff --git a/src/msgs/fr.po b/src/msgs/fr.po index 750b591c5..7a807d877 100644 --- a/src/msgs/fr.po +++ b/src/msgs/fr.po @@ -752,7 +752,7 @@ msgstr "La limite de trafic a été dépassée" msgid "" "This participant is kicked from the room because he sent an error message" msgstr "" -"Ce participant est expulsé du salon pour avoir envoyé un message erroné" +"Ce participant est expulsé du salon pour avoir envoyé un message erronée" #: mod_muc/mod_muc_room.erl:251 msgid "It is not allowed to send private messages to the conference" @@ -767,7 +767,7 @@ msgid "" "This participant is kicked from the room because he sent an error message to " "another participant" msgstr "" -"Ce participant est expulsé du salon pour avoir envoyé un message erroné à un " +"Ce participant est expulsé du salon pour avoir envoyé un message erronée à un " "autre participant" #: mod_muc/mod_muc_room.erl:419 @@ -821,7 +821,7 @@ msgstr "" msgid "" "This participant is kicked from the room because he sent an error presence" msgstr "" -"Ce participant est expulsé du salon pour avoir envoyé une présence erroné" +"Ce participant est expulsé du salon pour avoir envoyé une présence erronée" #: mod_muc/mod_muc_room.erl:912 msgid "Visitors are not allowed to change their nicknames in this room" @@ -1381,8 +1381,8 @@ msgid "~s access rule configuration" msgstr "Configuration des règles d'accès ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Serveurs virtuels d'ejabberd" +msgid "Virtual Hosts" +msgstr "Serveurs virtuels" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1824,6 +1824,3 @@ msgstr "Effacer" #~ msgid "(Raw)" #~ msgstr "(Brut)" - -#~ msgid "Virtual Hosts" -#~ msgstr "Serveurs virtuels" diff --git a/src/msgs/gl.msg b/src/msgs/gl.msg index d6db76590..219bea35d 100644 --- a/src/msgs/gl.msg +++ b/src/msgs/gl.msg @@ -73,7 +73,6 @@ {"ejabberd Publish-Subscribe module","Módulo de Publicar-Subscribir de ejabberd"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams module"}. {"ejabberd vCard module","Módulo vCard para ejabberd"}. -{"ejabberd virtual hosts","Hosts virtuais de ejabberd"}. {"ejabberd Web Admin","Ejabberd Administrador Web"}. {"Elements","Elementos"}. {"Email","Email"}. @@ -356,6 +355,7 @@ {"User","Usuario"}. {"Validate","Validar"}. {"vCard User Search","Procura de usuario en vCard"}. +{"Virtual Hosts","Hosts Virtuais"}. {"Visitors are not allowed to change their nicknames in this room","Os visitantes non están autorizados a cambiar os seus That alcumes nesta sala"}. {"Visitors are not allowed to send messages to all occupants","Os visitantes non poden enviar mensaxes a todos os ocupantes"}. {"Wednesday","Mércores"}. diff --git a/src/msgs/gl.po b/src/msgs/gl.po index 3fe3b09e9..0e0036831 100644 --- a/src/msgs/gl.po +++ b/src/msgs/gl.po @@ -1370,8 +1370,8 @@ msgid "~s access rule configuration" msgstr "Configuración das Regra de Acceso ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Hosts virtuais de ejabberd" +msgid "Virtual Hosts" +msgstr "Hosts Virtuais" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1805,9 +1805,6 @@ msgstr "" #~ msgid "(Raw)" #~ msgstr "(Cru)" -#~ msgid "Virtual Hosts" -#~ msgstr "Hosts Virtuais" - #~ msgid "Specified nickname is already registered" #~ msgstr "O alcume especificado xa está rexistrado, terás que buscar outro" diff --git a/src/msgs/id.msg b/src/msgs/id.msg new file mode 100644 index 000000000..1922ad0c7 --- /dev/null +++ b/src/msgs/id.msg @@ -0,0 +1,408 @@ +{"Access Configuration","Akses Konfigurasi"}. +{"Access Control List Configuration","Konfigurasi Daftar Akses Pengendalian"}. +{"Access Control Lists","Akses Daftar Pengendalian"}. +{"Access control lists","Daftar Pengendalian Akses"}. +{"Access denied by service policy","Akses ditolak oleh kebijakan layanan"}. +{"Access rules","Akses peraturan"}. +{"Access Rules","Aturan Akses"}. +{"Action on user","Tindakan pada pengguna"}. +{"Add Jabber ID","Tambah Jabber ID"}. +{"Add New","Tambah Baru"}. +{"Add User","Tambah Pengguna"}. +{"Administration","Administrasi"}. +{"Administration of ","Administrasi"}. +{"Administrator privileges required","Hak istimewa Administrator dibutuhkan"}. +{"A friendly name for the node","Nama yang dikenal untuk node"}. +{"All activity","Semua aktifitas"}. +{"Allow this Jabber ID to subscribe to this pubsub node?","Izinkan ID Jabber ini untuk berlangganan pada node pubsub ini?"}. +{"Allow users to change the subject","Perbolehkan pengguna untuk mengganti topik"}. +{"Allow users to query other users","Perbolehkan pengguna untuk mengetahui pengguna lain"}. +{"Allow users to send invites","Perbolehkan pengguna mengirimkan undangan"}. +{"Allow users to send private messages","perbolehkan pengguna mengirimkan pesan ke pengguna lain secara pribadi"}. +{"Allow visitors to change nickname","Perbolehkan visitor mengganti nama julukan"}. +{"Allow visitors to send status text in presence updates","Izinkan pengunjung untuk mengirim teks status terbaru"}. +{"All Users","Semua Pengguna"}. +{"Announcements","Pengumuman"}. +{"anyone","Siapapun"}. +{"A password is required to enter this room","Diperlukan kata sandi untuk masuk ruangan ini"}. +{"April","April"}. +{"August","Agustus"}. +{"Backup","Backup"}. +{"Backup Management","Manajemen Backup"}. +{"Backup of ","Cadangan dari"}. +{"Backup to File at ","Backup ke File pada"}. +{"Bad format","Format yang buruk"}. +{"Birthday","Hari Lahir"}. +{"CAPTCHA web page","CAPTCHA laman web"}. +{"Change Password","Ubah Kata Sandi"}. +{"Change User Password","Ubah User Password"}. +{"Characters not allowed:","Karakter tidak diperbolehkan:"}. +{"Chatroom configuration modified","Konfigurasi ruang chat diubah"}. +{"Chatroom is created","Ruang chat telah dibuat"}. +{"Chatroom is destroyed","Ruang chat dilenyapkan"}. +{"Chatroom is started","Ruang chat dimulai"}. +{"Chatroom is stopped","Ruang chat dihentikan"}. +{"Chatrooms","Ruangan Chat"}. +{"Choose a username and password to register with this server","Pilih nama pengguna dan kata sandi untuk mendaftar dengan layanan ini"}. +{"Choose modules to stop","Pilih Modul untuk berhenti"}. +{"Choose storage type of tables","Pilih jenis penyimpanan tabel"}. +{"Choose whether to approve this entity's subscription.","Pilih apakah akan menyetujui hubungan pertemanan ini."}. +{"City","Kota"}. +{"Commands","Perintah"}. +{"Conference room does not exist","Ruang Konferensi tidak ada"}. +{"Configuration of room ~s","Pengaturan ruangan ~s"}. +{"Configuration","Pengaturan"}. +{"Connected Resources:","Sumber Daya Terhubung:"}. +{"Connections parameters","Parameter Koneksi"}. +{"Country","Negara"}. +{"CPU Time:","Waktu CPU:"}. +{"Database","Database"}. +{"Database Tables at ","Tabel Database pada"}. +{"Database Tables Configuration at ","Database Tabel Konfigurasi pada"}. +{"December","Desember"}. +{"Default users as participants","pengguna pertama kali masuk sebagai participant"}. +{"Delete","Hapus"}. +{"Delete message of the day","Hapus pesan harian"}. +{"Delete message of the day on all hosts","Hapus pesan harian pada semua host"}. +{"Delete Selected","Hapus Yang Terpilih"}. +{"Delete User","Hapus Pengguna"}. +{"Deliver event notifications","Memberikan pemberitahuan acara"}. +{"Deliver payloads with event notifications","Memberikan muatan dengan pemberitahuan acara"}. +{"Description:","Keterangan:"}. +{"Disc only copy","Hanya salinan dari disc"}. +{"Displayed Groups:","Tampilkan Grup:"}. +{"Don't tell your password to anybody, not even the administrators of the Jabber server.","Jangan memberitahukan kata sandi Anda ke siapapun, bahkan para administrator dari layanan Jabber."}. +{"Dump Backup to Text File at ","Dump Backup ke File Teks di"}. +{"Dump to Text File","Dump menjadi File Teks"}. +{"Edit Properties","Ganti Properti"}. +{"ejabberd IRC module","ejabberd IRC modul"}. +{"ejabberd MUC module","ejabberd MUC Module"}. +{"ejabberd Publish-Subscribe module","Modul ejabberd Setujui-Pertemanan"}. +{"ejabberd SOCKS5 Bytestreams module","modul ejabberd SOCKS5 Bytestreams"}. +{"ejabberd vCard module","Modul ejabberd vCard"}. +{"ejabberd Web Admin","Admin Web ejabberd"}. +{"Elements","Elemen-elemen"}. +{"Email","Email"}. +{"Enable logging","Aktifkan catatan"}. +{"Encoding for server ~b","Pengkodean untuk layanan ~b"}. +{"End User Session","Akhir Sesi Pengguna"}. +{"Enter list of {Module, [Options]}","Masukkan daftar {Modul, [Options]}"}. +{"Enter nickname you want to register","Masukkan nama julukan Anda jika ingin mendaftar"}. +{"Enter path to backup file","Masukkan path untuk file cadangan"}. +{"Enter path to jabberd14 spool dir","Masukkan path ke direktori spool jabberd14"}. +{"Enter path to jabberd14 spool file","Masukkan path ke file jabberd14 spool"}. +{"Enter path to text file","Masukkan path ke file teks"}. +{"Enter the text you see","Masukkan teks yang Anda lihat"}. +{"Enter username and encodings you wish to use for connecting to IRC servers. Press 'Next' to get more fields to fill in. Press 'Complete' to save settings.","Masukkan username dan pengkodean yang ingin Anda gunakan untuk menghubungkan ke layanan IRC. Tekan 'Selanjutnya' untuk mendapatkan lagi formulir kemudian Tekan 'Lengkap' untuk menyimpan pengaturan."}. +{"Enter username, encodings, ports and passwords you wish to use for connecting to IRC servers","Masukkan username, pengkodean, port dan sandi yang ingin Anda gunakan untuk menghubungkan ke layanan IRC"}. +{"Erlang Jabber Server","Layanan Erlang Jabber"}. +{"Error","Kesalahan"}. +{"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}].","Contoh: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef.net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]."}. +{"Export data of all users in the server to PIEFXIS files (XEP-0227):","Ekspor data dari semua pengguna pada layanan ke berkas PIEFXIS (XEP-0227):"}. +{"Export data of users in a host to PIEFXIS files (XEP-0227):","Ekspor data pengguna pada sebuah host ke berkas PIEFXIS (XEP-0227):"}. +{"Family Name","Nama Keluarga (marga)"}. +{"February","Februari"}. +{"Fill in fields to search for any matching Jabber User","Isi kolom untuk mencari pengguna Jabber yang sama"}. +{"Fill in the form to search for any matching Jabber User (Add * to the end of field to match substring)","Isi formulir untuk pencarian pengguna Jabber yang cocok (Tambahkan * ke mengakhiri pengisian untuk menyamakan kata)"}. +{"Friday","Jumat"}. +{"From","Dari"}. +{"From ~s","Dari ~s"}. +{"Full Name","Nama Lengkap"}. +{"Get Number of Online Users","Dapatkan Jumlah User Yang Online"}. +{"Get Number of Registered Users","Dapatkan Jumlah Pengguna Yang Terdaftar"}. +{"Get User Last Login Time","Dapatkan Waktu Login Terakhir Pengguna "}. +{"Get User Password","Dapatkan User Password"}. +{"Get User Statistics","Dapatkan Statistik Pengguna"}. +{"Group ","Grup"}. +{"Groups","Grup"}. +{"has been banned","telah dibanned"}. +{"has been kicked because of an affiliation change","telah dikick karena perubahan afiliasi"}. +{"has been kicked because of a system shutdown","telah dikick karena sistem shutdown"}. +{"has been kicked because the room has been changed to members-only","telah dikick karena ruangan telah diubah menjadi hanya untuk member"}. +{"has been kicked","telah dikick"}. +{" has set the subject to: ","telah menetapkan topik yaitu:"}. +{"Host","Host"}. +{"If you don't see the CAPTCHA image here, visit the web page.","Jika Anda tidak melihat gambar CAPTCHA disini, silahkan kunjungi halaman web."}. +{"If you want to specify different ports, passwords, encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\", port, \"password\"}'. By default this service use \"~s\" encoding, port ~p, empty password.","Jika Anda ingin menentukan port yang berbeda, sandi, pengkodean untuk layanan IRC, isi daftar ini dengan nilai-nilai dalam format '{\"server irc \", \"encoding \", port, \"sandi \"}'. Secara default ini menggunakan layanan \"~s \" pengkodean, port ~p, kata sandi kosong."}. +{"Import Directory","Impor Direktori"}. +{"Import File","Impor File"}. +{"Import user data from jabberd14 spool file:","Impor data pengguna dari sekumpulan berkas jabberd14:"}. +{"Import User from File at ","Impor Pengguna dari File pada"}. +{"Import users data from a PIEFXIS file (XEP-0227):","impor data-data pengguna dari sebuah PIEFXIS (XEP-0227):"}. +{"Import users data from jabberd14 spool directory:","Импорт пользовательских данных из буферной директории jabberd14:"}. +{"Import Users from Dir at ","Impor Pengguna dari Dir di"}. +{"Import Users From jabberd14 Spool Files","Impor Pengguna Dari jabberd14 Spool File"}. +{"Improper message type","Jenis pesan yang tidak benar"}. +{"Incorrect password","Kata sandi salah"}. +{"Invalid affiliation: ~s","Afiliasi tidak valid: ~s"}. +{"Invalid role: ~s","Peran tidak valid: ~s"}. +{"IP addresses","Alamat IP"}. +{"IP","IP"}. +{"IRC channel (don't put the first #)","Channel IRC (tidak perlu menempatkan # sebelumnya)"}. +{"IRC server","Layanan IRC"}. +{"IRC settings","Pengaturan IRC"}. +{"IRC Transport","IRC Transport"}. +{"IRC username","Nama Pengguna IRC"}. +{"IRC Username","Nama Pengguna IRC"}. +{"is now known as","sekarang dikenal sebagai"}. +{"It is not allowed to send private messages","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi"}. +{"It is not allowed to send private messages of type \"groupchat\"","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi jenis \"groupchat \""}. +{"It is not allowed to send private messages to the conference","Hal ini tidak diperbolehkan untuk mengirim pesan pribadi ke konferensi"}. +{"Jabber Account Registration","Pendaftaran Akun Jabber"}. +{"Jabber ID","Jabber ID"}. +{"Jabber ID ~s is invalid","Jabber ID ~s tidak valid"}. +{"January","Januari"}. +{"Join IRC channel","Gabung channel IRC"}. +{"joins the room","bergabung ke ruangan"}. +{"Join the IRC channel here.","Gabung ke channel IRC disini"}. +{"Join the IRC channel in this Jabber ID: ~s","Gabung ke channel IRC dengan Jabber ID: ~s"}. +{"July","Juli"}. +{"June","Juni"}. +{"Last Activity","Aktifitas Terakhir"}. +{"Last login","Terakhir Login"}. +{"Last month","Akhir bulan"}. +{"Last year","Akhir tahun"}. +{"leaves the room","meninggalkan ruangan"}. +{"Listened Ports at ","Mendeteksi Port-port di"}. +{"Listened Ports","Port Terdeteksi"}. +{"List of modules to start","Daftar modul untuk memulai"}. +{"Low level update script","Perbaruan naskah tingkat rendah"}. +{"Make participants list public","Buat daftar participant diketahui oleh public"}. +{"Make room captcha protected","Buat ruangan dilindungi dengan chapta"}. +{"Make room members-only","Buat ruangan hanya untuk member saja"}. +{"Make room moderated","Buat ruangan hanya untuk moderator saja"}. +{"Make room password protected","Buat ruangan yang dilindungi dengan kata sandi"}. +{"Make room persistent","Buat ruangan menjadi permanent"}. +{"Make room public searchable","Buat ruangan dapat dicari"}. +{"March","Maret"}. +{"Maximum Number of Occupants","Maksimum Jumlah Penghuni"}. +{"Max # of items to persist","Max item untuk bertahan"}. +{"Max payload size in bytes","Max kapasitas ukuran dalam bytes"}. +{"May","Mei"}. +{"Members:","Anggota:"}. +{"Membership is required to enter this room","Hanya Member yang dapat masuk ruangan ini"}. +{"Memorize your password, or write it in a paper placed in a safe place. In Jabber there isn't an automated way to recover your password if you forget it.","Hafalkan kata sandi Anda, atau dicatat dan letakkan di tempat yang aman. Didalam Jabber tidak ada cara otomatis untuk mendapatkan kembali password Anda jika Anda lupa."}. +{"Memory","Memori"}. +{"Message body","Isi Pesan"}. +{"Middle Name","Nama Tengah"}. +{"Moderator privileges required","Hak istimewa moderator dibutuhkan"}. +{"moderators only","Hanya moderator"}. +{"Modified modules","Modifikasi modul-modul"}. +{"Module","Modul"}. +{"Modules at ","modul-modul di"}. +{"Modules","Modul"}. +{"Monday","Senin"}. +{"Name:","Nama:"}. +{"Name","Nama"}. +{"Never","Tidak Pernah"}. +{"New Password:","Password Baru:"}. +{"Nickname","Nama Julukan"}. +{"Nickname Registration at ","Pendaftaran Julukan pada"}. +{"Nickname ~s does not exist in the room","Nama Julukan ~s tidak berada di dalam ruangan"}. +{"No body provided for announce message","Tidak ada isi pesan yang disediakan untuk mengirimkan pesan"}. +{"No Data","Tidak Ada Data"}. +{"Node ID","ID Node"}. +{"Node ","Node"}. +{"Node not found","Node tidak ditemukan"}. +{"Nodes","Node-node"}. +{"No limit","Tidak terbatas"}. +{"None","Tak satupun"}. +{"No resource provided","Tidak ada sumber daya yang disediakan"}. +{"Not Found","Tidak Ditemukan"}. +{"Notify subscribers when items are removed from the node","Beritahu pelanggan ketika item tersebut dikeluarkan dari node"}. +{"Notify subscribers when the node configuration changes","Beritahu pelanggan ketika ada perubahan konfigurasi node"}. +{"Notify subscribers when the node is deleted","Beritahu pelanggan ketika node dihapus"}. +{"November","Nopember"}. +{"Number of occupants","Jumlah Penghuni"}. +{"Number of online users","Jumlah pengguna online"}. +{"Number of registered users","Jumlah pengguna terdaftar"}. +{"October","Oktober"}. +{"Offline Messages:","Pesan Offline:"}. +{"Offline Messages","Pesan Offline"}. +{"OK","YA"}. +{"Old Password:","Password Lama:"}. +{"Online","Online"}. +{"Online Users:","Pengguna Online:"}. +{"Online Users","Pengguna Yang Online"}. +{"Only deliver notifications to available users","Hanya mengirimkan pemberitahuan kepada pengguna yang tersedia"}. +{"Only moderators and participants are allowed to change the subject in this room","Hanya moderator dan peserta yang diizinkan untuk mengganti topik pembicaraan di ruangan ini"}. +{"Only moderators are allowed to change the subject in this room","Hanya moderator yang diperbolehkan untuk mengubah topik dalam ruangan ini"}. +{"Only occupants are allowed to send messages to the conference","Hanya penghuni yang diizinkan untuk mengirim pesan ke konferensi"}. +{"Only occupants are allowed to send queries to the conference","Hanya penghuni diizinkan untuk mengirim permintaan ke konferensi"}. +{"Only service administrators are allowed to send service messages","Layanan hanya diperuntukan kepada administrator yang diizinkan untuk mengirim layanan pesan"}. +{"Options","Pilihan-pilihan"}. +{"Organization Name","Nama Organisasi"}. +{"Organization Unit","Unit Organisasi"}. +{"Outgoing s2s Connections","Koneksi Keluar s2s"}. +{"Outgoing s2s Connections:","Koneksi s2s yang keluar:"}. +{"Outgoing s2s Servers:","Layanan s2s yang keluar:"}. +{"Owner privileges required","Hak istimewa owner dibutuhkan"}. +{"Packet","Paket"}. +{"Password ~b","Kata Sandi ~b"}. +{"Password:","Kata Sandi:"}. +{"Password","Sandi"}. +{"Password Verification:","Verifikasi Kata Sandi:"}. +{"Password Verification","Verifikasi Sandi"}. +{"Path to Dir","Jalur ke Dir"}. +{"Path to File","Jalur ke File"}. +{"Pending","Tertunda"}. +{"Period: ","Periode:"}. +{"Persist items to storage","Pertahankan item ke penyimpanan"}. +{"Ping","Ping"}. +{"Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately.","Harap dicatat bahwa pilihan ini hanya akan membuat cadangan builtin Mnesia database. Jika Anda menggunakan modul ODBC, anda juga perlu untuk membuat cadangan database SQL Anda secara terpisah."}. +{"Pong","Pong"}. +{"Port ~b","Port ~b"}. +{"Port","Port"}. +{"Present real Jabber IDs to","Tampilkan Jabber ID secara lengkap"}. +{"private, ","pribadi, "}. +{"Protocol","Protocol"}. +{"Publish-Subscribe","Setujui-Pertemanan"}. +{"PubSub subscriber request","Permintaan pertemanan PubSub"}. +{"Purge all items when the relevant publisher goes offline","Bersihkan semua item ketika penerbit yang relevan telah offline"}. +{"Queries to the conference members are not allowed in this room","Permintaan untuk para anggota konferensi tidak diperbolehkan di ruangan ini"}. +{"RAM and disc copy","RAM dan disc salinan"}. +{"RAM copy","Salinan RAM"}. +{"Raw","mentah"}. +{"Really delete message of the day?","Benar-benar ingin menghapus pesan harian?"}. +{"Recipient is not in the conference room","Penerima tidak berada di ruangan konferensi"}. +{"Register a Jabber account","Daftarkan sebuah akun jabber"}. +{"Registered Users:","Pengguna Terdaftar:"}. +{"Registered Users","Pengguna Terdaftar"}. +{"Register","Mendaftar"}. +{"Registration in mod_irc for ","Pendaftaran di mod_irc untuk"}. +{"Remote copy","Salinan Remote"}. +{"Remove All Offline Messages","Hapus Semua Pesan Offline"}. +{"Remove","Menghapus"}. +{"Remove User","Hapus Pengguna"}. +{"Replaced by new connection","Diganti dengan koneksi baru"}. +{"Resources","Sumber daya"}. +{"Restart","Jalankan Ulang"}. +{"Restart Service","Restart Layanan"}. +{"Restore Backup from File at ","Kembalikan Backup dari File pada"}. +{"Restore binary backup after next ejabberd restart (requires less memory):","Mengembalikan cadangan yang berpasanagn setelah ejabberd berikutnya dijalankan ulang (memerlukan memori lebih sedikit):"}. +{"Restore binary backup immediately:","Segera mengembalikan cadangan yang berpasangan:"}. +{"Restore","Mengembalikan"}. +{"Restore plain text backup immediately:","Segera mengembalikan cadangan teks biasa:"}. +{"Room Configuration","Konfigurasi Ruangan"}. +{"Room creation is denied by service policy","Pembuatan Ruangan ditolak oleh kebijakan layanan"}. +{"Room description","Keterangan ruangan"}. +{"Room Occupants","Penghuni Ruangan"}. +{"Room title","Nama Ruangan"}. +{"Roster groups allowed to subscribe","Kelompok kontak yang diizinkan untuk berlangganan"}. +{"Roster","Kontak"}. +{"Roster of ","Kontak dari"}. +{"Roster size","Ukuran Daftar Kontak"}. +{"RPC Call Error","Panggilan Kesalahan RPC"}. +{"Running Nodes","Menjalankan Node"}. +{"~s access rule configuration","~s aturan akses konfigurasi"}. +{"Saturday","Sabtu"}. +{"Script check","Periksa naskah"}. +{"Search Results for ","Hasil Pencarian untuk"}. +{"Search users in ","Pencarian pengguna dalam"}. +{"Send announcement to all online users","Kirim pengumuman untuk semua pengguna yang online"}. +{"Send announcement to all online users on all hosts","Kirim pengumuman untuk semua pengguna yang online pada semua host"}. +{"Send announcement to all users","Kirim pengumuman untuk semua pengguna"}. +{"Send announcement to all users on all hosts","Kirim pengumuman untuk semua pengguna pada semua host"}. +{"September","September"}. +{"Server ~b","Layanan ~b"}. +{"Server:","Layanan:"}. +{"Set message of the day and send to online users","Mengatur pesan harian dan mengirimkan ke pengguna yang online"}. +{"Set message of the day on all hosts and send to online users","Mengatur pesan harian pada semua host dan kirimkan ke pengguna yang online"}. +{"Shared Roster Groups","Berbagi grup kontak"}. +{"Show Integral Table","Tampilkan Tabel Terpisah"}. +{"Show Ordinary Table","Tampilkan Tabel Normal"}. +{"Shut Down Service","Shut Down Layanan"}. +{"~s invites you to the room ~s","~s mengundang anda ke ruangan ~s"}. +{"Some Jabber clients can store your password in your computer. Use that feature only if you trust your computer is safe.","Beberapa klien Jabber dapat menyimpan password di komputer Anda. Gunakan fitur itu hanya jika Anda mempercayai komputer Anda aman."}. +{"Specify the access model","Tentukan model akses"}. +{"Specify the event message type","Tentukan jenis acara pesan"}. +{"Specify the publisher model","Tentukan model penerbitan"}. +{"~s's Offline Messages Queue","Antrian Pesan Offline ~s"}. +{"Start Modules at ","Mulai Modul pada"}. +{"Start Modules","Memulai Modul"}. +{"Start","Mulai"}. +{"Statistics of ~p","statistik dari ~p"}. +{"Statistics","Statistik"}. +{"Stop","Hentikan"}. +{"Stop Modules at ","Hentikan Modul pada"}. +{"Stop Modules","Hentikan Modul"}. +{"Stopped Nodes","Menghentikan node"}. +{"Storage Type","Jenis Penyimpanan"}. +{"Store binary backup:","Penyimpanan cadangan yang berpasangan:"}. +{"Store plain text backup:","Simpan cadangan teks biasa:"}. +{"Subject","Subyek"}. +{"Submit","Serahkan"}. +{"Submitted","Ulangi masukan"}. +{"Subscriber Address","Alamat Pertemanan"}. +{"Subscription","Berlangganan"}. +{"Sunday","Minggu"}. +{"That nickname is already in use by another occupant","Julukan itu sudah digunakan oleh penghuni lain"}. +{"That nickname is registered by another person","Julukan tersebut telah didaftarkan oleh orang lain"}. +{"The captcha is valid.","Captcha ini benar."}. +{"The CAPTCHA verification has failed","Verifikasi CAPTCHA telah gagal"}. +{"The collections with which a node is affiliated","Koleksi dengan yang berafiliasi dengan sebuah node"}. +{"the password is","kata sandi yaitu:"}. +{"The password is too weak","Kata sandi terlalu lemah"}. +{"The password of your Jabber account was successfully changed.","Kata sandi pada akun Jabber Anda telah berhasil diubah."}. +{"There was an error changing the password: ","Ada kesalahan dalam mengubah password:"}. +{"There was an error creating the account: ","Ada kesalahan saat membuat akun:"}. +{"There was an error deleting the account: ","Ada kesalahan saat menghapus akun:"}. +{"This is case insensitive: macbeth is the same that MacBeth and Macbeth.","Pada bagian ini huruf besar dan kecil tidak dibedakan: Misalnya macbeth adalah sama dengan MacBeth juga Macbeth."}. +{"This page allows to create a Jabber account in this Jabber server. Your JID (Jabber IDentifier) will be of the form: username@server. Please read carefully the instructions to fill correctly the fields.","Halaman ini memungkinkan untuk membuat akun Jabber di layanan Jabber ini. JID Anda (Jabber Pengenal) akan berbentuk: namapengguna@layanan. Harap baca dengan seksama petunjuk-petunjuk untuk mengisi kolom dengan benar."}. +{"This page allows to unregister a Jabber account in this Jabber server.","Pada bagian ini memungkinkan Anda untuk membatalkan pendaftaran akun Jabber pada layanan Jabber ini."}. +{"This participant is kicked from the room because he sent an error message","Peserta ini dikick dari ruangan karena dia mengirim pesan kesalahan"}. +{"This participant is kicked from the room because he sent an error message to another participant","Participant ini dikick dari ruangan karena ia mengirim pesan kesalahan ke participant lain"}. +{"This participant is kicked from the room because he sent an error presence","Participant ini dikick dari ruangan karena ia mengirim kehadiran kesalahan"}. +{"This room is not anonymous","Ruangan ini tidak dikenal"}. +{"Thursday","Kamis"}. +{"Time delay","Waktu tunda"}. +{"Time","Waktu"}. +{"To","Kepada"}. +{"To ~s","Kepada ~s"}. +{"Traffic rate limit is exceeded","Lalu lintas melebihi batas"}. +{"Transactions Aborted:","Transaksi yang dibatalkan:"}. +{"Transactions Committed:","Transaksi yang dilakukan:"}. +{"Transactions Logged:","Transaksi yang ditempuh:"}. +{"Transactions Restarted:","Transaksi yang dijalankan ulang:"}. +{"Tuesday","Selasa"}. +{"Unable to generate a captcha","Tidak dapat menghasilkan captcha"}. +{"Unable to generate a CAPTCHA","Tidak dapat menghasilkan CAPTCHA"}. +{"Unauthorized","Ditolak"}. +{"Unregister a Jabber account","Nonaktifkan akun jabber"}. +{"Unregister","Nonaktifkan"}. +{"Update ","Memperbarui "}. +{"Update","Memperbarui"}. +{"Update message of the day (don't send)","Rubah pesan harian (tidak dikirim)"}. +{"Update message of the day on all hosts (don't send)","Rubah pesan harian pada semua host (tidak dikirim)"}. +{"Update plan","Rencana Perubahan"}. +{"Update script","Perbarui naskah"}. +{"Uptime:","Sampai saat:"}. +{"Use of STARTTLS required","Penggunaan STARTTLS diperlukan"}. +{"User Management","Manajemen Pengguna"}. +{"Username:","Nama Pengguna:"}. +{"User ","Pengguna"}. +{"User","Pengguna"}. +{"Users are not allowed to register accounts so quickly","Pengguna tidak diperkenankan untuk mendaftar akun begitu cepat"}. +{"Users Last Activity","Aktifitas terakhir para pengguna"}. +{"Users","Pengguna"}. +{"Validate","Mengesahkan"}. +{"vCard User Search","vCard Pencarian Pengguna"}. +{"Virtual Hosts","Virtual Hosts"}. +{"Visitors are not allowed to change their nicknames in this room","Visitor tidak diperbolehkan untuk mengubah nama julukan di ruangan ini"}. +{"Visitors are not allowed to send messages to all occupants","Visitor tidak diperbolehkan untuk mengirim pesan ke semua penghuni"}. +{"Wednesday","Rabu"}. +{"When to send the last published item","Ketika untuk mengirim item terakhir yang dipublikasikan"}. +{"Whether to allow subscriptions","Apakah diperbolehkan untuk berlangganan"}. +{"You can later change your password using a Jabber client.","Anda dapat mengubah kata sandi anda dilain waktu dengan menggunakan klien Jabber."}. +{"You have been banned from this room","Anda telah diblokir dari ruangan ini"}. +{"You must fill in field \"Nickname\" in the form","Anda harus mengisi kolom \"Julukan\" dalam formulir"}. +{"You need a client that supports x:data and CAPTCHA to register","Anda memerlukan klien yang mendukung x:data dan CAPTCHA untuk mendaftar"}. +{"You need a client that supports x:data to register the nickname","Anda memerlukan klien yang mendukung x:data untuk mendaftar julukan"}. +{"You need an x:data capable client to configure mod_irc settings","Anda memerlukan x:data klien untuk mampu mengkonfigurasi pengaturan mod_irc"}. +{"You need an x:data capable client to configure room","Anda memerlukan x:data klien untuk dapat mengkonfigurasi ruangan"}. +{"You need an x:data capable client to search","Anda memerlukan x:data klien untuk melakukan pencarian"}. +{"Your active privacy list has denied the routing of this stanza.","Daftar privasi aktif Anda telah menolak routing ztanza ini"}. +{"Your contact offline message queue is full. The message has been discarded.","Kontak offline Anda pada antrian pesan sudah penuh. Pesan telah dibuang."}. +{"Your Jabber account was successfully created.","Jabber akun Anda telah sukses dibuat"}. +{"Your Jabber account was successfully deleted.","Jabber akun Anda berhasil dihapus."}. +{"Your messages to ~s are being blocked. To unblock them, visit ~s","Pesan Anda untuk ~s sedang diblokir. Untuk membuka blokir tersebut, kunjungi ~s"}. diff --git a/src/msgs/id.po b/src/msgs/id.po new file mode 100644 index 000000000..e71c74836 --- /dev/null +++ b/src/msgs/id.po @@ -0,0 +1,1799 @@ +# , 2010. +msgid "" +msgstr "" +"Project-Id-Version: 2.1.0-alpha\n" +"PO-Revision-Date: 2011-02-15 07:09+0800\n" +"Last-Translator: Irfan Mahfudz Guntur \n" +"Language-Team: SmartCommunity \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Language: Indonesian (Bahasa Indonesia)\n" + +#: ejabberd_c2s.erl:424 ejabberd_c2s.erl:727 +msgid "Use of STARTTLS required" +msgstr "Penggunaan STARTTLS diperlukan" + +#: ejabberd_c2s.erl:503 +msgid "No resource provided" +msgstr "Tidak ada sumber daya yang disediakan" + +#: ejabberd_c2s.erl:1147 +msgid "Replaced by new connection" +msgstr "Diganti dengan koneksi baru" + +#: ejabberd_c2s.erl:1832 +msgid "Your active privacy list has denied the routing of this stanza." +msgstr "Daftar privasi aktif Anda telah menolak routing ztanza ini" + +#: ejabberd_captcha.erl:94 ejabberd_captcha.erl:150 ejabberd_captcha.erl:176 +msgid "Enter the text you see" +msgstr "Masukkan teks yang Anda lihat" + +#: ejabberd_captcha.erl:99 +msgid "Your messages to ~s are being blocked. To unblock them, visit ~s" +msgstr "" +"Pesan Anda untuk ~s sedang diblokir. Untuk membuka blokir tersebut, kunjungi " +"~s" + +#: ejabberd_captcha.erl:132 +msgid "If you don't see the CAPTCHA image here, visit the web page." +msgstr "" +"Jika Anda tidak melihat gambar CAPTCHA disini, silahkan kunjungi halaman web." + +#: ejabberd_captcha.erl:144 +msgid "CAPTCHA web page" +msgstr "CAPTCHA laman web" + +#: ejabberd_captcha.erl:302 +msgid "The captcha is valid." +msgstr "Captcha ini benar." + +#: mod_adhoc.erl:95 mod_adhoc.erl:125 mod_adhoc.erl:143 mod_adhoc.erl:161 +msgid "Commands" +msgstr "Perintah" + +#: mod_adhoc.erl:149 mod_adhoc.erl:243 +msgid "Ping" +msgstr "Ping" + +#: mod_adhoc.erl:260 +msgid "Pong" +msgstr "Pong" + +#: mod_announce.erl:506 +msgid "Really delete message of the day?" +msgstr "Benar-benar ingin menghapus pesan harian?" + +#: mod_announce.erl:514 mod_configure.erl:1083 mod_configure.erl:1128 +msgid "Subject" +msgstr "Subyek" + +#: mod_announce.erl:519 mod_configure.erl:1088 mod_configure.erl:1133 +msgid "Message body" +msgstr "Isi Pesan" + +#: mod_announce.erl:599 +msgid "No body provided for announce message" +msgstr "Tidak ada isi pesan yang disediakan untuk mengirimkan pesan" + +#: mod_announce.erl:634 +msgid "Announcements" +msgstr "Pengumuman" + +#: mod_announce.erl:636 +msgid "Send announcement to all users" +msgstr "Kirim pengumuman untuk semua pengguna" + +#: mod_announce.erl:638 +msgid "Send announcement to all users on all hosts" +msgstr "Kirim pengumuman untuk semua pengguna pada semua host" + +#: mod_announce.erl:640 +msgid "Send announcement to all online users" +msgstr "Kirim pengumuman untuk semua pengguna yang online" + +#: mod_announce.erl:642 mod_configure.erl:1078 mod_configure.erl:1123 +msgid "Send announcement to all online users on all hosts" +msgstr "Kirim pengumuman untuk semua pengguna yang online pada semua host" + +#: mod_announce.erl:644 +msgid "Set message of the day and send to online users" +msgstr "Mengatur pesan harian dan mengirimkan ke pengguna yang online" + +#: mod_announce.erl:646 +msgid "Set message of the day on all hosts and send to online users" +msgstr "" +"Mengatur pesan harian pada semua host dan kirimkan ke pengguna yang online" + +#: mod_announce.erl:648 +msgid "Update message of the day (don't send)" +msgstr "Rubah pesan harian (tidak dikirim)" + +#: mod_announce.erl:650 +msgid "Update message of the day on all hosts (don't send)" +msgstr "Rubah pesan harian pada semua host (tidak dikirim)" + +#: mod_announce.erl:652 +msgid "Delete message of the day" +msgstr "Hapus pesan harian" + +#: mod_announce.erl:654 +msgid "Delete message of the day on all hosts" +msgstr "Hapus pesan harian pada semua host" + +#: mod_configure.erl:114 mod_configure.erl:274 mod_configure.erl:296 +#: mod_configure.erl:498 +msgid "Configuration" +msgstr "Pengaturan" + +#: mod_configure.erl:125 mod_configure.erl:576 web/ejabberd_web_admin.erl:1930 +msgid "Database" +msgstr "Database" + +#: mod_configure.erl:127 mod_configure.erl:595 +msgid "Start Modules" +msgstr "Memulai Modul" + +#: mod_configure.erl:129 mod_configure.erl:596 +msgid "Stop Modules" +msgstr "Hentikan Modul" + +#: mod_configure.erl:131 mod_configure.erl:604 web/ejabberd_web_admin.erl:1931 +msgid "Backup" +msgstr "Backup" + +#: mod_configure.erl:133 mod_configure.erl:605 +msgid "Restore" +msgstr "Mengembalikan" + +#: mod_configure.erl:135 mod_configure.erl:606 +msgid "Dump to Text File" +msgstr "Dump menjadi File Teks" + +#: mod_configure.erl:137 mod_configure.erl:615 +msgid "Import File" +msgstr "Impor File" + +#: mod_configure.erl:139 mod_configure.erl:616 +msgid "Import Directory" +msgstr "Impor Direktori" + +#: mod_configure.erl:141 mod_configure.erl:581 mod_configure.erl:1057 +msgid "Restart Service" +msgstr "Restart Layanan" + +#: mod_configure.erl:143 mod_configure.erl:582 mod_configure.erl:1102 +msgid "Shut Down Service" +msgstr "Shut Down Layanan" + +#: mod_configure.erl:145 mod_configure.erl:518 mod_configure.erl:1197 +#: web/ejabberd_web_admin.erl:1524 +msgid "Add User" +msgstr "Tambah Pengguna" + +#: mod_configure.erl:147 mod_configure.erl:519 mod_configure.erl:1219 +msgid "Delete User" +msgstr "Hapus Pengguna" + +#: mod_configure.erl:149 mod_configure.erl:520 mod_configure.erl:1231 +msgid "End User Session" +msgstr "Akhir Sesi Pengguna" + +#: mod_configure.erl:151 mod_configure.erl:521 mod_configure.erl:1243 +#: mod_configure.erl:1255 +msgid "Get User Password" +msgstr "Dapatkan User Password" + +#: mod_configure.erl:153 mod_configure.erl:522 +msgid "Change User Password" +msgstr "Ubah User Password" + +#: mod_configure.erl:155 mod_configure.erl:523 mod_configure.erl:1272 +msgid "Get User Last Login Time" +msgstr "Dapatkan Waktu Login Terakhir Pengguna " + +#: mod_configure.erl:157 mod_configure.erl:524 mod_configure.erl:1284 +msgid "Get User Statistics" +msgstr "Dapatkan Statistik Pengguna" + +#: mod_configure.erl:159 mod_configure.erl:525 +msgid "Get Number of Registered Users" +msgstr "Dapatkan Jumlah Pengguna Yang Terdaftar" + +#: mod_configure.erl:161 mod_configure.erl:526 +msgid "Get Number of Online Users" +msgstr "Dapatkan Jumlah User Yang Online" + +#: mod_configure.erl:163 mod_configure.erl:509 web/ejabberd_web_admin.erl:827 +#: web/ejabberd_web_admin.erl:868 +msgid "Access Control Lists" +msgstr "Akses Daftar Pengendalian" + +#: mod_configure.erl:165 mod_configure.erl:510 web/ejabberd_web_admin.erl:936 +#: web/ejabberd_web_admin.erl:972 +msgid "Access Rules" +msgstr "Aturan Akses" + +#: mod_configure.erl:297 mod_configure.erl:499 +msgid "User Management" +msgstr "Manajemen Pengguna" + +#: mod_configure.erl:500 web/ejabberd_web_admin.erl:1054 +#: web/ejabberd_web_admin.erl:1459 +msgid "Online Users" +msgstr "Pengguna Yang Online" + +#: mod_configure.erl:501 +msgid "All Users" +msgstr "Semua Pengguna" + +#: mod_configure.erl:502 +msgid "Outgoing s2s Connections" +msgstr "Koneksi Keluar s2s" + +#: mod_configure.erl:503 web/ejabberd_web_admin.erl:1901 +msgid "Running Nodes" +msgstr "Menjalankan Node" + +#: mod_configure.erl:504 web/ejabberd_web_admin.erl:1903 +msgid "Stopped Nodes" +msgstr "Menghentikan node" + +#: mod_configure.erl:577 mod_configure.erl:587 web/ejabberd_web_admin.erl:1947 +msgid "Modules" +msgstr "Modul" + +#: mod_configure.erl:578 +msgid "Backup Management" +msgstr "Manajemen Backup" + +#: mod_configure.erl:579 +msgid "Import Users From jabberd14 Spool Files" +msgstr "Impor Pengguna Dari jabberd14 Spool File" + +#: mod_configure.erl:699 +msgid "To ~s" +msgstr "Kepada ~s" + +#: mod_configure.erl:717 +msgid "From ~s" +msgstr "Dari ~s" + +#: mod_configure.erl:913 +msgid "Database Tables Configuration at " +msgstr "Database Tabel Konfigurasi pada" + +#: mod_configure.erl:918 +msgid "Choose storage type of tables" +msgstr "Pilih jenis penyimpanan tabel" + +#: mod_configure.erl:926 mod_configure.erl:928 +msgid "Disc only copy" +msgstr "Hanya salinan dari disc" + +#: mod_configure.erl:926 mod_configure.erl:928 +msgid "RAM and disc copy" +msgstr "RAM dan disc salinan" + +#: mod_configure.erl:926 mod_configure.erl:928 +msgid "RAM copy" +msgstr "Salinan RAM" + +#: mod_configure.erl:926 mod_configure.erl:928 +msgid "Remote copy" +msgstr "Salinan Remote" + +#: mod_configure.erl:950 +msgid "Stop Modules at " +msgstr "Hentikan Modul pada" + +#: mod_configure.erl:954 +msgid "Choose modules to stop" +msgstr "Pilih Modul untuk berhenti" + +#: mod_configure.erl:969 +msgid "Start Modules at " +msgstr "Mulai Modul pada" + +#: mod_configure.erl:973 +msgid "Enter list of {Module, [Options]}" +msgstr "Masukkan daftar {Modul, [Options]}" + +#: mod_configure.erl:974 +msgid "List of modules to start" +msgstr "Daftar modul untuk memulai" + +#: mod_configure.erl:983 +msgid "Backup to File at " +msgstr "Backup ke File pada" + +#: mod_configure.erl:987 mod_configure.erl:1001 +msgid "Enter path to backup file" +msgstr "Masukkan path untuk file cadangan" + +#: mod_configure.erl:988 mod_configure.erl:1002 mod_configure.erl:1016 +#: mod_configure.erl:1030 +msgid "Path to File" +msgstr "Jalur ke File" + +#: mod_configure.erl:997 +msgid "Restore Backup from File at " +msgstr "Kembalikan Backup dari File pada" + +#: mod_configure.erl:1011 +msgid "Dump Backup to Text File at " +msgstr "Dump Backup ke File Teks di" + +#: mod_configure.erl:1015 +msgid "Enter path to text file" +msgstr "Masukkan path ke file teks" + +#: mod_configure.erl:1025 +msgid "Import User from File at " +msgstr "Impor Pengguna dari File pada" + +#: mod_configure.erl:1029 +msgid "Enter path to jabberd14 spool file" +msgstr "Masukkan path ke file jabberd14 spool" + +#: mod_configure.erl:1039 +msgid "Import Users from Dir at " +msgstr "Impor Pengguna dari Dir di" + +#: mod_configure.erl:1043 +msgid "Enter path to jabberd14 spool dir" +msgstr "Masukkan path ke direktori spool jabberd14" + +#: mod_configure.erl:1044 +msgid "Path to Dir" +msgstr "Jalur ke Dir" + +#: mod_configure.erl:1060 mod_configure.erl:1105 +msgid "Time delay" +msgstr "Waktu tunda" + +#: mod_configure.erl:1143 +msgid "Access Control List Configuration" +msgstr "Konfigurasi Daftar Akses Pengendalian" + +#: mod_configure.erl:1147 +msgid "Access control lists" +msgstr "Daftar Pengendalian Akses" + +#: mod_configure.erl:1171 +msgid "Access Configuration" +msgstr "Akses Konfigurasi" + +#: mod_configure.erl:1175 +msgid "Access rules" +msgstr "Akses peraturan" + +#: mod_configure.erl:1200 mod_configure.erl:1222 mod_configure.erl:1234 +#: mod_configure.erl:1246 mod_configure.erl:1258 mod_configure.erl:1275 +#: mod_configure.erl:1287 mod_configure.erl:1652 mod_configure.erl:1702 +#: mod_configure.erl:1723 mod_roster.erl:948 mod_roster_odbc.erl:1048 +#: mod_vcard.erl:466 mod_vcard_ldap.erl:550 mod_vcard_odbc.erl:442 +msgid "Jabber ID" +msgstr "Jabber ID" + +#: mod_configure.erl:1205 mod_configure.erl:1263 mod_configure.erl:1653 +#: mod_configure.erl:1865 mod_muc/mod_muc_room.erl:2925 mod_register.erl:233 +#: web/ejabberd_web_admin.erl:1517 +msgid "Password" +msgstr "Sandi" + +#: mod_configure.erl:1210 +msgid "Password Verification" +msgstr "Verifikasi Sandi" + +#: mod_configure.erl:1301 +msgid "Number of registered users" +msgstr "Jumlah pengguna terdaftar" + +#: mod_configure.erl:1315 +msgid "Number of online users" +msgstr "Jumlah pengguna online" + +#: mod_configure.erl:1684 web/ejabberd_web_admin.erl:1585 +#: web/ejabberd_web_admin.erl:1737 +msgid "Never" +msgstr "Tidak Pernah" + +#: mod_configure.erl:1698 web/ejabberd_web_admin.erl:1598 +#: web/ejabberd_web_admin.erl:1750 +msgid "Online" +msgstr "Online" + +#: mod_configure.erl:1703 +msgid "Last login" +msgstr "Terakhir Login" + +#: mod_configure.erl:1724 +msgid "Roster size" +msgstr "Ukuran Daftar Kontak" + +#: mod_configure.erl:1725 +msgid "IP addresses" +msgstr "Alamat IP" + +#: mod_configure.erl:1726 +msgid "Resources" +msgstr "Sumber daya" + +#: mod_configure.erl:1852 +msgid "Administration of " +msgstr "Administrasi" + +#: mod_configure.erl:1855 +msgid "Action on user" +msgstr "Tindakan pada pengguna" + +#: mod_configure.erl:1859 +msgid "Edit Properties" +msgstr "Ganti Properti" + +#: mod_configure.erl:1862 web/ejabberd_web_admin.erl:1763 +msgid "Remove User" +msgstr "Hapus Pengguna" + +#: mod_irc/mod_irc.erl:201 mod_muc/mod_muc.erl:336 +msgid "Access denied by service policy" +msgstr "Akses ditolak oleh kebijakan layanan" + +#: mod_irc/mod_irc.erl:400 +msgid "IRC Transport" +msgstr "IRC Transport" + +#: mod_irc/mod_irc.erl:427 +msgid "ejabberd IRC module" +msgstr "ejabberd IRC modul" + +#: mod_irc/mod_irc.erl:558 +msgid "You need an x:data capable client to configure mod_irc settings" +msgstr "" +"Anda memerlukan x:data klien untuk mampu mengkonfigurasi pengaturan mod_irc" + +#: mod_irc/mod_irc.erl:565 +msgid "Registration in mod_irc for " +msgstr "Pendaftaran di mod_irc untuk" + +#: mod_irc/mod_irc.erl:570 +msgid "" +"Enter username, encodings, ports and passwords you wish to use for " +"connecting to IRC servers" +msgstr "" +"Masukkan username, pengkodean, port dan sandi yang ingin Anda gunakan untuk " +"menghubungkan ke layanan IRC" + +#: mod_irc/mod_irc.erl:575 +msgid "IRC Username" +msgstr "Nama Pengguna IRC" + +#: mod_irc/mod_irc.erl:585 +msgid "" +"If you want to specify different ports, passwords, encodings for IRC " +"servers, fill this list with values in format '{\"irc server\", \"encoding" +"\", port, \"password\"}'. By default this service use \"~s\" encoding, port " +"~p, empty password." +msgstr "" +"Jika Anda ingin menentukan port yang berbeda, sandi, pengkodean untuk " +"layanan IRC, isi daftar ini dengan nilai-nilai dalam format '{\"server irc " +"\", \"encoding \", port, \"sandi \"}'. Secara default ini menggunakan " +"layanan \"~s \" pengkodean, port ~p, kata sandi kosong." + +#: mod_irc/mod_irc.erl:597 +msgid "" +"Example: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef." +"net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]." +msgstr "" +"Contoh: [{\"irc.lucky.net\", \"koi8-r\", 6667, \"secret\"}, {\"vendetta.fef." +"net\", \"iso8859-1\", 7000}, {\"irc.sometestserver.net\", \"utf-8\"}]." + +#: mod_irc/mod_irc.erl:602 +msgid "Connections parameters" +msgstr "Parameter Koneksi" + +#: mod_irc/mod_irc.erl:727 +msgid "Join IRC channel" +msgstr "Gabung channel IRC" + +#: mod_irc/mod_irc.erl:731 +msgid "IRC channel (don't put the first #)" +msgstr "Channel IRC (tidak perlu menempatkan # sebelumnya)" + +#: mod_irc/mod_irc.erl:736 +msgid "IRC server" +msgstr "Layanan IRC" + +#: mod_irc/mod_irc.erl:769 mod_irc/mod_irc.erl:773 +msgid "Join the IRC channel here." +msgstr "Gabung ke channel IRC disini" + +#: mod_irc/mod_irc.erl:777 +msgid "Join the IRC channel in this Jabber ID: ~s" +msgstr "Gabung ke channel IRC dengan Jabber ID: ~s" + +#: mod_irc/mod_irc.erl:862 +msgid "IRC settings" +msgstr "Pengaturan IRC" + +#: mod_irc/mod_irc.erl:867 +msgid "" +"Enter username and encodings you wish to use for connecting to IRC servers. " +"Press 'Next' to get more fields to fill in. Press 'Complete' to save " +"settings." +msgstr "" +"Masukkan username dan pengkodean yang ingin Anda gunakan untuk menghubungkan " +"ke layanan IRC. Tekan 'Selanjutnya' untuk mendapatkan lagi formulir kemudian " +"Tekan 'Lengkap' untuk menyimpan pengaturan." + +#: mod_irc/mod_irc.erl:873 +msgid "IRC username" +msgstr "Nama Pengguna IRC" + +#: mod_irc/mod_irc.erl:922 +msgid "Password ~b" +msgstr "Kata Sandi ~b" + +#: mod_irc/mod_irc.erl:927 +msgid "Port ~b" +msgstr "Port ~b" + +#: mod_irc/mod_irc.erl:932 +msgid "Encoding for server ~b" +msgstr "Pengkodean untuk layanan ~b" + +#: mod_irc/mod_irc.erl:941 +msgid "Server ~b" +msgstr "Layanan ~b" + +#: mod_muc/mod_muc.erl:449 +msgid "Only service administrators are allowed to send service messages" +msgstr "" +"Layanan hanya diperuntukan kepada administrator yang diizinkan untuk " +"mengirim layanan pesan" + +#: mod_muc/mod_muc.erl:493 +msgid "Room creation is denied by service policy" +msgstr "Pembuatan Ruangan ditolak oleh kebijakan layanan" + +#: mod_muc/mod_muc.erl:500 +msgid "Conference room does not exist" +msgstr "Ruang Konferensi tidak ada" + +#: mod_muc/mod_muc.erl:581 +msgid "Chatrooms" +msgstr "Ruangan Chat" + +#: mod_muc/mod_muc.erl:711 +msgid "You need a client that supports x:data to register the nickname" +msgstr "Anda memerlukan klien yang mendukung x:data untuk mendaftar julukan" + +#: mod_muc/mod_muc.erl:717 +msgid "Nickname Registration at " +msgstr "Pendaftaran Julukan pada" + +#: mod_muc/mod_muc.erl:721 +msgid "Enter nickname you want to register" +msgstr "Masukkan nama julukan Anda jika ingin mendaftar" + +#: mod_muc/mod_muc.erl:722 mod_roster.erl:949 mod_roster_odbc.erl:1049 +#: mod_vcard.erl:358 mod_vcard.erl:471 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:447 +msgid "Nickname" +msgstr "Nama Julukan" + +#: mod_muc/mod_muc.erl:761 mod_muc/mod_muc_room.erl:933 +#: mod_muc/mod_muc_room.erl:1577 +msgid "That nickname is registered by another person" +msgstr "Julukan tersebut telah didaftarkan oleh orang lain" + +#: mod_muc/mod_muc.erl:787 +msgid "You must fill in field \"Nickname\" in the form" +msgstr "Anda harus mengisi kolom \"Julukan\" dalam formulir" + +#: mod_muc/mod_muc.erl:807 +msgid "ejabberd MUC module" +msgstr "ejabberd MUC Module" + +#: mod_muc/mod_muc_log.erl:374 mod_muc/mod_muc_log.erl:381 +msgid "Chatroom configuration modified" +msgstr "Konfigurasi ruang chat diubah" + +#: mod_muc/mod_muc_log.erl:384 +msgid "joins the room" +msgstr "bergabung ke ruangan" + +#: mod_muc/mod_muc_log.erl:387 mod_muc/mod_muc_log.erl:390 +msgid "leaves the room" +msgstr "meninggalkan ruangan" + +#: mod_muc/mod_muc_log.erl:393 mod_muc/mod_muc_log.erl:396 +msgid "has been banned" +msgstr "telah dibanned" + +#: mod_muc/mod_muc_log.erl:399 mod_muc/mod_muc_log.erl:402 +msgid "has been kicked" +msgstr "telah dikick" + +#: mod_muc/mod_muc_log.erl:405 +msgid "has been kicked because of an affiliation change" +msgstr "telah dikick karena perubahan afiliasi" + +#: mod_muc/mod_muc_log.erl:408 +msgid "has been kicked because the room has been changed to members-only" +msgstr "telah dikick karena ruangan telah diubah menjadi hanya untuk member" + +#: mod_muc/mod_muc_log.erl:411 +msgid "has been kicked because of a system shutdown" +msgstr "telah dikick karena sistem shutdown" + +#: mod_muc/mod_muc_log.erl:414 +msgid "is now known as" +msgstr "sekarang dikenal sebagai" + +#: mod_muc/mod_muc_log.erl:417 mod_muc/mod_muc_log.erl:689 +#: mod_muc/mod_muc_room.erl:2112 +msgid " has set the subject to: " +msgstr "telah menetapkan topik yaitu:" + +#: mod_muc/mod_muc_log.erl:452 +msgid "Chatroom is created" +msgstr "Ruang chat telah dibuat" + +#: mod_muc/mod_muc_log.erl:453 +msgid "Chatroom is destroyed" +msgstr "Ruang chat dilenyapkan" + +#: mod_muc/mod_muc_log.erl:454 +msgid "Chatroom is started" +msgstr "Ruang chat dimulai" + +#: mod_muc/mod_muc_log.erl:455 +msgid "Chatroom is stopped" +msgstr "Ruang chat dihentikan" + +#: mod_muc/mod_muc_log.erl:459 +msgid "Monday" +msgstr "Senin" + +#: mod_muc/mod_muc_log.erl:460 +msgid "Tuesday" +msgstr "Selasa" + +#: mod_muc/mod_muc_log.erl:461 +msgid "Wednesday" +msgstr "Rabu" + +#: mod_muc/mod_muc_log.erl:462 +msgid "Thursday" +msgstr "Kamis" + +#: mod_muc/mod_muc_log.erl:463 +msgid "Friday" +msgstr "Jumat" + +#: mod_muc/mod_muc_log.erl:464 +msgid "Saturday" +msgstr "Sabtu" + +#: mod_muc/mod_muc_log.erl:465 +msgid "Sunday" +msgstr "Minggu" + +#: mod_muc/mod_muc_log.erl:469 +msgid "January" +msgstr "Januari" + +#: mod_muc/mod_muc_log.erl:470 +msgid "February" +msgstr "Februari" + +#: mod_muc/mod_muc_log.erl:471 +msgid "March" +msgstr "Maret" + +#: mod_muc/mod_muc_log.erl:472 +msgid "April" +msgstr "April" + +#: mod_muc/mod_muc_log.erl:473 +msgid "May" +msgstr "Mei" + +#: mod_muc/mod_muc_log.erl:474 +msgid "June" +msgstr "Juni" + +#: mod_muc/mod_muc_log.erl:475 +msgid "July" +msgstr "Juli" + +#: mod_muc/mod_muc_log.erl:476 +msgid "August" +msgstr "Agustus" + +#: mod_muc/mod_muc_log.erl:477 +msgid "September" +msgstr "September" + +#: mod_muc/mod_muc_log.erl:478 +msgid "October" +msgstr "Oktober" + +#: mod_muc/mod_muc_log.erl:479 +msgid "November" +msgstr "Nopember" + +#: mod_muc/mod_muc_log.erl:480 +msgid "December" +msgstr "Desember" + +#: mod_muc/mod_muc_log.erl:751 +msgid "Room Configuration" +msgstr "Konfigurasi Ruangan" + +#: mod_muc/mod_muc_log.erl:760 +msgid "Room Occupants" +msgstr "Penghuni Ruangan" + +#: mod_muc/mod_muc_room.erl:170 +msgid "Traffic rate limit is exceeded" +msgstr "Lalu lintas melebihi batas" + +#: mod_muc/mod_muc_room.erl:242 +msgid "" +"This participant is kicked from the room because he sent an error message" +msgstr "Peserta ini dikick dari ruangan karena dia mengirim pesan kesalahan" + +#: mod_muc/mod_muc_room.erl:251 +msgid "It is not allowed to send private messages to the conference" +msgstr "Hal ini tidak diperbolehkan untuk mengirim pesan pribadi ke konferensi" + +#: mod_muc/mod_muc_room.erl:296 +msgid "Improper message type" +msgstr "Jenis pesan yang tidak benar" + +#: mod_muc/mod_muc_room.erl:406 +msgid "" +"This participant is kicked from the room because he sent an error message to " +"another participant" +msgstr "" +"Participant ini dikick dari ruangan karena ia mengirim pesan kesalahan ke " +"participant lain" + +#: mod_muc/mod_muc_room.erl:419 +msgid "It is not allowed to send private messages of type \"groupchat\"" +msgstr "" +"Hal ini tidak diperbolehkan untuk mengirim pesan pribadi jenis \"groupchat \"" + +#: mod_muc/mod_muc_room.erl:431 mod_muc/mod_muc_room.erl:486 +msgid "Recipient is not in the conference room" +msgstr "Penerima tidak berada di ruangan konferensi" + +#: mod_muc/mod_muc_room.erl:451 mod_muc/mod_muc_room.erl:829 +#: mod_muc/mod_muc_room.erl:3543 +msgid "Only occupants are allowed to send messages to the conference" +msgstr "Hanya penghuni yang diizinkan untuk mengirim pesan ke konferensi" + +#: mod_muc/mod_muc_room.erl:460 +msgid "It is not allowed to send private messages" +msgstr "Hal ini tidak diperbolehkan untuk mengirim pesan pribadi" + +#: mod_muc/mod_muc_room.erl:509 +msgid "Only occupants are allowed to send queries to the conference" +msgstr "Hanya penghuni diizinkan untuk mengirim permintaan ke konferensi" + +#: mod_muc/mod_muc_room.erl:521 +msgid "Queries to the conference members are not allowed in this room" +msgstr "" +"Permintaan untuk para anggota konferensi tidak diperbolehkan di ruangan ini" + +#: mod_muc/mod_muc_room.erl:805 +msgid "" +"Only moderators and participants are allowed to change the subject in this " +"room" +msgstr "" +"Hanya moderator dan peserta yang diizinkan untuk mengganti topik pembicaraan " +"di ruangan ini" + +#: mod_muc/mod_muc_room.erl:810 +msgid "Only moderators are allowed to change the subject in this room" +msgstr "" +"Hanya moderator yang diperbolehkan untuk mengubah topik dalam ruangan ini" + +#: mod_muc/mod_muc_room.erl:820 +msgid "Visitors are not allowed to send messages to all occupants" +msgstr "Visitor tidak diperbolehkan untuk mengirim pesan ke semua penghuni" + +#: mod_muc/mod_muc_room.erl:891 +msgid "" +"This participant is kicked from the room because he sent an error presence" +msgstr "" +"Participant ini dikick dari ruangan karena ia mengirim kehadiran kesalahan" + +#: mod_muc/mod_muc_room.erl:909 +msgid "Visitors are not allowed to change their nicknames in this room" +msgstr "Visitor tidak diperbolehkan untuk mengubah nama julukan di ruangan ini" + +#: mod_muc/mod_muc_room.erl:922 mod_muc/mod_muc_room.erl:1569 +msgid "That nickname is already in use by another occupant" +msgstr "Julukan itu sudah digunakan oleh penghuni lain" + +#: mod_muc/mod_muc_room.erl:1558 +msgid "You have been banned from this room" +msgstr "Anda telah diblokir dari ruangan ini" + +#: mod_muc/mod_muc_room.erl:1561 +msgid "Membership is required to enter this room" +msgstr "Hanya Member yang dapat masuk ruangan ini" + +#: mod_muc/mod_muc_room.erl:1597 +msgid "This room is not anonymous" +msgstr "Ruangan ini tidak dikenal" + +#: mod_muc/mod_muc_room.erl:1623 +msgid "A password is required to enter this room" +msgstr "Diperlukan kata sandi untuk masuk ruangan ini" + +#: mod_muc/mod_muc_room.erl:1645 +msgid "Unable to generate a captcha" +msgstr "Tidak dapat menghasilkan captcha" + +#: mod_muc/mod_muc_room.erl:1655 +msgid "Incorrect password" +msgstr "Kata sandi salah" + +#: mod_muc/mod_muc_room.erl:2167 +msgid "Administrator privileges required" +msgstr "Hak istimewa Administrator dibutuhkan" + +#: mod_muc/mod_muc_room.erl:2182 +msgid "Moderator privileges required" +msgstr "Hak istimewa moderator dibutuhkan" + +#: mod_muc/mod_muc_room.erl:2337 +msgid "Jabber ID ~s is invalid" +msgstr "Jabber ID ~s tidak valid" + +#: mod_muc/mod_muc_room.erl:2351 +msgid "Nickname ~s does not exist in the room" +msgstr "Nama Julukan ~s tidak berada di dalam ruangan" + +#: mod_muc/mod_muc_room.erl:2377 mod_muc/mod_muc_room.erl:2759 +msgid "Invalid affiliation: ~s" +msgstr "Afiliasi tidak valid: ~s" + +#: mod_muc/mod_muc_room.erl:2434 +msgid "Invalid role: ~s" +msgstr "Peran tidak valid: ~s" + +#: mod_muc/mod_muc_room.erl:2736 mod_muc/mod_muc_room.erl:2772 +msgid "Owner privileges required" +msgstr "Hak istimewa owner dibutuhkan" + +#: mod_muc/mod_muc_room.erl:2896 +msgid "Configuration of room ~s" +msgstr "Pengaturan ruangan ~s" + +#: mod_muc/mod_muc_room.erl:2901 +msgid "Room title" +msgstr "Nama Ruangan" + +#: mod_muc/mod_muc_room.erl:2904 mod_muc/mod_muc_room.erl:3322 +msgid "Room description" +msgstr "Keterangan ruangan" + +#: mod_muc/mod_muc_room.erl:2911 +msgid "Make room persistent" +msgstr "Buat ruangan menjadi permanent" + +#: mod_muc/mod_muc_room.erl:2916 +msgid "Make room public searchable" +msgstr "Buat ruangan dapat dicari" + +#: mod_muc/mod_muc_room.erl:2919 +msgid "Make participants list public" +msgstr "Buat daftar participant diketahui oleh public" + +#: mod_muc/mod_muc_room.erl:2922 +msgid "Make room password protected" +msgstr "Buat ruangan yang dilindungi dengan kata sandi" + +#: mod_muc/mod_muc_room.erl:2933 +msgid "Maximum Number of Occupants" +msgstr "Maksimum Jumlah Penghuni" + +#: mod_muc/mod_muc_room.erl:2940 +msgid "No limit" +msgstr "Tidak terbatas" + +#: mod_muc/mod_muc_room.erl:2951 +msgid "Present real Jabber IDs to" +msgstr "Tampilkan Jabber ID secara lengkap" + +#: mod_muc/mod_muc_room.erl:2959 +msgid "moderators only" +msgstr "Hanya moderator" + +#: mod_muc/mod_muc_room.erl:2961 +msgid "anyone" +msgstr "Siapapun" + +#: mod_muc/mod_muc_room.erl:2963 +msgid "Make room members-only" +msgstr "Buat ruangan hanya untuk member saja" + +#: mod_muc/mod_muc_room.erl:2966 +msgid "Make room moderated" +msgstr "Buat ruangan hanya untuk moderator saja" + +#: mod_muc/mod_muc_room.erl:2969 +msgid "Default users as participants" +msgstr "pengguna pertama kali masuk sebagai participant" + +#: mod_muc/mod_muc_room.erl:2972 +msgid "Allow users to change the subject" +msgstr "Perbolehkan pengguna untuk mengganti topik" + +#: mod_muc/mod_muc_room.erl:2975 +msgid "Allow users to send private messages" +msgstr "perbolehkan pengguna mengirimkan pesan ke pengguna lain secara pribadi" + +#: mod_muc/mod_muc_room.erl:2978 +msgid "Allow users to query other users" +msgstr "Perbolehkan pengguna untuk mengetahui pengguna lain" + +#: mod_muc/mod_muc_room.erl:2981 +msgid "Allow users to send invites" +msgstr "Perbolehkan pengguna mengirimkan undangan" + +#: mod_muc/mod_muc_room.erl:2984 +msgid "Allow visitors to send status text in presence updates" +msgstr "Izinkan pengunjung untuk mengirim teks status terbaru" + +#: mod_muc/mod_muc_room.erl:2987 +msgid "Allow visitors to change nickname" +msgstr "Perbolehkan visitor mengganti nama julukan" + +#: mod_muc/mod_muc_room.erl:2993 +msgid "Make room captcha protected" +msgstr "Buat ruangan dilindungi dengan chapta" + +#: mod_muc/mod_muc_room.erl:3002 +msgid "Enable logging" +msgstr "Aktifkan catatan" + +#: mod_muc/mod_muc_room.erl:3010 +msgid "You need an x:data capable client to configure room" +msgstr "Anda memerlukan x:data klien untuk dapat mengkonfigurasi ruangan" + +#: mod_muc/mod_muc_room.erl:3324 +msgid "Number of occupants" +msgstr "Jumlah Penghuni" + +#: mod_muc/mod_muc_room.erl:3380 +msgid "private, " +msgstr "pribadi, " + +#: mod_muc/mod_muc_room.erl:3463 +msgid "~s invites you to the room ~s" +msgstr "~s mengundang anda ke ruangan ~s" + +#: mod_muc/mod_muc_room.erl:3472 +msgid "the password is" +msgstr "kata sandi yaitu:" + +#: mod_offline.erl:510 mod_offline_odbc.erl:352 +msgid "" +"Your contact offline message queue is full. The message has been discarded." +msgstr "" +"Kontak offline Anda pada antrian pesan sudah penuh. Pesan telah dibuang." + +#: mod_offline.erl:560 mod_offline_odbc.erl:408 +msgid "~s's Offline Messages Queue" +msgstr "Antrian Pesan Offline ~s" + +#: mod_offline.erl:563 mod_offline_odbc.erl:411 mod_roster.erl:992 +#: mod_roster_odbc.erl:1092 mod_shared_roster.erl:810 +#: mod_shared_roster.erl:911 web/ejabberd_web_admin.erl:829 +#: web/ejabberd_web_admin.erl:870 web/ejabberd_web_admin.erl:938 +#: web/ejabberd_web_admin.erl:974 web/ejabberd_web_admin.erl:1015 +#: web/ejabberd_web_admin.erl:1505 web/ejabberd_web_admin.erl:1754 +#: web/ejabberd_web_admin.erl:1925 web/ejabberd_web_admin.erl:1957 +#: web/ejabberd_web_admin.erl:2026 web/ejabberd_web_admin.erl:2130 +#: web/ejabberd_web_admin.erl:2155 web/ejabberd_web_admin.erl:2243 +msgid "Submitted" +msgstr "Ulangi masukan" + +#: mod_offline.erl:571 +msgid "Time" +msgstr "Waktu" + +#: mod_offline.erl:572 +msgid "From" +msgstr "Dari" + +#: mod_offline.erl:573 +msgid "To" +msgstr "Kepada" + +#: mod_offline.erl:574 mod_offline_odbc.erl:419 +msgid "Packet" +msgstr "Paket" + +#: mod_offline.erl:587 mod_offline_odbc.erl:432 mod_shared_roster.erl:817 +#: web/ejabberd_web_admin.erl:878 web/ejabberd_web_admin.erl:982 +msgid "Delete Selected" +msgstr "Hapus Yang Terpilih" + +#: mod_offline.erl:645 mod_offline_odbc.erl:519 +msgid "Offline Messages:" +msgstr "Pesan Offline:" + +#: mod_offline.erl:645 mod_offline_odbc.erl:519 +msgid "Remove All Offline Messages" +msgstr "Hapus Semua Pesan Offline" + +#: mod_proxy65/mod_proxy65_service.erl:213 +msgid "ejabberd SOCKS5 Bytestreams module" +msgstr "modul ejabberd SOCKS5 Bytestreams" + +#: mod_pubsub/mod_pubsub.erl:1119 mod_pubsub/mod_pubsub_odbc.erl:928 +msgid "Publish-Subscribe" +msgstr "Setujui-Pertemanan" + +#: mod_pubsub/mod_pubsub.erl:1213 mod_pubsub/mod_pubsub_odbc.erl:1024 +msgid "ejabberd Publish-Subscribe module" +msgstr "Modul ejabberd Setujui-Pertemanan" + +#: mod_pubsub/mod_pubsub.erl:1496 mod_pubsub/mod_pubsub_odbc.erl:1311 +msgid "PubSub subscriber request" +msgstr "Permintaan pertemanan PubSub" + +#: mod_pubsub/mod_pubsub.erl:1498 mod_pubsub/mod_pubsub_odbc.erl:1313 +msgid "Choose whether to approve this entity's subscription." +msgstr "Pilih apakah akan menyetujui hubungan pertemanan ini." + +#: mod_pubsub/mod_pubsub.erl:1504 mod_pubsub/mod_pubsub_odbc.erl:1319 +msgid "Node ID" +msgstr "ID Node" + +#: mod_pubsub/mod_pubsub.erl:1509 mod_pubsub/mod_pubsub_odbc.erl:1324 +msgid "Subscriber Address" +msgstr "Alamat Pertemanan" + +#: mod_pubsub/mod_pubsub.erl:1515 mod_pubsub/mod_pubsub_odbc.erl:1330 +msgid "Allow this Jabber ID to subscribe to this pubsub node?" +msgstr "Izinkan ID Jabber ini untuk berlangganan pada node pubsub ini?" + +#: mod_pubsub/mod_pubsub.erl:3387 mod_pubsub/mod_pubsub_odbc.erl:3229 +msgid "Deliver payloads with event notifications" +msgstr "Memberikan muatan dengan pemberitahuan acara" + +#: mod_pubsub/mod_pubsub.erl:3388 mod_pubsub/mod_pubsub_odbc.erl:3230 +msgid "Deliver event notifications" +msgstr "Memberikan pemberitahuan acara" + +#: mod_pubsub/mod_pubsub.erl:3389 mod_pubsub/mod_pubsub_odbc.erl:3231 +msgid "Notify subscribers when the node configuration changes" +msgstr "Beritahu pelanggan ketika ada perubahan konfigurasi node" + +#: mod_pubsub/mod_pubsub.erl:3390 mod_pubsub/mod_pubsub_odbc.erl:3232 +msgid "Notify subscribers when the node is deleted" +msgstr "Beritahu pelanggan ketika node dihapus" + +#: mod_pubsub/mod_pubsub.erl:3391 mod_pubsub/mod_pubsub_odbc.erl:3233 +msgid "Notify subscribers when items are removed from the node" +msgstr "Beritahu pelanggan ketika item tersebut dikeluarkan dari node" + +#: mod_pubsub/mod_pubsub.erl:3392 mod_pubsub/mod_pubsub_odbc.erl:3234 +msgid "Persist items to storage" +msgstr "Pertahankan item ke penyimpanan" + +#: mod_pubsub/mod_pubsub.erl:3393 mod_pubsub/mod_pubsub_odbc.erl:3235 +msgid "A friendly name for the node" +msgstr "Nama yang dikenal untuk node" + +#: mod_pubsub/mod_pubsub.erl:3394 mod_pubsub/mod_pubsub_odbc.erl:3236 +msgid "Max # of items to persist" +msgstr "Max item untuk bertahan" + +#: mod_pubsub/mod_pubsub.erl:3395 mod_pubsub/mod_pubsub_odbc.erl:3237 +msgid "Whether to allow subscriptions" +msgstr "Apakah diperbolehkan untuk berlangganan" + +#: mod_pubsub/mod_pubsub.erl:3396 mod_pubsub/mod_pubsub_odbc.erl:3238 +msgid "Specify the access model" +msgstr "Tentukan model akses" + +#: mod_pubsub/mod_pubsub.erl:3399 mod_pubsub/mod_pubsub_odbc.erl:3241 +msgid "Roster groups allowed to subscribe" +msgstr "Kelompok kontak yang diizinkan untuk berlangganan" + +#: mod_pubsub/mod_pubsub.erl:3400 mod_pubsub/mod_pubsub_odbc.erl:3242 +msgid "Specify the publisher model" +msgstr "Tentukan model penerbitan" + +#: mod_pubsub/mod_pubsub.erl:3402 mod_pubsub/mod_pubsub_odbc.erl:3244 +msgid "Purge all items when the relevant publisher goes offline" +msgstr "Bersihkan semua item ketika penerbit yang relevan telah offline" + +#: mod_pubsub/mod_pubsub.erl:3403 mod_pubsub/mod_pubsub_odbc.erl:3245 +msgid "Specify the event message type" +msgstr "Tentukan jenis acara pesan" + +#: mod_pubsub/mod_pubsub.erl:3405 mod_pubsub/mod_pubsub_odbc.erl:3247 +msgid "Max payload size in bytes" +msgstr "Max kapasitas ukuran dalam bytes" + +#: mod_pubsub/mod_pubsub.erl:3406 mod_pubsub/mod_pubsub_odbc.erl:3248 +msgid "When to send the last published item" +msgstr "Ketika untuk mengirim item terakhir yang dipublikasikan" + +#: mod_pubsub/mod_pubsub.erl:3408 mod_pubsub/mod_pubsub_odbc.erl:3250 +msgid "Only deliver notifications to available users" +msgstr "Hanya mengirimkan pemberitahuan kepada pengguna yang tersedia" + +#: mod_pubsub/mod_pubsub.erl:3409 mod_pubsub/mod_pubsub_odbc.erl:3251 +msgid "The collections with which a node is affiliated" +msgstr "Koleksi dengan yang berafiliasi dengan sebuah node" + +#: mod_register.erl:191 +msgid "The CAPTCHA verification has failed" +msgstr "Verifikasi CAPTCHA telah gagal" + +#: mod_register.erl:218 +msgid "You need a client that supports x:data and CAPTCHA to register" +msgstr "" +"Anda memerlukan klien yang mendukung x:data dan CAPTCHA untuk mendaftar" + +#: mod_register.erl:224 mod_register.erl:258 +msgid "Choose a username and password to register with this server" +msgstr "Pilih nama pengguna dan kata sandi untuk mendaftar dengan layanan ini" + +#: mod_register.erl:228 mod_vcard.erl:358 mod_vcard_odbc.erl:336 +#: web/ejabberd_web_admin.erl:1512 web/ejabberd_web_admin.erl:1569 +msgid "User" +msgstr "Pengguna" + +#: mod_register.erl:244 +msgid "Unable to generate a CAPTCHA" +msgstr "Tidak dapat menghasilkan CAPTCHA" + +#: mod_register.erl:309 mod_register.erl:354 +msgid "The password is too weak" +msgstr "Kata sandi terlalu lemah" + +#: mod_register.erl:358 +msgid "Users are not allowed to register accounts so quickly" +msgstr "Pengguna tidak diperkenankan untuk mendaftar akun begitu cepat" + +#: mod_roster.erl:943 mod_roster_odbc.erl:1043 web/ejabberd_web_admin.erl:1695 +#: web/ejabberd_web_admin.erl:1880 web/ejabberd_web_admin.erl:1891 +#: web/ejabberd_web_admin.erl:2214 +msgid "None" +msgstr "Tak satupun" + +#: mod_roster.erl:950 mod_roster_odbc.erl:1050 +msgid "Subscription" +msgstr "Berlangganan" + +#: mod_roster.erl:951 mod_roster_odbc.erl:1051 +msgid "Pending" +msgstr "Tertunda" + +#: mod_roster.erl:952 mod_roster_odbc.erl:1052 +msgid "Groups" +msgstr "Grup" + +#: mod_roster.erl:979 mod_roster_odbc.erl:1079 +msgid "Validate" +msgstr "Mengesahkan" + +#: mod_roster.erl:987 mod_roster_odbc.erl:1087 +msgid "Remove" +msgstr "Menghapus" + +#: mod_roster.erl:990 mod_roster_odbc.erl:1090 +msgid "Roster of " +msgstr "Kontak dari" + +#: mod_roster.erl:993 mod_roster_odbc.erl:1093 mod_shared_roster.erl:811 +#: mod_shared_roster.erl:912 web/ejabberd_web_admin.erl:830 +#: web/ejabberd_web_admin.erl:871 web/ejabberd_web_admin.erl:939 +#: web/ejabberd_web_admin.erl:975 web/ejabberd_web_admin.erl:1016 +#: web/ejabberd_web_admin.erl:1506 web/ejabberd_web_admin.erl:1755 +#: web/ejabberd_web_admin.erl:1926 web/ejabberd_web_admin.erl:2131 +#: web/ejabberd_web_admin.erl:2156 +msgid "Bad format" +msgstr "Format yang buruk" + +#: mod_roster.erl:1000 mod_roster_odbc.erl:1100 +msgid "Add Jabber ID" +msgstr "Tambah Jabber ID" + +#: mod_roster.erl:1099 mod_roster_odbc.erl:1199 +msgid "Roster" +msgstr "Kontak" + +#: mod_shared_roster.erl:766 mod_shared_roster.erl:808 +#: mod_shared_roster.erl:908 +msgid "Shared Roster Groups" +msgstr "Berbagi grup kontak" + +#: mod_shared_roster.erl:804 web/ejabberd_web_admin.erl:1362 +#: web/ejabberd_web_admin.erl:2456 +msgid "Add New" +msgstr "Tambah Baru" + +#: mod_shared_roster.erl:879 +msgid "Name:" +msgstr "Nama:" + +#: mod_shared_roster.erl:884 +msgid "Description:" +msgstr "Keterangan:" + +#: mod_shared_roster.erl:892 +msgid "Members:" +msgstr "Anggota:" + +#: mod_shared_roster.erl:900 +msgid "Displayed Groups:" +msgstr "Tampilkan Grup:" + +#: mod_shared_roster.erl:909 +msgid "Group " +msgstr "Grup" + +#: mod_shared_roster.erl:918 web/ejabberd_web_admin.erl:836 +#: web/ejabberd_web_admin.erl:880 web/ejabberd_web_admin.erl:945 +#: web/ejabberd_web_admin.erl:1022 web/ejabberd_web_admin.erl:2011 +msgid "Submit" +msgstr "Serahkan" + +#: mod_vcard.erl:165 mod_vcard_ldap.erl:237 mod_vcard_odbc.erl:129 +msgid "Erlang Jabber Server" +msgstr "Layanan Erlang Jabber" + +#: mod_vcard.erl:358 mod_vcard.erl:472 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:448 +msgid "Birthday" +msgstr "Hari Lahir" + +#: mod_vcard.erl:358 mod_vcard.erl:474 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:450 +msgid "City" +msgstr "Kota" + +#: mod_vcard.erl:358 mod_vcard.erl:473 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:449 +msgid "Country" +msgstr "Negara" + +#: mod_vcard.erl:358 mod_vcard.erl:475 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:451 +msgid "Email" +msgstr "Email" + +#: mod_vcard.erl:358 mod_vcard.erl:470 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:446 +msgid "Family Name" +msgstr "Nama Keluarga (marga)" + +#: mod_vcard.erl:358 mod_vcard_odbc.erl:336 +msgid "" +"Fill in the form to search for any matching Jabber User (Add * to the end of " +"field to match substring)" +msgstr "" +"Isi formulir untuk pencarian pengguna Jabber yang cocok (Tambahkan * ke " +"mengakhiri pengisian untuk menyamakan kata)" + +#: mod_vcard.erl:358 mod_vcard.erl:467 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:443 +msgid "Full Name" +msgstr "Nama Lengkap" + +#: mod_vcard.erl:358 mod_vcard.erl:469 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:445 +msgid "Middle Name" +msgstr "Nama Tengah" + +#: mod_vcard.erl:358 mod_vcard.erl:468 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:444 web/ejabberd_web_admin.erl:2000 +msgid "Name" +msgstr "Nama" + +#: mod_vcard.erl:358 mod_vcard.erl:476 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:452 +msgid "Organization Name" +msgstr "Nama Organisasi" + +#: mod_vcard.erl:358 mod_vcard.erl:477 mod_vcard_odbc.erl:336 +#: mod_vcard_odbc.erl:453 +msgid "Organization Unit" +msgstr "Unit Organisasi" + +#: mod_vcard.erl:358 mod_vcard_ldap.erl:458 mod_vcard_odbc.erl:336 +msgid "Search users in " +msgstr "Pencarian pengguna dalam" + +#: mod_vcard.erl:358 mod_vcard_ldap.erl:458 mod_vcard_odbc.erl:336 +msgid "You need an x:data capable client to search" +msgstr "Anda memerlukan x:data klien untuk melakukan pencarian" + +#: mod_vcard.erl:383 mod_vcard_ldap.erl:483 mod_vcard_odbc.erl:361 +msgid "vCard User Search" +msgstr "vCard Pencarian Pengguna" + +#: mod_vcard.erl:439 mod_vcard_ldap.erl:537 mod_vcard_odbc.erl:415 +msgid "ejabberd vCard module" +msgstr "Modul ejabberd vCard" + +#: mod_vcard.erl:463 mod_vcard_ldap.erl:547 mod_vcard_odbc.erl:439 +msgid "Search Results for " +msgstr "Hasil Pencarian untuk" + +#: mod_vcard_ldap.erl:458 +msgid "Fill in fields to search for any matching Jabber User" +msgstr "Isi kolom untuk mencari pengguna Jabber yang sama" + +#: web/ejabberd_web_admin.erl:189 web/ejabberd_web_admin.erl:199 +#: web/ejabberd_web_admin.erl:215 web/ejabberd_web_admin.erl:225 +msgid "Unauthorized" +msgstr "Ditolak" + +#: web/ejabberd_web_admin.erl:282 web/ejabberd_web_admin.erl:299 +msgid "ejabberd Web Admin" +msgstr "Admin Web ejabberd" + +#: web/ejabberd_web_admin.erl:765 web/ejabberd_web_admin.erl:776 +msgid "Administration" +msgstr "Administrasi" + +#: web/ejabberd_web_admin.erl:874 web/ejabberd_web_admin.erl:978 +msgid "Raw" +msgstr "mentah" + +#: web/ejabberd_web_admin.erl:1013 +msgid "~s access rule configuration" +msgstr "~s aturan akses konfigurasi" + +#: web/ejabberd_web_admin.erl:1031 +msgid "Virtual Hosts" +msgstr "Virtual Hosts" + +#: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 +msgid "Users" +msgstr "Pengguna" + +#: web/ejabberd_web_admin.erl:1074 +msgid "Users Last Activity" +msgstr "Aktifitas terakhir para pengguna" + +#: web/ejabberd_web_admin.erl:1076 +msgid "Period: " +msgstr "Periode:" + +#: web/ejabberd_web_admin.erl:1086 +msgid "Last month" +msgstr "Akhir bulan" + +#: web/ejabberd_web_admin.erl:1087 +msgid "Last year" +msgstr "Akhir tahun" + +#: web/ejabberd_web_admin.erl:1088 +msgid "All activity" +msgstr "Semua aktifitas" + +#: web/ejabberd_web_admin.erl:1090 +msgid "Show Ordinary Table" +msgstr "Tampilkan Tabel Normal" + +#: web/ejabberd_web_admin.erl:1092 +msgid "Show Integral Table" +msgstr "Tampilkan Tabel Terpisah" + +#: web/ejabberd_web_admin.erl:1101 web/ejabberd_web_admin.erl:1933 +msgid "Statistics" +msgstr "Statistik" + +#: web/ejabberd_web_admin.erl:1113 +msgid "Not Found" +msgstr "Tidak Ditemukan" + +#: web/ejabberd_web_admin.erl:1130 +msgid "Node not found" +msgstr "Node tidak ditemukan" + +#: web/ejabberd_web_admin.erl:1457 +msgid "Host" +msgstr "Host" + +#: web/ejabberd_web_admin.erl:1458 +msgid "Registered Users" +msgstr "Pengguna Terdaftar" + +#: web/ejabberd_web_admin.erl:1570 +msgid "Offline Messages" +msgstr "Pesan Offline" + +#: web/ejabberd_web_admin.erl:1571 web/ejabberd_web_admin.erl:1761 +msgid "Last Activity" +msgstr "Aktifitas Terakhir" + +#: web/ejabberd_web_admin.erl:1653 web/ejabberd_web_admin.erl:1669 +msgid "Registered Users:" +msgstr "Pengguna Terdaftar:" + +#: web/ejabberd_web_admin.erl:1655 web/ejabberd_web_admin.erl:1671 +#: web/ejabberd_web_admin.erl:2187 +msgid "Online Users:" +msgstr "Pengguna Online:" + +#: web/ejabberd_web_admin.erl:1657 +msgid "Outgoing s2s Connections:" +msgstr "Koneksi s2s yang keluar:" + +#: web/ejabberd_web_admin.erl:1659 +msgid "Outgoing s2s Servers:" +msgstr "Layanan s2s yang keluar:" + +#: web/ejabberd_web_admin.erl:1728 web/mod_register_web.erl:174 +#: web/mod_register_web.erl:367 web/mod_register_web.erl:376 +#: web/mod_register_web.erl:406 +msgid "Change Password" +msgstr "Ubah Kata Sandi" + +#: web/ejabberd_web_admin.erl:1752 +msgid "User " +msgstr "Pengguna" + +#: web/ejabberd_web_admin.erl:1759 +msgid "Connected Resources:" +msgstr "Sumber Daya Terhubung:" + +#: web/ejabberd_web_admin.erl:1760 web/mod_register_web.erl:226 +#: web/mod_register_web.erl:514 +msgid "Password:" +msgstr "Kata Sandi:" + +#: web/ejabberd_web_admin.erl:1822 +msgid "No Data" +msgstr "Tidak Ada Data" + +#: web/ejabberd_web_admin.erl:1900 +msgid "Nodes" +msgstr "Node-node" + +#: web/ejabberd_web_admin.erl:1923 web/ejabberd_web_admin.erl:1945 +msgid "Node " +msgstr "Node" + +#: web/ejabberd_web_admin.erl:1932 +msgid "Listened Ports" +msgstr "Port Terdeteksi" + +#: web/ejabberd_web_admin.erl:1934 web/ejabberd_web_admin.erl:2255 +#: web/ejabberd_web_admin.erl:2443 +msgid "Update" +msgstr "Memperbarui" + +#: web/ejabberd_web_admin.erl:1937 web/ejabberd_web_admin.erl:2564 +msgid "Restart" +msgstr "Jalankan Ulang" + +#: web/ejabberd_web_admin.erl:1939 web/ejabberd_web_admin.erl:2566 +msgid "Stop" +msgstr "Hentikan" + +#: web/ejabberd_web_admin.erl:1953 +msgid "RPC Call Error" +msgstr "Panggilan Kesalahan RPC" + +#: web/ejabberd_web_admin.erl:1994 +msgid "Database Tables at " +msgstr "Tabel Database pada" + +#: web/ejabberd_web_admin.erl:2001 +msgid "Storage Type" +msgstr "Jenis Penyimpanan" + +#: web/ejabberd_web_admin.erl:2002 +msgid "Elements" +msgstr "Elemen-elemen" + +#: web/ejabberd_web_admin.erl:2003 +msgid "Memory" +msgstr "Memori" + +#: web/ejabberd_web_admin.erl:2027 web/ejabberd_web_admin.erl:2132 +msgid "Error" +msgstr "Kesalahan" + +#: web/ejabberd_web_admin.erl:2029 +msgid "Backup of " +msgstr "Cadangan dari" + +#: web/ejabberd_web_admin.erl:2031 +msgid "" +"Please note that these options will only backup the builtin Mnesia database. " +"If you are using the ODBC module, you also need to backup your SQL database " +"separately." +msgstr "" +"Harap dicatat bahwa pilihan ini hanya akan membuat cadangan builtin Mnesia " +"database. Jika Anda menggunakan modul ODBC, anda juga perlu untuk membuat " +"cadangan database SQL Anda secara terpisah." + +#: web/ejabberd_web_admin.erl:2036 +msgid "Store binary backup:" +msgstr "Penyimpanan cadangan yang berpasangan:" + +#: web/ejabberd_web_admin.erl:2040 web/ejabberd_web_admin.erl:2047 +#: web/ejabberd_web_admin.erl:2055 web/ejabberd_web_admin.erl:2062 +#: web/ejabberd_web_admin.erl:2069 web/ejabberd_web_admin.erl:2076 +#: web/ejabberd_web_admin.erl:2083 web/ejabberd_web_admin.erl:2091 +#: web/ejabberd_web_admin.erl:2098 web/ejabberd_web_admin.erl:2105 +msgid "OK" +msgstr "YA" + +#: web/ejabberd_web_admin.erl:2043 +msgid "Restore binary backup immediately:" +msgstr "Segera mengembalikan cadangan yang berpasangan:" + +#: web/ejabberd_web_admin.erl:2051 +msgid "" +"Restore binary backup after next ejabberd restart (requires less memory):" +msgstr "" +"Mengembalikan cadangan yang berpasanagn setelah ejabberd berikutnya " +"dijalankan ulang (memerlukan memori lebih sedikit):" + +#: web/ejabberd_web_admin.erl:2058 +msgid "Store plain text backup:" +msgstr "Simpan cadangan teks biasa:" + +#: web/ejabberd_web_admin.erl:2065 +msgid "Restore plain text backup immediately:" +msgstr "Segera mengembalikan cadangan teks biasa:" + +#: web/ejabberd_web_admin.erl:2072 +msgid "Import users data from a PIEFXIS file (XEP-0227):" +msgstr "impor data-data pengguna dari sebuah PIEFXIS (XEP-0227):" + +#: web/ejabberd_web_admin.erl:2079 +msgid "Export data of all users in the server to PIEFXIS files (XEP-0227):" +msgstr "" +"Ekspor data dari semua pengguna pada layanan ke berkas PIEFXIS (XEP-0227):" + +#: web/ejabberd_web_admin.erl:2086 +msgid "Export data of users in a host to PIEFXIS files (XEP-0227):" +msgstr "Ekspor data pengguna pada sebuah host ke berkas PIEFXIS (XEP-0227):" + +#: web/ejabberd_web_admin.erl:2094 +msgid "Import user data from jabberd14 spool file:" +msgstr "Impor data pengguna dari sekumpulan berkas jabberd14:" + +#: web/ejabberd_web_admin.erl:2101 +msgid "Import users data from jabberd14 spool directory:" +msgstr "Импорт пользовательских данных из буферной директории jabberd14:" + +#: web/ejabberd_web_admin.erl:2127 +msgid "Listened Ports at " +msgstr "Mendeteksi Port-port di" + +#: web/ejabberd_web_admin.erl:2152 +msgid "Modules at " +msgstr "modul-modul di" + +#: web/ejabberd_web_admin.erl:2178 +msgid "Statistics of ~p" +msgstr "statistik dari ~p" + +#: web/ejabberd_web_admin.erl:2181 +msgid "Uptime:" +msgstr "Sampai saat:" + +#: web/ejabberd_web_admin.erl:2184 +msgid "CPU Time:" +msgstr "Waktu CPU:" + +#: web/ejabberd_web_admin.erl:2190 +msgid "Transactions Committed:" +msgstr "Transaksi yang dilakukan:" + +#: web/ejabberd_web_admin.erl:2193 +msgid "Transactions Aborted:" +msgstr "Transaksi yang dibatalkan:" + +#: web/ejabberd_web_admin.erl:2196 +msgid "Transactions Restarted:" +msgstr "Transaksi yang dijalankan ulang:" + +#: web/ejabberd_web_admin.erl:2199 +msgid "Transactions Logged:" +msgstr "Transaksi yang ditempuh:" + +#: web/ejabberd_web_admin.erl:2241 +msgid "Update " +msgstr "Memperbarui " + +#: web/ejabberd_web_admin.erl:2249 +msgid "Update plan" +msgstr "Rencana Perubahan" + +#: web/ejabberd_web_admin.erl:2250 +msgid "Modified modules" +msgstr "Modifikasi modul-modul" + +#: web/ejabberd_web_admin.erl:2251 +msgid "Update script" +msgstr "Perbarui naskah" + +#: web/ejabberd_web_admin.erl:2252 +msgid "Low level update script" +msgstr "Perbaruan naskah tingkat rendah" + +#: web/ejabberd_web_admin.erl:2253 +msgid "Script check" +msgstr "Periksa naskah" + +#: web/ejabberd_web_admin.erl:2421 +msgid "Port" +msgstr "Port" + +#: web/ejabberd_web_admin.erl:2422 +msgid "IP" +msgstr "IP" + +#: web/ejabberd_web_admin.erl:2423 +msgid "Protocol" +msgstr "Protocol" + +#: web/ejabberd_web_admin.erl:2424 web/ejabberd_web_admin.erl:2551 +msgid "Module" +msgstr "Modul" + +#: web/ejabberd_web_admin.erl:2425 web/ejabberd_web_admin.erl:2552 +msgid "Options" +msgstr "Pilihan-pilihan" + +#: web/ejabberd_web_admin.erl:2445 +msgid "Delete" +msgstr "Hapus" + +#: web/ejabberd_web_admin.erl:2574 +msgid "Start" +msgstr "Mulai" + +#: web/mod_register_web.erl:103 +msgid "Your Jabber account was successfully created." +msgstr "Jabber akun Anda telah sukses dibuat" + +#: web/mod_register_web.erl:106 +msgid "There was an error creating the account: " +msgstr "Ada kesalahan saat membuat akun:" + +#: web/mod_register_web.erl:114 +msgid "Your Jabber account was successfully deleted." +msgstr "Jabber akun Anda berhasil dihapus." + +#: web/mod_register_web.erl:117 +msgid "There was an error deleting the account: " +msgstr "Ada kesalahan saat menghapus akun:" + +#: web/mod_register_web.erl:127 +msgid "The password of your Jabber account was successfully changed." +msgstr "Kata sandi pada akun Jabber Anda telah berhasil diubah." + +#: web/mod_register_web.erl:130 +msgid "There was an error changing the password: " +msgstr "Ada kesalahan dalam mengubah password:" + +#: web/mod_register_web.erl:162 web/mod_register_web.erl:171 +msgid "Jabber Account Registration" +msgstr "Pendaftaran Akun Jabber" + +#: web/mod_register_web.erl:173 web/mod_register_web.erl:191 +#: web/mod_register_web.erl:200 +msgid "Register a Jabber account" +msgstr "Daftarkan sebuah akun jabber" + +#: web/mod_register_web.erl:175 web/mod_register_web.erl:488 +#: web/mod_register_web.erl:497 +msgid "Unregister a Jabber account" +msgstr "Nonaktifkan akun jabber" + +#: web/mod_register_web.erl:202 +msgid "" +"This page allows to create a Jabber account in this Jabber server. Your JID " +"(Jabber IDentifier) will be of the form: username@server. Please read " +"carefully the instructions to fill correctly the fields." +msgstr "" +"Halaman ini memungkinkan untuk membuat akun Jabber di layanan Jabber ini. " +"JID Anda (Jabber Pengenal) akan berbentuk: namapengguna@layanan. Harap baca " +"dengan seksama petunjuk-petunjuk untuk mengisi kolom dengan benar." + +#: web/mod_register_web.erl:211 web/mod_register_web.erl:381 +#: web/mod_register_web.erl:504 +msgid "Username:" +msgstr "Nama Pengguna:" + +#: web/mod_register_web.erl:216 +msgid "This is case insensitive: macbeth is the same that MacBeth and Macbeth." +msgstr "" +"Pada bagian ini huruf besar dan kecil tidak dibedakan: Misalnya macbeth " +"adalah sama dengan MacBeth juga Macbeth." + +#: web/mod_register_web.erl:217 +msgid "Characters not allowed:" +msgstr "Karakter tidak diperbolehkan:" + +#: web/mod_register_web.erl:221 web/mod_register_web.erl:386 +#: web/mod_register_web.erl:509 +msgid "Server:" +msgstr "Layanan:" + +#: web/mod_register_web.erl:231 +msgid "" +"Don't tell your password to anybody, not even the administrators of the " +"Jabber server." +msgstr "" +"Jangan memberitahukan kata sandi Anda ke siapapun, bahkan para administrator " +"dari layanan Jabber." + +#: web/mod_register_web.erl:233 +msgid "You can later change your password using a Jabber client." +msgstr "" +"Anda dapat mengubah kata sandi anda dilain waktu dengan menggunakan klien " +"Jabber." + +#: web/mod_register_web.erl:234 +msgid "" +"Some Jabber clients can store your password in your computer. Use that " +"feature only if you trust your computer is safe." +msgstr "" +"Beberapa klien Jabber dapat menyimpan password di komputer Anda. Gunakan " +"fitur itu hanya jika Anda mempercayai komputer Anda aman." + +#: web/mod_register_web.erl:236 +msgid "" +"Memorize your password, or write it in a paper placed in a safe place. In " +"Jabber there isn't an automated way to recover your password if you forget " +"it." +msgstr "" +"Hafalkan kata sandi Anda, atau dicatat dan letakkan di tempat yang aman. " +"Didalam Jabber tidak ada cara otomatis untuk mendapatkan kembali password " +"Anda jika Anda lupa." + +#: web/mod_register_web.erl:241 web/mod_register_web.erl:401 +msgid "Password Verification:" +msgstr "Verifikasi Kata Sandi:" + +#: web/mod_register_web.erl:249 +msgid "Register" +msgstr "Mendaftar" + +#: web/mod_register_web.erl:391 +msgid "Old Password:" +msgstr "Password Lama:" + +#: web/mod_register_web.erl:396 +msgid "New Password:" +msgstr "Password Baru:" + +#: web/mod_register_web.erl:499 +msgid "This page allows to unregister a Jabber account in this Jabber server." +msgstr "" +"Pada bagian ini memungkinkan Anda untuk membatalkan pendaftaran akun Jabber " +"pada layanan Jabber ini." + +#: web/mod_register_web.erl:519 +msgid "Unregister" +msgstr "Nonaktifkan" + +#, fuzzy +#~ msgid "Captcha test failed" +#~ msgstr "Проверка капчи прошла успешно." diff --git a/src/msgs/it.msg b/src/msgs/it.msg index 329450a54..712745abf 100644 --- a/src/msgs/it.msg +++ b/src/msgs/it.msg @@ -77,7 +77,6 @@ {"ejabberd Publish-Subscribe module","Modulo Pubblicazione/Iscrizione (PubSub) per ejabberd"}. {"ejabberd SOCKS5 Bytestreams module","Modulo SOCKS5 Bytestreams per ejabberd"}. {"ejabberd vCard module","Modulo vCard per ejabberd"}. -{"ejabberd virtual hosts","Host virtuali di ejabberd"}. {"ejabberd Web Admin","Amministrazione web ejabberd"}. {"Elements","Elementi"}. {"Email","E-mail"}. @@ -362,6 +361,7 @@ {"User","Utente"}. {"Validate","Validare"}. {"vCard User Search","Ricerca di utenti per vCard"}. +{"Virtual Hosts","Host Virtuali"}. {"Visitors are not allowed to change their nicknames in this room","Non è consentito ai visitatori cambiare il nickname in questa stanza"}. {"Visitors are not allowed to send messages to all occupants","Non è consentito ai visitatori l'invio di messaggi a tutti i presenti"}. {"Wednesday","Mercoledì"}. diff --git a/src/msgs/it.po b/src/msgs/it.po index 3dc6ddd24..b049385d6 100644 --- a/src/msgs/it.po +++ b/src/msgs/it.po @@ -1372,8 +1372,8 @@ msgid "~s access rule configuration" msgstr "Configurazione delle regole di accesso per ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Host virtuali di ejabberd" +msgid "Virtual Hosts" +msgstr "Host Virtuali" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/ja.msg b/src/msgs/ja.msg index c5b5af3bd..8da952c61 100644 --- a/src/msgs/ja.msg +++ b/src/msgs/ja.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe module"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams module"}. {"ejabberd vCard module","ejabberd vCard module"}. -{"ejabberd virtual hosts","ejabberd ヴァーチャルホスト"}. {"ejabberd Web Admin","ejabberd Web 管理"}. {"Elements","要素"}. {"Email","メールアドレス"}. @@ -388,6 +387,7 @@ {"Users Last Activity","ユーザーの活動履歴"}. {"Validate","検証"}. {"vCard User Search","vCard ユーザー検索"}. +{"Virtual Hosts","ヴァーチャルホスト"}. {"Visitors are not allowed to change their nicknames in this room","ビジターはこのチャットルームでニックネームを変更することは許可されていません"}. {"Visitors are not allowed to send messages to all occupants","ビジターが移住者ににメッセージを送ることは許可されていません"}. {"Wednesday","水曜日"}. diff --git a/src/msgs/ja.po b/src/msgs/ja.po index 2efb82439..2c11e4347 100644 --- a/src/msgs/ja.po +++ b/src/msgs/ja.po @@ -1,15 +1,17 @@ msgid "" msgstr "" -"Project-Id-Version: 2.1.0-alpha\n" -"Last-Translator: Tsukasa Hamano\n" +"Project-Id-Version: ejabberd 2.1.x\n" +"PO-Revision-Date: 2010-12-24 18:46+0900\n" +"Last-Translator: Mako N \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Language: Japanese (日本語)\n" +"X-Additional-Translator: Tsukasa Hamano \n" #: ejabberd_c2s.erl:424 ejabberd_c2s.erl:727 msgid "Use of STARTTLS required" -msgstr "STARTTLS を使用" +msgstr "STARTTLS の使用が必要です" #: ejabberd_c2s.erl:503 msgid "No resource provided" @@ -34,15 +36,15 @@ msgstr "" #: ejabberd_captcha.erl:132 msgid "If you don't see the CAPTCHA image here, visit the web page." -msgstr "ここにキャプチャ画像が表示されない場合、WEBページを参照してください。" +msgstr "ここに CAPTCHA 画像が表示されない場合、web ページを参照してください。" #: ejabberd_captcha.erl:144 msgid "CAPTCHA web page" -msgstr "キャプチャWEBページ" +msgstr "CAPTCHA web ページ" #: ejabberd_captcha.erl:302 msgid "The captcha is valid." -msgstr "キャプチャは有効です。" +msgstr "CAPTCHA は有効です。" #: mod_adhoc.erl:95 mod_adhoc.erl:125 mod_adhoc.erl:143 mod_adhoc.erl:161 msgid "Commands" @@ -58,7 +60,7 @@ msgstr "Pong" #: mod_announce.erl:506 msgid "Really delete message of the day?" -msgstr "本当にお知らせメッセージを削除しますか?" +msgstr "本当にお知らせメッセージを削除しますか ?" #: mod_announce.erl:514 mod_configure.erl:1083 mod_configure.erl:1128 msgid "Subject" @@ -78,43 +80,43 @@ msgstr "アナウンス" #: mod_announce.erl:636 msgid "Send announcement to all users" -msgstr "アナウンスを全てのユーザーに送信" +msgstr "すべてのユーザーにアナウンスを送信" #: mod_announce.erl:638 msgid "Send announcement to all users on all hosts" -msgstr "アナウンスを全ホストのユーザーに送信" +msgstr "全ホストのユーザーにアナウンスを送信" #: mod_announce.erl:640 msgid "Send announcement to all online users" -msgstr "アナウンスを全てのオンラインユーザーに送信" +msgstr "すべてのオンラインユーザーにアナウンスを送信" #: mod_announce.erl:642 mod_configure.erl:1078 mod_configure.erl:1123 msgid "Send announcement to all online users on all hosts" -msgstr "全ホストのオンラインユーザへアナウンスを送信" +msgstr "全ホストのオンラインユーザーにアナウンスを送信" #: mod_announce.erl:644 msgid "Set message of the day and send to online users" -msgstr "お知らせメッセージを設定し、オンラインユーザーに送信する" +msgstr "お知らせメッセージを設定し、オンラインユーザーに送信" #: mod_announce.erl:646 msgid "Set message of the day on all hosts and send to online users" -msgstr "全ホストのお知らせメッセージを設定し、オンラインユーザーに送信する" +msgstr "全ホストのお知らせメッセージを設定し、オンラインユーザーに送信" #: mod_announce.erl:648 msgid "Update message of the day (don't send)" -msgstr "お知らせメッセージを更新する(送信しない)" +msgstr "お知らせメッセージを更新 (送信しない)" #: mod_announce.erl:650 msgid "Update message of the day on all hosts (don't send)" -msgstr "全ホストのお知らせメッセージを更新する" +msgstr "全ホストのお知らせメッセージを更新 (送信しない)" #: mod_announce.erl:652 msgid "Delete message of the day" -msgstr "お知らせメッセージを削除する" +msgstr "お知らせメッセージを削除" #: mod_announce.erl:654 msgid "Delete message of the day on all hosts" -msgstr "全ホストのお知らせメッセージを削除する" +msgstr "全ホストのお知らせメッセージを削除" #: mod_configure.erl:114 mod_configure.erl:274 mod_configure.erl:296 #: mod_configure.erl:498 @@ -127,11 +129,11 @@ msgstr "データーベース" #: mod_configure.erl:127 mod_configure.erl:595 msgid "Start Modules" -msgstr "モジュールの起動" +msgstr "モジュールを起動" #: mod_configure.erl:129 mod_configure.erl:596 msgid "Stop Modules" -msgstr "モジュールの停止" +msgstr "モジュールを停止" #: mod_configure.erl:131 mod_configure.erl:604 web/ejabberd_web_admin.erl:1931 msgid "Backup" @@ -147,7 +149,7 @@ msgstr "テキストファイルに出力" #: mod_configure.erl:137 mod_configure.erl:615 msgid "Import File" -msgstr "ファイルかインポート" +msgstr "ファイルからインポート" #: mod_configure.erl:139 mod_configure.erl:616 msgid "Import Directory" @@ -168,7 +170,7 @@ msgstr "ユーザーを追加" #: mod_configure.erl:147 mod_configure.erl:519 mod_configure.erl:1219 msgid "Delete User" -msgstr "ユーザを削除" +msgstr "ユーザーを削除" #: mod_configure.erl:149 mod_configure.erl:520 mod_configure.erl:1231 msgid "End User Session" @@ -185,11 +187,11 @@ msgstr "パスワードを変更" #: mod_configure.erl:155 mod_configure.erl:523 mod_configure.erl:1272 msgid "Get User Last Login Time" -msgstr "最終ログイン時間の取得" +msgstr "最終ログイン時間を取得" #: mod_configure.erl:157 mod_configure.erl:524 mod_configure.erl:1284 msgid "Get User Statistics" -msgstr "ユーザー統計の取得" +msgstr "ユーザー統計を取得" #: mod_configure.erl:159 mod_configure.erl:525 msgid "Get Number of Registered Users" @@ -197,7 +199,7 @@ msgstr "登録ユーザー数を取得" #: mod_configure.erl:161 mod_configure.erl:526 msgid "Get Number of Online Users" -msgstr "登録ユーザーを取得" +msgstr "オンラインユーザー数を取得" #: mod_configure.erl:163 mod_configure.erl:509 web/ejabberd_web_admin.erl:827 #: web/ejabberd_web_admin.erl:868 @@ -260,7 +262,7 @@ msgstr "データーベーステーブル設定 " #: mod_configure.erl:918 msgid "Choose storage type of tables" -msgstr "テーブルのストレージタイプの選択" +msgstr "テーブルのストレージタイプを選択" #: mod_configure.erl:926 mod_configure.erl:928 msgid "Disc only copy" @@ -280,19 +282,19 @@ msgstr "リモートコピー" #: mod_configure.erl:950 msgid "Stop Modules at " -msgstr "モジュールの停止 " +msgstr "モジュールを停止: " #: mod_configure.erl:954 msgid "Choose modules to stop" -msgstr "停止するモジュールの選択" +msgstr "停止するモジュールを選択" #: mod_configure.erl:969 msgid "Start Modules at " -msgstr "モジュールの開始" +msgstr "モジュールを開始: " #: mod_configure.erl:973 msgid "Enter list of {Module, [Options]}" -msgstr "{モジュール, [オプション]}のリストを入力して下さい" +msgstr "{モジュール, [オプション]}のリストを入力してください" #: mod_configure.erl:974 msgid "List of modules to start" @@ -300,44 +302,44 @@ msgstr "起動モジュールの一覧" #: mod_configure.erl:983 msgid "Backup to File at " -msgstr "ファイルにバックアップ" +msgstr "ファイルにバックアップ: " #: mod_configure.erl:987 mod_configure.erl:1001 msgid "Enter path to backup file" -msgstr "バックアップファイルのパスを入力して下さい" +msgstr "バックアップファイルのパスを入力してください" #: mod_configure.erl:988 mod_configure.erl:1002 mod_configure.erl:1016 #: mod_configure.erl:1030 msgid "Path to File" -msgstr "ファイルパス" +msgstr "ファイルのパス" #: mod_configure.erl:997 msgid "Restore Backup from File at " -msgstr "ファイルからバックアップをリストア" +msgstr "ファイルからバックアップをリストア: " #: mod_configure.erl:1011 msgid "Dump Backup to Text File at " -msgstr "テキストファイルにバックアップ" +msgstr "テキストファイルにバックアップ: " #: mod_configure.erl:1015 msgid "Enter path to text file" -msgstr "テキストファイルのパスを入力して下さい" +msgstr "テキストファイルのパスを入力してください" #: mod_configure.erl:1025 msgid "Import User from File at " -msgstr "ファイルからユーザーをインポート" +msgstr "ファイルからユーザーをインポート: " #: mod_configure.erl:1029 msgid "Enter path to jabberd14 spool file" -msgstr "jabberd14 spool ファイルのパスを入力して下さい" +msgstr "jabberd14 spool ファイルのパスを入力してください" #: mod_configure.erl:1039 msgid "Import Users from Dir at " -msgstr "ディレクトリからユーザーをインポート" +msgstr "ディレクトリからユーザーをインポート: " #: mod_configure.erl:1043 msgid "Enter path to jabberd14 spool dir" -msgstr "jabberd14 spool ディレクトリのパスを入力して下さい" +msgstr "jabberd14 spool ディレクトリのディレクトリを入力してください" #: mod_configure.erl:1044 msgid "Path to Dir" @@ -379,7 +381,7 @@ msgstr "パスワード" #: mod_configure.erl:1210 msgid "Password Verification" -msgstr "パスワード(確認)" +msgstr "パスワード (確認)" #: mod_configure.erl:1301 msgid "Number of registered users" @@ -392,7 +394,7 @@ msgstr "オンラインユーザー数" #: mod_configure.erl:1684 web/ejabberd_web_admin.erl:1585 #: web/ejabberd_web_admin.erl:1737 msgid "Never" -msgstr "無し" +msgstr "なし" #: mod_configure.erl:1698 web/ejabberd_web_admin.erl:1598 #: web/ejabberd_web_admin.erl:1750 @@ -425,11 +427,11 @@ msgstr "ユーザー操作" #: mod_configure.erl:1859 msgid "Edit Properties" -msgstr "プロパティの編集" +msgstr "プロパティを編集" #: mod_configure.erl:1862 web/ejabberd_web_admin.erl:1763 msgid "Remove User" -msgstr "ユーザーの削除" +msgstr "ユーザーを削除" #: mod_irc/mod_irc.erl:201 mod_muc/mod_muc.erl:336 msgid "Access denied by service policy" @@ -456,12 +458,12 @@ msgid "" "Enter username, encodings, ports and passwords you wish to use for " "connecting to IRC servers" msgstr "" -"IRCサーバーに接続先する為に、使用するユーザー名、文字エンコーディング、ポー" -"ト、パスワードを入力して下さい" +"IRC サーバーに接続先するために使用するユーザー名、文字エンコーディング、ポー" +"ト、パスワードを入力してください" #: mod_irc/mod_irc.erl:575 msgid "IRC Username" -msgstr "IRCユーザー名" +msgstr "IRC ユーザー名" #: mod_irc/mod_irc.erl:585 msgid "" @@ -471,8 +473,8 @@ msgid "" "~p, empty password." msgstr "" "別のポートやパスワード、文字エンコーディングを使用したい場合、'{\"irc server" -"\", \"encoding\", port, \"password\"}' という形式のリストを入力して下さい。デ" -"フォルトでエンコーディングは \"~s\" を使用し、ポートは ~p、パスワードは空に" +"\", \"encoding\", port, \"password\"}' という形式のリストを入力してください。" +"デフォルトでエンコーディングは \"~s\" を使用し、ポートは ~p、パスワードは空に" "なっています。" #: mod_irc/mod_irc.erl:597 @@ -489,27 +491,27 @@ msgstr "接続パラメータ" #: mod_irc/mod_irc.erl:727 msgid "Join IRC channel" -msgstr "IRCチャンネルに参加" +msgstr "IRC チャンネルに参加" #: mod_irc/mod_irc.erl:731 msgid "IRC channel (don't put the first #)" -msgstr "IRCチャンネル(先頭に#は不要)" +msgstr "IRC チャンネル (先頭に#は不要)" #: mod_irc/mod_irc.erl:736 msgid "IRC server" -msgstr "IRCサーバー" +msgstr "IRC サーバー" #: mod_irc/mod_irc.erl:769 mod_irc/mod_irc.erl:773 msgid "Join the IRC channel here." -msgstr "このIRCチャンネルに参加" +msgstr "この IRC チャンネルに参加します。" #: mod_irc/mod_irc.erl:777 msgid "Join the IRC channel in this Jabber ID: ~s" -msgstr "Jabber ID: ~s でIRCチャンネルに参加" +msgstr "Jabber ID: ~s でこの IRC チャンネルに参加" #: mod_irc/mod_irc.erl:862 msgid "IRC settings" -msgstr "IRC設定" +msgstr "IRC 設定" #: mod_irc/mod_irc.erl:867 msgid "" @@ -517,13 +519,13 @@ msgid "" "Press 'Next' to get more fields to fill in. Press 'Complete' to save " "settings." msgstr "" -"IRCサーバーに接続先する為のユーザー名と文字エンコーディングを入力して下さ" -"い。'Next' を押して次の項目に進みます。'Complete' を押すと設定が保存されま" +"IRC サーバーに接続先するためのユーザー名と文字エンコーディングを入力してくだ" +"さい。'Next' を押して次の項目に進みます。'Complete' を押すと設定が保存されま" "す。" #: mod_irc/mod_irc.erl:873 msgid "IRC username" -msgstr "IRCユーザー名" +msgstr "IRC ユーザー名" #: mod_irc/mod_irc.erl:922 msgid "Password ~b" @@ -543,7 +545,7 @@ msgstr "サーバー ~b" #: mod_muc/mod_muc.erl:449 msgid "Only service administrators are allowed to send service messages" -msgstr "カービス管理者のみがサービスメッセージを送信出来ます" +msgstr "サービス管理者のみがサービスメッセージを送信できます" #: mod_muc/mod_muc.erl:493 msgid "Room creation is denied by service policy" @@ -568,7 +570,7 @@ msgstr "ニックネーム登録: " #: mod_muc/mod_muc.erl:721 msgid "Enter nickname you want to register" -msgstr "登録するニックネームを入力して下さい" +msgstr "登録するニックネームを入力してください" #: mod_muc/mod_muc.erl:722 mod_roster.erl:949 mod_roster_odbc.erl:1049 #: mod_vcard.erl:358 mod_vcard.erl:471 mod_vcard_odbc.erl:336 @@ -579,7 +581,7 @@ msgstr "ニックネーム" #: mod_muc/mod_muc.erl:761 mod_muc/mod_muc_room.erl:936 #: mod_muc/mod_muc_room.erl:1580 msgid "That nickname is registered by another person" -msgstr "ニックネームは他の人によって登録されています" +msgstr "ニックネームはほかの人によって登録されています" #: mod_muc/mod_muc.erl:787 msgid "You must fill in field \"Nickname\" in the form" @@ -591,15 +593,15 @@ msgstr "ejabberd MUC module" #: mod_muc/mod_muc_log.erl:374 mod_muc/mod_muc_log.erl:381 msgid "Chatroom configuration modified" -msgstr "チャットルームの設定を変更しました" +msgstr "チャットルームの設定が変更されました" #: mod_muc/mod_muc_log.erl:384 msgid "joins the room" -msgstr "チャットルームに参加" +msgstr "がチャットルームに参加しました" #: mod_muc/mod_muc_log.erl:387 mod_muc/mod_muc_log.erl:390 msgid "leaves the room" -msgstr "チャットルームから退出" +msgstr "がチャットルームから退出しました" #: mod_muc/mod_muc_log.erl:393 mod_muc/mod_muc_log.erl:396 msgid "has been banned" @@ -611,11 +613,11 @@ msgstr "はキックされました" #: mod_muc/mod_muc_log.erl:405 msgid "has been kicked because of an affiliation change" -msgstr "は提携が変更されたためキックされました" +msgstr "は分掌が変更されたためキックされました" #: mod_muc/mod_muc_log.erl:408 msgid "has been kicked because the room has been changed to members-only" -msgstr "はチャットルームのメンバー制限によりキックされました" +msgstr "はチャットルームのメンバーのみに変更されたためキックされました" #: mod_muc/mod_muc_log.erl:411 msgid "has been kicked because of a system shutdown" @@ -632,19 +634,19 @@ msgstr " は件名を設定しました: " #: mod_muc/mod_muc_log.erl:452 msgid "Chatroom is created" -msgstr "チャットルームは作られました。" +msgstr "チャットルームを作りました" #: mod_muc/mod_muc_log.erl:453 msgid "Chatroom is destroyed" -msgstr "チャットルームは削除されました。" +msgstr "チャットルームを削除しました" #: mod_muc/mod_muc_log.erl:454 msgid "Chatroom is started" -msgstr "チャットルームが開始しました。" +msgstr "チャットルームを開始しました" #: mod_muc/mod_muc_log.erl:455 msgid "Chatroom is stopped" -msgstr "チャットルームが停止しました。" +msgstr "チャットルームを停止しました" #: mod_muc/mod_muc_log.erl:459 msgid "Monday" @@ -728,7 +730,7 @@ msgstr "チャットルーム設定" #: mod_muc/mod_muc_log.erl:760 msgid "Room Occupants" -msgstr "居住者の数" +msgstr "在室者の数" #: mod_muc/mod_muc_room.erl:170 msgid "Traffic rate limit is exceeded" @@ -741,7 +743,7 @@ msgstr "エラーメッセージを送信したため、この参加者はキッ #: mod_muc/mod_muc_room.erl:251 msgid "It is not allowed to send private messages to the conference" -msgstr "カンファレンスルームにプライベートメッセージを送信することは出来ません" +msgstr "カンファレンスルームにプライベートメッセージを送信することはできません" #: mod_muc/mod_muc_room.erl:296 msgid "Improper message type" @@ -756,44 +758,44 @@ msgstr "" #: mod_muc/mod_muc_room.erl:419 msgid "It is not allowed to send private messages of type \"groupchat\"" -msgstr "種別が\"groupchat\" であるプライベートメッセージは許可されていません" +msgstr "" +"種別が\"groupchat\" であるプライベートメッセージを送信することはできません" #: mod_muc/mod_muc_room.erl:431 mod_muc/mod_muc_room.erl:486 msgid "Recipient is not in the conference room" -msgstr "受信者がカンファレンスルームに居ません" +msgstr "受信者がカンファレンスルームにいません" #: mod_muc/mod_muc_room.erl:451 mod_muc/mod_muc_room.erl:829 #: mod_muc/mod_muc_room.erl:3546 msgid "Only occupants are allowed to send messages to the conference" -msgstr "移住者のみがカンファレンスに" +msgstr "在室者のみがカンファレンスにメッセージを送ることができます" #: mod_muc/mod_muc_room.erl:460 msgid "It is not allowed to send private messages" -msgstr "プライベートメッセージを送信することは許可されませんでした" +msgstr "プライベートメッセージを送信することはできません" #: mod_muc/mod_muc_room.erl:509 msgid "Only occupants are allowed to send queries to the conference" -msgstr "移住者のみがカンファレンスにクエリーを送信出来ます" +msgstr "在室者のみがカンファレンスにクエリーを送信することができます" #: mod_muc/mod_muc_room.erl:521 msgid "Queries to the conference members are not allowed in this room" msgstr "" -"このチャットルームではカンファレンスメンバーへのクエリーは禁止されています" +"このチャットルームでは、カンファレンスメンバーへのクエリーは禁止されています" #: mod_muc/mod_muc_room.erl:805 msgid "" "Only moderators and participants are allowed to change the subject in this " "room" -msgstr "" -"モデレーターと参加者のみがチャットルームの件名を変更する事を許可されています" +msgstr "モデレーターと参加者のみがチャットルームの件名を変更できます" #: mod_muc/mod_muc_room.erl:810 msgid "Only moderators are allowed to change the subject in this room" -msgstr "モデレーターのみがチャットルームの件名を変更出来ます" +msgstr "モデレーターのみがチャットルームの件名を変更できます" #: mod_muc/mod_muc_room.erl:820 msgid "Visitors are not allowed to send messages to all occupants" -msgstr "ビジターが移住者ににメッセージを送ることは許可されていません" +msgstr "ビジターはすべての在室者にメッセージを送信することはできません" #: mod_muc/mod_muc_room.erl:894 msgid "" @@ -802,12 +804,11 @@ msgstr "エラープレゼンスを送信したため、この参加者はキッ #: mod_muc/mod_muc_room.erl:912 msgid "Visitors are not allowed to change their nicknames in this room" -msgstr "" -"ビジターはこのチャットルームでニックネームを変更することは許可されていません" +msgstr "ビジターはこのチャットルームでニックネームを変更することはできません" #: mod_muc/mod_muc_room.erl:925 mod_muc/mod_muc_room.erl:1572 msgid "That nickname is already in use by another occupant" -msgstr "ニックネームは既に他の移住者によって使用されています" +msgstr "ニックネームは既にほかの在室者によって使用されています" #: mod_muc/mod_muc_room.erl:1561 msgid "You have been banned from this room" @@ -827,7 +828,7 @@ msgstr "チャットルームに入るにはパスワードが必要です" #: mod_muc/mod_muc_room.erl:1648 msgid "Unable to generate a captcha" -msgstr "キャプチャを生成することが出来ません。" +msgstr "CAPTCHA を生成することができません" #: mod_muc/mod_muc_room.erl:1658 msgid "Incorrect password" @@ -847,15 +848,15 @@ msgstr "Jabber ID ~s は無効です" #: mod_muc/mod_muc_room.erl:2354 msgid "Nickname ~s does not exist in the room" -msgstr "ニックネーム ~s はこのチャットルームに居ません" +msgstr "ニックネーム ~s はこのチャットルームにいません" #: mod_muc/mod_muc_room.erl:2380 mod_muc/mod_muc_room.erl:2762 msgid "Invalid affiliation: ~s" -msgstr "無効な提携です: ~s" +msgstr "無効な分掌です: ~s" #: mod_muc/mod_muc_room.erl:2437 msgid "Invalid role: ~s" -msgstr "無効なロールです: ~s" +msgstr "無効な役です: ~s" #: mod_muc/mod_muc_room.erl:2739 mod_muc/mod_muc_room.erl:2775 msgid "Owner privileges required" @@ -871,35 +872,35 @@ msgstr "チャットルームタイトル" #: mod_muc/mod_muc_room.erl:2907 mod_muc/mod_muc_room.erl:3325 msgid "Room description" -msgstr "チャットルームの詳細" +msgstr "チャットルームの説明" #: mod_muc/mod_muc_room.erl:2914 msgid "Make room persistent" -msgstr "チャットルームを永続化します" +msgstr "チャットルームを永続化" #: mod_muc/mod_muc_room.erl:2919 msgid "Make room public searchable" -msgstr "チャットルームを検索可能にします" +msgstr "チャットルームを検索可" #: mod_muc/mod_muc_room.erl:2922 msgid "Make participants list public" -msgstr "参加者一覧を公開します" +msgstr "参加者一覧を公開" #: mod_muc/mod_muc_room.erl:2925 msgid "Make room password protected" -msgstr "チャットルームにパスワードを設定します" +msgstr "チャットルームにパスワードを設定" #: mod_muc/mod_muc_room.erl:2936 msgid "Maximum Number of Occupants" -msgstr "最大移住者数" +msgstr "最大在室者数" #: mod_muc/mod_muc_room.erl:2943 msgid "No limit" -msgstr "制限無し" +msgstr "制限なし" #: mod_muc/mod_muc_room.erl:2954 msgid "Present real Jabber IDs to" -msgstr "本当の Jabber ID を公開する" +msgstr "本当の Jabber ID を公開" #: mod_muc/mod_muc_room.erl:2962 msgid "moderators only" @@ -911,61 +912,61 @@ msgstr "誰でも" #: mod_muc/mod_muc_room.erl:2966 msgid "Make room members-only" -msgstr "チャットルームをメンバーのみに制限する" +msgstr "チャットルームをメンバーのみに制限" #: mod_muc/mod_muc_room.erl:2969 msgid "Make room moderated" -msgstr "チャットルームをモデレートする" +msgstr "チャットルームをモデレート化" #: mod_muc/mod_muc_room.erl:2972 msgid "Default users as participants" -msgstr "デフォルトのユーザーは参加者にする" +msgstr "デフォルトのユーザーは参加者" #: mod_muc/mod_muc_room.erl:2975 msgid "Allow users to change the subject" -msgstr "ユーザーによる件名の変更を許可する" +msgstr "ユーザーによる件名の変更を許可" #: mod_muc/mod_muc_room.erl:2978 msgid "Allow users to send private messages" -msgstr "ユーザーによるプライベートメッセージの送信を許可する" +msgstr "ユーザーによるプライベートメッセージの送信を許可" #: mod_muc/mod_muc_room.erl:2981 msgid "Allow users to query other users" -msgstr "ユーザーによる他のユーザーへの問い合わせを許可する" +msgstr "ユーザーによる他のユーザーへのクエリーを許可" #: mod_muc/mod_muc_room.erl:2984 msgid "Allow users to send invites" -msgstr "ユーザーによる招待を許可する" +msgstr "ユーザーによる招待を許可" #: mod_muc/mod_muc_room.erl:2987 msgid "Allow visitors to send status text in presence updates" -msgstr "ビジターがプレゼンス更新のステータス文を送信する事を許可する" +msgstr "ビジターによるプレゼンス更新のステータス文の送信を許可" #: mod_muc/mod_muc_room.erl:2990 msgid "Allow visitors to change nickname" -msgstr "ビジターがニックネームを変更する事を許可します" +msgstr "ビジターのニックネームの変更を許可" #: mod_muc/mod_muc_room.erl:2996 msgid "Make room captcha protected" -msgstr "チャットルームをキャプチャで保護します" +msgstr "チャットルームを CAPTCHA で保護" #: mod_muc/mod_muc_room.erl:3005 msgid "Enable logging" -msgstr "ロギングを有効にする" +msgstr "ロギングを有効" #: mod_muc/mod_muc_room.erl:3013 msgid "You need an x:data capable client to configure room" msgstr "" -"チャットルームを設定するにはにはクライアントが x:data をサポートする必要があ" -"ります" +"チャットルームを設定するにはクライアントが x:data をサポートする必要がありま" +"す" #: mod_muc/mod_muc_room.erl:3327 msgid "Number of occupants" -msgstr "居住者の数" +msgstr "在室者の数" #: mod_muc/mod_muc_room.erl:3383 msgid "private, " -msgstr "プライベート" +msgstr "プライベート、" #: mod_muc/mod_muc_room.erl:3466 msgid "~s invites you to the room ~s" @@ -983,7 +984,7 @@ msgstr "" #: mod_offline.erl:560 mod_offline_odbc.erl:408 msgid "~s's Offline Messages Queue" -msgstr "~s's オフラインメッセージキュー" +msgstr "~s' のオフラインメッセージキュー" #: mod_offline.erl:563 mod_offline_odbc.erl:411 mod_roster.erl:992 #: mod_roster_odbc.erl:1092 mod_shared_roster.erl:810 @@ -1020,11 +1021,11 @@ msgstr "選択した項目を削除" #: mod_offline.erl:645 mod_offline_odbc.erl:519 msgid "Offline Messages:" -msgstr "オフラインメッセージ" +msgstr "オフラインメッセージ:" #: mod_offline.erl:645 mod_offline_odbc.erl:519 msgid "Remove All Offline Messages" -msgstr "すべてのオフラインメッセージを削除します" +msgstr "すべてのオフラインメッセージを削除" #: mod_proxy65/mod_proxy65_service.erl:213 msgid "ejabberd SOCKS5 Bytestreams module" @@ -1040,11 +1041,11 @@ msgstr "ejabberd Publish-Subscribe module" #: mod_pubsub/mod_pubsub.erl:1496 mod_pubsub/mod_pubsub_odbc.erl:1311 msgid "PubSub subscriber request" -msgstr "PubSub 購読リクエスト" +msgstr "PubSub 購読者のリクエスト" #: mod_pubsub/mod_pubsub.erl:1498 mod_pubsub/mod_pubsub_odbc.erl:1313 msgid "Choose whether to approve this entity's subscription." -msgstr "このエントリを承認するかどうかを選択して下さい" +msgstr "このエントリを承認するかどうかを選択してください" #: mod_pubsub/mod_pubsub.erl:1504 mod_pubsub/mod_pubsub_odbc.erl:1319 msgid "Node ID" @@ -1052,31 +1053,31 @@ msgstr "ノードID" #: mod_pubsub/mod_pubsub.erl:1509 mod_pubsub/mod_pubsub_odbc.erl:1324 msgid "Subscriber Address" -msgstr "購読アドレス" +msgstr "購読者のアドレス" #: mod_pubsub/mod_pubsub.erl:1515 mod_pubsub/mod_pubsub_odbc.erl:1330 msgid "Allow this Jabber ID to subscribe to this pubsub node?" -msgstr "この Jabber ID をこの pubsubノードへ購読することを許可しますか?" +msgstr "この Jabber ID に、この pubsubノードの購読を許可しますか ?" #: mod_pubsub/mod_pubsub.erl:3387 mod_pubsub/mod_pubsub_odbc.erl:3229 msgid "Deliver payloads with event notifications" -msgstr "イベント通知と同時にペイロードを配送します" +msgstr "イベント通知と同時にペイロードを配送する" #: mod_pubsub/mod_pubsub.erl:3388 mod_pubsub/mod_pubsub_odbc.erl:3230 msgid "Deliver event notifications" -msgstr "イベント通知を配送します" +msgstr "イベント通知を配送する" #: mod_pubsub/mod_pubsub.erl:3389 mod_pubsub/mod_pubsub_odbc.erl:3231 msgid "Notify subscribers when the node configuration changes" -msgstr "ノード設定に変更があった時に購読者へ通知します" +msgstr "ノード設定に変更があった時に購読者へ通知する" #: mod_pubsub/mod_pubsub.erl:3390 mod_pubsub/mod_pubsub_odbc.erl:3232 msgid "Notify subscribers when the node is deleted" -msgstr "ノードが削除された時に購読者へ通知します" +msgstr "ノードが削除された時に購読者へ通知する" #: mod_pubsub/mod_pubsub.erl:3391 mod_pubsub/mod_pubsub_odbc.erl:3233 msgid "Notify subscribers when items are removed from the node" -msgstr "アイテムがノードから消された時に購読者へ通知します" +msgstr "アイテムがノードから消された時に購読者へ通知する" #: mod_pubsub/mod_pubsub.erl:3392 mod_pubsub/mod_pubsub_odbc.erl:3234 msgid "Persist items to storage" @@ -1084,11 +1085,11 @@ msgstr "アイテムをストレージに保存する" #: mod_pubsub/mod_pubsub.erl:3393 mod_pubsub/mod_pubsub_odbc.erl:3235 msgid "A friendly name for the node" -msgstr "ノードの為のフレンドリネーム" +msgstr "ノードのフレンドリネーム" #: mod_pubsub/mod_pubsub.erl:3394 mod_pubsub/mod_pubsub_odbc.erl:3236 msgid "Max # of items to persist" -msgstr "アイテムの最大保存数 #" +msgstr "アイテムの最大保存数" #: mod_pubsub/mod_pubsub.erl:3395 mod_pubsub/mod_pubsub_odbc.erl:3237 msgid "Whether to allow subscriptions" @@ -1108,7 +1109,7 @@ msgstr "公開モデルを指定する" #: mod_pubsub/mod_pubsub.erl:3402 mod_pubsub/mod_pubsub_odbc.erl:3244 msgid "Purge all items when the relevant publisher goes offline" -msgstr "公開者がオフラインになる時に、全てののアイテムを削除" +msgstr "公開者がオフラインになるときに、すべてのアイテムを削除" #: mod_pubsub/mod_pubsub.erl:3403 mod_pubsub/mod_pubsub_odbc.erl:3245 msgid "Specify the event message type" @@ -1116,7 +1117,7 @@ msgstr "イベントメッセージ種別を設定" #: mod_pubsub/mod_pubsub.erl:3405 mod_pubsub/mod_pubsub_odbc.erl:3247 msgid "Max payload size in bytes" -msgstr "最大ぺイロードサイズ(byte)" +msgstr "最大ぺイロードサイズ (byte)" #: mod_pubsub/mod_pubsub.erl:3406 mod_pubsub/mod_pubsub_odbc.erl:3248 msgid "When to send the last published item" @@ -1132,16 +1133,16 @@ msgstr "提携されたノードの集合です" #: mod_register.erl:191 msgid "The CAPTCHA verification has failed" -msgstr "キャプチャ検証は失敗しました" +msgstr "CAPTCHA 検証は失敗しました" #: mod_register.erl:218 msgid "You need a client that supports x:data and CAPTCHA to register" msgstr "" -"登録を行うにはクライアントがx:dataとキャプチャをサポートする必要があります" +"登録を行うにはクライアントが x:data と CAPTCHA をサポートする必要があります" #: mod_register.erl:224 mod_register.erl:258 msgid "Choose a username and password to register with this server" -msgstr "サーバーに登録するユーザー名とパスワードを選択して下さい" +msgstr "サーバーに登録するユーザー名とパスワードを選択してください" #: mod_register.erl:228 mod_vcard.erl:358 mod_vcard_odbc.erl:336 #: web/ejabberd_web_admin.erl:1512 web/ejabberd_web_admin.erl:1569 @@ -1150,7 +1151,7 @@ msgstr "ユーザー" #: mod_register.erl:244 msgid "Unable to generate a CAPTCHA" -msgstr "キャプチャを生成出来ませんでした" +msgstr "CAPTCHA を生成できませんでした" #: mod_register.erl:309 mod_register.erl:354 msgid "The password is too weak" @@ -1164,7 +1165,7 @@ msgstr "早すぎるユーザーアカウント登録は許可されていませ #: web/ejabberd_web_admin.erl:1880 web/ejabberd_web_admin.erl:1891 #: web/ejabberd_web_admin.erl:2214 msgid "None" -msgstr "無し" +msgstr "なし" #: mod_roster.erl:950 mod_roster_odbc.erl:1050 msgid "Subscription" @@ -1202,7 +1203,7 @@ msgstr "不正なフォーマット" #: mod_roster.erl:1000 mod_roster_odbc.erl:1100 msgid "Add Jabber ID" -msgstr "Jabber ID の追加" +msgstr "Jabber ID を追加" #: mod_roster.erl:1099 mod_roster_odbc.erl:1199 msgid "Roster" @@ -1220,11 +1221,11 @@ msgstr "新規追加" #: mod_shared_roster.erl:879 msgid "Name:" -msgstr "名前: " +msgstr "名前:" #: mod_shared_roster.erl:884 msgid "Description:" -msgstr "詳細:" +msgstr "説明:" #: mod_shared_roster.erl:892 msgid "Members:" @@ -1328,7 +1329,7 @@ msgstr "検索結果: " #: mod_vcard_ldap.erl:458 msgid "Fill in fields to search for any matching Jabber User" -msgstr "項目を埋めて Jabber User を検索して下さい" +msgstr "項目を埋めて Jabber User を検索してください" #: web/ejabberd_web_admin.erl:189 web/ejabberd_web_admin.erl:199 #: web/ejabberd_web_admin.erl:215 web/ejabberd_web_admin.erl:225 @@ -1377,15 +1378,15 @@ msgstr "去年" #: web/ejabberd_web_admin.erl:1088 msgid "All activity" -msgstr "全て" +msgstr "すべて" #: web/ejabberd_web_admin.erl:1090 msgid "Show Ordinary Table" -msgstr "Ordinaryテーブルを表示" +msgstr "Ordinary テーブルを表示" #: web/ejabberd_web_admin.erl:1092 msgid "Show Integral Table" -msgstr "Integralテーブルを表示" +msgstr "Integral テーブルを表示" #: web/ejabberd_web_admin.erl:1101 web/ejabberd_web_admin.erl:1933 msgid "Statistics" @@ -1436,7 +1437,7 @@ msgstr "外向き s2s サービス:" #: web/mod_register_web.erl:367 web/mod_register_web.erl:376 #: web/mod_register_web.erl:406 msgid "Change Password" -msgstr "パスワードの変更" +msgstr "パスワードを変更" #: web/ejabberd_web_admin.erl:1752 msgid "User " @@ -1453,7 +1454,7 @@ msgstr "パスワード" #: web/ejabberd_web_admin.erl:1822 msgid "No Data" -msgstr "データ無し" +msgstr "データなし" #: web/ejabberd_web_admin.erl:1900 msgid "Nodes" @@ -1514,13 +1515,13 @@ msgid "" "If you are using the ODBC module, you also need to backup your SQL database " "separately." msgstr "" -"これらのオプションは組み込みの Mnesiaデーターベースをバックアップのみを行うこ" -"とに注意して下さい。もしも ODBCモジュールを使用している場合は SQLデーターベー" -"スのバックアップを別に行う必要があります。" +"これらのオプションは組み込みの Mnesia データーベースのバックアップのみを行う" +"ことに注意してください。もし ODBC モジュールを使用している場合は、SQL デー" +"ターベースのバックアップを別に行う必要があります。" #: web/ejabberd_web_admin.erl:2036 msgid "Store binary backup:" -msgstr "バイナリバックアップの保存" +msgstr "バイナリバックアップを保存:" #: web/ejabberd_web_admin.erl:2040 web/ejabberd_web_admin.erl:2047 #: web/ejabberd_web_admin.erl:2055 web/ejabberd_web_admin.erl:2062 @@ -1532,33 +1533,34 @@ msgstr "OK" #: web/ejabberd_web_admin.erl:2043 msgid "Restore binary backup immediately:" -msgstr "直ちにバイナリバックアップからリストア" +msgstr "直ちにバイナリバックアップからリストア:" #: web/ejabberd_web_admin.erl:2051 msgid "" "Restore binary backup after next ejabberd restart (requires less memory):" -msgstr "ejabberd の再起動時にバイナリバックアップからリストア" +msgstr "ejabberd の再起動時にバイナリバックアップからリストア (メモリ少):" #: web/ejabberd_web_admin.erl:2058 msgid "Store plain text backup:" -msgstr "プレーンテキストバックアップの保存" +msgstr "プレーンテキストバックアップを保存:" #: web/ejabberd_web_admin.erl:2065 msgid "Restore plain text backup immediately:" -msgstr "直ちにプレーンテキストバックアップからリストア" +msgstr "直ちにプレーンテキストバックアップからリストア:" #: web/ejabberd_web_admin.erl:2072 msgid "Import users data from a PIEFXIS file (XEP-0227):" -msgstr "ユーザーデータを PIEFXISファイルからインポート(XEP-0227):" +msgstr "ユーザーデータを PIEFXIS ファイルからインポート (XEP-0227):" #: web/ejabberd_web_admin.erl:2079 msgid "Export data of all users in the server to PIEFXIS files (XEP-0227):" msgstr "" -"サーバーにある全てのユーザーデータをPIEFXISファイルにエクスポート(XEP-0227):" +"サーバーにあるすべてのユーザーデータを PIEFXIS ファイルにエクスポート " +"(XEP-0227):" #: web/ejabberd_web_admin.erl:2086 msgid "Export data of users in a host to PIEFXIS files (XEP-0227):" -msgstr "ホストのユーザーデータをPIEFXISファイルにエクスポート(XEP-0227):" +msgstr "ホストのユーザーデータを PIEFXIS ファイルにエクスポート (XEP-0227):" #: web/ejabberd_web_admin.erl:2094 msgid "Import user data from jabberd14 spool file:" @@ -1582,7 +1584,7 @@ msgstr "~p の統計" #: web/ejabberd_web_admin.erl:2181 msgid "Uptime:" -msgstr "起動時間" +msgstr "起動時間:" #: web/ejabberd_web_admin.erl:2184 msgid "CPU Time:" @@ -1614,7 +1616,7 @@ msgstr "更新計画" #: web/ejabberd_web_admin.erl:2250 msgid "Modified modules" -msgstr "モジュールを変更しました" +msgstr "変更されたモジュール" #: web/ejabberd_web_admin.erl:2251 msgid "Update script" @@ -1658,7 +1660,7 @@ msgstr "開始" #: web/mod_register_web.erl:103 msgid "Your Jabber account was successfully created." -msgstr "Jabberアカウントの作成に成功しました。" +msgstr "Jabber アカウントの作成に成功しました。" #: web/mod_register_web.erl:106 msgid "There was an error creating the account: " @@ -1666,7 +1668,7 @@ msgstr "アカウントの作成中にエラーが発生しました: " #: web/mod_register_web.erl:114 msgid "Your Jabber account was successfully deleted." -msgstr "Jabberアカウントの削除に成功しました。" +msgstr "Jabber アカウントの削除に成功しました。" #: web/mod_register_web.erl:117 msgid "There was an error deleting the account: " @@ -1674,7 +1676,7 @@ msgstr "アカウントの削除中にエラーが発生しました: " #: web/mod_register_web.erl:127 msgid "The password of your Jabber account was successfully changed." -msgstr "Jabberアカウントのパスワード変更に成功しました。" +msgstr "Jabber アカウントのパスワード変更に成功しました。" #: web/mod_register_web.erl:130 msgid "There was an error changing the password: " @@ -1682,17 +1684,17 @@ msgstr "パスワードの変更中にエラーが発生しました: " #: web/mod_register_web.erl:162 web/mod_register_web.erl:171 msgid "Jabber Account Registration" -msgstr "Jabberアカウント登録" +msgstr "Jabber アカウント登録" #: web/mod_register_web.erl:173 web/mod_register_web.erl:191 #: web/mod_register_web.erl:200 msgid "Register a Jabber account" -msgstr "Jabberアカウントを登録" +msgstr "Jabber アカウントを登録" #: web/mod_register_web.erl:175 web/mod_register_web.erl:488 #: web/mod_register_web.erl:497 msgid "Unregister a Jabber account" -msgstr "Jabberアカウントの登録解除" +msgstr "Jabber アカウントを削除" #: web/mod_register_web.erl:202 msgid "" @@ -1700,9 +1702,9 @@ msgid "" "(Jabber IDentifier) will be of the form: username@server. Please read " "carefully the instructions to fill correctly the fields." msgstr "" -"ここはJabberサーバーにアカウント作成を行うページです。あなたのJID(JabberID)" -"は username@server の様な形式に成ります。注意事項通り、正しく項目に記入して下" -"さい。" +"ここは Jabber サーバーにアカウントを作成するページです。あなたの JID " +"(JabberID) は username@server のような形式になります。注意事項どおり、正しく" +"項目を記入してください。" #: web/mod_register_web.erl:211 web/mod_register_web.erl:381 #: web/mod_register_web.erl:504 @@ -1716,7 +1718,7 @@ msgstr "" #: web/mod_register_web.erl:217 msgid "Characters not allowed:" -msgstr "許可されていない文字:" +msgstr "使用できない文字:" #: web/mod_register_web.erl:221 web/mod_register_web.erl:386 #: web/mod_register_web.erl:509 @@ -1728,21 +1730,22 @@ msgid "" "Don't tell your password to anybody, not even the administrators of the " "Jabber server." msgstr "" -"パスワードは誰にも教えないようにして下さい。Jabberサーバーの管理者があなたに" -"パスワードを尋ねることはありません。" +"パスワードは誰にも教えないようにしてください。Jabber サーバーの管理者があなた" +"にパスワードを尋ねることはありません。" #: web/mod_register_web.erl:233 msgid "You can later change your password using a Jabber client." msgstr "" -"あなたは後からJabberクライアントを使用してパスワードを変更する事が出来ます。" +"あなたは後から Jabber クライアントを使用してパスワードを変更することができま" +"す。" #: web/mod_register_web.erl:234 msgid "" "Some Jabber clients can store your password in your computer. Use that " "feature only if you trust your computer is safe." msgstr "" -"Jabberクライアントはコンピューターにあなたのパスワード記憶出来ます。この機能" -"を使用する場合コンピューターが安全であることを確認して下さい。" +"Jabber クライアントはコンピューターにあなたのパスワードを記憶できます。この機能" +"を使用する場合、コンピューターが安全であることを確認してください。" #: web/mod_register_web.erl:236 msgid "" @@ -1750,13 +1753,13 @@ msgid "" "Jabber there isn't an automated way to recover your password if you forget " "it." msgstr "" -"パスワードは記憶するか、紙に書いて安全な場所に保管して下さい。もしあなたがパ" -"スワードを忘れてしまった場合、Jabberではパスワードのリカバリを自動的に行うこ" -"とは出来ません。" +"パスワードは記憶するか、紙に書いて安全な場所に保管してください。もしあなたが" +"パスワードを忘れてしまった場合、Jabber ではパスワードのリカバリを自動的に行う" +"ことはできません。" #: web/mod_register_web.erl:241 web/mod_register_web.erl:401 msgid "Password Verification:" -msgstr "パスワード(確認):" +msgstr "パスワード (確認):" #: web/mod_register_web.erl:249 msgid "Register" @@ -1772,11 +1775,11 @@ msgstr "新しいパスワード:" #: web/mod_register_web.erl:499 msgid "This page allows to unregister a Jabber account in this Jabber server." -msgstr "ここはサーバーのJabberアカウントの登録削除を行うページです。" +msgstr "ここはサーバーの Jabber アカウントの削除を行うページです。" #: web/mod_register_web.erl:519 msgid "Unregister" -msgstr "登録解除" +msgstr "削除" #~ msgid "Captcha test failed" #~ msgstr "キャプチャのテストに失敗しました" @@ -1787,8 +1790,8 @@ msgstr "登録解除" #~ msgid "(Raw)" #~ msgstr "(Raw)" -#~ msgid "Virtual Hosts" -#~ msgstr "ヴァーチャルホスト" +msgid "Virtual Hosts" +msgstr "ヴァーチャルホスト" #~ msgid "Specified nickname is already registered" #~ msgstr "指定されたニックネームは既に登録されています" diff --git a/src/msgs/nl.msg b/src/msgs/nl.msg index a105c5d11..d793d3dea 100644 --- a/src/msgs/nl.msg +++ b/src/msgs/nl.msg @@ -77,7 +77,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe module"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams module"}. {"ejabberd vCard module","ejabberd's vCard-module"}. -{"ejabberd virtual hosts","Virtuele hosts"}. {"ejabberd Web Admin","ejabberd Webbeheer"}. {"Elements","Elementen"}. {"Email","E-mail"}. @@ -362,6 +361,7 @@ {"Users Last Activity","Laatste activiteit van gebruikers"}. {"Validate","Bevestigen"}. {"vCard User Search","Gebruikers zoeken"}. +{"Virtual Hosts","Virtuele hosts"}. {"Visitors are not allowed to change their nicknames in this room","Het is bezoekers niet toegestaan hun naam te veranderen in dit kanaal"}. {"Visitors are not allowed to send messages to all occupants","Bezoekers mogen geen berichten verzenden naar alle aanwezigen"}. {"Wednesday","Woensdag"}. diff --git a/src/msgs/nl.po b/src/msgs/nl.po index 0057aaaa8..bf2254700 100644 --- a/src/msgs/nl.po +++ b/src/msgs/nl.po @@ -1378,7 +1378,7 @@ msgid "~s access rule configuration" msgstr "Access rules op ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" +msgid "Virtual Hosts" msgstr "Virtuele hosts" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 diff --git a/src/msgs/no.msg b/src/msgs/no.msg index eea9eb526..0d8adf85f 100644 --- a/src/msgs/no.msg +++ b/src/msgs/no.msg @@ -77,7 +77,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe modul"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams modul"}. {"ejabberd vCard module","ejabberd vCard modul"}. -{"ejabberd virtual hosts","virtuella ejabberd maskiner"}. {"ejabberd Web Admin","ejabberd Web Admin"}. {"Elements","Elementer"}. {"Email","Epost"}. @@ -362,6 +361,7 @@ {"Users Last Activity","Brukers Siste Aktivitet"}. {"Validate","Bekrefte gyldighet"}. {"vCard User Search","vCard Bruker Søk"}. +{"Virtual Hosts","Virtuella Maskiner"}. {"Visitors are not allowed to change their nicknames in this room","Besøkende får ikke lov å endre kallenavn i dette "}. {"Visitors are not allowed to send messages to all occupants","Besøkende får ikke sende meldinger til alle deltakere"}. {"Wednesday","onsdag"}. diff --git a/src/msgs/no.po b/src/msgs/no.po index 560fe7fa7..cc66e81bb 100644 --- a/src/msgs/no.po +++ b/src/msgs/no.po @@ -1352,8 +1352,8 @@ msgid "~s access rule configuration" msgstr "tilgangsregel konfigurasjon for ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "virtuella ejabberd maskiner" +msgid "Virtual Hosts" +msgstr "Virtuella Maskiner" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/pl.msg b/src/msgs/pl.msg index 01083e38d..1dc68356b 100644 --- a/src/msgs/pl.msg +++ b/src/msgs/pl.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","Moduł Publish-Subscribe"}. {"ejabberd SOCKS5 Bytestreams module","Moduł SOCKS5 Bytestreams"}. {"ejabberd vCard module","Moduł vCard ejabberd"}. -{"ejabberd virtual hosts","wirtualne hosty ejabberd"}. {"ejabberd Web Admin","ejabberd: Panel Administracyjny"}. {"Elements","Elementy"}. {"Email","Email"}. @@ -388,6 +387,7 @@ {"User","Użytkownik"}. {"Validate","Potwierdź"}. {"vCard User Search","Wyszukiwanie vCard użytkowników"}. +{"Virtual Hosts","Wirtualne Hosty"}. {"Visitors are not allowed to change their nicknames in this room","Uczestnicy tego pokoju nie mogą zmieniać swoich nicków"}. {"Visitors are not allowed to send messages to all occupants","Odwiedzający nie mogą wysyłać wiadomości do wszystkich obecnych"}. {"Wednesday","Środa"}. diff --git a/src/msgs/pl.po b/src/msgs/pl.po index 28bfa21e1..bc3932b0a 100644 --- a/src/msgs/pl.po +++ b/src/msgs/pl.po @@ -1360,8 +1360,8 @@ msgid "~s access rule configuration" msgstr "~s konfiguracja zasad dostępu" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "wirtualne hosty ejabberd" +msgid "Virtual Hosts" +msgstr "Wirtualne Hosty" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/pt-br.msg b/src/msgs/pt-br.msg index f2cbf3e7f..f7c334751 100644 --- a/src/msgs/pt-br.msg +++ b/src/msgs/pt-br.msg @@ -78,7 +78,6 @@ {"ejabberd Publish-Subscribe module","Módulo para Publicar Tópicos do ejabberd"}. {"ejabberd SOCKS5 Bytestreams module","Modulo ejabberd SOCKS5 Bytestreams"}. {"ejabberd vCard module","Módulo vCard para ejabberd"}. -{"ejabberd virtual hosts","Hosts virtuais de ejabberd"}. {"ejabberd Web Admin","ejabberd Web Admin"}. {"Elements","Elementos"}. {"Email","Email"}. @@ -376,6 +375,7 @@ {"User","Usuário"}. {"Validate","Validar"}. {"vCard User Search","Busca de Usuário vCard"}. +{"Virtual Hosts","Hosts virtuais"}. {"Visitors are not allowed to change their nicknames in this room","Nesta sala, os visitantes não pode mudar seus apelidos"}. {"Visitors are not allowed to send messages to all occupants","Os visitantes não podem enviar mensagens a todos os ocupantes"}. {"Wednesday","Quarta"}. diff --git a/src/msgs/pt-br.po b/src/msgs/pt-br.po index 59be6338a..e82979288 100644 --- a/src/msgs/pt-br.po +++ b/src/msgs/pt-br.po @@ -1368,8 +1368,8 @@ msgid "~s access rule configuration" msgstr "Configuração da Regra de Acesso ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Hosts virtuais de ejabberd" +msgid "Virtual Hosts" +msgstr "Hosts virtuais" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/pt.po b/src/msgs/pt.po index 25145e820..7758ac720 100644 --- a/src/msgs/pt.po +++ b/src/msgs/pt.po @@ -1425,8 +1425,8 @@ msgstr "Configuração das Regra de Acesso ~s" #: web/ejabberd_web_admin.erl:1031 #, fuzzy -msgid "ejabberd virtual hosts" -msgstr "Estatísticas do ejabberd" +msgid "Virtual Hosts" +msgstr "Servidores virtuales" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/ru.msg b/src/msgs/ru.msg index b618e6bde..3949ae07e 100644 --- a/src/msgs/ru.msg +++ b/src/msgs/ru.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","Модуль ejabberd Публикации-Подписки"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams модуль"}. {"ejabberd vCard module","ejabberd vCard модуль"}. -{"ejabberd virtual hosts","Виртуальные хосты ejabberd"}. {"ejabberd Web Admin","Web-интерфейс администрирования ejabberd"}. {"Elements","Элементы"}. {"Email","Электронная почта"}. @@ -388,6 +387,7 @@ {"User","Пользователь"}. {"Validate","Утвердить"}. {"vCard User Search","Поиск пользователей по vCard"}. +{"Virtual Hosts","Виртуальные хосты"}. {"Visitors are not allowed to change their nicknames in this room","Посетителям запрещено изменять свои псевдонимы в этой комнате"}. {"Visitors are not allowed to send messages to all occupants","Посетителям не разрешается посылать сообщения всем присутствующим"}. {"Wednesday","Среда"}. diff --git a/src/msgs/ru.po b/src/msgs/ru.po index e4b567819..9f302d431 100644 --- a/src/msgs/ru.po +++ b/src/msgs/ru.po @@ -1363,8 +1363,8 @@ msgid "~s access rule configuration" msgstr "Конфигурация правила доступа ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Виртуальные хосты ejabberd" +msgid "Virtual Hosts" +msgstr "Виртуальные хосты" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/sk.msg b/src/msgs/sk.msg index 3f5b24ab8..3fd23a6df 100644 --- a/src/msgs/sk.msg +++ b/src/msgs/sk.msg @@ -77,7 +77,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe modul"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams modul"}. {"ejabberd vCard module","ejabberd vCard modul"}. -{"ejabberd virtual hosts","ejabberd virtuálne servery"}. {"ejabberd Web Admin","ejabberd Web Admin"}. {"Elements","Prvky"}. {"Email","E-mail"}. @@ -362,6 +361,7 @@ {"User","Užívateľ"}. {"Validate","Overiť"}. {"vCard User Search","Hľadať užívateľov vo vCard"}. +{"Virtual Hosts","Virtuálne servery"}. {"Visitors are not allowed to change their nicknames in this room","V tejto miestnosti nieje povolené meniť prezývky"}. {"Visitors are not allowed to send messages to all occupants","Návštevníci nemajú povolené zasielať správy všetkým prihláseným do konferencie"}. {"Wednesday","Streda"}. diff --git a/src/msgs/sk.po b/src/msgs/sk.po index c35d815c8..ad766ca82 100644 --- a/src/msgs/sk.po +++ b/src/msgs/sk.po @@ -1355,8 +1355,8 @@ msgid "~s access rule configuration" msgstr "~s konfigurácia prístupového pravidla" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "ejabberd virtuálne servery" +msgid "Virtual Hosts" +msgstr "Virtuálne servery" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1788,9 +1788,6 @@ msgstr "" #~ msgid "(Raw)" #~ msgstr "(Raw)" -#~ msgid "Virtual Hosts" -#~ msgstr "Virtuálne servery" - #~ msgid "Specified nickname is already registered" #~ msgstr "Zadaná prezývka je už registrovaná" diff --git a/src/msgs/sv.msg b/src/msgs/sv.msg index 96aecf28a..fb69f9dce 100644 --- a/src/msgs/sv.msg +++ b/src/msgs/sv.msg @@ -72,7 +72,6 @@ {"ejabberd Publish-Subscribe module","ejabberd publikprenumerations modul"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestrem modul"}. {"ejabberd vCard module","ejabberd vCard-modul"}. -{"ejabberd virtual hosts","Virtuella ejabberd-servrar"}. {"ejabberd Web Admin","ejabberd Web Admin"}. {"Elements","Elements"}. {"Email","Email"}. @@ -348,6 +347,7 @@ {"Users Last Activity","Användarens senaste aktivitet"}. {"Validate","Validera"}. {"vCard User Search","vCard användare sök"}. +{"Virtual Hosts","Virtuella servrar"}. {"Visitors are not allowed to change their nicknames in this room","Det är inte tillåtet for gäster att ändra sina smeknamn i detta rummet"}. {"Visitors are not allowed to send messages to all occupants","Besökare får inte skicka medelande till alla"}. {"Wednesday","Onsdag"}. diff --git a/src/msgs/sv.po b/src/msgs/sv.po index db5d1186f..6fa9b7110 100644 --- a/src/msgs/sv.po +++ b/src/msgs/sv.po @@ -1371,8 +1371,8 @@ msgid "~s access rule configuration" msgstr "Åtkomstregelkonfiguration för ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Virtuella ejabberd-servrar" +msgid "Virtual Hosts" +msgstr "Virtuella servrar" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/th.msg b/src/msgs/th.msg index 74edb47b5..892fe48bb 100644 --- a/src/msgs/th.msg +++ b/src/msgs/th.msg @@ -66,7 +66,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe module"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams module"}. {"ejabberd vCard module","ejabberd vCard module"}. -{"ejabberd virtual hosts","โฮสต์เสมือน ejabberd"}. {"Email","อีเมล"}. {"Enable logging","เปิดใช้งานการบันทึก"}. {"End User Session","สิ้นสุดเซสชันของผู้ใช้"}. @@ -297,6 +296,7 @@ {"Users Last Activity","กิจกรรมล่าสุดของผู้ใช้"}. {"Validate","ตรวจสอบ"}. {"vCard User Search","ค้นหาผู้ใช้ vCard "}. +{"Virtual Hosts","โฮสต์เสมือน"}. {"Visitors are not allowed to send messages to all occupants","ผู้เยี่ยมเยือนไม่ได้รับอนุญาตให้ส่งข้อความถึงผู้ครอบครองห้องทั้งหมด"}. {"Wednesday","วันพุธ"}. {"When to send the last published item","เวลาที่ส่งรายการที่เผยแพร่ครั้งล่าสุด"}. diff --git a/src/msgs/th.po b/src/msgs/th.po index 36b5151e1..4a2aabdeb 100644 --- a/src/msgs/th.po +++ b/src/msgs/th.po @@ -1372,8 +1372,8 @@ msgid "~s access rule configuration" msgstr "~s การกำหนดค่ากฎการเข้าถึง" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "โฮสต์เสมือน ejabberd" +msgid "Virtual Hosts" +msgstr "โฮสต์เสมือน" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1803,9 +1803,6 @@ msgstr "" #~ msgid "(Raw)" #~ msgstr "(ข้อมูลดิบ)" -#~ msgid "Virtual Hosts" -#~ msgstr "โฮสต์เสมือน" - #~ msgid "Specified nickname is already registered" #~ msgstr "ชื่อเล่นที่ระบุได้รับการลงได้ทะเบียนแล้ว" diff --git a/src/msgs/tr.msg b/src/msgs/tr.msg index beff9c153..014b7c81a 100644 --- a/src/msgs/tr.msg +++ b/src/msgs/tr.msg @@ -73,7 +73,6 @@ {"ejabberd Publish-Subscribe module","ejabberd Publish-Subscribe modülü"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams modülü"}. {"ejabberd vCard module","ejabberd vCard modülü"}. -{"ejabberd virtual hosts","ejabberd sanal sunucuları"}. {"ejabberd Web Admin","ejabberd Web Yöneticisi"}. {"Elements","Elementler"}. {"Email","E-posta"}. @@ -356,6 +355,7 @@ {"Users Last Activity","Kullanıcıların Son Aktiviteleri"}. {"Validate","Geçerli"}. {"vCard User Search","vCard Kullanıcı Araması"}. +{"Virtual Hosts","Sanal Sunucuları"}. {"Visitors are not allowed to change their nicknames in this room","Bu odada ziyaretçilerin takma isimlerini değiştirmesine izin verilmiyor"}. {"Visitors are not allowed to send messages to all occupants","Ziyaretçilerin odadaki tüm sakinlere mesaj göndermesine izin verilmiyor"}. {"Wednesday","Çarşamba"}. diff --git a/src/msgs/tr.po b/src/msgs/tr.po index 11e88f569..333eaa5c3 100644 --- a/src/msgs/tr.po +++ b/src/msgs/tr.po @@ -1375,8 +1375,8 @@ msgid "~s access rule configuration" msgstr "~s erişim kuralları ayarları" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "ejabberd sanal sunucuları" +msgid "Virtual Hosts" +msgstr "Sanal Sunucuları" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/uk.msg b/src/msgs/uk.msg index cf1e6a8db..4da7e09dc 100644 --- a/src/msgs/uk.msg +++ b/src/msgs/uk.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","Модуль ejabberd Публікації-Абонування"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 Bytestreams модуль"}. {"ejabberd vCard module","ejabberd vCard модуль"}. -{"ejabberd virtual hosts","віртуальні хости ejabberd"}. {"ejabberd Web Admin","Веб-інтерфейс Адміністрування ejabberd"}. {"Elements","Елементи"}. {"Email","Електронна пошта"}. @@ -388,6 +387,7 @@ {"User","Користувач"}. {"Validate","Затвердити"}. {"vCard User Search","Пошук користувачів по vCard"}. +{"Virtual Hosts","віртуальні хости"}. {"Visitors are not allowed to change their nicknames in this room","Відвідувачам не дозволяється змінювати псевдонім в цій кімнаті"}. {"Visitors are not allowed to send messages to all occupants","Відвідувачам не дозволяється надсилати повідомлення всім присутнім"}. {"Wednesday","Середа"}. diff --git a/src/msgs/uk.po b/src/msgs/uk.po index 2b1b195f9..92c3ab3f7 100644 --- a/src/msgs/uk.po +++ b/src/msgs/uk.po @@ -1368,8 +1368,8 @@ msgid "~s access rule configuration" msgstr "Конфігурація правила доступу ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "віртуальні хости ejabberd" +msgid "Virtual Hosts" +msgstr "віртуальні хости" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" diff --git a/src/msgs/vi.msg b/src/msgs/vi.msg index a773c4cc6..da3101110 100644 --- a/src/msgs/vi.msg +++ b/src/msgs/vi.msg @@ -66,7 +66,6 @@ {"ejabberd Publish-Subscribe module","Môdun ejabberd Xuất Bản-Đăng Ký Bản quyền"}. {"ejabberd SOCKS5 Bytestreams module","Môdun SOCKS5 Bytestreams Bản quyền"}. {"ejabberd vCard module","Môdun ejabberd vCard Bản quyền"}. -{"ejabberd virtual hosts","Máy chủ ảo ejabberd"}. {"Email","Email"}. {"Enable logging","Cho phép ghi nhật ký"}. {"End User Session","Kết Thúc Phiên Giao Dịch Người Sử Dụng"}. @@ -297,6 +296,7 @@ {"Users","Người sử dụng"}. {"Validate","Xác nhận hợp lệ"}. {"vCard User Search","Tìm Kiếm Người Sử Dụng vCard"}. +{"Virtual Hosts","Máy Chủ Ảo"}. {"Visitors are not allowed to send messages to all occupants","Người ghé thăm không được phép gửi thư đến tất cả các người tham dự"}. {"Wednesday","Thứ Tư"}. {"When to send the last published item","Khi cần gửi mục được xuất bản cuối cùng"}. diff --git a/src/msgs/vi.po b/src/msgs/vi.po index 5132061d7..721caab88 100644 --- a/src/msgs/vi.po +++ b/src/msgs/vi.po @@ -1395,8 +1395,8 @@ msgid "~s access rule configuration" msgstr "~s cấu hình quy tắc truy cập" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Máy chủ ảo ejabberd" +msgid "Virtual Hosts" +msgstr "Máy Chủ Ảo" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1828,9 +1828,6 @@ msgstr "" #~ msgid "(Raw)" #~ msgstr "(Thô)" -#~ msgid "Virtual Hosts" -#~ msgstr "Máy Chủ Ảo" - #~ msgid "Specified nickname is already registered" #~ msgstr "Bí danh xác định đã đăng ký rồi" diff --git a/src/msgs/wa.msg b/src/msgs/wa.msg index 327a08fac..199a6eaf8 100644 --- a/src/msgs/wa.msg +++ b/src/msgs/wa.msg @@ -69,7 +69,6 @@ {"ejabberd Publish-Subscribe module","Module d' eplaidaedje-abounmint po ejabberd"}. {"ejabberd SOCKS5 Bytestreams module","Module SOCKS5 Bytestreams po ejabberd"}. {"ejabberd vCard module","Module vCard ejabberd"}. -{"ejabberd virtual hosts","Forveyous sierveus da ejabberd"}. {"ejabberd Web Admin","Manaedjeu waibe ejabberd"}. {"Email","Emile"}. {"Enable logging","Mete en alaedje li djournå"}. @@ -310,6 +309,7 @@ {"User","Uzeu"}. {"Validate","Valider"}. {"vCard User Search","Calpin des uzeus"}. +{"Virtual Hosts","Forveyous sierveus"}. {"Visitors are not allowed to change their nicknames in this room","Les viziteus èn polèt nén candjî leus metous no po ç' såle ci"}. {"Visitors are not allowed to send messages to all occupants","Les viziteus n' polèt nén evoyî des messaedjes a tos les prezints"}. {"Wednesday","mierkidi"}. diff --git a/src/msgs/wa.po b/src/msgs/wa.po index 4e3a664e2..b3ac7c799 100644 --- a/src/msgs/wa.po +++ b/src/msgs/wa.po @@ -1393,8 +1393,8 @@ msgid "~s access rule configuration" msgstr "Apontiaedje des rîles d' accès a ~s" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "Forveyous sierveus da ejabberd" +msgid "Virtual Hosts" +msgstr "Forveyous sierveus" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1827,9 +1827,6 @@ msgstr "" #~ msgid "(Raw)" #~ msgstr "(Dinêyes brutes)" -#~ msgid "Virtual Hosts" -#~ msgstr "Forveyous sierveus" - #~ msgid "Specified nickname is already registered" #~ msgstr "Li no metou dné est ddja edjîstré" diff --git a/src/msgs/zh.msg b/src/msgs/zh.msg index b025b16d2..e13cd154f 100644 --- a/src/msgs/zh.msg +++ b/src/msgs/zh.msg @@ -80,7 +80,6 @@ {"ejabberd Publish-Subscribe module","ejabberd 发行-订阅模块"}. {"ejabberd SOCKS5 Bytestreams module","ejabberd SOCKS5 字节流模块"}. {"ejabberd vCard module","ejabberd vCard 模块"}. -{"ejabberd virtual hosts","ejabberd 虚拟主机"}. {"ejabberd Web Admin","ejabberd 网页管理"}. {"Elements","元素"}. {"Email","电子邮件"}. @@ -388,6 +387,7 @@ {"User","用户"}. {"Validate","确认"}. {"vCard User Search","vCard 用户搜索"}. +{"Virtual Hosts","虚拟主机"}. {"Visitors are not allowed to change their nicknames in this room","此房间不允许用户更改昵称"}. {"Visitors are not allowed to send messages to all occupants","不允许访客给所有占有者发送消息"}. {"Wednesday","星期三"}. diff --git a/src/msgs/zh.po b/src/msgs/zh.po index bd740c004..cb55bce26 100644 --- a/src/msgs/zh.po +++ b/src/msgs/zh.po @@ -1338,8 +1338,8 @@ msgid "~s access rule configuration" msgstr "~s 访问规则配置" #: web/ejabberd_web_admin.erl:1031 -msgid "ejabberd virtual hosts" -msgstr "ejabberd 虚拟主机" +msgid "Virtual Hosts" +msgstr "虚拟主机" #: web/ejabberd_web_admin.erl:1039 web/ejabberd_web_admin.erl:1046 msgid "Users" @@ -1765,9 +1765,6 @@ msgstr "取消注册" #~ msgid "(Raw)" #~ msgstr "(原始格式)" -#~ msgid "Virtual Hosts" -#~ msgstr "虚拟主机" - #~ msgid "Specified nickname is already registered" #~ msgstr "指定的昵称已被注册" diff --git a/src/odbc/ejabberd_odbc.erl b/src/odbc/ejabberd_odbc.erl index f7084be83..cedc894ae 100644 --- a/src/odbc/ejabberd_odbc.erl +++ b/src/odbc/ejabberd_odbc.erl @@ -5,7 +5,7 @@ %%% Created : 8 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/odbc/ejabberd_odbc_sup.erl b/src/odbc/ejabberd_odbc_sup.erl index 8d9ad07cc..a8536b204 100644 --- a/src/odbc/ejabberd_odbc_sup.erl +++ b/src/odbc/ejabberd_odbc_sup.erl @@ -5,7 +5,7 @@ %%% Created : 22 Dec 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/odbc/mssql2000.sql b/src/odbc/mssql2000.sql index 0b649cde4..d4a70989d 100644 --- a/src/odbc/mssql2000.sql +++ b/src/odbc/mssql2000.sql @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -597,6 +597,40 @@ BEGIN END GO +/******************************************************************/ +/****** Object: StoredProcedure [dbo].[set_roster_version] **/ +/** Update users roster_version **/ +/******************************************************************/ +CREATE PROCEDURE [dbo].[set_roster_version] + @Username varchar(200), + @Version varchar(50) +AS +BEGIN + IF EXISTS (SELECT username FROM roster_version WITH (NOLOCK) WHERE username=@Username) + BEGIN + UPDATE roster_version SET username=@Username, version=@Version WHERE username=@Username; + END + ELSE + BEGIN + INSERT INTO roster_version (username, version) VALUES (@Username, @Version); + END +END +GO + +/******************************************************************/ +/****** Object: StoredProcedure [dbo].[get_roster_version] **/ +/** Retrive the user roster_version **/ +/******************************************************************/ +CREATE PROCEDURE [dbo].[get_roster_version] + @Username varchar(200) +AS +BEGIN + SELECT roster_version.version as version + FROM roster_version WITH (NOLOCK) + WHERE username=@Username; +END +GO + /***************************************************************/ /****** Object: StoredProcedure [dbo].[clean_spool_msg] ******/ /** Delete messages older that 3 days from spool **/ diff --git a/src/odbc/mssql2005.sql b/src/odbc/mssql2005.sql index 41b4d5851..911207078 100644 --- a/src/odbc/mssql2005.sql +++ b/src/odbc/mssql2005.sql @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -918,6 +918,40 @@ BEGIN END GO +/******************************************************************/ +/****** Object: StoredProcedure [dbo].[set_roster_version] **/ +/** Update users roster_version **/ +/******************************************************************/ +CREATE PROCEDURE [dbo].[set_roster_version] + @Username varchar(200), + @Version varchar(8000) +AS +BEGIN + IF EXISTS (SELECT username FROM roster_version WITH (NOLOCK) WHERE username=@Username) + BEGIN + UPDATE roster_version SET username=@Username, version=@Version WHERE username=@Username; + END + ELSE + BEGIN + INSERT INTO roster_version (username, version) VALUES (@Username, @Version); + END +END +GO + +/******************************************************************/ +/****** Object: StoredProcedure [dbo].[get_roster_version] **/ +/** Retrive the user roster_version **/ +/******************************************************************/ +CREATE PROCEDURE [dbo].[get_roster_version] + @Username varchar(200) +AS +BEGIN + SELECT roster_version.version as version + FROM roster_version WITH (NOLOCK) + WHERE username=@Username; +END +GO + /***************************************************************/ /****** Object: StoredProcedure [dbo].[clean_spool_msg] ******/ /** Delete messages older that 3 days from spool **/ diff --git a/src/odbc/mysql.sql b/src/odbc/mysql.sql index f2c2963b7..4ef2ac339 100644 --- a/src/odbc/mysql.sql +++ b/src/odbc/mysql.sql @@ -1,5 +1,5 @@ -- --- ejabberd, Copyright (C) 2002-2010 ProcessOne +-- ejabberd, Copyright (C) 2002-2011 ProcessOne -- -- This program is free software; you can redistribute it and/or -- modify it under the terms of the GNU General Public License as diff --git a/src/odbc/odbc_queries.erl b/src/odbc/odbc_queries.erl index c2b280b1a..b614fd790 100644 --- a/src/odbc/odbc_queries.erl +++ b/src/odbc/odbc_queries.erl @@ -5,7 +5,7 @@ %%% Created : by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -90,6 +90,8 @@ -define(generic, true). -endif. +-include("ejabberd.hrl"). + %% Almost a copy of string:join/2. %% We use this version because string:join/2 is relatively %% new function (introduced in R12B-0). @@ -882,8 +884,14 @@ count_records_where(LServer, Table, WhereClause) -> ["select count(*) from ", Table, " with (nolock) ", WhereClause]). get_roster_version(LServer, LUser) -> - ejabberd_odbc:sql_query(LServer, - ["select version from dbo.roster_version with (nolock) where username = '", LUser, "'"]). -set_roster_version(LUser, Version) -> - update_t("dbo.roster_version", ["username", "version"], [LUser, Version], ["username = '", LUser, "'"]). + ejabberd_odbc:sql_query( + LServer, + ["EXECUTE dbo.get_roster_version '", LUser, "'"]). + +set_roster_version(Username, Version) -> + %% This function doesn't know the vhost, so we hope it's the first one defined: + LServer = ?MYNAME, + ejabberd_odbc:sql_query( + LServer, + ["EXECUTE dbo.set_roster_version '", Username, "', '", Version, "'"]). -endif. diff --git a/src/odbc/pg.sql b/src/odbc/pg.sql index 1d6a458aa..e530ef9e7 100644 --- a/src/odbc/pg.sql +++ b/src/odbc/pg.sql @@ -1,5 +1,5 @@ -- --- ejabberd, Copyright (C) 2002-2010 ProcessOne +-- ejabberd, Copyright (C) 2002-2011 ProcessOne -- -- This program is free software; you can redistribute it and/or -- modify it under the terms of the GNU General Public License as diff --git a/src/p1_fsm.erl b/src/p1_fsm.erl index adb4e00cf..4f2705782 100644 --- a/src/p1_fsm.erl +++ b/src/p1_fsm.erl @@ -14,7 +14,7 @@ %% AB. All Rights Reserved.'' %% %% The code has been modified and improved by ProcessOne. -%% Copyright 2007-2010, ProcessOne +%% Copyright 2007-2011, ProcessOne %% %% The change adds the following features: %% - You can send exit(priority_shutdown) to the p1_fsm process to @@ -395,7 +395,8 @@ loop(Parent, Name, StateName, StateData, Mod, Time, Debug, {process_limit, Limit} -> Reason = {process_limit, Limit}, Msg = {'EXIT', Parent, {error, {process_limit, Limit}}}, - terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) + terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug, + queue:new()) end, process_message(Parent, Name, StateName, StateData, Mod, Time, Debug, Limits, Queue, QueueLen). @@ -451,7 +452,8 @@ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug, [Name, StateName, StateData, Mod, Time, Limits, Queue, QueueLen], Hib); {'EXIT', Parent, Reason} -> - terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug); + terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug, + Queue); _Msg when Debug == [] -> handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time, Limits, Queue, QueueLen); @@ -471,8 +473,9 @@ system_continue(Parent, Debug, [Name, StateName, StateData, Limits, Queue, QueueLen). system_terminate(Reason, _Parent, Debug, - [Name, StateName, StateData, Mod, _Time, _Limits]) -> - terminate(Reason, Name, [], Mod, StateName, StateData, Debug). + [Name, StateName, StateData, Mod, _Time, + _Limits, Queue, _QueueLen]) -> + terminate(Reason, Name, [], Mod, StateName, StateData, Debug, Queue). system_code_change([Name, StateName, StateData, Mod, Time, Limits, Queue, QueueLen], @@ -574,19 +577,25 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, _ -> normal end, - terminate(Reason, Name, Msg, Mod, StateName, NStateData, []); + Queue1 = + case Reason of + normal -> Queue; + _ -> queue:new() + end, + terminate(Reason, Name, Msg, Mod, StateName, NStateData, [], + Queue1); {stop, Reason, NStateData} -> - terminate(Reason, Name, Msg, Mod, StateName, NStateData, []); + terminate(Reason, Name, Msg, Mod, StateName, NStateData, [], Queue); {stop, Reason, Reply, NStateData} when From =/= undefined -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, - StateName, NStateData, [])), + StateName, NStateData, [], Queue)), reply(From, Reply), exit(R); {'EXIT', What} -> - terminate(What, Name, Msg, Mod, StateName, StateData, []); + terminate(What, Name, Msg, Mod, StateName, StateData, [], Queue); Reply -> terminate({bad_return_value, Reply}, - Name, Msg, Mod, StateName, StateData, []) + Name, Msg, Mod, StateName, StateData, [], Queue) end. handle_msg(Msg, Parent, Name, StateName, StateData, @@ -631,19 +640,27 @@ handle_msg(Msg, Parent, Name, StateName, StateData, _ -> normal end, - terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug); + Queue1 = + case Reason of + normal -> Queue; + _ -> queue:new() + end, + terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug, + Queue1); {stop, Reason, NStateData} -> - terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug); + terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug, + Queue); {stop, Reason, Reply, NStateData} when From =/= undefined -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, - StateName, NStateData, Debug)), + StateName, NStateData, Debug, + Queue)), reply(Name, From, Reply, Debug, StateName), exit(R); {'EXIT', What} -> - terminate(What, Name, Msg, Mod, StateName, StateData, Debug); + terminate(What, Name, Msg, Mod, StateName, StateData, Debug, Queue); Reply -> terminate({bad_return_value, Reply}, - Name, Msg, Mod, StateName, StateData, Debug) + Name, Msg, Mod, StateName, StateData, Debug, Queue) end. dispatch({'$gen_event', Event}, Mod, StateName, StateData) -> @@ -679,7 +696,10 @@ reply(Name, {To, Tag}, Reply, Debug, StateName) -> %%% Terminate the server. %%% --------------------------------------------------- -terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) -> +terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug, Queue) -> + lists:foreach( + fun(Message) -> self() ! Message end, + queue:to_list(Queue)), case catch Mod:terminate(Reason, StateName, StateData) of {'EXIT', R} -> error_info(Mod, R, Name, Msg, StateName, StateData, Debug), @@ -762,7 +782,8 @@ get_msg(Msg) -> Msg. %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> - [PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time]] = + [PDict, SysState, Parent, Debug, + [Name, StateName, StateData, Mod, _Time, _Limits, _Queue, _QueueLen]] = StatusData, NameTag = if is_pid(Name) -> pid_to_list(Name); @@ -835,7 +856,7 @@ rpc_call(Node, Mod, Fun, Args, Timeout) -> {Ref, Result} -> erlang:demonitor(MRef, [flush]), Result; - {'DOWN', MRef, _, _, Reason} -> + {'DOWN', MRef, _, _, noconnection = Reason} -> {badrpc, Reason} after Timeout -> erlang:demonitor(MRef, [flush]), diff --git a/src/p1_mnesia.erl b/src/p1_mnesia.erl index 7ff74ddb1..166de760a 100644 --- a/src/p1_mnesia.erl +++ b/src/p1_mnesia.erl @@ -10,7 +10,7 @@ %% under the License. %% %% The Initial Developer of the Original Code is ProcessOne. -%% Portions created by ProcessOne are Copyright 2006-2010, ProcessOne +%% Portions created by ProcessOne are Copyright 2006-2011, ProcessOne %% All Rights Reserved.'' -module(p1_mnesia). diff --git a/src/p1_prof.erl b/src/p1_prof.erl index a7edbe139..ff4e94fc0 100644 --- a/src/p1_prof.erl +++ b/src/p1_prof.erl @@ -6,7 +6,7 @@ %%% Created : 23 Jan 2010 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -156,10 +156,10 @@ queue(N) -> dump(N, lists:reverse(lists:ukeysort(1, all_pids(queue)))). memory(N) -> - dump(N, lists:reverse(lists:ukeysort(2, all_pids(memory)))). + dump(N, lists:reverse(lists:ukeysort(3, all_pids(memory)))). reds(N) -> - dump(N, lists:reverse(lists:ukeysort(3, all_pids(reductions)))). + dump(N, lists:reverse(lists:ukeysort(4, all_pids(reductions)))). trace(Pid) -> erlang:trace(Pid, true, [send, 'receive']), @@ -299,10 +299,17 @@ all_pids(Type) -> registered_name]) of [{_, Len}, {_, Memory}, {_, Reds}, {_, Dict}, {_, CurFun}, {_, RegName}] -> - if Type == queue andalso Len == 0 -> + IntQLen = case lists:keysearch('$internal_queue_len', 1, Dict) of + {value, {_, N}} -> + N; + _ -> + 0 + end, + if Type == queue andalso Len == 0 andalso IntQLen == 0 -> Acc; true -> - [{Len, Memory, Reds, Dict, CurFun, P, RegName}|Acc] + MaxLen = lists:max([Len, IntQLen]), + [{MaxLen, Len, Memory, Reds, Dict, CurFun, P, RegName}|Acc] end; _ -> Acc @@ -311,7 +318,7 @@ all_pids(Type) -> dump(N, Rs) -> lists:foreach( - fun({MsgQLen, Memory, Reds, Dict, CurFun, Pid, RegName}) -> + fun({_, MsgQLen, Memory, Reds, Dict, CurFun, Pid, RegName}) -> PidStr = pid_to_list(Pid), [_, Maj, Min] = string:tokens( string:substr( diff --git a/src/pam/epam.c b/src/pam/epam.c index f12fb025a..0669550db 100644 --- a/src/pam/epam.c +++ b/src/pam/epam.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff --git a/src/pam/epam.erl b/src/pam/epam.erl index 1914f9231..8aed2f23c 100644 --- a/src/pam/epam.erl +++ b/src/pam/epam.erl @@ -5,7 +5,7 @@ %%% Created : 5 Jul 2007 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/pg2_backport.erl b/src/pg2_backport.erl index f706e66a9..faa775524 100644 --- a/src/pg2_backport.erl +++ b/src/pg2_backport.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -207,9 +207,12 @@ terminate(_Reason, _S) -> store(List) -> _ = [(assure_group(Name) andalso - [join_group(Name, P) || P <- Members -- group_members(Name)]) || + store2(Name, Members)) || [Name, Members] <- List], ok. +store2(Name, Members) -> + [join_group(Name, P) || P <- Members -- group_members(Name)], + true. assure_group(Name) -> Key = {group, Name}, diff --git a/src/randoms.erl b/src/randoms.erl index 455ea3529..493c60ebe 100644 --- a/src/randoms.erl +++ b/src/randoms.erl @@ -5,7 +5,7 @@ %%% Created : 13 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/sha.erl b/src/sha.erl index 06dd3c2d1..5a2ed3f1d 100644 --- a/src/sha.erl +++ b/src/sha.erl @@ -5,7 +5,7 @@ %%% Created : 20 Dec 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/shaper.erl b/src/shaper.erl index 8450dfcc2..8e4c3817b 100644 --- a/src/shaper.erl +++ b/src/shaper.erl @@ -5,7 +5,7 @@ %%% Created : 9 Feb 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/stringprep/stringprep.erl b/src/stringprep/stringprep.erl index 7cf6c547d..9db6d2847 100644 --- a/src/stringprep/stringprep.erl +++ b/src/stringprep/stringprep.erl @@ -5,7 +5,7 @@ %%% Created : 16 Feb 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/stringprep/stringprep_drv.c b/src/stringprep/stringprep_drv.c index 7fce98c98..f4d98f842 100644 --- a/src/stringprep/stringprep_drv.c +++ b/src/stringprep/stringprep_drv.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff --git a/src/stringprep/stringprep_sup.erl b/src/stringprep/stringprep_sup.erl index 7c45ef471..e1acbe832 100644 --- a/src/stringprep/stringprep_sup.erl +++ b/src/stringprep/stringprep_sup.erl @@ -5,7 +5,7 @@ %%% Created : 29 Jun 2007 by Mickael Remond %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/stun/ejabberd_stun.erl b/src/stun/ejabberd_stun.erl index d2b31f1df..8052b6147 100644 --- a/src/stun/ejabberd_stun.erl +++ b/src/stun/ejabberd_stun.erl @@ -7,7 +7,7 @@ %%% Created : 8 Aug 2009 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/stun/stun.hrl b/src/stun/stun.hrl index 7091581ff..23a4cfa4d 100644 --- a/src/stun/stun.hrl +++ b/src/stun/stun.hrl @@ -5,7 +5,7 @@ %%% Created : 8 Aug 2009 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/stun/stun_codec.erl b/src/stun/stun_codec.erl index ef36c9038..844e03f13 100644 --- a/src/stun/stun_codec.erl +++ b/src/stun/stun_codec.erl @@ -5,7 +5,7 @@ %%% Created : 7 Aug 2009 by Evgeniy Khramtsov %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/tls/sha_drv.c b/src/tls/sha_drv.c index 8e6de3203..22426703f 100644 --- a/src/tls/sha_drv.c +++ b/src/tls/sha_drv.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as diff --git a/src/tls/tls.erl b/src/tls/tls.erl index 7c90569cd..967cf9249 100644 --- a/src/tls/tls.erl +++ b/src/tls/tls.erl @@ -5,7 +5,7 @@ %%% Created : 24 Jul 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/tls/tls_drv.c b/src/tls/tls_drv.c index ae8707625..56bb2c709 100644 --- a/src/tls/tls_drv.c +++ b/src/tls/tls_drv.c @@ -1,5 +1,5 @@ /* - * ejabberd, Copyright (C) 2002-2010 ProcessOne + * ejabberd, Copyright (C) 2002-2011 ProcessOne * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -376,7 +376,8 @@ static int tls_drv_control(ErlDrvData handle, SSL_set_bio(d->ssl, d->bio_read, d->bio_write); if (command == SET_CERTIFICATE_FILE_ACCEPT) { - SSL_set_options(d->ssl, SSL_OP_NO_TICKET); + SSL_set_options(d->ssl, SSL_OP_NO_SSLv2|SSL_OP_NO_TICKET|SSL_OP_ALL); + SSL_set_accept_state(d->ssl); } else { SSL_set_options(d->ssl, SSL_OP_NO_SSLv2|SSL_OP_NO_TICKET); @@ -390,6 +391,7 @@ static int tls_drv_control(ErlDrvData handle, break; case SET_DECRYPTED_OUTPUT: die_unless(d->ssl, "SSL not initialized"); + res = SSL_write(d->ssl, buf, len); if (res <= 0) { @@ -407,9 +409,8 @@ static int tls_drv_control(ErlDrvData handle, break; case GET_ENCRYPTED_OUTPUT: die_unless(d->ssl, "SSL not initialized"); - size = BUF_SIZE + 1; rlen = 1; - b = driver_alloc_binary(size); + b = driver_alloc_binary(rlen + BUF_SIZE); b->orig_bytes[0] = 0; while ((res = BIO_read(d->bio_write, b->orig_bytes + rlen, BUF_SIZE)) > 0) @@ -417,8 +418,7 @@ static int tls_drv_control(ErlDrvData handle, //printf("%d bytes of encrypted data read from state machine\r\n", res); rlen += res; - size += BUF_SIZE; - b = driver_realloc_binary(b, size); + b = driver_realloc_binary(b, rlen + BUF_SIZE); } b = driver_realloc_binary(b, rlen); *rbuf = (char *)b; @@ -430,37 +430,31 @@ static int tls_drv_control(ErlDrvData handle, if (res <= 0) die_unless(SSL_get_error(d->ssl, res) == SSL_ERROR_WANT_READ, "SSL_do_handshake failed"); - } else { - size = BUF_SIZE + 1; - rlen = 1; - b = driver_alloc_binary(size); - b->orig_bytes[0] = 0; - - while ((res = SSL_read(d->ssl, - b->orig_bytes + rlen, BUF_SIZE)) > 0) - { - //printf("%d bytes of decrypted data read from state machine\r\n",res); - rlen += res; - size += BUF_SIZE; - b = driver_realloc_binary(b, size); - } - - if (res < 0) - { - int err = SSL_get_error(d->ssl, res); - - if (err == SSL_ERROR_WANT_READ) - { - //printf("SSL_read wants more data\r\n"); - //return 0; - } - // TODO - } - b = driver_realloc_binary(b, rlen); - *rbuf = (char *)b; - return rlen; } - break; + rlen = 1; + b = driver_alloc_binary(rlen + BUF_SIZE); + b->orig_bytes[0] = 0; + + while ((res = SSL_read(d->ssl, + b->orig_bytes + rlen, BUF_SIZE)) > 0) + { + //printf("%d bytes of decrypted data read from state machine\r\n",res); + rlen += res; + b = driver_realloc_binary(b, rlen + BUF_SIZE); + } + + if (res < 0) + { + int err = SSL_get_error(d->ssl, res); + + if (err != SSL_ERROR_WANT_READ) + { + // TODO + } + } + b = driver_realloc_binary(b, rlen); + *rbuf = (char *)b; + return rlen; case GET_PEER_CERTIFICATE: cert = SSL_get_peer_certificate(d->ssl); if (cert == NULL) diff --git a/src/translate.erl b/src/translate.erl index 880889a3a..cc40e92ca 100644 --- a/src/translate.erl +++ b/src/translate.erl @@ -5,7 +5,7 @@ %%% Created : 6 Jan 2003 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/treap.erl b/src/treap.erl index a5ed9b93b..4c2fd35a6 100644 --- a/src/treap.erl +++ b/src/treap.erl @@ -5,7 +5,7 @@ %%% Created : 22 Apr 2008 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl index 1018269ab..fb38b17a4 100644 --- a/src/web/ejabberd_http.erl +++ b/src/web/ejabberd_http.erl @@ -5,7 +5,7 @@ %%% Created : 27 Feb 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -189,30 +189,38 @@ receive_headers(State) -> _ -> case Data of {ok, Binary} -> - {Request, Trail} = parse_request( - State, - State#state.trail ++ binary_to_list(Binary)), - State1 = State#state{trail = Trail}, - NewState = lists:foldl( - fun(D, S) -> - case S#state.end_of_request of - true -> - S; - _ -> - process_header(S, D) - end - end, State1, Request), - case NewState#state.end_of_request of - true -> - ok; - _ -> - receive_headers(NewState) - end; + process_requests(State, binary_to_list(Binary)); _ -> ok end end. +process_requests(State, Data) -> + {Request, Trail} = parse_request( + State, + State#state.trail ++ Data), + State1 = State#state{trail = Trail}, + NewState = lists:foldl( + fun(D, S) -> + case S#state.end_of_request of + true -> + S; + _ -> + process_header(S, D) + end + end, State1, Request), + case State1#state.trail of + [] -> + case NewState#state.end_of_request of + true -> + ok; + _ -> + receive_headers(NewState) + end; + _ -> + process_requests(State1, "") + end. + process_header(State, Data) -> SockMod = State#state.sockmod, Socket = State#state.socket, @@ -340,7 +348,6 @@ process(Handlers, #ws{} = Ws)-> case (lists:prefix(HandlerPathPrefix, Ws#ws.path) or (HandlerPathPrefix==Ws#ws.path)) of true -> - ?DEBUG("~p matches ~p", [Ws#ws.path, HandlerPathPrefix]), LocalPath = lists:nthtail(length(HandlerPathPrefix), Ws#ws.path), ejabberd_hooks:run(ws_debug, [{LocalPath, Ws}]), Protocol = case lists:keysearch(protocol, 1, HandlerOpts) of @@ -351,9 +358,14 @@ process(Handlers, #ws{} = Ws)-> {value, {origins, O}} -> O; false -> [] end, + Auth = case lists:keysearch(auth, 1, HandlerOpts) of + {value, {auth, A}} -> A; + false -> undefined + end, WS2 = Ws#ws{local_path = LocalPath, protocol=Protocol, - acceptable_origins=Origins}, + acceptable_origins=Origins, + auth_module=Auth}, case ejabberd_websocket:is_acceptable(WS2) of true -> ejabberd_websocket:connect(WS2, HandlerModule); @@ -361,7 +373,6 @@ process(Handlers, #ws{} = Ws)-> process(HandlersLeft, Ws) end; false -> - ?DEBUG("HandlersLeft : ~p ", [HandlersLeft]), process(HandlersLeft, Ws) end; process(Handlers, Request) -> @@ -425,9 +436,10 @@ process_request(#state{request_method = Method, {_, Origin} = lists:keyfind("Origin", 1, RequestHeaders), Ws = #ws{socket = Socket, sockmod = SockMod, - ws_autoexit = true, + ws_autoexit = false, ip = IP, path = LPath, + q = LQuery, vsn = VSN, host = Host, port = Port, @@ -435,7 +447,6 @@ process_request(#state{request_method = Method, headers = RequestHeaders }, process(WebSocketHandlers, Ws), - ?DEBUG("It is a websocket.",[]), none; false -> Request = #request{method = Method, @@ -568,7 +579,11 @@ recv_data(_State, 0, Acc) -> recv_data(State, Len, Acc) -> case State#state.trail of [] -> - case (State#state.sockmod):recv(State#state.socket, Len, 300000) of + Len2 = case State#state.sockmod of + gen_tcp -> Len; + _ -> 0 + end, + case (State#state.sockmod):recv(State#state.socket, Len2, 300000) of {ok, Data} -> recv_data(State, Len - size(Data), [Acc | Data]); _ -> @@ -621,7 +636,13 @@ make_xhtml_output(State, Status, Headers, XHTML) -> end, HeadersOut), SL = [Version, integer_to_list(Status), " ", code_to_phrase(Status), "\r\n"], - [SL, H, "\r\n", Data]. + + Data2 = case State#state.request_method of + 'HEAD' -> ""; + _ -> Data + end, + + [SL, H, "\r\n", Data2]. make_text_output(State, Status, Headers, Text) when is_list(Text) -> make_text_output(State, Status, Headers, list_to_binary(Text)); @@ -658,7 +679,13 @@ make_text_output(State, Status, Headers, Data) when is_binary(Data) -> end, HeadersOut), SL = [Version, integer_to_list(Status), " ", code_to_phrase(Status), "\r\n"], - [SL, H, "\r\n", Data]. + + Data2 = case State#state.request_method of + 'HEAD' -> ""; + _ -> Data + end, + + [SL, H, "\r\n", Data2]. parse_lang(Langs) -> diff --git a/src/web/ejabberd_http.hrl b/src/web/ejabberd_http.hrl index 89c2a55c4..960923af6 100644 --- a/src/web/ejabberd_http.hrl +++ b/src/web/ejabberd_http.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -47,6 +47,8 @@ path, % the websocket GET request path headers, % [{Tag, Val}] local_path, + q, protocol, - acceptable_origins - }). \ No newline at end of file + acceptable_origins = [], + auth_module + }). diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl index 9e53b8146..9689b388d 100644 --- a/src/web/ejabberd_http_bind.erl +++ b/src/web/ejabberd_http_bind.erl @@ -13,7 +13,7 @@ -behaviour(gen_fsm). %% External exports --export([start_link/3, +-export([start_link/4, init/1, handle_event/3, handle_sync_event/4, @@ -70,6 +70,7 @@ wait_timer, ctime = 0, timer, + jid, pause=0, unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID} req_list = [], % list of requests (cache) @@ -124,7 +125,7 @@ start(XMPPDomain, Sid, Key, IP) -> ?DEBUG("Starting session", []), SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME_MHB), - case catch supervisor:start_child(SupervisorProc, [Sid, Key, IP]) of + case catch supervisor:start_child(SupervisorProc, [XMPPDomain, Sid, Key, IP]) of {ok, Pid} -> {ok, Pid}; {error, _} = Err -> @@ -140,8 +141,8 @@ start(XMPPDomain, Sid, Key, IP) -> {error, Exit} end. -start_link(Sid, Key, IP) -> - gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS). +start_link(ServerHost, Sid, Key, IP) -> + gen_fsm:start_link(?MODULE, [ServerHost, Sid, Key, IP], ?FSMOPTS). send({http_bind, FsmRef, _IP}, Packet) -> gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). @@ -333,7 +334,7 @@ handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([Sid, Key, IP]) -> +init([ServerHost, Sid, Key, IP]) -> ?DEBUG("started: ~p", [{Sid, Key, IP}]), %% Read c2s options from the first ejabberd_c2s configuration in @@ -348,15 +349,25 @@ init([Sid, Key, IP]) -> Shaper = none, ShaperState = shaper:new(Shaper), Socket = {http_bind, self(), IP}, - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), - {ok, loop, #state{id = Sid, - key = Key, - socket = Socket, - shaper_state = ShaperState, - max_inactivity = ?MAX_INACTIVITY, - max_pause = ?MAX_PAUSE, - timer = Timer}}. + State = #state{id = Sid, + key = Key, + socket = Socket, + shaper_state = ShaperState, + max_inactivity = ?MAX_INACTIVITY, + max_pause = ?MAX_PAUSE, + timer = Timer}, + case gen_mod:get_module_opt(ServerHost, mod_http_bind, + prebind, false) of + true -> + JID = make_random_jid(ServerHost), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, + [{jid, JID} | Opts]), + {ok, loop, State#state{jid = JID}}; + false -> + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + {ok, loop, State} + end. %%---------------------------------------------------------------------- %% Func: handle_event/3 @@ -469,6 +480,11 @@ handle_sync_event(#http_put{payload_size = PayloadSize} = Request, shaper_timer = NewShaperTimer}); %% HTTP GET: send packets to the client +handle_sync_event({http_get, _Rid, _Wait, _Hold}, _From, + StateName, #state{jid = JID} = StateData) + when JID /= undefined -> + %% This is a pre-bind state + {reply, {ok, {prebind, JID}}, StateName, StateData#state{jid = undefined}}; handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> %% setup timer send_receiver_reply(StateData#state.http_receiver, {ok, empty}), @@ -745,15 +761,16 @@ process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload, ip = IP }); C2SPid -> + JID = StateData#state.jid, case StreamTo of - {To, ""} -> + {To, ""} when JID == undefined -> gen_fsm:send_event( C2SPid, {xmlstreamstart, "stream:stream", [{"to", To}, {"xmlns", ?NS_CLIENT}, {"xmlns:stream", ?NS_STREAM}]}); - {To, Version} -> + {To, Version} when JID == undefined -> gen_fsm:send_event( C2SPid, {xmlstreamstart, "stream:stream", @@ -950,6 +967,16 @@ prepare_response(Sess, Rid, OutputEls, StreamStart) -> {200, ?HEADER, ""}; {ok, terminate} -> {200, ?HEADER, ""}; + {ok, {prebind, JID}} -> + {200, ?HEADER, + xml:element_to_string( + {xmlelement, "body", + [{"xmlns", ?NS_HTTP_BIND}, {"sid", Sess#http_bind.id}, + {"rid", integer_to_list(Rid + 1)}], + [{xmlelement, "iq", [{"id", "pre_bind"}, {"type", "result"}], + [{xmlelement, "bind", [{"xmlns", ?NS_BIND}], + [{xmlelement, "jid", [], + [{xmlcdata, jlib:jid_to_string(JID)}]}]}]}]})}; {ok, ROutPacket} -> OutPacket = lists:reverse(ROutPacket), ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]), @@ -1271,10 +1298,14 @@ check_default_xmlns(El) -> %% Print a warning in log file if this is not the case. check_bind_module(XmppDomain) -> case gen_mod:is_loaded(XmppDomain, mod_http_bind) of - true -> true; - false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), but the module mod_http_bind is not started.~n" - "Check your 'modules' section in your ejabberd configuration file.",[]), - false + true -> ok; + false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) in host ~p," + " but the module mod_http_bind is not started in" + " that host. Configure your BOSH client to connect" + " to the correct host, or add your desired host to" + " the configuration, or check your 'modules'" + " section in your ejabberd configuration file.", + [XmppDomain]) end. make_sid() -> @@ -1304,3 +1335,8 @@ get_session(SID) -> _ -> {error, enoent} end. + +make_random_jid(Host) -> + %% Copied from cyrsasl_anonymous.erl + User = lists:concat([randoms:get_string() | tuple_to_list(now())]), + jlib:make_jid(User, Host, randoms:get_string()). diff --git a/src/web/ejabberd_http_bindjson.erl b/src/web/ejabberd_http_bindjson.erl new file mode 100644 index 000000000..61f8779e8 --- /dev/null +++ b/src/web/ejabberd_http_bindjson.erl @@ -0,0 +1,1294 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_http_bindjson.erl +%%% Original Bind Author : Stefan Strigler +%%% Purpose : Implements XMPP over BOSH (XEP-0205) with a JSON Transport +%%% Created : 23 Sep 2010 by Eric Cestari +%%% Modified: may 2009 by Mickael Remond, Alexey Schepin +%%% Id : $Id: ejabberd_http_bind.erl 953 2009-05-07 10:40:40Z alexey $ +%%%---------------------------------------------------------------------- + +-module (ejabberd_http_bindjson). + +-behaviour(gen_fsm). + +%% External exports +-export([start_link/3, + init/1, + handle_event/3, + handle_sync_event/4, + code_change/4, + handle_info/3, + terminate/3, + send/2, + send_xml/2, + sockname/1, + peername/1, + setopts/2, + controlling_process/2, + become_controller/2, + change_controller/2, + custom_receiver/1, + reset_stream/1, + change_shaper/2, + monitor/1, + close/1, + start/4, + handle_session_start/8, + handle_http_put/7, + http_put/7, + http_get/2, + prepare_response/4, + process_request/2]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("ejabberd_http.hrl"). +-include("http_bind.hrl"). + +-record(http_bind, {id, pid, to, hold, wait, process_delay, version}). + +-define(NULL_PEER, {{0, 0, 0, 0}, 0}). + +%% http binding request +-record(hbr, {rid, + key, + out}). + +-record(state, {id, + rid = none, + key, + socket, + output = "", + input = queue:new(), + waiting_input = false, + shaper_state, + shaper_timer, + last_receiver, + last_poll, + http_receiver, + wait_timer, + ctime = 0, + timer, + pause=0, + unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID} + req_list = [], % list of requests (cache) + max_inactivity, + max_pause, + ip = ?NULL_PEER + }). + +%% Internal request format: +-record(http_put, {rid, + attrs, + payload, + payload_size, + hold, + stream, + ip}). + +%%-define(DBGFSM, true). +-ifdef(DBGFSM). +-define(FSMOPTS, [{debug, [trace]}]). +-else. +-define(FSMOPTS, []). +-endif. + +-define(BOSH_VERSION, "1.8"). +-define(NS_CLIENT, "jabber:client"). +-define(NS_BOSH, "urn:xmpp:xbosh"). +-define(NS_HTTP_BIND, "http://jabber.org/protocol/httpbind"). + +-define(MAX_REQUESTS, 2). % number of simultaneous requests +-define(MIN_POLLING, 2000000). % don't poll faster than that or we will + % shoot you (time in microsec) +-define(MAX_WAIT, 3600). % max num of secs to keep a request on hold +-define(MAX_INACTIVITY, 30000). % msecs to wait before terminating + % idle sessions +-define(MAX_PAUSE, 120). % may num of sec a client is allowed to pause + % the session + +%% Wait 100ms before continue processing, to allow the client provide more related stanzas. +-define(PROCESS_DELAY_DEFAULT, 100). +-define(PROCESS_DELAY_MIN, 0). +-define(PROCESS_DELAY_MAX, 1000). + + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +%% TODO: If compile with no supervisor option, start the session without +%% supervisor +start(XMPPDomain, Sid, Key, IP) -> + ?DEBUG("Starting session", []), + case catch supervisor:start_child(ejabberd_http_bind_sup, [Sid, Key, IP]) of + {ok, Pid} -> + {ok, Pid}; + {error, _} = Err -> + case check_bind_module(XMPPDomain) of + false -> + {error, "Cannot start HTTP bind session"}; + true -> + ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Err]), + Err + end; + Exit -> + ?ERROR_MSG("Cannot start HTTP bind session: ~p", [Exit]), + {error, Exit} + end. + +start_link(Sid, Key, IP) -> + gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS). + +send({http_bind, FsmRef, _IP}, Packet) -> + gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + +send_xml({http_bind, FsmRef, _IP}, Packet) -> + gen_fsm:sync_send_all_state_event(FsmRef, {send_xml, Packet}). + +setopts({http_bind, FsmRef, _IP}, Opts) -> + case lists:member({active, once}, Opts) of + true -> + gen_fsm:send_all_state_event(FsmRef, {activate, self()}); + _ -> + case lists:member({active, false}, Opts) of + true -> + gen_fsm:sync_send_all_state_event( + FsmRef, deactivate_socket); + _ -> + ok + end + end. + +controlling_process(_Socket, _Pid) -> + ok. + +custom_receiver({http_bind, FsmRef, _IP}) -> + {receiver, ?MODULE, FsmRef}. + +become_controller(FsmRef, C2SPid) -> + gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). + +change_controller({http_bind, FsmRef, _IP}, C2SPid) -> + become_controller(FsmRef, C2SPid). + +reset_stream({http_bind, _FsmRef, _IP}) -> + ok. + +change_shaper({http_bind, FsmRef, _IP}, Shaper) -> + gen_fsm:send_all_state_event(FsmRef, {change_shaper, Shaper}). + +monitor({http_bind, FsmRef, _IP}) -> + erlang:monitor(process, FsmRef). + +close({http_bind, FsmRef, _IP}) -> + catch gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}). + +sockname(_Socket) -> + {ok, ?NULL_PEER}. + +peername({http_bind, _FsmRef, IP}) -> + {ok, IP}. + +%% Entry point for data coming from client through ejabberd HTTP server: +process_request(Data, IP) -> + Opts1 = ejabberd_c2s_config:get_c2s_limits(), + Opts = [{xml_socket, true} | Opts1], + MaxStanzaSize = + case lists:keysearch(max_stanza_size, 1, Opts) of + {value, {_, Size}} -> Size; + _ -> infinity + end, + PayloadSize = iolist_size(Data), + case catch parse_request(Data, PayloadSize, MaxStanzaSize) of + %% No existing session: + {ok, {"", Rid, Attrs, Payload}} -> + case xml:get_attr_s("to",Attrs) of + "" -> + ?DEBUG("Session not created (Improper addressing)", []), + {200, ?HEADER, "{\"body\":{\"type\":\"terminate\" " + "\"condition\":\"improper-addressing\", " + "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\"}}"}; + XmppDomain -> + %% create new session + Sid = make_sid(), + case start(XmppDomain, Sid, "", IP) of + {error, _} -> + {500, ?HEADER,"{\"body\":{\"type\":\"terminate\" " + "\"condition\":\"internal-server-error\", " + "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\",\"$\":\"Internal Server Error\"}}"}; + {ok, Pid} -> + handle_session_start( + Pid, XmppDomain, Sid, Rid, Attrs, + Payload, PayloadSize, IP) + end + end; + %% Existing session + {ok, {Sid, Rid, Attrs, Payload1}} -> + StreamStart = + case xml:get_attr_s("xmpp:restart",Attrs) of + "true" -> + true; + _ -> + false + end, + Payload2 = case xml:get_attr_s("type",Attrs) of + "terminate" -> + %% close stream + Payload1 ++ [{xmlstreamend, "stream:stream"}]; + _ -> + Payload1 + end, + handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize, + StreamStart, IP); + {size_limit, Sid} -> + case get_session(Sid) of + {error, _} -> + {404, ?HEADER, ""}; + {ok, #http_bind{pid = FsmRef}} -> + gen_fsm:sync_send_all_state_event(FsmRef, {stop, close}), + {200, ?HEADER, "{\"body\": {\"type\"=\"terminate\" " + "\"condition\":\"undefined-condition\" " + "\"xmlns\":\"" ++ ?NS_HTTP_BIND ++ "\", \"$\":\"Request Too Large\"}}"} + end; + _ -> + ?DEBUG("Received bad request: ~p", [Data]), + {400, ?HEADER, ""} + end. + +handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, + Payload, PayloadSize, IP) -> + ?DEBUG("got pid: ~p", [Pid]), + Wait = case string:to_integer(xml:get_attr_s("wait",Attrs)) of + {error, _} -> + ?MAX_WAIT; + {CWait, _} -> + if + (CWait > ?MAX_WAIT) -> + ?MAX_WAIT; + true -> + CWait + end + end, + Hold = case string:to_integer(xml:get_attr_s("hold",Attrs)) of + {error, _} -> + (?MAX_REQUESTS - 1); + {CHold, _} -> + if + (CHold > (?MAX_REQUESTS - 1)) -> + (?MAX_REQUESTS - 1); + true -> + CHold + end + end, + Pdelay = case string:to_integer(xml:get_attr_s("process-delay",Attrs)) of + {error, _} -> + ?PROCESS_DELAY_DEFAULT; + {CPdelay, _} when + (?PROCESS_DELAY_MIN =< CPdelay) and + (CPdelay =< ?PROCESS_DELAY_MAX) -> + CPdelay; + {CPdelay, _} -> + erlang:max( + erlang:min(CPdelay,?PROCESS_DELAY_MAX), + ?PROCESS_DELAY_MIN) + end, + Version = + case catch list_to_float( + xml:get_attr_s("ver", Attrs)) of + {'EXIT', _} -> 0.0; + V -> V + end, + XmppVersion = xml:get_attr_s("xmpp:version", Attrs), + ?DEBUG("Create session: ~p", [Sid]), + mnesia:async_dirty( + fun() -> + mnesia:write( + #http_bind{id = Sid, + pid = Pid, + to = {XmppDomain, + XmppVersion}, + hold = Hold, + wait = Wait, + process_delay = Pdelay, + version = Version + }) + end), + handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_fsm +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, StateName, StateData} | +%% {ok, StateName, StateData, Timeout} | +%% ignore | +%% {stop, StopReason} +%%---------------------------------------------------------------------- +init([Sid, Key, IP]) -> + ?DEBUG("started: ~p", [{Sid, Key, IP}]), + + %% Read c2s options from the first ejabberd_c2s configuration in + %% the config file listen section + %% TODO: We should have different access and shaper values for + %% each connector. The default behaviour should be however to use + %% the default c2s restrictions if not defined for the current + %% connector. + Opts1 = ejabberd_c2s_config:get_c2s_limits(), + Opts = [{xml_socket, true} | Opts1], + + Shaper = none, + ShaperState = shaper:new(Shaper), + Socket = {http_bind, self(), IP}, + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), + {ok, loop, #state{id = Sid, + key = Key, + socket = Socket, + shaper_state = ShaperState, + max_inactivity = ?MAX_INACTIVITY, + max_pause = ?MAX_PAUSE, + timer = Timer}}. + +%%---------------------------------------------------------------------- +%% Func: handle_event/3 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} +%%---------------------------------------------------------------------- +handle_event({become_controller, C2SPid}, StateName, StateData) -> + erlang:monitor(process, C2SPid), + case StateData#state.input of + cancel -> + {next_state, StateName, StateData#state{ + waiting_input = C2SPid}}; + Input -> + lists:foreach( + fun(Event) -> + C2SPid ! Event + end, queue:to_list(Input)), + {next_state, StateName, StateData#state{ + input = queue:new(), + waiting_input = C2SPid}} + end; + +handle_event({change_shaper, Shaper}, StateName, StateData) -> + NewShaperState = shaper:new(Shaper), + {next_state, StateName, StateData#state{shaper_state = NewShaperState}}; +handle_event(_Event, StateName, StateData) -> + {next_state, StateName, StateData}. + +%%---------------------------------------------------------------------- +%% Func: handle_sync_event/4 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {reply, Reply, NextStateName, NextStateData} | +%% {reply, Reply, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} | +%% {stop, Reason, Reply, NewStateData} +%%---------------------------------------------------------------------- +handle_sync_event({send_xml, Packet}, _From, StateName, + #state{http_receiver = undefined} = StateData) -> + Output = [Packet | StateData#state.output], + Reply = ok, + {reply, Reply, StateName, StateData#state{output = Output}}; +handle_sync_event({send_xml, Packet}, _From, StateName, StateData) -> + Output = [Packet | StateData#state.output], + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + HTTPReply = {ok, Output}, + gen_fsm:reply(StateData#state.http_receiver, HTTPReply), + cancel_timer(StateData#state.wait_timer), + Rid = StateData#state.rid, + ReqList = [#hbr{rid = Rid, + key = StateData#state.key, + out = Output + } | + [El || El <- StateData#state.req_list, + El#hbr.rid /= Rid ] + ], + Reply = ok, + {reply, Reply, StateName, + StateData#state{output = [], + http_receiver = undefined, + req_list = ReqList, + wait_timer = undefined, + timer = Timer}}; + +handle_sync_event({stop,close}, _From, _StateName, StateData) -> + Reply = ok, + {stop, normal, Reply, StateData}; +handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) -> + Reply = ok, + {stop, normal, Reply, StateData}; +handle_sync_event(deactivate_socket, _From, StateName, StateData) -> + %% Input = case StateData#state.input of + %% cancel -> + %% queue:new(); + %% Q -> + %% Q + %% end, + {reply, ok, StateName, StateData#state{waiting_input = false}}; +handle_sync_event({stop,Reason}, _From, _StateName, StateData) -> + ?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]), + Reply = ok, + {stop, normal, Reply, StateData}; + +%% HTTP PUT: Receive packets from the client +handle_sync_event(#http_put{rid = Rid}, + _From, StateName, StateData) + when StateData#state.shaper_timer /= undefined -> + Pause = + case erlang:read_timer(StateData#state.shaper_timer) of + false -> + 0; + P -> P + end, + Reply = {wait, Pause}, + ?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]), + {reply, Reply, StateName, StateData}; + +handle_sync_event(#http_put{payload_size = PayloadSize} = Request, + _From, StateName, StateData) -> + ?DEBUG("New request: ~p",[Request]), + %% Updating trafic shaper + {NewShaperState, NewShaperTimer} = + update_shaper(StateData#state.shaper_state, PayloadSize), + + handle_http_put_event(Request, StateName, + StateData#state{shaper_state = NewShaperState, + shaper_timer = NewShaperTimer}); + +%% HTTP GET: send packets to the client +handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> + %% setup timer + send_receiver_reply(StateData#state.http_receiver, {ok, empty}), + cancel_timer(StateData#state.wait_timer), + TNow = tnow(), + if + (Hold > 0) and + (StateData#state.output == []) and + ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and + (StateData#state.rid == Rid) and + (StateData#state.input /= cancel) and + (StateData#state.pause == 0) -> + WaitTimer = erlang:start_timer(Wait * 1000, self(), []), + %% MR: Not sure we should cancel the state timer here. + cancel_timer(StateData#state.timer), + {next_state, StateName, StateData#state{ + http_receiver = From, + wait_timer = WaitTimer, + timer = undefined}}; + (StateData#state.input == cancel) -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + Reply = {ok, cancel}, + {reply, Reply, StateName, StateData#state{ + input = queue:new(), + http_receiver = undefined, + wait_timer = undefined, + timer = Timer}}; + true -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + Reply = {ok, StateData#state.output}, + %% save request + ReqList = [#hbr{rid = Rid, + key = StateData#state.key, + out = StateData#state.output + } | + [El || El <- StateData#state.req_list, + El#hbr.rid /= Rid ] + ], + {reply, Reply, StateName, StateData#state{ + output = [], + http_receiver = undefined, + wait_timer = undefined, + timer = Timer, + req_list = ReqList}} + end; + +handle_sync_event(peername, _From, StateName, StateData) -> + Reply = {ok, StateData#state.ip}, + {reply, Reply, StateName, StateData}; + +handle_sync_event(_Event, _From, StateName, StateData) -> + Reply = ok, + {reply, Reply, StateName, StateData}. + +code_change(_OldVsn, StateName, StateData, _Extra) -> + {ok, StateName, StateData}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/3 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} +%%---------------------------------------------------------------------- +%% We reached the max_inactivity timeout: +handle_info({timeout, Timer, _}, _StateName, + #state{id=SID, timer = Timer} = StateData) -> + ?INFO_MSG("Session timeout. Closing the HTTP bind session: ~p", [SID]), + {stop, normal, StateData}; + +handle_info({timeout, WaitTimer, _}, StateName, + #state{wait_timer = WaitTimer} = StateData) -> + if + StateData#state.http_receiver /= undefined -> + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(StateData#state.pause, + StateData#state.max_inactivity), + gen_fsm:reply(StateData#state.http_receiver, {ok, empty}), + Rid = StateData#state.rid, + ReqList = [#hbr{rid = Rid, + key = StateData#state.key, + out = [] + } | + [El || El <- StateData#state.req_list, + El#hbr.rid /= Rid ] + ], + {next_state, StateName, + StateData#state{http_receiver = undefined, + req_list = ReqList, + wait_timer = undefined, + timer = Timer}}; + true -> + {next_state, StateName, StateData} + end; + +handle_info({timeout, ShaperTimer, _}, StateName, + #state{shaper_timer = ShaperTimer} = StateData) -> + {next_state, StateName, StateData#state{shaper_timer = undefined}}; + +handle_info({'DOWN', _MRef, process, C2SPid, _}, _StateName, + #state{waiting_input = C2SPid} = StateData) -> + {stop, normal, StateData}; +handle_info(_, StateName, StateData) -> + {next_state, StateName, StateData}. + +%%---------------------------------------------------------------------- +%% Func: terminate/3 +%% Purpose: Shutdown the fsm +%% Returns: any +%%---------------------------------------------------------------------- +terminate(_Reason, _StateName, StateData) -> + ?DEBUG("terminate: Deleting session ~s", [StateData#state.id]), + mnesia:dirty_delete({http_bind, StateData#state.id}), + send_receiver_reply(StateData#state.http_receiver, {ok, terminate}), + case StateData#state.waiting_input of + false -> + ok; + C2SPid -> + gen_fsm:send_event(C2SPid, closed) + end, + ok. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +%% PUT / Get processing: +handle_http_put_event(#http_put{rid = Rid, attrs = Attrs, + hold = Hold} = Request, + StateName, StateData) -> + ?DEBUG("New request: ~p",[Request]), + %% Check if Rid valid + RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, Hold, + StateData#state.max_pause), + + %% Check if Rid is in sequence or out of sequence: + case RidAllow of + buffer -> + ?DEBUG("Buffered request: ~p", [Request]), + %% Request is out of sequence: + PendingRequests = StateData#state.unprocessed_req_list, + %% In case an existing RID was already buffered: + Requests = lists:keydelete(Rid, 2, PendingRequests), + ReqList = [#hbr{rid = Rid, + key = StateData#state.key, + out = [] + } | + [El || El <- StateData#state.req_list, + El#hbr.rid > (Rid - 1 - Hold)] + ], + ?DEBUG("reqlist: ~p", [ReqList]), + UnprocessedReqList = [Request | Requests], + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(0, StateData#state.max_inactivity), + {reply, buffered, StateName, + StateData#state{unprocessed_req_list = UnprocessedReqList, + req_list = ReqList, + timer = Timer}}; + _ -> + %% Request is in sequence: + process_http_put(Request, StateName, StateData, RidAllow) + end. + +process_http_put(#http_put{rid = Rid, attrs = Attrs, payload = Payload, + hold = Hold, stream = StreamTo, + ip = IP} = Request, + StateName, StateData, RidAllow) -> + ?DEBUG("Actually processing request: ~p", [Request]), + %% Check if key valid + Key = xml:get_attr_s("key", Attrs), + NewKey = xml:get_attr_s("newkey", Attrs), + KeyAllow = + case RidAllow of + repeat -> + true; + false -> + false; + {true, _} -> + case StateData#state.key of + "" -> + true; + OldKey -> + NextKey = sha:sha(Key), + ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", [Key, OldKey, NextKey]), + if + OldKey == NextKey -> + true; + true -> + ?DEBUG("wrong key: ~s",[Key]), + false + end + end + end, + TNow = tnow(), + LastPoll = if + Payload == [] -> + TNow; + true -> + 0 + end, + if + (Payload == []) and + (Hold == 0) and + (TNow - StateData#state.last_poll < ?MIN_POLLING) -> + Reply = {error, polling_too_frequently}, + {reply, Reply, StateName, StateData}; + KeyAllow -> + case RidAllow of + false -> + Reply = {error, not_exists}, + {reply, Reply, StateName, StateData}; + repeat -> + ?DEBUG("REPEATING ~p", [Rid]), + Reply = case [El#hbr.out || + El <- StateData#state.req_list, + El#hbr.rid == Rid] of + [] -> + {error, not_exists}; + [Out | _XS] -> + {repeat, lists:reverse(Out)} + end, + {reply, Reply, StateName, StateData#state{input = cancel, + last_poll = LastPoll}}; + {true, Pause} -> + SaveKey = if + NewKey == "" -> + Key; + true -> + NewKey + end, + ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), + + %% save request + ReqList1 = + [El || El <- StateData#state.req_list, + El#hbr.rid > (Rid - 1 - Hold)], + ReqList = + case lists:keymember(Rid, #hbr.rid, ReqList1) of + true -> + ReqList1; + false -> + [#hbr{rid = Rid, + key = StateData#state.key, + out = [] + } | + ReqList1 + ] + end, + ?DEBUG("reqlist: ~p", [ReqList]), + + %% setup next timer + cancel_timer(StateData#state.timer), + Timer = set_inactivity_timer(Pause, + StateData#state.max_inactivity), + case StateData#state.waiting_input of + false -> + Input = + lists:foldl( + fun queue:in/2, + StateData#state.input, Payload), + Reply = ok, + process_buffered_request(Reply, StateName, + StateData#state{input = Input, + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + pause = Pause, + last_poll = LastPoll, + req_list = ReqList, + ip = IP + }); + C2SPid -> + case StreamTo of + {To, ""} -> + gen_fsm:send_event( + C2SPid, + {xmlstreamstart, "stream:stream", + [{"to", To}, + {"xmlns", ?NS_CLIENT}, + {"xmlns:stream", ?NS_STREAM}]}); + {To, Version} -> + gen_fsm:send_event( + C2SPid, + {xmlstreamstart, "stream:stream", + [{"to", To}, + {"xmlns", ?NS_CLIENT}, + {"version", Version}, + {"xmlns:stream", ?NS_STREAM}]}); + _ -> + ok + end, + + MaxInactivity = get_max_inactivity(StreamTo, StateData#state.max_inactivity), + MaxPause = get_max_inactivity(StreamTo, StateData#state.max_pause), + + ?DEBUG("really sending now: ~p", [Payload]), + lists:foreach( + fun({xmlstreamend, End}) -> + gen_fsm:send_event( + C2SPid, {xmlstreamend, End}); + (El) -> + gen_fsm:send_event( + C2SPid, {xmlstreamelement, El}) + end, Payload), + Reply = ok, + process_buffered_request(Reply, StateName, + StateData#state{input = queue:new(), + rid = Rid, + key = SaveKey, + ctime = TNow, + timer = Timer, + pause = Pause, + last_poll = LastPoll, + req_list = ReqList, + max_inactivity = MaxInactivity, + max_pause = MaxPause, + ip = IP + }) + end + end; + true -> + Reply = {error, bad_key}, + {reply, Reply, StateName, StateData} + end. + +process_buffered_request(Reply, StateName, StateData) -> + Rid = StateData#state.rid, + Requests = StateData#state.unprocessed_req_list, + case lists:keysearch(Rid+1, 2, Requests) of + {value, Request} -> + ?DEBUG("Processing buffered request: ~p", [Request]), + NewRequests = lists:keydelete(Rid+1, 2, Requests), + handle_http_put_event( + Request, StateName, + StateData#state{unprocessed_req_list = NewRequests}); + _ -> + {reply, Reply, StateName, StateData, hibernate} + end. + +handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> + case http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) of + {error, not_exists} -> + ?DEBUG("no session associated with sid: ~p", [Sid]), + {404, ?HEADER, ""}; + {{error, Reason}, Sess} -> + ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]), + handle_http_put_error(Reason, Sess); + {{repeat, OutPacket}, Sess} -> + ?DEBUG("http_put said \"repeat!\" ...~nOutPacket: ~p", [OutPacket]), + send_outpacket(Sess, OutPacket); + {{wait, Pause}, _Sess} -> + ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), + timer:sleep(Pause), + %{200, ?HEADER, + % xmpp_json:to_json( + % {xmlelement, "body", + % [{"xmlns", ?NS_HTTP_BIND}, + % {"type", "error"}], []})}; + handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, + StreamStart, IP); + {buffered, _Sess} -> + {200, ?HEADER, "{\"body\":{ \"xmlns\":\""++?NS_HTTP_BIND++"\"}}"}; + {ok, Sess} -> + prepare_response(Sess, Rid, [], StreamStart) + end. + +http_put(Sid, Rid, Attrs, Payload, PayloadSize, StreamStart, IP) -> + ?DEBUG("Looking for session: ~p", [Sid]), + case get_session(Sid) of + {error, _} -> + {error, not_exists}; + {ok, #http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess}-> + NewStream = + case StreamStart of + true -> + {To, StreamVersion}; + _ -> + "" + end, + {gen_fsm:sync_send_all_state_event( + FsmRef, #http_put{rid = Rid, attrs = Attrs, payload = Payload, + payload_size = PayloadSize, hold = Hold, + stream = NewStream, ip = IP}, 30000), Sess} + end. + +handle_http_put_error(Reason, #http_bind{pid=FsmRef, version=Version}) + when Version >= 0 -> + gen_fsm:sync_send_all_state_event(FsmRef, {stop, {put_error,Reason}}), + case Reason of + not_exists -> + {200, ?HEADER, + mochijson2:encode( + xmpp_json:to_json( + {xmlelement, "body", + [{"xmlns", ?NS_HTTP_BIND}, + {"type", "terminate"}, + {"condition", "item-not-found"}], []}))}; + bad_key -> + {200, ?HEADER, + mochijson2:encode( + xmpp_json:to_json( + {xmlelement, "body", + [{"xmlns", ?NS_HTTP_BIND}, + {"type", "terminate"}, + {"condition", "item-not-found"}], []}))}; + polling_too_frequently -> + {200, ?HEADER, + mochijson2:encode( + xmpp_json:to_json( + {xmlelement, "body", + [{"xmlns", ?NS_HTTP_BIND}, + {"type", "terminate"}, + {"condition", "policy-violation"}], []}))} + end; +handle_http_put_error(Reason, #http_bind{pid=FsmRef}) -> + gen_fsm:sync_send_all_state_event(FsmRef,{stop, {put_error_no_version, Reason}}), + case Reason of + not_exists -> %% bad rid + ?DEBUG("Closing HTTP bind session (Bad rid).", []), + {404, ?HEADER, ""}; + bad_key -> + ?DEBUG("Closing HTTP bind session (Bad key).", []), + {404, ?HEADER, ""}; + polling_too_frequently -> + ?DEBUG("Closing HTTP bind session (User polling too frequently).", []), + {403, ?HEADER, ""} + end. + +%% Control RID ordering +rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) -> + %% First request - nothing saved so far + {true, 0}; +rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) -> + ?DEBUG("Previous rid / New rid: ~p/~p", [OldRid, NewRid]), + if + %% We did not miss any packet, we can process it immediately: + NewRid == OldRid + 1 -> + case catch list_to_integer( + xml:get_attr_s("pause", Attrs)) of + {'EXIT', _} -> + {true, 0}; + Pause1 when Pause1 =< MaxPause -> + ?DEBUG("got pause: ~p", [Pause1]), + {true, Pause1}; + _ -> + {true, 0} + end; + %% We have missed packets, we need to cached it to process it later on: + (OldRid < NewRid) and + (NewRid =< (OldRid + Hold + 1)) -> + buffer; + (NewRid =< OldRid) and + (NewRid > OldRid - Hold - 1) -> + repeat; + true -> + false + end. + +update_shaper(ShaperState, PayloadSize) -> + {NewShaperState, Pause} = shaper:update(ShaperState, PayloadSize), + if + Pause > 0 -> + ShaperTimer = erlang:start_timer(Pause, self(), activate), %% MR: Seems timer is not needed. Activate is not handled + {NewShaperState, ShaperTimer}; + true -> + {NewShaperState, undefined} + end. + +prepare_response(Sess, Rid, OutputEls, StreamStart) -> + receive after Sess#http_bind.process_delay -> ok end, + case catch http_get(Sess, Rid) of + {ok, cancel} -> + %% actually it would be better if we could completely + %% cancel this request, but then we would have to hack + %% ejabberd_http and I'm too lazy now + {200, ?HEADER, "{\"body\": {\"type\":\"error\", \"xmlns\":\""++?NS_HTTP_BIND++"\"/>"}; + {ok, empty} -> + {200, ?HEADER, "{\"body\":{ \"xmlns\":\""++?NS_HTTP_BIND++"\"}}"}; + {ok, terminate} -> + {200, ?HEADER, "{\"body\": {\"type\":\"terminate\", \"xmlns\":\""++?NS_HTTP_BIND++"\"/>"}; + {ok, ROutPacket} -> + OutPacket = lists:reverse(ROutPacket), + ?DEBUG("OutPacket: ~p", [OutputEls++OutPacket]), + prepare_outpacket_response(Sess, Rid, OutputEls++OutPacket, StreamStart); + {'EXIT', {shutdown, _}} -> + {200, ?HEADER, "{\"body\": {\"type\":\"terminate\",\"condition\":\"system-shutdown\", \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"}; + {'EXIT', _Reason} -> + {200, ?HEADER, "{\"body\": {\"type\":\"terminate, \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"} + end. + +%% Send output payloads on establised sessions +prepare_outpacket_response(Sess, _Rid, OutPacket, false) -> + case catch send_outpacket(Sess, OutPacket) of + {'EXIT', _Reason} -> + {200, ?HEADER, + "{\"body\": {\"type\":\"terminate\", \"xmlns\":\""++ ?NS_HTTP_BIND++"\"}}"}; + SendRes -> + SendRes + end; +%% Handle a new session along with its output payload +prepare_outpacket_response(#http_bind{id=Sid, wait=Wait, + hold=Hold, to=To}=Sess, + Rid, OutPacket, true) -> + case OutPacket of + [{xmlstreamstart, _, OutAttrs} | Els] -> + AuthID = xml:get_attr_s("id", OutAttrs), + From = xml:get_attr_s("from", OutAttrs), + Version = xml:get_attr_s("version", OutAttrs), + OutEls = + case Els of + [] -> + []; + [{xmlstreamelement, + {xmlelement, "stream:features", + StreamAttribs, StreamEls}} + | StreamTail] -> + TypedTail = + [check_default_xmlns(OEl) || + {xmlstreamelement, OEl} <- + StreamTail], + [{xmlelement, "stream:features", + [{"xmlns:stream", + ?NS_STREAM}] ++ + StreamAttribs, StreamEls}] ++ + TypedTail; + StreamTail -> + [check_default_xmlns(OEl) || + {xmlstreamelement, OEl} <- + StreamTail] + end, + case OutEls of + [] -> + prepare_response(Sess, Rid, OutPacket, true); + [{xmlelement, + "stream:error",_,_}] -> + {200, ?HEADER, "{\"body\" : {\"type\":\"terminate\", " + "\"condition\":\"host-unknown\", " + "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"}; + _ -> + BOSH_attribs = + [{"authid", AuthID}, + {"xmlns:xmpp", ?NS_BOSH}, + {"xmlns:stream", ?NS_STREAM}] ++ + case OutEls of + [] -> + []; + _ -> + [{"xmpp:version", Version}] + end, + MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), + MaxPause = get_max_pause(To), + {200, ?HEADER, + mochijson2:encode( + xmpp_json:to_json( + {xmlelement,"body", + [{"xmlns", + ?NS_HTTP_BIND}, + {"sid", Sid}, + {"wait", integer_to_list(Wait)}, + {"requests", integer_to_list(Hold+1)}, + {"inactivity", + integer_to_list( + trunc(MaxInactivity/1000))}, + {"maxpause", + integer_to_list(MaxPause)}, + {"polling", + integer_to_list( + trunc(?MIN_POLLING/1000000))}, + {"ver", ?BOSH_VERSION}, + {"from", From}, + {"secure", "true"} %% we're always being secure + ] ++ BOSH_attribs,OutEls}))} + end; + _ -> + {200, ?HEADER, "{\"body\" : {\"type\":\"terminate\", " + "\"condition\":\"internal-server-error\", " + "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"} + end. + + +http_get(#http_bind{pid = FsmRef, wait = Wait, hold = Hold}, Rid) -> + gen_fsm:sync_send_all_state_event( + FsmRef, {http_get, Rid, Wait, Hold}, 2 * ?MAX_WAIT * 1000). + +send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> + case OutPacket of + [] -> + {200, ?HEADER, "{\"body\": {\"xmlns\":\""++?NS_HTTP_BIND++"\"}}"}; + [{xmlstreamend, _}] -> + gen_fsm:sync_send_all_state_event(FsmRef,{stop,stream_closed}), + {200, ?HEADER, "{\"body\": {\"xmlns\":"++?NS_HTTP_BIND++"\"}}"}; + _ -> + %% TODO: We parse to add a default namespace to packet, + %% The spec says adding the jabber:client namespace if + %% mandatory, even if some implementation do not do that + %% change on packets. + %% I think this should be an option to avoid modifying + %% packet in most case. + AllElements = + lists:all(fun({xmlstreamelement, + {xmlelement, "stream:error", _, _}}) -> false; + ({xmlstreamelement, _}) -> true; + (_) -> false + end, OutPacket), + case AllElements of + true -> + TypedEls = [check_default_xmlns(OEl) || + {xmlstreamelement, OEl} <- OutPacket], + Body = mochijson2:encode(xmpp_json:to_json( + {xmlelement,"body", + [{"xmlns", + ?NS_HTTP_BIND}], + TypedEls})), + ?DEBUG(" --- outgoing data --- ~n~s~n --- END --- ~n", + [Body]), + {200, ?HEADER, Body}; + false -> + case OutPacket of + [{xmlstreamstart, _, _} | SEls] -> + OutEls = + case SEls of + [{xmlstreamelement, + {xmlelement, + "stream:features", + StreamAttribs, StreamEls}} | + StreamTail] -> + TypedTail = + [check_default_xmlns(OEl) || + {xmlstreamelement, OEl} <- + StreamTail], + [{xmlelement, + "stream:features", + [{"xmlns:stream", + ?NS_STREAM}] ++ + StreamAttribs, StreamEls}] ++ + TypedTail; + StreamTail -> + [check_default_xmlns(OEl) || + {xmlstreamelement, OEl} <- + StreamTail] + end, + {200, ?HEADER, + mochijson2:encode( + xmpp_json:to_json( + {xmlelement,"body", + [{"xmlns", + ?NS_HTTP_BIND}], + OutEls}))}; + _ -> + SErrCond = + lists:filter( + fun({xmlstreamelement, + {xmlelement, "stream:error", + _, _}}) -> + true; + (_) -> false + end, OutPacket), + StreamErrCond = + case SErrCond of + [] -> + null; + [{xmlstreamelement, + {xmlelement, _, _, _Cond} = + StreamErrorTag} | _] -> + [StreamErrorTag] + end, + gen_fsm:sync_send_all_state_event(FsmRef, + {stop, {stream_error,OutPacket}}), + case StreamErrCond of + null -> + {200, ?HEADER, + "{\"body\" : {\"\"type\"\":\"terminate\", " + "\"condition\":\"internal-server-error\", " + "\"xmlns\"=\""++?NS_HTTP_BIND++"\"}}"}; + _ -> + {200, ?HEADER, + "{\"body\" : {\"\"type\"\":\"terminate\", " + "\"condition\":\"remote-stream-error\", " + "\"xmlns\":\""++?NS_HTTP_BIND++"\", " ++ + "\"xmlns:stream\":\""++?NS_STREAM++"\" \"$\":" ++ + elements_to_string(StreamErrCond) ++ + "}}"} + end + end + end + end. + +parse_request(Data, PayloadSize, MaxStanzaSize) -> + ?DEBUG("--- incoming data --- ~n~p~n --- END --- ", [xmpp_json:from_json(mochijson2:decode(Data))]), + %% MR: I do not think it works if put put several elements in the + %% same body: + case xmpp_json:from_json(mochijson2:decode(Data)) of + {xmlstreamelement,{xmlelement, "body", Attrs, Els}} -> + Xmlns = xml:get_attr_s("xmlns",Attrs), + if + Xmlns /= ?NS_HTTP_BIND -> + {error, bad_request}; + true -> + case catch list_to_integer(xml:get_attr_s("rid", Attrs)) of + {'EXIT', _} -> + {error, bad_request}; + Rid -> + %% I guess this is to remove XMLCDATA: Is it really needed ? + FixedEls = + lists:filter( + fun(I) -> + case I of + {xmlelement, _, _, _} -> + true; + _ -> + false + end + end, Els), + Sid = xml:get_attr_s("sid",Attrs), + if + PayloadSize =< MaxStanzaSize -> + {ok, {Sid, Rid, Attrs, FixedEls}}; + true -> + {size_limit, Sid} + end + end + end; + {xmlstreamelement,{xmlelement, _Name, _Attrs, _Els}} -> + {error, bad_request}; + {error, _Reason} -> + {error, bad_request} + end. + +send_receiver_reply(undefined, _Reply) -> + ok; +send_receiver_reply(Receiver, Reply) -> + gen_fsm:reply(Receiver, Reply). + + +%% Cancel timer and empty message queue. +cancel_timer(undefined) -> + ok; +cancel_timer(Timer) -> + erlang:cancel_timer(Timer), + receive + {timeout, Timer, _} -> + ok + after 0 -> + ok + end. + +%% If client asked for a pause (pause > 0), we apply the pause value +%% as inactivity timer: +set_inactivity_timer(Pause, _MaxInactivity) when Pause > 0 -> + erlang:start_timer(Pause*1000, self(), []); +%% Otherwise, we apply the max_inactivity value as inactivity timer: +set_inactivity_timer(_Pause, MaxInactivity) -> + erlang:start_timer(MaxInactivity, self(), []). + + +%% TODO: Use tail recursion and list reverse ? +elements_to_string([]) -> + []; +elements_to_string([El | Els]) -> + [mochijson2:encode(xmpp_json:to_json(El))|elements_to_string(Els)]. + +%% @spec (To, Default::integer()) -> integer() +%% where To = [] | {Host::string(), Version::string()} +get_max_inactivity({Host, _}, Default) -> + case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, undefined) of + Seconds when is_integer(Seconds) -> + Seconds * 1000; + undefined -> + Default + end; +get_max_inactivity(_, Default) -> + Default. + +get_max_pause({Host, _}) -> + gen_mod:get_module_opt(Host, mod_http_bind, max_pause, ?MAX_PAUSE); +get_max_pause(_) -> + ?MAX_PAUSE. + +%% Current time as integer +tnow() -> + {TMegSec, TSec, TMSec} = now(), + (TMegSec * 1000000 + TSec) * 1000000 + TMSec. + +check_default_xmlns({xmlelement, Name, Attrs, Els} = El) -> + case xml:get_tag_attr_s("xmlns", El) of + "" -> {xmlelement, Name, [{"xmlns", ?NS_CLIENT} | Attrs], Els}; + _ -> El + end. + +%% Check that mod_http_bind has been defined in config file. +%% Print a warning in log file if this is not the case. +check_bind_module(XmppDomain) -> + case gen_mod:is_loaded(XmppDomain, mod_http_bind) of + true -> true; + false -> ?ERROR_MSG("You are trying to use BOSH (HTTP Bind), but the module mod_http_bind is not started.~n" + "Check your 'modules' section in your ejabberd configuration file.",[]), + false + end. + +make_sid() -> + sha:sha(term_to_binary({now(), make_ref()})) + ++ "-" ++ ejabberd_cluster:node_id(). + +get_session(SID) -> + case string:tokens(SID, "-") of + [_, NodeID] -> + case ejabberd_cluster:get_node_by_id(NodeID) of + Node when Node == node() -> + case mnesia:dirty_read({http_bind, SID}) of + [] -> + {error, enoent}; + [Session] -> + {ok, Session} + end; + Node -> + case catch rpc:call(Node, mnesia, dirty_read, + [{http_bind, SID}], 5000) of + [Session] -> + {ok, Session}; + _ -> + {error, enoent} + end + end; + _ -> + {error, enoent} + end. diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl index f7550b099..bd30e89a2 100644 --- a/src/web/ejabberd_http_poll.erl +++ b/src/web/ejabberd_http_poll.erl @@ -5,7 +5,7 @@ %%% Created : 4 Mar 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/web/ejabberd_http_ws.erl b/src/web/ejabberd_http_ws.erl index f8ffb3aef..f79c41ce1 100644 --- a/src/web/ejabberd_http_ws.erl +++ b/src/web/ejabberd_http_ws.erl @@ -155,21 +155,25 @@ handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) true -> list_to_binary(Packet) end, - ?DEBUG("sending on websocket : ~p ", [Packet2]), + %?DEBUG("sending on websocket : ~p ", [Packet2]), WS:send(Packet2), {reply, ok, StateName, StateData}; - -handle_sync_event(close, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}. + +handle_sync_event(close, From, _StateName, StateData)-> + {stop, normal, StateData}. + +handle_info(closed, _StateName, StateData) -> + {stop, normal, StateData}; handle_info({browser, Packet}, StateName, StateData)-> + %?DEBUG("Received on websocket : ~p ", [Packet]), + NPacket = unicode:characters_to_binary(Packet,latin1), NewState = case StateData#state.waiting_input of false -> - Input = [StateData#state.input|Packet], + Input = [StateData#state.input|NPacket], StateData#state{input = Input}; {Receiver, _Tag} -> - Receiver ! {tcp, StateData#state.socket,Packet}, + Receiver ! {tcp, StateData#state.socket,NPacket}, cancel_timer(StateData#state.timer), Timer = erlang:start_timer(StateData#state.timeout, self(), []), StateData#state{waiting_input = false, @@ -190,7 +194,15 @@ handle_info(_, StateName, StateData) -> code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. -terminate(_Reason, _StateName, _StateData) -> ok. +terminate(_Reason, _StateName, StateData) -> + case StateData#state.waiting_input of + false -> + ok; + {Receiver,_} -> + ?DEBUG("C2S Pid : ~p", [Receiver]), + Receiver ! {tcp_closed, StateData#state.socket } + end, + ok. cancel_timer(Timer) -> erlang:cancel_timer(Timer), @@ -199,4 +211,4 @@ cancel_timer(Timer) -> ok after 0 -> ok - end. + end. \ No newline at end of file diff --git a/src/web/ejabberd_http_wsjson.erl b/src/web/ejabberd_http_wsjson.erl new file mode 100644 index 000000000..2abd12b3b --- /dev/null +++ b/src/web/ejabberd_http_wsjson.erl @@ -0,0 +1,219 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_websocket.erl +%%% Author : Eric Cestari +%%% Purpose : JSON - XMPP Websocket module support +%%% Created : 09-10-2010 by Eric Cestari +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License +%%% along with this program; if not, write to the Free Software +%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +%%% 02111-1307 USA +%%% +%%%---------------------------------------------------------------------- + +-module (ejabberd_http_wsjson). +-author('ecestari@process-one.net'). + +-behaviour(gen_fsm). + +% External exports +-export([ + start/1, + start_link/1, + init/1, + handle_event/3, + handle_sync_event/4, + code_change/4, + handle_info/3, + terminate/3, + send_xml/2, + setopts/2, + sockname/1, peername/1, + controlling_process/2, + become_controller/2, + close/1]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("ejabberd_http.hrl"). + +-record(state, { + socket, + timeout, + timer, + input = [], + waiting_input = false, %% {ReceiverPid, Tag} + last_receiver, + ws}). + +%-define(DBGFSM, true). + +-ifdef(DBGFSM). +-define(FSMOPTS, [{debug, [trace]}]). +-else. +-define(FSMOPTS, []). +-endif. + +-define(WEBSOCKET_TIMEOUT, 300000). +% +% +%%%%---------------------------------------------------------------------- +%%%% API +%%%%---------------------------------------------------------------------- +start(WS) -> + supervisor:start_child(ejabberd_wsloop_sup, [WS]). + +start_link(WS) -> + gen_fsm:start_link(?MODULE, [WS],?FSMOPTS). + +send_xml({http_ws, FsmRef, _IP}, Packet) -> + gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}). + +setopts({http_ws, FsmRef, _IP}, Opts) -> + case lists:member({active, once}, Opts) of + true -> + gen_fsm:send_all_state_event(FsmRef, {activate, self()}); + _ -> + ok + end. + +sockname(_Socket) -> + {ok, {{0, 0, 0, 0}, 0}}. + +peername({http_ws, _FsmRef, IP}) -> + {ok, IP}. + +controlling_process(_Socket, _Pid) -> + ok. + +become_controller(FsmRef, C2SPid) -> + gen_fsm:send_all_state_event(FsmRef, {become_controller, C2SPid}). + +close({http_ws, FsmRef, _IP}) -> + catch gen_fsm:sync_send_all_state_event(FsmRef, close). + +%%% Internal + + +init([WS]) -> + %% Read c2s options from the first ejabberd_c2s configuration in + %% the config file listen section + %% TODO: We should have different access and shaper values for + %% each connector. The default behaviour should be however to use + %% the default c2s restrictions if not defined for the current + %% connector. + Opts = [{xml_socket, true}|ejabberd_c2s_config:get_c2s_limits()], + + WSTimeout = case ejabberd_config:get_local_option({websocket_timeout, + ?MYNAME}) of + %% convert seconds of option into milliseconds + Int when is_integer(Int) -> Int*1000; + undefined -> ?WEBSOCKET_TIMEOUT + end, + + Socket = {http_ws, self(), WS:get(ip)}, + ?DEBUG("Client connected through websocket ~p", [Socket]), + ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), + Timer = erlang:start_timer(WSTimeout, self(), []), + {ok, loop, #state{ + socket = Socket, + timeout = WSTimeout, + timer = Timer, + ws = WS}}. + +handle_event({activate, From}, StateName, StateData) -> + case StateData#state.input of + [] -> + {next_state, StateName, + StateData#state{waiting_input = {From, ok}}}; + Input -> + Receiver = From, + lists:reverse(lists:map(fun(Packet)-> + Receiver ! {tcp, StateData#state.socket, [Packet]} + end, Input)), + {next_state, StateName, StateData#state{input = "", + waiting_input = false, + last_receiver = Receiver + }} + end. + +handle_sync_event({send, Packet}, _From, StateName, #state{ws = WS} = StateData) -> + EJson = xmpp_json:to_json(Packet), + Json = mochijson2:encode(EJson), + WS:send(iolist_to_binary(Json)), + {reply, ok, StateName, StateData}; + +handle_sync_event(close, _From, _StateName, StateData) -> + Reply = ok, + {stop, normal, Reply, StateData}. + +handle_info({browser, <<"\n">>}, StateName, StateData)-> + NewState = case StateData#state.waiting_input of + false -> + ok; + {Receiver, _Tag} -> + Receiver ! {tcp, StateData#state.socket,<<"\n">>}, + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer(StateData#state.timeout, self(), []), + StateData#state{waiting_input = false, + last_receiver = Receiver, + timer = Timer} + end, + {next_state, StateName, NewState}; +handle_info({browser, JsonPacket}, StateName, StateData)-> + NewState = case StateData#state.waiting_input of + false -> + EJson = mochijson2:decode(JsonPacket), + Packet = xmpp_json:from_json(EJson), + Input = [Packet | StateData#state.input], + StateData#state{input = Input}; + {Receiver, _Tag} -> + %?DEBUG("Received from browser : ~p", [JsonPacket]), + EJson = mochijson2:decode(JsonPacket), + %?DEBUG("decoded : ~p", [EJson]), + Packet = xmpp_json:from_json(EJson), + %?DEBUG("sending to c2s : ~p", [Packet]), + Receiver ! {tcp, StateData#state.socket,[Packet]}, + cancel_timer(StateData#state.timer), + Timer = erlang:start_timer(StateData#state.timeout, self(), []), + StateData#state{waiting_input = false, + last_receiver = Receiver, + timer = Timer} + end, + {next_state, StateName, NewState}; + + +handle_info({timeout, Timer, _}, _StateName, + #state{timer = Timer} = StateData) -> + {stop, normal, StateData}; + +handle_info(_, StateName, StateData) -> + {next_state, StateName, StateData}. + + +code_change(_OldVsn, StateName, StateData, _Extra) -> + {ok, StateName, StateData}. + +terminate(_Reason, _StateName, _StateData) -> ok. + +cancel_timer(Timer) -> + erlang:cancel_timer(Timer), + receive + {timeout, Timer, _} -> + ok + after 0 -> + ok + end. diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl index 35140e5cf..29e4f0896 100644 --- a/src/web/ejabberd_web.erl +++ b/src/web/ejabberd_web.erl @@ -5,7 +5,7 @@ %%% Created : 28 Feb 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index f374cd3cc..a5503afe9 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -5,7 +5,7 @@ %%% Created : 9 Apr 2004 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -64,11 +64,15 @@ get_acl_rule(["additions.js"],_) -> {"localhost", [all]}; get_acl_rule(["vhosts"],_) -> {"localhost", [all]}; %% The pages of a vhost are only accesible if the user is admin of that vhost: -get_acl_rule(["server", VHost | _RPath], 'GET') -> {VHost, [configure, webadmin_view]}; +get_acl_rule(["server", VHost | _RPath], Method) + when Method=:='GET' orelse Method=:='HEAD' -> + {VHost, [configure, webadmin_view]}; get_acl_rule(["server", VHost | _RPath], 'POST') -> {VHost, [configure]}; %% Default rule: only global admins can access any other random page -get_acl_rule(_RPath, 'GET') -> {global, [configure, webadmin_view]}; +get_acl_rule(_RPath, Method) + when Method=:='GET' orelse Method=:='HEAD' -> + {global, [configure, webadmin_view]}; get_acl_rule(_RPath, 'POST') -> {global, [configure]}. is_acl_match(Host, Rules, Jid) -> @@ -314,7 +318,7 @@ make_xhtml(Els, Host, Node, Lang, JID) -> [?XAE("div", [{"id", "copyright"}], [?XC("p", - "ejabberd (c) 2002-2010 ProcessOne") + "ejabberd (c) 2002-2011 ProcessOne") ])])]) ]}}. @@ -1028,7 +1032,7 @@ process_admin(global, auth = {_, _Auth, AJID}, lang = Lang}) -> Res = list_vhosts(Lang, AJID), - make_xhtml(?H1GL(?T("ejabberd virtual hosts"), "virtualhost", "Virtual Hosting") ++ Res, global, Lang, AJID); + make_xhtml(?H1GL(?T("Virtual Hosts"), "virtualhost", "Virtual Hosting") ++ Res, global, Lang, AJID); process_admin(Host, #request{path = ["users"], @@ -2026,7 +2030,7 @@ get_node(global, Node, ["backup"], Query, Lang) -> ok -> [?XREST("Submitted")]; {error, Error} -> [?XRES(?T("Error") ++": " ++ io_lib:format("~p", [Error]))] end, - [?XC("h1", ?T("Backup of ") ++ atom_to_list(Node))] ++ + ?H1GL(?T("Backup of ") ++ atom_to_list(Node), "list-eja-commands", "List of ejabberd Commands") ++ ResS ++ [?XCT("p", "Please note that these options will only backup the builtin Mnesia database. If you are using the ODBC module, you also need to backup your SQL database separately."), ?XAE("form", [{"action", ""}, {"method", "post"}], diff --git a/src/web/ejabberd_web_admin.hrl b/src/web/ejabberd_web_admin.hrl index 1ab4fcc29..49fa47b38 100644 --- a/src/web/ejabberd_web_admin.hrl +++ b/src/web/ejabberd_web_admin.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/web/ejabberd_websocket.erl b/src/web/ejabberd_websocket.erl index 2b4d41292..adb6b571c 100644 --- a/src/web/ejabberd_websocket.erl +++ b/src/web/ejabberd_websocket.erl @@ -53,29 +53,30 @@ check(_Path, Headers)-> % If origins are set in configuration, check if it belongs % If origins not set, access is open. is_acceptable(#ws{origin=Origin, protocol=Protocol, - headers = Headers, acceptable_origins = Origins})-> + headers = Headers, acceptable_origins = Origins, auth_module=undefined})-> ClientProtocol = lists:keyfind("Sec-WebSocket-Protocol",1, Headers), - case {(Origin == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of + case {(Origins == []) or lists:member(Origin, Origins), ClientProtocol, Protocol } of {false, _, _} -> - ?DEBUG("client does not come from authorized origin", []), + ?INFO_MSG("client does not come from authorized origin", []), false; {_, false, _} -> - ?DEBUG("Client did not ask for protocol", []), true; {_, {_, P}, P} -> - ?DEBUG("Protocoles are matching", []), true; - _ -> false - end. - + _ = E-> + ?INFO_MSG("Wrong protocol requested : ~p", [E]), + false + end; +is_acceptable(#ws{local_path=LocalPath, origin=Origin, ip=IP, q=Q, protocol=Protocol, headers = Headers,auth_module=Module})-> + Module:is_acceptable(LocalPath, Q, Origin, Protocol, IP, Headers). % Connect and handshake with Websocket. -connect(#ws{vsn = Vsn, socket = Socket, origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) -> +connect(#ws{vsn = Vsn, socket = Socket, q=Q,origin=Origin, host=Host, port=Port, sockmod = SockMod, path = Path, headers = Headers, ws_autoexit = WsAutoExit} = Ws, WsLoop) -> % build handshake - HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Origin, Host, Port}), + HandshakeServer = handshake(Vsn, Socket,SockMod, Headers, {Path, Q, Origin, Host, Port}), % send handshake back - %?DEBUG("building handshake response : ~p", [HandshakeServer]), SockMod:send(Socket, HandshakeServer), + ?DEBUG("Sent handshake response : ~p", [HandshakeServer]), Ws0 = ejabberd_ws:new(Ws#ws{origin = Origin, host = Host}, self()), %?DEBUG("Ws0 : ~p",[Ws0]), % add data to ws record and spawn controlling process @@ -155,7 +156,7 @@ check_headers(Headers, RequiredHeaders) -> % Function: List % Description: Builds the server handshake response. -handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Origin, Host, Port}) -> +handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Q,Origin, Host, Port}) -> % build data {_, Key1} = lists:keyfind("Sec-Websocket-Key1",1, Headers), {_, Key2} = lists:keyfind("Sec-Websocket-Key2",1, Headers), @@ -175,6 +176,16 @@ handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Origin, Host, Port ?ERROR_MSG("tcp error treating data: ~p", [_Other]), <<>> end, + QParams = lists:map( + fun({nokey,[]})-> + none; + ({K, V})-> + K ++ "=" ++ V + end, Q), + QString = case QParams of + [none]-> ""; + QParams-> "?" ++ string:join(QParams, "&") + end, %?DEBUG("got content in body of websocket request: ~p, ~p", [Body,string:join([Host, Path],"/")]), % prepare handhsake response ["HTTP/1.1 101 WebSocket Protocol Handshake\r\n", @@ -183,7 +194,7 @@ handshake({'draft-hixie', 0}, Sock,SocketMod, Headers, {Path, Origin, Host, Port "Sec-WebSocket-Origin: ", Origin, "\r\n", "Sec-WebSocket-Location: ws://", string:join([Host, integer_to_list(Port)],":"), - "/",string:join(Path,"/") , "\r\n\r\n", + "/",string:join(Path,"/"),QString, "\r\n\r\n", build_challenge({'draft-hixie', 0}, {Key1, Key2, Body}) ]; handshake({'draft-hixie', 68}, _Sock,_SocketMod, _Headers, {Path, Origin, Host, Port}) -> @@ -214,6 +225,7 @@ build_challenge({'draft-hixie', 0}, {Key1, Key2, Key3}) -> ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit) -> receive {tcp, Socket, Data} -> + %?ERROR_MSG("[WS recv] ~p~n[Buffer state] ~p", [Data, Buffer]), handle_data(Buffer, Data, Socket, WsHandleLoopPid, SocketMode, WsAutoExit); {tcp_closed, Socket} -> ?DEBUG("tcp connection was closed, exit", []), @@ -232,7 +244,7 @@ ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit) -> % close websocket and custom controlling loop websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit); {send, Data} -> - ?DEBUG("sending data to websocket: ~p", [Data]), + %?DEBUG("sending data to websocket: ~p", [Data]), SocketMode:send(Socket, iolist_to_binary([0,Data,255])), ws_loop(Socket, Buffer, WsHandleLoopPid, SocketMode, WsAutoExit); shutdown -> @@ -257,7 +269,10 @@ handle_data(L, <<255,T/binary>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit handle_data(L, <>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) -> handle_data(iolist_to_binary([L, H]), T, Socket, WsHandleLoopPid, SocketMode, WsAutoExit); - + +handle_data(L, <<>>, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) -> + ws_loop(Socket, L, WsHandleLoopPid, SocketMode, WsAutoExit); + handle_data(<<>>, L, Socket, WsHandleLoopPid, SocketMode, WsAutoExit) -> ws_loop(Socket, L, WsHandleLoopPid, SocketMode, WsAutoExit). diff --git a/src/web/http_bind.hrl b/src/web/http_bind.hrl index e8f25f394..b34644099 100644 --- a/src/web/http_bind.hrl +++ b/src/web/http_bind.hrl @@ -1,6 +1,6 @@ %%%---------------------------------------------------------------------- %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/web/mochijson2.erl b/src/web/mochijson2.erl new file mode 100644 index 000000000..710ae9bce --- /dev/null +++ b/src/web/mochijson2.erl @@ -0,0 +1,782 @@ +%% @author Bob Ippolito +%% @copyright 2007 Mochi Media, Inc. + +%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works +%% with binaries as strings, arrays as lists (without an {array, _}) +%% wrapper and it only knows how to decode UTF-8 (and ASCII). + +-module(mochijson2). +-author('bob@mochimedia.com'). +-export([encoder/1, encode/1]). +-export([decoder/1, decode/1]). + +% This is a macro to placate syntax highlighters.. +-define(Q, $\"). +-define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset, + column=N+S#decoder.column}). +-define(INC_COL(S), S#decoder{offset=1+S#decoder.offset, + column=1+S#decoder.column}). +-define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset, + column=1, + line=1+S#decoder.line}). +-define(INC_CHAR(S, C), + case C of + $\n -> + S#decoder{column=1, + line=1+S#decoder.line, + offset=1+S#decoder.offset}; + _ -> + S#decoder{column=1+S#decoder.column, + offset=1+S#decoder.offset} + end). +-define(IS_WHITESPACE(C), + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). + +%% @type iolist() = [char() | binary() | iolist()] +%% @type iodata() = iolist() | binary() +%% @type json_string() = atom | binary() +%% @type json_number() = integer() | float() +%% @type json_array() = [json_term()] +%% @type json_object() = {struct, [{json_string(), json_term()}]} +%% @type json_iolist() = {json, iolist()} +%% @type json_term() = json_string() | json_number() | json_array() | +%% json_object() | json_iolist() + +-record(encoder, {handler=null, + utf8=false}). + +-record(decoder, {object_hook=null, + offset=0, + line=1, + column=1, + state=null}). + +%% @spec encoder([encoder_option()]) -> function() +%% @doc Create an encoder/1 with the given options. +%% @type encoder_option() = handler_option() | utf8_option() +%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false) +encoder(Options) -> + State = parse_encoder_options(Options, #encoder{}), + fun (O) -> json_encode(O, State) end. + +%% @spec encode(json_term()) -> iolist() +%% @doc Encode the given as JSON to an iolist. +encode(Any) -> + json_encode(Any, #encoder{}). + +%% @spec decoder([decoder_option()]) -> function() +%% @doc Create a decoder/1 with the given options. +decoder(Options) -> + State = parse_decoder_options(Options, #decoder{}), + fun (O) -> json_decode(O, State) end. + +%% @spec decode(iolist()) -> json_term() +%% @doc Decode the given iolist to Erlang terms. +decode(S) -> + json_decode(S, #decoder{}). + +%% Internal API + +parse_encoder_options([], State) -> + State; +parse_encoder_options([{handler, Handler} | Rest], State) -> + parse_encoder_options(Rest, State#encoder{handler=Handler}); +parse_encoder_options([{utf8, Switch} | Rest], State) -> + parse_encoder_options(Rest, State#encoder{utf8=Switch}). + +parse_decoder_options([], State) -> + State; +parse_decoder_options([{object_hook, Hook} | Rest], State) -> + parse_decoder_options(Rest, State#decoder{object_hook=Hook}). + +json_encode(true, _State) -> + <<"true">>; +json_encode(false, _State) -> + <<"false">>; +json_encode(null, _State) -> + <<"null">>; +json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 -> + %% Anything outside of 32-bit integers should be encoded as a float + integer_to_list(I); +json_encode(I, _State) when is_integer(I) -> + mochinum:digits(float(I)); +json_encode(F, _State) when is_float(F) -> + mochinum:digits(F); +json_encode(S, State) when is_binary(S); is_atom(S) -> + json_encode_string(S, State); +json_encode(Array, State) when is_list(Array) -> + json_encode_array(Array, State); +json_encode({struct, Props}, State) when is_list(Props) -> + json_encode_proplist(Props, State); +json_encode({json, IoList}, _State) -> + IoList; +json_encode(Bad, #encoder{handler=null}) -> + exit({json_encode, {bad_term, Bad}}); +json_encode(Bad, State=#encoder{handler=Handler}) -> + json_encode(Handler(Bad), State). + +json_encode_array([], _State) -> + <<"[]">>; +json_encode_array(L, State) -> + F = fun (O, Acc) -> + [$,, json_encode(O, State) | Acc] + end, + [$, | Acc1] = lists:foldl(F, "[", L), + lists:reverse([$\] | Acc1]). + +json_encode_proplist([], _State) -> + <<"{}">>; +json_encode_proplist(Props, State) -> + F = fun ({K, V}, Acc) -> + KS = json_encode_string(K, State), + VS = json_encode(V, State), + [$,, VS, $:, KS | Acc] + end, + [$, | Acc1] = lists:foldl(F, "{", Props), + lists:reverse([$\} | Acc1]). + +json_encode_string(A, State) when is_atom(A) -> + L = atom_to_list(A), + case json_string_is_safe(L) of + true -> + [?Q, L, ?Q]; + false -> + json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q]) + end; +json_encode_string(B, State) when is_binary(B) -> + case json_bin_is_safe(B) of + true -> + [?Q, B, ?Q]; + false -> + json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q]) + end; +json_encode_string(I, _State) when is_integer(I) -> + [?Q, integer_to_list(I), ?Q]; +json_encode_string(L, State) when is_list(L) -> + case json_string_is_safe(L) of + true -> + [?Q, L, ?Q]; + false -> + json_encode_string_unicode(L, State, [?Q]) + end. + +json_string_is_safe([]) -> + true; +json_string_is_safe([C | Rest]) -> + case C of + ?Q -> + false; + $\\ -> + false; + $\b -> + false; + $\f -> + false; + $\n -> + false; + $\r -> + false; + $\t -> + false; + C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> + false; + C when C < 16#7f -> + json_string_is_safe(Rest); + _ -> + false + end. + +json_bin_is_safe(<<>>) -> + true; +json_bin_is_safe(<>) -> + case C of + ?Q -> + false; + $\\ -> + false; + $\b -> + false; + $\f -> + false; + $\n -> + false; + $\r -> + false; + $\t -> + false; + C when C >= 0, C < $\s; C >= 16#7f -> + false; + C when C < 16#7f -> + json_bin_is_safe(Rest) + end. + +json_encode_string_unicode([], _State, Acc) -> + lists:reverse([$\" | Acc]); +json_encode_string_unicode([C | Cs], State, Acc) -> + Acc1 = case C of + ?Q -> + [?Q, $\\ | Acc]; + %% Escaping solidus is only useful when trying to protect + %% against "" injection attacks which are only + %% possible when JSON is inserted into a HTML document + %% in-line. mochijson2 does not protect you from this, so + %% if you do insert directly into HTML then you need to + %% uncomment the following case or escape the output of encode. + %% + %% $/ -> + %% [$/, $\\ | Acc]; + %% + $\\ -> + [$\\, $\\ | Acc]; + $\b -> + [$b, $\\ | Acc]; + $\f -> + [$f, $\\ | Acc]; + $\n -> + [$n, $\\ | Acc]; + $\r -> + [$r, $\\ | Acc]; + $\t -> + [$t, $\\ | Acc]; + C when C >= 0, C < $\s -> + [unihex(C) | Acc]; + C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 -> + [xmerl_ucs:to_utf8(C) | Acc]; + C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 -> + [unihex(C) | Acc]; + C when C < 16#7f -> + [C | Acc]; + _ -> + exit({json_encode, {bad_char, C}}) + end, + json_encode_string_unicode(Cs, State, Acc1). + +hexdigit(C) when C >= 0, C =< 9 -> + C + $0; +hexdigit(C) when C =< 15 -> + C + $a - 10. + +unihex(C) when C < 16#10000 -> + <> = <>, + Digits = [hexdigit(D) || D <- [D3, D2, D1, D0]], + [$\\, $u | Digits]; +unihex(C) when C =< 16#10FFFF -> + N = C - 16#10000, + S1 = 16#d800 bor ((N bsr 10) band 16#3ff), + S2 = 16#dc00 bor (N band 16#3ff), + [unihex(S1), unihex(S2)]. + +json_decode(L, S) when is_list(L) -> + json_decode(iolist_to_binary(L), S); +json_decode(B, S) -> + {Res, S1} = decode1(B, S), + {eof, _} = tokenize(B, S1#decoder{state=trim}), + Res. + +decode1(B, S=#decoder{state=null}) -> + case tokenize(B, S#decoder{state=any}) of + {{const, C}, S1} -> + {C, S1}; + {start_array, S1} -> + decode_array(B, S1); + {start_object, S1} -> + decode_object(B, S1) + end. + +make_object(V, #decoder{object_hook=null}) -> + V; +make_object(V, #decoder{object_hook=Hook}) -> + Hook(V). + +decode_object(B, S) -> + decode_object(B, S#decoder{state=key}, []). + +decode_object(B, S=#decoder{state=key}, Acc) -> + case tokenize(B, S) of + {end_object, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, S1#decoder{state=null}}; + {{const, K}, S1} -> + {colon, S2} = tokenize(B, S1), + {V, S3} = decode1(B, S2#decoder{state=null}), + decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) + end; +decode_object(B, S=#decoder{state=comma}, Acc) -> + case tokenize(B, S) of + {end_object, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, S1#decoder{state=null}}; + {comma, S1} -> + decode_object(B, S1#decoder{state=key}, Acc) + end. + +decode_array(B, S) -> + decode_array(B, S#decoder{state=any}, []). + +decode_array(B, S=#decoder{state=any}, Acc) -> + case tokenize(B, S) of + {end_array, S1} -> + {lists:reverse(Acc), S1#decoder{state=null}}; + {start_array, S1} -> + {Array, S2} = decode_array(B, S1), + decode_array(B, S2#decoder{state=comma}, [Array | Acc]); + {start_object, S1} -> + {Array, S2} = decode_object(B, S1), + decode_array(B, S2#decoder{state=comma}, [Array | Acc]); + {{const, Const}, S1} -> + decode_array(B, S1#decoder{state=comma}, [Const | Acc]) + end; +decode_array(B, S=#decoder{state=comma}, Acc) -> + case tokenize(B, S) of + {end_array, S1} -> + {lists:reverse(Acc), S1#decoder{state=null}}; + {comma, S1} -> + decode_array(B, S1#decoder{state=any}, Acc) + end. + +tokenize_string(B, S=#decoder{offset=O}) -> + case tokenize_string_fast(B, O) of + {escape, O1} -> + Length = O1 - O, + S1 = ?ADV_COL(S, Length), + <<_:O/binary, Head:Length/binary, _/binary>> = B, + tokenize_string(B, S1, lists:reverse(binary_to_list(Head))); + O1 -> + Length = O1 - O, + <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B, + {{const, String}, ?ADV_COL(S, Length + 1)} + end. + +tokenize_string_fast(B, O) -> + case B of + <<_:O/binary, ?Q, _/binary>> -> + O; + <<_:O/binary, $\\, _/binary>> -> + {escape, O}; + <<_:O/binary, C1, _/binary>> when C1 < 128 -> + tokenize_string_fast(B, 1 + O); + <<_:O/binary, C1, C2, _/binary>> when C1 >= 194, C1 =< 223, + C2 >= 128, C2 =< 191 -> + tokenize_string_fast(B, 2 + O); + <<_:O/binary, C1, C2, C3, _/binary>> when C1 >= 224, C1 =< 239, + C2 >= 128, C2 =< 191, + C3 >= 128, C3 =< 191 -> + tokenize_string_fast(B, 3 + O); + <<_:O/binary, C1, C2, C3, C4, _/binary>> when C1 >= 240, C1 =< 244, + C2 >= 128, C2 =< 191, + C3 >= 128, C3 =< 191, + C4 >= 128, C4 =< 191 -> + tokenize_string_fast(B, 4 + O); + _ -> + throw(invalid_utf8) + end. + +tokenize_string(B, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, ?Q, _/binary>> -> + {{const, iolist_to_binary(lists:reverse(Acc))}, ?INC_COL(S)}; + <<_:O/binary, "\\\"", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\" | Acc]); + <<_:O/binary, "\\\\", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\\ | Acc]); + <<_:O/binary, "\\/", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$/ | Acc]); + <<_:O/binary, "\\b", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\b | Acc]); + <<_:O/binary, "\\f", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\f | Acc]); + <<_:O/binary, "\\n", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\n | Acc]); + <<_:O/binary, "\\r", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\r | Acc]); + <<_:O/binary, "\\t", _/binary>> -> + tokenize_string(B, ?ADV_COL(S, 2), [$\t | Acc]); + <<_:O/binary, "\\u", C3, C2, C1, C0, Rest/binary>> -> + C = erlang:list_to_integer([C3, C2, C1, C0], 16), + if C > 16#D7FF, C < 16#DC00 -> + %% coalesce UTF-16 surrogate pair + <<"\\u", D3, D2, D1, D0, _/binary>> = Rest, + D = erlang:list_to_integer([D3,D2,D1,D0], 16), + [CodePoint] = xmerl_ucs:from_utf16be(<>), + Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc), + tokenize_string(B, ?ADV_COL(S, 12), Acc1); + true -> + Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc), + tokenize_string(B, ?ADV_COL(S, 6), Acc1) + end; + <<_:O/binary, C, _/binary>> -> + tokenize_string(B, ?INC_CHAR(S, C), [C | Acc]) + end. + +tokenize_number(B, S) -> + case tokenize_number(B, sign, S, []) of + {{int, Int}, S1} -> + {{const, list_to_integer(Int)}, S1}; + {{float, Float}, S1} -> + {{const, list_to_float(Float)}, S1} + end. + +tokenize_number(B, sign, S=#decoder{offset=O}, []) -> + case B of + <<_:O/binary, $-, _/binary>> -> + tokenize_number(B, int, ?INC_COL(S), [$-]); + _ -> + tokenize_number(B, int, S, []) + end; +tokenize_number(B, int, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, $0, _/binary>> -> + tokenize_number(B, frac, ?INC_COL(S), [$0 | Acc]); + <<_:O/binary, C, _/binary>> when C >= $1 andalso C =< $9 -> + tokenize_number(B, int1, ?INC_COL(S), [C | Acc]) + end; +tokenize_number(B, int1, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, int1, ?INC_COL(S), [C | Acc]); + _ -> + tokenize_number(B, frac, S, Acc) + end; +tokenize_number(B, frac, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, $., C, _/binary>> when C >= $0, C =< $9 -> + tokenize_number(B, frac1, ?ADV_COL(S, 2), [C, $. | Acc]); + <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> + tokenize_number(B, esign, ?INC_COL(S), [$e, $0, $. | Acc]); + _ -> + {{int, lists:reverse(Acc)}, S} + end; +tokenize_number(B, frac1, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, frac1, ?INC_COL(S), [C | Acc]); + <<_:O/binary, E, _/binary>> when E =:= $e orelse E =:= $E -> + tokenize_number(B, esign, ?INC_COL(S), [$e | Acc]); + _ -> + {{float, lists:reverse(Acc)}, S} + end; +tokenize_number(B, esign, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C =:= $- orelse C=:= $+ -> + tokenize_number(B, eint, ?INC_COL(S), [C | Acc]); + _ -> + tokenize_number(B, eint, S, Acc) + end; +tokenize_number(B, eint, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]) + end; +tokenize_number(B, eint1, S=#decoder{offset=O}, Acc) -> + case B of + <<_:O/binary, C, _/binary>> when C >= $0 andalso C =< $9 -> + tokenize_number(B, eint1, ?INC_COL(S), [C | Acc]); + _ -> + {{float, lists:reverse(Acc)}, S} + end. + +tokenize(B, S=#decoder{offset=O}) -> + case B of + <<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) -> + tokenize(B, ?INC_CHAR(S, C)); + <<_:O/binary, "{", _/binary>> -> + {start_object, ?INC_COL(S)}; + <<_:O/binary, "}", _/binary>> -> + {end_object, ?INC_COL(S)}; + <<_:O/binary, "[", _/binary>> -> + {start_array, ?INC_COL(S)}; + <<_:O/binary, "]", _/binary>> -> + {end_array, ?INC_COL(S)}; + <<_:O/binary, ",", _/binary>> -> + {comma, ?INC_COL(S)}; + <<_:O/binary, ":", _/binary>> -> + {colon, ?INC_COL(S)}; + <<_:O/binary, "null", _/binary>> -> + {{const, null}, ?ADV_COL(S, 4)}; + <<_:O/binary, "true", _/binary>> -> + {{const, true}, ?ADV_COL(S, 4)}; + <<_:O/binary, "false", _/binary>> -> + {{const, false}, ?ADV_COL(S, 5)}; + <<_:O/binary, "\"", _/binary>> -> + tokenize_string(B, ?INC_COL(S)); + <<_:O/binary, C, _/binary>> when (C >= $0 andalso C =< $9) + orelse C =:= $- -> + tokenize_number(B, S); + <<_:O/binary>> -> + trim = S#decoder.state, + {eof, S} + end. +%% +%% Tests +%% +-include_lib("eunit/include/eunit.hrl"). +-ifdef(TEST). + + +%% testing constructs borrowed from the Yaws JSON implementation. + +%% Create an object from a list of Key/Value pairs. + +obj_new() -> + {struct, []}. + +is_obj({struct, Props}) -> + F = fun ({K, _}) when is_binary(K) -> true end, + lists:all(F, Props). + +obj_from_list(Props) -> + Obj = {struct, Props}, + ?assert(is_obj(Obj)), + Obj. + +%% Test for equivalence of Erlang terms. +%% Due to arbitrary order of construction, equivalent objects might +%% compare unequal as erlang terms, so we need to carefully recurse +%% through aggregates (tuples and objects). + +equiv({struct, Props1}, {struct, Props2}) -> + equiv_object(Props1, Props2); +equiv(L1, L2) when is_list(L1), is_list(L2) -> + equiv_list(L1, L2); +equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; +equiv(A, A) when A =:= true orelse A =:= false orelse A =:= null -> true. + +%% Object representation and traversal order is unknown. +%% Use the sledgehammer and sort property lists. + +equiv_object(Props1, Props2) -> + L1 = lists:keysort(1, Props1), + L2 = lists:keysort(1, Props2), + Pairs = lists:zip(L1, L2), + true = lists:all(fun({{K1, V1}, {K2, V2}}) -> + equiv(K1, K2) and equiv(V1, V2) + end, Pairs). + +%% Recursively compare tuple elements for equivalence. + +equiv_list([], []) -> + true; +equiv_list([V1 | L1], [V2 | L2]) -> + equiv(V1, V2) andalso equiv_list(L1, L2). + +decode_test() -> + [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>), + <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]). + +e2j_vec_test() -> + test_one(e2j_test_vec(utf8), 1). + +test_one([], _N) -> + %% io:format("~p tests passed~n", [N-1]), + ok; +test_one([{E, J} | Rest], N) -> + %% io:format("[~p] ~p ~p~n", [N, E, J]), + true = equiv(E, decode(J)), + true = equiv(E, decode(encode(E))), + test_one(Rest, 1+N). + +e2j_test_vec(utf8) -> + [ + {1, "1"}, + {3.1416, "3.14160"}, %% text representation may truncate, trail zeroes + {-1, "-1"}, + {-3.1416, "-3.14160"}, + {12.0e10, "1.20000e+11"}, + {1.234E+10, "1.23400e+10"}, + {-1.234E-10, "-1.23400e-10"}, + {10.0, "1.0e+01"}, + {123.456, "1.23456E+2"}, + {10.0, "1e1"}, + {<<"foo">>, "\"foo\""}, + {<<"foo", 5, "bar">>, "\"foo\\u0005bar\""}, + {<<"">>, "\"\""}, + {<<"\n\n\n">>, "\"\\n\\n\\n\""}, + {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\""}, + {obj_new(), "{}"}, + {obj_from_list([{<<"foo">>, <<"bar">>}]), "{\"foo\":\"bar\"}"}, + {obj_from_list([{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]), + "{\"foo\":\"bar\",\"baz\":123}"}, + {[], "[]"}, + {[[]], "[[]]"}, + {[1, <<"foo">>], "[1,\"foo\"]"}, + + %% json array in a json object + {obj_from_list([{<<"foo">>, [123]}]), + "{\"foo\":[123]}"}, + + %% json object in a json object + {obj_from_list([{<<"foo">>, obj_from_list([{<<"bar">>, true}])}]), + "{\"foo\":{\"bar\":true}}"}, + + %% fold evaluation order + {obj_from_list([{<<"foo">>, []}, + {<<"bar">>, obj_from_list([{<<"baz">>, true}])}, + {<<"alice">>, <<"bob">>}]), + "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}"}, + + %% json object in a json array + {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null], + "[-123,\"foo\",{\"bar\":[]},null]"} + ]. + +%% test utf8 encoding +encoder_utf8_test() -> + %% safe conversion case (default) + [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] = + encode(<<1,"\321\202\320\265\321\201\321\202">>), + + %% raw utf8 output (optional) + Enc = mochijson2:encoder([{utf8, true}]), + [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] = + Enc(<<1,"\321\202\320\265\321\201\321\202">>). + +input_validation_test() -> + Good = [ + {16#00A3, <>}, %% pound + {16#20AC, <>}, %% euro + {16#10196, <>} %% denarius + ], + lists:foreach(fun({CodePoint, UTF8}) -> + Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)), + Expect = decode(UTF8) + end, Good), + + Bad = [ + %% 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte + <>, + %% missing continuations, last byte in each should be 80-BF + <>, + <>, + <>, + %% we don't support code points > 10FFFF per RFC 3629 + <> + ], + lists:foreach( + fun(X) -> + ok = try decode(X) catch invalid_utf8 -> ok end, + %% could be {ucs,{bad_utf8_character_code}} or + %% {json_encode,{bad_char,_}} + {'EXIT', _} = (catch encode(X)) + end, Bad). + +inline_json_test() -> + ?assertEqual(<<"\"iodata iodata\"">>, + iolist_to_binary( + encode({json, [<<"\"iodata">>, " iodata\""]}))), + ?assertEqual({struct, [{<<"key">>, <<"iodata iodata">>}]}, + decode( + encode({struct, + [{key, {json, [<<"\"iodata">>, " iodata\""]}}]}))), + ok. + +big_unicode_test() -> + UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)), + ?assertEqual( + <<"\"\\ud834\\udd20\"">>, + iolist_to_binary(encode(UTF8Seq))), + ?assertEqual( + UTF8Seq, + decode(iolist_to_binary(encode(UTF8Seq)))), + ok. + +custom_decoder_test() -> + ?assertEqual( + {struct, [{<<"key">>, <<"value">>}]}, + (decoder([]))("{\"key\": \"value\"}")), + F = fun ({struct, [{<<"key">>, <<"value">>}]}) -> win end, + ?assertEqual( + win, + (decoder([{object_hook, F}]))("{\"key\": \"value\"}")), + ok. + +atom_test() -> + %% JSON native atoms + [begin + ?assertEqual(A, decode(atom_to_list(A))), + ?assertEqual(iolist_to_binary(atom_to_list(A)), + iolist_to_binary(encode(A))) + end || A <- [true, false, null]], + %% Atom to string + ?assertEqual( + <<"\"foo\"">>, + iolist_to_binary(encode(foo))), + ?assertEqual( + <<"\"\\ud834\\udd20\"">>, + iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))), + ok. + +key_encode_test() -> + %% Some forms are accepted as keys that would not be strings in other + %% cases + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode({struct, [{foo, 1}]}))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode({struct, [{<<"foo">>, 1}]}))), + ?assertEqual( + <<"{\"foo\":1}">>, + iolist_to_binary(encode({struct, [{"foo", 1}]}))), + ?assertEqual( + <<"{\"\\ud834\\udd20\":1}">>, + iolist_to_binary( + encode({struct, [{[16#0001d120], 1}]}))), + ?assertEqual( + <<"{\"1\":1}">>, + iolist_to_binary(encode({struct, [{1, 1}]}))), + ok. + +unsafe_chars_test() -> + Chars = "\"\\\b\f\n\r\t", + [begin + ?assertEqual(false, json_string_is_safe([C])), + ?assertEqual(false, json_bin_is_safe(<>)), + ?assertEqual(<>, decode(encode(<>))) + end || C <- Chars], + ?assertEqual( + false, + json_string_is_safe([16#0001d120])), + ?assertEqual( + false, + json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))), + ?assertEqual( + [16#0001d120], + xmerl_ucs:from_utf8( + binary_to_list( + decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))), + ?assertEqual( + false, + json_string_is_safe([16#110000])), + ?assertEqual( + false, + json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))), + %% solidus can be escaped but isn't unsafe by default + ?assertEqual( + <<"/">>, + decode(<<"\"\\/\"">>)), + ok. + +int_test() -> + ?assertEqual(0, decode("0")), + ?assertEqual(1, decode("1")), + ?assertEqual(11, decode("11")), + ok. + +float_fallback_test() -> + ?assertEqual(<<"-2147483649.0">>, iolist_to_binary(encode(-2147483649))), + ?assertEqual(<<"2147483648.0">>, iolist_to_binary(encode(2147483648))), + ok. + +handler_test() -> + ?assertEqual( + {'EXIT',{json_encode,{bad_term,{}}}}, + catch encode({})), + F = fun ({}) -> [] end, + ?assertEqual( + <<"[]">>, + iolist_to_binary((encoder([{handler, F}]))({}))), + ok. + +-endif. \ No newline at end of file diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl index b8f5cf92b..6a3e807d2 100644 --- a/src/web/mod_http_bind.erl +++ b/src/web/mod_http_bind.erl @@ -5,7 +5,7 @@ %%% Created : Tue Feb 20 13:15:52 CET 2007 %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/web/mod_http_bindjson.erl b/src/web/mod_http_bindjson.erl new file mode 100644 index 000000000..6090ee458 --- /dev/null +++ b/src/web/mod_http_bindjson.erl @@ -0,0 +1,156 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_http_bindjson.erl +%%% Original Author : Stefan Strigler +%%% Purpose : Implementation of XMPP over BOSH (XEP-0206) +%%% Created : Tue Feb 20 13:15:52 CET 2007 +%%% +%%% ejabberd, Copyright (C) 2002-2010 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 +%%% +%%%---------------------------------------------------------------------- + +%%%---------------------------------------------------------------------- +%%% This module acts as a bridge to ejabberd_http_bind which implements +%%% the real stuff, this is to handle the new pluggable architecture for +%%% extending ejabberd's http service. +%%%---------------------------------------------------------------------- +%%% I will probable kill and merge code with the original mod_http_bind +%%% if this feature gains traction. +%%% Eric Cestari + +-module(mod_http_bindjson). +-author('steve@zeank.in-berlin.de'). + +%%-define(ejabberd_debug, true). + +-behaviour(gen_mod). + +-export([ + start/2, + stop/1, + process/2 + ]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("ejabberd_http.hrl"). +-include("http_bind.hrl"). + +%% Duplicated from ejabberd_http_bind. +%% TODO: move to hrl file. +-record(http_bind, {id, pid, to, hold, wait, process_delay, version}). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- + +process([], #request{method = 'POST', + data = []}) -> + ?DEBUG("Bad Request: no data", []), + {400, ?HEADER, {xmlelement, "h1", [], + [{xmlcdata, "400 Bad Request"}]}}; +process([], #request{method = 'POST', + data = Data, + ip = IP}) -> + ?DEBUG("Incoming data: ~s", [Data]), + %NOTE the whole point of this file is this line. + ejabberd_http_bindjson:process_request(Data, IP); +process([], #request{method = 'GET', + data = []}) -> + {200, ?HEADER, get_human_html_xmlel()}; +process([], #request{method = 'OPTIONS', + data = []}) -> + {200, ?OPTIONS_HEADER, []}; +process(_Path, _Request) -> + ?DEBUG("Bad Request: ~p", [_Request]), + {400, ?HEADER, {xmlelement, "h1", [], + [{xmlcdata, "400 Bad Request"}]}}. + +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-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 +%%%---------------------------------------------------------------------- +start(_Host, _Opts) -> + setup_database(), + HTTPBindSupervisor = + {ejabberd_http_bind_sup, + {ejabberd_tmp_sup, start_link, + [ejabberd_http_bind_sup, ejabberd_http_bind]}, + permanent, + infinity, + supervisor, + [ejabberd_tmp_sup]}, + case supervisor:start_child(ejabberd_sup, HTTPBindSupervisor) of + {ok, _Pid} -> + ok; + {ok, _Pid, _Info} -> + ok; + {error, {already_started, _PidOther}} -> + % mod_http_bind is already started so it will not be started again + ok; + {error, Error} -> + {'EXIT', {start_child_error, Error}} + end. + +stop(_Host) -> + case supervisor:terminate_child(ejabberd_sup, ejabberd_http_bind_sup) of + ok -> + ok; + {error, Error} -> + {'EXIT', {terminate_child_error, Error}} + end. + +setup_database() -> + migrate_database(), + mnesia:create_table(http_bind, + [{ram_copies, [node()]}, + {local_content, true}, + {attributes, record_info(fields, http_bind)}]), + mnesia:add_table_copy(http_bind, node(), ram_copies). + +migrate_database() -> + case catch mnesia:table_info(http_bind, attributes) of + [id, pid, to, hold, wait, process_delay, version] -> + ok; + _ -> + %% Since the stored information is not important, instead + %% of actually migrating data, let's just destroy the table + mnesia:delete_table(http_bind) + end, + case catch mnesia:table_info(http_bind, local_content) of + false -> + mnesia:delete_table(http_bind); + _ -> + ok + end. diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl index 116f5555d..209c2da25 100644 --- a/src/web/mod_http_fileserver.erl +++ b/src/web/mod_http_fileserver.erl @@ -5,7 +5,7 @@ %%% Created : %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -106,6 +106,13 @@ set_default_host(Host, _Opts)-> _ -> ok end. +% Returns current host if it exists or default host +get_host(Host)-> + DCT = mochiglobal:get(default_content_type), + case lists:keymember(Host, 1, DCT) of + true -> Host; + false -> mochiglobal:get(http_default_host) + end. conf_store(Host, Key, Value)-> R = case mochiglobal:get(Key) of @@ -118,18 +125,18 @@ conf_store(Host, Key, Value)-> end, mochiglobal:put(Key, R). -conf_get(Host, Key, Default) -> +conf_get(Host, Key) -> case mochiglobal:get(Key) of - undefined-> Default; + undefined-> undefined; A -> case lists:keyfind(Host, 1, A) of {Host, Val} -> Val; false -> case mochiglobal:get(http_default_host) of Host -> % stop recursion here - Default; + undefined; DefaultHost -> - conf_get(DefaultHost, Key, Default) + conf_get(DefaultHost, Key) end end end. @@ -193,15 +200,15 @@ stop(_Host) -> process(LocalPath, Request) -> ?DEBUG("Requested ~p", [LocalPath]), - Host = Request#request.host, + Host = get_host(Request#request.host), ClientHeaders = Request#request.headers, - DirectoryIndices = conf_get(Host, directory_indices, undefined), - CustomHeaders = conf_get(Host, custom_headers, undefined), - DefaultContentType = conf_get(Host, default_content_type, undefined), - ContentTypes = conf_get(Host, content_types, undefined), - Encoding = conf_get(Host, serve_gzip, undefined), + DirectoryIndices = conf_get(Host, directory_indices), + CustomHeaders = conf_get(Host, custom_headers), + DefaultContentType = conf_get(Host, default_content_type), + ContentTypes = conf_get(Host, content_types), + Encoding = conf_get(Host, serve_gzip), Static = select_encoding(ClientHeaders, Encoding), - DocRoot = conf_get(Host, docroot, undefined), + DocRoot = conf_get(Host, docroot), FileName = filename:join(filename:split(DocRoot) ++ LocalPath), {FileSize, Code, Headers, Contents} = case file:read_file_info(FileName) of {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; diff --git a/src/web/mod_register_web.erl b/src/web/mod_register_web.erl index 1e08ce48a..98ee52fb9 100644 --- a/src/web/mod_register_web.erl +++ b/src/web/mod_register_web.erl @@ -5,7 +5,7 @@ %%% Created : 4 May 2008 by Badlop %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -86,8 +86,9 @@ process([], #request{method = 'GET', lang = Lang}) -> process(["register.css"], #request{method = 'GET'}) -> serve_css(); -process(["new"], #request{method = 'GET', lang = Lang, host = Host}) -> - form_new_get(Host, Lang); +process(["new"], #request{method = 'GET', lang = Lang, host = Host, ip = IP}) -> + {Addr, _Port} = IP, + form_new_get(Host, Lang, Addr); process(["delete"], #request{method = 'GET', lang = Lang, host = Host}) -> form_del_get(Host, Lang); @@ -185,8 +186,8 @@ index_page(Lang) -> %%% Formulary new account GET %%%---------------------------------------------------------------------- -form_new_get(Host, Lang) -> - CaptchaEls = build_captcha_li_list(Lang), +form_new_get(Host, Lang, IP) -> + CaptchaEls = build_captcha_li_list(Lang, IP), HeadEls = [ ?XCT("title", "Register a Jabber account"), ?XA("link", @@ -336,27 +337,31 @@ form_new_post(Username, Host, Password, {Id, Key}) -> %%% Formulary Captcha support for new GET/POST %%%---------------------------------------------------------------------- -build_captcha_li_list(Lang) -> +build_captcha_li_list(Lang, IP) -> case ejabberd_captcha:is_feature_available() of - true -> build_captcha_li_list2(Lang); + true -> build_captcha_li_list2(Lang, IP); false -> [] end. -build_captcha_li_list2(Lang) -> - Id = randoms:get_string(), +build_captcha_li_list2(Lang, IP) -> SID = "", From = #jid{user = "", server = "test", resource = ""}, To = #jid{user = "", server = "test", resource = ""}, Args = [], - ejabberd_captcha:create_captcha(Id, SID, From, To, Lang, Args), - {_, {CImg,CText,CId,CKey}} = ejabberd_captcha:build_captcha_html(Id, Lang), - [?XE("li", [CText, - ?C(" "), - CId, - CKey, - ?BR, - CImg] - )]. + case ejabberd_captcha:create_captcha(SID, From, To, Lang, IP, Args) of + {ok, Id, _} -> + {_, {CImg,CText,CId,CKey}} = + ejabberd_captcha:build_captcha_html(Id, Lang), + [?XE("li", [CText, + ?C(" "), + CId, + CKey, + ?BR, + CImg] + )]; + _ -> + [] + end. %%%---------------------------------------------------------------------- %%% Formulary change password GET diff --git a/src/web/simple_ws_check.erl b/src/web/simple_ws_check.erl new file mode 100644 index 000000000..8ef160980 --- /dev/null +++ b/src/web/simple_ws_check.erl @@ -0,0 +1,11 @@ +-module (simple_ws_check). +-export ([is_acceptable/6]). +-include("ejabberd.hrl"). +is_acceptable(["true"]=Path, Q, Origin, Protocol, IP, Headers)-> + ?INFO_MSG("Authorized Websocket ~p with: ~n Q = ~p~n Origin = ~p~n Protocol = ~p~n IP = ~p~n Headers = ~p~n", + [Path, Q, Origin, Protocol, IP, Headers]), + true; +is_acceptable(["false"]=Path, Q, Origin, Protocol, IP, Headers)-> + ?INFO_MSG("Failed Websocket ~p with: ~n Q = ~p~n Origin = ~p~n Protocol = ~p~n IP = ~p~n Headers = ~p~n", + [Path, Q, Origin, Protocol, IP, Headers]), + false. \ No newline at end of file diff --git a/src/web/websocket_test.erl b/src/web/websocket_test.erl index 5928e7d83..b5491bc08 100644 --- a/src/web/websocket_test.erl +++ b/src/web/websocket_test.erl @@ -1,9 +1,10 @@ -module (websocket_test). --export([start/1, loop/1]). +-export([start_link/1, loop/1]). % callback on received websockets data -start(Ws) -> - spawn(?MODULE, loop, [Ws]). +start_link(Ws) -> + Pid = spawn_link(?MODULE, loop, [Ws]), + {ok, Pid}. loop(Ws) -> receive diff --git a/src/web/xmpp_json.erl b/src/web/xmpp_json.erl new file mode 100644 index 000000000..6fae2ab08 --- /dev/null +++ b/src/web/xmpp_json.erl @@ -0,0 +1,362 @@ +%%%---------------------------------------------------------------------- +%%% File : xmpp_json.erl +%%% Author : Eric Cestari +%%% Purpose : Converts {xmlelement,Name, A, Sub} to/from JSON as per protoxep +%%% Created : 09-20-2010 +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2010 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 (xmpp_json). + +-export([to_json/1, from_json/1]). + + + +%%% FROM JSON TO XML + +from_json({struct, [{<<"stream">>, _Attr}]=Elems}) -> + parse_start(Elems); + +from_json({struct, Elems}) -> + {xmlstreamelement, hd(from_json2({struct, Elems}))}. + +from_json2({struct, Elems}) -> + lists:map(fun parse_json/1, Elems). + +parse_start([{BinName, {struct, JAttrs}}]) -> + Name = binary_to_list(BinName), + {FullName, Attrs} = lists:foldl( + fun({<<"xml">>, {struct, XML}}, {N, Attrs}) -> + XmlAttrs = parse_json_special_attrs("xml", XML), + {N, lists:merge(Attrs, XmlAttrs)}; + ({<<"xmlns">>, {struct, XMLNS}}, {N, Attrs}) -> + XmlNsAttrs = parse_json_special_attrs("xmlns", XMLNS), + {N, lists:merge(Attrs, XmlNsAttrs)}; + ({<<"$$">>, BaseNS}, {N, Attrs})-> + {binary_to_list(BaseNS)++":"++N, Attrs}; + ({Key, Value}, {N, Attrs})-> + {N, [{ib2tol(Key), ib2tol(Value)}|Attrs]} + end, {Name, []}, JAttrs), + {xmlstreamstart, FullName, Attrs}. + +parse_json({Name, CData}) when is_binary(CData)-> + {xmlelement, binary_to_list(Name), [], [{xmlcdata, CData}]}; + +parse_json({Name, CDatas}) when is_list(CDatas)-> + lists:map(fun(CData)-> + {xmlelement, binary_to_list(Name), [], [{xmlcdata, CData}]} + end, CDatas); + +parse_json({BinName, {struct, JAttrs}}) -> + Name = binary_to_list(BinName), + {FullName, Attrs, SubEls} = lists:foldl( + fun({<<"$">>, Cdata}, {N, Attrs, _SubEls}) when is_binary(Cdata)-> + {N, Attrs, [{xmlcdata, Cdata}]}; + ({<<"$">>, {struct, Elems}}, {N, Attrs, _SubEls}) -> + SE = lists:map(fun parse_json/1, Elems), + {N, Attrs, lists:flatten(SE)}; % due to 4.2.3.3 + ({<<"xml">>, {struct, XML}}, {N, Attrs, SubEls}) -> + XmlAttrs = parse_json_special_attrs("xml", XML), + {N, lists:merge(Attrs, XmlAttrs), SubEls}; + ({<<"xmlns">>, {struct, XMLNS}}, {N, Attrs, SubEls}) -> + XmlNsAttrs = parse_json_special_attrs("xmlns", XMLNS), + {N, lists:merge(Attrs, XmlNsAttrs), SubEls}; + ({Key, {struct, []}}, {N, Attrs, SubEls})-> + {N, Attrs, [{xmlelement, ib2tol(Key), [], []}|SubEls]}; + ({Key, Value}, {N, Attrs, SubEls})-> + {N, [{binary_to_list(Key), ib2tol(Value)}|Attrs], SubEls} + end, {Name, [], []}, JAttrs), + {xmlelement, FullName, Attrs, SubEls}. + +parse_json_special_attrs(Prefix, XMLNS)-> + lists:reverse(lists:map( + fun({<<"$">>, Value})-> + {Prefix, ib2tol(Value)}; + ({<<"@",NS/binary>>, Value})-> + {Prefix ++ ":"++binary_to_list(NS), ib2tol(Value)} + end, XMLNS)). + +%%% FROM XML TO JSON +to_json({xmlstreamelement, XMLElement})-> + to_json(XMLElement); +to_json({xmlelement, _Name, [], []})-> + {struct, []}; +to_json({xmlelement, Name, [], [{xmlcdata, Cdata}]})-> + {SName, JsonAttrs2} = parse_namespace(Name, []), + {struct, [{SName, Cdata}|JsonAttrs2]}; +to_json({xmlstreamstart, Name, Attrs})-> + JsonAttrs = parse_attrs(Attrs), + {SName, Members2} = parse_namespace(Name, JsonAttrs), + {struct, [{SName, {struct, Members2}}]}; +to_json({xmlelement, Name, Attrs, SubEls})-> + JsonAttrs = parse_attrs(Attrs), + Members = case parse_subels(SubEls) of + [] -> + JsonAttrs; + [Elem] -> + [{<<"$">>,Elem}|JsonAttrs]; + Elems -> + [{<<"$">>,Elems}|JsonAttrs] + end, + {SName, Members2} = parse_namespace(Name, Members), + {struct, [{SName, {struct, Members2}}]}. + +parse_namespace(Name, AttrsList)-> + {l2b(Name), AttrsList}. + +parse_subels([{xmlcdata, Cdata}])-> + l2b(Cdata); +parse_subels([])-> + []; +parse_subels(SubEls)-> + {struct, lists:reverse(lists:foldl( + fun({xmlelement, SName, [], [{xmlcdata, UCdata}]}, Acc)-> + Cdata = l2b(UCdata), + Name = l2b(SName), + case lists:keyfind(Name, 1, Acc) of + {Name, PrevCdata} when is_binary(PrevCdata) -> + Acc1 = lists:keydelete(Name, 1, Acc), + [{Name,[PrevCdata, Cdata]} | Acc1]; + {Name, CDatas} when is_list(CDatas) -> + Acc1 = lists:keydelete(Name, 1, Acc), + [{Name,lists:append(CDatas, [Cdata])} | Acc1]; + _ -> + [{Name, Cdata}| Acc] + end; + ({xmlelement, SName, _, _} = Elem, Acc) -> + E = case to_json(Elem) of %TODO There could be a better way to iterate + {struct, [{_, ToKeep}]} -> ToKeep; + {struct, []} = Empty -> Empty + end, + [{l2b(SName), E}|Acc]; + ({xmlcdata,<<"\n">>}, Acc) -> + Acc + end,[], SubEls))}. + + +parse_attrs(XmlAttrs)-> + {Normal, XMLNS} = lists:foldl( + fun({"xmlns", NS}, {Attrs, XMLNS}) -> + {Attrs,[{<<"$">>, l2b(NS)}| XMLNS]}; + ({"xmlns:" ++ Short, NS}, {Attrs, XMLNS})-> + AttrName = iolist_to_binary([<<"@">>,l2b(Short)]), + {Attrs,[{AttrName, list_to_binary(NS)}| XMLNS]}; + ({"xml:" ++ Short, Val}, {Attrs, XMLNS})-> + % TODO currently tolerates only one xml:* attr per element + AttrName = iolist_to_binary([<<"@">>,l2b(Short)]), + {[{<<"xml">>,{struct, [{AttrName, l2b(Val)}]}}|Attrs], XMLNS}; + ({K, V}, {Attrs, XMLNS})-> + {[{l2b(K), l2b(V)}|Attrs], XMLNS} + end,{[], []}, XmlAttrs), + + case XMLNS of + [{<<"$">>, NS}]-> + [{<<"xmlns">>, NS}|Normal]; + []-> + Normal; + _ -> + [{<<"xmlns">>,{struct, XMLNS} }| Normal] + end. + +l2b(List) when is_list(List) -> list_to_binary(List); +l2b(Bin) when is_binary(Bin) -> Bin. + +ib2tol(Bin) when is_binary(Bin) -> binary_to_list(Bin ); +ib2tol(Integer) when is_integer(Integer) -> integer_to_list(Integer); +ib2tol(List) when is_list(List) -> List. + +%% +%% Tests +%% erlc -DTEST web/xmpp_json.erl && erl -pa web/ -run xmpp_json test -run init stop -noshell +-include_lib("eunit/include/eunit.hrl"). +-ifdef(TEST). + +% 4.2.3.1 Tag with text value +to_text_value_test()-> + In = {xmlstreamelement, {xmlelement, "tag", [], [{xmlcdata, <<"txt-value">>}]}}, + Out = {struct, [{<<"tag">>, <<"txt-value">>}]}, + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.2 Tag with recursive tags +to_tag_with_recursive_tags_test()-> + In = {xmlstreamelement, {xmlelement, "tag", [], + [{xmlelement,"tag2",[], [{xmlcdata, <<"txt-value">>}]}, + {xmlelement,"tag3",[], [ + {xmlelement,"tag4",[], [{xmlcdata, <<"txt2-value">>}]}]}]}}, + Out = {struct, [{<<"tag">>, + {struct, [{<<"$">>, + {struct, [ + {<<"tag2">>,<<"txt-value">>}, + {<<"tag3">>,{struct, [{<<"$">>,{struct, [{<<"tag4">>,<<"txt2-value">>}]}}]}} + ]} + }]} + }] + }, + %io:format("~n~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + io:format("~n~p", [from_json(Out)]), + io:format("~n~p", [to_json(In)]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.3 Multiple text value tags as array +multiple_text_value_tags_as_array_test()-> + In = {xmlstreamelement, {xmlelement, "tag", [], [ + {xmlelement,"tag2",[], [ + {xmlcdata, <<"txt-value">>}]}, + {xmlelement,"tag2",[], [ + {xmlcdata, <<"txt-value2">>}]}]}}, + Out = {struct, [{<<"tag">>, + {struct, [{<<"$">>, + {struct, [{<<"tag2">>, + [<<"txt-value">>, <<"txt-value2">>]}]} + }]} + }] + }, + io:format("~p~n", [to_json(In)]), + io:format("~p~n", [from_json(Out)]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.4 Tag with attribute, no value +tag_attr_no_value_test() -> + In = {xmlstreamelement, {xmlelement, "tag", [{"attr", "attr-value"}], []}}, + Out = {struct, [{<<"tag">>, {struct, [ + {<<"attr">>,<<"attr-value">>} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + io:format("~p", [from_json(Out)]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.5 Tag with multiple attributes as array, no value +% Not wellformed XML. + +% 4.2.3.6 Tags as array with unique attributes, no value + + +% 4.2.3.7 Tag with namespace attribute, no value +tag_with_namespace_no_value_test()-> + In = {xmlstreamelement, {xmlelement, "tag", [{"xmlns:ns", "ns-value"}], []}}, + Out = {struct, [{<<"tag">>, {struct, [ + {<<"xmlns">>,{struct, [{<<"@ns">>, <<"ns-value">>}]}} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + + +% 4.2.3.8 Tag with many attributes to namespace, no value +two_namespaces_tag_no_value_test()-> + In = {xmlstreamelement,{xmlelement, "tag", [{"xmlns:ns", "ns-value"}, + {"xmlns", "root-value"}], []}}, + Out = {struct, [{<<"tag">>, {struct, [ + {<<"xmlns">>,{struct, [ + {<<"$">>, <<"root-value">>}, + {<<"@ns">>, <<"ns-value">>}]}} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.9 Tag with namespace attribute, no value +% Removed namespace handling. More complex on both sides. +namespaced_tag_no_value_test()-> + In = {xmlstreamelement,{xmlelement, "ns:tag", [{"attr", "attr-value"}], []}}, + Out = {struct, [{<<"ns:tag">>, {struct, [ + {<<"attr">>,<<"attr-value">>} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.10 Tag with attribute and text value +tag_with_attribute_and_value_test()-> + In = {xmlstreamelement,{xmlelement, "tag", [{"attr", "attr-value"}], + [{xmlcdata, <<"txt-value">>}]}}, + Out = {struct, [{<<"tag">>, {struct, [ + {<<"$">>, <<"txt-value">>}, + {<<"attr">>,<<"attr-value">>} + ]}}]}, + %io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +% 4.2.3.11 Namespace tag with attribute and text value +% Removed namespace handling. More complex on both sides +namespaced_tag_with_value_test()-> + In = {xmlstreamelement,{xmlelement, "ns:tag", [{"attr", "attr-value"}], [{xmlcdata, <<"txt-value">>}]}}, + Out = {struct, [{<<"ns:tag">>, {struct, [ + {<<"$">>,<<"txt-value">>}, + {<<"attr">>,<<"attr-value">>} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +xml_lang_attr_test()-> + In = {xmlstreamelement,{xmlelement, "tag", [{"xml:lang", "en"}], []}}, + Out = {struct, [{<<"tag">>, {struct, [ + {<<"xml">>,{struct,[{<<"@lang">>,<<"en">>}]}} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +xmlns_tag_with_value_test()-> + Out = {struct,[{<<"response">>, + {struct,[{<<"$">>,<<"dXNlcm5hbWU9I">>}, + {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}]}} + ]}, + Out2 = {struct,[{<<"response">>, + {struct,[{<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>}, + {<<"$">>,<<"dXNlcm5hbWU9I">>} + ]}} + ]}, + In = {xmlstreamelement,{xmlelement,"response", + [{"xmlns","urn:ietf:params:xml:ns:xmpp-sasl"}], + [{xmlcdata, <<"dXNlcm5hbWU9I">>}]}}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)), + ?assertEqual(In, from_json(Out2)). + +no_attr_no_value_test()-> + In = {xmlstreamelement, {xmlelement,"failure", + [{"xmlns","urn:ietf:params:xml:ns:xmpp-sasl"}], + [{xmlelement,"not-authorized",[],[]}]}}, + Out = {struct, [{<<"failure">>,{struct, [ + {<<"$">>, {struct, [{<<"not-authorized">>, {struct, []}}]}}, + {<<"xmlns">>, <<"urn:ietf:params:xml:ns:xmpp-sasl">>} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + io:format("~p~n", [to_json(In)]), + io:format("~p~n", [from_json(Out)]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). + +xmlstream_test()-> + In = {xmlstreamstart, "stream", [{"xml:lang", "en"}]}, + Out = {struct, [{<<"stream">>, {struct, [ + {<<"xml">>,{struct,[{<<"@lang">>,<<"en">>}]}} + ]}}]}, + io:format("~p", [list_to_binary(mochijson2:encode(to_json(In)))]), + ?assertEqual(Out, to_json(In)), + ?assertEqual(In, from_json(Out)). +-endif. \ No newline at end of file diff --git a/src/win32_dns.erl b/src/win32_dns.erl index 7c60d8e25..0da059c7d 100644 --- a/src/win32_dns.erl +++ b/src/win32_dns.erl @@ -5,7 +5,7 @@ %%% Created : 5 Mar 2009 by Geoff Cant %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/xml.erl b/src/xml.erl index 3e0157313..c412e5d14 100644 --- a/src/xml.erl +++ b/src/xml.erl @@ -5,7 +5,7 @@ %%% Created : 20 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as diff --git a/src/xml_stream.erl b/src/xml_stream.erl index 947992ad0..db29ac0a3 100644 --- a/src/xml_stream.erl +++ b/src/xml_stream.erl @@ -5,7 +5,7 @@ %%% Created : 17 Nov 2002 by Alexey Shchepin %%% %%% -%%% ejabberd, Copyright (C) 2002-2010 ProcessOne +%%% ejabberd, Copyright (C) 2002-2011 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as