Commit e9040fae3d7aa49ba4536d281bec3a04b8b0dfc4

Authored by dg
1 parent e14c26d9

Added LuaExpat XML parsing library


git-svn-id: http://svn.net-core.org/repos/t-engine4@1297 51575b47-30f0-44d4-a5cc-537603b46e54

Too many changes to show.

To preserve performance only 26 of 26+ files are displayed.

... ... @@ -58,7 +58,7 @@ for i, file in ipairs(fs.list("/settings/")) do
58 58 end
59 59
60 60 -- Load default keys
61   -engine.KeyBind:load("move,actions")
  61 +engine.KeyBind:load("move,actions,debug")
62 62
63 63 -- Load remaps
64 64 if fs.exists("/keybinds.cfg") then
... ...
... ... @@ -261,6 +261,15 @@ Now go and have some fun!]]
261 261 self.step:setKeyHandling()
262 262 self.step:setMouseHandling()
263 263
  264 + self.step.key:addBinds{
  265 + -- Lua console
  266 + LUA_CONSOLE = function()
  267 + if config.settings.tome.cheat then
  268 + self:registerDialog(require("engine.DebugConsole").new())
  269 + end
  270 + end,
  271 + }
  272 +
264 273 if self.s_log then
265 274 local w, h = self.s_log:getSize()
266 275 self.mouse:registerZone(self.w - w, self.h - h, w, h, function(button)
... ...
  1 +local configmanager = require "core.configmanager";
  2 +local log = require "util.logger".init("certmanager");
  3 +local ssl = ssl;
  4 +local ssl_newcontext = ssl and ssl.newcontext;
  5 +
  6 +local setmetatable, tostring = setmetatable, tostring;
  7 +
  8 +local prosody = prosody;
  9 +
  10 +module "certmanager"
  11 +
  12 +-- These are the defaults if not overridden in the config
  13 +local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
  14 +local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
  15 +
  16 +local default_ssl_ctx_mt = { __index = default_ssl_ctx };
  17 +local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in };
  18 +
  19 +-- Global SSL options if not overridden per-host
  20 +local default_ssl_config = configmanager.get("*", "core", "ssl");
  21 +
  22 +function create_context(host, mode, config)
  23 + local ssl_config = config and config.core.ssl or default_ssl_config;
  24 + if ssl and ssl_config then
  25 + local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
  26 + if not ctx then
  27 + err = err or "invalid ssl config"
  28 + local file = err:match("^error loading (.-) %(");
  29 + if file then
  30 + if file == "private key" then
  31 + file = ssl_config.key or "your private key";
  32 + elseif file == "certificate" then
  33 + file = ssl_config.certificate or "your certificate file";
  34 + end
  35 + local reason = err:match("%((.+)%)$") or "some reason";
  36 + if reason == "Permission denied" then
  37 + reason = "Check that the permissions allow Prosody to read this file.";
  38 + elseif reason == "No such file or directory" then
  39 + reason = "Check that the path is correct, and the file exists.";
  40 + elseif reason == "system lib" then
  41 + reason = "Previous error (see logs), or other system error.";
  42 + elseif reason == "(null)" or not reason then
  43 + reason = "Check that the file exists and the permissions are correct";
  44 + else
  45 + reason = "Reason: "..tostring(reason):lower();
  46 + end
  47 + log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
  48 + else
  49 + log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
  50 + end
  51 + ssl = false
  52 + end
  53 + return ctx, err;
  54 + end
  55 + return nil;
  56 +end
  57 +
  58 +function reload_ssl_config()
  59 + default_ssl_config = configmanager.get("*", "core", "ssl");
  60 +end
  61 +
  62 +prosody.events.add_handler("config-reloaded", reload_ssl_config);
  63 +
  64 +return _M;
