Synchronize fork

This commit is contained in:
Konstantinos Kallas 2017-06-14 12:35:01 +03:00
commit 4fc3d511ce
139 changed files with 5590 additions and 5446 deletions

View File

@ -125,11 +125,11 @@ ifeq ($(MAKECMDGOALS),copy-files-sub)
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/d;s/ .*//')) 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_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
DEPS_DIRS=$(sort deps/ $(foreach DEP,$(DEPS),deps/$(DEP)/) $(dir $(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) MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
define DEP_VERSION_template define DEP_VERSION_template

2
README
View File

@ -108,7 +108,7 @@ To compile ejabberd you need:
- GCC. - GCC.
- Libexpat 1.95 or higher. - Libexpat 1.95 or higher.
- Libyaml 0.1.4 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. - 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. - Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional.
- PAM library. Optional. For Pluggable Authentication Modules (PAM). - PAM library. Optional. For Pluggable Authentication Modules (PAM).

View File

@ -101,10 +101,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic]) esac],[db_type=generic])
AC_ARG_ENABLE(all, 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 [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 ;; 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 debug=false tools=false ;; 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) ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[]) esac],[])
@ -212,6 +212,30 @@ AC_ARG_ENABLE(latest_deps,
*) AC_MSG_ERROR(bad value ${enableval} for --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]) 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 AC_CONFIG_FILES([Makefile
vars.config vars.config
src/ejabberd.app.src]) src/ejabberd.app.src])
@ -253,9 +277,12 @@ AC_SUBST(riak)
AC_SUBST(redis) AC_SUBST(redis)
AC_SUBST(elixir) AC_SUBST(elixir)
AC_SUBST(iconv) AC_SUBST(iconv)
AC_SUBST(stun)
AC_SUBST(sip)
AC_SUBST(debug) AC_SUBST(debug)
AC_SUBST(tools) AC_SUBST(tools)
AC_SUBST(latest_deps) AC_SUBST(latest_deps)
AC_SUBST(system_deps)
AC_SUBST(CFLAGS) AC_SUBST(CFLAGS)
AC_SUBST(CPPFLAGS) AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS) AC_SUBST(LDFLAGS)

View File

@ -487,6 +487,8 @@ acl:
loopback: loopback:
ip: ip:
- "127.0.0.0/8" - "127.0.0.0/8"
- "::1/128"
- "::FFFF:127.0.0.1/128"
## ##
## Bad XMPP servers ## Bad XMPP servers
@ -589,13 +591,13 @@ api_permissions:
who: who:
- access: - access:
- allow: - allow:
- ip: "127.0.0.1/8" - acl: loopback
- acl: admin - acl: admin
- oauth: - oauth:
- scope: "ejabberd:admin" - scope: "ejabberd:admin"
- access: - access:
- allow: - allow:
- ip: "127.0.0.1/8" - acl: loopback
- acl: admin - acl: admin
what: what:
- "*" - "*"

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/bin/sh
# define default configuration # define default configuration
POLL=true POLL=true
@ -10,135 +10,84 @@ FIREWALL_WINDOW=""
ERLANG_NODE=ejabberd@localhost ERLANG_NODE=ejabberd@localhost
# define default environment variables # define default environment variables
SCRIPT_DIR=`cd ${0%/*} && pwd`
ERL={{erl}} ERL={{erl}}
IEX={{bindir}}/iex IEX={{bindir}}/iex
EPMD={{epmd}} EPMD={{epmd}}
INSTALLUSER={{installuser}} INSTALLUSER={{installuser}}
ERL_LIBS={{libdir}}
# check the proper system user is used if defined # check the proper system user is used if defined
if [ "$INSTALLUSER" != "" ] ; then EXEC_CMD="false"
EXEC_CMD="false" if [ -n "$INSTALLUSER" ] ; then
for GID in `id -G`; do if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then
if [ $GID -eq 0 ] ; then EXEC_CMD="as_current_user"
INSTALLUSER_HOME=$(getent passwd "$INSTALLUSER" | cut -d: -f6) else
if [ -n "$INSTALLUSER_HOME" ] && [ ! -d "$INSTALLUSER_HOME" ] ; then id -Gn | grep -q wheel && EXEC_CMD="as_install_user"
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
fi fi
else 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 fi
# parse command line parameters # parse command line parameters
declare -a ARGS=() for arg; do
while [ $# -ne 0 ] ; do case $1 in
PARAM="$1" -n|--node) ERLANG_NODE_ARG=$2; shift;;
shift -s|--spool) SPOOL_DIR=$2; shift;;
case $PARAM in -l|--logs) LOGS_DIR=$2; shift;;
--) break ;; -f|--config) EJABBERD_CONFIG_PATH=$2; shift;;
--no-timeout) EJABBERD_NO_TIMEOUT="--no-timeout" ;; -c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift;;
--node) ERLANG_NODE_ARG=$1 ; shift ;; -d|--config-dir) ETC_DIR=$2; shift;;
--config-dir) ETC_DIR="$1" ; shift ;; -t|--no-timeout) NO_TIMEOUT="--no-timeout";;
--config) EJABBERD_CONFIG_PATH="$1" ; shift ;; --) :;;
--ctl-config) EJABBERDCTL_CONFIG_PATH="$1" ; shift ;; *) break;;
--logs) LOGS_DIR="$1" ; shift ;;
--spool) SPOOL_DIR="$1" ; shift ;;
*) ARGS=("${ARGS[@]}" "$PARAM") ;;
esac esac
shift
done done
# Define ejabberd variable if they have not been defined from the command line # define ejabberd variables if not already defined from the command line
if [ "$ETC_DIR" = "" ] ; then : ${ETC_DIR:={{sysconfdir}}/ejabberd}
ETC_DIR={{sysconfdir}}/ejabberd : ${LOGS_DIR:={{localstatedir}}/log/ejabberd}
fi : ${SPOOL_DIR:={{localstatedir}}/lib/ejabberd}
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then : ${EJABBERD_CONFIG_PATH:="$ETC_DIR"/ejabberd.yml}
EJABBERDCTL_CONFIG_PATH=$ETC_DIR/ejabberdctl.cfg : ${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR"/ejabberdctl.cfg}
fi [ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
if [ -f "$EJABBERDCTL_CONFIG_PATH" ] ; then [ "$ERLANG_NODE_ARG" != "" ] && ERLANG_NODE=$ERLANG_NODE_ARG
. "$EJABBERDCTL_CONFIG_PATH" [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
fi : ${EJABBERD_DOC_PATH:={{docdir}}}
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then : ${EJABBERD_LOG_PATH:="$LOGS_DIR"/ejabberd.log}
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 mnesia options
MNESIA_OPTS="-mnesia dir \"\\\"$SPOOL_DIR\\\"\" $MNESIA_OPTIONS"
# define erl parameters # define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
KERNEL_OPTS=""
if [ "$FIREWALL_WINDOW" != "" ] ; then 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 fi
if [ "$INET_DIST_INTERFACE" != "" ] ; then if [ "$INET_DIST_INTERFACE" != "" ] ; then
INET_DIST_INTERFACE2="$(echo $INET_DIST_INTERFACE | sed 's/\./,/g')" 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_INTERFACE" != "$INET_DIST_INTERFACE2" ] ; then if [ "$INET_DIST_INTERFACE2" != "" ] ; then
INET_DIST_INTERFACE2="{$INET_DIST_INTERFACE2}" ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface \"$INET_DIST_INTERFACE2\""
fi fi
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${INET_DIST_INTERFACE2}\""
fi fi
if [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] ; then ERL_LIBS={{libdir}}
NAME="-sname" ERL_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump
else ERL_INETRC="$ETC_DIR"/inetrc
NAME="-name"
fi
IEXNAME="-$NAME"
# define ejabberd environment parameters # define ejabberd parameters
if [ "$EJABBERD_CONFIG_PATH" != "${EJABBERD_CONFIG_PATH%.yml}" ] ; then EJABBERD_OPTS="$EJABBERD_OPTS\
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH) $(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH) $(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH) $(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH) $(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")"
else [ -n "$EJABBERD_OPTS" ] && EJABBERD_OPTS="-ejabberd $EJABBERD_OPTS"
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH) EJABBERD_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS $EJABBERD_OPTS -s ejabberd"
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"
# export global variables # export global variables
export EJABBERD_CONFIG_PATH export EJABBERD_CONFIG_PATH
export EJABBERD_LOG_PATH export EJABBERD_LOG_PATH
export EJABBERD_BIN_PATH
export EJABBERD_DOC_PATH export EJABBERD_DOC_PATH
export EJABBERD_PID_PATH export EJABBERD_PID_PATH
export ERL_CRASH_DUMP export ERL_CRASH_DUMP
@ -150,116 +99,26 @@ export CONTRIB_MODULES_PATH
export CONTRIB_MODULES_CONF_DIR export CONTRIB_MODULES_CONF_DIR
export ERL_LIBS export ERL_LIBS
shell_escape_str() # run command either directly or via su $INSTALLUSER
exec_cmd()
{ {
if test $# -eq 0; then case $EXEC_CMD in
printf '"" ' as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;;
else as_current_user) "$@" ;;
shell_escape "$@" esac
fi }
} exec_erl()
{
shell_escape() NODE=$1; shift
{ exec_cmd $ERL ${S:--}name $NODE $ERLANG_OPTS "$@"
local RES=() }
for i in "$@"; do exec_iex()
if test -z "$i"; then {
printf '"" ' NODE=$1; shift
else exec_cmd $IEX ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
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"
} }
# usage
debugwarning() debugwarning()
{ {
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
@ -286,7 +145,6 @@ debugwarning()
livewarning() livewarning()
{ {
check_start
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
echo "--------------------------------------------------------------------" echo "--------------------------------------------------------------------"
echo "" echo ""
@ -308,32 +166,6 @@ livewarning()
fi 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() help()
{ {
echo "" echo ""
@ -355,28 +187,11 @@ help()
echo "" echo ""
} }
# common control function # dynamic node name helper
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
}
uid() uid()
{ {
uuid=$(uuidgen 2>/dev/null) 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)$$) [ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
uuid=${uuid%%-*} uuid=${uuid%%-*}
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE} [ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
@ -391,6 +206,7 @@ stop_epmd()
} }
# make sure node not already running and node name unregistered # make sure node not already running and node name unregistered
# if all ok, ensure runtime directory exists and make it current directory
check_start() check_start()
{ {
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && { "$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
@ -407,11 +223,17 @@ check_start()
"$EPMD" -kill >/dev/null "$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 # allow sync calls
wait_for_status() wait_status()
{ {
# args: status try delay # args: status try delay
# return: 0 OK, 1 KO # return: 0 OK, 1 KO
@ -420,27 +242,69 @@ wait_for_status()
while [ $status -ne $1 ] ; do while [ $status -ne $1 ] ; do
sleep $3 sleep $3
timeout=`expr $timeout - 1` timeout=`expr $timeout - 1`
[ $timeout -eq 0 ] && { if [ $timeout -eq 0 ] ; then
status=$1 status=$1
} || { else
ctl status > /dev/null exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
-extra $ERLANG_NODE $NO_TIMEOUT status > /dev/null
status=$? status=$?
} fi
done done
[ $timeout -eq 0 ] && return 1 || return 0 [ $timeout -gt 0 ]
} }
# main handler # main
case "${ARGS[0]}" in case $1 in
'start') start;; start)
'debug') debug;; check_start
'iexdebug') iexdebug;; exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput -detached
'live') live;; ;;
'iexlive') iexlive;; foreground)
'foreground') foreground;; check_start
'ping'*) ping ${ARGS[1]};; exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput
'etop') etop;; ;;
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout live)
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout livewarning
*) ctl "${ARGS[@]}";; 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 esac

View File

@ -38,11 +38,3 @@
-type listitem_type() :: none | jid | group | subscription. -type listitem_type() :: none | jid | group | subscription.
-type listitem_value() :: none | both | from | to | jid:ljid() | binary(). -type listitem_value() :: none | both | from | to | jid:ljid() | binary().
-type listitem_action() :: allow | deny. -type listitem_action() :: allow | deny.
-record(userlist, {name = none :: none | binary(),
list = [] :: [listitem()],
needdb = false :: boolean()}).
-type userlist() :: #userlist{}.
-export_type([userlist/0]).

View File

@ -104,13 +104,6 @@
). ).
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'. %% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
-type(subscription() :: 'none'
| 'pending'
| 'unconfigured'
| 'subscribed'
).
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
-type(accessModel() :: 'open' -type(accessModel() :: 'open'
| 'presence' | 'presence'
| 'roster' | 'roster'

26
mix.exs
View File

@ -3,18 +3,18 @@ defmodule Ejabberd.Mixfile do
def project do def project do
[app: :ejabberd, [app: :ejabberd,
version: "17.03.0", version: "17.6.0",
description: description, description: description(),
elixir: "~> 1.3", elixir: "~> 1.4",
elixirc_paths: ["lib"], elixirc_paths: ["lib"],
compile_path: ".", compile_path: ".",
compilers: [:asn1] ++ Mix.compilers, compilers: [:asn1] ++ Mix.compilers,
erlc_options: erlc_options, erlc_options: erlc_options(),
erlc_paths: ["asn1", "src"], erlc_paths: ["asn1", "src"],
# Elixir tests are starting the part of ejabberd they need # Elixir tests are starting the part of ejabberd they need
aliases: [test: "test --no-start"], aliases: [test: "test --no-start"],
package: package, package: package(),
deps: deps] deps: deps()]
end end
def description do def description do
@ -29,7 +29,7 @@ defmodule Ejabberd.Mixfile do
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab, included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml, :xmpp, :fast_tls, :stringprep, :fast_xml, :xmpp,
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2] :stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
++ cond_apps] ++ cond_apps()]
end end
defp erlc_options do defp erlc_options do
@ -39,7 +39,7 @@ defmodule Ejabberd.Mixfile do
end end
defp deps do defp deps do
[{:lager, "~> 3.2"}, [{:lager, "~> 3.4.0"},
{:p1_utils, "~> 1.0"}, {:p1_utils, "~> 1.0"},
{:fast_xml, "~> 1.1"}, {:fast_xml, "~> 1.1"},
{:xmpp, "~> 1.1"}, {:xmpp, "~> 1.1"},
@ -53,7 +53,7 @@ defmodule Ejabberd.Mixfile do
{:p1_oauth2, "~> 0.6.1"}, {:p1_oauth2, "~> 0.6.1"},
{:distillery, "~> 1.0"}, {:distillery, "~> 1.0"},
{:ex_doc, ">= 0.0.0", only: :dev}] {:ex_doc, ">= 0.0.0", only: :dev}]
++ cond_deps ++ cond_deps()
end end
defp deps_include(deps) do defp deps_include(deps) do
@ -89,7 +89,7 @@ defmodule Ejabberd.Mixfile do
app app
end end
def package do defp package do
[# These are the default files included in the package [# These are the default files included in the package
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"], files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"],
maintainers: ["ProcessOne"], maintainers: ["ProcessOne"],
@ -100,7 +100,7 @@ defmodule Ejabberd.Mixfile do
"ProcessOne" => "http://www.process-one.net/"}] "ProcessOne" => "http://www.process-one.net/"}]
end end
def vars do defp vars do
case :file.consult("vars.config") do case :file.consult("vars.config") do
{:ok,config} -> config {:ok,config} -> config
_ -> [zlib: true, iconv: true] _ -> [zlib: true, iconv: true]
@ -108,7 +108,7 @@ defmodule Ejabberd.Mixfile do
end end
defp config(key) do defp config(key) do
case vars[key] do case vars()[key] do
nil -> false nil -> false
value -> value value -> value
end end
@ -142,7 +142,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
end) end)
end end
def manifests, do: [manifest] def manifests, do: [manifest()]
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest) defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
def clean, do: Erlang.clean(manifest()) def clean, do: Erlang.clean(manifest())

View File

@ -1,22 +1,18 @@
%{"cache_tab": {:hex, :cache_tab, "1.0.7", "8e2c958c0c2178e6c015aa7340c761521dc71b642192a5a7887f4aeffa29c7b1", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, 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.2.2", "d5a52920cbe2378c8a21dfc83b526b4225944b9dce7bf170fe5f5cddda81ffb3", [:mix], []}, "distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []}, "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []}, "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]}]},
"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.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []}, "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_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.22", "7eb81a738218541208fa3a126ee36197fb0346852d8c12ad678039e539e019df", [:rebar3], [{:p1_utils, "1.0.7", [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.9", "1bf41a576d3eedcb690499350994932340908b4968832adcec4b55152d4e5f20", [:rebar3], [{:p1_utils, "1.0.7", [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], []}, "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], []}, "jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
"lager": {:hex, :lager, "3.2.4", "a6deb74dae7927f46bd13255268308ef03eb206ec784a94eaf7c1c0f3b811615", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]}, "lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
"p1_mysql": {:hex, :p1_mysql, "1.0.2", "893a99415f98ce8b6ad014ef950d4e878895787b6c8333587f1e506f831571e0", [:rebar3], []},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []}, "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.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.7", "030adbce8935f1b87aaedfdb037d3127cc671ee3e1904b394e6dde9e449d6979", [:rebar3], []}, "stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []}, "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]}]},
"stringprep": {:hex, :stringprep, "1.0.8", "870d72db031796177261af88d1e6eb081dc314ad217377d441e5ea3c8504a310", [:rebar3], [{:p1_utils, "1.0.7", [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]}]}}
"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]}]}}

View 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>>.

View File

@ -18,24 +18,24 @@
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}}, {deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.4.2"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", "470539a"}}, {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "f31d039"}}, {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.11"}}}, {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.12"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}}, {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.21"}}}, {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e8dbfec277e7eb27b8130b13873b969cc346fafc"}}, {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.11"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {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"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}}, {luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{jose, ".*", {git, "git://github.com/potatosalad/erlang-jose.git", {branch, "master"}}}, {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", {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", {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", {if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
{tag, "1.1.5"}}}}, {tag, "1.1.5"}}}},
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam", {if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
@ -46,12 +46,12 @@
{tag, "2.4.1"}}}}, {tag, "2.4.1"}}}},
%% Elixir support, needed to run tests %% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir", {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 %% 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, ".*", {if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}}, {git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv", {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", {if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}}, {tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git", {if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
@ -65,7 +65,6 @@
stringprep, stringprep,
fast_xml, fast_xml,
esip, esip,
luerl,
stun, stun,
fast_yaml, fast_yaml,
xmpp, xmpp,
@ -85,12 +84,15 @@
{i, "deps/p1_utils/include"}, {i, "deps/p1_utils/include"},
{if_var_false, debug, no_debug_info}, {if_var_false, debug, no_debug_info},
{if_var_true, debug, 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_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}}, {if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}}, {if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}}, {if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_version_above, "18", {d, 'STRONG_RAND_BYTES'}}, {if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
{if_version_above, "17", {d, 'GB_SETS_ITERATOR_FROM'}}, {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}, {if_var_true, hipe, native},
{src_dirs, [asn1, src, {src_dirs, [asn1, src,
{if_var_true, tools, tools}, {if_var_true, tools, tools},
@ -100,7 +102,7 @@
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}. {if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
{if_not_rebar3, {plugins, [ {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_elixir_compiler},
{if_var_true, elixir, rebar_exunit} {if_var_true, elixir, rebar_exunit}
]}}. ]}}.
@ -146,7 +148,7 @@
{post_hook_configure, [{"fast_tls", []}, {post_hook_configure, [{"fast_tls", []},
{"stringprep", []}, {"stringprep", []},
{"fast_yaml", []}, {"fast_yaml", []},
{"esip", []}, {if_var_true, sip, {"esip", []}},
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]}, {"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{if_var_true, pam, {"epam", []}}, {if_var_true, pam, {"epam", []}},
{if_var_true, zlib, {"ezlib", []}}, {if_var_true, zlib, {"ezlib", []}},

View File

@ -23,10 +23,12 @@ Vars = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])
Terms; Terms;
_Err -> _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), {cflags, CFlags} = lists:keyfind(cflags, 1, Vars),
{cppflags, CPPFlags} = lists:keyfind(cppflags, 1, Vars), {cppflags, CPPFlags} = lists:keyfind(cppflags, 1, Vars),
{ldflags, LDFlags} = lists:keyfind(ldflags, 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) -> GetCfg0 = fun(F, Cfg, [Key | Tail], Default) ->
Val = case lists:keyfind(Key, 1, Cfg) of Val = case lists:keyfind(Key, 1, Cfg) of
@ -141,6 +143,15 @@ ProcessVars = fun(_F, [], Acc) ->
_ -> _ ->
F(F, Tail, Acc) F(F, Tail, Acc)
end; 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, [Other1 | Tail1], Acc) ->
F(F, Tail1, [F(F, Other1, []) | Acc]); F(F, Tail1, [F(F, Other1, []) | Acc]);
(F, Val, Acc) when is_tuple(Val) -> (F, Val, Acc) when is_tuple(Val) ->
@ -327,11 +338,12 @@ Rules = [
ProcessFloatingDeps, [], []}, ProcessFloatingDeps, [], []},
{[deps], IsRebar3, {[deps], IsRebar3,
Rebar3DepsFilter, []}, Rebar3DepsFilter, []},
{[deps], os:getenv("USE_GLOBAL_DEPS") /= false, {[deps], SystemDeps /= false,
GlobalDepsFilter, []} 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]), %io:format("ejabberd configuration:~n ~p~n", [Config]),

View File

@ -116,12 +116,6 @@ CREATE TABLE vcard (
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 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 ( CREATE TABLE vcard_search (
username text NOT NULL, username text NOT NULL,
lusername text PRIMARY KEY, lusername text PRIMARY KEY,

View File

@ -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) CREATE INDEX [vcard_search_lorgunit] ON [vcard_search] (lorgunit)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON); 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]) ALTER TABLE [dbo].[pubsub_item] WITH CHECK ADD CONSTRAINT [pubsub_item_ibfk_1] FOREIGN KEY([nodeid])
REFERENCES [dbo].[pubsub_node] ([nodeid]) REFERENCES [dbo].[pubsub_node] ([nodeid])
ON DELETE CASCADE; ON DELETE CASCADE;

View File

