| 1 | -- Prosody IM |
| 2 | -- Copyright (C) 2008-2010 Matthew Wild |
| 3 | -- Copyright (C) 2008-2010 Waqas Hussain |
| 4 | -- |
| 5 | -- This project is MIT/X11 licensed. Please see the |
| 6 | -- COPYING file in the source package for more information. |
| 7 | -- |
| 8 | |
| 9 | |
| 10 | if module:get_host_type() ~= "component" then |
| 11 | error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); |
| 12 | end |
| 13 | |
| 14 | local muc_host = module:get_host(); |
| 15 | local muc_name = module:get_option("name"); |
| 16 | if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end |
| 17 | local restrict_room_creation = module:get_option("restrict_room_creation"); |
| 18 | if restrict_room_creation then |
| 19 | if restrict_room_creation == true then |
| 20 | restrict_room_creation = "admin"; |
| 21 | elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then |
| 22 | restrict_room_creation = nil; |
| 23 | end |
| 24 | end |
| 25 | local muc_new_room = module:require "muc".new_room; |
| 26 | local jid_split = require "util.jid".split; |
| 27 | local jid_bare = require "util.jid".bare; |
| 28 | local st = require "util.stanza"; |
| 29 | local uuid_gen = require "util.uuid".generate; |
| 30 | local datamanager = require "util.datamanager"; |
| 31 | local um_is_admin = require "core.usermanager".is_admin; |
| 32 | |
| 33 | rooms = {}; |
| 34 | local rooms = rooms; |
| 35 | local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {}; |
| 36 | local component = hosts[module.host]; |
| 37 | |
| 38 | -- Configurable options |
| 39 | local max_history_messages = module:get_option_number("max_history_messages"); |
| 40 | |
| 41 | local function is_admin(jid) |
| 42 | return um_is_admin(jid, module.host); |
| 43 | end |
| 44 | |
| 45 | local function room_route_stanza(room, stanza) core_post_stanza(component, stanza); end |
| 46 | local function room_save(room, forced) |
| 47 | local node = jid_split(room.jid); |
| 48 | persistent_rooms[room.jid] = room._data.persistent; |
| 49 | if room._data.persistent then |
| 50 | local history = room._data.history; |
| 51 | room._data.history = nil; |
| 52 | local data = { |
| 53 | jid = room.jid; |
| 54 | _data = room._data; |
| 55 | _affiliations = room._affiliations; |
| 56 | }; |
| 57 | datamanager.store(node, muc_host, "config", data); |
| 58 | room._data.history = history; |
| 59 | elseif forced then |
| 60 | datamanager.store(node, muc_host, "config", nil); |
| 61 | end |
| 62 | if forced then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end |
| 63 | end |
| 64 | |
| 65 | for jid in pairs(persistent_rooms) do |
| 66 | local node = jid_split(jid); |
| 67 | local data = datamanager.load(node, muc_host, "config") or {}; |
| 68 | local room = muc_new_room(jid, { |
| 69 | history_length = max_history_messages; |
| 70 | }); |
| 71 | room._data = data._data; |
| 72 | room._data.history_length = max_history_messages; --TODO: Need to allow per-room with a global limit |
| 73 | room._affiliations = data._affiliations; |
| 74 | room.route_stanza = room_route_stanza; |
| 75 | room.save = room_save; |
| 76 | rooms[jid] = room; |
| 77 | end |
| 78 | |
| 79 | local host_room = muc_new_room(muc_host, { |
| 80 | history_length = max_history_messages; |
| 81 | }); |
| 82 | host_room.route_stanza = room_route_stanza; |
| 83 | host_room.save = room_save; |
| 84 | |
| 85 | local function get_disco_info(stanza) |
| 86 | return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info") |
| 87 | :tag("identity", {category='conference', type='text', name=muc_name}):up() |
| 88 | :tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply |
| 89 | end |
| 90 | local function get_disco_items(stanza) |
| 91 | local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items"); |
| 92 | for jid, room in pairs(rooms) do |
| 93 | if not room:is_hidden() then |
| 94 | reply:tag("item", {jid=jid, name=room:get_name()}):up(); |
| 95 | end |
| 96 | end |
| 97 | return reply; -- TODO cache disco reply |
| 98 | end |
| 99 | |
| 100 | local function handle_to_domain(origin, stanza) |
| 101 | local type = stanza.attr.type; |
| 102 | if type == "error" or type == "result" then return; end |
| 103 | if stanza.name == "iq" and type == "get" then |
| 104 | local xmlns = stanza.tags[1].attr.xmlns; |
| 105 | if xmlns == "http://jabber.org/protocol/disco#info" then |
| 106 | origin.send(get_disco_info(stanza)); |
| 107 | elseif xmlns == "http://jabber.org/protocol/disco#items" then |
| 108 | origin.send(get_disco_items(stanza)); |
| 109 | elseif xmlns == "http://jabber.org/protocol/muc#unique" then |
| 110 | origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions |
| 111 | else |
| 112 | origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc |
| 113 | end |
| 114 | else |
| 115 | host_room:handle_stanza(origin, stanza); |
| 116 | --origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); |
| 117 | end |
| 118 | end |
| 119 | |
| 120 | function stanza_handler(event) |
| 121 | local origin, stanza = event.origin, event.stanza; |
| 122 | local to_node, to_host, to_resource = jid_split(stanza.attr.to); |
| 123 | if to_node then |
| 124 | local bare = to_node.."@"..to_host; |
| 125 | if to_host == muc_host or bare == muc_host then |
| 126 | local room = rooms[bare]; |
| 127 | if not room then |
| 128 | if not(restrict_room_creation) or |
| 129 | (restrict_room_creation == "admin" and is_admin(stanza.attr.from)) or |
| 130 | (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then |
| 131 | room = muc_new_room(bare, { |
| 132 | history_length = max_history_messages; |
| 133 | }); |
| 134 | room.route_stanza = room_route_stanza; |
| 135 | room.save = room_save; |
| 136 | rooms[bare] = room; |
| 137 | end |
| 138 | end |
| 139 | if room then |
| 140 | room:handle_stanza(origin, stanza); |
| 141 | if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room |
| 142 | rooms[bare] = nil; -- discard room |
| 143 | end |
| 144 | else |
| 145 | origin.send(st.error_reply(stanza, "cancel", "not-allowed")); |
| 146 | end |
| 147 | else --[[not for us?]] end |
| 148 | return true; |
| 149 | end |
| 150 | -- to the main muc domain |
| 151 | handle_to_domain(origin, stanza); |
| 152 | return true; |
| 153 | end |
| 154 | module:hook("iq/bare", stanza_handler, -1); |
| 155 | module:hook("message/bare", stanza_handler, -1); |
| 156 | module:hook("presence/bare", stanza_handler, -1); |
| 157 | module:hook("iq/full", stanza_handler, -1); |
| 158 | module:hook("message/full", stanza_handler, -1); |
| 159 | module:hook("presence/full", stanza_handler, -1); |
| 160 | module:hook("iq/host", stanza_handler, -1); |
| 161 | module:hook("message/host", stanza_handler, -1); |
| 162 | module:hook("presence/host", stanza_handler, -1); |
| 163 | |
| 164 | hosts[module.host].send = function(stanza) -- FIXME do a generic fix |
| 165 | if stanza.attr.type == "result" or stanza.attr.type == "error" then |
| 166 | core_post_stanza(component, stanza); |
| 167 | else error("component.send only supports result and error stanzas at the moment"); end |
| 168 | end |
| 169 | |
| 170 | prosody.hosts[module:get_host()].muc = { rooms = rooms }; |
| 171 | |
| 172 | module.save = function() |
| 173 | return {rooms = rooms}; |
| 174 | end |
| 175 | module.restore = function(data) |
| 176 | for jid, oldroom in pairs(data.rooms or {}) do |
| 177 | local room = muc_new_room(jid); |
| 178 | room._jid_nick = oldroom._jid_nick; |
| 179 | room._occupants = oldroom._occupants; |
| 180 | room._data = oldroom._data; |
| 181 | room._affiliations = oldroom._affiliations; |
| 182 | room.route_stanza = room_route_stanza; |
| 183 | room.save = room_save; |
| 184 | rooms[jid] = room; |
| 185 | end |
| 186 | prosody.hosts[module:get_host()].muc = { rooms = rooms }; |
| 187 | end |