e7457627ad6098e34648c756d41d57034b94e53c
[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
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 "util.array"
166         require "util.iterators"
167         require "util.timer"
168         require "util.helpers"
169         
170         -- Commented to protect us from 
171         -- the second kind of people
172         --[[ 
173         pcall(require, "remdebug.engine");
174         if remdebug then remdebug.engine.start() end
175         ]]
176
177         require "net.connlisteners";
178         
179         require "util.stanza"
180         require "util.jid"
181 end
182
183 function init_data_store()
184         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
185         require "util.datamanager".set_data_path(data_path);
186         require "util.datamanager".add_callback(function(username, host, datastore, data)
187                 if config.get(host, "core", "anonymous_login") then
188                         return false;
189                 end
190                 return username, host, datastore, data;
191         end);
192 end
193
194 function prepare_to_start()
195         -- Signal to modules that we are ready to start
196         eventmanager.fire_event("server-starting");
197         prosody.events.fire_event("server-starting");
198
199         -- Load SSL settings from config, and create a ctx table
200         local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
201         if global_ssl_ctx then
202                 local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
203                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
204         end
205
206         local cl = require "net.connlisteners";
207         -- start listening on sockets
208         function net_activate_ports(option, listener, default, conntype)
209                 if not cl.get(listener) then return; end
210                 local ports = config.get("*", "core", option.."_ports") or default;
211                 if type(ports) == "number" then ports = {ports} end;
212                 
213                 if type(ports) ~= "table" then
214                         log("error", "core."..option.." is not a table");
215                 else
216                         for _, port in ipairs(ports) do
217                                 if type(port) ~= "number" then
218                                         log("error", "Non-numeric "..option.."_ports: "..tostring(port));
219                                 else
220                                         cl.start(listener, { 
221                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
222                                                 port = port,
223                                                 interface = config.get("*", "core", option.."_interface") 
224                                                         or cl.get(listener).default_interface 
225                                                         or config.get("*", "core", "interface"),
226                                                 type = conntype
227                                         });
228                                 end
229                         end
230                 end
231         end
232
233         net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
234         net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
235         net_activate_ports("component", "xmppcomponent", {}, "tcp");
236         net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
237         net_activate_ports("console", "console", {5582}, "tcp");
238
239         prosody.start_time = os.time();
240 end     
241
242 function init_global_protection()
243         -- Catch global accesses --
244         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 }
245                 
246         function prosody.unlock_globals()
247                 setmetatable(_G, nil);
248         end
249         
250         function prosody.lock_globals()
251                 setmetatable(_G, locked_globals_mt);
252         end
253
254         -- And lock now...
255         prosody.lock_globals();
256 end
257
258 function loop()
259         -- Error handler for errors that make it this far
260         local function catch_uncaught_error(err)
261                 if err:match("%d*: interrupted!$") then
262                         return "quitting";
263                 end
264                 
265                 log("error", "Top-level error, please report:\n%s", tostring(err));
266                 local traceback = debug.traceback("", 2);
267                 if traceback then
268                         log("error", "%s", traceback);
269                 end
270                 
271                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
272         end
273         
274         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
275                 socket.sleep(0.2);
276         end
277 end
278
279 function cleanup()
280         log("info", "Shutdown status: Cleaning up");
281         prosody.events.fire_event("server-cleanup");
282         
283         -- Ok, we're quitting I know, but we
284         -- need to do some tidying before we go :)
285         server.setquitting(false);
286         
287         log("info", "Shutdown status: Closing all active sessions");
288         for hostname, host in pairs(hosts) do
289                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
290                 if host.sessions then
291                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
292                         if prosody.shutdown_reason then
293                                 reason.text = reason.text..": "..prosody.shutdown_reason;
294                         end
295                         for username, user in pairs(host.sessions) do
296                                 for resource, session in pairs(user.sessions) do
297                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
298                                         session:close(reason);
299                                 end
300                         end
301                 end
302         
303                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
304                 if host.s2sout then
305                         for remotehost, session in pairs(host.s2sout) do
306                                 if session.close then
307                                         session:close("system-shutdown");
308                                 else
309                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
310                                 end
311                         end
312                 end
313         end
314
315         log("info", "Shutdown status: Closing all server connections");
316         server.closeall();
317         
318         server.setquitting(true);
319 end
320
321 read_config();
322 load_libraries();
323 init_global_state();
324 read_version();
325 log("info", "Hello and welcome to Prosody version %s", prosody.version);
326 load_secondary_libraries();
327 init_data_store();
328 prepare_to_start();
329 init_global_protection();
330
331 eventmanager.fire_event("server-started");
332 prosody.events.fire_event("server-started");
333
334 loop();
335
336 log("info", "Shutting down...");
337 cleanup();
338 eventmanager.fire_event("server-stopped");
339 prosody.events.fire_event("server-stopped");
340 log("info", "Shutdown complete");
341