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