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