... ...
  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 +local prosody = _G.prosody;
  10 +local log = require "util.logger".init("componentmanager");
  11 +local certmanager = require "core.certmanager";
  12 +local configmanager = require "core.configmanager";
  13 +local modulemanager = require "core.modulemanager";
  14 +local jid_split = require "util.jid".split;
  15 +local fire_event = require "core.eventmanager".fire_event;
  16 +local events_new = require "util.events".new;
  17 +local st = require "util.stanza";
  18 +local prosody, hosts = prosody, prosody.hosts;
  19 +local ssl = ssl;
  20 +local uuid_gen = require "util.uuid".generate;
  21 +
  22 +local pairs, setmetatable, type, tostring = pairs, setmetatable, type, tostring;
  23 +
  24 +local components = {};
  25 +
  26 +local disco_items = require "util.multitable".new();
  27 +local NULL = {};
  28 +
  29 +module "componentmanager"
  30 +
  31 +local function default_component_handler(origin, stanza)
  32 + log("warn", "Stanza being handled by default component; bouncing error for: %s", stanza:top_tag());
  33 + if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
  34 + origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
  35 + end
  36 +end
  37 +
  38 +function load_enabled_components(config)
  39 + local defined_hosts = config or configmanager.getconfig();
  40 +
  41 + for host, host_config in pairs(defined_hosts) do
  42 + if host ~= "*" and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
  43 + hosts[host] = create_component(host);
  44 + hosts[host].connected = false;
  45 + components[host] = default_component_handler;
  46 + local ok, err = modulemanager.load(host, host_config.core.component_module);
  47 + if not ok then
  48 + log("error", "Error loading %s component %s: %s", tostring(host_config.core.component_module), tostring(host), tostring(err));
  49 + else
  50 + fire_event("component-activated", host, host_config);
  51 + log("debug", "Activated %s component: %s", host_config.core.component_module, host);
  52 + end
  53 + end
  54 + end
  55 +end
  56 +
  57 +if prosody and prosody.events then
  58 + prosody.events.add_handler("server-starting", load_enabled_components);
  59 +end
  60 +
  61 +function handle_stanza(origin, stanza)
  62 + local node, host = jid_split(stanza.attr.to);
  63 + local component = nil;
  64 + if host then
  65 + if node then component = components[node.."@"..host]; end -- hack to allow hooking node@server
  66 + if not component then component = components[host]; end
  67 + end
  68 + if component then
  69 + log("debug", "%s stanza being handled by component: %s", stanza.name, host);
  70 + component(origin, stanza, hosts[host]);
  71 + else
  72 + log("error", "Component manager recieved a stanza for a non-existing component: "..tostring(stanza));
  73 + default_component_handler(origin, stanza);
  74 + end
  75 +end
  76 +
  77 +function create_component(host, component, events)
  78 + -- TODO check for host well-formedness
  79 + local ssl_ctx, ssl_ctx_in;
  80 + if host and ssl then
  81 + -- We need to find SSL context to use...
  82 + -- Discussion in prosody@ concluded that
  83 + -- 1 level back is usually enough by default
  84 + local base_host = host:gsub("^[^%.]+%.", "");
  85 + if hosts[base_host] then
  86 + ssl_ctx = hosts[base_host].ssl_ctx;
  87 + ssl_ctx_in = hosts[base_host].ssl_ctx_in;
  88 + else
  89 + -- We have no cert, and no parent host to borrow a cert from
  90 + -- Use global/default cert if there is one
  91 + ssl_ctx = certmanager.create_context(host, "client");
  92 + ssl_ctx_in = certmanager.create_context(host, "server");
  93 + end
  94 + end
  95 + return { type = "component", host = host, connected = true, s2sout = {},
  96 + ssl_ctx = ssl_ctx, ssl_ctx_in = ssl_ctx_in, events = events or events_new(),
  97 + dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen() };
  98 +end
  99 +
  100 +function register_component(host, component, session)
  101 + if not hosts[host] or (hosts[host].type == 'component' and not hosts[host].connected) then
  102 + local old_events = hosts[host] and hosts[host].events;
  103 +
  104 + components[host] = component;
  105 + hosts[host] = session or create_component(host, component, old_events);
  106 +
  107 + -- Add events object if not already one
  108 + if not hosts[host].events then
  109 + hosts[host].events = old_events or events_new();
  110 + end
  111 +
  112 + if not hosts[host].dialback_secret then
  113 + hosts[host].dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
  114 + end
  115 +
  116 + -- add to disco_items
  117 + if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
  118 + disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
  119 + end
  120 + modulemanager.load(host, "dialback");
  121 + modulemanager.load(host, "tls");
  122 + log("debug", "component added: "..host);
  123 + return session or hosts[host];
  124 + else
  125 + log("error", "Attempt to set component for existing host: "..host);
  126 + end
  127 +end
  128 +
  129 +function deregister_component(host)
  130 + if components[host] then
  131 + modulemanager.unload(host, "tls");
  132 + modulemanager.unload(host, "dialback");
  133 + hosts[host].connected = nil;
  134 + local host_config = configmanager.getconfig()[host];
  135 + if host_config and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
  136 + -- Set default handler
  137 + components[host] = default_component_handler;
  138 + else
  139 + -- Component not in config, or disabled, remove
  140 + hosts[host] = nil; -- FIXME do proper unload of all modules and other cleanup before removing
  141 + components[host] = nil;
  142 + end
  143 + -- remove from disco_items
  144 + if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
  145 + disco_items:remove(host:sub(host:find(".", 1, true)+1), host);
  146 + end
  147 + log("debug", "component removed: "..host);
  148 + return true;
  149 + else
  150 + log("error", "Attempt to remove component for non-existing host: "..host);
  151 + end
  152 +end
  153 +
  154 +function set_component_handler(host, handler)
  155 + components[host] = handler;
  156 +end
  157 +
  158 +function get_children(host)
  159 + return disco_items:get(host) or NULL;
  160 +end
  161 +
  162 +return _M;
