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