util.helpers: New util library to aid with debugging, etc.
[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=nil;
13 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR");
14 CFG_PLUGINDIR=nil;
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         
97         -- Function to reload the config file
98         function prosody.reload_config()
99                 log("info", "Reloading configuration file");
100                 prosody.events.fire_event("reloading-config");
101                 local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
102                 if not ok then
103                         if level == "parser" then
104                                 log("error", "There was an error parsing the configuration file: %s", tostring(err));
105                         elseif level == "file" then
106                                 log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
107                         end
108                 end
109         end
110
111         -- Function to reopen logfiles
112         function prosody.reopen_logfiles()
113                 log("info", "Re-opening log files");
114                 eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
115                 prosody.events.fire_event("reopen-log-files");
116         end
117
118         -- Function to initiate prosody shutdown
119         function prosody.shutdown(reason)
120                 log("info", "Shutting down: %s", reason or "unknown reason");
121                 prosody.events.fire_event("server-stopping", {reason = reason});
122                 server.setquitting(true);
123         end
124 end
125
126 function read_version()
127         -- Try to determine version
128         local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
129         if version_file then
130                 prosody.version = version_file:read("*a"):gsub("%s*$", "");
131                 version_file:close();
132                 if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
133                         prosody.version = "hg:"..prosody.version;
134                 end
135         else
136                 prosody.version = "unknown";
137         end
138 end
139
140 function load_secondary_libraries()
141         --- Load and initialise core modules
142         require "util.import"
143         require "core.xmlhandlers"
144         require "core.rostermanager"
145         require "core.eventmanager"
146         require "core.hostmanager"
147         require "core.modulemanager"
148         require "core.usermanager"
149         require "core.sessionmanager"
150         require "core.stanza_router"
151
152         require "util.array"
153         require "util.iterators"
154         require "util.timer"
155
156         -- Commented to protect us from 
157         -- the second kind of people
158         --[[ 
159         pcall(require, "remdebug.engine");
160         if remdebug then remdebug.engine.start() end
161         ]]
162
163         require "net.connlisteners";
164         
165         require "util.stanza"
166         require "util.jid"
167 end
168
169 function init_data_store()
170         local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
171         require "util.datamanager".set_data_path(data_path);
172         require "util.datamanager".add_callback(function(username, host, datastore, data)
173                 if config.get(host, "core", "anonymous_login") then
174                         return false;
175                 end
176                 return username, host, datastore, data;
177         end);
178 end
179
180 function prepare_to_start()
181         -- Signal to modules that we are ready to start
182         eventmanager.fire_event("server-starting");
183         prosody.events.fire_event("server-starting");
184
185         -- Load SSL settings from config, and create a ctx table
186         local global_ssl_ctx = 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"; };
189                 setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
190         end
191
192         local cl = require "net.connlisteners";
193         -- start listening on sockets
194         function net_activate_ports(option, listener, default, conntype)
195                 local ports = config.get("*", "core", option.."_ports") or default;
196                 if type(ports) == "number" then ports = {ports} end;
197                 
198                 if type(ports) ~= "table" then
199                         log("error", "core."..option.." is not a table");
200                 else
201                         for _, port in ipairs(ports) do
202                                 if type(port) ~= "number" then
203                                         log("error", "Non-numeric "..option.."_ports: "..tostring(port));
204                                 else
205                                         cl.start(listener, { 
206                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
207                                                 port = port,
208                                                 interface = config.get("*", "core", option.."_interface") 
209                                                         or cl.get(listener).default_interface 
210                                                         or config.get("*", "core", "interface"),
211                                                 type = conntype
212                                         });
213                                 end
214                         end
215                 end
216         end
217
218         net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
219         net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
220         net_activate_ports("component", "xmppcomponent", {}, "tcp");
221         net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
222         
223         if cl.get("console") then
224                 cl.start("console", { interface = config.get("*", "core", "console_interface") or "127.0.0.1" })
225         end
226
227         prosody.start_time = os.time();
228 end     
229
230 function init_global_protection()
231         -- Catch global accesses --
232         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 }
233                 
234         function prosody.unlock_globals()
235                 setmetatable(_G, nil);
236         end
237         
238         function prosody.lock_globals()
239                 setmetatable(_G, locked_globals_mt);
240         end
241
242         -- And lock now...
243         prosody.lock_globals();
244 end
245
246 function loop()
247         -- Error handler for errors that make it this far
248         local function catch_uncaught_error(err)
249                 if err:match("%d*: interrupted!$") then
250                         return "quitting";
251                 end
252                 
253                 log("error", "Top-level error, please report:\n%s", tostring(err));
254                 local traceback = debug.traceback("", 2);
255                 if traceback then
256                         log("error", "%s", traceback);
257                 end
258                 
259                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
260         end
261         
262         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
263                 socket.sleep(0.2);
264         end
265 end
266
267 function cleanup()
268         log("info", "Shutdown status: Cleaning up");
269         prosody.events.fire_event("server-cleanup");
270         
271         -- Ok, we're quitting I know, but we
272         -- need to do some tidying before we go :)
273         server.setquitting(false);
274         
275         log("info", "Shutdown status: Closing all active sessions");
276         for hostname, host in pairs(hosts) do
277                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
278                 if host.sessions then
279                         for username, user in pairs(host.sessions) do
280                                 for resource, session in pairs(user.sessions) do
281                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
282                                         session:close("system-shutdown");
283                                 end
284                         end
285                 end
286         
287                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
288                 if host.s2sout then
289                         for remotehost, session in pairs(host.s2sout) do
290                                 if session.close then
291                                         session:close("system-shutdown");
292                                 else
293                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
294                                 end
295                         end
296                 end
297         end
298
299         log("info", "Shutdown status: Closing all server connections");
300         server.closeall();
301         
302         server.setquitting(true);
303 end
304
305 read_config();
306 load_libraries();
307 init_global_state();
308 read_version();
309 log("info", "Hello and welcome to Prosody version %s", prosody.version);
310 load_secondary_libraries();
311 init_data_store();
312 prepare_to_start();
313 init_global_protection();
314
315 eventmanager.fire_event("server-started");
316 prosody.events.fire_event("server-started");
317
318 loop();
319
320 log("info", "Shutting down...");
321 cleanup();
322 eventmanager.fire_event("server-stopped");
323 prosody.events.fire_event("server-stopped");
324 log("info", "Shutdown complete");
325