25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

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_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/COPY* deps/$(DEP)/LICENSE* deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
DEPS_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
DEPS_DIRS=$(sort deps/ $(foreach DEP,$(DEPS),deps/$(DEP)/) $(dir $(DEPS_FILES)))
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl))
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl COPYING))
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
define DEP_VERSION_template

2
README
View File

@ -108,7 +108,7 @@ To compile ejabberd you need:
- GCC.
- Libexpat 1.95 or higher.
- Libyaml 0.1.4 or higher.
- Erlang/OTP 17.1 or higher.
- Erlang/OTP 17.5 or higher.
- OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption.
- Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional.
- PAM library. Optional. For Pluggable Authentication Modules (PAM).

View File

@ -101,10 +101,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic])
AC_ARG_ENABLE(all,
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
[case "${enableval}" in
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true debug=true tools=true ;;
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false debug=false tools=false ;;
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true stun=true sip=true debug=true tools=true ;;
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false stun=false sip=false debug=false tools=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[])
@ -212,6 +212,30 @@ AC_ARG_ENABLE(latest_deps,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-latest-deps) ;;
esac],[if test "x$latest_deps" = "x"; then latest_deps=false; fi])
AC_ARG_ENABLE(system_deps,
[AC_HELP_STRING([--enable-system-deps], [makes rebar use localy installed dependences instead of downloading them (default: no)])],
[case "${enableval}" in
yes) system_deps=true ;;
no) system_deps=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-system-deps) ;;
esac],[if test "x$system_deps" = "x"; then system_deps=false; fi])
AC_ARG_ENABLE(stun,
[AC_HELP_STRING([--enable-stun], [enable STUN/TURN support (default: no)])],
[case "${enableval}" in
yes) stun=true ;;
no) stun=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-stun) ;;
esac],[if test "x$stun" = "x"; then stun=false; fi])
AC_ARG_ENABLE(sip,
[AC_HELP_STRING([--enable-sip], [enable SIP support (default: no)])],
[case "${enableval}" in
yes) sip=true ;;
no) sip=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
esac],[if test "x$sip" = "x"; then sip=false; fi])
AC_CONFIG_FILES([Makefile
vars.config
src/ejabberd.app.src])
@ -253,9 +277,12 @@ AC_SUBST(riak)
AC_SUBST(redis)
AC_SUBST(elixir)
AC_SUBST(iconv)
AC_SUBST(stun)
AC_SUBST(sip)
AC_SUBST(debug)
AC_SUBST(tools)
AC_SUBST(latest_deps)
AC_SUBST(system_deps)
AC_SUBST(CFLAGS)
AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS)

View File

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

View File

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

View File

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

View File

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

26
mix.exs
View File

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

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]}]},
"distillery": {:hex, :distillery, "1.2.2", "d5a52920cbe2378c8a21dfc83b526b4225944b9dce7bf170fe5f5cddda81ffb3", [:mix], []},
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
"esip": {:hex, :esip, "1.0.11", "eeb0b1cbb64d56201dd6abb09126afab1e96da2418e833cba6c55f890f2f6649", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}, {:stun, "1.0.10", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
"esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []},
"fast_tls": {:hex, :fast_tls, "1.0.11", "8ccff4b68e6bb79b91c689da8cf92ec1006a575d2b6a09ac1ed5f9bf4724a39a", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.22", "7eb81a738218541208fa3a126ee36197fb0346852d8c12ad678039e539e019df", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.9", "1bf41a576d3eedcb690499350994932340908b4968832adcec4b55152d4e5f20", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []},
"iconv": {:hex, :iconv, "1.0.4", "faa4ac6755567a2806c7bee2cf26fc318016794ffc25481f7230db5f9d025dd8", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
"lager": {:hex, :lager, "3.2.4", "a6deb74dae7927f46bd13255268308ef03eb206ec784a94eaf7c1c0f3b811615", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
"p1_mysql": {:hex, :p1_mysql, "1.0.2", "893a99415f98ce8b6ad014ef950d4e878895787b6c8333587f1e506f831571e0", [:rebar3], []},
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.2", "27d3137e0b0098808d9c60bf197344669ed1107ed47ce4af2254099a62ccc27e", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.7", "030adbce8935f1b87aaedfdb037d3127cc671ee3e1904b394e6dde9e449d6979", [:rebar3], []},
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
"stringprep": {:hex, :stringprep, "1.0.8", "870d72db031796177261af88d1e6eb081dc314ad217377d441e5ea3c8504a310", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.10", "9fa83d4c5a76ca5ed3b536852ea00f3fbd2023241559ed6cb23a4ada62183b44", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"xmpp": {:hex, :xmpp, "1.1.9", "3548dc09faa414ee437c5db53a24af691724cb984b73af832d547c83f50313b9", [:rebar3], [{:fast_xml, "1.1.22", [hex: :fast_xml, optional: false]}, {:stringprep, "1.0.8", [hex: :stringprep, optional: false]}]}}
"p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}

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"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", "470539a"}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "f31d039"}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.11"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.21"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "e8dbfec277e7eb27b8130b13873b969cc346fafc"}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.9"}}},
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.4.2"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.8"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.12"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.11"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{jose, ".*", {git, "git://github.com/potatosalad/erlang-jose.git", {branch, "master"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.11"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.12"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
"31e035b"}}},
{tag, "1.0.3"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
"5559192"}}},
{tag, "1.1.3"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
{tag, "1.1.5"}}}},
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
@ -46,12 +46,12 @@
{tag, "2.4.1"}}}},
%% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
{tag, {if_version_above, "17", "v1.2.6", "v1.1.1"}}}}},
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
{tag, "1.0.4"}}}},
{tag, "1.0.5"}}}},
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
@ -65,7 +65,6 @@
stringprep,
fast_xml,
esip,
luerl,
stun,
fast_yaml,
xmpp,
@ -85,12 +84,15 @@
{i, "deps/p1_utils/include"},
{if_var_false, debug, no_debug_info},
{if_var_true, debug, debug_info},
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_version_above, "18", {d, 'STRONG_RAND_BYTES'}},
{if_version_above, "17", {d, 'GB_SETS_ITERATOR_FROM'}},
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
{if_var_true, hipe, native},
{src_dirs, [asn1, src,
{if_var_true, tools, tools},
@ -100,7 +102,7 @@
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts,
deps_erl_opts, override_deps_versions,
{if_var_true, elixir, rebar_elixir_compiler},
{if_var_true, elixir, rebar_exunit}
]}}.
@ -146,7 +148,7 @@
{post_hook_configure, [{"fast_tls", []},
{"stringprep", []},
{"fast_yaml", []},
{"esip", []},
{if_var_true, sip, {"esip", []}},
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{if_var_true, pam, {"epam", []}},
{if_var_true, zlib, {"ezlib", []}},

View File

@ -23,10 +23,12 @@ Vars = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])
Terms;
_Err ->
[]
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"}, {ldflags, ""}],
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"},
{ldflags, ""}, {system_deps, false}],
{cflags, CFlags} = lists:keyfind(cflags, 1, Vars),
{cppflags, CPPFlags} = lists:keyfind(cppflags, 1, Vars),
{ldflags, LDFlags} = lists:keyfind(ldflags, 1, Vars),
{system_deps, SystemDeps} = lists:keyfind(system_deps, 1, Vars),
GetCfg0 = fun(F, Cfg, [Key | Tail], Default) ->
Val = case lists:keyfind(Key, 1, Cfg) of
@ -141,6 +143,15 @@ ProcessVars = fun(_F, [], Acc) ->
_ ->
F(F, Tail, Acc)
end;
(F, [{if_have_fun, MFA, Value} | Tail], Acc) ->
{Mod, Fun, Arity} = MFA,
code:ensure_loaded(Mod),
case erlang:function_exported(Mod, Fun, Arity) of
true ->
F(F, Tail, ProcessSingleVar(F, Value, Acc));
false ->
F(F, Tail, Acc)
end;
(F, [Other1 | Tail1], Acc) ->
F(F, Tail1, [F(F, Other1, []) | Acc]);
(F, Val, Acc) when is_tuple(Val) ->
@ -327,11 +338,12 @@ Rules = [
ProcessFloatingDeps, [], []},
{[deps], IsRebar3,
Rebar3DepsFilter, []},
{[deps], os:getenv("USE_GLOBAL_DEPS") /= false,
{[deps], SystemDeps /= false,
GlobalDepsFilter, []}
],
Config = FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
Config = [{plugin_dir, filename:join([filename:dirname(SCRIPT),"plugins"])}]++
FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
%io:format("ejabberd configuration:~n ~p~n", [Config]),

View File