@ -121,12 +121,6 @@ CREATE TABLE vcard (
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ) 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 ( CREATE TABLE vcard_search (
username varchar(191) NOT NULL, username varchar(191) NOT NULL,
lusername varchar(191) PRIMARY KEY, lusername varchar(191) PRIMARY KEY,

View File

@ -120,12 +120,6 @@ CREATE TABLE vcard (
created_at TIMESTAMP NOT NULL DEFAULT now() 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 ( CREATE TABLE vcard_search (
username text NOT NULL, username text NOT NULL,
lusername text PRIMARY KEY, lusername text PRIMARY KEY,

View File

@ -260,6 +260,8 @@ response(KeyVals, User, Passwd, Nonce, AuthzId,
":", (hex((erlang:md5(A2))))/binary>>, ":", (hex((erlang:md5(A2))))/binary>>,
hex((erlang:md5(T))). hex((erlang:md5(T))).
-spec opt_type(fqdn) -> fun((binary() | [binary()]) -> [binary()]);
(atom()) -> [atom()].
opt_type(fqdn) -> opt_type(fqdn) ->
fun(FQDN) when is_binary(FQDN) -> fun(FQDN) when is_binary(FQDN) ->
[FQDN]; [FQDN];

View File

@ -111,7 +111,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
{error, saslprep_failed, UserName}; {error, saslprep_failed, UserName};
true -> true ->
{StoredKey, ServerKey, Salt, IterationCount} = {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 -> true ->
TempSalt = TempSalt =
randoms:bytes(?SALT_LENGTH), randoms:bytes(?SALT_LENGTH),
@ -128,14 +132,14 @@ mech_step(#state{step = 2} = State, ClientIn) ->
str:substr(ClientIn, str:substr(ClientIn,
str:str(ClientIn, <<"n=">>)), str:str(ClientIn, <<"n=">>)),
ServerNonce = ServerNonce =
misc:encode_base64(randoms:bytes(?NONCE_LENGTH)), base64:encode(randoms:bytes(?NONCE_LENGTH)),
ServerFirstMessage = ServerFirstMessage =
iolist_to_binary( iolist_to_binary(
["r=", ["r=",
ClientNonce, ClientNonce,
ServerNonce, ServerNonce,
",", "s=", ",", "s=",
misc:encode_base64(Salt), base64:encode(Salt),
",", "i=", ",", "i=",
integer_to_list(IterationCount)]), integer_to_list(IterationCount)]),
{continue, ServerFirstMessage, {continue, ServerFirstMessage,
@ -161,7 +165,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
ClientProofAttribute] -> ClientProofAttribute] ->
case parse_attribute(GS2ChannelBindingAttribute) of case parse_attribute(GS2ChannelBindingAttribute) of
{$c, CVal} -> {$c, CVal} ->
ChannelBindingSupport = binary:at(misc:decode_base64(CVal), 0), ChannelBindingSupport = try binary:first(base64:decode(CVal))
catch _:badarg -> 0
end,
if (ChannelBindingSupport == $n) if (ChannelBindingSupport == $n)
or (ChannelBindingSupport == $y) -> or (ChannelBindingSupport == $y) ->
Nonce = <<(State#state.client_nonce)/binary, Nonce = <<(State#state.client_nonce)/binary,
@ -170,7 +176,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{$r, CompareNonce} when CompareNonce == Nonce -> {$r, CompareNonce} when CompareNonce == Nonce ->
case parse_attribute(ClientProofAttribute) of case parse_attribute(ClientProofAttribute) of
{$p, ClientProofB64} -> {$p, ClientProofB64} ->
ClientProof = misc:decode_base64(ClientProofB64), ClientProof = try base64:decode(ClientProofB64)
catch _:badarg -> <<>>
end,
AuthMessage = iolist_to_binary( AuthMessage = iolist_to_binary(
[State#state.auth_message, [State#state.auth_message,
",", ",",
@ -191,7 +199,7 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{auth_module, State#state.auth_module}, {auth_module, State#state.auth_module},
{authzid, State#state.username}], {authzid, State#state.username}],
<<"v=", <<"v=",
(misc:encode_base64(ServerSignature))/binary>>}; (base64:encode(ServerSignature))/binary>>};
true -> {error, not_authorized, State#state.username} true -> {error, not_authorized, State#state.username}
end; end;
_ -> {error, bad_attribute} _ -> {error, bad_attribute}

View File

@ -138,7 +138,7 @@ get_commands_spec() ->
desc = "Get the current loglevel", desc = "Get the current loglevel",
module = ejabberd_logger, function = get, module = ejabberd_logger, function = get,
result_desc = "Tuple with the log level number, its keyword and description", result_desc = "Tuple with the log level number, its keyword and description",
result_example = {4, <<"info">>, <<"Info">>}, result_example = {4, info, <<"Info">>},
args = [], args = [],
result = {leveltuple, {tuple, [{levelnumber, integer}, result = {leveltuple, {tuple, [{levelnumber, integer},
{levelatom, atom}, {levelatom, atom},
@ -303,6 +303,7 @@ get_commands_spec() ->
#ejabberd_commands{name = export2sql, tags = [mnesia], #ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL file", 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, module = ejd2sql, function = export,
args_desc = ["Vhost", "Full path to the destination SQL file"], args_desc = ["Vhost", "Full path to the destination SQL file"],
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"], args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
@ -478,9 +479,9 @@ update_module(ModuleNameString) ->
register(User, Host, Password) -> register(User, Host, Password) ->
case ejabberd_auth:try_register(User, Host, Password) of case ejabberd_auth:try_register(User, Host, Password) of
{atomic, ok} -> ok ->
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])}; {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]), Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
{error, conflict, 10090, Msg}; {error, conflict, 10090, Msg};
{error, Reason} -> {error, Reason} ->
@ -494,7 +495,7 @@ unregister(User, Host) ->
{ok, ""}. {ok, ""}.
registered_users(Host) -> registered_users(Host) ->
Users = ejabberd_auth:get_vh_registered_users(Host), Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort(Users), SUsers = lists:sort(Users),
lists:map(fun({U, _S}) -> U end, SUsers). lists:map(fun({U, _S}) -> U end, SUsers).

View File

@ -160,6 +160,9 @@ start_apps() ->
ejabberd:start_app(xmpp), ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab). 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) -> opt_type(net_ticktime) ->
fun (P) when is_integer(P), P > 0 -> P end; fun (P) when is_integer(P), P > 0 -> P end;
opt_type(cluster_nodes) -> opt_type(cluster_nodes) ->

View File

@ -22,9 +22,6 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%% %%%
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%% TODO: Use the functions in ejabberd auth to add and remove users.
-module(ejabberd_auth). -module(ejabberd_auth).
-behaviour(gen_server). -behaviour(gen_server).
@ -37,12 +34,12 @@
set_password/3, check_password/4, set_password/3, check_password/4,
check_password/6, check_password_with_authmodule/4, check_password/6, check_password_with_authmodule/4,
check_password_with_authmodule/6, try_register/3, check_password_with_authmodule/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1, get_users/0, get_users/1, password_to_scram/1,
get_vh_registered_users/2, export/1, import_info/0, get_users/2, export/1, import_info/0,
get_vh_registered_users_number/1, import/5, import_start/2, count_users/1, import/5, import_start/2,
get_vh_registered_users_number/2, get_password/2, count_users/2, get_password/2,
get_password_s/2, get_password_with_authmodule/2, get_password_s/2, get_password_with_authmodule/2,
is_user_exists/2, is_user_exists_in_other_modules/3, user_exists/2, user_exists_in_other_modules/3,
remove_user/2, remove_user/3, plain_password_required/1, remove_user/2, remove_user/3, plain_password_required/1,
store_type/1, entropy/1, backend_type/1, password_format/1]). store_type/1, entropy/1, backend_type/1, password_format/1]).
%% gen_server callbacks %% gen_server callbacks
@ -54,10 +51,13 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-define(AUTH_CACHE, auth_cache).
-define(SALT_LENGTH, 16).
-record(state, {host_modules = #{} :: map()}). -record(state, {host_modules = #{} :: map()}).
-type scrammed_password() :: {binary(), binary(), binary(), non_neg_integer()}. -type password() :: binary() | #scram{}.
-type password() :: binary() | scrammed_password(). -type digest_fun() :: fun((binary()) -> binary()).
-export_type([password/0]). -export_type([password/0]).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -69,24 +69,29 @@
-callback start(binary()) -> any(). -callback start(binary()) -> any().
-callback stop(binary()) -> any(). -callback stop(binary()) -> any().
-callback plain_password_required() -> boolean(). -callback plain_password_required(binary()) -> boolean().
-callback store_type() -> plain | external | scram. -callback store_type(binary()) -> plain | external | scram.
-callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}. -callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}.
-callback remove_user(binary(), binary()) -> any(). -callback remove_user(binary(), binary()) -> ok | {error, any()}.
-callback remove_user(binary(), binary(), binary()) -> any(). -callback user_exists(binary(), binary()) -> boolean() | {error, atom()}.
-callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}.
-callback check_password(binary(), binary(), binary(), binary()) -> boolean(). -callback check_password(binary(), binary(), binary(), binary()) -> boolean().
-callback check_password(binary(), binary(), binary(), binary(), binary(), -callback try_register(binary(), binary(), password()) -> ok | {error, atom()}.
fun((binary()) -> binary())) -> boolean(). -callback get_users(binary(), opts()) -> [{binary(), binary()}].
-callback try_register(binary(), binary(), binary()) -> {atomic, atom()} | -callback count_users(binary(), opts()) -> number().
{error, atom()}. -callback get_password(binary(), binary()) -> {ok, password()} | error.
-callback dirty_get_registered_users() -> [{binary(), binary()}]. -callback use_cache(binary()) -> boolean().
-callback get_vh_registered_users(binary()) -> [{binary(), binary()}]. -callback cache_nodes(binary()) -> boolean().
-callback get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}].
-callback get_vh_registered_users_number(binary()) -> number(). -optional_callbacks([set_password/3,
-callback get_vh_registered_users_number(binary(), opts()) -> number(). remove_user/2,
-callback get_password(binary(), binary()) -> false | password(). user_exists/2,
-callback get_password_s(binary(), binary()) -> password(). check_password/4,
try_register/3,
get_users/2,
count_users/2,
get_password/2,
use_cache/1,
cache_nodes/1]).
-spec start_link() -> {ok, pid()} | {error, any()}. -spec start_link() -> {ok, pid()} | {error, any()}.
start_link() -> start_link() ->
@ -99,9 +104,13 @@ init([]) ->
HostModules = lists:foldl( HostModules = lists:foldl(
fun(Host, Acc) -> fun(Host, Acc) ->
Modules = auth_modules(Host), Modules = auth_modules(Host),
start(Host, Modules),
maps:put(Host, Modules, Acc) maps:put(Host, Modules, Acc)
end, #{}, ?MYHOSTS), end, #{}, ?MYHOSTS),
lists:foreach(
fun({Host, Modules}) ->
start(Host, Modules)
end, maps:to_list(HostModules)),
init_cache(HostModules),
{ok, #state{host_modules = HostModules}}. {ok, #state{host_modules = HostModules}}.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
@ -112,11 +121,13 @@ handle_cast({host_up, Host}, #state{host_modules = HostModules} = State) ->
Modules = auth_modules(Host), Modules = auth_modules(Host),
start(Host, Modules), start(Host, Modules),
NewHostModules = maps:put(Host, Modules, HostModules), NewHostModules = maps:put(Host, Modules, HostModules),
init_cache(NewHostModules),
{noreply, State#state{host_modules = NewHostModules}}; {noreply, State#state{host_modules = NewHostModules}};
handle_cast({host_down, Host}, #state{host_modules = HostModules} = State) -> handle_cast({host_down, Host}, #state{host_modules = HostModules} = State) ->
Modules = maps:get(Host, HostModules, []), Modules = maps:get(Host, HostModules, []),
stop(Host, Modules), stop(Host, Modules),
NewHostModules = maps:remove(Host, HostModules), NewHostModules = maps:remove(Host, HostModules),
init_cache(NewHostModules),
{noreply, State#state{host_modules = NewHostModules}}; {noreply, State#state{host_modules = NewHostModules}};
handle_cast(config_reloaded, #state{host_modules = HostModules} = State) -> handle_cast(config_reloaded, #state{host_modules = HostModules} = State) ->
NewHostModules = lists:foldl( NewHostModules = lists:foldl(
@ -127,6 +138,7 @@ handle_cast(config_reloaded, #state{host_modules = HostModules} = State) ->
stop(Host, OldModules -- NewModules), stop(Host, OldModules -- NewModules),
maps:put(Host, NewModules, Acc) maps:put(Host, NewModules, Acc)
end, HostModules, ?MYHOSTS), end, HostModules, ?MYHOSTS),
init_cache(NewHostModules),
{noreply, State#state{host_modules = NewHostModules}}; {noreply, State#state{host_modules = NewHostModules}};
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]), ?WARNING_MSG("unexpected cast: ~p", [Msg]),
@ -162,306 +174,266 @@ host_down(Host) ->
config_reloaded() -> config_reloaded() ->
gen_server:cast(?MODULE, config_reloaded). gen_server:cast(?MODULE, config_reloaded).
-spec plain_password_required(binary()) -> boolean().
plain_password_required(Server) -> plain_password_required(Server) ->
lists:any(fun (M) -> M:plain_password_required() end, lists:any(fun (M) -> M:plain_password_required(Server) end,
auth_modules(Server)). auth_modules(Server)).
-spec store_type(binary()) -> plain | scram | external.
store_type(Server) -> store_type(Server) ->
%% @doc Check if the user and password can login in server. lists:foldl(
%% @spec (User::string(), Server::string(), Password::string()) -> fun(_, external) -> external;
%% true | false
lists:foldl(fun (_, external) -> external;
(M, scram) -> (M, scram) ->
case M:store_type() of case M:store_type(Server) of
external -> external; external -> external;
_Else -> scram _ -> scram
end; end;
(M, plain) -> M:store_type() (M, plain) ->
end, M:store_type(Server)
plain, auth_modules(Server)). end, plain, auth_modules(Server)).
-spec check_password(binary(), binary(), binary(), binary()) -> boolean(). -spec check_password(binary(), binary(), binary(), binary()) -> boolean().
check_password(User, AuthzId, Server, Password) -> check_password(User, AuthzId, Server, Password) ->
case check_password_with_authmodule(User, AuthzId, Server, check_password(User, AuthzId, Server, Password, <<"">>, undefined).
Password)
of
{true, _AuthModule} -> true;
false -> false
end.
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
-spec check_password(binary(), binary(), binary(), binary(), binary(), -spec check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean(). digest_fun() | undefined) -> boolean().
check_password(User, AuthzId, Server, Password, Digest, DigestGen) ->
check_password(User, AuthzId, Server, Password, Digest, case check_password_with_authmodule(
DigestGen) -> User, AuthzId, Server, Password, Digest, DigestGen) of
case check_password_with_authmodule(User, AuthzId, Server,
Password, Digest, DigestGen)
of
{true, _AuthModule} -> true; {true, _AuthModule} -> true;
false -> false false -> false
end. end.
%% @doc Check if the user and password can login in server. -spec check_password_with_authmodule(binary(), binary(),
%% The user can login if at least an authentication method accepts the user binary(), binary()) -> false | {true, atom()}.
%% and the password. check_password_with_authmodule(User, AuthzId, Server, Password) ->
%% The first authentication method that accepts the credentials is returned. check_password_with_authmodule(
%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string()) -> User, AuthzId, Server, Password, <<"">>, undefined).
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_mnesia | ejabberd_auth_ldap
%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak
-spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false |
{true, atom()}.
check_password_with_authmodule(User, AuthzId, Server, -spec check_password_with_authmodule(
Password) -> binary(), binary(), binary(), binary(), binary(),
check_password_loop(auth_modules(Server), digest_fun() | undefined) -> false | {true, atom()}.
[User, AuthzId, Server, Password]). check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGen) ->
case validate_credentials(User, Server) of
-spec check_password_with_authmodule(binary(), binary(), binary(), binary(), binary(), {ok, LUser, LServer} ->
fun((binary()) -> binary())) -> false | lists:foldl(
{true, atom()}. fun(Mod, false) ->
case db_check_password(
check_password_with_authmodule(User, AuthzId, Server, Password, LUser, AuthzId, LServer, Password,
Digest, DigestGen) -> Digest, DigestGen, Mod) of
check_password_loop(auth_modules(Server), true -> {true, Mod};
[User, AuthzId, Server, Password, Digest, DigestGen]). false -> false
end;
check_password_loop([], _Args) -> false; (_, Acc) ->
check_password_loop([AuthModule | AuthModules], Args) -> Acc
case apply(AuthModule, check_password, Args) of end, false, auth_modules(LServer));
true -> {true, AuthModule}; _ ->
false -> check_password_loop(AuthModules, Args) false
end. end.
-spec set_password(binary(), binary(), binary()) -> ok | -spec set_password(binary(), binary(), password()) -> ok | {error, atom()}.
{error, atom()}.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, ErrorType}
%% where ErrorType = empty_password | not_allowed | invalid_jid
set_password(_User, _Server, <<"">>) ->
{error, empty_password};
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed} case validate_credentials(User, Server, Password) of
lists:foldl(fun (M, {error, _}) -> {ok, LUser, LServer} ->
M:set_password(User, Server, Password); lists:foldl(
(_M, Res) -> Res fun(M, {error, _}) ->
end, db_set_password(LUser, LServer, Password, M);
{error, not_allowed}, auth_modules(Server)). (_, ok) ->
ok
end, {error, not_allowed}, auth_modules(LServer));
Err ->
Err
end.
-spec try_register(binary(), binary(), binary()) -> {atomic, atom()} | -spec try_register(binary(), binary(), password()) -> ok | {error, atom()}.
{error, atom()}.
try_register(_User, _Server, <<"">>) ->
{error, not_allowed};
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case is_user_exists(User, Server) of case validate_credentials(User, Server, Password) of
true -> {atomic, exists}; {ok, LUser, LServer} ->
case user_exists(LUser, LServer) of
true ->
{error, exists};
false -> false ->
LServer = jid:nameprep(Server),
case ejabberd_router:is_my_host(LServer) of case ejabberd_router:is_my_host(LServer) of
true -> true ->
Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res; case lists:foldl(
(M, _) -> fun(_, ok) ->
M:try_register(User, Server, Password) ok;
end, (Mod, _) ->
{error, not_allowed}, auth_modules(Server)), db_try_register(
case Res of User, Server, Password, Mod)
{atomic, ok} -> end, {error, not_allowed},
ejabberd_hooks:run(register_user, Server, auth_modules(LServer)) of
[User, Server]), ok ->
{atomic, ok}; ejabberd_hooks:run(
_ -> Res register_user, Server, [User, Server]);
{error, _} = Err ->
Err
end; end;
false -> {error, not_allowed} false ->
{error, not_allowed}
end end
end;
Err ->
Err
end. end.
%% Registered users list do not include anonymous users logged -spec get_users() -> [{binary(), binary()}].
-spec dirty_get_registered_users() -> [{binary(), binary()}]. get_users() ->
lists:flatmap(
fun({Host, Mod}) ->
db_get_users(Host, [], Mod)
end, auth_modules()).
dirty_get_registered_users() -> -spec get_users(binary()) -> [{binary(), binary()}].
lists:flatmap(fun (M) -> M:dirty_get_registered_users() get_users(Server) ->
end, get_users(Server, []).
auth_modules()).
-spec get_vh_registered_users(binary()) -> [{binary(), binary()}]. -spec get_users(binary(), opts()) -> [{binary(), binary()}].
get_users(Server, Opts) ->
case jid:nameprep(Server) of
error -> [];
LServer ->
lists:flatmap(
fun(M) -> db_get_users(LServer, Opts, M) end,
auth_modules(LServer))
end.
%% Registered users list do not include anonymous users logged -spec count_users(binary()) -> non_neg_integer().
get_vh_registered_users(Server) -> count_users(Server) ->
lists:flatmap(fun (M) -> count_users(Server, []).
M:get_vh_registered_users(Server)
end,
auth_modules(Server)).
-spec get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}]. -spec count_users(binary(), opts()) -> non_neg_integer().
count_users(Server, Opts) ->
get_vh_registered_users(Server, Opts) -> case jid:nameprep(Server) of
lists:flatmap(fun (M) -> error -> 0;
case erlang:function_exported(M, LServer ->
get_vh_registered_users, lists:sum(
2) lists:map(
of fun(M) -> db_count_users(LServer, Opts, M) end,
true -> M:get_vh_registered_users(Server, Opts); auth_modules(LServer)))
false -> M:get_vh_registered_users(Server) end.
end
end,
auth_modules(Server)).
get_vh_registered_users_number(Server) ->
lists:sum(lists:map(fun (M) ->
case erlang:function_exported(M,
get_vh_registered_users_number,
1)
of
true ->
M:get_vh_registered_users_number(Server);
false ->
length(M:get_vh_registered_users(Server))
end
end,
auth_modules(Server))).
-spec get_vh_registered_users_number(binary(), opts()) -> number().
get_vh_registered_users_number(Server, Opts) ->
%% @doc Get the password of the user.
%% @spec (User::string(), Server::string()) -> Password::string()
lists:sum(lists:map(fun (M) ->
case erlang:function_exported(M,
get_vh_registered_users_number,
2)
of
true ->
M:get_vh_registered_users_number(Server,
Opts);
false ->
length(M:get_vh_registered_users(Server))
end
end,
auth_modules(Server))).
-spec get_password(binary(), binary()) -> false | password(). -spec get_password(binary(), binary()) -> false | password().
get_password(User, Server) -> get_password(User, Server) ->
lists:foldl(fun (M, false) -> case validate_credentials(User, Server) of
M:get_password(User, Server); {ok, LUser, LServer} ->
(_M, Password) -> Password case lists:foldl(
end, fun(M, error) -> db_get_password(LUser, LServer, M);
false, auth_modules(Server)). (_M, Acc) -> Acc
end, error, auth_modules(LServer)) of
{ok, Password} ->
Password;
error ->
false
end;
_ ->
false
end.
-spec get_password_s(binary(), binary()) -> password(). -spec get_password_s(binary(), binary()) -> password().
get_password_s(User, Server) -> get_password_s(User, Server) ->
case get_password(User, Server) of case get_password(User, Server) of
false -> <<"">>; false -> <<"">>;
Password -> Password Password -> Password
end. end.
%% @doc Get the password of the user and the auth module.
%% @spec (User::string(), Server::string()) ->
%% {Password::string(), AuthModule::atom()} | {false, none}
-spec get_password_with_authmodule(binary(), binary()) -> {false | password(), module()}. -spec get_password_with_authmodule(binary(), binary()) -> {false | password(), module()}.
get_password_with_authmodule(User, Server) -> get_password_with_authmodule(User, Server) ->
%% Returns true if the user exists in the DB or if an anonymous user is logged case validate_credentials(User, Server) of
%% under the given name {ok, LUser, LServer} ->
lists:foldl(fun (M, {false, _}) -> case lists:foldl(
{M:get_password(User, Server), M}; fun(M, {error, _}) ->
(_M, {Password, AuthModule}) -> {Password, AuthModule} {db_get_password(LUser, LServer, M), M};
end, (_M, Acc) ->
{false, none}, auth_modules(Server)). Acc
end, {error, undefined}, auth_modules(LServer)) of
{{ok, Password}, Module} ->
{Password, Module};
{error, Module} ->
{false, Module}
end;
_ ->
{false, undefined}
end.
-spec is_user_exists(binary(), binary()) -> boolean(). -spec user_exists(binary(), binary()) -> boolean().
user_exists(_User, <<"">>) ->
is_user_exists(_User, <<"">>) ->
false; false;
user_exists(User, Server) ->
is_user_exists(User, Server) -> case validate_credentials(User, Server) of
%% Check if the user exists in all authentications module except the module {ok, LUser, LServer} ->
%% passed as parameter lists:any(
%% @spec (Module::atom(), User, Server) -> true | false | maybe fun(M) ->
lists:any(fun (M) -> case db_user_exists(LUser, LServer, M) of
case M:is_user_exists(User, Server) of {error, _} ->
{error, Error} ->
?ERROR_MSG("The authentication module ~p returned "
"an error~nwhen checking user ~p in server "
"~p~nError message: ~p",
[M, User, Server, Error]),
false; false;
Else -> Else Else ->
Else
end end
end, end, auth_modules(LServer));
auth_modules(Server)). _ ->
false
end.
-spec is_user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe. -spec user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe.
user_exists_in_other_modules(Module, User, Server) ->
user_exists_in_other_modules_loop(
auth_modules(Server) -- [Module], User, Server).
is_user_exists_in_other_modules(Module, User, Server) -> user_exists_in_other_modules_loop([], _User, _Server) ->
is_user_exists_in_other_modules_loop(auth_modules(Server)
-- [Module],
User, Server).
is_user_exists_in_other_modules_loop([], _User,
_Server) ->
false; false;
is_user_exists_in_other_modules_loop([AuthModule user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
| AuthModules], case db_user_exists(User, Server, AuthModule) of
User, Server) -> true ->
case AuthModule:is_user_exists(User, Server) of true;
true -> true;
false -> false ->
is_user_exists_in_other_modules_loop(AuthModules, User, user_exists_in_other_modules_loop(AuthModules, User, Server);
Server); {error, _} ->
{error, Error} ->
?DEBUG("The authentication module ~p returned "
"an error~nwhen checking user ~p in server "
"~p~nError message: ~p",
[AuthModule, User, Server, Error]),
maybe maybe
end. end.
-spec remove_user(binary(), binary()) -> ok. -spec remove_user(binary(), binary()) -> ok.
%% @spec (User, Server) -> ok
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) -> remove_user(User, Server) ->
lists:foreach(fun (M) -> M:remove_user(User, Server) case validate_credentials(User, Server) of
end, {ok, LUser, LServer} ->
auth_modules(Server)), lists:foreach(
ejabberd_hooks:run(remove_user, jid:nameprep(Server), fun(Mod) -> db_remove_user(LUser, LServer, Mod) end,
[User, Server]), auth_modules(LServer)),
ok. ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
_Err ->
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error ok
%% @doc Try to remove user if the provided password is correct. end.
%% The removal is attempted in each auth method provided:
%% when one returns 'ok' the loop stops;
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
-spec remove_user(binary(), binary(), binary()) -> any().
-spec remove_user(binary(), binary(), password()) -> ok | {error, atom()}.
remove_user(User, Server, Password) -> remove_user(User, Server, Password) ->
R = lists:foldl(fun (_M, ok = Res) -> Res; case validate_credentials(User, Server, Password) of
(M, _) -> M:remove_user(User, Server, Password) {ok, LUser, LServer} ->
end, case lists:foldl(
error, auth_modules(Server)), fun (_, ok) ->
case R of ok;
(Mod, _) ->
case db_check_password(
LUser, <<"">>, LServer, Password,
<<"">>, undefined, Mod) of
true ->
db_remove_user(LUser, LServer, Mod);
false ->
{error, not_allowed}
end
end, {error, not_allowed}, auth_modules(Server)) of
ok -> ok ->
ejabberd_hooks:run(remove_user, jid:nameprep(Server), ejabberd_hooks:run(
[User, Server]); remove_user, LServer, [LUser, LServer]);
_ -> none Err ->
end, Err
R. end;
Err ->
Err
end.
%% @spec (IOList) -> non_negative_float()
%% @doc Calculate informational entropy. %% @doc Calculate informational entropy.
-spec entropy(iodata()) -> float().
entropy(B) -> entropy(B) ->
case binary_to_list(B) of case binary_to_list(B) of
"" -> 0.0; "" -> 0.0;
@ -497,15 +469,266 @@ backend_type(Mod) ->
_ -> Mod _ -> Mod
end. end.
-spec password_format(binary() | global) -> plain | scram.
password_format(LServer) -> password_format(LServer) ->
ejabberd_config:get_option({auth_password_format, LServer}, plain). ejabberd_config:get_option({auth_password_format, LServer}, plain).
%%%----------------------------------------------------------------------
%%% Backend calls
%%%----------------------------------------------------------------------
db_try_register(User, Server, Password, Mod) ->
case erlang:function_exported(Mod, try_register, 3) of
true ->
Password1 = case Mod:store_type(Server) of
scram -> password_to_scram(Password);
_ -> Password
end,
case use_cache(Mod, Server) of
true ->
case ets_cache:update(
?AUTH_CACHE, {User, Server}, {ok, Password},
fun() -> Mod:try_register(User, Server, Password1) end,
cache_nodes(Mod, Server)) of
{ok, _} -> ok;
{error, _} = Err -> Err
end;
false ->
Mod:try_register(User, Server, Password1)
end;
false ->
{error, not_allowed}
end.
db_set_password(User, Server, Password, Mod) ->
case erlang:function_exported(Mod, set_password, 3) of
true ->
Password1 = case Mod:store_type(Server) of
scram -> password_to_scram(Password);
_ -> Password
end,
case use_cache(Mod, Server) of
true ->
case ets_cache:update(
?AUTH_CACHE, {User, Server}, {ok, Password},
fun() -> Mod:set_password(User, Server, Password1) end,
cache_nodes(Mod, Server)) of
{ok, _} -> ok;
{error, _} = Err -> Err
end;
false ->
Mod:set_password(User, Server, Password1)
end;
false ->
{error, not_allowed}
end.
db_get_password(User, Server, Mod) ->
UseCache = use_cache(Mod, Server),
case erlang:function_exported(Mod, get_password, 2) of
false when UseCache ->
ets_cache:lookup(?AUTH_CACHE, {User, Server});
false ->
error;
true when UseCache ->
ets_cache:lookup(
?AUTH_CACHE, {User, Server},
fun() -> Mod:get_password(User, Server) end);
true ->
Mod:get_password(User, Server)
end.
db_user_exists(User, Server, Mod) ->
case db_get_password(User, Server, Mod) of
{ok, _} ->
true;
error ->
case Mod:store_type(Server) of
external ->
Mod:user_exists(User, Server);
_ ->
false
end
end.
db_check_password(User, AuthzId, Server, ProvidedPassword,
Digest, DigestFun, Mod) ->
case db_get_password(User, Server, Mod) of
{ok, ValidPassword} ->
match_passwords(ProvidedPassword, ValidPassword, Digest, DigestFun);
error ->
case {Mod:store_type(Server), use_cache(Mod, Server)} of
{external, true} ->
case ets_cache:update(
?AUTH_CACHE, {User, Server}, {ok, ProvidedPassword},
fun() ->
case Mod:check_password(
User, AuthzId, Server, ProvidedPassword) of
true ->
{ok, ProvidedPassword};
false ->
error
end
end, cache_nodes(Mod, Server)) of
{ok, _} ->
true;
error ->
false
end;
{external, false} ->
Mod:check_password(User, AuthzId, Server, ProvidedPassword);
_ ->
false
end
end.
db_remove_user(User, Server, Mod) ->
case erlang:function_exported(Mod, remove_user, 2) of
true ->
case Mod:remove_user(User, Server) of
ok ->
case use_cache(Mod, Server) of
true ->
ets_cache:delete(?AUTH_CACHE, {User, Server},
cache_nodes(Mod, Server));
false ->
ok
end;
{error, _} = Err ->
Err
end;
false ->
{error, not_allowed}
end.
db_get_users(Server, Opts, Mod) ->
case erlang:function_exported(Mod, get_users, 2) of
true ->
Mod:get_users(Server, Opts);
false ->
case use_cache(Mod, Server) of
true ->
ets_cache:fold(
fun({User, S}, {ok, _}, Users) when S == Server ->
[{User, Server}|Users];
(_, _, Users) ->
Users
end, [], ?AUTH_CACHE);
false ->
[]
end
end.
db_count_users(Server, Opts, Mod) ->
case erlang:function_exported(Mod, count_users, 2) of
true ->
Mod:count_users(Server, Opts);
false ->
case use_cache(Mod, Server) of
true ->
ets_cache:fold(
fun({_, S}, {ok, _}, Num) when S == Server ->
Num + 1;
(_, _, Num) ->
Num
end, 0, ?AUTH_CACHE);
false ->
0
end
end.
%%%----------------------------------------------------------------------
%%% SCRAM stuff
%%%----------------------------------------------------------------------
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = base64:decode(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
base64:decode(Scram#scram.storedkey) == StoredKey
end.
password_to_scram(Password) ->
password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(#scram{} = Password, _IterationCount) ->
Password;
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 = base64:encode(StoredKey),
serverkey = base64:encode(ServerKey),
salt = base64:encode(Salt),
iterationcount = IterationCount}.
%%%----------------------------------------------------------------------
%%% Cache stuff
%%%----------------------------------------------------------------------
-spec init_cache(map()) -> ok.
init_cache(HostModules) ->
case use_cache(HostModules) of
true ->
ets_cache:new(?AUTH_CACHE, cache_opts());
false ->
ets_cache:delete(?AUTH_CACHE)
end.
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
MaxSize = ejabberd_config:get_option(
auth_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
auth_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
auth_cache_life_time,
ejabberd_config:cache_life_time(global)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(map()) -> boolean().
use_cache(HostModules) ->
lists:any(
fun({Host, Modules}) ->
lists:any(fun(Module) ->
use_cache(Module, Host)
end, Modules)
end, maps:to_list(HostModules)).
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, LServer) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(LServer);
false ->
ejabberd_config:get_option(
{auth_use_cache, LServer},
ejabberd_config:use_cache(LServer))
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, LServer) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(LServer);
false -> ejabberd_cluster:get_nodes()
end.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
-spec auth_modules() -> [module()]. -spec auth_modules() -> [{binary(), module()}].
auth_modules() -> auth_modules() ->
lists:usort(lists:flatmap(fun auth_modules/1, ?MYHOSTS)). lists:flatmap(
fun(Host) ->
[{Host, Mod} || Mod <- auth_modules(Host)]
end, ?MYHOSTS).
-spec auth_modules(binary()) -> [module()]. -spec auth_modules(binary()) -> [module()].
auth_modules(Server) -> auth_modules(Server) ->
@ -516,6 +739,65 @@ auth_modules(Server) ->
(misc:atom_to_binary(M))/binary>>) (misc:atom_to_binary(M))/binary>>)
|| M <- Methods]. || M <- Methods].
-spec match_passwords(password(), password(),
binary(), digest_fun() | undefined) -> boolean().
match_passwords(Password, #scram{} = Scram, <<"">>, undefined) ->
is_password_scram_valid(Password, Scram);
match_passwords(Password, #scram{} = Scram, Digest, DigestFun) ->
StoredKey = base64:decode(Scram#scram.storedkey),
DigRes = if Digest /= <<"">> ->
Digest == DigestFun(StoredKey);
true -> false
end,
if DigRes ->
true;
true ->
StoredKey == Password andalso Password /= <<"">>
end;
match_passwords(ProvidedPassword, ValidPassword, <<"">>, undefined) ->
ProvidedPassword == ValidPassword andalso ProvidedPassword /= <<"">>;
match_passwords(ProvidedPassword, ValidPassword, Digest, DigestFun) ->
DigRes = if Digest /= <<"">> ->
Digest == DigestFun(ValidPassword);
true -> false
end,
if DigRes ->
true;
true ->
ValidPassword == ProvidedPassword andalso ProvidedPassword /= <<"">>
end.
-spec validate_credentials(binary(), binary()) ->
{ok, binary(), binary()} | {error, invalid_jid}.
validate_credentials(User, Server) ->
validate_credentials(User, Server, #scram{}).
-spec validate_credentials(binary(), binary(), password()) ->
{ok, binary(), binary()} | {error, invalid_jid | invalid_password}.
validate_credentials(_User, _Server, <<"">>) ->
{error, invalid_password};
validate_credentials(User, Server, Password) ->
case jid:nodeprep(User) of
error ->
{error, invalid_jid};
LUser ->
case jid:nameprep(Server) of
error ->
{error, invalid_jid};
LServer ->
if is_record(Password, scram) ->
{ok, LUser, LServer};
true ->
case jid:resourceprep(Password) of
error ->
{error, invalid_password};
_ ->
{ok, LUser, LServer}
end
end
end
end.
export(Server) -> export(Server) ->
ejabberd_auth_mnesia:export(Server). ejabberd_auth_mnesia:export(Server).
@ -534,6 +816,13 @@ import(Server, {sql, _}, riak, <<"users">>, Fields) ->
import(_LServer, {sql, _}, sql, <<"users">>, _) -> import(_LServer, {sql, _}, sql, <<"users">>, _) ->
ok. ok.
-spec opt_type(auth_method) -> fun((atom() | [atom()]) -> [atom()]);
(auth_password_format) -> fun((plain | scram) -> plain | scram);
(auth_use_cache) -> fun((boolean()) -> boolean());
(auth_cache_missed) -> fun((boolean()) -> boolean());
(auth_cache_life_time) -> fun((timeout()) -> timeout());
(auth_cache_size) -> fun((timeout()) -> timeout());
(atom()) -> [atom()].
opt_type(auth_method) -> opt_type(auth_method) ->
fun (V) when is_list(V) -> fun (V) when is_list(V) ->
lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V); lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V);
@ -543,4 +832,20 @@ opt_type(auth_password_format) ->
fun (plain) -> plain; fun (plain) -> plain;
(scram) -> scram (scram) -> scram
end; end;
opt_type(_) -> [auth_method, auth_password_format]. opt_type(auth_use_cache) ->
fun(B) when is_boolean(B) -> B end;
opt_type(auth_cache_missed) ->
fun(B) when is_boolean(B) -> B end;
opt_type(auth_cache_life_time) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(auth_cache_size) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(_) ->
[auth_method, auth_password_format, auth_use_cache,
auth_cache_missed, auth_cache_life_time, auth_cache_size].

View File

@ -40,15 +40,9 @@
unregister_connection/3 unregister_connection/3
]). ]).
-export([login/2, set_password/3, check_password/4, -export([login/2, check_password/4, user_exists/2,
check_password/6, try_register/3, get_users/2, count_users/2, store_type/1,
dirty_get_registered_users/0, get_vh_registered_users/1, plain_password_required/1, opt_type/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]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -139,17 +133,9 @@ unregister_connection(_SID,
%% --------------------------------- %% ---------------------------------
%% Specific anonymous auth functions %% Specific anonymous auth functions
%% --------------------------------- %% ---------------------------------
check_password(User, _AuthzId, Server, _Password) ->
%% 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) ->
case case
ejabberd_auth:is_user_exists_in_other_modules(?MODULE, ejabberd_auth:user_exists_in_other_modules(?MODULE,
User, Server) User, Server)
of of
%% If user exists in other module, reject anonnymous authentication %% If user exists in other module, reject anonnymous authentication
@ -173,69 +159,25 @@ login(User, Server) ->
end end
end. end.
%% When anonymous login is enabled, check that the user is permanent before get_users(Server, _) ->
%% changing its password [{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of
true -> ok;
false -> {error, not_allowed}
end.
%% When anonymous login is enabled, check if permanent users are allowed on count_users(Server, Opts) ->
%% the server: length(get_users(Server, Opts)).
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() -> []. user_exists(User, Server) ->
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) ->
anonymous_user_exist(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. store_type(_) ->
external.
plain_password_required() -> false.
store_type() ->
plain.
-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) -> opt_type(allow_multiple_connections) ->
fun (V) when is_boolean(V) -> V end; fun (V) when is_boolean(V) -> V end;
opt_type(anonymous_protocol) -> opt_type(anonymous_protocol) ->

View File

@ -32,14 +32,8 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4, -export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3, try_register/3, user_exists/2, remove_user/2,
dirty_get_registered_users/0, get_vh_registered_users/1, store_type/1, plain_password_required/1, opt_type/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]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -49,271 +43,60 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
start(Host) -> start(Host) ->
Cmd = ejabberd_config:get_option({extauth_program, Host}, "extauth"), Cmd = ejabberd_config:get_option({extauth_program, Host}, "extauth"),
extauth:start(Host, Cmd), extauth:start(Host, Cmd).
check_cache_last_options(Host),
ejabberd_auth_mnesia:start(Host).
stop(Host) -> stop(Host) ->
extauth:stop(Host), extauth:stop(Host).
ejabberd_auth_mnesia:stop(Host).
check_cache_last_options(Server) -> plain_password_required(_) -> true.
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. store_type(_) -> external.
store_type() -> external.
check_password(User, AuthzId, Server, Password) -> check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User -> if AuthzId /= <<>> andalso AuthzId /= User ->
false; false;
true -> true ->
case get_cache_option(Server) of check_password_extauth(User, AuthzId, Server, Password)
false ->
check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
check_password_cache(User, AuthzId, Server, Password,
CacheTime)
end
end. end.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of case extauth:set_password(User, Server, Password) of
true -> true -> ok;
set_password_mnesia(User, Server, Password), ok; _ -> {error, db_failure}
_ -> {error, unknown_problem}
end. end.
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
case get_cache_option(Server) of extauth:try_register(User, Server, Password).
false -> try_register_extauth(User, Server, Password);
{true, _CacheTime} ->
try_register_external_cache(User, Server, Password)
end.
dirty_get_registered_users() -> user_exists(User, Server) ->
ejabberd_auth_mnesia:dirty_get_registered_users(). try extauth:user_exists(User, Server) of
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 Res -> Res
catch catch
_:Error -> {error, Error} _:Error ->
?ERROR_MSG("external authentication program failure: ~p",
[Error]),
{error, db_failure}
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
case extauth:remove_user(User, Server) of case extauth:remove_user(User, Server) of
false -> false; false -> {error, not_allowed};
true -> true -> ok
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_mnesia:remove_user(User, Server)
end
end. 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) -> check_password_extauth(User, _AuthzId, Server, Password) ->
extauth:check_password(User, Server, Password) andalso extauth:check_password(User, Server, Password) andalso
Password /= <<"">>. Password /= <<"">>.
%% @spec (User, Server, Password) -> true | false -spec opt_type(extauth_cache) -> fun((false | non_neg_integer()) ->
try_register_extauth(User, Server, Password) -> false | non_neg_integer());
extauth:try_register(User, Server, Password). (extauth_program) -> fun((binary()) -> string());
(atom()) -> [atom()].
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).
opt_type(extauth_cache) -> 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; fun (false) -> false;
(I) when is_integer(I), I >= 0 -> I (I) when is_integer(I), I >= 0 -> I
end; end;

View File

@ -37,13 +37,9 @@
handle_cast/2, terminate/2, code_change/3]). handle_cast/2, terminate/2, code_change/3]).
-export([start/1, stop/1, start_link/1, set_password/3, -export([start/1, stop/1, start_link/1, set_password/3,
check_password/4, check_password/6, try_register/3, check_password/4, user_exists/2,
dirty_get_registered_users/0, get_vh_registered_users/1, get_users/2, count_users/2,
get_vh_registered_users/2, store_type/1, plain_password_required/1,
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]). opt_type/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
@ -112,9 +108,9 @@ init(Host) ->
State#state.password, State#state.tls_options), State#state.password, State#state.tls_options),
{ok, State}. {ok, State}.
plain_password_required() -> true. plain_password_required(_) -> true.
store_type() -> external. store_type(_) -> external.
check_password(User, AuthzId, Server, Password) -> check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User -> if AuthzId /= <<>> andalso AuthzId /= User ->
@ -129,60 +125,34 @@ check_password(User, AuthzId, Server, Password) ->
end end
end. end.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> {error, user_not_found}; false -> {error, notfound};
DN -> DN ->
eldap_pool:modify_passwd(State#state.eldap_id, DN, case eldap_pool:modify_passwd(State#state.eldap_id, DN,
Password) Password) of
ok -> ok;
_Err -> {error, db_failure}
end
end. end.
%% @spec (User, Server, Password) -> {error, not_allowed} get_users(Server, []) ->
try_register(_User, _Server, _Password) -> case catch get_users_ldap(Server) of
{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
{'EXIT', _} -> []; {'EXIT', _} -> [];
Result -> Result Result -> Result
end. end.
get_vh_registered_users(Server, _) -> count_users(Server, Opts) ->
get_vh_registered_users(Server). length(get_users(Server, Opts)).
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) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error} %% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) -> user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of case catch user_exists_ldap(User, Server) of
{'EXIT', Error} -> {error, Error}; {'EXIT', _Error} -> {error, db_failure};
Result -> Result Result -> Result
end. end.
remove_user(_User, _Server) -> {error, not_allowed}.
remove_user(_User, _Server, _Password) -> not_allowed.
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -199,7 +169,7 @@ check_password_ldap(User, Server, Password) ->
end end
end. end.
get_vh_registered_users_ldap(Server) -> get_users_ldap(Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
UIDs = State#state.uids, UIDs = State#state.uids,
Eldap_ID = State#state.eldap_id, Eldap_ID = State#state.eldap_id,
@ -248,7 +218,7 @@ get_vh_registered_users_ldap(Server) ->
_ -> [] _ -> []
end. end.
is_user_exists_ldap(User, Server) -> user_exists_ldap(User, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE), {ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of case find_user_dn(User, State) of
false -> false; false -> false;
@ -393,6 +363,10 @@ parse_options(Host) ->
sfilter = SearchFilter, lfilter = LocalFilter, sfilter = SearchFilter, lfilter = LocalFilter,
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}. 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) -> opt_type(ldap_dn_filter) ->
fun ([{DNF, DNFA}]) -> fun ([{DNF, DNFA}]) ->
NewDNFA = case DNFA of NewDNFA = case DNFA of

