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