... ...
  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 +
  11 +local _G = _G;
  12 +local setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, format =
  13 + setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, string.format;
  14 +
  15 +
  16 +local eventmanager = require "core.eventmanager";
  17 +
  18 +module "configmanager"
  19 +
  20 +local parsers = {};
  21 +
  22 +local config = { ["*"] = { core = {} } };
  23 +
  24 +local global_config = config["*"];
  25 +
  26 +-- When host not found, use global
  27 +setmetatable(config, { __index = function () return global_config; end});
  28 +local host_mt = { __index = global_config };
  29 +
  30 +-- When key not found in section, check key in global's section
  31 +function section_mt(section_name)
  32 + return { __index = function (t, k)
  33 + local section = rawget(global_config, section_name);
  34 + if not section then return nil; end
  35 + return section[k];
  36 + end };
  37 +end
  38 +
  39 +function getconfig()
  40 + return config;
  41 +end
  42 +
  43 +function get(host, section, key)
  44 + local sec = config[host][section];
  45 + if sec then
  46 + return sec[key];
  47 + end
  48 + return nil;
  49 +end
  50 +
  51 +function set(host, section, key, value)
  52 + if host and section and key then
  53 + local hostconfig = rawget(config, host);
  54 + if not hostconfig then
  55 + hostconfig = rawset(config, host, setmetatable({}, host_mt))[host];
  56 + end
  57 + if not rawget(hostconfig, section) then
  58 + hostconfig[section] = setmetatable({}, section_mt(section));
  59 + end
  60 + hostconfig[section][key] = value;
  61 + return true;
  62 + end
  63 + return false;
  64 +end
  65 +
  66 +function load(filename, format)
  67 + format = format or filename:match("%w+$");
  68 +
  69 + if parsers[format] and parsers[format].load then
  70 + local f, err = io.open(filename);
  71 + if f then
  72 + local ok, err = parsers[format].load(f:read("*a"), filename);
  73 + f:close();
  74 + if ok then
  75 + eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
  76 + end
  77 + return ok, "parser", err;
  78 + end
  79 + return f, "file", err;
  80 + end
  81 +
  82 + if not format then
  83 + return nil, "file", "no parser specified";
  84 + else
  85 + return nil, "file", "no parser for "..(format);
  86 + end
  87 +end
  88 +
  89 +function save(filename, format)
  90 +end
  91 +
  92 +function addparser(format, parser)
  93 + if format and parser then
  94 + parsers[format] = parser;
  95 + end
  96 +end
  97 +
  98 +-- _M needed to avoid name clash with local 'parsers'
  99 +function _M.parsers()
  100 + local p = {};
  101 + for format in pairs(parsers) do
  102 + table.insert(p, format);
  103 + end
  104 + return p;
  105 +end
  106 +
  107 +-- Built-in Lua parser
  108 +do
  109 + local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
  110 + local setfenv, rawget, tostring = _G.setfenv, _G.rawget, _G.tostring;
  111 + parsers.lua = {};
  112 + function parsers.lua.load(data, filename)
  113 + local env;
  114 + -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
  115 + env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true,
  116 + Include = true, include = true, RunScript = dofile }, { __index = function (t, k)
  117 + return rawget(_G, k) or
  118 + function (settings_table)
  119 + config[__currenthost or "*"][k] = settings_table;
  120 + end;
  121 + end,
  122 + __newindex = function (t, k, v)
  123 + set(env.__currenthost or "*", "core", k, v);
  124 + end});
  125 +
  126 + rawset(env, "__currenthost", "*") -- Default is global
  127 + function env.VirtualHost(name)
  128 + if rawget(config, name) and rawget(config[name].core, "component_module") then
  129 + error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
  130 + name, config[name].core.component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
  131 + end
  132 + rawset(env, "__currenthost", name);
  133 + -- Needs at least one setting to logically exist :)
  134 + set(name or "*", "core", "defined", true);
  135 + end
  136 + env.Host, env.host = env.VirtualHost, env.VirtualHost;
  137 +
  138 + function env.Component(name)
  139 + if rawget(config, name) and rawget(config[name].core, "defined") and not rawget(config[name].core, "component_module") then
  140 + error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
  141 + name, name, name), 0);
  142 + end
  143 + set(name, "core", "component_module", "component");
  144 + -- Don't load the global modules by default
  145 + set(name, "core", "load_global_modules", false);
  146 + rawset(env, "__currenthost", name);
  147 +
  148 + return function (module)
  149 + if type(module) == "string" then
  150 + set(name, "core", "component_module", module);
  151 + end
  152 + end
  153 + end
  154 + env.component = env.Component;
  155 +
  156 + function env.Include(file)
  157 + local f, err = io.open(file);
  158 + if f then
  159 + local data = f:read("*a");
  160 + local ok, err = parsers.lua.load(data, file);
  161 + if not ok then error(err:gsub("%[string.-%]", file), 0); end
  162 + end
  163 + if not f then error("Error loading included "..file..": "..err, 0); end
  164 + return f, err;
  165 + end
  166 + env.include = env.Include;
  167 +
  168 + local chunk, err = loadstring(data, "@"..filename);
  169 +
  170 + if not chunk then
  171 + return nil, err;
  172 + end
  173 +
  174 + setfenv(chunk, env);
  175 +
  176 + local ok, err = pcall(chunk);
  177 +
  178 + if not ok then
  179 + return nil, err;
  180 + end
  181 +
  182 + return true;
  183 + end
  184 +
  185 +end
  186 +
  187 +return _M;
... ...
  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 +local t_insert = table.insert;
  11 +local ipairs = ipairs;
  12 +
  13 +module "eventmanager"
  14 +
  15 +local event_handlers = {};
  16 +
  17 +function add_event_hook(name, handler)
  18 + if not event_handlers[name] then
  19 + event_handlers[name] = {};
  20 + end
  21 + t_insert(event_handlers[name] , handler);
  22 +end
  23 +
  24 +function fire_event(name, ...)
  25 + local event_handlers = event_handlers[name];
  26 + if event_handlers then
  27 + for name, handler in ipairs(event_handlers) do
  28 + handler(...);
  29 + end
  30 + end
  31 +end
  32 +
  33 +return _M;
