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