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