mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +01:00
Synchronize fork
This commit is contained in:
commit
4fc3d511ce
@ -125,11 +125,11 @@ ifeq ($(MAKECMDGOALS),copy-files-sub)
|
||||
|
||||
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/d;s/ .*//'))
|
||||
|
||||
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
|
||||
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/COPY* deps/$(DEP)/LICENSE* deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
|
||||
DEPS_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
|
||||
DEPS_DIRS=$(sort deps/ $(foreach DEP,$(DEPS),deps/$(DEP)/) $(dir $(DEPS_FILES)))
|
||||
|
||||
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl))
|
||||
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl COPYING))
|
||||
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
|
||||
|
||||
define DEP_VERSION_template
|
||||
|
2
README
2
README
@ -108,7 +108,7 @@ To compile ejabberd you need:
|
||||
- GCC.
|
||||
- Libexpat 1.95 or higher.
|
||||
- Libyaml 0.1.4 or higher.
|
||||
- Erlang/OTP 17.1 or higher.
|
||||
- Erlang/OTP 17.5 or higher.
|
||||
- OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional.
|
||||
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
|
||||
|
33
configure.ac
33
configure.ac
@ -101,10 +101,10 @@ AC_ARG_ENABLE(mssql,
|
||||
esac],[db_type=generic])
|
||||
|
||||
AC_ARG_ENABLE(all,
|
||||
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true debug=true tools=true ;;
|
||||
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false debug=false tools=false ;;
|
||||
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true stun=true sip=true debug=true tools=true ;;
|
||||
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false stun=false sip=false debug=false tools=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
||||
esac],[])
|
||||
|
||||
@ -212,6 +212,30 @@ AC_ARG_ENABLE(latest_deps,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-latest-deps) ;;
|
||||
esac],[if test "x$latest_deps" = "x"; then latest_deps=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(system_deps,
|
||||
[AC_HELP_STRING([--enable-system-deps], [makes rebar use localy installed dependences instead of downloading them (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) system_deps=true ;;
|
||||
no) system_deps=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-system-deps) ;;
|
||||
esac],[if test "x$system_deps" = "x"; then system_deps=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(stun,
|
||||
[AC_HELP_STRING([--enable-stun], [enable STUN/TURN support (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) stun=true ;;
|
||||
no) stun=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-stun) ;;
|
||||
esac],[if test "x$stun" = "x"; then stun=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(sip,
|
||||
[AC_HELP_STRING([--enable-sip], [enable SIP support (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) sip=true ;;
|
||||
no) sip=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
|
||||
esac],[if test "x$sip" = "x"; then sip=false; fi])
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
vars.config
|
||||
src/ejabberd.app.src])
|
||||
@ -253,9 +277,12 @@ AC_SUBST(riak)
|
||||
AC_SUBST(redis)
|
||||
AC_SUBST(elixir)
|
||||
AC_SUBST(iconv)
|
||||
AC_SUBST(stun)
|
||||
AC_SUBST(sip)
|
||||
AC_SUBST(debug)
|
||||
AC_SUBST(tools)
|
||||
AC_SUBST(latest_deps)
|
||||
AC_SUBST(system_deps)
|
||||
AC_SUBST(CFLAGS)
|
||||
AC_SUBST(CPPFLAGS)
|
||||
AC_SUBST(LDFLAGS)
|
||||
|
@ -487,6 +487,8 @@ acl:
|
||||
loopback:
|
||||
ip:
|
||||
- "127.0.0.0/8"
|
||||
- "::1/128"
|
||||
- "::FFFF:127.0.0.1/128"
|
||||
|
||||
##
|
||||
## Bad XMPP servers
|
||||
@ -589,14 +591,14 @@ api_permissions:
|
||||
who:
|
||||
- access:
|
||||
- allow:
|
||||
- ip: "127.0.0.1/8"
|
||||
- acl: loopback
|
||||
- acl: admin
|
||||
- oauth:
|
||||
- scope: "ejabberd:admin"
|
||||
- access:
|
||||
- allow:
|
||||
- ip: "127.0.0.1/8"
|
||||
- acl: admin
|
||||
- acl: loopback
|
||||
- acl: admin
|
||||
what:
|
||||
- "*"
|
||||
- "!stop"
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# define default configuration
|
||||
POLL=true
|
||||
@ -10,135 +10,84 @@ FIREWALL_WINDOW=""
|
||||
ERLANG_NODE=ejabberd@localhost
|
||||
|
||||
# define default environment variables
|
||||
SCRIPT_DIR=`cd ${0%/*} && pwd`
|
||||
ERL={{erl}}
|
||||
IEX={{bindir}}/iex
|
||||
EPMD={{epmd}}
|
||||
INSTALLUSER={{installuser}}
|
||||
ERL_LIBS={{libdir}}
|
||||
|
||||
# check the proper system user is used if defined
|
||||
if [ "$INSTALLUSER" != "" ] ; then
|
||||
EXEC_CMD="false"
|
||||
for GID in `id -G`; do
|
||||
if [ $GID -eq 0 ] ; then
|
||||
INSTALLUSER_HOME=$(getent passwd "$INSTALLUSER" | cut -d: -f6)
|
||||
if [ -n "$INSTALLUSER_HOME" ] && [ ! -d "$INSTALLUSER_HOME" ] ; then
|
||||
mkdir -p "$INSTALLUSER_HOME"
|
||||
chown "$INSTALLUSER" "$INSTALLUSER_HOME"
|
||||
fi
|
||||
EXEC_CMD="su $INSTALLUSER -c"
|
||||
fi
|
||||
done
|
||||
if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then
|
||||
EXEC_CMD="bash -c"
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 4
|
||||
EXEC_CMD="false"
|
||||
if [ -n "$INSTALLUSER" ] ; then
|
||||
if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then
|
||||
EXEC_CMD="as_current_user"
|
||||
else
|
||||
id -Gn | grep -q wheel && EXEC_CMD="as_install_user"
|
||||
fi
|
||||
else
|
||||
EXEC_CMD="bash -c"
|
||||
EXEC_CMD="as_current_user"
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 7
|
||||
fi
|
||||
|
||||
# parse command line parameters
|
||||
declare -a ARGS=()
|
||||
while [ $# -ne 0 ] ; do
|
||||
PARAM="$1"
|
||||
shift
|
||||
case $PARAM in
|
||||
--) break ;;
|
||||
--no-timeout) EJABBERD_NO_TIMEOUT="--no-timeout" ;;
|
||||
--node) ERLANG_NODE_ARG=$1 ; shift ;;
|
||||
--config-dir) ETC_DIR="$1" ; shift ;;
|
||||
--config) EJABBERD_CONFIG_PATH="$1" ; shift ;;
|
||||
--ctl-config) EJABBERDCTL_CONFIG_PATH="$1" ; shift ;;
|
||||
--logs) LOGS_DIR="$1" ; shift ;;
|
||||
--spool) SPOOL_DIR="$1" ; shift ;;
|
||||
*) ARGS=("${ARGS[@]}" "$PARAM") ;;
|
||||
for arg; do
|
||||
case $1 in
|
||||
-n|--node) ERLANG_NODE_ARG=$2; shift;;
|
||||
-s|--spool) SPOOL_DIR=$2; shift;;
|
||||
-l|--logs) LOGS_DIR=$2; shift;;
|
||||
-f|--config) EJABBERD_CONFIG_PATH=$2; shift;;
|
||||
-c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift;;
|
||||
-d|--config-dir) ETC_DIR=$2; shift;;
|
||||
-t|--no-timeout) NO_TIMEOUT="--no-timeout";;
|
||||
--) :;;
|
||||
*) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Define ejabberd variable if they have not been defined from the command line
|
||||
if [ "$ETC_DIR" = "" ] ; then
|
||||
ETC_DIR={{sysconfdir}}/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then
|
||||
EJABBERDCTL_CONFIG_PATH=$ETC_DIR/ejabberdctl.cfg
|
||||
fi
|
||||
if [ -f "$EJABBERDCTL_CONFIG_PATH" ] ; then
|
||||
. "$EJABBERDCTL_CONFIG_PATH"
|
||||
fi
|
||||
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then
|
||||
EJABBERD_CONFIG_PATH=$ETC_DIR/ejabberd.yml
|
||||
fi
|
||||
if [ "$LOGS_DIR" = "" ] ; then
|
||||
LOGS_DIR={{localstatedir}}/log/ejabberd
|
||||
fi
|
||||
if [ "$SPOOL_DIR" = "" ] ; then
|
||||
SPOOL_DIR={{localstatedir}}/lib/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERD_DOC_PATH" = "" ] ; then
|
||||
EJABBERD_DOC_PATH={{docdir}}
|
||||
fi
|
||||
if [ "$ERLANG_NODE_ARG" != "" ] ; then
|
||||
ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
fi
|
||||
if [ "{{release}}" != "true" -a "$EJABBERD_BIN_PATH" = "" ] ; then
|
||||
EJABBERD_BIN_PATH={{libdir}}/ejabberd/priv/bin
|
||||
fi
|
||||
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
|
||||
DATETIME=`date "+%Y%m%d-%H%M%S"`
|
||||
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
|
||||
ERL_INETRC=$ETC_DIR/inetrc
|
||||
# define ejabberd variables if not already defined from the command line
|
||||
: ${ETC_DIR:={{sysconfdir}}/ejabberd}
|
||||
: ${LOGS_DIR:={{localstatedir}}/log/ejabberd}
|
||||
: ${SPOOL_DIR:={{localstatedir}}/lib/ejabberd}
|
||||
: ${EJABBERD_CONFIG_PATH:="$ETC_DIR"/ejabberd.yml}
|
||||
: ${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR"/ejabberdctl.cfg}
|
||||
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
|
||||
[ "$ERLANG_NODE_ARG" != "" ] && ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
|
||||
: ${EJABBERD_DOC_PATH:={{docdir}}}
|
||||
: ${EJABBERD_LOG_PATH:="$LOGS_DIR"/ejabberd.log}
|
||||
|
||||
# define mnesia options
|
||||
MNESIA_OPTS="-mnesia dir \"\\\"$SPOOL_DIR\\\"\" $MNESIA_OPTIONS"
|
||||
# define erl parameters
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||
KERNEL_OPTS=""
|
||||
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
||||
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel " \
|
||||
"inet_dist_listen_min ${FIREWALL_WINDOW%-*} " \
|
||||
"inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
fi
|
||||
if [ "$INET_DIST_INTERFACE" != "" ] ; then
|
||||
INET_DIST_INTERFACE2="$(echo $INET_DIST_INTERFACE | sed 's/\./,/g')"
|
||||
if [ "$INET_DIST_INTERFACE" != "$INET_DIST_INTERFACE2" ] ; then
|
||||
INET_DIST_INTERFACE2="{$INET_DIST_INTERFACE2}"
|
||||
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface \"$INET_DIST_INTERFACE2\""
|
||||
fi
|
||||
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${INET_DIST_INTERFACE2}\""
|
||||
fi
|
||||
if [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] ; then
|
||||
NAME="-sname"
|
||||
else
|
||||
NAME="-name"
|
||||
fi
|
||||
IEXNAME="-$NAME"
|
||||
ERL_LIBS={{libdir}}
|
||||
ERL_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump
|
||||
ERL_INETRC="$ETC_DIR"/inetrc
|
||||
|
||||
# define ejabberd environment parameters
|
||||
if [ "$EJABBERD_CONFIG_PATH" != "${EJABBERD_CONFIG_PATH%.yml}" ] ; then
|
||||
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
else
|
||||
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
fi
|
||||
[ -z "$rate" ] || EJABBERD_OPTS="log_rate_limit $rate"
|
||||
[ -z "$rotate" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_size $rotate"
|
||||
[ -z "$count" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_count $count"
|
||||
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
|
||||
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
|
||||
|
||||
[ -d "$SPOOL_DIR" ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
cd "$SPOOL_DIR"
|
||||
# define ejabberd parameters
|
||||
EJABBERD_OPTS="$EJABBERD_OPTS\
|
||||
$(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
|
||||
$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
|
||||
$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
|
||||
$(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")"
|
||||
[ -n "$EJABBERD_OPTS" ] && EJABBERD_OPTS="-ejabberd $EJABBERD_OPTS"
|
||||
EJABBERD_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS $EJABBERD_OPTS -s ejabberd"
|
||||
|
||||
# export global variables
|
||||
export EJABBERD_CONFIG_PATH
|
||||
export EJABBERD_LOG_PATH
|
||||
export EJABBERD_BIN_PATH
|
||||
export EJABBERD_DOC_PATH
|
||||
export EJABBERD_PID_PATH
|
||||
export ERL_CRASH_DUMP
|
||||
@ -150,116 +99,26 @@ export CONTRIB_MODULES_PATH
|
||||
export CONTRIB_MODULES_CONF_DIR
|
||||
export ERL_LIBS
|
||||
|
||||
shell_escape_str()
|
||||
# run command either directly or via su $INSTALLUSER
|
||||
exec_cmd()
|
||||
{
|
||||
if test $# -eq 0; then
|
||||
printf '"" '
|
||||
else
|
||||
shell_escape "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
shell_escape()
|
||||
{
|
||||
local RES=()
|
||||
for i in "$@"; do
|
||||
if test -z "$i"; then
|
||||
printf '"" '
|
||||
else
|
||||
printf '%q ' "$i"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# start server
|
||||
start()
|
||||
{
|
||||
check_start
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \
|
||||
-noinput -detached \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
}
|
||||
|
||||
# attach to server
|
||||
debug()
|
||||
{
|
||||
debugwarning
|
||||
NID=$(uid debug)
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
|
||||
-remsh $ERLANG_NODE \
|
||||
-hidden \
|
||||
$KERNEL_OPTS \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
}
|
||||
|
||||
# attach to server using Elixir
|
||||
iexdebug()
|
||||
{
|
||||
debugwarning
|
||||
# Elixir shell is hidden as default
|
||||
NID=$(uid debug)
|
||||
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"$NID\"` \
|
||||
-remsh $ERLANG_NODE \
|
||||
--erl `shell_escape \"$KERNEL_OPTS\"` \
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
live()
|
||||
{
|
||||
livewarning
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"${ERLANG_NODE}\"` \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
}
|
||||
|
||||
# start interactive server with Elixir
|
||||
iexlive()
|
||||
{
|
||||
livewarning
|
||||
echo $@
|
||||
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"${ERLANG_NODE}\"` \
|
||||
--erl \"-mnesia dir \\\"$SPOOL_DIR\\\"\" \
|
||||
--erl \"`shell_escape \"$KERNEL_OPTS\"`\" \
|
||||
--erl \"`shell_escape \"$EJABBERD_OPTS\"`\" \
|
||||
--app ejabberd \
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start server in the foreground
|
||||
foreground()
|
||||
{
|
||||
check_start
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \
|
||||
-noinput \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
case $EXEC_CMD in
|
||||
as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_current_user) "$@" ;;
|
||||
esac
|
||||
}
|
||||
exec_erl()
|
||||
{
|
||||
NODE=$1; shift
|
||||
exec_cmd $ERL ${S:--}name $NODE $ERLANG_OPTS "$@"
|
||||
}
|
||||
exec_iex()
|
||||
{
|
||||
NODE=$1; shift
|
||||
exec_cmd $IEX ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
|
||||
}
|
||||
|
||||
# usage
|
||||
debugwarning()
|
||||
{
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
@ -286,7 +145,6 @@ debugwarning()
|
||||
|
||||
livewarning()
|
||||
{
|
||||
check_start
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
@ -308,32 +166,6 @@ livewarning()
|
||||
fi
|
||||
}
|
||||
|
||||
etop()
|
||||
{
|
||||
NID=$(uid top)
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $NID \
|
||||
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
|
||||
}
|
||||
|
||||
ping()
|
||||
{
|
||||
[ -z "$1" ] && PEER=${ERLANG_NODE} || PEER=$1
|
||||
if [ "$PEER" = "${PEER%.*}" ] ; then
|
||||
PING_NAME="-sname"
|
||||
PING_NODE=$(hostname -s)
|
||||
else
|
||||
PING_NAME="-name"
|
||||
PING_NODE=$(hostname)
|
||||
fi
|
||||
NID=$(uid ping ${PING_NODE})
|
||||
$EXEC_CMD "$ERL \
|
||||
$PING_NAME $NID \
|
||||
-hidden $KERNEL_OPTS $ERLANG_OPTS \
|
||||
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$PEER'\"'\"')])' \
|
||||
-s erlang halt -output text -noinput"
|
||||
}
|
||||
|
||||
help()
|
||||
{
|
||||
echo ""
|
||||
@ -355,28 +187,11 @@ help()
|
||||
echo ""
|
||||
}
|
||||
|
||||
# common control function
|
||||
ctl()
|
||||
{
|
||||
NID=$(uid ctl)
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
|
||||
-noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
|
||||
-extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
|
||||
`shell_escape \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
result=$?
|
||||
case $result in
|
||||
2) help;;
|
||||
3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
# dynamic node name helper
|
||||
uid()
|
||||
{
|
||||
uuid=$(uuidgen 2>/dev/null)
|
||||
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(</proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
|
||||
uuid=${uuid%%-*}
|
||||
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
|
||||
@ -391,6 +206,7 @@ stop_epmd()
|
||||
}
|
||||
|
||||
# make sure node not already running and node name unregistered
|
||||
# if all ok, ensure runtime directory exists and make it current directory
|
||||
check_start()
|
||||
{
|
||||
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
@ -407,11 +223,17 @@ check_start()
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
} || {
|
||||
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
|
||||
cd "$SPOOL_DIR" || {
|
||||
echo "ERROR: ejabberd can not access directory $SPOOL_DIR"
|
||||
exit 6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# allow sync calls
|
||||
wait_for_status()
|
||||
wait_status()
|
||||
{
|
||||
# args: status try delay
|
||||
# return: 0 OK, 1 KO
|
||||
@ -420,27 +242,69 @@ wait_for_status()
|
||||
while [ $status -ne $1 ] ; do
|
||||
sleep $3
|
||||
timeout=`expr $timeout - 1`
|
||||
[ $timeout -eq 0 ] && {
|
||||
if [ $timeout -eq 0 ] ; then
|
||||
status=$1
|
||||
} || {
|
||||
ctl status > /dev/null
|
||||
else
|
||||
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
|
||||
-extra $ERLANG_NODE $NO_TIMEOUT status > /dev/null
|
||||
status=$?
|
||||
}
|
||||
fi
|
||||
done
|
||||
[ $timeout -eq 0 ] && return 1 || return 0
|
||||
[ $timeout -gt 0 ]
|
||||
}
|
||||
|
||||
# main handler
|
||||
case "${ARGS[0]}" in
|
||||
'start') start;;
|
||||
'debug') debug;;
|
||||
'iexdebug') iexdebug;;
|
||||
'live') live;;
|
||||
'iexlive') iexlive;;
|
||||
'foreground') foreground;;
|
||||
'ping'*) ping ${ARGS[1]};;
|
||||
'etop') etop;;
|
||||
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout
|
||||
*) ctl "${ARGS[@]}";;
|
||||
# main
|
||||
case $1 in
|
||||
start)
|
||||
check_start
|
||||
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput -detached
|
||||
;;
|
||||
foreground)
|
||||
check_start
|
||||
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput
|
||||
;;
|
||||
live)
|
||||
livewarning
|
||||
check_start
|
||||
exec_erl $ERLANG_NODE $EJABBERD_OPTS
|
||||
;;
|
||||
debug)
|
||||
debugwarning
|
||||
exec_erl $(uid debug) -hidden -remsh $ERLANG_NODE
|
||||
;;
|
||||
etop)
|
||||
exec_erl $(uid top) -hidden -node $ERLANG_NODE -s etop \
|
||||
-s erlang halt -output text
|
||||
;;
|
||||
iexdebug)
|
||||
debugwarning
|
||||
exec_iex $(uid debug) --remsh "$ERLANG_NODE"
|
||||
;;
|
||||
iexlive)
|
||||
livewarning
|
||||
exec_iex $ERLANG_NODE --erl "$EJABBERD_OPTS" --app ejabberd
|
||||
;;
|
||||
ping)
|
||||
PEER=${2:-$ERLANG_NODE}
|
||||
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
|
||||
exec_cmd $ERL ${PS:--}name $(uid ping $(hostname $PS)) $ERLANG_OPTS \
|
||||
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \
|
||||
-s erlang halt -output text
|
||||
;;
|
||||
started)
|
||||
wait_status 0 30 2 # wait 30x2s before timeout
|
||||
;;
|
||||
stopped)
|
||||
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
|
||||
;;
|
||||
*)
|
||||
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
|
||||
-extra $ERLANG_NODE $NO_TIMEOUT "$@"
|
||||
result=$?
|
||||
case $result in
|
||||
2|3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
exit $result
|
||||
;;
|
||||
esac
|
||||
|
@ -38,11 +38,3 @@
|
||||
-type listitem_type() :: none | jid | group | subscription.
|
||||
-type listitem_value() :: none | both | from | to | jid:ljid() | binary().
|
||||
-type listitem_action() :: allow | deny.
|
||||
|
||||
-record(userlist, {name = none :: none | binary(),
|
||||
list = [] :: [listitem()],
|
||||
needdb = false :: boolean()}).
|
||||
|
||||
-type userlist() :: #userlist{}.
|
||||
|
||||
-export_type([userlist/0]).
|
||||
|
@ -104,13 +104,6 @@
|
||||
).
|
||||
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
|
||||
|
||||
-type(subscription() :: 'none'
|
||||
| 'pending'
|
||||
| 'unconfigured'
|
||||
| 'subscribed'
|
||||
).
|
||||
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
|
||||
|
||||
-type(accessModel() :: 'open'
|
||||
| 'presence'
|
||||
| 'roster'
|
||||
|
26
mix.exs
26
mix.exs
@ -3,18 +3,18 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "17.03.0",
|
||||
description: description,
|
||||
elixir: "~> 1.3",
|
||||
version: "17.6.0",
|
||||
description: description(),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: ["lib"],
|
||||
compile_path: ".",
|
||||
compilers: [:asn1] ++ Mix.compilers,
|
||||
erlc_options: erlc_options,
|
||||
erlc_options: erlc_options(),
|
||||
erlc_paths: ["asn1", "src"],
|
||||
# Elixir tests are starting the part of ejabberd they need
|
||||
aliases: [test: "test --no-start"],
|
||||
package: package,
|
||||
deps: deps]
|
||||
package: package(),
|
||||
deps: deps()]
|
||||
end
|
||||
|
||||
def description do
|
||||
@ -29,7 +29,7 @@ defmodule Ejabberd.Mixfile do
|
||||
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
|
||||
:fast_tls, :stringprep, :fast_xml, :xmpp,
|
||||
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
|
||||
++ cond_apps]
|
||||
++ cond_apps()]
|
||||
end
|
||||
|
||||
defp erlc_options do
|
||||
@ -39,7 +39,7 @@ defmodule Ejabberd.Mixfile do
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[{:lager, "~> 3.2"},
|
||||
[{:lager, "~> 3.4.0"},
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:fast_xml, "~> 1.1"},
|
||||
{:xmpp, "~> 1.1"},
|
||||
@ -53,7 +53,7 @@ defmodule Ejabberd.Mixfile do
|
||||
{:p1_oauth2, "~> 0.6.1"},
|
||||
{:distillery, "~> 1.0"},
|
||||
{:ex_doc, ">= 0.0.0", only: :dev}]
|
||||
++ cond_deps
|
||||
++ cond_deps()
|
||||
end
|
||||
|
||||
defp deps_include(deps) do
|
||||
@ -89,7 +89,7 @@ defmodule Ejabberd.Mixfile do
|
||||
app
|
||||
end
|
||||
|
||||
def package do
|
||||
defp package do
|
||||
[# These are the default files included in the package
|
||||
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"],
|
||||
maintainers: ["ProcessOne"],
|
||||
@ -100,7 +100,7 @@ defmodule Ejabberd.Mixfile do
|
||||
"ProcessOne" => "http://www.process-one.net/"}]
|
||||
end
|
||||
|
||||
def vars do
|
||||
defp vars do
|
||||
case :file.consult("vars.config") do
|
||||
{:ok,config} -> config
|
||||
_ -> [zlib: true, iconv: true]
|
||||
@ -108,7 +108,7 @@ defmodule Ejabberd.Mixfile do
|
||||
end
|
||||
|
||||
defp config(key) do
|
||||
case vars[key] do
|
||||
case vars()[key] do
|
||||
nil -> false
|
||||
value -> value
|
||||
end
|
||||
@ -142,7 +142,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
|
||||
end)
|
||||
end
|
||||
|
||||
def manifests, do: [manifest]
|
||||
def manifests, do: [manifest()]
|
||||
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
|
||||
|
||||
def clean, do: Erlang.clean(manifest())
|
||||
|
32
mix.lock
32
mix.lock
@ -1,22 +1,18 @@
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.7", "8e2c958c0c2178e6c015aa7340c761521dc71b642192a5a7887f4aeffa29c7b1", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.2.2", "d5a52920cbe2378c8a21dfc83b526b4225944b9dce7bf170fe5f5cddda81ffb3", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
|
||||
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
|
||||
"esip": {:hex, :esip, "1.0.11", "eeb0b1cbb64d56201dd6abb09126afab1e96da2418e833cba6c55f890f2f6649", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}, {:stun, "1.0.10", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
|
||||
"esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.11", "8ccff4b68e6bb79b91c689da8cf92ec1006a575d2b6a09ac1ed5f9bf4724a39a", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.22", "7eb81a738218541208fa3a126ee36197fb0346852d8c12ad678039e539e019df", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.9", "1bf41a576d3eedcb690499350994932340908b4968832adcec4b55152d4e5f20", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []},
|
||||
"iconv": {:hex, :iconv, "1.0.4", "faa4ac6755567a2806c7bee2cf26fc318016794ffc25481f7230db5f9d025dd8", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
|
||||
"lager": {:hex, :lager, "3.2.4", "a6deb74dae7927f46bd13255268308ef03eb206ec784a94eaf7c1c0f3b811615", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.2", "893a99415f98ce8b6ad014ef950d4e878895787b6c8333587f1e506f831571e0", [:rebar3], []},
|
||||
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.2", "27d3137e0b0098808d9c60bf197344669ed1107ed47ce4af2254099a62ccc27e", [:rebar3], []},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.7", "030adbce8935f1b87aaedfdb037d3127cc671ee3e1904b394e6dde9e449d6979", [:rebar3], []},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
|
||||
"stringprep": {:hex, :stringprep, "1.0.8", "870d72db031796177261af88d1e6eb081dc314ad217377d441e5ea3c8504a310", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.10", "9fa83d4c5a76ca5ed3b536852ea00f3fbd2023241559ed6cb23a4ada62183b44", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.9", "3548dc09faa414ee437c5db53a24af691724cb984b73af832d547c83f50313b9", [:rebar3], [{:fast_xml, "1.1.22", [hex: :fast_xml, optional: false]}, {:stringprep, "1.0.8", [hex: :stringprep, optional: false]}]}}
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
|
||||
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
|
||||
|
126
plugins/override_deps_versions.erl
Normal file
126
plugins/override_deps_versions.erl
Normal file
@ -0,0 +1,126 @@
|
||||
-module(override_deps_versions).
|
||||
-export([preprocess/2, 'pre_update-deps'/2, new_replace/1, new_replace/0]).
|
||||
|
||||
preprocess(Config, _Dirs) ->
|
||||
update_deps(Config).
|
||||
|
||||
update_deps(Config) ->
|
||||
LocalDeps = rebar_config:get_local(Config, deps, []),
|
||||
TopDeps = case rebar_config:get_xconf(Config, top_deps, []) of
|
||||
[] -> LocalDeps;
|
||||
Val -> Val
|
||||
end,
|
||||
Config2 = rebar_config:set_xconf(Config, top_deps, TopDeps),
|
||||
NewDeps = lists:map(fun({Name, _, _} = Dep) ->
|
||||
case lists:keyfind(Name, 1, TopDeps) of
|
||||
false -> Dep;
|
||||
TopDep -> TopDep
|
||||
end
|
||||
end, LocalDeps),
|
||||
%io:format("LD ~p~n", [LocalDeps]),
|
||||
%io:format("TD ~p~n", [TopDeps]),
|
||||
|
||||
Config3 = rebar_config:set(Config2, deps, NewDeps),
|
||||
{ok, Config3, []}.
|
||||
|
||||
|
||||
'pre_update-deps'(Config, _Dirs) ->
|
||||
{ok, Config2, _} = update_deps(Config),
|
||||
|
||||
case code:is_loaded(old_rebar_config) of
|
||||
false ->
|
||||
{_, Beam, _} = code:get_object_code(rebar_config),
|
||||
NBeam = rename(Beam, old_rebar_config),
|
||||
code:load_binary(old_rebar_config, "blank", NBeam),
|
||||
replace_mod(Beam);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{ok, Config2}.
|
||||
|
||||
new_replace() ->
|
||||
old_rebar_config:new().
|
||||
new_replace(Config) ->
|
||||
NC = old_rebar_config:new(Config),
|
||||
{ok, Conf, _} = update_deps(NC),
|
||||
Conf.
|
||||
|
||||
replace_mod(Beam) ->
|
||||
{ok, {_, [{exports, Exports}]}} = beam_lib:chunks(Beam, [exports]),
|
||||
Funcs = lists:filtermap(
|
||||
fun({module_info, _}) ->
|
||||
false;
|
||||
({Name, Arity}) ->
|
||||
Args = args(Arity),
|
||||
Call = case Name of
|
||||
new ->
|
||||
[erl_syntax:application(
|
||||
erl_syntax:abstract(override_deps_versions),
|
||||
erl_syntax:abstract(new_replace),
|
||||
Args)];
|
||||
_ ->
|
||||
[erl_syntax:application(
|
||||
erl_syntax:abstract(old_rebar_config),
|
||||
erl_syntax:abstract(Name),
|
||||
Args)]
|
||||
end,
|
||||
{true, erl_syntax:function(erl_syntax:abstract(Name),
|
||||
[erl_syntax:clause(Args, none,
|
||||
Call)])}
|
||||
end, Exports),
|
||||
Forms0 = ([erl_syntax:attribute(erl_syntax:abstract(module),
|
||||
[erl_syntax:abstract(rebar_config)])]
|
||||
++ Funcs),
|
||||
Forms = [erl_syntax:revert(Form) || Form <- Forms0],
|
||||
%io:format("--------------------------------------------------~n"
|
||||
% "~s~n",
|
||||
% [[erl_pp:form(Form) || Form <- Forms]]),
|
||||
{ok, Mod, Bin} = compile:forms(Forms, [report, export_all]),
|
||||
code:purge(rebar_config),
|
||||
{module, Mod} = code:load_binary(rebar_config, "mock", Bin).
|
||||
|
||||
|
||||
args(0) ->
|
||||
[];
|
||||
args(N) ->
|
||||
[arg(N) | args(N-1)].
|
||||
|
||||
arg(N) ->
|
||||
erl_syntax:variable(list_to_atom("A"++integer_to_list(N))).
|
||||
|
||||
rename(BeamBin0, Name) ->
|
||||
BeamBin = replace_in_atab(BeamBin0, Name),
|
||||
update_form_size(BeamBin).
|
||||
|
||||
%% Replace the first atom of the atom table with the new name
|
||||
replace_in_atab(<<"Atom", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
|
||||
replace_first_atom(<<"Atom">>, Cnk, CnkSz0, Rest, latin1, Name);
|
||||
replace_in_atab(<<"AtU8", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
|
||||
replace_first_atom(<<"AtU8">>, Cnk, CnkSz0, Rest, unicode, Name);
|
||||
replace_in_atab(<<C, Rest/binary>>, Name) ->
|
||||
<<C, (replace_in_atab(Rest, Name))/binary>>.
|
||||
|
||||
replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) ->
|
||||
<<NumAtoms:32, NameSz0:8, _Name0:NameSz0/binary, CnkRest/binary>> = Cnk,
|
||||
NumPad0 = num_pad_bytes(CnkSz0),
|
||||
<<_:NumPad0/unit:8, NextCnks/binary>> = Rest,
|
||||
NameBin = atom_to_binary(Name, Encoding),
|
||||
NameSz = byte_size(NameBin),
|
||||
CnkSz = CnkSz0 + NameSz - NameSz0,
|
||||
NumPad = num_pad_bytes(CnkSz),
|
||||
<<CnkName/binary, CnkSz:32, NumAtoms:32, NameSz:8, NameBin:NameSz/binary,
|
||||
CnkRest/binary, 0:NumPad/unit:8, NextCnks/binary>>.
|
||||
|
||||
|
||||
%% Calculate the number of padding bytes that have to be added for the
|
||||
%% BinSize to be an even multiple of ?beam_num_bytes_alignment.
|
||||
num_pad_bytes(BinSize) ->
|
||||
case 4 - (BinSize rem 4) of
|
||||
4 -> 0;
|
||||
N -> N
|
||||
end.
|
||||
|
||||
%% Update the size within the top-level form
|
||||
update_form_size(<<"FOR1", _OldSz:32, Rest/binary>> = Bin) ->
|
||||
Sz = size(Bin) - 8,
|
||||
<<"FOR1", Sz:32, Rest/binary>>.
|
40
rebar.config
40
rebar.config
@ -18,24 +18,24 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", "470539a"}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "f31d039"}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.11"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.21"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e8dbfec277e7eb27b8130b13873b969cc346fafc"}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.9"}}},
|
||||
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.4.2"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.8"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.12"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.11"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
||||
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
|
||||
{jose, ".*", {git, "git://github.com/potatosalad/erlang-jose.git", {branch, "master"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.11"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.12"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
"31e035b"}}},
|
||||
{tag, "1.0.3"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
"5559192"}}},
|
||||
{tag, "1.1.3"}}}},
|
||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||
{tag, "1.1.5"}}}},
|
||||
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
|
||||
@ -46,12 +46,12 @@
|
||||
{tag, "2.4.1"}}}},
|
||||
%% Elixir support, needed to run tests
|
||||
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
|
||||
{tag, {if_version_above, "17", "v1.2.6", "v1.1.1"}}}}},
|
||||
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
|
||||
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
|
||||
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
|
||||
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
|
||||
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
|
||||
{tag, "1.0.4"}}}},
|
||||
{tag, "1.0.5"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.4"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
|
||||
@ -65,7 +65,6 @@
|
||||
stringprep,
|
||||
fast_xml,
|
||||
esip,
|
||||
luerl,
|
||||
stun,
|
||||
fast_yaml,
|
||||
xmpp,
|
||||
@ -85,12 +84,15 @@
|
||||
{i, "deps/p1_utils/include"},
|
||||
{if_var_false, debug, no_debug_info},
|
||||
{if_var_true, debug, debug_info},
|
||||
{if_var_true, sip, {d, 'SIP'}},
|
||||
{if_var_true, stun, {d, 'STUN'}},
|
||||
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
|
||||
{if_var_match, db_type, mssql, {d, 'mssql'}},
|
||||
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
|
||||
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
|
||||
{if_version_above, "18", {d, 'STRONG_RAND_BYTES'}},
|
||||
{if_version_above, "17", {d, 'GB_SETS_ITERATOR_FROM'}},
|
||||
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
|
||||
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
|
||||
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
|
||||
{if_var_true, hipe, native},
|
||||
{src_dirs, [asn1, src,
|
||||
{if_var_true, tools, tools},
|
||||
@ -100,7 +102,7 @@
|
||||
|
||||
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
|
||||
{if_not_rebar3, {plugins, [
|
||||
deps_erl_opts,
|
||||
deps_erl_opts, override_deps_versions,
|
||||
{if_var_true, elixir, rebar_elixir_compiler},
|
||||
{if_var_true, elixir, rebar_exunit}
|
||||
]}}.
|
||||
@ -146,7 +148,7 @@
|
||||
{post_hook_configure, [{"fast_tls", []},
|
||||
{"stringprep", []},
|
||||
{"fast_yaml", []},
|
||||
{"esip", []},
|
||||
{if_var_true, sip, {"esip", []}},
|
||||
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
|
||||
{if_var_true, pam, {"epam", []}},
|
||||
{if_var_true, zlib, {"ezlib", []}},
|
||||
|
@ -23,10 +23,12 @@ Vars = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])
|
||||
Terms;
|
||||
_Err ->
|
||||
[]
|
||||
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"}, {ldflags, ""}],
|
||||
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"},
|
||||
{ldflags, ""}, {system_deps, false}],
|
||||
{cflags, CFlags} = lists:keyfind(cflags, 1, Vars),
|
||||
{cppflags, CPPFlags} = lists:keyfind(cppflags, 1, Vars),
|
||||
{ldflags, LDFlags} = lists:keyfind(ldflags, 1, Vars),
|
||||
{system_deps, SystemDeps} = lists:keyfind(system_deps, 1, Vars),
|
||||
|
||||
GetCfg0 = fun(F, Cfg, [Key | Tail], Default) ->
|
||||
Val = case lists:keyfind(Key, 1, Cfg) of
|
||||
@ -141,6 +143,15 @@ ProcessVars = fun(_F, [], Acc) ->
|
||||
_ ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
(F, [{if_have_fun, MFA, Value} | Tail], Acc) ->
|
||||
{Mod, Fun, Arity} = MFA,
|
||||
code:ensure_loaded(Mod),
|
||||
case erlang:function_exported(Mod, Fun, Arity) of
|
||||
true ->
|
||||
F(F, Tail, ProcessSingleVar(F, Value, Acc));
|
||||
false ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
(F, [Other1 | Tail1], Acc) ->
|
||||
F(F, Tail1, [F(F, Other1, []) | Acc]);
|
||||
(F, Val, Acc) when is_tuple(Val) ->
|
||||
@ -327,11 +338,12 @@ Rules = [
|
||||
ProcessFloatingDeps, [], []},
|
||||
{[deps], IsRebar3,
|
||||
Rebar3DepsFilter, []},
|
||||
{[deps], os:getenv("USE_GLOBAL_DEPS") /= false,
|
||||
{[deps], SystemDeps /= false,
|
||||
GlobalDepsFilter, []}
|
||||
],
|
||||
|
||||
Config = FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
|
||||
Config = [{plugin_dir, filename:join([filename:dirname(SCRIPT),"plugins"])}]++
|
||||
FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
|
||||
|
||||
%io:format("ejabberd configuration:~n ~p~n", [Config]),
|
||||
|
||||
|
@ -116,12 +116,6 @@ CREATE TABLE vcard (
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_xupdate (
|
||||
username text PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
username text NOT NULL,
|
||||
lusername text PRIMARY KEY,
|
||||
|
@ -470,16 +470,6 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
|
||||
CREATE INDEX [vcard_search_lorgunit] ON [vcard_search] (lorgunit)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
|
||||
|
||||
CREATE TABLE [dbo].[vcard_xupdate] (
|
||||
[username] [varchar] (250) NOT NULL,
|
||||
[hash] [text] NOT NULL,
|
||||
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
|
||||
CONSTRAINT [vcard_xupdate_PRIMARY] PRIMARY KEY CLUSTERED
|
||||
(
|
||||
[username] ASC
|
||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||
) TEXTIMAGE_ON [PRIMARY];
|
||||
|
||||
ALTER TABLE [dbo].[pubsub_item] WITH CHECK ADD CONSTRAINT [pubsub_item_ibfk_1] FOREIGN KEY([nodeid])
|
||||
REFERENCES [dbo].[pubsub_node] ([nodeid])
|
||||
ON DELETE CASCADE;
|
||||
|
@ -121,12 +121,6 @@ CREATE TABLE vcard (
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE vcard_xupdate (
|
||||
username varchar(191) PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
username varchar(191) NOT NULL,
|
||||
lusername varchar(191) PRIMARY KEY,
|
||||
|
@ -120,12 +120,6 @@ CREATE TABLE vcard (
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_xupdate (
|
||||
username text PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
username text NOT NULL,
|
||||
lusername text PRIMARY KEY,
|
||||
|
@ -260,6 +260,8 @@ response(KeyVals, User, Passwd, Nonce, AuthzId,
|
||||
":", (hex((erlang:md5(A2))))/binary>>,
|
||||
hex((erlang:md5(T))).
|
||||
|
||||
-spec opt_type(fqdn) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(fqdn) ->
|
||||
fun(FQDN) when is_binary(FQDN) ->
|
||||
[FQDN];
|
||||
|
@ -111,7 +111,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
{error, saslprep_failed, UserName};
|
||||
true ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_tuple(Pass) -> Pass;
|
||||
if is_record(Pass, scram) ->
|
||||
{base64:decode(Pass#scram.storedkey),
|
||||
base64:decode(Pass#scram.serverkey),
|
||||
base64:decode(Pass#scram.salt),
|
||||
Pass#scram.iterationcount};
|
||||
true ->
|
||||
TempSalt =
|
||||
randoms:bytes(?SALT_LENGTH),
|
||||
@ -128,14 +132,14 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
str:substr(ClientIn,
|
||||
str:str(ClientIn, <<"n=">>)),
|
||||
ServerNonce =
|
||||
misc:encode_base64(randoms:bytes(?NONCE_LENGTH)),
|
||||
base64:encode(randoms:bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage =
|
||||
iolist_to_binary(
|
||||
["r=",
|
||||
ClientNonce,
|
||||
ServerNonce,
|
||||
",", "s=",
|
||||
misc:encode_base64(Salt),
|
||||
base64:encode(Salt),
|
||||
",", "i=",
|
||||
integer_to_list(IterationCount)]),
|
||||
{continue, ServerFirstMessage,
|
||||
@ -161,7 +165,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
ClientProofAttribute] ->
|
||||
case parse_attribute(GS2ChannelBindingAttribute) of
|
||||
{$c, CVal} ->
|
||||
ChannelBindingSupport = binary:at(misc:decode_base64(CVal), 0),
|
||||
ChannelBindingSupport = try binary:first(base64:decode(CVal))
|
||||
catch _:badarg -> 0
|
||||
end,
|
||||
if (ChannelBindingSupport == $n)
|
||||
or (ChannelBindingSupport == $y) ->
|
||||
Nonce = <<(State#state.client_nonce)/binary,
|
||||
@ -170,7 +176,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = misc:decode_base64(ClientProofB64),
|
||||
ClientProof = try base64:decode(ClientProofB64)
|
||||
catch _:badarg -> <<>>
|
||||
end,
|
||||
AuthMessage = iolist_to_binary(
|
||||
[State#state.auth_message,
|
||||
",",
|
||||
@ -191,7 +199,7 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
{auth_module, State#state.auth_module},
|
||||
{authzid, State#state.username}],
|
||||
<<"v=",
|
||||
(misc:encode_base64(ServerSignature))/binary>>};
|
||||
(base64:encode(ServerSignature))/binary>>};
|
||||
true -> {error, not_authorized, State#state.username}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
|
@ -138,7 +138,7 @@ get_commands_spec() ->
|
||||
desc = "Get the current loglevel",
|
||||
module = ejabberd_logger, function = get,
|
||||
result_desc = "Tuple with the log level number, its keyword and description",
|
||||
result_example = {4, <<"info">>, <<"Info">>},
|
||||
result_example = {4, info, <<"Info">>},
|
||||
args = [],
|
||||
result = {leveltuple, {tuple, [{levelnumber, integer},
|
||||
{levelatom, atom},
|
||||
@ -303,6 +303,7 @@ get_commands_spec() ->
|
||||
|
||||
#ejabberd_commands{name = export2sql, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL file",
|
||||
longdesc = "Configure the modules to use SQL, then call this command.",
|
||||
module = ejd2sql, function = export,
|
||||
args_desc = ["Vhost", "Full path to the destination SQL file"],
|
||||
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
|
||||
@ -478,9 +479,9 @@ update_module(ModuleNameString) ->
|
||||
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
ok ->
|
||||
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
{error, exists} ->
|
||||
Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
|
||||
{error, conflict, 10090, Msg};
|
||||
{error, Reason} ->
|
||||
@ -494,7 +495,7 @@ unregister(User, Host) ->
|
||||
{ok, ""}.
|
||||
|
||||
registered_users(Host) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
Users = ejabberd_auth:get_users(Host),
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
|
@ -160,6 +160,9 @@ start_apps() ->
|
||||
ejabberd:start_app(xmpp),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
-spec opt_type(net_ticktime) -> fun((pos_integer()) -> pos_integer());
|
||||
(cluster_nodes) -> fun(([node()]) -> [node()]);
|
||||
(atom()) -> atom().
|
||||
opt_type(net_ticktime) ->
|
||||
fun (P) when is_integer(P), P > 0 -> P end;
|
||||
opt_type(cluster_nodes) ->
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -40,15 +40,9 @@
|
||||
unregister_connection/3
|
||||
]).
|
||||
|
||||
-export([login/2, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password_s/2,
|
||||
get_password/2, get_password/3, is_user_exists/2,
|
||||
remove_user/2, remove_user/3, store_type/0,
|
||||
plain_password_required/0, opt_type/1]).
|
||||
-export([login/2, check_password/4, user_exists/2,
|
||||
get_users/2, count_users/2, store_type/1,
|
||||
plain_password_required/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -139,17 +133,9 @@ unregister_connection(_SID,
|
||||
%% ---------------------------------
|
||||
%% Specific anonymous auth functions
|
||||
%% ---------------------------------
|
||||
|
||||
%% When anonymous login is enabled, check the password for permenant users
|
||||
%% before allowing access
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
check_password(User, AuthzId, Server, Password, undefined,
|
||||
undefined).
|
||||
|
||||
check_password(User, _AuthzId, Server, _Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, _AuthzId, Server, _Password) ->
|
||||
case
|
||||
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
ejabberd_auth:user_exists_in_other_modules(?MODULE,
|
||||
User, Server)
|
||||
of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
@ -173,69 +159,25 @@ login(User, Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% When anonymous login is enabled, check that the user is permanent before
|
||||
%% changing its password
|
||||
set_password(User, Server, _Password) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
true -> ok;
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
get_users(Server, _) ->
|
||||
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
|
||||
|
||||
%% When anonymous login is enabled, check if permanent users are allowed on
|
||||
%% the server:
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
count_users(Server, Opts) ->
|
||||
length(get_users(Server, Opts)).
|
||||
|
||||
dirty_get_registered_users() -> [].
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
[{U, S}
|
||||
|| {U, S, _R}
|
||||
<- ejabberd_sm:get_vh_session_list(Server)].
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
%% Return password of permanent user or false for anonymous users
|
||||
get_password(User, Server) ->
|
||||
get_password(User, Server, <<"">>).
|
||||
|
||||
get_password(User, Server, DefaultValue) ->
|
||||
case anonymous_user_exist(User, Server) or
|
||||
login(User, Server)
|
||||
of
|
||||
%% We return the default value if the user is anonymous
|
||||
true -> DefaultValue;
|
||||
%% We return the permanent user password otherwise
|
||||
false -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
<<"">>;
|
||||
Password ->
|
||||
Password
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
user_exists(User, Server) ->
|
||||
anonymous_user_exist(User, Server).
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
plain_password_required(_) ->
|
||||
false.
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
plain_password_required() -> false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
store_type(_) ->
|
||||
external.
|
||||
|
||||
-spec opt_type(allow_multiple_connection) -> fun((boolean()) -> boolean());
|
||||
(anonymous_protocol) -> fun((sasl_anon | login_anon | both) ->
|
||||
sasl_anon | login_anon | both);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(allow_multiple_connections) ->
|
||||
fun (V) when is_boolean(V) -> V end;
|
||||
opt_type(anonymous_protocol) ->
|
||||
|
@ -32,14 +32,8 @@
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
opt_type/1]).
|
||||
try_register/3, user_exists/2, remove_user/2,
|
||||
store_type/1, plain_password_required/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -49,271 +43,60 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
Cmd = ejabberd_config:get_option({extauth_program, Host}, "extauth"),
|
||||
extauth:start(Host, Cmd),
|
||||
check_cache_last_options(Host),
|
||||
ejabberd_auth_mnesia:start(Host).
|
||||
extauth:start(Host, Cmd).
|
||||
|
||||
stop(Host) ->
|
||||
extauth:stop(Host),
|
||||
ejabberd_auth_mnesia:stop(Host).
|
||||
extauth:stop(Host).
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> no_cache;
|
||||
{true, _CacheTime} ->
|
||||
case get_mod_last_configured(Server) of
|
||||
no_mod_last ->
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache "
|
||||
"is enabled but mod_last is not enabled.",
|
||||
[Server]),
|
||||
no_cache;
|
||||
_ -> cache
|
||||
end
|
||||
end.
|
||||
plain_password_required(_) -> true.
|
||||
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() -> external.
|
||||
store_type(_) -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false ->
|
||||
check_password_extauth(User, AuthzId, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime)
|
||||
end
|
||||
check_password_extauth(User, AuthzId, Server, Password)
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, AuthzId, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true ->
|
||||
set_password_mnesia(User, Server, Password), ok;
|
||||
_ -> {error, unknown_problem}
|
||||
true -> ok;
|
||||
_ -> {error, db_failure}
|
||||
end.
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> try_register_extauth(User, Server, Password);
|
||||
{true, _CacheTime} ->
|
||||
try_register_external_cache(User, Server, Password)
|
||||
end.
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
ejabberd_auth_mnesia:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server,
|
||||
Data).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
|
||||
Data).
|
||||
|
||||
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
|
||||
get_password(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, CacheTime} ->
|
||||
get_password_cache(User, Server, CacheTime)
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> <<"">>;
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
try extauth:is_user_exists(User, Server) of
|
||||
Res -> Res
|
||||
user_exists(User, Server) ->
|
||||
try extauth:user_exists(User, Server) of
|
||||
Res -> Res
|
||||
catch
|
||||
_:Error -> {error, Error}
|
||||
_:Error ->
|
||||
?ERROR_MSG("external authentication program failure: ~p",
|
||||
[Error]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
case extauth:remove_user(User, Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_mnesia:remove_user(User, Server)
|
||||
end
|
||||
false -> {error, not_allowed};
|
||||
true -> ok
|
||||
end.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
case extauth:remove_user(User, Server, Password) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_mnesia:remove_user(User, Server,
|
||||
Password)
|
||||
end
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Extauth cache management
|
||||
%%%
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_option({extauth_cache, Host}, false) of
|
||||
false -> false;
|
||||
CacheTime -> {true, CacheTime}
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false
|
||||
check_password_extauth(User, _AuthzId, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso
|
||||
Password /= <<"">>.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
try_register_extauth(User, Server, Password) ->
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
check_password_cache(User, AuthzId, Server, Password, 0) ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_mnesia(User, AuthzId, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled "
|
||||
"but mod_last is not enabled in that "
|
||||
"host",
|
||||
[]),
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_mnesia(User, AuthzId, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true -> true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password)
|
||||
end;
|
||||
%% Else (need to refresh), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_mnesia(User, Server) ->
|
||||
ejabberd_auth_mnesia:get_password(User, Server).
|
||||
|
||||
-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online -> get_password_mnesia(User, Server);
|
||||
never -> false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled "
|
||||
"but mod_last is not enabled in that "
|
||||
"host",
|
||||
[]),
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true -> get_password_mnesia(User, Server);
|
||||
false -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% Check the password using extauth; if success then cache it
|
||||
check_password_external_cache(User, AuthzId, Server, Password) ->
|
||||
case check_password_extauth(User, AuthzId, Server, Password) of
|
||||
true ->
|
||||
set_password_mnesia(User, Server, Password), true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
%% Try to register using extauth; if success then cache it
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_mnesia(User, Server, Password), R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false
|
||||
check_password_mnesia(User, AuthzId, Server, Password) ->
|
||||
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
|
||||
Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_mnesia(User, Server, Password) ->
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
ejabberd_auth_mnesia:set_password(User, Server,
|
||||
Password).
|
||||
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
Now = p1_time_compat:system_time(seconds),
|
||||
TimeStampLast + CacheTime > Now.
|
||||
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
|
||||
get_last_access(User, Server) ->
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
case get_last_info(User, Server) of
|
||||
mod_last_required -> mod_last_required;
|
||||
not_found -> never;
|
||||
{ok, Timestamp, _Status} -> Timestamp
|
||||
end;
|
||||
_ -> online
|
||||
end.
|
||||
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
|
||||
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
no_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | no_mod_last
|
||||
get_mod_last_enabled(Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
case is_configured(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
Os = ejabberd_config:get_option({modules, Host}, []),
|
||||
lists:keymember(Module, 1, Os).
|
||||
|
||||
-spec opt_type(extauth_cache) -> fun((false | non_neg_integer()) ->
|
||||
false | non_neg_integer());
|
||||
(extauth_program) -> fun((binary()) -> string());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(extauth_cache) ->
|
||||
?WARNING_MSG("option 'extauth_cache' is deprecated and has no effect, "
|
||||
"use authentication or global cache configuration "
|
||||
"options: auth_use_cache, auth_cache_life_time, "
|
||||
"use_cache, cache_life_time, and so on", []),
|
||||
fun (false) -> false;
|
||||
(I) when is_integer(I), I >= 0 -> I
|
||||
end;
|
||||
|
@ -37,13 +37,9 @@
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
-export([start/1, stop/1, start_link/1, set_password/3,
|
||||
check_password/4, check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
check_password/4, user_exists/2,
|
||||
get_users/2, count_users/2,
|
||||
store_type/1, plain_password_required/1,
|
||||
opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -112,9 +108,9 @@ init(Host) ->
|
||||
State#state.password, State#state.tls_options),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() -> true.
|
||||
plain_password_required(_) -> true.
|
||||
|
||||
store_type() -> external.
|
||||
store_type(_) -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
@ -129,60 +125,34 @@ check_password(User, AuthzId, Server, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, AuthzId, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false -> {error, user_not_found};
|
||||
false -> {error, notfound};
|
||||
DN ->
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN,
|
||||
Password)
|
||||
case eldap_pool:modify_passwd(State#state.eldap_id, DN,
|
||||
Password) of
|
||||
ok -> ok;
|
||||
_Err -> {error, db_failure}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case catch get_vh_registered_users_ldap(Server) of
|
||||
get_users(Server, []) ->
|
||||
case catch get_users_ldap(Server) of
|
||||
{'EXIT', _} -> [];
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(_User, _Server) -> false.
|
||||
|
||||
get_password_s(_User, _Server) -> <<"">>.
|
||||
count_users(Server, Opts) ->
|
||||
length(get_users(Server, Opts)).
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
case catch is_user_exists_ldap(User, Server) of
|
||||
{'EXIT', Error} -> {error, Error};
|
||||
user_exists(User, Server) ->
|
||||
case catch user_exists_ldap(User, Server) of
|
||||
{'EXIT', _Error} -> {error, db_failure};
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -199,7 +169,7 @@ check_password_ldap(User, Server, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
get_vh_registered_users_ldap(Server) ->
|
||||
get_users_ldap(Server) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
UIDs = State#state.uids,
|
||||
Eldap_ID = State#state.eldap_id,
|
||||
@ -248,7 +218,7 @@ get_vh_registered_users_ldap(Server) ->
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
is_user_exists_ldap(User, Server) ->
|
||||
user_exists_ldap(User, Server) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false -> false;
|
||||
@ -393,6 +363,10 @@ parse_options(Host) ->
|
||||
sfilter = SearchFilter, lfilter = LocalFilter,
|
||||
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
|
||||
|
||||
-spec opt_type(ldap_dn_filter) -> fun(([{binary(), binary()}]) ->
|
||||
[{binary(), binary()}]);
|
||||
(ldap_local_filter) -> fun((any()) -> any());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ldap_dn_filter) ->
|
||||
fun ([{DNF, DNFA}]) ->
|
||||
NewDNFA = case DNFA of
|
||||
|
@ -31,15 +31,11 @@
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2, init_db/0,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1, import/2,
|
||||
plain_password_required/0]).
|
||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
||||
get_users/2, init_db/0,
|
||||
count_users/2, get_password/2,
|
||||
remove_user/2, store_type/1, export/1, import/2,
|
||||
plain_password_required/1, use_cache/1]).
|
||||
-export([need_transform/1, transform/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -52,8 +48,6 @@
|
||||
-record(reg_users_counter, {vhost = <<"">> :: binary(),
|
||||
count = 0 :: integer() | '$1'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
@ -67,14 +61,14 @@ stop(_Host) ->
|
||||
|
||||
init_db() ->
|
||||
ejabberd_mnesia:create(?MODULE, passwd,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
ejabberd_mnesia:create(?MODULE, reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]).
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Set = get_users(Server, []),
|
||||
Size = length(Set),
|
||||
LServer = jid:nameprep(Server),
|
||||
F = fun () ->
|
||||
@ -83,309 +77,157 @@ update_reg_users_counter_table(Server) ->
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
plain_password_required() ->
|
||||
is_scrammed().
|
||||
|
||||
store_type() ->
|
||||
ejabberd_auth:password_format(?MYNAME).
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end
|
||||
use_cache(Host) ->
|
||||
case mnesia:table_info(passwd, storage_type) of
|
||||
disc_only_copies ->
|
||||
ejabberd_config:get_option(
|
||||
{auth_use_cache, Host},
|
||||
ejabberd_config:use_cache(Host));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = misc:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
plain_password_required(Server) ->
|
||||
store_type(Server) == scram.
|
||||
|
||||
store_type(Server) ->
|
||||
ejabberd_auth:password_format(Server).
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
F = fun () ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US, password = Password2})
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason}
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Password = if is_list(PasswordList); is_binary(PasswordList) ->
|
||||
iolist_to_binary(PasswordList);
|
||||
true -> PasswordList
|
||||
end,
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
(LPassword == error) and not is_record(Password, scram) ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
F = fun () ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[] ->
|
||||
Password2 = case is_scrammed() and
|
||||
is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password2}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, 1),
|
||||
ok;
|
||||
[_E] -> exists
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
%% Get all registered users in Mnesia
|
||||
dirty_get_registered_users() ->
|
||||
mnesia:dirty_all_keys(passwd).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
|
||||
|
||||
get_vh_registered_users(Server,
|
||||
[{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, End - Start + 1}, {offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_vh_registered_users(Server) of
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and
|
||||
is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and
|
||||
is_integer(Offset) ->
|
||||
case [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)]
|
||||
of
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Query = mnesia:dirty_select(reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer,
|
||||
count = '$1'},
|
||||
[], ['$1']}]),
|
||||
case Query of
|
||||
[Count] -> Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server,
|
||||
[{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
{misc:decode_base64(Scram#scram.storedkey),
|
||||
misc:decode_base64(Scram#scram.serverkey),
|
||||
misc:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[] -> false;
|
||||
[_] -> true;
|
||||
Other -> {error, Other}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it returns ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
US = {User, Server},
|
||||
F = fun () ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1)
|
||||
mnesia:write(#passwd{us = US, password = Password})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
try_register(User, Server, Password) ->
|
||||
US = {User, Server},
|
||||
F = fun () ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1),
|
||||
ok;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
case is_password_scram_valid(Password, Scram) of
|
||||
true ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
_ -> not_exists
|
||||
[] ->
|
||||
mnesia:write(#passwd{us = US, password = Password}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, Server, 1),
|
||||
ok;
|
||||
[_] ->
|
||||
{error, exists}
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, Res} -> Res;
|
||||
_ -> bad_request
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
get_users(Server, []) ->
|
||||
mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, Server}], ['$1']}]);
|
||||
get_users(Server, [{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
|
||||
get_users(Server, [{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_users(Server, []) of
|
||||
[] ->
|
||||
[];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and is_integer(End) ->
|
||||
get_users(Server, [{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start}]);
|
||||
get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) ->
|
||||
case [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)] of
|
||||
[] ->
|
||||
[];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_users(Server, _) ->
|
||||
get_users(Server, []).
|
||||
|
||||
count_users(Server, []) ->
|
||||
case mnesia:dirty_select(
|
||||
reg_users_counter,
|
||||
[{#reg_users_counter{vhost = Server, count = '$1'},
|
||||
[], ['$1']}]) of
|
||||
[Count] -> Count;
|
||||
_ -> 0
|
||||
end;
|
||||
count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
count_users(Server, _) ->
|
||||
count_users(Server, []).
|
||||
|
||||
get_password(User, Server) ->
|
||||
case mnesia:dirty_read(passwd, {User, Server}) of
|
||||
[#passwd{password = Password}] ->
|
||||
{ok, Password};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
US = {User, Server},
|
||||
F = fun () ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, Server, -1),
|
||||
ok
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
need_transform(#passwd{us = {U, S}, password = Pass}) ->
|
||||
if is_binary(Pass) ->
|
||||
IsScrammed = is_scrammed(),
|
||||
if IsScrammed ->
|
||||
case store_type(S) of
|
||||
scram ->
|
||||
?INFO_MSG("Passwords in Mnesia table 'passwd' "
|
||||
"will be SCRAM'ed", []);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
IsScrammed;
|
||||
"will be SCRAM'ed", []),
|
||||
true;
|
||||
plain ->
|
||||
false
|
||||
end;
|
||||
is_record(Pass, scram) ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
next;
|
||||
false ->
|
||||
case store_type(S) of
|
||||
scram ->
|
||||
false;
|
||||
plain ->
|
||||
?WARNING_MSG("Some passwords were stored in the database "
|
||||
"as SCRAM, but 'auth_password_format' "
|
||||
"is not configured as 'scram'.", []),
|
||||
"is not configured as 'scram': some "
|
||||
"authentication mechanisms such as DIGEST-MD5 "
|
||||
"would *fail*", []),
|
||||
false
|
||||
end;
|
||||
is_list(U) orelse is_list(S) orelse is_list(Pass) ->
|
||||
@ -410,61 +252,24 @@ transform(#passwd{us = {U, S}, password = Pass} = R)
|
||||
transform(R#passwd{us = NewUS, password = NewPass});
|
||||
transform(#passwd{us = {U, S}, password = Password} = P)
|
||||
when is_binary(Password) ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
case store_type(S) of
|
||||
scram ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
?ERROR_MSG("SASLprep failed for password of user ~s@~s",
|
||||
[U, S]),
|
||||
P;
|
||||
_ ->
|
||||
Scram = password_to_scram(Password),
|
||||
Scram = ejabberd_auth:password_to_scram(Password),
|
||||
P#passwd{password = Scram}
|
||||
end;
|
||||
false ->
|
||||
plain ->
|
||||
P
|
||||
end;
|
||||
transform(#passwd{password = Password} = P)
|
||||
when is_record(Password, scram) ->
|
||||
P.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
is_scrammed() ->
|
||||
scram == store_type().
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = misc:encode_base64(StoredKey),
|
||||
serverkey = misc:encode_base64(ServerKey),
|
||||
salt = misc:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
false;
|
||||
_ ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = misc:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
misc:decode_base64(Scram#scram.storedkey) == StoredKey
|
||||
end.
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
|
@ -30,14 +30,8 @@
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
-export([start/1, stop/1, check_password/4,
|
||||
user_exists/2, store_type/1, plain_password_required/1,
|
||||
opt_type/1]).
|
||||
|
||||
start(_Host) ->
|
||||
@ -46,13 +40,6 @@ start(_Host) ->
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, AuthzId, Server, Password).
|
||||
|
||||
check_password(User, AuthzId, Host, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
@ -70,26 +57,7 @@ check_password(User, AuthzId, Host, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() -> [].
|
||||
|
||||
get_vh_registered_users(_Host) -> [].
|
||||
|
||||
get_vh_registered_users(_Host, _) -> [].
|
||||
|
||||
get_vh_registered_users_number(_Host) -> 0.
|
||||
|
||||
get_vh_registered_users_number(_Host, _) -> 0.
|
||||
|
||||
get_password(_User, _Server) -> false.
|
||||
|
||||
get_password_s(_User, _Server) -> <<"">>.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
|
||||
is_user_exists(User, Host) ->
|
||||
user_exists(User, Host) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
@ -97,16 +65,13 @@ is_user_exists(User, Host) ->
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
false -> false;
|
||||
_Err -> {error, db_failure}
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
plain_password_required(_) -> true.
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() -> external.
|
||||
store_type(_) -> external.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
@ -117,6 +82,9 @@ get_pam_service(Host) ->
|
||||
get_pam_userinfotype(Host) ->
|
||||
ejabberd_config:get_option({pam_userinfotype, Host}, username).
|
||||
|
||||
-spec opt_type(pam_service) -> fun((binary()) -> binary());
|
||||
(pam_userinfotype) -> fun((username | jid) -> username | jid);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(pam_service) -> fun iolist_to_binary/1;
|
||||
opt_type(pam_userinfotype) ->
|
||||
fun (username) -> username;
|
||||
|
@ -32,15 +32,10 @@
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1, import/2,
|
||||
plain_password_required/0]).
|
||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
||||
get_users/2, count_users/2,
|
||||
get_password/2, remove_user/2, store_type/1, export/1, import/2,
|
||||
plain_password_required/1]).
|
||||
-export([passwd_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@ -49,258 +44,65 @@
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
case is_scrammed() of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
plain_password_required(Server) ->
|
||||
store_type(Server) == scram.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
store_type(Server) ->
|
||||
ejabberd_auth:password_format(Server).
|
||||
|
||||
passwd_schema() ->
|
||||
{record_info(fields, passwd), #passwd{}}.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}} when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
{ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
Passwd = misc:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
ok = ejabberd_riak:put(#passwd{us = US, password = Password2},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, LServer}]}])
|
||||
ejabberd_riak:put(#passwd{us = {User, Server}, password = Password},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, Server}]}]).
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
US = {User, Server},
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), US) of
|
||||
{error, notfound} ->
|
||||
ejabberd_riak:put(#passwd{us = US, password = Password},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, Server}]}]);
|
||||
{ok, _} ->
|
||||
{error, exists};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Password = if is_list(PasswordList); is_binary(PasswordList) ->
|
||||
iolist_to_binary(PasswordList);
|
||||
true -> PasswordList
|
||||
end,
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error and not is_record(Password, scram) ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), US) of
|
||||
{error, notfound} ->
|
||||
Password2 = case is_scrammed() and
|
||||
is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
{atomic, ejabberd_riak:put(
|
||||
#passwd{us = US,
|
||||
password = Password2},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, LServer}]}])};
|
||||
{ok, _} ->
|
||||
exists;
|
||||
Err ->
|
||||
{atomic, Err}
|
||||
end
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, ejabberd_config:get_vh_by_auth_method(riak)).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
|
||||
get_users(Server, _) ->
|
||||
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, Server) of
|
||||
{ok, Users} ->
|
||||
Users;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
|
||||
count_users(Server, _) ->
|
||||
case ejabberd_riak:count_by_index(passwd, <<"host">>, Server) of
|
||||
{ok, N} ->
|
||||
N;
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
{misc:decode_base64(Scram#scram.storedkey),
|
||||
misc:decode_base64(Scram#scram.serverkey),
|
||||
misc:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{error, notfound} -> false;
|
||||
{ok, _} -> true;
|
||||
Err -> Err
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {User, Server}) of
|
||||
{ok, Password} ->
|
||||
{ok, Password};
|
||||
{error, _} ->
|
||||
error
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
case is_password_scram_valid(Password, Scram) of
|
||||
true ->
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
_ -> not_exists
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
is_scrammed() ->
|
||||
scram == ejabberd_auth:password_format(?MYNAME).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = misc:encode_base64(StoredKey),
|
||||
serverkey = misc:encode_base64(ServerKey),
|
||||
salt = misc:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
false;
|
||||
_ ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = misc:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
misc:decode_base64(Scram#scram.storedkey) == StoredKey
|
||||
end.
|
||||
ejabberd_riak:delete(passwd, {User, Server}).
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
|
@ -30,16 +30,12 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
convert_to_scram/1]).
|
||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
||||
get_users/2, count_users/2, get_password/2,
|
||||
remove_user/2, store_type/1, plain_password_required/1,
|
||||
convert_to_scram/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -54,397 +50,84 @@ start(_Host) -> ok.
|
||||
|
||||
stop(_Host) -> ok.
|
||||
|
||||
plain_password_required() ->
|
||||
case is_scrammed() of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
plain_password_required(Server) ->
|
||||
store_type(Server) == scram.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
store_type(Server) ->
|
||||
ejabberd_auth:password_format(Server).
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
try sql_queries:get_password_scram(LServer, LUser) of
|
||||
{selected,
|
||||
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
Scram =
|
||||
#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = IterationCount},
|
||||
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end;
|
||||
false ->
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{Password}]} ->
|
||||
Password /= <<"">>;
|
||||
{selected, [{_Password2}]} ->
|
||||
false; %% Password is not correct
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
false ->
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
%% Account exists, check if password is valid
|
||||
{selected, [{Passwd}]} ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end;
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
Scram = password_to_scram(Password),
|
||||
case catch sql_queries:set_password_scram_t(
|
||||
LServer,
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
Scram#scram.serverkey,
|
||||
Scram#scram.salt,
|
||||
Scram#scram.iterationcount
|
||||
)
|
||||
of
|
||||
{atomic, ok} -> ok;
|
||||
Other -> {error, Other}
|
||||
end;
|
||||
false ->
|
||||
case catch sql_queries:set_password_t(LServer,
|
||||
LUser, Password)
|
||||
of
|
||||
{atomic, ok} -> ok;
|
||||
Other -> {error, Other}
|
||||
end
|
||||
end
|
||||
F = fun() ->
|
||||
if is_record(Password, scram) ->
|
||||
set_password_scram_t(
|
||||
User,
|
||||
Password#scram.storedkey, Password#scram.serverkey,
|
||||
Password#scram.salt, Password#scram.iterationcount);
|
||||
true ->
|
||||
set_password_t(User, Password)
|
||||
end
|
||||
end,
|
||||
case ejabberd_sql:sql_transaction(Server, F) of
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("failed to write to SQL table: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
|
||||
try_register(User, Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error and not is_record(Password, scram) ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
Scram = case is_record(Password, scram) of
|
||||
true -> Password;
|
||||
false -> password_to_scram(Password)
|
||||
end,
|
||||
case catch sql_queries:add_user_scram(
|
||||
LServer,
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
Scram#scram.serverkey,
|
||||
Scram#scram.salt,
|
||||
Scram#scram.iterationcount
|
||||
) of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
end;
|
||||
false ->
|
||||
case catch sql_queries:add_user(LServer, LUser,
|
||||
Password) of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
end
|
||||
end
|
||||
Res = if is_record(Password, scram) ->
|
||||
add_user_scram(
|
||||
Server, User,
|
||||
Password#scram.storedkey, Password#scram.serverkey,
|
||||
Password#scram.salt, Password#scram.iterationcount);
|
||||
true ->
|
||||
add_user(Server, User, Password)
|
||||
end,
|
||||
case Res of
|
||||
{updated, 1} -> ok;
|
||||
_ -> {error, exists}
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(sql),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> [];
|
||||
<<>> -> [];
|
||||
LServer ->
|
||||
case catch sql_queries:list_users(LServer) of
|
||||
{selected, Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ -> []
|
||||
end
|
||||
get_users(Server, Opts) ->
|
||||
case list_users(Server, Opts) of
|
||||
{selected, Res} ->
|
||||
[{U, Server} || {U} <- Res];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> [];
|
||||
<<>> -> [];
|
||||
LServer ->
|
||||
case catch sql_queries:list_users(LServer, Opts) of
|
||||
{selected, Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> 0;
|
||||
<<>> -> 0;
|
||||
LServer ->
|
||||
case catch sql_queries:users_number(LServer) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_ -> 0
|
||||
end
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> 0;
|
||||
<<>> -> 0;
|
||||
LServer ->
|
||||
case catch sql_queries:users_number(LServer, Opts) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_Other -> 0
|
||||
end
|
||||
count_users(Server, Opts) ->
|
||||
case users_number(Server, Opts) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_Other -> 0
|
||||
end.
|
||||
|
||||
get_password(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
case catch sql_queries:get_password_scram(
|
||||
LServer, LUser) of
|
||||
{selected,
|
||||
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
{misc:decode_base64(StoredKey),
|
||||
misc:decode_base64(ServerKey),
|
||||
misc:decode_base64(Salt),
|
||||
IterationCount};
|
||||
_ -> false
|
||||
end;
|
||||
false ->
|
||||
case catch sql_queries:get_password(LServer, LUser)
|
||||
of
|
||||
{selected, [{Password}]} -> Password;
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
case get_password_scram(Server, User) of
|
||||
{selected, [{Password, <<>>, <<>>, 0}]} ->
|
||||
{ok, Password};
|
||||
{selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
{ok, #scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = IterationCount}};
|
||||
{selected, []} ->
|
||||
error;
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to read password for user ~s@~s: ~p",
|
||||
[User, Server, Err]),
|
||||
error
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
<<"">>;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
<<"">>;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
false ->
|
||||
case catch sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{Password}]} -> Password;
|
||||
_ -> <<"">>
|
||||
end;
|
||||
true -> <<"">>
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{_Password}]} ->
|
||||
true; %% Account exists
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, Error} -> {error, Error}
|
||||
catch
|
||||
_:B -> {error, B}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok | error
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
error;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
error;
|
||||
true ->
|
||||
catch sql_queries:del_user(LServer, LUser),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
error;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
error;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
case check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
remove_user(User, Server),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
false ->
|
||||
F = fun () ->
|
||||
Result = sql_queries:del_user_return_password(
|
||||
LServer, LUser, Password),
|
||||
case Result of
|
||||
{selected, [{Password}]} -> ok;
|
||||
{selected, []} -> not_exists;
|
||||
_ -> not_allowed
|
||||
end
|
||||
end,
|
||||
{atomic, Result} = sql_queries:sql_transaction(
|
||||
LServer, F),
|
||||
Result
|
||||
end
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
is_scrammed() ->
|
||||
scram == ejabberd_auth:password_format(?MYNAME).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = misc:encode_base64(StoredKey),
|
||||
serverkey = misc:encode_base64(ServerKey),
|
||||
salt = misc:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
|
||||
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
|
||||
"enabled, but the password of user '~s' in the 'users' table is not "
|
||||
"scrammed. You may want to execute this command: "
|
||||
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
|
||||
false;
|
||||
is_password_scram_valid_stored(Password, Scram, _, _) ->
|
||||
is_password_scram_valid(Password, Scram).
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
false;
|
||||
_ ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = misc:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
misc:decode_base64(Scram#scram.storedkey) == StoredKey
|
||||
case del_user(Server, User) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to delete user ~s@~s: ~p",
|
||||
[User, Server, Err]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
-define(BATCH_SIZE, 1000).
|
||||
@ -459,6 +142,105 @@ set_password_scram_t(LUser,
|
||||
"salt=%(Salt)s",
|
||||
"iterationcount=%(IterationCount)d"]).
|
||||
|
||||
set_password_t(LUser, Password) ->
|
||||
?SQL_UPSERT_T(
|
||||
"users",
|
||||
["!username=%(LUser)s",
|
||||
"password=%(Password)s"]).
|
||||
|
||||
get_password_scram(LServer, LUser) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
|
||||
" from users"
|
||||
" where username=%(LUser)s")).
|
||||
|
||||
add_user_scram(LServer, LUser,
|
||||
StoredKey, ServerKey, Salt, IterationCount) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("insert into users(username, password, serverkey, salt, "
|
||||
"iterationcount) "
|
||||
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
|
||||
" %(Salt)s, %(IterationCount)d)")).
|
||||
|
||||
add_user(LServer, LUser, Password) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("insert into users(username, password) "
|
||||
"values (%(LUser)s, %(Password)s)")).
|
||||
|
||||
del_user(LServer, LUser) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from users where username=%(LUser)s")).
|
||||
|
||||
list_users(LServer, []) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users"));
|
||||
list_users(LServer, [{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
list_users(LServer,
|
||||
[{limit, End - Start + 1}, {offset, Start - 1}]);
|
||||
list_users(LServer,
|
||||
[{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and
|
||||
is_integer(End) ->
|
||||
list_users(LServer,
|
||||
[{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start - 1}]);
|
||||
list_users(LServer, [{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users "
|
||||
"order by username "
|
||||
"limit %(Limit)d offset %(Offset)d"));
|
||||
list_users(LServer,
|
||||
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and
|
||||
is_integer(Offset) ->
|
||||
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
|
||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users "
|
||||
"where username like %(SPrefix2)s escape '^' "
|
||||
"order by username "
|
||||
"limit %(Limit)d offset %(Offset)d")).
|
||||
|
||||
users_number(LServer) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
fun(pgsql, _) ->
|
||||
case
|
||||
ejabberd_config:get_option(
|
||||
{pgsql_users_number_estimate, LServer}, false) of
|
||||
true ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(reltuples :: bigint)d from pg_class"
|
||||
" where oid = 'users'::regclass::oid"));
|
||||
_ ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(count(*))d from users"))
|
||||
end;
|
||||
(_Type, _) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(count(*))d from users"))
|
||||
end).
|
||||
|
||||
users_number(LServer, [{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
|
||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(count(*))d from users "
|
||||
"where username like %(SPrefix2)s escape '^'"));
|
||||
users_number(LServer, []) ->
|
||||
users_number(LServer).
|
||||
|
||||
convert_to_scram(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
if
|
||||
@ -485,7 +267,7 @@ convert_to_scram(Server) ->
|
||||
"password of user ~s@~s",
|
||||
[LUser, LServer]);
|
||||
_ ->
|
||||
Scram = password_to_scram(Password),
|
||||
Scram = ejabberd_auth:password_to_scram(Password),
|
||||
set_password_scram_t(
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
@ -498,10 +280,16 @@ convert_to_scram(Server) ->
|
||||
Err -> {bad_reply, Err}
|
||||
end
|
||||
end,
|
||||
case sql_queries:sql_transaction(LServer, F) of
|
||||
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, continue} -> convert_to_scram(Server);
|
||||
{atomic, Error} -> {error, Error};
|
||||
Error -> Error
|
||||
end
|
||||
end.
|
||||
|
||||
-spec opt_type(pgsql_users_number_estimate) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(pgsql_users_number_estimate) ->
|
||||
fun (V) when is_boolean(V) -> V end;
|
||||
opt_type(_) -> [pgsql_users_number_estimate].
|
||||
|
@ -46,7 +46,7 @@
|
||||
reject_unauthenticated_packet/2, process_closed/2,
|
||||
process_terminated/2, process_info/2]).
|
||||
%% API
|
||||
-export([get_presence/1, get_subscription/2, get_subscribed/1,
|
||||
-export([get_presence/1, resend_presence/1, resend_presence/2,
|
||||
open_session/1, call/3, send/2, close/1, close/2, stop/1,
|
||||
reply/2, copy_state/2, set_timeout/2, route/2,
|
||||
host_up/1, host_down/1]).
|
||||
@ -54,6 +54,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
|
||||
@ -93,23 +94,13 @@ reply(Ref, Reply) ->
|
||||
get_presence(Ref) ->
|
||||
call(Ref, get_presence, 1000).
|
||||
|
||||
-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none.
|
||||
get_subscription(#jid{} = From, State) ->
|
||||
get_subscription(jid:tolower(From), State);
|
||||
get_subscription(LFrom, #{pres_f := PresF, pres_t := PresT}) ->
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
F = ?SETS:is_element(LFrom, PresF) orelse ?SETS:is_element(LBFrom, PresF),
|
||||
T = ?SETS:is_element(LFrom, PresT) orelse ?SETS:is_element(LBFrom, PresT),
|
||||
if F and T -> both;
|
||||
F -> from;
|
||||
T -> to;
|
||||
true -> none
|
||||
end.
|
||||
-spec resend_presence(pid()) -> ok.
|
||||
resend_presence(Pid) ->
|
||||
resend_presence(Pid, undefined).
|
||||
|
||||
-spec get_subscribed(pid()) -> [ljid()].
|
||||
%% Return list of all available resources of contacts
|
||||
get_subscribed(Ref) ->
|
||||
call(Ref, get_subscribed, 1000).
|
||||
-spec resend_presence(pid(), jid() | undefined) -> ok.
|
||||
resend_presence(Pid, To) ->
|
||||
route(Pid, {resend_presence, To}).
|
||||
|
||||
-spec close(pid()) -> ok;
|
||||
(state()) -> state().
|
||||
@ -183,8 +174,7 @@ host_down(Host) ->
|
||||
copy_state(#{owner := Owner} = NewState,
|
||||
#{jid := JID, resource := Resource, sid := {Time, _},
|
||||
auth_module := AuthModule, lserver := LServer,
|
||||
pres_t := PresT, pres_a := PresA,
|
||||
pres_f := PresF} = OldState) ->
|
||||
pres_a := PresA} = OldState) ->
|
||||
State1 = case OldState of
|
||||
#{pres_last := Pres, pres_timestamp := PresTS} ->
|
||||
NewState#{pres_last => Pres, pres_timestamp => PresTS};
|
||||
@ -196,8 +186,7 @@ copy_state(#{owner := Owner} = NewState,
|
||||
conn => Conn,
|
||||
sid => {Time, Owner},
|
||||
auth_module => AuthModule,
|
||||
pres_t => PresT, pres_a => PresA,
|
||||
pres_f => PresF},
|
||||
pres_a => PresA},
|
||||
ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]).
|
||||
|
||||
-spec open_session(state()) -> {ok, state()} | state().
|
||||
@ -238,10 +227,17 @@ process_info(#{lserver := LServer} = State, {route, Packet}) ->
|
||||
true ->
|
||||
State1
|
||||
end;
|
||||
process_info(State, force_update_presence) ->
|
||||
process_info(#{jid := JID} = State, {resend_presence, To}) ->
|
||||
case maps:get(pres_last, State, error) of
|
||||
error -> State;
|
||||
Pres -> process_self_presence(State, Pres)
|
||||
Pres when To == undefined ->
|
||||
process_self_presence(State, Pres);
|
||||
Pres when To#jid.luser == JID#jid.luser andalso
|
||||
To#jid.lserver == JID#jid.lserver andalso
|
||||
To#jid.lresource == <<"">> ->
|
||||
process_self_presence(State, Pres);
|
||||
Pres ->
|
||||
process_presence_out(State, xmpp:set_to(Pres, To))
|
||||
end;
|
||||
process_info(State, Info) ->
|
||||
?WARNING_MSG("got unexpected info: ~p", [Info]),
|
||||
@ -390,15 +386,11 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
|
||||
allow ->
|
||||
State1 = open_session(State#{resource => Resource,
|
||||
sid => ejabberd_sm:make_sid()}),
|
||||
LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
PresF = ?SETS:add_element(LBJID, maps:get(pres_f, State1)),
|
||||
PresT = ?SETS:add_element(LBJID, maps:get(pres_t, State1)),
|
||||
State2 = State1#{pres_f => PresF, pres_t => PresT},
|
||||
State3 = ejabberd_hooks:run_fold(
|
||||
c2s_session_opened, LServer, State2, []),
|
||||
State2 = ejabberd_hooks:run_fold(
|
||||
c2s_session_opened, LServer, State1, []),
|
||||
?INFO_MSG("(~s) Opened c2s session for ~s",
|
||||
[SockMod:pp(Socket), jid:encode(JID)]),
|
||||
{ok, State3};
|
||||
{ok, State2};
|
||||
deny ->
|
||||
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
|
||||
?INFO_MSG("(~s) Forbidden c2s session for ~s",
|
||||
@ -490,8 +482,8 @@ handle_send(Pkt, Result, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_send, LServer, State, [Pkt, Result]).
|
||||
|
||||
init([State, Opts]) ->
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
Shaper = gen_mod:get_opt(shaper, Opts, none),
|
||||
Access = proplists:get_value(access, Opts, all),
|
||||
Shaper = proplists:get_value(shaper, Opts, none),
|
||||
TLSOpts1 = lists:filter(
|
||||
fun({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
@ -513,8 +505,6 @@ init([State, Opts]) ->
|
||||
tls_enabled => TLSEnabled,
|
||||
tls_verify => TLSVerify,
|
||||
pres_a => ?SETS:new(),
|
||||
pres_f => ?SETS:new(),
|
||||
pres_t => ?SETS:new(),
|
||||
zlib => Zlib,
|
||||
lang => ?MYLANG,
|
||||
server => ?MYNAME,
|
||||
@ -532,9 +522,6 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
|
||||
end,
|
||||
reply(From, Pres),
|
||||
State;
|
||||
handle_call(get_subscribed, From, #{pres_f := PresF} = State) ->
|
||||
reply(From, ?SETS:to_list(PresF)),
|
||||
State;
|
||||
handle_call(Request, From, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(
|
||||
c2s_handle_call, LServer, State, [Request, From]).
|
||||
@ -589,36 +576,36 @@ process_message_in(State, #message{type = T} = Msg) ->
|
||||
|
||||
-spec process_presence_in(state(), presence()) -> {boolean(), state()}.
|
||||
process_presence_in(#{lserver := LServer, pres_a := PresA} = State0,
|
||||
#presence{from = From, to = To, type = T} = Pres) ->
|
||||
#presence{from = From, type = T} = Pres) ->
|
||||
State = ejabberd_hooks:run_fold(c2s_presence_in, LServer, State0, [Pres]),
|
||||
case T of
|
||||
probe ->
|
||||
NewState = add_to_pres_a(State, From),
|
||||
route_probe_reply(From, To, NewState),
|
||||
{false, NewState};
|
||||
route_probe_reply(From, State),
|
||||
{false, State};
|
||||
error ->
|
||||
A = ?SETS:del_element(jid:tolower(From), PresA),
|
||||
{true, State#{pres_a => A}};
|
||||
_ ->
|
||||
case privacy_check_packet(State, Pres, in) of
|
||||
allow ->
|
||||
NewState = add_to_pres_a(State, From),
|
||||
{true, NewState};
|
||||
{true, State};
|
||||
deny ->
|
||||
{false, State}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec route_probe_reply(jid(), jid(), state()) -> ok.
|
||||
route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
|
||||
pres_last := LastPres,
|
||||
pres_timestamp := TS} = State) ->
|
||||
LFrom = jid:tolower(From),
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
case ?SETS:is_element(LFrom, PresF)
|
||||
orelse ?SETS:is_element(LBFrom, PresF) of
|
||||
true ->
|
||||
%% To is my JID
|
||||
-spec route_probe_reply(jid(), state()) -> ok.
|
||||
route_probe_reply(From, #{jid := To,
|
||||
pres_last := LastPres,
|
||||
pres_timestamp := TS} = State) ->
|
||||
{LUser, LServer, LResource} = jid:tolower(To),
|
||||
IsAnotherResource = case jid:tolower(From) of
|
||||
{LUser, LServer, R} when R /= LResource -> true;
|
||||
_ -> false
|
||||
end,
|
||||
Subscription = get_subscription(To, From),
|
||||
if IsAnotherResource orelse
|
||||
Subscription == both orelse Subscription == from ->
|
||||
Packet = xmpp_util:add_delay_info(LastPres, To, TS),
|
||||
case privacy_check_packet(State, Packet, out) of
|
||||
deny ->
|
||||
@ -627,19 +614,12 @@ route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
|
||||
ejabberd_hooks:run(presence_probe_hook,
|
||||
LServer,
|
||||
[From, To, self()]),
|
||||
%% Don't route a presence probe to oneself
|
||||
case From == To of
|
||||
false ->
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(Packet, To, From));
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
ejabberd_router:route(xmpp:set_from_to(Packet, To, From))
|
||||
end;
|
||||
false ->
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
route_probe_reply(_, _, _) ->
|
||||
route_probe_reply(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec process_presence_out(state(), presence()) -> state().
|
||||
@ -675,11 +655,22 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
|
||||
State;
|
||||
allow ->
|
||||
ejabberd_router:route(Pres),
|
||||
A = case Type of
|
||||
available -> ?SETS:add_element(LTo, PresA);
|
||||
unavailable -> ?SETS:del_element(LTo, PresA)
|
||||
end,
|
||||
State#{pres_a => A}
|
||||
LBareTo = jid:remove_resource(LTo),
|
||||
LBareFrom = jid:remove_resource(jid:tolower(From)),
|
||||
if LBareTo /= LBareFrom ->
|
||||
Subscription = get_subscription(From, To),
|
||||
if Subscription /= both andalso Subscription /= from ->
|
||||
A = case Type of
|
||||
available -> ?SETS:add_element(LTo, PresA);
|
||||
unavailable -> ?SETS:del_element(LTo, PresA)
|
||||
end,
|
||||
State#{pres_a => A};
|
||||
true ->
|
||||
State
|
||||
end;
|
||||
true ->
|
||||
State
|
||||
end
|
||||
end.
|
||||
|
||||
-spec process_self_presence(state(), presence()) -> state().
|
||||
@ -716,24 +707,81 @@ update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
|
||||
ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
|
||||
|
||||
-spec broadcast_presence_unavailable(state(), presence()) -> state().
|
||||
broadcast_presence_unavailable(#{pres_a := PresA} = State, Pres) ->
|
||||
JIDs = filter_blocked(State, Pres, PresA),
|
||||
broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
BareJID = jid:remove_resource(JID),
|
||||
Items1 = ejabberd_hooks:run_fold(roster_get, LServer,
|
||||
[], [{LUser, LServer}]),
|
||||
Items2 = ?SETS:fold(
|
||||
fun(LJID, Items) ->
|
||||
[#roster{jid = LJID, subscription = from}|Items]
|
||||
end, Items1, PresA),
|
||||
JIDs = lists:foldl(
|
||||
fun(#roster{jid = LJID, subscription = Sub}, Tos)
|
||||
when Sub == both orelse Sub == from ->
|
||||
To = jid:make(LJID),
|
||||
P = xmpp:set_to(Pres, jid:make(LJID)),
|
||||
case privacy_check_packet(State, P, out) of
|
||||
allow -> [To|Tos];
|
||||
deny -> Tos
|
||||
end;
|
||||
(_, Tos) ->
|
||||
Tos
|
||||
end, [BareJID], Items2),
|
||||
route_multiple(State, JIDs, Pres),
|
||||
State#{pres_a => ?SETS:new()}.
|
||||
|
||||
-spec broadcast_presence_available(state(), presence(), boolean()) -> state().
|
||||
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF,
|
||||
pres_t := PresT, jid := JID} = State,
|
||||
broadcast_presence_available(#{jid := JID} = State,
|
||||
Pres, _FromUnavailable = true) ->
|
||||
Probe = #presence{from = JID, type = probe},
|
||||
TJIDs = filter_blocked(State, Probe, PresT),
|
||||
FJIDs = filter_blocked(State, Pres, PresF),
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
BareJID = jid:remove_resource(JID),
|
||||
Items = ejabberd_hooks:run_fold(roster_get, LServer,
|
||||
[], [{LUser, LServer}]),
|
||||
{FJIDs, TJIDs} =
|
||||
lists:foldl(
|
||||
fun(#roster{jid = LJID, subscription = Sub}, {F, T}) ->
|
||||
To = jid:make(LJID),
|
||||
F1 = if Sub == both orelse Sub == from ->
|
||||
Pres1 = xmpp:set_to(Pres, To),
|
||||
case privacy_check_packet(State, Pres1, out) of
|
||||
allow -> [To|F];
|
||||
deny -> F
|
||||
end;
|
||||
true -> F
|
||||
end,
|
||||
T1 = if Sub == both orelse Sub == to ->
|
||||
Probe1 = xmpp:set_to(Probe, To),
|
||||
case privacy_check_packet(State, Probe1, out) of
|
||||
allow -> [To|T];
|
||||
deny -> T
|
||||
end;
|
||||
true -> T
|
||||
end,
|
||||
{F1, T1}
|
||||
end, {[BareJID], [BareJID]}, Items),
|
||||
route_multiple(State, TJIDs, Probe),
|
||||
route_multiple(State, FJIDs, Pres),
|
||||
State#{pres_a => ?SETS:union(PresA, PresF)};
|
||||
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF} = State,
|
||||
State;
|
||||
broadcast_presence_available(#{jid := JID} = State,
|
||||
Pres, _FromUnavailable = false) ->
|
||||
JIDs = filter_blocked(State, Pres, ?SETS:intersection(PresA, PresF)),
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
BareJID = jid:remove_resource(JID),
|
||||
Items = ejabberd_hooks:run_fold(
|
||||
roster_get, LServer, [], [{LUser, LServer}]),
|
||||
JIDs = lists:foldl(
|
||||
fun(#roster{jid = LJID, subscription = Sub}, Tos)
|
||||
when Sub == both orelse Sub == from ->
|
||||
To = jid:make(LJID),
|
||||
P = xmpp:set_to(Pres, jid:make(LJID)),
|
||||
case privacy_check_packet(State, P, out) of
|
||||
allow -> [To|Tos];
|
||||
deny -> Tos
|
||||
end;
|
||||
(_, Tos) ->
|
||||
Tos
|
||||
end, [BareJID], Items),
|
||||
route_multiple(State, JIDs, Pres),
|
||||
State.
|
||||
|
||||
@ -761,23 +809,17 @@ get_priority_from_presence(#presence{priority = Prio}) ->
|
||||
_ -> Prio
|
||||
end.
|
||||
|
||||
-spec filter_blocked(state(), presence(), ?SETS:set()) -> [jid()].
|
||||
filter_blocked(#{jid := From} = State, Pres, LJIDSet) ->
|
||||
?SETS:fold(
|
||||
fun(LJID, Acc) ->
|
||||
To = jid:make(LJID),
|
||||
Pkt = xmpp:set_from_to(Pres, From, To),
|
||||
case privacy_check_packet(State, Pkt, out) of
|
||||
allow -> [To|Acc];
|
||||
deny -> Acc
|
||||
end
|
||||
end, [], LJIDSet).
|
||||
|
||||
-spec route_multiple(state(), [jid()], stanza()) -> ok.
|
||||
route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
|
||||
From = xmpp:get_from(Pkt),
|
||||
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
|
||||
|
||||
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
|
||||
{Subscription, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LServer, {none, []},
|
||||
[LUser, LServer, JID]),
|
||||
Subscription.
|
||||
|
||||
-spec resource_conflict_action(binary(), binary(), binary()) ->
|
||||
{accept_resource, binary()} | closenew.
|
||||
resource_conflict_action(U, S, R) ->
|
||||
@ -855,30 +897,6 @@ change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
|
||||
LServer),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
|
||||
-spec add_to_pres_a(state(), jid()) -> state().
|
||||
add_to_pres_a(#{pres_a := PresA, pres_f := PresF} = State, From) ->
|
||||
LFrom = jid:tolower(From),
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
case (?SETS):is_element(LFrom, PresA) orelse
|
||||
(?SETS):is_element(LBFrom, PresA) of
|
||||
true ->
|
||||
State;
|
||||
false ->
|
||||
case (?SETS):is_element(LFrom, PresF) of
|
||||
true ->
|
||||
A = (?SETS):add_element(LFrom, PresA),
|
||||
State#{pres_a => A};
|
||||
false ->
|
||||
case (?SETS):is_element(LBFrom, PresF) of
|
||||
true ->
|
||||
A = (?SETS):add_element(LBFrom, PresA),
|
||||
State#{pres_a => A};
|
||||
false ->
|
||||
State
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
-spec format_reason(state(), term()) -> binary().
|
||||
format_reason(#{stop_reason := Reason}, _) ->
|
||||
xmpp_stream_in:format_error(Reason);
|
||||
@ -894,10 +912,20 @@ format_reason(_, _) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
opt_type(c2s_certfile) -> fun iolist_to_binary/1;
|
||||
-type resource_conflict() :: setresource | closeold | closenew | acceptnew.
|
||||
-spec opt_type(c2s_certfile) -> fun((binary()) -> binary());
|
||||
(c2s_ciphers) -> fun((binary()) -> binary());
|
||||
(c2s_dhfile) -> fun((binary()) -> binary());
|
||||
(c2s_cafile) -> fun((binary()) -> binary());
|
||||
(c2s_protocol_options) -> fun(([binary()]) -> binary());
|
||||
(c2s_tls_compression) -> fun((boolean()) -> boolean());
|
||||
(resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
|
||||
(disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(c2s_certfile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_dhfile) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_protocol_options) ->
|
||||
fun (Options) -> str:join(Options, <<"|">>) end;
|
||||
opt_type(c2s_tls_compression) ->
|
||||
@ -920,9 +948,30 @@ opt_type(_) ->
|
||||
c2s_protocol_options, c2s_tls_compression, resource_conflict,
|
||||
disable_sasl_mechanisms].
|
||||
|
||||
-spec listen_opt_type(access) -> fun((any()) -> any());
|
||||
(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(starttls) -> fun((boolean()) -> boolean());
|
||||
(tls_verify) -> fun((boolean()) -> boolean());
|
||||
(zlib) -> fun((boolean()) -> boolean());
|
||||
(supervisor) -> fun((boolean()) -> boolean());
|
||||
(max_stanza_size) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((timeout()) -> timeout());
|
||||
(stream_management) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) -> opt_type(c2s_certfile);
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
|
||||
listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
|
||||
listen_opt_type(cafile) -> opt_type(c2s_cafile);
|
||||
@ -935,7 +984,7 @@ listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I) -> I;
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
|
@ -528,6 +528,10 @@ clean_treap(Treap, CleanPriority) ->
|
||||
now_priority() ->
|
||||
-p1_time_compat:system_time(micro_seconds).
|
||||
|
||||
-spec opt_type(captcha_cmd) -> fun((binary()) -> binary());
|
||||
(captcha_host) -> fun((binary()) -> binary());
|
||||
(captcha_limit) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(captcha_cmd) ->
|
||||
fun (FileName) ->
|
||||
F = iolist_to_binary(FileName), if F /= <<"">> -> F end
|
||||
|
@ -891,6 +891,9 @@ permission_addon() ->
|
||||
[{access, ejabberd_config:get_option(commands_admin_access, none)}],
|
||||
{get_exposed_commands(), []}}}].
|
||||
|
||||
-spec opt_type(commands_admin_access) -> fun((any()) -> any());
|
||||
(commands) -> fun((list()) -> list());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
|
||||
opt_type(commands) ->
|
||||
fun(V) when is_list(V) -> V end;
|
||||
|
@ -179,7 +179,10 @@ read_file(File, Opts) ->
|
||||
load_file(File) ->
|
||||
State0 = read_file(File),
|
||||
State1 = hosts_to_start(State0),
|
||||
validate_opts(State1).
|
||||
AllMods = get_modules(),
|
||||
init_module_db_table(AllMods),
|
||||
ModOpts = get_modules_with_options(AllMods),
|
||||
validate_opts(State1, ModOpts).
|
||||
|
||||
-spec reload_file() -> ok.
|
||||
|
||||
@ -971,11 +974,12 @@ default_db(Opt, Host, Module) ->
|
||||
end
|
||||
end.
|
||||
|
||||
get_modules_with_options() ->
|
||||
get_modules() ->
|
||||
{ok, Mods} = application:get_key(ejabberd, modules),
|
||||
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
|
||||
AllMods = [?MODULE|ExtMods++Mods],
|
||||
init_module_db_table(AllMods),
|
||||
ExtMods ++ Mods.
|
||||
|
||||
get_modules_with_options(Modules) ->
|
||||
lists:foldl(
|
||||
fun(Mod, D) ->
|
||||
case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of
|
||||
@ -992,10 +996,9 @@ get_modules_with_options() ->
|
||||
false ->
|
||||
D
|
||||
end
|
||||
end, dict:new(), AllMods).
|
||||
end, dict:new(), Modules).
|
||||
|
||||
validate_opts(#state{opts = Opts} = State) ->
|
||||
ModOpts = get_modules_with_options(),
|
||||
validate_opts(#state{opts = Opts} = State, ModOpts) ->
|
||||
NewOpts = lists:filtermap(
|
||||
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
|
||||
case dict:find(Opt, ModOpts) of
|
||||
@ -1089,7 +1092,7 @@ replace_module(mod_roster_odbc) -> {mod_roster, sql};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
|
||||
replace_module(mod_vcard_ldap) -> {mod_vcard, ldap};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> mod_vcard_xupdate;
|
||||
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
|
||||
replace_module(mod_http_bind) -> mod_bosh;
|
||||
replace_module(Module) ->
|
||||
@ -1362,6 +1365,23 @@ emit_deprecation_warning(Module, NewModule) ->
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
-spec opt_type(hide_sensitive_log_data) -> fun((boolean()) -> boolean());
|
||||
(hosts) -> fun(([binary()]) -> [binary()]);
|
||||
(language) -> fun((binary()) -> binary());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(default_db) -> fun((atom()) -> atom());
|
||||
(default_ram_db) -> fun((atom()) -> atom());
|
||||
(loglevel) -> fun((0..5) -> 0..5);
|
||||
(queue_dir) -> fun((binary()) -> binary());
|
||||
(queue_type) -> fun((ram | file) -> ram | file);
|
||||
(use_cache) -> fun((boolean()) -> boolean());
|
||||
(cache_size) -> fun((timeout()) -> timeout());
|
||||
(cache_missed) -> fun((boolean()) -> boolean());
|
||||
(cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(domain_certfile) -> fun((binary()) -> binary());
|
||||
(shared_key) -> fun((binary()) -> binary());
|
||||
(node_start) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(hide_sensitive_log_data) ->
|
||||
fun (H) when is_boolean(H) -> H end;
|
||||
opt_type(hosts) ->
|
||||
@ -1397,7 +1417,7 @@ opt_type(cache_life_time) ->
|
||||
(unlimited) -> infinity
|
||||
end;
|
||||
opt_type(domain_certfile) ->
|
||||
fun iolist_to_binary/1;
|
||||
fun misc:try_read_file/1;
|
||||
opt_type(shared_key) ->
|
||||
fun iolist_to_binary/1;
|
||||
opt_type(node_start) ->
|
||||
|
@ -875,6 +875,8 @@ print(Format, Args) ->
|
||||
%% ["aaaa bbb ccc"].
|
||||
|
||||
|
||||
-spec opt_type(ejabberdctl_access_commands) -> fun((list()) -> list());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ejabberdctl_access_commands) ->
|
||||
fun (V) when is_list(V) -> V end;
|
||||
opt_type(_) -> [ejabberdctl_access_commands].
|
||||
|
@ -136,15 +136,15 @@ init({SockMod, Socket}, Opts) ->
|
||||
true -> [{[], ejabberd_xmlrpc}];
|
||||
false -> []
|
||||
end,
|
||||
DefinedHandlers = gen_mod:get_opt(request_handlers, Opts, []),
|
||||
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||
Admin ++ Bind ++ XMLRPC,
|
||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||
|
||||
DefaultHost = gen_mod:get_opt(default_host, Opts, undefined),
|
||||
DefaultHost = proplists:get_value(default_host, Opts),
|
||||
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
|
||||
|
||||
CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
|
||||
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
|
||||
|
||||
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
|
||||
State = #state{sockmod = SockMod1,
|
||||
@ -770,7 +770,9 @@ code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
|
||||
|
||||
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
|
||||
parse_auth(<<"Basic ", Auth64/binary>>) ->
|
||||
Auth = misc:decode_base64(Auth64),
|
||||
Auth = try base64:decode(Auth64)
|
||||
catch _:badarg -> <<>>
|
||||
end,
|
||||
%% Auth should be a string with the format: user@server:password
|
||||
%% Note that password can contain additional characters '@' and ':'
|
||||
case str:chr(Auth, $:) of
|
||||
@ -899,19 +901,41 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(trusted_proxies) ->
|
||||
fun (all) -> all;
|
||||
(TPs) -> [iolist_to_binary(TP) || TP <- TPs] end;
|
||||
opt_type(_) -> [trusted_proxies].
|
||||
|
||||
-spec listen_opt_type(tls) -> fun((boolean()) -> boolean());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(captcha) -> fun((boolean()) -> boolean());
|
||||
(register) -> fun((boolean()) -> boolean());
|
||||
(web_admin) -> fun((boolean()) -> boolean());
|
||||
(http_bind) -> fun((boolean()) -> boolean());
|
||||
(xmlrpc) -> fun((boolean()) -> boolean());
|
||||
(request_handlers) -> fun(([{binary(), atom()}]) ->
|
||||
[{binary(), atom()}]);
|
||||
(default_host) -> fun((binary()) -> binary());
|
||||
(custom_headers) -> fun(([{binary(), binary()}]) ->
|
||||
[{binary(), binary()}]);
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(certfile) ->
|
||||
fun iolist_to_binary/1;
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) ->
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) ->
|
||||
fun iolist_to_binary/1;
|
||||
fun misc:try_read_file/1;
|
||||
listen_opt_type(protocol_options) ->
|
||||
fun(Options) -> str:join(Options, <<"|">>) end;
|
||||
listen_opt_type(tls_compression) ->
|
||||
|
@ -90,14 +90,7 @@ start(Port, Module, Opts) ->
|
||||
|
||||
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
|
||||
start_dependent(Port, Module, Opts) ->
|
||||
try check_listener_options(Opts) of
|
||||
ok ->
|
||||
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
|
||||
catch
|
||||
throw:{error, Error} ->
|
||||
?ERROR_MSG(Error, []),
|
||||
{error, Error}
|
||||
end.
|
||||
proc_lib:start_link(?MODULE, init, [Port, Module, Opts]).
|
||||
|
||||
init(PortIP, Module, RawOpts) ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
@ -456,48 +449,6 @@ config_reloaded() ->
|
||||
%%%
|
||||
%%% Check options
|
||||
%%%
|
||||
|
||||
check_listener_options(Opts) ->
|
||||
case includes_deprecated_ssl_option(Opts) of
|
||||
false -> ok;
|
||||
true ->
|
||||
Error = "There is a problem with your ejabberd configuration file: "
|
||||
"the option 'ssl' for listening sockets is no longer available."
|
||||
" To get SSL encryption use the option 'tls'.",
|
||||
throw({error, Error})
|
||||
end,
|
||||
case certfile_readable(Opts) of
|
||||
true -> ok;
|
||||
{false, Path} ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ Path})
|
||||
end,
|
||||
ok.
|
||||
|
||||
%% Parse the options of the socket,
|
||||
%% and return if the deprecated option 'ssl' is included
|
||||
%% @spec (Opts) -> true | false
|
||||
includes_deprecated_ssl_option(Opts) ->
|
||||
case lists:keysearch(ssl, 1, Opts) of
|
||||
{value, {ssl, _SSLOpts}} ->
|
||||
true;
|
||||
_ ->
|
||||
lists:member(ssl, Opts)
|
||||
end.
|
||||
|
||||
%% @spec (Opts) -> true | {false, Path::string()}
|
||||
certfile_readable(Opts) ->
|
||||
case proplists:lookup(certfile, Opts) of
|
||||
none -> true;
|
||||
{certfile, Path} ->
|
||||
PathS = binary_to_list(Path),
|
||||
case ejabberd_config:is_file_readable(PathS) of
|
||||
true -> true;
|
||||
false -> {false, PathS}
|
||||
end
|
||||
end.
|
||||
|
||||
get_proto(Opts) ->
|
||||
case proplists:get_value(proto, Opts) of
|
||||
undefined ->
|
||||
|
File diff suppressed because one or more lines are too long
@ -25,21 +25,33 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_mnesia).
|
||||
-behaviour(ejabberd_oauth).
|
||||
|
||||
-export([init/0,
|
||||
store/1,
|
||||
lookup/1,
|
||||
clean/1]).
|
||||
clean/1,
|
||||
use_cache/0]).
|
||||
|
||||
-include("ejabberd_oauth.hrl").
|
||||
|
||||
init() ->
|
||||
ejabberd_mnesia:create(?MODULE, oauth_token,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, oauth_token)}]),
|
||||
ok.
|
||||
|
||||
use_cache() ->
|
||||
case mnesia:table_info(oauth_token, storage_type) of
|
||||
disc_only_copies ->
|
||||
ejabberd_config:get_option(
|
||||
oauth_use_cache,
|
||||
ejabberd_config:use_cache(global));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
store(R) ->
|
||||
mnesia:dirty_write(R).
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_rest).
|
||||
|
||||
-behaviour(ejabberd_oauth).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-export([init/0,
|
||||
@ -93,6 +93,8 @@ path(Path) ->
|
||||
<<Base/binary, "/", Path/binary>>.
|
||||
|
||||
|
||||
-spec opt_type(ext_api_path_oauth) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ext_api_path_oauth) ->
|
||||
fun (X) -> iolist_to_binary(X) end;
|
||||
opt_type(_) -> [ext_api_path_oauth].
|
||||
|
@ -25,7 +25,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_sql).
|
||||
|
||||
-behaviour(ejabberd_oauth).
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-export([init/0,
|
||||
|
@ -135,7 +135,7 @@ export_host(Dir, FnH, Host) ->
|
||||
{ok, Fd} ->
|
||||
print(Fd, make_piefxis_xml_head()),
|
||||
print(Fd, make_piefxis_host_head(Host)),
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
Users = ejabberd_auth:get_users(Host),
|
||||
case export_users(Users, Host, Fd) of
|
||||
ok ->
|
||||
print(Fd, make_piefxis_host_tail()),
|
||||
@ -402,9 +402,9 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
|
||||
stop("Invalid 'user': ~s", [Name]);
|
||||
LUser ->
|
||||
case ejabberd_auth:try_register(LUser, LServer, Pass) of
|
||||
{atomic, _} ->
|
||||
ok ->
|
||||
process_user_els(Els, State#state{user = LUser});
|
||||
Err ->
|
||||
{error, Err} ->
|
||||
stop("Failed to create user '~s': ~p", [Name, Err])
|
||||
end
|
||||
end.
|
||||
|
535
src/ejabberd_pkix.erl
Normal file
535
src/ejabberd_pkix.erl
Normal file
@ -0,0 +1,535 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 4 Mar 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_pkix).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, add_certfile/1, format_error/1, opt_type/1,
|
||||
get_certfile/1, try_certfile/1, route_registered/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
-record(state, {validate = true :: boolean(),
|
||||
certs = #{}}).
|
||||
-record(cert_state, {domains = [] :: [binary()]}).
|
||||
|
||||
-type cert() :: #'OTPCertificate'{}.
|
||||
-type priv_key() :: public_key:private_key().
|
||||
-type pub_key() :: #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | #'ECPoint'{}.
|
||||
-type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature |
|
||||
name_not_permitted | missing_basic_constraint |
|
||||
invalid_key_usage | selfsigned_peer | unknown_sig_algo |
|
||||
unknown_ca | missing_priv_key.
|
||||
-type bad_cert() :: {bad_cert, bad_cert_reason()}.
|
||||
-type cert_error() :: not_cert | not_der | not_pem | encrypted.
|
||||
-export_type([cert_error/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec add_certfile(filename:filename())
|
||||
-> ok | {error, cert_error() | file:posix()}.
|
||||
add_certfile(Path) ->
|
||||
gen_server:call(?MODULE, {add_certfile, prep_path(Path)}).
|
||||
|
||||
-spec try_certfile(filename:filename()) -> binary().
|
||||
try_certfile(Path0) ->
|
||||
Path = prep_path(Path0),
|
||||
case mk_cert_state(Path, false) of
|
||||
{ok, _} -> Path;
|
||||
{error, _} -> erlang:error(badarg)
|
||||
end.
|
||||
|
||||
route_registered(Route) ->
|
||||
gen_server:call(?MODULE, {route_registered, Route}).
|
||||
|
||||
-spec format_error(cert_error() | file:posix()) -> string().
|
||||
format_error(not_cert) ->
|
||||
"no PEM encoded certificates found";
|
||||
format_error(not_pem) ->
|
||||
"failed to decode from PEM format";
|
||||
format_error(not_der) ->
|
||||
"failed to decode from DER format";
|
||||
format_error(encrypted) ->
|
||||
"encrypted certificate found in the chain";
|
||||
format_error({bad_cert, cert_expired}) ->
|
||||
"certificate is no longer valid as its expiration date has passed";
|
||||
format_error({bad_cert, invalid_issuer}) ->
|
||||
"certificate issuer name does not match the name of the "
|
||||
"issuer certificate in the chain";
|
||||
format_error({bad_cert, invalid_signature}) ->
|
||||
"certificate was not signed by its issuer certificate in the chain";
|
||||
format_error({bad_cert, name_not_permitted}) ->
|
||||
"invalid Subject Alternative Name extension";
|
||||
format_error({bad_cert, missing_basic_constraint}) ->
|
||||
"certificate, required to have the basic constraints extension, "
|
||||
"does not have a basic constraints extension";
|
||||
format_error({bad_cert, invalid_key_usage}) ->
|
||||
"certificate key is used in an invalid way according "
|
||||
"to the key-usage extension";
|
||||
format_error({bad_cert, selfsigned_peer}) ->
|
||||
"self-signed certificate in the chain";
|
||||
format_error({bad_cert, unknown_sig_algo}) ->
|
||||
"certificate is signed using unknown algorithm";
|
||||
format_error({bad_cert, unknown_ca}) ->
|
||||
"certificate is signed by unknown CA";
|
||||
format_error({bad_cert, missing_priv_key}) ->
|
||||
"no matching private key found for certificate in the chain";
|
||||
format_error({bad_cert, Unknown}) ->
|
||||
lists:flatten(io_lib:format("~w", [Unknown]));
|
||||
format_error(Why) ->
|
||||
case file:format_error(Why) of
|
||||
"unknown POSIX error" ->
|
||||
atom_to_list(Why);
|
||||
Reason ->
|
||||
Reason
|
||||
end.
|
||||
|
||||
-spec get_certfile(binary()) -> {ok, binary()} | error.
|
||||
get_certfile(Domain) ->
|
||||
case ejabberd_idna:domain_utf8_to_ascii(Domain) of
|
||||
false ->
|
||||
error;
|
||||
ASCIIDomain ->
|
||||
case ets:lookup(?MODULE, ASCIIDomain) of
|
||||
[] ->
|
||||
case binary:split(ASCIIDomain, <<".">>, [trim]) of
|
||||
[_, Host] ->
|
||||
case ets:lookup(?MODULE, <<"*.", Host/binary>>) of
|
||||
[{_, Path}|_] ->
|
||||
{ok, Path};
|
||||
[] ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
[{_, Path}|_] ->
|
||||
{ok, Path}
|
||||
end
|
||||
end.
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
opt_type(ca_path) ->
|
||||
fun(Path) -> iolist_to_binary(Path) end;
|
||||
opt_type(_) ->
|
||||
[ca_path].
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
ets:new(?MODULE, [named_table, public, bag]),
|
||||
ejabberd_hooks:add(route_registered, ?MODULE, route_registered, 50),
|
||||
Validate = case os:type() of
|
||||
{win32, _} -> false;
|
||||
_ ->
|
||||
code:ensure_loaded(public_key),
|
||||
erlang:function_exported(
|
||||
public_key, short_name_hash, 1)
|
||||
end,
|
||||
if Validate -> check_ca_dir();
|
||||
true -> ok
|
||||
end,
|
||||
State = #state{validate = Validate},
|
||||
{ok, add_certfiles(State)}.
|
||||
|
||||
handle_call({add_certfile, Path}, _, State) ->
|
||||
{Result, NewState} = add_certfile(Path, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({route_registered, Host}, _, State) ->
|
||||
NewState = add_certfiles(Host, State),
|
||||
case get_certfile(Host) of
|
||||
{ok, _} -> ok;
|
||||
error ->
|
||||
?WARNING_MSG("No certificate found matching '~s': strictly "
|
||||
"configured clients or servers will reject "
|
||||
"connections with this host", [Host])
|
||||
end,
|
||||
{reply, ok, NewState};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
?WARNING_MSG("unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_hooks:delete(route_registered, ?MODULE, route_registered, 50).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
add_certfiles(State) ->
|
||||
lists:foldl(
|
||||
fun(Host, AccState) ->
|
||||
add_certfiles(Host, AccState)
|
||||
end, State, ejabberd_config:get_myhosts()).
|
||||
|
||||
add_certfiles(Host, State) ->
|
||||
lists:foldl(
|
||||
fun(Opt, AccState) ->
|
||||
case ejabberd_config:get_option({Opt, Host}) of
|
||||
undefined -> AccState;
|
||||
Path ->
|
||||
{_, NewAccState} = add_certfile(Path, AccState),
|
||||
NewAccState
|
||||
end
|
||||
end, State, [c2s_certfile, s2s_certfile, domain_certfile]).
|
||||
|
||||
add_certfile(Path, State) ->
|
||||
case maps:get(Path, State#state.certs, undefined) of
|
||||
#cert_state{} ->
|
||||
{ok, State};
|
||||
undefined ->
|
||||
case mk_cert_state(Path, State#state.validate) of
|
||||
{error, Reason} ->
|
||||
{{error, Reason}, State};
|
||||
{ok, CertState} ->
|
||||
NewCerts = maps:put(Path, CertState, State#state.certs),
|
||||
lists:foreach(
|
||||
fun(Domain) ->
|
||||
ets:insert(?MODULE, {Domain, Path})
|
||||
end, CertState#cert_state.domains),
|
||||
{ok, State#state{certs = NewCerts}}
|
||||
end
|
||||
end.
|
||||
|
||||
mk_cert_state(Path, Validate) ->
|
||||
case check_certfile(Path, Validate) of
|
||||
{ok, Ds} ->
|
||||
{ok, #cert_state{domains = Ds}};
|
||||
{invalid, Ds, {bad_cert, _} = Why} ->
|
||||
?WARNING_MSG("certificate from ~s is invalid: ~s",
|
||||
[Path, format_error(Why)]),
|
||||
{ok, #cert_state{domains = Ds}};
|
||||
{error, Why} = Err ->
|
||||
?ERROR_MSG("failed to read certificate from ~s: ~s",
|
||||
[Path, format_error(Why)]),
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec check_certfile(filename:filename(), boolean())
|
||||
-> {ok, [binary()]} | {invalid, [binary()], bad_cert()} |
|
||||
{error, cert_error() | file:posix()}.
|
||||
check_certfile(Path, Validate) ->
|
||||
try
|
||||
{ok, Data} = file:read_file(Path),
|
||||
{ok, Certs, PrivKeys} = pem_decode(Data),
|
||||
CertPaths = get_cert_paths(Certs),
|
||||
Domains = get_domains(CertPaths),
|
||||
case match_cert_keys(CertPaths, PrivKeys) of
|
||||
{ok, _} ->
|
||||
case validate(CertPaths, Validate) of
|
||||
ok -> {ok, Domains};
|
||||
{error, Why} -> {invalid, Domains, Why}
|
||||
end;
|
||||
{error, Why} ->
|
||||
{invalid, Domains, Why}
|
||||
end
|
||||
catch _:{badmatch, {error, _} = Err} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec pem_decode(binary()) -> {ok, [cert()], [priv_key()]} |
|
||||
{error, cert_error()}.
|
||||
pem_decode(Data) ->
|
||||
try public_key:pem_decode(Data) of
|
||||
PemEntries ->
|
||||
case decode_certs(PemEntries) of
|
||||
{error, _} = Err ->
|
||||
Err;
|
||||
Objects ->
|
||||
case lists:partition(
|
||||
fun(#'OTPCertificate'{}) -> true;
|
||||
(_) -> false
|
||||
end, Objects) of
|
||||
{[], _} ->
|
||||
{error, not_cert};
|
||||
{Certs, PrivKeys} ->
|
||||
{ok, Certs, PrivKeys}
|
||||
end
|
||||
end
|
||||
catch _:_ ->
|
||||
{error, not_pem}
|
||||
end.
|
||||
|
||||
-spec decode_certs([public_key:pem_entry()]) -> {[cert()], [priv_key()]} |
|
||||
{error, not_der | encrypted}.
|
||||
decode_certs(PemEntries) ->
|
||||
try lists:foldr(
|
||||
fun(_, {error, _} = Err) ->
|
||||
Err;
|
||||
({_, _, Flag}, _) when Flag /= not_encrypted ->
|
||||
{error, encrypted};
|
||||
({'Certificate', Der, _}, Acc) ->
|
||||
[public_key:pkix_decode_cert(Der, otp)|Acc];
|
||||
({'PrivateKeyInfo', Der, not_encrypted}, Acc) ->
|
||||
#'PrivateKeyInfo'{privateKeyAlgorithm =
|
||||
#'PrivateKeyInfo_privateKeyAlgorithm'{
|
||||
algorithm = Algo},
|
||||
privateKey = Key} =
|
||||
public_key:der_decode('PrivateKeyInfo', Der),
|
||||
case Algo of
|
||||
?'rsaEncryption' ->
|
||||
[public_key:der_decode(
|
||||
'RSAPrivateKey', iolist_to_binary(Key))|Acc];
|
||||
?'id-dsa' ->
|
||||
[public_key:der_decode(
|
||||
'DSAPrivateKey', iolist_to_binary(Key))|Acc];
|
||||
?'id-ecPublicKey' ->
|
||||
[public_key:der_decode(
|
||||
'ECPrivateKey', iolist_to_binary(Key))|Acc];
|
||||
_ ->
|
||||
Acc
|
||||
end;
|
||||
({Tag, Der, _}, Acc) when Tag == 'RSAPrivateKey';
|
||||
Tag == 'DSAPrivateKey';
|
||||
Tag == 'ECPrivateKey' ->
|
||||
[public_key:der_decode(Tag, Der)|Acc];
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], PemEntries)
|
||||
catch _:_ ->
|
||||
{error, not_der}
|
||||
end.
|
||||
|
||||
-spec validate([{path, [cert()]}], boolean()) -> ok | {error, bad_cert()}.
|
||||
validate([{path, Path}|Paths], true) ->
|
||||
case validate_path(Path) of
|
||||
ok ->
|
||||
validate(Paths, true);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
validate(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec validate_path([cert()]) -> ok | {error, bad_cert()}.
|
||||
validate_path([Cert|_] = Certs) ->
|
||||
case find_local_issuer(Cert) of
|
||||
{ok, IssuerCert} ->
|
||||
try public_key:pkix_path_validation(IssuerCert, Certs, []) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
catch error:function_clause ->
|
||||
case erlang:get_stacktrace() of
|
||||
[{public_key, pkix_sign_types, _, _}|_] ->
|
||||
{error, {bad_cert, unknown_sig_algo}};
|
||||
ST ->
|
||||
%% Bug in public_key application
|
||||
erlang:raise(error, function_clause, ST)
|
||||
end
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
case public_key:pkix_is_self_signed(Cert) of
|
||||
true ->
|
||||
{error, {bad_cert, selfsigned_peer}};
|
||||
false ->
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
-spec ca_dir() -> string().
|
||||
ca_dir() ->
|
||||
ejabberd_config:get_option(ca_path, "/etc/ssl/certs").
|
||||
|
||||
-spec check_ca_dir() -> ok.
|
||||
check_ca_dir() ->
|
||||
case filelib:wildcard(filename:join(ca_dir(), "*.0")) of
|
||||
[] ->
|
||||
Hint = "configuring 'ca_path' option might help",
|
||||
case file:list_dir(ca_dir()) of
|
||||
{error, Why} ->
|
||||
?WARNING_MSG("failed to read CA directory ~s: ~s; ~s",
|
||||
[ca_dir(), file:format_error(Why), Hint]);
|
||||
{ok, _} ->
|
||||
?WARNING_MSG("CA directory ~s doesn't contain "
|
||||
"hashed certificate files; ~s",
|
||||
[ca_dir(), Hint])
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec find_local_issuer(cert()) -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
|
||||
find_local_issuer(Cert) ->
|
||||
{ok, {_, IssuerID}} = public_key:pkix_issuer_id(Cert, self),
|
||||
Hash = short_name_hash(IssuerID),
|
||||
filelib:fold_files(
|
||||
ca_dir(), Hash ++ "\\.[0-9]+", false,
|
||||
fun(_, {ok, IssuerCert}) ->
|
||||
{ok, IssuerCert};
|
||||
(CertFile, Acc) ->
|
||||
try
|
||||
{ok, Data} = file:read_file(CertFile),
|
||||
{ok, [IssuerCert|_], _} = pem_decode(Data),
|
||||
case public_key:pkix_is_issuer(Cert, IssuerCert) of
|
||||
true ->
|
||||
{ok, IssuerCert};
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
catch _:{badmatch, {error, Why}} ->
|
||||
?ERROR_MSG("failed to read CA certificate from \"~s\": ~s",
|
||||
[CertFile, format_error(Why)]),
|
||||
Acc
|
||||
end
|
||||
end, {error, {bad_cert, unknown_ca}}).
|
||||
|
||||
-spec match_cert_keys([{path, [cert()]}], [priv_key()])
|
||||
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
|
||||
match_cert_keys(CertPaths, PrivKeys) ->
|
||||
KeyPairs = [{pubkey_from_privkey(PrivKey), PrivKey} || PrivKey <- PrivKeys],
|
||||
match_cert_keys(CertPaths, KeyPairs, []).
|
||||
|
||||
-spec match_cert_keys([{path, [cert()]}], [{pub_key(), priv_key()}],
|
||||
[{cert(), priv_key()}])
|
||||
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
|
||||
match_cert_keys([{path, Certs}|CertPaths], KeyPairs, Result) ->
|
||||
[Cert|_] = RevCerts = lists:reverse(Certs),
|
||||
PubKey = pubkey_from_cert(Cert),
|
||||
case lists:keyfind(PubKey, 1, KeyPairs) of
|
||||
false ->
|
||||
{error, {bad_cert, missing_priv_key}};
|
||||
{_, PrivKey} ->
|
||||
match_cert_keys(CertPaths, KeyPairs, [{RevCerts, PrivKey}|Result])
|
||||
end;
|
||||
match_cert_keys([], _, Result) ->
|
||||
{ok, Result}.
|
||||
|
||||
-spec pubkey_from_cert(cert()) -> pub_key().
|
||||
pubkey_from_cert(Cert) ->
|
||||
TBSCert = Cert#'OTPCertificate'.tbsCertificate,
|
||||
PubKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
|
||||
SubjPubKey = PubKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,
|
||||
case PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm of
|
||||
#'PublicKeyAlgorithm'{
|
||||
algorithm = ?rsaEncryption} ->
|
||||
SubjPubKey;
|
||||
#'PublicKeyAlgorithm'{
|
||||
algorithm = ?'id-dsa',
|
||||
parameters = {params, DSSParams}} ->
|
||||
{SubjPubKey, DSSParams};
|
||||
#'PublicKeyAlgorithm'{
|
||||
algorithm = ?'id-ecPublicKey'} ->
|
||||
SubjPubKey
|
||||
end.
|
||||
|
||||
-spec pubkey_from_privkey(priv_key()) -> pub_key().
|
||||
pubkey_from_privkey(#'RSAPrivateKey'{modulus = Modulus,
|
||||
publicExponent = Exp}) ->
|
||||
#'RSAPublicKey'{modulus = Modulus,
|
||||
publicExponent = Exp};
|
||||
pubkey_from_privkey(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) ->
|
||||
{Y, #'Dss-Parms'{p = P, q = Q, g = G}};
|
||||
pubkey_from_privkey(#'ECPrivateKey'{publicKey = Key}) ->
|
||||
#'ECPoint'{point = Key}.
|
||||
|
||||
-spec get_domains([{path, [cert()]}]) -> [binary()].
|
||||
get_domains(CertPaths) ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun({path, Certs}) ->
|
||||
Cert = lists:last(Certs),
|
||||
xmpp_stream_pkix:get_cert_domains(Cert)
|
||||
end, CertPaths)).
|
||||
|
||||
-spec get_cert_paths([cert()]) -> [{path, [cert()]}].
|
||||
get_cert_paths(Certs) ->
|
||||
G = digraph:new([acyclic]),
|
||||
lists:foreach(
|
||||
fun(Cert) ->
|
||||
digraph:add_vertex(G, Cert)
|
||||
end, Certs),
|
||||
lists:foreach(
|
||||
fun({Cert1, Cert2}) when Cert1 /= Cert2 ->
|
||||
case public_key:pkix_is_issuer(Cert1, Cert2) of
|
||||
true ->
|
||||
digraph:add_edge(G, Cert1, Cert2);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
(_) ->
|
||||
ok
|
||||
end, [{Cert1, Cert2} || Cert1 <- Certs, Cert2 <- Certs]),
|
||||
Paths = lists:flatmap(
|
||||
fun(Cert) ->
|
||||
case digraph:in_degree(G, Cert) of
|
||||
0 ->
|
||||
get_cert_path(G, [Cert]);
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end, Certs),
|
||||
digraph:delete(G),
|
||||
Paths.
|
||||
|
||||
get_cert_path(G, [Root|_] = Acc) ->
|
||||
case digraph:out_edges(G, Root) of
|
||||
[] ->
|
||||
[{path, Acc}];
|
||||
Es ->
|
||||
lists:flatmap(
|
||||
fun(E) ->
|
||||
{_, _, V, _} = digraph:edge(G, E),
|
||||
get_cert_path(G, [V|Acc])
|
||||
end, Es)
|
||||
end.
|
||||
|
||||
-spec prep_path(filename:filename()) -> binary().
|
||||
prep_path(Path0) ->
|
||||
case filename:pathtype(Path0) of
|
||||
relative ->
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
iolist_to_binary(filename:join(CWD, Path0));
|
||||
_ ->
|
||||
iolist_to_binary(Path0)
|
||||
end.
|
||||
|
||||
-ifdef(SHORT_NAME_HASH).
|
||||
short_name_hash(IssuerID) ->
|
||||
public_key:short_name_hash(IssuerID).
|
||||
-else.
|
||||
short_name_hash(_) ->
|
||||
"".
|
||||
-endif.
|
@ -109,6 +109,9 @@ needs_sql(Host) ->
|
||||
undefined -> false
|
||||
end.
|
||||
|
||||
-type sql_type() :: mysql | pgsql | sqlite | mssql | odbc.
|
||||
-spec opt_type(sql_type) -> fun((sql_type()) -> sql_type());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
|
@ -248,17 +248,15 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
activate_socket(#state{socket = Socket,
|
||||
sock_mod = SockMod}) ->
|
||||
PeerName = case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{active, once}]),
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, [{active, once}]),
|
||||
SockMod:peername(Socket)
|
||||
end,
|
||||
case PeerName of
|
||||
Res = case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{active, once}]);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, [{active, once}])
|
||||
end,
|
||||
case Res of
|
||||
{error, _Reason} -> self() ! {tcp_closed, Socket};
|
||||
{ok, _} -> ok
|
||||
ok -> ok
|
||||
end.
|
||||
|
||||
%% Data processing for connectors directly generating xmlelement in
|
||||
@ -348,6 +346,9 @@ do_call(Pid, Msg) ->
|
||||
hibernate_timeout() ->
|
||||
ejabberd_config:get_option(receiver_hibernate, timer:seconds(90)).
|
||||
|
||||
-spec opt_type(receiver_hibernate) -> fun((pos_integer() | hibernate) ->
|
||||
pos_integer() | hibernate);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(receiver_hibernate) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(hibernate) -> hibernate
|
||||
|
@ -108,17 +108,11 @@ is_redis_configured(Host) ->
|
||||
PoolSize = ejabberd_config:has_option({redis_pool_size, Host}),
|
||||
ConnTimeoutConfigured = ejabberd_config:has_option(
|
||||
{redis_connect_timeout, Host}),
|
||||
Modules = ejabberd_config:get_option({modules, Host}, []),
|
||||
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis,
|
||||
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == redis,
|
||||
ModuleWithRedisDBConfigured =
|
||||
lists:any(
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == redis
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
|
||||
PoolSize or ConnTimeoutConfigured or
|
||||
SMConfigured or RouterConfigured or ModuleWithRedisDBConfigured.
|
||||
SMConfigured or RouterConfigured.
|
||||
|
||||
get_specs() ->
|
||||
lists:map(
|
||||
@ -133,6 +127,14 @@ get_pool_size() ->
|
||||
iolist_to_list(IOList) ->
|
||||
binary_to_list(iolist_to_binary(IOList)).
|
||||
|
||||
-spec opt_type(redis_connect_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(redis_db) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(redis_password) -> fun((binary()) -> binary());
|
||||
(redis_port) -> fun((0..65535) -> 0..65535);
|
||||
(redis_server) -> fun((binary()) -> binary());
|
||||
(redis_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(redis_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(redis_connect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(redis_db) ->
|
||||
@ -147,5 +149,4 @@ opt_type(redis_queue_type) ->
|
||||
fun(ram) -> ram; (file) -> file end;
|
||||
opt_type(_) ->
|
||||
[redis_connect_timeout, redis_db, redis_password,
|
||||
redis_port, redis_pool_size, redis_server,
|
||||
redis_pool_size, redis_queue_type].
|
||||
redis_port, redis_pool_size, redis_server, redis_queue_type].
|
||||
|
@ -89,16 +89,11 @@ is_riak_configured(Host) ->
|
||||
ejabberd_auth:auth_modules(Host)),
|
||||
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak,
|
||||
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == riak,
|
||||
Modules = ejabberd_config:get_option({modules, Host}, []),
|
||||
ModuleWithRiakDBConfigured = lists:any(
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == riak
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured or StartIntervalConfigured
|
||||
or PoolConfigured or CacertConfigured
|
||||
or UserConfigured or PassConfigured
|
||||
or SMConfigured or RouterConfigured
|
||||
or AuthConfigured or ModuleWithRiakDBConfigured.
|
||||
or AuthConfigured.
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
@ -179,6 +174,14 @@ transform_options({riak_server, {S, P}}, Opts) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec opt_type(riak_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(riak_port) -> fun((0..65535) -> 0..65535);
|
||||
(riak_server) -> fun((binary()) -> binary());
|
||||
(riak_start_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(riak_cacertfile) -> fun((binary()) -> binary());
|
||||
(riak_username) -> fun((binary()) -> binary());
|
||||
(riak_password) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(riak_pool_size) ->
|
||||
fun (N) when is_integer(N), N >= 1 -> N end;
|
||||
opt_type(riak_port) ->
|
||||
|
@ -157,6 +157,7 @@ register_route(Domain, ServerHost, LocalHint, Pid) ->
|
||||
get_component_number(LDomain), Pid) of
|
||||
ok ->
|
||||
?DEBUG("Route registered: ~s", [LDomain]),
|
||||
ejabberd_hooks:run(route_registered, [LDomain]),
|
||||
delete_cache(Mod, LDomain);
|
||||
{error, Err} ->
|
||||
?ERROR_MSG("Failed to register route ~s: ~p",
|
||||
@ -185,6 +186,7 @@ unregister_route(Domain, Pid) ->
|
||||
LDomain, get_component_number(LDomain), Pid) of
|
||||
ok ->
|
||||
?DEBUG("Route unregistered: ~s", [LDomain]),
|
||||
ejabberd_hooks:run(route_unregistered, [LDomain]),
|
||||
delete_cache(Mod, LDomain);
|
||||
{error, Err} ->
|
||||
?ERROR_MSG("Failed to unregister route ~s: ~p",
|
||||
@ -476,6 +478,16 @@ clean_cache(Node) ->
|
||||
clean_cache() ->
|
||||
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
|
||||
|
||||
-type domain_balancing() :: random | source | destination |
|
||||
bare_source | bare_destination.
|
||||
-spec opt_type(domain_balancing) -> fun((domain_balancing()) -> domain_balancing());
|
||||
(domain_balancing_component_number) -> fun((pos_integer()) -> pos_integer());
|
||||
(router_db_type) -> fun((atom()) -> atom());
|
||||
(router_use_cache) -> fun((boolean()) -> boolean());
|
||||
(router_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(router_cache_size) -> fun((timeout()) -> timeout());
|
||||
(router_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(domain_balancing) ->
|
||||
fun (random) -> random;
|
||||
(source) -> source;
|
||||
|
@ -480,9 +480,13 @@ new_connection(MyServer, Server, From, FromTo,
|
||||
end,
|
||||
TRes = mnesia:transaction(F),
|
||||
case TRes of
|
||||
{atomic, Pid} ->
|
||||
ejabberd_s2s_out:connect(Pid),
|
||||
[Pid];
|
||||
{atomic, Pid1} ->
|
||||
if Pid1 == Pid ->
|
||||
ejabberd_s2s_out:connect(Pid);
|
||||
true ->
|
||||
ejabberd_s2s_out:stop(Pid)
|
||||
end,
|
||||
[Pid1];
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("failed to register connection ~s -> ~s: ~p",
|
||||
[MyServer, Server, Reason]),
|
||||
@ -689,16 +693,30 @@ get_s2s_state(S2sPid) ->
|
||||
end,
|
||||
[{s2s_pid, S2sPid} | Infos].
|
||||
|
||||
-type use_starttls() :: boolean() | optional | required | required_trusted.
|
||||
-spec opt_type(route_subdomains) -> fun((s2s | local) -> s2s | local);
|
||||
(s2s_access) -> fun((any()) -> any());
|
||||
(s2s_certfile) -> fun((binary()) -> binary());
|
||||
(s2s_ciphers) -> fun((binary()) -> binary());
|
||||
(s2s_dhfile) -> fun((binary()) -> binary());
|
||||
(s2s_cafile) -> fun((binary()) -> binary());
|
||||
(s2s_protocol_options) -> fun(([binary()]) -> binary());
|
||||
(s2s_tls_compression) -> fun((boolean()) -> boolean());
|
||||
(s2s_use_starttls) -> fun((use_starttls()) -> use_starttls());
|
||||
(s2s_zlib) -> fun((boolean()) -> boolean());
|
||||
(s2s_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(route_subdomains) ->
|
||||
fun (s2s) -> s2s;
|
||||
(local) -> local
|
||||
end;
|
||||
opt_type(s2s_access) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_certfile) -> fun misc:try_read_file/1;
|
||||
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
|
||||
opt_type(s2s_cafile) -> fun misc:try_read_file/1;
|
||||
opt_type(s2s_protocol_options) ->
|
||||
fun (Options) -> str:join(Options, <<"|">>) end;
|
||||
opt_type(s2s_tls_compression) ->
|
||||
|
@ -21,13 +21,12 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_s2s_in).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(ejabberd_socket).
|
||||
|
||||
%% ejabberd_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0]).
|
||||
%% ejabberd_config callbacks
|
||||
-export([opt_type/1, listen_opt_type/1]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([listen_opt_type/1]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
@ -165,7 +164,7 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) ->
|
||||
case check_to(jid:make(LServer), State) of
|
||||
false ->
|
||||
send(State, xmpp:serr_host_unknown());
|
||||
true ->
|
||||
true ->
|
||||
ServerHost = ejabberd_router:host_of_route(LServer),
|
||||
State#{server_host => ServerHost}
|
||||
end.
|
||||
@ -187,7 +186,7 @@ handle_auth_success(RServer, Mech, _AuthModule,
|
||||
[SockMod:pp(Socket), Mech, RServer, LServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
|
||||
State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
|
||||
true ->
|
||||
true ->
|
||||
AuthDomains1 = sets:add_element(RServer, AuthDomains),
|
||||
change_shaper(State, RServer),
|
||||
State#{auth_domains => AuthDomains1};
|
||||
@ -245,7 +244,7 @@ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
|
||||
State, [Pkt, Result]).
|
||||
|
||||
init([State, Opts]) ->
|
||||
Shaper = gen_mod:get_opt(shaper, Opts, none),
|
||||
Shaper = proplists:get_value(shaper, Opts, none),
|
||||
TLSOpts1 = lists:filter(
|
||||
fun({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
@ -311,13 +310,13 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
-spec check_from_to(jid(), jid(), state()) -> ok | {error, stream_error()}.
|
||||
check_from_to(From, To, State) ->
|
||||
case check_from(From, State) of
|
||||
true ->
|
||||
true ->
|
||||
case check_to(To, State) of
|
||||
true ->
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
false ->
|
||||
{error, xmpp:serr_host_unknown()}
|
||||
end;
|
||||
end;
|
||||
false ->
|
||||
{error, xmpp:serr_invalid_from()}
|
||||
end.
|
||||
@ -344,11 +343,24 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
|
||||
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
|
||||
opt_type(_) ->
|
||||
[].
|
||||
|
||||
-spec listen_opt_type(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(supervisor) -> fun((boolean()) -> boolean());
|
||||
(max_stanza_type) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) -> ejabberd_s2s:opt_type(s2s_certfile);
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
|
||||
listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
|
||||
listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile);
|
||||
@ -357,7 +369,7 @@ listen_opt_type(tls_compression) -> ejabberd_s2s:opt_type(s2s_tls_compression);
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I) -> I;
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
|
@ -439,6 +439,13 @@ maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
|
||||
maybe_report_huge_timeout(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec opt_type(outgoing_s2s_families) -> fun(([ipv4|ipv6]) -> [inet|inet6]);
|
||||
(outgoing_s2s_port) -> fun((0..65535) -> 0..65535);
|
||||
(outgoing_s2s_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_dns_retries) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(s2s_dns_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_max_retry_delay) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(outgoing_s2s_families) ->
|
||||
fun(Families) ->
|
||||
lists:map(
|
||||
@ -447,7 +454,7 @@ opt_type(outgoing_s2s_families) ->
|
||||
end, Families)
|
||||
end;
|
||||
opt_type(outgoing_s2s_port) ->
|
||||
fun (I) when is_integer(I), I > 0, I =< 65536 -> I end;
|
||||
fun (I) when is_integer(I), I > 0, I < 65536 -> I end;
|
||||
opt_type(outgoing_s2s_timeout) ->
|
||||
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
timer:seconds(TimeOut);
|
||||
|
@ -21,15 +21,14 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_service).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(ejabberd_socket).
|
||||
|
||||
-protocol({xep, 114, '1.6'}).
|
||||
|
||||
%% ejabberd_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0, close/1, close/2]).
|
||||
%% ejabberd_config callbacks
|
||||
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([listen_opt_type/1, transform_listen_option/2]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_info/2, terminate/2, code_change/3]).
|
||||
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
|
||||
@ -80,15 +79,15 @@ tls_options(#{tls_options := TLSOptions}) ->
|
||||
TLSOptions.
|
||||
|
||||
init([State, Opts]) ->
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
Shaper = gen_mod:get_opt(shaper_rule, Opts, none),
|
||||
GlobalPassword = gen_mod:get_opt(password, Opts, random_password()),
|
||||
HostOpts = gen_mod:get_opt(hosts, Opts, [{global, GlobalPassword}]),
|
||||
Access = proplists:get_value(access, Opts, all),
|
||||
Shaper = proplists:get_value(shaper_rule, Opts, none),
|
||||
GlobalPassword = proplists:get_value(password, Opts, random_password()),
|
||||
HostOpts = proplists:get_value(hosts, Opts, [{global, GlobalPassword}]),
|
||||
HostOpts1 = lists:map(
|
||||
fun({Host, undefined}) -> {Host, GlobalPassword};
|
||||
({Host, Password}) -> {Host, Password}
|
||||
end, HostOpts),
|
||||
CheckFrom = gen_mod:get_opt(check_from, Opts, true),
|
||||
CheckFrom = proplists:get_value(check_from, Opts, true),
|
||||
TLSOpts1 = lists:filter(
|
||||
fun({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
@ -259,14 +258,32 @@ transform_listen_option({host, Host, Os}, Opts) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
opt_type(_) -> [].
|
||||
|
||||
-spec listen_opt_type(access) -> fun((any()) -> any());
|
||||
(shaper_rule) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(check_from) -> fun((boolean()) -> boolean());
|
||||
(password) -> fun((boolean()) -> boolean());
|
||||
(hosts) -> fun(([{binary(), [{password, binary()}]}]) ->
|
||||
[{binary(), binary() | undefined}]);
|
||||
(max_stanza_type) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
listen_opt_type(shaper_rule) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(cafile) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) -> fun misc:try_read_file/1;
|
||||
listen_opt_type(cafile) -> fun misc:try_read_file/1;
|
||||
listen_opt_type(protocol_options) ->
|
||||
fun(Options) -> str:join(Options, <<"|">>) end;
|
||||
listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end;
|
||||
|
@ -22,6 +22,21 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_sip).
|
||||
|
||||
-ifndef(SIP).
|
||||
-include("logger.hrl").
|
||||
-export([socket_type/0, start/2, listen_opt_type/1]).
|
||||
log_error() ->
|
||||
?CRITICAL_MSG("ejabberd is not compiled with SIP support", []).
|
||||
socket_type() ->
|
||||
log_error(),
|
||||
raw.
|
||||
listen_opt_type(_) ->
|
||||
log_error(),
|
||||
[].
|
||||
start(_, _) ->
|
||||
log_error(),
|
||||
{error, sip_not_compiled}.
|
||||
-else.
|
||||
%% API
|
||||
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
|
||||
socket_type/0, listen_opt_type/1]).
|
||||
@ -47,7 +62,10 @@ socket_type() ->
|
||||
raw.
|
||||
|
||||
listen_opt_type(certfile) ->
|
||||
fun iolist_to_binary/1;
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(_) ->
|
||||
@ -56,3 +74,4 @@ listen_opt_type(_) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-endif.
|
||||
|
@ -178,7 +178,7 @@ close_session(SID, User, Server, Resource) ->
|
||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
||||
binary()) -> boolean() | {stop, false}.
|
||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
case ejabberd_auth:user_exists(User, Server) of
|
||||
true -> Acc;
|
||||
false -> {stop, false}
|
||||
end.
|
||||
@ -321,7 +321,7 @@ get_offline_info(Time, User, Server, Resource) ->
|
||||
[#session{sid = {Time, _}, info = Info}] ->
|
||||
case proplists:get_bool(offline, Info) of
|
||||
true ->
|
||||
Info;
|
||||
Info;
|
||||
false ->
|
||||
none
|
||||
end;
|
||||
@ -716,7 +716,7 @@ route_message(#message{to = To, type = Type} = Packet) ->
|
||||
end,
|
||||
PrioRes);
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
|
||||
case ejabberd_auth:user_exists(LUser, LServer) andalso
|
||||
is_privacy_allow(Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(offline_message_hook,
|
||||
@ -858,7 +858,7 @@ force_update_presence({LUser, LServer}) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = online(get_sessions(Mod, LUser, LServer)),
|
||||
lists:foreach(fun (#session{sid = {_, Pid}}) ->
|
||||
ejabberd_c2s:route(Pid, force_update_presence)
|
||||
ejabberd_c2s:resend_presence(Pid)
|
||||
end,
|
||||
Ss).
|
||||
|
||||
@ -1010,6 +1010,12 @@ kick_user(User, Server) ->
|
||||
make_sid() ->
|
||||
{p1_time_compat:unique_timestamp(), self()}.
|
||||
|
||||
-spec opt_type(sm_db_type) -> fun((atom()) -> atom());
|
||||
(sm_use_cache) -> fun((boolean()) -> boolean());
|
||||
(sm_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(sm_cache_size) -> fun((timeout()) -> timeout());
|
||||
(sm_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(O) when O == sm_use_cache; O == sm_cache_missed ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
|
@ -194,9 +194,20 @@ abort(Reason) ->
|
||||
restart(Reason) ->
|
||||
throw({aborted, Reason}).
|
||||
|
||||
%% Escape character that will confuse an SQL engine
|
||||
-spec escape_char(char()) -> binary().
|
||||
escape_char($\000) -> <<"\\0">>;
|
||||
escape_char($\n) -> <<"\\n">>;
|
||||
escape_char($\t) -> <<"\\t">>;
|
||||
escape_char($\b) -> <<"\\b">>;
|
||||
escape_char($\r) -> <<"\\r">>;
|
||||
escape_char($') -> <<"''">>;
|
||||
escape_char($") -> <<"\\\"">>;
|
||||
escape_char($\\) -> <<"\\\\">>;
|
||||
escape_char(C) -> <<C>>.
|
||||
|
||||
-spec escape(binary()) -> binary().
|
||||
escape(S) ->
|
||||
<< <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
|
||||
<< <<(escape_char(Char))/binary>> || <<Char>> <= S >>.
|
||||
|
||||
%% Escape character that will confuse an SQL engine
|
||||
%% Percent and underscore only need to be escaped for pattern matching like
|
||||
@ -206,7 +217,7 @@ escape_like(S) when is_binary(S) ->
|
||||
escape_like($%) -> <<"\\%">>;
|
||||
escape_like($_) -> <<"\\_">>;
|
||||
escape_like($\\) -> <<"\\\\\\\\">>;
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> escape_char(C).
|
||||
|
||||
escape_like_arg(S) when is_binary(S) ->
|
||||
<< <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
|
||||
@ -1080,6 +1091,20 @@ check_error({error, Why} = Err, Query) ->
|
||||
check_error(Result, _Query) ->
|
||||
Result.
|
||||
|
||||
-spec opt_type(sql_database) -> fun((binary()) -> binary());
|
||||
(sql_keepalive_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_password) -> fun((binary()) -> binary());
|
||||
(sql_port) -> fun((0..65535) -> 0..65535);
|
||||
(sql_server) -> fun((binary()) -> binary());
|
||||
(sql_username) -> fun((binary()) -> binary());
|
||||
(sql_ssl) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_verify) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_certfile) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_cafile) -> fun((boolean()) -> boolean());
|
||||
(sql_query_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_connect_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sql_database) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_keepalive_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
@ -1090,8 +1115,8 @@ opt_type(sql_server) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_username) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(sql_ssl_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_ssl_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1;
|
||||
opt_type(sql_ssl_cafile) -> fun misc:try_read_file/1;
|
||||
opt_type(sql_query_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_connect_timeout) ->
|
||||
@ -1101,6 +1126,6 @@ opt_type(sql_queue_type) ->
|
||||
opt_type(_) ->
|
||||
[sql_database, sql_keepalive_interval,
|
||||
sql_password, sql_port, sql_server,
|
||||
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_cerfile,
|
||||
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_certfile,
|
||||
sql_ssl_cafile, sql_queue_type, sql_query_timeout,
|
||||
sql_connect_timeout].
|
||||
|
@ -218,6 +218,9 @@ read_lines(Fd, File, Acc) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec opt_type(sql_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_start_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sql_pool_size) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_start_interval) ->
|
||||
|
@ -27,6 +27,21 @@
|
||||
-protocol({rfc, 5766}).
|
||||
-protocol({xep, 176, '1.0'}).
|
||||
|
||||
-ifndef(STUN).
|
||||
-include("logger.hrl").
|
||||
-export([socket_type/0, start/2, listen_opt_type/1]).
|
||||
log_error() ->
|
||||
?CRITICAL_MSG("ejabberd is not compiled with STUN/TURN support", []).
|
||||
socket_type() ->
|
||||
log_error(),
|
||||
raw.
|
||||
listen_opt_type(_) ->
|
||||
log_error(),
|
||||
[].
|
||||
start(_, _) ->
|
||||
log_error(),
|
||||
{error, sip_not_compiled}.
|
||||
-else.
|
||||
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
|
||||
socket_type/0, listen_opt_type/1]).
|
||||
|
||||
@ -73,9 +88,9 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
|
||||
ok
|
||||
end,
|
||||
AuthFun = fun ejabberd_auth:get_password_s/2,
|
||||
Shaper = gen_mod:get_opt(shaper, Opts, none),
|
||||
AuthType = gen_mod:get_opt(auth_type, Opts, user),
|
||||
Realm = case gen_mod:get_opt(auth_realm, Opts) of
|
||||
Shaper = proplists:get_value(shaper, Opts, none),
|
||||
AuthType = proplists:get_value(auth_type, Opts, user),
|
||||
Realm = case proplists:get_value(auth_realm, Opts) of
|
||||
undefined when AuthType == user ->
|
||||
if NumberOfMyHosts > 1 ->
|
||||
?WARNING_MSG("you have several virtual "
|
||||
@ -114,7 +129,10 @@ listen_opt_type(auth_realm) ->
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(certfile) ->
|
||||
fun iolist_to_binary/1;
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(turn_min_port) ->
|
||||
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
|
||||
listen_opt_type(turn_max_port) ->
|
||||
@ -135,3 +153,4 @@ listen_opt_type(_) ->
|
||||
[shaper, auth_type, auth_realm, tls, certfile, turn_min_port,
|
||||
turn_max_port, turn_max_allocations, turn_max_permissions,
|
||||
server_name].
|
||||
-endif.
|
||||
|
@ -148,6 +148,8 @@ init([]) ->
|
||||
permanent, 5000, worker, [ejabberd_admin]},
|
||||
CyrSASL = {cyrsasl, {cyrsasl, start_link, []},
|
||||
permanent, 5000, worker, [cyrsasl]},
|
||||
PKIX = {ejabberd_pkix, {ejabberd_pkix, start_link, []},
|
||||
permanent, 5000, worker, [ejabberd_pkix]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[Hooks,
|
||||
CyrSASL,
|
||||
@ -156,6 +158,7 @@ init([]) ->
|
||||
Ctl,
|
||||
Commands,
|
||||
Admin,
|
||||
PKIX,
|
||||
Listener,
|
||||
SystemMonitor,
|
||||
S2S,
|
||||
|
@ -330,6 +330,9 @@ process_remote_command([setlh, NewValue]) ->
|
||||
[OldLH, NewLH]);
|
||||
process_remote_command(_) -> throw(unknown_command).
|
||||
|
||||
-spec opt_type(watchdog_admins) -> fun(([binary()]) -> ljid());
|
||||
(watchdog_large_heap) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(watchdog_admins) ->
|
||||
fun (JIDs) ->
|
||||
[jid:tolower(jid:decode(iolist_to_binary(S)))
|
||||
|
File diff suppressed because one or more lines are too long
@ -152,7 +152,7 @@ handshake(#ws{headers = Headers} = State) ->
|
||||
V ->
|
||||
[<<"Sec-Websocket-Protocol:">>, V, <<"\r\n">>]
|
||||
end,
|
||||
Hash = misc:encode_base64(
|
||||
Hash = base64:encode(
|
||||
crypto:hash(sha, <<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
|
||||
{State, [<<"HTTP/1.1 101 Switching Protocols\r\n">>,
|
||||
<<"Upgrade: websocket\r\n">>,
|
||||
|
@ -197,7 +197,7 @@ socket_type() -> raw.
|
||||
%% HTTP interface
|
||||
%% -----------------------------
|
||||
process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) ->
|
||||
AccessCommands = gen_mod:get_opt(access_commands, Opts),
|
||||
AccessCommands = proplists:get_value(access_commands, Opts),
|
||||
GetAuth = true,
|
||||
State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP},
|
||||
case fxml_stream:parse_element(Data) of
|
||||
|
@ -53,14 +53,14 @@ modules() ->
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_mam,
|
||||
mod_muc,
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard,
|
||||
mod_vcard_xupdate].
|
||||
mod_vcard].
|
||||
|
||||
export(Server, Output) ->
|
||||
LServer = jid:nameprep(iolist_to_binary(Server)),
|
||||
|
@ -130,7 +130,8 @@
|
||||
port = 389 :: inet:port_number(),
|
||||
sockmod = gen_tcp :: ssl | gen_tcp,
|
||||
tls = none :: none | tls,
|
||||
tls_options = [] :: [{cacertfile, string()} |
|
||||
tls_options = [] :: [{certfile, string()} |
|
||||
{cacertfile, string()} |
|
||||
{depth, non_neg_integer()} |
|
||||
{verify, non_neg_integer()}],
|
||||
fd :: gen_tcp:socket() | undefined,
|
||||
@ -565,7 +566,7 @@ get_handle(Name) when is_binary(Name) ->
|
||||
%% process.
|
||||
%%----------------------------------------------------------------------
|
||||
init([Hosts, Port, Rootdn, Passwd, Opts]) ->
|
||||
Encrypt = case gen_mod:get_opt(encrypt, Opts) of
|
||||
Encrypt = case proplists:get_value(encrypt, Opts) of
|
||||
tls -> tls;
|
||||
_ -> none
|
||||
end,
|
||||
@ -577,30 +578,36 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) ->
|
||||
end;
|
||||
PT -> PT
|
||||
end,
|
||||
CacertOpts = case gen_mod:get_opt(tls_cacertfile, Opts) of
|
||||
CertOpts = case proplists:get_value(tls_certfile, Opts) of
|
||||
undefined ->
|
||||
[];
|
||||
Path1 ->
|
||||
[{certfile, Path1}]
|
||||
end,
|
||||
CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of
|
||||
undefined ->
|
||||
[];
|
||||
Path ->
|
||||
[{cacertfile, Path}]
|
||||
Path2 ->
|
||||
[{cacertfile, Path2}]
|
||||
end,
|
||||
DepthOpts = case gen_mod:get_opt(tls_depth, Opts) of
|
||||
DepthOpts = case proplists:get_value(tls_depth, Opts) of
|
||||
undefined ->
|
||||
[];
|
||||
Depth ->
|
||||
[{depth, Depth}]
|
||||
end,
|
||||
Verify = gen_mod:get_opt(tls_verify, Opts, false),
|
||||
Verify = proplists:get_value(tls_verify, Opts, false),
|
||||
TLSOpts = if (Verify == hard orelse Verify == soft)
|
||||
andalso CacertOpts == [] ->
|
||||
?WARNING_MSG("TLS verification is enabled but no CA "
|
||||
"certfiles configured, so verification "
|
||||
"is disabled.",
|
||||
[]),
|
||||
[];
|
||||
CertOpts;
|
||||
Verify == soft ->
|
||||
[{verify, 1}] ++ CacertOpts ++ DepthOpts;
|
||||
[{verify, 1}] ++ CertOpts ++ CacertOpts ++ DepthOpts;
|
||||
Verify == hard ->
|
||||
[{verify, 2}] ++ CacertOpts ++ DepthOpts;
|
||||
[{verify, 2}] ++ CertOpts ++ CacertOpts ++ DepthOpts;
|
||||
true -> []
|
||||
end,
|
||||
{ok, connecting,
|
||||
|
@ -173,25 +173,26 @@ uids_domain_subst(Host, UIDs) ->
|
||||
-spec get_config(binary(), list()) -> eldap_config().
|
||||
|
||||
get_config(Host, Opts) ->
|
||||
Servers = gen_mod:get_opt({ldap_servers, Host}, Opts, [<<"localhost">>]),
|
||||
Backups = gen_mod:get_opt({ldap_backups, Host}, Opts, []),
|
||||
Encrypt = gen_mod:get_opt({ldap_encrypt, Host}, Opts, none),
|
||||
TLSVerify = gen_mod:get_opt({ldap_tls_verify, Host}, Opts, false),
|
||||
TLSCAFile = gen_mod:get_opt({ldap_tls_cacertfile, Host}, Opts),
|
||||
TLSDepth = gen_mod:get_opt({ldap_tls_depth, Host}, Opts),
|
||||
Port = gen_mod:get_opt({ldap_port, Host}, Opts,
|
||||
case Encrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end),
|
||||
RootDN = gen_mod:get_opt({ldap_rootdn, Host}, Opts, <<"">>),
|
||||
Password = gen_mod:get_opt({ldap_password, Host}, Opts, <<"">>),
|
||||
Base = gen_mod:get_opt({ldap_base, Host}, Opts, <<"">>),
|
||||
OldDerefAliases = gen_mod:get_opt({deref_aliases, Host}, Opts, unspecified),
|
||||
Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]),
|
||||
Backups = get_opt(ldap_backups, Host, Opts, []),
|
||||
Encrypt = get_opt(ldap_encrypt, Host, Opts, none),
|
||||
TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false),
|
||||
TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts),
|
||||
TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts),
|
||||
TLSDepth = get_opt(ldap_tls_depth, Host, Opts),
|
||||
Port = get_opt(ldap_port, Host, Opts,
|
||||
case Encrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end),
|
||||
RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>),
|
||||
Password = get_opt(ldap_password, Host, Opts, <<"">>),
|
||||
Base = get_opt(ldap_base, Host, Opts, <<"">>),
|
||||
OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified),
|
||||
DerefAliases =
|
||||
if OldDerefAliases == unspecified ->
|
||||
gen_mod:get_opt({ldap_deref_aliases, Host}, Opts, never);
|
||||
get_opt(ldap_deref_aliases, Host, Opts, never);
|
||||
true ->
|
||||
?WARNING_MSG("Option 'deref_aliases' is deprecated. "
|
||||
"The option is still supported "
|
||||
@ -203,6 +204,7 @@ get_config(Host, Opts) ->
|
||||
backups = Backups,
|
||||
tls_options = [{encrypt, Encrypt},
|
||||
{tls_verify, TLSVerify},
|
||||
{tls_certfile, TLSCertFile},
|
||||
{tls_cacertfile, TLSCAFile},
|
||||
{tls_depth, TLSDepth}],
|
||||
port = Port,
|
||||
@ -211,6 +213,15 @@ get_config(Host, Opts) ->
|
||||
base = Base,
|
||||
deref_aliases = DerefAliases}.
|
||||
|
||||
get_opt(Opt, Host, Opts) ->
|
||||
get_opt(Opt, Host, Opts, undefined).
|
||||
|
||||
get_opt(Opt, Host, Opts, Default) ->
|
||||
case proplists:get_value(Opt, Opts) of
|
||||
undefined -> ejabberd_config:get_option({Opt, Host}, Default);
|
||||
Value -> Value
|
||||
end.
|
||||
|
||||
%%----------------------------------------
|
||||
%% Borrowed from asn1rt_ber_bin_v2.erl
|
||||
%%----------------------------------------
|
||||
@ -318,12 +329,26 @@ collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
|
||||
collect_parts_bit([],Acc,Uacc) ->
|
||||
list_to_binary([Uacc|lists:reverse(Acc)]).
|
||||
|
||||
-type deref_aliases() :: never | searching | finding | always.
|
||||
-type uids() :: binary() | {binary()} | {binary(), binary()}.
|
||||
-spec opt_type(deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
|
||||
(ldap_backups) -> fun(([binary()]) -> [binary()]);
|
||||
(ldap_base) -> fun((binary()) -> binary());
|
||||
(ldap_deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
|
||||
(ldap_encrypt) -> fun((tls | starttls | none) -> tls | starttls | none);
|
||||
(ldap_password) -> fun((binary()) -> binary());
|
||||
(ldap_port) -> fun((0..65535) -> 0..65535);
|
||||
(ldap_rootdn) -> fun((binary()) -> binary());
|
||||
(ldap_servers) -> fun(([binary()]) -> [binary()]);
|
||||
(ldap_tls_certfile) -> fun((binary()) -> string());
|
||||
(ldap_tls_cacertfile) -> fun((binary()) -> string());
|
||||
(ldap_tls_depth) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(ldap_tls_verify) -> fun((hard | soft | false) -> hard | soft | false);
|
||||
(ldap_filter) -> fun((binary()) -> binary());
|
||||
(ldap_uids) -> fun((uids()) -> uids());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(deref_aliases) ->
|
||||
fun (never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end;
|
||||
opt_type(ldap_deref_aliases);
|
||||
opt_type(ldap_backups) ->
|
||||
fun (L) -> [iolist_to_binary(H) || H <- L] end;
|
||||
opt_type(ldap_base) -> fun iolist_to_binary/1;
|
||||
@ -344,8 +369,12 @@ opt_type(ldap_port) ->
|
||||
opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
|
||||
opt_type(ldap_servers) ->
|
||||
fun (L) -> [iolist_to_binary(H) || H <- L] end;
|
||||
opt_type(ldap_tls_certfile) ->
|
||||
fun(S) ->
|
||||
binary_to_list(ejabberd_pkix:try_certfile(S))
|
||||
end;
|
||||
opt_type(ldap_tls_cacertfile) ->
|
||||
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
|
||||
fun(S) -> binary_to_list(misc:try_read_file(S)) end;
|
||||
opt_type(ldap_tls_depth) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
opt_type(ldap_tls_verify) ->
|
||||
@ -368,4 +397,5 @@ opt_type(_) ->
|
||||
[deref_aliases, ldap_backups, ldap_base, ldap_uids,
|
||||
ldap_deref_aliases, ldap_encrypt, ldap_password,
|
||||
ldap_port, ldap_rootdn, ldap_servers, ldap_filter,
|
||||
ldap_tls_cacertfile, ldap_tls_depth, ldap_tls_verify].
|
||||
ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth,
|
||||
ldap_tls_verify].
|
||||
|
@ -652,6 +652,8 @@ format({Key, Val}) when is_binary(Val) ->
|
||||
format({Key, Val}) -> % TODO: improve Yaml parsing
|
||||
{Key, Val}.
|
||||
|
||||
-spec opt_type(allow_contrib_modules) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(allow_contrib_modules) ->
|
||||
fun (false) -> false;
|
||||
(no) -> false;
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
-export([start/2, stop/1, init/2, check_password/3,
|
||||
set_password/3, try_register/3, remove_user/2,
|
||||
remove_user/3, is_user_exists/2, opt_type/1]).
|
||||
remove_user/3, user_exists/2, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -73,7 +73,7 @@ get_process_name(Host, Integer) ->
|
||||
check_password(User, Server, Password) ->
|
||||
call_port(Server, [<<"auth">>, User, Server, Password]).
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
user_exists(User, Server) ->
|
||||
call_port(Server, [<<"isuser">>, User, Server]).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
@ -83,7 +83,7 @@ try_register(User, Server, Password) ->
|
||||
case call_port(Server,
|
||||
[<<"tryregister">>, User, Server, Password])
|
||||
of
|
||||
true -> {atomic, ok};
|
||||
true -> ok;
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
@ -154,6 +154,8 @@ encode(L) -> str:join(L, <<":">>).
|
||||
decode([0, 0]) -> false;
|
||||
decode([0, 1]) -> true.
|
||||
|
||||
-spec opt_type(extauth_instances) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(extauth_instances) ->
|
||||
fun (V) when is_integer(V), V > 0 -> V end;
|
||||
opt_type(_) -> [extauth_instances].
|
||||
|
@ -186,7 +186,7 @@ check_type(N) when is_integer(N), N>0 -> N;
|
||||
check_type(parallel) -> parallel.
|
||||
|
||||
iqdisc(Host) ->
|
||||
ejabberd_config:get_option({iqdisc, Host}, one_queue).
|
||||
ejabberd_config:get_option({iqdisc, Host}, no_queue).
|
||||
|
||||
-spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}].
|
||||
|
||||
@ -198,6 +198,8 @@ transform_module_options(Opts) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
-spec opt_type(iqdisc) -> fun((type()) -> type());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(iqdisc) -> fun check_type/1;
|
||||
opt_type(_) -> [iqdisc].
|
||||
|
||||
|
@ -99,7 +99,7 @@ start_child(Mod, Host, Opts) ->
|
||||
start_child(Mod, Host, Opts, Proc) ->
|
||||
Spec = {Proc, {?GEN_SERVER, start_link,
|
||||
[{local, Proc}, Mod, [Host, Opts], []]},
|
||||
transient, 2000, worker, [Mod]},
|
||||
transient, timer:minutes(1), worker, [Mod]},
|
||||
supervisor:start_child(ejabberd_gen_mod_sup, Spec).
|
||||
|
||||
-spec stop_child(module(), binary() | global) -> ok | {error, any()}.
|
||||
@ -677,6 +677,8 @@ is_equal_opt(Opt, NewOpts, OldOpts, Default) ->
|
||||
true
|
||||
end.
|
||||
|
||||
-spec opt_type(modules) -> fun(([{atom(), list()}]) -> [{atom(), list()}]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(modules) ->
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
|
@ -35,7 +35,7 @@
|
||||
binary_to_integer/1,
|
||||
integer_to_binary/1]}).
|
||||
|
||||
%% The following functions are deprected: use functions from aux.erl
|
||||
%% The following functions are deprected: use functions from misc.erl
|
||||
-export([tolower/1, term_to_base64/1, base64_to_term/1,
|
||||
decode_base64/1, encode_base64/1, ip_to_list/1,
|
||||
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
|
||||
|
116
src/misc.erl
116
src/misc.erl
@ -28,12 +28,20 @@
|
||||
-module(misc).
|
||||
|
||||
%% API
|
||||
-export([tolower/1, term_to_base64/1, base64_to_term/1,
|
||||
decode_base64/1, encode_base64/1, ip_to_list/1,
|
||||
-export([tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
|
||||
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
||||
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
|
||||
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2]).
|
||||
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2,
|
||||
try_read_file/1]).
|
||||
|
||||
%% Deprecated functions
|
||||
-export([decode_base64/1, encode_base64/1]).
|
||||
-deprecated([{decode_base64, 1},
|
||||
{encode_base64, 1}]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@ -54,83 +62,21 @@ term_to_base64(Term) ->
|
||||
|
||||
-spec base64_to_term(binary()) -> {term, term()} | error.
|
||||
base64_to_term(Base64) ->
|
||||
case catch binary_to_term(decode_base64(Base64), [safe]) of
|
||||
{'EXIT', _} ->
|
||||
error;
|
||||
Term ->
|
||||
{term, Term}
|
||||
try binary_to_term(base64:decode(Base64), [safe]) of
|
||||
Term -> {term, Term}
|
||||
catch _:badarg ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec decode_base64(binary()) -> binary().
|
||||
decode_base64(S) ->
|
||||
case catch binary:last(S) of
|
||||
C when C == $\n; C == $\s ->
|
||||
decode_base64(binary:part(S, 0, byte_size(S) - 1));
|
||||
_ ->
|
||||
decode_base64_bin(S, <<>>)
|
||||
try base64:mime_decode(S)
|
||||
catch _:badarg -> <<>>
|
||||
end.
|
||||
|
||||
take_without_spaces(Bin, Count) ->
|
||||
take_without_spaces(Bin, Count, <<>>).
|
||||
|
||||
take_without_spaces(Bin, 0, Acc) ->
|
||||
{Acc, Bin};
|
||||
take_without_spaces(<<>>, _, Acc) ->
|
||||
{Acc, <<>>};
|
||||
take_without_spaces(<<$\s, Tail/binary>>, Count, Acc) ->
|
||||
take_without_spaces(Tail, Count, Acc);
|
||||
take_without_spaces(<<$\t, Tail/binary>>, Count, Acc) ->
|
||||
take_without_spaces(Tail, Count, Acc);
|
||||
take_without_spaces(<<$\n, Tail/binary>>, Count, Acc) ->
|
||||
take_without_spaces(Tail, Count, Acc);
|
||||
take_without_spaces(<<$\r, Tail/binary>>, Count, Acc) ->
|
||||
take_without_spaces(Tail, Count, Acc);
|
||||
take_without_spaces(<<Char:8, Tail/binary>>, Count, Acc) ->
|
||||
take_without_spaces(Tail, Count-1, <<Acc/binary, Char:8>>).
|
||||
|
||||
decode_base64_bin(<<>>, Acc) ->
|
||||
Acc;
|
||||
decode_base64_bin(Bin, Acc) ->
|
||||
case take_without_spaces(Bin, 4) of
|
||||
{<<A, B, $=, $=>>, _} ->
|
||||
<<Acc/binary, (d(A)):6, (d(B) bsr 4):2>>;
|
||||
{<<A, B, C, $=>>, _} ->
|
||||
<<Acc/binary, (d(A)):6, (d(B)):6, (d(C) bsr 2):4>>;
|
||||
{<<A, B, C, D>>, Tail} ->
|
||||
Acc2 = <<Acc/binary, (d(A)):6, (d(B)):6, (d(C)):6, (d(D)):6>>,
|
||||
decode_base64_bin(Tail, Acc2);
|
||||
_ ->
|
||||
<<"">>
|
||||
end.
|
||||
|
||||
d(X) when X >= $A, X =< $Z -> X - 65;
|
||||
d(X) when X >= $a, X =< $z -> X - 71;
|
||||
d(X) when X >= $0, X =< $9 -> X + 4;
|
||||
d($+) -> 62;
|
||||
d($/) -> 63;
|
||||
d(_) -> 63.
|
||||
|
||||
|
||||
%% Convert Erlang inet IP to list
|
||||
-spec encode_base64(binary()) -> binary().
|
||||
encode_base64(Data) ->
|
||||
encode_base64_bin(Data, <<>>).
|
||||
|
||||
encode_base64_bin(<<A:6, B:6, C:6, D:6, Tail/binary>>, Acc) ->
|
||||
encode_base64_bin(Tail, <<Acc/binary, (e(A)):8, (e(B)):8, (e(C)):8, (e(D)):8>>);
|
||||
encode_base64_bin(<<A:6, B:6, C:4>>, Acc) ->
|
||||
<<Acc/binary, (e(A)):8, (e(B)):8, (e(C bsl 2)):8, $=>>;
|
||||
encode_base64_bin(<<A:6, B:2>>, Acc) ->
|
||||
<<Acc/binary, (e(A)):8, (e(B bsl 4)):8, $=, $=>>;
|
||||
encode_base64_bin(<<>>, Acc) ->
|
||||
Acc.
|
||||
|
||||
e(X) when X >= 0, X < 26 -> X + 65;
|
||||
e(X) when X > 25, X < 52 -> X + 71;
|
||||
e(X) when X > 51, X < 62 -> X - 4;
|
||||
e(62) -> $+;
|
||||
e(63) -> $/;
|
||||
e(X) -> exit({bad_encode_base64_token, X}).
|
||||
base64:encode(Data).
|
||||
|
||||
-spec ip_to_list(inet:ip_address() | undefined |
|
||||
{inet:ip_address(), inet:port_number()}) -> binary().
|
||||
@ -156,7 +102,7 @@ hex_to_bin([H1, H2 | T], Acc) ->
|
||||
|
||||
-spec hex_to_base64(binary()) -> binary().
|
||||
hex_to_base64(Hex) ->
|
||||
encode_base64(hex_to_bin(Hex)).
|
||||
base64:encode(hex_to_bin(Hex)).
|
||||
|
||||
-spec expand_keyword(binary(), binary(), binary()) -> binary().
|
||||
expand_keyword(Keyword, Input, Replacement) ->
|
||||
@ -241,6 +187,30 @@ compile_exprs(Mod, Exprs) ->
|
||||
join_atoms(Atoms, Sep) ->
|
||||
str:join([io_lib:format("~p", [A]) || A <- Atoms], Sep).
|
||||
|
||||
%% @doc Checks if the file is readable and converts its name to binary.
|
||||
%% Fails with `badarg` otherwise. The function is intended for usage
|
||||
%% in configuration validators only.
|
||||
-spec try_read_file(file:filename_all()) -> binary().
|
||||
try_read_file(Path) ->
|
||||
Res = case file:read_file_info(Path) of
|
||||
{ok, #file_info{type = Type, access = Access}} ->
|
||||
case {Type, Access} of
|
||||
{regular, read} -> ok;
|
||||
{regular, read_write} -> ok;
|
||||
{regular, _} -> {error, file:format_error(eaccess)};
|
||||
_ -> {error, "not a regular file"}
|
||||
end;
|
||||
{error, Why} ->
|
||||
{error, file:format_error(Why)}
|
||||
end,
|
||||
case Res of
|
||||
ok ->
|
||||
iolist_to_binary(Path);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]),
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
@ -36,7 +36,7 @@
|
||||
% Commands API
|
||||
-export([
|
||||
% Adminsys
|
||||
compile/1, get_cookie/0, export2sql/2,
|
||||
compile/1, get_cookie/0,
|
||||
restart_module/2,
|
||||
|
||||
% Sessions
|
||||
@ -148,15 +148,6 @@ get_commands_spec() ->
|
||||
result = {cookie, string},
|
||||
result_example = "MWTAVMODFELNLSMYXPPD",
|
||||
result_desc = "Erlang cookie used for authentication by ejabberd"},
|
||||
#ejabberd_commands{name = export2sql, tags = [mnesia],
|
||||
desc = "Export Mnesia tables to files in directory",
|
||||
module = ?MODULE, function = export2sql,
|
||||
args = [{host, string}, {path, string}],
|
||||
args_example = ["myserver.com","/tmp/export/sql"],
|
||||
args_desc = ["Server name", "File to write sql export"],
|
||||
result = {res, rescode},
|
||||
result_example = ok,
|
||||
result_desc = "Status code: 0 on success, 1 otherwise"},
|
||||
#ejabberd_commands{name = restart_module, tags = [erlang],
|
||||
desc = "Stop an ejabberd module, reload code and start",
|
||||
module = ?MODULE, function = restart_module,
|
||||
@ -183,9 +174,9 @@ get_commands_spec() ->
|
||||
desc = "Delete users that didn't log in last days, or that never logged",
|
||||
longdesc = "To protect admin accounts, configure this for example:\n"
|
||||
"access_rules:\n"
|
||||
" delete_old_users:\n"
|
||||
" - deny: admin\n"
|
||||
" - allow: all\n",
|
||||
" protect_old_users:\n"
|
||||
" - allow: admin\n"
|
||||
" - deny: all\n",
|
||||
module = ?MODULE, function = delete_old_users,
|
||||
args = [{days, integer}],
|
||||
args_example = [30],
|
||||
@ -210,7 +201,7 @@ get_commands_spec() ->
|
||||
result_desc = "Result tuple"},
|
||||
#ejabberd_commands{name = check_account, tags = [accounts],
|
||||
desc = "Check if an account exists or not",
|
||||
module = ejabberd_auth, function = is_user_exists,
|
||||
module = ejabberd_auth, function = user_exists,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_example = [<<"peter">>, <<"myserver.com">>],
|
||||
args_desc = ["User name to check", "Server to check"],
|
||||
@ -528,6 +519,10 @@ get_commands_spec() ->
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = push_roster_all, tags = [roster],
|
||||
desc = "Push template roster from file to all those users",
|
||||
longdesc = "The text file must contain an erlang term: a list "
|
||||
"of tuples with username, servername, group and nick. Example:\n"
|
||||
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||
module = ?MODULE, function = push_roster_all,
|
||||
args = [{file, binary}],
|
||||
result = {res, rescode}},
|
||||
@ -697,23 +692,6 @@ restart_module(Host, Module) when is_atom(Module) ->
|
||||
end
|
||||
end.
|
||||
|
||||
export2sql(Host, Directory) ->
|
||||
Tables = [{export_last, last},
|
||||
{export_offline, offline},
|
||||
{export_passwd, passwd},
|
||||
{export_private_storage, private_storage},
|
||||
{export_roster, roster},
|
||||
{export_vcard, vcard},
|
||||
{export_vcard_search, vcard_search}],
|
||||
Export = fun({TableFun, Table}) ->
|
||||
Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
|
||||
io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
|
||||
Res = (catch ejd2sql:TableFun(Host, Filename)),
|
||||
io:format(" Result: ~p~n", [Res])
|
||||
end,
|
||||
lists:foreach(Export, Tables),
|
||||
ok.
|
||||
|
||||
%%%
|
||||
%%% Accounts
|
||||
%%%
|
||||
@ -810,14 +788,14 @@ histogram([], _Integral, _Current, Count, Hist) ->
|
||||
|
||||
delete_old_users(Days) ->
|
||||
%% Get the list of registered users
|
||||
Users = ejabberd_auth:dirty_get_registered_users(),
|
||||
Users = ejabberd_auth:get_users(),
|
||||
|
||||
{removed, N, UR} = delete_old_users(Days, Users),
|
||||
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
|
||||
|
||||
delete_old_users_vhost(Host, Days) ->
|
||||
%% Get the list of registered users
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
Users = ejabberd_auth:get_users(Host),
|
||||
|
||||
{removed, N, UR} = delete_old_users(Days, Users),
|
||||
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
|
||||
@ -839,7 +817,7 @@ delete_old_users(Days, Users) ->
|
||||
{removed, length(Users_removed), Users_removed}.
|
||||
|
||||
delete_or_not(LUser, LServer, TimeStamp_oldest) ->
|
||||
allow = acl:match_rule(LServer, delete_old_users, jid:make(LUser, LServer)),
|
||||
deny = acl:match_rule(LServer, protect_old_users, jid:make(LUser, LServer)),
|
||||
[] = ejabberd_sm:get_user_resources(LUser, LServer),
|
||||
case mod_last:get_last_info(LUser, LServer) of
|
||||
{ok, TimeStamp, _Status} ->
|
||||
@ -1280,12 +1258,12 @@ subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -
|
||||
subscribe_roster({Name, Server, Group, Nick}, Roster);
|
||||
%% Subscribe Name2 to Name1
|
||||
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
|
||||
subscribe(Name1, Server1, iolist_to_binary(Name2), iolist_to_binary(Server2),
|
||||
subscribe(iolist_to_binary(Name1), iolist_to_binary(Server1), iolist_to_binary(Name2), iolist_to_binary(Server2),
|
||||
iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []),
|
||||
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
|
||||
|
||||
push_alltoall(S, G) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(S),
|
||||
Users = ejabberd_auth:get_users(S),
|
||||
Users2 = build_list_users(G, Users, []),
|
||||
subscribe_all(Users2),
|
||||
ok.
|
||||
@ -1485,10 +1463,7 @@ privacy_set(Username, Host, QueryS) ->
|
||||
SubEl = xmpp:decode(QueryEl),
|
||||
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
|
||||
from = From, to = To},
|
||||
ejabberd_hooks:run_fold(privacy_iq_set,
|
||||
Host,
|
||||
{error, xmpp:err_feature_not_implemented()},
|
||||
[IQ, #userlist{}]),
|
||||
mod_privacy:process_iq(IQ),
|
||||
ok.
|
||||
|
||||
%%%
|
||||
@ -1499,14 +1474,14 @@ stats(Name) ->
|
||||
case Name of
|
||||
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
|
||||
<<"processes">> -> length(erlang:processes());
|
||||
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:get_vh_registered_users_number(Host) + Sum end, 0, ?MYHOSTS);
|
||||
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ?MYHOSTS);
|
||||
<<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
|
||||
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
|
||||
end.
|
||||
|
||||
stats(Name, Host) ->
|
||||
case Name of
|
||||
<<"registeredusers">> -> ejabberd_auth:get_vh_registered_users_number(Host);
|
||||
<<"registeredusers">> -> ejabberd_auth:count_users(Host);
|
||||
<<"onlineusers">> -> length(ejabberd_sm:get_vh_session_list(Host))
|
||||
end.
|
||||
|
||||
@ -1638,7 +1613,7 @@ decide_rip_jid({UName, UServer}, Match_list) ->
|
||||
Match_list).
|
||||
|
||||
user_action(User, Server, Fun, OK) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
case ejabberd_auth:user_exists(User, Server) of
|
||||
true ->
|
||||
case catch Fun() of
|
||||
OK -> ok;
|
||||
|
@ -36,7 +36,7 @@
|
||||
import_start/2, import/5, announce/1, send_motd/1, disco_identity/5,
|
||||
disco_features/5, disco_items/5, depends/2,
|
||||
send_announcement_to_all/3, announce_commands/4,
|
||||
announce_items/4, mod_opt_type/1]).
|
||||
announce_items/4, mod_opt_type/1, clean_cache/1]).
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
-export([announce_all/1,
|
||||
@ -57,17 +57,22 @@
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(binary(), binary(), [binary()]) -> ok.
|
||||
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
|
||||
-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
|
||||
-callback delete_motd(binary()) -> {atomic, any()}.
|
||||
-callback get_motd(binary()) -> {ok, xmlel()} | error.
|
||||
-callback is_motd_user(binary(), binary()) -> boolean().
|
||||
-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
|
||||
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> ok | {error, any()}.
|
||||
-callback set_motd(binary(), xmlel()) -> ok | {error, any()}.
|
||||
-callback delete_motd(binary()) -> ok | {error, any()}.
|
||||
-callback get_motd(binary()) -> {ok, xmlel()} | error | {error, any()}.
|
||||
-callback is_motd_user(binary(), binary()) -> {ok, boolean()} | {error, any()}.
|
||||
-callback set_motd_user(binary(), binary()) -> ok | {error, any()}.
|
||||
-callback use_cache(binary()) -> boolean().
|
||||
-callback cache_nodes(binary()) -> [node()].
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
-record(state, {host :: binary()}).
|
||||
|
||||
-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
|
||||
<<"admin">>, <<Sub>>]).
|
||||
-define(MOTD_CACHE, motd_cache).
|
||||
|
||||
tokenize(Node) -> str:tokens(Node, <<"/#">>).
|
||||
|
||||
@ -88,7 +93,7 @@ reload(Host, NewOpts, OldOpts) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
ok.
|
||||
init_cache(NewMod, Host, NewOpts).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_adhoc, hard}].
|
||||
@ -100,6 +105,7 @@ init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, announce, 50),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
|
||||
@ -633,7 +639,7 @@ announce_all(#message{to = To} = Packet) ->
|
||||
Dest = jid:make(User, Server),
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
|
||||
end, ejabberd_auth:get_vh_registered_users(To#jid.lserver)).
|
||||
end, ejabberd_auth:get_users(To#jid.lserver)).
|
||||
|
||||
announce_all_hosts_all(#message{to = To} = Packet) ->
|
||||
Local = jid:make(To#jid.server),
|
||||
@ -642,7 +648,7 @@ announce_all_hosts_all(#message{to = To} = Packet) ->
|
||||
Dest = jid:make(User, Server),
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
|
||||
end, ejabberd_auth:dirty_get_registered_users()).
|
||||
end, ejabberd_auth:get_users()).
|
||||
|
||||
announce_online(#message{to = To} = Packet) ->
|
||||
announce_online1(ejabberd_sm:get_vh_session_list(To#jid.lserver),
|
||||
@ -684,19 +690,19 @@ announce_all_hosts_motd_update(Packet) ->
|
||||
|
||||
announce_motd_update(LServer, Packet) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:delete_motd(LServer),
|
||||
Mod:set_motd(LServer, xmpp:encode(Packet)).
|
||||
delete_motd(Mod, LServer),
|
||||
set_motd(Mod, LServer, xmpp:encode(Packet)).
|
||||
|
||||
announce_motd_delete(#message{to = To}) ->
|
||||
LServer = To#jid.lserver,
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:delete_motd(LServer).
|
||||
delete_motd(Mod, LServer).
|
||||
|
||||
announce_all_hosts_motd_delete(_Packet) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:delete_motd(Host)
|
||||
delete_motd(Mod, Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}.
|
||||
@ -707,16 +713,16 @@ send_motd({#presence{type = available},
|
||||
#{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc)
|
||||
when LUser /= <<>> ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:get_motd(LServer) of
|
||||
case get_motd(Mod, LServer) of
|
||||
{ok, Packet} ->
|
||||
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
|
||||
Msg ->
|
||||
case Mod:is_motd_user(LUser, LServer) of
|
||||
case is_motd_user(Mod, LUser, LServer) of
|
||||
false ->
|
||||
Local = jid:make(LServer),
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(Msg, Local, JID)),
|
||||
Mod:set_motd_user(LUser, LServer);
|
||||
set_motd_user(Mod, LUser, LServer);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
@ -724,16 +730,81 @@ send_motd({#presence{type = available},
|
||||
?ERROR_MSG("failed to decode motd packet ~p: ~s",
|
||||
[Packet, xmpp:format_error(Why)])
|
||||
end;
|
||||
error ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
Acc;
|
||||
send_motd(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec get_motd(module(), binary()) -> {ok, xmlel()} | error | {error, any()}.
|
||||
get_motd(Mod, LServer) ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?MOTD_CACHE, {<<"">>, LServer},
|
||||
fun() -> Mod:get_motd(LServer) end);
|
||||
false ->
|
||||
Mod:get_motd(LServer)
|
||||
end.
|
||||
|
||||
-spec set_motd(module(), binary(), xmlel()) -> any().
|
||||
set_motd(Mod, LServer, XML) ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:update(
|
||||
?MOTD_CACHE, {<<"">>, LServer}, {ok, XML},
|
||||
fun() -> Mod:set_motd(LServer, XML) end,
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
Mod:set_motd(LServer, XML)
|
||||
end.
|
||||
|
||||
-spec is_motd_user(module(), binary(), binary()) -> boolean().
|
||||
is_motd_user(Mod, LUser, LServer) ->
|
||||
Res = case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?MOTD_CACHE, {LUser, LServer},
|
||||
fun() -> Mod:is_motd_user(LUser, LServer) end);
|
||||
false ->
|
||||
Mod:is_motd_user(LUser, LServer)
|
||||
end,
|
||||
case Res of
|
||||
{ok, Bool} -> Bool;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
-spec set_motd_user(module(), binary(), binary()) -> any().
|
||||
set_motd_user(Mod, LUser, LServer) ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:update(
|
||||
?MOTD_CACHE, {LUser, LServer}, {ok, true},
|
||||
fun() -> Mod:set_motd_user(LUser, LServer) end,
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
Mod:set_motd_user(LUser, LServer)
|
||||
end.
|
||||
|
||||
-spec delete_motd(module(), binary()) -> ok | {error, any()}.
|
||||
delete_motd(Mod, LServer) ->
|
||||
case Mod:delete_motd(LServer) of
|
||||
ok ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ejabberd_cluster:eval_everywhere(
|
||||
?MODULE, clean_cache, [LServer]);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
get_stored_motd(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:get_motd(LServer) of
|
||||
case get_motd(Mod, LServer) of
|
||||
{ok, Packet} ->
|
||||
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
|
||||
#message{body = Body, subject = Subject} ->
|
||||
@ -742,7 +813,7 @@ get_stored_motd(LServer) ->
|
||||
?ERROR_MSG("failed to decode motd packet ~p: ~s",
|
||||
[Packet, xmpp:format_error(Why)])
|
||||
end;
|
||||
error ->
|
||||
_ ->
|
||||
{<<>>, <<>>}
|
||||
end.
|
||||
|
||||
@ -775,6 +846,55 @@ route_forbidden_error(Packet) ->
|
||||
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
|
||||
ejabberd_router:route_error(Packet, Err).
|
||||
|
||||
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||
init_cache(Mod, Host, Opts) ->
|
||||
case use_cache(Mod, Host) of
|
||||
true ->
|
||||
CacheOpts = cache_opts(Host, Opts),
|
||||
ets_cache:new(?MOTD_CACHE, CacheOpts);
|
||||
false ->
|
||||
ets_cache:delete(?MOTD_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
|
||||
cache_opts(Host, Opts) ->
|
||||
MaxSize = gen_mod:get_opt(
|
||||
cache_size, Opts,
|
||||
ejabberd_config:cache_size(Host)),
|
||||
CacheMissed = gen_mod:get_opt(
|
||||
cache_missed, Opts,
|
||||
ejabberd_config:cache_missed(Host)),
|
||||
LifeTime = case gen_mod:get_opt(
|
||||
cache_life_time, Opts,
|
||||
ejabberd_config:cache_life_time(Host)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec use_cache(module(), binary()) -> boolean().
|
||||
use_cache(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, use_cache, 1) of
|
||||
true -> Mod:use_cache(Host);
|
||||
false ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host))
|
||||
end.
|
||||
|
||||
-spec cache_nodes(module(), binary()) -> [node()].
|
||||
cache_nodes(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 1) of
|
||||
true -> Mod:cache_nodes(Host);
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
|
||||
-spec clean_cache(binary()) -> non_neg_integer().
|
||||
clean_cache(LServer) ->
|
||||
ets_cache:filter(
|
||||
?MOTD_CACHE,
|
||||
fun({_, S}, _) -> S /= LServer end).
|
||||
|
||||
%%-------------------------------------------------------------------------
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
|
@ -40,11 +40,11 @@
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ejabberd_mnesia:create(?MODULE, motd,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd)}]),
|
||||
ejabberd_mnesia:create(?MODULE, motd_users,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, motd_users)}]).
|
||||
|
||||
@ -55,13 +55,13 @@ set_motd_users(_LServer, USRs) ->
|
||||
mnesia:write(#motd_users{us = {U, S}})
|
||||
end, USRs)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#motd{server = LServer, packet = Packet})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
delete_motd(LServer) ->
|
||||
F = fun() ->
|
||||
@ -76,27 +76,27 @@ delete_motd(LServer) ->
|
||||
mnesia:delete({motd_users, US})
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
get_motd(LServer) ->
|
||||
case mnesia:dirty_read({motd, LServer}) of
|
||||
[#motd{packet = Packet}] ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
[] ->
|
||||
error
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
|
||||
[#motd_users{}] -> true;
|
||||
_ -> false
|
||||
[#motd_users{}] -> {ok, true};
|
||||
_ -> {ok, false}
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#motd_users{us = {LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
need_transform(#motd{server = S}) when is_list(S) ->
|
||||
?INFO_MSG("Mnesia table 'motd' will be converted to binary", []),
|
||||
@ -124,3 +124,11 @@ import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
transaction(F) ->
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
@ -46,47 +46,48 @@ set_motd_users(_LServer, USRs) ->
|
||||
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
|
||||
motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}])
|
||||
end, USRs),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end, USRs)
|
||||
catch _:{badmatch, {error, _} = Err} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
{atomic, ejabberd_riak:put(#motd{server = LServer,
|
||||
packet = Packet},
|
||||
motd_schema())}.
|
||||
ejabberd_riak:put(#motd{server = LServer,
|
||||
packet = Packet},
|
||||
motd_schema()).
|
||||
|
||||
delete_motd(LServer) ->
|
||||
try
|
||||
ok = ejabberd_riak:delete(motd, LServer),
|
||||
ok = ejabberd_riak:delete_by_index(motd_users,
|
||||
<<"server">>,
|
||||
LServer),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
LServer)
|
||||
catch _:{badmatch, {error, _} = Err} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
get_motd(LServer) ->
|
||||
case ejabberd_riak:get(motd, motd_schema(), LServer) of
|
||||
{ok, #motd{packet = Packet}} ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
error
|
||||
{error, notfound} ->
|
||||
error;
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
case ejabberd_riak:get(motd_users, motd_users_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #motd_users{}} -> true;
|
||||
_ -> false
|
||||
{ok, #motd_users{}} -> {ok, true};
|
||||
{error, notfound} -> {ok, false};
|
||||
{error, _} = Err -> Err
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
{atomic, ejabberd_riak:put(
|
||||
#motd_users{us = {LUser, LServer}}, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, LServer}]}])}.
|
||||
ejabberd_riak:put(
|
||||
#motd_users{us = {LUser, LServer}}, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, LServer}]}]).
|
||||
|
||||
import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) ->
|
||||
El = fxml_stream:parse_element(XML),
|
||||
|
@ -36,6 +36,7 @@
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_announce.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@ -53,7 +54,7 @@ set_motd_users(LServer, USRs) ->
|
||||
"xml=''"])
|
||||
end, USRs)
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
set_motd(LServer, Packet) ->
|
||||
XML = fxml:element_to_binary(Packet),
|
||||
@ -63,27 +64,24 @@ set_motd(LServer, Packet) ->
|
||||
["!username=''",
|
||||
"xml=%(XML)s"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
delete_motd(LServer) ->
|
||||
F = fun() ->
|
||||
ejabberd_sql:sql_query_t(?SQL("delete from motd"))
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
get_motd(LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(xml)s from motd where username=''")) of
|
||||
{selected, [{XML}]} ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
{error, _} ->
|
||||
error;
|
||||
Packet ->
|
||||
{ok, Packet}
|
||||
end;
|
||||
parse_element(XML);
|
||||
{selected, []} ->
|
||||
error;
|
||||
_ ->
|
||||
error
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
is_motd_user(LUser, LServer) ->
|
||||
@ -92,9 +90,11 @@ is_motd_user(LUser, LServer) ->
|
||||
?SQL("select @(username)s from motd"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, [_|_]} ->
|
||||
true;
|
||||
{ok, true};
|
||||
{selected, []} ->
|
||||
{ok, false};
|
||||
_ ->
|
||||
false
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
set_motd_user(LUser, LServer) ->
|
||||
@ -104,7 +104,7 @@ set_motd_user(LUser, LServer) ->
|
||||
["!username=%(LUser)s",
|
||||
"xml=''"])
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
export(_Server) ->
|
||||
[{motd,
|
||||
@ -131,3 +131,18 @@ import(_, _, _) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
transaction(LServer, F) ->
|
||||
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||
{atomic, _} -> ok;
|
||||
_ -> {error, db_failure}
|
||||
end.
|
||||
|
||||
parse_element(XML) ->
|
||||
case fxml_stream:parse_element(XML) of
|
||||
El when is_record(El, xmlel) ->
|
||||
{ok, El};
|
||||
_ ->
|
||||
?ERROR_MSG("malformed XML element in SQL table "
|
||||
"'motd' for username='': ~s", [XML]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
@ -57,39 +57,40 @@ filter_packet({#message{} = Msg, State} = Acc) ->
|
||||
From = xmpp:get_from(Msg),
|
||||
LFrom = jid:tolower(From),
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
#{pres_a := PresA,
|
||||
pres_t := PresT,
|
||||
pres_f := PresF} = State,
|
||||
#{pres_a := PresA, jid := JID, lserver := LServer} = State,
|
||||
case (Msg#message.body == [] andalso
|
||||
Msg#message.subject == [])
|
||||
orelse ejabberd_router:is_my_route(From#jid.lserver)
|
||||
orelse (?SETS):is_element(LFrom, PresA)
|
||||
orelse (?SETS):is_element(LBFrom, PresA)
|
||||
orelse sets_bare_member(LBFrom, PresA)
|
||||
orelse (?SETS):is_element(LFrom, PresT)
|
||||
orelse (?SETS):is_element(LBFrom, PresT)
|
||||
orelse (?SETS):is_element(LFrom, PresF)
|
||||
orelse (?SETS):is_element(LBFrom, PresF) of
|
||||
true ->
|
||||
Acc;
|
||||
orelse sets_bare_member(LBFrom, PresA) of
|
||||
false ->
|
||||
#{lserver := LServer} = State,
|
||||
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
|
||||
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
|
||||
if
|
||||
Log ->
|
||||
?INFO_MSG("Drop packet: ~s",
|
||||
[fxml:element_to_binary(
|
||||
xmpp:encode(Msg, ?NS_CLIENT))]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
Drop ->
|
||||
{stop, {drop, State}};
|
||||
true ->
|
||||
Acc
|
||||
end
|
||||
{Sub, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LServer,
|
||||
{none, []}, [JID#jid.luser, LServer, From]),
|
||||
case Sub of
|
||||
none ->
|
||||
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
|
||||
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
|
||||
if
|
||||
Log ->
|
||||
?INFO_MSG("Drop packet: ~s",
|
||||
[fxml:element_to_binary(
|
||||
xmpp:encode(Msg, ?NS_CLIENT))]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if
|
||||
Drop ->
|
||||
{stop, {drop, State}};
|
||||
true ->
|
||||
Acc
|
||||
end;
|
||||
_ ->
|
||||
Acc
|
||||
end;
|
||||
true ->
|
||||
Acc
|
||||
end;
|
||||
filter_packet(Acc) ->
|
||||
Acc.
|
||||
|
@ -39,12 +39,6 @@
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}.
|
||||
-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}.
|
||||
-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error.
|
||||
|
||||
-type block_event() :: {block, [jid()]} | {unblock, [jid()]} | unblock_all.
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50),
|
||||
@ -142,101 +136,111 @@ listitems_to_jids([_ | Items], JIDs) ->
|
||||
listitems_to_jids(Items, JIDs).
|
||||
|
||||
-spec process_block(iq(), [ljid()]) -> iq().
|
||||
process_block(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ, JIDs) ->
|
||||
Filter = fun (List) ->
|
||||
AlreadyBlocked = listitems_to_jids(List, []),
|
||||
lists:foldr(fun (JID, List1) ->
|
||||
case lists:member(JID, AlreadyBlocked)
|
||||
of
|
||||
true -> List1;
|
||||
false ->
|
||||
[#listitem{type = jid,
|
||||
value = JID,
|
||||
action = deny,
|
||||
order = 0,
|
||||
match_all = true}
|
||||
| List1]
|
||||
end
|
||||
end,
|
||||
List, JIDs)
|
||||
end,
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:process_blocklist_block(LUser, LServer, Filter) of
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, UserList, Default),
|
||||
broadcast_event(LUser, LServer,
|
||||
#block{items = [jid:make(J) || J <- JIDs]}),
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList));
|
||||
_Err ->
|
||||
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
|
||||
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
|
||||
xmpp:make_error(IQ, Err)
|
||||
process_block(#iq{from = From} = IQ, LJIDs) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
case mod_privacy:get_user_list(LUser, LServer, default) of
|
||||
{error, _} ->
|
||||
err_db_failure(IQ);
|
||||
Res ->
|
||||
{Name, List} = case Res of
|
||||
error -> {<<"Blocked contacts">>, []};
|
||||
{ok, NameList} -> NameList
|
||||
end,
|
||||
AlreadyBlocked = listitems_to_jids(List, []),
|
||||
NewList = lists:foldr(
|
||||
fun(LJID, List1) ->
|
||||
case lists:member(LJID, AlreadyBlocked) of
|
||||
true ->
|
||||
List1;
|
||||
false ->
|
||||
[#listitem{type = jid,
|
||||
value = LJID,
|
||||
action = deny,
|
||||
order = 0,
|
||||
match_all = true}|List1]
|
||||
end
|
||||
end, List, LJIDs),
|
||||
case mod_privacy:set_list(LUser, LServer, Name, NewList) of
|
||||
ok ->
|
||||
case (if Res == error ->
|
||||
mod_privacy:set_default_list(
|
||||
LUser, LServer, Name);
|
||||
true ->
|
||||
ok
|
||||
end) of
|
||||
ok ->
|
||||
mod_privacy:push_list_update(From, Name),
|
||||
Items = [jid:make(LJID) || LJID <- LJIDs],
|
||||
broadcast_event(From, #block{items = Items}),
|
||||
xmpp:make_iq_result(IQ);
|
||||
{error, notfound} ->
|
||||
?ERROR_MSG("Failed to set default list '~s': "
|
||||
"the list should exist, but not found",
|
||||
[Name]),
|
||||
err_db_failure(IQ);
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end;
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end
|
||||
end.
|
||||
|
||||
-spec process_unblock_all(iq()) -> iq().
|
||||
process_unblock_all(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ) ->
|
||||
Filter = fun (List) ->
|
||||
lists:filter(fun (#listitem{action = A}) -> A =/= deny
|
||||
end,
|
||||
List)
|
||||
end,
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:unblock_by_filter(LUser, LServer, Filter) of
|
||||
{atomic, ok} ->
|
||||
process_unblock_all(#iq{from = From} = IQ) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
case mod_privacy:get_user_list(LUser, LServer, default) of
|
||||
{ok, {Name, List}} ->
|
||||
NewList = lists:filter(
|
||||
fun(#listitem{action = A}) ->
|
||||
A /= deny
|
||||
end, List),
|
||||
case mod_privacy:set_list(LUser, LServer, Name, NewList) of
|
||||
ok ->
|
||||
mod_privacy:push_list_update(From, Name),
|
||||
broadcast_event(From, #unblock{}),
|
||||
xmpp:make_iq_result(IQ);
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end;
|
||||
error ->
|
||||
broadcast_event(From, #unblock{}),
|
||||
xmpp:make_iq_result(IQ);
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, UserList, Default),
|
||||
broadcast_event(LUser, LServer, #unblock{}),
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList));
|
||||
_Err ->
|
||||
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]),
|
||||
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
|
||||
xmpp:make_error(IQ, Err)
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end.
|
||||
|
||||
-spec process_unblock(iq(), [ljid()]) -> iq().
|
||||
process_unblock(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ, JIDs) ->
|
||||
Filter = fun (List) ->
|
||||
lists:filter(fun (#listitem{action = deny, type = jid,
|
||||
value = JID}) ->
|
||||
not lists:member(JID, JIDs);
|
||||
(_) -> true
|
||||
end,
|
||||
List)
|
||||
end,
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:unblock_by_filter(LUser, LServer, Filter) of
|
||||
{atomic, ok} ->
|
||||
process_unblock(#iq{from = From} = IQ, LJIDs) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
case mod_privacy:get_user_list(LUser, LServer, default) of
|
||||
{ok, {Name, List}} ->
|
||||
NewList = lists:filter(
|
||||
fun(#listitem{action = deny, type = jid,
|
||||
value = LJID}) ->
|
||||
not lists:member(LJID, LJIDs);
|
||||
(_) ->
|
||||
true
|
||||
end, List),
|
||||
case mod_privacy:set_list(LUser, LServer, Name, NewList) of
|
||||
ok ->
|
||||
mod_privacy:push_list_update(From, Name),
|
||||
Items = [jid:make(LJID) || LJID <- LJIDs],
|
||||
broadcast_event(From, #unblock{items = Items}),
|
||||
xmpp:make_iq_result(IQ);
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end;
|
||||
error ->
|
||||
Items = [jid:make(LJID) || LJID <- LJIDs],
|
||||
broadcast_event(From, #unblock{items = Items}),
|
||||
xmpp:make_iq_result(IQ);
|
||||
{atomic, {ok, Default, List}} ->
|
||||
UserList = make_userlist(Default, List),
|
||||
broadcast_list_update(LUser, LServer, UserList, Default),
|
||||
broadcast_event(LUser, LServer,
|
||||
#unblock{items = [jid:make(J) || J <- JIDs]}),
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList));
|
||||
_Err ->
|
||||
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]),
|
||||
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
|
||||
xmpp:make_error(IQ, Err)
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end.
|
||||
|
||||
-spec make_userlist(binary(), [listitem()]) -> userlist().
|
||||
make_userlist(Name, List) ->
|
||||
NeedDb = mod_privacy:is_list_needdb(List),
|
||||
#userlist{name = Name, list = List, needdb = NeedDb}.
|
||||
|
||||
-spec broadcast_list_update(binary(), binary(), userlist(), binary()) -> ok.
|
||||
broadcast_list_update(LUser, LServer, UserList, Name) ->
|
||||
mod_privacy:push_list_update(jid:make(LUser, LServer), UserList, Name).
|
||||
|
||||
-spec broadcast_event(binary(), binary(), block_event()) -> ok.
|
||||
broadcast_event(LUser, LServer, Event) ->
|
||||
From = jid:make(LUser, LServer),
|
||||
-spec broadcast_event(jid(), block() | unblock()) -> ok.
|
||||
broadcast_event(#jid{luser = LUser, lserver = LServer} = From, Event) ->
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
To = jid:replace_resource(From, R),
|
||||
@ -247,23 +251,21 @@ broadcast_event(LUser, LServer, Event) ->
|
||||
end, ejabberd_sm:get_user_resources(LUser, LServer)).
|
||||
|
||||
-spec process_get(iq()) -> iq().
|
||||
process_get(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ) ->
|
||||
Mod = db_mod(LServer),
|
||||
case Mod:process_blocklist_get(LUser, LServer) of
|
||||
error ->
|
||||
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
|
||||
xmpp:make_error(IQ, Err);
|
||||
List ->
|
||||
process_get(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
|
||||
case mod_privacy:get_user_list(LUser, LServer, default) of
|
||||
{ok, {_, List}} ->
|
||||
LJIDs = listitems_to_jids(List, []),
|
||||
Items = [jid:make(J) || J <- LJIDs],
|
||||
xmpp:make_iq_result(IQ, #block_list{items = Items})
|
||||
Items = [jid:make(J) || J <- LJIDs],
|
||||
xmpp:make_iq_result(IQ, #block_list{items = Items});
|
||||
error ->
|
||||
xmpp:make_iq_result(IQ, #block_list{});
|
||||
{error, _} ->
|
||||
err_db_failure(IQ)
|
||||
end.
|
||||
|
||||
-spec db_mod(binary()) -> module().
|
||||
db_mod(LServer) ->
|
||||
DBType = gen_mod:db_type(LServer, mod_privacy),
|
||||
gen_mod:db_mod(DBType, ?MODULE).
|
||||
err_db_failure(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)).
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [iqdisc].
|
||||
|
@ -1,100 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_blocking_mnesia.erl
|
||||
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_blocking_mnesia).
|
||||
|
||||
-behaviour(mod_blocking).
|
||||
|
||||
%% API
|
||||
-export([process_blocklist_block/3, unblock_by_filter/3,
|
||||
process_blocklist_get/2]).
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
process_blocklist_block(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = [];
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{default = NewDefault,
|
||||
lists = NewLists}),
|
||||
{ok, NewDefault, NewList}
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
%% No lists, nothing to unblock
|
||||
ok;
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists}),
|
||||
{ok, Default, NewList};
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> [];
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
@ -1,113 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_blocking_riak.erl
|
||||
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_blocking_riak).
|
||||
|
||||
-behaviour(mod_blocking).
|
||||
|
||||
%% API
|
||||
-export([process_blocklist_block/3, unblock_by_filter/3,
|
||||
process_blocklist_get/2]).
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
process_blocklist_block(LUser, LServer, Filter) ->
|
||||
{atomic,
|
||||
begin
|
||||
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end;
|
||||
{error, _} ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{default = NewDefault,
|
||||
lists = NewLists},
|
||||
mod_privacy_riak:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, NewDefault, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
end}.
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{error, _} ->
|
||||
%% No lists, nothing to unblock
|
||||
ok;
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
mod_privacy_riak:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, Default, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end}.
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end;
|
||||
{error, notfound} ->
|
||||
[];
|
||||
{error, _} ->
|
||||
error
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
@ -1,107 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : mod_blocking_sql.erl
|
||||
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_blocking_sql).
|
||||
|
||||
-behaviour(mod_blocking).
|
||||
|
||||
%% API
|
||||
-export([process_blocklist_block/3, unblock_by_filter/3,
|
||||
process_blocklist_get/2]).
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
process_blocklist_block(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} ->
|
||||
Name = <<"Blocked contacts">>,
|
||||
case mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Name) of
|
||||
{selected, []} ->
|
||||
mod_privacy_sql:sql_add_privacy_list(LUser, Name);
|
||||
{selected, [{_ID}]} ->
|
||||
ok
|
||||
end,
|
||||
mod_privacy_sql:sql_set_default_privacy_list(LUser, Name),
|
||||
Name;
|
||||
{selected, [{Name}]} -> Name
|
||||
end,
|
||||
{selected, [{ID}]} =
|
||||
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected, RItems = [_ | _]} ->
|
||||
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
|
||||
_ ->
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList}
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
unblock_by_filter(LUser, LServer, Filter) ->
|
||||
F = fun () ->
|
||||
case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} -> ok;
|
||||
{selected, [{Default}]} ->
|
||||
{selected, [{ID}]} =
|
||||
mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default),
|
||||
case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of
|
||||
{selected, RItems = [_ | _]} ->
|
||||
List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1,
|
||||
RItems),
|
||||
NewList = Filter(List),
|
||||
NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1,
|
||||
NewList),
|
||||
mod_privacy_sql:sql_set_privacy_list(ID, NewRItems),
|
||||
{ok, Default, NewList};
|
||||
_ -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
end
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
||||
process_blocklist_get(LUser, LServer) ->
|
||||
case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, []} -> [];
|
||||
{selected, [{Default}]} ->
|
||||
case catch mod_privacy_sql:sql_get_privacy_list_data(
|
||||
LUser, LServer, Default) of
|
||||
{selected, RItems} ->
|
||||
lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems);
|
||||
{'EXIT', _} -> error
|
||||
end;
|
||||
{'EXIT', _} -> error
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
@ -203,7 +203,9 @@ disco_info(Acc, _, _, _Node, _Lang) ->
|
||||
-spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state().
|
||||
c2s_presence_in(C2SState,
|
||||
#presence{from = From, to = To, type = Type} = Presence) ->
|
||||
Subscription = ejabberd_c2s:get_subscription(From, C2SState),
|
||||
{Subscription, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, To#jid.lserver,
|
||||
{none, []}, [To#jid.luser, To#jid.lserver, From]),
|
||||
Insert = (Type == available)
|
||||
and ((Subscription == both) or (Subscription == to)),
|
||||
Delete = (Type == unavailable) or (Type == error),
|
||||
@ -411,7 +413,7 @@ make_my_disco_hash(Host) ->
|
||||
make_disco_hash(DiscoInfo, Algo) ->
|
||||
Concat = list_to_binary([concat_identities(DiscoInfo),
|
||||
concat_features(DiscoInfo), concat_info(DiscoInfo)]),
|
||||
misc:encode_base64(case Algo of
|
||||
base64:encode(case Algo of
|
||||
md5 -> erlang:md5(Concat);
|
||||
sha -> crypto:hash(sha, Concat);
|
||||
sha224 -> crypto:hash(sha224, Concat);
|
||||
|
@ -547,7 +547,7 @@ get_local_items({_, Host}, [<<"all users">>], _Server,
|
||||
get_local_items({_, Host},
|
||||
[<<"all users">>, <<$@, Diap/binary>>], _Server,
|
||||
_Lang) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
Users = ejabberd_auth:get_users(Host),
|
||||
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
|
||||
try
|
||||
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
|
||||
@ -661,7 +661,7 @@ get_online_vh_users(Host) ->
|
||||
end.
|
||||
|
||||
get_all_vh_users(Host) ->
|
||||
case catch ejabberd_auth:get_vh_registered_users(Host)
|
||||
case catch ejabberd_auth:get_users(Host)
|
||||
of
|
||||
{'EXIT', _Reason} -> [];
|
||||
Users ->
|
||||
@ -1194,7 +1194,7 @@ get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) ->
|
||||
required = true}]}};
|
||||
get_form(Host,
|
||||
?NS_ADMINL(<<"get-registered-users-num">>), Lang) ->
|
||||
Num = integer_to_binary(ejabberd_auth:get_vh_registered_users_number(Host)),
|
||||
Num = integer_to_binary(ejabberd_auth:count_users(Host)),
|
||||
{result, completed,
|
||||
#xdata{type = form,
|
||||
fields = [?HFIELD(),
|
||||
@ -1541,7 +1541,7 @@ set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
|
||||
Server = JID#jid.lserver,
|
||||
true = Server == Host orelse
|
||||
get_permission_level(From) == global,
|
||||
true = ejabberd_auth:is_user_exists(User, Server),
|
||||
true = ejabberd_auth:user_exists(User, Server),
|
||||
{User, Server}
|
||||
end,
|
||||
AccountStringList),
|
||||
@ -1610,7 +1610,7 @@ set_form(From, Host,
|
||||
Server = JID#jid.lserver,
|
||||
true = Server == Host orelse
|
||||
get_permission_level(From) == global,
|
||||
true = ejabberd_auth:is_user_exists(User, Server),
|
||||
true = ejabberd_auth:user_exists(User, Server),
|
||||
ejabberd_auth:set_password(User, Server, Password),
|
||||
{result, undefined};
|
||||
set_form(From, Host,
|
||||
|
@ -376,7 +376,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang,
|
||||
get_sm_identity(Acc, _From,
|
||||
#jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
|
||||
Acc ++
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
case ejabberd_auth:user_exists(LUser, LServer) of
|
||||
true ->
|
||||
[#identity{category = <<"account">>, type = <<"registered">>}];
|
||||
_ -> []
|
||||
|
@ -45,17 +45,24 @@
|
||||
-include("mod_privacy.hrl").
|
||||
-include("mod_last.hrl").
|
||||
|
||||
-define(LAST_CACHE, last_activity_cache).
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(binary(), #last_activity{}) -> ok | pass.
|
||||
-callback get_last(binary(), binary()) ->
|
||||
{ok, non_neg_integer(), binary()} | not_found | {error, any()}.
|
||||
-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> any().
|
||||
{ok, {non_neg_integer(), binary()}} | error | {error, any()}.
|
||||
-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> ok | {error, any()}.
|
||||
-callback remove_user(binary(), binary()) -> any().
|
||||
-callback use_cache(binary()) -> boolean().
|
||||
-callback cache_nodes(binary()) -> [node()].
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
@ -91,6 +98,7 @@ reload(Host, NewOpts, OldOpts) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
init_cache(NewMod, Host, NewOpts),
|
||||
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
|
||||
{false, IQDisc, _} ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
|
||||
@ -157,7 +165,10 @@ privacy_check_packet(allow, C2SState,
|
||||
when T == get; T == set ->
|
||||
case xmpp:has_subtag(IQ, #last{}) of
|
||||
true ->
|
||||
Sub = ejabberd_c2s:get_subscription(From, C2SState),
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
{Sub, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LServer,
|
||||
{none, []}, [LUser, LServer, From]),
|
||||
if Sub == from; Sub == both ->
|
||||
Pres = #presence{from = To, to = From},
|
||||
case ejabberd_hooks:run_fold(
|
||||
@ -177,13 +188,23 @@ privacy_check_packet(allow, C2SState,
|
||||
privacy_check_packet(Acc, _, _, _) ->
|
||||
Acc.
|
||||
|
||||
%% @spec (LUser::string(), LServer::string()) ->
|
||||
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
|
||||
-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
|
||||
not_found | {error, any()}.
|
||||
get_last(LUser, LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_last(LUser, LServer).
|
||||
Res = case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?LAST_CACHE, {LUser, LServer},
|
||||
fun() -> Mod:get_last(LUser, LServer) end);
|
||||
false ->
|
||||
Mod:get_last(LUser, LServer)
|
||||
end,
|
||||
case Res of
|
||||
{ok, {TimeStamp, Status}} -> {ok, TimeStamp, Status};
|
||||
error -> not_found;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
-spec get_last_iq(iq(), binary(), binary()) -> iq().
|
||||
get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
|
||||
@ -223,7 +244,16 @@ store_last_info(User, Server, TimeStamp, Status) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:store_last_info(LUser, LServer, TimeStamp, Status).
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:update(
|
||||
?LAST_CACHE, {LUser, LServer}, {ok, {TimeStamp, Status}},
|
||||
fun() ->
|
||||
Mod:store_last_info(LUser, LServer, TimeStamp, Status)
|
||||
end, cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
Mod:store_last_info(LUser, LServer, TimeStamp, Status)
|
||||
end.
|
||||
|
||||
-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
|
||||
not_found.
|
||||
@ -238,7 +268,51 @@ remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:remove_user(LUser, LServer).
|
||||
Mod:remove_user(LUser, LServer),
|
||||
ets_cache:delete(?LAST_CACHE, {LUser, LServer}, cache_nodes(Mod, LServer)).
|
||||
|
||||
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||
init_cache(Mod, Host, Opts) ->
|
||||
case use_cache(Mod, Host) of
|
||||
true ->
|
||||
CacheOpts = cache_opts(Host, Opts),
|
||||
ets_cache:new(?LAST_CACHE, CacheOpts);
|
||||
false ->
|
||||
ets_cache:delete(?LAST_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
|
||||
cache_opts(Host, Opts) ->
|
||||
MaxSize = gen_mod:get_opt(
|
||||
cache_size, Opts,
|
||||
ejabberd_config:cache_size(Host)),
|
||||
CacheMissed = gen_mod:get_opt(
|
||||
cache_missed, Opts,
|
||||
ejabberd_config:cache_missed(Host)),
|
||||
LifeTime = case gen_mod:get_opt(
|
||||
cache_life_time, Opts,
|
||||
ejabberd_config:cache_life_time(Host)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec use_cache(module(), binary()) -> boolean().
|
||||
use_cache(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, use_cache, 1) of
|
||||
true -> Mod:use_cache(Host);
|
||||
false ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host))
|
||||
end.
|
||||
|
||||
-spec cache_nodes(module(), binary()) -> [node()].
|
||||
cache_nodes(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 1) of
|
||||
true -> Mod:cache_nodes(Host);
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
|
||||
import_info() ->
|
||||
[{<<"last">>, 3}].
|
||||
@ -267,4 +341,11 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) -> [db_type, iqdisc].
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun (I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[db_type, iqdisc, cache_life_time, cache_size, use_cache, cache_missed].
|
||||
|
@ -27,7 +27,8 @@
|
||||
-behaviour(mod_last).
|
||||
|
||||
%% API
|
||||
-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]).
|
||||
-export([init/2, import/2, get_last/2, store_last_info/4,
|
||||
remove_user/2, use_cache/1]).
|
||||
-export([need_transform/1, transform/1]).
|
||||
|
||||
-include("mod_last.hrl").
|
||||
@ -38,31 +39,36 @@
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ejabberd_mnesia:create(?MODULE, last_activity,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, last_activity)}]).
|
||||
|
||||
use_cache(Host) ->
|
||||
case mnesia:table_info(last_activity, storage_type) of
|
||||
disc_only_copies ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, mod_last, use_cache,
|
||||
ejabberd_config:use_cache(Host));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_last(LUser, LServer) ->
|
||||
case mnesia:dirty_read(last_activity, {LUser, LServer}) of
|
||||
[] ->
|
||||
not_found;
|
||||
error;
|
||||
[#last_activity{timestamp = TimeStamp,
|
||||
status = Status}] ->
|
||||
{ok, TimeStamp, Status}
|
||||
{ok, {TimeStamp, Status}}
|
||||
end.
|
||||
|
||||
store_last_info(LUser, LServer, TimeStamp, Status) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun () ->
|
||||
mnesia:write(#last_activity{us = US,
|
||||
timestamp = TimeStamp,
|
||||
status = Status})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:dirty_write(#last_activity{us = {LUser, LServer},
|
||||
timestamp = TimeStamp,
|
||||
status = Status}).
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun () -> mnesia:delete({last_activity, US}) end,
|
||||
mnesia:transaction(F).
|
||||
mnesia:dirty_delete({last_activity, US}).
|
||||
|
||||
import(_LServer, #last_activity{} = LA) ->
|
||||
mnesia:dirty_write(LA).
|
||||
|
@ -43,19 +43,20 @@ get_last(LUser, LServer) ->
|
||||
{LUser, LServer}) of
|
||||
{ok, #last_activity{timestamp = TimeStamp,
|
||||
status = Status}} ->
|
||||
{ok, TimeStamp, Status};
|
||||
{error, notfound} ->
|
||||
not_found;
|
||||
Err ->
|
||||
Err
|
||||
{ok, {TimeStamp, Status}};
|
||||
{error, notfound} ->
|
||||
error;
|
||||
_Err ->
|
||||
%% TODO: log error
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
store_last_info(LUser, LServer, TimeStamp, Status) ->
|
||||
US = {LUser, LServer},
|
||||
{atomic, ejabberd_riak:put(#last_activity{us = US,
|
||||
timestamp = TimeStamp,
|
||||
status = Status},
|
||||
last_activity_schema())}.
|
||||
ejabberd_riak:put(#last_activity{us = US,
|
||||
timestamp = TimeStamp,
|
||||
status = Status},
|
||||
last_activity_schema()).
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
{atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
|
||||
|
@ -43,22 +43,37 @@ init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
get_last(LUser, LServer) ->
|
||||
case catch sql_queries:get_last(LServer, LUser) of
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(seconds)d, @(state)s from last"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, []} ->
|
||||
not_found;
|
||||
error;
|
||||
{selected, [{TimeStamp, Status}]} ->
|
||||
{ok, TimeStamp, Status};
|
||||
{ok, {TimeStamp, Status}};
|
||||
Reason ->
|
||||
?ERROR_MSG("failed to get last for user ~s@~s: ~p",
|
||||
[LUser, LServer, Reason]),
|
||||
{error, {invalid_result, Reason}}
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
store_last_info(LUser, LServer, TimeStamp, Status) ->
|
||||
sql_queries:set_last_t(LServer, LUser, TimeStamp, Status).
|
||||
case ?SQL_UPSERT(LServer, "last",
|
||||
["!username=%(LUser)s",
|
||||
"seconds=%(TimeStamp)d",
|
||||
"state=%(Status)s"]) of
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to store last activity for ~s@~s: ~p",
|
||||
[LUser, LServer, Err]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
sql_queries:del_last(LServer, LUser).
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from last where username=%(LUser)s")).
|
||||
|
||||
export(_Server) ->
|
||||
[{last_activity,
|
||||
|
@ -37,7 +37,7 @@
|
||||
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2,
|
||||
muc_filter_message/3, message_is_archived/3, delete_old_messages/2,
|
||||
get_commands_spec/0, msg_to_el/4, get_room_config/4, set_room_option/3,
|
||||
offline_message/1]).
|
||||
offline_message/1, export/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@ -255,7 +255,7 @@ set_room_option(_Acc, {mam, Val}, _Lang) ->
|
||||
set_room_option(Acc, _Property, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
||||
-spec user_receive_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||
user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
|
||||
Peer = xmpp:get_from(Pkt),
|
||||
LUser = JID#jid.luser,
|
||||
@ -263,7 +263,7 @@ user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
|
||||
Pkt2 = case should_archive(Pkt, LServer) of
|
||||
true ->
|
||||
Pkt1 = strip_my_archived_tag(Pkt, LServer),
|
||||
case store_msg(C2SState, Pkt1, LUser, LServer, Peer, recv) of
|
||||
case store_msg(Pkt1, LUser, LServer, Peer, recv) of
|
||||
{ok, ID} ->
|
||||
set_stanza_id(Pkt1, JID, ID);
|
||||
_ ->
|
||||
@ -274,7 +274,7 @@ user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
|
||||
end,
|
||||
{Pkt2, C2SState}.
|
||||
|
||||
-spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
||||
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||
user_send_packet({Pkt, #{jid := JID} = C2SState}) ->
|
||||
Peer = xmpp:get_to(Pkt),
|
||||
LUser = JID#jid.luser,
|
||||
@ -282,7 +282,7 @@ user_send_packet({Pkt, #{jid := JID} = C2SState}) ->
|
||||
Pkt2 = case should_archive(Pkt, LServer) of
|
||||
true ->
|
||||
Pkt1 = strip_my_archived_tag(Pkt, LServer),
|
||||
case store_msg(C2SState, xmpp:set_from_to(Pkt1, JID, Peer),
|
||||
case store_msg(xmpp:set_from_to(Pkt1, JID, Peer),
|
||||
LUser, LServer, Peer, send) of
|
||||
{ok, ID} ->
|
||||
set_stanza_id(Pkt1, JID, ID);
|
||||
@ -301,7 +301,7 @@ offline_message({_Action, #message{from = Peer, to = To} = Pkt} = Acc) ->
|
||||
case should_archive(Pkt, LServer) of
|
||||
true ->
|
||||
Pkt1 = strip_my_archived_tag(Pkt, LServer),
|
||||
case store_msg(undefined, Pkt1, LUser, LServer, Peer, recv) of
|
||||
case store_msg(Pkt1, LUser, LServer, Peer, recv) of
|
||||
{ok, ID} ->
|
||||
{archived, set_stanza_id(Pkt1, To, ID)};
|
||||
_ ->
|
||||
@ -311,8 +311,8 @@ offline_message({_Action, #message{from = Peer, to = To} = Pkt} = Acc) ->
|
||||
Acc
|
||||
end.
|
||||
|
||||
-spec user_send_packet_strip_tag({stanza(), ejabberd_c2s:state()}) ->
|
||||
{stanza(), ejabberd_c2s:state()}.
|
||||
-spec user_send_packet_strip_tag({stanza(), c2s_state()}) ->
|
||||
{stanza(), c2s_state()}.
|
||||
user_send_packet_strip_tag({Pkt, #{jid := JID} = C2SState}) ->
|
||||
LServer = JID#jid.lserver,
|
||||
{strip_my_archived_tag(Pkt, LServer), C2SState}.
|
||||
@ -415,16 +415,16 @@ disco_sm_features({result, OtherFeatures},
|
||||
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
-spec message_is_archived(boolean(), ejabberd_c2s:state(), message()) -> boolean().
|
||||
-spec message_is_archived(boolean(), c2s_state(), message()) -> boolean().
|
||||
message_is_archived(true, _C2SState, _Pkt) ->
|
||||
true;
|
||||
message_is_archived(false, #{jid := JID} = C2SState, Pkt) ->
|
||||
message_is_archived(false, #{jid := JID}, Pkt) ->
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
Peer = xmpp:get_from(Pkt),
|
||||
case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage, false) of
|
||||
true ->
|
||||
should_archive(strip_my_archived_tag(Pkt, LServer), LServer)
|
||||
andalso should_archive_peer(C2SState, LUser, LServer,
|
||||
andalso should_archive_peer(LUser, LServer,
|
||||
get_prefs(LUser, LServer),
|
||||
Peer);
|
||||
false ->
|
||||
@ -457,6 +457,10 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
|
||||
delete_old_messages(_TypeBin, _Days) ->
|
||||
unsupported_type.
|
||||
|
||||
export(LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:export(LServer).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@ -611,9 +615,9 @@ strip_x_jid_tags(Pkt) ->
|
||||
end, Els),
|
||||
xmpp:set_els(Pkt, NewEls).
|
||||
|
||||
-spec should_archive_peer(c2s_state() | undefined, binary(), binary(),
|
||||
-spec should_archive_peer(binary(), binary(),
|
||||
#archive_prefs{}, jid()) -> boolean().
|
||||
should_archive_peer(C2SState, LUser, LServer,
|
||||
should_archive_peer(LUser, LServer,
|
||||
#archive_prefs{default = Default,
|
||||
always = Always,
|
||||
never = Never},
|
||||
@ -631,23 +635,11 @@ should_archive_peer(C2SState, LUser, LServer,
|
||||
always -> true;
|
||||
never -> false;
|
||||
roster ->
|
||||
Sub = case C2SState of
|
||||
undefined ->
|
||||
{S, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info,
|
||||
LServer, {none, []},
|
||||
[LUser, LServer, Peer]),
|
||||
S;
|
||||
_ ->
|
||||
ejabberd_c2s:get_subscription(
|
||||
LPeer, C2SState)
|
||||
end,
|
||||
case Sub of
|
||||
both -> true;
|
||||
from -> true;
|
||||
to -> true;
|
||||
_ -> false
|
||||
end
|
||||
{Sub, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info,
|
||||
LServer, {none, []},
|
||||
[LUser, LServer, Peer]),
|
||||
Sub == both orelse Sub == from orelse Sub == to
|
||||
end
|
||||
end
|
||||
end.
|
||||
@ -715,12 +707,12 @@ may_enter_room(From,
|
||||
may_enter_room(From, MUCState) ->
|
||||
mod_muc_room:is_occupant_or_admin(From, MUCState).
|
||||
|
||||
-spec store_msg(c2s_state() | undefined, stanza(),
|
||||
-spec store_msg(stanza(),
|
||||
binary(), binary(), jid(), send | recv) ->
|
||||
{ok, binary()} | pass.
|
||||
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) ->
|
||||
store_msg(Pkt, LUser, LServer, Peer, Dir) ->
|
||||
Prefs = get_prefs(LUser, LServer),
|
||||
case should_archive_peer(C2SState, LUser, LServer, Prefs, Peer) of
|
||||
case should_archive_peer(LUser, LServer, Prefs, Peer) of
|
||||
true ->
|
||||
US = {LUser, LServer},
|
||||
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
%% API
|
||||
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
|
||||
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
|
||||
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6, export/1]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("xmpp.hrl").
|
||||
@ -116,7 +116,7 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
|
||||
"def=%(SDefault)s",
|
||||
"always=%(SAlways)s",
|
||||
"never=%(SNever)s"]) of
|
||||
{updated, _} ->
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
@ -181,6 +181,47 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
|
||||
{[], false, 0}
|
||||
end.
|
||||
|
||||
export(_Server) ->
|
||||
[{archive_prefs,
|
||||
fun(Host, #archive_prefs{us =
|
||||
{LUser, LServer},
|
||||
default = Default,
|
||||
always = Always,
|
||||
never = Never})
|
||||
when LServer == Host ->
|
||||
SDefault = erlang:atom_to_binary(Default, utf8),
|
||||
SAlways = misc:term_to_expr(Always),
|
||||
SNever = misc:term_to_expr(Never),
|
||||
[?SQL("insert into archive_prefs (username, def, always, never) values"
|
||||
"(%(LUser)s, %(SDefault)s, %(SAlways)s, %(SNever)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
{archive_msg,
|
||||
fun(Host, #archive_msg{us ={_LUser, LServer},
|
||||
id = _ID, timestamp = TS, peer = Peer,
|
||||
bare_peer = {PUser, PServer, <<>>},
|
||||
type = Type, nick = Nick, packet = Pkt})
|
||||
when LServer == Host ->
|
||||
TStmp = now_to_usec(TS),
|
||||
SUser = case Type of
|
||||
chat -> PUser;
|
||||
groupchat -> jid:encode({PUser, PServer, <<>>})
|
||||
end,
|
||||
BarePeer = jid:encode(jid:tolower(jid:remove_resource(Peer))),
|
||||
LPeer = jid:encode(jid:tolower(Peer)),
|
||||
XML = fxml:element_to_binary(Pkt),
|
||||
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
|
||||
SType = misc:atom_to_binary(Type),
|
||||
[?SQL("insert into archive (username, timestamp, "
|
||||
"peer, bare_peer, xml, txt, kind, nick) "
|
||||
"values (%(SUser)s, %(TStmp)d, %(LPeer)s, "
|
||||
"%(BarePeer)s, %(XML)s, %(Body)s, %(SType)s, "
|
||||
"%(Nick)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
|
@ -25,7 +25,6 @@
|
||||
|
||||
-module(mod_metrics).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
-author('christophe.romain@process-one.net').
|
||||
-behaviour(gen_mod).
|
||||
|
||||
@ -33,8 +32,7 @@
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1,
|
||||
depends/2, reload/3]).
|
||||
-export([start/2, stop/1, mod_opt_type/1, depends/2, reload/3]).
|
||||
|
||||
-export([offline_message_hook/1,
|
||||
sm_register_connection_hook/3, sm_remove_connection_hook/3,
|
||||
@ -42,6 +40,9 @@
|
||||
s2s_send_packet/1, s2s_receive_packet/1,
|
||||
remove_user/2, register_user/2]).
|
||||
|
||||
-define(SOCKET_NAME, mod_metrics_udp_socket).
|
||||
-define(SOCKET_REGISTER_RETRIES, 10).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
@ -126,20 +127,20 @@ register_user(_User, Server) ->
|
||||
%%====================================================================
|
||||
|
||||
push(Host, Probe) ->
|
||||
spawn(?MODULE, send_metrics, [Host, Probe, {127,0,0,1}, 11111]).
|
||||
IP = gen_mod:get_module_opt(Host, ?MODULE, ip, {127,0,0,1}),
|
||||
Port = gen_mod:get_module_opt(Host, ?MODULE, port, 11111),
|
||||
send_metrics(Host, Probe, IP, Port).
|
||||
|
||||
send_metrics(Host, Probe, Peer, Port) ->
|
||||
% our default metrics handler is https://github.com/processone/grapherl
|
||||
% grapherl metrics are named first with service domain, then nodename
|
||||
% and name of the data itself, followed by type timestamp and value
|
||||
% example => process-one.net/xmpp-1.user_receive_packet:c/1441784958:1
|
||||
[_, NodeId] = str:tokens(misc:atom_to_binary(node()), <<"@">>),
|
||||
[Node | _] = str:tokens(NodeId, <<".">>),
|
||||
[_, FQDN] = binary:split(misc:atom_to_binary(node()), <<"@">>),
|
||||
[Node|_] = binary:split(FQDN, <<".">>),
|
||||
BaseId = <<Host/binary, "/", Node/binary, ".">>,
|
||||
DateTime = erlang:universaltime(),
|
||||
UnixTime = calendar:datetime_to_gregorian_seconds(DateTime) - 62167219200,
|
||||
TS = integer_to_binary(UnixTime),
|
||||
case gen_udp:open(0) of
|
||||
TS = integer_to_binary(p1_time_compat:system_time(seconds)),
|
||||
case get_socket(?SOCKET_REGISTER_RETRIES) of
|
||||
{ok, Socket} ->
|
||||
case Probe of
|
||||
{Key, Val} ->
|
||||
@ -151,14 +152,38 @@ send_metrics(Host, Probe, Peer, Port) ->
|
||||
Data = <<BaseId/binary, (misc:atom_to_binary(Key))/binary,
|
||||
":c/", TS/binary, ":1">>,
|
||||
gen_udp:send(Socket, Peer, Port, Data)
|
||||
end,
|
||||
gen_udp:close(Socket);
|
||||
Error ->
|
||||
?WARNING_MSG("can not open udp socket to grapherl: ~p", [Error])
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
opt_type(_) ->
|
||||
[].
|
||||
get_socket(N) ->
|
||||
case whereis(?SOCKET_NAME) of
|
||||
undefined ->
|
||||
case gen_udp:open(0) of
|
||||
{ok, Socket} ->
|
||||
try register(?SOCKET_NAME, Socket) of
|
||||
true -> {ok, Socket}
|
||||
catch _:badarg when N > 1 ->
|
||||
gen_udp:close(Socket),
|
||||
get_socket(N-1)
|
||||
end;
|
||||
{error, Reason} = Err ->
|
||||
?ERROR_MSG("can not open udp socket to grapherl: ~s",
|
||||
[inet:format_error(Reason)]),
|
||||
Err
|
||||
end;
|
||||
Socket ->
|
||||
{ok, Socket}
|
||||
end.
|
||||
|
||||
mod_opt_type(ip) ->
|
||||
fun(S) ->
|
||||
{ok, IP} = inet:parse_ipv4_address(
|
||||
binary_to_list(iolist_to_binary(S))),
|
||||
IP
|
||||
end;
|
||||
mod_opt_type(port) ->
|
||||
fun(I) when is_integer(I), I>0, I<65536 -> I end;
|
||||
mod_opt_type(_) ->
|
||||
[].
|
||||
[ip, port].
|
||||
|
@ -84,14 +84,18 @@ get_commands_spec() ->
|
||||
desc = "List existing rooms ('global' to get all vhosts)",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = muc_online_rooms,
|
||||
args_desc = ["Server domain where the MUC service is, or 'global' for all"],
|
||||
args_example = ["example.com"],
|
||||
result_desc = "List of rooms",
|
||||
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
|
||||
args = [{host, binary}],
|
||||
result = {rooms, {list, {room, string}}}},
|
||||
#ejabberd_commands{name = muc_register_nick, tags = [muc],
|
||||
desc = "Register a nick in the MUC service",
|
||||
longdesc = "Provide the nick, the user JID and the MUC service",
|
||||
module = ?MODULE, function = muc_register_nick,
|
||||
args_desc = ["Nick", "User JID", "MUC service"],
|
||||
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"muc.example.org">>],
|
||||
args = [{nick, binary}, {jid, binary}, {domain, binary}],
|
||||
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"conference.example.org">>],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
|
||||
desc = "Unregister the nick in the MUC service",
|
||||
@ -102,23 +106,31 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = create_room, tags = [muc_room],
|
||||
desc = "Create a MUC room name@service in host",
|
||||
module = ?MODULE, function = create_room,
|
||||
args_desc = ["Room name", "MUC service", "Server host"],
|
||||
args_example = ["room1", "muc.example.com", "example.com"],
|
||||
args = [{name, binary}, {service, binary},
|
||||
{host, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = destroy_room, tags = [muc_room],
|
||||
desc = "Destroy a MUC room",
|
||||
module = ?MODULE, function = destroy_room,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
args_example = ["room1", "muc.example.com"],
|
||||
args = [{name, binary}, {service, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = create_rooms_file, tags = [muc],
|
||||
desc = "Create the rooms indicated in file",
|
||||
longdesc = "Provide one room JID per line. Rooms will be created after restart.",
|
||||
module = ?MODULE, function = create_rooms_file,
|
||||
args_desc = ["Path to the text file with one room JID per line"],
|
||||
args_example = ["/home/ejabberd/rooms.txt"],
|
||||
args = [{file, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = create_room_with_opts, tags = [muc_room],
|
||||
desc = "Create a MUC room name@service in host with given options",
|
||||
module = ?MODULE, function = create_room_with_opts,
|
||||
args_desc = ["Room name", "MUC service", "Server host", "List of options"],
|
||||
args_example = ["room1", "muc.example.com", "localhost", [{"members_only","true"}]],
|
||||
args = [{name, binary}, {service, binary},
|
||||
{host, binary},
|
||||
{options, {list,
|
||||
@ -132,28 +144,45 @@ get_commands_spec() ->
|
||||
desc = "Destroy the rooms indicated in file",
|
||||
longdesc = "Provide one room JID per line.",
|
||||
module = ?MODULE, function = destroy_rooms_file,
|
||||
args_desc = ["Path to the text file with one room JID per line"],
|
||||
args_example = ["/home/ejabberd/rooms.txt"],
|
||||
args = [{file, string}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = rooms_unused_list, tags = [muc],
|
||||
desc = "List the rooms that are unused for many days in host",
|
||||
module = ?MODULE, function = rooms_unused_list,
|
||||
args_desc = ["Server host", "Number of days"],
|
||||
args_example = ["example.com", 31],
|
||||
result_desc = "List of unused rooms",
|
||||
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
|
||||
args = [{host, binary}, {days, integer}],
|
||||
result = {rooms, {list, {room, string}}}},
|
||||
#ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
|
||||
desc = "Destroy the rooms that are unused for many days in host",
|
||||
module = ?MODULE, function = rooms_unused_destroy,
|
||||
args_desc = ["Server host", "Number of days"],
|
||||
args_example = ["example.com", 31],
|
||||
result_desc = "List of unused rooms that has been destroyed",
|
||||
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
|
||||
args = [{host, binary}, {days, integer}],
|
||||
result = {rooms, {list, {room, string}}}},
|
||||
|
||||
#ejabberd_commands{name = get_user_rooms, tags = [muc],
|
||||
desc = "Get the list of rooms where this user is occupant",
|
||||
module = ?MODULE, function = get_user_rooms,
|
||||
args_desc = ["Username", "Server host"],
|
||||
args_example = ["tom", "example.com"],
|
||||
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
|
||||
args = [{user, binary}, {host, binary}],
|
||||
result = {rooms, {list, {room, string}}}},
|
||||
|
||||
#ejabberd_commands{name = get_room_occupants, tags = [muc_room],
|
||||
desc = "Get the list of occupants of a MUC room",
|
||||
module = ?MODULE, function = get_room_occupants,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
args_example = ["room1", "muc.example.com"],
|
||||
result_desc = "The list of occupants with JID, nick and affiliation",
|
||||
result_example = [{"user1@example.com/psi", "User 1", "owner"}],
|
||||
args = [{name, binary}, {service, binary}],
|
||||
result = {occupants, {list,
|
||||
{occupant, {tuple,
|
||||
@ -166,6 +195,10 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
|
||||
desc = "Get the number of occupants of a MUC room",
|
||||
module = ?MODULE, function = get_room_occupants_number,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
args_example = ["room1", "muc.example.com"],
|
||||
result_desc = "Number of room occupants",
|
||||
result_example = 7,
|
||||
args = [{name, binary}, {service, binary}],
|
||||
result = {occupants, integer}},
|
||||
|
||||
@ -173,18 +206,27 @@ get_commands_spec() ->
|
||||
desc = "Send a direct invitation to several destinations",
|
||||
longdesc = "Password and Message can also be: none. Users JIDs are separated with : ",
|
||||
module = ?MODULE, function = send_direct_invitation,
|
||||
args_desc = ["Room name", "MUC service", "Password, or none",
|
||||
"Reason text, or none", "Users JIDs separated with : characters"],
|
||||
args_example = ["room1", "muc.example.com", none, none, "user2@localhost:user3@example.com"],
|
||||
args = [{name, binary}, {service, binary}, {password, binary}, {reason, binary}, {users, binary}],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = change_room_option, tags = [muc_room],
|
||||
desc = "Change an option in a MUC room",
|
||||
module = ?MODULE, function = change_room_option,
|
||||
args_desc = ["Room name", "MUC service", "Option name", "Value to assign"],
|
||||
args_example = ["room1", "muc.example.com", "members_only", "true"],
|
||||
args = [{name, binary}, {service, binary},
|
||||
{option, binary}, {value, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_room_options, tags = [muc_room],
|
||||
desc = "Get options from a MUC room",
|
||||
module = ?MODULE, function = get_room_options,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
args_example = ["room1", "muc.example.com"],
|
||||
result_desc = "List of room options tuples with name and value",
|
||||
result_example = [{"members_only", "true"}],
|
||||
args = [{name, binary}, {service, binary}],
|
||||
result = {options, {list,
|
||||
{option, {tuple,
|
||||
@ -195,28 +237,47 @@ get_commands_spec() ->
|
||||
#ejabberd_commands{name = subscribe_room, tags = [muc_room],
|
||||
desc = "Subscribe to a MUC conference",
|
||||
module = ?MODULE, function = subscribe_room,
|
||||
args_desc = ["Full JID, including some resource", "a user's nick",
|
||||
"the room to subscribe", "nodes separated by commas: ,"],
|
||||
args_example = ["tom@localhost/dummy", "Tom", "room1@conference.localhost",
|
||||
"urn:xmpp:mucsub:nodes:messages,urn:xmpp:mucsub:nodes:affiliations"],
|
||||
result_desc = "The list of nodes that has subscribed",
|
||||
result_example = ["urn:xmpp:mucsub:nodes:messages",
|
||||
"urn:xmpp:mucsub:nodes:affiliations"],
|
||||
args = [{user, binary}, {nick, binary}, {room, binary},
|
||||
{nodes, binary}],
|
||||
result = {nodes, {list, {node, string}}}},
|
||||
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
|
||||
desc = "Unsubscribe from a MUC conference",
|
||||
module = ?MODULE, function = unsubscribe_room,
|
||||
args_desc = ["User JID", "the room to subscribe"],
|
||||
args_example = ["tom@localhost", "room1@conference.localhost"],
|
||||
args = [{user, binary}, {room, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_subscribers, tags = [muc_room],
|
||||
desc = "List subscribers of a MUC conference",
|
||||
module = ?MODULE, function = get_subscribers,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
args_example = ["room1", "muc.example.com"],
|
||||
result_desc = "The list of users that are subscribed to that room",
|
||||
result_example = ["user2@example.com", "user3@example.com"],
|
||||
args = [{name, binary}, {service, binary}],
|
||||
result = {subscribers, {list, {jid, string}}}},
|
||||
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
|
||||
desc = "Change an affiliation in a MUC room",
|
||||
module = ?MODULE, function = set_room_affiliation,
|
||||
args_desc = ["Room name", "MUC service", "User JID", "Affiliation to set"],
|
||||
args_example = ["room1", "muc.example.com", "user2@example.com", "member"],
|
||||
args = [{name, binary}, {service, binary},
|
||||
{jid, binary}, {affiliation, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
|
||||
desc = "Get the list of affiliations of a MUC room",
|
||||
module = ?MODULE, function = get_room_affiliations,
|
||||
args_desc = ["Room name", "MUC service"],
|
||||
args_example = ["room1", "muc.example.com"],
|
||||
result_desc = "The list of affiliations with username, domain, affiliation and reason",
|
||||
result_example = [{"user1", "example.com", "member"}],
|
||||
args = [{name, binary}, {service, binary}],
|
||||
result = {affiliations, {list,
|
||||
{affiliation, {tuple,
|
||||
@ -237,7 +298,7 @@ muc_online_rooms(ServerHost) ->
|
||||
Hosts = find_hosts(ServerHost),
|
||||
lists:flatmap(
|
||||
fun(Host) ->
|
||||
[{<<Name/binary, "@", Host/binary>>}
|
||||
[<<Name/binary, "@", Host/binary>>
|
||||
|| {Name, _, _} <- mod_muc:get_online_rooms(Host)]
|
||||
end, Hosts).
|
||||
|
||||
@ -337,7 +398,7 @@ web_page_host(_, Host,
|
||||
q = Q,
|
||||
lang = Lang} = _Request) ->
|
||||
Sort_query = get_sort_query(Q),
|
||||
Res = make_rooms_page(find_host(Host), Lang, Sort_query),
|
||||
Res = make_rooms_page(Host, Lang, Sort_query),
|
||||
{stop, Res};
|
||||
web_page_host(Acc, _, _) -> Acc.
|
||||
|
||||
@ -421,11 +482,11 @@ build_info_room({Name, Host, Pid}) ->
|
||||
|
||||
History = (S#state.history)#lqueue.queue,
|
||||
Ts_last_message =
|
||||
case queue:is_empty(History) of
|
||||
case p1_queue:is_empty(History) of
|
||||
true ->
|
||||
<<"A long time ago">>;
|
||||
false ->
|
||||
Last_message1 = queue:last(History),
|
||||
Last_message1 = get_queue_last(History),
|
||||
{_, _, _, Ts_last, _} = Last_message1,
|
||||
xmpp_util:encode_timestamp(Ts_last)
|
||||
end,
|
||||
@ -439,6 +500,10 @@ build_info_room({Name, Host, Pid}) ->
|
||||
Just_created,
|
||||
Title}.
|
||||
|
||||
get_queue_last(Queue) ->
|
||||
List = p1_queue:to_list(Queue),
|
||||
lists:last(List).
|
||||
|
||||
prepare_rooms_infos(Rooms) ->
|
||||
[prepare_room_info(Room) || Room <- Rooms].
|
||||
prepare_room_info(Room_info) ->
|
||||
@ -675,11 +740,11 @@ decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
|
||||
History = (S#state.history)#lqueue.queue,
|
||||
Ts_now = calendar:universal_time(),
|
||||
Ts_uptime = uptime_seconds(),
|
||||
{Has_hist, Last} = case queue:is_empty(History) of
|
||||
{Has_hist, Last} = case p1_queue:is_empty(History) of
|
||||
true ->
|
||||
{false, Ts_uptime};
|
||||
false ->
|
||||
Last_message = queue:last(History),
|
||||
Last_message = get_queue_last(History),
|
||||
{_, _, _, Ts_last, _} = Last_message,
|
||||
Ts_diff =
|
||||
calendar:datetime_to_gregorian_seconds(Ts_now)
|
||||
@ -832,6 +897,7 @@ format_room_option(OptionString, ValueString) ->
|
||||
password -> ValueString;
|
||||
subject ->ValueString;
|
||||
subject_author ->ValueString;
|
||||
presence_broadcast ->misc:expr_to_term(ValueString);
|
||||
max_users -> binary_to_integer(ValueString);
|
||||
_ -> misc:binary_to_atom(ValueString)
|
||||
end,
|
||||
@ -872,6 +938,7 @@ change_option(Option, Value, Config) ->
|
||||
password -> Config#config{password = Value};
|
||||
password_protected -> Config#config{password_protected = Value};
|
||||
persistent -> Config#config{persistent = Value};
|
||||
presence_broadcast -> Config#config{presence_broadcast = Value};
|
||||
public -> Config#config{public = Value};
|
||||
public_list -> Config#config{public_list = Value};
|
||||
title -> Config#config{title = Value};
|
||||
|
@ -500,7 +500,7 @@ make_dir_rec(Dir) ->
|
||||
%% {ok, F1}=file:open("valid-xhtml10.png", [read]).
|
||||
%% {ok, F1b}=file:read(F1, 1000000).
|
||||
%% c("../../ejabberd/src/jlib.erl").
|
||||
%% misc:encode_base64(F1b).
|
||||
%% base64:encode(F1b).
|
||||
|
||||
image_base64(<<"powered-by-erlang.png">>) ->
|
||||
<<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoA"
|
||||
@ -676,7 +676,7 @@ create_image_files(Images_dir) ->
|
||||
lists:foreach(fun (Filename) ->
|
||||
Filename_full = fjoin([Images_dir, Filename]),
|
||||
{ok, F} = file:open(Filename_full, [write]),
|
||||
Image = misc:decode_base64(image_base64(Filename)),
|
||||
Image = base64:decode(image_base64(Filename)),
|
||||
io:format(F, <<"~s">>, [Image]),
|
||||
file:close(F)
|
||||
end,
|
||||
@ -1170,7 +1170,7 @@ has_no_permanent_store_hint(Packet) ->
|
||||
|
||||
mod_opt_type(access_log) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(cssfile) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(cssfile) -> fun misc:try_read_file/1;
|
||||
mod_opt_type(dirname) ->
|
||||
fun (room_jid) -> room_jid;
|
||||
(room_name) -> room_name
|
||||
|
@ -1152,13 +1152,13 @@ handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) ->
|
||||
-spec stanzaid_pack(binary(), binary()) -> binary().
|
||||
stanzaid_pack(OriginalId, Resource) ->
|
||||
<<"berd",
|
||||
(misc:encode_base64(<<"ejab\000",
|
||||
(base64:encode(<<"ejab\000",
|
||||
OriginalId/binary, "\000",
|
||||
Resource/binary>>))/binary>>.
|
||||
|
||||
-spec stanzaid_unpack(binary()) -> {binary(), binary()}.
|
||||
stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
|
||||
StanzaId = misc:decode_base64(StanzaIdBase64),
|
||||
StanzaId = base64:decode(StanzaIdBase64),
|
||||
[<<"ejab">>, OriginalId, Resource] =
|
||||
str:tokens(StanzaId, <<"\000">>),
|
||||
{OriginalId, Resource}.
|
||||
|
@ -33,14 +33,13 @@
|
||||
-protocol({xep, 160, '1.0'}).
|
||||
-protocol({xep, 334, '0.2'}).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
reload/3,
|
||||
store_packet/1,
|
||||
store_offline_msg/5,
|
||||
store_offline_msg/1,
|
||||
c2s_self_presence/1,
|
||||
get_sm_features/5,
|
||||
get_sm_identity/5,
|
||||
@ -64,9 +63,7 @@
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3,
|
||||
mod_opt_type/1, depends/2]).
|
||||
-export([mod_opt_type/1, depends/2]).
|
||||
|
||||
-deprecated({get_queue_length,2}).
|
||||
|
||||
@ -86,14 +83,11 @@
|
||||
%% default value for the maximum number of user messages
|
||||
-define(MAX_USER_MESSAGES, infinity).
|
||||
|
||||
-type us() :: {binary(), binary()}.
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(#offline_msg{}) -> ok.
|
||||
-callback store_messages(binary(), us(), [#offline_msg{}],
|
||||
non_neg_integer(), non_neg_integer()) ->
|
||||
{atomic, any()}.
|
||||
-callback store_message(#offline_msg{}) -> ok | {error, any()}.
|
||||
-callback pop_messages(binary(), binary()) ->
|
||||
{ok, [#offline_msg{}]} | {error, any()}.
|
||||
-callback remove_expired_messages(binary()) -> {atomic, any()}.
|
||||
@ -108,25 +102,10 @@
|
||||
-callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
|
||||
-callback count_messages(binary(), binary()) -> non_neg_integer().
|
||||
|
||||
start(Host, Opts) ->
|
||||
gen_mod:start_child(?MODULE, Host, Opts).
|
||||
|
||||
stop(Host) ->
|
||||
gen_mod:stop_child(?MODULE, Host).
|
||||
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:cast(Proc, {reload, NewOpts, OldOpts}).
|
||||
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
start(Host, Opts) ->
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
@ -153,64 +132,9 @@ init([Host, Opts]) ->
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
|
||||
?MODULE, handle_offline_query, IQDisc),
|
||||
AccessMaxOfflineMsgs =
|
||||
gen_mod:get_opt(access_max_user_messages, Opts,
|
||||
max_user_offline_messages),
|
||||
{ok,
|
||||
#state{host = Host,
|
||||
access_max_offline_messages = AccessMaxOfflineMsgs}}.
|
||||
?MODULE, handle_offline_query, IQDisc).
|
||||
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State}.
|
||||
|
||||
handle_cast({reload, NewOpts, OldOpts}, #state{host = Host} = State) ->
|
||||
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
|
||||
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
|
||||
if NewMod /= OldMod ->
|
||||
NewMod:init(Host, NewOpts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
|
||||
{false, IQDisc, _} ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
|
||||
?MODULE, handle_offline_query, IQDisc);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
case gen_mod:is_equal_opt(access_max_user_messages, NewOpts, OldOpts,
|
||||
max_user_offline_messages) of
|
||||
{false, AccessMaxOfflineMsgs, _} ->
|
||||
{noreply,
|
||||
State#state{access_max_offline_messages = AccessMaxOfflineMsgs}};
|
||||
true ->
|
||||
{noreply, State}
|
||||
end;
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
handle_info(#offline_msg{us = UserServer} = Msg, State) ->
|
||||
#state{host = Host,
|
||||
access_max_offline_messages = AccessMaxOfflineMsgs} = State,
|
||||
DBType = gen_mod:db_type(Host, ?MODULE),
|
||||
Msgs = receive_all(UserServer, [Msg], DBType),
|
||||
Len = length(Msgs),
|
||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
||||
UserServer, Host),
|
||||
store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
?ERROR_MSG("got unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
Host = State#state.host,
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
|
||||
@ -229,41 +153,48 @@ terminate(_Reason, State) ->
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE),
|
||||
ok.
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE).
|
||||
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) ->
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
case Mod:store_messages(Host, US, Msgs, Len, MaxOfflineMsgs) of
|
||||
{atomic, discard} ->
|
||||
discard_warn_sender(Msgs);
|
||||
_ ->
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
|
||||
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
|
||||
if NewMod /= OldMod ->
|
||||
NewMod:init(Host, NewOpts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
|
||||
{false, IQDisc, _} ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
|
||||
?MODULE, handle_offline_query, IQDisc);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
get_max_user_messages(AccessRule, {User, Server}, Host) ->
|
||||
case acl:match_rule(
|
||||
Host, AccessRule, jid:make(User, Server)) of
|
||||
-spec store_offline_msg(#offline_msg{}) -> ok | {error, full | any()}.
|
||||
store_offline_msg(#offline_msg{us = {User, Server}} = Msg) ->
|
||||
Mod = gen_mod:db_mod(Server, ?MODULE),
|
||||
case get_max_user_messages(User, Server) of
|
||||
infinity ->
|
||||
Mod:store_message(Msg);
|
||||
Limit ->
|
||||
Num = count_offline_messages(User, Server),
|
||||
if Num < Limit ->
|
||||
Mod:store_message(Msg);
|
||||
true ->
|
||||
{error, full}
|
||||
end
|
||||
end.
|
||||
|
||||
get_max_user_messages(User, Server) ->
|
||||
Access = gen_mod:get_module_opt(Server, ?MODULE, access_max_user_messages,
|
||||
max_user_offline_messages),
|
||||
case acl:match_rule(Server, Access, jid:make(User, Server)) of
|
||||
Max when is_integer(Max) -> Max;
|
||||
infinity -> infinity;
|
||||
_ -> ?MAX_USER_MESSAGES
|
||||
end.
|
||||
|
||||
receive_all(US, Msgs, DBType) ->
|
||||
receive
|
||||
#offline_msg{us = US} = Msg ->
|
||||
receive_all(US, [Msg | Msgs], DBType)
|
||||
after 0 ->
|
||||
case DBType of
|
||||
mnesia -> Msgs;
|
||||
sql -> lists:reverse(Msgs);
|
||||
riak -> Msgs
|
||||
end
|
||||
end.
|
||||
|
||||
get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
|
||||
Feats = case Acc of
|
||||
{result, I} -> I;
|
||||
@ -484,14 +415,19 @@ store_packet({_Action, #message{from = From, to = To} = Packet} = Acc) ->
|
||||
NewPacket ->
|
||||
TimeStamp = p1_time_compat:timestamp(),
|
||||
Expire = find_x_expire(TimeStamp, NewPacket),
|
||||
gen_mod:get_module_proc(To#jid.lserver, ?MODULE) !
|
||||
#offline_msg{us = {LUser, LServer},
|
||||
timestamp = TimeStamp,
|
||||
expire = Expire,
|
||||
from = From,
|
||||
to = To,
|
||||
packet = NewPacket},
|
||||
{offlined, NewPacket}
|
||||
OffMsg = #offline_msg{us = {LUser, LServer},
|
||||
timestamp = TimeStamp,
|
||||
expire = Expire,
|
||||
from = From,
|
||||
to = To,
|
||||
packet = NewPacket},
|
||||
case store_offline_msg(OffMsg) of
|
||||
ok ->
|
||||
{offlined, NewPacket};
|
||||
{error, Reason} ->
|
||||
discard_warn_sender(Packet, Reason),
|
||||
stop
|
||||
end
|
||||
end;
|
||||
_ -> Acc
|
||||
end;
|
||||
@ -635,15 +571,18 @@ remove_user(User, Server) ->
|
||||
%% Helper functions:
|
||||
|
||||
%% Warn senders that their messages have been discarded:
|
||||
discard_warn_sender(Msgs) ->
|
||||
lists:foreach(
|
||||
fun(#offline_msg{packet = Packet}) ->
|
||||
ErrText = <<"Your contact offline message queue is "
|
||||
"full. The message has been discarded.">>,
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_resource_constraint(ErrText, Lang),
|
||||
ejabberd_router:route_error(Packet, Err)
|
||||
end, Msgs).
|
||||
-spec discard_warn_sender(message(), full | any()) -> ok.
|
||||
discard_warn_sender(Packet, full) ->
|
||||
ErrText = <<"Your contact offline message queue is "
|
||||
"full. The message has been discarded.">>,
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_resource_constraint(ErrText, Lang),
|
||||
ejabberd_router:route_error(Packet, Err);
|
||||
discard_warn_sender(Packet, _) ->
|
||||
ErrText = <<"Database failure">>,
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_internal_server_error(ErrText, Lang),
|
||||
ejabberd_router:route_error(Packet, Err).
|
||||
|
||||
webadmin_page(_, Host,
|
||||
#request{us = _US, path = [<<"user">>, U, <<"queue">>],
|
||||
@ -790,11 +729,7 @@ get_queue_length(LUser, LServer) ->
|
||||
count_offline_messages(LUser, LServer).
|
||||
|
||||
get_messages_subset(User, Host, MsgsAll) ->
|
||||
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
|
||||
max_user_offline_messages),
|
||||
MaxOfflineMsgs = case get_max_user_messages(Access,
|
||||
User, Host)
|
||||
of
|
||||
MaxOfflineMsgs = case get_max_user_messages(User, Host) of
|
||||
Number when is_integer(Number) -> Number;
|
||||
_ -> 100
|
||||
end,
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
-behaviour(mod_offline).
|
||||
|
||||
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
|
||||
-export([init/2, store_message/1, pop_messages/2, remove_expired_messages/1,
|
||||
remove_old_messages/2, remove_user/2, read_message_headers/2,
|
||||
read_message/3, remove_message/3, read_all_messages/2,
|
||||
remove_all_messages/2, count_messages/2, import/1]).
|
||||
@ -36,8 +36,6 @@
|
||||
-include("mod_offline.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
@ -46,26 +44,9 @@ init(_Host, _Opts) ->
|
||||
[{disc_only_copies, [node()]}, {type, bag},
|
||||
{attributes, record_info(fields, offline_msg)}]).
|
||||
|
||||
store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) ->
|
||||
F = fun () ->
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + count_mnesia_records(US);
|
||||
true -> 0
|
||||
end,
|
||||
if Count > MaxOfflineMsgs -> discard;
|
||||
true ->
|
||||
if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
|
||||
mnesia:write_lock_table(offline_msg);
|
||||
true -> ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(#offline_msg{packet = Pkt} = M) ->
|
||||
El = xmpp:encode(Pkt),
|
||||
mnesia:write(M#offline_msg{packet = El})
|
||||
end, Msgs)
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
store_message(#offline_msg{packet = Pkt} = OffMsg) ->
|
||||
El = xmpp:encode(Pkt),
|
||||
mnesia:dirty_write(OffMsg#offline_msg{packet = El}).
|
||||
|
||||
pop_messages(LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
-behaviour(mod_offline).
|
||||
|
||||
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
|
||||
-export([init/2, store_message/1, pop_messages/2, remove_expired_messages/1,
|
||||
remove_old_messages/2, remove_user/2, read_message_headers/2,
|
||||
read_message/3, remove_message/3, read_all_messages/2,
|
||||
remove_all_messages/2, count_messages/2, import/1]).
|
||||
@ -40,31 +40,11 @@
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) ->
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + count_messages(User, Host);
|
||||
true -> 0
|
||||
end,
|
||||
if
|
||||
Count > MaxOfflineMsgs ->
|
||||
{atomic, discard};
|
||||
true ->
|
||||
try
|
||||
lists:foreach(
|
||||
fun(#offline_msg{us = US,
|
||||
packet = Pkt,
|
||||
timestamp = TS} = M) ->
|
||||
El = xmpp:encode(Pkt),
|
||||
ok = ejabberd_riak:put(
|
||||
M#offline_msg{packet = El},
|
||||
offline_msg_schema(),
|
||||
[{i, TS}, {'2i', [{<<"us">>, US}]}])
|
||||
end, Msgs),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end
|
||||
end.
|
||||
store_message(#offline_msg{us = US, packet = Pkt, timestamp = TS} = M) ->
|
||||
El = xmpp:encode(Pkt),
|
||||
ejabberd_riak:put(M#offline_msg{packet = El},
|
||||
offline_msg_schema(),
|
||||
[{i, TS}, {'2i', [{<<"us">>, US}]}]).
|
||||
|
||||
pop_messages(LUser, LServer) ->
|
||||
case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
-behaviour(mod_offline).
|
||||
|
||||
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
|
||||
-export([init/2, store_message/1, pop_messages/2, remove_expired_messages/1,
|
||||
remove_old_messages/2, remove_user/2, read_message_headers/2,
|
||||
read_message/3, remove_message/3, read_all_messages/2,
|
||||
remove_all_messages/2, count_messages/2, import/1, export/1]).
|
||||
@ -44,34 +44,28 @@
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + count_messages(User, Host);
|
||||
true -> 0
|
||||
end,
|
||||
if Count > MaxOfflineMsgs -> {atomic, discard};
|
||||
true ->
|
||||
Query = lists:map(
|
||||
fun(M) ->
|
||||
LUser = (M#offline_msg.to)#jid.luser,
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
Packet = xmpp:set_from_to(
|
||||
M#offline_msg.packet, From, To),
|
||||
NewPacket = xmpp_util:add_delay_info(
|
||||
Packet, jid:make(Host),
|
||||
M#offline_msg.timestamp,
|
||||
<<"Offline Storage">>),
|
||||
XML = fxml:element_to_binary(
|
||||
xmpp:encode(NewPacket)),
|
||||
sql_queries:add_spool_sql(LUser, XML)
|
||||
end,
|
||||
Msgs),
|
||||
sql_queries:add_spool(Host, Query)
|
||||
store_message(#offline_msg{us = {LUser, LServer}} = M) ->
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
Packet = xmpp:set_from_to(M#offline_msg.packet, From, To),
|
||||
NewPacket = xmpp_util:add_delay_info(
|
||||
Packet, jid:make(LServer),
|
||||
M#offline_msg.timestamp,
|
||||
<<"Offline Storage">>),
|
||||
XML = fxml:element_to_binary(
|
||||
xmpp:encode(NewPacket)),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("insert into spool(username, xml) values "
|
||||
"(%(LUser)s, %(XML)s)")) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
_ ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
pop_messages(LUser, LServer) ->
|
||||
case sql_queries:get_and_del_spool_msg_t(LServer, LUser) of
|
||||
case get_and_del_spool_msg_t(LServer, LUser) of
|
||||
{atomic, {selected, Rs}} ->
|
||||
{ok, lists:flatmap(
|
||||
fun({_, XML}) ->
|
||||
@ -91,12 +85,12 @@ remove_expired_messages(_LServer) ->
|
||||
{atomic, ok}.
|
||||
|
||||
remove_old_messages(Days, LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"DELETE FROM spool"
|
||||
" WHERE created_at < "
|
||||
"NOW() - INTERVAL '">>,
|
||||
integer_to_list(Days), <<"' DAY;">>]) of
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
[<<"DELETE FROM spool"
|
||||
" WHERE created_at < "
|
||||
"NOW() - INTERVAL '">>,
|
||||
integer_to_list(Days), <<"' DAY;">>]) of
|
||||
{updated, N} ->
|
||||
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
|
||||
_Error ->
|
||||
@ -105,13 +99,15 @@ remove_old_messages(Days, LServer) ->
|
||||
{atomic, ok}.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
sql_queries:del_spool_msg(LServer, LUser).
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from spool where username=%(LUser)s")).
|
||||
|
||||
read_message_headers(LUser, LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(xml)s, @(seq)d from spool"
|
||||
" where username=%(LUser)s order by seq")) of
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(xml)s, @(seq)d from spool"
|
||||
" where username=%(LUser)s order by seq")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun({XML, Seq}) ->
|
||||
@ -153,10 +149,10 @@ remove_message(LUser, LServer, Seq) ->
|
||||
ok.
|
||||
|
||||
read_all_messages(LUser, LServer) ->
|
||||
case catch ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(xml)s from spool where "
|
||||
"username=%(LUser)s order by seq")) of
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(xml)s from spool where "
|
||||
"username=%(LUser)s order by seq")) of
|
||||
{selected, Rs} ->
|
||||
lists:flatmap(
|
||||
fun({XML}) ->
|
||||
@ -170,7 +166,7 @@ read_all_messages(LUser, LServer) ->
|
||||
end.
|
||||
|
||||
remove_all_messages(LUser, LServer) ->
|
||||
sql_queries:del_spool_msg(LServer, LUser),
|
||||
remove_user(LUser, LServer),
|
||||
{atomic, ok}.
|
||||
|
||||
count_messages(LUser, LServer) ->
|
||||
@ -241,3 +237,15 @@ el_to_offline_msg(El) ->
|
||||
?ERROR_MSG("failed to get 'from' JID from offline XML ~p", [El]),
|
||||
{error, bad_jid_from}
|
||||
end.
|
||||
|
||||
get_and_del_spool_msg_t(LServer, LUser) ->
|
||||
F = fun () ->
|
||||
Result =
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(username)s, @(xml)s from spool where "
|
||||
"username=%(LUser)s order by seq;")),
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from spool where username=%(LUser)s;")),
|
||||
Result
|
||||
end,
|
||||
ejabberd_sql:sql_transaction(LServer, F).
|
||||
|
@ -31,42 +31,51 @@
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, reload/3, process_iq/1, export/1, import_info/0,
|
||||
c2s_session_opened/1, c2s_copy_session/2, push_list_update/3,
|
||||
user_send_packet/1, user_receive_packet/1, disco_features/5,
|
||||
-export([start/2, stop/1, reload/3, process_iq/1, export/1,
|
||||
c2s_copy_session/2, push_list_update/2, disco_features/5,
|
||||
check_packet/4, remove_user/2, encode_list_item/1,
|
||||
is_list_needdb/1, import_start/2, import_stop/2,
|
||||
item_to_xml/1, get_user_lists/2, import/5,
|
||||
set_privacy_list/1, mod_opt_type/1, depends/2]).
|
||||
get_user_lists/2, get_user_list/3,
|
||||
set_list/1, set_list/4, set_default_list/3,
|
||||
user_send_packet/1, user_receive_packet/1,
|
||||
import_start/2, import_stop/2, import/5, import_info/0,
|
||||
mod_opt_type/1, depends/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
-define(PRIVACY_CACHE, privacy_cache).
|
||||
-define(PRIVACY_LIST_CACHE, privacy_list_cache).
|
||||
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
-callback init(binary(), gen_mod:opts()) -> any().
|
||||
-callback import(#privacy{}) -> ok.
|
||||
-callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error.
|
||||
-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found.
|
||||
-callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}.
|
||||
-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error.
|
||||
-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}.
|
||||
-callback set_privacy_list(#privacy{}) -> any().
|
||||
-callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}.
|
||||
-callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}.
|
||||
-callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error.
|
||||
-callback remove_user(binary(), binary()) -> any().
|
||||
-callback set_default(binary(), binary(), binary()) ->
|
||||
ok | {error, notfound | any()}.
|
||||
-callback unset_default(binary(), binary()) -> ok | {error, any()}.
|
||||
-callback remove_list(binary(), binary(), binary()) ->
|
||||
ok | {error, notfound | conflict | any()}.
|
||||
-callback remove_lists(binary(), binary()) -> ok | {error, any()}.
|
||||
-callback set_lists(#privacy{}) -> ok | {error, any()}.
|
||||
-callback set_list(binary(), binary(), binary(), listitem()) ->
|
||||
ok | {error, any()}.
|
||||
-callback get_list(binary(), binary(), binary() | default) ->
|
||||
{ok, {binary(), [listitem()]}} | error | {error, any()}.
|
||||
-callback get_lists(binary(), binary()) ->
|
||||
{ok, #privacy{}} | error | {error, any()}.
|
||||
-callback use_cache(binary()) -> boolean().
|
||||
-callback cache_nodes(binary()) -> [node()].
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
disco_features, 50),
|
||||
ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE,
|
||||
c2s_session_opened, 50),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
@ -83,8 +92,6 @@ start(Host, Opts) ->
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
|
||||
disco_features, 50),
|
||||
ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE,
|
||||
c2s_session_opened, 50),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
@ -106,6 +113,7 @@ reload(Host, NewOpts, OldOpts) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
init_cache(NewMod, Host, NewOpts),
|
||||
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
|
||||
{false, IQDisc, _} ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY,
|
||||
@ -162,47 +170,41 @@ process_iq_get(#iq{lang = Lang} = IQ) ->
|
||||
|
||||
-spec process_lists_get(iq()) -> iq().
|
||||
process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang,
|
||||
meta = #{privacy_active_list := Active}} = IQ) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_lists_get(LUser, LServer) of
|
||||
error ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
|
||||
{_Default, []} ->
|
||||
xmpp:make_iq_result(IQ, #privacy_query{});
|
||||
{Default, ListNames} ->
|
||||
lang = Lang} = IQ) ->
|
||||
case get_user_lists(LUser, LServer) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
Active = xmpp:get_meta(IQ, privacy_active_list, none),
|
||||
xmpp:make_iq_result(
|
||||
IQ,
|
||||
#privacy_query{active = Active,
|
||||
default = Default,
|
||||
lists = [#privacy_list{name = ListName}
|
||||
|| ListName <- ListNames]})
|
||||
IQ, #privacy_query{active = Active,
|
||||
default = Default,
|
||||
lists = [#privacy_list{name = Name}
|
||||
|| {Name, _} <- Lists]});
|
||||
error ->
|
||||
xmpp:make_iq_result(
|
||||
IQ, #privacy_query{active = none, default = none});
|
||||
{error, _} ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec process_list_get(iq(), binary()) -> iq().
|
||||
process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ, Name) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_list_get(LUser, LServer, Name) of
|
||||
error ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
|
||||
not_found ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
|
||||
Items ->
|
||||
LItems = lists:map(fun encode_list_item/1, Items),
|
||||
case get_user_list(LUser, LServer, Name) of
|
||||
{ok, {_, List}} ->
|
||||
Items = lists:map(fun encode_list_item/1, List),
|
||||
xmpp:make_iq_result(
|
||||
IQ,
|
||||
#privacy_query{
|
||||
lists = [#privacy_list{name = Name, items = LItems}]})
|
||||
#privacy_query{
|
||||
lists = [#privacy_list{name = Name, items = Items}]});
|
||||
error ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
|
||||
{error, _} ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec item_to_xml(listitem()) -> xmlel().
|
||||
item_to_xml(ListItem) ->
|
||||
xmpp:encode(encode_list_item(ListItem)).
|
||||
|
||||
-spec encode_list_item(listitem()) -> privacy_item().
|
||||
encode_list_item(#listitem{action = Action,
|
||||
order = Order,
|
||||
@ -283,69 +285,69 @@ process_iq_set(#iq{lang = Lang} = IQ) ->
|
||||
Txt = <<"No module is handling this query">>,
|
||||
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||
|
||||
-spec process_default_set(iq(), binary()) -> iq().
|
||||
-spec process_default_set(iq(), none | binary()) -> iq().
|
||||
process_default_set(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ, Value) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_default_set(LUser, LServer, Value) of
|
||||
{atomic, error} ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
|
||||
{atomic, not_found} ->
|
||||
case set_default_list(LUser, LServer, Value) of
|
||||
ok ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
{error, notfound} ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
|
||||
{atomic, ok} ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
Err ->
|
||||
?ERROR_MSG("failed to set default list '~s' for user ~s@~s: ~p",
|
||||
[Value, LUser, LServer, Err]),
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error())
|
||||
{error, _} ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec process_active_set(IQ, none | binary()) -> IQ.
|
||||
process_active_set(IQ, none) ->
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, #userlist{}));
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, none));
|
||||
process_active_set(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ, Name) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:process_active_set(LUser, LServer, Name) of
|
||||
case get_user_list(LUser, LServer, Name) of
|
||||
{ok, _} ->
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, Name));
|
||||
error ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
|
||||
Items ->
|
||||
NeedDb = is_list_needdb(Items),
|
||||
List = #userlist{name = Name, list = Items, needdb = NeedDb},
|
||||
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, List))
|
||||
end.
|
||||
|
||||
-spec set_privacy_list(privacy()) -> any().
|
||||
set_privacy_list(#privacy{us = {_, LServer}} = Privacy) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:set_privacy_list(Privacy).
|
||||
|
||||
-spec process_lists_set(iq(), binary(), [privacy_item()]) -> iq().
|
||||
process_lists_set(#iq{meta = #{privacy_active_list := Name},
|
||||
lang = Lang} = IQ, Name, []) ->
|
||||
Txt = <<"Cannot remove active list">>,
|
||||
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
|
||||
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
|
||||
lang = Lang} = IQ, Name, []) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:remove_privacy_list(LUser, LServer, Name) of
|
||||
{atomic, conflict} ->
|
||||
Txt = <<"Cannot remove default list">>,
|
||||
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
|
||||
{atomic, not_found} ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
|
||||
{atomic, ok} ->
|
||||
push_list_update(From, #userlist{name = Name}, Name),
|
||||
xmpp:make_iq_result(IQ);
|
||||
Err ->
|
||||
?ERROR_MSG("failed to remove privacy list '~s' for user ~s@~s: ~p",
|
||||
[Name, LUser, LServer, Err]),
|
||||
{error, _} ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end.
|
||||
|
||||
-spec set_list(privacy()) -> ok | {error, any()}.
|
||||
set_list(#privacy{us = {LUser, LServer}, lists = Lists} = Privacy) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:set_lists(Privacy) of
|
||||
ok ->
|
||||
Names = [Name || {Name, _} <- Lists],
|
||||
delete_cache(Mod, LUser, LServer, Names);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec process_lists_set(iq(), binary(), [privacy_item()]) -> iq().
|
||||
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer},
|
||||
lang = Lang} = IQ, Name, []) ->
|
||||
case xmpp:get_meta(IQ, privacy_active_list, none) of
|
||||
Name ->
|
||||
Txt = <<"Cannot remove active list">>,
|
||||
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
|
||||
_ ->
|
||||
case remove_list(LUser, LServer, Name) of
|
||||
ok ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
{error, conflict} ->
|
||||
Txt = <<"Cannot remove default list">>,
|
||||
xmpp:make_error(IQ, xmpp:err_conflict(Txt, Lang));
|
||||
{error, notfound} ->
|
||||
Txt = <<"No privacy list with this name found">>,
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
|
||||
{error, _} ->
|
||||
Txt = <<"Database failure">>,
|
||||
Err = xmpp:err_internal_server_error(Txt, Lang),
|
||||
xmpp:make_error(IQ, Err)
|
||||
end
|
||||
end;
|
||||
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
|
||||
lang = Lang} = IQ, Name, Items) ->
|
||||
@ -354,24 +356,18 @@ process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
|
||||
Txt = xmpp:format_error(Why),
|
||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||
List ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:set_privacy_list(LUser, LServer, Name, List) of
|
||||
{atomic, ok} ->
|
||||
UserList = #userlist{name = Name, list = List,
|
||||
needdb = is_list_needdb(List)},
|
||||
push_list_update(From, UserList, Name),
|
||||
case set_list(LUser, LServer, Name, List) of
|
||||
ok ->
|
||||
push_list_update(From, Name),
|
||||
xmpp:make_iq_result(IQ);
|
||||
Err ->
|
||||
?ERROR_MSG("failed to set privacy list '~s' "
|
||||
"for user ~s@~s: ~p",
|
||||
[Name, LUser, LServer, Err]),
|
||||
{error, _} ->
|
||||
Txt = <<"Database failure">>,
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
|
||||
end
|
||||
end.
|
||||
|
||||
-spec push_list_update(jid(), #userlist{}, binary() | none) -> ok.
|
||||
push_list_update(From, List, Name) ->
|
||||
-spec push_list_update(jid(), binary()) -> ok.
|
||||
push_list_update(From, Name) ->
|
||||
BareFrom = jid:remove_resource(From),
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
@ -379,44 +375,10 @@ push_list_update(From, List, Name) ->
|
||||
IQ = #iq{type = set, from = BareFrom, to = To,
|
||||
id = <<"push", (randoms:get_string())/binary>>,
|
||||
sub_els = [#privacy_query{
|
||||
lists = [#privacy_list{name = Name}]}],
|
||||
meta = #{privacy_updated_list => List}},
|
||||
lists = [#privacy_list{name = Name}]}]},
|
||||
ejabberd_router:route(IQ)
|
||||
end, ejabberd_sm:get_user_resources(From#jid.luser, From#jid.lserver)).
|
||||
|
||||
-spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
||||
user_send_packet({#iq{type = Type,
|
||||
to = #jid{luser = U, lserver = S, lresource = <<"">>},
|
||||
from = #jid{luser = U, lserver = S},
|
||||
sub_els = [_]} = IQ,
|
||||
#{privacy_list := #userlist{name = Name}} = State})
|
||||
when Type == get; Type == set ->
|
||||
NewIQ = case xmpp:has_subtag(IQ, #privacy_query{}) of
|
||||
true -> xmpp:put_meta(IQ, privacy_active_list, Name);
|
||||
false -> IQ
|
||||
end,
|
||||
{NewIQ, State};
|
||||
user_send_packet(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
||||
user_receive_packet({#iq{type = result, meta = #{privacy_list := List}} = IQ,
|
||||
State}) ->
|
||||
{IQ, State#{privacy_list => List}};
|
||||
user_receive_packet({#iq{type = set, meta = #{privacy_updated_list := New}} = IQ,
|
||||
#{user := U, server := S, resource := R,
|
||||
privacy_list := Old} = State}) ->
|
||||
State1 = if Old#userlist.name == New#userlist.name ->
|
||||
State#{privacy_list => New};
|
||||
true ->
|
||||
State
|
||||
end,
|
||||
From = jid:make(U, S),
|
||||
To = jid:make(U, S, R),
|
||||
{xmpp:set_from_to(IQ, From, To), State1};
|
||||
user_receive_packet(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec decode_item(privacy_item()) -> listitem().
|
||||
decode_item(#privacy_item{order = Order,
|
||||
action = Action,
|
||||
@ -448,47 +410,145 @@ decode_item(#privacy_item{order = Order,
|
||||
match_presence_out = MatchPresenceOut}
|
||||
end.
|
||||
|
||||
-spec is_list_needdb([listitem()]) -> boolean().
|
||||
is_list_needdb(Items) ->
|
||||
lists:any(fun (X) ->
|
||||
case X#listitem.type of
|
||||
subscription -> true;
|
||||
group -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
Items).
|
||||
-spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{privacy_active_list := List}) ->
|
||||
State#{privacy_active_list => List}.
|
||||
|
||||
-spec get_user_list(binary(), binary()) -> #userlist{}.
|
||||
get_user_list(LUser, LServer) ->
|
||||
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||
user_send_packet({#iq{type = Type,
|
||||
to = #jid{luser = U, lserver = S, lresource = <<"">>},
|
||||
from = #jid{luser = U, lserver = S},
|
||||
sub_els = [_]} = IQ,
|
||||
#{privacy_active_list := Name} = State})
|
||||
when Type == get; Type == set ->
|
||||
NewIQ = case xmpp:has_subtag(IQ, #privacy_query{}) of
|
||||
true -> xmpp:put_meta(IQ, privacy_active_list, Name);
|
||||
false -> IQ
|
||||
end,
|
||||
{NewIQ, State};
|
||||
user_send_packet(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec user_receive_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||
user_receive_packet({#iq{type = result,
|
||||
meta = #{privacy_active_list := Name}} = IQ, State}) ->
|
||||
{IQ, State#{privacy_active_list => Name}};
|
||||
user_receive_packet(Acc) ->
|
||||
Acc.
|
||||
|
||||
-spec set_list(binary(), binary(), binary(), [listitem()]) -> ok | {error, any()}.
|
||||
set_list(LUser, LServer, Name, List) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
{Default, Items} = Mod:get_user_list(LUser, LServer),
|
||||
NeedDb = is_list_needdb(Items),
|
||||
#userlist{name = Default, list = Items, needdb = NeedDb}.
|
||||
case Mod:set_list(LUser, LServer, Name, List) of
|
||||
ok ->
|
||||
delete_cache(Mod, LUser, LServer, [Name]);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec c2s_session_opened(ejabberd_c2s:state()) -> ejabberd_c2s:state().
|
||||
c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer}} = State) ->
|
||||
State#{privacy_list => get_user_list(LUser, LServer)}.
|
||||
-spec remove_list(binary(), binary(), binary()) ->
|
||||
ok | {error, conflict | notfound | any()}.
|
||||
remove_list(LUser, LServer, Name) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:remove_list(LUser, LServer, Name) of
|
||||
ok ->
|
||||
delete_cache(Mod, LUser, LServer, [Name]);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec c2s_copy_session(ejabberd_c2s:state(), ejabberd_c2s:state()) -> ejabberd_c2s:state().
|
||||
c2s_copy_session(State, #{privacy_list := List}) ->
|
||||
State#{privacy_list => List}.
|
||||
|
||||
-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error.
|
||||
-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error | {error, any()}.
|
||||
get_user_lists(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:get_user_lists(LUser, LServer).
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?PRIVACY_CACHE, {LUser, LServer},
|
||||
fun() -> Mod:get_lists(LUser, LServer) end);
|
||||
false ->
|
||||
Mod:get_lists(LUser, LServer)
|
||||
end.
|
||||
|
||||
-spec get_user_list(binary(), binary(), binary() | default) ->
|
||||
{ok, {binary(), [listitem()]}} | error | {error, any()}.
|
||||
get_user_list(LUser, LServer, Name) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?PRIVACY_LIST_CACHE, {LUser, LServer, Name},
|
||||
fun() ->
|
||||
case ets_cache:lookup(
|
||||
?PRIVACY_CACHE, {LUser, LServer}) of
|
||||
{ok, Privacy} ->
|
||||
get_list_by_name(Privacy, Name);
|
||||
error ->
|
||||
Mod:get_list(LUser, LServer, Name)
|
||||
end
|
||||
end);
|
||||
false ->
|
||||
Mod:get_list(LUser, LServer, Name)
|
||||
end.
|
||||
|
||||
-spec get_list_by_name(#privacy{}, binary() | default) ->
|
||||
{ok, {binary(), [listitem()]}} | error.
|
||||
get_list_by_name(#privacy{default = Default} = Privacy, default) ->
|
||||
get_list_by_name(Privacy, Default);
|
||||
get_list_by_name(#privacy{lists = Lists}, Name) ->
|
||||
case lists:keyfind(Name, 1, Lists) of
|
||||
{_, List} -> {ok, {Name, List}};
|
||||
false -> error
|
||||
end.
|
||||
|
||||
-spec set_default_list(binary(), binary(), binary() | none) ->
|
||||
ok | {error, notfound | any()}.
|
||||
set_default_list(LUser, LServer, Name) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Res = case Name of
|
||||
none -> Mod:unset_default(LUser, LServer);
|
||||
_ -> Mod:set_default(LUser, LServer, Name)
|
||||
end,
|
||||
case Res of
|
||||
ok ->
|
||||
delete_cache(Mod, LUser, LServer, []);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec check_packet(allow | deny, c2s_state() | jid(), stanza(), in | out) -> allow | deny.
|
||||
check_packet(Acc, #{jid := JID} = State, Packet, Dir) ->
|
||||
case maps:get(privacy_active_list, State, none) of
|
||||
none ->
|
||||
check_packet(Acc, JID, Packet, Dir);
|
||||
ListName ->
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
case get_user_list(LUser, LServer, ListName) of
|
||||
{ok, {_, List}} ->
|
||||
do_check_packet(JID, List, Packet, Dir);
|
||||
_ ->
|
||||
?DEBUG("Non-existing active list '~s' is set "
|
||||
"for user '~s'", [ListName, jid:encode(JID)]),
|
||||
check_packet(Acc, JID, Packet, Dir)
|
||||
end
|
||||
end;
|
||||
check_packet(_, JID, Packet, Dir) ->
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
case get_user_list(LUser, LServer, default) of
|
||||
{ok, {_, List}} ->
|
||||
do_check_packet(JID, List, Packet, Dir);
|
||||
_ ->
|
||||
allow
|
||||
end.
|
||||
|
||||
%% From is the sender, To is the destination.
|
||||
%% If Dir = out, User@Server is the sender account (From).
|
||||
%% If Dir = in, User@Server is the destination account (To).
|
||||
-spec check_packet(allow | deny, ejabberd_c2s:state() | jid(),
|
||||
stanza(), in | out) -> allow | deny.
|
||||
check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
|
||||
privacy_list := #userlist{list = List, needdb = NeedDb}},
|
||||
Packet, Dir) ->
|
||||
-spec do_check_packet(jid(), [listitem()], stanza(), in | out) -> allow | deny.
|
||||
do_check_packet(_, [], _, _) ->
|
||||
allow;
|
||||
do_check_packet(#jid{luser = LUser, lserver = LServer}, List, Packet, Dir) ->
|
||||
From = xmpp:get_from(Packet),
|
||||
To = xmpp:get_to(Packet),
|
||||
case {From, To} of
|
||||
@ -508,8 +568,6 @@ check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
|
||||
#jid{luser = LUser, lserver = LServer, lresource = <<"">>}} when Dir == out ->
|
||||
%% Allow outgoing packets from user's full jid to his bare JID
|
||||
allow;
|
||||
_ when List == [] ->
|
||||
allow;
|
||||
_ ->
|
||||
PType = case Packet of
|
||||
#message{} -> message;
|
||||
@ -529,21 +587,11 @@ check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
|
||||
in -> jid:tolower(From);
|
||||
out -> jid:tolower(To)
|
||||
end,
|
||||
{Subscription, Groups} =
|
||||
case NeedDb of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(roster_get_jid_info,
|
||||
LServer,
|
||||
{none, []},
|
||||
[LUser, LServer, LJID]);
|
||||
false ->
|
||||
{[], []}
|
||||
end,
|
||||
check_packet_aux(List, PType2, LJID, Subscription, Groups)
|
||||
end;
|
||||
check_packet(Acc, #jid{luser = LUser, lserver = LServer} = JID, Packet, Dir) ->
|
||||
List = get_user_list(LUser, LServer),
|
||||
check_packet(Acc, #{jid => JID, privacy_list => List}, Packet, Dir).
|
||||
{Subscription, Groups} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LServer,
|
||||
{none, []}, [LUser, LServer, LJID]),
|
||||
check_packet_aux(List, PType2, LJID, Subscription, Groups)
|
||||
end.
|
||||
|
||||
-spec check_packet_aux([listitem()],
|
||||
message | iq | presence_in | presence_out | other,
|
||||
@ -608,12 +656,82 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
|
||||
group -> lists:member(Value, Groups)
|
||||
end.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> any().
|
||||
-spec remove_user(binary(), binary()) -> ok.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Privacy = get_user_lists(LUser, LServer),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
Mod:remove_user(LUser, LServer).
|
||||
Mod:remove_lists(LUser, LServer),
|
||||
case Privacy of
|
||||
{ok, #privacy{lists = Lists}} ->
|
||||
Names = [Name || {Name, _} <- Lists],
|
||||
delete_cache(Mod, LUser, LServer, Names);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||
init_cache(Mod, Host, Opts) ->
|
||||
case use_cache(Mod, Host) of
|
||||
true ->
|
||||
CacheOpts = cache_opts(Host, Opts),
|
||||
ets_cache:new(?PRIVACY_CACHE, CacheOpts),
|
||||
ets_cache:new(?PRIVACY_LIST_CACHE, CacheOpts);
|
||||
false ->
|
||||
ets_cache:delete(?PRIVACY_CACHE),
|
||||
ets_cache:delete(?PRIVACY_LIST_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
|
||||
cache_opts(Host, Opts) ->
|
||||
MaxSize = gen_mod:get_opt(
|
||||
cache_size, Opts,
|
||||
ejabberd_config:cache_size(Host)),
|
||||
CacheMissed = gen_mod:get_opt(
|
||||
cache_missed, Opts,
|
||||
ejabberd_config:cache_missed(Host)),
|
||||
LifeTime = case gen_mod:get_opt(
|
||||
cache_life_time, Opts,
|
||||
ejabberd_config:cache_life_time(Host)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec use_cache(module(), binary()) -> boolean().
|
||||
use_cache(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, use_cache, 1) of
|
||||
true -> Mod:use_cache(Host);
|
||||
false ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host))
|
||||
end.
|
||||
|
||||
-spec cache_nodes(module(), binary()) -> [node()].
|
||||
cache_nodes(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 1) of
|
||||
true -> Mod:cache_nodes(Host);
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
|
||||
-spec delete_cache(module(), binary(), binary(), [binary()]) -> ok.
|
||||
delete_cache(Mod, LUser, LServer, Names) ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
Nodes = cache_nodes(Mod, LServer),
|
||||
ets_cache:delete(?PRIVACY_CACHE, {LUser, LServer}, Nodes),
|
||||
lists:foreach(
|
||||
fun(Name) ->
|
||||
ets_cache:delete(
|
||||
?PRIVACY_LIST_CACHE,
|
||||
{LUser, LServer, Name},
|
||||
Nodes)
|
||||
end, [default|Names]);
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
numeric_to_binary(<<0, 0, _/binary>>) ->
|
||||
<<"0">>;
|
||||
|
@ -27,11 +27,9 @@
|
||||
-behaviour(mod_privacy).
|
||||
|
||||
%% API
|
||||
-export([init/2, process_lists_get/2, process_list_get/3,
|
||||
process_default_set/3, process_active_set/3,
|
||||
remove_privacy_list/3, set_privacy_list/1,
|
||||
set_privacy_list/4, get_user_list/2, get_user_lists/2,
|
||||
remove_user/2, import/1]).
|
||||
-export([init/2, set_default/3, unset_default/2, set_lists/1,
|
||||
set_list/4, get_lists/2, get_list/3, remove_lists/2,
|
||||
remove_list/3, use_cache/1, import/1]).
|
||||
-export([need_transform/1, transform/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
@ -43,122 +41,106 @@
|
||||
%%%===================================================================
|
||||
init(_Host, _Opts) ->
|
||||
ejabberd_mnesia:create(?MODULE, privacy,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, privacy)}]).
|
||||
|
||||
process_lists_get(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> {none, []};
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
LItems = lists:map(fun ({N, _}) -> N end, Lists),
|
||||
{Default, LItems}
|
||||
use_cache(Host) ->
|
||||
case mnesia:table_info(privacy, storage_type) of
|
||||
disc_only_copies ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, mod_privacy, use_cache,
|
||||
ejabberd_config:use_cache(Host));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
process_list_get(LUser, LServer, Name) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
{'EXIT', _Reason} -> error;
|
||||
[] -> not_found;
|
||||
[#privacy{lists = Lists}] ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> not_found
|
||||
end
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, none) ->
|
||||
unset_default(LUser, LServer) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] -> ok;
|
||||
[R] -> mnesia:write(R#privacy{default = none})
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, Name) ->
|
||||
transaction(F).
|
||||
|
||||
set_default(LUser, LServer, Name) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] -> not_found;
|
||||
[] ->
|
||||
{error, notfound};
|
||||
[#privacy{lists = Lists} = P] ->
|
||||
case lists:keymember(Name, 1, Lists) of
|
||||
true ->
|
||||
mnesia:write(P#privacy{default = Name,
|
||||
lists = Lists}),
|
||||
ok;
|
||||
false -> not_found
|
||||
lists = Lists});
|
||||
false ->
|
||||
{error, notfound}
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
process_active_set(LUser, LServer, Name) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
[] -> error;
|
||||
[#privacy{lists = Lists}] ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
false -> error
|
||||
end
|
||||
end.
|
||||
|
||||
remove_privacy_list(LUser, LServer, Name) ->
|
||||
remove_list(LUser, LServer, Name) ->
|
||||
F = fun () ->
|
||||
case mnesia:read({privacy, {LUser, LServer}}) of
|
||||
[] -> ok;
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
if Name == Default -> conflict;
|
||||
true ->
|
||||
NewLists = lists:keydelete(Name, 1, Lists),
|
||||
mnesia:write(P#privacy{lists = NewLists})
|
||||
end
|
||||
[] ->
|
||||
{error, notfound};
|
||||
[#privacy{default = Default, lists = Lists} = P] ->
|
||||
if Name == Default ->
|
||||
{error, conflict};
|
||||
true ->
|
||||
NewLists = lists:keydelete(Name, 1, Lists),
|
||||
mnesia:write(P#privacy{lists = NewLists})
|
||||
end
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
set_privacy_list(Privacy) ->
|
||||
set_lists(Privacy) ->
|
||||
mnesia:dirty_write(Privacy).
|
||||
|
||||
set_privacy_list(LUser, LServer, Name, List) ->
|
||||
set_list(LUser, LServer, Name, List) ->
|
||||
F = fun () ->
|
||||
case mnesia:wread({privacy, {LUser, LServer}}) of
|
||||
[] ->
|
||||
NewLists = [{Name, List}],
|
||||
mnesia:write(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists});
|
||||
[#privacy{lists = Lists} = P] ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists})
|
||||
[] ->
|
||||
NewLists = [{Name, List}],
|
||||
mnesia:write(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists});
|
||||
[#privacy{lists = Lists} = P] ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
mnesia:write(P#privacy{lists = NewLists})
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
get_user_list(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer})
|
||||
of
|
||||
[] -> {none, []};
|
||||
[#privacy{default = Default, lists = Lists}] ->
|
||||
case Default of
|
||||
none -> {none, []};
|
||||
_ ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> {Default, List};
|
||||
_ -> {none, []}
|
||||
end
|
||||
end;
|
||||
_ -> {none, []}
|
||||
get_list(LUser, LServer, Name) ->
|
||||
case mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
[#privacy{default = Default, lists = Lists}] when Name == default ->
|
||||
case lists:keyfind(Default, 1, Lists) of
|
||||
{_, List} -> {ok, {Default, List}};
|
||||
false -> error
|
||||
end;
|
||||
[#privacy{lists = Lists}] ->
|
||||
case lists:keyfind(Name, 1, Lists) of
|
||||
{_, List} -> {ok, {Name, List}};
|
||||
false -> error
|
||||
end;
|
||||
[] ->
|
||||
error
|
||||
end.
|
||||
|
||||
get_user_lists(LUser, LServer) ->
|
||||
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
get_lists(LUser, LServer) ->
|
||||
case mnesia:dirty_read(privacy, {LUser, LServer}) of
|
||||
[#privacy{} = P] ->
|
||||
{ok, P};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
remove_lists(LUser, LServer) ->
|
||||
F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end,
|
||||
mnesia:transaction(F).
|
||||
transaction(F).
|
||||
|
||||
import(#privacy{} = P) ->
|
||||
mnesia:dirty_write(P).
|
||||
@ -199,3 +181,11 @@ transform(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
transaction(F) ->
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Result} ->
|
||||
Result;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
@ -27,11 +27,9 @@
|
||||
-behaviour(mod_privacy).
|
||||
|
||||
%% API
|
||||
-export([init/2, process_lists_get/2, process_list_get/3,
|
||||
process_default_set/3, process_active_set/3,
|
||||
remove_privacy_list/3, set_privacy_list/1,
|
||||
set_privacy_list/4, get_user_list/2, get_user_lists/2,
|
||||
remove_user/2, import/1]).
|
||||
-export([init/2, set_default/3, unset_default/2, set_lists/1,
|
||||
set_list/4, get_lists/2, get_list/3, remove_lists/2,
|
||||
remove_list/3, import/1]).
|
||||
|
||||
-export([privacy_schema/0]).
|
||||
|
||||
@ -44,122 +42,93 @@
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
process_lists_get(LUser, LServer) ->
|
||||
unset_default(LUser, LServer) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
LItems = lists:map(fun ({N, _}) -> N end, Lists),
|
||||
{Default, LItems};
|
||||
{error, notfound} ->
|
||||
{none, []};
|
||||
{error, _} ->
|
||||
error
|
||||
{ok, R} ->
|
||||
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
|
||||
{error, notfound} ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
process_list_get(LUser, LServer, Name) ->
|
||||
set_default(LUser, LServer, Name) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists}} ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> not_found
|
||||
end;
|
||||
{error, notfound} ->
|
||||
not_found;
|
||||
{error, _} ->
|
||||
error
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
case lists:keymember(Name, 1, Lists) of
|
||||
true ->
|
||||
ejabberd_riak:put(P#privacy{default = Name,
|
||||
lists = Lists},
|
||||
privacy_schema());
|
||||
false ->
|
||||
{error, notfound}
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, none) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, R} ->
|
||||
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
|
||||
{error, _} ->
|
||||
ok
|
||||
end};
|
||||
process_default_set(LUser, LServer, Name) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
case lists:keymember(Name, 1, Lists) of
|
||||
true ->
|
||||
ejabberd_riak:put(P#privacy{default = Name,
|
||||
lists = Lists},
|
||||
privacy_schema());
|
||||
false ->
|
||||
not_found
|
||||
end;
|
||||
{error, _} ->
|
||||
not_found
|
||||
end}.
|
||||
|
||||
process_active_set(LUser, LServer, Name) ->
|
||||
remove_list(LUser, LServer, Name) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists}} ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
false -> error
|
||||
end;
|
||||
{error, _} ->
|
||||
error
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
if Name == Default ->
|
||||
{error, conflict};
|
||||
true ->
|
||||
NewLists = lists:keydelete(Name, 1, Lists),
|
||||
ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
privacy_schema())
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
remove_privacy_list(LUser, LServer, Name) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
if Name == Default ->
|
||||
conflict;
|
||||
true ->
|
||||
NewLists = lists:keydelete(Name, 1, Lists),
|
||||
ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
privacy_schema())
|
||||
end;
|
||||
{error, _} ->
|
||||
ok
|
||||
end}.
|
||||
|
||||
set_privacy_list(Privacy) ->
|
||||
set_lists(Privacy) ->
|
||||
ejabberd_riak:put(Privacy, privacy_schema()).
|
||||
|
||||
set_privacy_list(LUser, LServer, Name, List) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
|
||||
{error, _} ->
|
||||
NewLists = [{Name, List}],
|
||||
ejabberd_riak:put(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists},
|
||||
privacy_schema())
|
||||
end}.
|
||||
|
||||
get_user_list(LUser, LServer) ->
|
||||
set_list(LUser, LServer, Name, List) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
case Default of
|
||||
none -> {none, []};
|
||||
_ ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> {Default, List};
|
||||
_ -> {none, []}
|
||||
end
|
||||
end;
|
||||
{error, _} ->
|
||||
{none, []}
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
|
||||
{error, notfound} ->
|
||||
NewLists = [{Name, List}],
|
||||
ejabberd_riak:put(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists},
|
||||
privacy_schema());
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
get_user_lists(LUser, LServer) ->
|
||||
get_list(LUser, LServer, Name) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} when Name == default ->
|
||||
case lists:keyfind(Default, 1, Lists) of
|
||||
{_, List} -> {ok, {Default, List}};
|
||||
false -> error
|
||||
end;
|
||||
{ok, #privacy{lists = Lists}} ->
|
||||
case lists:keyfind(Name, 1, Lists) of
|
||||
{_, List} -> {ok, {Name, List}};
|
||||
false -> error
|
||||
end;
|
||||
{error, notfound} ->
|
||||
error;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
get_lists(LUser, LServer) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{} = P} ->
|
||||
{ok, P};
|
||||
{error, _} ->
|
||||
error
|
||||
{error, notfound} ->
|
||||
error;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
{atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}.
|
||||
remove_lists(LUser, LServer) ->
|
||||
ejabberd_riak:delete(privacy, {LUser, LServer}).
|
||||
|
||||
import(#privacy{} = P) ->
|
||||
ejabberd_riak:put(P, privacy_schema()).
|
||||
|
@ -29,20 +29,11 @@
|
||||
-behaviour(mod_privacy).
|
||||
|
||||
%% API
|
||||
-export([init/2, process_lists_get/2, process_list_get/3,
|
||||
process_default_set/3, process_active_set/3,
|
||||
remove_privacy_list/3, set_privacy_list/1,
|
||||
set_privacy_list/4, get_user_list/2, get_user_lists/2,
|
||||
remove_user/2, import/1, export/1]).
|
||||
-export([init/2, set_default/3, unset_default/2, set_lists/1,
|
||||
set_list/4, get_lists/2, get_list/3, remove_lists/2,
|
||||
remove_list/3, import/1, export/1]).
|
||||
|
||||
-export([item_to_raw/1, raw_to_item/1,
|
||||
sql_add_privacy_list/2,
|
||||
sql_get_default_privacy_list/2,
|
||||
sql_get_default_privacy_list_t/1,
|
||||
sql_get_privacy_list_data/3,
|
||||
sql_get_privacy_list_data_by_id_t/1,
|
||||
sql_get_privacy_list_id_t/2,
|
||||
sql_set_default_privacy_list/2, sql_set_privacy_list/2]).
|
||||
-export([item_to_raw/1, raw_to_item/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("mod_privacy.hrl").
|
||||
@ -55,159 +46,143 @@
|
||||
init(_Host, _Opts) ->
|
||||
ok.
|
||||
|
||||
process_lists_get(LUser, LServer) ->
|
||||
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, []} -> none;
|
||||
{selected, [{DefName}]} -> DefName;
|
||||
_ -> none
|
||||
end,
|
||||
case catch sql_get_privacy_list_names(LUser, LServer) of
|
||||
{selected, Names} ->
|
||||
LItems = lists:map(fun ({N}) -> N end, Names),
|
||||
{Default, LItems};
|
||||
_ -> error
|
||||
unset_default(LUser, LServer) ->
|
||||
case unset_default_privacy_list(LUser, LServer) of
|
||||
ok ->
|
||||
ok;
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
process_list_get(LUser, LServer, Name) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
{selected, []} -> not_found;
|
||||
{selected, [{ID}]} ->
|
||||
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
|
||||
{selected, RItems} ->
|
||||
lists:flatmap(fun raw_to_item/1, RItems);
|
||||
_ -> error
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
process_default_set(LUser, LServer, none) ->
|
||||
case catch sql_unset_default_privacy_list(LUser,
|
||||
LServer)
|
||||
of
|
||||
{'EXIT', _Reason} -> {atomic, error};
|
||||
{error, _Reason} -> {atomic, error};
|
||||
_ -> {atomic, ok}
|
||||
end;
|
||||
process_default_set(LUser, LServer, Name) ->
|
||||
set_default(LUser, LServer, Name) ->
|
||||
F = fun () ->
|
||||
case sql_get_privacy_list_names_t(LUser) of
|
||||
{selected, []} -> not_found;
|
||||
{selected, Names} ->
|
||||
case lists:member({Name}, Names) of
|
||||
true -> sql_set_default_privacy_list(LUser, Name), ok;
|
||||
false -> not_found
|
||||
end
|
||||
case get_privacy_list_names_t(LUser) of
|
||||
{selected, []} ->
|
||||
{error, notfound};
|
||||
{selected, Names} ->
|
||||
case lists:member({Name}, Names) of
|
||||
true ->
|
||||
set_default_privacy_list(LUser, Name);
|
||||
false ->
|
||||
{error, notfound}
|
||||
end
|
||||
end
|
||||
end,
|
||||
sql_queries:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
process_active_set(LUser, LServer, Name) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name) of
|
||||
{selected, []} -> error;
|
||||
{selected, [{ID}]} ->
|
||||
case catch sql_get_privacy_list_data_by_id(ID, LServer) of
|
||||
{selected, RItems} ->
|
||||
lists:flatmap(fun raw_to_item/1, RItems);
|
||||
_ -> error
|
||||
end;
|
||||
_ -> error
|
||||
end.
|
||||
|
||||
remove_privacy_list(LUser, LServer, Name) ->
|
||||
remove_list(LUser, LServer, Name) ->
|
||||
F = fun () ->
|
||||
case sql_get_default_privacy_list_t(LUser) of
|
||||
{selected, []} ->
|
||||
sql_remove_privacy_list(LUser, Name), ok;
|
||||
{selected, [{Default}]} ->
|
||||
if Name == Default -> conflict;
|
||||
true -> sql_remove_privacy_list(LUser, Name), ok
|
||||
end
|
||||
case get_default_privacy_list_t(LUser) of
|
||||
{selected, []} ->
|
||||
remove_privacy_list_t(LUser, Name);
|
||||
{selected, [{Default}]} ->
|
||||
if Name == Default ->
|
||||
{error, conflict};
|
||||
true ->
|
||||
remove_privacy_list_t(LUser, Name)
|
||||
end
|
||||
end
|
||||
end,
|
||||
sql_queries:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
set_privacy_list(#privacy{us = {LUser, LServer},
|
||||
default = Default,
|
||||
lists = Lists}) ->
|
||||
set_lists(#privacy{us = {LUser, LServer},
|
||||
default = Default,
|
||||
lists = Lists}) ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({Name, List}) ->
|
||||
sql_add_privacy_list(LUser, Name),
|
||||
add_privacy_list(LUser, Name),
|
||||
{selected, [<<"id">>], [[I]]} =
|
||||
sql_get_privacy_list_id_t(LUser, Name),
|
||||
get_privacy_list_id_t(LUser, Name),
|
||||
RItems = lists:map(fun item_to_raw/1, List),
|
||||
sql_set_privacy_list(I, RItems),
|
||||
set_privacy_list(I, RItems),
|
||||
if is_binary(Default) ->
|
||||
sql_set_default_privacy_list(LUser, Default),
|
||||
ok;
|
||||
set_default_privacy_list(LUser, Default);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, Lists)
|
||||
end,
|
||||
sql_queries:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
set_privacy_list(LUser, LServer, Name, List) ->
|
||||
set_list(LUser, LServer, Name, List) ->
|
||||
RItems = lists:map(fun item_to_raw/1, List),
|
||||
F = fun () ->
|
||||
ID = case sql_get_privacy_list_id_t(LUser, Name) of
|
||||
ID = case get_privacy_list_id_t(LUser, Name) of
|
||||
{selected, []} ->
|
||||
sql_add_privacy_list(LUser, Name),
|
||||
{selected, [{I}]} =
|
||||
sql_get_privacy_list_id_t(LUser, Name),
|
||||
I;
|
||||
{selected, [{I}]} -> I
|
||||
add_privacy_list(LUser, Name),
|
||||
{selected, [{I}]} =
|
||||
get_privacy_list_id_t(LUser, Name),
|
||||
I;
|
||||
{selected, [{I}]} -> I
|
||||
end,
|
||||
sql_set_privacy_list(ID, RItems),
|
||||
ok
|
||||
set_privacy_list(ID, RItems)
|
||||
end,
|
||||
sql_queries:sql_transaction(LServer, F).
|
||||
transaction(LServer, F).
|
||||
|
||||
get_user_list(LUser, LServer) ->
|
||||
case catch sql_get_default_privacy_list(LUser, LServer)
|
||||
of
|
||||
{selected, []} -> {none, []};
|
||||
{selected, [{Default}]} ->
|
||||
case catch sql_get_privacy_list_data(LUser, LServer,
|
||||
Default) of
|
||||
{selected, RItems} ->
|
||||
{Default, lists:flatmap(fun raw_to_item/1, RItems)};
|
||||
_ -> {none, []}
|
||||
end;
|
||||
_ -> {none, []}
|
||||
get_list(LUser, LServer, default) ->
|
||||
case get_default_privacy_list(LUser, LServer) of
|
||||
{selected, []} ->
|
||||
error;
|
||||
{selected, [{Default}]} ->
|
||||
get_list(LUser, LServer, Default);
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end;
|
||||
get_list(LUser, LServer, Name) ->
|
||||
case get_privacy_list_data(LUser, LServer, Name) of
|
||||
{selected, []} ->
|
||||
error;
|
||||
{selected, RItems} ->
|
||||
{ok, {Name, lists:flatmap(fun raw_to_item/1, RItems)}};
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
get_user_lists(LUser, LServer) ->
|
||||
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, []} ->
|
||||
none;
|
||||
{selected, [{DefName}]} ->
|
||||
DefName;
|
||||
_ ->
|
||||
none
|
||||
end,
|
||||
case catch sql_get_privacy_list_names(LUser, LServer) of
|
||||
{selected, Names} ->
|
||||
Lists =
|
||||
lists:flatmap(
|
||||
fun({Name}) ->
|
||||
case catch sql_get_privacy_list_data(
|
||||
LUser, LServer, Name) of
|
||||
{selected, RItems} ->
|
||||
[{Name, lists:flatmap(fun raw_to_item/1, RItems)}];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end, Names),
|
||||
{ok, #privacy{default = Default,
|
||||
us = {LUser, LServer},
|
||||
lists = Lists}};
|
||||
_ ->
|
||||
error
|
||||
get_lists(LUser, LServer) ->
|
||||
case get_default_privacy_list(LUser, LServer) of
|
||||
{selected, Selected} ->
|
||||
Default = case Selected of
|
||||
[] -> none;
|
||||
[{DefName}] -> DefName
|
||||
end,
|
||||
case get_privacy_list_names(LUser, LServer) of
|
||||
{selected, Names} ->
|
||||
case lists:foldl(
|
||||
fun(_, {error, _} = Err) ->
|
||||
Err;
|
||||
({Name}, Acc) ->
|
||||
case get_privacy_list_data(LUser, LServer, Name) of
|
||||
{selected, RItems} ->
|
||||
Items = lists:flatmap(
|
||||
fun raw_to_item/1,
|
||||
RItems),
|
||||
[{Name, Items}|Acc];
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end
|
||||
end, [], Names) of
|
||||
{error, Reason} ->
|
||||
{error, Reason};
|
||||
Lists ->
|
||||
{ok, #privacy{default = Default,
|
||||
us = {LUser, LServer},
|
||||
lists = Lists}}
|
||||
end;
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end;
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
remove_user(LUser, LServer) ->
|
||||
sql_del_privacy_lists(LUser, LServer).
|
||||
remove_lists(LUser, LServer) ->
|
||||
case del_privacy_lists(LUser, LServer) of
|
||||
ok ->
|
||||
ok;
|
||||
_Err ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
export(Server) ->
|
||||
case catch ejabberd_sql:sql_query(jid:nameprep(Server),
|
||||
@ -271,6 +246,12 @@ import(_) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
transaction(LServer, F) ->
|
||||
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||
{atomic, Res} -> Res;
|
||||
{aborted, _Reason} -> {error, db_failure}
|
||||
end.
|
||||
|
||||
raw_to_item({SType, SValue, SAction, Order, MatchAll,
|
||||
MatchIQ, MatchMessage, MatchPresenceIn,
|
||||
MatchPresenceOut} = Row) ->
|
||||
@ -327,47 +308,102 @@ item_to_raw(#listitem{type = Type, value = Value,
|
||||
{SType, SValue, SAction, Order, MatchAll, MatchIQ,
|
||||
MatchMessage, MatchPresenceIn, MatchPresenceOut}.
|
||||
|
||||
sql_get_default_privacy_list(LUser, LServer) ->
|
||||
sql_queries:get_default_privacy_list(LServer, LUser).
|
||||
get_default_privacy_list(LUser, LServer) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(name)s from privacy_default_list "
|
||||
"where username=%(LUser)s")).
|
||||
|
||||
sql_get_default_privacy_list_t(LUser) ->
|
||||
sql_queries:get_default_privacy_list_t(LUser).
|
||||
get_default_privacy_list_t(LUser) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(name)s from privacy_default_list "
|
||||
"where username=%(LUser)s")).
|
||||
|
||||
sql_get_privacy_list_names(LUser, LServer) ->
|
||||
sql_queries:get_privacy_list_names(LServer, LUser).
|
||||
get_privacy_list_names(LUser, LServer) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(name)s from privacy_list"
|
||||
" where username=%(LUser)s")).
|
||||
|
||||
sql_get_privacy_list_names_t(LUser) ->
|
||||
sql_queries:get_privacy_list_names_t(LUser).
|
||||
get_privacy_list_names_t(LUser) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(name)s from privacy_list"
|
||||
" where username=%(LUser)s")).
|
||||
|
||||
sql_get_privacy_list_id(LUser, LServer, Name) ->
|
||||
sql_queries:get_privacy_list_id(LServer, LUser, Name).
|
||||
get_privacy_list_id_t(LUser, Name) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(id)d from privacy_list"
|
||||
" where username=%(LUser)s and name=%(Name)s")).
|
||||
|
||||
sql_get_privacy_list_id_t(LUser, Name) ->
|
||||
sql_queries:get_privacy_list_id_t(LUser, Name).
|
||||
get_privacy_list_data(LUser, LServer, Name) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
|
||||
"@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
|
||||
"@(match_presence_out)b from privacy_list_data "
|
||||
"where id ="
|
||||
" (select id from privacy_list"
|
||||
" where username=%(LUser)s and name=%(Name)s) "
|
||||
"order by ord")).
|
||||
|
||||
sql_get_privacy_list_data(LUser, LServer, Name) ->
|
||||
sql_queries:get_privacy_list_data(LServer, LUser, Name).
|
||||
set_default_privacy_list(LUser, Name) ->
|
||||
?SQL_UPSERT_T(
|
||||
"privacy_default_list",
|
||||
["!username=%(LUser)s",
|
||||
"name=%(Name)s"]).
|
||||
|
||||
sql_get_privacy_list_data_by_id(ID, LServer) ->
|
||||
sql_queries:get_privacy_list_data_by_id(LServer, ID).
|
||||
unset_default_privacy_list(LUser, LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from privacy_default_list"
|
||||
" where username=%(LUser)s")) of
|
||||
{updated, _} -> ok;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
sql_get_privacy_list_data_by_id_t(ID) ->
|
||||
sql_queries:get_privacy_list_data_by_id_t(ID).
|
||||
remove_privacy_list_t(LUser, Name) ->
|
||||
case ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from privacy_list where"
|
||||
" username=%(LUser)s and name=%(Name)s")) of
|
||||
{updated, 0} -> {error, notfound};
|
||||
{updated, _} -> ok;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
sql_set_default_privacy_list(LUser, Name) ->
|
||||
sql_queries:set_default_privacy_list(LUser, Name).
|
||||
add_privacy_list(LUser, Name) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("insert into privacy_list(username, name) "
|
||||
"values (%(LUser)s, %(Name)s)")).
|
||||
|
||||
sql_unset_default_privacy_list(LUser, LServer) ->
|
||||
sql_queries:unset_default_privacy_list(LServer, LUser).
|
||||
set_privacy_list(ID, RItems) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("delete from privacy_list_data where id=%(ID)d")),
|
||||
lists:foreach(
|
||||
fun({SType, SValue, SAction, Order, MatchAll, MatchIQ,
|
||||
MatchMessage, MatchPresenceIn, MatchPresenceOut}) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("insert into privacy_list_data(id, t, "
|
||||
"value, action, ord, match_all, match_iq, "
|
||||
"match_message, match_presence_in, match_presence_out) "
|
||||
"values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
|
||||
" %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
|
||||
" %(MatchMessage)b, %(MatchPresenceIn)b,"
|
||||
" %(MatchPresenceOut)b)"))
|
||||
end,
|
||||
RItems).
|
||||
|
||||
sql_remove_privacy_list(LUser, Name) ->
|
||||
sql_queries:remove_privacy_list(LUser, Name).
|
||||
|
||||
sql_add_privacy_list(LUser, Name) ->
|
||||
sql_queries:add_privacy_list(LUser, Name).
|
||||
|
||||
sql_set_privacy_list(ID, RItems) ->
|
||||
sql_queries:set_privacy_list(ID, RItems).
|
||||
|
||||
sql_del_privacy_lists(LUser, LServer) ->
|
||||
sql_queries:del_privacy_lists(LServer, LUser).
|
||||
del_privacy_lists(LUser, LServer) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from privacy_list where username=%(LUser)s")) of
|
||||
{updated, _} ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from privacy_default_list "
|
||||
"where username=%(LUser)s")) of
|
||||
{updated, _} -> ok;
|
||||
Err -> Err
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user