View File

@ -31,15 +31,11 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4, -export([start/1, stop/1, set_password/3, try_register/3,
check_password/6, try_register/3, get_users/2, init_db/0,
dirty_get_registered_users/0, get_vh_registered_users/1, count_users/2, get_password/2,
get_vh_registered_users/2, init_db/0, remove_user/2, store_type/1, export/1, import/2,
get_vh_registered_users_number/1, plain_password_required/1, use_cache/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([need_transform/1, transform/1]). -export([need_transform/1, transform/1]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
@ -52,8 +48,6 @@
-record(reg_users_counter, {vhost = <<"">> :: binary(), -record(reg_users_counter, {vhost = <<"">> :: binary(),
count = 0 :: integer() | '$1'}). count = 0 :: integer() | '$1'}).
-define(SALT_LENGTH, 16).
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% API %%% API
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -67,14 +61,14 @@ stop(_Host) ->
init_db() -> init_db() ->
ejabberd_mnesia:create(?MODULE, passwd, ejabberd_mnesia:create(?MODULE, passwd,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, record_info(fields, passwd)}]), {attributes, record_info(fields, passwd)}]),
ejabberd_mnesia:create(?MODULE, reg_users_counter, ejabberd_mnesia:create(?MODULE, reg_users_counter,
[{ram_copies, [node()]}, [{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]). {attributes, record_info(fields, reg_users_counter)}]).
update_reg_users_counter_table(Server) -> update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server), Set = get_users(Server, []),
Size = length(Set), Size = length(Set),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
F = fun () -> F = fun () ->
@ -83,309 +77,157 @@ update_reg_users_counter_table(Server) ->
end, end,
mnesia:sync_dirty(F). mnesia:sync_dirty(F).
plain_password_required() -> use_cache(Host) ->
is_scrammed(). case mnesia:table_info(passwd, storage_type) of
disc_only_copies ->
store_type() -> ejabberd_config:get_option(
ejabberd_auth:password_format(?MYNAME). {auth_use_cache, Host},
ejabberd_config:use_cache(Host));
check_password(User, AuthzId, Server, Password) -> _ ->
if AuthzId /= <<>> andalso AuthzId /= User -> false
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
end. end.
check_password(User, AuthzId, Server, Password, Digest, plain_password_required(Server) ->
DigestGen) -> store_type(Server) == scram.
if AuthzId /= <<>> andalso AuthzId /= User ->
false; store_type(Server) ->
true -> ejabberd_auth:password_format(Server).
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.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
LUser = jid:nodeprep(User), US = {User, Server},
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 () -> F = fun () ->
Password2 = case is_scrammed() and is_binary(Password) mnesia:write(#passwd{us = US, password = Password})
of
true -> password_to_scram(Password);
false -> Password
end, end,
mnesia:write(#passwd{us = US, password = Password2}) case mnesia:transaction(F) of
end, {atomic, ok} ->
{atomic, ok} = mnesia:transaction(F), ok;
ok {aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end. end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason} try_register(User, Server, Password) ->
try_register(User, Server, PasswordList) -> US = {User, Server},
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 () -> F = fun () ->
case mnesia:read({passwd, US}) of case mnesia:read({passwd, US}) of
[] -> [] ->
Password2 = case is_scrammed() and mnesia:write(#passwd{us = US, password = Password}),
is_binary(Password) mnesia:dirty_update_counter(reg_users_counter, Server, 1),
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; ok;
[_E] -> exists [_] ->
end {error, exists}
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},
F = fun () ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, LServer,
-1)
end,
mnesia:transaction(F),
ok.
%% @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},
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
end end
end, end,
case mnesia:transaction(F) of case mnesia:transaction(F) of
{atomic, ok} -> ok; {atomic, Res} ->
{atomic, Res} -> Res; Res;
_ -> bad_request {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. end.
need_transform(#passwd{us = {U, S}, password = Pass}) -> need_transform(#passwd{us = {U, S}, password = Pass}) ->
if is_binary(Pass) -> if is_binary(Pass) ->
IsScrammed = is_scrammed(), case store_type(S) of
if IsScrammed -> scram ->
?INFO_MSG("Passwords in Mnesia table 'passwd' " ?INFO_MSG("Passwords in Mnesia table 'passwd' "
"will be SCRAM'ed", []); "will be SCRAM'ed", []),
true -> true;
ok plain ->
end, false
IsScrammed; end;
is_record(Pass, scram) -> is_record(Pass, scram) ->
case is_scrammed() of case store_type(S) of
true -> scram ->
next; false;
false -> plain ->
?WARNING_MSG("Some passwords were stored in the database " ?WARNING_MSG("Some passwords were stored in the database "
"as SCRAM, but 'auth_password_format' " "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 false
end; end;
is_list(U) orelse is_list(S) orelse is_list(Pass) -> 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(R#passwd{us = NewUS, password = NewPass});
transform(#passwd{us = {U, S}, password = Password} = P) transform(#passwd{us = {U, S}, password = Password} = P)
when is_binary(Password) -> when is_binary(Password) ->
case is_scrammed() of case store_type(S) of
true -> scram ->
case jid:resourceprep(Password) of case jid:resourceprep(Password) of
error -> error ->
?ERROR_MSG("SASLprep failed for password of user ~s@~s", ?ERROR_MSG("SASLprep failed for password of user ~s@~s",
[U, S]), [U, S]),
P; P;
_ -> _ ->
Scram = password_to_scram(Password), Scram = ejabberd_auth:password_to_scram(Password),
P#passwd{password = Scram} P#passwd{password = Scram}
end; end;
false -> plain ->
P P
end; end;
transform(#passwd{password = Password} = P) transform(#passwd{password = Password} = P)
when is_record(Password, scram) -> when is_record(Password, scram) ->
P. 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) -> export(_Server) ->
[{passwd, [{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password}) fun(Host, #passwd{us = {LUser, LServer}, password = Password})

View File

@ -30,14 +30,8 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4, -export([start/1, stop/1, check_password/4,
check_password/6, try_register/3, user_exists/2, store_type/1, plain_password_required/1,
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]). opt_type/1]).
start(_Host) -> start(_Host) ->
@ -46,13 +40,6 @@ start(_Host) ->
stop(_Host) -> stop(_Host) ->
ok. 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) -> check_password(User, AuthzId, Host, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User -> if AuthzId /= <<>> andalso AuthzId /= User ->
false; false;
@ -70,26 +57,7 @@ check_password(User, AuthzId, Host, Password) ->
end end
end. end.
try_register(_User, _Server, _Password) -> user_exists(User, Host) ->
{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) ->
Service = get_pam_service(Host), Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of UserInfo = case get_pam_userinfotype(Host) of
username -> User; username -> User;
@ -97,16 +65,13 @@ is_user_exists(User, Host) ->
end, end,
case catch epam:acct_mgmt(Service, UserInfo) of case catch epam:acct_mgmt(Service, UserInfo) of
true -> true; true -> true;
_ -> false false -> false;
_Err -> {error, db_failure}
end. end.
remove_user(_User, _Server) -> {error, not_allowed}. plain_password_required(_) -> true.
remove_user(_User, _Server, _Password) -> not_allowed. store_type(_) -> external.
plain_password_required() -> true.
store_type() -> external.
%%==================================================================== %%====================================================================
%% Internal functions %% Internal functions
@ -117,6 +82,9 @@ get_pam_service(Host) ->
get_pam_userinfotype(Host) -> get_pam_userinfotype(Host) ->
ejabberd_config:get_option({pam_userinfotype, Host}, username). 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_service) -> fun iolist_to_binary/1;
opt_type(pam_userinfotype) -> opt_type(pam_userinfotype) ->
fun (username) -> username; fun (username) -> username;

View File

@ -32,15 +32,10 @@
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
%% External exports %% External exports
-export([start/1, stop/1, set_password/3, check_password/4, -export([start/1, stop/1, set_password/3, try_register/3,
check_password/6, try_register/3, get_users/2, count_users/2,
dirty_get_registered_users/0, get_vh_registered_users/1, get_password/2, remove_user/2, store_type/1, export/1, import/2,
get_vh_registered_users/2, plain_password_required/1]).
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([passwd_schema/0]). -export([passwd_schema/0]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
@ -49,258 +44,65 @@
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', -record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
password = <<"">> :: binary() | scram() | '_'}). password = <<"">> :: binary() | scram() | '_'}).
-define(SALT_LENGTH, 16).
start(_Host) -> start(_Host) ->
ok. ok.
stop(_Host) -> stop(_Host) ->
ok. ok.
plain_password_required() -> plain_password_required(Server) ->
case is_scrammed() of store_type(Server) == scram.
false -> false;
true -> true
end.
store_type() -> store_type(Server) ->
case is_scrammed() of ejabberd_auth:password_format(Server).
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
passwd_schema() -> passwd_schema() ->
{record_info(fields, passwd), #passwd{}}. {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) -> set_password(User, Server, Password) ->
LUser = jid:nodeprep(User), ejabberd_riak:put(#passwd{us = {User, Server}, password = Password},
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(), passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}]) [{'2i', [{<<"host">>, Server}]}]).
end.
try_register(User, Server, PasswordList) -> try_register(User, Server, Password) ->
LUser = jid:nodeprep(User), US = {User, Server},
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 case ejabberd_riak:get(passwd, passwd_schema(), US) of
{error, notfound} -> {error, notfound} ->
Password2 = case is_scrammed() and ejabberd_riak:put(#passwd{us = US, password = Password},
is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
{atomic, ejabberd_riak:put(
#passwd{us = US,
password = Password2},
passwd_schema(), passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])}; [{'2i', [{<<"host">>, Server}]}]);
{ok, _} -> {ok, _} ->
exists; {error, exists};
Err -> {error, _} = Err ->
{atomic, Err} Err
end
end. end.
dirty_get_registered_users() -> get_users(Server, _) ->
lists:flatmap( case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, Server) of
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
{ok, Users} -> {ok, Users} ->
Users; Users;
_ -> _ ->
[] []
end. end.
get_vh_registered_users(Server, _) -> count_users(Server, _) ->
get_vh_registered_users(Server). case ejabberd_riak:count_by_index(passwd, <<"host">>, Server) of
get_vh_registered_users_number(Server) ->
LServer = jid:nameprep(Server),
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
{ok, N} -> {ok, N} ->
N; N;
_ -> _ ->
0 0
end. end.
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) -> get_password(User, Server) ->
LUser = jid:nodeprep(User), case ejabberd_riak:get(passwd, passwd_schema(), {User, Server}) of
LServer = jid:nameprep(Server), {ok, Password} ->
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of {ok, Password};
{ok, #passwd{password = Password}} {error, _} ->
when is_binary(Password) -> error
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
end. end.
remove_user(User, Server) -> remove_user(User, Server) ->
LUser = jid:nodeprep(User), ejabberd_riak:delete(passwd, {User, Server}).
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.
export(_Server) -> export(_Server) ->
[{passwd, [{passwd,

View File

@ -30,16 +30,12 @@
-author('alexey@process-one.net'). -author('alexey@process-one.net').
-behaviour(ejabberd_auth). -behaviour(ejabberd_auth).
-behaviour(ejabberd_config).
-export([start/1, stop/1, set_password/3, check_password/4, -export([start/1, stop/1, set_password/3, try_register/3,
check_password/6, try_register/3, get_users/2, count_users/2, get_password/2,
dirty_get_registered_users/0, get_vh_registered_users/1, remove_user/2, store_type/1, plain_password_required/1,
get_vh_registered_users/2, convert_to_scram/1, opt_type/1]).
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]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -54,397 +50,84 @@ start(_Host) -> ok.
stop(_Host) -> ok. stop(_Host) -> ok.
plain_password_required() -> plain_password_required(Server) ->
case is_scrammed() of store_type(Server) == scram.
false -> false;
true -> true
end.
store_type() -> store_type(Server) ->
case is_scrammed() of ejabberd_auth:password_format(Server).
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
%% @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) -> set_password(User, Server, Password) ->
LServer = jid:nameprep(Server), F = fun() ->
LUser = jid:nodeprep(User), if is_record(Password, scram) ->
LPassword = jid:resourceprep(Password), set_password_scram_t(
if (LUser == error) or (LServer == error) -> User,
{error, invalid_jid}; Password#scram.storedkey, Password#scram.serverkey,
(LUser == <<>>) or (LServer == <<>>) -> Password#scram.salt, Password#scram.iterationcount);
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true -> true ->
case is_scrammed() of set_password_t(User, Password)
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 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. end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) -> try_register(User, Server, Password) ->
LServer = jid:nameprep(Server), Res = if is_record(Password, scram) ->
LUser = jid:nodeprep(User), add_user_scram(
LPassword = jid:resourceprep(Password), Server, User,
if (LUser == error) or (LServer == error) -> Password#scram.storedkey, Password#scram.serverkey,
{error, invalid_jid}; Password#scram.salt, Password#scram.iterationcount);
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
LPassword == error and not is_record(Password, scram) ->
{error, invalid_password};
true -> true ->
case is_scrammed() of add_user(Server, User, Password)
true ->
Scram = case is_record(Password, scram) of
true -> Password;
false -> password_to_scram(Password)
end, end,
case catch sql_queries:add_user_scram( case Res of
LServer, {updated, 1} -> ok;
LUser, _ -> {error, exists}
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
end. end.
dirty_get_registered_users() -> get_users(Server, Opts) ->
Servers = ejabberd_config:get_vh_by_auth_method(sql), case list_users(Server, Opts) of
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} -> {selected, Res} ->
[{U, LServer} || {U} <- Res]; [{U, Server} || {U} <- Res];
_ -> [] _ -> []
end
end. end.
get_vh_registered_users(Server, Opts) -> count_users(Server, Opts) ->
case jid:nameprep(Server) of case users_number(Server, Opts) 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}]} -> {selected, [{Res}]} ->
Res; Res;
_Other -> 0 _Other -> 0
end
end. end.
get_password(User, Server) -> get_password(User, Server) ->
LServer = jid:nameprep(Server), case get_password_scram(Server, User) of
LUser = jid:nodeprep(User), {selected, [{Password, <<>>, <<>>, 0}]} ->
if (LUser == error) or (LServer == error) -> {ok, Password};
false; {selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
(LUser == <<>>) or (LServer == <<>>) -> {ok, #scram{storedkey = StoredKey,
false; serverkey = ServerKey,
true -> salt = Salt,
case is_scrammed() of iterationcount = IterationCount}};
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
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, []} -> {selected, []} ->
false; %% Account does not exist error;
{error, Error} -> {error, Error} Err ->
catch ?ERROR_MSG("Failed to read password for user ~s@~s: ~p",
_:B -> {error, B} [User, Server, Err]),
end error
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) -> remove_user(User, Server) ->
LServer = jid:nameprep(Server), case del_user(Server, User) of
LUser = jid:nodeprep(User), {updated, _} ->
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; ok;
false -> not_allowed Err ->
end; ?ERROR_MSG("failed to delete user ~s@~s: ~p",
false -> [User, Server, Err]),
F = fun () -> {error, db_failure}
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
end. end.
-define(BATCH_SIZE, 1000). -define(BATCH_SIZE, 1000).
@ -459,6 +142,105 @@ set_password_scram_t(LUser,
"salt=%(Salt)s", "salt=%(Salt)s",
"iterationcount=%(IterationCount)d"]). "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) -> convert_to_scram(Server) ->
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
if if
@ -485,7 +267,7 @@ convert_to_scram(Server) ->
"password of user ~s@~s", "password of user ~s@~s",
[LUser, LServer]); [LUser, LServer]);
_ -> _ ->
Scram = password_to_scram(Password), Scram = ejabberd_auth:password_to_scram(Password),
set_password_scram_t( set_password_scram_t(
LUser, LUser,
Scram#scram.storedkey, Scram#scram.storedkey,
@ -498,10 +280,16 @@ convert_to_scram(Server) ->
Err -> {bad_reply, Err} Err -> {bad_reply, Err}
end end
end, end,
case sql_queries:sql_transaction(LServer, F) of case ejabberd_sql:sql_transaction(LServer, F) of
{atomic, ok} -> ok; {atomic, ok} -> ok;
{atomic, continue} -> convert_to_scram(Server); {atomic, continue} -> convert_to_scram(Server);
{atomic, Error} -> {error, Error}; {atomic, Error} -> {error, Error};
Error -> Error Error -> Error
end end
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].

View File

@ -46,7 +46,7 @@
reject_unauthenticated_packet/2, process_closed/2, reject_unauthenticated_packet/2, process_closed/2,
process_terminated/2, process_info/2]). process_terminated/2, process_info/2]).
%% API %% 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, open_session/1, call/3, send/2, close/1, close/2, stop/1,
reply/2, copy_state/2, set_timeout/2, route/2, reply/2, copy_state/2, set_timeout/2, route/2,
host_up/1, host_down/1]). host_up/1, host_down/1]).
@ -54,6 +54,7 @@
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("mod_roster.hrl").
-define(SETS, gb_sets). -define(SETS, gb_sets).
@ -93,23 +94,13 @@ reply(Ref, Reply) ->
get_presence(Ref) -> get_presence(Ref) ->
call(Ref, get_presence, 1000). call(Ref, get_presence, 1000).
-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none. -spec resend_presence(pid()) -> ok.
get_subscription(#jid{} = From, State) -> resend_presence(Pid) ->
get_subscription(jid:tolower(From), State); resend_presence(Pid, undefined).
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 get_subscribed(pid()) -> [ljid()]. -spec resend_presence(pid(), jid() | undefined) -> ok.
%% Return list of all available resources of contacts resend_presence(Pid, To) ->
get_subscribed(Ref) -> route(Pid, {resend_presence, To}).
call(Ref, get_subscribed, 1000).
-spec close(pid()) -> ok; -spec close(pid()) -> ok;
(state()) -> state(). (state()) -> state().
@ -183,8 +174,7 @@ host_down(Host) ->
copy_state(#{owner := Owner} = NewState, copy_state(#{owner := Owner} = NewState,
#{jid := JID, resource := Resource, sid := {Time, _}, #{jid := JID, resource := Resource, sid := {Time, _},
auth_module := AuthModule, lserver := LServer, auth_module := AuthModule, lserver := LServer,
pres_t := PresT, pres_a := PresA, pres_a := PresA} = OldState) ->
pres_f := PresF} = OldState) ->
State1 = case OldState of State1 = case OldState of
#{pres_last := Pres, pres_timestamp := PresTS} -> #{pres_last := Pres, pres_timestamp := PresTS} ->
NewState#{pres_last => Pres, pres_timestamp => PresTS}; NewState#{pres_last => Pres, pres_timestamp => PresTS};
@ -196,8 +186,7 @@ copy_state(#{owner := Owner} = NewState,
conn => Conn, conn => Conn,
sid => {Time, Owner}, sid => {Time, Owner},
auth_module => AuthModule, auth_module => AuthModule,
pres_t => PresT, pres_a => PresA, pres_a => PresA},
pres_f => PresF},
ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]). ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]).
-spec open_session(state()) -> {ok, state()} | state(). -spec open_session(state()) -> {ok, state()} | state().
@ -238,10 +227,17 @@ process_info(#{lserver := LServer} = State, {route, Packet}) ->
true -> true ->
State1 State1
end; end;
process_info(State, force_update_presence) -> process_info(#{jid := JID} = State, {resend_presence, To}) ->
case maps:get(pres_last, State, error) of case maps:get(pres_last, State, error) of
error -> State; 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; end;
process_info(State, Info) -> process_info(State, Info) ->
?WARNING_MSG("got unexpected info: ~p", [Info]), ?WARNING_MSG("got unexpected info: ~p", [Info]),
@ -390,15 +386,11 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
allow -> allow ->
State1 = open_session(State#{resource => Resource, State1 = open_session(State#{resource => Resource,
sid => ejabberd_sm:make_sid()}), sid => ejabberd_sm:make_sid()}),
LBJID = jid:remove_resource(jid:tolower(JID)), State2 = ejabberd_hooks:run_fold(
PresF = ?SETS:add_element(LBJID, maps:get(pres_f, State1)), c2s_session_opened, LServer, 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, []),
?INFO_MSG("(~s) Opened c2s session for ~s", ?INFO_MSG("(~s) Opened c2s session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]), [SockMod:pp(Socket), jid:encode(JID)]),
{ok, State3}; {ok, State2};
deny -> deny ->
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]), ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
?INFO_MSG("(~s) Forbidden c2s session for ~s", ?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]). ejabberd_hooks:run_fold(c2s_handle_send, LServer, State, [Pkt, Result]).
init([State, Opts]) -> init([State, Opts]) ->
Access = gen_mod:get_opt(access, Opts, all), Access = proplists:get_value(access, Opts, all),
Shaper = gen_mod:get_opt(shaper, Opts, none), Shaper = proplists:get_value(shaper, Opts, none),
TLSOpts1 = lists:filter( TLSOpts1 = lists:filter(
fun({certfile, _}) -> true; fun({certfile, _}) -> true;
({ciphers, _}) -> true; ({ciphers, _}) -> true;
@ -513,8 +505,6 @@ init([State, Opts]) ->
tls_enabled => TLSEnabled, tls_enabled => TLSEnabled,
tls_verify => TLSVerify, tls_verify => TLSVerify,
pres_a => ?SETS:new(), pres_a => ?SETS:new(),
pres_f => ?SETS:new(),
pres_t => ?SETS:new(),
zlib => Zlib, zlib => Zlib,
lang => ?MYLANG, lang => ?MYLANG,
server => ?MYNAME, server => ?MYNAME,
@ -532,9 +522,6 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
end, end,
reply(From, Pres), reply(From, Pres),
State; State;
handle_call(get_subscribed, From, #{pres_f := PresF} = State) ->
reply(From, ?SETS:to_list(PresF)),
State;
handle_call(Request, From, #{lserver := LServer} = State) -> handle_call(Request, From, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold( ejabberd_hooks:run_fold(
c2s_handle_call, LServer, State, [Request, From]). 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()}. -spec process_presence_in(state(), presence()) -> {boolean(), state()}.
process_presence_in(#{lserver := LServer, pres_a := PresA} = State0, 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]), State = ejabberd_hooks:run_fold(c2s_presence_in, LServer, State0, [Pres]),
case T of case T of
probe -> probe ->
NewState = add_to_pres_a(State, From), route_probe_reply(From, State),
route_probe_reply(From, To, NewState), {false, State};
{false, NewState};
error -> error ->
A = ?SETS:del_element(jid:tolower(From), PresA), A = ?SETS:del_element(jid:tolower(From), PresA),
{true, State#{pres_a => A}}; {true, State#{pres_a => A}};
_ -> _ ->
case privacy_check_packet(State, Pres, in) of case privacy_check_packet(State, Pres, in) of
allow -> allow ->
NewState = add_to_pres_a(State, From), {true, State};
{true, NewState};
deny -> deny ->
{false, State} {false, State}
end end
end. end.
-spec route_probe_reply(jid(), jid(), state()) -> ok. -spec route_probe_reply(jid(), state()) -> ok.
route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF, route_probe_reply(From, #{jid := To,
pres_last := LastPres, pres_last := LastPres,
pres_timestamp := TS} = State) -> pres_timestamp := TS} = State) ->
LFrom = jid:tolower(From), {LUser, LServer, LResource} = jid:tolower(To),
LBFrom = jid:remove_resource(LFrom), IsAnotherResource = case jid:tolower(From) of
case ?SETS:is_element(LFrom, PresF) {LUser, LServer, R} when R /= LResource -> true;
orelse ?SETS:is_element(LBFrom, PresF) of _ -> false
true -> end,
%% To is my JID Subscription = get_subscription(To, From),
if IsAnotherResource orelse
Subscription == both orelse Subscription == from ->
Packet = xmpp_util:add_delay_info(LastPres, To, TS), Packet = xmpp_util:add_delay_info(LastPres, To, TS),
case privacy_check_packet(State, Packet, out) of case privacy_check_packet(State, Packet, out) of
deny -> deny ->
@ -627,19 +614,12 @@ route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
ejabberd_hooks:run(presence_probe_hook, ejabberd_hooks:run(presence_probe_hook,
LServer, LServer,
[From, To, self()]), [From, To, self()]),
%% Don't route a presence probe to oneself ejabberd_router:route(xmpp:set_from_to(Packet, To, From))
case From == To of end;
false ->
ejabberd_router:route(
xmpp:set_from_to(Packet, To, From));
true -> true ->
ok ok
end
end; end;
false -> route_probe_reply(_, _) ->
ok
end;
route_probe_reply(_, _, _) ->
ok. ok.
-spec process_presence_out(state(), presence()) -> state(). -spec process_presence_out(state(), presence()) -> state().
@ -675,11 +655,22 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
State; State;
allow -> allow ->
ejabberd_router:route(Pres), ejabberd_router:route(Pres),
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 A = case Type of
available -> ?SETS:add_element(LTo, PresA); available -> ?SETS:add_element(LTo, PresA);
unavailable -> ?SETS:del_element(LTo, PresA) unavailable -> ?SETS:del_element(LTo, PresA)
end, end,
State#{pres_a => A} State#{pres_a => A};
true ->
State
end;
true ->
State
end
end. end.
-spec process_self_presence(state(), presence()) -> state(). -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). ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
-spec broadcast_presence_unavailable(state(), presence()) -> state(). -spec broadcast_presence_unavailable(state(), presence()) -> state().
broadcast_presence_unavailable(#{pres_a := PresA} = State, Pres) -> broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
JIDs = filter_blocked(State, Pres, PresA), #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), route_multiple(State, JIDs, Pres),
State#{pres_a => ?SETS:new()}. State#{pres_a => ?SETS:new()}.
-spec broadcast_presence_available(state(), presence(), boolean()) -> state(). -spec broadcast_presence_available(state(), presence(), boolean()) -> state().
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF, broadcast_presence_available(#{jid := JID} = State,
pres_t := PresT, jid := JID} = State,
Pres, _FromUnavailable = true) -> Pres, _FromUnavailable = true) ->
Probe = #presence{from = JID, type = probe}, Probe = #presence{from = JID, type = probe},
TJIDs = filter_blocked(State, Probe, PresT), #jid{luser = LUser, lserver = LServer} = JID,
FJIDs = filter_blocked(State, Pres, PresF), 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, TJIDs, Probe),
route_multiple(State, FJIDs, Pres), route_multiple(State, FJIDs, Pres),
State#{pres_a => ?SETS:union(PresA, PresF)}; State;
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF} = State, broadcast_presence_available(#{jid := JID} = State,
Pres, _FromUnavailable = false) -> 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), route_multiple(State, JIDs, Pres),
State. State.
@ -761,23 +809,17 @@ get_priority_from_presence(#presence{priority = Prio}) ->
_ -> Prio _ -> Prio
end. 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. -spec route_multiple(state(), [jid()], stanza()) -> ok.
route_multiple(#{lserver := LServer}, JIDs, Pkt) -> route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
From = xmpp:get_from(Pkt), From = xmpp:get_from(Pkt),
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, 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()) -> -spec resource_conflict_action(binary(), binary(), binary()) ->
{accept_resource, binary()} | closenew. {accept_resource, binary()} | closenew.
resource_conflict_action(U, S, R) -> resource_conflict_action(U, S, R) ->
@ -855,30 +897,6 @@ change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
LServer), LServer),
xmpp_stream_in:change_shaper(State, Shaper). 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(). -spec format_reason(state(), term()) -> binary().
format_reason(#{stop_reason := Reason}, _) -> format_reason(#{stop_reason := Reason}, _) ->
xmpp_stream_in:format_error(Reason); xmpp_stream_in:format_error(Reason);
@ -894,10 +912,20 @@ format_reason(_, _) ->
transform_listen_option(Opt, Opts) -> transform_listen_option(Opt, Opts) ->
[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_ciphers) -> fun iolist_to_binary/1;
opt_type(c2s_dhfile) -> fun iolist_to_binary/1; opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
opt_type(c2s_cafile) -> fun iolist_to_binary/1; opt_type(c2s_cafile) -> fun misc:try_read_file/1;
opt_type(c2s_protocol_options) -> opt_type(c2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end; fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(c2s_tls_compression) -> opt_type(c2s_tls_compression) ->
@ -920,9 +948,30 @@ opt_type(_) ->
c2s_protocol_options, c2s_tls_compression, resource_conflict, c2s_protocol_options, c2s_tls_compression, resource_conflict,
disable_sasl_mechanisms]. 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(access) -> fun acl:access_rules_validator/1;
listen_opt_type(shaper) -> fun acl:shaper_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(ciphers) -> opt_type(c2s_ciphers);
listen_opt_type(dhfile) -> opt_type(c2s_dhfile); listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
listen_opt_type(cafile) -> opt_type(c2s_cafile); 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(zlib) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(supervisor) -> 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) -> listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I) -> I; fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity; (unlimited) -> infinity;
(infinity) -> infinity (infinity) -> infinity
end; end;