@ -116,12 +116,6 @@ CREATE TABLE vcard (
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE vcard_xupdate (
username text PRIMARY KEY,
hash text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE vcard_search (
username text NOT NULL,
lusername text PRIMARY KEY,

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)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
CREATE TABLE [dbo].[vcard_xupdate] (
[username] [varchar] (250) NOT NULL,
[hash] [text] NOT NULL,
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
CONSTRAINT [vcard_xupdate_PRIMARY] PRIMARY KEY CLUSTERED
(
[username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
) TEXTIMAGE_ON [PRIMARY];
ALTER TABLE [dbo].[pubsub_item] WITH CHECK ADD CONSTRAINT [pubsub_item_ibfk_1] FOREIGN KEY([nodeid])
REFERENCES [dbo].[pubsub_node] ([nodeid])
ON DELETE CASCADE;

View File

@ -121,12 +121,6 @@ CREATE TABLE vcard (
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE vcard_xupdate (
username varchar(191) PRIMARY KEY,
hash text NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE vcard_search (
username varchar(191) NOT NULL,
lusername varchar(191) PRIMARY KEY,

View File

@ -120,12 +120,6 @@ CREATE TABLE vcard (
created_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE TABLE vcard_xupdate (
username text PRIMARY KEY,
hash text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE TABLE vcard_search (
username text NOT NULL,
lusername text PRIMARY KEY,

View File

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

View File

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

View File

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

View File

@ -160,6 +160,9 @@ start_apps() ->
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
-spec opt_type(net_ticktime) -> fun((pos_integer()) -> pos_integer());
(cluster_nodes) -> fun(([node()]) -> [node()]);
(atom()) -> atom().
opt_type(net_ticktime) ->
fun (P) when is_integer(P), P > 0 -> P end;
opt_type(cluster_nodes) ->

File diff suppressed because it is too large Load Diff

View File

@ -40,15 +40,9 @@
unregister_connection/3
]).
-export([login/2, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password_s/2,
get_password/2, get_password/3, is_user_exists/2,
remove_user/2, remove_user/3, store_type/0,
plain_password_required/0, opt_type/1]).
-export([login/2, check_password/4, user_exists/2,
get_users/2, count_users/2, store_type/1,
plain_password_required/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@ -139,17 +133,9 @@ unregister_connection(_SID,
%% ---------------------------------
%% Specific anonymous auth functions
%% ---------------------------------
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
check_password(User, AuthzId, Server, Password) ->
check_password(User, AuthzId, Server, Password, undefined,
undefined).
check_password(User, _AuthzId, Server, _Password, _Digest,
_DigestGen) ->
check_password(User, _AuthzId, Server, _Password) ->
case
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
ejabberd_auth:user_exists_in_other_modules(?MODULE,
User, Server)
of
%% If user exists in other module, reject anonnymous authentication
@ -173,69 +159,25 @@ login(User, Server) ->
end
end.
%% When anonymous login is enabled, check that the user is permanent before
%% changing its password
set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of
true -> ok;
false -> {error, not_allowed}
end.
get_users(Server, _) ->
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
%% When anonymous login is enabled, check if permanent users are allowed on
%% the server:
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
count_users(Server, Opts) ->
length(get_users(Server, Opts)).
dirty_get_registered_users() -> [].
get_vh_registered_users(Server) ->
[{U, S}
|| {U, S, _R}
<- ejabberd_sm:get_vh_session_list(Server)].
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
%% Return password of permanent user or false for anonymous users
get_password(User, Server) ->
get_password(User, Server, <<"">>).
get_password(User, Server, DefaultValue) ->
case anonymous_user_exist(User, Server) or
login(User, Server)
of
%% We return the default value if the user is anonymous
true -> DefaultValue;
%% We return the permanent user password otherwise
false -> false
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
false ->
<<"">>;
Password ->
Password
end.
is_user_exists(User, Server) ->
user_exists(User, Server) ->
anonymous_user_exist(User, Server).
remove_user(_User, _Server) -> {error, not_allowed}.
plain_password_required(_) ->
false.
remove_user(_User, _Server, _Password) -> not_allowed.
plain_password_required() -> false.
store_type() ->
plain.
store_type(_) ->
external.
-spec opt_type(allow_multiple_connection) -> fun((boolean()) -> boolean());
(anonymous_protocol) -> fun((sasl_anon | login_anon | both) ->
sasl_anon | login_anon | both);
(atom()) -> [atom()].
opt_type(allow_multiple_connections) ->
fun (V) when is_boolean(V) -> V end;
opt_type(anonymous_protocol) ->

View File

@ -32,14 +32,8 @@
-behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
opt_type/1]).
try_register/3, user_exists/2, remove_user/2,
store_type/1, plain_password_required/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@ -49,271 +43,60 @@
%%%----------------------------------------------------------------------
start(Host) ->
Cmd = ejabberd_config:get_option({extauth_program, Host}, "extauth"),
extauth:start(Host, Cmd),
check_cache_last_options(Host),
ejabberd_auth_mnesia:start(Host).
extauth:start(Host, Cmd).
stop(Host) ->
extauth:stop(Host),
ejabberd_auth_mnesia:stop(Host).
extauth:stop(Host).
check_cache_last_options(Server) ->
case get_cache_option(Server) of
false -> no_cache;
{true, _CacheTime} ->
case get_mod_last_configured(Server) of
no_mod_last ->
?ERROR_MSG("In host ~p extauth is used, extauth_cache "
"is enabled but mod_last is not enabled.",
[Server]),
no_cache;
_ -> cache
end
end.
plain_password_required(_) -> true.
plain_password_required() -> true.
store_type() -> external.
store_type(_) -> external.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
case get_cache_option(Server) of
false ->
check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
check_password_cache(User, AuthzId, Server, Password,
CacheTime)
end
check_password_extauth(User, AuthzId, Server, Password)
end.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
true ->
set_password_mnesia(User, Server, Password), ok;
_ -> {error, unknown_problem}
true -> ok;
_ -> {error, db_failure}
end.
try_register(User, Server, Password) ->
case get_cache_option(Server) of
false -> try_register_extauth(User, Server, Password);
{true, _CacheTime} ->
try_register_external_cache(User, Server, Password)
end.
extauth:try_register(User, Server, Password).
dirty_get_registered_users() ->
ejabberd_auth_mnesia:dirty_get_registered_users().
get_vh_registered_users(Server) ->
ejabberd_auth_mnesia:get_vh_registered_users(Server).
get_vh_registered_users(Server, Data) ->
ejabberd_auth_mnesia:get_vh_registered_users(Server,
Data).
get_vh_registered_users_number(Server) ->
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
get_vh_registered_users_number(Server, Data) ->
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
Data).
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
get_password(User, Server) ->
case get_cache_option(Server) of
false -> false;
{true, CacheTime} ->
get_password_cache(User, Server, CacheTime)
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
false -> <<"">>;
Other -> Other
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
try extauth:is_user_exists(User, Server) of
Res -> Res
user_exists(User, Server) ->
try extauth:user_exists(User, Server) of
Res -> Res
catch
_:Error -> {error, Error}
_:Error ->
?ERROR_MSG("external authentication program failure: ~p",
[Error]),
{error, db_failure}
end.
remove_user(User, Server) ->
case extauth:remove_user(User, Server) of
false -> false;
true ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_mnesia:remove_user(User, Server)
end
false -> {error, not_allowed};
true -> ok
end.
remove_user(User, Server, Password) ->
case extauth:remove_user(User, Server, Password) of
false -> false;
true ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_mnesia:remove_user(User, Server,
Password)
end
end.
%%%
%%% Extauth cache management
%%%
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
get_cache_option(Host) ->
case ejabberd_config:get_option({extauth_cache, Host}, false) of
false -> false;
CacheTime -> {true, CacheTime}
end.
%% @spec (User, AuthzId, Server, Password) -> true | false
check_password_extauth(User, _AuthzId, Server, Password) ->
extauth:check_password(User, Server, Password) andalso
Password /= <<"">>.
%% @spec (User, Server, Password) -> true | false
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
check_password_cache(User, AuthzId, Server, Password, 0) ->
check_password_external_cache(User, AuthzId, Server, Password);
check_password_cache(User, AuthzId, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of
online ->
check_password_mnesia(User, AuthzId, Server, Password);
never ->
check_password_external_cache(User, AuthzId, Server, Password);
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
check_password_external_cache(User, AuthzId, Server, Password);
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia
true ->
case check_password_mnesia(User, AuthzId, Server, Password) of
%% If password valid in Mnesia, accept it
true -> true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result
false ->
check_password_external_cache(User, AuthzId, Server, Password)
end;
%% Else (need to refresh), check in extauth and cache result
false ->
check_password_external_cache(User, AuthzId, Server, Password)
end
end.
get_password_mnesia(User, Server) ->
ejabberd_auth_mnesia:get_password(User, Server).
-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online -> get_password_mnesia(User, Server);
never -> false;
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
false;
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
true -> get_password_mnesia(User, Server);
false -> false
end
end.
%% Check the password using extauth; if success then cache it
check_password_external_cache(User, AuthzId, Server, Password) ->
case check_password_extauth(User, AuthzId, Server, Password) of
true ->
set_password_mnesia(User, Server, Password), true;
false -> false
end.
%% Try to register using extauth; if success then cache it
try_register_external_cache(User, Server, Password) ->
case try_register_extauth(User, Server, Password) of
{atomic, ok} = R ->
set_password_mnesia(User, Server, Password), R;
_ -> {error, not_allowed}
end.
%% @spec (User, AuthzId, Server, Password) -> true | false
check_password_mnesia(User, AuthzId, Server, Password) ->
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
set_password_mnesia(User, Server, Password) ->
%% @spec (TimeLast, CacheTime) -> true | false
%% TimeLast = online | never | integer()
%% CacheTime = integer() | false
ejabberd_auth_mnesia:set_password(User, Server,
Password).
is_fresh_enough(TimeStampLast, CacheTime) ->
Now = p1_time_compat:system_time(seconds),
TimeStampLast + CacheTime > Now.
%% Code copied from mod_configure.erl
%% Code copied from web/ejabberd_web_admin.erl
%% TODO: Update time format to XEP-0202: Entity Time
-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
case get_last_info(User, Server) of
mod_last_required -> mod_last_required;
not_found -> never;
{ok, Timestamp, _Status} -> Timestamp
end;
_ -> online
end.
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
get_last_info(User, Server) ->
case get_mod_last_enabled(Server) of
mod_last -> mod_last:get_last_info(User, Server);
no_mod_last -> mod_last_required
end.
%% @spec (Server) -> mod_last | no_mod_last
get_mod_last_enabled(Server) ->
case gen_mod:is_loaded(Server, mod_last) of
true -> mod_last;
false -> no_mod_last
end.
get_mod_last_configured(Server) ->
case is_configured(Server, mod_last) of
true -> mod_last;
false -> no_mod_last
end.
is_configured(Host, Module) ->
Os = ejabberd_config:get_option({modules, Host}, []),
lists:keymember(Module, 1, Os).
-spec opt_type(extauth_cache) -> fun((false | non_neg_integer()) ->
false | non_neg_integer());
(extauth_program) -> fun((binary()) -> string());
(atom()) -> [atom()].
opt_type(extauth_cache) ->
?WARNING_MSG("option 'extauth_cache' is deprecated and has no effect, "
"use authentication or global cache configuration "
"options: auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []),
fun (false) -> false;
(I) when is_integer(I), I >= 0 -> I
end;

View File

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

View File

@ -31,15 +31,11 @@
-behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2, init_db/0,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/2,
plain_password_required/0]).
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, init_db/0,
count_users/2, get_password/2,
remove_user/2, store_type/1, export/1, import/2,
plain_password_required/1, use_cache/1]).
-export([need_transform/1, transform/1]).
-include("ejabberd.hrl").
@ -52,8 +48,6 @@
-record(reg_users_counter, {vhost = <<"">> :: binary(),
count = 0 :: integer() | '$1'}).
-define(SALT_LENGTH, 16).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@ -67,14 +61,14 @@ stop(_Host) ->
init_db() ->
ejabberd_mnesia:create(?MODULE, passwd,
[{disc_copies, [node()]},
[{disc_only_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
ejabberd_mnesia:create(?MODULE, reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]).
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Set = get_users(Server, []),
Size = length(Set),
LServer = jid:nameprep(Server),
F = fun () ->
@ -83,309 +77,157 @@ update_reg_users_counter_table(Server) ->
end,
mnesia:sync_dirty(F).
plain_password_required() ->
is_scrammed().
store_type() ->
ejabberd_auth:password_format(?MYNAME).
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}] when is_binary(Password) ->
Password /= <<"">>;
[#passwd{password = Scram}] when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ -> false
end
use_cache(Host) ->
case mnesia:table_info(passwd, storage_type) of
disc_only_copies ->
ejabberd_config:get_option(
{auth_use_cache, Host},
ejabberd_config:use_cache(Host));
_ ->
false
end.
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
[#passwd{password = Scram}] when is_record(Scram, scram) ->
Passwd = misc:decode_base64(Scram#scram.storedkey),
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
end
end.
plain_password_required(Server) ->
store_type(Server) == scram.
store_type(Server) ->
ejabberd_auth:password_format(Server).
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true ->
F = fun () ->
Password2 = case is_scrammed() and is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
mnesia:write(#passwd{us = US, password = Password2})
end,
{atomic, ok} = mnesia:transaction(F),
ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason}
try_register(User, Server, PasswordList) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Password = if is_list(PasswordList); is_binary(PasswordList) ->
iolist_to_binary(PasswordList);
true -> PasswordList
end,
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LPassword == error) and not is_record(Password, scram) ->
{error, invalid_password};
true ->
F = fun () ->
case mnesia:read({passwd, US}) of
[] ->
Password2 = case is_scrammed() and
is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
mnesia:write(#passwd{us = US,
password = Password2}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, 1),
ok;
[_E] -> exists
end
end,
mnesia:transaction(F)
end.
%% Get all registered users in Mnesia
dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
get_vh_registered_users(Server) ->
LServer = jid:nameprep(Server),
mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
get_vh_registered_users(Server,
[{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server,
[{limit, End - Start + 1}, {offset, Start}]);
get_vh_registered_users(Server,
[{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
case get_vh_registered_users(Server) of
[] -> [];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, [{prefix, Prefix}])
when is_binary(Prefix) ->
Set = [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)],
lists:keysort(1, Set);
get_vh_registered_users(Server,
[{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and
is_integer(End) ->
get_vh_registered_users(Server,
[{prefix, Prefix}, {limit, End - Start + 1},
{offset, Start}]);
get_vh_registered_users(Server,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
case [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)]
of
[] -> [];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jid:nameprep(Server),
Query = mnesia:dirty_select(reg_users_counter,
[{#reg_users_counter{vhost = LServer,
count = '$1'},
[], ['$1']}]),
case Query of
[Count] -> Count;
_ -> 0
end.
get_vh_registered_users_number(Server,
[{prefix, Prefix}])
when is_binary(Prefix) ->
Set = [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)],
length(Set);
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}]
when is_binary(Password) ->
Password;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
{misc:decode_base64(Scram#scram.storedkey),
misc:decode_base64(Scram#scram.serverkey),
misc:decode_base64(Scram#scram.salt),
Scram#scram.iterationcount};
_ -> false
end.
get_password_s(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}]
when is_binary(Password) ->
Password;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
<<"">>;
_ -> <<"">>
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] -> false;
[_] -> true;
Other -> {error, Other}
end.
%% @spec (User, Server) -> ok
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
US = {User, Server},
F = fun () ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, LServer,
-1)
mnesia:write(#passwd{us = US, password = Password})
end,
mnesia:transaction(F),
ok.
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
try_register(User, Server, Password) ->
US = {User, Server},
F = fun () ->
case mnesia:read({passwd, US}) of
[#passwd{password = Password}]
when is_binary(Password) ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, LServer,
-1),
ok;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
case is_password_scram_valid(Password, Scram) of
true ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, -1),
ok;
false -> not_allowed
end;
_ -> not_exists
[] ->
mnesia:write(#passwd{us = US, password = Password}),
mnesia:dirty_update_counter(reg_users_counter, Server, 1),
ok;
[_] ->
{error, exists}
end
end,
case mnesia:transaction(F) of
{atomic, ok} -> ok;
{atomic, Res} -> Res;
_ -> bad_request
{atomic, Res} ->
Res;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.
get_users(Server, []) ->
mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Server}], ['$1']}]);
get_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
get_users(Server, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
case get_users(Server, []) of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
lists:keysort(1, Set);
get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and is_integer(End) ->
get_users(Server, [{prefix, Prefix}, {limit, End - Start + 1},
{offset, Start}]);
get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) ->
case [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)] of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_users(Server, _) ->
get_users(Server, []).
count_users(Server, []) ->
case mnesia:dirty_select(
reg_users_counter,
[{#reg_users_counter{vhost = Server, count = '$1'},
[], ['$1']}]) of
[Count] -> Count;
_ -> 0
end;
count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
length(Set);
count_users(Server, _) ->
count_users(Server, []).
get_password(User, Server) ->
case mnesia:dirty_read(passwd, {User, Server}) of
[#passwd{password = Password}] ->
{ok, Password};
_ ->
error
end.
remove_user(User, Server) ->
US = {User, Server},
F = fun () ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, Server, -1),
ok
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.
need_transform(#passwd{us = {U, S}, password = Pass}) ->
if is_binary(Pass) ->
IsScrammed = is_scrammed(),
if IsScrammed ->
case store_type(S) of
scram ->
?INFO_MSG("Passwords in Mnesia table 'passwd' "
"will be SCRAM'ed", []);
true ->
ok
end,
IsScrammed;
"will be SCRAM'ed", []),
true;
plain ->
false
end;
is_record(Pass, scram) ->
case is_scrammed() of
true ->
next;
false ->
case store_type(S) of
scram ->
false;
plain ->
?WARNING_MSG("Some passwords were stored in the database "
"as SCRAM, but 'auth_password_format' "
"is not configured as 'scram'.", []),
"is not configured as 'scram': some "
"authentication mechanisms such as DIGEST-MD5 "
"would *fail*", []),
false
end;
is_list(U) orelse is_list(S) orelse is_list(Pass) ->
@ -410,61 +252,24 @@ transform(#passwd{us = {U, S}, password = Pass} = R)
transform(R#passwd{us = NewUS, password = NewPass});
transform(#passwd{us = {U, S}, password = Password} = P)
when is_binary(Password) ->
case is_scrammed() of
true ->
case store_type(S) of
scram ->
case jid:resourceprep(Password) of
error ->
?ERROR_MSG("SASLprep failed for password of user ~s@~s",
[U, S]),
P;
_ ->
Scram = password_to_scram(Password),
Scram = ejabberd_auth:password_to_scram(Password),
P#passwd{password = Scram}
end;
false ->
plain ->
P
end;
transform(#passwd{password = Password} = P)
when is_record(Password, scram) ->
P.
%%%
%%% SCRAM
%%%
is_scrammed() ->
scram == store_type().
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = misc:encode_base64(StoredKey),
serverkey = misc:encode_base64(ServerKey),
salt = misc:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = misc:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
misc:decode_base64(Scram#scram.storedkey) == StoredKey
end.
export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})

View File

@ -30,14 +30,8 @@
-behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
-export([start/1, stop/1, check_password/4,
user_exists/2, store_type/1, plain_password_required/1,
opt_type/1]).
start(_Host) ->
@ -46,13 +40,6 @@ start(_Host) ->
stop(_Host) ->
ok.
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
check_password(User, AuthzId, Host, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
@ -70,26 +57,7 @@ check_password(User, AuthzId, Host, Password) ->
end
end.
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() -> [].
get_vh_registered_users(_Host) -> [].
get_vh_registered_users(_Host, _) -> [].
get_vh_registered_users_number(_Host) -> 0.
get_vh_registered_users_number(_Host, _) -> 0.
get_password(_User, _Server) -> false.
get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error}
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
is_user_exists(User, Host) ->
user_exists(User, Host) ->
Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of
username -> User;
@ -97,16 +65,13 @@ is_user_exists(User, Host) ->
end,
case catch epam:acct_mgmt(Service, UserInfo) of
true -> true;
_ -> false
false -> false;
_Err -> {error, db_failure}
end.
remove_user(_User, _Server) -> {error, not_allowed}.
plain_password_required(_) -> true.
remove_user(_User, _Server, _Password) -> not_allowed.
plain_password_required() -> true.
store_type() -> external.
store_type(_) -> external.
%%====================================================================
%% Internal functions
@ -117,6 +82,9 @@ get_pam_service(Host) ->
get_pam_userinfotype(Host) ->
ejabberd_config:get_option({pam_userinfotype, Host}, username).
-spec opt_type(pam_service) -> fun((binary()) -> binary());
(pam_userinfotype) -> fun((username | jid) -> username | jid);
(atom()) -> [atom()].
opt_type(pam_service) -> fun iolist_to_binary/1;
opt_type(pam_userinfotype) ->
fun (username) -> username;

View File

@ -32,15 +32,10 @@
-behaviour(ejabberd_auth).
%% External exports
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/2,
plain_password_required/0]).
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2,
get_password/2, remove_user/2, store_type/1, export/1, import/2,
plain_password_required/1]).
-export([passwd_schema/0]).
-include("ejabberd.hrl").
@ -49,258 +44,65 @@
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
password = <<"">> :: binary() | scram() | '_'}).
-define(SALT_LENGTH, 16).
start(_Host) ->
ok.
stop(_Host) ->
ok.
plain_password_required() ->
case is_scrammed() of
false -> false;
true -> true
end.
plain_password_required(Server) ->
store_type(Server) == scram.
store_type() ->
case is_scrammed() of
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
store_type(Server) ->
ejabberd_auth:password_format(Server).
passwd_schema() ->
{record_info(fields, passwd), #passwd{}}.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}} when is_binary(Password) ->
Password /= <<"">>;
{ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ ->
false
end
end.
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
Passwd = misc:decode_base64(Scram#scram.storedkey),
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
end
end.
set_password(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true ->
Password2 = case is_scrammed() and is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
ok = ejabberd_riak:put(#passwd{us = US, password = Password2},
passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])
ejabberd_riak:put(#passwd{us = {User, Server}, password = Password},
passwd_schema(),
[{'2i', [{<<"host">>, Server}]}]).
try_register(User, Server, Password) ->
US = {User, Server},
case ejabberd_riak:get(passwd, passwd_schema(), US) of
{error, notfound} ->
ejabberd_riak:put(#passwd{us = US, password = Password},
passwd_schema(),
[{'2i', [{<<"host">>, Server}]}]);
{ok, _} ->
{error, exists};
{error, _} = Err ->
Err
end.
try_register(User, Server, PasswordList) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Password = if is_list(PasswordList); is_binary(PasswordList) ->
iolist_to_binary(PasswordList);
true -> PasswordList
end,
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
LPassword == error and not is_record(Password, scram) ->
{error, invalid_password};
true ->
case ejabberd_riak:get(passwd, passwd_schema(), US) of
{error, notfound} ->
Password2 = case is_scrammed() and
is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
{atomic, ejabberd_riak:put(
#passwd{us = US,
password = Password2},
passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])};
{ok, _} ->
exists;
Err ->
{atomic, Err}
end
end.
dirty_get_registered_users() ->
lists:flatmap(
fun(Server) ->
get_vh_registered_users(Server)
end, ejabberd_config:get_vh_by_auth_method(riak)).
get_vh_registered_users(Server) ->
LServer = jid:nameprep(Server),
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
get_users(Server, _) ->
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, Server) of
{ok, Users} ->
Users;
_ ->
[]
end.
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jid:nameprep(Server),
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
count_users(Server, _) ->
case ejabberd_riak:count_by_index(passwd, <<"host">>, Server) of
{ok, N} ->
N;
_ ->
0
end.
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
Password;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
{misc:decode_base64(Scram#scram.storedkey),
misc:decode_base64(Scram#scram.serverkey),
misc:decode_base64(Scram#scram.salt),
Scram#scram.iterationcount};
_ -> false
end.
get_password_s(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
Password;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
<<"">>;
_ -> <<"">>
end.
is_user_exists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{error, notfound} -> false;
{ok, _} -> true;
Err -> Err
case ejabberd_riak:get(passwd, passwd_schema(), {User, Server}) of
{ok, Password} ->
{ok, Password};
{error, _} ->
error
end.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok.
remove_user(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
case is_password_scram_valid(Password, Scram) of
true ->
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok;
false -> not_allowed
end;
_ -> not_exists
end.
%%%
%%% SCRAM
%%%
is_scrammed() ->
scram == ejabberd_auth:password_format(?MYNAME).
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = misc:encode_base64(StoredKey),
serverkey = misc:encode_base64(ServerKey),
salt = misc:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = misc:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
misc:decode_base64(Scram#scram.storedkey) == StoredKey
end.
ejabberd_riak:delete(passwd, {User, Server}).
export(_Server) ->
[{passwd,

View File

@ -30,16 +30,12 @@
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-behaviour(ejabberd_config).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
convert_to_scram/1]).
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2, get_password/2,
remove_user/2, store_type/1, plain_password_required/1,
convert_to_scram/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@ -54,397 +50,84 @@ start(_Host) -> ok.
stop(_Host) -> ok.
plain_password_required() ->
case is_scrammed() of
false -> false;
true -> true
end.
plain_password_required(Server) ->
store_type(Server) == scram.
store_type() ->
case is_scrammed() of
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
store_type(Server) ->
ejabberd_auth:password_format(Server).
%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
case is_scrammed() of
true ->
try sql_queries:get_password_scram(LServer, LUser) of
{selected,
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
Scram =
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
iterationcount = IterationCount},
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end;
false ->
try sql_queries:get_password(LServer, LUser) of
{selected, [{Password}]} ->
Password /= <<"">>;
{selected, [{_Password2}]} ->
false; %% Password is not correct
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end
end
end
end.
%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
case is_scrammed() of
false ->
try sql_queries:get_password(LServer, LUser) of
%% Account exists, check if password is valid
{selected, [{Passwd}]} ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end;
true ->
false
end
end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
LPassword = jid:resourceprep(Password),
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true ->
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
case catch sql_queries:set_password_scram_t(
LServer,
LUser,
Scram#scram.storedkey,
Scram#scram.serverkey,
Scram#scram.salt,
Scram#scram.iterationcount
)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end;
false ->
case catch sql_queries:set_password_t(LServer,
LUser, Password)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end
end
F = fun() ->
if is_record(Password, scram) ->
set_password_scram_t(
User,
Password#scram.storedkey, Password#scram.serverkey,
Password#scram.salt, Password#scram.iterationcount);
true ->
set_password_t(User, Password)
end
end,
case ejabberd_sql:sql_transaction(Server, F) of
{atomic, _} ->
ok;
{aborted, Reason} ->
?ERROR_MSG("failed to write to SQL table: ~p", [Reason]),
{error, db_failure}
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
LPassword = jid:resourceprep(Password),
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
LPassword == error and not is_record(Password, scram) ->
{error, invalid_password};
true ->
case is_scrammed() of
true ->
Scram = case is_record(Password, scram) of
true -> Password;
false -> password_to_scram(Password)
end,
case catch sql_queries:add_user_scram(
LServer,
LUser,
Scram#scram.storedkey,
Scram#scram.serverkey,
Scram#scram.salt,
Scram#scram.iterationcount
) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end;
false ->
case catch sql_queries:add_user(LServer, LUser,
Password) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end
end
Res = if is_record(Password, scram) ->
add_user_scram(
Server, User,
Password#scram.storedkey, Password#scram.serverkey,
Password#scram.salt, Password#scram.iterationcount);
true ->
add_user(Server, User, Password)
end,
case Res of
{updated, 1} -> ok;
_ -> {error, exists}
end.
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(sql),
lists:flatmap(fun (Server) ->
get_vh_registered_users(Server)
end,
Servers).
get_vh_registered_users(Server) ->
case jid:nameprep(Server) of
error -> [];
<<>> -> [];
LServer ->
case catch sql_queries:list_users(LServer) of
{selected, Res} ->
[{U, LServer} || {U} <- Res];
_ -> []
end
get_users(Server, Opts) ->
case list_users(Server, Opts) of
{selected, Res} ->
[{U, Server} || {U} <- Res];
_ -> []
end.
get_vh_registered_users(Server, Opts) ->
case jid:nameprep(Server) of
error -> [];
<<>> -> [];
LServer ->
case catch sql_queries:list_users(LServer, Opts) of
{selected, Res} ->
[{U, LServer} || {U} <- Res];
_ -> []
end
end.
get_vh_registered_users_number(Server) ->
case jid:nameprep(Server) of
error -> 0;
<<>> -> 0;
LServer ->
case catch sql_queries:users_number(LServer) of
{selected, [{Res}]} ->
Res;
_ -> 0
end
end.
get_vh_registered_users_number(Server, Opts) ->
case jid:nameprep(Server) of
error -> 0;
<<>> -> 0;
LServer ->
case catch sql_queries:users_number(LServer, Opts) of
{selected, [{Res}]} ->
Res;
_Other -> 0
end
count_users(Server, Opts) ->
case users_number(Server, Opts) of
{selected, [{Res}]} ->
Res;
_Other -> 0
end.
get_password(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
case is_scrammed() of
true ->
case catch sql_queries:get_password_scram(
LServer, LUser) of
{selected,
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
{misc:decode_base64(StoredKey),
misc:decode_base64(ServerKey),
misc:decode_base64(Salt),
IterationCount};
_ -> false
end;
false ->
case catch sql_queries:get_password(LServer, LUser)
of
{selected, [{Password}]} -> Password;
_ -> false
end
end
case get_password_scram(Server, User) of
{selected, [{Password, <<>>, <<>>, 0}]} ->
{ok, Password};
{selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
{ok, #scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
iterationcount = IterationCount}};
{selected, []} ->
error;
Err ->
?ERROR_MSG("Failed to read password for user ~s@~s: ~p",
[User, Server, Err]),
error
end.
get_password_s(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
<<"">>;
(LUser == <<>>) or (LServer == <<>>) ->
<<"">>;
true ->
case is_scrammed() of
false ->
case catch sql_queries:get_password(LServer, LUser) of
{selected, [{Password}]} -> Password;
_ -> <<"">>
end;
true -> <<"">>
end
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
try sql_queries:get_password(LServer, LUser) of
{selected, [{_Password}]} ->
true; %% Account exists
{selected, []} ->
false; %% Account does not exist
{error, Error} -> {error, Error}
catch
_:B -> {error, B}
end
end.
%% @spec (User, Server) -> ok | error
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
error;
(LUser == <<>>) or (LServer == <<>>) ->
error;
true ->
catch sql_queries:del_user(LServer, LUser),
ok
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
error;
(LUser == <<>>) or (LServer == <<>>) ->
error;
true ->
case is_scrammed() of
true ->
case check_password(User, <<"">>, Server, Password) of
true ->
remove_user(User, Server),
ok;
false -> not_allowed
end;
false ->
F = fun () ->
Result = sql_queries:del_user_return_password(
LServer, LUser, Password),
case Result of
{selected, [{Password}]} -> ok;
{selected, []} -> not_exists;
_ -> not_allowed
end
end,
{atomic, Result} = sql_queries:sql_transaction(
LServer, F),
Result
end
end.
%%%
%%% SCRAM
%%%
is_scrammed() ->
scram == ejabberd_auth:password_format(?MYNAME).
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = misc:encode_base64(StoredKey),
serverkey = misc:encode_base64(ServerKey),
salt = misc:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
"enabled, but the password of user '~s' in the 'users' table is not "
"scrammed. You may want to execute this command: "
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
false;
is_password_scram_valid_stored(Password, Scram, _, _) ->
is_password_scram_valid(Password, Scram).
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = misc:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
misc:decode_base64(Scram#scram.storedkey) == StoredKey
case del_user(Server, User) of
{updated, _} ->
ok;
Err ->
?ERROR_MSG("failed to delete user ~s@~s: ~p",
[User, Server, Err]),
{error, db_failure}
end.
-define(BATCH_SIZE, 1000).
@ -459,6 +142,105 @@ set_password_scram_t(LUser,
"salt=%(Salt)s",
"iterationcount=%(IterationCount)d"]).
set_password_t(LUser, Password) ->
?SQL_UPSERT_T(
"users",
["!username=%(LUser)s",
"password=%(Password)s"]).
get_password_scram(LServer, LUser) ->
ejabberd_sql:sql_query(
LServer,
?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
" from users"
" where username=%(LUser)s")).
add_user_scram(LServer, LUser,
StoredKey, ServerKey, Salt, IterationCount) ->
ejabberd_sql:sql_query(
LServer,
?SQL("insert into users(username, password, serverkey, salt, "
"iterationcount) "
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
" %(Salt)s, %(IterationCount)d)")).
add_user(LServer, LUser, Password) ->
ejabberd_sql:sql_query(
LServer,
?SQL("insert into users(username, password) "
"values (%(LUser)s, %(Password)s)")).
del_user(LServer, LUser) ->
ejabberd_sql:sql_query(
LServer,
?SQL("delete from users where username=%(LUser)s")).
list_users(LServer, []) ->
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users"));
list_users(LServer, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
list_users(LServer,
[{limit, End - Start + 1}, {offset, Start - 1}]);
list_users(LServer,
[{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and
is_integer(End) ->
list_users(LServer,
[{prefix, Prefix}, {limit, End - Start + 1},
{offset, Start - 1}]);
list_users(LServer, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users "
"order by username "
"limit %(Limit)d offset %(Offset)d"));
list_users(LServer,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users "
"where username like %(SPrefix2)s escape '^' "
"order by username "
"limit %(Limit)d offset %(Offset)d")).
users_number(LServer) ->
ejabberd_sql:sql_query(
LServer,
fun(pgsql, _) ->
case
ejabberd_config:get_option(
{pgsql_users_number_estimate, LServer}, false) of
true ->
ejabberd_sql:sql_query_t(
?SQL("select @(reltuples :: bigint)d from pg_class"
" where oid = 'users'::regclass::oid"));
_ ->
ejabberd_sql:sql_query_t(
?SQL("select @(count(*))d from users"))
end;
(_Type, _) ->
ejabberd_sql:sql_query_t(
?SQL("select @(count(*))d from users"))
end).
users_number(LServer, [{prefix, Prefix}])
when is_binary(Prefix) ->
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query(
LServer,
?SQL("select @(count(*))d from users "
"where username like %(SPrefix2)s escape '^'"));
users_number(LServer, []) ->
users_number(LServer).
convert_to_scram(Server) ->
LServer = jid:nameprep(Server),
if
@ -485,7 +267,7 @@ convert_to_scram(Server) ->
"password of user ~s@~s",
[LUser, LServer]);
_ ->
Scram = password_to_scram(Password),
Scram = ejabberd_auth:password_to_scram(Password),
set_password_scram_t(
LUser,
Scram#scram.storedkey,
@ -498,10 +280,16 @@ convert_to_scram(Server) ->
Err -> {bad_reply, Err}
end
end,
case sql_queries:sql_transaction(LServer, F) of
case ejabberd_sql:sql_transaction(LServer, F) of
{atomic, ok} -> ok;
{atomic, continue} -> convert_to_scram(Server);
{atomic, Error} -> {error, Error};
Error -> Error
end
end.
-spec opt_type(pgsql_users_number_estimate) -> fun((boolean()) -> boolean());
(atom()) -> [atom()].
opt_type(pgsql_users_number_estimate) ->
fun (V) when is_boolean(V) -> V end;
opt_type(_) -> [pgsql_users_number_estimate].

View File

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

View File

@ -528,6 +528,10 @@ clean_treap(Treap, CleanPriority) ->
now_priority() ->
-p1_time_compat:system_time(micro_seconds).
-spec opt_type(captcha_cmd) -> fun((binary()) -> binary());
(captcha_host) -> fun((binary()) -> binary());
(captcha_limit) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(captcha_cmd) ->
fun (FileName) ->
F = iolist_to_binary(FileName), if F /= <<"">> -> F end

View File

@ -891,6 +891,9 @@ permission_addon() ->
[{access, ejabberd_config:get_option(commands_admin_access, none)}],
{get_exposed_commands(), []}}}].
-spec opt_type(commands_admin_access) -> fun((any()) -> any());
(commands) -> fun((list()) -> list());
(atom()) -> [atom()].
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;

View File

@ -179,7 +179,10 @@ read_file(File, Opts) ->
load_file(File) ->
State0 = read_file(File),
State1 = hosts_to_start(State0),
validate_opts(State1).
AllMods = get_modules(),
init_module_db_table(AllMods),
ModOpts = get_modules_with_options(AllMods),
validate_opts(State1, ModOpts).
-spec reload_file() -> ok.
@ -971,11 +974,12 @@ default_db(Opt, Host, Module) ->
end
end.
get_modules_with_options() ->
get_modules() ->
{ok, Mods} = application:get_key(ejabberd, modules),
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
AllMods = [?MODULE|ExtMods++Mods],
init_module_db_table(AllMods),
ExtMods ++ Mods.
get_modules_with_options(Modules) ->
lists:foldl(
fun(Mod, D) ->
case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of
@ -992,10 +996,9 @@ get_modules_with_options() ->
false ->
D
end
end, dict:new(), AllMods).
end, dict:new(), Modules).
validate_opts(#state{opts = Opts} = State) ->
ModOpts = get_modules_with_options(),
validate_opts(#state{opts = Opts} = State, ModOpts) ->
NewOpts = lists:filtermap(
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
case dict:find(Opt, ModOpts) of
@ -1089,7 +1092,7 @@ replace_module(mod_roster_odbc) -> {mod_roster, sql};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
replace_module(mod_vcard_ldap) -> {mod_vcard, ldap};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
replace_module(mod_vcard_xupdate_odbc) -> mod_vcard_xupdate;
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(mod_http_bind) -> mod_bosh;
replace_module(Module) ->
@ -1362,6 +1365,23 @@ emit_deprecation_warning(Module, NewModule) ->
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
-spec opt_type(hide_sensitive_log_data) -> fun((boolean()) -> boolean());
(hosts) -> fun(([binary()]) -> [binary()]);
(language) -> fun((binary()) -> binary());
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
(default_db) -> fun((atom()) -> atom());
(default_ram_db) -> fun((atom()) -> atom());
(loglevel) -> fun((0..5) -> 0..5);
(queue_dir) -> fun((binary()) -> binary());
(queue_type) -> fun((ram | file) -> ram | file);
(use_cache) -> fun((boolean()) -> boolean());
(cache_size) -> fun((timeout()) -> timeout());
(cache_missed) -> fun((boolean()) -> boolean());
(cache_life_time) -> fun((timeout()) -> timeout());
(domain_certfile) -> fun((binary()) -> binary());
(shared_key) -> fun((binary()) -> binary());
(node_start) -> fun((non_neg_integer()) -> non_neg_integer());
(atom()) -> [atom()].
opt_type(hide_sensitive_log_data) ->
fun (H) when is_boolean(H) -> H end;
opt_type(hosts) ->
@ -1397,7 +1417,7 @@ opt_type(cache_life_time) ->
(unlimited) -> infinity
end;
opt_type(domain_certfile) ->
fun iolist_to_binary/1;
fun misc:try_read_file/1;
opt_type(shared_key) ->
fun iolist_to_binary/1;
opt_type(node_start) ->

View File

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

View File

@ -136,15 +136,15 @@ init({SockMod, Socket}, Opts) ->
true -> [{[], ejabberd_xmlrpc}];
false -> []
end,
DefinedHandlers = gen_mod:get_opt(request_handlers, Opts, []),
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
?DEBUG("S: ~p~n", [RequestHandlers]),
DefaultHost = gen_mod:get_opt(default_host, Opts, undefined),
DefaultHost = proplists:get_value(default_host, Opts),
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
State = #state{sockmod = SockMod1,
@ -770,7 +770,9 @@ code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
parse_auth(<<"Basic ", Auth64/binary>>) ->
Auth = misc:decode_base64(Auth64),
Auth = try base64:decode(Auth64)
catch _:badarg -> <<>>
end,
%% Auth should be a string with the format: user@server:password
%% Note that password can contain additional characters '@' and ':'
case str:chr(Auth, $:) of
@ -899,19 +901,41 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
(atom()) -> [atom()].
opt_type(trusted_proxies) ->
fun (all) -> all;
(TPs) -> [iolist_to_binary(TP) || TP <- TPs] end;
opt_type(_) -> [trusted_proxies].
-spec listen_opt_type(tls) -> fun((boolean()) -> boolean());
(certfile) -> fun((binary()) -> binary());
(ciphers) -> fun((binary()) -> binary());
(dhfile) -> fun((binary()) -> binary());
(protocol_options) -> fun(([binary()]) -> binary());
(tls_compression) -> fun((boolean()) -> boolean());
(captcha) -> fun((boolean()) -> boolean());
(register) -> fun((boolean()) -> boolean());
(web_admin) -> fun((boolean()) -> boolean());
(http_bind) -> fun((boolean()) -> boolean());
(xmlrpc) -> fun((boolean()) -> boolean());
(request_handlers) -> fun(([{binary(), atom()}]) ->
[{binary(), atom()}]);
(default_host) -> fun((binary()) -> binary());
(custom_headers) -> fun(([{binary(), binary()}]) ->
[{binary(), binary()}]);
(atom()) -> [atom()].
listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(certfile) ->
fun iolist_to_binary/1;
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(ciphers) ->
fun iolist_to_binary/1;
listen_opt_type(dhfile) ->
fun iolist_to_binary/1;
fun misc:try_read_file/1;
listen_opt_type(protocol_options) ->
fun(Options) -> str:join(Options, <<"|">>) end;
listen_opt_type(tls_compression) ->

View File

@ -90,14 +90,7 @@ start(Port, Module, Opts) ->
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
start_dependent(Port, Module, Opts) ->
try check_listener_options(Opts) of
ok ->
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
catch
throw:{error, Error} ->
?ERROR_MSG(Error, []),
{error, Error}
end.
proc_lib:start_link(?MODULE, init, [Port, Module, Opts]).
init(PortIP, Module, RawOpts) ->
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
@ -456,48 +449,6 @@ config_reloaded() ->
%%%
%%% Check options
%%%
check_listener_options(Opts) ->
case includes_deprecated_ssl_option(Opts) of
false -> ok;
true ->
Error = "There is a problem with your ejabberd configuration file: "
"the option 'ssl' for listening sockets is no longer available."
" To get SSL encryption use the option 'tls'.",
throw({error, Error})
end,
case certfile_readable(Opts) of
true -> ok;
{false, Path} ->
ErrorText = "There is a problem in the configuration: "
"the specified file is not readable: ",
throw({error, ErrorText ++ Path})
end,
ok.
%% Parse the options of the socket,
%% and return if the deprecated option 'ssl' is included
%% @spec (Opts) -> true | false
includes_deprecated_ssl_option(Opts) ->
case lists:keysearch(ssl, 1, Opts) of
{value, {ssl, _SSLOpts}} ->
true;
_ ->
lists:member(ssl, Opts)
end.
%% @spec (Opts) -> true | {false, Path::string()}
certfile_readable(Opts) ->
case proplists:lookup(certfile, Opts) of
none -> true;
{certfile, Path} ->
PathS = binary_to_list(Path),
case ejabberd_config:is_file_readable(PathS) of
true -> true;
false -> {false, PathS}
end
end.
get_proto(Opts) ->
case proplists:get_value(proto, Opts) of
undefined ->

File diff suppressed because one or more lines are too long

View File

@ -25,21 +25,33 @@
%%%-------------------------------------------------------------------
-module(ejabberd_oauth_mnesia).
-behaviour(ejabberd_oauth).
-export([init/0,
store/1,
lookup/1,
clean/1]).
clean/1,
use_cache/0]).
-include("ejabberd_oauth.hrl").
init() ->
ejabberd_mnesia:create(?MODULE, oauth_token,
[{disc_copies, [node()]},
[{disc_only_copies, [node()]},
{attributes,
record_info(fields, oauth_token)}]),
ok.
use_cache() ->
case mnesia:table_info(oauth_token, storage_type) of
disc_only_copies ->
ejabberd_config:get_option(
oauth_use_cache,
ejabberd_config:use_cache(global));
_ ->
false
end.
store(R) ->
mnesia:dirty_write(R).

View File

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

View File

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

View File

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

535
src/ejabberd_pkix.erl Normal file
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
end.
-type sql_type() :: mysql | pgsql | sqlite | mssql | odbc.
-spec opt_type(sql_type) -> fun((sql_type()) -> sql_type());
(atom()) -> [atom()].
opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;

View File

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

View File

@ -108,17 +108,11 @@ is_redis_configured(Host) ->
PoolSize = ejabberd_config:has_option({redis_pool_size, Host}),
ConnTimeoutConfigured = ejabberd_config:has_option(
{redis_connect_timeout, Host}),
Modules = ejabberd_config:get_option({modules, Host}, []),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis,
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == redis,
ModuleWithRedisDBConfigured =
lists:any(
fun({Module, Opts}) ->
gen_mod:db_type(Host, Opts, Module) == redis
end, Modules),
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
PoolSize or ConnTimeoutConfigured or
SMConfigured or RouterConfigured or ModuleWithRedisDBConfigured.
SMConfigured or RouterConfigured.
get_specs() ->
lists:map(
@ -133,6 +127,14 @@ get_pool_size() ->
iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(IOList)).
-spec opt_type(redis_connect_timeout) -> fun((pos_integer()) -> pos_integer());
(redis_db) -> fun((non_neg_integer()) -> non_neg_integer());
(redis_password) -> fun((binary()) -> binary());
(redis_port) -> fun((0..65535) -> 0..65535);
(redis_server) -> fun((binary()) -> binary());
(redis_pool_size) -> fun((pos_integer()) -> pos_integer());
(redis_queue_type) -> fun((ram | file) -> ram | file);
(atom()) -> [atom()].
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_db) ->
@ -147,5 +149,4 @@ opt_type(redis_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(_) ->
[redis_connect_timeout, redis_db, redis_password,
redis_port, redis_pool_size, redis_server,
redis_pool_size, redis_queue_type].
redis_port, redis_pool_size, redis_server, redis_queue_type].

View File

@ -89,16 +89,11 @@ is_riak_configured(Host) ->
ejabberd_auth:auth_modules(Host)),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak,
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == riak,
Modules = ejabberd_config:get_option({modules, Host}, []),
ModuleWithRiakDBConfigured = lists:any(
fun({Module, Opts}) ->
gen_mod:db_type(Host, Opts, Module) == riak
end, Modules),
ServerConfigured or PortConfigured or StartIntervalConfigured
or PoolConfigured or CacertConfigured
or UserConfigured or PassConfigured
or SMConfigured or RouterConfigured
or AuthConfigured or ModuleWithRiakDBConfigured.
or AuthConfigured.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
@ -179,6 +174,14 @@ transform_options({riak_server, {S, P}}, Opts) ->
transform_options(Opt, Opts) ->
[Opt|Opts].
-spec opt_type(riak_pool_size) -> fun((pos_integer()) -> pos_integer());
(riak_port) -> fun((0..65535) -> 0..65535);
(riak_server) -> fun((binary()) -> binary());
(riak_start_interval) -> fun((pos_integer()) -> pos_integer());
(riak_cacertfile) -> fun((binary()) -> binary());
(riak_username) -> fun((binary()) -> binary());
(riak_password) -> fun((binary()) -> binary());
(atom()) -> [atom()].
opt_type(riak_pool_size) ->
fun (N) when is_integer(N), N >= 1 -> N end;
opt_type(riak_port) ->

View File

@ -157,6 +157,7 @@ register_route(Domain, ServerHost, LocalHint, Pid) ->
get_component_number(LDomain), Pid) of
ok ->
?DEBUG("Route registered: ~s", [LDomain]),
ejabberd_hooks:run(route_registered, [LDomain]),
delete_cache(Mod, LDomain);
{error, Err} ->
?ERROR_MSG("Failed to register route ~s: ~p",
@ -185,6 +186,7 @@ unregister_route(Domain, Pid) ->
LDomain, get_component_number(LDomain), Pid) of
ok ->
?DEBUG("Route unregistered: ~s", [LDomain]),
ejabberd_hooks:run(route_unregistered, [LDomain]),
delete_cache(Mod, LDomain);
{error, Err} ->
?ERROR_MSG("Failed to unregister route ~s: ~p",
@ -476,6 +478,16 @@ clean_cache(Node) ->
clean_cache() ->
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
-type domain_balancing() :: random | source | destination |
bare_source | bare_destination.
-spec opt_type(domain_balancing) -> fun((domain_balancing()) -> domain_balancing());
(domain_balancing_component_number) -> fun((pos_integer()) -> pos_integer());
(router_db_type) -> fun((atom()) -> atom());
(router_use_cache) -> fun((boolean()) -> boolean());
(router_cache_missed) -> fun((boolean()) -> boolean());
(router_cache_size) -> fun((timeout()) -> timeout());
(router_cache_life_time) -> fun((timeout()) -> timeout());
(atom()) -> [atom()].
opt_type(domain_balancing) ->
fun (random) -> random;
(source) -> source;

View File

@ -480,9 +480,13 @@ new_connection(MyServer, Server, From, FromTo,
end,
TRes = mnesia:transaction(F),
case TRes of
{atomic, Pid} ->
ejabberd_s2s_out:connect(Pid),
[Pid];
{atomic, Pid1} ->
if Pid1 == Pid ->
ejabberd_s2s_out:connect(Pid);
true ->
ejabberd_s2s_out:stop(Pid)
end,
[Pid1];
{aborted, Reason} ->
?ERROR_MSG("failed to register connection ~s -> ~s: ~p",
[MyServer, Server, Reason]),
@ -689,16 +693,30 @@ get_s2s_state(S2sPid) ->
end,
[{s2s_pid, S2sPid} | Infos].
-type use_starttls() :: boolean() | optional | required | required_trusted.
-spec opt_type(route_subdomains) -> fun((s2s | local) -> s2s | local);
(s2s_access) -> fun((any()) -> any());
(s2s_certfile) -> fun((binary()) -> binary());
(s2s_ciphers) -> fun((binary()) -> binary());
(s2s_dhfile) -> fun((binary()) -> binary());
(s2s_cafile) -> fun((binary()) -> binary());
(s2s_protocol_options) -> fun(([binary()]) -> binary());
(s2s_tls_compression) -> fun((boolean()) -> boolean());
(s2s_use_starttls) -> fun((use_starttls()) -> use_starttls());
(s2s_zlib) -> fun((boolean()) -> boolean());
(s2s_timeout) -> fun((timeout()) -> timeout());
(s2s_queue_type) -> fun((ram | file) -> ram | file);
(atom()) -> [atom()].
opt_type(route_subdomains) ->
fun (s2s) -> s2s;
(local) -> local
end;
opt_type(s2s_access) ->
fun acl:access_rules_validator/1;
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
opt_type(s2s_certfile) -> fun misc:try_read_file/1;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
opt_type(s2s_cafile) -> fun misc:try_read_file/1;
opt_type(s2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(s2s_tls_compression) ->

View File

@ -21,13 +21,12 @@
%%%-------------------------------------------------------------------
-module(ejabberd_s2s_in).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_socket).
%% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]).
%% ejabberd_config callbacks
-export([opt_type/1, listen_opt_type/1]).
%% ejabberd_listener callbacks
-export([listen_opt_type/1]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@ -165,7 +164,7 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) ->
case check_to(jid:make(LServer), State) of
false ->
send(State, xmpp:serr_host_unknown());
true ->
true ->
ServerHost = ejabberd_router:host_of_route(LServer),
State#{server_host => ServerHost}
end.
@ -187,7 +186,7 @@ handle_auth_success(RServer, Mech, _AuthModule,
[SockMod:pp(Socket), Mech, RServer, LServer,
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
true ->
true ->
AuthDomains1 = sets:add_element(RServer, AuthDomains),
change_shaper(State, RServer),
State#{auth_domains => AuthDomains1};
@ -245,7 +244,7 @@ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
State, [Pkt, Result]).
init([State, Opts]) ->
Shaper = gen_mod:get_opt(shaper, Opts, none),
Shaper = proplists:get_value(shaper, Opts, none),
TLSOpts1 = lists:filter(
fun({certfile, _}) -> true;
({ciphers, _}) -> true;
@ -311,13 +310,13 @@ code_change(_OldVsn, State, _Extra) ->
-spec check_from_to(jid(), jid(), state()) -> ok | {error, stream_error()}.
check_from_to(From, To, State) ->
case check_from(From, State) of
true ->
true ->
case check_to(To, State) of
true ->
true ->
ok;
false ->
false ->
{error, xmpp:serr_host_unknown()}
end;
end;
false ->
{error, xmpp:serr_invalid_from()}
end.
@ -344,11 +343,24 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
xmpp_stream_in:change_shaper(State, Shaper).
opt_type(_) ->
[].
-spec listen_opt_type(shaper) -> fun((any()) -> any());
(certfile) -> fun((binary()) -> binary());
(ciphers) -> fun((binary()) -> binary());
(dhfile) -> fun((binary()) -> binary());
(cafile) -> fun((binary()) -> binary());
(protocol_options) -> fun(([binary()]) -> binary());
(tls_compression) -> fun((boolean()) -> boolean());
(tls) -> fun((boolean()) -> boolean());
(supervisor) -> fun((boolean()) -> boolean());
(max_stanza_type) -> fun((timeout()) -> timeout());
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile) -> ejabberd_s2s:opt_type(s2s_certfile);
listen_opt_type(certfile) ->
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile);
@ -357,7 +369,7 @@ listen_opt_type(tls_compression) -> ejabberd_s2s:opt_type(s2s_tls_compression);
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I) -> I;
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;

View File

@ -439,6 +439,13 @@ maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
maybe_report_huge_timeout(_, _) ->
ok.
-spec opt_type(outgoing_s2s_families) -> fun(([ipv4|ipv6]) -> [inet|inet6]);
(outgoing_s2s_port) -> fun((0..65535) -> 0..65535);
(outgoing_s2s_timeout) -> fun((timeout()) -> timeout());
(s2s_dns_retries) -> fun((non_neg_integer()) -> non_neg_integer());
(s2s_dns_timeout) -> fun((timeout()) -> timeout());
(s2s_max_retry_delay) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(outgoing_s2s_families) ->
fun(Families) ->
lists:map(
@ -447,7 +454,7 @@ opt_type(outgoing_s2s_families) ->
end, Families)
end;
opt_type(outgoing_s2s_port) ->
fun (I) when is_integer(I), I > 0, I =< 65536 -> I end;
fun (I) when is_integer(I), I > 0, I < 65536 -> I end;
opt_type(outgoing_s2s_timeout) ->
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
timer:seconds(TimeOut);

View File

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

View File

@ -22,6 +22,21 @@
%%%-------------------------------------------------------------------
-module(ejabberd_sip).
-ifndef(SIP).
-include("logger.hrl").
-export([socket_type/0, start/2, listen_opt_type/1]).
log_error() ->
?CRITICAL_MSG("ejabberd is not compiled with SIP support", []).
socket_type() ->
log_error(),
raw.
listen_opt_type(_) ->
log_error(),
[].
start(_, _) ->
log_error(),
{error, sip_not_compiled}.
-else.
%% API
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
socket_type/0, listen_opt_type/1]).
@ -47,7 +62,10 @@ socket_type() ->
raw.
listen_opt_type(certfile) ->
fun iolist_to_binary/1;
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(_) ->
@ -56,3 +74,4 @@ listen_opt_type(_) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-endif.

View File

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

View File

@ -194,9 +194,20 @@ abort(Reason) ->
restart(Reason) ->
throw({aborted, Reason}).
%% Escape character that will confuse an SQL engine
-spec escape_char(char()) -> binary().
escape_char($\000) -> <<"\\0">>;
escape_char($\n) -> <<"\\n">>;
escape_char($\t) -> <<"\\t">>;
escape_char($\b) -> <<"\\b">>;
escape_char($\r) -> <<"\\r">>;
escape_char($') -> <<"''">>;
escape_char($") -> <<"\\\"">>;
escape_char($\\) -> <<"\\\\">>;
escape_char(C) -> <<C>>.
-spec escape(binary()) -> binary().
escape(S) ->
<< <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
<< <<(escape_char(Char))/binary>> || <<Char>> <= S >>.
%% Escape character that will confuse an SQL engine
%% Percent and underscore only need to be escaped for pattern matching like
@ -206,7 +217,7 @@ escape_like(S) when is_binary(S) ->
escape_like($%) -> <<"\\%">>;
escape_like($_) -> <<"\\_">>;
escape_like($\\) -> <<"\\\\\\\\">>;
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> escape_char(C).
escape_like_arg(S) when is_binary(S) ->
<< <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
@ -1080,6 +1091,20 @@ check_error({error, Why} = Err, Query) ->
check_error(Result, _Query) ->
Result.
-spec opt_type(sql_database) -> fun((binary()) -> binary());
(sql_keepalive_interval) -> fun((pos_integer()) -> pos_integer());
(sql_password) -> fun((binary()) -> binary());
(sql_port) -> fun((0..65535) -> 0..65535);
(sql_server) -> fun((binary()) -> binary());
(sql_username) -> fun((binary()) -> binary());
(sql_ssl) -> fun((boolean()) -> boolean());
(sql_ssl_verify) -> fun((boolean()) -> boolean());
(sql_ssl_certfile) -> fun((boolean()) -> boolean());
(sql_ssl_cafile) -> fun((boolean()) -> boolean());
(sql_query_timeout) -> fun((pos_integer()) -> pos_integer());
(sql_connect_timeout) -> fun((pos_integer()) -> pos_integer());
(sql_queue_type) -> fun((ram | file) -> ram | file);
(atom()) -> [atom()].
opt_type(sql_database) -> fun iolist_to_binary/1;
opt_type(sql_keepalive_interval) ->
fun (I) when is_integer(I), I > 0 -> I end;
@ -1090,8 +1115,8 @@ opt_type(sql_server) -> fun iolist_to_binary/1;
opt_type(sql_username) -> fun iolist_to_binary/1;
opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end;
opt_type(sql_ssl_certfile) -> fun iolist_to_binary/1;
opt_type(sql_ssl_cafile) -> fun iolist_to_binary/1;
opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1;
opt_type(sql_ssl_cafile) -> fun misc:try_read_file/1;
opt_type(sql_query_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_connect_timeout) ->
@ -1101,6 +1126,6 @@ opt_type(sql_queue_type) ->
opt_type(_) ->
[sql_database, sql_keepalive_interval,
sql_password, sql_port, sql_server,
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_cerfile,
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_certfile,
sql_ssl_cafile, sql_queue_type, sql_query_timeout,
sql_connect_timeout].

View File

@ -218,6 +218,9 @@ read_lines(Fd, File, Acc) ->
[]
end.
-spec opt_type(sql_pool_size) -> fun((pos_integer()) -> pos_integer());
(sql_start_interval) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(sql_pool_size) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(sql_start_interval) ->

View File

@ -27,6 +27,21 @@
-protocol({rfc, 5766}).
-protocol({xep, 176, '1.0'}).
-ifndef(STUN).
-include("logger.hrl").
-export([socket_type/0, start/2, listen_opt_type/1]).
log_error() ->
?CRITICAL_MSG("ejabberd is not compiled with STUN/TURN support", []).
socket_type() ->
log_error(),
raw.
listen_opt_type(_) ->
log_error(),
[].
start(_, _) ->
log_error(),
{error, sip_not_compiled}.
-else.
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
socket_type/0, listen_opt_type/1]).
@ -73,9 +88,9 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
ok
end,
AuthFun = fun ejabberd_auth:get_password_s/2,
Shaper = gen_mod:get_opt(shaper, Opts, none),
AuthType = gen_mod:get_opt(auth_type, Opts, user),
Realm = case gen_mod:get_opt(auth_realm, Opts) of
Shaper = proplists:get_value(shaper, Opts, none),
AuthType = proplists:get_value(auth_type, Opts, user),
Realm = case proplists:get_value(auth_realm, Opts) of
undefined when AuthType == user ->
if NumberOfMyHosts > 1 ->
?WARNING_MSG("you have several virtual "
@ -114,7 +129,10 @@ listen_opt_type(auth_realm) ->
listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(certfile) ->
fun iolist_to_binary/1;
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(turn_min_port) ->
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
listen_opt_type(turn_max_port) ->
@ -135,3 +153,4 @@ listen_opt_type(_) ->
[shaper, auth_type, auth_realm, tls, certfile, turn_min_port,
turn_max_port, turn_max_allocations, turn_max_permissions,
server_name].
-endif.

View File

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

View File

@ -330,6 +330,9 @@ process_remote_command([setlh, NewValue]) ->
[OldLH, NewLH]);
process_remote_command(_) -> throw(unknown_command).
-spec opt_type(watchdog_admins) -> fun(([binary()]) -> ljid());
(watchdog_large_heap) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(watchdog_admins) ->
fun (JIDs) ->
[jid:tolower(jid:decode(iolist_to_binary(S)))

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

@ -173,25 +173,26 @@ uids_domain_subst(Host, UIDs) ->
-spec get_config(binary(), list()) -> eldap_config().
get_config(Host, Opts) ->
Servers = gen_mod:get_opt({ldap_servers, Host}, Opts, [<<"localhost">>]),
Backups = gen_mod:get_opt({ldap_backups, Host}, Opts, []),
Encrypt = gen_mod:get_opt({ldap_encrypt, Host}, Opts, none),
TLSVerify = gen_mod:get_opt({ldap_tls_verify, Host}, Opts, false),
TLSCAFile = gen_mod:get_opt({ldap_tls_cacertfile, Host}, Opts),
TLSDepth = gen_mod:get_opt({ldap_tls_depth, Host}, Opts),
Port = gen_mod:get_opt({ldap_port, Host}, Opts,
case Encrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end),
RootDN = gen_mod:get_opt({ldap_rootdn, Host}, Opts, <<"">>),
Password = gen_mod:get_opt({ldap_password, Host}, Opts, <<"">>),
Base = gen_mod:get_opt({ldap_base, Host}, Opts, <<"">>),
OldDerefAliases = gen_mod:get_opt({deref_aliases, Host}, Opts, unspecified),
Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]),
Backups = get_opt(ldap_backups, Host, Opts, []),
Encrypt = get_opt(ldap_encrypt, Host, Opts, none),
TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false),
TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts),
TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts),
TLSDepth = get_opt(ldap_tls_depth, Host, Opts),
Port = get_opt(ldap_port, Host, Opts,
case Encrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end),
RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>),
Password = get_opt(ldap_password, Host, Opts, <<"">>),
Base = get_opt(ldap_base, Host, Opts, <<"">>),
OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified),
DerefAliases =
if OldDerefAliases == unspecified ->
gen_mod:get_opt({ldap_deref_aliases, Host}, Opts, never);
get_opt(ldap_deref_aliases, Host, Opts, never);
true ->
?WARNING_MSG("Option 'deref_aliases' is deprecated. "
"The option is still supported "
@ -203,6 +204,7 @@ get_config(Host, Opts) ->
backups = Backups,
tls_options = [{encrypt, Encrypt},
{tls_verify, TLSVerify},
{tls_certfile, TLSCertFile},
{tls_cacertfile, TLSCAFile},
{tls_depth, TLSDepth}],
port = Port,
@ -211,6 +213,15 @@ get_config(Host, Opts) ->
base = Base,
deref_aliases = DerefAliases}.
get_opt(Opt, Host, Opts) ->
get_opt(Opt, Host, Opts, undefined).
get_opt(Opt, Host, Opts, Default) ->
case proplists:get_value(Opt, Opts) of
undefined -> ejabberd_config:get_option({Opt, Host}, Default);
Value -> Value
end.
%%----------------------------------------
%% Borrowed from asn1rt_ber_bin_v2.erl
%%----------------------------------------
@ -318,12 +329,26 @@ collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]).
-type deref_aliases() :: never | searching | finding | always.
-type uids() :: binary() | {binary()} | {binary(), binary()}.
-spec opt_type(deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
(ldap_backups) -> fun(([binary()]) -> [binary()]);
(ldap_base) -> fun((binary()) -> binary());
(ldap_deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
(ldap_encrypt) -> fun((tls | starttls | none) -> tls | starttls | none);
(ldap_password) -> fun((binary()) -> binary());
(ldap_port) -> fun((0..65535) -> 0..65535);
(ldap_rootdn) -> fun((binary()) -> binary());
(ldap_servers) -> fun(([binary()]) -> [binary()]);
(ldap_tls_certfile) -> fun((binary()) -> string());
(ldap_tls_cacertfile) -> fun((binary()) -> string());
(ldap_tls_depth) -> fun((non_neg_integer()) -> non_neg_integer());
(ldap_tls_verify) -> fun((hard | soft | false) -> hard | soft | false);
(ldap_filter) -> fun((binary()) -> binary());
(ldap_uids) -> fun((uids()) -> uids());
(atom()) -> [atom()].
opt_type(deref_aliases) ->
fun (never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_deref_aliases);
opt_type(ldap_backups) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_base) -> fun iolist_to_binary/1;
@ -344,8 +369,12 @@ opt_type(ldap_port) ->
opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
opt_type(ldap_servers) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_tls_certfile) ->
fun(S) ->
binary_to_list(ejabberd_pkix:try_certfile(S))
end;
opt_type(ldap_tls_cacertfile) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
fun(S) -> binary_to_list(misc:try_read_file(S)) end;
opt_type(ldap_tls_depth) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(ldap_tls_verify) ->
@ -368,4 +397,5 @@ opt_type(_) ->
[deref_aliases, ldap_backups, ldap_base, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers, ldap_filter,
ldap_tls_cacertfile, ldap_tls_depth, ldap_tls_verify].
ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth,
ldap_tls_verify].

