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