View File

@ -528,6 +528,10 @@ clean_treap(Treap, CleanPriority) ->
now_priority() -> now_priority() ->
-p1_time_compat:system_time(micro_seconds). -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) -> opt_type(captcha_cmd) ->
fun (FileName) -> fun (FileName) ->
F = iolist_to_binary(FileName), if F /= <<"">> -> F end F = iolist_to_binary(FileName), if F /= <<"">> -> F end

View File

@ -891,6 +891,9 @@ permission_addon() ->
[{access, ejabberd_config:get_option(commands_admin_access, none)}], [{access, ejabberd_config:get_option(commands_admin_access, none)}],
{get_exposed_commands(), []}}}]. {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_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) -> opt_type(commands) ->
fun(V) when is_list(V) -> V end; fun(V) when is_list(V) -> V end;

View File

@ -179,7 +179,10 @@ read_file(File, Opts) ->
load_file(File) -> load_file(File) ->
State0 = read_file(File), State0 = read_file(File),
State1 = hosts_to_start(State0), 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. -spec reload_file() -> ok.
@ -971,11 +974,12 @@ default_db(Opt, Host, Module) ->
end end
end. end.
get_modules_with_options() -> get_modules() ->
{ok, Mods} = application:get_key(ejabberd, modules), {ok, Mods} = application:get_key(ejabberd, modules),
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()], ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
AllMods = [?MODULE|ExtMods++Mods], ExtMods ++ Mods.
init_module_db_table(AllMods),
get_modules_with_options(Modules) ->
lists:foldl( lists:foldl(
fun(Mod, D) -> fun(Mod, D) ->
case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of
@ -992,10 +996,9 @@ get_modules_with_options() ->
false -> false ->
D D
end end
end, dict:new(), AllMods). end, dict:new(), Modules).
validate_opts(#state{opts = Opts} = State) -> validate_opts(#state{opts = Opts} = State, ModOpts) ->
ModOpts = get_modules_with_options(),
NewOpts = lists:filtermap( NewOpts = lists:filtermap(
fun(#local_config{key = {Opt, _Host}, value = Val} = In) -> fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
case dict:find(Opt, ModOpts) of 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_shared_roster_odbc) -> {mod_shared_roster, sql};
replace_module(mod_vcard_odbc) -> {mod_vcard, sql}; replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
replace_module(mod_vcard_ldap) -> {mod_vcard, ldap}; 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_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(mod_http_bind) -> mod_bosh; replace_module(mod_http_bind) -> mod_bosh;
replace_module(Module) -> replace_module(Module) ->
@ -1362,6 +1365,23 @@ emit_deprecation_warning(Module, NewModule) ->
now_to_seconds({MegaSecs, Secs, _MicroSecs}) -> now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs. 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) -> opt_type(hide_sensitive_log_data) ->
fun (H) when is_boolean(H) -> H end; fun (H) when is_boolean(H) -> H end;
opt_type(hosts) -> opt_type(hosts) ->
@ -1397,7 +1417,7 @@ opt_type(cache_life_time) ->
(unlimited) -> infinity (unlimited) -> infinity
end; end;
opt_type(domain_certfile) -> opt_type(domain_certfile) ->
fun iolist_to_binary/1; fun misc:try_read_file/1;
opt_type(shared_key) -> opt_type(shared_key) ->
fun iolist_to_binary/1; fun iolist_to_binary/1;
opt_type(node_start) -> opt_type(node_start) ->

View File

@ -875,6 +875,8 @@ print(Format, Args) ->
%% ["aaaa bbb ccc"]. %% ["aaaa bbb ccc"].
-spec opt_type(ejabberdctl_access_commands) -> fun((list()) -> list());
(atom()) -> [atom()].
opt_type(ejabberdctl_access_commands) -> opt_type(ejabberdctl_access_commands) ->
fun (V) when is_list(V) -> V end; fun (V) when is_list(V) -> V end;
opt_type(_) -> [ejabberdctl_access_commands]. opt_type(_) -> [ejabberdctl_access_commands].

View File

@ -136,15 +136,15 @@ init({SockMod, Socket}, Opts) ->
true -> [{[], ejabberd_xmlrpc}]; true -> [{[], ejabberd_xmlrpc}];
false -> [] false -> []
end, end,
DefinedHandlers = gen_mod:get_opt(request_handlers, Opts, []), DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC, Admin ++ Bind ++ XMLRPC,
?DEBUG("S: ~p~n", [RequestHandlers]), ?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+))?$">>), {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}]), ?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
State = #state{sockmod = SockMod1, 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. -spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
parse_auth(<<"Basic ", Auth64/binary>>) -> 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 %% Auth should be a string with the format: user@server:password
%% Note that password can contain additional characters '@' and ':' %% Note that password can contain additional characters '@' and ':'
case str:chr(Auth, $:) of case str:chr(Auth, $:) of
@ -899,19 +901,41 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
transform_listen_option(Opt, Opts) -> transform_listen_option(Opt, Opts) ->
[Opt|Opts]. [Opt|Opts].
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
(atom()) -> [atom()].
opt_type(trusted_proxies) -> opt_type(trusted_proxies) ->
fun (all) -> all; fun (all) -> all;
(TPs) -> [iolist_to_binary(TP) || TP <- TPs] end; (TPs) -> [iolist_to_binary(TP) || TP <- TPs] end;
opt_type(_) -> [trusted_proxies]. 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) -> listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end; fun(B) when is_boolean(B) -> B end;
listen_opt_type(certfile) -> 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) -> listen_opt_type(ciphers) ->
fun iolist_to_binary/1; fun iolist_to_binary/1;
listen_opt_type(dhfile) -> listen_opt_type(dhfile) ->
fun iolist_to_binary/1; fun misc:try_read_file/1;
listen_opt_type(protocol_options) -> listen_opt_type(protocol_options) ->
fun(Options) -> str:join(Options, <<"|">>) end; fun(Options) -> str:join(Options, <<"|">>) end;
listen_opt_type(tls_compression) -> listen_opt_type(tls_compression) ->

View File

@ -90,14 +90,7 @@ start(Port, Module, Opts) ->
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage} %% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
start_dependent(Port, Module, Opts) -> start_dependent(Port, Module, Opts) ->
try check_listener_options(Opts) of proc_lib:start_link(?MODULE, init, [Port, Module, Opts]).
ok ->
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
catch
throw:{error, Error} ->
?ERROR_MSG(Error, []),
{error, Error}
end.
init(PortIP, Module, RawOpts) -> init(PortIP, Module, RawOpts) ->
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts), {Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
@ -456,48 +449,6 @@ config_reloaded() ->
%%% %%%
%%% Check options %%% 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) -> get_proto(Opts) ->
case proplists:get_value(proto, Opts) of case proplists:get_value(proto, Opts) of
undefined -> undefined ->

File diff suppressed because one or more lines are too long

View File

@ -25,21 +25,33 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_oauth_mnesia). -module(ejabberd_oauth_mnesia).
-behaviour(ejabberd_oauth).
-export([init/0, -export([init/0,
store/1, store/1,
lookup/1, lookup/1,
clean/1]). clean/1,
use_cache/0]).
-include("ejabberd_oauth.hrl"). -include("ejabberd_oauth.hrl").
init() -> init() ->
ejabberd_mnesia:create(?MODULE, oauth_token, ejabberd_mnesia:create(?MODULE, oauth_token,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, {attributes,
record_info(fields, oauth_token)}]), record_info(fields, oauth_token)}]),
ok. 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) -> store(R) ->
mnesia:dirty_write(R). mnesia:dirty_write(R).

View File

@ -25,7 +25,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_oauth_rest). -module(ejabberd_oauth_rest).
-behaviour(ejabberd_oauth).
-behaviour(ejabberd_config). -behaviour(ejabberd_config).
-export([init/0, -export([init/0,
@ -93,6 +93,8 @@ path(Path) ->
<<Base/binary, "/", Path/binary>>. <<Base/binary, "/", Path/binary>>.
-spec opt_type(ext_api_path_oauth) -> fun((binary()) -> binary());
(atom()) -> [atom()].
opt_type(ext_api_path_oauth) -> opt_type(ext_api_path_oauth) ->
fun (X) -> iolist_to_binary(X) end; fun (X) -> iolist_to_binary(X) end;
opt_type(_) -> [ext_api_path_oauth]. opt_type(_) -> [ext_api_path_oauth].

View File

@ -25,7 +25,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_oauth_sql). -module(ejabberd_oauth_sql).
-behaviour(ejabberd_oauth).
-compile([{parse_transform, ejabberd_sql_pt}]). -compile([{parse_transform, ejabberd_sql_pt}]).
-export([init/0, -export([init/0,

View File

@ -135,7 +135,7 @@ export_host(Dir, FnH, Host) ->
{ok, Fd} -> {ok, Fd} ->
print(Fd, make_piefxis_xml_head()), print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_host_head(Host)), 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 case export_users(Users, Host, Fd) of
ok -> ok ->
print(Fd, make_piefxis_host_tail()), print(Fd, make_piefxis_host_tail()),
@ -402,9 +402,9 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
stop("Invalid 'user': ~s", [Name]); stop("Invalid 'user': ~s", [Name]);
LUser -> LUser ->
case ejabberd_auth:try_register(LUser, LServer, Pass) of case ejabberd_auth:try_register(LUser, LServer, Pass) of
{atomic, _} -> ok ->
process_user_els(Els, State#state{user = LUser}); process_user_els(Els, State#state{user = LUser});
Err -> {error, Err} ->
stop("Failed to create user '~s': ~p", [Name, Err]) stop("Failed to create user '~s': ~p", [Name, Err])
end end
end. end.

535
src/ejabberd_pkix.erl Normal file
View 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.

View File

@ -109,6 +109,9 @@ needs_sql(Host) ->
undefined -> false undefined -> false
end. 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) -> opt_type(sql_type) ->
fun (mysql) -> mysql; fun (mysql) -> mysql;
(pgsql) -> pgsql; (pgsql) -> pgsql;

View File

@ -248,17 +248,15 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
activate_socket(#state{socket = Socket, activate_socket(#state{socket = Socket,
sock_mod = SockMod}) -> sock_mod = SockMod}) ->
PeerName = case SockMod of Res = case SockMod of
gen_tcp -> gen_tcp ->
inet:setopts(Socket, [{active, once}]), inet:setopts(Socket, [{active, once}]);
inet:peername(Socket);
_ -> _ ->
SockMod:setopts(Socket, [{active, once}]), SockMod:setopts(Socket, [{active, once}])
SockMod:peername(Socket)
end, end,
case PeerName of case Res of
{error, _Reason} -> self() ! {tcp_closed, Socket}; {error, _Reason} -> self() ! {tcp_closed, Socket};
{ok, _} -> ok ok -> ok
end. end.
%% Data processing for connectors directly generating xmlelement in %% Data processing for connectors directly generating xmlelement in
@ -348,6 +346,9 @@ do_call(Pid, Msg) ->
hibernate_timeout() -> hibernate_timeout() ->
ejabberd_config:get_option(receiver_hibernate, timer:seconds(90)). 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) -> opt_type(receiver_hibernate) ->
fun(I) when is_integer(I), I>0 -> I; fun(I) when is_integer(I), I>0 -> I;
(hibernate) -> hibernate (hibernate) -> hibernate

View File

@ -108,17 +108,11 @@ is_redis_configured(Host) ->
PoolSize = ejabberd_config:has_option({redis_pool_size, Host}), PoolSize = ejabberd_config:has_option({redis_pool_size, Host}),
ConnTimeoutConfigured = ejabberd_config:has_option( ConnTimeoutConfigured = ejabberd_config:has_option(
{redis_connect_timeout, Host}), {redis_connect_timeout, Host}),
Modules = ejabberd_config:get_option({modules, Host}, []),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis, SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis,
RouterConfigured = ejabberd_config:get_option({router_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 ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
PoolSize or ConnTimeoutConfigured or PoolSize or ConnTimeoutConfigured or
SMConfigured or RouterConfigured or ModuleWithRedisDBConfigured. SMConfigured or RouterConfigured.
get_specs() -> get_specs() ->
lists:map( lists:map(
@ -133,6 +127,14 @@ get_pool_size() ->
iolist_to_list(IOList) -> iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(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) -> opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end; fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_db) -> opt_type(redis_db) ->
@ -147,5 +149,4 @@ opt_type(redis_queue_type) ->
fun(ram) -> ram; (file) -> file end; fun(ram) -> ram; (file) -> file end;
opt_type(_) -> opt_type(_) ->
[redis_connect_timeout, redis_db, redis_password, [redis_connect_timeout, redis_db, redis_password,
redis_port, redis_pool_size, redis_server, redis_port, redis_pool_size, redis_server, redis_queue_type].
redis_pool_size, redis_queue_type].

View File

@ -89,16 +89,11 @@ is_riak_configured(Host) ->
ejabberd_auth:auth_modules(Host)), ejabberd_auth:auth_modules(Host)),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak, SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak,
RouterConfigured = ejabberd_config:get_option({router_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 ServerConfigured or PortConfigured or StartIntervalConfigured
or PoolConfigured or CacertConfigured or PoolConfigured or CacertConfigured
or UserConfigured or PassConfigured or UserConfigured or PassConfigured
or SMConfigured or RouterConfigured or SMConfigured or RouterConfigured
or AuthConfigured or ModuleWithRiakDBConfigured. or AuthConfigured.
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
@ -179,6 +174,14 @@ transform_options({riak_server, {S, P}}, Opts) ->
transform_options(Opt, Opts) -> transform_options(Opt, Opts) ->
[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) -> opt_type(riak_pool_size) ->
fun (N) when is_integer(N), N >= 1 -> N end; fun (N) when is_integer(N), N >= 1 -> N end;
opt_type(riak_port) -> opt_type(riak_port) ->

View File

@ -157,6 +157,7 @@ register_route(Domain, ServerHost, LocalHint, Pid) ->
get_component_number(LDomain), Pid) of get_component_number(LDomain), Pid) of
ok -> ok ->
?DEBUG("Route registered: ~s", [LDomain]), ?DEBUG("Route registered: ~s", [LDomain]),
ejabberd_hooks:run(route_registered, [LDomain]),
delete_cache(Mod, LDomain); delete_cache(Mod, LDomain);
{error, Err} -> {error, Err} ->
?ERROR_MSG("Failed to register route ~s: ~p", ?ERROR_MSG("Failed to register route ~s: ~p",
@ -185,6 +186,7 @@ unregister_route(Domain, Pid) ->
LDomain, get_component_number(LDomain), Pid) of LDomain, get_component_number(LDomain), Pid) of
ok -> ok ->
?DEBUG("Route unregistered: ~s", [LDomain]), ?DEBUG("Route unregistered: ~s", [LDomain]),
ejabberd_hooks:run(route_unregistered, [LDomain]),
delete_cache(Mod, LDomain); delete_cache(Mod, LDomain);
{error, Err} -> {error, Err} ->
?ERROR_MSG("Failed to unregister route ~s: ~p", ?ERROR_MSG("Failed to unregister route ~s: ~p",
@ -476,6 +478,16 @@ clean_cache(Node) ->
clean_cache() -> clean_cache() ->
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]). 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) -> opt_type(domain_balancing) ->
fun (random) -> random; fun (random) -> random;
(source) -> source; (source) -> source;

View File

@ -480,9 +480,13 @@ new_connection(MyServer, Server, From, FromTo,
end, end,
TRes = mnesia:transaction(F), TRes = mnesia:transaction(F),
case TRes of case TRes of
{atomic, Pid} -> {atomic, Pid1} ->
ejabberd_s2s_out:connect(Pid), if Pid1 == Pid ->
[Pid]; ejabberd_s2s_out:connect(Pid);
true ->
ejabberd_s2s_out:stop(Pid)
end,
[Pid1];
{aborted, Reason} -> {aborted, Reason} ->
?ERROR_MSG("failed to register connection ~s -> ~s: ~p", ?ERROR_MSG("failed to register connection ~s -> ~s: ~p",
[MyServer, Server, Reason]), [MyServer, Server, Reason]),
@ -689,16 +693,30 @@ get_s2s_state(S2sPid) ->
end, end,
[{s2s_pid, S2sPid} | Infos]. [{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) -> opt_type(route_subdomains) ->
fun (s2s) -> s2s; fun (s2s) -> s2s;
(local) -> local (local) -> local
end; end;
opt_type(s2s_access) -> opt_type(s2s_access) ->
fun acl:access_rules_validator/1; 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_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun iolist_to_binary/1; opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
opt_type(s2s_cafile) -> fun iolist_to_binary/1; opt_type(s2s_cafile) -> fun misc:try_read_file/1;
opt_type(s2s_protocol_options) -> opt_type(s2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end; fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(s2s_tls_compression) -> opt_type(s2s_tls_compression) ->

View File

@ -21,13 +21,12 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_s2s_in). -module(ejabberd_s2s_in).
-behaviour(xmpp_stream_in). -behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_socket). -behaviour(ejabberd_socket).
%% ejabberd_socket callbacks %% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]). -export([start/2, start_link/2, socket_type/0]).
%% ejabberd_config callbacks %% ejabberd_listener callbacks
-export([opt_type/1, listen_opt_type/1]). -export([listen_opt_type/1]).
%% xmpp_stream_in callbacks %% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2, -export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
@ -245,7 +244,7 @@ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
State, [Pkt, Result]). State, [Pkt, Result]).
init([State, Opts]) -> init([State, Opts]) ->
Shaper = gen_mod:get_opt(shaper, Opts, none), Shaper = proplists:get_value(shaper, Opts, none),
TLSOpts1 = lists:filter( TLSOpts1 = lists:filter(
fun({certfile, _}) -> true; fun({certfile, _}) -> true;
({ciphers, _}) -> true; ({ciphers, _}) -> true;
@ -344,11 +343,24 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)), Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
xmpp_stream_in:change_shaper(State, Shaper). 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(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(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile); listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile); 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(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(supervisor) -> 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) -> listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I) -> I; fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity; (unlimited) -> infinity;
(infinity) -> infinity (infinity) -> infinity
end; end;

View File

@ -439,6 +439,13 @@ maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
maybe_report_huge_timeout(_, _) -> maybe_report_huge_timeout(_, _) ->
ok. 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) -> opt_type(outgoing_s2s_families) ->
fun(Families) -> fun(Families) ->
lists:map( lists:map(
@ -447,7 +454,7 @@ opt_type(outgoing_s2s_families) ->
end, Families) end, Families)
end; end;
opt_type(outgoing_s2s_port) -> 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) -> opt_type(outgoing_s2s_timeout) ->
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 -> fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
timer:seconds(TimeOut); timer:seconds(TimeOut);

View File

@ -21,15 +21,14 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_service). -module(ejabberd_service).
-behaviour(xmpp_stream_in). -behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_socket). -behaviour(ejabberd_socket).
-protocol({xep, 114, '1.6'}). -protocol({xep, 114, '1.6'}).
%% ejabberd_socket callbacks %% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0, close/1, close/2]). -export([start/2, start_link/2, socket_type/0, close/1, close/2]).
%% ejabberd_config callbacks %% ejabberd_listener callbacks
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]). -export([listen_opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks %% xmpp_stream_in callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3]). -export([init/1, handle_info/2, terminate/2, code_change/3]).
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4, -export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
@ -80,15 +79,15 @@ tls_options(#{tls_options := TLSOptions}) ->
TLSOptions. TLSOptions.
init([State, Opts]) -> init([State, Opts]) ->
Access = gen_mod:get_opt(access, Opts, all), Access = proplists:get_value(access, Opts, all),
Shaper = gen_mod:get_opt(shaper_rule, Opts, none), Shaper = proplists:get_value(shaper_rule, Opts, none),
GlobalPassword = gen_mod:get_opt(password, Opts, random_password()), GlobalPassword = proplists:get_value(password, Opts, random_password()),
HostOpts = gen_mod:get_opt(hosts, Opts, [{global, GlobalPassword}]), HostOpts = proplists:get_value(hosts, Opts, [{global, GlobalPassword}]),
HostOpts1 = lists:map( HostOpts1 = lists:map(
fun({Host, undefined}) -> {Host, GlobalPassword}; fun({Host, undefined}) -> {Host, GlobalPassword};
({Host, Password}) -> {Host, Password} ({Host, Password}) -> {Host, Password}
end, HostOpts), end, HostOpts),
CheckFrom = gen_mod:get_opt(check_from, Opts, true), CheckFrom = proplists:get_value(check_from, Opts, true),
TLSOpts1 = lists:filter( TLSOpts1 = lists:filter(
fun({certfile, _}) -> true; fun({certfile, _}) -> true;
({ciphers, _}) -> true; ({ciphers, _}) -> true;
@ -259,14 +258,32 @@ transform_listen_option({host, Host, Os}, Opts) ->
transform_listen_option(Opt, Opts) -> transform_listen_option(Opt, Opts) ->
[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(access) -> fun acl:access_rules_validator/1;
listen_opt_type(shaper_rule) -> fun acl:shaper_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(ciphers) -> fun iolist_to_binary/1;
listen_opt_type(dhfile) -> fun iolist_to_binary/1; listen_opt_type(dhfile) -> fun misc:try_read_file/1;
listen_opt_type(cafile) -> fun iolist_to_binary/1; listen_opt_type(cafile) -> fun misc:try_read_file/1;
listen_opt_type(protocol_options) -> listen_opt_type(protocol_options) ->
fun(Options) -> str:join(Options, <<"|">>) end; fun(Options) -> str:join(Options, <<"|">>) end;
listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end; listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end;

View File

@ -22,6 +22,21 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(ejabberd_sip). -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 %% API
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2, -export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
socket_type/0, listen_opt_type/1]). socket_type/0, listen_opt_type/1]).
@ -47,7 +62,10 @@ socket_type() ->
raw. raw.
listen_opt_type(certfile) -> 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) -> listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end; fun(B) when is_boolean(B) -> B end;
listen_opt_type(_) -> listen_opt_type(_) ->
@ -56,3 +74,4 @@ listen_opt_type(_) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
-endif.

View File

@ -178,7 +178,7 @@ close_session(SID, User, Server, Resource) ->
subscribe | subscribed | unsubscribe | unsubscribed, subscribe | subscribed | unsubscribe | unsubscribed,
binary()) -> boolean() | {stop, false}. binary()) -> boolean() | {stop, false}.
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) -> 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; true -> Acc;
false -> {stop, false} false -> {stop, false}
end. end.
@ -716,7 +716,7 @@ route_message(#message{to = To, type = Type} = Packet) ->
end, end,
PrioRes); PrioRes);
_ -> _ ->
case ejabberd_auth:is_user_exists(LUser, LServer) andalso case ejabberd_auth:user_exists(LUser, LServer) andalso
is_privacy_allow(Packet) of is_privacy_allow(Packet) of
true -> true ->
ejabberd_hooks:run_fold(offline_message_hook, ejabberd_hooks:run_fold(offline_message_hook,
@ -858,7 +858,7 @@ force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer), Mod = get_sm_backend(LServer),
Ss = online(get_sessions(Mod, LUser, LServer)), Ss = online(get_sessions(Mod, LUser, LServer)),
lists:foreach(fun (#session{sid = {_, Pid}}) -> lists:foreach(fun (#session{sid = {_, Pid}}) ->
ejabberd_c2s:route(Pid, force_update_presence) ejabberd_c2s:resend_presence(Pid)
end, end,
Ss). Ss).
@ -1010,6 +1010,12 @@ kick_user(User, Server) ->
make_sid() -> make_sid() ->
{p1_time_compat:unique_timestamp(), self()}. {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(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(O) when O == sm_use_cache; O == sm_cache_missed -> opt_type(O) when O == sm_use_cache; O == sm_cache_missed ->
fun(B) when is_boolean(B) -> B end; fun(B) when is_boolean(B) -> B end;

View File

@ -194,9 +194,20 @@ abort(Reason) ->
restart(Reason) -> restart(Reason) ->
throw({aborted, 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) -> escape(S) ->
<< <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>. << <<(escape_char(Char))/binary>> || <<Char>> <= S >>.
%% Escape character that will confuse an SQL engine %% Escape character that will confuse an SQL engine
%% Percent and underscore only need to be escaped for pattern matching like %% 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($_) -> <<"\\_">>;
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(S) when is_binary(S) ->
<< <<(escape_like_arg(C))/binary>> || <<C>> <= S >>; << <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
@ -1080,6 +1091,20 @@ check_error({error, Why} = Err, Query) ->
check_error(Result, _Query) -> check_error(Result, _Query) ->
Result. 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_database) -> fun iolist_to_binary/1;
opt_type(sql_keepalive_interval) -> opt_type(sql_keepalive_interval) ->
fun (I) when is_integer(I), I > 0 -> I end; 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_username) -> fun iolist_to_binary/1;
opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end; 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_verify) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_certfile) -> fun iolist_to_binary/1; opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1;
opt_type(sql_ssl_cafile) -> fun iolist_to_binary/1; opt_type(sql_ssl_cafile) -> fun misc:try_read_file/1;
opt_type(sql_query_timeout) -> opt_type(sql_query_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end; fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_connect_timeout) -> opt_type(sql_connect_timeout) ->
@ -1101,6 +1126,6 @@ opt_type(sql_queue_type) ->
opt_type(_) -> opt_type(_) ->
[sql_database, sql_keepalive_interval, [sql_database, sql_keepalive_interval,
sql_password, sql_port, sql_server, 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_ssl_cafile, sql_queue_type, sql_query_timeout,
sql_connect_timeout]. sql_connect_timeout].

View File

@ -218,6 +218,9 @@ read_lines(Fd, File, Acc) ->
[] []
end. 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) -> opt_type(sql_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end; fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_start_interval) -> opt_type(sql_start_interval) ->

View File

