diff --git a/include/ejabberd_stacktrace.hrl b/include/ejabberd_stacktrace.hrl new file mode 100644 index 000000000..470abed15 --- /dev/null +++ b/include/ejabberd_stacktrace.hrl @@ -0,0 +1,27 @@ +%%%---------------------------------------------------------------------- +%%% +%%% ejabberd, Copyright (C) 2002-2018 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. +%%% +%%%---------------------------------------------------------------------- + +-ifdef(DEPRECATED_GET_STACKTRACE). +-define(EX_RULE(Class, Reason, Stack), Class:Reason:Stack). +-define(EX_STACK(Stack), Stack). +-else. +-define(EX_RULE(Class, Reason, _), Class:Reason). +-define(EX_STACK(_), erlang:get_stacktrace()). +-endif. diff --git a/mix.exs b/mix.exs index 176e229f6..92e4ca876 100644 --- a/mix.exs +++ b/mix.exs @@ -42,10 +42,19 @@ defmodule Ejabberd.Mixfile do end end + defp if_version_above(ver, okResult) do + if :erlang.system_info(:otp_release) > ver do + okResult + else + [] + end + end + defp erlc_options do # Use our own includes + includes from all dependencies includes = ["include"] ++ deps_include(["fast_xml", "xmpp", "p1_utils"]) [:debug_info, {:d, :ELIXIR_ENABLED}] ++ cond_options() ++ Enum.map(includes, fn(path) -> {:i, path} end) ++ + if_version_above('20', [{:d, :DEPRECATED_GET_STACKTRACE}]) ++ if_function_exported(:crypto, :strong_rand_bytes, 1, [{:d, :STRONG_RAND_BYTES}]) ++ if_function_exported(:rand, :uniform, 1, [{:d, :RAND_UNIFORM}]) ++ if_function_exported(:gb_sets, :iterator_from, 2, [{:d, :GB_SETS_ITERATOR_FROM}]) ++ diff --git a/rebar.config b/rebar.config index b2de1bd0e..4c61cc4a4 100644 --- a/rebar.config +++ b/rebar.config @@ -92,6 +92,7 @@ {if_var_true, debug, debug_info}, {if_var_true, sip, {d, 'SIP'}}, {if_var_true, stun, {d, 'STUN'}}, + {if_version_above, "20", {d, 'DEPRECATED_GET_STACKTRACE'}}, {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'}}, diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl index bd4f22418..9e25ed4e6 100644 --- a/src/ejabberd_acme.erl +++ b/src/ejabberd_acme.erl @@ -25,6 +25,7 @@ -include("ejabberd_commands.hrl"). -include("ejabberd_acme.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(DEFAULT_CONFIG_CONTACT, <<"mailto:example-admin@example.com">>). -define(DEFAULT_CONFIG_CA_URL, "https://acme-v01.api.letsencrypt.org"). @@ -100,10 +101,10 @@ is_valid_domain_opt(DomainString) -> end. -spec is_valid_revoke_cert(string()) -> boolean(). -is_valid_revoke_cert(DomainOrFile) -> +is_valid_revoke_cert(DomainOrFile) -> lists:prefix("file:", DomainOrFile) orelse lists:prefix("domain:", DomainOrFile). - + %% Commands get_commands_spec() -> [#ejabberd_commands{name = get_certificates, tags = [acme], @@ -142,7 +143,7 @@ get_commands_spec() -> %% -spec get_certificates(domains_opt()) -> string() | {'error', _}. get_certificates(Domains) -> - case is_valid_domain_opt(Domains) of + case is_valid_domain_opt(Domains) of true -> try CAUrl = get_config_ca_url(), @@ -150,9 +151,8 @@ get_certificates(Domains) -> catch throw:Throw -> Throw; - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), {error, get_certificates} end; false -> @@ -171,7 +171,7 @@ retrieve_or_create_account(CAUrl) -> case read_account_persistent() of none -> create_save_new_account(CAUrl); - + {ok, AccId, CAUrl, PrivateKey} -> {ok, AccId, PrivateKey}; {ok, _AccId, _, _PrivateKey} -> @@ -199,7 +199,7 @@ get_certificates2(CAUrl, PrivateKey, Hosts) -> %% Format the result to send back to ejabberdctl format_get_certificates_result(SavedCerts). --spec format_get_certificates_result([{'ok', bitstring(), _} | +-spec format_get_certificates_result([{'ok', bitstring(), _} | {'error', bitstring(), _}]) -> string(). format_get_certificates_result(Certs) -> @@ -211,26 +211,26 @@ format_get_certificates_result(Certs) -> case Cond of true -> Result = io_lib:format("Success:~n~s", [FormattedCerts]), - lists:flatten(Result); + lists:flatten(Result); _ -> Result = io_lib:format("Error with one or more certificates~n~s", [FormattedCerts]), lists:flatten(Result) end. --spec format_get_certificate({'ok', bitstring(), _} | +-spec format_get_certificate({'ok', bitstring(), _} | {'error', bitstring(), _}) -> string(). -format_get_certificate({ok, Domain, saved}) -> +format_get_certificate({ok, Domain, saved}) -> io_lib:format(" Certificate for domain: \"~s\" acquired and saved", [Domain]); -format_get_certificate({ok, Domain, not_found}) -> +format_get_certificate({ok, Domain, not_found}) -> io_lib:format(" Certificate for domain: \"~s\" not found, so it was not renewed", [Domain]); format_get_certificate({ok, Domain, no_expire}) -> io_lib:format(" Certificate for domain: \"~s\" is not close to expiring", [Domain]); format_get_certificate({error, Domain, Reason}) -> io_lib:format(" Error for domain: \"~s\", with reason: \'~s\'", [Domain, Reason]). --spec get_certificate(url(), bitstring(), jose_jwk:key()) -> - {'ok', bitstring(), pem()} | +-spec get_certificate(url(), bitstring(), jose_jwk:key()) -> + {'ok', bitstring(), pem()} | {'error', bitstring(), _}. get_certificate(CAUrl, DomainName, PrivateKey) -> try @@ -243,9 +243,8 @@ get_certificate(CAUrl, DomainName, PrivateKey) -> catch throw:Throw -> Throw; - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), {error, DomainName, get_certificate} end. @@ -267,23 +266,23 @@ create_save_new_account(CAUrl) -> %% TODO: %% Find a way to ask the user if he accepts the TOS --spec create_new_account(url(), bitstring(), jose_jwk:key()) -> {'ok', string()} | +-spec create_new_account(url(), bitstring(), jose_jwk:key()) -> {'ok', string()} | no_return(). create_new_account(CAUrl, Contact, PrivateKey) -> try {ok, Dirs, Nonce0} = ejabberd_acme_comm:directory(CAUrl), Req0 = [{ <<"contact">>, [Contact]}], - {ok, {TOS, Account}, Nonce1} = + {ok, {TOS, Account}, Nonce1} = ejabberd_acme_comm:new_account(Dirs, PrivateKey, Req0, Nonce0), {<<"id">>, AccIdInt} = lists:keyfind(<<"id">>, 1, Account), AccId = integer_to_list(AccIdInt), Req1 = [{ <<"agreement">>, list_to_bitstring(TOS)}], - {ok, _Account2, _Nonce2} = + {ok, _Account2, _Nonce2} = ejabberd_acme_comm:update_account({CAUrl, AccId}, PrivateKey, Req1, Nonce1), {ok, AccId} catch E:R -> - ?ERROR_MSG("Error: ~p creating an account for contact: ~p", + ?ERROR_MSG("Error: ~p creating an account for contact: ~p", [{E,R}, Contact]), throw({error,create_new_account}) end. @@ -298,7 +297,7 @@ create_new_authorization(CAUrl, DomainName, PrivateKey) -> {[{<<"type">>, <<"dns">>}, {<<"value">>, DomainName}]}}, {<<"existing">>, <<"accept">>}], - {ok, {AuthzUrl, Authz}, Nonce1} = + {ok, {AuthzUrl, Authz}, Nonce1} = ejabberd_acme_comm:new_authz(Dirs, PrivateKey, Req0, Nonce0), {ok, AuthzId} = location_to_id(AuthzUrl), @@ -321,7 +320,7 @@ create_new_authorization(CAUrl, DomainName, PrivateKey) -> acme_challenge:unregister_hooks(DomainName) end. --spec create_new_certificate(url(), {bitstring(), [bitstring()]}, jose_jwk:key()) -> +-spec create_new_certificate(url(), {bitstring(), [bitstring()]}, jose_jwk:key()) -> {ok, bitstring(), pem()}. create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) -> try @@ -338,7 +337,7 @@ create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) -> {ok, {IssuerCertLink, Certificate}, _Nonce1} = ejabberd_acme_comm:new_cert(Dirs, PrivateKey, Req, Nonce0), - DecodedCert = public_key:pkix_decode_cert(list_to_binary(Certificate), plain), + DecodedCert = public_key:pkix_decode_cert(list_to_binary(Certificate), plain), PemEntryCert = public_key:pem_entry_encode('Certificate', DecodedCert), {ok, IssuerCert, _Nonce2} = ejabberd_acme_comm:get_issuer_cert(IssuerCertLink), @@ -351,7 +350,7 @@ create_new_certificate(CAUrl, {DomainName, AllSubDomains}, PrivateKey) -> PemCertKey = public_key:pem_encode([PemEntryKey, PemEntryCert, PemEntryIssuerCert]), {ok, DomainName, PemCertKey} - catch + catch E:R -> ?ERROR_MSG("Error: ~p getting an authorization for domain: ~p~n", [{E,R}, DomainName]), @@ -383,9 +382,8 @@ renew_certificates() -> catch throw:Throw -> Throw; - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), {error, get_certificates} end. @@ -406,8 +404,8 @@ renew_certificates0(CAUrl) -> %% Format the result to send back to ejabberdctl format_get_certificates_result(SavedCerts). --spec renew_certificate(url(), {bitstring(), data_cert()}, jose_jwk:key()) -> - {'ok', bitstring(), _} | +-spec renew_certificate(url(), {bitstring(), data_cert()}, jose_jwk:key()) -> + {'ok', bitstring(), _} | {'error', bitstring(), _}. renew_certificate(CAUrl, {DomainName, _} = Cert, PrivateKey) -> case cert_to_expire(Cert) of @@ -449,9 +447,8 @@ list_certificates(Verbose) -> catch throw:Throw -> Throw; - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), {error, list_certificates} end; false -> @@ -492,34 +489,33 @@ format_certificate(DataCert, Verbose) -> format_certificate_verbose(DomainName, SANs, NotAfter, PemCert) end catch - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), fail_format_certificate(DomainName) end. --spec format_certificate_plain(bitstring(), [string()], {expired | ok, string()}, string()) +-spec format_certificate_plain(bitstring(), [string()], {expired | ok, string()}, string()) -> string(). format_certificate_plain(DomainName, SANs, NotAfter, Path) -> Result = lists:flatten(io_lib:format( - " Domain: ~s~n" + " Domain: ~s~n" "~s" - " ~s~n" - " Path: ~s", + " ~s~n" + " Path: ~s", [DomainName, lists:flatten([io_lib:format(" SAN: ~s~n", [SAN]) || SAN <- SANs]), format_validity(NotAfter), Path])), Result. --spec format_certificate_verbose(bitstring(), [string()], {expired | ok, string()}, bitstring()) +-spec format_certificate_verbose(bitstring(), [string()], {expired | ok, string()}, bitstring()) -> string(). format_certificate_verbose(DomainName, SANs, NotAfter, PemCert) -> Result = lists:flatten(io_lib:format( " Domain: ~s~n" - "~s" - " ~s~n" - " Certificate In PEM format: ~n~s", - [DomainName, + "~s" + " ~s~n" + " Certificate In PEM format: ~n~s", + [DomainName, lists:flatten([io_lib:format(" SAN: ~s~n", [SAN]) || SAN <- SANs]), format_validity(NotAfter), PemCert])), Result. @@ -533,8 +529,8 @@ format_validity({ok, NotAfter}) -> -spec fail_format_certificate(bitstring()) -> string(). fail_format_certificate(DomainName) -> Result = lists:flatten(io_lib:format( - " Domain: ~s~n" - " Failed to format Certificate", + " Domain: ~s~n" + " Failed to format Certificate", [DomainName])), Result. @@ -542,7 +538,7 @@ fail_format_certificate(DomainName) -> get_commonName(#'Certificate'{tbsCertificate = TbsCertificate}) -> #'TBSCertificate'{ subject = {rdnSequence, SubjectList} - } = TbsCertificate, + } = TbsCertificate, %% TODO: Not the best way to find the commonName ShallowSubjectList = [Attribute || [Attribute] <- SubjectList], @@ -560,9 +556,9 @@ get_notAfter(Certificate) -> true -> "19" ++ [Y1,Y2]; _ -> "20" ++ [Y1,Y2] end, - NotAfter = lists:flatten(io_lib:format("~s-~s-~s ~s:~s:~s", + NotAfter = lists:flatten(io_lib:format("~s-~s-~s ~s:~s:~s", [YEAR, [MO1,MO2], [D1,D2], - [H1,H2], [MI1,MI2], [S1,S2]])), + [H1,H2], [MI1,MI2], [S1,S2]])), case close_to_expire(UtcTime, 0) of true -> @@ -577,7 +573,7 @@ get_subjectAltNames(#'Certificate'{tbsCertificate = TbsCertificate}) -> extensions = Exts } = TbsCertificate, - EncodedSANs = [Val || #'Extension'{extnID = Oid, extnValue = Val} <- Exts, + EncodedSANs = [Val || #'Extension'{extnID = Oid, extnValue = Val} <- Exts, Oid =:= attribute_oid(subjectAltName)], lists:flatmap( @@ -586,7 +582,7 @@ get_subjectAltNames(#'Certificate'{tbsCertificate = TbsCertificate}) -> [Name || {dNSName, Name} <- SANs0] end, EncodedSANs). - + -spec get_utc_validity(#'Certificate'{}) -> string(). get_utc_validity(#'Certificate'{tbsCertificate = TbsCertificate}) -> @@ -618,18 +614,17 @@ revoke_certificates(DomainOrFile) -> catch throw:Throw -> Throw; - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), {error, revoke_certificate} - end. + end. -spec revoke_certificate0(url(), string()) -> {ok, deleted}. revoke_certificate0(CAUrl, DomainOrFile) -> ParsedCert = parse_revoke_cert_argument(DomainOrFile), revoke_certificate1(CAUrl, ParsedCert). --spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) -> +-spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) -> {ok, deleted}. revoke_certificate1(CAUrl, {domain, Domain}) -> case domain_certificate_exists(Domain) of @@ -650,7 +645,7 @@ revoke_certificate1(CAUrl, {file, File}) -> ?ERROR_MSG("Error: ~p reading pem certificate-key file: ~p", [Reason, File]), throw({error, Reason}) end. - + -spec revoke_certificate2(url(), pem()) -> ok. revoke_certificate2(CAUrl, PemEncodedCert) -> @@ -676,10 +671,10 @@ prepare_certificate_revoke(PemEncodedCert) -> DerCert = public_key:der_encode('Certificate', PemCert), Base64Cert = base64url:encode(DerCert), - {ok, Key} = find_private_key_in_pem(PemEncodedCert), + {ok, Key} = find_private_key_in_pem(PemEncodedCert), {Base64Cert, Key}. --spec domain_certificate_exists(bitstring()) -> {bitstring(), data_cert()} | false. +-spec domain_certificate_exists(bitstring()) -> {bitstring(), data_cert()} | false. domain_certificate_exists(Domain) -> Certs = read_certificates_persistent(), lists:keyfind(Domain, 1, Certs). @@ -693,7 +688,7 @@ domain_certificate_exists(Domain) -> %% For now we accept only generating a key of %% specific type for signing the csr --spec make_csr(proplist(), [{dNSName, bitstring()}]) +-spec make_csr(proplist(), [{dNSName, bitstring()}]) -> {binary(), jose_jwk:key()}. make_csr(Attributes, SANs) -> Key = generate_key(), @@ -749,9 +744,9 @@ extension(SANs) -> extension_request(SANs) -> #'AttributePKCS-10'{ type = ?'pkcs-9-at-extensionRequest', - values = [{'asn1_OPENTYPE', + values = [{'asn1_OPENTYPE', public_key:der_encode( - 'ExtensionRequest', + 'ExtensionRequest', [extension(SANs)])}] }. @@ -918,7 +913,7 @@ find_private_key_in_pem(Pem) -> JoseKey = jose_jwk:from_key(Key), {ok, JoseKey} end. - + -spec find_private_key_in_pem1([public_key:pki_asn1_type()], [public_key:pem_entry()]) -> @@ -948,10 +943,10 @@ private_key_types() -> find_all_sub_domains(DomainName) -> AllRoutes = ejabberd_router:get_all_routes(), DomainLen = size(DomainName), - [Route || Route <- AllRoutes, - binary:longest_common_suffix([DomainName, Route]) + [Route || Route <- AllRoutes, + binary:longest_common_suffix([DomainName, Route]) =:= DomainLen]. - + -spec is_error(_) -> boolean(). is_error({error, _}) -> true; @@ -981,13 +976,13 @@ data_get_account(Data) -> end. -spec data_set_account(acme_data(), {list(), url(), jose_jwk:key()}) -> acme_data(). -data_set_account(Data, {AccId, CAUrl, PrivateKey}) -> +data_set_account(Data, {AccId, CAUrl, PrivateKey}) -> NewAcc = {account, #data_acc{id = AccId, ca_url = CAUrl, key = PrivateKey}}, lists:keystore(account, 1, Data, NewAcc). %% %% Certificates -%% +%% -spec data_get_certificates(acme_data()) -> data_certs(). data_get_certificates(Data) -> @@ -999,7 +994,7 @@ data_get_certificates(Data) -> end. -spec data_set_certificates(acme_data(), data_certs()) -> acme_data(). -data_set_certificates(Data, NewCerts) -> +data_set_certificates(Data, NewCerts) -> lists:keystore(certs, 1, Data, {certs, NewCerts}). %% ATM we preserve one certificate for each domain @@ -1053,7 +1048,7 @@ write_persistent(Data) -> {error, Reason} -> ?ERROR_MSG("Error: ~p writing acme data file", [Reason]), throw({error, Reason}) - end. + end. -spec create_persistent() -> ok | no_return(). create_persistent() -> @@ -1069,7 +1064,7 @@ create_persistent() -> {error, Reason} -> ?ERROR_MSG("Error: ~p creating acme data file", [Reason]), throw({error, Reason}) - end. + end. -spec write_account_persistent({list(), url(), jose_jwk:key()}) -> ok | no_return(). write_account_persistent({AccId, CAUrl, PrivateKey}) -> @@ -1099,7 +1094,7 @@ remove_certificate_persistent(DataCert) -> NewData = data_remove_certificate(Data, DataCert), ok = write_persistent(NewData). --spec save_certificate({ok, bitstring(), binary()} | {error, _, _}) -> +-spec save_certificate({ok, bitstring(), binary()} | {error, _, _}) -> {ok, bitstring(), saved} | {error, bitstring(), _}. save_certificate({error, _, _} = Error) -> Error; @@ -1123,13 +1118,12 @@ save_certificate({ok, DomainName, Cert}) -> catch throw:Throw -> Throw; - E:R -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, St]), + ?EX_RULE(E, R, St) -> + ?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, ?EX_STACK(St)]), {error, DomainName, saving} end. --spec save_renewed_certificate({ok, bitstring(), _} | {error, _, _}) -> +-spec save_renewed_certificate({ok, bitstring(), _} | {error, _, _}) -> {ok, bitstring(), _} | {error, bitstring(), _}. save_renewed_certificate({error, _, _} = Error) -> Error; diff --git a/src/ejabberd_ctl.erl b/src/ejabberd_ctl.erl index c71649562..3930d8865 100644 --- a/src/ejabberd_ctl.erl +++ b/src/ejabberd_ctl.erl @@ -59,6 +59,7 @@ -include("ejabberd_ctl.hrl"). -include("ejabberd_commands.hrl"). -include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(DEFAULT_VERSION, 1000000). @@ -327,9 +328,9 @@ try_call_command(Args, Auth, AccessCommands, Version) -> catch throw:Error -> {io_lib:format("~p", [Error]), ?STATUS_ERROR}; - A:Why -> - Stack = erlang:get_stacktrace(), - {io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", [A, Why, Stack]), ?STATUS_ERROR} + ?EX_RULE(A, Why, Stack) -> + {io_lib:format("Problem '~p ~p' occurred executing the command.~nStacktrace: ~p", + [A, Why, ?EX_STACK(Stack)]), ?STATUS_ERROR} end. %% @spec (Args::[string()], Auth, AccessCommands) -> string() | integer() | {string(), integer()} | {error, ErrorType} diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index bc67b4c67..383f203cc 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -57,6 +57,7 @@ terminate/2]). -include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {}). -type local_hook() :: { Seq :: integer(), Module :: atom(), Function :: atom()}. @@ -129,14 +130,14 @@ delete_dist(Hook, Node, Module, Function, Seq) -> delete_dist(Hook, Host, Node, Module, Function, Seq) -> gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}). --spec delete_all_hooks() -> true. +-spec delete_all_hooks() -> true. %% @doc Primarily for testing / instrumentation delete_all_hooks() -> gen_server:call(ejabberd_hooks, {delete_all}). -spec get_handlers(atom(), binary() | global) -> [local_hook() | distributed_hook()]. -%% @doc Returns currently set handler for hook name +%% @doc Returns currently set handler for hook name get_handlers(Hookname, Host) -> gen_server:call(ejabberd_hooks, {get_handlers, Hookname, Host}). @@ -264,7 +265,7 @@ handle_delete(Hook, Host, El) -> ok; [] -> ok - end. + end. %%---------------------------------------------------------------------- %% Func: handle_cast/2 @@ -379,15 +380,11 @@ safe_apply(Hook, Module, Function, Args) -> true -> apply(Module, Function, Args) end - catch E:R when E /= exit; R /= normal -> - St = get_stacktrace(), + catch ?EX_RULE(E, R, St) when E /= exit; R /= normal -> ?ERROR_MSG("Hook ~p crashed when running ~p:~p/~p:~n" "** Reason = ~p~n" "** Arguments = ~p", [Hook, Module, Function, length(Args), - {E, R, St}, Args]), + {E, R, ?EX_STACK(St)}, Args]), 'EXIT' end. - -get_stacktrace() -> - [{Mod, Fun, Loc, Args} || {Mod, Fun, Args, Loc} <- erlang:get_stacktrace()]. diff --git a/src/ejabberd_local.erl b/src/ejabberd_local.erl index d9ef97129..2a1b532e0 100644 --- a/src/ejabberd_local.erl +++ b/src/ejabberd_local.erl @@ -48,6 +48,7 @@ -include("logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include("xmpp.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {}). @@ -70,10 +71,9 @@ start_link() -> -spec route(stanza()) -> any(). route(Packet) -> try do_route(Packet) - catch E:R -> - St = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet), {E, {R, St}}]) + [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}]) end. -spec route_iq(iq(), function()) -> ok. diff --git a/src/ejabberd_mnesia.erl b/src/ejabberd_mnesia.erl index 48bc6db5c..882f95041 100644 --- a/src/ejabberd_mnesia.erl +++ b/src/ejabberd_mnesia.erl @@ -43,6 +43,7 @@ -define(NEED_RESET, [local_content, type]). -include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {tables = #{} :: map(), schema = [] :: [{atom(), [{atom(), any()}]}]}). @@ -385,8 +386,8 @@ do_transform(OldAttrs, Attrs, Old) -> transform_fun(Module, Name) -> fun(Obj) -> try Module:transform(Obj) - catch E:R -> - StackTrace = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> + StackTrace = ?EX_STACK(St), ?ERROR_MSG("Failed to transform Mnesia table ~s:~n" "** Record: ~p~n" "** Reason: ~p~n" diff --git a/src/ejabberd_redis.erl b/src/ejabberd_redis.erl index 857bece0e..ac4421588 100644 --- a/src/ejabberd_redis.erl +++ b/src/ejabberd_redis.erl @@ -50,6 +50,7 @@ -define(CALL_TIMEOUT, 60*1000). %% 60 seconds -include("logger.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {connection :: pid() | undefined, num :: pos_integer(), @@ -106,9 +107,9 @@ multi(F) -> {error, _} = Err -> Err; Result -> get_result(Result) end - catch E:R -> + catch ?EX_RULE(E, R, St) -> erlang:erase(?TR_STACK), - erlang:raise(E, R, erlang:get_stacktrace()) + erlang:raise(E, R, ?EX_STACK(St)) end; _ -> erlang:error(nested_transaction) diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index bd9a87ce2..edfcf932a 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -71,6 +71,7 @@ -include("logger.hrl"). -include("ejabberd_router.hrl"). -include("xmpp.hrl"). +-include("ejabberd_stacktrace.hrl"). -callback init() -> any(). -callback register_route(binary(), binary(), local_hint(), @@ -90,10 +91,9 @@ start_link() -> -spec route(stanza()) -> ok. route(Packet) -> try do_route(Packet) - catch E:R -> - St = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet), {E, {R, St}}]) + [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}]) end. -spec route(jid(), jid(), xmlel() | stanza()) -> ok. diff --git a/src/ejabberd_router_sql.erl b/src/ejabberd_router_sql.erl index edf06dfe0..fa6579ce4 100644 --- a/src/ejabberd_router_sql.erl +++ b/src/ejabberd_router_sql.erl @@ -32,6 +32,7 @@ -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). -include("ejabberd_router.hrl"). +-include("ejabberd_stacktrace.hrl"). %%%=================================================================== %%% API @@ -121,12 +122,11 @@ row_to_route(Domain, {ServerHost, NodeS, PidS, LocalHintS} = Row) -> local_hint = dec_local_hint(LocalHintS)}] catch _:{bad_node, _} -> []; - E:R -> - St = erlang:get_stacktrace(), + ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to decode row from 'route' table:~n" "Row = ~p~n" "Domain = ~s~n" "Reason = ~p", - [Row, Domain, {E, {R, St}}]), + [Row, Domain, {E, {R, ?EX_STACK(St)}}]), [] end. diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index a33d477e5..b721ca1e7 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -55,12 +55,10 @@ transform_options/1, opt_type/1]). -include("logger.hrl"). - -include("xmpp.hrl"). - -include("ejabberd_commands.hrl"). - -include_lib("public_key/include/public_key.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(PKIXEXPLICIT, 'OTP-PUB-KEY'). @@ -94,10 +92,9 @@ start_link() -> route(Packet) -> try do_route(Packet) - catch E:R -> - St = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", - [xmpp:pp(Packet), {E, {R, St}}]) + [xmpp:pp(Packet), {E, {R, ?EX_STACK(St)}}]) end. clean_temporarily_blocked_table() -> diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl index 6c00815d5..873c32da0 100644 --- a/src/ejabberd_sm.erl +++ b/src/ejabberd_sm.erl @@ -90,6 +90,7 @@ -include("ejabberd_commands.hrl"). -include("ejabberd_sm.hrl"). +-include("ejabberd_stacktrace.hrl"). -callback init() -> ok | {error, any()}. -callback set_session(#session{}) -> ok | {error, any()}. @@ -140,11 +141,10 @@ route(Packet) -> ?DEBUG("hook dropped stanza:~n~s", [xmpp:pp(Packet)]); Packet1 -> try do_route(Packet1), ok - catch E:R -> - St = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to route packet:~n~s~nReason = ~p", [xmpp:pp(Packet1), - {E, {R, St}}]) + {E, {R, ?EX_STACK(St)}}]) end end. diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 9e088f211..5e35e344c 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -69,6 +69,7 @@ -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(state, {db_ref = self() :: pid(), @@ -516,24 +517,26 @@ outer_transaction(F, NRestarts, _Reason) -> end, sql_query_internal([<<"begin;">>]), put(?NESTING_KEY, PreviousNestingLevel + 1), - Result = (catch F()), - put(?NESTING_KEY, PreviousNestingLevel), - case Result of - {aborted, Reason} when NRestarts > 0 -> - sql_query_internal([<<"rollback;">>]), - outer_transaction(F, NRestarts - 1, Reason); - {aborted, Reason} when NRestarts =:= 0 -> - ?ERROR_MSG("SQL transaction restarts exceeded~n** " - "Restarts: ~p~n** Last abort reason: " - "~p~n** Stacktrace: ~p~n** When State " - "== ~p", - [?MAX_TRANSACTION_RESTARTS, Reason, - erlang:get_stacktrace(), get(?STATE_KEY)]), - sql_query_internal([<<"rollback;">>]), - {aborted, Reason}; - {'EXIT', Reason} -> - sql_query_internal([<<"rollback;">>]), {aborted, Reason}; - Res -> sql_query_internal([<<"commit;">>]), {atomic, Res} + try F() of + Res -> + sql_query_internal([<<"commit;">>]), + {atomic, Res} + catch + ?EX_RULE(throw, {aborted, Reason}, _) when NRestarts > 0 -> + sql_query_internal([<<"rollback;">>]), + outer_transaction(F, NRestarts - 1, Reason); + ?EX_RULE(throw, {aborted, Reason}, Stack) when NRestarts =:= 0 -> + ?ERROR_MSG("SQL transaction restarts exceeded~n** " + "Restarts: ~p~n** Last abort reason: " + "~p~n** Stacktrace: ~p~n** When State " + "== ~p", + [?MAX_TRANSACTION_RESTARTS, Reason, + ?EX_STACK(Stack), get(?STATE_KEY)]), + sql_query_internal([<<"rollback;">>]), + {aborted, Reason}; + ?EX_RULE(exit, Reason, _) -> + sql_query_internal([<<"rollback;">>]), + {aborted, Reason} end. execute_bloc(F) -> @@ -599,10 +602,9 @@ sql_query_internal(#sql_query{} = Query) -> {error, <<"killed">>}; exit:{normal, _} -> {error, <<"terminated unexpectedly">>}; - Class:Reason -> - ST = erlang:get_stacktrace(), + ?EX_RULE(Class, Reason, Stack) -> ?ERROR_MSG("Internal error while processing SQL query: ~p", - [{Class, Reason, ST}]), + [{Class, Reason, ?EX_STACK(Stack)}]), {error, <<"internal error">>} end, check_error(Res, Query); @@ -737,12 +739,11 @@ sql_query_format_res({selected, _, Rows}, SQLQuery) -> try [(SQLQuery#sql_query.format_res)(Row)] catch - Class:Reason -> - ST = erlang:get_stacktrace(), + ?EX_RULE(Class, Reason, Stack) -> ?ERROR_MSG("Error while processing " "SQL query result: ~p~n" "row: ~p", - [{Class, Reason, ST}, Row]), + [{Class, Reason, ?EX_STACK(Stack)}, Row]), [] end end, Rows), diff --git a/src/gen_iq_handler.erl b/src/gen_iq_handler.erl index f6a6744fd..9e7b56406 100644 --- a/src/gen_iq_handler.erl +++ b/src/gen_iq_handler.erl @@ -40,6 +40,7 @@ -include("logger.hrl"). -include("xmpp.hrl"). -include("translate.hrl"). +-include("ejabberd_stacktrace.hrl"). -type component() :: ejabberd_sm | ejabberd_local. @@ -113,10 +114,9 @@ process_iq(_Host, Module, Function, IQ) -> ejabberd_router:route(ResIQ); ignore -> ok - catch E:R -> - St = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to process iq:~n~s~nReason = ~p", - [xmpp:pp(IQ), {E, {R, St}}]), + [xmpp:pp(IQ), {E, {R, ?EX_STACK(St)}}]), Txt = <<"Module failed to handle the query">>, Err = xmpp:err_internal_server_error(Txt, IQ#iq.lang), ejabberd_router:route_error(IQ, Err) diff --git a/src/gen_mod.erl b/src/gen_mod.erl index cf107f7b0..d225beeff 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -58,6 +58,7 @@ -include("logger.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). +-include("ejabberd_stacktrace.hrl"). -record(ejabberd_module, {module_host = {undefined, <<"">>} :: {atom(), binary()}, @@ -217,8 +218,8 @@ start_module(Host, Module, Opts0, Order, NeedValidation) -> {ok, Pid} when is_pid(Pid) -> {ok, Pid}; Err -> erlang:error({bad_return, Module, Err}) end - catch Class:Reason -> - StackTrace = erlang:get_stacktrace(), + catch ?EX_RULE(Class, Reason, Stack) -> + StackTrace = ?EX_STACK(Stack), ets:delete(ejabberd_modules, {Module, Host}), ErrorText = format_module_error( Module, start, 2, @@ -282,8 +283,8 @@ reload_module(Host, Module, NewOpts, OldOpts, Order) -> {ok, Pid} when is_pid(Pid) -> {ok, Pid}; Err -> erlang:error({bad_return, Module, Err}) end - catch Class:Reason -> - StackTrace = erlang:get_stacktrace(), + catch ?EX_RULE(Class, Reason, Stack) -> + StackTrace = ?EX_STACK(Stack), ErrorText = format_module_error( Module, reload, 3, NewOpts, Class, Reason, diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl index 3fb0d5981..e6e618bd0 100644 --- a/src/mod_http_api.erl +++ b/src/mod_http_api.erl @@ -80,6 +80,7 @@ -include("xmpp.hrl"). -include("logger.hrl"). -include("ejabberd_http.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(DEFAULT_API_VERSION, 0). @@ -192,9 +193,8 @@ process([Call], #request{method = 'POST', data = Data, ip = IPPort} = Req) -> _:{error,{_,invalid_json}} = _Err -> ?DEBUG("Bad Request: ~p", [_Err]), badrequest_response(<<"Invalid JSON input">>); - _:_Error -> - St = erlang:get_stacktrace(), - ?DEBUG("Bad Request: ~p ~p", [_Error, St]), + ?EX_RULE(_Class, _Error, Stack) -> + ?DEBUG("Bad Request: ~p ~p", [_Error, ?EX_STACK(Stack)]), badrequest_response() end; process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) -> @@ -210,9 +210,8 @@ process([Call], #request{method = 'GET', q = Data, ip = {IP, _}} = Req) -> %% TODO We need to refactor to remove redundant error return formatting throw:{error, unknown_command} -> json_format({404, 44, <<"Command not found.">>}); - _:_Error -> - St = erlang:get_stacktrace(), - ?DEBUG("Bad Request: ~p ~p", [_Error, St]), + ?EX_RULE(_, _Error, Stack) -> + ?DEBUG("Bad Request: ~p ~p", [_Error, ?EX_STACK(Stack)]), badrequest_response() end; process([_Call], #request{method = 'OPTIONS', data = <<>>}) -> @@ -302,9 +301,8 @@ handle(Call, Auth, Args, Version) when is_atom(Call), is_list(Args) -> {400, misc:atom_to_binary(Error)}; throw:Msg when is_list(Msg); is_binary(Msg) -> {400, iolist_to_binary(Msg)}; - _Error -> - St = erlang:get_stacktrace(), - ?ERROR_MSG("REST API Error: ~p ~p", [_Error, St]), + ?EX_RULE(Class, Error, Stack) -> + ?ERROR_MSG("REST API Error: ~p:~p ~p", [Class, Error, ?EX_STACK(Stack)]), {500, <<"internal_error">>} end; {error, Msg} -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 39f727111..a52b3d55a 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -54,6 +54,7 @@ -include("xmpp.hrl"). -include("translate.hrl"). -include("mod_muc_room.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(MAX_USERS_DEFAULT_LIST, [5, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000]). @@ -2765,7 +2766,7 @@ process_item_change(Item, SD, UJID) -> maybe_send_affiliation(JID, A, SD1), SD1 end - catch E:R -> + catch ?EX_RULE(E, R, St) -> FromSuffix = case UJID of #jid{} -> JidString = jid:encode(UJID), @@ -2773,9 +2774,8 @@ process_item_change(Item, SD, UJID) -> undefined -> <<"">> end, - St = erlang:get_stacktrace(), ?ERROR_MSG("failed to set item ~p~s: ~p", - [Item, FromSuffix, {E, {R, St}}]), + [Item, FromSuffix, {E, {R, ?EX_STACK(St)}}]), {error, xmpp:err_internal_server_error()} end. diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 8d3fde6d1..f97b8ea5f 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -53,14 +53,11 @@ depends/2]). -include("logger.hrl"). - -include("xmpp.hrl"). - -include("mod_roster.hrl"). - -include("ejabberd_http.hrl"). - -include("ejabberd_web_admin.hrl"). +-include("ejabberd_stacktrace.hrl"). -define(ROSTER_CACHE, roster_cache). -define(ROSTER_ITEM_CACHE, roster_item_cache). @@ -320,10 +317,9 @@ process_iq_get(#iq{to = To, lang = Lang, #roster_query{items = Items, ver = Version} end) - catch E:R -> - St = erlang:get_stacktrace(), + catch ?EX_RULE(E, R, St) -> ?ERROR_MSG("failed to process roster get for ~s: ~p", - [jid:encode(To), {E, {R, St}}]), + [jid:encode(To), {E, {R, ?EX_STACK(St)}}]), Txt = <<"Roster module has failed">>, xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang)) end.