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