@ -27,6 +27,21 @@
-protocol({rfc, 5766}). -protocol({rfc, 5766}).
-protocol({xep, 176, '1.0'}). -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, -export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
socket_type/0, listen_opt_type/1]). socket_type/0, listen_opt_type/1]).
@ -73,9 +88,9 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
ok ok
end, end,
AuthFun = fun ejabberd_auth:get_password_s/2, AuthFun = fun ejabberd_auth:get_password_s/2,
Shaper = gen_mod:get_opt(shaper, Opts, none), Shaper = proplists:get_value(shaper, Opts, none),
AuthType = gen_mod:get_opt(auth_type, Opts, user), AuthType = proplists:get_value(auth_type, Opts, user),
Realm = case gen_mod:get_opt(auth_realm, Opts) of Realm = case proplists:get_value(auth_realm, Opts) of
undefined when AuthType == user -> undefined when AuthType == user ->
if NumberOfMyHosts > 1 -> if NumberOfMyHosts > 1 ->
?WARNING_MSG("you have several virtual " ?WARNING_MSG("you have several virtual "
@ -114,7 +129,10 @@ listen_opt_type(auth_realm) ->
listen_opt_type(tls) -> listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end; fun(B) when is_boolean(B) -> B end;
listen_opt_type(certfile) -> 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) -> listen_opt_type(turn_min_port) ->
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end; fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
listen_opt_type(turn_max_port) -> listen_opt_type(turn_max_port) ->
@ -135,3 +153,4 @@ listen_opt_type(_) ->
[shaper, auth_type, auth_realm, tls, certfile, turn_min_port, [shaper, auth_type, auth_realm, tls, certfile, turn_min_port,
turn_max_port, turn_max_allocations, turn_max_permissions, turn_max_port, turn_max_allocations, turn_max_permissions,
server_name]. server_name].
-endif.

View File

@ -148,6 +148,8 @@ init([]) ->
permanent, 5000, worker, [ejabberd_admin]}, permanent, 5000, worker, [ejabberd_admin]},
CyrSASL = {cyrsasl, {cyrsasl, start_link, []}, CyrSASL = {cyrsasl, {cyrsasl, start_link, []},
permanent, 5000, worker, [cyrsasl]}, permanent, 5000, worker, [cyrsasl]},
PKIX = {ejabberd_pkix, {ejabberd_pkix, start_link, []},
permanent, 5000, worker, [ejabberd_pkix]},
{ok, {{one_for_one, 10, 1}, {ok, {{one_for_one, 10, 1},
[Hooks, [Hooks,
CyrSASL, CyrSASL,
@ -156,6 +158,7 @@ init([]) ->
Ctl, Ctl,
Commands, Commands,
Admin, Admin,
PKIX,
Listener, Listener,
SystemMonitor, SystemMonitor,
S2S, S2S,

View File

@ -330,6 +330,9 @@ process_remote_command([setlh, NewValue]) ->
[OldLH, NewLH]); [OldLH, NewLH]);
process_remote_command(_) -> throw(unknown_command). 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) -> opt_type(watchdog_admins) ->
fun (JIDs) -> fun (JIDs) ->
[jid:tolower(jid:decode(iolist_to_binary(S))) [jid:tolower(jid:decode(iolist_to_binary(S)))

File diff suppressed because one or more lines are too long

View File

@ -152,7 +152,7 @@ handshake(#ws{headers = Headers} = State) ->
V -> V ->
[<<"Sec-Websocket-Protocol:">>, V, <<"\r\n">>] [<<"Sec-Websocket-Protocol:">>, V, <<"\r\n">>]
end, end,
Hash = misc:encode_base64( Hash = base64:encode(
crypto:hash(sha, <<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)), crypto:hash(sha, <<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
{State, [<<"HTTP/1.1 101 Switching Protocols\r\n">>, {State, [<<"HTTP/1.1 101 Switching Protocols\r\n">>,
<<"Upgrade: websocket\r\n">>, <<"Upgrade: websocket\r\n">>,

View File

@ -197,7 +197,7 @@ socket_type() -> raw.
%% HTTP interface %% HTTP interface
%% ----------------------------- %% -----------------------------
process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) -> 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, GetAuth = true,
State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP}, State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP},
case fxml_stream:parse_element(Data) of case fxml_stream:parse_element(Data) of

View File

@ -53,14 +53,14 @@ modules() ->
mod_caps, mod_caps,
mod_irc, mod_irc,
mod_last, mod_last,
mod_mam,
mod_muc, mod_muc,
mod_offline, mod_offline,
mod_privacy, mod_privacy,
mod_private, mod_private,
mod_roster, mod_roster,
mod_shared_roster, mod_shared_roster,
mod_vcard, mod_vcard].
mod_vcard_xupdate].
export(Server, Output) -> export(Server, Output) ->
LServer = jid:nameprep(iolist_to_binary(Server)), LServer = jid:nameprep(iolist_to_binary(Server)),

View File

@ -130,7 +130,8 @@
port = 389 :: inet:port_number(), port = 389 :: inet:port_number(),
sockmod = gen_tcp :: ssl | gen_tcp, sockmod = gen_tcp :: ssl | gen_tcp,
tls = none :: none | tls, tls = none :: none | tls,
tls_options = [] :: [{cacertfile, string()} | tls_options = [] :: [{certfile, string()} |
{cacertfile, string()} |
{depth, non_neg_integer()} | {depth, non_neg_integer()} |
{verify, non_neg_integer()}], {verify, non_neg_integer()}],
fd :: gen_tcp:socket() | undefined, fd :: gen_tcp:socket() | undefined,
@ -565,7 +566,7 @@ get_handle(Name) when is_binary(Name) ->
%% process. %% process.
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
init([Hosts, Port, Rootdn, Passwd, Opts]) -> 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; tls -> tls;
_ -> none _ -> none
end, end,
@ -577,30 +578,36 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) ->
end; end;
PT -> PT PT -> PT
end, end,
CacertOpts = case gen_mod:get_opt(tls_cacertfile, Opts) of CertOpts = case proplists:get_value(tls_certfile, Opts) of
undefined -> undefined ->
[]; [];
Path -> Path1 ->
[{cacertfile, Path}] [{certfile, Path1}]
end, end,
DepthOpts = case gen_mod:get_opt(tls_depth, Opts) of CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of
undefined ->
[];
Path2 ->
[{cacertfile, Path2}]
end,
DepthOpts = case proplists:get_value(tls_depth, Opts) of
undefined -> undefined ->
[]; [];
Depth -> Depth ->
[{depth, Depth}] [{depth, Depth}]
end, 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) TLSOpts = if (Verify == hard orelse Verify == soft)
andalso CacertOpts == [] -> andalso CacertOpts == [] ->
?WARNING_MSG("TLS verification is enabled but no CA " ?WARNING_MSG("TLS verification is enabled but no CA "
"certfiles configured, so verification " "certfiles configured, so verification "
"is disabled.", "is disabled.",
[]), []),
[]; CertOpts;
Verify == soft -> Verify == soft ->
[{verify, 1}] ++ CacertOpts ++ DepthOpts; [{verify, 1}] ++ CertOpts ++ CacertOpts ++ DepthOpts;
Verify == hard -> Verify == hard ->
[{verify, 2}] ++ CacertOpts ++ DepthOpts; [{verify, 2}] ++ CertOpts ++ CacertOpts ++ DepthOpts;
true -> [] true -> []
end, end,
{ok, connecting, {ok, connecting,

View File

@ -173,25 +173,26 @@ uids_domain_subst(Host, UIDs) ->
-spec get_config(binary(), list()) -> eldap_config(). -spec get_config(binary(), list()) -> eldap_config().
get_config(Host, Opts) -> get_config(Host, Opts) ->
Servers = gen_mod:get_opt({ldap_servers, Host}, Opts, [<<"localhost">>]), Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]),
Backups = gen_mod:get_opt({ldap_backups, Host}, Opts, []), Backups = get_opt(ldap_backups, Host, Opts, []),
Encrypt = gen_mod:get_opt({ldap_encrypt, Host}, Opts, none), Encrypt = get_opt(ldap_encrypt, Host, Opts, none),
TLSVerify = gen_mod:get_opt({ldap_tls_verify, Host}, Opts, false), TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false),
TLSCAFile = gen_mod:get_opt({ldap_tls_cacertfile, Host}, Opts), TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts),
TLSDepth = gen_mod:get_opt({ldap_tls_depth, Host}, Opts), TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts),
Port = gen_mod:get_opt({ldap_port, Host}, Opts, TLSDepth = get_opt(ldap_tls_depth, Host, Opts),
Port = get_opt(ldap_port, Host, Opts,
case Encrypt of case Encrypt of
tls -> ?LDAPS_PORT; tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT; starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT _ -> ?LDAP_PORT
end), end),
RootDN = gen_mod:get_opt({ldap_rootdn, Host}, Opts, <<"">>), RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>),
Password = gen_mod:get_opt({ldap_password, Host}, Opts, <<"">>), Password = get_opt(ldap_password, Host, Opts, <<"">>),
Base = gen_mod:get_opt({ldap_base, Host}, Opts, <<"">>), Base = get_opt(ldap_base, Host, Opts, <<"">>),
OldDerefAliases = gen_mod:get_opt({deref_aliases, Host}, Opts, unspecified), OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified),
DerefAliases = DerefAliases =
if OldDerefAliases == unspecified -> if OldDerefAliases == unspecified ->
gen_mod:get_opt({ldap_deref_aliases, Host}, Opts, never); get_opt(ldap_deref_aliases, Host, Opts, never);
true -> true ->
?WARNING_MSG("Option 'deref_aliases' is deprecated. " ?WARNING_MSG("Option 'deref_aliases' is deprecated. "
"The option is still supported " "The option is still supported "
@ -203,6 +204,7 @@ get_config(Host, Opts) ->
backups = Backups, backups = Backups,
tls_options = [{encrypt, Encrypt}, tls_options = [{encrypt, Encrypt},
{tls_verify, TLSVerify}, {tls_verify, TLSVerify},
{tls_certfile, TLSCertFile},
{tls_cacertfile, TLSCAFile}, {tls_cacertfile, TLSCAFile},
{tls_depth, TLSDepth}], {tls_depth, TLSDepth}],
port = Port, port = Port,
@ -211,6 +213,15 @@ get_config(Host, Opts) ->
base = Base, base = Base,
deref_aliases = DerefAliases}. 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 %% 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) -> collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]). 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) -> opt_type(deref_aliases) ->
fun (never) -> never; opt_type(ldap_deref_aliases);
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_backups) -> opt_type(ldap_backups) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end; fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_base) -> fun iolist_to_binary/1; 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_rootdn) -> fun iolist_to_binary/1;
opt_type(ldap_servers) -> opt_type(ldap_servers) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end; 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) -> 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) -> opt_type(ldap_tls_depth) ->
fun (I) when is_integer(I), I >= 0 -> I end; fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(ldap_tls_verify) -> opt_type(ldap_tls_verify) ->
@ -368,4 +397,5 @@ opt_type(_) ->
[deref_aliases, ldap_backups, ldap_base, ldap_uids, [deref_aliases, ldap_backups, ldap_base, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password, ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers, ldap_filter, 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].

View File

@ -652,6 +652,8 @@ format({Key, Val}) when is_binary(Val) ->
format({Key, Val}) -> % TODO: improve Yaml parsing format({Key, Val}) -> % TODO: improve Yaml parsing
{Key, Val}. {Key, Val}.
-spec opt_type(allow_contrib_modules) -> fun((boolean()) -> boolean());
(atom()) -> [atom()].
opt_type(allow_contrib_modules) -> opt_type(allow_contrib_modules) ->
fun (false) -> false; fun (false) -> false;
(no) -> false; (no) -> false;

View File

@ -31,7 +31,7 @@
-export([start/2, stop/1, init/2, check_password/3, -export([start/2, stop/1, init/2, check_password/3,
set_password/3, try_register/3, remove_user/2, 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("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -73,7 +73,7 @@ get_process_name(Host, Integer) ->
check_password(User, Server, Password) -> check_password(User, Server, Password) ->
call_port(Server, [<<"auth">>, 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]). call_port(Server, [<<"isuser">>, User, Server]).
set_password(User, Server, Password) -> set_password(User, Server, Password) ->
@ -83,7 +83,7 @@ try_register(User, Server, Password) ->
case call_port(Server, case call_port(Server,
[<<"tryregister">>, User, Server, Password]) [<<"tryregister">>, User, Server, Password])
of of
true -> {atomic, ok}; true -> ok;
false -> {error, not_allowed} false -> {error, not_allowed}
end. end.
@ -154,6 +154,8 @@ encode(L) -> str:join(L, <<":">>).
decode([0, 0]) -> false; decode([0, 0]) -> false;
decode([0, 1]) -> true. decode([0, 1]) -> true.
-spec opt_type(extauth_instances) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(extauth_instances) -> opt_type(extauth_instances) ->
fun (V) when is_integer(V), V > 0 -> V end; fun (V) when is_integer(V), V > 0 -> V end;
opt_type(_) -> [extauth_instances]. opt_type(_) -> [extauth_instances].

View File

@ -186,7 +186,7 @@ check_type(N) when is_integer(N), N>0 -> N;
check_type(parallel) -> parallel. check_type(parallel) -> parallel.
iqdisc(Host) -> 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()}]. -spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}].
@ -198,6 +198,8 @@ transform_module_options(Opts) ->
Opt Opt
end, Opts). end, Opts).
-spec opt_type(iqdisc) -> fun((type()) -> type());
(atom()) -> [atom()].
opt_type(iqdisc) -> fun check_type/1; opt_type(iqdisc) -> fun check_type/1;
opt_type(_) -> [iqdisc]. opt_type(_) -> [iqdisc].

View File

@ -99,7 +99,7 @@ start_child(Mod, Host, Opts) ->
start_child(Mod, Host, Opts, Proc) -> start_child(Mod, Host, Opts, Proc) ->
Spec = {Proc, {?GEN_SERVER, start_link, Spec = {Proc, {?GEN_SERVER, start_link,
[{local, Proc}, Mod, [Host, Opts], []]}, [{local, Proc}, Mod, [Host, Opts], []]},
transient, 2000, worker, [Mod]}, transient, timer:minutes(1), worker, [Mod]},
supervisor:start_child(ejabberd_gen_mod_sup, Spec). supervisor:start_child(ejabberd_gen_mod_sup, Spec).
-spec stop_child(module(), binary() | global) -> ok | {error, any()}. -spec stop_child(module(), binary() | global) -> ok | {error, any()}.
@ -677,6 +677,8 @@ is_equal_opt(Opt, NewOpts, OldOpts, Default) ->
true true
end. end.
-spec opt_type(modules) -> fun(([{atom(), list()}]) -> [{atom(), list()}]);
(atom()) -> [atom()].
opt_type(modules) -> opt_type(modules) ->
fun(Mods) -> fun(Mods) ->
lists:map( lists:map(

View File

@ -35,7 +35,7 @@
binary_to_integer/1, binary_to_integer/1,
integer_to_binary/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, -export([tolower/1, term_to_base64/1, base64_to_term/1,
decode_base64/1, encode_base64/1, ip_to_list/1, decode_base64/1, encode_base64/1, ip_to_list/1,
hex_to_bin/1, hex_to_base64/1, expand_keyword/3, hex_to_bin/1, hex_to_base64/1, expand_keyword/3,

View File

@ -28,12 +28,20 @@
-module(misc). -module(misc).
%% API %% API
-export([tolower/1, term_to_base64/1, base64_to_term/1, -export([tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
decode_base64/1, encode_base64/1, ip_to_list/1,
hex_to_bin/1, hex_to_base64/1, expand_keyword/3, hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1, 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, 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 %%% API
@ -54,83 +62,21 @@ term_to_base64(Term) ->
-spec base64_to_term(binary()) -> {term, term()} | error. -spec base64_to_term(binary()) -> {term, term()} | error.
base64_to_term(Base64) -> base64_to_term(Base64) ->
case catch binary_to_term(decode_base64(Base64), [safe]) of try binary_to_term(base64:decode(Base64), [safe]) of
{'EXIT', _} -> Term -> {term, Term}
error; catch _:badarg ->
Term -> error
{term, Term}
end. end.
-spec decode_base64(binary()) -> binary(). -spec decode_base64(binary()) -> binary().
decode_base64(S) -> decode_base64(S) ->
case catch binary:last(S) of try base64:mime_decode(S)
C when C == $\n; C == $\s -> catch _:badarg -> <<>>
decode_base64(binary:part(S, 0, byte_size(S) - 1));
_ ->
decode_base64_bin(S, <<>>)
end. 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(). -spec encode_base64(binary()) -> binary().
encode_base64(Data) -> encode_base64(Data) ->
encode_base64_bin(Data, <<>>). base64:encode(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}).
-spec ip_to_list(inet:ip_address() | undefined | -spec ip_to_list(inet:ip_address() | undefined |
{inet:ip_address(), inet:port_number()}) -> binary(). {inet:ip_address(), inet:port_number()}) -> binary().
@ -156,7 +102,7 @@ hex_to_bin([H1, H2 | T], Acc) ->
-spec hex_to_base64(binary()) -> binary(). -spec hex_to_base64(binary()) -> binary().
hex_to_base64(Hex) -> hex_to_base64(Hex) ->
encode_base64(hex_to_bin(Hex)). base64:encode(hex_to_bin(Hex)).
-spec expand_keyword(binary(), binary(), binary()) -> binary(). -spec expand_keyword(binary(), binary(), binary()) -> binary().
expand_keyword(Keyword, Input, Replacement) -> expand_keyword(Keyword, Input, Replacement) ->
@ -241,6 +187,30 @@ compile_exprs(Mod, Exprs) ->
join_atoms(Atoms, Sep) -> join_atoms(Atoms, Sep) ->
str:join([io_lib:format("~p", [A]) || A <- 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 %%% Internal functions
%%%=================================================================== %%%===================================================================

View File

@ -36,7 +36,7 @@
% Commands API % Commands API
-export([ -export([
% Adminsys % Adminsys
compile/1, get_cookie/0, export2sql/2, compile/1, get_cookie/0,
restart_module/2, restart_module/2,
% Sessions % Sessions
@ -148,15 +148,6 @@ get_commands_spec() ->
result = {cookie, string}, result = {cookie, string},
result_example = "MWTAVMODFELNLSMYXPPD", result_example = "MWTAVMODFELNLSMYXPPD",
result_desc = "Erlang cookie used for authentication by ejabberd"}, 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], #ejabberd_commands{name = restart_module, tags = [erlang],
desc = "Stop an ejabberd module, reload code and start", desc = "Stop an ejabberd module, reload code and start",
module = ?MODULE, function = restart_module, 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", desc = "Delete users that didn't log in last days, or that never logged",
longdesc = "To protect admin accounts, configure this for example:\n" longdesc = "To protect admin accounts, configure this for example:\n"
"access_rules:\n" "access_rules:\n"
" delete_old_users:\n" " protect_old_users:\n"
" - deny: admin\n" " - allow: admin\n"
" - allow: all\n", " - deny: all\n",
module = ?MODULE, function = delete_old_users, module = ?MODULE, function = delete_old_users,
args = [{days, integer}], args = [{days, integer}],
args_example = [30], args_example = [30],
@ -210,7 +201,7 @@ get_commands_spec() ->
result_desc = "Result tuple"}, result_desc = "Result tuple"},
#ejabberd_commands{name = check_account, tags = [accounts], #ejabberd_commands{name = check_account, tags = [accounts],
desc = "Check if an account exists or not", 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 = [{user, binary}, {host, binary}],
args_example = [<<"peter">>, <<"myserver.com">>], args_example = [<<"peter">>, <<"myserver.com">>],
args_desc = ["User name to check", "Server to check"], args_desc = ["User name to check", "Server to check"],
@ -528,6 +519,10 @@ get_commands_spec() ->
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = push_roster_all, tags = [roster], #ejabberd_commands{name = push_roster_all, tags = [roster],
desc = "Push template roster from file to all those users", 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, module = ?MODULE, function = push_roster_all,
args = [{file, binary}], args = [{file, binary}],
result = {res, rescode}}, result = {res, rescode}},
@ -697,23 +692,6 @@ restart_module(Host, Module) when is_atom(Module) ->
end end
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 %%% Accounts
%%% %%%
@ -810,14 +788,14 @@ histogram([], _Integral, _Current, Count, Hist) ->
delete_old_users(Days) -> delete_old_users(Days) ->
%% Get the list of registered users %% 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), {removed, N, UR} = delete_old_users(Days, Users),
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}. {ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
delete_old_users_vhost(Host, Days) -> delete_old_users_vhost(Host, Days) ->
%% Get the list of registered users %% 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), {removed, N, UR} = delete_old_users(Days, Users),
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}. {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}. {removed, length(Users_removed), Users_removed}.
delete_or_not(LUser, LServer, TimeStamp_oldest) -> 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), [] = ejabberd_sm:get_user_resources(LUser, LServer),
case mod_last:get_last_info(LUser, LServer) of case mod_last:get_last_info(LUser, LServer) of
{ok, TimeStamp, _Status} -> {ok, TimeStamp, _Status} ->
@ -1280,12 +1258,12 @@ subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -
subscribe_roster({Name, Server, Group, Nick}, Roster); subscribe_roster({Name, Server, Group, Nick}, Roster);
%% Subscribe Name2 to Name1 %% Subscribe Name2 to Name1
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) -> 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">>, []), iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []),
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster). subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
push_alltoall(S, G) -> push_alltoall(S, G) ->
Users = ejabberd_auth:get_vh_registered_users(S), Users = ejabberd_auth:get_users(S),
Users2 = build_list_users(G, Users, []), Users2 = build_list_users(G, Users, []),
subscribe_all(Users2), subscribe_all(Users2),
ok. ok.
@ -1485,10 +1463,7 @@ privacy_set(Username, Host, QueryS) ->
SubEl = xmpp:decode(QueryEl), SubEl = xmpp:decode(QueryEl),
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl], IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
from = From, to = To}, from = From, to = To},
ejabberd_hooks:run_fold(privacy_iq_set, mod_privacy:process_iq(IQ),
Host,
{error, xmpp:err_feature_not_implemented()},
[IQ, #userlist{}]),
ok. ok.
%%% %%%
@ -1499,14 +1474,14 @@ stats(Name) ->
case Name of case Name of
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000); <<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
<<"processes">> -> length(erlang:processes()); <<"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()); <<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list()) <<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
end. end.
stats(Name, Host) -> stats(Name, Host) ->
case Name of 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)) <<"onlineusers">> -> length(ejabberd_sm:get_vh_session_list(Host))
end. end.
@ -1638,7 +1613,7 @@ decide_rip_jid({UName, UServer}, Match_list) ->
Match_list). Match_list).
user_action(User, Server, Fun, OK) -> user_action(User, Server, Fun, OK) ->
case ejabberd_auth:is_user_exists(User, Server) of case ejabberd_auth:user_exists(User, Server) of
true -> true ->
case catch Fun() of case catch Fun() of
OK -> ok; OK -> ok;

View File

