prosody: Added a stub implementation of core.componentmanager to the package.loaded...
[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         prosody.arg = _G.arg;
167
168         prosody.platform = "unknown";
169         if os.getenv("WINDIR") then
170                 prosody.platform = "windows";
171         elseif package.config:sub(1,1) == "/" then
172                 prosody.platform = "posix";
173         end
174         
175         prosody.installed = nil;
176         if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
177                 prosody.installed = true;
178         end
179         
180         -- Function to reload the config file
181         function prosody.reload_config()
182                 log("info", "Reloading configuration file");
183                 prosody.events.fire_event("reloading-config");
184                 local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
185                 if not ok then
186                         if level == "parser" then
187                                 log("error", "There was an error parsing the configuration file: %s", tostring(err));
188                         elseif level == "file" then
189                                 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
190                         end
191                 end
192                 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
193         end
194
195         -- Function to reopen logfiles
196         function prosody.reopen_logfiles()
197                 log("info", "Re-opening log files");
198                 prosody.events.fire_event("reopen-log-files");
199         end
200
201         -- Function to initiate prosody shutdown
202         function prosody.shutdown(reason)
203                 log("info", "Shutting down: %s", reason or "unknown reason");
204                 prosody.shutdown_reason = reason;
205                 prosody.events.fire_event("server-stopping", {reason = reason});
206                 server.setquitting(true);
207         end
208
209         -- Load SSL settings from config, and create a ctx table
210         local certmanager = require "core.certmanager";
211         local global_ssl_ctx = certmanager.create_context("*", "server");
212         prosody.global_ssl_ctx = global_ssl_ctx;
213
214         local cl = require "net.connlisteners";
215         function prosody.net_activate_ports(option, listener, default, conntype)
216                 conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
217                 local ports_option = option and option.."_ports" or "ports";
218                 if not cl.get(listener) then return; end
219                 local ports = config.get("*", "core", ports_option) or default;
220                 if type(ports) == "number" then ports = {ports} end;
221                 
222                 if type(ports) ~= "table" then
223                         log("error", "core."..ports_option.." is not a table");
224                 else
225                         for _, port in ipairs(ports) do
226                                 port = tonumber(port);
227                                 if type(port) ~= "number" then
228                                         log("error", "Non-numeric "..ports_option..": "..tostring(port));
229                                 else
230                                         local ok, err = cl.start(listener, {
231                                                 ssl = conntype == "ssl" and global_ssl_ctx,
232                                                 port = port,
233                                                 interface = (option and config.get("*", "core", option.."_interface"))
234                                                         or cl.get(listener).default_interface
235                                                         or config.get("*", "core", "interface"),
236                                                 type = conntype
237                                         });
238                                         if not ok then
239                                                 local friendly_message = err;
240                                                 if err:match(" in use") then
241                                                         if port == 5222 or port == 5223 or port == 5269 then
242                                                                 friendly_message = "check that Prosody or another XMPP server is "
243                                                                         .."not already running and using this port";
244                                                         elseif port == 80 or port == 81 then
245                                                                 friendly_message = "check that a HTTP server is not already using "
246                                                                         .."this port";
247                                                         elseif port == 5280 then
248                                                                 friendly_message = "check that Prosody or a BOSH connection manager "
249                                                                         .."is not already running";
250                                                         else
251                                                                 friendly_message = "this port is in use by another application";
252                                                         end
253                                                 elseif err:match("permission") then
254                                                         friendly_message = "Prosody does not have sufficient privileges to use this port";
255                                                 elseif err == "no ssl context" then
256                                                         if not config.get("*", "core", "ssl") then
257                                                                 friendly_message = "there is no 'ssl' config under Host \"*\" which is "
258                                                                         .."require for legacy SSL ports";
259                                                         else
260                                                                 friendly_message = "initializing SSL support failed, see previous log entries";
261                                                         end
262                                                 end
263                                                 log("error", "Failed to open server port %d, %s", port, friendly_message);
264                                         end
265                                 end
266                         end
267                 end
268         end
269 end
270
271 function read_version()
272         -- Try to determine version
273         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
274         if version_file then
275                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
276                 version_file:close();
277                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
278                         prosody.version = "hg:"..prosody.version;
279                 end
280         else
281                 prosody.version = "unknown";
282         end
283 end
284
285 function load_secondary_libraries()
286         --- Load and initialise core modules
287         require "util.import"
288         require "util.xmppstream"
289         require "core.xmlhandlers"
290         require "core.rostermanager"
291         require "core.hostmanager"
292         require "core.modulemanager"
293         require "core.usermanager"
294         require "core.sessionmanager"
295         require "core.stanza_router"
296         package.loaded['core.componentmanager'] = setmetatable({},{__index=function()
297                 log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[\s\t]*([^\n]*)"));
298                 return function() end
299         end});
300
301         require "net.http"
302         
303         require "util.array"
304         require "util.datetime"
305         require "util.iterators"
306         require "util.timer"
307         require "util.helpers"
308         
309         pcall(require, "util.signal") -- Not on Windows
310         
311         -- Commented to protect us from 
312         -- the second kind of people
313         --[[ 
314         pcall(require, "remdebug.engine");
315         if remdebug then remdebug.engine.start() end
316         ]]
317
318         require "net.connlisteners";
319         
320         require "util.stanza"
321         require "util.jid"
322 end
323
324 function init_data_store()
325         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
326         require "util.datamanager".set_data_path(data_path);
327         require "util.datamanager".add_callback(function(username, host, datastore, data)
328                 if config.get(host, "core", "anonymous_login") then
329                         return false;
330                 end
331                 return username, host, datastore, data;
332         end);
333         require "core.storagemanager";
334 end
335
336 function prepare_to_start()
337         log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
338         -- Signal to modules that we are ready to start
339         prosody.events.fire_event("server-starting");
340
341         -- start listening on sockets
342         if config.get("*", "core", "ports") then
343                 prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
344                 if config.get("*", "core", "ssl_ports") then
345                         prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
346                 end
347         else
348                 prosody.net_activate_ports("c2s", "xmppclient", {5222});
349                 prosody.net_activate_ports("s2s", "xmppserver", {5269});
350                 prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
351                 prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
352         end
353
354         prosody.start_time = os.time();
355 end     
356
357 function init_global_protection()
358         -- Catch global accesses
359         local locked_globals_mt = {
360                 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
361                 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
362         };
363                 
364         function prosody.unlock_globals()
365                 setmetatable(_G, nil);
366         end
367         
368         function prosody.lock_globals()
369                 setmetatable(_G, locked_globals_mt);
370         end
371
372         -- And lock now...
373         prosody.lock_globals();
374 end
375
376 function loop()
377         -- Error handler for errors that make it this far
378         local function catch_uncaught_error(err)
379                 if type(err) == "string" and err:match("interrupted!$") then
380                         return "quitting";
381                 end
382                 
383                 log("error", "Top-level error, please report:\n%s", tostring(err));
384                 local traceback = debug.traceback("", 2);
385                 if traceback then
386                         log("error", "%s", traceback);
387                 end
388                 
389                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
390         end
391         
392         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
393                 socket.sleep(0.2);
394         end
395 end
396
397 function cleanup()
398         log("info", "Shutdown status: Cleaning up");
399         prosody.events.fire_event("server-cleanup");
400         
401         -- Ok, we're quitting I know, but we
402         -- need to do some tidying before we go :)
403         server.setquitting(false);
404         
405         log("info", "Shutdown status: Closing all active sessions");
406         for hostname, host in pairs(hosts) do
407                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
408                 if host.sessions then
409                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
410                         if prosody.shutdown_reason then
411                                 reason.text = reason.text..": "..prosody.shutdown_reason;
412                         end
413                         for username, user in pairs(host.sessions) do
414                                 for resource, session in pairs(user.sessions) do
415                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
416                                         session:close(reason);
417                                 end
418                         end
419                 end
420         
421                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
422                 if host.s2sout then
423                         for remotehost, session in pairs(host.s2sout) do
424                                 if session.close then
425                                         session:close("system-shutdown");
426                                 else
427                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
428                                 end
429                         end
430                 end
431         end
432
433         log("info", "Shutdown status: Closing all server connections");
434         server.closeall();
435         
436         server.setquitting(true);
437 end
438
439 -- Are you ready? :)
440 -- These actions are in a strict order, as many depend on
441 -- previous steps to have already been performed
442 read_config();
443 init_logging();
444 check_dependencies();
445 sandbox_require();
446 set_function_metatable();
447 load_libraries();
448 init_global_state();
449 read_version();
450 log("info", "Hello and welcome to Prosody version %s", prosody.version);
451 load_secondary_libraries();
452 init_data_store();
453 init_global_protection();
454 prepare_to_start();
455
456 prosody.events.fire_event("server-started");
457
458 loop();
459
460 log("info", "Shutting down...");
461 cleanup();
462 prosody.events.fire_event("server-stopped");
463 log("info", "Shutdown complete");
464