summaryrefslogtreecommitdiff
path: root/net-im/ejabberd
diff options
context:
space:
mode:
Diffstat (limited to 'net-im/ejabberd')
-rw-r--r--net-im/ejabberd/ejabberd-1.1.2-r2.ebuild207
-rw-r--r--net-im/ejabberd/files/check_pam.diff472
-rw-r--r--net-im/ejabberd/files/digest-ejabberd-1.1.2-r29
-rw-r--r--net-im/ejabberd/files/http_binding.patch855
-rw-r--r--net-im/ejabberd/files/mod_pep.patch2319
5 files changed, 3862 insertions, 0 deletions
diff --git a/net-im/ejabberd/ejabberd-1.1.2-r2.ebuild b/net-im/ejabberd/ejabberd-1.1.2-r2.ebuild
new file mode 100644
index 00000000..ec83fab4
--- /dev/null
+++ b/net-im/ejabberd/ejabberd-1.1.2-r2.ebuild
@@ -0,0 +1,207 @@
+# Copyright 1999-2006 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header: $
+
+inherit eutils multilib ssl-cert versionator autotools
+
+JABBER_ETC="/etc/jabber"
+JABBER_RUN="/var/run/jabber"
+JABBER_SPOOL="/var/spool/jabber"
+JABBER_LOG="/var/log/jabber"
+
+E_MYSQL_V="5"
+E_MYSQL_N="mysql"
+E_MYSQL=${E_MYSQL_N}-${E_MYSQL_V}
+
+E_PGSQL_V="0.0.1"
+E_PGSQL_N="pgsql-cvs"
+E_PGSQL=${E_PGSQL_N}-${E_PGSQL_V}
+
+DESCRIPTION="The Erlang Jabber Daemon"
+HOMEPAGE="http://ejabberd.jabber.ru/"
+SRC_URI="http://process-one.net/en/projects/${PN}/download/${PV}/${P}.tar.gz
+ mod_presence? ( http://www.goryachev.org/jabber/mod_presence-0.0.4.tar.gz )
+ mod_archive? ( http://mabber.com/static/ejabberd-1.1.1_httpbind.tar.gz )"
+LICENSE="GPL-2"
+SLOT="0"
+KEYWORDS="x86 ~amd64"
+IUSE="${IUSE} mod_archive mod_irc mod_muc mod_pep mod_pubsub mod_presence mod_proxy statsdx ldap odbc pam web mysql postgres http_binding"
+
+DEPEND="${RDEPEND}
+ >=net-im/jabber-base-0.01
+ >=dev-libs/expat-1.95
+ >=dev-lang/erlang-10.2.0
+ odbc? ( dev-db/unixODBC )
+ ldap? ( =net-nds/openldap-2* )
+ postgres? ( =dev-erl/${E_PGSQL} )
+ mysql? ( =dev-erl/${E_MYSQL} )
+ pam? ( sys-libs/pam)"
+RDEPEND="postgres? ( dev-db/postgresql )
+ mysql? ( dev-db/mysql )"
+
+RESTRICT="nomirror"
+
+PROVIDE="virtual/jabber-server"
+S=${WORKDIR}/${P}/src
+
+src_unpack() {
+ unpack ${A}
+ cd ${S}
+
+ MYSQL_P=$(best_version dev-db/mysql)
+ MYSQL_PV=${MYSQL_P/dev-db\/mysql-/}
+
+ #
+ # If we have to work with MySQL 4.1 or greater, ejabberd's native
+ # MySQL-driver has to be patched to query "SET NAMES 'utf8'" on connecting
+ # the database.
+ #
+ if use mysql && \
+ (( \
+ [ $(get_major_version ${MYSQL_PV}) -eq 4 ] && \
+ [ $(get_major_version $(get_after_major_version ${MYSQL_PV})) -ge 1] \
+ ) \
+ || \
+ [ $(get_major_version ${MYSQL_PV}) -ge 5 ] \
+ ); then
+ epatch ${FILESDIR}/${P}-mysql-connect-utf8.patch
+ fi
+
+ if use statsdx; then
+ epatch ${FILESDIR}/${P}-statsdx.patch
+ epatch ${FILESDIR}/${P}-statsdx-web.patch
+ fi
+
+ use pam && epatch ${FILESDIR}/check_pam.diff # http://ejabberd.jabber.ru/pam
+ use mod_proxy && epatch ${FILESDIR}/${P}-mod-proxy.patch # http://bugs.gentoo.org/show_bug.cgi?id=137724
+ use mod_pep && epatch ${FILESDIR}/mod_pep.patch # http://www.dtek.chalmers.se/~henoch/text/ejabberd-pep.html
+ use http_binding && epatch ${WORKDIR}/ejabberd-1.1.1_httpbind/ejabberd-1.1.1_httpbind.patch.gz # http://blog.mabber.de/eintrag.php?id=34
+ use mod_archive && cp ${FILESDIR}/mod_archive.erl ${S} # http://ejabberd.jabber.ru/mod_archive
+
+ if use mod_presence; then
+ cp -r ${WORKDIR}/mod_presence/pixmaps ${S}
+ epatch ${WORKDIR}/mod_presence/mod_presence.diff
+ fi
+}
+
+src_compile() {
+ local myconf
+
+ if ! use mysql && ! use postgres && ! use odbc; then
+ myconf="--disable-odbc"
+ else
+ myconf="--enable-odbc"
+ fi
+
+ eautoconf
+ econf ${myconf} \
+ --enable-roster-gateway-workaround \
+ $(use_enable mod_irc) \
+ $(use_enable ldap eldap) \
+ $(use_enable mod_muc) \
+ $(use_enable mod_pubsub) \
+ $(use_enable ssl tls) \
+ $(use_enable web) \
+ $(use_enable odbc) \
+ $(use_enable mod_presence) \
+ || die "econf failed"
+
+ emake || die "compiling ejabberd core failed"
+}
+
+src_install() {
+ make \
+ DESTDIR=${D} \
+ EJABBERDDIR=${D}/usr/$(get_libdir)/erlang/lib/${P} \
+ ETCDIR=${D}${JABBER_ETC} \
+ LOGDIR=${D}${JABBER_LOG} \
+ install \
+ || die "install failed"
+
+ insinto /usr/share/doc/${PF}
+ use postgres && doins odbc/pg.sql
+ use mysql && doins odbc/mysql.sql
+ cd ${S}/..
+ dodoc doc/release_notes_${PV}.txt
+ dohtml doc/*.{html,png}
+
+ use postgres && {
+ pa="-pa /usr/$(get_libdir)/erlang/lib/${E_PGSQL}/ebin"
+ }
+
+ use mysql && {
+ pa=${pa}" -pa /usr/$(get_libdir)/erlang/lib/${E_MYSQL}/ebin"
+ }
+
+ #
+ # Create /usr/bin/ejabberd
+ #
+ cat <<EOF > ${T}/ejabberd
+#!/bin/bash
+
+erl -pa /usr/$(get_libdir)/erlang/lib/${P}/ebin \\
+ ${pa} \\
+ -sname ejabberd \\
+ -s ejabberd \\
+ -ejabberd config \"${JABBER_ETC}/ejabberd.cfg\" \\
+ log_path \"${JABBER_LOG}/ejabberd.log\" \\
+ -kernel inetrc \"${JABBER_ETC}/inetrc\" \\
+ -sasl sasl_error_logger \{file,\"${JABBER_LOG}/sasl.log\"\} \\
+ -mnesia dir \"${JABBER_SPOOL}\" \\
+ \$@
+EOF
+
+ #
+ # Create /usr/bin/ejabberdctl
+ #
+ cat <<EOF > ${T}/ejabberdctl
+#!/bin/sh
+
+exec env HOME=${JABBER_RUN} \\
+ erl -pa /usr/$(get_libdir)/erlang/lib/${P}/ebin \\
+ ${pa} \\
+ -noinput \\
+ -sname ejabberdctl \\
+ -s ejabberd_ctl \\
+ -extra \$@
+EOF
+
+ dobin ${T}/ejabberdctl
+ dobin ${T}/ejabberd
+
+ newinitd ${FILESDIR}/${P}.initd ${PN}
+ newconfd ${FILESDIR}/${P}.confd ${PN}
+
+ insinto ${JABBER_ETC}
+ if use ssl; then
+ docert ssl
+ rm -f ${D}${JABBER_ETC}/ssl.{crt,csr,key}
+ fowners jabber:jabber ${JABBER_ETC}/ssl.pem
+ fi
+ doins ${FILESDIR}/inetrc
+}
+
+pkg_postinst() {
+ if [ ! -e ${JABBER_ETC}/ejabberd.cfg ]
+ then
+ einfo "Configuration file has been installed in ${JABBER_ETC}/ejabberd.cfg."
+ einfo "Edit it according to your needs. For configuration instructions,"
+ einfo "please see /usr/share/doc/${PF}/html/guide.html"
+ fi
+ if use ssl ; then
+ einfo "A script to generate a ssl key has been installed in"
+ einfo "${JABBER_ETC}/self-cert.sh . Use it and change the config file to"
+ einfo "point to the full path"
+ fi
+ if ! use web ; then
+ einfo "The web USE flag is off, this will disable the web admin interface,"
+ einfo "if this was not the intention then add web to your USE flags."
+ fi
+
+ if use modproxy ; then
+ einfo "mod_proxy enabled ! http://www.jabber.ru/bugzilla/show_bug.cgi?id=25"
+ fi
+ if use statsdx ; then
+ einfo "Advanced stats enabled ! http://ejabberd.jabber.ru/mod_statsdx"
+ fi
+}
diff --git a/net-im/ejabberd/files/check_pam.diff b/net-im/ejabberd/files/check_pam.diff
new file mode 100644
index 00000000..b03a8803
--- /dev/null
+++ b/net-im/ejabberd/files/check_pam.diff
@@ -0,0 +1,472 @@
+Index: Makefile.in
+===================================================================
+--- Makefile.in (revisión: 626)
++++ Makefile.in (copia de trabajo)
+@@ -27,7 +27,7 @@
+
+ prefix = @prefix@
+
+-SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@
++SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@ @pam@
+ ERLSHLIBS = expat_erl.so
+ SOURCES = $(wildcard *.erl)
+ BEAMS = $(SOURCES:.erl=.beam)
+Index: configure.ac
+===================================================================
+--- configure.ac (revisión: 626)
++++ configure.ac (copia de trabajo)
+@@ -16,6 +16,8 @@
+ AM_WITH_EXPAT
+ #locating zlib
+ AM_WITH_ZLIB
++#locating zlib
++AM_WITH_ZLIB
+
+ # Checks for typedefs, structures, and compiler characteristics.
+ AC_C_CONST
+@@ -27,6 +29,7 @@
+ AC_FUNC_MALLOC
+ AC_HEADER_STDC
+
++AC_MOD_ENABLE(pam, yes)
+ AC_MOD_ENABLE(mod_pubsub, yes)
+ AC_MOD_ENABLE(mod_irc, yes)
+ AC_MOD_ENABLE(mod_muc, yes)
+@@ -55,6 +58,7 @@
+ AC_SUBST(db_type)
+
+ AC_CONFIG_FILES([Makefile
++ $make_pam
+ $make_mod_irc
+ $make_mod_muc
+ $make_mod_pubsub
+Index: pam/stress_pam.erl
+===================================================================
+--- pam/stress_pam.erl (revisión: 0)
++++ pam/stress_pam.erl (revisión: 0)
+@@ -0,0 +1,11 @@
++-module(stress_pam).
++-export([start/0]).
++
++start() ->
++ Port = pam:start(),
++ stress(Port).
++
++stress(Port) ->
++ true = pam:check_password(Port, "user", "password"),
++ timer:sleep(200),
++ stress(Port).
+Index: pam/Makefile.in
+===================================================================
+--- pam/Makefile.in (revisión: 0)
++++ pam/Makefile.in (revisión: 0)
+@@ -0,0 +1,35 @@
++# $Id: Makefile.in 285 2004-11-05 21:14:31Z aleksey $
++
++CC = @CC@
++CFLAGS = @CFLAGS@ @PAM_CFLAGS@ @ERLANG_CFLAGS@
++CPPFLAGS = @CPPFLAGS@
++LDFLAGS = @LDFLAGS@
++LIBS = @LIBS@ @PAM_LIBS@ @ERLANG_LIBS@
++
++SUBDIRS =
++
++ERLSHLIBS = ../pam_drv.so
++
++OUTDIR = ..
++EFLAGS = -I .. -pz ..
++OBJS = \
++ $(OUTDIR)/pam.beam
++
++all: $(OBJS) $(ERLSHLIBS)
++
++$(OUTDIR)/%.beam: %.erl
++ @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
++
++$(ERLSHLIBS): ../%.so: %.c
++ $(CC) -Wall $(CFLAGS) $(LDFLAGS) \
++ $(subst ../,,$(subst .so,.c,$@)) $(LIBS) \
++ -o $@ -fpic -shared
++
++clean:
++ rm -f $(OBJS) $(ERLSHLIBS)
++
++distclean: clean
++ rm -f Makefile
++
++TAGS:
++ etags *.erl
+Index: pam/pam_drv.c
+===================================================================
+--- pam/pam_drv.c (revisión: 0)
++++ pam/pam_drv.c (revisión: 0)
+@@ -0,0 +1,235 @@
++#include <stdio.h>
++#include <string.h>
++#include <erl_driver.h>
++#include <ei.h>
++#include <security/pam_appl.h>
++
++typedef struct _PromptMatch
++{
++ struct _PromptMatch* next;
++ char* prompt;
++ char* response;
++} *PromptMatch;
++
++static PromptMatch _newMatch(PromptMatch list, char* prompt, char* response)
++{
++ PromptMatch result = (PromptMatch)malloc(sizeof(struct _PromptMatch));
++ memset(result, '\0', sizeof(struct _PromptMatch));
++ result->prompt = prompt;
++ result->response = response;
++ if (list != NULL)
++ {
++ list->next = result;
++ return list;
++ }
++ else
++ {
++ return result;
++ }
++}
++
++static char* _findMatch(PromptMatch list, const char* prompt)
++{
++ PromptMatch cur = list;
++ while (cur != NULL)
++ {
++ if (strstr(prompt, cur->prompt) != 0)
++ {
++ return cur->response;
++ }
++ cur = cur->next;
++ }
++ return NULL;
++}
++
++static void _cleanupMatch(PromptMatch list)
++{
++ PromptMatch cur = list;
++ PromptMatch next;
++ while (cur != NULL)
++ {
++ free(cur->prompt);
++ free(cur->response);
++ next = cur->next;
++ free(cur);
++ cur = next;
++ }
++}
++
++static int _handle_pam_conversation(int msgcnt, const struct pam_message** msgs, struct pam_response** res,
++ void* arg)
++{
++ int i;
++ PromptMatch prompts = (PromptMatch)arg;
++ struct pam_response *reply;
++
++ if(msgcnt <= 0)
++ {
++ return PAM_CONV_ERR;
++ }
++
++ // Allocate a response for each message
++ reply = (struct pam_response *) malloc(sizeof(struct pam_response) * msgcnt);
++ memset(reply, 0, sizeof(struct pam_response) * msgcnt);
++
++ // Walk each message from PAM and lookup the response corresponding to each prompt
++ for(i = 0; i < msgcnt; i++)
++ {
++ if(msgs[i]->msg_style == PAM_PROMPT_ECHO_OFF || msgs[i]->msg_style == PAM_PROMPT_ECHO_ON)
++ {
++ // Find the response for the current prompt; if none is found, warn
++ char* response = _findMatch(prompts, msgs[i]->msg);
++ if (response != NULL)
++ {
++ // Copy password into response
++ reply[i].resp = strdup(response);
++ reply[i].resp_retcode = 0;
++ }
++ else
++ {
++ printf("Warning: No prompt found for: %s\n", msgs[i]->msg);
++ reply[i].resp = strdup("Unexpected prompt");
++ reply[i].resp_retcode = 0;
++ }
++ }
++ }
++
++ *res = reply;
++
++ return PAM_SUCCESS;
++}
++
++#define DECODE_STRING(buffer, index, result) \
++ { \
++ int tmp = 0; int size = 0; \
++ ei_get_type(buffer, index, &tmp, &size); \
++ result = malloc(size + 1); \
++ ei_decode_string(buffer, index, result); \
++ }
++
++#define RETURN_INT(value) \
++ { \
++ ErlDrvBinary* b = driver_alloc_binary(1); \
++ rlen = 1; \
++ b->orig_bytes[0] = value; \
++ *rbuf = (char*)b; \
++ return rlen; \
++ }
++
++static int pam_erl_start(ErlDrvPort port, char* command)
++{
++ set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
++ return 0;
++}
++
++static int pam_erl_control(ErlDrvData drv_data, unsigned int command,
++ char *buf, int len,
++ char **rbuf, int rlen)
++{
++ int tmp = 0;
++ int index = 0;
++ int size = 0;
++ char* username = 0;
++ char* password = 0;
++
++ int i = 0;
++ int rc = 0;
++ struct pam_conv conv;
++ pam_handle_t* pam;
++
++ // Parse the version and tuple header
++ ei_decode_version(buf, &index, &tmp);
++ ei_decode_tuple_header(buf, &index, &tmp);
++ if (tmp != 2)
++ {
++ printf("Incorrect number of arguments!\n");
++ RETURN_INT(-1);
++ }
++
++ // The first item in the tuple should be the username
++ DECODE_STRING(buf, &index, username);
++
++ // The second item in the tuple should be a list of tuples
++ // Each tuple in the list consists of the expected prompt string
++ // and the value to return at that prompt
++ ei_decode_list_header(buf, &index, &size);
++
++ // Loop over the list and pull out each prompt/response tuple and
++ // store it into a PromptItem list that will get processed in
++ // the PAM conversation
++ PromptMatch head = NULL;
++ for (i = 0; i < size; i++)
++ {
++ int tuplecnt = 0;
++ char* prompt = 0;
++ char* response = 0;
++
++ ei_decode_tuple_header(buf, &index, &tuplecnt);
++ if (tuplecnt != 2)
++ {
++ printf("Incorrect number of arguments in prompt/response tuple: %d\n", tuplecnt);
++ _cleanupMatch(head);
++ free(username);
++ RETURN_INT(-2);
++ }
++
++ // Decode the prompt from the tuple
++ DECODE_STRING(buf, &index, prompt);
++ DECODE_STRING(buf, &index, response);
++
++ // Add a new prompt item to track this prompt/response pair
++ head = _newMatch(head, prompt, response);
++ }
++
++ // Setup PAM conversation structure; pass in the prompt match list
++ // so we can process the prompts from PAM
++ conv.conv = _handle_pam_conversation;
++ conv.appdata_ptr = head;
++
++ // Spin up PAM
++ rc = pam_start("login", username, &conv, &pam);
++ if (rc != PAM_SUCCESS)
++ {
++ printf("Unable to spin up PAM: %s\n", pam_strerror(NULL, rc));
++ RETURN_INT(-3);
++ }
++
++ // Attempt to authenticate the user
++ rc = pam_authenticate(pam, 0);
++ if (rc != PAM_SUCCESS)
++ {
++ pam_end(pam, rc);
++ printf("Authentication failed for: %s. Error: %s\n", username, pam_strerror(NULL, rc));
++ free(password);
++ free(username);
++ RETURN_INT(1);
++ }
++
++ pam_end(pam, rc);
++
++ _cleanupMatch(head);
++ free(username);
++
++ RETURN_INT(0);
++}
++
++ErlDrvEntry pam_driver_entry = {
++ NULL, /* F_PTR init, N/A */
++ pam_erl_start, /* L_PTR start, called when port is opened */
++ NULL, /* F_PTR stop, called when port is closed */
++ NULL, /* F_PTR output, called when erlang has sent */
++ NULL, /* F_PTR ready_input, called when input descriptor ready */
++ NULL, /* F_PTR ready_output, called when
++ output descriptor ready */
++ "pam_drv", /* char *driver_name, the argument to open_port */
++ NULL, /* F_PTR finish, called when unloaded */
++ NULL, /* handle */
++ pam_erl_control, /* F_PTR control, port_command callback */
++ NULL, /* F_PTR timeout, reserved */
++ NULL /* F_PTR outputv, reserved */
++};
++
++DRIVER_INIT(pam_drv) /* must match name in driver_entry */
++{
++ return &pam_driver_entry;
++}
+Index: pam/pam_test.c
+===================================================================
+--- pam/pam_test.c (revisión: 0)
++++ pam/pam_test.c (revisión: 0)
+@@ -0,0 +1,68 @@
++#include <stdio.h>
++#include <string.h>
++#include <security/pam_appl.h>
++
++static int _handle_pam_conversation(int msgcnt, const struct pam_message** msgs, struct pam_response** res,
++ void* arg)
++{
++ int i;
++ struct pam_response *reply;
++
++ if(msgcnt <= 0)
++ {
++ return PAM_CONV_ERR;
++ }
++
++ // Allocate a response for each message
++ reply = (struct pam_response *) malloc(sizeof(struct pam_response) * msgcnt);
++ memset(reply, 0, sizeof(struct pam_response) * msgcnt);
++
++ // Walk each message from PAM and lookup the response corresponding to each prompt
++ for(i = 0; i < msgcnt; i++)
++ {
++ printf("PAM Message(%d): %s\n", msgs[i]->msg_style, msgs[i]->msg);
++ reply[i].resp = strdup("Unexpected prompt");
++ reply[i].resp_retcode = 0;
++ }
++
++ *res = reply;
++
++ return PAM_SUCCESS;
++}
++
++
++int main(int args, char** argv)
++{
++ struct pam_conv conv;
++ pam_handle_t* pam;
++ int rc;
++
++ if (args < 3)
++ {
++ printf("Please provide a username and service as the first and second arguments!\n");
++ return -1;
++ }
++
++ char* username = argv[1];
++ char* service = argv[2];
++
++ // Setup PAM conversation structure; pass in the prompt match list
++ // so we can process the prompts from PAM
++ conv.conv = _handle_pam_conversation;
++ conv.appdata_ptr = 0;
++
++ // Spin up PAM
++ rc = pam_start(service, username, &conv, &pam);
++ if (rc != PAM_SUCCESS)
++ {
++ printf("Unable to spin up PAM: %s\n", pam_strerror(NULL, rc));
++ return -3;
++ }
++
++ // Attempt to authenticate the user
++ rc = pam_authenticate(pam, 0);
++
++ pam_end(pam, rc);
++
++ return 0;
++}
+Index: pam/pam.erl
+===================================================================
+--- pam/pam.erl (revisión: 0)
++++ pam/pam.erl (revisión: 0)
+@@ -0,0 +1,19 @@
++-module(pam).
++-author('dizzyd@dizzyd.com').
++
++-export([start/0,
++ check_password/3]).
++
++start() ->
++ ok = erl_ddll:load_driver(ejabberd:get_so_path(), pam_drv),
++ open_port({spawn, pam_drv}, [binary]).
++
++check_password(Port, Username, Password) ->
++ Bin = term_to_binary({Username, [{"Password:", Password}]}),
++ Res = port_control(Port, 0, Bin),
++ case Res of
++ <<0>> ->
++ true;
++ _ ->
++ false
++ end.
+Index: aclocal.m4
+===================================================================
+--- aclocal.m4 (revisión: 626)
++++ aclocal.m4 (copia de trabajo)
+@@ -292,3 +292,32 @@
+ fi
+ ])
+ dnl <openssl/>
++
++AC_DEFUN(AM_WITH_PAM,
++[ AC_ARG_WITH(pam, [ --with-pam=PREFIX prefix where PAM is installed ])
++unset PAM_LIBS;
++unset PAM_CFLAGS;
++
++if test x"$with_pam" != x; then
++ PAM_CFLAGS="-I$with_pam/include"
++ PAM_LIBS="-L$with_pam/lib"
++fi
++
++AC_CHECK_LIB(pam, pam_start,
++ [ PAM_LIBS="$PAM_LIBS -lpam" pam_found=yes ],
++ [ pam_found=no ],
++ "$PAM_LIBS")
++
++if test $pam_found = yes; then
++ pam_save_CFLAGS="$CFLAGS"
++ CFLAGS="$PAM_CFLAGS $CFLAGS"
++ AC_CHECK_HEADERS(security/pam_appl.h, ,$pam_found=no)
++ if test $pam_found = no; then
++ AC_MSG_ERROR([Could not find security/pam_appl.h])
++ fi
++ CFLAGS=$pam_save_CFLAGS
++
++ AC_SUBST(PAM_CFLAGS)
++ AC_SUBST(PAM_LIBS)
++fi
++])
diff --git a/net-im/ejabberd/files/digest-ejabberd-1.1.2-r2 b/net-im/ejabberd/files/digest-ejabberd-1.1.2-r2
new file mode 100644
index 00000000..8a35376e
--- /dev/null
+++ b/net-im/ejabberd/files/digest-ejabberd-1.1.2-r2
@@ -0,0 +1,9 @@
+MD5 e925a233029881c7bef1c168ae04af7a ejabberd-1.1.1_httpbind.tar.gz 14496
+RMD160 3dd17d39daf0ff58ed6572559dbc74faadc475cc ejabberd-1.1.1_httpbind.tar.gz 14496
+SHA256 a5e48ce27b9691ef428f82959e6d487e6b293b60fbc415cc88a99bed476e720c ejabberd-1.1.1_httpbind.tar.gz 14496
+MD5 5b947e19e18a6b554bf31d1c95176eb6 ejabberd-1.1.2.tar.gz 836240
+RMD160 e763752e6c5fb46c51b71e265ab2ceda6d043a0d ejabberd-1.1.2.tar.gz 836240
+SHA256 029129a6bcb5d15dbccc5aa756f61c52692eb6882ec7aad0193aa940b6a20bb6 ejabberd-1.1.2.tar.gz 836240
+MD5 5b0a9fd6fb1c595f6812cb33be8d2cd5 mod_presence-0.0.4.tar.gz 51874
+RMD160 f9a724b4c6d58fc83c09f71eacb18b6b0ba77ea9 mod_presence-0.0.4.tar.gz 51874
+SHA256 0f3983a61ac779ddbadde94961db0ce724bff2fb23483654d1be0221a13c5a11 mod_presence-0.0.4.tar.gz 51874
diff --git a/net-im/ejabberd/files/http_binding.patch b/net-im/ejabberd/files/http_binding.patch
new file mode 100644
index 00000000..a3f5c8b9
--- /dev/null
+++ b/net-im/ejabberd/files/http_binding.patch
@@ -0,0 +1,855 @@
+Index: src/ejabberd.cfg.example
+===================================================================
+--- src/ejabberd.cfg.example (Revision 565)
++++ src/ejabberd.cfg.example (Arbeitskopie)
+@@ -124,7 +124,7 @@
+ {5269, ejabberd_s2s_in, [{shaper, s2s_shaper},
+ {max_stanza_size, 131072}
+ ]},
+- {5280, ejabberd_http, [http_poll, web_admin]},
++ {5280, ejabberd_http, [http_poll, http_bind, web_admin]},
+ {8888, ejabberd_service, [{access, all},
+ {hosts, ["icq.localhost", "sms.localhost"],
+ [{password, "secret"}]}]}
+Index: src/web/Makefile.in
+===================================================================
+--- src/web/Makefile.in (Revision 565)
++++ src/web/Makefile.in (Arbeitskopie)
+@@ -15,7 +15,8 @@
+ $(OUTDIR)/ejabberd_http.beam \
+ $(OUTDIR)/ejabberd_web.beam \
+ $(OUTDIR)/ejabberd_web_admin.beam \
+- $(OUTDIR)/ejabberd_http_poll.beam
++ $(OUTDIR)/ejabberd_http_poll.beam \
++ $(OUTDIR)/ejabberd_http_bind.beam
+
+ all: $(OBJS)
+
+Index: src/web/ejabberd_http_bind.erl
+===================================================================
+--- src/web/ejabberd_http_bind.erl (Revision 0)
++++ src/web/ejabberd_http_bind.erl (Revision 0)
+@@ -0,0 +1,654 @@
++%%%----------------------------------------------------------------------
++%%% File : ejabberd_http_bind.erl
++%%% Author : Stefan Strigler <steve@zeank.in-berlin.de>
++%%% Purpose : HTTP Binding support (JEP-0124)
++%%% Created : 21 Sep 2005 by Stefan Strigler <steve@zeank.in-berlin.de>
++%%% Id : $Id: ejabberd_http_bind.erl,v 1.2 2006/01/16 10:47:50 zeank Exp $
++%%%----------------------------------------------------------------------
++
++-module(ejabberd_http_bind).
++-author('steve@zeank.in-berlin.de').
++-vsn('$Revision: 1.2 $ ').
++
++-behaviour(gen_fsm).
++
++%% External exports
++-export([start_link/2,
++ init/1,
++ handle_event/3,
++ handle_sync_event/4,
++ code_change/4,
++ handle_info/3,
++ terminate/3,
++ send/2,
++ setopts/2,
++ controlling_process/2,
++ close/1,
++ process_request/1]).
++
++%-define(ejabberd_debug, true).
++
++-include("ejabberd.hrl").
++-include("jlib.hrl").
++-include("ejabberd_http.hrl").
++
++-record(http_bind, {id, pid, hold, wait}).
++
++%% http binding request
++-record(hbr, {rid,
++ key,
++ in,
++ out}).
++
++-record(state, {id,
++ rid = error,
++ key,
++ output = "",
++ input = "",
++ waiting_input = false,
++ last_receiver,
++ last_poll,
++ ctime = 0,
++ timer,
++ req_list = [] % list of requests
++ }).
++
++
++%-define(DBGFSM, true).
++
++-ifdef(DBGFSM).
++-define(FSMOPTS, [{debug, [trace]}]).
++-else.
++-define(FSMOPTS, []).
++-endif.
++
++-define(MAX_REQUESTS, 2). % number of simultaneous requests
++-define(MIN_POLLING, "2"). % don't poll faster than that or we will shoot you
++-define(MAX_WAIT, 60). % max num of secs to keep a request on hold
++-define(MAX_INACTIVITY, 30000). % msecs to wait before terminating idle sessions
++-define(CT, {"Content-Type", "text/xml; charset=utf-8"}).
++-define(BAD_REQUEST, ?CT).
++
++
++%%%----------------------------------------------------------------------
++%%% API
++%%%----------------------------------------------------------------------
++start(ID, Key) ->
++ mnesia:create_table(http_bind,
++ [{ram_copies, [node()]},
++ {attributes, record_info(fields, http_bind)}]),
++ supervisor:start_child(ejabberd_http_bind_sup, [ID, Key]).
++
++start_link(ID, Key) ->
++ gen_fsm:start_link(?MODULE, [ID, Key], ?FSMOPTS).
++
++send({http_bind, FsmRef}, Packet) ->
++ gen_fsm:sync_send_all_state_event(FsmRef, {send, Packet}).
++
++setopts({http_bind, FsmRef}, Opts) ->
++ case lists:member({active, once}, Opts) of
++ true ->
++ gen_fsm:sync_send_all_state_event(FsmRef, activate);
++ _ ->
++ ok
++ end.
++
++controlling_process(_Socket, _Pid) ->
++ ok.
++
++close({http_bind, FsmRef}) ->
++ catch gen_fsm:sync_send_all_state_event(FsmRef, close).
++
++
++process_request(#request{path = [],
++ data = Data}) ->
++ case catch parse_request(Data) of
++ {ok, ID1, RID, Key, NewKey, Attrs, Packet} ->
++ XmppDomain = xml:get_attr_s("to",Attrs),
++ if
++ (ID1 == "") and (XmppDomain == "") ->
++ {200, [?CT], "<body type='terminate' condition='improper-addressing' xmlns='http://jabber.org/protocol/httpbind'/>"};
++ true ->
++ ID = if
++ (ID1 == "") ->
++ %% create new session
++ NewID = sha:sha(term_to_binary({now(), make_ref()})),
++ {ok, Pid} = start(NewID, Key),
++ 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,
++ mnesia:transaction(
++ fun() ->
++ mnesia:write(#http_bind{id = NewID,
++ pid = Pid,
++ wait = Wait,
++ hold = Hold})
++ end),
++ InPacket = if
++ (XmppDomain /= "") ->
++ ["<stream:stream to='",
++ XmppDomain,
++ "' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>"];
++ true ->
++ ""
++ end,
++ NewID;
++ true ->
++ %% old session
++ Type = xml:get_attr_s("type",Attrs),
++ Wait = ?MAX_WAIT,
++ Hold = (?MAX_REQUESTS - 1),
++ if
++ (Type == "terminate") ->
++ %% terminate session
++ InPacket = Packet ++ "</stream:stream>";
++ true ->
++ InPacket = Packet
++ end,
++ ID1
++ end,
++ ?DEBUG("~n InPacket: ~s ~n", [InPacket]),
++ case http_put(ID, RID, Key, NewKey, Hold, InPacket) of
++ {error, not_exists} ->
++ {404, [?BAD_REQUEST], ""};
++ {error, bad_key} ->
++ case mnesia:dirty_read({http_bind, ID}) of
++ [] ->
++ {404, [?BAD_REQUEST], ""};
++ [#http_bind{pid = FsmRef}] ->
++ gen_fsm:sync_send_all_state_event(FsmRef,stop),
++ {404, [?BAD_REQUEST], ""}
++ end;
++ {error, polling_too_frequently} ->
++ case mnesia:dirty_read({http_bind, ID}) of
++ [] -> %% unlikely! (?)
++ {404, [?BAD_REQUEST], ""};
++ [#http_bind{pid = FsmRef}] ->
++ gen_fsm:sync_send_all_state_event(FsmRef,stop),
++ {403, [?BAD_REQUEST], ""}
++ end;
++ {repeat, OutPacket} ->
++ ?DEBUG("http_put said 'repeat!' ...", []),
++ send_outpacket(ID, OutPacket);
++ ok ->
++ receive_loop(ID,ID1,RID,Wait,Hold,Attrs)
++ end
++ end;
++ _ ->
++ {400, [?BAD_REQUEST], ""}
++ end;
++process_request(_Request) ->
++ {400, [], {xmlelement, "h1", [],
++ [{xmlcdata, "400 Bad Request"}]}}.
++
++receive_loop(ID,ID1,RID,Wait,Hold,Attrs) ->
++ receive
++ after 100 -> ok
++ end,
++ prepare_response(ID,ID1,RID,Wait,Hold,Attrs).
++
++prepare_response(ID,ID1,RID,Wait,Hold,Attrs) ->
++ case http_get(ID,RID) of
++ {error, not_exists} ->
++ {404, [?BAD_REQUEST], ""};
++ {ok, keep_on_hold} ->
++ receive_loop(ID,ID1,RID,Wait,Hold,Attrs);
++ {ok, OutPacket} ->
++ if
++ ID == ID1 ->
++ send_outpacket(ID, OutPacket);
++ true ->
++ To = xml:get_attr_s("to",Attrs),
++ case xml_stream:parse_element(OutPacket++"</stream:stream>") of
++ El when element(1, El) == xmlelement ->
++ {xmlelement, _, OutAttrs, _} = El,
++ AuthID = xml:get_attr_s("id", OutAttrs),
++ StreamError = false;
++ {error, _} ->
++ AuthID = "",
++ StreamError = true
++ end,
++ if
++ To == "" ->
++ {200, [?CT], "<body type='terminate' condition='improper-addressing' xmlns='http://jabber.org/protocol/httpbind'/>"};
++ StreamError == true ->
++ {200, [?CT], "<body type='terminate' condition='host-unknown' xmlns='http://jabber.org/protocol/httpbind'/>"};
++ true ->
++ {200, [?CT],
++ xml:element_to_string(
++ {xmlelement,"body",
++ [{"xmlns",
++ "http://jabber.org/protocol/httpbind"},
++ {"sid",ID},
++ {"wait", integer_to_list(Wait)},
++ {"requests", integer_to_list(Hold+1)},
++ {"inactivity", integer_to_list(trunc(?MAX_INACTIVITY/1000))},
++ {"polling", ?MIN_POLLING},
++ {"authid", AuthID}
++ ],[]})}
++ end
++ end
++ end.
++
++send_outpacket(ID, OutPacket) ->
++ case OutPacket of
++ "" ->
++ {200, [?CT], "<body xmlns='http://jabber.org/protocol/httpbind'/>"};
++ "</stream:stream>" ->
++ case mnesia:dirty_read({http_bind, ID}) of
++ [#http_bind{pid = FsmRef}] ->
++ gen_fsm:sync_send_all_state_event(FsmRef,stop)
++ end,
++ {200, [?CT], "<body xmlns='http://jabber.org/protocol/httpbind'/>"};
++ _ ->
++ case xml_stream:parse_element("<body>"
++ ++ OutPacket
++ ++ "</body>")
++ of
++ El when element(1, El) == xmlelement ->
++ {xmlelement, _, _, OEls} = El,
++ TypedEls = [xml:replace_tag_attr("xmlns","jabber:client",OEl) ||
++ OEl <- OEls],
++ ?DEBUG(" --- outgoing data --- ~n~s~n --- outgoing data END --- ~n",
++ [xml:element_to_string(
++ {xmlelement,"body",
++ [{"xmlns",
++ "http://jabber.org/protocol/httpbind"}],
++ TypedEls})]
++ ),
++ {200, [?CT],
++ xml:element_to_string(
++ {xmlelement,"body",
++ [{"xmlns",
++ "http://jabber.org/protocol/httpbind"}],
++ TypedEls})};
++ {error, _} ->
++ case mnesia:dirty_read({http_bind, ID}) of
++ [#http_bind{pid = FsmRef}] ->
++ gen_fsm:sync_send_all_state_event(FsmRef,stop)
++ end,
++ {200, [?CT],
++ "<body type='terminate' condition='internal-server-error' xmlns='http://jabber.org/protocol/httpbind'/>"}
++ end
++ end.
++
++%%%----------------------------------------------------------------------
++%%% Callback functions from gen_fsm
++%%%----------------------------------------------------------------------
++
++%%----------------------------------------------------------------------
++%% Func: init/1
++%% Returns: {ok, StateName, StateData} |
++%% {ok, StateName, StateData, Timeout} |
++%% ignore |
++%% {stop, StopReason}
++%%----------------------------------------------------------------------
++init([ID, Key]) ->
++ ?INFO_MSG("started: ~p", [{ID, Key}]),
++ Opts = [], % TODO
++ {ok, C2SPid} = ejabberd_c2s:start({?MODULE, {http_bind, self()}}, Opts),
++ ejabberd_c2s:become_controller(C2SPid),
++ Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []),
++ {ok, loop, #state{id = ID,
++ key = Key,
++ timer = Timer}}.
++
++%%----------------------------------------------------------------------
++%% Func: StateName/2
++%% Returns: {next_state, NextStateName, NextStateData} |
++%% {next_state, NextStateName, NextStateData, Timeout} |
++%% {stop, Reason, NewStateData}
++%%----------------------------------------------------------------------
++
++
++%%----------------------------------------------------------------------
++%% Func: StateName/3
++%% 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}
++%%----------------------------------------------------------------------
++%state_name(Event, From, StateData) ->
++% Reply = ok,
++% {reply, Reply, state_name, StateData}.
++
++%%----------------------------------------------------------------------
++%% Func: handle_event/3
++%% Returns: {next_state, NextStateName, NextStateData} |
++%% {next_state, NextStateName, NextStateData, Timeout} |
++%% {stop, Reason, NewStateData}
++%%----------------------------------------------------------------------
++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, Packet}, _From, StateName, StateData) ->
++ Output = [StateData#state.output | Packet],
++ Reply = ok,
++ {reply, Reply, StateName, StateData#state{output = Output}};
++
++handle_sync_event(activate, From, StateName, StateData) ->
++ case StateData#state.input of
++ "" ->
++ {reply, ok, StateName, StateData#state{waiting_input = From}};
++ Input ->
++ From ! {tcp, {http_bind, self()}, list_to_binary(Input)},
++ {reply, ok, StateName, StateData#state{input = "",
++ waiting_input = false,
++ last_receiver = From}}
++ end;
++
++handle_sync_event(stop, _From, _StateName, StateData) ->
++ Reply = ok,
++ {stop, normal, Reply, StateData};
++
++handle_sync_event({http_put, RID, Key, NewKey, Hold, Packet},
++ _From, StateName, StateData) ->
++ %% check if RID valid
++ RidAllow = case RID of
++ error ->
++ false;
++ _ ->
++ case StateData#state.rid of
++ error ->
++ %% first request - nothing saved so far
++ true;
++ OldRID ->
++ ?DEBUG("state.rid/cur rid: ~p/~p", [OldRID, RID]),
++ if
++ (OldRID < RID) and (RID =< (OldRID + Hold + 1)) ->
++ true;
++ (RID =< OldRID) and (RID > OldRID - Hold - 1) ->
++ repeat;
++ true ->
++ false
++ end
++ end
++ end,
++ %% check if key valid
++ KeyAllow = case RidAllow of
++ repeat ->
++ true;
++ false ->
++ false;
++ true ->
++ case StateData#state.key of
++ "" ->
++ true;
++ OldKey ->
++ NextKey = httpd_util:to_lower(hex(
++ binary_to_list(crypto: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,
++ {_,TSec,TMSec} = now(),
++ TNow = TSec*1000*1000 + TMSec,
++ LastPoll = if
++ Packet == "" ->
++ TNow;
++ true ->
++ StateData#state.last_poll
++ end,
++ {MinPoll, _} = string:to_integer(?MIN_POLLING),
++ if
++ (Packet == "") and (TNow - StateData#state.last_poll < MinPoll*1000*1000) ->
++ 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]),
++ [Out | _XS] = [El#hbr.out || El <- StateData#state.req_list, El#hbr.rid == RID],
++ case Out of
++ [[] | OutPacket] ->
++ Reply = {repeat, OutPacket};
++ _ ->
++ Reply = {repeat, Out}
++ end,
++ {reply, Reply, StateName, StateData#state{last_poll = LastPoll}};
++ true ->
++ SaveKey = if
++ NewKey == "" ->
++ Key;
++ true ->
++ NewKey
++ end,
++ ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]),
++
++ %% save request
++ ReqList = [#hbr{rid=RID,
++ key=StateData#state.key,
++ in=StateData#state.input,
++ out=StateData#state.output
++ } |
++ [El || El <- StateData#state.req_list,
++ El#hbr.rid < RID, El#hbr.rid > (RID - 1 - Hold) ]
++ ],
++
++ ?DEBUG("reqlist: ~p", [ReqList]),
++
++ case StateData#state.waiting_input of
++ false ->
++ cancel_timer(StateData#state.timer),
++ Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []),
++ Input = Packet ++ [StateData#state.input],
++ Reply = ok,
++ {reply, Reply, StateName,
++ StateData#state{input = Input,
++ rid = RID,
++ key = SaveKey,
++ ctime = TNow,
++ timer = Timer,
++ last_poll = LastPoll,
++ req_list = ReqList
++ }};
++ {Receiver, _Tag} ->
++ Receiver ! {tcp, {http_bind, self()},
++ list_to_binary(Packet)},
++ cancel_timer(StateData#state.timer),
++ Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []),
++ Reply = ok,
++ {reply, Reply, StateName,
++ StateData#state{waiting_input = false,
++ last_receiver = Receiver,
++ input = "",
++ rid = RID,
++ key = SaveKey,
++ ctime = TNow,
++ timer = Timer,
++ last_poll = LastPoll,
++ req_list = ReqList
++ }}
++ end
++ end;
++ true ->
++ Reply = {error, bad_key},
++ {reply, Reply, StateName, StateData}
++ end;
++
++handle_sync_event({http_get, RID, Wait, Hold}, _From, StateName, StateData) ->
++ {_,TSec,TMSec} = now(),
++ TNow = TSec*1000*1000 + TMSec,
++%% ?DEBUG("Wait/Hold/cTime/Now: ~p/~p/~p/~p", [Wait, Hold, StateData#state.ctime,TNow]),
++ cancel_timer(StateData#state.timer),
++ Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []),
++ if
++ (Hold > 0) and
++ (StateData#state.output == "") and
++ ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and
++ (StateData#state.rid == RID) ->
++ Output = StateData#state.output,
++ ReqList = StateData#state.req_list,
++ Reply = {ok, keep_on_hold};
++ true ->
++ case StateData#state.output of
++ [[]| OutPacket] ->
++ Reply = {ok, OutPacket};
++ _ ->
++ Reply = {ok, StateData#state.output}
++ end,
++ %% save request
++ ReqList = [#hbr{rid=RID,
++ key=StateData#state.key,
++ in=StateData#state.input,
++ out=StateData#state.output
++ } |
++ [El || El <- StateData#state.req_list,
++ El#hbr.rid /= RID ]
++ ],
++ Output = ""
++ end,
++ {reply, Reply, StateName, StateData#state{output = Output,
++ timer = Timer,
++ req_list = ReqList}};
++
++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}
++%%----------------------------------------------------------------------
++handle_info({timeout, Timer, _}, _StateName,
++ #state{timer = Timer} = StateData) ->
++ ?DEBUG("ding dong", []),
++ {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:transaction(
++ fun() ->
++ mnesia:delete({http_bind, StateData#state.id})
++ end),
++ case StateData#state.waiting_input of
++ false ->
++ case StateData#state.last_receiver of
++ undefined -> ok;
++ Receiver -> Receiver ! {tcp_closed, {http_bind, self()}}
++ end;
++ {Receiver, _Tag} -> Receiver ! {tcp_closed, {http_bind, self()}}
++ end,
++ ok.
++
++%%%----------------------------------------------------------------------
++%%% Internal functions
++%%%----------------------------------------------------------------------
++
++
++http_put(ID, RID, Key, NewKey, Hold, Packet) ->
++ case mnesia:dirty_read({http_bind, ID}) of
++ [] ->
++ {error, not_exists};
++ [#http_bind{pid = FsmRef}] ->
++ gen_fsm:sync_send_all_state_event(
++ FsmRef, {http_put, RID, Key, NewKey, Hold, Packet})
++ end.
++
++http_get(ID,RID) ->
++ case mnesia:dirty_read({http_bind, ID}) of
++ [] ->
++ {error, not_exists};
++ [#http_bind{pid = FsmRef, wait = Wait, hold = Hold}] ->
++ gen_fsm:sync_send_all_state_event(FsmRef, {http_get, RID, Wait, Hold})
++ end.
++
++
++parse_request(Data) ->
++ ?DEBUG("--- incoming data --- ~n~s~n --- END incoming data END --- ",[Data]),
++ case xml_stream:parse_element(Data) of
++ El when element(1, El) == xmlelement ->
++ {xmlelement, Name, Attrs, Els} = El,
++ ID = xml:get_attr_s("sid",Attrs),
++ {RID,_X} = string:to_integer(xml:get_attr_s("rid",Attrs)),
++ Key = xml:get_attr_s("key",Attrs),
++ NewKey = xml:get_attr_s("newkey",Attrs),
++ Xmlns = xml:get_attr_s("xmlns",Attrs),
++ Packet = [xml:element_to_string(xml:remove_tag_attr("xmlns",E)) || E <- Els],
++ ?DEBUG(" --- incoming packet --- ~n~s~n --- END incoming packet END --- ", [Packet]),
++ if
++ Name /= "body" ->
++ {error, bad_request};
++ Xmlns /= "http://jabber.org/protocol/httpbind" ->
++ {error, bad_request};
++ true ->
++ {ok, ID, RID, Key, NewKey, Attrs, Packet}
++ end;
++ {error, _Reason} ->
++ {error, bad_request}
++ end.
++
++cancel_timer(Timer) ->
++ erlang:cancel_timer(Timer),
++ receive
++ {timeout, Timer, _} ->
++ ok
++ after 0 ->
++ ok
++ end.
++
++hex(Bin) when binary(Bin) -> hex(binary_to_list(Bin));
++hex([]) -> "";
++hex([H|T]) ->
++ [A,B] = if
++ H == 0 -> "00";
++ H < 16 -> [$0,element(H,{$1,$2,$3,$4,$5,$6,$7,$8,$9,$a,$b,$c,$d,$e,$f})];
++ true -> erlang:integer_to_list(H,16)
++ end,
++ [A,B|hex(T)].
+Index: src/web/ejabberd_web.erl
+===================================================================
+--- src/web/ejabberd_web.erl (Revision 565)
++++ src/web/ejabberd_web.erl (Arbeitskopie)
+@@ -49,7 +49,7 @@
+ {"value", Value}])).
+
+
+-process_get({_, true},
++process_get({_, _, true},
+ #request{auth = Auth,
+ path = ["admin", "server", SHost | RPath],
+ q = Query,
+@@ -94,7 +94,7 @@
+ {404, [], make_xhtml([?XC("h1", "Not found")])}
+ end;
+
+-process_get({_, true},
++process_get({_, _, true},
+ #request{auth = Auth,
+ path = ["admin" | RPath],
+ q = Query,
+@@ -133,12 +133,19 @@
+ [{xmlcdata, "401 Unauthorized"}]}])}
+ end;
+
+-process_get({true, _},
++process_get({true, _, _},
+ #request{path = ["http-poll" | RPath],
+ q = _Query,
+ lang = _Lang} = Request) ->
+ ejabberd_http_poll:process_request(Request#request{path = RPath});
+
++process_get({_, true, _},
++ #request{us = _US,
++ path = ["http-bind" | RPath],
++ q = _Query,
++ lang = _Lang} = Request) ->
++ ejabberd_http_bind:process_request(Request#request{path = RPath});
++
+ process_get(_, _Request) ->
+ {404, [], make_xhtml([?XC("h1", "Not found")])}.
+
+Index: src/web/ejabberd_http.erl
+===================================================================
+--- src/web/ejabberd_http.erl (Revision 565)
++++ src/web/ejabberd_http.erl (Arbeitskopie)
+@@ -30,6 +30,7 @@
+ request_keepalive,
+ request_content_length,
+ request_lang = "en",
++ use_http_bind = false,
+ use_http_poll = false,
+ use_web_admin = false,
+ end_of_request = false,
+@@ -70,15 +71,17 @@
+ _ ->
+ ok
+ end,
++ UseHTTPBind = lists:member(http_bind, Opts),
+ UseHTTPPoll = lists:member(http_poll, Opts),
+ UseWebAdmin = lists:member(web_admin, Opts),
+- ?DEBUG("S: ~p~n", [{UseHTTPPoll, UseWebAdmin}]),
++ ?DEBUG("S: ~p~n", [{UseHTTPPoll, UseHTTPBind, UseWebAdmin}]),
+ ?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
+ {ok, proc_lib:spawn_link(ejabberd_http,
+ receive_headers,
+ [#state{sockmod = SockMod1,
+ socket = Socket1,
+ use_http_poll = UseHTTPPoll,
++ use_http_bind = UseHTTPBind,
+ use_web_admin = UseWebAdmin}])}.
+
+
+@@ -185,6 +188,7 @@
+ #state{sockmod = SockMod,
+ socket = Socket,
+ use_http_poll = State#state.use_http_poll,
++ use_http_bind = State#state.use_http_bind,
+ use_web_admin = State#state.use_web_admin};
+ _ ->
+ #state{end_of_request = true}
+@@ -200,6 +204,7 @@
+ request_auth = Auth,
+ request_lang = Lang,
+ use_http_poll = UseHTTPPoll,
++ use_http_bind = UseHTTPBind,
+ use_web_admin = UseWebAdmin} = State) ->
+ case (catch url_decode_q_split(Path)) of
+ {'EXIT', _} ->
+@@ -217,7 +222,7 @@
+ q = LQuery,
+ auth = Auth,
+ lang = Lang},
+- case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
++ case ejabberd_web:process_get({UseHTTPPoll, UseHTTPBind, UseWebAdmin},
+ Request) of
+ El when element(1, El) == xmlelement ->
+ make_xhtml_output(State, 200, [], El);
+@@ -240,6 +245,7 @@
+ sockmod = SockMod,
+ socket = Socket,
+ use_http_poll = UseHTTPPoll,
++ use_http_bind = UseHTTPBind,
+ use_web_admin = UseWebAdmin} = State)
+ when is_integer(Len) ->
+ case SockMod of
+@@ -267,7 +273,7 @@
+ auth = Auth,
+ data = Data,
+ lang = Lang},
+- case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin},
++ case ejabberd_web:process_get({UseHTTPPoll, UseHTTPBind, UseWebAdmin},
+ Request) of
+ El when element(1, El) == xmlelement ->
+ make_xhtml_output(State, 200, [], El);
+Index: src/xml.erl
+===================================================================
+--- src/xml.erl (Revision 565)
++++ src/xml.erl (Arbeitskopie)
+@@ -18,6 +18,7 @@
+ get_tag_attr/2, get_tag_attr_s/2,
+ get_subtag/2,
+ get_path_s/2,
++ remove_tag_attr/2,
+ replace_tag_attr/3]).
+
+ %element_to_string(El) ->
+@@ -224,6 +225,14 @@
+ get_path_s(El, [cdata]) ->
+ get_tag_cdata(El).
+
++remove_tag_attr(Attr, El) ->
++ case El of
++ {xmlelement, Name, Attrs, Els} ->
++ Attrs1 = lists:keydelete(Attr, 1, Attrs),
++ {xmlelement, Name, Attrs1, Els};
++ _ ->
++ El
++ end.
+
+ replace_tag_attr(Attr, Value, {xmlelement, Name, Attrs, Els}) ->
+ Attrs1 = lists:keydelete(Attr, 1, Attrs),
+Index: src/ejabberd_sup.erl
+===================================================================
+--- src/ejabberd_sup.erl (Revision 565)
++++ src/ejabberd_sup.erl (Arbeitskopie)
+@@ -123,6 +123,14 @@
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
++ HTTPBindSupervisor =
++ {ejabberd_http_bind_sup,
++ {ejabberd_tmp_sup, start_link,
++ [ejabberd_http_bind_sup, ejabberd_http_bind]},
++ permanent,
++ infinity,
++ supervisor,
++ [ejabberd_tmp_sup]},
+ IQSupervisor =
+ {ejabberd_iq_sup,
+ {ejabberd_tmp_sup, start_link,
+@@ -145,6 +153,7 @@
+ ServiceSupervisor,
+ HTTPSupervisor,
+ HTTPPollSupervisor,
++ HTTPBindSupervisor,
+ IQSupervisor,
+ Listener]}}.
diff --git a/net-im/ejabberd/files/mod_pep.patch b/net-im/ejabberd/files/mod_pep.patch
new file mode 100644
index 00000000..a65a344c
--- /dev/null
+++ b/net-im/ejabberd/files/mod_pep.patch
@@ -0,0 +1,2319 @@
+--- orig/src/ejabberd_sm.erl
++++ mod/src/ejabberd_sm.erl
+@@ -353,7 +353,9 @@
+ true ->
+ ok
+ end
+- end, PResources);
++ end, PResources),
++ ejabberd_hooks:run(incoming_presence_hook, LServer,
++ [From, To, Packet]);
+ true ->
+ ok
+ end;
+--- orig/src/jlib.hrl
++++ mod/src/jlib.hrl
+@@ -33,6 +33,7 @@
+ -define(NS_PUBSUB_EVENT, "http://jabber.org/protocol/pubsub#event").
+ -define(NS_PUBSUB_OWNER, "http://jabber.org/protocol/pubsub#owner").
+ -define(NS_PUBSUB_NMI, "http://jabber.org/protocol/pubsub#node-meta-info").
++-define(NS_PUBSUB_ERRORS,"http://jabber.org/protocol/pubsub#errors").
+ -define(NS_COMMANDS, "http://jabber.org/protocol/commands").
+
+ -define(NS_EJABBERD_CONFIG, "ejabberd:config").
+--- orig/src/mod_disco.erl
++++ mod/src/mod_disco.erl
+@@ -168,11 +168,12 @@
+ end
+ end.
+
+-get_local_identity(_Acc, _From, _To, [], _Lang) ->
+- [{xmlelement, "identity",
+- [{"category", "server"},
+- {"type", "im"},
+- {"name", "ejabberd"}], []}];
++get_local_identity(Acc, _From, _To, [], _Lang) ->
++ Acc ++
++ [{xmlelement, "identity",
++ [{"category", "server"},
++ {"type", "im"},
++ {"name", "ejabberd"}], []}];
+
+ get_local_identity(Acc, _From, _To, _Node, _Lang) ->
+ Acc.
+--- orig/src/mod_pubsub/mod_pubsub.erl
++++ mod/src/mod_pubsub/mod_pubsub.erl
+@@ -18,13 +18,12 @@
+ start/2,
+ stop/1]).
+
+--export([delete_item/3,
+- set_entities/4,
+- delete_node/2,
+- create_new_node/2,
+- subscribe_node/3,
+- get_node_config/4,
+- set_node_config/4]).
++-export([incoming_presence/3]).
++
++-export([disco_local_identity/5,
++ iq_pep_local/3,
++ iq_pep_sm/3,
++ pep_disco_items/5]).
+
+ %% gen_server callbacks
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+@@ -36,10 +35,13 @@
+ -record(state, {host, server_host, access}).
+
+ -define(DICT, dict).
++%% XXX: this is currently a hard limit. Would be nice to have it
++%% configurable in certain cases.
+ -define(MAXITEMS, 20).
+ -define(MAX_PAYLOAD_SIZE, 100000).
+
+ -record(pubsub_node, {host_node, host_parent, info}).
++-record(pep_node, {owner_node, info}). %owner is {luser, lserver, ""}
+ -record(nodeinfo, {items = [],
+ options = [],
+ entities = ?DICT:new()
+@@ -48,10 +50,24 @@
+ subscription = none}).
+ -record(item, {id, publisher, payload}).
+
++-record(pubsub_presence, {to_from, resource}).
++%% to_from is {ToLUser, ToLServer, FromLUser, FromLServer}.
++
++get_node_info(#pubsub_node{info = Info}) -> Info;
++get_node_info(#pep_node{info = Info}) -> Info.
++
++set_node_info(#pubsub_node{} = N, NewInfo) -> N#pubsub_node{info = NewInfo};
++set_node_info(#pep_node{} = N, NewInfo) -> N#pep_node{info = NewInfo}.
++
++get_node_name(#pubsub_node{host_node = {_Host, Node}}) -> Node;
++get_node_name(#pep_node{owner_node = {_Owner, Node}}) -> Node.
++
+ -define(PROCNAME, ejabberd_mod_pubsub).
+ -define(MYJID, #jid{user = "", server = Host, resource = "",
+ luser = "", lserver = Host, lresource = ""}).
+
++-define(NS_PUBSUB_SUB_AUTH, "http://jabber.org/protocol/pubsub#subscribe_authorization").
++
+ %%====================================================================
+ %% API
+ %%====================================================================
+@@ -79,29 +95,9 @@
+ gen_server:call(Proc, stop),
+ supervisor:stop_child(ejabberd_sup, Proc).
+
+-delete_item(From, Node, ItemID) ->
+- delete_item(get_host(), From, Node, ItemID).
+-
+-delete_node(From, Node) ->
+- delete_node(get_host(), From, Node).
+-
+-create_new_node(Node, From) ->
+- create_new_node(get_host(), Node, From).
+-
+-subscribe_node(From, JID, Node) ->
+- subscribe_node(get_host(), From, JID, Node).
+-
+-set_node_config(From, Node, Els, Lang) ->
+- set_node_config(get_host(), From, Node, Els, Lang).
+-
+-get_host() ->
+- ejabberd_mod_pubsub ! {get_host, self()},
+- receive
+- {pubsub_host, Host} ->
+- Host
+- after 5000 ->
+- timeout
+- end.
++incoming_presence(From, #jid{lserver = Host} = To, Packet) ->
++ Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
++ gen_server:cast(Proc, {presence, From, To, Packet}).
+
+ %%====================================================================
+ %% gen_server callbacks
+@@ -118,12 +114,36 @@
+ mnesia:create_table(pubsub_node,
+ [{disc_only_copies, [node()]},
+ {attributes, record_info(fields, pubsub_node)}]),
++ mnesia:create_table(pep_node,
++ [{disc_only_copies, [node()]},
++ {attributes, record_info(fields, pep_node)}]),
++ mnesia:create_table(pubsub_presence,
++ [{ram_copies, [node()]},
++ {attributes, record_info(fields, pubsub_presence)},
++ {type, bag}]),
++
+ Host = gen_mod:get_opt(host, Opts, "pubsub." ++ ServerHost),
+ update_table(Host),
+ mnesia:add_table_index(pubsub_node, host_parent),
+ ServedHosts = gen_mod:get_opt(served_hosts, Opts, []),
+ Access = gen_mod:get_opt(access_createnode, Opts, all),
+
++ mod_disco:register_feature(ServerHost, ?NS_PUBSUB),
++ ejabberd_hooks:add(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
++ ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE, pep_disco_items, 50),
++
++ IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
++ gen_iq_handler:add_iq_handler(ejabberd_local, ServerHost, ?NS_PUBSUB,
++ ?MODULE, iq_pep_local, IQDisc),
++ gen_iq_handler:add_iq_handler(ejabberd_local, ServerHost, ?NS_PUBSUB_OWNER,
++ ?MODULE, iq_pep_local, IQDisc),
++ gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB,
++ ?MODULE, iq_pep_sm, IQDisc),
++ gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER,
++ ?MODULE, iq_pep_sm, IQDisc),
++
++ ejabberd_hooks:add(incoming_presence_hook, ServerHost, ?MODULE, incoming_presence, 50),
++
+ ejabberd_router:register_route(Host),
+ create_new_node(Host, ["pubsub"], ?MYJID),
+ create_new_node(Host, ["pubsub", "nodes"], ?MYJID),
+@@ -132,8 +152,6 @@
+ lists:foreach(fun(H) ->
+ create_new_node(Host, ["home", H], ?MYJID)
+ end, ServedHosts),
+- ets:new(gen_mod:get_module_proc(Host, pubsub_presence),
+- [set, named_table]),
+ {ok, #state{host = Host, server_host = ServerHost, access = Access}}.
+
+ %%--------------------------------------------------------------------
+@@ -154,6 +172,86 @@
+ %% {stop, Reason, State}
+ %% Description: Handling cast messages
+ %%--------------------------------------------------------------------
++handle_cast({presence, From, To, Packet}, State) ->
++ %% When we get available presence from a subscriber, send the last
++ %% published item.
++ Priority = case xml:get_subtag(Packet, "priority") of
++ false ->
++ 0;
++ SubEl ->
++ case catch list_to_integer(xml:get_tag_cdata(SubEl)) of
++ P when is_integer(P) ->
++ P;
++ _ ->
++ 0
++ end
++ end,
++ PType = xml:get_tag_attr_s("type", Packet),
++ Type = case PType of
++ "" ->
++ if Priority < 0 ->
++ unavailable;
++ true ->
++ available
++ end;
++ "unavailable" -> unavailable;
++ "error" -> unavailable;
++ _ -> none
++ end,
++ LJID = jlib:jid_tolower(jlib:jid_remove_resource(To)),
++ PreviouslyAvailable =
++ lists:member(From#jid.lresource,
++ get_present_resources(element(1, LJID), element(2, LJID),
++ From#jid.luser, From#jid.lserver)),
++ Key = {element(1, LJID), element(2, LJID), From#jid.luser, From#jid.lserver},
++ Record = #pubsub_presence{to_from = Key, resource = From#jid.lresource},
++ Host = case LJID of
++ {"", LServer, ""} ->
++ LServer;
++ _ ->
++ LJID
++ end,
++ case Type of
++ available ->
++ mnesia:dirty_write(Record);
++ unavailable ->
++ mnesia:dirty_delete_object(Record);
++ _ ->
++ ok
++ end,
++ if PreviouslyAvailable == false, Type == available ->
++ %% A new resource is available. Loop through all nodes
++ %% and see if the contact is subscribed, and if so, and if
++ %% the node is so configured, send the last item.
++ Match = case Host of
++ {_, _, _} ->
++ #pep_node{owner_node = {LJID, '_'}, _ = '_'};
++ _ ->
++ #pubsub_node{host_node = {LJID, '_'}, _ = '_'}
++ end,
++ case catch mnesia:dirty_match_object(Match) of
++ {'EXIT', Reason} ->
++ ?ERROR_MSG("~p", [Reason]);
++ Nodes ->
++ lists:foreach(
++ fun(N) ->
++ Node = get_node_name(N),
++ Info = get_node_info(N),
++ Subscription = get_subscription(Info, From),
++ SendWhen = get_node_option(Info, send_last_published_item),
++ if Subscription /= none, Subscription /= pending,
++ SendWhen == on_sub_and_presence ->
++ send_last_published_item(jlib:jid_tolower(From), Host, Node, Info);
++ true ->
++ ok
++ end
++ end, Nodes)
++ end,
++ {noreply, State};
++ true ->
++ {noreply, State}
++ end;
++
+ handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+@@ -183,7 +281,16 @@
+ %% The return value is ignored.
+ %%--------------------------------------------------------------------
+ terminate(_Reason, State) ->
+- ejabberd_router:unregister_route(State#state.host),
++ #state{host = Host, server_host = ServerHost} = State,
++ mod_disco:unregister_feature(ServerHost, ?NS_PUBSUB),
++ ejabberd_hooks:delete(disco_local_identity, ServerHost, ?MODULE, disco_local_identity, 75),
++ ejabberd_hooks:delete(disco_sm_items, ServerHost, ?MODULE, pep_disco_items, 50),
++ gen_iq_handler:remove_iq_handler(ejabberd_local, ServerHost, ?NS_PUBSUB),
++ gen_iq_handler:remove_iq_handler(ejabberd_local, ServerHost, ?NS_PUBSUB_OWNER),
++ gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB),
++ gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER),
++ ejabberd_hooks:delete(incoming_presence_hook, ServerHost, ?MODULE, incoming_presence, 50),
++ ejabberd_router:unregister_route(Host),
+ ok.
+
+ %%--------------------------------------------------------------------
+@@ -197,13 +304,13 @@
+ %%% Internal functions
+ %%--------------------------------------------------------------------
+ do_route(Host, ServerHost, Access, From, To, Packet) ->
+- {xmlelement, Name, Attrs, Els} = Packet,
++ {xmlelement, Name, Attrs, _Els} = Packet,
+ case To of
+ #jid{luser = "", lresource = ""} ->
+ case Name of
+ "iq" ->
+ case jlib:iq_query_info(Packet) of
+- #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS,
++ #iq{type = get, xmlns = ?NS_DISCO_INFO,
+ sub_el = SubEl} = IQ ->
+ {xmlelement, _, QAttrs, _} = SubEl,
+ Node = xml:get_attr_s("node", QAttrs),
+@@ -214,7 +321,7 @@
+ ejabberd_router:route(To,
+ From,
+ jlib:iq_to_xml(Res));
+- #iq{type = get, xmlns = ?NS_DISCO_ITEMS = XMLNS,
++ #iq{type = get, xmlns = ?NS_DISCO_ITEMS,
+ sub_el = SubEl} = IQ ->
+ {xmlelement, _, QAttrs, _} = SubEl,
+ Node = xml:get_attr_s("node", QAttrs),
+@@ -231,7 +338,7 @@
+ Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+- #iq{type = Type, xmlns = ?NS_PUBSUB = XMLNS,
++ #iq{type = Type, xmlns = ?NS_PUBSUB,
+ sub_el = SubEl} = IQ ->
+ Res =
+ case iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) of
+@@ -244,7 +351,7 @@
+ Packet, Error)
+ end,
+ ejabberd_router:route(To, From, Res);
+- #iq{type = Type, xmlns = ?NS_PUBSUB_OWNER = XMLNS,
++ #iq{type = Type, xmlns = ?NS_PUBSUB_OWNER,
+ lang = Lang, sub_el = SubEl} = IQ ->
+ Res =
+ case iq_pubsub_owner(
+@@ -259,7 +366,7 @@
+ end,
+ ejabberd_router:route(To, From, Res);
+ #iq{type = get, xmlns = ?NS_VCARD = XMLNS,
+- lang = Lang, sub_el = SubEl} = IQ ->
++ lang = Lang} = IQ ->
+ Res = IQ#iq{type = result,
+ sub_el = [{xmlelement, "vCard",
+ [{"xmlns", XMLNS}],
+@@ -276,20 +383,27 @@
+ ok
+ end;
+ "presence" ->
+- Type = xml:get_attr_s("type", Attrs),
+- if
+- (Type == "unavailable") or (Type == "error") ->
+- ets:delete(
+- gen_mod:get_module_proc(Host, pubsub_presence),
+- {From#jid.luser, From#jid.lserver});
+- true ->
+- ets:insert(
+- gen_mod:get_module_proc(Host, pubsub_presence),
+- {{From#jid.luser, From#jid.lserver}, []})
+- end,
++ %% XXX: subscriptions?
++ incoming_presence(From, To, Packet),
+ ok;
+- _ ->
+- ok
++ "message" ->
++ %% So why would anyone want to send messages to a
++ %% pubsub service? Subscription authorization
++ %% (section 8.6).
++ case xml:get_attr_s("type", Attrs) of
++ "error" ->
++ ok;
++ _ ->
++ case find_authorization_response(Packet) of
++ none ->
++ ok;
++ invalid ->
++ ejabberd_router:route(To, From,
++ jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST));
++ XFields ->
++ handle_authorization_response(From, To, Host, Packet, XFields)
++ end
++ end
+ end;
+ _ ->
+ case xml:get_attr_s("type", Attrs) of
+@@ -307,28 +421,121 @@
+
+
+ node_to_string(Node) ->
+- string:strip(lists:flatten(lists:map(fun(S) -> [S, "/"] end, Node)),
+- right, $/).
++ %% Flat (PEP) or normal node?
++ case Node of
++ [[_ | _] | _] ->
++ string:strip(lists:flatten(lists:map(fun(S) -> [S, "/"] end, Node)),
++ right, $/);
++ [Head | _] when is_integer(Head) ->
++ Node
++ end.
++
++disco_local_identity(Acc, _From, _To, [], _Lang) ->
++ Acc ++
++ [{xmlelement, "identity",
++ [{"category", "pubsub"},
++ {"type", "pep"}], []}];
++disco_local_identity(Acc, _From, _To, _Node, _Lang) ->
++ Acc.
++
++get_table(Host) ->
++ case Host of
++ {_, _, _} -> pep_node;
++ _ -> pubsub_node
++ end.
++
++get_sender(Host) ->
++ case Host of
++ {_, _, _} ->
++ jlib:make_jid(Host);
++ _ ->
++ jlib:make_jid("", Host, "")
++ end.
++
++iq_pep_local(From, To,
++ #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
++ ServerHost = To#jid.lserver,
++ %% Accept IQs to server only from our own users.
++ if From#jid.lserver /= ServerHost ->
++ IQ#iq{type = error, sub_el = [?ERR_FORBIDDEN, SubEl]};
++ true ->
++ LOwner = jlib:jid_tolower(jlib:jid_remove_resource(From)),
++ Res = case XMLNS of
++ ?NS_PUBSUB ->
++ %% XXX: "access all" correct? it corresponds to access_createnode.
++ iq_pubsub(LOwner, ServerHost, From, Type, SubEl, all);
++ ?NS_PUBSUB_OWNER ->
++ iq_pubsub_owner(LOwner, From, Type, Lang, SubEl)
++ end,
++ case Res of
++ {result, IQRes} ->
++ IQ#iq{type = result, sub_el = IQRes};
++ {error, Error} ->
++ IQ#iq{type = error, sub_el = [Error, SubEl]}
++ end
++ end.
+
++iq_pep_sm(From, To,
++ #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) ->
++ ServerHost = To#jid.lserver,
++ LOwner = jlib:jid_tolower(jlib:jid_remove_resource(To)),
++ Res = case XMLNS of
++ ?NS_PUBSUB ->
++ iq_pubsub(LOwner, ServerHost, From, Type, SubEl, all);
++ ?NS_PUBSUB_OWNER ->
++ iq_pubsub_owner(LOwner, From, Type, Lang, SubEl)
++ end,
++ case Res of
++ {result, IQRes} ->
++ IQ#iq{type = result, sub_el = IQRes};
++ {error, Error} ->
++ IQ#iq{type = error, sub_el = [Error, SubEl]}
++ end.
+
+ iq_disco_info(SNode) ->
+ Node = string:tokens(SNode, "/"),
+ case Node of
+ [] ->
++ PubsubFeatures =
++ ["config-node",
++ "create-and-configure",
++ "create-nodes",
++ "delete-nodes",
++ %% "get-pending",
++ "instant-nodes",
++ "item-ids",
++ %% "manage-subscriptions",
++ %% "modify-affiliations",
++ "outcast-affiliation",
++ "persistent-items",
++ "presence-notifications",
++ "publish",
++ "publisher-affiliation",
++ "purge-nodes",
++ "retract-items",
++ "retrieve-affiliations",
++ %% "retrieve-default",
++ "retrieve-items",
++ "retrieve-subscriptions",
++ "subscribe"
++ %% , "subscription-notifications"
++ ],
+ [{xmlelement, "identity",
+ [{"category", "pubsub"},
+- {"type", "generic"},
++ {"type", "service"},
+ {"name", "ejabberd/mod_pubsub"}], []},
+ {xmlelement, "feature", [{"var", ?NS_PUBSUB}], []},
+- {xmlelement, "feature", [{"var", ?NS_PUBSUB_EVENT}], []},
+- {xmlelement, "feature", [{"var", ?NS_PUBSUB_OWNER}], []},
+- {xmlelement, "feature", [{"var", ?NS_VCARD}], []}];
++ {xmlelement, "feature", [{"var", ?NS_VCARD}], []}] ++
++ lists:map(fun(Feature) ->
++ {xmlelement, "feature",
++ [{"var", ?NS_PUBSUB++"#"++Feature}], []}
++ end, PubsubFeatures);
+ _ ->
+ % TODO
+ []
+ end.
+
+-iq_disco_items(Host, From, SNode) ->
++iq_disco_items(Host, _From, SNode) ->
+ {Node,ItemID} = case SNode of
+ [] ->
+ {[],none};
+@@ -341,7 +548,7 @@
+ end,
+ {NodeList, ItemName}
+ end,
+- NodeFull = string:tokens(SNode,"/"),
++ %%NodeFull = string:tokens(SNode,"/"),
+ F = fun() ->
+ case mnesia:read({pubsub_node, {Host, Node}}) of
+ [#pubsub_node{info = Info}] ->
+@@ -402,6 +609,32 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
+
++pep_disco_items(Acc, _From, To, "", _Lang) ->
++ LJID = jlib:jid_tolower(jlib:jid_remove_resource(To)),
++ Match = #pep_node{owner_node = {LJID, '_'}, _ = '_'},
++ case catch mnesia:dirty_match_object(Match) of
++ {'EXIT', Reason} ->
++ ?ERROR_MSG("~p", [Reason]),
++ Acc;
++ [] ->
++ Acc;
++ Nodes ->
++ Items = case Acc of
++ {result, I} -> I;
++ _ -> []
++ end,
++ NodeItems = lists:map(
++ fun(#pep_node{owner_node = {_, Node}}) ->
++ {xmlelement, "item",
++ [{"jid", jlib:jid_to_string(LJID)},
++ {"node", node_to_string(Node)}],
++ []}
++ end, Nodes),
++ {result, NodeItems ++ Items}
++ end;
++pep_disco_items(Acc, _From, _To, _Node, _Lang) ->
++ Acc.
++
+ iq_get_vcard(Lang) ->
+ [{xmlelement, "FN", [],
+ [{xmlcdata, "ejabberd/mod_pubsub"}]},
+@@ -416,14 +649,32 @@
+
+
+ iq_pubsub(Host, ServerHost, From, Type, SubEl, Access) ->
++ %% Host may be a jid tuple, in which case we use PEP.
+ {xmlelement, _, _, SubEls} = SubEl,
+- case xml:remove_cdata(SubEls) of
++ WithoutCdata = xml:remove_cdata(SubEls),
++ Configuration = lists:filter(fun({xmlelement, Name, _, _}) ->
++ Name == "configure"
++ end, WithoutCdata),
++ Action = WithoutCdata -- Configuration,
++ case Action of
+ [{xmlelement, Name, Attrs, Els}] ->
+- SNode = xml:get_attr_s("node", Attrs),
+- Node = string:tokens(SNode, "/"),
++ %% For PEP, there is no node hierarchy.
++ Node = case Host of
++ {_, _, _} ->
++ xml:get_attr_s("node", Attrs);
++ _ ->
++ SNode = xml:get_attr_s("node", Attrs),
++ string:tokens(SNode, "/")
++ end,
+ case {Type, Name} of
+ {set, "create"} ->
+- create_new_node(Host, Node, From, ServerHost, Access);
++ case Configuration of
++ [{xmlelement, "configure", _, Config}] ->
++ create_new_node(Host, Node, From, ServerHost, Access, Config);
++ _ ->
++ ?INFO_MSG("Invalid configuration: ~p", [Configuration]),
++ {error, ?ERR_BAD_REQUEST}
++ end;
+ {set, "publish"} ->
+ case xml:remove_cdata(Els) of
+ [{xmlelement, "item", ItemAttrs, Payload}] ->
+@@ -438,7 +689,7 @@
+ ItemID = xml:get_attr_s("id", ItemAttrs),
+ delete_item(Host, From, Node, ItemID);
+ _ ->
+- {error, ?ERR_BAD_REQUEST}
++ {error, extend_error(?ERR_BAD_REQUEST, "item-required")}
+ end;
+ {set, "subscribe"} ->
+ JID = xml:get_attr_s("jid", Attrs),
+@@ -449,20 +700,15 @@
+ {get, "items"} ->
+ MaxItems = xml:get_attr_s("max_items", Attrs),
+ get_items(Host, From, Node, MaxItems);
+- {set, "delete"} ->
+- delete_node(Host, From, Node);
+- {set, "purge"} ->
+- purge_node(Host, From, Node);
+- {get, "entities"} ->
+- get_entities(Host, From, Node);
+- {set, "entities"} ->
+- set_entities(Host, From, Node, xml:remove_cdata(Els));
+ {get, "affiliations"} ->
+ get_affiliations(Host, From);
++ {get, "subscriptions"} ->
++ get_subscriptions(Host, From);
+ _ ->
+ {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+ _ ->
++ ?INFO_MSG("Too many actions: ~p", [Action]),
+ {error, ?ERR_BAD_REQUEST}
+ end.
+
+@@ -504,16 +750,81 @@
+ create_new_node(Host, Node, Owner) ->
+ %% This is the case use during "bootstrapping to create the initial
+ %% hierarchy. Should always be ... undefined,all
+- create_new_node(Host, Node, Owner, undefined, all).
+-create_new_node(Host, Node, Owner, ServerHost, Access) ->
+- case Node of
+- [] ->
++ create_new_node(Host, Node, Owner, undefined, all, []).
++create_new_node(Host, Node, Owner, ServerHost, Access, Configuration) ->
++ DefaultSet = get_table(Host), % get_table happens to DTRT here
++ ConfigOptions = case xml:remove_cdata(Configuration) of
++ [] ->
++ [];
++ [{xmlelement, "x", _Attrs, _SubEls} = XEl] ->
++ case jlib:parse_xdata_submit(XEl) of
++ invalid ->
++ {error, ?ERR_BAD_REQUEST};
++ XData ->
++ case set_xoption(XData, [{defaults, DefaultSet}]) of
++ NewOpts when is_list(NewOpts) ->
++ NewOpts;
++ Err ->
++ Err
++ end
++ end;
++ _ ->
++ ?INFO_MSG("Configuration not understood: ~p", [Configuration]),
++ {error, ?ERR_BAD_REQUEST}
++ end,
++ case {Host, Node, ConfigOptions} of
++ {_, _, {error, _} = Error} ->
++ Error;
++ %% If Host is a jid tuple, we are in PEP.
++ {{_, _, _}, [], _} ->
++ %% And in PEP, instant nodes are not supported.
++ {error, extend_error(?ERR_NOT_ACCEPTABLE, "nodeid-required")};
++ {{_, _, _}, _, _} ->
++ LOwner = Host,
++ F = fun() ->
++ case mnesia:read({pep_node, {LOwner, Node}}) of
++ [_] ->
++ {error, ?ERR_CONFLICT};
++ [] ->
++ Entities =
++ ?DICT:store(
++ LOwner,
++ #entity{affiliation = owner,
++ subscription = none},
++ ?DICT:new()),
++ mnesia:write(
++ #pep_node{owner_node = {LOwner, Node},
++ info = #nodeinfo{entities = Entities,
++ options = ConfigOptions}}),
++ ok
++ end
++ end,
++ case mnesia:transaction(F) of
++ {atomic, ok} ->
++ {result, []};
++ {atomic, {error, _} = Error} ->
++ Error;
++ _ ->
++ {error, ?ERR_INTERNAL_SERVER_ERROR}
++ end;
++ {_, [], _} ->
+ {LOU, LOS, _} = jlib:jid_tolower(Owner),
+ HomeNode = ["home", LOS, LOU],
+- create_new_node(Host, HomeNode, Owner, ServerHost, Access),
++ create_new_node(Host, HomeNode, Owner, ServerHost, Access, []),
+ NewNode = ["home", LOS, LOU, randoms:get_string()],
+- create_new_node(Host, NewNode, Owner, ServerHost, Access);
+- _ ->
++ %% When creating an instant node, we need to include the
++ %% node name in the result.
++ case create_new_node(Host, NewNode, Owner, ServerHost, Access, []) of
++ {result, []} ->
++ {result,
++ [{xmlelement, "pubsub",
++ [{"xmlns", ?NS_PUBSUB}],
++ [{xmlelement, "create",
++ [{"node", node_to_string(Node)}], []}]}]};
++ {error, _} = Error ->
++ Error
++ end;
++ {_, _, _} ->
+ LOwner = jlib:jid_tolower(jlib:jid_remove_resource(Owner)),
+ Parent = lists:sublist(Node, length(Node) - 1),
+ F = fun() ->
+@@ -542,7 +853,8 @@
+ #pubsub_node{host_node = {Host, Node},
+ host_parent = {Host, Parent},
+ info = #nodeinfo{
+- entities = Entities}}),
++ entities = Entities,
++ options = ConfigOptions}}),
+ ok
+ end
+ end
+@@ -562,11 +874,7 @@
+ ?XFIELD("jid-single", "Node Creator",
+ "creator",
+ jlib:jid_to_string(LOwner))]}]),
+- {result,
+- [{xmlelement, "pubsub",
+- [{"xmlns", ?NS_PUBSUB}],
+- [{xmlelement, "create",
+- [{"node", node_to_string(Node)}], []}]}]};
++ {result, []};
+ {atomic, {error, _} = Error} ->
+ Error;
+ _ ->
+@@ -579,33 +887,38 @@
+
+
+ publish_item(Host, JID, Node, ItemID, Payload) ->
++ Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
++ Table = get_table(Host),
++ %% XXX: Host is not a host if this is PEP. Good thing that this
++ %% hook isn't added to anywhere yet.
+ ejabberd_hooks:run(pubsub_publish_item, Host,
+ [JID, ?MYJID, Node, ItemID, Payload]),
+- Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info} = N] ->
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
+ Affiliation = get_affiliation(Info, Publisher),
+ Subscription = get_subscription(Info, Publisher),
+ MaxSize = get_node_option(Info, max_payload_size),
+ Model = get_node_option(Info, publish_model),
+ Size = size(term_to_binary(Payload)),
+ if
+- ((Model == open) or
+- ((Model == publishers) and
+- ((Affiliation == owner) or
+- (Affiliation == publisher))) or
+- ((Model == subscribers) and
+- (Subscription == subscribed))) and
+- (Size =< MaxSize) ->
++ not ((Model == open) or
++ ((Model == publishers) and
++ ((Affiliation == owner) or
++ (Affiliation == publisher))) or
++ ((Model == subscribers) and
++ (Subscription == subscribed))) ->
++ {error, ?ERR_FORBIDDEN};
++ (Size > MaxSize) ->
++ {error, extend_error(?ERR_NOT_ACCEPTABLE, "payload-too-big")};
++ true ->
+ NewInfo =
+ insert_item(Info, ItemID,
+ Publisher, Payload),
+- mnesia:write(
+- N#pubsub_node{info = NewInfo}),
+- {result, []};
+- true ->
+- {error, ?ERR_NOT_ALLOWED}
++ NewNode = set_node_info(N, NewInfo),
++ mnesia:write(NewNode),
++ {result, []}
+ end;
+ [] ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -624,20 +937,27 @@
+
+ delete_item(Host, JID, Node, ItemID) ->
+ Publisher = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
++ Table = get_table(Host),
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info} = N] ->
+- case check_item_publisher(Info, ItemID, Publisher)
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
++ ItemExists = lists:any(fun(I) ->
++ I#item.id == ItemID
++ end, Info#nodeinfo.items),
++ Allowed = check_item_publisher(Info, ItemID, Publisher)
+ orelse
+- (get_affiliation(Info, Publisher) == owner) of
+- true ->
++ (get_affiliation(Info, Publisher) == owner),
++ if not Allowed ->
++ {error, ?ERR_FORBIDDEN};
++ not ItemExists ->
++ {error, ?ERR_ITEM_NOT_FOUND};
++ true ->
+ NewInfo =
+ remove_item(Info, ItemID),
+ mnesia:write(
+- N#pubsub_node{info = NewInfo}),
+- {result, []};
+- _ ->
+- {error, ?ERR_NOT_ALLOWED}
++ set_node_info(N, NewInfo)),
++ {result, []}
+ end;
+ [] ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -653,6 +973,17 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
+
++%% Add pubsub-specific error element
++extend_error({xmlelement, "error", Attrs, SubEls}, Error) ->
++ {xmlelement, "error", Attrs,
++ [{xmlelement, Error, [{"xmlns", ?NS_PUBSUB_ERRORS}], []}
++ | SubEls]}.
++
++extend_error({xmlelement, "error", Attrs, SubEls}, unsupported, Feature) ->
++ {xmlelement, "error", Attrs,
++ [{xmlelement, "unsupported", [{"xmlns", ?NS_PUBSUB_ERRORS},
++ {"feature", Feature}], []}
++ | SubEls]}.
+
+ subscribe_node(Host, From, JID, Node) ->
+ Sender = jlib:jid_tolower(jlib:jid_remove_resource(From)),
+@@ -665,64 +996,234 @@
+ end,
+ Subscriber = jlib:jid_tolower(SubscriberJID),
+ SubscriberWithoutResource = jlib:jid_remove_resource(Subscriber),
++ AuthorizedToSubscribe = Sender == SubscriberWithoutResource,
++ Table = get_table(Host),
++ case catch mnesia:dirty_read({Table, {Host, Node}}) of
++ [NodeData] ->
++ NodeInfo = get_node_info(NodeData),
++ AllowSubscriptions = get_node_option(NodeInfo, subscribe),
++ AccessModel = get_node_option(NodeInfo, access_model),
++ AllowedGroups = get_node_option(NodeInfo, access_roster_groups),
++ Affiliation = get_affiliation(NodeInfo, Subscriber),
++ OldSubscription = get_subscription(NodeInfo, Subscriber),
++ CurrentApprover = get_node_option(NodeInfo, current_approver);
++ [] ->
++ {AllowSubscriptions,
++ AccessModel,
++ AllowedGroups,
++ Affiliation,
++ OldSubscription,
++ CurrentApprover} = {notfound, notfound, notfound, notfound, notfound, notfound};
++ _ ->
++ {AllowSubscriptions,
++ AccessModel,
++ AllowedGroups,
++ Affiliation,
++ OldSubscription,
++ CurrentApprover} = {error, error, error, error, error, error}
++ end,
++ Subscription = if AllowSubscriptions == notfound ->
++ {error, ?ERR_ITEM_NOT_FOUND};
++ AllowSubscriptions == error ->
++ {error, ?ERR_INTERNAL_SERVER_ERROR};
++ not AuthorizedToSubscribe ->
++ {error, extend_error(?ERR_BAD_REQUEST, "invalid-jid")};
++ not AllowSubscriptions ->
++ {error, extend_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "subscribe")};
++ OldSubscription == pending ->
++ {error, extend_error(?ERR_NOT_AUTHORIZED, "pending-subscription")};
++ Affiliation == outcast ->
++ {error, ?ERR_FORBIDDEN};
++ AccessModel == open; Affiliation == owner; Affiliation == publisher ->
++ subscribed;
++ AccessModel == authorize ->
++ pending;
++ AccessModel == presence ->
++ %% XXX: this applies only to PEP
++ {OUser, OServer, _} = Host,
++ {Subscription1, _Groups} =
++ ejabberd_hooks:run_fold(
++ roster_get_jid_info, OServer,
++ {none, []}, [OUser, OServer, SubscriberWithoutResource]),
++ if (Subscription1 == both) or
++ (Subscription1 == from) ->
++ subscribed;
++ true ->
++ {error, extend_error(?ERR_NOT_AUTHORIZED, "presence-subscription-required")}
++ end;
++ AccessModel == roster ->
++ %% XXX: this applies only to PEP
++ {OUser, OServer, _} = Host,
++ {_Subscription, Groups} =
++ ejabberd_hooks:run_fold(
++ roster_get_jid_info, OServer,
++ {none, []}, [OUser, OServer, SubscriberWithoutResource]),
++ case lists:any([lists:member(Group, AllowedGroups) || Group <- Groups]) of
++ true ->
++ subscribed;
++ false ->
++ {error, extend_error(?ERR_NOT_AUTHORIZED, "not-in-roster-group")}
++ end;
++ AccessModel == whitelist ->
++ %% Subscribers are added by owner (see set_entities)
++ {error, extend_error(?ERR_NOT_ALLOWED, "closed-node")}
++ end,
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info} = N] ->
+- Affiliation = get_affiliation(Info, Subscriber),
+- AllowSubscriptions = get_node_option(Info, subscribe),
+- if
+- AllowSubscriptions and
+- (Affiliation /= outcast) ->
+- NewInfo = add_subscriber(Info, Subscriber),
+- mnesia:write(N#pubsub_node{info = NewInfo}),
+- {result, [], Info};
+- true ->
+- {error, ?ERR_NOT_ALLOWED}
+- end;
+- [] ->
+- {error, ?ERR_ITEM_NOT_FOUND}
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
++ NewInfo = add_subscriber(Info, Subscriber, Subscription),
++ mnesia:write(set_node_info(N, NewInfo)),
++ {result, [{xmlelement, "subscription",
++ [{"node", Node},
++ {"jid", jlib:jid_to_string(Subscriber)},
++ {"subscription",
++ subscription_to_string(Subscription)}],
++ []}],
++ Info}
+ end
+ end,
+- if
+- Sender == SubscriberWithoutResource ->
++ case Subscription of
++ {error, _} = Error ->
++ Error;
++ _ ->
+ case mnesia:transaction(F) of
+ {atomic, {error, _} = Error} ->
+ Error;
+ {atomic, {result, Res, Info}} ->
+- case get_node_option(Info, send_item_subscribe) of
+- true ->
+- ItemsEls =
+- lists:map(
+- fun(#item{id = ItemID,
+- payload = Payload}) ->
+- ItemAttrs = case ItemID of
+- "" -> [];
+- _ -> [{"id", ItemID}]
+- end,
+- {xmlelement, "item",
+- ItemAttrs, Payload}
+- end, Info#nodeinfo.items),
+- Stanza =
+- {xmlelement, "message",
+- [],
+- [{xmlelement, "x",
+- [{"xmlns", ?NS_PUBSUB_EVENT}],
+- [{xmlelement, "items",
+- [{"node", node_to_string(Node)}],
+- ItemsEls}]}]},
+- ejabberd_router:route(
+- ?MYJID, jlib:make_jid(Subscriber), Stanza);
+- false ->
+- ok
+- end,
++ if Subscription == subscribed ->
++ SendLastPublishedItem = get_node_option(Info, send_last_published_item),
++ if SendLastPublishedItem /= never ->
++ send_last_published_item(Subscriber, Host, Node, Info);
++ true ->
++ ok
++ end;
++ Subscription == pending ->
++ %% send authorization request to node owner (section 8.6)
++
++ %% XXX: fix translation
++ Lang = "en",
++ send_authorization_request(Lang, CurrentApprover, Subscriber, Node, Host)
++ end,
+ {result, Res};
+ _ ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+- end;
+- true ->
+- {error, ?ERR_NOT_ALLOWED}
++ end
++ end.
++
++send_authorization_request(Lang, Approver, Subscriber, Node, Host) ->
++ Stanza =
++ {xmlelement, "message",
++ [],
++ [{xmlelement, "x", [{"xmlns", ?NS_XDATA},
++ {"type", "form"}],
++ [{xmlelement, "title", [],
++ [{xmlcdata, translate:translate(Lang, "PubSub subscriber request")}]},
++ {xmlelement, "instructions", [],
++ [{xmlcdata, translate:translate(Lang, "Choose whether to approve this entity's subscription.")}]},
++ {xmlelement, "field", [{"var", "FORM_TYPE"}, {"type", "hidden"}],
++ [{xmlelement, "value", [], [{xmlcdata, ?NS_PUBSUB_SUB_AUTH}]}]},
++ {xmlelement, "field", [{"var", "pubsub#node"}, {"type", "text-single"},
++ {"label", translate:translate(Lang, "Node ID")}],
++ [{xmlelement, "value", [], [{xmlcdata, node_to_string(Node)}]}]},
++ {xmlelement, "field", [{"var", "pubsub#subscriber_jid"},
++ {"type", "jid-single"},
++ {"label", translate:translate(Lang, "Subscriber Address")}],
++ [{xmlelement, "value", [], [{xmlcdata, jlib:jid_to_string(Subscriber)}]}]},
++ {xmlelement, "field", [{"var", "pubsub#allow"}, {"type", "boolean"},
++ {"label", translate:translate(Lang, "Allow this JID to subscribe to this pubsub node?")}],
++ [{xmlelement, "value", [], [{xmlcdata, "false"}]}]}]}]},
++ ejabberd_router:route(get_sender(Host), Approver, Stanza).
++
++find_authorization_response(Packet) ->
++ {xmlelement, _Name, _Attrs, Els} = Packet,
++ XData1 = lists:map(fun({xmlelement, "x", XAttrs, _} = XEl) ->
++ case xml:get_attr_s("xmlns", XAttrs) of
++ ?NS_XDATA ->
++ case xml:get_attr_s("type", XAttrs) of
++ "cancel" ->
++ none;
++ _ ->
++ jlib:parse_xdata_submit(XEl)
++ end;
++ _ ->
++ none
++ end;
++ (_) ->
++ none
++ end, xml:remove_cdata(Els)),
++ XData = lists:filter(fun(E) -> E /= none end, XData1),
++ case XData of
++ [invalid] -> invalid;
++ [] -> none;
++ [XFields] when is_list(XFields) ->
++ case lists:keysearch("FORM_TYPE", 1, XFields) of
++ {value, {_, ?NS_PUBSUB_SUB_AUTH}} ->
++ XFields;
++ _ ->
++ invalid
++ end
+ end.
+
++handle_authorization_response(From, To, Host, Packet, XFields) ->
++ case {lists:keysearch("pubsub#node", 1, XFields),
++ lists:keysearch("pubsub#subscriber_jid", 1, XFields),
++ lists:keysearch("pubsub#allow", 1, XFields)} of
++ {{value, {_, SNode}}, {value, {_, SSubscriber}},
++ {value, {_, SAllow}}} ->
++ Node = case Host of
++ {_, _, _} ->
++ SNode;
++ _ ->
++ string:tokens(SNode, "/")
++ end,
++ Subscriber = jlib:string_to_jid(SSubscriber),
++ Allow = case SAllow of
++ "1" -> true;
++ "true" -> true;
++ _ -> false
++ end,
++ Table = get_table(Host),
++ F = fun() ->
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
++ Subscription = get_subscription(Info, Subscriber),
++ Approver = get_node_option(N, current_approver),
++ IsApprover = jlib:jid_tolower(jlib:jid_remove_resource(From)) ==
++ jlib:jid_tolower(jlib:jid_remove_resource(Approver)),
++ if not IsApprover ->
++ {error, ?ERR_FORBIDDEN};
++ Subscription /= pending ->
++ {error, ?ERR_UNEXPECTED_REQUEST};
++ true ->
++ NewSubscription = case Allow of
++ true -> subscribed;
++ false -> none
++ end,
++ NewInfo = add_subscriber(Info, Subscriber, NewSubscription),
++ mnesia:write(set_node_info(N, NewInfo)),
++ NewSubscription
++ end;
++ [] ->
++ {error, ?ERR_ITEM_NOT_FOUND}
++ end
++ end,
++ case mnesia:transaction(F) of
++ {atomic, {error, Error}} ->
++ ejabberd_router:route(To, From,
++ jlib:make_error_reply(Packet, Error));
++ {atomic, NewSubscription} ->
++ %% XXX: notify about subscription state change, section 12.11
++ ok;
++ _ ->
++ ejabberd_router:route(To, From,
++ jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR))
++ end;
++ _ ->
++ ejabberd_router:route(To, From,
++ jlib:make_error_reply(Packet, ?ERR_NOT_ACCEPTABLE))
++ end.
+
+ unsubscribe_node(Host, From, JID, Node) ->
+ Sender = jlib:jid_tolower(jlib:jid_remove_resource(From)),
+@@ -734,19 +1235,21 @@
+ J
+ end,
+ Subscriber = jlib:jid_tolower(SubscriberJID),
++ Table = get_table(Host),
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info} = N] ->
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
+ Subscription = get_subscription(Info, Subscriber),
+ if
+ Subscription /= none ->
+ NewInfo =
+ remove_subscriber(Info, Subscriber),
+ mnesia:write(
+- N#pubsub_node{info = NewInfo}),
++ set_node_info(N, NewInfo)),
+ {result, []};
+ true ->
+- {error, ?ERR_NOT_ALLOWED}
++ {error, extend_error(?ERR_UNEXPECTED_REQUEST, "not-subscribed")}
+ end;
+ [] ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -763,7 +1266,7 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end;
+ true ->
+- {error, ?ERR_NOT_ALLOWED}
++ {error, ?ERR_FORBIDDEN}
+ end.
+
+
+@@ -780,12 +1283,14 @@
+ Val
+ end
+ end,
++ Table = get_table(Host),
+ case MaxItems of
+ {error, _} = Error ->
+ Error;
+ _ ->
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info}] ->
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ Items = lists:sublist(Info#nodeinfo.items, MaxItems),
+ ItemsEls =
+ lists:map(
+@@ -810,30 +1315,39 @@
+
+ delete_node(Host, JID, Node) ->
+ Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
++ Table = get_table(Host),
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info}] ->
++ case mnesia:read({Table, {Host, Node}}) of
++ [N1] ->
++ Info = get_node_info(N1),
+ case get_affiliation(Info, Owner) of
+ owner ->
+- % TODO: don't iterate over entire table
+- Removed =
+- mnesia:foldl(
+- fun(#pubsub_node{host_node = {_, N},
+- info = NInfo}, Acc) ->
+- case lists:prefix(Node, N) of
+- true ->
+- [{N, NInfo} | Acc];
+- _ ->
+- Acc
+- end
+- end, [], pubsub_node),
+- lists:foreach(
+- fun({N, _}) ->
+- mnesia:delete({pubsub_node, {Host, N}})
+- end, Removed),
+- {removed, Removed};
++ %% PEP nodes are not hierarchical, so removal is easier.
++ case Table of
++ pep_node ->
++ mnesia:delete({Table, {Host, Node}}),
++ {removed, [{N1, Info}]};
++ pubsub_node ->
++ %% TODO: don't iterate over entire table
++ Removed =
++ mnesia:foldl(
++ fun(#pubsub_node{host_node = {_, N},
++ info = NInfo}, Acc) ->
++ case lists:prefix(Node, N) of
++ true ->
++ [{N, NInfo} | Acc];
++ _ ->
++ Acc
++ end
++ end, [], pubsub_node),
++ lists:foreach(
++ fun({N, _}) ->
++ mnesia:delete({pubsub_node, {Host, N}})
++ end, Removed),
++ {removed, Removed}
++ end;
+ _ ->
+- {error, ?ERR_NOT_ALLOWED}
++ {error, ?ERR_FORBIDDEN}
+ end;
+ [] ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -844,7 +1358,6 @@
+ Error;
+ {atomic, {removed, Removed}} ->
+ broadcast_removed_node(Host, Removed),
+- Lang = "",
+ broadcast_retract_item(
+ Host, ["pubsub", "nodes"], node_to_string(Node)),
+ {result, []};
+@@ -855,17 +1368,18 @@
+
+ purge_node(Host, JID, Node) ->
+ Owner = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
++ Table = get_table(Host),
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info} = N] ->
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
+ case get_affiliation(Info, Owner) of
+ owner ->
+ NewInfo = Info#nodeinfo{items = []},
+- mnesia:write(
+- N#pubsub_node{info = NewInfo}),
+- {result, Info#nodeinfo.items, []};
++ mnesia:write(set_node_info(N, NewInfo)),
++ {result, []};
+ _ ->
+- {error, ?ERR_NOT_ALLOWED}
++ {error, ?ERR_FORBIDDEN}
+ end;
+ [] ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -874,52 +1388,170 @@
+ case mnesia:transaction(F) of
+ {atomic, {error, _} = Error} ->
+ Error;
+- {atomic, {result, Items, Res}} ->
+- lists:foreach(
+- fun(#item{id = ItemID}) ->
+- broadcast_retract_item(Host, Node, ItemID)
+- end, Items),
++ {atomic, {result, Res}} ->
++ broadcast_purge_node(Host, Node),
+ {result, Res};
+ _ ->
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
+
+
+-get_entities(Host, OJID, Node) ->
++owner_get_subscriptions(Host, OJID, Node) ->
+ Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info}] ->
++ Table = get_table(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ case get_affiliation(Info, Owner) of
+ owner ->
+ Entities = Info#nodeinfo.entities,
+ EntitiesEls =
+ ?DICT:fold(
+ fun(JID,
+- #entity{affiliation = Affiliation,
+- subscription = Subscription},
++ #entity{subscription = Subscription},
+ Acc) ->
+- [{xmlelement, "entity",
+- [{"jid", jlib:jid_to_string(JID)},
+- {"affiliation",
+- affiliation_to_string(Affiliation)},
+- {"subscription",
+- subscription_to_string(Subscription)}],
+- []} | Acc]
++ case Subscription of
++ none ->
++ Acc;
++ _ ->
++ [{xmlelement, "subscription",
++ [{"jid", jlib:jid_to_string(JID)},
++ {"subscription",
++ subscription_to_string(Subscription)}],
++ []} | Acc]
++ end
+ end, [], Entities),
+ {result, [{xmlelement, "pubsub",
+- [{"xmlns", ?NS_PUBSUB_EVENT}],
+- [{xmlelement, "entities",
++ [{"xmlns", ?NS_PUBSUB_OWNER}],
++ [{xmlelement, "subscriptions",
+ [{"node", node_to_string(Node)}],
+ EntitiesEls}]}]};
+ _ ->
+- {error, ?ERR_NOT_ALLOWED}
++ {error, ?ERR_FORBIDDEN}
+ end;
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end.
+
+
+-set_entities(Host, OJID, Node, EntitiesEls) ->
++owner_set_subscriptions(Host, OJID, Node, EntitiesEls) ->
++ %% XXX: not updated for PEP and new pubsub revision
++ Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
++ Entities =
++ lists:foldl(
++ fun(El, Acc) ->
++ case Acc of
++ error ->
++ error;
++ _ ->
++ case El of
++ {xmlelement, "entity", Attrs, _} ->
++ JID = jlib:string_to_jid(
++ xml:get_attr_s("jid", Attrs)),
++ Affiliation =
++ case xml:get_attr_s("affiliation",
++ Attrs) of
++ "owner" -> owner;
++ "publisher" -> publisher;
++ "outcast" -> outcast;
++ "none" -> none;
++ _ -> false
++ end,
++ Subscription =
++ case xml:get_attr_s("subscription",
++ Attrs) of
++ "subscribed" -> subscribed;
++ "pending" -> pending;
++ "unconfigured" -> unconfigured;
++ "none" -> none;
++ _ -> false
++ end,
++ if
++ (JID == error) or
++ (Affiliation == false) or
++ (Subscription == false) ->
++ error;
++ true ->
++ [{jlib:jid_tolower(JID),
++ #entity{
++ affiliation = Affiliation,
++ subscription = Subscription}} |
++ Acc]
++ end
++ end
++ end
++ end, [], EntitiesEls),
++ case Entities of
++ error ->
++ {error, ?ERR_BAD_REQUEST};
++ _ ->
++ F = fun() ->
++ case mnesia:read({pubsub_node, {Host, Node}}) of
++ [#pubsub_node{info = Info} = N] ->
++ case get_affiliation(Info, Owner) of
++ owner ->
++ NewInfo =
++ set_info_entities(Info, Entities),
++ mnesia:write(
++ N#pubsub_node{info = NewInfo}),
++ {result, []};
++ _ ->
++ {error, ?ERR_NOT_ALLOWED}
++ end;
++ [] ->
++ {error, ?ERR_ITEM_NOT_FOUND}
++ end
++ end,
++ case mnesia:transaction(F) of
++ {atomic, {error, _} = Error} ->
++ Error;
++ {atomic, {result, _}} ->
++ {result, []};
++ _ ->
++ {error, ?ERR_INTERNAL_SERVER_ERROR}
++ end
++ end.
++
++owner_get_affiliations(Host, OJID, Node) ->
++ Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
++ Table = get_table(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
++ case get_affiliation(Info, Owner) of
++ owner ->
++ Entities = Info#nodeinfo.entities,
++ EntitiesEls =
++ ?DICT:fold(
++ fun(JID,
++ #entity{affiliation = Affiliation},
++ Acc) ->
++ case Affiliation of
++ none ->
++ Acc;
++ _ ->
++ [{xmlelement, "affiliation",
++ [{"jid", jlib:jid_to_string(JID)},
++ {"affiliation",
++ affiliation_to_string(Affiliation)}],
++ []} | Acc]
++ end
++ end, [], Entities),
++ {result, [{xmlelement, "pubsub",
++ [{"xmlns", ?NS_PUBSUB_OWNER}],
++ [{xmlelement, "affiliations",
++ [{"node", node_to_string(Node)}],
++ EntitiesEls}]}]};
++ _ ->
++ {error, ?ERR_FORBIDDEN}
++ end;
++ _ ->
++ {error, ?ERR_ITEM_NOT_FOUND}
++ end.
++
++
++owner_set_affiliations(Host, OJID, Node, EntitiesEls) ->
++ %% XXX: not updated for PEP and new pubsub revision
+ Owner = jlib:jid_tolower(jlib:jid_remove_resource(OJID)),
+ Entities =
+ lists:foldl(
+@@ -999,42 +1631,114 @@
+
+ get_affiliations(Host, JID) ->
+ LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
++ Table = get_table(Host),
++ Template = case Table of
++ pubsub_node ->
++ #pubsub_node{_ = '_'};
++ pep_node ->
++ #pep_node{_ = '_'}
++ end,
+ case catch mnesia:dirty_select(
+- pubsub_node,
+- [{#pubsub_node{_ = '_'},
++ Table,
++ [{Template,
+ [],
+ ['$_']}]) of
+ {'EXIT', _} ->
+- {error, ?ERR_INTERNAL_SERVER_ERROR};
++ {error, ?ERR_INTERNAL_SERVER_ERROR};
+ Nodes ->
+- Entities =
+- lists:flatmap(
+- fun(#pubsub_node{host_node = {H, Node}, info = Info})
+- when H == Host ->
+- Affiliation = get_affiliation(Info, LJID),
+- Subscription = get_subscription(Info, LJID),
+- if
+- (Affiliation /= none) or
+- (Subscription /= none) ->
+- [{xmlelement, "entity",
+- [{"node", node_to_string(Node)},
+- {"jid", jlib:jid_to_string(JID)},
+- {"affiliation",
+- affiliation_to_string(Affiliation)},
+- {"subscription",
+- subscription_to_string(Subscription)}],
+- []}];
+- true ->
+- []
+- end;
+- (_) ->
+- []
+- end, Nodes),
+- {result, [{xmlelement, "pubsub",
+- [{"xmlns", ?NS_PUBSUB_EVENT}],
+- [{xmlelement, "affiliations", [],
+- Entities}]}]}
+- end.
++ Entities =
++ lists:flatmap(
++ fun(N) ->
++ Info = get_node_info(N),
++ {H, Node} =
++ case Table of
++ pubsub_node ->
++ N#pubsub_node.host_node;
++ pep_node ->
++ N#pep_node.owner_node
++ end,
++ if Host == H ->
++ Affiliation = get_affiliation(Info, LJID),
++ if Affiliation /= none ->
++ [{xmlelement, "affiliation",
++ [{"node", node_to_string(Node)},
++ {"affiliation",
++ affiliation_to_string(Affiliation)}],
++ []}];
++ true ->
++ []
++ end;
++ true ->
++ []
++ end
++ end,
++ Nodes),
++ case Entities of
++ [] ->
++ {error, ?ERR_ITEM_NOT_FOUND};
++ _ ->
++ {result, [{xmlelement, "pubsub",
++ [{"xmlns", ?NS_PUBSUB}],
++ [{xmlelement, "affiliations", [],
++ Entities}]}]}
++ end
++ end.
++
++get_subscriptions(Host, JID) ->
++ LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
++ Table = get_table(Host),
++ Template = case Table of
++ pubsub_node ->
++ #pubsub_node{_ = '_'};
++ pep_node ->
++ #pep_node{_ = '_'}
++ end,
++ case catch mnesia:dirty_select(
++ Table,
++ [{Template,
++ [],
++ ['$_']}]) of
++ {'EXIT', _} ->
++ {error, ?ERR_INTERNAL_SERVER_ERROR};
++ Nodes ->
++ Entities =
++ lists:flatmap(
++ fun(N) ->
++ Info = get_node_info(N),
++ {H, Node} =
++ case Table of
++ pubsub_node ->
++ N#pubsub_node.host_node;
++ pep_node ->
++ N#pep_node.owner_node
++ end,
++ if Host == H ->
++ Subscription = get_subscription(Info, LJID),
++ if Subscription /= none ->
++ [{xmlelement, "subscription",
++ [{"node", node_to_string(Node)},
++ {"jid", jlib:jid_to_string(LJID)}, %XXX: full JID?
++ {"subscription",
++ subscription_to_string(Subscription)}],
++ []}];
++ true ->
++ []
++ end;
++ true ->
++ []
++ end
++ end,
++ Nodes),
++ case Entities of
++ [] ->
++ {error, ?ERR_ITEM_NOT_FOUND};
++ _ ->
++ {result, [{xmlelement, "pubsub",
++ [{"xmlns", ?NS_PUBSUB}],
++ [{xmlelement, "subscriptions", [],
++ Entities}]}]}
++ end
++ end.
+
+
+
+@@ -1123,18 +1827,18 @@
+ false
+ end.
+
+-add_subscriber(Info, Subscriber) ->
++add_subscriber(Info, Subscriber, Subscription) ->
+ Entities = Info#nodeinfo.entities,
+ case ?DICT:find(Subscriber, Entities) of
+ {ok, Entity} ->
+ Info#nodeinfo{
+ entities = ?DICT:store(Subscriber,
+- Entity#entity{subscription = subscribed},
++ Entity#entity{subscription = Subscription},
+ Entities)};
+ _ ->
+ Info#nodeinfo{
+ entities = ?DICT:store(Subscriber,
+- #entity{subscription = subscribed},
++ #entity{subscription = Subscription},
+ Entities)}
+ end.
+
+@@ -1167,32 +1871,48 @@
+ end, Info#nodeinfo.entities, Entities),
+ Info#nodeinfo{entities = NewEntities}.
+
++send_last_published_item(Subscriber, Host, Node, Info) ->
++ case Info#nodeinfo.items of
++ [] ->
++ %% No published items - can't send anything.
++ ok;
++ [#item{id = ItemID, payload = Payload} | _] ->
++ %% At least one item - send the last one.
++ ItemAttrs = case ItemID of
++ "" -> [];
++ _ -> [{"id", ItemID}]
++ end,
++ ItemsEl = {xmlelement, "item",
++ ItemAttrs, Payload},
++ Stanza =
++ {xmlelement, "message",
++ [],
++ [{xmlelement, "event",
++ [{"xmlns", ?NS_PUBSUB_EVENT}],
++ [{xmlelement, "items",
++ [{"node", node_to_string(Node)}],
++ [ItemsEl]}]}]},
++ ejabberd_router:route(
++ get_sender(Host), jlib:make_jid(Subscriber), Stanza)
++ end.
++
+
+
+ broadcast_publish_item(Host, Node, ItemID, Payload) ->
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info}] ->
++ ?DEBUG("broadcasting for ~p / ~p", [Host, Node]),
++ Table = get_table(Host),
++ Sender = get_sender(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ ?DICT:fold(
+ fun(JID, #entity{subscription = Subscription}, _) ->
+- Present = case get_node_option(
+- Info, presence_based_delivery) of
+- true ->
+- case ets:lookup(
+- gen_mod:get_module_proc(Host, pubsub_presence),
+- {element(1, JID),
+- element(2, JID)}) of
+- [_] ->
+- true;
+- [] ->
+- false
+- end;
+- false ->
+- true
+- end,
++ Resources = get_recipient_resources(Host, JID, Info),
++ ?DEBUG("subscriber ~p: delivering to resources ~p", [JID, Resources]),
+ if
+- (Subscription /= none) and
+- (Subscription /= pending) and
+- Present ->
++ Subscription /= none,
++ Subscription /= pending,
++ Resources /= [] ->
+ ItemAttrs = case ItemID of
+ "" -> [];
+ _ -> [{"id", ItemID}]
+@@ -1213,8 +1933,12 @@
+ [{xmlelement, "item",
+ ItemAttrs,
+ Content}]}]}]},
+- ejabberd_router:route(
+- ?MYJID, jlib:make_jid(JID), Stanza);
++ TheJID = jlib:make_jid(JID),
++ lists:foreach(fun(Resource) ->
++ FullJID = jlib:jid_replace_resource(TheJID, Resource),
++ ejabberd_router:route(
++ Sender, FullJID, Stanza)
++ end, Resources);
+ true ->
+ ok
+ end
+@@ -1225,10 +1949,14 @@
+
+
+ broadcast_retract_item(Host, Node, ItemID) ->
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info}] ->
++ Table = get_table(Host),
++ Sender = get_sender(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ case get_node_option(Info, notify_retract) of
+ true ->
++ %% XXX: presence-based notifications?
+ ?DICT:fold(
+ fun(JID, #entity{subscription = Subscription}, _) ->
+ if
+@@ -1240,14 +1968,48 @@
+ end,
+ Stanza =
+ {xmlelement, "message", [],
+- [{xmlelement, "x",
++ [{xmlelement, "event",
+ [{"xmlns", ?NS_PUBSUB_EVENT}],
+ [{xmlelement, "items",
+ [{"node", node_to_string(Node)}],
+ [{xmlelement, "retract",
+ ItemAttrs, []}]}]}]},
+ ejabberd_router:route(
+- ?MYJID, jlib:make_jid(JID), Stanza);
++ Sender, jlib:make_jid(JID), Stanza);
++ true ->
++ ok
++ end
++ end, ok, Info#nodeinfo.entities);
++ false ->
++ ok
++ end;
++ _ ->
++ false
++ end.
++
++broadcast_purge_node(Host, Node) ->
++ Table = get_table(Host),
++ Sender = get_sender(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
++ case get_node_option(Info, notify_retract) of
++ true ->
++ %% XXX: presence-based notifications?
++ ?DICT:fold(
++ fun(JID, #entity{subscription = Subscription}, _) ->
++ if
++ (Subscription /= none) and
++ (Subscription /= pending) ->
++ Stanza =
++ {xmlelement, "message", [],
++ [{xmlelement, "event",
++ [{"xmlns", ?NS_PUBSUB_EVENT}],
++ [{xmlelement, "purge",
++ [{"node", node_to_string(Node)}],
++ []}]}]},
++ ejabberd_router:route(
++ Sender, jlib:make_jid(JID), Stanza);
+ true ->
+ ok
+ end
+@@ -1265,6 +2027,7 @@
+ fun({Node, Info}) ->
+ case get_node_option(Info, notify_delete) of
+ true ->
++ %% XXX: presence-based notifications?
+ Entities = Info#nodeinfo.entities,
+ ?DICT:fold(
+ fun(JID, #entity{subscription = Subscription}, _) ->
+@@ -1273,13 +2036,13 @@
+ (Subscription /= pending) ->
+ Stanza =
+ {xmlelement, "message", [],
+- [{xmlelement, "x",
++ [{xmlelement, "event",
+ [{"xmlns", ?NS_PUBSUB_EVENT}],
+ [{xmlelement, "delete",
+ [{"node", node_to_string(Node)}],
+ []}]}]},
+ ejabberd_router:route(
+- ?MYJID, jlib:make_jid(JID), Stanza);
++ get_sender(Host), jlib:make_jid(JID), Stanza);
+ true ->
+ ok
+ end
+@@ -1291,31 +2054,19 @@
+
+
+ broadcast_config_notification(Host, Node, Lang) ->
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info}] ->
++ Table = get_table(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ case get_node_option(Info, notify_config) of
+ true ->
+ ?DICT:fold(
+ fun(JID, #entity{subscription = Subscription}, _) ->
+- Present = case get_node_option(
+- Info, presence_based_delivery) of
+- true ->
+- case ets:lookup(
+- gen_mod:get_module_proc(Host, pubsub_presence),
+- {element(1, JID),
+- element(2, JID)}) of
+- [_] ->
+- true;
+- [] ->
+- false
+- end;
+- false ->
+- true
+- end,
++ Resources = get_recipient_resources(Host, JID, Info),
+ if
+- (Subscription /= none) and
+- (Subscription /= pending) and
+- Present ->
++ Subscription /= none,
++ Subscription /= pending,
++ Resources /= [] ->
+ Fields = get_node_config_xfields(
+ Node, Info, Lang),
+ Content = case get_node_option(
+@@ -1323,22 +2074,24 @@
+ true ->
+ [{xmlelement, "x",
+ [{"xmlns", ?NS_XDATA},
+- {"type", "form"}],
++ {"type", "result"}],
+ Fields}];
+ false ->
+ []
+ end,
+ Stanza =
+ {xmlelement, "message", [],
+- [{xmlelement, "x",
++ [{xmlelement, "event",
+ [{"xmlns", ?NS_PUBSUB_EVENT}],
+- [{xmlelement, "items",
++ [{xmlelement, "configuration",
+ [{"node", node_to_string(Node)}],
+- [{xmlelement, "item",
+- [{"id", "configuration"}],
+- Content}]}]}]},
+- ejabberd_router:route(
+- ?MYJID, jlib:make_jid(JID), Stanza);
++ Content}]}]},
++ TheJID = jlib:make_jid(JID),
++ lists:foreach(fun(Resource) ->
++ FullJID = jlib:jid_replace_resource(TheJID, Resource),
++ ejabberd_router:route(
++ get_sender(Host), FullJID, Stanza)
++ end, Resources);
+ true ->
+ ok
+ end
+@@ -1350,19 +2103,74 @@
+ false
+ end.
+
++get_recipient_resources(Host, JID, Info) ->
++ %% Return a list of resources that are supposed to receive event
++ %% notifications. An empty string in that list means a bare JID.
++ case get_node_option(Info, presence_based_delivery) of
++ false ->
++ [""];
++ true ->
++ To = case Host of
++ {_, _, _} ->
++ Host;
++ _ ->
++ {"", Host, ""}
++ end,
++ Resources = get_present_resources(To, JID),
++ %% Here, there is a difference between JEP-0060 and
++ %% JEP-0163. JEP-0060, section 12.1, says that the
++ %% service should not attempt to guess the correct
++ %% resource. JEP-0163, section 7.1.1.2, says that the
++ %% service must send a notification to each resource.
++ case Info#nodeinfo.options of
++ [{default, pep_node} | _] ->
++ Resources;
++ _ ->
++ %% That means, if noone is online, nothing is
++ %% sent. If someone is online, send one
++ %% notification.
++ case Resources of
++ [] ->
++ [];
++ _ ->
++ [""]
++ end
++ end
++ end.
++
+
+
+ iq_pubsub_owner(Host, From, Type, Lang, SubEl) ->
+ {xmlelement, _, _, SubEls} = SubEl,
+ case xml:remove_cdata(SubEls) of
+ [{xmlelement, Name, Attrs, Els}] ->
+- SNode = xml:get_attr_s("node", Attrs),
+- Node = string:tokens(SNode, "/"),
++ %% For PEP, there is no node hierarchy.
++ Node = case Host of
++ {_, _, _} ->
++ xml:get_attr_s("node", Attrs);
++ _ ->
++ SNode = xml:get_attr_s("node", Attrs),
++ string:tokens(SNode, "/")
++ end,
+ case {Type, Name} of
+ {get, "configure"} ->
+ get_node_config(Host, From, Node, Lang);
+ {set, "configure"} ->
+ set_node_config(Host, From, Node, Els, Lang);
++ {get, "default"} ->
++ {error, extend_error(?ERR_FEATURE_NOT_IMPLEMENTED, unsupported, "retrieve-default")};
++ {set, "delete"} ->
++ delete_node(Host, From, Node);
++ {set, "purge"} ->
++ purge_node(Host, From, Node);
++ {get, "subscriptions"} ->
++ owner_get_subscriptions(Host, From, Node);
++ {set, "subscriptions"} ->
++ owner_set_subscriptions(Host, From, Node, xml:remove_cdata(Els));
++ {get, "affiliations"} ->
++ owner_get_affiliations(Host, From, Node);
++ {set, "affiliations"} ->
++ owner_set_affiliations(Host, From, Node, xml:remove_cdata(Els));
+ _ ->
+ {error, ?ERR_FEATURE_NOT_IMPLEMENTED}
+ end;
+@@ -1371,8 +2179,10 @@
+ end.
+
+ get_node_config(Host, From, Node, Lang) ->
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info}] ->
++ Table = get_table(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ case get_affiliation(Info, From) of
+ owner ->
+ Fields = get_node_config_xfields(Node, Info, Lang),
+@@ -1384,7 +2194,7 @@
+ {"type", "form"}],
+ Fields}]}]}]};
+ _ ->
+- {error, ?ERR_NOT_AUTHORIZED}
++ {error, ?ERR_FORBIDDEN}
+ end;
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -1416,7 +2226,7 @@
+ [atom_to_list(O) || O <- Opts])).
+
+
+--define(DEFAULT_OPTIONS,
++-define(DEFAULT_PUBSUB_OPTIONS,
+ [{deliver_payloads, true},
+ {notify_config, false},
+ {notify_delete, false},
+@@ -1424,12 +2234,28 @@
+ {persist_items, true},
+ {max_items, ?MAXITEMS div 2},
+ {subscribe, true},
+- {subscription_model, open},
++ {access_model, open},
++ {access_roster_groups, []},
+ {publish_model, publishers},
+ {max_payload_size, ?MAX_PAYLOAD_SIZE},
+- {send_item_subscribe, false},
++ {send_last_published_item, never},
+ {presence_based_delivery, false}]).
+
++-define(DEFAULT_PEP_OPTIONS,
++ [{deliver_payloads, true},
++ {notify_config, false},
++ {notify_delete, false},
++ {notify_retract, false},
++ {persist_items, false},
++ {max_items, ?MAXITEMS div 2},
++ {subscribe, true},
++ {access_model, presence},
++ {access_roster_groups, []},
++ {publish_model, publishers},
++ {max_payload_size, ?MAX_PAYLOAD_SIZE},
++ {send_last_published_item, on_sub_and_presence},
++ {presence_based_delivery, true}]).
++
+ get_node_option(Info, current_approver) ->
+ Default = hd(get_owners_jids(Info)),
+ Options = Info#nodeinfo.options,
+@@ -1438,15 +2264,32 @@
+ current_approver, 1,
+ Options ++ [{current_approver, Default}])));
+ get_node_option(#nodeinfo{options = Options}, Var) ->
++ %% At this level, it's hard to know which set of defaults to
++ %% apply. Therefore, all newly created nodes have an extra
++ %% "defaults" field. We assume that all nodes created before this
++ %% change are pubsub nodes.
++ {Defaults, Opts} = case Options of
++ [{defaults, pubsub_node} | Tail] ->
++ {?DEFAULT_PUBSUB_OPTIONS, Tail};
++ [{defaults, pep_node} | Tail] ->
++ {?DEFAULT_PEP_OPTIONS, Tail};
++ _ ->
++ {?DEFAULT_PUBSUB_OPTIONS, Options}
++ end,
+ element(
+- 2, element(2, lists:keysearch(Var, 1, Options ++ ?DEFAULT_OPTIONS))).
++ 2, element(2, lists:keysearch(Var, 1, Opts ++ Defaults))).
+
+ get_max_items(Info) ->
+ case get_node_option(Info, persist_items) of
+ true ->
+ get_node_option(Info, max_items);
+ false ->
+- 0
++ case get_node_option(Info, send_last_published_item) of
++ never ->
++ 0;
++ _ ->
++ 1
++ end
+ end.
+
+ get_owners_jids(Info) ->
+@@ -1454,8 +2297,7 @@
+ Owners =
+ ?DICT:fold(
+ fun(JID,
+- #entity{affiliation = Affiliation,
+- subscription = Subscription},
++ #entity{affiliation = Affiliation},
+ Acc) ->
+ case Affiliation of
+ owner ->
+@@ -1467,7 +2309,11 @@
+ lists:sort(Owners).
+
+
+-get_node_config_xfields(Node, Info, Lang) ->
++get_node_config_xfields(_Node, Info, Lang) ->
++ Type = case Info#nodeinfo.options of
++ [{defaults, D} | _] -> D;
++ _ -> pubsub_node
++ end,
+ [?XFIELD("hidden", "", "FORM_TYPE", ?NS_PUBSUB_NODE_CONFIG),
+ ?BOOL_CONFIG_FIELD("Deliver payloads with event notifications", deliver_payloads),
+ ?BOOL_CONFIG_FIELD("Notify subscribers when the node configuration changes", notify_config),
+@@ -1476,12 +2322,24 @@
+ ?BOOL_CONFIG_FIELD("Persist items to storage", persist_items),
+ ?INTEGER_CONFIG_FIELD("Max # of items to persist", max_items),
+ ?BOOL_CONFIG_FIELD("Whether to allow subscriptions", subscribe),
+- ?ALIST_CONFIG_FIELD("Specify the subscriber model", subscription_model,
+- [open]),
++ ?ALIST_CONFIG_FIELD("Specify the access model", access_model,
++ [open, whitelist] ++
++ case Type of
++ pep_node -> [presence, roster];
++ pubsub_node -> [authorize]
++ end),
++ %% XXX: change to list-multi, include current roster groups as options
++ {xmlelement, "field", [{"type", "text-multi"},
++ {"label", translate:translate(Lang, "Roster groups that may subscribe (if access model is roster)")},
++ {"var", "pubsub#access_roster_groups"}],
++ [{xmlelement, "value", [], [{xmlcdata, Value}]} ||
++ Value <- get_node_option(Info, access_roster_groups)]},
+ ?ALIST_CONFIG_FIELD("Specify the publisher model", publish_model,
+ [publishers, subscribers, open]),
+ ?INTEGER_CONFIG_FIELD("Max payload size in bytes", max_payload_size),
+- ?BOOL_CONFIG_FIELD("Send items to new subscribers", send_item_subscribe),
++ %% XXX: fix labels for options
++ ?ALIST_CONFIG_FIELD("When to send the last published item", send_last_published_item,
++ [never, on_sub, on_sub_and_presence]),
+ ?BOOL_CONFIG_FIELD("Only deliver notifications to available users", presence_based_delivery),
+ ?JLIST_CONFIG_FIELD("Specify the current subscription approver", current_approver,
+ get_owners_jids(Info))
+@@ -1489,8 +2347,10 @@
+
+
+ set_node_config(Host, From, Node, Els, Lang) ->
+- case catch mnesia:dirty_read(pubsub_node, {Host, Node}) of
+- [#pubsub_node{info = Info} = N] ->
++ Table = get_table(Host),
++ case catch mnesia:dirty_read(Table, {Host, Node}) of
++ [N] ->
++ Info = get_node_info(N),
+ case get_affiliation(Info, From) of
+ owner ->
+ case xml:remove_cdata(Els) of
+@@ -1510,14 +2370,14 @@
+ {error, ?ERR_BAD_REQUEST}
+ end;
+ _ ->
+- {error, ?ERR_NOT_AUTHORIZED}
++ {error, ?ERR_FORBIDDEN}
+ end;
+ _ ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+ end.
+
+
+-set_node_config1(Host, From, Node, XEl, CurOpts, Lang) ->
++set_node_config1(Host, _From, Node, XEl, CurOpts, Lang) ->
+ XData = jlib:parse_xdata_submit(XEl),
+ case XData of
+ invalid ->
+@@ -1537,78 +2397,97 @@
+
+
+ -define(SET_BOOL_XOPT(Opt, Val),
+- case Val of
+- "0" -> set_xoption(Opts, add_opt(Opt, false, NewOpts));
+- "1" -> set_xoption(Opts, add_opt(Opt, true, NewOpts));
+- _ -> {error, ?ERR_BAD_REQUEST}
++ BoolVal = case Val of
++ "0" -> false;
++ "1" -> true;
++ "false" -> false;
++ "true" -> true;
++ _ -> error
++ end,
++ case BoolVal of
++ error -> {error, ?ERR_NOT_ACCEPTABLE};
++ _ -> set_xoption(Opts, add_opt(Opt, BoolVal, NewOpts), Type)
+ end).
+
+ -define(SET_STRING_XOPT(Opt, Val),
+- set_xoption(Opts, add_opt(Opt, Val, NewOpts))).
++ set_xoption(Opts, add_opt(Opt, Val, NewOpts), Type)).
+
+ -define(SET_INTEGER_XOPT(Opt, Val, Min, Max),
+ case catch list_to_integer(Val) of
+ IVal when is_integer(IVal),
+ IVal >= Min,
+ IVal =< Max ->
+- set_xoption(Opts, add_opt(Opt, IVal, NewOpts));
++ set_xoption(Opts, add_opt(Opt, IVal, NewOpts), Type);
+ _ ->
+- {error, ?ERR_BAD_REQUEST}
++ {error, ?ERR_NOT_ACCEPTABLE}
+ end).
+
+ -define(SET_ALIST_XOPT(Opt, Val, Vals),
+ case lists:member(Val, [atom_to_list(V) || V <- Vals]) of
+ true ->
+- set_xoption(Opts, add_opt(Opt, list_to_atom(Val), NewOpts));
++ set_xoption(Opts, add_opt(Opt, list_to_atom(Val), NewOpts), Type);
+ false ->
+- {error, ?ERR_BAD_REQUEST}
++ {error, ?ERR_NOT_ACCEPTABLE}
+ end).
+
+
+-set_xoption([], NewOpts) ->
++set_xoption(Opts, [{defaults, Type} = Defaults | NewOpts]) ->
++ %% Make sure that "defaults" remains at head of option list.
++ [Defaults | set_xoption(Opts, NewOpts, Type)];
++set_xoption(Opts, NewOpts) ->
++ set_xoption(Opts, NewOpts, pubsub_node).
++
++set_xoption([], NewOpts, _Type) ->
+ NewOpts;
+-set_xoption([{"FORM_TYPE", _} | Opts], NewOpts) ->
+- set_xoption(Opts, NewOpts);
+-set_xoption([{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts) ->
++set_xoption([{"FORM_TYPE", _} | Opts], NewOpts, Type) ->
++ set_xoption(Opts, NewOpts, Type);
++set_xoption([{"pubsub#deliver_payloads", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(deliver_payloads, Val);
+-set_xoption([{"pubsub#notify_config", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#notify_config", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(notify_config, Val);
+-set_xoption([{"pubsub#notify_delete", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#notify_delete", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(notify_delete, Val);
+-set_xoption([{"pubsub#notify_retract", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#notify_retract", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(notify_retract, Val);
+-set_xoption([{"pubsub#persist_items", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#persist_items", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(persist_items, Val);
+-set_xoption([{"pubsub#max_items", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#max_items", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_INTEGER_XOPT(max_items, Val, 0, ?MAXITEMS);
+-set_xoption([{"pubsub#subscribe", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#subscribe", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(subscribe, Val);
+-set_xoption([{"pubsub#subscription_model", [Val]} | Opts], NewOpts) ->
+- ?SET_ALIST_XOPT(subscription_model, Val, [open]);
+-set_xoption([{"pubsub#publish_model", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#access_model", [Val]} | Opts], NewOpts, Type) ->
++ AllowedModels = case Type of
++ pubsub_node -> [open, authorize, whitelist];
++ pep_node -> [open, presence, roster, whitelist]
++ end,
++ ?SET_ALIST_XOPT(access_model, Val, AllowedModels);
++set_xoption([{"pubsub#access_roster_groups", Values} | Opts], NewOpts, Type) ->
++ set_xoption(Opts, add_opt(access_roster_groups, Values, NewOpts), Type);
++set_xoption([{"pubsub#publish_model", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]);
+-set_xoption([{"pubsub#max_payload_size", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#max_payload_size", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_INTEGER_XOPT(max_payload_size, Val, 0, ?MAX_PAYLOAD_SIZE);
+-set_xoption([{"pubsub#send_item_subscribe", [Val]} | Opts], NewOpts) ->
+- ?SET_BOOL_XOPT(send_item_subscribe, Val);
+-set_xoption([{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts) ->
++set_xoption([{"pubsub#send_last_published_item", [Val]} | Opts], NewOpts, Type) ->
++ ?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]);
++set_xoption([{"pubsub#presence_based_delivery", [Val]} | Opts], NewOpts, Type) ->
+ ?SET_BOOL_XOPT(presence_based_delivery, Val);
+-set_xoption([{"pubsub#current_approver", _} | Opts], NewOpts) ->
++set_xoption([{"pubsub#current_approver", _} | Opts], NewOpts, Type) ->
+ % TODO
+- set_xoption(Opts, NewOpts);
++ set_xoption(Opts, NewOpts, Type);
+ %set_xoption([{"title", [Val]} | Opts], NewOpts) ->
+ % ?SET_STRING_XOPT(title, Val);
+-set_xoption([_ | _Opts], _NewOpts) ->
+- {error, ?ERR_BAD_REQUEST}.
++set_xoption([_ | _Opts], _NewOpts, _Type) ->
++ {error, ?ERR_NOT_ACCEPTABLE}.
+
+
+ change_node_opts(Host, NewOpts, Node, Lang) ->
++ Table = get_table(Host),
+ F = fun() ->
+- case mnesia:read({pubsub_node, {Host, Node}}) of
+- [#pubsub_node{info = Info} = N] ->
+- NewInfo = Info#nodeinfo{options = NewOpts},
+- mnesia:write(
+- N#pubsub_node{info = NewInfo}),
++ case mnesia:read({Table, {Host, Node}}) of
++ [N] ->
++ Info = get_node_info(N),
++ NewInfo = Info#nodeinfo{options = maybe_add_defaults(NewOpts, Table)},
++ mnesia:write(set_node_info(N, NewInfo)),
+ {result, []};
+ [] ->
+ {error, ?ERR_ITEM_NOT_FOUND}
+@@ -1624,29 +2503,33 @@
+ {error, ?ERR_INTERNAL_SERVER_ERROR}
+ end.
+
++maybe_add_defaults(NewOpts, Table) ->
++ %% When we change node configuration, we add an explicit default
++ %% marker, pubsub_node or pep_node.
++ case NewOpts of
++ [{defaults, _} | _] ->
++ NewOpts;
++ _ ->
++ [{defaults, Table} | NewOpts]
++ end.
+
+
++get_present_resources({ToUser, ToServer, _}, {FromUser, FromServer, _}) ->
++ get_present_resources(ToUser, ToServer, FromUser, FromServer).
++
++get_present_resources(ToUser, ToServer, FromUser, FromServer) ->
++ %% Return a list of resources of FromUser@FromServer that have
++ %% sent presence to ToUser@ToServer.
++ Key = {ToUser, ToServer, FromUser, FromServer},
++ lists:map(fun(#pubsub_presence{resource = Res}) -> Res end,
++ case catch mnesia:dirty_read(pubsub_presence, Key) of
++ Result when is_list(Result) ->
++ Result;
++ _ ->
++ []
++ end).
+
+
+-find_my_host(LServer) ->
+- Parts = string:tokens(LServer, "."),
+- find_my_host(Parts, ?MYHOSTS).
+-
+-find_my_host([], _Hosts) ->
+- ?MYNAME;
+-find_my_host([_ | Tail] = Parts, Hosts) ->
+- Domain = parts_to_string(Parts),
+- case lists:member(Domain, Hosts) of
+- true ->
+- Domain;
+- false ->
+- find_my_host(Tail, Hosts)
+- end.
+-
+-parts_to_string(Parts) ->
+- string:strip(lists:flatten(lists:map(fun(S) -> [S, $.] end, Parts)),
+- right, $.).
+-
+
+
+ update_table(Host) ->