\ No newline at end of file
... ...
  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 +local ssl = ssl
  10 +
  11 +local hosts = hosts;
  12 +local certmanager = require "core.certmanager";
  13 +local configmanager = require "core.configmanager";
  14 +local eventmanager = require "core.eventmanager";
  15 +local modulemanager = require "core.modulemanager";
  16 +local events_new = require "util.events".new;
  17 +
  18 +local uuid_gen = require "util.uuid".generate;
  19 +
  20 +if not _G.prosody.incoming_s2s then
  21 + require "core.s2smanager";
  22 +end
  23 +local incoming_s2s = _G.prosody.incoming_s2s;
  24 +
  25 +local log = require "util.logger".init("hostmanager");
  26 +
  27 +local pairs, setmetatable = pairs, setmetatable;
  28 +
  29 +module "hostmanager"
  30 +
  31 +local hosts_loaded_once;
  32 +
  33 +local function load_enabled_hosts(config)
  34 + local defined_hosts = config or configmanager.getconfig();
  35 + local activated_any_host;
  36 +
  37 + for host, host_config in pairs(defined_hosts) do
  38 + if host ~= "*" and host_config.core.enabled ~= false and not host_config.core.component_module then
  39 + activated_any_host = true;
  40 + activate(host, host_config);
  41 + end
  42 + end
  43 +
  44 + if not activated_any_host then
  45 + log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded.");
  46 + end
  47 +
  48 + eventmanager.fire_event("hosts-activated", defined_hosts);
  49 + hosts_loaded_once = true;
  50 +end
  51 +
  52 +eventmanager.add_event_hook("server-starting", load_enabled_hosts);
  53 +
  54 +function activate(host, host_config)
  55 + hosts[host] = {type = "local", connected = true, sessions = {},
  56 + host = host, s2sout = {}, events = events_new(),
  57 + disallow_s2s = configmanager.get(host, "core", "disallow_s2s")
  58 + or (configmanager.get(host, "core", "anonymous_login")
  59 + and (configmanager.get(host, "core", "disallow_s2s") ~= false));
  60 + dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
  61 + };
  62 + for option_name in pairs(host_config.core) do
  63 + if option_name:match("_ports$") or option_name:match("_interface$") then
  64 + log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name);
  65 + end
  66 + end
  67 +
  68 + hosts[host].ssl_ctx = certmanager.create_context(host, "client", host_config); -- for outgoing connections
  69 + hosts[host].ssl_ctx_in = certmanager.create_context(host, "server", host_config); -- for incoming connections
  70 +
  71 + log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
  72 + eventmanager.fire_event("host-activated", host, host_config);
  73 +end
  74 +
  75 +function deactivate(host, reason)
  76 + local host_session = hosts[host];
  77 + log("info", "Deactivating host: %s", host);
  78 + eventmanager.fire_event("host-deactivating", host, host_session);
  79 +
  80 + reason = reason or { condition = "host-gone", text = "This server has stopped serving "..host };
  81 +
  82 + -- Disconnect local users, s2s connections
  83 + if host_session.sessions then
  84 + for username, user in pairs(host_session.sessions) do
  85 + for resource, session in pairs(user.sessions) do
  86 + log("debug", "Closing connection for %s@%s/%s", username, host, resource);
  87 + session:close(reason);
  88 + end
  89 + end
  90 + end
  91 + if host_session.s2sout then
  92 + for remotehost, session in pairs(host_session.s2sout) do
  93 + if session.close then
  94 + log("debug", "Closing outgoing connection to %s", remotehost);
  95 + if session.srv_hosts then session.srv_hosts = nil; end
  96 + session:close(reason);
  97 + end
  98 + end
  99 + end
  100 + for remote_session in pairs(incoming_s2s) do
  101 + if remote_session.to_host == host then
  102 + log("debug", "Closing incoming connection from %s", remote_session.from_host or "<unknown>");
  103 + remote_session:close(reason);
  104 + end
  105 + end
  106 +
  107 + if host_session.modules then
  108 + for module in pairs(host_session.modules) do
  109 + modulemanager.unload(host, module);
  110 + end
  111 + end
  112 +
  113 + hosts[host] = nil;
  114 + eventmanager.fire_event("host-deactivated", host);
  115 + log("info", "Deactivated host: %s", host);
  116 +end
  117 +
  118 +function getconfig(name)
  119 +end
  120 +
  121 +return _M;
