prosody: Show friendly error when no config present for legacy SSL 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                                                 elseif err == "no ssl context" then
232                                                         friendly_message = "there is no 'ssl' config under Host \"*\" which is "
233                                                                 .."require for legacy SSL ports";
234                                                 end
235                                                 log("error", "Failed to open server port %d, %s", port, friendly_message);
236                                         end
237                                 end
238                         end
239                 end
240         end
241 end
242
243 function read_version()
244         -- Try to determine version
245         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
246         if version_file then
247                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
248                 version_file:close();
249                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
250                         prosody.version = "hg:"..prosody.version;
251                 end
252         else
253                 prosody.version = "unknown";
254         end
255 end
256
257 function load_secondary_libraries()
258         --- Load and initialise core modules
259         require "util.import"
260         require "core.xmlhandlers"
261         require "core.rostermanager"
262         require "core.eventmanager"
263         require "core.hostmanager"
264         require "core.modulemanager"
265         require "core.usermanager"
266         require "core.sessionmanager"
267         require "core.stanza_router"
268
269         require "net.http"
270         
271         require "util.array"
272         require "util.datetime"
273         require "util.iterators"
274         require "util.timer"
275         require "util.helpers"
276         
277         pcall(require, "util.signal") -- Not on Windows
278         
279         -- Commented to protect us from 
280         -- the second kind of people
281         --[[ 
282         pcall(require, "remdebug.engine");
283         if remdebug then remdebug.engine.start() end
284         ]]
285
286         require "net.connlisteners";
287         
288         require "util.stanza"
289         require "util.jid"
290 end
291
292 function init_data_store()
293         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
294         require "util.datamanager".set_data_path(data_path);
295         require "util.datamanager".add_callback(function(username, host, datastore, data)
296                 if config.get(host, "core", "anonymous_login") then
297                         return false;
298                 end
299                 return username, host, datastore, data;
300         end);
301 end
302
303 function prepare_to_start()
304         log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
305         -- Signal to modules that we are ready to start
306         eventmanager.fire_event("server-starting");
307         prosody.events.fire_event("server-starting");
308
309         -- start listening on sockets
310         if config.get("*", "core", "ports") then
311                 prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
312                 if config.get("*", "core", "ssl_ports") then
313                         prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
314                 end
315         else
316                 prosody.net_activate_ports("c2s", "xmppclient", {5222});
317                 prosody.net_activate_ports("s2s", "xmppserver", {5269});
318                 prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
319                 prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
320         end
321
322         prosody.start_time = os.time();
323 end     
324
325 function init_global_protection()
326         -- Catch global accesses
327         local locked_globals_mt = {
328                 __index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(k).."'", 2)); end;
329                 __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end;
330         };
331                 
332         function prosody.unlock_globals()
333                 setmetatable(_G, nil);
334         end
335         
336         function prosody.lock_globals()
337                 setmetatable(_G, locked_globals_mt);
338         end
339
340         -- And lock now...
341         prosody.lock_globals();
342 end
343
344 function loop()
345         -- Error handler for errors that make it this far
346         local function catch_uncaught_error(err)
347                 if type(err) == "string" and err:match("interrupted!$") then
348                         return "quitting";
349                 end
350                 
351                 log("error", "Top-level error, please report:\n%s", tostring(err));
352                 local traceback = debug.traceback("", 2);
353                 if traceback then
354                         log("error", "%s", traceback);
355                 end
356                 
357                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
358         end
359         
360         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
361                 socket.sleep(0.2);
362         end
363 end
364
365 function cleanup()
366         log("info", "Shutdown status: Cleaning up");
367         prosody.events.fire_event("server-cleanup");
368         
369         -- Ok, we're quitting I know, but we
370         -- need to do some tidying before we go :)
371         server.setquitting(false);
372         
373         log("info", "Shutdown status: Closing all active sessions");
374         for hostname, host in pairs(hosts) do
375                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
376                 if host.sessions then
377                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
378                         if prosody.shutdown_reason then
379                                 reason.text = reason.text..": "..prosody.shutdown_reason;
380                         end
381                         for username, user in pairs(host.sessions) do
382                                 for resource, session in pairs(user.sessions) do
383                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
384                                         session:close(reason);
385                                 end
386                         end
387                 end
388         
389                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
390                 if host.s2sout then
391                         for remotehost, session in pairs(host.s2sout) do
392                                 if session.close then
393                                         session:close("system-shutdown");
394                                 else
395                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
396                                 end
397                         end
398                 end
399         end
400
401         log("info", "Shutdown status: Closing all server connections");
402         server.closeall();
403         
404         server.setquitting(true);
405 end
406
407 -- Are you ready? :)
408 read_config();
409 load_libraries();
410 init_global_state();
411 read_version();
412 log("info", "Hello and welcome to Prosody version %s", prosody.version);
413 load_secondary_libraries();
414 init_data_store();
415 init_global_protection();
416 prepare_to_start();
417
418 eventmanager.fire_event("server-started");
419 prosody.events.fire_event("server-started");
420
421 loop();
422
423 log("info", "Shutting down...");
424 cleanup();
425 eventmanager.fire_event("server-stopped");
426 prosody.events.fire_event("server-stopped");
427 log("info", "Shutdown complete");
428