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