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