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