@ -36,7 +36,7 @@
import_start/2, import/5, announce/1, send_motd/1, disco_identity/5, import_start/2, import/5, announce/1, send_motd/1, disco_identity/5,
disco_features/5, disco_items/5, depends/2, disco_features/5, disco_items/5, depends/2,
send_announcement_to_all/3, announce_commands/4, 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, -export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]). handle_info/2, terminate/2, code_change/3]).
-export([announce_all/1, -export([announce_all/1,
@ -57,17 +57,22 @@
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok. -callback import(binary(), binary(), [binary()]) -> ok.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}. -callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> ok | {error, any()}.
-callback set_motd(binary(), xmlel()) -> {atomic, any()}. -callback set_motd(binary(), xmlel()) -> ok | {error, any()}.
-callback delete_motd(binary()) -> {atomic, any()}. -callback delete_motd(binary()) -> ok | {error, any()}.
-callback get_motd(binary()) -> {ok, xmlel()} | error. -callback get_motd(binary()) -> {ok, xmlel()} | error | {error, any()}.
-callback is_motd_user(binary(), binary()) -> boolean(). -callback is_motd_user(binary(), binary()) -> {ok, boolean()} | {error, any()}.
-callback set_motd_user(binary(), binary()) -> {atomic, 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()}). -record(state, {host :: binary()}).
-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>, -define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
<<"admin">>, <<Sub>>]). <<"admin">>, <<Sub>>]).
-define(MOTD_CACHE, motd_cache).
tokenize(Node) -> str:tokens(Node, <<"/#">>). tokenize(Node) -> str:tokens(Node, <<"/#">>).
@ -88,7 +93,7 @@ reload(Host, NewOpts, OldOpts) ->
true -> true ->
ok ok
end, end,
ok. init_cache(NewMod, Host, NewOpts).
depends(_Host, _Opts) -> depends(_Host, _Opts) ->
[{mod_adhoc, hard}]. [{mod_adhoc, hard}].
@ -100,6 +105,7 @@ init([Host, Opts]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host, ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50), ?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 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), Dest = jid:make(User, Server),
ejabberd_router:route( ejabberd_router:route(
xmpp:set_from_to(add_store_hint(Packet), Local, Dest)) 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) -> announce_all_hosts_all(#message{to = To} = Packet) ->
Local = jid:make(To#jid.server), Local = jid:make(To#jid.server),
@ -642,7 +648,7 @@ announce_all_hosts_all(#message{to = To} = Packet) ->
Dest = jid:make(User, Server), Dest = jid:make(User, Server),
ejabberd_router:route( ejabberd_router:route(
xmpp:set_from_to(add_store_hint(Packet), Local, Dest)) 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_online(#message{to = To} = Packet) ->
announce_online1(ejabberd_sm:get_vh_session_list(To#jid.lserver), 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) -> announce_motd_update(LServer, Packet) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer), delete_motd(Mod, LServer),
Mod:set_motd(LServer, xmpp:encode(Packet)). set_motd(Mod, LServer, xmpp:encode(Packet)).
announce_motd_delete(#message{to = To}) -> announce_motd_delete(#message{to = To}) ->
LServer = To#jid.lserver, LServer = To#jid.lserver,
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer). delete_motd(Mod, LServer).
announce_all_hosts_motd_delete(_Packet) -> announce_all_hosts_motd_delete(_Packet) ->
lists:foreach( lists:foreach(
fun(Host) -> fun(Host) ->
Mod = gen_mod:db_mod(Host, ?MODULE), Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:delete_motd(Host) delete_motd(Mod, Host)
end, ?MYHOSTS). end, ?MYHOSTS).
-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}. -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) #{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc)
when LUser /= <<>> -> when LUser /= <<>> ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of case get_motd(Mod, LServer) of
{ok, Packet} -> {ok, Packet} ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
Msg -> Msg ->
case Mod:is_motd_user(LUser, LServer) of case is_motd_user(Mod, LUser, LServer) of
false -> false ->
Local = jid:make(LServer), Local = jid:make(LServer),
ejabberd_router:route( ejabberd_router:route(
xmpp:set_from_to(Msg, Local, JID)), xmpp:set_from_to(Msg, Local, JID)),
Mod:set_motd_user(LUser, LServer); set_motd_user(Mod, LUser, LServer);
true -> true ->
ok ok
end end
@ -724,16 +730,81 @@ send_motd({#presence{type = available},
?ERROR_MSG("failed to decode motd packet ~p: ~s", ?ERROR_MSG("failed to decode motd packet ~p: ~s",
[Packet, xmpp:format_error(Why)]) [Packet, xmpp:format_error(Why)])
end; end;
error -> _ ->
ok ok
end, end,
Acc; Acc;
send_motd(Acc) -> send_motd(Acc) ->
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) -> get_stored_motd(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of case get_motd(Mod, LServer) of
{ok, Packet} -> {ok, Packet} ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
#message{body = Body, subject = Subject} -> #message{body = Body, subject = Subject} ->
@ -742,7 +813,7 @@ get_stored_motd(LServer) ->
?ERROR_MSG("failed to decode motd packet ~p: ~s", ?ERROR_MSG("failed to decode motd packet ~p: ~s",
[Packet, xmpp:format_error(Why)]) [Packet, xmpp:format_error(Why)])
end; end;
error -> _ ->
{<<>>, <<>>} {<<>>, <<>>}
end. end.
@ -775,6 +846,55 @@ route_forbidden_error(Packet) ->
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang), Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
ejabberd_router:route_error(Packet, Err). 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) -> export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), Mod = gen_mod:db_mod(LServer, ?MODULE),

View File

@ -40,11 +40,11 @@
%%%=================================================================== %%%===================================================================
init(_Host, _Opts) -> init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, motd, ejabberd_mnesia:create(?MODULE, motd,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, {attributes,
record_info(fields, motd)}]), record_info(fields, motd)}]),
ejabberd_mnesia:create(?MODULE, motd_users, ejabberd_mnesia:create(?MODULE, motd_users,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, {attributes,
record_info(fields, motd_users)}]). record_info(fields, motd_users)}]).
@ -55,13 +55,13 @@ set_motd_users(_LServer, USRs) ->
mnesia:write(#motd_users{us = {U, S}}) mnesia:write(#motd_users{us = {U, S}})
end, USRs) end, USRs)
end, end,
mnesia:transaction(F). transaction(F).
set_motd(LServer, Packet) -> set_motd(LServer, Packet) ->
F = fun() -> F = fun() ->
mnesia:write(#motd{server = LServer, packet = Packet}) mnesia:write(#motd{server = LServer, packet = Packet})
end, end,
mnesia:transaction(F). transaction(F).
delete_motd(LServer) -> delete_motd(LServer) ->
F = fun() -> F = fun() ->
@ -76,27 +76,27 @@ delete_motd(LServer) ->
mnesia:delete({motd_users, US}) mnesia:delete({motd_users, US})
end, Users) end, Users)
end, end,
mnesia:transaction(F). transaction(F).
get_motd(LServer) -> get_motd(LServer) ->
case mnesia:dirty_read({motd, LServer}) of case mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] -> [#motd{packet = Packet}] ->
{ok, Packet}; {ok, Packet};
_ -> [] ->
error error
end. end.
is_motd_user(LUser, LServer) -> is_motd_user(LUser, LServer) ->
case mnesia:dirty_read({motd_users, {LUser, LServer}}) of case mnesia:dirty_read({motd_users, {LUser, LServer}}) of
[#motd_users{}] -> true; [#motd_users{}] -> {ok, true};
_ -> false _ -> {ok, false}
end. end.
set_motd_user(LUser, LServer) -> set_motd_user(LUser, LServer) ->
F = fun() -> F = fun() ->
mnesia:write(#motd_users{us = {LUser, LServer}}) mnesia:write(#motd_users{us = {LUser, LServer}})
end, end,
mnesia:transaction(F). transaction(F).
need_transform(#motd{server = S}) when is_list(S) -> need_transform(#motd{server = S}) when is_list(S) ->
?INFO_MSG("Mnesia table 'motd' will be converted to binary", []), ?INFO_MSG("Mnesia table 'motd' will be converted to binary", []),
@ -124,3 +124,11 @@ import(LServer, <<"motd">>, [LUser, <<>>, _TimeStamp]) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% 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.

View File

@ -46,47 +46,48 @@ set_motd_users(_LServer, USRs) ->
ok = ejabberd_riak:put(#motd_users{us = {U, S}}, ok = ejabberd_riak:put(#motd_users{us = {U, S}},
motd_users_schema(), motd_users_schema(),
[{'2i', [{<<"server">>, S}]}]) [{'2i', [{<<"server">>, S}]}])
end, USRs), end, USRs)
{atomic, ok} catch _:{badmatch, {error, _} = Err} ->
catch _:{badmatch, Err} -> Err
{atomic, Err}
end. end.
set_motd(LServer, Packet) -> set_motd(LServer, Packet) ->
{atomic, ejabberd_riak:put(#motd{server = LServer, ejabberd_riak:put(#motd{server = LServer,
packet = Packet}, packet = Packet},
motd_schema())}. motd_schema()).
delete_motd(LServer) -> delete_motd(LServer) ->
try try
ok = ejabberd_riak:delete(motd, LServer), ok = ejabberd_riak:delete(motd, LServer),
ok = ejabberd_riak:delete_by_index(motd_users, ok = ejabberd_riak:delete_by_index(motd_users,
<<"server">>, <<"server">>,
LServer), LServer)
{atomic, ok} catch _:{badmatch, {error, _} = Err} ->
catch _:{badmatch, Err} -> Err
{atomic, Err}
end. end.
get_motd(LServer) -> get_motd(LServer) ->
case ejabberd_riak:get(motd, motd_schema(), LServer) of case ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} -> {ok, #motd{packet = Packet}} ->
{ok, Packet}; {ok, Packet};
_ -> {error, notfound} ->
error error;
{error, _} = Err ->
Err
end. end.
is_motd_user(LUser, LServer) -> is_motd_user(LUser, LServer) ->
case ejabberd_riak:get(motd_users, motd_users_schema(), case ejabberd_riak:get(motd_users, motd_users_schema(),
{LUser, LServer}) of {LUser, LServer}) of
{ok, #motd_users{}} -> true; {ok, #motd_users{}} -> {ok, true};
_ -> false {error, notfound} -> {ok, false};
{error, _} = Err -> Err
end. end.
set_motd_user(LUser, LServer) -> set_motd_user(LUser, LServer) ->
{atomic, ejabberd_riak:put( ejabberd_riak:put(
#motd_users{us = {LUser, LServer}}, motd_users_schema(), #motd_users{us = {LUser, LServer}}, motd_users_schema(),
[{'2i', [{<<"server">>, LServer}]}])}. [{'2i', [{<<"server">>, LServer}]}]).
import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) -> import(LServer, <<"motd">>, [<<>>, XML, _TimeStamp]) ->
El = fxml_stream:parse_element(XML), El = fxml_stream:parse_element(XML),

View File

@ -36,6 +36,7 @@
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("mod_announce.hrl"). -include("mod_announce.hrl").
-include("ejabberd_sql_pt.hrl"). -include("ejabberd_sql_pt.hrl").
-include("logger.hrl").
%%%=================================================================== %%%===================================================================
%%% API %%% API
@ -53,7 +54,7 @@ set_motd_users(LServer, USRs) ->
"xml=''"]) "xml=''"])
end, USRs) end, USRs)
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
set_motd(LServer, Packet) -> set_motd(LServer, Packet) ->
XML = fxml:element_to_binary(Packet), XML = fxml:element_to_binary(Packet),
@ -63,27 +64,24 @@ set_motd(LServer, Packet) ->
["!username=''", ["!username=''",
"xml=%(XML)s"]) "xml=%(XML)s"])
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
delete_motd(LServer) -> delete_motd(LServer) ->
F = fun() -> F = fun() ->
ejabberd_sql:sql_query_t(?SQL("delete from motd")) ejabberd_sql:sql_query_t(?SQL("delete from motd"))
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
get_motd(LServer) -> get_motd(LServer) ->
case catch ejabberd_sql:sql_query( case catch ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(xml)s from motd where username=''")) of ?SQL("select @(xml)s from motd where username=''")) of
{selected, [{XML}]} -> {selected, [{XML}]} ->
case fxml_stream:parse_element(XML) of parse_element(XML);
{error, _} -> {selected, []} ->
error; error;
Packet ->
{ok, Packet}
end;
_ -> _ ->
error {error, db_failure}
end. end.
is_motd_user(LUser, LServer) -> is_motd_user(LUser, LServer) ->
@ -92,9 +90,11 @@ is_motd_user(LUser, LServer) ->
?SQL("select @(username)s from motd" ?SQL("select @(username)s from motd"
" where username=%(LUser)s")) of " where username=%(LUser)s")) of
{selected, [_|_]} -> {selected, [_|_]} ->
true; {ok, true};
{selected, []} ->
{ok, false};
_ -> _ ->
false {error, db_failure}
end. end.
set_motd_user(LUser, LServer) -> set_motd_user(LUser, LServer) ->
@ -104,7 +104,7 @@ set_motd_user(LUser, LServer) ->
["!username=%(LUser)s", ["!username=%(LUser)s",
"xml=''"]) "xml=''"])
end, end,
ejabberd_sql:sql_transaction(LServer, F). transaction(LServer, F).
export(_Server) -> export(_Server) ->
[{motd, [{motd,
@ -131,3 +131,18 @@ import(_, _, _) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% 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.

View File

@ -57,23 +57,19 @@ filter_packet({#message{} = Msg, State} = Acc) ->
From = xmpp:get_from(Msg), From = xmpp:get_from(Msg),
LFrom = jid:tolower(From), LFrom = jid:tolower(From),
LBFrom = jid:remove_resource(LFrom), LBFrom = jid:remove_resource(LFrom),
#{pres_a := PresA, #{pres_a := PresA, jid := JID, lserver := LServer} = State,
pres_t := PresT,
pres_f := PresF} = State,
case (Msg#message.body == [] andalso case (Msg#message.body == [] andalso
Msg#message.subject == []) Msg#message.subject == [])
orelse ejabberd_router:is_my_route(From#jid.lserver) orelse ejabberd_router:is_my_route(From#jid.lserver)
orelse (?SETS):is_element(LFrom, PresA) orelse (?SETS):is_element(LFrom, PresA)
orelse (?SETS):is_element(LBFrom, PresA) orelse (?SETS):is_element(LBFrom, PresA)
orelse sets_bare_member(LBFrom, PresA) orelse sets_bare_member(LBFrom, PresA) of
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;
false -> false ->
#{lserver := LServer} = State, {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), Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false), Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if if
@ -89,7 +85,12 @@ filter_packet({#message{} = Msg, State} = Acc) ->
{stop, {drop, State}}; {stop, {drop, State}};
true -> true ->
Acc Acc
end end;
_ ->
Acc
end;
true ->
Acc
end; end;
filter_packet(Acc) -> filter_packet(Acc) ->
Acc. Acc.

View File

@ -39,12 +39,6 @@
-include("mod_privacy.hrl"). -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) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 50), 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). listitems_to_jids(Items, JIDs).
-spec process_block(iq(), [ljid()]) -> iq(). -spec process_block(iq(), [ljid()]) -> iq().
process_block(#iq{from = #jid{luser = LUser, lserver = LServer}, process_block(#iq{from = From} = IQ, LJIDs) ->
lang = Lang} = IQ, JIDs) -> #jid{luser = LUser, lserver = LServer} = From,
Filter = fun (List) -> 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, []), AlreadyBlocked = listitems_to_jids(List, []),
lists:foldr(fun (JID, List1) -> NewList = lists:foldr(
case lists:member(JID, AlreadyBlocked) fun(LJID, List1) ->
of case lists:member(LJID, AlreadyBlocked) of
true -> List1; true ->
List1;
false -> false ->
[#listitem{type = jid, [#listitem{type = jid,
value = JID, value = LJID,
action = deny, action = deny,
order = 0, order = 0,
match_all = true} match_all = true}|List1]
| 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
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)
end. end.
-spec process_unblock_all(iq()) -> iq(). -spec process_unblock_all(iq()) -> iq().
process_unblock_all(#iq{from = #jid{luser = LUser, lserver = LServer}, process_unblock_all(#iq{from = From} = IQ) ->
lang = Lang} = IQ) -> #jid{luser = LUser, lserver = LServer} = From,
Filter = fun (List) -> case mod_privacy:get_user_list(LUser, LServer, default) of
lists:filter(fun (#listitem{action = A}) -> A =/= deny {ok, {Name, List}} ->
end, NewList = lists:filter(
List) fun(#listitem{action = A}) ->
end, A /= deny
Mod = db_mod(LServer), end, List),
case Mod:unblock_by_filter(LUser, LServer, Filter) of case mod_privacy:set_list(LUser, LServer, Name, NewList) of
{atomic, ok} -> ok ->
mod_privacy:push_list_update(From, Name),
broadcast_event(From, #unblock{}),
xmpp:make_iq_result(IQ); xmpp:make_iq_result(IQ);
{atomic, {ok, Default, List}} -> {error, _} ->
UserList = make_userlist(Default, List), err_db_failure(IQ)
broadcast_list_update(LUser, LServer, UserList, Default), end;
broadcast_event(LUser, LServer, #unblock{}), error ->
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList)); broadcast_event(From, #unblock{}),
_Err -> xmpp:make_iq_result(IQ);
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer}, _Err]), {error, _} ->
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang), err_db_failure(IQ)
xmpp:make_error(IQ, Err)
end. end.
-spec process_unblock(iq(), [ljid()]) -> iq(). -spec process_unblock(iq(), [ljid()]) -> iq().
process_unblock(#iq{from = #jid{luser = LUser, lserver = LServer}, process_unblock(#iq{from = From} = IQ, LJIDs) ->
lang = Lang} = IQ, JIDs) -> #jid{luser = LUser, lserver = LServer} = From,
Filter = fun (List) -> case mod_privacy:get_user_list(LUser, LServer, default) of
lists:filter(fun (#listitem{action = deny, type = jid, {ok, {Name, List}} ->
value = JID}) -> NewList = lists:filter(
not lists:member(JID, JIDs); fun(#listitem{action = deny, type = jid,
(_) -> true value = LJID}) ->
end, not lists:member(LJID, LJIDs);
List) (_) ->
end, true
Mod = db_mod(LServer), end, List),
case Mod:unblock_by_filter(LUser, LServer, Filter) of case mod_privacy:set_list(LUser, LServer, Name, NewList) of
{atomic, ok} -> 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); xmpp:make_iq_result(IQ);
{atomic, {ok, Default, List}} -> {error, _} ->
UserList = make_userlist(Default, List), err_db_failure(IQ)
broadcast_list_update(LUser, LServer, UserList, Default), end;
broadcast_event(LUser, LServer, error ->
#unblock{items = [jid:make(J) || J <- JIDs]}), Items = [jid:make(LJID) || LJID <- LJIDs],
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_list, UserList)); broadcast_event(From, #unblock{items = Items}),
_Err -> xmpp:make_iq_result(IQ);
?ERROR_MSG("Error processing ~p: ~p", [{LUser, LServer, JIDs}, _Err]), {error, _} ->
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang), err_db_failure(IQ)
xmpp:make_error(IQ, Err)
end. end.
-spec make_userlist(binary(), [listitem()]) -> userlist(). -spec broadcast_event(jid(), block() | unblock()) -> ok.
make_userlist(Name, List) -> broadcast_event(#jid{luser = LUser, lserver = LServer} = From, Event) ->
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),
lists:foreach( lists:foreach(
fun(R) -> fun(R) ->
To = jid:replace_resource(From, R), To = jid:replace_resource(From, R),
@ -247,23 +251,21 @@ broadcast_event(LUser, LServer, Event) ->
end, ejabberd_sm:get_user_resources(LUser, LServer)). end, ejabberd_sm:get_user_resources(LUser, LServer)).
-spec process_get(iq()) -> iq(). -spec process_get(iq()) -> iq().
process_get(#iq{from = #jid{luser = LUser, lserver = LServer}, process_get(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
lang = Lang} = IQ) -> case mod_privacy:get_user_list(LUser, LServer, default) of
Mod = db_mod(LServer), {ok, {_, List}} ->
case Mod:process_blocklist_get(LUser, LServer) of
error ->
Err = xmpp:err_internal_server_error(<<"Database failure">>, Lang),
xmpp:make_error(IQ, Err);
List ->
LJIDs = listitems_to_jids(List, []), LJIDs = listitems_to_jids(List, []),
Items = [jid:make(J) || J <- LJIDs], Items = [jid:make(J) || J <- LJIDs],
xmpp:make_iq_result(IQ, #block_list{items = Items}) xmpp:make_iq_result(IQ, #block_list{items = Items});
error ->
xmpp:make_iq_result(IQ, #block_list{});
{error, _} ->
err_db_failure(IQ)
end. end.
-spec db_mod(binary()) -> module(). err_db_failure(#iq{lang = Lang} = IQ) ->
db_mod(LServer) -> Txt = <<"Database failure">>,
DBType = gen_mod:db_type(LServer, mod_privacy), xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)).
gen_mod:db_mod(DBType, ?MODULE).
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [iqdisc]. mod_opt_type(_) -> [iqdisc].

View File

@ -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
%%%===================================================================

View File

@ -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
%%%===================================================================

View File

@ -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
%%%===================================================================

View File

@ -203,7 +203,9 @@ disco_info(Acc, _, _, _Node, _Lang) ->
-spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state(). -spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state().
c2s_presence_in(C2SState, c2s_presence_in(C2SState,
#presence{from = From, to = To, type = Type} = Presence) -> #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) Insert = (Type == available)
and ((Subscription == both) or (Subscription == to)), and ((Subscription == both) or (Subscription == to)),
Delete = (Type == unavailable) or (Type == error), Delete = (Type == unavailable) or (Type == error),
@ -411,7 +413,7 @@ make_my_disco_hash(Host) ->
make_disco_hash(DiscoInfo, Algo) -> make_disco_hash(DiscoInfo, Algo) ->
Concat = list_to_binary([concat_identities(DiscoInfo), Concat = list_to_binary([concat_identities(DiscoInfo),
concat_features(DiscoInfo), concat_info(DiscoInfo)]), concat_features(DiscoInfo), concat_info(DiscoInfo)]),
misc:encode_base64(case Algo of base64:encode(case Algo of
md5 -> erlang:md5(Concat); md5 -> erlang:md5(Concat);
sha -> crypto:hash(sha, Concat); sha -> crypto:hash(sha, Concat);
sha224 -> crypto:hash(sha224, Concat); sha224 -> crypto:hash(sha224, Concat);

View File

@ -547,7 +547,7 @@ get_local_items({_, Host}, [<<"all users">>], _Server,
get_local_items({_, Host}, get_local_items({_, Host},
[<<"all users">>, <<$@, Diap/binary>>], _Server, [<<"all users">>, <<$@, Diap/binary>>], _Server,
_Lang) -> _Lang) ->
Users = ejabberd_auth:get_vh_registered_users(Host), Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]), SUsers = lists:sort([{S, U} || {U, S} <- Users]),
try try
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
@ -661,7 +661,7 @@ get_online_vh_users(Host) ->
end. end.
get_all_vh_users(Host) -> get_all_vh_users(Host) ->
case catch ejabberd_auth:get_vh_registered_users(Host) case catch ejabberd_auth:get_users(Host)
of of
{'EXIT', _Reason} -> []; {'EXIT', _Reason} -> [];
Users -> Users ->
@ -1194,7 +1194,7 @@ get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) ->
required = true}]}}; required = true}]}};
get_form(Host, get_form(Host,
?NS_ADMINL(<<"get-registered-users-num">>), Lang) -> ?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, {result, completed,
#xdata{type = form, #xdata{type = form,
fields = [?HFIELD(), fields = [?HFIELD(),
@ -1541,7 +1541,7 @@ set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
Server = JID#jid.lserver, Server = JID#jid.lserver,
true = Server == Host orelse true = Server == Host orelse
get_permission_level(From) == global, get_permission_level(From) == global,
true = ejabberd_auth:is_user_exists(User, Server), true = ejabberd_auth:user_exists(User, Server),
{User, Server} {User, Server}
end, end,
AccountStringList), AccountStringList),
@ -1610,7 +1610,7 @@ set_form(From, Host,
Server = JID#jid.lserver, Server = JID#jid.lserver,
true = Server == Host orelse true = Server == Host orelse
get_permission_level(From) == global, 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), ejabberd_auth:set_password(User, Server, Password),
{result, undefined}; {result, undefined};
set_form(From, Host, set_form(From, Host,

View File

@ -376,7 +376,7 @@ process_sm_iq_info(#iq{type = get, lang = Lang,
get_sm_identity(Acc, _From, get_sm_identity(Acc, _From,
#jid{luser = LUser, lserver = LServer}, _Node, _Lang) -> #jid{luser = LUser, lserver = LServer}, _Node, _Lang) ->
Acc ++ Acc ++
case ejabberd_auth:is_user_exists(LUser, LServer) of case ejabberd_auth:user_exists(LUser, LServer) of
true -> true ->
[#identity{category = <<"account">>, type = <<"registered">>}]; [#identity{category = <<"account">>, type = <<"registered">>}];
_ -> [] _ -> []

View File

@ -45,17 +45,24 @@
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
-include("mod_last.hrl"). -include("mod_last.hrl").
-define(LAST_CACHE, last_activity_cache).
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #last_activity{}) -> ok | pass. -callback import(binary(), #last_activity{}) -> ok | pass.
-callback get_last(binary(), binary()) -> -callback get_last(binary(), binary()) ->
{ok, non_neg_integer(), binary()} | not_found | {error, any()}. {ok, {non_neg_integer(), binary()}} | error | {error, any()}.
-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> any(). -callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> ok | {error, any()}.
-callback remove_user(binary(), binary()) -> 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) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq, IQDisc), ?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@ -91,6 +98,7 @@ reload(Host, NewOpts, OldOpts) ->
true -> true ->
ok ok
end, end,
init_cache(NewMod, Host, NewOpts),
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
{false, IQDisc, _} -> {false, IQDisc, _} ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST, 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 -> when T == get; T == set ->
case xmpp:has_subtag(IQ, #last{}) of case xmpp:has_subtag(IQ, #last{}) of
true -> 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 -> if Sub == from; Sub == both ->
Pres = #presence{from = To, to = From}, Pres = #presence{from = To, to = From},
case ejabberd_hooks:run_fold( case ejabberd_hooks:run_fold(
@ -177,13 +188,23 @@ privacy_check_packet(allow, C2SState,
privacy_check_packet(Acc, _, _, _) -> privacy_check_packet(Acc, _, _, _) ->
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()} | -spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found | {error, any()}. not_found | {error, any()}.
get_last(LUser, LServer) -> get_last(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), 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(). -spec get_last_iq(iq(), binary(), binary()) -> iq().
get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) -> get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
@ -223,7 +244,16 @@ store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE), 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()} | -spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found. not_found.
@ -238,7 +268,51 @@ remove_user(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE), 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() -> import_info() ->
[{<<"last">>, 3}]. [{<<"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(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(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].

View File

@ -27,7 +27,8 @@
-behaviour(mod_last). -behaviour(mod_last).
%% API %% 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]). -export([need_transform/1, transform/1]).
-include("mod_last.hrl"). -include("mod_last.hrl").
@ -38,31 +39,36 @@
%%%=================================================================== %%%===================================================================
init(_Host, _Opts) -> init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, last_activity, ejabberd_mnesia:create(?MODULE, last_activity,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, record_info(fields, last_activity)}]). {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) -> get_last(LUser, LServer) ->
case mnesia:dirty_read(last_activity, {LUser, LServer}) of case mnesia:dirty_read(last_activity, {LUser, LServer}) of
[] -> [] ->
not_found; error;
[#last_activity{timestamp = TimeStamp, [#last_activity{timestamp = TimeStamp,
status = Status}] -> status = Status}] ->
{ok, TimeStamp, Status} {ok, {TimeStamp, Status}}
end. end.
store_last_info(LUser, LServer, TimeStamp, Status) -> store_last_info(LUser, LServer, TimeStamp, Status) ->
US = {LUser, LServer}, mnesia:dirty_write(#last_activity{us = {LUser, LServer},
F = fun () ->
mnesia:write(#last_activity{us = US,
timestamp = TimeStamp, timestamp = TimeStamp,
status = Status}) status = Status}).
end,
mnesia:transaction(F).
remove_user(LUser, LServer) -> remove_user(LUser, LServer) ->
US = {LUser, LServer}, US = {LUser, LServer},
F = fun () -> mnesia:delete({last_activity, US}) end, mnesia:dirty_delete({last_activity, US}).
mnesia:transaction(F).
import(_LServer, #last_activity{} = LA) -> import(_LServer, #last_activity{} = LA) ->
mnesia:dirty_write(LA). mnesia:dirty_write(LA).

View File

@ -43,19 +43,20 @@ get_last(LUser, LServer) ->
{LUser, LServer}) of {LUser, LServer}) of
{ok, #last_activity{timestamp = TimeStamp, {ok, #last_activity{timestamp = TimeStamp,
status = Status}} -> status = Status}} ->
{ok, TimeStamp, Status}; {ok, {TimeStamp, Status}};
{error, notfound} -> {error, notfound} ->
not_found; error;
Err -> _Err ->
Err %% TODO: log error
{error, db_failure}
end. end.
store_last_info(LUser, LServer, TimeStamp, Status) -> store_last_info(LUser, LServer, TimeStamp, Status) ->
US = {LUser, LServer}, US = {LUser, LServer},
{atomic, ejabberd_riak:put(#last_activity{us = US, ejabberd_riak:put(#last_activity{us = US,
timestamp = TimeStamp, timestamp = TimeStamp,
status = Status}, status = Status},
last_activity_schema())}. last_activity_schema()).
remove_user(LUser, LServer) -> remove_user(LUser, LServer) ->
{atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}. {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.

View File

@ -43,22 +43,37 @@ init(_Host, _Opts) ->
ok. ok.
get_last(LUser, LServer) -> 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, []} -> {selected, []} ->
not_found; error;
{selected, [{TimeStamp, Status}]} -> {selected, [{TimeStamp, Status}]} ->
{ok, TimeStamp, Status}; {ok, {TimeStamp, Status}};
Reason -> Reason ->
?ERROR_MSG("failed to get last for user ~s@~s: ~p", ?ERROR_MSG("failed to get last for user ~s@~s: ~p",
[LUser, LServer, Reason]), [LUser, LServer, Reason]),
{error, {invalid_result, Reason}} {error, db_failure}
end. end.
store_last_info(LUser, LServer, TimeStamp, Status) -> 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) -> 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) -> export(_Server) ->
[{last_activity, [{last_activity,

View File

@ -37,7 +37,7 @@
remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/2, 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, 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, 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("xmpp.hrl").
-include("logger.hrl"). -include("logger.hrl").
@ -255,7 +255,7 @@ set_room_option(_Acc, {mam, Val}, _Lang) ->
set_room_option(Acc, _Property, _Lang) -> set_room_option(Acc, _Property, _Lang) ->
Acc. 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}) -> user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
Peer = xmpp:get_from(Pkt), Peer = xmpp:get_from(Pkt),
LUser = JID#jid.luser, LUser = JID#jid.luser,
@ -263,7 +263,7 @@ user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
Pkt2 = case should_archive(Pkt, LServer) of Pkt2 = case should_archive(Pkt, LServer) of
true -> true ->
Pkt1 = strip_my_archived_tag(Pkt, LServer), 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} -> {ok, ID} ->
set_stanza_id(Pkt1, JID, ID); set_stanza_id(Pkt1, JID, ID);
_ -> _ ->
@ -274,7 +274,7 @@ user_receive_packet({Pkt, #{jid := JID} = C2SState}) ->
end, end,
{Pkt2, C2SState}. {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}) -> user_send_packet({Pkt, #{jid := JID} = C2SState}) ->
Peer = xmpp:get_to(Pkt), Peer = xmpp:get_to(Pkt),
LUser = JID#jid.luser, LUser = JID#jid.luser,
@ -282,7 +282,7 @@ user_send_packet({Pkt, #{jid := JID} = C2SState}) ->
Pkt2 = case should_archive(Pkt, LServer) of Pkt2 = case should_archive(Pkt, LServer) of
true -> true ->
Pkt1 = strip_my_archived_tag(Pkt, LServer), 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 LUser, LServer, Peer, send) of
{ok, ID} -> {ok, ID} ->
set_stanza_id(Pkt1, JID, 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 case should_archive(Pkt, LServer) of
true -> true ->
Pkt1 = strip_my_archived_tag(Pkt, LServer), 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} -> {ok, ID} ->
{archived, set_stanza_id(Pkt1, To, ID)}; {archived, set_stanza_id(Pkt1, To, ID)};
_ -> _ ->
@ -311,8 +311,8 @@ offline_message({_Action, #message{from = Peer, to = To} = Pkt} = Acc) ->
Acc Acc
end. end.
-spec user_send_packet_strip_tag({stanza(), ejabberd_c2s:state()}) -> -spec user_send_packet_strip_tag({stanza(), c2s_state()}) ->
{stanza(), ejabberd_c2s:state()}. {stanza(), c2s_state()}.
user_send_packet_strip_tag({Pkt, #{jid := JID} = C2SState}) -> user_send_packet_strip_tag({Pkt, #{jid := JID} = C2SState}) ->
LServer = JID#jid.lserver, LServer = JID#jid.lserver,
{strip_my_archived_tag(Pkt, LServer), C2SState}. {strip_my_archived_tag(Pkt, LServer), C2SState}.
@ -415,16 +415,16 @@ disco_sm_features({result, OtherFeatures},
disco_sm_features(Acc, _From, _To, _Node, _Lang) -> disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc. 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) -> message_is_archived(true, _C2SState, _Pkt) ->
true; true;
message_is_archived(false, #{jid := JID} = C2SState, Pkt) -> message_is_archived(false, #{jid := JID}, Pkt) ->
#jid{luser = LUser, lserver = LServer} = JID, #jid{luser = LUser, lserver = LServer} = JID,
Peer = xmpp:get_from(Pkt), Peer = xmpp:get_from(Pkt),
case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage, false) of case gen_mod:get_module_opt(LServer, ?MODULE, assume_mam_usage, false) of
true -> true ->
should_archive(strip_my_archived_tag(Pkt, LServer), LServer) 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), get_prefs(LUser, LServer),
Peer); Peer);
false -> false ->
@ -457,6 +457,10 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
delete_old_messages(_TypeBin, _Days) -> delete_old_messages(_TypeBin, _Days) ->
unsupported_type. unsupported_type.
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% Internal functions
%%%=================================================================== %%%===================================================================
@ -611,9 +615,9 @@ strip_x_jid_tags(Pkt) ->
end, Els), end, Els),
xmpp:set_els(Pkt, NewEls). xmpp:set_els(Pkt, NewEls).
-spec should_archive_peer(c2s_state() | undefined, binary(), binary(), -spec should_archive_peer(binary(), binary(),
#archive_prefs{}, jid()) -> boolean(). #archive_prefs{}, jid()) -> boolean().
should_archive_peer(C2SState, LUser, LServer, should_archive_peer(LUser, LServer,
#archive_prefs{default = Default, #archive_prefs{default = Default,
always = Always, always = Always,
never = Never}, never = Never},
@ -631,23 +635,11 @@ should_archive_peer(C2SState, LUser, LServer,
always -> true; always -> true;
never -> false; never -> false;
roster -> roster ->
Sub = case C2SState of {Sub, _} = ejabberd_hooks:run_fold(
undefined ->
{S, _} = ejabberd_hooks:run_fold(
roster_get_jid_info, roster_get_jid_info,
LServer, {none, []}, LServer, {none, []},
[LUser, LServer, Peer]), [LUser, LServer, Peer]),
S; Sub == both orelse Sub == from orelse Sub == to
_ ->
ejabberd_c2s:get_subscription(
LPeer, C2SState)
end,
case Sub of
both -> true;
from -> true;
to -> true;
_ -> false
end
end end
end end
end. end.
@ -715,12 +707,12 @@ may_enter_room(From,
may_enter_room(From, MUCState) -> may_enter_room(From, MUCState) ->
mod_muc_room:is_occupant_or_admin(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) -> binary(), binary(), jid(), send | recv) ->
{ok, binary()} | pass. {ok, binary()} | pass.
store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) -> store_msg(Pkt, LUser, LServer, Peer, Dir) ->
Prefs = get_prefs(LUser, LServer), 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 -> true ->
US = {LUser, LServer}, US = {LUser, LServer},
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt, case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,

View File

@ -30,7 +30,7 @@
%% API %% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, -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_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl"). -include("xmpp.hrl").
@ -116,7 +116,7 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
"def=%(SDefault)s", "def=%(SDefault)s",
"always=%(SAlways)s", "always=%(SAlways)s",
"never=%(SNever)s"]) of "never=%(SNever)s"]) of
{updated, _} -> ok ->
ok; ok;
Err -> Err ->
Err Err
@ -181,6 +181,47 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
{[], false, 0} {[], false, 0}
end. 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 %%% Internal functions
%%%=================================================================== %%%===================================================================

View File

@ -25,7 +25,6 @@
-module(mod_metrics). -module(mod_metrics).
-behaviour(ejabberd_config).
-author('christophe.romain@process-one.net'). -author('christophe.romain@process-one.net').
-behaviour(gen_mod). -behaviour(gen_mod).
@ -33,8 +32,7 @@
-include("logger.hrl"). -include("logger.hrl").
-include("xmpp.hrl"). -include("xmpp.hrl").
-export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1, -export([start/2, stop/1, mod_opt_type/1, depends/2, reload/3]).
depends/2, reload/3]).
-export([offline_message_hook/1, -export([offline_message_hook/1,
sm_register_connection_hook/3, sm_remove_connection_hook/3, sm_register_connection_hook/3, sm_remove_connection_hook/3,
@ -42,6 +40,9 @@
s2s_send_packet/1, s2s_receive_packet/1, s2s_send_packet/1, s2s_receive_packet/1,
remove_user/2, register_user/2]). remove_user/2, register_user/2]).
-define(SOCKET_NAME, mod_metrics_udp_socket).
-define(SOCKET_REGISTER_RETRIES, 10).
%%==================================================================== %%====================================================================
%% API %% API
%%==================================================================== %%====================================================================
@ -126,20 +127,20 @@ register_user(_User, Server) ->
%%==================================================================== %%====================================================================
push(Host, Probe) -> 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) -> send_metrics(Host, Probe, Peer, Port) ->
% our default metrics handler is https://github.com/processone/grapherl % our default metrics handler is https://github.com/processone/grapherl
% grapherl metrics are named first with service domain, then nodename % grapherl metrics are named first with service domain, then nodename
% and name of the data itself, followed by type timestamp and value % and name of the data itself, followed by type timestamp and value
% example => process-one.net/xmpp-1.user_receive_packet:c/1441784958:1 % example => process-one.net/xmpp-1.user_receive_packet:c/1441784958:1
[_, NodeId] = str:tokens(misc:atom_to_binary(node()), <<"@">>), [_, FQDN] = binary:split(misc:atom_to_binary(node()), <<"@">>),
[Node | _] = str:tokens(NodeId, <<".">>), [Node|_] = binary:split(FQDN, <<".">>),
BaseId = <<Host/binary, "/", Node/binary, ".">>, BaseId = <<Host/binary, "/", Node/binary, ".">>,
DateTime = erlang:universaltime(), TS = integer_to_binary(p1_time_compat:system_time(seconds)),
UnixTime = calendar:datetime_to_gregorian_seconds(DateTime) - 62167219200, case get_socket(?SOCKET_REGISTER_RETRIES) of
TS = integer_to_binary(UnixTime),
case gen_udp:open(0) of
{ok, Socket} -> {ok, Socket} ->
case Probe of case Probe of
{Key, Val} -> {Key, Val} ->
@ -151,14 +152,38 @@ send_metrics(Host, Probe, Peer, Port) ->
Data = <<BaseId/binary, (misc:atom_to_binary(Key))/binary, Data = <<BaseId/binary, (misc:atom_to_binary(Key))/binary,
":c/", TS/binary, ":1">>, ":c/", TS/binary, ":1">>,
gen_udp:send(Socket, Peer, Port, Data) gen_udp:send(Socket, Peer, Port, Data)
end, end;
gen_udp:close(Socket); Err ->
Error -> Err
?WARNING_MSG("can not open udp socket to grapherl: ~p", [Error])
end. 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(_) -> mod_opt_type(_) ->
[]. [ip, port].

