6ff7743dff7411f3a8ef498ed38b278a25a365be
[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 -- Required to be able to find packages installed with luarocks
36 pcall(require, "luarocks.require");
37
38 -- Replace require() with one that doesn't pollute _G, required
39 -- for neat sandboxing of modules
40 do
41         local _realG = _G;
42         local _real_require = require;
43         function require(...)
44                 local curr_env = getfenv(2);
45                 local curr_env_mt = getmetatable(getfenv(2));
46                 local _realG_mt = getmetatable(_realG);
47                 if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
48                         local old_newindex
49                         old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
50                         local ret = _real_require(...);
51                         _realG_mt.__newindex = old_newindex;
52                         return ret;
53                 end
54                 return _real_require(...);
55         end
56 end
57
58 -- Load the config-parsing module
59 config = require "core.configmanager"
60
61 -- -- -- --
62 -- Define the functions we call during startup, the 
63 -- actual startup happens right at the end, where these
64 -- functions get called
65
66 function read_config()
67         -- TODO: Check for other formats when we add support for them
68         -- Use lfs? Make a new conf/ dir?
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                 table.insert(filenames, (CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
79         end
80         for _,_filename in ipairs(filenames) do
81                 filename = _filename;
82                 local file = io.open(filename);
83                 if file then
84                         file:close();
85                         CFG_CONFIGDIR = filename:match("^(.*)[\\/][^\\/]*$");
86                         break;
87                 end
88         end
89         local ok, level, err = config.load(filename);
90         if not ok then
91                 print("\n");
92                 print("**************************");
93                 if level == "parser" then
94                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
95                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
96                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
97                         print("");
98                 elseif level == "file" then
99                         print("Prosody was unable to find the configuration file.");
100                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
101                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
102                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
103                 end
104                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
105                 print("Good luck!");
106                 print("**************************");
107                 print("");
108                 os.exit(1);
109         end
110 end
111
112 function load_libraries()
113         -- Initialize logging
114         require "core.loggingmanager"
115         
116         -- Check runtime dependencies
117         require "util.dependencies"
118         
119         -- Load socket framework
120         server = require "net.server"
121 end     
122
123 function init_global_state()
124         bare_sessions = {};
125         full_sessions = {};
126         hosts = {};
127
128         -- Global 'prosody' object
129         prosody = {};
130         local prosody = prosody;
131         
132         prosody.bare_sessions = bare_sessions;
133         prosody.full_sessions = full_sessions;
134         prosody.hosts = hosts;
135         
136         prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
137                           plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
138         
139         prosody.arg = _G.arg;
140
141         prosody.events = require "util.events".new();
142         
143         prosody.platform = "unknown";
144         if os.getenv("WINDIR") then
145                 prosody.platform = "windows";
146         elseif package.config:sub(1,1) == "/" then
147                 prosody.platform = "posix";
148         end
149         
150         prosody.installed = nil;
151         if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
152                 prosody.installed = true;
153         end
154         
155         -- Function to reload the config file
156         function prosody.reload_config()
157                 log("info", "Reloading configuration file");
158                 prosody.events.fire_event("reloading-config");
159                 local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
160                 if not ok then
161                         if level == "parser" then
162                                 log("error", "There was an error parsing the configuration file: %s", tostring(err));
163                         elseif level == "file" then
164                                 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
165                         end
166                 end
167                 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
168         end
169
170         -- Function to reopen logfiles
171         function prosody.reopen_logfiles()
172                 log("info", "Re-opening log files");
173                 eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
174                 prosody.events.fire_event("reopen-log-files");
175         end
176
177         -- Function to initiate prosody shutdown
178         function prosody.shutdown(reason)
179                 log("info", "Shutting down: %s", reason or "unknown reason");
180                 prosody.shutdown_reason = reason;
181                 prosody.events.fire_event("server-stopping", {reason = reason});
182                 server.setquitting(true);
183         end
184
185         -- Load SSL settings from config, and create a ctx table
186         local global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
187         if global_ssl_ctx then
188                 local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2" };
189                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
190                 prosody.global_ssl_ctx = global_ssl_ctx;
191         end
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                                 if type(port) ~= "number" then
206                                         log("error", "Non-numeric "..ports_option..": "..tostring(port));
207                                 else
208                                         cl.start(listener, { 
209                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
210                                                 port = port,
211                                                 interface = 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", {}, "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