From 568909d5bb454fab2023e7ab1a7cf62eff8dcd39 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Thu, 19 Jan 2006 02:17:31 +0000 Subject: [PATCH] * src/aclocal.m4: Updated for zlib support * src/configure.ac: Likewise * src/mod_muc/mod_muc_room.erl: Weakened presence filtering, added warning in non-anonymous rooms, room destroying updated to latest JEP-0045, added a number of occupants and room name in room's disco#info reply, miscellaneous internal changes (thanks to Sergei Golovan) * src/mod_muc/mod_muc.erl: Better support for nick unregistration (thanks to Sergei Golovan) * src/ejabberd_zlib/ejabberd_zlib.erl: Zlib support (thanks to Sergei Golovan) * src/ejabberd_zlib/ejabberd_zlib_drv.c: Likewise * src/ejabberd_zlib/Makefile.in: Likewise * src/ejabberd_c2s.erl: Stream compression support (JEP-0138) * src/ejabberd_receiver.erl: Likewise * src/mod_disco.erl: Don't split node name before calling hooks (thanks to Sergei Golovan) * src/mod_configure.erl: Support for configuration using ad-hoc commands (thanks to Sergei Golovan) * src/mod_announce.erl: Support for sending announce messages using ad-hoc commands (thanks to Sergei Golovan) * src/mod_adhoc.erl: Ad-hoc support (JEP-0050) (thanks to Magnus Henoch) * src/adhoc.erl: Likewise * src/adhoc.hrl: Likewise * src/jlib.hrl: Updated (thanks to Sergei Golovan) * src/gen_mod.erl: Added function is_loaded/2 (thanks to Sergei Golovan) * src/ejabberd_service.erl: Changed error message on handshake error (thanks to Sergei Golovan) * src/ejabberd.cfg.example: Updated (thanks to Sergei Golovan) SVN Revision: 486 --- ChangeLog | 47 +- src/Makefile.in | 2 +- src/aclocal.m4 | 31 ++ src/adhoc.erl | 111 ++++ src/adhoc.hrl | 15 + src/configure | 286 ++++++++++- src/configure.ac | 6 +- src/ejabberd.cfg.example | 5 +- src/ejabberd_c2s.erl | 35 +- src/ejabberd_receiver.erl | 26 +- src/ejabberd_service.erl | 2 +- src/ejabberd_zlib/Makefile.in | 38 ++ src/ejabberd_zlib/ejabberd_zlib.erl | 134 +++++ src/ejabberd_zlib/ejabberd_zlib_drv.c | 171 +++++++ src/gen_mod.erl | 6 +- src/jlib.hrl | 4 + src/mod_adhoc.erl | 250 ++++++++++ src/mod_announce.erl | 443 +++++++++++++++- src/mod_configure.erl | 694 ++++++++++++++++---------- src/mod_disco.erl | 41 +- src/mod_muc/mod_muc.erl | 123 ++--- src/mod_muc/mod_muc_room.erl | 295 ++++++----- 22 files changed, 2264 insertions(+), 501 deletions(-) create mode 100644 src/adhoc.erl create mode 100644 src/adhoc.hrl create mode 100644 src/ejabberd_zlib/Makefile.in create mode 100644 src/ejabberd_zlib/ejabberd_zlib.erl create mode 100644 src/ejabberd_zlib/ejabberd_zlib_drv.c create mode 100644 src/mod_adhoc.erl diff --git a/ChangeLog b/ChangeLog index 6bb7b3e48..204f36349 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,49 @@ -2006-01-13 Mickaël Rémond +2006-01-19 Alexey Shchepin + + * src/aclocal.m4: Updated for zlib support + * src/configure.ac: Likewise + + * src/mod_muc/mod_muc_room.erl: Weakened presence filtering, added + warning in non-anonymous rooms, room destroying updated to latest + JEP-0045, added a number of occupants and room name in room's + disco#info reply, miscellaneous internal changes (thanks to Sergei + Golovan) + + * src/mod_muc/mod_muc.erl: Better support for nick unregistration + (thanks to Sergei Golovan) + + * src/ejabberd_zlib/ejabberd_zlib.erl: Zlib support (thanks to + Sergei Golovan) + * src/ejabberd_zlib/ejabberd_zlib_drv.c: Likewise + * src/ejabberd_zlib/Makefile.in: Likewise + * src/ejabberd_c2s.erl: Stream compression support (JEP-0138) + * src/ejabberd_receiver.erl: Likewise + + * src/mod_disco.erl: Don't split node name before calling hooks + (thanks to Sergei Golovan) + + * src/mod_configure.erl: Support for configuration using ad-hoc + commands (thanks to Sergei Golovan) + + * src/mod_announce.erl: Support for sending announce messages + using ad-hoc commands (thanks to Sergei Golovan) + + * src/mod_adhoc.erl: Ad-hoc support (JEP-0050) (thanks to Magnus + Henoch) + * src/adhoc.erl: Likewise + * src/adhoc.hrl: Likewise + + * src/jlib.hrl: Updated (thanks to Sergei Golovan) + + * src/gen_mod.erl: Added function is_loaded/2 (thanks to Sergei + Golovan) + + * src/ejabberd_service.erl: Changed error message on handshake + error (thanks to Sergei Golovan) + + * src/ejabberd.cfg.example: Updated (thanks to Sergei Golovan) + +2006-01-13 Mickael Remond * src/odbc/ejabberd_odbc.erl: underscore and percent are now only escaped in like queries. MySQL where not escaping those escaped diff --git a/src/Makefile.in b/src/Makefile.in index fe36ea2e7..ae9bc210d 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -23,7 +23,7 @@ endif prefix = @prefix@ -SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ +SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@ ERLSHLIBS = expat_erl.so SOURCES = $(wildcard *.erl) BEAMS = $(SOURCES:.erl=.beam) diff --git a/src/aclocal.m4 b/src/aclocal.m4 index 09a692199..362c70387 100644 --- a/src/aclocal.m4 +++ b/src/aclocal.m4 @@ -29,6 +29,37 @@ AC_DEFUN(AM_WITH_EXPAT, AC_SUBST(EXPAT_LIBS) ]) +AC_DEFUN(AM_WITH_ZLIB, +[ AC_ARG_WITH(zlib, + [ --with-zlib=PREFIX prefix where zlib is installed]) + + ZLIB_CFLAGS= + ZLIB_LIBS= + if test x"$with_zlib" != x; then + ZLIB_CFLAGS="-I$with_zlib/include" + ZLIB_LIBS="-L$with_zlib/lib" + fi + + AC_CHECK_LIB(z, gzgets, + [ ZLIB_LIBS="$ZLIB_LIBS -lz" + zlib_found=yes ], + [ zlib_found=no ], + "$ZLIB_LIBS") + if test $zlib_found = no; then + AC_MSG_ERROR([Could not find the zlib library]) + fi + zlib_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $ZLIB_CFLAGS" + AC_CHECK_HEADERS(zlib.h, , zlib_found=no) + if test $zlib_found = no; then + AC_MSG_ERROR([Could not find zlib.h]) + fi + CFLAGS="$zlib_save_CFLAGS" + + AC_SUBST(ZLIB_CFLAGS) + AC_SUBST(ZLIB_LIBS) +]) + AC_DEFUN(AM_WITH_ERLANG, [ AC_ARG_WITH(erlang, [ --with-erlang=PREFIX path to erlc and erl ]) diff --git a/src/adhoc.erl b/src/adhoc.erl new file mode 100644 index 000000000..b6954d2ec --- /dev/null +++ b/src/adhoc.erl @@ -0,0 +1,111 @@ +%%%---------------------------------------------------------------------- +%%% File : adhoc.erl +%%% Author : Magnus Henoch +%%% Purpose : Provide helper functions for ad-hoc commands (JEP-0050) +%%% Created : 31 Oct 2005 by Magnus Henoch +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(adhoc). +-author('henoch@dtek.chalmers.se'). +-vsn('$Revision$ '). + +-export([parse_request/1, + produce_response/2, + produce_response/1]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("adhoc.hrl"). + +%% Parse an ad-hoc request. Return either an adhoc_request record or +%% an {error, ErrorType} tuple. +parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) -> + ?DEBUG("entering parse_request...", []), + Node = xml:get_tag_attr_s("node", SubEl), + SessionID = xml:get_tag_attr_s("sessionid", SubEl), + Action = xml:get_tag_attr_s("action", SubEl), + XData = find_xdata_el(SubEl), + {xmlelement, _, _, AllEls} = SubEl, + if XData -> + Others = lists:delete(XData, AllEls); + true -> + Others = AllEls + end, + + #adhoc_request{lang = Lang, + node = Node, + sessionid = SessionID, + action = Action, + xdata = XData, + others = Others}; +parse_request(_) -> + {error, ?ERR_BAD_REQUEST}. + +%% Borrowed from mod_vcard.erl +find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> + find_xdata_el1(SubEls). + +find_xdata_el1([]) -> + false; +find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) -> + case xml:get_attr_s("xmlns", Attrs) of + ?NS_XDATA -> + {xmlelement, Name, Attrs, SubEls}; + _ -> + find_xdata_el1(Els) + end; +find_xdata_el1([_ | Els]) -> + find_xdata_el1(Els). + +%% Produce a node to use as response from an adhoc_response +%% record, filling in values for language, node and session id from +%% the request. +produce_response(#adhoc_request{lang = Lang, + node = Node, + sessionid = SessionID}, + Response) -> + produce_response(Response#adhoc_response{lang = Lang, + node = Node, + sessionid = SessionID}). + +%% Produce a node to use as response from an adhoc_response +%% record. +produce_response(#adhoc_response{lang = _Lang, + node = Node, + sessionid = ProvidedSessionID, + status = Status, + defaultaction = DefaultAction, + actions = Actions, + notes = Notes, + elements = Elements}) -> + SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" -> + ProvidedSessionID; + true -> + jlib:now_to_utc_string(now()) + end, + case Actions of + [] -> + ActionsEls = []; + _ -> + case DefaultAction of + "" -> + ActionsElAttrs = []; + _ -> + ActionsElAttrs = [{"execute", DefaultAction}] + end, + ActionsEls = [{xmlelement, "actions", + ActionsElAttrs, + [{xmlelement, Action, [], []} || Action <- Actions]}] + end, + NotesEls = lists:map(fun({Type, Text}) -> + {xmlelement, "note", + [{"type", Type}], + [{xmlcdata, Text}]} + end, Notes), + {xmlelement, "command", + [{"xmlns", ?NS_COMMANDS}, + {"sessionid", SessionID}, + {"node", Node}, + {"status", atom_to_list(Status)}], + ActionsEls ++ NotesEls ++ Elements}. diff --git a/src/adhoc.hrl b/src/adhoc.hrl new file mode 100644 index 000000000..2888a6639 --- /dev/null +++ b/src/adhoc.hrl @@ -0,0 +1,15 @@ +-record(adhoc_request, {lang, + node, + sessionid, + action, + xdata, + others}). + +-record(adhoc_response, {lang, + node, + sessionid, + status, + defaultaction = "", + actions = [], + notes = [], + elements = []}). diff --git a/src/configure b/src/configure index 132cbc927..06b982cfd 100755 --- a/src/configure +++ b/src/configure @@ -310,7 +310,7 @@ ac_includes_default="\ # include #endif" -ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT SET_MAKE ERLC ac_pt_ERLC ERL ac_pt_ERL ERLANG_CFLAGS ERLANG_LIBS LIBICONV CPP EGREP EXPAT_CFLAGS EXPAT_LIBS LIBOBJS mod_pubsub make_mod_pubsub mod_irc make_mod_irc mod_muc make_mod_muc eldap make_eldap web make_web tls make_tls odbc make_odbc roster_gateway_workaround SSL_LIBS SSL_CFLAGS LTLIBOBJS' +ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT SET_MAKE ERLC ac_pt_ERLC ERL ac_pt_ERL ERLANG_CFLAGS ERLANG_LIBS LIBICONV CPP EGREP EXPAT_CFLAGS EXPAT_LIBS ZLIB_CFLAGS ZLIB_LIBS LIBOBJS mod_pubsub make_mod_pubsub mod_irc make_mod_irc mod_muc make_mod_muc eldap make_eldap web make_web tls make_tls odbc make_odbc ejabberd_zlib make_ejabberd_zlib roster_gateway_workaround SSL_LIBS SSL_CFLAGS LTLIBOBJS' ac_subst_files='' # Initialize some variables set by options. @@ -850,6 +850,7 @@ Optional Features: --enable-web enable web (default: yes) --enable-tls enable tls (default: yes) --enable-odbc enable odbc (default: no) + --enable-ejabberd_zlib enable ejabberd_zlib (default: yes) --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions Optional Packages: @@ -858,6 +859,7 @@ Optional Packages: --with-erlang=PREFIX path to erlc and erl --with-libiconv-prefix=PREFIX prefix where libiconv is installed --with-expat=PREFIX prefix where EXPAT is installed + --with-zlib=PREFIX prefix where zlib is installed --with-openssl=PREFIX prefix where OPENSSL is installed Some influential environment variables: @@ -3563,6 +3565,259 @@ echo "$as_me: error: Could not find expat.h" >&2;} +#locating zlib + +# Check whether --with-zlib or --without-zlib was given. +if test "${with_zlib+set}" = set; then + withval="$with_zlib" + +fi; + + ZLIB_CFLAGS= + ZLIB_LIBS= + if test x"$with_zlib" != x; then + ZLIB_CFLAGS="-I$with_zlib/include" + ZLIB_LIBS="-L$with_zlib/lib" + fi + + echo "$as_me:$LINENO: checking for gzgets in -lz" >&5 +echo $ECHO_N "checking for gzgets in -lz... $ECHO_C" >&6 +if test "${ac_cv_lib_z_gzgets+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lz "$ZLIB_LIBS" $LIBS" +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +/* Override any gcc2 internal prototype to avoid an error. */ +#ifdef __cplusplus +extern "C" +#endif +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char gzgets (); +int +main () +{ +gzgets (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5 + (eval $ac_link) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; } && + { ac_try='test -s conftest$ac_exeext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_cv_lib_z_gzgets=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +ac_cv_lib_z_gzgets=no +fi +rm -f conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +echo "$as_me:$LINENO: result: $ac_cv_lib_z_gzgets" >&5 +echo "${ECHO_T}$ac_cv_lib_z_gzgets" >&6 +if test $ac_cv_lib_z_gzgets = yes; then + ZLIB_LIBS="$ZLIB_LIBS -lz" + zlib_found=yes +else + zlib_found=no +fi + + if test $zlib_found = no; then + { { echo "$as_me:$LINENO: error: Could not find the zlib library" >&5 +echo "$as_me: error: Could not find the zlib library" >&2;} + { (exit 1); exit 1; }; } + fi + zlib_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $ZLIB_CFLAGS" + +for ac_header in zlib.h +do +as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` +if eval "test \"\${$as_ac_Header+set}\" = set"; then + echo "$as_me:$LINENO: checking for $ac_header" >&5 +echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 +if eval "test \"\${$as_ac_Header+set}\" = set"; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +fi +echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 +echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 +else + # Is the header compilable? +echo "$as_me:$LINENO: checking $ac_header usability" >&5 +echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6 +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include <$ac_header> +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + ac_header_compiler=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +ac_header_compiler=no +fi +rm -f conftest.err conftest.$ac_objext conftest.$ac_ext +echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +echo "${ECHO_T}$ac_header_compiler" >&6 + +# Is the header present? +echo "$as_me:$LINENO: checking $ac_header presence" >&5 +echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6 +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <$ac_header> +_ACEOF +if { (eval echo "$as_me:$LINENO: \"$ac_cpp conftest.$ac_ext\"") >&5 + (eval $ac_cpp conftest.$ac_ext) 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null; then + if test -s conftest.err; then + ac_cpp_err=$ac_c_preproc_warn_flag + ac_cpp_err=$ac_cpp_err$ac_c_werror_flag + else + ac_cpp_err= + fi +else + ac_cpp_err=yes +fi +if test -z "$ac_cpp_err"; then + ac_header_preproc=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi +rm -f conftest.err conftest.$ac_ext +echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +echo "${ECHO_T}$ac_header_preproc" >&6 + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 +echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 +echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 +echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 +echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 +echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 +echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 +echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 +echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} + ( + cat <<\_ASBOX +## --------------------------------- ## +## Report this to BUG-REPORT-ADDRESS ## +## --------------------------------- ## +_ASBOX + ) | + sed "s/^/$as_me: WARNING: /" >&2 + ;; +esac +echo "$as_me:$LINENO: checking for $ac_header" >&5 +echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6 +if eval "test \"\${$as_ac_Header+set}\" = set"; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + eval "$as_ac_Header=\$ac_header_preproc" +fi +echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_Header'}'`" >&5 +echo "${ECHO_T}`eval echo '${'$as_ac_Header'}'`" >&6 + +fi +if test `eval echo '${'$as_ac_Header'}'` = yes; then + cat >>confdefs.h <<_ACEOF +#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +else + zlib_found=no +fi + +done + + if test $zlib_found = no; then + { { echo "$as_me:$LINENO: error: Could not find zlib.h" >&5 +echo "$as_me: error: Could not find zlib.h" >&2;} + { (exit 1); exit 1; }; } + fi + CFLAGS="$zlib_save_CFLAGS" + + + + # Checks for typedefs, structures, and compiler characteristics. echo "$as_me:$LINENO: checking for an ANSI C-conforming const" >&5 @@ -4224,6 +4479,28 @@ echo "${ECHO_T}$mr_enable_odbc" >&6 +ejabberd_zlib= +make_ejabberd_zlib= +echo "$as_me:$LINENO: checking whether build ejabberd_zlib" >&5 +echo $ECHO_N "checking whether build ejabberd_zlib... $ECHO_C" >&6 +# Check whether --enable-ejabberd_zlib or --disable-ejabberd_zlib was given. +if test "${enable_ejabberd_zlib+set}" = set; then + enableval="$enable_ejabberd_zlib" + mr_enable_ejabberd_zlib="$enableval" +else + mr_enable_ejabberd_zlib=yes +fi; +if test "$mr_enable_ejabberd_zlib" = "yes"; then +ejabberd_zlib=ejabberd_zlib +make_ejabberd_zlib=ejabberd_zlib/Makefile +fi +echo "$as_me:$LINENO: result: $mr_enable_ejabberd_zlib" >&5 +echo "${ECHO_T}$mr_enable_ejabberd_zlib" >&6 + + + + + # Check whether --enable-roster_gateway_workaround or --disable-roster_gateway_workaround was given. if test "${enable_roster_gateway_workaround+set}" = set; then enableval="$enable_roster_gateway_workaround" @@ -4239,7 +4516,7 @@ else fi; - ac_config_files="$ac_config_files Makefile $make_mod_irc $make_mod_muc $make_mod_pubsub $make_eldap $make_web stringprep/Makefile $make_tls $make_odbc" + ac_config_files="$ac_config_files Makefile $make_mod_irc $make_mod_muc $make_mod_pubsub $make_eldap $make_web stringprep/Makefile $make_tls $make_odbc $make_ejabberd_zlib" #openssl @@ -5056,6 +5333,7 @@ do "stringprep/Makefile" ) CONFIG_FILES="$CONFIG_FILES stringprep/Makefile" ;; "$make_tls" ) CONFIG_FILES="$CONFIG_FILES $make_tls" ;; "$make_odbc" ) CONFIG_FILES="$CONFIG_FILES $make_odbc" ;; + "$make_ejabberd_zlib" ) CONFIG_FILES="$CONFIG_FILES $make_ejabberd_zlib" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} { (exit 1); exit 1; }; };; @@ -5158,6 +5436,8 @@ s,@CPP@,$CPP,;t t s,@EGREP@,$EGREP,;t t s,@EXPAT_CFLAGS@,$EXPAT_CFLAGS,;t t s,@EXPAT_LIBS@,$EXPAT_LIBS,;t t +s,@ZLIB_CFLAGS@,$ZLIB_CFLAGS,;t t +s,@ZLIB_LIBS@,$ZLIB_LIBS,;t t s,@LIBOBJS@,$LIBOBJS,;t t s,@mod_pubsub@,$mod_pubsub,;t t s,@make_mod_pubsub@,$make_mod_pubsub,;t t @@ -5173,6 +5453,8 @@ s,@tls@,$tls,;t t s,@make_tls@,$make_tls,;t t s,@odbc@,$odbc,;t t s,@make_odbc@,$make_odbc,;t t +s,@ejabberd_zlib@,$ejabberd_zlib,;t t +s,@make_ejabberd_zlib@,$make_ejabberd_zlib,;t t s,@roster_gateway_workaround@,$roster_gateway_workaround,;t t s,@SSL_LIBS@,$SSL_LIBS,;t t s,@SSL_CFLAGS@,$SSL_CFLAGS,;t t diff --git a/src/configure.ac b/src/configure.ac index 166eb438a..04033f6b5 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -14,6 +14,8 @@ AM_WITH_ERLANG AM_ICONV #locating libexpat AM_WITH_EXPAT +#locating zlib +AM_WITH_ZLIB # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -29,6 +31,7 @@ AC_MOD_ENABLE(eldap, yes) AC_MOD_ENABLE(web, yes) AC_MOD_ENABLE(tls, yes) AC_MOD_ENABLE(odbc, no) +AC_MOD_ENABLE(ejabberd_zlib, yes) AC_ARG_ENABLE(roster_gateway_workaround, [ --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions], @@ -47,7 +50,8 @@ AC_CONFIG_FILES([Makefile $make_web stringprep/Makefile $make_tls - $make_odbc]) + $make_odbc + $make_ejabberd_zlib]) #openssl AM_WITH_OPENSSL AC_OUTPUT diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example index 2f7fdfe9a..749ba5276 100644 --- a/src/ejabberd.cfg.example +++ b/src/ejabberd.cfg.example @@ -134,13 +134,14 @@ {mod_register, [{access, register}]}, {mod_roster, []}, {mod_privacy, []}, - {mod_configure, []}, + {mod_adhoc, []}, + {mod_configure, []}, % Depends on mod_adhoc {mod_configure2, []}, {mod_disco, []}, {mod_stats, []}, {mod_vcard, []}, {mod_offline, []}, - {mod_announce, [{access, announce}]}, + {mod_announce, [{access, announce}]}, % Depends on mod_adhoc {mod_echo, [{host, "echo.localhost"}]}, {mod_private, []}, {mod_irc, []}, diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 891756acf..4a21772cb 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -46,6 +46,7 @@ sasl_state, access, shaper, + zlib = false, tls = false, tls_required = false, tls_enabled = false, @@ -125,6 +126,7 @@ init([{SockMod, Socket}, Opts]) -> {value, {_, S}} -> S; _ -> none end, + Zlib = lists:member(zlib, Opts), StartTLS = lists:member(starttls, Opts), StartTLSRequired = lists:member(starttls_required, Opts), TLSEnabled = lists:member(tls, Opts), @@ -145,6 +147,7 @@ init([{SockMod, Socket}, Opts]) -> {ok, wait_for_stream, #state{socket = Socket1, sockmod = SockMod1, receiver = ReceiverPid, + zlib = Zlib, tls = TLS, tls_required = StartTLSRequired, tls_enabled = TLSEnabled, @@ -200,10 +203,22 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> {xmlelement, "mechanism", [], [{xmlcdata, S}]} end, cyrsasl:listmech(Server)), + SockMod = StateData#state.sockmod, + Zlib = StateData#state.zlib, + CompressFeature = + case Zlib andalso + (SockMod /= ejabberd_zlib) of + true -> + [{xmlelement, "compression", + [{"xmlns", ?NS_FEATURE_COMPRESS}], + [{xmlelement, "method", + [], [{xmlcdata, "zlib"}]}]}]; + _ -> + [] + end, TLS = StateData#state.tls, TLSEnabled = StateData#state.tls_enabled, TLSRequired = StateData#state.tls_required, - SockMod = StateData#state.sockmod, TLSFeature = case (TLS == true) andalso (TLSEnabled == false) andalso @@ -224,7 +239,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> end, send_element(StateData, {xmlelement, "stream:features", [], - TLSFeature ++ + TLSFeature ++ CompressFeature ++ [{xmlelement, "mechanisms", [{"xmlns", ?NS_SASL}], Mechs}] ++ @@ -337,7 +352,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> end, send_element(StateData, Res), {next_state, wait_for_auth, StateData}; - {auth, _ID, set, {U, P, D, ""}} -> + {auth, _ID, set, {_U, _P, _D, ""}} -> Err = jlib:make_error_reply( El, ?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)), @@ -434,6 +449,7 @@ wait_for_auth(closed, StateData) -> wait_for_feature_request({xmlstreamelement, El}, StateData) -> {xmlelement, Name, Attrs, Els} = El, + Zlib = StateData#state.zlib, TLS = StateData#state.tls, TLSEnabled = StateData#state.tls_enabled, TLSRequired = StateData#state.tls_required, @@ -497,6 +513,19 @@ wait_for_feature_request({xmlstreamelement, El}, StateData) -> streamid = new_id(), tls_enabled = true }}; + {?NS_COMPRESS, "compress"} when Zlib == true, + SockMod /= ejabberd_zlib -> + Socket = StateData#state.socket, + {ok, ZlibSocket} = ejabberd_zlib:enable_zlib(SockMod, Socket), + ejabberd_receiver:compress(StateData#state.receiver, ZlibSocket), + send_element(StateData, + {xmlelement, "compressed", + [{"xmlns", ?NS_COMPRESS}], []}), + {next_state, wait_for_stream, + StateData#state{sockmod = ejabberd_zlib, + socket = ZlibSocket, + streamid = new_id() + }}; _ -> if (SockMod == gen_tcp) and TLSRequired -> diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index d09bef666..466a5c401 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -17,6 +17,7 @@ change_shaper/2, reset_stream/1, starttls/2, + compress/2, become_controller/1, close/1]). @@ -54,6 +55,9 @@ reset_stream(Pid) -> starttls(Pid, TLSSocket) -> gen_server:call(Pid, {starttls, TLSSocket}). +compress(Pid, ZlibSocket) -> + gen_server:call(Pid, {compress, ZlibSocket}). + become_controller(Pid) -> gen_server:call(Pid, become_controller). @@ -110,6 +114,20 @@ handle_call({starttls, TLSSocket}, _From, {error, _Reason} -> {stop, normal, ok, NewState} end; +handle_call({compress, ZlibSocket}, _From, + #state{xml_stream_state = XMLStreamState, + c2s_pid = C2SPid} = State) -> + xml_stream:close(XMLStreamState), + NewXMLStreamState = xml_stream:new(C2SPid), + NewState = State#state{socket = ZlibSocket, + sock_mod = ejabberd_zlib, + xml_stream_state = NewXMLStreamState}, + case ejabberd_zlib:recv_data(ZlibSocket, "") of + {ok, ZlibData} -> + {reply, ok, process_data(ZlibData, NewState)}; + {error, _Reason} -> + {stop, normal, ok, NewState} + end; handle_call(reset_stream, _From, #state{xml_stream_state = XMLStreamState, c2s_pid = C2SPid} = State) -> @@ -157,6 +175,13 @@ handle_info({Tag, _TCPSocket, Data}, {error, _Reason} -> {stop, normal, State} end; + ejabberd_zlib -> + case ejabberd_zlib:recv_data(Socket, Data) of + {ok, ZlibData} -> + {noreply, process_data(ZlibData, State)}; + {error, _Reason} -> + {stop, normal, State} + end; _ -> {noreply, process_data(Data, State)} end; @@ -211,7 +236,6 @@ activate_socket(#state{socket = Socket, SockMod:setopts(Socket, [{active, once}]) end. - process_data(Data, #state{xml_stream_state = XMLStreamState, shaper_state = ShaperState} = State) -> diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index a8c1989b4..2a207b129 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -179,7 +179,7 @@ wait_for_handshake({xmlstreamelement, El}, StateData) -> end, StateData#state.hosts), {next_state, stream_established, StateData}; _ -> - send_text(StateData, ?INVALID_HEADER_ERR), + send_text(StateData, ?INVALID_HANDSHAKE_ERR), {stop, normal, StateData} end; _ -> diff --git a/src/ejabberd_zlib/Makefile.in b/src/ejabberd_zlib/Makefile.in new file mode 100644 index 000000000..1b4b5a021 --- /dev/null +++ b/src/ejabberd_zlib/Makefile.in @@ -0,0 +1,38 @@ +# $Id$ + +CC = @CC@ +CFLAGS = @CFLAGS@ @ZLIB_CFLAGS@ @ERLANG_CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ @ZLIB_LIBS@ @ERLANG_LIBS@ + +SUBDIRS = + +ERLSHLIBS = ../ejabberd_zlib_drv.so + +OUTDIR = .. +EFLAGS = -I .. -pz .. +OBJS = \ + $(OUTDIR)/ejabberd_zlib.beam + +all: $(OBJS) $(ERLSHLIBS) + +$(OUTDIR)/%.beam: %.erl + @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $< + +#all: $(ERLSHLIBS) +# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt + +$(ERLSHLIBS): ../%.so: %.c + $(CC) -Wall $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \ + $(subst ../,,$(subst .so,.c,$@)) $(LIBS) \ + -o $@ -fpic -shared + +clean: + rm -f $(OBJS) $(ERLSHLIBS) + +distclean: clean + rm -f Makefile + +TAGS: + etags *.erl diff --git a/src/ejabberd_zlib/ejabberd_zlib.erl b/src/ejabberd_zlib/ejabberd_zlib.erl new file mode 100644 index 000000000..2f74108df --- /dev/null +++ b/src/ejabberd_zlib/ejabberd_zlib.erl @@ -0,0 +1,134 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_zlib.erl +%%% Author : Alexey Shchepin +%%% Purpose : Interface to zlib +%%% Created : 19 Jan 2006 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(ejabberd_zlib). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +-behaviour(gen_server). + +-export([start/0, start_link/0, + enable_zlib/2, disable_zlib/1, + send/2, + recv/2, recv/3, recv_data/2, + setopts/2, + controlling_process/2, + close/1]). + +%% Internal exports, call-back functions. +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + code_change/3, + terminate/2]). + +-define(DEFLATE, 1). +-define(INFLATE, 2). + +-record(zlibsock, {sockmod, socket, zlibport}). + +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of + ok -> ok; + {error, already_loaded} -> ok + end, + Port = open_port({spawn, ejabberd_zlib_drv}, [binary]), + {ok, Port}. + + +%%% -------------------------------------------------------- +%%% The call-back functions. +%%% -------------------------------------------------------- + +handle_call(_, _, State) -> + {noreply, State}. + +handle_cast(_, State) -> + {noreply, State}. + +handle_info({'EXIT', Port, Reason}, Port) -> + {stop, {port_died, Reason}, Port}; + +handle_info({'EXIT', _Pid, _Reason}, Port) -> + {noreply, Port}; + +handle_info(_, State) -> + {noreply, State}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +terminate(_Reason, Port) -> + Port ! {self, close}, + ok. + + +enable_zlib(SockMod, Socket) -> + case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of + ok -> ok; + {error, already_loaded} -> ok + end, + Port = open_port({spawn, ejabberd_zlib_drv}, [binary]), + {ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}. + +disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) -> + port_close(Port), + {SockMod, Socket}. + +recv(Socket, Length) -> + recv(Socket, Length, infinity). +recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock, + Length, Timeout) -> + case SockMod:recv(Socket, Length, Timeout) of + {ok, Packet} -> + recv_data(ZlibSock, Packet); + {error, _Reason} = Error -> + Error + end. + +recv_data(#zlibsock{zlibport = Port} = _ZlibSock, Packet) -> + case port_control(Port, ?INFLATE, Packet) of + <<0, In/binary>> -> + {ok, In}; + <<1, Error/binary>> -> + {error, binary_to_list(Error)} + end. + +send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}, + Packet) -> + case port_control(Port, ?DEFLATE, Packet) of + <<0, Out/binary>> -> + SockMod:send(Socket, Out); + <<1, Error/binary>> -> + {error, binary_to_list(Error)} + end. + + +setopts(#zlibsock{sockmod = SockMod, socket = Socket}, Opts) -> + case SockMod of + gen_tcp -> + inet:setopts(Socket, Opts); + _ -> + SockMod:setopts(Socket, Opts) + end. + +controlling_process(#zlibsock{sockmod = SockMod, socket = Socket}, Pid) -> + SockMod:controlling_process(Socket, Pid). + +close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) -> + SockMod:close(Socket), + port_close(Port). + + diff --git a/src/ejabberd_zlib/ejabberd_zlib_drv.c b/src/ejabberd_zlib/ejabberd_zlib_drv.c new file mode 100644 index 000000000..075e31176 --- /dev/null +++ b/src/ejabberd_zlib/ejabberd_zlib_drv.c @@ -0,0 +1,171 @@ +/* $Id$ */ + +#include +#include +#include +#include + + +#define BUF_SIZE 1024 + +typedef struct { + ErlDrvPort port; + z_stream *d_stream; + z_stream *i_stream; +} ejabberd_zlib_data; + + +static ErlDrvData ejabberd_zlib_drv_start(ErlDrvPort port, char *buff) +{ + ejabberd_zlib_data *d = + (ejabberd_zlib_data *)driver_alloc(sizeof(ejabberd_zlib_data)); + d->port = port; + + d->d_stream = (z_stream *)malloc(sizeof(z_stream)); + + d->d_stream->zalloc = (alloc_func)0; + d->d_stream->zfree = (free_func)0; + d->d_stream->opaque = (voidpf)0; + + deflateInit(d->d_stream, Z_DEFAULT_COMPRESSION); + + d->i_stream = (z_stream *)malloc(sizeof(z_stream)); + + d->i_stream->zalloc = (alloc_func)0; + d->i_stream->zfree = (free_func)0; + d->i_stream->opaque = (voidpf)0; + + inflateInit(d->i_stream); + + set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); + + return (ErlDrvData)d; +} + +static void ejabberd_zlib_drv_stop(ErlDrvData handle) +{ + ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle; + + deflateEnd(d->d_stream); + free(d->d_stream); + + inflateEnd(d->i_stream); + free(d->i_stream); + + driver_free((char *)handle); +} + + +#define DEFLATE 1 +#define INFLATE 2 + +#define die_unless(cond, errstr) \ + if (!(cond)) \ + { \ + rlen = strlen(errstr) + 1; \ + b = driver_realloc_binary(b, rlen); \ + b->orig_bytes[0] = 1; \ + strncpy(b->orig_bytes + 1, errstr, rlen - 1); \ + *rbuf = (char *)b; \ + return rlen; \ + } + + +static int ejabberd_zlib_drv_control(ErlDrvData handle, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle; + int err; + int size; + ErlDrvBinary *b; + + switch (command) + { + case DEFLATE: + size = BUF_SIZE + 1; + rlen = 1; + b = driver_alloc_binary(size); + b->orig_bytes[0] = 0; + + d->d_stream->next_in = buf; + d->d_stream->avail_in = len; + d->d_stream->avail_out = 0; + err = Z_OK; + + while (err == Z_OK && d->d_stream->avail_out == 0) + { + d->d_stream->next_out = b->orig_bytes + rlen; + d->d_stream->avail_out = BUF_SIZE; + + err = deflate(d->d_stream, Z_SYNC_FLUSH); + die_unless((err == Z_OK) || (err == Z_STREAM_END), + "Deflate error"); + + rlen += (BUF_SIZE - d->d_stream->avail_out); + size += (BUF_SIZE - d->d_stream->avail_out); + b = driver_realloc_binary(b, size); + } + b = driver_realloc_binary(b, rlen); + *rbuf = (char *)b; + return rlen; + case INFLATE: + size = BUF_SIZE + 1; + rlen = 1; + b = driver_alloc_binary(size); + b->orig_bytes[0] = 0; + + if (len > 0) { + d->i_stream->next_in = buf; + d->i_stream->avail_in = len; + d->i_stream->avail_out = 0; + err = Z_OK; + + while (err == Z_OK && d->i_stream->avail_out == 0) + { + d->i_stream->next_out = b->orig_bytes + rlen; + d->i_stream->avail_out = BUF_SIZE; + + err = inflate(d->i_stream, Z_SYNC_FLUSH); + die_unless((err == Z_OK) || (err == Z_STREAM_END), + "Inflate error"); + + rlen += (BUF_SIZE - d->i_stream->avail_out); + size += (BUF_SIZE - d->i_stream->avail_out); + b = driver_realloc_binary(b, size); + } + } + b = driver_realloc_binary(b, rlen); + *rbuf = (char *)b; + return rlen; + } + + b = driver_alloc_binary(1); + b->orig_bytes[0] = 0; + *rbuf = (char *)b; + return 1; +} + + +ErlDrvEntry ejabberd_zlib_driver_entry = { + NULL, /* F_PTR init, N/A */ + ejabberd_zlib_drv_start, /* L_PTR start, called when port is opened */ + ejabberd_zlib_drv_stop, /* F_PTR stop, called when port is closed */ + NULL, /* F_PTR output, called when erlang has sent */ + NULL, /* F_PTR ready_input, called when input descriptor ready */ + NULL, /* F_PTR ready_output, called when output descriptor ready */ + "ejabberd_zlib_drv", /* char *driver_name, the argument to open_port */ + NULL, /* F_PTR finish, called when unloaded */ + NULL, /* handle */ + ejabberd_zlib_drv_control, /* F_PTR control, port_command callback */ + NULL, /* F_PTR timeout, reserved */ + NULL /* F_PTR outputv, reserved */ +}; + +DRIVER_INIT(ejabberd_zlib_drv) /* must match name in driver_entry */ +{ + return &ejabberd_zlib_driver_entry; +} + + diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 70d1b0af6..5f96a2fc6 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -19,7 +19,8 @@ loaded_modules/1, loaded_modules_with_opts/1, get_hosts/2, - get_module_proc/2]). + get_module_proc/2, + is_loaded/2]). -export([behaviour_info/1]). @@ -144,3 +145,6 @@ get_hosts(Opts, Prefix) -> get_module_proc(Host, Base) -> list_to_atom(atom_to_list(Base) ++ "_" ++ Host). +is_loaded(Host, Module) -> + ets:member(ejabberd_modules, {Module, Host}). + diff --git a/src/jlib.hrl b/src/jlib.hrl index 5f9e17b50..ca261320f 100644 --- a/src/jlib.hrl +++ b/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_COMMANDS, "http://jabber.org/protocol/commands"). -define(NS_EJABBERD_CONFIG, "ejabberd:config"). @@ -48,6 +49,9 @@ -define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth"). -define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register"). +-define(NS_FEATURE_COMPRESS, "http://jabber.org/features/compress"). + +-define(NS_COMPRESS, "http://jabber.org/protocol/compress"). % TODO: remove "code" attribute (currently it used for backward-compatibility) -define(STANZA_ERROR(Code, Type, Condition), diff --git a/src/mod_adhoc.erl b/src/mod_adhoc.erl new file mode 100644 index 000000000..4288524ab --- /dev/null +++ b/src/mod_adhoc.erl @@ -0,0 +1,250 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_adhoc.erl +%%% Author : Magnus Henoch +%%% Purpose : Handle incoming ad-doc requests (JEP-0050) +%%% Created : 15 Nov 2005 by Magnus Henoch +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(mod_adhoc). +-author('henoch@dtek.chalmers.se'). +-vsn('$Revision$ '). + +-behaviour(gen_mod). + +-export([start/2, + stop/1, + process_local_iq/3, + process_sm_iq/3, + get_local_commands/5, + get_local_identity/5, + get_local_features/5, + get_sm_commands/5, + get_sm_identity/5, + get_sm_features/5, + ping_item/4, + ping_command/4]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("adhoc.hrl"). + +start(Host, Opts) -> + IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), + + gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS, + ?MODULE, process_local_iq, IQDisc), + gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS, + ?MODULE, process_sm_iq, IQDisc), + + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 99), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 99), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_commands, 99), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99), + ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 99), + ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_commands, 99), + ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, ping_item, 100), + ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, ping_command, 100). + +stop(Host) -> + ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, ping_command, 100), + ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, ping_item, 100), + ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_commands, 99), + ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 99), + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 99), + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_commands, 99), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 99), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 99), + + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS), + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS). + +%------------------------------------------------------------------------- + +get_local_commands(Acc, _From, #jid{server = Server, lserver = LServer} = _To, "", Lang) -> + Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false), + case Display of + false -> + Acc; + _ -> + Items = case Acc of + {result, I} -> I; + _ -> [] + end, + Nodes = [{xmlelement, + "item", + [{"jid", Server}, + {"node", ?NS_COMMANDS}, + {"name", translate:translate(Lang, "Commands")}], + []}], + {result, Items ++ Nodes} + end; + +get_local_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) -> + ejabberd_hooks:run_fold(adhoc_local_items, LServer, {result, []}, [From, To, Lang]); + +get_local_commands(_Acc, _From, _To, "ping", _Lang) -> + {result, []}; + +get_local_commands(Acc, _From, _To, _Node, _Lang) -> + Acc. + +%------------------------------------------------------------------------- + +get_sm_commands(Acc, _From, #jid{lserver = LServer} = To, "", Lang) -> + Display = gen_mod:get_module_opt(LServer, ?MODULE, report_commands_node, false), + case Display of + false -> + Acc; + _ -> + Items = case Acc of + {result, I} -> I; + _ -> [] + end, + Nodes = [{xmlelement, + "item", + [{"jid", jlib:jid_to_string(To)}, + {"node", ?NS_COMMANDS}, + {"name", translate:translate(Lang, "Commands")}], + []}], + {result, Items ++ Nodes} + end; + +get_sm_commands(_Acc, From, #jid{lserver = LServer} = To, ?NS_COMMANDS, Lang) -> + ejabberd_hooks:run_fold(adhoc_sm_items, LServer, {result, []}, [From, To, Lang]); + +get_sm_commands(Acc, _From, _To, _Node, _Lang) -> + Acc. + +%------------------------------------------------------------------------- + +%% On disco info request to the ad-hoc node, return automation/command-list. +get_local_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> + [{xmlelement, "identity", + [{"category", "automation"}, + {"type", "command-list"}, + {"name", translate:translate(Lang, "Commands")}], []} | Acc]; + +get_local_identity(Acc, _From, _To, "ping", Lang) -> + [{xmlelement, "identity", + [{"category", "automation"}, + {"type", "command-node"}, + {"name", translate:translate(Lang, "Ping")}], []} | Acc]; + +get_local_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +%------------------------------------------------------------------------- + +%% On disco info request to the ad-hoc node, return automation/command-list. +get_sm_identity(Acc, _From, _To, ?NS_COMMANDS, Lang) -> + [{xmlelement, "identity", + [{"category", "automation"}, + {"type", "command-list"}, + {"name", translate:translate(Lang, "Commands")}], []} | Acc]; + +get_sm_identity(Acc, _From, _To, _Node, _Lang) -> + Acc. + +%------------------------------------------------------------------------- + +get_local_features(Acc, _From, _To, "", _Lang) -> + Feats = case Acc of + {result, I} -> I; + _ -> [] + end, + {result, Feats ++ [?NS_COMMANDS]}; + +get_local_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) -> + %% override all lesser features... + {result, []}; + +get_local_features(_Acc, _From, _To, "ping", _Lang) -> + %% override all lesser features... + {result, [?NS_COMMANDS]}; + +get_local_features(Acc, _From, _To, _Node, _Lang) -> + Acc. + +%------------------------------------------------------------------------- + +get_sm_features(Acc, _From, _To, "", _Lang) -> + Feats = case Acc of + {result, I} -> I; + _ -> [] + end, + {result, Feats ++ [?NS_COMMANDS]}; + +get_sm_features(_Acc, _From, _To, ?NS_COMMANDS, _Lang) -> + %% override all lesser features... + {result, []}; + +get_sm_features(Acc, _From, _To, _Node, _Lang) -> + Acc. + +%------------------------------------------------------------------------- + +process_local_iq(From, To, IQ) -> + process_adhoc_request(From, To, IQ, adhoc_local_commands). + + +process_sm_iq(From, To, IQ) -> + process_adhoc_request(From, To, IQ, adhoc_sm_commands). + + +process_adhoc_request(From, To, #iq{sub_el = SubEl} = IQ, Hook) -> + ?DEBUG("About to parse ~p...", [IQ]), + case adhoc:parse_request(IQ) of + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]}; + #adhoc_request{} = AdhocRequest -> + Host = To#jid.lserver, + case ejabberd_hooks:run_fold(Hook, Host, empty, + [From, To, AdhocRequest]) of + ignore -> + ignore; + empty -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]}; + Command -> + IQ#iq{type = result, sub_el = [Command]} + end + end. + + +ping_item(Acc, _From, #jid{server = Server} = _To, Lang) -> + Items = case Acc of + {result, I} -> + I; + _ -> + [] + end, + Nodes = [{xmlelement, "item", + [{"jid", Server}, + {"node", "ping"}, + {"name", translate:translate(Lang, "Ping")}], + []}], + {result, Items ++ Nodes}. + + +ping_command(_Acc, _From, _To, + #adhoc_request{lang = Lang, + node = "ping", + sessionid = _Sessionid, + action = Action} = Request) -> + if + Action == ""; Action == "execute" -> + adhoc:produce_response( + Request, + #adhoc_response{status = completed, + notes = [{"info", translate:translate( + Lang, + "Pong")}]}); + true -> + {error, ?ERR_BAD_REQUEST} + end; + +ping_command(Acc, _From, _To, _Request) -> + Acc. + diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 2426ff371..8cabf748a 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -15,10 +15,16 @@ init/0, stop/1, announce/3, - send_motd/1]). + send_motd/1, + disco_identity/5, + disco_features/5, + disco_items/5, + announce_commands/4, + announce_items/4]). -include("ejabberd.hrl"). -include("jlib.hrl"). +-include("adhoc.hrl"). -record(motd, {server, packet}). -record(motd_users, {us, dummy = []}). @@ -33,6 +39,11 @@ start(Host, _Opts) -> update_tables(), ejabberd_hooks:add(local_send_to_resource_hook, Host, ?MODULE, announce, 50), + ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50), + ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50), + ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 50), + ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, announce_items, 50), + ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, announce_commands, 50), ejabberd_hooks:add(user_available_hook, Host, ?MODULE, send_motd, 50), register(gen_mod:get_module_proc(Host, ?PROCNAME), @@ -66,6 +77,11 @@ loop() -> end. stop(Host) -> + ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, announce_commands, 50), + ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, announce_items, 50), + ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 50), + ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 50), + ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 50), ejabberd_hooks:delete(local_send_to_resource_hook, Host, ?MODULE, announce, 50), ejabberd_hooks:delete(sm_register_connection_hook, Host, @@ -74,6 +90,7 @@ stop(Host) -> exit(whereis(Proc), stop), {wait, Proc}. +% Announcing via messages to a custom resource announce(From, To, Packet) -> case To of #jid{luser = "", lresource = Res} -> @@ -105,12 +122,422 @@ announce(From, To, Packet) -> ok end. +%------------------------------------------------------------------------- +% Announcing via ad-hoc commands +-define(INFO_COMMAND(Lang, Node), + [{xmlelement, "identity", + [{"category", "automation"}, + {"type", "command-node"}, + {"name", get_title(Lang, Node)}], []}]). + +disco_identity(Acc, _From, _To, Node, Lang) -> + case Node of + "announce/all" -> + ?INFO_COMMAND(Lang, Node); + "announce/all-hosts/online" -> + ?INFO_COMMAND(Lang, Node); + "announce/online" -> + ?INFO_COMMAND(Lang, Node); + "announce/motd" -> + ?INFO_COMMAND(Lang, Node); + "announce/motd/delete" -> + ?INFO_COMMAND(Lang, Node); + "announce/motd/update" -> + ?INFO_COMMAND(Lang, Node); + _ -> + Acc + end. + +%------------------------------------------------------------------------- + +-define(INFO_RESULT(Allow, Feats), + case Allow of + deny -> + {error, ?ERR_FORBIDDEN}; + allow -> + {result, Feats} + end). + +disco_features(Acc, From, #jid{lserver = LServer} = _To, + "announce", _Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), + case {acl:match_rule(LServer, Access1, From), + acl:match_rule(global, Access2, From)} of + {deny, deny} -> + {error, ?ERR_FORBIDDEN}; + _ -> + {result, []} + end + end; + +disco_features(Acc, From, #jid{lserver = LServer} = _To, + "announce/all-hosts/online", _Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Allow = acl:match_rule(global, Access, From), + ?INFO_RESULT(Allow, [?NS_COMMANDS]) + end; + +disco_features(Acc, From, #jid{lserver = LServer} = _To, + Node, _Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Allow = acl:match_rule(LServer, Access, From), + case Node of + "announce/all" -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + "announce/online" -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + "announce/motd" -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + "announce/motd/delete" -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + "announce/motd/update" -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + _ -> + Acc + end + end. + +%------------------------------------------------------------------------- + +-define(NODE_TO_ITEM(Lang, Server, Node), + {xmlelement, "item", + [{"jid", Server}, + {"node", Node}, + {"name", get_title(Lang, Node)}], + []}). + +-define(ITEMS_RESULT(Allow, Items), + case Allow of + deny -> + {error, ?ERR_FORBIDDEN}; + allow -> + {result, Items} + end). + +disco_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, + "", Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), + case {acl:match_rule(LServer, Access1, From), + acl:match_rule(global, Access2, From)} of + {deny, deny} -> + Acc; + _ -> + Items = case Acc of + {result, I} -> I; + _ -> [] + end, + Nodes = [?NODE_TO_ITEM(Lang, Server, "announce")], + {result, Items ++ Nodes} + end + end; + +disco_items(Acc, From, #jid{lserver = LServer} = To, "announce", Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + announce_items(Acc, From, To, Lang) + end; + +disco_items(Acc, From, #jid{lserver = LServer} = _To, + "announce/all-hosts/online", _Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Allow = acl:match_rule(global, Access, From), + ?ITEMS_RESULT(Allow, []) + end; + +disco_items(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Allow = acl:match_rule(LServer, Access, From), + case Node of + "announce/all" -> + ?ITEMS_RESULT(Allow, []); + "announce/online" -> + ?ITEMS_RESULT(Allow, []); + "announce/motd" -> + ?ITEMS_RESULT(Allow, []); + "announce/motd/delete" -> + ?ITEMS_RESULT(Allow, []); + "announce/motd/update" -> + ?ITEMS_RESULT(Allow, []); + _ -> + Acc + end + end. + +%------------------------------------------------------------------------- + +announce_items(Acc, From, #jid{lserver = LServer, server = Server} = _To, Lang) -> + Access1 = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Nodes1 = case acl:match_rule(LServer, Access1, From) of + allow -> + [?NODE_TO_ITEM(Lang, Server, "announce/all"), + ?NODE_TO_ITEM(Lang, Server, "announce/online"), + ?NODE_TO_ITEM(Lang, Server, "announce/motd"), + ?NODE_TO_ITEM(Lang, Server, "announce/motd/delete"), + ?NODE_TO_ITEM(Lang, Server, "announce/motd/update")]; + deny -> + [] + end, + Access2 = gen_mod:get_module_opt(global, ?MODULE, access, none), + Nodes2 = case acl:match_rule(global, Access2, From) of + allow -> + [?NODE_TO_ITEM(Lang, Server, "announce/all-hosts/online")]; + deny -> + [] + end, + case {Nodes1, Nodes2} of + {[], []} -> + Acc; + _ -> + Items = case Acc of + {result, I} -> I; + _ -> [] + end, + {result, Items ++ Nodes1 ++ Nodes2} + end. + +%------------------------------------------------------------------------- + +-define(COMMANDS_RESULT(Allow, From, To, Request), + case Allow of + deny -> + {error, ?ERR_FORBIDDEN}; + allow -> + announce_commands(From, To, Request) + end). + +announce_commands(_Acc, From, To, + #adhoc_request{ + node = "announce/all-hosts/online"} = Request) -> + Access = gen_mod:get_module_opt(global, ?MODULE, access, none), + Allow = acl:match_rule(global, Access, From), + ?COMMANDS_RESULT(Allow, From, To, Request); + +announce_commands(Acc, From, #jid{lserver = LServer} = To, + #adhoc_request{node = Node} = Request) -> + Access = gen_mod:get_module_opt(LServer, ?MODULE, access, none), + Allow = acl:match_rule(LServer, Access, From), + case Node of + "announce/all" -> + ?COMMANDS_RESULT(Allow, From, To, Request); + "announce/online" -> + ?COMMANDS_RESULT(Allow, From, To, Request); + "announce/motd" -> + ?COMMANDS_RESULT(Allow, From, To, Request); + "announce/motd/delete" -> + ?COMMANDS_RESULT(Allow, From, To, Request); + "announce/motd/update" -> + ?COMMANDS_RESULT(Allow, From, To, Request); + _ -> + Acc + end. + +%------------------------------------------------------------------------- + +announce_commands(From, To, + #adhoc_request{lang = Lang, + node = Node, + action = Action, + xdata = XData} = Request) -> + %% If the "action" attribute is not present, it is + %% understood as "execute". If there was no + %% element in the first response (which there isn't in our + %% case), "execute" and "complete" are equivalent. + ActionIsExecute = lists:member(Action, + ["", "execute", "complete"]), + if Action == "cancel" -> + %% User cancels request + adhoc:produce_response(Request, + #adhoc_response{status = canceled}); + XData == false, ActionIsExecute -> + %% User requests form + adhoc:produce_response( + Request, + #adhoc_response{status = executing, + elements = [generate_adhoc_form(Lang, Node)]}); + XData /= false, ActionIsExecute -> + %% User returns form. + case jlib:parse_xdata_submit(XData) of + invalid -> + {error, ?ERR_BAD_REQUEST}; + Fields -> + handle_adhoc_form(From, To, Request, Fields) + end; + true -> + {error, ?ERR_BAD_REQUEST} + end. + +generate_adhoc_form(Lang, Node) -> + {xmlelement, "x", + [{"xmlns", ?NS_XDATA}, + {"type", "form"}], + [{xmlelement, "title", [], [{xmlcdata, get_title(Lang, Node)}]}] + ++ + if Node == "announce/motd/delete" -> + [{xmlelement, "field", + [{"var", "confirm"}, + {"type", "boolean"}, + {"label", translate:translate(Lang, "Really delete message of the day?")}], + [{xmlelement, "value", + [], + [{xmlcdata, "true"}]}]}]; + true -> + [{xmlelement, "field", + [{"var", "subject"}, + {"type", "text-single"}, + {"label", translate:translate(Lang, "Subject")}], + []}, + {xmlelement, "field", + [{"var", "body"}, + {"type", "text-multi"}, + {"label", translate:translate(Lang, "Message body")}], + []}] + end}. + +join_lines([]) -> + []; +join_lines(Lines) -> + join_lines(Lines, []). +join_lines([Line|Lines], Acc) -> + join_lines(Lines, ["\n",Line|Acc]); +join_lines([], Acc) -> + %% Remove last newline + lists:flatten(lists:reverse(tl(Acc))). + +handle_adhoc_form(From, #jid{lserver = LServer} = To, + #adhoc_request{lang = Lang, + node = Node, + sessionid = SessionID}, + Fields) -> + Confirm = case lists:keysearch("confirm", 1, Fields) of + {value, {"confirm", ["true"]}} -> + true; + {value, {"confirm", ["1"]}} -> + true; + _ -> + false + end, + Subject = case lists:keysearch("subject", 1, Fields) of + {value, {"subject", SubjectLines}} -> + %% There really shouldn't be more than one + %% subject line, but can we stop them? + join_lines(SubjectLines); + _ -> + [] + end, + Body = case lists:keysearch("body", 1, Fields) of + {value, {"body", BodyLines}} -> + join_lines(BodyLines); + _ -> + [] + end, + Response = #adhoc_response{lang = Lang, + node = Node, + sessionid = SessionID, + status = completed}, + Packet = {xmlelement, "message", [{"type", "normal"}], + if Subject /= [] -> + [{xmlelement, "subject", [], + [{xmlcdata, Subject}]}]; + true -> + [] + end ++ + if Body /= [] -> + [{xmlelement, "body", [], + [{xmlcdata, Body}]}]; + true -> + [] + end}, + + Proc = gen_mod:get_module_proc(LServer, ?PROCNAME), + case {Node, Body} of + {"announce/motd/delete", _} -> + if Confirm -> + Proc ! {announce_motd_delete, From, To, Packet}, + adhoc:produce_response(Response); + true -> + adhoc:produce_response(Response) + end; + {_, []} -> + %% An announce message with no body is definitely an operator error. + %% Throw an error and give him/her a chance to send message again. + {error, ?ERRT_NOT_ACCEPTABLE( + Lang, + "No body provided for announce message")}; + %% Now send the packet to ?PROCNAME. + %% We don't use direct announce_* functions because it + %% leads to large delay in response and queries processing + {"announce/all", _} -> + Proc ! {announce_all, From, To, Packet}, + adhoc:produce_response(Response); + {"announce/online", _} -> + Proc ! {announce_online, From, To, Packet}, + adhoc:produce_response(Response); + {"announce/all-hosts/online", _} -> + Proc ! {announce_all_hosts_online, From, To, Packet}, + adhoc:produce_response(Response); + {"announce/motd", _} -> + Proc ! {announce_motd, From, To, Packet}, + adhoc:produce_response(Response); + {"announce/motd/update", _} -> + Proc ! {announce_motd_update, From, To, Packet}, + adhoc:produce_response(Response); + _ -> + %% This can't happen, as we haven't registered any other + %% command nodes. + {error, ?ERR_INTERNAL_SERVER_ERROR} + end. + +get_title(Lang, "announce") -> + translate:translate(Lang, "Announcements"); +get_title(Lang, "announce/all") -> + translate:translate(Lang, "Send announcement to all users"); +get_title(Lang, "announce/online") -> + translate:translate(Lang, "Send announcement to all online users"); +get_title(Lang, "announce/all-hosts/online") -> + translate:translate(Lang, "Send announcement to all online users on all hosts"); +get_title(Lang, "announce/motd") -> + translate:translate(Lang, "Set message of the day and send to online users"); +get_title(Lang, "announce/motd/update") -> + translate:translate(Lang, "Update message of the day (don't send)"); +get_title(Lang, "announce/motd/delete") -> + translate:translate(Lang, "Delete message of the day"). + +%------------------------------------------------------------------------- + announce_all(From, To, Packet) -> Host = To#jid.lserver, Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), + Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> Local = jlib:make_jid("", To#jid.server, ""), @@ -126,7 +553,7 @@ announce_online(From, To, Packet) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), + Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> announce_online1(ejabberd_sm:get_vh_session_list(Host), @@ -138,7 +565,7 @@ announce_all_hosts_online(From, To, Packet) -> Access = gen_mod:get_module_opt(global, ?MODULE, access, none), case acl:match_rule(global, Access, From) of deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), + Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> announce_online1(ejabberd_sm:dirty_get_sessions_list(), @@ -159,7 +586,7 @@ announce_motd(From, To, Packet) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), + Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> announce_motd_update(To#jid.lserver, Packet), @@ -179,7 +606,7 @@ announce_motd_update(From, To, Packet) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), + Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> announce_motd_update(Host, Packet) @@ -197,7 +624,7 @@ announce_motd_delete(From, To, Packet) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access, none), case acl:match_rule(Host, Access, From) of deny -> - Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED), + Err = jlib:make_error_reply(Packet, ?ERR_FORBIDDEN), ejabberd_router:route(To, From, Err); allow -> announce_motd_delete(Host) @@ -237,6 +664,7 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID) -> ok end. +%------------------------------------------------------------------------- update_tables() -> update_motd_table(), @@ -326,3 +754,4 @@ update_motd_users_table() -> ?INFO_MSG("Recreating motd_users table", []), mnesia:transform_table(motd_users, ignore, Fields) end. + diff --git a/src/mod_configure.erl b/src/mod_configure.erl index 078686747..0a21e02bb 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -17,134 +17,210 @@ get_local_identity/5, get_local_features/5, get_local_items/5, + adhoc_local_items/4, + adhoc_local_commands/4, + get_sm_identity/5, get_sm_features/5, get_sm_items/5, - process_local_iq/3, - process_sm_iq/3]). + adhoc_sm_items/4, + adhoc_sm_commands/4]). -include("ejabberd.hrl"). -include("jlib.hrl"). +-include("adhoc.hrl"). -start(Host, Opts) -> - IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), - gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_EJABBERD_CONFIG, - ?MODULE, process_local_iq, IQDisc), - gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_EJABBERD_CONFIG, - ?MODULE, process_sm_iq, IQDisc), +start(Host, _Opts) -> ejabberd_hooks:add(disco_local_items, Host, ?MODULE, get_local_items, 50), ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_local_features, 50), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, get_local_identity, 50), ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, get_sm_items, 50), ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50), + ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50), + ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE, adhoc_local_items, 50), + ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE, adhoc_local_commands, 50), + ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE, adhoc_sm_items, 50), + ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE, adhoc_sm_commands, 50), ok. stop(Host) -> + ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE, adhoc_sm_commands, 50), + ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE, adhoc_sm_items, 50), + ejabberd_hooks:delete(adhoc_local_commands, Host, ?MODULE, adhoc_local_commands, 50), + ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE, adhoc_local_items, 50), + ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, get_sm_items, 50), ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, get_local_identity, 50), ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_local_features, 50), ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, get_local_items, 50), - gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_EJABBERD_CONFIG), - gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_EJABBERD_CONFIG). + gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS), + gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_COMMANDS). +%%%----------------------------------------------------------------------- --define(EMPTY_INFO_RESULT, {result, Feats}). +-define(INFO_IDENTITY(Category, Type, Name, Lang), + [{xmlelement, "identity", + [{"category", Category}, + {"type", Type}, + {"name", translate:translate(Lang, Name)}], []}]). -get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> - Acc; +-define(INFO_COMMAND(Name, Lang), + ?INFO_IDENTITY("automation", "command-node", Name, Lang)). + +get_sm_identity(Acc, _From, _To, Node, Lang) -> + case Node of + "config" -> + ?INFO_COMMAND("Configuration", Lang); + _ -> + Acc + end. + +get_local_identity(Acc, _From, _To, Node, Lang) -> + LNode = string:tokens(Node, "/"), + case LNode of + ["running nodes", ENode] -> + ?INFO_IDENTITY("ejabberd", "node", ENode, Lang); + ["running nodes", _ENode, "DB"] -> + ?INFO_COMMAND("DB", Lang); + ["running nodes", _ENode, "modules", "start"] -> + ?INFO_COMMAND("Start Modules", Lang); + ["running nodes", _ENode, "modules", "stop"] -> + ?INFO_COMMAND("Stop Modules", Lang); + ["running nodes", _ENode, "backup", "backup"] -> + ?INFO_COMMAND("Backup", Lang); + ["running nodes", _ENode, "backup", "restore"] -> + ?INFO_COMMAND("Restore", Lang); + ["running nodes", _ENode, "backup", "textfile"] -> + ?INFO_COMMAND("Dump to Text File", Lang); + ["running nodes", _ENode, "import", "file"] -> + ?INFO_COMMAND("Import File", Lang); + ["running nodes", _ENode, "import", "dir"] -> + ?INFO_COMMAND("Import Directory", Lang); + ["config", "hostname"] -> + ?INFO_COMMAND("Host Name", Lang); + ["config", "acls"] -> + ?INFO_COMMAND("Access Control Lists", Lang); + ["config", "access"] -> + ?INFO_COMMAND("Access Rules", Lang); + _ -> + Acc + end. + +%%%----------------------------------------------------------------------- + +-define(INFO_RESULT(Allow, Feats), + case Allow of + deny -> + {error, ?ERR_FORBIDDEN}; + allow -> + {result, Feats} + end). get_sm_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> - case {acl:match_rule(LServer, configure, From), Node} of - {allow, []} -> - case Acc of - {result, Features} -> - {result, [?NS_EJABBERD_CONFIG | Features]}; - empty -> - {result, [?NS_EJABBERD_CONFIG]} - end; + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; _ -> - Acc + Allow = acl:match_rule(LServer, configure, From), + case Node of + "config" -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + _ -> + Acc + end end. -get_local_identity(Acc, _From, _To, Node, _Lang) -> - case Node of - ["running nodes", ENode] -> - [{xmlelement, "identity", - [{"category", "ejabberd"}, - {"type", "node"}, - {"name", ENode}], []}]; - _ -> - Acc - end. - -get_local_features({error, _Error} = Acc, _From, _To, _Node, _Lang) -> - Acc; - -get_local_features(Acc, _From, _To, [], _Lang) -> - Acc; - get_local_features(Acc, From, #jid{lserver = LServer} = _To, Node, _Lang) -> - Feats = case Acc of - {result, Features} -> Features; - empty -> [] - end, - case {acl:match_rule(LServer, configure, From), Node} of - {deny, _} -> - {error, ?ERR_NOT_ALLOWED}; - {allow, ["config"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["online users"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["all users"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["all users", [$@ | _]]} -> - ?EMPTY_INFO_RESULT; - {allow, ["outgoing s2s" | _]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["stopped nodes"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", _ENode]} -> - {result, [?NS_STATS | Feats]}; - {allow, ["running nodes", _ENode, "DB"]} -> - {result, [?NS_EJABBERD_CONFIG | Feats]}; - {allow, ["running nodes", _ENode, "modules"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", _ENode, "modules", _]} -> - {result, [?NS_EJABBERD_CONFIG | Feats]}; - {allow, ["running nodes", _ENode, "backup"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", _ENode, "backup", _]} -> - {result, [?NS_EJABBERD_CONFIG | Feats]}; - {allow, ["running nodes", _ENode, "import"]} -> - ?EMPTY_INFO_RESULT; - {allow, ["running nodes", _ENode, "import", _]} -> - {result, [?NS_EJABBERD_CONFIG | Feats]}; - {allow, ["config", _]} -> - {result, [?NS_EJABBERD_CONFIG | Feats]}; + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + LNode = string:tokens(Node, "/"), + Allow = acl:match_rule(LServer, configure, From), + case LNode of + ["config"] -> + ?INFO_RESULT(Allow, []); + ["online users"] -> + ?INFO_RESULT(Allow, []); + ["all users"] -> + ?INFO_RESULT(Allow, []); + ["all users", [$@ | _]] -> + ?INFO_RESULT(Allow, []); + ["outgoing s2s" | _] -> + ?INFO_RESULT(Allow, []); + ["running nodes"] -> + ?INFO_RESULT(Allow, []); + ["stopped nodes"] -> + ?INFO_RESULT(Allow, []); + ["running nodes", _ENode] -> + ?INFO_RESULT(Allow, [?NS_STATS]); + ["running nodes", _ENode, "DB"] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ["running nodes", _ENode, "modules"] -> + ?INFO_RESULT(Allow, []); + ["running nodes", _ENode, "modules", _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ["running nodes", _ENode, "backup"] -> + ?INFO_RESULT(Allow, []); + ["running nodes", _ENode, "backup", _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ["running nodes", _ENode, "import"] -> + ?INFO_RESULT(Allow, []); + ["running nodes", _ENode, "import", _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + ["config", _] -> + ?INFO_RESULT(Allow, [?NS_COMMANDS]); + _ -> + Acc + end + end. + +%%%----------------------------------------------------------------------- + +adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To, Lang) -> + case acl:match_rule(LServer, configure, From) of + allow -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + Nodes = [{xmlelement, "item", + [{"jid", jlib:jid_to_string(To)}, + {"name", translate:translate(Lang, "Configuration")}, + {"node", "config"}], []}], + {result, Items ++ Nodes}; _ -> Acc end. -get_sm_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> - Acc; +%%%----------------------------------------------------------------------- get_sm_items(Acc, From, - #jid{user = User, server = Server, lserver = LServer} = _To, - Node, _Lang) -> - case {acl:match_rule(LServer, configure, From), Node} of - {allow, []} -> - Items = case Acc of - {result, Its} -> - Its; - empty -> - [] - end, - {result, Items ++ get_user_resources(User, Server)}; + #jid{user = User, server = Server, lserver = LServer} = To, + Node, Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; _ -> - Acc + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + case {acl:match_rule(LServer, configure, From), Node} of + {allow, ""} -> + Nodes = [{xmlelement, "item", + [{"jid", jlib:jid_to_string(To)}, + {"name", translate:translate(Lang, "Configuration")}, + {"node", "config"}], []}], + {result, Items ++ Nodes ++ get_user_resources(User, Server)}; + {allow, "config"} -> + {result, []}; + {_, "config"} -> + {error, ?ERR_FORBIDDEN}; + _ -> + Acc + end end. get_user_resources(User, Server) -> @@ -155,31 +231,149 @@ get_user_resources(User, Server) -> {"name", User}], []} end, lists:sort(Rs)). -get_local_items({error, _Error} = Acc, _From, _To, _Node, _Lang) -> - Acc; +%%%----------------------------------------------------------------------- -get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) -> - Items = case Acc of - {result, Its} -> - Its; - empty -> +adhoc_local_items(Acc, From, #jid{lserver = LServer, server = Server} = To, + Lang) -> + case acl:match_rule(LServer, configure, From) of + allow -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + %% Recursively get all configure commands + Nodes = recursively_get_local_items(LServer, "", Server, Lang), + Nodes1 = lists:filter( + fun(N) -> + Nd = xml:get_tag_attr_s("node", N), + F = get_local_features([], From, To, Nd, Lang), + case F of + {result, [?NS_COMMANDS]} -> + true; + _ -> + false + end + end, Nodes), + {result, Items ++ Nodes1}; + _ -> + Acc + end. + +recursively_get_local_items(_LServer, "online users", _Server, _Lang) -> + []; + +recursively_get_local_items(_LServer, "all users", _Server, _Lang) -> + []; + +recursively_get_local_items(LServer, Node, Server, Lang) -> + LNode = string:tokens(Node, "/"), + Items = case get_local_items(LServer, LNode, Server, Lang) of + {result, Res} -> + Res; + {error, _Error} -> [] end, - case acl:match_rule(LServer, configure, From) of - deny when Node /= [] -> - {error, ?ERR_NOT_ALLOWED}; + Nodes = lists:flatten( + lists:map( + fun(N) -> + S = xml:get_tag_attr_s("jid", N), + Nd = xml:get_tag_attr_s("node", N), + if (S /= Server) or (Nd == "") -> + []; + true -> + [N, recursively_get_local_items( + LServer, Nd, Server, Lang)] + end + end, Items)), + Nodes. + +%%%----------------------------------------------------------------------- + +-define(ITEMS_RESULT(Allow, LNode, Fallback), + case Allow of deny -> - {result, Items}; - _ -> - case get_local_items(To#jid.lserver, Node, + Fallback; + allow -> + case get_local_items(LServer, LNode, jlib:jid_to_string(To), Lang) of {result, Res} -> - {result, Items ++ Res}; + {result, Res}; {error, Error} -> - {error, Error} + {error, Error} + end + end). + +get_local_items(Acc, From, #jid{lserver = LServer} = To, "", Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + Items = case Acc of + {result, Its} -> Its; + empty -> [] + end, + Allow = acl:match_rule(LServer, configure, From), + case Allow of + deny -> + {result, Items}; + allow -> + case get_local_items(LServer, [], + jlib:jid_to_string(To), Lang) of + {result, Res} -> + {result, Items ++ Res}; + {error, _Error} -> + {result, Items} + end + end + end; + +get_local_items(Acc, From, #jid{lserver = LServer} = To, Node, Lang) -> + case gen_mod:is_loaded(LServer, mod_adhoc) of + false -> + Acc; + _ -> + LNode = string:tokens(Node, "/"), + Allow = acl:match_rule(LServer, configure, From), + case LNode of + ["config"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["online users"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["all users"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["all users", [$@ | _]] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["outgoing s2s" | _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["stopped nodes"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "DB"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "modules"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "modules", _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "backup"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "backup", _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "import"] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["running nodes", _ENode, "import", _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + ["config", _] -> + ?ITEMS_RESULT(Allow, LNode, {error, ?ERR_FORBIDDEN}); + _ -> + Acc end end. +%%%----------------------------------------------------------------------- + -define(NODE(Name, Node), {xmlelement, "item", [{"jid", Server}, @@ -422,72 +616,86 @@ get_stopped_nodes(_Lang) -> end, lists:sort(DBNodes)) end. +%------------------------------------------------------------------------- - -process_local_iq(From, To, #iq{type = Type, xmlns = XMLNS, - lang = Lang, sub_el = SubEl} = IQ) -> - case acl:match_rule(To#jid.lserver, configure, From) of +-define(COMMANDS_RESULT(Allow, From, To, Request), + case Allow of deny -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + {error, ?ERR_FORBIDDEN}; allow -> - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, _SubEls} -> - case xml:get_attr_s("type", Attrs) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = - string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_form(Node, Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end - end; - get -> - Node = - string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), - case get_form(Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [{xmlelement, "query", [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end + adhoc_local_commands(From, To, Request) + end). + +adhoc_local_commands(Acc, From, #jid{lserver = LServer} = To, + #adhoc_request{node = Node} = Request) -> + LNode = string:tokens(Node, "/"), + Allow = acl:match_rule(LServer, configure, From), + case LNode of + ["running nodes", _ENode, "DB"] -> + ?COMMANDS_RESULT(Allow, From, To, Request); + ["running nodes", _ENode, "modules", _] -> + ?COMMANDS_RESULT(Allow, From, To, Request); + ["running nodes", _ENode, "backup", _] -> + ?COMMANDS_RESULT(Allow, From, To, Request); + ["running nodes", _ENode, "import", _] -> + ?COMMANDS_RESULT(Allow, From, To, Request); + ["config", _] -> + ?COMMANDS_RESULT(Allow, From, To, Request); + _ -> + Acc end. +adhoc_local_commands(_From, _To, + #adhoc_request{lang = Lang, + node = Node, + sessionid = SessionID, + action = Action, + xdata = XData} = Request) -> + LNode = string:tokens(Node, "/"), + %% If the "action" attribute is not present, it is + %% understood as "execute". If there was no + %% element in the first response (which there isn't in our + %% case), "execute" and "complete" are equivalent. + ActionIsExecute = lists:member(Action, + ["", "execute", "complete"]), + if Action == "cancel" -> + %% User cancels request + adhoc:produce_response( + Request, + #adhoc_response{status = canceled}); + XData == false, ActionIsExecute -> + %% User requests form + case get_form(LNode, Lang) of + {result, Form} -> + adhoc:produce_response( + Request, + #adhoc_response{status = executing, + elements = Form}); + {error, Error} -> + {error, Error} + end; + XData /= false, ActionIsExecute -> + %% User returns form. + case jlib:parse_xdata_submit(XData) of + invalid -> + {error, ?ERR_BAD_REQUEST}; + Fields -> + case set_form(LNode, Lang, Fields) of + {result, _Res} -> + adhoc:produce_response( + #adhoc_response{lang = Lang, + node = Node, + sessionid = SessionID, + status = completed}); + {error, Error} -> + {error, Error} + end + end; + true -> + {error, ?ERR_BAD_REQUEST} + end. + + -define(TLFIELD(Type, Label, Var), {xmlelement, "field", [{"type", Type}, {"label", translate:translate(Lang, Label)}, @@ -1107,83 +1315,60 @@ search_running_node(SNode, [Node | Nodes]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -process_sm_iq(From, To, - #iq{type = Type, xmlns = XMLNS, lang = Lang, sub_el = SubEl} = IQ) -> - case acl:match_rule(To#jid.lserver, configure, From) of +adhoc_sm_commands(_Acc, From, + #jid{user = User, server = Server, lserver = LServer} = _To, + #adhoc_request{lang = Lang, + node = "config", + action = Action, + xdata = XData} = Request) -> + case acl:match_rule(LServer, configure, From) of deny -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + {error, ?ERR_FORBIDDEN}; allow -> - #jid{user = User, server = Server} = To, - case Type of - set -> - XDataEl = find_xdata_el(SubEl), - case XDataEl of - false -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ACCEPTABLE]}; - {xmlelement, _Name, Attrs, _SubEls} -> - case xml:get_attr_s("type", Attrs) of - "cancel" -> - IQ#iq{type = result, - sub_el = [{xmlelement, "query", - [{"xmlns", XMLNS}], []}]}; - "submit" -> - XData = jlib:parse_xdata_submit(XDataEl), - case XData of - invalid -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - _ -> - Node = - string:tokens( - xml:get_tag_attr_s("node", SubEl), - "/"), - case set_sm_form( - User, Server, Node, - Lang, XData) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [{xmlelement, "query", - [{"xmlns", XMLNS}], - Res - }]}; - {error, Error} -> - IQ#iq{type = error, - sub_el = [SubEl, Error]} - end - end; - _ -> - IQ#iq{type = error, - sub_el = [SubEl, ?ERR_BAD_REQUEST]} - end - end; - get -> - Node = - string:tokens(xml:get_tag_attr_s("node", SubEl), "/"), - case get_sm_form(User, Server, Node, Lang) of - {result, Res} -> - IQ#iq{type = result, - sub_el = - [{xmlelement, "query", [{"xmlns", XMLNS}], - Res - }]}; + %% If the "action" attribute is not present, it is + %% understood as "execute". If there was no + %% element in the first response (which there isn't in our + %% case), "execute" and "complete" are equivalent. + ActionIsExecute = lists:member(Action, + ["", "execute", "complete"]), + if Action == "cancel" -> + %% User cancels request + adhoc:produce_response( + Request, + #adhoc_response{status = canceled}); + XData == false, ActionIsExecute -> + %% User requests form + case get_sm_form(User, Server, "config", Lang) of + {result, Form} -> + adhoc:produce_response( + Request, + #adhoc_response{status = executing, + elements = Form}); {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end + {error, Error} + end; + XData /= false, ActionIsExecute -> + %% User returns form. + case jlib:parse_xdata_submit(XData) of + invalid -> + {error, ?ERR_BAD_REQUEST}; + Fields -> + set_sm_form(User, Server, "config", Request, Fields) + end; + true -> + {error, ?ERR_BAD_REQUEST} end - end. + end; +adhoc_sm_commands(Acc, _From, _To, _Request) -> + Acc. -get_sm_form(User, Server, [], Lang) -> +get_sm_form(User, Server, "config", Lang) -> {result, [{xmlelement, "x", [{"xmlns", ?NS_XDATA}], [{xmlelement, "title", [], [{xmlcdata, translate:translate( Lang, "Administration of ") ++ User}]}, - %{xmlelement, "instructions", [], - % [{xmlcdata, - % translate:translate( - % Lang, "Choose host name")}]}, {xmlelement, "field", [{"type", "list-single"}, {"label", translate:translate(Lang, "Action on user")}, @@ -1198,49 +1383,36 @@ get_sm_form(User, Server, [], Lang) -> ]}, ?XFIELD("text-private", "Password", "password", ejabberd_auth:get_password_s(User, Server)) - %{xmlelement, "field", [{"type", "text-single"}, - % {"label", - % translate:translate(Lang, "Host name")}, - % {"var", "hostname"}], - % [{xmlelement, "value", [], [{xmlcdata, ?MYNAME}]}]} ]}]}; get_sm_form(_User, _Server, _Node, _Lang) -> {error, ?ERR_SERVICE_UNAVAILABLE}. -set_sm_form(User, Server, [], _Lang, XData) -> +set_sm_form(User, Server, "config", + #adhoc_request{lang = Lang, + node = Node, + sessionid = SessionID}, XData) -> + Response = #adhoc_response{lang = Lang, + node = Node, + sessionid = SessionID, + status = completed}, case lists:keysearch("action", 1, XData) of {value, {_, ["edit"]}} -> case lists:keysearch("password", 1, XData) of {value, {_, [Password]}} -> ejabberd_auth:set_password(User, Server, Password), - {result, []}; + adhoc:produce_response(Response); _ -> - {error, ?ERR_BAD_REQUEST} + {error, ?ERR_NOT_ACCEPTABLE} end; {value, {_, ["remove"]}} -> catch ejabberd_auth:remove_user(User, Server), - {result, []}; + adhoc:produce_response(Response); _ -> - {error, ?ERR_BAD_REQUEST} + {error, ?ERR_NOT_ACCEPTABLE} end; -set_sm_form(_User, _Server, _Node, _Lang, _XData) -> + +set_sm_form(_User, _Server, _Node, _Request, _Fields) -> {error, ?ERR_SERVICE_UNAVAILABLE}. -find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) -> - find_xdata_el1(SubEls). - -find_xdata_el1([]) -> - false; - -find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) -> - case xml:get_attr_s("xmlns", Attrs) of - ?NS_XDATA -> - {xmlelement, Name, Attrs, SubEls}; - _ -> - find_xdata_el1(Els) - end; - -find_xdata_el1([_ | Els]) -> - find_xdata_el1(Els). diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 14df47ca2..22f67da21 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -113,8 +113,7 @@ process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = set -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> - SNode = xml:get_tag_attr_s("node", SubEl), - Node = string:tokens(SNode, "/"), + Node = xml:get_tag_attr_s("node", SubEl), Host = To#jid.lserver, case ejabberd_hooks:run_fold(disco_local_items, @@ -123,8 +122,8 @@ process_local_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = [From, To, Node, Lang]) of {result, Items} -> ANode = case Node of - [] -> []; - _ -> [{"node", SNode}] + "" -> []; + _ -> [{"node", Node}] end, IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -144,8 +143,7 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang, IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> Host = To#jid.lserver, - SNode = xml:get_tag_attr_s("node", SubEl), - Node = string:tokens(SNode, "/"), + Node = xml:get_tag_attr_s("node", SubEl), Identity = ejabberd_hooks:run_fold(disco_local_identity, Host, [], @@ -156,8 +154,8 @@ process_local_iq_info(From, To, #iq{type = Type, lang = Lang, [From, To, Node, Lang]) of {result, Features} -> ANode = case Node of - [] -> []; - _ -> [{"node", SNode}] + "" -> []; + _ -> [{"node", Node}] end, IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -252,7 +250,7 @@ get_vh_services(Host) -> process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) -> case Type of set -> - #jid{user = User, luser = LTo, lserver = ToServer} = To, + #jid{luser = LTo, lserver = ToServer} = To, #jid{luser = LFrom, lserver = LServer} = From, Self = (LTo == LFrom) andalso (ToServer == LServer), Node = xml:get_tag_attr_s("node", SubEl), @@ -272,16 +270,15 @@ process_sm_iq_items(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ end; get -> Host = To#jid.lserver, - SNode = xml:get_tag_attr_s("node", SubEl), - Node = string:tokens(SNode, "/"), + Node = xml:get_tag_attr_s("node", SubEl), case ejabberd_hooks:run_fold(disco_sm_items, Host, empty, [From, To, Node, Lang]) of {result, Items} -> ANode = case Node of - [] -> []; - _ -> [{"node", SNode}] + "" -> []; + _ -> [{"node", Node}] end, IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -329,8 +326,7 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; get -> Host = To#jid.lserver, - SNode = xml:get_tag_attr_s("node", SubEl), - Node = string:tokens(SNode, "/"), + Node = xml:get_tag_attr_s("node", SubEl), Identity = ejabberd_hooks:run_fold(disco_sm_identity, Host, [], @@ -341,8 +337,8 @@ process_sm_iq_info(From, To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) [From, To, Node, Lang]) of {result, Features} -> ANode = case Node of - [] -> []; - _ -> [{"node", SNode}] + "" -> []; + _ -> [{"node", Node}] end, IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -384,24 +380,17 @@ get_user_resources(User, Server) -> get_publish_items(empty, #jid{luser = LFrom, lserver = LSFrom}, - #jid{user = User, server = Server, luser = LTo, lserver = LSTo} = _To, + #jid{luser = LTo, lserver = LSTo} = _To, Node, _Lang) -> if (LFrom == LTo) and (LSFrom == LSTo) -> - % Hack - SNode = join(Node, "/"), - retrieve_disco_publish({LTo, LSTo}, SNode); + retrieve_disco_publish({LTo, LSTo}, Node); true -> empty end; get_publish_items(Acc, _From, _To, _Node, _Lang) -> Acc. -join(List, Sep) -> - lists:foldl(fun(A, "") -> A; - (A, Acc) -> Acc ++ Sep ++ A - end, "", List). - process_disco_publish(User, Node, Items) -> F = fun() -> lists:foreach( diff --git a/src/mod_muc/mod_muc.erl b/src/mod_muc/mod_muc.erl index 5ebecf720..b3112472d 100644 --- a/src/mod_muc/mod_muc.erl +++ b/src/mod_muc/mod_muc.erl @@ -107,7 +107,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) -> "iq" -> case jlib:iq_query_info(Packet) of #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, - sub_el = SubEl} = IQ -> + sub_el = _SubEl} = IQ -> Res = IQ#iq{type = result, sub_el = [{xmlelement, "query", [{"xmlns", XMLNS}], @@ -123,7 +123,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) -> #iq{type = get, xmlns = ?NS_REGISTER = XMLNS, lang = Lang, - sub_el = SubEl} = IQ -> + sub_el = _SubEl} = IQ -> Res = IQ#iq{type = result, sub_el = [{xmlelement, "query", @@ -155,7 +155,7 @@ do_route1(Host, ServerHost, Access, From, To, Packet) -> #iq{type = get, xmlns = ?NS_VCARD = XMLNS, lang = Lang, - sub_el = SubEl} = IQ -> + sub_el = _SubEl} = IQ -> Res = IQ#iq{type = result, sub_el = [{xmlelement, "vCard", @@ -380,76 +380,81 @@ iq_get_register_info(Host, From, Lang) -> Lang, "Enter nickname you want to register")}]}, ?XFIELD("text-single", "Nickname", "nick", Nick)]}]. -iq_set_register_info(Host, From, XData, Lang) -> +iq_set_register_info(Host, From, Nick, Lang) -> {LUser, LServer, _} = jlib:jid_tolower(From), LUS = {LUser, LServer}, - case lists:keysearch("nick", 1, XData) of - false -> - ErrText = "You must fill in field \"nick\" in the form", - {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; - {value, {_, [Nick]}} -> - F = fun() -> - case Nick of - "" -> - mnesia:delete({muc_registered, {LUS, Host}}), + F = fun() -> + case Nick of + "" -> + mnesia:delete({muc_registered, {LUS, Host}}), + ok; + _ -> + Allow = + case mnesia:select( + muc_registered, + [{#muc_registered{us_host = '$1', + nick = Nick, + _ = '_'}, + [{'==', {element, 2, '$1'}, Host}], + ['$_']}]) of + [] -> + true; + [#muc_registered{us_host = {U, _Host}}] -> + U == LUS + end, + if + Allow -> + mnesia:write( + #muc_registered{us_host = {LUS, Host}, + nick = Nick}), ok; - _ -> - Allow = - case mnesia:select( - muc_registered, - [{#muc_registered{us_host = '$1', - nick = Nick, - _ = '_'}, - [{'==', {element, 2, '$1'}, Host}], - ['$_']}]) of - [] -> - true; - [#muc_registered{us_host = {U, _Host}}] -> - U == LUS - end, - if - Allow -> - mnesia:write( - #muc_registered{us_host = {LUS, Host}, - nick = Nick}), - ok; - true -> - false - end + true -> + false end - end, - case mnesia:transaction(F) of - {atomic, ok} -> - {result, []}; - {atomic, false} -> - ErrText = "Specified nickname is already registered", - {error, ?ERRT_CONFLICT(Lang, ErrText)}; - _ -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end + end + end, + case mnesia:transaction(F) of + {atomic, ok} -> + {result, []}; + {atomic, false} -> + ErrText = "Specified nickname is already registered", + {error, ?ERRT_CONFLICT(Lang, ErrText)}; + _ -> + {error, ?ERR_INTERNAL_SERVER_ERROR} end. process_iq_register_set(Host, From, SubEl, Lang) -> {xmlelement, _Name, _Attrs, Els} = SubEl, - case xml:remove_cdata(Els) of - [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> - case {xml:get_tag_attr_s("xmlns", XEl), - xml:get_tag_attr_s("type", XEl)} of - {?NS_XDATA, "cancel"} -> - {result, []}; - {?NS_XDATA, "submit"} -> - XData = jlib:parse_xdata_submit(XEl), - case XData of - invalid -> - {error, ?ERR_BAD_REQUEST}; + case xml:get_subtag(SubEl, "remove") of + false -> + case xml:remove_cdata(Els) of + [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> + case {xml:get_tag_attr_s("xmlns", XEl), + xml:get_tag_attr_s("type", XEl)} of + {?NS_XDATA, "cancel"} -> + {result, []}; + {?NS_XDATA, "submit"} -> + XData = jlib:parse_xdata_submit(XEl), + case XData of + invalid -> + {error, ?ERR_BAD_REQUEST}; + _ -> + case lists:keysearch("nick", 1, XData) of + false -> + ErrText = "You must fill in field \"Nickname\" in the form", + {error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}; + {value, {_, [Nick]}} -> + iq_set_register_info(Host, From, Nick, Lang) + end + end; _ -> - iq_set_register_info(Host, From, XData, Lang) + {error, ?ERR_BAD_REQUEST} end; _ -> {error, ?ERR_BAD_REQUEST} end; _ -> - {error, ?ERR_BAD_REQUEST} + iq_set_register_info(Host, From, "", Lang) end. iq_get_vcard(Lang) -> diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl index 15bacbfe2..9cbdc6874 100644 --- a/src/mod_muc/mod_muc_room.erl +++ b/src/mod_muc/mod_muc_room.erl @@ -101,7 +101,7 @@ start(Host, ServerHost, Access, Room, Opts) -> %% ignore | %% {stop, StopReason} %%---------------------------------------------------------------------- -init([Host, ServerHost, Access, Room, Creator, Nick]) -> +init([Host, ServerHost, Access, Room, Creator, _Nick]) -> State = set_affiliation(Creator, owner, #state{host = Host, server_host = ServerHost, @@ -559,16 +559,11 @@ normal_state({route, From, ToNick, end, {next_state, normal_state, StateData}; -normal_state(Event, StateData) -> +normal_state(_Event, StateData) -> {next_state, normal_state, StateData}. - - - - - %%---------------------------------------------------------------------- %% Func: handle_event/3 %% Returns: {next_state, NextStateName, NextStateData} | @@ -862,15 +857,13 @@ filter_presence({xmlelement, "presence", Attrs, Els}) -> case El of {xmlcdata, _} -> false; - {xmlelement, Name1, Attrs1, _Els1} -> + {xmlelement, _Name1, Attrs1, _Els1} -> XMLNS = xml:get_attr_s("xmlns", Attrs1), - case {Name1, XMLNS} of - {"show", ""} -> - true; - {"status", ""} -> - true; + case XMLNS of + ?NS_MUC ++ _ -> + false; _ -> - false + true end end end, Els), @@ -971,6 +964,20 @@ add_new_user(From, Nick, {xmlelement, _, Attrs, Els} = Packet, StateData) -> add_user_presence( From, Packet, add_online_user(From, Nick, Role, StateData)), + if not (NewState#state.config)#config.anonymous -> + WPacket = {xmlelement, "message", [{"type", "groupchat"}], + [{xmlelement, "body", [], + [{xmlcdata, translate:translate( + Lang, + "This room is not anonymous")}]}, + {xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], + [{xmlelement, "status", [{"code", "100"}], []}]}]}, + ejabberd_router:route( + StateData#state.jid, + From, WPacket); + true -> + ok + end, send_new_presence(From, NewState), send_existing_presences(From, NewState), Shift = count_stanza_shift(Nick, Els, NewState), @@ -1550,23 +1557,23 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> set_affiliation_and_reason( JID, outcast, Reason, set_role(JID, none, SD)); - {JID, affiliation, A, Reason} when + {JID, affiliation, A, _Reason} when (A == admin) or (A == owner) -> SD1 = set_affiliation(JID, A, SD), SD2 = set_role(JID, moderator, SD1), send_update_presence(JID, SD2), SD2; - {JID, affiliation, member, Reason} -> + {JID, affiliation, member, _Reason} -> SD1 = set_affiliation( JID, member, SD), SD2 = set_role(JID, participant, SD1), send_update_presence(JID, SD2), SD2; - {JID, role, R, Reason} -> + {JID, role, R, _Reason} -> SD1 = set_role(JID, R, SD), catch send_new_presence(JID, SD1), SD1; - {JID, affiliation, A, Reason} -> + {JID, affiliation, A, _Reason} -> SD1 = set_affiliation(JID, A, SD), send_update_presence(JID, SD1), SD1 @@ -1593,7 +1600,7 @@ process_admin_items_set(UJID, Items, Lang, StateData) -> end. -find_changed_items(UJID, UAffiliation, URole, [], _Lang, StateData, Res) -> +find_changed_items(_UJID, _UAffiliation, _URole, [], _Lang, _StateData, Res) -> {result, Res}; find_changed_items(UJID, UAffiliation, URole, [{xmlcdata, _} | Items], Lang, StateData, Res) -> @@ -1721,143 +1728,143 @@ find_changed_items(_UJID, _UAffiliation, _URole, _Items, {error, ?ERR_BAD_REQUEST}. -can_change_ra(FAffiliation, FRole, - TAffiliation, TRole, +can_change_ra(_FAffiliation, _FRole, + TAffiliation, _TRole, affiliation, Value) when (TAffiliation == Value) -> nothing; -can_change_ra(FAffiliation, FRole, - TAffiliation, TRole, +can_change_ra(_FAffiliation, _FRole, + _TAffiliation, TRole, role, Value) when (TRole == Value) -> nothing; -can_change_ra(FAffiliation, FRole, - outcast, TRole, +can_change_ra(FAffiliation, _FRole, + outcast, _TRole, affiliation, none) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, FRole, - outcast, TRole, +can_change_ra(FAffiliation, _FRole, + outcast, _TRole, affiliation, member) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(owner, FRole, - outcast, TRole, +can_change_ra(owner, _FRole, + outcast, _TRole, affiliation, admin) -> true; -can_change_ra(owner, FRole, - outcast, TRole, +can_change_ra(owner, _FRole, + outcast, _TRole, affiliation, owner) -> true; -can_change_ra(FAffiliation, FRole, - none, TRole, +can_change_ra(FAffiliation, _FRole, + none, _TRole, affiliation, outcast) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, FRole, - none, TRole, +can_change_ra(FAffiliation, _FRole, + none, _TRole, affiliation, member) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(owner, FRole, - none, TRole, +can_change_ra(owner, _FRole, + none, _TRole, affiliation, admin) -> true; -can_change_ra(owner, FRole, - none, TRole, +can_change_ra(owner, _FRole, + none, _TRole, affiliation, owner) -> true; -can_change_ra(FAffiliation, FRole, - member, TRole, +can_change_ra(FAffiliation, _FRole, + member, _TRole, affiliation, outcast) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, FRole, - member, TRole, +can_change_ra(FAffiliation, _FRole, + member, _TRole, affiliation, none) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(owner, FRole, - member, TRole, +can_change_ra(owner, _FRole, + member, _TRole, affiliation, admin) -> true; -can_change_ra(owner, FRole, - member, TRole, +can_change_ra(owner, _FRole, + member, _TRole, affiliation, owner) -> true; -can_change_ra(owner, FRole, - admin, TRole, +can_change_ra(owner, _FRole, + admin, _TRole, affiliation, _Affiliation) -> true; -can_change_ra(owner, FRole, - owner, TRole, +can_change_ra(owner, _FRole, + owner, _TRole, affiliation, _Affiliation) -> true; -can_change_ra(FAffiliation, FRole, - TAffiliation, TRole, - affiliation, Value) -> +can_change_ra(_FAffiliation, _FRole, + _TAffiliation, _TRole, + affiliation, _Value) -> false; -can_change_ra(FAffiliation, moderator, - TAffiliation, visitor, +can_change_ra(_FAffiliation, moderator, + _TAffiliation, visitor, role, none) -> true; -can_change_ra(FAffiliation, moderator, - TAffiliation, visitor, +can_change_ra(_FAffiliation, moderator, + _TAffiliation, visitor, role, participant) -> true; -can_change_ra(FAffiliation, FRole, - TAffiliation, visitor, +can_change_ra(FAffiliation, _FRole, + _TAffiliation, visitor, role, moderator) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, moderator, - TAffiliation, participant, +can_change_ra(_FAffiliation, moderator, + _TAffiliation, participant, role, none) -> true; -can_change_ra(FAffiliation, moderator, - TAffiliation, participant, +can_change_ra(_FAffiliation, moderator, + _TAffiliation, participant, role, visitor) -> true; -can_change_ra(FAffiliation, FRole, - TAffiliation, participant, +can_change_ra(FAffiliation, _FRole, + _TAffiliation, participant, role, moderator) when (FAffiliation == owner) or (FAffiliation == admin) -> true; -can_change_ra(FAffiliation, FRole, +can_change_ra(_FAffiliation, _FRole, owner, moderator, role, visitor) -> false; -can_change_ra(owner, FRole, - TAffiliation, moderator, +can_change_ra(owner, _FRole, + _TAffiliation, moderator, role, visitor) -> true; -can_change_ra(FAffiliation, FRole, +can_change_ra(_FAffiliation, _FRole, admin, moderator, role, visitor) -> false; -can_change_ra(admin, FRole, - TAffiliation, moderator, +can_change_ra(admin, _FRole, + _TAffiliation, moderator, role, visitor) -> true; -can_change_ra(FAffiliation, FRole, +can_change_ra(_FAffiliation, _FRole, owner, moderator, role, participant) -> false; -can_change_ra(owner, FRole, - TAffiliation, moderator, +can_change_ra(owner, _FRole, + _TAffiliation, moderator, role, participant) -> true; -can_change_ra(FAffiliation, FRole, +can_change_ra(_FAffiliation, _FRole, admin, moderator, role, participant) -> false; -can_change_ra(admin, FRole, - TAffiliation, moderator, +can_change_ra(admin, _FRole, + _TAffiliation, moderator, role, participant) -> true; -can_change_ra(FAffiliation, FRole, - TAffiliation, TRole, - role, Value) -> +can_change_ra(_FAffiliation, _FRole, + _TAffiliation, _TRole, + role, _Value) -> false. @@ -1887,13 +1894,13 @@ send_kickban_presence(JID, Reason, Code, StateData) -> end, LJIDs). send_kickban_presence1(UJID, Reason, Code, StateData) -> - {ok, #user{jid = RealJID, + {ok, #user{jid = _RealJID, nick = Nick}} = ?DICT:find(jlib:jid_tolower(UJID), StateData#state.users), Affiliation = get_affiliation(UJID, StateData), SAffiliation = affiliation_to_list(Affiliation), lists:foreach( - fun({LJID, Info}) -> + fun({_LJID, Info}) -> ItemAttrs = [{"affiliation", SAffiliation}, {"role", "none"}], ItemEls = case Reason of @@ -1922,9 +1929,9 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> FAffiliation = get_affiliation(From, StateData), case FAffiliation of owner -> - {xmlelement, Name, Attrs, Els} = SubEl, + {xmlelement, _Name, _Attrs, Els} = SubEl, case xml:remove_cdata(Els) of - [{xmlelement, "x", Attrs1, Els1} = XEl] -> + [{xmlelement, "x", _Attrs1, _Els1} = XEl] -> case {xml:get_tag_attr_s("xmlns", XEl), xml:get_tag_attr_s("type", XEl)} of {?NS_XDATA, "cancel"} -> @@ -1934,8 +1941,8 @@ process_iq_owner(From, set, Lang, SubEl, StateData) -> _ -> {error, ?ERR_BAD_REQUEST} end; - [{xmlelement, "destroy", Attrs1, Els1}] -> - destroy_room(Els1, StateData); + [{xmlelement, "destroy", _Attrs1, _Els1} = SubEl1] -> + destroy_room(SubEl1, StateData); Items -> process_admin_items_set(From, Items, Lang, StateData) end; @@ -1948,7 +1955,7 @@ process_iq_owner(From, get, Lang, SubEl, StateData) -> FAffiliation = get_affiliation(From, StateData), case FAffiliation of owner -> - {xmlelement, Name, Attrs, Els} = SubEl, + {xmlelement, _Name, _Attrs, Els} = SubEl, case xml:remove_cdata(Els) of [] -> get_config(Lang, StateData); @@ -2009,39 +2016,18 @@ get_config(Lang, StateData) -> [{xmlcdata, translate:translate(Lang, "Configuration for ") ++ jlib:jid_to_string(StateData#state.jid)}]}, ?STRINGXFIELD("Room title", - "title", - Config#config.title), - ?BOOLXFIELD("Allow users to change subject?", - "allow_change_subj", - Config#config.allow_change_subj), - ?BOOLXFIELD("Allow users to query other users?", - "allow_query_users", - Config#config.allow_query_users), - ?BOOLXFIELD("Allow users to send private messages?", - "allow_private_messages", - Config#config.allow_private_messages), - ?BOOLXFIELD("Make room public searchable?", - "public", - Config#config.public), - ?BOOLXFIELD("Make participants list public?", - "public_list", - Config#config.public_list), - ?BOOLXFIELD("Make room persistent?", + "title", + Config#config.title), + ?BOOLXFIELD("Make room persistent", "persistent", Config#config.persistent), - ?BOOLXFIELD("Make room moderated?", - "moderated", - Config#config.moderated), - ?BOOLXFIELD("Default users as members?", - "members_by_default", - Config#config.members_by_default), - ?BOOLXFIELD("Make room members only?", - "members_only", - Config#config.members_only), - ?BOOLXFIELD("Allow users to send invites?", - "allow_user_invites", - Config#config.allow_user_invites), - ?BOOLXFIELD("Make room password protected?", + ?BOOLXFIELD("Make room public searchable", + "public", + Config#config.public), + ?BOOLXFIELD("Make participants list public", + "public_list", + Config#config.public_list), + ?BOOLXFIELD("Make room password protected", "password_protected", Config#config.password_protected), ?PRIVATEXFIELD("Password", @@ -2050,10 +2036,31 @@ get_config(Lang, StateData) -> true -> Config#config.password; false -> "" end), - ?BOOLXFIELD("Make room anonymous?", + ?BOOLXFIELD("Make room semianonymous", "anonymous", Config#config.anonymous), - ?BOOLXFIELD("Enable logging?", + ?BOOLXFIELD("Make room members-only", + "members_only", + Config#config.members_only), + ?BOOLXFIELD("Make room moderated", + "moderated", + Config#config.moderated), + ?BOOLXFIELD("Default users as participants", + "members_by_default", + Config#config.members_by_default), + ?BOOLXFIELD("Allow users to change subject", + "allow_change_subj", + Config#config.allow_change_subj), + ?BOOLXFIELD("Allow users to send private messages", + "allow_private_messages", + Config#config.allow_private_messages), + ?BOOLXFIELD("Allow users to query other users", + "allow_query_users", + Config#config.allow_query_users), + ?BOOLXFIELD("Allow users to send invites", + "allow_user_invites", + Config#config.allow_user_invites), + ?BOOLXFIELD("Enable logging", "logging", Config#config.logging) ], @@ -2125,7 +2132,7 @@ set_xoption([{"anonymous", [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(anonymous, Val); set_xoption([{"logging", [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(logging, Val); -set_xoption([_ | Opts], Config) -> +set_xoption([_ | _Opts], _Config) -> {error, ?ERR_BAD_REQUEST}. @@ -2203,17 +2210,15 @@ make_opts(StateData) -> -destroy_room(DEls, StateData) -> +destroy_room(DEl, StateData) -> lists:foreach( - fun({LJID, Info}) -> + fun({_LJID, Info}) -> Nick = Info#user.nick, ItemAttrs = [{"affiliation", "none"}, {"role", "none"}], Packet = {xmlelement, "presence", [{"type", "unavailable"}], [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], - [{xmlelement, "item", ItemAttrs, []}, - {xmlelement, "destroy", [], - DEls}]}]}, + [{xmlelement, "item", ItemAttrs, []}, DEl]}]}, ejabberd_router:route( jlib:jid_replace_resource(StateData#state.jid, Nick), Info#user.jid, @@ -2243,10 +2248,10 @@ destroy_room(DEls, StateData) -> ?FEATURE(Fiffalse) end). -process_iq_disco_info(From, set, Lang, StateData) -> +process_iq_disco_info(_From, set, _Lang, _StateData) -> {error, ?ERR_NOT_ALLOWED}; -process_iq_disco_info(From, get, Lang, StateData) -> +process_iq_disco_info(_From, get, Lang, StateData) -> Config = StateData#state.config, {result, [{xmlelement, "identity", [{"category", "conference"}, @@ -2266,13 +2271,33 @@ process_iq_disco_info(From, get, Lang, StateData) -> "muc_moderated", "muc_unmoderated"), ?CONFIG_OPT_TO_FEATURE(Config#config.password_protected, "muc_passwordprotected", "muc_unsecured") - ], StateData}. + ] ++ iq_disco_info_extras(Lang, StateData), StateData}. +-define(RFIELDT(Type, Var, Val), + {xmlelement, "field", [{"type", Type}, {"var", Var}], + [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). -process_iq_disco_items(From, set, Lang, StateData) -> +-define(RFIELD(Label, Var, Val), + {xmlelement, "field", [{"label", translate:translate(Lang, Label)}, + {"var", Var}], + [{xmlelement, "value", [], [{xmlcdata, Val}]}]}). + +iq_disco_info_extras(Lang, StateData) -> + Len = length(?DICT:to_list(StateData#state.users)), + [{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "result"}], + [?RFIELDT("hidden", "FORM_TYPE", + "http://jabber.org/protocol/muc#roominfo"), + ?RFIELD("Description", "muc#roominfo_description", + (StateData#state.config)#config.title), + %?RFIELD("Subject", "muc#roominfo_subject", StateData#state.subject), + ?RFIELD("Number of occupants", "muc#roominfo_occupants", + integer_to_list(Len)) + ]}]. + +process_iq_disco_items(_From, set, _Lang, _StateData) -> {error, ?ERR_NOT_ALLOWED}; -process_iq_disco_items(From, get, Lang, StateData) -> +process_iq_disco_items(From, get, _Lang, StateData) -> FAffiliation = get_affiliation(From, StateData), FRole = get_role(From, StateData), case ((StateData#state.config)#config.public_list == true) orelse @@ -2282,7 +2307,7 @@ process_iq_disco_items(From, get, Lang, StateData) -> true -> UList = lists:map( - fun({LJID, Info}) -> + fun({_LJID, Info}) -> Nick = Info#user.nick, {xmlelement, "item", [{"jid", jlib:jid_to_string( @@ -2314,11 +2339,11 @@ check_invitation(From, Els, StateData) -> CanInvite = (StateData#state.config)#config.allow_user_invites orelse (FAffiliation == admin) orelse (FAffiliation == owner), case xml:remove_cdata(Els) of - [{xmlelement, "x", Attrs1, Els1} = XEl] -> + [{xmlelement, "x", _Attrs1, Els1} = XEl] -> case xml:get_tag_attr_s("xmlns", XEl) of ?NS_MUC_USER -> case xml:remove_cdata(Els1) of - [{xmlelement, "invite", Attrs2, Els2} = InviteEl] -> + [{xmlelement, "invite", Attrs2, _Els2} = InviteEl] -> case jlib:string_to_jid( xml:get_attr_s("to", Attrs2)) of error ->