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