View File

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

View File

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

View File

@ -186,7 +186,7 @@ check_type(N) when is_integer(N), N>0 -> N;
check_type(parallel) -> parallel.
iqdisc(Host) ->
ejabberd_config:get_option({iqdisc, Host}, one_queue).
ejabberd_config:get_option({iqdisc, Host}, no_queue).
-spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}].
@ -198,6 +198,8 @@ transform_module_options(Opts) ->
Opt
end, Opts).
-spec opt_type(iqdisc) -> fun((type()) -> type());
(atom()) -> [atom()].
opt_type(iqdisc) -> fun check_type/1;
opt_type(_) -> [iqdisc].

View File

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

View File

@ -35,7 +35,7 @@
binary_to_integer/1,
integer_to_binary/1]}).
%% The following functions are deprected: use functions from aux.erl
%% The following functions are deprected: use functions from misc.erl
-export([tolower/1, term_to_base64/1, base64_to_term/1,
decode_base64/1, encode_base64/1, ip_to_list/1,
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,

View File

@ -28,12 +28,20 @@
-module(misc).
%% API
-export([tolower/1, term_to_base64/1, base64_to_term/1,
decode_base64/1, encode_base64/1, ip_to_list/1,
-export([tolower/1, term_to_base64/1, base64_to_term/1, ip_to_list/1,
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2]).
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2,
try_read_file/1]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
-deprecated([{decode_base64, 1},
{encode_base64, 1}]).
-include("logger.hrl").
-include_lib("kernel/include/file.hrl").
%%%===================================================================
%%% API
@ -54,83 +62,21 @@ term_to_base64(Term) ->
-spec base64_to_term(binary()) -> {term, term()} | error.
base64_to_term(Base64) ->
case catch binary_to_term(decode_base64(Base64), [safe]) of
{'EXIT', _} ->
error;
Term ->
{term, Term}
try binary_to_term(base64:decode(Base64), [safe]) of
Term -> {term, Term}
catch _:badarg ->
error
end.
-spec decode_base64(binary()) -> binary().
decode_base64(S) ->
case catch binary:last(S) of
C when C == $\n; C == $\s ->
decode_base64(binary:part(S, 0, byte_size(S) - 1));
_ ->
decode_base64_bin(S, <<>>)
try base64:mime_decode(S)
catch _:badarg -> <<>>
end.
take_without_spaces(Bin, Count) ->
take_without_spaces(Bin, Count, <<>>).
take_without_spaces(Bin, 0, Acc) ->
{Acc, Bin};
take_without_spaces(<<>>, _, Acc) ->
{Acc, <<>>};
take_without_spaces(<<$\s, Tail/binary>>, Count, Acc) ->
take_without_spaces(Tail, Count, Acc);
take_without_spaces(<<$\t, Tail/binary>>, Count, Acc) ->
take_without_spaces(Tail, Count, Acc);
take_without_spaces(<<$\n, Tail/binary>>, Count, Acc) ->
take_without_spaces(Tail, Count, Acc);
take_without_spaces(<<$\r, Tail/binary>>, Count, Acc) ->
take_without_spaces(Tail, Count, Acc);
take_without_spaces(<<Char:8, Tail/binary>>, Count, Acc) ->
take_without_spaces(Tail, Count-1, <<Acc/binary, Char:8>>).
decode_base64_bin(<<>>, Acc) ->
Acc;
decode_base64_bin(Bin, Acc) ->
case take_without_spaces(Bin, 4) of
{<<A, B, $=, $=>>, _} ->
<<Acc/binary, (d(A)):6, (d(B) bsr 4):2>>;
{<<A, B, C, $=>>, _} ->
<<Acc/binary, (d(A)):6, (d(B)):6, (d(C) bsr 2):4>>;
{<<A, B, C, D>>, Tail} ->
Acc2 = <<Acc/binary, (d(A)):6, (d(B)):6, (d(C)):6, (d(D)):6>>,
decode_base64_bin(Tail, Acc2);
_ ->
<<"">>
end.
d(X) when X >= $A, X =< $Z -> X - 65;
d(X) when X >= $a, X =< $z -> X - 71;
d(X) when X >= $0, X =< $9 -> X + 4;
d($+) -> 62;
d($/) -> 63;
d(_) -> 63.
%% Convert Erlang inet IP to list
-spec encode_base64(binary()) -> binary().
encode_base64(Data) ->
encode_base64_bin(Data, <<>>).
encode_base64_bin(<<A:6, B:6, C:6, D:6, Tail/binary>>, Acc) ->
encode_base64_bin(Tail, <<Acc/binary, (e(A)):8, (e(B)):8, (e(C)):8, (e(D)):8>>);
encode_base64_bin(<<A:6, B:6, C:4>>, Acc) ->
<<Acc/binary, (e(A)):8, (e(B)):8, (e(C bsl 2)):8, $=>>;
encode_base64_bin(<<A:6, B:2>>, Acc) ->
<<Acc/binary, (e(A)):8, (e(B bsl 4)):8, $=, $=>>;
encode_base64_bin(<<>>, Acc) ->
Acc.
e(X) when X >= 0, X < 26 -> X + 65;
e(X) when X > 25, X < 52 -> X + 71;
e(X) when X > 51, X < 62 -> X - 4;
e(62) -> $+;
e(63) -> $/;
e(X) -> exit({bad_encode_base64_token, X}).
base64:encode(Data).
-spec ip_to_list(inet:ip_address() | undefined |
{inet:ip_address(), inet:port_number()}) -> binary().
@ -156,7 +102,7 @@ hex_to_bin([H1, H2 | T], Acc) ->
-spec hex_to_base64(binary()) -> binary().
hex_to_base64(Hex) ->
encode_base64(hex_to_bin(Hex)).
base64:encode(hex_to_bin(Hex)).
-spec expand_keyword(binary(), binary(), binary()) -> binary().
expand_keyword(Keyword, Input, Replacement) ->
@ -241,6 +187,30 @@ compile_exprs(Mod, Exprs) ->
join_atoms(Atoms, Sep) ->
str:join([io_lib:format("~p", [A]) || A <- Atoms], Sep).
%% @doc Checks if the file is readable and converts its name to binary.
%% Fails with `badarg` otherwise. The function is intended for usage
%% in configuration validators only.
-spec try_read_file(file:filename_all()) -> binary().
try_read_file(Path) ->
Res = case file:read_file_info(Path) of
{ok, #file_info{type = Type, access = Access}} ->
case {Type, Access} of
{regular, read} -> ok;
{regular, read_write} -> ok;
{regular, _} -> {error, file:format_error(eaccess)};
_ -> {error, "not a regular file"}
end;
{error, Why} ->
{error, file:format_error(Why)}
end,
case Res of
ok ->
iolist_to_binary(Path);
{error, Reason} ->
?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]),
erlang:error(badarg)
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -36,7 +36,7 @@
% Commands API
-export([
% Adminsys
compile/1, get_cookie/0, export2sql/2,
compile/1, get_cookie/0,
restart_module/2,
% Sessions
@ -148,15 +148,6 @@ get_commands_spec() ->
result = {cookie, string},
result_example = "MWTAVMODFELNLSMYXPPD",
result_desc = "Erlang cookie used for authentication by ejabberd"},
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export Mnesia tables to files in directory",
module = ?MODULE, function = export2sql,
args = [{host, string}, {path, string}],
args_example = ["myserver.com","/tmp/export/sql"],
args_desc = ["Server name", "File to write sql export"],
result = {res, rescode},
result_example = ok,
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = restart_module, tags = [erlang],
desc = "Stop an ejabberd module, reload code and start",
module = ?MODULE, function = restart_module,
@ -183,9 +174,9 @@ get_commands_spec() ->
desc = "Delete users that didn't log in last days, or that never logged",
longdesc = "To protect admin accounts, configure this for example:\n"
"access_rules:\n"
" delete_old_users:\n"
" - deny: admin\n"
" - allow: all\n",
" protect_old_users:\n"
" - allow: admin\n"
" - deny: all\n",
module = ?MODULE, function = delete_old_users,
args = [{days, integer}],
args_example = [30],
@ -210,7 +201,7 @@ get_commands_spec() ->
result_desc = "Result tuple"},
#ejabberd_commands{name = check_account, tags = [accounts],
desc = "Check if an account exists or not",
module = ejabberd_auth, function = is_user_exists,
module = ejabberd_auth, function = user_exists,
args = [{user, binary}, {host, binary}],
args_example = [<<"peter">>, <<"myserver.com">>],
args_desc = ["User name to check", "Server to check"],
@ -528,6 +519,10 @@ get_commands_spec() ->
result = {res, rescode}},
#ejabberd_commands{name = push_roster_all, tags = [roster],
desc = "Push template roster from file to all those users",
longdesc = "The text file must contain an erlang term: a list "
"of tuples with username, servername, group and nick. Example:\n"
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
module = ?MODULE, function = push_roster_all,
args = [{file, binary}],
result = {res, rescode}},
@ -697,23 +692,6 @@ restart_module(Host, Module) when is_atom(Module) ->
end
end.
export2sql(Host, Directory) ->
Tables = [{export_last, last},
{export_offline, offline},
{export_passwd, passwd},
{export_private_storage, private_storage},
{export_roster, roster},
{export_vcard, vcard},
{export_vcard_search, vcard_search}],
Export = fun({TableFun, Table}) ->
Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
Res = (catch ejd2sql:TableFun(Host, Filename)),
io:format(" Result: ~p~n", [Res])
end,
lists:foreach(Export, Tables),
ok.
%%%
%%% Accounts
%%%
@ -810,14 +788,14 @@ histogram([], _Integral, _Current, Count, Hist) ->
delete_old_users(Days) ->
%% Get the list of registered users
Users = ejabberd_auth:dirty_get_registered_users(),
Users = ejabberd_auth:get_users(),
{removed, N, UR} = delete_old_users(Days, Users),
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
delete_old_users_vhost(Host, Days) ->
%% Get the list of registered users
Users = ejabberd_auth:get_vh_registered_users(Host),
Users = ejabberd_auth:get_users(Host),
{removed, N, UR} = delete_old_users(Days, Users),
{ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
@ -839,7 +817,7 @@ delete_old_users(Days, Users) ->
{removed, length(Users_removed), Users_removed}.
delete_or_not(LUser, LServer, TimeStamp_oldest) ->
allow = acl:match_rule(LServer, delete_old_users, jid:make(LUser, LServer)),
deny = acl:match_rule(LServer, protect_old_users, jid:make(LUser, LServer)),
[] = ejabberd_sm:get_user_resources(LUser, LServer),
case mod_last:get_last_info(LUser, LServer) of
{ok, TimeStamp, _Status} ->
@ -1280,12 +1258,12 @@ subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) -
subscribe_roster({Name, Server, Group, Nick}, Roster);
%% Subscribe Name2 to Name1
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
subscribe(Name1, Server1, iolist_to_binary(Name2), iolist_to_binary(Server2),
subscribe(iolist_to_binary(Name1), iolist_to_binary(Server1), iolist_to_binary(Name2), iolist_to_binary(Server2),
iolist_to_binary(Nick2), iolist_to_binary(Group2), <<"both">>, []),
subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
push_alltoall(S, G) ->
Users = ejabberd_auth:get_vh_registered_users(S),
Users = ejabberd_auth:get_users(S),
Users2 = build_list_users(G, Users, []),
subscribe_all(Users2),
ok.
@ -1485,10 +1463,7 @@ privacy_set(Username, Host, QueryS) ->
SubEl = xmpp:decode(QueryEl),
IQ = #iq{type = set, id = <<"push">>, sub_els = [SubEl],
from = From, to = To},
ejabberd_hooks:run_fold(privacy_iq_set,
Host,
{error, xmpp:err_feature_not_implemented()},
[IQ, #userlist{}]),
mod_privacy:process_iq(IQ),
ok.
%%%
@ -1499,14 +1474,14 @@ stats(Name) ->
case Name of
<<"uptimeseconds">> -> trunc(element(1, erlang:statistics(wall_clock))/1000);
<<"processes">> -> length(erlang:processes());
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:get_vh_registered_users_number(Host) + Sum end, 0, ?MYHOSTS);
<<"registeredusers">> -> lists:foldl(fun(Host, Sum) -> ejabberd_auth:count_users(Host) + Sum end, 0, ?MYHOSTS);
<<"onlineusersnode">> -> length(ejabberd_sm:dirty_get_my_sessions_list());
<<"onlineusers">> -> length(ejabberd_sm:dirty_get_sessions_list())
end.
stats(Name, Host) ->
case Name of
<<"registeredusers">> -> ejabberd_auth:get_vh_registered_users_number(Host);
<<"registeredusers">> -> ejabberd_auth:count_users(Host);
<<"onlineusers">> -> length(ejabberd_sm:get_vh_session_list(Host))
end.
@ -1638,7 +1613,7 @@ decide_rip_jid({UName, UServer}, Match_list) ->
Match_list).
user_action(User, Server, Fun, OK) ->
case ejabberd_auth:is_user_exists(User, Server) of
case ejabberd_auth:user_exists(User, Server) of
true ->
case catch Fun() of
OK -> ok;

