mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-20 17:27:00 +01:00
Update MIX code to reflect newest specification
Note that support for older specification is completely dropped, i.e. no backward compatibility is provided since the XEPs are still very experimental and being changed drastically
This commit is contained in:
parent
109ed8f2f6
commit
d5e4da54cf
@ -24,7 +24,7 @@
|
|||||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.26"}}},
|
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.26"}}},
|
||||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
|
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
|
||||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}},
|
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}},
|
||||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.2.6"}}},
|
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "8bc04ba"}},
|
||||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}},
|
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}},
|
||||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
||||||
|
@ -410,3 +410,57 @@ CREATE TABLE push_session (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX i_push_session_susn ON push_session (server_host, username, service, node);
|
CREATE UNIQUE INDEX i_push_session_susn ON push_session (server_host, username, service, node);
|
||||||
|
|
||||||
|
CREATE TABLE mix_channel (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
hidden boolean NOT NULL,
|
||||||
|
hmac_key text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_channel ON mix_channel (channel, service);
|
||||||
|
CREATE INDEX i_mix_channel_serv ON mix_channel (service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_participant (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
nick text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_participant ON mix_participant (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_participant_chan_serv ON mix_participant (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_subscription (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
node text NOT NULL,
|
||||||
|
jid text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_subscription ON mix_subscription (channel, service, username, domain, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_ud ON mix_subscription (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_node ON mix_subscription (channel, service, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv ON mix_subscription (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_pam (
|
||||||
|
username text NOT NULL,
|
||||||
|
server_host text NOT NULL,
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, server_host, channel, service);
|
||||||
|
CREATE INDEX i_mix_pam_us ON mix_pam (username, server_host);
|
||||||
|
53
sql/lite.sql
53
sql/lite.sql
@ -379,3 +379,56 @@ CREATE TABLE push_session (
|
|||||||
|
|
||||||
CREATE UNIQUE INDEX i_push_usn ON push_session (username, service, node);
|
CREATE UNIQUE INDEX i_push_usn ON push_session (username, service, node);
|
||||||
CREATE UNIQUE INDEX i_push_ut ON push_session (username, timestamp);
|
CREATE UNIQUE INDEX i_push_ut ON push_session (username, timestamp);
|
||||||
|
|
||||||
|
CREATE TABLE mix_channel (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
hidden boolean NOT NULL,
|
||||||
|
hmac_key text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_channel ON mix_channel (channel, service);
|
||||||
|
CREATE INDEX i_mix_channel_serv ON mix_channel (service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_participant (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
nick text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_participant ON mix_participant (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_participant_chan_serv ON mix_participant (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_subscription (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
node text NOT NULL,
|
||||||
|
jid text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_subscription ON mix_subscription (channel, service, username, domain, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_ud ON mix_subscription (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_node ON mix_subscription (channel, service, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv ON mix_subscription (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_pam (
|
||||||
|
username text NOT NULL,
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, channel, service);
|
||||||
|
CREATE INDEX i_mix_pam_us ON mix_pam (username);
|
||||||
|
@ -426,3 +426,57 @@ CREATE TABLE push_session (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX i_push_session_susn ON push_session (server_host(191), username(191), service(191), node(191));
|
CREATE UNIQUE INDEX i_push_session_susn ON push_session (server_host(191), username(191), service(191), node(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_channel (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
hidden boolean NOT NULL,
|
||||||
|
hmac_key text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_channel ON mix_channel (channel(191), service(191));
|
||||||
|
CREATE INDEX i_mix_channel_serv ON mix_channel (service(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_participant (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
nick text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_participant ON mix_participant (channel(191), service(191), username(191), domain(191));
|
||||||
|
CREATE INDEX i_mix_participant_chan_serv ON mix_participant (channel(191), service(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_subscription (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
node text NOT NULL,
|
||||||
|
jid text NOT NULL
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_subscription ON mix_subscription (channel(153), service(153), username(153), domain(153), node(153));
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_ud ON mix_subscription (channel(191), service(191), username(191), domain(191));
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_node ON mix_subscription (channel(191), service(191), node(191));
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv ON mix_subscription (channel(191), service(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_pam (
|
||||||
|
username text NOT NULL,
|
||||||
|
server_host text NOT NULL,
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username(191), server_host(191), channel(191), service(191));
|
||||||
|
CREATE INDEX i_mix_pam_us ON mix_pam (username(191), server_host(191));
|
||||||
|
@ -395,3 +395,56 @@ CREATE TABLE push_session (
|
|||||||
|
|
||||||
CREATE UNIQUE INDEX i_push_usn ON push_session (username(191), service(191), node(191));
|
CREATE UNIQUE INDEX i_push_usn ON push_session (username(191), service(191), node(191));
|
||||||
CREATE UNIQUE INDEX i_push_ut ON push_session (username(191), timestamp);
|
CREATE UNIQUE INDEX i_push_ut ON push_session (username(191), timestamp);
|
||||||
|
|
||||||
|
CREATE TABLE mix_channel (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
hidden boolean NOT NULL,
|
||||||
|
hmac_key text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_channel ON mix_channel (channel(191), service(191));
|
||||||
|
CREATE INDEX i_mix_channel_serv ON mix_channel (service(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_participant (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
nick text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_participant ON mix_participant (channel(191), service(191), username(191), domain(191));
|
||||||
|
CREATE INDEX i_mix_participant_chan_serv ON mix_participant (channel(191), service(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_subscription (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
node text NOT NULL,
|
||||||
|
jid text NOT NULL
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_subscription ON mix_subscription (channel(153), service(153), username(153), domain(153), node(153));
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_ud ON mix_subscription (channel(191), service(191), username(191), domain(191));
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_node ON mix_subscription (channel(191), service(191), node(191));
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv ON mix_subscription (channel(191), service(191));
|
||||||
|
|
||||||
|
CREATE TABLE mix_pam (
|
||||||
|
username text NOT NULL,
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username(191), channel(191), service(191));
|
||||||
|
CREATE INDEX i_mix_pam_u ON mix_pam (username(191));
|
||||||
|
@ -571,3 +571,57 @@ CREATE TABLE push_session (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX i_push_session_susn ON push_session USING btree (server_host, username, service, node);
|
CREATE UNIQUE INDEX i_push_session_susn ON push_session USING btree (server_host, username, service, node);
|
||||||
|
|
||||||
|
CREATE TABLE mix_channel (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
hidden boolean NOT NULL,
|
||||||
|
hmac_key text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_channel ON mix_channel (channel, service);
|
||||||
|
CREATE INDEX i_mix_channel_serv ON mix_channel (service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_participant (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
nick text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_participant ON mix_participant (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_participant_chan_serv ON mix_participant (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_subscription (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
node text NOT NULL,
|
||||||
|
jid text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_subscription ON mix_subscription (channel, service, username, domain, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_ud ON mix_subscription (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_node ON mix_subscription (channel, service, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv ON mix_subscription (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_pam (
|
||||||
|
username text NOT NULL,
|
||||||
|
server_host text NOT NULL,
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, server_host, channel, service);
|
||||||
|
CREATE INDEX i_mix_pam_us ON mix_pam (username, server_host);
|
||||||
|
53
sql/pg.sql
53
sql/pg.sql
@ -399,3 +399,56 @@ CREATE TABLE push_session (
|
|||||||
|
|
||||||
CREATE UNIQUE INDEX i_push_usn ON push_session USING btree (username, service, node);
|
CREATE UNIQUE INDEX i_push_usn ON push_session USING btree (username, service, node);
|
||||||
CREATE UNIQUE INDEX i_push_ut ON push_session USING btree (username, timestamp);
|
CREATE UNIQUE INDEX i_push_ut ON push_session USING btree (username, timestamp);
|
||||||
|
|
||||||
|
CREATE TABLE mix_channel (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
hidden boolean NOT NULL,
|
||||||
|
hmac_key text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_channel ON mix_channel (channel, service);
|
||||||
|
CREATE INDEX i_mix_channel_serv ON mix_channel (service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_participant (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
jid text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
nick text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_participant ON mix_participant (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_participant_chan_serv ON mix_participant (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_subscription (
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
username text NOT NULL,
|
||||||
|
domain text NOT NULL,
|
||||||
|
node text NOT NULL,
|
||||||
|
jid text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_subscription ON mix_subscription (channel, service, username, domain, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_ud ON mix_subscription (channel, service, username, domain);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv_node ON mix_subscription (channel, service, node);
|
||||||
|
CREATE INDEX i_mix_subscription_chan_serv ON mix_subscription (channel, service);
|
||||||
|
|
||||||
|
CREATE TABLE mix_pam (
|
||||||
|
username text NOT NULL,
|
||||||
|
channel text NOT NULL,
|
||||||
|
service text NOT NULL,
|
||||||
|
id text NOT NULL,
|
||||||
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX i_mix_pam ON mix_pam (username, channel, service);
|
||||||
|
CREATE INDEX i_mix_pam_us ON mix_pam (username);
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
close_session/4,
|
close_session/4,
|
||||||
check_in_subscription/2,
|
check_in_subscription/2,
|
||||||
bounce_offline_message/1,
|
bounce_offline_message/1,
|
||||||
|
bounce_sm_packet/1,
|
||||||
disconnect_removed_user/2,
|
disconnect_removed_user/2,
|
||||||
get_user_resources/2,
|
get_user_resources/2,
|
||||||
get_user_present_resources/2,
|
get_user_present_resources/2,
|
||||||
@ -191,14 +192,22 @@ check_in_subscription(Acc, #presence{to = To}) ->
|
|||||||
|
|
||||||
-spec bounce_offline_message({bounce, message()} | any()) -> any().
|
-spec bounce_offline_message({bounce, message()} | any()) -> any().
|
||||||
|
|
||||||
bounce_offline_message({bounce, #message{type = T} = Packet} = Acc)
|
bounce_offline_message({bounce, #message{type = T}} = Acc)
|
||||||
when T == chat; T == groupchat; T == normal ->
|
when T == chat; T == groupchat; T == normal ->
|
||||||
|
bounce_sm_packet(Acc);
|
||||||
|
bounce_offline_message(Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
-spec bounce_sm_packet({bounce | term(), stanza()}) -> any().
|
||||||
|
bounce_sm_packet({bounce, Packet} = Acc) ->
|
||||||
Lang = xmpp:get_lang(Packet),
|
Lang = xmpp:get_lang(Packet),
|
||||||
Txt = <<"User session not found">>,
|
Txt = <<"User session not found">>,
|
||||||
Err = xmpp:err_service_unavailable(Txt, Lang),
|
Err = xmpp:err_service_unavailable(Txt, Lang),
|
||||||
ejabberd_router:route_error(Packet, Err),
|
ejabberd_router:route_error(Packet, Err),
|
||||||
{stop, Acc};
|
{stop, Acc};
|
||||||
bounce_offline_message(Acc) ->
|
bounce_sm_packet({_, Packet} = Acc) ->
|
||||||
|
?DEBUG("dropping packet to unavailable resource:~n~s",
|
||||||
|
[xmpp:pp(Packet)]),
|
||||||
Acc.
|
Acc.
|
||||||
|
|
||||||
-spec disconnect_removed_user(binary(), binary()) -> ok.
|
-spec disconnect_removed_user(binary(), binary()) -> ok.
|
||||||
@ -486,6 +495,8 @@ host_up(Host) ->
|
|||||||
ejabberd_sm, check_in_subscription, 20),
|
ejabberd_sm, check_in_subscription, 20),
|
||||||
ejabberd_hooks:add(offline_message_hook, Host,
|
ejabberd_hooks:add(offline_message_hook, Host,
|
||||||
ejabberd_sm, bounce_offline_message, 100),
|
ejabberd_sm, bounce_offline_message, 100),
|
||||||
|
ejabberd_hooks:add(bounce_sm_packet, Host,
|
||||||
|
ejabberd_sm, bounce_sm_packet, 100),
|
||||||
ejabberd_hooks:add(remove_user, Host,
|
ejabberd_hooks:add(remove_user, Host,
|
||||||
ejabberd_sm, disconnect_removed_user, 100),
|
ejabberd_sm, disconnect_removed_user, 100),
|
||||||
ejabberd_c2s:host_up(Host).
|
ejabberd_c2s:host_up(Host).
|
||||||
@ -510,6 +521,8 @@ host_down(Host) ->
|
|||||||
ejabberd_sm, check_in_subscription, 20),
|
ejabberd_sm, check_in_subscription, 20),
|
||||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||||
ejabberd_sm, bounce_offline_message, 100),
|
ejabberd_sm, bounce_offline_message, 100),
|
||||||
|
ejabberd_hooks:delete(bounce_sm_packet, Host,
|
||||||
|
ejabberd_sm, bounce_sm_packet, 100),
|
||||||
ejabberd_hooks:delete(remove_user, Host,
|
ejabberd_hooks:delete(remove_user, Host,
|
||||||
ejabberd_sm, disconnect_removed_user, 100),
|
ejabberd_sm, disconnect_removed_user, 100),
|
||||||
ejabberd_c2s:host_down(Host).
|
ejabberd_c2s:host_down(Host).
|
||||||
@ -645,19 +658,22 @@ do_route(#presence{to = #jid{lresource = <<"">>} = To} = Packet) ->
|
|||||||
fun({_, R}) ->
|
fun({_, R}) ->
|
||||||
do_route(Packet#presence{to = jid:replace_resource(To, R)})
|
do_route(Packet#presence{to = jid:replace_resource(To, R)})
|
||||||
end, get_user_present_resources(LUser, LServer));
|
end, get_user_present_resources(LUser, LServer));
|
||||||
do_route(#message{to = #jid{lresource = <<"">>}, type = T} = Packet) ->
|
do_route(#message{to = #jid{lresource = <<"">>} = To, type = T} = Packet) ->
|
||||||
?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]),
|
?DEBUG("processing message to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||||
if T == chat; T == headline; T == normal ->
|
if T == chat; T == headline; T == normal ->
|
||||||
route_message(Packet);
|
route_message(Packet);
|
||||||
true ->
|
true ->
|
||||||
Lang = xmpp:get_lang(Packet),
|
ejabberd_hooks:run_fold(bounce_sm_packet,
|
||||||
ErrTxt = <<"User session not found">>,
|
To#jid.lserver, {bounce, Packet}, [])
|
||||||
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
|
end;
|
||||||
ejabberd_router:route_error(Packet, Err)
|
do_route(#iq{to = #jid{lresource = <<"">>} = To, type = T} = Packet) ->
|
||||||
|
if T == set; T == get ->
|
||||||
|
?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]),
|
||||||
|
gen_iq_handler:handle(?MODULE, Packet);
|
||||||
|
true ->
|
||||||
|
ejabberd_hooks:run_fold(bounce_sm_packet,
|
||||||
|
To#jid.lserver, {pass, Packet}, [])
|
||||||
end;
|
end;
|
||||||
do_route(#iq{to = #jid{lresource = <<"">>}} = Packet) ->
|
|
||||||
?DEBUG("processing IQ to bare JID:~n~s", [xmpp:pp(Packet)]),
|
|
||||||
gen_iq_handler:handle(?MODULE, Packet);
|
|
||||||
do_route(Packet) ->
|
do_route(Packet) ->
|
||||||
?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]),
|
?DEBUG("processing packet to full JID:~n~s", [xmpp:pp(Packet)]),
|
||||||
To = xmpp:get_to(Packet),
|
To = xmpp:get_to(Packet),
|
||||||
@ -669,16 +685,14 @@ do_route(Packet) ->
|
|||||||
#message{type = T} when T == chat; T == normal ->
|
#message{type = T} when T == chat; T == normal ->
|
||||||
route_message(Packet);
|
route_message(Packet);
|
||||||
#message{type = T} when T == headline ->
|
#message{type = T} when T == headline ->
|
||||||
?DEBUG("dropping headline to unavailable resource:~n~s",
|
ejabberd_hooks:run_fold(bounce_sm_packet,
|
||||||
[xmpp:pp(Packet)]);
|
LServer, {pass, Packet}, []);
|
||||||
#presence{} ->
|
#presence{} ->
|
||||||
?DEBUG("dropping presence to unavailable resource:~n~s",
|
ejabberd_hooks:run_fold(bounce_sm_packet,
|
||||||
[xmpp:pp(Packet)]);
|
LServer, {pass, Packet}, []);
|
||||||
_ ->
|
_ ->
|
||||||
Lang = xmpp:get_lang(Packet),
|
ejabberd_hooks:run_fold(bounce_sm_packet,
|
||||||
ErrTxt = <<"User session not found">>,
|
LServer, {bounce, Packet}, [])
|
||||||
Err = xmpp:err_service_unavailable(ErrTxt, Lang),
|
|
||||||
ejabberd_router:route_error(Packet, Err)
|
|
||||||
end;
|
end;
|
||||||
Ss ->
|
Ss ->
|
||||||
Session = lists:max(Ss),
|
Session = lists:max(Ss),
|
||||||
|
@ -40,7 +40,8 @@
|
|||||||
muc_process_iq/2, muc_filter_message/3, message_is_archived/3,
|
muc_process_iq/2, muc_filter_message/3, message_is_archived/3,
|
||||||
delete_old_messages/2, get_commands_spec/0, msg_to_el/4,
|
delete_old_messages/2, get_commands_spec/0, msg_to_el/4,
|
||||||
get_room_config/4, set_room_option/3, offline_message/1, export/1,
|
get_room_config/4, set_room_option/3, offline_message/1, export/1,
|
||||||
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2]).
|
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2,
|
||||||
|
process_iq/3, store_mam_message/7, make_id/0]).
|
||||||
|
|
||||||
-include("xmpp.hrl").
|
-include("xmpp.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
@ -116,6 +117,8 @@ start(Host, Opts) ->
|
|||||||
get_room_config, 50),
|
get_room_config, 50),
|
||||||
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
|
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
|
||||||
set_room_option, 50),
|
set_room_option, 50),
|
||||||
|
ejabberd_hooks:add(store_mam_message, Host, ?MODULE,
|
||||||
|
store_mam_message, 100),
|
||||||
case gen_mod:get_opt(assume_mam_usage, Opts) of
|
case gen_mod:get_opt(assume_mam_usage, Opts) of
|
||||||
true ->
|
true ->
|
||||||
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
|
ejabberd_hooks:add(message_is_archived, Host, ?MODULE,
|
||||||
@ -181,6 +184,8 @@ stop(Host) ->
|
|||||||
get_room_config, 50),
|
get_room_config, 50),
|
||||||
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
|
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
|
||||||
set_room_option, 50),
|
set_room_option, 50),
|
||||||
|
ejabberd_hooks:delete(store_mam_message, Host, ?MODULE,
|
||||||
|
store_mam_message, 100),
|
||||||
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage) of
|
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage) of
|
||||||
true ->
|
true ->
|
||||||
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
|
ejabberd_hooks:delete(message_is_archived, Host, ?MODULE,
|
||||||
@ -412,6 +417,10 @@ muc_filter_message(#message{from = From} = Pkt,
|
|||||||
muc_filter_message(Acc, _MUCState, _FromNick) ->
|
muc_filter_message(Acc, _MUCState, _FromNick) ->
|
||||||
Acc.
|
Acc.
|
||||||
|
|
||||||
|
-spec make_id() -> binary().
|
||||||
|
make_id() ->
|
||||||
|
p1_time_compat:system_time(micro_seconds).
|
||||||
|
|
||||||
-spec get_stanza_id(stanza()) -> integer().
|
-spec get_stanza_id(stanza()) -> integer().
|
||||||
get_stanza_id(#message{meta = #{stanza_id := ID}}) ->
|
get_stanza_id(#message{meta = #{stanza_id := ID}}) ->
|
||||||
ID.
|
ID.
|
||||||
@ -422,7 +431,7 @@ init_stanza_id(#message{meta = #{stanza_id := _ID}} = Pkt, _LServer) ->
|
|||||||
init_stanza_id(#message{meta = #{from_offline := true}} = Pkt, _LServer) ->
|
init_stanza_id(#message{meta = #{from_offline := true}} = Pkt, _LServer) ->
|
||||||
Pkt;
|
Pkt;
|
||||||
init_stanza_id(Pkt, LServer) ->
|
init_stanza_id(Pkt, LServer) ->
|
||||||
ID = p1_time_compat:system_time(micro_seconds),
|
ID = make_id(),
|
||||||
Pkt1 = strip_my_stanza_id(Pkt, LServer),
|
Pkt1 = strip_my_stanza_id(Pkt, LServer),
|
||||||
xmpp:put_meta(Pkt1, stanza_id, ID).
|
xmpp:put_meta(Pkt1, stanza_id, ID).
|
||||||
|
|
||||||
@ -526,7 +535,7 @@ message_is_archived(false, #{lserver := LServer}, Pkt) ->
|
|||||||
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
|
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
|
||||||
TypeBin == <<"groupchat">>;
|
TypeBin == <<"groupchat">>;
|
||||||
TypeBin == <<"all">> ->
|
TypeBin == <<"all">> ->
|
||||||
CurrentTime = p1_time_compat:system_time(micro_seconds),
|
CurrentTime = make_id(),
|
||||||
Diff = Days * 24 * 60 * 60 * 1000000,
|
Diff = Days * 24 * 60 * 60 * 1000000,
|
||||||
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
|
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
|
||||||
Type = misc:binary_to_atom(TypeBin),
|
Type = misc:binary_to_atom(TypeBin),
|
||||||
@ -610,7 +619,7 @@ process_iq(LServer, #iq{from = #jid{luser = LUser}, lang = Lang,
|
|||||||
case MsgType of
|
case MsgType of
|
||||||
chat ->
|
chat ->
|
||||||
maybe_activate_mam(LUser, LServer);
|
maybe_activate_mam(LUser, LServer);
|
||||||
{groupchat, _Role, _MUCState} ->
|
_ ->
|
||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
case SubEl of
|
case SubEl of
|
||||||
@ -824,15 +833,9 @@ store_msg(Pkt, LUser, LServer, Peer, Dir) ->
|
|||||||
ok; % Already stored.
|
ok; % Already stored.
|
||||||
{true, _} ->
|
{true, _} ->
|
||||||
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
|
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
|
||||||
[LUser, LServer, Peer, chat, Dir]) of
|
[LUser, LServer, Peer, <<"">>, chat, Dir]) of
|
||||||
drop ->
|
#message{} -> ok;
|
||||||
pass;
|
_ -> pass
|
||||||
Pkt1 ->
|
|
||||||
US = {LUser, LServer},
|
|
||||||
ID = get_stanza_id(Pkt1),
|
|
||||||
El = xmpp:encode(Pkt1),
|
|
||||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
||||||
Mod:store(El, LServer, US, chat, Peer, <<"">>, Dir, ID)
|
|
||||||
end;
|
end;
|
||||||
{false, _} ->
|
{false, _} ->
|
||||||
pass
|
pass
|
||||||
@ -846,20 +849,23 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) ->
|
|||||||
{U, S, _} = jid:tolower(RoomJID),
|
{U, S, _} = jid:tolower(RoomJID),
|
||||||
LServer = MUCState#state.server_host,
|
LServer = MUCState#state.server_host,
|
||||||
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
|
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
|
||||||
[U, S, Peer, groupchat, recv]) of
|
[U, S, Peer, Nick, groupchat, recv]) of
|
||||||
drop ->
|
#message{} -> ok;
|
||||||
pass;
|
_ -> pass
|
||||||
Pkt1 ->
|
|
||||||
US = {U, S},
|
|
||||||
ID = get_stanza_id(Pkt1),
|
|
||||||
El = xmpp:encode(Pkt1),
|
|
||||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
||||||
Mod:store(El, LServer, US, groupchat, Peer, Nick, recv, ID)
|
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
pass
|
pass
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
store_mam_message(Pkt, U, S, Peer, Nick, Type, Dir) ->
|
||||||
|
LServer = ejabberd_router:host_of_route(S),
|
||||||
|
US = {U, S},
|
||||||
|
ID = get_stanza_id(Pkt),
|
||||||
|
El = xmpp:encode(Pkt),
|
||||||
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||||
|
Mod:store(El, LServer, US, Type, Peer, Nick, Dir, ID),
|
||||||
|
Pkt.
|
||||||
|
|
||||||
write_prefs(LUser, LServer, Host, Default, Always, Never) ->
|
write_prefs(LUser, LServer, Host, Default, Always, Never) ->
|
||||||
Prefs = #archive_prefs{us = {LUser, LServer},
|
Prefs = #archive_prefs{us = {LUser, LServer},
|
||||||
default = Default,
|
default = Default,
|
||||||
@ -944,7 +950,7 @@ select_and_send(LServer, Query, RSM, #iq{from = From, to = To} = IQ, MsgType) ->
|
|||||||
case MsgType of
|
case MsgType of
|
||||||
chat ->
|
chat ->
|
||||||
select(LServer, From, From, Query, RSM, MsgType);
|
select(LServer, From, From, Query, RSM, MsgType);
|
||||||
{groupchat, _Role, _MUCState} ->
|
_ ->
|
||||||
select(LServer, From, To, Query, RSM, MsgType)
|
select(LServer, From, To, Query, RSM, MsgType)
|
||||||
end,
|
end,
|
||||||
SortedMsgs = lists:keysort(2, Msgs),
|
SortedMsgs = lists:keysort(2, Msgs),
|
||||||
@ -1006,7 +1012,11 @@ msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick,
|
|||||||
CodecOpts = ejabberd_config:codec_options(LServer),
|
CodecOpts = ejabberd_config:codec_options(LServer),
|
||||||
try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of
|
try xmpp:decode(El, ?NS_CLIENT, CodecOpts) of
|
||||||
Pkt1 ->
|
Pkt1 ->
|
||||||
Pkt2 = set_stanza_id(Pkt1, JidArchive, ID),
|
Pkt2 = case MsgType of
|
||||||
|
chat -> set_stanza_id(Pkt1, JidArchive, ID);
|
||||||
|
{groupchat, _, _} -> set_stanza_id(Pkt1, JidArchive, ID);
|
||||||
|
_ -> Pkt1
|
||||||
|
end,
|
||||||
Pkt3 = maybe_update_from_to(
|
Pkt3 = maybe_update_from_to(
|
||||||
Pkt2, JidRequestor, JidArchive, Peer, MsgType, Nick),
|
Pkt2, JidRequestor, JidArchive, Peer, MsgType, Nick),
|
||||||
Delay = #delay{stamp = TS, from = jid:make(LServer)},
|
Delay = #delay{stamp = TS, from = jid:make(LServer)},
|
||||||
@ -1041,7 +1051,7 @@ maybe_update_from_to(#message{sub_els = Els} = Pkt, JidRequestor, JidArchive,
|
|||||||
Pkt#message{from = jid:replace_resource(JidArchive, Nick),
|
Pkt#message{from = jid:replace_resource(JidArchive, Nick),
|
||||||
to = undefined,
|
to = undefined,
|
||||||
sub_els = Items ++ Els};
|
sub_els = Items ++ Els};
|
||||||
maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, chat, _Nick) ->
|
maybe_update_from_to(Pkt, _JidRequestor, _JidArchive, _Peer, _MsgType, _Nick) ->
|
||||||
Pkt.
|
Pkt.
|
||||||
|
|
||||||
-spec send([{binary(), integer(), xmlel()}],
|
-spec send([{binary(), integer(), xmlel()}],
|
||||||
|
@ -175,7 +175,7 @@ select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive,
|
|||||||
MAMQuery, RSM, MsgType) ->
|
MAMQuery, RSM, MsgType) ->
|
||||||
User = case MsgType of
|
User = case MsgType of
|
||||||
chat -> LUser;
|
chat -> LUser;
|
||||||
{groupchat, _Role, _MUCState} -> jid:encode(JidArchive)
|
_ -> jid:encode(JidArchive)
|
||||||
end,
|
end,
|
||||||
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM),
|
{Query, CountQuery} = make_sql_query(User, LServer, MAMQuery, RSM),
|
||||||
% TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
|
% TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a
|
||||||
|
801
src/mod_mix.erl
801
src/mod_mix.erl
@ -21,32 +21,45 @@
|
|||||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
-module(mod_mix).
|
-module(mod_mix).
|
||||||
|
|
||||||
-behaviour(gen_server).
|
|
||||||
-behaviour(gen_mod).
|
-behaviour(gen_mod).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
-protocol({xep, 369, '0.13.0'}).
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([start/2, stop/1, process_iq/1,
|
-export([route/1]).
|
||||||
disco_items/5, disco_identity/5, disco_info/5,
|
%% gen_mod callbacks
|
||||||
disco_features/5, mod_opt_type/1, mod_options/1, depends/2]).
|
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3, format_status/2]).
|
||||||
|
%% Hooks
|
||||||
|
-export([process_disco_info/1,
|
||||||
|
process_disco_items/1,
|
||||||
|
process_mix_core/1,
|
||||||
|
process_mam_query/1,
|
||||||
|
process_pubsub_query/1]).
|
||||||
|
|
||||||
-include("logger.hrl").
|
|
||||||
-include("xmpp.hrl").
|
-include("xmpp.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("translate.hrl").
|
||||||
|
|
||||||
-define(NODES, [?NS_MIX_NODES_MESSAGES,
|
-callback init(binary(), gen_mod:opts()) -> ok | {error, db_failure}.
|
||||||
?NS_MIX_NODES_PRESENCE,
|
-callback set_channel(binary(), binary(), binary(),
|
||||||
?NS_MIX_NODES_PARTICIPANTS,
|
binary(), boolean(), binary()) ->
|
||||||
?NS_MIX_NODES_SUBJECT,
|
ok | {error, db_failure}.
|
||||||
?NS_MIX_NODES_CONFIG]).
|
-callback get_channels(binary(), binary()) ->
|
||||||
|
{ok, [binary()]} | {error, db_failure}.
|
||||||
|
-callback get_channel(binary(), binary(), binary()) ->
|
||||||
|
{ok, {jid(), boolean(), binary()}} |
|
||||||
|
{error, notfound | db_failure}.
|
||||||
|
-callback set_participant(binary(), binary(), binary(), jid(), binary(), binary()) ->
|
||||||
|
ok | {error, db_failure}.
|
||||||
|
-callback get_participant(binary(), binary(), binary(), jid()) ->
|
||||||
|
{ok, {binary(), binary()}} | {error, notfound | db_failure}.
|
||||||
|
|
||||||
-record(state, {server_host :: binary(),
|
-record(state, {hosts :: [binary()],
|
||||||
hosts :: [binary()]}).
|
server_host :: binary()}).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% API
|
%%% API
|
||||||
@ -57,267 +70,571 @@ start(Host, Opts) ->
|
|||||||
stop(Host) ->
|
stop(Host) ->
|
||||||
gen_mod:stop_child(?MODULE, Host).
|
gen_mod:stop_child(?MODULE, Host).
|
||||||
|
|
||||||
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
|
reload(Host, NewOpts, OldOpts) ->
|
||||||
jid(), jid(), binary(), binary()) -> {result, [binary()]}.
|
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||||
disco_features(_Acc, _From, _To, _Node, _Lang) ->
|
gen_server:cast(Proc, {reload, Host, NewOpts, OldOpts}).
|
||||||
{result, [?NS_MIX_0]}.
|
|
||||||
|
|
||||||
disco_items(_Acc, _From, To, _Node, _Lang) when To#jid.luser /= <<"">> ->
|
depends(_Host, _Opts) ->
|
||||||
BareTo = jid:remove_resource(To),
|
[{mod_mam, hard}].
|
||||||
{result, [#disco_item{jid = BareTo, node = Node} || Node <- ?NODES]};
|
|
||||||
disco_items(_Acc, _From, _To, _Node, _Lang) ->
|
|
||||||
{result, []}.
|
|
||||||
|
|
||||||
disco_identity(Acc, _From, To, _Node, _Lang) when To#jid.luser == <<"">> ->
|
mod_opt_type(access_create) -> fun acl:access_rules_validator/1;
|
||||||
Acc ++ [#identity{category = <<"conference">>,
|
mod_opt_type(name) -> fun iolist_to_binary/1;
|
||||||
name = <<"MIX service">>,
|
mod_opt_type(host) -> fun ejabberd_config:v_host/1;
|
||||||
type = <<"text">>}];
|
mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1;
|
||||||
disco_identity(Acc, _From, _To, _Node, _Lang) ->
|
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end.
|
||||||
Acc ++ [#identity{category = <<"conference">>,
|
|
||||||
type = <<"mix">>}].
|
|
||||||
|
|
||||||
-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
|
mod_options(Host) ->
|
||||||
([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
|
[{access_create, all},
|
||||||
disco_info(_Acc, _From, To, _Node, _Lang) when is_atom(To) ->
|
{host, <<"mix.@HOST@">>},
|
||||||
[#xdata{type = result,
|
{hosts, []},
|
||||||
fields = [#xdata_field{var = <<"FORM_TYPE">>,
|
{name, ?T("Channels")},
|
||||||
type = hidden,
|
{db_type, ejabberd_config:default_db(Host, ?MODULE)}].
|
||||||
values = [?NS_MIX_SERVICEINFO_0]}]}];
|
|
||||||
disco_info(Acc, _From, _To, _Node, _Lang) ->
|
|
||||||
Acc.
|
|
||||||
|
|
||||||
process_iq(#iq{type = set, from = From, to = To,
|
-spec route(stanza()) -> ok.
|
||||||
sub_els = [#mix_join{subscribe = SubNodes}]} = IQ) ->
|
route(#iq{} = IQ) ->
|
||||||
Nodes = [Node || Node <- SubNodes, lists:member(Node, ?NODES)],
|
ejabberd_router:process_iq(IQ);
|
||||||
case subscribe_nodes(From, To, Nodes) of
|
route(#message{type = groupchat, id = ID, lang = Lang,
|
||||||
{result, _} ->
|
to = #jid{luser = <<_, _/binary>>}} = Msg) ->
|
||||||
case publish_participant(From, To) of
|
case ID of
|
||||||
{result, _} ->
|
<<>> ->
|
||||||
BareFrom = jid:remove_resource(From),
|
Txt = <<"Attribute 'id' is mandatory for MIX messages">>,
|
||||||
xmpp:make_iq_result(
|
Err = xmpp:err_bad_request(Txt, Lang),
|
||||||
IQ, #mix_join{jid = BareFrom, subscribe = Nodes});
|
ejabberd_router:route_error(Msg, Err);
|
||||||
{error, Err} ->
|
_ ->
|
||||||
xmpp:make_error(IQ, Err)
|
process_mix_message(Msg)
|
||||||
end;
|
|
||||||
{error, Err} ->
|
|
||||||
xmpp:make_error(IQ, Err)
|
|
||||||
end;
|
end;
|
||||||
process_iq(#iq{type = set, from = From, to = To,
|
route(Pkt) ->
|
||||||
sub_els = [#mix_leave{}]} = IQ) ->
|
?DEBUG("Dropping packet:~n~s", [xmpp:pp(Pkt)]).
|
||||||
case delete_participant(From, To) of
|
|
||||||
{result, _} ->
|
-spec process_disco_info(iq()) -> iq().
|
||||||
case unsubscribe_nodes(From, To, ?NODES) of
|
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
|
||||||
{result, _} ->
|
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||||
xmpp:make_iq_result(IQ);
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||||
{error, Err} ->
|
process_disco_info(#iq{type = get, to = #jid{luser = <<>>} = To,
|
||||||
xmpp:make_error(IQ, Err)
|
from = _From, lang = Lang,
|
||||||
end;
|
sub_els = [#disco_info{node = <<>>}]} = IQ) ->
|
||||||
{error, Err} ->
|
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
|
||||||
xmpp:make_error(IQ, Err)
|
X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
|
||||||
|
[ServerHost, ?MODULE, <<"">>, Lang]),
|
||||||
|
Name = gen_mod:get_module_opt(ServerHost, ?MODULE, name),
|
||||||
|
Identity = #identity{category = <<"conference">>,
|
||||||
|
type = <<"text">>,
|
||||||
|
name = translate:translate(Lang, Name)},
|
||||||
|
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||||
|
?NS_MIX_CORE_0, ?NS_MIX_CORE_SEARCHABLE_0,
|
||||||
|
?NS_MIX_CORE_CREATE_CHANNEL_0],
|
||||||
|
xmpp:make_iq_result(
|
||||||
|
IQ, #disco_info{features = Features,
|
||||||
|
identities = [Identity],
|
||||||
|
xdata = X});
|
||||||
|
process_disco_info(#iq{type = get, to = #jid{luser = <<_, _/binary>>} = To,
|
||||||
|
sub_els = [#disco_info{node = <<"mix">>}]} = IQ) ->
|
||||||
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
|
{ok, _} ->
|
||||||
|
Identity = #identity{category = <<"conference">>,
|
||||||
|
type = <<"mix">>},
|
||||||
|
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||||
|
?NS_MIX_CORE_0, ?NS_MAM_2],
|
||||||
|
xmpp:make_iq_result(
|
||||||
|
IQ, #disco_info{node = <<"mix">>,
|
||||||
|
features = Features,
|
||||||
|
identities = [Identity]});
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
end;
|
end;
|
||||||
process_iq(#iq{lang = Lang} = IQ) ->
|
process_disco_info(#iq{type = get, sub_els = [#disco_info{node = Node}]} = IQ) ->
|
||||||
Txt = <<"Unsupported MIX query">>,
|
xmpp:make_iq_result(IQ, #disco_info{node = Node, features = [?NS_DISCO_INFO]});
|
||||||
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang)).
|
process_disco_info(IQ) ->
|
||||||
|
xmpp:make_error(IQ, unsupported_error(IQ)).
|
||||||
|
|
||||||
|
-spec process_disco_items(iq()) -> iq().
|
||||||
|
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
|
||||||
|
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
|
||||||
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||||
|
process_disco_items(#iq{type = get, to = #jid{luser = <<>>} = To,
|
||||||
|
sub_els = [#disco_items{node = <<>>}]} = IQ) ->
|
||||||
|
Host = To#jid.lserver,
|
||||||
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
case Mod:get_channels(ServerHost, Host) of
|
||||||
|
{ok, Channels} ->
|
||||||
|
Items = [#disco_item{jid = jid:make(Channel, Host)}
|
||||||
|
|| Channel <- Channels],
|
||||||
|
xmpp:make_iq_result(IQ, #disco_items{items = Items});
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
process_disco_items(#iq{type = get, to = #jid{luser = <<_, _/binary>>} = To,
|
||||||
|
sub_els = [#disco_items{node = <<"mix">>}]} = IQ) ->
|
||||||
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
|
{ok, _} ->
|
||||||
|
BTo = jid:remove_resource(To),
|
||||||
|
Items = [#disco_item{jid = BTo, node = Node} || Node <- known_nodes()],
|
||||||
|
xmpp:make_iq_result(IQ, #disco_items{node = <<"mix">>, items = Items});
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
process_disco_items(#iq{type = get, sub_els = [#disco_items{node = Node}]} = IQ) ->
|
||||||
|
xmpp:make_iq_result(IQ, #disco_items{node = Node});
|
||||||
|
process_disco_items(IQ) ->
|
||||||
|
xmpp:make_error(IQ, unsupported_error(IQ)).
|
||||||
|
|
||||||
|
-spec process_mix_core(iq()) -> iq().
|
||||||
|
process_mix_core(#iq{type = set, to = #jid{luser = <<>>},
|
||||||
|
sub_els = [#mix_create{}]} = IQ) ->
|
||||||
|
process_mix_create(IQ);
|
||||||
|
process_mix_core(#iq{type = set, to = #jid{luser = <<>>},
|
||||||
|
sub_els = [#mix_destroy{}]} = IQ) ->
|
||||||
|
process_mix_destroy(IQ);
|
||||||
|
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
|
||||||
|
sub_els = [#mix_join{}]} = IQ) ->
|
||||||
|
process_mix_join(IQ);
|
||||||
|
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
|
||||||
|
sub_els = [#mix_leave{}]} = IQ) ->
|
||||||
|
process_mix_leave(IQ);
|
||||||
|
process_mix_core(#iq{type = set, to = #jid{luser = <<_, _/binary>>},
|
||||||
|
sub_els = [#mix_setnick{}]} = IQ) ->
|
||||||
|
process_mix_setnick(IQ);
|
||||||
|
process_mix_core(IQ) ->
|
||||||
|
xmpp:make_error(IQ, unsupported_error(IQ)).
|
||||||
|
|
||||||
|
process_pubsub_query(#iq{type = get,
|
||||||
|
sub_els = [#pubsub{items = #ps_items{node = Node}}]} = IQ)
|
||||||
|
when Node == ?NS_MIX_NODES_PARTICIPANTS ->
|
||||||
|
process_participants_list(IQ);
|
||||||
|
process_pubsub_query(IQ) ->
|
||||||
|
xmpp:make_error(IQ, unsupported_error(IQ)).
|
||||||
|
|
||||||
|
process_mam_query(#iq{from = From, to = To, type = T,
|
||||||
|
sub_els = [#mam_query{}]} = IQ)
|
||||||
|
when T == get; T == set ->
|
||||||
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
|
{ok, _} ->
|
||||||
|
BFrom = jid:remove_resource(From),
|
||||||
|
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
|
||||||
|
{ok, _} ->
|
||||||
|
mod_mam:process_iq(ServerHost, IQ, mix);
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, not_joined_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
process_mam_query(IQ) ->
|
||||||
|
xmpp:make_error(IQ, unsupported_error(IQ)).
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
init([ServerHost, Opts]) ->
|
init([Host, Opts]) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts),
|
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||||
lists:foreach(
|
MyHosts = gen_mod:get_opt_hosts(Host, Opts),
|
||||||
fun(Host) ->
|
case Mod:init(Host, [{hosts, MyHosts}|Opts]) of
|
||||||
ConfigTab = gen_mod:get_module_proc(Host, config),
|
ok ->
|
||||||
ets:new(ConfigTab, [named_table]),
|
lists:foreach(
|
||||||
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
|
fun(MyHost) ->
|
||||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
|
ejabberd_router:register_route(
|
||||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
|
MyHost, Host, {apply, ?MODULE, route}),
|
||||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
register_iq_handlers(MyHost)
|
||||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
end, MyHosts),
|
||||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
{ok, #state{hosts = MyHosts, server_host = Host}};
|
||||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
{error, db_failure} ->
|
||||||
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
|
{stop, db_failure}
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
end.
|
||||||
?NS_DISCO_ITEMS, mod_disco,
|
|
||||||
process_local_iq_items),
|
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
|
||||||
?NS_DISCO_INFO, mod_disco,
|
|
||||||
process_local_iq_info),
|
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
||||||
?NS_DISCO_ITEMS, mod_disco,
|
|
||||||
process_local_iq_items),
|
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
||||||
?NS_DISCO_INFO, mod_disco,
|
|
||||||
process_local_iq_info),
|
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
||||||
?NS_PUBSUB, mod_pubsub, iq_sm),
|
|
||||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
||||||
?NS_MIX_0, ?MODULE, process_iq),
|
|
||||||
ejabberd_router:register_route(Host, ServerHost)
|
|
||||||
end, Hosts),
|
|
||||||
{ok, #state{server_host = ServerHost, hosts = Hosts}}.
|
|
||||||
|
|
||||||
handle_call(_Request, _From, State) ->
|
handle_call(Request, _From, State) ->
|
||||||
Reply = ok,
|
?WARNING_MSG("Unexpected call: ~p", [Request]),
|
||||||
{reply, Reply, State}.
|
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({route, Packet}, State) ->
|
handle_cast(Request, State) ->
|
||||||
case catch do_route(State, Packet) of
|
?WARNING_MSG("Unexpected cast: ~p", [Request]),
|
||||||
{'EXIT', _} = Err ->
|
|
||||||
try
|
|
||||||
?ERROR_MSG("failed to route packet:~n~s~nReason: ~p",
|
|
||||||
[xmpp:pp(Packet), Err]),
|
|
||||||
Error = xmpp:err_internal_server_error(),
|
|
||||||
ejabberd_router:route_error(Packet, Error)
|
|
||||||
catch _:_ ->
|
|
||||||
ok
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
{noreply, State};
|
|
||||||
handle_info(_Info, State) ->
|
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, #state{hosts = Hosts}) ->
|
handle_info(Info, State) ->
|
||||||
|
?WARNING_MSG("Unexpected info: ~p", [Info]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, State) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Host) ->
|
fun(MyHost) ->
|
||||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
|
unregister_iq_handlers(MyHost),
|
||||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
|
ejabberd_router:unregister_route(MyHost)
|
||||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
end, State#state.hosts).
|
||||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
|
||||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
|
||||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
|
||||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
|
|
||||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
|
||||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
|
||||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
|
|
||||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
|
|
||||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
|
||||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
|
|
||||||
ejabberd_router:unregister_route(Host)
|
|
||||||
end, Hosts).
|
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
|
format_status(_Opt, Status) ->
|
||||||
|
Status.
|
||||||
|
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
do_route(_State, #iq{} = Packet) ->
|
-spec process_mix_create(iq()) -> iq().
|
||||||
ejabberd_router:process_iq(Packet);
|
process_mix_create(#iq{to = To, from = From,
|
||||||
do_route(_State, #presence{from = From, to = To, type = unavailable})
|
sub_els = [#mix_create{channel = Chan}]} = IQ) ->
|
||||||
when To#jid.luser /= <<"">> ->
|
Host = To#jid.lserver,
|
||||||
delete_presence(From, To);
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
do_route(_State, _Packet) ->
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
ok.
|
Creator = jid:remove_resource(From),
|
||||||
|
Chan1 = case Chan of
|
||||||
|
<<>> -> p1_rand:get_string();
|
||||||
|
_ -> Chan
|
||||||
|
end,
|
||||||
|
Ret = case Mod:get_channel(ServerHost, Chan1, Host) of
|
||||||
|
{ok, {#jid{luser = U, lserver = S}, _, _}} ->
|
||||||
|
case {From#jid.luser, From#jid.lserver} of
|
||||||
|
{U, S} -> ok;
|
||||||
|
_ -> {error, conflict}
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
Key = xmpp_util:hex(p1_rand:bytes(20)),
|
||||||
|
Mod:set_channel(ServerHost, Chan1, Host,
|
||||||
|
Creator, Chan == <<>>, Key);
|
||||||
|
{error, db_failure} = Err ->
|
||||||
|
Err
|
||||||
|
end,
|
||||||
|
case Ret of
|
||||||
|
ok ->
|
||||||
|
xmpp:make_iq_result(IQ, #mix_create{channel = Chan1});
|
||||||
|
{error, conflict} ->
|
||||||
|
xmpp:make_error(IQ, channel_exists_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end.
|
||||||
|
|
||||||
subscribe_nodes(From, To, Nodes) ->
|
-spec process_mix_destroy(iq()) -> iq().
|
||||||
LTo = jid:tolower(jid:remove_resource(To)),
|
process_mix_destroy(#iq{to = To,
|
||||||
LFrom = jid:tolower(jid:remove_resource(From)),
|
from = #jid{luser = U, lserver = S},
|
||||||
lists:foldl(
|
sub_els = [#mix_destroy{channel = Chan}]} = IQ) ->
|
||||||
fun(_Node, {error, _} = Err) ->
|
Host = To#jid.lserver,
|
||||||
Err;
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
(Node, {result, _}) ->
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
case mod_pubsub:subscribe_node(LTo, Node, From, From, []) of
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
{error, _} = Err ->
|
{ok, {#jid{luser = U, lserver = S}, _, _}} ->
|
||||||
case is_item_not_found(Err) of
|
case Mod:del_channel(ServerHost, Chan, Host) of
|
||||||
true ->
|
ok ->
|
||||||
case mod_pubsub:create_node(
|
xmpp:make_iq_result(IQ, #mix_destroy{channel = Chan});
|
||||||
LTo, To#jid.lserver, Node, LFrom, <<"mix">>) of
|
{error, db_failure} ->
|
||||||
{result, _} ->
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
mod_pubsub:subscribe_node(LTo, Node, From, From, []);
|
end;
|
||||||
Error ->
|
{ok, _} ->
|
||||||
Error
|
xmpp:make_error(IQ, ownership_error(IQ));
|
||||||
end;
|
{error, notfound} ->
|
||||||
false ->
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
Err
|
{error, db_failure} ->
|
||||||
end;
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
{result, _} = Result ->
|
end.
|
||||||
Result
|
|
||||||
end
|
|
||||||
end, {result, []}, Nodes).
|
|
||||||
|
|
||||||
unsubscribe_nodes(From, To, Nodes) ->
|
-spec process_mix_join(iq()) -> iq().
|
||||||
LTo = jid:tolower(jid:remove_resource(To)),
|
process_mix_join(#iq{to = To, from = From,
|
||||||
BareFrom = jid:remove_resource(From),
|
sub_els = [#mix_join{} = JoinReq]} = IQ) ->
|
||||||
lists:foldl(
|
Chan = To#jid.luser,
|
||||||
fun(_Node, {error, _} = Err) ->
|
Host = To#jid.lserver,
|
||||||
Err;
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
(Node, {result, _} = Result) ->
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
case mod_pubsub:unsubscribe_node(LTo, Node, From, BareFrom, <<"">>) of
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
{error, _} = Err ->
|
{ok, {_, _, Key}} ->
|
||||||
case is_not_subscribed(Err) of
|
ID = make_id(From, Key),
|
||||||
true -> Result;
|
Nick = JoinReq#mix_join.nick,
|
||||||
_ -> Err
|
BFrom = jid:remove_resource(From),
|
||||||
end;
|
Nodes = filter_nodes(JoinReq#mix_join.subscribe),
|
||||||
{result, _} = Res ->
|
try
|
||||||
Res
|
ok = Mod:set_participant(ServerHost, Chan, Host, BFrom, ID, Nick),
|
||||||
end
|
ok = Mod:subscribe(ServerHost, Chan, Host, BFrom, Nodes),
|
||||||
end, {result, []}, Nodes).
|
notify_participant_joined(Mod, ServerHost, To, From, ID, Nick),
|
||||||
|
xmpp:make_iq_result(IQ, #mix_join{id = ID,
|
||||||
|
subscribe = Nodes,
|
||||||
|
nick = Nick})
|
||||||
|
catch _:{badmatch, {error, db_failure}} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end.
|
||||||
|
|
||||||
publish_participant(From, To) ->
|
-spec process_mix_leave(iq()) -> iq().
|
||||||
BareFrom = jid:remove_resource(From),
|
process_mix_leave(#iq{to = To, from = From,
|
||||||
LFrom = jid:tolower(BareFrom),
|
sub_els = [#mix_leave{}]} = IQ) ->
|
||||||
LTo = jid:tolower(jid:remove_resource(To)),
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
Participant = #mix_participant{jid = BareFrom},
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
ItemID = str:sha(jid:encode(LFrom)),
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
mod_pubsub:publish_item(
|
BFrom = jid:remove_resource(From),
|
||||||
LTo, To#jid.lserver, ?NS_MIX_NODES_PARTICIPANTS,
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
From, ItemID, [xmpp:encode(Participant)]).
|
{ok, _} ->
|
||||||
|
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
|
||||||
|
{ok, {ID, _}} ->
|
||||||
|
try
|
||||||
|
ok = Mod:unsubscribe(ServerHost, Chan, Host, BFrom),
|
||||||
|
ok = Mod:del_participant(ServerHost, Chan, Host, BFrom),
|
||||||
|
notify_participant_left(Mod, ServerHost, To, ID),
|
||||||
|
xmpp:make_iq_result(IQ, #mix_leave{})
|
||||||
|
catch _:{badmatch, {error, db_failure}} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_iq_result(IQ, #mix_leave{});
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_iq_result(IQ, #mix_leave{});
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end.
|
||||||
|
|
||||||
delete_presence(From, To) ->
|
-spec process_mix_setnick(iq()) -> iq().
|
||||||
LFrom = jid:tolower(From),
|
process_mix_setnick(#iq{to = To, from = From,
|
||||||
LTo = jid:tolower(jid:remove_resource(To)),
|
sub_els = [#mix_setnick{nick = Nick}]} = IQ) ->
|
||||||
case mod_pubsub:get_items(LTo, ?NS_MIX_NODES_PRESENCE) of
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
Items when is_list(Items) ->
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
BFrom = jid:remove_resource(From),
|
||||||
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
|
{ok, _} ->
|
||||||
|
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
|
||||||
|
{ok, {_, Nick}} ->
|
||||||
|
xmpp:make_iq_result(IQ, #mix_setnick{nick = Nick});
|
||||||
|
{ok, {ID, _}} ->
|
||||||
|
case Mod:set_participant(ServerHost, Chan, Host, BFrom, ID, Nick) of
|
||||||
|
ok ->
|
||||||
|
notify_participant_joined(Mod, ServerHost, To, From, ID, Nick),
|
||||||
|
xmpp:make_iq_result(IQ, #mix_setnick{nick = Nick});
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, not_joined_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec process_mix_message(message()) -> ok.
|
||||||
|
process_mix_message(#message{from = From, to = To,
|
||||||
|
id = SubmissionID} = Msg) ->
|
||||||
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
|
{ok, _} ->
|
||||||
|
BFrom = jid:remove_resource(From),
|
||||||
|
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
|
||||||
|
{ok, {_ID, Nick}} ->
|
||||||
|
MamID = mod_mam:make_id(),
|
||||||
|
Msg1 = xmpp:put_meta(
|
||||||
|
xmpp:set_subtag(
|
||||||
|
Msg#message{from = jid:remove_resource(To),
|
||||||
|
to = undefined,
|
||||||
|
id = integer_to_binary(MamID)},
|
||||||
|
#mix{jid = BFrom,
|
||||||
|
nick = Nick,
|
||||||
|
submission_id = SubmissionID}),
|
||||||
|
stanza_id, MamID),
|
||||||
|
case ejabberd_hooks:run_fold(
|
||||||
|
store_mam_message, ServerHost, Msg1,
|
||||||
|
[Chan, Host, BFrom, Nick, groupchat, recv]) of
|
||||||
|
#message{} = Msg2 ->
|
||||||
|
multicast(Mod, ServerHost, Chan, Host,
|
||||||
|
?NS_MIX_NODES_MESSAGES, Msg2);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
ejabberd_router:route_error(Msg, not_joined_error(Msg));
|
||||||
|
{error, db_failure} ->
|
||||||
|
ejabberd_router:route_error(Msg, db_error(Msg))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
ejabberd_router:route_error(Msg, no_channel_error(Msg));
|
||||||
|
{error, db_failure} ->
|
||||||
|
ejabberd_router:route_error(Msg, db_error(Msg))
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec process_participants_list(iq()) -> iq().
|
||||||
|
process_participants_list(#iq{from = From, to = To} = IQ) ->
|
||||||
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
|
ServerHost = ejabberd_router:host_of_route(Host),
|
||||||
|
Mod = gen_mod:db_mod(ServerHost, ?MODULE),
|
||||||
|
case Mod:get_channel(ServerHost, Chan, Host) of
|
||||||
|
{ok, _} ->
|
||||||
|
BFrom = jid:remove_resource(From),
|
||||||
|
case Mod:get_participant(ServerHost, Chan, Host, BFrom) of
|
||||||
|
{ok, _} ->
|
||||||
|
case Mod:get_participants(ServerHost, Chan, Host) of
|
||||||
|
{ok, Participants} ->
|
||||||
|
Items = items_of_participants(Participants),
|
||||||
|
Pubsub = #pubsub{
|
||||||
|
items = #ps_items{
|
||||||
|
node = ?NS_MIX_NODES_PARTICIPANTS,
|
||||||
|
items = Items}},
|
||||||
|
xmpp:make_iq_result(IQ, Pubsub);
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, not_joined_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
{error, notfound} ->
|
||||||
|
xmpp:make_error(IQ, no_channel_error(IQ));
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec items_of_participants([{jid(), binary(), binary()}]) -> [ps_item()].
|
||||||
|
items_of_participants(Participants) ->
|
||||||
|
lists:map(
|
||||||
|
fun({JID, ID, Nick}) ->
|
||||||
|
Participant = #mix_participant{jid = JID, nick = Nick},
|
||||||
|
#ps_item{id = ID,
|
||||||
|
sub_els = [xmpp:encode(Participant)]}
|
||||||
|
end, Participants).
|
||||||
|
|
||||||
|
-spec known_nodes() -> [binary()].
|
||||||
|
known_nodes() ->
|
||||||
|
[?NS_MIX_NODES_MESSAGES,
|
||||||
|
?NS_MIX_NODES_PARTICIPANTS].
|
||||||
|
|
||||||
|
-spec filter_nodes(binary()) -> [binary()].
|
||||||
|
filter_nodes(Nodes) ->
|
||||||
|
lists:filter(
|
||||||
|
fun(Node) ->
|
||||||
|
lists:member(Node, Nodes)
|
||||||
|
end, known_nodes()).
|
||||||
|
|
||||||
|
-spec multicast(module(), binary(), binary(),
|
||||||
|
binary(), binary(), message()) -> ok.
|
||||||
|
multicast(Mod, LServer, Chan, Service, Node, Msg) ->
|
||||||
|
case Mod:get_subscribed(LServer, Chan, Service, Node) of
|
||||||
|
{ok, Subscribers} ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({pubsub_item, {ItemID, _}, _, {_, LJID}, _})
|
fun(To) ->
|
||||||
when LJID == LFrom ->
|
ejabberd_router:route(Msg#message{to = To})
|
||||||
delete_item(From, To, ?NS_MIX_NODES_PRESENCE, ItemID);
|
end, Subscribers);
|
||||||
(_) ->
|
{error, db_failure} ->
|
||||||
ok
|
|
||||||
end, Items);
|
|
||||||
_ ->
|
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
delete_participant(From, To) ->
|
-spec notify_participant_joined(module(), binary(),
|
||||||
LFrom = jid:tolower(jid:remove_resource(From)),
|
jid(), jid(), binary(), binary()) -> ok.
|
||||||
ItemID = str:sha(jid:encode(LFrom)),
|
notify_participant_joined(Mod, LServer, To, From, ID, Nick) ->
|
||||||
delete_presence(From, To),
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
delete_item(From, To, ?NS_MIX_NODES_PARTICIPANTS, ItemID).
|
Participant = #mix_participant{jid = jid:remove_resource(From),
|
||||||
|
nick = Nick},
|
||||||
|
Item = #ps_item{id = ID,
|
||||||
|
sub_els = [xmpp:encode(Participant)]},
|
||||||
|
Items = #ps_items{node = ?NS_MIX_NODES_PARTICIPANTS,
|
||||||
|
items = [Item]},
|
||||||
|
Event = #ps_event{items = Items},
|
||||||
|
Msg = #message{from = jid:remove_resource(To),
|
||||||
|
id = p1_rand:get_string(),
|
||||||
|
sub_els = [Event]},
|
||||||
|
multicast(Mod, LServer, Chan, Host, ?NS_MIX_NODES_PARTICIPANTS, Msg).
|
||||||
|
|
||||||
delete_item(From, To, Node, ItemID) ->
|
-spec notify_participant_left(module(), binary(), jid(), binary()) -> ok.
|
||||||
LTo = jid:tolower(jid:remove_resource(To)),
|
notify_participant_left(Mod, LServer, To, ID) ->
|
||||||
case mod_pubsub:delete_item(
|
{Chan, Host, _} = jid:tolower(To),
|
||||||
LTo, Node, From, ItemID, true) of
|
Items = #ps_items{node = ?NS_MIX_NODES_PARTICIPANTS,
|
||||||
{result, _} = Res ->
|
retract = ID},
|
||||||
Res;
|
Event = #ps_event{items = Items},
|
||||||
{error, _} = Err ->
|
Msg = #message{from = jid:remove_resource(To),
|
||||||
case is_item_not_found(Err) of
|
id = p1_rand:get_string(),
|
||||||
true -> {result, []};
|
sub_els = [Event]},
|
||||||
false -> Err
|
multicast(Mod, LServer, Chan, Host, ?NS_MIX_NODES_PARTICIPANTS, Msg).
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec is_item_not_found({error, stanza_error()}) -> boolean().
|
-spec make_id(jid(), binary()) -> binary().
|
||||||
is_item_not_found({error, #stanza_error{reason = 'item-not-found'}}) -> true;
|
make_id(JID, Key) ->
|
||||||
is_item_not_found({error, _}) -> false.
|
Data = jid:encode(jid:tolower(jid:remove_resource(JID))),
|
||||||
|
xmpp_util:hex(crypto:hmac(sha256, Data, Key, 10)).
|
||||||
|
|
||||||
-spec is_not_subscribed({error, stanza_error()}) -> boolean().
|
%%%===================================================================
|
||||||
is_not_subscribed({error, StanzaError}) ->
|
%%% Error generators
|
||||||
xmpp:has_subtag(StanzaError, #ps_error{type = 'not-subscribed'}).
|
%%%===================================================================
|
||||||
|
-spec db_error(stanza()) -> stanza_error().
|
||||||
|
db_error(Pkt) ->
|
||||||
|
Txt = <<"Database failure">>,
|
||||||
|
xmpp:err_internal_server_error(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
depends(_Host, _Opts) ->
|
-spec channel_exists_error(stanza()) -> stanza_error().
|
||||||
[{mod_pubsub, hard}].
|
channel_exists_error(Pkt) ->
|
||||||
|
Txt = <<"Channel already exists">>,
|
||||||
|
xmpp:err_conflict(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
mod_opt_type(host) -> fun ejabberd_config:v_host/1;
|
-spec no_channel_error(stanza()) -> stanza_error().
|
||||||
mod_opt_type(hosts) -> fun ejabberd_config:v_hosts/1.
|
no_channel_error(Pkt) ->
|
||||||
|
Txt = <<"Channel does not exist">>,
|
||||||
|
xmpp:err_item_not_found(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
mod_options(_Host) ->
|
-spec not_joined_error(stanza()) -> stanza_error().
|
||||||
[{host, <<"mix.@HOST@">>},
|
not_joined_error(Pkt) ->
|
||||||
{hosts, []}].
|
Txt = <<"You are not joined to the conference">>,
|
||||||
|
xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
-spec unsupported_error(stanza()) -> stanza_error().
|
||||||
|
unsupported_error(Pkt) ->
|
||||||
|
Txt = <<"No module is handling this query">>,
|
||||||
|
xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
-spec ownership_error(stanza()) -> stanza_error().
|
||||||
|
ownership_error(Pkt) ->
|
||||||
|
Txt = <<"Owner privileges required">>,
|
||||||
|
xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% IQ handlers
|
||||||
|
%%%===================================================================
|
||||||
|
-spec register_iq_handlers(binary()) -> ok.
|
||||||
|
register_iq_handlers(Host) ->
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||||
|
?MODULE, process_disco_info),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||||
|
?MODULE, process_disco_items),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_0,
|
||||||
|
?MODULE, process_mix_core),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO,
|
||||||
|
?MODULE, process_disco_info),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS,
|
||||||
|
?MODULE, process_disco_items),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_0,
|
||||||
|
?MODULE, process_mix_core),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB,
|
||||||
|
?MODULE, process_pubsub_query),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MAM_2,
|
||||||
|
?MODULE, process_mam_query).
|
||||||
|
|
||||||
|
-spec unregister_iq_handlers(binary()) -> ok.
|
||||||
|
unregister_iq_handlers(Host) ->
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MIX_CORE_0),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_CORE_0),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MAM_2).
|
||||||
|
189
src/mod_mix_mnesia.erl
Normal file
189
src/mod_mix_mnesia.erl
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% Created : 1 Dec 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
-module(mod_mix_mnesia).
|
||||||
|
-behaviour(mod_mix).
|
||||||
|
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([init/2]).
|
||||||
|
-export([set_channel/6, get_channels/2, get_channel/3, del_channel/3]).
|
||||||
|
-export([set_participant/6, get_participant/4, get_participants/3, del_participant/4]).
|
||||||
|
-export([subscribe/5, unsubscribe/4, unsubscribe/5, get_subscribed/4]).
|
||||||
|
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("ejabberd_sql_pt.hrl").
|
||||||
|
|
||||||
|
-record(mix_channel,
|
||||||
|
{chan_serv :: {binary(), binary()},
|
||||||
|
service :: binary(),
|
||||||
|
creator :: jid:jid(),
|
||||||
|
hidden :: boolean(),
|
||||||
|
hmac_key :: binary(),
|
||||||
|
created_at :: erlang:timestamp()}).
|
||||||
|
|
||||||
|
-record(mix_participant,
|
||||||
|
{user_chan :: {binary(), binary(), binary(), binary()},
|
||||||
|
chan_serv :: {binary(), binary()},
|
||||||
|
jid :: jid:jid(),
|
||||||
|
id :: binary(),
|
||||||
|
nick :: binary(),
|
||||||
|
created_at :: erlang:timestamp()}).
|
||||||
|
|
||||||
|
-record(mix_subscription,
|
||||||
|
{user_chan_node :: {binary(), binary(), binary(), binary(), binary()},
|
||||||
|
user_chan :: {binary(), binary(), binary(), binary()},
|
||||||
|
chan_serv_node :: {binary(), binary(), binary()},
|
||||||
|
chan_serv :: {binary(), binary()},
|
||||||
|
jid :: jid:jid()}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
init(_Host, _Opts) ->
|
||||||
|
try
|
||||||
|
{atomic, _} = ejabberd_mnesia:create(
|
||||||
|
?MODULE, mix_channel,
|
||||||
|
[{disc_only_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, mix_channel)},
|
||||||
|
{index, [service]}]),
|
||||||
|
{atomic, _} = ejabberd_mnesia:create(
|
||||||
|
?MODULE, mix_participant,
|
||||||
|
[{disc_only_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, mix_participant)},
|
||||||
|
{index, [chan_serv]}]),
|
||||||
|
{atomic, _} = ejabberd_mnesia:create(
|
||||||
|
?MODULE, mix_subscription,
|
||||||
|
[{disc_only_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, mix_subscription)},
|
||||||
|
{index, [user_chan, chan_serv_node, chan_serv]}]),
|
||||||
|
ok
|
||||||
|
catch _:{badmatch, _} ->
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
set_channel(_LServer, Channel, Service, CreatorJID, Hidden, Key) ->
|
||||||
|
mnesia:dirty_write(
|
||||||
|
#mix_channel{chan_serv = {Channel, Service},
|
||||||
|
service = Service,
|
||||||
|
creator = jid:remove_resource(CreatorJID),
|
||||||
|
hidden = Hidden,
|
||||||
|
hmac_key = Key,
|
||||||
|
created_at = p1_time_compat:timestamp()}).
|
||||||
|
|
||||||
|
get_channels(_LServer, Service) ->
|
||||||
|
Ret = mnesia:dirty_index_read(mix_channel, Service, #mix_channel.service),
|
||||||
|
{ok, lists:filtermap(
|
||||||
|
fun(#mix_channel{chan_serv = {Channel, _},
|
||||||
|
hidden = false}) ->
|
||||||
|
{true, Channel};
|
||||||
|
(_) ->
|
||||||
|
false
|
||||||
|
end, Ret)}.
|
||||||
|
|
||||||
|
get_channel(_LServer, Channel, Service) ->
|
||||||
|
case mnesia:dirty_read(mix_channel, {Channel, Service}) of
|
||||||
|
[#mix_channel{creator = JID,
|
||||||
|
hidden = Hidden,
|
||||||
|
hmac_key = Key}] ->
|
||||||
|
{ok, {JID, Hidden, Key}};
|
||||||
|
[] ->
|
||||||
|
{error, notfound}
|
||||||
|
end.
|
||||||
|
|
||||||
|
del_channel(_LServer, Channel, Service) ->
|
||||||
|
Key = {Channel, Service},
|
||||||
|
L1 = mnesia:dirty_read(mix_channel, Key),
|
||||||
|
L2 = mnesia:dirty_index_read(mix_participant, Key,
|
||||||
|
#mix_participant.chan_serv),
|
||||||
|
L3 = mnesia:dirty_index_read(mix_subscription, Key,
|
||||||
|
#mix_subscription.chan_serv),
|
||||||
|
lists:foreach(fun mnesia:dirty_delete_object/1, L1++L2++L3).
|
||||||
|
|
||||||
|
set_participant(_LServer, Channel, Service, JID, ID, Nick) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
mnesia:dirty_write(
|
||||||
|
#mix_participant{
|
||||||
|
user_chan = {User, Domain, Channel, Service},
|
||||||
|
chan_serv = {Channel, Service},
|
||||||
|
jid = jid:remove_resource(JID),
|
||||||
|
id = ID,
|
||||||
|
nick = Nick,
|
||||||
|
created_at = p1_time_compat:timestamp()}).
|
||||||
|
|
||||||
|
get_participant(_LServer, Channel, Service, JID) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
case mnesia:dirty_read(mix_participant, {User, Domain, Channel, Service}) of
|
||||||
|
[#mix_participant{id = ID, nick = Nick}] -> {ok, {ID, Nick}};
|
||||||
|
[] -> {error, notfound}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_participants(_LServer, Channel, Service) ->
|
||||||
|
Ret = mnesia:dirty_index_read(mix_participant,
|
||||||
|
{Channel, Service},
|
||||||
|
#mix_participant.chan_serv),
|
||||||
|
{ok, lists:map(
|
||||||
|
fun(#mix_participant{jid = JID, id = ID, nick = Nick}) ->
|
||||||
|
{ok, {JID, ID, Nick}}
|
||||||
|
end, Ret)}.
|
||||||
|
|
||||||
|
del_participant(_LServer, Channel, Service, JID) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
mnesia:dirty_delete(mix_participant, {User, Domain, Channel, Service}).
|
||||||
|
|
||||||
|
subscribe(_LServer, Channel, Service, JID, Nodes) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
BJID = jid:remove_resource(JID),
|
||||||
|
lists:foreach(
|
||||||
|
fun(Node) ->
|
||||||
|
mnesia:dirty_write(
|
||||||
|
#mix_subscription{
|
||||||
|
user_chan_node = {User, Domain, Channel, Service, Node},
|
||||||
|
user_chan = {User, Domain, Channel, Service},
|
||||||
|
chan_serv_node = {Channel, Service, Node},
|
||||||
|
chan_serv = {Channel, Service},
|
||||||
|
jid = BJID})
|
||||||
|
end, Nodes).
|
||||||
|
|
||||||
|
get_subscribed(_LServer, Channel, Service, Node) ->
|
||||||
|
Ret = mnesia:dirty_index_read(mix_subscription,
|
||||||
|
{Channel, Service, Node},
|
||||||
|
#mix_subscription.chan_serv_node),
|
||||||
|
{ok, [JID || #mix_subscription{jid = JID} <- Ret]}.
|
||||||
|
|
||||||
|
unsubscribe(_LServer, Channel, Service, JID) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
Ret = mnesia:dirty_index_read(mix_subscription,
|
||||||
|
{User, Domain, Channel, Service},
|
||||||
|
#mix_subscription.user_chan),
|
||||||
|
lists:foreach(fun mnesia:dirty_delete_object/1, Ret).
|
||||||
|
|
||||||
|
unsubscribe(_LServer, Channel, Service, JID, Nodes) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
lists:foreach(
|
||||||
|
fun(Node) ->
|
||||||
|
mnesia:dirty_delete(mix_subscription,
|
||||||
|
{User, Domain, Channel, Service, Node})
|
||||||
|
end, Nodes).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
365
src/mod_mix_pam.erl
Normal file
365
src/mod_mix_pam.erl
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% Created : 4 Dec 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
-module(mod_mix_pam).
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
-protocol({xep, 405, '0.2.1'}).
|
||||||
|
|
||||||
|
%% gen_mod callbacks
|
||||||
|
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1, mod_options/1]).
|
||||||
|
%% Hooks and handlers
|
||||||
|
-export([bounce_sm_packet/1,
|
||||||
|
disco_sm_features/5,
|
||||||
|
remove_user/2,
|
||||||
|
process_iq/1]).
|
||||||
|
|
||||||
|
-include("xmpp.hrl").
|
||||||
|
-include("logger.hrl").
|
||||||
|
|
||||||
|
-define(MIX_PAM_CACHE, mix_pam_cache).
|
||||||
|
|
||||||
|
-callback init(binary(), gen_mod:opts()) -> ok | {error, db_failure}.
|
||||||
|
-callback add_channel(jid(), jid(), binary()) -> ok | {error, db_failure}.
|
||||||
|
-callback del_channel(jid(), jid()) -> ok | {error, db_failure}.
|
||||||
|
-callback get_channel(jid(), jid()) -> {ok, binary()} | {error, notfound | db_failure}.
|
||||||
|
-callback get_channels(jid()) -> {ok, [{jid(), binary()}]} | {error, db_failure}.
|
||||||
|
-callback del_channels(jid()) -> ok | {error, db_failure}.
|
||||||
|
-callback use_cache(binary()) -> boolean().
|
||||||
|
-callback cache_nodes(binary()) -> [node()].
|
||||||
|
|
||||||
|
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
start(Host, Opts) ->
|
||||||
|
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||||
|
case Mod:init(Host, Opts) of
|
||||||
|
ok ->
|
||||||
|
init_cache(Mod, Host, Opts),
|
||||||
|
ejabberd_hooks:add(bounce_sm_packet, Host, ?MODULE, bounce_sm_packet, 50),
|
||||||
|
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_sm_features, 50),
|
||||||
|
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0,
|
||||||
|
?MODULE, process_iq);
|
||||||
|
Err ->
|
||||||
|
Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
stop(Host) ->
|
||||||
|
ejabberd_hooks:delete(bounce_sm_packet, Host, ?MODULE, bounce_sm_packet, 50),
|
||||||
|
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_sm_features, 50),
|
||||||
|
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_PAM_0).
|
||||||
|
|
||||||
|
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,
|
||||||
|
init_cache(NewMod, Host, NewOpts).
|
||||||
|
|
||||||
|
depends(_Host, _Opts) ->
|
||||||
|
[].
|
||||||
|
|
||||||
|
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) 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_options(Host) ->
|
||||||
|
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
|
||||||
|
{use_cache, ejabberd_config:use_cache(Host)},
|
||||||
|
{cache_size, ejabberd_config:cache_size(Host)},
|
||||||
|
{cache_missed, ejabberd_config:cache_missed(Host)},
|
||||||
|
{cache_life_time, ejabberd_config:cache_life_time(Host)}].
|
||||||
|
|
||||||
|
-spec bounce_sm_packet({term(), stanza()}) -> {term(), stanza()}.
|
||||||
|
bounce_sm_packet({_, #message{to = #jid{lresource = <<>>} = To,
|
||||||
|
from = From,
|
||||||
|
type = groupchat} = Msg} = Acc) ->
|
||||||
|
case xmpp:has_subtag(Msg, #mix{}) of
|
||||||
|
true ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(To),
|
||||||
|
case get_channel(To, From) of
|
||||||
|
{ok, _} ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(R) ->
|
||||||
|
To1 = jid:replace_resource(To, R),
|
||||||
|
ejabberd_router:route(xmpp:set_to(Msg, To1))
|
||||||
|
end, ejabberd_sm:get_user_resources(LUser, LServer)),
|
||||||
|
{pass, Msg};
|
||||||
|
_ ->
|
||||||
|
Acc
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end;
|
||||||
|
bounce_sm_packet(Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
-spec disco_sm_features({error, stanza_error()} | empty | {result, [binary()]},
|
||||||
|
jid(), jid(), binary(), binary()) ->
|
||||||
|
{error, stanza_error()} | empty | {result, [binary()]}.
|
||||||
|
disco_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc;
|
||||||
|
disco_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
|
||||||
|
{result, [?NS_MIX_PAM_0 |
|
||||||
|
case Acc of
|
||||||
|
{result, Features} -> Features;
|
||||||
|
empty -> []
|
||||||
|
end]};
|
||||||
|
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
-spec process_iq(iq()) -> iq() | ignore.
|
||||||
|
process_iq(#iq{from = #jid{luser = U1, lserver = S1},
|
||||||
|
to = #jid{luser = U2, lserver = S2}} = IQ)
|
||||||
|
when {U1, S1} /= {U2, S2} ->
|
||||||
|
xmpp:make_error(IQ, forbidden_query_error(IQ));
|
||||||
|
process_iq(#iq{type = set,
|
||||||
|
sub_els = [#mix_client_join{} = Join]} = IQ) ->
|
||||||
|
case Join#mix_client_join.channel of
|
||||||
|
undefined ->
|
||||||
|
xmpp:make_error(IQ, missing_channel_error(IQ));
|
||||||
|
_ ->
|
||||||
|
process_join(IQ)
|
||||||
|
end;
|
||||||
|
process_iq(#iq{type = set,
|
||||||
|
sub_els = [#mix_client_leave{} = Leave]} = IQ) ->
|
||||||
|
case Leave#mix_client_leave.channel of
|
||||||
|
undefined ->
|
||||||
|
xmpp:make_error(IQ, missing_channel_error(IQ));
|
||||||
|
_ ->
|
||||||
|
process_leave(IQ)
|
||||||
|
end;
|
||||||
|
process_iq(IQ) ->
|
||||||
|
xmpp:make_error(IQ, unsupported_query_error(IQ)).
|
||||||
|
|
||||||
|
-spec remove_user(binary(), binary()) -> ok | {error, db_failure}.
|
||||||
|
remove_user(LUser, LServer) ->
|
||||||
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||||
|
JID = jid:make(LUser, LServer),
|
||||||
|
Chans = case Mod:get_channels(JID) of
|
||||||
|
{ok, Channels} ->
|
||||||
|
lists:map(
|
||||||
|
fun({Channel, _}) ->
|
||||||
|
ejabberd_router:route(
|
||||||
|
#iq{from = JID,
|
||||||
|
to = Channel,
|
||||||
|
id = p1_rand:get_string(),
|
||||||
|
type = set,
|
||||||
|
sub_els = [#mix_leave{}]}),
|
||||||
|
Channel
|
||||||
|
end, Channels);
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end,
|
||||||
|
Mod:del_channels(jid:make(LUser, LServer)),
|
||||||
|
lists:foreach(
|
||||||
|
fun(Chan) ->
|
||||||
|
delete_cache(Mod, JID, Chan)
|
||||||
|
end, Chans).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
-spec process_join(iq()) -> ignore.
|
||||||
|
process_join(#iq{from = From,
|
||||||
|
sub_els = [#mix_client_join{channel = Channel,
|
||||||
|
join = Join}]} = IQ) ->
|
||||||
|
ejabberd_router:route_iq(
|
||||||
|
#iq{from = jid:remove_resource(From),
|
||||||
|
to = Channel, type = set, sub_els = [Join]},
|
||||||
|
fun(ResIQ) -> process_join_result(ResIQ, IQ) end),
|
||||||
|
ignore.
|
||||||
|
|
||||||
|
-spec process_leave(iq()) -> iq() | error.
|
||||||
|
process_leave(#iq{from = From,
|
||||||
|
sub_els = [#mix_client_leave{channel = Channel,
|
||||||
|
leave = Leave}]} = IQ) ->
|
||||||
|
case del_channel(From, Channel) of
|
||||||
|
ok ->
|
||||||
|
ejabberd_router:route_iq(
|
||||||
|
#iq{from = jid:remove_resource(From),
|
||||||
|
to = Channel, type = set, sub_els = [Leave]},
|
||||||
|
fun(ResIQ) -> process_leave_result(ResIQ, IQ) end),
|
||||||
|
ignore;
|
||||||
|
{error, db_failure} ->
|
||||||
|
xmpp:make_error(IQ, db_error(IQ))
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec process_join_result(iq(), iq()) -> ok.
|
||||||
|
process_join_result(#iq{from = Channel,
|
||||||
|
type = result, sub_els = [#mix_join{id = ID} = Join]},
|
||||||
|
#iq{to = To} = IQ) ->
|
||||||
|
case add_channel(To, Channel, ID) of
|
||||||
|
ok ->
|
||||||
|
ChanID = make_channel_id(Channel, ID),
|
||||||
|
Join1 = Join#mix_join{id = <<"">>, jid = ChanID},
|
||||||
|
ResIQ = xmpp:make_iq_result(IQ, #mix_client_join{join = Join1}),
|
||||||
|
ejabberd_router:route(ResIQ);
|
||||||
|
{error, db_failure} ->
|
||||||
|
ejabberd_router:route_error(IQ, db_error(IQ))
|
||||||
|
end;
|
||||||
|
process_join_result(Err, IQ) ->
|
||||||
|
process_iq_error(Err, IQ).
|
||||||
|
|
||||||
|
-spec process_leave_result(iq(), iq()) -> ok.
|
||||||
|
process_leave_result(#iq{type = result, sub_els = [#mix_leave{} = Leave]}, IQ) ->
|
||||||
|
ResIQ = xmpp:make_iq_result(IQ, #mix_client_leave{leave = Leave}),
|
||||||
|
ejabberd_router:route(ResIQ);
|
||||||
|
process_leave_result(Err, IQ) ->
|
||||||
|
process_iq_error(Err, IQ).
|
||||||
|
|
||||||
|
-spec process_iq_error(iq(), iq()) -> ok.
|
||||||
|
process_iq_error(#iq{type = error} = ErrIQ, #iq{sub_els = [El]} = IQ) ->
|
||||||
|
case xmpp:get_error(ErrIQ) of
|
||||||
|
undefined ->
|
||||||
|
%% Not sure if this stuff is correct because
|
||||||
|
%% RFC6120 section 8.3.1 bullet 4 states that
|
||||||
|
%% an error stanza MUST contain an <error/> child element
|
||||||
|
IQ1 = xmpp:make_iq_result(IQ, El),
|
||||||
|
ejabberd_router:route(IQ1#iq{type = error});
|
||||||
|
Err ->
|
||||||
|
ejabberd_router:route_error(IQ, Err)
|
||||||
|
end;
|
||||||
|
process_iq_error(timeout, IQ) ->
|
||||||
|
Txt = <<"Request has timed out">>,
|
||||||
|
Err = xmpp:err_recipient_unavailable(Txt, IQ#iq.lang),
|
||||||
|
ejabberd_router:route_error(IQ, Err).
|
||||||
|
|
||||||
|
-spec make_channel_id(jid(), binary()) -> jid().
|
||||||
|
make_channel_id(JID, ID) ->
|
||||||
|
{U, S, R} = jid:split(JID),
|
||||||
|
jid:make(<<ID/binary, $#, U/binary>>, S, R).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Error generators
|
||||||
|
%%%===================================================================
|
||||||
|
-spec missing_channel_error(stanza()) -> stanza_error().
|
||||||
|
missing_channel_error(Pkt) ->
|
||||||
|
Txt = <<"Attribute 'channel' is required for this request">>,
|
||||||
|
xmpp:err_bad_request(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
-spec forbidden_query_error(stanza()) -> stanza_error().
|
||||||
|
forbidden_query_error(Pkt) ->
|
||||||
|
Txt = <<"Query to another users is forbidden">>,
|
||||||
|
xmpp:err_forbidden(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
-spec unsupported_query_error(stanza()) -> stanza_error().
|
||||||
|
unsupported_query_error(Pkt) ->
|
||||||
|
Txt = <<"No module is handling this query">>,
|
||||||
|
xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
-spec db_error(stanza()) -> stanza_error().
|
||||||
|
db_error(Pkt) ->
|
||||||
|
Txt = <<"Database failure">>,
|
||||||
|
xmpp:err_internal_server_error(Txt, xmpp:get_lang(Pkt)).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Database queries
|
||||||
|
%%%===================================================================
|
||||||
|
get_channel(JID, Channel) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(JID),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||||
|
case use_cache(Mod, LServer) of
|
||||||
|
false -> Mod:get_channel(JID, Channel);
|
||||||
|
true ->
|
||||||
|
case ets_cache:lookup(
|
||||||
|
?MIX_PAM_CACHE, {LUser, LServer, Chan, Service},
|
||||||
|
fun() -> Mod:get_channel(JID, Channel) end) of
|
||||||
|
error -> {error, notfound};
|
||||||
|
Ret -> Ret
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
add_channel(JID, Channel, ID) ->
|
||||||
|
Mod = gen_mod:db_mod(JID#jid.lserver, ?MODULE),
|
||||||
|
case Mod:add_channel(JID, Channel, ID) of
|
||||||
|
ok -> delete_cache(Mod, JID, Channel);
|
||||||
|
Err -> Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
del_channel(JID, Channel) ->
|
||||||
|
Mod = gen_mod:db_mod(JID#jid.lserver, ?MODULE),
|
||||||
|
case Mod:del_channel(JID, Channel) of
|
||||||
|
ok -> delete_cache(Mod, JID, Channel);
|
||||||
|
Err -> Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Cache management
|
||||||
|
%%%===================================================================
|
||||||
|
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||||
|
init_cache(Mod, Host, Opts) ->
|
||||||
|
case use_cache(Mod, Host) of
|
||||||
|
true ->
|
||||||
|
CacheOpts = cache_opts(Opts),
|
||||||
|
ets_cache:new(?MIX_PAM_CACHE, CacheOpts);
|
||||||
|
false ->
|
||||||
|
ets_cache:delete(?MIX_PAM_CACHE)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec cache_opts(gen_mod:opts()) -> [proplists:property()].
|
||||||
|
cache_opts(Opts) ->
|
||||||
|
MaxSize = gen_mod:get_opt(cache_size, Opts),
|
||||||
|
CacheMissed = gen_mod:get_opt(cache_missed, Opts),
|
||||||
|
LifeTime = case gen_mod:get_opt(cache_life_time, Opts) 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)
|
||||||
|
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(), jid(), jid()) -> ok.
|
||||||
|
delete_cache(Mod, JID, Channel) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(JID),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
case use_cache(Mod, LServer) of
|
||||||
|
true ->
|
||||||
|
ets_cache:delete(?MIX_PAM_CACHE,
|
||||||
|
{LUser, LServer, Chan, Service},
|
||||||
|
cache_nodes(Mod, LServer));
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end.
|
91
src/mod_mix_pam_mnesia.erl
Normal file
91
src/mod_mix_pam_mnesia.erl
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% Created : 4 Dec 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
-module(mod_mix_pam_mnesia).
|
||||||
|
-behaviour(mod_mix_pam).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([init/2, add_channel/3, get_channel/2,
|
||||||
|
get_channels/1, del_channel/2, del_channels/1,
|
||||||
|
use_cache/1]).
|
||||||
|
|
||||||
|
-record(mix_pam, {user_channel :: {binary(), binary(), binary(), binary()},
|
||||||
|
user :: {binary(), binary()},
|
||||||
|
id :: binary()}).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
init(_Host, _Opts) ->
|
||||||
|
case ejabberd_mnesia:create(?MODULE, mix_pam,
|
||||||
|
[{disc_only_copies, [node()]},
|
||||||
|
{attributes, record_info(fields, mix_pam)},
|
||||||
|
{index, [user]}]) of
|
||||||
|
{atomic, _} -> ok;
|
||||||
|
_ -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
use_cache(Host) ->
|
||||||
|
case mnesia:table_info(mix_pam, storage_type) of
|
||||||
|
disc_only_copies ->
|
||||||
|
gen_mod:get_module_opt(Host, mod_mix_pam, use_cache);
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
add_channel(User, Channel, ID) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
mnesia:dirty_write(#mix_pam{user_channel = {LUser, LServer, Chan, Service},
|
||||||
|
user = {LUser, LServer},
|
||||||
|
id = ID}).
|
||||||
|
|
||||||
|
get_channel(User, Channel) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
case mnesia:dirty_read(mix_pam, {LUser, LServer, Chan, Service}) of
|
||||||
|
[#mix_pam{id = ID}] -> {ok, ID};
|
||||||
|
[] -> {error, notfound}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_channels(User) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
Ret = mnesia:dirty_index_read(mix_pam, #mix_pam.user, {LUser, LServer}),
|
||||||
|
{ok, lists:map(
|
||||||
|
fun(#mix_pam{user_channel = {_, _, Chan, Service},
|
||||||
|
id = ID}) ->
|
||||||
|
{jid:make(Chan, Service), ID}
|
||||||
|
end, Ret)}.
|
||||||
|
|
||||||
|
del_channel(User, Channel) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
mnesia:dirty_delete(mix_pam, {LUser, LServer, Chan, Service}).
|
||||||
|
|
||||||
|
del_channels(User) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
Ret = mnesia:dirty_index_read(mix_pam, #mix_pam.user, {LUser, LServer}),
|
||||||
|
lists:foreach(fun mnesia:dirty_delete_object/1, Ret).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
114
src/mod_mix_pam_sql.erl
Normal file
114
src/mod_mix_pam_sql.erl
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%% Created : 4 Dec 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
-module(mod_mix_pam_sql).
|
||||||
|
-behaviour(mod_mix_pam).
|
||||||
|
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([init/2, add_channel/3, get_channel/2,
|
||||||
|
get_channels/1, del_channel/2, del_channels/1]).
|
||||||
|
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("ejabberd_sql_pt.hrl").
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
init(_Host, _Opts) ->
|
||||||
|
%% TODO
|
||||||
|
ok.
|
||||||
|
|
||||||
|
add_channel(User, Channel, ID) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
case ?SQL_UPSERT(LServer, "mix_pam",
|
||||||
|
["!channel=%(Chan)s",
|
||||||
|
"!service=%(Service)s",
|
||||||
|
"!username=%(LUser)s",
|
||||||
|
"!server_host=%(LServer)s",
|
||||||
|
"id=%(ID)s"]) of
|
||||||
|
ok -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_channel(User, Channel) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("select @(id)s from mix_pam where "
|
||||||
|
"channel=%(Chan)s and service=%(Service)s "
|
||||||
|
"and username=%(LUser)s and %(LServer)H")) of
|
||||||
|
{selected, [{ID}]} -> {ok, ID};
|
||||||
|
{selected, []} -> {error, notfound};
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_channels(User) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
SQL = ?SQL("select @(channel)s, @(service)s, @(id)s from mix_pam "
|
||||||
|
"where username=%(LUser)s and %(LServer)H"),
|
||||||
|
case ejabberd_sql:sql_query(LServer, SQL) of
|
||||||
|
{selected, Ret} ->
|
||||||
|
{ok, lists:filtermap(
|
||||||
|
fun({Chan, Service, ID}) ->
|
||||||
|
case jid:make(Chan, Service) of
|
||||||
|
error ->
|
||||||
|
report_corrupted(SQL),
|
||||||
|
false;
|
||||||
|
JID ->
|
||||||
|
{true, {JID, ID}}
|
||||||
|
end
|
||||||
|
end, Ret)};
|
||||||
|
_Err ->
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
del_channel(User, Channel) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
{Chan, Service, _} = jid:tolower(Channel),
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("delete from mix_pam where "
|
||||||
|
"channel=%(Chan)s and service=%(Service)s "
|
||||||
|
"and username=%(LUser)s and %(LServer)H")) of
|
||||||
|
{updated, _} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
del_channels(User) ->
|
||||||
|
{LUser, LServer, _} = jid:tolower(User),
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("delete from mix_pam where "
|
||||||
|
"username=%(LUser)s and %(LServer)H")) of
|
||||||
|
{updated, _} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
-spec report_corrupted(iolist()) -> ok.
|
||||||
|
report_corrupted(SQL) ->
|
||||||
|
?ERROR_MSG("Corrupted values returned by SQL request: ~s", [SQL]).
|
236
src/mod_mix_sql.erl
Normal file
236
src/mod_mix_sql.erl
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% Created : 1 Dec 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% 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.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
-module(mod_mix_sql).
|
||||||
|
-behaviour(mod_mix).
|
||||||
|
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([init/2]).
|
||||||
|
-export([set_channel/6, get_channels/2, get_channel/3, del_channel/3]).
|
||||||
|
-export([set_participant/6, get_participant/4, get_participants/3, del_participant/4]).
|
||||||
|
-export([subscribe/5, unsubscribe/4, unsubscribe/5, get_subscribed/4]).
|
||||||
|
|
||||||
|
-include("logger.hrl").
|
||||||
|
-include("ejabberd_sql_pt.hrl").
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
init(_Host, _Opts) ->
|
||||||
|
%% TODO
|
||||||
|
ok.
|
||||||
|
|
||||||
|
set_channel(LServer, Channel, Service, CreatorJID, Hidden, Key) ->
|
||||||
|
{User, Domain, _} = jid:tolower(CreatorJID),
|
||||||
|
RawJID = jid:encode(jid:remove_resource(CreatorJID)),
|
||||||
|
case ?SQL_UPSERT(LServer, "mix_channel",
|
||||||
|
["!channel=%(Channel)s",
|
||||||
|
"!service=%(Service)s",
|
||||||
|
"username=%(User)s",
|
||||||
|
"domain=%(Domain)s",
|
||||||
|
"jid=%(RawJID)s",
|
||||||
|
"hidden=%(Hidden)b",
|
||||||
|
"hmac_key=%(Key)s"]) of
|
||||||
|
ok -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_channels(LServer, Service) ->
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("select @(channel)s, @(hidden)b from mix_channel "
|
||||||
|
"where service=%(Service)s")) of
|
||||||
|
{selected, Ret} ->
|
||||||
|
{ok, [Channel || {Channel, Hidden} <- Ret, Hidden == false]};
|
||||||
|
_Err ->
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_channel(LServer, Channel, Service) ->
|
||||||
|
SQL = ?SQL("select @(jid)s, @(hidden)b, @(hmac_key)s from mix_channel "
|
||||||
|
"where channel=%(Channel)s and service=%(Service)s"),
|
||||||
|
case ejabberd_sql:sql_query(LServer, SQL) of
|
||||||
|
{selected, [{RawJID, Hidden, Key}]} ->
|
||||||
|
try jid:decode(RawJID) of
|
||||||
|
JID -> {ok, {JID, Hidden, Key}}
|
||||||
|
catch _:{bad_jid, _} ->
|
||||||
|
report_corrupted(jid, SQL),
|
||||||
|
{error, db_failure}
|
||||||
|
end;
|
||||||
|
{selected, []} -> {error, notfound};
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
del_channel(LServer, Channel, Service) ->
|
||||||
|
F = fun() ->
|
||||||
|
ejabberd_sql:sql_query_t(
|
||||||
|
?SQL("delete from mix_channel where "
|
||||||
|
"channel=%(Channel)s and service=%(Service)s")),
|
||||||
|
ejabberd_sql:sql_query_t(
|
||||||
|
?SQL("delete from mix_participant where "
|
||||||
|
"channel=%(Channel)s and service=%(Service)s")),
|
||||||
|
ejabberd_sql:sql_query_t(
|
||||||
|
?SQL("delete from mix_subscription where "
|
||||||
|
"channel=%(Channel)s and service=%(Service)s"))
|
||||||
|
end,
|
||||||
|
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||||
|
{atomic, _} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
set_participant(LServer, Channel, Service, JID, ID, Nick) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
RawJID = jid:encode(jid:remove_resource(JID)),
|
||||||
|
case ?SQL_UPSERT(LServer, "mix_participant",
|
||||||
|
["!channel=%(Channel)s",
|
||||||
|
"!service=%(Service)s",
|
||||||
|
"!username=%(User)s",
|
||||||
|
"!domain=%(Domain)s",
|
||||||
|
"jid=%(RawJID)s",
|
||||||
|
"id=%(ID)s",
|
||||||
|
"nick=%(Nick)s"]) of
|
||||||
|
ok -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_participant(LServer, Channel, Service, JID) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("select @(id)s, @(nick)s from mix_participant "
|
||||||
|
"where channel=%(Channel)s and service=%(Service)s "
|
||||||
|
"and username=%(User)s and domain=%(Domain)s")) of
|
||||||
|
{selected, [Ret]} -> {ok, Ret};
|
||||||
|
{selected, []} -> {error, notfound};
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_participants(LServer, Channel, Service) ->
|
||||||
|
SQL = ?SQL("select @(jid)s, @(id)s, @(nick)s from mix_participant "
|
||||||
|
"where channel=%(Channel)s and service=%(Service)s"),
|
||||||
|
case ejabberd_sql:sql_query(LServer, SQL) of
|
||||||
|
{selected, Ret} ->
|
||||||
|
{ok, lists:filtermap(
|
||||||
|
fun({RawJID, ID, Nick}) ->
|
||||||
|
try jid:decode(RawJID) of
|
||||||
|
JID -> {true, {JID, ID, Nick}}
|
||||||
|
catch _:{bad_jid, _} ->
|
||||||
|
report_corrupted(jid, SQL),
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end, Ret)};
|
||||||
|
_Err ->
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
del_participant(LServer, Channel, Service, JID) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("delete from mix_participant where "
|
||||||
|
"channel=%(Channel)s and service=%(Service)s "
|
||||||
|
"and username=%(User)s and domain=%(Domain)s")) of
|
||||||
|
{updated, _} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
subscribe(_LServer, _Channel, _Service, _JID, []) ->
|
||||||
|
ok;
|
||||||
|
subscribe(LServer, Channel, Service, JID, Nodes) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
RawJID = jid:encode(jid:remove_resource(JID)),
|
||||||
|
F = fun() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(Node) ->
|
||||||
|
?SQL_UPSERT_T(
|
||||||
|
"mix_subscription",
|
||||||
|
["!channel=%(Channel)s",
|
||||||
|
"!service=%(Service)s",
|
||||||
|
"!username=%(User)s",
|
||||||
|
"!domain=%(Domain)s",
|
||||||
|
"!node=%(Node)s",
|
||||||
|
"jid=%(RawJID)s"])
|
||||||
|
end, Nodes)
|
||||||
|
end,
|
||||||
|
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||||
|
{atomic, _} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_subscribed(LServer, Channel, Service, Node) ->
|
||||||
|
SQL = ?SQL("select @(jid)s from mix_subscription "
|
||||||
|
"where channel=%(Channel)s and service=%(Service)s "
|
||||||
|
"and node=%(Node)s"),
|
||||||
|
case ejabberd_sql:sql_query(LServer, SQL) of
|
||||||
|
{selected, Ret} ->
|
||||||
|
{ok, lists:filtermap(
|
||||||
|
fun({RawJID}) ->
|
||||||
|
try jid:decode(RawJID) of
|
||||||
|
JID -> {true, JID}
|
||||||
|
catch _:{bad_jid, _} ->
|
||||||
|
report_corrupted(jid, SQL),
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end, Ret)};
|
||||||
|
_Err ->
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
unsubscribe(LServer, Channel, Service, JID) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
case ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("delete from mix_subscription "
|
||||||
|
"where channel=%(Channel)s and service=%(Service)s "
|
||||||
|
"and username=%(User)s and domain=%(Domain)s")) of
|
||||||
|
{updated, _} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
unsubscribe(_LServer, _Channel, _Service, _JID, []) ->
|
||||||
|
ok;
|
||||||
|
unsubscribe(LServer, Channel, Service, JID, Nodes) ->
|
||||||
|
{User, Domain, _} = jid:tolower(JID),
|
||||||
|
F = fun() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(Node) ->
|
||||||
|
ejabberd_sql:sql_query_t(
|
||||||
|
?SQL("delete from mix_subscription "
|
||||||
|
"where channel=%(Channel)s "
|
||||||
|
"and service=%(Service)s "
|
||||||
|
"and username=%(User)s "
|
||||||
|
"and domain=%(Domain)s "
|
||||||
|
"and node=%(Node)s"))
|
||||||
|
end, Nodes)
|
||||||
|
end,
|
||||||
|
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||||
|
{atomic, ok} -> ok;
|
||||||
|
_Err -> {error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
|
-spec report_corrupted(atom(), iolist()) -> ok.
|
||||||
|
report_corrupted(Column, SQL) ->
|
||||||
|
?ERROR_MSG("Corrupted value of '~s' column returned by "
|
||||||
|
"SQL request: ~s", [Column, SQL]).
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
%% ejabberd_hooks callbacks.
|
%% ejabberd_hooks callbacks.
|
||||||
-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
|
-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
|
||||||
c2s_handle_cast/2, c2s_stanza/3, mam_message/6, offline_message/1,
|
c2s_handle_cast/2, c2s_stanza/3, mam_message/7, offline_message/1,
|
||||||
remove_user/2]).
|
remove_user/2]).
|
||||||
|
|
||||||
%% gen_iq_handler callback.
|
%% gen_iq_handler callback.
|
||||||
@ -352,8 +352,8 @@ c2s_stanza(State, _Pkt, _SendResult) ->
|
|||||||
State.
|
State.
|
||||||
|
|
||||||
-spec mam_message(message() | drop, binary(), binary(), jid(),
|
-spec mam_message(message() | drop, binary(), binary(), jid(),
|
||||||
chat | groupchat, recv | send) -> message().
|
binary(), chat | groupchat, recv | send) -> message().
|
||||||
mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, Dir) ->
|
mam_message(#message{} = Pkt, LUser, LServer, _Peer, _Nick, chat, Dir) ->
|
||||||
case lookup_sessions(LUser, LServer) of
|
case lookup_sessions(LUser, LServer) of
|
||||||
{ok, [_|_] = Clients} ->
|
{ok, [_|_] = Clients} ->
|
||||||
case drop_online_sessions(LUser, LServer, Clients) of
|
case drop_online_sessions(LUser, LServer, Clients) of
|
||||||
@ -367,7 +367,7 @@ mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, Dir) ->
|
|||||||
ok
|
ok
|
||||||
end,
|
end,
|
||||||
Pkt;
|
Pkt;
|
||||||
mam_message(Pkt, _LUser, _LServer, _Peer, _Type, _Dir) ->
|
mam_message(Pkt, _LUser, _LServer, _Peer, _Nick, _Type, _Dir) ->
|
||||||
Pkt.
|
Pkt.
|
||||||
|
|
||||||
-spec offline_message(message()) -> message().
|
-spec offline_message(message()) -> message().
|
||||||
|
Loading…
Reference in New Issue
Block a user