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