mod_csi_simple: Consider messages encrypted payload as important (fixes part of ...
[prosody.git] / plugins / muc / register.lib.lua
blob21cb3f2f2cc7d070d5a8bb0e7bedb61d28c1b0ae
1 local jid_bare = require "util.jid".bare;
2 local jid_resource = require "util.jid".resource;
3 local resourceprep = require "util.encodings".stringprep.resourceprep;
4 local st = require "util.stanza";
5 local dataforms = require "util.dataforms";
7 local allow_unaffiliated = module:get_option_boolean("allow_unaffiliated_register", false);
9 local enforce_nick = module:get_option_boolean("enforce_registered_nickname", false);
11 -- reserved_nicks[nick] = jid
12 local function get_reserved_nicks(room)
13 if room._reserved_nicks then
14 return room._reserved_nicks;
15 end
16 module:log("debug", "Refreshing reserved nicks...");
17 local reserved_nicks = {};
18 for jid in room:each_affiliation() do
19 local data = room._affiliation_data[jid];
20 local nick = data and data.reserved_nickname;
21 module:log("debug", "Refreshed for %s: %s", jid, nick);
22 if nick then
23 reserved_nicks[nick] = jid;
24 end
25 end
26 room._reserved_nicks = reserved_nicks;
27 return reserved_nicks;
28 end
30 -- Returns the registered nick, if any, for a JID
31 -- Note: this is just the *nick* part, i.e. the resource of the in-room JID
32 local function get_registered_nick(room, jid)
33 local registered_data = room._affiliation_data[jid];
34 if not registered_data then
35 return;
36 end
37 return registered_data.reserved_nickname;
38 end
40 -- Returns the JID, if any, that registered a nick (not in-room JID)
41 local function get_registered_jid(room, nick)
42 local reserved_nicks = get_reserved_nicks(room);
43 return reserved_nicks[nick];
44 end
46 module:hook("muc-set-affiliation", function (event)
47 -- Clear reserved nick cache
48 event.room._reserved_nicks = nil;
49 end);
51 module:add_feature("jabber:iq:register");
53 module:hook("muc-disco#info", function (event)
54 event.reply:tag("feature", { var = "jabber:iq:register" }):up();
55 end);
57 local registration_form = dataforms.new {
58 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#register" },
59 { name = "muc#register_roomnick", type = "text-single", label = "Nickname"},
62 local function enforce_nick_policy(event)
63 local origin, stanza = event.origin, event.stanza;
64 local room = assert(event.room); -- FIXME
65 if not room then return; end
67 -- Check if the chosen nickname is reserved
68 local requested_nick = jid_resource(stanza.attr.to);
69 local reserved_by = get_registered_jid(room, requested_nick);
70 if reserved_by and reserved_by ~= jid_bare(stanza.attr.from) then
71 module:log("debug", "%s attempted to use nick %s reserved by %s", stanza.attr.from, requested_nick, reserved_by);
72 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
73 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
74 return true;
75 end
77 -- Check if the occupant has a reservation they must use
78 if enforce_nick then
79 local nick = get_registered_nick(room, jid_bare(stanza.attr.from));
80 if nick then
81 if event.occupant then
82 event.occupant.nick = jid_bare(event.occupant.nick) .. "/" .. nick;
83 elseif event.dest_occupant.nick ~= jid_bare(event.dest_occupant.nick) .. "/" .. nick then
84 module:log("debug", "Attempt by %s to join as %s, but their reserved nick is %s", stanza.attr.from, requested_nick, nick);
85 local reply = st.error_reply(stanza, "cancel", "not-acceptable"):up();
86 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
87 return true;
88 end
89 end
90 end
91 end
93 module:hook("muc-occupant-pre-join", enforce_nick_policy);
94 module:hook("muc-occupant-pre-change", enforce_nick_policy);
96 -- Discovering Reserved Room Nickname
97 -- http://xmpp.org/extensions/xep-0045.html#reservednick
98 module:hook("muc-disco#info/x-roomuser-item", function (event)
99 local nick = get_registered_nick(event.room, jid_bare(event.stanza.attr.from));
100 if nick then
101 event.reply:tag("identity", { category = "conference", type = "text", name = nick })
103 end);
105 local function handle_register_iq(room, origin, stanza)
106 local user_jid = jid_bare(stanza.attr.from)
107 local affiliation = room:get_affiliation(user_jid);
108 if affiliation == "outcast" then
109 origin.send(st.error_reply(stanza, "auth", "forbidden"));
110 return true;
111 elseif not (affiliation or allow_unaffiliated) then
112 origin.send(st.error_reply(stanza, "auth", "registration-required"));
113 return true;
115 local reply = st.reply(stanza);
116 local registered_nick = get_registered_nick(room, user_jid);
117 if stanza.attr.type == "get" then
118 reply:query("jabber:iq:register");
119 if registered_nick then
120 reply:tag("registered"):up();
121 reply:tag("username"):text(registered_nick);
122 origin.send(reply);
123 return true;
125 reply:add_child(registration_form:form());
126 else -- type == set -- handle registration form
127 local query = stanza.tags[1];
128 if query:get_child("remove") then
129 -- Remove "member" affiliation, but preserve if any other
130 local new_affiliation = affiliation ~= "member" and affiliation;
131 local ok, err_type, err_condition = room:set_affiliation(true, user_jid, new_affiliation, nil, false);
132 if not ok then
133 origin.send(st.error_reply(stanza, err_type, err_condition));
134 return true;
136 origin.send(reply);
137 return true;
139 local form_tag = query:get_child("x", "jabber:x:data");
140 local reg_data = form_tag and registration_form:data(form_tag);
141 if not reg_data then
142 origin.send(st.error_reply(stanza, "modify", "bad-request", "Error in form"));
143 return true;
145 -- Is the nickname valid?
146 local desired_nick = resourceprep(reg_data["muc#register_roomnick"]);
147 if not desired_nick then
148 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid Nickname"));
149 return true;
151 -- Is the nickname currently in use by another user?
152 local current_occupant = room:get_occupant_by_nick(room.jid.."/"..desired_nick);
153 if current_occupant and current_occupant.bare_jid ~= user_jid then
154 origin.send(st.error_reply(stanza, "cancel", "conflict"));
155 return true;
157 -- Is the nickname currently reserved by another user?
158 local reserved_by = get_registered_jid(room, desired_nick);
159 if reserved_by and reserved_by ~= user_jid then
160 origin.send(st.error_reply(stanza, "cancel", "conflict"));
161 return true;
164 if enforce_nick then
165 -- Kick any sessions that are not using this nick before we register it
166 local required_room_nick = room.jid.."/"..desired_nick;
167 for room_nick, occupant in room:each_occupant() do
168 if occupant.bare_jid == user_jid and room_nick ~= required_room_nick then
169 room:set_role(true, room_nick, nil); -- Kick (TODO: would be nice to use 333 code)
174 -- Checks passed, save the registration
175 if registered_nick ~= desired_nick then
176 local registration_data = { reserved_nickname = desired_nick };
177 local ok, err_type, err_condition = room:set_affiliation(true, user_jid, affiliation or "member", nil, registration_data);
178 if not ok then
179 origin.send(st.error_reply(stanza, err_type, err_condition));
180 return true;
182 module:log("debug", "Saved nick registration for %s: %s", user_jid, desired_nick);
183 origin.send(reply);
184 return true;
187 origin.send(reply);
188 return true;
191 return {
192 get_registered_nick = get_registered_nick;
193 get_registered_jid = get_registered_jid;
194 handle_register_iq = handle_register_iq;