plugins/muc/register.lib.lua
changeset 9243 f9a83aca4421
child 9265 37b7cf3470f1
equal deleted inserted replaced
9242:03e37f7d6c97 9243:f9a83aca4421
       
     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";
       
     6 
       
     7 local allow_unaffiliated = module:get_option_boolean("allow_unaffiliated_register", false);
       
     8 
       
     9 local enforce_nick = module:get_option_boolean("enforce_registered_nickname", false);
       
    10 
       
    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
       
    29 
       
    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
       
    39 
       
    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
       
    45 
       
    46 module:hook("muc-set-affiliation", function (event)
       
    47 	-- Clear reserved nick cache
       
    48 	event.room._reserved_nicks = nil;
       
    49 end);
       
    50 
       
    51 module:add_feature("jabber:iq:register");
       
    52 
       
    53 module:hook("muc-disco#info", function (event)
       
    54 	event.reply:tag("feature", { var = "jabber:iq:register" }):up();
       
    55 end);
       
    56 
       
    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"},
       
    60 };
       
    61 
       
    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
       
    66 
       
    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
       
    76 
       
    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
       
    92 
       
    93 module:hook("muc-occupant-pre-join", enforce_nick_policy);
       
    94 module:hook("muc-occupant-pre-change", enforce_nick_policy);
       
    95 
       
    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 })
       
   102 	end
       
   103 end);
       
   104 
       
   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;
       
   114 	end
       
   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;
       
   124 		end
       
   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;
       
   135 			end
       
   136 			origin.send(reply);
       
   137 			return true;
       
   138 		end
       
   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;
       
   144 		end
       
   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;
       
   150 		end
       
   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;
       
   156 		end
       
   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;
       
   162 		end
       
   163 
       
   164 		-- Kick any sessions that are not using this nick before we register it
       
   165 		if enforce_nick then
       
   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)
       
   170 				end
       
   171 			end
       
   172 		end
       
   173 
       
   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, "member", nil, registration_data);
       
   178 			if not ok then
       
   179 				origin.send(st.error_reply(stanza, err_type, err_condition));
       
   180 				return true;
       
   181 			end
       
   182 			module:log("debug", "Saved nick registration for %s: %s", user_jid, desired_nick);
       
   183 			origin.send(reply);
       
   184 			return true;
       
   185 		end
       
   186 	end
       
   187 	origin.send(reply);
       
   188 	return true;
       
   189 end
       
   190 
       
   191 return {
       
   192 	get_registered_nick = get_registered_nick;
       
   193 	get_registered_jid = get_registered_jid;
       
   194 	handle_register_iq = handle_register_iq;
       
   195 }