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