View File

@ -84,14 +84,18 @@ get_commands_spec() ->
desc = "List existing rooms ('global' to get all vhosts)", desc = "List existing rooms ('global' to get all vhosts)",
policy = admin, policy = admin,
module = ?MODULE, function = muc_online_rooms, 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}], args = [{host, binary}],
result = {rooms, {list, {room, string}}}}, result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = muc_register_nick, tags = [muc], #ejabberd_commands{name = muc_register_nick, tags = [muc],
desc = "Register a nick in the MUC service", 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, 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 = [{nick, binary}, {jid, binary}, {domain, binary}],
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"conference.example.org">>],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = muc_unregister_nick, tags = [muc], #ejabberd_commands{name = muc_unregister_nick, tags = [muc],
desc = "Unregister the nick in the MUC service", desc = "Unregister the nick in the MUC service",
@ -102,23 +106,31 @@ get_commands_spec() ->
#ejabberd_commands{name = create_room, tags = [muc_room], #ejabberd_commands{name = create_room, tags = [muc_room],
desc = "Create a MUC room name@service in host", desc = "Create a MUC room name@service in host",
module = ?MODULE, function = create_room, 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}, args = [{name, binary}, {service, binary},
{host, binary}], {host, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = destroy_room, tags = [muc_room], #ejabberd_commands{name = destroy_room, tags = [muc_room],
desc = "Destroy a MUC room", desc = "Destroy a MUC room",
module = ?MODULE, function = destroy_room, module = ?MODULE, function = destroy_room,
args_desc = ["Room name", "MUC service"],
args_example = ["room1", "muc.example.com"],
args = [{name, binary}, {service, binary}], args = [{name, binary}, {service, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = create_rooms_file, tags = [muc], #ejabberd_commands{name = create_rooms_file, tags = [muc],
desc = "Create the rooms indicated in file", desc = "Create the rooms indicated in file",
longdesc = "Provide one room JID per line. Rooms will be created after restart.", longdesc = "Provide one room JID per line. Rooms will be created after restart.",
module = ?MODULE, function = create_rooms_file, 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}], args = [{file, string}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = create_room_with_opts, tags = [muc_room], #ejabberd_commands{name = create_room_with_opts, tags = [muc_room],
desc = "Create a MUC room name@service in host with given options", desc = "Create a MUC room name@service in host with given options",
module = ?MODULE, function = create_room_with_opts, 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}, args = [{name, binary}, {service, binary},
{host, binary}, {host, binary},
{options, {list, {options, {list,
@ -132,28 +144,45 @@ get_commands_spec() ->
desc = "Destroy the rooms indicated in file", desc = "Destroy the rooms indicated in file",
longdesc = "Provide one room JID per line.", longdesc = "Provide one room JID per line.",
module = ?MODULE, function = destroy_rooms_file, 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}], args = [{file, string}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = rooms_unused_list, tags = [muc], #ejabberd_commands{name = rooms_unused_list, tags = [muc],
desc = "List the rooms that are unused for many days in host", desc = "List the rooms that are unused for many days in host",
module = ?MODULE, function = rooms_unused_list, 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}], args = [{host, binary}, {days, integer}],
result = {rooms, {list, {room, string}}}}, result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = rooms_unused_destroy, tags = [muc], #ejabberd_commands{name = rooms_unused_destroy, tags = [muc],
desc = "Destroy the rooms that are unused for many days in host", desc = "Destroy the rooms that are unused for many days in host",
module = ?MODULE, function = rooms_unused_destroy, 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}], args = [{host, binary}, {days, integer}],
result = {rooms, {list, {room, string}}}}, result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = get_user_rooms, tags = [muc], #ejabberd_commands{name = get_user_rooms, tags = [muc],
desc = "Get the list of rooms where this user is occupant", desc = "Get the list of rooms where this user is occupant",
module = ?MODULE, function = get_user_rooms, 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}], args = [{user, binary}, {host, binary}],
result = {rooms, {list, {room, string}}}}, result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = get_room_occupants, tags = [muc_room], #ejabberd_commands{name = get_room_occupants, tags = [muc_room],
desc = "Get the list of occupants of a MUC room", desc = "Get the list of occupants of a MUC room",
module = ?MODULE, function = get_room_occupants, 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}], args = [{name, binary}, {service, binary}],
result = {occupants, {list, result = {occupants, {list,
{occupant, {tuple, {occupant, {tuple,
@ -166,6 +195,10 @@ get_commands_spec() ->
#ejabberd_commands{name = get_room_occupants_number, tags = [muc_room], #ejabberd_commands{name = get_room_occupants_number, tags = [muc_room],
desc = "Get the number of occupants of a MUC room", desc = "Get the number of occupants of a MUC room",
module = ?MODULE, function = get_room_occupants_number, 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}], args = [{name, binary}, {service, binary}],
result = {occupants, integer}}, result = {occupants, integer}},
@ -173,18 +206,27 @@ get_commands_spec() ->
desc = "Send a direct invitation to several destinations", desc = "Send a direct invitation to several destinations",
longdesc = "Password and Message can also be: none. Users JIDs are separated with : ", longdesc = "Password and Message can also be: none. Users JIDs are separated with : ",
module = ?MODULE, function = send_direct_invitation, 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}], args = [{name, binary}, {service, binary}, {password, binary}, {reason, binary}, {users, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = change_room_option, tags = [muc_room], #ejabberd_commands{name = change_room_option, tags = [muc_room],
desc = "Change an option in a MUC room", desc = "Change an option in a MUC room",
module = ?MODULE, function = change_room_option, 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}, args = [{name, binary}, {service, binary},
{option, binary}, {value, binary}], {option, binary}, {value, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = get_room_options, tags = [muc_room], #ejabberd_commands{name = get_room_options, tags = [muc_room],
desc = "Get options from a MUC room", desc = "Get options from a MUC room",
module = ?MODULE, function = get_room_options, 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}], args = [{name, binary}, {service, binary}],
result = {options, {list, result = {options, {list,
{option, {tuple, {option, {tuple,
@ -195,28 +237,47 @@ get_commands_spec() ->
#ejabberd_commands{name = subscribe_room, tags = [muc_room], #ejabberd_commands{name = subscribe_room, tags = [muc_room],
desc = "Subscribe to a MUC conference", desc = "Subscribe to a MUC conference",
module = ?MODULE, function = subscribe_room, 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}, args = [{user, binary}, {nick, binary}, {room, binary},
{nodes, binary}], {nodes, binary}],
result = {nodes, {list, {node, string}}}}, result = {nodes, {list, {node, string}}}},
#ejabberd_commands{name = unsubscribe_room, tags = [muc_room], #ejabberd_commands{name = unsubscribe_room, tags = [muc_room],
desc = "Unsubscribe from a MUC conference", desc = "Unsubscribe from a MUC conference",
module = ?MODULE, function = unsubscribe_room, 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}], args = [{user, binary}, {room, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = get_subscribers, tags = [muc_room], #ejabberd_commands{name = get_subscribers, tags = [muc_room],
desc = "List subscribers of a MUC conference", desc = "List subscribers of a MUC conference",
module = ?MODULE, function = get_subscribers, 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}], args = [{name, binary}, {service, binary}],
result = {subscribers, {list, {jid, string}}}}, result = {subscribers, {list, {jid, string}}}},
#ejabberd_commands{name = set_room_affiliation, tags = [muc_room], #ejabberd_commands{name = set_room_affiliation, tags = [muc_room],
desc = "Change an affiliation in a MUC room", desc = "Change an affiliation in a MUC room",
module = ?MODULE, function = set_room_affiliation, 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}, args = [{name, binary}, {service, binary},
{jid, binary}, {affiliation, binary}], {jid, binary}, {affiliation, binary}],
result = {res, rescode}}, result = {res, rescode}},
#ejabberd_commands{name = get_room_affiliations, tags = [muc_room], #ejabberd_commands{name = get_room_affiliations, tags = [muc_room],
desc = "Get the list of affiliations of a MUC room", desc = "Get the list of affiliations of a MUC room",
module = ?MODULE, function = get_room_affiliations, 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}], args = [{name, binary}, {service, binary}],
result = {affiliations, {list, result = {affiliations, {list,
{affiliation, {tuple, {affiliation, {tuple,
@ -237,7 +298,7 @@ muc_online_rooms(ServerHost) ->
Hosts = find_hosts(ServerHost), Hosts = find_hosts(ServerHost),
lists:flatmap( lists:flatmap(
fun(Host) -> fun(Host) ->
[{<<Name/binary, "@", Host/binary>>} [<<Name/binary, "@", Host/binary>>
|| {Name, _, _} <- mod_muc:get_online_rooms(Host)] || {Name, _, _} <- mod_muc:get_online_rooms(Host)]
end, Hosts). end, Hosts).
@ -337,7 +398,7 @@ web_page_host(_, Host,
q = Q, q = Q,
lang = Lang} = _Request) -> lang = Lang} = _Request) ->
Sort_query = get_sort_query(Q), 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}; {stop, Res};
web_page_host(Acc, _, _) -> Acc. web_page_host(Acc, _, _) -> Acc.
@ -421,11 +482,11 @@ build_info_room({Name, Host, Pid}) ->
History = (S#state.history)#lqueue.queue, History = (S#state.history)#lqueue.queue,
Ts_last_message = Ts_last_message =
case queue:is_empty(History) of case p1_queue:is_empty(History) of
true -> true ->
<<"A long time ago">>; <<"A long time ago">>;
false -> false ->
Last_message1 = queue:last(History), Last_message1 = get_queue_last(History),
{_, _, _, Ts_last, _} = Last_message1, {_, _, _, Ts_last, _} = Last_message1,
xmpp_util:encode_timestamp(Ts_last) xmpp_util:encode_timestamp(Ts_last)
end, end,
@ -439,6 +500,10 @@ build_info_room({Name, Host, Pid}) ->
Just_created, Just_created,
Title}. Title}.
get_queue_last(Queue) ->
List = p1_queue:to_list(Queue),
lists:last(List).
prepare_rooms_infos(Rooms) -> prepare_rooms_infos(Rooms) ->
[prepare_room_info(Room) || Room <- Rooms]. [prepare_room_info(Room) || Room <- Rooms].
prepare_room_info(Room_info) -> prepare_room_info(Room_info) ->
@ -675,11 +740,11 @@ decide_room({_Room_name, _Host, Room_pid}, Last_allowed) ->
History = (S#state.history)#lqueue.queue, History = (S#state.history)#lqueue.queue,
Ts_now = calendar:universal_time(), Ts_now = calendar:universal_time(),
Ts_uptime = uptime_seconds(), Ts_uptime = uptime_seconds(),
{Has_hist, Last} = case queue:is_empty(History) of {Has_hist, Last} = case p1_queue:is_empty(History) of
true -> true ->
{false, Ts_uptime}; {false, Ts_uptime};
false -> false ->
Last_message = queue:last(History), Last_message = get_queue_last(History),
{_, _, _, Ts_last, _} = Last_message, {_, _, _, Ts_last, _} = Last_message,
Ts_diff = Ts_diff =
calendar:datetime_to_gregorian_seconds(Ts_now) calendar:datetime_to_gregorian_seconds(Ts_now)
@ -832,6 +897,7 @@ format_room_option(OptionString, ValueString) ->
password -> ValueString; password -> ValueString;
subject ->ValueString; subject ->ValueString;
subject_author ->ValueString; subject_author ->ValueString;
presence_broadcast ->misc:expr_to_term(ValueString);
max_users -> binary_to_integer(ValueString); max_users -> binary_to_integer(ValueString);
_ -> misc:binary_to_atom(ValueString) _ -> misc:binary_to_atom(ValueString)
end, end,
@ -872,6 +938,7 @@ change_option(Option, Value, Config) ->
password -> Config#config{password = Value}; password -> Config#config{password = Value};
password_protected -> Config#config{password_protected = Value}; password_protected -> Config#config{password_protected = Value};
persistent -> Config#config{persistent = Value}; persistent -> Config#config{persistent = Value};
presence_broadcast -> Config#config{presence_broadcast = Value};
public -> Config#config{public = Value}; public -> Config#config{public = Value};
public_list -> Config#config{public_list = Value}; public_list -> Config#config{public_list = Value};
title -> Config#config{title = Value}; title -> Config#config{title = Value};

View File

@ -500,7 +500,7 @@ make_dir_rec(Dir) ->
%% {ok, F1}=file:open("valid-xhtml10.png", [read]). %% {ok, F1}=file:open("valid-xhtml10.png", [read]).
%% {ok, F1b}=file:read(F1, 1000000). %% {ok, F1b}=file:read(F1, 1000000).
%% c("../../ejabberd/src/jlib.erl"). %% c("../../ejabberd/src/jlib.erl").
%% misc:encode_base64(F1b). %% base64:encode(F1b).
image_base64(<<"powered-by-erlang.png">>) -> image_base64(<<"powered-by-erlang.png">>) ->
<<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoA" <<"iVBORw0KGgoAAAANSUhEUgAAAGUAAAAfCAYAAAD+xQNoA"
@ -676,7 +676,7 @@ create_image_files(Images_dir) ->
lists:foreach(fun (Filename) -> lists:foreach(fun (Filename) ->
Filename_full = fjoin([Images_dir, Filename]), Filename_full = fjoin([Images_dir, Filename]),
{ok, F} = file:open(Filename_full, [write]), {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]), io:format(F, <<"~s">>, [Image]),
file:close(F) file:close(F)
end, end,
@ -1170,7 +1170,7 @@ has_no_permanent_store_hint(Packet) ->
mod_opt_type(access_log) -> mod_opt_type(access_log) ->
fun acl:access_rules_validator/1; 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) -> mod_opt_type(dirname) ->
fun (room_jid) -> room_jid; fun (room_jid) -> room_jid;
(room_name) -> room_name (room_name) -> room_name

View File

@ -1152,13 +1152,13 @@ handle_iq_vcard(ToJID, NewId, #iq{type = Type, sub_els = SubEls} = IQ) ->
-spec stanzaid_pack(binary(), binary()) -> binary(). -spec stanzaid_pack(binary(), binary()) -> binary().
stanzaid_pack(OriginalId, Resource) -> stanzaid_pack(OriginalId, Resource) ->
<<"berd", <<"berd",
(misc:encode_base64(<<"ejab\000", (base64:encode(<<"ejab\000",
OriginalId/binary, "\000", OriginalId/binary, "\000",
Resource/binary>>))/binary>>. Resource/binary>>))/binary>>.
-spec stanzaid_unpack(binary()) -> {binary(), binary()}. -spec stanzaid_unpack(binary()) -> {binary(), binary()}.
stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) -> stanzaid_unpack(<<"berd", StanzaIdBase64/binary>>) ->
StanzaId = misc:decode_base64(StanzaIdBase64), StanzaId = base64:decode(StanzaIdBase64),
[<<"ejab">>, OriginalId, Resource] = [<<"ejab">>, OriginalId, Resource] =
str:tokens(StanzaId, <<"\000">>), str:tokens(StanzaId, <<"\000">>),
{OriginalId, Resource}. {OriginalId, Resource}.

View File

@ -33,14 +33,13 @@
-protocol({xep, 160, '1.0'}). -protocol({xep, 160, '1.0'}).
-protocol({xep, 334, '0.2'}). -protocol({xep, 334, '0.2'}).
-behaviour(gen_server).
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, -export([start/2,
stop/1, stop/1,
reload/3, reload/3,
store_packet/1, store_packet/1,
store_offline_msg/5, store_offline_msg/1,
c2s_self_presence/1, c2s_self_presence/1,
get_sm_features/5, get_sm_features/5,
get_sm_identity/5, get_sm_identity/5,
@ -64,9 +63,7 @@
webadmin_user/4, webadmin_user/4,
webadmin_user_parse_query/5]). webadmin_user_parse_query/5]).
-export([init/1, handle_call/3, handle_cast/2, -export([mod_opt_type/1, depends/2]).
handle_info/2, terminate/2, code_change/3,
mod_opt_type/1, depends/2]).
-deprecated({get_queue_length,2}). -deprecated({get_queue_length,2}).
@ -86,14 +83,11 @@
%% default value for the maximum number of user messages %% default value for the maximum number of user messages
-define(MAX_USER_MESSAGES, infinity). -define(MAX_USER_MESSAGES, infinity).
-type us() :: {binary(), binary()}.
-type c2s_state() :: ejabberd_c2s:state(). -type c2s_state() :: ejabberd_c2s:state().
-callback init(binary(), gen_mod:opts()) -> any(). -callback init(binary(), gen_mod:opts()) -> any().
-callback import(#offline_msg{}) -> ok. -callback import(#offline_msg{}) -> ok.
-callback store_messages(binary(), us(), [#offline_msg{}], -callback store_message(#offline_msg{}) -> ok | {error, any()}.
non_neg_integer(), non_neg_integer()) ->
{atomic, any()}.
-callback pop_messages(binary(), binary()) -> -callback pop_messages(binary(), binary()) ->
{ok, [#offline_msg{}]} | {error, any()}. {ok, [#offline_msg{}]} | {error, any()}.
-callback remove_expired_messages(binary()) -> {atomic, any()}. -callback remove_expired_messages(binary()) -> {atomic, any()}.
@ -108,25 +102,10 @@
-callback remove_all_messages(binary(), binary()) -> {atomic, any()}. -callback remove_all_messages(binary(), binary()) -> {atomic, any()}.
-callback count_messages(binary(), binary()) -> non_neg_integer(). -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) -> depends(_Host, _Opts) ->
[]. [].
%%==================================================================== start(Host, Opts) ->
%% gen_server callbacks
%%====================================================================
init([Host, Opts]) ->
process_flag(trap_exit, true),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), 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, ejabberd_hooks:add(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50), ?MODULE, webadmin_user_parse_query, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE, gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE,
?MODULE, handle_offline_query, IQDisc), ?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}}.
stop(Host) ->
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,
ejabberd_hooks:delete(offline_message_hook, Host, ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, store_packet, 50), ?MODULE, store_packet, 50),
ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50), ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
@ -229,41 +153,48 @@ terminate(_Reason, State) ->
?MODULE, webadmin_user, 50), ?MODULE, webadmin_user, 50),
ejabberd_hooks:delete(webadmin_user_parse_query, Host, ejabberd_hooks:delete(webadmin_user_parse_query, Host,
?MODULE, webadmin_user_parse_query, 50), ?MODULE, webadmin_user_parse_query, 50),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_FLEX_OFFLINE).
ok.
reload(Host, NewOpts, OldOpts) ->
code_change(_OldVsn, State, _Extra) -> {ok, State}. NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) -> if NewMod /= OldMod ->
Mod = gen_mod:db_mod(Host, ?MODULE), NewMod:init(Host, NewOpts);
case Mod:store_messages(Host, US, Msgs, Len, MaxOfflineMsgs) of true ->
{atomic, discard} -> ok
discard_warn_sender(Msgs); 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 ok
end. end.
get_max_user_messages(AccessRule, {User, Server}, Host) -> -spec store_offline_msg(#offline_msg{}) -> ok | {error, full | any()}.
case acl:match_rule( store_offline_msg(#offline_msg{us = {User, Server}} = Msg) ->
Host, AccessRule, jid:make(User, Server)) of 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; Max when is_integer(Max) -> Max;
infinity -> infinity; infinity -> infinity;
_ -> ?MAX_USER_MESSAGES _ -> ?MAX_USER_MESSAGES
end. 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) -> get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
Feats = case Acc of Feats = case Acc of
{result, I} -> I; {result, I} -> I;
@ -484,14 +415,19 @@ store_packet({_Action, #message{from = From, to = To} = Packet} = Acc) ->
NewPacket -> NewPacket ->
TimeStamp = p1_time_compat:timestamp(), TimeStamp = p1_time_compat:timestamp(),
Expire = find_x_expire(TimeStamp, NewPacket), Expire = find_x_expire(TimeStamp, NewPacket),
gen_mod:get_module_proc(To#jid.lserver, ?MODULE) ! OffMsg = #offline_msg{us = {LUser, LServer},
#offline_msg{us = {LUser, LServer},
timestamp = TimeStamp, timestamp = TimeStamp,
expire = Expire, expire = Expire,
from = From, from = From,
to = To, to = To,
packet = NewPacket}, packet = NewPacket},
{offlined, NewPacket} case store_offline_msg(OffMsg) of
ok ->
{offlined, NewPacket};
{error, Reason} ->
discard_warn_sender(Packet, Reason),
stop
end
end; end;
_ -> Acc _ -> Acc
end; end;
@ -635,15 +571,18 @@ remove_user(User, Server) ->
%% Helper functions: %% Helper functions:
%% Warn senders that their messages have been discarded: %% Warn senders that their messages have been discarded:
discard_warn_sender(Msgs) -> -spec discard_warn_sender(message(), full | any()) -> ok.
lists:foreach( discard_warn_sender(Packet, full) ->
fun(#offline_msg{packet = Packet}) ->
ErrText = <<"Your contact offline message queue is " ErrText = <<"Your contact offline message queue is "
"full. The message has been discarded.">>, "full. The message has been discarded.">>,
Lang = xmpp:get_lang(Packet), Lang = xmpp:get_lang(Packet),
Err = xmpp:err_resource_constraint(ErrText, Lang), Err = xmpp:err_resource_constraint(ErrText, Lang),
ejabberd_router:route_error(Packet, Err) ejabberd_router:route_error(Packet, Err);
end, Msgs). 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, webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"queue">>], #request{us = _US, path = [<<"user">>, U, <<"queue">>],
@ -790,11 +729,7 @@ get_queue_length(LUser, LServer) ->
count_offline_messages(LUser, LServer). count_offline_messages(LUser, LServer).
get_messages_subset(User, Host, MsgsAll) -> get_messages_subset(User, Host, MsgsAll) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages, MaxOfflineMsgs = case get_max_user_messages(User, Host) of
max_user_offline_messages),
MaxOfflineMsgs = case get_max_user_messages(Access,
User, Host)
of
Number when is_integer(Number) -> Number; Number when is_integer(Number) -> Number;
_ -> 100 _ -> 100
end, end,

View File

@ -26,7 +26,7 @@
-behaviour(mod_offline). -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, remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2, read_message/3, remove_message/3, read_all_messages/2,
remove_all_messages/2, count_messages/2, import/1]). remove_all_messages/2, count_messages/2, import/1]).
@ -36,8 +36,6 @@
-include("mod_offline.hrl"). -include("mod_offline.hrl").
-include("logger.hrl"). -include("logger.hrl").
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
%%%=================================================================== %%%===================================================================
%%% API %%% API
%%%=================================================================== %%%===================================================================
@ -46,26 +44,9 @@ init(_Host, _Opts) ->
[{disc_only_copies, [node()]}, {type, bag}, [{disc_only_copies, [node()]}, {type, bag},
{attributes, record_info(fields, offline_msg)}]). {attributes, record_info(fields, offline_msg)}]).
store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) -> store_message(#offline_msg{packet = Pkt} = OffMsg) ->
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), El = xmpp:encode(Pkt),
mnesia:write(M#offline_msg{packet = El}) mnesia:dirty_write(OffMsg#offline_msg{packet = El}).
end, Msgs)
end
end,
mnesia:transaction(F).
pop_messages(LUser, LServer) -> pop_messages(LUser, LServer) ->
US = {LUser, LServer}, US = {LUser, LServer},

View File

@ -26,7 +26,7 @@
-behaviour(mod_offline). -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, remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2, read_message/3, remove_message/3, read_all_messages/2,
remove_all_messages/2, count_messages/2, import/1]). remove_all_messages/2, count_messages/2, import/1]).
@ -40,31 +40,11 @@
init(_Host, _Opts) -> init(_Host, _Opts) ->
ok. ok.
store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) -> store_message(#offline_msg{us = US, packet = Pkt, timestamp = TS} = M) ->
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), El = xmpp:encode(Pkt),
ok = ejabberd_riak:put( ejabberd_riak:put(M#offline_msg{packet = El},
M#offline_msg{packet = El},
offline_msg_schema(), offline_msg_schema(),
[{i, TS}, {'2i', [{<<"us">>, US}]}]) [{i, TS}, {'2i', [{<<"us">>, US}]}]).
end, Msgs),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end
end.
pop_messages(LUser, LServer) -> pop_messages(LUser, LServer) ->
case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(), case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),

View File