... ...
  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 +local format, rep = string.format, string.rep;
  11 +local pcall = pcall;
  12 +local debug = debug;
  13 +local tostring, setmetatable, rawset, pairs, ipairs, type =
  14 + tostring, setmetatable, rawset, pairs, ipairs, type;
  15 +local io_open, io_write = io.open, io.write;
  16 +local math_max, rep = math.max, string.rep;
  17 +local os_date, os_getenv = os.date, os.getenv;
  18 +local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
  19 +
  20 +if os.getenv("__FLUSH_LOG") then
  21 + local io_flush = io.flush;
  22 + local _io_write = io_write;
  23 + io_write = function(...) _io_write(...); io_flush(); end
  24 +end
  25 +
  26 +local config = require "core.configmanager";
  27 +local eventmanager = require "core.eventmanager";
  28 +local logger = require "util.logger";
  29 +local debug_mode = config.get("*", "core", "debug");
  30 +
  31 +_G.log = logger.init("general");
  32 +
  33 +module "loggingmanager"
  34 +
  35 +-- The log config used if none specified in the config file
  36 +local default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
  37 +local default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } };
  38 +local default_timestamp = "%b %d %H:%M:%S";
  39 +-- The actual config loggingmanager is using
  40 +local logging_config = config.get("*", "core", "log") or default_logging;
  41 +
  42 +local apply_sink_rules;
  43 +local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
  44 +local get_levels;
  45 +local logging_levels = { "debug", "info", "warn", "error", "critical" }
  46 +
  47 +-- Put a rule into action. Requires that the sink type has already been registered.
  48 +-- This function is called automatically when a new sink type is added [see apply_sink_rules()]
  49 +local function add_rule(sink_config)
  50 + local sink_maker = log_sink_types[sink_config.to];
  51 + if sink_maker then
  52 + if sink_config.levels and not sink_config.source then
  53 + -- Create sink
  54 + local sink = sink_maker(sink_config);
  55 +
  56 + -- Set sink for all chosen levels
  57 + for level in pairs(get_levels(sink_config.levels)) do
  58 + logger.add_level_sink(level, sink);
  59 + end
  60 + elseif sink_config.source and not sink_config.levels then
  61 + logger.add_name_sink(sink_config.source, sink_maker(sink_config));
  62 + elseif sink_config.source and sink_config.levels then
  63 + local levels = get_levels(sink_config.levels);
  64 + local sink = sink_maker(sink_config);
  65 + logger.add_name_sink(sink_config.source,
  66 + function (name, level, ...)
  67 + if levels[level] then
  68 + return sink(name, level, ...);
  69 + end
  70 + end);
  71 + else
  72 + -- All sources
  73 + -- Create sink
  74 + local sink = sink_maker(sink_config);
  75 +
  76 + -- Set sink for all levels
  77 + for _, level in pairs(logging_levels) do
  78 + logger.add_level_sink(level, sink);
  79 + end
  80 + end
  81 + else
  82 + -- No such sink type
  83 + end
  84 +end
  85 +
  86 +-- Search for all rules using a particular sink type, and apply
  87 +-- them. Called automatically when a new sink type is added to
  88 +-- the log_sink_types table.
  89 +function apply_sink_rules(sink_type)
  90 + if type(logging_config) == "table" then
  91 + for _, sink_config in pairs(logging_config) do
  92 + if sink_config.to == sink_type then
  93 + add_rule(sink_config);
  94 + end
  95 + end
  96 + elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then
  97 + -- User specified simply a filename, and the "file" sink type
  98 + -- was just added
  99 + for _, sink_config in pairs(default_file_logging) do
  100 + sink_config.filename = logging_config;
  101 + add_rule(sink_config);
  102 + sink_config.filename = nil;
  103 + end
  104 + elseif type(logging_config) == "string" and logging_config:match("^%*(.+)") == sink_type then
  105 + -- Log all levels (debug+) to this sink
  106 + add_rule({ levels = { min = "debug" }, to = sink_type });
  107 + end
  108 +end
  109 +
  110 +
  111 +
  112 +--- Helper function to get a set of levels given a "criteria" table
  113 +function get_levels(criteria, set)
  114 + set = set or {};
  115 + if type(criteria) == "string" then
  116 + set[criteria] = true;
  117 + return set;
  118 + end
  119 + local min, max = criteria.min, criteria.max;
  120 + if min or max then
  121 + local in_range;
  122 + for _, level in ipairs(logging_levels) do
  123 + if min == level then
  124 + set[level] = true;
  125 + in_range = true;
  126 + elseif max == level then
  127 + set[level] = true;
  128 + return set;
  129 + elseif in_range then
  130 + set[level] = true;
  131 + end
  132 + end
  133 + end
  134 +
  135 + for _, level in ipairs(criteria) do
  136 + set[level] = true;
  137 + end
  138 + return set;
  139 +end
  140 +
  141 +--- Definition of built-in logging sinks ---
  142 +
  143 +-- Null sink, must enter log_sink_types *first*
  144 +function log_sink_types.nowhere()
  145 + return function () return false; end;
  146 +end
  147 +
  148 +-- Column width for "source" (used by stdout and console)
  149 +local sourcewidth = 20;
  150 +
  151 +function log_sink_types.stdout()
  152 + local timestamps = config.timestamps;
  153 +
  154 + if timestamps == true then
  155 + timestamps = default_timestamp; -- Default format
  156 + end
  157 +
  158 + return function (name, level, message, ...)
  159 + sourcewidth = math_max(#name+2, sourcewidth);
  160 + local namelen = #name;
  161 + if timestamps then
  162 + io_write(os_date(timestamps), " ");
  163 + end
  164 + if ... then
  165 + io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n");
  166 + else
  167 + io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n");
  168 + end
  169 + end
  170 +end
  171 +
  172 +do
  173 + local do_pretty_printing = not os_getenv("WINDIR");
  174 +
  175 + local logstyles = {};
  176 + if do_pretty_printing then
  177 + logstyles["info"] = getstyle("bold");
  178 + logstyles["warn"] = getstyle("bold", "yellow");
  179 + logstyles["error"] = getstyle("bold", "red");
  180 + end
  181 + function log_sink_types.console(config)
  182 + -- Really if we don't want pretty colours then just use plain stdout
  183 + if not do_pretty_printing then
  184 + return log_sink_types.stdout(config);
  185 + end
  186 +
  187 + local timestamps = config.timestamps;
  188 +
  189 + if timestamps == true then
  190 + timestamps = default_timestamp; -- Default format
  191 + end
  192 +
  193 + return function (name, level, message, ...)
  194 + sourcewidth = math_max(#name+2, sourcewidth);
  195 + local namelen = #name;
  196 +
  197 + if timestamps then
  198 + io_write(os_date(timestamps), " ");
  199 + end
  200 + if ... then
  201 + io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", format(message, ...), "\n");
  202 + else
  203 + io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", message, "\n");
  204 + end
  205 + end
  206 + end
  207 +end
  208 +
  209 +local empty_function = function () end;
  210 +function log_sink_types.file(config)
  211 + local log = config.filename;
  212 + local logfile = io_open(log, "a+");
  213 + if not logfile then
  214 + return empty_function;
  215 + end
  216 + local write, flush = logfile.write, logfile.flush;
  217 +
  218 + eventmanager.add_event_hook("reopen-log-files", function ()
  219 + if logfile then
  220 + logfile:close();
  221 + end
  222 + logfile = io_open(log, "a+");
  223 + if not logfile then
  224 + write, flush = empty_function, empty_function;
  225 + else
  226 + write, flush = logfile.write, logfile.flush;
  227 + end
  228 + end);
  229 +
  230 + local timestamps = config.timestamps;
  231 +
  232 + if timestamps == nil or timestamps == true then
  233 + timestamps = default_timestamp; -- Default format
  234 + end
  235 +
  236 + return function (name, level, message, ...)
  237 + if timestamps then
  238 + write(logfile, os_date(timestamps), " ");
  239 + end
  240 + if ... then
  241 + write(logfile, name, "\t", level, "\t", format(message, ...), "\n");
  242 + else
  243 + write(logfile, name, "\t" , level, "\t", message, "\n");
  244 + end
  245 + flush(logfile);
  246 + end;
  247 +end
  248 +
  249 +function register_sink_type(name, sink_maker)
  250 + local old_sink_maker = log_sink_types[name];
  251 + log_sink_types[name] = sink_maker;
  252 + return old_sink_maker;
  253 +end
  254 +
  255 +return _M;
... ...
  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 +local plugin_dir = CFG_PLUGINDIR or "./plugins/";
  10 +
  11 +local logger = require "util.logger";
  12 +local log = logger.init("modulemanager");
  13 +local eventmanager = require "core.eventmanager";
  14 +local config = require "core.configmanager";
  15 +local multitable_new = require "util.multitable".new;
  16 +local st = require "util.stanza";
  17 +local pluginloader = require "util.pluginloader";
  18 +
  19 +local hosts = hosts;
  20 +local prosody = prosody;
  21 +
  22 +local loadfile, pcall, xpcall = loadfile, pcall, xpcall;
  23 +local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
  24 +local pairs, ipairs = pairs, ipairs;
  25 +local t_insert, t_concat = table.insert, table.concat;
  26 +local type = type;
  27 +local next = next;
  28 +local rawget = rawget;
  29 +local error = error;
  30 +local tostring, tonumber = tostring, tonumber;
  31 +
  32 +local debug_traceback = debug.traceback;
  33 +local unpack, select = unpack, select;
  34 +pcall = function(f, ...)
  35 + local n = select("#", ...);
  36 + local params = {...};
  37 + return xpcall(function() f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
  38 +end
  39 +
  40 +local array, set = require "util.array", require "util.set";
  41 +
  42 +local autoload_modules = {"presence", "message", "iq"};
  43 +
  44 +-- We need this to let modules access the real global namespace
  45 +local _G = _G;
  46 +
  47 +module "modulemanager"
  48 +
  49 +api = {};
  50 +local api = api; -- Module API container
  51 +
  52 +local modulemap = { ["*"] = {} };
  53 +
  54 +local stanza_handlers = multitable_new();
  55 +local handler_info = {};
  56 +
  57 +local modulehelpers = setmetatable({}, { __index = _G });
  58 +
  59 +local handler_table = multitable_new();
  60 +local hooked = multitable_new();
  61 +local hooks = multitable_new();
  62 +local event_hooks = multitable_new();
  63 +
  64 +local NULL = {};
  65 +
  66 +-- Load modules when a host is activated
  67 +function load_modules_for_host(host)
  68 + local disabled_set = {};
  69 + local modules_disabled = config.get(host, "core", "modules_disabled");
  70 + if modules_disabled then
  71 + for _, module in ipairs(modules_disabled) do
  72 + disabled_set[module] = true;
  73 + end
  74 + end
  75 +
  76 + -- Load auto-loaded modules for this host
  77 + if hosts[host].type == "local" then
  78 + for _, module in ipairs(autoload_modules) do
  79 + if not disabled_set[module] then
  80 + load(host, module);
  81 + end
  82 + end
  83 + end
  84 +
  85 + -- Load modules from global section
  86 + if config.get(host, "core", "load_global_modules") ~= false then
  87 + local modules_enabled = config.get("*", "core", "modules_enabled");
  88 + if modules_enabled then
  89 + for _, module in ipairs(modules_enabled) do
  90 + if not disabled_set[module] and not is_loaded(host, module) then
  91 + load(host, module);
  92 + end
  93 + end
  94 + end
  95 + end
  96 +
  97 + -- Load modules from just this host
  98 + local modules_enabled = config.get(host, "core", "modules_enabled");
  99 + if modules_enabled and modules_enabled ~= config.get("*", "core", "modules_enabled") then
  100 + for _, module in pairs(modules_enabled) do
  101 + if not is_loaded(host, module) then
  102 + load(host, module);
  103 + end
  104 + end
  105 + end
  106 +end
  107 +eventmanager.add_event_hook("host-activated", load_modules_for_host);
  108 +eventmanager.add_event_hook("component-activated", load_modules_for_host);
  109 +--
  110 +
  111 +function load(host, module_name, config)
  112 + if not (host and module_name) then
  113 + return nil, "insufficient-parameters";
  114 + end
  115 +
  116 + if not modulemap[host] then
  117 + modulemap[host] = {};
  118 + end
  119 +
  120 + if modulemap[host][module_name] then
  121 + log("warn", "%s is already loaded for %s, so not loading again", module_name, host);
  122 + return nil, "module-already-loaded";
  123 + elseif modulemap["*"][module_name] then
  124 + return nil, "global-module-already-loaded";
  125 + end
  126 +
  127 +
  128 + local mod, err = pluginloader.load_code(module_name);
  129 + if not mod then
  130 + log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil");
  131 + return nil, err;
  132 + end
  133 +
  134 + local _log = logger.init(host..":"..module_name);
  135 + local api_instance = setmetatable({ name = module_name, host = host, config = config, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
  136 +
  137 + local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
  138 + api_instance.environment = pluginenv;
  139 +
  140 + setfenv(mod, pluginenv);
  141 + if not hosts[host] then
  142 + local create_component = _G.require "core.componentmanager".create_component;
  143 + hosts[host] = create_component(host);
  144 + hosts[host].connected = false;
  145 + log("debug", "Created new component: %s", host);
  146 + end
  147 + hosts[host].modules = modulemap[host];
  148 + modulemap[host][module_name] = pluginenv;
  149 +
  150 + local success, err = pcall(mod);
  151 + if success then
  152 + if module_has_method(pluginenv, "load") then
  153 + success, err = call_module_method(pluginenv, "load");
  154 + if not success then
  155 + log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil");
  156 + end
  157 + end
  158 +
  159 + -- Use modified host, if the module set one
  160 + if api_instance.host == "*" and host ~= "*" then
  161 + modulemap[host][module_name] = nil;
  162 + modulemap["*"][module_name] = pluginenv;
  163 + api_instance:set_global();
  164 + end
  165 + else
  166 + log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
  167 + end
  168 + if success then
  169 + (hosts[api_instance.host] or prosody).events.fire_event("module-loaded", { module = module_name, host = host });
  170 + return true;
  171 + else -- load failed, unloading
  172 + unload(api_instance.host, module_name);
  173 + return nil, err;
  174 + end
  175 +end
  176 +
  177 +function get_module(host, name)
  178 + return modulemap[host] and modulemap[host][name];
  179 +end
  180 +
  181 +function is_loaded(host, name)
  182 + return modulemap[host] and modulemap[host][name] and true;
  183 +end
  184 +
  185 +function unload(host, name, ...)
  186 + local mod = get_module(host, name);
  187 + if not mod then return nil, "module-not-loaded"; end
  188 +
  189 + if module_has_method(mod, "unload") then
  190 + local ok, err = call_module_method(mod, "unload");
  191 + if (not ok) and err then
  192 + log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
  193 + end
  194 + end
  195 + local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
  196 + for _, param in pairs(params or NULL) do
  197 + local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
  198 + if handlers then
  199 + handler_info[handlers[1]] = nil;
  200 + stanza_handlers:remove(param[1], param[2], param[3], param[4]);
  201 + end
  202 + end
  203 + event_hooks:remove(host, name);
  204 + -- unhook event handlers hooked by module:hook
  205 + for event, handlers in pairs(hooks:get(host, name) or NULL) do
  206 + for handler in pairs(handlers or NULL) do
  207 + (hosts[host] or prosody).events.remove_handler(event, handler);
  208 + end
  209 + end
  210 + hooks:remove(host, name);
  211 + if mod.module.items then -- remove items
  212 + for key,t in pairs(mod.module.items) do
  213 + for i = #t,1,-1 do
  214 + local value = t[i];
  215 + t[i] = nil;
  216 + hosts[host].events.fire_event("item-removed/"..key, {source = self, item = value});
  217 + end
  218 + end
  219 + end
  220 + modulemap[host][name] = nil;
  221 + (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
  222 + return true;
  223 +end
  224 +
  225 +function reload(host, name, ...)
  226 + local mod = get_module(host, name);
  227 + if not mod then return nil, "module-not-loaded"; end
  228 +
  229 + local _mod, err = pluginloader.load_code(name); -- checking for syntax errors
  230 + if not _mod then
  231 + log("error", "Unable to load module '%s': %s", name or "nil", err or "nil");
  232 + return nil, err;
  233 + end
  234 +
  235 + local saved;
  236 +
  237 + if module_has_method(mod, "save") then
  238 + local ok, ret, err = call_module_method(mod, "save");
  239 + if ok then
  240 + saved = ret;
  241 + else
  242 + log("warn", "Error saving module '%s:%s' state: %s", host, module, ret);
  243 + if not config.get(host, "core", "force_module_reload") then
  244 + log("warn", "Aborting reload due to error, set force_module_reload to ignore this");
  245 + return nil, "save-state-failed";
  246 + else
  247 + log("warn", "Continuing with reload (using the force)");
  248 + end
  249 + end
  250 + end
  251 +
  252 + unload(host, name, ...);
  253 + local ok, err = load(host, name, ...);
  254 + if ok then
  255 + mod = get_module(host, name);
  256 + if module_has_method(mod, "restore") then
  257 + local ok, err = call_module_method(mod, "restore", saved or {})
  258 + if (not ok) and err then
  259 + log("warn", "Error restoring module '%s' from '%s': %s", name, host, err);
  260 + end
  261 + end
  262 + return true;
  263 + end
  264 + return ok, err;
  265 +end
  266 +
  267 +function handle_stanza(host, origin, stanza)
  268 + local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
  269 + if name == "iq" and xmlns == "jabber:client" then
  270 + if stanza.attr.type == "get" or stanza.attr.type == "set" then
  271 + xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
  272 + log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
  273 + else
  274 + log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type);
  275 + return true;
  276 + end
  277 + end
  278 + local handlers = stanza_handlers:get(host, origin_type, name, xmlns);
  279 + if not handlers then handlers = stanza_handlers:get("*", origin_type, name, xmlns); end
  280 + if handlers then
  281 + log("debug", "Passing stanza to mod_%s", handler_info[handlers[1]].name);
  282 + (handlers[1])(origin, stanza);
  283 + return true;
  284 + else
  285 + if stanza.attr.xmlns == nil then
  286 + log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
  287 + if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
  288 + origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
  289 + end
  290 + elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
  291 + log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
  292 + origin:close("unsupported-stanza-type");
  293 + end
  294 + end
  295 +end
  296 +
  297 +function module_has_method(module, method)
  298 + return type(module.module[method]) == "function";
  299 +end
  300 +
  301 +function call_module_method(module, method, ...)
  302 + if module_has_method(module, method) then
  303 + local f = module.module[method];
  304 + return pcall(f, ...);
  305 + else
  306 + return false, "no-such-method";
  307 + end
  308 +end
  309 +
  310 +----- API functions exposed to modules -----------
  311 +-- Must all be in api.*
  312 +
  313 +-- Returns the name of the current module
  314 +function api:get_name()
  315 + return self.name;
  316 +end
  317 +
  318 +-- Returns the host that the current module is serving
  319 +function api:get_host()
  320 + return self.host;
  321 +end
  322 +
  323 +function api:get_host_type()
  324 + return hosts[self.host].type;
  325 +end
  326 +
  327 +function api:set_global()
  328 + self.host = "*";
  329 + -- Update the logger
  330 + local _log = logger.init("mod_"..self.name);
  331 + self.log = function (self, ...) return _log(...); end;
  332 + self._log = _log;
  333 +end
  334 +
  335 +local function _add_handler(module, origin_type, tag, xmlns, handler)
  336 + local handlers = stanza_handlers:get(module.host, origin_type, tag, xmlns);
  337 + local msg = (tag == "iq") and "namespace" or "payload namespace";
  338 + if not handlers then
  339 + stanza_handlers:add(module.host, origin_type, tag, xmlns, handler);
  340 + handler_info[handler] = module;
  341 + handler_table:add(module.host, module.name, {module.host, origin_type, tag, xmlns});
  342 + --module:log("debug", "I now handle tag '%s' [%s] with %s '%s'", tag, origin_type, msg, xmlns);
  343 + else
  344 + module:log("warn", "I wanted to handle tag '%s' [%s] with %s '%s' but mod_%s already handles that", tag, origin_type, msg, xmlns, handler_info[handlers[1]].module.name);
  345 + end
  346 +end
  347 +
  348 +function api:add_handler(origin_type, tag, xmlns, handler)
  349 + if not (origin_type and tag and xmlns and handler) then return false; end
  350 + if type(origin_type) == "table" then
  351 + for _, origin_type in ipairs(origin_type) do
  352 + _add_handler(self, origin_type, tag, xmlns, handler);
  353 + end
  354 + else
  355 + _add_handler(self, origin_type, tag, xmlns, handler);
  356 + end
  357 +end
  358 +function api:add_iq_handler(origin_type, xmlns, handler)
  359 + self:add_handler(origin_type, "iq", xmlns, handler);
  360 +end
  361 +
  362 +function api:add_feature(xmlns)
  363 + self:add_item("feature", xmlns);
  364 +end
  365 +function api:add_identity(category, type, name)
  366 + self:add_item("identity", {category = category, type = type, name = name});
  367 +end
  368 +
  369 +local event_hook = function(host, mod_name, event_name, ...)
  370 + if type((...)) == "table" and (...).host and (...).host ~= host then return; end
  371 + for handler in pairs(event_hooks:get(host, mod_name, event_name) or NULL) do
  372 + handler(...);
  373 + end
  374 +end;
  375 +function api:add_event_hook(name, handler)
  376 + if not hooked:get(self.host, self.name, name) then
  377 + eventmanager.add_event_hook(name, function(...) event_hook(self.host, self.name, name, ...); end);
  378 + hooked:set(self.host, self.name, name, true);
  379 + end
  380 + event_hooks:set(self.host, self.name, name, handler, true);
  381 +end
  382 +
  383 +function api:fire_event(...)
  384 + return (hosts[self.host] or prosody).events.fire_event(...);
  385 +end
  386 +
  387 +function api:hook(event, handler, priority)
  388 + hooks:set(self.host, self.name, event, handler, true);
  389 + (hosts[self.host] or prosody).events.add_handler(event, handler, priority);
  390 +end
  391 +
  392 +function api:hook_stanza(xmlns, name, handler, priority)
  393 + if not handler and type(name) == "function" then
  394 + -- If only 2 options then they specified no xmlns
  395 + xmlns, name, handler, priority = nil, xmlns, name, handler;
  396 + elseif not (handler and name) then
  397 + self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
  398 + return;
  399 + end
  400 + return api.hook(self, "stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
  401 +end
  402 +
  403 +function api:require(lib)
  404 + local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
  405 + if not f then
  406 + f, n = pluginloader.load_code(lib, lib..".lib.lua");
  407 + end
  408 + if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
  409 + setfenv(f, self.environment);
  410 + return f();
  411 +end
  412 +
  413 +function api:get_option(name, default_value)
  414 + local value = config.get(self.host, self.name, name);
  415 + if value == nil then
  416 + value = config.get(self.host, "core", name);
  417 + if value == nil then
  418 + value = default_value;
  419 + end
  420 + end
  421 + return value;
  422 +end
  423 +
  424 +function api:get_option_string(name, default_value)
  425 + local value = self:get_option(name, default_value);
  426 + if type(value) == "table" then
  427 + if #value > 1 then
  428 + self:log("error", "Config option '%s' does not take a list, using just the first item", name);
  429 + end
  430 + value = value[