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