Skip to content

Fix/mod privilege - #4651

Open
annelinlol wants to merge 3 commits into
processone:masterfrom
annelinlol:fix/mod-privilege
Open

annelinlol wants to merge 3 commits into
processone:masterfrom
annelinlol:fix/mod-privilege

Conversation

@annelinlol

Copy link
Copy Markdown

mod_privilege: Record privileges on every vhost that loads it

When an external component connects, mod_privilege is supposed to record the privileges granted to it on each vhost that loads the module and send it a <privilege/> message from each of them. Since 26.09 that stops at the first vhost that does not load mod_privilege: the component_connected hook crashes there with badarg, and every vhost listed after it in hosts never records the component's privileges. The component's privileged IQs, roster access and so on are then refused on those vhosts, and the log shows:

Hook component_connected crashed when running mod_privilege:component_connected/1:
** exception error: bad argument
   in function  binary_to_existing_atom/2
      called as binary_to_existing_atom(<<"mod_privilege_localhost">>,latin1)
      *** argument 1: not an already existing atom
   in call from gen_mod:get_module_proc_check/2
   in call from mod_privilege:'-component_connected/1-fun-0-'/2

component_disconnected fails the same way, so privileges recorded on such vhosts are never forgotten either.

Why

component_connected/1 and component_disconnected/2 iterate over ejabberd_option:hosts() and look up each vhost's process with gen_mod:get_module_proc_check/2. Since 602fe1c ("Add gen_mod:get_module_proc_check()", 26.09) that function uses binary_to_existing_atom/2, so it raises badarg for a vhost where the module was never started. The hook is global and runs the whole loop in one call, so one such vhost aborts the rest. Before 602fe1c the process name was created with binary_to_atom/2, and the cast to a nonexistent process was harmlessly ignored.

How

Both functions now walk only the vhosts where gen_mod:is_loaded(Host, mod_privilege) holds, as mod_antispam does for its own per-host loops.

Tests

New test_component_privilege_vhosts in the component group: it loads mod_privilege (granting urn:xmpp:ping) on mnesia.localhost only, which comes after localhost and configtest.localhost in the test configuration's hosts, runs the component_connected hook and expects the <privilege/> message from mnesia.localhost. It then stops the module and restores the configuration, so the rest of the suite runs as before.

CT_BACKENDS=agnostic make test-component: all 16 tests pass with the fix. Without it, test_component_privilege_vhosts fails on a timetrap timeout (the hook crash above is logged) and the 2 tests after it in the sequence are skipped.

…e#4472)

component_send_packet/1 dropped an IQ that a component sent to a bare
JID whenever mod_privilege had no privileges recorded for that component
on the server of the destination, before it checked whether the IQ was
a XEP-0356 <privileged_iq/> at all: the "Component seems disconnected"
clause matched ahead of the unprivileged_iq one. a4062f6 meant to
route such plain IQs as they are, but because of the clause order that
only happened when the destination's server had the component's
privileges recorded. So a gateway's IQs to a service that is not a host
(upload.<domain> for an HTTP File Upload slot, pubsub.<domain>), or any
IQ from a component that was granted nothing, were dropped without an
answer. This was broken since the IQ permission support in 81906b7
(24.10).

Match unprivileged_iq first, so an IQ that wraps no <privileged_iq/>
goes as it is whatever its destination. Wrapped IQs are handled exactly
as before.

The test configuration now loads mod_privilege with no privileges, so
the component group runs with it (test_component_send times out without
this fix). The new test_component_privilege checks plain IQs to the host
and to upload.localhost before and after the component is granted a
namespace, that a privileged IQ in that namespace is forwarded, and that
one in another namespace is still dropped.
component_connected/1 and component_disconnected/2 walk all vhosts and
look up the mod_privilege process of each with
gen_mod:get_module_proc_check/2. Since 602fe1c (26.09) that function
uses binary_to_existing_atom/2, so on the first vhost that does not load
mod_privilege the call fails with badarg, the hook logs a crash and the
loop stops: no vhost listed after it records the component's privileges
on connect, nor forgets them on disconnect.

Walk only the vhosts where the module is loaded, as mod_antispam does.

The new test_component_privilege_vhosts loads mod_privilege on
mnesia.localhost only, which comes after localhost and
configtest.localhost in the test hosts, runs component_connected and
expects the <privilege/> message from mnesia.localhost. Without this fix
it times out.
component_send_packet/1 inspects every IQ a component sends to a bare JID
whose type is not error, and get_iq_encapsulated_details/1 matches only an
IQ with exactly one child. A result with no payload, such as a push
service's answer to mod_push's publish or to a ping, has none, so the hook
crashes with function_clause on every such answer:

  Hook component_send_packet crashed when running mod_privilege:component_send_packet/1:
  ** exception error: no function clause matching
     mod_privilege:get_iq_encapsulated_details({iq,<<"rr-...">>,result,<<>>,
                                                {jid,<<>>,<<"push.example.org">>,...

The IQ is still routed (the hook's accumulator survives the crash), so the
only damage is an error logged per answer, but a push service answers
every push.

Only a get or a set can carry a privileged IQ, so the clause now matches
those two types; a result or an error goes through as it is, as the
unprivileged ones did already.

The new test_component_privilege_result passes a payload-less result and
an error through the hook and expects them back unchanged; without the
fix it fails with function_clause.

CT_BACKENDS=agnostic make test-component: all 16 tests pass.
@p1bot

p1bot commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Hi @annelinlol, many thanks for your contribution!

In order for us to evaluate and accept your PR, we ask that you sign a contribution license agreement. It's all electronic and will take just minutes.

@p1bot p1bot added the cla-missing Contributor needs to sign Contribution License Agreement label Nov 2, 2026
@p1bot

p1bot commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

You did it @annelinlol!

Thank you for signing the ProcessOne Contribution License Agreement.

We will have a look at your contribution!

@p1bot p1bot removed the cla-missing Contributor needs to sign Contribution License Agreement label Nov 9, 2026
@badlop badlop added Kind:Bug Component:Privilege XEP-0356: Privileged Entity labels Nov 9, 2026
@badlop badlop added this to the ejabberd 26.xx milestone Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component:Privilege XEP-0356: Privileged Entity Kind:Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants