3 -- Copyright (C) 2008-2010 Matthew Wild
4 -- Copyright (C) 2008-2010 Waqas Hussain
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
10 -- prosody - main executable for Prosody XMPP server
12 -- Will be modified by configure script if run --
14 CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
15 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
16 CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
17 CFG_DATADIR=os.getenv("PROSODY_DATADIR");
19 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
21 local function is_relative(path)
22 local path_sep = package.config:sub(1,1);
23 return ((path_sep == "/" and path:sub(1,1) ~= "/")
24 or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
27 -- Tell Lua where to find our libraries
29 local function filter_relative_paths(path)
30 if is_relative(path) then return ""; end
32 local function sanitise_paths(paths)
33 return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
35 package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
36 package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
39 -- Substitute ~ with path to home directory in data path
41 if os.getenv("HOME") then
42 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
46 -- Global 'prosody' object
47 local prosody = { events = require "util.events".new(); };
51 local dependencies = require "util.dependencies";
52 if not dependencies.check_dependencies() then
56 -- Load the config-parsing module
57 config = require "core.configmanager"
60 -- Define the functions we call during startup, the
61 -- actual startup happens right at the end, where these
62 -- functions get called
64 function read_config()
68 if arg[1] == "--config" and arg[2] then
69 table.insert(filenames, arg[2]);
71 table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
74 for _, format in ipairs(config.parsers()) do
75 table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
78 for _,_filename in ipairs(filenames) do
80 local file = io.open(filename);
83 CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
87 local ok, level, err = config.load(filename);
90 print("**************************");
91 if level == "parser" then
92 print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"..":");
94 local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
95 if err:match("chunk has too many syntax levels$") then
96 print("An Include statement in a config file is including an already-included");
97 print("file and causing an infinite loop. An Include statement in a config file is...");
99 print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
102 elseif level == "file" then
103 print("Prosody was unable to find the configuration file.");
104 print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
105 print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
106 print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
108 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
110 print("**************************");
116 function load_libraries()
117 -- Load socket framework
118 server = require "net.server"
121 function init_logging()
122 -- Initialize logging
123 require "core.loggingmanager"
126 function log_dependency_warnings()
127 dependencies.log_warnings();
130 function sanity_check()
131 for host, host_config in pairs(configmanager.getconfig()) do
133 and host_config.core.enabled ~= false
134 and not host_config.core.component_module then
138 log("error", "No enabled VirtualHost entries found in the config file.");
139 log("error", "At least one active host is required for Prosody to function. Exiting...");
143 function sandbox_require()
144 -- Replace require() with one that doesn't pollute _G, required
145 -- for neat sandboxing of modules
147 local _real_require = require;
148 function require(...)
149 local curr_env = getfenv(2);
150 local curr_env_mt = getmetatable(getfenv(2));
151 local _realG_mt = getmetatable(_realG);
152 if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
153 local old_newindex, old_index;
154 old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
155 old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k)
156 return rawget(curr_env, k);
158 local ret = _real_require(...);
159 _realG_mt.__newindex = old_newindex;
160 _realG_mt.__index = old_index;
163 return _real_require(...);
167 function set_function_metatable()
169 function mt.__index(f, upvalue)
170 local i, name, value = 0;
173 name, value = debug.getupvalue(f, i);
174 until name == upvalue or name == nil;
177 function mt.__newindex(f, upvalue, value)
181 name = debug.getupvalue(f, i);
182 until name == upvalue or name == nil;
184 debug.setupvalue(f, i, value);
187 function mt.__tostring(f)
188 local info = debug.getinfo(f);
189 return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
191 debug.setmetatable(function() end, mt);
194 function init_global_state()
199 prosody.bare_sessions = bare_sessions;
200 prosody.full_sessions = full_sessions;
201 prosody.hosts = hosts;
203 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
204 local custom_plugin_paths = config.get("*", "core", "plugin_paths");
205 if custom_plugin_paths then
206 local path_sep = package.config:sub(3,3);
207 -- path1;path2;path3;defaultpath...
208 CFG_PLUGINDIR = table.concat(custom_plugin_paths, path_sep)..path_sep..(CFG_PLUGINDIR or "plugins");
210 prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".",
211 plugins = CFG_PLUGINDIR or "plugins", data = data_path };
213 prosody.arg = _G.arg;
215 prosody.platform = "unknown";
216 if os.getenv("WINDIR") then
217 prosody.platform = "windows";
218 elseif package.config:sub(1,1) == "/" then
219 prosody.platform = "posix";
222 prosody.installed = nil;
223 if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
224 prosody.installed = true;
227 -- Function to reload the config file
228 function prosody.reload_config()
229 log("info", "Reloading configuration file");
230 prosody.events.fire_event("reloading-config");
231 local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
233 if level == "parser" then
234 log("error", "There was an error parsing the configuration file: %s", tostring(err));
235 elseif level == "file" then
236 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
239 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
242 -- Function to reopen logfiles
243 function prosody.reopen_logfiles()
244 log("info", "Re-opening log files");
245 prosody.events.fire_event("reopen-log-files");
248 -- Function to initiate prosody shutdown
249 function prosody.shutdown(reason)
250 log("info", "Shutting down: %s", reason or "unknown reason");
251 prosody.shutdown_reason = reason;
252 prosody.events.fire_event("server-stopping", {reason = reason});
253 server.setquitting(true);
256 -- Load SSL settings from config, and create a ctx table
257 local certmanager = require "core.certmanager";
258 local global_ssl_ctx = certmanager.create_context("*", "server");
259 prosody.global_ssl_ctx = global_ssl_ctx;
263 function read_version()
264 -- Try to determine version
265 local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
267 prosody.version = version_file:read("*a"):gsub("%s*$", "");
268 version_file:close();
269 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
270 prosody.version = "hg:"..prosody.version;
273 prosody.version = "unknown";
277 function load_secondary_libraries()
278 --- Load and initialise core modules
279 require "util.import"
280 require "util.xmppstream"
281 require "core.rostermanager"
282 require "core.hostmanager"
283 require "core.portmanager"
284 require "core.modulemanager"
285 require "core.usermanager"
286 require "core.sessionmanager"
287 require "core.stanza_router"
288 package.loaded['core.componentmanager'] = setmetatable({},{__index=function()
289 log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[ \t]*([^\n]*)"));
290 return function() end
296 require "util.datetime"
297 require "util.iterators"
299 require "util.helpers"
301 pcall(require, "util.signal") -- Not on Windows
303 -- Commented to protect us from
304 -- the second kind of people
306 pcall(require, "remdebug.engine");
307 if remdebug then remdebug.engine.start() end
310 require "util.stanza"
314 function init_data_store()
315 require "core.storagemanager";
318 function prepare_to_start()
319 log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
320 -- Signal to modules that we are ready to start
321 prosody.events.fire_event("server-starting");
322 prosody.start_time = os.time();
325 function init_global_protection()
326 -- Catch global accesses
327 local locked_globals_mt = {
328 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
329 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
332 function prosody.unlock_globals()
333 setmetatable(_G, nil);
336 function prosody.lock_globals()
337 setmetatable(_G, locked_globals_mt);
341 prosody.lock_globals();
345 -- Error handler for errors that make it this far
346 local function catch_uncaught_error(err)
347 if type(err) == "string" and err:match("interrupted!$") then
351 log("error", "Top-level error, please report:\n%s", tostring(err));
352 local traceback = debug.traceback("", 2);
354 log("error", "%s", traceback);
357 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
360 while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
366 log("info", "Shutdown status: Cleaning up");
367 prosody.events.fire_event("server-cleanup");
369 -- Ok, we're quitting I know, but we
370 -- need to do some tidying before we go :)
371 server.setquitting(false);
373 log("info", "Shutdown status: Closing all active sessions");
374 for hostname, host in pairs(hosts) do
375 log("debug", "Shutdown status: Closing client connections for %s", hostname)
376 if host.sessions then
377 local reason = { condition = "system-shutdown", text = "Server is shutting down" };
378 if prosody.shutdown_reason then
379 reason.text = reason.text..": "..prosody.shutdown_reason;
381 for username, user in pairs(host.sessions) do
382 for resource, session in pairs(user.sessions) do
383 log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
384 session:close(reason);
389 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
391 for remotehost, session in pairs(host.s2sout) do
392 if session.close then
393 session:close("system-shutdown");
395 log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
401 log("info", "Shutdown status: Closing all server connections");
404 server.setquitting(true);
408 -- These actions are in a strict order, as many depend on
409 -- previous steps to have already been performed
414 set_function_metatable();
418 log("info", "Hello and welcome to Prosody version %s", prosody.version);
419 log_dependency_warnings();
420 load_secondary_libraries();
422 init_global_protection();
425 prosody.events.fire_event("server-started");
429 log("info", "Shutting down...");
431 prosody.events.fire_event("server-stopped");
432 log("info", "Shutdown complete");