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