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