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