plugins/muc/muc.lib.lua
changeset 6054 7a5ddbaf758d
parent 6049 6d410ffd6e13
parent 6004 09151d26560a
child 6144 cb08bba0443a
child 6834 428b8da1cfce
equal deleted inserted replaced
6053:2f93a04564b2 6054:7a5ddbaf758d
     1 -- Prosody IM
     1 -- Prosody IM
     2 -- Copyright (C) 2008-2010 Matthew Wild
     2 -- Copyright (C) 2008-2010 Matthew Wild
     3 -- Copyright (C) 2008-2010 Waqas Hussain
     3 -- Copyright (C) 2008-2010 Waqas Hussain
     4 -- 
     4 --
     5 -- This project is MIT/X11 licensed. Please see the
     5 -- This project is MIT/X11 licensed. Please see the
     6 -- COPYING file in the source package for more information.
     6 -- COPYING file in the source package for more information.
     7 --
     7 --
     8 
     8 
     9 local select = select;
     9 local select = select;
    25 
    25 
    26 local muc_domain = nil; --module:get_host();
    26 local muc_domain = nil; --module:get_host();
    27 local default_history_length, max_history_length = 20, math.huge;
    27 local default_history_length, max_history_length = 20, math.huge;
    28 
    28 
    29 ------------
    29 ------------
    30 local function filter_xmlns_from_array(array, filters)
       
    31 	local count = 0;
       
    32 	for i=#array,1,-1 do
       
    33 		local attr = array[i].attr;
       
    34 		if filters[attr and attr.xmlns] then
       
    35 			t_remove(array, i);
       
    36 			count = count + 1;
       
    37 		end
       
    38 	end
       
    39 	return count;
       
    40 end
       
    41 local function filter_xmlns_from_stanza(stanza, filters)
       
    42 	if filters then
       
    43 		if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
       
    44 			return stanza, filter_xmlns_from_array(stanza, filters);
       
    45 		end
       
    46 	end
       
    47 	return stanza, 0;
       
    48 end
       
    49 local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
    30 local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
       
    31 local function presence_filter(tag)
       
    32 	if presence_filters[tag.attr.xmlns] then
       
    33 		return nil;
       
    34 	end
       
    35 	return tag;
       
    36 end
       
    37 
    50 local function get_filtered_presence(stanza)
    38 local function get_filtered_presence(stanza)
    51 	return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters);
    39 	return st.clone(stanza):maptags(presence_filter);
    52 end
    40 end
    53 local kickable_error_conditions = {
    41 local kickable_error_conditions = {
    54 	["gone"] = true;
    42 	["gone"] = true;
    55 	["internal-server-error"] = true;
    43 	["internal-server-error"] = true;
    56 	["item-not-found"] = true;
    44 	["item-not-found"] = true;
    70 
    58 
    71 local function is_kickable_error(stanza)
    59 local function is_kickable_error(stanza)
    72 	local cond = get_error_condition(stanza);
    60 	local cond = get_error_condition(stanza);
    73 	return kickable_error_conditions[cond] and cond;
    61 	return kickable_error_conditions[cond] and cond;
    74 end
    62 end
    75 local function getUsingPath(stanza, path, getText)
       
    76 	local tag = stanza;
       
    77 	for _, name in ipairs(path) do
       
    78 		if type(tag) ~= 'table' then return; end
       
    79 		tag = tag:child_with_name(name);
       
    80 	end
       
    81 	if tag and getText then tag = table.concat(tag); end
       
    82 	return tag;
       
    83 end
       
    84 local function getTag(stanza, path) return getUsingPath(stanza, path); end
       
    85 local function getText(stanza, path) return getUsingPath(stanza, path, true); end
       
    86 -----------
    63 -----------
    87 
    64 
    88 local room_mt = {};
    65 local room_mt = {};
    89 room_mt.__index = room_mt;
    66 room_mt.__index = room_mt;
    90 
    67 
    96 	if affiliation == "owner" or affiliation == "admin" then
    73 	if affiliation == "owner" or affiliation == "admin" then
    97 		return "moderator";
    74 		return "moderator";
    98 	elseif affiliation == "member" then
    75 	elseif affiliation == "member" then
    99 		return "participant";
    76 		return "participant";
   100 	elseif not affiliation then
    77 	elseif not affiliation then
   101 		if not self:is_members_only() then
    78 		if not self:get_members_only() then
   102 			return self:is_moderated() and "visitor" or "participant";
    79 			return self:get_moderated() and "visitor" or "participant";
   103 		end
    80 		end
   104 	end
    81 	end
   105 end
    82 end
   106 
    83 
   107 function room_mt:broadcast_presence(stanza, sid, code, nick)
    84 function room_mt:broadcast_presence(stanza, sid, code, nick)
   128 			self:_route_stanza(stanza);
   105 			self:_route_stanza(stanza);
   129 		end
   106 		end
   130 	end
   107 	end
   131 	stanza.attr.to = to;
   108 	stanza.attr.to = to;
   132 	if historic then -- add to history
   109 	if historic then -- add to history
   133 		local history = self._data['history'];
   110 		return self:save_to_history(stanza)
   134 		if not history then history = {}; self._data['history'] = history; end
   111 	end
   135 		stanza = st.clone(stanza);
   112 end
   136 		stanza.attr.to = "";
   113 function room_mt:save_to_history(stanza)
   137 		local stamp = datetime.datetime();
   114 	local history = self._data['history'];
   138 		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
   115 	if not history then history = {}; self._data['history'] = history; end
   139 		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
   116 	stanza = st.clone(stanza);
   140 		local entry = { stanza = stanza, stamp = stamp };
   117 	stanza.attr.to = "";
   141 		t_insert(history, entry);
   118 	local stamp = datetime.datetime();
   142 		while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
   119 	stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
   143 	end
   120 	stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
       
   121 	local entry = { stanza = stanza, stamp = stamp };
       
   122 	t_insert(history, entry);
       
   123 	while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
   144 end
   124 end
   145 function room_mt:broadcast_except_nick(stanza, nick)
   125 function room_mt:broadcast_except_nick(stanza, nick)
   146 	for rnick, occupant in pairs(self._occupants) do
   126 	for rnick, occupant in pairs(self._occupants) do
   147 		if rnick ~= nick then
   127 		if rnick ~= nick then
   148 			for jid in pairs(occupant.sessions) do
   128 			for jid in pairs(occupant.sessions) do
   168 function room_mt:send_history(to, stanza)
   148 function room_mt:send_history(to, stanza)
   169 	local history = self._data['history']; -- send discussion history
   149 	local history = self._data['history']; -- send discussion history
   170 	if history then
   150 	if history then
   171 		local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
   151 		local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
   172 		local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
   152 		local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
   173 		
   153 
   174 		local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
   154 		local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
   175 		if maxchars then maxchars = math.floor(maxchars); end
   155 		if maxchars then maxchars = math.floor(maxchars); end
   176 		
   156 
   177 		local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
   157 		local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
   178 		if not history_tag then maxstanzas = 20; end
   158 		if not history_tag then maxstanzas = 20; end
   179 
   159 
   180 		local seconds = history_tag and tonumber(history_tag.attr.seconds);
   160 		local seconds = history_tag and tonumber(history_tag.attr.seconds);
   181 		if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
   161 		if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
   184 		if since then since = datetime.parse(since); since = since and datetime.datetime(since); end
   164 		if since then since = datetime.parse(since); since = since and datetime.datetime(since); end
   185 		if seconds and (not since or since < seconds) then since = seconds; end
   165 		if seconds and (not since or since < seconds) then since = seconds; end
   186 
   166 
   187 		local n = 0;
   167 		local n = 0;
   188 		local charcount = 0;
   168 		local charcount = 0;
   189 		
   169 
   190 		for i=#history,1,-1 do
   170 		for i=#history,1,-1 do
   191 			local entry = history[i];
   171 			local entry = history[i];
   192 			if maxchars then
   172 			if maxchars then
   193 				if not entry.chars then
   173 				if not entry.chars then
   194 					entry.stanza.attr.to = "";
   174 					entry.stanza.attr.to = "";
   205 			local msg = history[i].stanza;
   185 			local msg = history[i].stanza;
   206 			msg.attr.to = to;
   186 			msg.attr.to = to;
   207 			self:_route_stanza(msg);
   187 			self:_route_stanza(msg);
   208 		end
   188 		end
   209 	end
   189 	end
       
   190 end
       
   191 function room_mt:send_subject(to)
   210 	if self._data['subject'] then
   192 	if self._data['subject'] then
   211 		self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject']));
   193 		self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject']));
   212 	end
   194 	end
   213 end
   195 end
   214 
   196 
   216 	local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
   198 	local count = 0; for _ in pairs(self._occupants) do count = count + 1; end
   217 	return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
   199 	return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
   218 		:tag("identity", {category="conference", type="text", name=self:get_name()}):up()
   200 		:tag("identity", {category="conference", type="text", name=self:get_name()}):up()
   219 		:tag("feature", {var="http://jabber.org/protocol/muc"}):up()
   201 		:tag("feature", {var="http://jabber.org/protocol/muc"}):up()
   220 		:tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
   202 		:tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
   221 		:tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
   203 		:tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
   222 		:tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
   204 		:tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up()
   223 		:tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
   205 		:tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up()
   224 		:tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
   206 		:tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up()
   225 		:tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
   207 		:tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
   226 		:add_child(dataform.new({
   208 		:add_child(dataform.new({
   227 			{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
   209 			{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" },
   228 			{ name = "muc#roominfo_description", label = "Description", value = "" },
   210 			{ name = "muc#roominfo_description", label = "Description", value = "" },
   229 			{ name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
   211 			{ name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }
   236 		reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up();
   218 		reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up();
   237 	end
   219 	end
   238 	return reply;
   220 	return reply;
   239 end
   221 end
   240 function room_mt:set_subject(current_nick, subject)
   222 function room_mt:set_subject(current_nick, subject)
   241 	-- TODO check nick's authority
       
   242 	if subject == "" then subject = nil; end
   223 	if subject == "" then subject = nil; end
   243 	self._data['subject'] = subject;
   224 	self._data['subject'] = subject;
   244 	self._data['subject_from'] = current_nick;
   225 	self._data['subject_from'] = current_nick;
   245 	if self.save then self:save(); end
   226 	if self.save then self:save(); end
   246 	local msg = st.message({type='groupchat', from=current_nick})
   227 	local msg = st.message({type='groupchat', from=current_nick})
   294 	if self._data.moderated ~= moderated then
   275 	if self._data.moderated ~= moderated then
   295 		self._data.moderated = moderated;
   276 		self._data.moderated = moderated;
   296 		if self.save then self:save(true); end
   277 		if self.save then self:save(true); end
   297 	end
   278 	end
   298 end
   279 end
   299 function room_mt:is_moderated()
   280 function room_mt:get_moderated()
   300 	return self._data.moderated;
   281 	return self._data.moderated;
   301 end
   282 end
   302 function room_mt:set_members_only(members_only)
   283 function room_mt:set_members_only(members_only)
   303 	members_only = members_only and true or nil;
   284 	members_only = members_only and true or nil;
   304 	if self._data.members_only ~= members_only then
   285 	if self._data.members_only ~= members_only then
   305 		self._data.members_only = members_only;
   286 		self._data.members_only = members_only;
   306 		if self.save then self:save(true); end
   287 		if self.save then self:save(true); end
   307 	end
   288 	end
   308 end
   289 end
   309 function room_mt:is_members_only()
   290 function room_mt:get_members_only()
   310 	return self._data.members_only;
   291 	return self._data.members_only;
   311 end
   292 end
   312 function room_mt:set_persistent(persistent)
   293 function room_mt:set_persistent(persistent)
   313 	persistent = persistent and true or nil;
   294 	persistent = persistent and true or nil;
   314 	if self._data.persistent ~= persistent then
   295 	if self._data.persistent ~= persistent then
   315 		self._data.persistent = persistent;
   296 		self._data.persistent = persistent;
   316 		if self.save then self:save(true); end
   297 		if self.save then self:save(true); end
   317 	end
   298 	end
   318 end
   299 end
   319 function room_mt:is_persistent()
   300 function room_mt:get_persistent()
   320 	return self._data.persistent;
   301 	return self._data.persistent;
   321 end
   302 end
   322 function room_mt:set_hidden(hidden)
   303 function room_mt:set_hidden(hidden)
   323 	hidden = hidden and true or nil;
   304 	hidden = hidden and true or nil;
   324 	if self._data.hidden ~= hidden then
   305 	if self._data.hidden ~= hidden then
   325 		self._data.hidden = hidden;
   306 		self._data.hidden = hidden;
   326 		if self.save then self:save(true); end
   307 		if self.save then self:save(true); end
   327 	end
   308 	end
   328 end
   309 end
   329 function room_mt:is_hidden()
   310 function room_mt:get_hidden()
   330 	return self._data.hidden;
   311 	return self._data.hidden;
       
   312 end
       
   313 function room_mt:get_public()
       
   314 	return not self:get_hidden();
       
   315 end
       
   316 function room_mt:set_public(public)
       
   317 	return self:set_hidden(not public);
   331 end
   318 end
   332 function room_mt:set_changesubject(changesubject)
   319 function room_mt:set_changesubject(changesubject)
   333 	changesubject = changesubject and true or nil;
   320 	changesubject = changesubject and true or nil;
   334 	if self._data.changesubject ~= changesubject then
   321 	if self._data.changesubject ~= changesubject then
   335 		self._data.changesubject = changesubject;
   322 		self._data.changesubject = changesubject;
   349 	end
   336 	end
   350 	self._data.history_length = length;
   337 	self._data.history_length = length;
   351 end
   338 end
   352 
   339 
   353 
   340 
       
   341 local valid_whois = { moderators = true, anyone = true };
       
   342 
       
   343 function room_mt:set_whois(whois)
       
   344 	if valid_whois[whois] and self._data.whois ~= whois then
       
   345 		self._data.whois = whois;
       
   346 		if self.save then self:save(true); end
       
   347 	end
       
   348 end
       
   349 
       
   350 function room_mt:get_whois()
       
   351 	return self._data.whois;
       
   352 end
       
   353 
   354 local function construct_stanza_id(room, stanza)
   354 local function construct_stanza_id(room, stanza)
   355 	local from_jid, to_nick = stanza.attr.from, stanza.attr.to;
   355 	local from_jid, to_nick = stanza.attr.from, stanza.attr.to;
   356 	local from_nick = room._jid_nick[from_jid];
   356 	local from_nick = room._jid_nick[from_jid];
   357 	local occupant = room._occupants[to_nick];
   357 	local occupant = room._occupants[to_nick];
   358 	local to_jid = occupant.jid;
   358 	local to_jid = occupant.jid;
   359 	
   359 
   360 	return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid));
   360 	return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid));
   361 end
   361 end
   362 local function deconstruct_stanza_id(room, stanza)
   362 local function deconstruct_stanza_id(room, stanza)
   363 	local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to;
   363 	local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to;
   364 	local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$");
   364 	local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$");
   483 					origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
   483 					origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
   484 				else
   484 				else
   485 					log("debug", "%s joining as %s", from, to);
   485 					log("debug", "%s joining as %s", from, to);
   486 					if not next(self._affiliations) then -- new room, no owners
   486 					if not next(self._affiliations) then -- new room, no owners
   487 						self._affiliations[jid_bare(from)] = "owner";
   487 						self._affiliations[jid_bare(from)] = "owner";
       
   488 						if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then
       
   489 							self.locked = nil; -- Older groupchat protocol doesn't lock
       
   490 						end
       
   491 					elseif self.locked then -- Deny entry
       
   492 						origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
       
   493 						return;
   488 					end
   494 					end
   489 					local affiliation = self:get_affiliation(from);
   495 					local affiliation = self:get_affiliation(from);
   490 					local role = self:get_default_role(affiliation)
   496 					local role = self:get_default_role(affiliation)
   491 					if role then -- new occupant
   497 					if role then -- new occupant
   492 						if not is_merge then
   498 						if not is_merge then
   504 						end
   510 						end
   505 						pr:tag("status", {code='110'}):up();
   511 						pr:tag("status", {code='110'}):up();
   506 						if self._data.whois == 'anyone' then
   512 						if self._data.whois == 'anyone' then
   507 							pr:tag("status", {code='100'}):up();
   513 							pr:tag("status", {code='100'}):up();
   508 						end
   514 						end
       
   515 						if self.locked then
       
   516 							pr:tag("status", {code='201'}):up();
       
   517 						end
   509 						pr.attr.to = from;
   518 						pr.attr.to = from;
   510 						self:_route_stanza(pr);
   519 						self:_route_stanza(pr);
   511 						self:send_history(from, stanza);
   520 						self:send_history(from, stanza);
       
   521 						self:send_subject(from);
   512 					elseif not affiliation then -- registration required for entering members-only room
   522 					elseif not affiliation then -- registration required for entering members-only room
   513 						local reply = st.error_reply(stanza, "auth", "registration-required"):up();
   523 						local reply = st.error_reply(stanza, "auth", "registration-required"):up();
   514 						reply.tags[1].attr.code = "407";
   524 						reply.tags[1].attr.code = "407";
   515 						origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
   525 						origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
   516 					else -- banned
   526 					else -- banned
   558 				if stanza.attr.id then
   568 				if stanza.attr.id then
   559 					self:_route_stanza(stanza);
   569 					self:_route_stanza(stanza);
   560 				end
   570 				end
   561 				stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
   571 				stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
   562 			else -- message
   572 			else -- message
       
   573 				stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
   563 				stanza.attr.from = current_nick;
   574 				stanza.attr.from = current_nick;
   564 				for jid in pairs(o_data.sessions) do
   575 				for jid in pairs(o_data.sessions) do
   565 					stanza.attr.to = jid;
   576 					stanza.attr.to = jid;
   566 					self:_route_stanza(stanza);
   577 					self:_route_stanza(stanza);
   567 				end
   578 				end
   573 	end
   584 	end
   574 end
   585 end
   575 
   586 
   576 function room_mt:send_form(origin, stanza)
   587 function room_mt:send_form(origin, stanza)
   577 	origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
   588 	origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
   578 		:add_child(self:get_form_layout():form())
   589 		:add_child(self:get_form_layout(stanza.attr.from):form())
   579 	);
   590 	);
   580 end
   591 end
   581 
   592 
   582 function room_mt:get_form_layout()
   593 function room_mt:get_form_layout(actor)
   583 	local form = dataform.new({
   594 	local form = dataform.new({
   584 		title = "Configuration for "..self.jid,
   595 		title = "Configuration for "..self.jid,
   585 		instructions = "Complete and submit this form to configure the room.",
   596 		instructions = "Complete and submit this form to configure the room.",
   586 		{
   597 		{
   587 			name = 'FORM_TYPE',
   598 			name = 'FORM_TYPE',
   602 		},
   613 		},
   603 		{
   614 		{
   604 			name = 'muc#roomconfig_persistentroom',
   615 			name = 'muc#roomconfig_persistentroom',
   605 			type = 'boolean',
   616 			type = 'boolean',
   606 			label = 'Make Room Persistent?',
   617 			label = 'Make Room Persistent?',
   607 			value = self:is_persistent()
   618 			value = self:get_persistent()
   608 		},
   619 		},
   609 		{
   620 		{
   610 			name = 'muc#roomconfig_publicroom',
   621 			name = 'muc#roomconfig_publicroom',
   611 			type = 'boolean',
   622 			type = 'boolean',
   612 			label = 'Make Room Publicly Searchable?',
   623 			label = 'Make Room Publicly Searchable?',
   613 			value = not self:is_hidden()
   624 			value = not self:get_hidden()
   614 		},
   625 		},
   615 		{
   626 		{
   616 			name = 'muc#roomconfig_changesubject',
   627 			name = 'muc#roomconfig_changesubject',
   617 			type = 'boolean',
   628 			type = 'boolean',
   618 			label = 'Allow Occupants to Change Subject?',
   629 			label = 'Allow Occupants to Change Subject?',
   635 		},
   646 		},
   636 		{
   647 		{
   637 			name = 'muc#roomconfig_moderatedroom',
   648 			name = 'muc#roomconfig_moderatedroom',
   638 			type = 'boolean',
   649 			type = 'boolean',
   639 			label = 'Make Room Moderated?',
   650 			label = 'Make Room Moderated?',
   640 			value = self:is_moderated()
   651 			value = self:get_moderated()
   641 		},
   652 		},
   642 		{
   653 		{
   643 			name = 'muc#roomconfig_membersonly',
   654 			name = 'muc#roomconfig_membersonly',
   644 			type = 'boolean',
   655 			type = 'boolean',
   645 			label = 'Make Room Members-Only?',
   656 			label = 'Make Room Members-Only?',
   646 			value = self:is_members_only()
   657 			value = self:get_members_only()
   647 		},
   658 		},
   648 		{
   659 		{
   649 			name = 'muc#roomconfig_historylength',
   660 			name = 'muc#roomconfig_historylength',
   650 			type = 'text-single',
   661 			type = 'text-single',
   651 			label = 'Maximum Number of History Messages Returned by Room',
   662 			label = 'Maximum Number of History Messages Returned by Room',
   652 			value = tostring(self:get_historylength())
   663 			value = tostring(self:get_historylength())
   653 		}
   664 		}
   654 	});
   665 	});
   655 	return module:fire_event("muc-config-form", { room = self, form = form }) or form;
   666 	return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form;
   656 end
   667 end
   657 
       
   658 local valid_whois = {
       
   659 	moderators = true,
       
   660 	anyone = true,
       
   661 }
       
   662 
   668 
   663 function room_mt:process_form(origin, stanza)
   669 function room_mt:process_form(origin, stanza)
   664 	local query = stanza.tags[1];
   670 	local query = stanza.tags[1];
   665 	local form;
   671 	local form;
   666 	for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
   672 	for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
   667 	if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
   673 	if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
   668 	if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
   674 	if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
   669 	if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end
   675 	if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end
   670 
   676 
   671 	local fields = self:get_form_layout():data(form);
   677 	local fields = self:get_form_layout(stanza.attr.from):data(form);
   672 	if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end
   678 	if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end
   673 
   679 
   674 	local dirty = false
   680 
   675 
   681 	local changed = {};
   676 	local event = { room = self, fields = fields, changed = dirty };
   682 
       
   683 	local function handle_option(name, field, allowed)
       
   684 		local new = fields[field];
       
   685 		if new == nil then return; end
       
   686 		if allowed and not allowed[new] then return; end
       
   687 		if new == self["get_"..name](self) then return; end
       
   688 		changed[name] = true;
       
   689 		self["set_"..name](self, new);
       
   690 	end
       
   691 
       
   692 	local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option };
   677 	module:fire_event("muc-config-submitted", event);
   693 	module:fire_event("muc-config-submitted", event);
   678 	dirty = event.changed or dirty;
   694 
   679 
   695 	handle_option("name", "muc#roomconfig_roomname");
   680 	local name = fields['muc#roomconfig_roomname'];
   696 	handle_option("description", "muc#roomconfig_roomdesc");
   681 	if name ~= self:get_name() then
   697 	handle_option("persistent", "muc#roomconfig_persistentroom");
   682 		self:set_name(name);
   698 	handle_option("moderated", "muc#roomconfig_moderatedroom");
   683 	end
   699 	handle_option("members_only", "muc#roomconfig_membersonly");
   684 
   700 	handle_option("public", "muc#roomconfig_publicroom");
   685 	local description = fields['muc#roomconfig_roomdesc'];
   701 	handle_option("changesubject", "muc#roomconfig_changesubject");
   686 	if description ~= self:get_description() then
   702 	handle_option("historylength", "muc#roomconfig_historylength");
   687 		self:set_description(description);
   703 	handle_option("whois", "muc#roomconfig_whois", valid_whois);
   688 	end
   704 	handle_option("password", "muc#roomconfig_roomsecret");
   689 
       
   690 	local persistent = fields['muc#roomconfig_persistentroom'];
       
   691 	dirty = dirty or (self:is_persistent() ~= persistent)
       
   692 	module:log("debug", "persistent=%s", tostring(persistent));
       
   693 
       
   694 	local moderated = fields['muc#roomconfig_moderatedroom'];
       
   695 	dirty = dirty or (self:is_moderated() ~= moderated)
       
   696 	module:log("debug", "moderated=%s", tostring(moderated));
       
   697 
       
   698 	local membersonly = fields['muc#roomconfig_membersonly'];
       
   699 	dirty = dirty or (self:is_members_only() ~= membersonly)
       
   700 	module:log("debug", "membersonly=%s", tostring(membersonly));
       
   701 
       
   702 	local public = fields['muc#roomconfig_publicroom'];
       
   703 	dirty = dirty or (self:is_hidden() ~= (not public and true or nil))
       
   704 
       
   705 	local changesubject = fields['muc#roomconfig_changesubject'];
       
   706 	dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
       
   707 	module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
       
   708 
       
   709 	local historylength = tonumber(fields['muc#roomconfig_historylength']);
       
   710 	dirty = dirty or (historylength and (self:get_historylength() ~= historylength));
       
   711 	module:log('debug', 'historylength=%s', historylength)
       
   712 
       
   713 
       
   714 	local whois = fields['muc#roomconfig_whois'];
       
   715 	if not valid_whois[whois] then
       
   716 	    origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
       
   717 	    return;
       
   718 	end
       
   719 	local whois_changed = self._data.whois ~= whois
       
   720 	self._data.whois = whois
       
   721 	module:log('debug', 'whois=%s', whois)
       
   722 
       
   723 	local password = fields['muc#roomconfig_roomsecret'];
       
   724 	if self:get_password() ~= password then
       
   725 		self:set_password(password);
       
   726 	end
       
   727 	self:set_moderated(moderated);
       
   728 	self:set_members_only(membersonly);
       
   729 	self:set_persistent(persistent);
       
   730 	self:set_hidden(not public);
       
   731 	self:set_changesubject(changesubject);
       
   732 	self:set_historylength(historylength);
       
   733 
   705 
   734 	if self.save then self:save(true); end
   706 	if self.save then self:save(true); end
       
   707 	if self.locked then
       
   708 		module:fire_event("muc-room-unlocked", { room = self });
       
   709 		self.locked = nil;
       
   710 	end
   735 	origin.send(st.reply(stanza));
   711 	origin.send(st.reply(stanza));
   736 
   712 
   737 	if dirty or whois_changed then
   713 	if next(changed) then
   738 		local msg = st.message({type='groupchat', from=self.jid})
   714 		local msg = st.message({type='groupchat', from=self.jid})
   739 			:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up()
   715 			:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up()
   740 
   716 				:tag('status', {code = '104'}):up();
   741 		if dirty then
   717 		if changed.whois then
   742 			msg.tags[1]:tag('status', {code = '104'}):up();
   718 			local code = (self:get_whois() == 'moderators') and "173" or "172";
   743 		end
       
   744 		if whois_changed then
       
   745 			local code = (whois == 'moderators') and "173" or "172";
       
   746 			msg.tags[1]:tag('status', {code = code}):up();
   719 			msg.tags[1]:tag('status', {code = code}):up();
   747 		end
   720 		end
   748 
       
   749 		self:broadcast_message(msg, false)
   721 		self:broadcast_message(msg, false)
   750 	end
   722 	end
   751 end
   723 end
   752 
   724 
   753 function room_mt:destroy(newjid, reason, password)
   725 function room_mt:destroy(newjid, reason, password)
   879 			end
   851 			end
   880 		elseif type == "set" or type == "get" then
   852 		elseif type == "set" or type == "get" then
   881 			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
   853 			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
   882 		end
   854 		end
   883 	elseif stanza.name == "message" and type == "groupchat" then
   855 	elseif stanza.name == "message" and type == "groupchat" then
   884 		local from, to = stanza.attr.from, stanza.attr.to;
   856 		local from = stanza.attr.from;
   885 		local current_nick = self._jid_nick[from];
   857 		local current_nick = self._jid_nick[from];
   886 		local occupant = self._occupants[current_nick];
   858 		local occupant = self._occupants[current_nick];
   887 		if not occupant then -- not in room
   859 		if not occupant then -- not in room
   888 			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
   860 			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
   889 		elseif occupant.role == "visitor" then
   861 		elseif occupant.role == "visitor" then
   890 			origin.send(st.error_reply(stanza, "auth", "forbidden"));
   862 			origin.send(st.error_reply(stanza, "auth", "forbidden"));
   891 		else
   863 		else
   892 			local from = stanza.attr.from;
   864 			local from = stanza.attr.from;
   893 			stanza.attr.from = current_nick;
   865 			stanza.attr.from = current_nick;
   894 			local subject = getText(stanza, {"subject"});
   866 			local subject = stanza:get_child_text("subject");
   895 			if subject then
   867 			if subject then
   896 				if occupant.role == "moderator" or
   868 				if occupant.role == "moderator" or
   897 					( self._data.changesubject and occupant.role == "participant" ) then -- and participant
   869 					( self._data.changesubject and occupant.role == "participant" ) then -- and participant
   898 					self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
   870 					self:set_subject(current_nick, subject);
   899 				else
   871 				else
   900 					stanza.attr.from = from;
   872 					stanza.attr.from = from;
   901 					origin.send(st.error_reply(stanza, "auth", "forbidden"));
   873 					origin.send(st.error_reply(stanza, "auth", "forbidden"));
   902 				end
   874 				end
   903 			else
   875 			else
   941 						:text(_reason or "")
   913 						:text(_reason or "")
   942 					:up()
   914 					:up()
   943 					:tag('body') -- Add a plain message for clients which don't support invites
   915 					:tag('body') -- Add a plain message for clients which don't support invites
   944 						:text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
   916 						:text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
   945 					:up();
   917 					:up();
   946 				if self:is_members_only() and not self:get_affiliation(_invitee) then
   918 				if self:get_members_only() and not self:get_affiliation(_invitee) then
   947 					log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
   919 					log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to);
   948 					self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
   920 					self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from])
   949 				end
   921 				end
   950 				self:_route_stanza(invite);
   922 				self:_route_stanza(invite);
   951 			else
   923 			else