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