e0d9f7681c8f7a8cd62bff3a56738341463f9941
[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                 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                 local ports = config.get("*", "core", option.."_ports") or default;
199                 if type(ports) == "number" then ports = {ports} end;
200                 
201                 if type(ports) ~= "table" then
202                         log("error", "core."..option.." is not a table");
203                 else
204                         for _, port in ipairs(ports) do
205                                 if type(port) ~= "number" then
206                                         log("error", "Non-numeric "..option.."_ports: "..tostring(port));
207                                 else
208                                         cl.start(listener, { 
209                                                 ssl = conntype ~= "tcp" and global_ssl_ctx,
210                                                 port = port,
211                                                 interface = config.get("*", "core", option.."_interface") 
212                                                         or cl.get(listener).default_interface 
213                                                         or config.get("*", "core", "interface"),
214                                                 type = conntype
215                                         });
216                                 end
217                         end
218                 end
219         end
220
221         net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
222         net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
223         net_activate_ports("component", "xmppcomponent", {}, "tcp");
224         net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
225         
226         if cl.get("console") then
227                 cl.start("console", { interface = config.get("*", "core", "console_interface") or "127.0.0.1" })
228         end
229
230         prosody.start_time = os.time();
231 end     
232
233 function init_global_protection()
234         -- Catch global accesses --
235         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 }
236                 
237         function prosody.unlock_globals()
238                 setmetatable(_G, nil);
239         end
240         
241         function prosody.lock_globals()
242                 setmetatable(_G, locked_globals_mt);
243         end
244
245         -- And lock now...
246         prosody.lock_globals();
247 end
248
249 function loop()
250         -- Error handler for errors that make it this far
251         local function catch_uncaught_error(err)
252                 if err:match("%d*: interrupted!$") then
253                         return "quitting";
254                 end
255                 
256                 log("error", "Top-level error, please report:\n%s", tostring(err));
257                 local traceback = debug.traceback("", 2);
258                 if traceback then
259                         log("error", "%s", traceback);
260                 end
261                 
262                 prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
263         end
264         
265         while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
266                 socket.sleep(0.2);
267         end
268 end
269
270 function cleanup()
271         log("info", "Shutdown status: Cleaning up");
272         prosody.events.fire_event("server-cleanup");
273         
274         -- Ok, we're quitting I know, but we
275         -- need to do some tidying before we go :)
276         server.setquitting(false);
277         
278         log("info", "Shutdown status: Closing all active sessions");
279         for hostname, host in pairs(hosts) do
280                 log("debug", "Shutdown status: Closing client connections for %s", hostname)
281                 if host.sessions then
282                         local reason = { condition = "system-shutdown", text = "Server is shutting down" };
283                         if prosody.shutdown_reason then
284                                 reason.text = reason.text..": "..prosody.shutdown_reason;
285                         end
286                         for username, user in pairs(host.sessions) do
287                                 for resource, session in pairs(user.sessions) do
288                                         log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
289                                         session:close(reason);
290                                 end
291                         end
292                 end
293         
294                 log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
295                 if host.s2sout then
296                         for remotehost, session in pairs(host.s2sout) do
297                                 if session.close then
298                                         session:close("system-shutdown");
299                                 else
300                                         log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
301                                 end
302                         end
303                 end
304         end
305
306         log("info", "Shutdown status: Closing all server connections");
307         server.closeall();
308         
309         server.setquitting(true);
310 end
311
312 read_config();
313 load_libraries();
314 init_global_state();
315 read_version();
316 log("info", "Hello and welcome to Prosody version %s", prosody.version);
317 load_secondary_libraries();
318 init_data_store();
319 prepare_to_start();
320 init_global_protection();
321
322 eventmanager.fire_event("server-started");
323 prosody.events.fire_event("server-started");
324
325 loop();
326
327 log("info", "Shutting down...");
328 cleanup();
329 eventmanager.fire_event("server-stopped");
330 prosody.events.fire_event("server-stopped");
331 log("info", "Shutdown complete");
332