mod_console: Update for new net.server API
[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
157         -- Load SSL settings from config, and create a ctx table
158         local global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
159         if global_ssl_ctx then
160                 local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
161                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
162         end
163
164         local cl = require "net.connlisteners";
165         function prosody.net_activate_ports(option, listener, default, conntype)
166                 conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
167                 if not cl.get(listener) then return; end
168                 local ports = config.get("*", "core", option.."_ports") or default;
169                 if type(ports) == "number" then ports = {ports} end;
170                 
171                 if type(ports) ~= "table" then
172                         log("error", "core."..option.." is not a table");
173                 else
174                         for _, port in ipairs(ports) do
175                                 if type(port) ~= "number" then
176                                         log("error", "Non-numeric "..option.."_ports: "..tostring(port));
177                                 else
178                                         cl.start(listener, { 
179                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
180                                                 port = port,
181                                                 interface = config.get("*", "core", option.."_interface") 
182                                                         or cl.get(listener).default_interface 
183                                                         or config.get("*", "core", "interface"),
184                                                 type = conntype
185                                         });
186                                 end
187                         end
188                 end
189         end
190 end
191
192 function read_version()
193         -- Try to determine version
194         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
195         if version_file then
196                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
197                 version_file:close();
198                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
199                         prosody.version = "hg:"..prosody.version;
200                 end
201         else
202                 prosody.version = "unknown";
203         end
204 end
205
206 function load_secondary_libraries()
207         --- Load and initialise core modules
208         require "util.import"
209         require "core.xmlhandlers"
210         require "core.rostermanager"
211         require "core.eventmanager"
212         require "core.hostmanager"
213         require "core.modulemanager"
214         require "core.usermanager"
215         require "core.sessionmanager"
216         require "core.stanza_router"
217
218         require "net.http"
219         
220         require "util.array"
221         require "util.datetime"
222         require "util.iterators"
223         require "util.timer"
224         require "util.helpers"
225         
226         pcall(require, "util.signal") -- Not on Windows
227         
228         -- Commented to protect us from 
229         -- the second kind of people
230         --[[ 
231         pcall(require, "remdebug.engine");
232         if remdebug then remdebug.engine.start() end
233         ]]
234
235         require "net.connlisteners";
236         
237         require "util.stanza"
238         require "util.jid"
239 end
240
241 function init_data_store()
242         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
243         require "util.datamanager".set_data_path(data_path);
244         require "util.datamanager".add_callback(function(username, host, datastore, data)
245                 if config.get(host, "core", "anonymous_login") then
246                         return false;
247                 end
248                 return username, host, datastore, data;
249         end);
250 end
251
252 function prepare_to_start()
253         -- Signal to modules that we are ready to start
254         eventmanager.fire_event("server-starting");
255         prosody.events.fire_event("server-starting");
256
257         -- start listening on sockets
258         prosody.net_activate_ports("c2s", "xmppclient", {5222});
259         prosody.net_activate_ports("s2s", "xmppserver", {5269});
260         prosody.net_activate_ports("component", "xmppcomponent", {}, "tcp");
261         prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
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