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         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.stanza_router"
313         package.loaded['core.componentmanager'] = setmetatable({},{__index=function()
314                 log("warn", "componentmanager is deprecated: %s", debug.traceback():match("\n[^\n]*\n[\s\t]*([^\n]*)"));
315                 return function() end
316         end});
317
318         require "net.http"
319         
320         require "util.array"
321         require "util.datetime"
322         require "util.iterators"
323         require "util.timer"
324         require "util.helpers"
325         
326         pcall(require, "util.signal") -- Not on Windows
327         
328         -- Commented to protect us from 
329         -- the second kind of people
330         --[[ 
331         pcall(require, "remdebug.engine");
332         if remdebug then remdebug.engine.start() end
333         ]]
334
335         require "net.connlisteners";
336         
337         require "util.stanza"
338         require "util.jid"
339 end
340
341 function init_data_store()
342         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
343         require "util.datamanager".set_data_path(data_path);
344         require "util.datamanager".add_callback(function(username, host, datastore, data)
345                 if config.get(host, "core", "anonymous_login") then
346                         return false;
347                 end
348                 return username, host, datastore, data;
349         end);
350         require "core.storagemanager";
351 end
352
353 function prepare_to_start()
354         log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
355         -- Signal to modules that we are ready to start
356         prosody.events.fire_event("server-starting");
357
358         -- start listening on sockets
359         if config.get("*", "core", "ports") then
360                 prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
361                 if config.get("*", "core", "ssl_ports") then
362                         prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
363                 end
364         else
365                 prosody.net_activate_ports("c2s", "xmppclient", {5222});
366                 prosody.net_activate_ports("s2s", "xmppserver", {5269});
367                 prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
368                 prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
369         end
370
371         prosody.start_time = os.time();
372 end     
373
374 function init_global_protection()
375         -- Catch global accesses
376         local locked_globals_mt = {
377                 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
378                 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
379         };
380                 
381         function prosody.unlock_globals()
382                 setmetatable(_G, nil);
383         end
384         
385         function prosody.lock_globals()
386                 setmetatable(_G, locked_globals_mt);
387         end
388
389         -- And lock now...
390         prosody.lock_globals();
391 end
392
393 function loop()
394         -- Error handler for errors that make it this far
395         local function catch_uncaught_error(err)
396                 if type(err) == "string" and err:match("interrupted!$") then
397                         return "quitting";
398                 end
399                 
400                 log("error", "Top-level error, please report:\n%s", tostring(err));
401                 local traceback = debug.traceback("", 2);
402                 if traceback then
403                         log("error", "%s", traceback);
404                 end
405                 
406                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
407         end
408         
409         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
410                 socket.sleep(0.2);
411         end
412 end
413
414 function cleanup()
415         log("info", "Shutdown status: Cleaning up");
416         prosody.events.fire_event("server-cleanup");
417         
418         -- Ok, we're quitting I know, but we
419         -- need to do some tidying before we go :)
420         server.setquitting(false);
421         
422         log("info", "Shutdown status: Closing all active sessions");
423         for hostname, host in pairs(hosts) do
424                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
425                 if host.sessions then
426                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
427                         if prosody.shutdown_reason then
428                                 reason.text = reason.text..": "..prosody.shutdown_reason;
429                         end
430                         for username, user in pairs(host.sessions) do
431                                 for resource, session in pairs(user.sessions) do
432                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
433                                         session:close(reason);
434                                 end
435                         end
436                 end
437         
438                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
439                 if host.s2sout then
440                         for remotehost, session in pairs(host.s2sout) do
441                                 if session.close then
442                                         session:close("system-shutdown");
443                                 else
444                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
445                                 end
446                         end
447                 end
448         end
449
450         log("info", "Shutdown status: Closing all server connections");
451         server.closeall();
452         
453         server.setquitting(true);
454 end
455
456 -- Are you ready? :)
457 -- These actions are in a strict order, as many depend on
458 -- previous steps to have already been performed
459 read_config();
460 init_logging();
461 check_dependencies();
462 sandbox_require();
463 set_function_metatable();
464 load_libraries();
465 init_global_state();
466 read_version();
467 log("info", "Hello and welcome to Prosody version %s", prosody.version);
468 load_secondary_libraries();
469 init_data_store();
470 init_global_protection();
471 prepare_to_start();
472
473 prosody.events.fire_event("server-started");
474
475 loop();
476
477 log("info", "Shutting down...");
478 cleanup();
479 prosody.events.fire_event("server-stopped");
480 log("info", "Shutdown complete");
481