util.pposix: Replace the unwieldy module table generation with luaL_register() call...
[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 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 -- Required to be able to find packages installed with luarocks
36 pcall(require, "luarocks.require");
37
38 -- Replace require() with one that doesn't pollute _G, required
39 -- for neat sandboxing of modules
40 do
41         local _realG = _G;
42         local _real_require = require;
43         function require(...)
44                 local curr_env = getfenv(2);
45                 local curr_env_mt = getmetatable(getfenv(2));
46                 local _realG_mt = getmetatable(_realG);
47                 if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
48                         local old_newindex
49                         old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
50                         local ret = _real_require(...);
51                         _realG_mt.__newindex = old_newindex;
52                         return ret;
53                 end
54                 return _real_require(...);
55         end
56 end
57
58 -- Load the config-parsing module
59 config = require "core.configmanager"
60
61 -- -- -- --
62 -- Define the functions we call during startup, the 
63 -- actual startup happens right at the end, where these
64 -- functions get called
65
66 function read_config()
67         local filenames = {};
68         
69         local filename;
70         if arg[1] == "--config" and arg[2] then
71                 table.insert(filenames, arg[2]);
72                 if CFG_CONFIGDIR then
73                         table.insert(filenames, CFG_CONFIGDIR.."/"..arg[2]);
74                 end
75         else
76                 for _, format in ipairs(config.parsers()) do
77                         table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg."..format);
78                 end
79         end
80         for _,_filename in ipairs(filenames) do
81                 filename = _filename;
82                 local file = io.open(filename);
83                 if file then
84                         file:close();
85                         CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
86                         break;
87                 end
88         end
89         local ok, level, err = config.load(filename);
90         if not ok then
91                 print("\n");
92                 print("**************************");
93                 if level == "parser" then
94                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
95                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
96                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
97                         print("");
98                 elseif level == "file" then
99                         print("Prosody was unable to find the configuration file.");
100                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
101                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
102                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
103                 end
104                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
105                 print("Good luck!");
106                 print("**************************");
107                 print("");
108                 os.exit(1);
109         end
110 end
111
112 function load_libraries()
113         -- Initialize logging
114         require "core.loggingmanager"
115         
116         -- Check runtime dependencies
117         require "util.dependencies"
118         
119         -- Load socket framework
120         server = require "net.server"
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 global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
187         if global_ssl_ctx then
188                 local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2" };
189                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
190                 prosody.global_ssl_ctx = global_ssl_ctx;
191         end
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                                         cl.start(listener, { 
210                                                 ssl = conntype ~= "tcp" 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                                 end
218                         end
219                 end
220         end
221 end
222
223 function read_version()
224         -- Try to determine version
225         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
226         if version_file then
227                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
228                 version_file:close();
229                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
230                         prosody.version = "hg:"..prosody.version;
231                 end
232         else
233                 prosody.version = "unknown";
234         end
235 end
236
237 function load_secondary_libraries()
238         --- Load and initialise core modules
239         require "util.import"
240         require "core.xmlhandlers"
241         require "core.rostermanager"
242         require "core.eventmanager"
243         require "core.hostmanager"
244         require "core.modulemanager"
245         require "core.usermanager"
246         require "core.sessionmanager"
247         require "core.stanza_router"
248
249         require "net.http"
250         
251         require "util.array"
252         require "util.datetime"
253         require "util.iterators"
254         require "util.timer"
255         require "util.helpers"
256         
257         pcall(require, "util.signal") -- Not on Windows
258         
259         -- Commented to protect us from 
260         -- the second kind of people
261         --[[ 
262         pcall(require, "remdebug.engine");
263         if remdebug then remdebug.engine.start() end
264         ]]
265
266         require "net.connlisteners";
267         
268         require "util.stanza"
269         require "util.jid"
270 end
271
272 function init_data_store()
273         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
274         require "util.datamanager".set_data_path(data_path);
275         require "util.datamanager".add_callback(function(username, host, datastore, data)
276                 if config.get(host, "core", "anonymous_login") then
277                         return false;
278                 end
279                 return username, host, datastore, data;
280         end);
281 end
282
283 function prepare_to_start()
284         log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
285         -- Signal to modules that we are ready to start
286         eventmanager.fire_event("server-starting");
287         prosody.events.fire_event("server-starting");
288
289         -- start listening on sockets
290         if config.get("*", "core", "ports") then
291                 prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
292                 if config.get("*", "core", "ssl_ports") then
293                         prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
294                 end
295         else
296                 prosody.net_activate_ports("c2s", "xmppclient", {5222});
297                 prosody.net_activate_ports("s2s", "xmppserver", {5269});
298                 prosody.net_activate_ports("component", "xmppcomponent", {}, "tcp");
299                 prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
300         end
301
302         prosody.start_time = os.time();
303 end     
304
305 function init_global_protection()
306         -- Catch global accesses
307         local locked_globals_mt = {
308                 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
309                 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
310         };
311                 
312         function prosody.unlock_globals()
313                 setmetatable(_G, nil);
314         end
315         
316         function prosody.lock_globals()
317                 setmetatable(_G, locked_globals_mt);
318         end
319
320         -- And lock now...
321         prosody.lock_globals();
322 end
323
324 function loop()
325         -- Error handler for errors that make it this far
326         local function catch_uncaught_error(err)
327                 if type(err) == "string" and err:match("interrupted!$") then
328                         return "quitting";
329                 end
330                 
331                 log("error", "Top-level error, please report:\n%s", tostring(err));
332                 local traceback = debug.traceback("", 2);
333                 if traceback then
334                         log("error", "%s", traceback);
335                 end
336                 
337                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
338         end
339         
340         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
341                 socket.sleep(0.2);
342         end
343 end
344
345 function cleanup()
346         log("info", "Shutdown status: Cleaning up");
347         prosody.events.fire_event("server-cleanup");
348         
349         -- Ok, we're quitting I know, but we
350         -- need to do some tidying before we go :)
351         server.setquitting(false);
352         
353         log("info", "Shutdown status: Closing all active sessions");
354         for hostname, host in pairs(hosts) do
355                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
356                 if host.sessions then
357                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
358                         if prosody.shutdown_reason then
359                                 reason.text = reason.text..": "..prosody.shutdown_reason;
360                         end
361                         for username, user in pairs(host.sessions) do
362                                 for resource, session in pairs(user.sessions) do
363                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
364                                         session:close(reason);
365                                 end
366                         end
367                 end
368         
369                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
370                 if host.s2sout then
371                         for remotehost, session in pairs(host.s2sout) do
372                                 if session.close then
373                                         session:close("system-shutdown");
374                                 else
375                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
376                                 end
377                         end
378                 end
379         end
380
381         log("info", "Shutdown status: Closing all server connections");
382         server.closeall();
383         
384         server.setquitting(true);
385 end
386
387 -- Are you ready? :)
388 read_config();
389 load_libraries();
390 init_global_state();
391 read_version();
392 log("info", "Hello and welcome to Prosody version %s", prosody.version);
393 load_secondary_libraries();
394 init_data_store();
395 init_global_protection();
396 prepare_to_start();
397
398 eventmanager.fire_event("server-started");
399 prosody.events.fire_event("server-started");
400
401 loop();
402
403 log("info", "Shutting down...");
404 cleanup();
405 eventmanager.fire_event("server-stopped");
406 prosody.events.fire_event("server-stopped");
407 log("info", "Shutdown complete");
408