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