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