From 404a7c33819aee11ad9754803de24e4f7067adef Mon Sep 17 00:00:00 2001 From: Johan Oudinet Date: Mon, 21 Nov 2016 15:25:58 +0100 Subject: [PATCH 01/91] Remove bashismes from ejabberdctl To avoid unecessary extra quoting, do not call commands with sh -c '' and use the -- option from su to supply arguments. Parse command line parameters is a bit tricky as the previous behavior allows to mix options to ejabberdctl with unknown options given to the next script (usually, the ctl). This is solved by relying on the fact that for loop saves its argument, so we can flush its content with set -- and re-add unknown options with set -- "$@" "$arg". Finally, remove unecessary quotes in mnesia options and in the ping command. --- ejabberdctl.template | 191 +++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 97 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 5b34ebee4..64fed558b 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # define default configuration POLL=true @@ -27,35 +27,59 @@ if [ "$INSTALLUSER" != "" ] ; then mkdir -p "$INSTALLUSER_HOME" chown "$INSTALLUSER" "$INSTALLUSER_HOME" fi - EXEC_CMD="su $INSTALLUSER -c" + EXEC_CMD="as_install_user" fi done if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then - EXEC_CMD="bash -c" + EXEC_CMD="as_current_user" fi if [ "$EXEC_CMD" = "false" ] ; then echo "This command can only be run by root or the user $INSTALLUSER" >&2 exit 4 fi else - EXEC_CMD="bash -c" + EXEC_CMD="as_current_user" fi +# run command either directly or via su $INSTALLUSER +exec_cmd() +{ + if [ "EXEC_CMD" = as_install_user ]; then + su -c '"$0" $@"' "INSTALLUSER" -- "$@" + else + "$@" + 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") ;; +next=init +for arg; do + # Empty argument list as it is already saved in the for buffer + if [ "$next" = init ]; then + next= + set -- + fi + case $next in + node) ERLANG_NODE_ARG=$arg; next=;; + config-dir) ETC_DIR=$arg; next=;; + config) EJABBERD_CONFIG_PATH=$arg; next=;; + ctl-config) EJABBERDCTL_CONFIG_PATH=$arg; next=;; + logs) LOGS_DIR=$arg; next=;; + spool) SPOOL_DIR=$arg; next=;; + "") + case $arg in + --) next=raw;; + --no-timeout) EJABBERD_NO_TIMEOUT="--no-timeout" ;; + --node) next=node;; + --config-dir) next=config-dir;; + --config) next=config;; + --ctl-config) next=ctl-config;; + --logs) next=logs;; + --spool) next=spool;; + *) set -- "$@" "$arg";; # unknown option, keep it. + esac;; + raw) # we are after --, keep options as it is. + set -- "$@" "$arg";; esac done @@ -93,7 +117,7 @@ ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump ERL_INETRC=$ETC_DIR/inetrc # define mnesia options -MNESIA_OPTS="-mnesia dir \"\\\"$SPOOL_DIR\\\"\" $MNESIA_OPTIONS" +MNESIA_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS" # define erl parameters ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS" KERNEL_OPTS="" @@ -150,40 +174,20 @@ export CONTRIB_MODULES_PATH export CONTRIB_MODULES_CONF_DIR export ERL_LIBS -shell_escape_str() -{ - 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 -} - +# TODO: Too much copy-and-paste below, factorize! # 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" + exec_cmd $ERL \ + $NAME $ERLANG_NODE \ + -noinput -detached \ + $MNESIA_OPTS \ + $KERNEL_OPTS \ + $EJABBERD_OPTS \ + -s ejabberd \ + $ERLANG_OPTS \ + "$@" } # attach to server @@ -191,13 +195,12 @@ 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" + exec_cmd $ERL $NAME $NID \ + -remsh $ERLANG_NODE \ + -hidden \ + $KERNEL_OPTS \ + $ERLANG_OPTS \ + "$@" } # attach to server using Elixir @@ -206,27 +209,24 @@ 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" + exec_cmd $IEX $IEXNAME $NID \ + -remsh "$ERLANG_NODE" \ + --erl "$KERNEL_OPTS" \ + --erl "$ERLANG_OPTS" \ + --erl "$@" } # 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" + exec_cmd $ERL $NAME $ERLANG_NODE \ + $MNESIA_OPTS \ + $KERNEL_OPTS \ + $EJABBERD_OPTS \ + -s ejabberd \ + $ERLANG_OPTS \ + "$@" } # start interactive server with Elixir @@ -234,30 +234,27 @@ 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\"`\" \ + exec_cmd $IEX $IEXNAME $ERLANG_NODE \ + --erl "-mnesia dir \"$SPOOL_DIR\"" \ + --erl "$KERNEL_OPTS" \ + --erl "$EJABBERD_OPTS" \ --app ejabberd \ - --erl `shell_escape \"$ERLANG_OPTS\"` \ - --erl `shell_escape \"${ARGS[@]}\"` \ - --erl `shell_escape_str \"$@\"`" - $EXEC_CMD "ERL_PATH=\"$ERL\" $CMD" + --erl "$ERLANG_OPTS" \ + --erl "$@" } # start server in the foreground foreground() { check_start - CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \ + exec_cmd $ERL $NAME $ERLANG_NODE \ -noinput \ $MNESIA_OPTS \ $KERNEL_OPTS \ $EJABBERD_OPTS \ -s ejabberd \ $ERLANG_OPTS \ - `shell_escape \"${ARGS[@]}\" \"$@\"`" - $EXEC_CMD "$CMD" + "$@" } debugwarning() @@ -311,9 +308,9 @@ livewarning() etop() { NID=$(uid top) - $EXEC_CMD "$ERL \ + exec_cmd $ERL \ $NAME $NID \ - -hidden -s etop -s erlang halt -output text -node $ERLANG_NODE" + -hidden -s etop -s erlang halt -output text -node $ERLANG_NODE } ping() @@ -327,11 +324,11 @@ ping() 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" + 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() @@ -359,11 +356,10 @@ help() ctl() { NID=$(uid ctl) - CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \ + exec_cmd $ERL $NAME $NID \ -noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \ - -extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \ - `shell_escape \"$@\"`" - $EXEC_CMD "$CMD" + -extra $ERLANG_NODE $EJABBERD_NO_TIMEOUT \ + "$@" result=$? case $result in 2) help;; @@ -376,7 +372,8 @@ ctl() uid() { uuid=$(uuidgen 2>/dev/null) - [ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$( Date: Mon, 8 May 2017 12:59:28 +0300 Subject: [PATCH 02/91] Add type specs for Module:opt_type/1 --- src/cyrsasl_digest.erl | 2 ++ src/ejabberd_app.erl | 3 +++ src/ejabberd_auth.erl | 3 +++ src/ejabberd_auth_anonymous.erl | 4 ++++ src/ejabberd_auth_external.erl | 4 ++++ src/ejabberd_auth_ldap.erl | 4 ++++ src/ejabberd_auth_pam.erl | 3 +++ src/ejabberd_c2s.erl | 29 ++++++++++++++++++++++++++++- src/ejabberd_captcha.erl | 4 ++++ src/ejabberd_commands.erl | 3 +++ src/ejabberd_config.erl | 17 +++++++++++++++++ src/ejabberd_ctl.erl | 2 ++ src/ejabberd_http.erl | 19 +++++++++++++++++++ src/ejabberd_oauth.erl | 8 ++++++++ src/ejabberd_oauth_rest.erl | 2 ++ src/ejabberd_rdbms.erl | 3 +++ src/ejabberd_receiver.erl | 3 +++ src/ejabberd_redis_sup.erl | 8 ++++++++ src/ejabberd_riak_sup.erl | 8 ++++++++ src/ejabberd_router.erl | 10 ++++++++++ src/ejabberd_s2s.erl | 14 ++++++++++++++ src/ejabberd_s2s_in.erl | 22 +++++++++++++++------- src/ejabberd_s2s_out.erl | 9 ++++++++- src/ejabberd_service.erl | 23 ++++++++++++++++++----- src/ejabberd_sm.erl | 6 ++++++ src/ejabberd_sql.erl | 14 ++++++++++++++ src/ejabberd_sql_sup.erl | 3 +++ src/ejabberd_system_monitor.erl | 3 +++ src/ejabberd_web_admin.erl | 2 ++ src/eldap_utils.erl | 23 ++++++++++++++++++----- src/ext_mod.erl | 2 ++ src/extauth.erl | 2 ++ src/gen_iq_handler.erl | 2 ++ src/gen_mod.erl | 2 ++ src/mod_metrics.erl | 6 +----- src/mod_register.erl | 2 ++ src/mod_vcard_ldap.erl | 11 +---------- src/rest.erl | 3 +++ src/shaper.erl | 2 ++ src/sql_queries.erl | 2 ++ 40 files changed, 258 insertions(+), 34 deletions(-) diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 9d23271d1..5bce53a02 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -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]; diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index b23249aa0..b52450d24 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -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) -> diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 756fafcf6..9751142a5 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -534,6 +534,9 @@ import(Server, {sql, _}, riak, <<"users">>, Fields) -> import(_LServer, {sql, _}, sql, <<"users">>, _) -> ok. +-spec opt_type(auth_method) -> fun((atom() | [atom()]) -> [atom()]); + (auth_password_format) -> fun((plain | scram) -> plain | scram); + (atom()) -> [atom()]. opt_type(auth_method) -> fun (V) when is_list(V) -> lists:map(fun(M) -> ejabberd_config:v_db(?MODULE, M) end, V); diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index a75af709e..5bb2daed7 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -236,6 +236,10 @@ plain_password_required() -> false. store_type() -> plain. +-spec opt_type(allow_multiple_connection) -> fun((boolean()) -> boolean()); + (anonymous_protocol) -> fun((sasl_anon | login_anon | both) -> + sasl_anon | login_anon | both); + (atom()) -> [atom()]. opt_type(allow_multiple_connections) -> fun (V) when is_boolean(V) -> V end; opt_type(anonymous_protocol) -> diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 8ba2e2b22..5bdd704a0 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -313,6 +313,10 @@ 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) -> fun (false) -> false; (I) when is_integer(I), I >= 0 -> I diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 53c256490..8a4532e38 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -393,6 +393,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 diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index 974cc8e43..9d2fc819b 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -117,6 +117,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; diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index fae75a4d2..9af58a080 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -894,6 +894,16 @@ format_reason(_, _) -> transform_listen_option(Opt, Opts) -> [Opt|Opts]. +-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 iolist_to_binary/1; opt_type(c2s_ciphers) -> fun iolist_to_binary/1; opt_type(c2s_dhfile) -> fun iolist_to_binary/1; @@ -920,6 +930,23 @@ 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); @@ -935,7 +962,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; diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl index 0a8dc0261..48e4ac1e6 100644 --- a/src/ejabberd_captcha.erl +++ b/src/ejabberd_captcha.erl @@ -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 diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index c8822032c..eccb0d621 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -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; diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 1ad76caf7..158ef86a7 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -1362,6 +1362,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) -> diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index a9643386d..87f2f87f1 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -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]. diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index b706215a9..2dd81854e 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -899,11 +899,30 @@ 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) -> diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index 455db85a5..af8680ef4 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -767,6 +767,14 @@ css() -> logo() -> <<"data:image/png;base64,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">>. +-spec opt_type(oauth_expire) -> fun((non_neg_integer()) -> non_neg_integer()); + (oauth_access) -> fun((any()) -> any()); + (oauth_db_type) -> fun((atom()) -> atom()); + (oauth_cache_life_time) -> fun((timeout()) -> timeout()); + (oauth_cache_size) -> fun((timeout()) -> timeout()); + (oauth_use_cache) -> fun((boolean()) -> boolean()); + (oauth_cache_misse) -> fun((boolean()) -> boolean()); + (atom()) -> [atom()]. opt_type(oauth_expire) -> fun(I) when is_integer(I), I >= 0 -> I end; opt_type(oauth_access) -> diff --git a/src/ejabberd_oauth_rest.erl b/src/ejabberd_oauth_rest.erl index 6c5b30523..404edd1fd 100644 --- a/src/ejabberd_oauth_rest.erl +++ b/src/ejabberd_oauth_rest.erl @@ -93,6 +93,8 @@ path(Path) -> <>. +-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]. diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index 7021c10b7..14bd968d2 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -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; diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 38ad70eb3..24eb92fad 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -348,6 +348,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 diff --git a/src/ejabberd_redis_sup.erl b/src/ejabberd_redis_sup.erl index 85cfd7abb..ff1cc0e12 100644 --- a/src/ejabberd_redis_sup.erl +++ b/src/ejabberd_redis_sup.erl @@ -133,6 +133,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) -> diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl index 3d754f6d6..30be65c4f 100644 --- a/src/ejabberd_riak_sup.erl +++ b/src/ejabberd_riak_sup.erl @@ -179,6 +179,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) -> diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index 490133d79..844651329 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -476,6 +476,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; diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index b6685c12e..a447dcd67 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -689,6 +689,20 @@ 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 diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index b1d0ae215..90e0feb27 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -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]). @@ -344,9 +343,18 @@ 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(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers); @@ -357,7 +365,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; diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index deff3e5b5..bcacd8e77 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -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); diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 0dcf74ab0..6fae16940 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -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, @@ -259,8 +258,22 @@ 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; diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index ce2abfff0..92c6bb94f 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -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; diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 4c85b874a..dc6292ce4 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -1080,6 +1080,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; diff --git a/src/ejabberd_sql_sup.erl b/src/ejabberd_sql_sup.erl index 7526aedbe..f7793eb2c 100644 --- a/src/ejabberd_sql_sup.erl +++ b/src/ejabberd_sql_sup.erl @@ -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) -> diff --git a/src/ejabberd_system_monitor.erl b/src/ejabberd_system_monitor.erl index 881bfa635..773104f9e 100644 --- a/src/ejabberd_system_monitor.erl +++ b/src/ejabberd_system_monitor.erl @@ -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))) diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index 0106bb9cd..5050ac095 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -2981,6 +2981,8 @@ make_menu_item(item, 3, URI, Name, Lang) -> %%%================================== +-spec opt_type(access_readonly) -> fun((any()) -> any()); + (atom()) -> [atom()]. opt_type(access_readonly) -> fun acl:access_rules_validator/1; opt_type(_) -> [access_readonly]. diff --git a/src/eldap_utils.erl b/src/eldap_utils.erl index d15e5bc9c..52671061d 100644 --- a/src/eldap_utils.erl +++ b/src/eldap_utils.erl @@ -318,12 +318,25 @@ collect_parts_bit([{?N_BIT_STRING,<>}|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_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; diff --git a/src/ext_mod.erl b/src/ext_mod.erl index 15fe8e17f..c5ce6874f 100644 --- a/src/ext_mod.erl +++ b/src/ext_mod.erl @@ -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; diff --git a/src/extauth.erl b/src/extauth.erl index c6f25102c..54f44953c 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -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]. diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index aaa7861dc..6ca706f63 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -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]. diff --git a/src/gen_mod.erl b/src/gen_mod.erl index fdfd94f2a..27d241c9c 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -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( diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index 9a3adc2d7..ecae7cd1b 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -25,7 +25,6 @@ -module(mod_metrics). --behaviour(ejabberd_config). -author('christophe.romain@process-one.net'). -behaviour(gen_mod). @@ -33,7 +32,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). --export([start/2, stop/1, send_metrics/4, opt_type/1, mod_opt_type/1, +-export([start/2, stop/1, send_metrics/4, mod_opt_type/1, depends/2, reload/3]). -export([offline_message_hook/1, @@ -157,8 +156,5 @@ send_metrics(Host, Probe, Peer, Port) -> ?WARNING_MSG("can not open udp socket to grapherl: ~p", [Error]) end. -opt_type(_) -> - []. - mod_opt_type(_) -> []. diff --git a/src/mod_register.erl b/src/mod_register.erl index 893a16c7a..af4ba02f4 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -612,6 +612,8 @@ mod_opt_type(_) -> iqdisc, password_strength, registration_watchers, {welcome_message, subject}, {welcome_message, body}]. +-spec opt_type(registration_timeout) -> fun((timeout()) -> timeout()); + (atom()) -> [atom()]. opt_type(registration_timeout) -> fun (TO) when is_integer(TO), TO > 0 -> TO; (infinity) -> infinity; diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index 28dda7af6..f97eb18a9 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -24,8 +24,6 @@ -module(mod_vcard_ldap). --behaviour(ejabberd_config). - -behaviour(gen_server). -behaviour(mod_vcard). @@ -33,7 +31,7 @@ -export([start_link/2]). -export([init/2, stop/1, get_vcard/2, set_vcard/4, search/4, remove_user/2, import/3, search_fields/1, search_reported/1, - mod_opt_type/1, opt_type/1]). + mod_opt_type/1]). -export([is_search_supported/1]). %% gen_server callbacks @@ -487,10 +485,3 @@ mod_opt_type(_) -> ldap_port, ldap_rootdn, ldap_servers, ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth, ldap_tls_verify]. - -opt_type(_) -> - [deref_aliases, ldap_backups, ldap_base, - ldap_deref_aliases, ldap_encrypt, ldap_password, - ldap_port, ldap_rootdn, ldap_servers, - ldap_tls_cacertfile, ldap_tls_certfile, ldap_tls_depth, - ldap_tls_verify]. diff --git a/src/rest.erl b/src/rest.erl index a3fb0eef2..833a498ed 100644 --- a/src/rest.erl +++ b/src/rest.erl @@ -177,6 +177,9 @@ url(Server, Path, Params) -> Tail = iolist_to_binary([ParHead | ParTail]), binary_to_list(<>). +-spec opt_type(ext_api_http_pool_size) -> fun((pos_integer()) -> pos_integer()); + (ext_api_url) -> fun((binary()) -> binary()); + (atom()) -> [atom()]. opt_type(ext_api_http_pool_size) -> fun (X) when is_integer(X), X > 0 -> X end; opt_type(ext_api_url) -> diff --git a/src/shaper.erl b/src/shaper.erl index ac1ecc36f..803d235a0 100644 --- a/src/shaper.erl +++ b/src/shaper.erl @@ -168,5 +168,7 @@ transform_options({OptName, Name, none}, Opts) when OptName == shaper -> transform_options(Opt, Opts) -> [Opt|Opts]. +-spec opt_type(shaper) -> fun((any()) -> any()); + (atom()) -> [atom()]. opt_type(shaper) -> fun (V) -> V end; opt_type(_) -> [shaper]. diff --git a/src/sql_queries.erl b/src/sql_queries.erl index 5d5729102..83cbd7b73 100644 --- a/src/sql_queries.erl +++ b/src/sql_queries.erl @@ -632,6 +632,8 @@ set_roster_version(LUser, Version) -> ["!username=%(LUser)s", "version=%(Version)s"]). +-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]. From 8368a0850ab077dc9317dc3b3046a0c28c16748d Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 8 May 2017 14:34:35 +0300 Subject: [PATCH 03/91] Don't call gen_mod:get_opt() outside of modules --- src/ejabberd_c2s.erl | 4 ++-- src/ejabberd_http.erl | 6 +++--- src/ejabberd_s2s_in.erl | 2 +- src/ejabberd_service.erl | 10 +++++----- src/ejabberd_stun.erl | 6 +++--- src/ejabberd_xmlrpc.erl | 2 +- src/eldap.erl | 8 ++++---- src/eldap_utils.erl | 43 ++++++++++++++++++++++++---------------- 8 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 9af58a080..92f2a3bdc 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -490,8 +490,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; diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 2dd81854e..06da10c9c 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -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, diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index 90e0feb27..c215557c4 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -244,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; diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 6fae16940..9829d6b6c 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -79,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; diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl index c45caf686..47a6b89de 100644 --- a/src/ejabberd_stun.erl +++ b/src/ejabberd_stun.erl @@ -73,9 +73,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 " diff --git a/src/ejabberd_xmlrpc.erl b/src/ejabberd_xmlrpc.erl index 41695cb10..213aef7aa 100644 --- a/src/ejabberd_xmlrpc.erl +++ b/src/ejabberd_xmlrpc.erl @@ -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 diff --git a/src/eldap.erl b/src/eldap.erl index 9763e2a79..3c565e71d 100644 --- a/src/eldap.erl +++ b/src/eldap.erl @@ -565,7 +565,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,19 +577,19 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) -> end; PT -> PT end, - CacertOpts = case gen_mod:get_opt(tls_cacertfile, Opts) of + CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of undefined -> []; Path -> [{cacertfile, Path}] 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 " diff --git a/src/eldap_utils.erl b/src/eldap_utils.erl index 52671061d..3704d2ce5 100644 --- a/src/eldap_utils.erl +++ b/src/eldap_utils.erl @@ -173,25 +173,25 @@ 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), + 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 " @@ -211,6 +211,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 %%---------------------------------------- From bf2a2f291f8770598607bbc2f56f325ac5bca035 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 8 May 2017 15:10:42 +0300 Subject: [PATCH 04/91] State that Erlang 17.5 or higher is only supported --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index b0dff0720..67e062267 100644 --- a/README +++ b/README @@ -108,7 +108,7 @@ To compile ejabberd you need: - GCC. - Libexpat 1.95 or higher. - Libyaml 0.1.4 or higher. - - Erlang/OTP 17.1 or higher. + - Erlang/OTP 17.5 or higher. - OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption. - Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional. - PAM library. Optional. For Pluggable Authentication Modules (PAM). From 6b6d07745da368d1649f31d315e97fa4c179523d Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 8 May 2017 16:29:01 +0300 Subject: [PATCH 05/91] Split some functions in smaller ones --- src/ejabberd_config.erl | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 158ef86a7..866cb607c 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -26,6 +26,8 @@ -module(ejabberd_config). -author('alexey@process-one.net'). +-compile(export_all). + -export([start/0, load_file/1, reload_file/0, read_file/1, get_option/1, get_option/2, add_option/2, has_option/1, get_vh_by_auth_method/1, is_file_readable/1, @@ -179,7 +181,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 +976,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 +998,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 From cee90a886e564ee36cdaa96df74dcd336df7e78f Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 8 May 2017 17:22:34 +0300 Subject: [PATCH 06/91] Don't list 'redis_pool_size' option multiple times --- src/ejabberd_redis_sup.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ejabberd_redis_sup.erl b/src/ejabberd_redis_sup.erl index ff1cc0e12..39b7ef798 100644 --- a/src/ejabberd_redis_sup.erl +++ b/src/ejabberd_redis_sup.erl @@ -155,5 +155,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]. From 5d7a704ca5eea6d4075bd7abe19e46207a97c3eb Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 8 May 2017 17:23:29 +0300 Subject: [PATCH 07/91] Remove forgotten 'export_all' --- src/ejabberd_config.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 866cb607c..75f64ecb5 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -26,8 +26,6 @@ -module(ejabberd_config). -author('alexey@process-one.net'). --compile(export_all). - -export([start/0, load_file/1, reload_file/0, read_file/1, get_option/1, get_option/2, add_option/2, has_option/1, get_vh_by_auth_method/1, is_file_readable/1, From a0908ba393ec6ba4e42db7684ea6c251e21be932 Mon Sep 17 00:00:00 2001 From: Lamtei W Date: Sun, 7 May 2017 18:28:11 +0530 Subject: [PATCH 08/91] Added export function for mam module --- src/ejd2sql.erl | 1 + src/mod_mam.erl | 6 +++++- src/mod_mam_sql.erl | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl index c775cd7bf..343c3518a 100644 --- a/src/ejd2sql.erl +++ b/src/ejd2sql.erl @@ -53,6 +53,7 @@ modules() -> mod_caps, mod_irc, mod_last, + mod_mam, mod_muc, mod_offline, mod_privacy, diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 947fb5fbe..c84cb907b 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -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"). @@ -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 %%%=================================================================== diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index d2fd48a9f..7ae3bb87f 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -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"). @@ -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:to_string({PUser, PServer, <<>>}) + end, + BarePeer = jid:to_string(jid:tolower(jid:remove_resource(Peer))), + LPeer = jid:to_string(jid:tolower(Peer)), + XML = fxml:element_to_binary(Pkt), + Body = fxml:get_subtag_cdata(Pkt, <<"body">>), + SType = jlib: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 %%%=================================================================== From cd18d3d8a72a05b20c0f8a9396de8e6ae8bc6dcd Mon Sep 17 00:00:00 2001 From: Lamtei W Date: Mon, 8 May 2017 13:21:12 +0530 Subject: [PATCH 09/91] Fix: update sql statement, added missing delimeter for sql queries --- src/mod_mam_sql.erl | 4 ++-- src/sql_queries.erl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 7ae3bb87f..c1c6b876b 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -193,7 +193,7 @@ export(_Server) -> 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)")]; + "(%(LUser)s, %(SDefault)s, %(SAlways)s, %(SNever)s);")]; (_Host, _R) -> [] end}, @@ -217,7 +217,7 @@ export(_Server) -> "peer, bare_peer, xml, txt, kind, nick) " "values (%(SUser)s, %(TStmp)d, %(LPeer)s, " "%(BarePeer)s, %(XML)s, %(Body)s, %(SType)s, " - "%(Nick)s)")]; + "%(Nick)s);")]; (_Host, _R) -> [] end}]. diff --git a/src/sql_queries.erl b/src/sql_queries.erl index 83cbd7b73..862e4e284 100644 --- a/src/sql_queries.erl +++ b/src/sql_queries.erl @@ -385,7 +385,7 @@ update_roster_sql({LUser, SJID, Name, SSubscription, SAsk, AskMessage}, " username=%(LUser)s and jid=%(SJID)s;")] ++ [?SQL("insert into rostergroups(username, jid, grp) " - "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)") + "values (%(LUser)s, %(SJID)s, %(ItemGroup)s);") || ItemGroup <- ItemGroups]. roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) -> From 4849ac978159096b916745d93fed4d5e99adedde Mon Sep 17 00:00:00 2001 From: Badlop Date: Wed, 10 May 2017 11:17:12 +0200 Subject: [PATCH 10/91] Use jid:encode/1 instead of the deprecated jid:to_string/1 (#1510) --- src/mod_mam_sql.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index c1c6b876b..3f7f2c2b5 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -206,10 +206,10 @@ export(_Server) -> TStmp = now_to_usec(TS), SUser = case Type of chat -> PUser; - groupchat -> jid:to_string({PUser, PServer, <<>>}) + groupchat -> jid:encode({PUser, PServer, <<>>}) end, - BarePeer = jid:to_string(jid:tolower(jid:remove_resource(Peer))), - LPeer = jid:to_string(jid:tolower(Peer)), + 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 = jlib:atom_to_binary(Type), From 6b8c61b3a2151cdb120fbbfaf54f5263cdf7d338 Mon Sep 17 00:00:00 2001 From: Badlop Date: Wed, 10 May 2017 12:03:13 +0200 Subject: [PATCH 11/91] Update comment: aux.erl was renamed to misc.erl --- src/jlib.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jlib.erl b/src/jlib.erl index 1d0b7509c..510d0257e 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -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, From e8905257886423d8332db50b1ea902b0500a33a2 Mon Sep 17 00:00:00 2001 From: Badlop Date: Wed, 10 May 2017 12:05:52 +0200 Subject: [PATCH 12/91] Use misc:atom_to_binary/1 instead of the deprecated jlib.erl (#1510) --- src/mod_mam_sql.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 3f7f2c2b5..83e269328 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -212,7 +212,7 @@ export(_Server) -> LPeer = jid:encode(jid:tolower(Peer)), XML = fxml:element_to_binary(Pkt), Body = fxml:get_subtag_cdata(Pkt, <<"body">>), - SType = jlib:atom_to_binary(Type), + 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, " From 633b68db1130c81551b063f3aa15d599b0d355e5 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 11 May 2017 14:37:21 +0300 Subject: [PATCH 13/91] Use cache for authentication backends The commit introduces the following API incompatibilities: In ejabberd_auth.erl: * dirty_get_registered_users/0 is renamed to get_users/0 * get_vh_registered_users/1 is renamed to get_users/1 * get_vh_registered_users/2 is renamed to get_users/2 * get_vh_registered_users_number/1 is renamed to count_users/1 * get_vh_registered_users_number/2 is renamed to count_users/2 In ejabberd_auth callbacks * plain_password_required/0 is replaced by plain_password_required/1 where the argument is a virtual host * store_type/0 is replaced by store_type/1 where the argument is a virtual host * set_password/3 is now an optional callback * remove_user/3 callback is no longer needed * remove_user/2 now should return `ok | {error, atom()}` * is_user_exists/2 now must only be implemented for backends with `external` store type * check_password/6 is no longer needed * check_password/4 now must only be implemented for backends with `external` store type * try_register/3 is now an optional callback and should return `ok | {error, atom()}` * dirty_get_registered_users/0 is no longer needed * get_vh_registered_users/1 is no longer needed * get_vh_registered_users/2 is renamed to get_users/2 * get_vh_registered_users_number/1 is no longer needed * get_vh_registered_users_number/2 is renamed to count_users/2 * get_password_s/2 is no longer needed * get_password/2 now must only be implemented for backends with `plain` or `scram` store type Additionally, the commit introduces two new callbacks: * use_cache/1 where the argument is a virtual host * cache_nodes/1 where the argument is a virtual host New options are also introduced: `auth_use_cache`, `auth_cache_missed`, `auth_cache_life_time` and `auth_cache_size`. --- rebar.config | 2 +- src/cyrsasl_scram.erl | 6 +- src/ejabberd_admin.erl | 6 +- src/ejabberd_auth.erl | 850 ++++++++++++++++++++++---------- src/ejabberd_auth_anonymous.erl | 86 +--- src/ejabberd_auth_external.erl | 263 +--------- src/ejabberd_auth_ldap.erl | 64 +-- src/ejabberd_auth_mnesia.erl | 475 ++++++------------ src/ejabberd_auth_pam.erl | 47 +- src/ejabberd_auth_riak.erl | 264 ++-------- src/ejabberd_auth_sql.erl | 448 +++-------------- src/ejabberd_piefxis.erl | 6 +- src/ejabberd_web_admin.erl | 10 +- src/extauth.erl | 2 +- src/mod_admin_extra.erl | 10 +- src/mod_announce.erl | 4 +- src/mod_configure.erl | 6 +- src/mod_register.erl | 4 +- src/mod_register_web.erl | 4 +- src/mod_shared_roster.erl | 4 +- src/mod_stats.erl | 4 +- src/prosody2ejabberd.erl | 2 +- test/suite.erl | 3 +- 23 files changed, 905 insertions(+), 1665 deletions(-) diff --git a/rebar.config b/rebar.config index b553931e6..7def98c79 100644 --- a/rebar.config +++ b/rebar.config @@ -20,7 +20,7 @@ {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"}}, + {cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "51eee22"}}, {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"}}}, diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl index 8cbc821c7..b496be0a4 100644 --- a/src/cyrsasl_scram.erl +++ b/src/cyrsasl_scram.erl @@ -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) -> + {misc:decode_base64(Pass#scram.storedkey), + misc:decode_base64(Pass#scram.serverkey), + misc:decode_base64(Pass#scram.salt), + Pass#scram.iterationcount}; true -> TempSalt = randoms:bytes(?SALT_LENGTH), diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index a615b2fee..c0429fb0c 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -478,9 +478,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 +494,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). diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 9751142a5..23ed0eeae 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -22,9 +22,6 @@ %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- - -%% TODO: Use the functions in ejabberd auth to add and remove users. - -module(ejabberd_auth). -behaviour(gen_server). @@ -37,10 +34,10 @@ set_password/3, check_password/4, check_password/6, check_password_with_authmodule/4, check_password_with_authmodule/6, try_register/3, - dirty_get_registered_users/0, get_vh_registered_users/1, - get_vh_registered_users/2, export/1, import_info/0, - get_vh_registered_users_number/1, import/5, import_start/2, - get_vh_registered_users_number/2, get_password/2, + get_users/0, get_users/1, password_to_scram/1, + get_users/2, export/1, import_info/0, + count_users/1, import/5, import_start/2, + count_users/2, get_password/2, get_password_s/2, get_password_with_authmodule/2, is_user_exists/2, is_user_exists_in_other_modules/3, remove_user/2, remove_user/3, plain_password_required/1, @@ -54,10 +51,13 @@ -include("ejabberd.hrl"). -include("logger.hrl"). +-define(AUTH_CACHE, auth_cache). +-define(SALT_LENGTH, 16). + -record(state, {host_modules = #{} :: map()}). --type scrammed_password() :: {binary(), binary(), binary(), non_neg_integer()}. --type password() :: binary() | scrammed_password(). +-type password() :: binary() | #scram{}. +-type digest_fun() :: fun((binary()) -> binary()). -export_type([password/0]). %%%---------------------------------------------------------------------- @@ -69,24 +69,29 @@ -callback start(binary()) -> any(). -callback stop(binary()) -> any(). --callback plain_password_required() -> boolean(). --callback store_type() -> plain | external | scram. +-callback plain_password_required(binary()) -> boolean(). +-callback store_type(binary()) -> plain | external | scram. -callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}. --callback remove_user(binary(), binary()) -> any(). --callback remove_user(binary(), binary(), binary()) -> any(). +-callback remove_user(binary(), binary()) -> ok | {error, any()}. -callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}. -callback check_password(binary(), binary(), binary(), binary()) -> boolean(). --callback check_password(binary(), binary(), binary(), binary(), binary(), - fun((binary()) -> binary())) -> boolean(). --callback try_register(binary(), binary(), binary()) -> {atomic, atom()} | - {error, atom()}. --callback dirty_get_registered_users() -> [{binary(), binary()}]. --callback get_vh_registered_users(binary()) -> [{binary(), binary()}]. --callback get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}]. --callback get_vh_registered_users_number(binary()) -> number(). --callback get_vh_registered_users_number(binary(), opts()) -> number(). --callback get_password(binary(), binary()) -> false | password(). --callback get_password_s(binary(), binary()) -> password(). +-callback try_register(binary(), binary(), password()) -> ok | {error, atom()}. +-callback get_users(binary(), opts()) -> [{binary(), binary()}]. +-callback count_users(binary(), opts()) -> number(). +-callback get_password(binary(), binary()) -> {ok, password()} | error. +-callback use_cache(binary()) -> boolean(). +-callback cache_nodes(binary()) -> boolean(). + +-optional_callbacks([set_password/3, + remove_user/2, + is_user_exists/2, + check_password/4, + try_register/3, + get_users/2, + count_users/2, + get_password/2, + use_cache/1, + cache_nodes/1]). -spec start_link() -> {ok, pid()} | {error, any()}. start_link() -> @@ -99,9 +104,13 @@ init([]) -> HostModules = lists:foldl( fun(Host, Acc) -> Modules = auth_modules(Host), - start(Host, Modules), maps:put(Host, Modules, Acc) end, #{}, ?MYHOSTS), + lists:foreach( + fun({Host, Modules}) -> + start(Host, Modules) + end, maps:to_list(HostModules)), + init_cache(HostModules), {ok, #state{host_modules = HostModules}}. handle_call(_Request, _From, State) -> @@ -112,11 +121,13 @@ handle_cast({host_up, Host}, #state{host_modules = HostModules} = State) -> Modules = auth_modules(Host), start(Host, Modules), NewHostModules = maps:put(Host, Modules, HostModules), + init_cache(NewHostModules), {noreply, State#state{host_modules = NewHostModules}}; handle_cast({host_down, Host}, #state{host_modules = HostModules} = State) -> Modules = maps:get(Host, HostModules, []), stop(Host, Modules), NewHostModules = maps:remove(Host, HostModules), + init_cache(NewHostModules), {noreply, State#state{host_modules = NewHostModules}}; handle_cast(config_reloaded, #state{host_modules = HostModules} = State) -> NewHostModules = lists:foldl( @@ -127,6 +138,7 @@ handle_cast(config_reloaded, #state{host_modules = HostModules} = State) -> stop(Host, OldModules -- NewModules), maps:put(Host, NewModules, Acc) end, HostModules, ?MYHOSTS), + init_cache(NewHostModules), {noreply, State#state{host_modules = NewHostModules}}; handle_cast(Msg, State) -> ?WARNING_MSG("unexpected cast: ~p", [Msg]), @@ -162,306 +174,266 @@ host_down(Host) -> config_reloaded() -> gen_server:cast(?MODULE, config_reloaded). +-spec plain_password_required(binary()) -> boolean(). plain_password_required(Server) -> - lists:any(fun (M) -> M:plain_password_required() end, + lists:any(fun (M) -> M:plain_password_required(Server) end, auth_modules(Server)). +-spec store_type(binary()) -> plain | scram | external. store_type(Server) -> -%% @doc Check if the user and password can login in server. -%% @spec (User::string(), Server::string(), Password::string()) -> -%% true | false - lists:foldl(fun (_, external) -> external; - (M, scram) -> - case M:store_type() of - external -> external; - _Else -> scram - end; - (M, plain) -> M:store_type() - end, - plain, auth_modules(Server)). + lists:foldl( + fun(_, external) -> external; + (M, scram) -> + case M:store_type(Server) of + external -> external; + _ -> scram + end; + (M, plain) -> + M:store_type(Server) + end, plain, auth_modules(Server)). -spec check_password(binary(), binary(), binary(), binary()) -> boolean(). - check_password(User, AuthzId, Server, Password) -> - case check_password_with_authmodule(User, AuthzId, Server, - Password) - of - {true, _AuthModule} -> true; - false -> false - end. + check_password(User, AuthzId, Server, Password, <<"">>, undefined). -%% @doc Check if the user and password can login in server. -%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string(), -%% Digest::string(), DigestGen::function()) -> -%% true | false -spec check_password(binary(), binary(), binary(), binary(), binary(), - fun((binary()) -> binary())) -> boolean(). - -check_password(User, AuthzId, Server, Password, Digest, - DigestGen) -> - case check_password_with_authmodule(User, AuthzId, Server, - Password, Digest, DigestGen) - of - {true, _AuthModule} -> true; - false -> false + digest_fun() | undefined) -> boolean(). +check_password(User, AuthzId, Server, Password, Digest, DigestGen) -> + case check_password_with_authmodule( + User, AuthzId, Server, Password, Digest, DigestGen) of + {true, _AuthModule} -> true; + false -> false end. -%% @doc Check if the user and password can login in server. -%% The user can login if at least an authentication method accepts the user -%% and the password. -%% The first authentication method that accepts the credentials is returned. -%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string()) -> -%% {true, AuthModule} | false -%% where -%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external -%% | ejabberd_auth_mnesia | ejabberd_auth_ldap -%% | ejabberd_auth_sql | ejabberd_auth_pam | ejabberd_auth_riak --spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false | - {true, atom()}. +-spec check_password_with_authmodule(binary(), binary(), + binary(), binary()) -> false | {true, atom()}. +check_password_with_authmodule(User, AuthzId, Server, Password) -> + check_password_with_authmodule( + User, AuthzId, Server, Password, <<"">>, undefined). -check_password_with_authmodule(User, AuthzId, Server, - Password) -> - check_password_loop(auth_modules(Server), - [User, AuthzId, Server, Password]). - --spec check_password_with_authmodule(binary(), binary(), binary(), binary(), binary(), - fun((binary()) -> binary())) -> false | - {true, atom()}. - -check_password_with_authmodule(User, AuthzId, Server, Password, - Digest, DigestGen) -> - check_password_loop(auth_modules(Server), - [User, AuthzId, Server, Password, Digest, DigestGen]). - -check_password_loop([], _Args) -> false; -check_password_loop([AuthModule | AuthModules], Args) -> - case apply(AuthModule, check_password, Args) of - true -> {true, AuthModule}; - false -> check_password_loop(AuthModules, Args) +-spec check_password_with_authmodule( + binary(), binary(), binary(), binary(), binary(), + digest_fun() | undefined) -> false | {true, atom()}. +check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGen) -> + case validate_credentials(User, Server) of + {ok, LUser, LServer} -> + lists:foldl( + fun(Mod, false) -> + case db_check_password( + LUser, AuthzId, LServer, Password, + Digest, DigestGen, Mod) of + true -> {true, Mod}; + false -> false + end; + (_, Acc) -> + Acc + end, false, auth_modules(LServer)); + _ -> + false end. --spec set_password(binary(), binary(), binary()) -> ok | - {error, atom()}. - -%% @spec (User::string(), Server::string(), Password::string()) -> -%% ok | {error, ErrorType} -%% where ErrorType = empty_password | not_allowed | invalid_jid -set_password(_User, _Server, <<"">>) -> - {error, empty_password}; +-spec set_password(binary(), binary(), password()) -> ok | {error, atom()}. set_password(User, Server, Password) -> -%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed} - lists:foldl(fun (M, {error, _}) -> - M:set_password(User, Server, Password); - (_M, Res) -> Res - end, - {error, not_allowed}, auth_modules(Server)). - --spec try_register(binary(), binary(), binary()) -> {atomic, atom()} | - {error, atom()}. - -try_register(_User, _Server, <<"">>) -> - {error, not_allowed}; -try_register(User, Server, Password) -> - case is_user_exists(User, Server) of - true -> {atomic, exists}; - false -> - LServer = jid:nameprep(Server), - case ejabberd_router:is_my_host(LServer) of - true -> - Res = lists:foldl(fun (_M, {atomic, ok} = Res) -> Res; - (M, _) -> - M:try_register(User, Server, Password) - end, - {error, not_allowed}, auth_modules(Server)), - case Res of - {atomic, ok} -> - ejabberd_hooks:run(register_user, Server, - [User, Server]), - {atomic, ok}; - _ -> Res - end; - false -> {error, not_allowed} - end + case validate_credentials(User, Server, Password) of + {ok, LUser, LServer} -> + lists:foldl( + fun(M, {error, _}) -> + db_set_password(LUser, LServer, Password, M); + (_, ok) -> + ok + end, {error, not_allowed}, auth_modules(LServer)); + Err -> + Err end. -%% Registered users list do not include anonymous users logged --spec dirty_get_registered_users() -> [{binary(), binary()}]. +-spec try_register(binary(), binary(), password()) -> ok | {error, atom()}. +try_register(User, Server, Password) -> + case validate_credentials(User, Server, Password) of + {ok, LUser, LServer} -> + case is_user_exists(LUser, LServer) of + true -> + {error, exists}; + false -> + case ejabberd_router:is_my_host(LServer) of + true -> + case lists:foldl( + fun(_, ok) -> + ok; + (Mod, _) -> + db_try_register( + User, Server, Password, Mod) + end, {error, not_allowed}, + auth_modules(LServer)) of + ok -> + ejabberd_hooks:run( + register_user, Server, [User, Server]); + {error, _} = Err -> + Err + end; + false -> + {error, not_allowed} + end + end; + Err -> + Err + end. -dirty_get_registered_users() -> - lists:flatmap(fun (M) -> M:dirty_get_registered_users() - end, - auth_modules()). +-spec get_users() -> [{binary(), binary()}]. +get_users() -> + lists:flatmap( + fun({Host, Mod}) -> + db_get_users(Host, [], Mod) + end, auth_modules()). --spec get_vh_registered_users(binary()) -> [{binary(), binary()}]. +-spec get_users(binary()) -> [{binary(), binary()}]. +get_users(Server) -> + get_users(Server, []). -%% Registered users list do not include anonymous users logged -get_vh_registered_users(Server) -> - lists:flatmap(fun (M) -> - M:get_vh_registered_users(Server) - end, - auth_modules(Server)). +-spec get_users(binary(), opts()) -> [{binary(), binary()}]. +get_users(Server, Opts) -> + case jid:nameprep(Server) of + error -> []; + LServer -> + lists:flatmap( + fun(M) -> db_get_users(LServer, Opts, M) end, + auth_modules(LServer)) + end. --spec get_vh_registered_users(binary(), opts()) -> [{binary(), binary()}]. +-spec count_users(binary()) -> non_neg_integer(). +count_users(Server) -> + count_users(Server, []). -get_vh_registered_users(Server, Opts) -> - lists:flatmap(fun (M) -> - case erlang:function_exported(M, - get_vh_registered_users, - 2) - of - true -> M:get_vh_registered_users(Server, Opts); - false -> M:get_vh_registered_users(Server) - end - end, - auth_modules(Server)). - -get_vh_registered_users_number(Server) -> - lists:sum(lists:map(fun (M) -> - case erlang:function_exported(M, - get_vh_registered_users_number, - 1) - of - true -> - M:get_vh_registered_users_number(Server); - false -> - length(M:get_vh_registered_users(Server)) - end - end, - auth_modules(Server))). - --spec get_vh_registered_users_number(binary(), opts()) -> number(). - -get_vh_registered_users_number(Server, Opts) -> -%% @doc Get the password of the user. -%% @spec (User::string(), Server::string()) -> Password::string() - lists:sum(lists:map(fun (M) -> - case erlang:function_exported(M, - get_vh_registered_users_number, - 2) - of - true -> - M:get_vh_registered_users_number(Server, - Opts); - false -> - length(M:get_vh_registered_users(Server)) - end - end, - auth_modules(Server))). +-spec count_users(binary(), opts()) -> non_neg_integer(). +count_users(Server, Opts) -> + case jid:nameprep(Server) of + error -> 0; + LServer -> + lists:sum( + lists:map( + fun(M) -> db_count_users(LServer, Opts, M) end, + auth_modules(LServer))) + end. -spec get_password(binary(), binary()) -> false | password(). - get_password(User, Server) -> - lists:foldl(fun (M, false) -> - M:get_password(User, Server); - (_M, Password) -> Password - end, - false, auth_modules(Server)). + case validate_credentials(User, Server) of + {ok, LUser, LServer} -> + case lists:foldl( + fun(M, error) -> db_get_password(LUser, LServer, M); + (_M, Acc) -> Acc + end, error, auth_modules(LServer)) of + {ok, Password} -> + Password; + error -> + false + end; + _ -> + false + end. -spec get_password_s(binary(), binary()) -> password(). - get_password_s(User, Server) -> case get_password(User, Server) of false -> <<"">>; Password -> Password end. -%% @doc Get the password of the user and the auth module. -%% @spec (User::string(), Server::string()) -> -%% {Password::string(), AuthModule::atom()} | {false, none} -spec get_password_with_authmodule(binary(), binary()) -> {false | password(), module()}. - get_password_with_authmodule(User, Server) -> -%% Returns true if the user exists in the DB or if an anonymous user is logged -%% under the given name - lists:foldl(fun (M, {false, _}) -> - {M:get_password(User, Server), M}; - (_M, {Password, AuthModule}) -> {Password, AuthModule} - end, - {false, none}, auth_modules(Server)). + case validate_credentials(User, Server) of + {ok, LUser, LServer} -> + case lists:foldl( + fun(M, {error, _}) -> + {db_get_password(LUser, LServer, M), M}; + (_M, Acc) -> + Acc + end, {error, undefined}, auth_modules(LServer)) of + {{ok, Password}, Module} -> + {Password, Module}; + {error, Module} -> + {false, Module} + end; + _ -> + {false, undefined} + end. -spec is_user_exists(binary(), binary()) -> boolean(). - is_user_exists(_User, <<"">>) -> false; - is_user_exists(User, Server) -> -%% Check if the user exists in all authentications module except the module -%% passed as parameter -%% @spec (Module::atom(), User, Server) -> true | false | maybe - lists:any(fun (M) -> - case M:is_user_exists(User, Server) of - {error, Error} -> - ?ERROR_MSG("The authentication module ~p returned " - "an error~nwhen checking user ~p in server " - "~p~nError message: ~p", - [M, User, Server, Error]), - false; - Else -> Else + case validate_credentials(User, Server) of + {ok, LUser, LServer} -> + lists:any( + fun(M) -> + case db_is_user_exists(LUser, LServer, M) of + {error, _} -> + false; + Else -> + Else end - end, - auth_modules(Server)). + end, auth_modules(LServer)); + _ -> + false + end. -spec is_user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe. - is_user_exists_in_other_modules(Module, User, Server) -> - is_user_exists_in_other_modules_loop(auth_modules(Server) - -- [Module], - User, Server). + is_user_exists_in_other_modules_loop( + auth_modules(Server) -- [Module], User, Server). -is_user_exists_in_other_modules_loop([], _User, - _Server) -> +is_user_exists_in_other_modules_loop([], _User, _Server) -> false; -is_user_exists_in_other_modules_loop([AuthModule - | AuthModules], - User, Server) -> - case AuthModule:is_user_exists(User, Server) of - true -> true; - false -> - is_user_exists_in_other_modules_loop(AuthModules, User, - Server); - {error, Error} -> - ?DEBUG("The authentication module ~p returned " - "an error~nwhen checking user ~p in server " - "~p~nError message: ~p", - [AuthModule, User, Server, Error]), - maybe +is_user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) -> + case db_is_user_exists(User, Server, AuthModule) of + true -> + true; + false -> + is_user_exists_in_other_modules_loop(AuthModules, User, Server); + {error, _} -> + maybe end. -spec remove_user(binary(), binary()) -> ok. - -%% @spec (User, Server) -> ok -%% @doc Remove user. -%% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> - lists:foreach(fun (M) -> M:remove_user(User, Server) - end, - auth_modules(Server)), - ejabberd_hooks:run(remove_user, jid:nameprep(Server), - [User, Server]), - ok. - -%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error -%% @doc Try to remove user if the provided password is correct. -%% The removal is attempted in each auth method provided: -%% when one returns 'ok' the loop stops; -%% if no method returns 'ok' then it returns the error message indicated by the last method attempted. --spec remove_user(binary(), binary(), binary()) -> any(). + case validate_credentials(User, Server) of + {ok, LUser, LServer} -> + lists:foreach( + fun(Mod) -> db_remove_user(LUser, LServer, Mod) end, + auth_modules(LServer)), + ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]); + _Err -> + ok + end. +-spec remove_user(binary(), binary(), password()) -> ok | {error, atom()}. remove_user(User, Server, Password) -> - R = lists:foldl(fun (_M, ok = Res) -> Res; - (M, _) -> M:remove_user(User, Server, Password) - end, - error, auth_modules(Server)), - case R of - ok -> - ejabberd_hooks:run(remove_user, jid:nameprep(Server), - [User, Server]); - _ -> none - end, - R. + case validate_credentials(User, Server, Password) of + {ok, LUser, LServer} -> + case lists:foldl( + fun (_, ok) -> + ok; + (Mod, _) -> + case db_check_password( + LUser, <<"">>, LServer, Password, + <<"">>, undefined, Mod) of + true -> + db_remove_user(LUser, LServer, Mod); + false -> + {error, not_allowed} + end + end, {error, not_allowed}, auth_modules(Server)) of + ok -> + ejabberd_hooks:run( + remove_user, LServer, [LUser, LServer]); + Err -> + Err + end; + Err -> + Err + end. -%% @spec (IOList) -> non_negative_float() %% @doc Calculate informational entropy. +-spec entropy(iodata()) -> float(). entropy(B) -> case binary_to_list(B) of "" -> 0.0; @@ -497,15 +469,266 @@ backend_type(Mod) -> _ -> Mod end. +-spec password_format(binary() | global) -> plain | scram. password_format(LServer) -> ejabberd_config:get_option({auth_password_format, LServer}, plain). +%%%---------------------------------------------------------------------- +%%% Backend calls +%%%---------------------------------------------------------------------- +db_try_register(User, Server, Password, Mod) -> + case erlang:function_exported(Mod, try_register, 3) of + true -> + Password1 = case Mod:store_type(Server) of + scram -> password_to_scram(Password); + _ -> Password + end, + case use_cache(Mod, Server) of + true -> + case ets_cache:update( + ?AUTH_CACHE, {User, Server}, {ok, Password}, + fun() -> Mod:try_register(User, Server, Password1) end, + cache_nodes(Mod, Server)) of + {ok, _} -> ok; + {error, _} = Err -> Err + end; + false -> + Mod:try_register(User, Server, Password1) + end; + false -> + {error, not_allowed} + end. + +db_set_password(User, Server, Password, Mod) -> + case erlang:function_exported(Mod, set_password, 3) of + true -> + Password1 = case Mod:store_type(Server) of + scram -> password_to_scram(Password); + _ -> Password + end, + case use_cache(Mod, Server) of + true -> + case ets_cache:update( + ?AUTH_CACHE, {User, Server}, {ok, Password}, + fun() -> Mod:set_password(User, Server, Password1) end, + cache_nodes(Mod, Server)) of + {ok, _} -> ok; + {error, _} = Err -> Err + end; + false -> + Mod:set_password(User, Server, Password1) + end; + false -> + {error, not_allowed} + end. + +db_get_password(User, Server, Mod) -> + UseCache = use_cache(Mod, Server), + case erlang:function_exported(Mod, get_password, 2) of + false when UseCache -> + ets_cache:lookup(?AUTH_CACHE, {User, Server}); + false -> + error; + true when UseCache -> + ets_cache:lookup( + ?AUTH_CACHE, {User, Server}, + fun() -> Mod:get_password(User, Server) end); + true -> + Mod:get_password(User, Server) + end. + +db_is_user_exists(User, Server, Mod) -> + case db_get_password(User, Server, Mod) of + {ok, _} -> + true; + error -> + case Mod:store_type(Server) of + external -> + Mod:is_user_exists(User, Server); + _ -> + false + end + end. + +db_check_password(User, AuthzId, Server, ProvidedPassword, + Digest, DigestFun, Mod) -> + case db_get_password(User, Server, Mod) of + {ok, ValidPassword} -> + match_passwords(ProvidedPassword, ValidPassword, Digest, DigestFun); + error -> + case {Mod:store_type(Server), use_cache(Mod, Server)} of + {external, true} -> + case ets_cache:update( + ?AUTH_CACHE, {User, Server}, {ok, ProvidedPassword}, + fun() -> + case Mod:check_password( + User, AuthzId, Server, ProvidedPassword) of + true -> + {ok, ProvidedPassword}; + false -> + error + end + end, cache_nodes(Mod, Server)) of + {ok, _} -> + true; + error -> + false + end; + {external, false} -> + Mod:check_password(User, AuthzId, Server, ProvidedPassword); + _ -> + false + end + end. + +db_remove_user(User, Server, Mod) -> + case erlang:function_exported(Mod, remove_user, 2) of + true -> + case Mod:remove_user(User, Server) of + ok -> + case use_cache(Mod, Server) of + true -> + ets_cache:delete(?AUTH_CACHE, {User, Server}, + cache_nodes(Mod, Server)); + false -> + ok + end; + {error, _} = Err -> + Err + end; + false -> + {error, not_allowed} + end. + +db_get_users(Server, Opts, Mod) -> + case erlang:function_exported(Mod, get_users, 2) of + true -> + Mod:get_users(Server, Opts); + false -> + case use_cache(Mod, Server) of + true -> + ets_cache:fold( + fun({User, S}, {ok, _}, Users) when S == Server -> + [{User, Server}|Users]; + (_, _, Users) -> + Users + end, [], ?AUTH_CACHE); + false -> + [] + end + end. + +db_count_users(Server, Opts, Mod) -> + case erlang:function_exported(Mod, count_users, 2) of + true -> + Mod:count_users(Server, Opts); + false -> + case use_cache(Mod, Server) of + true -> + ets_cache:fold( + fun({_, S}, {ok, _}, Num) when S == Server -> + Num + 1; + (_, _, Num) -> + Num + end, 0, ?AUTH_CACHE); + false -> + 0 + end + end. + +%%%---------------------------------------------------------------------- +%%% SCRAM stuff +%%%---------------------------------------------------------------------- +is_password_scram_valid(Password, Scram) -> + case jid:resourceprep(Password) of + error -> + false; + _ -> + IterationCount = Scram#scram.iterationcount, + Salt = 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. + +password_to_scram(Password) -> + password_to_scram(Password, ?SCRAM_DEFAULT_ITERATION_COUNT). + +password_to_scram(#scram{} = Password, _IterationCount) -> + Password; +password_to_scram(Password, IterationCount) -> + Salt = randoms:bytes(?SALT_LENGTH), + SaltedPassword = scram:salted_password(Password, Salt, IterationCount), + StoredKey = scram:stored_key(scram:client_key(SaltedPassword)), + ServerKey = scram:server_key(SaltedPassword), + #scram{storedkey = misc:encode_base64(StoredKey), + serverkey = misc:encode_base64(ServerKey), + salt = misc:encode_base64(Salt), + iterationcount = IterationCount}. + +%%%---------------------------------------------------------------------- +%%% Cache stuff +%%%---------------------------------------------------------------------- +-spec init_cache(map()) -> ok. +init_cache(HostModules) -> + case use_cache(HostModules) of + true -> + ets_cache:new(?AUTH_CACHE, cache_opts()); + false -> + ets_cache:delete(?AUTH_CACHE) + end. + +-spec cache_opts() -> [proplists:property()]. +cache_opts() -> + MaxSize = ejabberd_config:get_option( + auth_cache_size, + ejabberd_config:cache_size(global)), + CacheMissed = ejabberd_config:get_option( + auth_cache_missed, + ejabberd_config:cache_missed(global)), + LifeTime = case ejabberd_config:get_option( + auth_cache_life_time, + ejabberd_config:cache_life_time(global)) of + infinity -> infinity; + I -> timer:seconds(I) + end, + [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}]. + +-spec use_cache(map()) -> boolean(). +use_cache(HostModules) -> + lists:any( + fun({Host, Modules}) -> + lists:any(fun(Module) -> + use_cache(Module, Host) + end, Modules) + end, maps:to_list(HostModules)). + +-spec use_cache(module(), binary()) -> boolean(). +use_cache(Mod, LServer) -> + case erlang:function_exported(Mod, use_cache, 1) of + true -> Mod:use_cache(LServer); + false -> + ejabberd_config:get_option( + {auth_use_cache, LServer}, + ejabberd_config:use_cache(LServer)) + end. + +-spec cache_nodes(module(), binary()) -> [node()]. +cache_nodes(Mod, LServer) -> + case erlang:function_exported(Mod, cache_nodes, 1) of + true -> Mod:cache_nodes(LServer); + false -> ejabberd_cluster:get_nodes() + end. + %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- --spec auth_modules() -> [module()]. +-spec auth_modules() -> [{binary(), module()}]. auth_modules() -> - lists:usort(lists:flatmap(fun auth_modules/1, ?MYHOSTS)). + lists:flatmap( + fun(Host) -> + [{Host, Mod} || Mod <- auth_modules(Host)] + end, ?MYHOSTS). -spec auth_modules(binary()) -> [module()]. auth_modules(Server) -> @@ -516,6 +739,65 @@ auth_modules(Server) -> (misc:atom_to_binary(M))/binary>>) || M <- Methods]. +-spec match_passwords(password(), password(), + binary(), digest_fun() | undefined) -> boolean(). +match_passwords(Password, #scram{} = Scram, <<"">>, undefined) -> + is_password_scram_valid(Password, Scram); +match_passwords(Password, #scram{} = Scram, Digest, DigestFun) -> + StoredKey = misc:decode_base64(Scram#scram.storedkey), + DigRes = if Digest /= <<"">> -> + Digest == DigestFun(StoredKey); + true -> false + end, + if DigRes -> + true; + true -> + StoredKey == Password andalso Password /= <<"">> + end; +match_passwords(ProvidedPassword, ValidPassword, <<"">>, undefined) -> + ProvidedPassword == ValidPassword andalso ProvidedPassword /= <<"">>; +match_passwords(ProvidedPassword, ValidPassword, Digest, DigestFun) -> + DigRes = if Digest /= <<"">> -> + Digest == DigestFun(ValidPassword); + true -> false + end, + if DigRes -> + true; + true -> + ValidPassword == ProvidedPassword andalso ProvidedPassword /= <<"">> + end. + +-spec validate_credentials(binary(), binary()) -> + {ok, binary(), binary()} | {error, invalid_jid}. +validate_credentials(User, Server) -> + validate_credentials(User, Server, #scram{}). + +-spec validate_credentials(binary(), binary(), password()) -> + {ok, binary(), binary()} | {error, invalid_jid | invalid_password}. +validate_credentials(_User, _Server, <<"">>) -> + {error, invalid_password}; +validate_credentials(User, Server, Password) -> + case jid:nodeprep(User) of + error -> + {error, invalid_jid}; + LUser -> + case jid:nameprep(Server) of + error -> + {error, invalid_jid}; + LServer -> + if is_record(Password, scram) -> + {ok, LUser, LServer}; + true -> + case jid:resourceprep(Password) of + error -> + {error, invalid_password}; + _ -> + {ok, LUser, LServer} + end + end + end + end. + export(Server) -> ejabberd_auth_mnesia:export(Server). @@ -536,6 +818,10 @@ import(_LServer, {sql, _}, sql, <<"users">>, _) -> -spec opt_type(auth_method) -> fun((atom() | [atom()]) -> [atom()]); (auth_password_format) -> fun((plain | scram) -> plain | scram); + (auth_use_cache) -> fun((boolean()) -> boolean()); + (auth_cache_missed) -> fun((boolean()) -> boolean()); + (auth_cache_life_time) -> fun((timeout()) -> timeout()); + (auth_cache_size) -> fun((timeout()) -> timeout()); (atom()) -> [atom()]. opt_type(auth_method) -> fun (V) when is_list(V) -> @@ -546,4 +832,20 @@ opt_type(auth_password_format) -> fun (plain) -> plain; (scram) -> scram end; -opt_type(_) -> [auth_method, auth_password_format]. +opt_type(auth_use_cache) -> + fun(B) when is_boolean(B) -> B end; +opt_type(auth_cache_missed) -> + fun(B) when is_boolean(B) -> B end; +opt_type(auth_cache_life_time) -> + fun(I) when is_integer(I), I>0 -> I; + (unlimited) -> infinity; + (infinity) -> infinity + end; +opt_type(auth_cache_size) -> + fun(I) when is_integer(I), I>0 -> I; + (unlimited) -> infinity; + (infinity) -> infinity + end; +opt_type(_) -> + [auth_method, auth_password_format, auth_use_cache, + auth_cache_missed, auth_cache_life_time, auth_cache_size]. diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index 5bb2daed7..b3ae1f6dd 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -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, is_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,15 +133,7 @@ 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, User, Server) @@ -173,68 +159,20 @@ 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}. - -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. +count_users(Server, Opts) -> + length(get_users(Server, Opts)). is_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) -> diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 5bdd704a0..5b2b26c1a 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -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, is_user_exists/2, remove_user/2, + store_type/1, plain_password_required/1, opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -49,275 +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 + 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; diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 8a4532e38..15abebedc 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -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, is_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}; + {'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, diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 592b9c566..02c22f9d5 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -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,153 @@ 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(_) -> + case mnesia:table_info(passwd, storage_type) of + disc_only_copies -> true; + _ -> 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 +248,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}) diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index 9d2fc819b..f865f36f6 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -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, + is_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,25 +57,6 @@ 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) -> Service = get_pam_service(Host), UserInfo = case get_pam_userinfotype(Host) of @@ -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 diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl index 74f0f73ca..fccaba102 100644 --- a/src/ejabberd_auth_riak.erl +++ b/src/ejabberd_auth_riak.erl @@ -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, diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index 8514b9cf1..d682634f0 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -31,14 +31,9 @@ -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, 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]). -include("ejabberd.hrl"). @@ -54,397 +49,82 @@ 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 + Res = if is_record(Password, scram) -> + sql_queries:set_password_scram_t( + Server, User, + Password#scram.storedkey, Password#scram.serverkey, + Password#scram.salt, Password#scram.iterationcount); + true -> + sql_queries:set_password_t(Server, User, Password) + end, + case Res 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) -> + sql_queries:add_user_scram( + Server, User, + Password#scram.storedkey, Password#scram.serverkey, + Password#scram.salt, Password#scram.iterationcount); + true -> + sql_queries: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 sql_queries: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 sql_queries: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 sql_queries: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 sql_queries: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). @@ -485,7 +165,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, diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index aea280591..ecb4908a7 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -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. diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index 5050ac095..6fdac1971 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -1370,7 +1370,7 @@ list_vhosts2(Lang, Hosts) -> OnlineUsers = length(ejabberd_sm:get_vh_session_list(Host)), RegisteredUsers = - ejabberd_auth:get_vh_registered_users_number(Host), + ejabberd_auth:count_users(Host), ?XE(<<"tr">>, [?XE(<<"td">>, [?AC(<<"../server/", Host/binary, @@ -1388,7 +1388,7 @@ list_vhosts2(Lang, Hosts) -> list_users(Host, Query, Lang, URLFunc) -> Res = list_users_parse_query(Query, Host), - Users = ejabberd_auth:get_vh_registered_users(Host), + Users = ejabberd_auth:get_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), FUsers = case length(SUsers) of N when N =< 100 -> @@ -1469,7 +1469,7 @@ list_users_parse_query(Query, Host) -> end. list_users_in_diapason(Host, Diap, Lang, URLFunc) -> - Users = ejabberd_auth:get_vh_registered_users(Host), + Users = ejabberd_auth:get_users(Host), SUsers = lists:sort([{S, U} || {U, S} <- Users]), [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), N1 = binary_to_integer(S1), @@ -1565,7 +1565,7 @@ su_to_list({Server, User}) -> get_stats(global, Lang) -> OnlineUsers = ejabberd_sm:connected_users_number(), RegisteredUsers = lists:foldl(fun (Host, Total) -> - ejabberd_auth:get_vh_registered_users_number(Host) + ejabberd_auth:count_users(Host) + Total end, 0, ?MYHOSTS), @@ -1589,7 +1589,7 @@ get_stats(Host, Lang) -> OnlineUsers = length(ejabberd_sm:get_vh_session_list(Host)), RegisteredUsers = - ejabberd_auth:get_vh_registered_users_number(Host), + ejabberd_auth:count_users(Host), [?XAE(<<"table">>, [], [?XE(<<"tbody">>, [?XE(<<"tr">>, diff --git a/src/extauth.erl b/src/extauth.erl index 54f44953c..70f32bf8f 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -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. diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 133b2fd29..982772af0 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -810,14 +810,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])}. @@ -1285,7 +1285,7 @@ subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick 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. @@ -1499,14 +1499,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. diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 35d179c0c..379cd3ca0 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -633,7 +633,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 +642,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), diff --git a/src/mod_configure.erl b/src/mod_configure.erl index b9db1e511..f3eb496d8 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -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(), diff --git a/src/mod_register.erl b/src/mod_register.erl index af4ba02f4..5167370c1 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -323,7 +323,7 @@ try_register(User, Server, Password, SourceRaw, Lang) -> case ejabberd_auth:try_register(User, Server, Password) of - {atomic, ok} -> + ok -> send_welcome_message(JID), send_registration_notifications( ?MODULE, JID, Source), @@ -331,7 +331,7 @@ try_register(User, Server, Password, SourceRaw, Lang) -> Error -> remove_timeout(Source), case Error of - {atomic, exists} -> + {error, exists} -> Txt = <<"User already exists">>, {error, xmpp:err_conflict(Txt, Lang)}; {error, invalid_jid} -> diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl index cd19bb65a..d5e4112c3 100644 --- a/src/mod_register_web.erl +++ b/src/mod_register_web.erl @@ -510,8 +510,8 @@ register_account2(Username, Host, Password) -> case ejabberd_auth:try_register(Username, Host, Password) of - {atomic, Res} -> - {success, Res, {Username, Host, Password}}; + ok -> + {success, ok, {Username, Host, Password}}; Other -> Other end. diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 67b5870a2..c25b13f66 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -450,7 +450,7 @@ get_online_users(Host) -> get_group_users(Host1, Group1) -> {Host, Group} = split_grouphost(Host1, Group1), case get_group_opt(Host, Group, all_users, false) of - true -> ejabberd_auth:get_vh_registered_users(Host); + true -> ejabberd_auth:get_users(Host); false -> [] end ++ @@ -462,7 +462,7 @@ get_group_users(Host1, Group1) -> get_group_users(Host, Group, GroupOpts) -> case proplists:get_value(all_users, GroupOpts, false) of - true -> ejabberd_auth:get_vh_registered_users(Host); + true -> ejabberd_auth:get_users(Host); false -> [] end ++ diff --git a/src/mod_stats.erl b/src/mod_stats.erl index 92a6627c5..2bdbdbd33 100644 --- a/src/mod_stats.erl +++ b/src/mod_stats.erl @@ -119,7 +119,7 @@ get_local_stat(Server, [], Name) get_local_stat(Server, [], Name) when Name == <<"users/total">> -> case catch - ejabberd_auth:get_vh_registered_users_number(Server) + ejabberd_auth:count_users(Server) of {'EXIT', _Reason} -> ?STATERR(500, <<"Internal Server Error">>); @@ -134,7 +134,7 @@ get_local_stat(_Server, [], Name) get_local_stat(_Server, [], Name) when Name == <<"users/all-hosts/total">> -> NumUsers = lists:foldl(fun (Host, Total) -> - ejabberd_auth:get_vh_registered_users_number(Host) + ejabberd_auth:count_users(Host) + Total end, 0, ?MYHOSTS), diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 17bbc1d0a..f575724c6 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -129,7 +129,7 @@ convert_data(Host, "accounts", User, [Data]) -> Pass end, case ejabberd_auth:try_register(User, Host, Password) of - {atomic, ok} -> + ok -> ok; Err -> ?ERROR_MSG("failed to register user ~s@~s: ~p", diff --git a/test/suite.erl b/test/suite.erl index 76be5f806..6bd96002a 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -468,8 +468,7 @@ re_register(Config) -> User = ?config(user, Config), Server = ?config(server, Config), Pass = ?config(password, Config), - {atomic, ok} = ejabberd_auth:try_register(User, Server, Pass), - ok. + ok = ejabberd_auth:try_register(User, Server, Pass). match_failure(Received, [Match]) when is_list(Match)-> ct:fail("Received input:~n~n~p~n~ndon't match expected patterns:~n~n~s", [Received, Match]); From cdb191bb48ec8aa251031f7de0f963dee00f3022 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 11 May 2017 15:49:06 +0300 Subject: [PATCH 14/91] Rename is_user_exists -> user_exists --- src/ejabberd_auth.erl | 34 ++++++++++++++++----------------- src/ejabberd_auth_anonymous.erl | 6 +++--- src/ejabberd_auth_external.erl | 6 +++--- src/ejabberd_auth_ldap.erl | 8 ++++---- src/ejabberd_auth_pam.erl | 4 ++-- src/ejabberd_sm.erl | 4 ++-- src/ejabberd_web_admin.erl | 4 ++-- src/extauth.erl | 4 ++-- src/mod_admin_extra.erl | 4 ++-- src/mod_configure.erl | 4 ++-- src/mod_disco.erl | 2 +- src/mod_register.erl | 2 +- src/mod_register_web.erl | 2 +- src/mod_shared_roster_ldap.erl | 2 +- src/mod_vcard_ldap.erl | 2 +- 15 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 23ed0eeae..230bd87b5 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -39,7 +39,7 @@ count_users/1, import/5, import_start/2, count_users/2, get_password/2, get_password_s/2, get_password_with_authmodule/2, - is_user_exists/2, is_user_exists_in_other_modules/3, + user_exists/2, user_exists_in_other_modules/3, remove_user/2, remove_user/3, plain_password_required/1, store_type/1, entropy/1, backend_type/1, password_format/1]). %% gen_server callbacks @@ -73,7 +73,7 @@ -callback store_type(binary()) -> plain | external | scram. -callback set_password(binary(), binary(), binary()) -> ok | {error, atom()}. -callback remove_user(binary(), binary()) -> ok | {error, any()}. --callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}. +-callback user_exists(binary(), binary()) -> boolean() | {error, atom()}. -callback check_password(binary(), binary(), binary(), binary()) -> boolean(). -callback try_register(binary(), binary(), password()) -> ok | {error, atom()}. -callback get_users(binary(), opts()) -> [{binary(), binary()}]. @@ -84,7 +84,7 @@ -optional_callbacks([set_password/3, remove_user/2, - is_user_exists/2, + user_exists/2, check_password/4, try_register/3, get_users/2, @@ -250,7 +250,7 @@ set_password(User, Server, Password) -> try_register(User, Server, Password) -> case validate_credentials(User, Server, Password) of {ok, LUser, LServer} -> - case is_user_exists(LUser, LServer) of + case user_exists(LUser, LServer) of true -> {error, exists}; false -> @@ -357,15 +357,15 @@ get_password_with_authmodule(User, Server) -> {false, undefined} end. --spec is_user_exists(binary(), binary()) -> boolean(). -is_user_exists(_User, <<"">>) -> +-spec user_exists(binary(), binary()) -> boolean(). +user_exists(_User, <<"">>) -> false; -is_user_exists(User, Server) -> +user_exists(User, Server) -> case validate_credentials(User, Server) of {ok, LUser, LServer} -> lists:any( fun(M) -> - case db_is_user_exists(LUser, LServer, M) of + case db_user_exists(LUser, LServer, M) of {error, _} -> false; Else -> @@ -376,19 +376,19 @@ is_user_exists(User, Server) -> false end. --spec is_user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe. -is_user_exists_in_other_modules(Module, User, Server) -> - is_user_exists_in_other_modules_loop( +-spec user_exists_in_other_modules(atom(), binary(), binary()) -> boolean() | maybe. +user_exists_in_other_modules(Module, User, Server) -> + user_exists_in_other_modules_loop( auth_modules(Server) -- [Module], User, Server). -is_user_exists_in_other_modules_loop([], _User, _Server) -> +user_exists_in_other_modules_loop([], _User, _Server) -> false; -is_user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) -> - case db_is_user_exists(User, Server, AuthModule) of +user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) -> + case db_user_exists(User, Server, AuthModule) of true -> true; false -> - is_user_exists_in_other_modules_loop(AuthModules, User, Server); + user_exists_in_other_modules_loop(AuthModules, User, Server); {error, _} -> maybe end. @@ -537,14 +537,14 @@ db_get_password(User, Server, Mod) -> Mod:get_password(User, Server) end. -db_is_user_exists(User, Server, Mod) -> +db_user_exists(User, Server, Mod) -> case db_get_password(User, Server, Mod) of {ok, _} -> true; error -> case Mod:store_type(Server) of external -> - Mod:is_user_exists(User, Server); + Mod:user_exists(User, Server); _ -> false end diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index b3ae1f6dd..a4f3ac1c5 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -40,7 +40,7 @@ unregister_connection/3 ]). --export([login/2, check_password/4, is_user_exists/2, +-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]). @@ -135,7 +135,7 @@ unregister_connection(_SID, %% --------------------------------- 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 @@ -165,7 +165,7 @@ get_users(Server, _) -> count_users(Server, Opts) -> length(get_users(Server, Opts)). -is_user_exists(User, Server) -> +user_exists(User, Server) -> anonymous_user_exist(User, Server). plain_password_required(_) -> diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 5b2b26c1a..c56697610 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -32,7 +32,7 @@ -behaviour(ejabberd_auth). -export([start/1, stop/1, set_password/3, check_password/4, - try_register/3, is_user_exists/2, remove_user/2, + try_register/3, user_exists/2, remove_user/2, store_type/1, plain_password_required/1, opt_type/1]). -include("ejabberd.hrl"). @@ -68,8 +68,8 @@ set_password(User, Server, Password) -> try_register(User, Server, Password) -> extauth:try_register(User, Server, Password). -is_user_exists(User, Server) -> - try extauth:is_user_exists(User, Server) of +user_exists(User, Server) -> + try extauth:user_exists(User, Server) of Res -> Res catch _:Error -> diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 15abebedc..21f7ca62c 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -37,7 +37,7 @@ handle_cast/2, terminate/2, code_change/3]). -export([start/1, stop/1, start_link/1, set_password/3, - check_password/4, is_user_exists/2, + check_password/4, user_exists/2, get_users/2, count_users/2, store_type/1, plain_password_required/1, opt_type/1]). @@ -147,8 +147,8 @@ 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 +user_exists(User, Server) -> + case catch user_exists_ldap(User, Server) of {'EXIT', _Error} -> {error, db_failure}; Result -> Result end. @@ -218,7 +218,7 @@ get_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; diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index f865f36f6..f31ba4c9a 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -31,7 +31,7 @@ -behaviour(ejabberd_auth). -export([start/1, stop/1, check_password/4, - is_user_exists/2, store_type/1, plain_password_required/1, + user_exists/2, store_type/1, plain_password_required/1, opt_type/1]). start(_Host) -> @@ -57,7 +57,7 @@ check_password(User, AuthzId, Host, Password) -> end end. -is_user_exists(User, Host) -> +user_exists(User, Host) -> Service = get_pam_service(Host), UserInfo = case get_pam_userinfotype(Host) of username -> User; diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 92c6bb94f..9ab38a763 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -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. @@ -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, diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index 6fdac1971..4c2ca730a 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -281,7 +281,7 @@ get_auth_account(HostOfRule, AccessRule, User, Server, true -> {ok, {User, Server}} end; false -> - case ejabberd_auth:is_user_exists(User, Server) of + case ejabberd_auth:user_exists(User, Server) of true -> {unauthorized, <<"bad-password">>}; false -> {unauthorized, <<"inexistent-account">>} end @@ -1025,7 +1025,7 @@ process_admin(Host, process_admin(Host, #request{path = [<<"user">>, U], auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - case ejabberd_auth:is_user_exists(U, Host) of + case ejabberd_auth:user_exists(U, Host) of true -> Res = user_info(U, Host, Query, Lang), make_xhtml(Res, Host, Lang, AJID); diff --git a/src/extauth.erl b/src/extauth.erl index 70f32bf8f..9f04a44ea 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -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) -> diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 982772af0..0b3b007ce 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -210,7 +210,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"], @@ -1638,7 +1638,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; diff --git a/src/mod_configure.erl b/src/mod_configure.erl index f3eb496d8..3bb9f2279 100644 --- a/src/mod_configure.erl +++ b/src/mod_configure.erl @@ -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, diff --git a/src/mod_disco.erl b/src/mod_disco.erl index 7fde1b410..76be408f4 100644 --- a/src/mod_disco.erl +++ b/src/mod_disco.erl @@ -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">>}]; _ -> [] diff --git a/src/mod_register.erl b/src/mod_register.erl index 5167370c1..c2efca5c1 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -198,7 +198,7 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ, {IsRegistered, Username} = case From of #jid{user = User, lserver = Server} -> - case ejabberd_auth:is_user_exists(User, Server) of + case ejabberd_auth:user_exists(User, Server) of true -> {true, User}; false -> diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl index d5e4112c3..16c2d8020 100644 --- a/src/mod_register_web.erl +++ b/src/mod_register_web.erl @@ -438,7 +438,7 @@ change_password(Username, Host, PasswordOld, end. check_account_exists(Username, Host) -> - case ejabberd_auth:is_user_exists(Username, Host) of + case ejabberd_auth:user_exists(Username, Host) of true -> account_exists; false -> account_doesnt_exist end. diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index 08220f7ec..bc7694fe4 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -390,7 +390,7 @@ search_group_info(State, Group) -> end end, AuthChecker = case State#state.auth_check of - true -> fun ejabberd_auth:is_user_exists/2; + true -> fun ejabberd_auth:user_exists/2; _ -> fun (_U, _S) -> true end end, case eldap_search(State, diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index f97eb18a9..967a2c4cf 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -148,7 +148,7 @@ search_items(Entries, State) -> {U, UIDAttrFormat} -> case eldap_utils:get_user_part(U, UIDAttrFormat) of {ok, Username} -> - case ejabberd_auth:is_user_exists(Username, + case ejabberd_auth:user_exists(Username, LServer) of true -> RFields = lists:map( From 81d9770d4f7ad4239cc1105b21bdcc635d58d02e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 11 May 2017 16:15:18 +0300 Subject: [PATCH 15/91] Update Elixir tests for using new auth API --- test/ejabberd_auth_mock.exs | 2 +- test/ejabberd_cyrsasl_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ejabberd_auth_mock.exs b/test/ejabberd_auth_mock.exs index 08d1d7366..10daf03dc 100644 --- a/test/ejabberd_auth_mock.exs +++ b/test/ejabberd_auth_mock.exs @@ -32,7 +32,7 @@ defmodule EjabberdAuthMock do {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent) - mock(:ejabberd_auth, :is_user_exists, + mock(:ejabberd_auth, :user_exists, fn (user, domain) -> Agent.get(@agent, fn users -> Map.get(users, {user, domain}) end) != nil end) diff --git a/test/ejabberd_cyrsasl_test.exs b/test/ejabberd_cyrsasl_test.exs index a5127f0e0..f601c78ef 100644 --- a/test/ejabberd_cyrsasl_test.exs +++ b/test/ejabberd_cyrsasl_test.exs @@ -114,7 +114,7 @@ defmodule EjabberdCyrsaslTest do fn (_host) -> true end) - mock(:ejabberd_auth, :is_user_exists, + mock(:ejabberd_auth, :user_exists, fn (user, domain) -> domain == "domain1" and get_password(user) != {:false, :internal} end) From a8dc5f80d11305fc73d866d1cc3ecff9ecb2e1bd Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 11 May 2017 16:36:52 +0300 Subject: [PATCH 16/91] Add 'access_remove' ACL to mod_register --- src/mod_register.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mod_register.erl b/src/mod_register.erl index c2efca5c1..d477f149a 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -120,7 +120,7 @@ process_iq(#iq{from = From, to = To} = IQ, Source) -> false -> false end, Server = To#jid.lserver, - Access = gen_mod:get_module_opt(Server, ?MODULE, access, all), + Access = gen_mod:get_module_opt(Server, ?MODULE, access_remove, all), AllowRemove = allow == acl:match_rule(Server, Access, From), process_iq(IQ, Source, IsCaptchaEnabled, AllowRemove). @@ -588,6 +588,7 @@ check_ip_access(IPAddress, IPAccess) -> mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(access_from) -> fun acl:access_rules_validator/1; +mod_opt_type(access_remove) -> fun acl:access_rules_validator/1; mod_opt_type(captcha_protected) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(ip_access) -> fun acl:access_rules_validator/1; @@ -608,7 +609,7 @@ mod_opt_type({welcome_message, subject}) -> mod_opt_type({welcome_message, body}) -> fun iolist_to_binary/1; mod_opt_type(_) -> - [access, access_from, captcha_protected, ip_access, + [access, access_from, access_remove, captcha_protected, ip_access, iqdisc, password_strength, registration_watchers, {welcome_message, subject}, {welcome_message, body}]. From 31a3cc7b10c7f435d1aca31ce3b2c87896a55ec5 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 11 May 2017 17:09:26 +0300 Subject: [PATCH 17/91] Gracefully process malformed passwords during registration --- src/mod_register.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mod_register.erl b/src/mod_register.erl index d477f149a..a213a2441 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -336,6 +336,9 @@ try_register(User, Server, Password, SourceRaw, Lang) -> {error, xmpp:err_conflict(Txt, Lang)}; {error, invalid_jid} -> {error, xmpp:err_jid_malformed()}; + {error, invalid_password} -> + Txt = <<"Incorrect password">>, + {error, xmpp:err_not_allowed(Txt, Lang)}; {error, not_allowed} -> {error, xmpp:err_not_allowed()}; {error, too_many_users} -> From 9fe16a29e118ea36941b9e55f6bd76d9214aca71 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 11 May 2017 17:15:23 +0300 Subject: [PATCH 18/91] Gracefully process malformed passwords during password change --- src/mod_register.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mod_register.erl b/src/mod_register.erl index a213a2441..d6227f00f 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -289,6 +289,9 @@ try_set_password(User, Server, Password, #iq{lang = Lang, meta = M} = IQ) -> xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); {error, invalid_jid} -> xmpp:make_error(IQ, xmpp:err_jid_malformed()); + {error, invalid_password} -> + Txt = <<"Incorrect password">>, + xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang)); Err -> ?ERROR_MSG("failed to register user ~s@~s: ~p", [User, Server, Err]), From d3c8fb77055ff6c07f156bc7a098054301349a30 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 12 May 2017 09:34:57 +0300 Subject: [PATCH 19/91] Check presence of some files during option validation --- src/ejabberd_c2s.erl | 8 +++--- src/ejabberd_config.erl | 2 +- src/ejabberd_http.erl | 6 ++-- src/ejabberd_listener.erl | 51 +--------------------------------- src/ejabberd_s2s.erl | 8 +++--- src/ejabberd_service.erl | 8 +++--- src/ejabberd_sip.erl | 2 +- src/ejabberd_sql.erl | 4 +-- src/ejabberd_stun.erl | 2 +- src/eldap_utils.erl | 2 +- src/misc.erl | 30 +++++++++++++++++++- src/mod_muc_log.erl | 2 +- src/mod_shared_roster_ldap.erl | 4 +-- src/mod_vcard_ldap.erl | 4 +-- 14 files changed, 56 insertions(+), 77 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 92f2a3bdc..ac48444cb 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -904,10 +904,10 @@ transform_listen_option(Opt, Opts) -> (resource_conflict) -> fun((resource_conflict()) -> resource_conflict()); (disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]); (atom()) -> [atom()]. -opt_type(c2s_certfile) -> fun iolist_to_binary/1; -opt_type(c2s_ciphers) -> fun iolist_to_binary/1; -opt_type(c2s_dhfile) -> fun iolist_to_binary/1; -opt_type(c2s_cafile) -> fun iolist_to_binary/1; +opt_type(c2s_certfile) -> fun misc:try_read_file/1; +opt_type(c2s_ciphers) -> fun misc:try_read_file/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) -> diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 75f64ecb5..1dc30b1c5 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -1417,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) -> diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 06da10c9c..e19fbac44 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -926,11 +926,11 @@ opt_type(_) -> [trusted_proxies]. listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end; listen_opt_type(certfile) -> - fun iolist_to_binary/1; + fun misc:try_read_file/1; listen_opt_type(ciphers) -> - fun iolist_to_binary/1; + fun misc:try_read_file/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) -> diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 653ad931a..6dd8e706d 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -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 -> diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index a447dcd67..9e9a94754 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -709,10 +709,10 @@ opt_type(route_subdomains) -> end; opt_type(s2s_access) -> fun acl:access_rules_validator/1; -opt_type(s2s_certfile) -> fun iolist_to_binary/1; -opt_type(s2s_ciphers) -> fun iolist_to_binary/1; -opt_type(s2s_dhfile) -> fun iolist_to_binary/1; -opt_type(s2s_cafile) -> fun iolist_to_binary/1; +opt_type(s2s_certfile) -> fun misc:try_read_file/1; +opt_type(s2s_ciphers) -> fun misc:try_read_file/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) -> diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index 9829d6b6c..fff0eca5b 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -276,10 +276,10 @@ transform_listen_option(Opt, Opts) -> (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(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(certfile) -> fun misc:try_read_file/1; +listen_opt_type(ciphers) -> fun misc:try_read_file/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; diff --git a/src/ejabberd_sip.erl b/src/ejabberd_sip.erl index 5ee81cfa1..ee1f33c83 100644 --- a/src/ejabberd_sip.erl +++ b/src/ejabberd_sip.erl @@ -47,7 +47,7 @@ socket_type() -> raw. listen_opt_type(certfile) -> - fun iolist_to_binary/1; + fun misc:try_read_file/1; listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end; listen_opt_type(_) -> diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index dc6292ce4..7d607781c 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -1104,8 +1104,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 misc:try_read_file/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) -> diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl index 47a6b89de..7557df598 100644 --- a/src/ejabberd_stun.erl +++ b/src/ejabberd_stun.erl @@ -114,7 +114,7 @@ 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 misc:try_read_file/1; listen_opt_type(turn_min_port) -> fun(P) when is_integer(P), P > 0, P =< 65535 -> P end; listen_opt_type(turn_max_port) -> diff --git a/src/eldap_utils.erl b/src/eldap_utils.erl index 3704d2ce5..1dcdf0b48 100644 --- a/src/eldap_utils.erl +++ b/src/eldap_utils.erl @@ -367,7 +367,7 @@ 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_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) -> diff --git a/src/misc.erl b/src/misc.erl index dae95c6d5..b8bbe0e48 100644 --- a/src/misc.erl +++ b/src/misc.erl @@ -33,7 +33,11 @@ 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]). + +-include("logger.hrl"). +-include_lib("kernel/include/file.hrl"). %%%=================================================================== %%% API @@ -241,6 +245,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 %%%=================================================================== diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index 45ee645fc..9bf983422 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -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 diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index bc7694fe4..24bd3233c 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -603,9 +603,9 @@ mod_opt_type(ldap_rootdn) -> fun iolist_to_binary/1; mod_opt_type(ldap_servers) -> fun (L) -> [iolist_to_binary(H) || H <- L] end; mod_opt_type(ldap_tls_cacertfile) -> - fun iolist_to_binary/1; + fun misc:try_read_file/1; mod_opt_type(ldap_tls_certfile) -> - fun iolist_to_binary/1; + fun misc:try_read_file/1; mod_opt_type(ldap_tls_depth) -> fun (I) when is_integer(I), I >= 0 -> I end; mod_opt_type(ldap_tls_verify) -> diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index 967a2c4cf..fe11d0962 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -467,9 +467,9 @@ mod_opt_type(ldap_rootdn) -> fun iolist_to_binary/1; mod_opt_type(ldap_servers) -> fun (L) -> [iolist_to_binary(H) || H <- L] end; mod_opt_type(ldap_tls_cacertfile) -> - fun iolist_to_binary/1; + fun misc:try_read_file/1; mod_opt_type(ldap_tls_certfile) -> - fun iolist_to_binary/1; + fun misc:try_read_file/1; mod_opt_type(ldap_tls_depth) -> fun (I) when is_integer(I), I >= 0 -> I end; mod_opt_type(ldap_tls_verify) -> From cc58ce6301a6a60d5b3f67fb17731200572fff4b Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 12 May 2017 16:27:09 +0300 Subject: [PATCH 20/91] Introduce Certficate Manager The major goal is to simplify certificate management in ejabberd. Currently it requires some effort from a user to configure certficates, especially in the situation where a lot of virtual domains are hosted. The task is splitted in several sub-tasks: * Implement basic certificate validator. The validator should check all configured certificates for existence, validity, duration and so on. The validator should not perform any actions in the case of errors except logging an error message. This is actually implemented by this commit. * All certificates should be configured inside a single section (something like 'certfiles') where ejabberd should parse them, check the full-chain, find the corresponding private keys and, if needed, resort chains and split the certficates into separate files for easy to use by fast_tls. * Options like 'domain_certfile', 'c2s_certfile' or 's2s_certfile' should probably be deprecated, since the process of matching certificates with the corresponding virtual hosts should be done automatically and these options only introduce configuration errors without any meaningful purpose. --- src/ejabberd_c2s.erl | 6 +- src/ejabberd_http.erl | 5 +- src/ejabberd_pkix.erl | 513 +++++++++++++++++++++++++++++++++++++++ src/ejabberd_router.erl | 2 + src/ejabberd_s2s_in.erl | 6 +- src/ejabberd_service.erl | 6 +- src/ejabberd_sip.erl | 5 +- src/ejabberd_stun.erl | 5 +- src/ejabberd_sup.erl | 3 + 9 files changed, 545 insertions(+), 6 deletions(-) create mode 100644 src/ejabberd_pkix.erl diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index ac48444cb..0ac39518f 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -949,7 +949,11 @@ opt_type(_) -> (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); diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index e19fbac44..9f2c87e9b 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -926,7 +926,10 @@ opt_type(_) -> [trusted_proxies]. listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end; listen_opt_type(certfile) -> - fun misc:try_read_file/1; + fun(S) -> + ejabberd_pkix:add_certfile(S), + iolist_to_binary(S) + end; listen_opt_type(ciphers) -> fun misc:try_read_file/1; listen_opt_type(dhfile) -> diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl new file mode 100644 index 000000000..ffdc0cea4 --- /dev/null +++ b/src/ejabberd_pkix.erl @@ -0,0 +1,513 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% Created : 4 Mar 2017 by Evgeny Khramtsov +%%% +%%% +%%% 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, 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(Path0) -> + Path = case filename:pathtype(Path0) of + relative -> + {ok, CWD} = file:get_cwd(), + iolist_to_binary(filename:join(CWD, Path0)); + _ -> + iolist_to_binary(Path0) + end, + gen_server:call(?MODULE, {add_certfile, Path}). + +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; + _ -> true + 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 = public_key: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. diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index 844651329..69413c6de 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -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", diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index c215557c4..d8e124b52 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -356,7 +356,11 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State, (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); diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index fff0eca5b..a83ec41f0 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -276,7 +276,11 @@ transform_listen_option(Opt, Opts) -> (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 misc:try_read_file/1; +listen_opt_type(certfile) -> + fun(S) -> + ejabberd_pkix:add_certfile(S), + iolist_to_binary(S) + end; listen_opt_type(ciphers) -> fun misc:try_read_file/1; listen_opt_type(dhfile) -> fun misc:try_read_file/1; listen_opt_type(cafile) -> fun misc:try_read_file/1; diff --git a/src/ejabberd_sip.erl b/src/ejabberd_sip.erl index ee1f33c83..d7404a30e 100644 --- a/src/ejabberd_sip.erl +++ b/src/ejabberd_sip.erl @@ -47,7 +47,10 @@ socket_type() -> raw. listen_opt_type(certfile) -> - fun misc:try_read_file/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(_) -> diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl index 7557df598..3611edba7 100644 --- a/src/ejabberd_stun.erl +++ b/src/ejabberd_stun.erl @@ -114,7 +114,10 @@ listen_opt_type(auth_realm) -> listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end; listen_opt_type(certfile) -> - fun misc:try_read_file/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) -> diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index 91afbe89d..224ed16c1 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -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, From 2d17a2850c0d5fd61465c30c8430596b9071ffde Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 12 May 2017 17:51:17 +0300 Subject: [PATCH 21/91] Only validate certfiles if public_key:short_name_hash/1 is available --- src/ejabberd_pkix.erl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl index ffdc0cea4..f99a2c12e 100644 --- a/src/ejabberd_pkix.erl +++ b/src/ejabberd_pkix.erl @@ -152,7 +152,10 @@ init([]) -> ejabberd_hooks:add(route_registered, ?MODULE, route_registered, 50), Validate = case os:type() of {win32, _} -> false; - _ -> true + _ -> + code:ensure_loaded(public_key), + erlang:function_exported( + public_key, short_name_hash, 1) end, if Validate -> check_ca_dir(); true -> ok From 061d5f2380cdce9ae1e0fa94487346e0e1c67cec Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sat, 13 May 2017 13:11:08 +0300 Subject: [PATCH 22/91] Shut up dialyzer/xref if public_key:short_name_hash/1 is not available --- rebar.config | 1 + rebar.config.script | 9 +++++++++ src/ejabberd_pkix.erl | 10 +++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 7def98c79..b2f6ab4d7 100644 --- a/rebar.config +++ b/rebar.config @@ -90,6 +90,7 @@ {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, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}}, {if_var_true, hipe, native}, {src_dirs, [asn1, src, {if_var_true, tools, tools}, diff --git a/rebar.config.script b/rebar.config.script index 19142b9ee..d2ee31213 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -141,6 +141,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) -> diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl index f99a2c12e..7c03f1772 100644 --- a/src/ejabberd_pkix.erl +++ b/src/ejabberd_pkix.erl @@ -393,7 +393,7 @@ check_ca_dir() -> -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 = public_key:short_name_hash(IssuerID), + Hash = short_name_hash(IssuerID), filelib:fold_files( ca_dir(), Hash ++ "\\.[0-9]+", false, fun(_, {ok, IssuerCert}) -> @@ -514,3 +514,11 @@ get_cert_path(G, [Root|_] = Acc) -> get_cert_path(G, [V|Acc]) end, Es) end. + +-ifdef(SHORT_NAME_HASH). +short_name_hash(IssuerID) -> + public_key:short_name_hash(IssuerID). +-else. +short_name_hash(_) -> + "". +-endif. From 3f13396d73019e6aa083df369994c06bde1106a3 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 15 May 2017 08:58:37 +0300 Subject: [PATCH 23/91] Fix use_cache/1 callback --- src/ejabberd_auth_mnesia.erl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 02c22f9d5..9c2152578 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -77,10 +77,14 @@ update_reg_users_counter_table(Server) -> end, mnesia:sync_dirty(F). -use_cache(_) -> +use_cache(Host) -> case mnesia:table_info(passwd, storage_type) of - disc_only_copies -> true; - _ -> false + disc_only_copies -> + ejabberd_config:get_option( + {auth_use_cache, Host}, + ejabberd_config:use_cache(Host)); + _ -> + false end. plain_password_required(Server) -> From f782955c069f1091cd56cc9d139ec0b0e2d62780 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 14:47:35 +0300 Subject: [PATCH 24/91] Implement cache for roster --- include/pubsub.hrl | 7 - rebar.config | 2 +- src/ejabberd_c2s.erl | 240 +++++++------- src/ejabberd_sm.erl | 2 +- src/mod_block_strangers.erl | 55 ++-- src/mod_caps.erl | 4 +- src/mod_last.erl | 5 +- src/mod_mam.erl | 52 ++-- src/mod_pubsub.erl | 48 +-- src/mod_roster.erl | 550 ++++++++++++++++++--------------- src/mod_roster_mnesia.erl | 47 ++- src/mod_roster_riak.erl | 51 +-- src/mod_roster_sql.erl | 86 ++---- src/mod_shared_roster.erl | 25 +- src/mod_shared_roster_ldap.erl | 24 +- test/roster_tests.erl | 92 ++++-- 16 files changed, 646 insertions(+), 644 deletions(-) diff --git a/include/pubsub.hrl b/include/pubsub.hrl index 86cdb515d..8fbbf710c 100644 --- a/include/pubsub.hrl +++ b/include/pubsub.hrl @@ -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' diff --git a/rebar.config b/rebar.config index b2f6ab4d7..be9005679 100644 --- a/rebar.config +++ b/rebar.config @@ -20,7 +20,7 @@ {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", "51eee22"}}, + {cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "6f762a59"}}, {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"}}}, diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 0ac39518f..00a2bad80 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -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", @@ -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); diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 9ab38a763..ea19f832f 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -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). diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl index 6a4a96bf9..636d5077b 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -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. diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 5609030d3..87fdebbc8 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -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), diff --git a/src/mod_last.erl b/src/mod_last.erl index 052c17c02..79b3d614f 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -157,7 +157,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( diff --git a/src/mod_mam.erl b/src/mod_mam.erl index c84cb907b..eb2082fe2 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -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 -> @@ -615,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}, @@ -635,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. @@ -719,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, diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl index 2a9fa18c7..91d2f928a 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -43,6 +43,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). -include("pubsub.hrl"). +-include("mod_roster.hrl"). -define(STDTREE, <<"tree">>). -define(STDNODE, <<"flat">>). @@ -405,9 +406,19 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) -> TreePlugin:terminate(Host, ServerHost), ok. +get_subscribed(User, Server) -> + Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]), + lists:filtermap( + fun(#roster{jid = LJID, subscription = Sub}) + when Sub == both orelse Sub == from -> + {true, LJID}; + (_) -> + false + end, Items). + send_loop(State) -> receive - {presence, JID, Pid} -> + {presence, JID, _Pid} -> Host = State#state.host, ServerHost = State#state.server_host, DBType = State#state.db_type, @@ -429,26 +440,21 @@ send_loop(State) -> State#state.plugins), if not State#state.ignore_pep_from_offline -> {User, Server, Resource} = LJID, - case catch ejabberd_c2s:get_subscribed(Pid) of - Contacts when is_list(Contacts) -> - lists:foreach( - fun({U, S, R}) when S == ServerHost -> - case user_resources(U, S) of - [] -> %% offline - PeerJID = jid:make(U, S, R), - self() ! {presence, User, Server, [Resource], PeerJID}; - _ -> %% online - % this is already handled by presence probe - ok - end; - (_) -> - % we can not do anything in any cases - ok - end, - Contacts); - _ -> - ok - end; + Contacts = get_subscribed(User, Server), + lists:foreach( + fun({U, S, R}) when S == ServerHost -> + case user_resources(U, S) of + [] -> %% offline + PeerJID = jid:make(U, S, R), + self() ! {presence, User, Server, [Resource], PeerJID}; + _ -> %% online + %% this is already handled by presence probe + ok + end; + (_) -> + %% we can not do anything in any cases + ok + end, Contacts); true -> ok end, diff --git a/src/mod_roster.erl b/src/mod_roster.erl index f2bfc59de..b256f107d 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -43,8 +43,8 @@ -export([start/2, stop/1, reload/3, process_iq/1, export/1, import_info/0, process_local_iq/1, get_user_roster/2, - import/5, c2s_session_opened/1, get_roster/2, - import_start/2, import_stop/2, user_receive_packet/1, + import/5, get_roster/2, + import_start/2, import_stop/2, c2s_self_presence/1, in_subscription/6, out_subscription/4, set_items/3, remove_user/2, get_jid_info/4, encode_item/1, webadmin_page/3, @@ -63,38 +63,41 @@ -include("ejabberd_web_admin.hrl"). --define(SETS, gb_sets). +-define(ROSTER_CACHE, roster_cache). +-define(ROSTER_ITEM_CACHE, roster_item_cache). +-define(ROSTER_VERSION_CACHE, roster_version_cache). -export_type([subscription/0]). -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), binary(), #roster{} | [binary()]) -> ok. --callback read_roster_version(binary(), binary()) -> binary() | error. +-callback read_roster_version(binary(), binary()) -> {ok, binary()} | error. -callback write_roster_version(binary(), binary(), boolean(), binary()) -> any(). --callback get_roster(binary(), binary()) -> [#roster{}]. --callback get_roster_by_jid(binary(), binary(), ljid()) -> #roster{}. --callback get_only_items(binary(), binary()) -> [#roster{}]. +-callback get_roster(binary(), binary()) -> {ok, [#roster{}]} | error. +-callback get_roster_item(binary(), binary(), ljid()) -> {ok, #roster{}} | error. +-callback read_subscription_and_groups(binary(), binary(), ljid()) + -> {ok, {subscription(), [binary()]}} | error. -callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any(). -callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}. --callback get_roster_by_jid_with_groups(binary(), binary(), ljid()) -> #roster{}. --callback remove_user(binary(), binary()) -> {atomic, any()}. +-callback remove_user(binary(), binary()) -> any(). -callback update_roster(binary(), binary(), ljid(), #roster{}) -> any(). -callback del_roster(binary(), binary(), ljid()) -> any(). --callback read_subscription_and_groups(binary(), binary(), ljid()) -> - {subscription(), [binary()]}. +-callback use_cache(binary(), roster | roster_version) -> boolean(). +-callback cache_nodes(binary()) -> [node()]. + +-optional_callbacks([use_cache/2, 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(roster_get, Host, ?MODULE, get_user_roster, 50), ejabberd_hooks:add(roster_in_subscription, Host, ?MODULE, in_subscription, 50), ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, out_subscription, 50), - ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, - c2s_session_opened, 50), ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, get_jid_info, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE, @@ -107,8 +110,6 @@ start(Host, Opts) -> webadmin_page, 50), ejabberd_hooks:add(webadmin_user, Host, ?MODULE, webadmin_user, 50), - ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, - user_receive_packet, 50), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ROSTER, ?MODULE, process_iq, IQDisc). @@ -119,8 +120,6 @@ stop(Host) -> ?MODULE, in_subscription, 50), ejabberd_hooks:delete(roster_out_subscription, Host, ?MODULE, out_subscription, 50), - ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE, - c2s_session_opened, 50), ejabberd_hooks:delete(roster_get_jid_info, Host, ?MODULE, get_jid_info, 50), ejabberd_hooks:delete(remove_user, Host, ?MODULE, @@ -133,8 +132,6 @@ stop(Host) -> webadmin_page, 50), ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, webadmin_user, 50), - ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, - user_receive_packet, 50), gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ROSTER). @@ -244,7 +241,7 @@ roster_version(LServer, LUser) -> true -> case read_roster_version(LUser, LServer) of error -> not_found; - V -> V + {ok, V} -> V end; false -> roster_hash(ejabberd_hooks:run_fold(roster_get, LServer, @@ -252,8 +249,12 @@ roster_version(LServer, LUser) -> end. read_roster_version(LUser, LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:read_roster_version(LUser, LServer). + ets_cache:lookup( + ?ROSTER_VERSION_CACHE, {LUser, LServer}, + fun() -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:read_roster_version(LUser, LServer) + end). write_roster_version(LUser, LServer) -> write_roster_version(LUser, LServer, false). @@ -265,6 +266,11 @@ write_roster_version(LUser, LServer, InTransaction) -> Ver = str:sha(term_to_binary(p1_time_compat:unique_integer())), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:write_roster_version(LUser, LServer, InTransaction, Ver), + if InTransaction -> ok; + true -> + ets_cache:delete(?ROSTER_VERSION_CACHE, {LUser, LServer}, + cache_nodes(Mod, LServer)) + end, Ver. %% Load roster from DB only if neccesary. @@ -289,9 +295,9 @@ process_iq_get(#iq{to = To, lang = Lang, ejabberd_hooks:run_fold( roster_get, To#jid.lserver, [], [US])), RosterVersion}; - RequestedVersion -> + {ok, RequestedVersion} -> {false, false}; - NewVersion -> + {ok, NewVersion} -> {lists:map(fun encode_item/1, ejabberd_hooks:run_fold( roster_get, To#jid.lserver, [], [US])), @@ -343,18 +349,72 @@ get_user_roster(Acc, {LUser, LServer}) -> get_roster(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_roster(LUser, LServer). + R = case use_cache(Mod, LServer, roster) of + true -> + ets_cache:lookup( + ?ROSTER_CACHE, {LUser, LServer}, + fun() -> Mod:get_roster(LUser, LServer) end); + false -> + Mod:get_roster(LUser, LServer) + end, + case R of + {ok, Items} -> Items; + error -> [] + end. + +get_roster_item(LUser, LServer, LJID) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:get_roster_item(LUser, LServer, LJID) of + {ok, Item} -> + Item; + error -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID} + end. + +get_subscription_and_groups(LUser, LServer, LJID) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Res = case use_cache(Mod, LServer, roster) of + true -> + LBJID = jid:remove_resource(LJID), + ets_cache:lookup( + ?ROSTER_ITEM_CACHE, {LUser, LServer, LBJID}, + fun() -> + Items = get_roster(LUser, LServer), + case lists:keyfind(LBJID, #roster.jid, Items) of + #roster{subscription = Sub, groups = Groups} -> + {ok, {Sub, Groups}}; + false when element(3, LJID) == <<"">> -> + error; + false -> + case lists:keyfind(LJID, #roster.jid, Items) of + {Sub, Groups} -> + {ok, {Sub, Groups}}; + false -> + error + end + end + end); + false -> + Mod:read_subscription_and_groups(LUser, LServer, LJID) + end, + case Res of + {ok, SubAndGroups} -> + SubAndGroups; + error -> + {none, []} + end. set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) -> transaction( - LServer, + LUser, LServer, [LJID], fun() -> update_roster_t(LUser, LServer, LJID, Item) end). del_roster(LUser, LServer, LJID) -> transaction( - LServer, + LUser, LServer, [LJID], fun() -> del_roster_t(LUser, LServer, LJID) end). @@ -387,46 +447,36 @@ decode_item(Item, R, Managed) -> end, groups = Item#roster_item.groups}. -get_roster_by_jid_t(LUser, LServer, LJID) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_roster_by_jid(LUser, LServer, LJID). - process_iq_set(#iq{from = From, to = To, - sub_els = [#roster_query{items = QueryItems}]} = IQ) -> + sub_els = [#roster_query{items = [QueryItem]}]} = IQ) -> #jid{user = User, luser = LUser, lserver = LServer} = To, Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer}, + LJID = jid:tolower(QueryItem#roster_item.jid), F = fun () -> - lists:map( - fun(#roster_item{jid = JID1} = QueryItem) -> - LJID = jid:tolower(JID1), - Item = get_roster_by_jid_t(LUser, LServer, LJID), - Item2 = decode_item(QueryItem, Item, Managed), - Item3 = ejabberd_hooks:run_fold(roster_process_item, - LServer, Item2, - [LServer]), - case Item3#roster.subscription of - remove -> del_roster_t(LUser, LServer, LJID); - _ -> update_roster_t(LUser, LServer, LJID, Item3) - end, - case roster_version_on_db(LServer) of - true -> write_roster_version_t(LUser, LServer); - false -> ok - end, - {Item, Item3} - end, QueryItems) + Item = get_roster_item(LUser, LServer, LJID), + Item2 = decode_item(QueryItem, Item, Managed), + Item3 = ejabberd_hooks:run_fold(roster_process_item, + LServer, Item2, + [LServer]), + case Item3#roster.subscription of + remove -> del_roster_t(LUser, LServer, LJID); + _ -> update_roster_t(LUser, LServer, LJID, Item3) + end, + case roster_version_on_db(LServer) of + true -> write_roster_version_t(LUser, LServer); + false -> ok + end, + {Item, Item3} end, - case transaction(LServer, F) of - {atomic, ItemPairs} -> - lists:foreach( - fun({OldItem, Item}) -> - push_item(User, LServer, To, Item), - case Item#roster.subscription of - remove -> - send_unsubscribing_presence(To, OldItem); - _ -> - ok - end - end, ItemPairs), + case transaction(LUser, LServer, [LJID], F) of + {atomic, {OldItem, Item}} -> + push_item(User, LServer, To, OldItem, Item), + case Item#roster.subscription of + remove -> + send_unsubscribing_presence(To, OldItem); + _ -> + ok + end, xmpp:make_iq_result(IQ); E -> ?ERROR_MSG("roster set failed:~nIQ = ~s~nError = ~p", @@ -434,126 +484,66 @@ process_iq_set(#iq{from = From, to = To, xmpp:make_error(IQ, xmpp:err_internal_server_error()) end. -push_item(User, Server, From, Item) -> +push_item(User, Server, From, OldItem, NewItem) -> case roster_versioning_enabled(Server) of - true -> - push_item_version(Server, User, From, Item, - roster_version(Server, User)); - false -> - lists:foreach(fun (Resource) -> - push_item(User, Server, Resource, From, Item) - end, - ejabberd_sm:get_user_resources(User, Server)) + true -> + push_item_version(Server, User, From, OldItem, NewItem, + roster_version(Server, User)); + false -> + lists:foreach( + fun(Resource) -> + push_item(User, Server, Resource, From, OldItem, NewItem) + end, ejabberd_sm:get_user_resources(User, Server)) end. -push_item(User, Server, Resource, From, Item) -> - push_item(User, Server, Resource, From, Item, - not_found). +push_item(User, Server, Resource, From, OldItem, NewItem) -> + push_item(User, Server, Resource, From, OldItem, NewItem, undefined). -push_item(User, Server, Resource, From, Item, - RosterVersion) -> - Ver = case RosterVersion of - not_found -> undefined; - _ -> RosterVersion - end, +push_item(User, Server, Resource, From, OldItem, NewItem, Ver) -> To = jid:make(User, Server, Resource), + route_presence_change(To, OldItem, NewItem), ResIQ = #iq{type = set, from = From, to = To, id = <<"push", (randoms:get_string())/binary>>, sub_els = [#roster_query{ver = Ver, - items = [encode_item(Item)]}]}, - ejabberd_router:route(xmpp:put_meta(ResIQ, roster_item, Item)). + items = [encode_item(NewItem)]}]}, + ejabberd_router:route(ResIQ). -push_item_version(Server, User, From, Item, - RosterVersion) -> - lists:foreach(fun (Resource) -> - push_item(User, Server, Resource, From, Item, - RosterVersion) - end, - ejabberd_sm:get_user_resources(User, Server)). +push_item_version(Server, User, From, OldItem, NewItem, RosterVersion) -> + lists:foreach( + fun(Resource) -> + push_item(User, Server, Resource, From, + OldItem, NewItem, RosterVersion) + end, ejabberd_sm:get_user_resources(User, Server)). --spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}. -user_receive_packet({#iq{type = set, meta = #{roster_item := Item}} = IQ, State}) -> - {IQ, roster_change(State, Item)}; -user_receive_packet(Acc) -> - Acc. - --spec roster_change(ejabberd_c2s:state(), #roster{}) -> ejabberd_c2s:state(). -roster_change(#{user := U, server := S, resource := R, - pres_a := PresA, pres_f := PresF, pres_t := PresT} = State, - #roster{jid = IJID, subscription = ISubscription}) -> - LIJID = jid:tolower(IJID), - IsFrom = (ISubscription == both) or (ISubscription == from), - IsTo = (ISubscription == both) or (ISubscription == to), - OldIsFrom = ?SETS:is_element(LIJID, PresF), - FSet = if IsFrom -> ?SETS:add_element(LIJID, PresF); - true -> ?SETS:del_element(LIJID, PresF) - end, - TSet = if IsTo -> ?SETS:add_element(LIJID, PresT); - true -> ?SETS:del_element(LIJID, PresT) - end, - State1 = State#{pres_f => FSet, pres_t => TSet}, - case maps:get(pres_last, State, undefined) of - undefined -> - State1; - LastPres -> - From = jid:make(U, S, R), - To = jid:make(IJID), - Cond1 = IsFrom andalso not OldIsFrom, - Cond2 = not IsFrom andalso OldIsFrom andalso - ?SETS:is_element(LIJID, PresA), - if Cond1 -> - case ejabberd_hooks:run_fold( - privacy_check_packet, allow, - [State1, LastPres, out]) of - deny -> - ok; - allow -> - Pres = xmpp:set_from_to(LastPres, From, To), - ejabberd_router:route(Pres) - end, - A = ?SETS:add_element(LIJID, PresA), - State1#{pres_a => A}; - Cond2 -> - PU = #presence{from = From, to = To, type = unavailable}, - case ejabberd_hooks:run_fold( - privacy_check_packet, allow, - [State1, PU, out]) of - deny -> - ok; - allow -> - ejabberd_router:route(PU) - end, - A = ?SETS:del_element(LIJID, PresA), - State1#{pres_a => A}; - true -> - State1 - end +-spec route_presence_change(jid(), #roster{}, #roster{}) -> ok. +route_presence_change(From, OldItem, NewItem) -> + OldSub = OldItem#roster.subscription, + NewSub = NewItem#roster.subscription, + To = jid:make(NewItem#roster.jid), + NewIsFrom = NewSub == both orelse NewSub == from, + OldIsFrom = OldSub == both orelse OldSub == from, + if NewIsFrom andalso not OldIsFrom -> + case ejabberd_sm:get_session_pid( + From#jid.luser, From#jid.lserver, From#jid.lresource) of + none -> + ok; + Pid -> + ejabberd_c2s:resend_presence(Pid, To) + end; + OldIsFrom andalso not NewIsFrom -> + PU = #presence{from = From, to = To, type = unavailable}, + case ejabberd_hooks:run_fold( + privacy_check_packet, allow, + [From, PU, out]) of + deny -> + ok; + allow -> + ejabberd_router:route(PU) + end; + true -> + ok end. --spec c2s_session_opened(ejabberd_c2s:state()) -> ejabberd_c2s:state(). -c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer}, - pres_f := PresF, pres_t := PresT} = State) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Items = Mod:get_only_items(LUser, LServer), - {F, T} = fill_subscription_lists(Items, PresF, PresT), - State#{pres_f => F, pres_t => T}. - -fill_subscription_lists([I | Is], F, T) -> - J = element(3, I#roster.usj), - {F1, T1} = case I#roster.subscription of - both -> - {?SETS:add_element(J, F), ?SETS:add_element(J, T)}; - from -> - {?SETS:add_element(J, F), T}; - to -> - {F, ?SETS:add_element(J, T)}; - _ -> - {F, T} - end, - fill_subscription_lists(Is, F1, T1); -fill_subscription_lists([], F, T) -> - {F, T}. - ask_to_pending(subscribe) -> out; ask_to_pending(unsubscribe) -> none; ask_to_pending(Ask) -> Ask. @@ -562,9 +552,15 @@ roster_subscribe_t(LUser, LServer, LJID, Item) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:roster_subscribe(LUser, LServer, LJID, Item). -transaction(LServer, F) -> +transaction(LUser, LServer, LJIDs, F) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:transaction(LServer, F). + case Mod:transaction(LServer, F) of + {atomic, _} = Result -> + delete_cache(Mod, LUser, LServer, LJIDs), + Result; + Err -> + Err + end. -spec in_subscription(boolean(), binary(), binary(), jid(), subscribe | subscribed | unsubscribe | unsubscribed, @@ -579,18 +575,13 @@ in_subscription(_, User, Server, JID, Type, Reason) -> out_subscription(User, Server, JID, Type) -> process_subscription(out, User, Server, JID, Type, <<"">>). -get_roster_by_jid_with_groups_t(LUser, LServer, LJID) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_roster_by_jid_with_groups(LUser, LServer, LJID). - process_subscription(Direction, User, Server, JID1, Type, Reason) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), LJID = jid:tolower(JID1), F = fun () -> - Item = get_roster_by_jid_with_groups_t(LUser, LServer, - LJID), + Item = get_roster_item(LUser, LServer, LJID), NewState = case Direction of out -> out_state_change(Item#roster.subscription, @@ -611,46 +602,48 @@ process_subscription(Direction, User, Server, JID1, _ -> <<"">> end, case NewState of - none -> {none, AutoReply}; - {none, none} - when Item#roster.subscription == none, - Item#roster.ask == in -> - del_roster_t(LUser, LServer, LJID), {none, AutoReply}; - {Subscription, Pending} -> - NewItem = Item#roster{subscription = Subscription, - ask = Pending, - askmessage = AskMessage}, - roster_subscribe_t(LUser, LServer, LJID, NewItem), - case roster_version_on_db(LServer) of - true -> write_roster_version_t(LUser, LServer); - false -> ok - end, - {{push, NewItem}, AutoReply} + none -> + {none, AutoReply}; + {none, none} when Item#roster.subscription == none, + Item#roster.ask == in -> + del_roster_t(LUser, LServer, LJID), {none, AutoReply}; + {Subscription, Pending} -> + NewItem = Item#roster{subscription = Subscription, + ask = Pending, + askmessage = AskMessage}, + roster_subscribe_t(LUser, LServer, LJID, NewItem), + case roster_version_on_db(LServer) of + true -> write_roster_version_t(LUser, LServer); + false -> ok + end, + {{push, Item, NewItem}, AutoReply} end end, - case transaction(LServer, F) of - {atomic, {Push, AutoReply}} -> - case AutoReply of - none -> ok; - _ -> - ejabberd_router:route( - #presence{type = AutoReply, - from = jid:make(User, Server), - to = JID1}) - end, - case Push of - {push, Item} -> - if Item#roster.subscription == none, - Item#roster.ask == in -> - ok; - true -> - push_item(User, Server, - jid:make(User, Server), Item) - end, - true; - none -> false - end; - _ -> false + case transaction(LUser, LServer, [LJID], F) of + {atomic, {Push, AutoReply}} -> + case AutoReply of + none -> ok; + _ -> + ejabberd_router:route( + #presence{type = AutoReply, + from = jid:make(User, Server), + to = JID1}) + end, + case Push of + {push, OldItem, NewItem} -> + if NewItem#roster.subscription == none, + NewItem#roster.ask == in -> + ok; + true -> + push_item(User, Server, + jid:make(User, Server), OldItem, NewItem) + end, + true; + none -> + false + end; + _ -> + false end. %% in_state_change(Subscription, Pending, Type) -> NewState @@ -772,16 +765,16 @@ in_auto_reply(_, _, _) -> none. remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - send_unsubscription_to_rosteritems(LUser, LServer), + Items = get_user_roster([], {LUser, LServer}), + send_unsubscription_to_rosteritems(LUser, LServer, Items), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:remove_user(LUser, LServer), - ok. + delete_cache(Mod, LUser, LServer, [Item#roster.jid || Item <- Items]). %% For each contact with Subscription: %% Both or From, send a "unsubscribed" presence stanza; %% Both or To, send a "unsubscribe" presence stanza. -send_unsubscription_to_rosteritems(LUser, LServer) -> - RosterItems = get_user_roster([], {LUser, LServer}), +send_unsubscription_to_rosteritems(LUser, LServer, RosterItems) -> From = jid:make({LUser, LServer, <<"">>}), lists:foreach(fun (RosterItem) -> send_unsubscribing_presence(From, RosterItem) @@ -821,12 +814,14 @@ send_unsubscribing_presence(From, Item) -> set_items(User, Server, #roster_query{items = Items}) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), + LJIDs = [jid:tolower(Item#roster_item.jid) || Item <- Items], F = fun () -> - lists:foreach(fun (Item) -> - process_item_set_t(LUser, LServer, Item) - end, Items) + lists:foreach( + fun(Item) -> + process_item_set_t(LUser, LServer, Item) + end, Items) end, - transaction(LServer, F). + transaction(LUser, LServer, LJIDs, F). update_roster_t(LUser, LServer, LJID, Item) -> Mod = gen_mod:db_mod(LServer, ?MODULE), @@ -856,8 +851,7 @@ c2s_self_presence({#presence{type = available} = Pkt, #{lserver := LServer} = State}) -> Prio = get_priority_from_presence(Pkt), if Prio >= 0 -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - State1 = resend_pending_subscriptions(State, Mod), + State1 = resend_pending_subscriptions(State), {Pkt, State1}; true -> {Pkt, State} @@ -865,10 +859,10 @@ c2s_self_presence({#presence{type = available} = Pkt, c2s_self_presence(Acc) -> Acc. --spec resend_pending_subscriptions(ejabberd_c2s:state(), module()) -> ejabberd_c2s:state(). -resend_pending_subscriptions(#{jid := JID} = State, Mod) -> +-spec resend_pending_subscriptions(ejabberd_c2s:state()) -> ejabberd_c2s:state(). +resend_pending_subscriptions(#{jid := JID} = State) -> BareJID = jid:remove_resource(JID), - Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver), + Result = get_roster(JID#jid.luser, JID#jid.lserver), lists:foldl( fun(#roster{ask = Ask} = R, AccState) when Ask == in; Ask == both -> Message = R#roster.askmessage, @@ -892,30 +886,13 @@ get_priority_from_presence(#presence{priority = Prio}) -> end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -read_subscription_and_groups(User, Server, LJID) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:read_subscription_and_groups(LUser, LServer, LJID). - -spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid()) -> {subscription(), [binary()]}. get_jid_info(_, User, Server, JID) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), LJID = jid:tolower(JID), - case read_subscription_and_groups(User, Server, LJID) of - {Subscription, Groups} -> {Subscription, Groups}; - error -> - LRJID = jid:tolower(jid:remove_resource(JID)), - if LRJID == LJID -> {none, []}; - true -> - case read_subscription_and_groups(User, Server, LRJID) - of - {Subscription, Groups} -> {Subscription, Groups}; - error -> {none, []} - end - end - end. + get_subscription_and_groups(LUser, LServer, LJID). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1118,6 +1095,81 @@ has_duplicated_groups(Groups) -> GroupsPrep = lists:usort([jid:resourceprep(G) || G <- Groups]), not (length(GroupsPrep) == length(Groups)). +-spec init_cache(module(), binary(), gen_mod:opts()) -> ok. +init_cache(Mod, Host, Opts) -> + CacheOpts = cache_opts(Host, Opts), + case use_cache(Mod, Host, roster_version) of + true -> + ets_cache:new(?ROSTER_VERSION_CACHE, CacheOpts); + false -> + ets_cache:delete(?ROSTER_VERSION_CACHE) + end, + case use_cache(Mod, Host, roster) of + true -> + ets_cache:new(?ROSTER_CACHE, CacheOpts), + ets_cache:new(?ROSTER_ITEM_CACHE, CacheOpts); + false -> + ets_cache:delete(?ROSTER_CACHE), + ets_cache:delete(?ROSTER_ITEM_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(), roster | roster_version) -> boolean(). +use_cache(Mod, Host, Table) -> + case erlang:function_exported(Mod, use_cache, 2) of + true -> Mod:use_cache(Host, Table); + 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(), [ljid()]) -> ok. +delete_cache(Mod, LUser, LServer, LJIDs) -> + case use_cache(Mod, LServer, roster_version) of + true -> + ets_cache:delete(?ROSTER_VERSION_CACHE, {LUser, LServer}, + cache_nodes(Mod, LServer)); + false -> + ok + end, + case use_cache(Mod, LServer, roster) of + true -> + Nodes = cache_nodes(Mod, LServer), + ets_cache:delete(?ROSTER_CACHE, {LUser, LServer}, Nodes), + lists:foreach( + fun(LJID) -> + ets_cache:delete( + ?ROSTER_ITEM_CACHE, + {LUser, LServer, jid:remove_resource(LJID)}, + Nodes) + end, LJIDs); + false -> + ok + end. + export(LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:export(LServer). diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl index 0207b6dc5..723c1722e 100644 --- a/src/mod_roster_mnesia.erl +++ b/src/mod_roster_mnesia.erl @@ -28,10 +28,10 @@ %% API -export([init/2, read_roster_version/2, write_roster_version/4, - get_roster/2, get_roster_by_jid/3, get_only_items/2, - roster_subscribe/4, get_roster_by_jid_with_groups/3, + get_roster/2, get_roster_item/3, roster_subscribe/4, remove_user/2, update_roster/4, del_roster/3, transaction/2, - read_subscription_and_groups/3, import/3, create_roster/1]). + read_subscription_and_groups/3, import/3, create_roster/1, + use_cache/2]). -export([need_transform/1, transform/1]). -include("mod_roster.hrl"). @@ -42,18 +42,28 @@ %%%=================================================================== init(_Host, _Opts) -> ejabberd_mnesia:create(?MODULE, roster, - [{disc_copies, [node()]}, + [{disc_only_copies, [node()]}, {attributes, record_info(fields, roster)}, {index, [us]}]), ejabberd_mnesia:create(?MODULE, roster_version, - [{disc_copies, [node()]}, + [{disc_only_copies, [node()]}, {attributes, record_info(fields, roster_version)}]). +use_cache(Host, Table) -> + case mnesia:table_info(Table, storage_type) of + disc_only_copies -> + gen_mod:get_module_opt( + Host, ?MODULE, use_cache, + ejabberd_config:use_cache(Host)); + _ -> + false + end. + read_roster_version(LUser, LServer) -> US = {LUser, LServer}, case mnesia:dirty_read(roster_version, US) of - [#roster_version{version = V}] -> V; + [#roster_version{version = V}] -> {ok, V}; [] -> error end. @@ -66,32 +76,17 @@ write_roster_version(LUser, LServer, InTransaction, Ver) -> end. get_roster(LUser, LServer) -> - mnesia:dirty_index_read(roster, {LUser, LServer}, #roster.us). + {ok, mnesia:dirty_index_read(roster, {LUser, LServer}, #roster.us)}. -get_roster_by_jid(LUser, LServer, LJID) -> +get_roster_item(LUser, LServer, LJID) -> case mnesia:read({roster, {LUser, LServer, LJID}}) of - [] -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - [I] -> - I#roster{jid = LJID, name = <<"">>, groups = [], - xs = []} + [I] -> {ok, I}; + [] -> error end. -get_only_items(LUser, LServer) -> - get_roster(LUser, LServer). - roster_subscribe(_LUser, _LServer, _LJID, Item) -> mnesia:write(Item). -get_roster_by_jid_with_groups(LUser, LServer, LJID) -> - case mnesia:read({roster, {LUser, LServer, LJID}}) of - [] -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - [I] -> I - end. - remove_user(LUser, LServer) -> US = {LUser, LServer}, F = fun () -> @@ -110,7 +105,7 @@ del_roster(LUser, LServer, LJID) -> read_subscription_and_groups(LUser, LServer, LJID) -> case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of [#roster{subscription = Subscription, groups = Groups}] -> - {Subscription, Groups}; + {ok, {Subscription, Groups}}; _ -> error end. diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl index 25788cddf..c457e02f5 100644 --- a/src/mod_roster_riak.erl +++ b/src/mod_roster_riak.erl @@ -28,10 +28,10 @@ %% API -export([init/2, read_roster_version/2, write_roster_version/4, - get_roster/2, get_roster_by_jid/3, create_roster/1, - roster_subscribe/4, get_roster_by_jid_with_groups/3, - remove_user/2, update_roster/4, del_roster/3, transaction/2, - read_subscription_and_groups/3, get_only_items/2, import/3]). + get_roster/2, get_roster_item/3, create_roster/1, + roster_subscribe/4, remove_user/2, update_roster/4, + del_roster/3, read_subscription_and_groups/3, transaction/2, + import/3]). -include("mod_roster.hrl"). @@ -44,7 +44,7 @@ init(_Host, _Opts) -> read_roster_version(LUser, LServer) -> case ejabberd_riak:get(roster_version, roster_version_schema(), {LUser, LServer}) of - {ok, #roster_version{version = V}} -> V; + {ok, #roster_version{version = V}} -> {ok, V}; _Err -> error end. @@ -56,24 +56,10 @@ write_roster_version(LUser, LServer, _InTransaction, Ver) -> get_roster(LUser, LServer) -> case ejabberd_riak:get_by_index(roster, roster_schema(), <<"us">>, {LUser, LServer}) of - {ok, Items} -> Items; - _Err -> [] + {ok, Items} -> {ok, Items}; + _Err -> error end. -get_roster_by_jid(LUser, LServer, LJID) -> - case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of - {ok, I} -> - I#roster{jid = LJID, name = <<"">>, groups = [], xs = []}; - {error, notfound} -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - Err -> - exit(Err) - end. - -get_only_items(LUser, LServer) -> - get_roster(LUser, LServer). - roster_subscribe(LUser, LServer, _LJID, Item) -> ejabberd_riak:put(Item, roster_schema(), [{'2i', [{<<"us">>, {LUser, LServer}}]}]). @@ -81,19 +67,16 @@ roster_subscribe(LUser, LServer, _LJID, Item) -> transaction(_LServer, F) -> {atomic, F()}. -get_roster_by_jid_with_groups(LUser, LServer, LJID) -> +get_roster_item(LUser, LServer, LJID) -> case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of {ok, I} -> - I; - {error, notfound} -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - Err -> - exit(Err) + {ok, I}; + {error, _} -> + error end. remove_user(LUser, LServer) -> - {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}. + ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer}). update_roster(LUser, LServer, _LJID, Item) -> ejabberd_riak:put(Item, roster_schema(), @@ -104,11 +87,11 @@ del_roster(LUser, LServer, LJID) -> read_subscription_and_groups(LUser, LServer, LJID) -> case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of - {ok, #roster{subscription = Subscription, - groups = Groups}} -> - {Subscription, Groups}; - _ -> - error + {ok, #roster{subscription = Subscription, + groups = Groups}} -> + {ok, {Subscription, Groups}}; + _ -> + error end. create_roster(#roster{us = {LUser, LServer}} = RItem) -> diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl index 112c13a72..7c516568c 100644 --- a/src/mod_roster_sql.erl +++ b/src/mod_roster_sql.erl @@ -30,10 +30,9 @@ %% API -export([init/2, read_roster_version/2, write_roster_version/4, - get_roster/2, get_roster_by_jid/3, - roster_subscribe/4, get_roster_by_jid_with_groups/3, - remove_user/2, update_roster/4, del_roster/3, transaction/2, - read_subscription_and_groups/3, get_only_items/2, + get_roster/2, get_roster_item/3, roster_subscribe/4, + read_subscription_and_groups/3, remove_user/2, + update_roster/4, del_roster/3, transaction/2, import/3, export/1, raw_to_record/2]). -include("mod_roster.hrl"). @@ -48,7 +47,7 @@ init(_Host, _Opts) -> read_roster_version(LUser, LServer) -> case sql_queries:get_roster_version(LServer, LUser) of - {selected, [{Version}]} -> Version; + {selected, [{Version}]} -> {ok, Version}; {selected, []} -> error end. @@ -77,55 +76,22 @@ get_roster(LUser, LServer) -> dict:append(J, G, Acc) end, dict:new(), JIDGroups), - lists:flatmap( - fun(I) -> - case raw_to_record(LServer, I) of - %% Bad JID in database: - error -> []; - R -> - SJID = jid:encode(R#roster.jid), - Groups = case dict:find(SJID, GroupsDict) of - {ok, Gs} -> Gs; - error -> [] - end, - [R#roster{groups = Groups}] - end - end, Items); + {ok, lists:flatmap( + fun(I) -> + case raw_to_record(LServer, I) of + %% Bad JID in database: + error -> []; + R -> + SJID = jid:encode(R#roster.jid), + Groups = case dict:find(SJID, GroupsDict) of + {ok, Gs} -> Gs; + error -> [] + end, + [R#roster{groups = Groups}] + end + end, Items)}; _ -> - [] - end. - -get_roster_by_jid(LUser, LServer, LJID) -> - {selected, Res} = - sql_queries:get_roster_by_jid(LServer, LUser, jid:encode(LJID)), - case Res of - [] -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - [I] -> - R = raw_to_record(LServer, I), - case R of - %% Bad JID in database: - error -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - _ -> - R#roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID, name = <<"">>} - end - end. - -get_only_items(LUser, LServer) -> - case catch sql_queries:get_roster(LServer, LUser) of - {selected, Is} when is_list(Is) -> - lists:flatmap( - fun(I) -> - case raw_to_record(LServer, I) of - error -> []; - R -> [R] - end - end, Is); - _ -> [] + error end. roster_subscribe(_LUser, _LServer, _LJID, Item) -> @@ -135,14 +101,13 @@ roster_subscribe(_LUser, _LServer, _LJID, Item) -> transaction(LServer, F) -> ejabberd_sql:sql_transaction(LServer, F). -get_roster_by_jid_with_groups(LUser, LServer, LJID) -> +get_roster_item(LUser, LServer, LJID) -> SJID = jid:encode(LJID), case sql_queries:get_roster_by_jid(LServer, LUser, SJID) of {selected, [I]} -> case raw_to_record(LServer, I) of error -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; + error; R -> Groups = case sql_queries:get_roster_groups(LServer, LUser, SJID) of @@ -150,16 +115,15 @@ get_roster_by_jid_with_groups(LUser, LServer, LJID) -> [JGrp || {JGrp} <- JGrps]; _ -> [] end, - R#roster{groups = Groups} + {ok, R#roster{groups = Groups}} end; {selected, []} -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID} + error end. remove_user(LUser, LServer) -> sql_queries:del_user_roster_t(LServer, LUser), - {atomic, ok}. + ok. update_roster(LUser, LServer, LJID, Item) -> SJID = jid:encode(LJID), @@ -194,7 +158,7 @@ read_subscription_and_groups(LUser, LServer, LJID) -> [JGrp || {JGrp} <- JGrps]; _ -> [] end, - {Subscription, Groups}; + {ok, {Subscription, Groups}}; _ -> error end. diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index c25b13f66..5f95266b8 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -31,7 +31,7 @@ -export([start/2, stop/1, reload/3, export/1, import_info/0, webadmin_menu/3, webadmin_page/3, - get_user_roster/2, c2s_session_opened/1, + get_user_roster/2, get_jid_info/4, import/5, process_item/2, import_start/2, in_subscription/6, out_subscription/4, c2s_self_presence/1, unset_presence/4, register_user/2, remove_user/2, @@ -54,8 +54,6 @@ -include("mod_shared_roster.hrl"). --define(SETS, gb_sets). - -type group_options() :: [{atom(), any()}]. -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), binary(), [binary()]) -> ok. @@ -86,8 +84,6 @@ start(Host, Opts) -> ?MODULE, in_subscription, 30), ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, out_subscription, 30), - ejabberd_hooks:add(c2s_session_opened, Host, - ?MODULE, c2s_session_opened, 70), ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, get_jid_info, 70), ejabberd_hooks:add(roster_process_item, Host, ?MODULE, @@ -112,8 +108,6 @@ stop(Host) -> ?MODULE, in_subscription, 30), ejabberd_hooks:delete(roster_out_subscription, Host, ?MODULE, out_subscription, 30), - ejabberd_hooks:delete(c2s_session_opened, - Host, ?MODULE, c2s_session_opened, 70), ejabberd_hooks:delete(roster_get_jid_info, Host, ?MODULE, get_jid_info, 70), ejabberd_hooks:delete(roster_process_item, Host, @@ -300,23 +294,6 @@ set_item(User, Server, Resource, Item) -> items = [mod_roster:encode_item(Item)]}]}, ejabberd_router:route(ResIQ). -c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer}, - pres_f := PresF, pres_t := PresT} = State) -> - US = {LUser, LServer}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = lists:flatmap(fun(Group) -> - get_group_users(LServer, Group) - end, - DisplayedGroups), - PresBoth = lists:foldl( - fun({U, S, _}, Acc) -> - ?SETS:add_element({U, S, <<"">>}, Acc); - ({U, S}, Acc) -> - ?SETS:add_element({U, S, <<"">>}, Acc) - end, ?SETS:new(), SRUsers), - State#{pres_f => ?SETS:union(PresBoth, PresF), - pres_t => ?SETS:union(PresBoth, PresT)}. - -spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid()) -> {subscription(), [binary()]}. get_jid_info({Subscription, Groups}, User, Server, diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index 24bd3233c..30cd44568 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -39,7 +39,7 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([get_user_roster/2, c2s_session_opened/1, +-export([get_user_roster/2, get_jid_info/4, process_item/2, in_subscription/6, out_subscription/4, mod_opt_type/1, opt_type/1, depends/2, transform_module_options/1]). @@ -50,7 +50,6 @@ -include("mod_roster.hrl"). -include("eldap.hrl"). --define(SETS, gb_sets). -define(USER_CACHE, shared_roster_ldap_user_cache). -define(GROUP_CACHE, shared_roster_ldap_group_cache). -define(LDAP_SEARCH_TIMEOUT, 5). %% Timeout for LDAP search queries in seconds @@ -160,23 +159,6 @@ process_item(RosterItem, _Host) -> _ -> RosterItem#roster{subscription = both, ask = none} end. -c2s_session_opened(#{jid := #jid{luser = LUser, lserver = LServer}, - pres_f := PresF, pres_t := PresT} = State) -> - US = {LUser, LServer}, - DisplayedGroups = get_user_displayed_groups(US), - SRUsers = lists:flatmap(fun(Group) -> - get_group_users(LServer, Group) - end, - DisplayedGroups), - PresBoth = lists:foldl( - fun({U, S, _}, Acc) -> - ?SETS:add_element({U, S, <<"">>}, Acc); - ({U, S}, Acc) -> - ?SETS:add_element({U, S, <<"">>}, Acc) - end, ?SETS:new(), SRUsers), - State#{pres_f => ?SETS:union(PresBoth, PresF), - pres_t => ?SETS:union(PresBoth, PresT)}. - -spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid()) -> {subscription(), [binary()]}. get_jid_info({Subscription, Groups}, User, Server, @@ -245,8 +227,6 @@ init([Host, Opts]) -> ?MODULE, in_subscription, 30), ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, out_subscription, 30), - ejabberd_hooks:add(c2s_session_opened, Host, - ?MODULE, c2s_session_opened, 70), ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, get_jid_info, 70), ejabberd_hooks:add(roster_process_item, Host, ?MODULE, @@ -276,8 +256,6 @@ terminate(_Reason, State) -> ?MODULE, in_subscription, 30), ejabberd_hooks:delete(roster_out_subscription, Host, ?MODULE, out_subscription, 30), - ejabberd_hooks:delete(c2s_session_opened, - Host, ?MODULE, c2s_session_opened, 70), ejabberd_hooks:delete(roster_get_jid_info, Host, ?MODULE, get_jid_info, 70), ejabberd_hooks:delete(roster_process_item, Host, diff --git a/test/roster_tests.erl b/test/roster_tests.erl index 81dd34a26..e5482c78e 100644 --- a/test/roster_tests.erl +++ b/test/roster_tests.erl @@ -161,18 +161,25 @@ subscribe_slave(Config) -> process_subscriptions_master(Config, Actions) -> EnumeratedActions = lists:zip(lists:seq(1, length(Actions)), Actions), self_presence(Config, available), + Peer = ?config(peer, Config), lists:foldl( fun({N, {Dir, Type}}, State) -> - timer:sleep(100), if Dir == out -> put_event(Config, {N, in, Type}); Dir == in -> put_event(Config, {N, out, Type}) end, - wait_for_slave(Config), + Roster = get_roster(Config), ct:pal("Performing ~s-~s (#~p) " "in state:~n~s~nwith roster:~n~s", - [Dir, Type, N, pp(State), - pp(get_roster(Config))]), - transition(Config, Dir, Type, State) + [Dir, Type, N, pp(State), pp(Roster)]), + check_roster(Roster, Config, State), + wait_for_slave(Config), + Id = mk_id(N, Dir, Type), + NewState = transition(Id, Config, Dir, Type, State), + wait_for_slave(Config), + send_recv(Config, #iq{type = get, to = Peer, id = Id, + sub_els = [#ping{}]}), + check_roster_item(Config, NewState), + NewState end, #state{}, EnumeratedActions), put_event(Config, done), wait_for_slave(Config), @@ -186,11 +193,16 @@ process_subscriptions_slave(Config, done, _State) -> wait_for_master(Config), Config; process_subscriptions_slave(Config, {N, Dir, Type}, State) -> - wait_for_master(Config), + Roster = get_roster(Config), ct:pal("Performing ~s-~s (#~p) " "in state:~n~s~nwith roster:~n~s", - [Dir, Type, N, pp(State), pp(get_roster(Config))]), - NewState = transition(Config, Dir, Type, State), + [Dir, Type, N, pp(State), pp(Roster)]), + check_roster(Roster, Config, State), + wait_for_master(Config), + NewState = transition(mk_id(N, Dir, Type), Config, Dir, Type, State), + wait_for_master(Config), + send(Config, xmpp:make_iq_result(recv_iq(Config))), + check_roster_item(Config, NewState), process_subscriptions_slave(Config, get_event(Config), NewState). %%%=================================================================== @@ -288,12 +300,42 @@ pp(roster, N) -> catch _:_ -> no end; pp(_, _) -> no. +mk_id(N, Dir, Type) -> + list_to_binary([integer_to_list(N), $-, atom_to_list(Dir), + $-, atom_to_list(Type)]). + +check_roster([], _Config, _State) -> + ok; +check_roster([Roster], _Config, State) -> + case {Roster#roster.subscription == State#state.subscription, + Roster#roster.ask, State#state.pending_in, State#state.pending_out} of + {true, both, true, true} -> ok; + {true, in, true, false} -> ok; + {true, out, false, true} -> ok; + {true, none, false, false} -> ok; + _ -> + ct:fail({roster_mismatch, State, Roster}) + end. + +check_roster_item(Config, State) -> + Peer = jid:remove_resource(?config(peer, Config)), + RosterItem = case get_item(Config, Peer) of + false -> #roster_item{}; + Item -> Item + end, + case {RosterItem#roster_item.subscription == State#state.subscription, + RosterItem#roster_item.ask, State#state.pending_out} of + {true, subscribe, true} -> ok; + {true, undefined, false} -> ok; + _ -> ct:fail({roster_item_mismatch, State, RosterItem}) + end. + %% RFC6121, A.2.1 -transition(Config, out, subscribe, +transition(Id, Config, out, subscribe, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> PeerJID = ?config(peer, Config), PeerBareJID = jid:remove_resource(PeerJID), - send(Config, #presence{to = PeerBareJID, type = subscribe}), + send(Config, #presence{id = Id, to = PeerBareJID, type = subscribe}), case {Sub, Out, In} of {none, false, _} -> recv_push(Config, none, subscribe), @@ -309,11 +351,11 @@ transition(Config, out, subscribe, State end; %% RFC6121, A.2.2 -transition(Config, out, unsubscribe, +transition(Id, Config, out, unsubscribe, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> PeerJID = ?config(peer, Config), PeerBareJID = jid:remove_resource(PeerJID), - send(Config, #presence{to = PeerBareJID, type = unsubscribe}), + send(Config, #presence{id = Id, to = PeerBareJID, type = unsubscribe}), case {Sub, Out, In} of {none, true, _} -> recv_push(Config, none, undefined), @@ -333,11 +375,11 @@ transition(Config, out, unsubscribe, State end; %% RFC6121, A.2.3 -transition(Config, out, subscribed, +transition(Id, Config, out, subscribed, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> PeerJID = ?config(peer, Config), PeerBareJID = jid:remove_resource(PeerJID), - send(Config, #presence{to = PeerBareJID, type = subscribed}), + send(Config, #presence{id = Id, to = PeerBareJID, type = subscribed}), case {Sub, Out, In} of {none, false, true} -> recv_push(Config, from, undefined), @@ -356,11 +398,11 @@ transition(Config, out, subscribed, State end; %% RFC6121, A.2.4 -transition(Config, out, unsubscribed, +transition(Id, Config, out, unsubscribed, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> PeerJID = ?config(peer, Config), PeerBareJID = jid:remove_resource(PeerJID), - send(Config, #presence{to = PeerBareJID, type = unsubscribed}), + send(Config, #presence{id = Id, to = PeerBareJID, type = unsubscribed}), case {Sub, Out, In} of {none, false, true} -> State#state{subscription = none, pending_in = false}; @@ -382,7 +424,7 @@ transition(Config, out, unsubscribed, State end; %% RFC6121, A.3.1 -transition(Config, in, subscribe = Type, +transition(_, Config, in, subscribe = Type, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> case {Sub, Out, In} of {none, false, false} -> @@ -401,7 +443,7 @@ transition(Config, in, subscribe = Type, State end; %% RFC6121, A.3.2 -transition(Config, in, unsubscribe = Type, +transition(_, Config, in, unsubscribe = Type, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> case {Sub, Out, In} of {none, _, true} -> @@ -426,7 +468,7 @@ transition(Config, in, unsubscribe = Type, State end; %% RFC6121, A.3.3 -transition(Config, in, subscribed = Type, +transition(_, Config, in, subscribed = Type, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> case {Sub, Out, In} of {none, true, _} -> @@ -449,7 +491,7 @@ transition(Config, in, subscribed = Type, State end; %% RFC6121, A.3.4 -transition(Config, in, unsubscribed = Type, +transition(_, Config, in, unsubscribed = Type, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> case {Sub, Out, In} of {none, true, true} -> @@ -464,8 +506,8 @@ transition(Config, in, unsubscribed = Type, State; {to, false, _} -> recv_push(Config, none, undefined), - recv_subscription(Config, Type), recv_presence(Config, unavailable), + recv_subscription(Config, Type), State#state{subscription = none, peer_available = false}; {from, true, false} -> recv_push(Config, from, undefined), @@ -473,20 +515,20 @@ transition(Config, in, unsubscribed = Type, State#state{subscription = from, pending_out = false}; {both, _, _} -> recv_push(Config, from, undefined), - recv_subscription(Config, Type), recv_presence(Config, unavailable), + recv_subscription(Config, Type), State#state{subscription = from, peer_available = false}; _ -> State end; %% Outgoing roster remove -transition(Config, out, remove, +transition(Id, Config, out, remove, #state{subscription = Sub, pending_in = In, pending_out = Out}) -> PeerJID = ?config(peer, Config), PeerBareJID = jid:remove_resource(PeerJID), Item = #roster_item{jid = PeerBareJID, subscription = remove}, #iq{type = result, sub_els = []} = - send_recv(Config, #iq{type = set, + send_recv(Config, #iq{type = set, id = Id, sub_els = [#roster_query{items = [Item]}]}), recv_push(Config, remove, undefined), case {Sub, Out, In} of @@ -499,7 +541,7 @@ transition(Config, out, remove, end, #state{}; %% Incoming roster remove -transition(Config, in, remove, +transition(_, Config, in, remove, #state{subscription = Sub, pending_in = In, pending_out = Out} = State) -> case {Sub, Out, In} of {none, true, _} -> From 7165196211269b22f0d5e1be7ae6d50495284602 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 15:24:32 +0300 Subject: [PATCH 25/91] Get rid of unused variable --- src/mod_roster.erl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mod_roster.erl b/src/mod_roster.erl index b256f107d..7a3eebbe1 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -847,8 +847,7 @@ process_item_set_t(_LUser, _LServer, _) -> ok. -> {presence(), ejabberd_c2s:state()}. c2s_self_presence({_, #{pres_last := _}} = Acc) -> Acc; -c2s_self_presence({#presence{type = available} = Pkt, - #{lserver := LServer} = State}) -> +c2s_self_presence({#presence{type = available} = Pkt, State}) -> Prio = get_priority_from_presence(Pkt), if Prio >= 0 -> State1 = resend_pending_subscriptions(State), From a71065fcda24eae0f91401b0e36f2850fbf79614 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 15:42:18 +0300 Subject: [PATCH 26/91] Ciphers should be a binary string --- src/ejabberd_c2s.erl | 2 +- src/ejabberd_http.erl | 2 +- src/ejabberd_s2s.erl | 2 +- src/ejabberd_service.erl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 00a2bad80..8f374a440 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -923,7 +923,7 @@ transform_listen_option(Opt, Opts) -> (disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]); (atom()) -> [atom()]. opt_type(c2s_certfile) -> fun misc:try_read_file/1; -opt_type(c2s_ciphers) -> fun misc:try_read_file/1; +opt_type(c2s_ciphers) -> 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) -> diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index 9f2c87e9b..f513cecb0 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -931,7 +931,7 @@ listen_opt_type(certfile) -> iolist_to_binary(S) end; listen_opt_type(ciphers) -> - fun misc:try_read_file/1; + fun iolist_to_binary/1; listen_opt_type(dhfile) -> fun misc:try_read_file/1; listen_opt_type(protocol_options) -> diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 9e9a94754..d972f5866 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -710,7 +710,7 @@ opt_type(route_subdomains) -> opt_type(s2s_access) -> fun acl:access_rules_validator/1; opt_type(s2s_certfile) -> fun misc:try_read_file/1; -opt_type(s2s_ciphers) -> fun misc:try_read_file/1; +opt_type(s2s_ciphers) -> 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) -> diff --git a/src/ejabberd_service.erl b/src/ejabberd_service.erl index a83ec41f0..dd6310fbe 100644 --- a/src/ejabberd_service.erl +++ b/src/ejabberd_service.erl @@ -281,7 +281,7 @@ listen_opt_type(certfile) -> ejabberd_pkix:add_certfile(S), iolist_to_binary(S) end; -listen_opt_type(ciphers) -> fun misc:try_read_file/1; +listen_opt_type(ciphers) -> 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) -> From fc794b680a635fd764f538f88586f1e05aa4b62e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 16:03:41 +0300 Subject: [PATCH 27/91] Add cache options to the validator --- src/mod_roster.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 7a3eebbe1..d1dc714ef 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -1212,6 +1212,12 @@ mod_opt_type(store_current_id) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(versioning) -> fun (B) when is_boolean(B) -> B end; +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(_) -> [access, db_type, iqdisc, managers, store_current_id, - versioning]. + versioning, cache_life_time, cache_size, use_cache, cache_missed]. From 1925b94131a5fa5de8d718ae81d86dcca2ae653f Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 17:13:34 +0300 Subject: [PATCH 28/91] Implement cache for mod_vcard and mod_vcard_xupdate --- src/mod_vcard.erl | 80 ++++++++++++++- src/mod_vcard_ldap.erl | 4 +- src/mod_vcard_mnesia.erl | 2 +- src/mod_vcard_riak.erl | 4 +- src/mod_vcard_sql.erl | 4 +- src/mod_vcard_xupdate.erl | 141 ++++++++++++++++---------- src/mod_vcard_xupdate_mnesia.erl | 82 --------------- src/mod_vcard_xupdate_riak.erl | 64 ------------ src/mod_vcard_xupdate_sql.erl | 86 ---------------- test/ejabberd_SUITE_data/ejabberd.yml | 18 ++-- 10 files changed, 174 insertions(+), 311 deletions(-) delete mode 100644 src/mod_vcard_xupdate_mnesia.erl delete mode 100644 src/mod_vcard_xupdate_riak.erl delete mode 100644 src/mod_vcard_xupdate_sql.erl diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 00fd4bbc0..495393f72 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -48,11 +48,12 @@ -include("mod_vcard.hrl"). -define(JUD_MATCHES, 30). +-define(VCARD_CACHE, vcard_cache). -callback init(binary(), gen_mod:opts()) -> any(). -callback stop(binary()) -> any(). -callback import(binary(), binary(), [binary()]) -> ok. --callback get_vcard(binary(), binary()) -> [xmlel()] | error. +-callback get_vcard(binary(), binary()) -> {ok, [xmlel()]} | error. -callback set_vcard(binary(), binary(), xmlel(), #vcard_search{}) -> {atomic, any()}. -callback search_fields(binary()) -> [{binary(), binary()}]. @@ -61,6 +62,10 @@ infinity | pos_integer()) -> [{binary(), binary()}]. -callback remove_user(binary(), binary()) -> {atomic, any()}. -callback is_search_supported(binary()) -> boolean(). +-callback use_cache(binary()) -> boolean(). +-callback cache_nodes(binary()) -> [node()]. + +-optional_callbacks([use_cache/1, cache_nodes/1]). -record(state, {host :: binary(), server_host :: binary()}). @@ -80,6 +85,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(remove_user, Host, ?MODULE, remove_user, 50), IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)), @@ -288,7 +294,18 @@ disco_identity(Acc, _From, _To, _Node, _Lang) -> -spec get_vcard(binary(), binary()) -> [xmlel()] | error. get_vcard(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_vcard(LUser, LServer). + Result = case use_cache(Mod, LServer) of + true -> + ets_cache:lookup( + ?VCARD_CACHE, {LUser, LServer}, + fun() -> Mod:get_vcard(LUser, LServer) end); + false -> + Mod:get_vcard(LUser, LServer) + end, + case Result of + {ok, Els} -> Els; + error -> error + end. -spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}. make_vcard_search(User, LUser, LServer, VCARD) -> @@ -366,6 +383,8 @@ set_vcard(User, LServer, VCARD) -> VCardSearch = make_vcard_search(User, LUser, LServer, VCARD), Mod = gen_mod:db_mod(LServer, ?MODULE), Mod:set_vcard(LUser, LServer, VCARD, VCardSearch), + ets_cache:delete(?VCARD_CACHE, {LUser, LServer}, + cache_nodes(Mod, LServer)), ejabberd_hooks:run(vcard_set, LServer, [LUser, LServer, VCARD]) end. @@ -435,12 +454,56 @@ search(LServer, XFields) -> Mod:search(LServer, Data, AllowReturnAll, MaxMatch). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --spec remove_user(binary(), binary()) -> any(). +-spec remove_user(binary(), binary()) -> ok. 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(?VCARD_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(?VCARD_CACHE, CacheOpts); + false -> + ets_cache:delete(?VCARD_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() -> [{<<"vcard">>, 3}, {<<"vcard_search">>, 24}]. @@ -473,6 +536,13 @@ mod_opt_type(search) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(search_all_hosts) -> fun (B) when is_boolean(B) -> B end; +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(_) -> [allow_return_all, db_type, host, iqdisc, matches, - search, search_all_hosts]. + search, search_all_hosts, cache_life_time, cache_size, + use_cache, cache_missed]. diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index fe11d0962..6a690779a 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -97,9 +97,9 @@ get_vcard(LUser, LServer) -> #eldap_entry{attributes = Attributes} -> VCard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}), - [xmpp:encode(VCard)]; + {ok, [xmpp:encode(VCard)]}; _ -> - [] + {ok, []} end. set_vcard(_LUser, _LServer, _VCard, _VCardSearch) -> diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index 47072ed69..9dc3d860a 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -65,7 +65,7 @@ get_vcard(LUser, LServer) -> F = fun () -> mnesia:read({vcard, US}) end, case mnesia:transaction(F) of {atomic, Rs} -> - lists:map(fun (R) -> R#vcard.vcard end, Rs); + {ok, lists:map(fun (R) -> R#vcard.vcard end, Rs)}; {aborted, _Reason} -> error end. diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl index 31148ccb1..e5ad1b3dd 100644 --- a/src/mod_vcard_riak.erl +++ b/src/mod_vcard_riak.erl @@ -49,9 +49,9 @@ is_search_supported(_LServer) -> get_vcard(LUser, LServer) -> case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of {ok, R} -> - [R#vcard.vcard]; + {ok, [R#vcard.vcard]}; {error, notfound} -> - []; + {ok, []}; _ -> error end. diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index c2bc11dfb..3b38536ea 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -55,9 +55,9 @@ get_vcard(LUser, LServer) -> {selected, [{SVCARD}]} -> case fxml_stream:parse_element(SVCARD) of {error, _Reason} -> error; - VCARD -> [VCARD] + VCARD -> {ok, [VCARD]} end; - {selected, []} -> []; + {selected, []} -> {ok, []}; _ -> error end. diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index e31b30497..0da8198d9 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -30,52 +30,39 @@ %% gen_mod callbacks -export([start/2, stop/1, reload/3]). --export([update_presence/1, vcard_set/3, export/1, - import_info/0, import/5, import_start/2, +-export([update_presence/1, vcard_set/3, remove_user/2, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). -include("logger.hrl"). -include("xmpp.hrl"). --callback init(binary(), gen_mod:opts()) -> any(). --callback import(binary(), binary(), [binary()]) -> ok. --callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}. --callback get_xupdate(binary(), binary()) -> binary() | undefined. --callback remove_xupdate(binary(), binary()) -> {atomic, any()}. +-define(VCARD_XUPDATE_CACHE, vcard_xupdate_cache). %%==================================================================== %% gen_mod callbacks %%==================================================================== start(Host, Opts) -> - Mod = gen_mod:db_mod(Host, Opts, ?MODULE), - Mod:init(Host, Opts), + init_cache(Host, Opts), ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE, update_presence, 100), ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set, 100), - ok. + ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50). stop(Host) -> ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, update_presence, 100), ejabberd_hooks:delete(vcard_set, Host, ?MODULE, vcard_set, 100), - ok. + ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50). -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, - ok. +reload(Host, NewOpts, _OldOpts) -> + init_cache(Host, NewOpts). depends(_Host, _Opts) -> - []. + [{mod_vcard, hard}]. %%==================================================================== %% Hooks @@ -91,51 +78,95 @@ update_presence(Acc) -> Acc. -spec vcard_set(binary(), binary(), xmlel()) -> ok. -vcard_set(LUser, LServer, VCARD) -> - US = {LUser, LServer}, - case fxml:get_path_s(VCARD, - [{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata]) - of - <<>> -> remove_xupdate(LUser, LServer); - BinVal -> - add_xupdate(LUser, LServer, - str:sha(misc:decode_base64(BinVal))) - end, - ejabberd_sm:force_update_presence(US). +vcard_set(LUser, LServer, _VCARD) -> + ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}), + ejabberd_sm:force_update_presence({LUser, LServer}). + +-spec remove_user(binary(), binary()) -> ok. +remove_user(User, Server) -> + LUser = jid:nodeprep(User), + LServer = jid:nameprep(Server), + ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}). %%==================================================================== %% Storage %%==================================================================== - -add_xupdate(LUser, LServer, Hash) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:add_xupdate(LUser, LServer, Hash). - +-spec get_xupdate(binary(), binary()) -> binary() | undefined. get_xupdate(LUser, LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_xupdate(LUser, LServer). + Result = case use_cache(LServer) of + true -> + ets_cache:lookup( + ?VCARD_XUPDATE_CACHE, {LUser, LServer}, + fun() -> db_get_xupdate(LUser, LServer) end); + false -> + db_get_xupdate(LUser, LServer) + end, + case Result of + {ok, Hash} -> Hash; + error -> undefined + end. -remove_xupdate(LUser, LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:remove_xupdate(LUser, LServer). +-spec db_get_xupdate(binary(), binary()) -> {ok, binary()} | error. +db_get_xupdate(LUser, LServer) -> + case mod_vcard:get_vcard(LUser, LServer) of + [VCard] -> + {ok, compute_hash(VCard)}; + _ -> + error + end. -import_info() -> - [{<<"vcard_xupdate">>, 3}]. +-spec init_cache(binary(), gen_mod:opts()) -> ok. +init_cache(Host, Opts) -> + case use_cache(Host) of + true -> + CacheOpts = cache_opts(Host, Opts), + ets_cache:new(?VCARD_XUPDATE_CACHE, CacheOpts); + false -> + ets_cache:delete(?VCARD_XUPDATE_CACHE) + end. -import_start(LServer, DBType) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:init(LServer, []). +-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}]. -import(LServer, {sql, _}, DBType, Tab, [LUser, Hash, TimeStamp]) -> - Mod = gen_mod:db_mod(DBType, ?MODULE), - Mod:import(LServer, Tab, [LUser, Hash, TimeStamp]). +-spec use_cache(binary()) -> boolean(). +use_cache(Host) -> + gen_mod:get_module_opt( + Host, ?MODULE, use_cache, + ejabberd_config:use_cache(Host)). -export(LServer) -> - Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:export(LServer). +-spec compute_hash(xmlel()) -> binary(). +compute_hash(VCard) -> + case fxml:get_path_s(VCard, + [{elem, <<"PHOTO">>}, + {elem, <<"BINVAL">>}, + cdata]) of + <<>> -> + <<>>; + BinVal -> + str:sha(misc:decode_base64(BinVal)) + end. %%==================================================================== %% Options %%==================================================================== -mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end; -mod_opt_type(_) -> [db_type]. +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(_) -> + [cache_life_time, cache_size, use_cache, cache_missed]. diff --git a/src/mod_vcard_xupdate_mnesia.erl b/src/mod_vcard_xupdate_mnesia.erl deleted file mode 100644 index 9b80e0672..000000000 --- a/src/mod_vcard_xupdate_mnesia.erl +++ /dev/null @@ -1,82 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_vcard_xupdate_mnesia.erl -%%% Author : Evgeny Khramtsov -%%% Created : 13 Apr 2016 by Evgeny Khramtsov -%%% -%%% -%%% 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_vcard_xupdate_mnesia). - --behaviour(mod_vcard_xupdate). - -%% API --export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). --export([need_transform/1, transform/1]). - --include("mod_vcard_xupdate.hrl"). --include("logger.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== -init(_Host, _Opts) -> - ejabberd_mnesia:create(?MODULE, vcard_xupdate, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, vcard_xupdate)}]). - -add_xupdate(LUser, LServer, Hash) -> - F = fun () -> - mnesia:write(#vcard_xupdate{us = {LUser, LServer}, - hash = Hash}) - end, - mnesia:transaction(F). - -get_xupdate(LUser, LServer) -> - case mnesia:dirty_read(vcard_xupdate, {LUser, LServer}) - of - [#vcard_xupdate{hash = Hash}] -> Hash; - _ -> undefined - end. - -remove_xupdate(LUser, LServer) -> - F = fun () -> - mnesia:delete({vcard_xupdate, {LUser, LServer}}) - end, - mnesia:transaction(F). - -import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) -> - mnesia:dirty_write( - #vcard_xupdate{us = {LUser, LServer}, hash = Hash}). - -need_transform(#vcard_xupdate{us = {U, S}, hash = Hash}) - when is_list(U) orelse is_list(S) orelse is_list(Hash) -> - ?INFO_MSG("Mnesia table 'vcard_xupdate' will be converted to binary", []), - true; -need_transform(_) -> - false. - -transform(#vcard_xupdate{us = {U, S}, hash = Hash} = R) -> - R#vcard_xupdate{us = {iolist_to_binary(U), iolist_to_binary(S)}, - hash = iolist_to_binary(Hash)}. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/src/mod_vcard_xupdate_riak.erl b/src/mod_vcard_xupdate_riak.erl deleted file mode 100644 index 8e20bba4b..000000000 --- a/src/mod_vcard_xupdate_riak.erl +++ /dev/null @@ -1,64 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_vcard_xupdate_riak.erl -%%% Author : Evgeny Khramtsov -%%% Created : 13 Apr 2016 by Evgeny Khramtsov -%%% -%%% -%%% 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_vcard_xupdate_riak). - --behaviour(mod_vcard_xupdate). - -%% API --export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). - --include("mod_vcard_xupdate.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== -init(_Host, _Opts) -> - ok. - -add_xupdate(LUser, LServer, Hash) -> - {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer}, - hash = Hash}, - vcard_xupdate_schema())}. - -get_xupdate(LUser, LServer) -> - case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(), - {LUser, LServer}) of - {ok, #vcard_xupdate{hash = Hash}} -> Hash; - _ -> undefined - end. - -remove_xupdate(LUser, LServer) -> - {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}. - -import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) -> - ejabberd_riak:put( - #vcard_xupdate{us = {LUser, LServer}, hash = Hash}, - vcard_xupdate_schema()). - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== -vcard_xupdate_schema() -> - {record_info(fields, vcard_xupdate), #vcard_xupdate{}}. diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl deleted file mode 100644 index e2e3d1a78..000000000 --- a/src/mod_vcard_xupdate_sql.erl +++ /dev/null @@ -1,86 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_vcard_xupdate_sql.erl -%%% Author : Evgeny Khramtsov -%%% Created : 13 Apr 2016 by Evgeny Khramtsov -%%% -%%% -%%% 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_vcard_xupdate_sql). - --compile([{parse_transform, ejabberd_sql_pt}]). - --behaviour(mod_vcard_xupdate). - -%% API --export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2, - export/1]). - --include("mod_vcard_xupdate.hrl"). --include("ejabberd_sql_pt.hrl"). - -%%%=================================================================== -%%% API -%%%=================================================================== -init(_Host, _Opts) -> - ok. - -add_xupdate(LUser, LServer, Hash) -> - F = fun () -> - ?SQL_UPSERT_T( - "vcard_xupdate", - ["!username=%(LUser)s", - "hash=%(Hash)s"]) - end, - ejabberd_sql:sql_transaction(LServer, F). - -get_xupdate(LUser, LServer) -> - case ejabberd_sql:sql_query( - LServer, - ?SQL("select @(hash)s from vcard_xupdate where" - " username=%(LUser)s")) - of - {selected, [{Hash}]} -> Hash; - _ -> undefined - end. - -remove_xupdate(LUser, LServer) -> - F = fun () -> - ejabberd_sql:sql_query_t( - ?SQL("delete from vcard_xupdate where username=%(LUser)s")) - end, - ejabberd_sql:sql_transaction(LServer, F). - -export(_Server) -> - [{vcard_xupdate, - fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash}) - when LServer == Host -> - [?SQL("delete from vcard_xupdate where username=%(LUser)s;"), - ?SQL("insert into vcard_xupdate(username, hash) values (" - "%(LUser)s, %(Hash)s);")]; - (_Host, _R) -> - [] - end}]. - -import(_, _, _) -> - ok. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index f7e45c879..db858e219 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -44,8 +44,7 @@ host_config: db_type: sql mod_vcard: db_type: sql - mod_vcard_xupdate: - db_type: sql + mod_vcard_xupdate: [] mod_carboncopy: ram_db_type: sql mod_adhoc: [] @@ -103,8 +102,7 @@ Welcome to this XMPP server." db_type: sql mod_vcard: db_type: sql - mod_vcard_xupdate: - db_type: sql + mod_vcard_xupdate: [] mod_carboncopy: ram_db_type: sql mod_adhoc: [] @@ -167,8 +165,7 @@ Welcome to this XMPP server." db_type: sql mod_vcard: db_type: sql - mod_vcard_xupdate: - db_type: sql + mod_vcard_xupdate: [] mod_carboncopy: ram_db_type: sql mod_adhoc: [] @@ -222,8 +219,7 @@ Welcome to this XMPP server." db_type: internal mod_vcard: db_type: internal - mod_vcard_xupdate: - db_type: internal + mod_vcard_xupdate: [] mod_carboncopy: ram_db_type: internal mod_client_state: @@ -282,8 +278,7 @@ Welcome to this XMPP server." db_type: internal mod_vcard: db_type: internal - mod_vcard_xupdate: - db_type: internal + mod_vcard_xupdate: [] mod_carboncopy: ram_db_type: redis mod_client_state: @@ -332,8 +327,7 @@ Welcome to this XMPP server." db_type: riak mod_vcard: db_type: riak - mod_vcard_xupdate: - db_type: riak + mod_vcard_xupdate: [] mod_carboncopy: ram_db_type: riak mod_adhoc: [] From 5bdc6c082278695cd90694f57cba47f7db6a4023 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 17:21:59 +0300 Subject: [PATCH 29/91] Get rid of deprecated option 'resume_timeout' in test config --- test/ejabberd_SUITE_data/ejabberd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml index db858e219..89618c0c0 100644 --- a/test/ejabberd_SUITE_data/ejabberd.yml +++ b/test/ejabberd_SUITE_data/ejabberd.yml @@ -443,7 +443,6 @@ listen: tls_verify: true shaper: c2s_shaper access: c2s - resume_timeout: 3 - port: @@s2s_port@@ module: ejabberd_s2s_in @@ -477,6 +476,7 @@ Welcome to this XMPP server." mod_legacy_auth: [] mod_stream_mgmt: max_ack_queue: 10 + resume_timeout: 3 mod_time: [] mod_version: [] registration_timeout: infinity From 8f595b58a7e5de23b2187c22e7a19837b48e6a61 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 17:33:07 +0300 Subject: [PATCH 30/91] Increase gen_mod's supervisor shutdown time --- src/gen_mod.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 27d241c9c..5bfa3b4d4 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -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()}. From 1391d5a3045e1a02e42d04e5e87d82cbd75d4a38 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 17:42:22 +0300 Subject: [PATCH 31/91] Use disc_only_copies for oauth_token Mnesia table --- src/ejabberd_oauth_mnesia.erl | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ejabberd_oauth_mnesia.erl b/src/ejabberd_oauth_mnesia.erl index 8908afd39..c84174655 100644 --- a/src/ejabberd_oauth_mnesia.erl +++ b/src/ejabberd_oauth_mnesia.erl @@ -29,17 +29,28 @@ -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). From 6691c59a7ab2993f186f9b3309f440cd60ae3bcb Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 19:29:19 +0300 Subject: [PATCH 32/91] Clean up database code related to mod_vcard_xupdate --- sql/lite.sql | 6 ------ sql/mssql.sql | 10 ---------- sql/mysql.sql | 6 ------ sql/pg.sql | 6 ------ src/ejabberd_config.erl | 2 +- src/ejd2sql.erl | 3 +-- src/mod_vcard_xupdate.erl | 10 +++++++++- 7 files changed, 11 insertions(+), 32 deletions(-) diff --git a/sql/lite.sql b/sql/lite.sql index 1d057407f..1cc0c4dc5 100644 --- a/sql/lite.sql +++ b/sql/lite.sql @@ -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, diff --git a/sql/mssql.sql b/sql/mssql.sql index 72b490ecc..607acae8f 100644 --- a/sql/mssql.sql +++ b/sql/mssql.sql @@ -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; diff --git a/sql/mysql.sql b/sql/mysql.sql index c591cb761..3fddea510 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -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, diff --git a/sql/pg.sql b/sql/pg.sql index 539c1263a..f761e68da 100644 --- a/sql/pg.sql +++ b/sql/pg.sql @@ -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, diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 1dc30b1c5..1e5d495ce 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -1092,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) -> diff --git a/src/ejd2sql.erl b/src/ejd2sql.erl index 343c3518a..c4f00a551 100644 --- a/src/ejd2sql.erl +++ b/src/ejd2sql.erl @@ -60,8 +60,7 @@ modules() -> mod_private, mod_roster, mod_shared_roster, - mod_vcard, - mod_vcard_xupdate]. + mod_vcard]. export(Server, Output) -> LServer = jid:nameprep(iolist_to_binary(Server)), diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 0da8198d9..74f6b7364 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -162,6 +162,14 @@ compute_hash(VCard) -> %%==================================================================== %% Options %%==================================================================== +mod_opt_type(db_type) -> + fun(_) -> + ?WARNING_MSG("option 'db_type' for module '~s' has no effect: " + "the module doesn't require database anymore; " + "feel free to delete any tables related to the module", + [?MODULE]), + erlang:error(badarg) + end; mod_opt_type(O) when O == cache_life_time; O == cache_size -> fun (I) when is_integer(I), I > 0 -> I; (infinity) -> infinity @@ -169,4 +177,4 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size -> mod_opt_type(O) when O == use_cache; O == cache_missed -> fun (B) when is_boolean(B) -> B end; mod_opt_type(_) -> - [cache_life_time, cache_size, use_cache, cache_missed]. + [db_type, cache_life_time, cache_size, use_cache, cache_missed]. From 3e35d44b0f1e6bc80c65163646c6f953c772ad16 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 17 May 2017 19:37:06 +0300 Subject: [PATCH 33/91] Replace 'if_version_above' directive with 'if_have_fun' --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index be9005679..acd737681 100644 --- a/rebar.config +++ b/rebar.config @@ -88,8 +88,8 @@ {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, From b0b7ac101cf304acc579463c6e2f0a7bc9eadc1b Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 18 May 2017 09:51:04 +0300 Subject: [PATCH 34/91] Fix function_clause after authentication refactoring Fixes https://github.com/processone/ejabberd-contrib/issues/213 --- src/sql_queries.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sql_queries.erl b/src/sql_queries.erl index 862e4e284..2f2e55863 100644 --- a/src/sql_queries.erl +++ b/src/sql_queries.erl @@ -205,10 +205,12 @@ del_user_return_password(_LServer, LUser, Password) -> P. list_users(LServer) -> + list_users(LServer, []). + +list_users(LServer, []) -> ejabberd_sql:sql_query( LServer, - ?SQL("select @(username)s from users")). - + ?SQL("select @(username)s from users")); list_users(LServer, [{from, Start}, {to, End}]) when is_integer(Start) and is_integer(End) -> list_users(LServer, From 97bb1250baab7d4244ae1e26a7c688262b716168 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 18 May 2017 12:09:28 +0300 Subject: [PATCH 35/91] Avoid erroneous usage of ?MODULE macro --- src/mod_roster_mnesia.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl index 723c1722e..8ca6f8cfc 100644 --- a/src/mod_roster_mnesia.erl +++ b/src/mod_roster_mnesia.erl @@ -54,7 +54,7 @@ use_cache(Host, Table) -> case mnesia:table_info(Table, storage_type) of disc_only_copies -> gen_mod:get_module_opt( - Host, ?MODULE, use_cache, + Host, mod_roster, use_cache, ejabberd_config:use_cache(Host)); _ -> false From 736a18254462e9a9e763bc6a24ccc2c5005864d7 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 18 May 2017 12:10:36 +0300 Subject: [PATCH 36/91] ?SQL_UPSERT returns 'ok' on success --- src/mod_mam_sql.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index 83e269328..fae14955f 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -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 From bcb44ccb6f87efdf82cf66c9bd1249d5cc882da3 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 18 May 2017 13:21:17 +0300 Subject: [PATCH 37/91] Implement cache for mod_last --- src/mod_last.erl | 94 +++++++++++++++++++++++++++++++++++++---- src/mod_last_mnesia.erl | 32 ++++++++------ src/mod_last_riak.erl | 19 +++++---- src/mod_last_sql.erl | 15 +++++-- 4 files changed, 126 insertions(+), 34 deletions(-) diff --git a/src/mod_last.erl b/src/mod_last.erl index 79b3d614f..e97ef43fc 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -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, @@ -180,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) -> @@ -226,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. @@ -241,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}]. @@ -270,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]. diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl index ab8f47478..6d1dee7d9 100644 --- a/src/mod_last_mnesia.erl +++ b/src/mod_last_mnesia.erl @@ -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). diff --git a/src/mod_last_riak.erl b/src/mod_last_riak.erl index 9b71f9946..ac3faa14a 100644 --- a/src/mod_last_riak.erl +++ b/src/mod_last_riak.erl @@ -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})}. diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl index 8dee68b3f..2e3c3dd15 100644 --- a/src/mod_last_sql.erl +++ b/src/mod_last_sql.erl @@ -45,17 +45,24 @@ init(_Host, _Opts) -> get_last(LUser, LServer) -> case catch sql_queries:get_last(LServer, LUser) 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_queries:set_last_t(LServer, LUser, TimeStamp, Status) 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). From a78862e05ef57da22bb5762aab7eccf7e3e180b3 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 18 May 2017 19:13:18 +0300 Subject: [PATCH 38/91] The default 'iqdisc' is now 'no_queue' --- src/gen_iq_handler.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index 6ca706f63..b815a1c19 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -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()}]. From 0ed23980a6dafc0352b2ef066c18167199ccf5ab Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 18 May 2017 21:24:47 +0300 Subject: [PATCH 39/91] Get rid of Mnesia transaction in get_vcard/2 --- src/mod_vcard_mnesia.erl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl index 9dc3d860a..d2f4ef52d 100644 --- a/src/mod_vcard_mnesia.erl +++ b/src/mod_vcard_mnesia.erl @@ -62,12 +62,8 @@ is_search_supported(_ServerHost) -> get_vcard(LUser, LServer) -> US = {LUser, LServer}, - F = fun () -> mnesia:read({vcard, US}) end, - case mnesia:transaction(F) of - {atomic, Rs} -> - {ok, lists:map(fun (R) -> R#vcard.vcard end, Rs)}; - {aborted, _Reason} -> error - end. + Rs = mnesia:dirty_read(vcard, US), + {ok, lists:map(fun (R) -> R#vcard.vcard end, Rs)}. set_vcard(LUser, LServer, VCARD, VCardSearch) -> US = {LUser, LServer}, From b013c29c7ee0930fa056b8a0722e7ace30e6ce36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 19 May 2017 16:56:37 +0200 Subject: [PATCH 40/91] Fix values put in args_examples --- src/ejabberd_admin.erl | 2 +- src/ejabberd_oauth.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index c0429fb0c..d56ca0be7 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -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}, diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index af8680ef4..93ab704b6 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -80,7 +80,7 @@ get_commands_spec() -> module = ?MODULE, function = oauth_issue_token, args = [{jid, string},{ttl, integer}, {scopes, string}], policy = restricted, - args_example = ["user@server.com", "connected_users_number;muc_online_rooms"], + args_example = ["user@server.com", 3600, "connected_users_number;muc_online_rooms"], args_desc = ["Jid for which issue token", "Time to live of generated token in seconds", "List of scopes to allow, separated by ';'"], From 654d907dcfed85403c4a45070e093287ffe54328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 19 May 2017 17:03:41 +0200 Subject: [PATCH 41/91] export_all is not needed here --- src/ejabberd_oauth.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index 93ab704b6..9dd4268e1 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -29,8 +29,6 @@ -behaviour(gen_server). -behaviour(ejabberd_config). --compile(export_all). - %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). From 35d19b32f40a4f6aedecca36c3145ab3013ecf87 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sat, 20 May 2017 22:36:32 +0300 Subject: [PATCH 42/91] Implement cache for mod_privacy/mod_blocking --- include/mod_privacy.hrl | 8 - src/mod_admin_extra.erl | 5 +- src/mod_blocking.erl | 212 +++++++-------- src/mod_blocking_mnesia.erl | 100 ------- src/mod_blocking_riak.erl | 113 -------- src/mod_blocking_sql.erl | 107 -------- src/mod_privacy.erl | 510 ++++++++++++++++++++++-------------- src/mod_privacy_mnesia.erl | 150 +++++------ src/mod_privacy_riak.erl | 173 +++++------- src/mod_privacy_sql.erl | 298 ++++++++++----------- src/mod_roster.erl | 18 +- src/prosody2ejabberd.erl | 2 +- test/privacy_tests.erl | 16 +- 13 files changed, 718 insertions(+), 994 deletions(-) delete mode 100644 src/mod_blocking_mnesia.erl delete mode 100644 src/mod_blocking_riak.erl delete mode 100644 src/mod_blocking_sql.erl diff --git a/include/mod_privacy.hrl b/include/mod_privacy.hrl index b628a5e1e..0d773fb20 100644 --- a/include/mod_privacy.hrl +++ b/include/mod_privacy.hrl @@ -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]). diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 0b3b007ce..fa681f87a 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -1485,10 +1485,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. %%% diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index 6b6f4f19a..738c5e16f 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -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]. diff --git a/src/mod_blocking_mnesia.erl b/src/mod_blocking_mnesia.erl deleted file mode 100644 index f22e8171d..000000000 --- a/src/mod_blocking_mnesia.erl +++ /dev/null @@ -1,100 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_blocking_mnesia.erl -%%% Author : Evgeny Khramtsov -%%% Created : 14 Apr 2016 by Evgeny Khramtsov -%%% -%%% -%%% 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 -%%%=================================================================== diff --git a/src/mod_blocking_riak.erl b/src/mod_blocking_riak.erl deleted file mode 100644 index 307cd8744..000000000 --- a/src/mod_blocking_riak.erl +++ /dev/null @@ -1,113 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_blocking_riak.erl -%%% Author : Evgeny Khramtsov -%%% Created : 14 Apr 2016 by Evgeny Khramtsov -%%% -%%% -%%% 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 -%%%=================================================================== diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl deleted file mode 100644 index 191c389d9..000000000 --- a/src/mod_blocking_sql.erl +++ /dev/null @@ -1,107 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_blocking_sql.erl -%%% Author : Evgeny Khramtsov -%%% Created : 14 Apr 2016 by Evgeny Khramtsov -%%% -%%% -%%% 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 -%%%=================================================================== diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index 32a103e40..eca229813 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -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">>; diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl index efa4ae6c8..7449262b9 100644 --- a/src/mod_privacy_mnesia.erl +++ b/src/mod_privacy_mnesia.erl @@ -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. diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl index 28851d042..0cd39c110 100644 --- a/src/mod_privacy_riak.erl +++ b/src/mod_privacy_riak.erl @@ -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()). diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl index 615ef7a0d..45c86e12c 100644 --- a/src/mod_privacy_sql.erl +++ b/src/mod_privacy_sql.erl @@ -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(LUser, Name); + {selected, [{Default}]} -> + if Name == Default -> + {error, conflict}; + true -> + remove_privacy_list(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,48 @@ item_to_raw(#listitem{type = Type, value = Value, {SType, SValue, SAction, Order, MatchAll, MatchIQ, MatchMessage, MatchPresenceIn, MatchPresenceOut}. -sql_get_default_privacy_list(LUser, LServer) -> +get_default_privacy_list(LUser, LServer) -> sql_queries:get_default_privacy_list(LServer, LUser). -sql_get_default_privacy_list_t(LUser) -> +get_default_privacy_list_t(LUser) -> sql_queries:get_default_privacy_list_t(LUser). -sql_get_privacy_list_names(LUser, LServer) -> +get_privacy_list_names(LUser, LServer) -> sql_queries:get_privacy_list_names(LServer, LUser). -sql_get_privacy_list_names_t(LUser) -> +get_privacy_list_names_t(LUser) -> sql_queries:get_privacy_list_names_t(LUser). -sql_get_privacy_list_id(LUser, LServer, Name) -> - sql_queries:get_privacy_list_id(LServer, LUser, Name). - -sql_get_privacy_list_id_t(LUser, Name) -> +get_privacy_list_id_t(LUser, Name) -> sql_queries:get_privacy_list_id_t(LUser, Name). -sql_get_privacy_list_data(LUser, LServer, Name) -> +get_privacy_list_data(LUser, LServer, Name) -> sql_queries:get_privacy_list_data(LServer, LUser, Name). -sql_get_privacy_list_data_by_id(ID, LServer) -> - sql_queries:get_privacy_list_data_by_id(LServer, ID). - -sql_get_privacy_list_data_by_id_t(ID) -> - sql_queries:get_privacy_list_data_by_id_t(ID). - -sql_set_default_privacy_list(LUser, Name) -> +set_default_privacy_list(LUser, Name) -> sql_queries:set_default_privacy_list(LUser, Name). -sql_unset_default_privacy_list(LUser, LServer) -> - sql_queries:unset_default_privacy_list(LServer, LUser). +unset_default_privacy_list(LUser, LServer) -> + case sql_queries:unset_default_privacy_list(LServer, LUser) of + {updated, _} -> ok; + Err -> Err + end. -sql_remove_privacy_list(LUser, Name) -> - sql_queries:remove_privacy_list(LUser, Name). +remove_privacy_list(LUser, Name) -> + case sql_queries:remove_privacy_list(LUser, Name) of + {updated, 0} -> {error, notfound}; + {updated, _} -> ok; + Err -> Err + end. -sql_add_privacy_list(LUser, Name) -> +add_privacy_list(LUser, Name) -> sql_queries:add_privacy_list(LUser, Name). -sql_set_privacy_list(ID, RItems) -> +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 sql_queries:del_privacy_lists(LServer, LUser) of + {updated, _} -> ok; + Err -> Err + end. diff --git a/src/mod_roster.erl b/src/mod_roster.erl index d1dc714ef..28bc06171 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -368,15 +368,16 @@ get_roster_item(LUser, LServer, LJID) -> {ok, Item} -> Item; error -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID} + LBJID = jid:remove_resource(LJID), + #roster{usj = {LUser, LServer, LBJID}, + us = {LUser, LServer}, jid = LBJID} end. get_subscription_and_groups(LUser, LServer, LJID) -> + LBJID = jid:remove_resource(LJID), Mod = gen_mod:db_mod(LServer, ?MODULE), Res = case use_cache(Mod, LServer, roster) of true -> - LBJID = jid:remove_resource(LJID), ets_cache:lookup( ?ROSTER_ITEM_CACHE, {LUser, LServer, LBJID}, fun() -> @@ -384,19 +385,12 @@ get_subscription_and_groups(LUser, LServer, LJID) -> case lists:keyfind(LBJID, #roster.jid, Items) of #roster{subscription = Sub, groups = Groups} -> {ok, {Sub, Groups}}; - false when element(3, LJID) == <<"">> -> - error; false -> - case lists:keyfind(LJID, #roster.jid, Items) of - {Sub, Groups} -> - {ok, {Sub, Groups}}; - false -> - error - end + error end end); false -> - Mod:read_subscription_and_groups(LUser, LServer, LJID) + Mod:read_subscription_and_groups(LUser, LServer, LBJID) end, case Res of {ok, SubAndGroups} -> diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index f575724c6..072da0908 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -210,7 +210,7 @@ convert_data(Host, "privacy", User, [Data]) -> ListItems -> [{Name, ListItems}] end end, Lists)}, - mod_privacy:set_privacy_list(Priv); + mod_privacy:set_list(Priv); convert_data(_Host, _Type, _User, _Data) -> ok. diff --git a/test/privacy_tests.erl b/test/privacy_tests.erl index 44c050cbf..cf4168262 100644 --- a/test/privacy_tests.erl +++ b/test/privacy_tests.erl @@ -43,6 +43,7 @@ single_cases() -> [single_test(feature_enabled), single_test(set_get_list), single_test(get_list_non_existent), + single_test(get_empty_lists), single_test(set_default), single_test(del_default), single_test(set_default_non_existent), @@ -52,8 +53,7 @@ single_cases() -> single_test(remove_list), single_test(remove_default_list), single_test(remove_active_list), - %% TODO: this should be fixed - %% single_test(remove_list_non_existent), + single_test(remove_list_non_existent), single_test(allow_local_server), single_test(malformed_iq_query), single_test(malformed_get), @@ -98,6 +98,12 @@ get_list_non_existent(Config) -> #stanza_error{reason = 'item-not-found'} = get_list(Config, ListName), disconnect(Config). +get_empty_lists(Config) -> + #privacy_query{default = none, + active = none, + lists = []} = get_lists(Config), + disconnect(Config). + set_default(Config) -> ListName = <<"set-default">>, Item = #privacy_item{order = 0, action = deny}, @@ -561,12 +567,6 @@ del_list(Config, Name) -> lists = [#privacy_list{ name = Name}]}]}) of #iq{type = result, sub_els = []} -> - ct:comment("Receiving privacy list push"), - #iq{type = set, id = ID, - sub_els = [#privacy_query{lists = [#privacy_list{ - name = Name}]}]} = - recv_iq(Config), - send(Config, #iq{type = result, id = ID}), ok; #iq{type = error} = Err -> xmpp:get_error(Err) From be50d57dddacc8a3a5bf0f0ab86b1b3eb08fe974 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 21 May 2017 11:31:30 +0300 Subject: [PATCH 43/91] Declare ejabberd_oauth behaviour --- src/ejabberd_oauth.erl | 4 ++++ src/ejabberd_oauth_mnesia.erl | 1 + src/ejabberd_oauth_rest.erl | 2 +- src/ejabberd_oauth_sql.erl | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl index 9dd4268e1..026b30680 100644 --- a/src/ejabberd_oauth.erl +++ b/src/ejabberd_oauth.erl @@ -63,6 +63,10 @@ -include("ejabberd_commands.hrl"). +-callback init() -> any(). +-callback store(#oauth_token{}) -> ok | {error, any()}. +-callback lookup(binary()) -> {ok, #oauth_token{}} | error. +-callback clean(non_neg_integer()) -> any(). %% There are two ways to obtain an oauth token: %% * Using the web form/api results in the token being generated in behalf of the user providing the user/pass diff --git a/src/ejabberd_oauth_mnesia.erl b/src/ejabberd_oauth_mnesia.erl index c84174655..8a075c699 100644 --- a/src/ejabberd_oauth_mnesia.erl +++ b/src/ejabberd_oauth_mnesia.erl @@ -25,6 +25,7 @@ %%%------------------------------------------------------------------- -module(ejabberd_oauth_mnesia). +-behaviour(ejabberd_oauth). -export([init/0, store/1, diff --git a/src/ejabberd_oauth_rest.erl b/src/ejabberd_oauth_rest.erl index 404edd1fd..206fab436 100644 --- a/src/ejabberd_oauth_rest.erl +++ b/src/ejabberd_oauth_rest.erl @@ -25,7 +25,7 @@ %%%------------------------------------------------------------------- -module(ejabberd_oauth_rest). - +-behaviour(ejabberd_oauth). -behaviour(ejabberd_config). -export([init/0, diff --git a/src/ejabberd_oauth_sql.erl b/src/ejabberd_oauth_sql.erl index 5c4a96641..14eaca6a8 100644 --- a/src/ejabberd_oauth_sql.erl +++ b/src/ejabberd_oauth_sql.erl @@ -25,7 +25,7 @@ %%%------------------------------------------------------------------- -module(ejabberd_oauth_sql). - +-behaviour(ejabberd_oauth). -compile([{parse_transform, ejabberd_sql_pt}]). -export([init/0, From af29fb21df63a562d2d840623c4d2012c99ca410 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 21 May 2017 11:33:16 +0300 Subject: [PATCH 44/91] Get rid of detection of modules' db_type detection The detection sometimes leads to errorneous warnings. We need to improve it later. For now I just remove the detection as it doesn't fully work anyway. --- src/ejabberd_redis_sup.erl | 8 +------- src/ejabberd_riak_sup.erl | 7 +------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ejabberd_redis_sup.erl b/src/ejabberd_redis_sup.erl index 39b7ef798..9b3cc11bb 100644 --- a/src/ejabberd_redis_sup.erl +++ b/src/ejabberd_redis_sup.erl @@ -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( diff --git a/src/ejabberd_riak_sup.erl b/src/ejabberd_riak_sup.erl index 30be65c4f..67b50ea6a 100644 --- a/src/ejabberd_riak_sup.erl +++ b/src/ejabberd_riak_sup.erl @@ -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, []). From 470669fa6b5200bb522482b1c6e1c3f1eac2c754 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 21 May 2017 11:40:24 +0300 Subject: [PATCH 45/91] Get rid of db_type warning for mod_vcard_xupdate --- src/mod_vcard_xupdate.erl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 74f6b7364..0da8198d9 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -162,14 +162,6 @@ compute_hash(VCard) -> %%==================================================================== %% Options %%==================================================================== -mod_opt_type(db_type) -> - fun(_) -> - ?WARNING_MSG("option 'db_type' for module '~s' has no effect: " - "the module doesn't require database anymore; " - "feel free to delete any tables related to the module", - [?MODULE]), - erlang:error(badarg) - end; mod_opt_type(O) when O == cache_life_time; O == cache_size -> fun (I) when is_integer(I), I > 0 -> I; (infinity) -> infinity @@ -177,4 +169,4 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size -> mod_opt_type(O) when O == use_cache; O == cache_missed -> fun (B) when is_boolean(B) -> B end; mod_opt_type(_) -> - [db_type, cache_life_time, cache_size, use_cache, cache_missed]. + [cache_life_time, cache_size, use_cache, cache_missed]. From 0a77b9f43e1775cfd9c6fc8fcaf943b0b703b919 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 21 May 2017 13:30:46 +0300 Subject: [PATCH 46/91] Get rid of a workaround against old Erlang bug --- src/ejabberd_receiver.erl | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index 24eb92fad..44c29680c 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -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 From 66a4e405e05b87a7e90bb6ed1adf66ec44d87c23 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 21 May 2017 14:24:57 +0300 Subject: [PATCH 47/91] Improve mod_metrics * Do not spawn a process per event * Avoid UDP socket creation on every event * Get rid of calls to str.erl module * Add options 'ip' and 'port' --- src/mod_metrics.erl | 54 +++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index ecae7cd1b..450fe4141 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -32,8 +32,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). --export([start/2, stop/1, send_metrics/4, 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, @@ -41,6 +40,8 @@ s2s_send_packet/1, s2s_receive_packet/1, remove_user/2, register_user/2]). +-define(SOCKET_NAME, mod_metrics_udp_socket). + %%==================================================================== %% API %%==================================================================== @@ -125,20 +126,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 = <>, - 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() of {ok, Socket} -> case Probe of {Key, Val} -> @@ -150,11 +151,36 @@ send_metrics(Host, Probe, Peer, Port) -> Data = <>, 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. +get_socket() -> + 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 -> + {ok, Socket} + 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(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]. From d88e4d495ffc2ae950f77e440aa7c5d06c864309 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 21 May 2017 23:21:13 +0300 Subject: [PATCH 48/91] Don't store messages via a single process --- src/mod_offline.erl | 195 +++++++++++++------------------------ src/mod_offline_mnesia.erl | 27 +---- src/mod_offline_riak.erl | 32 ++---- src/mod_offline_sql.erl | 41 +++----- src/prosody2ejabberd.erl | 19 ++-- src/sql_queries.erl | 11 +-- 6 files changed, 106 insertions(+), 219 deletions(-) diff --git a/src/mod_offline.erl b/src/mod_offline.erl index c06bb8976..2c2c6185a 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -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, diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl index d0d0de418..a725ab003 100644 --- a/src/mod_offline_mnesia.erl +++ b/src/mod_offline_mnesia.erl @@ -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}, diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl index ffc1450aa..5d0fd1af8 100644 --- a/src/mod_offline_riak.erl +++ b/src/mod_offline_riak.erl @@ -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(), diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index a8c587679..48b32be81 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -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,30 +44,21 @@ 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 sql_queries:add_spool(LUser, LServer, XML) of + {updated, _} -> + ok; + _ -> + {error, db_failure} end. pop_messages(LUser, LServer) -> diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 072da0908..312a177be 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -185,15 +185,16 @@ convert_data(_Host, "config", _User, [Data]) -> convert_data(Host, "offline", User, [Data]) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Host), - Msgs = lists:flatmap( - fun({_, RawXML}) -> - case deserialize(RawXML) of - [El] -> el_to_offline_msg(LUser, LServer, El); - _ -> [] - end - end, Data), - mod_offline:store_offline_msg( - LServer, {LUser, LServer}, Msgs, length(Msgs), infinity); + lists:foreach( + fun({_, RawXML}) -> + case deserialize(RawXML) of + [El] -> + Msg = el_to_offline_msg(LUser, LServer, El), + ok = mod_offline:store_offline_msg(Msg); + _ -> + ok + end + end, Data); convert_data(Host, "privacy", User, [Data]) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Host), diff --git a/src/sql_queries.erl b/src/sql_queries.erl index 2f2e55863..0cf595bdf 100644 --- a/src/sql_queries.erl +++ b/src/sql_queries.erl @@ -37,7 +37,7 @@ set_password_scram_t/6, add_user/3, add_user_scram/6, del_user/2, del_user_return_password/3, list_users/1, list_users/2, users_number/1, users_number/2, - add_spool_sql/2, add_spool/2, get_and_del_spool_msg_t/2, + add_spool/3, get_and_del_spool_msg_t/2, del_spool_msg/2, get_roster/2, get_roster_jid_groups/2, get_roster_groups/3, del_user_roster_t/2, get_roster_by_jid/3, get_rostergroup_by_jid/3, @@ -273,11 +273,10 @@ users_number(LServer, [{prefix, Prefix}]) users_number(LServer, []) -> users_number(LServer). -add_spool_sql(LUser, XML) -> - ?SQL("insert into spool(username, xml) values (%(LUser)s, %(XML)s)"). - -add_spool(LServer, Queries) -> - ejabberd_sql:sql_transaction(LServer, Queries). +add_spool(LUser, LServer, XML) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("insert into spool(username, xml) values (%(LUser)s, %(XML)s)")). get_and_del_spool_msg_t(LServer, LUser) -> F = fun () -> From 3a96d72a7f8aec54ab19e1b88968b48d714ce01e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 22 May 2017 10:34:57 +0300 Subject: [PATCH 49/91] Implement cache for mod_private --- src/mod_private.erl | 161 +++++++++++++++++++++++++++++++------ src/mod_private_mnesia.erl | 43 +++++++--- src/mod_private_riak.erl | 31 ++++--- src/mod_private_sql.erl | 62 +++++++++----- 4 files changed, 229 insertions(+), 68 deletions(-) diff --git a/src/mod_private.erl b/src/mod_private.erl index 8f9059d90..1cc5e3c11 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -37,21 +37,27 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - -include("xmpp.hrl"). -include("mod_private.hrl"). +-define(PRIVATE_CACHE, private_cache). + -callback init(binary(), gen_mod:opts()) -> any(). -callback import(binary(), binary(), [binary()]) -> ok. --callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}. --callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error. --callback get_all_data(binary(), binary()) -> [xmlel()]. --callback remove_user(binary(), binary()) -> any(). +-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> ok | {error, any()}. +-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error | {error, any()}. +-callback get_all_data(binary(), binary()) -> {ok, [xmlel()]} | error | {error, any()}. +-callback del_data(binary(), binary()) -> ok | {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(remove_user, Host, ?MODULE, remove_user, 50), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, @@ -71,6 +77,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_PRIVATE, @@ -89,11 +96,23 @@ process_sm_iq(#iq{type = Type, lang = Lang, Txt = <<"No private data found in this query">>, xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)); Data when Type == set -> - set_data(LUser, LServer, Data), - xmpp:make_iq_result(IQ); + case set_data(LUser, LServer, Data) of + ok -> + xmpp:make_iq_result(IQ); + {error, _} -> + Txt = <<"Database failure">>, + Err = xmpp:err_internal_server_error(Txt, Lang), + xmpp:make_error(IQ, Err) + end; Data when Type == get -> - StorageEls = get_data(LUser, LServer, Data), - xmpp:make_iq_result(IQ, #private{xml_els = StorageEls}) + case get_data(LUser, LServer, Data) of + {error, _} -> + Txt = <<"Database failure">>, + Err = xmpp:err_internal_server_error(Txt, Lang), + xmpp:make_error(IQ, Err); + Els -> + xmpp:make_iq_result(IQ, #private{xml_els = Els}) + end end; process_sm_iq(#iq{lang = Lang} = IQ) -> Txt = <<"Query to another users is forbidden">>, @@ -109,35 +128,124 @@ filter_xmlels(Els) -> end end, Els). --spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}. +-spec set_data(binary(), binary(), [{binary(), xmlel()}]) -> ok | {error, _}. set_data(LUser, LServer, Data) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:set_data(LUser, LServer, Data). + case Mod:set_data(LUser, LServer, Data) of + ok -> + delete_cache(Mod, LServer, LServer, Data); + {error, _} = Err -> + Err + end. --spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()]. +-spec get_data(binary(), binary(), [{binary(), xmlel()}]) -> [xmlel()] | {error, _}. get_data(LUser, LServer, Data) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - lists:map( - fun({NS, El}) -> - case Mod:get_data(LUser, LServer, NS) of + lists:foldr( + fun(_, {error, _} = Err) -> + Err; + ({NS, El}, Els) -> + Res = case use_cache(Mod, LServer) of + true -> + ets_cache:lookup( + ?PRIVATE_CACHE, {LUser, LServer, NS}, + fun() -> Mod:get_data(LUser, LServer, NS) end); + false -> + Mod:get_data(LUser, LServer, NS) + end, + case Res of {ok, StorageEl} -> - StorageEl; + [StorageEl|Els]; error -> - El + [El|Els]; + {error, _} = Err -> + Err end - end, Data). + end, [], Data). --spec get_data(binary(), binary()) -> [xmlel()]. +-spec get_data(binary(), binary()) -> [xmlel()] | {error, _}. get_data(LUser, LServer) -> Mod = gen_mod:db_mod(LServer, ?MODULE), - Mod:get_all_data(LUser, LServer). + case Mod:get_all_data(LUser, LServer) of + {ok, Els} -> Els; + error -> []; + {error, _} = Err -> Err + 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), Mod = gen_mod:db_mod(Server, ?MODULE), - Mod:remove_user(LUser, LServer). + Data = case use_cache(Mod, LServer) of + true -> + case Mod:get_all_data(LUser, LServer) of + {ok, Els} -> filter_xmlels(Els); + _ -> [] + end; + false -> + [] + end, + Mod:del_data(LUser, LServer), + delete_cache(Mod, LUser, LServer, Data). + +-spec delete_cache(module(), binary(), binary(), [{binary(), xmlel()}]) -> ok. +delete_cache(Mod, LUser, LServer, Data) -> + case use_cache(Mod, LServer) of + true -> + Nodes = cache_nodes(Mod, LServer), + lists:foreach( + fun({NS, _}) -> + ets_cache:delete(?PRIVATE_CACHE, + {LUser, LServer, NS}, + Nodes) + end, Data); + false -> + 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(?PRIVATE_CACHE, CacheOpts); + false -> + ets_cache:delete(?PRIVATE_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() -> [{<<"private_storage">>, 4}]. @@ -159,4 +267,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]. diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl index 67417bb7f..04c1a04a1 100644 --- a/src/mod_private_mnesia.erl +++ b/src/mod_private_mnesia.erl @@ -27,8 +27,8 @@ -behaviour(mod_private). %% API --export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, - import/3]). +-export([init/2, set_data/3, get_data/3, get_all_data/2, del_data/2, + use_cache/1, import/3]). -export([need_transform/1, transform/1]). -include("xmpp.hrl"). @@ -43,6 +43,16 @@ init(_Host, _Opts) -> [{disc_only_copies, [node()]}, {attributes, record_info(fields, private_storage)}]). +use_cache(Host) -> + case mnesia:table_info(private_storage, storage_type) of + disc_only_copies -> + gen_mod:get_module_opt( + Host, mod_private, use_cache, + ejabberd_config:use_cache(Host)); + _ -> + false + end. + set_data(LUser, LServer, Data) -> F = fun () -> lists:foreach( @@ -53,7 +63,7 @@ set_data(LUser, LServer, Data) -> xml = Xmlel}) end, Data) end, - mnesia:transaction(F). + transaction(F). get_data(LUser, LServer, XmlNS) -> case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of @@ -64,13 +74,18 @@ get_data(LUser, LServer, XmlNS) -> end. get_all_data(LUser, LServer) -> - lists:flatten( - mnesia:dirty_select(private_storage, - [{#private_storage{usns = {LUser, LServer, '_'}, - xml = '$1'}, - [], ['$1']}])). + case lists:flatten( + mnesia:dirty_select(private_storage, + [{#private_storage{usns = {LUser, LServer, '_'}, + xml = '$1'}, + [], ['$1']}])) of + [] -> + error; + Res -> + {ok, Res} + end. -remove_user(LUser, LServer) -> +del_data(LUser, LServer) -> F = fun () -> Namespaces = mnesia:select(private_storage, [{#private_storage{usns = @@ -86,7 +101,7 @@ remove_user(LUser, LServer) -> end, Namespaces) end, - mnesia:transaction(F). + transaction(F). import(LServer, <<"private_storage">>, [LUser, XMLNS, XML, _TimeStamp]) -> @@ -110,3 +125,11 @@ transform(#private_storage{usns = {U, S, NS}, xml = El} = R) -> %%%=================================================================== %%% 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. diff --git a/src/mod_private_riak.erl b/src/mod_private_riak.erl index 5c5185573..be175f071 100644 --- a/src/mod_private_riak.erl +++ b/src/mod_private_riak.erl @@ -27,7 +27,7 @@ -behaviour(mod_private). %% API --export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, +-export([init/2, set_data/3, get_data/3, get_all_data/2, del_data/2, import/3]). -include("xmpp.hrl"). @@ -40,37 +40,42 @@ init(_Host, _Opts) -> ok. set_data(LUser, LServer, Data) -> - lists:foreach( - fun({XMLNS, El}) -> + lists:foldl( + fun(_, {error, _} = Err) -> + Err; + ({XMLNS, El}, _) -> ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS}, xml = El}, private_storage_schema(), [{'2i', [{<<"us">>, {LUser, LServer}}]}]) - end, Data), - {atomic, ok}. + end, ok, Data). get_data(LUser, LServer, XMLNS) -> case ejabberd_riak:get(private_storage, private_storage_schema(), {LUser, LServer, XMLNS}) of {ok, #private_storage{xml = El}} -> {ok, El}; - _ -> - error + {error, notfound} -> + error; + Err -> + Err end. get_all_data(LUser, LServer) -> case ejabberd_riak:get_by_index( private_storage, private_storage_schema(), <<"us">>, {LUser, LServer}) of + {ok, []} -> + error; {ok, Res} -> - [El || #private_storage{xml = El} <- Res]; - _ -> - [] + {ok, [El || #private_storage{xml = El} <- Res]}; + Err -> + Err end. -remove_user(LUser, LServer) -> - {atomic, ejabberd_riak:delete_by_index(private_storage, - <<"us">>, {LUser, LServer})}. +del_data(LUser, LServer) -> + ejabberd_riak:delete_by_index(private_storage, + <<"us">>, {LUser, LServer}). import(LServer, <<"private_storage">>, [LUser, XMLNS, XML, _TimeStamp]) -> diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl index 800b31a21..d7e4b3aef 100644 --- a/src/mod_private_sql.erl +++ b/src/mod_private_sql.erl @@ -27,11 +27,12 @@ -behaviour(mod_private). %% API --export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, +-export([init/2, set_data/3, get_data/3, get_all_data/2, del_data/2, import/3, export/1]). -include("xmpp.hrl"). -include("mod_private.hrl"). +-include("logger.hrl"). %%%=================================================================== %%% API @@ -48,39 +49,46 @@ set_data(LUser, LServer, Data) -> LServer, LUser, XMLNS, SData) end, Data) end, - ejabberd_sql:sql_transaction(LServer, F). + case ejabberd_sql:sql_transaction(LServer, F) of + {atomic, ok} -> + ok; + _ -> + {error, db_failure} + end. get_data(LUser, LServer, XMLNS) -> - case catch sql_queries:get_private_data(LServer, LUser, XMLNS) of + case sql_queries:get_private_data(LServer, LUser, XMLNS) of {selected, [{SData}]} -> - case fxml_stream:parse_element(SData) of - Data when is_record(Data, xmlel) -> - {ok, Data}; - _ -> - error - end; + parse_element(LUser, LServer, SData); + {selected, []} -> + error; _ -> - error + {error, db_failure} end. get_all_data(LUser, LServer) -> case catch sql_queries:get_private_data(LServer, LUser) of + {selected, []} -> + error; {selected, Res} -> - lists:flatmap( - fun({_, SData}) -> - case fxml_stream:parse_element(SData) of - #xmlel{} = El -> - [El]; - _ -> - [] - end - end, Res); + {ok, lists:flatmap( + fun({_, SData}) -> + case parse_element(LUser, LServer, SData) of + {ok, El} -> [El]; + error -> [] + end + end, Res)}; _ -> - [] + {error, db_failure} end. -remove_user(LUser, LServer) -> - sql_queries:del_user_private_storage(LServer, LUser). +del_data(LUser, LServer) -> + case sql_queries:del_user_private_storage(LServer, LUser) of + {updated, _} -> + ok; + _ -> + {error, db_failure} + end. export(_Server) -> [{private_storage, @@ -99,3 +107,13 @@ import(_, _, _) -> %%%=================================================================== %%% Internal functions %%%=================================================================== +parse_element(LUser, LServer, XML) -> + case fxml_stream:parse_element(XML) of + El when is_record(El, xmlel) -> + {ok, El}; + _ -> + ?ERROR_MSG("malformed XML element in SQL table " + "'private_storage' for user ~s@~s: ~s", + [LUser, LServer, XML]), + error + end. From 504860f065a889584b0e7b52e3f06cfa486723ac Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 22 May 2017 11:29:53 +0300 Subject: [PATCH 50/91] Don't leak with UDP sockets --- src/mod_metrics.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index 450fe4141..8906ef32e 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -41,6 +41,7 @@ remove_user/2, register_user/2]). -define(SOCKET_NAME, mod_metrics_udp_socket). +-define(SOCKET_REGISTER_RETRIES, 10). %%==================================================================== %% API @@ -139,7 +140,7 @@ send_metrics(Host, Probe, Peer, Port) -> [Node|_] = binary:split(FQDN, <<".">>), BaseId = <>, TS = integer_to_binary(p1_time_compat:system_time(seconds)), - case get_socket() of + case get_socket(?SOCKET_REGISTER_RETRIES) of {ok, Socket} -> case Probe of {Key, Val} -> @@ -156,15 +157,16 @@ send_metrics(Host, Probe, Peer, Port) -> Err end. -get_socket() -> +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 -> - {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", From 908bedeaa614b0736afd0fdd29cd69897dff7fc3 Mon Sep 17 00:00:00 2001 From: Badlop Date: Mon, 22 May 2017 12:55:32 +0200 Subject: [PATCH 51/91] Describe command arguments and results in mod_muc_admin --- src/mod_muc_admin.erl | 67 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index cf42e86c7..ad387b19b 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -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, _, _} <- mod_muc:get_online_rooms(Host)] end, Hosts). From d7878ef1316d68910c7e2a1b67476bc210f1bc45 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Mon, 22 May 2017 16:14:28 +0300 Subject: [PATCH 52/91] Implement cache for mod_announce --- src/mod_announce.erl | 156 +++++++++++++++++++++++++++++++----- src/mod_announce_mnesia.erl | 26 +++--- src/mod_announce_riak.erl | 37 ++++----- src/mod_announce_sql.erl | 41 +++++++--- 4 files changed, 202 insertions(+), 58 deletions(-) diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 379cd3ca0..39d68406f 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -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">>, <>]). +-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), @@ -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), diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl index c298627eb..f2e5c1c49 100644 --- a/src/mod_announce_mnesia.erl +++ b/src/mod_announce_mnesia.erl @@ -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. diff --git a/src/mod_announce_riak.erl b/src/mod_announce_riak.erl index b231dec9c..04a29a687 100644 --- a/src/mod_announce_riak.erl +++ b/src/mod_announce_riak.erl @@ -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), diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl index 42d990efc..1dea0ba75 100644 --- a/src/mod_announce_sql.erl +++ b/src/mod_announce_sql.erl @@ -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. From 268065e5c495cc952bcb2ad737f8c40e188d8964 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 23 May 2017 09:27:52 +0300 Subject: [PATCH 53/91] Validate all certfiles on startup --- src/ejabberd_pkix.erl | 31 +++++++++++++++++++++---------- src/ejabberd_sql.erl | 4 ++-- src/eldap.erl | 19 +++++++++++++------ src/eldap_utils.erl | 10 +++++++++- src/mod_shared_roster_ldap.erl | 2 +- src/mod_vcard_ldap.erl | 2 +- 6 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl index 7c03f1772..f9f0472f6 100644 --- a/src/ejabberd_pkix.erl +++ b/src/ejabberd_pkix.erl @@ -27,7 +27,7 @@ %% API -export([start_link/0, add_certfile/1, format_error/1, opt_type/1, - get_certfile/1, route_registered/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]). @@ -56,15 +56,16 @@ %%%=================================================================== -spec add_certfile(filename:filename()) -> ok | {error, cert_error() | file:posix()}. -add_certfile(Path0) -> - Path = case filename:pathtype(Path0) of - relative -> - {ok, CWD} = file:get_cwd(), - iolist_to_binary(filename:join(CWD, Path0)); - _ -> - iolist_to_binary(Path0) - end, - gen_server:call(?MODULE, {add_certfile, Path}). +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}). @@ -515,6 +516,16 @@ get_cert_path(G, [Root|_] = 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). diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 7d607781c..3b252631e 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -1104,7 +1104,7 @@ 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 misc:try_read_file/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; @@ -1115,6 +1115,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]. diff --git a/src/eldap.erl b/src/eldap.erl index 3c565e71d..f47550353 100644 --- a/src/eldap.erl +++ b/src/eldap.erl @@ -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, @@ -577,11 +578,17 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) -> end; PT -> PT end, + 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 proplists:get_value(tls_depth, Opts) of undefined -> @@ -596,11 +603,11 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) -> "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, diff --git a/src/eldap_utils.erl b/src/eldap_utils.erl index 1dcdf0b48..5924c8af7 100644 --- a/src/eldap_utils.erl +++ b/src/eldap_utils.erl @@ -177,6 +177,7 @@ get_config(Host, Opts) -> 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, @@ -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, @@ -339,6 +341,7 @@ collect_parts_bit([],Acc,Uacc) -> (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()); @@ -366,6 +369,10 @@ 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(misc:try_read_file(S)) end; opt_type(ldap_tls_depth) -> @@ -390,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]. diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl index 30cd44568..66cbebd93 100644 --- a/src/mod_shared_roster_ldap.erl +++ b/src/mod_shared_roster_ldap.erl @@ -583,7 +583,7 @@ mod_opt_type(ldap_servers) -> mod_opt_type(ldap_tls_cacertfile) -> fun misc:try_read_file/1; mod_opt_type(ldap_tls_certfile) -> - fun misc:try_read_file/1; + fun ejabberd_pkix:try_certfile/1; mod_opt_type(ldap_tls_depth) -> fun (I) when is_integer(I), I >= 0 -> I end; mod_opt_type(ldap_tls_verify) -> diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index 6a690779a..f1f076468 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -469,7 +469,7 @@ mod_opt_type(ldap_servers) -> mod_opt_type(ldap_tls_cacertfile) -> fun misc:try_read_file/1; mod_opt_type(ldap_tls_certfile) -> - fun misc:try_read_file/1; + fun ejabberd_pkix:try_certfile/1; mod_opt_type(ldap_tls_depth) -> fun (I) when is_integer(I), I >= 0 -> I end; mod_opt_type(ldap_tls_verify) -> From e93762a720ee7b29d918e92e36b0ca640ed29821 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 23 May 2017 10:43:26 +0300 Subject: [PATCH 54/91] Deprecate misc:encode_base64/1 and misc:decode_base64/1 --- src/cyrsasl_scram.erl | 20 +++++---- src/ejabberd_auth.erl | 12 +++--- src/ejabberd_http.erl | 4 +- src/ejabberd_web_admin.erl | 8 ++-- src/ejabberd_websocket.erl | 2 +- src/misc.erl | 86 +++++++------------------------------- src/mod_caps.erl | 2 +- src/mod_muc_log.erl | 4 +- src/mod_muc_room.erl | 4 +- src/mod_vcard_xupdate.erl | 4 +- 10 files changed, 48 insertions(+), 98 deletions(-) diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl index b496be0a4..67820a763 100644 --- a/src/cyrsasl_scram.erl +++ b/src/cyrsasl_scram.erl @@ -112,9 +112,9 @@ mech_step(#state{step = 2} = State, ClientIn) -> true -> {StoredKey, ServerKey, Salt, IterationCount} = if is_record(Pass, scram) -> - {misc:decode_base64(Pass#scram.storedkey), - misc:decode_base64(Pass#scram.serverkey), - misc:decode_base64(Pass#scram.salt), + {base64:decode(Pass#scram.storedkey), + base64:decode(Pass#scram.serverkey), + base64:decode(Pass#scram.salt), Pass#scram.iterationcount}; true -> TempSalt = @@ -132,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, @@ -165,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, @@ -174,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, ",", @@ -195,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} diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 230bd87b5..251e36ff7 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -645,10 +645,10 @@ is_password_scram_valid(Password, Scram) -> false; _ -> IterationCount = Scram#scram.iterationcount, - Salt = misc:decode_base64(Scram#scram.salt), + Salt = base64:decode(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 + base64:decode(Scram#scram.storedkey) == StoredKey end. password_to_scram(Password) -> @@ -661,9 +661,9 @@ password_to_scram(Password, IterationCount) -> 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), + #scram{storedkey = base64:encode(StoredKey), + serverkey = base64:encode(ServerKey), + salt = base64:encode(Salt), iterationcount = IterationCount}. %%%---------------------------------------------------------------------- @@ -744,7 +744,7 @@ auth_modules(Server) -> match_passwords(Password, #scram{} = Scram, <<"">>, undefined) -> is_password_scram_valid(Password, Scram); match_passwords(Password, #scram{} = Scram, Digest, DigestFun) -> - StoredKey = misc:decode_base64(Scram#scram.storedkey), + StoredKey = base64:decode(Scram#scram.storedkey), DigRes = if Digest /= <<"">> -> Digest == DigestFun(StoredKey); true -> false diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index f513cecb0..43bbb3f04 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -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 diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index 4c2ca730a..464ea6bfd 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -665,13 +665,13 @@ css(Host) -> "}">>. favicon() -> - misc:decode_base64(<<"AAABAAEAEBAAAAEAIAAoBQAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAA1AwMAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMARQUEA+oFAwCOBAQAaAQEAGkEBABpBAQAaQQEAGoFAgBcBAAAOQAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wUFA/wEBAHOBQICXgAAAAAAAAAAAAAAAAAAAAADAwBCBwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8DAwCUAAAABwAAAAAAAAAAAwMAQgcGBf8HBgX/BwYF/wcGBf8FBQPMBAAAaAQAAD8DAwNOAwMDlgUFA/QHBgX/BwYF/wQEAHkAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8EBAGeAAAACAAAAAAAAAASAAAABQAAAAAFBQGxBwYF/wcGBf8FBAPvAAAAKAAAAAADAwBCBwYF/wcGBf8EBAHPAAAADQAAACEFBQGuBQQD8AUEAeEFBQGuBQQB9QcGBf8HBgX/BwYF/wQEAH8AAAAAAwMAQgcGBf8HBgX/BgQAbwAAAAADAwOXBQQB3gUFAdgFBQHZBQQB3QUFAdYFBAHhBQUD/gcGBf8EBAK8AAAAAAMDAEIHBgX/BwYF/wQAAD0AAAAAAAAABQAAAAEAAAABAAAAAQAAAAEAAAAFAAAAEQUFArwKBgX/BQMDxQAAAAADAwBCBwYF/wcGBf8DAwBKAAAAAwYDAFAGAwBVBgMAVAYDAFQFAgJZAAAALwAAAAAFBQGuCgYF/wUDA8QAAAAAAAAAKwUEA/QHBgX/AwMDlgAAAAAFAwOIBwYF/wcGBf8HBgX/BQQB5wAAADMAAAAWBQUD5wcGBf8EBAGbAAAAAAAAAAYFBAG9BwYF/wUFA/EDAABAAAAAAAMDA1QDAwOYBQUAhQAAACQAAAAABAQBnQcGBf8HBgX/AwMATQAAAAAAAAAAAwAAQwUFA/oHBgX/BQQB5QYDA1UAAAAAAAAAAAAAAAAAAAAXAwMAlwcGBf8HBgX/BQUBtQAAAAcAAAAAAAAAAAAAAAAEBABzBQUD/gcGBf8HBgX/BQMDyQQEAZwGBAGqBQQB5AcGBf8HBgX/BAQB0QAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAUFAmQFBAHlBwYF/wcGBf8HBgX/BwYF/wcGBf8FBQP+BQUBsAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwUFA40FBAHrBwYF/wUFA/4FAwPGBgMAUgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==">>). + base64:decode(<<"AAABAAEAEBAAAAEAIAAoBQAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAA1AwMAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMARQUEA+oFAwCOBAQAaAQEAGkEBABpBAQAaQQEAGoFAgBcBAAAOQAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wUFA/wEBAHOBQICXgAAAAAAAAAAAAAAAAAAAAADAwBCBwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8HBgX/BwYF/wcGBf8DAwCUAAAABwAAAAAAAAAAAwMAQgcGBf8HBgX/BwYF/wcGBf8FBQPMBAAAaAQAAD8DAwNOAwMDlgUFA/QHBgX/BwYF/wQEAHkAAAAAAAAAAAMDAEIHBgX/BwYF/wcGBf8EBAGeAAAACAAAAAAAAAASAAAABQAAAAAFBQGxBwYF/wcGBf8FBAPvAAAAKAAAAAADAwBCBwYF/wcGBf8EBAHPAAAADQAAACEFBQGuBQQD8AUEAeEFBQGuBQQB9QcGBf8HBgX/BwYF/wQEAH8AAAAAAwMAQgcGBf8HBgX/BgQAbwAAAAADAwOXBQQB3gUFAdgFBQHZBQQB3QUFAdYFBAHhBQUD/gcGBf8EBAK8AAAAAAMDAEIHBgX/BwYF/wQAAD0AAAAAAAAABQAAAAEAAAABAAAAAQAAAAEAAAAFAAAAEQUFArwKBgX/BQMDxQAAAAADAwBCBwYF/wcGBf8DAwBKAAAAAwYDAFAGAwBVBgMAVAYDAFQFAgJZAAAALwAAAAAFBQGuCgYF/wUDA8QAAAAAAAAAKwUEA/QHBgX/AwMDlgAAAAAFAwOIBwYF/wcGBf8HBgX/BQQB5wAAADMAAAAWBQUD5wcGBf8EBAGbAAAAAAAAAAYFBAG9BwYF/wUFA/EDAABAAAAAAAMDA1QDAwOYBQUAhQAAACQAAAAABAQBnQcGBf8HBgX/AwMATQAAAAAAAAAAAwAAQwUFA/oHBgX/BQQB5QYDA1UAAAAAAAAAAAAAAAAAAAAXAwMAlwcGBf8HBgX/BQUBtQAAAAcAAAAAAAAAAAAAAAAEBABzBQUD/gcGBf8HBgX/BQMDyQQEAZwGBAGqBQQB5AcGBf8HBgX/BAQB0QAAACEAAAAAAAAAAAAAAAAAAAAAAAAAAAUFAmQFBAHlBwYF/wcGBf8HBgX/BwYF/wcGBf8FBQP+BQUBsAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwUFA40FBAHrBwYF/wUFA/4FAwPGBgMAUgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==">>). logo() -> - misc:decode_base64(<<"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V4NgkhURIAJEgAgQASJQGgSWwNI7QYsRqG4ojdU0lAgQASJABIgAESACRIAIEAEiQAR0EYiiaAwobdmGC/8RdIyuluROBIgAESACRIAIEAEiQASIABEgAqVFAEFnmsB1J677Bmh8aYGj4USACBABIkAEiAARIAJEgAgQASJgB4EUgesduGayHe0ohQgQASJABIgAESACRIAIEAEiQARKj0CCwPUl1D2j9IARACJABIgAESACRIAIEAEiQASIABGwi0DMwPWfUW+4Xc0ojQgQASJABIgAESACRIAIEAEiQASIABAYIHDdhfMXEigiQASIABEgAkSACBABIkAEiAARIAK5IdAkcF2Hc+/NTTEKJgJEgAgQASJABIgAESACRIAIEAEiIAg0CFxX4fgsIkQEiAARIAJEgAgQASJABIgAESACRCB3BOoErqtx7IjcFaMCRIAIEAEiQASIABEgAkSACBABIkAEBIE+gesWfJ9NZIgAESACRIAIEAEiQASIABEgAkSACDiDQI/AdT/+/4AzilERIkAEiAARIAJEgAgQASJABIgAESACgkCPwPXviQgRIAJEgAgQASJABIgAESACRIAIEAHnEKgGrk/hc5BzylEhIkAEiAARIAJEgAgQASJABIgAESACCFhHgo4lEkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAFjCPx/2P3JeG4VmJoAAAAASUVORK5CYII=">>). + base64:decode(<<"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V4NgkhURIAJEgAgQASJQGgSWwNI7QYsRqG4ojdU0lAgQASJABIgAESACRIAIEAEiQAR0EYiiaAwobdmGC/8RdIyuluROBIgAESACRIAIEAEiQASIABEgAqVFAEFnmsB1J677Bmh8aYGj4USACBABIkAEiAARIAJEgAgQASJgB4EUgesduGayHe0ohQgQASJABIgAESACRIAIEAEiQARKj0CCwPUl1D2j9IARACJABIgAESACRIAIEAEiQASIABGwi0DMwPWfUW+4Xc0ojQgQASJABIgAESACRIAIEAEiQASIABAYIHDdhfMXEigiQASIABEgAkSACBABIkAEiAARIAK5IdAkcF2Hc+/NTTEKJgJEgAgQASJABIgAESACRIAIEAEiIAg0CFxX4fgsIkQEiAARIAJEgAgQASJABIgAESACRCB3BOoErqtx7IjcFaMCRIAIEAEiQASIABEgAkSACBABIkAEBIE+gesWfJ9NZIgAESACRIAIEAEiQASIABEgAkSACDiDQI/AdT/+/4AzilERIkAEiAARIAJEgAgQASJABIgAESACgkCPwPXviQgRIAJEgAgQASJABIgAESACRIAIEAHnEKgGrk/hc5BzylEhIkAEiAARIAJEgAgQASJABIgAESACCFhHgo4lEkSACBABIkAEiAARIAJEgAgQASJABIgAESACRIAIEAFjCPx/2P3JeG4VmJoAAAAASUVORK5CYII=">>). logo_fill() -> - misc:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA" + base64:decode(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA" "AAAAXNSR0IArs4c6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p" "4q/q5K/rpq/sqM/tam/ubGzn/S/AAAAEFJREFUCNdlw0s" "RwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr" @@ -1156,7 +1156,7 @@ term_to_paragraph(T, Cols) -> NumLines = length(FieldList), {NumLines, Paragraph}. -term_to_id(T) -> misc:encode_base64((term_to_binary(T))). +term_to_id(T) -> base64:encode((term_to_binary(T))). acl_parse_query(Host, Query) -> ACLs = mnesia:dirty_select(acl, diff --git a/src/ejabberd_websocket.erl b/src/ejabberd_websocket.erl index 8adf625f8..9926c20cc 100644 --- a/src/ejabberd_websocket.erl +++ b/src/ejabberd_websocket.erl @@ -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, <>)), {State, [<<"HTTP/1.1 101 Switching Protocols\r\n">>, <<"Upgrade: websocket\r\n">>, diff --git a/src/misc.erl b/src/misc.erl index b8bbe0e48..604a458af 100644 --- a/src/misc.erl +++ b/src/misc.erl @@ -28,14 +28,18 @@ -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, 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"). @@ -58,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(<>, Count, Acc) -> - take_without_spaces(Tail, Count-1, <>). - -decode_base64_bin(<<>>, Acc) -> - Acc; -decode_base64_bin(Bin, Acc) -> - case take_without_spaces(Bin, 4) of - {<>, _} -> - <>; - {<>, _} -> - <>; - {<>, Tail} -> - Acc2 = <>, - 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(<>, Acc) -> - encode_base64_bin(Tail, <>); -encode_base64_bin(<>, Acc) -> - <>; -encode_base64_bin(<>, Acc) -> - <>; -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(). @@ -160,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) -> diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 87fdebbc8..c34c81631 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -413,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); diff --git a/src/mod_muc_log.erl b/src/mod_muc_log.erl index 9bf983422..3d4c87c0f 100644 --- a/src/mod_muc_log.erl +++ b/src/mod_muc_log.erl @@ -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, diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 231b59b2a..149e6c221 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -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}. diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 0da8198d9..c9819913b 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -156,7 +156,9 @@ compute_hash(VCard) -> <<>> -> <<>>; BinVal -> - str:sha(misc:decode_base64(BinVal)) + try str:sha(base64:decode(BinVal)) + catch _:badarg -> <<>> + end end. %%==================================================================== From 6e8895f9e9d9864709f41ca84669de54342bd881 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 23 May 2017 12:25:13 +0300 Subject: [PATCH 55/91] Get rid of sql_queries.erl --- src/ejabberd_auth_sql.erl | 142 ++++++++- src/ejabberd_sql.erl | 17 +- src/mod_last_sql.erl | 14 +- src/mod_offline_sql.erl | 53 ++-- src/mod_privacy_sql.erl | 88 +++++- src/mod_private_sql.erl | 30 +- src/mod_roster_sql.erl | 152 +++++++-- src/mod_vcard_sql.erl | 43 ++- src/sql_queries.erl | 640 -------------------------------------- 9 files changed, 438 insertions(+), 741 deletions(-) delete mode 100644 src/sql_queries.erl diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index d682634f0..0d7c7b375 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -30,11 +30,12 @@ -author('alexey@process-one.net'). -behaviour(ejabberd_auth). +-behaviour(ejabberd_config). -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]). + convert_to_scram/1, opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -56,15 +57,17 @@ store_type(Server) -> ejabberd_auth:password_format(Server). set_password(User, Server, Password) -> - Res = if is_record(Password, scram) -> - sql_queries:set_password_scram_t( - Server, User, - Password#scram.storedkey, Password#scram.serverkey, - Password#scram.salt, Password#scram.iterationcount); - true -> - sql_queries:set_password_t(Server, User, Password) - end, - case Res of + 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} -> @@ -74,12 +77,12 @@ set_password(User, Server, Password) -> try_register(User, Server, Password) -> Res = if is_record(Password, scram) -> - sql_queries:add_user_scram( + add_user_scram( Server, User, Password#scram.storedkey, Password#scram.serverkey, Password#scram.salt, Password#scram.iterationcount); true -> - sql_queries:add_user(Server, User, Password) + add_user(Server, User, Password) end, case Res of {updated, 1} -> ok; @@ -87,21 +90,21 @@ try_register(User, Server, Password) -> end. get_users(Server, Opts) -> - case sql_queries:list_users(Server, Opts) of + case list_users(Server, Opts) of {selected, Res} -> [{U, Server} || {U} <- Res]; _ -> [] end. count_users(Server, Opts) -> - case sql_queries:users_number(Server, Opts) of + case users_number(Server, Opts) of {selected, [{Res}]} -> Res; _Other -> 0 end. get_password(User, Server) -> - case sql_queries:get_password_scram(Server, User) of + case get_password_scram(Server, User) of {selected, [{Password, <<>>, <<>>, 0}]} -> {ok, Password}; {selected, [{StoredKey, ServerKey, Salt, IterationCount}]} -> @@ -118,7 +121,7 @@ get_password(User, Server) -> end. remove_user(User, Server) -> - case sql_queries:del_user(Server, User) of + case del_user(Server, User) of {updated, _} -> ok; Err -> @@ -139,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 = <>, + 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 = <>, + 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 @@ -178,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]. diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 3b252631e..35d970291 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -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) -> <>. + +-spec escape(binary()) -> binary(). escape(S) -> - << <<(sql_queries:escape(Char))/binary>> || <> <= S >>. + << <<(escape_char(Char))/binary>> || <> <= 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>> || <> <= S >>; diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl index 2e3c3dd15..b777ba30d 100644 --- a/src/mod_last_sql.erl +++ b/src/mod_last_sql.erl @@ -43,7 +43,10 @@ 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, []} -> error; {selected, [{TimeStamp, Status}]} -> @@ -55,7 +58,10 @@ get_last(LUser, LServer) -> end. store_last_info(LUser, LServer, TimeStamp, Status) -> - case sql_queries:set_last_t(LServer, LUser, TimeStamp, Status) of + case ?SQL_UPSERT(LServer, "last", + ["!username=%(LUser)s", + "seconds=%(TimeStamp)d", + "state=%(Status)s"]) of ok -> ok; Err -> @@ -65,7 +71,9 @@ store_last_info(LUser, LServer, TimeStamp, Status) -> 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, diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index 48b32be81..bcf5b7aad 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -54,7 +54,10 @@ store_message(#offline_msg{us = {LUser, LServer}} = M) -> <<"Offline Storage">>), XML = fxml:element_to_binary( xmpp:encode(NewPacket)), - case sql_queries:add_spool(LUser, LServer, XML) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("insert into spool(username, xml) values " + "(%(LUser)s, %(XML)s)")) of {updated, _} -> ok; _ -> @@ -62,7 +65,7 @@ store_message(#offline_msg{us = {LUser, LServer}} = M) -> 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}) -> @@ -82,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 -> @@ -96,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}) -> @@ -144,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}) -> @@ -161,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) -> @@ -232,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). diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl index 45c86e12c..b19c95fe5 100644 --- a/src/mod_privacy_sql.erl +++ b/src/mod_privacy_sql.erl @@ -74,12 +74,12 @@ remove_list(LUser, LServer, Name) -> F = fun () -> case get_default_privacy_list_t(LUser) of {selected, []} -> - remove_privacy_list(LUser, Name); + remove_privacy_list_t(LUser, Name); {selected, [{Default}]} -> if Name == Default -> {error, conflict}; true -> - remove_privacy_list(LUser, Name) + remove_privacy_list_t(LUser, Name) end end end, @@ -309,47 +309,101 @@ item_to_raw(#listitem{type = Type, value = Value, MatchMessage, MatchPresenceIn, MatchPresenceOut}. get_default_privacy_list(LUser, LServer) -> - sql_queries:get_default_privacy_list(LServer, LUser). + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(name)s from privacy_default_list " + "where username=%(LUser)s")). get_default_privacy_list_t(LUser) -> - sql_queries:get_default_privacy_list_t(LUser). + ejabberd_sql:sql_query_t( + ?SQL("select @(name)s from privacy_default_list " + "where username=%(LUser)s")). get_privacy_list_names(LUser, LServer) -> - sql_queries:get_privacy_list_names(LServer, LUser). + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(name)s from privacy_list" + " where username=%(LUser)s")). get_privacy_list_names_t(LUser) -> - sql_queries:get_privacy_list_names_t(LUser). + ejabberd_sql:sql_query_t( + ?SQL("select @(name)s from privacy_list" + " where username=%(LUser)s")). get_privacy_list_id_t(LUser, Name) -> - sql_queries: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")). get_privacy_list_data(LUser, LServer, Name) -> - sql_queries:get_privacy_list_data(LServer, LUser, Name). + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " + "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " + "@(match_presence_out)b from privacy_list_data " + "where id =" + " (select id from privacy_list" + " where username=%(LUser)s and name=%(Name)s) " + "order by ord")). set_default_privacy_list(LUser, Name) -> - sql_queries:set_default_privacy_list(LUser, Name). + ?SQL_UPSERT_T( + "privacy_default_list", + ["!username=%(LUser)s", + "name=%(Name)s"]). unset_default_privacy_list(LUser, LServer) -> - case sql_queries:unset_default_privacy_list(LServer, LUser) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("delete from privacy_default_list" + " where username=%(LUser)s")) of {updated, _} -> ok; Err -> Err end. -remove_privacy_list(LUser, Name) -> - case sql_queries:remove_privacy_list(LUser, Name) of +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. add_privacy_list(LUser, Name) -> - sql_queries:add_privacy_list(LUser, Name). + ejabberd_sql:sql_query_t( + ?SQL("insert into privacy_list(username, name) " + "values (%(LUser)s, %(Name)s)")). set_privacy_list(ID, RItems) -> - sql_queries: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). del_privacy_lists(LUser, LServer) -> - case sql_queries:del_privacy_lists(LServer, LUser) of - {updated, _} -> ok; - Err -> Err + 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. diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl index d7e4b3aef..907eeaf3a 100644 --- a/src/mod_private_sql.erl +++ b/src/mod_private_sql.erl @@ -23,7 +23,7 @@ %%%---------------------------------------------------------------------- -module(mod_private_sql). - +-compile([{parse_transform, ejabberd_sql_pt}]). -behaviour(mod_private). %% API @@ -32,6 +32,7 @@ -include("xmpp.hrl"). -include("mod_private.hrl"). +-include("ejabberd_sql_pt.hrl"). -include("logger.hrl"). %%%=================================================================== @@ -45,8 +46,11 @@ set_data(LUser, LServer, Data) -> lists:foreach( fun({XMLNS, El}) -> SData = fxml:element_to_binary(El), - sql_queries:set_private_data( - LServer, LUser, XMLNS, SData) + ?SQL_UPSERT_T( + "private_storage", + ["!username=%(LUser)s", + "!namespace=%(XMLNS)s", + "data=%(SData)s"]) end, Data) end, case ejabberd_sql:sql_transaction(LServer, F) of @@ -57,7 +61,10 @@ set_data(LUser, LServer, Data) -> end. get_data(LUser, LServer, XMLNS) -> - case sql_queries:get_private_data(LServer, LUser, XMLNS) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("select @(data)s from private_storage" + " where username=%(LUser)s and namespace=%(XMLNS)s")) of {selected, [{SData}]} -> parse_element(LUser, LServer, SData); {selected, []} -> @@ -67,7 +74,10 @@ get_data(LUser, LServer, XMLNS) -> end. get_all_data(LUser, LServer) -> - case catch sql_queries:get_private_data(LServer, LUser) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("select @(namespace)s, @(data)s from private_storage" + " where username=%(LUser)s")) of {selected, []} -> error; {selected, Res} -> @@ -83,7 +93,9 @@ get_all_data(LUser, LServer) -> end. del_data(LUser, LServer) -> - case sql_queries:del_user_private_storage(LServer, LUser) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("delete from private_storage where username=%(LUser)s")) of {updated, _} -> ok; _ -> @@ -96,7 +108,11 @@ export(_Server) -> xml = Data}) when LServer == Host -> SData = fxml:element_to_binary(Data), - sql_queries:set_private_data_sql(LUser, XMLNS, SData); + [?SQL("delete from private_storage where" + " username=%(LUser)s and namespace=%(XMLNS)s;"), + ?SQL("insert into private_storage(username, " + "namespace, data) values (" + "%(LUser)s, %(XMLNS)s, %(SData)s);")]; (_Host, _R) -> [] end}]. diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl index 7c516568c..77899624a 100644 --- a/src/mod_roster_sql.erl +++ b/src/mod_roster_sql.erl @@ -46,27 +46,32 @@ init(_Host, _Opts) -> ok. read_roster_version(LUser, LServer) -> - case sql_queries:get_roster_version(LServer, LUser) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("select @(version)s from roster_version" + " where username = %(LUser)s")) of {selected, [{Version}]} -> {ok, Version}; - {selected, []} -> error + {selected, []} -> error; + _ -> {error, db_failure} end. write_roster_version(LUser, LServer, InTransaction, Ver) -> if InTransaction -> - sql_queries:set_roster_version(LUser, Ver); + set_roster_version(LUser, Ver); true -> - sql_queries:sql_transaction( + transaction( LServer, - fun () -> - sql_queries:set_roster_version(LUser, Ver) - end) + fun () -> set_roster_version(LUser, Ver) end) end. get_roster(LUser, LServer) -> - case catch sql_queries:get_roster(LServer, LUser) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, " + "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, " + "@(type)s from rosterusers where username=%(LUser)s")) of {selected, Items} when is_list(Items) -> - JIDGroups = case catch sql_queries:get_roster_jid_groups( - LServer, LUser) of + JIDGroups = case get_roster_jid_groups(LServer, LUser) of {selected, JGrps} when is_list(JGrps) -> JGrps; _ -> @@ -96,25 +101,24 @@ get_roster(LUser, LServer) -> roster_subscribe(_LUser, _LServer, _LJID, Item) -> ItemVals = record_to_row(Item), - sql_queries:roster_subscribe(ItemVals). + roster_subscribe(ItemVals). transaction(LServer, F) -> ejabberd_sql:sql_transaction(LServer, F). get_roster_item(LUser, LServer, LJID) -> SJID = jid:encode(LJID), - case sql_queries:get_roster_by_jid(LServer, LUser, SJID) of + case get_roster_by_jid(LServer, LUser, SJID) of {selected, [I]} -> case raw_to_record(LServer, I) of error -> error; R -> - Groups = - case sql_queries:get_roster_groups(LServer, LUser, SJID) of - {selected, JGrps} when is_list(JGrps) -> - [JGrp || {JGrp} <- JGrps]; - _ -> [] - end, + Groups = case get_roster_groups(LServer, LUser, SJID) of + {selected, JGrps} when is_list(JGrps) -> + [JGrp || {JGrp} <- JGrps]; + _ -> [] + end, {ok, R#roster{groups = Groups}} end; {selected, []} -> @@ -122,23 +126,44 @@ get_roster_item(LUser, LServer, LJID) -> end. remove_user(LUser, LServer) -> - sql_queries:del_user_roster_t(LServer, LUser), + transaction( + LServer, + fun () -> + ejabberd_sql:sql_query_t( + ?SQL("delete from rosterusers where username=%(LUser)s")), + ejabberd_sql:sql_query_t( + ?SQL("delete from rostergroups where username=%(LUser)s")) + end), ok. -update_roster(LUser, LServer, LJID, Item) -> +update_roster(LUser, _LServer, LJID, Item) -> SJID = jid:encode(LJID), ItemVals = record_to_row(Item), ItemGroups = Item#roster.groups, - sql_queries:update_roster(LServer, LUser, SJID, ItemVals, - ItemGroups). + roster_subscribe(ItemVals), + ejabberd_sql:sql_query_t( + ?SQL("delete from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")), + lists:foreach( + fun(ItemGroup) -> + ejabberd_sql:sql_query_t( + ?SQL("insert into rostergroups(username, jid, grp) " + "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")) + end, + ItemGroups). -del_roster(LUser, LServer, LJID) -> +del_roster(LUser, _LServer, LJID) -> SJID = jid:encode(LJID), - sql_queries:del_roster(LServer, LUser, SJID). + ejabberd_sql:sql_query_t( + ?SQL("delete from rosterusers" + " where username=%(LUser)s and jid=%(SJID)s")), + ejabberd_sql:sql_query_t( + ?SQL("delete from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). read_subscription_and_groups(LUser, LServer, LJID) -> SJID = jid:encode(LJID), - case catch sql_queries:get_subscription(LServer, LUser, SJID) of + case get_subscription(LServer, LUser, SJID) of {selected, [{SSubscription}]} -> Subscription = case SSubscription of <<"B">> -> both; @@ -152,8 +177,7 @@ read_subscription_and_groups(LUser, LServer, LJID) -> {subscription, SSubscription})]), none end, - Groups = case catch sql_queries:get_rostergroup_by_jid( - LServer, LUser, SJID) of + Groups = case get_rostergroup_by_jid(LServer, LUser, SJID) of {selected, JGrps} when is_list(JGrps) -> [JGrp || {JGrp} <- JGrps]; _ -> [] @@ -169,7 +193,7 @@ export(_Server) -> when LServer == Host -> ItemVals = record_to_row(R), ItemGroups = R#roster.groups, - sql_queries:update_roster_sql(ItemVals, ItemGroups); + update_roster_sql(ItemVals, ItemGroups); (_Host, _R) -> [] end}, @@ -189,6 +213,78 @@ import(_, _, _) -> %%%=================================================================== %%% Internal functions %%%=================================================================== +set_roster_version(LUser, Version) -> + ?SQL_UPSERT_T( + "roster_version", + ["!username=%(LUser)s", + "version=%(Version)s"]). + +get_roster_jid_groups(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(jid)s, @(grp)s from rostergroups where " + "username=%(LUser)s")). + +get_roster_groups(_LServer, LUser, SJID) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(grp)s from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) -> + ?SQL_UPSERT_T( + "rosterusers", + ["!username=%(LUser)s", + "!jid=%(SJID)s", + "nick=%(Name)s", + "subscription=%(SSubscription)s", + "ask=%(SAsk)s", + "askmessage=%(AskMessage)s", + "server='N'", + "subscribe=''", + "type='item'"]). + +get_roster_by_jid(_LServer, LUser, SJID) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s," + " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s," + " @(type)s from rosterusers" + " where username=%(LUser)s and jid=%(SJID)s")). + +get_rostergroup_by_jid(LServer, LUser, SJID) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(grp)s from rostergroups" + " where username=%(LUser)s and jid=%(SJID)s")). + +get_subscription(LServer, LUser, SJID) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(subscription)s from rosterusers " + "where username=%(LUser)s and jid=%(SJID)s")). + +update_roster_sql({LUser, SJID, Name, SSubscription, SAsk, AskMessage}, + ItemGroups) -> + [?SQL("delete from rosterusers where" + " username=%(LUser)s and jid=%(SJID)s;"), + ?SQL("insert into rosterusers(" + " username, jid, nick," + " subscription, ask, askmessage," + " server, subscribe, type) " + "values (" + "%(LUser)s, " + "%(SJID)s, " + "%(Name)s, " + "%(SSubscription)s, " + "%(SAsk)s, " + "%(AskMessage)s, " + "'N', '', 'item');"), + ?SQL("delete from rostergroups where" + " username=%(LUser)s and jid=%(SJID)s;")] + ++ + [?SQL("insert into rostergroups(username, jid, grp) " + "values (%(LUser)s, %(SJID)s, %(ItemGroup)s);") + || ItemGroup <- ItemGroups]. + raw_to_record(LServer, [User, SJID, Nick, SSubscription, SAsk, SAskMessage, _SServer, _SSubscribe, _SType]) -> diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index 3b38536ea..fd1d05478 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -51,7 +51,9 @@ is_search_supported(_LServer) -> true. get_vcard(LUser, LServer) -> - case catch sql_queries:get_vcard(LServer, LUser) of + case ejabberd_sql:sql_query( + LServer, + ?SQL("select @(vcard)s from vcard where username=%(LUser)s")) of {selected, [{SVCARD}]} -> case fxml_stream:parse_element(SVCARD) of {error, _Reason} -> error; @@ -86,13 +88,38 @@ set_vcard(LUser, LServer, VCARD, orgunit = OrgUnit, lorgunit = LOrgUnit}) -> SVCARD = fxml:element_to_binary(VCARD), - sql_queries:set_vcard(LServer, LUser, BDay, CTRY, - EMail, FN, Family, Given, LBDay, - LCTRY, LEMail, LFN, LFamily, - LGiven, LLocality, LMiddle, - LNickname, LOrgName, LOrgUnit, - Locality, Middle, Nickname, OrgName, - OrgUnit, SVCARD, User). + ejabberd_sql:sql_transaction( + LServer, + fun() -> + ?SQL_UPSERT(LServer, "vcard", + ["!username=%(LUser)s", + "vcard=%(SVCARD)s"]), + ?SQL_UPSERT(LServer, "vcard_search", + ["username=%(User)s", + "!lusername=%(LUser)s", + "fn=%(FN)s", + "lfn=%(LFN)s", + "family=%(Family)s", + "lfamily=%(LFamily)s", + "given=%(Given)s", + "lgiven=%(LGiven)s", + "middle=%(Middle)s", + "lmiddle=%(LMiddle)s", + "nickname=%(Nickname)s", + "lnickname=%(LNickname)s", + "bday=%(BDay)s", + "lbday=%(LBDay)s", + "ctry=%(CTRY)s", + "lctry=%(LCTRY)s", + "locality=%(Locality)s", + "llocality=%(LLocality)s", + "email=%(EMail)s", + "lemail=%(LEMail)s", + "orgname=%(OrgName)s", + "lorgname=%(LOrgName)s", + "orgunit=%(OrgUnit)s", + "lorgunit=%(LOrgUnit)s"]) + end). search(LServer, Data, AllowReturnAll, MaxMatch) -> MatchSpec = make_matchspec(LServer, Data), diff --git a/src/sql_queries.erl b/src/sql_queries.erl deleted file mode 100644 index 0cf595bdf..000000000 --- a/src/sql_queries.erl +++ /dev/null @@ -1,640 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : sql_queries.erl -%%% Author : Mickael Remond -%%% Purpose : ODBC queries dependind on back-end -%%% Created : by Mickael Remond -%%% -%%% -%%% 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(sql_queries). - --compile([{parse_transform, ejabberd_sql_pt}]). - --behaviour(ejabberd_config). - --author("mremond@process-one.net"). - --export([get_db_type/0, update/5, update_t/4, - sql_transaction/2, get_last/2, set_last_t/4, del_last/2, - get_password/2, get_password_scram/2, set_password_t/3, - set_password_scram_t/6, add_user/3, add_user_scram/6, - del_user/2, del_user_return_password/3, list_users/1, - list_users/2, users_number/1, users_number/2, - add_spool/3, get_and_del_spool_msg_t/2, - del_spool_msg/2, get_roster/2, get_roster_jid_groups/2, - get_roster_groups/3, del_user_roster_t/2, - get_roster_by_jid/3, get_rostergroup_by_jid/3, - del_roster/3, del_roster_sql/2, update_roster/5, - update_roster_sql/2, roster_subscribe/1, - get_subscription/3, set_private_data/4, - set_private_data_sql/3, get_private_data/3, - get_private_data/2, del_user_private_storage/2, - get_default_privacy_list/2, - get_default_privacy_list_t/1, get_privacy_list_names/2, - get_privacy_list_names_t/1, get_privacy_list_id/3, - get_privacy_list_id_t/2, get_privacy_list_data/3, - get_privacy_list_data_by_id/2, - get_privacy_list_data_t/2, - get_privacy_list_data_by_id_t/1, - set_default_privacy_list/2, - unset_default_privacy_list/2, remove_privacy_list/2, - add_privacy_list/2, set_privacy_list/2, - del_privacy_lists/2, set_vcard/26, get_vcard/2, - escape/1, count_records_where/3, get_roster_version/2, - set_roster_version/2, opt_type/1]). - --include("ejabberd.hrl"). --include("logger.hrl"). --include("ejabberd_sql_pt.hrl"). - -%% Almost a copy of string:join/2. -%% We use this version because string:join/2 is relatively -%% new function (introduced in R12B-0). -join([], _Sep) -> []; -join([H | T], Sep) -> [H, [[Sep, X] || X <- T]]. - -get_db_type() -> generic. - -%% Safe atomic update. -update_t(Table, Fields, Vals, Where) -> - UPairs = lists:zipwith(fun (A, B) -> - <> - end, - Fields, Vals), - case ejabberd_sql:sql_query_t([<<"update ">>, Table, - <<" set ">>, join(UPairs, <<", ">>), - <<" where ">>, Where, <<";">>]) - of - {updated, 1} -> ok; - _ -> - Res = ejabberd_sql:sql_query_t([<<"insert into ">>, Table, - <<"(">>, join(Fields, <<", ">>), - <<") values ('">>, join(Vals, <<"', '">>), - <<"');">>]), - case Res of - {updated,1} -> ok; - _ -> Res - end - end. - -update(LServer, Table, Fields, Vals, Where) -> - UPairs = lists:zipwith(fun (A, B) -> - <> - end, - Fields, Vals), - case ejabberd_sql:sql_query(LServer, - [<<"update ">>, Table, <<" set ">>, - join(UPairs, <<", ">>), <<" where ">>, Where, - <<";">>]) - of - {updated, 1} -> ok; - _ -> - Res = ejabberd_sql:sql_query(LServer, - [<<"insert into ">>, Table, <<"(">>, - join(Fields, <<", ">>), <<") values ('">>, - join(Vals, <<"', '">>), <<"');">>]), - case Res of - {updated,1} -> ok; - _ -> Res - end - end. - -%% F can be either a fun or a list of queries -%% TODO: We should probably move the list of queries transaction -%% wrapper from the ejabberd_sql module to this one (sql_queries) -sql_transaction(LServer, F) -> - ejabberd_sql:sql_transaction(LServer, F). - -get_last(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(seconds)d, @(state)s from last" - " where username=%(LUser)s")). - -set_last_t(LServer, LUser, TimeStamp, Status) -> - ?SQL_UPSERT(LServer, "last", - ["!username=%(LUser)s", - "seconds=%(TimeStamp)d", - "state=%(Status)s"]). - -del_last(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from last where username=%(LUser)s")). - -get_password(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(password)s from users where username=%(LUser)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")). - -set_password_t(LServer, LUser, Password) -> - ejabberd_sql:sql_transaction( - LServer, - fun () -> - ?SQL_UPSERT_T( - "users", - ["!username=%(LUser)s", - "password=%(Password)s"]) - end). - -set_password_scram_t(LServer, LUser, - StoredKey, ServerKey, Salt, IterationCount) -> - ejabberd_sql:sql_transaction( - LServer, - fun () -> - ?SQL_UPSERT_T( - "users", - ["!username=%(LUser)s", - "password=%(StoredKey)s", - "serverkey=%(ServerKey)s", - "salt=%(Salt)s", - "iterationcount=%(IterationCount)d"]) - end). - -add_user(LServer, LUser, Password) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("insert into users(username, password) " - "values (%(LUser)s, %(Password)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)")). - -del_user(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from users where username=%(LUser)s")). - -del_user_return_password(_LServer, LUser, Password) -> - P = - ejabberd_sql:sql_query_t( - ?SQL("select @(password)s from users where username=%(LUser)s")), - ejabberd_sql:sql_query_t( - ?SQL("delete from users" - " where username=%(LUser)s and password=%(Password)s")), - P. - -list_users(LServer) -> - list_users(LServer, []). - -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 = <>, - 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 = <>, - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(count(*))d from users " - "where username like %(SPrefix2)s escape '^'")); -users_number(LServer, []) -> - users_number(LServer). - -add_spool(LUser, LServer, XML) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("insert into spool(username, xml) values (%(LUser)s, %(XML)s)")). - -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). - -del_spool_msg(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from spool where username=%(LUser)s")). - -get_roster(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, " - "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, " - "@(type)s from rosterusers where username=%(LUser)s")). - -get_roster_jid_groups(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(jid)s, @(grp)s from rostergroups where " - "username=%(LUser)s")). - -get_roster_groups(_LServer, LUser, SJID) -> - ejabberd_sql:sql_query_t( - ?SQL("select @(grp)s from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")). - -del_user_roster_t(LServer, LUser) -> - ejabberd_sql:sql_transaction( - LServer, - fun () -> - ejabberd_sql:sql_query_t( - ?SQL("delete from rosterusers where username=%(LUser)s")), - ejabberd_sql:sql_query_t( - ?SQL("delete from rostergroups where username=%(LUser)s")) - end). - -get_roster_by_jid(_LServer, LUser, SJID) -> - ejabberd_sql:sql_query_t( - ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s," - " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s," - " @(type)s from rosterusers" - " where username=%(LUser)s and jid=%(SJID)s")). - -get_rostergroup_by_jid(LServer, LUser, SJID) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(grp)s from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")). - -del_roster(_LServer, LUser, SJID) -> - ejabberd_sql:sql_query_t( - ?SQL("delete from rosterusers" - " where username=%(LUser)s and jid=%(SJID)s")), - ejabberd_sql:sql_query_t( - ?SQL("delete from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")). - -del_roster_sql(Username, SJID) -> - [[<<"delete from rosterusers where " - "username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>], - [<<"delete from rostergroups where " - "username='">>, - Username, <<"' and jid='">>, SJID, <<"';">>]]. - -update_roster(_LServer, LUser, SJID, ItemVals, - ItemGroups) -> - roster_subscribe(ItemVals), - ejabberd_sql:sql_query_t( - ?SQL("delete from rostergroups" - " where username=%(LUser)s and jid=%(SJID)s")), - lists:foreach( - fun(ItemGroup) -> - ejabberd_sql:sql_query_t( - ?SQL("insert into rostergroups(username, jid, grp) " - "values (%(LUser)s, %(SJID)s, %(ItemGroup)s)")) - end, - ItemGroups). - -update_roster_sql({LUser, SJID, Name, SSubscription, SAsk, AskMessage}, - ItemGroups) -> - [?SQL("delete from rosterusers where" - " username=%(LUser)s and jid=%(SJID)s;"), - ?SQL("insert into rosterusers(" - " username, jid, nick," - " subscription, ask, askmessage," - " server, subscribe, type) " - "values (" - "%(LUser)s, " - "%(SJID)s, " - "%(Name)s, " - "%(SSubscription)s, " - "%(SAsk)s, " - "%(AskMessage)s, " - "'N', '', 'item');"), - ?SQL("delete from rostergroups where" - " username=%(LUser)s and jid=%(SJID)s;")] - ++ - [?SQL("insert into rostergroups(username, jid, grp) " - "values (%(LUser)s, %(SJID)s, %(ItemGroup)s);") - || ItemGroup <- ItemGroups]. - -roster_subscribe({LUser, SJID, Name, SSubscription, SAsk, AskMessage}) -> - ?SQL_UPSERT_T( - "rosterusers", - ["!username=%(LUser)s", - "!jid=%(SJID)s", - "nick=%(Name)s", - "subscription=%(SSubscription)s", - "ask=%(SAsk)s", - "askmessage=%(AskMessage)s", - "server='N'", - "subscribe=''", - "type='item'"]). - -get_subscription(LServer, LUser, SJID) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(subscription)s from rosterusers " - "where username=%(LUser)s and jid=%(SJID)s")). - -set_private_data(_LServer, LUser, XMLNS, SData) -> - ?SQL_UPSERT_T( - "private_storage", - ["!username=%(LUser)s", - "!namespace=%(XMLNS)s", - "data=%(SData)s"]). - -set_private_data_sql(LUser, XMLNS, SData) -> - [?SQL("delete from private_storage where" - " username=%(LUser)s and namespace=%(XMLNS)s;"), - ?SQL("insert into private_storage(username, " - "namespace, data) values (" - "%(LUser)s, %(XMLNS)s, %(SData)s);")]. - -get_private_data(LServer, LUser, XMLNS) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(data)s from private_storage" - " where username=%(LUser)s and namespace=%(XMLNS)s")). - -get_private_data(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(namespace)s, @(data)s from private_storage" - " where username=%(LUser)s")). - -del_user_private_storage(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from private_storage" - " where username=%(LUser)s")). - -set_vcard(LServer, LUser, BDay, CTRY, EMail, FN, - Family, Given, LBDay, LCTRY, LEMail, LFN, - LFamily, LGiven, LLocality, LMiddle, LNickname, - LOrgName, LOrgUnit, Locality, Middle, Nickname, - OrgName, OrgUnit, SVCARD, User) -> - ejabberd_sql:sql_transaction( - LServer, - fun() -> - ?SQL_UPSERT(LServer, "vcard", - ["!username=%(LUser)s", - "vcard=%(SVCARD)s"]), - ?SQL_UPSERT(LServer, "vcard_search", - ["username=%(User)s", - "!lusername=%(LUser)s", - "fn=%(FN)s", - "lfn=%(LFN)s", - "family=%(Family)s", - "lfamily=%(LFamily)s", - "given=%(Given)s", - "lgiven=%(LGiven)s", - "middle=%(Middle)s", - "lmiddle=%(LMiddle)s", - "nickname=%(Nickname)s", - "lnickname=%(LNickname)s", - "bday=%(BDay)s", - "lbday=%(LBDay)s", - "ctry=%(CTRY)s", - "lctry=%(LCTRY)s", - "locality=%(Locality)s", - "llocality=%(LLocality)s", - "email=%(EMail)s", - "lemail=%(LEMail)s", - "orgname=%(OrgName)s", - "lorgname=%(LOrgName)s", - "orgunit=%(OrgUnit)s", - "lorgunit=%(LOrgUnit)s"]) - end). - -get_vcard(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(vcard)s from vcard where username=%(LUser)s")). - -get_default_privacy_list(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(name)s from privacy_default_list " - "where username=%(LUser)s")). - -get_default_privacy_list_t(LUser) -> - ejabberd_sql:sql_query_t( - ?SQL("select @(name)s from privacy_default_list " - "where username=%(LUser)s")). - -get_privacy_list_names(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(name)s from privacy_list" - " where username=%(LUser)s")). - -get_privacy_list_names_t(LUser) -> - ejabberd_sql:sql_query_t( - ?SQL("select @(name)s from privacy_list" - " where username=%(LUser)s")). - -get_privacy_list_id(LServer, LUser, Name) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(id)d from privacy_list" - " where username=%(LUser)s and name=%(Name)s")). - -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")). - -get_privacy_list_data(LServer, LUser, Name) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, " - "@(match_iq)b, @(match_message)b, @(match_presence_in)b, " - "@(match_presence_out)b from privacy_list_data " - "where id =" - " (select id from privacy_list" - " where username=%(LUser)s and name=%(Name)s) " - "order by ord")). - -%% Not used? -get_privacy_list_data_t(LUser, Name) -> - ejabberd_sql:sql_query_t( - ?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")). - -get_privacy_list_data_by_id(LServer, ID) -> - 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=%(ID)d order by ord")). - -get_privacy_list_data_by_id_t(ID) -> - ejabberd_sql:sql_query_t( - ?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=%(ID)d order by ord")). - -set_default_privacy_list(LUser, Name) -> - ?SQL_UPSERT_T( - "privacy_default_list", - ["!username=%(LUser)s", - "name=%(Name)s"]). - -unset_default_privacy_list(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from privacy_default_list" - " where username=%(LUser)s")). - -remove_privacy_list(LUser, Name) -> - ejabberd_sql:sql_query_t( - ?SQL("delete from privacy_list where" - " username=%(LUser)s and name=%(Name)s")). - -add_privacy_list(LUser, Name) -> - ejabberd_sql:sql_query_t( - ?SQL("insert into privacy_list(username, name) " - "values (%(LUser)s, %(Name)s)")). - -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). - -del_privacy_lists(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from privacy_list where username=%(LUser)s")), - %US = <>, - %ejabberd_sql:sql_query( - % LServer, - % ?SQL("delete from privacy_list_data where value=%(US)s")), - ejabberd_sql:sql_query( - LServer, - ?SQL("delete from privacy_default_list where username=%(LUser)s")). - -%% Characters to escape -escape($\000) -> <<"\\0">>; -escape($\n) -> <<"\\n">>; -escape($\t) -> <<"\\t">>; -escape($\b) -> <<"\\b">>; -escape($\r) -> <<"\\r">>; -escape($') -> <<"''">>; -escape($") -> <<"\\\"">>; -escape($\\) -> <<"\\\\">>; -escape(C) -> <>. - -%% Count number of records in a table given a where clause -count_records_where(LServer, Table, WhereClause) -> - ejabberd_sql:sql_query(LServer, - [<<"select count(*) from ">>, Table, <<" ">>, - WhereClause, <<";">>]). - -get_roster_version(LServer, LUser) -> - ejabberd_sql:sql_query( - LServer, - ?SQL("select @(version)s from roster_version" - " where username = %(LUser)s")). - -set_roster_version(LUser, Version) -> - ?SQL_UPSERT_T( - "roster_version", - ["!username=%(LUser)s", - "version=%(Version)s"]). - --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]. From 69de1780a0824afbd6d7e52e4df7cd4bfa931974 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 23 May 2017 13:12:48 +0300 Subject: [PATCH 56/91] Introduce --enable-stun and --enable-sip configure options STUN/TURN and SIP is not compiled by default anymore. Use --enable-stun, --enable-sip or --enable-all to enable them. --- configure.ac | 24 +++++++++++++++++++++--- rebar.config | 8 +++++--- src/ejabberd_sip.erl | 16 ++++++++++++++++ src/ejabberd_stun.erl | 16 ++++++++++++++++ src/mod_sip.erl | 17 ++++++++++++++++- src/mod_sip_proxy.erl | 5 +++++ src/mod_sip_registrar.erl | 5 +++++ vars.config.in | 2 ++ 8 files changed, 86 insertions(+), 7 deletions(-) diff --git a/configure.ac b/configure.ac index f5eee8cbf..aa92cf38e 100644 --- a/configure.ac +++ b/configure.ac @@ -101,10 +101,10 @@ AC_ARG_ENABLE(mssql, esac],[db_type=generic]) AC_ARG_ENABLE(all, -[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])], +[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])], [case "${enableval}" in - yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true debug=true tools=true ;; - no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false debug=false tools=false ;; + yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true stun=true sip=true debug=true tools=true ;; + no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false stun=false sip=false debug=false tools=false ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;; esac],[]) @@ -212,6 +212,22 @@ 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(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,6 +269,8 @@ 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) diff --git a/rebar.config b/rebar.config index acd737681..c67a86f67 100644 --- a/rebar.config +++ b/rebar.config @@ -25,12 +25,12 @@ {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"}}}, {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"}}}, + {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}}}, + {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}}}, {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", "31e035b"}}}, {if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", @@ -84,6 +84,8 @@ {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'}}, @@ -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", []}}, diff --git a/src/ejabberd_sip.erl b/src/ejabberd_sip.erl index d7404a30e..2c98aec16 100644 --- a/src/ejabberd_sip.erl +++ b/src/ejabberd_sip.erl @@ -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]). @@ -59,3 +74,4 @@ listen_opt_type(_) -> %%%=================================================================== %%% Internal functions %%%=================================================================== +-endif. diff --git a/src/ejabberd_stun.erl b/src/ejabberd_stun.erl index 3611edba7..35a04ce45 100644 --- a/src/ejabberd_stun.erl +++ b/src/ejabberd_stun.erl @@ -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]). @@ -138,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. diff --git a/src/mod_sip.erl b/src/mod_sip.erl index eb5cbe545..7c3e60917 100644 --- a/src/mod_sip.erl +++ b/src/mod_sip.erl @@ -25,6 +25,20 @@ -module(mod_sip). -protocol({rfc, 3261}). +-include("logger.hrl"). + +-ifndef(SIP). +-export([start/2, stop/1, depends/2, mod_opt_type/1]). +start(_, _) -> + ?CRITICAL_MSG("ejabberd is not compiled with SIP support", []), + {error, sip_not_compiled}. +stop(_) -> + ok. +depends(_, _) -> + []. +mod_opt_type(_) -> + []. +-else. -behaviour(gen_mod). -behaviour(esip). @@ -37,7 +51,6 @@ locate/1, mod_opt_type/1, depends/2]). -include("ejabberd.hrl"). --include("logger.hrl"). -include_lib("esip/include/esip.hrl"). %%%=================================================================== @@ -350,3 +363,5 @@ mod_opt_type(via) -> mod_opt_type(_) -> [always_record_route, flow_timeout_tcp, flow_timeout_udp, record_route, routes, via]. + +-endif. diff --git a/src/mod_sip_proxy.erl b/src/mod_sip_proxy.erl index 5e0cfa25e..19a02e8e4 100644 --- a/src/mod_sip_proxy.erl +++ b/src/mod_sip_proxy.erl @@ -24,6 +24,9 @@ %%%------------------------------------------------------------------- -module(mod_sip_proxy). +-ifndef(SIP). +-export([]). +-else. -define(GEN_FSM, p1_fsm). -behaviour(?GEN_FSM). @@ -424,3 +427,5 @@ safe_nameprep(S) -> error -> S; S1 -> S1 end. + +-endif. diff --git a/src/mod_sip_registrar.erl b/src/mod_sip_registrar.erl index 1bc819c08..0e131eee6 100644 --- a/src/mod_sip_registrar.erl +++ b/src/mod_sip_registrar.erl @@ -24,6 +24,9 @@ %%%------------------------------------------------------------------- -module(mod_sip_registrar). +-ifndef(SIP). +-export([]). +-else. -ifndef(GEN_SERVER). -define(GEN_SERVER, gen_server). -endif. @@ -580,3 +583,5 @@ process_ping(SIPSocket) -> (_, Acc) -> Acc end, ErrResponse, Sessions). + +-endif. diff --git a/vars.config.in b/vars.config.in index 5eca4889a..94584dad4 100644 --- a/vars.config.in +++ b/vars.config.in @@ -40,6 +40,8 @@ {redis, @redis@}. {elixir, @elixir@}. {iconv, @iconv@}. +{stun, @stun@}. +{sip, @sip@}. %% Version {vsn, "@PACKAGE_VERSION@"}. From 5802062746e44a881c998b8a82676e8dbbc99c68 Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Wed, 24 May 2017 17:16:16 +0200 Subject: [PATCH 57/91] Cosmetic change: Fix indentation errors --- src/ejabberd_s2s_in.erl | 12 ++++++------ src/ejabberd_sm.erl | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index d8e124b52..76a844f87 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -164,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. @@ -186,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}; @@ -310,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. diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index ea19f832f..344febb5d 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -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; From 50327a0cfc74cc8d17daf0eb79c4e4f87ba3e5f6 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 25 May 2017 13:46:17 +0300 Subject: [PATCH 58/91] Fix case clause Fixes #1746 --- src/ejabberd_s2s.erl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index d972f5866..4b74b8c4a 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -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]), From 0982a9bc3c8701f4cce00c67ac5a9223dddfd638 Mon Sep 17 00:00:00 2001 From: Badlop Date: Mon, 29 May 2017 12:46:59 +0200 Subject: [PATCH 59/91] Parse correctly presence_broadcast option in change_room_option command --- src/mod_muc_admin.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index ad387b19b..614cdda95 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -893,6 +893,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, @@ -933,6 +934,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}; From 2d8ce266bdf44ac1ae58484d3616e3c95549cd76 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 30 May 2017 15:02:53 +0200 Subject: [PATCH 60/91] Add license files into lib and deps directories --- Makefile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.in b/Makefile.in index a0b4553dc..56429d68f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 From 5d3870faa3637b22a60493beea4af5b6695cccb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Wed, 31 May 2017 11:30:34 +0200 Subject: [PATCH 61/91] Add --enable-system-deps configure option --- configure.ac | 9 +++++++++ rebar.config.script | 6 ++++-- vars.config.in | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index aa92cf38e..884db5d4e 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,14 @@ 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 @@ -274,6 +282,7 @@ 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) diff --git a/rebar.config.script b/rebar.config.script index d2ee31213..8cef5791a 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -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 @@ -336,7 +338,7 @@ Rules = [ ProcessFloatingDeps, [], []}, {[deps], IsRebar3, Rebar3DepsFilter, []}, - {[deps], os:getenv("USE_GLOBAL_DEPS") /= false, + {[deps], SystemDeps /= false, GlobalDepsFilter, []} ], diff --git a/vars.config.in b/vars.config.in index 94584dad4..469711182 100644 --- a/vars.config.in +++ b/vars.config.in @@ -60,6 +60,7 @@ {docdir, "{{release_dir}}/doc"}. {latest_deps, @latest_deps@}. +{system_deps, @system_deps@}. {ldflags, "@LDFLAGS@"}. {cflags, "@CFLAGS@"}. From cbe6553baa6fea9df2c8ba711247b2bb99413f2d Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 31 May 2017 18:11:45 +0200 Subject: [PATCH 62/91] Refactor ejabberdctl --- ejabberdctl.template | 406 ++++++++++++++++--------------------------- 1 file changed, 147 insertions(+), 259 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 64fed558b..8053ea2df 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -10,159 +10,95 @@ 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="as_install_user" - fi - done - if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then +EXEC_CMD="false" +if [ -n "$INSTALLUSER" ] ; then + if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then EXEC_CMD="as_current_user" - fi - if [ "$EXEC_CMD" = "false" ] ; then - echo "This command can only be run by root or the user $INSTALLUSER" >&2 - exit 4 + else + id -Gn | grep -q wheel && EXEC_CMD="as_install_user" fi else 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 -# run command either directly or via su $INSTALLUSER -exec_cmd() -{ - if [ "EXEC_CMD" = as_install_user ]; then - su -c '"$0" $@"' "INSTALLUSER" -- "$@" - else - "$@" - fi -} - +# set backward compatibility on command line parameters +set -- $(echo "$*" | sed -e \ + "s/--node/-n/;s/--spool/-s/;s/--logs/-l/;\ + s/--config/-f/;s/--ctl-config/-c/;s/--config-dir/-d/;\ + s/--no-timeout/-t/") # parse command line parameters -next=init -for arg; do - # Empty argument list as it is already saved in the for buffer - if [ "$next" = init ]; then - next= - set -- - fi - case $next in - node) ERLANG_NODE_ARG=$arg; next=;; - config-dir) ETC_DIR=$arg; next=;; - config) EJABBERD_CONFIG_PATH=$arg; next=;; - ctl-config) EJABBERDCTL_CONFIG_PATH=$arg; next=;; - logs) LOGS_DIR=$arg; next=;; - spool) SPOOL_DIR=$arg; next=;; - "") - case $arg in - --) next=raw;; - --no-timeout) EJABBERD_NO_TIMEOUT="--no-timeout" ;; - --node) next=node;; - --config-dir) next=config-dir;; - --config) next=config;; - --ctl-config) next=ctl-config;; - --logs) next=logs;; - --spool) next=spool;; - *) set -- "$@" "$arg";; # unknown option, keep it. - esac;; - raw) # we are after --, keep options as it is. - set -- "$@" "$arg";; +while getopts n:s:l:f:c:d:tx opt; do + case $opt in + n) ERLANG_NODE_ARG=$OPTARG;; + s) SPOOL_DIR=$OPTARG;; + l) LOGS_DIR=$OPTARG;; + f) EJABBERD_CONFIG_PATH=$OPTARG;; + c) EJABBERDCTL_CONFIG_PATH=$OPTARG;; + d) ETC_DIR=$OPTARG;; + t) NO_TIMEOUT="--no-timeout";; esac done +# keep extra command line parameters for ejabberd +shift $((OPTIND-1)) -# 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 +: ${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 +# define ejabberd parameters +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") [ -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" +[ -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" +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 @@ -174,89 +110,26 @@ export CONTRIB_MODULES_PATH export CONTRIB_MODULES_CONF_DIR export ERL_LIBS -# TODO: Too much copy-and-paste below, factorize! -# start server -start() +# run command either directly or via su $INSTALLUSER +exec_cmd() { - check_start - exec_cmd $ERL \ - $NAME $ERLANG_NODE \ - -noinput -detached \ - $MNESIA_OPTS \ - $KERNEL_OPTS \ - $EJABBERD_OPTS \ - -s ejabberd \ - $ERLANG_OPTS \ - "$@" -} - -# attach to server -debug() -{ - debugwarning - NID=$(uid debug) - exec_cmd $ERL $NAME $NID \ - -remsh $ERLANG_NODE \ - -hidden \ - $KERNEL_OPTS \ - $ERLANG_OPTS \ - "$@" -} - -# attach to server using Elixir -iexdebug() -{ - debugwarning - # Elixir shell is hidden as default - NID=$(uid debug) - exec_cmd $IEX $IEXNAME $NID \ - -remsh "$ERLANG_NODE" \ - --erl "$KERNEL_OPTS" \ - --erl "$ERLANG_OPTS" \ - --erl "$@" -} - -# start interactive server -live() -{ - livewarning - exec_cmd $ERL $NAME $ERLANG_NODE \ - $MNESIA_OPTS \ - $KERNEL_OPTS \ - $EJABBERD_OPTS \ - -s ejabberd \ - $ERLANG_OPTS \ - "$@" -} - -# start interactive server with Elixir -iexlive() -{ - livewarning - echo $@ - exec_cmd $IEX $IEXNAME $ERLANG_NODE \ - --erl "-mnesia dir \"$SPOOL_DIR\"" \ - --erl "$KERNEL_OPTS" \ - --erl "$EJABBERD_OPTS" \ - --app ejabberd \ - --erl "$ERLANG_OPTS" \ - --erl "$@" -} - -# start server in the foreground -foreground() -{ - check_start - exec_cmd $ERL $NAME $ERLANG_NODE \ - -noinput \ - $MNESIA_OPTS \ - $KERNEL_OPTS \ - $EJABBERD_OPTS \ - -s ejabberd \ - $ERLANG_OPTS \ - "$@" + case $EXEC_CMD in + as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;; + as_current_user) "$@" ;; + esac +} +exec_erl() +{ + NODE=$1; shift + exec_cmd $ERL $NAME $NODE $ERLANG_OPTS "$@" +} +exec_iex() +{ + NODE=$1; shift + exec_cmd $IEX -$NAME $NODE --erl "$ERLANG_OPTS" "$@" } +# usage debugwarning() { if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then @@ -283,7 +156,6 @@ debugwarning() livewarning() { - check_start if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then echo "--------------------------------------------------------------------" echo "" @@ -305,32 +177,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 "" @@ -352,28 +198,27 @@ help() echo "" } -# common control function -ctl() +# generic erlang node ping feature +ping() { - NID=$(uid ctl) - exec_cmd $ERL $NAME $NID \ - -noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \ - -extra $ERLANG_NODE $EJABBERD_NO_TIMEOUT \ - "$@" - result=$? - case $result in - 2) help;; - 3) help;; - *) :;; - esac - return $result + PEER=${1:-$ERLANG_NODE} + if [ "$PEER" = "${PEER%.*}" ] ; then + PING_NAME="-sname" + PING_NODE=$(hostname -s) + else + PING_NAME="-name" + PING_NODE=$(hostname) + fi + exec_cmd $ERL $PING_NAME $(uid ping $PING_NODE) $ERLANG_OPTS \ + -noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \ + -s erlang halt -output text } +# dynamic node name helper uid() { uuid=$(uuidgen 2>/dev/null) - [ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && \ - uuid=$(cat /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} @@ -388,6 +233,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%@*} " && { @@ -404,11 +250,16 @@ check_start() "$EPMD" -kill >/dev/null } } + } || { + 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 @@ -417,27 +268,64 @@ wait_for_status() while [ $status -ne $1 ] ; do sleep $3 timeout=`expr $timeout - 1` - [ $timeout -eq 0 ] && { + if [ $timeout -eq 0 ] ; then status=$1 - } || { + else ctl status > /dev/null status=$? - } + fi done - [ $timeout -eq 0 ] && return 1 || return 0 + [ $timeout -gt 0 ] } -# main handler -case $@ in - 'start') start;; - 'debug') debug;; - 'iexdebug') iexdebug;; - 'live') live;; - 'iexlive') iexlive;; - 'foreground') foreground;; - 'ping'*) shift; ping "$@";; - '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 "$@";; +# 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) + ping $2 + ;; + 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 From 92003fa4dc99b2de024860dd0a89e5b1d6f9c87d Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 1 Jun 2017 08:04:11 +0200 Subject: [PATCH 63/91] Minor ejabberdctl improvements --- ejabberdctl.template | 47 +++++++++++++------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 8053ea2df..eafca9126 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -59,6 +59,7 @@ shift $((OPTIND-1)) : ${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} @@ -75,25 +76,17 @@ if [ "$INET_DIST_INTERFACE" != "" ] ; then ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface \"$INET_DIST_INTERFACE2\"" fi fi -if [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] ; then - NAME="-sname" -else - NAME="-name" -fi 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 parameters -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") -[ -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" +EJABBERD_OPTS="$EJABBERD_OPTS\ + $(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ + $(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ + $(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ + $(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/' "$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 @@ -121,12 +114,12 @@ exec_cmd() exec_erl() { NODE=$1; shift - exec_cmd $ERL $NAME $NODE $ERLANG_OPTS "$@" + exec_cmd $ERL ${S:--}name $NODE $ERLANG_OPTS "$@" } exec_iex() { NODE=$1; shift - exec_cmd $IEX -$NAME $NODE --erl "$ERLANG_OPTS" "$@" + exec_cmd $IEX ${S:--}name $NODE --erl "$ERLANG_OPTS" "$@" } # usage @@ -198,22 +191,6 @@ help() echo "" } -# generic erlang node ping feature -ping() -{ - PEER=${1:-$ERLANG_NODE} - if [ "$PEER" = "${PEER%.*}" ] ; then - PING_NAME="-sname" - PING_NODE=$(hostname -s) - else - PING_NAME="-name" - PING_NODE=$(hostname) - fi - exec_cmd $ERL $PING_NAME $(uid ping $PING_NODE) $ERLANG_OPTS \ - -noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \ - -s erlang halt -output text -} - # dynamic node name helper uid() { @@ -310,7 +287,11 @@ case $1 in exec_iex $ERLANG_NODE --erl "$EJABBERD_OPTS" --app ejabberd ;; ping) - ping $2 + 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 From 3201f8e513c260e19ff3eb1a0855d679d7d16f8f Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 1 Jun 2017 11:48:11 +0200 Subject: [PATCH 64/91] Improve ejabberdctl parameters parsing --- ejabberdctl.template | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index eafca9126..5454757d3 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -31,25 +31,22 @@ if [ "$EXEC_CMD" = "false" ] ; then exit 7 fi -# set backward compatibility on command line parameters -set -- $(echo "$*" | sed -e \ - "s/--node/-n/;s/--spool/-s/;s/--logs/-l/;\ - s/--config/-f/;s/--ctl-config/-c/;s/--config-dir/-d/;\ - s/--no-timeout/-t/") # parse command line parameters -while getopts n:s:l:f:c:d:tx opt; do - case $opt in - n) ERLANG_NODE_ARG=$OPTARG;; - s) SPOOL_DIR=$OPTARG;; - l) LOGS_DIR=$OPTARG;; - f) EJABBERD_CONFIG_PATH=$OPTARG;; - c) EJABBERDCTL_CONFIG_PATH=$OPTARG;; - d) ETC_DIR=$OPTARG;; - t) NO_TIMEOUT="--no-timeout";; +ARGS=`getopt -o n:s:l:f:c:d:t --long node:,spool:,logs:,config:,ctl-config:,config-dir:,no-timeout -n ejabberdctl -- "$@"` +eval set -- "$ARGS"; +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";; + --) shift; break;; esac + shift done -# keep extra command line parameters for ejabberd -shift $((OPTIND-1)) # define ejabberd variables if not already defined from the command line : ${ETC_DIR:={{sysconfdir}}/ejabberd} From a576f3a6d1cc3e881df5d4e864dd7959435a9e38 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 1 Jun 2017 12:10:06 +0200 Subject: [PATCH 65/91] Remove use of getopt to support simpler shells --- ejabberdctl.template | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 5454757d3..3fb302187 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -32,8 +32,6 @@ if [ "$EXEC_CMD" = "false" ] ; then fi # parse command line parameters -ARGS=`getopt -o n:s:l:f:c:d:t --long node:,spool:,logs:,config:,ctl-config:,config-dir:,no-timeout -n ejabberdctl -- "$@"` -eval set -- "$ARGS"; for arg; do case $1 in -n|--node) ERLANG_NODE_ARG=$2; shift;; @@ -43,7 +41,8 @@ for arg; do -c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift;; -d|--config-dir) ETC_DIR=$2; shift;; -t|--no-timeout) NO_TIMEOUT="--no-timeout";; - --) shift; break;; + --) :;; + *) break;; esac shift done From 5081a180fa761f6c8864be0906e39d0ba97d2449 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 1 Jun 2017 17:44:23 +0200 Subject: [PATCH 66/91] Create spool directory at start if not exists --- ejabberdctl.template | 1 + 1 file changed, 1 insertion(+) diff --git a/ejabberdctl.template b/ejabberdctl.template index 3fb302187..d03de6640 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -224,6 +224,7 @@ check_start() } } } || { + [ -d "$SPOOL_DIR" ] || mkdir -p "$SPOOL_DIR" cd "$SPOOL_DIR" || { echo "ERROR: ejabberd can not access directory $SPOOL_DIR" exit 6 From de10a7a8ce692925feed2b839dd7f05c51d798f6 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Thu, 1 Jun 2017 17:48:44 +0200 Subject: [PATCH 67/91] Respect INSTALLUSER when creating spool directory --- ejabberdctl.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index d03de6640..303c8c044 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -224,7 +224,7 @@ check_start() } } } || { - [ -d "$SPOOL_DIR" ] || mkdir -p "$SPOOL_DIR" + [ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR" cd "$SPOOL_DIR" || { echo "ERROR: ejabberd can not access directory $SPOOL_DIR" exit 6 From f6767ed0612024a82341b7902d096be43b9b9df6 Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 1 Jun 2017 19:27:28 +0200 Subject: [PATCH 68/91] Fix rooms list in WebAdmin (#1753) --- src/mod_muc_admin.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 614cdda95..8726856b5 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -398,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. @@ -482,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, @@ -500,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) -> @@ -736,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) From 12733bd21b636737c2c2971c73e183c4c5ee2a13 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 2 Jun 2017 17:56:31 +0200 Subject: [PATCH 69/91] Fix EJABBERD_OPTS --- ejabberdctl.template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 303c8c044..a4e63b072 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -78,10 +78,10 @@ ERL_INETRC="$ETC_DIR"/inetrc # define ejabberd parameters EJABBERD_OPTS="$EJABBERD_OPTS\ - $(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ - $(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ - $(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ - $(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")" +$(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ +$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ +$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ +$(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")" [ -n "$EJABBERD_OPTS" ] && EJABBERD_OPTS="-ejabberd $EJABBERD_OPTS" EJABBERD_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS $EJABBERD_OPTS -s ejabberd" From a98685e0bb2cf269eec142589de621bd09c47707 Mon Sep 17 00:00:00 2001 From: Rene Klacan Date: Sun, 4 Jun 2017 03:01:51 +0200 Subject: [PATCH 70/91] Fix version to be SemVer compatible --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index a13d6b782..86c5d750b 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do def project do [app: :ejabberd, - version: "17.03.0", + version: "17.3.0", description: description, elixir: "~> 1.3", elixirc_paths: ["lib"], From 04fd5567a7afcfad51c14ddab69a96c8a9a22fea Mon Sep 17 00:00:00 2001 From: Rene Klacan Date: Sun, 4 Jun 2017 03:00:57 +0200 Subject: [PATCH 71/91] Fix mix warnings --- mix.exs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mix.exs b/mix.exs index 86c5d750b..6ba8a3146 100644 --- a/mix.exs +++ b/mix.exs @@ -4,17 +4,17 @@ defmodule Ejabberd.Mixfile do def project do [app: :ejabberd, version: "17.3.0", - description: description, + description: description(), elixir: "~> 1.3", 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 @@ -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 @@ -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()) From 2a73068aacd89c4753e33f2571b6962b8cabcd2f Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 6 Jun 2017 15:41:48 +0200 Subject: [PATCH 72/91] Add missing space separator on EJABBERD_OPTS --- ejabberdctl.template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index a4e63b072..7a225e831 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -78,10 +78,10 @@ ERL_INETRC="$ETC_DIR"/inetrc # define ejabberd parameters EJABBERD_OPTS="$EJABBERD_OPTS\ -$(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ -$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ -$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")\ -$(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/' "$EJABBERD_CONFIG_PATH")" +$(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" From f773edcb98e5e93196ce70caa4422c731a1fbdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Wed, 7 Jun 2017 16:06:28 +0200 Subject: [PATCH 73/91] Override version of subdeps with version from main rebar.config --- plugins/override_deps_versions.erl | 126 +++++++++++++++++++++++++++++ rebar.config | 2 +- rebar.config.script | 3 +- 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 plugins/override_deps_versions.erl diff --git a/plugins/override_deps_versions.erl b/plugins/override_deps_versions.erl new file mode 100644 index 000000000..d9820f711 --- /dev/null +++ b/plugins/override_deps_versions.erl @@ -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(<>, Name) -> + <>. + +replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) -> + <> = 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), + <>. + + +%% 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>>. diff --git a/rebar.config b/rebar.config index c67a86f67..e790310ac 100644 --- a/rebar.config +++ b/rebar.config @@ -102,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} ]}}. diff --git a/rebar.config.script b/rebar.config.script index 8cef5791a..f31ceaae2 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -342,7 +342,8 @@ Rules = [ 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]), From e3c801f1f5681fe4b0b592abeba66158d12aaeeb Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 7 Jun 2017 16:18:41 +0200 Subject: [PATCH 74/91] Update dependencies --- rebar.config | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rebar.config b/rebar.config index e790310ac..f7f1e62b8 100644 --- a/rebar.config +++ b/rebar.config @@ -19,12 +19,12 @@ %%%---------------------------------------------------------------------- {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", "6f762a59"}}, + {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.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"}}, + {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.22"}}}, + {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.10"}}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.9"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}}, From baf574d6c4cee621870531285d4d28a640849ae2 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 7 Jun 2017 16:56:00 +0200 Subject: [PATCH 75/91] Update lager p1_mysql and p1_pgsql dependencies --- rebar.config | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rebar.config b/rebar.config index f7f1e62b8..249893f65 100644 --- a/rebar.config +++ b/rebar.config @@ -18,7 +18,7 @@ %%% %%%---------------------------------------------------------------------- -{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}}, +{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.4.2"}}}, {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {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.11"}}}, @@ -32,9 +32,9 @@ {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}}}, {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}}}, {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", From fbead19c88da6a8353c2237d0f95e316a085928b Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Wed, 7 Jun 2017 17:09:06 +0200 Subject: [PATCH 76/91] Update elixir --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 249893f65..96560a32a 100644 --- a/rebar.config +++ b/rebar.config @@ -45,7 +45,7 @@ {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"}}}}, From 0973a8d6c1c9ecd041ccbdfa40e0d15ee86f8c8f Mon Sep 17 00:00:00 2001 From: Ivy Rogatko Date: Wed, 7 Jun 2017 21:15:17 -0700 Subject: [PATCH 77/91] fixing iex bug with proper --name arguement --- ejabberdctl.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 7a225e831..8976414d5 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -115,7 +115,7 @@ exec_erl() exec_iex() { NODE=$1; shift - exec_cmd $IEX ${S:--}name $NODE --erl "$ERLANG_OPTS" "$@" + exec_cmd $IEX ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@" } # usage From ee8bbccb2a2dcc8206b894f6ac693b0183641c2b Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 8 Jun 2017 19:54:34 +0200 Subject: [PATCH 78/91] Fix and document push_roster_all command --- src/mod_admin_extra.erl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index fa681f87a..455871a78 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -528,6 +528,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}}, @@ -1280,7 +1284,7 @@ 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). From 444c385f2382151252da518874501fa47513f3b9 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Fri, 9 Jun 2017 10:34:35 +0200 Subject: [PATCH 79/91] Update dependencies --- rebar.config | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rebar.config b/rebar.config index 96560a32a..d2ba6794e 100644 --- a/rebar.config +++ b/rebar.config @@ -21,11 +21,11 @@ {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.11"}}}, - {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}}, - {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.22"}}}, - {xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.10"}}}, - {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.9"}}}, + {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"}}}, @@ -50,7 +50,7 @@ {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", From b25b5c2f987f365a666165802214f42d1b6e230b Mon Sep 17 00:00:00 2001 From: Badlop Date: Fri, 9 Jun 2017 12:02:49 +0200 Subject: [PATCH 80/91] Improve export2sql explanation; remove obsolete and duplicated command --- src/ejabberd_admin.erl | 1 + src/mod_admin_extra.erl | 28 +--------------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index d56ca0be7..2b17ebc08 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -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"], diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index 455871a78..d8f2167fb 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -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, @@ -701,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 %%% From 62806607bf52cf57a123885ee18fd33a39ac1894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 9 Jun 2017 12:10:40 +0200 Subject: [PATCH 81/91] Add missing , --- src/ejabberd_admin.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 2b17ebc08..8b4af2857 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -303,7 +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." + 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"], From 115cb23bd8a3b68a6188dbe6dab0c2a83704adfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 9 Jun 2017 12:59:47 +0200 Subject: [PATCH 82/91] Fix elixir tests on elixir 1.4 --- test/elixir_SUITE.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/elixir_SUITE.erl b/test/elixir_SUITE.erl index 7ef4f2cdb..f4612fa6d 100644 --- a/test/elixir_SUITE.erl +++ b/test/elixir_SUITE.erl @@ -85,7 +85,11 @@ undefined_function(Module, Func, Args) -> run_elixir_test(Func) -> %% Elixir tests can be tagged as follow to be ignored (place before test start) %% @tag pending: true - 'Elixir.ExUnit':start([{exclude, [{pending, true}]}, {formatters, ['Elixir.ExUnit.CLIFormatter', 'Elixir.ExUnit.CTFormatter']}]), + 'Elixir.ExUnit':start([{exclude, [{pending, true}]}, + {formatters, + ['Elixir.ExUnit.CLIFormatter', + 'Elixir.ExUnit.CTFormatter']}, + {autorun, false}]), filelib:fold_files(test_dir(), ".*mock\.exs\$", true, fun (File, N) -> @@ -95,6 +99,7 @@ run_elixir_test(Func) -> 'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), atom_to_list(Func)))), %% I did not use map syntax, so that this file can still be build under R16 + 'Elixir.ExUnit.Server':cases_loaded(), ResultMap = 'Elixir.ExUnit':run(), case maps:find(failures, ResultMap) of {ok, 0} -> From 7b5895c90d4c94a52254a0b6c22cb29c31a5742b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 9 Jun 2017 13:57:26 +0200 Subject: [PATCH 83/91] Allow api access on both ipv4 and 6 loopback addresses This should fix issue #1769 --- ejabberd.yml.example | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 638588998..87b0b81a4 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -487,6 +487,7 @@ acl: loopback: ip: - "127.0.0.0/8" + - "::1/128" ## ## Bad XMPP servers @@ -589,14 +590,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" From 8c1568ff93a468ab4cc99dd7fb026648bc71e8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Fri, 9 Jun 2017 14:38:34 +0200 Subject: [PATCH 84/91] Add more ipv6 loopback addresses --- ejabberd.yml.example | 1 + 1 file changed, 1 insertion(+) diff --git a/ejabberd.yml.example b/ejabberd.yml.example index 87b0b81a4..c55830563 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -488,6 +488,7 @@ acl: ip: - "127.0.0.0/8" - "::1/128" + - "::FFFF:127.0.0.1/128" ## ## Bad XMPP servers From 63b6e0d3811cc1ec5b682a07d47ef14685661cbd Mon Sep 17 00:00:00 2001 From: Badlop Date: Fri, 9 Jun 2017 19:18:47 +0200 Subject: [PATCH 85/91] Switch access rule delete_old_users with protect_old_users (#1772) --- src/mod_admin_extra.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index d8f2167fb..df2206d8d 100644 --- a/src/mod_admin_extra.erl +++ b/src/mod_admin_extra.erl @@ -174,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], @@ -817,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} -> From d6f4c99243aeebd7d57ebf366afd46d310e5c674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Mon, 12 Jun 2017 19:10:00 +0200 Subject: [PATCH 86/91] Remove luerl from floating_deps --- rebar.config | 1 - 1 file changed, 1 deletion(-) diff --git a/rebar.config b/rebar.config index d2ba6794e..ce5b77a70 100644 --- a/rebar.config +++ b/rebar.config @@ -64,7 +64,6 @@ stringprep, fast_xml, esip, - luerl, stun, fast_yaml, xmpp, From d8f05acb679372546ed1aa02c971b752f65f5390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Mon, 12 Jun 2017 19:23:23 +0200 Subject: [PATCH 87/91] Add script for managing updates to deps --- tools/update-deps-releases.pl | 389 ++++++++++++++++++++++++++++++++++ 1 file changed, 389 insertions(+) create mode 100755 tools/update-deps-releases.pl diff --git a/tools/update-deps-releases.pl b/tools/update-deps-releases.pl new file mode 100755 index 000000000..586ae0fe4 --- /dev/null +++ b/tools/update-deps-releases.pl @@ -0,0 +1,389 @@ +#!/usr/bin/perl + +use v5.10; +use strict; +use warnings; + +use File::Slurp qw(slurp write_file); +use File::stat; +use File::Touch; +use Data::Dumper qw(Dumper); +use Carp; +use Term::ANSIColor; +use Term::ReadKey; +use List::Util qw(first unpairs); +use Clone qw(clone); + +sub get_deps { + my ($config, %fdeps) = @_; + + my %deps; + + return { } unless $config =~ /\{\s*deps\s*,\s*\[(.*?)\]/s; + my $sdeps = $1; + + while ($sdeps =~ /\{\s*(\w+)\s*,\s*".*?"\s*,\s*\{\s*git\s*,\s*"(.*?)"\s*,\s*(?:{\s*tag\s*,\s*"(.*?)"|"(.*?)" )/sg) { + next unless not %fdeps or exists $fdeps{$1}; + $deps{$1} = { repo => $2, commit => $3 || $4 }; + } + return \%deps; +} +my (%info_updates, %top_deps_updates, %sub_deps_updates, @operations); + +sub top_deps { + state %deps; + if (not %deps) { + my $config = slurp "rebar.config"; + croak "Unable to extract floating_deps" unless $config =~ /\{floating_deps, \[(.*?)\]/s; + + my $fdeps = $1; + $fdeps =~ s/\s*//g; + my %fdeps = map { $_ => 1 } split /,/, $fdeps; + %deps = %{get_deps($config, %fdeps)}; + } + return {%deps, %top_deps_updates}; +} + +sub update_deps_repos { + my $deps = top_deps(); + mkdir(".deps-update") unless -d ".deps-update"; + for my $dep (keys %{$deps}) { + my $dd = ".deps-update/$dep"; + if (not -d $dd) { + say "Downloading $dep..."; + my $repo = $deps->{$dep}->{repo}; + $repo =~ s/^https?/git/; + system("git", "-C", ".deps-update", "clone", $repo); + } elsif (time() - stat($dd)->mtime > 24 * 60 * 60) { + say "Updating $dep..."; + system("git", "-C", $dd, "fetch"); + touch($dd) + } + } +} + +sub sub_deps { + state %sub_deps; + if (not %sub_deps) { + my $deps = top_deps(); + for my $dep (keys %{$deps}) { + my $rc = ".deps-update/$dep/rebar.config"; + $sub_deps{$dep} = { }; + next unless -f $rc; + $sub_deps{$dep} = get_deps(scalar(slurp($rc))); + } + } + return {%sub_deps, %sub_deps_updates}; +} + +sub rev_deps_helper { + my ($rev_deps, $dep) = @_; + if (not exists $rev_deps->{$dep}->{indirect}) { + my %deps = %{$rev_deps->{$dep}->{direct} || {}}; + for (keys %{$rev_deps->{$dep}->{direct}}) { + %deps = (%deps, %{rev_deps_helper($rev_deps, $_)}); + } + $rev_deps->{$dep}->{indirect} = \%deps; + } + return $rev_deps->{$dep}->{indirect}; +} + +sub rev_deps { + state %rev_deps; + if (not %rev_deps) { + my $sub_deps = sub_deps(); + for my $dep (keys %$sub_deps) { + $rev_deps{$_}->{direct}->{$dep} = 1 for keys %{$sub_deps->{$dep}}; + } + for my $dep (keys %$sub_deps) { + $rev_deps{$dep}->{indirect} = rev_deps_helper(\%rev_deps, $dep); + } + } + return \%rev_deps; +} + +sub update_changelog { + my ($dep, $version, @reasons) = @_; + my $cl = ".deps-update/$dep/CHANGELOG.md"; + return if not -f $cl; + my $reason = join "\n", map {"* $_"} @reasons; + my $content = slurp($cl); + if (not $content =~ /^# Version $version/) { + $content = "# Version $version\n\n$reason\n\n$content" + } else { + $content =~ s/(# Version $version\n\n)/$1$reason\n/; + } + write_file($cl, $content); +} + +sub update_app_src { + my ($dep, $version) = @_; + my $app = ".deps-update/$dep/src/$dep.app.src"; + return if not -f $app; + my $content = slurp($app); + $content =~ s/({\s*vsn\s*,\s*)".*"/$1"$version"/; + write_file($app, $content); +} + +sub update_deps_versions { + my ($config_path, %deps) = @_; + my $config = slurp $config_path; + + for (keys %deps) { + $config =~ s/(\{\s*$_\s*,\s*".*?"\s*,\s*\{\s*git\s*,\s*".*?"\s*,\s*)(?:{\s*tag\s*,\s*"(.*?)"\s*}|"(.*?)" )/$1\{tag, "$deps{$_}"}/s; + } + + write_file($config_path, $config); +} + +sub cmp_ver { + my @a = split /(\d+)/, $a; + my @b = split /(\d+)/, $b; + my $is_num = 1; + + return - 1 if $#a == 0; + return 1 if $#b == 0; + + while (1) { + my $ap = shift @a; + my $bp = shift @b; + $is_num = 1 - $is_num; + + if (defined $ap) { + if (defined $bp) { + if ($is_num) { + next if $ap == $bp; + return 1 if $ap > $bp; + return - 1; + } else { + next if $ap eq $bp; + return 1 if $ap gt $bp; + return - 1; + } + } else { + return 1; + } + } elsif (defined $bp) { + return - 1; + } else { + return 0; + } + } +} + +sub deps_git_info { + state %info; + if (not %info) { + my $deps = top_deps(); + for my $dep (keys %{$deps}) { + my $dir = ".deps-update/$dep"; + my @tags = `git -C "$dir" tag`; + chomp(@tags); + @tags = sort cmp_ver @tags; + my $last_tag = $tags[$#tags]; + my @new = `git -C $dir log --oneline $last_tag..origin/master`; + my $new_tag = $last_tag; + $new_tag =~ s/(\d+)$/$1+1/e; + chomp(@new); + $info{$dep} = { last_tag => $last_tag, new_commits => \@new, new_tag => $new_tag }; + } + } + return { %info, %info_updates }; +} + +sub show_commands { + my %commands = @_; + my @keys; + while (@_) { + push @keys, shift; + shift; + } + for (@keys) { + say color("red"), $_, color("reset"), ") $commands{$_}"; + } + ReadMode(4); + while (1) { + my $key = ReadKey(0); + if (defined $commands{uc($key)}) { + ReadMode(0); + say ""; + return uc($key); + } + } +} + +sub schedule_operation { + my ($type, $dep, $tag, $reason, $op) = @_; + + my $idx = first { $operations[$_]->{dep} eq $dep } 0..$#operations; + + if (defined $idx) { + push @{$operations[$idx]->{reasons}}, $reason; + push @{$operations[$idx]->{operations}}, $op; + return if $type eq "update"; + $operations[$idx]->{type} = $type; + $info_updates{$dep}->{new_commits} = []; + return; + } + + my $info = deps_git_info(); + + $top_deps_updates{$dep} = {commit => $tag}; + $info_updates{$dep} = {last_tag => $tag, new_tag => $tag, + new_commits => $type eq "tupdate" ? [] : $info->{$dep}->{new_commits}}; + + my $rev_deps = rev_deps(); + @operations = sort { + exists $rev_deps->{$a->{dep}}->{indirect}->{$b->{dep}} ? -1 : + exists $rev_deps->{$b->{dep}}->{indirect}->{$a->{dep}} ? 1 : $a->{dep} cmp $b->{dep} + } (@operations, { + type => $type, + dep => $dep, + version => $tag, + reasons => ($reason ? [$reason] : []), + operations => ($op ? [$op] : [])} + ); + + my $sub_deps = sub_deps(); + + for (keys %{$rev_deps->{$dep}->{direct}}) { + schedule_operation("update", $_, $info->{$_}->{new_tag}, "Updating $dep to version $tag.", [$dep, $tag]); + $sub_deps_updates{$_} = $sub_deps_updates{$_} || clone($sub_deps->{$_}); + $sub_deps_updates{$_}->{$dep}->{commit} = $tag; + } +} + +sub git_tag { + my ($dep, $ver, $msg) = @_; + + system("git", "-C", ".deps-update/$dep", "commit", "-a", "-m", $msg); + system("git", "-C", ".deps-update/$dep", "tag", $ver); +} + +sub git_push { + my ($dep) = @_; + system("git", "-C", ".deps-update/$dep", "push"); + system("git", "-C", ".deps-update/$dep", "push", "--tags"); +} + +update_deps_repos(); + +while (1) { + my $top_deps = top_deps(); + my $git_info = deps_git_info(); + print color("bold blue"), "Dependences with newer tags:\n", color("reset"); + my $old_deps = 0; + for my $dep (sort keys %$top_deps) { + next unless $git_info->{$dep}->{last_tag} ne $top_deps->{$dep}->{commit}; + say color("red"), "$dep", color("reset"), ": $top_deps->{$dep}->{commit} -> $git_info->{$dep}->{last_tag}"; + $old_deps = 1; + } + say "(none)" if not $old_deps; + say ""; + + print color("bold blue"), "Dependences that have commits after last tags:\n", color("reset"); + my $changed_deps = 0; + for my $dep (sort keys %$top_deps) { + next unless @{$git_info->{$dep}->{new_commits}}; + say color("red"), "$dep", color("reset"), " ($top_deps->{$dep}->{commit}):"; + say " $_" for @{$git_info->{$dep}->{new_commits}}; + $changed_deps = 1; + } + say "(none)" if not $changed_deps; + say ""; + + my $cmd = show_commands($old_deps ? (U => "Update dependency") : (), + $changed_deps ? (T => "Tag new release") : (), + @operations ? (A => "Apply changes") : (), + E => "Exit"); + last if $cmd eq "E"; + + if ($cmd eq "U") { + while (1) { + my @deps_to_update; + my @od; + my $idx = 1; + for my $dep (sort keys %$top_deps) { + next unless $git_info->{$dep}->{last_tag} ne $top_deps->{$dep}->{commit}; + $od[$idx] = $dep; + push @deps_to_update, $idx++, "Update $dep to $git_info->{$dep}->{last_tag}"; + } + last if $idx == 1; + my $cmd = show_commands(@deps_to_update, E => "Exit"); + last if $cmd eq "E"; + + my $dep = $od[$cmd]; + schedule_operation("update", $dep, $git_info->{$dep}->{last_tag}); + + $top_deps = top_deps(); + $git_info = deps_git_info(); + } + } + + if ($cmd eq "T") { + while (1) { + my @deps_to_tag; + my @od; + my $idx = 1; + for my $dep (sort keys %$top_deps) { + next unless @{$git_info->{$dep}->{new_commits}}; + $od[$idx] = $dep; + push @deps_to_tag, $idx++, "Tag $dep with version $git_info->{$dep}->{new_tag}"; + } + last if $idx == 1; + my $cmd = show_commands(@deps_to_tag, E => "Exit"); + last if $cmd eq "E"; + + my $dep = $od[$cmd]; + my $d = $git_info->{$dep}; + schedule_operation("tupdate", $dep, $d->{new_tag}); + + $top_deps = top_deps(); + $git_info = deps_git_info(); + } + } + + if ($cmd eq "A") { + $top_deps = top_deps(); + $git_info = deps_git_info(); + my $sub_deps = sub_deps(); + + for my $dep (keys %$top_deps) { + for my $sdep (keys %{$sub_deps->{$dep}}) { + next if $sub_deps->{$dep}->{$sdep}->{commit} eq $top_deps->{$sdep}->{commit}; + schedule_operation("update", $dep, $git_info->{$dep}->{new_tag}, + "Updating $sdep to version $top_deps->{$sdep}->{commit}.", [$sdep, $top_deps->{$sdep}->{commit}]); + } + } + + %info_updates = (); + %top_deps_updates = (); + %sub_deps_updates = (); + + $top_deps = top_deps(); + $git_info = deps_git_info(); + $sub_deps = sub_deps(); + + my %top_changes; + for my $op (@operations) { + update_changelog($op->{dep}, $op->{version}, @{$op->{reasons}}) + if @{$op->{reasons}}; + update_deps_versions(".deps-update/$op->{dep}/rebar.config", unpairs(@{$op->{operations}})) + if @{$op->{operations}}; + if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) { + update_app_src($op->{dep}, $op->{version}); + git_tag($op->{dep}, $op->{version}, "Release $op->{version}"); + } + + $top_changes{$op->{dep}} = $op->{version}; + } + update_deps_versions("rebar.config", %top_changes); + + for my $op (@operations) { + if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) { + git_push($op->{dep}); + } + } + last; + } +} From 5e148df0a9a978ce0502790d6715bc6f66e8372a Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 13 Jun 2017 10:35:00 +0200 Subject: [PATCH 88/91] Update esip and stun dependencies --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index ce5b77a70..7f8d94e7b 100644 --- a/rebar.config +++ b/rebar.config @@ -29,8 +29,8 @@ {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, {p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}}, {luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}}, - {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}}}, - {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}}}, + {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", {tag, "1.0.3"}}}}, {if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql", From d63ea000c73cca9c1c7feb70375a138fa91df926 Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 13 Jun 2017 11:02:30 +0200 Subject: [PATCH 89/91] Prepare package for 17.06-beta --- mix.exs | 10 +++++----- mix.lock | 32 ++++++++++++++------------------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/mix.exs b/mix.exs index 6ba8a3146..025552f45 100644 --- a/mix.exs +++ b/mix.exs @@ -3,9 +3,9 @@ defmodule Ejabberd.Mixfile do def project do [app: :ejabberd, - version: "17.3.0", + version: "17.6.0", description: description(), - elixir: "~> 1.3", + elixir: "~> 1.4", elixirc_paths: ["lib"], compile_path: ".", compilers: [:asn1] ++ Mix.compilers, @@ -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"}, @@ -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] diff --git a/mix.lock b/mix.lock index 92334fc3b..40eda1930 100644 --- a/mix.lock +++ b/mix.lock @@ -1,22 +1,18 @@ -%{"cache_tab": {:hex, :cache_tab, "1.0.7", "8e2c958c0c2178e6c015aa7340c761521dc71b642192a5a7887f4aeffa29c7b1", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, - "distillery": {:hex, :distillery, "1.2.2", "d5a52920cbe2378c8a21dfc83b526b4225944b9dce7bf170fe5f5cddda81ffb3", [:mix], []}, - "earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []}, - "eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []}, - "esip": {:hex, :esip, "1.0.11", "eeb0b1cbb64d56201dd6abb09126afab1e96da2418e833cba6c55f890f2f6649", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}, {:stun, "1.0.10", [hex: :stun, optional: false]}]}, - "ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, +%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []}, + "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []}, + "esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]}, + "ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]}, "ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []}, - "fast_tls": {:hex, :fast_tls, "1.0.11", "8ccff4b68e6bb79b91c689da8cf92ec1006a575d2b6a09ac1ed5f9bf4724a39a", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, - "fast_xml": {:hex, :fast_xml, "1.1.22", "7eb81a738218541208fa3a126ee36197fb0346852d8c12ad678039e539e019df", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, - "fast_yaml": {:hex, :fast_yaml, "1.0.9", "1bf41a576d3eedcb690499350994932340908b4968832adcec4b55152d4e5f20", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, + "fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []}, - "iconv": {:hex, :iconv, "1.0.4", "faa4ac6755567a2806c7bee2cf26fc318016794ffc25481f7230db5f9d025dd8", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, + "iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, "jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []}, - "lager": {:hex, :lager, "3.2.4", "a6deb74dae7927f46bd13255268308ef03eb206ec784a94eaf7c1c0f3b811615", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]}, - "p1_mysql": {:hex, :p1_mysql, "1.0.2", "893a99415f98ce8b6ad014ef950d4e878895787b6c8333587f1e506f831571e0", [:rebar3], []}, + "lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]}, "p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []}, - "p1_pgsql": {:hex, :p1_pgsql, "1.1.2", "27d3137e0b0098808d9c60bf197344669ed1107ed47ce4af2254099a62ccc27e", [:rebar3], []}, - "p1_utils": {:hex, :p1_utils, "1.0.7", "030adbce8935f1b87aaedfdb037d3127cc671ee3e1904b394e6dde9e449d6979", [:rebar3], []}, - "sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []}, - "stringprep": {:hex, :stringprep, "1.0.8", "870d72db031796177261af88d1e6eb081dc314ad217377d441e5ea3c8504a310", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, - "stun": {:hex, :stun, "1.0.10", "9fa83d4c5a76ca5ed3b536852ea00f3fbd2023241559ed6cb23a4ada62183b44", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]}, - "xmpp": {:hex, :xmpp, "1.1.9", "3548dc09faa414ee437c5db53a24af691724cb984b73af832d547c83f50313b9", [:rebar3], [{:fast_xml, "1.1.22", [hex: :fast_xml, optional: false]}, {:stringprep, "1.0.8", [hex: :stringprep, optional: false]}]}} + "p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []}, + "stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]}, + "xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}} From 6c8b037422666c5941252048cb5de2faf6bd016d Mon Sep 17 00:00:00 2001 From: Christophe Romain Date: Tue, 13 Jun 2017 12:37:27 +0200 Subject: [PATCH 90/91] Fix refactor bug on wait_status --- ejabberdctl.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ejabberdctl.template b/ejabberdctl.template index 8976414d5..26719cc26 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -245,7 +245,8 @@ wait_status() if [ $timeout -eq 0 ] ; then status=$1 else - ctl status > /dev/null + exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \ + -extra $ERLANG_NODE $NO_TIMEOUT status > /dev/null status=$? fi done From 0aa64381ffd74303e372eaf712ff6f0f2dbe175f Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Tue, 13 Jun 2017 16:54:29 +0300 Subject: [PATCH 91/91] Fix IP address parsing for mod_metrics --- src/mod_metrics.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mod_metrics.erl b/src/mod_metrics.erl index 8906ef32e..73a68a8df 100644 --- a/src/mod_metrics.erl +++ b/src/mod_metrics.erl @@ -179,7 +179,8 @@ get_socket(N) -> mod_opt_type(ip) -> fun(S) -> - {ok, IP} = inet:parse_ipv4_address(iolist_to_binary(S)), + {ok, IP} = inet:parse_ipv4_address( + binary_to_list(iolist_to_binary(S))), IP end; mod_opt_type(port) ->