1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
|
-- Prosody IM v0.1
-- Copyright (C) 2008 Matthew Wild
-- Copyright (C) 2008 Waqas Hussain
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License
-- as published by the Free Software Foundation; either version 2
-- of the License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
--
local plugin_dir = CFG_PLUGINDIR or "./plugins/";
local logger = require "util.logger";
local log = logger.init("modulemanager");
local addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler;
local eventmanager = require "core.eventmanager";
local config = require "core.configmanager";
local multitable_new = require "util.multitable".new;
local loadfile, pcall = loadfile, pcall;
local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
local pairs, ipairs = pairs, ipairs;
local t_insert = table.insert;
local type = type;
local tostring, print = tostring, print;
-- We need this to let modules access the real global namespace
local _G = _G;
module "modulemanager"
local api = {}; -- Module API container
local modulemap = {};
local m_handler_info = multitable_new();
local m_stanza_handlers = multitable_new();
local handler_info = {};
local stanza_handlers = {};
local modulehelpers = setmetatable({}, { __index = _G });
-- Load modules when a host is activated
function load_modules_for_host(host)
local modules_enabled = config.get(host, "core", "modules_enabled");
if modules_enabled then
for _, module in pairs(modules_enabled) do
load(host, module);
end
end
end
eventmanager.add_event_hook("host-activated", load_modules_for_host);
--
function load(host, module_name, config)
if not (host and module_name) then
return nil, "insufficient-parameters";
end
local mod, err = loadfile(plugin_dir.."mod_"..module_name..".lua");
if not mod then
log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil");
return nil, err;
end
if not modulemap[host] then
modulemap[host] = {};
stanza_handlers[host] = {};
elseif modulemap[host][module_name] then
log("warn", "%s is already loaded for %s, so not loading again", module_name, host);
return nil, "module-already-loaded";
end
local _log = logger.init(host..":"..module_name);
local api_instance = setmetatable({ name = module_name, host = host, config = config, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
setfenv(mod, pluginenv);
local success, ret = pcall(mod);
if not success then
log("error", "Error initialising module '%s': %s", name or "nil", ret or "nil");
return nil, ret;
end
modulemap[host][module_name] = mod;
return true;
end
function is_loaded(host, name)
return modulemap[host] and modulemap[host][name] and true;
end
function unload(host, name, ...)
local mod = modulemap[host] and modulemap[host][name];
if not mod then return nil, "module-not-loaded"; end
if type(mod.unload) == "function" then
local ok, err = pcall(mod.unload, ...)
if (not ok) and err then
log("warn", "Non-fatal error unloading module '%s' from '%s': %s", name, host, err);
end
end
end
function _handle_stanza(host, origin, stanza)
local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
local handlers = stanza_handlers[host];
if not handlers then
log("warn", "No handlers for %s", host);
return false;
end
if name == "iq" and xmlns == "jabber:client" and handlers[origin_type] then
local child = stanza.tags[1];
if child then
local xmlns = child.attr.xmlns or xmlns;
log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
local handler = handlers[origin_type][name] and handlers[origin_type][name][xmlns];
if handler then
log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
return handler(origin, stanza) or true;
end
end
elseif handlers[origin_type] then
local handler = handlers[origin_type][name];
if handler then
handler = handler[xmlns];
if handler then
log("debug", "Passing stanza to mod_%s", handler_info[handler].name);
return handler(origin, stanza) or true;
end
end
end
log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns);
return false; -- we didn't handle it
end
function handle_stanza(host, origin, stanza)
local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
if name == "iq" and xmlns == "jabber:client" then
xmlns = stanza.tags[1].attr.xmlns;
log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
end
local handlers = m_stanza_handlers:get(host, origin_type, name, xmlns);
if handlers then
log("debug", "Passing stanza to mod_%s", handler_info[handlers[1]].name);
(handlers[1])(origin, stanza);
return true;
else
log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns); -- we didn't handle it
end
end
----- API functions exposed to modules -----------
-- Must all be in api.*
-- Returns the name of the current module
function api:get_name()
return self.name;
end
-- Returns the host that the current module is serving
function api:get_host()
return self.host;
end
local function __add_iq_handler(module, origin_type, xmlns, handler)
local handlers = stanza_handlers[module.host];
handlers[origin_type] = handlers[origin_type] or {};
handlers[origin_type].iq = handlers[origin_type].iq or {};
if not handlers[origin_type].iq[xmlns] then
handlers[origin_type].iq[xmlns]= handler;
handler_info[handler] = module;
module:log("debug", "I now handle tag 'iq' [%s] with payload namespace '%s'", origin_type, xmlns);
else
module:log("warn", "I wanted to handle tag 'iq' [%s] with payload namespace '%s' but mod_%s already handles that", origin_type, xmlns, handler_info[handlers[origin_type].iq[xmlns]].name);
end
end
local function _add_iq_handler(module, origin_type, xmlns, handler)
local handlers = m_stanza_handlers:get(module.host, origin_type, "iq", xmlns);
if not handlers then
m_stanza_handlers:add(module.host, origin_type, "iq", xmlns, handler);
handler_info[handler] = module;
module:log("debug", "I now handle tag 'iq' [%s] with payload namespace '%s'", origin_type, xmlns);
else
module:log("warn", "I wanted to handle tag 'iq' [%s] with payload namespace '%s' but mod_%s already handles that", origin_type, xmlns, handler_info[handlers[1]].name);
end
end
function api:add_iq_handler(origin_type, xmlns, handler)
if not (origin_type and handler and xmlns) then return false; end
if type(origin_type) == "table" then
for _, origin_type in ipairs(origin_type) do
_add_iq_handler(self, origin_type, xmlns, handler);
end
return;
end
_add_iq_handler(self, origin_type, xmlns, handler);
end
function api:add_feature(xmlns)
addDiscoInfoHandler(self.host, function(reply, to, from, node)
if #node == 0 then
reply:tag("feature", {var = xmlns}):up();
return true;
end
end);
end
function api:add_event_hook (...) return eventmanager.add_event_hook(...); end
local function __add_handler(module, origin_type, tag, xmlns, handler)
local handlers = stanza_handlers[module.host];
handlers[origin_type] = handlers[origin_type] or {};
if not handlers[origin_type][tag] then
handlers[origin_type][tag] = handlers[origin_type][tag] or {};
handlers[origin_type][tag][xmlns]= handler;
handler_info[handler] = module;
module:log("debug", "I now handle tag '%s' [%s] with xmlns '%s'", tag, origin_type, xmlns);
elseif handler_info[handlers[origin_type][tag]] then
log("warning", "I wanted to handle tag '%s' [%s] but mod_%s already handles that", tag, origin_type, handler_info[handlers[origin_type][tag]].module.name);
end
end
local function _add_handler(module, origin_type, tag, xmlns, handler)
local handlers = m_stanza_handlers:get(module.host, origin_type, tag, xmlns);
if not handlers then
m_stanza_handlers:add(module.host, origin_type, tag, xmlns, handler);
handler_info[handler] = module;
module:log("debug", "I now handle tag '%s' [%s] with xmlns '%s'", tag, origin_type, xmlns);
else
module:log("warning", "I wanted to handle tag '%s' [%s] but mod_%s already handles that", tag, origin_type, handler_info[handlers[1]].module.name);
end
end
function api:add_handler(origin_type, tag, xmlns, handler)
if not (origin_type and tag and xmlns and handler) then return false; end
if type(origin_type) == "table" then
for _, origin_type in ipairs(origin_type) do
_add_handler(self, origin_type, tag, xmlns, handler);
end
return;
end
_add_handler(self, origin_type, tag, xmlns, handler);
end
--------------------------------------------------------------------
return _M;
|