util.encodings: Spell out all IDNA 2008 options ICU has
[prosody.git] / plugins / muc / register.lib.lua
blobda106f8cd07ca8561747deb4454705bacf4a89d0
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, _, data in room:each_affiliation() do
19 local nick = data and data.reserved_nickname;
20 module:log("debug", "Refreshed for %s: %s", jid, nick);
21 if nick then
22 reserved_nicks[nick] = jid;
23 end
24 end
25 room._reserved_nicks = reserved_nicks;
26 return reserved_nicks;
27 end
29 -- Returns the registered nick, if any, for a JID
30 -- Note: this is just the *nick* part, i.e. the resource of the in-room JID
31 local function get_registered_nick(room, jid)
32 local registered_data = room._affiliation_data[jid];
33 if not registered_data then
34 return;
35 end
36 return registered_data.reserved_nickname;
37 end
39 -- Returns the JID, if any, that registered a nick (not in-room JID)
40 local function get_registered_jid(room, nick)
41 local reserved_nicks = get_reserved_nicks(room);
42 return reserved_nicks[nick];
43 end
45 module:hook("muc-set-affiliation", function (event)
46 -- Clear reserved nick cache
47 event.room._reserved_nicks = nil;
48 end);
50 module:add_feature("jabber:iq:register");
52 module:hook("muc-disco#info", function (event)
53 event.reply:tag("feature", { var = "jabber:iq:register" }):up();
54 end);
56 local registration_form = dataforms.new {
57 { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#register" },
58 { name = "muc#register_roomnick", type = "text-single", label = "Nickname"},
61 local function enforce_nick_policy(event)
62 local origin, stanza = event.origin, event.stanza;
63 local room = assert(event.room); -- FIXME
64 if not room then return; end
66 -- Check if the chosen nickname is reserved
67 local requested_nick = jid_resource(stanza.attr.to);
68 local reserved_by = get_registered_jid(room, requested_nick);
69 if reserved_by and reserved_by ~= jid_bare(stanza.attr.from) then
70 module:log("debug", "%s attempted to use nick %s reserved by %s", stanza.attr.from, requested_nick, reserved_by);
71 local reply = st.error_reply(stanza, "cancel", "conflict"):up();
72 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
73 return true;
74 end
76 -- Check if the occupant has a reservation they must use
77 if enforce_nick then
78 local nick = get_registered_nick(room, jid_bare(stanza.attr.from));
79 if nick then
80 if event.occupant then
81 event.occupant.nick = jid_bare(event.occupant.nick) .. "/" .. nick;
82 elseif event.dest_occupant.nick ~= jid_bare(event.dest_occupant.nick) .. "/" .. nick then
83 module:log("debug", "Attempt by %s to join as %s, but their reserved nick is %s", stanza.attr.from, requested_nick, nick);
84 local reply = st.error_reply(stanza, "cancel", "not-acceptable"):up();
85 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
86 return true;
87 end
88 end
89 end
90 end
92 module:hook("muc-occupant-pre-join", enforce_nick_policy);
93 module:hook("muc-occupant-pre-change", enforce_nick_policy);
95 -- Discovering Reserved Room Nickname
96 -- http://xmpp.org/extensions/xep-0045.html#reservednick
97 module:hook("muc-disco#info/x-roomuser-item", function (event)
98 local nick = get_registered_nick(event.room, jid_bare(event.stanza.attr.from));
99 if nick then
100 event.reply:tag("identity", { category = "conference", type = "text", name = nick })
102 end);
104 local function handle_register_iq(room, origin, stanza)
105 local user_jid = jid_bare(stanza.attr.from)
106 local affiliation = room:get_affiliation(user_jid);
107 if affiliation == "outcast" then
108 origin.send(st.error_reply(stanza, "auth", "forbidden"));
109 return true;
110 elseif not (affiliation or allow_unaffiliated) then
111 origin.send(st.error_reply(stanza, "auth", "registration-required"));
112 return true;
114 local reply = st.reply(stanza);
115 local registered_nick = get_registered_nick(room, user_jid);
116 if stanza.attr.type == "get" then
117 reply:query("jabber:iq:register");
118 if registered_nick then
119 reply:tag("registered"):up();
120 reply:tag("username"):text(registered_nick);
121 origin.send(reply);
122 return true;
124 reply:add_child(registration_form:form());
125 else -- type == set -- handle registration form
126 local query = stanza.tags[1];
127 if query:get_child("remove") then
128 -- Remove "member" affiliation, but preserve if any other
129 local new_affiliation = affiliation ~= "member" and affiliation;
130 local ok, err_type, err_condition = room:set_affiliation(true, user_jid, new_affiliation, nil, false);
131 if not ok then
132 origin.send(st.error_reply(stanza, err_type, err_condition));
133 return true;
135 origin.send(reply);
136 return true;
138 local form_tag = query:get_child("x", "jabber:x:data");
139 local reg_data = form_tag and registration_form:data(form_tag);
140 if not reg_data then
141 origin.send(st.error_reply(stanza, "modify", "bad-request", "Error in form"));
142 return true;
144 -- Is the nickname valid?
145 local desired_nick = resourceprep(reg_data["muc#register_roomnick"]);
146 if not desired_nick then
147 origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid Nickname"));
148 return true;
150 -- Is the nickname currently in use by another user?
151 local current_occupant = room:get_occupant_by_nick(room.jid.."/"..desired_nick);
152 if current_occupant and current_occupant.bare_jid ~= user_jid then
153 origin.send(st.error_reply(stanza, "cancel", "conflict"));
154 return true;
156 -- Is the nickname currently reserved by another user?
157 local reserved_by = get_registered_jid(room, desired_nick);
158 if reserved_by and reserved_by ~= user_jid then
159 origin.send(st.error_reply(stanza, "cancel", "conflict"));
160 return true;
163 if enforce_nick then
164 -- Kick any sessions that are not using this nick before we register it
165 local required_room_nick = room.jid.."/"..desired_nick;
166 for room_nick, occupant in room:each_occupant() do
167 if occupant.bare_jid == user_jid and room_nick ~= required_room_nick then
168 room:set_role(true, room_nick, nil); -- Kick (TODO: would be nice to use 333 code)
173 -- Checks passed, save the registration
174 if registered_nick ~= desired_nick then
175 local registration_data = { reserved_nickname = desired_nick };
176 local ok, err_type, err_condition = room:set_affiliation(true, user_jid, affiliation or "member", nil, registration_data);
177 if not ok then
178 origin.send(st.error_reply(stanza, err_type, err_condition));
179 return true;
181 module:log("debug", "Saved nick registration for %s: %s", user_jid, desired_nick);
182 origin.send(reply);
183 return true;
186 origin.send(reply);
187 return true;
190 return {
191 get_registered_nick = get_registered_nick;
192 get_registered_jid = get_registered_jid;
193 handle_register_iq = handle_register_iq;