@ -28,7 +28,7 @@
-behaviour(mod_offline). -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, remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2, read_message/3, remove_message/3, read_all_messages/2,
remove_all_messages/2, count_messages/2, import/1, export/1]). remove_all_messages/2, count_messages/2, import/1, export/1]).
@ -44,34 +44,28 @@
init(_Host, _Opts) -> init(_Host, _Opts) ->
ok. ok.
store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> store_message(#offline_msg{us = {LUser, LServer}} = M) ->
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, From = M#offline_msg.from,
To = M#offline_msg.to, To = M#offline_msg.to,
Packet = xmpp:set_from_to( Packet = xmpp:set_from_to(M#offline_msg.packet, From, To),
M#offline_msg.packet, From, To),
NewPacket = xmpp_util:add_delay_info( NewPacket = xmpp_util:add_delay_info(
Packet, jid:make(Host), Packet, jid:make(LServer),
M#offline_msg.timestamp, M#offline_msg.timestamp,
<<"Offline Storage">>), <<"Offline Storage">>),
XML = fxml:element_to_binary( XML = fxml:element_to_binary(
xmpp:encode(NewPacket)), xmpp:encode(NewPacket)),
sql_queries:add_spool_sql(LUser, XML) case ejabberd_sql:sql_query(
end, LServer,
Msgs), ?SQL("insert into spool(username, xml) values "
sql_queries:add_spool(Host, Query) "(%(LUser)s, %(XML)s)")) of
{updated, _} ->
ok;
_ ->
{error, db_failure}
end. end.
pop_messages(LUser, LServer) -> 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}} -> {atomic, {selected, Rs}} ->
{ok, lists:flatmap( {ok, lists:flatmap(
fun({_, XML}) -> fun({_, XML}) ->
@ -91,7 +85,7 @@ remove_expired_messages(_LServer) ->
{atomic, ok}. {atomic, ok}.
remove_old_messages(Days, LServer) -> remove_old_messages(Days, LServer) ->
case catch ejabberd_sql:sql_query( case ejabberd_sql:sql_query(
LServer, LServer,
[<<"DELETE FROM spool" [<<"DELETE FROM spool"
" WHERE created_at < " " WHERE created_at < "
@ -105,10 +99,12 @@ remove_old_messages(Days, LServer) ->
{atomic, ok}. {atomic, ok}.
remove_user(LUser, LServer) -> 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) -> read_message_headers(LUser, LServer) ->
case catch ejabberd_sql:sql_query( case ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(xml)s, @(seq)d from spool" ?SQL("select @(xml)s, @(seq)d from spool"
" where username=%(LUser)s order by seq")) of " where username=%(LUser)s order by seq")) of
@ -153,7 +149,7 @@ remove_message(LUser, LServer, Seq) ->
ok. ok.
read_all_messages(LUser, LServer) -> read_all_messages(LUser, LServer) ->
case catch ejabberd_sql:sql_query( case ejabberd_sql:sql_query(
LServer, LServer,
?SQL("select @(xml)s from spool where " ?SQL("select @(xml)s from spool where "
"username=%(LUser)s order by seq")) of "username=%(LUser)s order by seq")) of
@ -170,7 +166,7 @@ read_all_messages(LUser, LServer) ->
end. end.
remove_all_messages(LUser, LServer) -> remove_all_messages(LUser, LServer) ->
sql_queries:del_spool_msg(LServer, LUser), remove_user(LUser, LServer),
{atomic, ok}. {atomic, ok}.
count_messages(LUser, LServer) -> 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_MSG("failed to get 'from' JID from offline XML ~p", [El]),
{error, bad_jid_from} {error, bad_jid_from}
end. 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).

View File

@ -31,42 +31,51 @@
-behaviour(gen_mod). -behaviour(gen_mod).
-export([start/2, stop/1, reload/3, process_iq/1, export/1, import_info/0, -export([start/2, stop/1, reload/3, process_iq/1, export/1,
c2s_session_opened/1, c2s_copy_session/2, push_list_update/3, c2s_copy_session/2, push_list_update/2, disco_features/5,
user_send_packet/1, user_receive_packet/1, disco_features/5,
check_packet/4, remove_user/2, encode_list_item/1, check_packet/4, remove_user/2, encode_list_item/1,
is_list_needdb/1, import_start/2, import_stop/2, get_user_lists/2, get_user_list/3,
item_to_xml/1, get_user_lists/2, import/5, set_list/1, set_list/4, set_default_list/3,
set_privacy_list/1, mod_opt_type/1, depends/2]). 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("ejabberd.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("mod_privacy.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 init(binary(), gen_mod:opts()) -> any().
-callback import(#privacy{}) -> ok. -callback import(#privacy{}) -> ok.
-callback process_lists_get(binary(), binary()) -> {none | binary(), [binary()]} | error. -callback set_default(binary(), binary(), binary()) ->
-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found. ok | {error, notfound | any()}.
-callback process_default_set(binary(), binary(), binary() | none) -> {atomic, any()}. -callback unset_default(binary(), binary()) -> ok | {error, any()}.
-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error. -callback remove_list(binary(), binary(), binary()) ->
-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}. ok | {error, notfound | conflict | any()}.
-callback set_privacy_list(#privacy{}) -> any(). -callback remove_lists(binary(), binary()) -> ok | {error, any()}.
-callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}. -callback set_lists(#privacy{}) -> ok | {error, any()}.
-callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}. -callback set_list(binary(), binary(), binary(), listitem()) ->
-callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error. ok | {error, any()}.
-callback remove_user(binary(), binary()) -> 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) -> start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE), Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts), Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
disco_features, 50), disco_features, 50),
ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE,
c2s_session_opened, 50),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE, ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50), c2s_copy_session, 50),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
@ -83,8 +92,6 @@ start(Host, Opts) ->
stop(Host) -> stop(Host) ->
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, ejabberd_hooks:delete(disco_local_features, Host, ?MODULE,
disco_features, 50), disco_features, 50),
ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE,
c2s_session_opened, 50),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE, ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50), c2s_copy_session, 50),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
@ -106,6 +113,7 @@ reload(Host, NewOpts, OldOpts) ->
true -> true ->
ok ok
end, end,
init_cache(NewMod, Host, NewOpts),
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
{false, IQDisc, _} -> {false, IQDisc, _} ->
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PRIVACY, 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(). -spec process_lists_get(iq()) -> iq().
process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer}, process_lists_get(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang, lang = Lang} = IQ) ->
meta = #{privacy_active_list := Active}} = IQ) -> case get_user_lists(LUser, LServer) of
Mod = gen_mod:db_mod(LServer, ?MODULE), {ok, #privacy{default = Default, lists = Lists}} ->
case Mod:process_lists_get(LUser, LServer) of Active = xmpp:get_meta(IQ, privacy_active_list, none),
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} ->
xmpp:make_iq_result( xmpp:make_iq_result(
IQ, IQ, #privacy_query{active = Active,
#privacy_query{active = Active,
default = Default, default = Default,
lists = [#privacy_list{name = ListName} lists = [#privacy_list{name = Name}
|| ListName <- ListNames]}) || {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. end.
-spec process_list_get(iq(), binary()) -> iq(). -spec process_list_get(iq(), binary()) -> iq().
process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer}, process_list_get(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Name) -> lang = Lang} = IQ, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case get_user_list(LUser, LServer, Name) of
case Mod:process_list_get(LUser, LServer, Name) of {ok, {_, List}} ->
error -> Items = lists:map(fun encode_list_item/1, List),
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),
xmpp:make_iq_result( xmpp:make_iq_result(
IQ, IQ,
#privacy_query{ #privacy_query{
lists = [#privacy_list{name = Name, items = LItems}]}) 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. end.
-spec item_to_xml(listitem()) -> xmlel().
item_to_xml(ListItem) ->
xmpp:encode(encode_list_item(ListItem)).
-spec encode_list_item(listitem()) -> privacy_item(). -spec encode_list_item(listitem()) -> privacy_item().
encode_list_item(#listitem{action = Action, encode_list_item(#listitem{action = Action,
order = Order, order = Order,
@ -283,69 +285,69 @@ process_iq_set(#iq{lang = Lang} = IQ) ->
Txt = <<"No module is handling this query">>, Txt = <<"No module is handling this query">>,
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)). 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}, process_default_set(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Value) -> lang = Lang} = IQ, Value) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case set_default_list(LUser, LServer, Value) of
case Mod:process_default_set(LUser, LServer, Value) of ok ->
{atomic, error} -> xmpp:make_iq_result(IQ);
Txt = <<"Database failure">>, {error, notfound} ->
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang));
{atomic, not_found} ->
Txt = <<"No privacy list with this name found">>, Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
{atomic, ok} -> {error, _} ->
xmpp:make_iq_result(IQ); Txt = <<"Database failure">>,
Err -> xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
?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())
end. end.
-spec process_active_set(IQ, none | binary()) -> IQ. -spec process_active_set(IQ, none | binary()) -> IQ.
process_active_set(IQ, none) -> 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}, process_active_set(#iq{from = #jid{luser = LUser, lserver = LServer},
lang = Lang} = IQ, Name) -> lang = Lang} = IQ, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case get_user_list(LUser, LServer, Name) of
case Mod:process_active_set(LUser, LServer, Name) of {ok, _} ->
xmpp:make_iq_result(xmpp:put_meta(IQ, privacy_active_list, Name));
error -> error ->
Txt = <<"No privacy list with this name found">>, Txt = <<"No privacy list with this name found">>,
xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang)); xmpp:make_error(IQ, xmpp:err_item_not_found(Txt, Lang));
Items -> {error, _} ->
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]),
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) 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; end;
process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From, process_lists_set(#iq{from = #jid{luser = LUser, lserver = LServer} = From,
lang = Lang} = IQ, Name, Items) -> 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), Txt = xmpp:format_error(Why),
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
List -> List ->
Mod = gen_mod:db_mod(LServer, ?MODULE), case set_list(LUser, LServer, Name, List) of
case Mod:set_privacy_list(LUser, LServer, Name, List) of ok ->
{atomic, ok} -> push_list_update(From, Name),
UserList = #userlist{name = Name, list = List,
needdb = is_list_needdb(List)},
push_list_update(From, UserList, Name),
xmpp:make_iq_result(IQ); xmpp:make_iq_result(IQ);
Err -> {error, _} ->
?ERROR_MSG("failed to set privacy list '~s' "
"for user ~s@~s: ~p",
[Name, LUser, LServer, Err]),
Txt = <<"Database failure">>, Txt = <<"Database failure">>,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end end
end. end.
-spec push_list_update(jid(), #userlist{}, binary() | none) -> ok. -spec push_list_update(jid(), binary()) -> ok.
push_list_update(From, List, Name) -> push_list_update(From, Name) ->
BareFrom = jid:remove_resource(From), BareFrom = jid:remove_resource(From),
lists:foreach( lists:foreach(
fun(R) -> fun(R) ->
@ -379,44 +375,10 @@ push_list_update(From, List, Name) ->
IQ = #iq{type = set, from = BareFrom, to = To, IQ = #iq{type = set, from = BareFrom, to = To,
id = <<"push", (randoms:get_string())/binary>>, id = <<"push", (randoms:get_string())/binary>>,
sub_els = [#privacy_query{ sub_els = [#privacy_query{
lists = [#privacy_list{name = Name}]}], lists = [#privacy_list{name = Name}]}]},
meta = #{privacy_updated_list => List}},
ejabberd_router:route(IQ) ejabberd_router:route(IQ)
end, ejabberd_sm:get_user_resources(From#jid.luser, From#jid.lserver)). 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(). -spec decode_item(privacy_item()) -> listitem().
decode_item(#privacy_item{order = Order, decode_item(#privacy_item{order = Order,
action = Action, action = Action,
@ -448,47 +410,145 @@ decode_item(#privacy_item{order = Order,
match_presence_out = MatchPresenceOut} match_presence_out = MatchPresenceOut}
end. end.
-spec is_list_needdb([listitem()]) -> boolean(). -spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
is_list_needdb(Items) -> c2s_copy_session(State, #{privacy_active_list := List}) ->
lists:any(fun (X) -> State#{privacy_active_list => List}.
case X#listitem.type of
subscription -> true; -spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
group -> true; user_send_packet({#iq{type = Type,
_ -> false to = #jid{luser = U, lserver = S, lresource = <<"">>},
end 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, end,
Items). {NewIQ, State};
user_send_packet(Acc) ->
Acc.
-spec get_user_list(binary(), binary()) -> #userlist{}. -spec user_receive_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
get_user_list(LUser, LServer) -> 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), Mod = gen_mod:db_mod(LServer, ?MODULE),
{Default, Items} = Mod:get_user_list(LUser, LServer), case Mod:set_list(LUser, LServer, Name, List) of
NeedDb = is_list_needdb(Items), ok ->
#userlist{name = Default, list = Items, needdb = NeedDb}. delete_cache(Mod, LUser, LServer, [Name]);
{error, _} = Err ->
Err
end.
-spec c2s_session_opened(ejabberd_c2s:state()) -> ejabberd_c2s:state(). -spec remove_list(binary(), binary(), binary()) ->
c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer}} = State) -> ok | {error, conflict | notfound | any()}.
State#{privacy_list => get_user_list(LUser, LServer)}. 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(). -spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error | {error, any()}.
c2s_copy_session(State, #{privacy_list := List}) ->
State#{privacy_list => List}.
-spec get_user_lists(binary(), binary()) -> {ok, privacy()} | error.
get_user_lists(User, Server) -> get_user_lists(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE), 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. %% From is the sender, To is the destination.
%% If Dir = out, User@Server is the sender account (From). %% If Dir = out, User@Server is the sender account (From).
%% If Dir = in, User@Server is the destination account (To). %% If Dir = in, User@Server is the destination account (To).
-spec check_packet(allow | deny, ejabberd_c2s:state() | jid(), -spec do_check_packet(jid(), [listitem()], stanza(), in | out) -> allow | deny.
stanza(), in | out) -> allow | deny. do_check_packet(_, [], _, _) ->
check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer}, allow;
privacy_list := #userlist{list = List, needdb = NeedDb}}, do_check_packet(#jid{luser = LUser, lserver = LServer}, List, Packet, Dir) ->
Packet, Dir) ->
From = xmpp:get_from(Packet), From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet), To = xmpp:get_to(Packet),
case {From, To} of 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 -> #jid{luser = LUser, lserver = LServer, lresource = <<"">>}} when Dir == out ->
%% Allow outgoing packets from user's full jid to his bare JID %% Allow outgoing packets from user's full jid to his bare JID
allow; allow;
_ when List == [] ->
allow;
_ -> _ ->
PType = case Packet of PType = case Packet of
#message{} -> message; #message{} -> message;
@ -529,21 +587,11 @@ check_packet(_, #{jid := #jid{luser = LUser, lserver = LServer},
in -> jid:tolower(From); in -> jid:tolower(From);
out -> jid:tolower(To) out -> jid:tolower(To)
end, end,
{Subscription, Groups} = {Subscription, Groups} = ejabberd_hooks:run_fold(
case NeedDb of roster_get_jid_info, LServer,
true -> {none, []}, [LUser, LServer, LJID]),
ejabberd_hooks:run_fold(roster_get_jid_info,
LServer,
{none, []},
[LUser, LServer, LJID]);
false ->
{[], []}
end,
check_packet_aux(List, PType2, LJID, Subscription, Groups) check_packet_aux(List, PType2, LJID, Subscription, Groups)
end; 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).
-spec check_packet_aux([listitem()], -spec check_packet_aux([listitem()],
message | iq | presence_in | presence_out | other, message | iq | presence_in | presence_out | other,
@ -608,12 +656,82 @@ is_type_match(Type, Value, JID, Subscription, Groups) ->
group -> lists:member(Value, Groups) group -> lists:member(Value, Groups)
end. end.
-spec remove_user(binary(), binary()) -> any(). -spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) -> remove_user(User, Server) ->
LUser = jid:nodeprep(User), LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server), LServer = jid:nameprep(Server),
Privacy = get_user_lists(LUser, LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE), 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>>) -> numeric_to_binary(<<0, 0, _/binary>>) ->
<<"0">>; <<"0">>;

View File

@ -27,11 +27,9 @@
-behaviour(mod_privacy). -behaviour(mod_privacy).
%% API %% API
-export([init/2, process_lists_get/2, process_list_get/3, -export([init/2, set_default/3, unset_default/2, set_lists/1,
process_default_set/3, process_active_set/3, set_list/4, get_lists/2, get_list/3, remove_lists/2,
remove_privacy_list/3, set_privacy_list/1, remove_list/3, use_cache/1, import/1]).
set_privacy_list/4, get_user_list/2, get_user_lists/2,
remove_user/2, import/1]).
-export([need_transform/1, transform/1]). -export([need_transform/1, transform/1]).
-include("xmpp.hrl"). -include("xmpp.hrl").
@ -43,81 +41,65 @@
%%%=================================================================== %%%===================================================================
init(_Host, _Opts) -> init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, privacy, ejabberd_mnesia:create(?MODULE, privacy,
[{disc_copies, [node()]}, [{disc_only_copies, [node()]},
{attributes, record_info(fields, privacy)}]). {attributes, record_info(fields, privacy)}]).
process_lists_get(LUser, LServer) -> use_cache(Host) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of case mnesia:table_info(privacy, storage_type) of
{'EXIT', _Reason} -> error; disc_only_copies ->
[] -> {none, []}; gen_mod:get_module_opt(
[#privacy{default = Default, lists = Lists}] -> Host, mod_privacy, use_cache,
LItems = lists:map(fun ({N, _}) -> N end, Lists), ejabberd_config:use_cache(Host));
{Default, LItems} _ ->
false
end. end.
process_list_get(LUser, LServer, Name) -> unset_default(LUser, LServer) ->
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) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> ok; [] -> ok;
[R] -> mnesia:write(R#privacy{default = none}) [R] -> mnesia:write(R#privacy{default = none})
end end
end, end,
mnesia:transaction(F); transaction(F).
process_default_set(LUser, LServer, Name) ->
set_default(LUser, LServer, Name) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> not_found; [] ->
{error, notfound};
[#privacy{lists = Lists} = P] -> [#privacy{lists = Lists} = P] ->
case lists:keymember(Name, 1, Lists) of case lists:keymember(Name, 1, Lists) of
true -> true ->
mnesia:write(P#privacy{default = Name, mnesia:write(P#privacy{default = Name,
lists = Lists}), lists = Lists});
ok; false ->
false -> not_found {error, notfound}
end end
end end
end, end,
mnesia:transaction(F). transaction(F).
process_active_set(LUser, LServer, Name) -> remove_list(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) ->
F = fun () -> F = fun () ->
case mnesia:read({privacy, {LUser, LServer}}) of case mnesia:read({privacy, {LUser, LServer}}) of
[] -> ok; [] ->
{error, notfound};
[#privacy{default = Default, lists = Lists} = P] -> [#privacy{default = Default, lists = Lists} = P] ->
if Name == Default -> conflict; if Name == Default ->
{error, conflict};
true -> true ->
NewLists = lists:keydelete(Name, 1, Lists), NewLists = lists:keydelete(Name, 1, Lists),
mnesia:write(P#privacy{lists = NewLists}) mnesia:write(P#privacy{lists = NewLists})
end end
end end
end, end,
mnesia:transaction(F). transaction(F).
set_privacy_list(Privacy) -> set_lists(Privacy) ->
mnesia:dirty_write(Privacy). mnesia:dirty_write(Privacy).
set_privacy_list(LUser, LServer, Name, List) -> set_list(LUser, LServer, Name, List) ->
F = fun () -> F = fun () ->
case mnesia:wread({privacy, {LUser, LServer}}) of case mnesia:wread({privacy, {LUser, LServer}}) of
[] -> [] ->
@ -130,35 +112,35 @@ set_privacy_list(LUser, LServer, Name, List) ->
mnesia:write(P#privacy{lists = NewLists}) mnesia:write(P#privacy{lists = NewLists})
end end
end, end,
mnesia:transaction(F). transaction(F).
get_user_list(LUser, LServer) -> get_list(LUser, LServer, Name) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) case mnesia:dirty_read(privacy, {LUser, LServer}) of
of [#privacy{default = Default, lists = Lists}] when Name == default ->
[] -> {none, []}; case lists:keyfind(Default, 1, Lists) of
[#privacy{default = Default, lists = Lists}] -> {_, List} -> {ok, {Default, List}};
case Default of false -> error
none -> {none, []};
_ ->
case lists:keysearch(Default, 1, Lists) of
{value, {_, List}} -> {Default, List};
_ -> {none, []}
end
end; end;
_ -> {none, []} [#privacy{lists = Lists}] ->
case lists:keyfind(Name, 1, Lists) of
{_, List} -> {ok, {Name, List}};
false -> error
end;
[] ->
error
end. end.
get_user_lists(LUser, LServer) -> get_lists(LUser, LServer) ->
case catch mnesia:dirty_read(privacy, {LUser, LServer}) of case mnesia:dirty_read(privacy, {LUser, LServer}) of
[#privacy{} = P] -> [#privacy{} = P] ->
{ok, P}; {ok, P};
_ -> _ ->
error error
end. end.
remove_user(LUser, LServer) -> remove_lists(LUser, LServer) ->
F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end, F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end,
mnesia:transaction(F). transaction(F).
import(#privacy{} = P) -> import(#privacy{} = P) ->
mnesia:dirty_write(P). mnesia:dirty_write(P).
@ -199,3 +181,11 @@ transform(#privacy{us = {U, S}, default = Def, lists = Lists} = R) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% 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.

View File

@ -27,11 +27,9 @@
-behaviour(mod_privacy). -behaviour(mod_privacy).
%% API %% API
-export([init/2, process_lists_get/2, process_list_get/3, -export([init/2, set_default/3, unset_default/2, set_lists/1,
process_default_set/3, process_active_set/3, set_list/4, get_lists/2, get_list/3, remove_lists/2,
remove_privacy_list/3, set_privacy_list/1, remove_list/3, import/1]).
set_privacy_list/4, get_user_list/2, get_user_lists/2,
remove_user/2, import/1]).
-export([privacy_schema/0]). -export([privacy_schema/0]).
@ -44,40 +42,17 @@
init(_Host, _Opts) -> init(_Host, _Opts) ->
ok. 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
end.
process_list_get(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
end.
process_default_set(LUser, LServer, none) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, R} -> {ok, R} ->
ejabberd_riak:put(R#privacy{default = none}, privacy_schema()); ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
{error, _} -> {error, notfound} ->
ok ok;
end}; Err ->
process_default_set(LUser, LServer, Name) -> Err
{atomic, end.
set_default(LUser, LServer, Name) ->
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} -> {ok, #privacy{lists = Lists} = P} ->
case lists:keymember(Name, 1, Lists) of case lists:keymember(Name, 1, Lists) of
@ -86,80 +61,74 @@ process_default_set(LUser, LServer, Name) ->
lists = Lists}, lists = Lists},
privacy_schema()); privacy_schema());
false -> false ->
not_found {error, notfound}
end; end;
{error, _} -> Err ->
not_found Err
end}.
process_active_set(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
end. end.
remove_privacy_list(LUser, LServer, Name) -> remove_list(LUser, LServer, Name) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{default = Default, lists = Lists} = P} -> {ok, #privacy{default = Default, lists = Lists} = P} ->
if Name == Default -> if Name == Default ->
conflict; {error, conflict};
true -> true ->
NewLists = lists:keydelete(Name, 1, Lists), NewLists = lists:keydelete(Name, 1, Lists),
ejabberd_riak:put(P#privacy{lists = NewLists}, ejabberd_riak:put(P#privacy{lists = NewLists},
privacy_schema()) privacy_schema())
end; end;
{error, _} -> Err ->
ok Err
end}. end.
set_privacy_list(Privacy) -> set_lists(Privacy) ->
ejabberd_riak:put(Privacy, privacy_schema()). ejabberd_riak:put(Privacy, privacy_schema()).
set_privacy_list(LUser, LServer, Name, List) -> set_list(LUser, LServer, Name, List) ->
{atomic,
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{lists = Lists} = P} -> {ok, #privacy{lists = Lists} = P} ->
NewLists1 = lists:keydelete(Name, 1, Lists), NewLists1 = lists:keydelete(Name, 1, Lists),
NewLists = [{Name, List} | NewLists1], NewLists = [{Name, List} | NewLists1],
ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema()); ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
{error, _} -> {error, notfound} ->
NewLists = [{Name, List}], NewLists = [{Name, List}],
ejabberd_riak:put(#privacy{us = {LUser, LServer}, ejabberd_riak:put(#privacy{us = {LUser, LServer},
lists = NewLists}, lists = NewLists},
privacy_schema()) privacy_schema());
end}. Err ->
Err
get_user_list(LUser, LServer) ->
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, []}
end. 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 case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
{ok, #privacy{} = P} -> {ok, #privacy{} = P} ->
{ok, P}; {ok, P};
{error, _} -> {error, notfound} ->
error error;
Err ->
Err
end. end.
remove_user(LUser, LServer) -> remove_lists(LUser, LServer) ->
{atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}. ejabberd_riak:delete(privacy, {LUser, LServer}).
import(#privacy{} = P) -> import(#privacy{} = P) ->
ejabberd_riak:put(P, privacy_schema()). ejabberd_riak:put(P, privacy_schema()).

View File

@ -29,20 +29,11 @@
-behaviour(mod_privacy). -behaviour(mod_privacy).
%% API %% API
-export([init/2, process_lists_get/2, process_list_get/3, -export([init/2, set_default/3, unset_default/2, set_lists/1,
process_default_set/3, process_active_set/3, set_list/4, get_lists/2, get_list/3, remove_lists/2,
remove_privacy_list/3, set_privacy_list/1, remove_list/3, import/1, export/1]).
set_privacy_list/4, get_user_list/2, get_user_lists/2,
remove_user/2, import/1, export/1]).
-export([item_to_raw/1, raw_to_item/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]).
-include("xmpp.hrl"). -include("xmpp.hrl").
-include("mod_privacy.hrl"). -include("mod_privacy.hrl").
@ -55,159 +46,143 @@
init(_Host, _Opts) -> init(_Host, _Opts) ->
ok. ok.
process_lists_get(LUser, LServer) -> unset_default(LUser, LServer) ->
Default = case catch sql_get_default_privacy_list(LUser, LServer) of case unset_default_privacy_list(LUser, LServer) of
{selected, []} -> none; ok ->
{selected, [{DefName}]} -> DefName; ok;
_ -> none _Err ->
end, {error, db_failure}
case catch sql_get_privacy_list_names(LUser, LServer) of
{selected, Names} ->
LItems = lists:map(fun ({N}) -> N end, Names),
{Default, LItems};
_ -> error
end. end.
process_list_get(LUser, LServer, Name) -> set_default(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) ->
F = fun () -> F = fun () ->
case sql_get_privacy_list_names_t(LUser) of case get_privacy_list_names_t(LUser) of
{selected, []} -> not_found; {selected, []} ->
{error, notfound};
{selected, Names} -> {selected, Names} ->
case lists:member({Name}, Names) of case lists:member({Name}, Names) of
true -> sql_set_default_privacy_list(LUser, Name), ok; true ->
false -> not_found set_default_privacy_list(LUser, Name);
false ->
{error, notfound}
end end
end end
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
process_active_set(LUser, LServer, Name) -> remove_list(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) ->
F = fun () -> F = fun () ->
case sql_get_default_privacy_list_t(LUser) of case get_default_privacy_list_t(LUser) of
{selected, []} -> {selected, []} ->
sql_remove_privacy_list(LUser, Name), ok; remove_privacy_list_t(LUser, Name);
{selected, [{Default}]} -> {selected, [{Default}]} ->
if Name == Default -> conflict; if Name == Default ->
true -> sql_remove_privacy_list(LUser, Name), ok {error, conflict};
true ->
remove_privacy_list_t(LUser, Name)
end end
end end
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
set_privacy_list(#privacy{us = {LUser, LServer}, set_lists(#privacy{us = {LUser, LServer},
default = Default, default = Default,
lists = Lists}) -> lists = Lists}) ->
F = fun() -> F = fun() ->
lists:foreach( lists:foreach(
fun({Name, List}) -> fun({Name, List}) ->
sql_add_privacy_list(LUser, Name), add_privacy_list(LUser, Name),
{selected, [<<"id">>], [[I]]} = {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), RItems = lists:map(fun item_to_raw/1, List),
sql_set_privacy_list(I, RItems), set_privacy_list(I, RItems),
if is_binary(Default) -> if is_binary(Default) ->
sql_set_default_privacy_list(LUser, Default), set_default_privacy_list(LUser, Default);
ok;
true -> true ->
ok ok
end end
end, Lists) end, Lists)
end, 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), RItems = lists:map(fun item_to_raw/1, List),
F = fun () -> F = fun () ->
ID = case sql_get_privacy_list_id_t(LUser, Name) of ID = case get_privacy_list_id_t(LUser, Name) of
{selected, []} -> {selected, []} ->
sql_add_privacy_list(LUser, Name), add_privacy_list(LUser, Name),
{selected, [{I}]} = {selected, [{I}]} =
sql_get_privacy_list_id_t(LUser, Name), get_privacy_list_id_t(LUser, Name),
I; I;
{selected, [{I}]} -> I {selected, [{I}]} -> I
end, end,
sql_set_privacy_list(ID, RItems), set_privacy_list(ID, RItems)
ok
end, end,
sql_queries:sql_transaction(LServer, F). transaction(LServer, F).
get_user_list(LUser, LServer) -> get_list(LUser, LServer, default) ->
case catch sql_get_default_privacy_list(LUser, LServer) case get_default_privacy_list(LUser, LServer) of
of {selected, []} ->
{selected, []} -> {none, []}; error;
{selected, [{Default}]} -> {selected, [{Default}]} ->
case catch sql_get_privacy_list_data(LUser, LServer, get_list(LUser, LServer, Default);
Default) of _Err ->
{selected, RItems} -> {error, db_failure}
{Default, lists:flatmap(fun raw_to_item/1, RItems)};
_ -> {none, []}
end; end;
_ -> {none, []} 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. end.
get_user_lists(LUser, LServer) -> get_lists(LUser, LServer) ->
Default = case catch sql_get_default_privacy_list(LUser, LServer) of case get_default_privacy_list(LUser, LServer) of
{selected, []} -> {selected, Selected} ->
none; Default = case Selected of
{selected, [{DefName}]} -> [] -> none;
DefName; [{DefName}] -> DefName
_ ->
none
end, end,
case catch sql_get_privacy_list_names(LUser, LServer) of case get_privacy_list_names(LUser, LServer) of
{selected, Names} -> {selected, Names} ->
Lists = case lists:foldl(
lists:flatmap( fun(_, {error, _} = Err) ->
fun({Name}) -> Err;
case catch sql_get_privacy_list_data( ({Name}, Acc) ->
LUser, LServer, Name) of case get_privacy_list_data(LUser, LServer, Name) of
{selected, RItems} -> {selected, RItems} ->
[{Name, lists:flatmap(fun raw_to_item/1, RItems)}]; Items = lists:flatmap(
_ -> fun raw_to_item/1,
[] RItems),
[{Name, Items}|Acc];
_Err ->
{error, db_failure}
end end
end, Names), end, [], Names) of
{error, Reason} ->
{error, Reason};
Lists ->
{ok, #privacy{default = Default, {ok, #privacy{default = Default,
us = {LUser, LServer}, us = {LUser, LServer},
lists = Lists}}; lists = Lists}}
_ -> end;
error _Err ->
{error, db_failure}
end;
_Err ->
{error, db_failure}
end. end.
remove_user(LUser, LServer) -> remove_lists(LUser, LServer) ->
sql_del_privacy_lists(LUser, LServer). case del_privacy_lists(LUser, LServer) of
ok ->
ok;
_Err ->
{error, db_failure}
end.
export(Server) -> export(Server) ->
case catch ejabberd_sql:sql_query(jid:nameprep(Server), case catch ejabberd_sql:sql_query(jid:nameprep(Server),
@ -271,6 +246,12 @@ import(_) ->
%%%=================================================================== %%%===================================================================
%%% Internal functions %%% 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, raw_to_item({SType, SValue, SAction, Order, MatchAll,
MatchIQ, MatchMessage, MatchPresenceIn, MatchIQ, MatchMessage, MatchPresenceIn,
MatchPresenceOut} = Row) -> MatchPresenceOut} = Row) ->
@ -327,47 +308,102 @@ item_to_raw(#listitem{type = Type, value = Value,
{SType, SValue, SAction, Order, MatchAll, MatchIQ, {SType, SValue, SAction, Order, MatchAll, MatchIQ,
MatchMessage, MatchPresenceIn, MatchPresenceOut}. MatchMessage, MatchPresenceIn, MatchPresenceOut}.
sql_get_default_privacy_list(LUser, LServer) -> get_default_privacy_list(LUser, LServer) ->
sql_queries:get_default_privacy_list(LServer, LUser). ejabberd_sql:sql_query(
LServer,
?SQL("select @(name)s from privacy_default_list "
"where username=%(LUser)s")).
sql_get_default_privacy_list_t(LUser) -> get_default_privacy_list_t(LUser) ->
sql_queries: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) -> get_privacy_list_names(LUser, LServer) ->
sql_queries:get_privacy_list_names(LServer, LUser). ejabberd_sql:sql_query(
LServer,
?SQL("select @(name)s from privacy_list"
" where username=%(LUser)s")).
sql_get_privacy_list_names_t(LUser) -> get_privacy_list_names_t(LUser) ->
sql_queries: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) -> get_privacy_list_id_t(LUser, Name) ->
sql_queries:get_privacy_list_id(LServer, 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) -> get_privacy_list_data(LUser, LServer, Name) ->
sql_queries:get_privacy_list_id_t(LUser, 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) -> set_default_privacy_list(LUser, Name) ->
sql_queries:get_privacy_list_data(LServer, LUser, Name). ?SQL_UPSERT_T(
"privacy_default_list",
["!username=%(LUser)s",
"name=%(Name)s"]).
sql_get_privacy_list_data_by_id(ID, LServer) -> unset_default_privacy_list(LUser, LServer) ->
sql_queries:get_privacy_list_data_by_id(LServer, ID). 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) -> remove_privacy_list_t(LUser, Name) ->
sql_queries:get_privacy_list_data_by_id_t(ID). 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) -> add_privacy_list(LUser, Name) ->
sql_queries:set_default_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) -> set_privacy_list(ID, RItems) ->
sql_queries:unset_default_privacy_list(LServer, LUser). 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) -> del_privacy_lists(LUser, LServer) ->
sql_queries:remove_privacy_list(LUser, Name). case ejabberd_sql:sql_query(
LServer,
sql_add_privacy_list(LUser, Name) -> ?SQL("delete from privacy_list where username=%(LUser)s")) of
sql_queries:add_privacy_list(LUser, Name). {updated, _} ->
case ejabberd_sql:sql_query(
sql_set_privacy_list(ID, RItems) -> LServer,
sql_queries:set_privacy_list(ID, RItems). ?SQL("delete from privacy_default_list "
"where username=%(LUser)s")) of
sql_del_privacy_lists(LUser, LServer) -> {updated, _} -> ok;
sql_queries:del_privacy_lists(LServer, LUser). Err -> Err
end;
Err ->
Err
end.

Some files were not shown because too many files have changed in this diff Show More