diff options
-rw-r--r-- | CHANGES | 1 | ||||
-rw-r--r-- | plugins/mod_tombstones.lua | 75 |
2 files changed, 76 insertions, 0 deletions
@@ -16,6 +16,7 @@ TRUNK - mod_cron: One module to rule all the periodic tasks - mod_admin_shell: New home of the Console admin interface - mod_admin_socket: Enable secure connections to the Console +- mod_tombstones: Prevent registration of deleted accounts ### Security and authentication diff --git a/plugins/mod_tombstones.lua b/plugins/mod_tombstones.lua new file mode 100644 index 00000000..1707c9dd --- /dev/null +++ b/plugins/mod_tombstones.lua @@ -0,0 +1,75 @@ +-- TODO warn when trying to create an user before the tombstone expires +-- e.g. via telnet or other admin interface +local datetime = require "util.datetime"; +local errors = require "util.error"; +local jid_split = require"util.jid".split; +local st = require "util.stanza"; + +-- Using a map store as key-value store so that removal of all user data +-- does not also remove the tombstone, which would defeat the point +local graveyard = module:open_store(nil, "map"); + +local ttl = module:get_option_number("user_tombstone_expiry", nil); +-- Keep tombstones forever by default +-- +-- Rationale: +-- There is no way to be completely sure when remote services have +-- forgotten and revoked all memberships. + +module:hook_global("user-deleted", function(event) + if event.host == module.host then + local ok, err = graveyard:set(nil, event.username, os.time()); + if not ok then module:log("error", "Could store tombstone for %s: %s", event.username, err); end + end +end); + +-- Public API +function has_tombstone(username) + local tombstone, err = graveyard:get(nil, username); + + if err or not tombstone then return tombstone, err; end + + if ttl and tombstone + ttl < os.time() then + module:log("debug", "Tombstone for %s created at %s has expired", username, datetime.datetime(tombstone)); + graveyard:set(nil, username, nil); + return nil; + end + return tombstone; +end + +module:hook("user-registering", function(event) + local tombstone, err = has_tombstone(event.username); + + if err then + event.allowed, event.error = errors.coerce(false, err); + return true; + elseif not tombstone then + -- Feel free + return; + end + + module:log("debug", "Tombstone for %s created at %s", event.username, datetime.datetime(tombstone)); + event.allowed = false; + return true; +end); + +module:hook("presence/bare", function(event) + local origin, presence = event.origin, event.stanza; + + -- We want to undo any left-over presence subscriptions and notify the former + -- contact that they're gone. + -- + -- FIXME This leaks that the user once existed. Hard to avoid without keeping + -- the contact list in some form, which we don't want to do for privacy + -- reasons. Bloom filter perhaps? + if has_tombstone(jid_split(presence.attr.to)) then + if presence.attr.type == "probe" then + origin.send(st.error_reply(presence, "cancel", "gone", "User deleted")); + origin.send(st.presence({ type = "unsubscribed"; to = presence.attr.from; from = presence.attr.to })); + elseif presence.attr.type == nil or presence.attr.type == "unavailable" then + origin.send(st.error_reply(presence, "cancel", "gone", "User deleted")); + origin.send(st.presence({ type = "unsubscribe"; to = presence.attr.from; from = presence.attr.to })); + end + return true; + end +end, 1); |