prosody: Load portmanager at startup, to ensure it is ready to receive item-added...
[prosody.git] / prosody
1 #!/usr/bin/env lua
2 -- Prosody IM
3 -- Copyright (C) 2008-2010 Matthew Wild
4 -- Copyright (C) 2008-2010 Waqas Hussain
5 -- 
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
8 --
9
10 -- prosody - main executable for Prosody XMPP server
11
12 -- Will be modified by configure script if run --
13
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");
18
19 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20
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) ~= ":\\")))
25 end
26
27 -- Tell Lua where to find our libraries
28 if CFG_SOURCEDIR then
29         local function filter_relative_paths(path)
30                 if is_relative(path) then return ""; end
31         end
32         local function sanitise_paths(paths)
33                 return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
34         end
35         package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
36         package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
37 end
38
39 -- Substitute ~ with path to home directory in data path
40 if CFG_DATADIR then
41         if os.getenv("HOME") then
42                 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
43         end
44 end
45
46 -- Global 'prosody' object
47 local prosody = { events = require "util.events".new(); };
48 _G.prosody = prosody;
49
50 -- Check dependencies
51 local dependencies = require "util.dependencies";
52 if not dependencies.check_dependencies() then
53         os.exit(1);
54 end
55
56 -- Load the config-parsing module
57 config = require "core.configmanager"
58
59 -- -- -- --
60 -- Define the functions we call during startup, the 
61 -- actual startup happens right at the end, where these
62 -- functions get called
63
64 function read_config()
65         local filenames = {};
66         
67         local filename;
68         if arg[1] == "--config" and arg[2] then
69                 table.insert(filenames, arg[2]);
70                 if CFG_CONFIGDIR then
71                         table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
72                 end
73         else
74                 for _, format in ipairs(config.parsers()) do
75                         table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
76                 end
77         end
78         for _,_filename in ipairs(filenames) do
79                 filename = _filename;
80                 local file = io.open(filename);
81                 if file then
82                         file:close();
83                         CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
84                         break;
85                 end
86         end
87         local ok, level, err = config.load(filename);
88         if not ok then
89                 print("\n");
90                 print("**************************");
91                 if level == "parser" then
92                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua"..":");
93                         print("");
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...");
98                         else
99                                 print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
100                         end
101                         print("");
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.");
107                 end
108                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
109                 print("Good luck!");
110                 print("**************************");
111                 print("");
112                 os.exit(1);
113         end
114 end
115
116 function load_libraries()
117         -- Load socket framework
118         server = require "net.server"
119 end     
120
121 function init_logging()
122         -- Initialize logging
123         require "core.loggingmanager"
124 end
125
126 function log_dependency_warnings()
127         dependencies.log_warnings();
128 end
129
130 function sanity_check()
131         for host, host_config in pairs(configmanager.getconfig()) do
132                 if host ~= "*"
133                 and host_config.core.enabled ~= false
134                 and not host_config.core.component_module then
135                         return;
136                 end
137         end
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...");
140         os.exit(1);
141 end
142
143 function sandbox_require()
144         -- Replace require() with one that doesn't pollute _G, required
145         -- for neat sandboxing of modules
146         local _realG = _G;
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);
157                         end;
158                         local ret = _real_require(...);
159                         _realG_mt.__newindex = old_newindex;
160                         _realG_mt.__index = old_index;
161                         return ret;
162                 end
163                 return _real_require(...);
164         end
165 end
166
167 function set_function_metatable()
168         local mt = {};
169         function mt.__index(f, upvalue)
170                 local i, name, value = 0;
171                 repeat
172                         i = i + 1;
173                         name, value = debug.getupvalue(f, i);
174                 until name == upvalue or name == nil;
175                 return value;
176         end
177         function mt.__newindex(f, upvalue, value)
178                 local i, name = 0;
179                 repeat
180                         i = i + 1;
181                         name = debug.getupvalue(f, i);
182                 until name == upvalue or name == nil;
183                 if name then
184                         debug.setupvalue(f, i, value);
185                 end
186         end
187         function mt.__tostring(f)
188                 local info = debug.getinfo(f);
189                 return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
190         end
191         debug.setmetatable(function() end, mt);
192 end
193
194 function init_global_state()
195         bare_sessions = {};
196         full_sessions = {};
197         hosts = {};
198
199         prosody.bare_sessions = bare_sessions;
200         prosody.full_sessions = full_sessions;
201         prosody.hosts = hosts;
202         
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");
209         end
210         prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR or ".", 
211                           plugins = CFG_PLUGINDIR or "plugins", data = data_path };
212
213         prosody.arg = _G.arg;
214
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";
220         end
221         
222         prosody.installed = nil;
223         if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
224                 prosody.installed = true;
225         end
226         
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");
232                 if not ok then
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));
237                         end
238                 end
239                 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
240         end
241
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");
246         end
247
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);
254         end
255
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;
260
261 end
262
263 function read_version()
264         -- Try to determine version
265         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
266         if version_file then
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;
271                 end
272         else
273                 prosody.version = "unknown";
274         end
275 end
276
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
291         end});
292
293         require "net.http"
294         
295         require "util.array"
296         require "util.datetime"
297         require "util.iterators"
298         require "util.timer"
299         require "util.helpers"
300         
301         pcall(require, "util.signal") -- Not on Windows
302         
303         -- Commented to protect us from 
304         -- the second kind of people
305         --[[ 
306         pcall(require, "remdebug.engine");
307         if remdebug then remdebug.engine.start() end
308         ]]
309
310         require "util.stanza"
311         require "util.jid"
312 end
313
314 function init_data_store()
315         require "core.storagemanager";
316 end
317
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();
323 end     
324
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;
330         };
331                 
332         function prosody.unlock_globals()
333                 setmetatable(_G, nil);
334         end
335         
336         function prosody.lock_globals()
337                 setmetatable(_G, locked_globals_mt);
338         end
339
340         -- And lock now...
341         prosody.lock_globals();
342 end
343
344 function loop()
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
348                         return "quitting";
349                 end
350                 
351                 log("error", "Top-level error, please report:\n%s", tostring(err));
352                 local traceback = debug.traceback("", 2);
353                 if traceback then
354                         log("error", "%s", traceback);
355                 end
356                 
357                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
358         end
359         
360         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
361                 socket.sleep(0.2);
362         end
363 end
364
365 function cleanup()
366         log("info", "Shutdown status: Cleaning up");
367         prosody.events.fire_event("server-cleanup");
368         
369         -- Ok, we're quitting I know, but we
370         -- need to do some tidying before we go :)
371         server.setquitting(false);
372         
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;
380                         end
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);
385                                 end
386                         end
387                 end
388         
389                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
390                 if host.s2sout then
391                         for remotehost, session in pairs(host.s2sout) do
392                                 if session.close then
393                                         session:close("system-shutdown");
394                                 else
395                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
396                                 end
397                         end
398                 end
399         end
400
401         log("info", "Shutdown status: Closing all server connections");
402         server.closeall();
403         
404         server.setquitting(true);
405 end
406
407 -- Are you ready? :)
408 -- These actions are in a strict order, as many depend on
409 -- previous steps to have already been performed
410 read_config();
411 init_logging();
412 sanity_check();
413 sandbox_require();
414 set_function_metatable();
415 load_libraries();
416 init_global_state();
417 read_version();
418 log("info", "Hello and welcome to Prosody version %s", prosody.version);
419 log_dependency_warnings();
420 load_secondary_libraries();
421 init_data_store();
422 init_global_protection();
423 prepare_to_start();
424
425 prosody.events.fire_event("server-started");
426
427 loop();
428
429 log("info", "Shutting down...");
430 cleanup();
431 prosody.events.fire_event("server-stopped");
432 log("info", "Shutdown complete");
433