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