diff options
Diffstat (limited to 'net-im/ejabberd')
-rw-r--r-- | net-im/ejabberd/ejabberd-1.1.2-r2.ebuild | 207 | ||||
-rw-r--r-- | net-im/ejabberd/files/check_pam.diff | 472 | ||||
-rw-r--r-- | net-im/ejabberd/files/digest-ejabberd-1.1.2-r2 | 9 | ||||
-rw-r--r-- | net-im/ejabberd/files/http_binding.patch | 855 | ||||
-rw-r--r-- | net-im/ejabberd/files/mod_pep.patch | 2319 |
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) -> |