prosody: Ensure componentmanager is loaded in the main file.
[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 -- Will be modified by configure script if run --
11
12 CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR");
13 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
14 CFG_PLUGINDIR=os.getenv("PROSODY_PLUGINDIR");
15 CFG_DATADIR=os.getenv("PROSODY_DATADIR");
16
17 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
18
19 -- Tell Lua where to find our libraries
20 if CFG_SOURCEDIR then
21         package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
22         package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
23 end
24
25 -- Substitute ~ with path to home directory in data path
26 if CFG_DATADIR then
27         if os.getenv("HOME") then
28                 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
29         end
30 end
31
32 -- Global 'prosody' object
33 prosody = { events = require "util.events".new(); };
34 local prosody = prosody;
35
36 -- Load the config-parsing module
37 config = require "core.configmanager"
38
39 -- -- -- --
40 -- Define the functions we call during startup, the 
41 -- actual startup happens right at the end, where these
42 -- functions get called
43
44 function read_config()
45         local filenames = {};
46         
47         local filename;
48         if arg[1] == "--config" and arg[2] then
49                 table.insert(filenames, arg[2]);
50                 if CFG_CONFIGDIR then
51                         table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
52                 end
53         else
54                 for _, format in ipairs(config.parsers()) do
55                         table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
56                 end
57         end
58         for _,_filename in ipairs(filenames) do
59                 filename = _filename;
60                 local file = io.open(filename);
61                 if file then
62                         file:close();
63                         CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
64                         break;
65                 end
66         end
67         local ok, level, err = config.load(filename);
68         if not ok then
69                 print("\n");
70                 print("**************************");
71                 if level == "parser" then
72                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
73                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
74                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
75                         print("");
76                 elseif level == "file" then
77                         print("Prosody was unable to find the configuration file.");
78                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
79                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
80                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
81                 end
82                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
83                 print("Good luck!");
84                 print("**************************");
85                 print("");
86                 os.exit(1);
87         end
88 end
89
90 function load_libraries()
91         -- Load socket framework
92         server = require "net.server"
93 end     
94
95 function init_logging()
96         -- Initialize logging
97         require "core.loggingmanager"
98 end
99
100 function check_dependencies()
101         -- Check runtime dependencies
102         if not require "util.dependencies".check_dependencies() then
103                 os.exit(1);
104         end
105 end
106
107 function sandbox_require()
108         -- Replace require() with one that doesn't pollute _G, required
109         -- for neat sandboxing of modules
110         local _realG = _G;
111         local _real_require = require;
112         function require(...)
113                 local curr_env = getfenv(2);
114                 local curr_env_mt = getmetatable(getfenv(2));
115                 local _realG_mt = getmetatable(_realG);
116                 if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
117                         local old_newindex
118                         old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
119                         local ret = _real_require(...);
120                         _realG_mt.__newindex = old_newindex;
121                         return ret;
122                 end
123                 return _real_require(...);
124         end
125 end
126
127 function set_function_metatable()
128         local mt = {};
129         function mt.__index(f, upvalue)
130                 local i, name, value = 0;
131                 repeat
132                         i = i + 1;
133                         name, value = debug.getupvalue(f, i);
134                 until name == upvalue or name == nil;
135                 return value;
136         end
137         function mt.__newindex(f, upvalue, value)
138                 local i, name = 0;
139                 repeat
140                         i = i + 1;
141                         name = debug.getupvalue(f, i);
142                 until name == upvalue or name == nil;
143                 if name then
144                         debug.setupvalue(f, i, value);
145                 end
146         end
147         function mt.__tostring(f)
148                 local info = debug.getinfo(f);
149                 return ("function(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.linedefined);
150         end
151         debug.setmetatable(function() end, mt);
152 end
153
154 function init_global_state()
155         bare_sessions = {};
156         full_sessions = {};
157         hosts = {};
158
159         prosody.bare_sessions = bare_sessions;
160         prosody.full_sessions = full_sessions;
161         prosody.hosts = hosts;
162         
163         prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
164                           plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
165         
166         local path_sep = package.config:sub(1,1);
167         local rel_path_start = ".."..path_sep;
168         function prosody.resolve_relative_path(parent_path, path)
169                 if path then
170                         local is_relative;
171                         if path_sep == "/" and path:sub(1,1) ~= "/" then
172                                 is_relative = true;
173                         elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\") then
174                                 is_relative = true;
175                         end
176                         if is_relative then
177                                 return parent_path..path_sep..path;
178                         end
179                 end
180                 return path;
181         end
182         
183         prosody.arg = _G.arg;
184
185         prosody.platform = "unknown";
186         if os.getenv("WINDIR") then
187                 prosody.platform = "windows";
188         elseif package.config:sub(1,1) == "/" then
189                 prosody.platform = "posix";
190         end
191         
192         prosody.installed = nil;
193         if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
194                 prosody.installed = true;
195         end
196         
197         -- Function to reload the config file
198         function prosody.reload_config()
199                 log("info", "Reloading configuration file");
200                 prosody.events.fire_event("reloading-config");
201                 local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
202                 if not ok then
203                         if level == "parser" then
204                                 log("error", "There was an error parsing the configuration file: %s", tostring(err));
205                         elseif level == "file" then
206                                 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
207                         end
208                 end
209                 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
210         end
211
212         -- Function to reopen logfiles
213         function prosody.reopen_logfiles()
214                 log("info", "Re-opening log files");
215                 prosody.events.fire_event("reopen-log-files");
216         end
217
218         -- Function to initiate prosody shutdown
219         function prosody.shutdown(reason)
220                 log("info", "Shutting down: %s", reason or "unknown reason");
221                 prosody.shutdown_reason = reason;
222                 prosody.events.fire_event("server-stopping", {reason = reason});
223                 server.setquitting(true);
224         end
225
226         -- Load SSL settings from config, and create a ctx table
227         local certmanager = require "core.certmanager";
228         local global_ssl_ctx = certmanager.create_context("*", "server");
229         prosody.global_ssl_ctx = global_ssl_ctx;
230
231         local cl = require "net.connlisteners";
232         function prosody.net_activate_ports(option, listener, default, conntype)
233                 conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
234                 local ports_option = option and option.."_ports" or "ports";
235                 if not cl.get(listener) then return; end
236                 local ports = config.get("*", "core", ports_option) or default;
237                 if type(ports) == "number" then ports = {ports} end;
238                 
239                 if type(ports) ~= "table" then
240                         log("error", "core."..ports_option.." is not a table");
241                 else
242                         for _, port in ipairs(ports) do
243                                 port = tonumber(port);
244                                 if type(port) ~= "number" then
245                                         log("error", "Non-numeric "..ports_option..": "..tostring(port));
246                                 else
247                                         local ok, err = cl.start(listener, {
248                                                 ssl = conntype == "ssl" and global_ssl_ctx,
249                                                 port = port,
250                                                 interface = (option and config.get("*", "core", option.."_interface"))
251                                                         or cl.get(listener).default_interface
252                                                         or config.get("*", "core", "interface"),
253                                                 type = conntype
254                                         });
255                                         if not ok then
256                                                 local friendly_message = err;
257                                                 if err:match(" in use") then
258                                                         if port == 5222 or port == 5223 or port == 5269 then
259                                                                 friendly_message = "check that Prosody or another XMPP server is "
260                                                                         .."not already running and using this port";
261                                                         elseif port == 80 or port == 81 then
262                                                                 friendly_message = "check that a HTTP server is not already using "
263                                                                         .."this port";
264                                                         elseif port == 5280 then
265                                                                 friendly_message = "check that Prosody or a BOSH connection manager "
266                                                                         .."is not already running";
267                                                         else
268                                                                 friendly_message = "this port is in use by another application";
269                                                         end
270                                                 elseif err:match("permission") then
271                                                         friendly_message = "Prosody does not have sufficient privileges to use this port";
272                                                 elseif err == "no ssl context" then
273                                                         if not config.get("*", "core", "ssl") then
274                                                                 friendly_message = "there is no 'ssl' config under Host \"*\" which is "
275                                                                         .."require for legacy SSL ports";
276                                                         else
277                                                                 friendly_message = "initializing SSL support failed, see previous log entries";
278                                                         end
279                                                 end
280                                                 log("error", "Failed to open server port %d, %s", port, friendly_message);
281                                         end
282                                 end
283                         end
284                 end
285         end
286 end
287
288 function read_version()
289         -- Try to determine version
290         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
291         if version_file then
292                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
293                 version_file:close();
294                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
295                         prosody.version = "hg:"..prosody.version;
296                 end
297         else
298                 prosody.version = "unknown";
299         end
300 end
301
302 function load_secondary_libraries()
303         --- Load and initialise core modules
304         require "util.import"
305         require "util.xmppstream"
306         require "core.xmlhandlers"
307         require "core.rostermanager"
308         require "core.hostmanager"
309         require "core.modulemanager"
310         require "core.usermanager"
311         require "core.sessionmanager"
312         require "core.componentmanager"
313         require "core.stanza_router"
314
315         require "net.http"
316         
317         require "util.array"
318         require "util.datetime"
319         require "util.iterators"
320         require "util.timer"
321         require "util.helpers"
322         
323         pcall(require, "util.signal") -- Not on Windows
324         
325         -- Commented to protect us from 
326         -- the second kind of people
327         --[[ 
328         pcall(require, "remdebug.engine");
329         if remdebug then remdebug.engine.start() end
330         ]]
331
332         require "net.connlisteners";
333         
334         require "util.stanza"
335         require "util.jid"
336 end
337
338 function init_data_store()
339         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
340         require "util.datamanager".set_data_path(data_path);
341         require "util.datamanager".add_callback(function(username, host, datastore, data)
342                 if config.get(host, "core", "anonymous_login") then
343                         return false;
344                 end
345                 return username, host, datastore, data;
346         end);
347         require "core.storagemanager";
348 end
349
350 function prepare_to_start()
351         log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
352         -- Signal to modules that we are ready to start
353         prosody.events.fire_event("server-starting");
354
355         -- start listening on sockets
356         if config.get("*", "core", "ports") then
357                 prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
358                 if config.get("*", "core", "ssl_ports") then
359                         prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
360                 end
361         else
362                 prosody.net_activate_ports("c2s", "xmppclient", {5222});
363                 prosody.net_activate_ports("s2s", "xmppserver", {5269});
364                 prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
365                 prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
366         end
367
368         prosody.start_time = os.time();
369 end     
370
371 function init_global_protection()
372         -- Catch global accesses
373         local locked_globals_mt = {
374                 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
375                 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
376         };
377                 
378         function prosody.unlock_globals()
379                 setmetatable(_G, nil);
380         end
381         
382         function prosody.lock_globals()
383                 setmetatable(_G, locked_globals_mt);
384         end
385
386         -- And lock now...
387         prosody.lock_globals();
388 end
389
390 function loop()
391         -- Error handler for errors that make it this far
392         local function catch_uncaught_error(err)
393                 if type(err) == "string" and err:match("interrupted!$") then
394                         return "quitting";
395                 end
396                 
397                 log("error", "Top-level error, please report:\n%s", tostring(err));
398                 local traceback = debug.traceback("", 2);
399                 if traceback then
400                         log("error", "%s", traceback);
401                 end
402                 
403                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
404         end
405         
406         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
407                 socket.sleep(0.2);
408         end
409 end
410
411 function cleanup()
412         log("info", "Shutdown status: Cleaning up");
413         prosody.events.fire_event("server-cleanup");
414         
415         -- Ok, we're quitting I know, but we
416         -- need to do some tidying before we go :)
417         server.setquitting(false);
418         
419         log("info", "Shutdown status: Closing all active sessions");
420         for hostname, host in pairs(hosts) do
421                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
422                 if host.sessions then
423                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
424                         if prosody.shutdown_reason then
425                                 reason.text = reason.text..": "..prosody.shutdown_reason;
426                         end
427                         for username, user in pairs(host.sessions) do
428                                 for resource, session in pairs(user.sessions) do
429                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
430                                         session:close(reason);
431                                 end
432                         end
433                 end
434         
435                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
436                 if host.s2sout then
437                         for remotehost, session in pairs(host.s2sout) do
438                                 if session.close then
439                                         session:close("system-shutdown");
440                                 else
441                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
442                                 end
443                         end
444                 end
445         end
446
447         log("info", "Shutdown status: Closing all server connections");
448         server.closeall();
449         
450         server.setquitting(true);
451 end
452
453 -- Are you ready? :)
454 -- These actions are in a strict order, as many depend on
455 -- previous steps to have already been performed
456 read_config();
457 init_logging();
458 check_dependencies();
459 sandbox_require();
460 set_function_metatable();
461 load_libraries();
462 init_global_state();
463 read_version();
464 log("info", "Hello and welcome to Prosody version %s", prosody.version);
465 load_secondary_libraries();
466 init_data_store();
467 init_global_protection();
468 prepare_to_start();
469
470 prosody.events.fire_event("server-started");
471
472 loop();
473
474 log("info", "Shutting down...");
475 cleanup();
476 prosody.events.fire_event("server-stopped");
477 log("info", "Shutdown complete");
478