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