util.pposix: Add abort() function
[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 ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
62         if not ok then
63                 print("\n");
64                 print("**************************");
65                 if level == "parser" then
66                         print("A problem occured while reading the config file "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
67                         local err_line, err_message = tostring(err):match("%[string .-%]:(%d*): (.*)");
68                         print("Error"..(err_line and (" on line "..err_line) or "")..": "..(err_message or tostring(err)));
69                         print("");
70                 elseif level == "file" then
71                         print("Prosody was unable to find the configuration file.");
72                         print("We looked for: "..(CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
73                         print("A sample config file is included in the Prosody download called prosody.cfg.lua.dist");
74                         print("Copy or rename it to prosody.cfg.lua and edit as necessary.");
75                 end
76                 print("More help on configuring Prosody can be found at http://prosody.im/doc/configure");
77                 print("Good luck!");
78                 print("**************************");
79                 print("");
80                 os.exit(1);
81         end
82 end
83
84 function load_libraries()
85         --- Initialize logging
86         require "core.loggingmanager"
87         
88         --- Check runtime dependencies
89         require "util.dependencies"
90         
91         --- Load socket framework
92         server = require "net.server"
93 end     
94
95 function init_global_state()
96         bare_sessions = {};
97         full_sessions = {};
98         hosts = {};
99
100         -- Global 'prosody' object
101         prosody = {};
102         local prosody = prosody;
103         
104         prosody.bare_sessions = bare_sessions;
105         prosody.full_sessions = full_sessions;
106         prosody.hosts = hosts;
107         
108         prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
109                           plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
110         
111         prosody.arg = _G.arg;
112
113         prosody.events = require "util.events".new();
114         
115         prosody.platform = "unknown";
116         if os.getenv("WINDIR") then
117                 prosody.platform = "windows";
118         elseif package.config:sub(1,1) == "/" then
119                 prosody.platform = "posix";
120         end
121         
122         prosody.installed = nil;
123         if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
124                 prosody.installed = true;
125         end
126         
127         -- Function to reload the config file
128         function prosody.reload_config()
129                 log("info", "Reloading configuration file");
130                 prosody.events.fire_event("reloading-config");
131                 local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
132                 if not ok then
133                         if level == "parser" then
134                                 log("error", "There was an error parsing the configuration file: %s", tostring(err));
135                         elseif level == "file" then
136                                 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
137                         end
138                 end
139                 return ok, (err and tostring(level)..": "..tostring(err)) or nil;
140         end
141
142         -- Function to reopen logfiles
143         function prosody.reopen_logfiles()
144                 log("info", "Re-opening log files");
145                 eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
146                 prosody.events.fire_event("reopen-log-files");
147         end
148
149         -- Function to initiate prosody shutdown
150         function prosody.shutdown(reason)
151                 log("info", "Shutting down: %s", reason or "unknown reason");
152                 prosody.shutdown_reason = reason;
153                 prosody.events.fire_event("server-stopping", {reason = reason});
154                 server.setquitting(true);
155         end
156 end
157
158 function read_version()
159         -- Try to determine version
160         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
161         if version_file then
162                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
163                 version_file:close();
164                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
165                         prosody.version = "hg:"..prosody.version;
166                 end
167         else
168                 prosody.version = "unknown";
169         end
170 end
171
172 function load_secondary_libraries()
173         --- Load and initialise core modules
174         require "util.import"
175         require "core.xmlhandlers"
176         require "core.rostermanager"
177         require "core.eventmanager"
178         require "core.hostmanager"
179         require "core.modulemanager"
180         require "core.usermanager"
181         require "core.sessionmanager"
182         require "core.stanza_router"
183
184         require "net.http"
185         
186         require "util.array"
187         require "util.datetime"
188         require "util.iterators"
189         require "util.timer"
190         require "util.helpers"
191         
192         pcall(require, "util.signal") -- Not on Windows
193         
194         -- Commented to protect us from 
195         -- the second kind of people
196         --[[ 
197         pcall(require, "remdebug.engine");
198         if remdebug then remdebug.engine.start() end
199         ]]
200
201         require "net.connlisteners";
202         
203         require "util.stanza"
204         require "util.jid"
205 end
206
207 function init_data_store()
208         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
209         require "util.datamanager".set_data_path(data_path);
210         require "util.datamanager".add_callback(function(username, host, datastore, data)
211                 if config.get(host, "core", "anonymous_login") then
212                         return false;
213                 end
214                 return username, host, datastore, data;
215         end);
216 end
217
218 function prepare_to_start()
219         -- Signal to modules that we are ready to start
220         eventmanager.fire_event("server-starting");
221         prosody.events.fire_event("server-starting");
222
223         -- Load SSL settings from config, and create a ctx table
224         local global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
225         if global_ssl_ctx then
226                 local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
227                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
228         end
229
230         local cl = require "net.connlisteners";
231         -- start listening on sockets
232         function prosody.net_activate_ports(option, listener, default, conntype)
233                 if not cl.get(listener) then return; end
234                 local ports = config.get("*", "core", option.."_ports") or default;
235                 if type(ports) == "number" then ports = {ports} end;
236                 
237                 if type(ports) ~= "table" then
238                         log("error", "core."..option.." is not a table");
239                 else
240                         for _, port in ipairs(ports) do
241                                 if type(port) ~= "number" then
242                                         log("error", "Non-numeric "..option.."_ports: "..tostring(port));
243                                 else
244                                         cl.start(listener, { 
245                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
246                                                 port = port,
247                                                 interface = config.get("*", "core", option.."_interface") 
248                                                         or cl.get(listener).default_interface 
249                                                         or config.get("*", "core", "interface"),
250                                                 type = conntype
251                                         });
252                                 end
253                         end
254                 end
255         end
256
257         prosody.net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
258         prosody.net_activate_ports("s2s", "xmppserver", {5269}, (global_ssl_ctx and "tls") or "tcp");
259         prosody.net_activate_ports("component", "xmppcomponent", {}, "tcp");
260         prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
261         prosody.net_activate_ports("console", "console", {5582}, "tcp");
262
263         prosody.start_time = os.time();
264 end     
265
266 function init_global_protection()
267         -- Catch global accesses --
268         local locked_globals_mt = { __index = function (t, k) error("Attempt to read a non-existent global '"..k.."'", 2); end, __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end }
269                 
270         function prosody.unlock_globals()
271                 setmetatable(_G, nil);
272         end
273         
274         function prosody.lock_globals()
275                 setmetatable(_G, locked_globals_mt);
276         end
277
278         -- And lock now...
279         prosody.lock_globals();
280 end
281
282 function loop()
283         -- Error handler for errors that make it this far
284         local function catch_uncaught_error(err)
285                 if type(err) == "string" and err:match("%d*: interrupted!$") then
286                         return "quitting";
287                 end
288                 
289                 log("error", "Top-level error, please report:\n%s", tostring(err));
290                 local traceback = debug.traceback("", 2);
291                 if traceback then
292                         log("error", "%s", traceback);
293                 end
294                 
295                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
296         end
297         
298         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
299                 socket.sleep(0.2);
300         end
301 end
302
303 function cleanup()
304         log("info", "Shutdown status: Cleaning up");
305         prosody.events.fire_event("server-cleanup");
306         
307         -- Ok, we're quitting I know, but we
308         -- need to do some tidying before we go :)
309         server.setquitting(false);
310         
311         log("info", "Shutdown status: Closing all active sessions");
312         for hostname, host in pairs(hosts) do
313                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
314                 if host.sessions then
315                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
316                         if prosody.shutdown_reason then
317                                 reason.text = reason.text..": "..prosody.shutdown_reason;
318                         end
319                         for username, user in pairs(host.sessions) do
320                                 for resource, session in pairs(user.sessions) do
321                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
322                                         session:close(reason);
323                                 end
324                         end
325                 end
326         
327                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
328                 if host.s2sout then
329                         for remotehost, session in pairs(host.s2sout) do
330                                 if session.close then
331                                         session:close("system-shutdown");
332                                 else
333                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
334                                 end
335                         end
336                 end
337         end
338
339         log("info", "Shutdown status: Closing all server connections");
340         server.closeall();
341         
342         server.setquitting(true);
343 end
344
345 read_config();
346 load_libraries();
347 init_global_state();
348 read_version();
349 log("info", "Hello and welcome to Prosody version %s", prosody.version);
350 load_secondary_libraries();
351 init_data_store();
352 init_global_protection();
353 prepare_to_start();
354
355 eventmanager.fire_event("server-started");
356 prosody.events.fire_event("server-started");
357
358 loop();
359
360 log("info", "Shutting down...");
361 cleanup();
362 eventmanager.fire_event("server-stopped");
363 prosody.events.fire_event("server-stopped");
364 log("info", "Shutdown complete");
365