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