View File

@ -36,7 +36,7 @@
import_start/2, import/5, announce/1, send_motd/1, disco_identity/5,
disco_features/5, disco_items/5, depends/2,
send_announcement_to_all/3, announce_commands/4,
announce_items/4, mod_opt_type/1]).
announce_items/4, mod_opt_type/1, clean_cache/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
-export([announce_all/1,
@ -57,17 +57,22 @@
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
-callback delete_motd(binary()) -> {atomic, any()}.
-callback get_motd(binary()) -> {ok, xmlel()} | error.
-callback is_motd_user(binary(), binary()) -> boolean().
-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> ok | {error, any()}.
-callback set_motd(binary(), xmlel()) -> ok | {error, any()}.
-callback delete_motd(binary()) -> ok | {error, any()}.
-callback get_motd(binary()) -> {ok, xmlel()} | error | {error, any()}.
-callback is_motd_user(binary(), binary()) -> {ok, boolean()} | {error, any()}.
-callback set_motd_user(binary(), binary()) -> ok | {error, any()}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
-record(state, {host :: binary()}).
-define(NS_ADMINL(Sub), [<<"http:">>, <<"jabber.org">>, <<"protocol">>,
<<"admin">>, <<Sub>>]).
-define(MOTD_CACHE, motd_cache).
tokenize(Node) -> str:tokens(Node, <<"/#">>).
@ -88,7 +93,7 @@ reload(Host, NewOpts, OldOpts) ->
true ->
ok
end,
ok.
init_cache(NewMod, Host, NewOpts).
depends(_Host, _Opts) ->
[{mod_adhoc, hard}].
@ -100,6 +105,7 @@ init([Host, Opts]) ->
process_flag(trap_exit, true),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
@ -633,7 +639,7 @@ announce_all(#message{to = To} = Packet) ->
Dest = jid:make(User, Server),
ejabberd_router:route(
xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
end, ejabberd_auth:get_vh_registered_users(To#jid.lserver)).
end, ejabberd_auth:get_users(To#jid.lserver)).
announce_all_hosts_all(#message{to = To} = Packet) ->
Local = jid:make(To#jid.server),
@ -642,7 +648,7 @@ announce_all_hosts_all(#message{to = To} = Packet) ->
Dest = jid:make(User, Server),
ejabberd_router:route(
xmpp:set_from_to(add_store_hint(Packet), Local, Dest))
end, ejabberd_auth:dirty_get_registered_users()).
end, ejabberd_auth:get_users()).
announce_online(#message{to = To} = Packet) ->
announce_online1(ejabberd_sm:get_vh_session_list(To#jid.lserver),
@ -684,19 +690,19 @@ announce_all_hosts_motd_update(Packet) ->
announce_motd_update(LServer, Packet) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer),
Mod:set_motd(LServer, xmpp:encode(Packet)).
delete_motd(Mod, LServer),
set_motd(Mod, LServer, xmpp:encode(Packet)).
announce_motd_delete(#message{to = To}) ->
LServer = To#jid.lserver,
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer).
delete_motd(Mod, LServer).
announce_all_hosts_motd_delete(_Packet) ->
lists:foreach(
fun(Host) ->
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:delete_motd(Host)
delete_motd(Mod, Host)
end, ?MYHOSTS).
-spec send_motd({presence(), ejabberd_c2s:state()}) -> {presence(), ejabberd_c2s:state()}.
@ -707,16 +713,16 @@ send_motd({#presence{type = available},
#{jid := #jid{luser = LUser, lserver = LServer} = JID}} = Acc)
when LUser /= <<>> ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
case get_motd(Mod, LServer) of
{ok, Packet} ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
Msg ->
case Mod:is_motd_user(LUser, LServer) of
case is_motd_user(Mod, LUser, LServer) of
false ->
Local = jid:make(LServer),
ejabberd_router:route(
xmpp:set_from_to(Msg, Local, JID)),
Mod:set_motd_user(LUser, LServer);
set_motd_user(Mod, LUser, LServer);
true ->
ok
end
@ -724,16 +730,81 @@ send_motd({#presence{type = available},
?ERROR_MSG("failed to decode motd packet ~p: ~s",
[Packet, xmpp:format_error(Why)])
end;
error ->
_ ->
ok
end,
Acc;
send_motd(Acc) ->
Acc.
-spec get_motd(module(), binary()) -> {ok, xmlel()} | error | {error, any()}.
get_motd(Mod, LServer) ->
case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?MOTD_CACHE, {<<"">>, LServer},
fun() -> Mod:get_motd(LServer) end);
false ->
Mod:get_motd(LServer)
end.
-spec set_motd(module(), binary(), xmlel()) -> any().
set_motd(Mod, LServer, XML) ->
case use_cache(Mod, LServer) of
true ->
ets_cache:update(
?MOTD_CACHE, {<<"">>, LServer}, {ok, XML},
fun() -> Mod:set_motd(LServer, XML) end,
cache_nodes(Mod, LServer));
false ->
Mod:set_motd(LServer, XML)
end.
-spec is_motd_user(module(), binary(), binary()) -> boolean().
is_motd_user(Mod, LUser, LServer) ->
Res = case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?MOTD_CACHE, {LUser, LServer},
fun() -> Mod:is_motd_user(LUser, LServer) end);
false ->
Mod:is_motd_user(LUser, LServer)
end,
case Res of
{ok, Bool} -> Bool;
_ -> false
end.
-spec set_motd_user(module(), binary(), binary()) -> any().
set_motd_user(Mod, LUser, LServer) ->
case use_cache(Mod, LServer) of
true ->
ets_cache:update(
?MOTD_CACHE, {LUser, LServer}, {ok, true},
fun() -> Mod:set_motd_user(LUser, LServer) end,
cache_nodes(Mod, LServer));
false ->
Mod:set_motd_user(LUser, LServer)
end.
-spec delete_motd(module(), binary()) -> ok | {error, any()}.
delete_motd(Mod, LServer) ->
case Mod:delete_motd(LServer) of
ok ->
case use_cache(Mod, LServer) of
true ->
ejabberd_cluster:eval_everywhere(
?MODULE, clean_cache, [LServer]);
false ->
ok
end;
Err ->
Err
end.
get_stored_motd(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
case get_motd(Mod, LServer) of
{ok, Packet} ->
try xmpp:decode(Packet, ?NS_CLIENT, [ignore_els]) of
#message{body = Body, subject = Subject} ->
@ -742,7 +813,7 @@ get_stored_motd(LServer) ->
?ERROR_MSG("failed to decode motd packet ~p: ~s",
[Packet, xmpp:format_error(Why)])
end;
error ->
_ ->
{<<>>, <<>>}
end.
@ -775,6 +846,55 @@ route_forbidden_error(Packet) ->
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
ejabberd_router:route_error(Packet, Err).
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
true ->
CacheOpts = cache_opts(Host, Opts),
ets_cache:new(?MOTD_CACHE, CacheOpts);
false ->
ets_cache:delete(?MOTD_CACHE)
end.
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
cache_opts(Host, Opts) ->
MaxSize = gen_mod:get_opt(
cache_size, Opts,
ejabberd_config:cache_size(Host)),
CacheMissed = gen_mod:get_opt(
cache_missed, Opts,
ejabberd_config:cache_missed(Host)),
LifeTime = case gen_mod:get_opt(
cache_life_time, Opts,
ejabberd_config:cache_life_time(Host)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false ->
gen_mod:get_module_opt(
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host))
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, Host) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(Host);
false -> ejabberd_cluster:get_nodes()
end.
-spec clean_cache(binary()) -> non_neg_integer().
clean_cache(LServer) ->
ets_cache:filter(
?MOTD_CACHE,
fun({_, S}, _) -> S /= LServer end).
%%-------------------------------------------------------------------------
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),

View File

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

View File

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

View File

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

View File

@ -57,39 +57,40 @@ filter_packet({#message{} = Msg, State} = Acc) ->
From = xmpp:get_from(Msg),
LFrom = jid:tolower(From),
LBFrom = jid:remove_resource(LFrom),
#{pres_a := PresA,
pres_t := PresT,
pres_f := PresF} = State,
#{pres_a := PresA, jid := JID, lserver := LServer} = State,
case (Msg#message.body == [] andalso
Msg#message.subject == [])
orelse ejabberd_router:is_my_route(From#jid.lserver)
orelse (?SETS):is_element(LFrom, PresA)
orelse (?SETS):is_element(LBFrom, PresA)
orelse sets_bare_member(LBFrom, PresA)
orelse (?SETS):is_element(LFrom, PresT)
orelse (?SETS):is_element(LBFrom, PresT)
orelse (?SETS):is_element(LFrom, PresF)
orelse (?SETS):is_element(LBFrom, PresF) of
true ->
Acc;
orelse sets_bare_member(LBFrom, PresA) of
false ->
#{lserver := LServer} = State,
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if
Log ->
?INFO_MSG("Drop packet: ~s",
[fxml:element_to_binary(
xmpp:encode(Msg, ?NS_CLIENT))]);
true ->
ok
end,
if
Drop ->
{stop, {drop, State}};
true ->
Acc
end
{Sub, _} = ejabberd_hooks:run_fold(
roster_get_jid_info, LServer,
{none, []}, [JID#jid.luser, LServer, From]),
case Sub of
none ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if
Log ->
?INFO_MSG("Drop packet: ~s",
[fxml:element_to_binary(
xmpp:encode(Msg, ?NS_CLIENT))]);
true ->
ok
end,
if
Drop ->
{stop, {drop, State}};
true ->
Acc
end;
_ ->
Acc
end;
true ->
Acc
end;
filter_packet(Acc) ->
Acc.

View File

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

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

View File

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

View File

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

View File

@ -45,17 +45,24 @@
-include("mod_privacy.hrl").
-include("mod_last.hrl").
-define(LAST_CACHE, last_activity_cache).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #last_activity{}) -> ok | pass.
-callback get_last(binary(), binary()) ->
{ok, non_neg_integer(), binary()} | not_found | {error, any()}.
-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> any().
{ok, {non_neg_integer(), binary()}} | error | {error, any()}.
-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> ok | {error, any()}.
-callback remove_user(binary(), binary()) -> any().
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@ -91,6 +98,7 @@ reload(Host, NewOpts, OldOpts) ->
true ->
ok
end,
init_cache(NewMod, Host, NewOpts),
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
{false, IQDisc, _} ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
@ -157,7 +165,10 @@ privacy_check_packet(allow, C2SState,
when T == get; T == set ->
case xmpp:has_subtag(IQ, #last{}) of
true ->
Sub = ejabberd_c2s:get_subscription(From, C2SState),
#jid{luser = LUser, lserver = LServer} = To,
{Sub, _} = ejabberd_hooks:run_fold(
roster_get_jid_info, LServer,
{none, []}, [LUser, LServer, From]),
if Sub == from; Sub == both ->
Pres = #presence{from = To, to = From},
case ejabberd_hooks:run_fold(
@ -177,13 +188,23 @@ privacy_check_packet(allow, C2SState,
privacy_check_packet(Acc, _, _, _) ->
Acc.
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found | {error, any()}.
get_last(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_last(LUser, LServer).
Res = case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?LAST_CACHE, {LUser, LServer},
fun() -> Mod:get_last(LUser, LServer) end);
false ->
Mod:get_last(LUser, LServer)
end,
case Res of
{ok, {TimeStamp, Status}} -> {ok, TimeStamp, Status};
error -> not_found;
Err -> Err
end.
-spec get_last_iq(iq(), binary(), binary()) -> iq().
get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
@ -223,7 +244,16 @@ store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_last_info(LUser, LServer, TimeStamp, Status).
case use_cache(Mod, LServer) of
true ->
ets_cache:update(
?LAST_CACHE, {LUser, LServer}, {ok, {TimeStamp, Status}},
fun() ->
Mod:store_last_info(LUser, LServer, TimeStamp, Status)
end, cache_nodes(Mod, LServer));
false ->
Mod:store_last_info(LUser, LServer, TimeStamp, Status)
end.
-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found.
@ -238,7 +268,51 @@ remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
Mod:remove_user(LUser, LServer),
ets_cache:delete(?LAST_CACHE, {LUser, LServer}, cache_nodes(Mod, LServer)).
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
true ->
CacheOpts = cache_opts(Host, Opts),
ets_cache:new(?LAST_CACHE, CacheOpts);
false ->
ets_cache:delete(?LAST_CACHE)
end.
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
cache_opts(Host, Opts) ->
MaxSize = gen_mod:get_opt(
cache_size, Opts,
ejabberd_config:cache_size(Host)),
CacheMissed = gen_mod:get_opt(
cache_missed, Opts,
ejabberd_config:cache_missed(Host)),
LifeTime = case gen_mod:get_opt(
cache_life_time, Opts,
ejabberd_config:cache_life_time(Host)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false ->
gen_mod:get_module_opt(
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host))
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, Host) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(Host);
false -> ejabberd_cluster:get_nodes()
end.
import_info() ->
[{<<"last">>, 3}].
@ -267,4 +341,11 @@ depends(_Host, _Opts) ->
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [db_type, iqdisc].
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[db_type, iqdisc, cache_life_time, cache_size, use_cache, cache_missed].

View File

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

View File

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

View File

@ -43,22 +43,37 @@ init(_Host, _Opts) ->
ok.
get_last(LUser, LServer) ->
case catch sql_queries:get_last(LServer, LUser) of
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(seconds)d, @(state)s from last"
" where username=%(LUser)s")) of
{selected, []} ->
not_found;
error;
{selected, [{TimeStamp, Status}]} ->
{ok, TimeStamp, Status};
{ok, {TimeStamp, Status}};
Reason ->
?ERROR_MSG("failed to get last for user ~s@~s: ~p",
[LUser, LServer, Reason]),
{error, {invalid_result, Reason}}
{error, db_failure}
end.
store_last_info(LUser, LServer, TimeStamp, Status) ->
sql_queries:set_last_t(LServer, LUser, TimeStamp, Status).
case ?SQL_UPSERT(LServer, "last",
["!username=%(LUser)s",
"seconds=%(TimeStamp)d",
"state=%(Status)s"]) of
ok ->
ok;
Err ->
?ERROR_MSG("failed to store last activity for ~s@~s: ~p",
[LUser, LServer, Err]),
{error, db_failure}
end.
remove_user(LUser, LServer) ->
sql_queries:del_last(LServer, LUser).
ejabberd_sql:sql_query(
LServer,
?SQL("delete from last where username=%(LUser)s")).
export(_Server) ->
[{last_activity,

View File

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

View File

@ -30,7 +30,7 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6]).
extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/6, export/1]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@ -116,7 +116,7 @@ write_prefs(LUser, _LServer, #archive_prefs{default = Default,
"def=%(SDefault)s",
"always=%(SAlways)s",
"never=%(SNever)s"]) of
{updated, _} ->
ok ->
ok;
Err ->
Err
@ -181,6 +181,47 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
{[], false, 0}
end.
export(_Server) ->
[{archive_prefs,
fun(Host, #archive_prefs{us =
{LUser, LServer},
default = Default,
always = Always,
never = Never})
when LServer == Host ->
SDefault = erlang:atom_to_binary(Default, utf8),
SAlways = misc:term_to_expr(Always),
SNever = misc:term_to_expr(Never),
[?SQL("insert into archive_prefs (username, def, always, never) values"
"(%(LUser)s, %(SDefault)s, %(SAlways)s, %(SNever)s);")];
(_Host, _R) ->
[]
end},
{archive_msg,
fun(Host, #archive_msg{us ={_LUser, LServer},
id = _ID, timestamp = TS, peer = Peer,
bare_peer = {PUser, PServer, <<>>},
type = Type, nick = Nick, packet = Pkt})
when LServer == Host ->
TStmp = now_to_usec(TS),
SUser = case Type of
chat -> PUser;
groupchat -> jid:encode({PUser, PServer, <<>>})
end,
BarePeer = jid:encode(jid:tolower(jid:remove_resource(Peer))),
LPeer = jid:encode(jid:tolower(Peer)),
XML = fxml:element_to_binary(Pkt),
Body = fxml:get_subtag_cdata(Pkt, <<"body">>),
SType = misc:atom_to_binary(Type),
[?SQL("insert into archive (username, timestamp, "
"peer, bare_peer, xml, txt, kind, nick) "
"values (%(SUser)s, %(TStmp)d, %(LPeer)s, "
"%(BarePeer)s, %(XML)s, %(Body)s, %(SType)s, "
"%(Nick)s);")];
(_Host, _R) ->
[]
end}].
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@
-behaviour(mod_offline).
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
-export([init/2, store_message/1, pop_messages/2, remove_expired_messages/1,
remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2,
remove_all_messages/2, count_messages/2, import/1]).
@ -36,8 +36,6 @@
-include("mod_offline.hrl").
-include("logger.hrl").
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
%%%===================================================================
%%% API
%%%===================================================================
@ -46,26 +44,9 @@ init(_Host, _Opts) ->
[{disc_only_copies, [node()]}, {type, bag},
{attributes, record_info(fields, offline_msg)}]).
store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) ->
F = fun () ->
Count = if MaxOfflineMsgs =/= infinity ->
Len + count_mnesia_records(US);
true -> 0
end,
if Count > MaxOfflineMsgs -> discard;
true ->
if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) ->
mnesia:write_lock_table(offline_msg);
true -> ok
end,
lists:foreach(
fun(#offline_msg{packet = Pkt} = M) ->
El = xmpp:encode(Pkt),
mnesia:write(M#offline_msg{packet = El})
end, Msgs)
end
end,
mnesia:transaction(F).
store_message(#offline_msg{packet = Pkt} = OffMsg) ->
El = xmpp:encode(Pkt),
mnesia:dirty_write(OffMsg#offline_msg{packet = El}).
pop_messages(LUser, LServer) ->
US = {LUser, LServer},

View File

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

View File

@ -28,7 +28,7 @@
-behaviour(mod_offline).
-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1,
-export([init/2, store_message/1, pop_messages/2, remove_expired_messages/1,
remove_old_messages/2, remove_user/2, read_message_headers/2,
read_message/3, remove_message/3, read_all_messages/2,
remove_all_messages/2, count_messages/2, import/1, export/1]).
@ -44,34 +44,28 @@
init(_Host, _Opts) ->
ok.
store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) ->
Count = if MaxOfflineMsgs =/= infinity ->
Len + count_messages(User, Host);
true -> 0
end,
if Count > MaxOfflineMsgs -> {atomic, discard};
true ->
Query = lists:map(
fun(M) ->
LUser = (M#offline_msg.to)#jid.luser,
From = M#offline_msg.from,
To = M#offline_msg.to,
Packet = xmpp:set_from_to(
M#offline_msg.packet, From, To),
NewPacket = xmpp_util:add_delay_info(
Packet, jid:make(Host),
M#offline_msg.timestamp,
<<"Offline Storage">>),
XML = fxml:element_to_binary(
xmpp:encode(NewPacket)),
sql_queries:add_spool_sql(LUser, XML)
end,
Msgs),
sql_queries:add_spool(Host, Query)
store_message(#offline_msg{us = {LUser, LServer}} = M) ->
From = M#offline_msg.from,
To = M#offline_msg.to,
Packet = xmpp:set_from_to(M#offline_msg.packet, From, To),
NewPacket = xmpp_util:add_delay_info(
Packet, jid:make(LServer),
M#offline_msg.timestamp,
<<"Offline Storage">>),
XML = fxml:element_to_binary(
xmpp:encode(NewPacket)),
case ejabberd_sql:sql_query(
LServer,
?SQL("insert into spool(username, xml) values "
"(%(LUser)s, %(XML)s)")) of
{updated, _} ->
ok;
_ ->
{error, db_failure}
end.
pop_messages(LUser, LServer) ->
case sql_queries:get_and_del_spool_msg_t(LServer, LUser) of
case get_and_del_spool_msg_t(LServer, LUser) of
{atomic, {selected, Rs}} ->
{ok, lists:flatmap(
fun({_, XML}) ->
@ -91,12 +85,12 @@ remove_expired_messages(_LServer) ->
{atomic, ok}.
remove_old_messages(Days, LServer) ->
case catch ejabberd_sql:sql_query(
LServer,
[<<"DELETE FROM spool"
" WHERE created_at < "
"NOW() - INTERVAL '">>,
integer_to_list(Days), <<"' DAY;">>]) of
case ejabberd_sql:sql_query(
LServer,
[<<"DELETE FROM spool"
" WHERE created_at < "
"NOW() - INTERVAL '">>,
integer_to_list(Days), <<"' DAY;">>]) of
{updated, N} ->
?INFO_MSG("~p message(s) deleted from offline spool", [N]);
_Error ->
@ -105,13 +99,15 @@ remove_old_messages(Days, LServer) ->
{atomic, ok}.
remove_user(LUser, LServer) ->
sql_queries:del_spool_msg(LServer, LUser).
ejabberd_sql:sql_query(
LServer,
?SQL("delete from spool where username=%(LUser)s")).
read_message_headers(LUser, LServer) ->
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(xml)s, @(seq)d from spool"
" where username=%(LUser)s order by seq")) of
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(xml)s, @(seq)d from spool"
" where username=%(LUser)s order by seq")) of
{selected, Rows} ->
lists:flatmap(
fun({XML, Seq}) ->
@ -153,10 +149,10 @@ remove_message(LUser, LServer, Seq) ->
ok.
read_all_messages(LUser, LServer) ->
case catch ejabberd_sql:sql_query(
LServer,
?SQL("select @(xml)s from spool where "
"username=%(LUser)s order by seq")) of
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(xml)s from spool where "
"username=%(LUser)s order by seq")) of
{selected, Rs} ->
lists:flatmap(
fun({XML}) ->
@ -170,7 +166,7 @@ read_all_messages(LUser, LServer) ->
end.
remove_all_messages(LUser, LServer) ->
sql_queries:del_spool_msg(LServer, LUser),
remove_user(LUser, LServer),
{atomic, ok}.
count_messages(LUser, LServer) ->
@ -241,3 +237,15 @@ el_to_offline_msg(El) ->
?ERROR_MSG("failed to get 'from' JID from offline XML ~p", [El]),
{error, bad_jid_from}
end.
get_and_del_spool_msg_t(LServer, LUser) ->
F = fun () ->
Result =
ejabberd_sql:sql_query_t(
?SQL("select @(username)s, @(xml)s from spool where "
"username=%(LUser)s order by seq;")),
ejabberd_sql:sql_query_t(
?SQL("delete from spool where username=%(LUser)s;")),
Result
end,
ejabberd_sql:sql_transaction(LServer, F).

View File

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

View File

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

View File

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

View File

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

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